{"version":3,"sources":["/Users/mong-e/workspace/kordoc/dist/chunk-GZ7W64QU.cjs","../src/utils.ts","../src/types.ts"],"names":[],"mappings":"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","file":"/Users/mong-e/workspace/kordoc/dist/chunk-GZ7W64QU.cjs","sourcesContent":[null,"/** kordoc 공용 유틸리티 */\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n    return buf.buffer as ArrayBuffer\n  }\n  return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n  constructor(message: string) {\n    super(message)\n    this.name = \"KordocError\"\n  }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n  if (err instanceof KordocError) return err.message\n  return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n  if (name.includes(\"\\x00\")) return true\n  const normalized = name.replace(/\\\\/g, \"/\")\n  const segments = normalized.split(\"/\")\n  return segments.some(s => s === \"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── HWPX 섹션 href 해석 (parser/roundtrip 공용) ────────────────────\n\n/**\n * manifest href를 본문 섹션 경로(`Contents/sectionN.xml`)로 정규화.\n * 비본문 XML(header/script/settings)·path traversal·드라이브 경로는 null로 거른다.\n * 백슬래시 구분자를 슬래시로 통일해 parser와 roundtrip이 동일한 목록을 만든다.\n */\nexport function normalizeSectionHref(href: string): string | null {\n  if (!href) return null\n  let normalized = href.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\")\n  if (isPathTraversal(normalized)) return null\n  if (/^[Ss]ection\\d+\\.xml$/.test(normalized)) normalized = \"Contents/\" + normalized\n  return /(?:^|\\/)[Ss]ection\\d+\\.xml$/.test(normalized) ? normalized : null\n}\n\n/** sectionN.xml을 N 숫자 순서로 정렬 (section10 > section2). */\nexport function compareSectionPaths(a: string, b: string): number {\n  const ai = Number(a.match(/[Ss]ection(\\d+)\\.xml$/)?.[1] ?? Number.MAX_SAFE_INTEGER)\n  const bi = Number(b.match(/[Ss]ection(\\d+)\\.xml$/)?.[1] ?? Number.MAX_SAFE_INTEGER)\n  return ai === bi ? a.localeCompare(b) : ai - bi\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n  buffer: ArrayBuffer,\n  maxUncompressedSize = 256 * 1024 * 1024, // parser-shared MAX_DECOMPRESS_SIZE 와 동기\n  maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n  try {\n    const data = new DataView(buffer)\n    const len = buffer.byteLength\n    // EOCD 시그니처 역방향 스캔\n    let eocdOffset = -1\n    for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n      if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n    }\n    if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n    const entryCount = data.getUint16(eocdOffset + 10, true)\n    if (entryCount > maxEntries) {\n      throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n    }\n\n    const cdSize = data.getUint32(eocdOffset + 12, true)\n    const cdOffset = data.getUint32(eocdOffset + 16, true)\n    if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n    let totalUncompressed = 0\n    let pos = cdOffset\n    for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n      if (data.getUint32(pos, true) !== 0x02014b50) break\n      totalUncompressed += data.getUint32(pos + 24, true)\n      const nameLen = data.getUint16(pos + 28, true)\n      const extraLen = data.getUint16(pos + 30, true)\n      const commentLen = data.getUint16(pos + 32, true)\n      pos += 46 + nameLen + extraLen + commentLen\n    }\n\n    if (totalUncompressed > maxUncompressedSize) {\n      throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n    }\n\n    return { totalUncompressed, entryCount }\n  } catch (err) {\n    if (err instanceof KordocError) throw err\n    return { totalUncompressed: 0, entryCount: 0 }\n  }\n}\n\n/** XXE/Billion Laughs 방지 — DOCTYPE 제거 (내부 DTD 서브셋 포함) */\nexport function stripDtd(xml: string): string {\n  return xml.replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n  const trimmed = href.trim()\n  if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n  // 괄호 percent-encoding — 마크다운 링크 목적지에서 불균형 ')'는 링크를 조기 종료시켜\n  // 문법이 깨진다 (원문 하이퍼링크에 ')'가 박힌 실문서 존재). %28/%29는 의미 동일.\n  return trimmed.replace(/\\(/g, \"%28\").replace(/\\)/g, \"%29\")\n}\n\n// ─── 안전한 min/max (스택 오버플로 방지) ─────────────\n\n/** Math.min(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMin(arr: number[]): number {\n  let min = Infinity\n  for (let i = 0; i < arr.length; i++) if (arr[i] < min) min = arr[i]\n  return min\n}\n\n/** Math.max(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMax(arr: number[]): number {\n  let max = -Infinity\n  for (let i = 0; i < arr.length; i++) if (arr[i] > max) max = arr[i]\n  return max\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n  if (!(err instanceof Error)) return \"PARSE_ERROR\"\n  const msg = err.message\n  if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n  if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n  if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n  if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n  if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n  if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n  if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n  return \"PARSE_ERROR\"\n}\n","/** kordoc 공통 타입 정의 */\n\n// ─── 중간 표현 (Intermediate Representation) ─────────\n\nexport interface CellContext {\n  text: string\n  colSpan: number\n  rowSpan: number\n  /** HWP5 셀 열 주소 (0-based) — 병합 테이블 배치용 */\n  colAddr?: number\n  /** HWP5 셀 행 주소 (0-based) — 병합 테이블 배치용 */\n  rowAddr?: number\n}\n\n/** 블록 타입 — v2.0에서 heading, list, image, separator 추가 */\nexport type IRBlockType = \"paragraph\" | \"table\" | \"heading\" | \"list\" | \"image\" | \"separator\"\n\n/** 인라인 강조 run-span — 문단 텍스트를 서식 단위로 쪼갠 조각 (텍스트 연결 = block.text) */\nexport interface IRSpan {\n  text: string\n  bold?: boolean\n  italic?: boolean\n  strike?: boolean\n  code?: boolean\n}\n\nexport interface IRBlock {\n  type: IRBlockType\n  text?: string\n  table?: IRTable\n  /** 헤딩 레벨 (1-6), type=\"heading\"일 때 사용 */\n  level?: number\n  /** 원본 페이지 번호 (1-based) */\n  pageNumber?: number\n  /** 바운딩 박스 — PDF에서만 제공 */\n  bbox?: BoundingBox\n  /** 텍스트 스타일 정보 (선택) */\n  style?: InlineStyle\n  /** 리스트 타입, type=\"list\"일 때 사용 */\n  listType?: \"ordered\" | \"unordered\"\n  /** 중첩 리스트 아이템 */\n  children?: IRBlock[]\n  /** 하이퍼링크 URL */\n  href?: string\n  /** 각주/미주 텍스트 (인라인 삽입용) */\n  footnoteText?: string\n  /** 이미지 데이터 (type=\"image\"일 때) */\n  imageData?: ImageData\n  /** 인라인 강조 run-span (선택 — kordoc 생성 hwpx 왕복 채널 + 외래 실속성 볼드/이탤릭).\n   *  존재하면 마크다운 변환이 **·*·` 마커를 재방출한다 */\n  spans?: IRSpan[]\n  /** 인용문 문단 (선택 — kordoc 생성 hwpx 왕복 채널) — 마크다운 변환이 \"> \" 접두 재방출 */\n  quote?: boolean\n  /**\n   * 문단 들여쓰기(HWPUNIT, 선택) — HWPX paraPr `<hh:margin>` 자식요소형 hc:left\n   * (+양수 hc:intent 첫줄분). 마크다운 방출엔 쓰지 않는 관찰 슬롯 — gongmun 리스트\n   * depth 재유도·양식 분석 등 소비자 몫 (v4.0.4)\n   */\n  indent?: number\n  /**\n   * gongmun 리스트 단계(1~7, 선택) — indent를 levelIndent 단위로 역산한 소비 결과\n   * (v4.0.5). md 리스트 문법과 충돌하는 부호('- '·'1) ') 문단에만 채워지며,\n   * 마크다운 변환이 2칸/단계 선행 공백을 방출해 재생성 시 depth가 복원된다\n   */\n  listDepth?: number\n}\n\n/** 추출된 이미지 바이너리 데이터 */\nexport interface ImageData {\n  /** 이미지 바이너리 */\n  data: Uint8Array\n  /** MIME 타입 (image/png, image/jpeg, image/gif, image/bmp, image/wmf, image/emf) */\n  mimeType: string\n  /** 원본 파일명 (있는 경우) */\n  filename?: string\n}\n\n/** 바운딩 박스 — PDF 포인트 단위 (72pt = 1인치) */\nexport interface BoundingBox {\n  page: number\n  x: number\n  y: number\n  width: number\n  height: number\n}\n\n/** 인라인 텍스트 스타일 */\nexport interface InlineStyle {\n  bold?: boolean\n  italic?: boolean\n  /** 취소선 — 법령 개정문 등의 삭제 표시. 판정은 취소선 모양 whitelist (비트만 믿으면 오탐) */\n  strike?: boolean\n  fontSize?: number\n  fontName?: string\n}\n\nexport interface IRTable {\n  rows: number\n  cols: number\n  cells: IRCell[][]\n  /** 첫 행을 헤더로 렌더링할지 여부 (현재: rows > 1이면 true — 의미적 감지가 아닌 레이아웃 힌트) */\n  hasHeader: boolean\n  /** 표 캡션 (예: \"표 1. 부서별 예산\") — v3.0 */\n  caption?: string\n  /**\n   * 캡션 내부 블록 콘텐츠 — v4.2.8 (#55).\n   * 캡션 안 중첩표·문단을 구조 그대로, 원문 순서대로 보존한다 (IRCell.blocks와 같은 계약).\n   * 표가 있을 때만 채워지며, caption은 하위 호환용 평탄화 문자열로 계속 제공된다.\n   */\n  captionBlocks?: IRBlock[]\n}\n\nexport interface IRCell {\n  text: string\n  colSpan: number\n  rowSpan: number\n  /**\n   * 셀 내부 블록 콘텐츠 — v3.0.\n   * 중첩 표·이미지·다중 문단을 구조 그대로, 문서(원문) 순서대로 보존한다.\n   * 표와 텍스트가 한 줄에 번갈아 놓인 셀도 배치 순서를 따른다 (v4.2.3, #49).\n   * blocks가 있으면 text는 blocks의 평탄화 텍스트(하위 호환용)다.\n   */\n  blocks?: IRBlock[]\n  /** 제목 셀 여부 (HWP5 width_ref bit2 / HWPX header 속성) — v3.0 */\n  isHeader?: boolean\n}\n\n// ─── 메타데이터 ─────────────────────────────────────\n\n/** 문서 메타데이터 — 각 포맷에서 추출 가능한 필드만 채워짐 */\nexport interface DocumentMetadata {\n  /** 문서 제목 */\n  title?: string\n  /** 작성자 */\n  author?: string\n  /** 작성 프로그램 (예: \"한글 2020\", \"Adobe Acrobat\") */\n  creator?: string\n  /** 생성일시 (ISO 8601) */\n  createdAt?: string\n  /** 수정일시 (ISO 8601) */\n  modifiedAt?: string\n  /** 페이지/섹션 수 */\n  pageCount?: number\n  /** 문서 포맷 버전 (예: HWP \"5.1.0.1\") */\n  version?: string\n  /** 설명 */\n  description?: string\n  /** 키워드 */\n  keywords?: string[]\n}\n\n// ─── 파싱 옵션 ──────────────────────────────────────\n\n/** 파싱 옵션 — parse() 함수에 전달 */\nexport interface ParseOptions {\n  /**\n   * 파싱할 페이지/섹션 범위 (1-based).\n   * - 배열: [1, 2, 3]\n   * - 문자열: \"1-3\", \"1,3,5-7\"\n   *\n   * PDF: 정확한 페이지 단위. HWP/HWPX: 섹션 단위 근사치.\n   */\n  pages?: number[] | string\n  /** 이미지 기반 PDF OCR (선택).\n   *  - `true`: 내장 엔진(PP-OCRv5 korean, ~18MB 자동 다운로드)으로 OCR 필요 판정\n   *    페이지만 인식 (스캔 페이지·글꼴 매핑 깨진 페이지). 정상 페이지는 파싱 결과 유지.\n   *  - `\"force\"`: 전 페이지를 내장 엔진으로 강제 OCR.\n   *  - 함수: 사용자 제공 OcrProvider (Claude Vision·Tesseract 등) — 판정은 `true`와 동일. */\n  ocr?: boolean | \"force\" | OcrProvider\n  /** 진행률 콜백 — current: 현재 페이지/섹션, total: 전체 수 */\n  onProgress?: (current: number, total: number) => void\n  /** PDF 머리글/바닥글 자동 제거 */\n  removeHeaderFooter?: boolean\n  /** 표 오른쪽 끝의 빈 열(서식 문서의 입력란) 보존 (#47).\n   *  기본 false: 마크다운 가독성을 위해 후행 빈 열을 트림.\n   *  양식 인식 경로(parse_form·fill)는 내부적으로 항상 켠다. */\n  keepTrailingEmptyCols?: boolean\n  /** 원본 파일 경로 (DRM COM fallback에 필요, 내부 전용) */\n  filePath?: string\n  /**\n   * PDF 수식 OCR 활성화 (기본 false).\n   *\n   * 활성화 시 각 PDF 페이지를 이미지로 렌더링 → YOLOv8 기반 수식 영역 검출 →\n   * TrOCR 기반 LaTeX 인식. 감지된 수식은 `$...$` (inline) / `$$...$$` (display) 로\n   * 블록 텍스트에 삽입된다.\n   *\n   * 필수 optional 의존성: `onnxruntime-node`, `@huggingface/transformers`,\n   * `@hyzyla/pdfium`, `sharp`. 미설치 시 parse 에 실패하지 않고 **경고만** 남기고\n   * 수식 인식은 skip 한다 (일반 텍스트 추출은 정상 동작).\n   *\n   * 모델(~155MB) 은 첫 사용 시 HuggingFace 에서 자동 다운로드 되어\n   * `~/.cache/kordoc/models/pix2text/` 에 SHA-256 검증과 함께 저장된다.\n   */\n  formulaOcr?: boolean\n  /**\n   * 레이아웃 표 페이지 반복 헤더(러닝 헤더) 정리 휴리스틱 활성화 (기본 false).\n   *\n   * 구형 HWP5 문서가 페이지마다 재삽입한 짧은 번호매김 러닝 헤더\n   * (\"2. 과제 구축 내용\")를 최초 1회만 남기고 이후 중복을 제거한다.\n   * 다만 페이지 위치 정보가 없는 HWP5 특성상 이 휴리스틱은 정당하게 반복되는\n   * 번호매김 문단(예: 붙임/별지별 재번호 \"1. 목적\")도 삭제할 수 있어 opt-in 으로\n   * 둔다. 활성 시 제거가 발생하면 HIDDEN_TEXT_FILTERED 경고를 남긴다.\n   */\n  dedupeRunningHeaders?: boolean\n  /**\n   * 추출된 이미지를 마크다운에 base64 data URI 로 인라인 (기본 false).\n   *\n   * 활성화 시 `![image](image_001.bmp)` 참조를 `![image](data:image/png;base64,...)` 로\n   * 치환한다. 임베드된 BMP 는 PNG 로 무손실 압축 후 인라인하여 용량을 크게 줄인다.\n   * 별도 이미지 파일 없이 자체 완결형 마크다운이 되어 MCP/AI 에이전트 소비에 적합하다.\n   * (현재 HWP5 경로 지원)\n   */\n  inlineImages?: boolean\n}\n\n// ─── 파싱 경고 ──────────────────────────────────────\n\n/** 파싱 중 스킵/실패한 요소 보고 */\nexport interface ParseWarning {\n  /** 관련 페이지 번호 (알 수 있는 경우) */\n  page?: number\n  /** 경고 메시지 */\n  message: string\n  /** 구조화된 경고 코드 */\n  code: WarningCode\n}\n\nexport type WarningCode =\n  | \"SKIPPED_IMAGE\"\n  | \"SKIPPED_OLE\"\n  | \"TRUNCATED_TABLE\"\n  | \"OCR_FALLBACK\"\n  | \"UNSUPPORTED_ELEMENT\"\n  | \"BROKEN_ZIP_RECOVERY\"\n  | \"HIDDEN_TEXT_FILTERED\"\n  | \"MALFORMED_XML\"\n  | \"PARTIAL_PARSE\"\n  | \"LENIENT_CFB_RECOVERY\"\n  | \"NEEDS_OCR\"\n  | \"OCR_FAILED\"\n  | \"OCR_APPLIED\"\n  | \"COM_EMPTY\"\n  | \"DRM_COM_FALLBACK\"\n\n/** 문서 구조 (헤딩 트리) */\nexport interface OutlineItem {\n  level: number\n  text: string\n  pageNumber?: number\n}\n\n// ─── 에러 코드 ──────────────────────────────────────\n\n/** 구조화된 에러 코드 — 프로그래밍적 에러 핸들링용 */\nexport type ErrorCode =\n  | \"EMPTY_INPUT\"\n  | \"UNSUPPORTED_FORMAT\"\n  | \"ENCRYPTED\"\n  | \"DRM_PROTECTED\"\n  | \"CORRUPTED\"\n  | \"DECOMPRESSION_BOMB\"\n  | \"ZIP_BOMB\"\n  | \"IMAGE_BASED_PDF\"\n  | \"NO_SECTIONS\"\n  | \"PARSE_ERROR\"\n  | \"MISSING_DEPENDENCY\"\n\n// ─── 파싱 결과 (discriminated union) ────────────────\n\nexport type FileType = \"hwpx\" | \"hwp\" | \"hwp3\" | \"hwpml\" | \"pdf\" | \"xlsx\" | \"xls\" | \"docx\" | \"image\" | \"unknown\"\n\ninterface ParseResultBase {\n  fileType: FileType\n  /** 페이지/섹션 수 — PDF: 실제 페이지 수, HWP/HWPX: 섹션 수, XLSX: 시트 수 */\n  pageCount?: number\n  /** 이미지 기반 PDF 여부 (텍스트 추출 불가) */\n  isImageBased?: boolean\n}\n\nexport interface ParseSuccess extends ParseResultBase {\n  success: true\n  /** 추출된 마크다운 텍스트 */\n  markdown: string\n  /**\n   * 중간 표현 블록 (구조화된 데이터 접근용).\n   * 블록은 문서(원문) 읽기 순서를 따른다 — 한 문단 안에 글자취급(treatAsChar)\n   * 표와 텍스트가 섞여 있어도 배치 순서대로 방출된다 (v4.2.3, #50).\n   */\n  blocks: IRBlock[]\n  /** 문서 메타데이터 */\n  metadata?: DocumentMetadata\n  /** 문서 구조 (헤딩 트리) — v2.0 */\n  outline?: OutlineItem[]\n  /** 파싱 중 발생한 경고 — v2.0 */\n  warnings?: ParseWarning[]\n  /** 추출된 이미지 목록 — 마크다운에서 파일명으로 참조됨 */\n  images?: ExtractedImage[]\n  /** 페이지별 텍스트 품질 신호 — PDF에서만 제공 */\n  pageQuality?: PageQuality[]\n  /** 문서 단위 품질 요약 — PDF에서만 제공 */\n  qualitySummary?: DocumentQualitySummary\n}\n\n/** 페이지별 텍스트 품질 신호 (PDF 전용). 자세한 설명은 src/pdf/quality.ts */\nexport interface PageQuality {\n  page: number\n  textChars: number\n  hangulRatio: number\n  controlCharRatio: number\n  replacementCharRatio: number\n  puaRatio: number\n  needsOcr: boolean\n  ocrReason?: \"low_text\" | \"high_pua\" | \"high_control\" | \"high_replacement\" | \"garbled_hangul\"\n}\n\n/** 문서 단위 품질 요약 (PDF 전용). */\nexport interface DocumentQualitySummary {\n  totalPages: number\n  totalTextChars: number\n  avgHangulRatio: number\n  avgControlCharRatio: number\n  avgReplacementCharRatio: number\n  avgPuaRatio: number\n  lowTextPageCount: number\n  highPuaPageCount: number\n  needsOcr: boolean\n  ocrCandidatePages: number[]\n}\n\n/** 추출된 이미지 — ParseSuccess.images에 포함 */\nexport interface ExtractedImage {\n  /** 마크다운에서 참조되는 파일명 (예: image_001.png) */\n  filename: string\n  /** 이미지 바이너리 */\n  data: Uint8Array\n  /** MIME 타입 */\n  mimeType: string\n}\n\nexport interface ParseFailure extends ParseResultBase {\n  success: false\n  /** 오류 메시지 */\n  error: string\n  /** 구조화된 에러 코드 */\n  code?: ErrorCode\n}\n\nexport type ParseResult = ParseSuccess | ParseFailure\n\n// ─── 문서 비교 (Diff) ───────────────────────────────\n\nexport type DiffChangeType = \"added\" | \"removed\" | \"modified\" | \"unchanged\"\n\nexport interface BlockDiff {\n  type: DiffChangeType\n  /** 원본 블록 (added이면 undefined) */\n  before?: IRBlock\n  /** 변경 후 블록 (removed이면 undefined) */\n  after?: IRBlock\n  /** modified 테이블의 셀 단위 diff */\n  cellDiffs?: CellDiff[][]\n  /** 유사도 (0-1) */\n  similarity?: number\n}\n\nexport interface CellDiff {\n  type: DiffChangeType\n  before?: string\n  after?: string\n}\n\nexport interface DiffResult {\n  stats: { added: number; removed: number; modified: number; unchanged: number }\n  diffs: BlockDiff[]\n}\n\n// ─── 라운드트립 패치 (v3.0) ─────────────────────────\n\n/** 패치 중 매핑 실패/미지원으로 건너뛴 항목 — silent 실패 금지 */\nexport interface PatchSkip {\n  /** 건너뛴 사유 */\n  reason: string\n  /** 원본 쪽 내용 요약 (최대 80자) */\n  before?: string\n  /** 편집 쪽 내용 요약 (최대 80자) */\n  after?: string\n  /**\n   * 부분 적용 표시 — 변경이 적용은 됐지만(applied 계상) 편집 원형 그대로는\n   * 아님 (예: 셀 내 줄 추가를 마지막 문단에 병합). 완전 미적용 skip과 구분.\n   */\n  partial?: boolean\n}\n\n/** patchHwpx 옵션 */\nexport interface PatchOptions {\n  /** 패치 후 재파싱 자동 검증 (기본 true) */\n  verify?: boolean\n}\n\n/** patchHwpx / patchBlocks 결과 */\nexport interface PatchResult {\n  success: boolean\n  /** 패치된 HWPX (success=true) */\n  data?: Uint8Array\n  /** 적용된 변경 수 */\n  applied: number\n  /** 매핑 실패 항목 (이유 포함) */\n  skipped: PatchSkip[]\n  /**\n   * 무손실 검증 (patchHwpx/patchHwp 전용): 패치본 재파싱 vs 편집 마크다운의\n   * 잔차 diff — modified/added/removed가 0이어야 의도가 전부 반영된 것.\n   * session.patchBlocks는 이 필드를 채우지 않는다 (changes 참조).\n   */\n  verification?: DiffResult\n  /**\n   * 변경 가시화 (session.patchBlocks 전용): 패치 전 → 후 문서 diff —\n   * 적용된 편집 수만큼 modified가 나오는 것이 정상. verification과 의미가\n   * 정반대이므로 혼용 금지.\n   */\n  changes?: DiffResult\n  /** 실패 사유 (success=false) */\n  error?: string\n}\n\n// ─── 양식 인식 ──────────────────────────────────────\n\nexport interface FormField {\n  label: string\n  value: string\n  /** 0-based 소스 행 */\n  row: number\n  /** 0-based 소스 열 */\n  col: number\n  /** 매칭된 입력 키(정규화) — 모호 라벨 거부 가드(fillWithUniqueGuard)의 집계 기준 */\n  key?: string\n}\n\nexport interface FormResult {\n  fields: FormField[]\n  /** 양식 확신도 (0-1) */\n  confidence: number\n}\n\n// ─── OCR 프로바이더 ─────────────────────────────────\n\n/** 사용자 제공 OCR 함수 — 페이지 이미지를 받아 텍스트 반환 */\nexport type OcrProvider = (\n  pageImage: Uint8Array,\n  pageNumber: number,\n  mimeType: \"image/png\"\n) => Promise<string>\n\n// ─── Watch 모드 ─────────────────────────────────────\n\nexport interface WatchOptions {\n  dir: string\n  outDir?: string\n  webhook?: string\n  format?: \"markdown\" | \"json\"\n  pages?: string\n  silent?: boolean\n}\n\n// ─── 헤딩 감지 공통 임계값 ──────────────────────────\n\n/** 폰트 크기 비율 → heading level (전 파서 공통) */\nexport const HEADING_RATIO_H1 = 1.5\nexport const HEADING_RATIO_H2 = 1.3\nexport const HEADING_RATIO_H3 = 1.15\n\n// ─── 내부 파서 반환 타입 ─────────────────────────────\n\n/** 내부 파서가 index.ts에 반환하는 공통 타입 (HWP5/HWPX/PDF/XLSX/DOCX) */\nexport interface InternalParseResult {\n  markdown: string\n  blocks: IRBlock[]\n  metadata?: DocumentMetadata\n  outline?: OutlineItem[]\n  warnings?: ParseWarning[]\n  images?: ExtractedImage[]\n  /** PDF 전용: 이미지 기반 PDF 여부 */\n  isImageBased?: boolean\n  /** PDF 전용: 페이지별 품질 신호 */\n  pageQuality?: PageQuality[]\n  /** PDF 전용: 문서 단위 품질 요약 */\n  qualitySummary?: DocumentQualitySummary\n}\n"]}