kordoc 4.22.18 → 4.23.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.
- package/README.md +4 -2
- package/dist/{-OTAPIBUW.js → -6U2DXAP7.js} +17 -11
- package/dist/{chunk-TLGOPNMC.cjs → chunk-27HAGR4H.cjs} +24 -24
- package/dist/{chunk-TLGOPNMC.cjs.map → chunk-27HAGR4H.cjs.map} +1 -1
- package/dist/{chunk-NUQG356J.cjs → chunk-4RQYUDGT.cjs} +3 -3
- package/dist/{chunk-NUQG356J.cjs.map → chunk-4RQYUDGT.cjs.map} +1 -1
- package/dist/{chunk-G4PPT5CR.js → chunk-5BGQOKZS.js} +6 -6
- package/dist/{chunk-3AVHW7UT.js → chunk-B4WJQMSZ.js} +2 -2
- package/dist/{chunk-MI2YNEQJ.js → chunk-CU7WLAPQ.js} +4086 -2640
- package/dist/chunk-CU7WLAPQ.js.map +1 -0
- package/dist/{chunk-MKEBORZM.js → chunk-DE7ETARS.js} +2 -2
- package/dist/{chunk-XOX5L7WN.js → chunk-EEM4AJTY.js} +2 -2
- package/dist/{chunk-N5QRS6VT.js → chunk-FDRIJEES.js} +2 -2
- package/dist/{chunk-DQBY4PQ4.js → chunk-H7K2FTV2.js} +8 -6
- package/dist/{chunk-DQBY4PQ4.js.map → chunk-H7K2FTV2.js.map} +1 -1
- package/dist/{chunk-6LTGCEGP.js → chunk-IMS56622.js} +2 -2
- package/dist/{chunk-OWO3RVJY.js → chunk-K6CTZQZW.js} +2 -2
- package/dist/{chunk-SJYHFKKB.js → chunk-KMHHSOSR.js} +2 -2
- package/dist/{chunk-MW42GCQ4.js → chunk-KPCGYMQK.js} +2 -2
- package/dist/{chunk-WITGYMV5.js → chunk-KRK65GHM.js} +4 -4
- package/dist/{chunk-JO5OR4F5.js → chunk-KWBPDHFT.js} +2 -2
- package/dist/{chunk-BXZW4Q5G.cjs → chunk-PWHYNFTN.cjs} +6 -6
- package/dist/{chunk-BXZW4Q5G.cjs.map → chunk-PWHYNFTN.cjs.map} +1 -1
- package/dist/{chunk-DKZZVFTK.cjs → chunk-Q2SOQOWP.cjs} +3 -3
- package/dist/{chunk-DKZZVFTK.cjs.map → chunk-Q2SOQOWP.cjs.map} +1 -1
- package/dist/{chunk-3GVPNEIG.js → chunk-Q6T5BIWZ.js} +2 -2
- package/dist/{chunk-3YHUMMZ5.js → chunk-QE2XH2ZI.js} +8 -1
- package/dist/chunk-QE2XH2ZI.js.map +1 -0
- package/dist/{chunk-VCGGP7SY.js → chunk-QV367L5S.js} +2 -2
- package/dist/{chunk-7QLKNWMN.js → chunk-RJZQLUGW.js} +4 -4
- package/dist/{chunk-RTFUKVOX.js → chunk-SQS4D55F.js} +3 -3
- package/dist/{chunk-TLU4OKIO.js → chunk-SSKZ2KL2.js} +2 -2
- package/dist/{chunk-B3Z2PY4R.cjs → chunk-T63JP3GD.cjs} +2 -2
- package/dist/{chunk-B3Z2PY4R.cjs.map → chunk-T63JP3GD.cjs.map} +1 -1
- package/dist/{chunk-XIDMSNU2.cjs → chunk-TSKGMN46.cjs} +23 -23
- package/dist/{chunk-XIDMSNU2.cjs.map → chunk-TSKGMN46.cjs.map} +1 -1
- package/dist/{chunk-M7SCUHXE.js → chunk-UZZGT6XI.js} +4 -4
- package/dist/{chunk-S2WH5IZ3.js → chunk-WG43OZYN.js} +2 -2
- package/dist/{chunk-S2WH5IZ3.js.map → chunk-WG43OZYN.js.map} +1 -1
- package/dist/{chunk-HX24KLQN.js → chunk-XBTFGKMR.js} +2 -2
- package/dist/{chunk-VROCP4X4.js → chunk-YPTW52ZJ.js} +2 -2
- package/dist/{chunk-VROCP4X4.js.map → chunk-YPTW52ZJ.js.map} +1 -1
- package/dist/{chunk-NV3WEN4C.js → chunk-ZC3WNCZV.js} +3 -3
- package/dist/chunks-FPFZZPUE.js +10 -0
- package/dist/cli.js +148 -141
- package/dist/cli.js.map +1 -1
- package/dist/{formula-2ISHDHXF.cjs → formula-6FPGJ7PI.cjs} +9 -9
- package/dist/{formula-2ISHDHXF.cjs.map → formula-6FPGJ7PI.cjs.map} +1 -1
- package/dist/{formula-WHYRNHRE.js → formula-73LPYA6R.js} +4 -4
- package/dist/{formula-E5G3A3NZ.js → formula-L3RKXO2Z.js} +3 -3
- package/dist/{image-ocr-6J7B5UVA.js → image-ocr-37S4CHFI.js} +9 -9
- package/dist/{image-ocr-MRTNIUZQ.cjs → image-ocr-4IW435IF.cjs} +17 -17
- package/dist/{image-ocr-MRTNIUZQ.cjs.map → image-ocr-4IW435IF.cjs.map} +1 -1
- package/dist/{image-ocr-5WJQITNQ.js → image-ocr-R42ZVGCH.js} +6 -6
- package/dist/index.cjs +3379 -1931
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -3
- package/dist/index.d.ts +114 -3
- package/dist/index.js +2795 -1347
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +134 -32
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-CNNDBCIQ.js → model-bundle-L3TZDQJF.js} +5 -5
- package/dist/{models-PQYMCKFR.js → models-2IDIYAGL.js} +5 -5
- package/dist/{parse-worker-v2-Z3W53OUZ.js → parse-worker-v2-NVI6YJN6.js} +12 -12
- package/dist/{parser-YHUWM4KF.js → parser-7BDTFYHE.js} +9 -9
- package/dist/{parser-UYBCCGY4.js → parser-KHVN5B3I.js} +12 -12
- package/dist/{parser-2YUGVORR.cjs → parser-M7HKGKEN.cjs} +157 -157
- package/dist/{parser-2YUGVORR.cjs.map → parser-M7HKGKEN.cjs.map} +1 -1
- package/dist/{pdf-ocr-6JSBUWUO.js → pdf-ocr-EYDE264W.js} +9 -9
- package/dist/pdf-ocr-HUL7G577.cjs +31 -0
- package/dist/{pdf-ocr-6Q34QQ7A.cjs.map → pdf-ocr-HUL7G577.cjs.map} +1 -1
- package/dist/{pdf-ocr-DE7PYWN7.js → pdf-ocr-XFMBTHEE.js} +6 -6
- package/dist/{profile-io-DCZ4DINU.js → profile-io-FDA5PEW2.js} +3 -3
- package/dist/{rasterize-6XD2LLEN.js → rasterize-NR6JJSJV.js} +3 -3
- package/dist/{redact-doc-2BLB3IOP.js → redact-doc-K74LDR3O.js} +11 -11
- package/dist/{render-YPZMJSZL.js → render-RM5BAYGB.js} +7 -7
- package/dist/seal-LRVVOPOD.js +10 -0
- package/dist/{setup-GUE2OUWB.js → setup-M4RKKK3P.js} +3 -3
- package/dist/visual-WTFQPXO2.js +21 -0
- package/dist/{watch-EILA6XGX.js → watch-ZWD4LADV.js} +12 -12
- package/package.json +1 -1
- package/THIRD_PARTY/hwp-auto-docfit.LICENSE +0 -23
- package/dist/chunk-3YHUMMZ5.js.map +0 -1
- package/dist/chunk-MI2YNEQJ.js.map +0 -1
- package/dist/chunks-GMADIKNS.js +0 -10
- package/dist/pdf-ocr-6Q34QQ7A.cjs +0 -31
- package/dist/seal-32KIG7OS.js +0 -10
- package/dist/visual-Y32AKOM4.js +0 -21
- /package/dist/{-OTAPIBUW.js.map → -6U2DXAP7.js.map} +0 -0
- /package/dist/{chunk-G4PPT5CR.js.map → chunk-5BGQOKZS.js.map} +0 -0
- /package/dist/{chunk-3AVHW7UT.js.map → chunk-B4WJQMSZ.js.map} +0 -0
- /package/dist/{chunk-MKEBORZM.js.map → chunk-DE7ETARS.js.map} +0 -0
- /package/dist/{chunk-XOX5L7WN.js.map → chunk-EEM4AJTY.js.map} +0 -0
- /package/dist/{chunk-N5QRS6VT.js.map → chunk-FDRIJEES.js.map} +0 -0
- /package/dist/{chunk-6LTGCEGP.js.map → chunk-IMS56622.js.map} +0 -0
- /package/dist/{chunk-OWO3RVJY.js.map → chunk-K6CTZQZW.js.map} +0 -0
- /package/dist/{chunk-SJYHFKKB.js.map → chunk-KMHHSOSR.js.map} +0 -0
- /package/dist/{chunk-MW42GCQ4.js.map → chunk-KPCGYMQK.js.map} +0 -0
- /package/dist/{chunk-WITGYMV5.js.map → chunk-KRK65GHM.js.map} +0 -0
- /package/dist/{chunk-JO5OR4F5.js.map → chunk-KWBPDHFT.js.map} +0 -0
- /package/dist/{chunk-3GVPNEIG.js.map → chunk-Q6T5BIWZ.js.map} +0 -0
- /package/dist/{chunk-VCGGP7SY.js.map → chunk-QV367L5S.js.map} +0 -0
- /package/dist/{chunk-7QLKNWMN.js.map → chunk-RJZQLUGW.js.map} +0 -0
- /package/dist/{chunk-RTFUKVOX.js.map → chunk-SQS4D55F.js.map} +0 -0
- /package/dist/{chunk-TLU4OKIO.js.map → chunk-SSKZ2KL2.js.map} +0 -0
- /package/dist/{chunk-M7SCUHXE.js.map → chunk-UZZGT6XI.js.map} +0 -0
- /package/dist/{chunk-HX24KLQN.js.map → chunk-XBTFGKMR.js.map} +0 -0
- /package/dist/{chunk-NV3WEN4C.js.map → chunk-ZC3WNCZV.js.map} +0 -0
- /package/dist/{chunks-GMADIKNS.js.map → chunks-FPFZZPUE.js.map} +0 -0
- /package/dist/{formula-WHYRNHRE.js.map → formula-73LPYA6R.js.map} +0 -0
- /package/dist/{formula-E5G3A3NZ.js.map → formula-L3RKXO2Z.js.map} +0 -0
- /package/dist/{image-ocr-6J7B5UVA.js.map → image-ocr-37S4CHFI.js.map} +0 -0
- /package/dist/{image-ocr-5WJQITNQ.js.map → image-ocr-R42ZVGCH.js.map} +0 -0
- /package/dist/{model-bundle-CNNDBCIQ.js.map → model-bundle-L3TZDQJF.js.map} +0 -0
- /package/dist/{models-PQYMCKFR.js.map → models-2IDIYAGL.js.map} +0 -0
- /package/dist/{parse-worker-v2-Z3W53OUZ.js.map → parse-worker-v2-NVI6YJN6.js.map} +0 -0
- /package/dist/{parser-YHUWM4KF.js.map → parser-7BDTFYHE.js.map} +0 -0
- /package/dist/{parser-UYBCCGY4.js.map → parser-KHVN5B3I.js.map} +0 -0
- /package/dist/{pdf-ocr-6JSBUWUO.js.map → pdf-ocr-EYDE264W.js.map} +0 -0
- /package/dist/{pdf-ocr-DE7PYWN7.js.map → pdf-ocr-XFMBTHEE.js.map} +0 -0
- /package/dist/{profile-io-DCZ4DINU.js.map → profile-io-FDA5PEW2.js.map} +0 -0
- /package/dist/{rasterize-6XD2LLEN.js.map → rasterize-NR6JJSJV.js.map} +0 -0
- /package/dist/{redact-doc-2BLB3IOP.js.map → redact-doc-K74LDR3O.js.map} +0 -0
- /package/dist/{render-YPZMJSZL.js.map → render-RM5BAYGB.js.map} +0 -0
- /package/dist/{seal-32KIG7OS.js.map → seal-LRVVOPOD.js.map} +0 -0
- /package/dist/{setup-GUE2OUWB.js.map → setup-M4RKKK3P.js.map} +0 -0
- /package/dist/{visual-Y32AKOM4.js.map → visual-WTFQPXO2.js.map} +0 -0
- /package/dist/{watch-EILA6XGX.js.map → watch-ZWD4LADV.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/form/seal.ts","../src/form/match.ts","../src/form/recognize.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, precheckZipSize } 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\"\nimport { readZipEntry } from \"../hwpx/parser-shared.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 precheckZipSize(hwpxBuffer) // 파싱 경로와 같은 ZIP bomb 가드\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 readZipEntry(zip.file(headerPath)!, \"text\")) : null\n\n const sectionXmls: string[] = []\n const scans: SectionScan[] = []\n for (let si = 0; si < sectionPaths.length; si++) {\n const xml = await readZipEntry(zip.file(sectionPaths[si])!, \"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 readZipEntry(zip.file(manifestPath)!, \"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","/** 양식 필드 매칭 공용 유틸 — filler.ts, filler-hwpx.ts에서 공유 */\n\nimport { LABEL_KEYWORDS, isBlankValue, isDatePlaceholder } from \"./recognize.js\"\nimport type { FormField } from \"../types.js\"\n\n/**\n * 채울 값 — 문자열이면 같은 라벨 모든 등장에 동일값(단일 양식),\n * 배열이면 등장 순서대로 하나씩 소진(2~30장 반복 양식·명부형 표).\n */\nexport type FillValue = string | string[]\n\n/**\n * 채울 값 입력 — 값만 주거나, 서식 변환(format)을 함께 지정한다.\n * 프로필엔 정준값 하나(생년월일 \"19900315\"), 서식마다 모양이 다를 때\n * format이 도구 안에서 변환한다 (claw-hwp secure-fill 포맷엔진 이식).\n */\nexport type FillInput = FillValue | { value: FillValue; format?: string }\n\n// ─── 값 서식 변환 (claw-hwp secure-fill formatValue 이식) ─────────────\n\nfunction parseYMD(v: string): { y: string; yy: string; m: string; d: string } | null {\n let y: string, m: string, day: string\n // 구분자(. - / 년월일 등)로 분리된 3토큰이면 미패딩 날짜(2026.7.5·2026년 7월 5일)로 우선 해석\n const parts = v.split(/[^0-9]+/).filter(Boolean)\n if (parts.length === 3) {\n const [yp, mp, dp] = parts\n const yn = +yp\n y = yp.length >= 3 ? yp : String(yn <= 29 ? 2000 + yn : 1900 + yn)\n m = mp.padStart(2, \"0\")\n day = dp.padStart(2, \"0\")\n } else {\n const d = v.replace(/\\D/g, \"\")\n if (d.length >= 8) { y = d.slice(0, 4); m = d.slice(4, 6); day = d.slice(6, 8) }\n else if (d.length === 6) { const yy = +d.slice(0, 2); y = String(yy <= 29 ? 2000 + yy : 1900 + yy); m = d.slice(2, 4); day = d.slice(4, 6) }\n else return null\n }\n // 월 1-12·일 1-31 범위 밖이면 fail-open(쓰레기 날짜 무음 기입 방지)\n if (+m < 1 || +m > 12 || +day < 1 || +day > 31) return null\n return { y, yy: y.slice(2), m, d: day }\n}\n\nfunction fmtDate(v: string, style: string): string {\n const p = parseYMD(v)\n if (!p) return v\n // 토큰 치환(대소문자 무시 — yyyy-MM-dd 혼용 수용, 긴 것 우선). 단독 m/d(선행 0\n // 제거)는 영문 리터럴(예: \"amended\")을 오염시키지 않게 영문자에 비인접일 때만 치환.\n return (style || \"yyyy-mm-dd\")\n .replace(/yyyy/gi, p.y).replace(/yy/gi, p.yy)\n .replace(/mm/gi, p.m).replace(/dd/gi, p.d)\n .replace(/(?<![a-z])m(?![a-z])/gi, String(+p.m))\n .replace(/(?<![a-z])d(?![a-z])/gi, String(+p.d))\n}\n\n/** 숫자 마스크 — `#` 하나가 값의 다음 숫자 하나를 소비, 리터럴은 통과 */\nfunction maskDigits(v: string, pattern: string): string {\n const ds = v.replace(/\\D/g, \"\")\n const need = (pattern.match(/#/g) ?? []).length\n // # 개수와 숫자 개수가 다르거나 패턴에 # 가 없으면, 그럴듯한 오값·끝자리 폐기·\n // 값 삭제 대신 원형을 반환한다 (무음 왜곡 금지 — fail-open).\n if (need === 0 || ds.length !== need) return v\n let i = 0\n return pattern.replace(/#/g, () => ds[i++])\n}\n\nfunction fmtPhone(v: string, style: string): string {\n const d = v.replace(/\\D/g, \"\")\n if (d.length < 9) return v\n // 서울(02)은 2자리 지역번호, 그 외(휴대폰 010·지역 0XX)는 3자리\n const areaLen = d.startsWith(\"02\") ? 2 : 3\n const a = d.slice(0, areaLen), b = d.slice(areaLen, -4), c = d.slice(-4)\n switch (style) {\n case \"digits\": return d\n case \"dot\": return `${a}.${b}.${c}`\n case \"space\": return `${a} ${b} ${c}`\n case \"intl\": return `+82-${d.slice(1, areaLen)}-${b}-${c}`\n case \"intl-paren\": return `82)${d.slice(1, areaLen)}-${b}-${c}`\n default: return `${a}-${b}-${c}` // hyphen\n }\n}\n\nfunction fmtRRN(v: string, style: string): string {\n const d = v.replace(/\\D/g, \"\")\n if (d.length !== 13) return v\n switch (style) {\n case \"digits\": return d\n case \"front\": return d.slice(0, 6)\n case \"masked\": return `${d.slice(0, 6)}-${d[6]}******`\n default: return `${d.slice(0, 6)}-${d.slice(6)}` // hyphen\n }\n}\n\n/**\n * 값 서식 변환 — `종류:스타일` 접두형(date:/phone:/rrn:/mask:/digits/upper/lower/nospace)\n * 또는 접두 없는 자유 패턴(`#` 포함 → 숫자마스크, yyyy·yy·mm·dd 포함 → 날짜 토큰).\n * 모르는 포맷은 값 원형 반환 (fail-open — 채움 자체는 진행).\n */\nexport function formatFillValue(value: string, format?: string): string {\n if (!format) return value\n const ci = format.indexOf(\":\")\n const kind = ci >= 0 ? format.slice(0, ci) : format\n const style = ci >= 0 ? format.slice(ci + 1) : \"\"\n if (kind === \"date\") return fmtDate(value, style)\n if (kind === \"phone\") return fmtPhone(value, style)\n if (kind === \"rrn\") return fmtRRN(value, style)\n if (kind === \"mask\") return maskDigits(value, style)\n if (kind === \"digits\") { const only = value.replace(/\\D/g, \"\"); return only || value }\n if (kind === \"upper\") return value.toUpperCase()\n if (kind === \"lower\") return value.toLowerCase()\n if (kind === \"nospace\") return value.replace(/\\s+/g, \"\")\n if (format.includes(\"#\")) return maskDigits(value, format)\n if (/(yyyy|yy|mm|dd)/.test(format)) return fmtDate(value, format)\n return value\n}\n\n/**\n * 다중값 커서 — 라벨별 값 소비 상태를 추적한다.\n * 스칼라 값은 무한 반복(기존 동작), 배열 값은 적용 순서대로 소진되며\n * 다 쓰면 available=false가 되어 이후 등장은 채우지 않는다.\n */\nexport class ValueCursor {\n private nextIdx = new Map<string, number>()\n constructor(private values: Map<string, FillValue>) {}\n\n keys(): IterableIterator<string> {\n return this.values.keys()\n }\n\n has(key: string): boolean {\n return this.values.has(key)\n }\n\n isArray(key: string): boolean {\n return Array.isArray(this.values.get(key))\n }\n\n /** 남은 값이 있으면 true (스칼라는 항상 true) */\n available(key: string): boolean {\n const v = this.values.get(key)\n if (v === undefined) return false\n return typeof v === \"string\" || (this.nextIdx.get(key) ?? 0) < v.length\n }\n\n /** 현재 값 미리보기 (소진 없음) */\n peek(key: string): string | undefined {\n const v = this.values.get(key)\n if (v === undefined) return undefined\n if (typeof v === \"string\") return v\n const i = this.nextIdx.get(key) ?? 0\n return i < v.length ? v[i] : undefined\n }\n\n /** 값 소비 — 배열이면 커서 전진, 소진 시 undefined */\n consume(key: string): string | undefined {\n const v = this.values.get(key)\n if (v === undefined) return undefined\n if (typeof v === \"string\") return v\n const i = this.nextIdx.get(key) ?? 0\n if (i >= v.length) return undefined\n this.nextIdx.set(key, i + 1)\n return v[i]\n }\n\n /** 스칼라 키를 처음 쓴 칸의 원래 글: 두 번째 이후 등장은 빈 칸·같은 견본 글에만 쓴다 */\n private firstTarget = new Map<string, string>()\n\n /** 스칼라 키의 두 번째 이후 등장: 빈 칸이거나 첫 등장과 같은 견본 글일 때만 쓴다 (끝의 수신자 명단·서명란 같은 남의 글 보호) */\n laterOk(key: string, current: string): boolean {\n return this.isArray(key) || !this.firstTarget.has(key) || isBlankValue(current) || current.trim() === this.firstTarget.get(key)\n }\n\n markFirst(key: string, current: string): void {\n if (!this.isArray(key) && !this.firstTarget.has(key)) this.firstTarget.set(key, current.trim())\n }\n}\n\n/** 어노테이션 빈칸 \"(한자: )\"·\"(전화번호: )\": 라벨처럼 보여도 값을 받는 자리 */\nexport const ANNOTATION_BLANK_RE = /[((][^))]*[::]\\s*[))]/\n\n/** 남의 라벨 칸(값으로 덮으면 안 되는 칸): 날짜 자리표시가 아닌 키워드 라벨·다른 입력 키의 라벨(\"허가일자\"·\"전 공\")·①번호 라벨(\"① 지번\").\n * 두 채우기 경로(filler-hwpx·filler)가 같은 규칙을 쓴다 */\nexport function isForeignLabel(text: string, values: ValueCursor): boolean {\n // 날짜 자리표시는 값 자리 — 문단을 이어 붙인 \"년 월 입학\" + \"년 월 졸업\" 은 키워드 \"학년\" 을 품는다 (#146)\n if (isDatePlaceholder(text)) return false\n if (isKeywordLabel(text)) return true\n const t = text.trim()\n if (/^[①-⑳]/.test(t) && t.length <= 20) return true\n if (ANNOTATION_BLANK_RE.test(t)) return false // \"(한자: )\" 어노테이션 빈칸 칸은 값 자리\n const n = normalizeLabel(t)\n return !!n && values.has(n) // 정확 일치만: 접두 매칭이면 값 \"수신자 참조\" 가 키 \"수신자\" 의 라벨로 오인된다\n}\n\n/** 라벨 정규화 — 콜론/공백/특수문자 제거, 비교용 */\nexport function normalizeLabel(label: string): string {\n // 첨자 태그(<sup>·<sub>, 파서가 칸 글에 넣음)는 걷는다 — 이름표 \"면적(m<sup>2</sup>)\" 가 입력 키 \"면적(m2)\" 와 맞게\n return label.trim().replace(/<\\/?su[bp]>/g, \"\").replace(/[::\\s()()·]/g, \"\")\n}\n\n/**\n * 정규화된 셀 라벨과 입력 값 맵에서 최적 매칭 키를 찾음.\n *\n * 매칭 우선순위:\n * 1. 정확 매칭 (normalizedCellLabel === key)\n * 2. 접두사 기반 매칭 (60% 이상 겹침 필요)\n */\nexport function findMatchingKey(\n cellLabel: string,\n values: { has(key: string): boolean; keys(): Iterable<string> },\n): string | undefined {\n // 1) 정확 매칭\n if (values.has(cellLabel)) return cellLabel\n\n // 2) 접두사 기반 매칭 — 가장 긴 매칭 우선 (= 가장 구체적)\n // 단, 길이 비율 60% 이상 겹쳐야 매칭 (오탐 방지)\n let bestKey: string | undefined\n let bestLen = 0\n\n for (const key of values.keys()) {\n if (cellLabel.startsWith(key)) {\n if (key.length >= cellLabel.length * 0.6 && key.length > bestLen) {\n bestLen = key.length\n bestKey = key\n }\n } else if (key.startsWith(cellLabel)) {\n // 역방향(짧은 셀이 긴 키를 흡수)은 0.75 — 0.6이면 셀 \"대리인\"이 키\n // \"대리인성명\"을 흡수해 성명 값이 대리인 라벨 옆에 들어간다\n if (cellLabel.length >= key.length * 0.75 && cellLabel.length > bestLen) {\n bestLen = cellLabel.length\n bestKey = key\n }\n }\n }\n\n return bestKey\n}\n\n/**\n * 값 셀이 키워드 라벨(섹션 헤더의 하위 라벨)인지 판별.\n * \"성명\", \"주소\" 같은 키워드 라벨이면 true → 스킵 대상.\n * \"(한자:)\" 같은 어노테이션이면 false → 채울 수 있음.\n */\nexport function isKeywordLabel(text: string): boolean {\n const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, \"\").trim()\n if (!trimmed || trimmed.length > 15) return false\n for (const kw of LABEL_KEYWORDS) {\n if (trimmed.includes(kw)) return true\n }\n return false\n}\n\n/**\n * 셀 텍스트에서 인셀 패턴을 찾아 교체 — 체크박스 + 괄호 빈칸.\n *\n * 지원 패턴:\n * 1. 괄호 빈칸: `일반( )통` → 키 \"일반통\" 또는 \"일반\" 매칭 시 → `일반(값)통`\n * 2. 체크박스: `□부` → 키 \"부\" 매칭 시 → `☑부` (값이 \"☑\",\"✓\",\"v\",\"V\",\"true\",\"1\" 등)\n *\n * @returns 교체된 텍스트 + 매칭된 키 목록. null이면 교체 없음.\n */\nexport function fillInCellPatterns(\n cellText: string,\n values: ValueCursor,\n matchedLabels: Set<string>,\n /** require_unique 2차에서 거부된 라벨 셀 차단 (sfill-2 전략0) — 전략 1~3과 동일 계약 */\n blockedLabels?: Set<string>,\n /** 어노테이션 빈칸 \"(라벨: )\" 으로는 채우지 않을 키: 문서에 그 라벨의 제 칸이 따로 있는 스칼라 키 (filler-hwpx directKeys) */\n annotationSkip?: Set<string>,\n): { text: string; matches: Array<{ key: string; label: string; value: string }> } | null {\n let text = cellText\n const matches: Array<{ key: string; label: string; value: string }> = []\n\n // 1) 괄호 빈칸: keyword(\\s+)suffix → keyword(value)suffix\n text = text.replace(\n /([가-힣A-Za-z]+)\\(\\s{1,}\\)([가-힣A-Za-z]*)/g,\n (match, prefix: string, suffix: string) => {\n const label = prefix + suffix // \"일반\" + \"통\" = \"일반통\"\n const normalizedLabel = normalizeLabel(label)\n // require_unique 2차: 이 라벨이 거부됐으면 접두사 폴백으로 남의 키에 재배정 금지 (sfill-2 전략0)\n if (blockedLabels?.has(normalizedLabel)) return match\n // 정확 매칭 → 접두사만 매칭 순\n const matchKey = values.available(normalizedLabel)\n ? normalizedLabel\n : values.available(normalizeLabel(prefix))\n ? normalizeLabel(prefix)\n : undefined\n if (matchKey === undefined) return match\n\n const newValue = values.consume(matchKey)!\n matchedLabels.add(matchKey)\n matches.push({ key: matchKey, label, value: newValue })\n return `${prefix}(${newValue})${suffix}`\n },\n )\n\n // 2) 체크박스: □keyword → ☑keyword (값이 truthy)\n text = text.replace(\n /□([가-힣A-Za-z]+)/g,\n (match, keyword: string) => {\n const normalizedKw = normalizeLabel(keyword)\n const matchKey = values.available(normalizedKw) ? normalizedKw : undefined\n if (matchKey === undefined) return match\n\n const val = values.peek(matchKey)!\n // 계약: 빈 문자열은 \"체크 안 함\" — 체크 의사는 ☑/true/1 등 명시 값으로만 표현한다\n const isTruthy = [\"☑\", \"✓\", \"✔\", \"v\", \"V\", \"true\", \"1\", \"yes\", \"o\", \"O\"].includes(val.trim())\n if (!isTruthy) return match\n\n values.consume(matchKey)\n matchedLabels.add(matchKey)\n matches.push({ key: matchKey, label: `□${keyword}`, value: \"☑\" })\n return `☑${keyword}`\n },\n )\n\n // 3) 어노테이션 빈칸: (keyword:\\s+) → (keyword:value)\n // 예: \"(한자: )\" → \"(한자:金民秀)\"\n // 예: \"(성명: )\" → \"(성명: 홍길동)\"\n text = text.replace(\n /\\(([가-힣A-Za-z]+)[::]\\s{1,}\\)/g,\n (match, keyword: string) => {\n const normalizedKw = normalizeLabel(keyword)\n const matchKey = values.available(normalizedKw) ? normalizedKw : undefined\n if (matchKey === undefined || annotationSkip?.has(matchKey)) return match\n\n const newValue = values.consume(matchKey)!\n matchedLabels.add(matchKey)\n matches.push({ key: matchKey, label: keyword, value: newValue })\n return `(${keyword}:${newValue})`\n },\n )\n\n return matches.length > 0 ? { text, matches } : null\n}\n\n// ─── 인라인 \"라벨: 값\" 세그먼트 분해 ─────────────────\n\nexport interface InlineSegment {\n /** 라벨 (콜론 제외) */\n label: string\n /** 라벨 시작 오프셋 */\n labelStart: number\n /** 값 시작 오프셋 (콜론·공백 뒤) */\n valueStart: number\n /** 값 끝 오프셋 (다음 라벨/구분자 직전, 우측 공백 제외) */\n valueEnd: number\n /** text.slice(valueStart, valueEnd) */\n value: string\n /**\n * 직전 어절을 포함한 확장 라벨 (\"신청인 성명\") — 정확 매칭 후보.\n * 직전 어절이 이전 라벨의 값(\"… 강남구 전화:\")일 수 있으므로 정확 매칭에만\n * 쓰고, 접두사 매칭은 핵심 라벨(label)로만 한다. 직전 어절이 없으면 undefined.\n */\n extLabel?: string\n /** 확장 라벨 시작 오프셋 (직전 어절 첫 글자) — extLabel 있을 때만 */\n extStart?: number\n}\n\nconst INLINE_LABEL_RE = /((?:[가-힣A-Za-z]{1,10} )?)([가-힣A-Za-z]{2,10})\\s*[::]/g\n/** 값 끝의 날인 표시(\"(인)\"·\"(서명 또는 인)\") — 값이 아니라 도장 자리(place_seal 앵커)라 값 범위에서 뺀다. 종전엔 \"대표자 : (인)\" 을\n * 채우면 \"(인)\" 이 값으로 덮여 사라졌다 (#146) */\nconst SEAL_TAIL_RE = /[((]\\s*(?:인|서\\s*명|날\\s*인|서명\\s*또는\\s*인|인\\s*또는\\s*서명|서명\\s*또는\\s*날인)\\s*[))]\\s*$/\n/** 균등 배분 라벨(\"업 체 명 :\"·\"대 표 자 :\"·\"상 호:\") — 한 글자씩 같은 너비(1~3칸)로 띄운 2~8자. 앞이 글자면 낱말 중간이라 보지 않는다 (#146).\n * 종전엔 한 칸 띄움만 읽어 서명란 \"주 소:\"·\"상 호:\"·\"성 명:\" 이 매칭되지 않았다 (#151) */\nconst SPACED_INLINE_LABEL_RE = /(?<![가-힣A-Za-z]\\s?)([가-힣]( {1,3})[가-힣](?:\\2[가-힣]){0,6})\\s*[::]/g\n\n/**\n * 인라인 양식 한 줄을 라벨 단위로 분해 — 한 줄에 라벨이 여러 개인 양식\n * (\"자문위원 성명: 작성일자: \") 지원. 값은 다음 라벨 직전(또는\n * 구분자 [,;\\n] / 100자 / 줄끝)에서 끝난다. 값이 빈 라벨도 세그먼트로\n * 반환한다 (value=\"\", valueStart===valueEnd — 채움 대상).\n * \"http://\" 류 URL 스킴의 콜론은 라벨로 보지 않는다.\n */\nexport function scanInlineSegments(text: string): InlineSegment[] {\n const labels: Array<{ label: string; ext?: string; extStart?: number; start: number; end: number }> = []\n INLINE_LABEL_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = INLINE_LABEL_RE.exec(text)) !== null) {\n if (text[INLINE_LABEL_RE.lastIndex] === \"/\") continue // \"://\" — URL 스킴\n // m[1]=직전 어절(확장 라벨용, 공백 포함), m[2]=핵심 라벨. 세그먼트 좌표는\n // 핵심 라벨 기준(기존 값 경계 보존) — \"신청인 성명:\"과 \"대리인 성명:\"이 같은\n // \"성명\"으로 붕괴하지 않게 확장 라벨을 정확 매칭 후보로 함께 넘긴다.\n labels.push({\n label: m[2],\n ext: m[1] ? m[1] + m[2] : undefined,\n extStart: m[1] ? m.index : undefined,\n start: m.index + m[1].length,\n end: INLINE_LABEL_RE.lastIndex,\n })\n }\n SPACED_INLINE_LABEL_RE.lastIndex = 0\n while ((m = SPACED_INLINE_LABEL_RE.exec(text)) !== null) {\n const start = m.index, end = SPACED_INLINE_LABEL_RE.lastIndex\n if (labels.some(l => l.start < end && start < l.end)) continue\n labels.push({ label: m[1], start, end })\n }\n labels.sort((a, b) => a.start - b.start)\n\n const segments: InlineSegment[] = []\n for (let i = 0; i < labels.length; i++) {\n const cur = labels[i]\n let vs = cur.end\n while (vs < text.length && (text[vs] === \" \" || text[vs] === \"\\t\")) vs++\n let ve = i + 1 < labels.length ? labels[i + 1].start : text.length\n if (ve < vs) ve = vs // 다음 라벨이 공백 스킵 구간 안에서 시작하는 경우\n const sep = text.slice(vs, ve).search(/[\\n,;]/)\n if (sep !== -1) ve = vs + sep\n if (ve - vs > 100) ve = vs + 100\n const seal = text.slice(vs, ve).search(SEAL_TAIL_RE)\n if (seal !== -1) ve = vs + seal\n const sealAt = ve\n while (ve > vs && /\\s/.test(text[ve - 1])) ve--\n // 서명란 고정 문구(#151): \"서약자: 대표 (인)\" 처럼 넓은 빈칸 뒤의 글이 날인 앞 넓은 빈칸으로 끝나면 값이 아니라 고정 문구다.\n // 값 자리는 그 앞(라벨 뒤 첫 빈칸) — 종전엔 \"대표\" 를 값으로 덮어썼다. 빈 서명란에 채운 \"성 명: 홍길동 (인)\" 은 날인 앞이 한 칸이라 그대로 값이다\n if (seal !== -1 && ve > vs && sealAt - ve >= 3) {\n const gaps = [...text.slice(cur.end, ve).matchAll(/[ \\t]{3,}/g)]\n const gap = gaps[gaps.length - 1]\n if (gap) {\n ve = cur.end + gap.index\n while (ve > cur.end && /\\s/.test(text[ve - 1])) ve--\n vs = cur.end\n while (vs < ve && /\\s/.test(text[vs])) vs++\n if (vs >= ve) vs = ve = cur.end + (text[cur.end] === \" \" || text[cur.end] === \"\\t\" ? 1 : 0) // 빈 값: 콜론 뒤 한 칸 다음에 넣는다\n }\n }\n segments.push({\n label: cur.label,\n labelStart: cur.start,\n valueStart: vs,\n valueEnd: ve,\n value: text.slice(vs, ve),\n ...(cur.ext !== undefined ? { extLabel: cur.ext, extStart: cur.extStart } : {}),\n })\n }\n return segments\n}\n\n/**\n * 인라인 세그먼트 매칭 키 결정 — 확장 라벨(\"신청인 성명\") 정확 매칭 우선,\n * 핵심 라벨(\"성명\")은 기존 findMatchingKey(정확+접두사) 폴백.\n * 확장 라벨은 직전 어절이 이전 라벨의 값일 수 있어 정확 매칭만 허용한다 (과탐 방지).\n * @returns 매칭 키 + filled 보고용 라벨 + 확장 매칭 여부. blockedLabels(sfill-2)에 걸리면 undefined.\n */\nexport function matchInlineSegment(\n seg: InlineSegment,\n values: { has(key: string): boolean; keys(): Iterable<string> },\n blockedLabels?: Set<string>,\n): { key: string; label: string; viaExt: boolean } | undefined {\n const nlabel = normalizeLabel(seg.label)\n if (seg.extLabel !== undefined) {\n const nExt = normalizeLabel(seg.extLabel)\n if (nExt !== nlabel && !blockedLabels?.has(nExt) && values.has(nExt)) {\n return { key: nExt, label: seg.extLabel, viaExt: true }\n }\n }\n if (blockedLabels?.has(nlabel)) return undefined\n const key = findMatchingKey(nlabel, values)\n return key !== undefined ? { key, label: seg.label, viaExt: false } : undefined\n}\n\n/**\n * 세그먼트 값의 유효 끝 — 다음 세그먼트가 확장 라벨로 매칭됐으면 값 범위가\n * 그 직전 어절(확장 라벨의 일부, \"대리인 성명\"의 \"대리인\")을 삼키지 않게 앞당긴다.\n */\nexport function clampSegmentEnd(\n text: string,\n seg: InlineSegment,\n next: InlineSegment | undefined,\n nextViaExt: boolean,\n): number {\n let ve = seg.valueEnd\n if (nextViaExt && next?.extStart !== undefined && next.extStart < ve) ve = next.extStart\n while (ve > seg.valueStart && /\\s/.test(text[ve - 1])) ve--\n return ve\n}\n\n/** 빈 자리(valueStart===valueEnd) 삽입 시 콜론·다음 라벨과 붙지 않게 공백 부착 */\nexport function padInsertion(text: string, pos: number, value: string): string {\n const lead = pos > 0 && !/\\s/.test(text[pos - 1]) ? \" \" : \"\"\n const trail = pos < text.length && !/\\s/.test(text[pos]) ? \" \" : \"\"\n return lead + value + trail\n}\n\n/**\n * 입력 values 맵을 정규화된 키로 변환 — format 지정(FillInput 객체형)은 여기서 값으로 변환.\n * @param warnings 정규화 후 키가 충돌하는 라벨(\"성 명\"과 \"성명\")의 무음 덮어쓰기 경고 수집\n */\nexport function normalizeValues(values: Record<string, FillInput>, warnings?: string[]): Map<string, FillValue> {\n const map = new Map<string, FillValue>()\n for (const [label, raw] of Object.entries(values)) {\n const { value, format } = typeof raw === \"object\" && !Array.isArray(raw)\n ? raw\n : { value: raw, format: undefined }\n const key = normalizeLabel(label)\n if (map.has(key)) {\n warnings?.push(`입력 라벨 \"${label}\"이 정규화 키 \"${key}\"에서 다른 라벨과 충돌 — 뒤 값으로 덮어씀`)\n }\n map.set(key, Array.isArray(value)\n ? value.map(v => formatFillValue(v, format))\n : formatFillValue(value, format))\n }\n return map\n}\n\n/**\n * 모호 라벨 거부 가드 — 한 입력 키(스칼라)가 서식의 2곳 이상에 채워지면 남의 블록\n * 오염 위험(대표자 행에 참여인력 값이 들어가는 류)이므로, 그 키는 채우지 않고\n * rejected로 보고한 뒤 나머지 키만으로 다시 채운다 (claw-hwp require_occurrence 이식).\n * 배열 값은 다중 등장 소진이 의도된 동작이라 거부 대상이 아니다.\n * @param run 같은 원본에 대해 결정적으로 재실행 가능한 채우기 함수\n */\nexport async function fillWithUniqueGuard<R extends { filled: Array<{ key?: string; label: string }> }>(\n values: Record<string, FillInput>,\n run: (vals: Record<string, FillInput>, blockedLabels?: Set<string>) => R | Promise<R>,\n): Promise<R & { rejected: string[] }> {\n const first = await run(values)\n const counts = new Map<string, number>()\n for (const f of first.filled) {\n // 누름틀(clickhere)은 이름 정확 일치라 다중 등장이 곧 서식의 설계다 — 집계에서 뺀다.\n // 세지 않으면 동명 누름틀 2곳짜리 서식이 통째로 rejected 되어 한 곳도 안 채워진다.\n if (f.key && (f as { source?: string }).source !== \"clickhere\") {\n counts.set(f.key, (counts.get(f.key) ?? 0) + 1)\n }\n }\n const isArrayValue = (normKey: string): boolean => {\n for (const [label, raw] of Object.entries(values)) {\n if (normalizeLabel(label) !== normKey) continue\n return Array.isArray(typeof raw === \"object\" && !Array.isArray(raw) ? raw.value : raw)\n }\n return false\n }\n const dup = new Set([...counts].filter(([k, n]) => n >= 2 && !isArrayValue(k)).map(([k]) => k))\n if (dup.size === 0) return { ...first, rejected: [] }\n // 거부된 키에 매칭됐던 셀 라벨들 — 2차에서 이 셀이 접두사 매칭으로 남은 다른 키에\n // 재배정되어 남의 값으로 오염되는 것을 차단한다 (sfill-2).\n const blockedLabels = new Set<string>()\n for (const f of first.filled) {\n if (f.key && dup.has(f.key)) blockedLabels.add(normalizeLabel(f.label))\n }\n const filtered = Object.fromEntries(Object.entries(values).filter(([label]) => !dup.has(normalizeLabel(label))))\n const second = await run(filtered, blockedLabels)\n const rejected = Object.keys(values).filter(label => dup.has(normalizeLabel(label)))\n return { ...second, rejected }\n}\n\n/** 매칭 안 된 라벨을 원본 키로 복원 */\nexport function resolveUnmatched(\n normalizedValues: Map<string, FillValue>,\n matchedLabels: Set<string>,\n originalValues: Record<string, FillInput>,\n): string[] {\n return [...normalizedValues.keys()]\n .filter(k => !matchedLabels.has(k))\n .map(k => {\n for (const orig of Object.keys(originalValues)) {\n if (normalizeLabel(orig) === k) return orig\n }\n return k\n })\n}\n\n/** fill --dry-run 에 값을 함께 준 결과 — 키·배열 번호별 값을 쓸 칸(target: 표·행·열). 쓰지 않고 실제 채움을 메모리에서만 돌려 낸다.\n * 같은 칸을 나중 키가 다시 쓰면 overwrittenBy 로 알린다 (#145) */\nexport function fillPlan(values: Record<string, FillInput>, filled: FormField[], unmatched: string[], rejected: string[]) {\n const inputKey = new Map(Object.keys(values).map(k => [normalizeLabel(k), k]))\n const seen = new Map<string, number>()\n const targets = filled.map((f, i) => {\n const key = (f.key !== undefined && inputKey.get(f.key)) || f.key || f.label\n const index = seen.get(key) ?? 0\n seen.set(key, index + 1)\n const t = f.target\n const later = t && filled.slice(i + 1).find(g => g.target?.table === t.table && g.target.row === t.row && g.target.col === t.col)\n const overwrittenBy = later ? ((later.key !== undefined && inputKey.get(later.key)) || later.key || later.label) : undefined\n return { key, index, label: f.label, value: f.value, target: t ?? null, ...(overwrittenBy ? { overwrittenBy } : {}) }\n })\n return { targets, unmatched, ...(rejected.length > 0 ? { rejected } : {}) }\n}\n","/** 양식(서식) 필드 인식 — 테이블 기반 label-value 패턴 매칭 */\n\nimport type { IRBlock, IRCell, IRTable, FormField, FormResult } from \"../types.js\"\nimport { scanInlineSegments, normalizeLabel } from \"./match.js\"\n\n/** 한국 공문서 필드 라벨 키워드 */\nexport const LABEL_KEYWORDS = new Set([\n \"성명\", \"이름\", \"주소\", \"전화\", \"전화번호\", \"휴대폰\", \"핸드폰\", \"연락처\",\n \"생년월일\", \"주민등록번호\", \"소속\", \"직위\", \"직급\", \"부서\",\n \"이메일\", \"팩스\", \"학교\", \"학년\", \"반\", \"번호\",\n \"신청인\", \"대표자\", \"담당자\", \"작성자\", \"확인자\", \"승인자\",\n \"일시\", \"날짜\", \"기간\", \"장소\", \"목적\", \"사유\", \"비고\",\n \"금액\", \"수량\", \"단가\", \"합계\", \"계\", \"소계\",\n \"등록기준지\", \"본적\", \"위임인\", \"청구사유\", \"소명자료\",\n])\n\n/** 콜론 없는 영문 라벨로 인정하는 단어 (한국 공문서 병기 관행) */\nconst ENGLISH_LABEL_WORDS = new Set([\n \"name\", \"date\", \"address\", \"tel\", \"phone\", \"mobile\", \"fax\", \"email\", \"e-mail\",\n \"dept\", \"department\", \"division\", \"title\", \"position\", \"grade\", \"rank\",\n \"birth\", \"nationality\", \"sex\", \"gender\", \"signature\", \"sign\", \"seal\",\n \"remarks\", \"note\", \"period\", \"place\", \"purpose\", \"reason\", \"amount\", \"total\",\n \"sum\", \"qty\", \"quantity\", \"unit\", \"no\", \"id\", \"passport\",\n])\nconst ENGLISH_STOPWORDS = new Set([\"of\", \"the\", \"and\", \"or\", \"in\"])\n\n/** 수량/단위 값 형태 — \"6개월\"·\"1억원\"·\"5백만원\"·\"2026년\" 등은 라벨이 아님 */\nconst NUMERIC_VALUE_RE = /^제?\\d+(?:[.,]\\d+)*[십백천만억조]*(?:원|명|건|개|회|부|매|장|점|호|번|년|월|일|시|분|초|개월|주년|차례|퍼센트)?$/\n\n/** 서술형 문장 어미 — 셀에 든 안내 문구를 라벨로 오인하지 않기 위한 컷 */\nconst SENTENCE_ENDING_RE = /(?:입니다|합니다|습니다|하세요|십시오|시오|바랍니다|바람|할 것|할것|하며|하고|한다|된다|됨|음|임)$/\n\n/** 라벨처럼 보이는 셀인지 판별 */\nexport function isLabelCell(text: string): boolean {\n // 각주 번호/특수문자 제거 후 판별 (예: \"등록기준지²\" → \"등록기준지\")\n const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, \"\").trim()\n if (!trimmed || trimmed.length > 30) return false\n // 키워드 매칭\n for (const kw of LABEL_KEYWORDS) {\n if (trimmed.includes(kw)) return true\n }\n // 짧은 한글 텍스트 (2-12자, 공백/괄호/특수기호 허용) — 숫자는 낄 수 있으나\n // (\"연번1\"·\"제1항목\"·\"1차소속\") 수량/단위 값·서술형 문구·법인명은 라벨이 아님.\n // 9자 이상 확장 구간은 3어절 이상 제목성 문구를 컷 (자간 공백 라벨\n // \"업 체 명\"류가 있는 8자 이하 기존 구간에는 어절 제한을 걸지 않는다)\n const compact = trimmed.replace(/\\s/g, \"\")\n if (/^[가-힣0-9()()·::\\-]+$/.test(compact)\n && compact.length >= 2 && compact.length <= 12\n && (compact.match(/[가-힣]/g) ?? []).length >= 2\n && (compact.length <= 8 || trimmed.split(/\\s+/).length <= 2)\n && !NUMERIC_VALUE_RE.test(compact)\n && !SENTENCE_ENDING_RE.test(trimmed)\n && !/^[((]주[))]|^주식회사/.test(compact)) {\n return true\n }\n // \"라벨:\" 패턴\n if (/^[가-힣A-Za-z\\s]+[::]$/.test(trimmed)) return true\n // 콜론 없는 영문 라벨 (\"Name\"·\"Date of Birth\") — 관행 단어 목록으로 한정\n if (/^[A-Za-z][A-Za-z\\s./&-]*$/.test(trimmed) && trimmed.length <= 20) {\n const words = trimmed.toLowerCase().split(/[\\s/&]+/).filter(w => w && !ENGLISH_STOPWORDS.has(w))\n if (words.length >= 1 && words.length <= 3 && words.every(w => ENGLISH_LABEL_WORDS.has(w.replace(/\\.$/, \"\")))) {\n return true\n }\n }\n return false\n}\n\n/** 날짜 자리표시 칸(\"년 월 - 년 월\"·\"년 월 일 ~ 년 월 일\"·\"년 월\") — 날짜 단위 글자와 숫자·구분 기호뿐이고, 기간 구분 기호가 있거나\n * 단위 글자가 띄어 있다. 열 머리 아래의 이 칸은 값 자리다: 라벨로 보면 \"첫 행이 전부 라벨인 표\" 의 머리 행 보호가 풀려 머리 칸 값이 옆\n * 머리 칸에 들어갔다 (#146). isLabelCell 은 그대로 둔다 — 날짜 줄(\"년 월 일\")은 dry-run 이 필드로 내주는 키다 */\nexport function isDatePlaceholder(text: string): boolean {\n // 숫자는 연도 앞자리(\"20 년 월 일\")만 — 단위 사이에 숫자가 있으면 \"8월 31일\" 같은 실제 날짜 값이다.\n // 학력·경력 칸의 \"년 월 입학 / 년 월 졸업\"·\"년 월 ~ 현재\" 꼬리말도 자리표시다\n const t = text.trim().replace(/부터|까지|입학|졸업|수료|재학|중퇴|편입|입사|퇴사|현재/g, \" \").replace(/(^|[-~~∼])\\s*(?:19|20)(?=\\s)/g, \"$1\")\n if (!/[년월일시분]/.test(t) || !/^[년월일시분초\\s.\\-~~∼()()/]+$/.test(t)) return false\n return /[-~~∼]/.test(t) || /[년월일시분]\\s+[년월일시분]/.test(t)\n}\n\n/** 빈 값 칸: 비었거나 밑줄·괄호 빈칸·대시 같은 자리표시뿐 */\nexport function isBlankValue(text: string): boolean {\n return /^[\\s_()()\\-\\u2014\\u2013~.·,]*$/.test(text) // 대시(U+2014·U+2013)도 자리표시\n}\n\n/**\n * 라벨 칸 오른쪽 칸이 하위 라벨인가 — 행 머리(\"신청인\"·위임장 \"대표자\") 옆 \"상호 또는 법인명칭\"·\"성 명\" 처럼 라벨 꼴이고 그 옆이 빈 값 칸이면\n * 값 자리가 아니다. 채우기(filler-hwpx·filler)가 같은 규칙을 쓴다. 입력 키와 무관한 구조 판정이라 배열 값 순서가 다른 키 구성에 따라\n * 바뀌지 않는다 (#145). 다시 채우는 \"성명 | 김철수 | 빈칸\" 은 행 머리(여러 행에 걸침)도 키워드 라벨도 아니라 값 자리로 둔다\n */\nexport function isSubLabelBeside(labelRowSpan: number, text: string, after: string | undefined): boolean {\n if (after === undefined || isBlankValue(text) || !isBlankValue(after) || !isLabelCell(text)) return false\n const compact = text.replace(/\\s/g, \"\")\n return labelRowSpan > 1 || (compact.length <= 15 && [...LABEL_KEYWORDS].some(kw => compact.includes(kw)))\n}\n\n/**\n * 열 머리 행 판정: 라벨이 위에, 값이 아래에 오는 표의 머리 행인가. 채우기(filler-hwpx)와 인식(extractFormSchema)이 같은 규칙을 쓴다.\n * heads: 그 행의 머리 후보 칸 글(아래로 걸친 구역 라벨 rowSpan 칸은 빼고), below: 같은 칸 좌표의 다음 행 칸 글(없으면 undefined).\n * - 아래 칸이 전부 비었으면 머리 글이 라벨 꼴이 아니어도 머리(\"검토의견 (의견제출 내용)\")\n * - 아니면 머리 3칸 이상이 전부 라벨이고 아래 칸이 하나 빼고 빈 경우만 (줄 이름표 \"고등학교\"·\"년 월\" 자리표시 허용).\n * \"성명 | 홍길동\" 같은 라벨-값 2칸 행을 머리로 오인하지 않게 좁힌다\n */\nexport function isColumnHeaderRow(heads: string[], below: Array<string | undefined>): boolean {\n if (heads.length < 2 || below.some(b => b === undefined)) return false\n const hs = heads.map(h => h.trim())\n if (hs.some(h => !h || h.length > 40 || SENTENCE_ENDING_RE.test(h))) return false\n const blanks = below.filter(b => isBlankValue(b!) || isDatePlaceholder(b!)).length // 날짜 자리표시도 빈 값 칸 (#146)\n if (blanks === below.length) return true\n return hs.length >= 3 && blanks >= hs.length - 1 && hs.every(h => h.length <= 20 && isLabelCell(h))\n}\n\n/**\n * IRBlock[]에서 양식 필드를 인식하여 추출.\n * 테이블의 label-value 패턴을 감지.\n */\nexport function extractFormFields(blocks: IRBlock[]): FormResult {\n const fields: FormField[] = []\n let totalTables = 0\n let formTables = 0\n\n for (const table of collectTables(blocks)) {\n totalTables++\n\n const tableFields = extractFromTable(table)\n if (tableFields.length > 0) {\n formTables++\n fields.push(...tableFields)\n }\n }\n\n // 인라인 \"라벨: 값\" 패턴도 검사 (paragraph만 — heading/list는 양식 필드가 아님)\n for (const block of blocks) {\n if (block.type === \"paragraph\" && block.text) {\n const inlineFields = extractInlineFields(block.text)\n fields.push(...inlineFields)\n }\n }\n\n const confidence = totalTables > 0 ? formTables / totalTables : (fields.length > 0 ? 0.3 : 0)\n return { fields, confidence: Math.min(confidence, 1) }\n}\n\n/**\n * 문서 순서 표 수집 — 셀 blocks 의 중첩표 포함. 법령 별지서식은 1칸 틀 안에 신청인 정보·발신명의 표가\n * 중첩되어 있고(HWPX 파서·PDF 클립 셀 그리드 모두 셀 blocks 로 낸다) fillHwpx 도 중첩표를 DFS 로 훑으므로\n * 인식기도 같은 경계로 본다 — 최상위만 보면 틀 안 표의 라벨-값이 통째로 빠진다 (v4.12.2)\n */\nfunction collectTables(blocks: IRBlock[], out: IRTable[] = [], depth = 0): IRTable[] {\n if (depth > 12) return out\n for (const block of blocks) {\n if (block.type !== \"table\" || !block.table) continue\n out.push(block.table)\n for (const row of block.table.cells) {\n for (const cell of row) if (cell?.blocks?.length) collectTables(cell.blocks, out, depth + 1)\n }\n }\n return out\n}\n\n/** IR 격자에서 병합으로 덮인 자리: 격자는 덮인 자리에도 빈 칸을 두므로 앵커와 구분해야 한다 */\nfunction coveredGrid(table: IRTable): boolean[][] {\n const covered = Array.from({ length: table.rows }, () => new Array<boolean>(table.cols).fill(false))\n for (let r = 0; r < table.rows; r++) {\n for (let c = 0; c < table.cols; c++) {\n const cell = table.cells[r]?.[c]\n if (!cell || covered[r][c]) continue\n for (let rr = r; rr < Math.min(r + cell.rowSpan, table.rows); rr++) {\n for (let cc = c; cc < Math.min(c + cell.colSpan, table.cols); cc++) if (rr !== r || cc !== c) covered[rr][cc] = true\n }\n }\n }\n return covered\n}\n\n/** 열 머리 행 번호들 (isColumnHeaderRow): 머리 아래 같은 열 칸이 값 */\nfunction columnHeaderRows(table: IRTable): Set<number> {\n const covered = coveredGrid(table)\n const rows = new Set<number>()\n for (let r = 0; r + 1 < table.rows; r++) {\n const heads: number[] = []\n for (let c = 0; c < table.cols; c++) {\n const cell = table.cells[r]?.[c]\n if (cell && !covered[r][c] && cell.rowSpan <= 1) heads.push(c)\n }\n const below = heads.map(c => (covered[r + 1][c] ? undefined : table.cells[r + 1]?.[c]?.text))\n if (isColumnHeaderRow(heads.map(c => table.cells[r]![c].text), below)) rows.add(r)\n }\n return rows\n}\n\n/**\n * 열 머리 지도 — 열 머리 행(isColumnHeaderRow) → 격자 열 → 그 열의 머리 칸이 있는 행. 인식(extractFormFields)과 IR 채우기(filler)가\n * 같이 쓴다(filler-hwpx 는 스캔 칸으로 같은 규칙). 2단 머리(#146): 머리 행 바로 위 행이 전부 라벨이고 그 칸이 머리 행까지 내려오는\n * 세로 병합(\"기 간\")이거나 묶음 가로 병합(\"담당업무\" → \"분야\"·\"내용\")뿐이면 세로 병합 칸도 그 열의 머리다. tops 는 그런 위 행 —\n * 옆 칸을 값으로 보지 않는다(옆 칸은 다음 머리)\n */\nexport function columnHeaderMap(table: IRTable): { heads: Map<number, Map<number, number>>; tops: Set<number> } {\n const heads = new Map<number, Map<number, number>>()\n const tops = new Set<number>()\n if (table.cols < 2) return { heads, tops }\n const covered = coveredGrid(table)\n for (const r of [...columnHeaderRows(table)].sort((a, b) => a - b)) {\n const cols = new Map<number, number>()\n for (let c = 0; c < table.cols; c++) {\n const cell = table.cells[r]?.[c]\n if (cell && !covered[r][c] && cell.rowSpan <= 1 && cell.text.trim()) cols.set(c, r)\n }\n const above: Array<[number, IRCell]> = []\n for (let c = 0; r > 0 && c < table.cols; c++) {\n const cell = table.cells[r - 1]?.[c]\n if (cell && !covered[r - 1][c]) above.push([c, cell])\n }\n if (above.length && above.some(([, a]) => a.rowSpan === 2)\n && above.every(([, a]) => isLabelCell(a.text) && (a.rowSpan === 2 || (a.rowSpan === 1 && a.colSpan > 1)))) {\n for (const [c, a] of above) if (a.rowSpan === 2) cols.set(c, r - 1)\n tops.add(r - 1)\n }\n heads.set(r, new Map([...cols].sort((x, y) => x[0] - y[0])))\n }\n return { heads, tops }\n}\n\nfunction extractFromTable(table: IRTable): FormField[] {\n const fields: FormField[] = []\n const { heads, tops } = columnHeaderMap(table)\n\n // 전략 1: 인접셀 label-value (2열 이상 테이블): 열 머리 행은 아래 칸이 값 (채우기와 같은 방향)\n if (table.cols >= 2) {\n for (let r = 0; r < table.rows; r++) {\n const head = heads.get(r)\n if (head) {\n for (const [c, hr] of head) {\n fields.push({ label: table.cells[hr]![c].text.trim().replace(/[::]\\s*$/, \"\"), value: (table.cells[r + 1]?.[c]?.text ?? \"\").trim(), row: r + 1, col: c })\n }\n continue\n }\n if (tops.has(r)) continue\n for (let c = 0; c < table.cols - 1; c++) {\n const labelCell = table.cells[r]?.[c]\n const valueCell = table.cells[r]?.[c + 1]\n if (!labelCell || !valueCell) continue\n // 행 머리 옆 하위 라벨 칸(\"신청인 | 상호 또는 법인명칭 | 빈칸\")은 값이 아니다 — 채우기도 그 칸에 쓰지 않는다 (#145)\n if (isSubLabelBeside(labelCell.rowSpan, valueCell.text, table.cells[r]?.[c + 1 + valueCell.colSpan]?.text)) continue\n if (isLabelCell(labelCell.text)) {\n fields.push({\n label: labelCell.text.trim().replace(/[::]\\s*$/, \"\"),\n value: valueCell.text.trim(),\n row: r,\n col: c,\n })\n }\n }\n }\n }\n\n // 전략 2: 헤더+데이터 행 (첫 행이 전부 라벨이면)\n if (fields.length === 0 && table.rows >= 2 && table.cols >= 2) {\n const headerRow = table.cells[0]\n const allLabels = headerRow?.every(cell => {\n const t = cell?.text.trim() ?? \"\"\n return t.length > 0 && t.length <= 20\n }) ?? false\n if (allLabels) {\n for (let r = 1; r < table.rows; r++) {\n for (let c = 0; c < table.cols; c++) {\n const label = headerRow[c]?.text.trim() ?? \"\"\n const value = table.cells[r]?.[c]?.text.trim() ?? \"\"\n if (label && value) {\n fields.push({ label, value, row: r, col: c })\n }\n }\n }\n }\n }\n\n return fields\n}\n\nfunction extractInlineFields(text: string): FormField[] {\n // \"라벨: 값\" — 한 줄 다중 라벨(\"성명: 작성일자: \")은 세그먼트 단위로 분해.\n // 값이 빈 라벨은 여기선 제외 (기존 계약 유지) — extractFormSchema가 수집한다.\n const fields: FormField[] = []\n for (const seg of scanInlineSegments(text)) {\n if (seg.value) {\n fields.push({ label: seg.label, value: seg.value, row: -1, col: -1 })\n }\n }\n return fields\n}\n\n// ─── 필드 스키마 (타입 추론) — v3.1 ─────────────────\n\n/** 양식 필드 타입 — 폼 UI 위젯 선택의 근거 (데이트피커/체크박스 등) */\nexport type FormFieldType = \"text\" | \"date\" | \"phone\" | \"email\" | \"amount\" | \"checkbox\" | \"idnum\"\n\n/** 타입이 추론된 양식 필드 */\nexport interface FormFieldSchema extends FormField {\n type: FormFieldType\n /** 라벨에 필수 표시(※·*·★·\"(필수)\")가 있을 때만 true */\n required?: boolean\n /** 값이 비어 있거나 플레이스홀더(밑줄/괄호 빈칸)뿐 — 채움 대상 */\n empty: boolean\n}\n\nexport interface FormSchemaResult {\n fields: FormFieldSchema[]\n /** 양식 확신도 (0-1, extractFormFields와 동일) */\n confidence: number\n}\n\n/** 라벨 키워드 → 타입 (값 패턴이 우선, 라벨은 폴백) */\nconst LABEL_TYPE_RULES: Array<[RegExp, FormFieldType]> = [\n [/주민등록번호|외국인등록번호/, \"idnum\"],\n [/생년월일|일시|날짜|일자|기간|연월일|년월일|신청일|작성일|발급일|접수일/, \"date\"],\n [/전화|연락처|휴대폰|핸드폰|팩스/, \"phone\"],\n [/이메일|전자우편|email/i, \"email\"],\n [/금액|단가|수량|합계|소계|예산|비용|인원|급여|연봉/, \"amount\"],\n]\n\n/** 필드 타입 추론 — 기존 값의 패턴 우선, 없으면 라벨 키워드 */\nexport function inferFieldType(label: string, value: string): FormFieldType {\n if (/[□☑✓✔]/.test(value) || /[□☑✓✔]/.test(label)) return \"checkbox\"\n\n const v = value.trim()\n if (v) {\n if (/^\\d{6}[-\\s]?[1-4]\\d{6}$/.test(v)) return \"idnum\"\n if (/^\\d{4}\\s*[-./년]\\s*\\d{1,2}\\s*[-./월]\\s*\\d{1,2}\\s*일?\\s*\\.?$/.test(v)) return \"date\"\n if (/^0\\d{1,2}[-.)\\s]?\\d{3,4}[-.\\s]?\\d{4}$/.test(v)) return \"phone\"\n if (/^[\\w.+-]+@[\\w-]+(?:\\.[\\w-]+)+$/.test(v)) return \"email\"\n // 단위 접미사 또는 천단위 콤마가 있을 때만 값 기반 amount — 맨 숫자\n // (우편번호/접수번호/연도 등)는 라벨 폴백으로 넘긴다\n if (/^[\\d,.\\s]+(?:원|명|건|개|회|부|매|%)$/.test(v) && /\\d/.test(v)) return \"amount\"\n if (/^\\d{1,3}(?:,\\d{3})+$/.test(v)) return \"amount\"\n }\n\n const norm = label.replace(/\\s/g, \"\")\n for (const [re, type] of LABEL_TYPE_RULES) {\n if (re.test(norm)) return type\n }\n return \"text\"\n}\n\n/** 라벨의 필수 표시 감지 (※·*·★·\"(필수)\") */\nfunction isRequiredLabel(label: string): boolean {\n return /[*※★]|\\(\\s*필수\\s*\\)|(\\s*필수\\s*)/.test(label)\n}\n\n/** 값이 비어 있거나 플레이스홀더(밑줄·괄호 빈칸·대시)뿐인지 */\nfunction isEmptyValue(value: string): boolean {\n const v = value.trim()\n if (!v) return true\n return /^[\\s_()()\\-—–~.·,]*$/.test(v)\n}\n\n/**\n * 양식 필드 인식 + 타입/필수/빈값 추론 — 폼 UI 자동 생성용 (v3.1).\n * extractFormFields 결과에 type(date/phone/amount/checkbox/...)·required·empty를 부여한다.\n */\nexport function extractFormSchema(blocks: IRBlock[]): FormSchemaResult {\n const { fields, confidence } = extractFormFields(blocks)\n const schemaFields: FormFieldSchema[] = fields.map(f => ({\n ...f,\n type: inferFieldType(f.label, f.value),\n required: isRequiredLabel(f.label) || undefined,\n empty: isEmptyValue(f.value),\n }))\n\n // 값이 빈 인라인 라벨(\"작성일자:\")도 채움 대상으로 노출 —\n // extractFormFields는 값 있는 필드만 수집하므로 여기서 보충한다\n const seen = new Set(schemaFields.map(f => normalizeLabel(f.label)))\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text) continue\n for (const seg of scanInlineSegments(block.text)) {\n if (seg.value) continue\n const key = normalizeLabel(seg.label)\n if (seen.has(key)) continue\n seen.add(key)\n schemaFields.push({\n label: seg.label,\n value: \"\",\n row: -1,\n col: -1,\n type: inferFieldType(seg.label, \"\"),\n required: isRequiredLabel(seg.label) || undefined,\n empty: true,\n })\n }\n }\n\n return { confidence, fields: schemaFields }\n}\n","/**\n * HWPX 라운드트립 소스맵 — section XML 문자열에서 문단/표 셀의 문자 범위를 추적.\n *\n * DOM 재직렬화를 거치지 않고(속성 순서·공백·자기닫힘 표기까지 보존하기 위해)\n * 정규식 토크나이저로 태그를 순회하며, 각 <hp:p>가 소유한 <hp:t> 콘텐츠 범위를\n * 기록한다. 패치는 이 범위에 대한 문자열 splice로만 수행되어\n * 변경 문단 외 XML 바이트가 그대로 보존된다 (filler-hwpx.ts 패턴의 오프셋 버전).\n */\n\nimport { isDatePlaceholder } from \"../form/recognize.js\"\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 /** <hp:tc name=\"…\"> — kordoc 생성기의 왕복 마커(`__kordoc_h1` 등) 판독용 */\n name?: string\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 * kordoc 생성기의 장식표(제목 틀·장 띠·요약 상자·목차) — 파서가 표 대신 heading·인용문으로 내거나 건너뛰므로\n * 패치의 표 서수에서 빠지고, heading·quote 면 표시 칸(channelCell)의 문단이 문단 매핑 대상이 된다.\n * 최상위 표만 판독 (파서 section-walker kordocTableChannel 과 같은 규칙)\n */\n channel?: \"heading\" | \"quote\" | \"skip\"\n channelCell?: 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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\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/nbSpace/\n * br/lineBreak)만 공백, 나머지 태그(markpen 등 파서가 무시)는 제거 (파서 모델 정합) */\nfunction tContentToText(raw: string): string {\n return decodeXmlEntities(\n raw\n .replace(/<\\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|nbSpace|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 if (table.topLevel) markKordocChannel(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 === \"nbSpace\" || 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 const name = getAttr(attrsRaw, \"name\")\n cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [], ...(name ? { name } : {}) })\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 === \"nbSpace\" || 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/** 표의 첫 `__kordoc_` 이름 칸으로 왕복 채널 판독 — 파서가 그 표를 heading·인용문으로 내거나 건너뛴다 */\nfunction markKordocChannel(table: ScanTable): void {\n let cell: ScanCell | undefined\n for (const row of table.rows) {\n cell = row.find(c => c.name?.startsWith(\"__kordoc_\"))\n if (cell) break\n }\n const m = cell?.name?.match(/^__kordoc_(?:(h[1-6])|(toc|skip)|(summary))$/)\n if (!cell || !m) return\n table.channel = m[1] ? \"heading\" : m[3] ? \"quote\" : \"skip\"\n if (table.channel !== \"skip\") table.channelCell = cell\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 // 날짜 자리표시(\" 년 월 - 년 월\")의 공백은 값이 들어갈 빈칸이지 들여쓰기가 아니다 — 복원하지 않는다(patch 와 같다, #151)\n if (orig && orig.trim() !== \"\" && !isDatePlaceholder(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 * 선두 부호 run 보존 — kordoc 공문서 항목은 부호를 따로 된 run(부호 + 탭)으로 낸다(내어쓰기용 자동 탭 정렬).\n * 편집이 부호 뒤 내용만 바꿨으면 부호 run 은 그대로 두고 나머지 hp:t 만 다시 쓴다. 문단 전체 재작성은 부호와 내용을\n * 첫 hp:t 하나에 합치며 탭을 공백으로 바꿔 정렬이 깨진다. 부호가 바뀌었거나 그 꼴이 아니면 null(호출자가 전체 재작성).\n */\nexport function markerRunSplices(para: ScanParagraph, xml: string, newText: string): SpliceEdit[] | null {\n const first = para.tRanges[0]\n if (!first || first.selfClosing || para.tRanges.length < 2) return null\n const m = /^([^<&\\s]{1,6})<(?:\\w+:)?tab\\b[^>]*\\/>$/.exec(xml.slice(first.contentStart, first.contentEnd))\n if (!m || !newText.startsWith(m[1] + \" \")) return null\n return buildParagraphSplices({ ...para, tRanges: para.tRanges.slice(1) }, newText.slice(m[1].length + 1), xml)\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/**\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\n/**\n * 글이 바뀐 문단의 <hp:linesegarray> 만 지우는 splice 목록: 편집 위치를 품은 가장 안쪽 문단(본문 문단·칸 문단)의 캐시만\n * 지운다. 표를 담은 호스트 문단은 글이 그대로라 캐시를 둔다(행 추가·삭제처럼 칸 밖 편집이면 그 자리의 가장 안쪽 문단이\n * 호스트라 지워진다). 편집 범위 안에 통째로 든 캐시(행 삭제·문단 교체)는 편집이 지우므로 뺀다.\n *\n * 섹션 전체를 지우면(allLinesegRemovalSplices) 자체 조판이 없는 뷰어(rhwp 미리보기·PDF)가 손대지 않은 쪽까지 다시 짜서\n * 글자가 벌어지고 줄이 꺾이고 아래가 잘렸다(광진구 실문서 채우기·패치 27건 전부, 원본에서 캐시만 지운 대조본과 픽셀 차이 0).\n * rhwp 는 최상위 문단 하나라도 캐시가 없으면 그 구역의 세로 배치를 다시 짜고(줄 나눔은 캐시대로), 칸 문단의 캐시가 없는\n * 것은 그 칸만 다시 짠다: 그래서 호스트 문단 캐시를 지우지 않는 것이 채우기 결과를 지킨다(정부포상 동의서 실측: 최상위\n * 문단 캐시 1개 삭제 → 두 쪽 픽셀 차이 11.4%·11.2%, 칸 문단 캐시 1개 삭제 → 0%).\n * 한컴은 캐시가 일부 문단에만 없어도 경고 없이 연다: 한글 2024 실기기 대조(4.x Verified: 전체·일부·미삭제 세 변형 모두\n * 경고 없음)와 한컴 저장본 자체의 혼합 캐시(verify-reflow 36264961) 근거\n */\nexport function changedLinesegRemovalSplices(xml: string, edits: SpliceEdit[]): SpliceEdit[] {\n if (edits.length === 0) return []\n const points = edits.map(e => e.start).sort((a, b) => a - b)\n const tagRe = /<(\\/?)((?:[A-Za-z0-9_]+:)?(?:p|linesegarray))(?=[\\s/>])(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?(\\/?)>/g\n const stack: Array<{ dirty: boolean; seg?: { start: number; end: number } }> = []\n const out: SpliceEdit[] = []\n let pi = 0\n let m: RegExpExecArray | null\n while ((m = tagRe.exec(xml)) !== null) {\n // 이 태그 앞(같은 자리 포함)의 편집 지점은 지금 열린 가장 안쪽 문단을 더럽힌다: 문단 여는 태그 자리의 편집(문단 교체)은 바깥 문단\n while (pi < points.length && points[pi] <= m.index) {\n if (stack.length) stack[stack.length - 1].dirty = true\n pi++\n }\n const [full, close, qname, selfClose] = m\n const isSeg = qname.endsWith(\"linesegarray\")\n if (isSeg) {\n if (close) continue\n let end = m.index + full.length\n if (!selfClose) {\n const ci = xml.indexOf(`</${qname}>`, end)\n if (ci < 0) continue\n end = ci + qname.length + 3\n tagRe.lastIndex = end\n }\n const top = stack[stack.length - 1]\n if (top && !top.seg) top.seg = { start: m.index, end }\n continue\n }\n if (close) {\n const p = stack.pop()\n if (p?.dirty && p.seg) out.push({ start: p.seg.start, end: p.seg.end, replacement: \"\" })\n } else if (!selfClose) {\n stack.push({ dirty: false })\n }\n }\n const claimed = edits.filter(e => e.end > e.start)\n return out.filter(ls => !claimed.some(c => ls.start >= c.start && ls.end <= c.end))\n}\n\n/**\n * splice 일괄 적용 — 시작 위치로 정렬(같으면 넣은 순서), 겹치면 내부 오류, 원문 조각과 치환 글을 한 번에 잇는다.\n * 종전엔 뒤에서부터 slice+치환+slice 를 되풀이해 splice 마다 문자열 전체를 복사했다(O(글자 수 × splice 수)) —\n * 줄 배치 캐시를 전부 지우는 큰 섹션(1,020만 자·26,939 splice)에서 patch·fill·seal 이 32.6초 (v4.14.4 리뷰 실측).\n * 결과는 종전과 같다 (redact-hwpx.ts applySplicesLinear 와 같은 방식)\n */\nexport function applySplices(xml: string, splices: SpliceEdit[]): string {\n const sorted = [...splices].sort((a, b) => a.start - b.start)\n const parts: string[] = []\n let cursor = 0\n for (const s of sorted) {\n if (s.start < cursor) throw new Error(\"소스맵 splice 범위 겹침 — 내부 오류\")\n parts.push(xml.slice(cursor, s.start), s.replacement)\n cursor = s.end\n }\n parts.push(xml.slice(cursor))\n return parts.join(\"\")\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
|
+
{"version":3,"sources":["../src/form/seal.ts","../src/form/match.ts","../src/form/recognize.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, precheckZipSize } 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\"\nimport { readZipEntry } from \"../hwpx/parser-shared.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 precheckZipSize(hwpxBuffer) // 파싱 경로와 같은 ZIP bomb 가드\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 readZipEntry(zip.file(headerPath)!, \"text\")) : null\n\n const sectionXmls: string[] = []\n const scans: SectionScan[] = []\n for (let si = 0; si < sectionPaths.length; si++) {\n const xml = await readZipEntry(zip.file(sectionPaths[si])!, \"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 readZipEntry(zip.file(manifestPath)!, \"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","/** 양식 필드 매칭 공용 유틸 — filler.ts, filler-hwpx.ts에서 공유 */\n\nimport { LABEL_KEYWORDS, isBlankValue, isDatePlaceholder } from \"./recognize.js\"\nimport type { FormField } from \"../types.js\"\n\n/**\n * 채울 값 — 문자열이면 같은 라벨 모든 등장에 동일값(단일 양식),\n * 배열이면 등장 순서대로 하나씩 소진(2~30장 반복 양식·명부형 표).\n */\nexport type FillValue = string | string[]\n\n/**\n * 채울 값 입력 — 값만 주거나, 서식 변환(format)을 함께 지정한다.\n * 프로필엔 정준값 하나(생년월일 \"19900315\"), 서식마다 모양이 다를 때\n * format이 도구 안에서 변환한다 (claw-hwp secure-fill 포맷엔진 이식).\n */\nexport type FillInput = FillValue | { value: FillValue; format?: string }\n\n// ─── 값 서식 변환 (claw-hwp secure-fill formatValue 이식) ─────────────\n\nfunction parseYMD(v: string): { y: string; yy: string; m: string; d: string } | null {\n let y: string, m: string, day: string\n // 구분자(. - / 년월일 등)로 분리된 3토큰이면 미패딩 날짜(2026.7.5·2026년 7월 5일)로 우선 해석\n const parts = v.split(/[^0-9]+/).filter(Boolean)\n if (parts.length === 3) {\n const [yp, mp, dp] = parts\n const yn = +yp\n y = yp.length >= 3 ? yp : String(yn <= 29 ? 2000 + yn : 1900 + yn)\n m = mp.padStart(2, \"0\")\n day = dp.padStart(2, \"0\")\n } else {\n const d = v.replace(/\\D/g, \"\")\n if (d.length >= 8) { y = d.slice(0, 4); m = d.slice(4, 6); day = d.slice(6, 8) }\n else if (d.length === 6) { const yy = +d.slice(0, 2); y = String(yy <= 29 ? 2000 + yy : 1900 + yy); m = d.slice(2, 4); day = d.slice(4, 6) }\n else return null\n }\n // 월 1-12·일 1-31 범위 밖이면 fail-open(쓰레기 날짜 무음 기입 방지)\n if (+m < 1 || +m > 12 || +day < 1 || +day > 31) return null\n return { y, yy: y.slice(2), m, d: day }\n}\n\nfunction fmtDate(v: string, style: string): string {\n const p = parseYMD(v)\n if (!p) return v\n // 토큰 치환(대소문자 무시 — yyyy-MM-dd 혼용 수용, 긴 것 우선). 단독 m/d(선행 0\n // 제거)는 영문 리터럴(예: \"amended\")을 오염시키지 않게 영문자에 비인접일 때만 치환.\n return (style || \"yyyy-mm-dd\")\n .replace(/yyyy/gi, p.y).replace(/yy/gi, p.yy)\n .replace(/mm/gi, p.m).replace(/dd/gi, p.d)\n .replace(/(?<![a-z])m(?![a-z])/gi, String(+p.m))\n .replace(/(?<![a-z])d(?![a-z])/gi, String(+p.d))\n}\n\n/** 숫자 마스크 — `#` 하나가 값의 다음 숫자 하나를 소비, 리터럴은 통과 */\nfunction maskDigits(v: string, pattern: string): string {\n const ds = v.replace(/\\D/g, \"\")\n const need = (pattern.match(/#/g) ?? []).length\n // # 개수와 숫자 개수가 다르거나 패턴에 # 가 없으면, 그럴듯한 오값·끝자리 폐기·\n // 값 삭제 대신 원형을 반환한다 (무음 왜곡 금지 — fail-open).\n if (need === 0 || ds.length !== need) return v\n let i = 0\n return pattern.replace(/#/g, () => ds[i++])\n}\n\nfunction fmtPhone(v: string, style: string): string {\n const d = v.replace(/\\D/g, \"\")\n if (d.length < 9) return v\n // 서울(02)은 2자리 지역번호, 그 외(휴대폰 010·지역 0XX)는 3자리\n const areaLen = d.startsWith(\"02\") ? 2 : 3\n const a = d.slice(0, areaLen), b = d.slice(areaLen, -4), c = d.slice(-4)\n switch (style) {\n case \"digits\": return d\n case \"dot\": return `${a}.${b}.${c}`\n case \"space\": return `${a} ${b} ${c}`\n case \"intl\": return `+82-${d.slice(1, areaLen)}-${b}-${c}`\n case \"intl-paren\": return `82)${d.slice(1, areaLen)}-${b}-${c}`\n default: return `${a}-${b}-${c}` // hyphen\n }\n}\n\nfunction fmtRRN(v: string, style: string): string {\n const d = v.replace(/\\D/g, \"\")\n if (d.length !== 13) return v\n switch (style) {\n case \"digits\": return d\n case \"front\": return d.slice(0, 6)\n case \"masked\": return `${d.slice(0, 6)}-${d[6]}******`\n default: return `${d.slice(0, 6)}-${d.slice(6)}` // hyphen\n }\n}\n\n/**\n * 값 서식 변환 — `종류:스타일` 접두형(date:/phone:/rrn:/mask:/digits/upper/lower/nospace)\n * 또는 접두 없는 자유 패턴(`#` 포함 → 숫자마스크, yyyy·yy·mm·dd 포함 → 날짜 토큰).\n * 모르는 포맷은 값 원형 반환 (fail-open — 채움 자체는 진행).\n */\nexport function formatFillValue(value: string, format?: string): string {\n if (!format) return value\n const ci = format.indexOf(\":\")\n const kind = ci >= 0 ? format.slice(0, ci) : format\n const style = ci >= 0 ? format.slice(ci + 1) : \"\"\n if (kind === \"date\") return fmtDate(value, style)\n if (kind === \"phone\") return fmtPhone(value, style)\n if (kind === \"rrn\") return fmtRRN(value, style)\n if (kind === \"mask\") return maskDigits(value, style)\n if (kind === \"digits\") { const only = value.replace(/\\D/g, \"\"); return only || value }\n if (kind === \"upper\") return value.toUpperCase()\n if (kind === \"lower\") return value.toLowerCase()\n if (kind === \"nospace\") return value.replace(/\\s+/g, \"\")\n if (format.includes(\"#\")) return maskDigits(value, format)\n if (/(yyyy|yy|mm|dd)/.test(format)) return fmtDate(value, format)\n return value\n}\n\n/**\n * 다중값 커서 — 라벨별 값 소비 상태를 추적한다.\n * 스칼라 값은 무한 반복(기존 동작), 배열 값은 적용 순서대로 소진되며\n * 다 쓰면 available=false가 되어 이후 등장은 채우지 않는다.\n */\nexport class ValueCursor {\n private nextIdx = new Map<string, number>()\n constructor(private values: Map<string, FillValue>) {}\n\n keys(): IterableIterator<string> {\n return this.values.keys()\n }\n\n has(key: string): boolean {\n return this.values.has(key)\n }\n\n isArray(key: string): boolean {\n return Array.isArray(this.values.get(key))\n }\n\n /** 남은 값이 있으면 true (스칼라는 항상 true) */\n available(key: string): boolean {\n const v = this.values.get(key)\n if (v === undefined) return false\n return typeof v === \"string\" || (this.nextIdx.get(key) ?? 0) < v.length\n }\n\n /** 현재 값 미리보기 (소진 없음) */\n peek(key: string): string | undefined {\n const v = this.values.get(key)\n if (v === undefined) return undefined\n if (typeof v === \"string\") return v\n const i = this.nextIdx.get(key) ?? 0\n return i < v.length ? v[i] : undefined\n }\n\n /** 값 소비 — 배열이면 커서 전진, 소진 시 undefined */\n consume(key: string): string | undefined {\n const v = this.values.get(key)\n if (v === undefined) return undefined\n if (typeof v === \"string\") return v\n const i = this.nextIdx.get(key) ?? 0\n if (i >= v.length) return undefined\n this.nextIdx.set(key, i + 1)\n return v[i]\n }\n\n /** 스칼라 키를 처음 쓴 칸의 원래 글: 두 번째 이후 등장은 빈 칸·같은 견본 글에만 쓴다 */\n private firstTarget = new Map<string, string>()\n\n /** 스칼라 키의 두 번째 이후 등장: 빈 칸이거나 첫 등장과 같은 견본 글일 때만 쓴다 (끝의 수신자 명단·서명란 같은 남의 글 보호) */\n laterOk(key: string, current: string): boolean {\n return this.isArray(key) || !this.firstTarget.has(key) || isBlankValue(current) || current.trim() === this.firstTarget.get(key)\n }\n\n markFirst(key: string, current: string): void {\n if (!this.isArray(key) && !this.firstTarget.has(key)) this.firstTarget.set(key, current.trim())\n }\n}\n\n/** 어노테이션 빈칸 \"(한자: )\"·\"(전화번호: )\": 라벨처럼 보여도 값을 받는 자리 */\nexport const ANNOTATION_BLANK_RE = /[((][^))]*[::]\\s*[))]/\n\n/** 남의 라벨 칸(값으로 덮으면 안 되는 칸): 날짜 자리표시가 아닌 키워드 라벨·다른 입력 키의 라벨(\"허가일자\"·\"전 공\")·①번호 라벨(\"① 지번\").\n * 두 채우기 경로(filler-hwpx·filler)가 같은 규칙을 쓴다 */\nexport function isForeignLabel(text: string, values: ValueCursor): boolean {\n // 날짜 자리표시는 값 자리 — 문단을 이어 붙인 \"년 월 입학\" + \"년 월 졸업\" 은 키워드 \"학년\" 을 품는다 (#146)\n if (isDatePlaceholder(text)) return false\n if (isKeywordLabel(text)) return true\n const t = text.trim()\n if (/^[①-⑳]/.test(t) && t.length <= 20) return true\n if (ANNOTATION_BLANK_RE.test(t)) return false // \"(한자: )\" 어노테이션 빈칸 칸은 값 자리\n const n = normalizeLabel(t)\n return !!n && values.has(n) // 정확 일치만: 접두 매칭이면 값 \"수신자 참조\" 가 키 \"수신자\" 의 라벨로 오인된다\n}\n\n/** 라벨 정규화 — 콜론/공백/특수문자 제거, 비교용 */\nexport function normalizeLabel(label: string): string {\n // 첨자 태그(<sup>·<sub>, 파서가 칸 글에 넣음)는 걷는다 — 이름표 \"면적(m<sup>2</sup>)\" 가 입력 키 \"면적(m2)\" 와 맞게\n return label.trim().replace(/<\\/?su[bp]>/g, \"\").replace(/[::\\s()()·]/g, \"\")\n}\n\n/**\n * 정규화된 셀 라벨과 입력 값 맵에서 최적 매칭 키를 찾음.\n *\n * 매칭 우선순위:\n * 1. 정확 매칭 (normalizedCellLabel === key)\n * 2. 접두사 기반 매칭 (60% 이상 겹침 필요)\n */\nexport function findMatchingKey(\n cellLabel: string,\n values: { has(key: string): boolean; keys(): Iterable<string> },\n): string | undefined {\n // 1) 정확 매칭\n if (values.has(cellLabel)) return cellLabel\n\n // 2) 접두사 기반 매칭 — 가장 긴 매칭 우선 (= 가장 구체적)\n // 단, 길이 비율 60% 이상 겹쳐야 매칭 (오탐 방지)\n let bestKey: string | undefined\n let bestLen = 0\n\n for (const key of values.keys()) {\n if (cellLabel.startsWith(key)) {\n if (key.length >= cellLabel.length * 0.6 && key.length > bestLen) {\n bestLen = key.length\n bestKey = key\n }\n } else if (key.startsWith(cellLabel)) {\n // 역방향(짧은 셀이 긴 키를 흡수)은 0.75 — 0.6이면 셀 \"대리인\"이 키\n // \"대리인성명\"을 흡수해 성명 값이 대리인 라벨 옆에 들어간다\n if (cellLabel.length >= key.length * 0.75 && cellLabel.length > bestLen) {\n bestLen = cellLabel.length\n bestKey = key\n }\n }\n }\n\n return bestKey\n}\n\n/**\n * 값 셀이 키워드 라벨(섹션 헤더의 하위 라벨)인지 판별.\n * \"성명\", \"주소\" 같은 키워드 라벨이면 true → 스킵 대상.\n * \"(한자:)\" 같은 어노테이션이면 false → 채울 수 있음.\n */\nexport function isKeywordLabel(text: string): boolean {\n const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, \"\").trim()\n if (!trimmed || trimmed.length > 15) return false\n for (const kw of LABEL_KEYWORDS) {\n if (trimmed.includes(kw)) return true\n }\n return false\n}\n\n/**\n * 셀 텍스트에서 인셀 패턴을 찾아 교체 — 체크박스 + 괄호 빈칸.\n *\n * 지원 패턴:\n * 1. 괄호 빈칸: `일반( )통` → 키 \"일반통\" 또는 \"일반\" 매칭 시 → `일반(값)통`\n * 2. 체크박스: `□부` → 키 \"부\" 매칭 시 → `☑부` (값이 \"☑\",\"✓\",\"v\",\"V\",\"true\",\"1\" 등)\n *\n * @returns 교체된 텍스트 + 매칭된 키 목록. null이면 교체 없음.\n */\nexport function fillInCellPatterns(\n cellText: string,\n values: ValueCursor,\n matchedLabels: Set<string>,\n /** require_unique 2차에서 거부된 라벨 셀 차단 (sfill-2 전략0) — 전략 1~3과 동일 계약 */\n blockedLabels?: Set<string>,\n /** 어노테이션 빈칸 \"(라벨: )\" 으로는 채우지 않을 키: 문서에 그 라벨의 제 칸이 따로 있는 스칼라 키 (filler-hwpx directKeys) */\n annotationSkip?: Set<string>,\n): { text: string; matches: Array<{ key: string; label: string; value: string }> } | null {\n let text = cellText\n const matches: Array<{ key: string; label: string; value: string }> = []\n\n // 1) 괄호 빈칸: keyword(\\s+)suffix → keyword(value)suffix\n text = text.replace(\n /([가-힣A-Za-z]+)\\(\\s{1,}\\)([가-힣A-Za-z]*)/g,\n (match, prefix: string, suffix: string) => {\n const label = prefix + suffix // \"일반\" + \"통\" = \"일반통\"\n const normalizedLabel = normalizeLabel(label)\n // require_unique 2차: 이 라벨이 거부됐으면 접두사 폴백으로 남의 키에 재배정 금지 (sfill-2 전략0)\n if (blockedLabels?.has(normalizedLabel)) return match\n // 정확 매칭 → 접두사만 매칭 순\n const matchKey = values.available(normalizedLabel)\n ? normalizedLabel\n : values.available(normalizeLabel(prefix))\n ? normalizeLabel(prefix)\n : undefined\n if (matchKey === undefined) return match\n\n const newValue = values.consume(matchKey)!\n matchedLabels.add(matchKey)\n matches.push({ key: matchKey, label, value: newValue })\n return `${prefix}(${newValue})${suffix}`\n },\n )\n\n // 2) 체크박스: □keyword → ☑keyword (값이 truthy)\n text = text.replace(\n /□([가-힣A-Za-z]+)/g,\n (match, keyword: string) => {\n const normalizedKw = normalizeLabel(keyword)\n const matchKey = values.available(normalizedKw) ? normalizedKw : undefined\n if (matchKey === undefined) return match\n\n const val = values.peek(matchKey)!\n // 계약: 빈 문자열은 \"체크 안 함\" — 체크 의사는 ☑/true/1 등 명시 값으로만 표현한다\n const isTruthy = [\"☑\", \"✓\", \"✔\", \"v\", \"V\", \"true\", \"1\", \"yes\", \"o\", \"O\"].includes(val.trim())\n if (!isTruthy) return match\n\n values.consume(matchKey)\n matchedLabels.add(matchKey)\n matches.push({ key: matchKey, label: `□${keyword}`, value: \"☑\" })\n return `☑${keyword}`\n },\n )\n\n // 3) 어노테이션 빈칸: (keyword:\\s+) → (keyword:value)\n // 예: \"(한자: )\" → \"(한자:金民秀)\"\n // 예: \"(성명: )\" → \"(성명: 홍길동)\"\n text = text.replace(\n /\\(([가-힣A-Za-z]+)[::]\\s{1,}\\)/g,\n (match, keyword: string) => {\n const normalizedKw = normalizeLabel(keyword)\n const matchKey = values.available(normalizedKw) ? normalizedKw : undefined\n if (matchKey === undefined || annotationSkip?.has(matchKey)) return match\n\n const newValue = values.consume(matchKey)!\n matchedLabels.add(matchKey)\n matches.push({ key: matchKey, label: keyword, value: newValue })\n return `(${keyword}:${newValue})`\n },\n )\n\n return matches.length > 0 ? { text, matches } : null\n}\n\n// ─── 인라인 \"라벨: 값\" 세그먼트 분해 ─────────────────\n\nexport interface InlineSegment {\n /** 라벨 (콜론 제외) */\n label: string\n /** 라벨 시작 오프셋 */\n labelStart: number\n /** 값 시작 오프셋 (콜론·공백 뒤) */\n valueStart: number\n /** 값 끝 오프셋 (다음 라벨/구분자 직전, 우측 공백 제외) */\n valueEnd: number\n /** text.slice(valueStart, valueEnd) */\n value: string\n /**\n * 직전 어절을 포함한 확장 라벨 (\"신청인 성명\") — 정확 매칭 후보.\n * 직전 어절이 이전 라벨의 값(\"… 강남구 전화:\")일 수 있으므로 정확 매칭에만\n * 쓰고, 접두사 매칭은 핵심 라벨(label)로만 한다. 직전 어절이 없으면 undefined.\n */\n extLabel?: string\n /** 확장 라벨 시작 오프셋 (직전 어절 첫 글자) — extLabel 있을 때만 */\n extStart?: number\n}\n\nconst INLINE_LABEL_RE = /((?:[가-힣A-Za-z]{1,10} )?)([가-힣A-Za-z]{2,10})\\s*[::]/g\n/** 값 끝의 날인 표시(\"(인)\"·\"(서명 또는 인)\") — 값이 아니라 도장 자리(place_seal 앵커)라 값 범위에서 뺀다. 종전엔 \"대표자 : (인)\" 을\n * 채우면 \"(인)\" 이 값으로 덮여 사라졌다 (#146) */\nconst SEAL_TAIL_RE = /[((]\\s*(?:인|서\\s*명|날\\s*인|서명\\s*또는\\s*인|인\\s*또는\\s*서명|서명\\s*또는\\s*날인)\\s*[))]\\s*$/\n/** 균등 배분 라벨(\"업 체 명 :\"·\"대 표 자 :\"·\"상 호:\") — 한 글자씩 같은 너비(1~3칸)로 띄운 2~8자. 앞이 글자면 낱말 중간이라 보지 않는다 (#146).\n * 종전엔 한 칸 띄움만 읽어 서명란 \"주 소:\"·\"상 호:\"·\"성 명:\" 이 매칭되지 않았다 (#151) */\nconst SPACED_INLINE_LABEL_RE = /(?<![가-힣A-Za-z]\\s?)([가-힣]( {1,3})[가-힣](?:\\2[가-힣]){0,6})\\s*[::]/g\n\n/**\n * 인라인 양식 한 줄을 라벨 단위로 분해 — 한 줄에 라벨이 여러 개인 양식\n * (\"자문위원 성명: 작성일자: \") 지원. 값은 다음 라벨 직전(또는\n * 구분자 [,;\\n] / 100자 / 줄끝)에서 끝난다. 값이 빈 라벨도 세그먼트로\n * 반환한다 (value=\"\", valueStart===valueEnd — 채움 대상).\n * \"http://\" 류 URL 스킴의 콜론은 라벨로 보지 않는다.\n */\nexport function scanInlineSegments(text: string): InlineSegment[] {\n const labels: Array<{ label: string; ext?: string; extStart?: number; start: number; end: number }> = []\n INLINE_LABEL_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = INLINE_LABEL_RE.exec(text)) !== null) {\n if (text[INLINE_LABEL_RE.lastIndex] === \"/\") continue // \"://\" — URL 스킴\n // m[1]=직전 어절(확장 라벨용, 공백 포함), m[2]=핵심 라벨. 세그먼트 좌표는\n // 핵심 라벨 기준(기존 값 경계 보존) — \"신청인 성명:\"과 \"대리인 성명:\"이 같은\n // \"성명\"으로 붕괴하지 않게 확장 라벨을 정확 매칭 후보로 함께 넘긴다.\n labels.push({\n label: m[2],\n ext: m[1] ? m[1] + m[2] : undefined,\n extStart: m[1] ? m.index : undefined,\n start: m.index + m[1].length,\n end: INLINE_LABEL_RE.lastIndex,\n })\n }\n SPACED_INLINE_LABEL_RE.lastIndex = 0\n while ((m = SPACED_INLINE_LABEL_RE.exec(text)) !== null) {\n const start = m.index, end = SPACED_INLINE_LABEL_RE.lastIndex\n if (labels.some(l => l.start < end && start < l.end)) continue\n labels.push({ label: m[1], start, end })\n }\n labels.sort((a, b) => a.start - b.start)\n\n const segments: InlineSegment[] = []\n for (let i = 0; i < labels.length; i++) {\n const cur = labels[i]\n let vs = cur.end\n while (vs < text.length && (text[vs] === \" \" || text[vs] === \"\\t\")) vs++\n let ve = i + 1 < labels.length ? labels[i + 1].start : text.length\n if (ve < vs) ve = vs // 다음 라벨이 공백 스킵 구간 안에서 시작하는 경우\n const sep = text.slice(vs, ve).search(/[\\n,;]/)\n if (sep !== -1) ve = vs + sep\n if (ve - vs > 100) ve = vs + 100\n const seal = text.slice(vs, ve).search(SEAL_TAIL_RE)\n if (seal !== -1) ve = vs + seal\n const sealAt = ve\n while (ve > vs && /\\s/.test(text[ve - 1])) ve--\n // 서명란 고정 문구(#151): \"서약자: 대표 (인)\" 처럼 넓은 빈칸 뒤의 글이 날인 앞 넓은 빈칸으로 끝나면 값이 아니라 고정 문구다.\n // 값 자리는 그 앞(라벨 뒤 첫 빈칸) — 종전엔 \"대표\" 를 값으로 덮어썼다. 빈 서명란에 채운 \"성 명: 홍길동 (인)\" 은 날인 앞이 한 칸이라 그대로 값이다\n if (seal !== -1 && ve > vs && sealAt - ve >= 3) {\n const gaps = [...text.slice(cur.end, ve).matchAll(/[ \\t]{3,}/g)]\n const gap = gaps[gaps.length - 1]\n if (gap) {\n ve = cur.end + gap.index\n while (ve > cur.end && /\\s/.test(text[ve - 1])) ve--\n vs = cur.end\n while (vs < ve && /\\s/.test(text[vs])) vs++\n if (vs >= ve) vs = ve = cur.end + (text[cur.end] === \" \" || text[cur.end] === \"\\t\" ? 1 : 0) // 빈 값: 콜론 뒤 한 칸 다음에 넣는다\n }\n }\n segments.push({\n label: cur.label,\n labelStart: cur.start,\n valueStart: vs,\n valueEnd: ve,\n value: text.slice(vs, ve),\n ...(cur.ext !== undefined ? { extLabel: cur.ext, extStart: cur.extStart } : {}),\n })\n }\n return segments\n}\n\n/**\n * 인라인 세그먼트 매칭 키 결정 — 확장 라벨(\"신청인 성명\") 정확 매칭 우선,\n * 핵심 라벨(\"성명\")은 기존 findMatchingKey(정확+접두사) 폴백.\n * 확장 라벨은 직전 어절이 이전 라벨의 값일 수 있어 정확 매칭만 허용한다 (과탐 방지).\n * @returns 매칭 키 + filled 보고용 라벨 + 확장 매칭 여부. blockedLabels(sfill-2)에 걸리면 undefined.\n */\nexport function matchInlineSegment(\n seg: InlineSegment,\n values: { has(key: string): boolean; keys(): Iterable<string> },\n blockedLabels?: Set<string>,\n): { key: string; label: string; viaExt: boolean } | undefined {\n const nlabel = normalizeLabel(seg.label)\n if (seg.extLabel !== undefined) {\n const nExt = normalizeLabel(seg.extLabel)\n if (nExt !== nlabel && !blockedLabels?.has(nExt) && values.has(nExt)) {\n return { key: nExt, label: seg.extLabel, viaExt: true }\n }\n }\n if (blockedLabels?.has(nlabel)) return undefined\n const key = findMatchingKey(nlabel, values)\n return key !== undefined ? { key, label: seg.label, viaExt: false } : undefined\n}\n\n/**\n * 세그먼트 값의 유효 끝 — 다음 세그먼트가 확장 라벨로 매칭됐으면 값 범위가\n * 그 직전 어절(확장 라벨의 일부, \"대리인 성명\"의 \"대리인\")을 삼키지 않게 앞당긴다.\n */\nexport function clampSegmentEnd(\n text: string,\n seg: InlineSegment,\n next: InlineSegment | undefined,\n nextViaExt: boolean,\n): number {\n let ve = seg.valueEnd\n if (nextViaExt && next?.extStart !== undefined && next.extStart < ve) ve = next.extStart\n while (ve > seg.valueStart && /\\s/.test(text[ve - 1])) ve--\n return ve\n}\n\n/** 빈 자리(valueStart===valueEnd) 삽입 시 콜론·다음 라벨과 붙지 않게 공백 부착 */\nexport function padInsertion(text: string, pos: number, value: string): string {\n const lead = pos > 0 && !/\\s/.test(text[pos - 1]) ? \" \" : \"\"\n const trail = pos < text.length && !/\\s/.test(text[pos]) ? \" \" : \"\"\n return lead + value + trail\n}\n\n/**\n * 입력 values 맵을 정규화된 키로 변환 — format 지정(FillInput 객체형)은 여기서 값으로 변환.\n * @param warnings 정규화 후 키가 충돌하는 라벨(\"성 명\"과 \"성명\")의 무음 덮어쓰기 경고 수집\n */\nexport function normalizeValues(values: Record<string, FillInput>, warnings?: string[]): Map<string, FillValue> {\n const map = new Map<string, FillValue>()\n for (const [label, raw] of Object.entries(values)) {\n const { value, format } = typeof raw === \"object\" && !Array.isArray(raw)\n ? raw\n : { value: raw, format: undefined }\n const key = normalizeLabel(label)\n if (map.has(key)) {\n warnings?.push(`입력 라벨 \"${label}\"이 정규화 키 \"${key}\"에서 다른 라벨과 충돌 — 뒤 값으로 덮어씀`)\n }\n map.set(key, Array.isArray(value)\n ? value.map(v => formatFillValue(v, format))\n : formatFillValue(value, format))\n }\n return map\n}\n\n/**\n * 모호 라벨 거부 가드 — 한 입력 키(스칼라)가 서식의 2곳 이상에 채워지면 남의 블록\n * 오염 위험(대표자 행에 참여인력 값이 들어가는 류)이므로, 그 키는 채우지 않고\n * rejected로 보고한 뒤 나머지 키만으로 다시 채운다 (claw-hwp require_occurrence 이식).\n * 배열 값은 다중 등장 소진이 의도된 동작이라 거부 대상이 아니다.\n * @param run 같은 원본에 대해 결정적으로 재실행 가능한 채우기 함수\n */\nexport async function fillWithUniqueGuard<R extends { filled: Array<{ key?: string; label: string }> }>(\n values: Record<string, FillInput>,\n run: (vals: Record<string, FillInput>, blockedLabels?: Set<string>) => R | Promise<R>,\n): Promise<R & { rejected: string[] }> {\n const first = await run(values)\n const counts = new Map<string, number>()\n for (const f of first.filled) {\n // 누름틀(clickhere)은 이름 정확 일치라 다중 등장이 곧 서식의 설계다 — 집계에서 뺀다.\n // 세지 않으면 동명 누름틀 2곳짜리 서식이 통째로 rejected 되어 한 곳도 안 채워진다.\n if (f.key && (f as { source?: string }).source !== \"clickhere\") {\n counts.set(f.key, (counts.get(f.key) ?? 0) + 1)\n }\n }\n const isArrayValue = (normKey: string): boolean => {\n for (const [label, raw] of Object.entries(values)) {\n if (normalizeLabel(label) !== normKey) continue\n return Array.isArray(typeof raw === \"object\" && !Array.isArray(raw) ? raw.value : raw)\n }\n return false\n }\n const dup = new Set([...counts].filter(([k, n]) => n >= 2 && !isArrayValue(k)).map(([k]) => k))\n if (dup.size === 0) return { ...first, rejected: [] }\n // 거부된 키에 매칭됐던 셀 라벨들 — 2차에서 이 셀이 접두사 매칭으로 남은 다른 키에\n // 재배정되어 남의 값으로 오염되는 것을 차단한다 (sfill-2).\n const blockedLabels = new Set<string>()\n for (const f of first.filled) {\n if (f.key && dup.has(f.key)) blockedLabels.add(normalizeLabel(f.label))\n }\n const filtered = Object.fromEntries(Object.entries(values).filter(([label]) => !dup.has(normalizeLabel(label))))\n const second = await run(filtered, blockedLabels)\n const rejected = Object.keys(values).filter(label => dup.has(normalizeLabel(label)))\n return { ...second, rejected }\n}\n\n/** 매칭 안 된 라벨을 원본 키로 복원 */\nexport function resolveUnmatched(\n normalizedValues: Map<string, FillValue>,\n matchedLabels: Set<string>,\n originalValues: Record<string, FillInput>,\n): string[] {\n return [...normalizedValues.keys()]\n .filter(k => !matchedLabels.has(k))\n .map(k => {\n for (const orig of Object.keys(originalValues)) {\n if (normalizeLabel(orig) === k) return orig\n }\n return k\n })\n}\n\n/** fill --dry-run 에 값을 함께 준 결과 — 키·배열 번호별 값을 쓸 칸(target: 표·행·열). 쓰지 않고 실제 채움을 메모리에서만 돌려 낸다.\n * 같은 칸을 나중 키가 다시 쓰면 overwrittenBy 로 알린다 (#145) */\nexport function fillPlan(values: Record<string, FillInput>, filled: FormField[], unmatched: string[], rejected: string[]) {\n const inputKey = new Map(Object.keys(values).map(k => [normalizeLabel(k), k]))\n const seen = new Map<string, number>()\n const targets = filled.map((f, i) => {\n const key = (f.key !== undefined && inputKey.get(f.key)) || f.key || f.label\n const index = seen.get(key) ?? 0\n seen.set(key, index + 1)\n const t = f.target\n const later = t && filled.slice(i + 1).find(g => g.target?.table === t.table && g.target.row === t.row && g.target.col === t.col)\n const overwrittenBy = later ? ((later.key !== undefined && inputKey.get(later.key)) || later.key || later.label) : undefined\n return { key, index, label: f.label, value: f.value, target: t ?? null, ...(overwrittenBy ? { overwrittenBy } : {}) }\n })\n return { targets, unmatched, ...(rejected.length > 0 ? { rejected } : {}) }\n}\n","/** 양식(서식) 필드 인식 — 테이블 기반 label-value 패턴 매칭 */\n\nimport type { IRBlock, IRCell, IRTable, FormField, FormResult } from \"../types.js\"\nimport { scanInlineSegments, normalizeLabel } from \"./match.js\"\n\n/** 한국 공문서 필드 라벨 키워드 */\nexport const LABEL_KEYWORDS = new Set([\n \"성명\", \"이름\", \"주소\", \"전화\", \"전화번호\", \"휴대폰\", \"핸드폰\", \"연락처\",\n \"생년월일\", \"주민등록번호\", \"소속\", \"직위\", \"직급\", \"부서\",\n \"이메일\", \"팩스\", \"학교\", \"학년\", \"반\", \"번호\",\n \"신청인\", \"대표자\", \"담당자\", \"작성자\", \"확인자\", \"승인자\",\n \"일시\", \"날짜\", \"기간\", \"장소\", \"목적\", \"사유\", \"비고\",\n \"금액\", \"수량\", \"단가\", \"합계\", \"계\", \"소계\",\n \"등록기준지\", \"본적\", \"위임인\", \"청구사유\", \"소명자료\",\n])\n\n/** 콜론 없는 영문 라벨로 인정하는 단어 (한국 공문서 병기 관행) */\nconst ENGLISH_LABEL_WORDS = new Set([\n \"name\", \"date\", \"address\", \"tel\", \"phone\", \"mobile\", \"fax\", \"email\", \"e-mail\",\n \"dept\", \"department\", \"division\", \"title\", \"position\", \"grade\", \"rank\",\n \"birth\", \"nationality\", \"sex\", \"gender\", \"signature\", \"sign\", \"seal\",\n \"remarks\", \"note\", \"period\", \"place\", \"purpose\", \"reason\", \"amount\", \"total\",\n \"sum\", \"qty\", \"quantity\", \"unit\", \"no\", \"id\", \"passport\",\n])\nconst ENGLISH_STOPWORDS = new Set([\"of\", \"the\", \"and\", \"or\", \"in\"])\n\n/** 수량/단위 값 형태 — \"6개월\"·\"1억원\"·\"5백만원\"·\"2026년\" 등은 라벨이 아님 */\nconst NUMERIC_VALUE_RE = /^제?\\d+(?:[.,]\\d+)*[십백천만억조]*(?:원|명|건|개|회|부|매|장|점|호|번|년|월|일|시|분|초|개월|주년|차례|퍼센트)?$/\n\n/** 서술형 문장 어미 — 셀에 든 안내 문구를 라벨로 오인하지 않기 위한 컷 */\nconst SENTENCE_ENDING_RE = /(?:입니다|합니다|습니다|하세요|십시오|시오|바랍니다|바람|할 것|할것|하며|하고|한다|된다|됨|음|임)$/\n\n/** 라벨처럼 보이는 셀인지 판별 */\nexport function isLabelCell(text: string): boolean {\n // 각주 번호/특수문자 제거 후 판별 (예: \"등록기준지²\" → \"등록기준지\")\n const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, \"\").trim()\n if (!trimmed || trimmed.length > 30) return false\n // 키워드 매칭\n for (const kw of LABEL_KEYWORDS) {\n if (trimmed.includes(kw)) return true\n }\n // 짧은 한글 텍스트 (2-12자, 공백/괄호/특수기호 허용) — 숫자는 낄 수 있으나\n // (\"연번1\"·\"제1항목\"·\"1차소속\") 수량/단위 값·서술형 문구·법인명은 라벨이 아님.\n // 9자 이상 확장 구간은 3어절 이상 제목성 문구를 컷 (자간 공백 라벨\n // \"업 체 명\"류가 있는 8자 이하 기존 구간에는 어절 제한을 걸지 않는다)\n const compact = trimmed.replace(/\\s/g, \"\")\n if (/^[가-힣0-9()()·::\\-]+$/.test(compact)\n && compact.length >= 2 && compact.length <= 12\n && (compact.match(/[가-힣]/g) ?? []).length >= 2\n && (compact.length <= 8 || trimmed.split(/\\s+/).length <= 2)\n && !NUMERIC_VALUE_RE.test(compact)\n && !SENTENCE_ENDING_RE.test(trimmed)\n && !/^[((]주[))]|^주식회사/.test(compact)) {\n return true\n }\n // \"라벨:\" 패턴\n if (/^[가-힣A-Za-z\\s]+[::]$/.test(trimmed)) return true\n // 콜론 없는 영문 라벨 (\"Name\"·\"Date of Birth\") — 관행 단어 목록으로 한정\n if (/^[A-Za-z][A-Za-z\\s./&-]*$/.test(trimmed) && trimmed.length <= 20) {\n const words = trimmed.toLowerCase().split(/[\\s/&]+/).filter(w => w && !ENGLISH_STOPWORDS.has(w))\n if (words.length >= 1 && words.length <= 3 && words.every(w => ENGLISH_LABEL_WORDS.has(w.replace(/\\.$/, \"\")))) {\n return true\n }\n }\n return false\n}\n\n/** 날짜 자리표시 칸(\"년 월 - 년 월\"·\"년 월 일 ~ 년 월 일\"·\"년 월\") — 날짜 단위 글자와 숫자·구분 기호뿐이고, 기간 구분 기호가 있거나\n * 단위 글자가 띄어 있다. 열 머리 아래의 이 칸은 값 자리다: 라벨로 보면 \"첫 행이 전부 라벨인 표\" 의 머리 행 보호가 풀려 머리 칸 값이 옆\n * 머리 칸에 들어갔다 (#146). isLabelCell 은 그대로 둔다 — 날짜 줄(\"년 월 일\")은 dry-run 이 필드로 내주는 키다 */\nexport function isDatePlaceholder(text: string): boolean {\n // 숫자는 연도 앞자리(\"20 년 월 일\")만 — 단위 사이에 숫자가 있으면 \"8월 31일\" 같은 실제 날짜 값이다.\n // 학력·경력 칸의 \"년 월 입학 / 년 월 졸업\"·\"년 월 ~ 현재\" 꼬리말도 자리표시다\n const t = text.trim().replace(/부터|까지|입학|졸업|수료|재학|중퇴|편입|입사|퇴사|현재/g, \" \").replace(/(^|[-~~∼])\\s*(?:19|20)(?=\\s)/g, \"$1\")\n if (!/[년월일시분]/.test(t) || !/^[년월일시분초\\s.\\-~~∼()()/]+$/.test(t)) return false\n return /[-~~∼]/.test(t) || /[년월일시분]\\s+[년월일시분]/.test(t)\n}\n\n/** 빈 값 칸: 비었거나 밑줄·괄호 빈칸·대시 같은 자리표시뿐 */\nexport function isBlankValue(text: string): boolean {\n return /^[\\s_()()\\-\\u2014\\u2013~.·,]*$/.test(text) // 대시(U+2014·U+2013)도 자리표시\n}\n\n/**\n * 라벨 칸 오른쪽 칸이 하위 라벨인가 — 행 머리(\"신청인\"·위임장 \"대표자\") 옆 \"상호 또는 법인명칭\"·\"성 명\" 처럼 라벨 꼴이고 그 옆이 빈 값 칸이면\n * 값 자리가 아니다. 채우기(filler-hwpx·filler)가 같은 규칙을 쓴다. 입력 키와 무관한 구조 판정이라 배열 값 순서가 다른 키 구성에 따라\n * 바뀌지 않는다 (#145). 다시 채우는 \"성명 | 김철수 | 빈칸\" 은 행 머리(여러 행에 걸침)도 키워드 라벨도 아니라 값 자리로 둔다\n */\nexport function isSubLabelBeside(labelRowSpan: number, text: string, after: string | undefined): boolean {\n if (after === undefined || isBlankValue(text) || !isBlankValue(after) || !isLabelCell(text)) return false\n const compact = text.replace(/\\s/g, \"\")\n return labelRowSpan > 1 || (compact.length <= 15 && [...LABEL_KEYWORDS].some(kw => compact.includes(kw)))\n}\n\n/**\n * 열 머리 행 판정: 라벨이 위에, 값이 아래에 오는 표의 머리 행인가. 채우기(filler-hwpx)와 인식(extractFormSchema)이 같은 규칙을 쓴다.\n * heads: 그 행의 머리 후보 칸 글(아래로 걸친 구역 라벨 rowSpan 칸은 빼고), below: 같은 칸 좌표의 다음 행 칸 글(없으면 undefined).\n * - 아래 칸이 전부 비었으면 머리 글이 라벨 꼴이 아니어도 머리(\"검토의견 (의견제출 내용)\")\n * - 아니면 머리 3칸 이상이 전부 라벨이고 아래 칸이 하나 빼고 빈 경우만 (줄 이름표 \"고등학교\"·\"년 월\" 자리표시 허용).\n * \"성명 | 홍길동\" 같은 라벨-값 2칸 행을 머리로 오인하지 않게 좁힌다\n */\nexport function isColumnHeaderRow(heads: string[], below: Array<string | undefined>): boolean {\n if (heads.length < 2 || below.some(b => b === undefined)) return false\n const hs = heads.map(h => h.trim())\n if (hs.some(h => !h || h.length > 40 || SENTENCE_ENDING_RE.test(h))) return false\n const blanks = below.filter(b => isBlankValue(b!) || isDatePlaceholder(b!)).length // 날짜 자리표시도 빈 값 칸 (#146)\n if (blanks === below.length) return true\n return hs.length >= 3 && blanks >= hs.length - 1 && hs.every(h => h.length <= 20 && isLabelCell(h))\n}\n\n/**\n * IRBlock[]에서 양식 필드를 인식하여 추출.\n * 테이블의 label-value 패턴을 감지.\n */\nexport function extractFormFields(blocks: IRBlock[]): FormResult {\n const fields: FormField[] = []\n let totalTables = 0\n let formTables = 0\n\n for (const table of collectTables(blocks)) {\n totalTables++\n\n const tableFields = extractFromTable(table)\n if (tableFields.length > 0) {\n formTables++\n fields.push(...tableFields)\n }\n }\n\n // 인라인 \"라벨: 값\" 패턴도 검사 (paragraph만 — heading/list는 양식 필드가 아님)\n for (const block of blocks) {\n if (block.type === \"paragraph\" && block.text) {\n const inlineFields = extractInlineFields(block.text)\n fields.push(...inlineFields)\n }\n }\n\n const confidence = totalTables > 0 ? formTables / totalTables : (fields.length > 0 ? 0.3 : 0)\n return { fields, confidence: Math.min(confidence, 1) }\n}\n\n/**\n * 문서 순서 표 수집 — 셀 blocks 의 중첩표 포함. 법령 별지서식은 1칸 틀 안에 신청인 정보·발신명의 표가\n * 중첩되어 있고(HWPX 파서·PDF 클립 셀 그리드 모두 셀 blocks 로 낸다) fillHwpx 도 중첩표를 DFS 로 훑으므로\n * 인식기도 같은 경계로 본다 — 최상위만 보면 틀 안 표의 라벨-값이 통째로 빠진다 (v4.12.2)\n */\nfunction collectTables(blocks: IRBlock[], out: IRTable[] = [], depth = 0): IRTable[] {\n if (depth > 12) return out\n for (const block of blocks) {\n if (block.type !== \"table\" || !block.table) continue\n out.push(block.table)\n for (const row of block.table.cells) {\n for (const cell of row) if (cell?.blocks?.length) collectTables(cell.blocks, out, depth + 1)\n }\n }\n return out\n}\n\n/** IR 격자에서 병합으로 덮인 자리: 격자는 덮인 자리에도 빈 칸을 두므로 앵커와 구분해야 한다 */\nfunction coveredGrid(table: IRTable): boolean[][] {\n const covered = Array.from({ length: table.rows }, () => new Array<boolean>(table.cols).fill(false))\n for (let r = 0; r < table.rows; r++) {\n for (let c = 0; c < table.cols; c++) {\n const cell = table.cells[r]?.[c]\n if (!cell || covered[r][c]) continue\n for (let rr = r; rr < Math.min(r + cell.rowSpan, table.rows); rr++) {\n for (let cc = c; cc < Math.min(c + cell.colSpan, table.cols); cc++) if (rr !== r || cc !== c) covered[rr][cc] = true\n }\n }\n }\n return covered\n}\n\n/** 열 머리 행 번호들 (isColumnHeaderRow): 머리 아래 같은 열 칸이 값 */\nfunction columnHeaderRows(table: IRTable): Set<number> {\n const covered = coveredGrid(table)\n const rows = new Set<number>()\n for (let r = 0; r + 1 < table.rows; r++) {\n const heads: number[] = []\n for (let c = 0; c < table.cols; c++) {\n const cell = table.cells[r]?.[c]\n if (cell && !covered[r][c] && cell.rowSpan <= 1) heads.push(c)\n }\n const below = heads.map(c => (covered[r + 1][c] ? undefined : table.cells[r + 1]?.[c]?.text))\n if (isColumnHeaderRow(heads.map(c => table.cells[r]![c].text), below)) rows.add(r)\n }\n return rows\n}\n\n/**\n * 열 머리 지도 — 열 머리 행(isColumnHeaderRow) → 격자 열 → 그 열의 머리 칸이 있는 행. 인식(extractFormFields)과 IR 채우기(filler)가\n * 같이 쓴다(filler-hwpx 는 스캔 칸으로 같은 규칙). 2단 머리(#146): 머리 행 바로 위 행이 전부 라벨이고 그 칸이 머리 행까지 내려오는\n * 세로 병합(\"기 간\")이거나 묶음 가로 병합(\"담당업무\" → \"분야\"·\"내용\")뿐이면 세로 병합 칸도 그 열의 머리다. tops 는 그런 위 행 —\n * 옆 칸을 값으로 보지 않는다(옆 칸은 다음 머리)\n */\nexport function columnHeaderMap(table: IRTable): { heads: Map<number, Map<number, number>>; tops: Set<number> } {\n const heads = new Map<number, Map<number, number>>()\n const tops = new Set<number>()\n if (table.cols < 2) return { heads, tops }\n const covered = coveredGrid(table)\n for (const r of [...columnHeaderRows(table)].sort((a, b) => a - b)) {\n const cols = new Map<number, number>()\n for (let c = 0; c < table.cols; c++) {\n const cell = table.cells[r]?.[c]\n if (cell && !covered[r][c] && cell.rowSpan <= 1 && cell.text.trim()) cols.set(c, r)\n }\n const above: Array<[number, IRCell]> = []\n for (let c = 0; r > 0 && c < table.cols; c++) {\n const cell = table.cells[r - 1]?.[c]\n if (cell && !covered[r - 1][c]) above.push([c, cell])\n }\n if (above.length && above.some(([, a]) => a.rowSpan === 2)\n && above.every(([, a]) => isLabelCell(a.text) && (a.rowSpan === 2 || (a.rowSpan === 1 && a.colSpan > 1)))) {\n for (const [c, a] of above) if (a.rowSpan === 2) cols.set(c, r - 1)\n tops.add(r - 1)\n }\n heads.set(r, new Map([...cols].sort((x, y) => x[0] - y[0])))\n }\n return { heads, tops }\n}\n\nfunction extractFromTable(table: IRTable): FormField[] {\n const fields: FormField[] = []\n const { heads, tops } = columnHeaderMap(table)\n\n // 전략 1: 인접셀 label-value (2열 이상 테이블): 열 머리 행은 아래 칸이 값 (채우기와 같은 방향)\n if (table.cols >= 2) {\n for (let r = 0; r < table.rows; r++) {\n const head = heads.get(r)\n if (head) {\n for (const [c, hr] of head) {\n fields.push({ label: table.cells[hr]![c].text.trim().replace(/[::]\\s*$/, \"\"), value: (table.cells[r + 1]?.[c]?.text ?? \"\").trim(), row: r + 1, col: c })\n }\n continue\n }\n if (tops.has(r)) continue\n for (let c = 0; c < table.cols - 1; c++) {\n const labelCell = table.cells[r]?.[c]\n const valueCell = table.cells[r]?.[c + 1]\n if (!labelCell || !valueCell) continue\n // 행 머리 옆 하위 라벨 칸(\"신청인 | 상호 또는 법인명칭 | 빈칸\")은 값이 아니다 — 채우기도 그 칸에 쓰지 않는다 (#145)\n if (isSubLabelBeside(labelCell.rowSpan, valueCell.text, table.cells[r]?.[c + 1 + valueCell.colSpan]?.text)) continue\n if (isLabelCell(labelCell.text)) {\n fields.push({\n label: labelCell.text.trim().replace(/[::]\\s*$/, \"\"),\n value: valueCell.text.trim(),\n row: r,\n col: c,\n })\n }\n }\n }\n }\n\n // 전략 2: 헤더+데이터 행 (첫 행이 전부 라벨이면)\n if (fields.length === 0 && table.rows >= 2 && table.cols >= 2) {\n const headerRow = table.cells[0]\n const allLabels = headerRow?.every(cell => {\n const t = cell?.text.trim() ?? \"\"\n return t.length > 0 && t.length <= 20\n }) ?? false\n if (allLabels) {\n for (let r = 1; r < table.rows; r++) {\n for (let c = 0; c < table.cols; c++) {\n const label = headerRow[c]?.text.trim() ?? \"\"\n const value = table.cells[r]?.[c]?.text.trim() ?? \"\"\n if (label && value) {\n fields.push({ label, value, row: r, col: c })\n }\n }\n }\n }\n }\n\n return fields\n}\n\nfunction extractInlineFields(text: string): FormField[] {\n // \"라벨: 값\" — 한 줄 다중 라벨(\"성명: 작성일자: \")은 세그먼트 단위로 분해.\n // 값이 빈 라벨은 여기선 제외 (기존 계약 유지) — extractFormSchema가 수집한다.\n const fields: FormField[] = []\n for (const seg of scanInlineSegments(text)) {\n if (seg.value) {\n fields.push({ label: seg.label, value: seg.value, row: -1, col: -1 })\n }\n }\n return fields\n}\n\n// ─── 필드 스키마 (타입 추론) — v3.1 ─────────────────\n\n/** 양식 필드 타입 — 폼 UI 위젯 선택의 근거 (데이트피커/체크박스 등) */\nexport type FormFieldType = \"text\" | \"date\" | \"phone\" | \"email\" | \"amount\" | \"checkbox\" | \"idnum\"\n\n/** 타입이 추론된 양식 필드 */\nexport interface FormFieldSchema extends FormField {\n type: FormFieldType\n /** 라벨에 필수 표시(※·*·★·\"(필수)\")가 있을 때만 true */\n required?: boolean\n /** 값이 비어 있거나 플레이스홀더(밑줄/괄호 빈칸)뿐 — 채움 대상 */\n empty: boolean\n}\n\nexport interface FormSchemaResult {\n fields: FormFieldSchema[]\n /** 양식 확신도 (0-1, extractFormFields와 동일) */\n confidence: number\n}\n\n/** 라벨 키워드 → 타입 (값 패턴이 우선, 라벨은 폴백) */\nconst LABEL_TYPE_RULES: Array<[RegExp, FormFieldType]> = [\n [/주민등록번호|외국인등록번호/, \"idnum\"],\n [/생년월일|일시|날짜|일자|기간|연월일|년월일|신청일|작성일|발급일|접수일/, \"date\"],\n [/전화|연락처|휴대폰|핸드폰|팩스/, \"phone\"],\n [/이메일|전자우편|email/i, \"email\"],\n [/금액|단가|수량|합계|소계|예산|비용|인원|급여|연봉/, \"amount\"],\n]\n\n/** 필드 타입 추론 — 기존 값의 패턴 우선, 없으면 라벨 키워드 */\nexport function inferFieldType(label: string, value: string): FormFieldType {\n if (/[□☑✓✔]/.test(value) || /[□☑✓✔]/.test(label)) return \"checkbox\"\n\n const v = value.trim()\n if (v) {\n if (/^\\d{6}[-\\s]?[1-4]\\d{6}$/.test(v)) return \"idnum\"\n if (/^\\d{4}\\s*[-./년]\\s*\\d{1,2}\\s*[-./월]\\s*\\d{1,2}\\s*일?\\s*\\.?$/.test(v)) return \"date\"\n if (/^0\\d{1,2}[-.)\\s]?\\d{3,4}[-.\\s]?\\d{4}$/.test(v)) return \"phone\"\n if (/^[\\w.+-]+@[\\w-]+(?:\\.[\\w-]+)+$/.test(v)) return \"email\"\n // 단위 접미사 또는 천단위 콤마가 있을 때만 값 기반 amount — 맨 숫자\n // (우편번호/접수번호/연도 등)는 라벨 폴백으로 넘긴다\n if (/^[\\d,.\\s]+(?:원|명|건|개|회|부|매|%)$/.test(v) && /\\d/.test(v)) return \"amount\"\n if (/^\\d{1,3}(?:,\\d{3})+$/.test(v)) return \"amount\"\n }\n\n const norm = label.replace(/\\s/g, \"\")\n for (const [re, type] of LABEL_TYPE_RULES) {\n if (re.test(norm)) return type\n }\n return \"text\"\n}\n\n/** 라벨의 필수 표시 감지 (※·*·★·\"(필수)\") */\nfunction isRequiredLabel(label: string): boolean {\n return /[*※★]|\\(\\s*필수\\s*\\)|(\\s*필수\\s*)/.test(label)\n}\n\n/** 값이 비어 있거나 플레이스홀더(밑줄·괄호 빈칸·대시)뿐인지 */\nfunction isEmptyValue(value: string): boolean {\n const v = value.trim()\n if (!v) return true\n return /^[\\s_()()\\-—–~.·,]*$/.test(v)\n}\n\n/**\n * 양식 필드 인식 + 타입/필수/빈값 추론 — 폼 UI 자동 생성용 (v3.1).\n * extractFormFields 결과에 type(date/phone/amount/checkbox/...)·required·empty를 부여한다.\n */\nexport function extractFormSchema(blocks: IRBlock[]): FormSchemaResult {\n const { fields, confidence } = extractFormFields(blocks)\n const schemaFields: FormFieldSchema[] = fields.map(f => ({\n ...f,\n type: inferFieldType(f.label, f.value),\n required: isRequiredLabel(f.label) || undefined,\n empty: isEmptyValue(f.value),\n }))\n\n // 값이 빈 인라인 라벨(\"작성일자:\")도 채움 대상으로 노출 —\n // extractFormFields는 값 있는 필드만 수집하므로 여기서 보충한다\n const seen = new Set(schemaFields.map(f => normalizeLabel(f.label)))\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text) continue\n for (const seg of scanInlineSegments(block.text)) {\n if (seg.value) continue\n const key = normalizeLabel(seg.label)\n if (seen.has(key)) continue\n seen.add(key)\n schemaFields.push({\n label: seg.label,\n value: \"\",\n row: -1,\n col: -1,\n type: inferFieldType(seg.label, \"\"),\n required: isRequiredLabel(seg.label) || undefined,\n empty: true,\n })\n }\n }\n\n return { confidence, fields: schemaFields }\n}\n","/**\n * HWPX 라운드트립 소스맵 — section XML 문자열에서 문단/표 셀의 문자 범위를 추적.\n *\n * DOM 재직렬화를 거치지 않고(속성 순서·공백·자기닫힘 표기까지 보존하기 위해)\n * 정규식 토크나이저로 태그를 순회하며, 각 <hp:p>가 소유한 <hp:t> 콘텐츠 범위를\n * 기록한다. 패치는 이 범위에 대한 문자열 splice로만 수행되어\n * 변경 문단 외 XML 바이트가 그대로 보존된다 (filler-hwpx.ts 패턴의 오프셋 버전).\n */\n\nimport { isDatePlaceholder } from \"../form/recognize.js\"\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 /** <hp:tc name=\"…\"> — kordoc 생성기의 왕복 마커(`__kordoc_h1` 등) 판독용 */\n name?: string\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 * kordoc 생성기의 장식표(제목 틀·장 띠·요약 상자·목차) — 파서가 표 대신 heading·인용문으로 내거나 건너뛰므로\n * 패치의 표 서수에서 빠지고, heading·quote 면 표시 칸(channelCell)의 문단이 문단 매핑 대상이 된다.\n * 최상위 표만 판독 (파서 section-walker kordocTableChannel 과 같은 규칙)\n */\n channel?: \"heading\" | \"quote\" | \"skip\"\n channelCell?: 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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\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/nbSpace/\n * br/lineBreak)만 공백, 나머지 태그(markpen 등 파서가 무시)는 제거 (파서 모델 정합) */\nfunction tContentToText(raw: string): string {\n return decodeXmlEntities(\n raw\n .replace(/<\\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|nbSpace|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 if (table.topLevel) markKordocChannel(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 === \"nbSpace\" || 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 const name = getAttr(attrsRaw, \"name\")\n cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [], ...(name ? { name } : {}) })\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 === \"nbSpace\" || 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 // 요소 이름은 XML 이름 꼴만 — 문서에서 온 이름을 정규식에 그대로 넣으면 `<(a|a)+b` 같은 이름이 지수 역추적·문법 오류를 낸다\n const head = /<([\\w.-]+(?::[\\w.-]+)?)(?=[\\s/>])/y\n head.lastIndex = start\n const open = head.exec(xml)\n if (!open) return -1\n const qname = open[1].replace(/\\./g, \"\\\\.\")\n // 속성 구간은 따옴표 값·그 밖 글자·\"/>\" 아닌 '/' 로 겹침 없이 — 역추적이 선형\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/** 표의 첫 `__kordoc_` 이름 칸으로 왕복 채널 판독 — 파서가 그 표를 heading·인용문으로 내거나 건너뛴다 */\nfunction markKordocChannel(table: ScanTable): void {\n let cell: ScanCell | undefined\n for (const row of table.rows) {\n cell = row.find(c => c.name?.startsWith(\"__kordoc_\"))\n if (cell) break\n }\n const m = cell?.name?.match(/^__kordoc_(?:(h[1-6])|(toc|skip)|(summary))$/)\n if (!cell || !m) return\n table.channel = m[1] ? \"heading\" : m[3] ? \"quote\" : \"skip\"\n if (table.channel !== \"skip\") table.channelCell = cell\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 // 날짜 자리표시(\" 년 월 - 년 월\")의 공백은 값이 들어갈 빈칸이지 들여쓰기가 아니다 — 복원하지 않는다(patch 와 같다, #151)\n if (orig && orig.trim() !== \"\" && !isDatePlaceholder(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 * 선두 부호 run 보존 — kordoc 공문서 항목은 부호를 따로 된 run(부호 + 탭)으로 낸다(내어쓰기용 자동 탭 정렬).\n * 편집이 부호 뒤 내용만 바꿨으면 부호 run 은 그대로 두고 나머지 hp:t 만 다시 쓴다. 문단 전체 재작성은 부호와 내용을\n * 첫 hp:t 하나에 합치며 탭을 공백으로 바꿔 정렬이 깨진다. 부호가 바뀌었거나 그 꼴이 아니면 null(호출자가 전체 재작성).\n */\nexport function markerRunSplices(para: ScanParagraph, xml: string, newText: string): SpliceEdit[] | null {\n const first = para.tRanges[0]\n if (!first || first.selfClosing || para.tRanges.length < 2) return null\n const m = /^([^<&\\s]{1,6})<(?:\\w+:)?tab\\b[^>]*\\/>$/.exec(xml.slice(first.contentStart, first.contentEnd))\n if (!m || !newText.startsWith(m[1] + \" \")) return null\n return buildParagraphSplices({ ...para, tRanges: para.tRanges.slice(1) }, newText.slice(m[1].length + 1), xml)\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/**\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\n/**\n * 글이 바뀐 문단의 <hp:linesegarray> 만 지우는 splice 목록: 편집 위치를 품은 가장 안쪽 문단(본문 문단·칸 문단)의 캐시만\n * 지운다. 표를 담은 호스트 문단은 글이 그대로라 캐시를 둔다(행 추가·삭제처럼 칸 밖 편집이면 그 자리의 가장 안쪽 문단이\n * 호스트라 지워진다). 편집 범위 안에 통째로 든 캐시(행 삭제·문단 교체)는 편집이 지우므로 뺀다.\n *\n * 섹션 전체를 지우면(allLinesegRemovalSplices) 자체 조판이 없는 뷰어(rhwp 미리보기·PDF)가 손대지 않은 쪽까지 다시 짜서\n * 글자가 벌어지고 줄이 꺾이고 아래가 잘렸다(광진구 실문서 채우기·패치 27건 전부, 원본에서 캐시만 지운 대조본과 픽셀 차이 0).\n * rhwp 는 최상위 문단 하나라도 캐시가 없으면 그 구역의 세로 배치를 다시 짜고(줄 나눔은 캐시대로), 칸 문단의 캐시가 없는\n * 것은 그 칸만 다시 짠다: 그래서 호스트 문단 캐시를 지우지 않는 것이 채우기 결과를 지킨다(정부포상 동의서 실측: 최상위\n * 문단 캐시 1개 삭제 → 두 쪽 픽셀 차이 11.4%·11.2%, 칸 문단 캐시 1개 삭제 → 0%).\n * 한컴은 캐시가 일부 문단에만 없어도 경고 없이 연다: 한글 2024 실기기 대조(4.x Verified: 전체·일부·미삭제 세 변형 모두\n * 경고 없음)와 한컴 저장본 자체의 혼합 캐시(verify-reflow 36264961) 근거\n */\nexport function changedLinesegRemovalSplices(xml: string, edits: SpliceEdit[]): SpliceEdit[] {\n if (edits.length === 0) return []\n const points = edits.map(e => e.start).sort((a, b) => a - b)\n const tagRe = /<(\\/?)((?:[A-Za-z0-9_]+:)?(?:p|linesegarray))(?=[\\s/>])(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?(\\/?)>/g\n const stack: Array<{ dirty: boolean; seg?: { start: number; end: number } }> = []\n const out: SpliceEdit[] = []\n let pi = 0\n let m: RegExpExecArray | null\n while ((m = tagRe.exec(xml)) !== null) {\n // 이 태그 앞(같은 자리 포함)의 편집 지점은 지금 열린 가장 안쪽 문단을 더럽힌다: 문단 여는 태그 자리의 편집(문단 교체)은 바깥 문단\n while (pi < points.length && points[pi] <= m.index) {\n if (stack.length) stack[stack.length - 1].dirty = true\n pi++\n }\n const [full, close, qname, selfClose] = m\n const isSeg = qname.endsWith(\"linesegarray\")\n if (isSeg) {\n if (close) continue\n let end = m.index + full.length\n if (!selfClose) {\n const ci = xml.indexOf(`</${qname}>`, end)\n if (ci < 0) continue\n end = ci + qname.length + 3\n tagRe.lastIndex = end\n }\n const top = stack[stack.length - 1]\n if (top && !top.seg) top.seg = { start: m.index, end }\n continue\n }\n if (close) {\n const p = stack.pop()\n if (p?.dirty && p.seg) out.push({ start: p.seg.start, end: p.seg.end, replacement: \"\" })\n } else if (!selfClose) {\n stack.push({ dirty: false })\n }\n }\n const claimed = edits.filter(e => e.end > e.start)\n return out.filter(ls => !claimed.some(c => ls.start >= c.start && ls.end <= c.end))\n}\n\n/**\n * splice 일괄 적용 — 시작 위치로 정렬(같으면 넣은 순서), 겹치면 내부 오류, 원문 조각과 치환 글을 한 번에 잇는다.\n * 종전엔 뒤에서부터 slice+치환+slice 를 되풀이해 splice 마다 문자열 전체를 복사했다(O(글자 수 × splice 수)) —\n * 줄 배치 캐시를 전부 지우는 큰 섹션(1,020만 자·26,939 splice)에서 patch·fill·seal 이 32.6초 (v4.14.4 리뷰 실측).\n * 결과는 종전과 같다 (redact-hwpx.ts applySplicesLinear 와 같은 방식)\n */\nexport function applySplices(xml: string, splices: SpliceEdit[]): string {\n const sorted = [...splices].sort((a, b) => a.start - b.start)\n const parts: string[] = []\n let cursor = 0\n for (const s of sorted) {\n if (s.start < cursor) throw new Error(\"소스맵 splice 범위 겹침 — 내부 오류\")\n parts.push(xml.slice(cursor, s.start), s.replacement)\n cursor = s.end\n }\n parts.push(xml.slice(cursor))\n return parts.join(\"\")\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"]}
|