kordoc 4.2.9 → 4.3.1

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/{-ECUCTQTR.js → -RH6BEVTT.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-PATPZTH7.cjs → chunk-5F5UCACZ.cjs} +6 -6
  6. package/dist/chunk-5F5UCACZ.cjs.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-GEPINOL6.js → chunk-6XPXIG57.js} +2 -2
  10. package/dist/chunk-6XPXIG57.js.map +1 -0
  11. package/dist/{chunk-4ZWKFDUC.cjs → chunk-7DZPPUQD.cjs} +44 -13
  12. package/dist/chunk-7DZPPUQD.cjs.map +1 -0
  13. package/dist/{chunk-MHMVFI2N.js → chunk-BPJSTJBN.js} +4 -4
  14. package/dist/chunk-BPJSTJBN.js.map +1 -0
  15. package/dist/{chunk-XU4VWOKL.js → chunk-BYEQDLZS.js} +4 -3
  16. package/dist/chunk-BYEQDLZS.js.map +1 -0
  17. package/dist/{chunk-3PLF5UIS.js → chunk-CD5X6UG5.js} +37 -6
  18. package/dist/chunk-CD5X6UG5.js.map +1 -0
  19. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  20. package/dist/{chunk-STYN45MS.js → chunk-FUXDD36A.js} +4 -4
  21. package/dist/chunk-FUXDD36A.js.map +1 -0
  22. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  23. package/dist/{chunk-YR4PF56B.js → chunk-GTPEKBN6.js} +2 -2
  24. package/dist/chunk-GTPEKBN6.js.map +1 -0
  25. package/dist/{chunk-27Z4PUZZ.js → chunk-HPRK67YB.js} +38 -7
  26. package/dist/chunk-HPRK67YB.js.map +1 -0
  27. package/dist/{chunk-NIISBGSR.js → chunk-HZU7Y22R.js} +71 -29
  28. package/dist/chunk-HZU7Y22R.js.map +1 -0
  29. package/dist/{chunk-7ZARSBQ3.js → chunk-LEC2HG7A.js} +4 -3
  30. package/dist/chunk-LEC2HG7A.js.map +1 -0
  31. package/dist/{chunk-H7OWEYH2.cjs → chunk-MGGU3ZRK.cjs} +1 -1
  32. package/dist/chunk-MGGU3ZRK.cjs.map +1 -0
  33. package/dist/chunk-MOL7MDBG.js +0 -0
  34. package/dist/{chunk-DZIXKL7E.js → chunk-O5EK52TG.js} +1 -1
  35. package/dist/chunk-O5EK52TG.js.map +1 -0
  36. package/dist/{chunk-LXE7OR6N.js → chunk-PV3NVBGL.js} +3 -3
  37. package/dist/chunk-PV3NVBGL.js.map +1 -0
  38. package/dist/{chunk-G3DMDTZI.js → chunk-RO4Y4BFI.js} +1511 -1135
  39. package/dist/chunk-RO4Y4BFI.js.map +1 -0
  40. package/dist/{chunk-WTT2KJOM.js → chunk-SA22NK56.js} +14 -10
  41. package/dist/chunk-SA22NK56.js.map +1 -0
  42. package/dist/{chunk-GZ7W64QU.cjs → chunk-VQB5P5KJ.cjs} +4 -3
  43. package/dist/chunk-VQB5P5KJ.cjs.map +1 -0
  44. package/dist/{chunk-7FCNOKOR.js → chunk-W34QSJFF.js} +1 -1
  45. package/dist/chunk-W34QSJFF.js.map +1 -0
  46. package/dist/{chunk-VEHC4EJL.js → chunk-YDCD4AV4.js} +5 -5
  47. package/dist/chunk-YDCD4AV4.js.map +1 -0
  48. package/dist/{chunk-NNDVFNMJ.js → chunk-YNJDNP44.js} +1 -1
  49. package/dist/chunk-YNJDNP44.js.map +1 -0
  50. package/dist/{chunk-3TSTZGCJ.js → chunk-ZJ75YDW7.js} +4 -3
  51. package/dist/chunk-ZJ75YDW7.js.map +1 -0
  52. package/dist/{chunk-6GM2QLM3.cjs → chunk-ZMKTKJZN.cjs} +21 -21
  53. package/dist/chunk-ZMKTKJZN.cjs.map +1 -0
  54. package/dist/{chunk-LM7C6HI2.js → chunk-ZSL6MQVE.js} +1 -1
  55. package/dist/chunk-ZSL6MQVE.js.map +1 -0
  56. package/dist/{chunk-KTPZYXN2.js → chunk-ZYYTU7NY.js} +2 -2
  57. package/dist/chunk-ZYYTU7NY.js.map +1 -0
  58. package/dist/chunks-JEYQWXIZ.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-PS7XCV6K.js → image-ocr-6MO4FXJK.js} +8 -6
  68. package/dist/image-ocr-6MO4FXJK.js.map +1 -0
  69. package/dist/{image-ocr-J5656BEE.cjs → image-ocr-MGVXT6XD.cjs} +13 -11
  70. package/dist/image-ocr-MGVXT6XD.cjs.map +1 -0
  71. package/dist/{image-ocr-KZTNJ62U.js → image-ocr-TWUU2RNG.js} +10 -8
  72. package/dist/image-ocr-TWUU2RNG.js.map +1 -0
  73. package/dist/index.cjs +1993 -1845
  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 +2128 -1980
  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-CSYU3CAW.cjs → parser-JFGLN4DA.cjs} +25 -25
  85. package/dist/parser-JFGLN4DA.cjs.map +1 -0
  86. package/dist/{parser-WNFWMBSL.js → parser-VR2BTUT7.js} +8 -8
  87. package/dist/parser-VR2BTUT7.js.map +1 -0
  88. package/dist/{parser-A34NAYKA.js → parser-YA7HSSPK.js} +6 -6
  89. package/dist/parser-YA7HSSPK.js.map +1 -0
  90. package/dist/pdf-ocr-4LISPZEM.cjs +13 -0
  91. package/dist/pdf-ocr-4LISPZEM.cjs.map +1 -0
  92. package/dist/pdf-ocr-AIWB2QCS.js +12 -0
  93. package/dist/pdf-ocr-QMOKUMC2.js +15 -0
  94. package/dist/{profile-io-43MF52H6.js → profile-io-QPIMGVO3.js} +3 -3
  95. package/dist/{rasterize-OG7KXBJS.js → rasterize-AFYGNKXG.js} +2 -2
  96. package/dist/rasterize-AFYGNKXG.js.map +1 -0
  97. package/dist/{redact-7ILEAUTS.js → redact-W5K3ILQH.js} +2 -2
  98. package/dist/render-PSPTMZIZ.js +10 -0
  99. package/dist/seal-4XBJ7ISE.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-SOHQMZN2.js → watch-D3JJEOAB.js} +12 -14
  103. package/dist/watch-D3JJEOAB.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-27Z4PUZZ.js.map +0 -1
  110. package/dist/chunk-3PLF5UIS.js.map +0 -1
  111. package/dist/chunk-3TSTZGCJ.js.map +0 -1
  112. package/dist/chunk-4ZWKFDUC.cjs.map +0 -1
  113. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  114. package/dist/chunk-6GM2QLM3.cjs.map +0 -1
  115. package/dist/chunk-7FCNOKOR.js.map +0 -1
  116. package/dist/chunk-7ZARSBQ3.js.map +0 -1
  117. package/dist/chunk-DZIXKL7E.js.map +0 -1
  118. package/dist/chunk-G3DMDTZI.js.map +0 -1
  119. package/dist/chunk-GEPINOL6.js.map +0 -1
  120. package/dist/chunk-GZ7W64QU.cjs.map +0 -1
  121. package/dist/chunk-H7OWEYH2.cjs.map +0 -1
  122. package/dist/chunk-J2RZ444H.js.map +0 -1
  123. package/dist/chunk-KTPZYXN2.js.map +0 -1
  124. package/dist/chunk-LM7C6HI2.js.map +0 -1
  125. package/dist/chunk-LXE7OR6N.js.map +0 -1
  126. package/dist/chunk-MHMVFI2N.js.map +0 -1
  127. package/dist/chunk-NIISBGSR.js.map +0 -1
  128. package/dist/chunk-NNDVFNMJ.js.map +0 -1
  129. package/dist/chunk-PATPZTH7.cjs.map +0 -1
  130. package/dist/chunk-STYN45MS.js.map +0 -1
  131. package/dist/chunk-TCJRGFYY.js +0 -280
  132. package/dist/chunk-TCJRGFYY.js.map +0 -1
  133. package/dist/chunk-VEHC4EJL.js.map +0 -1
  134. package/dist/chunk-WTT2KJOM.js.map +0 -1
  135. package/dist/chunk-XU4VWOKL.js.map +0 -1
  136. package/dist/chunk-YR4PF56B.js.map +0 -1
  137. package/dist/chunks-VFIMYK36.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-J5656BEE.cjs.map +0 -1
  143. package/dist/image-ocr-KZTNJ62U.js.map +0 -1
  144. package/dist/image-ocr-PS7XCV6K.js.map +0 -1
  145. package/dist/parser-A34NAYKA.js.map +0 -1
  146. package/dist/parser-CSYU3CAW.cjs.map +0 -1
  147. package/dist/parser-WNFWMBSL.js.map +0 -1
  148. package/dist/pdf-ocr-KBMWPG2L.js +0 -12
  149. package/dist/pdf-ocr-NPZG67FN.js +0 -15
  150. package/dist/pdf-ocr-O44DAGTY.cjs +0 -13
  151. package/dist/pdf-ocr-O44DAGTY.cjs.map +0 -1
  152. package/dist/rasterize-OG7KXBJS.js.map +0 -1
  153. package/dist/render-336CJHAS.js +0 -10
  154. package/dist/seal-TM2XCE2R.js +0 -10
  155. package/dist/seal-TM2XCE2R.js.map +0 -1
  156. package/dist/setup-GUQT5ELJ.js.map +0 -1
  157. package/dist/watch-SOHQMZN2.js.map +0 -1
  158. /package/dist/{-ECUCTQTR.js.map → -RH6BEVTT.js.map} +0 -0
  159. /package/dist/{chunks-VFIMYK36.js.map → chunks-JEYQWXIZ.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-AIWB2QCS.js.map} +0 -0
  162. /package/dist/{pdf-ocr-KBMWPG2L.js.map → pdf-ocr-QMOKUMC2.js.map} +0 -0
  163. /package/dist/{pdf-ocr-NPZG67FN.js.map → profile-io-QPIMGVO3.js.map} +0 -0
  164. /package/dist/{profile-io-43MF52H6.js.map → redact-W5K3ILQH.js.map} +0 -0
  165. /package/dist/{redact-7ILEAUTS.js.map → render-PSPTMZIZ.js.map} +0 -0
  166. /package/dist/{render-336CJHAS.js.map → seal-4XBJ7ISE.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/form/seal.ts","../src/roundtrip/source-map.ts","../src/roundtrip/zip-patch.ts"],"sourcesContent":["/**\n * place_seal — 도장/서명 이미지를 앵커 문구(\"(인)\"·\"서명 또는 인\" 등) 위에\n * 부유(글 앞) 배치. 표/페이지 불확장 (P6, claw-hwp placeSeal 이식).\n *\n * 원리:\n * - 앵커 문구가 든 문단을 찾아 그 문단에 <hp:pic> 부유 개체를 anchor 한다.\n * 핵심 속성: treatAsChar=\"0\" + flowWithText=\"0\" + allowOverlap=\"1\" +\n * textWrap=\"IN_FRONT_OF_TEXT\" — flowWithText=\"1\"이면 한컴이 개체 높이만큼\n * 셀/페이지를 키운다 (claw-hwp GT 검증 규칙).\n * - 수평 위치는 폰트 메트릭으로 계산: 전각(한글/CJK)=1em, ASCII·반각=0.5em,\n * em = 앵커 run charPr 높이(1/100pt). 렌더 없이 배치하고 렌더는 검증용.\n * - 가운데/오른쪽 정렬 문단(서식 셀에 흔함)은 블록 전체가 밀리므로\n * (사용가능폭 − 문단폭)의 정렬 이동분을 x에 더한다.\n * - 위치 프레임은 PARA(문단 좌상단 기준) — 한컴은 문단 위로는 못 올라가게\n * 클램프하므로, 여유 공간이 있는 표 셀에서는 줄 세로 중앙에 앉고 본문\n * 최상단 줄에서는 상단 정렬로 눕는다.\n *\n * 적용 범위: 본문/표 셀/글상자 문단 (머리말·꼬리말·각주 내 앵커는 제외).\n * ZIP 재조립은 patchZipEntries — 비변경 엔트리는 1바이트도 건드리지 않고,\n * 도장 PNG는 BinData 신규 엔트리 + manifest(opf:item) 등재로 추가한다.\n */\n\nimport JSZip from \"jszip\"\nimport { KordocError } from \"../utils.js\"\nimport {\n scanSectionXml, applySplices,\n type ScanParagraph, type ScanCell, type ScanTable, type SectionScan, type SpliceEdit,\n} from \"../roundtrip/source-map.js\"\nimport { patchZipEntries } from \"../roundtrip/zip-patch.js\"\nimport { parseRenderStyles } from \"../render/head-styles.js\"\n\nconst HU_PER_MM = 7200 / 25.4\nconst mm2hu = (mm: number): number => Math.round(mm * HU_PER_MM)\n\n/** 도장 배치 요청 */\nexport interface SealOp {\n /** 앵커 문구 (예: \"(인)\", \"서명 또는 인\") */\n anchor: string\n /** 같은 앵커가 여럿일 때 0-based 선택 (기본 0) */\n occurrence?: number\n /** 도장/서명 이미지 바이트 (투명 배경 PNG 권장) */\n image: Uint8Array\n /** 이미지 확장자 (기본 png) */\n ext?: \"png\" | \"jpg\" | \"jpeg\" | \"bmp\" | \"gif\"\n /** 도장 한 변 크기 mm (기본: 줄높이×1.6, 7~18mm 클램프) */\n sizeMm?: number\n /** overlap=문구 위에 겹침, right=문구 오른쪽 옆, auto=공간 있으면 right (기본 auto) */\n mode?: \"overlap\" | \"right\" | \"auto\"\n /** 미세조정 mm */\n dxMm?: number\n dyMm?: number\n}\n\n/** 배치 결과 (도장 1개당 1건) */\nexport interface SealPlacement {\n anchor: string\n occurrence: number\n sectionIndex: number\n mode: \"overlap\" | \"right\"\n posXMm: number\n posYMm: number\n sizeMm: number\n /** ZIP에 추가된 이미지 파트 경로 (BinData/imageN.ext) */\n entry: string\n /** 근사 한계 경고 — 탭·줄바꿈 문단(seal-8), 중첩표(seal-2), 글상자(seal-3). 실측 전 근사. */\n warnings?: string[]\n}\n\nexport interface PlaceSealResult {\n buffer: ArrayBuffer\n placed: SealPlacement[]\n}\n\nconst MIME: Record<string, string> = {\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n bmp: \"image/bmp\",\n gif: \"image/gif\",\n}\n\n/** 이미지 매직바이트가 확장자와 일치하는지 — .png 이름의 비-PNG(HTML 에러페이지·텍스트)가\n * BinData 에 매장되고 image/png 로 등재되는 것을 차단 */\nfunction imageMagicMatches(buf: Uint8Array, ext: string): boolean {\n switch (ext) {\n case \"png\": return buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47\n case \"jpg\": case \"jpeg\": return buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff\n case \"bmp\": return buf.length >= 2 && buf[0] === 0x42 && buf[1] === 0x4d\n case \"gif\": return buf.length >= 6 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46\n default: return false\n }\n}\n\n/** 코드 유닛 하나의 시각 폭(em) — CJK/한글/전각=1, ASCII·반각(공백 포함)=0.5, 제어=0 */\nfunction glyphEm(code: number): number {\n if (code < 0x20) return 0\n if (code <= 0x7e) return 0.5\n if (code >= 0xff61 && code <= 0xffdc) return 0.5 // 반각 가나/자모\n return 1.0\n}\n\n/** 문자열의 폰트 메트릭 폭(mm) */\nfunction measureMm(text: string, emMm: number): number {\n let w = 0\n for (const ch of text) w += glyphEm(ch.codePointAt(0) ?? 0) * emMm\n return w\n}\n\n/** 문단이 속한 셀·표 (앵커 문단 → 셀 폭·좌측 오프셋·정렬 이동 계산용) */\ninterface ParaSite {\n para: ScanParagraph\n cell?: ScanCell\n table?: ScanTable\n /** 중첩표(depth>0) 셀에 속한 문단 — seal-2 근사 한계 경고용 */\n nested?: boolean\n /** 바깥(조상) 셀 체인 — 중첩표 앵커 가로 원점에 바깥 셀 좌측 오프셋 가산용 (seal-2) */\n outer?: Array<{ cell: ScanCell; table: ScanTable }>\n}\n\n/** 섹션의 본문+셀+글상자 문단을 문서 순서로 평탄화 (머리말·각주 등 excluded 제외) */\nfunction collectSites(scan: SectionScan): ParaSite[] {\n const sites: ParaSite[] = []\n for (const p of scan.bodyParagraphs) sites.push({ para: p })\n const walkTables = (tables: ScanTable[], depth: number, outer: Array<{ cell: ScanCell; table: ScanTable }>): void => {\n if (depth > 16) return\n for (const t of tables) {\n for (const row of t.rows) {\n for (const cell of row) {\n for (const p of cell.paragraphs) sites.push({ para: p, cell, table: t, nested: depth > 0, outer: outer.length ? outer : undefined })\n walkTables(cell.tables, depth + 1, [...outer, { cell, table: t }])\n }\n }\n }\n }\n walkTables(scan.tables, 0, [])\n walkTables(scan.orphanTables, 0, [])\n sites.sort((a, b) => a.para.start - b.para.start)\n return sites\n}\n\n/** 여는 태그 문자열에서 속성값 추출 */\nfunction attrOf(openTag: string, name: string): string | undefined {\n const m = openTag.match(new RegExp(`\\\\b${name}=\"([^\"]*)\"`))\n return m?.[1]\n}\n\n/** para.start의 <hp:p ...> 여는 태그 전체 */\nfunction paraOpenTag(xml: string, para: ScanParagraph): string {\n const end = xml.indexOf(\">\", para.start)\n return end === -1 ? \"\" : xml.slice(para.start, end + 1)\n}\n\n/** 앵커가 든 run의 charPrIDRef·네임스페이스 프리픽스·run 닫는 위치 */\nfunction anchorRunInfo(\n xml: string,\n para: ScanParagraph,\n anchor: string,\n): { charPr: string; prefix: string; insertAt: number } | null {\n // 앵커 문자열이 통째로 들어있는 tRange 우선, 없으면 마지막 tRange (run 경계에 걸친 앵커)\n let tr = para.tRanges.find(r => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(anchor))\n if (!tr) {\n // 앵커가 run 경계에 걸침(부분 굵게·엔티티 분할) — 마지막 run 이 아니라 앵커 첫 글자가\n // 든 run(앵커 대표 크기)으로 폴백해 글자 크기 배율 드리프트를 줄인다.\n const head = anchor[0]\n tr = para.tRanges.find(r => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(head))\n ?? para.tRanges[para.tRanges.length - 1]\n }\n if (!tr) return null\n\n const before = xml.slice(Math.max(0, para.start), tr.contentStart)\n const runOpen = [...before.matchAll(/<([A-Za-z0-9]+):run\\b[^>]*>/g)].pop()\n const prefix = runOpen?.[1] ?? tr.prefix ?? \"hp\"\n const charPr = (runOpen && attrOf(runOpen[0], \"charPrIDRef\")) || \"0\"\n const close = xml.indexOf(`</${prefix}:run>`, tr.contentEnd)\n if (close === -1) return null\n return { charPr, prefix, insertAt: close + `</${prefix}:run>`.length }\n}\n\n/** 셀 <hp:tc> 안의 cellAddr 근접 창 — cellSz/cellMargin 은 cellAddr 의 형제\n * (한컴 저장본: addr→span→sz→margin→subList, kordoc 생성본: subList→addr→span→sz→margin) */\nfunction cellAddrWindow(xml: string, cell: ScanCell): string | null {\n if (!cell.addrTagRange) return null\n return xml.slice(cell.addrTagRange.end, cell.addrTagRange.end + 400)\n}\n\n/** 셀의 cellSz 폭(hwpunit) */\nfunction cellSzWidthHu(xml: string, cell: ScanCell): number | null {\n const win = cellAddrWindow(xml, cell)\n if (win) {\n const m = win.match(/<[A-Za-z0-9]+:cellSz\\b[^>]*\\bwidth=\"(\\d+)\"/)\n if (m) return Number(m[1])\n }\n // 폴백: 첫 문단 앞 역탐색 (addrTagRange 없는 변형 — 한컴 저장본 배치에서 유효)\n const firstPara = cell.paragraphs[0]\n if (!firstPara) return null\n const upto = xml.slice(0, firstPara.start)\n const szMatch = [...upto.matchAll(/<[A-Za-z0-9]+:cellSz\\b[^>]*\\bwidth=\"(\\d+)\"[^>]*>/g)].pop()\n return szMatch ? Number(szMatch[1]) : null\n}\n\n/** 셀의 콘텐츠 폭(mm) — cellSz − cellMargin 좌우 */\nfunction cellContentWidthMm(xml: string, cell: ScanCell): number | null {\n const width = cellSzWidthHu(xml, cell)\n if (width === null) return null\n let content = width\n const win = cellAddrWindow(xml, cell)\n const mg = win?.match(/<[A-Za-z0-9]+:cellMargin\\b[^>]*>/)\n if (mg) {\n content -= Number(attrOf(mg[0], \"left\") ?? 0) + Number(attrOf(mg[0], \"right\") ?? 0)\n }\n return content > 0 ? content / HU_PER_MM : null\n}\n\n/**\n * 앵커 셀의 표 내 좌측 오프셋(mm) — 같은 행에서 colAddr 이 앞서는 셀들의 cellSz 폭 합.\n *\n * 한컴은 셀 문단에 anchor 된 front 부유(flowWithText=0) 개체의 가로 오프셋을\n * 셀이 아니라 **단(컬럼) 원점**에서 잰다(P1 실측 — PARA/COLUMN 동일). 그래서\n * 셀 내부 좌표에 셀의 좌측 오프셋을 더해야 화면상 앵커 문구를 따라간다.\n * 한계: rowSpan 이 얽힌 복잡 그리드·들여쓴 표는 근사가 어긋날 수 있다(dx 로 보정).\n */\nfunction cellLeftOffsetMm(xml: string, table: ScanTable, cell: ScanCell): number {\n if (cell.colAddr === undefined) return 0\n // 표 전체 행을 훑어 colAddr별 열폭 테이블을 구성한다 — rowSpan 으로 덮인 행의 <hp:tr>\n // 에는 선행 열 셀 엔트리가 없어(그 행만 보면 오프셋 0), 결재란 세로병합에서 도장이 한\n // 열(약 77mm) 왼쪽에 찍혔다. 다른 행에서 같은 colAddr 의 열폭을 조회해 면역시킨다.\n const colW = new Map<number, number>()\n for (const row of table.rows) {\n for (const c of row) {\n if (c.colAddr === undefined || colW.has(c.colAddr)) continue\n // colSpan>1 병합 셀은 폭이 여러 열에 걸쳐 있어 한 colAddr 에 귀속되지 않는다 — 단일\n // 열 셀로만 열폭을 확정해 가로 병합 제목행에서의 이중계상(도장 표 밖)을 막는다 (seal-1 colspan).\n if (c.colSpan > 1) continue\n const w = cellSzWidthHu(xml, c)\n if (w) colW.set(c.colAddr, w)\n }\n }\n let sum = 0\n for (let col = 0; col < cell.colAddr; col++) sum += colW.get(col) ?? 0\n return sum / HU_PER_MM\n}\n\n/** 본문 단 폭(mm) — secPr pagePr(용지 − 좌우 여백), 실패 시 150mm */\nfunction bodyColumnWidthMm(xml: string): number {\n const page = xml.match(/<[A-Za-z0-9]+:pagePr\\b[^>]*\\bwidth=\"(\\d+)\"[^>]*>/)\n const margin = xml.match(/<[A-Za-z0-9]+:margin\\b[^>]*\\bleft=\"(\\d+)\"[^>]*\\bright=\"(\\d+)\"[^>]*>/)\n if (!page) return 150\n const w = Number(page[1]) - (margin ? Number(margin[1]) + Number(margin[2]) : 0)\n return w > 0 ? w / HU_PER_MM : 150\n}\n\n/** 부유(글 앞) hp:pic XML — claw-hwp buildPic 폴백 템플릿의 float 변형 */\nfunction buildFloatPicXml(\n itemId: string,\n sizeHu: number,\n posXHu: number,\n posYHu: number,\n ids: { id: number; instid: number },\n): string {\n const w = sizeHu\n const h = sizeHu\n // xmlns:hc 자체 선언 — kordoc 생성 section0.xml 은 hc 프리픽스를 선언하지 않아\n // (한컴 저장본은 선언), pic 요소에 지역 선언해야 웰폼드가 유지된다 (중복 선언 무해).\n return (\n `<hp:pic xmlns:hc=\"http://www.hancom.co.kr/hwpml/2011/core\" id=\"${ids.id}\" zOrder=\"0\" numberingType=\"PICTURE\" textWrap=\"IN_FRONT_OF_TEXT\" textFlow=\"BOTH_SIDES\" lock=\"0\" dropcapstyle=\"None\" href=\"\" groupLevel=\"0\" instid=\"${ids.instid}\" reverse=\"0\">` +\n `<hp:offset x=\"0\" y=\"0\"/><hp:orgSz width=\"${w}\" height=\"${h}\"/><hp:curSz width=\"${w}\" height=\"${h}\"/>` +\n `<hp:flip horizontal=\"0\" vertical=\"0\"/><hp:rotationInfo angle=\"0\" centerX=\"${Math.round(w / 2)}\" centerY=\"${Math.round(h / 2)}\" rotateimage=\"1\"/>` +\n `<hp:renderingInfo><hc:transMatrix e1=\"1\" e2=\"0\" e3=\"0\" e4=\"0\" e5=\"1\" e6=\"0\"/><hc:scaMatrix e1=\"1\" e2=\"0\" e3=\"0\" e4=\"0\" e5=\"1\" e6=\"0\"/><hc:rotMatrix e1=\"1\" e2=\"0\" e3=\"0\" e4=\"0\" e5=\"1\" e6=\"0\"/></hp:renderingInfo>` +\n `<hp:imgRect><hc:pt0 x=\"0\" y=\"0\"/><hc:pt1 x=\"${w}\" y=\"0\"/><hc:pt2 x=\"${w}\" y=\"${h}\"/><hc:pt3 x=\"0\" y=\"${h}\"/></hp:imgRect>` +\n `<hp:imgClip left=\"0\" right=\"${w}\" top=\"0\" bottom=\"${h}\"/><hp:inMargin left=\"0\" right=\"0\" top=\"0\" bottom=\"0\"/>` +\n `<hp:imgDim dimwidth=\"${w}\" dimheight=\"${h}\"/>` +\n `<hc:img binaryItemIDRef=\"${itemId}\" bright=\"0\" contrast=\"0\" effect=\"REAL_PIC\" alpha=\"0\"/><hp:effects/>` +\n `<hp:sz width=\"${w}\" widthRelTo=\"ABSOLUTE\" height=\"${h}\" heightRelTo=\"ABSOLUTE\" protect=\"0\"/>` +\n // horzRelTo=COLUMN: 셀 안에서는 셀 콘텐츠 영역이 원점(한컴 실측 — v3.10 사진대지),\n // 본문에서는 단 왼쪽이 원점이라 PARA와 동일하게 동작한다. PARA를 쓰면 셀 내부\n // 개체의 가로 원점을 한컴이 표를 담은 바깥 문단으로 잡아 도장이 옆 셀로 밀린다\n // (P1 시각검증 실측). 세로는 PARA 유지 — 앵커 줄 기준 배치.\n `<hp:pos treatAsChar=\"0\" affectLSpacing=\"0\" flowWithText=\"0\" allowOverlap=\"1\" holdAnchorAndSO=\"0\" vertRelTo=\"PARA\" horzRelTo=\"COLUMN\" vertAlign=\"TOP\" horzAlign=\"LEFT\" vertOffset=\"${posYHu}\" horzOffset=\"${posXHu}\"/>` +\n `<hp:outMargin left=\"0\" right=\"0\" top=\"0\" bottom=\"0\"/><hp:shapeComment>kordoc seal</hp:shapeComment>` +\n `</hp:pic>`\n )\n}\n\n/**\n * HWPX에 도장/서명 이미지를 앵커 문구 기준으로 부유 배치한다.\n *\n * @param hwpxBuffer 원본 HWPX\n * @param ops 도장 배치 요청 (여러 개 가능 — 같은 이미지 재사용 시에도 op마다 파트가 추가됨)\n * @throws KordocError 앵커 미발견 (본문 내 등장 횟수를 메시지에 포함)\n */\nexport async function placeSealHwpx(hwpxBuffer: ArrayBuffer, ops: SealOp[]): Promise<PlaceSealResult> {\n if (ops.length === 0) throw new KordocError(\"place_seal: 배치할 도장이 없습니다\")\n const u8 = new Uint8Array(hwpxBuffer)\n const zip = await JSZip.loadAsync(hwpxBuffer)\n\n const sectionPaths = Object.keys(zip.files)\n .filter(name => /[Ss]ection\\d+\\.xml$/i.test(name))\n // 사전순 대신 숫자순 — section10 이 section2 앞에 오면 occurrence·sectionIndex 가\n // 파서(zip-sections compareSectionPaths)의 문서 순서와 어긋나 엉뚱한 위치에 날인된다.\n .sort((a, b) => (Number(a.match(/(\\d+)\\.xml$/i)?.[1] ?? 0)) - (Number(b.match(/(\\d+)\\.xml$/i)?.[1] ?? 0)))\n if (sectionPaths.length === 0) throw new KordocError(\"HWPX에서 섹션 파일을 찾을 수 없습니다\")\n\n const manifestPath = Object.keys(zip.files).find(name => /\\.hpf$/i.test(name))\n\n // 스타일 (charPr 높이 = 1/100pt, 문단 정렬)\n const headerPath = Object.keys(zip.files).find(name => /(^|\\/)header\\.xml$/i.test(name))\n const styles = headerPath ? parseRenderStyles(await zip.file(headerPath)!.async(\"text\")) : null\n\n const sectionXmls: string[] = []\n const scans: SectionScan[] = []\n for (let si = 0; si < sectionPaths.length; si++) {\n const xml = await zip.file(sectionPaths[si])!.async(\"text\")\n sectionXmls.push(xml)\n scans.push(scanSectionXml(xml, si))\n }\n const sitesBySection = scans.map(collectSites)\n\n // 기존 BinData 번호·manifest id와 충돌하지 않는 시작 번호\n const usedIds = new Set<string>()\n let manifestXml = \"\"\n if (manifestPath) {\n manifestXml = await zip.file(manifestPath)!.async(\"text\")\n for (const m of manifestXml.matchAll(/<opf:item\\b[^>]*\\bid=\"([^\"]+)\"/g)) usedIds.add(m[1])\n }\n const usedImageNums = new Set<number>()\n for (const name of Object.keys(zip.files)) {\n const m = name.match(/^BinData\\/(?:image|img)(\\d+)\\./i)\n if (m) usedImageNums.add(Number(m[1]))\n }\n const nextImageNum = (): number => {\n let n = 1\n while (usedImageNums.has(n) || usedIds.has(`image${n}`)) n++\n usedImageNums.add(n)\n return n\n }\n\n // 개체 id — 문서 내 기존 숫자 id 최댓값 다음부터 (충돌 방지·결정적)\n let maxId = 1_000_000\n for (const xml of sectionXmls) {\n for (const m of xml.matchAll(/\\b(?:id|instid)=\"(\\d+)\"/g)) {\n const v = Number(m[1])\n if (Number.isFinite(v) && v > maxId) maxId = v\n }\n }\n\n const splicesBySection: SpliceEdit[][] = sectionPaths.map(() => [])\n const additions = new Map<string, Uint8Array>()\n const manifestItems: string[] = []\n const placed: SealPlacement[] = []\n\n for (const op of ops) {\n if (!op.anchor) throw new KordocError(\"place_seal: anchor 문구가 필요합니다\")\n if (!op.image || op.image.length === 0) throw new KordocError(\"place_seal: 도장 이미지가 필요합니다\")\n const ext = (op.ext ?? \"png\").toLowerCase()\n if (!MIME[ext]) throw new KordocError(`place_seal: 지원하지 않는 이미지 확장자 .${ext} (png/jpg/bmp/gif)`)\n if (!imageMagicMatches(op.image, ext)) throw new KordocError(`place_seal: 이미지 내용이 .${ext} 형식이 아닙니다 (매직바이트 불일치)`)\n const wantOcc = op.occurrence ?? 0\n\n // 앵커 탐색 — 전 섹션 문서 순서, 문단 내 다중 등장 포함\n let found: { si: number; site: ParaSite; idxInText: number } | null = null\n let total = 0\n for (let si = 0; si < scans.length && !found; si++) {\n for (const site of sitesBySection[si]) {\n let from = 0\n for (;;) {\n const idx = site.para.text.indexOf(op.anchor, from)\n if (idx === -1) break\n if (total === wantOcc) {\n found = { si, site, idxInText: idx }\n break\n }\n total++\n from = idx + op.anchor.length\n }\n if (found) break\n }\n }\n if (!found) {\n // 전체 등장 수를 처음부터 다시 세어 에러 메시지에 (claw-hwp와 동일한 안내)\n total = 0\n for (let si = 0; si < scans.length; si++) {\n for (const site of sitesBySection[si]) {\n let from = 0\n for (;;) {\n const idx = site.para.text.indexOf(op.anchor, from)\n if (idx === -1) break\n total++\n from = idx + op.anchor.length\n }\n }\n }\n throw new KordocError(\n `place_seal: 앵커 \"${op.anchor}\" ${wantOcc}번째 등장을 찾지 못했습니다 ` +\n `(본문 내 ${total}회 등장 — occurrence 0..${Math.max(0, total - 1)})`,\n )\n }\n\n const { si, site, idxInText } = found\n const xml = sectionXmls[si]\n const run = anchorRunInfo(xml, site.para, op.anchor)\n if (!run) {\n throw new KordocError(`place_seal: 앵커 \"${op.anchor}\" 문단에서 run을 찾지 못했습니다`)\n }\n\n // 폰트 메트릭\n const charPrHeight = styles?.charPr.get(run.charPr)?.height ?? 1000 // 1/100pt\n const fontPt = charPrHeight / 100\n const emMm = (fontPt * 25.4) / 72\n const lineHMm = emMm\n const startXMm = measureMm(site.para.text.slice(0, idxInText), emMm)\n const anchorWMm = measureMm(op.anchor, emMm)\n // NaN·음수 sizeMm(CLI 비숫자·직접 호출)은 기본 크기로 폴백 — hp:sz width=\"NaN\" 등\n // OWPML 정수 속성에 NaN 문자열이 기록되던 것 원천 차단 (seal-4)\n const sizeMm = (op.sizeMm != null && Number.isFinite(op.sizeMm) && op.sizeMm > 0)\n ? op.sizeMm\n : Math.max(7, Math.min(18, lineHMm * 1.6))\n\n // 사용가능 폭 + 정렬 이동\n const availMm = (site.cell ? cellContentWidthMm(xml, site.cell) : null) ?? bodyColumnWidthMm(xml)\n const paraPrId = attrOf(paraOpenTag(xml, site.para), \"paraPrIDRef\") ?? \"0\"\n const align = styles?.paraAlign.get(paraPrId)\n let alignShiftMm = 0\n if (align === \"CENTER\" || align === \"RIGHT\") {\n const paraWMm = measureMm(site.para.text, emMm)\n alignShiftMm = align === \"CENTER\" ? (availMm - paraWMm) / 2 : availMm - paraWMm\n if (!Number.isFinite(alignShiftMm) || alignShiftMm < 0) alignShiftMm = 0\n }\n\n let mode = op.mode ?? \"auto\"\n if (mode === \"auto\") {\n mode = availMm - (alignShiftMm + startXMm + anchorWMm) >= sizeMm + 2 ? \"right\" : \"overlap\"\n }\n\n // 셀 앵커: 한컴이 front 부유 가로 오프셋을 단(컬럼) 원점에서 재므로 셀 좌측 오프셋 가산.\n // 중첩표는 바깥(조상) 셀 좌측 오프셋도 체인으로 더한다 — 실측 확정: 바깥 셀 미가산 시\n // 도장이 바깥 열 폭만큼 왼쪽(옆 셀)으로 밀린다 (seal-2, '항상 페이지 단' 모델).\n const cellShiftMm = site.cell && site.table\n ? cellLeftOffsetMm(xml, site.table, site.cell) +\n (site.outer ?? []).reduce((s, o) => s + cellLeftOffsetMm(xml, o.table, o.cell), 0)\n : 0\n const posXMm =\n (mode === \"right\" ? startXMm + anchorWMm + 2 : startXMm + anchorWMm / 2 - sizeMm / 2) +\n alignShiftMm + cellShiftMm + (op.dxMm ?? 0)\n // 줄 세로 중앙 — PARA 프레임은 문단 위로 클램프되므로 본문 최상단 줄에선 상단 정렬로 동작\n const posYMm = -(sizeMm - lineHMm) / 2 + (op.dyMm ?? 0)\n\n // 이미지 파트 + manifest 아이템\n const n = nextImageNum()\n const entry = `BinData/image${n}.${ext}`\n const itemId = `image${n}`\n usedIds.add(itemId)\n additions.set(entry, op.image)\n manifestItems.push(`<opf:item id=\"${itemId}\" href=\"${entry}\" media-type=\"${MIME[ext]}\" isEmbeded=\"1\"/>`)\n\n // 부유 pic run 삽입 (앵커 run 뒤 — PARA 프레임이라 run 위치는 배치에 영향 없음)\n const pic = buildFloatPicXml(itemId, mm2hu(sizeMm), mm2hu(posXMm), mm2hu(posYMm), {\n id: ++maxId,\n instid: ++maxId,\n })\n splicesBySection[si].push({\n start: run.insertAt,\n end: run.insertAt,\n replacement: `<${run.prefix}:run charPrIDRef=\"${run.charPr}\">${pic}</${run.prefix}:run>`,\n })\n\n // 근사 한계 경고 — 메트릭·원점 모델이 근사인 케이스를 결과에 명시(placed 소비자가 dx/dy 보정)\n const warnings: string[] = []\n const paraSeg = xml.slice(site.para.start, run.insertAt)\n if (/<[A-Za-z0-9]+:tab\\b/.test(paraSeg)) warnings.push(\"탭이 든 문단 — 도장 가로 위치가 근사값입니다 (dx 로 보정)\")\n if (/<[A-Za-z0-9]+:(lineBreak|br)\\b/.test(paraSeg)) warnings.push(\"줄바꿈이 든 문단 — 앵커가 2번째 줄 이하면 세로 위치가 어긋날 수 있습니다 (dy 로 보정)\")\n if (site.nested) warnings.push(\"중첩표(표 안 표) 셀 앵커 — 바깥 셀 오프셋을 체인으로 가산하나 셀 여백·정렬은 근사입니다 (필요 시 dx 로 보정)\")\n if (site.para.inTextbox) warnings.push(\"글상자 앵커 — 글상자 기하를 반영하지 않아 정렬 보정이 근사입니다 (dx/dy 로 보정)\")\n\n placed.push({\n anchor: op.anchor,\n occurrence: wantOcc,\n sectionIndex: si,\n mode,\n posXMm: Math.round(posXMm * 100) / 100,\n posYMm: Math.round(posYMm * 100) / 100,\n sizeMm: Math.round(sizeMm * 100) / 100,\n entry,\n ...(warnings.length > 0 ? { warnings } : {}),\n })\n }\n\n // 재조립 — 변경 섹션 + manifest 교체, 이미지 파트 추가\n const encoder = new TextEncoder()\n const replacements = new Map<string, Uint8Array>()\n for (let si = 0; si < sectionPaths.length; si++) {\n if (splicesBySection[si].length === 0) continue\n replacements.set(sectionPaths[si], encoder.encode(applySplices(sectionXmls[si], splicesBySection[si])))\n }\n if (manifestPath && manifestItems.length > 0) {\n const patched = manifestXml.includes(\"</opf:manifest>\")\n ? manifestXml.replace(\"</opf:manifest>\", `${manifestItems.join(\"\")}</opf:manifest>`)\n : manifestXml\n if (patched !== manifestXml) replacements.set(manifestPath, encoder.encode(patched))\n }\n\n const out = patchZipEntries(u8, replacements, additions)\n return {\n buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength) as ArrayBuffer,\n placed,\n }\n}\n","/**\n * HWPX 라운드트립 소스맵 — section XML 문자열에서 문단/표 셀의 문자 범위를 추적.\n *\n * DOM 재직렬화를 거치지 않고(속성 순서·공백·자기닫힘 표기까지 보존하기 위해)\n * 정규식 토크나이저로 태그를 순회하며, 각 <hp:p>가 소유한 <hp:t> 콘텐츠 범위를\n * 기록한다. 패치는 이 범위에 대한 문자열 splice로만 수행되어\n * 변경 문단 외 XML 바이트가 그대로 보존된다 (filler-hwpx.ts 패턴의 오프셋 버전).\n */\n\n// ─── 타입 ───────────────────────────────────────────\n\n/** <hp:t> 콘텐츠 범위 — [contentStart, contentEnd) 가 여는/닫는 태그 사이 */\nexport interface TRange {\n contentStart: number\n contentEnd: number\n /** 자기닫힘 <hp:t/> — 범위가 태그 전체이며, 치환 시 태그째 교체 */\n selfClosing?: boolean\n /** 자기닫힘 치환용 네임스페이스 프리픽스 (예: \"hp\") */\n prefix?: string\n}\n\nexport type ScanParaKind = \"body\" | \"cell\" | \"draw\" | \"excluded\"\n\n/** 스캔된 문단 — 소유한 hp:t 범위와 합산 텍스트 */\nexport interface ScanParagraph {\n sectionIndex: number\n kind: ScanParaKind\n /** <hp:p ...> 여는 태그 시작 위치 (문서 순서 정렬용) */\n start: number\n /** 소유 hp:t 콘텐츠 범위 (문서 순서) */\n tRanges: TRange[]\n /** hp:t 텍스트 합산 (엔티티 디코딩, 내부 태그는 공백화) */\n text: string\n /** hp:t가 하나도 없을 때 텍스트 삽입 가능한 위치 (첫 run 닫는 태그 앞) */\n runInsertPos?: number\n /** runInsertPos에 삽입할 때 쓸 t 태그 prefix (예: \"hp\") */\n runPrefix?: string\n /** 자기닫힘 <hp:run/> 태그 범위 — t 삽입 시 펼쳐서 사용 (한컴 빈 문단 패턴) */\n selfCloseRun?: { start: number; end: number }\n /** 글상자(drawText) 경유 문단 — 셀 라벨 판정 등에서 본문과 구분 (v3.1) */\n inTextbox?: boolean\n}\n\n/** 스캔된 표 셀 — 앵커 좌표와 셀 내부 문단 */\nexport interface ScanCell {\n rowAddr?: number\n colAddr?: number\n colSpan: number\n rowSpan: number\n paragraphs: ScanParagraph[]\n /** 셀 내부 중첩표 (문서 순서) */\n tables: ScanTable[]\n /** <hp:cellAddr> 태그 범위 (자기닫힘=태그 전체, 펼친형=여는 태그) — rowAddr 재작성용 */\n addrTagRange?: { start: number; end: number }\n}\n\n/** 스캔된 표 */\nexport interface ScanTable {\n sectionIndex: number\n start: number\n /** 최상위 표 여부 (다른 표/ctrl/캡션 내부가 아님) */\n topLevel: boolean\n /** 비어있지 않은 행들 (tr 순서) */\n rows: ScanCell[][]\n /** rows[i]의 <hp:tr>...</hp:tr> XML 범위 (행 추가/삭제 splice용, rows와 정렬) */\n rowRanges: { start: number; end: number }[]\n /** 앵커 좌표 → 셀 (\"r,c\") */\n cellByAnchor: Map<string, ScanCell>\n}\n\n/** 섹션 하나의 스캔 결과 */\nexport interface SectionScan {\n sectionIndex: number\n xml: string\n /** body + draw 문단 (문서 순서) — 본문 텍스트 매핑 대상 */\n bodyParagraphs: ScanParagraph[]\n /** 최상위 표 (문서 순서) */\n tables: ScanTable[]\n /** 머리말/꼬리말 문단 텍스트 (OB 앞/뒤 배치 블록 식별용) */\n headerTexts: string[]\n footerTexts: string[]\n /**\n * 파서 비가시 영역(머리말/꼬리말/각주/캡션/메모 등 ctrl 내부) 문단 — v3.1.\n * patcher 매핑 대상이 아니며, filler가 이 영역의 인라인 패턴을 채울 때 사용.\n */\n excludedParagraphs: ScanParagraph[]\n /**\n * 어느 셀에도 속하지 않는 비최상위 표(머리말/꼬리말 등 ctrl 내부) — v3.1.\n * filler 전용. patcher의 표 서수 매핑(tables)에는 포함되지 않는다.\n */\n orphanTables: ScanTable[]\n}\n\n/** 문자열 splice 편집 — [start, end) 를 replacement로 치환 */\nexport interface SpliceEdit {\n start: number\n end: number\n replacement: string\n}\n\n// ─── XML 텍스트 유틸 ─────────────────────────────────\n\n/** XML 텍스트 노드 이스케이프 (속성 아님 — & < > 만).\n * XML 1.0 불법 C0 제어문자는 제거한다(\\t \\n \\r 보존) — 문서를 못 여는 손상 방지 */\nexport function escapeXmlText(text: string): string {\n return text.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g, \"\")\n .replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n}\n\n/** XML 엔티티 디코딩 */\nexport function decodeXmlEntities(text: string): string {\n return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent: string) => {\n switch (ent) {\n case \"lt\": return \"<\"\n case \"gt\": return \">\"\n case \"amp\": return \"&\"\n case \"quot\": return '\"'\n case \"apos\": return \"'\"\n }\n try {\n const code = ent[1] === \"x\" || ent[1] === \"X\"\n ? parseInt(ent.slice(2), 16)\n : parseInt(ent.slice(1), 10)\n if (!isNaN(code) && code >= 0 && code <= 0x10ffff) return String.fromCodePoint(code)\n } catch { /* 잘못된 참조는 원본 유지 */ }\n return m\n })\n}\n\n/** hp:t 콘텐츠에서 텍스트 추출 — 파서가 공백류로 취급하는 태그(tab/fwSpace/hwSpace/\n * br/lineBreak)만 공백, 나머지 태그(markpen 등 파서가 무시)는 제거 (파서 모델 정합) */\nfunction tContentToText(raw: string): string {\n return decodeXmlEntities(\n raw\n .replace(/<\\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\\s[^>]*)?\\/?>/g, \" \")\n .replace(/<[^>]*>/g, \"\"),\n )\n}\n\n// ─── 토크나이저 ──────────────────────────────────────\n\nconst TAG_RE = /<!--[\\s\\S]*?-->|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|<\\?[\\s\\S]*?\\?>|<!(?:\"[^\"]*\"|'[^']*'|[^>\"'])*>|<\\/([^\\s>]+)\\s*>|<([^\\s/>!?]+)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*?)(\\/?)>/g\n\n/** hp:t 소유권을 끊는 컨테이너 — 이 안의 t는 호스트 문단 텍스트가 아님 */\nconst T_BARRIER = new Set([\n \"tbl\", \"ctrl\", \"caption\", \"pic\", \"shape\", \"drawingObject\", \"drawText\",\n \"shapeComment\", \"memogroup\", \"memo\", \"hiddenComment\", \"equation\",\n \"parameters\", \"subList\", \"p\",\n])\n\n/** 문단 분류용 컨테이너 (가장 가까운 것 기준) */\nconst PARA_CONTAINER = new Set([\n \"tc\", \"ctrl\", \"caption\", \"drawText\", \"pic\", \"shape\", \"drawingObject\",\n \"memogroup\", \"memo\", \"hiddenComment\",\n \"footNote\", \"endNote\", \"fn\", \"en\", // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수\n])\n\n/** 표 분류 시 \"최상위\"가 아니게 만드는 조상 */\nconst TABLE_BARRIER = new Set([\n \"tbl\", \"ctrl\", \"caption\", \"memogroup\", \"memo\", \"hiddenComment\",\n])\n\nfunction localOf(qname: string): string {\n const i = qname.indexOf(\":\")\n return i >= 0 ? qname.slice(i + 1) : qname\n}\n\nfunction prefixOf(qname: string): string {\n const i = qname.indexOf(\":\")\n return i >= 0 ? qname.slice(0, i) : \"\"\n}\n\ninterface StackFrame {\n local: string\n qname: string\n /** 여는 태그가 끝나는 위치 (콘텐츠 시작) */\n contentStart: number\n}\n\ninterface OpenCtrlSub { kind: \"header\" | \"footer\"; texts: string[] }\n\n/**\n * section XML 한 개를 스캔하여 소스맵을 만든다.\n * 토크나이저는 xmldom과 무관하게 동작하며, 위치(오프셋) 정보가 핵심이다.\n */\nexport function scanSectionXml(xml: string, sectionIndex: number): SectionScan {\n const stack: StackFrame[] = []\n const bodyParagraphs: ScanParagraph[] = []\n const tables: ScanTable[] = []\n const headerTexts: string[] = []\n const footerTexts: string[] = []\n const excludedParagraphs: ScanParagraph[] = []\n const orphanTables: ScanTable[] = []\n\n // 진행 상태\n const paraStack: ScanParagraph[] = []\n const tableStack: ScanTable[] = []\n const rowStack: ScanCell[][] = [] // 테이블별 currentRow\n const trStartStack: number[] = [] // 테이블별 진행 중 <hp:tr> 시작 위치 (-1 = 없음)\n const cellStack: ScanCell[] = []\n let pendingT: { para: ScanParagraph; contentStart: number } | null = null\n const ctrlSubStack: OpenCtrlSub[] = []\n\n /** 스택 위에서 아래로 보며 가장 가까운 PARA_CONTAINER/장벽 판별.\n * drawText는 투과 — 바깥에 tc가 있으면(셀 안 글상자) 파서의 mergeBlocksIntoCell과\n * 동일하게 cell로 귀속, 아니면 draw(본문 글상자). */\n const classifyPara = (): { kind: ScanParaKind; inTextbox: boolean } => {\n let sawDrawText = false\n for (let i = stack.length - 1; i >= 0; i--) {\n const l = stack[i].local\n if (l === \"tc\") return { kind: \"cell\", inTextbox: sawDrawText }\n if (l === \"drawText\") { sawDrawText = true; continue }\n if (PARA_CONTAINER.has(l)) return { kind: \"excluded\", inTextbox: sawDrawText }\n }\n return sawDrawText ? { kind: \"draw\", inTextbox: true } : { kind: \"body\", inTextbox: false }\n }\n\n /** 현재 위치의 t/tab 등이 최상위 열린 문단 소유인지 (사이에 장벽 없음) */\n const owningPara = (): ScanParagraph | null => {\n if (paraStack.length === 0) return null\n for (let i = stack.length - 1; i >= 0; i--) {\n const l = stack[i].local\n if (l === \"p\") return paraStack[paraStack.length - 1]\n if (T_BARRIER.has(l)) return null\n }\n return null\n }\n\n const isTableTopLevel = (): boolean => {\n for (let i = stack.length - 1; i >= 0; i--) {\n if (TABLE_BARRIER.has(stack[i].local)) return false\n }\n return true\n }\n\n /** ctrl 내부 header/footer 수집 컨텍스트 */\n const currentCtrlSub = (): OpenCtrlSub | null =>\n ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null\n\n TAG_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = TAG_RE.exec(xml)) !== null) {\n const [full, closeName, openName, , selfClose] = m\n\n // 주석/CDATA/PI/DOCTYPE\n if (closeName === undefined && openName === undefined) continue\n\n if (closeName !== undefined) {\n // ── 닫는 태그 ──\n const local = localOf(closeName)\n\n // 진행 중인 hp:t 닫힘\n if (local === \"t\" && pendingT) {\n const { para, contentStart } = pendingT\n para.tRanges.push({ contentStart, contentEnd: m.index })\n para.text += tContentToText(xml.slice(contentStart, m.index))\n pendingT = null\n }\n\n // 스택 pop (불일치 태그 관용 처리 — 일치하는 프레임까지 pop)\n for (let i = stack.length - 1; i >= 0; i--) {\n if (stack[i].local === local) {\n stack.length = i\n break\n }\n }\n\n if (local === \"p\") {\n const para = paraStack.pop()\n if (para && para.kind === \"excluded\") {\n const sub = currentCtrlSub()\n if (sub && para.text.trim()) sub.texts.push(para.text)\n }\n } else if (local === \"tc\") {\n const cell = cellStack.pop()\n const row = rowStack[rowStack.length - 1]\n if (cell && row) row.push(cell)\n } else if (local === \"tr\") {\n const row = rowStack[rowStack.length - 1]\n const table = tableStack[tableStack.length - 1]\n if (row && table && row.length > 0) {\n table.rows.push(row)\n const trStart = trStartStack[trStartStack.length - 1]\n if (trStart >= 0) table.rowRanges.push({ start: trStart, end: m.index + full.length })\n }\n if (rowStack.length > 0) rowStack[rowStack.length - 1] = []\n if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = -1\n } else if (local === \"tbl\") {\n const table = tableStack.pop()\n rowStack.pop()\n trStartStack.pop()\n if (table) {\n finalizeTable(table)\n // 중첩표는 둘러싼 셀에 부착 (재귀 패치용), 셀이 없으면(머리말 등\n // ctrl 내부) orphanTables로 — filler가 채우기 대상에 포함 (v3.1)\n if (!table.topLevel) {\n const cell = cellStack[cellStack.length - 1]\n if (cell) cell.tables.push(table)\n else orphanTables.push(table)\n }\n }\n } else if (local === \"header\" || local === \"footer\") {\n const sub = ctrlSubStack[ctrlSubStack.length - 1]\n if (sub) {\n ctrlSubStack.pop()\n // 파서(collectSubListText)는 문단들을 \"\\n\"으로 합쳐 한 블록으로 만든다\n const joined = sub.texts.join(\"\\n\").trim()\n if (joined) (sub.kind === \"header\" ? headerTexts : footerTexts).push(joined)\n }\n }\n continue\n }\n\n // ── 여는 태그 ──\n const qname = openName!\n const local = localOf(qname)\n const attrsRaw = m[3] || \"\"\n const isSelfClose = selfClose === \"/\"\n const contentStart = m.index + full.length\n\n if (isSelfClose) {\n // 자기닫힘 요소 처리\n if (local === \"t\") {\n const para = owningPara()\n if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) })\n } else if (local === \"tab\" || local === \"fwSpace\" || local === \"hwSpace\" || local === \"br\" || local === \"lineBreak\") {\n // pendingT 내부면 t 콘텐츠 추출이 공백 처리하므로 중복 방지\n if (!pendingT) {\n const para = owningPara()\n if (para) para.text += \" \"\n }\n } else if (local === \"run\" || local === \"r\") {\n // 한컴 빈 문단 패턴: <hp:run charPrIDRef=\"...\"/> — t 삽입 시 펼칠 수 있도록 기록\n const para = owningPara()\n if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length }\n } else if (local === \"cellAddr\") {\n const cell = cellStack[cellStack.length - 1]\n if (cell && insideCurrentTable(stack, tableStack)) {\n const ca = parseInt(getAttr(attrsRaw, \"colAddr\") || \"\", 10)\n const ra = parseInt(getAttr(attrsRaw, \"rowAddr\") || \"\", 10)\n if (!isNaN(ca)) cell.colAddr = ca\n if (!isNaN(ra)) cell.rowAddr = ra\n cell.addrTagRange = { start: m.index, end: m.index + full.length }\n }\n } else if (local === \"cellSpan\") {\n const cell = cellStack[cellStack.length - 1]\n if (cell && insideCurrentTable(stack, tableStack)) {\n const cs = parseInt(getAttr(attrsRaw, \"colSpan\") || \"1\", 10)\n const rs = parseInt(getAttr(attrsRaw, \"rowSpan\") || \"1\", 10)\n cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs\n cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs\n }\n }\n continue\n }\n\n // 일반 여는 태그 — 스택 push 전 처리\n if (local === \"t\") {\n const para = owningPara()\n if (para) pendingT = { para, contentStart }\n stack.push({ local, qname, contentStart })\n continue\n }\n\n stack.push({ local, qname, contentStart })\n\n if (local === \"p\") {\n const para: ScanParagraph = {\n sectionIndex,\n kind: \"excluded\", // 분류는 push 직후 스택 기준 (자기 자신 제외)\n start: m.index,\n tRanges: [],\n text: \"\",\n }\n // 자기 자신(p)을 제외하고 분류\n stack.pop()\n const cls = classifyPara()\n para.kind = cls.kind\n if (cls.inTextbox) para.inTextbox = true\n stack.push({ local, qname, contentStart })\n paraStack.push(para)\n if (para.kind === \"body\" || para.kind === \"draw\") bodyParagraphs.push(para)\n else if (para.kind === \"cell\") {\n const cell = cellStack[cellStack.length - 1]\n if (cell) cell.paragraphs.push(para)\n } else if (para.kind === \"excluded\") {\n excludedParagraphs.push(para)\n }\n } else if (local === \"run\" || local === \"r\") {\n // hp:t가 없는 문단의 삽입 지점 후보 — 첫 run의 닫는 태그 위치는 닫힐 때 알 수 있으므로\n // 여기서는 prefix만 기억해두고, 닫는 태그에서 채움 (아래 참조)\n const para = owningPara()\n if (para && para.runPrefix === undefined) para.runPrefix = prefixOf(qname)\n } else if (local === \"tbl\") {\n const table: ScanTable = {\n sectionIndex,\n start: m.index,\n topLevel: false,\n rows: [],\n rowRanges: [],\n cellByAnchor: new Map(),\n }\n // 자기 자신 제외하고 판별\n stack.pop()\n table.topLevel = isTableTopLevel()\n stack.push({ local, qname, contentStart })\n tableStack.push(table)\n rowStack.push([])\n trStartStack.push(-1)\n if (table.topLevel) tables.push(table)\n } else if (local === \"tr\") {\n if (rowStack.length > 0) rowStack[rowStack.length - 1] = []\n if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = m.index\n } else if (local === \"tc\") {\n cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] })\n } else if (local === \"cellAddr\" || local === \"cellSpan\") {\n // 자기닫힘이 일반적이지만 펼친 형태(<hp:cellAddr ...></hp:cellAddr>)도 동일 처리\n const cell = cellStack[cellStack.length - 1]\n if (cell && insideCurrentTable(stack, tableStack)) {\n if (local === \"cellAddr\") {\n const ca = parseInt(getAttr(attrsRaw, \"colAddr\") || \"\", 10)\n const ra = parseInt(getAttr(attrsRaw, \"rowAddr\") || \"\", 10)\n if (!isNaN(ca)) cell.colAddr = ca\n if (!isNaN(ra)) cell.rowAddr = ra\n cell.addrTagRange = { start: m.index, end: contentStart }\n } else {\n const cs = parseInt(getAttr(attrsRaw, \"colSpan\") || \"1\", 10)\n const rs = parseInt(getAttr(attrsRaw, \"rowSpan\") || \"1\", 10)\n cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs\n cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs\n }\n }\n } else if (local === \"header\" || local === \"footer\") {\n // ctrl 내부의 머리말/꼬리말만 수집\n if (stack.some(f => f.local === \"ctrl\")) {\n ctrlSubStack.push({ kind: local, texts: [] })\n }\n } else if (local === \"tab\" || local === \"fwSpace\" || local === \"hwSpace\" || local === \"br\" || local === \"lineBreak\") {\n const para = owningPara()\n if (para) para.text += \" \"\n }\n }\n\n // run 삽입 지점 보강: t가 없는 body/cell 문단에 대해 첫 run의 닫는 태그 위치 탐색\n for (const para of bodyParagraphs) fillRunInsertPos(para, xml)\n for (const para of excludedParagraphs) fillRunInsertPos(para, xml)\n const fillTableInsertPos = (table: ScanTable, depth = 0): void => {\n if (depth > 16) return\n for (const row of table.rows) {\n for (const cell of row) {\n for (const para of cell.paragraphs) fillRunInsertPos(para, xml)\n for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1)\n }\n }\n }\n for (const table of tables) fillTableInsertPos(table)\n for (const table of orphanTables) fillTableInsertPos(table)\n\n return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables }\n}\n\n/** 속성 문자열에서 값 추출 */\nfunction getAttr(attrsRaw: string, name: string): string | undefined {\n const re = new RegExp(`(?:^|\\\\s)${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)')`)\n const m = attrsRaw.match(re)\n return m ? (m[1] ?? m[2]) : undefined\n}\n\n/** 문단 스택 컨텍스트가 현재 테이블 셀 직속인지 — cellAddr/cellSpan 소유 확인 */\nfunction insideCurrentTable(stack: StackFrame[], tableStack: ScanTable[]): boolean {\n if (tableStack.length === 0) return false\n for (let i = stack.length - 1; i >= 0; i--) {\n const l = stack[i].local\n if (l === \"tc\") return true\n if (l === \"tbl\") return false\n }\n return false\n}\n\n/** hp:t 없는 문단의 삽입 위치 — 첫 <hp:run ...>...</hp:run>의 닫는 태그 직전 */\nfunction fillRunInsertPos(para: ScanParagraph, xml: string): void {\n if (para.tRanges.length > 0) return\n // 문단 범위 내 첫 run 탐색 (문단 닫는 태그까지 — 간단히 다음 </hp:p>까지로 한정)\n const pEnd = findElementEnd(xml, para.start)\n if (pEnd < 0) return\n const slice = xml.slice(para.start, pEnd)\n const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\\s(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?)?(\\/?)>/)\n if (!runOpen || runOpen.index === undefined) return\n if (runOpen[2] === \"/\") return // 자기닫힘 run — v1 미지원\n const qname = runOpen[1]\n const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index)\n if (closeIdx < 0) return\n para.runInsertPos = para.start + closeIdx\n para.runPrefix = prefixOf(qname)\n}\n\n/** start 위치의 여는 태그에 대응하는 닫는 태그 뒤 위치 (동일 로컬명 중첩 추적) */\nexport function findElementEnd(xml: string, start: number): number {\n const open = xml.slice(start).match(/^<([^\\s/>!?]+)/)\n if (!open) return -1\n const qname = open[1]\n const re = new RegExp(`<${qname}(?=[\\\\s/>])(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?(/?)>|</${qname}\\\\s*>`, \"g\")\n re.lastIndex = start\n let depth = 0\n let mm: RegExpExecArray | null\n while ((mm = re.exec(xml)) !== null) {\n if (mm[0].startsWith(\"</\")) {\n depth--\n if (depth === 0) return mm.index + mm[0].length\n } else if (mm[1] !== \"/\") {\n depth++\n }\n }\n return -1\n}\n\n/** 표 마무리 — 앵커 좌표 맵 구축 (cellAddr 없으면 first-fit, builder.ts buildTable과 동일 규칙) */\nfunction finalizeTable(table: ScanTable): void {\n const hasAddr = table.rows.some(row => row.some(c => c.colAddr !== undefined && c.rowAddr !== undefined))\n if (hasAddr) {\n for (const row of table.rows) {\n for (const cell of row) {\n if (cell.rowAddr !== undefined && cell.colAddr !== undefined) {\n table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell)\n }\n }\n }\n return\n }\n // first-fit 배치 (builder.ts buildTable Pass1/2와 동일한 점유 규칙)\n const numRows = table.rows.length\n const occupied: boolean[][] = Array.from({ length: numRows }, () => [])\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\n let colIdx = 0\n for (const cell of table.rows[rowIdx]) {\n while (occupied[rowIdx][colIdx]) colIdx++\n cell.rowAddr = rowIdx\n cell.colAddr = colIdx\n table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell)\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\n for (let c = colIdx; c < colIdx + cell.colSpan; c++) {\n occupied[r][c] = true\n }\n }\n colIdx += cell.colSpan\n }\n }\n}\n\n// ─── 치환 ────────────────────────────────────────────\n\n/**\n * 문단 텍스트 치환용 splice 생성 — filler-hwpx.ts replaceCellText와 동일 전략:\n * 첫 hp:t에 새 텍스트(이스케이프), 나머지 hp:t는 비움. run 구조/charPr 보존.\n * t가 없으면 첫 run 끝에 <hp:t> 삽입. 실패 시 null.\n *\n * IR 텍스트는 sanitize로 양끝 공백이 제거된 상태라, 통째 교체 시 원본의\n * 선행 공백(들여쓰기)/후행 공백이 소실된다 — 원본 t-도메인 텍스트에서\n * 양끝 공백을 복원해 새 텍스트에 입힌다 (재파싱 IR은 동일하게 유지됨).\n */\nexport function buildParagraphSplices(para: ScanParagraph, newText: string, xml?: string): SpliceEdit[] | null {\n if (newText && xml) {\n const orig = paraTText(para, xml)\n if (orig && orig.trim() !== \"\") {\n const lead = orig.match(/^\\s*/)![0]\n const trail = orig.match(/\\s*$/)![0]\n if ((lead || trail) && newText.trim() !== \"\") {\n newText = lead + newText.replace(/^\\s+|\\s+$/g, \"\") + trail\n }\n }\n }\n const escaped = escapeXmlText(newText)\n if (para.tRanges.length > 0) {\n const splices: SpliceEdit[] = []\n const first = para.tRanges[0]\n if (first.selfClosing) {\n // 자기닫힘 <hp:t/> — 태그째 <hp:t>텍스트</hp:t>로 교체\n const prefix = first.prefix ? first.prefix + \":\" : \"\"\n splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` })\n } else {\n splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped })\n }\n // 나머지 hp:t 비우기 (자기닫힘은 이미 빈 상태)\n for (let i = 1; i < para.tRanges.length; i++) {\n const r = para.tRanges[i]\n if (!r.selfClosing && r.contentStart < r.contentEnd) {\n splices.push({ start: r.contentStart, end: r.contentEnd, replacement: \"\" })\n }\n }\n return splices\n }\n // hp:t 없음 — 첫 run 끝에 삽입\n if (para.runInsertPos !== undefined) {\n if (!newText) return [] // 비우기인데 t도 없음 — 이미 빈 문단\n const prefix = para.runPrefix ? para.runPrefix + \":\" : \"\"\n return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }]\n }\n // 자기닫힘 <hp:run/> — 태그를 펼쳐서 t 삽입 (한컴 빈 문단 패턴, xml 필요)\n if (para.selfCloseRun && xml) {\n if (!newText) return []\n const { start, end } = para.selfCloseRun\n const tag = xml.slice(start, end)\n const qm = tag.match(/^<([^\\s/>]+)/)\n if (!qm || !tag.endsWith(\"/>\")) return null\n const qname = qm[1]\n const colon = qname.indexOf(\":\")\n const prefix = colon >= 0 ? qname.slice(0, colon) + \":\" : \"\"\n const opened = tag.slice(0, tag.length - 2).trimEnd() + \">\"\n return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }]\n }\n return newText ? null : []\n}\n\n/**\n * 문단의 t-도메인 텍스트 — hp:t 콘텐츠만 이어붙인 문자열 (v3.1).\n *\n * para.text와 달리 tab/br 등 비-t 요소의 공백 기여가 없어, 그 사이에 끼인\n * 요소를 건드리지 않는 정밀 치환(buildRangeSplices)의 좌표계로 쓴다.\n * hp:t 콘텐츠에 엔티티/내부 태그([<&])가 있으면 좌표가 어긋나므로 null.\n */\nexport function paraTText(para: ScanParagraph, xml: string): string | null {\n let text = \"\"\n for (const t of para.tRanges) {\n if (t.selfClosing) continue\n const raw = xml.slice(t.contentStart, t.contentEnd)\n if (/[<&]/.test(raw)) return null\n text += raw\n }\n return text\n}\n\n/** 문단 텍스트가 전부 hp:t에서 왔는지 — tab/br 등 비-t 기여가 있으면 false.\n * false면 문단 전체 재작성 시 비-t 요소와 텍스트가 중복/순서 역전되므로 금지. */\nexport function paraTextPureT(para: ScanParagraph, xml: string): boolean {\n let len = 0\n for (const t of para.tRanges) {\n if (t.selfClosing) continue\n len += tContentToText(xml.slice(t.contentStart, t.contentEnd)).length\n }\n return len === para.text.length\n}\n\n/**\n * t-도메인 텍스트(paraTText 좌표계)의 [start, end) 범위만 치환하는 정밀 splice\n * — run/탭 구조 보존 (v3.1).\n *\n * 좌표계가 t-도메인이므로 tab/br이 사이에 끼어 있어도 동작한다 (해당 요소는\n * 건드리지 않고 t 콘텐츠만 수술). hp:t 콘텐츠에 엔티티/내부 태그가 있으면\n * 오프셋 정합이 깨지므로 null을 반환한다 (호출자가 폴백 결정).\n */\nexport function buildRangeSplices(\n para: ScanParagraph, xml: string, start: number, end: number, replacement: string,\n): SpliceEdit[] | null {\n if (start < 0 || end < start) return null\n\n // t 세그먼트 ↔ t-도메인 오프셋 정렬 — 디코딩 불변 검증\n interface Seg { contentStart: number; from: number; to: number }\n const segs: Seg[] = []\n let offset = 0\n for (const t of para.tRanges) {\n if (t.selfClosing) continue // 텍스트 기여 없음\n const raw = xml.slice(t.contentStart, t.contentEnd)\n if (/[<&]/.test(raw)) return null // 엔티티/내부 태그 — 오프셋 불일치 가능\n segs.push({ contentStart: t.contentStart, from: offset, to: offset + raw.length })\n offset += raw.length\n }\n if (segs.length === 0 || end > offset) return null\n\n const escaped = escapeXmlText(replacement)\n\n // 삽입 (start === end) — 위치를 포함하는 첫 세그먼트에 삽입\n if (start === end) {\n for (const seg of segs) {\n if (start >= seg.from && start <= seg.to) {\n const at = seg.contentStart + (start - seg.from)\n return [{ start: at, end: at, replacement: escaped }]\n }\n }\n return null\n }\n\n // 치환 — 첫 겹침 세그먼트에 새 텍스트, 나머지 겹침 구간은 제거\n const splices: SpliceEdit[] = []\n let placed = false\n for (const seg of segs) {\n if (seg.to <= start || seg.from >= end) continue\n const localStart = Math.max(seg.from, start) - seg.from\n const localEnd = Math.min(seg.to, end) - seg.from\n splices.push({\n start: seg.contentStart + localStart,\n end: seg.contentStart + localEnd,\n replacement: placed ? \"\" : escaped,\n })\n placed = true\n }\n return placed ? splices : null\n}\n\n/** splice 일괄 적용 — 겹침 검증 후 뒤에서부터 치환 */\n/**\n * 섹션 내 모든 <hp:linesegarray> 제거 splice 목록.\n *\n * hp:linesegarray는 문단 각 줄의 레이아웃(위치·크기) 정보를 담는데, hp:t 텍스트만\n * 바꾸면 이 정보가 새 텍스트와 어긋난다. 한컴 한글은 이를 \"변조\"로 감지해 보안\n * 경고를 띄운다(한컴 개발자포럼 권고). 선택적(optional)인 요소라 제거해도 뷰어가\n * 레이아웃을 다시 계산하므로, 텍스트가 바뀐 섹션은 전체 linesegarray를 비운다\n * (일부만 비우면 한컴이 문서 전체 정합성 검사에서 여전히 경고를 띄움).\n */\nexport function allLinesegRemovalSplices(xml: string): SpliceEdit[] {\n const segRe = /<(\\w+:)?linesegarray\\b[^>]*?(?:\\/>|>[\\s\\S]*?<\\/\\1linesegarray>)/g\n const splices: SpliceEdit[] = []\n let m: RegExpExecArray | null\n while ((m = segRe.exec(xml)) !== null) {\n splices.push({ start: m.index, end: m.index + m[0].length, replacement: \"\" })\n }\n return splices\n}\n\nexport function applySplices(xml: string, splices: SpliceEdit[]): string {\n const sorted = [...splices].sort((a, b) => a.start - b.start)\n for (let i = 1; i < sorted.length; i++) {\n if (sorted[i].start < sorted[i - 1].end) {\n throw new Error(\"소스맵 splice 범위 겹침 — 내부 오류\")\n }\n }\n let result = xml\n for (let i = sorted.length - 1; i >= 0; i--) {\n const s = sorted[i]\n result = result.slice(0, s.start) + s.replacement + result.slice(s.end)\n }\n return result\n}\n","/**\n * ZIP in-place 패치 — 지정 엔트리만 교체하고 나머지는 원본 바이트 그대로 보존.\n *\n * JSZip generateAsync는 전 엔트리를 재압축/재직렬화하므로 바이트 보존이 깨진다.\n * 여기서는 Central Directory를 직접 파싱해 변경 엔트리의 로컬 레코드만 재작성하고,\n * 나머지 로컬 레코드·CD 엔트리·EOCD는 원본 바이트를 복사(오프셋 필드만 패치)한다.\n * mimetype 첫 엔트리 + 무압축(STORE) 같은 HWPX/OPC 규약은 원본 순서·메서드를\n * 그대로 따르므로 자동 보존된다.\n */\n\nimport { deflateRawSync } from \"zlib\"\nimport { KordocError } from \"../utils.js\"\n\nconst EOCD_SIG = 0x06054b50\nconst CD_SIG = 0x02014b50\nconst LOCAL_SIG = 0x04034b50\nconst ZIP64_EOCD_LOC_SIG = 0x07064b50\n\ninterface CdEntry {\n /** CD 레코드 원본 바이트 범위 [start, end) */\n cdStart: number\n cdEnd: number\n name: string\n flags: number\n method: number\n crc: number\n compSize: number\n uncompSize: number\n localOffset: number\n}\n\n/** ZIP 내 엔트리별 압축 해제 데이터 읽기 (검증용) */\nexport function readZipEntries(buf: Uint8Array): Map<string, { method: number; compData: Uint8Array }> {\n const { entries } = parseCentralDirectory(buf)\n const result = new Map<string, { method: number; compData: Uint8Array }>()\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n for (const e of entries) {\n const dataStart = localDataStart(view, e.localOffset)\n result.set(e.name, { method: e.method, compData: buf.subarray(dataStart, dataStart + e.compSize) })\n }\n return result\n}\n\nfunction localDataStart(view: DataView, localOffset: number): number {\n if (view.getUint32(localOffset, true) !== LOCAL_SIG) {\n throw new KordocError(\"ZIP 로컬 헤더 시그니처 불일치\")\n }\n const nameLen = view.getUint16(localOffset + 26, true)\n const extraLen = view.getUint16(localOffset + 28, true)\n return localOffset + 30 + nameLen + extraLen\n}\n\n/** Buffer.prototype.slice는 view를 반환하므로 (Uint8Array와 달리) 명시적 복사 */\nfunction copyBytes(buf: Uint8Array, start: number, end?: number): Uint8Array {\n return new Uint8Array(buf.subarray(start, end))\n}\n\nfunction parseCentralDirectory(buf: Uint8Array): { entries: CdEntry[]; cdOffset: number; cdSize: number; eocdOffset: number } {\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n // EOCD 탐색 (뒤에서부터, 주석 최대 64KB) — comment 길이가 파일 끝과 일치해야\n // 진짜 EOCD (엔트리 데이터/comment 안 가짜 시그니처 배제)\n const minEocd = Math.max(0, buf.length - 22 - 65535)\n let eocdOffset = -1\n for (let i = buf.length - 22; i >= minEocd; i--) {\n if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {\n eocdOffset = i\n break\n }\n }\n if (eocdOffset < 0) {\n // 폴백: EOCD 뒤 trailing 정크가 붙은 파일 (parse()는 수용) — comment 길이가\n // 파일 끝에 못 미치더라도 CD 시그니처가 검증되는 첫 후보를 채택\n for (let i = buf.length - 22; i >= minEocd; i--) {\n if (view.getUint32(i, true) !== EOCD_SIG) continue\n if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue\n const cand = view.getUint32(i + 16, true)\n if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) { eocdOffset = i; break }\n }\n }\n if (eocdOffset < 0) throw new KordocError(\"ZIP EOCD를 찾을 수 없습니다\")\n\n const totalEntries = view.getUint16(eocdOffset + 10, true)\n const cdSize = view.getUint32(eocdOffset + 12, true)\n const cdOffset = view.getUint32(eocdOffset + 16, true)\n if (cdOffset === 0xffffffff || totalEntries === 0xffff) throw new KordocError(\"ZIP64는 지원하지 않습니다\")\n // ZIP64 EOCD locator 존재 여부\n if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {\n throw new KordocError(\"ZIP64는 지원하지 않습니다\")\n }\n\n const decoder = new TextDecoder(\"utf-8\")\n const entries: CdEntry[] = []\n let pos = cdOffset\n for (let i = 0; i < totalEntries; i++) {\n if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError(\"ZIP Central Directory 손상\")\n const flags = view.getUint16(pos + 8, true)\n const method = view.getUint16(pos + 10, true)\n const crc = view.getUint32(pos + 16, true)\n const compSize = view.getUint32(pos + 20, true)\n const uncompSize = view.getUint32(pos + 24, true)\n const nameLen = view.getUint16(pos + 28, true)\n const extraLen = view.getUint16(pos + 30, true)\n const commentLen = view.getUint16(pos + 32, true)\n const localOffset = view.getUint32(pos + 42, true)\n if (compSize === 0xffffffff || uncompSize === 0xffffffff || localOffset === 0xffffffff) {\n throw new KordocError(\"ZIP64는 지원하지 않습니다\")\n }\n const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen))\n const cdEnd = pos + 46 + nameLen + extraLen + commentLen\n entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset })\n pos = cdEnd\n }\n return { entries, cdOffset, cdSize, eocdOffset }\n}\n\n// ─── CRC-32 ─────────────────────────────────────────\n\nconst CRC_TABLE = (() => {\n const table = new Uint32Array(256)\n for (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n table[n] = c >>> 0\n }\n return table\n})()\n\nfunction crc32(data: Uint8Array): number {\n let crc = 0xffffffff\n for (let i = 0; i < data.length; i++) {\n crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8)\n }\n return (crc ^ 0xffffffff) >>> 0\n}\n\n// ─── 재조립 ──────────────────────────────────────────\n\n/**\n * ZIP에서 replacements에 지정된 엔트리만 새 데이터로 교체하고 나머지는\n * 원본 로컬 레코드 바이트를 그대로 복사하여 재조립한다.\n *\n * @param original 원본 ZIP 바이트\n * @param replacements 엔트리 이름 → 새 압축 전 데이터\n * @param additions 신규 엔트리 이름 → 데이터 (기존 로컬 레코드 뒤·CD 앞에 추가 —\n * 도장 이미지 등 BinData 파트 추가용. 기존 엔트리와 이름 충돌 시 에러)\n */\nexport function patchZipEntries(\n original: Uint8Array,\n replacements: Map<string, Uint8Array>,\n additions?: Map<string, Uint8Array>,\n): Uint8Array {\n const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original)\n const view = new DataView(original.buffer, original.byteOffset, original.byteLength)\n\n for (const name of replacements.keys()) {\n if (!entries.some(e => e.name === name)) throw new KordocError(`ZIP에 없는 엔트리: ${name}`)\n }\n if (additions) {\n for (const name of additions.keys()) {\n if (entries.some(e => e.name === name)) throw new KordocError(`ZIP에 이미 있는 엔트리: ${name}`)\n }\n }\n\n // 로컬 레코드 순서 = 원본 로컬 오프셋 순서 (mimetype 첫 엔트리 보존)\n const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset)\n const segments: Uint8Array[] = []\n const newLocalOffset = new Map<CdEntry, number>()\n const newMeta = new Map<CdEntry, { crc: number; compSize: number; uncompSize: number; flags: number }>()\n let offset = 0\n\n for (let i = 0; i < byLocal.length; i++) {\n const e = byLocal[i]\n const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset\n newLocalOffset.set(e, offset)\n\n const newData = replacements.get(e.name)\n if (newData === undefined) {\n // 원본 로컬 레코드 (데이터 디스크립터 포함) 바이트 그대로\n const seg = original.subarray(e.localOffset, segEnd)\n segments.push(seg)\n offset += seg.length\n continue\n }\n\n // 교체 엔트리 — 원본 로컬 헤더를 복사한 뒤 crc/size/flags만 패치\n if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError(\"ZIP 로컬 헤더 시그니처 불일치\")\n const nameLen = view.getUint16(e.localOffset + 26, true)\n const extraLen = view.getUint16(e.localOffset + 28, true)\n const headerLen = 30 + nameLen + extraLen\n const header = copyBytes(original, e.localOffset, e.localOffset + headerLen)\n const hview = new DataView(header.buffer, header.byteOffset, header.byteLength)\n\n const method = e.method\n if (method !== 0 && method !== 8) {\n throw new KordocError(`지원하지 않는 ZIP 압축 방식(method=${method}): ${e.name} — STORE(0)/DEFLATE(8)만 교체 가능`)\n }\n const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData))\n const crc = crc32(newData)\n const flags = e.flags & ~0x0008 // 데이터 디스크립터 비트 해제 (사이즈를 헤더에 기록)\n\n hview.setUint16(6, flags, true)\n hview.setUint32(14, crc, true)\n hview.setUint32(18, compData.length, true)\n hview.setUint32(22, newData.length, true)\n\n segments.push(header, compData)\n offset += headerLen + compData.length\n newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags })\n }\n\n // 신규 엔트리 — 기존 로컬 레코드 뒤에 UTF-8 이름·고정 타임스탬프로 기록.\n // PNG 등 기압축 데이터는 deflate가 오히려 커질 수 있어 작아질 때만 압축(STORE 폴백).\n const added: Array<{\n nameBytes: Uint8Array\n crc: number\n compSize: number\n uncompSize: number\n method: number\n localOffset: number\n }> = []\n if (additions) {\n const encoder = new TextEncoder()\n for (const [name, data] of additions) {\n const nameBytes = encoder.encode(name)\n const deflated = new Uint8Array(deflateRawSync(data))\n const method = deflated.length < data.length ? 8 : 0\n const compData = method === 8 ? deflated : data\n const crc = crc32(data)\n const header = new Uint8Array(30 + nameBytes.length)\n const hv = new DataView(header.buffer)\n hv.setUint32(0, LOCAL_SIG, true)\n hv.setUint16(4, 20, true) // version needed\n hv.setUint16(6, 0x0800, true) // UTF-8 이름 플래그\n hv.setUint16(8, method, true)\n hv.setUint16(10, 0, true) // time\n hv.setUint16(12, 0x21, true) // date 1980-01-01 (결정적 출력)\n hv.setUint32(14, crc, true)\n hv.setUint32(18, compData.length, true)\n hv.setUint32(22, data.length, true)\n hv.setUint16(26, nameBytes.length, true)\n hv.setUint16(28, 0, true)\n header.set(nameBytes, 30)\n added.push({ nameBytes, crc, compSize: compData.length, uncompSize: data.length, method, localOffset: offset })\n segments.push(header, compData)\n offset += header.length + compData.length\n }\n }\n\n // Central Directory — 원본 CD 엔트리 순서 유지, 오프셋/메타 패치\n const newCdOffset = offset\n for (const e of entries) {\n const cd = copyBytes(original, e.cdStart, e.cdEnd)\n const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength)\n cview.setUint32(42, newLocalOffset.get(e)!, true)\n const meta = newMeta.get(e)\n if (meta) {\n cview.setUint16(8, meta.flags, true)\n cview.setUint32(16, meta.crc, true)\n cview.setUint32(20, meta.compSize, true)\n cview.setUint32(24, meta.uncompSize, true)\n }\n segments.push(cd)\n offset += cd.length\n }\n\n // 신규 엔트리 CD 레코드 (원본 CD 뒤)\n for (const a of added) {\n const cd = new Uint8Array(46 + a.nameBytes.length)\n const cv = new DataView(cd.buffer)\n cv.setUint32(0, CD_SIG, true)\n cv.setUint16(4, 20, true) // version made by\n cv.setUint16(6, 20, true) // version needed\n cv.setUint16(8, 0x0800, true) // UTF-8 이름 플래그\n cv.setUint16(10, a.method, true)\n cv.setUint16(12, 0, true) // time\n cv.setUint16(14, 0x21, true) // date 1980-01-01\n cv.setUint32(16, a.crc, true)\n cv.setUint32(20, a.compSize, true)\n cv.setUint32(24, a.uncompSize, true)\n cv.setUint16(28, a.nameBytes.length, true)\n cv.setUint32(42, a.localOffset, true)\n cd.set(a.nameBytes, 46)\n segments.push(cd)\n offset += cd.length\n }\n const newCdSize = offset - newCdOffset\n\n // EOCD — 원본 복사 후 CD 오프셋/크기 패치 (신규 엔트리만큼 카운트 증가)\n const eocd = copyBytes(original, eocdOffset)\n const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength)\n if (added.length > 0) {\n eview.setUint16(8, view.getUint16(eocdOffset + 8, true) + added.length, true)\n eview.setUint16(10, view.getUint16(eocdOffset + 10, true) + added.length, true)\n }\n eview.setUint32(12, newCdSize, true)\n eview.setUint32(16, newCdOffset, true)\n segments.push(eocd)\n offset += eocd.length\n\n const result = new Uint8Array(offset)\n let pos = 0\n for (const seg of segments) {\n result.set(seg, pos)\n pos += seg.length\n }\n return result\n}\n"],"mappings":"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","names":["local","contentStart","si","site"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hwpx/profile-io.ts"],"sourcesContent":["/**\n * FormatProfile JSON 경계 검증 — CLI(--profile)와 MCP(profile_path)가 공유하는 1벌\n * 스키마 (이슈 #41 표면 노출, v4.0.6). 라이브러리 API(markdownToHwpx options.profile)는\n * 타입 계약을 신뢰하고, 파일에서 들어오는 JSON(손편집 가능)만 여기서 검증한다.\n */\n\nimport { z } from \"zod\"\nimport { KordocError } from \"../utils.js\"\nimport type { FormatProfile } from \"./gen-profile.js\"\n\n// 괘선 값 검증 (v4.0.4) — type/width/color는 XML에 원문 방출되므로 손편집 오타가\n// 한컴 로드 실패·무시로 번지기 전에 여기서 잡는다.\n// type: HWPX LineType2 열거(KS X 6101). 실측 코퍼스 등장: NONE·SOLID·DASH·DOUBLE_SLIM·SLIM_THICK\nconst borderType = z.enum([\n \"NONE\", \"SOLID\", \"DASH\", \"DOT\", \"DASH_DOT\", \"DASH_DOT_DOT\", \"LONG_DASH\", \"CIRCLE\",\n \"DOUBLE_SLIM\", \"SLIM_THICK\", \"THICK_SLIM\", \"SLIM_THICK_SLIM\", \"WAVE\", \"DOUBLEWAVE\",\n])\nconst mmWidth = z.string().regex(/^\\d+(\\.\\d+)? ?mm$/, '\"0.12 mm\" 형식(mm 단위)이어야 합니다')\nconst hexColor = z.string().regex(/^(#[0-9A-Fa-f]{6}|none)$/i, '\"#RRGGBB\" 또는 \"none\"이어야 합니다')\n\nconst borderDef = z.object({\n type: borderType,\n width: mmWidth,\n color: hexColor,\n})\n\nconst borderFillDef = z.object({\n leftBorder: borderDef.optional(),\n rightBorder: borderDef.optional(),\n topBorder: borderDef.optional(),\n bottomBorder: borderDef.optional(),\n fill: z.object({ faceColor: hexColor }).optional(),\n})\n\nconst charPrDef = z.object({\n height_hwpunit: z.string().optional(),\n textColor: hexColor.optional(),\n bold: z.boolean().optional(),\n italic: z.boolean().optional(),\n underline: z.boolean().optional(),\n fontRef_hangul: z.string().optional(),\n fontName_hangul: z.string().optional(),\n})\n\nconst cellProfile = z.object({\n row: z.number().int().min(0),\n col: z.number().int().min(0),\n rowSpan: z.number().int().min(1).optional(),\n colSpan: z.number().int().min(1).optional(),\n width_hwpunit: z.string().optional(),\n height_hwpunit: z.string().optional(),\n borderFillIDRef: z.string().optional(),\n charPrIDRef: z.string().optional(),\n})\n\nconst tableProfile = z.object({\n table_index: z.number().int().min(0),\n rows: z.number().int().min(1),\n cols: z.number().int().min(1),\n anchor_text: z.string().optional(),\n anchor_row: z.string().optional(),\n width_hwpunit: z.string().optional(),\n col_widths_hwpunit: z.array(z.string()).optional(),\n cells: z.array(cellProfile),\n used_border_fills: z.record(borderFillDef),\n used_char_prs: z.record(charPrDef).optional(),\n})\n\nexport const formatProfileSchema = z.object({\n schema_version: z.string().optional(),\n tables: z.array(tableProfile),\n})\n\n/** 프로필 JSON 텍스트 → 검증된 FormatProfile. 실패 시 KordocError(위치·사유 요약) */\nexport function parseFormatProfileJson(text: string): FormatProfile {\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (e) {\n throw new KordocError(`프로필 JSON 파싱 실패: ${e instanceof Error ? e.message : String(e)}`)\n }\n const r = formatProfileSchema.safeParse(raw)\n if (!r.success) {\n const first = r.error.issues.slice(0, 3).map(i => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`).join(\" / \")\n throw new KordocError(`프로필 스키마 불일치: ${first}`)\n }\n return r.data as FormatProfile\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- blocksToChunks
4
- } from "./chunk-KTPZYXN2.js";
5
- import "./chunk-MHMVFI2N.js";
6
- import "./chunk-3TSTZGCJ.js";
7
- export {
8
- blocksToChunks
9
- };
10
- //# sourceMappingURL=chunks-VFIMYK36.js.map
@@ -1,26 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- detectFormat,
4
- detectOle2Format,
5
- detectZipFormat,
6
- isHwp3File,
7
- isHwpmlFile,
8
- isHwpxFile,
9
- isImageFile,
10
- isOldHwpFile,
11
- isPdfFile,
12
- isZipFile
13
- } from "./chunk-TCJRGFYY.js";
14
- export {
15
- detectFormat,
16
- detectOle2Format,
17
- detectZipFormat,
18
- isHwp3File,
19
- isHwpmlFile,
20
- isHwpxFile,
21
- isImageFile,
22
- isOldHwpFile,
23
- isPdfFile,
24
- isZipFile
25
- };
26
- //# sourceMappingURL=detect-FX2ZHKZP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/pdf/formula/postprocess.ts","../src/pdf/formula/detector.ts","../src/pdf/formula/recognizer.ts","../src/pdf/formula/pipeline.ts"],"sourcesContent":["/**\n * LaTeX 후처리 — Pix2Text `latex_ocr.py` 의 post_process 간소화 + 자체 필터\n *\n * 1) 후행 whitespace command 제거 (\\, \\: \\; \\! \\quad \\qquad \\enspace \\thinspace \\ )\n * 2) 연속 공백 → 1개\n * 3) 빈 그룹 반복 제거 (^{} _{} \\hat{} \\bar{} \\vec{} 등)\n * 4) \\cmd 뒤 영문자 공백 분리 (\\cdotd → \\cdot d, \\timesd → \\times d)\n * 5) isTrivialFormula — 다이어그램 단일 글자/반복/장식 오탐 제거\n */\n\nconst TRAILING_WHITESPACE_CMDS = [\n \"\\\\,\",\n \"\\\\:\",\n \"\\\\;\",\n \"\\\\!\",\n \"\\\\ \",\n \"\\\\quad\",\n \"\\\\qquad\",\n \"\\\\enspace\",\n \"\\\\thinspace\",\n] as const\n\nexport function postProcessLatex(latex: string): string {\n let s = stripTrailingWhitespace(latex)\n s = collapseSpaces(s)\n for (let i = 0; i < 10; i++) {\n const next = stripEmptyGroups(s)\n if (next === s) break\n s = next\n }\n s = fixLatexSpacing(s)\n s = normalizeFormulaSpacing(s)\n s = s.trim()\n // trivial 이면 빈 문자열 반환 — 상위(pipeline/parser) 에서 latex.trim() 비었을 때 skip.\n if (isTrivialFormula(s)) return \"\"\n return s\n}\n\nexport function stripTrailingWhitespace(s: string): string {\n let t = s\n // 바깥 loop: 한 번 제거 후 다시 체크 (중첩 \"\\\\, \\\\quad\" 같은 경우)\n for (;;) {\n const trimmed = t.replace(/[\\s]+$/, \"\")\n let changed = false\n for (const p of TRAILING_WHITESPACE_CMDS) {\n if (trimmed.endsWith(p)) {\n t = trimmed.slice(0, trimmed.length - p.length)\n changed = true\n break\n }\n }\n if (!changed) return trimmed\n }\n}\n\nexport function collapseSpaces(s: string): string {\n let out = \"\"\n let prevSpace = false\n for (const c of s) {\n if (/\\s/.test(c)) {\n if (!prevSpace) {\n out += \" \"\n prevSpace = true\n }\n } else {\n out += c\n prevSpace = false\n }\n }\n return out\n}\n\n/**\n * 빈 그룹 `{}` 또는 `{\\s+}` 를 찾아 선행하는 `^`, `_`, 또는 `\\cmd` 와 함께 제거.\n */\nexport function stripEmptyGroups(s: string): string {\n let out = \"\"\n let i = 0\n const bytes = s\n while (i < bytes.length) {\n const ch = bytes[i]\n if (ch === \"{\") {\n // 빈 { \\s* } 스캔\n let j = i + 1\n while (j < bytes.length && /\\s/.test(bytes[j])) j++\n if (j < bytes.length && bytes[j] === \"}\") {\n // 앞쪽 공백 제거\n while (out.endsWith(\" \") || out.endsWith(\"\\t\")) {\n out = out.slice(0, -1)\n }\n if (out.endsWith(\"^\") || out.endsWith(\"_\")) {\n out = out.slice(0, -1)\n } else {\n // \\cmd 형태인지 탐색\n let k = out.length\n while (k > 0 && /[A-Za-z]/.test(out[k - 1])) k--\n if (k > 0 && out[k - 1] === \"\\\\\" && k < out.length) {\n out = out.slice(0, k - 1)\n }\n // 아니면 아무것도 안 함 — 빈 {} 만 제거\n }\n i = j + 1\n continue\n }\n }\n out += ch\n i++\n }\n return out\n}\n\n/**\n * LaTeX 명령어 화이트리스트 — 공백 누락 감지에 사용.\n * MFR 이 `\\cdot d` 를 `\\cdotd` 로 합쳐 출력할 때, 어느 지점에서 명령어가 끝나는지\n * 알려진 커맨드명으로 판단한다. 완전한 리스트는 아니며 OCR 에서 실제 관찰된 케이스 중심.\n */\nconst KNOWN_LATEX_CMDS: ReadonlySet<string> = new Set([\n // 연산자\n \"cdot\", \"cdots\", \"ldots\", \"dots\", \"vdots\", \"ddots\",\n \"times\", \"div\", \"pm\", \"mp\", \"ast\", \"star\", \"circ\", \"bullet\",\n \"oplus\", \"ominus\", \"otimes\", \"odot\",\n // 관계\n \"approx\", \"equiv\", \"neq\", \"ne\", \"sim\", \"simeq\", \"cong\",\n \"leq\", \"geq\", \"le\", \"ge\", \"ll\", \"gg\", \"prec\", \"succ\", \"preceq\", \"succeq\",\n \"propto\", \"parallel\", \"perp\",\n // 집합/논리\n \"in\", \"notin\", \"ni\", \"subset\", \"supset\", \"subseteq\", \"supseteq\",\n \"cap\", \"cup\", \"bigcap\", \"bigcup\", \"emptyset\", \"varnothing\",\n \"forall\", \"exists\", \"nexists\", \"neg\", \"lnot\", \"land\", \"lor\", \"vee\", \"wedge\",\n // 그리스 소문자\n \"alpha\", \"beta\", \"gamma\", \"delta\", \"epsilon\", \"varepsilon\",\n \"zeta\", \"eta\", \"theta\", \"vartheta\", \"iota\", \"kappa\", \"lambda\",\n \"mu\", \"nu\", \"xi\", \"omicron\", \"pi\", \"varpi\", \"rho\", \"varrho\",\n \"sigma\", \"varsigma\", \"tau\", \"upsilon\", \"phi\", \"varphi\",\n \"chi\", \"psi\", \"omega\",\n // 그리스 대문자\n \"Gamma\", \"Delta\", \"Theta\", \"Lambda\", \"Xi\", \"Pi\", \"Sigma\",\n \"Upsilon\", \"Phi\", \"Psi\", \"Omega\",\n // 화살표\n \"to\", \"gets\", \"mapsto\", \"rightarrow\", \"leftarrow\", \"leftrightarrow\",\n \"Rightarrow\", \"Leftarrow\", \"Leftrightarrow\", \"uparrow\", \"downarrow\",\n \"longrightarrow\", \"longleftarrow\", \"longmapsto\",\n // 큰 연산자\n \"sum\", \"prod\", \"coprod\", \"int\", \"iint\", \"iiint\", \"oint\", \"bigoplus\", \"bigotimes\",\n // 함수명\n \"sin\", \"cos\", \"tan\", \"sec\", \"csc\", \"cot\",\n \"arcsin\", \"arccos\", \"arctan\", \"sinh\", \"cosh\", \"tanh\",\n \"log\", \"ln\", \"lg\", \"exp\", \"lim\", \"liminf\", \"limsup\",\n \"sup\", \"inf\", \"max\", \"min\", \"arg\", \"det\", \"dim\", \"gcd\", \"deg\", \"hom\", \"ker\", \"mod\",\n // 특수 기호/수식\n \"infty\", \"partial\", \"nabla\", \"prime\", \"aleph\", \"ell\", \"hbar\", \"Re\", \"Im\",\n \"top\", \"bot\", \"angle\", \"vdash\", \"dashv\",\n // 기타\n \"left\", \"right\", \"big\", \"Big\", \"bigg\", \"Bigg\",\n])\n\n/**\n * `\\cmd` 뒤에 영문자가 바로 붙으면 공백 분리 (e.g. `\\cdotd` → `\\cdot d`, `\\timesd_{k}` → `\\times d_{k}`).\n *\n * MFR 이 좁은 영역에서 가끔 `\\cdot` 과 다음 변수 사이 공백을 누락. 그대로 두면 LaTeX 파서가\n * `\\cdotd` 를 undefined command 로 처리 (KaTeX: 에러 / TeX: 정의되지 않은 제어 시퀀스).\n * 알려진 LaTeX 명령어 prefix 중 가장 긴 것을 찾아 분할하면 렌더 안정성 확보.\n *\n * `\\cmd{..}` (영문자 뒤 `{`) 는 인자 포함 형태로 간주해 분리하지 않음.\n */\nexport function fixLatexSpacing(s: string): string {\n let out = \"\"\n let i = 0\n while (i < s.length) {\n if (s[i] === \"\\\\\" && i + 1 < s.length && /[A-Za-z]/.test(s[i + 1])) {\n // \\cmd 최장일치\n let j = i + 1\n while (j < s.length && /[A-Za-z]/.test(s[j])) j++\n const full = s.slice(i + 1, j) // 명령어 이름 (앞 \\ 제거)\n const nextChar = j < s.length ? s[j] : \"\"\n\n // \\cmd{..} 형태 — 인자로 이어지므로 분리하지 않음\n if (nextChar === \"{\") {\n out += \"\\\\\" + full\n i = j\n continue\n }\n\n // 자체가 알려진 명령어이거나, 긴 prefix 가 없으면 그대로\n let splitAt = full.length\n if (!KNOWN_LATEX_CMDS.has(full) && full.length >= 3) {\n // 뒤에서부터 prefix 길이를 줄이며 가장 긴 known cmd 찾기\n for (let len = full.length - 1; len >= 2; len--) {\n if (KNOWN_LATEX_CMDS.has(full.slice(0, len))) {\n splitAt = len\n break\n }\n }\n }\n\n out += \"\\\\\" + full.slice(0, splitAt)\n if (splitAt < full.length) {\n out += \" \" + full.slice(splitAt)\n }\n i = j\n } else {\n out += s[i]\n i++\n }\n }\n return out\n}\n\n/**\n * trivial(noise) 수식 판정.\n *\n * true 인 케이스 (MFD 오탐 주범):\n * 1) 공백/중괄호 제거 후 2자 이하 (e.g. `O`, `a`, `.`, `n`)\n * 2) 단일 `\\cmd` (e.g. `\\imath`, `\\pi`, `\\eta`, `\\sigma`, `\\theta`, `\\emptyset`, `\\varPi`)\n * 3) 단일 `\\mathrm{..}` / `\\textrm{..}` / `\\operatorname{..}` 류 + 짧은 내용 (e.g. `\\mathrm{fcloc}`)\n * 4) 동일 토큰이 3회 이상 + 전체의 50% 이상 (e.g. `\\pm \\pm \\pm \\pm`, `\\cap \\exists \\exists \\rceil`)\n *\n * false 유지 (의미 있는 수식):\n * `O(1)`, `d_{k}=64`, `\\sqrt{d_{k}}`, `PE_{pos+k}`, 등\n */\nexport function isTrivialFormula(s: string): boolean {\n const t = s.trim()\n if (t.length === 0) return true\n\n // 1) 공백/중괄호 제거 후 길이 ≤ 2\n const stripped = t.replace(/[\\s{}]/g, \"\")\n if (stripped.length <= 2) return true\n\n // 2) 단일 \\cmd (e.g. \\imath, \\varPi, \\pi)\n if (/^\\\\[A-Za-z]+$/.test(t)) return true\n\n // 3) 단일 \\mathrm{..} / \\textrm{..} / \\operatorname{..} / \\mathit{..} / \\mathbf{..} / \\mathcal{..} + 짧은 내용\n if (\n /^\\\\(?:mathrm|textrm|text|operatorname|mathit|mathbf|mathcal|mathsf|mathtt)\\{[A-Za-z]{1,6}\\}$/.test(\n t,\n )\n )\n return true\n\n const tokens = tokenizeLatex(t)\n\n // 4) 반복 토큰 dominant (e.g. \"\\\\pm \\\\pm \\\\pm \\\\pm\")\n if (tokens.length >= 3) {\n const freq = new Map<string, number>()\n for (const tok of tokens) freq.set(tok, (freq.get(tok) ?? 0) + 1)\n let maxCount = 0\n for (const c of freq.values()) if (c > maxCount) maxCount = c\n if (maxCount >= 3 && maxCount / tokens.length >= 0.5) return true\n }\n\n // 5) 심볼만 구성된 짧은 식 — 토큰 2~4개이고 연산자/숫자 없음 (e.g. \"\\\\cap \\\\exists \\\\exists \\\\rceil\")\n // 의미 있는 수식은 대개 `=` 또는 숫자가 포함됨. 허용되는 연산자: = + - / * < > 숫자\n if (tokens.length >= 2 && tokens.length <= 4) {\n const hasOpOrNum = tokens.some((tok) =>\n /^[=+\\-/*<>]$/.test(tok) || /^[0-9]$/.test(tok),\n )\n if (!hasOpOrNum) return true\n }\n\n // 6) substring 반복 패턴 — 동일 5~15자 substring 이 3회 이상 + 커버리지 60%+\n // (e.g. \"\\\\alpha_{1}=\\\\alpha_{2}=\\\\alpha_{3}=...\" 의 `\\\\alpha_{` 반복,\n // \"c_{c}^{\\\\prime}\\\\phi_{c}^{\\\\prime}=c_{c}^{\\\\prime}...\" 의 `c_{c}^{\\\\prime}` 반복)\n if (hasHighRepetition(t)) return true\n\n // 7) MFR placeholder 인 `\\square` 포함 — 인식 실패 영역을 `\\square` 로 대체하는 특성\n // 정당한 수식에 `\\square` 가 나오는 경우는 거의 없음 (대부분 `\\Box`, `\\blacksquare` 등 사용)\n if (t.includes(\"\\\\square\")) return true\n\n // 8) 단독 숫자/실수 (e.g. \"$1.0$\", \"$42$\", \"$-3.14$\")\n // 공백/중괄호/백슬래시 제거 후 순수 숫자/부호/소수점만 남으면 trivial\n if (/^[-+]?\\d+\\.?\\d*$/.test(t.replace(/[\\s{}\\\\]/g, \"\"))) return true\n\n // 9) `(X)(X)` 또는 `{X}{X}` 연속 중복 — 동일 그룹 반복 (e.g. \"$C(T_{2})(T_{2})$\")\n // 한 단계 중첩 허용: `{k_{c}}` 같이 내부에 `{...}` 하나 포함 가능.\n if (/(\\([^()]{2,15}\\))\\s*\\1/.test(t)) return true\n if (/(\\{(?:[^{}]|\\{[^{}]*\\})+\\})\\s*\\1/.test(t)) return true\n\n // 10) 동일 인자 반복 — 괄호 인자 중 쉼표 구분 3개 중 2개 이상 동일 (e.g. `C(\\tau_{2},\\mu^{\\prime},\\mu^{\\prime})`)\n // 완전 일치 인자가 2회+ 반복이면 OCR 반복 오류로 간주\n const argMatch = t.match(/^[A-Za-z\\\\][A-Za-z]*\\(([^()]+)\\)$/)\n if (argMatch) {\n const args = argMatch[1].split(\",\").map((a) => a.trim())\n if (args.length >= 2) {\n const freq = new Map<string, number>()\n for (const a of args) if (a) freq.set(a, (freq.get(a) ?? 0) + 1)\n for (const [, c] of freq) {\n if (c >= 2 && c / args.length >= 0.5) return true\n }\n }\n }\n\n // 11) X/X 또는 \\frac{X}{X} — 분자=분모 = 1, 의미 없는 수식\n // 수식 **어디에나** 나타나면 trivial (e.g. \"$k<\\\\frac{\\\\eta}{\\\\eta}$\" 도 포함)\n // 정당한 수식에서 $\\\\frac{a}{a}$ 를 쓰는 경우는 거의 없음 (대수적으로 1).\n if (/\\\\frac\\{([^{}]+)\\}\\{\\1\\}/.test(t)) return true\n // X/X 꼴 (단일 변수 또는 \\cmd)\n if (/(\\\\[A-Za-z]+|\\b[A-Za-z])\\s*\\/\\s*\\1\\b/.test(t)) return true\n\n // 12) `\\begin{matrix}` 내부 `\\cdots` 여러 번 — MFR 이 다이어그램/표를 matrix 로 오인식\n // 본문 수식의 matrix 는 구체적 원소가 있어 `\\cdots` 가 2회 이상 나오는 경우 거의 없음\n if (/\\\\begin\\{(?:matrix|pmatrix|bmatrix|vmatrix)\\}/.test(t)) {\n const cdotsCount = (t.match(/\\\\cdots/g) ?? []).length\n if (cdotsCount >= 2) return true\n }\n\n // 13) 다이어그램 텍스트 레이블 — `\\mathrm{word}` 가 2회+ 포함된 짧은 식 (토큰 수 ≤ 12)\n // 또는 `[a-z]{1,3}_\\{\\mathrm{word}\\}` 꼴의 비정상 변수명 (e.g. `cl_{\\mathrm{model}}`)\n if (tokens.length <= 12) {\n const mathrmCount = (t.match(/\\\\mathrm\\{/g) ?? []).length\n if (mathrmCount >= 2) {\n // 연산자/등호/숫자가 거의 없어 \"단순 레이블\" 로 보이는지 추가 확인\n const hasRealMath = /[=+\\-*/<>^]/.test(t) && /\\d/.test(t)\n if (!hasRealMath) return true\n }\n }\n // 비정상 변수명: 2-3자 영문 접두 + _{\\mathrm{긴단어}} 단독 (e.g. `cl_{\\mathrm{model}}`, `to_{\\mathrm{max}}`)\n // 정상 변수명은 보통 단일 문자 (`d_{\\mathrm{model}}`, `W_{i}`) — 2자 이상이면 다이어그램 오인식\n if (/^[a-zA-Z]{2,3}_\\{\\\\mathrm\\{[a-zA-Z]{3,}\\}\\}$/.test(t)) return true\n\n // 14) `\\mathrm{word}` + 이항 연산자 + single 요소 꼴 — 다이어그램 \"word → value\" 레이블\n // (e.g. `\\mathrm{to}-\\infty`, `\\mathrm{to}+\\infty`)\n if (/^\\\\mathrm\\{[a-z]{2,}\\}[-+][-+]?(?:\\\\[a-zA-Z]+|[a-zA-Z0-9])$/.test(t)) return true\n\n // 15) `\\mathsf`, `\\mathtt`, `\\texttt` 는 본문 수식에 거의 쓰이지 않고 다이어그램 타이포그래피 오인식이 대부분\n // — 포함만으로 trivial (e.g. `\\mathtt{Welgnt}`, `\\mathsf{Tr}_{s}`, `\\texttt{wg}`)\n if (/\\\\(?:mathsf|mathtt|texttt)\\{/.test(t)) return true\n\n // 16) `\\begin{aligned}` 이지만 `=` 없음 — 정당한 aligned 는 항상 등호 포함 (여러 줄 equation)\n // 등호 없으면 OCR garbage 조합\n if (/\\\\begin\\{aligned\\}/.test(t) && !t.includes(\"=\")) return true\n\n // 17) `\\begin{matrix}` + `\\downarrow` 2회+ — 네트워크 architecture 다이어그램 오인식\n if (/\\\\begin\\{matrix\\}/.test(t) && (t.match(/\\\\downarrow/g) ?? []).length >= 2) return true\n\n return false\n}\n\n/**\n * substring 반복 탐지 — 동일한 N자 substring 이 3회 이상 나타나고\n * 그 반복 총 길이가 전체의 60% 이상이면 true.\n *\n * 5~15자 범위만 검사 (짧으면 정상 수식의 `^{2}` 같은 패턴도 걸림, 길면 노이즈 거의 없음).\n * 영문자 포함 substring 만 검사 (구두점만 반복은 정상 수식에도 많음).\n *\n * 정당한 수식은 다양한 구조로 거의 겹치지 않아 false-positive 낮음.\n */\nexport function hasHighRepetition(s: string): boolean {\n if (s.length < 15) return false\n\n for (let len = 5; len <= 15; len++) {\n // substring 길이 × 3회가 전체보다 길면 의미 없음\n if (len * 3 > s.length) break\n const seen = new Map<string, number>()\n for (let i = 0; i <= s.length - len; i++) {\n const sub = s.slice(i, i + len)\n if (!/[a-zA-Z]/.test(sub)) continue\n seen.set(sub, (seen.get(sub) ?? 0) + 1)\n }\n for (const [, count] of seen) {\n if (count < 3) continue\n // 겹치지 않는 점유율 추정: count*len (overlapping 포함이라 약간 과대)\n // 보수적 판정 위해 최소 3회 + 60% 커버리지\n if ((count * len) / s.length >= 0.6) return true\n }\n }\n return false\n}\n\n/**\n * LaTeX 를 단순 토큰 배열로 분할 — `\\cmd` 는 하나의 토큰, 그 외는 non-space 단일 문자.\n * isTrivialFormula 의 반복 감지용.\n */\nfunction tokenizeLatex(s: string): string[] {\n const result: string[] = []\n let i = 0\n while (i < s.length) {\n const c = s[i]\n if (c === \"\\\\\") {\n let j = i + 1\n while (j < s.length && /[A-Za-z]/.test(s[j])) j++\n if (j === i + 1 && j < s.length) j++ // `\\\\` 또는 `\\{` 같은 단일 escape\n result.push(s.slice(i, j))\n i = j\n } else if (/\\s/.test(c)) {\n i++\n } else {\n result.push(c)\n i++\n }\n }\n return result\n}\n\n/**\n * MFR tokenizer 가 LaTeX 토큰 사이에 삽입하는 과도한 공백을 정리.\n *\n * 예: `\\mathrm { m o d d }` → `\\mathrm{modd}`, `6 4` → `64`, `( Q, K, V )` → `(Q,K,V)`\n *\n * 규칙: 공백을 앞뒤 토큰 기준으로 유지/제거 결정.\n * - 직전이 `\\cmd` 이고 직후가 영문자 → **유지** (e.g. `\\cdot d`, `\\alpha b` — 공백 없으면 greedy 로 `\\cdotd` 가 명령어로 해석될 수 있음)\n * - 그 외 → **제거** (숫자/괄호/중괄호/첨자/구두점 주변 공백은 LaTeX 의미를 바꾸지 않음)\n *\n * `fixLatexSpacing` 이후에 호출되는 것을 전제 — 먼저 공백 누락을 복원한 뒤 과도한 공백을 걷어냄.\n */\nexport function normalizeFormulaSpacing(s: string): string {\n // `\\cmd`, 단일 escape, 공백, 일반 문자 단위로 토큰화 (공백도 토큰으로 유지)\n const tokens: string[] = []\n let i = 0\n while (i < s.length) {\n const c = s[i]\n if (c === \"\\\\\") {\n let j = i + 1\n while (j < s.length && /[A-Za-z]/.test(s[j])) j++\n if (j === i + 1 && j < s.length) j++ // 단일 escape (`\\\\`, `\\{` 등)\n tokens.push(s.slice(i, j))\n i = j\n } else if (/\\s/.test(c)) {\n tokens.push(\" \")\n i++\n } else {\n tokens.push(c)\n i++\n }\n }\n\n const out: string[] = []\n for (let k = 0; k < tokens.length; k++) {\n if (tokens[k] !== \" \") {\n out.push(tokens[k])\n continue\n }\n // 공백 토큰 — 유지 여부 결정\n // 연속 공백은 나중에 병합 (아래 로직은 앞뒤 non-space 토큰 기준)\n let prev = \"\"\n for (let p = k - 1; p >= 0; p--) {\n if (tokens[p] !== \" \") {\n prev = tokens[p]\n break\n }\n }\n let next = \"\"\n for (let q = k + 1; q < tokens.length; q++) {\n if (tokens[q] !== \" \") {\n next = tokens[q]\n break\n }\n }\n const prevIsCmd = /^\\\\[A-Za-z]+$/.test(prev)\n const nextIsAlpha = /^[A-Za-z]$/.test(next)\n if (prevIsCmd && nextIsAlpha) {\n // 앞 cmd 와 뒤 변수 분리용 공백 (연속 공백이더라도 하나만 남김)\n if (out.length === 0 || out[out.length - 1] !== \" \") {\n out.push(\" \")\n }\n }\n // 그 외 공백은 drop\n }\n\n // 앞/뒤 공백 제거\n while (out.length > 0 && out[0] === \" \") out.shift()\n while (out.length > 0 && out[out.length - 1] === \" \") out.pop()\n\n return out.join(\"\")\n}\n","/**\n * MFD (Mathematical Formula Detection) — YOLOv8 기반 수식 영역 검출\n *\n * 모델: breezedeus/pix2text-mfd (YOLOv8 small, imgsz=768, stride=32)\n * 출력: [1, 6, 12096] — (cx, cy, w, h, class0_score, class1_score) × anchors\n * - class 0 = embedding (inline 수식)\n * - class 1 = isolated (display 수식)\n *\n * 전처리: letterbox (긴 변 비율 유지 + 회색 패딩 114)\n * 후처리: conf ≥ 0.25 → class-per-class NMS (IoU ≥ 0.45)\n */\n\nimport type { InferenceSession, Tensor } from \"onnxruntime-node\"\nimport type { FormulaKind, PixelFrame, FormulaRegion } from \"./types.js\"\n\nconst MFD_IMG_SIZE = 768\nconst MFD_NUM_CLASSES = 2\nconst MFD_CHANNELS = 4 + MFD_NUM_CLASSES\n/** 기본 confidence 임계값 (Pix2Text 원본 0.25 — 누락은 적지만 noise 발생) */\nconst MFD_CONF_INLINE = 0.30\n/** display 수식 임계값 — diagram/figure 오탐 감소 목적으로 inline 보다 높게 */\nconst MFD_CONF_DISPLAY = 0.40\nconst MFD_IOU_THRESHOLD = 0.45\n/** 최소 bbox 면적 (px²) — 이보다 작으면 OCR noise 가능성 높음 */\nconst MFD_MIN_AREA = 80\nconst PAD_VALUE = 114 / 255\n\ninterface RawDetection {\n x1: number\n y1: number\n x2: number\n y2: number\n kind: FormulaKind\n score: number\n}\n\n/**\n * 단일 페이지 이미지에서 수식 영역 검출 (인식 없음 — recognizer 로 넘김).\n * bbox 는 원본 이미지 좌표계.\n */\nexport async function detectFormulaRegions(\n session: InferenceSession,\n frame: PixelFrame,\n ort: typeof import(\"onnxruntime-node\"),\n): Promise<Omit<FormulaRegion, \"latex\">[]> {\n const { scale, padX, padY, tensor } = letterbox(frame, MFD_IMG_SIZE)\n\n const input = new ort.Tensor(\"float32\", tensor, [1, 3, MFD_IMG_SIZE, MFD_IMG_SIZE])\n const feeds: Record<string, Tensor> = { images: input }\n const outputs = await session.run(feeds)\n\n const firstKey = Object.keys(outputs)[0]\n const out = outputs[firstKey]\n if (!out || out.type !== \"float32\") {\n throw new Error(\"MFD 출력 없음 또는 dtype 불일치\")\n }\n\n const outDims = out.dims\n if (outDims.length !== 3) {\n throw new Error(`MFD 출력 차원 예상 3, 실제 ${outDims.length}: [${outDims.join(\",\")}]`)\n }\n const channels = outDims[1]\n const anchors = outDims[2]\n if (channels !== MFD_CHANNELS) {\n throw new Error(`MFD 채널 수 예상 ${MFD_CHANNELS}, 실제 ${channels}`)\n }\n if (anchors <= 0) return []\n\n const data = out.data as Float32Array\n const candidates: RawDetection[] = []\n\n for (let a = 0; a < anchors; a++) {\n const cx = data[a]\n const cy = data[anchors + a]\n const w = data[2 * anchors + a]\n const h = data[3 * anchors + a]\n\n let bestCls = 0\n let bestScore = 0\n for (let c = 0; c < MFD_NUM_CLASSES; c++) {\n const s = data[(4 + c) * anchors + a]\n if (s > bestScore) {\n bestScore = s\n bestCls = c\n }\n }\n // 클래스별 임계값 — display 쪽은 diagram/figure 오탐 감소 목적으로 inline 보다 엄격.\n const threshold = bestCls === 1 ? MFD_CONF_DISPLAY : MFD_CONF_INLINE\n if (bestScore < threshold) continue\n\n // letterbox → 원본 좌표\n let x1 = (cx - w / 2 - padX) / scale\n let y1 = (cy - h / 2 - padY) / scale\n let x2 = (cx + w / 2 - padX) / scale\n let y2 = (cy + h / 2 - padY) / scale\n\n x1 = clamp(x1, 0, frame.width - 1)\n y1 = clamp(y1, 0, frame.height - 1)\n x2 = clamp(x2, 0, frame.width - 1)\n y2 = clamp(y2, 0, frame.height - 1)\n\n if (x2 - x1 < 2 || y2 - y1 < 2) continue\n // 너무 작은 bbox (픽셀 단위 noise 로 보이는 영역) 제외\n if ((x2 - x1) * (y2 - y1) < MFD_MIN_AREA) continue\n\n candidates.push({\n x1,\n y1,\n x2,\n y2,\n kind: bestCls === 1 ? \"display\" : \"inline\",\n score: bestScore,\n })\n }\n\n // 클래스별 NMS (혼합 클래스 bbox 간 중복은 허용 — Pix2Text 원본 동작)\n const kept: RawDetection[] = []\n for (const kind of [\"inline\", \"display\"] as FormulaKind[]) {\n const subset = candidates.filter((c) => c.kind === kind)\n kept.push(...nms(subset, MFD_IOU_THRESHOLD))\n }\n\n // 읽기 순서 정렬 (위→아래, 왼→오른쪽)\n kept.sort((a, b) => a.y1 - b.y1 || a.x1 - b.x1)\n\n return kept.map((d) => ({\n bbox: { x1: d.x1, y1: d.y1, x2: d.x2, y2: d.y2 },\n kind: d.kind,\n score: d.score,\n }))\n}\n\n/**\n * YOLOv8 letterbox 전처리 — RGBA 프레임을 target×target float32 CHW tensor 로.\n *\n * - 긴 변 기준으로 비율 유지 리사이즈\n * - 가운데 패딩 (회색 114)\n * - /255 정규화\n * - CHW 레이아웃\n */\nexport function letterbox(\n frame: PixelFrame,\n target: number,\n): { scale: number; padX: number; padY: number; tensor: Float32Array } {\n const w = frame.width\n const h = frame.height\n const scale = Math.min(target / w, target / h)\n const newW = Math.max(1, Math.round(w * scale))\n const newH = Math.max(1, Math.round(h * scale))\n const padX = (target - newW) / 2\n const padY = (target - newH) / 2\n const offX = Math.floor(padX)\n const offY = Math.floor(padY)\n\n const ts = target\n const tensor = new Float32Array(3 * ts * ts)\n tensor.fill(PAD_VALUE)\n\n // RGBA 원본 → 먼저 nearest-neighbor resize 한 뒤 복사.\n // (sharp 로 정확한 bilinear 가능하지만, 여기선 이미 렌더링된 bitmap 이 충분히 해상도 높음 —\n // 결과는 동일한 수준으로 수식 박스 위치 맞춤. Rust 구현도 Triangle 사용.)\n const src = frame.data\n const srcW = frame.width\n const srcH = frame.height\n\n for (let y = 0; y < newH; y++) {\n const sy = Math.min(srcH - 1, Math.floor((y + 0.5) / newH * srcH))\n for (let x = 0; x < newW; x++) {\n const sx = Math.min(srcW - 1, Math.floor((x + 0.5) / newW * srcW))\n const srcIdx = (sy * srcW + sx) * 4\n const r = src[srcIdx]\n const g = src[srcIdx + 1]\n const b = src[srcIdx + 2]\n\n const tx = x + offX\n const ty = y + offY\n const idx = ty * ts + tx\n tensor[idx] = r / 255\n tensor[ts * ts + idx] = g / 255\n tensor[2 * ts * ts + idx] = b / 255\n }\n }\n\n return { scale, padX, padY, tensor }\n}\n\nfunction nms(cands: RawDetection[], iouThreshold: number): RawDetection[] {\n const sorted = [...cands].sort((a, b) => b.score - a.score)\n const kept: RawDetection[] = []\n for (const cand of sorted) {\n let keep = true\n for (const k of kept) {\n if (iou(cand, k) > iouThreshold) {\n keep = false\n break\n }\n }\n if (keep) kept.push(cand)\n }\n return kept\n}\n\nfunction iou(\n a: { x1: number; y1: number; x2: number; y2: number },\n b: { x1: number; y1: number; x2: number; y2: number },\n): number {\n const x1 = Math.max(a.x1, b.x1)\n const y1 = Math.max(a.y1, b.y1)\n const x2 = Math.min(a.x2, b.x2)\n const y2 = Math.min(a.y2, b.y2)\n const interW = Math.max(0, x2 - x1)\n const interH = Math.max(0, y2 - y1)\n const inter = interW * interH\n const areaA = Math.max(0, a.x2 - a.x1) * Math.max(0, a.y2 - a.y1)\n const areaB = Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1)\n const union = areaA + areaB - inter\n return union <= 0 ? 0 : inter / union\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n if (v < lo) return lo\n if (v > hi) return hi\n return v\n}\n\n// 테스트 용도로만 export\nexport const __internal = { nms, iou, letterbox }\n","/**\n * MFR (Mathematical Formula Recognition) — DeiT encoder + TrOCR decoder\n *\n * 모델: breezedeus/pix2text-mfr\n * - encoder: DeiT distilled, input 384×384 → output [1, 578, 384]\n * - decoder: TrOCR (no KV cache), vocab=1200, EOS=2\n *\n * 전처리 (DeiT): Catmull-Rom resize → /255 → (x - 0.5) / 0.5 == x*2 - 1\n *\n * 디코딩: greedy, start=[2], stop at 2 또는 max_new_tokens=256\n * decoder 는 매 스텝 전체 시퀀스를 재계산 (no KV cache — 모델 고정)\n */\n\nimport type { InferenceSession, Tensor } from \"onnxruntime-node\"\nimport type { PreTrainedTokenizer } from \"@huggingface/transformers\"\nimport { postProcessLatex } from \"./postprocess.js\"\nimport type { PixelFrame } from \"./types.js\"\n\nconst MFR_IMG_SIZE = 384\nconst MFR_ENC_HIDDEN = 384\nconst MFR_MAX_NEW_TOKENS = 256\nconst MFR_EOS_ID = 2\nconst MFR_PAD_ID = 0\n\nexport interface RecognizeDeps {\n encoder: InferenceSession\n decoder: InferenceSession\n tokenizer: PreTrainedTokenizer\n ort: typeof import(\"onnxruntime-node\")\n}\n\n/**\n * 단일 수식 crop 을 LaTeX 로 인식.\n * 입력 frame 은 이미 해당 영역만 잘린 RGBA 이미지이어야 한다 (recognizer 가 resize 수행).\n */\nexport async function recognizeFormula(\n deps: RecognizeDeps,\n crop: PixelFrame,\n): Promise<string> {\n const tensor = deitPreprocess(crop, MFR_IMG_SIZE)\n const { ort, encoder, decoder, tokenizer } = deps\n\n const pixelInput = new ort.Tensor(\"float32\", tensor, [1, 3, MFR_IMG_SIZE, MFR_IMG_SIZE])\n const encOut = await encoder.run({ pixel_values: pixelInput })\n\n const encKey =\n Object.keys(encOut).find((k) => k.includes(\"hidden\")) ?? Object.keys(encOut)[0]\n const encTensor = encOut[encKey]\n if (!encTensor || encTensor.type !== \"float32\") {\n throw new Error(\"MFR encoder 출력 없음\")\n }\n const encDims = encTensor.dims\n if (encDims.length !== 3) {\n throw new Error(`MFR encoder 차원 예상 3, 실제 ${encDims.length}`)\n }\n const encSeq = encDims[1]\n const encHidden = encDims[2]\n if (encHidden !== MFR_ENC_HIDDEN) {\n throw new Error(`MFR encoder hidden 예상 ${MFR_ENC_HIDDEN}, 실제 ${encHidden}`)\n }\n const encData = encTensor.data as Float32Array\n\n // greedy decode\n const tokens: number[] = [MFR_EOS_ID] // decoder_start_token_id\n\n for (let step = 0; step < MFR_MAX_NEW_TOKENS; step++) {\n const seqLen = tokens.length\n const idsArr = BigInt64Array.from(tokens.map((t) => BigInt(t)))\n const idsTensor = new ort.Tensor(\"int64\", idsArr, [1, seqLen])\n\n // encoder_hidden_states 는 매 스텝 동일 — 복사 필요 (Tensor 는 buffer 소유)\n const hidCopy = new Float32Array(encData)\n const hidTensor = new ort.Tensor(\"float32\", hidCopy, [1, encSeq, encHidden])\n\n const decOut = await decoder.run({\n input_ids: idsTensor,\n encoder_hidden_states: hidTensor,\n })\n\n const logitKey =\n Object.keys(decOut).find((k) => k.includes(\"logit\")) ?? Object.keys(decOut)[0]\n const logitsTensor = decOut[logitKey]\n if (!logitsTensor || logitsTensor.type !== \"float32\") {\n throw new Error(\"MFR decoder logits 없음\")\n }\n const dims = logitsTensor.dims\n if (dims.length !== 3) {\n throw new Error(`MFR decoder 차원 예상 3, 실제 ${dims.length}`)\n }\n const decSeq = dims[1]\n const vocab = dims[2]\n const logitsData = logitsTensor.data as Float32Array\n\n // 마지막 step logits 만\n const lastOffset = (decSeq - 1) * vocab\n let bestId = 0\n let bestVal = -Infinity\n for (let v = 0; v < vocab; v++) {\n const val = logitsData[lastOffset + v]\n if (val > bestVal) {\n bestVal = val\n bestId = v\n }\n }\n tokens.push(bestId)\n if (bestId === MFR_EOS_ID) break\n }\n\n // 첫 start 토큰 제외 + EOS/PAD 제거\n const body: number[] = []\n for (let i = 1; i < tokens.length; i++) {\n const t = tokens[i]\n if (t === MFR_EOS_ID) break\n if (t === MFR_PAD_ID) continue\n if (t < 0) continue\n body.push(t)\n }\n\n // Transformers.js tokenizer.decode 는 skip_special_tokens=true 기본\n const raw = tokenizer.decode(body, { skip_special_tokens: true })\n return postProcessLatex(raw)\n}\n\n/**\n * DeiT preprocess:\n * resize to 384×384 (bilinear)\n * (pixel/255 - 0.5) / 0.5 == pixel/127.5 - 1\n * CHW layout\n */\nexport function deitPreprocess(crop: PixelFrame, target: number): Float32Array {\n const ts = target\n const out = new Float32Array(3 * ts * ts)\n const { data: src, width: srcW, height: srcH } = crop\n\n // 단순 nearest (이미 수식 crop 은 300DPI 근방이라 충분). 필요시 sharp bilinear 로 교체.\n for (let y = 0; y < ts; y++) {\n const sy = Math.min(srcH - 1, Math.max(0, Math.floor(((y + 0.5) / ts) * srcH)))\n for (let x = 0; x < ts; x++) {\n const sx = Math.min(srcW - 1, Math.max(0, Math.floor(((x + 0.5) / ts) * srcW)))\n const srcIdx = (sy * srcW + sx) * 4\n const r = src[srcIdx]\n const g = src[srcIdx + 1]\n const b = src[srcIdx + 2]\n\n const idx = y * ts + x\n out[idx] = r / 127.5 - 1\n out[ts * ts + idx] = g / 127.5 - 1\n out[2 * ts * ts + idx] = b / 127.5 - 1\n }\n }\n return out\n}\n","/**\n * 수식 OCR 파이프라인 — PDFium 렌더 + MFD + MFR 통합\n *\n * 한 번 생성된 FormulaPipeline 인스턴스는 ONNX 세션을 재사용해 여러 PDF 에 적용 가능.\n * Docufinder 는 CLI 프로세스 단위로 새로 띄우기 때문에 세션 재사용은 PDF 내부 페이지 루프에만 적용됨.\n */\n\nimport type { PDFiumLibrary, PDFiumDocument } from \"@hyzyla/pdfium\"\nimport type { InferenceSession } from \"onnxruntime-node\"\nimport type { PreTrainedTokenizer } from \"@huggingface/transformers\"\n\n/** sharp 은 CommonJS `export =` 형태라 타입을 직접 import 하기 까다로움. 함수형 시그니처만 사용. */\ntype SharpFactory = (\n input: Uint8Array | Buffer,\n options?: { raw?: { width: number; height: number; channels: number } },\n) => {\n extract(region: { left: number; top: number; width: number; height: number }): {\n raw(): { toBuffer(): Promise<Buffer> }\n }\n}\n\nimport { detectFormulaRegions } from \"./detector.js\"\nimport { recognizeFormula } from \"./recognizer.js\"\nimport {\n getFormulaModelsDir,\n MFD_MODEL,\n MFR_ENCODER_MODEL,\n MFR_DECODER_MODEL,\n MFR_TOKENIZER,\n} from \"./models.js\"\nimport type { FormulaRegion, PixelFrame } from \"./types.js\"\nimport { join } from \"path\"\n\n/** PDF 페이지 렌더 해상도 (scale=2 → 약 144 DPI). 수식 인식에는 이 이상은 과함. */\nconst RENDER_SCALE = 2\n\nexport interface FormulaPipelineOptions {\n /** 수식 인식 페이지 스케일 (기본 2). 값을 올리면 작은 수식 인식률 ↑, 속도 ↓. */\n scale?: number\n /** 페이지별 최대 수식 수 (안전 상한). 기본 50. */\n maxRegionsPerPage?: number\n /** 페이지 하나당 타임아웃 (ms, 기본 60000 = 1분). 초과 시 해당 페이지 skip. */\n pageTimeoutMs?: number\n}\n\nexport interface PageFormulaResult {\n pageNumber: number\n /** 페이지 전체 렌더 이미지의 가로/세로 (원본 좌표계, scale 반영됨) */\n renderedWidth: number\n renderedHeight: number\n /** 원본 PDF 포인트 기준 너비/높이 (pdfjs 와 좌표 매핑용) */\n pdfWidth: number\n pdfHeight: number\n regions: FormulaRegion[]\n}\n\n/**\n * 수식 OCR 실행 컨텍스트. 처음 호출 시 무겁게 초기화 (ONNX 세션 로드),\n * 이후 runOnBuffer / runOnPages 를 여러 번 호출해 재사용 가능.\n *\n * 모든 의존성(onnxruntime-node, @huggingface/transformers, @hyzyla/pdfium, sharp)은\n * optional 이므로 dynamic import. 미설치 시 초기화에서 명확한 에러 메시지 반환.\n */\nexport class FormulaPipeline {\n private mfd: InferenceSession\n private encoder: InferenceSession\n private decoder: InferenceSession\n private tokenizer: PreTrainedTokenizer\n private ort: typeof import(\"onnxruntime-node\")\n private sharp: SharpFactory\n private pdfium: PDFiumLibrary\n private opts: Required<FormulaPipelineOptions>\n\n private constructor(parts: {\n mfd: InferenceSession\n encoder: InferenceSession\n decoder: InferenceSession\n tokenizer: PreTrainedTokenizer\n ort: typeof import(\"onnxruntime-node\")\n sharp: SharpFactory\n pdfium: PDFiumLibrary\n opts: Required<FormulaPipelineOptions>\n }) {\n this.mfd = parts.mfd\n this.encoder = parts.encoder\n this.decoder = parts.decoder\n this.tokenizer = parts.tokenizer\n this.ort = parts.ort\n this.sharp = parts.sharp\n this.pdfium = parts.pdfium\n this.opts = parts.opts\n }\n\n /**\n * 수식 OCR 엔진 초기화. 모델 파일이 로컬에 없으면 즉시 실패 — 호출자가\n * `ensureFormulaModels()` 를 먼저 돌려야 한다.\n */\n static async create(options?: FormulaPipelineOptions): Promise<FormulaPipeline> {\n const opts: Required<FormulaPipelineOptions> = {\n scale: options?.scale ?? RENDER_SCALE,\n maxRegionsPerPage: options?.maxRegionsPerPage ?? 50,\n pageTimeoutMs: options?.pageTimeoutMs ?? 60_000,\n }\n\n const [ortMod, sharpModRaw, hfMod, pdfiumMod] = await Promise.all([\n tryImport<typeof import(\"onnxruntime-node\")>(\n \"onnxruntime-node\",\n () => import(\"onnxruntime-node\"),\n ),\n tryImport<{ default?: SharpFactory } & SharpFactory>(\n \"sharp\",\n () => import(\"sharp\") as unknown as Promise<{ default?: SharpFactory } & SharpFactory>,\n ),\n tryImport<typeof import(\"@huggingface/transformers\")>(\n \"@huggingface/transformers\",\n () => import(\"@huggingface/transformers\"),\n ),\n tryImport<typeof import(\"@hyzyla/pdfium\")>(\n \"@hyzyla/pdfium\",\n () => import(\"@hyzyla/pdfium\"),\n ),\n ])\n // CJS `export =` + ESM interop → default 속성에 래핑될 때도, 안 될 때도 있음.\n const sharpAny = sharpModRaw as { default?: SharpFactory } | SharpFactory\n const sharpMod: SharpFactory =\n typeof sharpAny === \"function\"\n ? sharpAny\n : (sharpAny.default ?? (sharpAny as unknown as SharpFactory))\n\n const modelsDir = getFormulaModelsDir()\n const mfdPath = join(modelsDir, MFD_MODEL.filename)\n const encPath = join(modelsDir, MFR_ENCODER_MODEL.filename)\n const decPath = join(modelsDir, MFR_DECODER_MODEL.filename)\n const tokPath = join(modelsDir, MFR_TOKENIZER.filename)\n\n const sessionOpts: import(\"onnxruntime-node\").InferenceSession.SessionOptions = {\n graphOptimizationLevel: \"all\",\n executionProviders: [\"cpu\"],\n }\n\n // 각 세션은 I/O 바운드 아님 → 병렬 로드로 1~2초 단축\n const [mfd, encoder, decoder] = await Promise.all([\n ortMod.InferenceSession.create(mfdPath, sessionOpts),\n ortMod.InferenceSession.create(encPath, sessionOpts),\n ortMod.InferenceSession.create(decPath, sessionOpts),\n ])\n\n // Transformers.js 의 PreTrainedTokenizer 를 로컬 tokenizer.json 으로 로드.\n // AutoTokenizer.from_pretrained 는 HF hub 접근을 시도하므로 우회.\n const { readFile } = await import(\"fs/promises\")\n const tokenizerJson = JSON.parse(await readFile(tokPath, \"utf-8\"))\n const PretrainedCtor = hfMod.PreTrainedTokenizer as unknown as new (\n json: unknown,\n config: Record<string, unknown>,\n ) => PreTrainedTokenizer\n const tokenizer = new PretrainedCtor(tokenizerJson, {})\n\n const pdfium = await pdfiumMod.PDFiumLibrary.init()\n\n return new FormulaPipeline({\n mfd,\n encoder,\n decoder,\n tokenizer,\n ort: ortMod,\n sharp: sharpMod,\n pdfium,\n opts,\n })\n }\n\n /** 리소스 해제 — 더 이상 사용하지 않을 때 호출. */\n async destroy(): Promise<void> {\n // onnxruntime-node InferenceSession 은 release() 없음 (GC 의존).\n try {\n this.pdfium.destroy()\n } catch {\n // ignore\n }\n }\n\n /**\n * PDF 버퍼를 열어 페이지별 수식 영역을 인식한다.\n * 실패한 페이지는 skip (에러 전파 없음 — 로그만).\n *\n * @param pageFilter null 이면 전체 페이지. Set 이면 1-based 페이지 번호 일치만.\n */\n async runOnBuffer(\n buffer: ArrayBuffer | Uint8Array,\n pageFilter: Set<number> | null = null,\n onPageProgress?: (pageNumber: number, total: number) => void,\n ): Promise<PageFormulaResult[]> {\n const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n const doc: PDFiumDocument = await this.pdfium.loadDocument(view)\n try {\n const pages: PageFormulaResult[] = []\n for (const page of doc.pages()) {\n // pdfium page.number 는 0-based pageIndex — pdfjs 블록/pageFilter 는 1-based.\n // 환산 없이는 필터가 한 페이지 밀리고 수식이 이전 페이지 블록에 붙는다 (off-by-one).\n const pageNo = page.number + 1\n if (pageFilter && !pageFilter.has(pageNo)) continue\n\n onPageProgress?.(pageNo, doc.getPageCount())\n\n try {\n const result = await withTimeout(\n this.processPage(pageNo, page),\n this.opts.pageTimeoutMs,\n `formula page ${pageNo} timed out after ${this.opts.pageTimeoutMs}ms`,\n )\n if (result) pages.push(result)\n } catch (e) {\n // 페이지 단위 실패는 조용히 넘어간다 — 전체 PDF 파싱 실패 방지.\n process.stderr.write(\n `[kordoc-formula] page ${pageNo} skipped: ${(e as Error).message}\\n`,\n )\n }\n }\n return pages\n } finally {\n doc.destroy()\n }\n }\n\n private async processPage(\n pageNumber: number,\n page: import(\"@hyzyla/pdfium\").PDFiumPage,\n ): Promise<PageFormulaResult | null> {\n const { originalWidth: pdfWidth, originalHeight: pdfHeight } = page.getOriginalSize()\n\n const sharpCtor = this.sharp\n const rendered = await page.render({\n scale: this.opts.scale,\n render: async ({ data, width, height }) => {\n // 임시로 PNG 로 인코딩하지 않고 raw 그대로 반환 — 바로 쓸 거라서.\n return data\n },\n })\n // rendered.data 는 BGRA — sharp 로 RGBA 로 변환\n const { data: bgra, width: rw, height: rh } = rendered\n const rgba = bgraToRgba(bgra)\n\n const pageFrame: PixelFrame = { width: rw, height: rh, data: rgba }\n\n // 1) 수식 영역 검출\n const regions0 = await detectFormulaRegions(this.mfd, pageFrame, this.ort)\n if (regions0.length === 0) {\n return { pageNumber, renderedWidth: rw, renderedHeight: rh, pdfWidth, pdfHeight, regions: [] }\n }\n\n const capped = regions0.slice(0, this.opts.maxRegionsPerPage)\n const regions: FormulaRegion[] = []\n\n // 2) 각 영역 crop → 인식\n for (const r of capped) {\n const x1 = Math.floor(Math.max(0, r.bbox.x1))\n const y1 = Math.floor(Math.max(0, r.bbox.y1))\n const x2 = Math.ceil(Math.min(rw, r.bbox.x2))\n const y2 = Math.ceil(Math.min(rh, r.bbox.y2))\n const cw = x2 - x1\n const ch = y2 - y1\n if (cw < 4 || ch < 4) continue\n\n // sharp 로 raw RGBA 에서 crop → raw RGBA 반환\n const cropRgba = await sharpCtor(rgba, {\n raw: { width: rw, height: rh, channels: 4 },\n })\n .extract({ left: x1, top: y1, width: cw, height: ch })\n .raw()\n .toBuffer()\n\n const cropFrame: PixelFrame = { width: cw, height: ch, data: new Uint8Array(cropRgba) }\n\n let latex = \"\"\n try {\n latex = await recognizeFormula(\n {\n encoder: this.encoder,\n decoder: this.decoder,\n tokenizer: this.tokenizer,\n ort: this.ort,\n },\n cropFrame,\n )\n } catch (e) {\n process.stderr.write(\n `[kordoc-formula] recognize failed at page ${pageNumber} ${JSON.stringify(r.bbox)}: ${(e as Error).message}\\n`,\n )\n latex = \"\"\n }\n\n regions.push({ ...r, latex })\n }\n\n return {\n pageNumber,\n renderedWidth: rw,\n renderedHeight: rh,\n pdfWidth,\n pdfHeight,\n regions,\n }\n }\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 // 타임아웃으로 race 가 끝난 뒤에도 원 promise 는 계속 실행된다 — 사후 reject 가\n // unhandled rejection(Node 기본 정책: 프로세스 종료)이 되지 않게 흡수\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 로 변환 (Red ↔ Blue 스왑). */\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] // R ← B\n out[i + 1] = bgra[i + 1] // G ← G\n out[i + 2] = bgra[i] // B ← R\n out[i + 3] = bgra[i + 3] // A ← A\n }\n return out\n}\n"],"mappings":"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","names":[]}