kordoc 4.2.8 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +922 -885
  2. package/dist/{-SCTT2TBZ.js → -DYHT3HWH.js} +28 -20
  3. package/dist/{chunk-J2RZ444H.js → chunk-3FFPHTXD.js} +1 -1
  4. package/dist/chunk-3FFPHTXD.js.map +1 -0
  5. package/dist/{chunk-IJUAWDOS.js → chunk-4DBCUF3C.js} +38 -7
  6. package/dist/chunk-4DBCUF3C.js.map +1 -0
  7. package/dist/{chunk-5RPYBC2Q.js → chunk-6GBLFQMA.js} +1 -1
  8. package/dist/chunk-6GBLFQMA.js.map +1 -0
  9. package/dist/{chunk-JP5MNHUK.js → chunk-6RAYTNQT.js} +5 -5
  10. package/dist/chunk-6RAYTNQT.js.map +1 -0
  11. package/dist/{chunk-GEPINOL6.js → chunk-6XPXIG57.js} +2 -2
  12. package/dist/chunk-6XPXIG57.js.map +1 -0
  13. package/dist/{chunk-WP4VD5T7.js → chunk-7MFO4TKP.js} +4 -3
  14. package/dist/chunk-7MFO4TKP.js.map +1 -0
  15. package/dist/{chunk-CELJIGBQ.cjs → chunk-AL34DGS5.cjs} +6 -6
  16. package/dist/chunk-AL34DGS5.cjs.map +1 -0
  17. package/dist/{chunk-FS3XQ3ZQ.js → chunk-ARXNKC5D.js} +3 -3
  18. package/dist/chunk-ARXNKC5D.js.map +1 -0
  19. package/dist/{chunk-2BTKLDNX.cjs → chunk-AUOEZKJE.cjs} +21 -21
  20. package/dist/chunk-AUOEZKJE.cjs.map +1 -0
  21. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  22. package/dist/{chunk-7GEBDS6B.js → chunk-DRQUAYNT.js} +4 -3
  23. package/dist/chunk-DRQUAYNT.js.map +1 -0
  24. package/dist/{chunk-YNAPK4H6.js → chunk-DRZUJ2RK.js} +1521 -1144
  25. package/dist/chunk-DRZUJ2RK.js.map +1 -0
  26. package/dist/{chunk-GKSFNCVP.js → chunk-EFVAY5VE.js} +71 -29
  27. package/dist/chunk-EFVAY5VE.js.map +1 -0
  28. package/dist/{chunk-UKEZ46VL.js → chunk-EZHW3RNQ.js} +37 -6
  29. package/dist/chunk-EZHW3RNQ.js.map +1 -0
  30. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  31. package/dist/{chunk-4FTXKIGT.js → chunk-GSMPTZC7.js} +4 -4
  32. package/dist/chunk-GSMPTZC7.js.map +1 -0
  33. package/dist/{chunk-OQNFGKRY.cjs → chunk-JFV7QT5T.cjs} +4 -3
  34. package/dist/chunk-JFV7QT5T.cjs.map +1 -0
  35. package/dist/{chunk-ROKHZE76.cjs → chunk-LPQWKQQQ.cjs} +44 -13
  36. package/dist/chunk-LPQWKQQQ.cjs.map +1 -0
  37. package/dist/{chunk-H7OWEYH2.cjs → chunk-MGGU3ZRK.cjs} +1 -1
  38. package/dist/chunk-MGGU3ZRK.cjs.map +1 -0
  39. package/dist/chunk-MOL7MDBG.js +0 -0
  40. package/dist/{chunk-55QIELGV.js → chunk-NMWXBJ5B.js} +4 -3
  41. package/dist/chunk-NMWXBJ5B.js.map +1 -0
  42. package/dist/{chunk-DZIXKL7E.js → chunk-O5EK52TG.js} +1 -1
  43. package/dist/chunk-O5EK52TG.js.map +1 -0
  44. package/dist/{chunk-COHHXQSP.js → chunk-SPKBIFSK.js} +2 -2
  45. package/dist/chunk-SPKBIFSK.js.map +1 -0
  46. package/dist/{chunk-QZCAIFUW.js → chunk-US4WFSIQ.js} +4 -4
  47. package/dist/chunk-US4WFSIQ.js.map +1 -0
  48. package/dist/{chunk-EOVJ27WC.js → chunk-VXMLENCV.js} +15 -11
  49. package/dist/chunk-VXMLENCV.js.map +1 -0
  50. package/dist/{chunk-7FCNOKOR.js → chunk-W34QSJFF.js} +1 -1
  51. package/dist/chunk-W34QSJFF.js.map +1 -0
  52. package/dist/{chunk-URKX77GB.js → chunk-WFOQFJI4.js} +2 -2
  53. package/dist/chunk-WFOQFJI4.js.map +1 -0
  54. package/dist/{chunk-NNDVFNMJ.js → chunk-YNJDNP44.js} +1 -1
  55. package/dist/chunk-YNJDNP44.js.map +1 -0
  56. package/dist/{chunk-LM7C6HI2.js → chunk-ZSL6MQVE.js} +1 -1
  57. package/dist/chunk-ZSL6MQVE.js.map +1 -0
  58. package/dist/chunks-Q7T4SQGW.js +10 -0
  59. package/dist/cli.js +75 -39
  60. package/dist/cli.js.map +1 -1
  61. package/dist/{formula-SXC6LRRC.js → formula-7ZPC54JO.js} +2 -2
  62. package/dist/formula-7ZPC54JO.js.map +1 -0
  63. package/dist/{formula-KSNAEBZ2.js → formula-EQRHSMYW.js} +2 -2
  64. package/dist/formula-EQRHSMYW.js.map +1 -0
  65. package/dist/{formula-SB3NHDUM.cjs → formula-IWV5IJ3Y.cjs} +8 -8
  66. package/dist/formula-IWV5IJ3Y.cjs.map +1 -0
  67. package/dist/{image-ocr-ANSXR5KJ.js → image-ocr-G2LYTEUH.js} +8 -6
  68. package/dist/image-ocr-G2LYTEUH.js.map +1 -0
  69. package/dist/{image-ocr-ORATZW73.js → image-ocr-R44RYDBU.js} +10 -8
  70. package/dist/image-ocr-R44RYDBU.js.map +1 -0
  71. package/dist/{image-ocr-GHQ6YKJH.cjs → image-ocr-XEJFXH6M.cjs} +13 -11
  72. package/dist/image-ocr-XEJFXH6M.cjs.map +1 -0
  73. package/dist/index.cjs +2017 -1868
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.cts +75 -4
  76. package/dist/index.d.ts +75 -4
  77. package/dist/index.js +2139 -1990
  78. package/dist/index.js.map +1 -1
  79. package/dist/mcp.js +123 -78
  80. package/dist/mcp.js.map +1 -1
  81. package/dist/{models-KA4GLQK5.js → models-6VMAVZR6.js} +3 -3
  82. package/dist/page-range-737B4EZW.js +0 -0
  83. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  84. package/dist/{parser-BPVTYM5K.js → parser-2G245MMN.js} +6 -6
  85. package/dist/parser-2G245MMN.js.map +1 -0
  86. package/dist/{parser-GZDOZCHM.js → parser-W3POOYQQ.js} +8 -8
  87. package/dist/parser-W3POOYQQ.js.map +1 -0
  88. package/dist/{parser-KTNIUF2N.cjs → parser-W3ZGQUCQ.cjs} +25 -25
  89. package/dist/parser-W3ZGQUCQ.cjs.map +1 -0
  90. package/dist/pdf-ocr-3GDTJSHF.js +15 -0
  91. package/dist/pdf-ocr-N7IU23UV.cjs +13 -0
  92. package/dist/pdf-ocr-N7IU23UV.cjs.map +1 -0
  93. package/dist/pdf-ocr-U7MEGNBZ.js +12 -0
  94. package/dist/{profile-io-I2TH44EO.js → profile-io-72R3B7KO.js} +3 -3
  95. package/dist/{rasterize-QBA43YAQ.js → rasterize-GNJQVURE.js} +2 -2
  96. package/dist/rasterize-GNJQVURE.js.map +1 -0
  97. package/dist/{redact-7ILEAUTS.js → redact-W5K3ILQH.js} +2 -2
  98. package/dist/render-6OW6U4L4.js +10 -0
  99. package/dist/seal-GTNLVBJX.js +10 -0
  100. package/dist/{setup-GUQT5ELJ.js → setup-I4I75QJU.js} +1 -1
  101. package/dist/setup-I4I75QJU.js.map +1 -0
  102. package/dist/{watch-7V6X7LHV.js → watch-2ADAJLIJ.js} +12 -14
  103. package/dist/watch-2ADAJLIJ.js.map +1 -0
  104. package/package.json +101 -98
  105. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  106. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +9 -0
  107. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  108. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +25 -0
  109. package/dist/chunk-2BTKLDNX.cjs.map +0 -1
  110. package/dist/chunk-4FTXKIGT.js.map +0 -1
  111. package/dist/chunk-55QIELGV.js.map +0 -1
  112. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  113. package/dist/chunk-7FCNOKOR.js.map +0 -1
  114. package/dist/chunk-7GEBDS6B.js.map +0 -1
  115. package/dist/chunk-CELJIGBQ.cjs.map +0 -1
  116. package/dist/chunk-COHHXQSP.js.map +0 -1
  117. package/dist/chunk-DZIXKL7E.js.map +0 -1
  118. package/dist/chunk-EOVJ27WC.js.map +0 -1
  119. package/dist/chunk-FS3XQ3ZQ.js.map +0 -1
  120. package/dist/chunk-GEPINOL6.js.map +0 -1
  121. package/dist/chunk-GKSFNCVP.js.map +0 -1
  122. package/dist/chunk-H7OWEYH2.cjs.map +0 -1
  123. package/dist/chunk-IJUAWDOS.js.map +0 -1
  124. package/dist/chunk-J2RZ444H.js.map +0 -1
  125. package/dist/chunk-JP5MNHUK.js.map +0 -1
  126. package/dist/chunk-LM7C6HI2.js.map +0 -1
  127. package/dist/chunk-NNDVFNMJ.js.map +0 -1
  128. package/dist/chunk-OQNFGKRY.cjs.map +0 -1
  129. package/dist/chunk-QZCAIFUW.js.map +0 -1
  130. package/dist/chunk-ROKHZE76.cjs.map +0 -1
  131. package/dist/chunk-TCJRGFYY.js +0 -280
  132. package/dist/chunk-TCJRGFYY.js.map +0 -1
  133. package/dist/chunk-UKEZ46VL.js.map +0 -1
  134. package/dist/chunk-URKX77GB.js.map +0 -1
  135. package/dist/chunk-WP4VD5T7.js.map +0 -1
  136. package/dist/chunk-YNAPK4H6.js.map +0 -1
  137. package/dist/chunks-KKYJDJT7.js +0 -10
  138. package/dist/detect-FX2ZHKZP.js +0 -26
  139. package/dist/formula-KSNAEBZ2.js.map +0 -1
  140. package/dist/formula-SB3NHDUM.cjs.map +0 -1
  141. package/dist/formula-SXC6LRRC.js.map +0 -1
  142. package/dist/image-ocr-ANSXR5KJ.js.map +0 -1
  143. package/dist/image-ocr-GHQ6YKJH.cjs.map +0 -1
  144. package/dist/image-ocr-ORATZW73.js.map +0 -1
  145. package/dist/parser-BPVTYM5K.js.map +0 -1
  146. package/dist/parser-GZDOZCHM.js.map +0 -1
  147. package/dist/parser-KTNIUF2N.cjs.map +0 -1
  148. package/dist/pdf-ocr-6XKLXZTF.cjs +0 -13
  149. package/dist/pdf-ocr-6XKLXZTF.cjs.map +0 -1
  150. package/dist/pdf-ocr-HWPNMKKC.js +0 -12
  151. package/dist/pdf-ocr-W2PA2K3U.js +0 -15
  152. package/dist/rasterize-QBA43YAQ.js.map +0 -1
  153. package/dist/render-MDJDKKNH.js +0 -10
  154. package/dist/seal-Z52JR2GX.js +0 -10
  155. package/dist/seal-Z52JR2GX.js.map +0 -1
  156. package/dist/setup-GUQT5ELJ.js.map +0 -1
  157. package/dist/watch-7V6X7LHV.js.map +0 -1
  158. /package/dist/{-SCTT2TBZ.js.map → -DYHT3HWH.js.map} +0 -0
  159. /package/dist/{chunks-KKYJDJT7.js.map → chunks-Q7T4SQGW.js.map} +0 -0
  160. /package/dist/{detect-FX2ZHKZP.js.map → models-6VMAVZR6.js.map} +0 -0
  161. /package/dist/{models-KA4GLQK5.js.map → pdf-ocr-3GDTJSHF.js.map} +0 -0
  162. /package/dist/{pdf-ocr-HWPNMKKC.js.map → pdf-ocr-U7MEGNBZ.js.map} +0 -0
  163. /package/dist/{pdf-ocr-W2PA2K3U.js.map → profile-io-72R3B7KO.js.map} +0 -0
  164. /package/dist/{profile-io-I2TH44EO.js.map → redact-W5K3ILQH.js.map} +0 -0
  165. /package/dist/{redact-7ILEAUTS.js.map → render-6OW6U4L4.js.map} +0 -0
  166. /package/dist/{render-MDJDKKNH.js.map → seal-GTNLVBJX.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils.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 = 256 * 1024 * 1024, // parser-shared MAX_DECOMPRESS_SIZE 와 동기\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** XXE/Billion Laughs 방지 — DOCTYPE 제거 (내부 DTD 서브셋 포함) */\nexport function stripDtd(xml: string): string {\n return xml.replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n // 괄호 percent-encoding — 마크다운 링크 목적지에서 불균형 ')'는 링크를 조기 종료시켜\n // 문법이 깨진다 (원문 하이퍼링크에 ')'가 박힌 실문서 존재). %28/%29는 의미 동일.\n return trimmed.replace(/\\(/g, \"%28\").replace(/\\)/g, \"%29\")\n}\n\n// ─── 안전한 min/max (스택 오버플로 방지) ─────────────\n\n/** Math.min(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMin(arr: number[]): number {\n let min = Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] < min) min = arr[i]\n return min\n}\n\n/** Math.max(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMax(arr: number[]): number {\n let max = -Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] > max) max = arr[i]\n return max\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/mong-e/workspace/kordoc/dist/chunk-CELJIGBQ.cjs","../src/ocr/ruling-lines.ts","../src/ocr/engine.ts","../src/ocr/models.ts","../src/ocr/pdf-ocr.ts"],"names":["m"],"mappings":"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","file":"/Users/mong-e/workspace/kordoc/dist/chunk-CELJIGBQ.cjs","sourcesContent":[null,"/**\n * 래스터 괘선 감지 — 스캔/이미지 페이지의 픽셀에서 표 괘선(수평/수직 선)을\n * 찾아 선 기반 표 파이프라인(line-detector → table-grid)에 공급한다.\n *\n * PDF 그래픽 ops 가 없는 OCR 경로는 종전에 클러스터 감지기만 탔는데,\n * 병합 라벨 셀 + 다중줄 서술형 서식(정부 제출 서식류)은 라인 클러스터로\n * 행 경계를 잡을 수 없다. 괘선은 이미지에 실존하므로 이진화 + 런렝스로\n * 직접 감지한다 — ML 없음, 축 정렬 전제(스캔 스큐 보정은 스코프 밖).\n *\n * 오탐 방어 3겹 (실측: 우수사례 제출 서식 스캔 — 진짜 괘선은 길고 얇음):\n * - 최소 길이 20pt: 글리프 획(가로 ≤한 글자폭·세로 ≤글자높이)은 걸러짐\n * - 두께 상한 2.5pt: 굵은 제목 획(6~13px)·텍스트 줄 밴드·채운 색상바 탈락\n * - 양측 잉크 포위 제외: 색상바(붙임 박스 등) 안 흰 글자 틈새 슬리버 탈락\n */\n\nimport type { LineSegment } from \"../pdf/line-types.js\"\n\n/** 잉크 판정 휘도 상한 — 셀 배경 음영(회색 ~220)은 통과, 실선(진회~검정·남색)만 잉크 */\nconst INK_LUMA_MAX = 190\n/** 최소 선 길이 (pt) — 최소 셀 변(≈7mm)과 대형 글리프 획(≤1em) 사이 */\nconst MIN_LINE_LENGTH_PT = 20\n/** 선 두께 상한 (pt) — 진짜 괘선(0.12~0.7mm ≈ 0.3~2pt)과 굵은 글리프 획(2pt+) 분리 */\nconst MAX_LINE_WIDTH_PT = 2.5\n/** 런 내 허용 끊김 (px) — 스캔 노이즈/JPEG 아티팩트 */\nconst GAP_TOL_PX = 2\n/** 밴드 병합 시 요구하는 x(또는 y) 범위 겹침 비율 (짧은 쪽 기준) */\nconst BAND_OVERLAP_RATIO = 0.5\n/** 밴드 양측(±2px)이 이 비율 이상 잉크면 채운 영역 내부 슬리버로 판정 */\nconst SURROUND_INK_RATIO = 0.35\n\n/** 픽셀 공간(top-left origin) 선분 — 두께는 밴드 픽셀 수 */\nexport interface PxSegment {\n x1: number; y1: number\n x2: number; y2: number\n thicknessPx: number\n}\n\nexport interface RulingLines {\n horizontals: PxSegment[]\n verticals: PxSegment[]\n}\n\n/**\n * RGBA 래스터에서 수평/수직 괘선 감지.\n * @param scale 렌더 스케일 (px/pt) — 길이·두께 임계를 pt 기준으로 환산\n */\nexport function detectRulingLines(\n rgba: Uint8Array,\n width: number,\n height: number,\n scale: number,\n): RulingLines {\n const minLenPx = Math.round(MIN_LINE_LENGTH_PT * scale)\n const maxThickPx = Math.max(1, Math.floor(MAX_LINE_WIDTH_PT * scale))\n\n // 잉크 마스크 1회 구축 — 수평/수직 두 패스가 공유\n const ink = new Uint8Array(width * height)\n for (let p = 0, i = 0; p < ink.length; p++, i += 4) {\n // ITU-R BT.601 휘도 근사 (정수 시프트)\n const luma = (rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29) >> 8\n if (luma <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1\n }\n\n const horizontals = detectBands(ink, width, height, minLenPx, maxThickPx, false)\n const verticals = detectBands(ink, width, height, minLenPx, maxThickPx, true)\n return { horizontals, verticals }\n}\n\n/** 열린 밴드 — 인접 스캔라인의 런을 누적 */\ninterface Band {\n lo: number // 런 진행축 시작 (h: x1, v: y1)\n hi: number // 런 진행축 끝\n first: number // 직교축 시작 스캔라인 (h: yTop, v: xLeft)\n last: number // 직교축 마지막 스캔라인\n}\n\n/**\n * 스캔라인 런렝스 → 인접 라인 밴드 병합 → 두께 필터 → 선분 방출.\n * transpose=false 면 수평선(행 스캔), true 면 수직선(열 스캔).\n */\nfunction detectBands(\n ink: Uint8Array,\n width: number,\n height: number,\n minLenPx: number,\n maxThickPx: number,\n transpose: boolean,\n): PxSegment[] {\n const lines = transpose ? width : height // 스캔라인 개수 (직교축)\n const span = transpose ? height : width // 런 진행축 길이\n const at = transpose\n ? (line: number, pos: number) => ink[pos * width + line]\n : (line: number, pos: number) => ink[line * width + pos]\n\n const out: PxSegment[] = []\n let open: Band[] = []\n\n /** 밴드 양측(±2px 스캔라인)의 잉크 비율 — 채운 색상바 안 흰 글자 틈새 슬리버 판별 */\n const surroundInkRatio = (b: Band, side: number): number => {\n if (side < 0 || side >= lines) return 0\n let dark = 0, total = 0\n for (let pos = b.lo; pos <= b.hi; pos += 3) {\n total++\n if (at(side, pos) === 1) dark++\n }\n return total > 0 ? dark / total : 0\n }\n\n const emit = (b: Band) => {\n const thick = b.last - b.first + 1\n if (thick > maxThickPx) return // 색상바·텍스트 밴드\n if (\n surroundInkRatio(b, b.first - 2) >= SURROUND_INK_RATIO &&\n surroundInkRatio(b, b.last + 2) >= SURROUND_INK_RATIO\n ) return // 채운 영역 내부 슬리버\n const center = (b.first + b.last) / 2\n out.push(\n transpose\n ? { x1: center, y1: b.lo, x2: center, y2: b.hi, thicknessPx: thick }\n : { x1: b.lo, y1: center, x2: b.hi, y2: center, thicknessPx: thick },\n )\n }\n\n for (let line = 0; line < lines; line++) {\n // 1) 이 스캔라인의 잉크 런 수집 (GAP_TOL_PX 이하 끊김 허용)\n const runs: Array<{ lo: number; hi: number }> = []\n let runStart = -1\n let gap = 0\n for (let pos = 0; pos <= span; pos++) {\n const on = pos < span && at(line, pos) === 1\n if (on) {\n if (runStart < 0) runStart = pos\n gap = 0\n } else if (runStart >= 0) {\n if (++gap > GAP_TOL_PX || pos >= span) {\n const hi = pos - gap\n if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi })\n runStart = -1\n gap = 0\n }\n }\n }\n\n // 2) 열린 밴드와 병합 — 직전 스캔라인까지 이어졌고 범위가 겹치면 확장\n const next: Band[] = []\n const used = new Set<number>()\n for (const band of open) {\n if (band.last !== line - 1) { emit(band); continue } // 연속 끊김 → 확정\n let merged = false\n for (let r = 0; r < runs.length; r++) {\n if (used.has(r)) continue\n const run = runs[r]\n const overlap = Math.min(band.hi, run.hi) - Math.max(band.lo, run.lo) + 1\n const shorter = Math.min(band.hi - band.lo, run.hi - run.lo) + 1\n if (overlap >= shorter * BAND_OVERLAP_RATIO) {\n band.lo = Math.min(band.lo, run.lo)\n band.hi = Math.max(band.hi, run.hi)\n band.last = line\n next.push(band)\n used.add(r)\n merged = true\n break\n }\n }\n if (!merged) emit(band)\n }\n for (let r = 0; r < runs.length; r++) {\n if (!used.has(r)) next.push({ lo: runs[r].lo, hi: runs[r].hi, first: line, last: line })\n }\n open = next\n }\n for (const band of open) emit(band)\n return out\n}\n\n/**\n * 픽셀 선분 → PDF pt LineSegment (bottom-up, classifyAndAdd 규약:\n * 수평선 y1==y2·x1<x2, 수직선 x1==x2·y1<y2).\n */\nexport function rulingToPdfLines(\n ruling: RulingLines,\n scale: number,\n pdfHeight: number,\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const horizontals: LineSegment[] = ruling.horizontals.map(s => {\n const y = pdfHeight - (s.y1 + s.y2) / 2 / scale\n return {\n x1: Math.min(s.x1, s.x2) / scale,\n y1: y,\n x2: Math.max(s.x1, s.x2) / scale,\n y2: y,\n lineWidth: s.thicknessPx / scale,\n }\n })\n const verticals: LineSegment[] = ruling.verticals.map(s => {\n const x = (s.x1 + s.x2) / 2 / scale\n const yA = pdfHeight - Math.max(s.y1, s.y2) / scale\n const yB = pdfHeight - Math.min(s.y1, s.y2) / scale\n return { x1: x, y1: yA, x2: x, y2: yB, lineWidth: s.thicknessPx / scale }\n })\n return { horizontals, verticals }\n}\n","/**\n * 내장 텍스트 OCR 엔진 — PP-OCRv5 korean (det DBNet + rec SVTR/CTC) ONNX 추론.\n *\n * 파이프라인: 페이지 RGBA → det(선 검출) → 라인 crop → rec(CTC 인식) → OcrItem[]\n * (좌표는 입력 픽셀 기준 top-left origin — 호출자가 PDF 좌표계로 변환).\n *\n * 전·후처리는 공식 inference.yml 스펙 그대로:\n * - det: BGR, 긴 변 960 리사이즈(32 배수), mean/std [0.485,0.456,0.406]/[0.229,0.224,0.225],\n * DBPostProcess thresh 0.3 / box_thresh 0.6 / unclip_ratio 1.5\n * - rec: BGR, 높이 48 고정 비율 리사이즈 + 우측 zero-pad, (x/255-0.5)/0.5,\n * CTC 디코드 (blank=0, 1..N=사전, N+1=공백), text_score 0.5\n * DB 후처리의 contour+minAreaRect 는 축정렬 connected-component bbox 로 근사\n * (공문서 스캔은 수평 텍스트가 지배적 — 회전 텍스트는 v1 범위 밖).\n *\n * 의존성(onnxruntime-node, sharp)은 optional — 미설치 시 create()가 명확한 에러.\n * 모델 미다운로드 시에도 즉시 실패 — 호출자가 ensureOcrModels() 먼저.\n */\n\nimport type { InferenceSession } from \"onnxruntime-node\"\nimport { readFile } from \"fs/promises\"\nimport { join } from \"path\"\nimport {\n OCR_DET_MODEL,\n OCR_REC_MODEL,\n OCR_REC_DICT,\n getOcrModelsDir,\n parseCharacterDict,\n} from \"./models.js\"\n\n/** OCR 인식 결과 한 줄 — 좌표는 입력 이미지 픽셀 (top-left origin, y down) */\nexport interface OcrItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n /** CTC 평균 신뢰도 0~1 */\n confidence: number\n}\n\n/** det 입력 긴 변 (공식 resize_long) */\nconst DET_LONG_SIDE = 960\nconst DET_THRESH = 0.3\nconst DET_BOX_THRESH = 0.6\nconst DET_UNCLIP_RATIO = 1.5\nconst DET_MIN_SIZE = 3\nconst DET_MAX_BOXES = 1000\nconst REC_HEIGHT = 48\nconst REC_MIN_WIDTH = 320\nconst REC_MAX_WIDTH = 3200\nconst TEXT_SCORE = 0.5\n\n// det: BGR 채널 순서에 yml 기재 순서 그대로 적용 (mean[0]→B)\nconst DET_MEAN = [0.485, 0.456, 0.406]\nconst DET_STD = [0.229, 0.224, 0.225]\n\ntype SharpFactory = (\n input: Uint8Array | Buffer,\n options?: { raw?: { width: number; height: number; channels: number } },\n) => SharpChain\ninterface SharpChain {\n extract(region: { left: number; top: number; width: number; height: number }): SharpChain\n resize(w: number, h: number, opts?: { fit?: string }): SharpChain\n removeAlpha(): SharpChain\n raw(): { toBuffer(): Promise<Buffer> }\n}\n\nexport class OcrEngine {\n private det: InferenceSession\n private rec: InferenceSession\n private dict: string[]\n private ort: typeof import(\"onnxruntime-node\")\n private sharp: SharpFactory\n\n private constructor(parts: {\n det: InferenceSession\n rec: InferenceSession\n dict: string[]\n ort: typeof import(\"onnxruntime-node\")\n sharp: SharpFactory\n }) {\n this.det = parts.det\n this.rec = parts.rec\n this.dict = parts.dict\n this.ort = parts.ort\n this.sharp = parts.sharp\n }\n\n static async create(): Promise<OcrEngine> {\n const [ortMod, sharpModRaw] = await Promise.all([\n tryImport<typeof import(\"onnxruntime-node\")>(\"onnxruntime-node\", () => import(\"onnxruntime-node\")),\n tryImport<{ default?: SharpFactory } & SharpFactory>(\n \"sharp\",\n () => import(\"sharp\") as unknown as Promise<{ default?: SharpFactory } & SharpFactory>,\n ),\n ])\n const sharpAny = sharpModRaw as { default?: SharpFactory } | SharpFactory\n const sharpMod: SharpFactory =\n typeof sharpAny === \"function\" ? sharpAny : (sharpAny.default ?? (sharpAny as unknown as SharpFactory))\n\n const dir = getOcrModelsDir()\n const sessionOpts: import(\"onnxruntime-node\").InferenceSession.SessionOptions = {\n graphOptimizationLevel: \"all\",\n executionProviders: [\"cpu\"],\n logSeverityLevel: 3, // paddle2onnx 변환 잔여물 W 로그 폭주 억제\n }\n const [det, rec, dictYml] = await Promise.all([\n ortMod.InferenceSession.create(join(dir, OCR_DET_MODEL.filename), sessionOpts),\n ortMod.InferenceSession.create(join(dir, OCR_REC_MODEL.filename), sessionOpts),\n readFile(join(dir, OCR_REC_DICT.filename), \"utf-8\"),\n ])\n const dict = parseCharacterDict(dictYml)\n if (dict.length === 0) throw new Error(\"OCR 사전 파싱 실패 — 모델 캐시를 삭제 후 재다운로드하세요\")\n\n return new OcrEngine({ det, rec, dict, ort: ortMod, sharp: sharpMod })\n }\n\n /** onnxruntime-node 1.14+ InferenceSession.release() — 구버전은 무시 */\n async destroy(): Promise<void> {\n for (const s of [this.det, this.rec]) {\n const rel = (s as unknown as { release?: () => Promise<void> }).release\n if (typeof rel === \"function\") {\n try { await rel.call(s) } catch { /* ignore */ }\n }\n }\n }\n\n /**\n * 페이지 RGBA 픽셀 → 텍스트 라인 인식.\n * 반환 좌표는 입력 픽셀 기준. 라인은 위→아래, 좌→우 정렬.\n */\n async recognizePage(rgba: Uint8Array, width: number, height: number): Promise<OcrItem[]> {\n if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return []\n const boxes = await this.detect(rgba, width, height)\n const items: OcrItem[] = []\n for (const b of boxes) {\n const r = await this.recognizeLine(rgba, width, height, b)\n if (r && r.confidence >= TEXT_SCORE && r.text.trim()) items.push(r)\n }\n items.sort((a, b) => (a.y - b.y) || (a.x - b.x))\n return items\n }\n\n // ─── det ─────────────────────────────────────────────\n\n private async detect(\n rgba: Uint8Array,\n width: number,\n height: number,\n ): Promise<Array<{ x: number; y: number; w: number; h: number }>> {\n const ratio = DET_LONG_SIDE / Math.max(width, height)\n const dw = Math.max(32, Math.round((width * ratio) / 32) * 32)\n const dh = Math.max(32, Math.round((height * ratio) / 32) * 32)\n\n const rgb = await this.sharp(rgba, { raw: { width, height, channels: 4 } })\n .resize(dw, dh, { fit: \"fill\" })\n .removeAlpha()\n .raw()\n .toBuffer()\n\n // HWC RGB → CHW BGR float32 정규화\n const plane = dw * dh\n const input = new Float32Array(3 * plane)\n for (let i = 0; i < plane; i++) {\n const r = rgb[i * 3] / 255\n const g = rgb[i * 3 + 1] / 255\n const b = rgb[i * 3 + 2] / 255\n input[i] = (b - DET_MEAN[0]) / DET_STD[0]\n input[plane + i] = (g - DET_MEAN[1]) / DET_STD[1]\n input[2 * plane + i] = (r - DET_MEAN[2]) / DET_STD[2]\n }\n\n const tensor = new this.ort.Tensor(\"float32\", input, [1, 3, dh, dw])\n const out = await this.det.run({ [this.det.inputNames[0]]: tensor })\n const probMap = out[this.det.outputNames[0]].data as Float32Array\n\n const rawBoxes = componentBoxes(probMap, dw, dh)\n const sx = width / dw\n const sy = height / dh\n const boxes: Array<{ x: number; y: number; w: number; h: number }> = []\n for (const rb of rawBoxes.slice(0, DET_MAX_BOXES)) {\n // unclip: DB 는 학습 시 텍스트 영역을 수축시키므로 검출 박스를 되팽창\n const bw = rb.x2 - rb.x1 + 1\n const bh = rb.y2 - rb.y1 + 1\n const delta = (bw * bh * DET_UNCLIP_RATIO) / (2 * (bw + bh))\n const x1 = Math.max(0, Math.floor((rb.x1 - delta) * sx))\n const y1 = Math.max(0, Math.floor((rb.y1 - delta) * sy))\n const x2 = Math.min(width, Math.ceil((rb.x2 + 1 + delta) * sx))\n const y2 = Math.min(height, Math.ceil((rb.y2 + 1 + delta) * sy))\n if (x2 - x1 < DET_MIN_SIZE || y2 - y1 < DET_MIN_SIZE) continue\n boxes.push({ x: x1, y: y1, w: x2 - x1, h: y2 - y1 })\n }\n return boxes\n }\n\n // ─── rec ─────────────────────────────────────────────\n\n private async recognizeLine(\n rgba: Uint8Array,\n width: number,\n height: number,\n box: { x: number; y: number; w: number; h: number },\n ): Promise<OcrItem | null> {\n const rw = Math.min(REC_MAX_WIDTH, Math.max(16, Math.round((box.w * REC_HEIGHT) / box.h)))\n const padded = Math.max(REC_MIN_WIDTH, rw)\n\n const rgb = await this.sharp(rgba, { raw: { width, height, channels: 4 } })\n .extract({ left: box.x, top: box.y, width: box.w, height: box.h })\n .resize(rw, REC_HEIGHT, { fit: \"fill\" })\n .removeAlpha()\n .raw()\n .toBuffer()\n\n // HWC RGB → CHW BGR, (x/255-0.5)/0.5, 우측 zero-pad\n const plane = padded * REC_HEIGHT\n const input = new Float32Array(3 * plane) // pad 영역은 0 (=정규화 후 회색 아님 주의: PP 도 0 pad)\n for (let y = 0; y < REC_HEIGHT; y++) {\n for (let x = 0; x < rw; x++) {\n const src = (y * rw + x) * 3\n const dst = y * padded + x\n input[dst] = rgb[src + 2] / 255 / 0.5 - 1 // B\n input[plane + dst] = rgb[src + 1] / 255 / 0.5 - 1 // G\n input[2 * plane + dst] = rgb[src] / 255 / 0.5 - 1 // R\n }\n }\n\n const tensor = new this.ort.Tensor(\"float32\", input, [1, 3, REC_HEIGHT, padded])\n const out = await this.rec.run({ [this.rec.inputNames[0]]: tensor })\n const logits = out[this.rec.outputNames[0]]\n const [, T, C] = logits.dims as number[]\n const data = logits.data as Float32Array\n\n const decoded = ctcDecode(data, T, C, this.dict)\n if (!decoded) return null\n return { text: decoded.text, x: box.x, y: box.y, w: box.w, h: box.h, confidence: decoded.confidence }\n }\n}\n\n/** CTC greedy 디코드 — 연속 중복 붕괴 → blank(0) 제거 → 사전 매핑 (테스트용 export) */\nexport function ctcDecode(\n data: Float32Array,\n T: number,\n C: number,\n dict: string[],\n): { text: string; confidence: number } | null {\n let text = \"\"\n let confSum = 0\n let confCount = 0\n let prev = -1\n for (let t = 0; t < T; t++) {\n const off = t * C\n let best = 0\n let bestV = data[off]\n for (let c = 1; c < C; c++) {\n const v = data[off + c]\n if (v > bestV) { bestV = v; best = c }\n }\n const repeat = best === prev\n prev = best\n if (best === 0 || repeat) continue\n // 모델 출력이 softmax 확률이 아니면 (>1) 해당 스텝만 정규화\n let p = bestV\n if (p > 1.0001 || p < 0) {\n let denom = 0\n for (let c = 0; c < C; c++) denom += Math.exp(data[off + c] - bestV)\n p = 1 / denom\n }\n confSum += p\n confCount++\n if (best >= 1 && best <= dict.length) text += dict[best - 1]\n else if (best === dict.length + 1) text += \" \"\n }\n if (!text) return null\n return { text, confidence: confCount > 0 ? confSum / confCount : 0 }\n}\n\n/** 이진화 확률맵의 4-연결 성분 bbox (score = 성분 평균 확률, 테스트용 export) */\nexport function componentBoxes(\n prob: Float32Array,\n w: number,\n h: number,\n): Array<{ x1: number; y1: number; x2: number; y2: number }> {\n const visited = new Uint8Array(w * h)\n const boxes: Array<{ x1: number; y1: number; x2: number; y2: number; score: number }> = []\n const stack: number[] = []\n\n for (let start = 0; start < w * h; start++) {\n if (visited[start] || prob[start] <= DET_THRESH) continue\n let x1 = start % w, x2 = x1, y1 = (start / w) | 0, y2 = y1\n let sum = 0\n let count = 0\n stack.length = 0\n stack.push(start)\n visited[start] = 1\n while (stack.length) {\n const p = stack.pop()!\n const px = p % w\n const py = (p / w) | 0\n sum += prob[p]\n count++\n if (px < x1) x1 = px\n if (px > x2) x2 = px\n if (py < y1) y1 = py\n if (py > y2) y2 = py\n // 4-이웃\n if (px > 0 && !visited[p - 1] && prob[p - 1] > DET_THRESH) { visited[p - 1] = 1; stack.push(p - 1) }\n if (px < w - 1 && !visited[p + 1] && prob[p + 1] > DET_THRESH) { visited[p + 1] = 1; stack.push(p + 1) }\n if (py > 0 && !visited[p - w] && prob[p - w] > DET_THRESH) { visited[p - w] = 1; stack.push(p - w) }\n if (py < h - 1 && !visited[p + w] && prob[p + w] > DET_THRESH) { visited[p + w] = 1; stack.push(p + w) }\n }\n if (x2 - x1 + 1 < DET_MIN_SIZE && y2 - y1 + 1 < DET_MIN_SIZE) continue\n boxes.push({ x1, y1, x2, y2, score: sum / count })\n }\n\n return boxes\n .filter(b => b.score >= DET_BOX_THRESH)\n .sort((a, b) => (a.y1 - b.y1) || (a.x1 - b.x1))\n}\n\nasync function tryImport<T>(name: string, loader: () => Promise<T>): Promise<T> {\n try {\n return await loader()\n } catch (e) {\n throw new Error(\n `내장 OCR 을 사용하려면 optional dependency '${name}' 이 필요합니다. ` +\n `\\`npm install ${name}\\` 후 다시 실행하세요. 원인: ${(e as Error).message}`,\n )\n }\n}\n\n// ─── 엔진 싱글턴 (watch/서버 장기 실행에서 세션 재사용) ───\nlet enginePromise: Promise<OcrEngine> | null = null\n\nexport function getOcrEngine(): Promise<OcrEngine> {\n if (!enginePromise) {\n enginePromise = OcrEngine.create().catch(err => {\n enginePromise = null // 실패는 캐시하지 않음 — 모델 설치 후 재시도 가능\n throw err\n })\n }\n return enginePromise\n}\n","/**\n * 내장 텍스트 OCR 모델 (PP-OCRv5 korean) 스펙 + 다운로드/검증.\n *\n * 캐시 위치: `~/.cache/kordoc/models/ppocr/` — 총 ~18MB (det 4.6 + rec 12.8 + dict 0.1)\n * 다운로드 인프라는 수식 OCR 과 공용 (formula/models.ts ensureModelsIn).\n *\n * 모델 출처: PaddlePaddle 공식 HuggingFace org 의 ONNX 변환본 (Apache-2.0).\n * SHA-256 은 실제 다운로드 + HF API lfs.oid 대조로 검증됨 — 변경 금지.\n * 한국어 사전은 rec 리포의 inference.yml `PostProcess.character_dict` 에\n * 내장 (11,945자 — 완성형 한글 11,172 음절 전량 + 자모/라틴/기호).\n */\n\nimport { join } from \"path\"\nimport {\n type ModelSpec,\n type ModelStatus,\n type ProgressHandler,\n ensureModelsIn,\n getModelStatusIn,\n getModelsDir,\n} from \"../pdf/formula/models.js\"\n\nexport const OCR_DET_MODEL: ModelSpec = {\n name: \"PP-OCRv5 mobile det\",\n filename: \"det.onnx\",\n url: \"https://huggingface.co/PaddlePaddle/PP-OCRv5_mobile_det_onnx/resolve/main/inference.onnx\",\n sha256: \"a431985659dc921974177a95adcfbb90fd9e51989a5e04d70d0b75f597b6e61d\",\n sizeMb: 5,\n}\n\nexport const OCR_REC_MODEL: ModelSpec = {\n name: \"PP-OCRv5 korean rec\",\n filename: \"rec_korean.onnx\",\n url: \"https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.onnx\",\n sha256: \"92f0b7785e64fc9090106a241cf4c1eb97472824558272751b88a2a4476d3a08\",\n sizeMb: 13,\n}\n\nexport const OCR_REC_DICT: ModelSpec = {\n name: \"PP-OCRv5 korean dict\",\n filename: \"rec_korean.yml\",\n url: \"https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.yml\",\n sha256: \"f757fa1c40e99edcf27e9cce879b93eb2a51fa46f5ef39095689b8c37dd75998\",\n sizeMb: 1,\n}\n\nexport const ALL_OCR_MODELS: ReadonlyArray<ModelSpec> = [OCR_DET_MODEL, OCR_REC_MODEL, OCR_REC_DICT]\n\nexport function getOcrModelsDir(): string {\n return getModelsDir(\"ppocr\")\n}\n\n/** 모든 텍스트 OCR 모델 다운로드/검증 (있으면 skip) */\nexport async function ensureOcrModels(onProgress?: ProgressHandler): Promise<void> {\n return ensureModelsIn(getOcrModelsDir(), ALL_OCR_MODELS, onProgress)\n}\n\n/** 텍스트 OCR 모델 상태 (다운로드 없이 확인만) */\nexport async function getOcrModelStatus(): Promise<ModelStatus[]> {\n return getModelStatusIn(getOcrModelsDir(), ALL_OCR_MODELS)\n}\n\n/**\n * inference.yml 에서 `PostProcess.character_dict` 리스트를 추출.\n * 전체 YAML 파서 없이 해당 블록의 `- <char>` 라인만 순서대로 읽는다\n * (공식 yml 구조 고정 — 들여쓰기 2칸, 한 줄 한 글자).\n *\n * CTC 클래스 배열: index 0 = blank, 1..N = 사전 순서, N+1(마지막) = space.\n */\nexport function parseCharacterDict(yml: string): string[] {\n const lines = yml.split(\"\\n\")\n const chars: string[] = []\n let inDict = false\n let dictIndent = -1\n for (const line of lines) {\n if (!inDict) {\n const m = /^(\\s*)character_dict:\\s*$/.exec(line)\n if (m) {\n inDict = true\n dictIndent = m[1].length\n }\n continue\n }\n // 리스트 항목 — YAML 은 키와 같은 들여쓰기의 \"- x\" 도 허용 (공식 yml 실측: 둘 다 2칸)\n const m = /^(\\s*)- (.*)$/.exec(line)\n if (m && m[1].length >= dictIndent) {\n let v = m[2]\n // YAML 인용 문자 처리 ('#' 같은 예약 문자는 인용됨)\n if (v.length >= 2 && ((v.startsWith(\"'\") && v.endsWith(\"'\")) || (v.startsWith('\"') && v.endsWith('\"')))) {\n v = v.slice(1, -1).replace(/''/g, \"'\")\n }\n chars.push(v)\n continue\n }\n // 들여쓰기가 얕아지면 블록 종료 (빈 줄은 통과)\n if (line.trim() !== \"\") break\n }\n return chars\n}\n\nexport function ocrModelPath(spec: ModelSpec): string {\n return join(getOcrModelsDir(), spec.filename)\n}\n","/**\n * PDF OCR 브릿지 — 대상 페이지를 pdfium 으로 래스터해 내장 엔진 또는\n * 사용자 OcrProvider 로 인식하고, 페이지별 IRBlock[] 을 돌려준다.\n *\n * - 내장 엔진: OcrItem 좌표를 PDF 포인트로 환산해 기존 블록 파이프라인\n * (extractPageBlocksWithLines — xy-cut 읽기순서·클러스터 표 감지)에 태운다.\n * 스캔 문서도 표 구조 복원이 가능한 이유.\n * - 사용자 프로바이더: 페이지 PNG → 텍스트 → 페이지당 paragraph 블록 (종전 계약).\n *\n * 에러 계약 (v4.1.0 리뷰 F6/F7):\n * - 환경 오류(의존성·모델 미설치, PDF 오픈 실패)는 throw — 호출자가 NEEDS_OCR 폴백.\n * - 페이지 단위 실패는 본문 오염 대신 warnings(OCR_FAILED) 로 기록하고 계속.\n */\n\nimport type { IRBlock, OcrProvider, ParseWarning } from \"../types.js\"\nimport type { NormItem } from \"../pdf/text-line.js\"\nimport type { LineSegment } from \"../pdf/line-types.js\"\nimport { extractPageBlocksWithLines } from \"../pdf/page-blocks.js\"\nimport { detectRulingLines, rulingToPdfLines } from \"./ruling-lines.js\"\nimport { getOcrEngine, type OcrItem } from \"./engine.js\"\nimport { ensureOcrModels } from \"./models.js\"\n\n/** OCR 렌더 스케일 (72dpi × 3 = 216dpi) — 10pt 본문이 rec 입력 높이(48px)에 근접 */\nconst OCR_RENDER_SCALE = 3\n/** 페이지 하나당 OCR 타임아웃 — det+rec 수십 라인 기준 넉넉히 */\nconst PAGE_TIMEOUT_MS = 120_000\n\nexport type OcrMode = \"builtin\" | OcrProvider\n\n/**\n * 대상 페이지들을 OCR 해 페이지별 블록 맵 반환.\n * @param buffer 원본 PDF (pdfjs 가 detach 하기 전에 clone 해 둔 것)\n * @param targets 1-based 페이지 번호 집합\n */\nexport async function runPdfOcr(\n buffer: ArrayBuffer,\n targets: Set<number>,\n mode: OcrMode,\n warnings: ParseWarning[],\n onProgress?: (current: number, total: number) => void,\n): Promise<Map<number, IRBlock[]>> {\n const result = new Map<number, IRBlock[]>()\n if (targets.size === 0) return result\n\n // 환경 준비 — 실패는 그대로 throw (호출자가 NEEDS_OCR 폴백)\n const pdfiumMod = await tryImport<typeof import(\"@hyzyla/pdfium\")>(\n \"@hyzyla/pdfium\",\n () => import(\"@hyzyla/pdfium\"),\n )\n if (mode === \"builtin\") {\n await ensureOcrModels(p => {\n if (p.phase === \"download\" && p.downloaded === 0) {\n process.stderr.write(`[kordoc-ocr] ${p.spec.name} 다운로드 중 (~${p.spec.sizeMb}MB)...\\n`)\n }\n })\n }\n const engine = mode === \"builtin\" ? await getOcrEngine() : null\n\n const pdfium = await pdfiumMod.PDFiumLibrary.init()\n const doc = await pdfium.loadDocument(new Uint8Array(buffer))\n try {\n let done = 0\n for (const page of doc.pages()) {\n // pdfium page.number 는 0-based pageIndex — 대외 계약(1-based)으로 환산\n const pageNo = page.number + 1\n if (!targets.has(pageNo)) continue\n onProgress?.(++done, targets.size)\n try {\n const blocks = await withTimeout(\n ocrOnePage(page, pageNo, mode, engine, warnings),\n PAGE_TIMEOUT_MS,\n `OCR 페이지 ${pageNo} 타임아웃 (${PAGE_TIMEOUT_MS / 1000}초)`,\n )\n result.set(pageNo, blocks)\n } catch (e) {\n warnings.push({\n page: pageNo,\n message: `페이지 ${pageNo} OCR 실패: ${e instanceof Error ? e.message : String(e)}`,\n code: \"OCR_FAILED\",\n })\n }\n }\n } finally {\n doc.destroy()\n pdfium.destroy()\n }\n return result\n}\n\nasync function ocrOnePage(\n page: import(\"@hyzyla/pdfium\").PDFiumPage,\n pageNo: number,\n mode: OcrMode,\n engine: Awaited<ReturnType<typeof getOcrEngine>> | null,\n warnings: ParseWarning[],\n): Promise<IRBlock[]> {\n const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize()\n const rendered = await page.render({\n scale: OCR_RENDER_SCALE,\n render: async ({ data }) => data,\n })\n const { data: bgra, width: rw, height: rh } = rendered\n const rgba = bgraToRgba(bgra)\n\n if (mode === \"builtin\") {\n const items = await engine!.recognizePage(rgba, rw, rh)\n // 래스터에서 표 괘선 감지 — 스캔본 병합셀 서식도 선 기반 표 파이프라인을 탄다\n const scale = rh / pdfH\n const ruling = detectRulingLines(rgba, rw, rh, scale)\n const extraLines = rulingToPdfLines(ruling, scale, pdfH)\n return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines)\n }\n\n // 사용자 프로바이더 — PNG 인코딩 후 호출, 페이지당 paragraph (종전 계약)\n type SharpPngFactory = (\n input: Uint8Array,\n opts: { raw: { width: number; height: number; channels: number } },\n ) => { png(): { toBuffer(): Promise<Buffer> } }\n const sharpModRaw = await tryImport<{ default?: SharpPngFactory } & SharpPngFactory>(\n \"sharp\",\n () => import(\"sharp\") as unknown as Promise<{ default?: SharpPngFactory } & SharpPngFactory>,\n )\n const sharpAny = sharpModRaw as { default?: SharpPngFactory } | SharpPngFactory\n const sharp: SharpPngFactory =\n typeof sharpAny === \"function\" ? sharpAny : (sharpAny.default ?? (sharpAny as unknown as SharpPngFactory))\n const png = await sharp(rgba, { raw: { width: rw, height: rh, channels: 4 } })\n .png()\n .toBuffer()\n\n const text = await mode(new Uint8Array(png), pageNo, \"image/png\")\n if (!text.trim()) {\n warnings.push({ page: pageNo, message: `페이지 ${pageNo} OCR 결과 없음`, code: \"OCR_FAILED\" })\n return []\n }\n return [{ type: \"paragraph\", text: text.trim(), pageNumber: pageNo }]\n}\n\n/**\n * OcrItem(렌더 픽셀, top-left origin) → NormItem(PDF pt, bottom-up baseline)\n * 으로 환산해 기존 블록 파이프라인에 태운다. 괘선은 그래픽 ops 대신\n * 래스터 감지 결과(extraLines)로 공급 — 없으면 클러스터 감지기 몫이다.\n * (이미지 직접 입력 경로 image-ocr.ts 와 공유)\n */\nexport function ocrItemsToBlocks(\n items: OcrItem[],\n pageNumber: number,\n pdfW: number,\n pdfH: number,\n scale: number,\n extraLines?: { horizontals: LineSegment[]; verticals: LineSegment[] },\n): IRBlock[] {\n const norm: NormItem[] = items.map(it => {\n const h = it.h / scale\n return {\n text: it.text,\n x: Math.round(it.x / scale),\n // NormItem.y 는 pdfjs transform[5] = 베이스라인 (bottom-up) — 박스 하단으로 근사\n y: Math.round(pdfH - (it.y + it.h) / scale),\n w: Math.round(it.w / scale),\n h: Math.round(h),\n fontSize: Math.max(1, Math.round(h * 0.8)), // 박스높이는 어센더+디센더 포함 — 글자크기 근사\n fontName: \"ocr\",\n isHidden: false,\n }\n })\n return extractPageBlocksWithLines(norm, pageNumber, { fnArray: [], argsArray: [] }, pdfW, pdfH, extraLines)\n}\n\nasync function tryImport<T>(name: string, loader: () => Promise<T>): Promise<T> {\n try {\n return await loader()\n } catch (e) {\n throw new Error(\n `OCR 을 사용하려면 optional dependency '${name}' 이 필요합니다. ` +\n `\\`npm install ${name}\\` 후 다시 실행하세요. 원인: ${(e as Error).message}`,\n )\n }\n}\n\nasync function withTimeout<T>(promise: Promise<T>, ms: number, msg: string): Promise<T> {\n // 타임아웃 패배 후 원 promise 의 사후 reject 가 unhandled rejection 이 되지 않게 흡수\n promise.catch(() => {})\n let timer: NodeJS.Timeout | undefined\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(msg)), ms)\n }),\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** pdfium BGRA → RGBA */\nfunction bgraToRgba(bgra: Uint8Array): Uint8Array {\n const out = new Uint8Array(bgra.length)\n for (let i = 0; i < bgra.length; i += 4) {\n out[i] = bgra[i + 2]\n out[i + 1] = bgra[i + 1]\n out[i + 2] = bgra[i]\n out[i + 3] = bgra[i + 3]\n }\n return out\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/chunks.ts"],"sourcesContent":["/**\n * RAG용 구조 보존 청킹 — IRBlock[] → DocChunk[].\n *\n * 문서의 위계(마크다운 헤딩 스택 + 개조식 리스트 깊이)를 breadcrumb으로 보존한\n * 청크 목록을 만든다. 토큰 상한·오버랩 등 자르기 정책은 의도적으로 없다 —\n * \"구조 트리 제공\"까지가 이 모듈의 몫이고 실제 분할은 소비자(RAG 파이프라인)가 한다.\n *\n * breadcrumb 규칙:\n * - 헤딩(level 1~6)은 스택 구조 — 같거나 깊은 level을 팝하고 push, 리스트 위계 리셋\n * - 리스트 항목은 깊이 d 등장 시 depth>=d를 팝하고 push — 하위 항목이 뒤따르면\n * 그 breadcrumb으로 승격되고, 자식이 없으면 다음 형제/헤딩이 팝해 자연 소멸\n * - 깊이는 IR의 listDepth 그대로. listDepth가 없는 개조식 선두 부호 문단\n * (□·○·- / 1.·가.·1) 등)은 depth 0 항목으로 취급 — IR에 없는 깊이는 지어내지 않는다\n * - 청크의 breadcrumb은 자기 자신을 제외한 상위 경로다\n */\n\nimport type { IRBlock } from \"./types.js\"\nimport { blocksToMarkdown } from \"./table/builder.js\"\n\nexport interface DocChunk {\n /** \"c0001\" 순번 id — 같은 입력이면 같은 출력 (결정적) */\n id: string\n type: \"text\" | \"table\" | \"heading\"\n /** 상위 헤딩 + 리스트 위계 경로 (예: [\"1. 개요\", \"가. 목적\"]) */\n breadcrumb: string[]\n /** 청크 본문 마크다운 — 표는 GFM/HTML 그대로 (blocksToMarkdown 재사용) */\n text: string\n /** 원본 페이지/섹션 번호 (1-based) — IR에 없으면 생략 */\n page?: number\n /** 원본 IRBlock 인덱스 범위 [start, end] (양끝 포함) — 출처 앵커 */\n blockRange: [number, number]\n /** 표 청크 전용 구조 요약 — cells는 includeTableCells 옵션 시에만 */\n table?: { rows: number; cols: number; cells?: string[][] }\n}\n\nexport interface ChunkOptions {\n /** 표 청크에 셀 텍스트 행렬 포함 여부 (기본 false) */\n includeTableCells?: boolean\n /**\n * \"section\"(기본): 같은 breadcrumb 아래 연속 텍스트 블록을 하나로 병합,\n * \"block\": IRBlock 1개 = 청크 1개\n */\n granularity?: \"block\" | \"section\"\n}\n\n/**\n * 개조식/항목부호 선두 마커 — listDepth 미기재 리스트 항목의 depth 0 식별용.\n * gongmun 8단계 부호(1. 가. 1) 가) (1) (가) ① ㉮)와 개조식 글머리(□ ○ - ※ 등).\n * 파서는 md 문법과 충돌하는 부호('- '·'1) ')에만 listDepth(1~)를 채우므로,\n * depth 0 항목은 부호 텍스트로만 식별 가능하다 (승격이 동작하기 위한 최소 휴리스틱)\n */\nconst LIST_MARKER_RE =\n /^(?:[□■◇◆○◎●◦ㅇ•▪▸▶※-]|\\d{1,3}[.)]|[가나다라마바사아자차카타파하][.)]|\\([가나다라마바사아자차카타파하0-9]{1,3}\\)|[①-⑳㉮-㉻㈎-㈛])\\s/\n\n/** 블록의 리스트 깊이 — 리스트 항목이 아니면 undefined */\nfunction listDepthOf(block: IRBlock): number | undefined {\n if (block.listDepth !== undefined) return block.listDepth\n if (block.type === \"list\") return 0\n if (block.type === \"paragraph\" && block.text && LIST_MARKER_RE.test(block.text.trim())) return 0\n return undefined\n}\n\nfunction sameBreadcrumb(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i])\n}\n\nexport function blocksToChunks(blocks: IRBlock[], options?: ChunkOptions): DocChunk[] {\n const granularity = options?.granularity ?? \"section\"\n const includeCells = options?.includeTableCells ?? false\n\n const chunks: DocChunk[] = []\n const headingStack: { level: number; text: string }[] = []\n const listStack: { depth: number; text: string }[] = []\n const crumb = () => [...headingStack.map(h => h.text), ...listStack.map(l => l.text)]\n\n const push = (\n type: DocChunk[\"type\"],\n breadcrumb: string[],\n text: string,\n blockRange: [number, number],\n page?: number,\n table?: DocChunk[\"table\"]\n ) => {\n const chunk: DocChunk = { id: \"c\" + String(chunks.length + 1).padStart(4, \"0\"), type, breadcrumb, text, blockRange }\n if (page !== undefined) chunk.page = page\n if (table) chunk.table = table\n chunks.push(chunk)\n }\n\n // section 병합 누적 상태 — 같은 breadcrumb의 연속 텍스트 블록\n let run: { breadcrumb: string[]; blocks: IRBlock[]; start: number; end: number; page?: number } | null = null\n const flushRun = () => {\n if (!run) return\n push(\"text\", run.breadcrumb, blocksToMarkdown(run.blocks), [run.start, run.end], run.page)\n run = null\n }\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n const md = blocksToMarkdown([block])\n if (!md) continue // 빈 블록 스킵 — 스택에도 불참\n\n if (block.type === \"heading\") {\n flushRun()\n const level = Math.min(block.level || 2, 6) // blocksToMarkdown과 동일한 기본값\n while (headingStack.length && headingStack[headingStack.length - 1].level >= level) headingStack.pop()\n listStack.length = 0 // 새 헤딩 = 리스트 위계 리셋\n push(\"heading\", crumb(), md, [i, i], block.pageNumber)\n headingStack.push({ level, text: (block.text ?? \"\").trim() })\n continue\n }\n\n if (block.type === \"table\" && block.table) {\n // 표는 항상 독립 청크 — 병합 금지\n flushRun()\n const summary: NonNullable<DocChunk[\"table\"]> = { rows: block.table.rows, cols: block.table.cols }\n if (includeCells) summary.cells = block.table.cells.map(row => row.map(c => c.text))\n push(\"table\", crumb(), md, [i, i], block.pageNumber, summary)\n continue\n }\n\n // 텍스트 계열 (paragraph·list·image·separator)\n const depth = listDepthOf(block)\n if (depth !== undefined) {\n while (listStack.length && listStack[listStack.length - 1].depth >= depth) listStack.pop()\n }\n const breadcrumb = crumb()\n if (depth !== undefined) listStack.push({ depth, text: (block.text ?? \"\").trim() })\n\n if (granularity === \"block\") {\n push(\"text\", breadcrumb, md, [i, i], block.pageNumber)\n continue\n }\n if (run && sameBreadcrumb(run.breadcrumb, breadcrumb)) {\n run.blocks.push(block)\n run.end = i\n if (run.page === undefined) run.page = block.pageNumber\n } else {\n flushRun()\n run = { breadcrumb, blocks: [block], start: i, end: i, page: block.pageNumber }\n }\n }\n flushRun()\n\n return chunks\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/redact.ts"],"sourcesContent":["/**\n * 한국 공문서 PII(개인정보) 탐지·마스킹 순수 로직.\n *\n * 텍스트 in → 마스킹된 텍스트 + 히트 리포트 out. 문서 파싱/patch는 기존 인프라가\n * 담당하고 여기서는 텍스트만 다룬다 (CLI/MCP 배선은 별도).\n *\n * 원칙:\n * - 서식 보존 마스킹 — 자릿수·구분자를 유지해 마스킹 전후 길이가 동일\n * - 히트 리포트에 원본 PII를 절대 담지 않는다 (`masked` 필드만 존재)\n * - 룰 우선순위 겹침 처리 — 우선순위순으로 매치를 수집하고, 이미 점유된\n * 구간과 겹치는 하위 룰 매치는 스킵 (RULE_PRIORITY 참조)\n * - 정규식은 모듈 로드 시 1회 컴파일 (matchAll은 내부 클론이라 lastIndex 안전)\n */\n\nexport type RedactRule = \"rrn\" | \"phone\" | \"email\" | \"card\" | \"account\" | \"passport\" | \"driver\"\n\nexport interface RedactHit {\n rule: RedactRule\n /** 마스킹 후 문자열 — 원본 PII는 리포트에 담지 않는다 */\n masked: string\n /** 원문 내 시작 오프셋 (UTF-16 단위) */\n index: number\n /** 매치 길이 (서식 보존이라 마스킹 전후 동일) */\n length: number\n}\n\nexport interface RedactTextResult {\n text: string\n hits: RedactHit[]\n}\n\nexport interface RedactOptions {\n /** 적용할 룰 (기본: DEFAULT_REDACT_RULES — passport·driver는 기본 OFF) */\n rules?: RedactRule[]\n /** 마스크 문자 — 1글자(UTF-16 1단위), 영숫자 금지. 기본 \"●\" (마크다운 특수문자 아님) */\n maskChar?: string\n}\n\n/** 기본 적용 룰 — passport(여권)·driver(운전면허)는 오탐 여지가 있어 opt-in */\nexport const DEFAULT_REDACT_RULES: readonly RedactRule[] = [\n \"rrn\",\n \"phone\",\n \"email\",\n \"card\",\n \"account\",\n]\n\n/**\n * 룰 우선순위 (앞이 높음). 스펙 요구는 rrn > card > phone > account —\n * 나머지는 포섭 관계로 배치: email을 phone 앞에(로컬파트 숫자에 전화 패턴 오탐 방지),\n * driver를 account 앞에(면허번호 12자리 4그룹이 계좌 패턴에 포섭됨).\n */\nconst RULE_PRIORITY: readonly RedactRule[] = [\n \"rrn\",\n \"email\",\n \"card\",\n \"phone\",\n \"driver\",\n \"account\",\n \"passport\",\n]\n\ninterface RuleDef {\n pattern: RegExp\n /** 오탐 필터 — false면 매치 스킵 */\n validate?: (m: RegExpMatchArray) => boolean\n /** 매치 → 같은 길이의 마스킹 문자열 */\n mask: (m: RegExpMatchArray, mc: string) => string\n}\n\n/** Luhn 체크섬 (카드번호 오탐 축소) */\nfunction luhnValid(digits: string): boolean {\n let sum = 0\n for (let i = 0; i < digits.length; i++) {\n let d = digits.charCodeAt(digits.length - 1 - i) - 48\n if (i % 2 === 1) {\n d *= 2\n if (d > 9) d -= 9\n }\n sum += d\n }\n return sum % 10 === 0\n}\n\n/** 주민번호 앞 6자리(YYMMDD)의 월 01-12, 일 01-31 검증 */\nfunction birthdateValid(front6: string): boolean {\n const mm = Number(front6.slice(2, 4))\n const dd = Number(front6.slice(4, 6))\n return mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31\n}\n\n// 전체 정규식은 모듈 로드 시 1회 컴파일\nconst RULES: Record<RedactRule, RuleDef> = {\n // 주민/외국인등록번호 — 뒷자리 첫 숫자 1-8 + 생년월일 유효성으로 오탐 축소.\n // 유니코드 대시 변형(‐ ‑ – —)은 rrn만 허용. 앞 6자리 유지, 뒤 7자리 전부 마스크.\n rrn: {\n pattern: /(?<!\\d)(\\d{6})([-‐‑–—])([1-8]\\d{6})(?!\\d)/g,\n validate: (m) => birthdateValid(m[1]),\n mask: (m, mc) => m[1] + m[2] + mc.repeat(7),\n },\n // 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지\n email: {\n pattern: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g,\n mask: (m, mc) => {\n const at = m[0].indexOf(\"@\")\n return m[0][0] + mc.repeat(at - 1) + m[0].slice(at)\n },\n },\n // 카드번호 — 구분자 필수(무구분 16자리는 오탐 높아 제외), 동일 구분자 강제(\\2),\n // Luhn 체크. 가운데 8자리 마스크.\n card: {\n pattern: /(?<!\\d)(\\d{4})([- ])(\\d{4})\\2(\\d{4})\\2(\\d{4})(?!\\d)/g,\n validate: (m) => luhnValid(m[1] + m[3] + m[4] + m[5]),\n mask: (m, mc) => m[1] + m[2] + mc.repeat(4) + m[2] + mc.repeat(4) + m[2] + m[5],\n },\n // 전화번호 — 휴대폰(01[016789])·서울(02)·지역(0[3-6]\\d)·인터넷(070)은 구분자\n // -·.·공백 또는 무구분(동일 구분자 강제), 대표번호(15xx/16xx/18xx)는 구분자 필수.\n // 가운데 자리만 마스크 (대표번호는 뒤 4자리). 선행 [\\d-] 금지 — 계좌 부분매치 방지.\n phone: {\n pattern:\n /(?<![\\d-])(?:(01[016789]|070|02|0[3-6]\\d)([-. ]?)(\\d{3,4})\\2(\\d{4})|(1[568]\\d{2})([-. ])(\\d{4}))(?!\\d)/g,\n mask: (m, mc) =>\n m[1] !== undefined\n ? m[1] + m[2] + mc.repeat(m[3].length) + m[2] + m[4]\n : m[5] + m[6] + mc.repeat(4),\n },\n // 운전면허 (기본 OFF) — 신형 12자리만 (지역명 2글자 선행 구버전은 스킵). 뒷 8자리 마스크.\n driver: {\n pattern: /(?<![\\d-])(\\d{2})-(\\d{2})-\\d{6}-\\d{2}(?!-?\\d)/g,\n mask: (m, mc) => m[1] + \"-\" + m[2] + \"-\" + mc.repeat(6) + \"-\" + mc.repeat(2),\n },\n // 계좌번호 — 3~4그룹 + 총 자릿수 10~16. rrn·card·phone과 겹치면 그쪽 우선.\n // 마지막 그룹 빼고 전부 마스크. 사업자등록번호(3-2-5, 10자리)도 걸린다 — 계약상 의도\n // (테스트로 명시). 날짜(2026-07-16)는 8자리라 총자릿수 검증에서 탈락.\n account: {\n pattern: /(?<!\\d)(?<!\\d-)\\d{2,6}(?:-\\d{2,6}){1,2}-\\d{2,8}(?!-?\\d)/g,\n validate: (m) => {\n const digits = m[0].replace(/-/g, \"\").length\n return digits >= 10 && digits <= 16\n },\n mask: (m, mc) => {\n const parts = m[0].split(\"-\")\n return parts.map((p, i) => (i === parts.length - 1 ? p : mc.repeat(p.length))).join(\"-\")\n },\n },\n // 여권번호 (기본 OFF) — 단어 경계, 첫 글자만 남기고 전부 마스크\n passport: {\n pattern: /\\b([MSRODG])\\d{8}(?![0-9A-Za-z])/g,\n mask: (m, mc) => m[1] + mc.repeat(8),\n },\n}\n\n/**\n * 텍스트에서 PII를 탐지해 서식 보존 마스킹.\n *\n * @param text - 대상 텍스트 (마크다운 포함)\n * @param options - 룰 선택·마스크 문자 (기본: rrn·phone·email·card·account, \"●\")\n * @returns 마스킹된 텍스트 + 히트 리포트 (index 오름차순, 원본 PII 미포함)\n */\nexport function redactText(text: string, options?: RedactOptions): RedactTextResult {\n const maskChar = options?.maskChar ?? \"●\"\n if (maskChar.length !== 1 || /[0-9A-Za-z]/.test(maskChar)) {\n throw new Error(`maskChar는 영숫자가 아닌 1글자여야 함: ${JSON.stringify(maskChar)}`)\n }\n\n const enabled = options?.rules ?? DEFAULT_REDACT_RULES\n const hits: RedactHit[] = []\n if (text === \"\" || enabled.length === 0) return { text, hits }\n\n // 우선순위순으로 매치 수집 — 점유 구간과 겹치는 하위 룰 매치는 스킵\n const occupied: Array<{ start: number; end: number }> = []\n for (const rule of RULE_PRIORITY) {\n if (!enabled.includes(rule)) continue\n const def = RULES[rule]\n for (const m of text.matchAll(def.pattern)) {\n if (def.validate && !def.validate(m)) continue\n const start = m.index as number\n const end = start + m[0].length\n if (occupied.some((o) => start < o.end && end > o.start)) continue\n occupied.push({ start, end })\n hits.push({ rule, masked: def.mask(m, maskChar), index: start, length: m[0].length })\n }\n }\n\n hits.sort((a, b) => a.index - b.index)\n\n let out = \"\"\n let cursor = 0\n for (const h of hits) {\n out += text.slice(cursor, h.index) + h.masked\n cursor = h.index + h.length\n }\n out += text.slice(cursor)\n return { text: out, hits }\n}\n\n/**\n * 마크다운 문서 전용 래퍼 — base64 이미지(data URI) 라인은 마스킹에서 제외한다.\n * base64 페이로드의 숫자열이 phone 등에 오탐되면 이미지가 깨지기 때문.\n * hits의 index는 문서 전체 기준 절대 오프셋으로 환산된다.\n */\nexport function redactMarkdown(markdown: string, options?: RedactOptions): RedactTextResult {\n const lines = markdown.split(\"\\n\")\n const hits: RedactHit[] = []\n let offset = 0\n const outLines = lines.map((line) => {\n if (line.includes(\"data:image/\")) {\n offset += line.length + 1\n return line\n }\n const r = redactText(line, options)\n for (const h of r.hits) hits.push({ ...h, index: h.index + offset })\n offset += line.length + 1\n return r.text\n })\n return { text: outLines.join(\"\\n\"), hits }\n}\n"],"mappings":"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","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 /** 셀/표 측정 메모 — 중첩표 지수 재계산 캡 (렌더 1회 공유) */\n extentMemo: ExtentMemo\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=\"${escapeXml(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/**\n * 셀/표 측정 메모 — drawTable(rowH·yoff)과 cellContentExtent↔measureTableHeight 상호재귀가\n * 중첩 단계마다 하위 표를 재측정해 지수적으로 불어나는 것을 캡. 렌더 1회 안에서만 공유\n * (reflow의 DOM 변형은 드로잉 시작 전에 끝나므로 캐시가 stale해지지 않는다).\n */\nexport interface ExtentMemo { cell: WeakMap<Element, number>; table: WeakMap<Element, number> }\n\n/** 셀 콘텐츠 세로 범위 — 줄(vp+th) + 인라인 개체(중첩표·treatAsChar) + PARA 앵커 개체(anchor+h) 최대값 */\nfunction cellContentExtent(cell: CellModel, memo?: ExtentMemo): number {\n if (!cell.sub) return 0\n const hit = memo?.cell.get(cell.el)\n if (hit !== undefined) return hit\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, memo)) : 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 memo?.cell.set(cell.el, ext)\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 // 음수 주소(uint32 역변환·손상 입력) 방어 — colX/rowY 인덱스 이탈로 NaN 좌표 방지\n ca: Math.max(0, num(addr, \"colAddr\")), ra: Math.max(0, 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, memo?: ExtentMemo): number {\n const hit = memo?.table.get(tbl)\n if (hit !== undefined) return hit\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, memo) : undefined })),\n nRows,\n )\n let sum = 0\n for (const h of rowH) sum += h\n memo?.table.set(tbl, sum)\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, ctx.extentMemo) : 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, ctx.extentMemo)\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\") || (bytes.length > 3 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)) return \"image/gif\"\n if (lower.endsWith(\".svg\")) return \"image/svg+xml\"\n if (bytes.length > 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return \"image/jpeg\"\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 extentMemo: { cell: new WeakMap(), table: new WeakMap() },\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 // u 플래그 필수 — 없으면 astral 문자(𝐀·이모지 등)가 서로게이트 반쪽 2개로 쪼개져 폭이 2배로 계산된다\n const units = text.match(mode === \"keep\" ? / +|[^ ]+/gu : / +|[^ ]/gu) ?? []\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"]}