kordoc 4.0.5 → 4.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +806 -800
  3. package/dist/{-QVHGOOCR.js → -6HWEFXVV.js} +9 -9
  4. package/dist/{chunk-6PED5OOG.js → chunk-2SN3CKME.js} +2 -2
  5. package/dist/chunk-2SN3CKME.js.map +1 -0
  6. package/dist/{chunk-MOL7MDBG.js → chunk-3TBUDJDE.js} +1 -1
  7. package/dist/chunk-3TBUDJDE.js.map +1 -0
  8. package/dist/{chunk-FKK4K53F.js → chunk-5OKHAJ62.js} +2 -2
  9. package/dist/chunk-5OKHAJ62.js.map +1 -0
  10. package/dist/{chunk-5RPYBC2Q.js → chunk-6GBLFQMA.js} +1 -1
  11. package/dist/chunk-6GBLFQMA.js.map +1 -0
  12. package/dist/{chunk-GE43BE46.js → chunk-AIQ3ISQU.js} +1 -1
  13. package/dist/chunk-AIQ3ISQU.js.map +1 -0
  14. package/dist/{chunk-OMERK4AW.js → chunk-BKWHX3RC.js} +3 -3
  15. package/dist/chunk-BKWHX3RC.js.map +1 -0
  16. package/dist/{chunk-MEPHGCPQ.js → chunk-CFQLK3LM.js} +1 -1
  17. package/dist/chunk-CFQLK3LM.js.map +1 -0
  18. package/dist/{chunk-6BABRVOJ.js → chunk-CNM75ZSX.js} +2 -2
  19. package/dist/chunk-CNM75ZSX.js.map +1 -0
  20. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  21. package/dist/{chunk-6WDPB4SN.js → chunk-NGSHDBQR.js} +3 -3
  22. package/dist/chunk-NGSHDBQR.js.map +1 -0
  23. package/dist/{chunk-ZGMMLYAL.js → chunk-ONXVBURQ.js} +2 -2
  24. package/dist/chunk-ONXVBURQ.js.map +1 -0
  25. package/dist/{chunk-QK2G34WE.js → chunk-Q7EN4EUJ.js} +2 -2
  26. package/dist/chunk-Q7EN4EUJ.js.map +1 -0
  27. package/dist/{chunk-Z6Q3YG22.js → chunk-TJJTLM76.js} +197 -98
  28. package/dist/chunk-TJJTLM76.js.map +1 -0
  29. package/dist/{chunk-6R5FY47W.cjs → chunk-U25XGCNH.cjs} +2 -2
  30. package/dist/chunk-U25XGCNH.cjs.map +1 -0
  31. package/dist/{chunk-DCZVOIEO.cjs → chunk-X34YWRL5.cjs} +1 -1
  32. package/dist/chunk-X34YWRL5.cjs.map +1 -0
  33. package/dist/cli.js +18 -18
  34. package/dist/cli.js.map +1 -1
  35. package/dist/{detect-RI2MQ33K.js → detect-NTR2FUEG.js} +2 -2
  36. package/dist/{formula-JCNF43NE.js → formula-KOQV353G.js} +1 -1
  37. package/dist/formula-KOQV353G.js.map +1 -0
  38. package/dist/{formula-RXVSQPXI.js → formula-MEDGYQXS.js} +1 -1
  39. package/dist/formula-MEDGYQXS.js.map +1 -0
  40. package/dist/{formula-5NKVS2LR.cjs → formula-ZYUDQHD3.cjs} +1 -1
  41. package/dist/formula-ZYUDQHD3.cjs.map +1 -0
  42. package/dist/index.cjs +328 -229
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.js +191 -92
  45. package/dist/index.js.map +1 -1
  46. package/dist/mcp.js +14 -14
  47. package/dist/mcp.js.map +1 -1
  48. package/dist/page-range-CIRRJPXJ.js +7 -0
  49. package/dist/page-range-OF5I4PQY.js +8 -0
  50. package/dist/page-range-TPIRSA3J.cjs +8 -0
  51. package/dist/page-range-TPIRSA3J.cjs.map +1 -0
  52. package/dist/{parser-3MAYZUJX.js → parser-5EHWYJMC.js} +93 -7
  53. package/dist/parser-5EHWYJMC.js.map +1 -0
  54. package/dist/{parser-2V5SPTU7.cjs → parser-E6U5TNAU.cjs} +105 -19
  55. package/dist/parser-E6U5TNAU.cjs.map +1 -0
  56. package/dist/{parser-YSHZC5FQ.js → parser-UGB3NIVH.js} +92 -6
  57. package/dist/parser-UGB3NIVH.js.map +1 -0
  58. package/dist/{profile-io-3EECPDHG.js → profile-io-CAKRPQRD.js} +3 -3
  59. package/dist/{provider-G4C2V2PD.cjs → provider-5K7O3Z2O.cjs} +1 -1
  60. package/dist/provider-5K7O3Z2O.cjs.map +1 -0
  61. package/dist/{provider-AKROB7WQ.js → provider-7H4CPZYS.js} +1 -1
  62. package/dist/provider-7H4CPZYS.js.map +1 -0
  63. package/dist/{provider-4ZJKV3DC.js → provider-H4WWSPOV.js} +1 -1
  64. package/dist/provider-H4WWSPOV.js.map +1 -0
  65. package/dist/render-P6663O4I.js +10 -0
  66. package/dist/seal-7PPTXKKV.js +10 -0
  67. package/dist/{setup-57FB3LSP.js → setup-QSJ2INNS.js} +1 -1
  68. package/dist/setup-QSJ2INNS.js.map +1 -0
  69. package/dist/{watch-7MHSCKEQ.js → watch-RMX427YR.js} +9 -9
  70. package/dist/watch-RMX427YR.js.map +1 -0
  71. package/package.json +97 -97
  72. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  73. package/dist/chunk-6BABRVOJ.js.map +0 -1
  74. package/dist/chunk-6PED5OOG.js.map +0 -1
  75. package/dist/chunk-6R5FY47W.cjs.map +0 -1
  76. package/dist/chunk-6WDPB4SN.js.map +0 -1
  77. package/dist/chunk-DCZVOIEO.cjs.map +0 -1
  78. package/dist/chunk-FKK4K53F.js.map +0 -1
  79. package/dist/chunk-GE43BE46.js.map +0 -1
  80. package/dist/chunk-MEPHGCPQ.js.map +0 -1
  81. package/dist/chunk-MOL7MDBG.js.map +0 -1
  82. package/dist/chunk-OMERK4AW.js.map +0 -1
  83. package/dist/chunk-QK2G34WE.js.map +0 -1
  84. package/dist/chunk-Z6Q3YG22.js.map +0 -1
  85. package/dist/chunk-ZGMMLYAL.js.map +0 -1
  86. package/dist/formula-5NKVS2LR.cjs.map +0 -1
  87. package/dist/formula-JCNF43NE.js.map +0 -1
  88. package/dist/formula-RXVSQPXI.js.map +0 -1
  89. package/dist/page-range-737B4EZW.js +0 -8
  90. package/dist/page-range-LNMXJU6W.js +0 -7
  91. package/dist/page-range-P7SDW6LR.cjs +0 -8
  92. package/dist/page-range-P7SDW6LR.cjs.map +0 -1
  93. package/dist/parser-2V5SPTU7.cjs.map +0 -1
  94. package/dist/parser-3MAYZUJX.js.map +0 -1
  95. package/dist/parser-YSHZC5FQ.js.map +0 -1
  96. package/dist/provider-4ZJKV3DC.js.map +0 -1
  97. package/dist/provider-AKROB7WQ.js.map +0 -1
  98. package/dist/provider-G4C2V2PD.cjs.map +0 -1
  99. package/dist/render-MJSQ4TG2.js +0 -10
  100. package/dist/seal-R3POFNOU.js +0 -10
  101. package/dist/setup-57FB3LSP.js.map +0 -1
  102. package/dist/watch-7MHSCKEQ.js.map +0 -1
  103. /package/dist/{-QVHGOOCR.js.map → -6HWEFXVV.js.map} +0 -0
  104. /package/dist/{detect-RI2MQ33K.js.map → detect-NTR2FUEG.js.map} +0 -0
  105. /package/dist/{page-range-737B4EZW.js.map → page-range-CIRRJPXJ.js.map} +0 -0
  106. /package/dist/{page-range-LNMXJU6W.js.map → page-range-OF5I4PQY.js.map} +0 -0
  107. /package/dist/{profile-io-3EECPDHG.js.map → profile-io-CAKRPQRD.js.map} +0 -0
  108. /package/dist/{render-MJSQ4TG2.js.map → render-P6663O4I.js.map} +0 -0
  109. /package/dist/{seal-R3POFNOU.js.map → seal-7PPTXKKV.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/shared/pua.ts","../src/table/builder.ts","../src/types.ts"],"sourcesContent":["/** kordoc 공용 유틸리티 */\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"KordocError\"\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n const segments = normalized.split(\"/\")\n return segments.some(s => s === \"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── 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 = 100 * 1024 * 1024,\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** XXE/Billion Laughs 방지 — DOCTYPE 제거 (내부 DTD 서브셋 포함) */\nexport function stripDtd(xml: string): string {\n return xml.replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n return trimmed\n}\n\n// ─── 안전한 min/max (스택 오버플로 방지) ─────────────\n\n/** Math.min(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMin(arr: number[]): number {\n let min = Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] < min) min = arr[i]\n return min\n}\n\n/** Math.max(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMax(arr: number[]): number {\n let max = -Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] > max) max = arr[i]\n return max\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n","/**\n * 한컴 PUA(Private Use Area) 문자 → 유니코드 표준 문자 매핑.\n *\n * 한글(HWP)은 글머리표·문자표 기호를 Symbol/Wingdings 계열 PUA 코드포인트\n * (U+F020~U+F0FF)와 한컴 자체 Supplementary PUA-A 영역(U+F0000대)에 저장한다.\n * 매핑 없이 마크다운에 내보내면 빈 네모로 깨진다.\n *\n * 매핑 출처: rhwp (MIT, https://github.com/edwardkim/rhwp)\n * paragraph_layout.rs map_pua_bullet_char — 한컴 PDF 정답지 시각 검증 테이블.\n */\n\n/** BMP Symbol 영역 (U+F020~U+F0FF) 매핑 — 키는 (code - 0xF000) */\nconst BMP_SYMBOL_MAP: Record<number, string> = {\n // 도형/기호\n 0x6c: \"●\", // ●\n 0x6d: \"●\", // ● (그림자 원 근사)\n 0x6e: \"■\", // ■\n 0x6f: \"□\", // □\n 0x70: \"□\", // □ (굵은 흰 사각 근사)\n 0x71: \"□\", // □ (그림자 근사)\n 0x72: \"□\", // □ (그림자 근사)\n 0x73: \"⬧\", // ⬧\n 0x74: \"⧫\", // ⧫\n 0x75: \"◆\", // ◆\n 0x76: \"❖\", // ❖\n 0x77: \"⬥\", // ⬥\n // 체크/별/점\n 0x9e: \"·\", // ·\n 0x9f: \"•\", // •\n 0xa0: \"·\", // · (한컴 PDF 정답지 정합 — ▪ 아님)\n 0xa1: \"⚪\", // ⚪\n 0xa2: \"○\", // ○\n 0xa3: \"○\", // ○\n 0xa4: \"◉\", // ◉\n 0xa5: \"◎\", // ◎\n 0xa7: \"▪\", // ▪\n 0xa8: \"◻\", // ◻\n 0xaa: \"✦\", // ✦\n 0xab: \"★\", // ★\n 0xac: \"✶\", // ✶\n 0xad: \"✴\", // ✴\n 0xae: \"✹\", // ✹\n // 손 모양\n 0x45: \"☜\", // ☜\n 0x46: \"☞\", // ☞\n 0x47: \"☝\", // ☝\n 0x48: \"☟\", // ☟\n // 체크마크\n 0xfb: \"✗\", // ✗\n 0xfc: \"✔\", // ✔\n 0xfd: \"☒\", // ☒\n 0xfe: \"☑\", // ☑\n // 화살표\n 0xe8: \"➔\", // ➔ (heavy wide-headed — 한컴 PDF 정답지 정합)\n 0xef: \"⇦\", // ⇦\n 0xf0: \"⇨\", // ⇨\n 0xf1: \"⇧\", // ⇧\n 0xf2: \"⇩\", // ⇩\n // 기타\n 0x22: \"✂\", // ✂\n 0x36: \"⌛\", // ⌛\n 0x4a: \"☺\", // ☺\n 0x4e: \"☠\", // ☠\n 0x52: \"☼\", // ☼\n 0x54: \"❄\", // ❄\n 0x58: \"✠\", // ✠\n 0x59: \"✡\", // ✡\n}\n\n/** Supplementary PUA-A (U+F0000대) — 한컴 자체 영역 매핑 */\nconst SUPPLEMENTARY_MAP: Record<number, string> = {\n 0xf003b: \"↓\", // ↓\n 0xf02ef: \"·\", // ·\n 0xf0854: \"《\", // 《\n 0xf0855: \"》\", // 》\n 0xf00da: \"▸\", // ▸\n 0xf080f: \"━\", // ━\n 0xf0827: \"■\", // ■\n}\n\n/** 단일 코드포인트 매핑 — 매핑 없으면 원본 유지 */\nexport function mapPuaChar(code: number): string | undefined {\n if (code >= 0xf020 && code <= 0xf0ff) {\n return BMP_SYMBOL_MAP[code - 0xf000]\n }\n if (code >= 0xf0000 && code <= 0xf09ff) {\n return SUPPLEMENTARY_MAP[code]\n }\n return undefined\n}\n\n/** BMP PUA 영역 여부 (U+E000~U+F8FF) */\nexport function isBmpPua(code: number): boolean {\n return code >= 0xe000 && code <= 0xf8ff\n}\n\n/**\n * 문자열 내 한컴 PUA 문자를 표준 유니코드로 치환.\n * 매핑 없는 BMP PUA는 원본 유지(옛한글 한양PUA 가능성 — 제거하면 안 됨),\n * 매핑 없는 Supplementary PUA-A는 호출부의 기존 제거 로직에 위임한다.\n */\nexport function mapPuaText(text: string): string {\n let out = \"\"\n for (const ch of text) {\n const code = ch.codePointAt(0)!\n out += mapPuaChar(code) ?? ch\n }\n return out\n}\n","/** 2-pass colSpan/rowSpan 테이블 빌더 및 Markdown 변환 */\n\nimport type { CellContext, IRBlock, IRCell, IRSpan, IRTable } from \"../types.js\"\nimport { sanitizeHref } from \"../utils.js\"\nimport { mapPuaText } from \"../shared/pua.js\"\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 // colAddr/rowAddr가 있으면 직접 배치 (HWPX cellAddr, HWP5 colAddr/rowAddr)\n const hasAddr = rows.some(row => row.some(c => c.colAddr !== undefined && c.rowAddr !== undefined))\n if (hasAddr) return buildTableDirect(rows, numRows)\n\n // Pass 1: maxCols 계산 — 2D 배열 사용 (동적 확장)\n let maxCols = 0\n const tempOccupied: boolean[][] = Array.from({ length: numRows }, () => [])\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[rowIdx][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[r][c] = true\n }\n }\n colIdx += cell.colSpan\n if (colIdx > maxCols) maxCols = colIdx\n }\n }\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 trimAndReturn(grid, numRows, maxCols)\n}\n\n/** colAddr/rowAddr 절대 좌표 기반 직접 배치 */\nfunction buildTableDirect(rows: CellContext[][], numRows: number): IRTable {\n // 전체 셀에서 maxCols 계산 (MAX_COLS 상한 적용)\n let maxCols = 0\n for (const row of rows) {\n for (const cell of row) {\n const end = (cell.colAddr ?? 0) + cell.colSpan\n if (end > maxCols) maxCols = end\n }\n }\n if (maxCols > MAX_COLS) maxCols = MAX_COLS\n if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false }\n\n const grid: IRCell[][] = Array.from({ length: numRows }, () =>\n Array.from({ length: maxCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 }))\n )\n\n for (const row of rows) {\n for (const cell of row) {\n const r = cell.rowAddr ?? 0\n const c = cell.colAddr ?? 0\n if (r >= numRows || c >= maxCols || r < 0 || c < 0) continue\n\n grid[r][c] = { text: cell.text.trim(), colSpan: cell.colSpan, rowSpan: cell.rowSpan }\n\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 < maxCols) {\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\n }\n }\n }\n }\n }\n\n return trimAndReturn(grid, numRows, maxCols)\n}\n\n/** 빈 후행 열 제거 후 IRTable 반환 */\nfunction trimAndReturn(grid: IRCell[][], numRows: number, maxCols: number): IRTable {\n let effectiveCols = maxCols\n while (effectiveCols > 0) {\n const colEmpty = grid.every(row => !row[effectiveCols - 1]?.text?.trim())\n if (!colEmpty) break\n effectiveCols--\n }\n if (effectiveCols < maxCols && effectiveCols > 0) {\n const trimmed = grid.map(row => row.slice(0, effectiveCols))\n return { rows: numRows, cols: effectiveCols, cells: trimmed, hasHeader: numRows > 1 }\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, \" \").replace(/\\|/g, \"\\\\|\"))\n .filter(Boolean)\n .join(\" / \")\n )\n .filter(Boolean)\n .join(\"\\n\")\n}\n\n/** 마크다운 GFM 특수문자 이스케이프 — remark-gfm 오해석 방지 */\nfunction escapeGfm(text: string): string {\n // ~ → \\~ (GFM strikethrough 방지), * → \\* (emphasis/HR·마스킹 별표 \"******\" 방지).\n // 단 $...$ / $$...$$ 수식 스팬은 KaTeX 문법이라 이스케이프하면 파스 에러가 나므로 보호한다\n // (스팬을 임시 필러로 가린 뒤 escape → 복원). NUL 필러는 마크다운 본문에 등장하지 않는다.\n const NUL = String.fromCharCode(0) // 마크다운 본문에 없는 안전한 필러 (소스에 raw NUL 미기입)\n const spans: string[] = []\n const masked = text.replace(/\\$\\$[^$]*\\$\\$|\\$[^$\\n]*\\$/g, (m) => {\n spans.push(m)\n return NUL + (spans.length - 1) + NUL\n })\n const escaped = masked.replace(/([~*])/g, \"\\\\$1\")\n return escaped.replace(new RegExp(NUL + \"(\\\\d+)\" + NUL, \"g\"), (_, n) => spans[Number(n)])\n}\n\n/** HWP 자동생성 도형/개체 대체텍스트 정규식 — 한컴오피스가 삽입하는 모든 알려진 패턴 */\nconst HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\\s?개체|묶음\\s?개체|글상자|수식\\s?개체|OLE\\s?개체)\\s?입니다\\.?/g\n\n/** HWP PUA 특수문자 및 도형 대체텍스트 제거 — 모든 포맷 공통 */\nfunction sanitizeText(text: string): string {\n // 한컴 PUA → 표준 유니코드 매핑 (rhwp 검증 테이블) — 제거 regex보다 먼저 적용\n let result = mapPuaText(text)\n // Supplementary Private Use Area (U+F0000-U+FFFFD) — HWP 전용 기호 (매핑 안 된 잔여분)\n .replace(/[\\u{F0000}-\\u{FFFFD}]/gu, \"\")\n // HWP 도형/개체 자동생성 대체텍스트 제거\n .replace(HWP_SHAPE_ALT_TEXT_RE, \"\")\n .replace(/ +/g, \" \")\n .trim()\n // 균등배분 스페이스 정리 (\"현 장 대 응 단 장\" → \"현장대응단장\")\n // 짧은 텍스트(30자 이하)에서 70%+ 토큰이 한글 1글자면 균등배분으로 판단\n if (result.length <= 30 && result.includes(\" \")) {\n const tokens = result.split(\" \")\n // 한글 1글자 토큰만 카운트 — ASCII 특수문자(< > & 등)는 균등배분이 아님\n const koreanSingleCharCount = tokens.filter(t => t.length === 1 && /[\\uAC00-\\uD7AF\\u3131-\\u318E]/.test(t)).length\n if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {\n result = tokens.join(\"\")\n }\n }\n return result\n}\n\n/**\n * 레이아웃 테이블 감지 및 해체 — IRBlock 레벨에서 수행\n * 적은 행(≤3) + 셀 내 줄바꿈 다량 → table 블록을 paragraph 블록들로 분해\n * heading 감지 전에 호출해야 해체된 텍스트에 heading 감지 적용 가능\n *\n * 호출 정책(의도): HWP5 파서만 호출한다. 구형 HWP5 문서는 제목/본문을 표로\n * 감싼 레이아웃 표가 흔하지만, HWPX는 그 관행이 드물고 무엇보다 patchHwpx/\n * fillHwpx 무손실 라운드트립이 \"파서 렌더 = 소스맵 표 서수\" 대응에 의존하므로\n * HWPX에서 표를 문단으로 해체하면 표 매핑이 깨진다. HWPX 적용은 코퍼스\n * 전/후 정량 비교 + 라운드트립 e2e 검증이 선행되어야 한다.\n */\nexport function flattenLayoutTables(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n\n for (const block of blocks) {\n if (block.type !== \"table\" || !block.table) {\n result.push(block)\n continue\n }\n\n const { rows: numRows, cols: numCols, cells } = block.table\n\n // 1x1 테이블은 기존 로직(tableToMarkdown)에서 처리\n if (numRows === 1 && numCols === 1) {\n result.push(block)\n continue\n }\n\n // 레이아웃 테이블 휴리스틱\n if (numRows <= 3) {\n let totalNewlines = 0\n let totalTextLen = 0\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n const t = cells[r]?.[c]?.text || \"\"\n totalNewlines += (t.match(/\\n/g) || []).length\n totalTextLen += t.length\n }\n }\n\n // 레이아웃 테이블 판정: 많은 줄바꿈(>5), 또는 적은 행에 비해 총 텍스트 과다(>300)\n // 단, 열이 4개 이상이면 헤더-값 구조의 데이터 표일 가능성이 높아 해체하지 않는다\n // (실증: 2×10 모집프로그램 표가 문단으로 해체되어 헤더↔값 연결 파괴)\n if (numCols < 4 && (totalNewlines > 5 || (numRows <= 2 && totalTextLen > 300))) {\n // 레이아웃 테이블 → 각 셀을 paragraph 블록으로 분해\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n const cell = cells[r]?.[c]\n if (!cell) continue\n // 셀에 구조화 블록(중첩표·이미지·다중문단)이 있으면 재귀 해체로 구조 보존.\n // 중첩 spec 표(numRows>3)는 해체되지 않고 실제 table 블록으로 살아남는다\n // (text의 \" / \" 평탄화 폴백으로만 남아 유실되던 버그 수정). 셀 blocks는\n // 이미 같은 pageNumber로 생성되므로 그대로 보존된다.\n if (cell.blocks?.length) {\n result.push(...flattenLayoutTables(cell.blocks))\n continue\n }\n const cellText = cell.text?.trim()\n if (!cellText) continue\n // 셀 내 줄바꿈을 별도 paragraph로 분리\n for (const line of cellText.split(\"\\n\")) {\n const trimmed = line.trim()\n if (!trimmed) continue\n result.push({ type: \"paragraph\", text: trimmed, pageNumber: block.pageNumber })\n }\n }\n }\n continue\n }\n }\n\n result.push(block)\n }\n\n return result\n}\n\n/** 러닝 헤더 후보 판정 최대 길이 — 긴 본문 문단을 헤더로 오인하지 않도록 */\nconst RUNNING_HEADER_MAX_LEN = 40\n/** 러닝 헤더로 판정할 최소 반복 횟수 — 페이지마다 재삽입되는 노이즈만 제거 */\nconst RUNNING_HEADER_MIN_FREQ = 3\n\n/** 러닝 헤더 후보 여부 — 짧은 번호매김 섹션 제목(\"2. 과제 구축 내용\")만 */\nfunction isRunningHeaderCandidate(block: IRBlock): boolean {\n if (block.type !== \"paragraph\" && block.type !== \"heading\") return false\n const text = block.text?.trim()\n if (!text || text.length > RUNNING_HEADER_MAX_LEN) return false\n return /^\\d+\\.\\s/.test(text)\n}\n\n/**\n * 페이지 레이아웃 표에서 유래한 반복 러닝 헤더 문단 중복 제거 — IRBlock 레벨.\n *\n * 배경: 구형 HWP5 문서(군 제안서 등)는 본문 전체를 \"페이지 = N×1 표\" 사슬로\n * 구성하고 각 표의 cell(0,0)에 동일한 섹션 헤더(\"2. 과제 구축 내용\")를 반복\n * 배치한다. flattenLayoutTables가 이 표들을 문단으로 해체하면 같은 헤더가\n * 페이지마다 1회씩 독립 문단으로 남아 본문 사이에 노이즈로 흩어진다. HWP\n * 머리말/꼬리말(CTRL_HEAD/CTRL_FOOT)은 applyHeaderFooterEffect에서 이미\n * dedupe되지만, 본문 레이아웃 표의 러닝 헤더는 그 대상이 아니다.\n *\n * 정책(보수적): \"짧은 번호매김 섹션 제목\"이 정확히 동일 텍스트로 3회 이상\n * 반복될 때만 최초 1회를 남기고 이후 중복을 제거한다. 비후보 블록과 3회 미만\n * 후보는 원형 그대로 통과시킨다(본문 삭제 위험 최소화). 위치 무관 — 평탄화된\n * 블록 스트림 전체를 대상으로 하며, 입력 블록을 변형하지 않고 새 배열을 반환한다.\n */\nexport function dedupeRunningHeaders(blocks: IRBlock[]): IRBlock[] {\n // Pass 1: 후보 텍스트 빈도 집계\n const freq = new Map<string, number>()\n for (const block of blocks) {\n if (!isRunningHeaderCandidate(block)) continue\n const text = block.text!.trim()\n freq.set(text, (freq.get(text) ?? 0) + 1)\n }\n\n // Pass 2: 빈도 ≥ 임계값인 후보는 최초 1회만 유지, 이후 중복 제거\n const seen = new Set<string>()\n const result: IRBlock[] = []\n for (const block of blocks) {\n if (isRunningHeaderCandidate(block)) {\n const text = block.text!.trim()\n if ((freq.get(text) ?? 0) >= RUNNING_HEADER_MIN_FREQ) {\n if (seen.has(text)) continue // 이후 중복 — 제거\n seen.add(text)\n }\n }\n result.push(block)\n }\n\n return result\n}\n\n/**\n * 왕복 채널 run-span → 강조 마커 재방출. 마커는 텍스트에 밀착해야 유효하므로\n * span 가장자리 공백은 마커 밖으로 옮긴다. 코드 span 내부는 이스케이프 없이 원문.\n */\nfunction spansToMarkdown(spans: IRSpan[]): string {\n let out = \"\"\n for (const s of spans) {\n if (!s.text) continue\n const marker = s.code ? \"`\" : s.bold && s.italic ? \"***\" : s.bold ? \"**\" : s.italic ? \"*\" : \"\"\n if (!marker) {\n out += escapeGfm(s.text)\n continue\n }\n const m = /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(s.text)!\n const core = m[2]\n if (!core) {\n out += s.text\n continue\n }\n out += m[1] + marker + (s.code ? core : escapeGfm(core)) + marker + m[3]\n }\n return out\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 // 헤딩 블록 — escapeGfm 필수: 마스킹 별표(\"홍**\")가 볼드로 소비·삭제되는 것 방지\n if (block.type === \"heading\" && block.text) {\n const prefix = \"#\".repeat(Math.min(block.level || 2, 6))\n const headingText = sanitizeText(block.text)\n if (headingText) lines.push(\"\", `${prefix} ${escapeGfm(headingText)}`, \"\")\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 const listText = sanitizeText(block.text)\n if (!listText) continue\n // 텍스트가 이미 번호로 시작하면 그대로 출력 (원래 번호 보존)\n const alreadyNumbered = block.listType === \"ordered\" && /^\\d+\\.\\s/.test(listText)\n const prefix = alreadyNumbered ? \"\" : block.listType === \"ordered\" ? \"1. \" : \"- \"\n lines.push(`${prefix}${escapeGfm(listText)}`)\n if (block.children) {\n for (const child of block.children) {\n const childPrefix = child.listType === \"ordered\" ? \"1.\" : \"-\"\n lines.push(` ${childPrefix} ${escapeGfm(child.text || \"\")}`)\n }\n }\n continue\n }\n\n if (block.type === \"paragraph\" && block.text) {\n let text = sanitizeText(block.text)\n if (!text) continue\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(\"\", `## ${escapeGfm(text)} ${escapeGfm(nextBlock.text)}`, \"\")\n i++\n } else {\n lines.push(\"\", `## ${escapeGfm(text)}`, \"\")\n }\n continue\n }\n\n if (/^\\([^)]*조[^)]*관련\\)$/.test(text)) {\n lines.push(`*${escapeGfm(text)}*`, \"\")\n continue\n }\n\n // gongmun 리스트 depth 선행 공백 (v4.0.5) — sanitizeText가 선두 공백을 지우므로\n // 텍스트가 아니라 방출 시점에 붙인다. 2칸/단계 = md-runs 리스트 그리드와 동일\n const listIndent = block.listDepth ? \" \".repeat(block.listDepth) : \"\"\n\n // 인라인 강조 run-span (왕복 채널 — 자사 생성 파일 + 외래 실속성 복원) → 마커 재방출.\n // 마커 자체는 이스케이프 대상이 아니므로 span 내부 텍스트만 escapeGfm 후 감싼다\n if (block.spans?.length) {\n let rendered = spansToMarkdown(block.spans)\n if (block.href) {\n const href = sanitizeHref(block.href)\n if (href) rendered = `[${rendered}](${href})`\n }\n if (block.footnoteText) rendered += ` (주: ${block.footnoteText})`\n lines.push(block.quote ? \"> \" + rendered : listIndent + rendered, \"\")\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(block.quote ? \"> \" + escapeGfm(text) : listIndent + escapeGfm(text), \"\")\n } else if (block.type === \"table\" && block.table) {\n // 테이블 앞에 빈 줄 보장 (마크다운 렌더링 필수)\n if (lines.length > 0 && lines[lines.length - 1] !== \"\") {\n lines.push(\"\")\n }\n // 표 캡션 — 표 위에 강조 문단으로 출력 (v3.0)\n if (block.table.caption) {\n const caption = sanitizeText(block.table.caption)\n if (caption) lines.push(`**${escapeGfm(caption)}**`, \"\")\n }\n const tableMd = tableToMarkdown(block.table)\n if (tableMd) {\n lines.push(tableMd)\n lines.push(\"\")\n }\n }\n }\n\n return lines.join(\"\\n\").trim()\n}\n\n/** 병합 셀 존재 여부 확인 */\nfunction hasMergedCells(table: IRTable): boolean {\n for (const row of table.cells) {\n for (const cell of row) {\n if (cell.colSpan > 1 || cell.rowSpan > 1) return true\n }\n }\n return false\n}\n\n/** 셀 내부에 중첩 표 블록 존재 여부 — v3.0 */\nfunction hasNestedTables(table: IRTable): boolean {\n for (const row of table.cells) {\n for (const cell of row) {\n if (cell.blocks?.some(b => b.type === \"table\" && b.table)) return true\n }\n }\n return false\n}\n\n/** 셀 내부 콘텐츠 → HTML — blocks(중첩표/다중문단) 있으면 구조 보존 재귀 렌더링 */\nfunction cellInnerHtml(cell: IRCell): string {\n if (cell.blocks?.length) {\n return cell.blocks\n .map(b => {\n if (b.type === \"table\" && b.table) {\n // 중첩표 캡션도 보존 — 표 위에 텍스트로\n const cap = b.table.caption ? sanitizeText(b.table.caption) : \"\"\n return (cap ? cap + \"<br>\" : \"\") + tableToHtml(b.table)\n }\n if (b.type === \"image\" && b.text) return `<img src=\"${b.text}\" alt=\"image\">`\n const t = sanitizeText(b.text ?? \"\")\n return t ? t.replace(/\\n/g, \"<br>\") : \"\"\n })\n .filter(Boolean)\n .join(\"<br>\")\n }\n return sanitizeText(cell.text).replace(/\\n/g, \"<br>\")\n}\n\nfunction containsInlineMath(text: string): boolean {\n return /(^|[^\\\\])\\$(?=\\S)(?:\\\\.|[^$\\n])+?\\S\\$/.test(text)\n}\n\nfunction tableContainsInlineMath(table: IRTable): boolean {\n for (const row of table.cells) {\n for (const cell of row) {\n if (containsInlineMath(cell.text)) return true\n }\n }\n return false\n}\n\n/** 병합 테이블 → HTML <table> 출력 (rowspan/colspan 보존) */\nfunction tableToHtml(table: IRTable): string {\n const { cells, rows: numRows, cols: numCols } = table\n const skip = new Set<string>()\n const lines: string[] = [\"<table>\"]\n\n for (let r = 0; r < numRows; r++) {\n const tag = r === 0 ? \"th\" : \"td\"\n const rowHtml: string[] = []\n for (let c = 0; c < numCols; c++) {\n if (skip.has(`${r},${c}`)) continue\n const cell = cells[r]?.[c]\n if (!cell) continue\n\n // 병합 영역 skip 마킹\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) skip.add(`${r + dr},${c + dc}`)\n }\n }\n\n const text = cellInnerHtml(cell)\n const attrs: string[] = []\n if (cell.colSpan > 1) attrs.push(`colspan=\"${cell.colSpan}\"`)\n if (cell.rowSpan > 1) attrs.push(`rowspan=\"${cell.rowSpan}\"`)\n const attrStr = attrs.length ? \" \" + attrs.join(\" \") : \"\"\n rowHtml.push(`<${tag}${attrStr}>${text}</${tag}>`)\n }\n if (rowHtml.length) lines.push(`<tr>${rowHtml.join(\"\")}</tr>`)\n }\n\n lines.push(\"</table>\")\n return lines.join(\"\\n\")\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 // 병합 셀·중첩표가 있으면 HTML 테이블로 출력하되, 수식이 있으면 GFM 표로 출력한다.\n // 많은 Markdown 렌더러가 raw HTML table 내부의 $...$를 수식으로 다시 처리하지 않는다.\n if ((hasMergedCells(table) || hasNestedTables(table)) && !tableContainsInlineMath(table)) {\n return tableToHtml(table)\n }\n\n // 1행 1열 → 구조화된 텍스트 (빈 셀이면 스킵)\n if (numRows === 1 && numCols === 1) {\n const content = sanitizeText(cells[0][0].text)\n if (!content) return \"\"\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 `**${escapeGfm(trimmed)}**`\n if (/^[가-힣]\\.\\s/.test(trimmed)) return ` ${escapeGfm(trimmed)}`\n return escapeGfm(trimmed)\n })\n .filter(Boolean)\n .join(\"\\n\")\n }\n\n // 1열 다행 테이블 → 각 행을 별도 라인으로 출력 (목록성 데이터)\n if (numCols === 1 && numRows >= 2) {\n return cells\n .map(row => escapeGfm(sanitizeText(row[0].text)).replace(/\\n/g, \" \"))\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 if (!cell) continue\n // 왕복 채널 셀 spans (v4.0.4) — 강조 마커 재방출 (문단별, 개행은 <br> 규약)\n display[r][c] = (cell.blocks?.some(b => b.spans)\n ? cell.blocks\n .map(b => b.type === \"image\" && b.text\n ? `![image](${b.text})`\n : b.spans ? spansToMarkdown(b.spans) : escapeGfm(sanitizeText(b.text ?? \"\")))\n .filter(Boolean)\n .join(\"<br>\")\n : escapeGfm(sanitizeText(cell.text)).replace(/\\n/g, \"<br>\")\n ).replace(/\\|/g, \"\\\\|\")\n\n // colSpan/rowSpan: 병합된 열은 빈 칸으로 유지 (텍스트 중복 방지)\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 // colSpan > 1이면 display 열 인덱스를 건너뜀\n c += cell.colSpan - 1\n }\n }\n\n // rowSpan 잔류 처리:\n // 1) 완전 빈 행 제거\n // 2) \"첫 열만 값, 나머지 빈\" 행 → 다음 데이터 행의 첫 열에 값을 전파\n // 단, colSpan으로 인한 빈 열(skip 셀)은 이 대상이 아님\n const uniqueRows: string[][] = []\n let pendingLabelRow: string[] | null = null\n for (let r = 0; r < display.length; r++) {\n const row = display[r]\n const isEmptyPlaceholder = row.every(cell => cell === \"\")\n if (isEmptyPlaceholder) continue\n\n // 첫 열만 값이 있고 나머지 모두 빈 행 → 다음 데이터 행의 첫 열에 전파\n // 단, colSpan으로 인한 빈 열(skip 셀)은 \"진짜 빈\"이 아니므로 제외\n const nonEmptyCols = row.filter(cell => cell !== \"\")\n const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`))\n if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0] !== \"\" && row.slice(1).every(c => c === \"\")) {\n if (pendingLabelRow) uniqueRows.push(pendingLabelRow) // 연속 보류 — 앞 행 소실 방지\n pendingLabelRow = row\n continue\n }\n\n // 보류한 첫 열 값을 현재 행의 빈 첫 열에 전파, 전파 불가면 보류 행 그대로 출력\n if (pendingLabelRow) {\n if (row[0] === \"\") row[0] = pendingLabelRow[0]\n else uniqueRows.push(pendingLabelRow)\n pendingLabelRow = null\n }\n uniqueRows.push(row)\n }\n if (pendingLabelRow) uniqueRows.push(pendingLabelRow) // 표 끝 보류 행 소실 방지\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// ─── 중간 표현 (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 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 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\nexport interface IRCell {\n text: string\n colSpan: number\n rowSpan: number\n /**\n * 셀 내부 블록 콘텐츠 — v3.0.\n * 중첩 표·이미지·다중 문단을 구조 그대로 보존한다.\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 ocr?: OcrProvider\n /** 진행률 콜백 — current: 현재 페이지/섹션, total: 전체 수 */\n onProgress?: (current: number, total: number) => void\n /** PDF 머리글/바닥글 자동 제거 */\n removeHeaderFooter?: 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 | \"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\" | \"unknown\"\n\ninterface ParseResultBase {\n fileType: FileType\n /** 페이지/섹션 수 — PDF: 실제 페이지 수, HWP/HWPX: 섹션 수, XLSX: 시트 수 */\n pageCount?: number\n /** 이미지 기반 PDF 여부 (텍스트 추출 불가) */\n isImageBased?: boolean\n}\n\nexport interface ParseSuccess extends ParseResultBase {\n success: true\n /** 추출된 마크다운 텍스트 */\n markdown: string\n /** 중간 표현 블록 (구조화된 데이터 접근용) */\n blocks: IRBlock[]\n /** 문서 메타데이터 */\n metadata?: DocumentMetadata\n /** 문서 구조 (헤딩 트리) — v2.0 */\n outline?: OutlineItem[]\n /** 파싱 중 발생한 경고 — v2.0 */\n warnings?: ParseWarning[]\n /** 추출된 이미지 목록 — 마크다운에서 파일명으로 참조됨 */\n images?: ExtractedImage[]\n /** 페이지별 텍스트 품질 신호 — 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\"\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"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/page-range.ts"],"sourcesContent":["/** 페이지/섹션 범위 파싱 유틸리티 */\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"],"mappings":";AASO,SAAS,eAAe,MAAyB,UAA+B;AACrF,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,YAAY,EAAG,QAAO;AAE1B,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI,QAAO;AAE3D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,QAAQ,MAAM,qBAAqB;AACtD,QAAI,YAAY;AACd,YAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AACrD,YAAM,MAAM,KAAK,IAAI,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AAC1D,eAAS,IAAI,OAAO,KAAK,KAAK,IAAK,QAAO,IAAI,CAAC;AAAA,IACjD,OAAO;AACL,YAAM,OAAO,SAAS,SAAS,EAAE;AACjC,UAAI,CAAC,MAAM,IAAI,KAAK,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/detect.ts","../src/hwp5/cfb-lenient.ts"],"sourcesContent":["/** 매직 바이트 기반 파일 포맷 감지 */\n\nimport JSZip from \"jszip\"\nimport type { FileType } from \"./types.js\"\nimport { parseLenientCfb } from \"./hwp5/cfb-lenient.js\"\n\n/** 매직 바이트 뷰 생성 (복사 없이 view) */\nfunction magicBytes(buffer: ArrayBuffer): Uint8Array {\n return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength))\n}\n\n/** ZIP 파일 여부: PK\\x03\\x04 */\nexport function isZipFile(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/** HWPX (ZIP 기반 한컴 문서): PK\\x03\\x04 — 하위 호환용 */\nexport function isHwpxFile(buffer: ArrayBuffer): boolean {\n return isZipFile(buffer)\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/**\n * HWP 3.x (한글 워드프로세서 3.0): \"HWP Document File V3.00 \\x1A\\x01\\x02\\x03\\x04\\x05\" 30 byte.\n * CFB(OLE2) 컨테이너 아닌 단일 binary stream — isOldHwpFile 과 magic 이 다르다.\n */\nconst HWP3_PREFIX = new TextEncoder().encode(\"HWP Document File V3.00\")\nexport function isHwp3File(buffer: ArrayBuffer): boolean {\n if (buffer.byteLength < HWP3_PREFIX.length) return false\n const head = new Uint8Array(buffer, 0, HWP3_PREFIX.length)\n for (let i = 0; i < HWP3_PREFIX.length; i++) {\n if (head[i] !== HWP3_PREFIX[i]) return false\n }\n return true\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/** HWPML (XML 기반 한컴 문서): <?xml ... <HWPML */\nexport function isHwpmlFile(buffer: ArrayBuffer): boolean {\n const bytes = new Uint8Array(buffer, 0, Math.min(512, buffer.byteLength))\n const head = new TextDecoder(\"utf-8\", { fatal: false }).decode(bytes).replace(/^\\uFEFF/, \"\")\n return head.trimStart().startsWith(\"<?xml\") && head.includes(\"<HWPML\")\n}\n\n/** 동기 포맷 감지 — ZIP은 모두 \"hwpx\"로 반환 (하위 호환) */\nexport function detectFormat(buffer: ArrayBuffer): FileType {\n if (buffer.byteLength < 4) return \"unknown\"\n if (isHwp3File(buffer)) return \"hwp3\"\n if (isZipFile(buffer)) return \"hwpx\"\n if (isOldHwpFile(buffer)) return \"hwp\"\n if (isPdfFile(buffer)) return \"pdf\"\n if (isHwpmlFile(buffer)) return \"hwpml\"\n return \"unknown\"\n}\n\n/**\n * OLE2 컨테이너 내부 스트림 기반 포맷 세분화.\n * HWP 5.x, XLS 모두 OLE2이므로 스트림 이름으로 구분.\n * - \"Workbook\" 또는 \"Book\" → 'xls'\n * - 그 외 (FileHeader 등) → 'hwp'\n */\nexport function detectOle2Format(buffer: ArrayBuffer): \"hwp\" | \"xls\" | \"unknown\" {\n try {\n const cfb = parseLenientCfb(Buffer.from(buffer))\n const names = cfb.entries().map(e => e.name)\n if (names.includes(\"Workbook\") || names.includes(\"Book\")) return \"xls\"\n if (names.includes(\"FileHeader\")) return \"hwp\"\n // FileHeader 없어도 BodyText/DocInfo 있으면 hwp\n if (names.some(n => n === \"DocInfo\" || n.startsWith(\"Section\"))) return \"hwp\"\n return \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n\n/**\n * ZIP 내부 구조 기반 포맷 세분화.\n * HWPX, XLSX, DOCX 모두 ZIP이므로 내부 파일로 구분.\n */\nexport async function detectZipFormat(buffer: ArrayBuffer): Promise<\"hwpx\" | \"xlsx\" | \"docx\" | \"unknown\"> {\n try {\n const zip = await JSZip.loadAsync(buffer)\n // XLSX: xl/workbook.xml\n if (zip.file(\"xl/workbook.xml\")) return \"xlsx\"\n // DOCX: word/document.xml\n if (zip.file(\"word/document.xml\")) return \"docx\"\n // HWPX: Contents/ 또는 content.hpf 또는 mimetype\n if (zip.file(\"Contents/content.hpf\") || zip.file(\"mimetype\")) return \"hwpx\"\n // 기타 ZIP 내에 section 파일이 있으면 HWPX로 추정\n const hasSection = Object.keys(zip.files).some(f => f.startsWith(\"Contents/\"))\n if (hasSection) return \"hwpx\"\n return \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n","/**\n * Lenient CFB (Compound File Binary / OLE2) 파서.\n *\n * 표준 cfb 모듈이 FAT 검증 실패로 거부하는 손상된 HWP 파일을 열기 위한 폴백.\n * 직접 헤더/FAT/디렉토리를 파싱하여 스트림 데이터를 추출.\n *\n * 참조: rhwp (MIT) src/parser/cfb_reader.rs (LenientCfbReader)\n * 참조: MS-CFB spec (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb)\n */\n\nimport { decompressStream } from \"./record.js\"\n\n// ── 상수 ──\n\nconst CFB_MAGIC = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])\nconst END_OF_CHAIN = 0xfffffffe\nconst FREE_SECT = 0xffffffff\n\n/** 순환 감지용 최대 체인 길이 */\nconst MAX_CHAIN_LENGTH = 1_000_000\n/** 최대 디렉토리 엔트리 수 */\nconst MAX_DIR_ENTRIES = 100_000\n/** 최대 스트림 크기 (100MB) */\nconst MAX_STREAM_SIZE = 100 * 1024 * 1024\n\n// ── 디렉토리 엔트리 ──\n\ninterface DirEntry {\n name: string\n type: number // 0=unknown, 1=storage, 2=stream, 5=root\n startSector: number\n size: number\n}\n\n// ── CFB 컨테이너 ──\n\nexport interface LenientCfbContainer {\n /** 이름 기반 스트림 탐색 */\n findStream(path: string): Buffer | null\n /** 디렉토리 엔트리 목록 */\n entries(): DirEntry[]\n}\n\n// ── 구현 ──\n\nexport function parseLenientCfb(data: Buffer): LenientCfbContainer {\n if (data.length < 512) throw new Error(\"CFB 파일이 너무 짧습니다 (최소 512바이트)\")\n if (!data.subarray(0, 8).equals(CFB_MAGIC)) throw new Error(\"CFB 매직 바이트 불일치\")\n\n // ── 헤더 파싱 ──\n\n const sectorSizeShift = data.readUInt16LE(30)\n if (sectorSizeShift < 7 || sectorSizeShift > 16) throw new Error(\"유효하지 않은 섹터 크기 시프트: \" + sectorSizeShift)\n const sectorSize = 1 << sectorSizeShift // 보통 512\n const miniSectorSizeShift = data.readUInt16LE(32)\n if (miniSectorSizeShift > 16) throw new Error(\"유효하지 않은 미니 섹터 크기 시프트: \" + miniSectorSizeShift)\n const miniSectorSize = 1 << miniSectorSizeShift // 보통 64\n\n const fatSectorCount = data.readUInt32LE(44)\n if (fatSectorCount > 10000) throw new Error(\"FAT 섹터 수가 너무 많습니다: \" + fatSectorCount)\n const firstDirSector = data.readUInt32LE(48)\n const miniStreamCutoff = data.readUInt32LE(56) // 보통 4096\n const firstMiniFatSector = data.readUInt32LE(60)\n const miniFatSectorCount = data.readUInt32LE(64)\n const firstDifatSector = data.readUInt32LE(68)\n const difatSectorCount = data.readUInt32LE(72)\n\n // ── 유틸 ──\n\n function sectorOffset(id: number): number {\n return 512 + id * sectorSize\n }\n\n function readSectorData(id: number): Buffer {\n const off = sectorOffset(id)\n if (off + sectorSize > data.length) return Buffer.alloc(0)\n return data.subarray(off, off + sectorSize)\n }\n\n // ── DIFAT → FAT 섹터 목록 ──\n\n const fatSectors: number[] = []\n\n // 헤더 내 DIFAT (최대 109개)\n for (let i = 0; i < 109 && fatSectors.length < fatSectorCount; i++) {\n const sid = data.readUInt32LE(76 + i * 4)\n if (sid === FREE_SECT || sid === END_OF_CHAIN) break\n fatSectors.push(sid)\n }\n\n // 추가 DIFAT 섹터 체인\n let difatSector = firstDifatSector\n const visitedDifat = new Set<number>()\n for (let d = 0; d < difatSectorCount && difatSector !== END_OF_CHAIN && difatSector !== FREE_SECT; d++) {\n if (visitedDifat.has(difatSector)) break\n visitedDifat.add(difatSector)\n\n const buf = readSectorData(difatSector)\n const entriesPerSector = (sectorSize / 4) - 1 // 마지막 4바이트는 다음 DIFAT 포인터\n for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {\n const sid = buf.readUInt32LE(i * 4)\n if (sid === FREE_SECT || sid === END_OF_CHAIN) continue\n fatSectors.push(sid)\n }\n difatSector = buf.readUInt32LE(entriesPerSector * 4)\n }\n\n // ── FAT 테이블 구축 ──\n\n const entriesPerFatSector = sectorSize / 4\n const fatTable = new Uint32Array(fatSectors.length * entriesPerFatSector)\n\n for (let fi = 0; fi < fatSectors.length; fi++) {\n const buf = readSectorData(fatSectors[fi])\n for (let i = 0; i < entriesPerFatSector; i++) {\n fatTable[fi * entriesPerFatSector + i] = i * 4 + 3 < buf.length\n ? buf.readUInt32LE(i * 4)\n : FREE_SECT\n }\n }\n\n // ── 체인 리더 (순환 방지) ──\n\n function readChain(startSector: number, maxBytes: number): Buffer {\n if (startSector === END_OF_CHAIN || startSector === FREE_SECT) return Buffer.alloc(0)\n if (maxBytes > MAX_STREAM_SIZE) throw new Error(\"스트림이 너무 큽니다\")\n\n const chunks: Buffer[] = []\n let current = startSector\n let totalRead = 0\n const visited = new Set<number>()\n\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < maxBytes) {\n if (visited.has(current)) break // 순환 감지\n if (visited.size > MAX_CHAIN_LENGTH) break\n visited.add(current)\n\n const buf = readSectorData(current)\n const remaining = maxBytes - totalRead\n chunks.push(remaining < sectorSize ? buf.subarray(0, remaining) : buf)\n totalRead += Math.min(buf.length, remaining)\n\n current = current < fatTable.length ? fatTable[current] : END_OF_CHAIN\n }\n\n return Buffer.concat(chunks)\n }\n\n // ── Mini-FAT 테이블 ──\n\n let miniFatTable: Uint32Array | null = null\n\n function getMiniFatTable(): Uint32Array {\n if (miniFatTable) return miniFatTable\n\n if (miniFatSectorCount === 0 || firstMiniFatSector === END_OF_CHAIN) {\n miniFatTable = new Uint32Array(0)\n return miniFatTable\n }\n\n const miniFatData = readChain(firstMiniFatSector, miniFatSectorCount * sectorSize)\n const entries = miniFatData.length / 4\n miniFatTable = new Uint32Array(entries)\n for (let i = 0; i < entries; i++) {\n miniFatTable[i] = miniFatData.readUInt32LE(i * 4)\n }\n return miniFatTable\n }\n\n // ── 디렉토리 엔트리 파싱 ──\n\n const dirData = readChain(firstDirSector, MAX_DIR_ENTRIES * 128)\n const dirEntries: DirEntry[] = []\n\n for (let offset = 0; offset + 128 <= dirData.length && dirEntries.length < MAX_DIR_ENTRIES; offset += 128) {\n const nameLen = dirData.readUInt16LE(offset + 64) // 바이트 수 (null 포함)\n if (nameLen <= 0 || nameLen > 64) {\n dirEntries.push({ name: \"\", type: 0, startSector: 0, size: 0 })\n continue\n }\n\n const nameBytes = nameLen - 2 // null terminator 제외\n const name = nameBytes > 0\n ? dirData.subarray(offset, offset + nameBytes).toString(\"utf16le\")\n : \"\"\n\n const type = dirData[offset + 66]\n const startSector = dirData.readUInt32LE(offset + 116)\n // CFBv3에서는 size가 u32 (offset 120), v4에서는 u64\n const size = dirData.readUInt32LE(offset + 120)\n\n dirEntries.push({ name, type, startSector, size })\n }\n\n // ── Root 엔트리에서 미니 스트림 추출 ──\n\n let miniStreamData: Buffer | null = null\n\n function getMiniStream(): Buffer {\n if (miniStreamData) return miniStreamData\n const root = dirEntries[0]\n if (!root || root.type !== 5) {\n miniStreamData = Buffer.alloc(0)\n return miniStreamData\n }\n miniStreamData = readChain(root.startSector, root.size || MAX_STREAM_SIZE)\n return miniStreamData\n }\n\n // ── 미니 스트림에서 읽기 ──\n\n function readMiniStream(startSector: number, size: number): Buffer {\n const mft = getMiniFatTable()\n const ms = getMiniStream()\n if (mft.length === 0 || ms.length === 0) return Buffer.alloc(0)\n\n const chunks: Buffer[] = []\n let current = startSector\n let totalRead = 0\n const visited = new Set<number>()\n\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < size) {\n if (visited.has(current)) break\n if (visited.size > MAX_CHAIN_LENGTH) break\n visited.add(current)\n\n const off = current * miniSectorSize\n const remaining = size - totalRead\n const chunkSize = Math.min(miniSectorSize, remaining)\n if (off + chunkSize <= ms.length) {\n chunks.push(ms.subarray(off, off + chunkSize))\n }\n totalRead += chunkSize\n\n current = current < mft.length ? mft[current] : END_OF_CHAIN\n }\n\n return Buffer.concat(chunks)\n }\n\n // ── 스트림 읽기 (일반/미니 자동 분기) ──\n\n function readStreamData(entry: DirEntry): Buffer {\n if (entry.size === 0) return Buffer.alloc(0)\n if (entry.size < miniStreamCutoff) {\n const miniResult = readMiniStream(entry.startSector, entry.size)\n // 미니스트림이 비어있으면 일반 체인으로 폴백 (lenient)\n if (miniResult.length > 0) return miniResult\n }\n return readChain(entry.startSector, entry.size)\n }\n\n // ── 경로 기반 탐색 ──\n\n // 전체 경로 맵 구축 (간이: 이름 기반 flat lookup)\n // HWP 파일의 디렉토리 구조는 보통 1~2 depth이므로 이름 매칭으로 충분\n function findEntryByPath(path: string): DirEntry | null {\n // \"/FileHeader\" → \"FileHeader\"\n // \"/BodyText/Section0\" → path component matching\n const parts = path.replace(/^\\//, \"\").split(\"/\")\n\n if (parts.length === 1) {\n // 단일 이름 매칭\n return dirEntries.find(e => e.name === parts[0] && e.type === 2) ?? null\n }\n\n // 2-depth: storage/stream\n // HWP 구조: Root/BodyText/Section0, Root/DocInfo, Root/BinData/BIN0001 등\n const storageName = parts[0]\n const streamName = parts.slice(1).join(\"/\")\n\n // 디렉토리 구조 대신 이름 패턴으로 찾기 (lenient)\n for (const e of dirEntries) {\n if (e.type === 2 && e.name === streamName) {\n // 부모 확인은 생략 (lenient) — 중복 이름 시 첫 번째 반환\n return e\n }\n }\n\n // 정확한 이름이 아닌 경우 (ViewText/Section0 등)\n const lastPart = parts[parts.length - 1]\n return dirEntries.find(e => e.type === 2 && e.name === lastPart) ?? null\n }\n\n // ── 공개 API ──\n\n return {\n findStream(path: string): Buffer | null {\n // \\005 prefix 처리 (SummaryInformation)\n const normalized = path.replace(/^\\//, \"\")\n const entry = findEntryByPath(normalized)\n if (!entry || entry.type !== 2) return null\n const stream = readStreamData(entry)\n return stream.length > 0 ? stream : null\n },\n\n entries(): DirEntry[] {\n return dirEntries.filter(e => e.type === 2) // stream만\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/page-range.ts"],"sourcesContent":["/** 페이지/섹션 범위 파싱 유틸리티 */\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"],"mappings":";;;AASO,SAAS,eAAe,MAAyB,UAA+B;AACrF,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,YAAY,EAAG,QAAO;AAE1B,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI,QAAO;AAE3D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,QAAQ,MAAM,qBAAqB;AACtD,QAAI,YAAY;AACd,YAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AACrD,YAAM,MAAM,KAAK,IAAI,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AAC1D,eAAS,IAAI,OAAO,KAAK,KAAK,IAAK,QAAO,IAAI,CAAC;AAAA,IACjD,OAAO;AACL,YAAM,OAAO,SAAS,SAAS,EAAE;AACjC,UAAI,CAAC,MAAM,IAAI,KAAK,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/render/svg-render.ts","../src/render/layout.ts","../src/hwpx/text-metrics.ts","../src/render/reflow.ts"],"sourcesContent":["/**\n * 레이아웃 보존 렌더 — HWPX 조판 캐시(lineseg·cellAddr·hp:pos)를 SVG 절대배치로 그린다.\n *\n * 조판 엔진 없음: 한컴이 저장 시 기록한 좌표를 그대로 사용한다. 따라서\n * 한컴(또는 조판 캐시를 기록하는 편집기)에서 저장한 파일만 렌더 가능 —\n * markdownToHwpx 산출물엔 linesegarray가 없어 KordocError를 던진다.\n *\n * 좌표 산식(실측 검증 — .claude/plans/render-poc/findings.md):\n * - 단위 HWPUNIT(1/7200in), pt = /100. 최상위 문단 lineseg = 본문영역 로컬,\n * 셀 문단 = 셀 로컬. PARA 밀어내기 개체 anchor = 호스트vp − (omTop+h+omBottom) 역산.\n * - horzsize는 줄 \"영역\" 폭(텍스트 폭 아님) — 마지막 줄이 아닌 줄은 원본 줄바꿈에\n * 맞춰 textLength로 고정하고, 마지막 줄만 paraPr 정렬(LEFT 자연폭/CENTER/RIGHT/배분)을 적용.\n * - 좌표 속성엔 uint32로 저장된 음수가 섞여 있다(toInt32 필수).\n * - 표 열은 span 제약 경계 전파로, 행은 rs=1 max + 콘텐츠 초과 성장으로 푼다.\n *\n * 페이지: 최상위 lineseg vertpos는 페이지 로컬(페이지마다 0부터 리셋)이므로 역행\n * 지점을 페이지 경계로 감지, 전 페이지를 세로 스택으로 그린다(페이지별 흰 배경 +\n * 클립). 페이지에 걸친 표는 시작 페이지에서 잘린다(조판 캐시에 분할점이 없음).\n *\n * 범위: section0 한정(다중 구역은 첫 구역만), 수식·그리기개체 도형은 미지원(경고 수집).\n */\n\nimport JSZip from \"jszip\"\nimport { KordocError, precheckZipSize } from \"../utils.js\"\nimport { createXmlParser, findChildByLocalName, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES } from \"../hwpx/parser-shared.js\"\nimport { toInt32, solveBoundaries, solveRowHeights, type SpanConstraint } from \"./layout.js\"\nimport { measureTextWidth, type WrapMode } from \"../hwpx/text-metrics.js\"\nimport { parseRenderStyles, DEFAULT_CHAR, type RenderStyles, type RenderBorderEdge } from \"./head-styles.js\"\nimport { reflowSection } from \"./reflow.js\"\n\nexport interface RenderSvgOptions {\n /** 이미지 1장당 허용 최대 바이트 (기본 40MB) */\n maxImageBytes?: number\n /** 검색어 형광펜 — 텍스트 조각 내 매치 구간에 배경 rect (대소문자 무시).\n * charPr(스타일) 경계에 걸친 매치는 칠하지 못한다. */\n highlights?: string[]\n /** Tier-2 reflow — 조판 캐시(linesegarray) 없는 파일도 순수 TS 조판으로 렌더.\n * 캐시가 있으면 무시(한컴본은 캐시 재생). 기본 false(캐시 없으면 KordocError). */\n reflow?: boolean\n /** reflow 줄바꿈 폴백 모드 — 'keep'(어절) / 'charAll'(글자). 문단 paraPr에\n * breakSetting 선언이 있으면 그 값이 우선하며(BREAK_WORD=어절, KEEP_WORD=글자 —\n * 이름 역전 주의), 이 옵션은 선언 없는 문단에만 적용된다. */\n reflowMode?: WrapMode\n}\n\nexport interface RenderSvgResult {\n svg: string\n /** 페이지 폭 (pt) */\n width: number\n /** 전체 캔버스 높이 (pt) — 페이지 세로 스택 + 간격 */\n height: number\n /** 렌더된 페이지 수 */\n pageCount: number\n warnings: string[]\n stats: { texts: number; images: number; tables: number }\n}\n\n// ─── XML 헬퍼 ─────────────────────────────────────\n\nfunction ln(el: Element): string {\n return (el.tagName || \"\").replace(/^[^:]+:/, \"\")\n}\n\nfunction elements(el: Element): Element[] {\n const out: Element[] = []\n const children = el.childNodes\n if (!children) return out\n for (let i = 0; i < children.length; i++) {\n if (children[i].nodeType === 1) out.push(children[i] as Element)\n }\n return out\n}\n\nfunction num(el: Element | null, attr: string, fallback = 0): number {\n return el ? toInt32(el.getAttribute(attr) ?? undefined, fallback) : fallback\n}\n\nfunction findFirst(el: Element, name: string, depth = 0): Element | null {\n if (depth > 64) return null\n for (const ch of elements(el)) {\n if (ln(ch) === name) return ch\n const found = findFirst(ch, name, depth + 1)\n if (found) return found\n }\n return null\n}\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n}\n\n// ─── 내부 모델 ────────────────────────────────────\n\ninterface PageGeom { PW: number; PH: number; ML: number; MT: number; BODY_W: number; BODY_H: number }\n\nexport interface Seg { textpos: number; vertpos: number; horzpos: number; horzsize: number; textheight: number; baseline: number }\n\nexport interface ParaChar { ch: string; prId: string | null }\n\n/** 렌더 대상 개체 태그 — 이 외(도형류)는 경고 후 생략 */\nconst OBJ_TAGS = new Set([\"tbl\", \"pic\", \"container\", \"equation\", \"rect\", \"ellipse\", \"polygon\", \"curv\", \"line\", \"arc\", \"ole\", \"textart\"])\n\ninterface ParaObj { el: Element; tag: string; index: number; inline: boolean; width: number; height: number }\n\nexport interface ParaModel { chars: ParaChar[]; segs: Seg[]; objs: ParaObj[]; paraPrId: string | null }\n\ninterface Ctx {\n /** 페이지별 SVG 버퍼 — emit()이 현재 페이지(page)로 라우팅 */\n pages: string[][]\n /** 현재 그리는 페이지 인덱스 — 최상위 문단만 갱신, 셀/중첩 콘텐츠는 호스트 페이지 상속 */\n page: number\n geom: PageGeom\n styles: RenderStyles\n images: Map<string, { dataUri: string; symId?: string; orgW?: number; orgH?: number }>\n /** 이미지 심볼 defs — dataURI는 여기 1회만, 본문은 <use> 참조 (occurrence당 재emit 금지) */\n defs: string[]\n /** 검색어 형광펜 (소문자 정규화, 빈 문자열 제거) */\n highlights: string[]\n warnings: string[]\n warned: Set<string>\n stats: { texts: number; images: number; tables: number }\n}\n\nconst pt = (u: number): string => String(Math.round(u) / 100)\n\nfunction emit(ctx: Ctx, s: string): void {\n ctx.pages[ctx.page].push(s)\n}\n\nfunction warnOnce(ctx: Ctx, key: string, msg: string): void {\n if (ctx.warned.has(key)) return\n ctx.warned.add(key)\n ctx.warnings.push(msg)\n}\n\n// ─── 문단 모델 구축 ────────────────────────────────\n\n/** hp:t 안에서 1슬롯을 차지하는 문자형 컨트롤 (HWP5 문자 스트림 모델) —\n * 탭(0x09)은 char가 아니라 inline 컨트롤 = 8슬롯(16바이트)이라 여기 넣으면 안 된다\n * (record.ts 0x09 처리의 i+=14와 같은 모델. 1슬롯로 세면 탭당 7슬롯씩 줄 경계가 밀린다) */\nconst CHAR_CTRL_1SLOT = new Set([\"lineBreak\", \"hyphen\", \"nbSpace\", \"fwSpace\"])\n\n/** lineseg textpos 정합용 0폭 필러 슬롯 — 컨트롤이 차지하는 문자 위치를 채운다 */\nfunction pushFillers(chars: ParaChar[], n: number, prId: string | null): void {\n for (let i = 0; i < n; i++) chars.push({ ch: \"\", prId })\n}\n\n/**\n * hp:t 내용을 슬롯 스트림으로 변환 — lineseg textpos 는 HWP5 문자 스트림 기준이라\n * 텍스트 1문자=1슬롯(서로게이트 쌍은 2), tab 등 문자형 컨트롤도 1슬롯을 차지한다.\n * markpen 등 래퍼 요소는 슬롯 없이 내용만 재귀한다.\n * (탭 폭은 탭스톱 미해석으로 0 — 경계 정합이 우선, 폭 오차는 줄 스케일이 흡수)\n */\nfunction pushTextSlots(t: Element, chars: ParaChar[], prId: string | null, depth: number): void {\n if (depth > 32) return\n const kids = t.childNodes\n if (!kids) return\n for (let i = 0; i < kids.length; i++) {\n const c = kids[i]\n if (c.nodeType === 3) {\n for (const cp of c.textContent ?? \"\") {\n chars.push({ ch: cp, prId })\n if (cp.length === 2) chars.push({ ch: \"\", prId }) // UTF-16 두 번째 유닛 슬롯\n }\n } else if (c.nodeType === 1) {\n const el = c as Element\n const tag = ln(el)\n if (tag === \"tab\") {\n pushFillers(chars, 8, prId) // inline 컨트롤 8슬롯 (전부 폭 0 — 탭스톱 미해석)\n } else if (CHAR_CTRL_1SLOT.has(tag)) {\n chars.push({ ch: tag === \"nbSpace\" || tag === \"fwSpace\" ? \" \" : \"\", prId })\n } else {\n pushTextSlots(el, chars, prId, depth + 1)\n }\n }\n }\n}\n\nexport function buildPara(p: Element): ParaModel {\n const chars: ParaChar[] = []\n const objs: ParaObj[] = []\n let segs: Seg[] = []\n for (const runEl of elements(p)) {\n const tag = ln(runEl)\n if (tag === \"run\") {\n const prId = runEl.getAttribute(\"charPrIDRef\")\n for (const ch of elements(runEl)) {\n const cn = ln(ch)\n if (cn === \"t\") {\n pushTextSlots(ch, chars, prId, 0)\n } else if (OBJ_TAGS.has(cn)) {\n const sz = findChildByLocalName(ch, \"sz\")\n const pos = findChildByLocalName(ch, \"pos\")\n // 그리기 도형은 hp:sz가 없다 — curSz(>0) → orgSz 폴백\n const w = num(sz, \"width\") || num(findChildByLocalName(ch, \"curSz\"), \"width\") || num(findChildByLocalName(ch, \"orgSz\"), \"width\")\n const h = num(sz, \"height\") || num(findChildByLocalName(ch, \"curSz\"), \"height\") || num(findChildByLocalName(ch, \"orgSz\"), \"height\")\n objs.push({\n el: ch, tag: cn, index: chars.length,\n inline: pos?.getAttribute(\"treatAsChar\") === \"1\",\n width: w, height: h,\n })\n // 확장 컨트롤(GSO 등)은 문자 스트림에서 8슬롯 — 실측: 데모 코퍼스 1,132개\n // 멀티라인 문단에서 textpos 가 8슬롯 블록 중간에 걸린 경계 0건\n pushFillers(chars, 8, prId)\n } else {\n // secPr·ctrl(구역/단 정의)·필드 등 나머지 run 자식도 확장/인라인 컨트롤 8슬롯\n pushFillers(chars, 8, prId)\n }\n }\n } else if (tag === \"linesegarray\") {\n segs = elements(runEl).filter(s => ln(s) === \"lineseg\").map(s => ({\n textpos: num(s, \"textpos\"), vertpos: num(s, \"vertpos\"), horzpos: num(s, \"horzpos\"),\n horzsize: num(s, \"horzsize\"), textheight: num(s, \"textheight\", 1000), baseline: num(s, \"baseline\", 850),\n }))\n }\n }\n return { chars, segs, objs, paraPrId: p.getAttribute(\"paraPrIDRef\") }\n}\n\n// ─── 줄 렌더 (run별 charPr + 정렬) ──────────────────\n\ninterface LinePlan {\n seg: Seg\n /** 줄 시작 x 보정 (정렬) */\n xoff: number\n /** 텍스트 조각 폭 스케일 */\n scale: number\n start: number\n end: number\n}\n\nfunction charW(c: ParaChar, styles: RenderStyles): number {\n const st = (c.prId != null ? styles.charPr.get(c.prId) : undefined) ?? DEFAULT_CHAR\n return measureTextWidth(c.ch, st.height, st.ratio, { spacingPct: st.spacing })\n}\n\n/** 줄 자연폭 = 텍스트 조각 + 인라인 개체 폭 (개체 폭은 스케일 불변) */\nfunction lineNaturalWidth(m: ParaModel, styles: RenderStyles, start: number, end: number): { text: number; obj: number } {\n let text = 0\n for (let i = start; i < end && i < m.chars.length; i++) text += charW(m.chars[i], styles)\n let obj = 0\n for (const o of m.objs) if (o.inline && o.index >= start && o.index < end) obj += o.width\n return { text, obj }\n}\n\nfunction planLines(m: ParaModel, styles: RenderStyles): LinePlan[] {\n const align = (m.paraPrId != null ? styles.paraAlign.get(m.paraPrId) : undefined) ?? \"JUSTIFY\"\n const plans: LinePlan[] = []\n for (let i = 0; i < m.segs.length; i++) {\n const seg = m.segs[i]\n const start = seg.textpos\n const end = i + 1 < m.segs.length ? m.segs[i + 1].textpos : Math.max(m.chars.length, start)\n const nat = lineNaturalWidth(m, styles, start, end)\n const isLast = i === m.segs.length - 1\n let xoff = 0\n let scale = 1\n const avail = seg.horzsize - nat.obj\n if (nat.text > 0 && (!isLast || align === \"DISTRIBUTE\" || align === \"DISTRIBUTE_SPACE\")) {\n // 줄바꿈이 고정된 중간 줄·배분정렬 — 줄 영역 폭에 맞춰 고정 (원본 조판 재현)\n scale = avail > 0 ? avail / nat.text : 1\n } else if (nat.text + nat.obj > 0 && isLast) {\n const w = nat.text + nat.obj\n if (align === \"CENTER\") xoff = Math.max(0, (seg.horzsize - w) / 2)\n else if (align === \"RIGHT\") xoff = Math.max(0, seg.horzsize - w)\n }\n if (!Number.isFinite(scale) || scale <= 0) scale = 1\n scale = Math.min(4, Math.max(0.25, scale))\n plans.push({ seg, xoff, scale, start, end })\n }\n return plans\n}\n\n/** 줄 안 [start, upto) 구간의 전진폭 (텍스트×스케일 + 인라인 개체) */\nfunction advanceTo(m: ParaModel, styles: RenderStyles, plan: LinePlan, upto: number): number {\n let x = 0\n for (let i = plan.start; i < upto && i < m.chars.length; i++) x += charW(m.chars[i], styles) * plan.scale\n for (const o of m.objs) if (o.inline && o.index >= plan.start && o.index < upto) x += o.width\n return x\n}\n\n// ─── 문단 드로잉 ───────────────────────────────────\n\n/**\n * @param segPages 최상위 문단 전용 — seg 인덱스별 페이지 배정 (vertpos 리셋 감지 결과).\n * 셀/중첩 콘텐츠는 전달하지 않아 호스트의 ctx.page 를 상속한다.\n */\nfunction drawPara(p: Element, ox: number, oy: number, areaW: number, ctx: Ctx, depth: number, segPages?: number[]): void {\n if (depth > 16) { warnOnce(ctx, \"depth\", \"중첩 깊이 16 초과 — 이하 생략\"); return }\n const m = buildPara(p)\n if (m.segs.length === 0) {\n // 조판 캐시 없는 문단 — 개체만이라도 문단 원점에 배치. 텍스트는 무음 생략 금지\n if (m.chars.some(c => c.ch !== \"\")) {\n warnOnce(ctx, \"no-lineseg\", \"조판 캐시 없는 문단 텍스트 생략 — reflow 옵션으로 합성 가능\")\n }\n for (const o of m.objs) drawObject(o, ox, oy, 0, areaW, ctx, depth)\n return\n }\n const plans = planLines(m, ctx.styles)\n const baseV = m.segs[0].vertpos\n\n for (let li = 0; li < plans.length; li++) {\n const plan = plans[li]\n if (segPages && segPages[li] !== undefined) ctx.page = segPages[li]\n const { seg } = plan\n // charPr 단위 조각으로 분할\n let i = plan.start\n let cursor = ox + seg.horzpos + plan.xoff\n const y = oy + seg.vertpos + seg.baseline\n while (i < plan.end && i < m.chars.length) {\n // 필러 슬롯(컨트롤·서로게이트 자리)은 그리지 않고 건너뛴다 —\n // 인라인 개체의 폭 전진은 개체 첫 슬롯에서 1회 수행\n if (m.chars[i].ch === \"\") {\n for (const o of m.objs) if (o.inline && o.index === i) cursor += o.width\n i++\n continue\n }\n const prId = m.chars[i].prId\n let j = i\n let piece = \"\"\n // 필러에서 멈추므로 piece 안은 실문자뿐 — 개체 경계 절단이 자연 발생\n while (j < plan.end && j < m.chars.length && m.chars[j].prId === prId && m.chars[j].ch !== \"\") { piece += m.chars[j].ch; j++ }\n // 연속 공백(2+) 경계 절단 — 공백 폭 오차(한컴 0.5em 고정 vs 뷰어 폰트)를\n // 공백 구간에 가둔다 (공무원 스페이스 정렬 원문에서 글자 벌어짐 방지)\n // (문자열 인덱스를 슬롯 오프셋으로 쓴다 — 서로게이트 쌍이 섞이면 1슬롯 오차, 무시)\n {\n const cut = piece.search(/ {2,}/)\n if (cut > 0) { piece = piece.slice(0, cut); j = i + cut }\n else if (cut === 0) {\n const runEnd = piece.match(/^ +/)![0].length\n piece = piece.slice(0, runEnd); j = i + runEnd\n }\n }\n const st = (prId != null ? ctx.styles.charPr.get(prId) : undefined) ?? DEFAULT_CHAR\n\n // 텍스트 세그먼트 1개를 렌더하고 전진폭 반환. hit=true 면 형광펜 배경 rect 를\n // 텍스트 앞에 깐다. 세그먼트마다 자체 textLength 를 쓰므로 rect(hit)와 글자가\n // 완전히 같은 폭·위치로 계산돼 형광펜이 어긋나지 않는다.\n const renderSeg = (text: string, cx: number, hit: boolean): number => {\n const sw = measureTextWidth(text, st.height, st.ratio, { spacingPct: st.spacing }) * plan.scale\n if (hit) {\n emit(ctx, `<rect x=\"${pt(cx)}\" y=\"${pt(oy + seg.vertpos)}\" width=\"${pt(sw)}\" height=\"${pt(seg.textheight)}\" fill=\"#ffd54f\" fill-opacity=\"0.45\"/>`)\n }\n if (text.trim().length > 0) {\n const attrs: string[] = [`x=\"${pt(cx)}\"`, `y=\"${pt(y)}\"`, `font-size=\"${pt(st.height)}\"`]\n if (st.fontFamily) attrs.push(`font-family=\"${st.fontFamily}\"`)\n if ([...text].length > 1 && sw > 50) {\n attrs.push(`textLength=\"${pt(sw)}\"`, `lengthAdjust=\"${plan.scale < 1 ? \"spacingAndGlyphs\" : \"spacing\"}\"`)\n }\n if (st.bold) attrs.push(`font-weight=\"bold\"`)\n if (st.italic) attrs.push(`font-style=\"italic\"`)\n if (st.underline) attrs.push(`text-decoration=\"underline\"`)\n if (st.color) attrs.push(`fill=\"${escapeXml(st.color)}\"`)\n emit(ctx, `<text ${attrs.join(\" \")}>${escapeXml(text)}</text>`)\n ctx.stats.texts++\n }\n return sw\n }\n\n // 형광펜 매치 구간 수집 → 병합(겹침 제거) → [평문·매치·평문…] 분할 렌더\n const merged: Array<[number, number]> = []\n if (ctx.highlights.length > 0 && piece.trim().length > 0) {\n const found: Array<[number, number]> = []\n const lower = piece.toLowerCase()\n for (const term of ctx.highlights) {\n for (let f = lower.indexOf(term); f !== -1; f = lower.indexOf(term, f + term.length)) {\n found.push([f, f + term.length])\n }\n }\n found.sort((a, b) => a[0] - b[0])\n for (const [s, e] of found) {\n const tail = merged[merged.length - 1]\n if (tail && s <= tail[1]) tail[1] = Math.max(tail[1], e)\n else merged.push([s, e])\n }\n }\n\n if (merged.length === 0) {\n cursor += renderSeg(piece, cursor, false)\n } else {\n let segCur = cursor\n let last = 0\n for (const [s, e] of merged) {\n segCur += renderSeg(piece.slice(last, s), segCur, false)\n segCur += renderSeg(piece.slice(s, e), segCur, true)\n last = e\n }\n segCur += renderSeg(piece.slice(last), segCur, false)\n cursor = segCur\n }\n i = j\n }\n }\n\n // 개체 배치 — 인라인은 소속 줄 위치, 앵커는 hp:pos 해석\n for (const o of m.objs) {\n if (o.inline) {\n let planIdx = 0\n for (let k = 0; k < plans.length; k++) {\n const pl = plans[k]\n if (pl.start <= o.index && (o.index < pl.end || k === plans.length - 1)) planIdx = k\n }\n const plan = plans[planIdx]\n if (segPages && segPages[planIdx] !== undefined) ctx.page = segPages[planIdx]\n const x = ox + plan.seg.horzpos + plan.xoff + advanceTo(m, ctx.styles, plan, o.index)\n // 개체가 줄보다 낮으면 baseline 위에 얹고, 줄을 채우는 개체(th==h)는 줄 상단\n const yTop = oy + plan.seg.vertpos + Math.max(0, plan.seg.baseline - o.height)\n drawObject(o, x, yTop, baseV, areaW, ctx, depth)\n } else {\n // 앵커 좌표는 첫 seg(baseV) 기준이므로 첫 seg 의 페이지에 귀속\n if (segPages && segPages[0] !== undefined) ctx.page = segPages[0]\n const { x, y } = anchorObject(o, ox, oy, baseV, areaW, ctx)\n drawObject(o, x, y, baseV, areaW, ctx, depth)\n }\n }\n}\n\n/** hp:pos 기준계 해석 → 개체 좌상단 절대좌표 (tac=0) */\nfunction anchorObject(o: ParaObj, ox: number, oy: number, baseV: number, areaW: number, ctx: Ctx): { x: number; y: number } {\n const { PW, PH, ML, MT, BODY_W, BODY_H } = ctx.geom\n const pos = findChildByLocalName(o.el, \"pos\")\n const om = findChildByLocalName(o.el, \"outMargin\")\n const omT = num(om, \"top\"), omB = num(om, \"bottom\")\n const w = o.width, h = o.height\n if (!pos) return { x: ox, y: oy + baseV }\n const vo = num(pos, \"vertOffset\")\n const ho = num(pos, \"horzOffset\")\n const vrel = pos.getAttribute(\"vertRelTo\") ?? \"PARA\"\n const hrel = pos.getAttribute(\"horzRelTo\") ?? \"PARA\"\n const va = pos.getAttribute(\"vertAlign\") ?? \"TOP\"\n const ha = pos.getAttribute(\"horzAlign\") ?? \"LEFT\"\n const wrap = o.el.getAttribute(\"textWrap\") ?? \"TOP_AND_BOTTOM\"\n\n let y: number\n if (vrel === \"PAPER\") {\n y = va === \"BOTTOM\" ? PH - h - vo : va === \"CENTER\" ? (PH - h) / 2 + vo : vo\n } else if (vrel === \"PAGE\") {\n y = va === \"BOTTOM\" ? MT + BODY_H - h - vo : va === \"CENTER\" ? MT + (BODY_H - h) / 2 + vo : MT + vo\n } else if (wrap === \"TOP_AND_BOTTOM\") {\n // PARA 밀어내기 역산 — 호스트 문단 vp가 개체(여백 포함)만큼 밀렸다는 모델 (실측 일치).\n // 음수면 밀지 않은 개체(빈 호스트 줄 등)로 보고 현재 흐름 y 사용.\n const pushed = baseV - (omT + h + omB)\n const anchor = pushed >= -100 ? pushed : baseV\n y = oy + anchor + omT + vo\n } else {\n // BEHIND_TEXT/IN_FRONT/SQUARE — 문단을 밀지 않는 개체는 문단 원점 흐름 배치\n y = oy + baseV + vo\n }\n\n let x: number\n if (hrel === \"PAGE\") {\n x = ha === \"RIGHT\" ? ML + BODY_W - w - ho : ha === \"CENTER\" ? ML + (BODY_W - w) / 2 + ho : ML + ho\n } else if (hrel === \"PAPER\") {\n x = ha === \"RIGHT\" ? PW - w - ho : ha === \"CENTER\" ? (PW - w) / 2 + ho : ho\n } else {\n // PARA/COLUMN — 현재 텍스트 영역 기준 (셀 안이면 셀 내부 폭. COLUMN을 페이지\n // 단으로 읽으면 셀 안 개체가 페이지 왼쪽으로 튄다 — 사진대지 우측 셀 실측)\n x = ha === \"RIGHT\" ? ox + areaW - w - ho : ha === \"CENTER\" ? ox + (areaW - w) / 2 + ho : ox + ho\n }\n return { x, y }\n}\n\nfunction drawObject(o: ParaObj, x: number, y: number, baseV: number, areaW: number, ctx: Ctx, depth: number): void {\n if (o.tag === \"tbl\") drawTable(o.el, x, y, ctx, depth + 1)\n else if (o.tag === \"pic\") drawPic(o.el, x, y, ctx)\n else if (o.tag === \"container\") {\n // 그리기개체 묶음 — 자식 개체를 컨테이너 원점 기준으로 재귀 배치\n for (const ch of elements(o.el)) {\n const tag = ln(ch)\n if (!OBJ_TAGS.has(tag)) continue\n const sz = findChildByLocalName(ch, \"sz\")\n const off = findChildByLocalName(ch, \"offset\")\n const sub: ParaObj = { el: ch, tag, index: 0, inline: true, width: num(sz, \"width\"), height: num(sz, \"height\") }\n drawObject(sub, x + num(off, \"x\"), y + num(off, \"y\"), baseV, areaW, ctx, depth + 1)\n }\n } else if (o.tag === \"equation\") {\n warnOnce(ctx, \"equation\", \"수식 개체는 렌더 미지원 — 생략\")\n } else if (SHAPE_TAGS.has(o.tag)) {\n drawShape(o, x, y, ctx, depth)\n } else {\n warnOnce(ctx, `shape:${o.tag}`, `개체(${o.tag}) 렌더 미지원 — 생략`)\n }\n}\n\n// ─── 그리기 도형 (rect/ellipse/line/polygon/curv/arc) ────────────────\n// geometry 좌표는 개체 로컬(orgSz 기준). 실제 크기 = curSz(있으면)로 스케일.\n// lineShape=선(color/width/style), fillBrush>winBrush=채움(faceColor). 회전은 근사 생략.\n\nconst SHAPE_TAGS = new Set([\"rect\", \"ellipse\", \"line\", \"polygon\", \"curv\", \"arc\"])\n\n/** lineShape width(1/100 mm) → pt */\nfunction shapeStrokePt(v: number): number {\n return Math.max(0.2, (v / 100) * 2.834645)\n}\n\nfunction drawShape(o: ParaObj, x: number, y: number, ctx: Ctx, depth: number): void {\n const el = o.el\n const orgSz = findChildByLocalName(el, \"orgSz\")\n const curSz = findChildByLocalName(el, \"curSz\")\n const ow = num(orgSz, \"width\"), oh = num(orgSz, \"height\")\n const w = num(curSz, \"width\") || ow || o.width\n const h = num(curSz, \"height\") || oh || o.height\n const sx = ow > 0 ? w / ow : 1\n const sy = oh > 0 ? h / oh : 1\n\n const lineShape = findChildByLocalName(el, \"lineShape\")\n const lstyle = lineShape?.getAttribute(\"style\") ?? \"SOLID\"\n const strokeCol = lineShape?.getAttribute(\"color\") || \"#000000\"\n const hasStroke = lstyle !== \"NONE\"\n const strokeW = hasStroke ? shapeStrokePt(lineShape ? num(lineShape, \"width\") : 33) : 0\n const dash = /DASH|DOT/.test(lstyle) ? ` stroke-dasharray=\"${lstyle.includes(\"DOT\") ? \"1,1.5\" : \"3,1.5\"}\"` : \"\"\n const strokeAttr = hasStroke ? ` stroke=\"${escapeXml(strokeCol)}\" stroke-width=\"${strokeW.toFixed(2)}\"${dash}` : \"\"\n\n const fillBrush = findChildByLocalName(el, \"fillBrush\")\n const winBrush = fillBrush ? findChildByLocalName(fillBrush, \"winBrush\") : null\n const face = winBrush?.getAttribute(\"faceColor\")\n const fill = face && face.toLowerCase() !== \"none\" ? face : \"none\"\n const fillAttr = ` fill=\"${fill === \"none\" ? \"none\" : escapeXml(fill)}\"`\n\n if (o.tag === \"rect\") {\n emit(ctx, `<rect x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\"${fillAttr}${strokeAttr}/>`)\n } else if (o.tag === \"ellipse\") {\n emit(ctx, `<ellipse cx=\"${pt(x + w / 2)}\" cy=\"${pt(y + h / 2)}\" rx=\"${pt(w / 2)}\" ry=\"${pt(h / 2)}\"${fillAttr}${strokeAttr}/>`)\n } else if (o.tag === \"line\") {\n const s = findChildByLocalName(el, \"startPt\"), e = findChildByLocalName(el, \"endPt\")\n const x1 = x + num(s, \"x\") * sx, y1 = y + num(s, \"y\") * sy\n const x2 = x + num(e, \"x\") * sx, y2 = y + num(e, \"y\") * sy\n emit(ctx, `<line x1=\"${pt(x1)}\" y1=\"${pt(y1)}\" x2=\"${pt(x2)}\" y2=\"${pt(y2)}\" stroke=\"${escapeXml(strokeCol)}\" stroke-width=\"${(strokeW || 0.3).toFixed(2)}\"${dash}/>`)\n } else if (o.tag === \"polygon\" || o.tag === \"curv\") {\n const pts: string[] = []\n for (const c of elements(el)) if (ln(c) === \"pt\") pts.push(`${pt(x + num(c, \"x\") * sx)},${pt(y + num(c, \"y\") * sy)}`)\n if (pts.length >= 2) emit(ctx, `<polygon points=\"${pts.join(\" \")}\"${fillAttr}${strokeAttr}/>`)\n } else if (o.tag === \"arc\") {\n // 호는 외접 박스 타원으로 근사 (start/sweep 각 미해석)\n emit(ctx, `<ellipse cx=\"${pt(x + w / 2)}\" cy=\"${pt(y + h / 2)}\" rx=\"${pt(w / 2)}\" ry=\"${pt(h / 2)}\" fill=\"none\"${strokeAttr || ` stroke=\"${escapeXml(strokeCol)}\" stroke-width=\"0.3\"`}/>`)\n }\n\n // 도형 안 텍스트(drawText>subList) — 조판 캐시 있으면 그린다\n const dt = findChildByLocalName(el, \"drawText\")\n const sub = dt ? findChildByLocalName(dt, \"subList\") : null\n if (sub) {\n for (const p of elements(sub)) if (ln(p) === \"p\") drawPara(p, x, y, w, ctx, depth + 1)\n }\n}\n\n// ─── 표 ───────────────────────────────────────────\n\ninterface CellModel {\n el: Element\n ca: number; ra: number; cs: number; rs: number\n w: number; h: number\n bfId: string | null\n sub: Element | null\n marginL: number; marginR: number; marginT: number; marginB: number\n}\n\n/** 셀 콘텐츠 세로 범위 — 줄(vp+th) + 인라인 개체(중첩표·treatAsChar) + PARA 앵커 개체(anchor+h) 최대값 */\nfunction cellContentExtent(cell: CellModel): number {\n if (!cell.sub) return 0\n let ext = 0\n for (const p of elements(cell.sub)) {\n if (ln(p) !== \"p\") continue\n const m = buildPara(p)\n for (const s of m.segs) ext = Math.max(ext, s.vertpos + s.textheight)\n const baseV = m.segs[0]?.vertpos ?? 0\n for (const o of m.objs) {\n if (o.inline) {\n // 인라인 개체(중첩 표·treatAsChar 이미지)는 줄 위치에서 개체 높이만큼 아래로 뻗는다.\n // 이를 빼먹으면 중첩 표를 담은 셀 높이가 과소측정돼 표지 중첩표가 겹친다(리뷰: 셀 성장 누락).\n const h = o.tag === \"tbl\" ? Math.max(o.height, measureTableHeight(o.el)) : o.height\n ext = Math.max(ext, baseV + h)\n continue\n }\n const pos = findChildByLocalName(o.el, \"pos\")\n if ((pos?.getAttribute(\"vertRelTo\") ?? \"PARA\") !== \"PARA\") continue\n const om = findChildByLocalName(o.el, \"outMargin\")\n const pushed = baseV - (num(om, \"top\") + o.height + num(om, \"bottom\"))\n const anchor = pushed >= -100 ? pushed : baseV\n ext = Math.max(ext, anchor + num(om, \"top\") + num(pos, \"vertOffset\") + o.height)\n }\n }\n return ext\n}\n\nfunction edgeLine(x1: number, y1: number, x2: number, y2: number, e: RenderBorderEdge): string {\n const dash = /DASH|DOT/.test(e.type) ? ` stroke-dasharray=\"${e.type.includes(\"DOT\") ? \"1,1.5\" : \"3,1.5\"}\"` : \"\"\n return `<line x1=\"${pt(x1)}\" y1=\"${pt(y1)}\" x2=\"${pt(x2)}\" y2=\"${pt(y2)}\" stroke=\"${escapeXml(e.color)}\" stroke-width=\"${e.widthPt.toFixed(2)}\"${dash}/>`\n}\n\n/** tbl의 셀 모델 수집 — drawTable과 measureTableHeight가 같은 셀 해석을 공유 */\nfunction collectCells(tbl: Element): CellModel[] {\n const inMargin = findChildByLocalName(tbl, \"inMargin\")\n const defL = num(inMargin, \"left\", 141), defR = num(inMargin, \"right\", 141)\n const defT = num(inMargin, \"top\", 141), defB = num(inMargin, \"bottom\", 141)\n\n const cells: CellModel[] = []\n for (const tr of elements(tbl)) {\n if (ln(tr) !== \"tr\") continue\n for (const tc of elements(tr)) {\n if (ln(tc) !== \"tc\") continue\n const addr = findChildByLocalName(tc, \"cellAddr\")\n const span = findChildByLocalName(tc, \"cellSpan\")\n const csz = findChildByLocalName(tc, \"cellSz\")\n const cm = findChildByLocalName(tc, \"cellMargin\")\n if (!addr || !csz) continue\n cells.push({\n el: tc,\n ca: num(addr, \"colAddr\"), ra: num(addr, \"rowAddr\"),\n cs: Math.max(1, num(span, \"colSpan\", 1)), rs: Math.max(1, num(span, \"rowSpan\", 1)),\n w: num(csz, \"width\"), h: num(csz, \"height\"),\n bfId: tc.getAttribute(\"borderFillIDRef\"),\n sub: findChildByLocalName(tc, \"subList\"),\n marginL: cm ? num(cm, \"left\", defL) : defL, marginR: cm ? num(cm, \"right\", defR) : defR,\n marginT: cm ? num(cm, \"top\", defT) : defT, marginB: cm ? num(cm, \"bottom\", defB) : defB,\n })\n }\n }\n return cells\n}\n\n/**\n * 표 실효 높이(HWPUNIT) — drawTable의 rowH 모델(solveRowHeights + 셀 콘텐츠 성장) 그대로.\n * 선언 hp:sz는 셀 콘텐츠로 자란 높이를 모르므로, reflow가 표 뒤 문단을 실제 그려질\n * 표 바닥 아래로 배치할 때 이 값을 쓴다. 셀 lineseg가 있어야 성장분이 측정된다.\n */\nexport function measureTableHeight(tbl: Element): number {\n const cells = collectCells(tbl)\n if (cells.length === 0 || cells.length > 4096) return 0\n const nRows = Math.max(...cells.map(c => c.ra + c.rs))\n const rowH = solveRowHeights(\n cells.map(c => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : undefined })),\n nRows,\n )\n let sum = 0\n for (const h of rowH) sum += h\n return sum\n}\n\nfunction drawTable(tbl: Element, tx: number, ty: number, ctx: Ctx, depth: number): void {\n if (depth > 16) { warnOnce(ctx, \"depth\", \"중첩 깊이 16 초과 — 이하 생략\"); return }\n ctx.stats.tables++\n const tblSz = findChildByLocalName(tbl, \"sz\")\n const cells = collectCells(tbl)\n if (cells.length === 0 || cells.length > 4096) return\n\n const nCols = Math.max(...cells.map(c => c.ca + c.cs))\n const nRows = Math.max(...cells.map(c => c.ra + c.rs))\n const colCons: SpanConstraint[] = cells.map(c => ({ a: c.ca, b: c.ca + c.cs, size: c.w }))\n const colX = solveBoundaries(colCons, nCols, num(tblSz, \"width\") || undefined)\n const rowH = solveRowHeights(\n cells.map(c => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : undefined })),\n nRows,\n )\n const rowY: number[] = [0]\n for (let r = 0; r < nRows; r++) rowY.push(rowY[r] + rowH[r])\n\n // 1패스: 배경 → 2패스: 콘텐츠 → 3패스: 테두리 (테두리가 배경/콘텐츠 위)\n const geom = cells.map(c => ({\n c,\n x: tx + colX[c.ca], y: ty + rowY[c.ra],\n w: colX[Math.min(c.ca + c.cs, nCols)] - colX[c.ca],\n h: rowY[Math.min(c.ra + c.rs, nRows)] - rowY[c.ra],\n }))\n for (const g of geom) {\n const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : undefined\n if (bf?.fill) emit(ctx, `<rect x=\"${pt(g.x)}\" y=\"${pt(g.y)}\" width=\"${pt(g.w)}\" height=\"${pt(g.h)}\" fill=\"${escapeXml(bf.fill)}\"/>`)\n }\n for (const g of geom) {\n const { c } = g\n if (!c.sub) continue\n const innerH = g.h - c.marginT - c.marginB\n const extent = cellContentExtent(c)\n const va = c.sub.getAttribute(\"vertAlign\") ?? \"TOP\"\n let yoff = 0\n if (va === \"CENTER\") yoff = Math.max(0, (innerH - extent) / 2)\n else if (va === \"BOTTOM\") yoff = Math.max(0, innerH - extent)\n for (const p of elements(c.sub)) {\n if (ln(p) !== \"p\") continue\n drawPara(p, g.x + c.marginL, g.y + c.marginT + yoff, g.w - c.marginL - c.marginR, ctx, depth + 1)\n }\n }\n for (const g of geom) {\n const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : undefined\n if (!bf) continue\n if (bf.top) emit(ctx, edgeLine(g.x, g.y, g.x + g.w, g.y, bf.top))\n if (bf.bottom) emit(ctx, edgeLine(g.x, g.y + g.h, g.x + g.w, g.y + g.h, bf.bottom))\n if (bf.left) emit(ctx, edgeLine(g.x, g.y, g.x, g.y + g.h, bf.left))\n if (bf.right) emit(ctx, edgeLine(g.x + g.w, g.y, g.x + g.w, g.y + g.h, bf.right))\n }\n}\n\n// ─── 이미지 ───────────────────────────────────────\n\n/**\n * 이미지 심볼 등록(바이너리당 1회) — dataURI를 defs에 한 번만 넣고 <use>로 참조한다.\n * occurrence마다 dataURI를 재emit하면 반복 참조 문서에서 SVG 문자열이 기하급수로\n * 커져 RangeError/OOM (소형 입력 DoS). viewBox 100×100 + preserveAspectRatio none\n * 이라 <use width/height>가 기존 <image width/height> stretch와 동일하게 스케일된다.\n */\nfunction imageSymbol(loaded: { dataUri: string; symId?: string }, ctx: Ctx): string {\n if (!loaded.symId) {\n loaded.symId = `bin${ctx.defs.length}`\n ctx.defs.push(\n `<symbol id=\"${loaded.symId}\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\">` +\n `<image width=\"100\" height=\"100\" preserveAspectRatio=\"none\" href=\"${loaded.dataUri}\"/></symbol>`,\n )\n }\n return loaded.symId\n}\n\nfunction drawPic(pic: Element, x: number, y: number, ctx: Ctx): void {\n const sz = findChildByLocalName(pic, \"sz\")\n const w = num(sz, \"width\", 5669), h = num(sz, \"height\", 5669)\n const img = findFirst(pic, \"img\")\n const ref = img?.getAttribute(\"binaryItemIDRef\")\n const loaded = ref != null ? ctx.images.get(ref) : undefined\n if (!loaded) {\n emit(ctx, `<rect x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\" fill=\"#eee\" stroke=\"#c00\" stroke-width=\"0.5\"/>`)\n warnOnce(ctx, `img:${ref}`, `이미지 바이너리 누락: ${ref ?? \"(ref 없음)\"}`)\n return\n }\n ctx.stats.images++\n // imgClip 좌표계는 imgDim(이미지 내용 상자) 기준이다 — orgSz(최초 삽입 크기)가 아니다.\n // 실측(데모 코퍼스 pic 267개): clip==dim 254개(크롭 없음)·실제 크롭 8개 모두 dim 기준.\n // orgSz 로 비교하면 리사이즈된 로고(dim<org)가 좌상단 코너로 잘못 잘려 로고가 깨진다.\n const clip = findChildByLocalName(pic, \"imgClip\")\n const imgDim = findChildByLocalName(pic, \"imgDim\")\n const orgSz = findChildByLocalName(pic, \"orgSz\")\n const dimW = num(imgDim, \"dimwidth\"), dimH = num(imgDim, \"dimheight\")\n // 참조(전체 내용) 공간: imgDim 우선, 없으면 orgSz 폴백\n const refW = dimW > 0 ? dimW : num(orgSz, \"width\")\n const refH = dimH > 0 ? dimH : num(orgSz, \"height\")\n const cl = num(clip, \"left\"), ct = num(clip, \"top\")\n const cr = num(clip, \"right\", refW), cb = num(clip, \"bottom\", refH)\n const cropped =\n refW > 0 && refH > 0 && clip != null && (cl > 0 || ct > 0 || cr < refW || cb < refH) && cr > cl && cb > ct\n const symId = imageSymbol(loaded, ctx)\n if (cropped) {\n // 참조 좌표계 viewBox로 크롭 창을 내고, 이미지는 전체 내용 크기로 깐다\n emit(ctx,\n `<svg x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\" viewBox=\"${pt(cl)} ${pt(ct)} ${pt(cr - cl)} ${pt(cb - ct)}\" preserveAspectRatio=\"none\">` +\n `<use href=\"#${symId}\" x=\"0\" y=\"0\" width=\"${pt(refW)}\" height=\"${pt(refH)}\"/></svg>`,\n )\n } else {\n emit(ctx, `<use href=\"#${symId}\" x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\"/>`)\n }\n}\n\nfunction sniffMime(name: string, bytes: Uint8Array): string {\n const lower = name.toLowerCase()\n if (lower.endsWith(\".png\") || (bytes.length > 4 && bytes[0] === 0x89 && bytes[1] === 0x50)) return \"image/png\"\n if (lower.endsWith(\".bmp\") || (bytes.length > 2 && bytes[0] === 0x42 && bytes[1] === 0x4d)) return \"image/bmp\"\n if (lower.endsWith(\".gif\")) return \"image/gif\"\n if (lower.endsWith(\".svg\")) return \"image/svg+xml\"\n return \"image/jpeg\"\n}\n\n// ─── 엔트리 ───────────────────────────────────────\n\n/** 구역 하나의 페이지 지오메트리 (HWPUNIT) */\nfunction readSectionGeom(root: Element): PageGeom {\n const pagePr = findFirst(root, \"pagePr\")\n const margin = pagePr ? findChildByLocalName(pagePr, \"margin\") : null\n let PW = num(pagePr, \"width\", 59528), PH = num(pagePr, \"height\", 84188)\n // 용지 방향 — width/height는 용지 원방향(세로) 치수로 저장되고 landscape=\"NARROWLY\"가\n // 가로(90° 회전)를 뜻한다(WIDELY=세로). 무시하면 가로 문서의 오른쪽이 잘린다\n // (실측: 가로 A4 문서 lineseg 줄폭 78520 = 84188 − 좌우마진, 세로폭 59528 초과).\n if (pagePr?.getAttribute(\"landscape\") === \"NARROWLY\" && PW < PH) {\n const t = PW; PW = PH; PH = t\n }\n const ML = num(margin, \"left\", 8504)\n const MT = num(margin, \"top\", 5668) + num(margin, \"header\", 0)\n const BODY_H = PH - MT - num(margin, \"bottom\", 4252) - num(margin, \"footer\", 0)\n const BODY_W = PW - ML - num(margin, \"right\", 8504)\n return { PW, PH, ML, MT, BODY_W, BODY_H }\n}\n\n/** 렌더된 구역 — 페이지 버퍼 + 페이지 크기(구역별 pagePr 상이 가능) */\ninterface RenderedSection { pages: string[][]; PW: number; pageH: number; clipId: string }\n\n/**\n * 구역 하나를 렌더 → 페이지 버퍼. 공유 자원(styles/images/defs/warnings/stats)은 ctx로 넘겨\n * 이미지 심볼·통계·경고가 전 구역에 누적되게 한다(dataURI 중복·페이지 카운트 정합).\n */\nfunction renderSectionToPages(\n root: Element,\n geom: PageGeom,\n ctxBase: Omit<Ctx, \"pages\" | \"page\" | \"geom\">,\n doReflow: boolean,\n reflowMode: WrapMode,\n): { pages: string[][]; pageH: number } {\n const { PW, PH, ML, MT, BODY_W, BODY_H } = geom\n // Tier-2 reflow — 캐시 없는 문단에 linesegarray 합성 주입. 혼합 캐시 문서(한컴\n // 저장본을 프로그램 편집해 일부 문단만 캐시 없음)도 reflow 옵션이면 진입한다 —\n // 전량 캐시 문서는 전 문단 skip(Tier-1 무회귀)이라 no-op.\n if (doReflow) reflowSection(root, ctxBase.styles, { BODY_W, BODY_H }, reflowMode)\n\n // 페이지 분할 프리패스 — 최상위 lineseg vertpos는 페이지 로컬(페이지마다 0부터)이라\n // 역행 지점이 곧 페이지 경계다. 다단(colCount>1)은 단 이동도 vertpos가 리셋되지만\n // horzpos가 오른쪽으로 점프하므로, horzpos가 왼쪽으로 돌아올 때만 새 페이지로 본다.\n // vertpos \"동일\"도 문단 첫 seg + horzpos 비전진이면 경계다 — 페이지 전체가 표 하나인\n // 문단이 연속되면 vertpos가 매 페이지 0으로 같아서 strict 역행만으론 못 가른다\n // (의사일정표류: v0 문단 연속 → 뒤 페이지들이 전부 한 페이지에 겹침).\n // 문단 내부의 vertpos 동일 seg는 개체 좌우로 갈라진 같은 줄(h 우측 점프)이라 제외.\n const colPr = findFirst(root, \"colPr\")\n const multiCol = num(colPr, \"colCount\", 1) > 1\n const paraSegPages = new Map<Element, number[]>()\n let nPages = 1\n let maxTopV = 0\n {\n let prevV = Number.NEGATIVE_INFINITY\n let prevH = Number.NEGATIVE_INFINITY\n let cur = 0\n for (const p of elements(root)) {\n if (ln(p) !== \"p\") continue\n const lsa = findChildByLocalName(p, \"linesegarray\")\n const segEls = lsa ? elements(lsa).filter(s => ln(s) === \"lineseg\") : []\n const pagesOf: number[] = []\n let paraFirst = true\n for (const s of segEls) {\n const v = num(s, \"vertpos\")\n const h = num(s, \"horzpos\")\n const brk = v < prevV\n ? (!multiCol || h <= prevH)\n : (paraFirst && v === prevV && h <= prevH)\n if (brk) cur++\n paraFirst = false\n pagesOf.push(cur)\n maxTopV = Math.max(maxTopV, v + num(s, \"textheight\", 1000))\n prevV = v\n prevH = h\n }\n paraSegPages.set(p, pagesOf)\n nPages = Math.max(nPages, cur + 1)\n }\n }\n\n const ctx: Ctx = {\n ...ctxBase,\n pages: Array.from({ length: nPages }, () => []),\n page: 0,\n geom,\n }\n for (const p of elements(root)) {\n if (ln(p) !== \"p\") continue\n drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p))\n }\n\n // 리셋이 없는데 본문이 페이지를 넘는 파일(누적 vertpos 기록본) 방어 —\n // 한 페이지로 두되 캔버스만 내용 끝까지 늘려 잘림을 막는다.\n const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2000) : PH\n return { pages: ctx.pages, pageH }\n}\n\n/**\n * HWPX(한컴 저장본) → 레이아웃 보존 SVG. **전 구역(section*)을 세로 스택으로** 렌더한다.\n * 조판 캐시(linesegarray)가 없는 구역은 reflow 옵션으로 합성 조판(없으면 그 구역 생략);\n * 렌더 가능한 구역이 하나도 없으면 KordocError.\n */\nexport async function renderHwpxToSvg(input: ArrayBuffer | Uint8Array, options?: RenderSvgOptions): Promise<RenderSvgResult> {\n const maxImg = options?.maxImageBytes ?? 40 * 1024 * 1024\n // 압축폭탄 가드 — 파서 진입점과 동일하게 압축해제 전 central directory 선언 크기를 검사한다.\n // 렌더 이미지 캡은 f.async 로 엔트리를 전량 압축해제한 뒤에야 크기를 보므로, 단일 BinData\n // 압축폭탄(수 GB 선언)이 캡을 우회해 OOM 을 유발하던 것을 원천 차단한다 (reflow-1).\n const ab = input instanceof Uint8Array\n ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer\n : input\n precheckZipSize(ab, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES)\n let zip: JSZip\n try {\n zip = await JSZip.loadAsync(input)\n } catch {\n throw new KordocError(\"HWPX(ZIP) 형식이 아닙니다 — 렌더는 HWPX만 지원\")\n }\n // 모든 구역 파일 (section0, section1, … — 이름 정렬)\n const secFiles = zip.file(/Contents\\/section\\d+\\.xml$/i).sort((a, b) => a.name.localeCompare(b.name))\n if (secFiles.length === 0) throw new KordocError(\"Contents/section0.xml 없음 — HWPX가 아니거나 손상됨\")\n\n const warnings: string[] = []\n const headFile = zip.file(\"Contents/header.xml\") ?? zip.file(\"Contents/head.xml\")\n const styles: RenderStyles = headFile\n ? parseRenderStyles(await headFile.async(\"string\"))\n : { charPr: new Map(), paraAlign: new Map(), paraGeom: new Map(), borderFill: new Map() }\n if (!headFile) warnings.push(\"header.xml 없음 — 기본 스타일로 렌더\")\n\n // 구역 XML 선로딩 + 크기 가드\n const secXmls: string[] = []\n for (const f of secFiles) {\n const xml = await f.async(\"string\")\n if (xml.length > MAX_DECOMPRESS_SIZE) throw new KordocError(\"섹션 XML이 허용 크기를 초과\")\n secXmls.push(xml)\n }\n\n // BinData 매니페스트 (id → href) — content.hpf 우선, 파일명 휴리스틱 폴백\n const binmap = new Map<string, string>()\n const hpf = zip.file(/content\\.hpf$/i)[0]\n if (hpf) {\n const man = await hpf.async(\"string\")\n for (const m of man.matchAll(/<[^>]*\\bid=\"([^\"]+)\"[^>]*\\bhref=\"(BinData\\/[^\"]+)\"[^>]*>/g)) binmap.set(m[1], m[2])\n for (const m of man.matchAll(/<[^>]*\\bhref=\"(BinData\\/[^\"]+)\"[^>]*\\bid=\"([^\"]+)\"[^>]*>/g)) binmap.set(m[2], m[1])\n }\n // 참조된 이미지만 선로딩 — 전 구역 ref 합집합 (DOM 워크 전에 async 구간 종료).\n // 장당 캡(maxImg) 외에 개수·누적 바이트 캡 — distinct 다수/대형 반복 참조로\n // 렌더 경로에서만 OOM 가능하던 구멍(파서 경로의 ZIP bomb 가드에 상응)\n const MAX_IMAGE_REFS = 256\n const MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024\n const images: Ctx[\"images\"] = new Map()\n const refs = new Set<string>()\n for (const xml of secXmls) for (const m of xml.matchAll(/binaryItemIDRef=\"([^\"]+)\"/g)) refs.add(m[1])\n let totalImgBytes = 0\n for (const ref of refs) {\n if (images.size >= MAX_IMAGE_REFS) {\n warnings.push(`이미지 ${refs.size}종 중 ${MAX_IMAGE_REFS}종만 로딩 — 개수 한도 초과분 생략`)\n break\n }\n let href = binmap.get(ref)\n if (!href) {\n const cand = zip.file(new RegExp(`BinData/.*${ref.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"))[0]\n href = cand?.name\n }\n if (!href) continue\n const f = zip.file(href) ?? zip.file(\"Contents/\" + href)\n if (!f) continue\n const bytes = await f.async(\"uint8array\")\n if (bytes.length > maxImg) {\n warnings.push(`이미지 ${href} ${(bytes.length / 1048576).toFixed(1)}MB — 한도 초과로 생략`)\n continue\n }\n if (totalImgBytes + bytes.length > MAX_TOTAL_IMAGE_BYTES) {\n warnings.push(`이미지 누적 ${Math.round(MAX_TOTAL_IMAGE_BYTES / 1048576)}MB 한도 초과 — 이후 생략`)\n break\n }\n totalImgBytes += bytes.length\n images.set(ref, { dataUri: `data:${sniffMime(href, bytes)};base64,${Buffer.from(bytes).toString(\"base64\")}` })\n }\n\n // 전 구역 공유 컨텍스트 자원 (이미지 심볼 defs·통계·경고를 누적)\n const ctxBase: Omit<Ctx, \"pages\" | \"page\" | \"geom\"> = {\n styles, images, defs: [],\n highlights: (options?.highlights ?? []).map(s => s.trim().toLowerCase()).filter(s => s.length > 0),\n warnings, warned: new Set(), stats: { texts: 0, images: 0, tables: 0 },\n }\n\n // 구역별 렌더\n const rendered: RenderedSection[] = []\n let noCacheSkipped = false\n for (let si = 0; si < secXmls.length; si++) {\n const secXml = secXmls[si]\n // 태그 마크업만 매치 — 본문 텍스트에 \"linesegarray\"가 있어도 캐시로 오판하지 않는다\n // (본문의 <는 XML에서 &lt;로 이스케이프되므로 리터럴 <…linesegarray는 태그뿐)\n const hasCache = /<(?:[A-Za-z][\\w.-]*:)?linesegarray[\\s/>]/.test(secXml)\n if (!hasCache && !options?.reflow) {\n noCacheSkipped = true\n warnings.push(`구역 ${si}: 조판 캐시 없음 — reflow 옵션 필요, 생략`)\n continue\n }\n const doc = createXmlParser().parseFromString(secXml, \"text/xml\")\n const root = doc.documentElement as unknown as Element\n if (!root) { warnings.push(`구역 ${si} XML 파싱 실패 — 생략`); continue }\n const geom = readSectionGeom(root)\n const { pages, pageH } = renderSectionToPages(root, geom, ctxBase, !!options?.reflow, options?.reflowMode ?? \"keep\")\n rendered.push({ pages, PW: geom.PW, pageH, clipId: `pgclip${si}` })\n }\n\n if (rendered.length === 0) {\n if (noCacheSkipped) {\n throw new KordocError(\"조판 캐시(linesegarray) 없음 — 한컴에서 저장한 HWPX만 렌더 가능 (reflow 옵션으로 합성 렌더 가능)\")\n }\n throw new KordocError(\"렌더할 구역이 없습니다 — HWPX가 손상되었을 수 있습니다\")\n }\n\n // 전 구역 페이지를 세로 스택으로 조립 (구역마다 page 크기 상이 가능 → 구역별 clip)\n const GAP = 2400 // 페이지 사이 시각 간격 (24pt)\n const clipDefs: string[] = []\n const groups: string[] = []\n let y = 0\n let maxPW = 0\n let pageNo = 0\n for (const rs of rendered) {\n maxPW = Math.max(maxPW, rs.PW)\n clipDefs.push(`<clipPath id=\"${rs.clipId}\"><rect x=\"0\" y=\"0\" width=\"${pt(rs.PW)}\" height=\"${pt(rs.pageH)}\"/></clipPath>`)\n for (const buf of rs.pages) {\n pageNo++\n groups.push(\n `<g data-page=\"${pageNo}\" transform=\"translate(0 ${pt(y)})\">` +\n `<rect width=\"${pt(rs.PW)}\" height=\"${pt(rs.pageH)}\" fill=\"white\" stroke=\"#c9c7c4\" stroke-width=\"0.75\"/>` +\n `<g clip-path=\"url(#${rs.clipId})\">\\n${buf.join(\"\\n\")}\\n</g></g>`,\n )\n y += rs.pageH + GAP\n }\n }\n const totalH = Math.max(0, y - GAP)\n\n // width/height는 pt 단위 명시 — 단위 없는 px로 두면 A4 실물(96dpi 기준)보다 25% 작게 보인다 (v3.10.1)\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${pt(maxPW)} ${pt(totalH)}\" width=\"${pt(maxPW)}pt\" height=\"${pt(totalH)}pt\" font-family=\"'HCR Batang','함초롬바탕','Hancom Batang',AppleMyungjo,'Noto Serif CJK KR',serif\" xml:space=\"preserve\">\\n` +\n `<defs>${clipDefs.join(\"\")}${ctxBase.defs.join(\"\")}</defs>\\n` +\n `${groups.join(\"\\n\")}\\n</svg>`\n return { svg, width: Math.round(maxPW) / 100, height: Math.round(totalH) / 100, pageCount: pageNo, warnings, stats: ctxBase.stats }\n}\n","/**\n * 레이아웃 보존 렌더 — 순수 계산 유틸 (XML 무접촉).\n * 좌표 산식은 .claude/plans/render-poc/findings.md에서 실측 검증된 모델.\n * 단위: HWPUNIT(1/7200in), pt 변환은 /100.\n */\n\n/** HWPX 좌표 속성은 uint32로 저장된 음수가 섞여 있다 (예: vertOffset=4294967103 = -193) */\nexport function toInt32(v: string | undefined, fallback = 0): number {\n if (v == null || v === \"\") return fallback\n const n = Number(v)\n if (!Number.isFinite(n)) return fallback\n return n > 0x7fffffff ? n - 0x100000000 : n\n}\n\n/** 셀 하나가 주는 경계 제약: boundary[a] + size = boundary[b] */\nexport interface SpanConstraint {\n a: number\n b: number\n size: number\n}\n\n/**\n * 표 열 경계 솔버 — 경계 전파.\n * 결재문서 트레일러처럼 41열 그리드에 span-1 셀이 거의 없어도,\n * x[ca+cs] = x[ca] + w 제약을 고정점까지 전파하면 전 경계가 풀린다 (실측 검증).\n * 미해결 경계는 인접 확정 경계 사이 균등 보간.\n */\nexport function solveBoundaries(constraints: SpanConstraint[], count: number, total?: number): number[] {\n const x = new Array<number | undefined>(count + 1).fill(undefined)\n x[0] = 0\n if (total != null && total > 0) x[count] = total\n let changed = true\n let guard = 0\n while (changed && guard++ < count + 8) {\n changed = false\n for (const c of constraints) {\n if (c.a < 0 || c.b > count || c.a >= c.b) continue\n const xa = x[c.a]\n const xb = x[c.b]\n if (xa != null && xb == null) {\n x[c.b] = xa + c.size\n changed = true\n } else if (xb != null && xa == null) {\n x[c.a] = xb - c.size\n changed = true\n }\n }\n }\n // 잔여 미해결 경계 — 좌우 확정 경계 사이 균등 보간\n let i = 0\n while (i <= count) {\n if (x[i] != null) { i++; continue }\n let lo = i - 1\n let hi = i\n while (hi <= count && x[hi] == null) hi++\n const loV = x[lo] as number\n const hiV = hi <= count ? (x[hi] as number) : loV + (hi - lo) * 1000\n const n = hi - lo\n for (let k = 1; k < n; k++) x[lo + k] = loV + ((hiV - loV) * k) / n\n if (hi > count) x[count] = hiV\n i = hi\n }\n // 단조 보정 (모순 제약 방어)\n const out = x as number[]\n for (let k = 1; k <= count; k++) if (out[k] < out[k - 1]) out[k] = out[k - 1]\n return out\n}\n\n/**\n * 표 행 높이 솔버 — rowSpan=1 셀의 max가 기본, rowSpan>1은 잔여 균등분배.\n * h=1 더미 셀(헤어라인 행 장식)이 섞이므로 열과 달리 max 방식이 안전.\n * grow: 셀 콘텐츠(사진 등)가 설계 높이를 넘으면 초과분만큼 행이 자란다 —\n * 사진대지 실측: 설계 16441 + 사진 22209 → 행 22209, 표 sz 합치 정확 일치.\n */\nexport function solveRowHeights(\n cells: { rowAddr: number; rowSpan: number; height: number; contentH?: number }[],\n rowCount: number,\n): number[] {\n const h = new Array<number>(rowCount).fill(0)\n for (const c of cells) {\n if (c.rowSpan === 1 && c.rowAddr >= 0 && c.rowAddr < rowCount) {\n h[c.rowAddr] = Math.max(h[c.rowAddr], c.height)\n }\n }\n // rowSpan>1 셀 — 포함 행 중 미확정(0) 행에 잔여 균등분배\n for (const c of cells) {\n if (c.rowSpan <= 1) continue\n const rows: number[] = []\n for (let r = c.rowAddr; r < Math.min(c.rowAddr + c.rowSpan, rowCount); r++) rows.push(r)\n const known = rows.reduce((s, r) => s + h[r], 0)\n const missing = rows.filter(r => h[r] === 0)\n if (missing.length > 0 && c.height > known) {\n const each = (c.height - known) / missing.length\n for (const r of missing) h[r] = each\n }\n }\n // 콘텐츠 초과 성장 (rowSpan=1 셀만 — 사진 셀 케이스)\n for (const c of cells) {\n if (c.rowSpan === 1 && c.contentH != null && c.rowAddr >= 0 && c.rowAddr < rowCount) {\n if (c.contentH > h[c.rowAddr]) h[c.rowAddr] = c.contentH\n }\n }\n return h\n}\n\n// 자연폭 계산은 hwpx/text-metrics.ts의 함초롬바탕 실측 advance 테이블(measureTextWidth)을 재사용한다.\n","/**\n * 한글 조판 텍스트 폭 계산 — 함초롬바탕(HCR Batang) 실측 advance 테이블\n *\n * 공문서 표준 본문 글꼴인 함초롬바탕 정품 TTF(한컴 공개 배포)의 hmtx advance를\n * upem=1000 기준으로 추출한 값이다. Bold도 advance가 Regular와 완전히 동일함을\n * 전수 확인했다(한컴이 굵기 간 폭을 통일해 제작). 한글 음절 11,172자 전수 = 970,\n * 한자 = 1000, 전각형·원문자·도형·화살표·단위기호 = 970으로 균일하다.\n *\n * 단위: em×1000. 실제 폭(HWPUNIT) = w/1000 × charPr.height × ratio(장평)/100.\n * (1em = 글자크기 = charPr.height HWPUNIT — 1pt = 100 HWPUNIT이므로)\n *\n * 공백: HWP는 charPr useFontSpace=0(기본)일 때 글꼴의 space advance(0.30em) 대신\n * 반각 고정폭(0.50em)을 쓴다 — kordoc 생성 문서는 모두 useFontSpace=0이므로\n * 기본 SPACE_EM=500. (글꼴값을 쓰려면 measure 옵션 fontSpace로 300 지정)\n *\n * 다른 글꼴(맑은 고딕 등)도 한글=전각 균일·숫자≈0.55em의 동일 부류 구조라\n * 이 테이블로 근사한다(오차 수 % 이내 — 공문서 본문은 어차피 함초롬바탕 관행).\n */\n\n/** ASCII 0x20~0x7E advance (em×1000). 0x20은 useFontSpace=1일 때의 글꼴값(300) */\nconst ASCII_W = [\n 300, 320, 320, 610, 610, 830, 724, 320, 320, 320, 550, 550, 320, 550, 320, 550, // 0x20-0x2F\n 550, 550, 550, 550, 550, 550, 550, 550, 550, 550, 320, 320, 550, 550, 550, 550, // 0x30-0x3F\n 830, 706, 605, 685, 719, 627, 617, 683, 734, 305, 315, 660, 605, 839, 734, 732, // 0x40-0x4F\n 603, 705, 660, 627, 664, 731, 706, 910, 705, 705, 626, 320, 550, 320, 550, 550, // 0x50-0x5F\n 320, 569, 597, 552, 597, 536, 356, 562, 635, 287, 288, 582, 287, 907, 635, 588, // 0x60-0x6F\n 597, 579, 478, 496, 356, 635, 563, 720, 542, 543, 486, 320, 320, 320, 550, 0, // 0x70-0x7E(+DEL)\n]\n\n/** 개별 실측 예외 기호 (em×1000) */\nconst SYM_W: Record<number, number> = {\n 0xa0: 300, 0xa3: 568, 0xa5: 707, 0xa7: 498, 0xab: 440, 0xac: 564, 0xb0: 291,\n 0xb1: 798, 0xb6: 606, 0xb7: 320, 0xbb: 440, 0xd7: 617, 0xf7: 678,\n 0x2013: 625, 0x2014: 875, 0x2015: 875, 0x2018: 320, 0x2019: 320, 0x201c: 480, 0x201d: 480,\n 0x2020: 558, 0x2021: 438, 0x2025: 640, 0x2026: 960, 0x2030: 988, 0x2032: 335, 0x2033: 474,\n 0x203b: 770, 0x20ac: 656, 0x261c: 1012, 0x261e: 1012,\n}\n\n/** 코드포인트의 advance(em×1000). 미상 문자는 CJK권 970 / 라틴권 550 폴백 */\nexport function charWidthEm1000(cp: number): number {\n if (cp >= 0x20 && cp <= 0x7e) return ASCII_W[cp - 0x20]\n const sym = SYM_W[cp]\n if (sym !== undefined) return sym\n if (cp >= 0xac00 && cp <= 0xd7a3) return 970 // 한글 음절 (전수 균일 확인)\n if (cp >= 0x1100 && cp <= 0x11ff) return 970 // 옛한글 자모\n if (cp >= 0x3131 && cp <= 0x318e) return 970 // 호환 자모 (ㆍ 포함)\n if ((cp >= 0x4e00 && cp <= 0x9fff) || (cp >= 0xf900 && cp <= 0xfaff)) return 1000 // 한자\n if ((cp >= 0x3008 && cp <= 0x3011) || (cp >= 0x3014 && cp <= 0x301b)) return 500 // 「」『』〈〉《》〔〕【】\n if (cp === 0x3000) return 970 // 전각 공백\n if (cp >= 0x2160 && cp <= 0x2183) return 970 // 로마숫자 Ⅰ~Ⅻ (한글 글꼴 전각)\n if (cp >= 0x2190 && cp <= 0x22ff) return 970 // 화살표·수학 기호\n if (cp >= 0x2460 && cp <= 0x24ff) return 970 // 원문자 ①⑴\n if (cp >= 0x25a0 && cp <= 0x26ff) return 970 // 도형 □○◆★\n if (cp >= 0x3200 && cp <= 0x33ff) return 970 // 괄호한글 ㉮·단위 ㎡㎏\n if (cp >= 0xff01 && cp <= 0xff60) return 970 // 전각형 !~\n return cp >= 0x2e80 ? 970 : 550\n}\n\n/** HWP 공백 폭(em×1000) — useFontSpace=0(기본): 반각 고정 500 / =1: 글꼴값 300 */\nexport const SPACE_EM_FIXED = 500\nexport const SPACE_EM_FONT = 300\n\n/**\n * 폭 테이블 클래스 — 'hcr'(함초롬 실측, 기본) / 'fixedPitch'(고정폭 글꼴:\n * ASCII 0.5em·그 외 1.0em). 굴림체·돋움체·바탕체·궁서체('체' 접미 = 고정폭)가\n * 대상 — 전자결재 변환기 산출물의 지배 본문 글꼴. 한글이 HCR(0.97em)보다 3%\n * 넓어 함초롬 테이블로 재면 줄당 1~2자 과대적재로 wrap 지점이 어긋난다\n * (bench/verify-linebreak.mjs seoul 코퍼스 실측: fixedPitch 테이블로 74/75 일치).\n */\nexport type FaceClass = \"hcr\" | \"fixedPitch\"\n\n/** HWP 글꼴명 → 폭 테이블 클래스. 미상/미지정은 hcr (기존 동작 불변) */\nexport function faceClassOf(face: string | null | undefined): FaceClass {\n return face && /^(굴림체|돋움체|바탕체|궁서체)$/.test(face.trim()) ? \"fixedPitch\" : \"hcr\"\n}\n\n/** 고정폭 글꼴 advance(em×1000) — ASCII 반각 500, 그 외 전각 1000 */\nfunction fixedPitchWidthEm1000(cp: number): number {\n return cp < 0x80 ? 500 : 1000\n}\n\nexport interface MeasureOptions {\n /** 공백 폭(em×1000). 기본 SPACE_EM_FIXED(500) = useFontSpace 0 */\n spaceEm?: number\n /** 자간(charPr spacing %) — 글자폭의 %가 문자마다 추가 */\n spacingPct?: number\n /** 폭 테이블 클래스 — 기본 'hcr' (함초롬 실측 테이블) */\n faceClass?: FaceClass\n}\n\n/**\n * 텍스트 폭(HWPUNIT). height=charPr height(pt×100), ratioPct=장평 %.\n * 결과는 float — 호출부에서 비교 시 ±0.5 HWPUNIT 오차 허용 권장.\n */\nexport function measureTextWidth(\n text: string,\n height: number,\n ratioPct: number,\n opts?: MeasureOptions,\n): number {\n const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED\n const spacing = opts?.spacingPct ?? 0\n const widthEm = opts?.faceClass === \"fixedPitch\" ? fixedPitchWidthEm1000 : charWidthEm1000\n let em = 0\n for (const ch of text) {\n const cp = ch.codePointAt(0)!\n const w = cp === 0x20 ? spaceEm : widthEm(cp)\n em += w * (1 + spacing / 100)\n }\n return (em / 1000) * height * (ratioPct / 100)\n}\n\n// ─── 줄바꿈 시뮬레이션 (한컴 조판 모델 — 실측 linesegarray 98% 일치 검증) ───\n//\n// 검증: bench/verify-linebreak.mjs — 서울 정보소통광장 실제 결재문서의 linesegarray\n// (한컴 계열 조판기가 계산한 줄 시작 오프셋)와 대조. 고정폭 글꼴 버킷에서 98%\n// (56/57 줄바꿈점, 미스는 코퍼스 좌표계 노이즈) 일치. 이 과정에서 확정된 규칙:\n// - 공백 폭 = 0.5em 고정(useFontSpace=0), 장평·자간 모두 공백에도 적용\n// - 자간 = 글자폭 × (1+spacing/100)\n// - 줄 끝 공백은 hang (줄바꿈을 유발하지 않음)\n// - 금칙: 시작금칙(닫는 부호 줄머리 금지)은 직전 글자 1개를 동반해 다음 줄로\n// (밀어내기), 끝금칙(여는 괄호 줄끝 금지)은 그 글자를 다음 줄로 내림\n\n/** 줄머리 금지(시작금칙) 문자 — 한컴 기본 */\nconst FORBID_START = new Set([...\"!%),.:;?]}¢°′″℃〉》」』】〕!%),.:;?]}₩~…·、。〃\"])\n/** 줄끝 금지(끝금칙) 문자 — 한컴 기본 */\nconst FORBID_END = new Set([...\"$([{£¥〈《「『【〔$([{₩\"])\n\nexport type WrapMode = \"keep\" | \"charAll\"\n\nexport interface WrapResult {\n /** 줄 수 */\n lines: number\n /** 각 줄의 시작 오프셋(UTF-16) — [0, …] */\n starts: number[]\n /** 마지막 줄 텍스트 폭(HWPUNIT) */\n lastLineWidth: number\n}\n\n/**\n * 문단 줄바꿈 시뮬레이션.\n * mode 'keep' = 어절 단위(공문서 모드 — 저장 속성 breakNonLatinWord=\"BREAK_WORD\", 이름 역전 주의),\n * mode 'charAll' = 글자 단위(breakNonLatinWord=\"KEEP_WORD\" — 한글 기본값·전자결재 변환기).\n * 한 어절이 줄보다 길면 keep에서도 글자 단위로 강제 분해(한컴 동일).\n *\n * @param text 문단 전체 텍스트(항목 부호 포함)\n * @param firstWidth 첫 줄 가용 폭(HWPUNIT)\n * @param contWidth 둘째 줄부터 가용 폭(HWPUNIT) — 내어쓰기 반영\n * @param height charPr height (pt×100)\n * @param ratioPct 장평 %\n */\nexport function simulateWrap(\n text: string,\n firstWidth: number,\n contWidth: number,\n height: number,\n ratioPct: number,\n mode: WrapMode = \"keep\",\n opts?: MeasureOptions,\n): WrapResult {\n const EPS = 0.5\n const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED\n const spacing = opts?.spacingPct ?? 0\n const widthEm = opts?.faceClass === \"fixedPitch\" ? fixedPitchWidthEm1000 : charWidthEm1000\n const k = (height * ratioPct) / 100 / 1000\n const cwCp = (cp: number): number =>\n (cp === 0x20 ? spaceEm : widthEm(cp)) * (1 + spacing / 100) * k\n const charW = (ch: string): number => cwCp(ch.codePointAt(0)!)\n const rangeW = (from: number, to: number): number => {\n let w = 0\n for (const ch of text.slice(from, to)) w += charW(ch)\n return w\n }\n\n const units = text.match(mode === \"keep\" ? / +|[^ ]+/g : / +|[^ ]/g) ?? []\n\n const starts = [0]\n let lineW = 0\n let avail = firstWidth\n let pos = 0\n const lineStart = (): number => starts[starts.length - 1]\n\n /** 유닛(시작 unitPos, 폭 w)이 안 들어갈 때 줄바꿈 + 금칙 보정 */\n const breakBefore = (unitPos: number, w: number): void => {\n let bp = unitPos\n const u = text[unitPos]\n // 시작금칙: 줄머리 금지 문자면 직전 글자 1개를 함께 내린다 (밀어내기)\n if (u !== undefined && FORBID_START.has(u) && bp - 1 > lineStart() && text[bp - 1] !== \" \") bp--\n // 끝금칙: 남는 줄 끝이 여는 괄호류면 그 글자(들)도 함께 내린다\n while (bp - 1 > lineStart() && FORBID_END.has(text[bp - 1])) bp--\n if (bp <= lineStart()) bp = unitPos\n starts.push(bp)\n avail = contWidth\n lineW = rangeW(bp, unitPos) + w\n }\n\n for (const u of units) {\n if (u[0] === \" \") {\n lineW += charW(\" \") * u.length // 줄 끝 공백은 hang\n pos += u.length\n continue\n }\n const w = rangeW(pos, pos + u.length)\n if (lineW + w <= avail + EPS) {\n lineW += w\n pos += u.length\n continue\n }\n if (lineW === 0 || w > contWidth + EPS) {\n // 빈 줄이거나 다음 줄에도 안 들어가는 초장 유닛 — 글자 단위 강제 분해\n let sub = 0\n for (const ch of u) {\n const c = charW(ch)\n if (lineW + c > avail + EPS && lineW > 0) breakBefore(pos + sub, 0)\n lineW += c\n sub += ch.length\n }\n pos += u.length\n continue\n }\n breakBefore(pos, w)\n pos += u.length\n }\n return { lines: starts.length, starts, lastLineWidth: lineW }\n}\n\n/** @deprecated simulateWrap(text, …, 'keep') 별칭 — 하위 호환 */\nexport function simulateWrapKeepWord(\n text: string,\n firstWidth: number,\n contWidth: number,\n height: number,\n ratioPct: number,\n opts?: MeasureOptions,\n): WrapResult {\n return simulateWrap(text, firstWidth, contWidth, height, ratioPct, \"keep\", opts)\n}\n\n/**\n * 한두 글자(짧은 꼬리)가 다음 줄로 넘어간 문단을 장평 축소로 한 줄 줄일 수 있는지\n * 탐색. baseRatio에서 줄 수 N≥2일 때 r=baseRatio-1…minRatio를 내려가며 처음으로\n * 줄 수가 줄어드는(가장 큰) r을 반환. 불가능하면 null.\n * (실무 관행: 공무원이 문단 장평을 95→92 등으로 줄여 orphan을 위로 당기는 조작의 자동화.\n * keep 모드로 판정 — 글자 단위 조판은 항상 keep 이하의 줄 수라 함께 만족된다.)\n */\nexport function fitRatioForFewerLines(\n text: string,\n firstWidth: number,\n contWidth: number,\n height: number,\n baseRatio: number,\n minRatio: number,\n opts?: MeasureOptions,\n): number | null {\n const base = simulateWrap(text, firstWidth, contWidth, height, baseRatio, \"keep\", opts)\n if (base.lines < 2) return null\n for (let r = baseRatio - 1; r >= minRatio; r--) {\n const sim = simulateWrap(text, firstWidth, contWidth, height, r, \"keep\", opts)\n if (sim.lines < base.lines) return r\n }\n return null\n}\n","/**\n * Tier-2 reflow — 조판 캐시(linesegarray)가 없는 문단에 좌표를 합성 주입한다.\n *\n * 한컴은 저장 시 각 줄의 좌표(vertpos/horzpos/…)를 linesegarray로 캐시한다.\n * markdownToHwpx 산출물·에이전트 생성본·편집본엔 이 캐시가 없어 svg-render가\n * KordocError로 거부한다. reflow는 `simulateWrap`(수평, 실측 98% 일치)과 세로 모델\n * (실측 역설계 — `.claude/plans/render-poc/findings.md`)로 lineseg를 계산해 DOM\n * `<hp:p>`에 `<hp:linesegarray>`를 append → 그 뒤는 기존 렌더 파이프가 그대로 소비한다.\n *\n * 세로 모델(HWPUNIT): textheight = 줄 지배 charPr.height, baseline = round(0.85×th),\n * 줄 pitch = round(th × lineSpacing%/100), spacing(leading) = pitch − th.\n * 문단 세로 흐름 = 본문영역 로컬 누적(다음문단 = 이전끝 + pitch + next + prev).\n *\n * 원칙: 한컴본 캐시는 절대 건드리지 않는다(Tier-1 무회귀). segs가 있는 문단은 건너뛴다.\n * Phase 3(v4.0.4): 개체 세로 흐름 — float(TOP_AND_BOTTOM)는 텍스트를 outMargin 포함\n * 개체 아래로 밀고, PAGE/PAPER 앵커·BEHIND/IN_FRONT는 흐름 불참, inline 표는 실효높이\n * +leading 전진. 혼합 캐시 문서는 캐시 문단의 한컴 좌표로 커서를 이어 받는다\n * (seoul 코퍼스 자기일관성 59/59 — 36264961 전 문단 d=0 실측).\n */\n\nimport { buildPara, measureTableHeight } from \"./svg-render.js\"\nimport { simulateWrap, faceClassOf, type WrapMode } from \"../hwpx/text-metrics.js\"\nimport { DEFAULT_CHAR, DEFAULT_PARA_GEOM, type RenderStyles, type RenderParaGeom } from \"./head-styles.js\"\nimport { findChildByLocalName } from \"../hwpx/parser-shared.js\"\nimport { toInt32 } from \"./layout.js\"\n\n/** lineseg flags 고정값 (한컴 저장본 실측 — 0x60000) */\nconst LINESEG_FLAGS = \"393216\"\n/** baseline / textheight 비율 (실측 94/94 일치) */\nconst BASELINE_RATIO = 0.85\n\nfunction ln(el: Element): string {\n return (el.tagName || \"\").replace(/^[^:]+:/, \"\")\n}\n\nfunction elements(el: Element): Element[] {\n const out: Element[] = []\n const kids = el.childNodes\n if (!kids) return out\n for (let i = 0; i < kids.length; i++) if (kids[i].nodeType === 1) out.push(kids[i] as Element)\n return out\n}\n\nfunction num(el: Element | null, attr: string, fallback = 0): number {\n return el ? toInt32(el.getAttribute(attr) ?? undefined, fallback) : fallback\n}\n\n/** 문단의 합성 linesegarray 줄들의 vertpos를 delta만큼 이동 (페이지 로컬 리셋용) */\nfunction shiftParaVert(p: Element, delta: number): void {\n for (const lsa of elements(p)) {\n if (ln(lsa) !== \"linesegarray\") continue\n for (const seg of elements(lsa)) {\n if (ln(seg) !== \"lineseg\") continue\n seg.setAttribute(\"vertpos\", String(num(seg, \"vertpos\") + delta))\n }\n }\n}\n\nexport interface ReflowGeom {\n BODY_W: number\n BODY_H: number\n}\n\n/** 줄 pitch(다음 줄 vertpos 증분, HWPUNIT) — lineSpacing type별 */\nfunction pitchFor(height: number, geom: RenderParaGeom): number {\n const v = geom.lineSpacingValue\n switch (geom.lineSpacingType) {\n case \"PERCENT\": return Math.round((height * v) / 100)\n case \"FIXED\": return v > 0 ? v : Math.round(height * 1.6) // 고정 줄높이(HWPUNIT)\n case \"AT_LEAST\": return Math.max(v, height)\n default: return Math.round(height * 1.6) // BETWEEN_LINES 등 — 기본 160% 근사\n }\n}\n\n/**\n * 문단 하나의 linesegarray를 합성해 p에 삽입.\n * @returns 세로 흐름 갱신값 (캐시 있어 건너뛴 경우 null)\n */\nfunction reflowPara(\n p: Element,\n doc: Document,\n styles: RenderStyles,\n areaW: number,\n startV: number,\n mode: WrapMode,\n): { paraBottom: number; spaceAfter: number } | null {\n const m = buildPara(p)\n if (m.segs.length > 0) return null // 이미 캐시 있음 — Tier-1 무회귀\n\n // 실텍스트 + UTF-16 유닛 → chars 슬롯 인덱스 매핑 (서로게이트 쌍은 슬롯 1개, 유닛 2개)\n const realIdx: number[] = []\n let text = \"\"\n for (let i = 0; i < m.chars.length; i++) {\n const ch = m.chars[i].ch\n if (ch === \"\") continue\n for (let u = 0; u < ch.length; u++) realIdx.push(i)\n text += ch\n }\n\n const geom = styles.paraGeom.get(m.paraPrId ?? \"\") ?? DEFAULT_PARA_GEOM\n // 문단 지배 charPr — 첫 실문자 우선(height/장평/자간)\n let domChar = DEFAULT_CHAR\n for (const c of m.chars) {\n if (c.ch !== \"\" && c.prId != null) {\n const st = styles.charPr.get(c.prId)\n if (st) { domChar = st; break }\n }\n }\n // 실문자가 없는 문단(빈 줄·개체 전용)도 run의 charPrIDRef가 줄높이를 지배한다 —\n // DEFAULT_CHAR(1000)로 재면 빈 문단 pitch가 본문(예: 1300→2080)보다 작아 이후\n // 문단 전체가 위로 밀린다 (seoul 코퍼스 36264961 실측 −480×빈줄 누적)\n if (domChar === DEFAULT_CHAR) {\n for (const run of elements(p)) {\n if (ln(run) !== \"run\") continue\n const st = styles.charPr.get(run.getAttribute(\"charPrIDRef\") ?? \"\")\n if (st) { domChar = st; break }\n }\n }\n const height = domChar.height || 1000\n const ratio = domChar.ratio || 100\n const spacingPct = domChar.spacing || 0\n\n const marginL = geom.marginLeft\n const avail = Math.max(1000, areaW - marginL - geom.marginRight)\n // hanging(intent<0): 둘째 줄부터 |intent| 더 들어감 → contWidth 축소·contHorz 우측 이동\n const firstWidth = avail\n const contWidth = Math.max(500, avail + Math.min(0, geom.marginIntent))\n const contHorz = marginL - Math.min(0, geom.marginIntent)\n\n // 문단 paraPr의 breakSetting이 있으면 그 선언(어절/글자)을 따르고, 없으면 호출자 모드\n const paraMode = geom.wrapMode ?? mode\n // 폭 테이블: 지배 charPr 글꼴이 고정폭(굴림체류)이면 전용 클래스 — 함초롬 테이블(한글\n // 0.97em)로 재면 줄당 1~2자 과대적재로 wrap이 어긋난다 (seoul 코퍼스 3건 실측)\n const faceClass = faceClassOf(domChar.face)\n const wrap = text.length === 0\n ? { lines: 1, starts: [0], lastLineWidth: 0 }\n : simulateWrap(text, firstWidth, contWidth, height, ratio, paraMode, { spacingPct, faceClass })\n\n const pitch = pitchFor(height, geom)\n const baseline = Math.round(height * BASELINE_RATIO)\n const spacing = Math.max(0, pitch - height)\n\n // ── 개체(표·이미지) 세로 흐름 분류 (Phase 3 — 한컴 실측 모델, 36264961) ──\n // · float(treatAsChar=0) + TOP_AND_BOTTOM + PARA 앵커: 개체가 문단 상단을 차지하고\n // 텍스트는 outTop+개체높이+outBottom 아래부터 흐른다 (두문 결재표 실측:\n // 140+16653+852=17645 정확 일치)\n // · BEHIND_TEXT/IN_FRONT_OF_TEXT 또는 PAGE/PAPER 앵커: 본문 흐름에 불참\n // (페이지 하단 직인 스탬프가 커서를 밀던 것 제거)\n // · inline(treatAsChar=1): 줄 하나로 취급 — outMargin 포함 높이 + 줄 leading(spacing)\n // 만큼 전진 (실측: 표끝→다음 문단 간격 = 성장 포함 표높이 + spacing)\n let floatBelow = 0 // 텍스트 시작을 아래로 미는 float 개체 바닥 (문단 로컬)\n let objBottom = startV\n for (const o of m.objs) {\n const effH = o.tag === \"tbl\" ? Math.max(o.height, measureTableHeight(o.el)) : o.height\n const pos = findChildByLocalName(o.el, \"pos\")\n const om = findChildByLocalName(o.el, \"outMargin\")\n const outT = num(om, \"top\"), outB = num(om, \"bottom\")\n if (o.inline) {\n objBottom = Math.max(objBottom, startV + outT + effH + outB + spacing)\n continue\n }\n const wrapAttr = o.el.getAttribute(\"textWrap\") ?? \"\"\n if (wrapAttr === \"BEHIND_TEXT\" || wrapAttr === \"IN_FRONT_OF_TEXT\") continue\n const vertRel = pos?.getAttribute(\"vertRelTo\") ?? \"PARA\"\n if (vertRel === \"PAGE\" || vertRel === \"PAPER\") continue\n const vo = Math.max(0, num(pos, \"vertOffset\"))\n if (wrapAttr === \"TOP_AND_BOTTOM\") {\n floatBelow = Math.max(floatBelow, vo + outT + effH + outB)\n } else {\n objBottom = Math.max(objBottom, startV + vo + outT + effH + outB)\n }\n }\n const textStartV = startV + floatBelow\n\n const lsa = doc.createElement(\"hp:linesegarray\")\n for (let li = 0; li < wrap.starts.length; li++) {\n const startReal = wrap.starts[li]\n // 실텍스트 없는 문단(인라인 표·개체만)의 textpos는 0 — chars.length로 폴백하면\n // planLines의 plan.start가 개체 index보다 커져 advanceTo 가로 전진에서 개체가\n // 전부 빠지고 같은 x에 겹쳐 그려진다 (공문 결재란 라벨표∩스탬프표 겹침의 원인)\n const textpos = startReal < realIdx.length ? realIdx[startReal] : 0\n const vertpos = textStartV + li * pitch\n const isFirst = li === 0\n const seg = doc.createElement(\"hp:lineseg\")\n seg.setAttribute(\"textpos\", String(textpos))\n seg.setAttribute(\"vertpos\", String(vertpos))\n seg.setAttribute(\"vertsize\", String(height))\n seg.setAttribute(\"textheight\", String(height))\n seg.setAttribute(\"baseline\", String(baseline))\n seg.setAttribute(\"spacing\", String(spacing))\n seg.setAttribute(\"horzpos\", String(isFirst ? marginL : contHorz))\n seg.setAttribute(\"horzsize\", String(isFirst ? firstWidth : contWidth))\n seg.setAttribute(\"flags\", LINESEG_FLAGS)\n lsa.appendChild(seg)\n }\n p.appendChild(lsa)\n\n // 문단 바닥 = 텍스트 줄 흐름(float 개체 아래부터)과 개체 바닥 중 큰 쪽.\n // 표는 셀 콘텐츠 성장으로 선언 sz보다 커질 수 있어 drawTable과 같은 실효 높이를 쓴다\n // (셀 lineseg 필요 — reflowBlockFlow가 reflowTablesIn을 먼저 돌린다).\n const textBottom = textStartV + wrap.starts.length * pitch\n return { paraBottom: Math.max(textBottom, objBottom), spaceAfter: geom.spaceAfter }\n}\n\n/** 문단 run 안의 표를 찾아 각 셀 subList를 셀 로컬로 reflow (중첩 재귀) */\nfunction reflowTablesIn(p: Element, doc: Document, styles: RenderStyles, mode: WrapMode, counter: { n: number }): void {\n for (const run of elements(p)) {\n if (ln(run) !== \"run\") continue\n for (const obj of elements(run)) {\n if (ln(obj) !== \"tbl\") continue\n for (const tr of elements(obj)) {\n if (ln(tr) !== \"tr\") continue\n for (const tc of elements(tr)) {\n if (ln(tc) !== \"tc\") continue\n const csz = findChildByLocalName(tc, \"cellSz\")\n const cm = findChildByLocalName(tc, \"cellMargin\")\n const cellW = num(csz, \"width\")\n const mL = cm ? num(cm, \"left\", 141) : 141\n const mR = cm ? num(cm, \"right\", 141) : 141\n const areaW = Math.max(500, cellW - mL - mR)\n const sub = findChildByLocalName(tc, \"subList\")\n if (sub) reflowBlockFlow(sub, doc, styles, areaW, mode, counter, 0)\n }\n }\n }\n }\n}\n\n/**\n * 한 블록 컨테이너(본문 root 또는 셀 subList) 안 문단들을 세로 흐름으로 reflow.\n * @param bodyH 최상위(본문)일 때만 >0 — 문단 단위 자동 페이지 나눔(vertpos 페이지 로컬 리셋).\n * 셀 subList는 0(페이지 나눔 없음).\n */\nfunction reflowBlockFlow(\n container: Element,\n doc: Document,\n styles: RenderStyles,\n areaW: number,\n mode: WrapMode,\n counter: { n: number },\n bodyH: number,\n): void {\n let cursorV = 0\n let prevSpaceAfter = 0\n for (const p of elements(container)) {\n if (ln(p) !== \"p\") continue\n // 문단 안 표 셀을 먼저 셀 로컬 좌표로 reflow (본문 세로 흐름과 무관) —\n // reflowPara의 표 실효높이 측정이 셀 lineseg를 읽으므로 호스트 문단보다 앞서야 한다\n reflowTablesIn(p, doc, styles, mode, counter)\n const g = styles.paraGeom.get(p.getAttribute(\"paraPrIDRef\") ?? \"\")\n const startV = cursorV + prevSpaceAfter + (g?.spaceBefore ?? 0)\n const res = reflowPara(p, doc, styles, areaW, startV, mode)\n if (!res) {\n // 캐시 보유 문단(Tier-1) — 한컴 실좌표로 커서를 전진시킨다. 혼합 캐시 문서\n // (한컴 저장본을 프로그램 편집해 일부 문단만 캐시가 없는 경우)에서 캐시 없는\n // 문단이 흐름 위치 대신 0에 겹쳐 그려지던 결함 수정 (36264961 실측: 캐시 문단\n // p18 57165+1300+780 → 캐시 없는 p19가 정확히 59245에 붙는다).\n const lsa = findChildByLocalName(p, \"linesegarray\")\n if (lsa) {\n let bottom = Number.NEGATIVE_INFINITY\n for (const seg of elements(lsa)) {\n if (ln(seg) !== \"lineseg\") continue\n const th = Math.max(num(seg, \"vertsize\", 1000), num(seg, \"textheight\", 1000))\n bottom = Math.max(bottom, num(seg, \"vertpos\") + th + num(seg, \"spacing\"))\n }\n if (Number.isFinite(bottom)) {\n cursorV = bottom\n prevSpaceAfter = g?.spaceAfter ?? 0\n }\n }\n continue\n }\n const paraH = res.paraBottom - startV\n // 페이지 넘김: 문단이 현재 페이지를 넘치고, 문단 자체는 한 페이지에 들어가면 다음 페이지로.\n if (bodyH > 0 && startV > 0 && res.paraBottom > bodyH && paraH <= bodyH) {\n shiftParaVert(p, -startV) // 새 페이지 상단(로컬 0)으로 이동 → 프리패스가 vertpos 역행 감지\n cursorV = paraH\n } else {\n cursorV = res.paraBottom\n }\n prevSpaceAfter = res.spaceAfter\n counter.n++\n }\n}\n\n/**\n * section root의 조판 캐시 없는 문단에 linesegarray를 합성 주입한다(표 셀 재귀 포함).\n * 반환: 합성한 문단 수.\n * (Phase 2~3: 최상위 텍스트 + 표 셀 내부. 개체 밀어내기·자동 페이지 분할은 후속.)\n */\nexport function reflowSection(\n root: Element,\n styles: RenderStyles,\n geom: ReflowGeom,\n mode: WrapMode = \"keep\",\n): number {\n const doc = root.ownerDocument as unknown as Document\n const counter = { n: 0 }\n reflowBlockFlow(root, doc, styles, geom.BODY_W, mode, counter, geom.BODY_H)\n return counter.n\n}\n"],"mappings":"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","names":["charW","ln","elements","num"]}