kordoc 2.2.5 → 2.3.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 (33) hide show
  1. package/README.md +16 -4
  2. package/dist/{chunk-UU2O6D3R.js → chunk-JFTFC2BB.js} +2 -2
  3. package/dist/{chunk-JH5XLWJQ.js.map → chunk-JFTFC2BB.js.map} +1 -1
  4. package/dist/{chunk-5Y2Q3BRW.js → chunk-M3E3C5GS.js} +8 -1
  5. package/dist/chunk-M3E3C5GS.js.map +1 -0
  6. package/dist/{chunk-RQWICKON.js → chunk-OEJJPCMM.js} +369 -73
  7. package/dist/chunk-OEJJPCMM.js.map +1 -0
  8. package/dist/{chunk-JH5XLWJQ.js → chunk-Z7UPTVMX.js} +2 -2
  9. package/dist/{chunk-UU2O6D3R.js.map → chunk-Z7UPTVMX.js.map} +1 -1
  10. package/dist/{chunk-OJ4QR33V.cjs → chunk-ZNJPRRIA.cjs} +2 -2
  11. package/dist/{chunk-OJ4QR33V.cjs.map → chunk-ZNJPRRIA.cjs.map} +1 -1
  12. package/dist/cli.js +7 -4
  13. package/dist/cli.js.map +1 -1
  14. package/dist/{detect-GYK3HKD5.js → detect-I7YIS4Q6.js} +4 -2
  15. package/dist/index.cjs +463 -160
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +4 -2
  18. package/dist/index.d.ts +4 -2
  19. package/dist/index.js +387 -84
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp.js +5 -5
  22. package/dist/{parser-OIRWPKIQ.js → parser-25LF2S2J.js} +45 -42
  23. package/dist/{parser-OIRWPKIQ.js.map → parser-25LF2S2J.js.map} +1 -1
  24. package/dist/{parser-PXD73E4H.js → parser-4LKJXBPP.js} +45 -42
  25. package/dist/{parser-PXD73E4H.js.map → parser-4LKJXBPP.js.map} +1 -1
  26. package/dist/{parser-CYBX5MP4.cjs → parser-KBQZB3QY.cjs} +61 -58
  27. package/dist/{parser-CYBX5MP4.cjs.map → parser-KBQZB3QY.cjs.map} +1 -1
  28. package/dist/{watch-NSBABJ4A.js → watch-GXRBLW3Y.js} +4 -4
  29. package/package.json +2 -2
  30. package/dist/chunk-5Y2Q3BRW.js.map +0 -1
  31. package/dist/chunk-RQWICKON.js.map +0 -1
  32. /package/dist/{detect-GYK3HKD5.js.map → detect-I7YIS4Q6.js.map} +0 -0
  33. /package/dist/{watch-NSBABJ4A.js.map → watch-GXRBLW3Y.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pdf/line-detector.ts","../src/pdf/cluster-detector.ts","../src/pdf/polyfill.ts","../src/pdf/parser.ts"],"sourcesContent":["/**\r\n * PDF 그래픽 명령에서 수평/수직 선을 추출하고,\r\n * 선 교차점(Vertex) 기반으로 테이블 그리드를 구성하는 모듈.\r\n *\r\n * 이 파일의 테이블 감지 알고리즘은 OpenDataLoader PDF의\r\n * TableBorderBuilder / LinesPreprocessingConsumer를 참고하여\r\n * TypeScript로 clean-room 재구현한 것입니다.\r\n *\r\n * v2: Vertex 기반 동적 tolerance, 선 전처리 파이프라인,\r\n * 정밀 병합 셀 감지 (ODL 알고리즘 충실 포팅)\r\n *\r\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\r\n * https://github.com/opendataloader-project/opendataloader-pdf\r\n * Core algorithm concepts from veraPDF-wcag-algs (GPLv3+/MPLv2+)\r\n * This is an independent clean-room reimplementation in TypeScript.\r\n */\r\n\r\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\r\n\r\n// ─── pdfjs-dist v5 DrawOPS ──\r\nconst enum DrawOPS {\r\n moveTo = 0,\r\n lineTo = 1,\r\n curveTo = 2,\r\n quadraticCurveTo = 3,\r\n closePath = 4,\r\n}\r\n\r\n// ─── 타입 ─────────────────────────────────────────────\r\n\r\nexport interface LineSegment {\r\n x1: number; y1: number\r\n x2: number; y2: number\r\n lineWidth: number\r\n}\r\n\r\n/** 선 교차점 (Vertex) — ODL의 핵심 개념 */\r\ninterface Vertex {\r\n x: number\r\n y: number\r\n /** 교차하는 선들의 최대 lineWidth → tolerance 계산에 사용 */\r\n radius: number\r\n}\r\n\r\nexport interface TableGrid {\r\n /** 행 Y 좌표 경계 (위→아래 내림차순) */\r\n rowYs: number[]\r\n /** 열 X 좌표 경계 (좌→우 오름차순) */\r\n colXs: number[]\r\n /** 테이블 바운딩 박스 */\r\n bbox: { x1: number; y1: number; x2: number; y2: number }\r\n /** 그리드 내 교차점 반경 (동적 tolerance용) */\r\n vertexRadius: number\r\n}\r\n\r\nexport interface ExtractedCell {\r\n row: number; col: number\r\n rowSpan: number; colSpan: number\r\n /** 셀 바운딩 박스 */\r\n bbox: { x1: number; y1: number; x2: number; y2: number }\r\n}\r\n\r\n// ─── 상수 ─────────────────────────────────────────────\r\n\r\n/** 수평/수직 판별 허용 오차 (pt) */\r\nconst ORIENTATION_TOL = 2\r\n/** 최소 선 길이 — 짧은 장식선(체크박스 테두리 등) 무시 */\r\nconst MIN_LINE_LENGTH = 15\r\n/** 굵은 선 필터 — ODL: MAX_LINE_WIDTH = 5.0 (배경 채움/장식 사각형 제외) */\r\nconst MAX_LINE_WIDTH = 5.0\r\n/** 두 선이 같은 테이블에 속하는지 판별하는 거리 */\r\nconst CONNECT_TOL = 5\r\n/** 셀 경계 내부 판별 여유 (텍스트 매핑용) */\r\nconst CELL_PADDING = 2\r\n/** 최소 열 폭 (pt) — 이보다 좁은 열은 인접 열과 병합 */\r\nconst MIN_COL_WIDTH = 15\r\n/** 최소 행 높이 (pt) */\r\nconst MIN_ROW_HEIGHT = 6\r\n/** Vertex 기반 좌표 병합 시 radius 배수 — ODL: VERTEX_TABLE_FACTOR */\r\nconst VERTEX_MERGE_FACTOR = 4\r\n/** 좌표 병합 최소 tolerance (pt) — vertexRadius가 작아도 이 값 이하로 내려가지 않음 */\r\nconst MIN_COORD_MERGE_TOL = 8\r\n\r\n// ─── 선 추출 ──────────────────────────────────────────\r\n\r\n/**\r\n * pdfjs operatorList에서 수평/수직 선을 추출.\r\n * constructPath(91) 내의 moveTo→lineTo, rectangle 패턴을 인식.\r\n */\r\nexport function extractLines(\r\n fnArray: Uint32Array | number[],\r\n argsArray: unknown[][],\r\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\r\n const horizontals: LineSegment[] = []\r\n const verticals: LineSegment[] = []\r\n let lineWidth = 1\r\n\r\n let currentPath: Array<{ x1: number; y1: number; x2: number; y2: number }> = []\r\n let pathStartX = 0, pathStartY = 0\r\n let curX = 0, curY = 0\r\n\r\n function pushRectangle(\r\n path: Array<{ x1: number; y1: number; x2: number; y2: number }>,\r\n rx: number, ry: number, rw: number, rh: number,\r\n ) {\r\n if (Math.abs(rh) < ORIENTATION_TOL * 2) {\r\n path.push({ x1: rx, y1: ry + rh / 2, x2: rx + rw, y2: ry + rh / 2 })\r\n } else if (Math.abs(rw) < ORIENTATION_TOL * 2) {\r\n path.push({ x1: rx + rw / 2, y1: ry, x2: rx + rw / 2, y2: ry + rh })\r\n } else {\r\n path.push(\r\n { x1: rx, y1: ry, x2: rx + rw, y2: ry },\r\n { x1: rx + rw, y1: ry, x2: rx + rw, y2: ry + rh },\r\n { x1: rx + rw, y1: ry + rh, x2: rx, y2: ry + rh },\r\n { x1: rx, y1: ry + rh, x2: rx, y2: ry },\r\n )\r\n }\r\n }\r\n\r\n function flushPath(isStroke: boolean) {\r\n if (!isStroke) { currentPath = []; return }\r\n for (const seg of currentPath) {\r\n classifyAndAdd(seg, lineWidth, horizontals, verticals)\r\n }\r\n currentPath = []\r\n }\r\n\r\n for (let i = 0; i < fnArray.length; i++) {\r\n const op = fnArray[i]\r\n const args = argsArray[i]\r\n\r\n switch (op) {\r\n case OPS.setLineWidth:\r\n lineWidth = (args as number[])[0] || 1\r\n break\r\n\r\n case OPS.constructPath: {\r\n const arg0 = args[0]\r\n\r\n if (Array.isArray(arg0)) {\r\n // ── pdfjs-dist v4 형식 ──\r\n const subOps = arg0 as number[]\r\n const coords = (args as [number[], number[]])[1]\r\n let ci = 0\r\n\r\n for (const subOp of subOps) {\r\n if (subOp === OPS.moveTo) {\r\n curX = coords[ci++]; curY = coords[ci++]\r\n pathStartX = curX; pathStartY = curY\r\n } else if (subOp === OPS.lineTo) {\r\n const x2 = coords[ci++], y2 = coords[ci++]\r\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\r\n curX = x2; curY = y2\r\n } else if (subOp === OPS.rectangle) {\r\n const rx = coords[ci++], ry = coords[ci++]\r\n const rw = coords[ci++], rh = coords[ci++]\r\n pushRectangle(currentPath, rx, ry, rw, rh)\r\n } else if (subOp === OPS.closePath) {\r\n if (curX !== pathStartX || curY !== pathStartY) {\r\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\r\n }\r\n curX = pathStartX; curY = pathStartY\r\n } else if (subOp === OPS.curveTo) {\r\n ci += 6\r\n } else if (subOp === OPS.curveTo2 || subOp === OPS.curveTo3) {\r\n ci += 4\r\n }\r\n }\r\n } else {\r\n // ── pdfjs-dist v5 형식 ──\r\n const afterOp = arg0 as number\r\n const dataArr = args[1] as unknown[]\r\n const pathData = dataArr?.[0] as Record<number, number> | undefined\r\n if (pathData && typeof pathData === \"object\") {\r\n const len = Object.keys(pathData).length\r\n let di = 0\r\n while (di < len) {\r\n const drawOp = pathData[di++]\r\n if (drawOp === DrawOPS.moveTo) {\r\n curX = pathData[di++]; curY = pathData[di++]\r\n pathStartX = curX; pathStartY = curY\r\n } else if (drawOp === DrawOPS.lineTo) {\r\n const x2 = pathData[di++], y2 = pathData[di++]\r\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\r\n curX = x2; curY = y2\r\n } else if (drawOp === DrawOPS.curveTo) {\r\n di += 6\r\n } else if (drawOp === DrawOPS.quadraticCurveTo) {\r\n di += 4\r\n } else if (drawOp === DrawOPS.closePath) {\r\n if (curX !== pathStartX || curY !== pathStartY) {\r\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\r\n }\r\n curX = pathStartX; curY = pathStartY\r\n } else {\r\n break\r\n }\r\n }\r\n }\r\n\r\n if (afterOp === OPS.stroke || afterOp === OPS.closeStroke) {\r\n flushPath(true)\r\n } else if (afterOp === OPS.fill || afterOp === OPS.eoFill ||\r\n afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke ||\r\n afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {\r\n flushPath(true)\r\n } else if (afterOp === OPS.endPath) {\r\n flushPath(false)\r\n }\r\n }\r\n break\r\n }\r\n\r\n case OPS.stroke:\r\n case OPS.closeStroke:\r\n flushPath(true)\r\n break\r\n\r\n case OPS.fill:\r\n case OPS.eoFill:\r\n case OPS.fillStroke:\r\n case OPS.eoFillStroke:\r\n case OPS.closeFillStroke:\r\n case OPS.closeEOFillStroke:\r\n flushPath(true)\r\n break\r\n\r\n case OPS.endPath:\r\n flushPath(false)\r\n break\r\n }\r\n }\r\n\r\n return { horizontals, verticals }\r\n}\r\n\r\nfunction classifyAndAdd(\r\n seg: { x1: number; y1: number; x2: number; y2: number },\r\n lineWidth: number,\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n) {\r\n const dx = Math.abs(seg.x2 - seg.x1)\r\n const dy = Math.abs(seg.y2 - seg.y1)\r\n const length = Math.sqrt(dx * dx + dy * dy)\r\n\r\n if (length < MIN_LINE_LENGTH) return\r\n\r\n if (dy <= ORIENTATION_TOL) {\r\n const y = (seg.y1 + seg.y2) / 2\r\n const x1 = Math.min(seg.x1, seg.x2)\r\n const x2 = Math.max(seg.x1, seg.x2)\r\n horizontals.push({ x1, y1: y, x2, y2: y, lineWidth })\r\n } else if (dx <= ORIENTATION_TOL) {\r\n const x = (seg.x1 + seg.x2) / 2\r\n const y1 = Math.min(seg.y1, seg.y2)\r\n const y2 = Math.max(seg.y1, seg.y2)\r\n verticals.push({ x1: x, y1, x2: x, y2, lineWidth })\r\n }\r\n}\r\n\r\n// ─── 선 전처리 파이프라인 (ODL LinesPreprocessingConsumer 포팅) ──\r\n\r\n/**\r\n * 선 전처리: 굵은 선 필터 → 근접 선 병합 → 장식선 필터링\r\n * ODL의 LinesPreprocessingConsumer가 하는 핵심 로직.\r\n */\r\nexport function preprocessLines(\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\r\n // 1. 굵은 선 필터링 (배경 채움 사각형, 장식 테두리 등)\r\n let h = horizontals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\r\n let v = verticals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\r\n\r\n // 2. 근접 평행 선 병합 (인쇄 잔상, 이중선)\r\n h = mergeParallelLines(h, \"h\")\r\n v = mergeParallelLines(v, \"v\")\r\n\r\n return { horizontals: h, verticals: v }\r\n}\r\n\r\n/**\r\n * 근접 평행 선 병합 — 같은 방향의 가까운 선을 하나로 합침.\r\n * 이중선, 인쇄 잔상, PDF 렌더링 미세 차이로 인한 중복 선 제거.\r\n */\r\nfunction mergeParallelLines(lines: LineSegment[], dir: \"h\" | \"v\"): LineSegment[] {\r\n if (lines.length <= 1) return lines\r\n\r\n // 수평선: y로 정렬, 수직선: x로 정렬\r\n const sorted = [...lines].sort((a, b) => {\r\n const posA = dir === \"h\" ? a.y1 : a.x1\r\n const posB = dir === \"h\" ? b.y1 : b.x1\r\n if (Math.abs(posA - posB) > 0.1) return posA - posB\r\n // 같은 위치면 시작 좌표로\r\n return dir === \"h\" ? (a.x1 - b.x1) : (a.y1 - b.y1)\r\n })\r\n\r\n const MERGE_TOL = 3 // 3pt 이내 평행 선 병합\r\n\r\n const result: LineSegment[] = [sorted[0]]\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prev = result[result.length - 1]\r\n const curr = sorted[i]\r\n\r\n const prevPos = dir === \"h\" ? prev.y1 : prev.x1\r\n const currPos = dir === \"h\" ? curr.y1 : curr.x1\r\n\r\n if (Math.abs(prevPos - currPos) <= MERGE_TOL) {\r\n // 범위가 겹치는지 확인\r\n const prevStart = dir === \"h\" ? prev.x1 : prev.y1\r\n const prevEnd = dir === \"h\" ? prev.x2 : prev.y2\r\n const currStart = dir === \"h\" ? curr.x1 : curr.y1\r\n const currEnd = dir === \"h\" ? curr.x2 : curr.y2\r\n\r\n const overlap = Math.min(prevEnd, currEnd) - Math.max(prevStart, currStart)\r\n const minLen = Math.min(prevEnd - prevStart, currEnd - currStart)\r\n\r\n if (overlap > minLen * 0.3) {\r\n // 병합: 범위 확장, lineWidth는 최대값 유지\r\n if (dir === \"h\") {\r\n prev.x1 = Math.min(prev.x1, curr.x1)\r\n prev.x2 = Math.max(prev.x2, curr.x2)\r\n prev.y1 = (prev.y1 + curr.y1) / 2\r\n prev.y2 = prev.y1\r\n } else {\r\n prev.y1 = Math.min(prev.y1, curr.y1)\r\n prev.y2 = Math.max(prev.y2, curr.y2)\r\n prev.x1 = (prev.x1 + curr.x1) / 2\r\n prev.x2 = prev.x1\r\n }\r\n prev.lineWidth = Math.max(prev.lineWidth, curr.lineWidth)\r\n continue\r\n }\r\n }\r\n result.push(curr)\r\n }\r\n return result\r\n}\r\n\r\n// ─── 페이지 경계(클립) 선 필터링 ──────────────────────\r\n\r\nexport function filterPageBorderLines(\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n pageWidth: number,\r\n pageHeight: number,\r\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\r\n const margin = 5\r\n return {\r\n horizontals: horizontals.filter(l =>\r\n !(Math.abs(l.y1) < margin || Math.abs(l.y1 - pageHeight) < margin) ||\r\n (l.x2 - l.x1) < pageWidth * 0.9\r\n ),\r\n verticals: verticals.filter(l =>\r\n !(Math.abs(l.x1) < margin || Math.abs(l.x1 - pageWidth) < margin) ||\r\n (l.y2 - l.y1) < pageHeight * 0.9\r\n ),\r\n }\r\n}\r\n\r\n// ─── Vertex(교차점) 생성 ─────────────────────────────\r\n\r\n/**\r\n * 수평선과 수직선의 교차점(Vertex)을 생성.\r\n * ODL의 TableBorderBuilder.addLine()이 교차점을 자동 생성하는 것과 동일.\r\n * 각 Vertex는 교차하는 선들의 lineWidth로 radius를 계산 → 동적 tolerance.\r\n */\r\nfunction buildVertices(horizontals: LineSegment[], verticals: LineSegment[]): Vertex[] {\r\n const vertices: Vertex[] = []\r\n const tol = CONNECT_TOL\r\n\r\n for (const h of horizontals) {\r\n for (const v of verticals) {\r\n // 수평선의 X범위에 수직선의 X가 포함되고\r\n // 수직선의 Y범위에 수평선의 Y가 포함되면 → 교차\r\n if (v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\r\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol) {\r\n const radius = Math.max(h.lineWidth, v.lineWidth, 1)\r\n vertices.push({ x: v.x1, y: h.y1, radius })\r\n }\r\n }\r\n }\r\n\r\n return vertices\r\n}\r\n\r\n/**\r\n * 근접 Vertex 병합 — 같은 교차점의 미세 위치 차이를 하나로 합침.\r\n */\r\nfunction mergeVertices(vertices: Vertex[]): Vertex[] {\r\n if (vertices.length <= 1) return vertices\r\n\r\n const merged: Vertex[] = []\r\n const used = new Array(vertices.length).fill(false)\r\n\r\n for (let i = 0; i < vertices.length; i++) {\r\n if (used[i]) continue\r\n let sumX = vertices[i].x, sumY = vertices[i].y\r\n let maxRadius = vertices[i].radius\r\n let count = 1\r\n\r\n for (let j = i + 1; j < vertices.length; j++) {\r\n if (used[j]) continue\r\n const mergeTol = VERTEX_MERGE_FACTOR * Math.max(maxRadius, vertices[j].radius)\r\n if (Math.abs(vertices[i].x - vertices[j].x) <= mergeTol &&\r\n Math.abs(vertices[i].y - vertices[j].y) <= mergeTol) {\r\n sumX += vertices[j].x\r\n sumY += vertices[j].y\r\n maxRadius = Math.max(maxRadius, vertices[j].radius)\r\n count++\r\n used[j] = true\r\n }\r\n }\r\n\r\n merged.push({ x: sumX / count, y: sumY / count, radius: maxRadius })\r\n }\r\n\r\n return merged\r\n}\r\n\r\n// ─── 테이블 그리드 구성 (Vertex 기반) ─────────────────\r\n\r\n/**\r\n * 수평/수직 선에서 테이블 그리드를 추출.\r\n * ODL과 동일한 흐름:\r\n * 1. 선 전처리 (preprocessLines — 호출측에서 수행)\r\n * 2. 교차점(Vertex) 생성 + 병합\r\n * 3. 교차하는 선들을 그룹화 (연결 컴포넌트)\r\n * 4. 각 그룹에서 Vertex의 X/Y 좌표를 동적 tolerance로 클러스터링\r\n * 5. 그리드 검증 (최소 열 폭, 최소 행 높이)\r\n */\r\nexport function buildTableGrids(\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n): TableGrid[] {\r\n if (horizontals.length < 2 || verticals.length < 2) return []\r\n\r\n // 1. 교차점 생성\r\n const allVertices = buildVertices(horizontals, verticals)\r\n const vertices = mergeVertices(allVertices)\r\n\r\n if (vertices.length < 4) return [] // 최소 4꼭짓점 필요 (사각형)\r\n\r\n // 전체 vertex의 대표 radius (동적 tolerance)\r\n const globalRadius = vertices.reduce((max, v) => Math.max(max, v.radius), 1)\r\n\r\n // 2. 선들을 교차 관계로 그룹화\r\n const allLines = [\r\n ...horizontals.map((l, i) => ({ ...l, type: \"h\" as const, id: i })),\r\n ...verticals.map((l, i) => ({ ...l, type: \"v\" as const, id: i + horizontals.length })),\r\n ]\r\n\r\n const groups = groupConnectedLines(allLines)\r\n const grids: TableGrid[] = []\r\n\r\n for (const group of groups) {\r\n const hLines = group.filter(l => l.type === \"h\")\r\n const vLines = group.filter(l => l.type === \"v\")\r\n\r\n if (hLines.length < 2 || vLines.length < 2) continue\r\n\r\n // 3. 이 그룹의 Vertex만 수집\r\n let gx1 = Infinity, gy1 = Infinity, gx2 = -Infinity, gy2 = -Infinity\r\n for (const l of vLines) { if (l.x1 < gx1) gx1 = l.x1; if (l.x1 > gx2) gx2 = l.x1 }\r\n for (const l of hLines) { if (l.y1 < gy1) gy1 = l.y1; if (l.y1 > gy2) gy2 = l.y1 }\r\n const groupBbox = {\r\n x1: gx1 - CONNECT_TOL,\r\n y1: gy1 - CONNECT_TOL,\r\n x2: gx2 + CONNECT_TOL,\r\n y2: gy2 + CONNECT_TOL,\r\n }\r\n\r\n const groupVertices = vertices.filter(v =>\r\n v.x >= groupBbox.x1 && v.x <= groupBbox.x2 &&\r\n v.y >= groupBbox.y1 && v.y <= groupBbox.y2\r\n )\r\n\r\n // 그룹 vertex의 대표 radius\r\n const groupRadius = groupVertices.length > 0\r\n ? groupVertices.reduce((max, v) => Math.max(max, v.radius), 1)\r\n : globalRadius\r\n\r\n // 4. Vertex 기반 좌표 클러스터링 (동적 tolerance)\r\n const coordMergeTol = Math.max(VERTEX_MERGE_FACTOR * groupRadius, MIN_COORD_MERGE_TOL)\r\n\r\n // Y좌표: 수평선 y + Vertex y\r\n const rawYs = [\r\n ...hLines.map(l => l.y1),\r\n ...groupVertices.map(v => v.y),\r\n ]\r\n const rowYs = clusterCoordinates(rawYs, coordMergeTol).sort((a, b) => b - a)\r\n\r\n // X좌표: 수직선 x + Vertex x\r\n const rawXs = [\r\n ...vLines.map(l => l.x1),\r\n ...groupVertices.map(v => v.x),\r\n ]\r\n const colXs = clusterCoordinates(rawXs, coordMergeTol).sort((a, b) => a - b)\r\n\r\n if (rowYs.length < 2 || colXs.length < 2) continue\r\n\r\n // 5. 그리드 검증: 최소 열 폭, 최소 행 높이\r\n const validColXs = enforceMinWidth(colXs, MIN_COL_WIDTH)\r\n const validRowYs = enforceMinHeight(rowYs, MIN_ROW_HEIGHT)\r\n\r\n if (validRowYs.length < 2 || validColXs.length < 2) continue\r\n\r\n const bbox = {\r\n x1: validColXs[0], y1: validRowYs[validRowYs.length - 1],\r\n x2: validColXs[validColXs.length - 1], y2: validRowYs[0],\r\n }\r\n\r\n grids.push({ rowYs: validRowYs, colXs: validColXs, bbox, vertexRadius: groupRadius })\r\n }\r\n\r\n return mergeAdjacentGrids(grids)\r\n}\r\n\r\n/** 최소 열 폭 보장 — 너무 좁은 열은 인접 열과 병합 */\r\nfunction enforceMinWidth(colXs: number[], minWidth: number): number[] {\r\n if (colXs.length <= 2) return colXs\r\n const result: number[] = [colXs[0]]\r\n for (let i = 1; i < colXs.length; i++) {\r\n const prevX = result[result.length - 1]\r\n if (colXs[i] - prevX < minWidth && i < colXs.length - 1) {\r\n // 너무 좁으면 스킵 (다음 열과 병합)\r\n continue\r\n }\r\n result.push(colXs[i])\r\n }\r\n return result\r\n}\r\n\r\n/** 최소 행 높이 보장 — 너무 낮은 행은 인접 행과 병합 */\r\nfunction enforceMinHeight(rowYs: number[], minHeight: number): number[] {\r\n if (rowYs.length <= 2) return rowYs\r\n // rowYs는 내림차순 (위→아래)\r\n const result: number[] = [rowYs[0]]\r\n for (let i = 1; i < rowYs.length; i++) {\r\n const prevY = result[result.length - 1]\r\n if (prevY - rowYs[i] < minHeight && i < rowYs.length - 1) {\r\n continue\r\n }\r\n result.push(rowYs[i])\r\n }\r\n return result\r\n}\r\n\r\n/** 같은 열 구조를 가진 인접 그리드를 병합 */\r\nfunction mergeAdjacentGrids(grids: TableGrid[]): TableGrid[] {\r\n if (grids.length <= 1) return grids\r\n const sorted = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\r\n const merged: TableGrid[] = [sorted[0]]\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prev = merged[merged.length - 1]\r\n const curr = sorted[i]\r\n\r\n if (prev.colXs.length === curr.colXs.length) {\r\n const mergeTol = Math.max(VERTEX_MERGE_FACTOR * Math.max(prev.vertexRadius, curr.vertexRadius), 6) * 3\r\n const colMatch = prev.colXs.every((x, ci) => Math.abs(x - curr.colXs[ci]) <= mergeTol)\r\n const verticalGap = prev.bbox.y1 - curr.bbox.y2\r\n if (colMatch && verticalGap >= -CONNECT_TOL && verticalGap <= 20) {\r\n const allRowYs = [...new Set([...prev.rowYs, ...curr.rowYs])].sort((a, b) => b - a)\r\n merged[merged.length - 1] = {\r\n rowYs: allRowYs,\r\n colXs: prev.colXs,\r\n bbox: {\r\n x1: Math.min(prev.bbox.x1, curr.bbox.x1),\r\n y1: Math.min(prev.bbox.y1, curr.bbox.y1),\r\n x2: Math.max(prev.bbox.x2, curr.bbox.x2),\r\n y2: Math.max(prev.bbox.y2, curr.bbox.y2),\r\n },\r\n vertexRadius: Math.max(prev.vertexRadius, curr.vertexRadius),\r\n }\r\n continue\r\n }\r\n }\r\n merged.push(curr)\r\n }\r\n return merged\r\n}\r\n\r\n/** 좌표값 클러스터링 — 동적 tolerance 기반 (ODL의 vertex radius 반영) */\r\nfunction clusterCoordinates(values: number[], tolerance: number): number[] {\r\n if (values.length === 0) return []\r\n const sorted = [...values].sort((a, b) => a - b)\r\n const clusters: { sum: number; count: number }[] = [{ sum: sorted[0], count: 1 }]\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const last = clusters[clusters.length - 1]\r\n const avg = last.sum / last.count\r\n if (Math.abs(sorted[i] - avg) <= tolerance) {\r\n last.sum += sorted[i]\r\n last.count++\r\n } else {\r\n clusters.push({ sum: sorted[i], count: 1 })\r\n }\r\n }\r\n\r\n return clusters.map(c => c.sum / c.count)\r\n}\r\n\r\ntype TypedLine = LineSegment & { type: \"h\" | \"v\"; id: number }\r\n\r\n/** 교차하는 선들을 Union-Find로 그룹화 */\r\nfunction groupConnectedLines(lines: TypedLine[]): TypedLine[][] {\r\n const parent = lines.map((_, i) => i)\r\n\r\n function find(x: number): number {\r\n while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x] }\r\n return x\r\n }\r\n function union(a: number, b: number) {\r\n const ra = find(a), rb = find(b)\r\n if (ra !== rb) parent[ra] = rb\r\n }\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n for (let j = i + 1; j < lines.length; j++) {\r\n if (linesIntersect(lines[i], lines[j])) {\r\n union(i, j)\r\n }\r\n }\r\n }\r\n\r\n const groups = new Map<number, TypedLine[]>()\r\n for (let i = 0; i < lines.length; i++) {\r\n const root = find(i)\r\n if (!groups.has(root)) groups.set(root, [])\r\n groups.get(root)!.push(lines[i])\r\n }\r\n\r\n return [...groups.values()]\r\n}\r\n\r\n/** 수평선과 수직선의 교차 판정 (tolerance 포함) */\r\nfunction linesIntersect(a: TypedLine, b: TypedLine): boolean {\r\n if (a.type === b.type) {\r\n if (a.type === \"h\") {\r\n if (Math.abs(a.y1 - b.y1) > CONNECT_TOL) return false\r\n return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - CONNECT_TOL\r\n } else {\r\n if (Math.abs(a.x1 - b.x1) > CONNECT_TOL) return false\r\n return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - CONNECT_TOL\r\n }\r\n }\r\n\r\n const h = a.type === \"h\" ? a : b\r\n const v = a.type === \"h\" ? b : a\r\n const tol = CONNECT_TOL\r\n\r\n return (\r\n v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\r\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol\r\n )\r\n}\r\n\r\n// ─── 셀 구조 추출 (Vertex 기반 정밀 병합 셀 감지) ─────\r\n\r\n/**\r\n * 테이블 그리드에서 셀 목록을 추출.\r\n * ODL의 createMatrix() 알고리즘:\r\n * - 수직선 존재 여부로 colSpan 감지 (75% 커버 기준)\r\n * - 수평선 존재 여부로 rowSpan 감지 (75% 커버 기준)\r\n * - 우하단→좌상단 propagation으로 병합 셀 정리\r\n * - 중복 행/열 제거\r\n */\r\nexport function extractCells(\r\n grid: TableGrid,\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n): ExtractedCell[] {\r\n const { rowYs, colXs } = grid\r\n const numRows = rowYs.length - 1\r\n const numCols = colXs.length - 1\r\n if (numRows <= 0 || numCols <= 0) return []\r\n\r\n // 경계선 존재 여부를 행렬로 사전 계산\r\n // vBorders[r][c] = colXs[c]에 row r 구간의 수직선이 있는지\r\n const vBorders: boolean[][] = Array.from({ length: numRows },\r\n (_, r) => Array.from({ length: numCols + 1 },\r\n (_, c) => hasVerticalLine(verticals, colXs[c], rowYs[r], rowYs[r + 1], grid.vertexRadius)))\r\n\r\n // hBorders[r][c] = rowYs[r]에 col c 구간의 수평선이 있는지\r\n const hBorders: boolean[][] = Array.from({ length: numRows + 1 },\r\n (_, r) => Array.from({ length: numCols },\r\n (_, c) => hasHorizontalLine(horizontals, rowYs[r], colXs[c], colXs[c + 1], grid.vertexRadius)))\r\n\r\n // 셀이 이미 병합된 셀에 포함되는지 추적\r\n const occupied = Array.from({ length: numRows }, () => Array(numCols).fill(false))\r\n const cells: ExtractedCell[] = []\r\n\r\n for (let r = 0; r < numRows; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n if (occupied[r][c]) continue\r\n\r\n let colSpan = 1\r\n let rowSpan = 1\r\n\r\n // colSpan: 오른쪽 내부 경계에 수직선이 없으면 병합\r\n while (c + colSpan < numCols && !vBorders[r][c + colSpan]) {\r\n // 추가 검증: 확장하려는 영역의 모든 행에서 수직선이 없어야 함\r\n let canExpand = true\r\n for (let dr = 0; dr < rowSpan; dr++) {\r\n if (vBorders[r + dr][c + colSpan]) { canExpand = false; break }\r\n }\r\n if (!canExpand) break\r\n colSpan++\r\n }\r\n\r\n // rowSpan: 아래쪽 내부 경계에 수평선이 없으면 병합\r\n while (r + rowSpan < numRows) {\r\n let hasLine = false\r\n for (let dc = 0; dc < colSpan; dc++) {\r\n if (hBorders[r + rowSpan][c + dc]) { hasLine = true; break }\r\n }\r\n if (hasLine) break\r\n rowSpan++\r\n }\r\n\r\n // 병합 영역 마킹\r\n for (let dr = 0; dr < rowSpan; dr++) {\r\n for (let dc = 0; dc < colSpan; dc++) {\r\n occupied[r + dr][c + dc] = true\r\n }\r\n }\r\n\r\n cells.push({\r\n row: r, col: c, rowSpan, colSpan,\r\n bbox: {\r\n x1: colXs[c], y1: rowYs[r + rowSpan],\r\n x2: colXs[c + colSpan], y2: rowYs[r],\r\n },\r\n })\r\n }\r\n }\r\n\r\n return cells\r\n}\r\n\r\n/**\r\n * 특정 X 위치에 수직선이 Y 범위를 커버하는지 확인.\r\n * v2: 75% 커버 기준 + 동적 tolerance (vertex radius 기반)\r\n */\r\nfunction hasVerticalLine(\r\n verticals: LineSegment[], x: number, topY: number, botY: number, vertexRadius: number,\r\n): boolean {\r\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\r\n for (const v of verticals) {\r\n if (Math.abs(v.x1 - x) <= tol) {\r\n const cellH = Math.abs(topY - botY)\r\n if (cellH < 0.1) continue\r\n const overlapTop = Math.min(v.y2, topY)\r\n const overlapBot = Math.max(v.y1, botY)\r\n const overlap = overlapTop - overlapBot\r\n // 75% 커버 기준 (기존 50% → 병합 셀 내부 단선 오탐 방지)\r\n if (overlap >= cellH * 0.75) return true\r\n }\r\n }\r\n return false\r\n}\r\n\r\n/**\r\n * 특정 Y 위치에 수평선이 X 범위를 커버하는지 확인.\r\n * v2: 75% 커버 기준 + 동적 tolerance\r\n */\r\nfunction hasHorizontalLine(\r\n horizontals: LineSegment[], y: number, leftX: number, rightX: number, vertexRadius: number,\r\n): boolean {\r\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\r\n for (const h of horizontals) {\r\n if (Math.abs(h.y1 - y) <= tol) {\r\n const cellW = Math.abs(rightX - leftX)\r\n if (cellW < 0.1) continue\r\n const overlapLeft = Math.max(h.x1, leftX)\r\n const overlapRight = Math.min(h.x2, rightX)\r\n const overlap = overlapRight - overlapLeft\r\n if (overlap >= cellW * 0.75) return true\r\n }\r\n }\r\n return false\r\n}\r\n\r\n// ─── 텍스트→셀 매핑 ──────────────────────────────────\r\n\r\nexport interface TextItem {\r\n text: string\r\n x: number; y: number; w: number; h: number\r\n fontSize: number; fontName: string\r\n}\r\n\r\n/**\r\n * 텍스트 아이템을 셀에 매핑.\r\n * v2: ODL의 getIntersectionPercent 방식 — 텍스트 bbox와 셀 bbox의 교차 비율로 판별.\r\n * 중심점만 보는 기존 방식보다 정확 (긴 텍스트가 셀 경계를 걸치는 경우 처리).\r\n */\r\nexport function mapTextToCells(\r\n items: TextItem[],\r\n cells: ExtractedCell[],\r\n): Map<ExtractedCell, TextItem[]> {\r\n const result = new Map<ExtractedCell, TextItem[]>()\r\n for (const cell of cells) {\r\n result.set(cell, [])\r\n }\r\n\r\n for (const item of items) {\r\n const pad = CELL_PADDING\r\n\r\n let bestCell: ExtractedCell | null = null\r\n let bestScore = 0\r\n\r\n for (const cell of cells) {\r\n // 텍스트 bbox와 셀 bbox의 교차 영역 계산\r\n const ix1 = Math.max(item.x, cell.bbox.x1 - pad)\r\n const ix2 = Math.min(item.x + item.w, cell.bbox.x2 + pad)\r\n const iy1 = Math.max(item.y, cell.bbox.y1 - pad)\r\n const iy2 = Math.min(item.y + (item.h || item.fontSize), cell.bbox.y2 + pad)\r\n\r\n if (ix1 >= ix2 || iy1 >= iy2) continue\r\n\r\n const intersectArea = (ix2 - ix1) * (iy2 - iy1)\r\n const itemArea = Math.max(item.w, 1) * Math.max(item.h || item.fontSize, 1)\r\n const score = intersectArea / itemArea // ODL의 MIN_CELL_CONTENT_INTERSECTION_PERCENT\r\n\r\n if (score > bestScore) {\r\n bestScore = score\r\n bestCell = cell\r\n }\r\n }\r\n\r\n // 교차 비율 > 0.3이면 셀에 할당 (ODL은 0.6이지만 PDF 텍스트 좌표 오차 고려)\r\n if (bestCell && bestScore > 0.3) {\r\n result.get(bestCell)!.push(item)\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기.\r\n * Y 내림차순 (위→아래) → X 오름차순 (좌→우)\r\n */\r\nexport function cellTextToString(items: TextItem[]): string {\r\n if (items.length === 0) return \"\"\r\n if (items.length === 1) return items[0].text\r\n\r\n // Y좌표로 행 그룹핑 (tolerance: max(3, fontSize*0.6))\r\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\r\n const lines: TextItem[][] = []\r\n let curLine: TextItem[] = [sorted[0]]\r\n let curY = sorted[0].y\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const tol = Math.max(3, Math.min(sorted[i].fontSize, curLine[0].fontSize) * 0.6)\r\n if (Math.abs(sorted[i].y - curY) <= tol) {\r\n curLine.push(sorted[i])\r\n } else {\r\n lines.push(curLine)\r\n curLine = [sorted[i]]\r\n curY = sorted[i].y\r\n }\r\n }\r\n lines.push(curLine)\r\n\r\n // 각 행을 텍스트로 변환 — 좌표 기반 균등배분 감지 포함\r\n const textLines = lines.map(line => {\r\n const s = line.sort((a, b) => a.x - b.x)\r\n if (s.length === 1) return s[0].text\r\n\r\n // 균등배분 구간 감지 (좌표 기반)\r\n const evenSpaced = detectEvenSpacedItems(s)\r\n\r\n let result = s[0].text\r\n for (let j = 1; j < s.length; j++) {\r\n // 균등배분 구간이면 무조건 공백 없이 합침\r\n if (evenSpaced[j]) {\r\n result += s[j].text\r\n continue\r\n }\r\n\r\n const gap = s[j].x - (s[j - 1].x + s[j - 1].w)\r\n const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2\r\n const prevIsKorean = /[가-힣]$/.test(result)\r\n const currIsKorean = /^[가-힣]/.test(s[j].text)\r\n if (gap < avgFs * 0.15) {\r\n result += s[j].text\r\n } else if (gap < avgFs * 0.35 && (prevIsKorean || currIsKorean)) {\r\n result += s[j].text\r\n } else {\r\n result += \" \" + s[j].text\r\n }\r\n }\r\n return result\r\n })\r\n\r\n return mergeCellTextLines(textLines)\r\n}\r\n\r\n/**\r\n * 좌표 기반 균등배분 감지 — TextItem 배열에서 한글 1~2자 아이템이\r\n * 일정 간격으로 3개+ 연속되면 균등배분으로 판단.\r\n * ODL TextLineProcessor의 핵심 로직을 좌표 기반으로 구현.\r\n */\r\nfunction detectEvenSpacedItems(items: TextItem[]): boolean[] {\r\n const result = new Array(items.length).fill(false)\r\n if (items.length < 3) return result\r\n\r\n let runStart = -1\r\n for (let i = 0; i < items.length; i++) {\r\n // 균등배분 = 한글 1자 개별 배치. 2자 단어는 균등배분이 아니라 실제 단어.\r\n const isShortKorean = /^[가-힣]{1}$/.test(items[i].text) || /^[\\d]{1}$/.test(items[i].text)\r\n\r\n // 이전 아이템과의 갭이 fontSize*3+ 이면 run 끊기 (다른 영역)\r\n if (isShortKorean && runStart >= 0 && i > 0) {\r\n const gap = items[i].x - (items[i - 1].x + items[i - 1].w)\r\n const maxRunGap = Math.max(items[i].fontSize * 3, 30)\r\n if (gap > maxRunGap) {\r\n if (i - runStart >= 3) markEvenRun(items, result, runStart, i)\r\n runStart = i\r\n continue\r\n }\r\n }\r\n\r\n if (isShortKorean) {\r\n if (runStart < 0) runStart = i\r\n } else {\r\n if (runStart >= 0 && i - runStart >= 3) {\r\n markEvenRun(items, result, runStart, i)\r\n }\r\n runStart = -1\r\n }\r\n }\r\n if (runStart >= 0 && items.length - runStart >= 3) {\r\n markEvenRun(items, result, runStart, items.length)\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction markEvenRun(items: TextItem[], result: boolean[], start: number, end: number): void {\r\n const gaps: number[] = []\r\n for (let i = start + 1; i < end; i++) {\r\n gaps.push(items[i].x - (items[i - 1].x + items[i - 1].w))\r\n }\r\n const posGaps = gaps.filter(g => g > 0)\r\n if (posGaps.length < 2) return\r\n\r\n let minGap = Infinity, maxGap = -Infinity\r\n for (const g of posGaps) { if (g < minGap) minGap = g; if (g > maxGap) maxGap = g }\r\n const avgFs = items[start].fontSize\r\n\r\n // 간격이 fontSize의 0.1~3배 사이이고, 최대/최소 비율 3배 이내\r\n if (minGap >= avgFs * 0.1 && maxGap <= avgFs * 3 && maxGap / Math.max(minGap, 0.1) <= 3) {\r\n for (let i = start + 1; i < end; i++) {\r\n result[i] = true\r\n }\r\n }\r\n}\r\n\r\nexport { detectEvenSpacedItems }\r\n\r\n/**\r\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기 — 줄바꿈 병합 전용.\r\n * (cellTextToString 내부에서 사용)\r\n */\r\nfunction mergeCellTextLines(textLines: string[]): string {\r\n // 셀 내 줄바꿈 병합 — 잘린 단어/숫자 조각 복구\r\n if (textLines.length <= 1) return textLines[0] || \"\"\r\n const merged: string[] = [textLines[0]]\r\n for (let i = 1; i < textLines.length; i++) {\r\n const prev = merged[merged.length - 1]\r\n const curr = textLines[i]\r\n if (/[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(\" \")) {\r\n merged[merged.length - 1] = prev + curr\r\n }\r\n else if (curr.trim().length <= 3 && /^[)\\]%}]/.test(curr.trim())) {\r\n merged[merged.length - 1] = prev + curr.trim()\r\n }\r\n else if (/[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {\r\n merged[merged.length - 1] = prev + curr.trim()\r\n }\r\n else if (/[\\d,]$/.test(prev) && /^[\\d,]+[)\\]]?$/.test(curr.trim()) && curr.trim().length <= 10) {\r\n merged[merged.length - 1] = prev + curr.trim()\r\n }\r\n else {\r\n merged.push(curr)\r\n }\r\n }\r\n return merged.join(\"\\n\")\r\n}\r\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}\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 const rows = 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 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/** 연속 행에서 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 * 서버리스/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","/**\r\n * PDF 텍스트 추출 (pdfjs-dist static import 기반)\r\n *\r\n * polyfill을 먼저 import해야 DOMMatrix/Path2D/pdfjsWorker가 주입됨.\r\n * ES 모듈 호이스팅 때문에 별도 파일로 분리되어 있음.\r\n */\r\n\r\nimport type { ParseResult, InternalParseResult, IRBlock, IRTable, DocumentMetadata, ParseOptions, BoundingBox, ParseWarning, OutlineItem } from \"../types.js\"\r\nimport { HEADING_RATIO_H1, HEADING_RATIO_H2, HEADING_RATIO_H3 } from \"../types.js\"\r\nimport { KordocError, safeMin, safeMax } from \"../utils.js\"\r\nimport { parsePageRange } from \"../page-range.js\"\r\nimport { blocksToMarkdown } from \"../table/builder.js\"\r\nimport { extractLines, preprocessLines, filterPageBorderLines, buildTableGrids, extractCells, mapTextToCells, cellTextToString, detectEvenSpacedItems, type TextItem, type TableGrid, type ExtractedCell } from \"./line-detector.js\"\r\nimport { detectClusterTables, type ClusterItem } from \"./cluster-detector.js\"\r\n// polyfill 먼저 (ES 모듈 호이스팅되므로 별도 파일 필수)\r\nimport \"./polyfill.js\"\r\nimport { getDocument, OPS, GlobalWorkerOptions } from \"pdfjs-dist/legacy/build/pdf.mjs\"\r\n\r\n// worker 비활성화 (polyfill에서 pdfjsWorker를 이미 주입했으므로)\r\nGlobalWorkerOptions.workerSrc = \"\"\r\n\r\n// ─── 안전 한계값 (구조적 파싱과 무관) ────────────────\r\nconst MAX_PAGES = 5000\r\nconst MAX_TOTAL_TEXT = 100 * 1024 * 1024 // 100MB\r\n/** PDF 로딩 타임아웃 (30초) — 악성/대용량 PDF 무한 대기 방지 */\r\nconst PDF_LOAD_TIMEOUT_MS = 30_000\r\n\r\n/** getDocument + 타임아웃 래퍼 */\r\nasync function loadPdfWithTimeout(buffer: ArrayBuffer) {\r\n const loadingTask = getDocument({\r\n data: new Uint8Array(buffer),\r\n useSystemFonts: true,\r\n disableFontFace: true,\r\n isEvalSupported: false,\r\n })\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n try {\r\n return await Promise.race([\r\n loadingTask.promise,\r\n new Promise<never>((_, reject) => {\r\n timer = setTimeout(() => { loadingTask.destroy(); reject(new KordocError(\"PDF 로딩 타임아웃 (30초 초과)\")) }, PDF_LOAD_TIMEOUT_MS)\r\n }),\r\n ])\r\n } finally {\r\n if (timer !== undefined) clearTimeout(timer)\r\n }\r\n}\r\n\r\ninterface PdfTextItem {\r\n str: string\r\n transform: number[]\r\n width: number\r\n height: number\r\n fontName?: string\r\n}\r\n\r\ninterface NormItem {\r\n text: string\r\n x: number\r\n y: number\r\n w: number\r\n h: number\r\n /** 폰트 높이(≈폰트 크기) — 헤딩 감지용 */\r\n fontSize: number\r\n fontName: string\r\n /** hidden text 여부 (투명/0pt) */\r\n isHidden: boolean\r\n /** pdfjs 공백 아이템이 이 아이템 직전에 있었음 — 단어 경계 힌트 */\r\n hasSpaceBefore?: boolean\r\n}\r\n\r\nexport async function parsePdfDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<InternalParseResult> {\r\n const doc = await loadPdfWithTimeout(buffer)\r\n\r\n try {\r\n const pageCount = doc.numPages\r\n if (pageCount === 0) throw new KordocError(\"PDF에 페이지가 없습니다.\")\r\n\r\n // 메타데이터 추출 (best-effort)\r\n const metadata: DocumentMetadata = { pageCount }\r\n await extractPdfMetadata(doc, metadata)\r\n\r\n const blocks: IRBlock[] = []\r\n const warnings: ParseWarning[] = []\r\n let totalChars = 0\r\n let totalTextBytes = 0\r\n const effectivePageCount = Math.min(pageCount, MAX_PAGES)\r\n\r\n // 페이지 범위 필터링\r\n const pageFilter = options?.pages ? parsePageRange(options.pages, effectivePageCount) : null\r\n const totalTarget = pageFilter ? pageFilter.size : effectivePageCount\r\n\r\n // 전체 문서의 폰트 크기 빈도 수집 (헤딩 감지용) — 빈도 Map으로 메모리 절약\r\n const fontSizeFreq = new Map<number, number>()\r\n const pageHeights = new Map<number, number>()\r\n\r\n let parsedPages = 0\r\n for (let i = 1; i <= effectivePageCount; i++) {\r\n if (pageFilter && !pageFilter.has(i)) continue\r\n try {\r\n const page = await doc.getPage(i)\r\n const tc = await page.getTextContent()\r\n const viewport = page.getViewport({ scale: 1 })\r\n pageHeights.set(i, viewport.height)\r\n const rawItems = tc.items as PdfTextItem[]\r\n const items = normalizeItems(rawItems)\r\n\r\n // hidden text 필터링 + 경고 수집\r\n const { visible, hiddenCount } = filterHiddenText(items, viewport.width, viewport.height)\r\n if (hiddenCount > 0) {\r\n warnings.push({ page: i, message: `${hiddenCount}개 숨겨진 텍스트 요소 필터링됨`, code: \"HIDDEN_TEXT_FILTERED\" })\r\n }\r\n\r\n // 폰트 크기 빈도 수집\r\n for (const item of visible) {\r\n if (item.fontSize > 0) fontSizeFreq.set(item.fontSize, (fontSizeFreq.get(item.fontSize) || 0) + 1)\r\n }\r\n\r\n // 선 기반 테이블 감지를 위한 operatorList\r\n const opList = await page.getOperatorList()\r\n\r\n const pageBlocks = extractPageBlocksWithLines(visible, i, opList, viewport.width, viewport.height)\r\n for (const b of pageBlocks) blocks.push(b)\r\n\r\n // 이미지 기반 PDF 감지 + 크기 제한용 문자 수 집계\r\n for (const b of pageBlocks) {\r\n const t = b.text || \"\"\r\n totalChars += t.replace(/\\s/g, \"\").length\r\n totalTextBytes += t.length * 2\r\n }\r\n if (totalTextBytes > MAX_TOTAL_TEXT) throw new KordocError(\"텍스트 추출 크기 초과\")\r\n parsedPages++\r\n options?.onProgress?.(parsedPages, totalTarget)\r\n } catch (pageErr) {\r\n // 크기 초과는 전체 중단\r\n if (pageErr instanceof KordocError) throw pageErr\r\n warnings.push({ page: i, message: `페이지 ${i} 파싱 실패: ${pageErr instanceof Error ? pageErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\r\n }\r\n }\r\n\r\n const parsedPageCount = parsedPages || (pageFilter ? pageFilter.size : effectivePageCount)\r\n if (totalChars / Math.max(parsedPageCount, 1) < 10) {\r\n if (options?.ocr) {\r\n try {\r\n const { ocrPages } = await import(\"../ocr/provider.js\")\r\n const ocrBlocks = await ocrPages(doc, options.ocr, pageFilter, effectivePageCount)\r\n if (ocrBlocks.length > 0) {\r\n const ocrMarkdown = ocrBlocks.map(b => b.text || \"\").filter(Boolean).join(\"\\n\\n\")\r\n return { markdown: ocrMarkdown, blocks: ocrBlocks, metadata, warnings, isImageBased: true }\r\n }\r\n } catch {\r\n // OCR 실패 시 원래 에러 반환\r\n }\r\n }\r\n throw Object.assign(new KordocError(`이미지 기반 PDF (${pageCount}페이지, ${totalChars}자)`), { isImageBased: true })\r\n }\r\n\r\n // 머리글/바닥글 필터링 (기본 ON — 명시적 false일 때만 비활성화)\r\n if (options?.removeHeaderFooter !== false && parsedPageCount >= 3) {\r\n const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings)\r\n // 필터링된 블록 제거 (뒤에서부터 삭제)\r\n for (let ri = removed.length - 1; ri >= 0; ri--) {\r\n blocks.splice(removed[ri], 1)\r\n }\r\n }\r\n\r\n // 헤딩 감지: 폰트 크기 기반\r\n const medianFontSize = computeMedianFontSizeFromFreq(fontSizeFreq)\r\n if (medianFontSize > 0) {\r\n detectHeadings(blocks, medianFontSize)\r\n }\r\n\r\n // □/■ 마커 기반 서브헤딩 감지 (ODL 패턴)\r\n detectMarkerHeadings(blocks)\r\n\r\n // outline 구축\r\n const outline: OutlineItem[] = blocks\r\n .filter(b => b.type === \"heading\" && b.level && b.text)\r\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\r\n\r\n // blocksToMarkdown로 통일 — 헤딩 마크다운 반영 (HWP5/HWPX와 일관성)\r\n let markdown = cleanPdfText(blocksToMarkdown(blocks))\r\n\r\n return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined }\r\n } finally {\r\n await doc.destroy().catch(() => {})\r\n }\r\n}\r\n\r\n// ─── PDF 메타데이터 추출 ────────────────────────────\r\n\r\nasync function extractPdfMetadata(doc: { getMetadata(): Promise<unknown> }, metadata: DocumentMetadata): Promise<void> {\r\n try {\r\n const result = await doc.getMetadata() as { info?: Record<string, unknown> } | null\r\n if (!result?.info) return\r\n const info = result.info\r\n\r\n if (typeof info.Title === \"string\" && info.Title.trim()) metadata.title = info.Title.trim()\r\n if (typeof info.Author === \"string\" && info.Author.trim()) metadata.author = info.Author.trim()\r\n if (typeof info.Creator === \"string\" && info.Creator.trim()) metadata.creator = info.Creator.trim()\r\n if (typeof info.Subject === \"string\" && info.Subject.trim()) metadata.description = info.Subject.trim()\r\n if (typeof info.Keywords === \"string\" && info.Keywords.trim()) {\r\n metadata.keywords = info.Keywords.split(/[,;]/).map((k: string) => k.trim()).filter(Boolean)\r\n }\r\n if (typeof info.CreationDate === \"string\") metadata.createdAt = parsePdfDate(info.CreationDate)\r\n if (typeof info.ModDate === \"string\") metadata.modifiedAt = parsePdfDate(info.ModDate)\r\n } catch {\r\n // best-effort\r\n }\r\n}\r\n\r\n/** PDF 날짜 형식 (D:YYYYMMDDHHmmSS) → ISO 8601 변환 */\r\nfunction parsePdfDate(dateStr: string): string | undefined {\r\n const m = dateStr.match(/D:(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?/)\r\n if (!m) return undefined\r\n const [, year, month = \"01\", day = \"01\", hour = \"00\", min = \"00\", sec = \"00\"] = m\r\n return `${year}-${month}-${day}T${hour}:${min}:${sec}`\r\n}\r\n\r\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\r\nexport async function extractPdfMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\r\n const doc = await loadPdfWithTimeout(buffer)\r\n\r\n try {\r\n const metadata: DocumentMetadata = { pageCount: doc.numPages }\r\n await extractPdfMetadata(doc, metadata)\r\n return metadata\r\n } finally {\r\n await doc.destroy().catch(() => {})\r\n }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// Hidden text 필터링 (prompt injection 방어)\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction filterHiddenText(items: NormItem[], pageWidth: number, pageHeight: number): { visible: NormItem[]; hiddenCount: number } {\r\n let hiddenCount = 0\r\n const visible: NormItem[] = []\r\n\r\n for (const item of items) {\r\n // 0pt 폰트 / 너비 0 → 숨겨진 텍스트\r\n if (item.isHidden) { hiddenCount++; continue }\r\n // 페이지 범위 밖 (여백 10% 허용)\r\n const margin = Math.max(pageWidth, pageHeight) * 0.1\r\n if (item.x < -margin || item.x > pageWidth + margin || item.y < -margin || item.y > pageHeight + margin) {\r\n hiddenCount++; continue\r\n }\r\n visible.push(item)\r\n }\r\n\r\n return { visible, hiddenCount }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 헤딩 감지 (폰트 크기 기반)\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction computeMedianFontSizeFromFreq(freq: Map<number, number>): number {\r\n if (freq.size === 0) return 0\r\n let total = 0\r\n for (const count of freq.values()) total += count\r\n const sorted = [...freq.entries()].sort((a, b) => a[0] - b[0])\r\n const mid = Math.floor(total / 2)\r\n let cumulative = 0\r\n for (const [size, count] of sorted) {\r\n cumulative += count\r\n if (cumulative > mid) return size\r\n }\r\n return sorted[sorted.length - 1][0]\r\n}\r\n\r\n/**\r\n * 블록의 폰트 크기를 median과 비교하여 헤딩으로 승격.\r\n * - 150%+ → heading level 1\r\n * - 130%+ → heading level 2\r\n * - 115%+ → heading level 3\r\n * 조건: 짧은 텍스트 (200자 미만), 숫자만으로 구성되지 않음\r\n */\r\nfunction detectHeadings(blocks: IRBlock[], medianFontSize: number): void {\r\n for (const block of blocks) {\r\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontSize) continue\r\n const text = block.text.trim()\r\n if (text.length === 0 || text.length > 200) continue\r\n // 숫자만이면 헤딩 아님\r\n if (/^\\d+$/.test(text)) continue\r\n\r\n const ratio = block.style.fontSize / medianFontSize\r\n let level = 0\r\n if (ratio >= HEADING_RATIO_H1) level = 1\r\n else if (ratio >= HEADING_RATIO_H2) level = 2\r\n else if (ratio >= HEADING_RATIO_H3) level = 3\r\n\r\n if (level > 0) {\r\n block.type = \"heading\"\r\n block.level = level\r\n // PDF 균등배분 스페이스 제거 (\"기 본 현 황\" → \"기본현황\")\r\n // 한글 글자 사이에 단독 공백이 반복되면 균등배분으로 판단\r\n block.text = collapseEvenSpacing(text)\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 문자열 기반 균등배분 제거.\r\n * normalizeItems에서 분해 + 좌표 기반 감지가 주 경로이고, 여기는 안전망.\r\n * pdfjs가 이미 합친 \"홍 보 담 당 관\" 같은 TextItem 문자열에 적용.\r\n */\r\nfunction collapseEvenSpacing(text: string): string {\r\n // 1. 전체가 균등배분: 토큰의 70%가 1글자\r\n const tokens = text.split(\" \")\r\n const singleCharCount = tokens.filter(t => t.length === 1).length\r\n if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7) {\r\n return tokens.join(\"\")\r\n }\r\n\r\n // 2. 부분 균등배분: 한글 1자가 3개+ 연속 (2자 단어는 건드리지 않음)\r\n // \"홍 보 담 당 관\" → \"홍보담당관\", \"지 역 경 제 과\" → \"지역경제과\"\r\n // \"중동 사태 대응\" (2자 단어)는 매칭 안 됨 → 공백 유지\r\n return text.replace(\r\n /(?<![가-힣])[가-힣](?: [가-힣\\d]){2,}(?![가-힣])/g,\r\n match => match.replace(/ /g, \"\"),\r\n )\r\n}\r\n\r\n/**\r\n * 의사 테이블 감지: 실제 데이터 테이블이 아닌 텍스트가 우연히 테이블로 감지된 경우.\r\n */\r\nfunction shouldDemoteTable(table: IRTable): boolean {\r\n const allCells = table.cells.flatMap(row => row.map(c => c.text.trim())).filter(Boolean)\r\n const allText = allCells.join(\" \")\r\n\r\n // 텍스트 박스 패턴: 3행 이하 + 3열 이하 + <...> 또는 ㅇ 마커 포함\r\n // 공문서 \"중점 추진사항\" 등 요약 박스\r\n if (table.rows <= 3 && table.cols <= 3) {\r\n // 빈 셀이 과반 → 텍스트 박스 (테두리 안에 텍스트만 있는 형태)\r\n const totalCells = table.rows * table.cols\r\n const emptyCells = totalCells - allCells.length\r\n if (emptyCells >= totalCells * 0.3) return true\r\n\r\n // 마커 패턴 (ㅇ, □, ○, <> 등) → 텍스트성\r\n if (/[□■◆○●▶ㅇ]/.test(allText)) return true\r\n if (/<[^>]+>/.test(allText)) return true\r\n }\r\n\r\n if (allText.length > 200) return false\r\n // □, ○, ■ 마커 포함 + 3행 이하 → 텍스트성\r\n if (/[□■◆○●▶]/.test(allText) && table.rows <= 3) return true\r\n // 빈 셀이 과반 → 의사 테이블\r\n const totalCells = table.rows * table.cols\r\n const emptyCells = totalCells - allCells.length\r\n if (table.rows <= 2 && emptyCells > totalCells * 0.5) return true\r\n // 1행 + 숫자 데이터 없음 → 의사 테이블\r\n if (table.rows === 1 && !/\\d{2,}/.test(allText)) return true\r\n return false\r\n}\r\n\r\n/** demote된 테이블을 구조화된 텍스트로 변환 */\r\nfunction demoteTableToText(table: IRTable): string {\r\n const lines: string[] = []\r\n for (let r = 0; r < table.rows; r++) {\r\n const cells = table.cells[r].map(c => c.text.trim()).filter(Boolean)\r\n if (cells.length === 0) continue\r\n if (table.cols === 2 && cells.length === 2) {\r\n lines.push(`${cells[0]} : ${cells[1]}`)\r\n } else {\r\n // 각 셀 텍스트를 공백으로 합침 (br 태그는 줄바꿈으로 유지)\r\n lines.push(cells.join(\" \"))\r\n }\r\n }\r\n return lines.join(\"\\n\")\r\n}\r\n\r\n/** □/■ 마커 및 짧은 섹션명을 서브헤딩으로 변환 */\r\nfunction detectMarkerHeadings(blocks: IRBlock[]): void {\r\n for (let i = 0; i < blocks.length; i++) {\r\n const block = blocks[i]\r\n if (block.type !== \"paragraph\" || !block.text) continue\r\n const text = block.text.trim()\r\n // □/■ + 한글로 시작하는 짧은 텍스트 (50자 미만)\r\n if (text.length < 50 && /^[□■◆◇▶]\\s*[가-힣]/.test(text)) {\r\n block.type = \"heading\"\r\n block.level = 4\r\n continue\r\n }\r\n // 순수 한글 2-6자 + 앞뒤가 표/헤딩/빈블록 → 섹션 제목으로 추정\r\n // (예: \"사업설명\", \"사업효과\", \"추진경위\")\r\n if (/^[가-힣]{2,6}$/.test(text) && block.style?.fontSize) {\r\n const prev = blocks[i - 1]\r\n const next = blocks[i + 1]\r\n const prevIsStructural = !prev || prev.type === \"table\" || prev.type === \"heading\" || prev.type === \"separator\"\r\n const nextIsStructural = !next || next.type === \"table\" || next.type === \"heading\" || (next.type === \"paragraph\" && next.text && /^[□■◆○●]/.test(next.text.trim()))\r\n if (prevIsStructural || nextIsStructural) {\r\n block.type = \"heading\"\r\n block.level = 3\r\n }\r\n }\r\n }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// XY-Cut 읽기 순서 알고리즘\r\n// ═══════════════════════════════════════════════════════\r\n\r\ninterface TextRegion {\r\n items: NormItem[]\r\n minX: number; minY: number; maxX: number; maxY: number\r\n}\r\n\r\n/**\r\n * XY-Cut: 페이지를 재귀적으로 X/Y축 공백으로 분할하여 읽기 순서 결정.\r\n * - Y축 분할 (수평 공백 감지) → 위에서 아래\r\n * - X축 분할 (수직 공백 감지) → 왼쪽에서 오른쪽\r\n * - 분할 불가능하면 리프 노드 (하나의 텍스트 블록)\r\n */\r\n/** 재귀 깊이 제한 — 수천 아이템의 pathological 레이아웃에서 스택 오버플로 방지 */\r\nconst MAX_XYCUT_DEPTH = 50\r\n\r\nfunction xyCutOrder(items: NormItem[], gapThreshold: number, depth = 0): NormItem[][] {\r\n if (items.length === 0) return []\r\n if (items.length <= 2 || depth >= MAX_XYCUT_DEPTH) return [items]\r\n\r\n const region = computeRegion(items)\r\n\r\n // Y축 분할 시도 (수평 공백 감지)\r\n const ySplit = findYSplit(items, region, gapThreshold)\r\n if (ySplit !== null) {\r\n const upper = items.filter(i => i.y > ySplit)\r\n const lower = items.filter(i => i.y <= ySplit)\r\n // 빈 파티션 방어 — 한쪽이 비면 분할 의미 없음\r\n if (upper.length > 0 && lower.length > 0 && upper.length < items.length) {\r\n return [...xyCutOrder(upper, gapThreshold, depth + 1), ...xyCutOrder(lower, gapThreshold, depth + 1)]\r\n }\r\n }\r\n\r\n // X축 분할 시도 (수직 공백 감지)\r\n const xSplit = findXSplit(items, region, gapThreshold)\r\n if (xSplit !== null) {\r\n const left = items.filter(i => i.x + i.w / 2 < xSplit)\r\n const right = items.filter(i => i.x + i.w / 2 >= xSplit)\r\n if (left.length > 0 && right.length > 0 && left.length < items.length) {\r\n return [...xyCutOrder(left, gapThreshold, depth + 1), ...xyCutOrder(right, gapThreshold, depth + 1)]\r\n }\r\n }\r\n\r\n // 분할 불가 → 리프 노드\r\n return [items]\r\n}\r\n\r\nfunction computeRegion(items: NormItem[]): TextRegion {\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\r\n for (const i of items) {\r\n if (i.x < minX) minX = i.x\r\n if (i.y < minY) minY = i.y\r\n if (i.x + i.w > maxX) maxX = i.x + i.w\r\n if (i.y + i.h > maxY) maxY = i.y + i.h\r\n }\r\n return { items, minX, minY, maxX, maxY }\r\n}\r\n\r\n/** Y축 분할점 찾기 — 수평 공백 밴드 중 가장 넓은 갭 */\r\nfunction findYSplit(items: NormItem[], _region: TextRegion, gapThreshold: number): number | null {\r\n // 아이템을 Y좌표로 정렬 (내림차순 — 위에서 아래)\r\n const sorted = [...items].sort((a, b) => b.y - a.y)\r\n let bestGap = gapThreshold\r\n let bestSplit: number | null = null\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prevBottom = sorted[i - 1].y - sorted[i - 1].h\r\n const currTop = sorted[i].y\r\n const gap = prevBottom - currTop\r\n if (gap > bestGap) {\r\n bestGap = gap\r\n bestSplit = (prevBottom + currTop) / 2\r\n }\r\n }\r\n return bestSplit\r\n}\r\n\r\n/** X축 분할점 찾기 — 수직 공백 밴드 중 가장 넓은 갭 */\r\nfunction findXSplit(items: NormItem[], _region: TextRegion, gapThreshold: number): number | null {\r\n const sorted = [...items].sort((a, b) => a.x - b.x)\r\n let bestGap = gapThreshold\r\n let bestSplit: number | null = null\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prevRight = sorted[i - 1].x + sorted[i - 1].w\r\n const currLeft = sorted[i].x\r\n const gap = currLeft - prevRight\r\n if (gap > bestGap) {\r\n bestGap = gap\r\n bestSplit = (prevRight + currLeft) / 2\r\n }\r\n }\r\n return bestSplit\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 페이지 콘텐츠 추출 → IRBlock[] (v2: 바운딩 박스 + 페이지 번호)\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/**\r\n * 선 기반 테이블 감지를 우선 시도, 실패 시 기존 휴리스틱 fallback.\r\n */\r\nfunction extractPageBlocksWithLines(\r\n items: NormItem[],\r\n pageNum: number,\r\n opList: { fnArray: Uint32Array | number[]; argsArray: unknown[][] },\r\n pageWidth: number,\r\n pageHeight: number,\r\n): IRBlock[] {\r\n if (items.length === 0) return []\r\n\r\n // 1단계: PDF 그래픽 명령에서 선 추출\r\n let { horizontals, verticals } = extractLines(opList.fnArray, opList.argsArray)\r\n ;({ horizontals, verticals } = filterPageBorderLines(horizontals, verticals, pageWidth, pageHeight))\r\n\r\n // 1.5단계: 선 전처리 (ODL LinesPreprocessingConsumer 포팅)\r\n // 굵은 선 필터 + 근접 평행 선 병합\r\n ;({ horizontals, verticals } = preprocessLines(horizontals, verticals))\r\n\r\n // 2단계: 선으로 테이블 그리드 구성\r\n const grids = buildTableGrids(horizontals, verticals)\r\n\r\n if (grids.length > 0) {\r\n return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals)\r\n }\r\n\r\n // Fallback: 기존 휴리스틱 (선이 없는 PDF)\r\n return extractPageBlocksFallback(items, pageNum)\r\n}\r\n\r\n/**\r\n * 선 기반 그리드가 감지된 경우: 테이블 영역의 텍스트는 셀에 매핑,\r\n * 나머지는 일반 텍스트 블록으로 처리.\r\n */\r\nfunction extractBlocksWithGrids(\r\n items: NormItem[],\r\n pageNum: number,\r\n grids: TableGrid[],\r\n horizontals: import(\"./line-detector.js\").LineSegment[],\r\n verticals: import(\"./line-detector.js\").LineSegment[],\r\n): IRBlock[] {\r\n const blocks: IRBlock[] = []\r\n const usedItems = new Set<NormItem>()\r\n\r\n // 그리드를 Y좌표 내림차순 정렬 (위→아래)\r\n const sortedGrids = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\r\n\r\n for (const grid of sortedGrids) {\r\n // 1행 다열 그리드는 테이블 헤더일 가능성 높음 → 스킵하여 클러스터 감지에 위임\r\n const numGridRows = grid.rowYs.length - 1\r\n const numGridCols = grid.colXs.length - 1\r\n if (numGridRows === 1 && numGridCols >= 2) continue\r\n\r\n // 그리드 영역 내 텍스트 아이템 수집\r\n const tableItems: NormItem[] = []\r\n const pad = 3\r\n const gridW = grid.bbox.x2 - grid.bbox.x1\r\n for (const item of items) {\r\n if (usedItems.has(item)) continue\r\n // Y 범위 체크\r\n if (item.y < grid.bbox.y1 - pad || item.y > grid.bbox.y2 + pad) continue\r\n // X 범위 체크 — 아이템의 시작과 끝이 모두 그리드 안에 있어야 함\r\n if (item.x < grid.bbox.x1 - pad || item.x + item.w > grid.bbox.x2 + pad) continue\r\n // 좁은 그리드(120px 미만)에서 큰 아이템이 경계에 걸치면 제외\r\n // 제목 텍스트가 인접 그리드에 잡히는 것을 방지\r\n if (gridW < 120 && item.x + item.w > grid.bbox.x2 - 2) continue\r\n tableItems.push(item)\r\n usedItems.add(item)\r\n }\r\n\r\n // 셀 추출\r\n const cells = extractCells(grid, horizontals, verticals)\r\n if (cells.length === 0) continue\r\n\r\n // 텍스트→셀 매핑\r\n const textItems: TextItem[] = tableItems.map(i => ({\r\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\r\n fontSize: i.fontSize, fontName: i.fontName,\r\n }))\r\n const cellTextMap = mapTextToCells(textItems, cells)\r\n\r\n // IRTable 구성\r\n const numRows = grid.rowYs.length - 1\r\n const numCols = grid.colXs.length - 1\r\n const irGrid: import(\"../types.js\").IRCell[][] = Array.from(\r\n { length: numRows },\r\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\r\n )\r\n\r\n for (const cell of cells) {\r\n const cellItems = cellTextMap.get(cell) || []\r\n let text = cellTextToString(cellItems)\r\n // 셀 안의 페이지 번호 표시 제거 (\"- 2 -\" 등)\r\n text = text.replace(/^[\\s]*[-–—]\\s*\\d+\\s*[-–—][\\s]*$/gm, \"\").trim()\r\n // 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\r\n text = text.split(\"\\n\").map(line => collapseEvenSpacing(line)).join(\"\\n\")\r\n irGrid[cell.row][cell.col] = {\r\n text,\r\n colSpan: cell.colSpan,\r\n rowSpan: cell.rowSpan,\r\n }\r\n }\r\n\r\n const irTable: IRTable = {\r\n rows: numRows,\r\n cols: numCols,\r\n cells: irGrid,\r\n hasHeader: numRows > 1,\r\n }\r\n\r\n // 빈 테이블(모든 셀이 빈 문자열) 스킵\r\n const hasContent = irGrid.some(row => row.some(cell => cell.text.trim() !== \"\"))\r\n if (!hasContent) continue\r\n\r\n const tableBbox: BoundingBox = {\r\n page: pageNum,\r\n x: grid.bbox.x1, y: grid.bbox.y1,\r\n width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1,\r\n }\r\n\r\n // 의사 테이블 필터: 텍스트성 내용 → paragraph로 복원 (구조 보존)\r\n if (shouldDemoteTable(irTable)) {\r\n const demoted = demoteTableToText(irTable)\r\n if (demoted) {\r\n // 텍스트 박스(1x1 또는 1행 그리드) demote 시 앞뒤 줄바꿈으로 본문과 분리\r\n const text = numGridRows === 1 ? \"\\n\" + demoted + \"\\n\" : demoted\r\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox: tableBbox, style: dominantStyle(tableItems) })\r\n }\r\n continue\r\n }\r\n\r\n blocks.push({ type: \"table\", table: irTable, pageNumber: pageNum, bbox: tableBbox })\r\n }\r\n\r\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\r\n let remaining = items.filter(i => !usedItems.has(i))\r\n if (remaining.length > 0) {\r\n remaining.sort((a, b) => b.y - a.y || a.x - b.x)\r\n\r\n // 클러스터 기반 테이블 감지 (XY-Cut 전에 실행 — 테이블이 쪼개지지 않도록)\r\n const clusterItems: ClusterItem[] = remaining.map(i => ({\r\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\r\n fontSize: i.fontSize, fontName: i.fontName,\r\n }))\r\n const clusterResults = detectClusterTables(clusterItems, pageNum)\r\n if (clusterResults.length > 0) {\r\n const ciToIdx = new Map<ClusterItem, number>()\r\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\r\n const usedClusterIndices = new Set<number>()\r\n for (const cr of clusterResults) {\r\n for (const ci of cr.usedItems) {\r\n const idx = ciToIdx.get(ci)\r\n if (idx !== undefined) usedClusterIndices.add(idx)\r\n }\r\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\r\n }\r\n remaining = remaining.filter((_, idx) => !usedClusterIndices.has(idx))\r\n }\r\n\r\n // XY-Cut으로 왼쪽 본문과 오른쪽 부서명 등을 분리 후 개별 처리\r\n if (remaining.length > 0) {\r\n const allY = remaining.map(i => i.y)\r\n const pageH = safeMax(allY) - safeMin(allY)\r\n const groups = xyCutOrder(remaining, Math.max(15, pageH * 0.03))\r\n const textBlocks: IRBlock[] = []\r\n for (const group of groups) {\r\n if (group.length === 0) continue\r\n const groupBlocks = extractPageBlocksFallback(group, pageNum)\r\n for (const b of groupBlocks) textBlocks.push(b)\r\n }\r\n const finalTextBlocks = detectListBlocks(textBlocks)\r\n for (const b of finalTextBlocks) blocks.push(b)\r\n }\r\n\r\n // Y좌표 기반 정렬\r\n blocks.sort((a, b) => {\r\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\r\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\r\n return by - ay // PDF는 y가 위가 큼 → 내림차순\r\n })\r\n return mergeAdjacentTableBlocks(blocks)\r\n }\r\n\r\n return mergeAdjacentTableBlocks(blocks)\r\n}\r\n\r\n/** 같은 열 수의 연속 테이블 블록을 하나로 합침 */\r\nfunction mergeAdjacentTableBlocks(blocks: IRBlock[]): IRBlock[] {\r\n if (blocks.length <= 1) return blocks\r\n const result: IRBlock[] = [blocks[0]]\r\n for (let i = 1; i < blocks.length; i++) {\r\n const prev = result[result.length - 1]\r\n const curr = blocks[i]\r\n if (prev.type === \"table\" && curr.type === \"table\" && prev.table && curr.table &&\r\n prev.table.cols === curr.table.cols) {\r\n // 합치기: prev의 cells에 curr의 cells 추가\r\n const merged: IRTable = {\r\n rows: prev.table.rows + curr.table.rows,\r\n cols: prev.table.cols,\r\n cells: [...prev.table.cells, ...curr.table.cells],\r\n hasHeader: prev.table.hasHeader,\r\n }\r\n result[result.length - 1] = { ...prev, table: merged }\r\n } else {\r\n result.push(curr)\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 기존 휴리스틱 기반 페이지 블록 추출 (선이 없는 PDF 대비 fallback).\r\n */\r\nfunction extractPageBlocksFallback(items: NormItem[], pageNum: number): IRBlock[] {\r\n if (items.length === 0) return []\r\n\r\n const blocks: IRBlock[] = []\r\n\r\n // 1단계: 클러스터 기반 테이블 감지 우선 (헤더 감지 시 정확도 높음)\r\n const clusterItems: ClusterItem[] = items.map(i => ({\r\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\r\n fontSize: i.fontSize, fontName: i.fontName,\r\n }))\r\n const clusterResults = detectClusterTables(clusterItems, pageNum)\r\n\r\n if (clusterResults.length > 0) {\r\n const ciToIdx = new Map<ClusterItem, number>()\r\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\r\n const usedIndices = new Set<number>()\r\n for (const cr of clusterResults) {\r\n for (const ci of cr.usedItems) {\r\n const idx = ciToIdx.get(ci)\r\n if (idx !== undefined) usedIndices.add(idx)\r\n }\r\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\r\n }\r\n\r\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\r\n const remaining = items.filter((_, idx) => !usedIndices.has(idx))\r\n if (remaining.length > 0) {\r\n const yLines = groupByY(remaining)\r\n for (const line of yLines) {\r\n const text = mergeLineSimple(line)\r\n if (!text.trim()) continue\r\n const bbox = computeBBox(line, pageNum)\r\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\r\n }\r\n }\r\n\r\n blocks.sort((a, b) => {\r\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\r\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\r\n return by - ay\r\n })\r\n } else {\r\n // 2단계: 레거시 컬럼 감지 (3+ 열)\r\n const allYLines = groupByY(items)\r\n const columns = detectColumns(allYLines)\r\n\r\n if (columns && columns.length >= 3) {\r\n const tableText = extractWithColumns(allYLines, columns)\r\n const bbox = computeBBox(items, pageNum)\r\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(items) })\r\n } else {\r\n // 3단계: XY-Cut으로 읽기 순서 결정\r\n const allY = items.map(i => i.y)\r\n const pageHeight = safeMax(allY) - safeMin(allY)\r\n const gapThreshold = Math.max(15, pageHeight * 0.03)\r\n\r\n const orderedGroups = xyCutOrder(items, gapThreshold)\r\n\r\n for (const group of orderedGroups) {\r\n if (group.length === 0) continue\r\n const yLines = groupByY(group)\r\n\r\n const groupColumns = detectColumns(yLines)\r\n if (groupColumns && groupColumns.length >= 3) {\r\n const tableText = extractWithColumns(yLines, groupColumns)\r\n const bbox = computeBBox(group, pageNum)\r\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(group) })\r\n } else {\r\n for (const line of yLines) {\r\n const text = mergeLineSimple(line)\r\n if (!text.trim()) continue\r\n const bbox = computeBBox(line, pageNum)\r\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n // 한국어 특수 테이블 감지 (구분/항목/종류 패턴)\r\n return detectSpecialKoreanTables(blocks)\r\n}\r\n\r\n/** 아이템 그룹에서 바운딩 박스 계산 */\r\nfunction computeBBox(items: NormItem[], pageNum: number): BoundingBox {\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\r\n for (const i of items) {\r\n if (i.x < minX) minX = i.x\r\n if (i.y < minY) minY = i.y\r\n if (i.x + i.w > maxX) maxX = i.x + i.w\r\n // h가 0인 경우 fontSize를 높이 대용으로 사용 (pdfjs가 height를 제공하지 않는 경우)\r\n const effectiveH = i.h > 0 ? i.h : i.fontSize\r\n if (i.y + effectiveH > maxY) maxY = i.y + effectiveH\r\n }\r\n return { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }\r\n}\r\n\r\n/** 아이템 그룹의 대표 스타일 (최빈 폰트 크기) */\r\nfunction dominantStyle(items: NormItem[]): { fontSize: number; fontName: string } | undefined {\r\n if (items.length === 0) return undefined\r\n // 최빈 폰트 크기 찾기\r\n const freq = new Map<number, number>()\r\n let maxCount = 0, dominantSize = 0\r\n for (const i of items) {\r\n if (i.fontSize <= 0) continue\r\n const count = (freq.get(i.fontSize) || 0) + 1\r\n freq.set(i.fontSize, count)\r\n if (count > maxCount) { maxCount = count; dominantSize = i.fontSize }\r\n }\r\n if (dominantSize === 0) return undefined\r\n // 대표 폰트명 (빈 문자열은 undefined로)\r\n const fontName = items.find(i => i.fontSize === dominantSize)?.fontName || undefined\r\n return { fontSize: dominantSize, fontName }\r\n}\r\n\r\nfunction normalizeItems(rawItems: PdfTextItem[]): NormItem[] {\r\n const items: NormItem[] = []\r\n // pdfjs 공백 아이템 위치 수집 — 단어 경계 힌트로 활용\r\n const spacePositions: { x: number; y: number }[] = []\r\n\r\n for (const i of rawItems) {\r\n if (typeof i.str !== \"string\") continue\r\n const x = Math.round(i.transform[4])\r\n const y = Math.round(i.transform[5])\r\n\r\n if (!i.str.trim()) {\r\n // 공백 전용 아이템: 위치만 기록 (단어 구분 힌트)\r\n spacePositions.push({ x, y })\r\n continue\r\n }\r\n\r\n const scaleY = Math.abs(i.transform[3])\r\n const scaleX = Math.abs(i.transform[0])\r\n const fontSize = Math.round(Math.max(scaleY, scaleX))\r\n const w = Math.round(i.width)\r\n const h = Math.round(i.height)\r\n const isHidden = fontSize === 0 || (i.width === 0 && i.str.trim().length > 0)\r\n\r\n // letterSpacing이 적용된 숫자/기호 문자열 정규화\r\n // \"45 0 -7 3 40 )\" → \"450-7340)\" (전화번호, 금액 등)\r\n let text = i.str.trim()\r\n if (/^[\\d\\s\\-().·,☎]+$/.test(text) && /\\d/.test(text) && / /.test(text)) {\r\n text = text.replace(/ /g, \"\")\r\n }\r\n\r\n // 균등배분 TextItem 분해: \"홍 보 지 원 반\" → 개별 글자 아이템으로\r\n const split = splitEvenSpacedItem(text, x, w, fontSize)\r\n if (split) {\r\n for (const s of split) {\r\n items.push({ text: s.text, x: s.x, y, w: s.w, h, fontSize, fontName: i.fontName || \"\", isHidden })\r\n }\r\n } else {\r\n items.push({ text, x, y, w, h, fontSize, fontName: i.fontName || \"\", isHidden })\r\n }\r\n }\r\n\r\n const sorted = items.sort((a, b) => b.y - a.y || a.x - b.x)\r\n\r\n // 1. 가짜 볼드 중복 제거: 같은 텍스트가 거의 동일한 좌표(±3px)에 2~3회 겹쳐진 경우\r\n // PDF에서 볼드 효과를 위해 텍스트를 여러 번 렌더링하는 기법\r\n const deduped: NormItem[] = []\r\n for (let i = 0; i < sorted.length; i++) {\r\n let isDup = false\r\n // Y 정렬(desc)이므로 역순 스캔 — Y 차이가 tolerance를 넘으면 중단\r\n for (let j = deduped.length - 1; j >= 0; j--) {\r\n const prev = deduped[j]\r\n if (prev.y - sorted[i].y > 3) break // 이전 아이템이 너무 높음 → 중단\r\n if (Math.abs(prev.y - sorted[i].y) <= 3 &&\r\n prev.text === sorted[i].text && Math.abs(prev.x - sorted[i].x) <= 3) {\r\n isDup = true\r\n break\r\n }\r\n }\r\n if (!isDup) deduped.push(sorted[i])\r\n }\r\n\r\n // 2. 공백 아이템 위치를 NormItem.hasSpaceBefore로 전파\r\n // 같은 Y라인(±3px)에서 공백 아이템의 x가 현재 아이템 직전(왼쪽)에 있으면 표시\r\n if (spacePositions.length > 0) {\r\n for (const item of deduped) {\r\n for (const sp of spacePositions) {\r\n // 같은 Y라인(±3px) + 공백이 아이템 왼쪽에 인접 (0 ~ 20px 이내)\r\n if (Math.abs(sp.y - item.y) <= 3) {\r\n const dist = item.x - sp.x\r\n if (dist >= 0 && dist <= 20) {\r\n item.hasSpaceBefore = true\r\n break\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return deduped\r\n}\r\n\r\n/**\r\n * 균등배분 TextItem 감지 및 분해.\r\n * \"홍 보 지 원 반\" (1자+공백 패턴) → [{text:\"홍\",x,w}, {text:\"보\",x,w}, ...]\r\n * 분해하면 이후 detectEvenSpacedItems가 좌표 기반으로 정확히 감지할 수 있음.\r\n */\r\nfunction splitEvenSpacedItem(\r\n text: string, itemX: number, itemW: number, fontSize: number,\r\n): { text: string; x: number; w: number }[] | null {\r\n // 한글/숫자 1자 + 공백이 3회+ 반복되는 패턴\r\n // \"홍 보 지 원 반\", \"세 무 1 과\", \"주 요 내 용\"\r\n if (!/^[가-힣\\d](?: [가-힣\\d]){2,}$/.test(text)) return null\r\n\r\n const chars = text.split(\" \")\r\n if (chars.length < 3) return null\r\n\r\n // 글자당 폭 계산 — 전체 width를 글자 수로 나눔\r\n const charW = itemW / chars.length\r\n // 글자 폭이 너무 크면 균등배분이 아님 (한 글자가 fontSize의 2배 넘으면 이상)\r\n if (charW > fontSize * 2) return null\r\n\r\n return chars.map((ch, idx) => ({\r\n text: ch,\r\n x: Math.round(itemX + idx * charW),\r\n w: Math.round(charW * 0.8), // 실제 글자 폭은 간격보다 좁음\r\n }))\r\n}\r\n\r\nfunction groupByY(items: NormItem[]): NormItem[][] {\r\n if (items.length === 0) return []\r\n const lines: NormItem[][] = []\r\n let curY = items[0].y\r\n let curLine: NormItem[] = [items[0]]\r\n\r\n for (let i = 1; i < items.length; i++) {\r\n // Y좌표 허용 오차 3px — PDF 렌더링 미세 오차 보정, 별표 행 경계 감지에 최적화된 값\r\n if (Math.abs(items[i].y - curY) > 3) {\r\n lines.push(curLine)\r\n curLine = []\r\n curY = items[i].y\r\n }\r\n curLine.push(items[i])\r\n }\r\n if (curLine.length > 0) lines.push(curLine)\r\n return lines\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 열 경계 감지 — 빈도 기반 x-히스토그램 클러스터링\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/** prose 라인 판별: 아이템 간 gap이 모두 작으면 문장 (단어 나열) */\r\nfunction isProseSpread(items: NormItem[]): boolean {\r\n if (items.length < 4) return false\r\n const sorted = [...items].sort((a, b) => a.x - b.x)\r\n const gaps: number[] = []\r\n for (let i = 1; i < sorted.length; i++) {\r\n gaps.push(sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w))\r\n }\r\n // gap의 최대값이 작고 평균 단어 길이가 짧으면 prose\r\n const maxGap = safeMax(gaps)\r\n const avgLen = items.reduce((s, i) => s + i.text.length, 0) / items.length\r\n // 짧은 단어들이 좁은 간격으로 나열 = prose (예: \"위 표 제3호나목에서 남은 유효기간...\")\r\n return maxGap < 40 && avgLen < 5\r\n}\r\n\r\nfunction detectColumns(yLines: NormItem[][]): number[] | null {\r\n const allItems = yLines.flat()\r\n if (allItems.length === 0) return null\r\n const pageWidth = safeMax(allItems.map(i => i.x + i.w)) - safeMin(allItems.map(i => i.x))\r\n if (pageWidth < 100) return null\r\n\r\n // \"비고\" 이전 아이템만 사용 (비고 이후는 prose)\r\n let bigoLineIdx = -1\r\n for (let i = 0; i < yLines.length; i++) {\r\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\r\n bigoLineIdx = i\r\n break\r\n }\r\n }\r\n const tableYLines = bigoLineIdx >= 0 ? yLines.slice(0, bigoLineIdx) : yLines\r\n\r\n // Step 1: 모든 아이템의 x를 수집 (prose 라인 제외)\r\n // CLUSTER_TOL 22px — 한국 공문서 PDF 열 간격에 최적화, 별표 표 열 감지 핵심값\r\n const CLUSTER_TOL = 22\r\n const xClusters: { center: number; count: number; minX: number }[] = []\r\n\r\n for (const line of tableYLines) {\r\n if (isProseSpread(line)) continue\r\n for (const item of line) {\r\n let found = false\r\n for (const c of xClusters) {\r\n if (Math.abs(item.x - c.center) <= CLUSTER_TOL) {\r\n c.center = Math.round((c.center * c.count + item.x) / (c.count + 1))\r\n c.minX = Math.min(c.minX, item.x)\r\n c.count++\r\n found = true\r\n break\r\n }\r\n }\r\n if (!found) {\r\n xClusters.push({ center: item.x, count: 1, minX: item.x })\r\n }\r\n }\r\n }\r\n\r\n // Step 2: 빈도 피크 — 최소 3회 이상 등장 (단발성 텍스트 노이즈 제거)\r\n const peaks = xClusters\r\n .filter(c => c.count >= 3)\r\n .sort((a, b) => a.minX - b.minX)\r\n\r\n // 최소 3개 열이 있어야 테이블로 판별 — 2열은 일반 2단 레이아웃과 구분 불가\r\n if (peaks.length < 3) return null\r\n\r\n // Step 3: 가까운 피크 병합 — MERGE_TOL 40px (같은 논리 열의 미세 위치 차이 흡수)\r\n const MERGE_TOL = 40\r\n const merged: { center: number; count: number; minX: number }[] = [peaks[0]]\r\n for (let i = 1; i < peaks.length; i++) {\r\n const prev = merged[merged.length - 1]\r\n if (peaks[i].minX - prev.minX < MERGE_TOL) {\r\n // 빈도 높은 쪽 유지, 최소 x는 작은 값\r\n if (peaks[i].count > prev.count) {\r\n prev.center = peaks[i].center\r\n }\r\n prev.count += peaks[i].count\r\n prev.minX = Math.min(prev.minX, peaks[i].minX)\r\n } else {\r\n merged.push({ ...peaks[i] })\r\n }\r\n }\r\n\r\n // 열 경계 = 각 클러스터의 minX (왼쪽 정렬 기준), 병합 후 재검증\r\n const rawColumns = merged.filter(c => c.count >= 3).map(c => c.minX)\r\n if (rawColumns.length < 3) return null\r\n\r\n // 최소 열 폭 검증: 30px 미만인 열은 인접 열과 병합 (한 글자 열 방지)\r\n const MIN_DETECT_COL_WIDTH = 30\r\n const columns: number[] = [rawColumns[0]]\r\n for (let i = 1; i < rawColumns.length; i++) {\r\n if (rawColumns[i] - columns[columns.length - 1] < MIN_DETECT_COL_WIDTH) continue\r\n columns.push(rawColumns[i])\r\n }\r\n return columns.length >= 3 ? columns : null\r\n}\r\n\r\nfunction findColumn(x: number, columns: number[]): number {\r\n for (let i = columns.length - 1; i >= 0; i--) {\r\n // 10px 왼쪽 허용 오차 — 셀 내 텍스트 미세 좌측 이탈 보정\r\n if (x >= columns[i] - 10) return i\r\n }\r\n return 0\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 열 기반 추출 — 테이블/텍스트 영역 분리\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction extractWithColumns(yLines: NormItem[][], columns: number[]): string {\r\n const result: string[] = []\r\n const colMin = columns[0]\r\n const colMax = columns[columns.length - 1]\r\n\r\n // \"비고\" 라인 감지 — 이후는 텍스트로 처리\r\n let bigoIdx = -1\r\n for (let i = 0; i < yLines.length; i++) {\r\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\r\n bigoIdx = i\r\n break\r\n }\r\n }\r\n\r\n // 테이블 시작: 첫 번째 다열(3+ 열 사용) 라인\r\n let tableStart = -1\r\n for (let i = 0; i < (bigoIdx >= 0 ? bigoIdx : yLines.length); i++) {\r\n const usedCols = new Set(yLines[i].map(item => findColumn(item.x, columns)))\r\n if (usedCols.size >= 3) {\r\n tableStart = i\r\n break\r\n }\r\n }\r\n\r\n const tableEnd = bigoIdx >= 0 ? bigoIdx : yLines.length\r\n\r\n // 테이블 시작 이전 = 텍스트\r\n for (let i = 0; i < (tableStart >= 0 ? tableStart : tableEnd); i++) {\r\n result.push(mergeLineSimple(yLines[i]))\r\n }\r\n\r\n // 테이블 영역: 모든 라인을 그리드에 포함 (단일 아이템 라인도)\r\n if (tableStart >= 0) {\r\n const tableLines = yLines.slice(tableStart, tableEnd)\r\n // 테이블 x범위 밖의 라인만 텍스트로 분리\r\n // 좌측 20px, 우측 200px 허용 — 비고/주석 열이 오른쪽에 넓게 위치하는 공문서 특성 반영\r\n const gridLines: NormItem[][] = []\r\n for (const line of tableLines) {\r\n const inRange = line.some(item =>\r\n item.x >= colMin - 20 && item.x <= colMax + 200\r\n )\r\n if (inRange && !isProseSpread(line)) {\r\n gridLines.push(line)\r\n } else {\r\n // 그리드 밖 라인은 현재까지 축적된 그리드 출력 후 텍스트로\r\n if (gridLines.length > 0) {\r\n result.push(buildGridTable(gridLines.splice(0), columns))\r\n }\r\n result.push(mergeLineSimple(line))\r\n }\r\n }\r\n if (gridLines.length > 0) {\r\n result.push(buildGridTable(gridLines, columns))\r\n }\r\n }\r\n\r\n // 비고 영역\r\n if (bigoIdx >= 0) {\r\n result.push(\"\")\r\n for (let i = bigoIdx; i < yLines.length; i++) {\r\n result.push(mergeLineSimple(yLines[i]))\r\n }\r\n }\r\n\r\n return result.join(\"\\n\")\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 그리드 테이블 빌더 — y-라인을 열에 배치 후 행 병합\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction buildGridTable(lines: NormItem[][], columns: number[]): string {\r\n const numCols = columns.length\r\n\r\n // Step 1: 각 y-라인을 열에 배치\r\n const yRows: string[][] = lines.map(items => {\r\n const row = Array(numCols).fill(\"\")\r\n for (const item of items) {\r\n const col = findColumn(item.x, columns)\r\n row[col] = row[col] ? row[col] + \" \" + item.text : item.text\r\n }\r\n return row\r\n })\r\n\r\n // Step 2: 행 병합 — 새 논리적 행 판별\r\n // 데이터 열 기준점 (가격 등이 들어가는 오른쪽 열들)\r\n const dataColStart = Math.max(2, Math.floor(numCols / 2))\r\n const merged: string[][] = []\r\n\r\n for (const row of yRows) {\r\n if (row.every(c => c === \"\")) continue\r\n\r\n if (merged.length === 0) {\r\n merged.push([...row])\r\n continue\r\n }\r\n\r\n const prev = merged[merged.length - 1]\r\n const filledCols = row.map((c, i) => c ? i : -1).filter(i => i >= 0)\r\n const filledCount = filledCols.length\r\n\r\n let isNewRow = false\r\n\r\n // Rule 1: col 0에 텍스트 (3글자 이상) → 새 행 (단, \"권\"처럼 짧은 건 continuation)\r\n if (row[0] && row[0].length >= 3) {\r\n isNewRow = true\r\n }\r\n\r\n // Rule 2: col 1에 텍스트 → 항상 새 행 (새 항목 시작)\r\n if (!isNewRow && numCols > 1 && row[1]) {\r\n isNewRow = true\r\n }\r\n\r\n // Rule 3: 데이터 열(3+)에 새 값이 있고 이전 행 데이터 열에도 이미 값 있음 → 새 가격 행\r\n if (!isNewRow) {\r\n const hasData = row.slice(dataColStart).some(c => c !== \"\")\r\n const prevHasData = prev.slice(dataColStart).some(c => c !== \"\")\r\n if (hasData && prevHasData) {\r\n isNewRow = true\r\n }\r\n }\r\n\r\n // Exception: filledCount=1이고 col 0에 짧은 텍스트(≤2자) → word continuation (예: \"권\", \"여권\")\r\n if (isNewRow && filledCount === 1 && row[0] && row[0].length <= 2) {\r\n isNewRow = false\r\n }\r\n\r\n if (isNewRow) {\r\n merged.push([...row])\r\n } else {\r\n for (let c = 0; c < numCols; c++) {\r\n if (row[c]) {\r\n prev[c] = prev[c] ? prev[c] + \" \" + row[c] : row[c]\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (merged.length < 2) {\r\n return merged.map(r => r.filter(c => c).join(\" \")).join(\"\\n\")\r\n }\r\n\r\n // Step 3: 헤더 행 병합 — 첫 N행이 모두 데이터열(dataColStart+)에 값이 없으면 헤더\r\n let headerEnd = 0\r\n for (let r = 0; r < merged.length; r++) {\r\n const hasDataValues = merged[r].slice(dataColStart).some(c => c && /\\d/.test(c))\r\n if (hasDataValues) break\r\n headerEnd = r + 1\r\n }\r\n\r\n if (headerEnd > 1) {\r\n // 헤더 행들을 하나로 합침\r\n const headerRow = Array(numCols).fill(\"\")\r\n for (let r = 0; r < headerEnd; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n if (merged[r][c]) {\r\n headerRow[c] = headerRow[c] ? headerRow[c] + \" \" + merged[r][c] : merged[r][c]\r\n }\r\n }\r\n }\r\n merged.splice(0, headerEnd, headerRow)\r\n }\r\n\r\n // Step 3.5: 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\r\n for (const row of merged) {\r\n for (let c = 0; c < row.length; c++) {\r\n if (row[c]) row[c] = collapseEvenSpacing(row[c])\r\n }\r\n }\r\n\r\n // Step 3.6: 테이블 품질 검증 — 선 없는 fallback 경로에서는 보수적으로\r\n const totalCells = merged.length * numCols\r\n const filledCells = merged.reduce((s, row) => s + row.filter(c => c).length, 0)\r\n // 빈 셀 과반, 행이 2 미만, 또는 3행 이하+7열 이상 → 텍스트로 복원\r\n if (filledCells < totalCells * 0.35 || merged.length < 2 ||\r\n (merged.length <= 3 && numCols >= 7)) {\r\n return merged.map(r => r.filter(c => c).join(\"\\t\")).join(\"\\n\")\r\n }\r\n\r\n // Step 4: 마크다운 테이블\r\n const md: string[] = []\r\n md.push(\"| \" + merged[0].join(\" | \") + \" |\")\r\n md.push(\"| \" + merged[0].map(() => \"---\").join(\" | \") + \" |\")\r\n for (let r = 1; r < merged.length; r++) {\r\n md.push(\"| \" + merged[r].join(\" | \") + \" |\")\r\n }\r\n return md.join(\"\\n\")\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 유틸\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction mergeLineSimple(items: NormItem[]): string {\r\n if (items.length <= 1) return items[0]?.text || \"\"\r\n const sorted = [...items].sort((a, b) => a.x - b.x)\r\n\r\n // 좌표 기반 균등배분 감지 (ODL TextLineProcessor 방식)\r\n const isEvenSpaced = detectEvenSpacedItems(sorted)\r\n\r\n let result = sorted[0].text\r\n for (let i = 1; i < sorted.length; i++) {\r\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\r\n const avgFs = (sorted[i].fontSize + sorted[i - 1].fontSize) / 2\r\n\r\n // 탭 갭은 항상 탭으로 — 균등배분보다 우선\r\n // 기준: fontSize의 2배 이상 또는 30px+ (균등배분 간격은 보통 fontSize*1.5 이하)\r\n const tabThreshold = Math.max(avgFs * 2, 30)\r\n if (gap > tabThreshold) {\r\n result += \"\\t\"\r\n result += sorted[i].text\r\n continue\r\n }\r\n\r\n // 균등배분 구간이면 공백 없이 합침\r\n if (isEvenSpaced[i]) {\r\n result += sorted[i].text\r\n continue\r\n }\r\n\r\n // pdfjs 공백 아이템이 있었으면 단어 경계 — 갭 크기 무관하게 공백 삽입\r\n if (sorted[i].hasSpaceBefore && gap >= avgFs * 0.05) {\r\n result += \" \"\r\n result += sorted[i].text\r\n continue\r\n }\r\n // 마커(□○▶ 등) 뒤에 한글이 오면 항상 공백 보장 — \"□장소\" → \"□ 장소\"\r\n if (/[□■○●▶◆◇ㅇ]$/.test(sorted[i - 1].text) && /^[가-힣]/.test(sorted[i].text) && gap > 1) {\r\n result += \" \"\r\n result += sorted[i].text\r\n continue\r\n }\r\n // 매우 작은 갭 — 공백 없이 붙임\r\n if (gap < avgFs * 0.15) { /* no space */ }\r\n // 한글 관련 작은 갭 — PDF 문자 개별 배치 잔재\r\n else if (gap < avgFs * 0.35 && (/[가-힣]$/.test(result) || /^[가-힣]/.test(sorted[i].text))) { /* no space */ }\r\n // 3px+ 갭 = 공백 (단어 구분)\r\n else if (gap > 3) result += \" \"\r\n result += sorted[i].text\r\n }\r\n return result\r\n}\r\n\r\n\r\n\r\n\r\nexport function cleanPdfText(text: string): string {\r\n return mergeKoreanLines(\r\n text\r\n // 문서 시작 단독 페이지 번호\r\n .replace(/^\\d{1,4}\\n/, \"\")\r\n // \"- 2 -\" 스타일 페이지 번호 (독립 라인 및 목록 항목 형태 포함)\r\n .replace(/^[\\s]*[-–—]\\s*[-–—]?\\d+[-–—]?[\\s]*[-–—]?[\\s]*$/gm, \"\")\r\n // \"1 / 5\" 스타일 페이지 번호\r\n .replace(/^\\s*\\d+\\s*\\/\\s*\\d+\\s*$/gm, \"\")\r\n // 단독 페이지 번호 (줄 끝에 혼자 있는 숫자)\r\n .replace(/\\n\\d{1,4}\\n/g, \"\\n\")\r\n // 문서 마지막 단독 페이지 번호\r\n .replace(/\\n\\d{1,4}$/, \"\")\r\n // 단독 숫자 헤딩 제거 (\"# 6\\n재무과\" → \"\\n재무과\")\r\n .replace(/^#{1,6}\\s*\\d{1,4}\\s*$/gm, \"\")\r\n )\r\n // 균등배분 문자열 후처리 (pdfjs가 합친 TextItem + buildGridTable 셀 텍스트)\r\n .replace(/^(?!\\| ---).*$/gm, line => collapseEvenSpacing(line))\r\n // 마커 뒤 2글자 균등배분 합침 (\"□ 일 시\" → \"□ 일시\", \"□ 장 소\" → \"□ 장소\")\r\n .replace(/([□■◆○●▶ㅇ])\\s+([가-힣])\\s+([가-힣])/g, \"$1 $2$3\")\r\n .replace(/\\n{3,}/g, \"\\n\\n\")\r\n .trim()\r\n}\r\n\r\nfunction startsWithMarker(line: string): boolean {\r\n const t = line.trimStart()\r\n return /^[가-힣ㄱ-ㅎ][.)]/.test(t) || /^\\d+[.)]/.test(t) || /^\\([가-힣ㄱ-ㅎ\\d]+\\)/.test(t) ||\r\n /^[○●※▶▷◆◇■□★☆\\-·]\\s/.test(t) || /^제\\d+[조항호장절]/.test(t)\r\n}\r\n\r\nfunction isStandaloneHeader(line: string): boolean {\r\n return /^제\\d+[조항호장절](\\([^)]*\\))?(\\s+\\S+){0,7}$/.test(line.trim())\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 리스트 감지 — paragraph 블록 중 번호 패턴을 list 블록으로 변환\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/**\r\n * 연속된 paragraph 블록에서 번호 리스트 패턴을 감지하여 list 블록으로 변환.\r\n * \"비고\" 헤더 뒤에 오는 \"1.\", \"2.\" 패턴이 대표적.\r\n */\r\nfunction detectListBlocks(blocks: IRBlock[]): IRBlock[] {\r\n const result: IRBlock[] = []\r\n\r\n for (let i = 0; i < blocks.length; i++) {\r\n const block = blocks[i]\r\n\r\n if (block.type === \"paragraph\" && block.text) {\r\n const text = block.text.trim()\r\n // 번호 리스트: \"1.\", \"2.\" 등\r\n if (/^\\d+\\.\\s/.test(text)) {\r\n result.push({ ...block, type: \"list\", listType: \"ordered\", text: block.text })\r\n continue\r\n }\r\n // 비번호 리스트: ○, -, ·, ※, ▶ 등\r\n if (/^[○●·※▶▷◆◇\\-]\\s/.test(text)) {\r\n result.push({ ...block, type: \"list\", listType: \"unordered\", text: block.text })\r\n continue\r\n }\r\n }\r\n\r\n result.push(block)\r\n }\r\n\r\n return result\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 한국어 특수 테이블 감지 — \"구분/항목/종류\" 패턴 기반 key-value 테이블\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/**\r\n * ODL SpecialTableProcessor 포팅: 연속된 \"구분:\", \"항목:\", \"종류:\" 등\r\n * 한국어 key-value 패턴을 2열 테이블로 변환.\r\n *\r\n * 동작:\r\n * 1) paragraph 블록의 텍스트에서 한국어 key-value 패턴 감지\r\n * 2) \":\"가 있으면 key | value 2열, 없으면 colSpan=2 (전체 행)\r\n * 3) 연속된 패턴을 하나의 테이블로 그룹화\r\n */\r\nconst KOREAN_TABLE_HEADER_RE = /^\\(?(구분|항목|종류|분류|유형|대상|내용|기간|금액|비율|방법|절차|요건|조건|근거|목적|범위|기준)\\)?[:\\s]/\r\n\r\n/** KV 오탐 패턴: 시간(14:30), URL(://), 숫자:숫자(3:2) */\r\nconst KV_FALSE_POSITIVE_RE = /\\d{1,2}:\\d{2}|:\\/\\/|\\d+:\\d+/\r\n\r\nfunction detectSpecialKoreanTables(blocks: IRBlock[]): IRBlock[] {\r\n const result: IRBlock[] = []\r\n let kvLines: { key: string; value: string; block: IRBlock }[] = []\r\n\r\n const flushKvTable = () => {\r\n if (kvLines.length < 2) {\r\n // 2행 미만이면 테이블로 만들 가치 없음 → 원래 블록 복원\r\n for (const kv of kvLines) result.push(kv.block)\r\n kvLines = []\r\n return\r\n }\r\n\r\n // 2열 테이블 생성\r\n const cells: import(\"../types.js\").IRCell[][] = kvLines.map(kv => {\r\n if (kv.value) {\r\n return [\r\n { text: kv.key, colSpan: 1, rowSpan: 1 },\r\n { text: kv.value, colSpan: 1, rowSpan: 1 },\r\n ]\r\n }\r\n // \":\" 없는 줄 → 전체 행 (colSpan=2)\r\n return [\r\n { text: kv.key, colSpan: 2, rowSpan: 1 },\r\n { text: \"\", colSpan: 1, rowSpan: 1 },\r\n ]\r\n })\r\n\r\n const irTable: IRTable = {\r\n rows: cells.length,\r\n cols: 2,\r\n cells,\r\n hasHeader: true,\r\n }\r\n\r\n // 첫 블록의 위치 정보 사용\r\n const firstBlock = kvLines[0].block\r\n result.push({\r\n type: \"table\",\r\n table: irTable,\r\n pageNumber: firstBlock.pageNumber,\r\n bbox: firstBlock.bbox,\r\n })\r\n kvLines = []\r\n }\r\n\r\n for (const block of blocks) {\r\n if (block.type !== \"paragraph\" || !block.text) {\r\n flushKvTable()\r\n result.push(block)\r\n continue\r\n }\r\n\r\n const text = block.text.trim()\r\n\r\n // \"구분: xxx\" 또는 \"항목: xxx\" 패턴 매칭\r\n if (KOREAN_TABLE_HEADER_RE.test(text)) {\r\n const colonIdx = text.indexOf(\":\")\r\n if (colonIdx >= 0) {\r\n kvLines.push({\r\n key: text.slice(0, colonIdx).trim(),\r\n value: text.slice(colonIdx + 1).trim(),\r\n block,\r\n })\r\n } else {\r\n // \":\" 없이 공백으로 구분된 경우: \"구분 xxx\"\r\n const spaceIdx = text.search(/\\s/)\r\n if (spaceIdx > 0) {\r\n kvLines.push({\r\n key: text.slice(0, spaceIdx).trim(),\r\n value: text.slice(spaceIdx + 1).trim(),\r\n block,\r\n })\r\n } else {\r\n kvLines.push({ key: text, value: \"\", block })\r\n }\r\n }\r\n continue\r\n }\r\n\r\n // key-value 패턴이 아닌 블록이 나오면 축적된 것을 flush\r\n // 단, 이미 수집 중이고 현재 블록이 \"label: value\" 형태면 계속 수집\r\n if (kvLines.length > 0 && text.includes(\":\")) {\r\n // 오탐 제외: 시간(14:30), URL(http://), 숫자:숫자(3:2), 괄호 포함\r\n if (!KV_FALSE_POSITIVE_RE.test(text) && !text.includes(\"(\") && !text.includes(\")\")) {\r\n const colonIdx = text.indexOf(\":\")\r\n const key = text.slice(0, colonIdx).trim()\r\n // key가 순수 한글 2~8자 (공백/괄호 없음)면 유효한 key-value 라인\r\n if (/^[가-힣]+$/.test(key) && key.length >= 2 && key.length <= 8) {\r\n kvLines.push({\r\n key,\r\n value: text.slice(colonIdx + 1).trim(),\r\n block,\r\n })\r\n continue\r\n }\r\n }\r\n }\r\n\r\n flushKvTable()\r\n result.push(block)\r\n }\r\n\r\n flushKvTable()\r\n return result\r\n}\r\n\r\n// ─── 머리글/바닥글 감지 ────────────────────────────\r\n\r\n/** 상단/하단 10% 영역에서 페이지간 반복 텍스트를 감지하여 제거 대상 인덱스 반환 */\r\nfunction removeHeaderFooterBlocks(\r\n blocks: IRBlock[],\r\n pageHeights: Map<number, number>,\r\n warnings: ParseWarning[],\r\n): number[] {\r\n const ZONE_RATIO = 0.1 // 상하 10%\r\n const MIN_REPEAT = 3 // 최소 3페이지에서 반복\r\n\r\n // 페이지별 상단/하단 텍스트 수집\r\n const headerTexts = new Map<number, string[]>() // page → texts\r\n const footerTexts = new Map<number, string[]>()\r\n\r\n for (let bi = 0; bi < blocks.length; bi++) {\r\n const b = blocks[bi]\r\n if (!b.bbox || !b.pageNumber || !b.text?.trim()) continue\r\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\r\n if (!ph) continue\r\n\r\n const blockTop = ph - (b.bbox.y + b.bbox.height) // PDF Y좌표는 아래가 0\r\n const blockBottom = ph - b.bbox.y\r\n\r\n if (blockBottom <= ph * ZONE_RATIO) {\r\n // 하단 영역\r\n const arr = footerTexts.get(b.pageNumber) || []\r\n arr.push(b.text.trim())\r\n footerTexts.set(b.pageNumber, arr)\r\n } else if (blockTop >= ph * (1 - ZONE_RATIO)) {\r\n // 상단 영역\r\n const arr = headerTexts.get(b.pageNumber) || []\r\n arr.push(b.text.trim())\r\n headerTexts.set(b.pageNumber, arr)\r\n }\r\n }\r\n\r\n // 반복 패턴 찾기: 페이지 번호 변동 허용 (숫자만 다른 경우)\r\n const repeatedPatterns = new Set<string>()\r\n for (const textsMap of [headerTexts, footerTexts]) {\r\n const patternCount = new Map<string, number>()\r\n for (const [, texts] of textsMap) {\r\n for (const t of texts) {\r\n // 숫자를 와일드카드로 치환하여 \"- 1 -\", \"- 2 -\" 같은 패턴 통합\r\n const normalized = t.replace(/\\d+/g, \"#\")\r\n patternCount.set(normalized, (patternCount.get(normalized) || 0) + 1)\r\n }\r\n }\r\n for (const [pattern, count] of patternCount) {\r\n if (count >= MIN_REPEAT) repeatedPatterns.add(pattern)\r\n }\r\n }\r\n\r\n if (repeatedPatterns.size === 0) return []\r\n\r\n // 반복 패턴에 매칭되는 블록 인덱스 수집\r\n const removeIndices: number[] = []\r\n for (let bi = 0; bi < blocks.length; bi++) {\r\n const b = blocks[bi]\r\n if (!b.bbox || !b.pageNumber || !b.text?.trim()) continue\r\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\r\n if (!ph) continue\r\n\r\n const blockTop = ph - (b.bbox.y + b.bbox.height)\r\n const blockBottom = ph - b.bbox.y\r\n const inZone = blockBottom <= ph * ZONE_RATIO || blockTop >= ph * (1 - ZONE_RATIO)\r\n if (!inZone) continue\r\n\r\n const normalized = b.text.trim().replace(/\\d+/g, \"#\")\r\n if (repeatedPatterns.has(normalized)) {\r\n removeIndices.push(bi)\r\n }\r\n }\r\n\r\n if (removeIndices.length > 0) {\r\n warnings.push({ message: `${removeIndices.length}개 머리글/바닥글 요소 제거됨`, code: \"HIDDEN_TEXT_FILTERED\" })\r\n }\r\n\r\n return removeIndices\r\n}\r\n\r\nfunction mergeKoreanLines(text: string): string {\r\n if (!text) return \"\"\r\n const lines = text.split(\"\\n\")\r\n if (lines.length <= 1) return text\r\n const result: string[] = [lines[0]]\r\n\r\n for (let i = 1; i < lines.length; i++) {\r\n const prev = result[result.length - 1]\r\n const curr = lines[i]\r\n const currTrimmed = curr.trim()\r\n // 마크다운 헤딩/테이블/구분선은 병합하지 않음\r\n if (/^#{1,6}\\s/.test(prev) || /^#{1,6}\\s/.test(curr) || /^\\|/.test(currTrimmed) || /^---/.test(currTrimmed)) {\r\n result.push(curr)\r\n continue\r\n }\r\n // 쉼표로 끝나는 줄 + 다음 줄 = 연속 문장\r\n if (/,$/.test(prev.trim()) && currTrimmed.length > 0) {\r\n result[result.length - 1] = prev + \"\\n\" + curr\r\n continue\r\n }\r\n // (※ 로 시작하는 줄 = 이전 줄의 부연설명\r\n if (/^\\(※/.test(currTrimmed)) {\r\n result[result.length - 1] = prev + \" \" + currTrimmed\r\n continue\r\n }\r\n // 한글 줄바꿈 병합 — 마커(○, □ 등)로 시작하는 이전 줄은 합치지 않음\r\n if (/[가-힣·,\\-]$/.test(prev) && /^[가-힣(]/.test(curr) &&\r\n !startsWithMarker(curr) && !isStandaloneHeader(prev) &&\r\n !startsWithMarker(prev)) {\r\n result[result.length - 1] = prev + \" \" + curr\r\n } else {\r\n result.push(curr)\r\n }\r\n }\r\n return result.join(\"\\n\")\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;AAiBA,SAAS,WAAW;AAgDpB,IAAM,kBAAkB;AAExB,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AAEvB,IAAM,cAAc;AAEpB,IAAM,eAAe;AAErB,IAAM,gBAAgB;AAEtB,IAAM,iBAAiB;AAEvB,IAAM,sBAAsB;AAE5B,IAAM,sBAAsB;AAQrB,SAAS,aACd,SACA,WAC0D;AAC1D,QAAM,cAA6B,CAAC;AACpC,QAAM,YAA2B,CAAC;AAClC,MAAI,YAAY;AAEhB,MAAI,cAAyE,CAAC;AAC9E,MAAI,aAAa,GAAG,aAAa;AACjC,MAAI,OAAO,GAAG,OAAO;AAErB,WAAS,cACP,MACA,IAAY,IAAY,IAAY,IACpC;AACA,QAAI,KAAK,IAAI,EAAE,IAAI,kBAAkB,GAAG;AACtC,WAAK,KAAK,EAAE,IAAI,IAAI,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;AAAA,IACrE,WAAW,KAAK,IAAI,EAAE,IAAI,kBAAkB,GAAG;AAC7C,WAAK,KAAK,EAAE,IAAI,KAAK,KAAK,GAAG,IAAI,IAAI,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,GAAG,CAAC;AAAA,IACrE,OAAO;AACL,WAAK;AAAA,QACH,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG;AAAA,QACtC,EAAE,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,GAAG;AAAA,QAChD,EAAE,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG;AAAA,QAChD,EAAE,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,GAAG;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAEA,WAAS,UAAU,UAAmB;AACpC,QAAI,CAAC,UAAU;AAAE,oBAAc,CAAC;AAAG;AAAA,IAAO;AAC1C,eAAW,OAAO,aAAa;AAC7B,qBAAe,KAAK,WAAW,aAAa,SAAS;AAAA,IACvD;AACA,kBAAc,CAAC;AAAA,EACjB;AAEA,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,KAAK,QAAQ,CAAC;AACpB,UAAM,OAAO,UAAU,CAAC;AAExB,YAAQ,IAAI;AAAA,MACV,KAAK,IAAI;AACP,oBAAa,KAAkB,CAAC,KAAK;AACrC;AAAA,MAEF,KAAK,IAAI,eAAe;AACtB,cAAM,OAAO,KAAK,CAAC;AAEnB,YAAI,MAAM,QAAQ,IAAI,GAAG;AAEvB,gBAAM,SAAS;AACf,gBAAM,SAAU,KAA8B,CAAC;AAC/C,cAAI,KAAK;AAET,qBAAW,SAAS,QAAQ;AAC1B,gBAAI,UAAU,IAAI,QAAQ;AACxB,qBAAO,OAAO,IAAI;AAAG,qBAAO,OAAO,IAAI;AACvC,2BAAa;AAAM,2BAAa;AAAA,YAClC,WAAW,UAAU,IAAI,QAAQ;AAC/B,oBAAM,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO,IAAI;AACzC,0BAAY,KAAK,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,GAAG,CAAC;AAC/C,qBAAO;AAAI,qBAAO;AAAA,YACpB,WAAW,UAAU,IAAI,WAAW;AAClC,oBAAM,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO,IAAI;AACzC,oBAAM,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO,IAAI;AACzC,4BAAc,aAAa,IAAI,IAAI,IAAI,EAAE;AAAA,YAC3C,WAAW,UAAU,IAAI,WAAW;AAClC,kBAAI,SAAS,cAAc,SAAS,YAAY;AAC9C,4BAAY,KAAK,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,YAAY,IAAI,WAAW,CAAC;AAAA,cACzE;AACA,qBAAO;AAAY,qBAAO;AAAA,YAC5B,WAAW,UAAU,IAAI,SAAS;AAChC,oBAAM;AAAA,YACR,WAAW,UAAU,IAAI,YAAY,UAAU,IAAI,UAAU;AAC3D,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,OAAO;AAEL,gBAAM,UAAU;AAChB,gBAAM,UAAU,KAAK,CAAC;AACtB,gBAAM,WAAW,UAAU,CAAC;AAC5B,cAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,kBAAM,MAAM,OAAO,KAAK,QAAQ,EAAE;AAClC,gBAAI,KAAK;AACT,mBAAO,KAAK,KAAK;AACf,oBAAM,SAAS,SAAS,IAAI;AAC5B,kBAAI,WAAW,gBAAgB;AAC7B,uBAAO,SAAS,IAAI;AAAG,uBAAO,SAAS,IAAI;AAC3C,6BAAa;AAAM,6BAAa;AAAA,cAClC,WAAW,WAAW,gBAAgB;AACpC,sBAAM,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI;AAC7C,4BAAY,KAAK,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,GAAG,CAAC;AAC/C,uBAAO;AAAI,uBAAO;AAAA,cACpB,WAAW,WAAW,iBAAiB;AACrC,sBAAM;AAAA,cACR,WAAW,WAAW,0BAA0B;AAC9C,sBAAM;AAAA,cACR,WAAW,WAAW,mBAAmB;AACvC,oBAAI,SAAS,cAAc,SAAS,YAAY;AAC9C,8BAAY,KAAK,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,YAAY,IAAI,WAAW,CAAC;AAAA,gBACzE;AACA,uBAAO;AAAY,uBAAO;AAAA,cAC5B,OAAO;AACL;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAEA,cAAI,YAAY,IAAI,UAAU,YAAY,IAAI,aAAa;AACzD,sBAAU,IAAI;AAAA,UAChB,WAAW,YAAY,IAAI,QAAQ,YAAY,IAAI,UACxC,YAAY,IAAI,cAAc,YAAY,IAAI,gBAC9C,YAAY,IAAI,mBAAmB,YAAY,IAAI,mBAAmB;AAC/E,sBAAU,IAAI;AAAA,UAChB,WAAW,YAAY,IAAI,SAAS;AAClC,sBAAU,KAAK;AAAA,UACjB;AAAA,QACF;AACA;AAAA,MACF;AAAA,MAEA,KAAK,IAAI;AAAA,MACT,KAAK,IAAI;AACP,kBAAU,IAAI;AACd;AAAA,MAEF,KAAK,IAAI;AAAA,MACT,KAAK,IAAI;AAAA,MACT,KAAK,IAAI;AAAA,MACT,KAAK,IAAI;AAAA,MACT,KAAK,IAAI;AAAA,MACT,KAAK,IAAI;AACP,kBAAU,IAAI;AACd;AAAA,MAEF,KAAK,IAAI;AACP,kBAAU,KAAK;AACf;AAAA,IACJ;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,UAAU;AAClC;AAEA,SAAS,eACP,KACA,WACA,aACA,WACA;AACA,QAAM,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;AACnC,QAAM,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;AACnC,QAAM,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AAE1C,MAAI,SAAS,gBAAiB;AAE9B,MAAI,MAAM,iBAAiB;AACzB,UAAM,KAAK,IAAI,KAAK,IAAI,MAAM;AAC9B,UAAM,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,EAAE;AAClC,UAAM,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,EAAE;AAClC,gBAAY,KAAK,EAAE,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,UAAU,CAAC;AAAA,EACtD,WAAW,MAAM,iBAAiB;AAChC,UAAM,KAAK,IAAI,KAAK,IAAI,MAAM;AAC9B,UAAM,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,EAAE;AAClC,UAAM,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,EAAE;AAClC,cAAU,KAAK,EAAE,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,UAAU,CAAC;AAAA,EACpD;AACF;AAQO,SAAS,gBACd,aACA,WAC0D;AAE1D,MAAI,IAAI,YAAY,OAAO,OAAK,EAAE,aAAa,cAAc;AAC7D,MAAI,IAAI,UAAU,OAAO,OAAK,EAAE,aAAa,cAAc;AAG3D,MAAI,mBAAmB,GAAG,GAAG;AAC7B,MAAI,mBAAmB,GAAG,GAAG;AAE7B,SAAO,EAAE,aAAa,GAAG,WAAW,EAAE;AACxC;AAMA,SAAS,mBAAmB,OAAsB,KAA+B;AAC/E,MAAI,MAAM,UAAU,EAAG,QAAO;AAG9B,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AACvC,UAAM,OAAO,QAAQ,MAAM,EAAE,KAAK,EAAE;AACpC,UAAM,OAAO,QAAQ,MAAM,EAAE,KAAK,EAAE;AACpC,QAAI,KAAK,IAAI,OAAO,IAAI,IAAI,IAAK,QAAO,OAAO;AAE/C,WAAO,QAAQ,MAAO,EAAE,KAAK,EAAE,KAAO,EAAE,KAAK,EAAE;AAAA,EACjD,CAAC;AAED,QAAM,YAAY;AAElB,QAAM,SAAwB,CAAC,OAAO,CAAC,CAAC;AACxC,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,OAAO,CAAC;AAErB,UAAM,UAAU,QAAQ,MAAM,KAAK,KAAK,KAAK;AAC7C,UAAM,UAAU,QAAQ,MAAM,KAAK,KAAK,KAAK;AAE7C,QAAI,KAAK,IAAI,UAAU,OAAO,KAAK,WAAW;AAE5C,YAAM,YAAY,QAAQ,MAAM,KAAK,KAAK,KAAK;AAC/C,YAAM,UAAU,QAAQ,MAAM,KAAK,KAAK,KAAK;AAC7C,YAAM,YAAY,QAAQ,MAAM,KAAK,KAAK,KAAK;AAC/C,YAAM,UAAU,QAAQ,MAAM,KAAK,KAAK,KAAK;AAE7C,YAAM,UAAU,KAAK,IAAI,SAAS,OAAO,IAAI,KAAK,IAAI,WAAW,SAAS;AAC1E,YAAM,SAAS,KAAK,IAAI,UAAU,WAAW,UAAU,SAAS;AAEhE,UAAI,UAAU,SAAS,KAAK;AAE1B,YAAI,QAAQ,KAAK;AACf,eAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE;AACnC,eAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE;AACnC,eAAK,MAAM,KAAK,KAAK,KAAK,MAAM;AAChC,eAAK,KAAK,KAAK;AAAA,QACjB,OAAO;AACL,eAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE;AACnC,eAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE;AACnC,eAAK,MAAM,KAAK,KAAK,KAAK,MAAM;AAChC,eAAK,KAAK,KAAK;AAAA,QACjB;AACA,aAAK,YAAY,KAAK,IAAI,KAAK,WAAW,KAAK,SAAS;AACxD;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAIO,SAAS,sBACd,aACA,WACA,WACA,YAC0D;AAC1D,QAAM,SAAS;AACf,SAAO;AAAA,IACL,aAAa,YAAY;AAAA,MAAO,OAC9B,EAAE,KAAK,IAAI,EAAE,EAAE,IAAI,UAAU,KAAK,IAAI,EAAE,KAAK,UAAU,IAAI,WAC1D,EAAE,KAAK,EAAE,KAAM,YAAY;AAAA,IAC9B;AAAA,IACA,WAAW,UAAU;AAAA,MAAO,OAC1B,EAAE,KAAK,IAAI,EAAE,EAAE,IAAI,UAAU,KAAK,IAAI,EAAE,KAAK,SAAS,IAAI,WACzD,EAAE,KAAK,EAAE,KAAM,aAAa;AAAA,IAC/B;AAAA,EACF;AACF;AASA,SAAS,cAAc,aAA4B,WAAoC;AACrF,QAAM,WAAqB,CAAC;AAC5B,QAAM,MAAM;AAEZ,aAAW,KAAK,aAAa;AAC3B,eAAW,KAAK,WAAW;AAGzB,UAAI,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,OACrC,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK;AAC5C,cAAM,SAAS,KAAK,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC;AACnD,iBAAS,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,IAAI,OAAO,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,cAAc,UAA8B;AACnD,MAAI,SAAS,UAAU,EAAG,QAAO;AAEjC,QAAM,SAAmB,CAAC;AAC1B,QAAM,OAAO,IAAI,MAAM,SAAS,MAAM,EAAE,KAAK,KAAK;AAElD,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,QAAI,KAAK,CAAC,EAAG;AACb,QAAI,OAAO,SAAS,CAAC,EAAE,GAAG,OAAO,SAAS,CAAC,EAAE;AAC7C,QAAI,YAAY,SAAS,CAAC,EAAE;AAC5B,QAAI,QAAQ;AAEZ,aAAS,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AAC5C,UAAI,KAAK,CAAC,EAAG;AACb,YAAM,WAAW,sBAAsB,KAAK,IAAI,WAAW,SAAS,CAAC,EAAE,MAAM;AAC7E,UAAI,KAAK,IAAI,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,EAAE,CAAC,KAAK,YAC3C,KAAK,IAAI,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,EAAE,CAAC,KAAK,UAAU;AACvD,gBAAQ,SAAS,CAAC,EAAE;AACpB,gBAAQ,SAAS,CAAC,EAAE;AACpB,oBAAY,KAAK,IAAI,WAAW,SAAS,CAAC,EAAE,MAAM;AAClD;AACA,aAAK,CAAC,IAAI;AAAA,MACZ;AAAA,IACF;AAEA,WAAO,KAAK,EAAE,GAAG,OAAO,OAAO,GAAG,OAAO,OAAO,QAAQ,UAAU,CAAC;AAAA,EACrE;AAEA,SAAO;AACT;AAaO,SAAS,gBACd,aACA,WACa;AACb,MAAI,YAAY,SAAS,KAAK,UAAU,SAAS,EAAG,QAAO,CAAC;AAG5D,QAAM,cAAc,cAAc,aAAa,SAAS;AACxD,QAAM,WAAW,cAAc,WAAW;AAE1C,MAAI,SAAS,SAAS,EAAG,QAAO,CAAC;AAGjC,QAAM,eAAe,SAAS,OAAO,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,MAAM,GAAG,CAAC;AAG3E,QAAM,WAAW;AAAA,IACf,GAAG,YAAY,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,GAAG,MAAM,KAAc,IAAI,EAAE,EAAE;AAAA,IAClE,GAAG,UAAU,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,GAAG,MAAM,KAAc,IAAI,IAAI,YAAY,OAAO,EAAE;AAAA,EACvF;AAEA,QAAM,SAAS,oBAAoB,QAAQ;AAC3C,QAAM,QAAqB,CAAC;AAE5B,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,MAAM,OAAO,OAAK,EAAE,SAAS,GAAG;AAC/C,UAAM,SAAS,MAAM,OAAO,OAAK,EAAE,SAAS,GAAG;AAE/C,QAAI,OAAO,SAAS,KAAK,OAAO,SAAS,EAAG;AAG5C,QAAI,MAAM,UAAU,MAAM,UAAU,MAAM,WAAW,MAAM;AAC3D,eAAW,KAAK,QAAQ;AAAE,UAAI,EAAE,KAAK,IAAK,OAAM,EAAE;AAAI,UAAI,EAAE,KAAK,IAAK,OAAM,EAAE;AAAA,IAAG;AACjF,eAAW,KAAK,QAAQ;AAAE,UAAI,EAAE,KAAK,IAAK,OAAM,EAAE;AAAI,UAAI,EAAE,KAAK,IAAK,OAAM,EAAE;AAAA,IAAG;AACjF,UAAM,YAAY;AAAA,MAChB,IAAI,MAAM;AAAA,MACV,IAAI,MAAM;AAAA,MACV,IAAI,MAAM;AAAA,MACV,IAAI,MAAM;AAAA,IACZ;AAEA,UAAM,gBAAgB,SAAS;AAAA,MAAO,OACpC,EAAE,KAAK,UAAU,MAAM,EAAE,KAAK,UAAU,MACxC,EAAE,KAAK,UAAU,MAAM,EAAE,KAAK,UAAU;AAAA,IAC1C;AAGA,UAAM,cAAc,cAAc,SAAS,IACvC,cAAc,OAAO,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,MAAM,GAAG,CAAC,IAC3D;AAGJ,UAAM,gBAAgB,KAAK,IAAI,sBAAsB,aAAa,mBAAmB;AAGrF,UAAM,QAAQ;AAAA,MACZ,GAAG,OAAO,IAAI,OAAK,EAAE,EAAE;AAAA,MACvB,GAAG,cAAc,IAAI,OAAK,EAAE,CAAC;AAAA,IAC/B;AACA,UAAM,QAAQ,mBAAmB,OAAO,aAAa,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAG3E,UAAM,QAAQ;AAAA,MACZ,GAAG,OAAO,IAAI,OAAK,EAAE,EAAE;AAAA,MACvB,GAAG,cAAc,IAAI,OAAK,EAAE,CAAC;AAAA,IAC/B;AACA,UAAM,QAAQ,mBAAmB,OAAO,aAAa,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAE3E,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG;AAG1C,UAAM,aAAa,gBAAgB,OAAO,aAAa;AACvD,UAAM,aAAa,iBAAiB,OAAO,cAAc;AAEzD,QAAI,WAAW,SAAS,KAAK,WAAW,SAAS,EAAG;AAEpD,UAAM,OAAO;AAAA,MACX,IAAI,WAAW,CAAC;AAAA,MAAG,IAAI,WAAW,WAAW,SAAS,CAAC;AAAA,MACvD,IAAI,WAAW,WAAW,SAAS,CAAC;AAAA,MAAG,IAAI,WAAW,CAAC;AAAA,IACzD;AAEA,UAAM,KAAK,EAAE,OAAO,YAAY,OAAO,YAAY,MAAM,cAAc,YAAY,CAAC;AAAA,EACtF;AAEA,SAAO,mBAAmB,KAAK;AACjC;AAGA,SAAS,gBAAgB,OAAiB,UAA4B;AACpE,MAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,QAAM,SAAmB,CAAC,MAAM,CAAC,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,OAAO,OAAO,SAAS,CAAC;AACtC,QAAI,MAAM,CAAC,IAAI,QAAQ,YAAY,IAAI,MAAM,SAAS,GAAG;AAEvD;AAAA,IACF;AACA,WAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EACtB;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,OAAiB,WAA6B;AACtE,MAAI,MAAM,UAAU,EAAG,QAAO;AAE9B,QAAM,SAAmB,CAAC,MAAM,CAAC,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,OAAO,OAAO,SAAS,CAAC;AACtC,QAAI,QAAQ,MAAM,CAAC,IAAI,aAAa,IAAI,MAAM,SAAS,GAAG;AACxD;AAAA,IACF;AACA,WAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EACtB;AACA,SAAO;AACT;AAGA,SAAS,mBAAmB,OAAiC;AAC3D,MAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE;AAC9D,QAAM,SAAsB,CAAC,OAAO,CAAC,CAAC;AAEtC,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,OAAO,CAAC;AAErB,QAAI,KAAK,MAAM,WAAW,KAAK,MAAM,QAAQ;AAC3C,YAAM,WAAW,KAAK,IAAI,sBAAsB,KAAK,IAAI,KAAK,cAAc,KAAK,YAAY,GAAG,CAAC,IAAI;AACrG,YAAM,WAAW,KAAK,MAAM,MAAM,CAAC,GAAG,OAAO,KAAK,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC,KAAK,QAAQ;AACrF,YAAM,cAAc,KAAK,KAAK,KAAK,KAAK,KAAK;AAC7C,UAAI,YAAY,eAAe,CAAC,eAAe,eAAe,IAAI;AAChE,cAAM,WAAW,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAClF,eAAO,OAAO,SAAS,CAAC,IAAI;AAAA,UAC1B,OAAO;AAAA,UACP,OAAO,KAAK;AAAA,UACZ,MAAM;AAAA,YACJ,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,YACvC,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,YACvC,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,YACvC,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE;AAAA,UACzC;AAAA,UACA,cAAc,KAAK,IAAI,KAAK,cAAc,KAAK,YAAY;AAAA,QAC7D;AACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAGA,SAAS,mBAAmB,QAAkB,WAA6B;AACzE,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,QAAM,WAA6C,CAAC,EAAE,KAAK,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;AAEhF,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,OAAO,SAAS,SAAS,SAAS,CAAC;AACzC,UAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,QAAI,KAAK,IAAI,OAAO,CAAC,IAAI,GAAG,KAAK,WAAW;AAC1C,WAAK,OAAO,OAAO,CAAC;AACpB,WAAK;AAAA,IACP,OAAO;AACL,eAAS,KAAK,EAAE,KAAK,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;AAAA,IAC5C;AAAA,EACF;AAEA,SAAO,SAAS,IAAI,OAAK,EAAE,MAAM,EAAE,KAAK;AAC1C;AAKA,SAAS,oBAAoB,OAAmC;AAC9D,QAAM,SAAS,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC;AAEpC,WAAS,KAAK,GAAmB;AAC/B,WAAO,OAAO,CAAC,MAAM,GAAG;AAAE,aAAO,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AAAG,UAAI,OAAO,CAAC;AAAA,IAAE;AACvE,WAAO;AAAA,EACT;AACA,WAAS,MAAM,GAAW,GAAW;AACnC,UAAM,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC;AAC/B,QAAI,OAAO,GAAI,QAAO,EAAE,IAAI;AAAA,EAC9B;AAEA,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,aAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,UAAI,eAAe,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG;AACtC,cAAM,GAAG,CAAC;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,oBAAI,IAAyB;AAC5C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,KAAK,CAAC;AACnB,QAAI,CAAC,OAAO,IAAI,IAAI,EAAG,QAAO,IAAI,MAAM,CAAC,CAAC;AAC1C,WAAO,IAAI,IAAI,EAAG,KAAK,MAAM,CAAC,CAAC;AAAA,EACjC;AAEA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC5B;AAGA,SAAS,eAAe,GAAc,GAAuB;AAC3D,MAAI,EAAE,SAAS,EAAE,MAAM;AACrB,QAAI,EAAE,SAAS,KAAK;AAClB,UAAI,KAAK,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,YAAa,QAAO;AAChD,aAAO,KAAK,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,KAAK,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI;AAAA,IACxD,OAAO;AACL,UAAI,KAAK,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,YAAa,QAAO;AAChD,aAAO,KAAK,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,KAAK,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,IAAI,EAAE,SAAS,MAAM,IAAI;AAC/B,QAAM,IAAI,EAAE,SAAS,MAAM,IAAI;AAC/B,QAAM,MAAM;AAEZ,SACE,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,OACrC,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK;AAEzC;AAYO,SAAS,aACd,MACA,aACA,WACiB;AACjB,QAAM,EAAE,OAAO,MAAM,IAAI;AACzB,QAAM,UAAU,MAAM,SAAS;AAC/B,QAAM,UAAU,MAAM,SAAS;AAC/B,MAAI,WAAW,KAAK,WAAW,EAAG,QAAO,CAAC;AAI1C,QAAM,WAAwB,MAAM;AAAA,IAAK,EAAE,QAAQ,QAAQ;AAAA,IACzD,CAAC,GAAG,MAAM,MAAM;AAAA,MAAK,EAAE,QAAQ,UAAU,EAAE;AAAA,MACzC,CAACA,IAAG,MAAM,gBAAgB,WAAW,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,KAAK,YAAY;AAAA,IAAC;AAAA,EAAC;AAG9F,QAAM,WAAwB,MAAM;AAAA,IAAK,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC7D,CAAC,GAAG,MAAM,MAAM;AAAA,MAAK,EAAE,QAAQ,QAAQ;AAAA,MACrC,CAACA,IAAG,MAAM,kBAAkB,aAAa,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,KAAK,YAAY;AAAA,IAAC;AAAA,EAAC;AAGlG,QAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,MAAM,MAAM,OAAO,EAAE,KAAK,KAAK,CAAC;AACjF,QAAM,QAAyB,CAAC;AAEhC,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAI,SAAS,CAAC,EAAE,CAAC,EAAG;AAEpB,UAAI,UAAU;AACd,UAAI,UAAU;AAGd,aAAO,IAAI,UAAU,WAAW,CAAC,SAAS,CAAC,EAAE,IAAI,OAAO,GAAG;AAEzD,YAAI,YAAY;AAChB,iBAAS,KAAK,GAAG,KAAK,SAAS,MAAM;AACnC,cAAI,SAAS,IAAI,EAAE,EAAE,IAAI,OAAO,GAAG;AAAE,wBAAY;AAAO;AAAA,UAAM;AAAA,QAChE;AACA,YAAI,CAAC,UAAW;AAChB;AAAA,MACF;AAGA,aAAO,IAAI,UAAU,SAAS;AAC5B,YAAI,UAAU;AACd,iBAAS,KAAK,GAAG,KAAK,SAAS,MAAM;AACnC,cAAI,SAAS,IAAI,OAAO,EAAE,IAAI,EAAE,GAAG;AAAE,sBAAU;AAAM;AAAA,UAAM;AAAA,QAC7D;AACA,YAAI,QAAS;AACb;AAAA,MACF;AAGA,eAAS,KAAK,GAAG,KAAK,SAAS,MAAM;AACnC,iBAAS,KAAK,GAAG,KAAK,SAAS,MAAM;AACnC,mBAAS,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI;AAAA,QAC7B;AAAA,MACF;AAEA,YAAM,KAAK;AAAA,QACT,KAAK;AAAA,QAAG,KAAK;AAAA,QAAG;AAAA,QAAS;AAAA,QACzB,MAAM;AAAA,UACJ,IAAI,MAAM,CAAC;AAAA,UAAG,IAAI,MAAM,IAAI,OAAO;AAAA,UACnC,IAAI,MAAM,IAAI,OAAO;AAAA,UAAG,IAAI,MAAM,CAAC;AAAA,QACrC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,gBACP,WAA0B,GAAW,MAAc,MAAc,cACxD;AACT,QAAM,MAAM,KAAK,IAAI,sBAAsB,cAAc,CAAC;AAC1D,aAAW,KAAK,WAAW;AACzB,QAAI,KAAK,IAAI,EAAE,KAAK,CAAC,KAAK,KAAK;AAC7B,YAAM,QAAQ,KAAK,IAAI,OAAO,IAAI;AAClC,UAAI,QAAQ,IAAK;AACjB,YAAM,aAAa,KAAK,IAAI,EAAE,IAAI,IAAI;AACtC,YAAM,aAAa,KAAK,IAAI,EAAE,IAAI,IAAI;AACtC,YAAM,UAAU,aAAa;AAE7B,UAAI,WAAW,QAAQ,KAAM,QAAO;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,kBACP,aAA4B,GAAW,OAAe,QAAgB,cAC7D;AACT,QAAM,MAAM,KAAK,IAAI,sBAAsB,cAAc,CAAC;AAC1D,aAAW,KAAK,aAAa;AAC3B,QAAI,KAAK,IAAI,EAAE,KAAK,CAAC,KAAK,KAAK;AAC7B,YAAM,QAAQ,KAAK,IAAI,SAAS,KAAK;AACrC,UAAI,QAAQ,IAAK;AACjB,YAAM,cAAc,KAAK,IAAI,EAAE,IAAI,KAAK;AACxC,YAAM,eAAe,KAAK,IAAI,EAAE,IAAI,MAAM;AAC1C,YAAM,UAAU,eAAe;AAC/B,UAAI,WAAW,QAAQ,KAAM,QAAO;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAeO,SAAS,eACd,OACA,OACgC;AAChC,QAAM,SAAS,oBAAI,IAA+B;AAClD,aAAW,QAAQ,OAAO;AACxB,WAAO,IAAI,MAAM,CAAC,CAAC;AAAA,EACrB;AAEA,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM;AAEZ,QAAI,WAAiC;AACrC,QAAI,YAAY;AAEhB,eAAW,QAAQ,OAAO;AAExB,YAAM,MAAM,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,KAAK,GAAG;AAC/C,YAAM,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,KAAK,GAAG;AACxD,YAAM,MAAM,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,KAAK,GAAG;AAC/C,YAAM,MAAM,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW,KAAK,KAAK,KAAK,GAAG;AAE3E,UAAI,OAAO,OAAO,OAAO,IAAK;AAE9B,YAAM,iBAAiB,MAAM,QAAQ,MAAM;AAC3C,YAAM,WAAW,KAAK,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,UAAU,CAAC;AAC1E,YAAM,QAAQ,gBAAgB;AAE9B,UAAI,QAAQ,WAAW;AACrB,oBAAY;AACZ,mBAAW;AAAA,MACb;AAAA,IACF;AAGA,QAAI,YAAY,YAAY,KAAK;AAC/B,aAAO,IAAI,QAAQ,EAAG,KAAK,IAAI;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,iBAAiB,OAA2B;AAC1D,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC,EAAE;AAGxC,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC/D,QAAM,QAAsB,CAAC;AAC7B,MAAI,UAAsB,CAAC,OAAO,CAAC,CAAC;AACpC,MAAI,OAAO,OAAO,CAAC,EAAE;AAErB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,CAAC,EAAE,UAAU,QAAQ,CAAC,EAAE,QAAQ,IAAI,GAAG;AAC/E,QAAI,KAAK,IAAI,OAAO,CAAC,EAAE,IAAI,IAAI,KAAK,KAAK;AACvC,cAAQ,KAAK,OAAO,CAAC,CAAC;AAAA,IACxB,OAAO;AACL,YAAM,KAAK,OAAO;AAClB,gBAAU,CAAC,OAAO,CAAC,CAAC;AACpB,aAAO,OAAO,CAAC,EAAE;AAAA,IACnB;AAAA,EACF;AACA,QAAM,KAAK,OAAO;AAGlB,QAAM,YAAY,MAAM,IAAI,UAAQ;AAClC,UAAM,IAAI,KAAK,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AACvC,QAAI,EAAE,WAAW,EAAG,QAAO,EAAE,CAAC,EAAE;AAGhC,UAAM,aAAa,sBAAsB,CAAC;AAE1C,QAAI,SAAS,EAAE,CAAC,EAAE;AAClB,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAEjC,UAAI,WAAW,CAAC,GAAG;AACjB,kBAAU,EAAE,CAAC,EAAE;AACf;AAAA,MACF;AAEA,YAAM,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;AAC5C,YAAM,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,YAAY;AACpD,YAAM,eAAe,SAAS,KAAK,MAAM;AACzC,YAAM,eAAe,SAAS,KAAK,EAAE,CAAC,EAAE,IAAI;AAC5C,UAAI,MAAM,QAAQ,MAAM;AACtB,kBAAU,EAAE,CAAC,EAAE;AAAA,MACjB,WAAW,MAAM,QAAQ,SAAS,gBAAgB,eAAe;AAC/D,kBAAU,EAAE,CAAC,EAAE;AAAA,MACjB,OAAO;AACL,kBAAU,MAAM,EAAE,CAAC,EAAE;AAAA,MACvB;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,mBAAmB,SAAS;AACrC;AAOA,SAAS,sBAAsB,OAA8B;AAC3D,QAAM,SAAS,IAAI,MAAM,MAAM,MAAM,EAAE,KAAK,KAAK;AACjD,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,MAAI,WAAW;AACf,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AAErC,UAAM,gBAAgB,aAAa,KAAK,MAAM,CAAC,EAAE,IAAI,KAAK,YAAY,KAAK,MAAM,CAAC,EAAE,IAAI;AAGxF,QAAI,iBAAiB,YAAY,KAAK,IAAI,GAAG;AAC3C,YAAM,MAAM,MAAM,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,EAAE,IAAI,MAAM,IAAI,CAAC,EAAE;AACxD,YAAM,YAAY,KAAK,IAAI,MAAM,CAAC,EAAE,WAAW,GAAG,EAAE;AACpD,UAAI,MAAM,WAAW;AACnB,YAAI,IAAI,YAAY,EAAG,aAAY,OAAO,QAAQ,UAAU,CAAC;AAC7D,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AAEA,QAAI,eAAe;AACjB,UAAI,WAAW,EAAG,YAAW;AAAA,IAC/B,OAAO;AACL,UAAI,YAAY,KAAK,IAAI,YAAY,GAAG;AACtC,oBAAY,OAAO,QAAQ,UAAU,CAAC;AAAA,MACxC;AACA,iBAAW;AAAA,IACb;AAAA,EACF;AACA,MAAI,YAAY,KAAK,MAAM,SAAS,YAAY,GAAG;AACjD,gBAAY,OAAO,QAAQ,UAAU,MAAM,MAAM;AAAA,EACnD;AAEA,SAAO;AACT;AAEA,SAAS,YAAY,OAAmB,QAAmB,OAAe,KAAmB;AAC3F,QAAM,OAAiB,CAAC;AACxB,WAAS,IAAI,QAAQ,GAAG,IAAI,KAAK,KAAK;AACpC,SAAK,KAAK,MAAM,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,EAAE,IAAI,MAAM,IAAI,CAAC,EAAE,EAAE;AAAA,EAC1D;AACA,QAAM,UAAU,KAAK,OAAO,CAAAC,OAAKA,KAAI,CAAC;AACtC,MAAI,QAAQ,SAAS,EAAG;AAExB,MAAI,SAAS,UAAU,SAAS;AAChC,aAAWA,MAAK,SAAS;AAAE,QAAIA,KAAI,OAAQ,UAASA;AAAG,QAAIA,KAAI,OAAQ,UAASA;AAAA,EAAE;AAClF,QAAM,QAAQ,MAAM,KAAK,EAAE;AAG3B,MAAI,UAAU,QAAQ,OAAO,UAAU,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ,GAAG,KAAK,GAAG;AACvF,aAAS,IAAI,QAAQ,GAAG,IAAI,KAAK,KAAK;AACpC,aAAO,CAAC,IAAI;AAAA,IACd;AAAA,EACF;AACF;AAQA,SAAS,mBAAmB,WAA6B;AAEvD,MAAI,UAAU,UAAU,EAAG,QAAO,UAAU,CAAC,KAAK;AAClD,QAAM,SAAmB,CAAC,UAAU,CAAC,CAAC;AACtC,WAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,UAAU,CAAC;AACxB,QAAI,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC,KAAK,SAAS,GAAG,GAAG;AAC3F,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO;AAAA,IACrC,WACS,KAAK,KAAK,EAAE,UAAU,KAAK,WAAW,KAAK,KAAK,KAAK,CAAC,GAAG;AAChE,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,KAAK,KAAK;AAAA,IAC/C,WACS,QAAQ,KAAK,KAAK,KAAK,CAAC,KAAK,KAAK,KAAK,EAAE,UAAU,IAAI;AAC9D,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,KAAK,KAAK;AAAA,IAC/C,WACS,SAAS,KAAK,IAAI,KAAK,iBAAiB,KAAK,KAAK,KAAK,CAAC,KAAK,KAAK,KAAK,EAAE,UAAU,IAAI;AAC9F,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,KAAK,KAAK;AAAA,IAC/C,OACK;AACH,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AACA,SAAO,OAAO,KAAK,IAAI;AACzB;;;ACh8BA,IAAM,QAAQ;AAEd,IAAM,kBAAkB;AAExB,IAAM,WAAW;AAEjB,IAAM,WAAW;AAEjB,IAAM,iBAAiB;AAEvB,IAAM,mBAAmB;AAEzB,IAAM,qBAAqB;AA2BpB,SAAS,oBAAoB,OAAsB,SAAuC;AAC/F,MAAI,MAAM,SAAS,WAAW,SAAU,QAAO,CAAC;AAGhD,QAAM,EAAE,QAAQ,UAAU,IAAI,wBAAwB,KAAK;AAG3D,QAAM,OAAO,gBAAgB,MAAM;AACnC,MAAI,KAAK,SAAS,SAAU,QAAO,CAAC;AAEpC,QAAM,UAAgC,CAAC;AAGvC,QAAM,eAAe,gBAAgB,IAAI;AAEzC,MAAI,cAAc;AAEhB,UAAM,EAAE,SAAS,UAAU,IAAI;AAC/B,UAAM,YAAY,KAAK,SAAS;AAChC,UAAM,cAAc,CAAC,GAAG,UAAU,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AACjE,UAAM,iBAAiB,KAAK,MAAM,SAAS;AAE3C,UAAM,aAAa,mBAAmB,gBAAgB,OAAO;AAE7D,UAAM,eAAe,yBAAyB,YAAY,SAAS,WAAW;AAC9E,eAAW,UAAU,cAAc;AACjC,YAAM,QAAQ,kBAAkB,OAAO,MAAM,SAAS,OAAO;AAC7D,UAAI,OAAO;AACT,wBAAgB,MAAM,WAAW,SAAS;AAC1C,gBAAQ,KAAK,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,GAAG;AAExB,UAAM,iBAAiB,KAAK,OAAO,SAAO,kBAAkB,GAAG,CAAC;AAChE,QAAI,eAAe,UAAU,UAAU;AACrC,YAAM,UAAU,sBAAsB,cAAc;AACpD,UAAI,QAAQ,UAAU,UAAU;AAC9B,cAAM,eAAe,iBAAiB,MAAM,OAAO;AACnD,mBAAW,UAAU,cAAc;AACjC,gBAAM,aAAa,mBAAmB,OAAO,MAAM,OAAO;AAC1D,gBAAM,QAAQ,kBAAkB,YAAY,SAAS,OAAO;AAC5D,cAAI,OAAO;AACT,4BAAgB,MAAM,WAAW,SAAS;AAC1C,oBAAQ,KAAK,KAAK;AAAA,UACpB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,wBACP,OACuE;AACvE,QAAM,YAAY,oBAAI,IAAgC;AACtD,QAAM,OAAO,gBAAgB,KAAK;AAClC,QAAM,SAAwB,CAAC;AAE/B,aAAW,OAAO,MAAM;AACtB,UAAM,SAAS,CAAC,GAAG,IAAI,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AACtD,QAAI,IAAI;AACR,WAAO,IAAI,OAAO,QAAQ;AACxB,UAAI,YAAY,KAAK,OAAO,CAAC,EAAE,IAAI,GAAG;AACpC,YAAI,SAAS,IAAI;AACjB,eAAO,SAAS,OAAO,UAAU,YAAY,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG;AACtE,gBAAM,MAAM,OAAO,MAAM,EAAE,KAAK,OAAO,SAAS,CAAC,EAAE,IAAI,OAAO,SAAS,CAAC,EAAE;AAC1E,gBAAM,KAAK,OAAO,MAAM,EAAE;AAC1B,cAAI,MAAM,KAAK,OAAO,MAAM,KAAK,EAAG;AACpC;AAAA,QACF;AACA,YAAI,SAAS,KAAK,GAAG;AACnB,gBAAM,OAAiB,CAAC;AACxB,mBAASC,KAAI,IAAI,GAAGA,KAAI,QAAQA,MAAK;AACnC,iBAAK,KAAK,OAAOA,EAAC,EAAE,KAAK,OAAOA,KAAI,CAAC,EAAE,IAAI,OAAOA,KAAI,CAAC,EAAE,EAAE;AAAA,UAC7D;AACA,cAAI,OAAO,UAAU,OAAO;AAC5B,qBAAWA,MAAK,MAAM;AAAE,gBAAIA,KAAI,KAAM,QAAOA;AAAG,gBAAIA,KAAI,KAAM,QAAOA;AAAA,UAAE;AACvE,cAAI,OAAO,KAAK,OAAO,QAAQ,GAAG;AAChC,kBAAM,MAAM,OAAO,MAAM,GAAG,MAAM;AAClC,kBAAM,OAAO,IAAI,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,EAAE;AACzC,kBAAM,QAAQ,IAAI,CAAC,GAAG,OAAO,IAAI,SAAS,IAAI,CAAC;AAC/C,kBAAM,OAAoB;AAAA,cACxB;AAAA,cAAM,GAAG,MAAM;AAAA,cAAG,GAAG,MAAM;AAAA,cAC3B,GAAI,KAAK,IAAI,KAAK,IAAK,MAAM;AAAA,cAAG,GAAG,MAAM;AAAA,cACzC,UAAU,MAAM;AAAA,cAAU,UAAU,MAAM;AAAA,YAC5C;AACA,sBAAU,IAAI,MAAM,GAAG;AACvB,mBAAO,KAAK,IAAI;AAChB,gBAAI;AACJ;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,aAAO,KAAK,OAAO,CAAC,CAAC;AACrB;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,UAAU;AAC7B;AAGA,SAAS,gBAAgB,WAA6B,WAAkD;AACtG,QAAM,QAAuB,CAAC;AAC9B,aAAW,QAAQ,WAAW;AAC5B,UAAM,UAAU,UAAU,IAAI,IAAI;AAClC,QAAI,QAAS,YAAW,KAAK,QAAS,OAAM,KAAK,CAAC;AAAA,EACpD;AACA,aAAW,KAAK,MAAO,WAAU,IAAI,CAAC;AACxC;AASA,SAAS,gBAAgB,MAAuC;AAC9D,QAAM,WAAW,KAAK,QAAQ,OAAK,EAAE,KAAK;AAC1C,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,UAAU,UAAU,UAAU;AAClC,aAAW,KAAK,UAAU;AAAE,QAAI,EAAE,IAAI,QAAS,WAAU,EAAE;AAAG,UAAM,IAAI,EAAE,IAAI,EAAE;AAAG,QAAI,IAAI,QAAS,WAAU;AAAA,EAAE;AAChH,QAAM,WAAW,UAAU;AAC3B,MAAI,YAAY,EAAG,QAAO;AAG1B,WAAS,KAAK,GAAG,KAAK,KAAK,QAAQ,MAAM;AACvC,UAAM,MAAM,KAAK,EAAE;AAEnB,QAAI,IAAI,MAAM,SAAS,YAAY,IAAI,MAAM,SAAS,EAAG;AAEzD,QAAI,IAAI,MAAM,KAAK,OAAK,EAAE,KAAK,SAAS,CAAC,EAAG;AAE5C,QAAI,CAAC,IAAI,MAAM,KAAK,OAAK,QAAQ,KAAK,EAAE,IAAI,CAAC,EAAG;AAEhD,QAAI,IAAI,MAAM,KAAK,OAAK,kBAAkB,KAAK,EAAE,IAAI,CAAC,EAAG;AAEzD,UAAM,SAAS,CAAC,GAAG,IAAI,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AACtD,UAAM,QAAS,OAAO,OAAO,SAAS,CAAC,EAAE,IAAI,OAAO,OAAO,SAAS,CAAC,EAAE,IAAK,OAAO,CAAC,EAAE;AAGtF,QAAI,QAAQ,WAAW,IAAK;AAG5B,UAAM,QAAQ,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,UAAU,CAAC,IAAI,OAAO;AAClE,QAAI,cAAc;AAClB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,MAAM,OAAO,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE;AAC3D,UAAI,OAAO,QAAQ,KAAK;AAAE,sBAAc;AAAM;AAAA,MAAM;AAAA,IACtD;AACA,QAAI,CAAC,YAAa;AAGlB,UAAM,UAAwB,OAAO,IAAI,WAAS,EAAE,GAAG,KAAK,GAAG,OAAO,EAAE,EAAE;AAG1E,QAAI,aAAa;AACjB,aAAS,IAAI,KAAK,GAAG,IAAI,KAAK,UAAU,aAAa,WAAW,GAAG,KAAK;AACtE,YAAM,UAAU,yBAAyB,KAAK,CAAC,GAAG,SAAS,MAAM;AACjE,UAAI,WAAW,SAAU;AAAA,IAC3B;AACA,QAAI,aAAa,SAAU;AAE3B,WAAO,EAAE,SAAS,WAAW,GAAG;AAAA,EAClC;AACA,SAAO;AACT;AAKA,SAAS,mBAAmB,MAAkB,SAAmC;AAC/E,MAAI,KAAK,UAAU,EAAG,QAAO;AAC7B,QAAM,SAAqB,CAAC,KAAK,CAAC,CAAC;AACnC,QAAM,eAAe,KAAK,QAAQ,OAAK,EAAE,KAAK,EAAE,IAAI,OAAK,EAAE,QAAQ;AACnE,QAAM,cAAc,aAAa,SAAS,IACtC,aAAa,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,aAAa,SAAS;AAEpE,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,KAAK,CAAC;AACnB,UAAM,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC;AACrC,UAAM,cAAc,oBAAoB,MAAM,OAAO;AAIrD,QAAI,OAAO,cAAc,OAAO,KAAK,MAAM,UAAU,MAAM,cAAc,YAAY,KAAK,MAAM,WAAW,IAAI;AAC7G,aAAO,OAAO,SAAS,CAAC,IAAI;AAAA,QAC1B,GAAG,KAAK;AAAA,QACR,OAAO,CAAC,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK;AAAA,MACtC;AAAA,IACF,OAAO;AACL,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,gBAAgB,OAAkC;AACzD,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC/D,QAAM,OAAmB,CAAC;AAC1B,MAAI,WAA0B,CAAC,OAAO,CAAC,CAAC;AACxC,MAAI,OAAO,OAAO,CAAC,EAAE;AAErB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,QAAI,KAAK,IAAI,OAAO,CAAC,EAAE,IAAI,IAAI,KAAK,OAAO;AACzC,eAAS,KAAK,OAAO,CAAC,CAAC;AAAA,IACzB,OAAO;AACL,WAAK,KAAK,EAAE,GAAG,MAAM,OAAO,SAAS,CAAC;AACtC,iBAAW,CAAC,OAAO,CAAC,CAAC;AACrB,aAAO,OAAO,CAAC,EAAE;AAAA,IACnB;AAAA,EACF;AACA,MAAI,SAAS,SAAS,EAAG,MAAK,KAAK,EAAE,GAAG,MAAM,OAAO,SAAS,CAAC;AAE/D,SAAO;AACT;AAGA,SAAS,kBAAkB,KAAwB;AACjD,MAAI,IAAI,MAAM,SAAS,EAAG,QAAO;AAEjC,QAAM,SAAS,CAAC,GAAG,IAAI,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAEtD,MAAI,OAAO,WAAW,KAAK,OAAO,CAAC,EAAE,KAAK,SAAS,GAAI,QAAO;AAE9D,QAAM,cAAc,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,UAAU,CAAC,IAAI,OAAO;AACxE,QAAM,SAAS,KAAK,IAAI,cAAc,gBAAgB,gBAAgB;AAEtE,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,MAAM,OAAO,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE;AAC3D,QAAI,OAAO,OAAQ,QAAO;AAAA,EAC5B;AACA,SAAO;AACT;AAGA,SAAS,sBAAsB,MAAgC;AAC7D,QAAM,OAAiB,CAAC;AACxB,aAAW,OAAO,MAAM;AACtB,eAAW,QAAQ,IAAI,MAAO,MAAK,KAAK,KAAK,CAAC;AAAA,EAChD;AACA,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAE/B,OAAK,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAEzB,QAAM,WAAyB,CAAC;AAChC,MAAI,eAAe;AAEnB,WAAS,IAAI,GAAG,KAAK,KAAK,QAAQ,KAAK;AACrC,QAAI,MAAM,KAAK,UAAU,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,iBAAiB;AAChE,YAAM,QAAQ,KAAK,MAAM,cAAc,CAAC;AACxC,YAAM,MAAM,KAAK,MAAM,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,MAAM,MAAM;AACtE,eAAS,KAAK,EAAE,GAAG,KAAK,OAAO,MAAM,OAAO,CAAC;AAC7C,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,SAAS,kBAAkB,CAAC;AACzE,SAAO,SACJ,OAAO,OAAK,EAAE,SAAS,QAAQ,EAC/B,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAC7B;AAGA,SAAS,yBACP,SAAqB,SAAuB,aACpB;AACxB,QAAM,UAAkC,CAAC;AACzC,MAAI,gBAA4B,CAAC;AACjC,MAAI,aAAa;AAEjB,aAAW,OAAO,SAAS;AACzB,UAAM,cAAc,yBAAyB,KAAK,SAAS,WAAW;AACtE,QAAI,eAAe,UAAU;AAC3B,oBAAc,KAAK,GAAG;AACtB,mBAAa;AAAA,IACf,WAAW,cAAc,SAAS,MAAM,IAAI,MAAM,UAAU,KAAK,eAAe,IAAI;AAElF,oBAAc,KAAK,GAAG;AACtB;AAAA,IACF,OAAO;AAGL,aAAO,cAAc,SAAS,GAAG;AAC/B,cAAM,OAAO,cAAc,cAAc,SAAS,CAAC;AACnD,YAAI,yBAAyB,MAAM,SAAS,WAAW,KAAK,SAAU;AACtE,sBAAc,IAAI;AAAA,MACpB;AACA,UAAI,cAAc,UAAU,UAAU;AACpC,gBAAQ,KAAK,EAAE,MAAM,CAAC,GAAG,aAAa,EAAE,CAAC;AAAA,MAC3C;AACA,sBAAgB,CAAC;AACjB,mBAAa;AAAA,IACf;AAAA,EACF;AAGA,SAAO,cAAc,SAAS,GAAG;AAC/B,UAAM,OAAO,cAAc,cAAc,SAAS,CAAC;AACnD,QAAI,yBAAyB,MAAM,SAAS,WAAW,KAAK,SAAU;AACtE,kBAAc,IAAI;AAAA,EACpB;AACA,MAAI,cAAc,UAAU,UAAU;AACpC,YAAQ,KAAK,EAAE,MAAM,cAAc,CAAC;AAAA,EACtC;AAEA,SAAO;AACT;AAGA,SAAS,iBAAiB,SAAqB,SAA+C;AAC5F,QAAM,UAAkC,CAAC;AACzC,MAAI,gBAA4B,CAAC;AAEjC,aAAW,OAAO,SAAS;AACzB,UAAM,cAAc,oBAAoB,KAAK,OAAO;AACpD,QAAI,eAAe,UAAU;AAC3B,oBAAc,KAAK,GAAG;AAAA,IACxB,WAAW,IAAI,MAAM,WAAW,GAAG;AACjC,UAAI,cAAc,SAAS,GAAG;AAC5B,sBAAc,KAAK,GAAG;AAAA,MACxB;AAAA,IACF,OAAO;AACL,UAAI,cAAc,UAAU,UAAU;AACpC,gBAAQ,KAAK,EAAE,MAAM,CAAC,GAAG,aAAa,EAAE,CAAC;AAAA,MAC3C;AACA,sBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAEA,MAAI,cAAc,UAAU,UAAU;AACpC,YAAQ,KAAK,EAAE,MAAM,cAAc,CAAC;AAAA,EACtC;AAEA,SAAO;AACT;AAGA,SAAS,oBAAoB,KAAe,SAA+B;AACzE,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,QAAQ,IAAI,OAAO;AAC5B,aAAS,KAAK,GAAG,KAAK,QAAQ,QAAQ,MAAM;AAC1C,UAAI,KAAK,IAAI,KAAK,IAAI,QAAQ,EAAE,EAAE,CAAC,KAAK,kBAAkB,GAAG;AAC3D,gBAAQ,IAAI,EAAE;AACd;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,QAAQ;AACjB;AAMA,SAAS,yBACP,KAAe,SAAuB,aAC9B;AAER,QAAM,aAAgD,CAAC;AACvD,WAAS,KAAK,GAAG,KAAK,YAAY,QAAQ,MAAM;AAC9C,UAAM,OAAO,OAAO,IAAI,KAAK,YAAY,KAAK,CAAC,EAAE,IAAI,YAAY,KAAK,CAAC,EAAE,IAAI,YAAY,EAAE,EAAE,KAAK;AAClG,UAAM,QAAQ,OAAO,YAAY,SAAS,IACtC,YACC,YAAY,EAAE,EAAE,IAAI,YAAY,EAAE,EAAE,IAAI,YAAY,KAAK,CAAC,EAAE,KAAK;AACtE,eAAW,KAAK,EAAE,MAAM,MAAM,CAAC;AAAA,EACjC;AAEA,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,QAAQ,IAAI,OAAO;AAC5B,aAAS,KAAK,GAAG,KAAK,WAAW,QAAQ,MAAM;AAC7C,UAAI,KAAK,KAAK,WAAW,EAAE,EAAE,QAAQ,KAAK,IAAI,WAAW,EAAE,EAAE,OAAO;AAClE,gBAAQ,IAAI,EAAE;AACd;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,QAAQ;AACjB;AAYA,SAAS,eACP,OAAsB,SAAuB,SACJ;AACzC,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAGlD,QAAM,aAAa,QAAQ,IAAI,OAAK,EAAE,CAAC;AAGvC,QAAM,OAAwC,CAAC;AAC/C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,SAAK,KAAK,EAAE,KAAK,GAAG,MAAM,OAAO,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE,GAAG,CAAC;AAAA,EAC/E;AAKA,QAAM,WAAW,KAAK,IAAI,CAAAA,OAAKA,GAAE,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC3D,QAAM,YAAY,SAAS,SAAS,IAAI,SAAS,KAAK,MAAM,SAAS,SAAS,CAAC,CAAC,IAAI;AACpF,QAAM,eAAe,OAAO,UAAU,UAAU,IAC5C,KACA,KAAK,IAAI,YAAY,KAAK,EAAE;AAChC,QAAM,kBAAkB,KACrB,OAAO,CAAAA,OAAKA,GAAE,QAAQ,YAAY,EAClC,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAC9B,MAAM,GAAG,UAAU,CAAC,EACpB,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;AAG/B,QAAM,SAA0B,CAAC;AACjC,MAAI,QAAQ;AACZ,aAAW,OAAO,iBAAiB;AACjC,WAAO,KAAK,OAAO,MAAM,OAAO,IAAI,GAAG,CAAC;AACxC,YAAQ,IAAI;AAAA,EACd;AACA,SAAO,KAAK,OAAO,MAAM,KAAK,CAAC;AAG/B,QAAM,SAAkD,CAAC;AACzD,QAAM,WAAW,oBAAI,IAAY;AAEjC,QAAM,eAAe,OAAO,IAAI,CAAAA,OAAK;AACnC,QAAI,OAAO,UAAU,OAAO;AAC5B,eAAW,KAAKA,IAAG;AAAE,UAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AAAG,YAAM,IAAI,EAAE,IAAI,EAAE;AAAG,UAAI,IAAI,KAAM,QAAO;AAAA,IAAE;AAC7F,YAAQ,OAAO,QAAQ;AAAA,EACzB,CAAC;AAGD,QAAM,cAA0D,CAAC;AACjE,WAAS,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM;AACzC,aAAS,KAAK,GAAG,KAAK,SAAS,MAAM;AACnC,kBAAY,KAAK,EAAE,IAAI,IAAI,MAAM,KAAK,IAAI,aAAa,EAAE,IAAI,WAAW,EAAE,CAAC,EAAE,CAAC;AAAA,IAChF;AAAA,EACF;AACA,cAAY,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAE1C,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,EAAE,IAAI,GAAG,KAAK,aAAa;AACpC,QAAI,eAAe,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,EAAG;AAChD,WAAO,KAAK,EAAE,KAAK,IAAI,OAAO,OAAO,EAAE,EAAE,CAAC;AAC1C,mBAAe,IAAI,EAAE;AACrB,aAAS,IAAI,EAAE;AAAA,EACjB;AAEA,WAAS,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM;AACzC,QAAI,eAAe,IAAI,EAAE,EAAG;AAC5B,QAAI,UAAU,GAAG,WAAW;AAC5B,aAAS,KAAK,GAAG,KAAK,SAAS,MAAM;AACnC,YAAM,IAAI,KAAK,IAAI,aAAa,EAAE,IAAI,WAAW,EAAE,CAAC;AACpD,UAAI,IAAI,UAAU;AAAE,mBAAW;AAAG,kBAAU;AAAA,MAAG;AAAA,IACjD;AACA,WAAO,KAAK,EAAE,KAAK,SAAS,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,EACjD;AAEA,SAAO;AACT;AAGA,SAAS,kBACP,MACA,SACA,SAC2B;AAC3B,QAAM,UAAU,QAAQ;AACxB,QAAM,UAAU,KAAK;AAErB,MAAI,UAAU,YAAY,UAAU,SAAU,QAAO;AAErD,QAAM,QAAoB,MAAM;AAAA,IAC9B,EAAE,QAAQ,QAAQ;AAAA,IAClB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,OAAO,EAAE,MAAM,IAAI,SAAS,GAAG,SAAS,EAAE,EAAE;AAAA,EACpF;AAEA,QAAM,YAAY,oBAAI,IAAiB;AAEvC,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,MAAM,KAAK,CAAC;AAElB,QAAI,IAAI,MAAM,WAAW,KAAK,UAAU,GAAG;AACzC,YAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,MAAM,CAAC,EAAE,MAAM,SAAS,SAAS,SAAS,EAAE;AACtE,gBAAU,IAAI,IAAI,MAAM,CAAC,CAAC;AAC1B;AAAA,IACF;AAGA,UAAM,cAAc,eAAe,IAAI,OAAO,SAAS,OAAO;AAC9D,eAAW,EAAE,KAAK,MAAM,KAAK,aAAa;AACxC,YAAM,OAAO,MAAM,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,GAAG;AAC5C,YAAM,WAAW,MAAM,CAAC,EAAE,GAAG,EAAE;AAC/B,YAAM,CAAC,EAAE,GAAG,EAAE,OAAO,WAAW,WAAW,MAAM,OAAO;AACxD,iBAAW,QAAQ,MAAO,WAAU,IAAI,IAAI;AAAA,IAC9C;AAAA,EACF;AAGA,MAAI,YAAY;AAChB,aAAW,OAAO,OAAO;AACvB,QAAI,IAAI,MAAM,OAAK,EAAE,SAAS,EAAE,EAAG;AAAA,EACrC;AACA,MAAI,YAAY,UAAU,IAAK,QAAO;AAGtC,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,WAAW,MAAM,KAAK,SAAO,IAAI,CAAC,EAAE,SAAS,EAAE;AACrD,QAAI,CAAC,SAAU,QAAO;AAAA,EACxB;AAIA,WAAS,IAAI,UAAU,GAAG,KAAK,GAAG,KAAK;AACrC,UAAM,eAAe,MAAM,CAAC,EAAE,OAAO,OAAK,EAAE,KAAK,KAAK,CAAC,EAAE;AACzD,QAAI,iBAAiB,EAAG;AACxB,QAAI,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,KAAK,MAAM,GAAI;AACpC,UAAM,cAAc,MAAM,CAAC,EAAE,KAAK,OAAK,EAAE,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,KAAK;AAEtE,QAAI,YAAY,KAAK,WAAW,EAAG;AACnC,aAAS,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM;AAClC,UAAI,MAAM,EAAE,EAAE,KAAK,OAAK,EAAE,KAAK,KAAK,CAAC,GAAG;AACtC,iBAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,gBAAM,OAAO,MAAM,EAAE,EAAE,CAAC,EAAE,KAAK,KAAK;AACpC,gBAAM,OAAO,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,KAAK;AACnC,cAAI,KAAM,OAAM,EAAE,EAAE,CAAC,EAAE,OAAO,OAAO,OAAO,MAAM,OAAO;AAAA,QAC3D;AACA,iBAAS,IAAI,GAAG,IAAI,SAAS,IAAK,OAAM,CAAC,EAAE,CAAC,EAAE,OAAO;AACrD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,UAAU,IAAI,CAAC,EAAE,KAAK,KAAK,MAAM;AACvC,UAAM,aAAa,UAAU,KAAK,IAAI,UAAU,CAAC,EAAE,KAAK,KAAK,MAAM;AACnE,UAAM,WAAW,IAAI,MAAM,GAAG,UAAU,CAAC,EAAE,MAAM,OAAK,EAAE,KAAK,KAAK,MAAM,EAAE;AAC1E,QAAI,WAAW,cAAc,UAAU;AAErC,YAAM,OAAO,MAAM,IAAI,CAAC;AACxB,UAAI,KAAK,CAAC,EAAE,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK,OAAK,EAAE,KAAK,KAAK,CAAC,GAAG;AAC/D,iBAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,gBAAM,OAAO,KAAK,CAAC,EAAE,KAAK,KAAK;AAC/B,cAAI,KAAM,KAAI,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC,EAAE,KAAK,KAAK,IAAI,MAAM,OAAO;AAAA,QACjF;AACA,iBAAS,IAAI,GAAG,IAAI,SAAS,IAAK,MAAK,CAAC,EAAE,OAAO;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,OAAO,SAAO,IAAI,KAAK,OAAK,EAAE,KAAK,KAAK,CAAC,CAAC;AACtE,QAAM,gBAAgB,cAAc;AAGpC,MAAI,gBAAgB,SAAU,QAAO;AAErC,QAAM,UAAmB;AAAA,IACvB,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW,gBAAgB;AAAA,EAC7B;AAGA,QAAM,WAAW,KAAK,QAAQ,OAAK,EAAE,KAAK;AAC1C,MAAI,OAAO,UAAU,OAAO,UAAU,OAAO,WAAW,OAAO;AAC/D,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,EAAE,IAAI,KAAM,QAAO,EAAE,IAAI,EAAE;AACrC,UAAM,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE;AAC5B,QAAI,EAAE,IAAI,IAAI,KAAM,QAAO,EAAE,IAAI;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,MAAM,EAAE,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,OAAO,OAAO,MAAM,QAAQ,OAAO,KAAK;AAAA,IACjF;AAAA,EACF;AACF;;;AC3oBA,YAAY,iBAAiB;AAf7B,IAAM,IAAI;AAEV,IAAI,OAAO,EAAE,cAAc,aAAa;AACtC,IAAE,YAAY,MAAM,UAAU;AAAA,IAC5B,IAAc,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAAA,IAC/B,YAAY,MAAiB;AAAE,UAAI,KAAM,MAAK,IAAI;AAAA,IAAK;AAAA,EACzD;AACF;AAEA,IAAI,OAAO,EAAE,WAAW,aAAa;AACnC,IAAE,SAAS,MAAM,OAAO;AAAA,EAAC;AAC3B;AAKA,EAAE,cAAc;;;ACThB,SAAS,aAAkB,2BAA2B;AAGtD,oBAAoB,YAAY;AAGhC,IAAM,YAAY;AAClB,IAAM,iBAAiB,MAAM,OAAO;AAEpC,IAAM,sBAAsB;AAG5B,eAAe,mBAAmB,QAAqB;AACrD,QAAM,cAAc,YAAY;AAAA,IAC9B,MAAM,IAAI,WAAW,MAAM;AAAA,IAC3B,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB,CAAC;AACD,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK;AAAA,MACxB,YAAY;AAAA,MACZ,IAAI,QAAe,CAAC,GAAG,WAAW;AAChC,gBAAQ,WAAW,MAAM;AAAE,sBAAY,QAAQ;AAAG,iBAAO,IAAI,YAAY,mEAAsB,CAAC;AAAA,QAAE,GAAG,mBAAmB;AAAA,MAC1H,CAAC;AAAA,IACH,CAAC;AAAA,EACH,UAAE;AACA,QAAI,UAAU,OAAW,cAAa,KAAK;AAAA,EAC7C;AACF;AAyBA,eAAsB,iBAAiB,QAAqB,SAAsD;AAChH,QAAM,MAAM,MAAM,mBAAmB,MAAM;AAE3C,MAAI;AACF,UAAM,YAAY,IAAI;AACtB,QAAI,cAAc,EAAG,OAAM,IAAI,YAAY,8DAAiB;AAG5D,UAAM,WAA6B,EAAE,UAAU;AAC/C,UAAM,mBAAmB,KAAK,QAAQ;AAEtC,UAAM,SAAoB,CAAC;AAC3B,UAAM,WAA2B,CAAC;AAClC,QAAI,aAAa;AACjB,QAAI,iBAAiB;AACrB,UAAM,qBAAqB,KAAK,IAAI,WAAW,SAAS;AAGxD,UAAM,aAAa,SAAS,QAAQ,eAAe,QAAQ,OAAO,kBAAkB,IAAI;AACxF,UAAM,cAAc,aAAa,WAAW,OAAO;AAGnD,UAAM,eAAe,oBAAI,IAAoB;AAC7C,UAAM,cAAc,oBAAI,IAAoB;AAE5C,QAAI,cAAc;AAClB,aAAS,IAAI,GAAG,KAAK,oBAAoB,KAAK;AAC5C,UAAI,cAAc,CAAC,WAAW,IAAI,CAAC,EAAG;AACtC,UAAI;AACF,cAAM,OAAO,MAAM,IAAI,QAAQ,CAAC;AAChC,cAAM,KAAK,MAAM,KAAK,eAAe;AACrC,cAAM,WAAW,KAAK,YAAY,EAAE,OAAO,EAAE,CAAC;AAC9C,oBAAY,IAAI,GAAG,SAAS,MAAM;AAClC,cAAM,WAAW,GAAG;AACpB,cAAM,QAAQ,eAAe,QAAQ;AAGrC,cAAM,EAAE,SAAS,YAAY,IAAI,iBAAiB,OAAO,SAAS,OAAO,SAAS,MAAM;AACxF,YAAI,cAAc,GAAG;AACnB,mBAAS,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,sFAAqB,MAAM,uBAAuB,CAAC;AAAA,QACrG;AAGA,mBAAW,QAAQ,SAAS;AAC1B,cAAI,KAAK,WAAW,EAAG,cAAa,IAAI,KAAK,WAAW,aAAa,IAAI,KAAK,QAAQ,KAAK,KAAK,CAAC;AAAA,QACnG;AAGA,cAAM,SAAS,MAAM,KAAK,gBAAgB;AAE1C,cAAM,aAAa,2BAA2B,SAAS,GAAG,QAAQ,SAAS,OAAO,SAAS,MAAM;AACjG,mBAAW,KAAK,WAAY,QAAO,KAAK,CAAC;AAGzC,mBAAW,KAAK,YAAY;AAC1B,gBAAM,IAAI,EAAE,QAAQ;AACpB,wBAAc,EAAE,QAAQ,OAAO,EAAE,EAAE;AACnC,4BAAkB,EAAE,SAAS;AAAA,QAC/B;AACA,YAAI,iBAAiB,eAAgB,OAAM,IAAI,YAAY,2DAAc;AACzE;AACA,iBAAS,aAAa,aAAa,WAAW;AAAA,MAChD,SAAS,SAAS;AAEhB,YAAI,mBAAmB,YAAa,OAAM;AAC1C,iBAAS,KAAK,EAAE,MAAM,GAAG,SAAS,sBAAO,CAAC,+BAAW,mBAAmB,QAAQ,QAAQ,UAAU,yCAAW,IAAI,MAAM,gBAAgB,CAAC;AAAA,MAC1I;AAAA,IACF;AAEA,UAAM,kBAAkB,gBAAgB,aAAa,WAAW,OAAO;AACvE,QAAI,aAAa,KAAK,IAAI,iBAAiB,CAAC,IAAI,IAAI;AAClD,UAAI,SAAS,KAAK;AAChB,YAAI;AACF,gBAAM,EAAE,SAAS,IAAI,MAAM,OAAO,wBAAoB;AACtD,gBAAM,YAAY,MAAM,SAAS,KAAK,QAAQ,KAAK,YAAY,kBAAkB;AACjF,cAAI,UAAU,SAAS,GAAG;AACxB,kBAAM,cAAc,UAAU,IAAI,OAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,MAAM;AAChF,mBAAO,EAAE,UAAU,aAAa,QAAQ,WAAW,UAAU,UAAU,cAAc,KAAK;AAAA,UAC5F;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,YAAM,OAAO,OAAO,IAAI,YAAY,wCAAe,SAAS,uBAAQ,UAAU,SAAI,GAAG,EAAE,cAAc,KAAK,CAAC;AAAA,IAC7G;AAGA,QAAI,SAAS,uBAAuB,SAAS,mBAAmB,GAAG;AACjE,YAAM,UAAU,yBAAyB,QAAQ,aAAa,QAAQ;AAEtE,eAAS,KAAK,QAAQ,SAAS,GAAG,MAAM,GAAG,MAAM;AAC/C,eAAO,OAAO,QAAQ,EAAE,GAAG,CAAC;AAAA,MAC9B;AAAA,IACF;AAGA,UAAM,iBAAiB,8BAA8B,YAAY;AACjE,QAAI,iBAAiB,GAAG;AACtB,qBAAe,QAAQ,cAAc;AAAA,IACvC;AAGA,yBAAqB,MAAM;AAG3B,UAAM,UAAyB,OAC5B,OAAO,OAAK,EAAE,SAAS,aAAa,EAAE,SAAS,EAAE,IAAI,EACrD,IAAI,QAAM,EAAE,OAAO,EAAE,OAAQ,MAAM,EAAE,MAAO,YAAY,EAAE,WAAW,EAAE;AAG1E,QAAI,WAAW,aAAa,iBAAiB,MAAM,CAAC;AAEpD,WAAO,EAAE,UAAU,QAAQ,UAAU,SAAS,QAAQ,SAAS,IAAI,UAAU,QAAW,UAAU,SAAS,SAAS,IAAI,WAAW,OAAU;AAAA,EAC/I,UAAE;AACA,UAAM,IAAI,QAAQ,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACpC;AACF;AAIA,eAAe,mBAAmB,KAA0C,UAA2C;AACrH,MAAI;AACF,UAAM,SAAS,MAAM,IAAI,YAAY;AACrC,QAAI,CAAC,QAAQ,KAAM;AACnB,UAAM,OAAO,OAAO;AAEpB,QAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAG,UAAS,QAAQ,KAAK,MAAM,KAAK;AAC1F,QAAI,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAG,UAAS,SAAS,KAAK,OAAO,KAAK;AAC9F,QAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,KAAK,EAAG,UAAS,UAAU,KAAK,QAAQ,KAAK;AAClG,QAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,KAAK,EAAG,UAAS,cAAc,KAAK,QAAQ,KAAK;AACtG,QAAI,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,KAAK,GAAG;AAC7D,eAAS,WAAW,KAAK,SAAS,MAAM,MAAM,EAAE,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAAA,IAC7F;AACA,QAAI,OAAO,KAAK,iBAAiB,SAAU,UAAS,YAAY,aAAa,KAAK,YAAY;AAC9F,QAAI,OAAO,KAAK,YAAY,SAAU,UAAS,aAAa,aAAa,KAAK,OAAO;AAAA,EACvF,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,aAAa,SAAqC;AACzD,QAAM,IAAI,QAAQ,MAAM,mDAAmD;AAC3E,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,IAAI,IAAI;AAChF,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG;AACtD;AAGA,eAAsB,uBAAuB,QAAgD;AAC3F,QAAM,MAAM,MAAM,mBAAmB,MAAM;AAE3C,MAAI;AACF,UAAM,WAA6B,EAAE,WAAW,IAAI,SAAS;AAC7D,UAAM,mBAAmB,KAAK,QAAQ;AACtC,WAAO;AAAA,EACT,UAAE;AACA,UAAM,IAAI,QAAQ,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACpC;AACF;AAMA,SAAS,iBAAiB,OAAmB,WAAmB,YAAkE;AAChI,MAAI,cAAc;AAClB,QAAM,UAAsB,CAAC;AAE7B,aAAW,QAAQ,OAAO;AAExB,QAAI,KAAK,UAAU;AAAE;AAAe;AAAA,IAAS;AAE7C,UAAM,SAAS,KAAK,IAAI,WAAW,UAAU,IAAI;AACjD,QAAI,KAAK,IAAI,CAAC,UAAU,KAAK,IAAI,YAAY,UAAU,KAAK,IAAI,CAAC,UAAU,KAAK,IAAI,aAAa,QAAQ;AACvG;AAAe;AAAA,IACjB;AACA,YAAQ,KAAK,IAAI;AAAA,EACnB;AAEA,SAAO,EAAE,SAAS,YAAY;AAChC;AAMA,SAAS,8BAA8B,MAAmC;AACxE,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,MAAI,QAAQ;AACZ,aAAW,SAAS,KAAK,OAAO,EAAG,UAAS;AAC5C,QAAM,SAAS,CAAC,GAAG,KAAK,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7D,QAAM,MAAM,KAAK,MAAM,QAAQ,CAAC;AAChC,MAAI,aAAa;AACjB,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,kBAAc;AACd,QAAI,aAAa,IAAK,QAAO;AAAA,EAC/B;AACA,SAAO,OAAO,OAAO,SAAS,CAAC,EAAE,CAAC;AACpC;AASA,SAAS,eAAe,QAAmB,gBAA8B;AACvE,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,eAAe,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO,SAAU;AACzE,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,KAAK,WAAW,KAAK,KAAK,SAAS,IAAK;AAE5C,QAAI,QAAQ,KAAK,IAAI,EAAG;AAExB,UAAM,QAAQ,MAAM,MAAM,WAAW;AACrC,QAAI,QAAQ;AACZ,QAAI,SAAS,iBAAkB,SAAQ;AAAA,aAC9B,SAAS,iBAAkB,SAAQ;AAAA,aACnC,SAAS,iBAAkB,SAAQ;AAE5C,QAAI,QAAQ,GAAG;AACb,YAAM,OAAO;AACb,YAAM,QAAQ;AAGd,YAAM,OAAO,oBAAoB,IAAI;AAAA,IACvC;AAAA,EACF;AACF;AAOA,SAAS,oBAAoB,MAAsB;AAEjD,QAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAM,kBAAkB,OAAO,OAAO,OAAK,EAAE,WAAW,CAAC,EAAE;AAC3D,MAAI,OAAO,UAAU,KAAK,kBAAkB,OAAO,UAAU,KAAK;AAChE,WAAO,OAAO,KAAK,EAAE;AAAA,EACvB;AAKA,SAAO,KAAK;AAAA,IACV;AAAA,IACA,WAAS,MAAM,QAAQ,MAAM,EAAE;AAAA,EACjC;AACF;AAKA,SAAS,kBAAkB,OAAyB;AAClD,QAAM,WAAW,MAAM,MAAM,QAAQ,SAAO,IAAI,IAAI,OAAK,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,OAAO,OAAO;AACvF,QAAM,UAAU,SAAS,KAAK,GAAG;AAIjC,MAAI,MAAM,QAAQ,KAAK,MAAM,QAAQ,GAAG;AAEtC,UAAMC,cAAa,MAAM,OAAO,MAAM;AACtC,UAAMC,cAAaD,cAAa,SAAS;AACzC,QAAIC,eAAcD,cAAa,IAAK,QAAO;AAG3C,QAAI,YAAY,KAAK,OAAO,EAAG,QAAO;AACtC,QAAI,UAAU,KAAK,OAAO,EAAG,QAAO;AAAA,EACtC;AAEA,MAAI,QAAQ,SAAS,IAAK,QAAO;AAEjC,MAAI,WAAW,KAAK,OAAO,KAAK,MAAM,QAAQ,EAAG,QAAO;AAExD,QAAM,aAAa,MAAM,OAAO,MAAM;AACtC,QAAM,aAAa,aAAa,SAAS;AACzC,MAAI,MAAM,QAAQ,KAAK,aAAa,aAAa,IAAK,QAAO;AAE7D,MAAI,MAAM,SAAS,KAAK,CAAC,SAAS,KAAK,OAAO,EAAG,QAAO;AACxD,SAAO;AACT;AAGA,SAAS,kBAAkB,OAAwB;AACjD,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,MAAM,MAAM,KAAK;AACnC,UAAM,QAAQ,MAAM,MAAM,CAAC,EAAE,IAAI,OAAK,EAAE,KAAK,KAAK,CAAC,EAAE,OAAO,OAAO;AACnE,QAAI,MAAM,WAAW,EAAG;AACxB,QAAI,MAAM,SAAS,KAAK,MAAM,WAAW,GAAG;AAC1C,YAAM,KAAK,GAAG,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,EAAE;AAAA,IACxC,OAAO;AAEL,YAAM,KAAK,MAAM,KAAK,GAAG,CAAC;AAAA,IAC5B;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAGA,SAAS,qBAAqB,QAAyB;AACrD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,MAAM,SAAS,eAAe,CAAC,MAAM,KAAM;AAC/C,UAAM,OAAO,MAAM,KAAK,KAAK;AAE7B,QAAI,KAAK,SAAS,MAAM,mBAAmB,KAAK,IAAI,GAAG;AACrD,YAAM,OAAO;AACb,YAAM,QAAQ;AACd;AAAA,IACF;AAGA,QAAI,eAAe,KAAK,IAAI,KAAK,MAAM,OAAO,UAAU;AACtD,YAAM,OAAO,OAAO,IAAI,CAAC;AACzB,YAAM,OAAO,OAAO,IAAI,CAAC;AACzB,YAAM,mBAAmB,CAAC,QAAQ,KAAK,SAAS,WAAW,KAAK,SAAS,aAAa,KAAK,SAAS;AACpG,YAAM,mBAAmB,CAAC,QAAQ,KAAK,SAAS,WAAW,KAAK,SAAS,aAAc,KAAK,SAAS,eAAe,KAAK,QAAQ,WAAW,KAAK,KAAK,KAAK,KAAK,CAAC;AACjK,UAAI,oBAAoB,kBAAkB;AACxC,cAAM,OAAO;AACb,cAAM,QAAQ;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;AAkBA,IAAM,kBAAkB;AAExB,SAAS,WAAW,OAAmB,cAAsB,QAAQ,GAAiB;AACpF,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,MAAI,MAAM,UAAU,KAAK,SAAS,gBAAiB,QAAO,CAAC,KAAK;AAEhE,QAAM,SAAS,cAAc,KAAK;AAGlC,QAAM,SAAS,WAAW,OAAO,QAAQ,YAAY;AACrD,MAAI,WAAW,MAAM;AACnB,UAAM,QAAQ,MAAM,OAAO,OAAK,EAAE,IAAI,MAAM;AAC5C,UAAM,QAAQ,MAAM,OAAO,OAAK,EAAE,KAAK,MAAM;AAE7C,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,KAAK,MAAM,SAAS,MAAM,QAAQ;AACvE,aAAO,CAAC,GAAG,WAAW,OAAO,cAAc,QAAQ,CAAC,GAAG,GAAG,WAAW,OAAO,cAAc,QAAQ,CAAC,CAAC;AAAA,IACtG;AAAA,EACF;AAGA,QAAM,SAAS,WAAW,OAAO,QAAQ,YAAY;AACrD,MAAI,WAAW,MAAM;AACnB,UAAM,OAAO,MAAM,OAAO,OAAK,EAAE,IAAI,EAAE,IAAI,IAAI,MAAM;AACrD,UAAM,QAAQ,MAAM,OAAO,OAAK,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM;AACvD,QAAI,KAAK,SAAS,KAAK,MAAM,SAAS,KAAK,KAAK,SAAS,MAAM,QAAQ;AACrE,aAAO,CAAC,GAAG,WAAW,MAAM,cAAc,QAAQ,CAAC,GAAG,GAAG,WAAW,OAAO,cAAc,QAAQ,CAAC,CAAC;AAAA,IACrG;AAAA,EACF;AAGA,SAAO,CAAC,KAAK;AACf;AAEA,SAAS,cAAc,OAA+B;AACpD,MAAI,OAAO,UAAU,OAAO,UAAU,OAAO,WAAW,OAAO;AAC/D,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,EAAE,IAAI,KAAM,QAAO,EAAE,IAAI,EAAE;AACrC,QAAI,EAAE,IAAI,EAAE,IAAI,KAAM,QAAO,EAAE,IAAI,EAAE;AAAA,EACvC;AACA,SAAO,EAAE,OAAO,MAAM,MAAM,MAAM,KAAK;AACzC;AAGA,SAAS,WAAW,OAAmB,SAAqB,cAAqC;AAE/F,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAClD,MAAI,UAAU;AACd,MAAI,YAA2B;AAE/B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,aAAa,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE;AACnD,UAAM,UAAU,OAAO,CAAC,EAAE;AAC1B,UAAM,MAAM,aAAa;AACzB,QAAI,MAAM,SAAS;AACjB,gBAAU;AACV,mBAAa,aAAa,WAAW;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,WAAW,OAAmB,SAAqB,cAAqC;AAC/F,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAClD,MAAI,UAAU;AACd,MAAI,YAA2B;AAE/B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,YAAY,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE;AAClD,UAAM,WAAW,OAAO,CAAC,EAAE;AAC3B,UAAM,MAAM,WAAW;AACvB,QAAI,MAAM,SAAS;AACjB,gBAAU;AACV,mBAAa,YAAY,YAAY;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AASA,SAAS,2BACP,OACA,SACA,QACA,WACA,YACW;AACX,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAGhC,MAAI,EAAE,aAAa,UAAU,IAAI,aAAa,OAAO,SAAS,OAAO,SAAS;AAC7E,GAAC,EAAE,aAAa,UAAU,IAAI,sBAAsB,aAAa,WAAW,WAAW,UAAU;AAIjG,GAAC,EAAE,aAAa,UAAU,IAAI,gBAAgB,aAAa,SAAS;AAGrE,QAAM,QAAQ,gBAAgB,aAAa,SAAS;AAEpD,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO,uBAAuB,OAAO,SAAS,OAAO,aAAa,SAAS;AAAA,EAC7E;AAGA,SAAO,0BAA0B,OAAO,OAAO;AACjD;AAMA,SAAS,uBACP,OACA,SACA,OACA,aACA,WACW;AACX,QAAM,SAAoB,CAAC;AAC3B,QAAM,YAAY,oBAAI,IAAc;AAGpC,QAAM,cAAc,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE;AAEnE,aAAW,QAAQ,aAAa;AAE9B,UAAM,cAAc,KAAK,MAAM,SAAS;AACxC,UAAM,cAAc,KAAK,MAAM,SAAS;AACxC,QAAI,gBAAgB,KAAK,eAAe,EAAG;AAG3C,UAAM,aAAyB,CAAC;AAChC,UAAM,MAAM;AACZ,UAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,KAAK;AACvC,eAAW,QAAQ,OAAO;AACxB,UAAI,UAAU,IAAI,IAAI,EAAG;AAEzB,UAAI,KAAK,IAAI,KAAK,KAAK,KAAK,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK,IAAK;AAEhE,UAAI,KAAK,IAAI,KAAK,KAAK,KAAK,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,IAAK;AAGzE,UAAI,QAAQ,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,EAAG;AACvD,iBAAW,KAAK,IAAI;AACpB,gBAAU,IAAI,IAAI;AAAA,IACpB;AAGA,UAAM,QAAQ,aAAa,MAAM,aAAa,SAAS;AACvD,QAAI,MAAM,WAAW,EAAG;AAGxB,UAAM,YAAwB,WAAW,IAAI,QAAM;AAAA,MACjD,MAAM,EAAE;AAAA,MAAM,GAAG,EAAE;AAAA,MAAG,GAAG,EAAE;AAAA,MAAG,GAAG,EAAE;AAAA,MAAG,GAAG,EAAE;AAAA,MAC3C,UAAU,EAAE;AAAA,MAAU,UAAU,EAAE;AAAA,IACpC,EAAE;AACF,UAAM,cAAc,eAAe,WAAW,KAAK;AAGnD,UAAM,UAAU,KAAK,MAAM,SAAS;AACpC,UAAM,UAAU,KAAK,MAAM,SAAS;AACpC,UAAM,SAA2C,MAAM;AAAA,MACrD,EAAE,QAAQ,QAAQ;AAAA,MAClB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,GAAG,OAAO,EAAE,MAAM,IAAI,SAAS,GAAG,SAAS,EAAE,EAAE;AAAA,IACpF;AAEA,eAAW,QAAQ,OAAO;AACxB,YAAM,YAAY,YAAY,IAAI,IAAI,KAAK,CAAC;AAC5C,UAAI,OAAO,iBAAiB,SAAS;AAErC,aAAO,KAAK,QAAQ,qCAAqC,EAAE,EAAE,KAAK;AAElE,aAAO,KAAK,MAAM,IAAI,EAAE,IAAI,UAAQ,oBAAoB,IAAI,CAAC,EAAE,KAAK,IAAI;AACxE,aAAO,KAAK,GAAG,EAAE,KAAK,GAAG,IAAI;AAAA,QAC3B;AAAA,QACA,SAAS,KAAK;AAAA,QACd,SAAS,KAAK;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,UAAmB;AAAA,MACvB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAW,UAAU;AAAA,IACvB;AAGA,UAAM,aAAa,OAAO,KAAK,SAAO,IAAI,KAAK,UAAQ,KAAK,KAAK,KAAK,MAAM,EAAE,CAAC;AAC/E,QAAI,CAAC,WAAY;AAEjB,UAAM,YAAyB;AAAA,MAC7B,MAAM;AAAA,MACN,GAAG,KAAK,KAAK;AAAA,MAAI,GAAG,KAAK,KAAK;AAAA,MAC9B,OAAO,KAAK,KAAK,KAAK,KAAK,KAAK;AAAA,MAAI,QAAQ,KAAK,KAAK,KAAK,KAAK,KAAK;AAAA,IACvE;AAGA,QAAI,kBAAkB,OAAO,GAAG;AAC9B,YAAM,UAAU,kBAAkB,OAAO;AACzC,UAAI,SAAS;AAEX,cAAM,OAAO,gBAAgB,IAAI,OAAO,UAAU,OAAO;AACzD,eAAO,KAAK,EAAE,MAAM,aAAa,MAAM,YAAY,SAAS,MAAM,WAAW,OAAO,cAAc,UAAU,EAAE,CAAC;AAAA,MACjH;AACA;AAAA,IACF;AAEA,WAAO,KAAK,EAAE,MAAM,SAAS,OAAO,SAAS,YAAY,SAAS,MAAM,UAAU,CAAC;AAAA,EACrF;AAGA,MAAI,YAAY,MAAM,OAAO,OAAK,CAAC,UAAU,IAAI,CAAC,CAAC;AACnD,MAAI,UAAU,SAAS,GAAG;AACxB,cAAU,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAG/C,UAAM,eAA8B,UAAU,IAAI,QAAM;AAAA,MACtD,MAAM,EAAE;AAAA,MAAM,GAAG,EAAE;AAAA,MAAG,GAAG,EAAE;AAAA,MAAG,GAAG,EAAE;AAAA,MAAG,GAAG,EAAE;AAAA,MAC3C,UAAU,EAAE;AAAA,MAAU,UAAU,EAAE;AAAA,IACpC,EAAE;AACF,UAAM,iBAAiB,oBAAoB,cAAc,OAAO;AAChE,QAAI,eAAe,SAAS,GAAG;AAC7B,YAAM,UAAU,oBAAI,IAAyB;AAC7C,eAAS,KAAK,GAAG,KAAK,aAAa,QAAQ,KAAM,SAAQ,IAAI,aAAa,EAAE,GAAG,EAAE;AACjF,YAAM,qBAAqB,oBAAI,IAAY;AAC3C,iBAAW,MAAM,gBAAgB;AAC/B,mBAAW,MAAM,GAAG,WAAW;AAC7B,gBAAM,MAAM,QAAQ,IAAI,EAAE;AAC1B,cAAI,QAAQ,OAAW,oBAAmB,IAAI,GAAG;AAAA,QACnD;AACA,eAAO,KAAK,EAAE,MAAM,SAAS,OAAO,GAAG,OAAO,YAAY,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,MACpF;AACA,kBAAY,UAAU,OAAO,CAAC,GAAG,QAAQ,CAAC,mBAAmB,IAAI,GAAG,CAAC;AAAA,IACvE;AAGA,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,OAAO,UAAU,IAAI,OAAK,EAAE,CAAC;AACnC,YAAM,QAAQ,QAAQ,IAAI,IAAI,QAAQ,IAAI;AAC1C,YAAM,SAAS,WAAW,WAAW,KAAK,IAAI,IAAI,QAAQ,IAAI,CAAC;AAC/D,YAAM,aAAwB,CAAC;AAC/B,iBAAW,SAAS,QAAQ;AAC1B,YAAI,MAAM,WAAW,EAAG;AACxB,cAAM,cAAc,0BAA0B,OAAO,OAAO;AAC5D,mBAAW,KAAK,YAAa,YAAW,KAAK,CAAC;AAAA,MAChD;AACA,YAAM,kBAAkB,iBAAiB,UAAU;AACnD,iBAAW,KAAK,gBAAiB,QAAO,KAAK,CAAC;AAAA,IAChD;AAGA,WAAO,KAAK,CAAC,GAAG,MAAM;AACpB,YAAM,KAAK,EAAE,OAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAU;AACjD,YAAM,KAAK,EAAE,OAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAU;AACjD,aAAO,KAAK;AAAA,IACd,CAAC;AACD,WAAO,yBAAyB,MAAM;AAAA,EACxC;AAEA,SAAO,yBAAyB,MAAM;AACxC;AAGA,SAAS,yBAAyB,QAA8B;AAC9D,MAAI,OAAO,UAAU,EAAG,QAAO;AAC/B,QAAM,SAAoB,CAAC,OAAO,CAAC,CAAC;AACpC,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,OAAO,CAAC;AACrB,QAAI,KAAK,SAAS,WAAW,KAAK,SAAS,WAAW,KAAK,SAAS,KAAK,SACrE,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM;AAEvC,YAAM,SAAkB;AAAA,QACtB,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM;AAAA,QACnC,MAAM,KAAK,MAAM;AAAA,QACjB,OAAO,CAAC,GAAG,KAAK,MAAM,OAAO,GAAG,KAAK,MAAM,KAAK;AAAA,QAChD,WAAW,KAAK,MAAM;AAAA,MACxB;AACA,aAAO,OAAO,SAAS,CAAC,IAAI,EAAE,GAAG,MAAM,OAAO,OAAO;AAAA,IACvD,OAAO;AACL,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,0BAA0B,OAAmB,SAA4B;AAChF,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAM,SAAoB,CAAC;AAG3B,QAAM,eAA8B,MAAM,IAAI,QAAM;AAAA,IAClD,MAAM,EAAE;AAAA,IAAM,GAAG,EAAE;AAAA,IAAG,GAAG,EAAE;AAAA,IAAG,GAAG,EAAE;AAAA,IAAG,GAAG,EAAE;AAAA,IAC3C,UAAU,EAAE;AAAA,IAAU,UAAU,EAAE;AAAA,EACpC,EAAE;AACF,QAAM,iBAAiB,oBAAoB,cAAc,OAAO;AAEhE,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,UAAU,oBAAI,IAAyB;AAC7C,aAAS,KAAK,GAAG,KAAK,aAAa,QAAQ,KAAM,SAAQ,IAAI,aAAa,EAAE,GAAG,EAAE;AACjF,UAAM,cAAc,oBAAI,IAAY;AACpC,eAAW,MAAM,gBAAgB;AAC/B,iBAAW,MAAM,GAAG,WAAW;AAC7B,cAAM,MAAM,QAAQ,IAAI,EAAE;AAC1B,YAAI,QAAQ,OAAW,aAAY,IAAI,GAAG;AAAA,MAC5C;AACA,aAAO,KAAK,EAAE,MAAM,SAAS,OAAO,GAAG,OAAO,YAAY,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,IACpF;AAGA,UAAM,YAAY,MAAM,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,IAAI,GAAG,CAAC;AAChE,QAAI,UAAU,SAAS,GAAG;AACxB,YAAM,SAAS,SAAS,SAAS;AACjC,iBAAW,QAAQ,QAAQ;AACzB,cAAM,OAAO,gBAAgB,IAAI;AACjC,YAAI,CAAC,KAAK,KAAK,EAAG;AAClB,cAAM,OAAO,YAAY,MAAM,OAAO;AACtC,eAAO,KAAK,EAAE,MAAM,aAAa,MAAM,YAAY,SAAS,MAAM,OAAO,cAAc,IAAI,EAAE,CAAC;AAAA,MAChG;AAAA,IACF;AAEA,WAAO,KAAK,CAAC,GAAG,MAAM;AACpB,YAAM,KAAK,EAAE,OAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAU;AACjD,YAAM,KAAK,EAAE,OAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,SAAU;AACjD,aAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH,OAAO;AAEL,UAAM,YAAY,SAAS,KAAK;AAChC,UAAM,UAAU,cAAc,SAAS;AAEvC,QAAI,WAAW,QAAQ,UAAU,GAAG;AAClC,YAAM,YAAY,mBAAmB,WAAW,OAAO;AACvD,YAAM,OAAO,YAAY,OAAO,OAAO;AACvC,aAAO,KAAK,EAAE,MAAM,aAAa,MAAM,WAAW,YAAY,SAAS,MAAM,OAAO,cAAc,KAAK,EAAE,CAAC;AAAA,IAC5G,OAAO;AAEL,YAAM,OAAO,MAAM,IAAI,OAAK,EAAE,CAAC;AAC/B,YAAM,aAAa,QAAQ,IAAI,IAAI,QAAQ,IAAI;AAC/C,YAAM,eAAe,KAAK,IAAI,IAAI,aAAa,IAAI;AAEnD,YAAM,gBAAgB,WAAW,OAAO,YAAY;AAEpD,iBAAW,SAAS,eAAe;AACjC,YAAI,MAAM,WAAW,EAAG;AACxB,cAAM,SAAS,SAAS,KAAK;AAE7B,cAAM,eAAe,cAAc,MAAM;AACzC,YAAI,gBAAgB,aAAa,UAAU,GAAG;AAC5C,gBAAM,YAAY,mBAAmB,QAAQ,YAAY;AACzD,gBAAM,OAAO,YAAY,OAAO,OAAO;AACvC,iBAAO,KAAK,EAAE,MAAM,aAAa,MAAM,WAAW,YAAY,SAAS,MAAM,OAAO,cAAc,KAAK,EAAE,CAAC;AAAA,QAC5G,OAAO;AACL,qBAAW,QAAQ,QAAQ;AACzB,kBAAM,OAAO,gBAAgB,IAAI;AACjC,gBAAI,CAAC,KAAK,KAAK,EAAG;AAClB,kBAAM,OAAO,YAAY,MAAM,OAAO;AACtC,mBAAO,KAAK,EAAE,MAAM,aAAa,MAAM,YAAY,SAAS,MAAM,OAAO,cAAc,IAAI,EAAE,CAAC;AAAA,UAChG;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO,0BAA0B,MAAM;AACzC;AAGA,SAAS,YAAY,OAAmB,SAA8B;AACpE,MAAI,OAAO,UAAU,OAAO,UAAU,OAAO,WAAW,OAAO;AAC/D,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,EAAE,IAAI,KAAM,QAAO,EAAE,IAAI,EAAE;AAErC,UAAM,aAAa,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE;AACrC,QAAI,EAAE,IAAI,aAAa,KAAM,QAAO,EAAE,IAAI;AAAA,EAC5C;AACA,SAAO,EAAE,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,OAAO,OAAO,MAAM,QAAQ,OAAO,KAAK;AACpF;AAGA,SAAS,cAAc,OAAuE;AAC5F,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,OAAO,oBAAI,IAAoB;AACrC,MAAI,WAAW,GAAG,eAAe;AACjC,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,YAAY,EAAG;AACrB,UAAM,SAAS,KAAK,IAAI,EAAE,QAAQ,KAAK,KAAK;AAC5C,SAAK,IAAI,EAAE,UAAU,KAAK;AAC1B,QAAI,QAAQ,UAAU;AAAE,iBAAW;AAAO,qBAAe,EAAE;AAAA,IAAS;AAAA,EACtE;AACA,MAAI,iBAAiB,EAAG,QAAO;AAE/B,QAAM,WAAW,MAAM,KAAK,OAAK,EAAE,aAAa,YAAY,GAAG,YAAY;AAC3E,SAAO,EAAE,UAAU,cAAc,SAAS;AAC5C;AAEA,SAAS,eAAe,UAAqC;AAC3D,QAAM,QAAoB,CAAC;AAE3B,QAAM,iBAA6C,CAAC;AAEpD,aAAW,KAAK,UAAU;AACxB,QAAI,OAAO,EAAE,QAAQ,SAAU;AAC/B,UAAM,IAAI,KAAK,MAAM,EAAE,UAAU,CAAC,CAAC;AACnC,UAAM,IAAI,KAAK,MAAM,EAAE,UAAU,CAAC,CAAC;AAEnC,QAAI,CAAC,EAAE,IAAI,KAAK,GAAG;AAEjB,qBAAe,KAAK,EAAE,GAAG,EAAE,CAAC;AAC5B;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,IAAI,EAAE,UAAU,CAAC,CAAC;AACtC,UAAM,SAAS,KAAK,IAAI,EAAE,UAAU,CAAC,CAAC;AACtC,UAAM,WAAW,KAAK,MAAM,KAAK,IAAI,QAAQ,MAAM,CAAC;AACpD,UAAM,IAAI,KAAK,MAAM,EAAE,KAAK;AAC5B,UAAM,IAAI,KAAK,MAAM,EAAE,MAAM;AAC7B,UAAM,WAAW,aAAa,KAAM,EAAE,UAAU,KAAK,EAAE,IAAI,KAAK,EAAE,SAAS;AAI3E,QAAI,OAAO,EAAE,IAAI,KAAK;AACtB,QAAI,oBAAoB,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG;AACvE,aAAO,KAAK,QAAQ,MAAM,EAAE;AAAA,IAC9B;AAGA,UAAM,QAAQ,oBAAoB,MAAM,GAAG,GAAG,QAAQ;AACtD,QAAI,OAAO;AACT,iBAAW,KAAK,OAAO;AACrB,cAAM,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,UAAU,UAAU,EAAE,YAAY,IAAI,SAAS,CAAC;AAAA,MACnG;AAAA,IACF,OAAO;AACL,YAAM,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,UAAU,UAAU,EAAE,YAAY,IAAI,SAAS,CAAC;AAAA,IACjF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAI1D,QAAM,UAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,QAAI,QAAQ;AAEZ,aAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,YAAM,OAAO,QAAQ,CAAC;AACtB,UAAI,KAAK,IAAI,OAAO,CAAC,EAAE,IAAI,EAAG;AAC9B,UAAI,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,EAAE,CAAC,KAAK,KAClC,KAAK,SAAS,OAAO,CAAC,EAAE,QAAQ,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,EAAE,CAAC,KAAK,GAAG;AACvE,gBAAQ;AACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,MAAO,SAAQ,KAAK,OAAO,CAAC,CAAC;AAAA,EACpC;AAIA,MAAI,eAAe,SAAS,GAAG;AAC7B,eAAW,QAAQ,SAAS;AAC1B,iBAAW,MAAM,gBAAgB;AAE/B,YAAI,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC,KAAK,GAAG;AAChC,gBAAM,OAAO,KAAK,IAAI,GAAG;AACzB,cAAI,QAAQ,KAAK,QAAQ,IAAI;AAC3B,iBAAK,iBAAiB;AACtB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAOA,SAAS,oBACP,MAAc,OAAe,OAAe,UACK;AAGjD,MAAI,CAAC,4BAA4B,KAAK,IAAI,EAAG,QAAO;AAEpD,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,SAAS,EAAG,QAAO;AAG7B,QAAM,QAAQ,QAAQ,MAAM;AAE5B,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,SAAO,MAAM,IAAI,CAAC,IAAI,SAAS;AAAA,IAC7B,MAAM;AAAA,IACN,GAAG,KAAK,MAAM,QAAQ,MAAM,KAAK;AAAA,IACjC,GAAG,KAAK,MAAM,QAAQ,GAAG;AAAA;AAAA,EAC3B,EAAE;AACJ;AAEA,SAAS,SAAS,OAAiC;AACjD,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,QAAsB,CAAC;AAC7B,MAAI,OAAO,MAAM,CAAC,EAAE;AACpB,MAAI,UAAsB,CAAC,MAAM,CAAC,CAAC;AAEnC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AAErC,QAAI,KAAK,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,IAAI,GAAG;AACnC,YAAM,KAAK,OAAO;AAClB,gBAAU,CAAC;AACX,aAAO,MAAM,CAAC,EAAE;AAAA,IAClB;AACA,YAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,EACvB;AACA,MAAI,QAAQ,SAAS,EAAG,OAAM,KAAK,OAAO;AAC1C,SAAO;AACT;AAOA,SAAS,cAAc,OAA4B;AACjD,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAClD,QAAM,OAAiB,CAAC;AACxB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,SAAK,KAAK,OAAO,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE,EAAE;AAAA,EAC7D;AAEA,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,SAAS,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,KAAK,QAAQ,CAAC,IAAI,MAAM;AAEpE,SAAO,SAAS,MAAM,SAAS;AACjC;AAEA,SAAS,cAAc,QAAuC;AAC5D,QAAM,WAAW,OAAO,KAAK;AAC7B,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,YAAY,QAAQ,SAAS,IAAI,OAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,QAAQ,SAAS,IAAI,OAAK,EAAE,CAAC,CAAC;AACxF,MAAI,YAAY,IAAK,QAAO;AAG5B,MAAI,cAAc;AAClB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,QAAI,OAAO,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,EAAE,KAAK,UAAQ,KAAK,SAAS,cAAI,GAAG;AACvE,oBAAc;AACd;AAAA,IACF;AAAA,EACF;AACA,QAAM,cAAc,eAAe,IAAI,OAAO,MAAM,GAAG,WAAW,IAAI;AAItE,QAAM,cAAc;AACpB,QAAM,YAA+D,CAAC;AAEtE,aAAW,QAAQ,aAAa;AAC9B,QAAI,cAAc,IAAI,EAAG;AACzB,eAAW,QAAQ,MAAM;AACvB,UAAI,QAAQ;AACZ,iBAAW,KAAK,WAAW;AACzB,YAAI,KAAK,IAAI,KAAK,IAAI,EAAE,MAAM,KAAK,aAAa;AAC9C,YAAE,SAAS,KAAK,OAAO,EAAE,SAAS,EAAE,QAAQ,KAAK,MAAM,EAAE,QAAQ,EAAE;AACnE,YAAE,OAAO,KAAK,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,YAAE;AACF,kBAAQ;AACR;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAC,OAAO;AACV,kBAAU,KAAK,EAAE,QAAQ,KAAK,GAAG,OAAO,GAAG,MAAM,KAAK,EAAE,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAGA,QAAM,QAAQ,UACX,OAAO,OAAK,EAAE,SAAS,CAAC,EACxB,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAGjC,MAAI,MAAM,SAAS,EAAG,QAAO;AAG7B,QAAM,YAAY;AAClB,QAAM,SAA4D,CAAC,MAAM,CAAC,CAAC;AAC3E,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAI,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,WAAW;AAEzC,UAAI,MAAM,CAAC,EAAE,QAAQ,KAAK,OAAO;AAC/B,aAAK,SAAS,MAAM,CAAC,EAAE;AAAA,MACzB;AACA,WAAK,SAAS,MAAM,CAAC,EAAE;AACvB,WAAK,OAAO,KAAK,IAAI,KAAK,MAAM,MAAM,CAAC,EAAE,IAAI;AAAA,IAC/C,OAAO;AACL,aAAO,KAAK,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,aAAa,OAAO,OAAO,OAAK,EAAE,SAAS,CAAC,EAAE,IAAI,OAAK,EAAE,IAAI;AACnE,MAAI,WAAW,SAAS,EAAG,QAAO;AAGlC,QAAM,uBAAuB;AAC7B,QAAM,UAAoB,CAAC,WAAW,CAAC,CAAC;AACxC,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,QAAI,WAAW,CAAC,IAAI,QAAQ,QAAQ,SAAS,CAAC,IAAI,qBAAsB;AACxE,YAAQ,KAAK,WAAW,CAAC,CAAC;AAAA,EAC5B;AACA,SAAO,QAAQ,UAAU,IAAI,UAAU;AACzC;AAEA,SAAS,WAAW,GAAW,SAA2B;AACxD,WAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAE5C,QAAI,KAAK,QAAQ,CAAC,IAAI,GAAI,QAAO;AAAA,EACnC;AACA,SAAO;AACT;AAMA,SAAS,mBAAmB,QAAsB,SAA2B;AAC3E,QAAM,SAAmB,CAAC;AAC1B,QAAM,SAAS,QAAQ,CAAC;AACxB,QAAM,SAAS,QAAQ,QAAQ,SAAS,CAAC;AAGzC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,QAAI,OAAO,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,EAAE,KAAK,UAAQ,KAAK,SAAS,cAAI,GAAG;AACvE,gBAAU;AACV;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAa;AACjB,WAAS,IAAI,GAAG,KAAK,WAAW,IAAI,UAAU,OAAO,SAAS,KAAK;AACjE,UAAM,WAAW,IAAI,IAAI,OAAO,CAAC,EAAE,IAAI,UAAQ,WAAW,KAAK,GAAG,OAAO,CAAC,CAAC;AAC3E,QAAI,SAAS,QAAQ,GAAG;AACtB,mBAAa;AACb;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,WAAW,IAAI,UAAU,OAAO;AAGjD,WAAS,IAAI,GAAG,KAAK,cAAc,IAAI,aAAa,WAAW,KAAK;AAClE,WAAO,KAAK,gBAAgB,OAAO,CAAC,CAAC,CAAC;AAAA,EACxC;AAGA,MAAI,cAAc,GAAG;AACnB,UAAM,aAAa,OAAO,MAAM,YAAY,QAAQ;AAGpD,UAAM,YAA0B,CAAC;AACjC,eAAW,QAAQ,YAAY;AAC7B,YAAM,UAAU,KAAK;AAAA,QAAK,UACxB,KAAK,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS;AAAA,MAC9C;AACA,UAAI,WAAW,CAAC,cAAc,IAAI,GAAG;AACnC,kBAAU,KAAK,IAAI;AAAA,MACrB,OAAO;AAEL,YAAI,UAAU,SAAS,GAAG;AACxB,iBAAO,KAAK,eAAe,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC;AAAA,QAC1D;AACA,eAAO,KAAK,gBAAgB,IAAI,CAAC;AAAA,MACnC;AAAA,IACF;AACA,QAAI,UAAU,SAAS,GAAG;AACxB,aAAO,KAAK,eAAe,WAAW,OAAO,CAAC;AAAA,IAChD;AAAA,EACF;AAGA,MAAI,WAAW,GAAG;AAChB,WAAO,KAAK,EAAE;AACd,aAAS,IAAI,SAAS,IAAI,OAAO,QAAQ,KAAK;AAC5C,aAAO,KAAK,gBAAgB,OAAO,CAAC,CAAC,CAAC;AAAA,IACxC;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,IAAI;AACzB;AAMA,SAAS,eAAe,OAAqB,SAA2B;AACtE,QAAM,UAAU,QAAQ;AAGxB,QAAM,QAAoB,MAAM,IAAI,WAAS;AAC3C,UAAM,MAAM,MAAM,OAAO,EAAE,KAAK,EAAE;AAClC,eAAW,QAAQ,OAAO;AACxB,YAAM,MAAM,WAAW,KAAK,GAAG,OAAO;AACtC,UAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK,OAAO,KAAK;AAAA,IAC1D;AACA,WAAO;AAAA,EACT,CAAC;AAID,QAAM,eAAe,KAAK,IAAI,GAAG,KAAK,MAAM,UAAU,CAAC,CAAC;AACxD,QAAM,SAAqB,CAAC;AAE5B,aAAW,OAAO,OAAO;AACvB,QAAI,IAAI,MAAM,OAAK,MAAM,EAAE,EAAG;AAE9B,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,KAAK,CAAC,GAAG,GAAG,CAAC;AACpB;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,aAAa,IAAI,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,EAAE,OAAO,OAAK,KAAK,CAAC;AACnE,UAAM,cAAc,WAAW;AAE/B,QAAI,WAAW;AAGf,QAAI,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,UAAU,GAAG;AAChC,iBAAW;AAAA,IACb;AAGA,QAAI,CAAC,YAAY,UAAU,KAAK,IAAI,CAAC,GAAG;AACtC,iBAAW;AAAA,IACb;AAGA,QAAI,CAAC,UAAU;AACb,YAAM,UAAU,IAAI,MAAM,YAAY,EAAE,KAAK,OAAK,MAAM,EAAE;AAC1D,YAAM,cAAc,KAAK,MAAM,YAAY,EAAE,KAAK,OAAK,MAAM,EAAE;AAC/D,UAAI,WAAW,aAAa;AAC1B,mBAAW;AAAA,MACb;AAAA,IACF;AAGA,QAAI,YAAY,gBAAgB,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,UAAU,GAAG;AACjE,iBAAW;AAAA,IACb;AAEA,QAAI,UAAU;AACZ,aAAO,KAAK,CAAC,GAAG,GAAG,CAAC;AAAA,IACtB,OAAO;AACL,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAI,IAAI,CAAC,GAAG;AACV,eAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,WAAO,OAAO,IAAI,OAAK,EAAE,OAAO,OAAK,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI;AAAA,EAC9D;AAGA,MAAI,YAAY;AAChB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,gBAAgB,OAAO,CAAC,EAAE,MAAM,YAAY,EAAE,KAAK,OAAK,KAAK,KAAK,KAAK,CAAC,CAAC;AAC/E,QAAI,cAAe;AACnB,gBAAY,IAAI;AAAA,EAClB;AAEA,MAAI,YAAY,GAAG;AAEjB,UAAM,YAAY,MAAM,OAAO,EAAE,KAAK,EAAE;AACxC,aAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAI,OAAO,CAAC,EAAE,CAAC,GAAG;AAChB,oBAAU,CAAC,IAAI,UAAU,CAAC,IAAI,UAAU,CAAC,IAAI,MAAM,OAAO,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,EAAE,CAAC;AAAA,QAC/E;AAAA,MACF;AAAA,IACF;AACA,WAAO,OAAO,GAAG,WAAW,SAAS;AAAA,EACvC;AAGA,aAAW,OAAO,QAAQ;AACxB,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAI,IAAI,CAAC,EAAG,KAAI,CAAC,IAAI,oBAAoB,IAAI,CAAC,CAAC;AAAA,IACjD;AAAA,EACF;AAGA,QAAM,aAAa,OAAO,SAAS;AACnC,QAAM,cAAc,OAAO,OAAO,CAAC,GAAG,QAAQ,IAAI,IAAI,OAAO,OAAK,CAAC,EAAE,QAAQ,CAAC;AAE9E,MAAI,cAAc,aAAa,QAAQ,OAAO,SAAS,KAClD,OAAO,UAAU,KAAK,WAAW,GAAI;AACxC,WAAO,OAAO,IAAI,OAAK,EAAE,OAAO,OAAK,CAAC,EAAE,KAAK,GAAI,CAAC,EAAE,KAAK,IAAI;AAAA,EAC/D;AAGA,QAAM,KAAe,CAAC;AACtB,KAAG,KAAK,OAAO,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI,IAAI;AAC3C,KAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,MAAM,KAAK,EAAE,KAAK,KAAK,IAAI,IAAI;AAC5D,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,OAAG,KAAK,OAAO,OAAO,CAAC,EAAE,KAAK,KAAK,IAAI,IAAI;AAAA,EAC7C;AACA,SAAO,GAAG,KAAK,IAAI;AACrB;AAMA,SAAS,gBAAgB,OAA2B;AAClD,MAAI,MAAM,UAAU,EAAG,QAAO,MAAM,CAAC,GAAG,QAAQ;AAChD,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAGlD,QAAM,eAAe,sBAAsB,MAAM;AAEjD,MAAI,SAAS,OAAO,CAAC,EAAE;AACvB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,MAAM,OAAO,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,EAAE;AAC3D,UAAM,SAAS,OAAO,CAAC,EAAE,WAAW,OAAO,IAAI,CAAC,EAAE,YAAY;AAI9D,UAAM,eAAe,KAAK,IAAI,QAAQ,GAAG,EAAE;AAC3C,QAAI,MAAM,cAAc;AACtB,gBAAU;AACV,gBAAU,OAAO,CAAC,EAAE;AACpB;AAAA,IACF;AAGA,QAAI,aAAa,CAAC,GAAG;AACnB,gBAAU,OAAO,CAAC,EAAE;AACpB;AAAA,IACF;AAGA,QAAI,OAAO,CAAC,EAAE,kBAAkB,OAAO,QAAQ,MAAM;AACnD,gBAAU;AACV,gBAAU,OAAO,CAAC,EAAE;AACpB;AAAA,IACF;AAEA,QAAI,cAAc,KAAK,OAAO,IAAI,CAAC,EAAE,IAAI,KAAK,SAAS,KAAK,OAAO,CAAC,EAAE,IAAI,KAAK,MAAM,GAAG;AACtF,gBAAU;AACV,gBAAU,OAAO,CAAC,EAAE;AACpB;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,MAAM;AAAA,IAAiB,WAEhC,MAAM,QAAQ,SAAS,SAAS,KAAK,MAAM,KAAK,SAAS,KAAK,OAAO,CAAC,EAAE,IAAI,IAAI;AAAA,IAAiB,WAEjG,MAAM,EAAG,WAAU;AAC5B,cAAU,OAAO,CAAC,EAAE;AAAA,EACtB;AACA,SAAO;AACT;AAKO,SAAS,aAAa,MAAsB;AACjD,SAAO;AAAA,IACL,KAEG,QAAQ,cAAc,EAAE,EAExB,QAAQ,oDAAoD,EAAE,EAE9D,QAAQ,4BAA4B,EAAE,EAEtC,QAAQ,gBAAgB,IAAI,EAE5B,QAAQ,cAAc,EAAE,EAExB,QAAQ,2BAA2B,EAAE;AAAA,EAC1C,EAEG,QAAQ,oBAAoB,UAAQ,oBAAoB,IAAI,CAAC,EAE7D,QAAQ,oCAAoC,SAAS,EACrD,QAAQ,WAAW,MAAM,EACzB,KAAK;AACV;AAEA,SAAS,iBAAiB,MAAuB;AAC/C,QAAM,IAAI,KAAK,UAAU;AACzB,SAAO,gBAAgB,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,KAAK,mBAAmB,KAAK,CAAC,KAC/E,sBAAsB,KAAK,CAAC,KAAK,eAAe,KAAK,CAAC;AAC1D;AAEA,SAAS,mBAAmB,MAAuB;AACjD,SAAO,yCAAyC,KAAK,KAAK,KAAK,CAAC;AAClE;AAUA,SAAS,iBAAiB,QAA8B;AACtD,QAAM,SAAoB,CAAC;AAE3B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC;AAEtB,QAAI,MAAM,SAAS,eAAe,MAAM,MAAM;AAC5C,YAAM,OAAO,MAAM,KAAK,KAAK;AAE7B,UAAI,WAAW,KAAK,IAAI,GAAG;AACzB,eAAO,KAAK,EAAE,GAAG,OAAO,MAAM,QAAQ,UAAU,WAAW,MAAM,MAAM,KAAK,CAAC;AAC7E;AAAA,MACF;AAEA,UAAI,kBAAkB,KAAK,IAAI,GAAG;AAChC,eAAO,KAAK,EAAE,GAAG,OAAO,MAAM,QAAQ,UAAU,aAAa,MAAM,MAAM,KAAK,CAAC;AAC/E;AAAA,MACF;AAAA,IACF;AAEA,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,SAAO;AACT;AAeA,IAAM,yBAAyB;AAG/B,IAAM,uBAAuB;AAE7B,SAAS,0BAA0B,QAA8B;AAC/D,QAAM,SAAoB,CAAC;AAC3B,MAAI,UAA4D,CAAC;AAEjE,QAAM,eAAe,MAAM;AACzB,QAAI,QAAQ,SAAS,GAAG;AAEtB,iBAAW,MAAM,QAAS,QAAO,KAAK,GAAG,KAAK;AAC9C,gBAAU,CAAC;AACX;AAAA,IACF;AAGA,UAAM,QAA0C,QAAQ,IAAI,QAAM;AAChE,UAAI,GAAG,OAAO;AACZ,eAAO;AAAA,UACL,EAAE,MAAM,GAAG,KAAK,SAAS,GAAG,SAAS,EAAE;AAAA,UACvC,EAAE,MAAM,GAAG,OAAO,SAAS,GAAG,SAAS,EAAE;AAAA,QAC3C;AAAA,MACF;AAEA,aAAO;AAAA,QACL,EAAE,MAAM,GAAG,KAAK,SAAS,GAAG,SAAS,EAAE;AAAA,QACvC,EAAE,MAAM,IAAI,SAAS,GAAG,SAAS,EAAE;AAAA,MACrC;AAAA,IACF,CAAC;AAED,UAAM,UAAmB;AAAA,MACvB,MAAM,MAAM;AAAA,MACZ,MAAM;AAAA,MACN;AAAA,MACA,WAAW;AAAA,IACb;AAGA,UAAM,aAAa,QAAQ,CAAC,EAAE;AAC9B,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,OAAO;AAAA,MACP,YAAY,WAAW;AAAA,MACvB,MAAM,WAAW;AAAA,IACnB,CAAC;AACD,cAAU,CAAC;AAAA,EACb;AAEA,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,eAAe,CAAC,MAAM,MAAM;AAC7C,mBAAa;AACb,aAAO,KAAK,KAAK;AACjB;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,KAAK,KAAK;AAG7B,QAAI,uBAAuB,KAAK,IAAI,GAAG;AACrC,YAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,UAAI,YAAY,GAAG;AACjB,gBAAQ,KAAK;AAAA,UACX,KAAK,KAAK,MAAM,GAAG,QAAQ,EAAE,KAAK;AAAA,UAClC,OAAO,KAAK,MAAM,WAAW,CAAC,EAAE,KAAK;AAAA,UACrC;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AAEL,cAAM,WAAW,KAAK,OAAO,IAAI;AACjC,YAAI,WAAW,GAAG;AAChB,kBAAQ,KAAK;AAAA,YACX,KAAK,KAAK,MAAM,GAAG,QAAQ,EAAE,KAAK;AAAA,YAClC,OAAO,KAAK,MAAM,WAAW,CAAC,EAAE,KAAK;AAAA,YACrC;AAAA,UACF,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,KAAK,EAAE,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC;AAAA,QAC9C;AAAA,MACF;AACA;AAAA,IACF;AAIA,QAAI,QAAQ,SAAS,KAAK,KAAK,SAAS,GAAG,GAAG;AAE5C,UAAI,CAAC,qBAAqB,KAAK,IAAI,KAAK,CAAC,KAAK,SAAS,GAAG,KAAK,CAAC,KAAK,SAAS,GAAG,GAAG;AAClF,cAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,cAAM,MAAM,KAAK,MAAM,GAAG,QAAQ,EAAE,KAAK;AAEzC,YAAI,WAAW,KAAK,GAAG,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,GAAG;AAC9D,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA,OAAO,KAAK,MAAM,WAAW,CAAC,EAAE,KAAK;AAAA,YACrC;AAAA,UACF,CAAC;AACD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,iBAAa;AACb,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,eAAa;AACb,SAAO;AACT;AAKA,SAAS,yBACP,QACA,aACA,UACU;AACV,QAAM,aAAa;AACnB,QAAM,aAAa;AAGnB,QAAM,cAAc,oBAAI,IAAsB;AAC9C,QAAM,cAAc,oBAAI,IAAsB;AAE9C,WAAS,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM;AACzC,UAAM,IAAI,OAAO,EAAE;AACnB,QAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,EAAG;AACjD,UAAM,KAAK,YAAY,IAAI,EAAE,KAAK,IAAI,KAAK,YAAY,IAAI,EAAE,UAAU;AACvE,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,MAAM,EAAE,KAAK,IAAI,EAAE,KAAK;AACzC,UAAM,cAAc,KAAK,EAAE,KAAK;AAEhC,QAAI,eAAe,KAAK,YAAY;AAElC,YAAM,MAAM,YAAY,IAAI,EAAE,UAAU,KAAK,CAAC;AAC9C,UAAI,KAAK,EAAE,KAAK,KAAK,CAAC;AACtB,kBAAY,IAAI,EAAE,YAAY,GAAG;AAAA,IACnC,WAAW,YAAY,MAAM,IAAI,aAAa;AAE5C,YAAM,MAAM,YAAY,IAAI,EAAE,UAAU,KAAK,CAAC;AAC9C,UAAI,KAAK,EAAE,KAAK,KAAK,CAAC;AACtB,kBAAY,IAAI,EAAE,YAAY,GAAG;AAAA,IACnC;AAAA,EACF;AAGA,QAAM,mBAAmB,oBAAI,IAAY;AACzC,aAAW,YAAY,CAAC,aAAa,WAAW,GAAG;AACjD,UAAM,eAAe,oBAAI,IAAoB;AAC7C,eAAW,CAAC,EAAE,KAAK,KAAK,UAAU;AAChC,iBAAW,KAAK,OAAO;AAErB,cAAM,aAAa,EAAE,QAAQ,QAAQ,GAAG;AACxC,qBAAa,IAAI,aAAa,aAAa,IAAI,UAAU,KAAK,KAAK,CAAC;AAAA,MACtE;AAAA,IACF;AACA,eAAW,CAAC,SAAS,KAAK,KAAK,cAAc;AAC3C,UAAI,SAAS,WAAY,kBAAiB,IAAI,OAAO;AAAA,IACvD;AAAA,EACF;AAEA,MAAI,iBAAiB,SAAS,EAAG,QAAO,CAAC;AAGzC,QAAM,gBAA0B,CAAC;AACjC,WAAS,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM;AACzC,UAAM,IAAI,OAAO,EAAE;AACnB,QAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,EAAG;AACjD,UAAM,KAAK,YAAY,IAAI,EAAE,KAAK,IAAI,KAAK,YAAY,IAAI,EAAE,UAAU;AACvE,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,MAAM,EAAE,KAAK,IAAI,EAAE,KAAK;AACzC,UAAM,cAAc,KAAK,EAAE,KAAK;AAChC,UAAM,SAAS,eAAe,KAAK,cAAc,YAAY,MAAM,IAAI;AACvE,QAAI,CAAC,OAAQ;AAEb,UAAM,aAAa,EAAE,KAAK,KAAK,EAAE,QAAQ,QAAQ,GAAG;AACpD,QAAI,iBAAiB,IAAI,UAAU,GAAG;AACpC,oBAAc,KAAK,EAAE;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,cAAc,SAAS,GAAG;AAC5B,aAAS,KAAK,EAAE,SAAS,GAAG,cAAc,MAAM,gFAAoB,MAAM,uBAAuB,CAAC;AAAA,EACpG;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAAsB;AAC9C,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,QAAM,SAAmB,CAAC,MAAM,CAAC,CAAC;AAElC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,cAAc,KAAK,KAAK;AAE9B,QAAI,YAAY,KAAK,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,MAAM,KAAK,WAAW,KAAK,OAAO,KAAK,WAAW,GAAG;AAC3G,aAAO,KAAK,IAAI;AAChB;AAAA,IACF;AAEA,QAAI,KAAK,KAAK,KAAK,KAAK,CAAC,KAAK,YAAY,SAAS,GAAG;AACpD,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,OAAO;AAC1C;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,MAAM;AACzC;AAAA,IACF;AAEA,QAAI,aAAa,KAAK,IAAI,KAAK,UAAU,KAAK,IAAI,KAC9C,CAAC,iBAAiB,IAAI,KAAK,CAAC,mBAAmB,IAAI,KACnD,CAAC,iBAAiB,IAAI,GAAG;AAC3B,aAAO,OAAO,SAAS,CAAC,IAAI,OAAO,MAAM;AAAA,IAC3C,OAAO;AACL,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AACA,SAAO,OAAO,KAAK,IAAI;AACzB;","names":["_","g","g","totalCells","emptyCells"]}
1
+ {"version":3,"sources":["../src/pdf/line-detector.ts","../src/pdf/cluster-detector.ts","../src/pdf/polyfill.ts","../src/pdf/parser.ts"],"sourcesContent":["/**\r\n * PDF 그래픽 명령에서 수평/수직 선을 추출하고,\r\n * 선 교차점(Vertex) 기반으로 테이블 그리드를 구성하는 모듈.\r\n *\r\n * 이 파일의 테이블 감지 알고리즘은 OpenDataLoader PDF의\r\n * TableBorderBuilder / LinesPreprocessingConsumer를 참고하여\r\n * TypeScript로 clean-room 재구현한 것입니다.\r\n *\r\n * v2: Vertex 기반 동적 tolerance, 선 전처리 파이프라인,\r\n * 정밀 병합 셀 감지 (ODL 알고리즘 충실 포팅)\r\n *\r\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\r\n * https://github.com/opendataloader-project/opendataloader-pdf\r\n * Core algorithm concepts from veraPDF-wcag-algs (GPLv3+/MPLv2+)\r\n * This is an independent clean-room reimplementation in TypeScript.\r\n */\r\n\r\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\r\n\r\n// ─── pdfjs-dist v5 DrawOPS ──\r\nconst enum DrawOPS {\r\n moveTo = 0,\r\n lineTo = 1,\r\n curveTo = 2,\r\n quadraticCurveTo = 3,\r\n closePath = 4,\r\n}\r\n\r\n// ─── 타입 ─────────────────────────────────────────────\r\n\r\nexport interface LineSegment {\r\n x1: number; y1: number\r\n x2: number; y2: number\r\n lineWidth: number\r\n}\r\n\r\n/** 선 교차점 (Vertex) — ODL의 핵심 개념 */\r\ninterface Vertex {\r\n x: number\r\n y: number\r\n /** 교차하는 선들의 최대 lineWidth → tolerance 계산에 사용 */\r\n radius: number\r\n}\r\n\r\nexport interface TableGrid {\r\n /** 행 Y 좌표 경계 (위→아래 내림차순) */\r\n rowYs: number[]\r\n /** 열 X 좌표 경계 (좌→우 오름차순) */\r\n colXs: number[]\r\n /** 테이블 바운딩 박스 */\r\n bbox: { x1: number; y1: number; x2: number; y2: number }\r\n /** 그리드 내 교차점 반경 (동적 tolerance용) */\r\n vertexRadius: number\r\n}\r\n\r\nexport interface ExtractedCell {\r\n row: number; col: number\r\n rowSpan: number; colSpan: number\r\n /** 셀 바운딩 박스 */\r\n bbox: { x1: number; y1: number; x2: number; y2: number }\r\n}\r\n\r\n// ─── 상수 ─────────────────────────────────────────────\r\n\r\n/** 수평/수직 판별 허용 오차 (pt) */\r\nconst ORIENTATION_TOL = 2\r\n/** 최소 선 길이 — 짧은 장식선(체크박스 테두리 등) 무시 */\r\nconst MIN_LINE_LENGTH = 15\r\n/** 굵은 선 필터 — ODL: MAX_LINE_WIDTH = 5.0 (배경 채움/장식 사각형 제외) */\r\nconst MAX_LINE_WIDTH = 5.0\r\n/** 두 선이 같은 테이블에 속하는지 판별하는 거리 */\r\nconst CONNECT_TOL = 5\r\n/** 셀 경계 내부 판별 여유 (텍스트 매핑용) */\r\nconst CELL_PADDING = 2\r\n/** 최소 열 폭 (pt) — 이보다 좁은 열은 인접 열과 병합 */\r\nconst MIN_COL_WIDTH = 15\r\n/** 최소 행 높이 (pt) */\r\nconst MIN_ROW_HEIGHT = 6\r\n/** Vertex 기반 좌표 병합 시 radius 배수 — ODL: VERTEX_TABLE_FACTOR */\r\nconst VERTEX_MERGE_FACTOR = 4\r\n/** 좌표 병합 최소 tolerance (pt) — vertexRadius가 작아도 이 값 이하로 내려가지 않음 */\r\nconst MIN_COORD_MERGE_TOL = 8\r\n\r\n// ─── 선 추출 ──────────────────────────────────────────\r\n\r\n/**\r\n * pdfjs operatorList에서 수평/수직 선을 추출.\r\n * constructPath(91) 내의 moveTo→lineTo, rectangle 패턴을 인식.\r\n */\r\nexport function extractLines(\r\n fnArray: Uint32Array | number[],\r\n argsArray: unknown[][],\r\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\r\n const horizontals: LineSegment[] = []\r\n const verticals: LineSegment[] = []\r\n let lineWidth = 1\r\n\r\n let currentPath: Array<{ x1: number; y1: number; x2: number; y2: number }> = []\r\n let pathStartX = 0, pathStartY = 0\r\n let curX = 0, curY = 0\r\n\r\n function pushRectangle(\r\n path: Array<{ x1: number; y1: number; x2: number; y2: number }>,\r\n rx: number, ry: number, rw: number, rh: number,\r\n ) {\r\n if (Math.abs(rh) < ORIENTATION_TOL * 2) {\r\n path.push({ x1: rx, y1: ry + rh / 2, x2: rx + rw, y2: ry + rh / 2 })\r\n } else if (Math.abs(rw) < ORIENTATION_TOL * 2) {\r\n path.push({ x1: rx + rw / 2, y1: ry, x2: rx + rw / 2, y2: ry + rh })\r\n } else {\r\n path.push(\r\n { x1: rx, y1: ry, x2: rx + rw, y2: ry },\r\n { x1: rx + rw, y1: ry, x2: rx + rw, y2: ry + rh },\r\n { x1: rx + rw, y1: ry + rh, x2: rx, y2: ry + rh },\r\n { x1: rx, y1: ry + rh, x2: rx, y2: ry },\r\n )\r\n }\r\n }\r\n\r\n function flushPath(isStroke: boolean) {\r\n if (!isStroke) { currentPath = []; return }\r\n for (const seg of currentPath) {\r\n classifyAndAdd(seg, lineWidth, horizontals, verticals)\r\n }\r\n currentPath = []\r\n }\r\n\r\n for (let i = 0; i < fnArray.length; i++) {\r\n const op = fnArray[i]\r\n const args = argsArray[i]\r\n\r\n switch (op) {\r\n case OPS.setLineWidth:\r\n lineWidth = (args as number[])[0] || 1\r\n break\r\n\r\n case OPS.constructPath: {\r\n const arg0 = args[0]\r\n\r\n if (Array.isArray(arg0)) {\r\n // ── pdfjs-dist v4 형식 ──\r\n const subOps = arg0 as number[]\r\n const coords = (args as [number[], number[]])[1]\r\n let ci = 0\r\n\r\n for (const subOp of subOps) {\r\n if (subOp === OPS.moveTo) {\r\n curX = coords[ci++]; curY = coords[ci++]\r\n pathStartX = curX; pathStartY = curY\r\n } else if (subOp === OPS.lineTo) {\r\n const x2 = coords[ci++], y2 = coords[ci++]\r\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\r\n curX = x2; curY = y2\r\n } else if (subOp === OPS.rectangle) {\r\n const rx = coords[ci++], ry = coords[ci++]\r\n const rw = coords[ci++], rh = coords[ci++]\r\n pushRectangle(currentPath, rx, ry, rw, rh)\r\n } else if (subOp === OPS.closePath) {\r\n if (curX !== pathStartX || curY !== pathStartY) {\r\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\r\n }\r\n curX = pathStartX; curY = pathStartY\r\n } else if (subOp === OPS.curveTo) {\r\n ci += 6\r\n } else if (subOp === OPS.curveTo2 || subOp === OPS.curveTo3) {\r\n ci += 4\r\n }\r\n }\r\n } else {\r\n // ── pdfjs-dist v5 형식 ──\r\n const afterOp = arg0 as number\r\n const dataArr = args[1] as unknown[]\r\n const pathData = dataArr?.[0] as Record<number, number> | undefined\r\n if (pathData && typeof pathData === \"object\") {\r\n const len = Object.keys(pathData).length\r\n let di = 0\r\n while (di < len) {\r\n const drawOp = pathData[di++]\r\n if (drawOp === DrawOPS.moveTo) {\r\n curX = pathData[di++]; curY = pathData[di++]\r\n pathStartX = curX; pathStartY = curY\r\n } else if (drawOp === DrawOPS.lineTo) {\r\n const x2 = pathData[di++], y2 = pathData[di++]\r\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\r\n curX = x2; curY = y2\r\n } else if (drawOp === DrawOPS.curveTo) {\r\n di += 6\r\n } else if (drawOp === DrawOPS.quadraticCurveTo) {\r\n di += 4\r\n } else if (drawOp === DrawOPS.closePath) {\r\n if (curX !== pathStartX || curY !== pathStartY) {\r\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\r\n }\r\n curX = pathStartX; curY = pathStartY\r\n } else {\r\n break\r\n }\r\n }\r\n }\r\n\r\n if (afterOp === OPS.stroke || afterOp === OPS.closeStroke) {\r\n flushPath(true)\r\n } else if (afterOp === OPS.fill || afterOp === OPS.eoFill ||\r\n afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke ||\r\n afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {\r\n flushPath(true)\r\n } else if (afterOp === OPS.endPath) {\r\n flushPath(false)\r\n }\r\n }\r\n break\r\n }\r\n\r\n case OPS.stroke:\r\n case OPS.closeStroke:\r\n flushPath(true)\r\n break\r\n\r\n case OPS.fill:\r\n case OPS.eoFill:\r\n case OPS.fillStroke:\r\n case OPS.eoFillStroke:\r\n case OPS.closeFillStroke:\r\n case OPS.closeEOFillStroke:\r\n flushPath(true)\r\n break\r\n\r\n case OPS.endPath:\r\n flushPath(false)\r\n break\r\n }\r\n }\r\n\r\n return { horizontals, verticals }\r\n}\r\n\r\nfunction classifyAndAdd(\r\n seg: { x1: number; y1: number; x2: number; y2: number },\r\n lineWidth: number,\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n) {\r\n const dx = Math.abs(seg.x2 - seg.x1)\r\n const dy = Math.abs(seg.y2 - seg.y1)\r\n const length = Math.sqrt(dx * dx + dy * dy)\r\n\r\n if (length < MIN_LINE_LENGTH) return\r\n\r\n if (dy <= ORIENTATION_TOL) {\r\n const y = (seg.y1 + seg.y2) / 2\r\n const x1 = Math.min(seg.x1, seg.x2)\r\n const x2 = Math.max(seg.x1, seg.x2)\r\n horizontals.push({ x1, y1: y, x2, y2: y, lineWidth })\r\n } else if (dx <= ORIENTATION_TOL) {\r\n const x = (seg.x1 + seg.x2) / 2\r\n const y1 = Math.min(seg.y1, seg.y2)\r\n const y2 = Math.max(seg.y1, seg.y2)\r\n verticals.push({ x1: x, y1, x2: x, y2, lineWidth })\r\n }\r\n}\r\n\r\n// ─── 선 전처리 파이프라인 (ODL LinesPreprocessingConsumer 포팅) ──\r\n\r\n/**\r\n * 선 전처리: 굵은 선 필터 → 근접 선 병합 → 장식선 필터링\r\n * ODL의 LinesPreprocessingConsumer가 하는 핵심 로직.\r\n */\r\nexport function preprocessLines(\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\r\n // 1. 굵은 선 필터링 (배경 채움 사각형, 장식 테두리 등)\r\n let h = horizontals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\r\n let v = verticals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\r\n\r\n // 2. 근접 평행 선 병합 (인쇄 잔상, 이중선)\r\n h = mergeParallelLines(h, \"h\")\r\n v = mergeParallelLines(v, \"v\")\r\n\r\n return { horizontals: h, verticals: v }\r\n}\r\n\r\n/**\r\n * 근접 평행 선 병합 — 같은 방향의 가까운 선을 하나로 합침.\r\n * 이중선, 인쇄 잔상, PDF 렌더링 미세 차이로 인한 중복 선 제거.\r\n */\r\nfunction mergeParallelLines(lines: LineSegment[], dir: \"h\" | \"v\"): LineSegment[] {\r\n if (lines.length <= 1) return lines\r\n\r\n // 수평선: y로 정렬, 수직선: x로 정렬\r\n const sorted = [...lines].sort((a, b) => {\r\n const posA = dir === \"h\" ? a.y1 : a.x1\r\n const posB = dir === \"h\" ? b.y1 : b.x1\r\n if (Math.abs(posA - posB) > 0.1) return posA - posB\r\n // 같은 위치면 시작 좌표로\r\n return dir === \"h\" ? (a.x1 - b.x1) : (a.y1 - b.y1)\r\n })\r\n\r\n const MERGE_TOL = 3 // 3pt 이내 평행 선 병합\r\n\r\n const result: LineSegment[] = [sorted[0]]\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prev = result[result.length - 1]\r\n const curr = sorted[i]\r\n\r\n const prevPos = dir === \"h\" ? prev.y1 : prev.x1\r\n const currPos = dir === \"h\" ? curr.y1 : curr.x1\r\n\r\n if (Math.abs(prevPos - currPos) <= MERGE_TOL) {\r\n // 범위가 겹치는지 확인\r\n const prevStart = dir === \"h\" ? prev.x1 : prev.y1\r\n const prevEnd = dir === \"h\" ? prev.x2 : prev.y2\r\n const currStart = dir === \"h\" ? curr.x1 : curr.y1\r\n const currEnd = dir === \"h\" ? curr.x2 : curr.y2\r\n\r\n const overlap = Math.min(prevEnd, currEnd) - Math.max(prevStart, currStart)\r\n const minLen = Math.min(prevEnd - prevStart, currEnd - currStart)\r\n\r\n if (overlap > minLen * 0.3) {\r\n // 병합: 범위 확장, lineWidth는 최대값 유지\r\n if (dir === \"h\") {\r\n prev.x1 = Math.min(prev.x1, curr.x1)\r\n prev.x2 = Math.max(prev.x2, curr.x2)\r\n prev.y1 = (prev.y1 + curr.y1) / 2\r\n prev.y2 = prev.y1\r\n } else {\r\n prev.y1 = Math.min(prev.y1, curr.y1)\r\n prev.y2 = Math.max(prev.y2, curr.y2)\r\n prev.x1 = (prev.x1 + curr.x1) / 2\r\n prev.x2 = prev.x1\r\n }\r\n prev.lineWidth = Math.max(prev.lineWidth, curr.lineWidth)\r\n continue\r\n }\r\n }\r\n result.push(curr)\r\n }\r\n return result\r\n}\r\n\r\n// ─── 페이지 경계(클립) 선 필터링 ──────────────────────\r\n\r\nexport function filterPageBorderLines(\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n pageWidth: number,\r\n pageHeight: number,\r\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\r\n const margin = 5\r\n return {\r\n horizontals: horizontals.filter(l =>\r\n !(Math.abs(l.y1) < margin || Math.abs(l.y1 - pageHeight) < margin) ||\r\n (l.x2 - l.x1) < pageWidth * 0.9\r\n ),\r\n verticals: verticals.filter(l =>\r\n !(Math.abs(l.x1) < margin || Math.abs(l.x1 - pageWidth) < margin) ||\r\n (l.y2 - l.y1) < pageHeight * 0.9\r\n ),\r\n }\r\n}\r\n\r\n// ─── Vertex(교차점) 생성 ─────────────────────────────\r\n\r\n/**\r\n * 수평선과 수직선의 교차점(Vertex)을 생성.\r\n * ODL의 TableBorderBuilder.addLine()이 교차점을 자동 생성하는 것과 동일.\r\n * 각 Vertex는 교차하는 선들의 lineWidth로 radius를 계산 → 동적 tolerance.\r\n */\r\nfunction buildVertices(horizontals: LineSegment[], verticals: LineSegment[]): Vertex[] {\r\n const vertices: Vertex[] = []\r\n const tol = CONNECT_TOL\r\n\r\n for (const h of horizontals) {\r\n for (const v of verticals) {\r\n // 수평선의 X범위에 수직선의 X가 포함되고\r\n // 수직선의 Y범위에 수평선의 Y가 포함되면 → 교차\r\n if (v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\r\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol) {\r\n const radius = Math.max(h.lineWidth, v.lineWidth, 1)\r\n vertices.push({ x: v.x1, y: h.y1, radius })\r\n }\r\n }\r\n }\r\n\r\n return vertices\r\n}\r\n\r\n/**\r\n * 근접 Vertex 병합 — 같은 교차점의 미세 위치 차이를 하나로 합침.\r\n */\r\nfunction mergeVertices(vertices: Vertex[]): Vertex[] {\r\n if (vertices.length <= 1) return vertices\r\n\r\n const merged: Vertex[] = []\r\n const used = new Array(vertices.length).fill(false)\r\n\r\n for (let i = 0; i < vertices.length; i++) {\r\n if (used[i]) continue\r\n let sumX = vertices[i].x, sumY = vertices[i].y\r\n let maxRadius = vertices[i].radius\r\n let count = 1\r\n\r\n for (let j = i + 1; j < vertices.length; j++) {\r\n if (used[j]) continue\r\n const mergeTol = VERTEX_MERGE_FACTOR * Math.max(maxRadius, vertices[j].radius)\r\n if (Math.abs(vertices[i].x - vertices[j].x) <= mergeTol &&\r\n Math.abs(vertices[i].y - vertices[j].y) <= mergeTol) {\r\n sumX += vertices[j].x\r\n sumY += vertices[j].y\r\n maxRadius = Math.max(maxRadius, vertices[j].radius)\r\n count++\r\n used[j] = true\r\n }\r\n }\r\n\r\n merged.push({ x: sumX / count, y: sumY / count, radius: maxRadius })\r\n }\r\n\r\n return merged\r\n}\r\n\r\n// ─── 테이블 그리드 구성 (Vertex 기반) ─────────────────\r\n\r\n/**\r\n * 수평/수직 선에서 테이블 그리드를 추출.\r\n * ODL과 동일한 흐름:\r\n * 1. 선 전처리 (preprocessLines — 호출측에서 수행)\r\n * 2. 교차점(Vertex) 생성 + 병합\r\n * 3. 교차하는 선들을 그룹화 (연결 컴포넌트)\r\n * 4. 각 그룹에서 Vertex의 X/Y 좌표를 동적 tolerance로 클러스터링\r\n * 5. 그리드 검증 (최소 열 폭, 최소 행 높이)\r\n */\r\nexport function buildTableGrids(\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n): TableGrid[] {\r\n if (horizontals.length < 2 || verticals.length < 2) return []\r\n\r\n // 1. 교차점 생성\r\n const allVertices = buildVertices(horizontals, verticals)\r\n const vertices = mergeVertices(allVertices)\r\n\r\n if (vertices.length < 4) return [] // 최소 4꼭짓점 필요 (사각형)\r\n\r\n // 전체 vertex의 대표 radius (동적 tolerance)\r\n const globalRadius = vertices.reduce((max, v) => Math.max(max, v.radius), 1)\r\n\r\n // 2. 선들을 교차 관계로 그룹화\r\n const allLines = [\r\n ...horizontals.map((l, i) => ({ ...l, type: \"h\" as const, id: i })),\r\n ...verticals.map((l, i) => ({ ...l, type: \"v\" as const, id: i + horizontals.length })),\r\n ]\r\n\r\n const groups = groupConnectedLines(allLines)\r\n const grids: TableGrid[] = []\r\n\r\n for (const group of groups) {\r\n const hLines = group.filter(l => l.type === \"h\")\r\n const vLines = group.filter(l => l.type === \"v\")\r\n\r\n if (hLines.length < 2 || vLines.length < 2) continue\r\n\r\n // 3. 이 그룹의 Vertex만 수집\r\n let gx1 = Infinity, gy1 = Infinity, gx2 = -Infinity, gy2 = -Infinity\r\n for (const l of vLines) { if (l.x1 < gx1) gx1 = l.x1; if (l.x1 > gx2) gx2 = l.x1 }\r\n for (const l of hLines) { if (l.y1 < gy1) gy1 = l.y1; if (l.y1 > gy2) gy2 = l.y1 }\r\n const groupBbox = {\r\n x1: gx1 - CONNECT_TOL,\r\n y1: gy1 - CONNECT_TOL,\r\n x2: gx2 + CONNECT_TOL,\r\n y2: gy2 + CONNECT_TOL,\r\n }\r\n\r\n const groupVertices = vertices.filter(v =>\r\n v.x >= groupBbox.x1 && v.x <= groupBbox.x2 &&\r\n v.y >= groupBbox.y1 && v.y <= groupBbox.y2\r\n )\r\n\r\n // 그룹 vertex의 대표 radius\r\n const groupRadius = groupVertices.length > 0\r\n ? groupVertices.reduce((max, v) => Math.max(max, v.radius), 1)\r\n : globalRadius\r\n\r\n // 4. Vertex 기반 좌표 클러스터링 (동적 tolerance)\r\n const coordMergeTol = Math.max(VERTEX_MERGE_FACTOR * groupRadius, MIN_COORD_MERGE_TOL)\r\n\r\n // Y좌표: 수평선 y + Vertex y\r\n const rawYs = [\r\n ...hLines.map(l => l.y1),\r\n ...groupVertices.map(v => v.y),\r\n ]\r\n const rowYs = clusterCoordinates(rawYs, coordMergeTol).sort((a, b) => b - a)\r\n\r\n // X좌표: 수직선 x + Vertex x\r\n const rawXs = [\r\n ...vLines.map(l => l.x1),\r\n ...groupVertices.map(v => v.x),\r\n ]\r\n const colXs = clusterCoordinates(rawXs, coordMergeTol).sort((a, b) => a - b)\r\n\r\n if (rowYs.length < 2 || colXs.length < 2) continue\r\n\r\n // 5. 그리드 검증: 최소 열 폭, 최소 행 높이\r\n const validColXs = enforceMinWidth(colXs, MIN_COL_WIDTH)\r\n const validRowYs = enforceMinHeight(rowYs, MIN_ROW_HEIGHT)\r\n\r\n if (validRowYs.length < 2 || validColXs.length < 2) continue\r\n\r\n const bbox = {\r\n x1: validColXs[0], y1: validRowYs[validRowYs.length - 1],\r\n x2: validColXs[validColXs.length - 1], y2: validRowYs[0],\r\n }\r\n\r\n grids.push({ rowYs: validRowYs, colXs: validColXs, bbox, vertexRadius: groupRadius })\r\n }\r\n\r\n return mergeAdjacentGrids(grids)\r\n}\r\n\r\n/** 최소 열 폭 보장 — 너무 좁은 열은 인접 열과 병합 */\r\nfunction enforceMinWidth(colXs: number[], minWidth: number): number[] {\r\n if (colXs.length <= 2) return colXs\r\n const result: number[] = [colXs[0]]\r\n for (let i = 1; i < colXs.length; i++) {\r\n const prevX = result[result.length - 1]\r\n if (colXs[i] - prevX < minWidth && i < colXs.length - 1) {\r\n // 너무 좁으면 스킵 (다음 열과 병합)\r\n continue\r\n }\r\n result.push(colXs[i])\r\n }\r\n return result\r\n}\r\n\r\n/** 최소 행 높이 보장 — 너무 낮은 행은 인접 행과 병합 */\r\nfunction enforceMinHeight(rowYs: number[], minHeight: number): number[] {\r\n if (rowYs.length <= 2) return rowYs\r\n // rowYs는 내림차순 (위→아래)\r\n const result: number[] = [rowYs[0]]\r\n for (let i = 1; i < rowYs.length; i++) {\r\n const prevY = result[result.length - 1]\r\n if (prevY - rowYs[i] < minHeight && i < rowYs.length - 1) {\r\n continue\r\n }\r\n result.push(rowYs[i])\r\n }\r\n return result\r\n}\r\n\r\n/** 같은 열 구조를 가진 인접 그리드를 병합 */\r\nfunction mergeAdjacentGrids(grids: TableGrid[]): TableGrid[] {\r\n if (grids.length <= 1) return grids\r\n const sorted = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\r\n const merged: TableGrid[] = [sorted[0]]\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prev = merged[merged.length - 1]\r\n const curr = sorted[i]\r\n\r\n if (prev.colXs.length === curr.colXs.length) {\r\n const mergeTol = Math.max(VERTEX_MERGE_FACTOR * Math.max(prev.vertexRadius, curr.vertexRadius), 6) * 3\r\n const colMatch = prev.colXs.every((x, ci) => Math.abs(x - curr.colXs[ci]) <= mergeTol)\r\n const verticalGap = prev.bbox.y1 - curr.bbox.y2\r\n if (colMatch && verticalGap >= -CONNECT_TOL && verticalGap <= 20) {\r\n const allRowYs = [...new Set([...prev.rowYs, ...curr.rowYs])].sort((a, b) => b - a)\r\n merged[merged.length - 1] = {\r\n rowYs: allRowYs,\r\n colXs: prev.colXs,\r\n bbox: {\r\n x1: Math.min(prev.bbox.x1, curr.bbox.x1),\r\n y1: Math.min(prev.bbox.y1, curr.bbox.y1),\r\n x2: Math.max(prev.bbox.x2, curr.bbox.x2),\r\n y2: Math.max(prev.bbox.y2, curr.bbox.y2),\r\n },\r\n vertexRadius: Math.max(prev.vertexRadius, curr.vertexRadius),\r\n }\r\n continue\r\n }\r\n }\r\n merged.push(curr)\r\n }\r\n return merged\r\n}\r\n\r\n/** 좌표값 클러스터링 — 동적 tolerance 기반 (ODL의 vertex radius 반영) */\r\nfunction clusterCoordinates(values: number[], tolerance: number): number[] {\r\n if (values.length === 0) return []\r\n const sorted = [...values].sort((a, b) => a - b)\r\n const clusters: { sum: number; count: number }[] = [{ sum: sorted[0], count: 1 }]\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const last = clusters[clusters.length - 1]\r\n const avg = last.sum / last.count\r\n if (Math.abs(sorted[i] - avg) <= tolerance) {\r\n last.sum += sorted[i]\r\n last.count++\r\n } else {\r\n clusters.push({ sum: sorted[i], count: 1 })\r\n }\r\n }\r\n\r\n return clusters.map(c => c.sum / c.count)\r\n}\r\n\r\ntype TypedLine = LineSegment & { type: \"h\" | \"v\"; id: number }\r\n\r\n/** 교차하는 선들을 Union-Find로 그룹화 */\r\nfunction groupConnectedLines(lines: TypedLine[]): TypedLine[][] {\r\n const parent = lines.map((_, i) => i)\r\n\r\n function find(x: number): number {\r\n while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x] }\r\n return x\r\n }\r\n function union(a: number, b: number) {\r\n const ra = find(a), rb = find(b)\r\n if (ra !== rb) parent[ra] = rb\r\n }\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n for (let j = i + 1; j < lines.length; j++) {\r\n if (linesIntersect(lines[i], lines[j])) {\r\n union(i, j)\r\n }\r\n }\r\n }\r\n\r\n const groups = new Map<number, TypedLine[]>()\r\n for (let i = 0; i < lines.length; i++) {\r\n const root = find(i)\r\n if (!groups.has(root)) groups.set(root, [])\r\n groups.get(root)!.push(lines[i])\r\n }\r\n\r\n return [...groups.values()]\r\n}\r\n\r\n/** 수평선과 수직선의 교차 판정 (tolerance 포함) */\r\nfunction linesIntersect(a: TypedLine, b: TypedLine): boolean {\r\n if (a.type === b.type) {\r\n if (a.type === \"h\") {\r\n if (Math.abs(a.y1 - b.y1) > CONNECT_TOL) return false\r\n return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - CONNECT_TOL\r\n } else {\r\n if (Math.abs(a.x1 - b.x1) > CONNECT_TOL) return false\r\n return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - CONNECT_TOL\r\n }\r\n }\r\n\r\n const h = a.type === \"h\" ? a : b\r\n const v = a.type === \"h\" ? b : a\r\n const tol = CONNECT_TOL\r\n\r\n return (\r\n v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\r\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol\r\n )\r\n}\r\n\r\n// ─── 셀 구조 추출 (Vertex 기반 정밀 병합 셀 감지) ─────\r\n\r\n/**\r\n * 테이블 그리드에서 셀 목록을 추출.\r\n * ODL의 createMatrix() 알고리즘:\r\n * - 수직선 존재 여부로 colSpan 감지 (75% 커버 기준)\r\n * - 수평선 존재 여부로 rowSpan 감지 (75% 커버 기준)\r\n * - 우하단→좌상단 propagation으로 병합 셀 정리\r\n * - 중복 행/열 제거\r\n */\r\nexport function extractCells(\r\n grid: TableGrid,\r\n horizontals: LineSegment[],\r\n verticals: LineSegment[],\r\n): ExtractedCell[] {\r\n const { rowYs, colXs } = grid\r\n const numRows = rowYs.length - 1\r\n const numCols = colXs.length - 1\r\n if (numRows <= 0 || numCols <= 0) return []\r\n\r\n // 경계선 존재 여부를 행렬로 사전 계산\r\n // vBorders[r][c] = colXs[c]에 row r 구간의 수직선이 있는지\r\n const vBorders: boolean[][] = Array.from({ length: numRows },\r\n (_, r) => Array.from({ length: numCols + 1 },\r\n (_, c) => hasVerticalLine(verticals, colXs[c], rowYs[r], rowYs[r + 1], grid.vertexRadius)))\r\n\r\n // hBorders[r][c] = rowYs[r]에 col c 구간의 수평선이 있는지\r\n const hBorders: boolean[][] = Array.from({ length: numRows + 1 },\r\n (_, r) => Array.from({ length: numCols },\r\n (_, c) => hasHorizontalLine(horizontals, rowYs[r], colXs[c], colXs[c + 1], grid.vertexRadius)))\r\n\r\n // 셀이 이미 병합된 셀에 포함되는지 추적\r\n const occupied = Array.from({ length: numRows }, () => Array(numCols).fill(false))\r\n const cells: ExtractedCell[] = []\r\n\r\n for (let r = 0; r < numRows; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n if (occupied[r][c]) continue\r\n\r\n let colSpan = 1\r\n let rowSpan = 1\r\n\r\n // colSpan: 오른쪽 내부 경계에 수직선이 없으면 병합\r\n while (c + colSpan < numCols && !vBorders[r][c + colSpan]) {\r\n // 추가 검증: 확장하려는 영역의 모든 행에서 수직선이 없어야 함\r\n let canExpand = true\r\n for (let dr = 0; dr < rowSpan; dr++) {\r\n if (vBorders[r + dr][c + colSpan]) { canExpand = false; break }\r\n }\r\n if (!canExpand) break\r\n colSpan++\r\n }\r\n\r\n // rowSpan: 아래쪽 내부 경계에 수평선이 없으면 병합\r\n while (r + rowSpan < numRows) {\r\n let hasLine = false\r\n for (let dc = 0; dc < colSpan; dc++) {\r\n if (hBorders[r + rowSpan][c + dc]) { hasLine = true; break }\r\n }\r\n if (hasLine) break\r\n rowSpan++\r\n }\r\n\r\n // 병합 영역 마킹\r\n for (let dr = 0; dr < rowSpan; dr++) {\r\n for (let dc = 0; dc < colSpan; dc++) {\r\n occupied[r + dr][c + dc] = true\r\n }\r\n }\r\n\r\n cells.push({\r\n row: r, col: c, rowSpan, colSpan,\r\n bbox: {\r\n x1: colXs[c], y1: rowYs[r + rowSpan],\r\n x2: colXs[c + colSpan], y2: rowYs[r],\r\n },\r\n })\r\n }\r\n }\r\n\r\n return cells\r\n}\r\n\r\n/**\r\n * 특정 X 위치에 수직선이 Y 범위를 커버하는지 확인.\r\n * v2: 75% 커버 기준 + 동적 tolerance (vertex radius 기반)\r\n */\r\nfunction hasVerticalLine(\r\n verticals: LineSegment[], x: number, topY: number, botY: number, vertexRadius: number,\r\n): boolean {\r\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\r\n for (const v of verticals) {\r\n if (Math.abs(v.x1 - x) <= tol) {\r\n const cellH = Math.abs(topY - botY)\r\n if (cellH < 0.1) continue\r\n const overlapTop = Math.min(v.y2, topY)\r\n const overlapBot = Math.max(v.y1, botY)\r\n const overlap = overlapTop - overlapBot\r\n // 75% 커버 기준 (기존 50% → 병합 셀 내부 단선 오탐 방지)\r\n if (overlap >= cellH * 0.75) return true\r\n }\r\n }\r\n return false\r\n}\r\n\r\n/**\r\n * 특정 Y 위치에 수평선이 X 범위를 커버하는지 확인.\r\n * v2: 75% 커버 기준 + 동적 tolerance\r\n */\r\nfunction hasHorizontalLine(\r\n horizontals: LineSegment[], y: number, leftX: number, rightX: number, vertexRadius: number,\r\n): boolean {\r\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\r\n for (const h of horizontals) {\r\n if (Math.abs(h.y1 - y) <= tol) {\r\n const cellW = Math.abs(rightX - leftX)\r\n if (cellW < 0.1) continue\r\n const overlapLeft = Math.max(h.x1, leftX)\r\n const overlapRight = Math.min(h.x2, rightX)\r\n const overlap = overlapRight - overlapLeft\r\n if (overlap >= cellW * 0.75) return true\r\n }\r\n }\r\n return false\r\n}\r\n\r\n// ─── 텍스트→셀 매핑 ──────────────────────────────────\r\n\r\nexport interface TextItem {\r\n text: string\r\n x: number; y: number; w: number; h: number\r\n fontSize: number; fontName: string\r\n}\r\n\r\n/**\r\n * 텍스트 아이템을 셀에 매핑.\r\n * v2: ODL의 getIntersectionPercent 방식 — 텍스트 bbox와 셀 bbox의 교차 비율로 판별.\r\n * 중심점만 보는 기존 방식보다 정확 (긴 텍스트가 셀 경계를 걸치는 경우 처리).\r\n */\r\nexport function mapTextToCells(\r\n items: TextItem[],\r\n cells: ExtractedCell[],\r\n): Map<ExtractedCell, TextItem[]> {\r\n const result = new Map<ExtractedCell, TextItem[]>()\r\n for (const cell of cells) {\r\n result.set(cell, [])\r\n }\r\n\r\n for (const item of items) {\r\n const pad = CELL_PADDING\r\n\r\n let bestCell: ExtractedCell | null = null\r\n let bestScore = 0\r\n\r\n for (const cell of cells) {\r\n // 텍스트 bbox와 셀 bbox의 교차 영역 계산\r\n const ix1 = Math.max(item.x, cell.bbox.x1 - pad)\r\n const ix2 = Math.min(item.x + item.w, cell.bbox.x2 + pad)\r\n const iy1 = Math.max(item.y, cell.bbox.y1 - pad)\r\n const iy2 = Math.min(item.y + (item.h || item.fontSize), cell.bbox.y2 + pad)\r\n\r\n if (ix1 >= ix2 || iy1 >= iy2) continue\r\n\r\n const intersectArea = (ix2 - ix1) * (iy2 - iy1)\r\n const itemArea = Math.max(item.w, 1) * Math.max(item.h || item.fontSize, 1)\r\n const score = intersectArea / itemArea // ODL의 MIN_CELL_CONTENT_INTERSECTION_PERCENT\r\n\r\n if (score > bestScore) {\r\n bestScore = score\r\n bestCell = cell\r\n }\r\n }\r\n\r\n // 교차 비율 > 0.3이면 셀에 할당 (ODL은 0.6이지만 PDF 텍스트 좌표 오차 고려)\r\n if (bestCell && bestScore > 0.3) {\r\n result.get(bestCell)!.push(item)\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기.\r\n * Y 내림차순 (위→아래) → X 오름차순 (좌→우)\r\n */\r\nexport function cellTextToString(items: TextItem[]): string {\r\n if (items.length === 0) return \"\"\r\n if (items.length === 1) return items[0].text\r\n\r\n // Y좌표로 행 그룹핑 (tolerance: max(3, fontSize*0.6))\r\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\r\n const lines: TextItem[][] = []\r\n let curLine: TextItem[] = [sorted[0]]\r\n let curY = sorted[0].y\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const tol = Math.max(3, Math.min(sorted[i].fontSize, curLine[0].fontSize) * 0.6)\r\n if (Math.abs(sorted[i].y - curY) <= tol) {\r\n curLine.push(sorted[i])\r\n } else {\r\n lines.push(curLine)\r\n curLine = [sorted[i]]\r\n curY = sorted[i].y\r\n }\r\n }\r\n lines.push(curLine)\r\n\r\n // 각 행을 텍스트로 변환 — 좌표 기반 균등배분 감지 포함\r\n const textLines = lines.map(line => {\r\n const s = line.sort((a, b) => a.x - b.x)\r\n if (s.length === 1) return s[0].text\r\n\r\n // 균등배분 구간 감지 (좌표 기반)\r\n const evenSpaced = detectEvenSpacedItems(s)\r\n\r\n let result = s[0].text\r\n for (let j = 1; j < s.length; j++) {\r\n // 균등배분 구간이면 무조건 공백 없이 합침\r\n if (evenSpaced[j]) {\r\n result += s[j].text\r\n continue\r\n }\r\n\r\n const gap = s[j].x - (s[j - 1].x + s[j - 1].w)\r\n const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2\r\n const prevIsKorean = /[가-힣]$/.test(result)\r\n const currIsKorean = /^[가-힣]/.test(s[j].text)\r\n if (gap < avgFs * 0.15) {\r\n result += s[j].text\r\n } else if (gap < avgFs * 0.35 && (prevIsKorean || currIsKorean)) {\r\n result += s[j].text\r\n } else {\r\n result += \" \" + s[j].text\r\n }\r\n }\r\n return result\r\n })\r\n\r\n return mergeCellTextLines(textLines)\r\n}\r\n\r\n/**\r\n * 좌표 기반 균등배분 감지 — TextItem 배열에서 한글 1~2자 아이템이\r\n * 일정 간격으로 3개+ 연속되면 균등배분으로 판단.\r\n * ODL TextLineProcessor의 핵심 로직을 좌표 기반으로 구현.\r\n */\r\nfunction detectEvenSpacedItems(items: TextItem[]): boolean[] {\r\n const result = new Array(items.length).fill(false)\r\n if (items.length < 3) return result\r\n\r\n let runStart = -1\r\n for (let i = 0; i < items.length; i++) {\r\n // 균등배분 = 한글 1자 개별 배치. 2자 단어는 균등배분이 아니라 실제 단어.\r\n const isShortKorean = /^[가-힣]{1}$/.test(items[i].text) || /^[\\d]{1}$/.test(items[i].text)\r\n\r\n // 이전 아이템과의 갭이 fontSize*3+ 이면 run 끊기 (다른 영역)\r\n if (isShortKorean && runStart >= 0 && i > 0) {\r\n const gap = items[i].x - (items[i - 1].x + items[i - 1].w)\r\n const maxRunGap = Math.max(items[i].fontSize * 3, 30)\r\n if (gap > maxRunGap) {\r\n if (i - runStart >= 3) markEvenRun(items, result, runStart, i)\r\n runStart = i\r\n continue\r\n }\r\n }\r\n\r\n if (isShortKorean) {\r\n if (runStart < 0) runStart = i\r\n } else {\r\n if (runStart >= 0 && i - runStart >= 3) {\r\n markEvenRun(items, result, runStart, i)\r\n }\r\n runStart = -1\r\n }\r\n }\r\n if (runStart >= 0 && items.length - runStart >= 3) {\r\n markEvenRun(items, result, runStart, items.length)\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction markEvenRun(items: TextItem[], result: boolean[], start: number, end: number): void {\r\n const gaps: number[] = []\r\n for (let i = start + 1; i < end; i++) {\r\n gaps.push(items[i].x - (items[i - 1].x + items[i - 1].w))\r\n }\r\n const posGaps = gaps.filter(g => g > 0)\r\n if (posGaps.length < 2) return\r\n\r\n let minGap = Infinity, maxGap = -Infinity\r\n for (const g of posGaps) { if (g < minGap) minGap = g; if (g > maxGap) maxGap = g }\r\n const avgFs = items[start].fontSize\r\n\r\n // 간격이 fontSize의 0.1~3배 사이이고, 최대/최소 비율 3배 이내\r\n if (minGap >= avgFs * 0.1 && maxGap <= avgFs * 3 && maxGap / Math.max(minGap, 0.1) <= 3) {\r\n for (let i = start + 1; i < end; i++) {\r\n result[i] = true\r\n }\r\n }\r\n}\r\n\r\nexport { detectEvenSpacedItems }\r\n\r\n/**\r\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기 — 줄바꿈 병합 전용.\r\n * (cellTextToString 내부에서 사용)\r\n */\r\nfunction mergeCellTextLines(textLines: string[]): string {\r\n // 셀 내 줄바꿈 병합 — 잘린 단어/숫자 조각 복구\r\n if (textLines.length <= 1) return textLines[0] || \"\"\r\n const merged: string[] = [textLines[0]]\r\n for (let i = 1; i < textLines.length; i++) {\r\n const prev = merged[merged.length - 1]\r\n const curr = textLines[i]\r\n if (/[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(\" \")) {\r\n merged[merged.length - 1] = prev + curr\r\n }\r\n else if (curr.trim().length <= 3 && /^[)\\]%}]/.test(curr.trim())) {\r\n merged[merged.length - 1] = prev + curr.trim()\r\n }\r\n else if (/[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {\r\n merged[merged.length - 1] = prev + curr.trim()\r\n }\r\n else if (/[\\d,]$/.test(prev) && /^[\\d,]+[)\\]]?$/.test(curr.trim()) && curr.trim().length <= 10) {\r\n merged[merged.length - 1] = prev + curr.trim()\r\n }\r\n else {\r\n merged.push(curr)\r\n }\r\n }\r\n return merged.join(\"\\n\")\r\n}\r\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}\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 const rows = 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 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/** 연속 행에서 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 * 서버리스/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","/**\r\n * PDF 텍스트 추출 (pdfjs-dist static import 기반)\r\n *\r\n * polyfill을 먼저 import해야 DOMMatrix/Path2D/pdfjsWorker가 주입됨.\r\n * ES 모듈 호이스팅 때문에 별도 파일로 분리되어 있음.\r\n */\r\n\r\nimport type { ParseResult, InternalParseResult, IRBlock, IRTable, DocumentMetadata, ParseOptions, BoundingBox, ParseWarning, OutlineItem } from \"../types.js\"\r\nimport { HEADING_RATIO_H1, HEADING_RATIO_H2, HEADING_RATIO_H3 } from \"../types.js\"\r\nimport { KordocError, safeMin, safeMax } from \"../utils.js\"\r\nimport { parsePageRange } from \"../page-range.js\"\r\nimport { blocksToMarkdown } from \"../table/builder.js\"\r\nimport { extractLines, preprocessLines, filterPageBorderLines, buildTableGrids, extractCells, mapTextToCells, cellTextToString, detectEvenSpacedItems, type TextItem, type TableGrid, type ExtractedCell } from \"./line-detector.js\"\r\nimport { detectClusterTables, type ClusterItem } from \"./cluster-detector.js\"\r\n// polyfill 먼저 (ES 모듈 호이스팅되므로 별도 파일 필수)\r\nimport \"./polyfill.js\"\r\nimport { getDocument, OPS, GlobalWorkerOptions } from \"pdfjs-dist/legacy/build/pdf.mjs\"\r\n\r\n// worker 비활성화 (polyfill에서 pdfjsWorker를 이미 주입했으므로)\r\nGlobalWorkerOptions.workerSrc = \"\"\r\n\r\n// ─── 안전 한계값 (구조적 파싱과 무관) ────────────────\r\nconst MAX_PAGES = 5000\r\nconst MAX_TOTAL_TEXT = 100 * 1024 * 1024 // 100MB\r\n/** PDF 로딩 타임아웃 (30초) — 악성/대용량 PDF 무한 대기 방지 */\r\nconst PDF_LOAD_TIMEOUT_MS = 30_000\r\n\r\n/** getDocument + 타임아웃 래퍼 */\r\nasync function loadPdfWithTimeout(buffer: ArrayBuffer) {\r\n const loadingTask = getDocument({\r\n data: new Uint8Array(buffer),\r\n useSystemFonts: true,\r\n disableFontFace: true,\r\n isEvalSupported: false,\r\n })\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n try {\r\n return await Promise.race([\r\n loadingTask.promise,\r\n new Promise<never>((_, reject) => {\r\n timer = setTimeout(() => { loadingTask.destroy(); reject(new KordocError(\"PDF 로딩 타임아웃 (30초 초과)\")) }, PDF_LOAD_TIMEOUT_MS)\r\n }),\r\n ])\r\n } finally {\r\n if (timer !== undefined) clearTimeout(timer)\r\n }\r\n}\r\n\r\ninterface PdfTextItem {\r\n str: string\r\n transform: number[]\r\n width: number\r\n height: number\r\n fontName?: string\r\n}\r\n\r\ninterface NormItem {\r\n text: string\r\n x: number\r\n y: number\r\n w: number\r\n h: number\r\n /** 폰트 높이(≈폰트 크기) — 헤딩 감지용 */\r\n fontSize: number\r\n fontName: string\r\n /** hidden text 여부 (투명/0pt) */\r\n isHidden: boolean\r\n /** pdfjs 공백 아이템이 이 아이템 직전에 있었음 — 단어 경계 힌트 */\r\n hasSpaceBefore?: boolean\r\n}\r\n\r\nexport async function parsePdfDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<InternalParseResult> {\r\n const doc = await loadPdfWithTimeout(buffer)\r\n\r\n try {\r\n const pageCount = doc.numPages\r\n if (pageCount === 0) throw new KordocError(\"PDF에 페이지가 없습니다.\")\r\n\r\n // 메타데이터 추출 (best-effort)\r\n const metadata: DocumentMetadata = { pageCount }\r\n await extractPdfMetadata(doc, metadata)\r\n\r\n const blocks: IRBlock[] = []\r\n const warnings: ParseWarning[] = []\r\n let totalChars = 0\r\n let totalTextBytes = 0\r\n const effectivePageCount = Math.min(pageCount, MAX_PAGES)\r\n\r\n // 페이지 범위 필터링\r\n const pageFilter = options?.pages ? parsePageRange(options.pages, effectivePageCount) : null\r\n const totalTarget = pageFilter ? pageFilter.size : effectivePageCount\r\n\r\n // 전체 문서의 폰트 크기 빈도 수집 (헤딩 감지용) — 빈도 Map으로 메모리 절약\r\n const fontSizeFreq = new Map<number, number>()\r\n const pageHeights = new Map<number, number>()\r\n\r\n let parsedPages = 0\r\n for (let i = 1; i <= effectivePageCount; i++) {\r\n if (pageFilter && !pageFilter.has(i)) continue\r\n try {\r\n const page = await doc.getPage(i)\r\n const tc = await page.getTextContent()\r\n const viewport = page.getViewport({ scale: 1 })\r\n pageHeights.set(i, viewport.height)\r\n const rawItems = tc.items as PdfTextItem[]\r\n const items = normalizeItems(rawItems)\r\n\r\n // hidden text 필터링 + 경고 수집\r\n const { visible, hiddenCount } = filterHiddenText(items, viewport.width, viewport.height)\r\n if (hiddenCount > 0) {\r\n warnings.push({ page: i, message: `${hiddenCount}개 숨겨진 텍스트 요소 필터링됨`, code: \"HIDDEN_TEXT_FILTERED\" })\r\n }\r\n\r\n // 폰트 크기 빈도 수집\r\n for (const item of visible) {\r\n if (item.fontSize > 0) fontSizeFreq.set(item.fontSize, (fontSizeFreq.get(item.fontSize) || 0) + 1)\r\n }\r\n\r\n // 선 기반 테이블 감지를 위한 operatorList\r\n const opList = await page.getOperatorList()\r\n\r\n const pageBlocks = extractPageBlocksWithLines(visible, i, opList, viewport.width, viewport.height)\r\n for (const b of pageBlocks) blocks.push(b)\r\n\r\n // 이미지 기반 PDF 감지 + 크기 제한용 문자 수 집계\r\n for (const b of pageBlocks) {\r\n const t = b.text || \"\"\r\n totalChars += t.replace(/\\s/g, \"\").length\r\n totalTextBytes += t.length * 2\r\n }\r\n if (totalTextBytes > MAX_TOTAL_TEXT) throw new KordocError(\"텍스트 추출 크기 초과\")\r\n parsedPages++\r\n options?.onProgress?.(parsedPages, totalTarget)\r\n } catch (pageErr) {\r\n // 크기 초과는 전체 중단\r\n if (pageErr instanceof KordocError) throw pageErr\r\n warnings.push({ page: i, message: `페이지 ${i} 파싱 실패: ${pageErr instanceof Error ? pageErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\r\n }\r\n }\r\n\r\n const parsedPageCount = parsedPages || (pageFilter ? pageFilter.size : effectivePageCount)\r\n if (totalChars / Math.max(parsedPageCount, 1) < 10) {\r\n if (options?.ocr) {\r\n try {\r\n const { ocrPages } = await import(\"../ocr/provider.js\")\r\n const ocrBlocks = await ocrPages(doc, options.ocr, pageFilter, effectivePageCount)\r\n if (ocrBlocks.length > 0) {\r\n const ocrMarkdown = ocrBlocks.map(b => b.text || \"\").filter(Boolean).join(\"\\n\\n\")\r\n return { markdown: ocrMarkdown, blocks: ocrBlocks, metadata, warnings, isImageBased: true }\r\n }\r\n } catch {\r\n // OCR 실패 시 원래 에러 반환\r\n }\r\n }\r\n throw Object.assign(new KordocError(`이미지 기반 PDF (${pageCount}페이지, ${totalChars}자)`), { isImageBased: true })\r\n }\r\n\r\n // 머리글/바닥글 필터링 (기본 ON — 명시적 false일 때만 비활성화)\r\n if (options?.removeHeaderFooter !== false && parsedPageCount >= 3) {\r\n const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings)\r\n // 필터링된 블록 제거 (뒤에서부터 삭제)\r\n for (let ri = removed.length - 1; ri >= 0; ri--) {\r\n blocks.splice(removed[ri], 1)\r\n }\r\n }\r\n\r\n // 헤딩 감지: 폰트 크기 기반\r\n const medianFontSize = computeMedianFontSizeFromFreq(fontSizeFreq)\r\n if (medianFontSize > 0) {\r\n detectHeadings(blocks, medianFontSize)\r\n }\r\n\r\n // □/■ 마커 기반 서브헤딩 감지 (ODL 패턴)\r\n detectMarkerHeadings(blocks)\r\n\r\n // outline 구축\r\n const outline: OutlineItem[] = blocks\r\n .filter(b => b.type === \"heading\" && b.level && b.text)\r\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\r\n\r\n // blocksToMarkdown로 통일 — 헤딩 마크다운 반영 (HWP5/HWPX와 일관성)\r\n let markdown = cleanPdfText(blocksToMarkdown(blocks))\r\n\r\n return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined }\r\n } finally {\r\n await doc.destroy().catch(() => {})\r\n }\r\n}\r\n\r\n// ─── PDF 메타데이터 추출 ────────────────────────────\r\n\r\nasync function extractPdfMetadata(doc: { getMetadata(): Promise<unknown> }, metadata: DocumentMetadata): Promise<void> {\r\n try {\r\n const result = await doc.getMetadata() as { info?: Record<string, unknown> } | null\r\n if (!result?.info) return\r\n const info = result.info\r\n\r\n if (typeof info.Title === \"string\" && info.Title.trim()) metadata.title = info.Title.trim()\r\n if (typeof info.Author === \"string\" && info.Author.trim()) metadata.author = info.Author.trim()\r\n if (typeof info.Creator === \"string\" && info.Creator.trim()) metadata.creator = info.Creator.trim()\r\n if (typeof info.Subject === \"string\" && info.Subject.trim()) metadata.description = info.Subject.trim()\r\n if (typeof info.Keywords === \"string\" && info.Keywords.trim()) {\r\n metadata.keywords = info.Keywords.split(/[,;]/).map((k: string) => k.trim()).filter(Boolean)\r\n }\r\n if (typeof info.CreationDate === \"string\") metadata.createdAt = parsePdfDate(info.CreationDate)\r\n if (typeof info.ModDate === \"string\") metadata.modifiedAt = parsePdfDate(info.ModDate)\r\n } catch {\r\n // best-effort\r\n }\r\n}\r\n\r\n/** PDF 날짜 형식 (D:YYYYMMDDHHmmSS) → ISO 8601 변환 */\r\nfunction parsePdfDate(dateStr: string): string | undefined {\r\n const m = dateStr.match(/D:(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?/)\r\n if (!m) return undefined\r\n const [, year, month = \"01\", day = \"01\", hour = \"00\", min = \"00\", sec = \"00\"] = m\r\n return `${year}-${month}-${day}T${hour}:${min}:${sec}`\r\n}\r\n\r\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\r\nexport async function extractPdfMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\r\n const doc = await loadPdfWithTimeout(buffer)\r\n\r\n try {\r\n const metadata: DocumentMetadata = { pageCount: doc.numPages }\r\n await extractPdfMetadata(doc, metadata)\r\n return metadata\r\n } finally {\r\n await doc.destroy().catch(() => {})\r\n }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// Hidden text 필터링 (prompt injection 방어)\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction filterHiddenText(items: NormItem[], pageWidth: number, pageHeight: number): { visible: NormItem[]; hiddenCount: number } {\r\n let hiddenCount = 0\r\n const visible: NormItem[] = []\r\n\r\n for (const item of items) {\r\n // 0pt 폰트 / 너비 0 → 숨겨진 텍스트\r\n if (item.isHidden) { hiddenCount++; continue }\r\n // 페이지 범위 밖 (여백 10% 허용)\r\n const margin = Math.max(pageWidth, pageHeight) * 0.1\r\n if (item.x < -margin || item.x > pageWidth + margin || item.y < -margin || item.y > pageHeight + margin) {\r\n hiddenCount++; continue\r\n }\r\n visible.push(item)\r\n }\r\n\r\n return { visible, hiddenCount }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 헤딩 감지 (폰트 크기 기반)\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction computeMedianFontSizeFromFreq(freq: Map<number, number>): number {\r\n if (freq.size === 0) return 0\r\n let total = 0\r\n for (const count of freq.values()) total += count\r\n const sorted = [...freq.entries()].sort((a, b) => a[0] - b[0])\r\n const mid = Math.floor(total / 2)\r\n let cumulative = 0\r\n for (const [size, count] of sorted) {\r\n cumulative += count\r\n if (cumulative > mid) return size\r\n }\r\n return sorted[sorted.length - 1][0]\r\n}\r\n\r\n/**\r\n * 블록의 폰트 크기를 median과 비교하여 헤딩으로 승격.\r\n * - 150%+ → heading level 1\r\n * - 130%+ → heading level 2\r\n * - 115%+ → heading level 3\r\n * 조건: 짧은 텍스트 (200자 미만), 숫자만으로 구성되지 않음\r\n */\r\nfunction detectHeadings(blocks: IRBlock[], medianFontSize: number): void {\r\n for (const block of blocks) {\r\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontSize) continue\r\n const text = block.text.trim()\r\n if (text.length === 0 || text.length > 200) continue\r\n // 숫자만이면 헤딩 아님\r\n if (/^\\d+$/.test(text)) continue\r\n\r\n const ratio = block.style.fontSize / medianFontSize\r\n let level = 0\r\n if (ratio >= HEADING_RATIO_H1) level = 1\r\n else if (ratio >= HEADING_RATIO_H2) level = 2\r\n else if (ratio >= HEADING_RATIO_H3) level = 3\r\n\r\n if (level > 0) {\r\n block.type = \"heading\"\r\n block.level = level\r\n // PDF 균등배분 스페이스 제거 (\"기 본 현 황\" → \"기본현황\")\r\n // 한글 글자 사이에 단독 공백이 반복되면 균등배분으로 판단\r\n block.text = collapseEvenSpacing(text)\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 문자열 기반 균등배분 제거.\r\n * normalizeItems에서 분해 + 좌표 기반 감지가 주 경로이고, 여기는 안전망.\r\n * pdfjs가 이미 합친 \"홍 보 담 당 관\" 같은 TextItem 문자열에 적용.\r\n */\r\nfunction collapseEvenSpacing(text: string): string {\r\n // 1. 전체가 균등배분: 토큰의 70%가 1글자\r\n const tokens = text.split(\" \")\r\n const singleCharCount = tokens.filter(t => t.length === 1).length\r\n if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7) {\r\n return tokens.join(\"\")\r\n }\r\n\r\n // 2. 부분 균등배분: 한글 1자가 3개+ 연속 (2자 단어는 건드리지 않음)\r\n // \"홍 보 담 당 관\" → \"홍보담당관\", \"지 역 경 제 과\" → \"지역경제과\"\r\n // \"중동 사태 대응\" (2자 단어)는 매칭 안 됨 → 공백 유지\r\n return text.replace(\r\n /(?<![가-힣])[가-힣](?: [가-힣\\d]){2,}(?![가-힣])/g,\r\n match => match.replace(/ /g, \"\"),\r\n )\r\n}\r\n\r\n/**\r\n * 의사 테이블 감지: 실제 데이터 테이블이 아닌 텍스트가 우연히 테이블로 감지된 경우.\r\n */\r\nfunction shouldDemoteTable(table: IRTable): boolean {\r\n const allCells = table.cells.flatMap(row => row.map(c => c.text.trim())).filter(Boolean)\r\n const allText = allCells.join(\" \")\r\n\r\n // 텍스트 박스 패턴: 3행 이하 + 3열 이하 + <...> 또는 ㅇ 마커 포함\r\n // 공문서 \"중점 추진사항\" 등 요약 박스\r\n if (table.rows <= 3 && table.cols <= 3) {\r\n // 빈 셀이 과반 → 텍스트 박스 (테두리 안에 텍스트만 있는 형태)\r\n const totalCells = table.rows * table.cols\r\n const emptyCells = totalCells - allCells.length\r\n if (emptyCells >= totalCells * 0.3) return true\r\n\r\n // 마커 패턴 (ㅇ, □, ○, <> 등) → 텍스트성\r\n if (/[□■◆○●▶ㅇ]/.test(allText)) return true\r\n if (/<[^>]+>/.test(allText)) return true\r\n }\r\n\r\n if (allText.length > 200) return false\r\n // □, ○, ■ 마커 포함 + 3행 이하 → 텍스트성\r\n if (/[□■◆○●▶]/.test(allText) && table.rows <= 3) return true\r\n // 빈 셀이 과반 → 의사 테이블\r\n const totalCells = table.rows * table.cols\r\n const emptyCells = totalCells - allCells.length\r\n if (table.rows <= 2 && emptyCells > totalCells * 0.5) return true\r\n // 1행 + 숫자 데이터 없음 → 의사 테이블\r\n if (table.rows === 1 && !/\\d{2,}/.test(allText)) return true\r\n return false\r\n}\r\n\r\n/** demote된 테이블을 구조화된 텍스트로 변환 */\r\nfunction demoteTableToText(table: IRTable): string {\r\n const lines: string[] = []\r\n for (let r = 0; r < table.rows; r++) {\r\n const cells = table.cells[r].map(c => c.text.trim()).filter(Boolean)\r\n if (cells.length === 0) continue\r\n if (table.cols === 2 && cells.length === 2) {\r\n lines.push(`${cells[0]} : ${cells[1]}`)\r\n } else {\r\n // 각 셀 텍스트를 공백으로 합침 (br 태그는 줄바꿈으로 유지)\r\n lines.push(cells.join(\" \"))\r\n }\r\n }\r\n return lines.join(\"\\n\")\r\n}\r\n\r\n/** □/■ 마커 및 짧은 섹션명을 서브헤딩으로 변환 */\r\nfunction detectMarkerHeadings(blocks: IRBlock[]): void {\r\n for (let i = 0; i < blocks.length; i++) {\r\n const block = blocks[i]\r\n if (block.type !== \"paragraph\" || !block.text) continue\r\n const text = block.text.trim()\r\n // □/■ + 한글로 시작하는 짧은 텍스트 (50자 미만)\r\n if (text.length < 50 && /^[□■◆◇▶]\\s*[가-힣]/.test(text)) {\r\n block.type = \"heading\"\r\n block.level = 4\r\n continue\r\n }\r\n // 순수 한글 2-6자 + 앞뒤가 표/헤딩/빈블록 → 섹션 제목으로 추정\r\n // (예: \"사업설명\", \"사업효과\", \"추진경위\")\r\n if (/^[가-힣]{2,6}$/.test(text) && block.style?.fontSize) {\r\n const prev = blocks[i - 1]\r\n const next = blocks[i + 1]\r\n const prevIsStructural = !prev || prev.type === \"table\" || prev.type === \"heading\" || prev.type === \"separator\"\r\n const nextIsStructural = !next || next.type === \"table\" || next.type === \"heading\" || (next.type === \"paragraph\" && next.text && /^[□■◆○●]/.test(next.text.trim()))\r\n if (prevIsStructural || nextIsStructural) {\r\n block.type = \"heading\"\r\n block.level = 3\r\n }\r\n }\r\n }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// XY-Cut 읽기 순서 알고리즘\r\n// ═══════════════════════════════════════════════════════\r\n\r\ninterface TextRegion {\r\n items: NormItem[]\r\n minX: number; minY: number; maxX: number; maxY: number\r\n}\r\n\r\n/**\r\n * XY-Cut: 페이지를 재귀적으로 X/Y축 공백으로 분할하여 읽기 순서 결정.\r\n * - Y축 분할 (수평 공백 감지) → 위에서 아래\r\n * - X축 분할 (수직 공백 감지) → 왼쪽에서 오른쪽\r\n * - 분할 불가능하면 리프 노드 (하나의 텍스트 블록)\r\n */\r\n/** 재귀 깊이 제한 — 수천 아이템의 pathological 레이아웃에서 스택 오버플로 방지 */\r\nconst MAX_XYCUT_DEPTH = 50\r\n\r\nfunction xyCutOrder(items: NormItem[], gapThreshold: number, depth = 0): NormItem[][] {\r\n if (items.length === 0) return []\r\n if (items.length <= 2 || depth >= MAX_XYCUT_DEPTH) return [items]\r\n\r\n const region = computeRegion(items)\r\n\r\n // Y축 분할 시도 (수평 공백 감지)\r\n const ySplit = findYSplit(items, region, gapThreshold)\r\n if (ySplit !== null) {\r\n const upper = items.filter(i => i.y > ySplit)\r\n const lower = items.filter(i => i.y <= ySplit)\r\n // 빈 파티션 방어 — 한쪽이 비면 분할 의미 없음\r\n if (upper.length > 0 && lower.length > 0 && upper.length < items.length) {\r\n return [...xyCutOrder(upper, gapThreshold, depth + 1), ...xyCutOrder(lower, gapThreshold, depth + 1)]\r\n }\r\n }\r\n\r\n // X축 분할 시도 (수직 공백 감지)\r\n const xSplit = findXSplit(items, region, gapThreshold)\r\n if (xSplit !== null) {\r\n const left = items.filter(i => i.x + i.w / 2 < xSplit)\r\n const right = items.filter(i => i.x + i.w / 2 >= xSplit)\r\n if (left.length > 0 && right.length > 0 && left.length < items.length) {\r\n return [...xyCutOrder(left, gapThreshold, depth + 1), ...xyCutOrder(right, gapThreshold, depth + 1)]\r\n }\r\n }\r\n\r\n // 분할 불가 → 리프 노드\r\n return [items]\r\n}\r\n\r\nfunction computeRegion(items: NormItem[]): TextRegion {\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\r\n for (const i of items) {\r\n if (i.x < minX) minX = i.x\r\n if (i.y < minY) minY = i.y\r\n if (i.x + i.w > maxX) maxX = i.x + i.w\r\n if (i.y + i.h > maxY) maxY = i.y + i.h\r\n }\r\n return { items, minX, minY, maxX, maxY }\r\n}\r\n\r\n/** Y축 분할점 찾기 — 수평 공백 밴드 중 가장 넓은 갭 */\r\nfunction findYSplit(items: NormItem[], _region: TextRegion, gapThreshold: number): number | null {\r\n // 아이템을 Y좌표로 정렬 (내림차순 — 위에서 아래)\r\n const sorted = [...items].sort((a, b) => b.y - a.y)\r\n let bestGap = gapThreshold\r\n let bestSplit: number | null = null\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prevBottom = sorted[i - 1].y - sorted[i - 1].h\r\n const currTop = sorted[i].y\r\n const gap = prevBottom - currTop\r\n if (gap > bestGap) {\r\n bestGap = gap\r\n bestSplit = (prevBottom + currTop) / 2\r\n }\r\n }\r\n return bestSplit\r\n}\r\n\r\n/** X축 분할점 찾기 — 수직 공백 밴드 중 가장 넓은 갭 */\r\nfunction findXSplit(items: NormItem[], _region: TextRegion, gapThreshold: number): number | null {\r\n const sorted = [...items].sort((a, b) => a.x - b.x)\r\n let bestGap = gapThreshold\r\n let bestSplit: number | null = null\r\n\r\n for (let i = 1; i < sorted.length; i++) {\r\n const prevRight = sorted[i - 1].x + sorted[i - 1].w\r\n const currLeft = sorted[i].x\r\n const gap = currLeft - prevRight\r\n if (gap > bestGap) {\r\n bestGap = gap\r\n bestSplit = (prevRight + currLeft) / 2\r\n }\r\n }\r\n return bestSplit\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 페이지 콘텐츠 추출 → IRBlock[] (v2: 바운딩 박스 + 페이지 번호)\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/**\r\n * 선 기반 테이블 감지를 우선 시도, 실패 시 기존 휴리스틱 fallback.\r\n */\r\nfunction extractPageBlocksWithLines(\r\n items: NormItem[],\r\n pageNum: number,\r\n opList: { fnArray: Uint32Array | number[]; argsArray: unknown[][] },\r\n pageWidth: number,\r\n pageHeight: number,\r\n): IRBlock[] {\r\n if (items.length === 0) return []\r\n\r\n // 1단계: PDF 그래픽 명령에서 선 추출\r\n let { horizontals, verticals } = extractLines(opList.fnArray, opList.argsArray)\r\n ;({ horizontals, verticals } = filterPageBorderLines(horizontals, verticals, pageWidth, pageHeight))\r\n\r\n // 1.5단계: 선 전처리 (ODL LinesPreprocessingConsumer 포팅)\r\n // 굵은 선 필터 + 근접 평행 선 병합\r\n ;({ horizontals, verticals } = preprocessLines(horizontals, verticals))\r\n\r\n // 2단계: 선으로 테이블 그리드 구성\r\n const grids = buildTableGrids(horizontals, verticals)\r\n\r\n if (grids.length > 0) {\r\n return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals)\r\n }\r\n\r\n // Fallback: 기존 휴리스틱 (선이 없는 PDF)\r\n return extractPageBlocksFallback(items, pageNum)\r\n}\r\n\r\n/**\r\n * 선 기반 그리드가 감지된 경우: 테이블 영역의 텍스트는 셀에 매핑,\r\n * 나머지는 일반 텍스트 블록으로 처리.\r\n */\r\nfunction extractBlocksWithGrids(\r\n items: NormItem[],\r\n pageNum: number,\r\n grids: TableGrid[],\r\n horizontals: import(\"./line-detector.js\").LineSegment[],\r\n verticals: import(\"./line-detector.js\").LineSegment[],\r\n): IRBlock[] {\r\n const blocks: IRBlock[] = []\r\n const usedItems = new Set<NormItem>()\r\n\r\n // 그리드를 Y좌표 내림차순 정렬 (위→아래)\r\n const sortedGrids = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\r\n\r\n for (const grid of sortedGrids) {\r\n // 1행 다열 그리드는 테이블 헤더일 가능성 높음 → 스킵하여 클러스터 감지에 위임\r\n const numGridRows = grid.rowYs.length - 1\r\n const numGridCols = grid.colXs.length - 1\r\n if (numGridRows === 1 && numGridCols >= 2) continue\r\n\r\n // 그리드 영역 내 텍스트 아이템 수집\r\n const tableItems: NormItem[] = []\r\n const pad = 3\r\n const gridW = grid.bbox.x2 - grid.bbox.x1\r\n for (const item of items) {\r\n if (usedItems.has(item)) continue\r\n // Y 범위 체크\r\n if (item.y < grid.bbox.y1 - pad || item.y > grid.bbox.y2 + pad) continue\r\n // X 범위 체크 — 아이템의 시작과 끝이 모두 그리드 안에 있어야 함\r\n if (item.x < grid.bbox.x1 - pad || item.x + item.w > grid.bbox.x2 + pad) continue\r\n // 좁은 그리드(120px 미만)에서 큰 아이템이 경계에 걸치면 제외\r\n // 제목 텍스트가 인접 그리드에 잡히는 것을 방지\r\n if (gridW < 120 && item.x + item.w > grid.bbox.x2 - 2) continue\r\n tableItems.push(item)\r\n usedItems.add(item)\r\n }\r\n\r\n // 셀 추출\r\n const cells = extractCells(grid, horizontals, verticals)\r\n if (cells.length === 0) continue\r\n\r\n // 텍스트→셀 매핑\r\n const textItems: TextItem[] = tableItems.map(i => ({\r\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\r\n fontSize: i.fontSize, fontName: i.fontName,\r\n }))\r\n const cellTextMap = mapTextToCells(textItems, cells)\r\n\r\n // IRTable 구성\r\n const numRows = grid.rowYs.length - 1\r\n const numCols = grid.colXs.length - 1\r\n const irGrid: import(\"../types.js\").IRCell[][] = Array.from(\r\n { length: numRows },\r\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\r\n )\r\n\r\n for (const cell of cells) {\r\n const cellItems = cellTextMap.get(cell) || []\r\n let text = cellTextToString(cellItems)\r\n // 셀 안의 페이지 번호 표시 제거 (\"- 2 -\" 등)\r\n text = text.replace(/^[\\s]*[-–—]\\s*\\d+\\s*[-–—][\\s]*$/gm, \"\").trim()\r\n // 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\r\n text = text.split(\"\\n\").map(line => collapseEvenSpacing(line)).join(\"\\n\")\r\n irGrid[cell.row][cell.col] = {\r\n text,\r\n colSpan: cell.colSpan,\r\n rowSpan: cell.rowSpan,\r\n }\r\n }\r\n\r\n const irTable: IRTable = {\r\n rows: numRows,\r\n cols: numCols,\r\n cells: irGrid,\r\n hasHeader: numRows > 1,\r\n }\r\n\r\n // 빈 테이블(모든 셀이 빈 문자열) 스킵\r\n const hasContent = irGrid.some(row => row.some(cell => cell.text.trim() !== \"\"))\r\n if (!hasContent) continue\r\n\r\n const tableBbox: BoundingBox = {\r\n page: pageNum,\r\n x: grid.bbox.x1, y: grid.bbox.y1,\r\n width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1,\r\n }\r\n\r\n // 의사 테이블 필터: 텍스트성 내용 → paragraph로 복원 (구조 보존)\r\n if (shouldDemoteTable(irTable)) {\r\n const demoted = demoteTableToText(irTable)\r\n if (demoted) {\r\n // 텍스트 박스(1x1 또는 1행 그리드) demote 시 앞뒤 줄바꿈으로 본문과 분리\r\n const text = numGridRows === 1 ? \"\\n\" + demoted + \"\\n\" : demoted\r\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox: tableBbox, style: dominantStyle(tableItems) })\r\n }\r\n continue\r\n }\r\n\r\n blocks.push({ type: \"table\", table: irTable, pageNumber: pageNum, bbox: tableBbox })\r\n }\r\n\r\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\r\n let remaining = items.filter(i => !usedItems.has(i))\r\n if (remaining.length > 0) {\r\n remaining.sort((a, b) => b.y - a.y || a.x - b.x)\r\n\r\n // 클러스터 기반 테이블 감지 (XY-Cut 전에 실행 — 테이블이 쪼개지지 않도록)\r\n const clusterItems: ClusterItem[] = remaining.map(i => ({\r\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\r\n fontSize: i.fontSize, fontName: i.fontName,\r\n }))\r\n const clusterResults = detectClusterTables(clusterItems, pageNum)\r\n if (clusterResults.length > 0) {\r\n const ciToIdx = new Map<ClusterItem, number>()\r\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\r\n const usedClusterIndices = new Set<number>()\r\n for (const cr of clusterResults) {\r\n for (const ci of cr.usedItems) {\r\n const idx = ciToIdx.get(ci)\r\n if (idx !== undefined) usedClusterIndices.add(idx)\r\n }\r\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\r\n }\r\n remaining = remaining.filter((_, idx) => !usedClusterIndices.has(idx))\r\n }\r\n\r\n // XY-Cut으로 왼쪽 본문과 오른쪽 부서명 등을 분리 후 개별 처리\r\n if (remaining.length > 0) {\r\n const allY = remaining.map(i => i.y)\r\n const pageH = safeMax(allY) - safeMin(allY)\r\n const groups = xyCutOrder(remaining, Math.max(15, pageH * 0.03))\r\n const textBlocks: IRBlock[] = []\r\n for (const group of groups) {\r\n if (group.length === 0) continue\r\n const groupBlocks = extractPageBlocksFallback(group, pageNum)\r\n for (const b of groupBlocks) textBlocks.push(b)\r\n }\r\n const finalTextBlocks = detectListBlocks(textBlocks)\r\n for (const b of finalTextBlocks) blocks.push(b)\r\n }\r\n\r\n // Y좌표 기반 정렬\r\n blocks.sort((a, b) => {\r\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\r\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\r\n return by - ay // PDF는 y가 위가 큼 → 내림차순\r\n })\r\n return mergeAdjacentTableBlocks(blocks)\r\n }\r\n\r\n return mergeAdjacentTableBlocks(blocks)\r\n}\r\n\r\n/** 같은 열 수의 연속 테이블 블록을 하나로 합침 */\r\nfunction mergeAdjacentTableBlocks(blocks: IRBlock[]): IRBlock[] {\r\n if (blocks.length <= 1) return blocks\r\n const result: IRBlock[] = [blocks[0]]\r\n for (let i = 1; i < blocks.length; i++) {\r\n const prev = result[result.length - 1]\r\n const curr = blocks[i]\r\n if (prev.type === \"table\" && curr.type === \"table\" && prev.table && curr.table &&\r\n prev.table.cols === curr.table.cols) {\r\n // 합치기: prev의 cells에 curr의 cells 추가\r\n const merged: IRTable = {\r\n rows: prev.table.rows + curr.table.rows,\r\n cols: prev.table.cols,\r\n cells: [...prev.table.cells, ...curr.table.cells],\r\n hasHeader: prev.table.hasHeader,\r\n }\r\n result[result.length - 1] = { ...prev, table: merged }\r\n } else {\r\n result.push(curr)\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 기존 휴리스틱 기반 페이지 블록 추출 (선이 없는 PDF 대비 fallback).\r\n */\r\nfunction extractPageBlocksFallback(items: NormItem[], pageNum: number): IRBlock[] {\r\n if (items.length === 0) return []\r\n\r\n const blocks: IRBlock[] = []\r\n\r\n // 1단계: 클러스터 기반 테이블 감지 우선 (헤더 감지 시 정확도 높음)\r\n const clusterItems: ClusterItem[] = items.map(i => ({\r\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\r\n fontSize: i.fontSize, fontName: i.fontName,\r\n }))\r\n const clusterResults = detectClusterTables(clusterItems, pageNum)\r\n\r\n if (clusterResults.length > 0) {\r\n const ciToIdx = new Map<ClusterItem, number>()\r\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\r\n const usedIndices = new Set<number>()\r\n for (const cr of clusterResults) {\r\n for (const ci of cr.usedItems) {\r\n const idx = ciToIdx.get(ci)\r\n if (idx !== undefined) usedIndices.add(idx)\r\n }\r\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\r\n }\r\n\r\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\r\n const remaining = items.filter((_, idx) => !usedIndices.has(idx))\r\n if (remaining.length > 0) {\r\n const yLines = groupByY(remaining)\r\n for (const line of yLines) {\r\n const text = mergeLineSimple(line)\r\n if (!text.trim()) continue\r\n const bbox = computeBBox(line, pageNum)\r\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\r\n }\r\n }\r\n\r\n blocks.sort((a, b) => {\r\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\r\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\r\n return by - ay\r\n })\r\n } else {\r\n // 2단계: 레거시 컬럼 감지 (3+ 열)\r\n const allYLines = groupByY(items)\r\n const columns = detectColumns(allYLines)\r\n\r\n if (columns && columns.length >= 3) {\r\n const tableText = extractWithColumns(allYLines, columns)\r\n const bbox = computeBBox(items, pageNum)\r\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(items) })\r\n } else {\r\n // 3단계: XY-Cut으로 읽기 순서 결정\r\n const allY = items.map(i => i.y)\r\n const pageHeight = safeMax(allY) - safeMin(allY)\r\n const gapThreshold = Math.max(15, pageHeight * 0.03)\r\n\r\n const orderedGroups = xyCutOrder(items, gapThreshold)\r\n\r\n for (const group of orderedGroups) {\r\n if (group.length === 0) continue\r\n const yLines = groupByY(group)\r\n\r\n const groupColumns = detectColumns(yLines)\r\n if (groupColumns && groupColumns.length >= 3) {\r\n const tableText = extractWithColumns(yLines, groupColumns)\r\n const bbox = computeBBox(group, pageNum)\r\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(group) })\r\n } else {\r\n for (const line of yLines) {\r\n const text = mergeLineSimple(line)\r\n if (!text.trim()) continue\r\n const bbox = computeBBox(line, pageNum)\r\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n // 한국어 특수 테이블 감지 (구분/항목/종류 패턴)\r\n return detectSpecialKoreanTables(blocks)\r\n}\r\n\r\n/** 아이템 그룹에서 바운딩 박스 계산 */\r\nfunction computeBBox(items: NormItem[], pageNum: number): BoundingBox {\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\r\n for (const i of items) {\r\n if (i.x < minX) minX = i.x\r\n if (i.y < minY) minY = i.y\r\n if (i.x + i.w > maxX) maxX = i.x + i.w\r\n // h가 0인 경우 fontSize를 높이 대용으로 사용 (pdfjs가 height를 제공하지 않는 경우)\r\n const effectiveH = i.h > 0 ? i.h : i.fontSize\r\n if (i.y + effectiveH > maxY) maxY = i.y + effectiveH\r\n }\r\n return { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }\r\n}\r\n\r\n/** 아이템 그룹의 대표 스타일 (최빈 폰트 크기) */\r\nfunction dominantStyle(items: NormItem[]): { fontSize: number; fontName: string } | undefined {\r\n if (items.length === 0) return undefined\r\n // 최빈 폰트 크기 찾기\r\n const freq = new Map<number, number>()\r\n let maxCount = 0, dominantSize = 0\r\n for (const i of items) {\r\n if (i.fontSize <= 0) continue\r\n const count = (freq.get(i.fontSize) || 0) + 1\r\n freq.set(i.fontSize, count)\r\n if (count > maxCount) { maxCount = count; dominantSize = i.fontSize }\r\n }\r\n if (dominantSize === 0) return undefined\r\n // 대표 폰트명 (빈 문자열은 undefined로)\r\n const fontName = items.find(i => i.fontSize === dominantSize)?.fontName || undefined\r\n return { fontSize: dominantSize, fontName }\r\n}\r\n\r\nfunction normalizeItems(rawItems: PdfTextItem[]): NormItem[] {\r\n const items: NormItem[] = []\r\n // pdfjs 공백 아이템 위치 수집 — 단어 경계 힌트로 활용\r\n const spacePositions: { x: number; y: number }[] = []\r\n\r\n for (const i of rawItems) {\r\n if (typeof i.str !== \"string\") continue\r\n const x = Math.round(i.transform[4])\r\n const y = Math.round(i.transform[5])\r\n\r\n if (!i.str.trim()) {\r\n // 공백 전용 아이템: 위치만 기록 (단어 구분 힌트)\r\n spacePositions.push({ x, y })\r\n continue\r\n }\r\n\r\n const scaleY = Math.abs(i.transform[3])\r\n const scaleX = Math.abs(i.transform[0])\r\n const fontSize = Math.round(Math.max(scaleY, scaleX))\r\n const w = Math.round(i.width)\r\n const h = Math.round(i.height)\r\n const isHidden = fontSize === 0 || (i.width === 0 && i.str.trim().length > 0)\r\n\r\n // letterSpacing이 적용된 숫자/기호 문자열 정규화\r\n // \"45 0 -7 3 40 )\" → \"450-7340)\" (전화번호, 금액 등)\r\n let text = i.str.trim()\r\n if (/^[\\d\\s\\-().·,☎]+$/.test(text) && /\\d/.test(text) && / /.test(text)) {\r\n text = text.replace(/ /g, \"\")\r\n }\r\n\r\n // 균등배분 TextItem 분해: \"홍 보 지 원 반\" → 개별 글자 아이템으로\r\n const split = splitEvenSpacedItem(text, x, w, fontSize)\r\n if (split) {\r\n for (const s of split) {\r\n items.push({ text: s.text, x: s.x, y, w: s.w, h, fontSize, fontName: i.fontName || \"\", isHidden })\r\n }\r\n } else {\r\n items.push({ text, x, y, w, h, fontSize, fontName: i.fontName || \"\", isHidden })\r\n }\r\n }\r\n\r\n const sorted = items.sort((a, b) => b.y - a.y || a.x - b.x)\r\n\r\n // 1. 가짜 볼드 중복 제거: 같은 텍스트가 거의 동일한 좌표(±3px)에 2~3회 겹쳐진 경우\r\n // PDF에서 볼드 효과를 위해 텍스트를 여러 번 렌더링하는 기법\r\n const deduped: NormItem[] = []\r\n for (let i = 0; i < sorted.length; i++) {\r\n let isDup = false\r\n // Y 정렬(desc)이므로 역순 스캔 — Y 차이가 tolerance를 넘으면 중단\r\n for (let j = deduped.length - 1; j >= 0; j--) {\r\n const prev = deduped[j]\r\n if (prev.y - sorted[i].y > 3) break // 이전 아이템이 너무 높음 → 중단\r\n if (Math.abs(prev.y - sorted[i].y) <= 3 &&\r\n prev.text === sorted[i].text && Math.abs(prev.x - sorted[i].x) <= 3) {\r\n isDup = true\r\n break\r\n }\r\n }\r\n if (!isDup) deduped.push(sorted[i])\r\n }\r\n\r\n // 2. 공백 아이템 위치를 NormItem.hasSpaceBefore로 전파\r\n // 같은 Y라인(±3px)에서 공백 아이템의 x가 현재 아이템 직전(왼쪽)에 있으면 표시\r\n if (spacePositions.length > 0) {\r\n for (const item of deduped) {\r\n for (const sp of spacePositions) {\r\n // 같은 Y라인(±3px) + 공백이 아이템 왼쪽에 인접 (0 ~ 20px 이내)\r\n if (Math.abs(sp.y - item.y) <= 3) {\r\n const dist = item.x - sp.x\r\n if (dist >= 0 && dist <= 20) {\r\n item.hasSpaceBefore = true\r\n break\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return deduped\r\n}\r\n\r\n/**\r\n * 균등배분 TextItem 감지 및 분해.\r\n * \"홍 보 지 원 반\" (1자+공백 패턴) → [{text:\"홍\",x,w}, {text:\"보\",x,w}, ...]\r\n * 분해하면 이후 detectEvenSpacedItems가 좌표 기반으로 정확히 감지할 수 있음.\r\n */\r\nfunction splitEvenSpacedItem(\r\n text: string, itemX: number, itemW: number, fontSize: number,\r\n): { text: string; x: number; w: number }[] | null {\r\n // 한글/숫자 1자 + 공백이 3회+ 반복되는 패턴\r\n // \"홍 보 지 원 반\", \"세 무 1 과\", \"주 요 내 용\"\r\n if (!/^[가-힣\\d](?: [가-힣\\d]){2,}$/.test(text)) return null\r\n\r\n const chars = text.split(\" \")\r\n if (chars.length < 3) return null\r\n\r\n // 글자당 폭 계산 — 전체 width를 글자 수로 나눔\r\n const charW = itemW / chars.length\r\n // 글자 폭이 너무 크면 균등배분이 아님 (한 글자가 fontSize의 2배 넘으면 이상)\r\n if (charW > fontSize * 2) return null\r\n\r\n return chars.map((ch, idx) => ({\r\n text: ch,\r\n x: Math.round(itemX + idx * charW),\r\n w: Math.round(charW * 0.8), // 실제 글자 폭은 간격보다 좁음\r\n }))\r\n}\r\n\r\nfunction groupByY(items: NormItem[]): NormItem[][] {\r\n if (items.length === 0) return []\r\n const lines: NormItem[][] = []\r\n let curY = items[0].y\r\n let curLine: NormItem[] = [items[0]]\r\n\r\n for (let i = 1; i < items.length; i++) {\r\n // Y좌표 허용 오차 3px — PDF 렌더링 미세 오차 보정, 별표 행 경계 감지에 최적화된 값\r\n if (Math.abs(items[i].y - curY) > 3) {\r\n lines.push(curLine)\r\n curLine = []\r\n curY = items[i].y\r\n }\r\n curLine.push(items[i])\r\n }\r\n if (curLine.length > 0) lines.push(curLine)\r\n return lines\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 열 경계 감지 — 빈도 기반 x-히스토그램 클러스터링\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/** prose 라인 판별: 아이템 간 gap이 모두 작으면 문장 (단어 나열) */\r\nfunction isProseSpread(items: NormItem[]): boolean {\r\n if (items.length < 4) return false\r\n const sorted = [...items].sort((a, b) => a.x - b.x)\r\n const gaps: number[] = []\r\n for (let i = 1; i < sorted.length; i++) {\r\n gaps.push(sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w))\r\n }\r\n // gap의 최대값이 작고 평균 단어 길이가 짧으면 prose\r\n const maxGap = safeMax(gaps)\r\n const avgLen = items.reduce((s, i) => s + i.text.length, 0) / items.length\r\n // 짧은 단어들이 좁은 간격으로 나열 = prose (예: \"위 표 제3호나목에서 남은 유효기간...\")\r\n return maxGap < 40 && avgLen < 5\r\n}\r\n\r\nfunction detectColumns(yLines: NormItem[][]): number[] | null {\r\n const allItems = yLines.flat()\r\n if (allItems.length === 0) return null\r\n const pageWidth = safeMax(allItems.map(i => i.x + i.w)) - safeMin(allItems.map(i => i.x))\r\n if (pageWidth < 100) return null\r\n\r\n // \"비고\" 이전 아이템만 사용 (비고 이후는 prose)\r\n let bigoLineIdx = -1\r\n for (let i = 0; i < yLines.length; i++) {\r\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\r\n bigoLineIdx = i\r\n break\r\n }\r\n }\r\n const tableYLines = bigoLineIdx >= 0 ? yLines.slice(0, bigoLineIdx) : yLines\r\n\r\n // Step 1: 모든 아이템의 x를 수집 (prose 라인 제외)\r\n // CLUSTER_TOL 22px — 한국 공문서 PDF 열 간격에 최적화, 별표 표 열 감지 핵심값\r\n const CLUSTER_TOL = 22\r\n const xClusters: { center: number; count: number; minX: number }[] = []\r\n\r\n for (const line of tableYLines) {\r\n if (isProseSpread(line)) continue\r\n for (const item of line) {\r\n let found = false\r\n for (const c of xClusters) {\r\n if (Math.abs(item.x - c.center) <= CLUSTER_TOL) {\r\n c.center = Math.round((c.center * c.count + item.x) / (c.count + 1))\r\n c.minX = Math.min(c.minX, item.x)\r\n c.count++\r\n found = true\r\n break\r\n }\r\n }\r\n if (!found) {\r\n xClusters.push({ center: item.x, count: 1, minX: item.x })\r\n }\r\n }\r\n }\r\n\r\n // Step 2: 빈도 피크 — 최소 3회 이상 등장 (단발성 텍스트 노이즈 제거)\r\n const peaks = xClusters\r\n .filter(c => c.count >= 3)\r\n .sort((a, b) => a.minX - b.minX)\r\n\r\n // 최소 3개 열이 있어야 테이블로 판별 — 2열은 일반 2단 레이아웃과 구분 불가\r\n if (peaks.length < 3) return null\r\n\r\n // Step 3: 가까운 피크 병합 — MERGE_TOL 40px (같은 논리 열의 미세 위치 차이 흡수)\r\n const MERGE_TOL = 40\r\n const merged: { center: number; count: number; minX: number }[] = [peaks[0]]\r\n for (let i = 1; i < peaks.length; i++) {\r\n const prev = merged[merged.length - 1]\r\n if (peaks[i].minX - prev.minX < MERGE_TOL) {\r\n // 빈도 높은 쪽 유지, 최소 x는 작은 값\r\n if (peaks[i].count > prev.count) {\r\n prev.center = peaks[i].center\r\n }\r\n prev.count += peaks[i].count\r\n prev.minX = Math.min(prev.minX, peaks[i].minX)\r\n } else {\r\n merged.push({ ...peaks[i] })\r\n }\r\n }\r\n\r\n // 열 경계 = 각 클러스터의 minX (왼쪽 정렬 기준), 병합 후 재검증\r\n const rawColumns = merged.filter(c => c.count >= 3).map(c => c.minX)\r\n if (rawColumns.length < 3) return null\r\n\r\n // 최소 열 폭 검증: 30px 미만인 열은 인접 열과 병합 (한 글자 열 방지)\r\n const MIN_DETECT_COL_WIDTH = 30\r\n const columns: number[] = [rawColumns[0]]\r\n for (let i = 1; i < rawColumns.length; i++) {\r\n if (rawColumns[i] - columns[columns.length - 1] < MIN_DETECT_COL_WIDTH) continue\r\n columns.push(rawColumns[i])\r\n }\r\n return columns.length >= 3 ? columns : null\r\n}\r\n\r\nfunction findColumn(x: number, columns: number[]): number {\r\n for (let i = columns.length - 1; i >= 0; i--) {\r\n // 10px 왼쪽 허용 오차 — 셀 내 텍스트 미세 좌측 이탈 보정\r\n if (x >= columns[i] - 10) return i\r\n }\r\n return 0\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 열 기반 추출 — 테이블/텍스트 영역 분리\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction extractWithColumns(yLines: NormItem[][], columns: number[]): string {\r\n const result: string[] = []\r\n const colMin = columns[0]\r\n const colMax = columns[columns.length - 1]\r\n\r\n // \"비고\" 라인 감지 — 이후는 텍스트로 처리\r\n let bigoIdx = -1\r\n for (let i = 0; i < yLines.length; i++) {\r\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\r\n bigoIdx = i\r\n break\r\n }\r\n }\r\n\r\n // 테이블 시작: 첫 번째 다열(3+ 열 사용) 라인\r\n let tableStart = -1\r\n for (let i = 0; i < (bigoIdx >= 0 ? bigoIdx : yLines.length); i++) {\r\n const usedCols = new Set(yLines[i].map(item => findColumn(item.x, columns)))\r\n if (usedCols.size >= 3) {\r\n tableStart = i\r\n break\r\n }\r\n }\r\n\r\n const tableEnd = bigoIdx >= 0 ? bigoIdx : yLines.length\r\n\r\n // 테이블 시작 이전 = 텍스트\r\n for (let i = 0; i < (tableStart >= 0 ? tableStart : tableEnd); i++) {\r\n result.push(mergeLineSimple(yLines[i]))\r\n }\r\n\r\n // 테이블 영역: 모든 라인을 그리드에 포함 (단일 아이템 라인도)\r\n if (tableStart >= 0) {\r\n const tableLines = yLines.slice(tableStart, tableEnd)\r\n // 테이블 x범위 밖의 라인만 텍스트로 분리\r\n // 좌측 20px, 우측 200px 허용 — 비고/주석 열이 오른쪽에 넓게 위치하는 공문서 특성 반영\r\n const gridLines: NormItem[][] = []\r\n for (const line of tableLines) {\r\n const inRange = line.some(item =>\r\n item.x >= colMin - 20 && item.x <= colMax + 200\r\n )\r\n if (inRange && !isProseSpread(line)) {\r\n gridLines.push(line)\r\n } else {\r\n // 그리드 밖 라인은 현재까지 축적된 그리드 출력 후 텍스트로\r\n if (gridLines.length > 0) {\r\n result.push(buildGridTable(gridLines.splice(0), columns))\r\n }\r\n result.push(mergeLineSimple(line))\r\n }\r\n }\r\n if (gridLines.length > 0) {\r\n result.push(buildGridTable(gridLines, columns))\r\n }\r\n }\r\n\r\n // 비고 영역\r\n if (bigoIdx >= 0) {\r\n result.push(\"\")\r\n for (let i = bigoIdx; i < yLines.length; i++) {\r\n result.push(mergeLineSimple(yLines[i]))\r\n }\r\n }\r\n\r\n return result.join(\"\\n\")\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 그리드 테이블 빌더 — y-라인을 열에 배치 후 행 병합\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction buildGridTable(lines: NormItem[][], columns: number[]): string {\r\n const numCols = columns.length\r\n\r\n // Step 1: 각 y-라인을 열에 배치\r\n const yRows: string[][] = lines.map(items => {\r\n const row = Array(numCols).fill(\"\")\r\n for (const item of items) {\r\n const col = findColumn(item.x, columns)\r\n row[col] = row[col] ? row[col] + \" \" + item.text : item.text\r\n }\r\n return row\r\n })\r\n\r\n // Step 2: 행 병합 — 새 논리적 행 판별\r\n // 데이터 열 기준점 (가격 등이 들어가는 오른쪽 열들)\r\n const dataColStart = Math.max(2, Math.floor(numCols / 2))\r\n const merged: string[][] = []\r\n\r\n for (const row of yRows) {\r\n if (row.every(c => c === \"\")) continue\r\n\r\n if (merged.length === 0) {\r\n merged.push([...row])\r\n continue\r\n }\r\n\r\n const prev = merged[merged.length - 1]\r\n const filledCols = row.map((c, i) => c ? i : -1).filter(i => i >= 0)\r\n const filledCount = filledCols.length\r\n\r\n let isNewRow = false\r\n\r\n // Rule 1: col 0에 텍스트 (3글자 이상) → 새 행 (단, \"권\"처럼 짧은 건 continuation)\r\n if (row[0] && row[0].length >= 3) {\r\n isNewRow = true\r\n }\r\n\r\n // Rule 2: col 1에 텍스트 → 항상 새 행 (새 항목 시작)\r\n if (!isNewRow && numCols > 1 && row[1]) {\r\n isNewRow = true\r\n }\r\n\r\n // Rule 3: 데이터 열(3+)에 새 값이 있고 이전 행 데이터 열에도 이미 값 있음 → 새 가격 행\r\n if (!isNewRow) {\r\n const hasData = row.slice(dataColStart).some(c => c !== \"\")\r\n const prevHasData = prev.slice(dataColStart).some(c => c !== \"\")\r\n if (hasData && prevHasData) {\r\n isNewRow = true\r\n }\r\n }\r\n\r\n // Exception: filledCount=1이고 col 0에 짧은 텍스트(≤2자) → word continuation (예: \"권\", \"여권\")\r\n if (isNewRow && filledCount === 1 && row[0] && row[0].length <= 2) {\r\n isNewRow = false\r\n }\r\n\r\n if (isNewRow) {\r\n merged.push([...row])\r\n } else {\r\n for (let c = 0; c < numCols; c++) {\r\n if (row[c]) {\r\n prev[c] = prev[c] ? prev[c] + \" \" + row[c] : row[c]\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (merged.length < 2) {\r\n return merged.map(r => r.filter(c => c).join(\" \")).join(\"\\n\")\r\n }\r\n\r\n // Step 3: 헤더 행 병합 — 첫 N행이 모두 데이터열(dataColStart+)에 값이 없으면 헤더\r\n let headerEnd = 0\r\n for (let r = 0; r < merged.length; r++) {\r\n const hasDataValues = merged[r].slice(dataColStart).some(c => c && /\\d/.test(c))\r\n if (hasDataValues) break\r\n headerEnd = r + 1\r\n }\r\n\r\n if (headerEnd > 1) {\r\n // 헤더 행들을 하나로 합침\r\n const headerRow = Array(numCols).fill(\"\")\r\n for (let r = 0; r < headerEnd; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n if (merged[r][c]) {\r\n headerRow[c] = headerRow[c] ? headerRow[c] + \" \" + merged[r][c] : merged[r][c]\r\n }\r\n }\r\n }\r\n merged.splice(0, headerEnd, headerRow)\r\n }\r\n\r\n // Step 3.5: 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\r\n for (const row of merged) {\r\n for (let c = 0; c < row.length; c++) {\r\n if (row[c]) row[c] = collapseEvenSpacing(row[c])\r\n }\r\n }\r\n\r\n // Step 3.6: 테이블 품질 검증 — 선 없는 fallback 경로에서는 보수적으로\r\n const totalCells = merged.length * numCols\r\n const filledCells = merged.reduce((s, row) => s + row.filter(c => c).length, 0)\r\n // 빈 셀 과반, 행이 2 미만, 또는 3행 이하+7열 이상 → 텍스트로 복원\r\n if (filledCells < totalCells * 0.35 || merged.length < 2 ||\r\n (merged.length <= 3 && numCols >= 7)) {\r\n return merged.map(r => r.filter(c => c).join(\"\\t\")).join(\"\\n\")\r\n }\r\n\r\n // Step 4: 마크다운 테이블\r\n const md: string[] = []\r\n md.push(\"| \" + merged[0].join(\" | \") + \" |\")\r\n md.push(\"| \" + merged[0].map(() => \"---\").join(\" | \") + \" |\")\r\n for (let r = 1; r < merged.length; r++) {\r\n md.push(\"| \" + merged[r].join(\" | \") + \" |\")\r\n }\r\n return md.join(\"\\n\")\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 유틸\r\n// ═══════════════════════════════════════════════════════\r\n\r\nfunction mergeLineSimple(items: NormItem[]): string {\r\n if (items.length <= 1) return items[0]?.text || \"\"\r\n const sorted = [...items].sort((a, b) => a.x - b.x)\r\n\r\n // 좌표 기반 균등배분 감지 (ODL TextLineProcessor 방식)\r\n const isEvenSpaced = detectEvenSpacedItems(sorted)\r\n\r\n let result = sorted[0].text\r\n for (let i = 1; i < sorted.length; i++) {\r\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\r\n const avgFs = (sorted[i].fontSize + sorted[i - 1].fontSize) / 2\r\n\r\n // 탭 갭은 항상 탭으로 — 균등배분보다 우선\r\n // 기준: fontSize의 2배 이상 또는 30px+ (균등배분 간격은 보통 fontSize*1.5 이하)\r\n const tabThreshold = Math.max(avgFs * 2, 30)\r\n if (gap > tabThreshold) {\r\n result += \"\\t\"\r\n result += sorted[i].text\r\n continue\r\n }\r\n\r\n // 균등배분 구간이면 공백 없이 합침\r\n if (isEvenSpaced[i]) {\r\n result += sorted[i].text\r\n continue\r\n }\r\n\r\n // pdfjs 공백 아이템이 있었으면 단어 경계 — 갭 크기 무관하게 공백 삽입\r\n if (sorted[i].hasSpaceBefore && gap >= avgFs * 0.05) {\r\n result += \" \"\r\n result += sorted[i].text\r\n continue\r\n }\r\n // 마커(□○▶ 등) 뒤에 한글이 오면 항상 공백 보장 — \"□장소\" → \"□ 장소\"\r\n if (/[□■○●▶◆◇ㅇ]$/.test(sorted[i - 1].text) && /^[가-힣]/.test(sorted[i].text) && gap > 1) {\r\n result += \" \"\r\n result += sorted[i].text\r\n continue\r\n }\r\n // 매우 작은 갭 — 공백 없이 붙임\r\n if (gap < avgFs * 0.15) { /* no space */ }\r\n // 한글 관련 작은 갭 — PDF 문자 개별 배치 잔재\r\n else if (gap < avgFs * 0.35 && (/[가-힣]$/.test(result) || /^[가-힣]/.test(sorted[i].text))) { /* no space */ }\r\n // 3px+ 갭 = 공백 (단어 구분)\r\n else if (gap > 3) result += \" \"\r\n result += sorted[i].text\r\n }\r\n return result\r\n}\r\n\r\n\r\n\r\n\r\nexport function cleanPdfText(text: string): string {\r\n return mergeKoreanLines(\r\n text\r\n // 문서 시작 단독 페이지 번호\r\n .replace(/^\\d{1,4}\\n/, \"\")\r\n // \"- 2 -\" 스타일 페이지 번호 (독립 라인 및 목록 항목 형태 포함)\r\n .replace(/^[\\s]*[-–—]\\s*[-–—]?\\d+[-–—]?[\\s]*[-–—]?[\\s]*$/gm, \"\")\r\n // \"1 / 5\" 스타일 페이지 번호\r\n .replace(/^\\s*\\d+\\s*\\/\\s*\\d+\\s*$/gm, \"\")\r\n // 단독 페이지 번호 (줄 끝에 혼자 있는 숫자)\r\n .replace(/\\n\\d{1,4}\\n/g, \"\\n\")\r\n // 문서 마지막 단독 페이지 번호\r\n .replace(/\\n\\d{1,4}$/, \"\")\r\n // 단독 숫자 헤딩 제거 (\"# 6\\n재무과\" → \"\\n재무과\")\r\n .replace(/^#{1,6}\\s*\\d{1,4}\\s*$/gm, \"\")\r\n )\r\n // 균등배분 문자열 후처리 (pdfjs가 합친 TextItem + buildGridTable 셀 텍스트)\r\n .replace(/^(?!\\| ---).*$/gm, line => collapseEvenSpacing(line))\r\n // 마커 뒤 2글자 균등배분 합침 (\"□ 일 시\" → \"□ 일시\", \"□ 장 소\" → \"□ 장소\")\r\n .replace(/([□■◆○●▶ㅇ])\\s+([가-힣])\\s+([가-힣])/g, \"$1 $2$3\")\r\n .replace(/\\n{3,}/g, \"\\n\\n\")\r\n .trim()\r\n}\r\n\r\nfunction startsWithMarker(line: string): boolean {\r\n const t = line.trimStart()\r\n return /^[가-힣ㄱ-ㅎ][.)]/.test(t) || /^\\d+[.)]/.test(t) || /^\\([가-힣ㄱ-ㅎ\\d]+\\)/.test(t) ||\r\n /^[○●※▶▷◆◇■□★☆\\-·]\\s/.test(t) || /^제\\d+[조항호장절]/.test(t)\r\n}\r\n\r\nfunction isStandaloneHeader(line: string): boolean {\r\n return /^제\\d+[조항호장절](\\([^)]*\\))?(\\s+\\S+){0,7}$/.test(line.trim())\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 리스트 감지 — paragraph 블록 중 번호 패턴을 list 블록으로 변환\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/**\r\n * 연속된 paragraph 블록에서 번호 리스트 패턴을 감지하여 list 블록으로 변환.\r\n * \"비고\" 헤더 뒤에 오는 \"1.\", \"2.\" 패턴이 대표적.\r\n */\r\nfunction detectListBlocks(blocks: IRBlock[]): IRBlock[] {\r\n const result: IRBlock[] = []\r\n\r\n for (let i = 0; i < blocks.length; i++) {\r\n const block = blocks[i]\r\n\r\n if (block.type === \"paragraph\" && block.text) {\r\n const text = block.text.trim()\r\n // 번호 리스트: \"1.\", \"2.\" 등\r\n if (/^\\d+\\.\\s/.test(text)) {\r\n result.push({ ...block, type: \"list\", listType: \"ordered\", text: block.text })\r\n continue\r\n }\r\n // 비번호 리스트: ○, -, ·, ※, ▶ 등\r\n if (/^[○●·※▶▷◆◇\\-]\\s/.test(text)) {\r\n result.push({ ...block, type: \"list\", listType: \"unordered\", text: block.text })\r\n continue\r\n }\r\n }\r\n\r\n result.push(block)\r\n }\r\n\r\n return result\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════\r\n// 한국어 특수 테이블 감지 — \"구분/항목/종류\" 패턴 기반 key-value 테이블\r\n// ═══════════════════════════════════════════════════════\r\n\r\n/**\r\n * ODL SpecialTableProcessor 포팅: 연속된 \"구분:\", \"항목:\", \"종류:\" 등\r\n * 한국어 key-value 패턴을 2열 테이블로 변환.\r\n *\r\n * 동작:\r\n * 1) paragraph 블록의 텍스트에서 한국어 key-value 패턴 감지\r\n * 2) \":\"가 있으면 key | value 2열, 없으면 colSpan=2 (전체 행)\r\n * 3) 연속된 패턴을 하나의 테이블로 그룹화\r\n */\r\nconst KOREAN_TABLE_HEADER_RE = /^\\(?(구분|항목|종류|분류|유형|대상|내용|기간|금액|비율|방법|절차|요건|조건|근거|목적|범위|기준)\\)?[:\\s]/\r\n\r\n/** KV 오탐 패턴: 시간(14:30), URL(://), 숫자:숫자(3:2) */\r\nconst KV_FALSE_POSITIVE_RE = /\\d{1,2}:\\d{2}|:\\/\\/|\\d+:\\d+/\r\n\r\nfunction detectSpecialKoreanTables(blocks: IRBlock[]): IRBlock[] {\r\n const result: IRBlock[] = []\r\n let kvLines: { key: string; value: string; block: IRBlock }[] = []\r\n\r\n const flushKvTable = () => {\r\n if (kvLines.length < 2) {\r\n // 2행 미만이면 테이블로 만들 가치 없음 → 원래 블록 복원\r\n for (const kv of kvLines) result.push(kv.block)\r\n kvLines = []\r\n return\r\n }\r\n\r\n // 2열 테이블 생성\r\n const cells: import(\"../types.js\").IRCell[][] = kvLines.map(kv => {\r\n if (kv.value) {\r\n return [\r\n { text: kv.key, colSpan: 1, rowSpan: 1 },\r\n { text: kv.value, colSpan: 1, rowSpan: 1 },\r\n ]\r\n }\r\n // \":\" 없는 줄 → 전체 행 (colSpan=2)\r\n return [\r\n { text: kv.key, colSpan: 2, rowSpan: 1 },\r\n { text: \"\", colSpan: 1, rowSpan: 1 },\r\n ]\r\n })\r\n\r\n const irTable: IRTable = {\r\n rows: cells.length,\r\n cols: 2,\r\n cells,\r\n hasHeader: true,\r\n }\r\n\r\n // 첫 블록의 위치 정보 사용\r\n const firstBlock = kvLines[0].block\r\n result.push({\r\n type: \"table\",\r\n table: irTable,\r\n pageNumber: firstBlock.pageNumber,\r\n bbox: firstBlock.bbox,\r\n })\r\n kvLines = []\r\n }\r\n\r\n for (const block of blocks) {\r\n if (block.type !== \"paragraph\" || !block.text) {\r\n flushKvTable()\r\n result.push(block)\r\n continue\r\n }\r\n\r\n const text = block.text.trim()\r\n\r\n // \"구분: xxx\" 또는 \"항목: xxx\" 패턴 매칭\r\n if (KOREAN_TABLE_HEADER_RE.test(text)) {\r\n const colonIdx = text.indexOf(\":\")\r\n if (colonIdx >= 0) {\r\n kvLines.push({\r\n key: text.slice(0, colonIdx).trim(),\r\n value: text.slice(colonIdx + 1).trim(),\r\n block,\r\n })\r\n } else {\r\n // \":\" 없이 공백으로 구분된 경우: \"구분 xxx\"\r\n const spaceIdx = text.search(/\\s/)\r\n if (spaceIdx > 0) {\r\n kvLines.push({\r\n key: text.slice(0, spaceIdx).trim(),\r\n value: text.slice(spaceIdx + 1).trim(),\r\n block,\r\n })\r\n } else {\r\n kvLines.push({ key: text, value: \"\", block })\r\n }\r\n }\r\n continue\r\n }\r\n\r\n // key-value 패턴이 아닌 블록이 나오면 축적된 것을 flush\r\n // 단, 이미 수집 중이고 현재 블록이 \"label: value\" 형태면 계속 수집\r\n if (kvLines.length > 0 && text.includes(\":\")) {\r\n // 오탐 제외: 시간(14:30), URL(http://), 숫자:숫자(3:2), 괄호 포함\r\n if (!KV_FALSE_POSITIVE_RE.test(text) && !text.includes(\"(\") && !text.includes(\")\")) {\r\n const colonIdx = text.indexOf(\":\")\r\n const key = text.slice(0, colonIdx).trim()\r\n // key가 순수 한글 2~8자 (공백/괄호 없음)면 유효한 key-value 라인\r\n if (/^[가-힣]+$/.test(key) && key.length >= 2 && key.length <= 8) {\r\n kvLines.push({\r\n key,\r\n value: text.slice(colonIdx + 1).trim(),\r\n block,\r\n })\r\n continue\r\n }\r\n }\r\n }\r\n\r\n flushKvTable()\r\n result.push(block)\r\n }\r\n\r\n flushKvTable()\r\n return result\r\n}\r\n\r\n// ─── 머리글/바닥글 감지 ────────────────────────────\r\n\r\n/**\r\n * 머리글/바닥글 감지 — 2-pass 하이브리드:\r\n * 1) 텍스트 반복 패턴 (숫자 normalization) — 고정 문구\r\n * 2) y 위치 클러스터 (±3pt) — 텍스트가 페이지별로 달라도(챕터명 등) 위치가 반복되면 제거\r\n */\r\nfunction removeHeaderFooterBlocks(\r\n blocks: IRBlock[],\r\n pageHeights: Map<number, number>,\r\n warnings: ParseWarning[],\r\n): number[] {\r\n const ZONE_RATIO = 0.12 // 상하 12% (10% 초과 여백 대응)\r\n const MIN_REPEAT = 3 // 최소 3페이지 반복\r\n const Y_BUCKET = 5 // y 좌표 클러스터링 버킷 (pt)\r\n\r\n type ZoneEntry = { blockIdx: number; page: number; y: number; text: string }\r\n const topEntries: ZoneEntry[] = []\r\n const bottomEntries: ZoneEntry[] = []\r\n\r\n for (let bi = 0; bi < blocks.length; bi++) {\r\n const b = blocks[bi]\r\n if (!b.bbox || !b.pageNumber || !b.text?.trim()) continue\r\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\r\n if (!ph) continue\r\n\r\n const blockTop = ph - (b.bbox.y + b.bbox.height)\r\n const blockBottom = ph - b.bbox.y\r\n const entry: ZoneEntry = { blockIdx: bi, page: b.pageNumber, y: b.bbox.y, text: b.text.trim() }\r\n\r\n if (blockBottom <= ph * ZONE_RATIO) bottomEntries.push(entry)\r\n else if (blockTop >= ph * (1 - ZONE_RATIO)) topEntries.push(entry)\r\n }\r\n\r\n const removeSet = new Set<number>()\r\n\r\n for (const entries of [topEntries, bottomEntries]) {\r\n if (entries.length === 0) continue\r\n\r\n // (1) 텍스트 반복 패턴\r\n const patternCount = new Map<string, number>()\r\n const patternPages = new Map<string, Set<number>>()\r\n for (const e of entries) {\r\n const norm = e.text.replace(/\\d+/g, \"#\")\r\n patternCount.set(norm, (patternCount.get(norm) || 0) + 1)\r\n const pages = patternPages.get(norm) || new Set<number>()\r\n pages.add(e.page)\r\n patternPages.set(norm, pages)\r\n }\r\n const repeatedPatterns = new Set<string>()\r\n for (const [p, count] of patternCount) {\r\n // 서로 다른 페이지에서 MIN_REPEAT번 이상 등장\r\n if (count >= MIN_REPEAT && (patternPages.get(p)?.size ?? 0) >= MIN_REPEAT) {\r\n repeatedPatterns.add(p)\r\n }\r\n }\r\n\r\n // (2) y 위치 클러스터 — bucket별 등장 페이지 수\r\n const bucketPages = new Map<number, Set<number>>()\r\n for (const e of entries) {\r\n const bucket = Math.round(e.y / Y_BUCKET)\r\n const pages = bucketPages.get(bucket) || new Set<number>()\r\n pages.add(e.page)\r\n bucketPages.set(bucket, pages)\r\n }\r\n const repeatedBuckets = new Set<number>()\r\n for (const [b, pages] of bucketPages) {\r\n if (pages.size >= MIN_REPEAT) repeatedBuckets.add(b)\r\n }\r\n\r\n // 제거 대상: 텍스트 반복 OR 위치 반복\r\n for (const e of entries) {\r\n const norm = e.text.replace(/\\d+/g, \"#\")\r\n const bucket = Math.round(e.y / Y_BUCKET)\r\n if (repeatedPatterns.has(norm) || repeatedBuckets.has(bucket)) {\r\n removeSet.add(e.blockIdx)\r\n }\r\n }\r\n }\r\n\r\n if (removeSet.size > 0) {\r\n warnings.push({ message: `${removeSet.size}개 머리글/바닥글 요소 제거됨`, code: \"HIDDEN_TEXT_FILTERED\" })\r\n }\r\n\r\n return [...removeSet].sort((a, b) => a - b)\r\n}\r\n\r\nfunction mergeKoreanLines(text: string): string {\r\n if (!text) return \"\"\r\n const lines = text.split(\"\\n\")\r\n if (lines.length <= 1) return text\r\n const result: string[] = [lines[0]]\r\n\r\n for (let i = 1; i < lines.length; i++) {\r\n const prev = result[result.length - 1]\r\n const curr = lines[i]\r\n const currTrimmed = curr.trim()\r\n // 마크다운 헤딩/테이블/구분선은 병합하지 않음\r\n if (/^#{1,6}\\s/.test(prev) || /^#{1,6}\\s/.test(curr) || /^\\|/.test(currTrimmed) || /^---/.test(currTrimmed)) {\r\n result.push(curr)\r\n continue\r\n }\r\n // 쉼표로 끝나는 줄 + 다음 줄 = 연속 문장\r\n if (/,$/.test(prev.trim()) && currTrimmed.length > 0) {\r\n result[result.length - 1] = prev + \"\\n\" + curr\r\n continue\r\n }\r\n // (※ 로 시작하는 줄 = 이전 줄의 부연설명\r\n if (/^\\(※/.test(currTrimmed)) {\r\n result[result.length - 1] = prev + \" \" + currTrimmed\r\n continue\r\n }\r\n // 한글 줄바꿈 병합 — 마커(○, □ 등)로 시작하는 이전 줄은 합치지 않음\r\n if (/[가-힣·,\\-]$/.test(prev) && /^[가-힣(]/.test(curr) &&\r\n !startsWithMarker(curr) && !isStandaloneHeader(prev) &&\r\n !startsWithMarker(prev)) {\r\n result[result.length - 1] = prev + \" \" + curr\r\n } else {\r\n result.push(curr)\r\n }\r\n }\r\n return result.join(\"\\n\")\r\n}\r\n"],"mappings":"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","names":["_","g","g","totalCells","emptyCells"]}