kordoc 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/detect.ts","../src/table/builder.ts","../src/utils.ts","../src/hwpx/parser.ts","../src/page-range.ts","../src/hwp5/record.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/form/recognize.ts","../src/hwpx/generator.ts","../src/diff/text-diff.ts","../src/diff/compare.ts"],"sourcesContent":["/** 매직 바이트 기반 파일 포맷 감지 */\n\nimport type { FileType } from \"./types.js\"\n\n/** 매직 바이트 뷰 생성 (복사 없이 view) */\nfunction magicBytes(buffer: ArrayBuffer): Uint8Array {\n return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength))\n}\n\n/** HWPX (ZIP 기반 한컴 문서): PK\\x03\\x04 */\nexport function isHwpxFile(buffer: ArrayBuffer): boolean {\n const b = magicBytes(buffer)\n return b[0] === 0x50 && b[1] === 0x4b && b[2] === 0x03 && b[3] === 0x04\n}\n\n/** HWP 5.x (OLE2 바이너리 한컴 문서): \\xD0\\xCF\\x11\\xE0 */\nexport function isOldHwpFile(buffer: ArrayBuffer): boolean {\n const b = magicBytes(buffer)\n return b[0] === 0xd0 && b[1] === 0xcf && b[2] === 0x11 && b[3] === 0xe0\n}\n\n/** PDF 문서: %PDF */\nexport function isPdfFile(buffer: ArrayBuffer): boolean {\n const b = magicBytes(buffer)\n return b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46\n}\n\n/** 버퍼로부터 파일 포맷 감지 */\nexport function detectFormat(buffer: ArrayBuffer): FileType {\n if (buffer.byteLength < 4) return \"unknown\"\n if (isHwpxFile(buffer)) return \"hwpx\"\n if (isOldHwpFile(buffer)) return \"hwp\"\n if (isPdfFile(buffer)) return \"pdf\"\n return \"unknown\"\n}\n","/** 2-pass colSpan/rowSpan 테이블 빌더 및 Markdown 변환 */\n\nimport type { CellContext, IRBlock, IRCell, IRTable } from \"../types.js\"\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nfunction 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/** 테이블 열 수 상한 — 한국 공공문서 기준 충분한 값 */\nexport const MAX_COLS = 200\n/** 테이블 행 수 상한 — 메모리 폭주 방지 */\nexport const MAX_ROWS = 10000\n\nexport function buildTable(rows: CellContext[][]): IRTable {\n if (rows.length > MAX_ROWS) rows = rows.slice(0, MAX_ROWS)\n const numRows = rows.length\n\n // Pass 1: maxCols 계산 (sparse Set — 메모리 효율적)\n const tempOccupied = new Set<number>()\n let maxCols = 0\n\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\n let colIdx = 0\n for (const cell of rows[rowIdx]) {\n while (colIdx < MAX_COLS && tempOccupied.has(rowIdx * MAX_COLS + colIdx)) colIdx++\n if (colIdx >= MAX_COLS) break\n\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\n for (let c = colIdx; c < Math.min(colIdx + cell.colSpan, MAX_COLS); c++) {\n tempOccupied.add(r * MAX_COLS + c)\n }\n }\n colIdx += cell.colSpan\n if (colIdx > maxCols) maxCols = colIdx\n }\n }\n tempOccupied.clear()\n\n if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false }\n\n // Pass 2: 실제 배치\n const grid: IRCell[][] = Array.from({ length: numRows }, () =>\n Array.from({ length: maxCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 }))\n )\n const occupied: boolean[][] = Array.from({ length: numRows }, () => Array(maxCols).fill(false))\n\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\n let colIdx = 0\n let cellIdx = 0\n\n while (colIdx < maxCols && cellIdx < rows[rowIdx].length) {\n while (colIdx < maxCols && occupied[rowIdx][colIdx]) colIdx++\n if (colIdx >= maxCols) break\n\n const cell = rows[rowIdx][cellIdx]\n grid[rowIdx][colIdx] = {\n text: cell.text.trim(),\n colSpan: cell.colSpan,\n rowSpan: cell.rowSpan,\n }\n\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\n for (let c = colIdx; c < Math.min(colIdx + cell.colSpan, maxCols); c++) {\n occupied[r][c] = true\n }\n }\n\n colIdx += cell.colSpan\n cellIdx++\n }\n }\n\n return { rows: numRows, cols: maxCols, cells: grid, hasHeader: numRows > 1 }\n}\n\nexport function convertTableToText(rows: CellContext[][]): string {\n return rows\n .map(row =>\n row\n .map(c => c.text.trim().replace(/\\n/g, \" \"))\n .filter(Boolean)\n .join(\" | \")\n )\n .filter(Boolean)\n .join(\"\\n\")\n}\n\nexport function blocksToMarkdown(blocks: IRBlock[]): string {\n const lines: string[] = []\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n\n // 헤딩 블록\n if (block.type === \"heading\" && block.text) {\n const prefix = \"#\".repeat(Math.min(block.level || 2, 6))\n lines.push(\"\", `${prefix} ${block.text}`, \"\")\n continue\n }\n\n // 이미지 블록 — ![alt](filename) 참조\n if (block.type === \"image\" && block.text) {\n lines.push(\"\", `![image](${block.text})`, \"\")\n continue\n }\n\n // 구분선 블록\n if (block.type === \"separator\") {\n lines.push(\"\", \"---\", \"\")\n continue\n }\n\n // 리스트 블록\n if (block.type === \"list\" && block.text) {\n // 텍스트가 이미 번호로 시작하면 그대로 출력 (원래 번호 보존)\n const alreadyNumbered = block.listType === \"ordered\" && /^\\d+\\.\\s/.test(block.text)\n const prefix = alreadyNumbered ? \"\" : block.listType === \"ordered\" ? \"1. \" : \"- \"\n lines.push(`${prefix}${block.text}`)\n if (block.children) {\n for (const child of block.children) {\n const childPrefix = child.listType === \"ordered\" ? \"1.\" : \"-\"\n lines.push(` ${childPrefix} ${child.text || \"\"}`)\n }\n }\n continue\n }\n\n if (block.type === \"paragraph\" && block.text) {\n let text = block.text\n\n // 별표 패턴 (기존 호환)\n if (/^\\[별표\\s*\\d+/.test(text)) {\n const nextBlock = blocks[i + 1]\n if (nextBlock?.type === \"paragraph\" && nextBlock.text && /관련\\)?$/.test(nextBlock.text)) {\n lines.push(\"\", `## ${text} ${nextBlock.text}`, \"\")\n i++\n } else {\n lines.push(\"\", `## ${text}`, \"\")\n }\n continue\n }\n\n if (/^\\([^)]*조[^)]*관련\\)$/.test(text)) {\n lines.push(`*${text}*`, \"\")\n continue\n }\n\n // 하이퍼링크가 있으면 텍스트에 링크 삽입 (javascript: 등 위험 스킴 제거)\n if (block.href) {\n const href = sanitizeHref(block.href)\n if (href) text = `[${text}](${href})`\n }\n\n // 각주가 있으면 괄호로 인라인 삽입\n if (block.footnoteText) {\n text += ` (주: ${block.footnoteText})`\n }\n\n lines.push(text)\n } else if (block.type === \"table\" && block.table) {\n // 테이블 앞에 빈 줄 보장 (마크다운 렌더링 필수)\n if (lines.length > 0 && lines[lines.length - 1] !== \"\") {\n lines.push(\"\")\n }\n lines.push(tableToMarkdown(block.table))\n lines.push(\"\")\n }\n }\n\n return lines.join(\"\\n\").trim()\n}\n\nfunction tableToMarkdown(table: IRTable): string {\n if (table.rows === 0 || table.cols === 0) return \"\"\n\n const { cells, rows: numRows, cols: numCols } = table\n\n // 1행 1열 → 구조화된 텍스트\n if (numRows === 1 && numCols === 1) {\n const content = cells[0][0].text\n return content\n .split(/\\n/)\n .map(line => {\n const trimmed = line.trim()\n if (!trimmed) return \"\"\n if (/^\\d+\\.\\s/.test(trimmed)) return `**${trimmed}**`\n if (/^[가-힣]\\.\\s/.test(trimmed)) return ` ${trimmed}`\n return trimmed\n })\n .filter(Boolean)\n .join(\"\\n\")\n }\n\n // 병합 셀: 행/열 병합된 셀은 빈 칸으로\n const display: string[][] = Array.from({ length: numRows }, () => Array(numCols).fill(\"\"))\n const skip = new Set<string>()\n\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n if (skip.has(`${r},${c}`)) continue\n const cell = cells[r][c]\n display[r][c] = cell.text.replace(/\\n/g, \"<br>\")\n\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n if (r + dr < numRows && c + dc < numCols) {\n skip.add(`${r + dr},${c + dc}`)\n }\n }\n }\n }\n }\n\n // rowSpan에 의해 생긴 빈 placeholder 행만 제거 (내용이 동일한 실제 데이터 행은 유지)\n const uniqueRows: string[][] = []\n for (const row of display) {\n const isEmptyPlaceholder = row.every(cell => cell === \"\")\n if (!isEmptyPlaceholder) uniqueRows.push(row)\n }\n\n if (uniqueRows.length === 0) return \"\"\n\n const md: string[] = []\n md.push(\"| \" + uniqueRows[0].join(\" | \") + \" |\")\n md.push(\"| \" + uniqueRows[0].map(() => \"---\").join(\" | \") + \" |\")\n for (let i = 1; i < uniqueRows.length; i++) {\n md.push(\"| \" + uniqueRows[i].join(\" | \") + \" |\")\n }\n return md.join(\"\\n\")\n}\n","/** 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 return normalized.includes(\"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\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","/**\n * HWPX 파서 — manifest 멀티섹션, colSpan/rowSpan, 중첩테이블\n *\n * lexdiff 기반 + edu-facility-ai 손상ZIP 복구\n */\n\nimport JSZip from \"jszip\"\nimport { inflateRawSync } from \"zlib\"\nimport { DOMParser } from \"@xmldom/xmldom\"\nimport { buildTable, convertTableToText, blocksToMarkdown, MAX_COLS, MAX_ROWS } from \"../table/builder.js\"\nimport type { CellContext, IRBlock, DocumentMetadata, InternalParseResult, ParseOptions, ParseWarning, OutlineItem, InlineStyle, ExtractedImage } from \"../types.js\"\nimport { KordocError, isPathTraversal } from \"../utils.js\"\nimport { parsePageRange } from \"../page-range.js\"\n\n/** 압축 해제 최대 크기 (100MB) — ZIP bomb 방지 */\nconst MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024\n/** 손상 ZIP 복구 시 최대 엔트리 수 */\nconst MAX_ZIP_ENTRIES = 500\n\n/** colSpan/rowSpan을 안전한 범위로 클램핑 */\nfunction clampSpan(val: number, max: number): number {\n return Math.max(1, Math.min(val, max))\n}\n\n/** XML DOM 재귀 최대 깊이 — 악성 파일의 스택 오버플로 방지 */\nconst MAX_XML_DEPTH = 200\n\ninterface TableState { rows: CellContext[][]; currentRow: CellContext[]; cell: CellContext | null }\n\n/** xmldom DOMParser 생성 — onError 콜백으로 malformed XML 경고 수집 */\nfunction createXmlParser(warnings?: ParseWarning[]): DOMParser {\n return new DOMParser({\n onError(level: \"warn\" | \"error\" | \"fatalError\", msg: string) {\n if (level === \"fatalError\") throw new KordocError(`XML 파싱 실패: ${msg}`)\n warnings?.push({ code: \"MALFORMED_XML\", message: `XML ${level === \"warn\" ? \"경고\" : \"오류\"}: ${msg}` })\n },\n })\n}\n\n// ─── HWPX 스타일 정보 ──────────────────────────────\n\ninterface HwpxCharProperty {\n fontSize?: number // 단위: pt (hwpx는 centi-pt → /100)\n bold?: boolean\n italic?: boolean\n fontName?: string\n}\n\ninterface HwpxStyleMap {\n charProperties: Map<string, HwpxCharProperty> // id → property\n styles: Map<string, { name: string; charPrId?: string; paraPrId?: string }> // id → style\n}\n\n/** head.xml 또는 header.xml에서 스타일 정보 추출 */\nasync function extractHwpxStyles(zip: JSZip, decompressed?: { total: number }): Promise<HwpxStyleMap> {\n const result: HwpxStyleMap = {\n charProperties: new Map(),\n styles: new Map(),\n }\n\n const headerPaths = [\"Contents/header.xml\", \"header.xml\", \"Contents/head.xml\", \"head.xml\"]\n for (const hp of headerPaths) {\n const hpLower = hp.toLowerCase()\n const file = zip.file(hp) || Object.values(zip.files).find(f => f.name.toLowerCase() === hpLower) || null\n if (!file) continue\n\n try {\n const xml = await file.async(\"text\")\n if (decompressed) {\n decompressed.total += xml.length * 2\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\n }\n const parser = createXmlParser()\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\n if (!doc.documentElement) continue\n\n // charProperties 파싱\n parseCharProperties(doc, result.charProperties)\n // styles 파싱\n parseStyleElements(doc, result.styles)\n break\n } catch { continue }\n }\n\n return result\n}\n\nfunction parseCharProperties(doc: Document, map: Map<string, HwpxCharProperty>): void {\n // <hh:charPr> 또는 <charPr> 요소 탐색\n const tagNames = [\"hh:charPr\", \"charPr\", \"hp:charPr\"]\n for (const tagName of tagNames) {\n const elements = doc.getElementsByTagName(tagName)\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i]\n const id = el.getAttribute(\"id\") || el.getAttribute(\"IDRef\") || \"\"\n if (!id) continue\n\n const prop: HwpxCharProperty = {}\n\n // height 속성 (centi-pt 단위)\n const height = el.getAttribute(\"height\")\n if (height) prop.fontSize = parseInt(height, 10) / 100\n\n // bold/italic\n const bold = el.getAttribute(\"bold\")\n if (bold === \"true\" || bold === \"1\") prop.bold = true\n const italic = el.getAttribute(\"italic\")\n if (italic === \"true\" || italic === \"1\") prop.italic = true\n\n // 하위 요소에서 fontface 탐색\n const fontFaces = el.getElementsByTagName(\"*\")\n for (let j = 0; j < fontFaces.length; j++) {\n const ff = fontFaces[j]\n const localTag = (ff.tagName || \"\").replace(/^[^:]+:/, \"\")\n if (localTag === \"fontface\" || localTag === \"fontRef\") {\n const face = ff.getAttribute(\"face\") || ff.getAttribute(\"FontFace\")\n if (face) { prop.fontName = face; break }\n }\n }\n\n map.set(id, prop)\n }\n }\n}\n\nfunction parseStyleElements(doc: Document, map: Map<string, { name: string; charPrId?: string; paraPrId?: string }>): void {\n const tagNames = [\"hh:style\", \"style\", \"hp:style\"]\n for (const tagName of tagNames) {\n const elements = doc.getElementsByTagName(tagName)\n for (let i = 0; i < elements.length; i++) {\n const el = elements[i]\n const id = el.getAttribute(\"id\") || el.getAttribute(\"IDRef\") || String(i)\n const name = el.getAttribute(\"name\") || el.getAttribute(\"engName\") || \"\"\n const charPrId = el.getAttribute(\"charPrIDRef\") || undefined\n const paraPrId = el.getAttribute(\"paraPrIDRef\") || undefined\n map.set(id, { name, charPrId, paraPrId })\n }\n }\n}\n\n/** XXE/Billion Laughs 방지 — DOCTYPE 제거 (내부 DTD 서브셋 포함) */\nfunction stripDtd(xml: string): string {\n return xml.replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\nexport async function parseHwpxDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<InternalParseResult> {\n // Best-effort 사전 검증 — CD 선언 크기 기반 (위조 가능, 실제 방어는 per-file 누적 체크)\n const precheck = precheckZipSize(buffer)\n if (precheck.totalUncompressed > MAX_DECOMPRESS_SIZE) {\n throw new KordocError(\"ZIP 비압축 크기 초과 (ZIP bomb 의심)\")\n }\n if (precheck.entryCount > MAX_ZIP_ENTRIES) {\n throw new KordocError(\"ZIP 엔트리 수 초과 (ZIP bomb 의심)\")\n }\n\n let zip: JSZip\n\n try {\n zip = await JSZip.loadAsync(buffer)\n } catch {\n return extractFromBrokenZip(buffer)\n }\n\n // loadAsync 후 실제 엔트리 수 검증 — CD 위조와 무관한 진짜 방어선\n const actualEntryCount = Object.keys(zip.files).length\n if (actualEntryCount > MAX_ZIP_ENTRIES) {\n throw new KordocError(\"ZIP 엔트리 수 초과 (ZIP bomb 의심)\")\n }\n\n // ZIP 전체 파일 누적 압축해제 크기 추적 (비섹션 파일 포함)\n const decompressed = { total: 0 }\n\n // 메타데이터 추출 (best-effort)\n const metadata: DocumentMetadata = {}\n await extractHwpxMetadata(zip, metadata, decompressed)\n\n // 스타일 정보 추출 (best-effort)\n const styleMap = await extractHwpxStyles(zip, decompressed)\n const warnings: ParseWarning[] = []\n\n const sectionPaths = await resolveSectionPaths(zip)\n if (sectionPaths.length === 0) throw new KordocError(\"HWPX에서 섹션 파일을 찾을 수 없습니다\")\n\n metadata.pageCount = sectionPaths.length\n\n // 페이지 범위 필터링 (섹션 단위 근사치)\n const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null\n const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length\n const blocks: IRBlock[] = []\n let parsedSections = 0\n for (let si = 0; si < sectionPaths.length; si++) {\n if (pageFilter && !pageFilter.has(si + 1)) continue\n const file = zip.file(sectionPaths[si])\n if (!file) continue\n try {\n const xml = await file.async(\"text\")\n decompressed.total += xml.length * 2\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\n blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1))\n parsedSections++\n options?.onProgress?.(parsedSections, totalTarget)\n } catch (secErr) {\n if (secErr instanceof KordocError) throw secErr\n warnings.push({ page: si + 1, message: `섹션 ${si + 1} 파싱 실패: ${secErr instanceof Error ? secErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\n }\n }\n\n // 이미지 블록에서 ZIP 바이너리 추출\n const images = await extractImagesFromZip(zip, blocks, decompressed, warnings)\n\n // 스타일 기반 헤딩 감지\n detectHwpxHeadings(blocks, styleMap)\n\n // outline 구축\n const outline: OutlineItem[] = blocks\n .filter(b => b.type === \"heading\" && b.level && b.text)\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\n\n const markdown = blocksToMarkdown(blocks)\n return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined, images: images.length > 0 ? images : undefined }\n}\n\n// ─── 이미지 추출 ───────────────────────────────────\n\n/** 확장자 → MIME 타입 */\nfunction imageExtToMime(ext: string): string {\n switch (ext.toLowerCase()) {\n case \"jpg\": case \"jpeg\": return \"image/jpeg\"\n case \"png\": return \"image/png\"\n case \"gif\": return \"image/gif\"\n case \"bmp\": return \"image/bmp\"\n case \"tif\": case \"tiff\": return \"image/tiff\"\n case \"wmf\": return \"image/wmf\"\n case \"emf\": return \"image/emf\"\n case \"svg\": return \"image/svg+xml\"\n default: return \"application/octet-stream\"\n }\n}\n\n/** MIME → 확장자 */\nfunction mimeToExt(mime: string): string {\n if (mime.includes(\"jpeg\")) return \"jpg\"\n if (mime.includes(\"png\")) return \"png\"\n if (mime.includes(\"gif\")) return \"gif\"\n if (mime.includes(\"bmp\")) return \"bmp\"\n if (mime.includes(\"tiff\")) return \"tif\"\n if (mime.includes(\"wmf\")) return \"wmf\"\n if (mime.includes(\"emf\")) return \"emf\"\n if (mime.includes(\"svg\")) return \"svg\"\n return \"bin\"\n}\n\n/** blocks에서 type=\"image\" 블록의 참조를 ZIP에서 실제 바이너리로 변환 */\nasync function extractImagesFromZip(\n zip: JSZip,\n blocks: IRBlock[],\n decompressed: { total: number },\n warnings?: ParseWarning[],\n): Promise<ExtractedImage[]> {\n const images: ExtractedImage[] = []\n let imageIndex = 0\n\n for (const block of blocks) {\n if (block.type !== \"image\" || !block.text) continue\n\n const ref = block.text\n // BinData/ 폴더 내에서 참조 파일 찾기\n const candidates = [\n `BinData/${ref}`,\n `Contents/BinData/${ref}`,\n ref, // 절대 경로일 수도 있음\n ]\n\n let found = false\n for (const path of candidates) {\n if (isPathTraversal(path)) continue\n const file = zip.file(path)\n if (!file) continue\n\n try {\n const data = await file.async(\"uint8array\")\n decompressed.total += data.length\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\n\n const ext = ref.includes(\".\") ? ref.split(\".\").pop()! : \"png\"\n const mimeType = imageExtToMime(ext)\n imageIndex++\n const filename = `image_${String(imageIndex).padStart(3, \"0\")}.${mimeToExt(mimeType)}`\n\n images.push({ filename, data, mimeType })\n // 블록 텍스트를 참조 파일명으로 교체\n block.text = filename\n block.imageData = { data, mimeType, filename: ref }\n found = true\n break\n } catch (err) {\n if (err instanceof KordocError) throw err\n // 개별 이미지 실패는 경고로 처리\n }\n }\n\n if (!found) {\n warnings?.push({ page: block.pageNumber, message: `이미지 파일 없음: ${ref}`, code: \"SKIPPED_IMAGE\" })\n // image 블록을 paragraph로 전환 (참조만 남김)\n block.type = \"paragraph\"\n block.text = `[이미지: ${ref}]`\n }\n }\n\n return images\n}\n\n// ─── 메타데이터 추출 (best-effort) ───────────────────\n\n/**\n * HWPX ZIP 내 메타데이터 파일에서 Dublin Core 정보 추출.\n * 표준 경로: meta.xml, docProps/core.xml, META-INF/container.xml\n */\nasync function extractHwpxMetadata(zip: JSZip, metadata: DocumentMetadata, decompressed?: { total: number }): Promise<void> {\n try {\n // meta.xml (HWPX 표준) 또는 docProps/core.xml (OOXML 호환)\n const metaPaths = [\"meta.xml\", \"META-INF/meta.xml\", \"docProps/core.xml\"]\n for (const mp of metaPaths) {\n const file = zip.file(mp) || Object.values(zip.files).find(f => f.name.toLowerCase() === mp.toLowerCase()) || null\n if (!file) continue\n const xml = await file.async(\"text\")\n if (decompressed) {\n decompressed.total += xml.length * 2\n if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError(\"ZIP 압축 해제 크기 초과 (ZIP bomb 의심)\")\n }\n parseDublinCoreMetadata(xml, metadata)\n if (metadata.title || metadata.author) return\n }\n } catch {\n // best-effort\n }\n}\n\n/** Dublin Core (dc:) 메타데이터 XML 파싱 */\nfunction parseDublinCoreMetadata(xml: string, metadata: DocumentMetadata): void {\n const parser = createXmlParser()\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\n if (!doc.documentElement) return\n\n const getText = (tagNames: string[]): string | undefined => {\n for (const tag of tagNames) {\n const els = doc.getElementsByTagName(tag)\n if (els.length > 0) {\n const text = els[0].textContent?.trim()\n if (text) return text\n }\n }\n return undefined\n }\n\n metadata.title = metadata.title || getText([\"dc:title\", \"title\"])\n metadata.author = metadata.author || getText([\"dc:creator\", \"creator\", \"cp:lastModifiedBy\"])\n metadata.description = metadata.description || getText([\"dc:description\", \"description\", \"dc:subject\", \"subject\"])\n metadata.createdAt = metadata.createdAt || getText([\"dcterms:created\", \"meta:creation-date\"])\n metadata.modifiedAt = metadata.modifiedAt || getText([\"dcterms:modified\", \"meta:date\"])\n\n const keywords = getText([\"dc:keyword\", \"cp:keywords\", \"meta:keyword\"])\n if (keywords && !metadata.keywords) {\n metadata.keywords = keywords.split(/[,;]/).map(k => k.trim()).filter(Boolean)\n }\n}\n\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\nexport async function extractHwpxMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\n let zip: JSZip\n try {\n zip = await JSZip.loadAsync(buffer)\n } catch {\n throw new KordocError(\"HWPX ZIP을 열 수 없습니다\")\n }\n\n const metadata: DocumentMetadata = {}\n await extractHwpxMetadata(zip, metadata)\n\n const sectionPaths = await resolveSectionPaths(zip)\n metadata.pageCount = sectionPaths.length\n\n return metadata\n}\n\n/**\n * loadAsync 전 raw buffer에서 Central Directory를 파싱하여\n * 선언된 비압축 크기 합산 + 엔트리 수를 사전 검증.\n *\n * ⚠️ 한계: CD에 선언된 비압축 크기는 공격자가 위조 가능.\n * 이 함수는 \"정직한 ZIP\"에 대한 조기 거부(best-effort early rejection)만 수행.\n * 실제 ZIP bomb 방어는 loadAsync 후 per-file 누적 크기 체크에서 담당.\n *\n * Central Directory가 손상된 경우(extractFromBrokenZip으로 폴백될 케이스)에는\n * 안전한 기본값을 반환하여 loadAsync가 시도되도록 함.\n *\n * @internal 테스트 전용 export — public API(index.ts)에서 재노출하지 않음\n */\nexport function precheckZipSize(buffer: ArrayBuffer): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n if (len < 22) return { totalUncompressed: 0, entryCount: 0 }\n\n // End of Central Directory (EOCD) 시그니처를 뒤에서부터 탐색\n // EOCD는 최소 22바이트, comment 최대 65535바이트\n const searchStart = Math.max(0, len - 22 - 65535)\n let eocdOffset = -1\n for (let i = len - 22; i >= searchStart; 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 const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n // Central Directory 엔트리 순회\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 return { totalUncompressed, entryCount }\n } catch {\n // DataView 범위 초과 등 예외 시 안전한 기본값 반환\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n// ─── 손상 ZIP 복구 (edu-facility-ai에서 포팅) ──────────\n\nfunction extractFromBrokenZip(buffer: ArrayBuffer): InternalParseResult {\n const data = new Uint8Array(buffer)\n const view = new DataView(buffer)\n let pos = 0\n const blocks: IRBlock[] = []\n let totalDecompressed = 0\n let entryCount = 0\n\n while (pos < data.length - 30) {\n // PK\\x03\\x04 시그니처 확인 — 미매칭 시 다음 PK 시그니처까지 스캔 (중간 손상 복구)\n if (data[pos] !== 0x50 || data[pos + 1] !== 0x4b || data[pos + 2] !== 0x03 || data[pos + 3] !== 0x04) {\n pos++\n while (pos < data.length - 30) {\n if (data[pos] === 0x50 && data[pos + 1] === 0x4b && data[pos + 2] === 0x03 && data[pos + 3] === 0x04) break\n pos++\n }\n continue\n }\n\n if (++entryCount > MAX_ZIP_ENTRIES) break\n\n const method = view.getUint16(pos + 8, true)\n const compSize = view.getUint32(pos + 18, true)\n const nameLen = view.getUint16(pos + 26, true)\n const extraLen = view.getUint16(pos + 28, true)\n\n // nameLen 상한 — 비정상 값에 의한 대규모 버퍼 할당 방지\n if (nameLen > 1024 || extraLen > 65535) { pos += 30 + nameLen + extraLen; continue }\n\n const fileStart = pos + 30 + nameLen + extraLen\n // 범위 초과 검증 — OOB 및 무한 루프 방지\n if (fileStart + compSize > data.length) break\n if (compSize === 0 && method !== 0) { pos = fileStart; continue }\n\n const nameBytes = data.slice(pos + 30, pos + 30 + nameLen)\n const name = new TextDecoder().decode(nameBytes)\n\n // 경로 순회 방지 — 상위 디렉토리 참조 및 절대 경로 차단\n if (isPathTraversal(name)) { pos = fileStart + compSize; continue }\n const fileData = data.slice(fileStart, fileStart + compSize)\n pos = fileStart + compSize\n\n if (!name.toLowerCase().includes(\"section\") || !name.endsWith(\".xml\")) continue\n\n try {\n let content: string\n if (method === 0) {\n content = new TextDecoder().decode(fileData)\n } else if (method === 8) {\n const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE })\n content = new TextDecoder().decode(decompressed)\n } else {\n continue\n }\n totalDecompressed += content.length * 2\n if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError(\"압축 해제 크기 초과\")\n blocks.push(...parseSectionXml(content))\n } catch {\n continue\n }\n }\n\n if (blocks.length === 0) throw new KordocError(\"손상된 HWPX에서 섹션 데이터를 복구할 수 없습니다\")\n const markdown = blocksToMarkdown(blocks)\n return { markdown, blocks }\n}\n\n// ─── Manifest 해석 ───────────────────────────────────\n\nasync function resolveSectionPaths(zip: JSZip): Promise<string[]> {\n const manifestPaths = [\"Contents/content.hpf\", \"content.hpf\"]\n for (const mp of manifestPaths) {\n const mpLower = mp.toLowerCase()\n const file = zip.file(mp) || Object.values(zip.files).find(f => f.name.toLowerCase() === mpLower) || null\n if (!file) continue\n const xml = await file.async(\"text\")\n const paths = parseSectionPathsFromManifest(xml)\n if (paths.length > 0) return paths\n }\n\n // fallback: section*.xml 직접 검색\n const sectionFiles = zip.file(/[Ss]ection\\d+\\.xml$/)\n return sectionFiles.map(f => f.name).sort()\n}\n\nfunction parseSectionPathsFromManifest(xml: string): string[] {\n const parser = createXmlParser()\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\n const items = doc.getElementsByTagName(\"opf:item\")\n const spine = doc.getElementsByTagName(\"opf:itemref\")\n\n const isSectionId = (id: string) => /^s/i.test(id) || id.toLowerCase().includes(\"section\")\n const idToHref = new Map<string, string>()\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const id = item.getAttribute(\"id\") || \"\"\n let href = item.getAttribute(\"href\") || \"\"\n const mediaType = item.getAttribute(\"media-type\") || \"\"\n if (!isSectionId(id) && !mediaType.includes(\"xml\")) continue\n if (!href.startsWith(\"/\") && !href.startsWith(\"Contents/\") && isSectionId(id))\n href = \"Contents/\" + href\n idToHref.set(id, href)\n }\n\n if (spine.length > 0) {\n const ordered: string[] = []\n for (let i = 0; i < spine.length; i++) {\n const href = idToHref.get(spine[i].getAttribute(\"idref\") || \"\")\n if (href) ordered.push(href)\n }\n if (ordered.length > 0) return ordered\n }\n return Array.from(idToHref.entries())\n .filter(([id]) => isSectionId(id))\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([, href]) => href)\n}\n\n// ─── 헤딩 감지 (스타일 기반) ────────────────────────\n\n/** HWPX 스타일 기반 헤딩 감지 */\nfunction detectHwpxHeadings(blocks: IRBlock[], styleMap: HwpxStyleMap): void {\n // 본문 폰트 크기 결정\n let baseFontSize = 0\n const sizeFreq = new Map<number, number>()\n for (const b of blocks) {\n if (b.style?.fontSize) {\n sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1)\n }\n }\n let maxCount = 0\n for (const [size, count] of sizeFreq) {\n if (count > maxCount) { maxCount = count; baseFontSize = size }\n }\n\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text) continue\n const text = block.text.trim()\n if (text.length === 0 || text.length > 200 || /^\\d+$/.test(text)) continue\n\n let level = 0\n\n // 폰트 크기 기반\n if (baseFontSize > 0 && block.style?.fontSize) {\n const ratio = block.style.fontSize / baseFontSize\n if (ratio >= 1.5) level = 1\n else if (ratio >= 1.3) level = 2\n else if (ratio >= 1.15) level = 3\n }\n\n // \"제N조/장/절\" 패턴\n if (/^제\\d+[조장절편]/.test(text) && text.length <= 50) {\n if (level === 0) level = 3\n }\n\n if (level > 0) {\n block.type = \"heading\"\n block.level = level\n }\n }\n}\n\n// ─── 섹션 XML 파싱 ──────────────────────────────────\n\nfunction parseSectionXml(xml: string, styleMap?: HwpxStyleMap, warnings?: ParseWarning[], sectionNum?: number): IRBlock[] {\n const parser = createXmlParser(warnings)\n const doc = parser.parseFromString(stripDtd(xml), \"text/xml\")\n if (!doc.documentElement) return []\n\n const blocks: IRBlock[] = []\n walkSection(doc.documentElement, blocks, null, [], styleMap, warnings, sectionNum)\n return blocks\n}\n\n/** pic/shape 요소에서 이미지 참조 경로 추출 (binaryItemIDRef 또는 href) */\nfunction extractImageRef(el: Element): string | null {\n // HWPX: <hp:imgRect> 또는 <hp:img> 내 binaryItemIDRef 속성\n // 또는 하위에서 img 관련 속성 탐색\n const children = el.childNodes\n if (!children) return null\n for (let i = 0; i < children.length; i++) {\n const child = children[i] as Element\n if (child.nodeType !== 1) continue\n const tag = (child.tagName || child.localName || \"\").replace(/^[^:]+:/, \"\")\n if (tag === \"imgRect\" || tag === \"img\" || tag === \"imgClip\") {\n const ref = child.getAttribute(\"binaryItemIDRef\") || child.getAttribute(\"href\") || \"\"\n if (ref) return ref\n }\n // lineShape > imgRect 같은 중첩 구조\n const nested = extractImageRef(child)\n if (nested) return nested\n }\n // 직접 속성 체크\n const directRef = el.getAttribute(\"binaryItemIDRef\") || \"\"\n if (directRef) return directRef\n return null\n}\n\nfunction walkSection(\n node: Node, blocks: IRBlock[],\n tableCtx: TableState | null, tableStack: TableState[],\n styleMap?: HwpxStyleMap, warnings?: ParseWarning[], sectionNum?: number,\n depth: number = 0\n): void {\n if (depth > MAX_XML_DEPTH) return\n const children = node.childNodes\n if (!children) return\n\n for (let i = 0; i < children.length; i++) {\n const el = children[i] as Element\n if (el.nodeType !== 1) continue\n\n const tag = el.tagName || el.localName || \"\"\n const localTag = tag.replace(/^[^:]+:/, \"\")\n\n switch (localTag) {\n case \"tbl\": {\n if (tableCtx) tableStack.push(tableCtx)\n const newTable: TableState = { rows: [], currentRow: [], cell: null }\n walkSection(el, blocks, newTable, tableStack, styleMap, warnings, sectionNum, depth + 1)\n\n if (newTable.rows.length > 0) {\n if (tableStack.length > 0) {\n const parentTable = tableStack.pop()!\n const nestedText = convertTableToText(newTable.rows)\n if (parentTable.cell) {\n parentTable.cell.text += (parentTable.cell.text ? \"\\n\" : \"\") + nestedText\n }\n tableCtx = parentTable\n } else {\n blocks.push({ type: \"table\", table: buildTable(newTable.rows), pageNumber: sectionNum })\n tableCtx = null\n }\n } else {\n tableCtx = tableStack.length > 0 ? tableStack.pop()! : null\n }\n break\n }\n\n case \"tr\":\n if (tableCtx) {\n tableCtx.currentRow = []\n walkSection(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\n if (tableCtx.currentRow.length > 0) tableCtx.rows.push(tableCtx.currentRow)\n tableCtx.currentRow = []\n }\n break\n\n case \"tc\":\n if (tableCtx) {\n tableCtx.cell = { text: \"\", colSpan: 1, rowSpan: 1 }\n walkSection(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\n if (tableCtx.cell) {\n tableCtx.currentRow.push(tableCtx.cell)\n tableCtx.cell = null\n }\n }\n break\n\n case \"cellSpan\":\n if (tableCtx?.cell) {\n const cs = parseInt(el.getAttribute(\"colSpan\") || \"1\", 10)\n const rs = parseInt(el.getAttribute(\"rowSpan\") || \"1\", 10)\n tableCtx.cell.colSpan = clampSpan(cs, MAX_COLS)\n tableCtx.cell.rowSpan = clampSpan(rs, MAX_ROWS)\n }\n break\n\n case \"p\": {\n const { text, href, footnote, style } = extractParagraphInfo(el, styleMap)\n if (text) {\n if (tableCtx?.cell) {\n tableCtx.cell.text += (tableCtx.cell.text ? \"\\n\" : \"\") + text\n } else if (!tableCtx) {\n const block: IRBlock = { type: \"paragraph\", text, pageNumber: sectionNum }\n if (style) block.style = style\n if (href) block.href = href\n if (footnote) block.footnoteText = footnote\n blocks.push(block)\n }\n }\n // <p> 내부의 <tbl>만 별도 처리 — extractParagraphInfo가 이미 텍스트를 추출했으므로\n // 전체 walkSection 재귀 대신 테이블/이미지 자식만 선택적으로 처리\n tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\n break\n }\n\n // 이미지/그림 — 경로 추출 또는 경고\n case \"pic\": case \"shape\": case \"drawingObject\": {\n const imgRef = extractImageRef(el)\n if (imgRef) {\n blocks.push({ type: \"image\", text: imgRef, pageNumber: sectionNum })\n } else if (warnings && sectionNum) {\n warnings.push({ page: sectionNum, message: `스킵된 요소: ${localTag}`, code: \"SKIPPED_IMAGE\" })\n }\n break\n }\n\n default:\n walkSection(el, blocks, tableCtx, tableStack, styleMap, warnings, sectionNum, depth + 1)\n break\n }\n }\n}\n\n/** <p> 내부에서 텍스트가 아닌 구조적 자식만 처리 (tbl, pic, shape). tableCtx 반환으로 상태 전파 */\nfunction walkParagraphChildren(\n node: Node, blocks: IRBlock[],\n tableCtx: TableState | null, tableStack: TableState[],\n styleMap?: HwpxStyleMap, warnings?: ParseWarning[], sectionNum?: number,\n depth: number = 0\n): TableState | null {\n if (depth > MAX_XML_DEPTH) return tableCtx\n const children = node.childNodes\n if (!children) return tableCtx\n for (let i = 0; i < children.length; i++) {\n const el = children[i] as Element\n if (el.nodeType !== 1) continue\n const tag = el.tagName || el.localName || \"\"\n const localTag = tag.replace(/^[^:]+:/, \"\")\n // 테이블은 walkSection으로 위임\n if (localTag === \"tbl\") {\n if (tableCtx) tableStack.push(tableCtx)\n const newTable: TableState = { rows: [], currentRow: [], cell: null }\n walkSection(el, blocks, newTable, tableStack, styleMap, warnings, sectionNum, depth + 1)\n if (newTable.rows.length > 0) {\n if (tableStack.length > 0) {\n const parentTable = tableStack.pop()!\n const nestedText = convertTableToText(newTable.rows)\n if (parentTable.cell) {\n parentTable.cell.text += (parentTable.cell.text ? \"\\n\" : \"\") + nestedText\n }\n tableCtx = parentTable\n } else {\n blocks.push({ type: \"table\", table: buildTable(newTable.rows), pageNumber: sectionNum })\n tableCtx = null\n }\n } else {\n tableCtx = tableStack.length > 0 ? tableStack.pop()! : null\n }\n } else if (localTag === \"pic\" || localTag === \"shape\" || localTag === \"drawingObject\") {\n const imgRef = extractImageRef(el)\n if (imgRef) {\n blocks.push({ type: \"image\", text: imgRef, pageNumber: sectionNum })\n } else if (warnings && sectionNum) {\n warnings.push({ page: sectionNum, message: `스킵된 요소: ${localTag}`, code: \"SKIPPED_IMAGE\" })\n }\n }\n }\n return tableCtx\n}\n\ninterface ParagraphInfo {\n text: string\n href?: string\n footnote?: string\n style?: InlineStyle\n}\n\nfunction extractParagraphInfo(para: Element, styleMap?: HwpxStyleMap): ParagraphInfo {\n let text = \"\"\n let href: string | undefined\n let footnote: string | undefined\n let charPrId: string | undefined\n\n // 문단의 스타일 참조 → charPr로 간접 조회\n // HWPX <p>에는 paraPrIDRef/styleIDRef가 있고, charPrIDRef는 <r> 요소에 있음\n // 여기서는 일단 null — <r> 요소에서 charPrIDRef를 가져옴\n\n const walk = (node: Node) => {\n const children = node.childNodes\n if (!children) return\n for (let i = 0; i < children.length; i++) {\n const child = children[i] as Element\n if (child.nodeType === 3) { text += child.textContent || \"\"; continue }\n if (child.nodeType !== 1) continue\n\n const tag = (child.tagName || child.localName || \"\").replace(/^[^:]+:/, \"\")\n switch (tag) {\n case \"t\": text += child.textContent || \"\"; break\n case \"tab\": text += \"\\t\"; break\n case \"br\":\n if ((child.getAttribute(\"type\") || \"line\") === \"line\") text += \"\\n\"\n break\n case \"fwSpace\": case \"hwSpace\": text += \" \"; break\n case \"tbl\": break // 테이블은 walkSection에서 처리\n\n // 하이퍼링크\n case \"hyperlink\": {\n const url = child.getAttribute(\"url\") || child.getAttribute(\"href\") || \"\"\n if (url) href = url\n // 하이퍼링크 내 텍스트 추출\n walk(child)\n break\n }\n\n // 각주/미주\n case \"footNote\": case \"endNote\": case \"fn\": case \"en\": {\n const noteText = extractTextFromNode(child)\n if (noteText) footnote = (footnote ? footnote + \"; \" : \"\") + noteText\n break\n }\n\n // run 요소에서 charPrIDRef 추출\n case \"r\": {\n const runCharPr = child.getAttribute(\"charPrIDRef\")\n if (runCharPr && !charPrId) charPrId = runCharPr\n walk(child)\n break\n }\n\n default: walk(child); break\n }\n }\n }\n walk(para)\n\n const cleanText = text.replace(/[ \\t]+/g, \" \").trim()\n\n // 스타일 정보 조회\n let style: InlineStyle | undefined\n if (styleMap && charPrId) {\n const charProp = styleMap.charProperties.get(charPrId)\n if (charProp) {\n style = {}\n if (charProp.fontSize) style.fontSize = charProp.fontSize\n if (charProp.bold) style.bold = true\n if (charProp.italic) style.italic = true\n if (charProp.fontName) style.fontName = charProp.fontName\n if (!style.fontSize && !style.bold && !style.italic) style = undefined\n }\n }\n\n return { text: cleanText, href, footnote, style }\n}\n\n/** 노드 내 모든 텍스트를 재귀적으로 추출 */\nfunction extractTextFromNode(node: Node): string {\n let result = \"\"\n const children = node.childNodes\n if (!children) return result\n for (let i = 0; i < children.length; i++) {\n const child = children[i]\n if (child.nodeType === 3) result += child.textContent || \"\"\n else if (child.nodeType === 1) result += extractTextFromNode(child)\n }\n return result.trim()\n}\n","/** 페이지/섹션 범위 파싱 유틸리티 */\n\n/**\n * 페이지 범위 지정을 1-based Set<number>로 변환.\n *\n * @param spec - [1,2,3] 또는 \"1-3\" 또는 \"1,3,5-7\"\n * @param maxPages - 최대 페이지 수 (클램핑 상한)\n * @returns 1-based 페이지 번호 Set\n */\nexport function parsePageRange(spec: number[] | string, maxPages: number): Set<number> {\n const result = new Set<number>()\n if (maxPages <= 0) return result\n\n if (Array.isArray(spec)) {\n for (const n of spec) {\n const page = Math.round(n)\n if (page >= 1 && page <= maxPages) result.add(page)\n }\n return result\n }\n\n if (typeof spec !== \"string\" || spec.trim() === \"\") return result\n\n const parts = spec.split(\",\")\n for (const part of parts) {\n const trimmed = part.trim()\n if (!trimmed) continue\n\n const rangeMatch = trimmed.match(/^(\\d+)\\s*-\\s*(\\d+)$/)\n if (rangeMatch) {\n const start = Math.max(1, parseInt(rangeMatch[1], 10))\n const end = Math.min(maxPages, parseInt(rangeMatch[2], 10))\n for (let i = start; i <= end; i++) result.add(i)\n } else {\n const page = parseInt(trimmed, 10)\n if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page)\n }\n }\n\n return result\n}\n","/** HWP 5.x 레코드 리더, UTF-16LE 텍스트 추출, 스트림 압축해제 */\n\nimport { inflateRawSync, inflateSync } from \"zlib\"\nimport { KordocError } from \"../utils.js\"\n\n// ─── 레코드 태그 상수 ────────────────────────────────\n\nexport const TAG_PARA_HEADER = 0x0042\nexport const TAG_PARA_TEXT = 0x0043\nexport const TAG_CHAR_SHAPE = 0x0044\nexport const TAG_PARA_SHAPE = 0x0045\nexport const TAG_CTRL_HEADER = 0x0047\nexport const TAG_LIST_HEADER = 0x0048\nexport const TAG_TABLE = 0x004d\n\n// DocInfo 태그 (스타일 정보 해석용)\nexport const TAG_ID_MAPPINGS = 0x0032\nexport const TAG_FACE_NAME = 0x0033\nexport const TAG_DOC_CHAR_SHAPE = 0x0037\nexport const TAG_DOC_PARA_SHAPE = 0x0039\nexport const TAG_DOC_STYLE = 0x003a\n\n// 특수 문자 코드 (UTF-16LE)\n// HWP 스펙에서 0x0000은 NUL이 아닌 줄바꿈(line break)으로 정의됨\nconst CHAR_LINE = 0x0000\nconst CHAR_PARA = 0x000d\nconst CHAR_TAB = 0x0009\nconst CHAR_HYPHEN = 0x001e\nconst CHAR_NBSP = 0x001f\nconst CHAR_FIXED_NBSP = 0x0018\n\n// FileHeader 플래그\nexport const FLAG_COMPRESSED = 1 << 0\nexport const FLAG_ENCRYPTED = 1 << 1\nexport const FLAG_DRM = 1 << 4\n\n// ─── 레코드 구조 ─────────────────────────────────────\n\nexport interface HwpRecord {\n tagId: number\n level: number\n size: number\n data: Buffer\n}\n\nexport interface HwpFileHeader {\n signature: string\n versionMajor: number\n flags: number\n}\n\n// ─── 레코드 리더 ─────────────────────────────────────\n\n/** 최대 레코드 수 — 비정상 파일에 의한 메모리 폭주 방지 */\nconst MAX_RECORDS = 500_000\n\nexport function readRecords(data: Buffer): HwpRecord[] {\n const records: HwpRecord[] = []\n let offset = 0\n\n while (offset + 4 <= data.length && records.length < MAX_RECORDS) {\n const header = data.readUInt32LE(offset)\n offset += 4\n\n const tagId = header & 0x3ff\n const level = (header >> 10) & 0x3ff\n let size = (header >> 20) & 0xfff\n\n // 확장 크기\n if (size === 0xfff) {\n if (offset + 4 > data.length) break\n size = data.readUInt32LE(offset)\n offset += 4\n }\n\n if (offset + size > data.length) break\n records.push({ tagId, level, size, data: data.subarray(offset, offset + size) })\n offset += size\n }\n\n return records\n}\n\n// ─── 스트림 압축 해제 ────────────────────────────────\n\n/** 압축 해제 최대 크기 (100MB) — decompression bomb 방지 */\nconst MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024\n\nexport function decompressStream(data: Buffer): Buffer {\n const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE }\n if (data.length >= 2 && data[0] === 0x78) {\n try { return inflateSync(data, opts) } catch { /* fallback to raw */ }\n }\n return inflateRawSync(data, opts)\n}\n\n// ─── FileHeader 파싱 ─────────────────────────────────\n\nexport function parseFileHeader(data: Buffer): HwpFileHeader {\n if (data.length < 40) throw new KordocError(\"FileHeader가 너무 짧습니다 (최소 40바이트)\")\n const sig = data.subarray(0, 32).toString(\"utf8\").replace(/\\0+$/, \"\")\n return {\n signature: sig,\n versionMajor: data[35],\n flags: data.readUInt32LE(36),\n }\n}\n\n// ─── 스타일 정보 구조 ────────────────────────────────\n\n/** DocInfo에서 추출한 글자 모양 (CHAR_SHAPE) */\nexport interface HwpCharShape {\n /** 글꼴 크기 (단위: 0.1pt, 예: 100 = 10pt) */\n fontSize: number\n /**\n * 속성 플래그 (HWP5 바이너리 스펙 1.1 기준):\n * bit 0 = italic, bit 1 = bold, bit 2 = underline, bit 3 = outline\n * 검증 완료: 공식 스펙 + pyhwp/hwp.js 등 오픈소스 파서와 일치 (v1.7)\n */\n attrFlags: number\n}\n\n/** DocInfo에서 추출한 스타일 */\nexport interface HwpStyle {\n name: string\n /** 한글 이름 (UTF-16LE) */\n nameKo: string\n /** 연결된 charShape 인덱스 */\n charShapeId: number\n /** 연결된 paraShape 인덱스 */\n paraShapeId: number\n /** 스타일 타입: 0=paragraph, 1=character */\n type: number\n}\n\n/** DocInfo 파싱 결과 */\nexport interface HwpDocInfo {\n charShapes: HwpCharShape[]\n styles: HwpStyle[]\n}\n\n/** DocInfo 레코드들에서 스타일 정보 추출 */\nexport function parseDocInfo(records: HwpRecord[]): HwpDocInfo {\n const charShapes: HwpCharShape[] = []\n const styles: HwpStyle[] = []\n\n for (const rec of records) {\n if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {\n // HWP5 CHAR_SHAPE 구조 (바이너리 스펙 1.1 기준):\n // faceId: 7개 언어 * u16 = 14바이트 (offset 0-13)\n // ratio: 7개 언어 * u8 = 7바이트 (offset 14-20)\n // spacing: 7개 언어 * s8 = 7바이트 (offset 21-27)\n // relSize: 7개 언어 * u8 = 7바이트 (offset 28-34)\n // charOffset: 7개 언어 * s8 = 7바이트 (offset 35-41)\n // baseSize: u32 at offset 42 (단위: 0.1pt)\n // attrFlags: u32 at offset 46 (bit0=italic, bit1=bold) — 공식 스펙 검증 완료\n if (rec.data.length >= 50) {\n const fontSize = rec.data.readUInt32LE(42) // 단위: 0.1pt\n const attrFlags = rec.data.readUInt32LE(46)\n charShapes.push({ fontSize, attrFlags })\n } else {\n // 짧은 레코드 — 스타일 정보 없음\n charShapes.push({ fontSize: 0, attrFlags: 0 })\n }\n }\n\n if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {\n try {\n // STYLE 구조: nameLen(u16) + name(UTF-16LE) + nameKoLen(u16) + nameKo(UTF-16LE)\n // + type(u8) + nextStyleId(u16) + langId(s16) + paraShapeId(u16) + charShapeId(u16)\n let offset = 0\n const nameLen = rec.data.readUInt16LE(offset); offset += 2\n const nameBytes = nameLen * 2\n const name = nameBytes > 0 && offset + nameBytes <= rec.data.length\n ? rec.data.subarray(offset, offset + nameBytes).toString(\"utf16le\")\n : \"\"\n offset += nameBytes\n\n let nameKo = \"\"\n if (offset + 2 <= rec.data.length) {\n const nameKoLen = rec.data.readUInt16LE(offset); offset += 2\n const nameKoBytes = nameKoLen * 2\n if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {\n nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString(\"utf16le\")\n }\n offset += nameKoBytes\n }\n\n // type(u8) + nextStyleId(u16) + langId(s16) + paraShapeId(u16) + charShapeId(u16)\n const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0; offset += 1\n offset += 2 // nextStyleId\n offset += 2 // langId\n const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0; offset += 2\n const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0\n\n styles.push({ name, nameKo, charShapeId, paraShapeId, type })\n } catch {\n // 파싱 실패 — 스킵\n }\n }\n }\n\n return { charShapes, styles }\n}\n\n// ─── UTF-16LE 텍스트 추출 (21가지 제어문자 처리) ─────\n\nexport function extractText(data: Buffer): string {\n let result = \"\"\n let i = 0\n\n while (i + 1 < data.length) {\n const ch = data.readUInt16LE(i)\n i += 2\n\n switch (ch) {\n case CHAR_LINE: result += \"\\n\"; break\n case CHAR_PARA: break\n case CHAR_TAB:\n result += \"\\t\"\n // TAB(0x0009)은 인라인 컨트롤(ch 4-9)로 14바이트 확장 데이터가 뒤따름\n if (i + 14 <= data.length) i += 14\n break\n case CHAR_HYPHEN: result += \"-\"; break\n case CHAR_NBSP: case CHAR_FIXED_NBSP: result += \" \"; break\n default:\n if (ch >= 0x0001 && ch <= 0x001f) {\n const isExt = (ch >= 1 && ch <= 3) || (ch >= 10 && ch <= 18) || (ch >= 21 && ch <= 23)\n const isInline = (ch >= 4 && ch <= 9) || (ch >= 19 && ch <= 20)\n if ((isExt || isInline) && i + 14 <= data.length) i += 14\n } else if (ch >= 0x0020) {\n // UTF-16 surrogate pair 처리 (BMP 외 문자: 이모지, CJK 확장 등)\n if (ch >= 0xd800 && ch <= 0xdbff && i + 1 < data.length) {\n const lo = data.readUInt16LE(i)\n if (lo >= 0xdc00 && lo <= 0xdfff) {\n i += 2\n const codePoint = ((ch - 0xd800) << 10) + (lo - 0xdc00) + 0x10000\n result += String.fromCodePoint(codePoint)\n break\n }\n }\n result += String.fromCharCode(ch)\n }\n break\n }\n }\n\n return result\n}\n","/** HWP 5.x 바이너리 파서 — OLE2 컨테이너 → 섹션 → Markdown */\n\nimport {\n readRecords, decompressStream, parseFileHeader, extractText, parseDocInfo,\n TAG_PARA_HEADER, TAG_PARA_TEXT, TAG_CHAR_SHAPE, TAG_CTRL_HEADER, TAG_LIST_HEADER, TAG_TABLE,\n FLAG_COMPRESSED, FLAG_ENCRYPTED, FLAG_DRM,\n type HwpRecord, type HwpDocInfo, type HwpCharShape,\n} from \"./record.js\"\nimport { buildTable, blocksToMarkdown, MAX_COLS, MAX_ROWS } from \"../table/builder.js\"\nimport type { CellContext, IRBlock, DocumentMetadata, InternalParseResult, ParseOptions, ParseWarning, OutlineItem, InlineStyle, ExtractedImage } from \"../types.js\"\nimport { KordocError } from \"../utils.js\"\nimport { parsePageRange } from \"../page-range.js\"\n\nimport { createRequire } from \"module\"\nconst require = createRequire(import.meta.url)\nconst CFB: CfbModule = require(\"cfb\")\n\ninterface CfbEntry { name?: string; content?: Buffer | Uint8Array }\ninterface CfbContainer { FileIndex?: CfbEntry[] }\ninterface CfbModule {\n parse(data: Buffer): CfbContainer\n find(cfb: CfbContainer, path: string): CfbEntry | null\n}\n\n/** 최대 섹션 수 — 비정상 파일에 의한 무한 루프 방지 */\nconst MAX_SECTIONS = 100\n/** 누적 압축 해제 최대 크기 (100MB) */\nconst MAX_TOTAL_DECOMPRESS = 100 * 1024 * 1024\n\nexport function parseHwp5Document(buffer: Buffer, options?: ParseOptions): InternalParseResult {\n const cfb = CFB.parse(buffer)\n\n const headerEntry = CFB.find(cfb, \"/FileHeader\")\n if (!headerEntry?.content) throw new KordocError(\"FileHeader 스트림 없음\")\n const header = parseFileHeader(Buffer.from(headerEntry.content))\n if (header.signature !== \"HWP Document File\") throw new KordocError(\"HWP 시그니처 불일치\")\n if (header.flags & FLAG_ENCRYPTED) throw new KordocError(\"암호화된 HWP는 지원하지 않습니다\")\n if (header.flags & FLAG_DRM) throw new KordocError(\"DRM 보호된 HWP는 지원하지 않습니다\")\n const compressed = (header.flags & FLAG_COMPRESSED) !== 0\n\n const metadata: DocumentMetadata = {\n version: `${header.versionMajor}.x`,\n }\n extractHwp5Metadata(cfb, metadata)\n\n // DocInfo 파싱 (스타일 정보 추출)\n const docInfo = parseDocInfoStream(cfb, compressed)\n const warnings: ParseWarning[] = []\n\n const sections = findSections(cfb)\n if (sections.length === 0) throw new KordocError(\"섹션 스트림을 찾을 수 없습니다\")\n\n metadata.pageCount = sections.length\n\n // 페이지 범위 필터링 (섹션 단위 근사치)\n const pageFilter = options?.pages ? parsePageRange(options.pages, sections.length) : null\n const totalTarget = pageFilter ? pageFilter.size : sections.length\n\n const blocks: IRBlock[] = []\n let totalDecompressed = 0\n let parsedSections = 0\n for (let si = 0; si < sections.length; si++) {\n if (pageFilter && !pageFilter.has(si + 1)) continue\n try {\n const sectionData = sections[si]\n const data = compressed ? decompressStream(Buffer.from(sectionData)) : Buffer.from(sectionData)\n totalDecompressed += data.length\n if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError(\"총 압축 해제 크기 초과 (decompression bomb 의심)\")\n const records = readRecords(data)\n const sectionBlocks = parseSection(records, docInfo, warnings, si + 1)\n blocks.push(...sectionBlocks)\n parsedSections++\n options?.onProgress?.(parsedSections, totalTarget)\n } catch (secErr) {\n if (secErr instanceof KordocError) throw secErr\n warnings.push({ page: si + 1, message: `섹션 ${si + 1} 파싱 실패: ${secErr instanceof Error ? secErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\n }\n }\n\n // BinData에서 이미지 추출\n const images = extractHwp5Images(cfb, blocks, compressed, warnings)\n\n // 스타일 기반 헤딩 감지\n if (docInfo) {\n detectHwp5Headings(blocks, docInfo)\n }\n\n // outline 구축\n const outline: OutlineItem[] = blocks\n .filter(b => b.type === \"heading\" && b.level && b.text)\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\n\n const markdown = blocksToMarkdown(blocks)\n return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined, images: images.length > 0 ? images : undefined }\n}\n\n/** DocInfo 스트림 파싱 (best-effort) */\nfunction parseDocInfoStream(cfb: CfbContainer, compressed: boolean): HwpDocInfo | null {\n try {\n const entry = CFB.find(cfb, \"/DocInfo\")\n if (!entry?.content) return null\n const data = compressed ? decompressStream(Buffer.from(entry.content)) : Buffer.from(entry.content)\n const records = readRecords(data)\n return parseDocInfo(records)\n } catch {\n return null\n }\n}\n\n/** 스타일 기반 헤딩 감지 — 큰 폰트 + 짧은 텍스트 → heading */\nfunction detectHwp5Headings(blocks: IRBlock[], docInfo: HwpDocInfo): void {\n // 기본 폰트 크기 결정 (본문 스타일 또는 가장 많이 사용되는 크기)\n let baseFontSize = 0\n\n // \"바탕글\", \"본문\" 등 본문 스타일 찾기\n for (const style of docInfo.styles) {\n const name = (style.nameKo || style.name).toLowerCase()\n if (name.includes(\"바탕\") || name.includes(\"본문\") || name === \"normal\" || name === \"body\") {\n const cs = docInfo.charShapes[style.charShapeId]\n // cs.fontSize는 0.1pt 단위 → pt로 변환 (블록의 style.fontSize와 동일 단위)\n if (cs?.fontSize > 0) { baseFontSize = cs.fontSize / 10; break }\n }\n }\n\n // 본문 스타일 못 찾으면 블록의 폰트 크기 중 최빈값 사용\n if (baseFontSize === 0) {\n const sizeFreq = new Map<number, number>()\n for (const b of blocks) {\n if (b.style?.fontSize) {\n sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1)\n }\n }\n let maxCount = 0\n for (const [size, count] of sizeFreq) {\n if (count > maxCount) { maxCount = count; baseFontSize = size }\n }\n }\n\n if (baseFontSize <= 0) return\n\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontSize) continue\n const text = block.text.trim()\n if (text.length === 0 || text.length > 200) continue\n if (/^\\d+$/.test(text)) continue\n\n const ratio = block.style.fontSize / baseFontSize\n let level = 0\n // 통일된 threshold: PDF/HWPX와 동일 (1.5/1.3/1.15)\n if (ratio >= 1.5) level = 1\n else if (ratio >= 1.3) level = 2\n else if (ratio >= 1.15) level = 3\n\n // \"제N조\", \"제N장\" 패턴은 heading으로 강제 지정\n if (/^제\\d+[조장절편]/.test(text) && text.length <= 50) {\n if (level === 0) level = 3\n }\n\n if (level > 0) {\n block.type = \"heading\"\n block.level = level\n }\n }\n}\n\n// ─── 메타데이터 추출 (best-effort) ───────────────────\n\n/**\n * OLE2 SummaryInformation 스트림에서 제목/작성자 추출.\n * HWP5는 \\005HwpSummaryInformation 또는 \\005SummaryInformation에 저장.\n * OLE2 Property Set 포맷의 간이 파싱 — 실패 시 조용히 무시.\n */\nfunction extractHwp5Metadata(cfb: CfbContainer, metadata: DocumentMetadata): void {\n try {\n // HWP 전용 SummaryInformation 먼저, 없으면 표준 OLE2\n const summaryEntry =\n CFB.find(cfb, \"/\\x05HwpSummaryInformation\") ||\n CFB.find(cfb, \"/\\x05SummaryInformation\")\n if (!summaryEntry?.content) return\n\n const data = Buffer.from(summaryEntry.content)\n if (data.length < 48) return\n\n // OLE2 Property Set Header: byte order(2) + version(2) + OS(4) + CLSID(16) + numSets(4) = 28\n // Then FMTID(16) + offset(4)\n const numSets = data.readUInt32LE(24)\n if (numSets === 0) return\n\n const setOffset = data.readUInt32LE(44)\n if (setOffset >= data.length - 8) return\n\n // Property Set: size(4) + numProperties(4) + [propertyId(4) + offset(4)] * N\n const numProps = data.readUInt32LE(setOffset + 4)\n if (numProps === 0 || numProps > 100) return\n\n for (let i = 0; i < numProps; i++) {\n const entryOffset = setOffset + 8 + i * 8\n if (entryOffset + 8 > data.length) break\n\n const propId = data.readUInt32LE(entryOffset)\n const propOffset = setOffset + data.readUInt32LE(entryOffset + 4)\n if (propOffset + 8 > data.length) continue\n\n // Property ID: 2=Title, 4=Author, 6=Subject/Description\n if (propId !== 2 && propId !== 4 && propId !== 6) continue\n\n const propType = data.readUInt32LE(propOffset)\n // Type 0x1E = VT_LPSTR (ANSI string)\n if (propType !== 0x1e) continue\n\n const strLen = data.readUInt32LE(propOffset + 4)\n if (strLen === 0 || strLen > 10000 || propOffset + 8 + strLen > data.length) continue\n\n const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString(\"utf8\").replace(/\\0+$/, \"\").trim()\n if (!str) continue\n\n if (propId === 2) metadata.title = str\n else if (propId === 4) metadata.author = str\n else if (propId === 6) metadata.description = str\n }\n } catch {\n // best-effort — 실패 시 조용히 무시\n }\n}\n\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\nexport function extractHwp5MetadataOnly(buffer: Buffer): DocumentMetadata {\n const cfb = CFB.parse(buffer)\n const headerEntry = CFB.find(cfb, \"/FileHeader\")\n if (!headerEntry?.content) throw new KordocError(\"FileHeader 스트림 없음\")\n const header = parseFileHeader(Buffer.from(headerEntry.content))\n if (header.signature !== \"HWP Document File\") throw new KordocError(\"HWP 시그니처 불일치\")\n\n const metadata: DocumentMetadata = {\n version: `${header.versionMajor}.x`,\n }\n extractHwp5Metadata(cfb, metadata)\n\n const sections = findSections(cfb)\n metadata.pageCount = sections.length\n\n return metadata\n}\n\nfunction findSections(cfb: CfbContainer): Buffer[] {\n const sections: Array<{ idx: number; content: Buffer }> = []\n\n for (let i = 0; i < MAX_SECTIONS; i++) {\n const entry = CFB.find(cfb, `/BodyText/Section${i}`)\n if (!entry?.content) break\n sections.push({ idx: i, content: Buffer.from(entry.content) })\n }\n\n if (sections.length === 0 && cfb.FileIndex) {\n for (const entry of cfb.FileIndex) {\n if (sections.length >= MAX_SECTIONS) break\n if (entry.name?.startsWith(\"Section\") && entry.content) {\n const idx = parseInt(entry.name.replace(\"Section\", \"\"), 10) || 0\n sections.push({ idx, content: Buffer.from(entry.content) })\n }\n }\n }\n\n return sections.sort((a, b) => a.idx - b.idx).map(s => s.content)\n}\n\n// ─── BinData 이미지 추출 ────────────────────────────\n\n/** SHAPE_COMPONENT 태그 — HWP5 스펙 */\nconst TAG_SHAPE_COMPONENT = 0x004a\n\n/** gso 제어 뒤의 하위 레코드에서 binDataId 추출 (best-effort) */\nfunction extractBinDataId(records: HwpRecord[], ctrlIdx: number): number {\n const ctrlLevel = records[ctrlIdx].level\n // CTRL_HEADER 이후의 하위 레코드들을 순회\n for (let j = ctrlIdx + 1; j < records.length && j < ctrlIdx + 50; j++) {\n const r = records[j]\n if (r.level <= ctrlLevel) break // 같은/상위 레벨이면 이 제어 블록 끝\n // SHAPE_COMPONENT에서 picture 타입이면 binDataId 추출\n // picture 데이터는 SHAPE_COMPONENT 뒤에 오는 하위 레코드에 있음\n // HWP5에서 그림 정보는 level이 높은 하위 레코드에 binDataId가 uint16LE로 저장\n if (r.data.length >= 2) {\n // 매직바이트로 이미지인지 확인하는 대신, SHAPE_COMPONENT 뒤의 하위 레코드에서 binDataId를 읽음\n // HWP5 picture 구조: CTRL_HEADER(gso) → LIST_HEADER → SHAPE_COMPONENT → [picture data record]\n // picture data record에서 offset 0부터 uint16LE = binDataId\n if (r.tagId > TAG_SHAPE_COMPONENT && r.level > ctrlLevel + 1 && r.data.length >= 4) {\n const possibleId = r.data.readUInt16LE(0)\n if (possibleId < 10000) return possibleId // 합리적 범위\n }\n }\n }\n return -1\n}\n\n/** MIME 타입 매직바이트 판별 */\nfunction detectImageMime(data: Buffer | Uint8Array): string | null {\n if (data.length < 4) return null\n if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) return \"image/png\"\n if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) return \"image/jpeg\"\n if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) return \"image/gif\"\n if (data[0] === 0x42 && data[1] === 0x4d) return \"image/bmp\"\n if (data[0] === 0xd7 && data[1] === 0xcd && data[2] === 0xc6 && data[3] === 0x9a) return \"image/wmf\"\n if (data[0] === 0x01 && data[1] === 0x00 && data[2] === 0x00 && data[3] === 0x00) return \"image/emf\"\n return null\n}\n\n/** OLE2 BinData 스토리지에서 이미지 추출, blocks의 image 블록과 매핑 */\nfunction extractHwp5Images(\n cfb: CfbContainer,\n blocks: IRBlock[],\n compressed: boolean,\n warnings: ParseWarning[],\n): ExtractedImage[] {\n // BinData 스토리지의 모든 파일을 인덱스순으로 나열\n const binDataMap = new Map<number, { data: Buffer; name: string }>()\n for (let idx = 0; idx < 10000; idx++) {\n const entry = CFB.find(cfb, `/BinData/BIN${String(idx).padStart(4, \"0\")}`)\n || CFB.find(cfb, `/BinData/Bin${String(idx).padStart(4, \"0\")}`)\n if (!entry?.content) {\n if (idx > 0) break // 연속된 인덱스가 끝나면 중단\n continue\n }\n let data = Buffer.from(entry.content)\n // compressed 플래그가 있으면 BinData도 압축됨\n if (compressed) {\n try { data = decompressStream(data) } catch { /* 이미 비압축일 수 있음 */ }\n }\n binDataMap.set(idx, { data, name: entry.name || `BIN${idx}` })\n }\n\n if (binDataMap.size === 0) return []\n\n const images: ExtractedImage[] = []\n let imageIndex = 0\n\n for (const block of blocks) {\n if (block.type !== \"image\" || !block.text) continue\n const binId = parseInt(block.text, 10)\n if (isNaN(binId)) continue\n\n const bin = binDataMap.get(binId)\n if (!bin) {\n warnings.push({ page: block.pageNumber, message: `BinData ${binId} 없음`, code: \"SKIPPED_IMAGE\" })\n block.type = \"paragraph\"\n block.text = `[이미지: BinData ${binId}]`\n continue\n }\n\n const mime = detectImageMime(bin.data)\n if (!mime) {\n warnings.push({ page: block.pageNumber, message: `BinData ${binId}: 알 수 없는 이미지 형식`, code: \"SKIPPED_IMAGE\" })\n block.type = \"paragraph\"\n block.text = `[이미지: ${bin.name}]`\n continue\n }\n\n imageIndex++\n const ext = mime.includes(\"jpeg\") ? \"jpg\" : mime.includes(\"png\") ? \"png\" : mime.includes(\"gif\") ? \"gif\" : mime.includes(\"bmp\") ? \"bmp\" : \"bin\"\n const filename = `image_${String(imageIndex).padStart(3, \"0\")}.${ext}`\n\n images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime })\n block.text = filename\n block.imageData = { data: new Uint8Array(bin.data), mimeType: mime, filename: bin.name }\n }\n\n return images\n}\n\nfunction parseSection(records: HwpRecord[], docInfo: HwpDocInfo | null, warnings: ParseWarning[], sectionNum: number): IRBlock[] {\n const blocks: IRBlock[] = []\n let i = 0\n\n while (i < records.length) {\n const rec = records[i]\n\n if (rec.tagId === TAG_PARA_HEADER && rec.level === 0) {\n const { paragraph, tables, nextIdx, charShapeIds } = parseParagraphWithTables(records, i)\n if (paragraph) {\n const block: IRBlock = { type: \"paragraph\", text: paragraph, pageNumber: sectionNum }\n // CHAR_SHAPE 기반 스타일 정보 추가\n if (docInfo && charShapeIds.length > 0) {\n const style = resolveCharStyle(charShapeIds, docInfo)\n if (style) block.style = style\n }\n blocks.push(block)\n }\n for (const t of tables) blocks.push({ type: \"table\", table: t, pageNumber: sectionNum })\n i = nextIdx\n continue\n }\n\n if (rec.tagId === TAG_CTRL_HEADER && rec.level <= 1 && rec.data.length >= 4) {\n const ctrlId = rec.data.subarray(0, 4).toString(\"ascii\")\n if (ctrlId === \" lbt\" || ctrlId === \"tbl \") {\n const { table, nextIdx } = parseTableBlock(records, i)\n if (table) blocks.push({ type: \"table\", table, pageNumber: sectionNum })\n i = nextIdx\n continue\n }\n // 이미지/OLE 제어 — binDataId 추출 시도\n if (ctrlId === \"gso \" || ctrlId === \" osg\") {\n const binId = extractBinDataId(records, i)\n if (binId >= 0) {\n blocks.push({ type: \"image\", text: String(binId), pageNumber: sectionNum })\n } else {\n warnings.push({ page: sectionNum, message: `스킵된 제어 요소: ${ctrlId.trim()}`, code: \"SKIPPED_IMAGE\" })\n }\n } else if (ctrlId === \" elo\" || ctrlId === \"ole \") {\n warnings.push({ page: sectionNum, message: `스킵된 제어 요소: ${ctrlId.trim()}`, code: \"SKIPPED_IMAGE\" })\n }\n }\n\n i++\n }\n\n return blocks\n}\n\n/** CHAR_SHAPE ID 배열에서 대표 스타일 결정 (최빈값) */\nfunction resolveCharStyle(charShapeIds: number[], docInfo: HwpDocInfo): InlineStyle | undefined {\n if (charShapeIds.length === 0 || docInfo.charShapes.length === 0) return undefined\n\n // 가장 많이 나타나는 charShapeId 사용\n const freq = new Map<number, number>()\n let maxCount = 0, dominantId = charShapeIds[0]\n for (const id of charShapeIds) {\n const count = (freq.get(id) || 0) + 1\n freq.set(id, count)\n if (count > maxCount) { maxCount = count; dominantId = id }\n }\n\n const cs = docInfo.charShapes[dominantId]\n if (!cs) return undefined\n\n const style: InlineStyle = {}\n if (cs.fontSize > 0) style.fontSize = cs.fontSize / 10 // 0.1pt → pt\n if (cs.attrFlags & 0x01) style.italic = true\n if (cs.attrFlags & 0x02) style.bold = true\n\n return (style.fontSize || style.bold || style.italic) ? style : undefined\n}\n\nfunction parseParagraphWithTables(records: HwpRecord[], startIdx: number) {\n const startLevel = records[startIdx].level\n let text = \"\"\n const tables: ReturnType<typeof buildTable>[] = []\n const charShapeIds: number[] = []\n let i = startIdx + 1\n\n while (i < records.length) {\n const rec = records[i]\n if (rec.tagId === TAG_PARA_HEADER && rec.level <= startLevel) break\n\n if (rec.tagId === TAG_PARA_TEXT) {\n text = extractText(rec.data)\n }\n\n // CHAR_SHAPE 레코드 — 문단 내 글자 모양 인덱스 배열\n if (rec.tagId === TAG_CHAR_SHAPE && rec.data.length >= 8) {\n // 구조: [position(u32) + charShapeId(u32)] * N\n for (let offset = 0; offset + 7 < rec.data.length; offset += 8) {\n charShapeIds.push(rec.data.readUInt32LE(offset + 4))\n }\n }\n\n if (rec.tagId === TAG_CTRL_HEADER && rec.data.length >= 4) {\n const ctrlId = rec.data.subarray(0, 4).toString(\"ascii\")\n if (ctrlId === \" lbt\" || ctrlId === \"tbl \") {\n const { table, nextIdx } = parseTableBlock(records, i)\n if (table) tables.push(table)\n i = nextIdx\n continue\n }\n }\n i++\n }\n\n const trimmed = text.trim()\n return { paragraph: trimmed || null, tables, nextIdx: i, charShapeIds }\n}\n\nfunction parseTableBlock(records: HwpRecord[], startIdx: number) {\n const tableLevel = records[startIdx].level\n let i = startIdx + 1\n let rows = 0, cols = 0\n const cells: CellContext[] = []\n\n while (i < records.length) {\n const rec = records[i]\n if (rec.tagId === TAG_PARA_HEADER && rec.level <= tableLevel) break\n if (rec.tagId === TAG_CTRL_HEADER && rec.level <= tableLevel) break\n\n if (rec.tagId === TAG_TABLE && rec.data.length >= 8) {\n rows = Math.min(rec.data.readUInt16LE(4), MAX_ROWS)\n cols = Math.min(rec.data.readUInt16LE(6), MAX_COLS)\n }\n\n if (rec.tagId === TAG_LIST_HEADER) {\n const { cell, nextIdx } = parseCellBlock(records, i, tableLevel)\n if (cell) cells.push(cell)\n i = nextIdx\n continue\n }\n i++\n }\n\n if (rows === 0 || cols === 0 || cells.length === 0) return { table: null, nextIdx: i }\n\n const cellRows = arrangeCells(rows, cols, cells)\n return { table: buildTable(cellRows), nextIdx: i }\n}\n\nfunction parseCellBlock(records: HwpRecord[], startIdx: number, tableLevel: number) {\n const rec = records[startIdx]\n const cellLevel = rec.level\n const texts: string[] = []\n\n // LIST_HEADER에서 셀 위치 및 병합 정보 추출\n // HWP5 셀 LIST_HEADER 구조:\n // paraCount(u16) + flags(u32) + width(u16) + colAddr(u16) + rowAddr(u16) + colSpan(u16) + rowSpan(u16)\n // offset: 0 2 6 8 10 12 14\n let colSpan = 1\n let rowSpan = 1\n let colAddr: number | undefined\n let rowAddr: number | undefined\n if (rec.data.length >= 16) {\n colAddr = rec.data.readUInt16LE(8)\n rowAddr = rec.data.readUInt16LE(10)\n const cs = rec.data.readUInt16LE(12)\n const rs = rec.data.readUInt16LE(14)\n if (cs > 0) colSpan = Math.min(cs, MAX_COLS)\n if (rs > 0) rowSpan = Math.min(rs, MAX_ROWS)\n }\n\n let i = startIdx + 1\n\n while (i < records.length) {\n const r = records[i]\n if (r.tagId === TAG_LIST_HEADER && r.level <= cellLevel) break\n if (r.level <= tableLevel && (r.tagId === TAG_PARA_HEADER || r.tagId === TAG_CTRL_HEADER)) break\n\n if (r.tagId === TAG_PARA_TEXT) {\n const t = extractText(r.data).trim()\n if (t) texts.push(t)\n }\n i++\n }\n\n return { cell: { text: texts.join(\"\\n\"), colSpan, rowSpan, colAddr, rowAddr } as CellContext, nextIdx: i }\n}\n\nfunction arrangeCells(rows: number, cols: number, cells: CellContext[]): CellContext[][] {\n const grid: (CellContext | null)[][] = Array.from({ length: rows }, () => Array(cols).fill(null))\n\n // colAddr/rowAddr가 있으면 직접 배치 (HWP5 병합 테이블 정확도 향상)\n const hasAddr = cells.some(c => c.colAddr !== undefined && c.rowAddr !== undefined)\n\n if (hasAddr) {\n for (const cell of cells) {\n const r = cell.rowAddr ?? 0\n const c = cell.colAddr ?? 0\n if (r >= rows || c >= cols) continue\n grid[r][c] = cell\n\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n if (r + dr < rows && c + dc < cols)\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\n }\n }\n }\n } else {\n // fallback: 순차 배치 (colAddr 없는 경우)\n let cellIdx = 0\n for (let r = 0; r < rows && cellIdx < cells.length; r++) {\n for (let c = 0; c < cols && cellIdx < cells.length; c++) {\n if (grid[r][c] !== null) continue\n const cell = cells[cellIdx++]\n grid[r][c] = cell\n\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n if (r + dr < rows && c + dc < cols)\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\n }\n }\n }\n }\n }\n\n return grid.map(row => row.map(c => c || { text: \"\", colSpan: 1, rowSpan: 1 }))\n}\n","/**\n * PDF 그래픽 명령에서 수평/수직 선을 추출하고,\n * 선 교차점 기반으로 테이블 그리드를 구성하는 모듈.\n *\n * ODL(OpenDataLoader) TableBorderBuilder 알고리즘을 TypeScript로 포팅.\n * pdfjs-dist의 getOperatorList() 결과를 입력으로 받음.\n */\n\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\n\n// ─── 타입 ─────────────────────────────────────────────\n\nexport interface LineSegment {\n x1: number; y1: number\n x2: number; y2: number\n lineWidth: number\n}\n\nexport interface TableGrid {\n /** 행 Y 좌표 경계 (위→아래 내림차순) */\n rowYs: number[]\n /** 열 X 좌표 경계 (좌→우 오름차순) */\n colXs: number[]\n /** 테이블 바운딩 박스 */\n bbox: { x1: number; y1: number; x2: number; y2: number }\n}\n\nexport interface ExtractedCell {\n row: number; col: number\n rowSpan: number; colSpan: number\n /** 셀 바운딩 박스 */\n bbox: { x1: number; y1: number; x2: number; y2: number }\n}\n\n// ─── 상수 ─────────────────────────────────────────────\n\n/** 수평/수직 판별 허용 오차 (pt) */\nconst ORIENTATION_TOL = 2\n/** 최소 선 길이 (너무 짧은 장식선 무시) */\nconst MIN_LINE_LENGTH = 10\n/** 좌표 병합 tolerance — ODL: 4 * vertexRadius, 여기선 고정 3pt */\nconst COORD_MERGE_TOL = 3\n/** 두 선이 같은 테이블에 속하는지 판별하는 거리 */\nconst CONNECT_TOL = 5\n/** 셀 경계 내부 판별 여유 (텍스트 매핑용) */\nconst CELL_PADDING = 2\n\n// ─── 선 추출 ──────────────────────────────────────────\n\n/**\n * pdfjs operatorList에서 수평/수직 선을 추출.\n * constructPath(91) 내의 moveTo→lineTo, rectangle 패턴을 인식.\n */\nexport function extractLines(\n fnArray: Uint32Array | number[],\n argsArray: unknown[][],\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const horizontals: LineSegment[] = []\n const verticals: LineSegment[] = []\n let lineWidth = 1\n\n // 현재 path의 세그먼트를 수집\n let currentPath: Array<{ x1: number; y1: number; x2: number; y2: number }> = []\n let pathStartX = 0, pathStartY = 0\n let curX = 0, curY = 0\n\n function flushPath(isStroke: boolean) {\n if (!isStroke) { currentPath = []; return }\n for (const seg of currentPath) {\n classifyAndAdd(seg, lineWidth, horizontals, verticals)\n }\n currentPath = []\n }\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n const args = argsArray[i]\n\n switch (op) {\n case OPS.setLineWidth:\n lineWidth = (args as number[])[0] || 1\n break\n\n case OPS.constructPath: {\n const subOps = (args as [number[], number[], number[]])[0]\n const coords = (args as [number[], number[], number[]])[1]\n let ci = 0\n\n for (const subOp of subOps) {\n if (subOp === OPS.moveTo) {\n curX = coords[ci++]; curY = coords[ci++]\n pathStartX = curX; pathStartY = curY\n } else if (subOp === OPS.lineTo) {\n const x2 = coords[ci++], y2 = coords[ci++]\n currentPath.push({ x1: curX, y1: curY, x2, y2 })\n curX = x2; curY = y2\n } else if (subOp === OPS.rectangle) {\n const rx = coords[ci++], ry = coords[ci++]\n const rw = coords[ci++], rh = coords[ci++]\n // 사각형 → 4변 (매우 얇으면 선 1개로)\n if (Math.abs(rh) < ORIENTATION_TOL * 2) {\n // 얇은 수평 사각형 → 수평선\n currentPath.push({ x1: rx, y1: ry + rh / 2, x2: rx + rw, y2: ry + rh / 2 })\n } else if (Math.abs(rw) < ORIENTATION_TOL * 2) {\n // 얇은 수직 사각형 → 수직선\n currentPath.push({ x1: rx + rw / 2, y1: ry, x2: rx + rw / 2, y2: ry + rh })\n } else {\n // 일반 사각형 → 4변\n currentPath.push(\n { x1: rx, y1: ry, x2: rx + rw, y2: ry }, // bottom\n { x1: rx + rw, y1: ry, x2: rx + rw, y2: ry + rh }, // right\n { x1: rx + rw, y1: ry + rh, x2: rx, y2: ry + rh }, // top\n { x1: rx, y1: ry + rh, x2: rx, y2: ry }, // left\n )\n }\n } else if (subOp === OPS.closePath) {\n if (curX !== pathStartX || curY !== pathStartY) {\n currentPath.push({ x1: curX, y1: curY, x2: pathStartX, y2: pathStartY })\n }\n curX = pathStartX; curY = pathStartY\n } else if (subOp === OPS.curveTo) {\n ci += 6 // skip control points\n } else if (subOp === OPS.curveTo2 || subOp === OPS.curveTo3) {\n ci += 4\n }\n }\n break\n }\n\n case OPS.stroke:\n case OPS.closeStroke:\n flushPath(true)\n break\n\n case OPS.fill:\n case OPS.eoFill:\n case OPS.fillStroke:\n case OPS.eoFillStroke:\n case OPS.closeFillStroke:\n case OPS.closeEOFillStroke:\n // fill된 사각형도 테이블 선으로 처리 (셀 배경이 아닌 경우)\n flushPath(true)\n break\n\n case OPS.endPath:\n flushPath(false)\n break\n }\n }\n\n return { horizontals, verticals }\n}\n\nfunction classifyAndAdd(\n seg: { x1: number; y1: number; x2: number; y2: number },\n lineWidth: number,\n horizontals: LineSegment[],\n verticals: LineSegment[],\n) {\n const dx = Math.abs(seg.x2 - seg.x1)\n const dy = Math.abs(seg.y2 - seg.y1)\n const length = Math.sqrt(dx * dx + dy * dy)\n\n if (length < MIN_LINE_LENGTH) return\n\n if (dy <= ORIENTATION_TOL) {\n // 수평선\n const y = (seg.y1 + seg.y2) / 2\n const x1 = Math.min(seg.x1, seg.x2)\n const x2 = Math.max(seg.x1, seg.x2)\n horizontals.push({ x1, y1: y, x2, y2: y, lineWidth })\n } else if (dx <= ORIENTATION_TOL) {\n // 수직선\n const x = (seg.x1 + seg.x2) / 2\n const y1 = Math.min(seg.y1, seg.y2)\n const y2 = Math.max(seg.y1, seg.y2)\n verticals.push({ x1: x, y1, x2: x, y2, lineWidth })\n }\n // 대각선은 무시 (테이블 경계가 아님)\n}\n\n// ─── 페이지 경계(클립) 선 필터링 ──────────────────────\n\n/** 페이지 전체를 감싸는 클립 경계 선 제거 */\nexport function filterPageBorderLines(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n pageWidth: number,\n pageHeight: number,\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const margin = 5\n return {\n horizontals: horizontals.filter(l =>\n !(Math.abs(l.y1) < margin || Math.abs(l.y1 - pageHeight) < margin) ||\n (l.x2 - l.x1) < pageWidth * 0.9\n ),\n verticals: verticals.filter(l =>\n !(Math.abs(l.x1) < margin || Math.abs(l.x1 - pageWidth) < margin) ||\n (l.y2 - l.y1) < pageHeight * 0.9\n ),\n }\n}\n\n// ─── 테이블 그리드 구성 ────────────────────────────────\n\n/**\n * 수평/수직 선에서 테이블 그리드를 추출.\n * 1. 교차하는 선들을 그룹화 (연결 컴포넌트)\n * 2. 각 그룹에서 X/Y 좌표를 클러스터링하여 그리드 구성\n */\nexport function buildTableGrids(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): TableGrid[] {\n if (horizontals.length < 2 || verticals.length < 2) return []\n\n // 1. 선들을 교차 관계로 그룹화\n const allLines = [\n ...horizontals.map((l, i) => ({ ...l, type: \"h\" as const, id: i })),\n ...verticals.map((l, i) => ({ ...l, type: \"v\" as const, id: i + horizontals.length })),\n ]\n\n const groups = groupConnectedLines(allLines)\n\n const grids: TableGrid[] = []\n\n for (const group of groups) {\n const hLines = group.filter(l => l.type === \"h\")\n const vLines = group.filter(l => l.type === \"v\")\n\n // 최소 2개 수평 + 2개 수직 선이 있어야 테이블\n if (hLines.length < 2 || vLines.length < 2) continue\n\n // 2. Y 좌표 클러스터링 (수평선의 y 값)\n const rawYs = hLines.map(l => l.y1)\n const rowYs = clusterCoordinates(rawYs).sort((a, b) => b - a) // 위→아래 (PDF는 y가 위가 큼)\n\n // 3. X 좌표 클러스터링 (수직선의 x 값)\n const rawXs = vLines.map(l => l.x1)\n const colXs = clusterCoordinates(rawXs).sort((a, b) => a - b)\n\n // 최소 2행 2열\n if (rowYs.length < 2 || colXs.length < 2) continue\n\n // 그리드에 실제로 셀이 형성되는지 검증\n const bbox = {\n x1: colXs[0], y1: rowYs[rowYs.length - 1],\n x2: colXs[colXs.length - 1], y2: rowYs[0],\n }\n\n grids.push({ rowYs, colXs, bbox })\n }\n\n return grids\n}\n\n/** 좌표값 클러스터링 — 가까운 값끼리 병합 */\nfunction clusterCoordinates(values: number[]): number[] {\n if (values.length === 0) return []\n const sorted = [...values].sort((a, b) => a - b)\n const clusters: { sum: number; count: number }[] = [{ sum: sorted[0], count: 1 }]\n\n for (let i = 1; i < sorted.length; i++) {\n const last = clusters[clusters.length - 1]\n const avg = last.sum / last.count\n if (Math.abs(sorted[i] - avg) <= COORD_MERGE_TOL) {\n last.sum += sorted[i]\n last.count++\n } else {\n clusters.push({ sum: sorted[i], count: 1 })\n }\n }\n\n return clusters.map(c => c.sum / c.count)\n}\n\ntype TypedLine = LineSegment & { type: \"h\" | \"v\"; id: number }\n\n/** 교차하는 선들을 Union-Find로 그룹화 */\nfunction groupConnectedLines(lines: TypedLine[]): TypedLine[][] {\n const parent = lines.map((_, i) => i)\n\n function find(x: number): number {\n while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x] }\n return x\n }\n function union(a: number, b: number) {\n const ra = find(a), rb = find(b)\n if (ra !== rb) parent[ra] = rb\n }\n\n // O(n²) 교차 검사 — 선 수가 수백 수준이므로 충분\n for (let i = 0; i < lines.length; i++) {\n for (let j = i + 1; j < lines.length; j++) {\n if (linesIntersect(lines[i], lines[j])) {\n union(i, j)\n }\n }\n }\n\n const groups = new Map<number, TypedLine[]>()\n for (let i = 0; i < lines.length; i++) {\n const root = find(i)\n if (!groups.has(root)) groups.set(root, [])\n groups.get(root)!.push(lines[i])\n }\n\n return [...groups.values()]\n}\n\n/** 수평선과 수직선의 교차 판정 (tolerance 포함) */\nfunction linesIntersect(a: TypedLine, b: TypedLine): boolean {\n // 같은 방향이면 교차 안 함 (평행)\n if (a.type === b.type) {\n // 같은 방향이라도 연결된 선일 수 있음 (끝점이 가까운 경우)\n if (a.type === \"h\") {\n if (Math.abs(a.y1 - b.y1) > CONNECT_TOL) return false\n // X 범위 겹침\n return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - CONNECT_TOL\n } else {\n if (Math.abs(a.x1 - b.x1) > CONNECT_TOL) return false\n return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - CONNECT_TOL\n }\n }\n\n // 수평 + 수직 교차\n const h = a.type === \"h\" ? a : b\n const v = a.type === \"h\" ? b : a\n const tol = CONNECT_TOL\n\n return (\n v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol\n )\n}\n\n// ─── 셀 구조 추출 (colspan/rowspan 감지) ──────────────\n\n/**\n * 테이블 그리드에서 셀 목록을 추출.\n * 수평/수직 선의 존재 여부로 셀 병합(colspan/rowspan)을 감지.\n */\nexport function extractCells(\n grid: TableGrid,\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): ExtractedCell[] {\n const { rowYs, colXs } = grid\n const numRows = rowYs.length - 1\n const numCols = colXs.length - 1\n if (numRows <= 0 || numCols <= 0) return []\n\n // 셀이 이미 병합된 셀에 포함되는지 추적\n const occupied = Array.from({ length: numRows }, () => Array(numCols).fill(false))\n const cells: ExtractedCell[] = []\n\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n if (occupied[r][c]) continue\n\n // 이 셀에서 오른쪽/아래로 병합 가능한 범위 찾기\n let colSpan = 1\n let rowSpan = 1\n\n // colSpan: 오른쪽 경계에 수직선이 없으면 병합\n while (c + colSpan < numCols) {\n const borderX = colXs[c + colSpan]\n const topY = rowYs[r]\n const botY = rowYs[r + 1]\n if (hasVerticalLine(verticals, borderX, topY, botY)) break\n colSpan++\n }\n\n // rowSpan: 아래쪽 경계에 수평선이 없으면 병합\n while (r + rowSpan < numRows) {\n const borderY = rowYs[r + rowSpan]\n const leftX = colXs[c]\n const rightX = colXs[c + colSpan]\n if (hasHorizontalLine(horizontals, borderY, leftX, rightX)) break\n rowSpan++\n }\n\n // 병합 영역 마킹\n for (let dr = 0; dr < rowSpan; dr++) {\n for (let dc = 0; dc < colSpan; dc++) {\n occupied[r + dr][c + dc] = true\n }\n }\n\n cells.push({\n row: r, col: c, rowSpan, colSpan,\n bbox: {\n x1: colXs[c], y1: rowYs[r + rowSpan],\n x2: colXs[c + colSpan], y2: rowYs[r],\n },\n })\n }\n }\n\n return cells\n}\n\n/** 특정 X 위치에 수직선이 Y 범위를 커버하는지 확인 */\nfunction hasVerticalLine(verticals: LineSegment[], x: number, topY: number, botY: number): boolean {\n const tol = COORD_MERGE_TOL + 1\n for (const v of verticals) {\n if (Math.abs(v.x1 - x) <= tol) {\n // 선의 Y 범위가 셀 경계의 상당 부분(50%)을 커버하는지\n const cellH = Math.abs(topY - botY)\n const overlapTop = Math.min(v.y2, topY)\n const overlapBot = Math.max(v.y1, botY)\n const overlap = overlapTop - overlapBot\n if (overlap >= cellH * 0.5) return true\n }\n }\n return false\n}\n\n/** 특정 Y 위치에 수평선이 X 범위를 커버하는지 확인 */\nfunction hasHorizontalLine(horizontals: LineSegment[], y: number, leftX: number, rightX: number): boolean {\n const tol = COORD_MERGE_TOL + 1\n for (const h of horizontals) {\n if (Math.abs(h.y1 - y) <= tol) {\n const cellW = Math.abs(rightX - leftX)\n const overlapLeft = Math.max(h.x1, leftX)\n const overlapRight = Math.min(h.x2, rightX)\n const overlap = overlapRight - overlapLeft\n if (overlap >= cellW * 0.5) return true\n }\n }\n return false\n}\n\n// ─── 텍스트→셀 매핑 ──────────────────────────────────\n\nexport interface TextItem {\n text: string\n x: number; y: number; w: number; h: number\n fontSize: number; fontName: string\n}\n\n/**\n * 텍스트 아이템을 셀에 매핑.\n * 각 텍스트의 중심점이 어떤 셀의 bbox 안에 있는지로 판별.\n */\nexport function mapTextToCells(\n items: TextItem[],\n cells: ExtractedCell[],\n): Map<ExtractedCell, TextItem[]> {\n const result = new Map<ExtractedCell, TextItem[]>()\n for (const cell of cells) {\n result.set(cell, [])\n }\n\n for (const item of items) {\n const cx = item.x + item.w / 2\n const cy = item.y\n const pad = CELL_PADDING\n\n let bestCell: ExtractedCell | null = null\n let bestDist = Infinity\n\n for (const cell of cells) {\n // 중심점이 셀 bbox 안에 있는지 (패딩 포함)\n if (cx >= cell.bbox.x1 - pad && cx <= cell.bbox.x2 + pad &&\n cy >= cell.bbox.y1 - pad && cy <= cell.bbox.y2 + pad) {\n // 여러 셀에 해당하면 가장 가까운 셀 중심에 배정\n const cellCx = (cell.bbox.x1 + cell.bbox.x2) / 2\n const cellCy = (cell.bbox.y1 + cell.bbox.y2) / 2\n const dist = Math.abs(cx - cellCx) + Math.abs(cy - cellCy)\n if (dist < bestDist) {\n bestDist = dist\n bestCell = cell\n }\n }\n }\n\n if (bestCell) {\n result.get(bestCell)!.push(item)\n }\n }\n\n return result\n}\n\n/**\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기.\n * Y 내림차순 (위→아래) → X 오름차순 (좌→우)\n */\nexport function cellTextToString(items: TextItem[]): string {\n if (items.length === 0) return \"\"\n if (items.length === 1) return items[0].text\n\n // Y좌표로 행 그룹핑 (tolerance: max(3, fontSize*0.6))\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const lines: TextItem[][] = []\n let curLine: TextItem[] = [sorted[0]]\n let curY = sorted[0].y\n\n for (let i = 1; i < sorted.length; i++) {\n const tol = Math.max(3, Math.min(sorted[i].fontSize, curLine[0].fontSize) * 0.6)\n if (Math.abs(sorted[i].y - curY) <= tol) {\n curLine.push(sorted[i])\n } else {\n lines.push(curLine)\n curLine = [sorted[i]]\n curY = sorted[i].y\n }\n }\n lines.push(curLine)\n\n // 각 행을 텍스트로 변환 — 한글 간 작은 갭은 공백 없이 붙임\n const textLines = lines.map(line => {\n const s = line.sort((a, b) => a.x - b.x)\n if (s.length === 1) return s[0].text\n let result = s[0].text\n for (let j = 1; j < s.length; j++) {\n const gap = s[j].x - (s[j - 1].x + s[j - 1].w)\n const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2\n // 한글-한글 사이 매우 작은 갭 (< fontSize * 0.3) → PDF 문자 개별 배치 잔재\n if (gap < avgFs * 0.3 && /[가-힣]$/.test(result) && /^[가-힣]/.test(s[j].text)) {\n result += s[j].text\n } else {\n result += \" \" + s[j].text\n }\n }\n return result\n })\n\n // 한국어 줄바꿈 병합: \"전자여\\n권\" → \"전자여권\"\n // 셀 컨텍스트에서는 더 공격적으로: 8자 이하 순수 한글 또는 한글+숫자 단어\n if (textLines.length <= 1) return textLines[0] || \"\"\n const merged: string[] = [textLines[0]]\n for (let i = 1; i < textLines.length; i++) {\n const prev = merged[merged.length - 1]\n const curr = textLines[i]\n // 짧은 순수 한글 (8자 이하, 공백 없음) = 잘린 단어 조각 또는 조사\n if (/[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(\" \")) {\n merged[merged.length - 1] = prev + curr\n } else {\n merged.push(curr)\n }\n }\n return merged.join(\"\\n\")\n}\n","/**\n * 클러스터 기반 테이블 감지 — 선이 없는 PDF에서 텍스트 정렬 패턴으로 테이블 구조 추론.\n *\n * ODL의 ClusterTableConsumer를 kordoc 컨텍스트에 맞게 단순화한 구현.\n *\n * 핵심 아이디어:\n * 1. 텍스트 아이템을 baseline(Y좌표)으로 그룹핑하여 행(row) 구성\n * 2. 각 행의 아이템 X좌표를 수집, 행 간 공통 X좌표 클러스터(열) 감지\n * 3. 2+ 열이 3+ 행에 걸쳐 일관되면 테이블로 판정\n * 4. 기존 detectColumns(min 3열)보다 느슨한 기준(min 2열)으로\n * 2열 테이블(key-value 등)도 감지\n */\n\nimport type { IRBlock, 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) — detectColumns의 CLUSTER_TOL=22보다 엄격 */\nconst COL_CLUSTER_TOL = 15\n/** 테이블로 인정하기 위한 최소 행 수 */\nconst MIN_ROWS = 3\n/** 테이블로 인정하기 위한 최소 열 수 */\nconst MIN_COLS = 2\n/** 같은 행 내 아이템 간 최소 갭 (테이블 컬럼 구분) — fontSize 배수 */\nconst MIN_GAP_FACTOR = 1.5\n/** 열에 값이 있는 행의 비율 최소 기준 */\nconst MIN_COL_FILL_RATIO = 0.3\n\ninterface RowGroup {\n y: number // 대표 Y좌표 (평균 baseline)\n items: ClusterItem[]\n}\n\ninterface ColCluster {\n x: number // 열 X좌표 (왼쪽 경계)\n count: number // 이 열에 속한 아이템 수\n}\n\nexport interface ClusterTableResult {\n table: IRTable\n bbox: BoundingBox\n usedItems: Set<ClusterItem>\n}\n\n/**\n * 클러스터 기반 테이블 감지. 선이 없는 PDF의 fallback 경로에서 호출.\n *\n * @param items 페이지의 텍스트 아이템 (Y 내림차순 정렬)\n * @param pageNum 페이지 번호\n * @returns 감지된 테이블들 (없으면 빈 배열)\n */\nexport function detectClusterTables(items: ClusterItem[], pageNum: number): ClusterTableResult[] {\n if (items.length < MIN_ROWS * MIN_COLS) return []\n\n // 1. Y좌표로 행 그룹핑\n const rows = groupByBaseline(items)\n if (rows.length < MIN_ROWS) return []\n\n // 2. \"의심스러운\" 행 식별 — 아이템 간 큰 갭이 있는 행\n const suspiciousRows = rows.filter(row => hasSuspiciousGaps(row))\n if (suspiciousRows.length < MIN_ROWS) return []\n\n // 3. 의심스러운 행들의 X좌표에서 열 클러스터 추출\n const columns = extractColumnClusters(suspiciousRows)\n if (columns.length < MIN_COLS) return []\n\n // 4. 연속된 의심스러운 행들을 테이블 영역으로 그룹화\n const tableRegions = findTableRegions(rows, columns)\n const results: ClusterTableResult[] = []\n\n for (const region of tableRegions) {\n const table = buildClusterTable(region.rows, columns, pageNum)\n if (table) results.push(table)\n }\n\n return results\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 const avgFontSize = sorted.reduce((s, i) => s + i.fontSize, 0) / sorted.length\n const minGap = avgFontSize * MIN_GAP_FACTOR\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좌표에서 열 클러스터 추출 (sort-and-split 방식, 순서 무관) */\nfunction extractColumnClusters(rows: RowGroup[]): ColCluster[] {\n // 모든 X좌표 수집\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 // 정렬 후 갭 기반 분할\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 // 클러스터 완성: [clusterStart, i)\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 // 최소 빈도 필터 — 행 수의 30% 이상 등장해야 유효한 열\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/** 연속된 테이블 행 영역 찾기 */\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 // 이 행이 열 구조에 맞는지 확인\n const matchedCols = countMatchedColumns(row, columns)\n if (matchedCols >= MIN_COLS) {\n currentRegion.push(row)\n } else if (row.items.length === 1) {\n // 단일 아이템 행 — 병합 셀이거나 헤더일 수 있음\n if (currentRegion.length > 0) {\n currentRegion.push(row)\n }\n } else {\n // 비테이블 행 → 현재 영역 종료\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/** 행의 아이템이 몇 개의 열에 매칭되는지 */\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/** 아이템을 열에 배정. 거리 제한 초과 시 -1 반환. */\nfunction assignToColumn(item: ClusterItem, columns: ColCluster[]): number {\n const MAX_DIST = COL_CLUSTER_TOL * 3\n let bestCol = -1\n let bestDist = Infinity\n for (let ci = 0; ci < columns.length; ci++) {\n const dist = Math.abs(item.x - columns[ci].x)\n if (dist < bestDist) {\n bestDist = dist\n bestCol = ci\n }\n }\n return bestDist <= MAX_DIST ? bestCol : -1\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 // 셀 그리드 구성\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 for (const item of row.items) {\n const col = assignToColumn(item, columns)\n if (col < 0) continue // 열에 매칭 안 되는 아이템은 무시\n const existing = cells[r][col].text\n cells[r][col].text = existing ? existing + \" \" + item.text : item.text\n 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 const irTable: IRTable = {\n rows: numRows,\n cols: numCols,\n cells,\n hasHeader: numRows > 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","/**\n * PDF 텍스트 추출 (pdfjs-dist static import 기반)\n *\n * polyfill을 먼저 import해야 DOMMatrix/Path2D/pdfjsWorker가 주입됨.\n * ES 모듈 호이스팅 때문에 별도 파일로 분리되어 있음.\n */\n\nimport type { ParseResult, IRBlock, IRTable, DocumentMetadata, ParseOptions, BoundingBox, ParseWarning, OutlineItem } from \"../types.js\"\nimport { KordocError } from \"../utils.js\"\nimport { parsePageRange } from \"../page-range.js\"\nimport { blocksToMarkdown } from \"../table/builder.js\"\nimport { extractLines, filterPageBorderLines, buildTableGrids, extractCells, mapTextToCells, cellTextToString, type TextItem, type TableGrid, type ExtractedCell } from \"./line-detector.js\"\nimport { detectClusterTables, type ClusterItem } from \"./cluster-detector.js\"\n// polyfill 먼저 (ES 모듈 호이스팅되므로 별도 파일 필수)\nimport \"./polyfill.js\"\nimport { getDocument, OPS, GlobalWorkerOptions } from \"pdfjs-dist/legacy/build/pdf.mjs\"\n\n// worker 비활성화 (polyfill에서 pdfjsWorker를 이미 주입했으므로)\nGlobalWorkerOptions.workerSrc = \"\"\n\n// ─── 안전 한계값 (구조적 파싱과 무관) ────────────────\nconst MAX_PAGES = 5000\nconst MAX_TOTAL_TEXT = 100 * 1024 * 1024 // 100MB\n/** PDF 로딩 타임아웃 (30초) — 악성/대용량 PDF 무한 대기 방지 */\nconst PDF_LOAD_TIMEOUT_MS = 30_000\n\n/** getDocument + 타임아웃 래퍼 */\nasync function loadPdfWithTimeout(buffer: ArrayBuffer) {\n const loadingTask = getDocument({\n data: new Uint8Array(buffer),\n useSystemFonts: true,\n disableFontFace: true,\n isEvalSupported: false,\n })\n return Promise.race([\n loadingTask.promise,\n new Promise<never>((_, reject) =>\n setTimeout(() => { loadingTask.destroy(); reject(new KordocError(\"PDF 로딩 타임아웃 (30초 초과)\")) }, PDF_LOAD_TIMEOUT_MS)\n ),\n ])\n}\n\ninterface PdfTextItem {\n str: string\n transform: number[]\n width: number\n height: number\n fontName?: string\n}\n\ninterface NormItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n /** 폰트 높이(≈폰트 크기) — 헤딩 감지용 */\n fontSize: number\n fontName: string\n /** hidden text 여부 (투명/0pt) */\n isHidden: boolean\n}\n\nexport async function parsePdfDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<ParseResult> {\n const doc = await loadPdfWithTimeout(buffer)\n\n try {\n const pageCount = doc.numPages\n if (pageCount === 0) return { success: false, fileType: \"pdf\", pageCount: 0, error: \"PDF에 페이지가 없습니다.\", code: \"PARSE_ERROR\" }\n\n // 메타데이터 추출 (best-effort)\n const metadata: DocumentMetadata = { pageCount }\n await extractPdfMetadata(doc, metadata)\n\n const blocks: IRBlock[] = []\n const warnings: ParseWarning[] = []\n let totalChars = 0\n let totalTextBytes = 0\n const effectivePageCount = Math.min(pageCount, MAX_PAGES)\n\n // 페이지 범위 필터링\n const pageFilter = options?.pages ? parsePageRange(options.pages, effectivePageCount) : null\n const totalTarget = pageFilter ? pageFilter.size : effectivePageCount\n\n // 전체 문서의 폰트 크기 통계 수집 (헤딩 감지용)\n const allFontSizes: number[] = []\n const pageHeights = new Map<number, number>()\n\n let parsedPages = 0\n for (let i = 1; i <= effectivePageCount; i++) {\n if (pageFilter && !pageFilter.has(i)) continue\n try {\n const page = await doc.getPage(i)\n const tc = await page.getTextContent()\n const viewport = page.getViewport({ scale: 1 })\n pageHeights.set(i, viewport.height)\n const rawItems = tc.items as PdfTextItem[]\n const items = normalizeItems(rawItems)\n\n // hidden text 필터링 + 경고 수집\n const { visible, hiddenCount } = filterHiddenText(items, viewport.width, viewport.height)\n if (hiddenCount > 0) {\n warnings.push({ page: i, message: `${hiddenCount}개 숨겨진 텍스트 요소 필터링됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n }\n\n // 폰트 크기 통계 수집\n for (const item of visible) {\n if (item.fontSize > 0) allFontSizes.push(item.fontSize)\n }\n\n // 선 기반 테이블 감지를 위한 operatorList\n const opList = await page.getOperatorList()\n\n const pageBlocks = extractPageBlocksWithLines(visible, i, opList, viewport.width, viewport.height)\n for (const b of pageBlocks) blocks.push(b)\n\n // 이미지 기반 PDF 감지 + 크기 제한용 문자 수 집계\n for (const b of pageBlocks) {\n const t = b.text || \"\"\n totalChars += t.replace(/\\s/g, \"\").length\n totalTextBytes += t.length * 2\n }\n if (totalTextBytes > MAX_TOTAL_TEXT) throw new KordocError(\"텍스트 추출 크기 초과\")\n parsedPages++\n options?.onProgress?.(parsedPages, totalTarget)\n } catch (pageErr) {\n // 크기 초과는 전체 중단\n if (pageErr instanceof KordocError) throw pageErr\n warnings.push({ page: i, message: `페이지 ${i} 파싱 실패: ${pageErr instanceof Error ? pageErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\n }\n }\n\n const parsedPageCount = parsedPages || (pageFilter ? pageFilter.size : effectivePageCount)\n if (totalChars / Math.max(parsedPageCount, 1) < 10) {\n if (options?.ocr) {\n try {\n const { ocrPages } = await import(\"../ocr/provider.js\")\n const ocrBlocks = await ocrPages(doc, options.ocr, pageFilter, effectivePageCount)\n if (ocrBlocks.length > 0) {\n const ocrMarkdown = ocrBlocks.map(b => b.text || \"\").filter(Boolean).join(\"\\n\\n\")\n return { success: true, fileType: \"pdf\", markdown: ocrMarkdown, pageCount: parsedPageCount, blocks: ocrBlocks, metadata, isImageBased: true, warnings }\n }\n } catch {\n // OCR 실패 시 원래 에러 반환\n }\n }\n return { success: false, fileType: \"pdf\", pageCount, isImageBased: true, error: `이미지 기반 PDF (${pageCount}페이지, ${totalChars}자)`, code: \"IMAGE_BASED_PDF\" }\n }\n\n // 머리글/바닥글 필터링\n if (options?.removeHeaderFooter && parsedPageCount >= 3) {\n const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings)\n // 필터링된 블록 제거 (뒤에서부터 삭제)\n for (let ri = removed.length - 1; ri >= 0; ri--) {\n blocks.splice(removed[ri], 1)\n }\n }\n\n // 헤딩 감지: 폰트 크기 기반\n const medianFontSize = computeMedianFontSize(allFontSizes)\n if (medianFontSize > 0) {\n detectHeadings(blocks, medianFontSize)\n }\n\n // outline 구축\n const outline: OutlineItem[] = blocks\n .filter(b => b.type === \"heading\" && b.level && b.text)\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\n\n // blocksToMarkdown로 통일 — 헤딩 마크다운 반영 (HWP5/HWPX와 일관성)\n let markdown = cleanPdfText(blocksToMarkdown(blocks))\n\n return { success: true, fileType: \"pdf\", markdown, pageCount: parsedPageCount, blocks, metadata, outline: outline.length > 0 ? outline : undefined, warnings: warnings.length > 0 ? warnings : undefined }\n } finally {\n await doc.destroy().catch(() => {})\n }\n}\n\n// ─── PDF 메타데이터 추출 ────────────────────────────\n\nasync function extractPdfMetadata(doc: { getMetadata(): Promise<unknown> }, metadata: DocumentMetadata): Promise<void> {\n try {\n const result = await doc.getMetadata() as { info?: Record<string, unknown> } | null\n if (!result?.info) return\n const info = result.info\n\n if (typeof info.Title === \"string\" && info.Title.trim()) metadata.title = info.Title.trim()\n if (typeof info.Author === \"string\" && info.Author.trim()) metadata.author = info.Author.trim()\n if (typeof info.Creator === \"string\" && info.Creator.trim()) metadata.creator = info.Creator.trim()\n if (typeof info.Subject === \"string\" && info.Subject.trim()) metadata.description = info.Subject.trim()\n if (typeof info.Keywords === \"string\" && info.Keywords.trim()) {\n metadata.keywords = info.Keywords.split(/[,;]/).map((k: string) => k.trim()).filter(Boolean)\n }\n if (typeof info.CreationDate === \"string\") metadata.createdAt = parsePdfDate(info.CreationDate)\n if (typeof info.ModDate === \"string\") metadata.modifiedAt = parsePdfDate(info.ModDate)\n } catch {\n // best-effort\n }\n}\n\n/** PDF 날짜 형식 (D:YYYYMMDDHHmmSS) → ISO 8601 변환 */\nfunction parsePdfDate(dateStr: string): string | undefined {\n const m = dateStr.match(/D:(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?/)\n if (!m) return undefined\n const [, year, month = \"01\", day = \"01\", hour = \"00\", min = \"00\", sec = \"00\"] = m\n return `${year}-${month}-${day}T${hour}:${min}:${sec}`\n}\n\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\nexport async function extractPdfMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\n const doc = await loadPdfWithTimeout(buffer)\n\n try {\n const metadata: DocumentMetadata = { pageCount: doc.numPages }\n await extractPdfMetadata(doc, metadata)\n return metadata\n } finally {\n await doc.destroy().catch(() => {})\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// Hidden text 필터링 (prompt injection 방어)\n// ═══════════════════════════════════════════════════════\n\nfunction filterHiddenText(items: NormItem[], pageWidth: number, pageHeight: number): { visible: NormItem[]; hiddenCount: number } {\n let hiddenCount = 0\n const visible: NormItem[] = []\n\n for (const item of items) {\n // 0pt 폰트 / 너비 0 → 숨겨진 텍스트\n if (item.isHidden) { hiddenCount++; continue }\n // 페이지 범위 밖 (여백 10% 허용)\n const margin = Math.max(pageWidth, pageHeight) * 0.1\n if (item.x < -margin || item.x > pageWidth + margin || item.y < -margin || item.y > pageHeight + margin) {\n hiddenCount++; continue\n }\n visible.push(item)\n }\n\n return { visible, hiddenCount }\n}\n\n// ═══════════════════════════════════════════════════════\n// 헤딩 감지 (폰트 크기 기반)\n// ═══════════════════════════════════════════════════════\n\nfunction computeMedianFontSize(sizes: number[]): number {\n if (sizes.length === 0) return 0\n const sorted = [...sizes].sort((a, b) => a - b)\n const mid = Math.floor(sorted.length / 2)\n return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid]\n}\n\n/**\n * 블록의 폰트 크기를 median과 비교하여 헤딩으로 승격.\n * - 150%+ → heading level 1\n * - 130%+ → heading level 2\n * - 115%+ → heading level 3\n * 조건: 짧은 텍스트 (200자 미만), 숫자만으로 구성되지 않음\n */\nfunction detectHeadings(blocks: IRBlock[], medianFontSize: number): void {\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontSize) continue\n const text = block.text.trim()\n if (text.length === 0 || text.length > 200) continue\n // 숫자만이면 헤딩 아님\n if (/^\\d+$/.test(text)) continue\n\n const ratio = block.style.fontSize / medianFontSize\n let level = 0\n if (ratio >= 1.5) level = 1\n else if (ratio >= 1.3) level = 2\n else if (ratio >= 1.15) level = 3\n\n if (level > 0) {\n block.type = \"heading\"\n block.level = level\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// XY-Cut 읽기 순서 알고리즘\n// ═══════════════════════════════════════════════════════\n\ninterface TextRegion {\n items: NormItem[]\n minX: number; minY: number; maxX: number; maxY: number\n}\n\n/**\n * XY-Cut: 페이지를 재귀적으로 X/Y축 공백으로 분할하여 읽기 순서 결정.\n * - Y축 분할 (수평 공백 감지) → 위에서 아래\n * - X축 분할 (수직 공백 감지) → 왼쪽에서 오른쪽\n * - 분할 불가능하면 리프 노드 (하나의 텍스트 블록)\n */\n/** 재귀 깊이 제한 — 수천 아이템의 pathological 레이아웃에서 스택 오버플로 방지 */\nconst MAX_XYCUT_DEPTH = 50\n\nfunction xyCutOrder(items: NormItem[], gapThreshold: number, depth = 0): NormItem[][] {\n if (items.length === 0) return []\n if (items.length <= 2 || depth >= MAX_XYCUT_DEPTH) return [items]\n\n const region = computeRegion(items)\n\n // Y축 분할 시도 (수평 공백 감지)\n const ySplit = findYSplit(items, region, gapThreshold)\n if (ySplit !== null) {\n const upper = items.filter(i => i.y > ySplit)\n const lower = items.filter(i => i.y <= ySplit)\n // 빈 파티션 방어 — 한쪽이 비면 분할 의미 없음\n if (upper.length > 0 && lower.length > 0 && upper.length < items.length) {\n return [...xyCutOrder(upper, gapThreshold, depth + 1), ...xyCutOrder(lower, gapThreshold, depth + 1)]\n }\n }\n\n // X축 분할 시도 (수직 공백 감지)\n const xSplit = findXSplit(items, region, gapThreshold)\n if (xSplit !== null) {\n const left = items.filter(i => i.x + i.w / 2 < xSplit)\n const right = items.filter(i => i.x + i.w / 2 >= xSplit)\n if (left.length > 0 && right.length > 0 && left.length < items.length) {\n return [...xyCutOrder(left, gapThreshold, depth + 1), ...xyCutOrder(right, gapThreshold, depth + 1)]\n }\n }\n\n // 분할 불가 → 리프 노드\n return [items]\n}\n\nfunction computeRegion(items: NormItem[]): TextRegion {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n if (i.y < minY) minY = i.y\n if (i.x + i.w > maxX) maxX = i.x + i.w\n if (i.y + i.h > maxY) maxY = i.y + i.h\n }\n return { items, minX, minY, maxX, maxY }\n}\n\n/** Y축 분할점 찾기 — 수평 공백 밴드 중 가장 넓은 갭 */\nfunction findYSplit(items: NormItem[], region: TextRegion, gapThreshold: number): number | null {\n // 아이템을 Y좌표로 정렬 (내림차순 — 위에서 아래)\n const sorted = [...items].sort((a, b) => b.y - a.y)\n let bestGap = gapThreshold\n let bestSplit: number | null = null\n\n for (let i = 1; i < sorted.length; i++) {\n const prevBottom = sorted[i - 1].y - sorted[i - 1].h\n const currTop = sorted[i].y\n const gap = prevBottom - currTop\n if (gap > bestGap) {\n bestGap = gap\n bestSplit = (prevBottom + currTop) / 2\n }\n }\n return bestSplit\n}\n\n/** X축 분할점 찾기 — 수직 공백 밴드 중 가장 넓은 갭 */\nfunction findXSplit(items: NormItem[], region: TextRegion, gapThreshold: number): number | null {\n const sorted = [...items].sort((a, b) => a.x - b.x)\n let bestGap = gapThreshold\n let bestSplit: number | null = null\n\n for (let i = 1; i < sorted.length; i++) {\n const prevRight = sorted[i - 1].x + sorted[i - 1].w\n const currLeft = sorted[i].x\n const gap = currLeft - prevRight\n if (gap > bestGap) {\n bestGap = gap\n bestSplit = (prevRight + currLeft) / 2\n }\n }\n return bestSplit\n}\n\n// ═══════════════════════════════════════════════════════\n// 페이지 콘텐츠 추출 → IRBlock[] (v2: 바운딩 박스 + 페이지 번호)\n// ═══════════════════════════════════════════════════════\n\n/**\n * 선 기반 테이블 감지를 우선 시도, 실패 시 기존 휴리스틱 fallback.\n */\nfunction extractPageBlocksWithLines(\n items: NormItem[],\n pageNum: number,\n opList: { fnArray: Uint32Array | number[]; argsArray: unknown[][] },\n pageWidth: number,\n pageHeight: number,\n): IRBlock[] {\n if (items.length === 0) return []\n\n // 1단계: PDF 그래픽 명령에서 선 추출\n let { horizontals, verticals } = extractLines(opList.fnArray, opList.argsArray)\n ;({ horizontals, verticals } = filterPageBorderLines(horizontals, verticals, pageWidth, pageHeight))\n\n // 2단계: 선으로 테이블 그리드 구성\n const grids = buildTableGrids(horizontals, verticals)\n\n if (grids.length > 0) {\n return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals)\n }\n\n // Fallback: 기존 휴리스틱 (선이 없는 PDF)\n return extractPageBlocksFallback(items, pageNum)\n}\n\n/**\n * 선 기반 그리드가 감지된 경우: 테이블 영역의 텍스트는 셀에 매핑,\n * 나머지는 일반 텍스트 블록으로 처리.\n */\nfunction extractBlocksWithGrids(\n items: NormItem[],\n pageNum: number,\n grids: TableGrid[],\n horizontals: import(\"./line-detector.js\").LineSegment[],\n verticals: import(\"./line-detector.js\").LineSegment[],\n): IRBlock[] {\n const blocks: IRBlock[] = []\n const usedItems = new Set<NormItem>()\n\n // 그리드를 Y좌표 내림차순 정렬 (위→아래)\n const sortedGrids = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\n\n for (const grid of sortedGrids) {\n // 그리드 영역 내 텍스트 아이템 수집\n const tableItems: NormItem[] = []\n const pad = 3\n for (const item of items) {\n if (usedItems.has(item)) continue\n if (item.x >= grid.bbox.x1 - pad && item.x + item.w <= grid.bbox.x2 + pad &&\n item.y >= grid.bbox.y1 - pad && item.y <= grid.bbox.y2 + pad) {\n tableItems.push(item)\n usedItems.add(item)\n }\n }\n\n // 셀 추출\n const cells = extractCells(grid, horizontals, verticals)\n if (cells.length === 0) continue\n\n // 텍스트→셀 매핑\n const textItems: TextItem[] = tableItems.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName,\n }))\n const cellTextMap = mapTextToCells(textItems, cells)\n\n // IRTable 구성\n const numRows = grid.rowYs.length - 1\n const numCols = grid.colXs.length - 1\n const irGrid: import(\"../types.js\").IRCell[][] = Array.from(\n { length: numRows },\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\n )\n\n for (const cell of cells) {\n const cellItems = cellTextMap.get(cell) || []\n const text = cellTextToString(cellItems)\n irGrid[cell.row][cell.col] = {\n text,\n colSpan: cell.colSpan,\n rowSpan: cell.rowSpan,\n }\n }\n\n const irTable: IRTable = {\n rows: numRows,\n cols: numCols,\n cells: irGrid,\n hasHeader: numRows > 1,\n }\n\n // 빈 테이블(모든 셀이 빈 문자열) 스킵\n const hasContent = irGrid.some(row => row.some(cell => cell.text.trim() !== \"\"))\n if (!hasContent) continue\n\n blocks.push({\n type: \"table\",\n table: irTable,\n pageNumber: pageNum,\n bbox: {\n page: pageNum,\n x: grid.bbox.x1, y: grid.bbox.y1,\n width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1,\n },\n })\n }\n\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\n const remaining = items.filter(i => !usedItems.has(i))\n if (remaining.length > 0) {\n // 위→아래 순서로 정렬\n remaining.sort((a, b) => b.y - a.y || a.x - b.x)\n\n // 테이블 전/후 텍스트를 Y좌표 기준으로 적절히 배치\n const textBlocks = detectListBlocks(extractPageBlocksFallback(remaining, pageNum))\n\n // Y좌표 기반으로 테이블과 텍스트를 올바른 순서로 병합\n const allBlocks = [...blocks, ...textBlocks]\n allBlocks.sort((a, b) => {\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\n return by - ay // PDF는 y가 위가 큼 → 내림차순\n })\n return allBlocks\n }\n\n return blocks\n}\n\n/**\n * 기존 휴리스틱 기반 페이지 블록 추출 (선이 없는 PDF 대비 fallback).\n */\nfunction extractPageBlocksFallback(items: NormItem[], pageNum: number): IRBlock[] {\n if (items.length === 0) return []\n\n const blocks: IRBlock[] = []\n\n // 1단계: 페이지 전체에서 컬럼 감지 (테이블 우선)\n const allYLines = groupByY(items)\n const columns = detectColumns(allYLines)\n\n if (columns && columns.length >= 3) {\n // 테이블 감지됨 → 기존 extractWithColumns 로직 사용 (XY-Cut 스킵)\n const tableText = extractWithColumns(allYLines, columns)\n const bbox = computeBBox(items, pageNum)\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(items) })\n } else {\n // 2단계: 클러스터 기반 테이블 감지 (2열 이상, 선 없는 PDF)\n const clusterItems: ClusterItem[] = items.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName,\n }))\n const clusterResults = detectClusterTables(clusterItems, pageNum)\n\n if (clusterResults.length > 0) {\n // 클러스터 테이블로 소비된 아이템 추적 (인덱스 기반 — 문자열 키보다 안전)\n const usedIndices = new Set<number>()\n for (const cr of clusterResults) {\n for (const ci of cr.usedItems) {\n // clusterItems와 items는 1:1 매핑, 동일 참조로 인덱스 찾기\n const idx = clusterItems.indexOf(ci)\n if (idx >= 0) usedIndices.add(idx)\n }\n blocks.push({ type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox })\n }\n\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\n const remaining = items.filter((_, idx) => !usedIndices.has(idx))\n if (remaining.length > 0) {\n const yLines = groupByY(remaining)\n for (const line of yLines) {\n const text = mergeLineSimple(line)\n if (!text.trim()) continue\n const bbox = computeBBox(line, pageNum)\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\n }\n }\n\n // Y좌표 기준 정렬\n blocks.sort((a, b) => {\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\n return by - ay\n })\n } else {\n // 3단계: XY-Cut으로 읽기 순서 결정\n const allY = items.map(i => i.y)\n const pageHeight = Math.max(...allY) - Math.min(...allY)\n const gapThreshold = Math.max(15, pageHeight * 0.03)\n\n const orderedGroups = xyCutOrder(items, gapThreshold)\n\n for (const group of orderedGroups) {\n if (group.length === 0) continue\n const yLines = groupByY(group)\n\n // 그룹 내에서도 컬럼 감지 시도 (소형 테이블)\n const groupColumns = detectColumns(yLines)\n if (groupColumns && groupColumns.length >= 3) {\n const tableText = extractWithColumns(yLines, groupColumns)\n const bbox = computeBBox(group, pageNum)\n blocks.push({ type: \"paragraph\", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(group) })\n } else {\n for (const line of yLines) {\n const text = mergeLineSimple(line)\n if (!text.trim()) continue\n const bbox = computeBBox(line, pageNum)\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(line) })\n }\n }\n }\n }\n }\n\n // 한국어 특수 테이블 감지 (구분/항목/종류 패턴)\n return detectSpecialKoreanTables(blocks)\n}\n\n/** 아이템 그룹에서 바운딩 박스 계산 */\nfunction computeBBox(items: NormItem[], pageNum: number): BoundingBox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n if (i.y < minY) minY = i.y\n if (i.x + i.w > maxX) maxX = i.x + i.w\n // h가 0인 경우 fontSize를 높이 대용으로 사용 (pdfjs가 height를 제공하지 않는 경우)\n const effectiveH = i.h > 0 ? i.h : i.fontSize\n if (i.y + effectiveH > maxY) maxY = i.y + effectiveH\n }\n return { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n}\n\n/** 아이템 그룹의 대표 스타일 (최빈 폰트 크기) */\nfunction dominantStyle(items: NormItem[]): { fontSize: number; fontName: string } | undefined {\n if (items.length === 0) return undefined\n // 최빈 폰트 크기 찾기\n const freq = new Map<number, number>()\n let maxCount = 0, dominantSize = 0\n for (const i of items) {\n if (i.fontSize <= 0) continue\n const count = (freq.get(i.fontSize) || 0) + 1\n freq.set(i.fontSize, count)\n if (count > maxCount) { maxCount = count; dominantSize = i.fontSize }\n }\n if (dominantSize === 0) return undefined\n // 대표 폰트명 (빈 문자열은 undefined로)\n const fontName = items.find(i => i.fontSize === dominantSize)?.fontName || undefined\n return { fontSize: dominantSize, fontName }\n}\n\nfunction normalizeItems(rawItems: PdfTextItem[]): NormItem[] {\n return rawItems\n .filter(i => typeof i.str === \"string\" && i.str.trim() !== \"\")\n .map(i => {\n // transform matrix: [scaleX, skewY, skewX, scaleY, translateX, translateY]\n // 폰트 크기 ≈ scaleY의 절대값 (일반적으로 transform[3])\n const scaleY = Math.abs(i.transform[3])\n const scaleX = Math.abs(i.transform[0])\n const fontSize = Math.round(Math.max(scaleY, scaleX))\n\n return {\n text: i.str.trim(),\n x: Math.round(i.transform[4]),\n y: Math.round(i.transform[5]),\n w: Math.round(i.width),\n h: Math.round(i.height),\n fontSize,\n fontName: i.fontName || \"\",\n // 0pt 폰트이거나 너비 0 → hidden text (prompt injection 의심)\n isHidden: fontSize === 0 || (i.width === 0 && i.str.trim().length > 0),\n }\n })\n .sort((a, b) => b.y - a.y || a.x - b.x)\n}\n\nfunction groupByY(items: NormItem[]): NormItem[][] {\n if (items.length === 0) return []\n const lines: NormItem[][] = []\n let curY = items[0].y\n let curLine: NormItem[] = [items[0]]\n\n for (let i = 1; i < items.length; i++) {\n // Y좌표 허용 오차 3px — PDF 렌더링 미세 오차 보정, 별표 행 경계 감지에 최적화된 값\n if (Math.abs(items[i].y - curY) > 3) {\n lines.push(curLine)\n curLine = []\n curY = items[i].y\n }\n curLine.push(items[i])\n }\n if (curLine.length > 0) lines.push(curLine)\n return lines\n}\n\n// ═══════════════════════════════════════════════════════\n// 열 경계 감지 — 빈도 기반 x-히스토그램 클러스터링\n// ═══════════════════════════════════════════════════════\n\n/** prose 라인 판별: 아이템 간 gap이 모두 작으면 문장 (단어 나열) */\nfunction isProseSpread(items: NormItem[]): boolean {\n if (items.length < 4) return false\n const sorted = [...items].sort((a, b) => a.x - b.x)\n const gaps: number[] = []\n for (let i = 1; i < sorted.length; i++) {\n gaps.push(sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w))\n }\n // gap의 최대값이 작고 평균 단어 길이가 짧으면 prose\n const maxGap = Math.max(...gaps)\n const avgLen = items.reduce((s, i) => s + i.text.length, 0) / items.length\n // 짧은 단어들이 좁은 간격으로 나열 = prose (예: \"위 표 제3호나목에서 남은 유효기간...\")\n return maxGap < 40 && avgLen < 5\n}\n\nfunction detectColumns(yLines: NormItem[][]): number[] | null {\n const allItems = yLines.flat()\n if (allItems.length === 0) return null\n const pageWidth = Math.max(...allItems.map(i => i.x + i.w)) - Math.min(...allItems.map(i => i.x))\n if (pageWidth < 100) return null\n\n // \"비고\" 이전 아이템만 사용 (비고 이후는 prose)\n let bigoLineIdx = -1\n for (let i = 0; i < yLines.length; i++) {\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\n bigoLineIdx = i\n break\n }\n }\n const tableYLines = bigoLineIdx >= 0 ? yLines.slice(0, bigoLineIdx) : yLines\n\n // Step 1: 모든 아이템의 x를 수집 (prose 라인 제외)\n // CLUSTER_TOL 22px — 한국 공문서 PDF 열 간격에 최적화, 별표 표 열 감지 핵심값\n const CLUSTER_TOL = 22\n const xClusters: { center: number; count: number; minX: number }[] = []\n\n for (const line of tableYLines) {\n if (isProseSpread(line)) continue\n for (const item of line) {\n let found = false\n for (const c of xClusters) {\n if (Math.abs(item.x - c.center) <= CLUSTER_TOL) {\n c.center = Math.round((c.center * c.count + item.x) / (c.count + 1))\n c.minX = Math.min(c.minX, item.x)\n c.count++\n found = true\n break\n }\n }\n if (!found) {\n xClusters.push({ center: item.x, count: 1, minX: item.x })\n }\n }\n }\n\n // Step 2: 빈도 피크 — 최소 3회 이상 등장 (단발성 텍스트 노이즈 제거)\n const peaks = xClusters\n .filter(c => c.count >= 3)\n .sort((a, b) => a.minX - b.minX)\n\n // 최소 3개 열이 있어야 테이블로 판별 — 2열은 일반 2단 레이아웃과 구분 불가\n if (peaks.length < 3) return null\n\n // Step 3: 가까운 피크 병합 — MERGE_TOL 30px (같은 논리 열의 미세 위치 차이 흡수)\n const MERGE_TOL = 30\n const merged: { center: number; count: number; minX: number }[] = [peaks[0]]\n for (let i = 1; i < peaks.length; i++) {\n const prev = merged[merged.length - 1]\n if (peaks[i].minX - prev.minX < MERGE_TOL) {\n // 빈도 높은 쪽 유지, 최소 x는 작은 값\n if (peaks[i].count > prev.count) {\n prev.center = peaks[i].center\n }\n prev.count += peaks[i].count\n prev.minX = Math.min(prev.minX, peaks[i].minX)\n } else {\n merged.push({ ...peaks[i] })\n }\n }\n\n // 열 경계 = 각 클러스터의 minX (왼쪽 정렬 기준), 병합 후 재검증\n const columns = merged.filter(c => c.count >= 3).map(c => c.minX)\n return columns.length >= 3 ? columns : null\n}\n\nfunction findColumn(x: number, columns: number[]): number {\n for (let i = columns.length - 1; i >= 0; i--) {\n // 10px 왼쪽 허용 오차 — 셀 내 텍스트 미세 좌측 이탈 보정\n if (x >= columns[i] - 10) return i\n }\n return 0\n}\n\n// ═══════════════════════════════════════════════════════\n// 열 기반 추출 — 테이블/텍스트 영역 분리\n// ═══════════════════════════════════════════════════════\n\nfunction extractWithColumns(yLines: NormItem[][], columns: number[]): string {\n const result: string[] = []\n const colMin = columns[0]\n const colMax = columns[columns.length - 1]\n\n // \"비고\" 라인 감지 — 이후는 텍스트로 처리\n let bigoIdx = -1\n for (let i = 0; i < yLines.length; i++) {\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\n bigoIdx = i\n break\n }\n }\n\n // 테이블 시작: 첫 번째 다열(3+ 열 사용) 라인\n let tableStart = -1\n for (let i = 0; i < (bigoIdx >= 0 ? bigoIdx : yLines.length); i++) {\n const usedCols = new Set(yLines[i].map(item => findColumn(item.x, columns)))\n if (usedCols.size >= 3) {\n tableStart = i\n break\n }\n }\n\n const tableEnd = bigoIdx >= 0 ? bigoIdx : yLines.length\n\n // 테이블 시작 이전 = 텍스트\n for (let i = 0; i < (tableStart >= 0 ? tableStart : tableEnd); i++) {\n result.push(mergeLineSimple(yLines[i]))\n }\n\n // 테이블 영역: 모든 라인을 그리드에 포함 (단일 아이템 라인도)\n if (tableStart >= 0) {\n const tableLines = yLines.slice(tableStart, tableEnd)\n // 테이블 x범위 밖의 라인만 텍스트로 분리\n // 좌측 20px, 우측 200px 허용 — 비고/주석 열이 오른쪽에 넓게 위치하는 공문서 특성 반영\n const gridLines: NormItem[][] = []\n for (const line of tableLines) {\n const inRange = line.some(item =>\n item.x >= colMin - 20 && item.x <= colMax + 200\n )\n if (inRange && !isProseSpread(line)) {\n gridLines.push(line)\n } else {\n // 그리드 밖 라인은 현재까지 축적된 그리드 출력 후 텍스트로\n if (gridLines.length > 0) {\n result.push(buildGridTable(gridLines.splice(0), columns))\n }\n result.push(mergeLineSimple(line))\n }\n }\n if (gridLines.length > 0) {\n result.push(buildGridTable(gridLines, columns))\n }\n }\n\n // 비고 영역\n if (bigoIdx >= 0) {\n result.push(\"\")\n for (let i = bigoIdx; i < yLines.length; i++) {\n result.push(mergeLineSimple(yLines[i]))\n }\n }\n\n return result.join(\"\\n\")\n}\n\n// ═══════════════════════════════════════════════════════\n// 그리드 테이블 빌더 — y-라인을 열에 배치 후 행 병합\n// ═══════════════════════════════════════════════════════\n\nfunction buildGridTable(lines: NormItem[][], columns: number[]): string {\n const numCols = columns.length\n\n // Step 1: 각 y-라인을 열에 배치\n const yRows: string[][] = lines.map(items => {\n const row = Array(numCols).fill(\"\")\n for (const item of items) {\n const col = findColumn(item.x, columns)\n row[col] = row[col] ? row[col] + \" \" + item.text : item.text\n }\n return row\n })\n\n // Step 2: 행 병합 — 새 논리적 행 판별\n // 데이터 열 기준점 (가격 등이 들어가는 오른쪽 열들)\n const dataColStart = Math.max(2, Math.floor(numCols / 2))\n const merged: string[][] = []\n\n for (const row of yRows) {\n if (row.every(c => c === \"\")) continue\n\n if (merged.length === 0) {\n merged.push([...row])\n continue\n }\n\n const prev = merged[merged.length - 1]\n const filledCols = row.map((c, i) => c ? i : -1).filter(i => i >= 0)\n const filledCount = filledCols.length\n\n let isNewRow = false\n\n // Rule 1: col 0에 텍스트 (3글자 이상) → 새 행 (단, \"권\"처럼 짧은 건 continuation)\n if (row[0] && row[0].length >= 3) {\n isNewRow = true\n }\n\n // Rule 2: col 1에 텍스트 → 항상 새 행 (새 항목 시작)\n if (!isNewRow && numCols > 1 && row[1]) {\n isNewRow = true\n }\n\n // Rule 3: 데이터 열(3+)에 새 값이 있고 이전 행 데이터 열에도 이미 값 있음 → 새 가격 행\n if (!isNewRow) {\n const hasData = row.slice(dataColStart).some(c => c !== \"\")\n const prevHasData = prev.slice(dataColStart).some(c => c !== \"\")\n if (hasData && prevHasData) {\n isNewRow = true\n }\n }\n\n // Exception: filledCount=1이고 col 0에 짧은 텍스트(≤2자) → word continuation (예: \"권\", \"여권\")\n if (isNewRow && filledCount === 1 && row[0] && row[0].length <= 2) {\n isNewRow = false\n }\n\n if (isNewRow) {\n merged.push([...row])\n } else {\n for (let c = 0; c < numCols; c++) {\n if (row[c]) {\n prev[c] = prev[c] ? prev[c] + \" \" + row[c] : row[c]\n }\n }\n }\n }\n\n if (merged.length < 2) {\n return merged.map(r => r.filter(c => c).join(\" \")).join(\"\\n\")\n }\n\n // Step 3: 헤더 행 병합 — 첫 N행이 모두 데이터열(dataColStart+)에 값이 없으면 헤더\n let headerEnd = 0\n for (let r = 0; r < merged.length; r++) {\n const hasDataValues = merged[r].slice(dataColStart).some(c => c && /\\d/.test(c))\n if (hasDataValues) break\n headerEnd = r + 1\n }\n\n if (headerEnd > 1) {\n // 헤더 행들을 하나로 합침\n const headerRow = Array(numCols).fill(\"\")\n for (let r = 0; r < headerEnd; r++) {\n for (let c = 0; c < numCols; c++) {\n if (merged[r][c]) {\n headerRow[c] = headerRow[c] ? headerRow[c] + \" \" + merged[r][c] : merged[r][c]\n }\n }\n }\n merged.splice(0, headerEnd, headerRow)\n }\n\n // Step 4: 마크다운 테이블\n const md: string[] = []\n md.push(\"| \" + merged[0].join(\" | \") + \" |\")\n md.push(\"| \" + merged[0].map(() => \"---\").join(\" | \") + \" |\")\n for (let r = 1; r < merged.length; r++) {\n md.push(\"| \" + merged[r].join(\" | \") + \" |\")\n }\n return md.join(\"\\n\")\n}\n\n// ═══════════════════════════════════════════════════════\n// 유틸\n// ═══════════════════════════════════════════════════════\n\nfunction mergeLineSimple(items: NormItem[]): string {\n if (items.length <= 1) return items[0]?.text || \"\"\n const sorted = [...items].sort((a, b) => a.x - b.x)\n let result = sorted[0].text\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n const avgFs = (sorted[i].fontSize + sorted[i - 1].fontSize) / 2\n // 15px+ 갭 = 탭 (열 구분)\n if (gap > 15) result += \"\\t\"\n // 한글-한글 사이 매우 작은 갭 (< fontSize * 0.3) → 공백 없이 붙임\n else if (gap < avgFs * 0.3 && /[가-힣]$/.test(result) && /^[가-힣]/.test(sorted[i].text)) {\n /* 한글 어절 끊김 복원: PDF가 문자별로 배치할 때 생기는 미세 갭 */\n }\n // 3px+ 갭 = 공백 (단어 구분)\n else if (gap > 3) result += \" \"\n result += sorted[i].text\n }\n return result\n}\n\nexport function cleanPdfText(text: string): string {\n return mergeKoreanLines(\n text\n .replace(/^[\\s]*[-–—]\\s*\\d+\\s*[-–—][\\s]*$/gm, \"\")\n .replace(/^\\s*\\d+\\s*\\/\\s*\\d+\\s*$/gm, \"\")\n )\n // NOTE: 한국어 어절 중간 끊김(\"기 준을\")은 형태소 분석 없이 정규식으로 수리하기 어려움.\n // 과매칭 위험이 높아 현재는 적용하지 않음.\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim()\n}\n\nfunction startsWithMarker(line: string): boolean {\n const t = line.trimStart()\n return /^[가-힣ㄱ-ㅎ][.)]/.test(t) || /^\\d+[.)]/.test(t) || /^\\([가-힣ㄱ-ㅎ\\d]+\\)/.test(t) ||\n /^[○●※▶▷◆◇■□★☆\\-·]\\s/.test(t) || /^제\\d+[조항호장절]/.test(t)\n}\n\nfunction isStandaloneHeader(line: string): boolean {\n return /^제\\d+[조항호장절](\\([^)]*\\))?(\\s+\\S+){0,7}$/.test(line.trim())\n}\n\n// ═══════════════════════════════════════════════════════\n// 리스트 감지 — paragraph 블록 중 번호 패턴을 list 블록으로 변환\n// ═══════════════════════════════════════════════════════\n\n/**\n * 연속된 paragraph 블록에서 번호 리스트 패턴을 감지하여 list 블록으로 변환.\n * \"비고\" 헤더 뒤에 오는 \"1.\", \"2.\" 패턴이 대표적.\n */\nfunction detectListBlocks(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n\n // paragraph에서 번호 리스트 패턴 감지\n if (block.type === \"paragraph\" && block.text) {\n const match = block.text.match(/^(\\d+)\\.\\s/)\n if (match) {\n result.push({\n ...block,\n type: \"list\",\n listType: \"ordered\",\n // 원래 번호를 text에 보존 (blocksToMarkdown에서 그대로 출력)\n text: block.text,\n })\n continue\n }\n }\n\n result.push(block)\n }\n\n return result\n}\n\n// ═══════════════════════════════════════════════════════\n// 한국어 특수 테이블 감지 — \"구분/항목/종류\" 패턴 기반 key-value 테이블\n// ═══════════════════════════════════════════════════════\n\n/**\n * ODL SpecialTableProcessor 포팅: 연속된 \"구분:\", \"항목:\", \"종류:\" 등\n * 한국어 key-value 패턴을 2열 테이블로 변환.\n *\n * 동작:\n * 1) paragraph 블록의 텍스트에서 한국어 key-value 패턴 감지\n * 2) \":\"가 있으면 key | value 2열, 없으면 colSpan=2 (전체 행)\n * 3) 연속된 패턴을 하나의 테이블로 그룹화\n */\nconst KOREAN_TABLE_HEADER_RE = /^\\(?(구분|항목|종류|분류|유형|대상|내용|기간|금액|비율|방법|절차|요건|조건|근거|목적|범위|기준)\\)?[:\\s]/\n\n/** KV 오탐 패턴: 시간(14:30), URL(://), 숫자:숫자(3:2) */\nconst KV_FALSE_POSITIVE_RE = /\\d{1,2}:\\d{2}|:\\/\\/|\\d+:\\d+/\n\nfunction detectSpecialKoreanTables(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n let kvLines: { key: string; value: string; block: IRBlock }[] = []\n\n const flushKvTable = () => {\n if (kvLines.length < 2) {\n // 2행 미만이면 테이블로 만들 가치 없음 → 원래 블록 복원\n for (const kv of kvLines) result.push(kv.block)\n kvLines = []\n return\n }\n\n // 2열 테이블 생성\n const cells: import(\"../types.js\").IRCell[][] = kvLines.map(kv => {\n if (kv.value) {\n return [\n { text: kv.key, colSpan: 1, rowSpan: 1 },\n { text: kv.value, colSpan: 1, rowSpan: 1 },\n ]\n }\n // \":\" 없는 줄 → 전체 행 (colSpan=2)\n return [\n { text: kv.key, colSpan: 2, rowSpan: 1 },\n { text: \"\", colSpan: 1, rowSpan: 1 },\n ]\n })\n\n const irTable: IRTable = {\n rows: cells.length,\n cols: 2,\n cells,\n hasHeader: true,\n }\n\n // 첫 블록의 위치 정보 사용\n const firstBlock = kvLines[0].block\n result.push({\n type: \"table\",\n table: irTable,\n pageNumber: firstBlock.pageNumber,\n bbox: firstBlock.bbox,\n })\n kvLines = []\n }\n\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text) {\n flushKvTable()\n result.push(block)\n continue\n }\n\n const text = block.text.trim()\n\n // \"구분: xxx\" 또는 \"항목: xxx\" 패턴 매칭\n if (KOREAN_TABLE_HEADER_RE.test(text)) {\n const colonIdx = text.indexOf(\":\")\n if (colonIdx >= 0) {\n kvLines.push({\n key: text.slice(0, colonIdx).trim(),\n value: text.slice(colonIdx + 1).trim(),\n block,\n })\n } else {\n // \":\" 없이 공백으로 구분된 경우: \"구분 xxx\"\n const spaceIdx = text.search(/\\s/)\n if (spaceIdx > 0) {\n kvLines.push({\n key: text.slice(0, spaceIdx).trim(),\n value: text.slice(spaceIdx + 1).trim(),\n block,\n })\n } else {\n kvLines.push({ key: text, value: \"\", block })\n }\n }\n continue\n }\n\n // key-value 패턴이 아닌 블록이 나오면 축적된 것을 flush\n // 단, 이미 수집 중이고 현재 블록이 \"label: value\" 형태면 계속 수집\n if (kvLines.length > 0 && text.includes(\":\")) {\n // 오탐 제외: 시간(14:30), URL(http://), 숫자:숫자(3:2), 괄호 포함\n if (!KV_FALSE_POSITIVE_RE.test(text) && !text.includes(\"(\") && !text.includes(\")\")) {\n const colonIdx = text.indexOf(\":\")\n const key = text.slice(0, colonIdx).trim()\n // key가 순수 한글 2~8자 (공백/괄호 없음)면 유효한 key-value 라인\n if (/^[가-힣]+$/.test(key) && key.length >= 2 && key.length <= 8) {\n kvLines.push({\n key,\n value: text.slice(colonIdx + 1).trim(),\n block,\n })\n continue\n }\n }\n }\n\n flushKvTable()\n result.push(block)\n }\n\n flushKvTable()\n return result\n}\n\n// ─── 머리글/바닥글 감지 ────────────────────────────\n\n/** 상단/하단 10% 영역에서 페이지간 반복 텍스트를 감지하여 제거 대상 인덱스 반환 */\nfunction removeHeaderFooterBlocks(\n blocks: IRBlock[],\n pageHeights: Map<number, number>,\n warnings: ParseWarning[],\n): number[] {\n const ZONE_RATIO = 0.1 // 상하 10%\n const MIN_REPEAT = 3 // 최소 3페이지에서 반복\n\n // 페이지별 상단/하단 텍스트 수집\n const headerTexts = new Map<number, string[]>() // page → texts\n const footerTexts = new Map<number, string[]>()\n\n for (let bi = 0; bi < blocks.length; bi++) {\n const b = blocks[bi]\n if (!b.bbox || !b.pageNumber || !b.text?.trim()) continue\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\n if (!ph) continue\n\n const blockTop = ph - (b.bbox.y + b.bbox.height) // PDF Y좌표는 아래가 0\n const blockBottom = ph - b.bbox.y\n\n if (blockBottom <= ph * ZONE_RATIO) {\n // 하단 영역\n const arr = footerTexts.get(b.pageNumber) || []\n arr.push(b.text.trim())\n footerTexts.set(b.pageNumber, arr)\n } else if (blockTop >= ph * (1 - ZONE_RATIO)) {\n // 상단 영역\n const arr = headerTexts.get(b.pageNumber) || []\n arr.push(b.text.trim())\n headerTexts.set(b.pageNumber, arr)\n }\n }\n\n // 반복 패턴 찾기: 페이지 번호 변동 허용 (숫자만 다른 경우)\n const repeatedPatterns = new Set<string>()\n for (const textsMap of [headerTexts, footerTexts]) {\n const patternCount = new Map<string, number>()\n for (const [, texts] of textsMap) {\n for (const t of texts) {\n // 숫자를 와일드카드로 치환하여 \"- 1 -\", \"- 2 -\" 같은 패턴 통합\n const normalized = t.replace(/\\d+/g, \"#\")\n patternCount.set(normalized, (patternCount.get(normalized) || 0) + 1)\n }\n }\n for (const [pattern, count] of patternCount) {\n if (count >= MIN_REPEAT) repeatedPatterns.add(pattern)\n }\n }\n\n if (repeatedPatterns.size === 0) return []\n\n // 반복 패턴에 매칭되는 블록 인덱스 수집\n const removeIndices: number[] = []\n for (let bi = 0; bi < blocks.length; bi++) {\n const b = blocks[bi]\n if (!b.bbox || !b.pageNumber || !b.text?.trim()) continue\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\n if (!ph) continue\n\n const blockTop = ph - (b.bbox.y + b.bbox.height)\n const blockBottom = ph - b.bbox.y\n const inZone = blockBottom <= ph * ZONE_RATIO || blockTop >= ph * (1 - ZONE_RATIO)\n if (!inZone) continue\n\n const normalized = b.text.trim().replace(/\\d+/g, \"#\")\n if (repeatedPatterns.has(normalized)) {\n removeIndices.push(bi)\n }\n }\n\n if (removeIndices.length > 0) {\n warnings.push({ message: `${removeIndices.length}개 머리글/바닥글 요소 제거됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n }\n\n return removeIndices\n}\n\nfunction mergeKoreanLines(text: string): string {\n if (!text) return \"\"\n const lines = text.split(\"\\n\")\n if (lines.length <= 1) return text\n const result: string[] = [lines[0]]\n\n for (let i = 1; i < lines.length; i++) {\n const prev = result[result.length - 1]\n const curr = lines[i]\n // 마크다운 헤딩 라인(#)은 병합하지 않음\n if (/^#{1,6}\\s/.test(prev) || /^#{1,6}\\s/.test(curr)) {\n result.push(curr)\n continue\n }\n if (/[가-힣·,\\-]$/.test(prev) && /^[가-힣(]/.test(curr) && !curr.trimStart().startsWith(\"|\") && !startsWithMarker(curr) && !isStandaloneHeader(prev)) {\n result[result.length - 1] = prev + \" \" + curr\n } else {\n result.push(curr)\n }\n }\n return result.join(\"\\n\")\n}\n","/**\n * kordoc — 모두 파싱해버리겠다\n *\n * HWP, HWPX, PDF → Markdown 변환 통합 라이브러리\n */\n\nimport { readFile } from \"fs/promises\"\nimport { detectFormat, isHwpxFile, isOldHwpFile, isPdfFile } from \"./detect.js\"\nimport { parseHwpxDocument } from \"./hwpx/parser.js\"\nimport { parseHwp5Document } from \"./hwp5/parser.js\"\nimport { parsePdfDocument } from \"./pdf/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 return parseHwpx(buffer, options)\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 return await parsePdfDocument(buffer, options)\n } catch (err) {\n return { success: false, fileType: \"pdf\", error: err instanceof Error ? err.message : \"PDF 파싱 실패\", 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, isHwpxFile, isOldHwpFile, isPdfFile } 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","/** 양식(서식) 필드 인식 — 테이블 기반 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","/**\n * Markdown → HWPX 역변환 (MVP)\n *\n * 지원: 단락, 헤딩, 테이블 (텍스트+구조만, 스타일 없음)\n * jszip으로 HWPX ZIP 패키징.\n */\n\nimport JSZip from \"jszip\"\nimport type { IRBlock, IRTable, IRCell } from \"../types.js\"\n\nconst HWPML_NS = \"http://www.hancom.co.kr/hwpml/2016/HwpMl\"\n\n/**\n * 마크다운 텍스트를 HWPX (ArrayBuffer)로 변환.\n *\n * @example\n * ```ts\n * import { markdownToHwpx } from \"kordoc\"\n * const hwpxBuffer = await markdownToHwpx(\"# 제목\\n\\n본문 텍스트\")\n * writeFileSync(\"output.hwpx\", Buffer.from(hwpxBuffer))\n * ```\n */\nexport async function markdownToHwpx(markdown: string): Promise<ArrayBuffer> {\n const blocks = parseMarkdownToBlocks(markdown)\n const sectionXml = blocksToSectionXml(blocks)\n\n const zip = new JSZip()\n\n // mimetype (압축 없이)\n zip.file(\"mimetype\", \"application/hwp+zip\", { compression: \"STORE\" })\n\n // 매니페스트\n zip.file(\"Contents/content.hpf\", generateManifest())\n\n // 섹션 콘텐츠\n zip.file(\"Contents/section0.xml\", sectionXml)\n\n return await zip.generateAsync({ type: \"arraybuffer\" })\n}\n\n// ─── 마크다운 파싱 (간이) ────────────────────────────\n\ninterface MdBlock {\n type: \"paragraph\" | \"heading\" | \"table\"\n text?: string\n level?: number // heading level\n rows?: string[][] // table rows\n}\n\nfunction parseMarkdownToBlocks(md: string): MdBlock[] {\n const lines = md.split(\"\\n\")\n const blocks: MdBlock[] = []\n let i = 0\n\n while (i < lines.length) {\n const line = lines[i]\n\n // 빈 줄 스킵\n if (!line.trim()) { i++; continue }\n\n // 헤딩\n const headingMatch = line.match(/^(#{1,6})\\s+(.+)$/)\n if (headingMatch) {\n blocks.push({ type: \"heading\", text: headingMatch[2].trim(), level: headingMatch[1].length })\n i++; continue\n }\n\n // 테이블\n if (line.trimStart().startsWith(\"|\")) {\n const tableRows: string[][] = []\n while (i < lines.length && lines[i].trimStart().startsWith(\"|\")) {\n const row = lines[i]\n // 구분선(| --- | --- |) 스킵\n if (/^[\\s|:\\-]+$/.test(row)) {\n i++; continue\n }\n const cells = row.split(\"|\").slice(1, -1).map(c => c.trim())\n if (cells.length > 0) tableRows.push(cells)\n i++\n }\n if (tableRows.length > 0) {\n blocks.push({ type: \"table\", rows: tableRows })\n }\n continue\n }\n\n // 일반 단락\n blocks.push({ type: \"paragraph\", text: line.trim() })\n i++\n }\n\n return blocks\n}\n\n// ─── HWPX XML 생성 ──────────────────────────────────\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n}\n\nfunction generateParagraph(text: string): string {\n return `<hp:p><hp:run><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`\n}\n\nfunction generateTable(rows: string[][]): string {\n const trElements = rows.map(row => {\n const tdElements = row.map(cell =>\n `<hp:tc><hp:cellSpan colSpan=\"1\" rowSpan=\"1\"/>${generateParagraph(cell)}</hp:tc>`\n ).join(\"\")\n return `<hp:tr>${tdElements}</hp:tr>`\n }).join(\"\")\n return `<hp:tbl>${trElements}</hp:tbl>`\n}\n\nfunction blocksToSectionXml(blocks: MdBlock[]): string {\n const body = blocks.map(block => {\n switch (block.type) {\n case \"heading\":\n return generateParagraph(block.text || \"\")\n case \"table\":\n return block.rows ? generateTable(block.rows) : \"\"\n case \"paragraph\":\n return generateParagraph(block.text || \"\")\n default:\n return \"\"\n }\n }).join(\"\\n \")\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hs:sec xmlns:hs=\"${HWPML_NS}\" xmlns:hp=\"${HWPML_NS}\">\n ${body}\n</hs:sec>`\n}\n\nfunction generateManifest(): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<opf:package xmlns:opf=\"http://www.idpf.org/2007/opf\">\n <opf:manifest>\n <opf:item id=\"s0\" href=\"section0.xml\" media-type=\"application/xml\"/>\n </opf:manifest>\n <opf:spine>\n <opf:itemref idref=\"s0\"/>\n </opf:spine>\n</opf:package>`\n}\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) return Math.abs(a.length - b.length)\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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","names":["inflateRawSync","MAX_DECOMPRESS_SIZE","inflateRawSync","require","JSZip","i","j"]}