kordoc 2.2.1 → 2.2.3

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/table/builder.ts","../src/hwpx/parser.ts","../src/types.ts","../src/hwp5/record.ts","../src/hwp5/aes.ts","../src/hwp5/crypto.ts","../src/hwp5/cfb-lenient.ts","../src/hwp5/parser.ts","../src/pdf/line-detector.ts","../src/pdf/cluster-detector.ts","../src/pdf/polyfill.ts","../src/pdf/parser.ts","../src/index.ts","../src/xlsx/parser.ts","../src/docx/parser.ts","../src/form/recognize.ts","../src/hwpx/generator.ts","../src/diff/text-diff.ts","../src/diff/compare.ts"],"sourcesContent":["/** kordoc 공용 유틸리티 */\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"KordocError\"\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n const segments = normalized.split(\"/\")\n return segments.some(s => s === \"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n buffer: ArrayBuffer,\n maxUncompressedSize = 100 * 1024 * 1024,\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** XXE/Billion Laughs 방지 — DOCTYPE 제거 (내부 DTD 서브셋 포함) */\nexport function stripDtd(xml: string): string {\n return xml.replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n return trimmed\n}\n\n// ─── 안전한 min/max (스택 오버플로 방지) ─────────────\n\n/** Math.min(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMin(arr: number[]): number {\n let min = Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] < min) min = arr[i]\n return min\n}\n\n/** Math.max(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMax(arr: number[]): number {\n let max = -Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] > max) max = arr[i]\n return max\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n","/** 2-pass colSpan/rowSpan 테이블 빌더 및 Markdown 변환 */\r\n\r\nimport type { CellContext, IRBlock, IRCell, IRTable } from \"../types.js\"\r\nimport { sanitizeHref } from \"../utils.js\"\r\n\r\n/** 테이블 열 수 상한 — 한국 공공문서 기준 충분한 값 */\r\nexport const MAX_COLS = 200\r\n/** 테이블 행 수 상한 — 메모리 폭주 방지 */\r\nexport const MAX_ROWS = 10000\r\n\r\nexport function buildTable(rows: CellContext[][]): IRTable {\r\n if (rows.length > MAX_ROWS) rows = rows.slice(0, MAX_ROWS)\r\n const numRows = rows.length\r\n\r\n // colAddr/rowAddr가 있으면 직접 배치 (HWPX cellAddr, HWP5 colAddr/rowAddr)\r\n const hasAddr = rows.some(row => row.some(c => c.colAddr !== undefined && c.rowAddr !== undefined))\r\n if (hasAddr) return buildTableDirect(rows, numRows)\r\n\r\n // Pass 1: maxCols 계산 — 2D 배열 사용 (동적 확장)\r\n let maxCols = 0\r\n const tempOccupied: boolean[][] = Array.from({ length: numRows }, () => [])\r\n\r\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\r\n let colIdx = 0\r\n for (const cell of rows[rowIdx]) {\r\n while (colIdx < MAX_COLS && tempOccupied[rowIdx][colIdx]) colIdx++\r\n if (colIdx >= MAX_COLS) break\r\n\r\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\r\n for (let c = colIdx; c < Math.min(colIdx + cell.colSpan, MAX_COLS); c++) {\r\n tempOccupied[r][c] = true\r\n }\r\n }\r\n colIdx += cell.colSpan\r\n if (colIdx > maxCols) maxCols = colIdx\r\n }\r\n }\r\n\r\n if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false }\r\n\r\n // Pass 2: 실제 배치\r\n const grid: IRCell[][] = Array.from({ length: numRows }, () =>\r\n Array.from({ length: maxCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 }))\r\n )\r\n const occupied: boolean[][] = Array.from({ length: numRows }, () => Array(maxCols).fill(false))\r\n\r\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\r\n let colIdx = 0\r\n let cellIdx = 0\r\n\r\n while (colIdx < maxCols && cellIdx < rows[rowIdx].length) {\r\n while (colIdx < maxCols && occupied[rowIdx][colIdx]) colIdx++\r\n if (colIdx >= maxCols) break\r\n\r\n const cell = rows[rowIdx][cellIdx]\r\n grid[rowIdx][colIdx] = {\r\n text: cell.text.trim(),\r\n colSpan: cell.colSpan,\r\n rowSpan: cell.rowSpan,\r\n }\r\n\r\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\r\n for (let c = colIdx; c < Math.min(colIdx + cell.colSpan, maxCols); c++) {\r\n occupied[r][c] = true\r\n }\r\n }\r\n\r\n colIdx += cell.colSpan\r\n cellIdx++\r\n }\r\n }\r\n\r\n return trimAndReturn(grid, numRows, maxCols)\r\n}\r\n\r\n/** colAddr/rowAddr 절대 좌표 기반 직접 배치 */\r\nfunction buildTableDirect(rows: CellContext[][], numRows: number): IRTable {\r\n // 전체 셀에서 maxCols 계산 (MAX_COLS 상한 적용)\r\n let maxCols = 0\r\n for (const row of rows) {\r\n for (const cell of row) {\r\n const end = (cell.colAddr ?? 0) + cell.colSpan\r\n if (end > maxCols) maxCols = end\r\n }\r\n }\r\n if (maxCols > MAX_COLS) maxCols = MAX_COLS\r\n if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false }\r\n\r\n const grid: IRCell[][] = Array.from({ length: numRows }, () =>\r\n Array.from({ length: maxCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 }))\r\n )\r\n\r\n for (const row of rows) {\r\n for (const cell of row) {\r\n const r = cell.rowAddr ?? 0\r\n const c = cell.colAddr ?? 0\r\n if (r >= numRows || c >= maxCols || r < 0 || c < 0) continue\r\n\r\n grid[r][c] = { text: cell.text.trim(), colSpan: cell.colSpan, rowSpan: cell.rowSpan }\r\n\r\n // 병합 영역 마킹\r\n for (let dr = 0; dr < cell.rowSpan; dr++) {\r\n for (let dc = 0; dc < cell.colSpan; dc++) {\r\n if (dr === 0 && dc === 0) continue\r\n if (r + dr < numRows && c + dc < maxCols) {\r\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return trimAndReturn(grid, numRows, maxCols)\r\n}\r\n\r\n/** 빈 후행 열 제거 후 IRTable 반환 */\r\nfunction trimAndReturn(grid: IRCell[][], numRows: number, maxCols: number): IRTable {\r\n let effectiveCols = maxCols\r\n while (effectiveCols > 0) {\r\n const colEmpty = grid.every(row => !row[effectiveCols - 1]?.text?.trim())\r\n if (!colEmpty) break\r\n effectiveCols--\r\n }\r\n if (effectiveCols < maxCols && effectiveCols > 0) {\r\n const trimmed = grid.map(row => row.slice(0, effectiveCols))\r\n return { rows: numRows, cols: effectiveCols, cells: trimmed, hasHeader: numRows > 1 }\r\n }\r\n return { rows: numRows, cols: maxCols, cells: grid, hasHeader: numRows > 1 }\r\n}\r\n\r\nexport function convertTableToText(rows: CellContext[][]): string {\r\n return rows\r\n .map(row =>\r\n row\r\n .map(c => c.text.trim().replace(/\\n/g, \" \").replace(/\\|/g, \"\\\\|\"))\r\n .filter(Boolean)\r\n .join(\" / \")\r\n )\r\n .filter(Boolean)\r\n .join(\"\\n\")\r\n}\r\n\r\n/** 마크다운 GFM 특수문자 이스케이프 — remark-gfm 오해석 방지 */\r\nfunction escapeGfm(text: string): string {\r\n // ~ → \\~ (GFM strikethrough 방지)\r\n return text.replace(/~/g, \"\\\\~\")\r\n}\r\n\r\n/** HWP 자동생성 도형/개체 대체텍스트 정규식 — 한컴오피스가 삽입하는 모든 알려진 패턴 */\r\nconst HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\\s?개체|묶음\\s?개체|글상자|수식\\s?개체|OLE\\s?개체)\\s?입니다\\.?/g\r\n\r\n/** HWP PUA 특수문자 및 도형 대체텍스트 제거 — 모든 포맷 공통 */\r\nfunction sanitizeText(text: string): string {\r\n let result = text\r\n // Supplementary Private Use Area (U+F0000-U+FFFFD) — HWP 전용 기호\r\n .replace(/[\\u{F0000}-\\u{FFFFD}]/gu, \"\")\r\n // HWP 도형/개체 자동생성 대체텍스트 제거\r\n .replace(HWP_SHAPE_ALT_TEXT_RE, \"\")\r\n .replace(/ +/g, \" \")\r\n .trim()\r\n // 균등배분 스페이스 정리 (\"현 장 대 응 단 장\" → \"현장대응단장\")\r\n // 짧은 텍스트(30자 이하)에서 70%+ 토큰이 한글 1글자면 균등배분으로 판단\r\n if (result.length <= 30 && result.includes(\" \")) {\r\n const tokens = result.split(\" \")\r\n // 한글 1글자 토큰만 카운트 — ASCII 특수문자(< > & 등)는 균등배분이 아님\r\n const koreanSingleCharCount = tokens.filter(t => t.length === 1 && /[\\uAC00-\\uD7AF\\u3131-\\u318E]/.test(t)).length\r\n if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {\r\n result = tokens.join(\"\")\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/**\r\n * 레이아웃 테이블 감지 및 해체 — IRBlock 레벨에서 수행\r\n * 적은 행(≤3) + 셀 내 줄바꿈 다량 → table 블록을 paragraph 블록들로 분해\r\n * heading 감지 전에 호출해야 해체된 텍스트에 heading 감지 적용 가능\r\n */\r\nexport function flattenLayoutTables(blocks: IRBlock[]): IRBlock[] {\r\n const result: IRBlock[] = []\r\n\r\n for (const block of blocks) {\r\n if (block.type !== \"table\" || !block.table) {\r\n result.push(block)\r\n continue\r\n }\r\n\r\n const { rows: numRows, cols: numCols, cells } = block.table\r\n\r\n // 1x1 테이블은 기존 로직(tableToMarkdown)에서 처리\r\n if (numRows === 1 && numCols === 1) {\r\n result.push(block)\r\n continue\r\n }\r\n\r\n // 레이아웃 테이블 휴리스틱\r\n if (numRows <= 3) {\r\n let totalNewlines = 0\r\n let totalTextLen = 0\r\n for (let r = 0; r < numRows; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n const t = cells[r]?.[c]?.text || \"\"\r\n totalNewlines += (t.match(/\\n/g) || []).length\r\n totalTextLen += t.length\r\n }\r\n }\r\n\r\n // 레이아웃 테이블 판정: 많은 줄바꿈(>5), 또는 적은 행에 비해 총 텍스트 과다(>300)\r\n if (totalNewlines > 5 || (numRows <= 2 && totalTextLen > 300)) {\r\n // 레이아웃 테이블 → 각 셀을 paragraph 블록으로 분해\r\n for (let r = 0; r < numRows; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n const cellText = cells[r]?.[c]?.text?.trim()\r\n if (!cellText) continue\r\n // 셀 내 줄바꿈을 별도 paragraph로 분리\r\n for (const line of cellText.split(\"\\n\")) {\r\n const trimmed = line.trim()\r\n if (!trimmed) continue\r\n result.push({ type: \"paragraph\", text: trimmed, pageNumber: block.pageNumber })\r\n }\r\n }\r\n }\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\nexport function blocksToMarkdown(blocks: IRBlock[]): string {\r\n const lines: string[] = []\r\n\r\n for (let i = 0; i < blocks.length; i++) {\r\n const block = blocks[i]\r\n\r\n // 헤딩 블록\r\n if (block.type === \"heading\" && block.text) {\r\n const prefix = \"#\".repeat(Math.min(block.level || 2, 6))\r\n const headingText = sanitizeText(block.text)\r\n if (headingText) lines.push(\"\", `${prefix} ${headingText}`, \"\")\r\n continue\r\n }\r\n\r\n // 이미지 블록 — ![alt](filename) 참조\r\n if (block.type === \"image\" && block.text) {\r\n lines.push(\"\", `![image](${block.text})`, \"\")\r\n continue\r\n }\r\n\r\n // 구분선 블록\r\n if (block.type === \"separator\") {\r\n lines.push(\"\", \"---\", \"\")\r\n continue\r\n }\r\n\r\n // 리스트 블록\r\n if (block.type === \"list\" && block.text) {\r\n const listText = sanitizeText(block.text)\r\n if (!listText) continue\r\n // 텍스트가 이미 번호로 시작하면 그대로 출력 (원래 번호 보존)\r\n const alreadyNumbered = block.listType === \"ordered\" && /^\\d+\\.\\s/.test(listText)\r\n const prefix = alreadyNumbered ? \"\" : block.listType === \"ordered\" ? \"1. \" : \"- \"\r\n lines.push(`${prefix}${listText}`)\r\n if (block.children) {\r\n for (const child of block.children) {\r\n const childPrefix = child.listType === \"ordered\" ? \"1.\" : \"-\"\r\n lines.push(` ${childPrefix} ${child.text || \"\"}`)\r\n }\r\n }\r\n continue\r\n }\r\n\r\n if (block.type === \"paragraph\" && block.text) {\r\n let text = sanitizeText(block.text)\r\n if (!text) continue\r\n\r\n // 별표 패턴 (기존 호환)\r\n if (/^\\[별표\\s*\\d+/.test(text)) {\r\n const nextBlock = blocks[i + 1]\r\n if (nextBlock?.type === \"paragraph\" && nextBlock.text && /관련\\)?$/.test(nextBlock.text)) {\r\n lines.push(\"\", `## ${text} ${nextBlock.text}`, \"\")\r\n i++\r\n } else {\r\n lines.push(\"\", `## ${text}`, \"\")\r\n }\r\n continue\r\n }\r\n\r\n if (/^\\([^)]*조[^)]*관련\\)$/.test(text)) {\r\n lines.push(`*${text}*`, \"\")\r\n continue\r\n }\r\n\r\n // 하이퍼링크가 있으면 텍스트에 링크 삽입 (javascript: 등 위험 스킴 제거)\r\n if (block.href) {\r\n const href = sanitizeHref(block.href)\r\n if (href) text = `[${text}](${href})`\r\n }\r\n\r\n // 각주가 있으면 괄호로 인라인 삽입\r\n if (block.footnoteText) {\r\n text += ` (주: ${block.footnoteText})`\r\n }\r\n\r\n lines.push(escapeGfm(text), \"\")\r\n } else if (block.type === \"table\" && block.table) {\r\n // 테이블 앞에 빈 줄 보장 (마크다운 렌더링 필수)\r\n if (lines.length > 0 && lines[lines.length - 1] !== \"\") {\r\n lines.push(\"\")\r\n }\r\n const tableMd = tableToMarkdown(block.table)\r\n if (tableMd) {\r\n lines.push(tableMd)\r\n lines.push(\"\")\r\n }\r\n }\r\n }\r\n\r\n return lines.join(\"\\n\").trim()\r\n}\r\n\r\n/** 병합 셀 존재 여부 확인 */\r\nfunction hasMergedCells(table: IRTable): boolean {\r\n for (const row of table.cells) {\r\n for (const cell of row) {\r\n if (cell.colSpan > 1 || cell.rowSpan > 1) return true\r\n }\r\n }\r\n return false\r\n}\r\n\r\n/** 병합 테이블 → HTML <table> 출력 (rowspan/colspan 보존) */\r\nfunction tableToHtml(table: IRTable): string {\r\n const { cells, rows: numRows, cols: numCols } = table\r\n const skip = new Set<string>()\r\n const lines: string[] = [\"<table>\"]\r\n\r\n for (let r = 0; r < numRows; r++) {\r\n const tag = r === 0 ? \"th\" : \"td\"\r\n const rowHtml: string[] = []\r\n for (let c = 0; c < numCols; c++) {\r\n if (skip.has(`${r},${c}`)) continue\r\n const cell = cells[r]?.[c]\r\n if (!cell) continue\r\n\r\n // 병합 영역 skip 마킹\r\n for (let dr = 0; dr < cell.rowSpan; dr++) {\r\n for (let dc = 0; dc < cell.colSpan; dc++) {\r\n if (dr === 0 && dc === 0) continue\r\n if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`)\r\n }\r\n }\r\n\r\n const text = sanitizeText(cell.text).replace(/\\n/g, \"<br>\")\r\n const attrs: string[] = []\r\n if (cell.colSpan > 1) attrs.push(`colspan=\"${cell.colSpan}\"`)\r\n if (cell.rowSpan > 1) attrs.push(`rowspan=\"${cell.rowSpan}\"`)\r\n const attrStr = attrs.length ? \" \" + attrs.join(\" \") : \"\"\r\n rowHtml.push(`<${tag}${attrStr}>${text}</${tag}>`)\r\n }\r\n if (rowHtml.length) lines.push(`<tr>${rowHtml.join(\"\")}</tr>`)\r\n }\r\n\r\n lines.push(\"</table>\")\r\n return lines.join(\"\\n\")\r\n}\r\n\r\nfunction tableToMarkdown(table: IRTable): string {\r\n if (table.rows === 0 || table.cols === 0) return \"\"\r\n\r\n const { cells, rows: numRows, cols: numCols } = table\r\n\r\n // 병합 셀이 있으면 HTML 테이블로 출력\r\n if (hasMergedCells(table)) return tableToHtml(table)\r\n\r\n // 1행 1열 → 구조화된 텍스트 (빈 셀이면 스킵)\r\n if (numRows === 1 && numCols === 1) {\r\n const content = sanitizeText(cells[0][0].text)\r\n if (!content) return \"\"\r\n return content\r\n .split(/\\n/)\r\n .map(line => {\r\n const trimmed = line.trim()\r\n if (!trimmed) return \"\"\r\n if (/^\\d+\\.\\s/.test(trimmed)) return `**${escapeGfm(trimmed)}**`\r\n if (/^[가-힣]\\.\\s/.test(trimmed)) return ` ${escapeGfm(trimmed)}`\r\n return escapeGfm(trimmed)\r\n })\r\n .filter(Boolean)\r\n .join(\"\\n\")\r\n }\r\n\r\n // 1열 다행 테이블 → 각 행을 별도 라인으로 출력 (목록성 데이터)\r\n if (numCols === 1 && numRows >= 2) {\r\n return cells\r\n .map(row => escapeGfm(sanitizeText(row[0].text)).replace(/\\n/g, \" \"))\r\n .filter(Boolean)\r\n .join(\"\\n\")\r\n }\r\n\r\n // 병합 셀: 행/열 병합된 셀은 빈 칸으로\r\n const display: string[][] = Array.from({ length: numRows }, () => Array(numCols).fill(\"\"))\r\n const skip = new Set<string>()\r\n\r\n for (let r = 0; r < numRows; r++) {\r\n for (let c = 0; c < numCols; c++) {\r\n if (skip.has(`${r},${c}`)) continue\r\n const cell = cells[r]?.[c]\r\n if (!cell) continue\r\n display[r][c] = escapeGfm(sanitizeText(cell.text)).replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \"<br>\")\r\n\r\n // colSpan/rowSpan: 병합된 열은 빈 칸으로 유지 (텍스트 중복 방지)\r\n for (let dr = 0; dr < cell.rowSpan; dr++) {\r\n for (let dc = 0; dc < cell.colSpan; dc++) {\r\n if (dr === 0 && dc === 0) continue\r\n if (r + dr < numRows && c + dc < numCols) {\r\n skip.add(`${r + dr},${c + dc}`)\r\n }\r\n }\r\n }\r\n // colSpan > 1이면 display 열 인덱스를 건너뜀\r\n c += cell.colSpan - 1\r\n }\r\n }\r\n\r\n // rowSpan 잔류 처리:\r\n // 1) 완전 빈 행 제거\r\n // 2) \"첫 열만 값, 나머지 빈\" 행 → 다음 데이터 행의 첫 열에 값을 전파\r\n // 단, colSpan으로 인한 빈 열(skip 셀)은 이 대상이 아님\r\n const uniqueRows: string[][] = []\r\n let pendingFirstCol = \"\"\r\n for (let r = 0; r < display.length; r++) {\r\n const row = display[r]\r\n const isEmptyPlaceholder = row.every(cell => cell === \"\")\r\n if (isEmptyPlaceholder) continue\r\n\r\n // 첫 열만 값이 있고 나머지 모두 빈 행 → 다음 데이터 행의 첫 열에 전파\r\n // 단, colSpan으로 인한 빈 열(skip 셀)은 \"진짜 빈\"이 아니므로 제외\r\n const nonEmptyCols = row.filter(cell => cell !== \"\")\r\n const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`))\r\n if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0] !== \"\" && row.slice(1).every(c => c === \"\")) {\r\n pendingFirstCol = row[0]\r\n continue\r\n }\r\n\r\n // 저장된 첫 열 값을 현재 행의 빈 첫 열에 전파\r\n if (pendingFirstCol && row[0] === \"\") {\r\n row[0] = pendingFirstCol\r\n pendingFirstCol = \"\"\r\n } else {\r\n pendingFirstCol = \"\"\r\n }\r\n uniqueRows.push(row)\r\n }\r\n\r\n if (uniqueRows.length === 0) return \"\"\r\n\r\n const md: string[] = []\r\n md.push(\"| \" + uniqueRows[0].join(\" | \") + \" |\")\r\n md.push(\"| \" + uniqueRows[0].map(() => \"---\").join(\" | \") + \" |\")\r\n for (let i = 1; i < uniqueRows.length; i++) {\r\n md.push(\"| \" + uniqueRows[i].join(\" | \") + \" |\")\r\n }\r\n return md.join(\"\\n\")\r\n}\r\n","/**\r\n * HWPX 파서 — manifest 멀티섹션, colSpan/rowSpan, 중첩테이블\r\n *\r\n * lexdiff 기반 + edu-facility-ai 손상ZIP 복구\r\n */\r\n\r\nimport JSZip from \"jszip\"\r\nimport { inflateRawSync } from \"zlib\"\r\nimport { DOMParser } from \"@xmldom/xmldom\"\r\nimport { buildTable, convertTableToText, blocksToMarkdown, MAX_COLS, MAX_ROWS } from \"../table/builder.js\"\r\nimport type { CellContext, IRBlock, DocumentMetadata, InternalParseResult, ParseOptions, ParseWarning, OutlineItem, InlineStyle, ExtractedImage } from \"../types.js\"\r\nimport { HEADING_RATIO_H1, HEADING_RATIO_H2, HEADING_RATIO_H3 } from \"../types.js\"\r\nimport { KordocError, isPathTraversal, sanitizeHref, precheckZipSize, stripDtd } from \"../utils.js\"\r\n// 테스트 호환성 re-export\r\nexport { precheckZipSize } from \"../utils.js\"\r\nimport { parsePageRange } from \"../page-range.js\"\r\n\r\n/** 압축 해제 최대 크기 (100MB) — ZIP bomb 방지 */\r\nconst MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024\r\n/** 손상 ZIP 복구 시 최대 엔트리 수 */\r\nconst MAX_ZIP_ENTRIES = 500\r\n\r\n/** colSpan/rowSpan을 안전한 범위로 클램핑 */\r\nfunction clampSpan(val: number, max: number): number {\r\n return Math.max(1, Math.min(val, max))\r\n}\r\n\r\n/** XML DOM 재귀 최대 깊이 — 악성 파일의 스택 오버플로 방지 */\r\nconst MAX_XML_DEPTH = 200\r\n\r\ninterface TableState { rows: CellContext[][]; currentRow: CellContext[]; cell: CellContext | null }\r\n\r\n/** xmldom DOMParser 생성 — onError 콜백으로 malformed XML 경고 수집 */\r\nfunction createXmlParser(warnings?: ParseWarning[]): DOMParser {\r\n return new DOMParser({\r\n onError(level: \"warn\" | \"error\" | \"fatalError\", msg: string) {\r\n if (level === \"fatalError\") throw new KordocError(`XML 파싱 실패: ${msg}`)\r\n warnings?.push({ code: \"MALFORMED_XML\", message: `XML ${level === \"warn\" ? \"경고\" : \"오류\"}: ${msg}` })\r\n },\r\n })\r\n}\r\n\r\n// ─── HWPX 스타일 정보 ──────────────────────────────\r\n\r\ninterface HwpxCharProperty {\r\n fontSize?: number // 단위: pt (hwpx는 centi-pt → /100)\r\n bold?: boolean\r\n italic?: boolean\r\n fontName?: string\r\n}\r\n\r\ninterface HwpxStyleMap {\r\n charProperties: Map<string, HwpxCharProperty> // id → property\r\n styles: Map<string, { name: string; charPrId?: string; paraPrId?: string }> // id → style\r\n}\r\n\r\n/** head.xml 또는 header.xml에서 스타일 정보 추출 */\r\nasync function extractHwpxStyles(zip: JSZip, decompressed?: { total: number }): Promise<HwpxStyleMap> {\r\n const result: HwpxStyleMap = {\r\n charProperties: new Map(),\r\n styles: new Map(),\r\n }\r\n\r\n const headerPaths = [\"Contents/header.xml\", \"header.xml\", \"Contents/head.xml\", \"head.xml\"]\r\n for (const hp of headerPaths) {\r\n const hpLower = hp.toLowerCase()\r\n const file = zip.file(hp) || Object.values(zip.files).find(f => f.name.toLowerCase() === hpLower) || null\r\n if (!file) continue\r\n\r\n try {\r\n const xml = await file.async(\"text\")\r\n if (decompressed) {\r\n decompressed.total += xml.length * 2\r\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\r\n }\r\n const parser = createXmlParser()\r\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\r\n if (!doc.documentElement) continue\r\n\r\n // charProperties 파싱\r\n parseCharProperties(doc, result.charProperties)\r\n // styles 파싱\r\n parseStyleElements(doc, result.styles)\r\n break\r\n } catch { continue }\r\n }\r\n\r\n return result\r\n}\r\n\r\nfunction parseCharProperties(doc: Document, map: Map<string, HwpxCharProperty>): void {\r\n // <hh:charPr> 또는 <charPr> 요소 탐색\r\n const tagNames = [\"hh:charPr\", \"charPr\", \"hp:charPr\"]\r\n for (const tagName of tagNames) {\r\n const elements = doc.getElementsByTagName(tagName)\r\n for (let i = 0; i < elements.length; i++) {\r\n const el = elements[i]\r\n const id = el.getAttribute(\"id\") || el.getAttribute(\"IDRef\") || \"\"\r\n if (!id) continue\r\n\r\n const prop: HwpxCharProperty = {}\r\n\r\n // height 속성 (centi-pt 단위)\r\n const height = el.getAttribute(\"height\")\r\n if (height) {\r\n const parsedHeight = parseInt(height, 10)\r\n if (!isNaN(parsedHeight) && parsedHeight > 0) {\r\n prop.fontSize = parsedHeight / 100\r\n }\r\n }\r\n\r\n // bold/italic\r\n const bold = el.getAttribute(\"bold\")\r\n if (bold === \"true\" || bold === \"1\") prop.bold = true\r\n const italic = el.getAttribute(\"italic\")\r\n if (italic === \"true\" || italic === \"1\") prop.italic = true\r\n\r\n // 하위 요소에서 fontface 탐색\r\n const fontFaces = el.getElementsByTagName(\"*\")\r\n for (let j = 0; j < fontFaces.length; j++) {\r\n const ff = fontFaces[j]\r\n const localTag = (ff.tagName || \"\").replace(/^[^:]+:/, \"\")\r\n if (localTag === \"fontface\" || localTag === \"fontRef\") {\r\n const face = ff.getAttribute(\"face\") || ff.getAttribute(\"FontFace\")\r\n if (face) { prop.fontName = face; break }\r\n }\r\n }\r\n\r\n map.set(id, prop)\r\n }\r\n }\r\n}\r\n\r\nfunction parseStyleElements(doc: Document, map: Map<string, { name: string; charPrId?: string; paraPrId?: string }>): void {\r\n const tagNames = [\"hh:style\", \"style\", \"hp:style\"]\r\n for (const tagName of tagNames) {\r\n const elements = doc.getElementsByTagName(tagName)\r\n for (let i = 0; i < elements.length; i++) {\r\n const el = elements[i]\r\n const id = el.getAttribute(\"id\") || el.getAttribute(\"IDRef\") || String(i)\r\n const name = el.getAttribute(\"name\") || el.getAttribute(\"engName\") || \"\"\r\n const charPrId = el.getAttribute(\"charPrIDRef\") || undefined\r\n const paraPrId = el.getAttribute(\"paraPrIDRef\") || undefined\r\n map.set(id, { name, charPrId, paraPrId })\r\n }\r\n }\r\n}\r\n\r\n// stripDtd는 utils.js에서 import\r\n\r\nexport async function parseHwpxDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<InternalParseResult> {\r\n // Best-effort 사전 검증 — CD 선언 크기 기반 (위조 가능, 실제 방어는 per-file 누적 체크)\r\n precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES)\r\n\r\n let zip: JSZip\r\n\r\n try {\r\n zip = await JSZip.loadAsync(buffer)\r\n } catch {\r\n return extractFromBrokenZip(buffer)\r\n }\r\n\r\n // loadAsync 후 실제 엔트리 수 검증 — CD 위조와 무관한 진짜 방어선\r\n const actualEntryCount = Object.keys(zip.files).length\r\n if (actualEntryCount > MAX_ZIP_ENTRIES) {\r\n throw new KordocError(\"ZIP 엔트리 수 초과 (ZIP bomb 의심)\")\r\n }\r\n\r\n // ZIP 전체 파일 누적 압축해제 크기 추적 (비섹션 파일 포함)\r\n const decompressed = { total: 0 }\r\n\r\n // 메타데이터 추출 (best-effort)\r\n const metadata: DocumentMetadata = {}\r\n await extractHwpxMetadata(zip, metadata, decompressed)\r\n\r\n // 스타일 정보 추출 (best-effort)\r\n const styleMap = await extractHwpxStyles(zip, decompressed)\r\n const warnings: ParseWarning[] = []\r\n\r\n const sectionPaths = await resolveSectionPaths(zip)\r\n if (sectionPaths.length === 0) throw new KordocError(\"HWPX에서 섹션 파일을 찾을 수 없습니다\")\r\n\r\n metadata.pageCount = sectionPaths.length\r\n\r\n // 페이지 범위 필터링 (섹션 단위 근사치)\r\n const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null\r\n const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length\r\n const blocks: IRBlock[] = []\r\n let parsedSections = 0\r\n for (let si = 0; si < sectionPaths.length; si++) {\r\n if (pageFilter && !pageFilter.has(si + 1)) continue\r\n const file = zip.file(sectionPaths[si])\r\n if (!file) continue\r\n try {\r\n const xml = await file.async(\"text\")\r\n decompressed.total += xml.length * 2\r\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\r\n blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1))\r\n parsedSections++\r\n options?.onProgress?.(parsedSections, totalTarget)\r\n } catch (secErr) {\r\n if (secErr instanceof KordocError) throw secErr\r\n warnings.push({ page: si + 1, message: `섹션 ${si + 1} 파싱 실패: ${secErr instanceof Error ? secErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\r\n }\r\n }\r\n\r\n // 이미지 블록에서 ZIP 바이너리 추출\r\n const images = await extractImagesFromZip(zip, blocks, decompressed, warnings)\r\n\r\n // 스타일 기반 헤딩 감지\r\n detectHwpxHeadings(blocks, styleMap)\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 const markdown = blocksToMarkdown(blocks)\r\n return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined, images: images.length > 0 ? images : undefined }\r\n}\r\n\r\n// ─── 이미지 추출 ───────────────────────────────────\r\n\r\n/** 확장자 → MIME 타입 */\r\nfunction imageExtToMime(ext: string): string {\r\n switch (ext.toLowerCase()) {\r\n case \"jpg\": case \"jpeg\": return \"image/jpeg\"\r\n case \"png\": return \"image/png\"\r\n case \"gif\": return \"image/gif\"\r\n case \"bmp\": return \"image/bmp\"\r\n case \"tif\": case \"tiff\": return \"image/tiff\"\r\n case \"wmf\": return \"image/wmf\"\r\n case \"emf\": return \"image/emf\"\r\n case \"svg\": return \"image/svg+xml\"\r\n default: return \"application/octet-stream\"\r\n }\r\n}\r\n\r\n/** MIME → 확장자 */\r\nfunction mimeToExt(mime: string): string {\r\n if (mime.includes(\"jpeg\")) return \"jpg\"\r\n if (mime.includes(\"png\")) return \"png\"\r\n if (mime.includes(\"gif\")) return \"gif\"\r\n if (mime.includes(\"bmp\")) return \"bmp\"\r\n if (mime.includes(\"tiff\")) return \"tif\"\r\n if (mime.includes(\"wmf\")) return \"wmf\"\r\n if (mime.includes(\"emf\")) return \"emf\"\r\n if (mime.includes(\"svg\")) return \"svg\"\r\n return \"bin\"\r\n}\r\n\r\n/** blocks에서 type=\"image\" 블록의 참조를 ZIP에서 실제 바이너리로 변환 */\r\nasync function extractImagesFromZip(\r\n zip: JSZip,\r\n blocks: IRBlock[],\r\n decompressed: { total: number },\r\n warnings?: ParseWarning[],\r\n): Promise<ExtractedImage[]> {\r\n const images: ExtractedImage[] = []\r\n let imageIndex = 0\r\n\r\n for (const block of blocks) {\r\n if (block.type !== \"image\" || !block.text) continue\r\n\r\n const ref = block.text\r\n // BinData/ 폴더 내에서 참조 파일 찾기\r\n const candidates = [\r\n `BinData/${ref}`,\r\n `Contents/BinData/${ref}`,\r\n ref, // 절대 경로일 수도 있음\r\n ]\r\n\r\n let found = false\r\n for (const path of candidates) {\r\n if (isPathTraversal(path)) continue\r\n const file = zip.file(path)\r\n if (!file) continue\r\n\r\n try {\r\n const data = await file.async(\"uint8array\")\r\n decompressed.total += data.length\r\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\r\n\r\n const ext = ref.includes(\".\") ? (ref.split(\".\").pop() || \"png\") : \"png\"\r\n const mimeType = imageExtToMime(ext)\r\n imageIndex++\r\n const filename = `image_${String(imageIndex).padStart(3, \"0\")}.${mimeToExt(mimeType)}`\r\n\r\n images.push({ filename, data, mimeType })\r\n // 블록 텍스트를 참조 파일명으로 교체\r\n block.text = filename\r\n block.imageData = { data, mimeType, filename: ref }\r\n found = true\r\n break\r\n } catch (err) {\r\n if (err instanceof KordocError) throw err\r\n // 개별 이미지 실패는 경고로 처리\r\n }\r\n }\r\n\r\n if (!found) {\r\n warnings?.push({ page: block.pageNumber, message: `이미지 파일 없음: ${ref}`, code: \"SKIPPED_IMAGE\" })\r\n // image 블록을 paragraph로 전환 (참조만 남김)\r\n block.type = \"paragraph\"\r\n block.text = `[이미지: ${ref}]`\r\n }\r\n }\r\n\r\n return images\r\n}\r\n\r\n// ─── 메타데이터 추출 (best-effort) ───────────────────\r\n\r\n/**\r\n * HWPX ZIP 내 메타데이터 파일에서 Dublin Core 정보 추출.\r\n * 표준 경로: meta.xml, docProps/core.xml, META-INF/container.xml\r\n */\r\nasync function extractHwpxMetadata(zip: JSZip, metadata: DocumentMetadata, decompressed?: { total: number }): Promise<void> {\r\n try {\r\n // meta.xml (HWPX 표준) 또는 docProps/core.xml (OOXML 호환)\r\n const metaPaths = [\"meta.xml\", \"META-INF/meta.xml\", \"docProps/core.xml\"]\r\n for (const mp of metaPaths) {\r\n const file = zip.file(mp) || Object.values(zip.files).find(f => f.name.toLowerCase() === mp.toLowerCase()) || null\r\n if (!file) continue\r\n const xml = await file.async(\"text\")\r\n if (decompressed) {\r\n decompressed.total += xml.length * 2\r\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\r\n }\r\n parseDublinCoreMetadata(xml, metadata)\r\n if (metadata.title || metadata.author) return\r\n }\r\n } catch {\r\n // best-effort\r\n }\r\n}\r\n\r\n/** Dublin Core (dc:) 메타데이터 XML 파싱 */\r\nfunction parseDublinCoreMetadata(xml: string, metadata: DocumentMetadata): void {\r\n const parser = createXmlParser()\r\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\r\n if (!doc.documentElement) return\r\n\r\n const getText = (tagNames: string[]): string | undefined => {\r\n for (const tag of tagNames) {\r\n const els = doc.getElementsByTagName(tag)\r\n if (els.length > 0) {\r\n const text = els[0].textContent?.trim()\r\n if (text) return text\r\n }\r\n }\r\n return undefined\r\n }\r\n\r\n metadata.title = metadata.title || getText([\"dc:title\", \"title\"])\r\n metadata.author = metadata.author || getText([\"dc:creator\", \"creator\", \"cp:lastModifiedBy\"])\r\n metadata.description = metadata.description || getText([\"dc:description\", \"description\", \"dc:subject\", \"subject\"])\r\n metadata.createdAt = metadata.createdAt || getText([\"dcterms:created\", \"meta:creation-date\"])\r\n metadata.modifiedAt = metadata.modifiedAt || getText([\"dcterms:modified\", \"meta:date\"])\r\n\r\n const keywords = getText([\"dc:keyword\", \"cp:keywords\", \"meta:keyword\"])\r\n if (keywords && !metadata.keywords) {\r\n metadata.keywords = keywords.split(/[,;]/).map(k => k.trim()).filter(Boolean)\r\n }\r\n}\r\n\r\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\r\nexport async function extractHwpxMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\r\n let zip: JSZip\r\n try {\r\n zip = await JSZip.loadAsync(buffer)\r\n } catch {\r\n throw new KordocError(\"HWPX ZIP을 열 수 없습니다\")\r\n }\r\n\r\n const metadata: DocumentMetadata = {}\r\n await extractHwpxMetadata(zip, metadata)\r\n\r\n const sectionPaths = await resolveSectionPaths(zip)\r\n metadata.pageCount = sectionPaths.length\r\n\r\n return metadata\r\n}\r\n\r\n// ─── 손상 ZIP 복구 (edu-facility-ai에서 포팅) ──────────\r\n\r\nfunction extractFromBrokenZip(buffer: ArrayBuffer): InternalParseResult {\r\n const data = new Uint8Array(buffer)\r\n const view = new DataView(buffer)\r\n let pos = 0\r\n const blocks: IRBlock[] = []\r\n const warnings: ParseWarning[] = [\r\n { code: \"BROKEN_ZIP_RECOVERY\", message: \"손상된 ZIP 구조 — Local File Header 기반 복구 모드\" },\r\n ]\r\n let totalDecompressed = 0\r\n let entryCount = 0\r\n let sectionNum = 0\r\n\r\n while (pos < data.length - 30) {\r\n // PK\\x03\\x04 시그니처 확인 — 미매칭 시 다음 PK 시그니처까지 스캔 (중간 손상 복구)\r\n if (data[pos] !== 0x50 || data[pos + 1] !== 0x4b || data[pos + 2] !== 0x03 || data[pos + 3] !== 0x04) {\r\n pos++\r\n while (pos < data.length - 30) {\r\n if (data[pos] === 0x50 && data[pos + 1] === 0x4b && data[pos + 2] === 0x03 && data[pos + 3] === 0x04) break\r\n pos++\r\n }\r\n continue\r\n }\r\n\r\n if (++entryCount > MAX_ZIP_ENTRIES) break\r\n\r\n const method = view.getUint16(pos + 8, true)\r\n const compSize = view.getUint32(pos + 18, true)\r\n const nameLen = view.getUint16(pos + 26, true)\r\n const extraLen = view.getUint16(pos + 28, true)\r\n\r\n // nameLen 상한 — 비정상 값에 의한 대규모 버퍼 할당 방지\r\n if (nameLen > 1024 || extraLen > 65535) { pos += 30 + nameLen + extraLen; continue }\r\n\r\n const fileStart = pos + 30 + nameLen + extraLen\r\n // 범위 초과 검증 — OOB 및 무한 루프 방지\r\n if (fileStart + compSize > data.length) break\r\n if (compSize === 0 && method !== 0) { pos = fileStart; continue }\r\n\r\n const nameBytes = data.slice(pos + 30, pos + 30 + nameLen)\r\n const name = new TextDecoder().decode(nameBytes)\r\n\r\n // 경로 순회 방지 — 상위 디렉토리 참조 및 절대 경로 차단\r\n if (isPathTraversal(name)) { pos = fileStart + compSize; continue }\r\n const fileData = data.slice(fileStart, fileStart + compSize)\r\n pos = fileStart + compSize\r\n\r\n if (!name.toLowerCase().includes(\"section\") || !name.endsWith(\".xml\")) continue\r\n\r\n try {\r\n let content: string\r\n if (method === 0) {\r\n content = new TextDecoder().decode(fileData)\r\n } else if (method === 8) {\r\n const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE })\r\n content = new TextDecoder().decode(decompressed)\r\n } else {\r\n continue\r\n }\r\n totalDecompressed += content.length * 2\r\n if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError(\"압축 해제 크기 초과\")\r\n sectionNum++\r\n blocks.push(...parseSectionXml(content, undefined, warnings, sectionNum))\r\n } catch {\r\n continue\r\n }\r\n }\r\n\r\n if (blocks.length === 0) throw new KordocError(\"손상된 HWPX에서 섹션 데이터를 복구할 수 없습니다\")\r\n const markdown = blocksToMarkdown(blocks)\r\n return { markdown, blocks, warnings: warnings.length > 0 ? warnings : undefined }\r\n}\r\n\r\n// ─── Manifest 해석 ───────────────────────────────────\r\n\r\nasync function resolveSectionPaths(zip: JSZip): Promise<string[]> {\r\n const manifestPaths = [\"Contents/content.hpf\", \"content.hpf\"]\r\n for (const mp of manifestPaths) {\r\n const mpLower = mp.toLowerCase()\r\n const file = zip.file(mp) || Object.values(zip.files).find(f => f.name.toLowerCase() === mpLower) || null\r\n if (!file) continue\r\n const xml = await file.async(\"text\")\r\n const paths = parseSectionPathsFromManifest(xml)\r\n if (paths.length > 0) return paths\r\n }\r\n\r\n // fallback: section*.xml 직접 검색\r\n const sectionFiles = zip.file(/[Ss]ection\\d+\\.xml$/)\r\n return sectionFiles.map(f => f.name).sort()\r\n}\r\n\r\nfunction parseSectionPathsFromManifest(xml: string): string[] {\r\n const parser = createXmlParser()\r\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\r\n const items = doc.getElementsByTagName(\"opf:item\")\r\n const spine = doc.getElementsByTagName(\"opf:itemref\")\r\n\r\n const isSectionId = (id: string) => /^s/i.test(id) || id.toLowerCase().includes(\"section\")\r\n const idToHref = new Map<string, string>()\r\n for (let i = 0; i < items.length; i++) {\r\n const item = items[i]\r\n const id = item.getAttribute(\"id\") || \"\"\r\n let href = item.getAttribute(\"href\") || \"\"\r\n const mediaType = item.getAttribute(\"media-type\") || \"\"\r\n if (!isSectionId(id) && !mediaType.includes(\"xml\")) continue\r\n if (!href.startsWith(\"/\") && !href.startsWith(\"Contents/\") && isSectionId(id))\r\n href = \"Contents/\" + href\r\n idToHref.set(id, href)\r\n }\r\n\r\n if (spine.length > 0) {\r\n const ordered: string[] = []\r\n for (let i = 0; i < spine.length; i++) {\r\n const href = idToHref.get(spine[i].getAttribute(\"idref\") || \"\")\r\n if (href) ordered.push(href)\r\n }\r\n if (ordered.length > 0) return ordered\r\n }\r\n return Array.from(idToHref.entries())\r\n .filter(([id]) => isSectionId(id))\r\n .sort((a, b) => a[0].localeCompare(b[0]))\r\n .map(([, href]) => href)\r\n}\r\n\r\n// ─── 헤딩 감지 (스타일 기반) ────────────────────────\r\n\r\n/** HWPX 스타일 기반 헤딩 감지 */\r\nfunction detectHwpxHeadings(blocks: IRBlock[], styleMap: HwpxStyleMap): void {\r\n // 본문 폰트 크기 결정\r\n let baseFontSize = 0\r\n const sizeFreq = new Map<number, number>()\r\n for (const b of blocks) {\r\n if (b.style?.fontSize) {\r\n sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1)\r\n }\r\n }\r\n let maxCount = 0\r\n for (const [size, count] of sizeFreq) {\r\n if (count > maxCount) { maxCount = count; baseFontSize = size }\r\n }\r\n\r\n for (const block of blocks) {\r\n if (block.type !== \"paragraph\" || !block.text) continue\r\n const text = block.text.trim()\r\n if (text.length === 0 || text.length > 200 || /^\\d+$/.test(text)) continue\r\n\r\n let level = 0\r\n\r\n // 폰트 크기 기반\r\n if (baseFontSize > 0 && block.style?.fontSize) {\r\n const ratio = block.style.fontSize / baseFontSize\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\r\n // \"제N조/장/절\" 패턴 — 균등배분 공백 허용 (\"제 1 장\" → \"제1장\")\r\n const compactText = text.replace(/\\s+/g, \"\")\r\n if (/^제\\d+[조장절편]/.test(compactText) && text.length <= 50) {\r\n if (level === 0) level = 3\r\n }\r\n\r\n if (level > 0) {\r\n block.type = \"heading\"\r\n block.level = level\r\n }\r\n }\r\n}\r\n\r\n// ─── 섹션 XML 파싱 ──────────────────────────────────\r\n\r\nfunction parseSectionXml(xml: string, styleMap?: HwpxStyleMap, warnings?: ParseWarning[], sectionNum?: number): IRBlock[] {\r\n const parser = createXmlParser(warnings)\r\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\r\n if (!doc.documentElement) return []\r\n\r\n const blocks: IRBlock[] = []\r\n walkSection(doc.documentElement, blocks, null, [], styleMap, warnings, sectionNum)\r\n return blocks\r\n}\r\n\r\n/** pic/shape 요소에서 이미지 참조 경로 추출 (binaryItemIDRef 또는 href) */\r\nfunction extractImageRef(el: Element): string | null {\r\n // HWPX: <hp:imgRect> 또는 <hp:img> 내 binaryItemIDRef 속성\r\n // 또는 하위에서 img 관련 속성 탐색\r\n const children = el.childNodes\r\n if (!children) return null\r\n for (let i = 0; i < children.length; i++) {\r\n const child = children[i] as Element\r\n if (child.nodeType !== 1) continue\r\n const tag = (child.tagName || child.localName || \"\").replace(/^[^:]+:/, \"\")\r\n if (tag === \"imgRect\" || tag === \"img\" || tag === \"imgClip\") {\r\n const ref = child.getAttribute(\"binaryItemIDRef\") || child.getAttribute(\"href\") || \"\"\r\n if (ref) return ref\r\n }\r\n // lineShape > imgRect 같은 중첩 구조\r\n const nested = extractImageRef(child)\r\n if (nested) return nested\r\n }\r\n // 직접 속성 체크\r\n const directRef = el.getAttribute(\"binaryItemIDRef\") || \"\"\r\n if (directRef) return directRef\r\n return null\r\n}\r\n\r\nfunction walkSection(\r\n node: Node, blocks: IRBlock[],\r\n tableCtx: TableState | null, tableStack: TableState[],\r\n styleMap?: HwpxStyleMap, warnings?: ParseWarning[], sectionNum?: number,\r\n depth: number = 0\r\n): void {\r\n if (depth > MAX_XML_DEPTH) return\r\n const children = node.childNodes\r\n if (!children) return\r\n\r\n for (let i = 0; i < children.length; i++) {\r\n const el = children[i] as Element\r\n if (el.nodeType !== 1) continue\r\n\r\n const tag = el.tagName || el.localName || \"\"\r\n const localTag = tag.replace(/^[^:]+:/, \"\")\r\n\r\n switch (localTag) {\r\n case \"tbl\": {\r\n if (tableCtx) tableStack.push(tableCtx)\r\n const newTable: TableState = { rows: [], currentRow: [], cell: null }\r\n walkSection(el, blocks, newTable, tableStack, styleMap, warnings, sectionNum, depth + 1)\r\n\r\n if (newTable.rows.length > 0) {\r\n if (tableStack.length > 0) {\r\n const parentTable = tableStack.pop()!\r\n // 중첩 표가 충분히 크면 (3행+, 2열+) 별도 블록으로 분리\r\n let nestedCols = 0; for (const r of newTable.rows) if (r.length > nestedCols) nestedCols = r.length\r\n if (newTable.rows.length >= 3 && nestedCols >= 2) {\r\n blocks.push({ type: \"table\", table: buildTable(newTable.rows), pageNumber: sectionNum })\r\n } else {\r\n const nestedText = convertTableToText(newTable.rows)\r\n if (parentTable.cell) {\r\n parentTable.cell.text += (parentTable.cell.text ? \"\\n\" : \"\") + nestedText\r\n }\r\n }\r\n tableCtx = parentTable\r\n } else {\r\n blocks.push({ type: \"table\", table: buildTable(newTable.rows), pageNumber: sectionNum })\r\n tableCtx = null\r\n }\r\n } else {\r\n tableCtx = tableStack.length > 0 ? tableStack.pop()! : null\r\n }\r\n break\r\n }\r\n\r\n case \"tr\":\r\n if (tableCtx) {\r\n tableCtx.currentRow = []\r\n walkSection(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\r\n if (tableCtx.currentRow.length > 0) tableCtx.rows.push(tableCtx.currentRow)\r\n tableCtx.currentRow = []\r\n }\r\n break\r\n\r\n case \"tc\":\r\n if (tableCtx) {\r\n tableCtx.cell = { text: \"\", colSpan: 1, rowSpan: 1 }\r\n walkSection(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\r\n if (tableCtx.cell) {\r\n tableCtx.currentRow.push(tableCtx.cell)\r\n tableCtx.cell = null\r\n }\r\n }\r\n break\r\n\r\n case \"cellAddr\":\r\n if (tableCtx?.cell) {\r\n const ca = parseInt(el.getAttribute(\"colAddr\") || \"\", 10)\r\n const ra = parseInt(el.getAttribute(\"rowAddr\") || \"\", 10)\r\n if (!isNaN(ca)) tableCtx.cell.colAddr = ca\r\n if (!isNaN(ra)) tableCtx.cell.rowAddr = ra\r\n }\r\n break\r\n\r\n case \"cellSpan\":\r\n if (tableCtx?.cell) {\r\n const rawCs = parseInt(el.getAttribute(\"colSpan\") || \"1\", 10)\r\n const cs = isNaN(rawCs) ? 1 : rawCs\r\n const rawRs = parseInt(el.getAttribute(\"rowSpan\") || \"1\", 10)\r\n const rs = isNaN(rawRs) ? 1 : rawRs\r\n tableCtx.cell.colSpan = clampSpan(cs, MAX_COLS)\r\n tableCtx.cell.rowSpan = clampSpan(rs, MAX_ROWS)\r\n }\r\n break\r\n\r\n case \"p\": {\r\n const { text, href, footnote, style } = extractParagraphInfo(el, styleMap)\r\n if (text) {\r\n if (tableCtx?.cell) {\r\n tableCtx.cell.text += (tableCtx.cell.text ? \"\\n\" : \"\") + text\r\n } else if (!tableCtx) {\r\n const block: IRBlock = { type: \"paragraph\", text, pageNumber: sectionNum }\r\n if (style) block.style = style\r\n if (href) block.href = href\r\n if (footnote) block.footnoteText = footnote\r\n blocks.push(block)\r\n }\r\n }\r\n // <p> 내부의 <tbl>만 별도 처리 — extractParagraphInfo가 이미 텍스트를 추출했으므로\r\n // 전체 walkSection 재귀 대신 테이블/이미지 자식만 선택적으로 처리\r\n tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\r\n break\r\n }\r\n\r\n // 이미지/그림 — 경로 추출 또는 경고\r\n case \"pic\": case \"shape\": case \"drawingObject\": {\r\n const imgRef = extractImageRef(el)\r\n if (imgRef) {\r\n blocks.push({ type: \"image\", text: imgRef, pageNumber: sectionNum })\r\n } else if (warnings && sectionNum) {\r\n warnings.push({ page: sectionNum, message: `스킵된 요소: ${localTag}`, code: \"SKIPPED_IMAGE\" })\r\n }\r\n break\r\n }\r\n\r\n default:\r\n walkSection(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\r\n break\r\n }\r\n }\r\n}\r\n\r\n/** <p> 내부에서 텍스트가 아닌 구조적 자식만 처리 (tbl, pic, shape). tableCtx 반환으로 상태 전파 */\r\nfunction walkParagraphChildren(\r\n node: Node, blocks: IRBlock[],\r\n tableCtx: TableState | null, tableStack: TableState[],\r\n styleMap?: HwpxStyleMap, warnings?: ParseWarning[], sectionNum?: number,\r\n depth: number = 0\r\n): TableState | null {\r\n if (depth > MAX_XML_DEPTH) return tableCtx\r\n const children = node.childNodes\r\n if (!children) return tableCtx\r\n const walkChildren = (parent: Node, d: number) => {\r\n if (d > MAX_XML_DEPTH) return\r\n const kids = parent.childNodes\r\n if (!kids) return\r\n for (let i = 0; i < kids.length; i++) {\r\n const el = kids[i] as Element\r\n if (el.nodeType !== 1) continue\r\n const tag = el.tagName || el.localName || \"\"\r\n const localTag = tag.replace(/^[^:]+:/, \"\")\r\n\r\n if (localTag === \"tbl\") {\r\n // 테이블은 walkSection으로 위임\r\n if (tableCtx) tableStack.push(tableCtx)\r\n const newTable: TableState = { rows: [], currentRow: [], cell: null }\r\n walkSection(el, blocks, newTable, tableStack, styleMap, warnings, sectionNum, d + 1)\r\n if (newTable.rows.length > 0) {\r\n if (tableStack.length > 0) {\r\n const parentTable = tableStack.pop()!\r\n let nestedCols = 0; for (const r of newTable.rows) if (r.length > nestedCols) nestedCols = r.length\r\n if (newTable.rows.length >= 3 && nestedCols >= 2) {\r\n blocks.push({ type: \"table\", table: buildTable(newTable.rows), pageNumber: sectionNum })\r\n } else {\r\n const nestedText = convertTableToText(newTable.rows)\r\n if (parentTable.cell) {\r\n parentTable.cell.text += (parentTable.cell.text ? \"\\n\" : \"\") + nestedText\r\n }\r\n }\r\n tableCtx = parentTable\r\n } else {\r\n blocks.push({ type: \"table\", table: buildTable(newTable.rows), pageNumber: sectionNum })\r\n tableCtx = null\r\n }\r\n } else {\r\n tableCtx = tableStack.length > 0 ? tableStack.pop()! : null\r\n }\r\n } else if (localTag === \"pic\" || localTag === \"shape\" || localTag === \"drawingObject\") {\r\n // 도형/이미지 안에 drawText(글상자)가 있으면 텍스트 추출 우선\r\n const drawTextChild = findDescendant(el, \"drawText\")\r\n if (drawTextChild) {\r\n extractDrawTextBlocks(drawTextChild, blocks, styleMap, sectionNum)\r\n } else {\r\n const imgRef = extractImageRef(el)\r\n if (imgRef) {\r\n blocks.push({ type: \"image\", text: imgRef, pageNumber: sectionNum })\r\n } else if (warnings && sectionNum) {\r\n warnings.push({ page: sectionNum, message: `스킵된 요소: ${localTag}`, code: \"SKIPPED_IMAGE\" })\r\n }\r\n }\r\n } else if (localTag === \"drawText\") {\r\n // 글상자(TextBox) 안 텍스트 추출 — <hp:p> 순회\r\n extractDrawTextBlocks(el, blocks, styleMap, sectionNum)\r\n } else if (localTag === \"r\" || localTag === \"run\" || localTag === \"ctrl\"\r\n || localTag === \"rect\" || localTag === \"ellipse\" || localTag === \"polygon\"\r\n || localTag === \"line\" || localTag === \"arc\" || localTag === \"curve\"\r\n || localTag === \"connectLine\" || localTag === \"container\") {\r\n // <hp:run>, <hp:ctrl>, 도형 요소 내부에 테이블/이미지/글상자가 포함될 수 있음 — 재귀\r\n walkChildren(el, d + 1)\r\n } else if (localTag === \"run\") {\r\n tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\r\n }\r\n }\r\n }\r\n walkChildren(node, depth)\r\n return tableCtx\r\n}\r\n\r\n/** 자손에서 특정 태그명의 첫 번째 요소 탐색 (최대 깊이 5) */\r\nfunction findDescendant(node: Node, targetTag: string, depth = 0): Element | null {\r\n if (depth > 5) return null\r\n const children = node.childNodes\r\n if (!children) return null\r\n for (let i = 0; i < children.length; i++) {\r\n const child = children[i] as Element\r\n if (child.nodeType !== 1) continue\r\n const tag = (child.tagName || child.localName || \"\").replace(/^[^:]+:/, \"\")\r\n if (tag === targetTag) return child\r\n const found = findDescendant(child, targetTag, depth + 1)\r\n if (found) return found\r\n }\r\n return null\r\n}\r\n\r\n/** drawText(글상자) 내부의 <p> 요소들에서 텍스트를 추출하여 paragraph 블록 생성 */\r\nfunction extractDrawTextBlocks(drawTextNode: Node, blocks: IRBlock[], styleMap?: HwpxStyleMap, sectionNum?: number): void {\r\n const children = drawTextNode.childNodes\r\n if (!children) return\r\n for (let i = 0; i < children.length; i++) {\r\n const child = children[i] as Element\r\n if (child.nodeType !== 1) continue\r\n const tag = (child.tagName || child.localName || \"\").replace(/^[^:]+:/, \"\")\r\n if (tag === \"subList\" || tag === \"p\" || tag === \"para\") {\r\n // subList 안의 <p>들을 순회\r\n if (tag === \"subList\") {\r\n extractDrawTextBlocks(child, blocks, styleMap, sectionNum)\r\n } else {\r\n const info = extractParagraphInfo(child, styleMap)\r\n const text = info.text.trim()\r\n if (text) {\r\n blocks.push({ type: \"paragraph\", text, style: info.style ?? undefined, pageNumber: sectionNum })\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\ninterface ParagraphInfo {\r\n text: string\r\n href?: string\r\n footnote?: string\r\n style?: InlineStyle\r\n}\r\n\r\nfunction extractParagraphInfo(para: Element, styleMap?: HwpxStyleMap): ParagraphInfo {\r\n let text = \"\"\r\n let href: string | undefined\r\n let footnote: string | undefined\r\n let charPrId: string | undefined\r\n\r\n // 문단의 스타일 참조 → charPr로 간접 조회\r\n // HWPX <p>에는 paraPrIDRef/styleIDRef가 있고, charPrIDRef는 <r> 요소에 있음\r\n // 여기서는 일단 null — <r> 요소에서 charPrIDRef를 가져옴\r\n\r\n const walk = (node: Node) => {\r\n const children = node.childNodes\r\n if (!children) return\r\n for (let i = 0; i < children.length; i++) {\r\n const child = children[i] as Element\r\n if (child.nodeType === 3) { text += child.textContent || \"\"; continue }\r\n if (child.nodeType !== 1) continue\r\n\r\n const tag = (child.tagName || child.localName || \"\").replace(/^[^:]+:/, \"\")\r\n switch (tag) {\r\n case \"t\": walk(child); break // 자식 순회 (tab 등 하위 요소 처리)\r\n case \"tab\": {\r\n const leader = child.getAttribute(\"leader\")\r\n if (leader && leader !== \"0\") {\r\n // 목차 리더 탭 (점선/실선 등) — 뒤에 페이지번호가 오므로 이후 텍스트 무시\r\n text += \"\\x1F\" // 특수 마커: 이후 텍스트 제거용\r\n } else {\r\n text += \"\\t\"\r\n }\r\n break\r\n }\r\n case \"br\":\r\n if ((child.getAttribute(\"type\") || \"line\") === \"line\") text += \"\\n\"\r\n break\r\n case \"fwSpace\": case \"hwSpace\": text += \" \"; break\r\n case \"tbl\": break // 테이블은 walkSection에서 처리\r\n\r\n // 하이퍼링크\r\n case \"hyperlink\": {\r\n const url = child.getAttribute(\"url\") || child.getAttribute(\"href\") || \"\"\r\n if (url) {\r\n // XSS 방지: 추출 시점에서 href 살균\r\n const safe = sanitizeHref(url)\r\n if (safe) href = safe\r\n }\r\n // 하이퍼링크 내 텍스트 추출\r\n walk(child)\r\n break\r\n }\r\n\r\n // 각주/미주\r\n case \"footNote\": case \"endNote\": case \"fn\": case \"en\": {\r\n const noteText = extractTextFromNode(child)\r\n if (noteText) footnote = (footnote ? footnote + \"; \" : \"\") + noteText\r\n break\r\n }\r\n\r\n // 제어 요소 — 필드, 컨트롤, 매개변수 등 스킵\r\n case \"ctrl\": case \"fieldBegin\": case \"fieldEnd\":\r\n case \"parameters\": case \"stringParam\": case \"integerParam\":\r\n case \"boolParam\": case \"floatParam\":\r\n case \"secPr\": // 섹션 속성 (페이지 설정 등)\r\n case \"colPr\": // 다단 속성\r\n case \"linesegarray\": case \"lineseg\": // 레이아웃 정보\r\n // 도형/이미지 요소 — 대체텍스트(\"사각형입니다.\" 등) 누출 방지\r\n case \"pic\": case \"shape\": case \"drawingObject\":\r\n case \"shapeComment\": case \"drawText\":\r\n break\r\n\r\n // run 요소에서 charPrIDRef 추출\r\n case \"r\": {\r\n const runCharPr = child.getAttribute(\"charPrIDRef\")\r\n if (runCharPr && !charPrId) charPrId = runCharPr\r\n walk(child)\r\n break\r\n }\r\n\r\n default: walk(child); break\r\n }\r\n }\r\n }\r\n walk(para)\r\n\r\n // 목차 리더 마커(\\x1F) 이후 텍스트(페이지번호) 제거\r\n const leaderIdx = text.indexOf(\"\\x1F\")\r\n if (leaderIdx >= 0) text = text.substring(0, leaderIdx)\r\n\r\n let cleanText = text.replace(/[ \\t]+/g, \" \").trim()\r\n\r\n // 한글 이미지 OLE 대체 텍스트 필터링 (\"그림입니다. 원본 그림의 이름: ...\")\r\n if (/^그림입니다\\.?\\s*원본\\s*그림의\\s*(이름|크기)/.test(cleanText)) cleanText = \"\"\r\n // 멀티라인으로 삽입된 OLE 대체 텍스트도 제거\r\n cleanText = cleanText.replace(/그림입니다\\.?\\s*원본\\s*그림의\\s*(이름|크기)[^\\n]*(\\n[^\\n]*원본\\s*그림의\\s*(이름|크기)[^\\n]*)*/g, \"\").trim()\r\n // HWP 도형/개체 대체텍스트 제거 (\"사각형입니다.\", \"개체 입니다.\" 등)\r\n cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\\s?개체|묶음\\s?개체|글상자|수식|표|그림|OLE\\s?개체)\\s?입니다\\.?/g, \"\").trim()\r\n\r\n // 스타일 정보 조회\r\n let style: InlineStyle | undefined\r\n if (styleMap && charPrId) {\r\n const charProp = styleMap.charProperties.get(charPrId)\r\n if (charProp) {\r\n style = {}\r\n if (charProp.fontSize) style.fontSize = charProp.fontSize\r\n if (charProp.bold) style.bold = true\r\n if (charProp.italic) style.italic = true\r\n if (charProp.fontName) style.fontName = charProp.fontName\r\n if (!style.fontSize && !style.bold && !style.italic) style = undefined\r\n }\r\n }\r\n\r\n return { text: cleanText, href, footnote, style }\r\n}\r\n\r\n/** 노드 내 모든 텍스트를 재귀적으로 추출 */\r\nfunction extractTextFromNode(node: Node): string {\r\n let result = \"\"\r\n const children = node.childNodes\r\n if (!children) return result\r\n for (let i = 0; i < children.length; i++) {\r\n const child = children[i]\r\n if (child.nodeType === 3) result += child.textContent || \"\"\r\n else if (child.nodeType === 1) result += extractTextFromNode(child)\r\n }\r\n return result.trim()\r\n}\r\n","/** kordoc 공통 타입 정의 */\n\n// ─── 중간 표현 (Intermediate Representation) ─────────\n\nexport interface CellContext {\n text: string\n colSpan: number\n rowSpan: number\n /** HWP5 셀 열 주소 (0-based) — 병합 테이블 배치용 */\n colAddr?: number\n /** HWP5 셀 행 주소 (0-based) — 병합 테이블 배치용 */\n rowAddr?: number\n}\n\n/** 블록 타입 — v2.0에서 heading, list, image, separator 추가 */\nexport type IRBlockType = \"paragraph\" | \"table\" | \"heading\" | \"list\" | \"image\" | \"separator\"\n\nexport interface IRBlock {\n type: IRBlockType\n text?: string\n table?: IRTable\n /** 헤딩 레벨 (1-6), type=\"heading\"일 때 사용 */\n level?: number\n /** 원본 페이지 번호 (1-based) */\n pageNumber?: number\n /** 바운딩 박스 — PDF에서만 제공 */\n bbox?: BoundingBox\n /** 텍스트 스타일 정보 (선택) */\n style?: InlineStyle\n /** 리스트 타입, type=\"list\"일 때 사용 */\n listType?: \"ordered\" | \"unordered\"\n /** 중첩 리스트 아이템 */\n children?: IRBlock[]\n /** 하이퍼링크 URL */\n href?: string\n /** 각주/미주 텍스트 (인라인 삽입용) */\n footnoteText?: string\n /** 이미지 데이터 (type=\"image\"일 때) */\n imageData?: ImageData\n}\n\n/** 추출된 이미지 바이너리 데이터 */\nexport interface ImageData {\n /** 이미지 바이너리 */\n data: Uint8Array\n /** MIME 타입 (image/png, image/jpeg, image/gif, image/bmp, image/wmf, image/emf) */\n mimeType: string\n /** 원본 파일명 (있는 경우) */\n filename?: string\n}\n\n/** 바운딩 박스 — PDF 포인트 단위 (72pt = 1인치) */\nexport interface BoundingBox {\n page: number\n x: number\n y: number\n width: number\n height: number\n}\n\n/** 인라인 텍스트 스타일 */\nexport interface InlineStyle {\n bold?: boolean\n italic?: boolean\n fontSize?: number\n fontName?: string\n}\n\nexport interface IRTable {\n rows: number\n cols: number\n cells: IRCell[][]\n /** 첫 행을 헤더로 렌더링할지 여부 (현재: rows > 1이면 true — 의미적 감지가 아닌 레이아웃 힌트) */\n hasHeader: boolean\n}\n\nexport interface IRCell {\n text: string\n colSpan: number\n rowSpan: number\n}\n\n// ─── 메타데이터 ─────────────────────────────────────\n\n/** 문서 메타데이터 — 각 포맷에서 추출 가능한 필드만 채워짐 */\nexport interface DocumentMetadata {\n /** 문서 제목 */\n title?: string\n /** 작성자 */\n author?: string\n /** 작성 프로그램 (예: \"한글 2020\", \"Adobe Acrobat\") */\n creator?: string\n /** 생성일시 (ISO 8601) */\n createdAt?: string\n /** 수정일시 (ISO 8601) */\n modifiedAt?: string\n /** 페이지/섹션 수 */\n pageCount?: number\n /** 문서 포맷 버전 (예: HWP \"5.1.0.1\") */\n version?: string\n /** 설명 */\n description?: string\n /** 키워드 */\n keywords?: string[]\n}\n\n// ─── 파싱 옵션 ──────────────────────────────────────\n\n/** 파싱 옵션 — parse() 함수에 전달 */\nexport interface ParseOptions {\n /**\n * 파싱할 페이지/섹션 범위 (1-based).\n * - 배열: [1, 2, 3]\n * - 문자열: \"1-3\", \"1,3,5-7\"\n *\n * PDF: 정확한 페이지 단위. HWP/HWPX: 섹션 단위 근사치.\n */\n pages?: number[] | string\n /** 이미지 기반 PDF용 OCR 프로바이더 (선택) */\n ocr?: OcrProvider\n /** 진행률 콜백 — current: 현재 페이지/섹션, total: 전체 수 */\n onProgress?: (current: number, total: number) => void\n /** PDF 머리글/바닥글 자동 제거 */\n removeHeaderFooter?: boolean\n}\n\n// ─── 파싱 경고 ──────────────────────────────────────\n\n/** 파싱 중 스킵/실패한 요소 보고 */\nexport interface ParseWarning {\n /** 관련 페이지 번호 (알 수 있는 경우) */\n page?: number\n /** 경고 메시지 */\n message: string\n /** 구조화된 경고 코드 */\n code: WarningCode\n}\n\nexport type WarningCode =\n | \"SKIPPED_IMAGE\"\n | \"SKIPPED_OLE\"\n | \"TRUNCATED_TABLE\"\n | \"OCR_FALLBACK\"\n | \"UNSUPPORTED_ELEMENT\"\n | \"BROKEN_ZIP_RECOVERY\"\n | \"HIDDEN_TEXT_FILTERED\"\n | \"MALFORMED_XML\"\n | \"PARTIAL_PARSE\"\n | \"LENIENT_CFB_RECOVERY\"\n\n/** 문서 구조 (헤딩 트리) */\nexport interface OutlineItem {\n level: number\n text: string\n pageNumber?: number\n}\n\n// ─── 에러 코드 ──────────────────────────────────────\n\n/** 구조화된 에러 코드 — 프로그래밍적 에러 핸들링용 */\nexport type ErrorCode =\n | \"EMPTY_INPUT\"\n | \"UNSUPPORTED_FORMAT\"\n | \"ENCRYPTED\"\n | \"DRM_PROTECTED\"\n | \"CORRUPTED\"\n | \"DECOMPRESSION_BOMB\"\n | \"ZIP_BOMB\"\n | \"IMAGE_BASED_PDF\"\n | \"NO_SECTIONS\"\n | \"PARSE_ERROR\"\n\n// ─── 파싱 결과 (discriminated union) ────────────────\n\nexport type FileType = \"hwpx\" | \"hwp\" | \"pdf\" | \"xlsx\" | \"docx\" | \"unknown\"\n\ninterface ParseResultBase {\n fileType: FileType\n /** 페이지/섹션 수 — PDF: 실제 페이지 수, HWP/HWPX: 섹션 수, XLSX: 시트 수 */\n pageCount?: number\n /** 이미지 기반 PDF 여부 (텍스트 추출 불가) */\n isImageBased?: boolean\n}\n\nexport interface ParseSuccess extends ParseResultBase {\n success: true\n /** 추출된 마크다운 텍스트 */\n markdown: string\n /** 중간 표현 블록 (구조화된 데이터 접근용) */\n blocks: IRBlock[]\n /** 문서 메타데이터 */\n metadata?: DocumentMetadata\n /** 문서 구조 (헤딩 트리) — v2.0 */\n outline?: OutlineItem[]\n /** 파싱 중 발생한 경고 — v2.0 */\n warnings?: ParseWarning[]\n /** 추출된 이미지 목록 — 마크다운에서 파일명으로 참조됨 */\n images?: ExtractedImage[]\n}\n\n/** 추출된 이미지 — ParseSuccess.images에 포함 */\nexport interface ExtractedImage {\n /** 마크다운에서 참조되는 파일명 (예: image_001.png) */\n filename: string\n /** 이미지 바이너리 */\n data: Uint8Array\n /** MIME 타입 */\n mimeType: string\n}\n\nexport interface ParseFailure extends ParseResultBase {\n success: false\n /** 오류 메시지 */\n error: string\n /** 구조화된 에러 코드 */\n code?: ErrorCode\n}\n\nexport type ParseResult = ParseSuccess | ParseFailure\n\n// ─── 문서 비교 (Diff) ───────────────────────────────\n\nexport type DiffChangeType = \"added\" | \"removed\" | \"modified\" | \"unchanged\"\n\nexport interface BlockDiff {\n type: DiffChangeType\n /** 원본 블록 (added이면 undefined) */\n before?: IRBlock\n /** 변경 후 블록 (removed이면 undefined) */\n after?: IRBlock\n /** modified 테이블의 셀 단위 diff */\n cellDiffs?: CellDiff[][]\n /** 유사도 (0-1) */\n similarity?: number\n}\n\nexport interface CellDiff {\n type: DiffChangeType\n before?: string\n after?: string\n}\n\nexport interface DiffResult {\n stats: { added: number; removed: number; modified: number; unchanged: number }\n diffs: BlockDiff[]\n}\n\n// ─── 양식 인식 ──────────────────────────────────────\n\nexport interface FormField {\n label: string\n value: string\n /** 0-based 소스 행 */\n row: number\n /** 0-based 소스 열 */\n col: number\n}\n\nexport interface FormResult {\n fields: FormField[]\n /** 양식 확신도 (0-1) */\n confidence: number\n}\n\n// ─── OCR 프로바이더 ─────────────────────────────────\n\n/** 사용자 제공 OCR 함수 — 페이지 이미지를 받아 텍스트 반환 */\nexport type OcrProvider = (\n pageImage: Uint8Array,\n pageNumber: number,\n mimeType: \"image/png\"\n) => Promise<string>\n\n// ─── Watch 모드 ─────────────────────────────────────\n\nexport interface WatchOptions {\n dir: string\n outDir?: string\n webhook?: string\n format?: \"markdown\" | \"json\"\n pages?: string\n silent?: boolean\n}\n\n// ─── 헤딩 감지 공통 임계값 ──────────────────────────\n\n/** 폰트 크기 비율 → heading level (전 파서 공통) */\nexport const HEADING_RATIO_H1 = 1.5\nexport const HEADING_RATIO_H2 = 1.3\nexport const HEADING_RATIO_H3 = 1.15\n\n// ─── 내부 파서 반환 타입 ─────────────────────────────\n\n/** 내부 파서가 index.ts에 반환하는 공통 타입 (HWP5/HWPX/PDF/XLSX/DOCX) */\nexport interface InternalParseResult {\n markdown: string\n blocks: IRBlock[]\n metadata?: DocumentMetadata\n outline?: OutlineItem[]\n warnings?: ParseWarning[]\n images?: ExtractedImage[]\n /** PDF 전용: 이미지 기반 PDF 여부 */\n isImageBased?: boolean\n}\n","/** HWP 5.x 레코드 리더, UTF-16LE 텍스트 추출, 스트림 압축해제 */\r\n\r\nimport { inflateRawSync, inflateSync } from \"zlib\"\r\nimport { KordocError } from \"../utils.js\"\r\n\r\n// ─── 레코드 태그 상수 ────────────────────────────────\r\n\r\nexport const TAG_PARA_HEADER = 0x0042\r\nexport const TAG_PARA_TEXT = 0x0043\r\nexport const TAG_CHAR_SHAPE = 0x0044\r\nexport const TAG_PARA_SHAPE = 0x0045\r\nexport const TAG_CTRL_HEADER = 0x0047\r\nexport const TAG_LIST_HEADER = 0x0048\r\nexport const TAG_TABLE = 0x004d\r\n\r\n// DocInfo 태그 (스타일 정보 해석용) — HWPTAG_BEGIN(0x0010) 기준\r\nexport const TAG_ID_MAPPINGS = 0x0011 // HWPTAG_BEGIN + 1\r\nexport const TAG_FACE_NAME = 0x0013 // HWPTAG_BEGIN + 3\r\nexport const TAG_DOC_CHAR_SHAPE = 0x0015 // HWPTAG_BEGIN + 5\r\nexport const TAG_DOC_PARA_SHAPE = 0x0019 // HWPTAG_BEGIN + 9\r\nexport const TAG_DOC_STYLE = 0x001a // HWPTAG_BEGIN + 10\r\n\r\n// 특수 문자 코드 (UTF-16LE) — HWP 5.0 바이너리 스펙 + rhwp 검증\r\n// 3가지 카테고리: char(2바이트), inline(16바이트), extended(16바이트)\r\n// char: 0, 13, 24-31 — 제어문자만, 확장 데이터 없음\r\n// inline: 4-9, 19-20 — 제어문자(2) + 확장(14) = 16바이트\r\n// extended: 1-3, 10-12, 14-18, 21-23 — 제어문자(2) + 확장(14) = 16바이트\r\nconst CHAR_LINE = 0x0000 // char: 줄바꿈\r\nconst CHAR_SECTION_BREAK = 0x000a // extended: 구역/단 정의 (14바이트 확장 데이터)\r\nconst CHAR_PARA = 0x000d // char: 문단 끝\r\nconst CHAR_TAB = 0x0009 // inline: 탭\r\nconst CHAR_HYPHEN = 0x001e // char: 하이픈\r\nconst CHAR_NBSP = 0x001f // char: 비분리 공백\r\nconst CHAR_FIXED_NBSP = 0x0018 // char: 고정 비분리 공백\r\nconst CHAR_FIXED_WIDTH = 0x0019 // char: 고정폭 공백\r\n\r\n// FileHeader 플래그\r\nexport const FLAG_COMPRESSED = 1 << 0\r\nexport const FLAG_ENCRYPTED = 1 << 1\r\nexport const FLAG_DISTRIBUTION = 1 << 2\r\nexport const FLAG_DRM = 1 << 4\r\n\r\n// ─── 레코드 구조 ─────────────────────────────────────\r\n\r\nexport interface HwpRecord {\r\n tagId: number\r\n level: number\r\n size: number\r\n data: Buffer\r\n}\r\n\r\nexport interface HwpFileHeader {\r\n signature: string\r\n versionMajor: number\r\n flags: number\r\n}\r\n\r\n// ─── 레코드 리더 ─────────────────────────────────────\r\n\r\n/** 최대 레코드 수 — 비정상 파일에 의한 메모리 폭주 방지 */\r\nconst MAX_RECORDS = 500_000\r\n\r\nexport function readRecords(data: Buffer): HwpRecord[] {\r\n const records: HwpRecord[] = []\r\n let offset = 0\r\n\r\n while (offset + 4 <= data.length && records.length < MAX_RECORDS) {\r\n const header = data.readUInt32LE(offset)\r\n offset += 4\r\n\r\n const tagId = header & 0x3ff\r\n const level = (header >> 10) & 0x3ff\r\n let size = (header >> 20) & 0xfff\r\n\r\n // 확장 크기\r\n if (size === 0xfff) {\r\n if (offset + 4 > data.length) break\r\n size = data.readUInt32LE(offset)\r\n offset += 4\r\n }\r\n\r\n if (offset + size > data.length) break\r\n records.push({ tagId, level, size, data: data.subarray(offset, offset + size) })\r\n offset += size\r\n }\r\n\r\n return records\r\n}\r\n\r\n// ─── 스트림 압축 해제 ────────────────────────────────\r\n\r\n/** 압축 해제 최대 크기 (100MB) — decompression bomb 방지 */\r\nconst MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024\r\n\r\nexport function decompressStream(data: Buffer): Buffer {\r\n const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE }\r\n if (data.length >= 2 && data[0] === 0x78) {\r\n try { return inflateSync(data, opts) } catch { /* fallback to raw */ }\r\n }\r\n return inflateRawSync(data, opts)\r\n}\r\n\r\n// ─── FileHeader 파싱 ─────────────────────────────────\r\n\r\nexport function parseFileHeader(data: Buffer): HwpFileHeader {\r\n if (data.length < 40) throw new KordocError(\"FileHeader가 너무 짧습니다 (최소 40바이트)\")\r\n const sig = data.subarray(0, 32).toString(\"utf8\").replace(/\\0+$/, \"\")\r\n return {\r\n signature: sig,\r\n versionMajor: data[35],\r\n flags: data.readUInt32LE(36),\r\n }\r\n}\r\n\r\n// ─── 스타일 정보 구조 ────────────────────────────────\r\n\r\n/** DocInfo에서 추출한 문단 모양 (PARA_SHAPE) */\r\nexport interface HwpParaShape {\r\n /** 개요 수준: 0=본문, 1-7=개요수준 1-7 (heading 계층) */\r\n outlineLevel: number\r\n}\r\n\r\n/** DocInfo에서 추출한 글자 모양 (CHAR_SHAPE) */\r\nexport interface HwpCharShape {\r\n /** 글꼴 크기 (단위: 0.1pt, 예: 100 = 10pt) */\r\n fontSize: number\r\n /**\r\n * 속성 플래그 (HWP5 바이너리 스펙 1.1 기준):\r\n * bit 0 = italic, bit 1 = bold, bit 2 = underline, bit 3 = outline\r\n * 검증 완료: 공식 스펙 + pyhwp/hwp.js 등 오픈소스 파서와 일치 (v1.7)\r\n */\r\n attrFlags: number\r\n}\r\n\r\n/** DocInfo에서 추출한 스타일 */\r\nexport interface HwpStyle {\r\n name: string\r\n /** 한글 이름 (UTF-16LE) */\r\n nameKo: string\r\n /** 연결된 charShape 인덱스 */\r\n charShapeId: number\r\n /** 연결된 paraShape 인덱스 */\r\n paraShapeId: number\r\n /** 스타일 타입: 0=paragraph, 1=character */\r\n type: number\r\n}\r\n\r\n/** DocInfo 파싱 결과 */\r\nexport interface HwpDocInfo {\r\n charShapes: HwpCharShape[]\r\n paraShapes: HwpParaShape[]\r\n styles: HwpStyle[]\r\n}\r\n\r\n/** DocInfo 레코드들에서 스타일 정보 추출 */\r\nexport function parseDocInfo(records: HwpRecord[]): HwpDocInfo {\r\n const charShapes: HwpCharShape[] = []\r\n const paraShapes: HwpParaShape[] = []\r\n const styles: HwpStyle[] = []\r\n\r\n for (const rec of records) {\r\n // PARA_SHAPE — 문단 모양 (개요 수준 추출)\r\n // 첫 4바이트(u32) 비트 팩: bits 25-27 = 개요 수준 (0=본문, 1-7=heading)\r\n if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {\r\n const flags = rec.data.readUInt32LE(0)\r\n const outlineLevel = (flags >> 25) & 0x07 // bits 25-27 → 3bit (0-7)\r\n paraShapes.push({ outlineLevel })\r\n }\r\n\r\n if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {\r\n // HWP5 CHAR_SHAPE 구조 (바이너리 스펙 1.1 기준):\r\n // faceId: 7개 언어 * u16 = 14바이트 (offset 0-13)\r\n // ratio: 7개 언어 * u8 = 7바이트 (offset 14-20)\r\n // spacing: 7개 언어 * s8 = 7바이트 (offset 21-27)\r\n // relSize: 7개 언어 * u8 = 7바이트 (offset 28-34)\r\n // charOffset: 7개 언어 * s8 = 7바이트 (offset 35-41)\r\n // baseSize: u32 at offset 42 (단위: 0.1pt)\r\n // attrFlags: u32 at offset 46 (bit0=italic, bit1=bold) — 공식 스펙 검증 완료\r\n if (rec.data.length >= 50) {\r\n const fontSize = rec.data.readUInt32LE(42) // 단위: 0.1pt\r\n const attrFlags = rec.data.readUInt32LE(46)\r\n charShapes.push({ fontSize, attrFlags })\r\n } else {\r\n // 짧은 레코드 — 스타일 정보 없음\r\n charShapes.push({ fontSize: 0, attrFlags: 0 })\r\n }\r\n }\r\n\r\n if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {\r\n try {\r\n // STYLE 구조: nameLen(u16) + name(UTF-16LE) + nameKoLen(u16) + nameKo(UTF-16LE)\r\n // + type(u8) + nextStyleId(u16) + langId(s16) + paraShapeId(u16) + charShapeId(u16)\r\n let offset = 0\r\n const nameLen = rec.data.readUInt16LE(offset); offset += 2\r\n const nameBytes = nameLen * 2\r\n const name = nameBytes > 0 && offset + nameBytes <= rec.data.length\r\n ? rec.data.subarray(offset, offset + nameBytes).toString(\"utf16le\")\r\n : \"\"\r\n offset += nameBytes\r\n\r\n let nameKo = \"\"\r\n if (offset + 2 <= rec.data.length) {\r\n const nameKoLen = rec.data.readUInt16LE(offset); offset += 2\r\n const nameKoBytes = nameKoLen * 2\r\n if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {\r\n nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString(\"utf16le\")\r\n }\r\n offset += nameKoBytes\r\n }\r\n\r\n // type(u8) + nextStyleId(u16) + langId(s16) + paraShapeId(u16) + charShapeId(u16)\r\n const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0; offset += 1\r\n offset += 2 // nextStyleId\r\n offset += 2 // langId\r\n const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0; offset += 2\r\n const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0\r\n\r\n styles.push({ name, nameKo, charShapeId, paraShapeId, type })\r\n } catch {\r\n // 파싱 실패 — 스킵\r\n }\r\n }\r\n }\r\n\r\n return { charShapes, paraShapes, styles }\r\n}\r\n\r\n// ─── UTF-16LE 텍스트 추출 (21가지 제어문자 처리) ─────\r\n\r\nexport function extractText(data: Buffer): string {\r\n let result = \"\"\r\n let i = 0\r\n\r\n while (i + 1 < data.length) {\r\n const ch = data.readUInt16LE(i)\r\n i += 2\r\n\r\n switch (ch) {\r\n // ── char 타입 (2바이트만, 확장 데이터 없음) ──\r\n case CHAR_LINE: result += \"\\n\"; break\r\n case CHAR_SECTION_BREAK: // 구역/단 정의 — extended(14바이트 스킵)\r\n result += \"\\n\"\r\n if (i + 14 <= data.length) i += 14\r\n break\r\n case CHAR_PARA: break // 문단 끝\r\n case CHAR_HYPHEN: result += \"-\"; break\r\n case CHAR_NBSP: result += \" \"; break\r\n case CHAR_FIXED_NBSP: result += \"\\u00a0\"; break // 진짜 NBSP\r\n case CHAR_FIXED_WIDTH: result += \" \"; break // 고정폭 공백\r\n\r\n // ── inline 타입 (2바이트 + 14바이트 확장) ──\r\n case CHAR_TAB:\r\n result += \"\\t\"\r\n if (i + 14 <= data.length) i += 14\r\n break\r\n\r\n default:\r\n if (ch >= 0x0001 && ch <= 0x001f) {\r\n // rhwp 기준 3-카테고리 분류:\r\n // extended(1-3, 11-12, 14-18, 21-23) + inline(4-9, 19-20) → 14바이트 스킵\r\n // char(24-31) → 스킵 없음 (이미 switch에서 24,25,30,31 처리됨)\r\n const isExtended = (ch >= 1 && ch <= 3) || (ch >= 11 && ch <= 12) || (ch >= 14 && ch <= 18) || (ch >= 21 && ch <= 23)\r\n const isInline = (ch >= 4 && ch <= 9) || (ch >= 19 && ch <= 20)\r\n if ((isExtended || isInline) && i + 14 <= data.length) i += 14\r\n } else if (ch >= 0x0020) {\r\n // UTF-16 surrogate pair 처리 (BMP 외 문자: 이모지, CJK 확장 등)\r\n if (ch >= 0xd800 && ch <= 0xdbff && i + 1 < data.length) {\r\n const lo = data.readUInt16LE(i)\r\n if (lo >= 0xdc00 && lo <= 0xdfff) {\r\n i += 2\r\n const codePoint = ((ch - 0xd800) << 10) + (lo - 0xdc00) + 0x10000\r\n result += String.fromCodePoint(codePoint)\r\n break\r\n }\r\n }\r\n result += String.fromCharCode(ch)\r\n }\r\n break\r\n }\r\n }\r\n\r\n return result\r\n}\r\n","/**\n * AES-128 ECB 순수 JS 구현 — 배포용 HWP 복호화 전용.\n * 외부 의존성 없음 (kordoc 제로 네이티브 의존성 원칙 유지).\n * 참조: rhwp (MIT) src/parser/crypto.rs + FIPS-197 (AES 표준)\n */\n\n// ── S-Box & 역 S-Box ──\n\nconst S_BOX = new Uint8Array([\n 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,\n 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,\n 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,\n 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,\n 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,\n 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,\n 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,\n 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,\n 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,\n 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,\n 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,\n 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,\n 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,\n 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,\n 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,\n 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,\n])\n\nconst INV_S_BOX = new Uint8Array([\n 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,\n 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,\n 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,\n 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,\n 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,\n 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,\n 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,\n 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,\n 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,\n 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,\n 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,\n 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,\n 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,\n 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,\n 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,\n 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d,\n])\n\n// ── RCON (라운드 상수) ──\n\nconst RCON = new Uint8Array([0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36])\n\n// ── GF(2^8) 곱셈 ──\n\nfunction gmul(a: number, b: number): number {\n let p = 0\n for (let i = 0; i < 8; i++) {\n if (b & 1) p ^= a\n const hi = a & 0x80\n a = (a << 1) & 0xff\n if (hi) a ^= 0x1b\n b >>= 1\n }\n return p\n}\n\n// ── 키 확장 (AES-128: 4 words → 44 words) ──\n\nfunction expandKey(key: Uint8Array): Uint32Array {\n const w = new Uint32Array(44)\n\n // 첫 4 words: 원본 키\n for (let i = 0; i < 4; i++) {\n w[i] = (key[4 * i] << 24) | (key[4 * i + 1] << 16) | (key[4 * i + 2] << 8) | key[4 * i + 3]\n }\n\n for (let i = 4; i < 44; i++) {\n let temp = w[i - 1]\n if (i % 4 === 0) {\n // RotWord + SubWord + RCON\n temp = ((temp << 8) | (temp >>> 24)) >>> 0\n temp = (S_BOX[(temp >>> 24) & 0xff] << 24) |\n (S_BOX[(temp >>> 16) & 0xff] << 16) |\n (S_BOX[(temp >>> 8) & 0xff] << 8) |\n S_BOX[temp & 0xff]\n temp = (temp ^ (RCON[i / 4 - 1] << 24)) >>> 0\n }\n w[i] = (w[i - 4] ^ temp) >>> 0\n }\n\n return w\n}\n\n// ── AES-128 단일 블록 복호화 (16바이트) ──\n\nfunction decryptBlock(block: Uint8Array, roundKeys: Uint32Array): Uint8Array {\n // state를 4x4 column-major 배열로 로드\n const s = new Uint8Array(16)\n for (let i = 0; i < 16; i++) s[i] = block[i]\n\n // AddRoundKey (round 10)\n addRoundKey(s, roundKeys, 10)\n\n // Rounds 9 → 1\n for (let round = 9; round >= 1; round--) {\n invShiftRows(s)\n invSubBytes(s)\n addRoundKey(s, roundKeys, round)\n invMixColumns(s)\n }\n\n // Final round (round 0)\n invShiftRows(s)\n invSubBytes(s)\n addRoundKey(s, roundKeys, 0)\n\n return s\n}\n\nfunction addRoundKey(s: Uint8Array, w: Uint32Array, round: number): void {\n const base = round * 4\n for (let c = 0; c < 4; c++) {\n const k = w[base + c]\n s[c * 4] ^= (k >>> 24) & 0xff\n s[c * 4 + 1] ^= (k >>> 16) & 0xff\n s[c * 4 + 2] ^= (k >>> 8) & 0xff\n s[c * 4 + 3] ^= k & 0xff\n }\n}\n\nfunction invSubBytes(s: Uint8Array): void {\n for (let i = 0; i < 16; i++) s[i] = INV_S_BOX[s[i]]\n}\n\nfunction invShiftRows(s: Uint8Array): void {\n // Row 0: no shift\n // Row 1: shift right 1\n let t = s[13]; s[13] = s[9]; s[9] = s[5]; s[5] = s[1]; s[1] = t\n // Row 2: shift right 2\n t = s[2]; s[2] = s[10]; s[10] = t\n t = s[6]; s[6] = s[14]; s[14] = t\n // Row 3: shift right 3 (= left 1)\n t = s[3]; s[3] = s[7]; s[7] = s[11]; s[11] = s[15]; s[15] = t\n}\n\nfunction invMixColumns(s: Uint8Array): void {\n for (let c = 0; c < 4; c++) {\n const i = c * 4\n const a0 = s[i], a1 = s[i + 1], a2 = s[i + 2], a3 = s[i + 3]\n s[i] = gmul(a0, 0x0e) ^ gmul(a1, 0x0b) ^ gmul(a2, 0x0d) ^ gmul(a3, 0x09)\n s[i + 1] = gmul(a0, 0x09) ^ gmul(a1, 0x0e) ^ gmul(a2, 0x0b) ^ gmul(a3, 0x0d)\n s[i + 2] = gmul(a0, 0x0d) ^ gmul(a1, 0x09) ^ gmul(a2, 0x0e) ^ gmul(a3, 0x0b)\n s[i + 3] = gmul(a0, 0x0b) ^ gmul(a1, 0x0d) ^ gmul(a2, 0x09) ^ gmul(a3, 0x0e)\n }\n}\n\n// ── 공개 API ──\n\n/** AES-128 ECB 복호화. data 길이는 16의 배수여야 함. */\nexport function aes128EcbDecrypt(data: Uint8Array, key: Uint8Array): Uint8Array {\n if (key.length !== 16) throw new Error(\"AES-128 키는 16바이트여야 합니다\")\n if (data.length % 16 !== 0) throw new Error(\"AES ECB 입력은 16바이트의 배수여야 합니다\")\n\n const roundKeys = expandKey(key)\n const out = new Uint8Array(data.length)\n\n for (let offset = 0; offset < data.length; offset += 16) {\n const block = data.subarray(offset, offset + 16)\n const decrypted = decryptBlock(block, roundKeys)\n out.set(decrypted, offset)\n }\n\n return out\n}\n","/**\n * HWP 배포용(distribution) 문서 복호화.\n *\n * 배포용 HWP는 ViewText/Section{N} 스트림에 암호화된 본문을 저장.\n * 첫 레코드(HWPTAG_DISTRIBUTE_DOC_DATA)의 256바이트 payload에서 AES 키를 추출한 뒤\n * 나머지 데이터를 AES-128 ECB로 복호화.\n *\n * 알고리즘 참조: rhwp (MIT) src/parser/crypto.rs\n * 포맷 참조: HWP 5.0 바이너리 스펙 — 배포용 문서 구조\n */\n\nimport { aes128EcbDecrypt } from \"./aes.js\"\nimport { decompressStream } from \"./record.js\"\n\n// ── MSVC LCG (Linear Congruential Generator) ──\n\n/** MSVC CRT rand() 호환 LCG */\nclass MsvcLcg {\n private seed: number\n\n constructor(seed: number) {\n this.seed = seed >>> 0 // u32로 강제\n }\n\n /** 0 ~ 0x7FFF 범위 난수 반환 (MSVC rand() 호환) */\n rand(): number {\n // MSVC LCG: seed = seed * 214013 + 2531011\n // JS에서 32bit 정수 오버플로우를 정확히 재현하기 위해 Math.imul 사용\n this.seed = (Math.imul(this.seed, 214013) + 2531011) >>> 0\n return (this.seed >>> 16) & 0x7fff\n }\n}\n\n// ── 배포용 문서 256바이트 payload 복호화 ──\n\n/**\n * DISTRIBUTE_DOC_DATA 레코드의 256바이트 payload를 LCG+XOR로 복호화.\n *\n * 구조:\n * - bytes[0..4]: LCG seed (u32 LE)\n * - bytes[4..256]: XOR 암호화된 데이터\n *\n * XOR 규칙: LCG에서 키 바이트를 뽑고, n = (lcg.rand() & 0xF) + 1 바이트마다 키 교체\n */\nfunction decryptDistributePayload(payload: Uint8Array): Uint8Array {\n if (payload.length < 256) throw new Error(\"배포용 payload가 256바이트 미만입니다\")\n\n const seed = (payload[0] | (payload[1] << 8) | (payload[2] << 16) | (payload[3] << 24)) >>> 0\n const lcg = new MsvcLcg(seed)\n\n const result = new Uint8Array(payload.subarray(0, 256)) // 원본 복사\n\n // rhwp 호환: i=0부터 시작하여 n 카운터를 소비하되, i<4는 XOR 스킵 (seed 보존)\n // i=4부터 시작하면 LCG 시퀀스가 어긋남 — i=0~3에서도 n을 소비해야 정확함\n let i = 0\n let n = 0\n let key = 0\n\n while (i < 256) {\n if (n === 0) {\n key = lcg.rand() & 0xff\n n = (lcg.rand() & 0x0f) + 1\n }\n if (i >= 4) {\n result[i] ^= key\n }\n i++\n n--\n }\n\n return result\n}\n\n// ── AES 키 추출 ──\n\n/**\n * 복호화된 256바이트 payload에서 AES-128 키(16바이트) 추출.\n * offset = 4 + (decrypted[0] & 0x0F)\n */\nfunction extractAesKey(decryptedPayload: Uint8Array): Uint8Array {\n const offset = 4 + (decryptedPayload[0] & 0x0f)\n if (offset + 16 > decryptedPayload.length) {\n throw new Error(\"AES 키 추출 실패: 오프셋이 payload 범위를 초과합니다\")\n }\n return decryptedPayload.slice(offset, offset + 16)\n}\n\n// ── 레코드 헤더 파싱 ──\n\n/** HWP 레코드 헤더에서 tag_id와 size 추출 */\nfunction parseRecordHeader(data: Uint8Array, offset: number): { tagId: number; size: number; headerSize: number } {\n if (offset + 4 > data.length) throw new Error(\"레코드 헤더 파싱 실패: 데이터 부족\")\n\n const header = (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) >>> 0\n const tagId = header & 0x3ff\n let size = (header >>> 20) & 0xfff\n let headerSize = 4\n\n if (size === 0xfff) {\n if (offset + 8 > data.length) throw new Error(\"확장 레코드 크기 파싱 실패: 데이터 부족\")\n size = (data[offset + 4] | (data[offset + 5] << 8) | (data[offset + 6] << 16) | (data[offset + 7] << 24)) >>> 0\n headerSize = 8\n }\n\n return { tagId, size, headerSize }\n}\n\n// ── 공개 API ──\n\n/** HWPTAG_DISTRIBUTE_DOC_DATA 태그 ID (HWPTAG_BEGIN + 12 = 0x10 + 12 = 0x1C = 28) */\nconst TAG_DISTRIBUTE_DOC_DATA = 0x10 + 12 // = 28\n\n/**\n * ViewText 스트림을 복호화하여 일반 BodyText 레코드 데이터로 변환.\n *\n * @param viewTextRaw ViewText/Section{N} 스트림의 원본 바이트\n * @param compressed FileHeader의 compressed 플래그\n * @returns 복호화된 레코드 데이터 (readRecords()로 파싱 가능)\n */\nexport function decryptViewText(viewTextRaw: Buffer, compressed: boolean): Buffer {\n const data = new Uint8Array(viewTextRaw)\n\n // 1. 첫 레코드 파싱 (DISTRIBUTE_DOC_DATA)\n const rec = parseRecordHeader(data, 0)\n if (rec.tagId !== TAG_DISTRIBUTE_DOC_DATA) {\n throw new Error(`배포용 문서의 첫 레코드가 DISTRIBUTE_DOC_DATA(${TAG_DISTRIBUTE_DOC_DATA})가 아닙니다 (실제: ${rec.tagId})`)\n }\n\n const payloadStart = rec.headerSize\n const payloadEnd = payloadStart + rec.size\n if (payloadEnd > data.length || rec.size < 256) {\n throw new Error(\"배포용 payload가 유효하지 않습니다\")\n }\n\n // 2. 256바이트 payload 복호화 (LCG + XOR)\n const payload = data.subarray(payloadStart, payloadStart + 256)\n const decryptedPayload = decryptDistributePayload(payload)\n\n // 3. AES-128 키 추출\n const aesKey = extractAesKey(decryptedPayload)\n\n // 4. 나머지 데이터를 AES-128 ECB 복호화\n const encryptedStart = payloadEnd\n const encryptedData = data.subarray(encryptedStart)\n\n if (encryptedData.length === 0) {\n throw new Error(\"배포용 문서에 암호화된 본문 데이터가 없습니다\")\n }\n\n // AES ECB는 16바이트 블록 단위 — 패딩 처리\n const alignedLen = encryptedData.length - (encryptedData.length % 16)\n if (alignedLen === 0) {\n throw new Error(\"암호화된 데이터가 너무 짧습니다 (16바이트 미만)\")\n }\n\n const alignedData = encryptedData.subarray(0, alignedLen)\n const decrypted = aes128EcbDecrypt(alignedData, aesKey)\n\n // 5. 압축 해제 (compressed 플래그가 설정된 경우)\n if (compressed) {\n try {\n return decompressStream(Buffer.from(decrypted))\n } catch {\n // 압축이 아닐 수도 있음 — 그대로 반환\n return Buffer.from(decrypted)\n }\n }\n\n return Buffer.from(decrypted)\n}\n\n// 테스트용 내부 함수 export\nexport { MsvcLcg as _MsvcLcg, decryptDistributePayload as _decryptDistributePayload, extractAesKey as _extractAesKey }\n","/**\n * Lenient CFB (Compound File Binary / OLE2) 파서.\n *\n * 표준 cfb 모듈이 FAT 검증 실패로 거부하는 손상된 HWP 파일을 열기 위한 폴백.\n * 직접 헤더/FAT/디렉토리를 파싱하여 스트림 데이터를 추출.\n *\n * 참조: rhwp (MIT) src/parser/cfb_reader.rs (LenientCfbReader)\n * 참조: MS-CFB spec (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb)\n */\n\nimport { decompressStream } from \"./record.js\"\n\n// ── 상수 ──\n\nconst CFB_MAGIC = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])\nconst END_OF_CHAIN = 0xfffffffe\nconst FREE_SECT = 0xffffffff\n\n/** 순환 감지용 최대 체인 길이 */\nconst MAX_CHAIN_LENGTH = 1_000_000\n/** 최대 디렉토리 엔트리 수 */\nconst MAX_DIR_ENTRIES = 100_000\n/** 최대 스트림 크기 (100MB) */\nconst MAX_STREAM_SIZE = 100 * 1024 * 1024\n\n// ── 디렉토리 엔트리 ──\n\ninterface DirEntry {\n name: string\n type: number // 0=unknown, 1=storage, 2=stream, 5=root\n startSector: number\n size: number\n}\n\n// ── CFB 컨테이너 ──\n\nexport interface LenientCfbContainer {\n /** 이름 기반 스트림 탐색 */\n findStream(path: string): Buffer | null\n /** 디렉토리 엔트리 목록 */\n entries(): DirEntry[]\n}\n\n// ── 구현 ──\n\nexport function parseLenientCfb(data: Buffer): LenientCfbContainer {\n if (data.length < 512) throw new Error(\"CFB 파일이 너무 짧습니다 (최소 512바이트)\")\n if (!data.subarray(0, 8).equals(CFB_MAGIC)) throw new Error(\"CFB 매직 바이트 불일치\")\n\n // ── 헤더 파싱 ──\n\n const sectorSizeShift = data.readUInt16LE(30)\n if (sectorSizeShift < 7 || sectorSizeShift > 16) throw new Error(\"유효하지 않은 섹터 크기 시프트: \" + sectorSizeShift)\n const sectorSize = 1 << sectorSizeShift // 보통 512\n const miniSectorSizeShift = data.readUInt16LE(32)\n if (miniSectorSizeShift > 16) throw new Error(\"유효하지 않은 미니 섹터 크기 시프트: \" + miniSectorSizeShift)\n const miniSectorSize = 1 << miniSectorSizeShift // 보통 64\n\n const fatSectorCount = data.readUInt32LE(44)\n if (fatSectorCount > 10000) throw new Error(\"FAT 섹터 수가 너무 많습니다: \" + fatSectorCount)\n const firstDirSector = data.readUInt32LE(48)\n const miniStreamCutoff = data.readUInt32LE(56) // 보통 4096\n const firstMiniFatSector = data.readUInt32LE(60)\n const miniFatSectorCount = data.readUInt32LE(64)\n const firstDifatSector = data.readUInt32LE(68)\n const difatSectorCount = data.readUInt32LE(72)\n\n // ── 유틸 ──\n\n function sectorOffset(id: number): number {\n return 512 + id * sectorSize\n }\n\n function readSectorData(id: number): Buffer {\n const off = sectorOffset(id)\n if (off + sectorSize > data.length) return Buffer.alloc(0)\n return data.subarray(off, off + sectorSize)\n }\n\n // ── DIFAT → FAT 섹터 목록 ──\n\n const fatSectors: number[] = []\n\n // 헤더 내 DIFAT (최대 109개)\n for (let i = 0; i < 109 && fatSectors.length < fatSectorCount; i++) {\n const sid = data.readUInt32LE(76 + i * 4)\n if (sid === FREE_SECT || sid === END_OF_CHAIN) break\n fatSectors.push(sid)\n }\n\n // 추가 DIFAT 섹터 체인\n let difatSector = firstDifatSector\n const visitedDifat = new Set<number>()\n for (let d = 0; d < difatSectorCount && difatSector !== END_OF_CHAIN && difatSector !== FREE_SECT; d++) {\n if (visitedDifat.has(difatSector)) break\n visitedDifat.add(difatSector)\n\n const buf = readSectorData(difatSector)\n const entriesPerSector = (sectorSize / 4) - 1 // 마지막 4바이트는 다음 DIFAT 포인터\n for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {\n const sid = buf.readUInt32LE(i * 4)\n if (sid === FREE_SECT || sid === END_OF_CHAIN) continue\n fatSectors.push(sid)\n }\n difatSector = buf.readUInt32LE(entriesPerSector * 4)\n }\n\n // ── FAT 테이블 구축 ──\n\n const entriesPerFatSector = sectorSize / 4\n const fatTable = new Uint32Array(fatSectors.length * entriesPerFatSector)\n\n for (let fi = 0; fi < fatSectors.length; fi++) {\n const buf = readSectorData(fatSectors[fi])\n for (let i = 0; i < entriesPerFatSector; i++) {\n fatTable[fi * entriesPerFatSector + i] = i * 4 + 3 < buf.length\n ? buf.readUInt32LE(i * 4)\n : FREE_SECT\n }\n }\n\n // ── 체인 리더 (순환 방지) ──\n\n function readChain(startSector: number, maxBytes: number): Buffer {\n if (startSector === END_OF_CHAIN || startSector === FREE_SECT) return Buffer.alloc(0)\n if (maxBytes > MAX_STREAM_SIZE) throw new Error(\"스트림이 너무 큽니다\")\n\n const chunks: Buffer[] = []\n let current = startSector\n let totalRead = 0\n const visited = new Set<number>()\n\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < maxBytes) {\n if (visited.has(current)) break // 순환 감지\n if (visited.size > MAX_CHAIN_LENGTH) break\n visited.add(current)\n\n const buf = readSectorData(current)\n const remaining = maxBytes - totalRead\n chunks.push(remaining < sectorSize ? buf.subarray(0, remaining) : buf)\n totalRead += Math.min(buf.length, remaining)\n\n current = current < fatTable.length ? fatTable[current] : END_OF_CHAIN\n }\n\n return Buffer.concat(chunks)\n }\n\n // ── Mini-FAT 테이블 ──\n\n let miniFatTable: Uint32Array | null = null\n\n function getMiniFatTable(): Uint32Array {\n if (miniFatTable) return miniFatTable\n\n if (miniFatSectorCount === 0 || firstMiniFatSector === END_OF_CHAIN) {\n miniFatTable = new Uint32Array(0)\n return miniFatTable\n }\n\n const miniFatData = readChain(firstMiniFatSector, miniFatSectorCount * sectorSize)\n const entries = miniFatData.length / 4\n miniFatTable = new Uint32Array(entries)\n for (let i = 0; i < entries; i++) {\n miniFatTable[i] = miniFatData.readUInt32LE(i * 4)\n }\n return miniFatTable\n }\n\n // ── 디렉토리 엔트리 파싱 ──\n\n const dirData = readChain(firstDirSector, MAX_DIR_ENTRIES * 128)\n const dirEntries: DirEntry[] = []\n\n for (let offset = 0; offset + 128 <= dirData.length && dirEntries.length < MAX_DIR_ENTRIES; offset += 128) {\n const nameLen = dirData.readUInt16LE(offset + 64) // 바이트 수 (null 포함)\n if (nameLen <= 0 || nameLen > 64) {\n dirEntries.push({ name: \"\", type: 0, startSector: 0, size: 0 })\n continue\n }\n\n const nameBytes = nameLen - 2 // null terminator 제외\n const name = nameBytes > 0\n ? dirData.subarray(offset, offset + nameBytes).toString(\"utf16le\")\n : \"\"\n\n const type = dirData[offset + 66]\n const startSector = dirData.readUInt32LE(offset + 116)\n // CFBv3에서는 size가 u32 (offset 120), v4에서는 u64\n const size = dirData.readUInt32LE(offset + 120)\n\n dirEntries.push({ name, type, startSector, size })\n }\n\n // ── Root 엔트리에서 미니 스트림 추출 ──\n\n let miniStreamData: Buffer | null = null\n\n function getMiniStream(): Buffer {\n if (miniStreamData) return miniStreamData\n const root = dirEntries[0]\n if (!root || root.type !== 5) {\n miniStreamData = Buffer.alloc(0)\n return miniStreamData\n }\n miniStreamData = readChain(root.startSector, root.size || MAX_STREAM_SIZE)\n return miniStreamData\n }\n\n // ── 미니 스트림에서 읽기 ──\n\n function readMiniStream(startSector: number, size: number): Buffer {\n const mft = getMiniFatTable()\n const ms = getMiniStream()\n if (mft.length === 0 || ms.length === 0) return Buffer.alloc(0)\n\n const chunks: Buffer[] = []\n let current = startSector\n let totalRead = 0\n const visited = new Set<number>()\n\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < size) {\n if (visited.has(current)) break\n if (visited.size > MAX_CHAIN_LENGTH) break\n visited.add(current)\n\n const off = current * miniSectorSize\n const remaining = size - totalRead\n const chunkSize = Math.min(miniSectorSize, remaining)\n if (off + chunkSize <= ms.length) {\n chunks.push(ms.subarray(off, off + chunkSize))\n }\n totalRead += chunkSize\n\n current = current < mft.length ? mft[current] : END_OF_CHAIN\n }\n\n return Buffer.concat(chunks)\n }\n\n // ── 스트림 읽기 (일반/미니 자동 분기) ──\n\n function readStreamData(entry: DirEntry): Buffer {\n if (entry.size === 0) return Buffer.alloc(0)\n if (entry.size < miniStreamCutoff) {\n const miniResult = readMiniStream(entry.startSector, entry.size)\n // 미니스트림이 비어있으면 일반 체인으로 폴백 (lenient)\n if (miniResult.length > 0) return miniResult\n }\n return readChain(entry.startSector, entry.size)\n }\n\n // ── 경로 기반 탐색 ──\n\n // 전체 경로 맵 구축 (간이: 이름 기반 flat lookup)\n // HWP 파일의 디렉토리 구조는 보통 1~2 depth이므로 이름 매칭으로 충분\n function findEntryByPath(path: string): DirEntry | null {\n // \"/FileHeader\" → \"FileHeader\"\n // \"/BodyText/Section0\" → path component matching\n const parts = path.replace(/^\\//, \"\").split(\"/\")\n\n if (parts.length === 1) {\n // 단일 이름 매칭\n return dirEntries.find(e => e.name === parts[0] && e.type === 2) ?? null\n }\n\n // 2-depth: storage/stream\n // HWP 구조: Root/BodyText/Section0, Root/DocInfo, Root/BinData/BIN0001 등\n const storageName = parts[0]\n const streamName = parts.slice(1).join(\"/\")\n\n // 디렉토리 구조 대신 이름 패턴으로 찾기 (lenient)\n for (const e of dirEntries) {\n if (e.type === 2 && e.name === streamName) {\n // 부모 확인은 생략 (lenient) — 중복 이름 시 첫 번째 반환\n return e\n }\n }\n\n // 정확한 이름이 아닌 경우 (ViewText/Section0 등)\n const lastPart = parts[parts.length - 1]\n return dirEntries.find(e => e.type === 2 && e.name === lastPart) ?? null\n }\n\n // ── 공개 API ──\n\n return {\n findStream(path: string): Buffer | null {\n // \\005 prefix 처리 (SummaryInformation)\n const normalized = path.replace(/^\\//, \"\")\n const entry = findEntryByPath(normalized)\n if (!entry || entry.type !== 2) return null\n const stream = readStreamData(entry)\n return stream.length > 0 ? stream : null\n },\n\n entries(): DirEntry[] {\n return dirEntries.filter(e => e.type === 2) // stream만\n },\n }\n}\n","/** HWP 5.x 바이너리 파서 — OLE2 컨테이너 → 섹션 → Markdown */\r\n\r\nimport {\r\n readRecords, decompressStream, parseFileHeader, extractText, parseDocInfo,\r\n TAG_PARA_HEADER, TAG_PARA_TEXT, TAG_CHAR_SHAPE, TAG_CTRL_HEADER, TAG_LIST_HEADER, TAG_TABLE,\r\n FLAG_COMPRESSED, FLAG_ENCRYPTED, FLAG_DISTRIBUTION, FLAG_DRM,\r\n type HwpRecord, type HwpDocInfo, type HwpCharShape, type HwpParaShape,\r\n} from \"./record.js\"\r\nimport { decryptViewText } from \"./crypto.js\"\r\nimport { parseLenientCfb, type LenientCfbContainer } from \"./cfb-lenient.js\"\r\nimport { buildTable, blocksToMarkdown, flattenLayoutTables, MAX_COLS, MAX_ROWS } from \"../table/builder.js\"\r\nimport type { CellContext, IRBlock, IRTable, DocumentMetadata, InternalParseResult, ParseOptions, ParseWarning, OutlineItem, InlineStyle, ExtractedImage } from \"../types.js\"\r\nimport { HEADING_RATIO_H1, HEADING_RATIO_H2, HEADING_RATIO_H3 } from \"../types.js\"\r\nimport { KordocError, sanitizeHref } from \"../utils.js\"\r\nimport { parsePageRange } from \"../page-range.js\"\r\n\r\nimport { createRequire } from \"module\"\r\nconst require = createRequire(import.meta.url)\r\nconst CFB: CfbModule = require(\"cfb\")\r\n\r\ninterface CfbEntry { name?: string; content?: Buffer | Uint8Array }\r\ninterface CfbContainer { FileIndex?: CfbEntry[] }\r\ninterface CfbModule {\r\n parse(data: Buffer): CfbContainer\r\n find(cfb: CfbContainer, path: string): CfbEntry | null\r\n}\r\n\r\n/** 최대 섹션 수 — 비정상 파일에 의한 무한 루프 방지 */\r\nconst MAX_SECTIONS = 100\r\n/** 누적 압축 해제 최대 크기 (100MB) */\r\nconst MAX_TOTAL_DECOMPRESS = 100 * 1024 * 1024\r\n\r\nexport function parseHwp5Document(buffer: Buffer, options?: ParseOptions): InternalParseResult {\r\n // CFB 파싱: strict 먼저, 실패 시 lenient 폴백\r\n let cfb: CfbContainer | null = null\r\n let lenientCfb: LenientCfbContainer | null = null\r\n const warnings: ParseWarning[] = []\r\n\r\n try {\r\n cfb = CFB.parse(buffer)\r\n } catch {\r\n try {\r\n lenientCfb = parseLenientCfb(buffer)\r\n warnings.push({ message: \"손상된 CFB 컨테이너 — lenient 모드로 복구\", code: \"LENIENT_CFB_RECOVERY\" })\r\n } catch {\r\n throw new KordocError(\"CFB 컨테이너 파싱 실패 (strict 및 lenient 모두)\")\r\n }\r\n }\r\n\r\n // CFB 래퍼: strict/lenient 통합 인터페이스\r\n const findStream = (path: string): Buffer | null => {\r\n if (cfb) {\r\n const entry = CFB.find(cfb, path)\r\n return entry?.content ? Buffer.from(entry.content) : null\r\n }\r\n return lenientCfb!.findStream(path)\r\n }\r\n\r\n const headerData = findStream(\"/FileHeader\")\r\n if (!headerData) throw new KordocError(\"FileHeader 스트림 없음\")\r\n const header = parseFileHeader(headerData)\r\n if (header.signature !== \"HWP Document File\") throw new KordocError(\"HWP 시그니처 불일치\")\r\n if (header.flags & FLAG_ENCRYPTED) throw new KordocError(\"암호화된 HWP는 지원하지 않습니다\")\r\n if (header.flags & FLAG_DRM) throw new KordocError(\"DRM 보호된 HWP는 지원하지 않습니다\")\r\n const compressed = (header.flags & FLAG_COMPRESSED) !== 0\r\n const distribution = (header.flags & FLAG_DISTRIBUTION) !== 0\r\n\r\n const metadata: DocumentMetadata = {\r\n version: `${header.versionMajor}.x`,\r\n }\r\n if (cfb) extractHwp5Metadata(cfb, metadata)\r\n\r\n // DocInfo 파싱 (스타일 정보 추출)\r\n const docInfo = cfb\r\n ? parseDocInfoStream(cfb, compressed)\r\n : parseDocInfoFromStream(findStream(\"/DocInfo\"), compressed)\r\n\r\n const sections = distribution\r\n ? (cfb ? findViewTextSections(cfb, compressed) : findViewTextSectionsLenient(lenientCfb!, compressed))\r\n : (cfb ? findSections(cfb) : findSectionsLenient(lenientCfb!, compressed))\r\n if (sections.length === 0) throw new KordocError(\"섹션 스트림을 찾을 수 없습니다\")\r\n\r\n metadata.pageCount = sections.length\r\n\r\n // 페이지 범위 필터링 (섹션 단위 근사치)\r\n const pageFilter = options?.pages ? parsePageRange(options.pages, sections.length) : null\r\n const totalTarget = pageFilter ? pageFilter.size : sections.length\r\n\r\n const blocks: IRBlock[] = []\r\n let totalDecompressed = 0\r\n let parsedSections = 0\r\n for (let si = 0; si < sections.length; si++) {\r\n if (pageFilter && !pageFilter.has(si + 1)) continue\r\n try {\r\n const sectionData = sections[si]\r\n // 배포용 문서는 findViewTextSections에서 이미 복호화+압축해제 완료\r\n const data = (!distribution && compressed) ? decompressStream(Buffer.from(sectionData)) : Buffer.from(sectionData)\r\n totalDecompressed += data.length\r\n if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError(\"총 압축 해제 크기 초과 (decompression bomb 의심)\")\r\n const records = readRecords(data)\r\n const sectionBlocks = parseSection(records, docInfo, warnings, si + 1)\r\n blocks.push(...sectionBlocks)\r\n parsedSections++\r\n options?.onProgress?.(parsedSections, totalTarget)\r\n } catch (secErr) {\r\n if (secErr instanceof KordocError) throw secErr\r\n warnings.push({ page: si + 1, message: `섹션 ${si + 1} 파싱 실패: ${secErr instanceof Error ? secErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\r\n }\r\n }\r\n\r\n // BinData에서 이미지 추출\r\n const images = cfb\r\n ? extractHwp5Images(cfb, blocks, compressed, warnings)\r\n : extractHwp5ImagesLenient(lenientCfb!, blocks, compressed, warnings)\r\n\r\n // 레이아웃 테이블 해체 (heading 감지 전에 수행하여 해체된 텍스트도 heading 감지 대상)\r\n const flatBlocks = flattenLayoutTables(blocks)\r\n\r\n // 스타일 기반 헤딩 감지\r\n if (docInfo) {\r\n detectHwp5Headings(flatBlocks, docInfo)\r\n }\r\n\r\n // outline 구축\r\n const outline: OutlineItem[] = flatBlocks\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 const markdown = blocksToMarkdown(flatBlocks)\r\n return { markdown, blocks: flatBlocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined, images: images.length > 0 ? images : undefined }\r\n}\r\n\r\n/** DocInfo 스트림 파싱 (best-effort) */\r\nfunction parseDocInfoStream(cfb: CfbContainer, compressed: boolean): HwpDocInfo | null {\r\n try {\r\n const entry = CFB.find(cfb, \"/DocInfo\")\r\n if (!entry?.content) return null\r\n const data = compressed ? decompressStream(Buffer.from(entry.content)) : Buffer.from(entry.content)\r\n const records = readRecords(data)\r\n return parseDocInfo(records)\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\n/** DocInfo — Buffer에서 직접 파싱 (lenient용) */\r\nfunction parseDocInfoFromStream(raw: Buffer | null, compressed: boolean): HwpDocInfo | null {\r\n if (!raw) return null\r\n try {\r\n const data = compressed ? decompressStream(raw) : raw\r\n return parseDocInfo(readRecords(data))\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\n/** 스타일 기반 헤딩 감지 — 큰 폰트 + 짧은 텍스트 → heading */\r\nfunction detectHwp5Headings(blocks: IRBlock[], docInfo: HwpDocInfo): void {\r\n // 기본 폰트 크기 결정 (본문 스타일 또는 가장 많이 사용되는 크기)\r\n let baseFontSize = 0\r\n\r\n // \"바탕글\", \"본문\" 등 본문 스타일 찾기\r\n for (const style of docInfo.styles) {\r\n const name = (style.nameKo || style.name).toLowerCase()\r\n if (name.includes(\"바탕\") || name.includes(\"본문\") || name === \"normal\" || name === \"body\") {\r\n const cs = docInfo.charShapes[style.charShapeId]\r\n // cs.fontSize는 0.1pt 단위 → pt로 변환 (블록의 style.fontSize와 동일 단위)\r\n if (cs?.fontSize > 0) { baseFontSize = cs.fontSize / 10; break }\r\n }\r\n }\r\n\r\n // 본문 스타일 못 찾으면 블록의 폰트 크기 중 최빈값 사용\r\n if (baseFontSize === 0) {\r\n const sizeFreq = new Map<number, number>()\r\n for (const b of blocks) {\r\n if (b.style?.fontSize) {\r\n sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1)\r\n }\r\n }\r\n let maxCount = 0\r\n for (const [size, count] of sizeFreq) {\r\n if (count > maxCount) { maxCount = count; baseFontSize = size }\r\n }\r\n }\r\n\r\n if (baseFontSize <= 0) return\r\n\r\n for (const block of blocks) {\r\n // 개요 수준(outlineLevel)으로 이미 heading이 된 블록은 스킵\r\n if (block.type === \"heading\") continue\r\n if (block.type !== \"paragraph\" || !block.text) continue\r\n const text = block.text.trim()\r\n if (text.length === 0 || text.length > 200) continue\r\n if (/^\\d+$/.test(text)) continue\r\n\r\n let level = 0\r\n\r\n // 폰트 크기 비율 기반 헤딩 감지 (스타일 정보가 있을 때만)\r\n if (block.style?.fontSize && baseFontSize > 0) {\r\n const ratio = block.style.fontSize / baseFontSize\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\r\n // \"제N장/절/편\" 패턴 → H2, \"제N조\" 패턴 → H3 (스타일 유무 무관)\r\n if (/^제\\d+[장절편]\\s/.test(text) && text.length <= 50) {\r\n if (level === 0) level = 2\r\n } else if (/^제\\d+(조의?\\d*)\\s*[\\((]/.test(text) && text.length <= 80) {\r\n if (level === 0) level = 3\r\n }\r\n\r\n if (level > 0) {\r\n block.type = \"heading\"\r\n block.level = level\r\n }\r\n }\r\n}\r\n\r\n// ─── 메타데이터 추출 (best-effort) ───────────────────\r\n\r\n/**\r\n * OLE2 SummaryInformation 스트림에서 제목/작성자 추출.\r\n * HWP5는 \\005HwpSummaryInformation 또는 \\005SummaryInformation에 저장.\r\n * OLE2 Property Set 포맷의 간이 파싱 — 실패 시 조용히 무시.\r\n */\r\nfunction extractHwp5Metadata(cfb: CfbContainer, metadata: DocumentMetadata): void {\r\n try {\r\n // HWP 전용 SummaryInformation 먼저, 없으면 표준 OLE2\r\n const summaryEntry =\r\n CFB.find(cfb, \"/\\x05HwpSummaryInformation\") ||\r\n CFB.find(cfb, \"/\\x05SummaryInformation\")\r\n if (!summaryEntry?.content) return\r\n\r\n const data = Buffer.from(summaryEntry.content)\r\n if (data.length < 48) return\r\n\r\n // OLE2 Property Set Header: byte order(2) + version(2) + OS(4) + CLSID(16) + numSets(4) = 28\r\n // Then FMTID(16) + offset(4)\r\n const numSets = data.readUInt32LE(24)\r\n if (numSets === 0) return\r\n\r\n const setOffset = data.readUInt32LE(44)\r\n if (setOffset >= data.length - 8) return\r\n\r\n // Property Set: size(4) + numProperties(4) + [propertyId(4) + offset(4)] * N\r\n const numProps = data.readUInt32LE(setOffset + 4)\r\n if (numProps === 0 || numProps > 100) return\r\n\r\n for (let i = 0; i < numProps; i++) {\r\n const entryOffset = setOffset + 8 + i * 8\r\n if (entryOffset + 8 > data.length) break\r\n\r\n const propId = data.readUInt32LE(entryOffset)\r\n const propOffset = setOffset + data.readUInt32LE(entryOffset + 4)\r\n if (propOffset + 8 > data.length) continue\r\n\r\n // Property ID: 2=Title, 4=Author, 6=Subject/Description\r\n if (propId !== 2 && propId !== 4 && propId !== 6) continue\r\n\r\n const propType = data.readUInt32LE(propOffset)\r\n // Type 0x1E = VT_LPSTR (ANSI string)\r\n if (propType !== 0x1e) continue\r\n\r\n const strLen = data.readUInt32LE(propOffset + 4)\r\n if (strLen === 0 || strLen > 10000 || propOffset + 8 + strLen > data.length) continue\r\n\r\n const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString(\"utf8\").replace(/\\0+$/, \"\").trim()\r\n if (!str) continue\r\n\r\n if (propId === 2) metadata.title = str\r\n else if (propId === 4) metadata.author = str\r\n else if (propId === 6) metadata.description = str\r\n }\r\n } catch {\r\n // best-effort — 실패 시 조용히 무시\r\n }\r\n}\r\n\r\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\r\nexport function extractHwp5MetadataOnly(buffer: Buffer): DocumentMetadata {\r\n const cfb = CFB.parse(buffer)\r\n const headerEntry = CFB.find(cfb, \"/FileHeader\")\r\n if (!headerEntry?.content) throw new KordocError(\"FileHeader 스트림 없음\")\r\n const header = parseFileHeader(Buffer.from(headerEntry.content))\r\n if (header.signature !== \"HWP Document File\") throw new KordocError(\"HWP 시그니처 불일치\")\r\n\r\n const metadata: DocumentMetadata = {\r\n version: `${header.versionMajor}.x`,\r\n }\r\n extractHwp5Metadata(cfb, metadata)\r\n\r\n const sections = findSections(cfb)\r\n metadata.pageCount = sections.length\r\n\r\n return metadata\r\n}\r\n\r\n/** 배포용 문서: ViewText/Section{N} 스트림을 복호화하여 반환 */\r\nfunction findViewTextSections(cfb: CfbContainer, compressed: boolean): Buffer[] {\r\n const sections: Array<{ idx: number; content: Buffer }> = []\r\n\r\n for (let i = 0; i < MAX_SECTIONS; i++) {\r\n const entry = CFB.find(cfb, `/ViewText/Section${i}`)\r\n if (!entry?.content) break\r\n try {\r\n const decrypted = decryptViewText(Buffer.from(entry.content), compressed)\r\n sections.push({ idx: i, content: decrypted })\r\n } catch {\r\n // 복호화 실패 시 해당 섹션 스킵\r\n break\r\n }\r\n }\r\n\r\n return sections.sort((a, b) => a.idx - b.idx).map(s => s.content)\r\n}\r\n\r\nfunction findSections(cfb: CfbContainer): Buffer[] {\r\n const sections: Array<{ idx: number; content: Buffer }> = []\r\n\r\n for (let i = 0; i < MAX_SECTIONS; i++) {\r\n const entry = CFB.find(cfb, `/BodyText/Section${i}`)\r\n if (!entry?.content) break\r\n sections.push({ idx: i, content: Buffer.from(entry.content) })\r\n }\r\n\r\n if (sections.length === 0 && cfb.FileIndex) {\r\n for (const entry of cfb.FileIndex) {\r\n if (sections.length >= MAX_SECTIONS) break\r\n if (entry.name?.startsWith(\"Section\") && entry.content) {\r\n const idx = parseInt(entry.name.replace(\"Section\", \"\"), 10) || 0\r\n sections.push({ idx, content: Buffer.from(entry.content) })\r\n }\r\n }\r\n }\r\n\r\n return sections.sort((a, b) => a.idx - b.idx).map(s => s.content)\r\n}\r\n\r\n/** Lenient CFB: BodyText/Section{N} 탐색 — 누적 압축해제 크기 추적 */\r\nfunction findSectionsLenient(lcfb: LenientCfbContainer, compressed: boolean): Buffer[] {\r\n const sections: Array<{ idx: number; content: Buffer }> = []\r\n let totalDecompressed = 0\r\n for (let i = 0; i < MAX_SECTIONS; i++) {\r\n const raw = lcfb.findStream(`/BodyText/Section${i}`) ?? lcfb.findStream(`Section${i}`)\r\n if (!raw) break\r\n const content = compressed ? decompressStream(raw) : raw\r\n totalDecompressed += content.length\r\n if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError(\"총 압축 해제 크기 초과 (decompression bomb 의심)\")\r\n sections.push({ idx: i, content })\r\n }\r\n if (sections.length === 0) {\r\n // fallback: 이름에 \"Section\" 포함된 스트림\r\n for (const e of lcfb.entries()) {\r\n if (sections.length >= MAX_SECTIONS) break\r\n if (e.name.startsWith(\"Section\")) {\r\n const idx = parseInt(e.name.replace(\"Section\", \"\"), 10) || 0\r\n const raw = lcfb.findStream(e.name)\r\n if (raw) {\r\n const content = compressed ? decompressStream(raw) : raw\r\n totalDecompressed += content.length\r\n if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError(\"총 압축 해제 크기 초과 (decompression bomb 의심)\")\r\n sections.push({ idx, content })\r\n }\r\n }\r\n }\r\n }\r\n return sections.sort((a, b) => a.idx - b.idx).map(s => s.content)\r\n}\r\n\r\n/** Lenient CFB: ViewText/Section{N} 복호화 — 누적 크기 추적 */\r\nfunction findViewTextSectionsLenient(lcfb: LenientCfbContainer, compressed: boolean): Buffer[] {\r\n const sections: Array<{ idx: number; content: Buffer }> = []\r\n let totalDecompressed = 0\r\n for (let i = 0; i < MAX_SECTIONS; i++) {\r\n const raw = lcfb.findStream(`/ViewText/Section${i}`) ?? lcfb.findStream(`Section${i}`)\r\n if (!raw) break\r\n try {\r\n const content = decryptViewText(raw, compressed)\r\n totalDecompressed += content.length\r\n if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError(\"총 압축 해제 크기 초과 (decompression bomb 의심)\")\r\n sections.push({ idx: i, content })\r\n } catch { break }\r\n }\r\n return sections.sort((a, b) => a.idx - b.idx).map(s => s.content)\r\n}\r\n\r\n// ─── BinData ���미지 추출 ─────��─────────────────��────\r\n\r\n/** SHAPE_COMPONENT 태그 — HWP5 스펙 */\r\nconst TAG_SHAPE_COMPONENT = 0x004a\r\n\r\n/** gso 제어 뒤의 하위 레코드에서 binDataId 추출 (best-effort) */\r\nfunction extractBinDataId(records: HwpRecord[], ctrlIdx: number): number {\r\n const ctrlLevel = records[ctrlIdx].level\r\n // CTRL_HEADER 이후의 하위 레코드들을 순회\r\n for (let j = ctrlIdx + 1; j < records.length && j < ctrlIdx + 50; j++) {\r\n const r = records[j]\r\n if (r.level <= ctrlLevel) break // 같은/상위 레벨이면 이 제어 블록 끝\r\n // SHAPE_COMPONENT에서 picture 타입이면 binDataId 추출\r\n // picture 데이터는 SHAPE_COMPONENT 뒤에 오는 하위 레코드에 있음\r\n // HWP5에서 그림 정보는 level이 높은 하위 레코드에 binDataId가 uint16LE로 저장\r\n if (r.data.length >= 2) {\r\n // 매직바이트로 이미지인지 확인하는 대신, SHAPE_COMPONENT 뒤의 하위 레코드에서 binDataId를 읽음\r\n // HWP5 picture 구조: CTRL_HEADER(gso) → LIST_HEADER → SHAPE_COMPONENT → [picture data record]\r\n // picture data record에서 offset 0부터 uint16LE = binDataId\r\n if (r.tagId > TAG_SHAPE_COMPONENT && r.level > ctrlLevel + 1 && r.data.length >= 4) {\r\n const possibleId = r.data.readUInt16LE(0)\r\n if (possibleId < 10000) return possibleId // 합리적 범위\r\n }\r\n }\r\n }\r\n return -1\r\n}\r\n\r\n/** MIME 타입 매직바이트 판별 */\r\nfunction detectImageMime(data: Buffer | Uint8Array): string | null {\r\n if (data.length < 4) return null\r\n if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) return \"image/png\"\r\n if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) return \"image/jpeg\"\r\n if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) return \"image/gif\"\r\n if (data[0] === 0x42 && data[1] === 0x4d) return \"image/bmp\"\r\n if (data[0] === 0xd7 && data[1] === 0xcd && data[2] === 0xc6 && data[3] === 0x9a) return \"image/wmf\"\r\n if (data[0] === 0x01 && data[1] === 0x00 && data[2] === 0x00 && data[3] === 0x00) return \"image/emf\"\r\n return null\r\n}\r\n\r\n/** OLE2 BinData 스토리지에서 이미지 추출, blocks의 image 블록과 매핑 */\r\nfunction extractHwp5Images(\r\n cfb: CfbContainer,\r\n blocks: IRBlock[],\r\n compressed: boolean,\r\n warnings: ParseWarning[],\r\n): ExtractedImage[] {\r\n // BinData 스토리지의 모든 파일을 FileIndex 순회로 수집 (O(n), 기존 O(20000) CFB.find 제거)\r\n const binDataMap = new Map<number, { data: Buffer; name: string }>()\r\n const binDataRe = /\\/BinData\\/[Bb][Ii][Nn](\\d{4})$/\r\n if (cfb.FileIndex) {\r\n for (const entry of cfb.FileIndex) {\r\n if (!entry?.name || !entry.content) continue\r\n const match = entry.name.match(binDataRe)\r\n if (!match) continue\r\n const idx = parseInt(match[1], 10)\r\n let data = Buffer.from(entry.content)\r\n if (compressed) {\r\n try { data = decompressStream(data) } catch { /* 이미 비압축일 수 있음 */ }\r\n }\r\n binDataMap.set(idx, { data, name: entry.name })\r\n }\r\n }\r\n\r\n if (binDataMap.size === 0) return []\r\n\r\n const images: ExtractedImage[] = []\r\n let imageIndex = 0\r\n\r\n for (const block of blocks) {\r\n if (block.type !== \"image\" || !block.text) continue\r\n const binId = parseInt(block.text, 10)\r\n if (isNaN(binId)) continue\r\n\r\n const bin = binDataMap.get(binId)\r\n if (!bin) {\r\n warnings.push({ page: block.pageNumber, message: `BinData ${binId} 없음`, code: \"SKIPPED_IMAGE\" })\r\n block.type = \"paragraph\"\r\n block.text = `[이미지: BinData ${binId}]`\r\n continue\r\n }\r\n\r\n const mime = detectImageMime(bin.data)\r\n if (!mime) {\r\n warnings.push({ page: block.pageNumber, message: `BinData ${binId}: 알 수 없는 이미지 형식`, code: \"SKIPPED_IMAGE\" })\r\n block.type = \"paragraph\"\r\n block.text = `[이미지: ${bin.name}]`\r\n continue\r\n }\r\n\r\n imageIndex++\r\n const ext = mime.includes(\"jpeg\") ? \"jpg\" : mime.includes(\"png\") ? \"png\" : mime.includes(\"gif\") ? \"gif\" : mime.includes(\"bmp\") ? \"bmp\" : \"bin\"\r\n const filename = `image_${String(imageIndex).padStart(3, \"0\")}.${ext}`\r\n\r\n images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime })\r\n block.text = filename\r\n block.imageData = { data: new Uint8Array(bin.data), mimeType: mime, filename: bin.name }\r\n }\r\n\r\n return images\r\n}\r\n\r\n/** Lenient CFB: BinData 이미지 추출 */\r\nfunction extractHwp5ImagesLenient(\r\n lcfb: LenientCfbContainer,\r\n blocks: IRBlock[],\r\n compressed: boolean,\r\n warnings: ParseWarning[],\r\n): ExtractedImage[] {\r\n // BinData 엔트리 수집\r\n const binDataMap = new Map<number, { data: Buffer; name: string }>()\r\n const binRe = /^BIN(\\d{4})/i\r\n for (const e of lcfb.entries()) {\r\n const match = e.name.match(binRe)\r\n if (!match) continue\r\n const idx = parseInt(match[1], 10)\r\n let raw = lcfb.findStream(e.name)\r\n if (!raw) continue\r\n if (compressed) {\r\n try { raw = decompressStream(raw) } catch { /* 이미 비압축일 수 있음 */ }\r\n }\r\n binDataMap.set(idx, { data: raw, name: e.name })\r\n }\r\n if (binDataMap.size === 0) return []\r\n\r\n const images: ExtractedImage[] = []\r\n let imageIndex = 0\r\n for (const block of blocks) {\r\n if (block.type !== \"image\" || !block.text) continue\r\n const binId = parseInt(block.text, 10)\r\n if (isNaN(binId)) continue\r\n const bin = binDataMap.get(binId)\r\n if (!bin) {\r\n warnings.push({ page: block.pageNumber, message: `BinData ${binId} ���음`, code: \"SKIPPED_IMAGE\" })\r\n block.type = \"paragraph\"; block.text = `[이미지: BinData ${binId}]`; continue\r\n }\r\n const mime = detectImageMime(bin.data)\r\n if (!mime) {\r\n warnings.push({ page: block.pageNumber, message: `BinData ${binId}: 알 수 없는 이미지 형식`, code: \"SKIPPED_IMAGE\" })\r\n block.type = \"paragraph\"; block.text = `[이미지: ${bin.name}]`; continue\r\n }\r\n imageIndex++\r\n const ext = mime.includes(\"jpeg\") ? \"jpg\" : mime.includes(\"png\") ? \"png\" : mime.includes(\"gif\") ? \"gif\" : mime.includes(\"bmp\") ? \"bmp\" : \"bin\"\r\n const filename = `image_${String(imageIndex).padStart(3, \"0\")}.${ext}`\r\n images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime })\r\n block.text = filename\r\n block.imageData = { data: new Uint8Array(bin.data), mimeType: mime, filename: bin.name }\r\n }\r\n return images\r\n}\r\n\r\nfunction parseSection(records: HwpRecord[], docInfo: HwpDocInfo | null, warnings: ParseWarning[], sectionNum: number): IRBlock[] {\r\n const blocks: IRBlock[] = []\r\n let i = 0\r\n\r\n while (i < records.length) {\r\n const rec = records[i]\r\n\r\n if (rec.tagId === TAG_PARA_HEADER && rec.level === 0) {\r\n const { paragraph, tables, nextIdx, charShapeIds, paraShapeId } = parseParagraphWithTables(records, i)\r\n if (paragraph) {\r\n const block: IRBlock = { type: \"paragraph\", text: paragraph, pageNumber: sectionNum }\r\n // CHAR_SHAPE 기반 스타일 정보 추가\r\n if (docInfo && charShapeIds.length > 0) {\r\n const style = resolveCharStyle(charShapeIds, docInfo)\r\n if (style) block.style = style\r\n }\r\n // PARA_SHAPE 개요 수준으로 heading 즉시 설정\r\n if (docInfo && paraShapeId >= 0 && paraShapeId < docInfo.paraShapes.length) {\r\n const ol = docInfo.paraShapes[paraShapeId].outlineLevel\r\n if (ol >= 1 && ol <= 6) {\r\n block.type = \"heading\"\r\n block.level = ol\r\n }\r\n }\r\n blocks.push(block)\r\n }\r\n for (const t of tables) blocks.push({ type: \"table\", table: t, pageNumber: sectionNum })\r\n i = nextIdx\r\n continue\r\n }\r\n\r\n if (rec.tagId === TAG_CTRL_HEADER && rec.level <= 1 && rec.data.length >= 4) {\r\n const ctrlId = rec.data.subarray(0, 4).toString(\"ascii\")\r\n if (ctrlId === \" lbt\" || ctrlId === \"tbl \") {\r\n const { table, nextIdx } = parseTableBlock(records, i)\r\n if (table) blocks.push({ type: \"table\", table, pageNumber: sectionNum })\r\n i = nextIdx\r\n continue\r\n }\r\n // 그리기 객체(gso) — 이미지 또는 글상자\r\n if (ctrlId === \"gso \" || ctrlId === \" osg\") {\r\n const binId = extractBinDataId(records, i)\r\n if (binId >= 0) {\r\n blocks.push({ type: \"image\", text: String(binId), pageNumber: sectionNum })\r\n } else {\r\n // 이미지가 아니면 글상자(TextBox) 텍스트 추출 시도\r\n const boxText = extractTextBoxText(records, i)\r\n if (boxText) {\r\n blocks.push({ type: \"paragraph\", text: boxText, pageNumber: sectionNum })\r\n }\r\n // 텍스트도 없으면 조용히 스킵 (장식용 도형)\r\n }\r\n } else if (ctrlId === \" elo\" || ctrlId === \"ole \") {\r\n warnings.push({ page: sectionNum, message: `스킵된 제어 요소: ${ctrlId.trim()}`, code: \"SKIPPED_IMAGE\" })\r\n }\r\n // 각주/미주 — CTRL_HEADER 아래의 텍스트를 추출하여 footnoteText로 연결\r\n else if (ctrlId === \"fn \" || ctrlId === \" nf \" || ctrlId === \"en \" || ctrlId === \" ne \") {\r\n const noteText = extractNoteText(records, i)\r\n if (noteText && blocks.length > 0) {\r\n // 직전 paragraph 블록에 footnoteText 연결\r\n const lastBlock = blocks[blocks.length - 1]\r\n if (lastBlock.type === \"paragraph\") {\r\n lastBlock.footnoteText = lastBlock.footnoteText\r\n ? lastBlock.footnoteText + \"; \" + noteText\r\n : noteText\r\n }\r\n }\r\n }\r\n // 하이퍼링크 — CTRL_HEADER 데이터에서 URL 추출\r\n else if (ctrlId === \"%tok\" || ctrlId === \"klnk\") {\r\n const url = extractHyperlinkUrl(rec.data)\r\n if (url && blocks.length > 0) {\r\n const lastBlock = blocks[blocks.length - 1]\r\n if (lastBlock.type === \"paragraph\" && !lastBlock.href) {\r\n lastBlock.href = sanitizeHref(url) ?? undefined\r\n }\r\n }\r\n }\r\n }\r\n\r\n i++\r\n }\r\n\r\n return blocks\r\n}\r\n\r\n/** 각주/미주 CTRL_HEADER 아래의 본문 텍스트 추출 */\r\nfunction extractNoteText(records: HwpRecord[], ctrlIdx: number): string | null {\r\n const ctrlLevel = records[ctrlIdx].level\r\n const texts: string[] = []\r\n\r\n for (let j = ctrlIdx + 1; j < records.length && j < ctrlIdx + 100; j++) {\r\n const r = records[j]\r\n if (r.level <= ctrlLevel) break // 상위 레벨 도달 → 이 컨트롤 블록 끝\r\n\r\n if (r.tagId === TAG_PARA_TEXT) {\r\n const t = extractText(r.data).trim()\r\n if (t) texts.push(t)\r\n }\r\n }\r\n\r\n return texts.length > 0 ? texts.join(\" \") : null\r\n}\r\n\r\n/** 글상자(TextBox) 제어 요소 아래의 텍스트 추출 — extractNoteText와 동일 패턴 */\r\nfunction extractTextBoxText(records: HwpRecord[], ctrlIdx: number): string | null {\r\n const ctrlLevel = records[ctrlIdx].level\r\n const texts: string[] = []\r\n\r\n for (let j = ctrlIdx + 1; j < records.length && j < ctrlIdx + 200; j++) {\r\n const r = records[j]\r\n if (r.level <= ctrlLevel) break\r\n\r\n if (r.tagId === TAG_PARA_TEXT) {\r\n const t = extractText(r.data).trim()\r\n if (t) texts.push(t)\r\n }\r\n }\r\n\r\n return texts.length > 0 ? texts.join(\"\\n\") : null\r\n}\r\n\r\n/** 하이퍼링크 CTRL_HEADER에서 URL 추출 (best-effort) */\r\nfunction extractHyperlinkUrl(data: Buffer): string | null {\r\n // HWP5 하이퍼링크 CTRL_HEADER 구조:\r\n // ctrlId(4) + 기타 필드들... + URL 문자열 (UTF-16LE, length-prefixed)\r\n // 정확한 오프셋은 버전마다 다를 수 있으므로 URL 패턴 스캔으로 폴백\r\n try {\r\n // UTF-16LE에서 \"http\" 시그니처 스캔\r\n const httpSig = Buffer.from(\"http\", \"utf16le\") // \"h\\0t\\0t\\0p\\0\"\r\n const idx = data.indexOf(httpSig)\r\n if (idx >= 0) {\r\n // null terminator(0x0000 0x0000)까지 UTF-16LE로 읽기\r\n let end = idx\r\n while (end + 1 < data.length) {\r\n const ch = data.readUInt16LE(end)\r\n if (ch === 0) break\r\n end += 2\r\n }\r\n const url = data.subarray(idx, end).toString(\"utf16le\")\r\n // 기본 URL 검증\r\n if (/^https?:\\/\\/.+/.test(url) && url.length < 2000) {\r\n return url\r\n }\r\n }\r\n } catch { /* best-effort */ }\r\n return null\r\n}\r\n\r\n/** CHAR_SHAPE ID 배열에서 대표 스타일 결정 (최빈값) */\r\nfunction resolveCharStyle(charShapeIds: number[], docInfo: HwpDocInfo): InlineStyle | undefined {\r\n if (charShapeIds.length === 0 || docInfo.charShapes.length === 0) return undefined\r\n\r\n // 가장 많이 나타나는 charShapeId 사용\r\n const freq = new Map<number, number>()\r\n let maxCount = 0, dominantId = charShapeIds[0]\r\n for (const id of charShapeIds) {\r\n const count = (freq.get(id) || 0) + 1\r\n freq.set(id, count)\r\n if (count > maxCount) { maxCount = count; dominantId = id }\r\n }\r\n\r\n const cs = docInfo.charShapes[dominantId]\r\n if (!cs) return undefined\r\n\r\n const style: InlineStyle = {}\r\n if (cs.fontSize > 0) style.fontSize = cs.fontSize / 10 // 0.1pt → pt\r\n if (cs.attrFlags & 0x01) style.italic = true\r\n if (cs.attrFlags & 0x02) style.bold = true\r\n\r\n return (style.fontSize || style.bold || style.italic) ? style : undefined\r\n}\r\n\r\nfunction parseParagraphWithTables(records: HwpRecord[], startIdx: number) {\r\n const startLevel = records[startIdx].level\r\n let text = \"\"\r\n const tables: ReturnType<typeof buildTable>[] = []\r\n const charShapeIds: number[] = []\r\n\r\n // PARA_HEADER에서 paraShapeId 추출 (offset 8-9, u16)\r\n const paraHeaderData = records[startIdx].data\r\n const paraShapeId = paraHeaderData.length >= 10 ? paraHeaderData.readUInt16LE(8) : -1\r\n\r\n let i = startIdx + 1\r\n\r\n while (i < records.length) {\r\n const rec = records[i]\r\n if (rec.tagId === TAG_PARA_HEADER && rec.level <= startLevel) break\r\n\r\n if (rec.tagId === TAG_PARA_TEXT) {\r\n text = extractText(rec.data)\r\n }\r\n\r\n // CHAR_SHAPE 레코드 — 문단 내 글자 모양 인덱스 배열\r\n if (rec.tagId === TAG_CHAR_SHAPE && rec.data.length >= 8) {\r\n // 구조: [position(u32) + charShapeId(u32)] * N\r\n for (let offset = 0; offset + 7 < rec.data.length; offset += 8) {\r\n charShapeIds.push(rec.data.readUInt32LE(offset + 4))\r\n }\r\n }\r\n\r\n if (rec.tagId === TAG_CTRL_HEADER && rec.data.length >= 4) {\r\n const ctrlId = rec.data.subarray(0, 4).toString(\"ascii\")\r\n if (ctrlId === \" lbt\" || ctrlId === \"tbl \") {\r\n const { table, nextIdx } = parseTableBlock(records, i)\r\n if (table) tables.push(table)\r\n i = nextIdx\r\n continue\r\n }\r\n }\r\n i++\r\n }\r\n\r\n const trimmed = text.trim()\r\n return { paragraph: trimmed || null, tables, nextIdx: i, charShapeIds, paraShapeId }\r\n}\r\n\r\nfunction parseTableBlock(records: HwpRecord[], startIdx: number) {\r\n const tableLevel = records[startIdx].level\r\n let i = startIdx + 1\r\n let rows = 0, cols = 0\r\n const cells: CellContext[] = []\r\n\r\n while (i < records.length) {\r\n const rec = records[i]\r\n if (rec.tagId === TAG_PARA_HEADER && rec.level <= tableLevel) break\r\n if (rec.tagId === TAG_CTRL_HEADER && rec.level <= tableLevel) break\r\n\r\n if (rec.tagId === TAG_TABLE && rec.data.length >= 8) {\r\n rows = Math.min(rec.data.readUInt16LE(4), MAX_ROWS)\r\n cols = Math.min(rec.data.readUInt16LE(6), MAX_COLS)\r\n }\r\n\r\n if (rec.tagId === TAG_LIST_HEADER) {\r\n const { cell, nextIdx } = parseCellBlock(records, i, tableLevel)\r\n if (cell) cells.push(cell)\r\n i = nextIdx\r\n continue\r\n }\r\n i++\r\n }\r\n\r\n if (rows === 0 || cols === 0 || cells.length === 0) return { table: null, nextIdx: i }\r\n\r\n // colAddr/rowAddr가 있으면 arrangeCells가 이미 완성된 그리드를 반환하므로\r\n // buildTable(2-pass) 없이 직접 IRTable 생성 — 이중 colSpan 확장 방지\r\n const hasAddr = cells.some(c => c.colAddr !== undefined && c.rowAddr !== undefined)\r\n if (hasAddr) {\r\n const cellRows = arrangeCells(rows, cols, cells)\r\n const irCells = cellRows.map(row => row.map(c => ({\r\n text: c.text.trim(),\r\n colSpan: c.colSpan,\r\n rowSpan: c.rowSpan,\r\n })))\r\n return { table: { rows, cols, cells: irCells, hasHeader: rows > 1 }, nextIdx: i }\r\n }\r\n\r\n const cellRows = arrangeCells(rows, cols, cells)\r\n return { table: buildTable(cellRows), nextIdx: i }\r\n}\r\n\r\nfunction parseCellBlock(records: HwpRecord[], startIdx: number, tableLevel: number) {\r\n const rec = records[startIdx]\r\n const cellLevel = rec.level\r\n const texts: string[] = []\r\n\r\n // LIST_HEADER에서 셀 위치 및 병합 정보 추출\r\n // HWP5 셀 LIST_HEADER 구조:\r\n // paraCount(u16) + flags(u32) + width(u16) + colAddr(u16) + rowAddr(u16) + colSpan(u16) + rowSpan(u16)\r\n // offset: 0 2 6 8 10 12 14\r\n let colSpan = 1\r\n let rowSpan = 1\r\n let colAddr: number | undefined\r\n let rowAddr: number | undefined\r\n if (rec.data.length >= 16) {\r\n colAddr = rec.data.readUInt16LE(8)\r\n rowAddr = rec.data.readUInt16LE(10)\r\n const cs = rec.data.readUInt16LE(12)\r\n const rs = rec.data.readUInt16LE(14)\r\n if (cs > 0) colSpan = Math.min(cs, MAX_COLS)\r\n if (rs > 0) rowSpan = Math.min(rs, MAX_ROWS)\r\n }\r\n\r\n let i = startIdx + 1\r\n\r\n while (i < records.length) {\r\n const r = records[i]\r\n if (r.tagId === TAG_LIST_HEADER && r.level <= cellLevel) break\r\n if (r.level <= tableLevel && (r.tagId === TAG_PARA_HEADER || r.tagId === TAG_CTRL_HEADER)) break\r\n\r\n if (r.tagId === TAG_PARA_TEXT) {\r\n const t = extractText(r.data).trim()\r\n if (t) texts.push(t)\r\n }\r\n i++\r\n }\r\n\r\n return { cell: { text: texts.join(\"\\n\"), colSpan, rowSpan, colAddr, rowAddr } as CellContext, nextIdx: i }\r\n}\r\n\r\nfunction arrangeCells(rows: number, cols: number, cells: CellContext[]): CellContext[][] {\r\n const grid: (CellContext | null)[][] = Array.from({ length: rows }, () => Array(cols).fill(null))\r\n\r\n // colAddr/rowAddr가 있으면 직접 배치 (HWP5 병합 테이블 정확도 향상)\r\n const hasAddr = cells.some(c => c.colAddr !== undefined && c.rowAddr !== undefined)\r\n\r\n if (hasAddr) {\r\n for (const cell of cells) {\r\n const r = cell.rowAddr ?? 0\r\n const c = cell.colAddr ?? 0\r\n if (r >= rows || c >= cols) continue\r\n grid[r][c] = cell\r\n\r\n for (let dr = 0; dr < cell.rowSpan; dr++) {\r\n for (let dc = 0; dc < cell.colSpan; dc++) {\r\n if (dr === 0 && dc === 0) continue\r\n if (r + dr < rows && c + dc < cols)\r\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\r\n }\r\n }\r\n }\r\n } else {\r\n // fallback: 순차 배치 (colAddr 없는 경우)\r\n let cellIdx = 0\r\n for (let r = 0; r < rows && cellIdx < cells.length; r++) {\r\n for (let c = 0; c < cols && cellIdx < cells.length; c++) {\r\n if (grid[r][c] !== null) continue\r\n const cell = cells[cellIdx++]\r\n grid[r][c] = cell\r\n\r\n for (let dr = 0; dr < cell.rowSpan; dr++) {\r\n for (let dc = 0; dc < cell.colSpan; dc++) {\r\n if (dr === 0 && dc === 0) continue\r\n if (r + dr < rows && c + dc < cols)\r\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return grid.map(row => row.map(c => c || { text: \"\", colSpan: 1, rowSpan: 1 }))\r\n}\r\n","/**\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","/**\n * kordoc — 모두 파싱해버리겠다\n *\n * HWP, HWPX, PDF → Markdown 변환 통합 라이브러리\n */\n\nimport { readFile } from \"fs/promises\"\nimport { detectFormat, detectZipFormat, isHwpxFile, isOldHwpFile, isPdfFile, isZipFile } from \"./detect.js\"\nimport { parseHwpxDocument } from \"./hwpx/parser.js\"\nimport { parseHwp5Document } from \"./hwp5/parser.js\"\nimport { parsePdfDocument } from \"./pdf/parser.js\"\nimport { parseXlsxDocument } from \"./xlsx/parser.js\"\nimport { parseDocxDocument } from \"./docx/parser.js\"\nimport type { ParseResult, ParseOptions } from \"./types.js\"\nimport { classifyError, toArrayBuffer } from \"./utils.js\"\n\n// ─── 메인 API ────────────────────────────────────────\n\n/**\n * 파일 버퍼를 자동 감지하여 Markdown으로 변환\n *\n * @example\n * ```ts\n * import { parse } from \"kordoc\"\n * // 파일 경로로 파싱\n * const result = await parse(\"document.hwp\")\n * // 또는 Buffer로 파싱\n * const result = await parse(buffer)\n * ```\n */\nexport async function parse(input: string | ArrayBuffer | Buffer, options?: ParseOptions): Promise<ParseResult> {\n let buffer: ArrayBuffer\n if (typeof input === \"string\") {\n try {\n const buf = await readFile(input)\n buffer = toArrayBuffer(buf)\n } catch (err) {\n const msg = err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\"\n ? `파일을 찾을 수 없습니다: ${input}`\n : `파일 읽기 실패: ${input}`\n return { success: false, fileType: \"unknown\", error: msg, code: \"PARSE_ERROR\" }\n }\n } else if (Buffer.isBuffer(input)) {\n buffer = toArrayBuffer(input)\n } else {\n buffer = input\n }\n\n if (!buffer || buffer.byteLength === 0) {\n return { success: false, fileType: \"unknown\", error: \"빈 버퍼이거나 유효하지 않은 입력입니다.\", code: \"EMPTY_INPUT\" }\n }\n const format = detectFormat(buffer)\n\n switch (format) {\n case \"hwpx\": {\n // ZIP 기반 포맷 세분화: HWPX, XLSX, DOCX 구분\n const zipFormat = await detectZipFormat(buffer)\n if (zipFormat === \"xlsx\") return parseXlsx(buffer, options)\n if (zipFormat === \"docx\") return parseDocx(buffer, options)\n return parseHwpx(buffer, options)\n }\n case \"hwp\":\n return parseHwp(buffer, options)\n case \"pdf\":\n return parsePdf(buffer, options)\n default:\n return { success: false, fileType: \"unknown\", error: \"지원하지 않는 파일 형식입니다.\", code: \"UNSUPPORTED_FORMAT\" }\n }\n}\n\n// ─── 포맷별 API ──────────────────────────────────────\n\n/** HWPX 파일을 Markdown으로 변환 */\nexport async function parseHwpx(buffer: ArrayBuffer, options?: ParseOptions): Promise<ParseResult> {\n try {\n const { markdown, blocks, metadata, outline, warnings, images } = await parseHwpxDocument(buffer, options)\n return { success: true, fileType: \"hwpx\", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : undefined }\n } catch (err) {\n return { success: false, fileType: \"hwpx\", error: err instanceof Error ? err.message : \"HWPX 파싱 실패\", code: classifyError(err) }\n }\n}\n\n/** HWP 5.x 바이너리 파일을 Markdown으로 변환 */\nexport async function parseHwp(buffer: ArrayBuffer, options?: ParseOptions): Promise<ParseResult> {\n try {\n const { markdown, blocks, metadata, outline, warnings, images } = parseHwp5Document(Buffer.from(buffer), options)\n return { success: true, fileType: \"hwp\", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : undefined }\n } catch (err) {\n return { success: false, fileType: \"hwp\", error: err instanceof Error ? err.message : \"HWP 파싱 실패\", code: classifyError(err) }\n }\n}\n\n/** PDF 파일에서 텍스트를 추출하여 Markdown으로 변환 */\nexport async function parsePdf(buffer: ArrayBuffer, options?: ParseOptions): Promise<ParseResult> {\n try {\n const { markdown, blocks, metadata, outline, warnings, isImageBased } = await parsePdfDocument(buffer, options)\n return { success: true, fileType: \"pdf\", markdown, blocks, metadata, outline, warnings, isImageBased }\n } catch (err) {\n const isImageBased = err instanceof Error && \"isImageBased\" in err ? true : undefined\n return { success: false, fileType: \"pdf\", error: err instanceof Error ? err.message : \"PDF 파싱 실패\", code: classifyError(err), isImageBased }\n }\n}\n\n/** XLSX 파일을 Markdown으로 변환 */\nexport async function parseXlsx(buffer: ArrayBuffer, options?: ParseOptions): Promise<ParseResult> {\n try {\n const { markdown, blocks, metadata, warnings } = await parseXlsxDocument(buffer, options)\n return { success: true, fileType: \"xlsx\", markdown, blocks, metadata, warnings }\n } catch (err) {\n return { success: false, fileType: \"xlsx\", error: err instanceof Error ? err.message : \"XLSX 파싱 실패\", code: classifyError(err) }\n }\n}\n\n/** DOCX 파일을 Markdown으로 변환 */\nexport async function parseDocx(buffer: ArrayBuffer, options?: ParseOptions): Promise<ParseResult> {\n try {\n const { markdown, blocks, metadata, outline, warnings, images } = await parseDocxDocument(buffer, options)\n return { success: true, fileType: \"docx\", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : undefined }\n } catch (err) {\n return { success: false, fileType: \"docx\", error: err instanceof Error ? err.message : \"DOCX 파싱 실패\", code: classifyError(err) }\n }\n}\n\n// ─── 게임체인저 API ─────────────────────────────────\n\nexport { compare, diffBlocks } from \"./diff/compare.js\"\nexport { extractFormFields } from \"./form/recognize.js\"\nexport { markdownToHwpx } from \"./hwpx/generator.js\"\n\n// ─── Re-exports ──────────────────────────────────────\n\nexport { detectFormat, detectZipFormat, isHwpxFile, isOldHwpFile, isPdfFile, isZipFile } from \"./detect.js\"\nexport type {\n ParseResult, ParseSuccess, ParseFailure, FileType,\n IRBlock, IRBlockType, IRTable, IRCell, CellContext,\n BoundingBox, InlineStyle, ImageData, ExtractedImage,\n DocumentMetadata, ParseOptions, ErrorCode,\n ParseWarning, WarningCode, OutlineItem,\n DiffResult, BlockDiff, CellDiff, DiffChangeType,\n FormField, FormResult,\n OcrProvider, WatchOptions,\n} from \"./types.js\"\nexport { blocksToMarkdown } from \"./table/builder.js\"\nexport { VERSION } from \"./utils.js\"\n","/**\n * XLSX (Office Open XML Spreadsheet) 파서\n *\n * ZIP + XML 구조를 jszip + xmldom으로 파싱하여 IRBlock[]로 변환.\n * 각 시트 → heading(시트명) + table(데이터) 블록.\n */\n\nimport JSZip from \"jszip\"\nimport { DOMParser } from \"@xmldom/xmldom\"\nimport type {\n IRBlock, IRTable, IRCell, CellContext, DocumentMetadata, InternalParseResult,\n ParseOptions, ParseWarning, ExtractedImage,\n} from \"../types.js\"\nimport { KordocError, precheckZipSize, stripDtd } from \"../utils.js\"\nimport { buildTable, blocksToMarkdown } from \"../table/builder.js\"\n\n// ─── 상수 ────────────────────────────────────────────\n\nconst MAX_SHEETS = 100\n/** ZIP 압축 해제 누적 최대 크기 (100MB) — ZIP bomb 방지 */\nconst MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024\nconst MAX_ROWS = 10000\nconst MAX_COLS = 200\n\n// ─── 숫자값 정리 ──────────────────────────────────────\n\n/** 부동소수점 아티팩트 정리 (132.30000000000001 → 132.3) */\nfunction cleanNumericValue(raw: string): string {\n if (!/^-?\\d+\\.\\d+$/.test(raw)) return raw\n const num = parseFloat(raw)\n if (!isFinite(num)) return raw\n // toPrecision(15)로 IEEE 754 오차 제거 후 불필요한 후행 0 제거\n const cleaned = parseFloat(num.toPrecision(15)).toString()\n return cleaned\n}\n\n// ─── 셀 참조 파싱 ──────────────────────────────────────\n\n/** \"A1\" → { col: 0, row: 0 }, \"AB123\" → { col: 27, row: 122 } */\nfunction parseCellRef(ref: string): { col: number; row: number } | null {\n const m = ref.match(/^([A-Z]+)(\\d+)$/)\n if (!m) return null\n let col = 0\n for (const ch of m[1]) col = col * 26 + (ch.charCodeAt(0) - 64)\n return { col: col - 1, row: parseInt(m[2], 10) - 1 }\n}\n\n/** \"A1:C3\" → { startCol, startRow, endCol, endRow } */\nfunction parseMergeRef(ref: string): { startCol: number; startRow: number; endCol: number; endRow: number } | null {\n const parts = ref.split(\":\")\n if (parts.length !== 2) return null\n const start = parseCellRef(parts[0])\n const end = parseCellRef(parts[1])\n if (!start || !end) return null\n return { startCol: start.col, startRow: start.row, endCol: end.col, endRow: end.row }\n}\n\n// ─── XML 헬퍼 ──────────────────────────────────────────\n\nfunction getElements(parent: Element, tagName: string): Element[] {\n const nodes = parent.getElementsByTagName(tagName)\n const result: Element[] = []\n for (let i = 0; i < nodes.length; i++) result.push(nodes[i] as Element)\n return result\n}\n\nfunction getTextContent(el: Element): string {\n return el.textContent?.trim() ?? \"\"\n}\n\nfunction parseXml(text: string): Document {\n return new DOMParser().parseFromString(stripDtd(text), \"text/xml\")\n}\n\n// ─── 공유 문자열 파싱 ──────────────────────────────────\n\nfunction parseSharedStrings(xml: string): string[] {\n const doc = parseXml(xml)\n const strings: string[] = []\n const siList = getElements(doc.documentElement, \"si\")\n for (const si of siList) {\n // <si><t>text</t></si> 또는 <si><r><t>text</t></r>...</si>\n const tElements = getElements(si, \"t\")\n strings.push(tElements.map(t => t.textContent ?? \"\").join(\"\"))\n }\n return strings\n}\n\n// ─── 시트 목록 파싱 ─────────────────────────────────────\n\ninterface SheetInfo {\n name: string\n sheetId: string\n rId: string\n}\n\nfunction parseWorkbook(xml: string): SheetInfo[] {\n const doc = parseXml(xml)\n const sheets: SheetInfo[] = []\n const sheetElements = getElements(doc.documentElement, \"sheet\")\n for (const el of sheetElements) {\n sheets.push({\n name: el.getAttribute(\"name\") ?? `Sheet${sheets.length + 1}`,\n sheetId: el.getAttribute(\"sheetId\") ?? \"\",\n rId: el.getAttribute(\"r:id\") ?? \"\",\n })\n }\n return sheets\n}\n\n/** workbook.xml.rels 파싱 → rId → target 매핑 */\nfunction parseRels(xml: string): Map<string, string> {\n const doc = parseXml(xml)\n const map = new Map<string, string>()\n const rels = getElements(doc.documentElement, \"Relationship\")\n for (const rel of rels) {\n const id = rel.getAttribute(\"Id\")\n const target = rel.getAttribute(\"Target\")\n if (id && target) map.set(id, target)\n }\n return map\n}\n\n// ─── 워크시트 파싱 ──────────────────────────────────────\n\ninterface MergeInfo {\n startCol: number\n startRow: number\n endCol: number\n endRow: number\n}\n\nfunction parseWorksheet(\n xml: string,\n sharedStrings: string[],\n): { grid: string[][]; merges: MergeInfo[]; maxRow: number; maxCol: number } {\n const doc = parseXml(xml)\n const grid: string[][] = []\n let maxRow = 0\n let maxCol = 0\n\n // 데이터 행 파싱\n const rows = getElements(doc.documentElement, \"row\")\n for (const rowEl of rows) {\n const rowNum = parseInt(rowEl.getAttribute(\"r\") ?? \"0\", 10) - 1\n if (rowNum < 0 || rowNum >= MAX_ROWS) continue\n\n const cells = getElements(rowEl, \"c\")\n for (const cellEl of cells) {\n const ref = cellEl.getAttribute(\"r\")\n if (!ref) continue\n const pos = parseCellRef(ref)\n if (!pos || pos.col >= MAX_COLS) continue\n\n // 값 추출\n const type = cellEl.getAttribute(\"t\")\n const vElements = getElements(cellEl, \"v\")\n const fElements = getElements(cellEl, \"f\")\n let value = \"\"\n\n if (vElements.length > 0) {\n const raw = getTextContent(vElements[0])\n if (type === \"s\") {\n // shared string\n const idx = parseInt(raw, 10)\n value = sharedStrings[idx] ?? \"\"\n } else if (type === \"b\") {\n value = raw === \"1\" ? \"TRUE\" : \"FALSE\"\n } else {\n // 숫자값 부동소수점 아티팩트 정리 (9895607.8000000007 → 9895607.8)\n value = cleanNumericValue(raw)\n }\n } else if (type === \"inlineStr\") {\n // <is><t>text</t></is>\n const isEl = getElements(cellEl, \"is\")\n if (isEl.length > 0) {\n const tElements = getElements(isEl[0], \"t\")\n value = tElements.map(t => t.textContent ?? \"\").join(\"\")\n }\n }\n\n // 수식이 있고 값이 없으면 수식 표시\n if (!value && fElements.length > 0) {\n value = `=${getTextContent(fElements[0])}`\n }\n\n // 그리드 확장\n while (grid.length <= pos.row) grid.push([])\n while (grid[pos.row].length <= pos.col) grid[pos.row].push(\"\")\n grid[pos.row][pos.col] = value\n\n if (pos.row > maxRow) maxRow = pos.row\n if (pos.col > maxCol) maxCol = pos.col\n }\n }\n\n // 병합 셀 파싱\n const merges: MergeInfo[] = []\n const mergeCellElements = getElements(doc.documentElement, \"mergeCell\")\n for (const el of mergeCellElements) {\n const ref = el.getAttribute(\"ref\")\n if (!ref) continue\n const m = parseMergeRef(ref)\n if (m) merges.push(m)\n }\n\n return { grid, merges, maxRow, maxCol }\n}\n\n// ─── 시트 → IRBlock[] 변환 ────────────────────────────\n\nfunction sheetToBlocks(\n sheetName: string,\n grid: string[][],\n merges: MergeInfo[],\n maxRow: number,\n maxCol: number,\n sheetIndex: number,\n): IRBlock[] {\n const blocks: IRBlock[] = []\n\n // 시트명 = heading\n if (sheetName) {\n blocks.push({\n type: \"heading\",\n text: sheetName,\n level: 2,\n pageNumber: sheetIndex + 1,\n })\n }\n\n // 빈 시트\n if (maxRow < 0 || maxCol < 0 || grid.length === 0) return blocks\n\n // 병합 맵: \"row,col\" → { colSpan, rowSpan }\n const mergeMap = new Map<string, { colSpan: number; rowSpan: number }>()\n const mergeSkip = new Set<string>()\n for (const m of merges) {\n const colSpan = m.endCol - m.startCol + 1\n const rowSpan = m.endRow - m.startRow + 1\n mergeMap.set(`${m.startRow},${m.startCol}`, { colSpan, rowSpan })\n for (let r = m.startRow; r <= m.endRow; r++) {\n for (let c = m.startCol; c <= m.endCol; c++) {\n if (r !== m.startRow || c !== m.startCol) {\n mergeSkip.add(`${r},${c}`)\n }\n }\n }\n }\n\n // 유효 행 범위 감지 (앞뒤 빈 행 제거)\n let firstRow = -1\n let lastRow = -1\n for (let r = 0; r <= maxRow; r++) {\n const row = grid[r]\n if (row && row.some(cell => cell !== \"\")) {\n if (firstRow === -1) firstRow = r\n lastRow = r\n }\n }\n if (firstRow === -1) return blocks\n\n // CellContext[][] → buildTable로 IRTable 생성 (2-pass 알고리즘 재사용)\n const cellRows: CellContext[][] = []\n\n for (let r = firstRow; r <= lastRow; r++) {\n const row: CellContext[] = []\n for (let c = 0; c <= maxCol; c++) {\n const key = `${r},${c}`\n if (mergeSkip.has(key)) continue\n\n const text = (grid[r] && grid[r][c]) ?? \"\"\n const merge = mergeMap.get(key)\n row.push({\n text,\n colSpan: merge?.colSpan ?? 1,\n rowSpan: merge?.rowSpan ?? 1,\n })\n }\n cellRows.push(row)\n }\n\n if (cellRows.length > 0) {\n const table = buildTable(cellRows)\n if (table.rows > 0) {\n blocks.push({ type: \"table\", table, pageNumber: sheetIndex + 1 })\n }\n }\n\n return blocks\n}\n\n// ─── 메인 파서 ─────────────────────────────────────────\n\nexport async function parseXlsxDocument(\n buffer: ArrayBuffer,\n options?: ParseOptions,\n): Promise<InternalParseResult> {\n // ZIP bomb 사전 검사\n precheckZipSize(buffer, MAX_DECOMPRESS_SIZE)\n\n const zip = await JSZip.loadAsync(buffer)\n const warnings: ParseWarning[] = []\n\n // XLSX 구조 검증\n const workbookFile = zip.file(\"xl/workbook.xml\")\n if (!workbookFile) {\n throw new KordocError(\"유효하지 않은 XLSX 파일: xl/workbook.xml이 없습니다\")\n }\n\n // 1. 공유 문자열 로드\n let sharedStrings: string[] = []\n const ssFile = zip.file(\"xl/sharedStrings.xml\")\n if (ssFile) {\n sharedStrings = parseSharedStrings(await ssFile.async(\"text\"))\n }\n\n // 2. 시트 목록 로드\n const sheets = parseWorkbook(await workbookFile.async(\"text\"))\n if (sheets.length === 0) {\n throw new KordocError(\"XLSX 파일에 시트가 없습니다\")\n }\n\n // 3. 관계 매핑 (rId → 파일 경로)\n let relsMap = new Map<string, string>()\n const relsFile = zip.file(\"xl/_rels/workbook.xml.rels\")\n if (relsFile) {\n relsMap = parseRels(await relsFile.async(\"text\"))\n }\n\n // 4. 페이지 필터\n let pageFilter: Set<number> | null = null\n if (options?.pages) {\n const { parsePageRange } = await import(\"../page-range.js\")\n pageFilter = parsePageRange(options.pages, sheets.length)\n }\n\n // 5. 각 시트 파싱\n const blocks: IRBlock[] = []\n const processedSheets = Math.min(sheets.length, MAX_SHEETS)\n\n for (let i = 0; i < processedSheets; i++) {\n if (pageFilter && !pageFilter.has(i + 1)) continue\n\n const sheet = sheets[i]\n options?.onProgress?.(i + 1, processedSheets)\n\n // 시트 파일 경로 결정\n let sheetPath = relsMap.get(sheet.rId)\n if (sheetPath) {\n // 상대 경로 → 절대 경로\n if (!sheetPath.startsWith(\"xl/\") && !sheetPath.startsWith(\"/\")) {\n sheetPath = `xl/${sheetPath}`\n } else if (sheetPath.startsWith(\"/\")) {\n sheetPath = sheetPath.slice(1)\n }\n } else {\n sheetPath = `xl/worksheets/sheet${i + 1}.xml`\n }\n\n const sheetFile = zip.file(sheetPath)\n if (!sheetFile) {\n warnings.push({\n page: i + 1,\n message: `시트 \"${sheet.name}\" 파일을 찾을 수 없습니다: ${sheetPath}`,\n code: \"PARTIAL_PARSE\",\n })\n continue\n }\n\n try {\n const sheetXml = await sheetFile.async(\"text\")\n const { grid, merges, maxRow, maxCol } = parseWorksheet(sheetXml, sharedStrings)\n const sheetBlocks = sheetToBlocks(sheet.name, grid, merges, maxRow, maxCol, i)\n blocks.push(...sheetBlocks)\n } catch (err) {\n warnings.push({\n page: i + 1,\n message: `시트 \"${sheet.name}\" 파싱 실패: ${err instanceof Error ? err.message : \"알 수 없는 오류\"}`,\n code: \"PARTIAL_PARSE\",\n })\n }\n }\n\n // 6. 메타데이터 추출\n const metadata: DocumentMetadata = {\n pageCount: processedSheets,\n }\n const coreFile = zip.file(\"docProps/core.xml\")\n if (coreFile) {\n try {\n const coreXml = await coreFile.async(\"text\")\n const doc = parseXml(coreXml)\n const getFirst = (tag: string) => {\n const els = doc.getElementsByTagName(tag)\n return els.length > 0 ? (els[0].textContent ?? \"\").trim() : undefined\n }\n metadata.title = getFirst(\"dc:title\") || getFirst(\"dcterms:title\")\n metadata.author = getFirst(\"dc:creator\")\n metadata.description = getFirst(\"dc:description\")\n const created = getFirst(\"dcterms:created\")\n if (created) metadata.createdAt = created\n const modified = getFirst(\"dcterms:modified\")\n if (modified) metadata.modifiedAt = modified\n } catch { /* 메타데이터 실패는 무시 */ }\n }\n\n const markdown = blocksToMarkdown(blocks)\n\n return { markdown, blocks, metadata, warnings: warnings.length > 0 ? warnings : undefined }\n}\n","/**\n * DOCX (Office Open XML Document) 파서\n *\n * ZIP + XML 구조를 jszip + xmldom으로 파싱하여 IRBlock[]로 변환.\n * w:p → paragraph/heading, w:tbl → table, w:drawing → image.\n */\n\nimport JSZip from \"jszip\"\nimport { DOMParser } from \"@xmldom/xmldom\"\nimport type {\n IRBlock, IRTable, IRCell, DocumentMetadata, InternalParseResult,\n ParseOptions, ParseWarning, ExtractedImage, InlineStyle,\n} from \"../types.js\"\nimport { KordocError, precheckZipSize, stripDtd } from \"../utils.js\"\nimport { blocksToMarkdown } from \"../table/builder.js\"\n\n/** ZIP 압축 해제 누적 최대 크기 (100MB) — ZIP bomb 방지 */\nconst MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024\n\n// ─── XML 헬퍼 ──────────────────────────────────────────\n\n/** 네임스페이스 무시 태그 검색 — DOCX는 네임스페이스가 많음 */\nfunction getChildElements(parent: Element | Document, localName: string): Element[] {\n const result: Element[] = []\n const children = parent.childNodes\n for (let i = 0; i < children.length; i++) {\n const node = children[i]\n if (node.nodeType === 1) {\n const el = node as Element\n if (el.localName === localName || el.tagName?.endsWith(`:${localName}`)) {\n result.push(el)\n }\n }\n }\n return result\n}\n\n/** 재귀적으로 localName 매칭 — getElementsByTagName 대안 */\nfunction findElements(parent: Element | Document, localName: string): Element[] {\n const result: Element[] = []\n const walk = (node: Element | Document) => {\n const children = node.childNodes\n for (let i = 0; i < children.length; i++) {\n const child = children[i]\n if (child.nodeType === 1) {\n const el = child as Element\n if (el.localName === localName || el.tagName?.endsWith(`:${localName}`)) {\n result.push(el)\n }\n walk(el)\n }\n }\n }\n walk(parent)\n return result\n}\n\nfunction getAttr(el: Element, localName: string): string | null {\n // w:val, r:id 등 네임스페이스 포함 속성\n const attrs = el.attributes\n for (let i = 0; i < attrs.length; i++) {\n const attr = attrs[i]\n if (attr.localName === localName || attr.name === localName) return attr.value\n }\n return null\n}\n\nfunction parseXml(text: string): Document {\n return new DOMParser().parseFromString(stripDtd(text), \"text/xml\")\n}\n\n// ─── 스타일 파싱 ────────────────────────────────────────\n\ninterface StyleInfo {\n name: string\n basedOn?: string\n outlineLevel?: number\n}\n\nfunction parseStyles(xml: string): Map<string, StyleInfo> {\n const doc = parseXml(xml)\n const styles = new Map<string, StyleInfo>()\n const styleElements = findElements(doc, \"style\")\n\n for (const el of styleElements) {\n const styleId = getAttr(el, \"styleId\")\n if (!styleId) continue\n\n const nameEls = getChildElements(el, \"name\")\n const name = nameEls.length > 0 ? (getAttr(nameEls[0], \"val\") ?? \"\") : \"\"\n const basedOnEls = getChildElements(el, \"basedOn\")\n const basedOn = basedOnEls.length > 0 ? (getAttr(basedOnEls[0], \"val\") ?? undefined) : undefined\n\n // outlineLevel으로 heading 감지\n const pPrEls = getChildElements(el, \"pPr\")\n let outlineLevel: number | undefined\n if (pPrEls.length > 0) {\n const outlineEls = getChildElements(pPrEls[0], \"outlineLvl\")\n if (outlineEls.length > 0) {\n const val = getAttr(outlineEls[0], \"val\")\n if (val !== null) outlineLevel = parseInt(val, 10)\n }\n }\n\n // Heading 패턴 매칭\n if (outlineLevel === undefined) {\n const headingMatch = name.match(/^(?:heading|Heading)\\s*(\\d+)$/i)\n if (headingMatch) outlineLevel = parseInt(headingMatch[1], 10) - 1\n }\n\n styles.set(styleId, { name, basedOn, outlineLevel })\n }\n return styles\n}\n\n// ─── 번호 매기기 파싱 ──────────────────────────────────\n\ninterface NumberingInfo {\n numFmt: string // \"decimal\", \"bullet\", etc.\n level: number\n}\n\nfunction parseNumbering(xml: string): Map<string, Map<number, NumberingInfo>> {\n const doc = parseXml(xml)\n const abstractNums = new Map<string, Map<number, NumberingInfo>>()\n\n // abstractNum 파싱\n const abstractElements = findElements(doc, \"abstractNum\")\n for (const el of abstractElements) {\n const abstractNumId = getAttr(el, \"abstractNumId\")\n if (!abstractNumId) continue\n const levels = new Map<number, NumberingInfo>()\n const lvlElements = getChildElements(el, \"lvl\")\n for (const lvl of lvlElements) {\n const ilvl = parseInt(getAttr(lvl, \"ilvl\") ?? \"0\", 10)\n const numFmtEls = getChildElements(lvl, \"numFmt\")\n const numFmt = numFmtEls.length > 0 ? (getAttr(numFmtEls[0], \"val\") ?? \"bullet\") : \"bullet\"\n levels.set(ilvl, { numFmt, level: ilvl })\n }\n abstractNums.set(abstractNumId, levels)\n }\n\n // num → abstractNum 매핑\n const nums = new Map<string, Map<number, NumberingInfo>>()\n const numElements = findElements(doc, \"num\")\n for (const el of numElements) {\n const numId = getAttr(el, \"numId\")\n if (!numId) continue\n const abstractRefs = getChildElements(el, \"abstractNumId\")\n if (abstractRefs.length > 0) {\n const ref = getAttr(abstractRefs[0], \"val\")\n if (ref && abstractNums.has(ref)) {\n nums.set(numId, abstractNums.get(ref)!)\n }\n }\n }\n return nums\n}\n\n// ─── 관계 파싱 ─────────────────────────────────────────\n\nfunction parseRels(xml: string): Map<string, string> {\n const doc = parseXml(xml)\n const map = new Map<string, string>()\n const rels = findElements(doc, \"Relationship\")\n for (const rel of rels) {\n const id = getAttr(rel, \"Id\")\n const target = getAttr(rel, \"Target\")\n if (id && target) map.set(id, target)\n }\n return map\n}\n\n// ─── 각주 파싱 ─────────────────────────────────────────\n\nfunction parseFootnotes(xml: string): Map<string, string> {\n const doc = parseXml(xml)\n const notes = new Map<string, string>()\n const fnElements = findElements(doc, \"footnote\")\n for (const fn of fnElements) {\n const id = getAttr(fn, \"id\")\n if (!id || id === \"0\" || id === \"-1\") continue // 0=separator, -1=continuation\n const texts: string[] = []\n const pElements = findElements(fn, \"p\")\n for (const p of pElements) {\n const runs = findElements(p, \"r\")\n for (const r of runs) {\n const tElements = getChildElements(r, \"t\")\n for (const t of tElements) texts.push(t.textContent ?? \"\")\n }\n }\n notes.set(id, texts.join(\"\").trim())\n }\n return notes\n}\n\n// ─── Run 텍스트 추출 ──────────────────────────────────\n\ninterface RunResult {\n text: string\n bold: boolean\n italic: boolean\n}\n\nfunction extractRun(r: Element): RunResult {\n const tElements = getChildElements(r, \"t\")\n const text = tElements.map(t => t.textContent ?? \"\").join(\"\")\n\n let bold = false\n let italic = false\n const rPrEls = getChildElements(r, \"rPr\")\n if (rPrEls.length > 0) {\n bold = getChildElements(rPrEls[0], \"b\").length > 0\n italic = getChildElements(rPrEls[0], \"i\").length > 0\n }\n\n return { text, bold, italic }\n}\n\n// ─── 단락 파싱 ─────────────────────────────────────────\n\nfunction parseParagraph(\n p: Element,\n styles: Map<string, StyleInfo>,\n numbering: Map<string, Map<number, NumberingInfo>>,\n footnotes: Map<string, string>,\n rels: Map<string, string>,\n): IRBlock | null {\n // 스타일 확인\n const pPrEls = getChildElements(p, \"pPr\")\n let styleId = \"\"\n let numId = \"\"\n let ilvl = 0\n\n if (pPrEls.length > 0) {\n const pStyleEls = getChildElements(pPrEls[0], \"pStyle\")\n if (pStyleEls.length > 0) styleId = getAttr(pStyleEls[0], \"val\") ?? \"\"\n\n const numPrEls = getChildElements(pPrEls[0], \"numPr\")\n if (numPrEls.length > 0) {\n const numIdEls = getChildElements(numPrEls[0], \"numId\")\n const ilvlEls = getChildElements(numPrEls[0], \"ilvl\")\n numId = numIdEls.length > 0 ? (getAttr(numIdEls[0], \"val\") ?? \"\") : \"\"\n ilvl = ilvlEls.length > 0 ? parseInt(getAttr(ilvlEls[0], \"val\") ?? \"0\", 10) : 0\n }\n }\n\n // 텍스트 수집\n const parts: string[] = []\n let hasBold = false\n let hasItalic = false\n let href: string | undefined\n let footnoteText: string | undefined\n\n // 하이퍼링크 처리\n const hyperlinks = getChildElements(p, \"hyperlink\")\n const hyperlinkTexts = new Set<string>()\n\n for (const hl of hyperlinks) {\n const rId = getAttr(hl, \"id\")\n const hlText: string[] = []\n const runs = findElements(hl, \"r\")\n for (const r of runs) {\n const result = extractRun(r)\n hlText.push(result.text)\n }\n const text = hlText.join(\"\")\n if (text) {\n hyperlinkTexts.add(text)\n if (rId && rels.has(rId)) {\n href = rels.get(rId)\n parts.push(text)\n } else {\n parts.push(text)\n }\n }\n }\n\n // 일반 run 처리\n const runs = getChildElements(p, \"r\")\n for (const r of runs) {\n // 하이퍼링크 내부 run은 이미 처리됨 — 부모가 hyperlink이면 스킵\n if (r.parentNode && (r.parentNode as Element).localName === \"hyperlink\") continue\n\n const result = extractRun(r)\n if (result.bold) hasBold = true\n if (result.italic) hasItalic = true\n\n // 각주 참조 확인\n const fnRefEls = getChildElements(r, \"footnoteReference\")\n if (fnRefEls.length > 0) {\n const fnId = getAttr(fnRefEls[0], \"id\")\n if (fnId && footnotes.has(fnId)) {\n footnoteText = footnotes.get(fnId)\n }\n }\n\n if (result.text) parts.push(result.text)\n }\n\n const text = parts.join(\"\").trim()\n if (!text) return null\n\n // Heading 판별\n const style = styles.get(styleId)\n if (style?.outlineLevel !== undefined && style.outlineLevel >= 0 && style.outlineLevel <= 5) {\n return {\n type: \"heading\",\n text,\n level: style.outlineLevel + 1,\n }\n }\n\n // 리스트 판별\n if (numId && numId !== \"0\") {\n const numDef = numbering.get(numId)\n const levelInfo = numDef?.get(ilvl)\n const listType = levelInfo?.numFmt === \"bullet\" ? \"unordered\" : \"ordered\"\n return { type: \"list\", text, listType }\n }\n\n // 일반 단락\n const block: IRBlock = { type: \"paragraph\", text }\n if (hasBold || hasItalic) {\n block.style = { bold: hasBold || undefined, italic: hasItalic || undefined }\n }\n if (href) block.href = href\n if (footnoteText) block.footnoteText = footnoteText\n return block\n}\n\n// ─── 테이블 파싱 ────────────────────────────────────────\n\nfunction parseTable(\n tbl: Element,\n styles: Map<string, StyleInfo>,\n numbering: Map<string, Map<number, NumberingInfo>>,\n footnotes: Map<string, string>,\n rels: Map<string, string>,\n): IRBlock | null {\n const trElements = getChildElements(tbl, \"tr\")\n if (trElements.length === 0) return null\n\n const rows: IRCell[][] = []\n let maxCols = 0\n\n for (const tr of trElements) {\n const tcElements = getChildElements(tr, \"tc\")\n const row: IRCell[] = []\n\n for (const tc of tcElements) {\n // 셀 속성\n let colSpan = 1\n let rowSpan = 1\n const tcPrEls = getChildElements(tc, \"tcPr\")\n if (tcPrEls.length > 0) {\n const gridSpanEls = getChildElements(tcPrEls[0], \"gridSpan\")\n if (gridSpanEls.length > 0) {\n colSpan = parseInt(getAttr(gridSpanEls[0], \"val\") ?? \"1\", 10)\n }\n const vMergeEls = getChildElements(tcPrEls[0], \"vMerge\")\n if (vMergeEls.length > 0) {\n const val = getAttr(vMergeEls[0], \"val\")\n if (val !== \"restart\" && val !== null) {\n // 병합 계속 셀 — 스킵 마커\n row.push({ text: \"\", colSpan, rowSpan: 0 })\n continue\n }\n }\n }\n\n // 셀 텍스트\n const cellTexts: string[] = []\n const pElements = getChildElements(tc, \"p\")\n for (const p of pElements) {\n const block = parseParagraph(p, styles, numbering, footnotes, rels)\n if (block?.text) cellTexts.push(block.text)\n }\n\n row.push({ text: cellTexts.join(\"\\n\"), colSpan, rowSpan })\n }\n rows.push(row)\n if (row.length > maxCols) maxCols = row.length\n }\n\n // vMerge rowSpan 후처리: restart에서 아래로 연속되는 rowSpan=0 카운트\n for (let c = 0; c < maxCols; c++) {\n for (let r = 0; r < rows.length; r++) {\n const cell = rows[r][c]\n if (!cell || cell.rowSpan === 0) continue\n let span = 1\n for (let nr = r + 1; nr < rows.length; nr++) {\n if (rows[nr][c]?.rowSpan === 0) span++\n else break\n }\n cell.rowSpan = span\n }\n }\n\n // rowSpan=0인 placeholder 제거\n const cleanRows: IRCell[][] = []\n for (const row of rows) {\n const clean = row.filter(cell => cell.rowSpan !== 0)\n cleanRows.push(clean)\n }\n\n // 빈 테이블 체크\n if (cleanRows.length === 0) return null\n\n // 컬럼 수 재계산\n let cols = 0\n for (const row of cleanRows) {\n let c = 0\n for (const cell of row) c += cell.colSpan\n if (c > cols) cols = c\n }\n\n const table: IRTable = {\n rows: cleanRows.length,\n cols,\n cells: cleanRows,\n hasHeader: cleanRows.length > 1,\n }\n return { type: \"table\", table }\n}\n\n// ─── 이미지 추출 ────────────────────────────────────────\n\nasync function extractImages(\n zip: JSZip,\n rels: Map<string, string>,\n doc: Document,\n): Promise<{ blocks: IRBlock[]; images: ExtractedImage[] }> {\n const blocks: IRBlock[] = []\n const images: ExtractedImage[] = []\n\n const drawingElements = findElements(doc.documentElement, \"drawing\")\n let imgIdx = 0\n\n for (const drawing of drawingElements) {\n // a:blip → r:embed\n const blips = findElements(drawing, \"blip\")\n for (const blip of blips) {\n const embedId = getAttr(blip, \"embed\")\n if (!embedId) continue\n const target = rels.get(embedId)\n if (!target) continue\n\n const imgPath = target.startsWith(\"/\") ? target.slice(1)\n : target.startsWith(\"word/\") ? target\n : `word/${target}`\n\n const imgFile = zip.file(imgPath)\n if (!imgFile) continue\n\n try {\n const data = await imgFile.async(\"uint8array\")\n imgIdx++\n const ext = imgPath.split(\".\").pop()?.toLowerCase() ?? \"png\"\n const mimeMap: Record<string, string> = {\n png: \"image/png\", jpg: \"image/jpeg\", jpeg: \"image/jpeg\",\n gif: \"image/gif\", bmp: \"image/bmp\", wmf: \"image/wmf\", emf: \"image/emf\",\n }\n const filename = `image_${String(imgIdx).padStart(3, \"0\")}.${ext}`\n images.push({ filename, data, mimeType: mimeMap[ext] ?? \"image/png\" })\n blocks.push({ type: \"image\", text: filename })\n } catch { /* 이미지 실패 무시 */ }\n }\n }\n return { blocks, images }\n}\n\n// ─── 메인 파서 ─────────────────────────────────────────\n\nexport async function parseDocxDocument(\n buffer: ArrayBuffer,\n options?: ParseOptions,\n): Promise<InternalParseResult> {\n // ZIP bomb 사전 검사\n precheckZipSize(buffer, MAX_DECOMPRESS_SIZE)\n\n const zip = await JSZip.loadAsync(buffer)\n const warnings: ParseWarning[] = []\n\n // DOCX 구조 검증\n const docFile = zip.file(\"word/document.xml\")\n if (!docFile) {\n throw new KordocError(\"유효하지 않은 DOCX 파일: word/document.xml이 없습니다\")\n }\n\n // 1. 관계 로드\n let rels = new Map<string, string>()\n const relsFile = zip.file(\"word/_rels/document.xml.rels\")\n if (relsFile) {\n rels = parseRels(await relsFile.async(\"text\"))\n }\n\n // 2. 스타일 로드\n let styles = new Map<string, StyleInfo>()\n const stylesFile = zip.file(\"word/styles.xml\")\n if (stylesFile) {\n try {\n styles = parseStyles(await stylesFile.async(\"text\"))\n } catch { /* 스타일 실패 무시 */ }\n }\n\n // 3. 번호 매기기 로드\n let numbering = new Map<string, Map<number, NumberingInfo>>()\n const numFile = zip.file(\"word/numbering.xml\")\n if (numFile) {\n try {\n numbering = parseNumbering(await numFile.async(\"text\"))\n } catch { /* 번호 매기기 실패 무시 */ }\n }\n\n // 4. 각주 로드\n let footnotes = new Map<string, string>()\n const fnFile = zip.file(\"word/footnotes.xml\")\n if (fnFile) {\n try {\n footnotes = parseFootnotes(await fnFile.async(\"text\"))\n } catch { /* 각주 실패 무시 */ }\n }\n\n // 5. 본문 파싱\n const docXml = await docFile.async(\"text\")\n const doc = parseXml(docXml)\n const body = findElements(doc, \"body\")\n if (body.length === 0) {\n throw new KordocError(\"DOCX 본문(w:body)을 찾을 수 없습니다\")\n }\n\n const blocks: IRBlock[] = []\n const bodyEl = body[0]\n const children = bodyEl.childNodes\n\n for (let i = 0; i < children.length; i++) {\n const node = children[i]\n if (node.nodeType !== 1) continue\n const el = node as Element\n const localName = el.localName ?? el.tagName?.split(\":\").pop()\n\n if (localName === \"p\") {\n const block = parseParagraph(el, styles, numbering, footnotes, rels)\n if (block) blocks.push(block)\n } else if (localName === \"tbl\") {\n const block = parseTable(el, styles, numbering, footnotes, rels)\n if (block) blocks.push(block)\n }\n }\n\n // 6. 이미지 추출\n const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc)\n // 이미지 블록은 본문에 이미 포함되어야 하지만, 누락된 것 추가\n // (drawing이 paragraph 내에 있으므로 대부분 이미 포함됨)\n\n // 7. 메타데이터\n const metadata: DocumentMetadata = {}\n const coreFile = zip.file(\"docProps/core.xml\")\n if (coreFile) {\n try {\n const coreXml = await coreFile.async(\"text\")\n const coreDoc = parseXml(coreXml)\n const getFirst = (tag: string) => {\n const els = coreDoc.getElementsByTagName(tag)\n return els.length > 0 ? (els[0].textContent ?? \"\").trim() : undefined\n }\n metadata.title = getFirst(\"dc:title\") || getFirst(\"dcterms:title\")\n metadata.author = getFirst(\"dc:creator\")\n metadata.description = getFirst(\"dc:description\")\n const created = getFirst(\"dcterms:created\")\n if (created) metadata.createdAt = created\n const modified = getFirst(\"dcterms:modified\")\n if (modified) metadata.modifiedAt = modified\n } catch { /* 메타데이터 실패 무시 */ }\n }\n\n // 8. 아웃라인\n const outline = blocks\n .filter(b => b.type === \"heading\")\n .map(b => ({ level: b.level ?? 2, text: b.text ?? \"\" }))\n\n const markdown = blocksToMarkdown(blocks)\n\n return {\n markdown,\n blocks,\n metadata,\n outline: outline.length > 0 ? outline : undefined,\n warnings: warnings.length > 0 ? warnings : undefined,\n images: images.length > 0 ? images : undefined,\n }\n}\n","/** 양식(서식) 필드 인식 — 테이블 기반 label-value 패턴 매칭 */\n\nimport type { IRBlock, IRTable, FormField, FormResult } from \"../types.js\"\n\n/** 한국 공문서 필드 라벨 키워드 */\nconst LABEL_KEYWORDS = new Set([\n \"성명\", \"이름\", \"주소\", \"전화\", \"전화번호\", \"휴대폰\", \"핸드폰\", \"연락처\",\n \"생년월일\", \"주민등록번호\", \"소속\", \"직위\", \"직급\", \"부서\",\n \"이메일\", \"팩스\", \"학교\", \"학년\", \"반\", \"번호\",\n \"신청인\", \"대표자\", \"담당자\", \"작성자\", \"확인자\", \"승인자\",\n \"일시\", \"날짜\", \"기간\", \"장소\", \"목적\", \"사유\", \"비고\",\n \"금액\", \"수량\", \"단가\", \"합계\", \"계\", \"소계\",\n])\n\n/** 라벨처럼 보이는 셀인지 판별 */\nfunction isLabelCell(text: string): boolean {\n const trimmed = text.trim()\n if (!trimmed || trimmed.length > 30) return false\n // 키워드 매칭\n for (const kw of LABEL_KEYWORDS) {\n if (trimmed.includes(kw)) return true\n }\n // 짧은 한글 텍스트 (2-8자) + 숫자 없음\n if (/^[가-힣\\s()·:]{2,8}$/.test(trimmed) && !/\\d/.test(trimmed)) return true\n // \"라벨:\" 패턴\n if (/^[가-힣A-Za-z\\s]+[::]$/.test(trimmed)) return true\n return false\n}\n\n/**\n * IRBlock[]에서 양식 필드를 인식하여 추출.\n * 테이블의 label-value 패턴을 감지.\n */\nexport function extractFormFields(blocks: IRBlock[]): FormResult {\n const fields: FormField[] = []\n let totalTables = 0\n let formTables = 0\n\n for (const block of blocks) {\n if (block.type !== \"table\" || !block.table) continue\n totalTables++\n\n const tableFields = extractFromTable(block.table)\n if (tableFields.length > 0) {\n formTables++\n fields.push(...tableFields)\n }\n }\n\n // 인라인 \"라벨: 값\" 패턴도 검사 (paragraph만 — heading/list는 양식 필드가 아님)\n for (const block of blocks) {\n if (block.type === \"paragraph\" && block.text) {\n const inlineFields = extractInlineFields(block.text)\n fields.push(...inlineFields)\n }\n }\n\n const confidence = totalTables > 0 ? formTables / totalTables : (fields.length > 0 ? 0.3 : 0)\n return { fields, confidence: Math.min(confidence, 1) }\n}\n\nfunction extractFromTable(table: IRTable): FormField[] {\n const fields: FormField[] = []\n\n // 전략 1: 인접셀 label-value (2열 이상 테이블)\n if (table.cols >= 2) {\n for (let r = 0; r < table.rows; r++) {\n for (let c = 0; c < table.cols - 1; c++) {\n const labelCell = table.cells[r][c]\n const valueCell = table.cells[r][c + 1]\n if (isLabelCell(labelCell.text) && valueCell.text.trim()) {\n fields.push({\n label: labelCell.text.trim().replace(/[::]\\s*$/, \"\"),\n value: valueCell.text.trim(),\n row: r,\n col: c,\n })\n }\n }\n }\n }\n\n // 전략 2: 헤더+데이터 행 (첫 행이 전부 라벨이면)\n if (fields.length === 0 && table.rows >= 2 && table.cols >= 2) {\n const headerRow = table.cells[0]\n const allLabels = headerRow.every(cell => {\n const t = cell.text.trim()\n return t.length > 0 && t.length <= 20\n })\n if (allLabels) {\n for (let r = 1; r < table.rows; r++) {\n for (let c = 0; c < table.cols; c++) {\n const label = headerRow[c].text.trim()\n const value = table.cells[r][c].text.trim()\n if (label && value) {\n fields.push({ label, value, row: r, col: c })\n }\n }\n }\n }\n }\n\n return fields\n}\n\nfunction extractInlineFields(text: string): FormField[] {\n const fields: FormField[] = []\n // \"라벨: 값\" 또는 \"라벨 : 값\" 패턴\n const pattern = /([가-힣A-Za-z]{2,10})\\s*[::]\\s*([^\\n,;]{1,100})/g\n let match\n while ((match = pattern.exec(text)) !== null) {\n const label = match[1].trim()\n const value = match[2].trim()\n if (value) {\n fields.push({ label, value, row: -1, col: -1 })\n }\n }\n return fields\n}\n","/**\r\n * Markdown → HWPX 역변환\r\n *\r\n * 지원: 헤딩(h1~h6), 단락, 볼드, 이탤릭, 인라인코드, 코드블록,\r\n * 순서/비순서 리스트, 수평선, 인용문, 테이블\r\n * jszip으로 HWPX ZIP 패키징.\r\n */\r\n\r\nimport JSZip from \"jszip\"\r\n\r\nconst NS_SECTION = \"http://www.hancom.co.kr/hwpml/2011/section\"\r\nconst NS_PARA = \"http://www.hancom.co.kr/hwpml/2011/paragraph\"\r\nconst NS_HEAD = \"http://www.hancom.co.kr/hwpml/2011/head\"\r\nconst NS_OPF = \"http://www.idpf.org/2007/opf/\"\r\nconst NS_HPF = \"http://www.hancom.co.kr/schema/2011/hpf\"\r\nconst NS_OCF = \"urn:oasis:names:tc:opendocument:xmlns:container\"\r\n\r\n// ─── 스타일 ID 매핑 ─────────────────────────────────\r\n// charPr: 0=본문, 1=볼드, 2=이탤릭, 3=볼드이탤릭, 4=인라인코드, 5=h1, 6=h2, 7=h3, 8=h4~h6\r\n// paraPr: 0=본문, 1=h1, 2=h2, 3=h3, 4=h4~h6, 5=코드블록, 6=인용문, 7=리스트\r\n\r\nconst CHAR_NORMAL = 0\r\nconst CHAR_BOLD = 1\r\nconst CHAR_ITALIC = 2\r\nconst CHAR_BOLD_ITALIC = 3\r\nconst CHAR_CODE = 4\r\nconst CHAR_H1 = 5\r\nconst CHAR_H2 = 6\r\nconst CHAR_H3 = 7\r\nconst CHAR_H4 = 8\r\n\r\nconst PARA_NORMAL = 0\r\nconst PARA_H1 = 1\r\nconst PARA_H2 = 2\r\nconst PARA_H3 = 3\r\nconst PARA_H4 = 4\r\nconst PARA_CODE = 5\r\nconst PARA_QUOTE = 6\r\nconst PARA_LIST = 7\r\n\r\n/**\r\n * 마크다운 텍스트를 HWPX (ArrayBuffer)로 변환.\r\n */\r\nexport async function markdownToHwpx(markdown: string): Promise<ArrayBuffer> {\r\n const blocks = parseMarkdownToBlocks(markdown)\r\n const sectionXml = blocksToSectionXml(blocks)\r\n\r\n const zip = new JSZip()\r\n zip.file(\"mimetype\", \"application/hwp+zip\", { compression: \"STORE\" })\r\n zip.file(\"META-INF/container.xml\", generateContainerXml())\r\n zip.file(\"Contents/content.hpf\", generateManifest())\r\n zip.file(\"Contents/header.xml\", generateHeaderXml())\r\n zip.file(\"Contents/section0.xml\", sectionXml)\r\n\r\n return await zip.generateAsync({ type: \"arraybuffer\" })\r\n}\r\n\r\n// ─── 마크다운 파싱 ───────────────────────────────────\r\n\r\ninterface MdBlock {\r\n type: \"paragraph\" | \"heading\" | \"table\" | \"code_block\" | \"hr\" | \"blockquote\" | \"list_item\"\r\n text?: string\r\n level?: number\r\n rows?: string[][]\r\n lang?: string\r\n ordered?: boolean\r\n indent?: number\r\n}\r\n\r\nfunction parseMarkdownToBlocks(md: string): MdBlock[] {\r\n const lines = md.split(\"\\n\")\r\n const blocks: MdBlock[] = []\r\n let i = 0\r\n\r\n while (i < lines.length) {\r\n const line = lines[i]\r\n\r\n // 빈 줄 스킵\r\n if (!line.trim()) { i++; continue }\r\n\r\n // 코드블록\r\n const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/)\r\n if (fenceMatch) {\r\n const fence = fenceMatch[1]\r\n const lang = fenceMatch[2].trim()\r\n const codeLines: string[] = []\r\n i++\r\n while (i < lines.length && !lines[i].startsWith(fence)) {\r\n codeLines.push(lines[i])\r\n i++\r\n }\r\n if (i < lines.length) i++ // 닫는 fence\r\n blocks.push({ type: \"code_block\", text: codeLines.join(\"\\n\"), lang })\r\n continue\r\n }\r\n\r\n // 수평선\r\n if (/^(\\*{3,}|-{3,}|_{3,})\\s*$/.test(line.trim())) {\r\n blocks.push({ type: \"hr\" })\r\n i++; continue\r\n }\r\n\r\n // 헤딩\r\n const headingMatch = line.match(/^(#{1,6})\\s+(.+)$/)\r\n if (headingMatch) {\r\n blocks.push({ type: \"heading\", text: headingMatch[2].trim(), level: headingMatch[1].length })\r\n i++; continue\r\n }\r\n\r\n // 테이블\r\n if (line.trimStart().startsWith(\"|\")) {\r\n const tableRows: string[][] = []\r\n while (i < lines.length && lines[i].trimStart().startsWith(\"|\")) {\r\n const row = lines[i]\r\n if (/^[\\s|:\\-]+$/.test(row)) { i++; continue }\r\n const cells = row.split(\"|\").slice(1, -1).map(c => c.trim())\r\n if (cells.length > 0) tableRows.push(cells)\r\n i++\r\n }\r\n if (tableRows.length > 0) blocks.push({ type: \"table\", rows: tableRows })\r\n continue\r\n }\r\n\r\n // 인용문\r\n if (line.trimStart().startsWith(\"> \")) {\r\n const quoteLines: string[] = []\r\n while (i < lines.length && (lines[i].trimStart().startsWith(\"> \") || lines[i].trimStart().startsWith(\">\"))) {\r\n quoteLines.push(lines[i].replace(/^>\\s?/, \"\"))\r\n i++\r\n }\r\n for (const ql of quoteLines) {\r\n blocks.push({ type: \"blockquote\", text: ql.trim() || \"\" })\r\n }\r\n continue\r\n }\r\n\r\n // 리스트\r\n const listMatch = line.match(/^(\\s*)([-*+]|\\d+[.)]) (.+)$/)\r\n if (listMatch) {\r\n const indent = Math.floor(listMatch[1].length / 2)\r\n const ordered = /\\d/.test(listMatch[2])\r\n blocks.push({ type: \"list_item\", text: listMatch[3].trim(), ordered, indent })\r\n i++; continue\r\n }\r\n\r\n // 일반 단락\r\n blocks.push({ type: \"paragraph\", text: line.trim() })\r\n i++\r\n }\r\n\r\n return blocks\r\n}\r\n\r\n// ─── 인라인 마크다운 → 멀티 run ─────────────────────\r\n\r\ninterface InlineSpan {\r\n text: string\r\n bold: boolean\r\n italic: boolean\r\n code: boolean\r\n}\r\n\r\nfunction parseInlineMarkdown(text: string): InlineSpan[] {\r\n // 전처리: 마크다운 링크/이미지 → 텍스트만 추출\r\n text = text.replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\") // ![alt](url) → alt\r\n text = text.replace(/\\[([^\\]]*)\\]\\(([^)]*)\\)/g, (_, t, u) => t || u) // [text](url) → text or url\r\n // 전처리: ~~취소선~~ → 텍스트만\r\n text = text.replace(/~~([^~]+)~~/g, \"$1\")\r\n\r\n const spans: InlineSpan[] = []\r\n // 패턴: `code`, ***bolditalic***, **bold**, *italic*, __bold__, _italic_\r\n const regex = /(`[^`]+`|\\*{3}[^*]+\\*{3}|\\*{2}[^*]+\\*{2}|\\*[^*]+\\*|_{2}[^_]+_{2}|_[^_]+_)/g\r\n let lastIdx = 0\r\n\r\n for (const match of text.matchAll(regex)) {\r\n const idx = match.index!\r\n if (idx > lastIdx) {\r\n spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false })\r\n }\r\n const raw = match[0]\r\n if (raw.startsWith(\"`\")) {\r\n spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true })\r\n } else if (raw.startsWith(\"***\") || raw.startsWith(\"___\")) {\r\n spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false })\r\n } else if (raw.startsWith(\"**\") || raw.startsWith(\"__\")) {\r\n spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false })\r\n } else {\r\n spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false })\r\n }\r\n lastIdx = idx + raw.length\r\n }\r\n if (lastIdx < text.length) {\r\n spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false })\r\n }\r\n if (spans.length === 0) {\r\n spans.push({ text, bold: false, italic: false, code: false })\r\n }\r\n return spans\r\n}\r\n\r\nfunction spanToCharPrId(span: InlineSpan): number {\r\n if (span.code) return CHAR_CODE\r\n if (span.bold && span.italic) return CHAR_BOLD_ITALIC\r\n if (span.bold) return CHAR_BOLD\r\n if (span.italic) return CHAR_ITALIC\r\n return CHAR_NORMAL\r\n}\r\n\r\n// ─── XML 생성 헬퍼 ───────────────────────────────────\r\n\r\nfunction escapeXml(text: string): string {\r\n return text\r\n .replace(/&/g, \"&amp;\")\r\n .replace(/</g, \"&lt;\")\r\n .replace(/>/g, \"&gt;\")\r\n .replace(/\"/g, \"&quot;\")\r\n}\r\n\r\nfunction generateRuns(text: string, defaultCharPr: number = CHAR_NORMAL): string {\r\n const spans = parseInlineMarkdown(text)\r\n return spans.map(span => {\r\n const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr\r\n return `<hp:run charPrIDRef=\"${charId}\"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`\r\n }).join(\"\")\r\n}\r\n\r\nfunction generateParagraph(text: string, paraPrId: number = PARA_NORMAL, charPrId: number = CHAR_NORMAL): string {\r\n if (paraPrId === PARA_CODE) {\r\n // 코드블록은 인라인 파싱 안 함\r\n return `<hp:p paraPrIDRef=\"${paraPrId}\" styleIDRef=\"0\"><hp:run charPrIDRef=\"${CHAR_CODE}\"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`\r\n }\r\n const runs = generateRuns(text, charPrId)\r\n return `<hp:p paraPrIDRef=\"${paraPrId}\" styleIDRef=\"0\">${runs}</hp:p>`\r\n}\r\n\r\nfunction headingParaPrId(level: number): number {\r\n if (level === 1) return PARA_H1\r\n if (level === 2) return PARA_H2\r\n if (level === 3) return PARA_H3\r\n return PARA_H4\r\n}\r\n\r\nfunction headingCharPrId(level: number): number {\r\n if (level === 1) return CHAR_H1\r\n if (level === 2) return CHAR_H2\r\n if (level === 3) return CHAR_H3\r\n return CHAR_H4\r\n}\r\n\r\n// ─── HWPX 구조 파일 생성 ─────────────────────────────\r\n\r\nfunction generateContainerXml(): string {\r\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\r\n<ocf:container xmlns:ocf=\"${NS_OCF}\" xmlns:hpf=\"${NS_HPF}\">\r\n <ocf:rootfiles>\r\n <ocf:rootfile full-path=\"Contents/content.hpf\" media-type=\"application/hwpml-package+xml\"/>\r\n </ocf:rootfiles>\r\n</ocf:container>`\r\n}\r\n\r\nfunction generateManifest(): string {\r\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\r\n<opf:package xmlns:opf=\"${NS_OPF}\" xmlns:hpf=\"${NS_HPF}\" xmlns:hh=\"${NS_HEAD}\">\r\n <opf:manifest>\r\n <opf:item id=\"header\" href=\"Contents/header.xml\" media-type=\"application/xml\"/>\r\n <opf:item id=\"section0\" href=\"Contents/section0.xml\" media-type=\"application/xml\"/>\r\n </opf:manifest>\r\n <opf:spine>\r\n <opf:itemref idref=\"header\" linear=\"no\"/>\r\n <opf:itemref idref=\"section0\" linear=\"yes\"/>\r\n </opf:spine>\r\n</opf:package>`\r\n}\r\n\r\n// ─── charPr 생성 헬퍼 ───────────────────────────────\r\n\r\nfunction charPr(id: number, height: number, bold: boolean, italic: boolean, fontId: number = 0): string {\r\n const boldAttr = bold ? ` bold=\"1\"` : \"\"\r\n const italicAttr = italic ? ` italic=\"1\"` : \"\"\r\n return ` <hh:charPr id=\"${id}\" height=\"${height}\" textColor=\"#000000\" shadeColor=\"none\" useFontSpace=\"0\" useKerning=\"0\" symMark=\"NONE\" borderFillIDRef=\"0\"${boldAttr}${italicAttr}>\r\n <hh:fontRef hangul=\"${fontId}\" latin=\"${fontId}\" hanja=\"${fontId}\" japanese=\"${fontId}\" other=\"${fontId}\" symbol=\"${fontId}\" user=\"${fontId}\"/>\r\n <hh:ratio hangul=\"100\" latin=\"100\" hanja=\"100\" japanese=\"100\" other=\"100\" symbol=\"100\" user=\"100\"/>\r\n <hh:spacing hangul=\"0\" latin=\"0\" hanja=\"0\" japanese=\"0\" other=\"0\" symbol=\"0\" user=\"0\"/>\r\n <hh:relSz hangul=\"100\" latin=\"100\" hanja=\"100\" japanese=\"100\" other=\"100\" symbol=\"100\" user=\"100\"/>\r\n <hh:offset hangul=\"0\" latin=\"0\" hanja=\"0\" japanese=\"0\" other=\"0\" symbol=\"0\" user=\"0\"/>\r\n </hh:charPr>`\r\n}\r\n\r\n// ─── paraPr 생성 헬퍼 ───────────────────────────────\r\n\r\nfunction paraPr(id: number, opts: { align?: string; spaceBefore?: number; spaceAfter?: number; lineSpacing?: number; indent?: number } = {}): string {\r\n const { align = \"JUSTIFY\", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts\r\n return ` <hh:paraPr id=\"${id}\" tabPrIDRef=\"0\" condense=\"0\" fontLineHeight=\"0\" snapToGrid=\"1\" suppressLineNumbers=\"0\" checked=\"0\" textDir=\"AUTO\">\r\n <hh:align horizontal=\"${align}\" vertical=\"BASELINE\"/>\r\n <hh:heading type=\"NONE\" idRef=\"0\" level=\"0\"/>\r\n <hh:breakSetting breakLatinWord=\"KEEP_WORD\" breakNonLatinWord=\"BREAK_WORD\" widowOrphan=\"0\" keepWithNext=\"0\" keepLines=\"0\" pageBreakBefore=\"0\" lineWrap=\"BREAK\"/>\r\n <hh:autoSpacing eAsianEng=\"0\" eAsianNum=\"0\"/>\r\n <hh:margin indent=\"${indent}\" left=\"0\" right=\"0\" prev=\"${spaceBefore}\" next=\"${spaceAfter}\"/>\r\n <hh:lineSpacing type=\"PERCENT\" value=\"${lineSpacing}\"/>\r\n <hh:border borderFillIDRef=\"0\" offsetLeft=\"0\" offsetRight=\"0\" offsetTop=\"0\" offsetBottom=\"0\" connect=\"0\" ignoreMargin=\"0\"/>\r\n </hh:paraPr>`\r\n}\r\n\r\nfunction generateHeaderXml(): string {\r\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\r\n<hh:head xmlns:hh=\"${NS_HEAD}\" xmlns:hp=\"${NS_PARA}\" version=\"1.4\" secCnt=\"1\">\r\n <hh:beginNum page=\"1\" footnote=\"1\" endnote=\"1\" pic=\"1\" tbl=\"1\" equation=\"1\"/>\r\n <hh:refList>\r\n <hh:fontfaces itemCnt=\"7\">\r\n <hh:fontface lang=\"HANGUL\" fontCnt=\"2\">\r\n <hh:font id=\"0\" face=\"함초롬바탕\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"4\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n <hh:font id=\"1\" face=\"함초롬돋움\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"4\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n <hh:fontface lang=\"LATIN\" fontCnt=\"2\">\r\n <hh:font id=\"0\" face=\"Times New Roman\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_OLDSTYLE\" weight=\"5\" proportion=\"4\" contrast=\"2\" strokeVariation=\"0\" armStyle=\"0\" letterform=\"0\" midline=\"0\" xHeight=\"4\"/>\r\n </hh:font>\r\n <hh:font id=\"1\" face=\"Consolas\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_MODERN\" weight=\"5\" proportion=\"0\" contrast=\"0\" strokeVariation=\"0\" armStyle=\"0\" letterform=\"0\" midline=\"0\" xHeight=\"0\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n <hh:fontface lang=\"HANJA\" fontCnt=\"1\">\r\n <hh:font id=\"0\" face=\"함초롬바탕\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"4\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n <hh:fontface lang=\"JAPANESE\" fontCnt=\"1\">\r\n <hh:font id=\"0\" face=\"굴림\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"0\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n <hh:fontface lang=\"OTHER\" fontCnt=\"1\">\r\n <hh:font id=\"0\" face=\"굴림\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"0\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n <hh:fontface lang=\"SYMBOL\" fontCnt=\"1\">\r\n <hh:font id=\"0\" face=\"Symbol\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"0\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n <hh:fontface lang=\"USER\" fontCnt=\"1\">\r\n <hh:font id=\"0\" face=\"굴림\" type=\"TTF\" isEmbedded=\"0\">\r\n <hh:typeInfo familyType=\"FCAT_GOTHIC\" weight=\"6\" proportion=\"0\" contrast=\"0\" strokeVariation=\"1\" armStyle=\"1\" letterform=\"1\" midline=\"1\" xHeight=\"1\"/>\r\n </hh:font>\r\n </hh:fontface>\r\n </hh:fontfaces>\r\n <hh:borderFills itemCnt=\"1\">\r\n <hh:borderFill id=\"0\" threeD=\"0\" shadow=\"0\" centerLine=\"0\" breakCellSeparateLine=\"0\">\r\n <hh:slash type=\"NONE\" Crooked=\"0\" isCounter=\"0\"/>\r\n <hh:backSlash type=\"NONE\" Crooked=\"0\" isCounter=\"0\"/>\r\n <hh:leftBorder type=\"NONE\" width=\"0.1mm\" color=\"#000000\"/>\r\n <hh:rightBorder type=\"NONE\" width=\"0.1mm\" color=\"#000000\"/>\r\n <hh:topBorder type=\"NONE\" width=\"0.1mm\" color=\"#000000\"/>\r\n <hh:bottomBorder type=\"NONE\" width=\"0.1mm\" color=\"#000000\"/>\r\n <hh:diagonal type=\"NONE\" width=\"0.1mm\" color=\"#000000\"/>\r\n <hh:fillInfo/>\r\n </hh:borderFill>\r\n </hh:borderFills>\r\n <hh:charProperties itemCnt=\"9\">\r\n${charPr(0, 1000, false, false)}\r\n${charPr(1, 1000, true, false)}\r\n${charPr(2, 1000, false, true)}\r\n${charPr(3, 1000, true, true)}\r\n${charPr(4, 900, false, false, 1)}\r\n${charPr(5, 1800, true, false, 1)}\r\n${charPr(6, 1400, true, false, 1)}\r\n${charPr(7, 1200, true, false, 1)}\r\n${charPr(8, 1100, true, false, 1)}\r\n </hh:charProperties>\r\n <hh:tabProperties itemCnt=\"0\"/>\r\n <hh:numberings itemCnt=\"0\"/>\r\n <hh:bullets itemCnt=\"0\"/>\r\n <hh:paraProperties itemCnt=\"8\">\r\n${paraPr(0)}\r\n${paraPr(1, { align: \"LEFT\", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}\r\n${paraPr(2, { align: \"LEFT\", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}\r\n${paraPr(3, { align: \"LEFT\", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}\r\n${paraPr(4, { align: \"LEFT\", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}\r\n${paraPr(5, { align: \"LEFT\", lineSpacing: 130, indent: 400 })}\r\n${paraPr(6, { align: \"LEFT\", lineSpacing: 150, indent: 600 })}\r\n${paraPr(7, { align: \"LEFT\", lineSpacing: 160, indent: 600 })}\r\n </hh:paraProperties>\r\n <hh:styles itemCnt=\"1\">\r\n <hh:style id=\"0\" type=\"PARA\" name=\"바탕글\" engName=\"Normal\" paraPrIDRef=\"0\" charPrIDRef=\"0\" nextStyleIDRef=\"0\" langIDRef=\"1042\" lockForm=\"0\"/>\r\n </hh:styles>\r\n </hh:refList>\r\n <hh:compatibleDocument targetProgram=\"HWP2018\"/>\r\n</hh:head>`\r\n}\r\n\r\n// ─── 섹션 속성 (공문서 표준 여백) ────────────────────\r\n\r\nfunction generateSecPr(): string {\r\n // A4: 210mm × 297mm → 59528 × 84188 HWPUNIT (1mm ≈ 283.46 HWPUNIT)\r\n // 공문서 표준: 위 30mm(8504), 아래 15mm(4252), 왼쪽 20mm(5670), 오른쪽 15mm(4252)\r\n // 머리말 10mm(2835), 꼬리말 10mm(2835)\r\n return `<hp:secPr textDirection=\"HORIZONTAL\" spaceColumns=\"1134\" tabStop=\"8000\" outlineShapeIDRef=\"0\" memoShapeIDRef=\"0\" textVerticalWidthHead=\"0\" masterPageCnt=\"0\">` +\r\n `<hp:grid lineGrid=\"0\" charGrid=\"0\" wonggojiFormat=\"0\"/>` +\r\n `<hp:startNum pageStartsOn=\"BOTH\" page=\"0\" pic=\"0\" tbl=\"0\" equation=\"0\"/>` +\r\n `<hp:visibility hideFirstHeader=\"0\" hideFirstFooter=\"0\" hideFirstMasterPage=\"0\" border=\"SHOW_ALL\" fill=\"SHOW_ALL\" hideFirstPageNum=\"0\" hideFirstEmptyLine=\"0\" showLineNumber=\"0\"/>` +\r\n `<hp:pagePr landscape=\"WIDELY\" width=\"59528\" height=\"84188\" gutterType=\"LEFT_ONLY\">` +\r\n `<hp:margin header=\"2835\" footer=\"2835\" gutter=\"0\" left=\"5670\" right=\"4252\" top=\"8504\" bottom=\"4252\"/>` +\r\n `</hp:pagePr>` +\r\n `<hp:footNotePr><hp:autoNumFormat type=\"DIGIT\" userChar=\"\" prefixChar=\"\" suffixChar=\")\" supscript=\"0\"/><hp:noteLine length=\"-1\" type=\"SOLID\" width=\"0.12 mm\" color=\"#000000\"/><hp:noteSpacing betweenNotes=\"283\" belowLine=\"567\" aboveLine=\"850\"/><hp:numbering type=\"CONTINUOUS\" newNum=\"1\"/><hp:placement place=\"EACH_COLUMN\" beneathText=\"0\"/></hp:footNotePr>` +\r\n `<hp:endNotePr><hp:autoNumFormat type=\"DIGIT\" userChar=\"\" prefixChar=\"\" suffixChar=\")\" supscript=\"0\"/><hp:noteLine length=\"14692344\" type=\"SOLID\" width=\"0.12 mm\" color=\"#000000\"/><hp:noteSpacing betweenNotes=\"0\" belowLine=\"567\" aboveLine=\"850\"/><hp:numbering type=\"CONTINUOUS\" newNum=\"1\"/><hp:placement place=\"END_OF_DOCUMENT\" beneathText=\"0\"/></hp:endNotePr>` +\r\n `</hp:secPr>`\r\n}\r\n\r\n// ─── 테이블 생성 ─────────────────────────────────────\r\n\r\nfunction generateTable(rows: string[][]): string {\r\n const trElements = rows.map(row => {\r\n const tdElements = row.map(cell => {\r\n const runs = generateRuns(cell)\r\n return `<hp:tc><hp:cellSpan colSpan=\"1\" rowSpan=\"1\"/><hp:p paraPrIDRef=\"0\" styleIDRef=\"0\">${runs}</hp:p></hp:tc>`\r\n }).join(\"\")\r\n return `<hp:tr>${tdElements}</hp:tr>`\r\n }).join(\"\")\r\n return `<hp:tbl>${trElements}</hp:tbl>`\r\n}\r\n\r\n// ─── 섹션 XML 생성 ──────────────────────────────────\r\n\r\nfunction blocksToSectionXml(blocks: MdBlock[]): string {\r\n const paraXmls: string[] = []\r\n let isFirst = true\r\n\r\n for (const block of blocks) {\r\n let xml = \"\"\r\n switch (block.type) {\r\n case \"heading\": {\r\n const pId = headingParaPrId(block.level || 1)\r\n const cId = headingCharPrId(block.level || 1)\r\n xml = generateParagraph(block.text || \"\", pId, cId)\r\n break\r\n }\r\n case \"paragraph\":\r\n xml = generateParagraph(block.text || \"\")\r\n break\r\n case \"code_block\": {\r\n const codeLines = (block.text || \"\").split(\"\\n\")\r\n xml = codeLines.map(line => generateParagraph(line || \" \", PARA_CODE)).join(\"\\n \")\r\n break\r\n }\r\n case \"blockquote\":\r\n xml = generateParagraph(block.text || \"\", PARA_QUOTE)\r\n break\r\n case \"list_item\": {\r\n const marker = block.ordered ? `${(block.indent || 0) + 1}. ` : \"· \"\r\n const indentPrefix = \" \".repeat(block.indent || 0)\r\n xml = generateParagraph(indentPrefix + marker + (block.text || \"\"), PARA_LIST)\r\n break\r\n }\r\n case \"hr\":\r\n // 수평선 — 긴 대시로 대체\r\n xml = `<hp:p paraPrIDRef=\"0\" styleIDRef=\"0\"><hp:run charPrIDRef=\"0\"><hp:t>────────────────────────────────────────</hp:t></hp:run></hp:p>`\r\n break\r\n case \"table\":\r\n if (block.rows) {\r\n if (isFirst) {\r\n // 테이블이 첫 블록이면 빈 단락에 secPr\r\n const secRun = `<hp:run charPrIDRef=\"0\">${generateSecPr()}<hp:t></hp:t></hp:run>`\r\n paraXmls.push(`<hp:p paraPrIDRef=\"0\" styleIDRef=\"0\">${secRun}</hp:p>`)\r\n isFirst = false\r\n }\r\n xml = generateTable(block.rows)\r\n }\r\n break\r\n }\r\n\r\n if (!xml) continue\r\n\r\n // 첫 번째 단락에 secPr 주입\r\n if (isFirst && block.type !== \"table\") {\r\n xml = xml.replace(\r\n /<hp:run charPrIDRef=\"(\\d+)\">/,\r\n `<hp:run charPrIDRef=\"$1\">${generateSecPr()}`\r\n )\r\n isFirst = false\r\n }\r\n\r\n paraXmls.push(xml)\r\n }\r\n\r\n // 블록이 없으면 빈 단락\r\n if (paraXmls.length === 0) {\r\n paraXmls.push(`<hp:p paraPrIDRef=\"0\" styleIDRef=\"0\"><hp:run charPrIDRef=\"0\">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`)\r\n }\r\n\r\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\r\n<hs:sec xmlns:hs=\"${NS_SECTION}\" xmlns:hp=\"${NS_PARA}\">\r\n ${paraXmls.join(\"\\n \")}\r\n</hs:sec>`\r\n}\r\n","/** 텍스트 유사도 및 diff 유틸리티 — 외부 의존성 없음 */\n\nexport interface TextChange {\n type: \"equal\" | \"insert\" | \"delete\"\n text: string\n}\n\n/** 두 문자열의 유사도 (0-1). 1 = 동일, 0 = 완전히 다름 */\nexport function similarity(a: string, b: string): number {\n if (a === b) return 1\n if (!a || !b) return 0\n const maxLen = Math.max(a.length, b.length)\n if (maxLen === 0) return 1\n return 1 - levenshtein(a, b) / maxLen\n}\n\n/** 공백 정규화 후 유사도 비교 (HWP/HWPX 포맷 차이 흡수) */\nexport function normalizedSimilarity(a: string, b: string): number {\n return similarity(normalize(a), normalize(b))\n}\n\nfunction normalize(s: string): string {\n return s.replace(/\\s+/g, \" \").trim()\n}\n\n/** 최대 입력 길이 합 — 초과 시 길이 차이 기반 빠른 추정 (O(m*n) CPU 폭발 방지) */\nconst MAX_LEVENSHTEIN_LEN = 10_000\n\n/** Levenshtein 편집 거리 — O(min(m,n)) 공간 최적화 */\nfunction levenshtein(a: string, b: string): number {\n if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {\n // 길이 차이 + 앞 500자 샘플 비교로 근사 거리 추정\n const sampleLen = Math.min(500, a.length, b.length)\n let diffs = 0\n for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++\n const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1\n return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate)\n }\n if (a.length > b.length) [a, b] = [b, a]\n const m = a.length\n const n = b.length\n let prev = Array.from({ length: m + 1 }, (_, i) => i)\n let curr = new Array(m + 1)\n\n for (let j = 1; j <= n; j++) {\n curr[0] = j\n for (let i = 1; i <= m; i++) {\n if (a[i - 1] === b[j - 1]) {\n curr[i] = prev[i - 1]\n } else {\n curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1])\n }\n }\n ;[prev, curr] = [curr, prev]\n }\n return prev[m]\n}\n\n/** 단어 단위 diff — LCS 기반 */\nexport function textDiff(a: string, b: string): TextChange[] {\n const wordsA = a.split(/(\\s+)/)\n const wordsB = b.split(/(\\s+)/)\n const lcs = lcsWords(wordsA, wordsB)\n\n const changes: TextChange[] = []\n let ia = 0, ib = 0, il = 0\n\n while (il < lcs.length) {\n // lcs 원소 이전에 있는 것들\n while (ia < wordsA.length && wordsA[ia] !== lcs[il]) {\n changes.push({ type: \"delete\", text: wordsA[ia++] })\n }\n while (ib < wordsB.length && wordsB[ib] !== lcs[il]) {\n changes.push({ type: \"insert\", text: wordsB[ib++] })\n }\n changes.push({ type: \"equal\", text: lcs[il] })\n ia++; ib++; il++\n }\n // 나머지\n while (ia < wordsA.length) changes.push({ type: \"delete\", text: wordsA[ia++] })\n while (ib < wordsB.length) changes.push({ type: \"insert\", text: wordsB[ib++] })\n\n return mergeChanges(changes)\n}\n\nfunction lcsWords(a: string[], b: string[]): string[] {\n const m = a.length, n = b.length\n // 대형 문서 보호: 5000 단어 초과 시 간이 비교\n if (m * n > 25_000_000) return simpleIntersect(a, b)\n\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] = a[i - 1] === b[j - 1]\n ? dp[i - 1][j - 1] + 1\n : Math.max(dp[i - 1][j], dp[i][j - 1])\n }\n }\n\n const result: string[] = []\n let i = m, j = n\n while (i > 0 && j > 0) {\n if (a[i - 1] === b[j - 1]) { result.push(a[i - 1]); i--; j-- }\n else if (dp[i - 1][j] >= dp[i][j - 1]) i--\n else j--\n }\n return result.reverse()\n}\n\nfunction simpleIntersect(a: string[], b: string[]): string[] {\n const setB = new Set(b)\n return a.filter(w => setB.has(w))\n}\n\nfunction mergeChanges(changes: TextChange[]): TextChange[] {\n if (changes.length === 0) return changes\n const merged: TextChange[] = [changes[0]]\n for (let i = 1; i < changes.length; i++) {\n const last = merged[merged.length - 1]\n if (last.type === changes[i].type) {\n last.text += changes[i].text\n } else {\n merged.push({ ...changes[i] })\n }\n }\n return merged\n}\n","/** 문서 비교 엔진 — IR 레벨 블록 비교로 신구대조표 생성 */\n\nimport { parse } from \"../index.js\"\nimport { normalizedSimilarity } from \"./text-diff.js\"\nimport type { IRBlock, IRTable, DiffResult, BlockDiff, CellDiff, DiffChangeType, ParseOptions } from \"../types.js\"\n\n/** 유사도 임계값 — 이 이상이면 modified, 미만이면 removed+added */\nconst SIMILARITY_THRESHOLD = 0.4\n\n/**\n * 두 문서를 비교하여 블록 단위 diff 생성.\n * 크로스 포맷 지원 — HWP vs HWPX 비교 가능 (IR 레벨).\n */\nexport async function compare(\n bufferA: ArrayBuffer,\n bufferB: ArrayBuffer,\n options?: ParseOptions\n): Promise<DiffResult> {\n const [resultA, resultB] = await Promise.all([\n parse(bufferA, options),\n parse(bufferB, options),\n ])\n\n if (!resultA.success) throw new Error(`문서A 파싱 실패: ${resultA.error}`)\n if (!resultB.success) throw new Error(`문서B 파싱 실패: ${resultB.error}`)\n\n return diffBlocks(resultA.blocks, resultB.blocks)\n}\n\n/** IRBlock[] 간 diff — LCS 기반 정렬 */\nexport function diffBlocks(blocksA: IRBlock[], blocksB: IRBlock[]): DiffResult {\n const aligned = alignBlocks(blocksA, blocksB)\n const stats = { added: 0, removed: 0, modified: 0, unchanged: 0 }\n const diffs: BlockDiff[] = []\n\n for (const [a, b] of aligned) {\n if (a && b) {\n const sim = blockSimilarity(a, b)\n if (sim >= 0.99) {\n diffs.push({ type: \"unchanged\", before: a, after: b, similarity: 1 })\n stats.unchanged++\n } else {\n const diff: BlockDiff = { type: \"modified\", before: a, after: b, similarity: sim }\n if (a.type === \"table\" && b.type === \"table\" && a.table && b.table) {\n diff.cellDiffs = diffTableCells(a.table, b.table)\n }\n diffs.push(diff)\n stats.modified++\n }\n } else if (a) {\n diffs.push({ type: \"removed\", before: a })\n stats.removed++\n } else if (b) {\n diffs.push({ type: \"added\", after: b })\n stats.added++\n }\n }\n\n return { stats, diffs }\n}\n\n// ─── 블록 정렬 (LCS 기반) ───────────────────────────\n\nfunction alignBlocks(a: IRBlock[], b: IRBlock[]): [IRBlock | null, IRBlock | null][] {\n const m = a.length, n = b.length\n\n // 대형 문서 보호\n if (m * n > 10_000_000) return fallbackAlign(a, b)\n\n // 유사도 매트릭스 캐시\n const simCache = new Map<string, number>()\n const getSim = (i: number, j: number): number => {\n const key = `${i},${j}`\n let v = simCache.get(key)\n if (v === undefined) { v = blockSimilarity(a[i], b[j]); simCache.set(key, v) }\n return v\n }\n\n // LCS with similarity threshold\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (getSim(i - 1, j - 1) >= SIMILARITY_THRESHOLD) {\n dp[i][j] = dp[i - 1][j - 1] + 1\n } else {\n dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])\n }\n }\n }\n\n // 역추적\n const pairs: [number, number][] = []\n let i = m, j = n\n while (i > 0 && j > 0) {\n if (getSim(i - 1, j - 1) >= SIMILARITY_THRESHOLD && dp[i][j] === dp[i - 1][j - 1] + 1) {\n pairs.push([i - 1, j - 1]); i--; j--\n } else if (dp[i - 1][j] >= dp[i][j - 1]) {\n i--\n } else {\n j--\n }\n }\n pairs.reverse()\n\n // 정렬 결과 조립\n const result: [IRBlock | null, IRBlock | null][] = []\n let ai = 0, bi = 0\n for (const [pi, pj] of pairs) {\n while (ai < pi) result.push([a[ai++], null])\n while (bi < pj) result.push([null, b[bi++]])\n result.push([a[ai++], b[bi++]])\n }\n while (ai < m) result.push([a[ai++], null])\n while (bi < n) result.push([null, b[bi++]])\n\n return result\n}\n\nfunction fallbackAlign(a: IRBlock[], b: IRBlock[]): [IRBlock | null, IRBlock | null][] {\n const result: [IRBlock | null, IRBlock | null][] = []\n const len = Math.max(a.length, b.length)\n for (let i = 0; i < len; i++) {\n result.push([a[i] || null, b[i] || null])\n }\n return result\n}\n\n// ─── 블록 유사도 ────────────────────────────────────\n\nfunction blockSimilarity(a: IRBlock, b: IRBlock): number {\n if (a.type !== b.type) return 0\n\n // 텍스트 기반 블록: paragraph, heading, list, image(alt text)\n if (a.text !== undefined && b.text !== undefined) {\n return normalizedSimilarity(a.text || \"\", b.text || \"\")\n }\n\n if (a.type === \"table\" && a.table && b.table) {\n return tableSimilarity(a.table, b.table)\n }\n\n // separator 등 텍스트 없는 동일 타입 → 완전 일치\n if (a.type === b.type) return 1\n\n return 0\n}\n\nfunction tableSimilarity(a: IRTable, b: IRTable): number {\n // 구조 유사도 (차원)\n const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1)\n\n // 내용 유사도 (셀 텍스트)\n const textsA = a.cells.flat().map(c => c.text).join(\" \")\n const textsB = b.cells.flat().map(c => c.text).join(\" \")\n const contentSim = normalizedSimilarity(textsA, textsB)\n\n return dimSim * 0.3 + contentSim * 0.7\n}\n\n// ─── 테이블 셀 diff ─────────────────────────────────\n\nfunction diffTableCells(a: IRTable, b: IRTable): CellDiff[][] {\n const maxRows = Math.max(a.rows, b.rows)\n const maxCols = Math.max(a.cols, b.cols)\n const result: CellDiff[][] = []\n\n for (let r = 0; r < maxRows; r++) {\n const row: CellDiff[] = []\n for (let c = 0; c < maxCols; c++) {\n const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : undefined\n const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : undefined\n\n let type: DiffChangeType\n if (cellA === undefined) type = \"added\"\n else if (cellB === undefined) type = \"removed\"\n else if (cellA === cellB) type = \"unchanged\"\n else type = \"modified\"\n\n row.push({ type, before: cellA, after: cellB })\n }\n result.push(row)\n }\n return result\n}\n"],"mappings":"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AA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAC7E,CAAC;AAED,IAAM,YAAY,IAAI,WAAW;AAAA,EAC/B;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAC7E,CAAC;AAID,IAAM,OAAO,IAAI,WAAW,CAAC,GAAM,GAAM,GAAM,GAAM,IAAM,IAAM,IAAM,KAAM,IAAM,EAAI,CAAC;AAIxF,SAAS,KAAK,GAAW,GAAmB;AAC1C,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,IAAI,EAAG,MAAK;AAChB,UAAM,KAAK,IAAI;AACf,QAAK,KAAK,IAAK;AACf,QAAI,GAAI,MAAK;AACb,UAAM;AAAA,EACR;AACA,SAAO;AACT;AAIA,SAAS,UAAU,KAA8B;AAC/C,QAAM,IAAI,IAAI,YAAY,EAAE;AAG5B,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,MAAE,CAAC,IAAK,IAAI,IAAI,CAAC,KAAK,KAAO,IAAI,IAAI,IAAI,CAAC,KAAK,KAAO,IAAI,IAAI,IAAI,CAAC,KAAK,IAAK,IAAI,IAAI,IAAI,CAAC;AAAA,EAC5F;AAEA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,OAAO,EAAE,IAAI,CAAC;AAClB,QAAI,IAAI,MAAM,GAAG;AAEf,cAAS,QAAQ,IAAM,SAAS,QAAS;AACzC,aAAQ,MAAO,SAAS,KAAM,GAAI,KAAK,KAC/B,MAAO,SAAS,KAAM,GAAI,KAAK,KAC/B,MAAO,SAAS,IAAK,GAAI,KAAK,IAC9B,MAAM,OAAO,GAAI;AACzB,cAAQ,OAAQ,KAAK,IAAI,IAAI,CAAC,KAAK,QAAS;AAAA,IAC9C;AACA,MAAE,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,UAAU;AAAA,EAC/B;AAEA,SAAO;AACT;AAIA,SAAS,aAAa,OAAmB,WAAoC;AAE3E,QAAM,IAAI,IAAI,WAAW,EAAE;AAC3B,WAAS,IAAI,GAAG,IAAI,IAAI,IAAK,GAAE,CAAC,IAAI,MAAM,CAAC;AAG3C,cAAY,GAAG,WAAW,EAAE;AAG5B,WAAS,QAAQ,GAAG,SAAS,GAAG,SAAS;AACvC,iBAAa,CAAC;AACd,gBAAY,CAAC;AACb,gBAAY,GAAG,WAAW,KAAK;AAC/B,kBAAc,CAAC;AAAA,EACjB;AAGA,eAAa,CAAC;AACd,cAAY,CAAC;AACb,cAAY,GAAG,WAAW,CAAC;AAE3B,SAAO;AACT;AAEA,SAAS,YAAY,GAAe,GAAgB,OAAqB;AACvE,QAAM,OAAO,QAAQ;AACrB,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,EAAE,OAAO,CAAC;AACpB,MAAE,IAAI,CAAC,KAAM,MAAM,KAAM;AACzB,MAAE,IAAI,IAAI,CAAC,KAAM,MAAM,KAAM;AAC7B,MAAE,IAAI,IAAI,CAAC,KAAM,MAAM,IAAK;AAC5B,MAAE,IAAI,IAAI,CAAC,KAAK,IAAI;AAAA,EACtB;AACF;AAEA,SAAS,YAAY,GAAqB;AACxC,WAAS,IAAI,GAAG,IAAI,IAAI,IAAK,GAAE,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;AACpD;AAEA,SAAS,aAAa,GAAqB;AAGzC,MAAI,IAAI,EAAE,EAAE;AAAG,IAAE,EAAE,IAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI;AAE9D,MAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI,EAAE,EAAE;AAAG,IAAE,EAAE,IAAI;AAChC,MAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI,EAAE,EAAE;AAAG,IAAE,EAAE,IAAI;AAEhC,MAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI,EAAE,CAAC;AAAG,IAAE,CAAC,IAAI,EAAE,EAAE;AAAG,IAAE,EAAE,IAAI,EAAE,EAAE;AAAG,IAAE,EAAE,IAAI;AAC9D;AAEA,SAAS,cAAc,GAAqB;AAC1C,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,IAAI;AACd,UAAM,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC;AAC3D,MAAE,CAAC,IAAQ,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,CAAI;AAC3E,MAAE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAI,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,EAAI;AAC3E,MAAE,IAAI,CAAC,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,CAAI,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,EAAI;AAC3E,MAAE,IAAI,CAAC,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,EAAI,IAAI,KAAK,IAAI,CAAI,IAAI,KAAK,IAAI,EAAI;AAAA,EAC7E;AACF;AAKO,SAAS,iBAAiB,MAAkB,KAA6B;AAC9E,MAAI,IAAI,WAAW,GAAI,OAAM,IAAI,MAAM,0EAAwB;AAC/D,MAAI,KAAK,SAAS,OAAO,EAAG,OAAM,IAAI,MAAM,mGAA6B;AAEzE,QAAM,YAAY,UAAU,GAAG;AAC/B,QAAM,MAAM,IAAI,WAAW,KAAK,MAAM;AAEtC,WAAS,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU,IAAI;AACvD,UAAM,QAAQ,KAAK,SAAS,QAAQ,SAAS,EAAE;AAC/C,UAAM,YAAY,aAAa,OAAO,SAAS;AAC/C,QAAI,IAAI,WAAW,MAAM;AAAA,EAC3B;AAEA,SAAO;AACT;;;AC1JA,IAAM,UAAN,MAAc;AAAA,EACJ;AAAA,EAER,YAAY,MAAc;AACxB,SAAK,OAAO,SAAS;AAAA,EACvB;AAAA;AAAA,EAGA,OAAe;AAGb,SAAK,OAAQ,KAAK,KAAK,KAAK,MAAM,MAAM,IAAI,YAAa;AACzD,WAAQ,KAAK,SAAS,KAAM;AAAA,EAC9B;AACF;AAaA,SAAS,yBAAyB,SAAiC;AACjE,MAAI,QAAQ,SAAS,IAAK,OAAM,IAAI,MAAM,uFAA2B;AAErE,QAAM,QAAQ,QAAQ,CAAC,IAAK,QAAQ,CAAC,KAAK,IAAM,QAAQ,CAAC,KAAK,KAAO,QAAQ,CAAC,KAAK,QAAS;AAC5F,QAAM,MAAM,IAAI,QAAQ,IAAI;AAE5B,QAAM,SAAS,IAAI,WAAW,QAAQ,SAAS,GAAG,GAAG,CAAC;AAItD,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,MAAM;AAEV,SAAO,IAAI,KAAK;AACd,QAAI,MAAM,GAAG;AACX,YAAM,IAAI,KAAK,IAAI;AACnB,WAAK,IAAI,KAAK,IAAI,MAAQ;AAAA,IAC5B;AACA,QAAI,KAAK,GAAG;AACV,aAAO,CAAC,KAAK;AAAA,IACf;AACA;AACA;AAAA,EACF;AAEA,SAAO;AACT;AAQA,SAAS,cAAc,kBAA0C;AAC/D,QAAM,SAAS,KAAK,iBAAiB,CAAC,IAAI;AAC1C,MAAI,SAAS,KAAK,iBAAiB,QAAQ;AACzC,UAAM,IAAI,MAAM,0HAAqC;AAAA,EACvD;AACA,SAAO,iBAAiB,MAAM,QAAQ,SAAS,EAAE;AACnD;AAKA,SAAS,kBAAkB,MAAkB,QAAqE;AAChH,MAAI,SAAS,IAAI,KAAK,OAAQ,OAAM,IAAI,MAAM,4FAAsB;AAEpE,QAAM,UAAU,KAAK,MAAM,IAAK,KAAK,SAAS,CAAC,KAAK,IAAM,KAAK,SAAS,CAAC,KAAK,KAAO,KAAK,SAAS,CAAC,KAAK,QAAS;AAClH,QAAM,QAAQ,SAAS;AACvB,MAAI,OAAQ,WAAW,KAAM;AAC7B,MAAI,aAAa;AAEjB,MAAI,SAAS,MAAO;AAClB,QAAI,SAAS,IAAI,KAAK,OAAQ,OAAM,IAAI,MAAM,yGAAyB;AACvE,YAAQ,KAAK,SAAS,CAAC,IAAK,KAAK,SAAS,CAAC,KAAK,IAAM,KAAK,SAAS,CAAC,KAAK,KAAO,KAAK,SAAS,CAAC,KAAK,QAAS;AAC9G,iBAAa;AAAA,EACf;AAEA,SAAO,EAAE,OAAO,MAAM,WAAW;AACnC;AAKA,IAAM,0BAA0B,KAAO;AAShC,SAAS,gBAAgB,aAAqB,YAA6B;AAChF,QAAM,OAAO,IAAI,WAAW,WAAW;AAGvC,QAAM,MAAM,kBAAkB,MAAM,CAAC;AACrC,MAAI,IAAI,UAAU,yBAAyB;AACzC,UAAM,IAAI,MAAM,6FAAsC,uBAAuB,mDAAgB,IAAI,KAAK,GAAG;AAAA,EAC3G;AAEA,QAAM,eAAe,IAAI;AACzB,QAAM,aAAa,eAAe,IAAI;AACtC,MAAI,aAAa,KAAK,UAAU,IAAI,OAAO,KAAK;AAC9C,UAAM,IAAI,MAAM,oFAAwB;AAAA,EAC1C;AAGA,QAAM,UAAU,KAAK,SAAS,cAAc,eAAe,GAAG;AAC9D,QAAM,mBAAmB,yBAAyB,OAAO;AAGzD,QAAM,SAAS,cAAc,gBAAgB;AAG7C,QAAM,iBAAiB;AACvB,QAAM,gBAAgB,KAAK,SAAS,cAAc;AAElD,MAAI,cAAc,WAAW,GAAG;AAC9B,UAAM,IAAI,MAAM,+HAA2B;AAAA,EAC7C;AAGA,QAAM,aAAa,cAAc,SAAU,cAAc,SAAS;AAClE,MAAI,eAAe,GAAG;AACpB,UAAM,IAAI,MAAM,6HAA8B;AAAA,EAChD;AAEA,QAAM,cAAc,cAAc,SAAS,GAAG,UAAU;AACxD,QAAM,YAAY,iBAAiB,aAAa,MAAM;AAGtD,MAAI,YAAY;AACd,QAAI;AACF,aAAO,iBAAiB,OAAO,KAAK,SAAS,CAAC;AAAA,IAChD,QAAQ;AAEN,aAAO,OAAO,KAAK,SAAS;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,SAAS;AAC9B;;;AC3JA,IAAM,YAAY,OAAO,KAAK,CAAC,KAAM,KAAM,IAAM,KAAM,KAAM,KAAM,IAAM,GAAI,CAAC;AAC9E,IAAM,eAAe;AACrB,IAAM,YAAY;AAGlB,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AAExB,IAAM,kBAAkB,MAAM,OAAO;AAsB9B,SAAS,gBAAgB,MAAmC;AACjE,MAAI,KAAK,SAAS,IAAK,OAAM,IAAI,MAAM,mGAA6B;AACpE,MAAI,CAAC,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,SAAS,EAAG,OAAM,IAAI,MAAM,wDAAgB;AAI5E,QAAM,kBAAkB,KAAK,aAAa,EAAE;AAC5C,MAAI,kBAAkB,KAAK,kBAAkB,GAAI,OAAM,IAAI,MAAM,yFAAwB,eAAe;AACxG,QAAM,aAAa,KAAK;AACxB,QAAM,sBAAsB,KAAK,aAAa,EAAE;AAChD,MAAI,sBAAsB,GAAI,OAAM,IAAI,MAAM,sGAA2B,mBAAmB;AAC5F,QAAM,iBAAiB,KAAK;AAE5B,QAAM,iBAAiB,KAAK,aAAa,EAAE;AAC3C,MAAI,iBAAiB,IAAO,OAAM,IAAI,MAAM,0EAAwB,cAAc;AAClF,QAAM,iBAAiB,KAAK,aAAa,EAAE;AAC3C,QAAM,mBAAmB,KAAK,aAAa,EAAE;AAC7C,QAAM,qBAAqB,KAAK,aAAa,EAAE;AAC/C,QAAM,qBAAqB,KAAK,aAAa,EAAE;AAC/C,QAAM,mBAAmB,KAAK,aAAa,EAAE;AAC7C,QAAM,mBAAmB,KAAK,aAAa,EAAE;AAI7C,WAAS,aAAa,IAAoB;AACxC,WAAO,MAAM,KAAK;AAAA,EACpB;AAEA,WAAS,eAAe,IAAoB;AAC1C,UAAM,MAAM,aAAa,EAAE;AAC3B,QAAI,MAAM,aAAa,KAAK,OAAQ,QAAO,OAAO,MAAM,CAAC;AACzD,WAAO,KAAK,SAAS,KAAK,MAAM,UAAU;AAAA,EAC5C;AAIA,QAAM,aAAuB,CAAC;AAG9B,WAAS,IAAI,GAAG,IAAI,OAAO,WAAW,SAAS,gBAAgB,KAAK;AAClE,UAAM,MAAM,KAAK,aAAa,KAAK,IAAI,CAAC;AACxC,QAAI,QAAQ,aAAa,QAAQ,aAAc;AAC/C,eAAW,KAAK,GAAG;AAAA,EACrB;AAGA,MAAI,cAAc;AAClB,QAAM,eAAe,oBAAI,IAAY;AACrC,WAAS,IAAI,GAAG,IAAI,oBAAoB,gBAAgB,gBAAgB,gBAAgB,WAAW,KAAK;AACtG,QAAI,aAAa,IAAI,WAAW,EAAG;AACnC,iBAAa,IAAI,WAAW;AAE5B,UAAM,MAAM,eAAe,WAAW;AACtC,UAAM,mBAAoB,aAAa,IAAK;AAC5C,aAAS,IAAI,GAAG,IAAI,oBAAoB,WAAW,SAAS,gBAAgB,KAAK;AAC/E,YAAM,MAAM,IAAI,aAAa,IAAI,CAAC;AAClC,UAAI,QAAQ,aAAa,QAAQ,aAAc;AAC/C,iBAAW,KAAK,GAAG;AAAA,IACrB;AACA,kBAAc,IAAI,aAAa,mBAAmB,CAAC;AAAA,EACrD;AAIA,QAAM,sBAAsB,aAAa;AACzC,QAAM,WAAW,IAAI,YAAY,WAAW,SAAS,mBAAmB;AAExE,WAAS,KAAK,GAAG,KAAK,WAAW,QAAQ,MAAM;AAC7C,UAAM,MAAM,eAAe,WAAW,EAAE,CAAC;AACzC,aAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,eAAS,KAAK,sBAAsB,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,SACrD,IAAI,aAAa,IAAI,CAAC,IACtB;AAAA,IACN;AAAA,EACF;AAIA,WAAS,UAAU,aAAqB,UAA0B;AAChE,QAAI,gBAAgB,gBAAgB,gBAAgB,UAAW,QAAO,OAAO,MAAM,CAAC;AACpF,QAAI,WAAW,gBAAiB,OAAM,IAAI,MAAM,0DAAa;AAE7D,UAAM,SAAmB,CAAC;AAC1B,QAAI,UAAU;AACd,QAAI,YAAY;AAChB,UAAM,UAAU,oBAAI,IAAY;AAEhC,WAAO,YAAY,gBAAgB,YAAY,aAAa,YAAY,UAAU;AAChF,UAAI,QAAQ,IAAI,OAAO,EAAG;AAC1B,UAAI,QAAQ,OAAO,iBAAkB;AACrC,cAAQ,IAAI,OAAO;AAEnB,YAAM,MAAM,eAAe,OAAO;AAClC,YAAM,YAAY,WAAW;AAC7B,aAAO,KAAK,YAAY,aAAa,IAAI,SAAS,GAAG,SAAS,IAAI,GAAG;AACrE,mBAAa,KAAK,IAAI,IAAI,QAAQ,SAAS;AAE3C,gBAAU,UAAU,SAAS,SAAS,SAAS,OAAO,IAAI;AAAA,IAC5D;AAEA,WAAO,OAAO,OAAO,MAAM;AAAA,EAC7B;AAIA,MAAI,eAAmC;AAEvC,WAAS,kBAA+B;AACtC,QAAI,aAAc,QAAO;AAEzB,QAAI,uBAAuB,KAAK,uBAAuB,cAAc;AACnE,qBAAe,IAAI,YAAY,CAAC;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,UAAU,oBAAoB,qBAAqB,UAAU;AACjF,UAAM,UAAU,YAAY,SAAS;AACrC,mBAAe,IAAI,YAAY,OAAO;AACtC,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,mBAAa,CAAC,IAAI,YAAY,aAAa,IAAI,CAAC;AAAA,IAClD;AACA,WAAO;AAAA,EACT;AAIA,QAAM,UAAU,UAAU,gBAAgB,kBAAkB,GAAG;AAC/D,QAAM,aAAyB,CAAC;AAEhC,WAAS,SAAS,GAAG,SAAS,OAAO,QAAQ,UAAU,WAAW,SAAS,iBAAiB,UAAU,KAAK;AACzG,UAAM,UAAU,QAAQ,aAAa,SAAS,EAAE;AAChD,QAAI,WAAW,KAAK,UAAU,IAAI;AAChC,iBAAW,KAAK,EAAE,MAAM,IAAI,MAAM,GAAG,aAAa,GAAG,MAAM,EAAE,CAAC;AAC9D;AAAA,IACF;AAEA,UAAM,YAAY,UAAU;AAC5B,UAAM,OAAO,YAAY,IACrB,QAAQ,SAAS,QAAQ,SAAS,SAAS,EAAE,SAAS,SAAS,IAC/D;AAEJ,UAAM,OAAO,QAAQ,SAAS,EAAE;AAChC,UAAM,cAAc,QAAQ,aAAa,SAAS,GAAG;AAErD,UAAM,OAAO,QAAQ,aAAa,SAAS,GAAG;AAE9C,eAAW,KAAK,EAAE,MAAM,MAAM,aAAa,KAAK,CAAC;AAAA,EACnD;AAIA,MAAI,iBAAgC;AAEpC,WAAS,gBAAwB;AAC/B,QAAI,eAAgB,QAAO;AAC3B,UAAM,OAAO,WAAW,CAAC;AACzB,QAAI,CAAC,QAAQ,KAAK,SAAS,GAAG;AAC5B,uBAAiB,OAAO,MAAM,CAAC;AAC/B,aAAO;AAAA,IACT;AACA,qBAAiB,UAAU,KAAK,aAAa,KAAK,QAAQ,eAAe;AACzE,WAAO;AAAA,EACT;AAIA,WAAS,eAAe,aAAqB,MAAsB;AACjE,UAAM,MAAM,gBAAgB;AAC5B,UAAM,KAAK,cAAc;AACzB,QAAI,IAAI,WAAW,KAAK,GAAG,WAAW,EAAG,QAAO,OAAO,MAAM,CAAC;AAE9D,UAAM,SAAmB,CAAC;AAC1B,QAAI,UAAU;AACd,QAAI,YAAY;AAChB,UAAM,UAAU,oBAAI,IAAY;AAEhC,WAAO,YAAY,gBAAgB,YAAY,aAAa,YAAY,MAAM;AAC5E,UAAI,QAAQ,IAAI,OAAO,EAAG;AAC1B,UAAI,QAAQ,OAAO,iBAAkB;AACrC,cAAQ,IAAI,OAAO;AAEnB,YAAM,MAAM,UAAU;AACtB,YAAM,YAAY,OAAO;AACzB,YAAM,YAAY,KAAK,IAAI,gBAAgB,SAAS;AACpD,UAAI,MAAM,aAAa,GAAG,QAAQ;AAChC,eAAO,KAAK,GAAG,SAAS,KAAK,MAAM,SAAS,CAAC;AAAA,MAC/C;AACA,mBAAa;AAEb,gBAAU,UAAU,IAAI,SAAS,IAAI,OAAO,IAAI;AAAA,IAClD;AAEA,WAAO,OAAO,OAAO,MAAM;AAAA,EAC7B;AAIA,WAAS,eAAe,OAAyB;AAC/C,QAAI,MAAM,SAAS,EAAG,QAAO,OAAO,MAAM,CAAC;AAC3C,QAAI,MAAM,OAAO,kBAAkB;AACjC,YAAM,aAAa,eAAe,MAAM,aAAa,MAAM,IAAI;AAE/D,UAAI,WAAW,SAAS,EAAG,QAAO;AAAA,IACpC;AACA,WAAO,UAAU,MAAM,aAAa,MAAM,IAAI;AAAA,EAChD;AAMA,WAAS,gBAAgB,MAA+B;AAGtD,UAAM,QAAQ,KAAK,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG;AAE/C,QAAI,MAAM,WAAW,GAAG;AAEtB,aAAO,WAAW,KAAK,OAAK,EAAE,SAAS,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK;AAAA,IACtE;AAIA,UAAM,cAAc,MAAM,CAAC;AAC3B,UAAM,aAAa,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG;AAG1C,eAAW,KAAK,YAAY;AAC1B,UAAI,EAAE,SAAS,KAAK,EAAE,SAAS,YAAY;AAEzC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AACvC,WAAO,WAAW,KAAK,OAAK,EAAE,SAAS,KAAK,EAAE,SAAS,QAAQ,KAAK;AAAA,EACtE;AAIA,SAAO;AAAA,IACL,WAAW,MAA6B;AAEtC,YAAM,aAAa,KAAK,QAAQ,OAAO,EAAE;AACzC,YAAM,QAAQ,gBAAgB,UAAU;AACxC,UAAI,CAAC,SAAS,MAAM,SAAS,EAAG,QAAO;AACvC,YAAM,SAAS,eAAe,KAAK;AACnC,aAAO,OAAO,SAAS,IAAI,SAAS;AAAA,IACtC;AAAA,IAEA,UAAsB;AACpB,aAAO,WAAW,OAAO,OAAK,EAAE,SAAS,CAAC;AAAA,IAC5C;AAAA,EACF;AACF;;;AC5RA,SAAS,qBAAqB;AAC9B,IAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,MAAiBA,SAAQ,KAAK;AAUpC,IAAM,eAAe;AAErB,IAAM,uBAAuB,MAAM,OAAO;AAEnC,SAAS,kBAAkB,QAAgB,SAA6C;AAE7F,MAAI,MAA2B;AAC/B,MAAI,aAAyC;AAC7C,QAAM,WAA2B,CAAC;AAElC,MAAI;AACF,UAAM,IAAI,MAAM,MAAM;AAAA,EACxB,QAAQ;AACN,QAAI;AACF,mBAAa,gBAAgB,MAAM;AACnC,eAAS,KAAK,EAAE,SAAS,kGAAiC,MAAM,uBAAuB,CAAC;AAAA,IAC1F,QAAQ;AACN,YAAM,IAAI,YAAY,6FAAsC;AAAA,IAC9D;AAAA,EACF;AAGA,QAAM,aAAa,CAAC,SAAgC;AAClD,QAAI,KAAK;AACP,YAAM,QAAQ,IAAI,KAAK,KAAK,IAAI;AAChC,aAAO,OAAO,UAAU,OAAO,KAAK,MAAM,OAAO,IAAI;AAAA,IACvD;AACA,WAAO,WAAY,WAAW,IAAI;AAAA,EACpC;AAEA,QAAM,aAAa,WAAW,aAAa;AAC3C,MAAI,CAAC,WAAY,OAAM,IAAI,YAAY,4CAAmB;AAC1D,QAAM,SAAS,gBAAgB,UAAU;AACzC,MAAI,OAAO,cAAc,oBAAqB,OAAM,IAAI,YAAY,iDAAc;AAClF,MAAI,OAAO,QAAQ,eAAgB,OAAM,IAAI,YAAY,sFAAqB;AAC9E,MAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,YAAY,oFAAwB;AAC3E,QAAM,cAAc,OAAO,QAAQ,qBAAqB;AACxD,QAAM,gBAAgB,OAAO,QAAQ,uBAAuB;AAE5D,QAAM,WAA6B;AAAA,IACjC,SAAS,GAAG,OAAO,YAAY;AAAA,EACjC;AACA,MAAI,IAAK,qBAAoB,KAAK,QAAQ;AAG1C,QAAM,UAAU,MACZ,mBAAmB,KAAK,UAAU,IAClC,uBAAuB,WAAW,UAAU,GAAG,UAAU;AAE7D,QAAM,WAAW,eACZ,MAAM,qBAAqB,KAAK,UAAU,IAAI,4BAA4B,YAAa,UAAU,IACjG,MAAM,aAAa,GAAG,IAAI,oBAAoB,YAAa,UAAU;AAC1E,MAAI,SAAS,WAAW,EAAG,OAAM,IAAI,YAAY,oFAAmB;AAEpE,WAAS,YAAY,SAAS;AAG9B,QAAM,aAAa,SAAS,QAAQ,eAAe,QAAQ,OAAO,SAAS,MAAM,IAAI;AACrF,QAAM,cAAc,aAAa,WAAW,OAAO,SAAS;AAE5D,QAAM,SAAoB,CAAC;AAC3B,MAAI,oBAAoB;AACxB,MAAI,iBAAiB;AACrB,WAAS,KAAK,GAAG,KAAK,SAAS,QAAQ,MAAM;AAC3C,QAAI,cAAc,CAAC,WAAW,IAAI,KAAK,CAAC,EAAG;AAC3C,QAAI;AACF,YAAM,cAAc,SAAS,EAAE;AAE/B,YAAM,OAAQ,CAAC,gBAAgB,aAAc,iBAAiB,OAAO,KAAK,WAAW,CAAC,IAAI,OAAO,KAAK,WAAW;AACjH,2BAAqB,KAAK;AAC1B,UAAI,oBAAoB,qBAAsB,OAAM,IAAI,YAAY,8FAAuC;AAC3G,YAAM,UAAU,YAAY,IAAI;AAChC,YAAM,gBAAgB,aAAa,SAAS,SAAS,UAAU,KAAK,CAAC;AACrE,aAAO,KAAK,GAAG,aAAa;AAC5B;AACA,eAAS,aAAa,gBAAgB,WAAW;AAAA,IACnD,SAAS,QAAQ;AACf,UAAI,kBAAkB,YAAa,OAAM;AACzC,eAAS,KAAK,EAAE,MAAM,KAAK,GAAG,SAAS,gBAAM,KAAK,CAAC,+BAAW,kBAAkB,QAAQ,OAAO,UAAU,yCAAW,IAAI,MAAM,gBAAgB,CAAC;AAAA,IACjJ;AAAA,EACF;AAGA,QAAM,SAAS,MACX,kBAAkB,KAAK,QAAQ,YAAY,QAAQ,IACnD,yBAAyB,YAAa,QAAQ,YAAY,QAAQ;AAGtE,QAAM,aAAa,oBAAoB,MAAM;AAG7C,MAAI,SAAS;AACX,uBAAmB,YAAY,OAAO;AAAA,EACxC;AAGA,QAAM,UAAyB,WAC5B,OAAO,OAAK,EAAE,SAAS,aAAa,EAAE,SAAS,EAAE,IAAI,EACrD,IAAI,QAAM,EAAE,OAAO,EAAE,OAAQ,MAAM,EAAE,MAAO,YAAY,EAAE,WAAW,EAAE;AAE1E,QAAM,WAAW,iBAAiB,UAAU;AAC5C,SAAO,EAAE,UAAU,QAAQ,YAAY,UAAU,SAAS,QAAQ,SAAS,IAAI,UAAU,QAAW,UAAU,SAAS,SAAS,IAAI,WAAW,QAAW,QAAQ,OAAO,SAAS,IAAI,SAAS,OAAU;AAC3M;AAGA,SAAS,mBAAmB,KAAmB,YAAwC;AACrF,MAAI;AACF,UAAM,QAAQ,IAAI,KAAK,KAAK,UAAU;AACtC,QAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,UAAM,OAAO,aAAa,iBAAiB,OAAO,KAAK,MAAM,OAAO,CAAC,IAAI,OAAO,KAAK,MAAM,OAAO;AAClG,UAAM,UAAU,YAAY,IAAI;AAChC,WAAO,aAAa,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,uBAAuB,KAAoB,YAAwC;AAC1F,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,OAAO,aAAa,iBAAiB,GAAG,IAAI;AAClD,WAAO,aAAa,YAAY,IAAI,CAAC;AAAA,EACvC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,mBAAmB,QAAmB,SAA2B;AAExE,MAAI,eAAe;AAGnB,aAAW,SAAS,QAAQ,QAAQ;AAClC,UAAM,QAAQ,MAAM,UAAU,MAAM,MAAM,YAAY;AACtD,QAAI,KAAK,SAAS,cAAI,KAAK,KAAK,SAAS,cAAI,KAAK,SAAS,YAAY,SAAS,QAAQ;AACtF,YAAM,KAAK,QAAQ,WAAW,MAAM,WAAW;AAE/C,UAAI,IAAI,WAAW,GAAG;AAAE,uBAAe,GAAG,WAAW;AAAI;AAAA,MAAM;AAAA,IACjE;AAAA,EACF;AAGA,MAAI,iBAAiB,GAAG;AACtB,UAAM,WAAW,oBAAI,IAAoB;AACzC,eAAW,KAAK,QAAQ;AACtB,UAAI,EAAE,OAAO,UAAU;AACrB,iBAAS,IAAI,EAAE,MAAM,WAAW,SAAS,IAAI,EAAE,MAAM,QAAQ,KAAK,KAAK,CAAC;AAAA,MAC1E;AAAA,IACF;AACA,QAAI,WAAW;AACf,eAAW,CAAC,MAAM,KAAK,KAAK,UAAU;AACpC,UAAI,QAAQ,UAAU;AAAE,mBAAW;AAAO,uBAAe;AAAA,MAAK;AAAA,IAChE;AAAA,EACF;AAEA,MAAI,gBAAgB,EAAG;AAEvB,aAAW,SAAS,QAAQ;AAE1B,QAAI,MAAM,SAAS,UAAW;AAC9B,QAAI,MAAM,SAAS,eAAe,CAAC,MAAM,KAAM;AAC/C,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,KAAK,WAAW,KAAK,KAAK,SAAS,IAAK;AAC5C,QAAI,QAAQ,KAAK,IAAI,EAAG;AAExB,QAAI,QAAQ;AAGZ,QAAI,MAAM,OAAO,YAAY,eAAe,GAAG;AAC7C,YAAM,QAAQ,MAAM,MAAM,WAAW;AACrC,UAAI,SAAS,iBAAkB,SAAQ;AAAA,eAC9B,SAAS,iBAAkB,SAAQ;AAAA,eACnC,SAAS,iBAAkB,SAAQ;AAAA,IAC9C;AAGA,QAAI,eAAe,KAAK,IAAI,KAAK,KAAK,UAAU,IAAI;AAClD,UAAI,UAAU,EAAG,SAAQ;AAAA,IAC3B,WAAW,wBAAwB,KAAK,IAAI,KAAK,KAAK,UAAU,IAAI;AAClE,UAAI,UAAU,EAAG,SAAQ;AAAA,IAC3B;AAEA,QAAI,QAAQ,GAAG;AACb,YAAM,OAAO;AACb,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AACF;AASA,SAAS,oBAAoB,KAAmB,UAAkC;AAChF,MAAI;AAEF,UAAM,eACJ,IAAI,KAAK,KAAK,yBAA4B,KAC1C,IAAI,KAAK,KAAK,sBAAyB;AACzC,QAAI,CAAC,cAAc,QAAS;AAE5B,UAAM,OAAO,OAAO,KAAK,aAAa,OAAO;AAC7C,QAAI,KAAK,SAAS,GAAI;AAItB,UAAM,UAAU,KAAK,aAAa,EAAE;AACpC,QAAI,YAAY,EAAG;AAEnB,UAAM,YAAY,KAAK,aAAa,EAAE;AACtC,QAAI,aAAa,KAAK,SAAS,EAAG;AAGlC,UAAM,WAAW,KAAK,aAAa,YAAY,CAAC;AAChD,QAAI,aAAa,KAAK,WAAW,IAAK;AAEtC,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,YAAM,cAAc,YAAY,IAAI,IAAI;AACxC,UAAI,cAAc,IAAI,KAAK,OAAQ;AAEnC,YAAM,SAAS,KAAK,aAAa,WAAW;AAC5C,YAAM,aAAa,YAAY,KAAK,aAAa,cAAc,CAAC;AAChE,UAAI,aAAa,IAAI,KAAK,OAAQ;AAGlC,UAAI,WAAW,KAAK,WAAW,KAAK,WAAW,EAAG;AAElD,YAAM,WAAW,KAAK,aAAa,UAAU;AAE7C,UAAI,aAAa,GAAM;AAEvB,YAAM,SAAS,KAAK,aAAa,aAAa,CAAC;AAC/C,UAAI,WAAW,KAAK,SAAS,OAAS,aAAa,IAAI,SAAS,KAAK,OAAQ;AAE7E,YAAM,MAAM,KAAK,SAAS,aAAa,GAAG,aAAa,IAAI,MAAM,EAAE,SAAS,MAAM,EAAE,QAAQ,QAAQ,EAAE,EAAE,KAAK;AAC7G,UAAI,CAAC,IAAK;AAEV,UAAI,WAAW,EAAG,UAAS,QAAQ;AAAA,eAC1B,WAAW,EAAG,UAAS,SAAS;AAAA,eAChC,WAAW,EAAG,UAAS,cAAc;AAAA,IAChD;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,wBAAwB,QAAkC;AACxE,QAAM,MAAM,IAAI,MAAM,MAAM;AAC5B,QAAM,cAAc,IAAI,KAAK,KAAK,aAAa;AAC/C,MAAI,CAAC,aAAa,QAAS,OAAM,IAAI,YAAY,4CAAmB;AACpE,QAAM,SAAS,gBAAgB,OAAO,KAAK,YAAY,OAAO,CAAC;AAC/D,MAAI,OAAO,cAAc,oBAAqB,OAAM,IAAI,YAAY,iDAAc;AAElF,QAAM,WAA6B;AAAA,IACjC,SAAS,GAAG,OAAO,YAAY;AAAA,EACjC;AACA,sBAAoB,KAAK,QAAQ;AAEjC,QAAM,WAAW,aAAa,GAAG;AACjC,WAAS,YAAY,SAAS;AAE9B,SAAO;AACT;AAGA,SAAS,qBAAqB,KAAmB,YAA+B;AAC9E,QAAM,WAAoD,CAAC;AAE3D,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,QAAQ,IAAI,KAAK,KAAK,oBAAoB,CAAC,EAAE;AACnD,QAAI,CAAC,OAAO,QAAS;AACrB,QAAI;AACF,YAAM,YAAY,gBAAgB,OAAO,KAAK,MAAM,OAAO,GAAG,UAAU;AACxE,eAAS,KAAK,EAAE,KAAK,GAAG,SAAS,UAAU,CAAC;AAAA,IAC9C,QAAQ;AAEN;AAAA,IACF;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,OAAK,EAAE,OAAO;AAClE;AAEA,SAAS,aAAa,KAA6B;AACjD,QAAM,WAAoD,CAAC;AAE3D,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,QAAQ,IAAI,KAAK,KAAK,oBAAoB,CAAC,EAAE;AACnD,QAAI,CAAC,OAAO,QAAS;AACrB,aAAS,KAAK,EAAE,KAAK,GAAG,SAAS,OAAO,KAAK,MAAM,OAAO,EAAE,CAAC;AAAA,EAC/D;AAEA,MAAI,SAAS,WAAW,KAAK,IAAI,WAAW;AAC1C,eAAW,SAAS,IAAI,WAAW;AACjC,UAAI,SAAS,UAAU,aAAc;AACrC,UAAI,MAAM,MAAM,WAAW,SAAS,KAAK,MAAM,SAAS;AACtD,cAAM,MAAM,SAAS,MAAM,KAAK,QAAQ,WAAW,EAAE,GAAG,EAAE,KAAK;AAC/D,iBAAS,KAAK,EAAE,KAAK,SAAS,OAAO,KAAK,MAAM,OAAO,EAAE,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,OAAK,EAAE,OAAO;AAClE;AAGA,SAAS,oBAAoB,MAA2B,YAA+B;AACrF,QAAM,WAAoD,CAAC;AAC3D,MAAI,oBAAoB;AACxB,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,MAAM,KAAK,WAAW,oBAAoB,CAAC,EAAE,KAAK,KAAK,WAAW,UAAU,CAAC,EAAE;AACrF,QAAI,CAAC,IAAK;AACV,UAAM,UAAU,aAAa,iBAAiB,GAAG,IAAI;AACrD,yBAAqB,QAAQ;AAC7B,QAAI,oBAAoB,qBAAsB,OAAM,IAAI,YAAY,8FAAuC;AAC3G,aAAS,KAAK,EAAE,KAAK,GAAG,QAAQ,CAAC;AAAA,EACnC;AACA,MAAI,SAAS,WAAW,GAAG;AAEzB,eAAW,KAAK,KAAK,QAAQ,GAAG;AAC9B,UAAI,SAAS,UAAU,aAAc;AACrC,UAAI,EAAE,KAAK,WAAW,SAAS,GAAG;AAChC,cAAM,MAAM,SAAS,EAAE,KAAK,QAAQ,WAAW,EAAE,GAAG,EAAE,KAAK;AAC3D,cAAM,MAAM,KAAK,WAAW,EAAE,IAAI;AAClC,YAAI,KAAK;AACP,gBAAM,UAAU,aAAa,iBAAiB,GAAG,IAAI;AACrD,+BAAqB,QAAQ;AAC7B,cAAI,oBAAoB,qBAAsB,OAAM,IAAI,YAAY,8FAAuC;AAC3G,mBAAS,KAAK,EAAE,KAAK,QAAQ,CAAC;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,OAAK,EAAE,OAAO;AAClE;AAGA,SAAS,4BAA4B,MAA2B,YAA+B;AAC7F,QAAM,WAAoD,CAAC;AAC3D,MAAI,oBAAoB;AACxB,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,MAAM,KAAK,WAAW,oBAAoB,CAAC,EAAE,KAAK,KAAK,WAAW,UAAU,CAAC,EAAE;AACrF,QAAI,CAAC,IAAK;AACV,QAAI;AACF,YAAM,UAAU,gBAAgB,KAAK,UAAU;AAC/C,2BAAqB,QAAQ;AAC7B,UAAI,oBAAoB,qBAAsB,OAAM,IAAI,YAAY,8FAAuC;AAC3G,eAAS,KAAK,EAAE,KAAK,GAAG,QAAQ,CAAC;AAAA,IACnC,QAAQ;AAAE;AAAA,IAAM;AAAA,EAClB;AACA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,OAAK,EAAE,OAAO;AAClE;AAKA,IAAM,sBAAsB;AAG5B,SAAS,iBAAiB,SAAsB,SAAyB;AACvE,QAAM,YAAY,QAAQ,OAAO,EAAE;AAEnC,WAAS,IAAI,UAAU,GAAG,IAAI,QAAQ,UAAU,IAAI,UAAU,IAAI,KAAK;AACrE,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,EAAE,SAAS,UAAW;AAI1B,QAAI,EAAE,KAAK,UAAU,GAAG;AAItB,UAAI,EAAE,QAAQ,uBAAuB,EAAE,QAAQ,YAAY,KAAK,EAAE,KAAK,UAAU,GAAG;AAClF,cAAM,aAAa,EAAE,KAAK,aAAa,CAAC;AACxC,YAAI,aAAa,IAAO,QAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,MAA0C;AACjE,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,MAAI,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,GAAM,QAAO;AACzF,MAAI,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,IAAM,QAAO;AACrE,MAAI,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,GAAM,QAAO;AACrE,MAAI,KAAK,CAAC,MAAM,MAAQ,KAAK,CAAC,MAAM,GAAM,QAAO;AACjD,MAAI,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,OAAQ,KAAK,CAAC,MAAM,IAAM,QAAO;AACzF,MAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,EAAM,QAAO;AACzF,SAAO;AACT;AAGA,SAAS,kBACP,KACA,QACA,YACA,UACkB;AAElB,QAAM,aAAa,oBAAI,IAA4C;AACnE,QAAM,YAAY;AAClB,MAAI,IAAI,WAAW;AACjB,eAAW,SAAS,IAAI,WAAW;AACjC,UAAI,CAAC,OAAO,QAAQ,CAAC,MAAM,QAAS;AACpC,YAAM,QAAQ,MAAM,KAAK,MAAM,SAAS;AACxC,UAAI,CAAC,MAAO;AACZ,YAAM,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE;AACjC,UAAI,OAAO,OAAO,KAAK,MAAM,OAAO;AACpC,UAAI,YAAY;AACd,YAAI;AAAE,iBAAO,iBAAiB,IAAI;AAAA,QAAE,QAAQ;AAAA,QAAqB;AAAA,MACnE;AACA,iBAAW,IAAI,KAAK,EAAE,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,MAAI,WAAW,SAAS,EAAG,QAAO,CAAC;AAEnC,QAAM,SAA2B,CAAC;AAClC,MAAI,aAAa;AAEjB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,WAAW,CAAC,MAAM,KAAM;AAC3C,UAAM,QAAQ,SAAS,MAAM,MAAM,EAAE;AACrC,QAAI,MAAM,KAAK,EAAG;AAElB,UAAM,MAAM,WAAW,IAAI,KAAK;AAChC,QAAI,CAAC,KAAK;AACR,eAAS,KAAK,EAAE,MAAM,MAAM,YAAY,SAAS,WAAW,KAAK,iBAAO,MAAM,gBAAgB,CAAC;AAC/F,YAAM,OAAO;AACb,YAAM,OAAO,gCAAiB,KAAK;AACnC;AAAA,IACF;AAEA,UAAM,OAAO,gBAAgB,IAAI,IAAI;AACrC,QAAI,CAAC,MAAM;AACT,eAAS,KAAK,EAAE,MAAM,MAAM,YAAY,SAAS,WAAW,KAAK,gEAAmB,MAAM,gBAAgB,CAAC;AAC3G,YAAM,OAAO;AACb,YAAM,OAAO,wBAAS,IAAI,IAAI;AAC9B;AAAA,IACF;AAEA;AACA,UAAM,MAAM,KAAK,SAAS,MAAM,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ;AACzI,UAAM,WAAW,SAAS,OAAO,UAAU,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,GAAG;AAEpE,WAAO,KAAK,EAAE,UAAU,MAAM,IAAI,WAAW,IAAI,IAAI,GAAG,UAAU,KAAK,CAAC;AACxE,UAAM,OAAO;AACb,UAAM,YAAY,EAAE,MAAM,IAAI,WAAW,IAAI,IAAI,GAAG,UAAU,MAAM,UAAU,IAAI,KAAK;AAAA,EACzF;AAEA,SAAO;AACT;AAGA,SAAS,yBACP,MACA,QACA,YACA,UACkB;AAElB,QAAM,aAAa,oBAAI,IAA4C;AACnE,QAAM,QAAQ;AACd,aAAW,KAAK,KAAK,QAAQ,GAAG;AAC9B,UAAM,QAAQ,EAAE,KAAK,MAAM,KAAK;AAChC,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE;AACjC,QAAI,MAAM,KAAK,WAAW,EAAE,IAAI;AAChC,QAAI,CAAC,IAAK;AACV,QAAI,YAAY;AACd,UAAI;AAAE,cAAM,iBAAiB,GAAG;AAAA,MAAE,QAAQ;AAAA,MAAqB;AAAA,IACjE;AACA,eAAW,IAAI,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC;AAAA,EACjD;AACA,MAAI,WAAW,SAAS,EAAG,QAAO,CAAC;AAEnC,QAAM,SAA2B,CAAC;AAClC,MAAI,aAAa;AACjB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,WAAW,CAAC,MAAM,KAAM;AAC3C,UAAM,QAAQ,SAAS,MAAM,MAAM,EAAE;AACrC,QAAI,MAAM,KAAK,EAAG;AAClB,UAAM,MAAM,WAAW,IAAI,KAAK;AAChC,QAAI,CAAC,KAAK;AACR,eAAS,KAAK,EAAE,MAAM,MAAM,YAAY,SAAS,WAAW,KAAK,6BAAS,MAAM,gBAAgB,CAAC;AACjG,YAAM,OAAO;AAAa,YAAM,OAAO,gCAAiB,KAAK;AAAK;AAAA,IACpE;AACA,UAAM,OAAO,gBAAgB,IAAI,IAAI;AACrC,QAAI,CAAC,MAAM;AACT,eAAS,KAAK,EAAE,MAAM,MAAM,YAAY,SAAS,WAAW,KAAK,gEAAmB,MAAM,gBAAgB,CAAC;AAC3G,YAAM,OAAO;AAAa,YAAM,OAAO,wBAAS,IAAI,IAAI;AAAK;AAAA,IAC/D;AACA;AACA,UAAM,MAAM,KAAK,SAAS,MAAM,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI,QAAQ;AACzI,UAAM,WAAW,SAAS,OAAO,UAAU,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,GAAG;AACpE,WAAO,KAAK,EAAE,UAAU,MAAM,IAAI,WAAW,IAAI,IAAI,GAAG,UAAU,KAAK,CAAC;AACxE,UAAM,OAAO;AACb,UAAM,YAAY,EAAE,MAAM,IAAI,WAAW,IAAI,IAAI,GAAG,UAAU,MAAM,UAAU,IAAI,KAAK;AAAA,EACzF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,SAAsB,SAA4B,UAA0B,YAA+B;AAC/H,QAAM,SAAoB,CAAC;AAC3B,MAAI,IAAI;AAER,SAAO,IAAI,QAAQ,QAAQ;AACzB,UAAM,MAAM,QAAQ,CAAC;AAErB,QAAI,IAAI,UAAU,mBAAmB,IAAI,UAAU,GAAG;AACpD,YAAM,EAAE,WAAW,QAAQ,SAAS,cAAc,YAAY,IAAI,yBAAyB,SAAS,CAAC;AACrG,UAAI,WAAW;AACb,cAAM,QAAiB,EAAE,MAAM,aAAa,MAAM,WAAW,YAAY,WAAW;AAEpF,YAAI,WAAW,aAAa,SAAS,GAAG;AACtC,gBAAM,QAAQ,iBAAiB,cAAc,OAAO;AACpD,cAAI,MAAO,OAAM,QAAQ;AAAA,QAC3B;AAEA,YAAI,WAAW,eAAe,KAAK,cAAc,QAAQ,WAAW,QAAQ;AAC1E,gBAAM,KAAK,QAAQ,WAAW,WAAW,EAAE;AAC3C,cAAI,MAAM,KAAK,MAAM,GAAG;AACtB,kBAAM,OAAO;AACb,kBAAM,QAAQ;AAAA,UAChB;AAAA,QACF;AACA,eAAO,KAAK,KAAK;AAAA,MACnB;AACA,iBAAW,KAAK,OAAQ,QAAO,KAAK,EAAE,MAAM,SAAS,OAAO,GAAG,YAAY,WAAW,CAAC;AACvF,UAAI;AACJ;AAAA,IACF;AAEA,QAAI,IAAI,UAAU,mBAAmB,IAAI,SAAS,KAAK,IAAI,KAAK,UAAU,GAAG;AAC3E,YAAM,SAAS,IAAI,KAAK,SAAS,GAAG,CAAC,EAAE,SAAS,OAAO;AACvD,UAAI,WAAW,UAAU,WAAW,QAAQ;AAC1C,cAAM,EAAE,OAAO,QAAQ,IAAI,gBAAgB,SAAS,CAAC;AACrD,YAAI,MAAO,QAAO,KAAK,EAAE,MAAM,SAAS,OAAO,YAAY,WAAW,CAAC;AACvE,YAAI;AACJ;AAAA,MACF;AAEA,UAAI,WAAW,UAAU,WAAW,QAAQ;AAC1C,cAAM,QAAQ,iBAAiB,SAAS,CAAC;AACzC,YAAI,SAAS,GAAG;AACd,iBAAO,KAAK,EAAE,MAAM,SAAS,MAAM,OAAO,KAAK,GAAG,YAAY,WAAW,CAAC;AAAA,QAC5E,OAAO;AAEL,gBAAM,UAAU,mBAAmB,SAAS,CAAC;AAC7C,cAAI,SAAS;AACX,mBAAO,KAAK,EAAE,MAAM,aAAa,MAAM,SAAS,YAAY,WAAW,CAAC;AAAA,UAC1E;AAAA,QAEF;AAAA,MACF,WAAW,WAAW,UAAU,WAAW,QAAQ;AACjD,iBAAS,KAAK,EAAE,MAAM,YAAY,SAAS,iDAAc,OAAO,KAAK,CAAC,IAAI,MAAM,gBAAgB,CAAC;AAAA,MACnG,WAES,WAAW,UAAU,WAAW,UAAU,WAAW,UAAU,WAAW,QAAQ;AACzF,cAAM,WAAW,gBAAgB,SAAS,CAAC;AAC3C,YAAI,YAAY,OAAO,SAAS,GAAG;AAEjC,gBAAM,YAAY,OAAO,OAAO,SAAS,CAAC;AAC1C,cAAI,UAAU,SAAS,aAAa;AAClC,sBAAU,eAAe,UAAU,eAC/B,UAAU,eAAe,OAAO,WAChC;AAAA,UACN;AAAA,QACF;AAAA,MACF,WAES,WAAW,UAAU,WAAW,QAAQ;AAC/C,cAAM,MAAM,oBAAoB,IAAI,IAAI;AACxC,YAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,gBAAM,YAAY,OAAO,OAAO,SAAS,CAAC;AAC1C,cAAI,UAAU,SAAS,eAAe,CAAC,UAAU,MAAM;AACrD,sBAAU,OAAO,aAAa,GAAG,KAAK;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,gBAAgB,SAAsB,SAAgC;AAC7E,QAAM,YAAY,QAAQ,OAAO,EAAE;AACnC,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,UAAU,GAAG,IAAI,QAAQ,UAAU,IAAI,UAAU,KAAK,KAAK;AACtE,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,EAAE,SAAS,UAAW;AAE1B,QAAI,EAAE,UAAU,eAAe;AAC7B,YAAM,IAAI,YAAY,EAAE,IAAI,EAAE,KAAK;AACnC,UAAI,EAAG,OAAM,KAAK,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI;AAC9C;AAGA,SAAS,mBAAmB,SAAsB,SAAgC;AAChF,QAAM,YAAY,QAAQ,OAAO,EAAE;AACnC,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,UAAU,GAAG,IAAI,QAAQ,UAAU,IAAI,UAAU,KAAK,KAAK;AACtE,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,EAAE,SAAS,UAAW;AAE1B,QAAI,EAAE,UAAU,eAAe;AAC7B,YAAM,IAAI,YAAY,EAAE,IAAI,EAAE,KAAK;AACnC,UAAI,EAAG,OAAM,KAAK,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAC/C;AAGA,SAAS,oBAAoB,MAA6B;AAIxD,MAAI;AAEF,UAAM,UAAU,OAAO,KAAK,QAAQ,SAAS;AAC7C,UAAM,MAAM,KAAK,QAAQ,OAAO;AAChC,QAAI,OAAO,GAAG;AAEZ,UAAI,MAAM;AACV,aAAO,MAAM,IAAI,KAAK,QAAQ;AAC5B,cAAM,KAAK,KAAK,aAAa,GAAG;AAChC,YAAI,OAAO,EAAG;AACd,eAAO;AAAA,MACT;AACA,YAAM,MAAM,KAAK,SAAS,KAAK,GAAG,EAAE,SAAS,SAAS;AAEtD,UAAI,iBAAiB,KAAK,GAAG,KAAK,IAAI,SAAS,KAAM;AACnD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAAoB;AAC5B,SAAO;AACT;AAGA,SAAS,iBAAiB,cAAwB,SAA8C;AAC9F,MAAI,aAAa,WAAW,KAAK,QAAQ,WAAW,WAAW,EAAG,QAAO;AAGzE,QAAM,OAAO,oBAAI,IAAoB;AACrC,MAAI,WAAW,GAAG,aAAa,aAAa,CAAC;AAC7C,aAAW,MAAM,cAAc;AAC7B,UAAM,SAAS,KAAK,IAAI,EAAE,KAAK,KAAK;AACpC,SAAK,IAAI,IAAI,KAAK;AAClB,QAAI,QAAQ,UAAU;AAAE,iBAAW;AAAO,mBAAa;AAAA,IAAG;AAAA,EAC5D;AAEA,QAAM,KAAK,QAAQ,WAAW,UAAU;AACxC,MAAI,CAAC,GAAI,QAAO;AAEhB,QAAM,QAAqB,CAAC;AAC5B,MAAI,GAAG,WAAW,EAAG,OAAM,WAAW,GAAG,WAAW;AACpD,MAAI,GAAG,YAAY,EAAM,OAAM,SAAS;AACxC,MAAI,GAAG,YAAY,EAAM,OAAM,OAAO;AAEtC,SAAQ,MAAM,YAAY,MAAM,QAAQ,MAAM,SAAU,QAAQ;AAClE;AAEA,SAAS,yBAAyB,SAAsB,UAAkB;AACxE,QAAM,aAAa,QAAQ,QAAQ,EAAE;AACrC,MAAI,OAAO;AACX,QAAM,SAA0C,CAAC;AACjD,QAAM,eAAyB,CAAC;AAGhC,QAAM,iBAAiB,QAAQ,QAAQ,EAAE;AACzC,QAAM,cAAc,eAAe,UAAU,KAAK,eAAe,aAAa,CAAC,IAAI;AAEnF,MAAI,IAAI,WAAW;AAEnB,SAAO,IAAI,QAAQ,QAAQ;AACzB,UAAM,MAAM,QAAQ,CAAC;AACrB,QAAI,IAAI,UAAU,mBAAmB,IAAI,SAAS,WAAY;AAE9D,QAAI,IAAI,UAAU,eAAe;AAC/B,aAAO,YAAY,IAAI,IAAI;AAAA,IAC7B;AAGA,QAAI,IAAI,UAAU,kBAAkB,IAAI,KAAK,UAAU,GAAG;AAExD,eAAS,SAAS,GAAG,SAAS,IAAI,IAAI,KAAK,QAAQ,UAAU,GAAG;AAC9D,qBAAa,KAAK,IAAI,KAAK,aAAa,SAAS,CAAC,CAAC;AAAA,MACrD;AAAA,IACF;AAEA,QAAI,IAAI,UAAU,mBAAmB,IAAI,KAAK,UAAU,GAAG;AACzD,YAAM,SAAS,IAAI,KAAK,SAAS,GAAG,CAAC,EAAE,SAAS,OAAO;AACvD,UAAI,WAAW,UAAU,WAAW,QAAQ;AAC1C,cAAM,EAAE,OAAO,QAAQ,IAAI,gBAAgB,SAAS,CAAC;AACrD,YAAI,MAAO,QAAO,KAAK,KAAK;AAC5B,YAAI;AACJ;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,KAAK;AAC1B,SAAO,EAAE,WAAW,WAAW,MAAM,QAAQ,SAAS,GAAG,cAAc,YAAY;AACrF;AAEA,SAAS,gBAAgB,SAAsB,UAAkB;AAC/D,QAAM,aAAa,QAAQ,QAAQ,EAAE;AACrC,MAAI,IAAI,WAAW;AACnB,MAAI,OAAO,GAAG,OAAO;AACrB,QAAM,QAAuB,CAAC;AAE9B,SAAO,IAAI,QAAQ,QAAQ;AACzB,UAAM,MAAM,QAAQ,CAAC;AACrB,QAAI,IAAI,UAAU,mBAAmB,IAAI,SAAS,WAAY;AAC9D,QAAI,IAAI,UAAU,mBAAmB,IAAI,SAAS,WAAY;AAE9D,QAAI,IAAI,UAAU,aAAa,IAAI,KAAK,UAAU,GAAG;AACnD,aAAO,KAAK,IAAI,IAAI,KAAK,aAAa,CAAC,GAAG,QAAQ;AAClD,aAAO,KAAK,IAAI,IAAI,KAAK,aAAa,CAAC,GAAG,QAAQ;AAAA,IACpD;AAEA,QAAI,IAAI,UAAU,iBAAiB;AACjC,YAAM,EAAE,MAAM,QAAQ,IAAI,eAAe,SAAS,GAAG,UAAU;AAC/D,UAAI,KAAM,OAAM,KAAK,IAAI;AACzB,UAAI;AACJ;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,SAAS,KAAK,SAAS,KAAK,MAAM,WAAW,EAAG,QAAO,EAAE,OAAO,MAAM,SAAS,EAAE;AAIrF,QAAM,UAAU,MAAM,KAAK,OAAK,EAAE,YAAY,UAAa,EAAE,YAAY,MAAS;AAClF,MAAI,SAAS;AACX,UAAMC,YAAW,aAAa,MAAM,MAAM,KAAK;AAC/C,UAAM,UAAUA,UAAS,IAAI,SAAO,IAAI,IAAI,QAAM;AAAA,MAChD,MAAM,EAAE,KAAK,KAAK;AAAA,MAClB,SAAS,EAAE;AAAA,MACX,SAAS,EAAE;AAAA,IACb,EAAE,CAAC;AACH,WAAO,EAAE,OAAO,EAAE,MAAM,MAAM,OAAO,SAAS,WAAW,OAAO,EAAE,GAAG,SAAS,EAAE;AAAA,EAClF;AAEA,QAAM,WAAW,aAAa,MAAM,MAAM,KAAK;AAC/C,SAAO,EAAE,OAAO,WAAW,QAAQ,GAAG,SAAS,EAAE;AACnD;AAEA,SAAS,eAAe,SAAsB,UAAkB,YAAoB;AAClF,QAAM,MAAM,QAAQ,QAAQ;AAC5B,QAAM,YAAY,IAAI;AACtB,QAAM,QAAkB,CAAC;AAMzB,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI;AACJ,MAAI;AACJ,MAAI,IAAI,KAAK,UAAU,IAAI;AACzB,cAAU,IAAI,KAAK,aAAa,CAAC;AACjC,cAAU,IAAI,KAAK,aAAa,EAAE;AAClC,UAAM,KAAK,IAAI,KAAK,aAAa,EAAE;AACnC,UAAM,KAAK,IAAI,KAAK,aAAa,EAAE;AACnC,QAAI,KAAK,EAAG,WAAU,KAAK,IAAI,IAAI,QAAQ;AAC3C,QAAI,KAAK,EAAG,WAAU,KAAK,IAAI,IAAI,QAAQ;AAAA,EAC7C;AAEA,MAAI,IAAI,WAAW;AAEnB,SAAO,IAAI,QAAQ,QAAQ;AACzB,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,EAAE,UAAU,mBAAmB,EAAE,SAAS,UAAW;AACzD,QAAI,EAAE,SAAS,eAAe,EAAE,UAAU,mBAAmB,EAAE,UAAU,iBAAkB;AAE3F,QAAI,EAAE,UAAU,eAAe;AAC7B,YAAM,IAAI,YAAY,EAAE,IAAI,EAAE,KAAK;AACnC,UAAI,EAAG,OAAM,KAAK,CAAC;AAAA,IACrB;AACA;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,EAAE,MAAM,MAAM,KAAK,IAAI,GAAG,SAAS,SAAS,SAAS,QAAQ,GAAkB,SAAS,EAAE;AAC3G;AAEA,SAAS,aAAa,MAAc,MAAc,OAAuC;AACvF,QAAM,OAAiC,MAAM,KAAK,EAAE,QAAQ,KAAK,GAAG,MAAM,MAAM,IAAI,EAAE,KAAK,IAAI,CAAC;AAGhG,QAAM,UAAU,MAAM,KAAK,OAAK,EAAE,YAAY,UAAa,EAAE,YAAY,MAAS;AAElF,MAAI,SAAS;AACX,eAAW,QAAQ,OAAO;AACxB,YAAM,IAAI,KAAK,WAAW;AAC1B,YAAM,IAAI,KAAK,WAAW;AAC1B,UAAI,KAAK,QAAQ,KAAK,KAAM;AAC5B,WAAK,CAAC,EAAE,CAAC,IAAI;AAEb,eAAS,KAAK,GAAG,KAAK,KAAK,SAAS,MAAM;AACxC,iBAAS,KAAK,GAAG,KAAK,KAAK,SAAS,MAAM;AACxC,cAAI,OAAO,KAAK,OAAO,EAAG;AAC1B,cAAI,IAAI,KAAK,QAAQ,IAAI,KAAK;AAC5B,iBAAK,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,SAAS,GAAG,SAAS,EAAE;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AAEL,QAAI,UAAU;AACd,aAAS,IAAI,GAAG,IAAI,QAAQ,UAAU,MAAM,QAAQ,KAAK;AACvD,eAAS,IAAI,GAAG,IAAI,QAAQ,UAAU,MAAM,QAAQ,KAAK;AACvD,YAAI,KAAK,CAAC,EAAE,CAAC,MAAM,KAAM;AACzB,cAAM,OAAO,MAAM,SAAS;AAC5B,aAAK,CAAC,EAAE,CAAC,IAAI;AAEb,iBAAS,KAAK,GAAG,KAAK,KAAK,SAAS,MAAM;AACxC,mBAAS,KAAK,GAAG,KAAK,KAAK,SAAS,MAAM;AACxC,gBAAI,OAAO,KAAK,OAAO,EAAG;AAC1B,gBAAI,IAAI,KAAK,QAAQ,IAAI,KAAK;AAC5B,mBAAK,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,SAAS,GAAG,SAAS,EAAE;AAAA,UAC9D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,KAAK,IAAI,SAAO,IAAI,IAAI,OAAK,KAAK,EAAE,MAAM,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC,CAAC;AAChF;;;AC/1BA,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,CAACC,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;;;AC/kDA,SAAS,gBAAgB;;;ACCzB,OAAOE,YAAW;AAClB,SAAS,aAAAC,kBAAiB;AAU1B,IAAM,aAAa;AAEnB,IAAMC,uBAAsB,MAAM,OAAO;AACzC,IAAMC,YAAW;AACjB,IAAMC,YAAW;AAKjB,SAAS,kBAAkB,KAAqB;AAC9C,MAAI,CAAC,eAAe,KAAK,GAAG,EAAG,QAAO;AACtC,QAAM,MAAM,WAAW,GAAG;AAC1B,MAAI,CAAC,SAAS,GAAG,EAAG,QAAO;AAE3B,QAAM,UAAU,WAAW,IAAI,YAAY,EAAE,CAAC,EAAE,SAAS;AACzD,SAAO;AACT;AAKA,SAAS,aAAa,KAAkD;AACtE,QAAM,IAAI,IAAI,MAAM,iBAAiB;AACrC,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,MAAM;AACV,aAAW,MAAM,EAAE,CAAC,EAAG,OAAM,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI;AAC5D,SAAO,EAAE,KAAK,MAAM,GAAG,KAAK,SAAS,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE;AACrD;AAGA,SAAS,cAAc,KAA4F;AACjH,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,QAAQ,aAAa,MAAM,CAAC,CAAC;AACnC,QAAM,MAAM,aAAa,MAAM,CAAC,CAAC;AACjC,MAAI,CAAC,SAAS,CAAC,IAAK,QAAO;AAC3B,SAAO,EAAE,UAAU,MAAM,KAAK,UAAU,MAAM,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,IAAI;AACtF;AAIA,SAAS,YAAY,QAAiB,SAA4B;AAChE,QAAM,QAAQ,OAAO,qBAAqB,OAAO;AACjD,QAAM,SAAoB,CAAC;AAC3B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,QAAO,KAAK,MAAM,CAAC,CAAY;AACtE,SAAO;AACT;AAEA,SAAS,eAAe,IAAqB;AAC3C,SAAO,GAAG,aAAa,KAAK,KAAK;AACnC;AAEA,SAAS,SAAS,MAAwB;AACxC,SAAO,IAAIC,WAAU,EAAE,gBAAgB,SAAS,IAAI,GAAG,UAAU;AACnE;AAIA,SAAS,mBAAmB,KAAuB;AACjD,QAAM,MAAM,SAAS,GAAG;AACxB,QAAM,UAAoB,CAAC;AAC3B,QAAM,SAAS,YAAY,IAAI,iBAAiB,IAAI;AACpD,aAAW,MAAM,QAAQ;AAEvB,UAAM,YAAY,YAAY,IAAI,GAAG;AACrC,YAAQ,KAAK,UAAU,IAAI,OAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC;AAAA,EAC/D;AACA,SAAO;AACT;AAUA,SAAS,cAAc,KAA0B;AAC/C,QAAM,MAAM,SAAS,GAAG;AACxB,QAAM,SAAsB,CAAC;AAC7B,QAAM,gBAAgB,YAAY,IAAI,iBAAiB,OAAO;AAC9D,aAAW,MAAM,eAAe;AAC9B,WAAO,KAAK;AAAA,MACV,MAAM,GAAG,aAAa,MAAM,KAAK,QAAQ,OAAO,SAAS,CAAC;AAAA,MAC1D,SAAS,GAAG,aAAa,SAAS,KAAK;AAAA,MACvC,KAAK,GAAG,aAAa,MAAM,KAAK;AAAA,IAClC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,SAAS,UAAU,KAAkC;AACnD,QAAM,MAAM,SAAS,GAAG;AACxB,QAAM,MAAM,oBAAI,IAAoB;AACpC,QAAM,OAAO,YAAY,IAAI,iBAAiB,cAAc;AAC5D,aAAW,OAAO,MAAM;AACtB,UAAM,KAAK,IAAI,aAAa,IAAI;AAChC,UAAM,SAAS,IAAI,aAAa,QAAQ;AACxC,QAAI,MAAM,OAAQ,KAAI,IAAI,IAAI,MAAM;AAAA,EACtC;AACA,SAAO;AACT;AAWA,SAAS,eACP,KACA,eAC2E;AAC3E,QAAM,MAAM,SAAS,GAAG;AACxB,QAAM,OAAmB,CAAC;AAC1B,MAAI,SAAS;AACb,MAAI,SAAS;AAGb,QAAM,OAAO,YAAY,IAAI,iBAAiB,KAAK;AACnD,aAAW,SAAS,MAAM;AACxB,UAAM,SAAS,SAAS,MAAM,aAAa,GAAG,KAAK,KAAK,EAAE,IAAI;AAC9D,QAAI,SAAS,KAAK,UAAUF,UAAU;AAEtC,UAAM,QAAQ,YAAY,OAAO,GAAG;AACpC,eAAW,UAAU,OAAO;AAC1B,YAAM,MAAM,OAAO,aAAa,GAAG;AACnC,UAAI,CAAC,IAAK;AACV,YAAM,MAAM,aAAa,GAAG;AAC5B,UAAI,CAAC,OAAO,IAAI,OAAOC,UAAU;AAGjC,YAAM,OAAO,OAAO,aAAa,GAAG;AACpC,YAAM,YAAY,YAAY,QAAQ,GAAG;AACzC,YAAM,YAAY,YAAY,QAAQ,GAAG;AACzC,UAAI,QAAQ;AAEZ,UAAI,UAAU,SAAS,GAAG;AACxB,cAAM,MAAM,eAAe,UAAU,CAAC,CAAC;AACvC,YAAI,SAAS,KAAK;AAEhB,gBAAM,MAAM,SAAS,KAAK,EAAE;AAC5B,kBAAQ,cAAc,GAAG,KAAK;AAAA,QAChC,WAAW,SAAS,KAAK;AACvB,kBAAQ,QAAQ,MAAM,SAAS;AAAA,QACjC,OAAO;AAEL,kBAAQ,kBAAkB,GAAG;AAAA,QAC/B;AAAA,MACF,WAAW,SAAS,aAAa;AAE/B,cAAM,OAAO,YAAY,QAAQ,IAAI;AACrC,YAAI,KAAK,SAAS,GAAG;AACnB,gBAAM,YAAY,YAAY,KAAK,CAAC,GAAG,GAAG;AAC1C,kBAAQ,UAAU,IAAI,OAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE;AAAA,QACzD;AAAA,MACF;AAGA,UAAI,CAAC,SAAS,UAAU,SAAS,GAAG;AAClC,gBAAQ,IAAI,eAAe,UAAU,CAAC,CAAC,CAAC;AAAA,MAC1C;AAGA,aAAO,KAAK,UAAU,IAAI,IAAK,MAAK,KAAK,CAAC,CAAC;AAC3C,aAAO,KAAK,IAAI,GAAG,EAAE,UAAU,IAAI,IAAK,MAAK,IAAI,GAAG,EAAE,KAAK,EAAE;AAC7D,WAAK,IAAI,GAAG,EAAE,IAAI,GAAG,IAAI;AAEzB,UAAI,IAAI,MAAM,OAAQ,UAAS,IAAI;AACnC,UAAI,IAAI,MAAM,OAAQ,UAAS,IAAI;AAAA,IACrC;AAAA,EACF;AAGA,QAAM,SAAsB,CAAC;AAC7B,QAAM,oBAAoB,YAAY,IAAI,iBAAiB,WAAW;AACtE,aAAW,MAAM,mBAAmB;AAClC,UAAM,MAAM,GAAG,aAAa,KAAK;AACjC,QAAI,CAAC,IAAK;AACV,UAAM,IAAI,cAAc,GAAG;AAC3B,QAAI,EAAG,QAAO,KAAK,CAAC;AAAA,EACtB;AAEA,SAAO,EAAE,MAAM,QAAQ,QAAQ,OAAO;AACxC;AAIA,SAAS,cACP,WACA,MACA,QACA,QACA,QACA,YACW;AACX,QAAM,SAAoB,CAAC;AAG3B,MAAI,WAAW;AACb,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,YAAY,aAAa;AAAA,IAC3B,CAAC;AAAA,EACH;AAGA,MAAI,SAAS,KAAK,SAAS,KAAK,KAAK,WAAW,EAAG,QAAO;AAG1D,QAAM,WAAW,oBAAI,IAAkD;AACvE,QAAM,YAAY,oBAAI,IAAY;AAClC,aAAW,KAAK,QAAQ;AACtB,UAAM,UAAU,EAAE,SAAS,EAAE,WAAW;AACxC,UAAM,UAAU,EAAE,SAAS,EAAE,WAAW;AACxC,aAAS,IAAI,GAAG,EAAE,QAAQ,IAAI,EAAE,QAAQ,IAAI,EAAE,SAAS,QAAQ,CAAC;AAChE,aAAS,IAAI,EAAE,UAAU,KAAK,EAAE,QAAQ,KAAK;AAC3C,eAAS,IAAI,EAAE,UAAU,KAAK,EAAE,QAAQ,KAAK;AAC3C,YAAI,MAAM,EAAE,YAAY,MAAM,EAAE,UAAU;AACxC,oBAAU,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,WAAW;AACf,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,KAAK,QAAQ,KAAK;AAChC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,OAAO,IAAI,KAAK,UAAQ,SAAS,EAAE,GAAG;AACxC,UAAI,aAAa,GAAI,YAAW;AAChC,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,aAAa,GAAI,QAAO;AAG5B,QAAM,WAA4B,CAAC;AAEnC,WAAS,IAAI,UAAU,KAAK,SAAS,KAAK;AACxC,UAAM,MAAqB,CAAC;AAC5B,aAAS,IAAI,GAAG,KAAK,QAAQ,KAAK;AAChC,YAAM,MAAM,GAAG,CAAC,IAAI,CAAC;AACrB,UAAI,UAAU,IAAI,GAAG,EAAG;AAExB,YAAM,QAAQ,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC,MAAM;AACxC,YAAM,QAAQ,SAAS,IAAI,GAAG;AAC9B,UAAI,KAAK;AAAA,QACP;AAAA,QACA,SAAS,OAAO,WAAW;AAAA,QAC3B,SAAS,OAAO,WAAW;AAAA,MAC7B,CAAC;AAAA,IACH;AACA,aAAS,KAAK,GAAG;AAAA,EACnB;AAEA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,QAAQ,WAAW,QAAQ;AACjC,QAAI,MAAM,OAAO,GAAG;AAClB,aAAO,KAAK,EAAE,MAAM,SAAS,OAAO,YAAY,aAAa,EAAE,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,SAAO;AACT;AAIA,eAAsB,kBACpB,QACA,SAC8B;AAE9B,kBAAgB,QAAQF,oBAAmB;AAE3C,QAAM,MAAM,MAAMI,OAAM,UAAU,MAAM;AACxC,QAAM,WAA2B,CAAC;AAGlC,QAAM,eAAe,IAAI,KAAK,iBAAiB;AAC/C,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI,YAAY,yGAAwC;AAAA,EAChE;AAGA,MAAI,gBAA0B,CAAC;AAC/B,QAAM,SAAS,IAAI,KAAK,sBAAsB;AAC9C,MAAI,QAAQ;AACV,oBAAgB,mBAAmB,MAAM,OAAO,MAAM,MAAM,CAAC;AAAA,EAC/D;AAGA,QAAM,SAAS,cAAc,MAAM,aAAa,MAAM,MAAM,CAAC;AAC7D,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,YAAY,qEAAmB;AAAA,EAC3C;AAGA,MAAI,UAAU,oBAAI,IAAoB;AACtC,QAAM,WAAW,IAAI,KAAK,4BAA4B;AACtD,MAAI,UAAU;AACZ,cAAU,UAAU,MAAM,SAAS,MAAM,MAAM,CAAC;AAAA,EAClD;AAGA,MAAI,aAAiC;AACrC,MAAI,SAAS,OAAO;AAClB,UAAM,EAAE,gBAAAC,gBAAe,IAAI,MAAM,OAAO,0BAAkB;AAC1D,iBAAaA,gBAAe,QAAQ,OAAO,OAAO,MAAM;AAAA,EAC1D;AAGA,QAAM,SAAoB,CAAC;AAC3B,QAAM,kBAAkB,KAAK,IAAI,OAAO,QAAQ,UAAU;AAE1D,WAAS,IAAI,GAAG,IAAI,iBAAiB,KAAK;AACxC,QAAI,cAAc,CAAC,WAAW,IAAI,IAAI,CAAC,EAAG;AAE1C,UAAM,QAAQ,OAAO,CAAC;AACtB,aAAS,aAAa,IAAI,GAAG,eAAe;AAG5C,QAAI,YAAY,QAAQ,IAAI,MAAM,GAAG;AACrC,QAAI,WAAW;AAEb,UAAI,CAAC,UAAU,WAAW,KAAK,KAAK,CAAC,UAAU,WAAW,GAAG,GAAG;AAC9D,oBAAY,MAAM,SAAS;AAAA,MAC7B,WAAW,UAAU,WAAW,GAAG,GAAG;AACpC,oBAAY,UAAU,MAAM,CAAC;AAAA,MAC/B;AAAA,IACF,OAAO;AACL,kBAAY,sBAAsB,IAAI,CAAC;AAAA,IACzC;AAEA,UAAM,YAAY,IAAI,KAAK,SAAS;AACpC,QAAI,CAAC,WAAW;AACd,eAAS,KAAK;AAAA,QACZ,MAAM,IAAI;AAAA,QACV,SAAS,iBAAO,MAAM,IAAI,sEAAoB,SAAS;AAAA,QACvD,MAAM;AAAA,MACR,CAAC;AACD;AAAA,IACF;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,UAAU,MAAM,MAAM;AAC7C,YAAM,EAAE,MAAM,QAAQ,QAAQ,OAAO,IAAI,eAAe,UAAU,aAAa;AAC/E,YAAM,cAAc,cAAc,MAAM,MAAM,MAAM,QAAQ,QAAQ,QAAQ,CAAC;AAC7E,aAAO,KAAK,GAAG,WAAW;AAAA,IAC5B,SAAS,KAAK;AACZ,eAAS,KAAK;AAAA,QACZ,MAAM,IAAI;AAAA,QACV,SAAS,iBAAO,MAAM,IAAI,gCAAY,eAAe,QAAQ,IAAI,UAAU,yCAAW;AAAA,QACtF,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,WAA6B;AAAA,IACjC,WAAW;AAAA,EACb;AACA,QAAM,WAAW,IAAI,KAAK,mBAAmB;AAC7C,MAAI,UAAU;AACZ,QAAI;AACF,YAAM,UAAU,MAAM,SAAS,MAAM,MAAM;AAC3C,YAAM,MAAM,SAAS,OAAO;AAC5B,YAAM,WAAW,CAAC,QAAgB;AAChC,cAAM,MAAM,IAAI,qBAAqB,GAAG;AACxC,eAAO,IAAI,SAAS,KAAK,IAAI,CAAC,EAAE,eAAe,IAAI,KAAK,IAAI;AAAA,MAC9D;AACA,eAAS,QAAQ,SAAS,UAAU,KAAK,SAAS,eAAe;AACjE,eAAS,SAAS,SAAS,YAAY;AACvC,eAAS,cAAc,SAAS,gBAAgB;AAChD,YAAM,UAAU,SAAS,iBAAiB;AAC1C,UAAI,QAAS,UAAS,YAAY;AAClC,YAAM,WAAW,SAAS,kBAAkB;AAC5C,UAAI,SAAU,UAAS,aAAa;AAAA,IACtC,QAAQ;AAAA,IAAqB;AAAA,EAC/B;AAEA,QAAM,WAAW,iBAAiB,MAAM;AAExC,SAAO,EAAE,UAAU,QAAQ,UAAU,UAAU,SAAS,SAAS,IAAI,WAAW,OAAU;AAC5F;;;ACnZA,OAAOC,YAAW;AAClB,SAAS,aAAAC,kBAAiB;AAS1B,IAAMC,uBAAsB,MAAM,OAAO;AAKzC,SAAS,iBAAiB,QAA4B,WAA8B;AAClF,QAAM,SAAoB,CAAC;AAC3B,QAAM,WAAW,OAAO;AACxB,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,OAAO,SAAS,CAAC;AACvB,QAAI,KAAK,aAAa,GAAG;AACvB,YAAM,KAAK;AACX,UAAI,GAAG,cAAc,aAAa,GAAG,SAAS,SAAS,IAAI,SAAS,EAAE,GAAG;AACvE,eAAO,KAAK,EAAE;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,aAAa,QAA4B,WAA8B;AAC9E,QAAM,SAAoB,CAAC;AAC3B,QAAM,OAAO,CAAC,SAA6B;AACzC,UAAM,WAAW,KAAK;AACtB,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,QAAQ,SAAS,CAAC;AACxB,UAAI,MAAM,aAAa,GAAG;AACxB,cAAM,KAAK;AACX,YAAI,GAAG,cAAc,aAAa,GAAG,SAAS,SAAS,IAAI,SAAS,EAAE,GAAG;AACvE,iBAAO,KAAK,EAAE;AAAA,QAChB;AACA,aAAK,EAAE;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,OAAK,MAAM;AACX,SAAO;AACT;AAEA,SAAS,QAAQ,IAAa,WAAkC;AAE9D,QAAM,QAAQ,GAAG;AACjB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,KAAK,cAAc,aAAa,KAAK,SAAS,UAAW,QAAO,KAAK;AAAA,EAC3E;AACA,SAAO;AACT;AAEA,SAASC,UAAS,MAAwB;AACxC,SAAO,IAAIC,WAAU,EAAE,gBAAgB,SAAS,IAAI,GAAG,UAAU;AACnE;AAUA,SAAS,YAAY,KAAqC;AACxD,QAAM,MAAMD,UAAS,GAAG;AACxB,QAAM,SAAS,oBAAI,IAAuB;AAC1C,QAAM,gBAAgB,aAAa,KAAK,OAAO;AAE/C,aAAW,MAAM,eAAe;AAC9B,UAAM,UAAU,QAAQ,IAAI,SAAS;AACrC,QAAI,CAAC,QAAS;AAEd,UAAM,UAAU,iBAAiB,IAAI,MAAM;AAC3C,UAAM,OAAO,QAAQ,SAAS,IAAK,QAAQ,QAAQ,CAAC,GAAG,KAAK,KAAK,KAAM;AACvE,UAAM,aAAa,iBAAiB,IAAI,SAAS;AACjD,UAAM,UAAU,WAAW,SAAS,IAAK,QAAQ,WAAW,CAAC,GAAG,KAAK,KAAK,SAAa;AAGvF,UAAM,SAAS,iBAAiB,IAAI,KAAK;AACzC,QAAI;AACJ,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,aAAa,iBAAiB,OAAO,CAAC,GAAG,YAAY;AAC3D,UAAI,WAAW,SAAS,GAAG;AACzB,cAAM,MAAM,QAAQ,WAAW,CAAC,GAAG,KAAK;AACxC,YAAI,QAAQ,KAAM,gBAAe,SAAS,KAAK,EAAE;AAAA,MACnD;AAAA,IACF;AAGA,QAAI,iBAAiB,QAAW;AAC9B,YAAM,eAAe,KAAK,MAAM,gCAAgC;AAChE,UAAI,aAAc,gBAAe,SAAS,aAAa,CAAC,GAAG,EAAE,IAAI;AAAA,IACnE;AAEA,WAAO,IAAI,SAAS,EAAE,MAAM,SAAS,aAAa,CAAC;AAAA,EACrD;AACA,SAAO;AACT;AASA,SAAS,eAAe,KAAsD;AAC5E,QAAM,MAAMA,UAAS,GAAG;AACxB,QAAM,eAAe,oBAAI,IAAwC;AAGjE,QAAM,mBAAmB,aAAa,KAAK,aAAa;AACxD,aAAW,MAAM,kBAAkB;AACjC,UAAM,gBAAgB,QAAQ,IAAI,eAAe;AACjD,QAAI,CAAC,cAAe;AACpB,UAAM,SAAS,oBAAI,IAA2B;AAC9C,UAAM,cAAc,iBAAiB,IAAI,KAAK;AAC9C,eAAW,OAAO,aAAa;AAC7B,YAAM,OAAO,SAAS,QAAQ,KAAK,MAAM,KAAK,KAAK,EAAE;AACrD,YAAM,YAAY,iBAAiB,KAAK,QAAQ;AAChD,YAAM,SAAS,UAAU,SAAS,IAAK,QAAQ,UAAU,CAAC,GAAG,KAAK,KAAK,WAAY;AACnF,aAAO,IAAI,MAAM,EAAE,QAAQ,OAAO,KAAK,CAAC;AAAA,IAC1C;AACA,iBAAa,IAAI,eAAe,MAAM;AAAA,EACxC;AAGA,QAAM,OAAO,oBAAI,IAAwC;AACzD,QAAM,cAAc,aAAa,KAAK,KAAK;AAC3C,aAAW,MAAM,aAAa;AAC5B,UAAM,QAAQ,QAAQ,IAAI,OAAO;AACjC,QAAI,CAAC,MAAO;AACZ,UAAM,eAAe,iBAAiB,IAAI,eAAe;AACzD,QAAI,aAAa,SAAS,GAAG;AAC3B,YAAM,MAAM,QAAQ,aAAa,CAAC,GAAG,KAAK;AAC1C,UAAI,OAAO,aAAa,IAAI,GAAG,GAAG;AAChC,aAAK,IAAI,OAAO,aAAa,IAAI,GAAG,CAAE;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAIA,SAASE,WAAU,KAAkC;AACnD,QAAM,MAAMF,UAAS,GAAG;AACxB,QAAM,MAAM,oBAAI,IAAoB;AACpC,QAAM,OAAO,aAAa,KAAK,cAAc;AAC7C,aAAW,OAAO,MAAM;AACtB,UAAM,KAAK,QAAQ,KAAK,IAAI;AAC5B,UAAM,SAAS,QAAQ,KAAK,QAAQ;AACpC,QAAI,MAAM,OAAQ,KAAI,IAAI,IAAI,MAAM;AAAA,EACtC;AACA,SAAO;AACT;AAIA,SAAS,eAAe,KAAkC;AACxD,QAAM,MAAMA,UAAS,GAAG;AACxB,QAAM,QAAQ,oBAAI,IAAoB;AACtC,QAAM,aAAa,aAAa,KAAK,UAAU;AAC/C,aAAW,MAAM,YAAY;AAC3B,UAAM,KAAK,QAAQ,IAAI,IAAI;AAC3B,QAAI,CAAC,MAAM,OAAO,OAAO,OAAO,KAAM;AACtC,UAAM,QAAkB,CAAC;AACzB,UAAM,YAAY,aAAa,IAAI,GAAG;AACtC,eAAW,KAAK,WAAW;AACzB,YAAM,OAAO,aAAa,GAAG,GAAG;AAChC,iBAAW,KAAK,MAAM;AACpB,cAAM,YAAY,iBAAiB,GAAG,GAAG;AACzC,mBAAW,KAAK,UAAW,OAAM,KAAK,EAAE,eAAe,EAAE;AAAA,MAC3D;AAAA,IACF;AACA,UAAM,IAAI,IAAI,MAAM,KAAK,EAAE,EAAE,KAAK,CAAC;AAAA,EACrC;AACA,SAAO;AACT;AAUA,SAAS,WAAW,GAAuB;AACzC,QAAM,YAAY,iBAAiB,GAAG,GAAG;AACzC,QAAM,OAAO,UAAU,IAAI,OAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE;AAE5D,MAAI,OAAO;AACX,MAAI,SAAS;AACb,QAAM,SAAS,iBAAiB,GAAG,KAAK;AACxC,MAAI,OAAO,SAAS,GAAG;AACrB,WAAO,iBAAiB,OAAO,CAAC,GAAG,GAAG,EAAE,SAAS;AACjD,aAAS,iBAAiB,OAAO,CAAC,GAAG,GAAG,EAAE,SAAS;AAAA,EACrD;AAEA,SAAO,EAAE,MAAM,MAAM,OAAO;AAC9B;AAIA,SAAS,eACP,GACA,QACA,WACA,WACA,MACgB;AAEhB,QAAM,SAAS,iBAAiB,GAAG,KAAK;AACxC,MAAI,UAAU;AACd,MAAI,QAAQ;AACZ,MAAI,OAAO;AAEX,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,YAAY,iBAAiB,OAAO,CAAC,GAAG,QAAQ;AACtD,QAAI,UAAU,SAAS,EAAG,WAAU,QAAQ,UAAU,CAAC,GAAG,KAAK,KAAK;AAEpE,UAAM,WAAW,iBAAiB,OAAO,CAAC,GAAG,OAAO;AACpD,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,WAAW,iBAAiB,SAAS,CAAC,GAAG,OAAO;AACtD,YAAM,UAAU,iBAAiB,SAAS,CAAC,GAAG,MAAM;AACpD,cAAQ,SAAS,SAAS,IAAK,QAAQ,SAAS,CAAC,GAAG,KAAK,KAAK,KAAM;AACpE,aAAO,QAAQ,SAAS,IAAI,SAAS,QAAQ,QAAQ,CAAC,GAAG,KAAK,KAAK,KAAK,EAAE,IAAI;AAAA,IAChF;AAAA,EACF;AAGA,QAAM,QAAkB,CAAC;AACzB,MAAI,UAAU;AACd,MAAI,YAAY;AAChB,MAAI;AACJ,MAAI;AAGJ,QAAM,aAAa,iBAAiB,GAAG,WAAW;AAClD,QAAM,iBAAiB,oBAAI,IAAY;AAEvC,aAAW,MAAM,YAAY;AAC3B,UAAM,MAAM,QAAQ,IAAI,IAAI;AAC5B,UAAM,SAAmB,CAAC;AAC1B,UAAMG,QAAO,aAAa,IAAI,GAAG;AACjC,eAAW,KAAKA,OAAM;AACpB,YAAM,SAAS,WAAW,CAAC;AAC3B,aAAO,KAAK,OAAO,IAAI;AAAA,IACzB;AACA,UAAMC,QAAO,OAAO,KAAK,EAAE;AAC3B,QAAIA,OAAM;AACR,qBAAe,IAAIA,KAAI;AACvB,UAAI,OAAO,KAAK,IAAI,GAAG,GAAG;AACxB,eAAO,KAAK,IAAI,GAAG;AACnB,cAAM,KAAKA,KAAI;AAAA,MACjB,OAAO;AACL,cAAM,KAAKA,KAAI;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,OAAO,iBAAiB,GAAG,GAAG;AACpC,aAAW,KAAK,MAAM;AAEpB,QAAI,EAAE,cAAe,EAAE,WAAuB,cAAc,YAAa;AAEzE,UAAM,SAAS,WAAW,CAAC;AAC3B,QAAI,OAAO,KAAM,WAAU;AAC3B,QAAI,OAAO,OAAQ,aAAY;AAG/B,UAAM,WAAW,iBAAiB,GAAG,mBAAmB;AACxD,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,OAAO,QAAQ,SAAS,CAAC,GAAG,IAAI;AACtC,UAAI,QAAQ,UAAU,IAAI,IAAI,GAAG;AAC/B,uBAAe,UAAU,IAAI,IAAI;AAAA,MACnC;AAAA,IACF;AAEA,QAAI,OAAO,KAAM,OAAM,KAAK,OAAO,IAAI;AAAA,EACzC;AAEA,QAAM,OAAO,MAAM,KAAK,EAAE,EAAE,KAAK;AACjC,MAAI,CAAC,KAAM,QAAO;AAGlB,QAAM,QAAQ,OAAO,IAAI,OAAO;AAChC,MAAI,OAAO,iBAAiB,UAAa,MAAM,gBAAgB,KAAK,MAAM,gBAAgB,GAAG;AAC3F,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,OAAO,MAAM,eAAe;AAAA,IAC9B;AAAA,EACF;AAGA,MAAI,SAAS,UAAU,KAAK;AAC1B,UAAM,SAAS,UAAU,IAAI,KAAK;AAClC,UAAM,YAAY,QAAQ,IAAI,IAAI;AAClC,UAAM,WAAW,WAAW,WAAW,WAAW,cAAc;AAChE,WAAO,EAAE,MAAM,QAAQ,MAAM,SAAS;AAAA,EACxC;AAGA,QAAM,QAAiB,EAAE,MAAM,aAAa,KAAK;AACjD,MAAI,WAAW,WAAW;AACxB,UAAM,QAAQ,EAAE,MAAM,WAAW,QAAW,QAAQ,aAAa,OAAU;AAAA,EAC7E;AACA,MAAI,KAAM,OAAM,OAAO;AACvB,MAAI,aAAc,OAAM,eAAe;AACvC,SAAO;AACT;AAIA,SAAS,WACP,KACA,QACA,WACA,WACA,MACgB;AAChB,QAAM,aAAa,iBAAiB,KAAK,IAAI;AAC7C,MAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,QAAM,OAAmB,CAAC;AAC1B,MAAI,UAAU;AAEd,aAAW,MAAM,YAAY;AAC3B,UAAM,aAAa,iBAAiB,IAAI,IAAI;AAC5C,UAAM,MAAgB,CAAC;AAEvB,eAAW,MAAM,YAAY;AAE3B,UAAI,UAAU;AACd,UAAI,UAAU;AACd,YAAM,UAAU,iBAAiB,IAAI,MAAM;AAC3C,UAAI,QAAQ,SAAS,GAAG;AACtB,cAAM,cAAc,iBAAiB,QAAQ,CAAC,GAAG,UAAU;AAC3D,YAAI,YAAY,SAAS,GAAG;AAC1B,oBAAU,SAAS,QAAQ,YAAY,CAAC,GAAG,KAAK,KAAK,KAAK,EAAE;AAAA,QAC9D;AACA,cAAM,YAAY,iBAAiB,QAAQ,CAAC,GAAG,QAAQ;AACvD,YAAI,UAAU,SAAS,GAAG;AACxB,gBAAM,MAAM,QAAQ,UAAU,CAAC,GAAG,KAAK;AACvC,cAAI,QAAQ,aAAa,QAAQ,MAAM;AAErC,gBAAI,KAAK,EAAE,MAAM,IAAI,SAAS,SAAS,EAAE,CAAC;AAC1C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,YAAM,YAAsB,CAAC;AAC7B,YAAM,YAAY,iBAAiB,IAAI,GAAG;AAC1C,iBAAW,KAAK,WAAW;AACzB,cAAM,QAAQ,eAAe,GAAG,QAAQ,WAAW,WAAW,IAAI;AAClE,YAAI,OAAO,KAAM,WAAU,KAAK,MAAM,IAAI;AAAA,MAC5C;AAEA,UAAI,KAAK,EAAE,MAAM,UAAU,KAAK,IAAI,GAAG,SAAS,QAAQ,CAAC;AAAA,IAC3D;AACA,SAAK,KAAK,GAAG;AACb,QAAI,IAAI,SAAS,QAAS,WAAU,IAAI;AAAA,EAC1C;AAGA,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,OAAO,KAAK,CAAC,EAAE,CAAC;AACtB,UAAI,CAAC,QAAQ,KAAK,YAAY,EAAG;AACjC,UAAI,OAAO;AACX,eAAS,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,MAAM;AAC3C,YAAI,KAAK,EAAE,EAAE,CAAC,GAAG,YAAY,EAAG;AAAA,YAC3B;AAAA,MACP;AACA,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,YAAwB,CAAC;AAC/B,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,IAAI,OAAO,UAAQ,KAAK,YAAY,CAAC;AACnD,cAAU,KAAK,KAAK;AAAA,EACtB;AAGA,MAAI,UAAU,WAAW,EAAG,QAAO;AAGnC,MAAI,OAAO;AACX,aAAW,OAAO,WAAW;AAC3B,QAAI,IAAI;AACR,eAAW,QAAQ,IAAK,MAAK,KAAK;AAClC,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AAEA,QAAM,QAAiB;AAAA,IACrB,MAAM,UAAU;AAAA,IAChB;AAAA,IACA,OAAO;AAAA,IACP,WAAW,UAAU,SAAS;AAAA,EAChC;AACA,SAAO,EAAE,MAAM,SAAS,MAAM;AAChC;AAIA,eAAe,cACb,KACA,MACA,KAC0D;AAC1D,QAAM,SAAoB,CAAC;AAC3B,QAAM,SAA2B,CAAC;AAElC,QAAM,kBAAkB,aAAa,IAAI,iBAAiB,SAAS;AACnE,MAAI,SAAS;AAEb,aAAW,WAAW,iBAAiB;AAErC,UAAM,QAAQ,aAAa,SAAS,MAAM;AAC1C,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,QAAQ,MAAM,OAAO;AACrC,UAAI,CAAC,QAAS;AACd,YAAM,SAAS,KAAK,IAAI,OAAO;AAC/B,UAAI,CAAC,OAAQ;AAEb,YAAM,UAAU,OAAO,WAAW,GAAG,IAAI,OAAO,MAAM,CAAC,IACnD,OAAO,WAAW,OAAO,IAAI,SAC7B,QAAQ,MAAM;AAElB,YAAM,UAAU,IAAI,KAAK,OAAO;AAChC,UAAI,CAAC,QAAS;AAEd,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,MAAM,YAAY;AAC7C;AACA,cAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY,KAAK;AACvD,cAAM,UAAkC;AAAA,UACtC,KAAK;AAAA,UAAa,KAAK;AAAA,UAAc,MAAM;AAAA,UAC3C,KAAK;AAAA,UAAa,KAAK;AAAA,UAAa,KAAK;AAAA,UAAa,KAAK;AAAA,QAC7D;AACA,cAAM,WAAW,SAAS,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,GAAG;AAChE,eAAO,KAAK,EAAE,UAAU,MAAM,UAAU,QAAQ,GAAG,KAAK,YAAY,CAAC;AACrE,eAAO,KAAK,EAAE,MAAM,SAAS,MAAM,SAAS,CAAC;AAAA,MAC/C,QAAQ;AAAA,MAAkB;AAAA,IAC5B;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAIA,eAAsB,kBACpB,QACA,SAC8B;AAE9B,kBAAgB,QAAQL,oBAAmB;AAE3C,QAAM,MAAM,MAAMM,OAAM,UAAU,MAAM;AACxC,QAAM,WAA2B,CAAC;AAGlC,QAAM,UAAU,IAAI,KAAK,mBAAmB;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,YAAY,2GAA0C;AAAA,EAClE;AAGA,MAAI,OAAO,oBAAI,IAAoB;AACnC,QAAM,WAAW,IAAI,KAAK,8BAA8B;AACxD,MAAI,UAAU;AACZ,WAAOH,WAAU,MAAM,SAAS,MAAM,MAAM,CAAC;AAAA,EAC/C;AAGA,MAAI,SAAS,oBAAI,IAAuB;AACxC,QAAM,aAAa,IAAI,KAAK,iBAAiB;AAC7C,MAAI,YAAY;AACd,QAAI;AACF,eAAS,YAAY,MAAM,WAAW,MAAM,MAAM,CAAC;AAAA,IACrD,QAAQ;AAAA,IAAkB;AAAA,EAC5B;AAGA,MAAI,YAAY,oBAAI,IAAwC;AAC5D,QAAM,UAAU,IAAI,KAAK,oBAAoB;AAC7C,MAAI,SAAS;AACX,QAAI;AACF,kBAAY,eAAe,MAAM,QAAQ,MAAM,MAAM,CAAC;AAAA,IACxD,QAAQ;AAAA,IAAqB;AAAA,EAC/B;AAGA,MAAI,YAAY,oBAAI,IAAoB;AACxC,QAAM,SAAS,IAAI,KAAK,oBAAoB;AAC5C,MAAI,QAAQ;AACV,QAAI;AACF,kBAAY,eAAe,MAAM,OAAO,MAAM,MAAM,CAAC;AAAA,IACvD,QAAQ;AAAA,IAAiB;AAAA,EAC3B;AAGA,QAAM,SAAS,MAAM,QAAQ,MAAM,MAAM;AACzC,QAAM,MAAMF,UAAS,MAAM;AAC3B,QAAM,OAAO,aAAa,KAAK,MAAM;AACrC,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,IAAI,YAAY,8EAA4B;AAAA,EACpD;AAEA,QAAM,SAAoB,CAAC;AAC3B,QAAM,SAAS,KAAK,CAAC;AACrB,QAAM,WAAW,OAAO;AAExB,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,OAAO,SAAS,CAAC;AACvB,QAAI,KAAK,aAAa,EAAG;AACzB,UAAM,KAAK;AACX,UAAM,YAAY,GAAG,aAAa,GAAG,SAAS,MAAM,GAAG,EAAE,IAAI;AAE7D,QAAI,cAAc,KAAK;AACrB,YAAM,QAAQ,eAAe,IAAI,QAAQ,WAAW,WAAW,IAAI;AACnE,UAAI,MAAO,QAAO,KAAK,KAAK;AAAA,IAC9B,WAAW,cAAc,OAAO;AAC9B,YAAM,QAAQ,WAAW,IAAI,QAAQ,WAAW,WAAW,IAAI;AAC/D,UAAI,MAAO,QAAO,KAAK,KAAK;AAAA,IAC9B;AAAA,EACF;AAGA,QAAM,EAAE,QAAQ,WAAW,OAAO,IAAI,MAAM,cAAc,KAAK,MAAM,GAAG;AAKxE,QAAM,WAA6B,CAAC;AACpC,QAAM,WAAW,IAAI,KAAK,mBAAmB;AAC7C,MAAI,UAAU;AACZ,QAAI;AACF,YAAM,UAAU,MAAM,SAAS,MAAM,MAAM;AAC3C,YAAM,UAAUA,UAAS,OAAO;AAChC,YAAM,WAAW,CAAC,QAAgB;AAChC,cAAM,MAAM,QAAQ,qBAAqB,GAAG;AAC5C,eAAO,IAAI,SAAS,KAAK,IAAI,CAAC,EAAE,eAAe,IAAI,KAAK,IAAI;AAAA,MAC9D;AACA,eAAS,QAAQ,SAAS,UAAU,KAAK,SAAS,eAAe;AACjE,eAAS,SAAS,SAAS,YAAY;AACvC,eAAS,cAAc,SAAS,gBAAgB;AAChD,YAAM,UAAU,SAAS,iBAAiB;AAC1C,UAAI,QAAS,UAAS,YAAY;AAClC,YAAM,WAAW,SAAS,kBAAkB;AAC5C,UAAI,SAAU,UAAS,aAAa;AAAA,IACtC,QAAQ;AAAA,IAAoB;AAAA,EAC9B;AAGA,QAAM,UAAU,OACb,OAAO,OAAK,EAAE,SAAS,SAAS,EAChC,IAAI,QAAM,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,EAAE,QAAQ,GAAG,EAAE;AAEzD,QAAM,WAAW,iBAAiB,MAAM;AAExC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,QAAQ,SAAS,IAAI,UAAU;AAAA,IACxC,UAAU,SAAS,SAAS,IAAI,WAAW;AAAA,IAC3C,QAAQ,OAAO,SAAS,IAAI,SAAS;AAAA,EACvC;AACF;;;AC3kBA,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAO;AAAA,EAC9C;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EACpC;AAAA,EAAO;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAK;AAAA,EAC9B;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACnC;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EACpC;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAK;AAC/B,CAAC;AAGD,SAAS,YAAY,MAAuB;AAC1C,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,WAAW,QAAQ,SAAS,GAAI,QAAO;AAE5C,aAAW,MAAM,gBAAgB;AAC/B,QAAI,QAAQ,SAAS,EAAE,EAAG,QAAO;AAAA,EACnC;AAEA,MAAI,qBAAqB,KAAK,OAAO,KAAK,CAAC,KAAK,KAAK,OAAO,EAAG,QAAO;AAEtE,MAAI,uBAAuB,KAAK,OAAO,EAAG,QAAO;AACjD,SAAO;AACT;AAMO,SAAS,kBAAkB,QAA+B;AAC/D,QAAM,SAAsB,CAAC;AAC7B,MAAI,cAAc;AAClB,MAAI,aAAa;AAEjB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,WAAW,CAAC,MAAM,MAAO;AAC5C;AAEA,UAAM,cAAc,iBAAiB,MAAM,KAAK;AAChD,QAAI,YAAY,SAAS,GAAG;AAC1B;AACA,aAAO,KAAK,GAAG,WAAW;AAAA,IAC5B;AAAA,EACF;AAGA,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,eAAe,MAAM,MAAM;AAC5C,YAAM,eAAe,oBAAoB,MAAM,IAAI;AACnD,aAAO,KAAK,GAAG,YAAY;AAAA,IAC7B;AAAA,EACF;AAEA,QAAM,aAAa,cAAc,IAAI,aAAa,cAAe,OAAO,SAAS,IAAI,MAAM;AAC3F,SAAO,EAAE,QAAQ,YAAY,KAAK,IAAI,YAAY,CAAC,EAAE;AACvD;AAEA,SAAS,iBAAiB,OAA6B;AACrD,QAAM,SAAsB,CAAC;AAG7B,MAAI,MAAM,QAAQ,GAAG;AACnB,aAAS,IAAI,GAAG,IAAI,MAAM,MAAM,KAAK;AACnC,eAAS,IAAI,GAAG,IAAI,MAAM,OAAO,GAAG,KAAK;AACvC,cAAM,YAAY,MAAM,MAAM,CAAC,EAAE,CAAC;AAClC,cAAM,YAAY,MAAM,MAAM,CAAC,EAAE,IAAI,CAAC;AACtC,YAAI,YAAY,UAAU,IAAI,KAAK,UAAU,KAAK,KAAK,GAAG;AACxD,iBAAO,KAAK;AAAA,YACV,OAAO,UAAU,KAAK,KAAK,EAAE,QAAQ,YAAY,EAAE;AAAA,YACnD,OAAO,UAAU,KAAK,KAAK;AAAA,YAC3B,KAAK;AAAA,YACL,KAAK;AAAA,UACP,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,WAAW,KAAK,MAAM,QAAQ,KAAK,MAAM,QAAQ,GAAG;AAC7D,UAAM,YAAY,MAAM,MAAM,CAAC;AAC/B,UAAM,YAAY,UAAU,MAAM,UAAQ;AACxC,YAAM,IAAI,KAAK,KAAK,KAAK;AACzB,aAAO,EAAE,SAAS,KAAK,EAAE,UAAU;AAAA,IACrC,CAAC;AACD,QAAI,WAAW;AACb,eAAS,IAAI,GAAG,IAAI,MAAM,MAAM,KAAK;AACnC,iBAAS,IAAI,GAAG,IAAI,MAAM,MAAM,KAAK;AACnC,gBAAM,QAAQ,UAAU,CAAC,EAAE,KAAK,KAAK;AACrC,gBAAM,QAAQ,MAAM,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,KAAK;AAC1C,cAAI,SAAS,OAAO;AAClB,mBAAO,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,KAAK,EAAE,CAAC;AAAA,UAC9C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAA2B;AACtD,QAAM,SAAsB,CAAC;AAE7B,QAAM,UAAU;AAChB,MAAI;AACJ,UAAQ,QAAQ,QAAQ,KAAK,IAAI,OAAO,MAAM;AAC5C,UAAM,QAAQ,MAAM,CAAC,EAAE,KAAK;AAC5B,UAAM,QAAQ,MAAM,CAAC,EAAE,KAAK;AAC5B,QAAI,OAAO;AACT,aAAO,KAAK,EAAE,OAAO,OAAO,KAAK,IAAI,KAAK,GAAG,CAAC;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;;;AC9GA,OAAOM,YAAW;;;AJsBlB,eAAsB,MAAM,OAAsC,SAA8C;AAC9G,MAAI;AACJ,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,eAAS,cAAc,GAAG;AAAA,IAC5B,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,WACzF,oEAAkB,KAAK,KACvB,2CAAa,KAAK;AACtB,aAAO,EAAE,SAAS,OAAO,UAAU,WAAW,OAAO,KAAK,MAAM,cAAc;AAAA,IAChF;AAAA,EACF,WAAW,OAAO,SAAS,KAAK,GAAG;AACjC,aAAS,cAAc,KAAK;AAAA,EAC9B,OAAO;AACL,aAAS;AAAA,EACX;AAEA,MAAI,CAAC,UAAU,OAAO,eAAe,GAAG;AACtC,WAAO,EAAE,SAAS,OAAO,UAAU,WAAW,OAAO,+GAA0B,MAAM,cAAc;AAAA,EACrG;AACA,QAAM,SAAS,aAAa,MAAM;AAElC,UAAQ,QAAQ;AAAA,IACd,KAAK,QAAQ;AAEX,YAAM,YAAY,MAAM,gBAAgB,MAAM;AAC9C,UAAI,cAAc,OAAQ,QAAO,UAAU,QAAQ,OAAO;AAC1D,UAAI,cAAc,OAAQ,QAAO,UAAU,QAAQ,OAAO;AAC1D,aAAO,UAAU,QAAQ,OAAO;AAAA,IAClC;AAAA,IACA,KAAK;AACH,aAAO,SAAS,QAAQ,OAAO;AAAA,IACjC,KAAK;AACH,aAAO,SAAS,QAAQ,OAAO;AAAA,IACjC;AACE,aAAO,EAAE,SAAS,OAAO,UAAU,WAAW,OAAO,sFAAqB,MAAM,qBAAqB;AAAA,EACzG;AACF;AAKA,eAAsB,UAAU,QAAqB,SAA8C;AACjG,MAAI;AACF,UAAM,EAAE,UAAU,QAAQ,UAAU,SAAS,UAAU,OAAO,IAAI,MAAM,kBAAkB,QAAQ,OAAO;AACzG,WAAO,EAAE,SAAS,MAAM,UAAU,QAAQ,UAAU,QAAQ,UAAU,SAAS,UAAU,QAAQ,QAAQ,SAAS,SAAS,OAAU;AAAA,EACvI,SAAS,KAAK;AACZ,WAAO,EAAE,SAAS,OAAO,UAAU,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,kCAAc,MAAM,cAAc,GAAG,EAAE;AAAA,EAChI;AACF;AAGA,eAAsB,SAAS,QAAqB,SAA8C;AAChG,MAAI;AACF,UAAM,EAAE,UAAU,QAAQ,UAAU,SAAS,UAAU,OAAO,IAAI,kBAAkB,OAAO,KAAK,MAAM,GAAG,OAAO;AAChH,WAAO,EAAE,SAAS,MAAM,UAAU,OAAO,UAAU,QAAQ,UAAU,SAAS,UAAU,QAAQ,QAAQ,SAAS,SAAS,OAAU;AAAA,EACtI,SAAS,KAAK;AACZ,WAAO,EAAE,SAAS,OAAO,UAAU,OAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,iCAAa,MAAM,cAAc,GAAG,EAAE;AAAA,EAC9H;AACF;AAGA,eAAsB,SAAS,QAAqB,SAA8C;AAChG,MAAI;AACF,UAAM,EAAE,UAAU,QAAQ,UAAU,SAAS,UAAU,aAAa,IAAI,MAAM,iBAAiB,QAAQ,OAAO;AAC9G,WAAO,EAAE,SAAS,MAAM,UAAU,OAAO,UAAU,QAAQ,UAAU,SAAS,UAAU,aAAa;AAAA,EACvG,SAAS,KAAK;AACZ,UAAM,eAAe,eAAe,SAAS,kBAAkB,MAAM,OAAO;AAC5E,WAAO,EAAE,SAAS,OAAO,UAAU,OAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,iCAAa,MAAM,cAAc,GAAG,GAAG,aAAa;AAAA,EAC5I;AACF;AAGA,eAAsB,UAAU,QAAqB,SAA8C;AACjG,MAAI;AACF,UAAM,EAAE,UAAU,QAAQ,UAAU,SAAS,IAAI,MAAM,kBAAkB,QAAQ,OAAO;AACxF,WAAO,EAAE,SAAS,MAAM,UAAU,QAAQ,UAAU,QAAQ,UAAU,SAAS;AAAA,EACjF,SAAS,KAAK;AACZ,WAAO,EAAE,SAAS,OAAO,UAAU,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,kCAAc,MAAM,cAAc,GAAG,EAAE;AAAA,EAChI;AACF;AAGA,eAAsB,UAAU,QAAqB,SAA8C;AACjG,MAAI;AACF,UAAM,EAAE,UAAU,QAAQ,UAAU,SAAS,UAAU,OAAO,IAAI,MAAM,kBAAkB,QAAQ,OAAO;AACzG,WAAO,EAAE,SAAS,MAAM,UAAU,QAAQ,UAAU,QAAQ,UAAU,SAAS,UAAU,QAAQ,QAAQ,SAAS,SAAS,OAAU;AAAA,EACvI,SAAS,KAAK;AACZ,WAAO,EAAE,SAAS,OAAO,UAAU,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,kCAAc,MAAM,cAAc,GAAG,EAAE;AAAA,EAChI;AACF;;;AKjHO,SAAS,WAAW,GAAW,GAAmB;AACvD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,SAAS,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AAC1C,MAAI,WAAW,EAAG,QAAO;AACzB,SAAO,IAAI,YAAY,GAAG,CAAC,IAAI;AACjC;AAGO,SAAS,qBAAqB,GAAW,GAAmB;AACjE,SAAO,WAAW,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC;AAC9C;AAEA,SAAS,UAAU,GAAmB;AACpC,SAAO,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACrC;AAGA,IAAM,sBAAsB;AAG5B,SAAS,YAAY,GAAW,GAAmB;AACjD,MAAI,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAE7C,UAAM,YAAY,KAAK,IAAI,KAAK,EAAE,QAAQ,EAAE,MAAM;AAClD,QAAI,QAAQ;AACZ,aAAS,IAAI,GAAG,IAAI,WAAW,IAAK,KAAI,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG;AACvD,UAAM,aAAa,YAAY,IAAI,QAAQ,YAAY;AACvD,WAAO,KAAK,IAAI,EAAE,SAAS,EAAE,MAAM,IAAI,KAAK,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,UAAU;AAAA,EAC7F;AACA,MAAI,EAAE,SAAS,EAAE,OAAQ,EAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;AACvC,QAAM,IAAI,EAAE;AACZ,QAAM,IAAI,EAAE;AACZ,MAAI,OAAO,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;AACpD,MAAI,OAAO,IAAI,MAAM,IAAI,CAAC;AAE1B,WAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,SAAK,CAAC,IAAI;AACV,aAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,UAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG;AACzB,aAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AAAA,MACtB,OAAO;AACL,aAAK,CAAC,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;AAAA,MAC1D;AAAA,IACF;AACA;AAAC,KAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI;AAAA,EAC7B;AACA,SAAO,KAAK,CAAC;AACf;;;ACjDA,IAAM,uBAAuB;AAM7B,eAAsB,QACpB,SACA,SACA,SACqB;AACrB,QAAM,CAAC,SAAS,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC3C,MAAM,SAAS,OAAO;AAAA,IACtB,MAAM,SAAS,OAAO;AAAA,EACxB,CAAC;AAED,MAAI,CAAC,QAAQ,QAAS,OAAM,IAAI,MAAM,4CAAc,QAAQ,KAAK,EAAE;AACnE,MAAI,CAAC,QAAQ,QAAS,OAAM,IAAI,MAAM,4CAAc,QAAQ,KAAK,EAAE;AAEnE,SAAO,WAAW,QAAQ,QAAQ,QAAQ,MAAM;AAClD;AAGO,SAAS,WAAW,SAAoB,SAAgC;AAC7E,QAAM,UAAU,YAAY,SAAS,OAAO;AAC5C,QAAM,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,EAAE;AAChE,QAAM,QAAqB,CAAC;AAE5B,aAAW,CAAC,GAAG,CAAC,KAAK,SAAS;AAC5B,QAAI,KAAK,GAAG;AACV,YAAM,MAAM,gBAAgB,GAAG,CAAC;AAChC,UAAI,OAAO,MAAM;AACf,cAAM,KAAK,EAAE,MAAM,aAAa,QAAQ,GAAG,OAAO,GAAG,YAAY,EAAE,CAAC;AACpE,cAAM;AAAA,MACR,OAAO;AACL,cAAM,OAAkB,EAAE,MAAM,YAAY,QAAQ,GAAG,OAAO,GAAG,YAAY,IAAI;AACjF,YAAI,EAAE,SAAS,WAAW,EAAE,SAAS,WAAW,EAAE,SAAS,EAAE,OAAO;AAClE,eAAK,YAAY,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,QAClD;AACA,cAAM,KAAK,IAAI;AACf,cAAM;AAAA,MACR;AAAA,IACF,WAAW,GAAG;AACZ,YAAM,KAAK,EAAE,MAAM,WAAW,QAAQ,EAAE,CAAC;AACzC,YAAM;AAAA,IACR,WAAW,GAAG;AACZ,YAAM,KAAK,EAAE,MAAM,SAAS,OAAO,EAAE,CAAC;AACtC,YAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,MAAM;AACxB;AAIA,SAAS,YAAY,GAAc,GAAkD;AACnF,QAAM,IAAI,EAAE,QAAQ,IAAI,EAAE;AAG1B,MAAI,IAAI,IAAI,IAAY,QAAO,cAAc,GAAG,CAAC;AAGjD,QAAM,WAAW,oBAAI,IAAoB;AACzC,QAAM,SAAS,CAACC,IAAWC,OAAsB;AAC/C,UAAM,MAAM,GAAGD,EAAC,IAAIC,EAAC;AACrB,QAAI,IAAI,SAAS,IAAI,GAAG;AACxB,QAAI,MAAM,QAAW;AAAE,UAAI,gBAAgB,EAAED,EAAC,GAAG,EAAEC,EAAC,CAAC;AAAG,eAAS,IAAI,KAAK,CAAC;AAAA,IAAE;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,KAAiB,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAAG,MAAM,IAAI,MAAM,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;AACnF,WAASD,KAAI,GAAGA,MAAK,GAAGA,MAAK;AAC3B,aAASC,KAAI,GAAGA,MAAK,GAAGA,MAAK;AAC3B,UAAI,OAAOD,KAAI,GAAGC,KAAI,CAAC,KAAK,sBAAsB;AAChD,WAAGD,EAAC,EAAEC,EAAC,IAAI,GAAGD,KAAI,CAAC,EAAEC,KAAI,CAAC,IAAI;AAAA,MAChC,OAAO;AACL,WAAGD,EAAC,EAAEC,EAAC,IAAI,KAAK,IAAI,GAAGD,KAAI,CAAC,EAAEC,EAAC,GAAG,GAAGD,EAAC,EAAEC,KAAI,CAAC,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,QAA4B,CAAC;AACnC,MAAI,IAAI,GAAG,IAAI;AACf,SAAO,IAAI,KAAK,IAAI,GAAG;AACrB,QAAI,OAAO,IAAI,GAAG,IAAI,CAAC,KAAK,wBAAwB,GAAG,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG;AACrF,YAAM,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAAG;AAAK;AAAA,IACnC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG;AACvC;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAAQ;AAGd,QAAM,SAA6C,CAAC;AACpD,MAAI,KAAK,GAAG,KAAK;AACjB,aAAW,CAAC,IAAI,EAAE,KAAK,OAAO;AAC5B,WAAO,KAAK,GAAI,QAAO,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;AAC3C,WAAO,KAAK,GAAI,QAAO,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC3C,WAAO,KAAK,CAAC,EAAE,IAAI,GAAG,EAAE,IAAI,CAAC,CAAC;AAAA,EAChC;AACA,SAAO,KAAK,EAAG,QAAO,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;AAC1C,SAAO,KAAK,EAAG,QAAO,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAE1C,SAAO;AACT;AAEA,SAAS,cAAc,GAAc,GAAkD;AACrF,QAAM,SAA6C,CAAC;AACpD,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,WAAO,KAAK,CAAC,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;AAIA,SAAS,gBAAgB,GAAY,GAAoB;AACvD,MAAI,EAAE,SAAS,EAAE,KAAM,QAAO;AAG9B,MAAI,EAAE,SAAS,UAAa,EAAE,SAAS,QAAW;AAChD,WAAO,qBAAqB,EAAE,QAAQ,IAAI,EAAE,QAAQ,EAAE;AAAA,EACxD;AAEA,MAAI,EAAE,SAAS,WAAW,EAAE,SAAS,EAAE,OAAO;AAC5C,WAAO,gBAAgB,EAAE,OAAO,EAAE,KAAK;AAAA,EACzC;AAGA,MAAI,EAAE,SAAS,EAAE,KAAM,QAAO;AAE9B,SAAO;AACT;AAEA,SAAS,gBAAgB,GAAY,GAAoB;AAEvD,QAAM,SAAS,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;AAG7G,QAAM,SAAS,EAAE,MAAM,KAAK,EAAE,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,GAAG;AACvD,QAAM,SAAS,EAAE,MAAM,KAAK,EAAE,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,GAAG;AACvD,QAAM,aAAa,qBAAqB,QAAQ,MAAM;AAEtD,SAAO,SAAS,MAAM,aAAa;AACrC;AAIA,SAAS,eAAe,GAAY,GAA0B;AAC5D,QAAM,UAAU,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI;AACvC,QAAM,UAAU,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI;AACvC,QAAM,SAAuB,CAAC;AAE9B,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,MAAkB,CAAC;AACzB,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAM,QAAQ,IAAI,EAAE,QAAQ,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,OAAO;AAC9D,YAAM,QAAQ,IAAI,EAAE,QAAQ,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,OAAO;AAE9D,UAAI;AACJ,UAAI,UAAU,OAAW,QAAO;AAAA,eACvB,UAAU,OAAW,QAAO;AAAA,eAC5B,UAAU,MAAO,QAAO;AAAA,UAC5B,QAAO;AAEZ,UAAI,KAAK,EAAE,MAAM,QAAQ,OAAO,OAAO,MAAM,CAAC;AAAA,IAChD;AACA,WAAO,KAAK,GAAG;AAAA,EACjB;AACA,SAAO;AACT;","names":["inflateRawSync","MAX_DECOMPRESS_SIZE","inflateRawSync","require","cellRows","_","g","g","totalCells","emptyCells","JSZip","DOMParser","MAX_DECOMPRESS_SIZE","MAX_ROWS","MAX_COLS","DOMParser","JSZip","parsePageRange","JSZip","DOMParser","MAX_DECOMPRESS_SIZE","parseXml","DOMParser","parseRels","runs","text","JSZip","JSZip","i","j"]}