kordoc 4.0.6 → 4.0.8
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 +823 -806
- package/dist/{-NDU7YGSS.js → -LE4RLXVA.js} +8 -8
- package/dist/{chunk-5SOZXPQY.js → chunk-2DZQF6YJ.js} +3 -3
- package/dist/chunk-2DZQF6YJ.js.map +1 -0
- package/dist/{chunk-6GBLFQMA.js → chunk-5RPYBC2Q.js} +1 -1
- package/dist/chunk-5RPYBC2Q.js.map +1 -0
- package/dist/{chunk-DQJTNXVS.cjs → chunk-6XAAYAWL.cjs} +142 -3
- package/dist/chunk-6XAAYAWL.cjs.map +1 -0
- package/dist/{chunk-HC2SEH6I.js → chunk-7S3M4N4E.js} +939 -946
- package/dist/chunk-7S3M4N4E.js.map +1 -0
- package/dist/{chunk-4C7SBZNW.js → chunk-BNU5QGIZ.js} +2 -2
- package/dist/chunk-BNU5QGIZ.js.map +1 -0
- package/dist/{chunk-63ASYC4N.js → chunk-D35VBACN.js} +3 -3
- package/dist/chunk-D35VBACN.js.map +1 -0
- package/dist/chunk-DCZVOIEO.cjs.map +1 -1
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/{chunk-L3I4TIRP.js → chunk-HMUEU7D7.js} +2 -2
- package/dist/chunk-HMUEU7D7.js.map +1 -0
- package/dist/{chunk-5CJGKKMZ.js → chunk-MEPHGCPQ.js} +1 -1
- package/dist/chunk-MEPHGCPQ.js.map +1 -0
- package/dist/chunk-MOL7MDBG.js +0 -0
- package/dist/{chunk-HOGJ5ALY.js → chunk-OQPILS7B.js} +142 -3
- package/dist/chunk-OQPILS7B.js.map +1 -0
- package/dist/{chunk-KA4FEBS2.js → chunk-RROH5WHO.js} +2 -2
- package/dist/chunk-RROH5WHO.js.map +1 -0
- package/dist/{chunk-UVSOSFER.js → chunk-UEIYETCQ.js} +142 -3
- package/dist/chunk-UEIYETCQ.js.map +1 -0
- package/dist/cli.js +15 -15
- package/dist/cli.js.map +1 -1
- package/dist/{detect-PJZMUL2Z.js → detect-RI2MQ33K.js} +2 -2
- package/dist/formula-5NKVS2LR.cjs.map +1 -1
- package/dist/formula-JCNF43NE.js +0 -0
- package/dist/index.cjs +1248 -1255
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +925 -932
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +13 -13
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-737B4EZW.js +0 -0
- package/dist/page-range-P7SDW6LR.cjs.map +1 -1
- package/dist/{parser-VEY4B5CJ.cjs → parser-ITMU2WN5.cjs} +231 -37
- package/dist/parser-ITMU2WN5.cjs.map +1 -0
- package/dist/{parser-DMKHIV4E.js → parser-KMMUS73Q.js} +200 -6
- package/dist/parser-KMMUS73Q.js.map +1 -0
- package/dist/{parser-C636QIOB.js → parser-TCTCSBYZ.js} +202 -8
- package/dist/parser-TCTCSBYZ.js.map +1 -0
- package/dist/{profile-io-JYGCC6XQ.js → profile-io-SLN4P76T.js} +3 -3
- package/dist/{provider-H4WWSPOV.js → provider-4ZJKV3DC.js} +1 -1
- package/dist/provider-4ZJKV3DC.js.map +1 -0
- package/dist/{provider-7H4CPZYS.js → provider-AKROB7WQ.js} +1 -1
- package/dist/provider-AKROB7WQ.js.map +1 -0
- package/dist/{provider-5K7O3Z2O.cjs → provider-G4C2V2PD.cjs} +1 -1
- package/dist/provider-G4C2V2PD.cjs.map +1 -0
- package/dist/render-25BT623I.js +10 -0
- package/dist/seal-R4TETA4C.js +10 -0
- package/dist/setup-57FB3LSP.js +0 -0
- package/dist/{watch-ZQUFJBFR.js → watch-OHQWSVPE.js} +8 -8
- package/dist/watch-OHQWSVPE.js.map +1 -0
- package/package.json +97 -97
- package/dist/chunk-4C7SBZNW.js.map +0 -1
- package/dist/chunk-5CJGKKMZ.js.map +0 -1
- package/dist/chunk-5SOZXPQY.js.map +0 -1
- package/dist/chunk-63ASYC4N.js.map +0 -1
- package/dist/chunk-6GBLFQMA.js.map +0 -1
- package/dist/chunk-DQJTNXVS.cjs.map +0 -1
- package/dist/chunk-HC2SEH6I.js.map +0 -1
- package/dist/chunk-HOGJ5ALY.js.map +0 -1
- package/dist/chunk-KA4FEBS2.js.map +0 -1
- package/dist/chunk-L3I4TIRP.js.map +0 -1
- package/dist/chunk-UVSOSFER.js.map +0 -1
- package/dist/parser-C636QIOB.js.map +0 -1
- package/dist/parser-DMKHIV4E.js.map +0 -1
- package/dist/parser-VEY4B5CJ.cjs.map +0 -1
- package/dist/provider-5K7O3Z2O.cjs.map +0 -1
- package/dist/provider-7H4CPZYS.js.map +0 -1
- package/dist/provider-H4WWSPOV.js.map +0 -1
- package/dist/render-ISOEOMTM.js +0 -10
- package/dist/seal-676ZIAZZ.js +0 -10
- package/dist/watch-ZQUFJBFR.js.map +0 -1
- /package/dist/{-NDU7YGSS.js.map → -LE4RLXVA.js.map} +0 -0
- /package/dist/{detect-PJZMUL2Z.js.map → detect-RI2MQ33K.js.map} +0 -0
- /package/dist/{profile-io-JYGCC6XQ.js.map → profile-io-SLN4P76T.js.map} +0 -0
- /package/dist/{render-ISOEOMTM.js.map → render-25BT623I.js.map} +0 -0
- /package/dist/{seal-676ZIAZZ.js.map → seal-R4TETA4C.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render/svg-render.ts","../src/render/layout.ts","../src/hwpx/text-metrics.ts","../src/render/reflow.ts"],"sourcesContent":["/**\r\n * 레이아웃 보존 렌더 — HWPX 조판 캐시(lineseg·cellAddr·hp:pos)를 SVG 절대배치로 그린다.\r\n *\r\n * 조판 엔진 없음: 한컴이 저장 시 기록한 좌표를 그대로 사용한다. 따라서\r\n * 한컴(또는 조판 캐시를 기록하는 편집기)에서 저장한 파일만 렌더 가능 —\r\n * markdownToHwpx 산출물엔 linesegarray가 없어 KordocError를 던진다.\r\n *\r\n * 좌표 산식(실측 검증 — .claude/plans/render-poc/findings.md):\r\n * - 단위 HWPUNIT(1/7200in), pt = /100. 최상위 문단 lineseg = 본문영역 로컬,\r\n * 셀 문단 = 셀 로컬. PARA 밀어내기 개체 anchor = 호스트vp − (omTop+h+omBottom) 역산.\r\n * - horzsize는 줄 \"영역\" 폭(텍스트 폭 아님) — 마지막 줄이 아닌 줄은 원본 줄바꿈에\r\n * 맞춰 textLength로 고정하고, 마지막 줄만 paraPr 정렬(LEFT 자연폭/CENTER/RIGHT/배분)을 적용.\r\n * - 좌표 속성엔 uint32로 저장된 음수가 섞여 있다(toInt32 필수).\r\n * - 표 열은 span 제약 경계 전파로, 행은 rs=1 max + 콘텐츠 초과 성장으로 푼다.\r\n *\r\n * 페이지: 최상위 lineseg vertpos는 페이지 로컬(페이지마다 0부터 리셋)이므로 역행\r\n * 지점을 페이지 경계로 감지, 전 페이지를 세로 스택으로 그린다(페이지별 흰 배경 +\r\n * 클립). 페이지에 걸친 표는 시작 페이지에서 잘린다(조판 캐시에 분할점이 없음).\r\n *\r\n * 범위: section0 한정(다중 구역은 첫 구역만), 수식·그리기개체 도형은 미지원(경고 수집).\r\n */\r\n\r\nimport JSZip from \"jszip\"\r\nimport { KordocError, precheckZipSize } from \"../utils.js\"\r\nimport { createXmlParser, findChildByLocalName, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES } from \"../hwpx/parser-shared.js\"\r\nimport { toInt32, solveBoundaries, solveRowHeights, type SpanConstraint } from \"./layout.js\"\r\nimport { measureTextWidth, type WrapMode } from \"../hwpx/text-metrics.js\"\r\nimport { parseRenderStyles, DEFAULT_CHAR, type RenderStyles, type RenderBorderEdge } from \"./head-styles.js\"\r\nimport { reflowSection } from \"./reflow.js\"\r\n\r\nexport interface RenderSvgOptions {\r\n /** 이미지 1장당 허용 최대 바이트 (기본 40MB) */\r\n maxImageBytes?: number\r\n /** 검색어 형광펜 — 텍스트 조각 내 매치 구간에 배경 rect (대소문자 무시).\r\n * charPr(스타일) 경계에 걸친 매치는 칠하지 못한다. */\r\n highlights?: string[]\r\n /** Tier-2 reflow — 조판 캐시(linesegarray) 없는 파일도 순수 TS 조판으로 렌더.\r\n * 캐시가 있으면 무시(한컴본은 캐시 재생). 기본 false(캐시 없으면 KordocError). */\r\n reflow?: boolean\r\n /** reflow 줄바꿈 폴백 모드 — 'keep'(어절) / 'charAll'(글자). 문단 paraPr에\r\n * breakSetting 선언이 있으면 그 값이 우선하며(BREAK_WORD=어절, KEEP_WORD=글자 —\r\n * 이름 역전 주의), 이 옵션은 선언 없는 문단에만 적용된다. */\r\n reflowMode?: WrapMode\r\n}\r\n\r\nexport interface RenderSvgResult {\r\n svg: string\r\n /** 페이지 폭 (pt) */\r\n width: number\r\n /** 전체 캔버스 높이 (pt) — 페이지 세로 스택 + 간격 */\r\n height: number\r\n /** 렌더된 페이지 수 */\r\n pageCount: number\r\n warnings: string[]\r\n stats: { texts: number; images: number; tables: number }\r\n}\r\n\r\n// ─── XML 헬퍼 ─────────────────────────────────────\r\n\r\nfunction ln(el: Element): string {\r\n return (el.tagName || \"\").replace(/^[^:]+:/, \"\")\r\n}\r\n\r\nfunction elements(el: Element): Element[] {\r\n const out: Element[] = []\r\n const children = el.childNodes\r\n if (!children) return out\r\n for (let i = 0; i < children.length; i++) {\r\n if (children[i].nodeType === 1) out.push(children[i] as Element)\r\n }\r\n return out\r\n}\r\n\r\nfunction num(el: Element | null, attr: string, fallback = 0): number {\r\n return el ? toInt32(el.getAttribute(attr) ?? undefined, fallback) : fallback\r\n}\r\n\r\nfunction findFirst(el: Element, name: string, depth = 0): Element | null {\r\n if (depth > 64) return null\r\n for (const ch of elements(el)) {\r\n if (ln(ch) === name) return ch\r\n const found = findFirst(ch, name, depth + 1)\r\n if (found) return found\r\n }\r\n return null\r\n}\r\n\r\nfunction escapeXml(s: string): string {\r\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\")\r\n}\r\n\r\n// ─── 내부 모델 ────────────────────────────────────\r\n\r\ninterface PageGeom { PW: number; PH: number; ML: number; MT: number; BODY_W: number; BODY_H: number }\r\n\r\nexport interface Seg { textpos: number; vertpos: number; horzpos: number; horzsize: number; textheight: number; baseline: number }\r\n\r\nexport interface ParaChar { ch: string; prId: string | null }\r\n\r\n/** 렌더 대상 개체 태그 — 이 외(도형류)는 경고 후 생략 */\r\nconst OBJ_TAGS = new Set([\"tbl\", \"pic\", \"container\", \"equation\", \"rect\", \"ellipse\", \"polygon\", \"curv\", \"line\", \"arc\", \"ole\", \"textart\"])\r\n\r\ninterface ParaObj { el: Element; tag: string; index: number; inline: boolean; width: number; height: number }\r\n\r\nexport interface ParaModel { chars: ParaChar[]; segs: Seg[]; objs: ParaObj[]; paraPrId: string | null }\r\n\r\ninterface Ctx {\r\n /** 페이지별 SVG 버퍼 — emit()이 현재 페이지(page)로 라우팅 */\r\n pages: string[][]\r\n /** 현재 그리는 페이지 인덱스 — 최상위 문단만 갱신, 셀/중첩 콘텐츠는 호스트 페이지 상속 */\r\n page: number\r\n geom: PageGeom\r\n styles: RenderStyles\r\n images: Map<string, { dataUri: string; symId?: string; orgW?: number; orgH?: number }>\r\n /** 이미지 심볼 defs — dataURI는 여기 1회만, 본문은 <use> 참조 (occurrence당 재emit 금지) */\r\n defs: string[]\r\n /** 검색어 형광펜 (소문자 정규화, 빈 문자열 제거) */\r\n highlights: string[]\r\n warnings: string[]\r\n warned: Set<string>\r\n stats: { texts: number; images: number; tables: number }\r\n}\r\n\r\nconst pt = (u: number): string => String(Math.round(u) / 100)\r\n\r\nfunction emit(ctx: Ctx, s: string): void {\r\n ctx.pages[ctx.page].push(s)\r\n}\r\n\r\nfunction warnOnce(ctx: Ctx, key: string, msg: string): void {\r\n if (ctx.warned.has(key)) return\r\n ctx.warned.add(key)\r\n ctx.warnings.push(msg)\r\n}\r\n\r\n// ─── 문단 모델 구축 ────────────────────────────────\r\n\r\n/** hp:t 안에서 1슬롯을 차지하는 문자형 컨트롤 (HWP5 문자 스트림 모델) —\r\n * 탭(0x09)은 char가 아니라 inline 컨트롤 = 8슬롯(16바이트)이라 여기 넣으면 안 된다\r\n * (record.ts 0x09 처리의 i+=14와 같은 모델. 1슬롯로 세면 탭당 7슬롯씩 줄 경계가 밀린다) */\r\nconst CHAR_CTRL_1SLOT = new Set([\"lineBreak\", \"hyphen\", \"nbSpace\", \"fwSpace\"])\r\n\r\n/** lineseg textpos 정합용 0폭 필러 슬롯 — 컨트롤이 차지하는 문자 위치를 채운다 */\r\nfunction pushFillers(chars: ParaChar[], n: number, prId: string | null): void {\r\n for (let i = 0; i < n; i++) chars.push({ ch: \"\", prId })\r\n}\r\n\r\n/**\r\n * hp:t 내용을 슬롯 스트림으로 변환 — lineseg textpos 는 HWP5 문자 스트림 기준이라\r\n * 텍스트 1문자=1슬롯(서로게이트 쌍은 2), tab 등 문자형 컨트롤도 1슬롯을 차지한다.\r\n * markpen 등 래퍼 요소는 슬롯 없이 내용만 재귀한다.\r\n * (탭 폭은 탭스톱 미해석으로 0 — 경계 정합이 우선, 폭 오차는 줄 스케일이 흡수)\r\n */\r\nfunction pushTextSlots(t: Element, chars: ParaChar[], prId: string | null, depth: number): void {\r\n if (depth > 32) return\r\n const kids = t.childNodes\r\n if (!kids) return\r\n for (let i = 0; i < kids.length; i++) {\r\n const c = kids[i]\r\n if (c.nodeType === 3) {\r\n for (const cp of c.textContent ?? \"\") {\r\n chars.push({ ch: cp, prId })\r\n if (cp.length === 2) chars.push({ ch: \"\", prId }) // UTF-16 두 번째 유닛 슬롯\r\n }\r\n } else if (c.nodeType === 1) {\r\n const el = c as Element\r\n const tag = ln(el)\r\n if (tag === \"tab\") {\r\n pushFillers(chars, 8, prId) // inline 컨트롤 8슬롯 (전부 폭 0 — 탭스톱 미해석)\r\n } else if (CHAR_CTRL_1SLOT.has(tag)) {\r\n chars.push({ ch: tag === \"nbSpace\" || tag === \"fwSpace\" ? \" \" : \"\", prId })\r\n } else {\r\n pushTextSlots(el, chars, prId, depth + 1)\r\n }\r\n }\r\n }\r\n}\r\n\r\nexport function buildPara(p: Element): ParaModel {\r\n const chars: ParaChar[] = []\r\n const objs: ParaObj[] = []\r\n let segs: Seg[] = []\r\n for (const runEl of elements(p)) {\r\n const tag = ln(runEl)\r\n if (tag === \"run\") {\r\n const prId = runEl.getAttribute(\"charPrIDRef\")\r\n for (const ch of elements(runEl)) {\r\n const cn = ln(ch)\r\n if (cn === \"t\") {\r\n pushTextSlots(ch, chars, prId, 0)\r\n } else if (OBJ_TAGS.has(cn)) {\r\n const sz = findChildByLocalName(ch, \"sz\")\r\n const pos = findChildByLocalName(ch, \"pos\")\r\n // 그리기 도형은 hp:sz가 없다 — curSz(>0) → orgSz 폴백\r\n const w = num(sz, \"width\") || num(findChildByLocalName(ch, \"curSz\"), \"width\") || num(findChildByLocalName(ch, \"orgSz\"), \"width\")\r\n const h = num(sz, \"height\") || num(findChildByLocalName(ch, \"curSz\"), \"height\") || num(findChildByLocalName(ch, \"orgSz\"), \"height\")\r\n objs.push({\r\n el: ch, tag: cn, index: chars.length,\r\n inline: pos?.getAttribute(\"treatAsChar\") === \"1\",\r\n width: w, height: h,\r\n })\r\n // 확장 컨트롤(GSO 등)은 문자 스트림에서 8슬롯 — 실측: 데모 코퍼스 1,132개\r\n // 멀티라인 문단에서 textpos 가 8슬롯 블록 중간에 걸린 경계 0건\r\n pushFillers(chars, 8, prId)\r\n } else {\r\n // secPr·ctrl(구역/단 정의)·필드 등 나머지 run 자식도 확장/인라인 컨트롤 8슬롯\r\n pushFillers(chars, 8, prId)\r\n }\r\n }\r\n } else if (tag === \"linesegarray\") {\r\n segs = elements(runEl).filter(s => ln(s) === \"lineseg\").map(s => ({\r\n textpos: num(s, \"textpos\"), vertpos: num(s, \"vertpos\"), horzpos: num(s, \"horzpos\"),\r\n horzsize: num(s, \"horzsize\"), textheight: num(s, \"textheight\", 1000), baseline: num(s, \"baseline\", 850),\r\n }))\r\n }\r\n }\r\n return { chars, segs, objs, paraPrId: p.getAttribute(\"paraPrIDRef\") }\r\n}\r\n\r\n// ─── 줄 렌더 (run별 charPr + 정렬) ──────────────────\r\n\r\ninterface LinePlan {\r\n seg: Seg\r\n /** 줄 시작 x 보정 (정렬) */\r\n xoff: number\r\n /** 텍스트 조각 폭 스케일 */\r\n scale: number\r\n start: number\r\n end: number\r\n}\r\n\r\nfunction charW(c: ParaChar, styles: RenderStyles): number {\r\n const st = (c.prId != null ? styles.charPr.get(c.prId) : undefined) ?? DEFAULT_CHAR\r\n return measureTextWidth(c.ch, st.height, st.ratio, { spacingPct: st.spacing })\r\n}\r\n\r\n/** 줄 자연폭 = 텍스트 조각 + 인라인 개체 폭 (개체 폭은 스케일 불변) */\r\nfunction lineNaturalWidth(m: ParaModel, styles: RenderStyles, start: number, end: number): { text: number; obj: number } {\r\n let text = 0\r\n for (let i = start; i < end && i < m.chars.length; i++) text += charW(m.chars[i], styles)\r\n let obj = 0\r\n for (const o of m.objs) if (o.inline && o.index >= start && o.index < end) obj += o.width\r\n return { text, obj }\r\n}\r\n\r\nfunction planLines(m: ParaModel, styles: RenderStyles): LinePlan[] {\r\n const align = (m.paraPrId != null ? styles.paraAlign.get(m.paraPrId) : undefined) ?? \"JUSTIFY\"\r\n const plans: LinePlan[] = []\r\n for (let i = 0; i < m.segs.length; i++) {\r\n const seg = m.segs[i]\r\n const start = seg.textpos\r\n const end = i + 1 < m.segs.length ? m.segs[i + 1].textpos : Math.max(m.chars.length, start)\r\n const nat = lineNaturalWidth(m, styles, start, end)\r\n const isLast = i === m.segs.length - 1\r\n let xoff = 0\r\n let scale = 1\r\n const avail = seg.horzsize - nat.obj\r\n if (nat.text > 0 && (!isLast || align === \"DISTRIBUTE\" || align === \"DISTRIBUTE_SPACE\")) {\r\n // 줄바꿈이 고정된 중간 줄·배분정렬 — 줄 영역 폭에 맞춰 고정 (원본 조판 재현)\r\n scale = avail > 0 ? avail / nat.text : 1\r\n } else if (nat.text + nat.obj > 0 && isLast) {\r\n const w = nat.text + nat.obj\r\n if (align === \"CENTER\") xoff = Math.max(0, (seg.horzsize - w) / 2)\r\n else if (align === \"RIGHT\") xoff = Math.max(0, seg.horzsize - w)\r\n }\r\n if (!Number.isFinite(scale) || scale <= 0) scale = 1\r\n scale = Math.min(4, Math.max(0.25, scale))\r\n plans.push({ seg, xoff, scale, start, end })\r\n }\r\n return plans\r\n}\r\n\r\n/** 줄 안 [start, upto) 구간의 전진폭 (텍스트×스케일 + 인라인 개체) */\r\nfunction advanceTo(m: ParaModel, styles: RenderStyles, plan: LinePlan, upto: number): number {\r\n let x = 0\r\n for (let i = plan.start; i < upto && i < m.chars.length; i++) x += charW(m.chars[i], styles) * plan.scale\r\n for (const o of m.objs) if (o.inline && o.index >= plan.start && o.index < upto) x += o.width\r\n return x\r\n}\r\n\r\n// ─── 문단 드로잉 ───────────────────────────────────\r\n\r\n/**\r\n * @param segPages 최상위 문단 전용 — seg 인덱스별 페이지 배정 (vertpos 리셋 감지 결과).\r\n * 셀/중첩 콘텐츠는 전달하지 않아 호스트의 ctx.page 를 상속한다.\r\n */\r\nfunction drawPara(p: Element, ox: number, oy: number, areaW: number, ctx: Ctx, depth: number, segPages?: number[]): void {\r\n if (depth > 16) { warnOnce(ctx, \"depth\", \"중첩 깊이 16 초과 — 이하 생략\"); return }\r\n const m = buildPara(p)\r\n if (m.segs.length === 0) {\r\n // 조판 캐시 없는 문단 — 개체만이라도 문단 원점에 배치. 텍스트는 무음 생략 금지\r\n if (m.chars.some(c => c.ch !== \"\")) {\r\n warnOnce(ctx, \"no-lineseg\", \"조판 캐시 없는 문단 텍스트 생략 — reflow 옵션으로 합성 가능\")\r\n }\r\n for (const o of m.objs) drawObject(o, ox, oy, 0, areaW, ctx, depth)\r\n return\r\n }\r\n const plans = planLines(m, ctx.styles)\r\n const baseV = m.segs[0].vertpos\r\n\r\n for (let li = 0; li < plans.length; li++) {\r\n const plan = plans[li]\r\n if (segPages && segPages[li] !== undefined) ctx.page = segPages[li]\r\n const { seg } = plan\r\n // charPr 단위 조각으로 분할\r\n let i = plan.start\r\n let cursor = ox + seg.horzpos + plan.xoff\r\n const y = oy + seg.vertpos + seg.baseline\r\n while (i < plan.end && i < m.chars.length) {\r\n // 필러 슬롯(컨트롤·서로게이트 자리)은 그리지 않고 건너뛴다 —\r\n // 인라인 개체의 폭 전진은 개체 첫 슬롯에서 1회 수행\r\n if (m.chars[i].ch === \"\") {\r\n for (const o of m.objs) if (o.inline && o.index === i) cursor += o.width\r\n i++\r\n continue\r\n }\r\n const prId = m.chars[i].prId\r\n let j = i\r\n let piece = \"\"\r\n // 필러에서 멈추므로 piece 안은 실문자뿐 — 개체 경계 절단이 자연 발생\r\n while (j < plan.end && j < m.chars.length && m.chars[j].prId === prId && m.chars[j].ch !== \"\") { piece += m.chars[j].ch; j++ }\r\n // 연속 공백(2+) 경계 절단 — 공백 폭 오차(한컴 0.5em 고정 vs 뷰어 폰트)를\r\n // 공백 구간에 가둔다 (공무원 스페이스 정렬 원문에서 글자 벌어짐 방지)\r\n // (문자열 인덱스를 슬롯 오프셋으로 쓴다 — 서로게이트 쌍이 섞이면 1슬롯 오차, 무시)\r\n {\r\n const cut = piece.search(/ {2,}/)\r\n if (cut > 0) { piece = piece.slice(0, cut); j = i + cut }\r\n else if (cut === 0) {\r\n const runEnd = piece.match(/^ +/)![0].length\r\n piece = piece.slice(0, runEnd); j = i + runEnd\r\n }\r\n }\r\n const st = (prId != null ? ctx.styles.charPr.get(prId) : undefined) ?? DEFAULT_CHAR\r\n\r\n // 텍스트 세그먼트 1개를 렌더하고 전진폭 반환. hit=true 면 형광펜 배경 rect 를\r\n // 텍스트 앞에 깐다. 세그먼트마다 자체 textLength 를 쓰므로 rect(hit)와 글자가\r\n // 완전히 같은 폭·위치로 계산돼 형광펜이 어긋나지 않는다.\r\n const renderSeg = (text: string, cx: number, hit: boolean): number => {\r\n const sw = measureTextWidth(text, st.height, st.ratio, { spacingPct: st.spacing }) * plan.scale\r\n if (hit) {\r\n emit(ctx, `<rect x=\"${pt(cx)}\" y=\"${pt(oy + seg.vertpos)}\" width=\"${pt(sw)}\" height=\"${pt(seg.textheight)}\" fill=\"#ffd54f\" fill-opacity=\"0.45\"/>`)\r\n }\r\n if (text.trim().length > 0) {\r\n const attrs: string[] = [`x=\"${pt(cx)}\"`, `y=\"${pt(y)}\"`, `font-size=\"${pt(st.height)}\"`]\r\n if (st.fontFamily) attrs.push(`font-family=\"${st.fontFamily}\"`)\r\n if ([...text].length > 1 && sw > 50) {\r\n attrs.push(`textLength=\"${pt(sw)}\"`, `lengthAdjust=\"${plan.scale < 1 ? \"spacingAndGlyphs\" : \"spacing\"}\"`)\r\n }\r\n if (st.bold) attrs.push(`font-weight=\"bold\"`)\r\n if (st.italic) attrs.push(`font-style=\"italic\"`)\r\n if (st.underline) attrs.push(`text-decoration=\"underline\"`)\r\n if (st.color) attrs.push(`fill=\"${escapeXml(st.color)}\"`)\r\n emit(ctx, `<text ${attrs.join(\" \")}>${escapeXml(text)}</text>`)\r\n ctx.stats.texts++\r\n }\r\n return sw\r\n }\r\n\r\n // 형광펜 매치 구간 수집 → 병합(겹침 제거) → [평문·매치·평문…] 분할 렌더\r\n const merged: Array<[number, number]> = []\r\n if (ctx.highlights.length > 0 && piece.trim().length > 0) {\r\n const found: Array<[number, number]> = []\r\n const lower = piece.toLowerCase()\r\n for (const term of ctx.highlights) {\r\n for (let f = lower.indexOf(term); f !== -1; f = lower.indexOf(term, f + term.length)) {\r\n found.push([f, f + term.length])\r\n }\r\n }\r\n found.sort((a, b) => a[0] - b[0])\r\n for (const [s, e] of found) {\r\n const tail = merged[merged.length - 1]\r\n if (tail && s <= tail[1]) tail[1] = Math.max(tail[1], e)\r\n else merged.push([s, e])\r\n }\r\n }\r\n\r\n if (merged.length === 0) {\r\n cursor += renderSeg(piece, cursor, false)\r\n } else {\r\n let segCur = cursor\r\n let last = 0\r\n for (const [s, e] of merged) {\r\n segCur += renderSeg(piece.slice(last, s), segCur, false)\r\n segCur += renderSeg(piece.slice(s, e), segCur, true)\r\n last = e\r\n }\r\n segCur += renderSeg(piece.slice(last), segCur, false)\r\n cursor = segCur\r\n }\r\n i = j\r\n }\r\n }\r\n\r\n // 개체 배치 — 인라인은 소속 줄 위치, 앵커는 hp:pos 해석\r\n for (const o of m.objs) {\r\n if (o.inline) {\r\n let planIdx = 0\r\n for (let k = 0; k < plans.length; k++) {\r\n const pl = plans[k]\r\n if (pl.start <= o.index && (o.index < pl.end || k === plans.length - 1)) planIdx = k\r\n }\r\n const plan = plans[planIdx]\r\n if (segPages && segPages[planIdx] !== undefined) ctx.page = segPages[planIdx]\r\n const x = ox + plan.seg.horzpos + plan.xoff + advanceTo(m, ctx.styles, plan, o.index)\r\n // 개체가 줄보다 낮으면 baseline 위에 얹고, 줄을 채우는 개체(th==h)는 줄 상단\r\n const yTop = oy + plan.seg.vertpos + Math.max(0, plan.seg.baseline - o.height)\r\n drawObject(o, x, yTop, baseV, areaW, ctx, depth)\r\n } else {\r\n // 앵커 좌표는 첫 seg(baseV) 기준이므로 첫 seg 의 페이지에 귀속\r\n if (segPages && segPages[0] !== undefined) ctx.page = segPages[0]\r\n const { x, y } = anchorObject(o, ox, oy, baseV, areaW, ctx)\r\n drawObject(o, x, y, baseV, areaW, ctx, depth)\r\n }\r\n }\r\n}\r\n\r\n/** hp:pos 기준계 해석 → 개체 좌상단 절대좌표 (tac=0) */\r\nfunction anchorObject(o: ParaObj, ox: number, oy: number, baseV: number, areaW: number, ctx: Ctx): { x: number; y: number } {\r\n const { PW, PH, ML, MT, BODY_W, BODY_H } = ctx.geom\r\n const pos = findChildByLocalName(o.el, \"pos\")\r\n const om = findChildByLocalName(o.el, \"outMargin\")\r\n const omT = num(om, \"top\"), omB = num(om, \"bottom\")\r\n const w = o.width, h = o.height\r\n if (!pos) return { x: ox, y: oy + baseV }\r\n const vo = num(pos, \"vertOffset\")\r\n const ho = num(pos, \"horzOffset\")\r\n const vrel = pos.getAttribute(\"vertRelTo\") ?? \"PARA\"\r\n const hrel = pos.getAttribute(\"horzRelTo\") ?? \"PARA\"\r\n const va = pos.getAttribute(\"vertAlign\") ?? \"TOP\"\r\n const ha = pos.getAttribute(\"horzAlign\") ?? \"LEFT\"\r\n const wrap = o.el.getAttribute(\"textWrap\") ?? \"TOP_AND_BOTTOM\"\r\n\r\n let y: number\r\n if (vrel === \"PAPER\") {\r\n y = va === \"BOTTOM\" ? PH - h - vo : va === \"CENTER\" ? (PH - h) / 2 + vo : vo\r\n } else if (vrel === \"PAGE\") {\r\n y = va === \"BOTTOM\" ? MT + BODY_H - h - vo : va === \"CENTER\" ? MT + (BODY_H - h) / 2 + vo : MT + vo\r\n } else if (wrap === \"TOP_AND_BOTTOM\") {\r\n // PARA 밀어내기 역산 — 호스트 문단 vp가 개체(여백 포함)만큼 밀렸다는 모델 (실측 일치).\r\n // 음수면 밀지 않은 개체(빈 호스트 줄 등)로 보고 현재 흐름 y 사용.\r\n const pushed = baseV - (omT + h + omB)\r\n const anchor = pushed >= -100 ? pushed : baseV\r\n y = oy + anchor + omT + vo\r\n } else {\r\n // BEHIND_TEXT/IN_FRONT/SQUARE — 문단을 밀지 않는 개체는 문단 원점 흐름 배치\r\n y = oy + baseV + vo\r\n }\r\n\r\n let x: number\r\n if (hrel === \"PAGE\") {\r\n x = ha === \"RIGHT\" ? ML + BODY_W - w - ho : ha === \"CENTER\" ? ML + (BODY_W - w) / 2 + ho : ML + ho\r\n } else if (hrel === \"PAPER\") {\r\n x = ha === \"RIGHT\" ? PW - w - ho : ha === \"CENTER\" ? (PW - w) / 2 + ho : ho\r\n } else {\r\n // PARA/COLUMN — 현재 텍스트 영역 기준 (셀 안이면 셀 내부 폭. COLUMN을 페이지\r\n // 단으로 읽으면 셀 안 개체가 페이지 왼쪽으로 튄다 — 사진대지 우측 셀 실측)\r\n x = ha === \"RIGHT\" ? ox + areaW - w - ho : ha === \"CENTER\" ? ox + (areaW - w) / 2 + ho : ox + ho\r\n }\r\n return { x, y }\r\n}\r\n\r\nfunction drawObject(o: ParaObj, x: number, y: number, baseV: number, areaW: number, ctx: Ctx, depth: number): void {\r\n if (o.tag === \"tbl\") drawTable(o.el, x, y, ctx, depth + 1)\r\n else if (o.tag === \"pic\") drawPic(o.el, x, y, ctx)\r\n else if (o.tag === \"container\") {\r\n // 그리기개체 묶음 — 자식 개체를 컨테이너 원점 기준으로 재귀 배치\r\n for (const ch of elements(o.el)) {\r\n const tag = ln(ch)\r\n if (!OBJ_TAGS.has(tag)) continue\r\n const sz = findChildByLocalName(ch, \"sz\")\r\n const off = findChildByLocalName(ch, \"offset\")\r\n const sub: ParaObj = { el: ch, tag, index: 0, inline: true, width: num(sz, \"width\"), height: num(sz, \"height\") }\r\n drawObject(sub, x + num(off, \"x\"), y + num(off, \"y\"), baseV, areaW, ctx, depth + 1)\r\n }\r\n } else if (o.tag === \"equation\") {\r\n warnOnce(ctx, \"equation\", \"수식 개체는 렌더 미지원 — 생략\")\r\n } else if (SHAPE_TAGS.has(o.tag)) {\r\n drawShape(o, x, y, ctx, depth)\r\n } else {\r\n warnOnce(ctx, `shape:${o.tag}`, `개체(${o.tag}) 렌더 미지원 — 생략`)\r\n }\r\n}\r\n\r\n// ─── 그리기 도형 (rect/ellipse/line/polygon/curv/arc) ────────────────\r\n// geometry 좌표는 개체 로컬(orgSz 기준). 실제 크기 = curSz(있으면)로 스케일.\r\n// lineShape=선(color/width/style), fillBrush>winBrush=채움(faceColor). 회전은 근사 생략.\r\n\r\nconst SHAPE_TAGS = new Set([\"rect\", \"ellipse\", \"line\", \"polygon\", \"curv\", \"arc\"])\r\n\r\n/** lineShape width(1/100 mm) → pt */\r\nfunction shapeStrokePt(v: number): number {\r\n return Math.max(0.2, (v / 100) * 2.834645)\r\n}\r\n\r\nfunction drawShape(o: ParaObj, x: number, y: number, ctx: Ctx, depth: number): void {\r\n const el = o.el\r\n const orgSz = findChildByLocalName(el, \"orgSz\")\r\n const curSz = findChildByLocalName(el, \"curSz\")\r\n const ow = num(orgSz, \"width\"), oh = num(orgSz, \"height\")\r\n const w = num(curSz, \"width\") || ow || o.width\r\n const h = num(curSz, \"height\") || oh || o.height\r\n const sx = ow > 0 ? w / ow : 1\r\n const sy = oh > 0 ? h / oh : 1\r\n\r\n const lineShape = findChildByLocalName(el, \"lineShape\")\r\n const lstyle = lineShape?.getAttribute(\"style\") ?? \"SOLID\"\r\n const strokeCol = lineShape?.getAttribute(\"color\") || \"#000000\"\r\n const hasStroke = lstyle !== \"NONE\"\r\n const strokeW = hasStroke ? shapeStrokePt(lineShape ? num(lineShape, \"width\") : 33) : 0\r\n const dash = /DASH|DOT/.test(lstyle) ? ` stroke-dasharray=\"${lstyle.includes(\"DOT\") ? \"1,1.5\" : \"3,1.5\"}\"` : \"\"\r\n const strokeAttr = hasStroke ? ` stroke=\"${escapeXml(strokeCol)}\" stroke-width=\"${strokeW.toFixed(2)}\"${dash}` : \"\"\r\n\r\n const fillBrush = findChildByLocalName(el, \"fillBrush\")\r\n const winBrush = fillBrush ? findChildByLocalName(fillBrush, \"winBrush\") : null\r\n const face = winBrush?.getAttribute(\"faceColor\")\r\n const fill = face && face.toLowerCase() !== \"none\" ? face : \"none\"\r\n const fillAttr = ` fill=\"${fill === \"none\" ? \"none\" : escapeXml(fill)}\"`\r\n\r\n if (o.tag === \"rect\") {\r\n emit(ctx, `<rect x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\"${fillAttr}${strokeAttr}/>`)\r\n } else if (o.tag === \"ellipse\") {\r\n emit(ctx, `<ellipse cx=\"${pt(x + w / 2)}\" cy=\"${pt(y + h / 2)}\" rx=\"${pt(w / 2)}\" ry=\"${pt(h / 2)}\"${fillAttr}${strokeAttr}/>`)\r\n } else if (o.tag === \"line\") {\r\n const s = findChildByLocalName(el, \"startPt\"), e = findChildByLocalName(el, \"endPt\")\r\n const x1 = x + num(s, \"x\") * sx, y1 = y + num(s, \"y\") * sy\r\n const x2 = x + num(e, \"x\") * sx, y2 = y + num(e, \"y\") * sy\r\n emit(ctx, `<line x1=\"${pt(x1)}\" y1=\"${pt(y1)}\" x2=\"${pt(x2)}\" y2=\"${pt(y2)}\" stroke=\"${escapeXml(strokeCol)}\" stroke-width=\"${(strokeW || 0.3).toFixed(2)}\"${dash}/>`)\r\n } else if (o.tag === \"polygon\" || o.tag === \"curv\") {\r\n const pts: string[] = []\r\n for (const c of elements(el)) if (ln(c) === \"pt\") pts.push(`${pt(x + num(c, \"x\") * sx)},${pt(y + num(c, \"y\") * sy)}`)\r\n if (pts.length >= 2) emit(ctx, `<polygon points=\"${pts.join(\" \")}\"${fillAttr}${strokeAttr}/>`)\r\n } else if (o.tag === \"arc\") {\r\n // 호는 외접 박스 타원으로 근사 (start/sweep 각 미해석)\r\n emit(ctx, `<ellipse cx=\"${pt(x + w / 2)}\" cy=\"${pt(y + h / 2)}\" rx=\"${pt(w / 2)}\" ry=\"${pt(h / 2)}\" fill=\"none\"${strokeAttr || ` stroke=\"${escapeXml(strokeCol)}\" stroke-width=\"0.3\"`}/>`)\r\n }\r\n\r\n // 도형 안 텍스트(drawText>subList) — 조판 캐시 있으면 그린다\r\n const dt = findChildByLocalName(el, \"drawText\")\r\n const sub = dt ? findChildByLocalName(dt, \"subList\") : null\r\n if (sub) {\r\n for (const p of elements(sub)) if (ln(p) === \"p\") drawPara(p, x, y, w, ctx, depth + 1)\r\n }\r\n}\r\n\r\n// ─── 표 ───────────────────────────────────────────\r\n\r\ninterface CellModel {\r\n el: Element\r\n ca: number; ra: number; cs: number; rs: number\r\n w: number; h: number\r\n bfId: string | null\r\n sub: Element | null\r\n marginL: number; marginR: number; marginT: number; marginB: number\r\n}\r\n\r\n/** 셀 콘텐츠 세로 범위 — 줄(vp+th) + 인라인 개체(중첩표·treatAsChar) + PARA 앵커 개체(anchor+h) 최대값 */\r\nfunction cellContentExtent(cell: CellModel): number {\r\n if (!cell.sub) return 0\r\n let ext = 0\r\n for (const p of elements(cell.sub)) {\r\n if (ln(p) !== \"p\") continue\r\n const m = buildPara(p)\r\n for (const s of m.segs) ext = Math.max(ext, s.vertpos + s.textheight)\r\n const baseV = m.segs[0]?.vertpos ?? 0\r\n for (const o of m.objs) {\r\n if (o.inline) {\r\n // 인라인 개체(중첩 표·treatAsChar 이미지)는 줄 위치에서 개체 높이만큼 아래로 뻗는다.\r\n // 이를 빼먹으면 중첩 표를 담은 셀 높이가 과소측정돼 표지 중첩표가 겹친다(리뷰: 셀 성장 누락).\r\n const h = o.tag === \"tbl\" ? Math.max(o.height, measureTableHeight(o.el)) : o.height\r\n ext = Math.max(ext, baseV + h)\r\n continue\r\n }\r\n const pos = findChildByLocalName(o.el, \"pos\")\r\n if ((pos?.getAttribute(\"vertRelTo\") ?? \"PARA\") !== \"PARA\") continue\r\n const om = findChildByLocalName(o.el, \"outMargin\")\r\n const pushed = baseV - (num(om, \"top\") + o.height + num(om, \"bottom\"))\r\n const anchor = pushed >= -100 ? pushed : baseV\r\n ext = Math.max(ext, anchor + num(om, \"top\") + num(pos, \"vertOffset\") + o.height)\r\n }\r\n }\r\n return ext\r\n}\r\n\r\nfunction edgeLine(x1: number, y1: number, x2: number, y2: number, e: RenderBorderEdge): string {\r\n const dash = /DASH|DOT/.test(e.type) ? ` stroke-dasharray=\"${e.type.includes(\"DOT\") ? \"1,1.5\" : \"3,1.5\"}\"` : \"\"\r\n return `<line x1=\"${pt(x1)}\" y1=\"${pt(y1)}\" x2=\"${pt(x2)}\" y2=\"${pt(y2)}\" stroke=\"${escapeXml(e.color)}\" stroke-width=\"${e.widthPt.toFixed(2)}\"${dash}/>`\r\n}\r\n\r\n/** tbl의 셀 모델 수집 — drawTable과 measureTableHeight가 같은 셀 해석을 공유 */\r\nfunction collectCells(tbl: Element): CellModel[] {\r\n const inMargin = findChildByLocalName(tbl, \"inMargin\")\r\n const defL = num(inMargin, \"left\", 141), defR = num(inMargin, \"right\", 141)\r\n const defT = num(inMargin, \"top\", 141), defB = num(inMargin, \"bottom\", 141)\r\n\r\n const cells: CellModel[] = []\r\n for (const tr of elements(tbl)) {\r\n if (ln(tr) !== \"tr\") continue\r\n for (const tc of elements(tr)) {\r\n if (ln(tc) !== \"tc\") continue\r\n const addr = findChildByLocalName(tc, \"cellAddr\")\r\n const span = findChildByLocalName(tc, \"cellSpan\")\r\n const csz = findChildByLocalName(tc, \"cellSz\")\r\n const cm = findChildByLocalName(tc, \"cellMargin\")\r\n if (!addr || !csz) continue\r\n cells.push({\r\n el: tc,\r\n ca: num(addr, \"colAddr\"), ra: num(addr, \"rowAddr\"),\r\n cs: Math.max(1, num(span, \"colSpan\", 1)), rs: Math.max(1, num(span, \"rowSpan\", 1)),\r\n w: num(csz, \"width\"), h: num(csz, \"height\"),\r\n bfId: tc.getAttribute(\"borderFillIDRef\"),\r\n sub: findChildByLocalName(tc, \"subList\"),\r\n marginL: cm ? num(cm, \"left\", defL) : defL, marginR: cm ? num(cm, \"right\", defR) : defR,\r\n marginT: cm ? num(cm, \"top\", defT) : defT, marginB: cm ? num(cm, \"bottom\", defB) : defB,\r\n })\r\n }\r\n }\r\n return cells\r\n}\r\n\r\n/**\r\n * 표 실효 높이(HWPUNIT) — drawTable의 rowH 모델(solveRowHeights + 셀 콘텐츠 성장) 그대로.\r\n * 선언 hp:sz는 셀 콘텐츠로 자란 높이를 모르므로, reflow가 표 뒤 문단을 실제 그려질\r\n * 표 바닥 아래로 배치할 때 이 값을 쓴다. 셀 lineseg가 있어야 성장분이 측정된다.\r\n */\r\nexport function measureTableHeight(tbl: Element): number {\r\n const cells = collectCells(tbl)\r\n if (cells.length === 0 || cells.length > 4096) return 0\r\n const nRows = Math.max(...cells.map(c => c.ra + c.rs))\r\n const rowH = solveRowHeights(\r\n cells.map(c => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : undefined })),\r\n nRows,\r\n )\r\n let sum = 0\r\n for (const h of rowH) sum += h\r\n return sum\r\n}\r\n\r\nfunction drawTable(tbl: Element, tx: number, ty: number, ctx: Ctx, depth: number): void {\r\n if (depth > 16) { warnOnce(ctx, \"depth\", \"중첩 깊이 16 초과 — 이하 생략\"); return }\r\n ctx.stats.tables++\r\n const tblSz = findChildByLocalName(tbl, \"sz\")\r\n const cells = collectCells(tbl)\r\n if (cells.length === 0 || cells.length > 4096) return\r\n\r\n const nCols = Math.max(...cells.map(c => c.ca + c.cs))\r\n const nRows = Math.max(...cells.map(c => c.ra + c.rs))\r\n const colCons: SpanConstraint[] = cells.map(c => ({ a: c.ca, b: c.ca + c.cs, size: c.w }))\r\n const colX = solveBoundaries(colCons, nCols, num(tblSz, \"width\") || undefined)\r\n const rowH = solveRowHeights(\r\n cells.map(c => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : undefined })),\r\n nRows,\r\n )\r\n const rowY: number[] = [0]\r\n for (let r = 0; r < nRows; r++) rowY.push(rowY[r] + rowH[r])\r\n\r\n // 1패스: 배경 → 2패스: 콘텐츠 → 3패스: 테두리 (테두리가 배경/콘텐츠 위)\r\n const geom = cells.map(c => ({\r\n c,\r\n x: tx + colX[c.ca], y: ty + rowY[c.ra],\r\n w: colX[Math.min(c.ca + c.cs, nCols)] - colX[c.ca],\r\n h: rowY[Math.min(c.ra + c.rs, nRows)] - rowY[c.ra],\r\n }))\r\n for (const g of geom) {\r\n const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : undefined\r\n if (bf?.fill) emit(ctx, `<rect x=\"${pt(g.x)}\" y=\"${pt(g.y)}\" width=\"${pt(g.w)}\" height=\"${pt(g.h)}\" fill=\"${escapeXml(bf.fill)}\"/>`)\r\n }\r\n for (const g of geom) {\r\n const { c } = g\r\n if (!c.sub) continue\r\n const innerH = g.h - c.marginT - c.marginB\r\n const extent = cellContentExtent(c)\r\n const va = c.sub.getAttribute(\"vertAlign\") ?? \"TOP\"\r\n let yoff = 0\r\n if (va === \"CENTER\") yoff = Math.max(0, (innerH - extent) / 2)\r\n else if (va === \"BOTTOM\") yoff = Math.max(0, innerH - extent)\r\n for (const p of elements(c.sub)) {\r\n if (ln(p) !== \"p\") continue\r\n drawPara(p, g.x + c.marginL, g.y + c.marginT + yoff, g.w - c.marginL - c.marginR, ctx, depth + 1)\r\n }\r\n }\r\n for (const g of geom) {\r\n const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : undefined\r\n if (!bf) continue\r\n if (bf.top) emit(ctx, edgeLine(g.x, g.y, g.x + g.w, g.y, bf.top))\r\n if (bf.bottom) emit(ctx, edgeLine(g.x, g.y + g.h, g.x + g.w, g.y + g.h, bf.bottom))\r\n if (bf.left) emit(ctx, edgeLine(g.x, g.y, g.x, g.y + g.h, bf.left))\r\n if (bf.right) emit(ctx, edgeLine(g.x + g.w, g.y, g.x + g.w, g.y + g.h, bf.right))\r\n }\r\n}\r\n\r\n// ─── 이미지 ───────────────────────────────────────\r\n\r\n/**\r\n * 이미지 심볼 등록(바이너리당 1회) — dataURI를 defs에 한 번만 넣고 <use>로 참조한다.\r\n * occurrence마다 dataURI를 재emit하면 반복 참조 문서에서 SVG 문자열이 기하급수로\r\n * 커져 RangeError/OOM (소형 입력 DoS). viewBox 100×100 + preserveAspectRatio none\r\n * 이라 <use width/height>가 기존 <image width/height> stretch와 동일하게 스케일된다.\r\n */\r\nfunction imageSymbol(loaded: { dataUri: string; symId?: string }, ctx: Ctx): string {\r\n if (!loaded.symId) {\r\n loaded.symId = `bin${ctx.defs.length}`\r\n ctx.defs.push(\r\n `<symbol id=\"${loaded.symId}\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\">` +\r\n `<image width=\"100\" height=\"100\" preserveAspectRatio=\"none\" href=\"${loaded.dataUri}\"/></symbol>`,\r\n )\r\n }\r\n return loaded.symId\r\n}\r\n\r\nfunction drawPic(pic: Element, x: number, y: number, ctx: Ctx): void {\r\n const sz = findChildByLocalName(pic, \"sz\")\r\n const w = num(sz, \"width\", 5669), h = num(sz, \"height\", 5669)\r\n const img = findFirst(pic, \"img\")\r\n const ref = img?.getAttribute(\"binaryItemIDRef\")\r\n const loaded = ref != null ? ctx.images.get(ref) : undefined\r\n if (!loaded) {\r\n emit(ctx, `<rect x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\" fill=\"#eee\" stroke=\"#c00\" stroke-width=\"0.5\"/>`)\r\n warnOnce(ctx, `img:${ref}`, `이미지 바이너리 누락: ${ref ?? \"(ref 없음)\"}`)\r\n return\r\n }\r\n ctx.stats.images++\r\n // imgClip 좌표계는 imgDim(이미지 내용 상자) 기준이다 — orgSz(최초 삽입 크기)가 아니다.\r\n // 실측(데모 코퍼스 pic 267개): clip==dim 254개(크롭 없음)·실제 크롭 8개 모두 dim 기준.\r\n // orgSz 로 비교하면 리사이즈된 로고(dim<org)가 좌상단 코너로 잘못 잘려 로고가 깨진다.\r\n const clip = findChildByLocalName(pic, \"imgClip\")\r\n const imgDim = findChildByLocalName(pic, \"imgDim\")\r\n const orgSz = findChildByLocalName(pic, \"orgSz\")\r\n const dimW = num(imgDim, \"dimwidth\"), dimH = num(imgDim, \"dimheight\")\r\n // 참조(전체 내용) 공간: imgDim 우선, 없으면 orgSz 폴백\r\n const refW = dimW > 0 ? dimW : num(orgSz, \"width\")\r\n const refH = dimH > 0 ? dimH : num(orgSz, \"height\")\r\n const cl = num(clip, \"left\"), ct = num(clip, \"top\")\r\n const cr = num(clip, \"right\", refW), cb = num(clip, \"bottom\", refH)\r\n const cropped =\r\n refW > 0 && refH > 0 && clip != null && (cl > 0 || ct > 0 || cr < refW || cb < refH) && cr > cl && cb > ct\r\n const symId = imageSymbol(loaded, ctx)\r\n if (cropped) {\r\n // 참조 좌표계 viewBox로 크롭 창을 내고, 이미지는 전체 내용 크기로 깐다\r\n emit(ctx,\r\n `<svg x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\" viewBox=\"${pt(cl)} ${pt(ct)} ${pt(cr - cl)} ${pt(cb - ct)}\" preserveAspectRatio=\"none\">` +\r\n `<use href=\"#${symId}\" x=\"0\" y=\"0\" width=\"${pt(refW)}\" height=\"${pt(refH)}\"/></svg>`,\r\n )\r\n } else {\r\n emit(ctx, `<use href=\"#${symId}\" x=\"${pt(x)}\" y=\"${pt(y)}\" width=\"${pt(w)}\" height=\"${pt(h)}\"/>`)\r\n }\r\n}\r\n\r\nfunction sniffMime(name: string, bytes: Uint8Array): string {\r\n const lower = name.toLowerCase()\r\n if (lower.endsWith(\".png\") || (bytes.length > 4 && bytes[0] === 0x89 && bytes[1] === 0x50)) return \"image/png\"\r\n if (lower.endsWith(\".bmp\") || (bytes.length > 2 && bytes[0] === 0x42 && bytes[1] === 0x4d)) return \"image/bmp\"\r\n if (lower.endsWith(\".gif\")) return \"image/gif\"\r\n if (lower.endsWith(\".svg\")) return \"image/svg+xml\"\r\n return \"image/jpeg\"\r\n}\r\n\r\n// ─── 엔트리 ───────────────────────────────────────\r\n\r\n/** 구역 하나의 페이지 지오메트리 (HWPUNIT) */\r\nfunction readSectionGeom(root: Element): PageGeom {\r\n const pagePr = findFirst(root, \"pagePr\")\r\n const margin = pagePr ? findChildByLocalName(pagePr, \"margin\") : null\r\n let PW = num(pagePr, \"width\", 59528), PH = num(pagePr, \"height\", 84188)\r\n // 용지 방향 — width/height는 용지 원방향(세로) 치수로 저장되고 landscape=\"NARROWLY\"가\r\n // 가로(90° 회전)를 뜻한다(WIDELY=세로). 무시하면 가로 문서의 오른쪽이 잘린다\r\n // (실측: 가로 A4 문서 lineseg 줄폭 78520 = 84188 − 좌우마진, 세로폭 59528 초과).\r\n if (pagePr?.getAttribute(\"landscape\") === \"NARROWLY\" && PW < PH) {\r\n const t = PW; PW = PH; PH = t\r\n }\r\n const ML = num(margin, \"left\", 8504)\r\n const MT = num(margin, \"top\", 5668) + num(margin, \"header\", 0)\r\n const BODY_H = PH - MT - num(margin, \"bottom\", 4252) - num(margin, \"footer\", 0)\r\n const BODY_W = PW - ML - num(margin, \"right\", 8504)\r\n return { PW, PH, ML, MT, BODY_W, BODY_H }\r\n}\r\n\r\n/** 렌더된 구역 — 페이지 버퍼 + 페이지 크기(구역별 pagePr 상이 가능) */\r\ninterface RenderedSection { pages: string[][]; PW: number; pageH: number; clipId: string }\r\n\r\n/**\r\n * 구역 하나를 렌더 → 페이지 버퍼. 공유 자원(styles/images/defs/warnings/stats)은 ctx로 넘겨\r\n * 이미지 심볼·통계·경고가 전 구역에 누적되게 한다(dataURI 중복·페이지 카운트 정합).\r\n */\r\nfunction renderSectionToPages(\r\n root: Element,\r\n geom: PageGeom,\r\n ctxBase: Omit<Ctx, \"pages\" | \"page\" | \"geom\">,\r\n doReflow: boolean,\r\n reflowMode: WrapMode,\r\n): { pages: string[][]; pageH: number } {\r\n const { PW, PH, ML, MT, BODY_W, BODY_H } = geom\r\n // Tier-2 reflow — 캐시 없는 문단에 linesegarray 합성 주입. 혼합 캐시 문서(한컴\r\n // 저장본을 프로그램 편집해 일부 문단만 캐시 없음)도 reflow 옵션이면 진입한다 —\r\n // 전량 캐시 문서는 전 문단 skip(Tier-1 무회귀)이라 no-op.\r\n if (doReflow) reflowSection(root, ctxBase.styles, { BODY_W, BODY_H }, reflowMode)\r\n\r\n // 페이지 분할 프리패스 — 최상위 lineseg vertpos는 페이지 로컬(페이지마다 0부터)이라\r\n // 역행 지점이 곧 페이지 경계다. 다단(colCount>1)은 단 이동도 vertpos가 리셋되지만\r\n // horzpos가 오른쪽으로 점프하므로, horzpos가 왼쪽으로 돌아올 때만 새 페이지로 본다.\r\n // vertpos \"동일\"도 문단 첫 seg + horzpos 비전진이면 경계다 — 페이지 전체가 표 하나인\r\n // 문단이 연속되면 vertpos가 매 페이지 0으로 같아서 strict 역행만으론 못 가른다\r\n // (의사일정표류: v0 문단 연속 → 뒤 페이지들이 전부 한 페이지에 겹침).\r\n // 문단 내부의 vertpos 동일 seg는 개체 좌우로 갈라진 같은 줄(h 우측 점프)이라 제외.\r\n const colPr = findFirst(root, \"colPr\")\r\n const multiCol = num(colPr, \"colCount\", 1) > 1\r\n const paraSegPages = new Map<Element, number[]>()\r\n let nPages = 1\r\n let maxTopV = 0\r\n {\r\n let prevV = Number.NEGATIVE_INFINITY\r\n let prevH = Number.NEGATIVE_INFINITY\r\n let cur = 0\r\n for (const p of elements(root)) {\r\n if (ln(p) !== \"p\") continue\r\n const lsa = findChildByLocalName(p, \"linesegarray\")\r\n const segEls = lsa ? elements(lsa).filter(s => ln(s) === \"lineseg\") : []\r\n const pagesOf: number[] = []\r\n let paraFirst = true\r\n for (const s of segEls) {\r\n const v = num(s, \"vertpos\")\r\n const h = num(s, \"horzpos\")\r\n const brk = v < prevV\r\n ? (!multiCol || h <= prevH)\r\n : (paraFirst && v === prevV && h <= prevH)\r\n if (brk) cur++\r\n paraFirst = false\r\n pagesOf.push(cur)\r\n maxTopV = Math.max(maxTopV, v + num(s, \"textheight\", 1000))\r\n prevV = v\r\n prevH = h\r\n }\r\n paraSegPages.set(p, pagesOf)\r\n nPages = Math.max(nPages, cur + 1)\r\n }\r\n }\r\n\r\n const ctx: Ctx = {\r\n ...ctxBase,\r\n pages: Array.from({ length: nPages }, () => []),\r\n page: 0,\r\n geom,\r\n }\r\n for (const p of elements(root)) {\r\n if (ln(p) !== \"p\") continue\r\n drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p))\r\n }\r\n\r\n // 리셋이 없는데 본문이 페이지를 넘는 파일(누적 vertpos 기록본) 방어 —\r\n // 한 페이지로 두되 캔버스만 내용 끝까지 늘려 잘림을 막는다.\r\n const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2000) : PH\r\n return { pages: ctx.pages, pageH }\r\n}\r\n\r\n/**\r\n * HWPX(한컴 저장본) → 레이아웃 보존 SVG. **전 구역(section*)을 세로 스택으로** 렌더한다.\r\n * 조판 캐시(linesegarray)가 없는 구역은 reflow 옵션으로 합성 조판(없으면 그 구역 생략);\r\n * 렌더 가능한 구역이 하나도 없으면 KordocError.\r\n */\r\nexport async function renderHwpxToSvg(input: ArrayBuffer | Uint8Array, options?: RenderSvgOptions): Promise<RenderSvgResult> {\r\n const maxImg = options?.maxImageBytes ?? 40 * 1024 * 1024\r\n // 압축폭탄 가드 — 파서 진입점과 동일하게 압축해제 전 central directory 선언 크기를 검사한다.\r\n // 렌더 이미지 캡은 f.async 로 엔트리를 전량 압축해제한 뒤에야 크기를 보므로, 단일 BinData\r\n // 압축폭탄(수 GB 선언)이 캡을 우회해 OOM 을 유발하던 것을 원천 차단한다 (reflow-1).\r\n const ab = input instanceof Uint8Array\r\n ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer\r\n : input\r\n precheckZipSize(ab, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES)\r\n let zip: JSZip\r\n try {\r\n zip = await JSZip.loadAsync(input)\r\n } catch {\r\n throw new KordocError(\"HWPX(ZIP) 형식이 아닙니다 — 렌더는 HWPX만 지원\")\r\n }\r\n // 모든 구역 파일 (section0, section1, … — 이름 정렬)\r\n const secFiles = zip.file(/Contents\\/section\\d+\\.xml$/i).sort((a, b) => a.name.localeCompare(b.name))\r\n if (secFiles.length === 0) throw new KordocError(\"Contents/section0.xml 없음 — HWPX가 아니거나 손상됨\")\r\n\r\n const warnings: string[] = []\r\n const headFile = zip.file(\"Contents/header.xml\") ?? zip.file(\"Contents/head.xml\")\r\n const styles: RenderStyles = headFile\r\n ? parseRenderStyles(await headFile.async(\"string\"))\r\n : { charPr: new Map(), paraAlign: new Map(), paraGeom: new Map(), borderFill: new Map() }\r\n if (!headFile) warnings.push(\"header.xml 없음 — 기본 스타일로 렌더\")\r\n\r\n // 구역 XML 선로딩 + 크기 가드\r\n const secXmls: string[] = []\r\n for (const f of secFiles) {\r\n const xml = await f.async(\"string\")\r\n if (xml.length > MAX_DECOMPRESS_SIZE) throw new KordocError(\"섹션 XML이 허용 크기를 초과\")\r\n secXmls.push(xml)\r\n }\r\n\r\n // BinData 매니페스트 (id → href) — content.hpf 우선, 파일명 휴리스틱 폴백\r\n const binmap = new Map<string, string>()\r\n const hpf = zip.file(/content\\.hpf$/i)[0]\r\n if (hpf) {\r\n const man = await hpf.async(\"string\")\r\n for (const m of man.matchAll(/<[^>]*\\bid=\"([^\"]+)\"[^>]*\\bhref=\"(BinData\\/[^\"]+)\"[^>]*>/g)) binmap.set(m[1], m[2])\r\n for (const m of man.matchAll(/<[^>]*\\bhref=\"(BinData\\/[^\"]+)\"[^>]*\\bid=\"([^\"]+)\"[^>]*>/g)) binmap.set(m[2], m[1])\r\n }\r\n // 참조된 이미지만 선로딩 — 전 구역 ref 합집합 (DOM 워크 전에 async 구간 종료).\r\n // 장당 캡(maxImg) 외에 개수·누적 바이트 캡 — distinct 다수/대형 반복 참조로\r\n // 렌더 경로에서만 OOM 가능하던 구멍(파서 경로의 ZIP bomb 가드에 상응)\r\n const MAX_IMAGE_REFS = 256\r\n const MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024\r\n const images: Ctx[\"images\"] = new Map()\r\n const refs = new Set<string>()\r\n for (const xml of secXmls) for (const m of xml.matchAll(/binaryItemIDRef=\"([^\"]+)\"/g)) refs.add(m[1])\r\n let totalImgBytes = 0\r\n for (const ref of refs) {\r\n if (images.size >= MAX_IMAGE_REFS) {\r\n warnings.push(`이미지 ${refs.size}종 중 ${MAX_IMAGE_REFS}종만 로딩 — 개수 한도 초과분 생략`)\r\n break\r\n }\r\n let href = binmap.get(ref)\r\n if (!href) {\r\n const cand = zip.file(new RegExp(`BinData/.*${ref.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"))[0]\r\n href = cand?.name\r\n }\r\n if (!href) continue\r\n const f = zip.file(href) ?? zip.file(\"Contents/\" + href)\r\n if (!f) continue\r\n const bytes = await f.async(\"uint8array\")\r\n if (bytes.length > maxImg) {\r\n warnings.push(`이미지 ${href} ${(bytes.length / 1048576).toFixed(1)}MB — 한도 초과로 생략`)\r\n continue\r\n }\r\n if (totalImgBytes + bytes.length > MAX_TOTAL_IMAGE_BYTES) {\r\n warnings.push(`이미지 누적 ${Math.round(MAX_TOTAL_IMAGE_BYTES / 1048576)}MB 한도 초과 — 이후 생략`)\r\n break\r\n }\r\n totalImgBytes += bytes.length\r\n images.set(ref, { dataUri: `data:${sniffMime(href, bytes)};base64,${Buffer.from(bytes).toString(\"base64\")}` })\r\n }\r\n\r\n // 전 구역 공유 컨텍스트 자원 (이미지 심볼 defs·통계·경고를 누적)\r\n const ctxBase: Omit<Ctx, \"pages\" | \"page\" | \"geom\"> = {\r\n styles, images, defs: [],\r\n highlights: (options?.highlights ?? []).map(s => s.trim().toLowerCase()).filter(s => s.length > 0),\r\n warnings, warned: new Set(), stats: { texts: 0, images: 0, tables: 0 },\r\n }\r\n\r\n // 구역별 렌더\r\n const rendered: RenderedSection[] = []\r\n let noCacheSkipped = false\r\n for (let si = 0; si < secXmls.length; si++) {\r\n const secXml = secXmls[si]\r\n // 태그 마크업만 매치 — 본문 텍스트에 \"linesegarray\"가 있어도 캐시로 오판하지 않는다\r\n // (본문의 <는 XML에서 <로 이스케이프되므로 리터럴 <…linesegarray는 태그뿐)\r\n const hasCache = /<(?:[A-Za-z][\\w.-]*:)?linesegarray[\\s/>]/.test(secXml)\r\n if (!hasCache && !options?.reflow) {\r\n noCacheSkipped = true\r\n warnings.push(`구역 ${si}: 조판 캐시 없음 — reflow 옵션 필요, 생략`)\r\n continue\r\n }\r\n const doc = createXmlParser().parseFromString(secXml, \"text/xml\")\r\n const root = doc.documentElement as unknown as Element\r\n if (!root) { warnings.push(`구역 ${si} XML 파싱 실패 — 생략`); continue }\r\n const geom = readSectionGeom(root)\r\n const { pages, pageH } = renderSectionToPages(root, geom, ctxBase, !!options?.reflow, options?.reflowMode ?? \"keep\")\r\n rendered.push({ pages, PW: geom.PW, pageH, clipId: `pgclip${si}` })\r\n }\r\n\r\n if (rendered.length === 0) {\r\n if (noCacheSkipped) {\r\n throw new KordocError(\"조판 캐시(linesegarray) 없음 — 한컴에서 저장한 HWPX만 렌더 가능 (reflow 옵션으로 합성 렌더 가능)\")\r\n }\r\n throw new KordocError(\"렌더할 구역이 없습니다 — HWPX가 손상되었을 수 있습니다\")\r\n }\r\n\r\n // 전 구역 페이지를 세로 스택으로 조립 (구역마다 page 크기 상이 가능 → 구역별 clip)\r\n const GAP = 2400 // 페이지 사이 시각 간격 (24pt)\r\n const clipDefs: string[] = []\r\n const groups: string[] = []\r\n let y = 0\r\n let maxPW = 0\r\n let pageNo = 0\r\n for (const rs of rendered) {\r\n maxPW = Math.max(maxPW, rs.PW)\r\n clipDefs.push(`<clipPath id=\"${rs.clipId}\"><rect x=\"0\" y=\"0\" width=\"${pt(rs.PW)}\" height=\"${pt(rs.pageH)}\"/></clipPath>`)\r\n for (const buf of rs.pages) {\r\n pageNo++\r\n groups.push(\r\n `<g data-page=\"${pageNo}\" transform=\"translate(0 ${pt(y)})\">` +\r\n `<rect width=\"${pt(rs.PW)}\" height=\"${pt(rs.pageH)}\" fill=\"white\" stroke=\"#c9c7c4\" stroke-width=\"0.75\"/>` +\r\n `<g clip-path=\"url(#${rs.clipId})\">\\n${buf.join(\"\\n\")}\\n</g></g>`,\r\n )\r\n y += rs.pageH + GAP\r\n }\r\n }\r\n const totalH = Math.max(0, y - GAP)\r\n\r\n // width/height는 pt 단위 명시 — 단위 없는 px로 두면 A4 실물(96dpi 기준)보다 25% 작게 보인다 (v3.10.1)\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${pt(maxPW)} ${pt(totalH)}\" width=\"${pt(maxPW)}pt\" height=\"${pt(totalH)}pt\" font-family=\"'HCR Batang','함초롬바탕','Hancom Batang',AppleMyungjo,'Noto Serif CJK KR',serif\" xml:space=\"preserve\">\\n` +\r\n `<defs>${clipDefs.join(\"\")}${ctxBase.defs.join(\"\")}</defs>\\n` +\r\n `${groups.join(\"\\n\")}\\n</svg>`\r\n return { svg, width: Math.round(maxPW) / 100, height: Math.round(totalH) / 100, pageCount: pageNo, warnings, stats: ctxBase.stats }\r\n}\r\n","/**\r\n * 레이아웃 보존 렌더 — 순수 계산 유틸 (XML 무접촉).\r\n * 좌표 산식은 .claude/plans/render-poc/findings.md에서 실측 검증된 모델.\r\n * 단위: HWPUNIT(1/7200in), pt 변환은 /100.\r\n */\r\n\r\n/** HWPX 좌표 속성은 uint32로 저장된 음수가 섞여 있다 (예: vertOffset=4294967103 = -193) */\r\nexport function toInt32(v: string | undefined, fallback = 0): number {\r\n if (v == null || v === \"\") return fallback\r\n const n = Number(v)\r\n if (!Number.isFinite(n)) return fallback\r\n return n > 0x7fffffff ? n - 0x100000000 : n\r\n}\r\n\r\n/** 셀 하나가 주는 경계 제약: boundary[a] + size = boundary[b] */\r\nexport interface SpanConstraint {\r\n a: number\r\n b: number\r\n size: number\r\n}\r\n\r\n/**\r\n * 표 열 경계 솔버 — 경계 전파.\r\n * 결재문서 트레일러처럼 41열 그리드에 span-1 셀이 거의 없어도,\r\n * x[ca+cs] = x[ca] + w 제약을 고정점까지 전파하면 전 경계가 풀린다 (실측 검증).\r\n * 미해결 경계는 인접 확정 경계 사이 균등 보간.\r\n */\r\nexport function solveBoundaries(constraints: SpanConstraint[], count: number, total?: number): number[] {\r\n const x = new Array<number | undefined>(count + 1).fill(undefined)\r\n x[0] = 0\r\n if (total != null && total > 0) x[count] = total\r\n let changed = true\r\n let guard = 0\r\n while (changed && guard++ < count + 8) {\r\n changed = false\r\n for (const c of constraints) {\r\n if (c.a < 0 || c.b > count || c.a >= c.b) continue\r\n const xa = x[c.a]\r\n const xb = x[c.b]\r\n if (xa != null && xb == null) {\r\n x[c.b] = xa + c.size\r\n changed = true\r\n } else if (xb != null && xa == null) {\r\n x[c.a] = xb - c.size\r\n changed = true\r\n }\r\n }\r\n }\r\n // 잔여 미해결 경계 — 좌우 확정 경계 사이 균등 보간\r\n let i = 0\r\n while (i <= count) {\r\n if (x[i] != null) { i++; continue }\r\n let lo = i - 1\r\n let hi = i\r\n while (hi <= count && x[hi] == null) hi++\r\n const loV = x[lo] as number\r\n const hiV = hi <= count ? (x[hi] as number) : loV + (hi - lo) * 1000\r\n const n = hi - lo\r\n for (let k = 1; k < n; k++) x[lo + k] = loV + ((hiV - loV) * k) / n\r\n if (hi > count) x[count] = hiV\r\n i = hi\r\n }\r\n // 단조 보정 (모순 제약 방어)\r\n const out = x as number[]\r\n for (let k = 1; k <= count; k++) if (out[k] < out[k - 1]) out[k] = out[k - 1]\r\n return out\r\n}\r\n\r\n/**\r\n * 표 행 높이 솔버 — rowSpan=1 셀의 max가 기본, rowSpan>1은 잔여 균등분배.\r\n * h=1 더미 셀(헤어라인 행 장식)이 섞이므로 열과 달리 max 방식이 안전.\r\n * grow: 셀 콘텐츠(사진 등)가 설계 높이를 넘으면 초과분만큼 행이 자란다 —\r\n * 사진대지 실측: 설계 16441 + 사진 22209 → 행 22209, 표 sz 합치 정확 일치.\r\n */\r\nexport function solveRowHeights(\r\n cells: { rowAddr: number; rowSpan: number; height: number; contentH?: number }[],\r\n rowCount: number,\r\n): number[] {\r\n const h = new Array<number>(rowCount).fill(0)\r\n for (const c of cells) {\r\n if (c.rowSpan === 1 && c.rowAddr >= 0 && c.rowAddr < rowCount) {\r\n h[c.rowAddr] = Math.max(h[c.rowAddr], c.height)\r\n }\r\n }\r\n // rowSpan>1 셀 — 포함 행 중 미확정(0) 행에 잔여 균등분배\r\n for (const c of cells) {\r\n if (c.rowSpan <= 1) continue\r\n const rows: number[] = []\r\n for (let r = c.rowAddr; r < Math.min(c.rowAddr + c.rowSpan, rowCount); r++) rows.push(r)\r\n const known = rows.reduce((s, r) => s + h[r], 0)\r\n const missing = rows.filter(r => h[r] === 0)\r\n if (missing.length > 0 && c.height > known) {\r\n const each = (c.height - known) / missing.length\r\n for (const r of missing) h[r] = each\r\n }\r\n }\r\n // 콘텐츠 초과 성장 (rowSpan=1 셀만 — 사진 셀 케이스)\r\n for (const c of cells) {\r\n if (c.rowSpan === 1 && c.contentH != null && c.rowAddr >= 0 && c.rowAddr < rowCount) {\r\n if (c.contentH > h[c.rowAddr]) h[c.rowAddr] = c.contentH\r\n }\r\n }\r\n return h\r\n}\r\n\r\n// 자연폭 계산은 hwpx/text-metrics.ts의 함초롬바탕 실측 advance 테이블(measureTextWidth)을 재사용한다.\r\n","/**\r\n * 한글 조판 텍스트 폭 계산 — 함초롬바탕(HCR Batang) 실측 advance 테이블\r\n *\r\n * 공문서 표준 본문 글꼴인 함초롬바탕 정품 TTF(한컴 공개 배포)의 hmtx advance를\r\n * upem=1000 기준으로 추출한 값이다. Bold도 advance가 Regular와 완전히 동일함을\r\n * 전수 확인했다(한컴이 굵기 간 폭을 통일해 제작). 한글 음절 11,172자 전수 = 970,\r\n * 한자 = 1000, 전각형·원문자·도형·화살표·단위기호 = 970으로 균일하다.\r\n *\r\n * 단위: em×1000. 실제 폭(HWPUNIT) = w/1000 × charPr.height × ratio(장평)/100.\r\n * (1em = 글자크기 = charPr.height HWPUNIT — 1pt = 100 HWPUNIT이므로)\r\n *\r\n * 공백: HWP는 charPr useFontSpace=0(기본)일 때 글꼴의 space advance(0.30em) 대신\r\n * 반각 고정폭(0.50em)을 쓴다 — kordoc 생성 문서는 모두 useFontSpace=0이므로\r\n * 기본 SPACE_EM=500. (글꼴값을 쓰려면 measure 옵션 fontSpace로 300 지정)\r\n *\r\n * 다른 글꼴(맑은 고딕 등)도 한글=전각 균일·숫자≈0.55em의 동일 부류 구조라\r\n * 이 테이블로 근사한다(오차 수 % 이내 — 공문서 본문은 어차피 함초롬바탕 관행).\r\n */\r\n\r\n/** ASCII 0x20~0x7E advance (em×1000). 0x20은 useFontSpace=1일 때의 글꼴값(300) */\r\nconst ASCII_W = [\r\n 300, 320, 320, 610, 610, 830, 724, 320, 320, 320, 550, 550, 320, 550, 320, 550, // 0x20-0x2F\r\n 550, 550, 550, 550, 550, 550, 550, 550, 550, 550, 320, 320, 550, 550, 550, 550, // 0x30-0x3F\r\n 830, 706, 605, 685, 719, 627, 617, 683, 734, 305, 315, 660, 605, 839, 734, 732, // 0x40-0x4F\r\n 603, 705, 660, 627, 664, 731, 706, 910, 705, 705, 626, 320, 550, 320, 550, 550, // 0x50-0x5F\r\n 320, 569, 597, 552, 597, 536, 356, 562, 635, 287, 288, 582, 287, 907, 635, 588, // 0x60-0x6F\r\n 597, 579, 478, 496, 356, 635, 563, 720, 542, 543, 486, 320, 320, 320, 550, 0, // 0x70-0x7E(+DEL)\r\n]\r\n\r\n/** 개별 실측 예외 기호 (em×1000) */\r\nconst SYM_W: Record<number, number> = {\r\n 0xa0: 300, 0xa3: 568, 0xa5: 707, 0xa7: 498, 0xab: 440, 0xac: 564, 0xb0: 291,\r\n 0xb1: 798, 0xb6: 606, 0xb7: 320, 0xbb: 440, 0xd7: 617, 0xf7: 678,\r\n 0x2013: 625, 0x2014: 875, 0x2015: 875, 0x2018: 320, 0x2019: 320, 0x201c: 480, 0x201d: 480,\r\n 0x2020: 558, 0x2021: 438, 0x2025: 640, 0x2026: 960, 0x2030: 988, 0x2032: 335, 0x2033: 474,\r\n 0x203b: 770, 0x20ac: 656, 0x261c: 1012, 0x261e: 1012,\r\n}\r\n\r\n/** 코드포인트의 advance(em×1000). 미상 문자는 CJK권 970 / 라틴권 550 폴백 */\r\nexport function charWidthEm1000(cp: number): number {\r\n if (cp >= 0x20 && cp <= 0x7e) return ASCII_W[cp - 0x20]\r\n const sym = SYM_W[cp]\r\n if (sym !== undefined) return sym\r\n if (cp >= 0xac00 && cp <= 0xd7a3) return 970 // 한글 음절 (전수 균일 확인)\r\n if (cp >= 0x1100 && cp <= 0x11ff) return 970 // 옛한글 자모\r\n if (cp >= 0x3131 && cp <= 0x318e) return 970 // 호환 자모 (ㆍ 포함)\r\n if ((cp >= 0x4e00 && cp <= 0x9fff) || (cp >= 0xf900 && cp <= 0xfaff)) return 1000 // 한자\r\n if ((cp >= 0x3008 && cp <= 0x3011) || (cp >= 0x3014 && cp <= 0x301b)) return 500 // 「」『』〈〉《》〔〕【】\r\n if (cp === 0x3000) return 970 // 전각 공백\r\n if (cp >= 0x2160 && cp <= 0x2183) return 970 // 로마숫자 Ⅰ~Ⅻ (한글 글꼴 전각)\r\n if (cp >= 0x2190 && cp <= 0x22ff) return 970 // 화살표·수학 기호\r\n if (cp >= 0x2460 && cp <= 0x24ff) return 970 // 원문자 ①⑴\r\n if (cp >= 0x25a0 && cp <= 0x26ff) return 970 // 도형 □○◆★\r\n if (cp >= 0x3200 && cp <= 0x33ff) return 970 // 괄호한글 ㉮·단위 ㎡㎏\r\n if (cp >= 0xff01 && cp <= 0xff60) return 970 // 전각형 !~\r\n return cp >= 0x2e80 ? 970 : 550\r\n}\r\n\r\n/** HWP 공백 폭(em×1000) — useFontSpace=0(기본): 반각 고정 500 / =1: 글꼴값 300 */\r\nexport const SPACE_EM_FIXED = 500\r\nexport const SPACE_EM_FONT = 300\r\n\r\n/**\r\n * 폭 테이블 클래스 — 'hcr'(함초롬 실측, 기본) / 'fixedPitch'(고정폭 글꼴:\r\n * ASCII 0.5em·그 외 1.0em). 굴림체·돋움체·바탕체·궁서체('체' 접미 = 고정폭)가\r\n * 대상 — 전자결재 변환기 산출물의 지배 본문 글꼴. 한글이 HCR(0.97em)보다 3%\r\n * 넓어 함초롬 테이블로 재면 줄당 1~2자 과대적재로 wrap 지점이 어긋난다\r\n * (bench/verify-linebreak.mjs seoul 코퍼스 실측: fixedPitch 테이블로 74/75 일치).\r\n */\r\nexport type FaceClass = \"hcr\" | \"fixedPitch\"\r\n\r\n/** HWP 글꼴명 → 폭 테이블 클래스. 미상/미지정은 hcr (기존 동작 불변) */\r\nexport function faceClassOf(face: string | null | undefined): FaceClass {\r\n return face && /^(굴림체|돋움체|바탕체|궁서체)$/.test(face.trim()) ? \"fixedPitch\" : \"hcr\"\r\n}\r\n\r\n/** 고정폭 글꼴 advance(em×1000) — ASCII 반각 500, 그 외 전각 1000 */\r\nfunction fixedPitchWidthEm1000(cp: number): number {\r\n return cp < 0x80 ? 500 : 1000\r\n}\r\n\r\nexport interface MeasureOptions {\r\n /** 공백 폭(em×1000). 기본 SPACE_EM_FIXED(500) = useFontSpace 0 */\r\n spaceEm?: number\r\n /** 자간(charPr spacing %) — 글자폭의 %가 문자마다 추가 */\r\n spacingPct?: number\r\n /** 폭 테이블 클래스 — 기본 'hcr' (함초롬 실측 테이블) */\r\n faceClass?: FaceClass\r\n}\r\n\r\n/**\r\n * 텍스트 폭(HWPUNIT). height=charPr height(pt×100), ratioPct=장평 %.\r\n * 결과는 float — 호출부에서 비교 시 ±0.5 HWPUNIT 오차 허용 권장.\r\n */\r\nexport function measureTextWidth(\r\n text: string,\r\n height: number,\r\n ratioPct: number,\r\n opts?: MeasureOptions,\r\n): number {\r\n const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED\r\n const spacing = opts?.spacingPct ?? 0\r\n const widthEm = opts?.faceClass === \"fixedPitch\" ? fixedPitchWidthEm1000 : charWidthEm1000\r\n let em = 0\r\n for (const ch of text) {\r\n const cp = ch.codePointAt(0)!\r\n const w = cp === 0x20 ? spaceEm : widthEm(cp)\r\n em += w * (1 + spacing / 100)\r\n }\r\n return (em / 1000) * height * (ratioPct / 100)\r\n}\r\n\r\n// ─── 줄바꿈 시뮬레이션 (한컴 조판 모델 — 실측 linesegarray 98% 일치 검증) ───\r\n//\r\n// 검증: bench/verify-linebreak.mjs — 서울 정보소통광장 실제 결재문서의 linesegarray\r\n// (한컴 계열 조판기가 계산한 줄 시작 오프셋)와 대조. 고정폭 글꼴 버킷에서 98%\r\n// (56/57 줄바꿈점, 미스는 코퍼스 좌표계 노이즈) 일치. 이 과정에서 확정된 규칙:\r\n// - 공백 폭 = 0.5em 고정(useFontSpace=0), 장평·자간 모두 공백에도 적용\r\n// - 자간 = 글자폭 × (1+spacing/100)\r\n// - 줄 끝 공백은 hang (줄바꿈을 유발하지 않음)\r\n// - 금칙: 시작금칙(닫는 부호 줄머리 금지)은 직전 글자 1개를 동반해 다음 줄로\r\n// (밀어내기), 끝금칙(여는 괄호 줄끝 금지)은 그 글자를 다음 줄로 내림\r\n\r\n/** 줄머리 금지(시작금칙) 문자 — 한컴 기본 */\r\nconst FORBID_START = new Set([...\"!%),.:;?]}¢°′″℃〉》」』】〕!%),.:;?]}₩~…·、。〃\"])\r\n/** 줄끝 금지(끝금칙) 문자 — 한컴 기본 */\r\nconst FORBID_END = new Set([...\"$([{£¥〈《「『【〔$([{₩\"])\r\n\r\nexport type WrapMode = \"keep\" | \"charAll\"\r\n\r\nexport interface WrapResult {\r\n /** 줄 수 */\r\n lines: number\r\n /** 각 줄의 시작 오프셋(UTF-16) — [0, …] */\r\n starts: number[]\r\n /** 마지막 줄 텍스트 폭(HWPUNIT) */\r\n lastLineWidth: number\r\n}\r\n\r\n/**\r\n * 문단 줄바꿈 시뮬레이션.\r\n * mode 'keep' = 어절 단위(공문서 모드 — 저장 속성 breakNonLatinWord=\"BREAK_WORD\", 이름 역전 주의),\r\n * mode 'charAll' = 글자 단위(breakNonLatinWord=\"KEEP_WORD\" — 한글 기본값·전자결재 변환기).\r\n * 한 어절이 줄보다 길면 keep에서도 글자 단위로 강제 분해(한컴 동일).\r\n *\r\n * @param text 문단 전체 텍스트(항목 부호 포함)\r\n * @param firstWidth 첫 줄 가용 폭(HWPUNIT)\r\n * @param contWidth 둘째 줄부터 가용 폭(HWPUNIT) — 내어쓰기 반영\r\n * @param height charPr height (pt×100)\r\n * @param ratioPct 장평 %\r\n */\r\nexport function simulateWrap(\r\n text: string,\r\n firstWidth: number,\r\n contWidth: number,\r\n height: number,\r\n ratioPct: number,\r\n mode: WrapMode = \"keep\",\r\n opts?: MeasureOptions,\r\n): WrapResult {\r\n const EPS = 0.5\r\n const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED\r\n const spacing = opts?.spacingPct ?? 0\r\n const widthEm = opts?.faceClass === \"fixedPitch\" ? fixedPitchWidthEm1000 : charWidthEm1000\r\n const k = (height * ratioPct) / 100 / 1000\r\n const cwCp = (cp: number): number =>\r\n (cp === 0x20 ? spaceEm : widthEm(cp)) * (1 + spacing / 100) * k\r\n const charW = (ch: string): number => cwCp(ch.codePointAt(0)!)\r\n const rangeW = (from: number, to: number): number => {\r\n let w = 0\r\n for (const ch of text.slice(from, to)) w += charW(ch)\r\n return w\r\n }\r\n\r\n const units = text.match(mode === \"keep\" ? / +|[^ ]+/g : / +|[^ ]/g) ?? []\r\n\r\n const starts = [0]\r\n let lineW = 0\r\n let avail = firstWidth\r\n let pos = 0\r\n const lineStart = (): number => starts[starts.length - 1]\r\n\r\n /** 유닛(시작 unitPos, 폭 w)이 안 들어갈 때 줄바꿈 + 금칙 보정 */\r\n const breakBefore = (unitPos: number, w: number): void => {\r\n let bp = unitPos\r\n const u = text[unitPos]\r\n // 시작금칙: 줄머리 금지 문자면 직전 글자 1개를 함께 내린다 (밀어내기)\r\n if (u !== undefined && FORBID_START.has(u) && bp - 1 > lineStart() && text[bp - 1] !== \" \") bp--\r\n // 끝금칙: 남는 줄 끝이 여는 괄호류면 그 글자(들)도 함께 내린다\r\n while (bp - 1 > lineStart() && FORBID_END.has(text[bp - 1])) bp--\r\n if (bp <= lineStart()) bp = unitPos\r\n starts.push(bp)\r\n avail = contWidth\r\n lineW = rangeW(bp, unitPos) + w\r\n }\r\n\r\n for (const u of units) {\r\n if (u[0] === \" \") {\r\n lineW += charW(\" \") * u.length // 줄 끝 공백은 hang\r\n pos += u.length\r\n continue\r\n }\r\n const w = rangeW(pos, pos + u.length)\r\n if (lineW + w <= avail + EPS) {\r\n lineW += w\r\n pos += u.length\r\n continue\r\n }\r\n if (lineW === 0 || w > contWidth + EPS) {\r\n // 빈 줄이거나 다음 줄에도 안 들어가는 초장 유닛 — 글자 단위 강제 분해\r\n let sub = 0\r\n for (const ch of u) {\r\n const c = charW(ch)\r\n if (lineW + c > avail + EPS && lineW > 0) breakBefore(pos + sub, 0)\r\n lineW += c\r\n sub += ch.length\r\n }\r\n pos += u.length\r\n continue\r\n }\r\n breakBefore(pos, w)\r\n pos += u.length\r\n }\r\n return { lines: starts.length, starts, lastLineWidth: lineW }\r\n}\r\n\r\n/** @deprecated simulateWrap(text, …, 'keep') 별칭 — 하위 호환 */\r\nexport function simulateWrapKeepWord(\r\n text: string,\r\n firstWidth: number,\r\n contWidth: number,\r\n height: number,\r\n ratioPct: number,\r\n opts?: MeasureOptions,\r\n): WrapResult {\r\n return simulateWrap(text, firstWidth, contWidth, height, ratioPct, \"keep\", opts)\r\n}\r\n\r\n/**\r\n * 한두 글자(짧은 꼬리)가 다음 줄로 넘어간 문단을 장평 축소로 한 줄 줄일 수 있는지\r\n * 탐색. baseRatio에서 줄 수 N≥2일 때 r=baseRatio-1…minRatio를 내려가며 처음으로\r\n * 줄 수가 줄어드는(가장 큰) r을 반환. 불가능하면 null.\r\n * (실무 관행: 공무원이 문단 장평을 95→92 등으로 줄여 orphan을 위로 당기는 조작의 자동화.\r\n * keep 모드로 판정 — 글자 단위 조판은 항상 keep 이하의 줄 수라 함께 만족된다.)\r\n */\r\nexport function fitRatioForFewerLines(\r\n text: string,\r\n firstWidth: number,\r\n contWidth: number,\r\n height: number,\r\n baseRatio: number,\r\n minRatio: number,\r\n opts?: MeasureOptions,\r\n): number | null {\r\n const base = simulateWrap(text, firstWidth, contWidth, height, baseRatio, \"keep\", opts)\r\n if (base.lines < 2) return null\r\n for (let r = baseRatio - 1; r >= minRatio; r--) {\r\n const sim = simulateWrap(text, firstWidth, contWidth, height, r, \"keep\", opts)\r\n if (sim.lines < base.lines) return r\r\n }\r\n return null\r\n}\r\n","/**\r\n * Tier-2 reflow — 조판 캐시(linesegarray)가 없는 문단에 좌표를 합성 주입한다.\r\n *\r\n * 한컴은 저장 시 각 줄의 좌표(vertpos/horzpos/…)를 linesegarray로 캐시한다.\r\n * markdownToHwpx 산출물·에이전트 생성본·편집본엔 이 캐시가 없어 svg-render가\r\n * KordocError로 거부한다. reflow는 `simulateWrap`(수평, 실측 98% 일치)과 세로 모델\r\n * (실측 역설계 — `.claude/plans/render-poc/findings.md`)로 lineseg를 계산해 DOM\r\n * `<hp:p>`에 `<hp:linesegarray>`를 append → 그 뒤는 기존 렌더 파이프가 그대로 소비한다.\r\n *\r\n * 세로 모델(HWPUNIT): textheight = 줄 지배 charPr.height, baseline = round(0.85×th),\r\n * 줄 pitch = round(th × lineSpacing%/100), spacing(leading) = pitch − th.\r\n * 문단 세로 흐름 = 본문영역 로컬 누적(다음문단 = 이전끝 + pitch + next + prev).\r\n *\r\n * 원칙: 한컴본 캐시는 절대 건드리지 않는다(Tier-1 무회귀). segs가 있는 문단은 건너뛴다.\r\n * Phase 3(v4.0.4): 개체 세로 흐름 — float(TOP_AND_BOTTOM)는 텍스트를 outMargin 포함\r\n * 개체 아래로 밀고, PAGE/PAPER 앵커·BEHIND/IN_FRONT는 흐름 불참, inline 표는 실효높이\r\n * +leading 전진. 혼합 캐시 문서는 캐시 문단의 한컴 좌표로 커서를 이어 받는다\r\n * (seoul 코퍼스 자기일관성 59/59 — 36264961 전 문단 d=0 실측).\r\n */\r\n\r\nimport { buildPara, measureTableHeight } from \"./svg-render.js\"\r\nimport { simulateWrap, faceClassOf, type WrapMode } from \"../hwpx/text-metrics.js\"\r\nimport { DEFAULT_CHAR, DEFAULT_PARA_GEOM, type RenderStyles, type RenderParaGeom } from \"./head-styles.js\"\r\nimport { findChildByLocalName } from \"../hwpx/parser-shared.js\"\r\nimport { toInt32 } from \"./layout.js\"\r\n\r\n/** lineseg flags 고정값 (한컴 저장본 실측 — 0x60000) */\r\nconst LINESEG_FLAGS = \"393216\"\r\n/** baseline / textheight 비율 (실측 94/94 일치) */\r\nconst BASELINE_RATIO = 0.85\r\n\r\nfunction ln(el: Element): string {\r\n return (el.tagName || \"\").replace(/^[^:]+:/, \"\")\r\n}\r\n\r\nfunction elements(el: Element): Element[] {\r\n const out: Element[] = []\r\n const kids = el.childNodes\r\n if (!kids) return out\r\n for (let i = 0; i < kids.length; i++) if (kids[i].nodeType === 1) out.push(kids[i] as Element)\r\n return out\r\n}\r\n\r\nfunction num(el: Element | null, attr: string, fallback = 0): number {\r\n return el ? toInt32(el.getAttribute(attr) ?? undefined, fallback) : fallback\r\n}\r\n\r\n/** 문단의 합성 linesegarray 줄들의 vertpos를 delta만큼 이동 (페이지 로컬 리셋용) */\r\nfunction shiftParaVert(p: Element, delta: number): void {\r\n for (const lsa of elements(p)) {\r\n if (ln(lsa) !== \"linesegarray\") continue\r\n for (const seg of elements(lsa)) {\r\n if (ln(seg) !== \"lineseg\") continue\r\n seg.setAttribute(\"vertpos\", String(num(seg, \"vertpos\") + delta))\r\n }\r\n }\r\n}\r\n\r\nexport interface ReflowGeom {\r\n BODY_W: number\r\n BODY_H: number\r\n}\r\n\r\n/** 줄 pitch(다음 줄 vertpos 증분, HWPUNIT) — lineSpacing type별 */\r\nfunction pitchFor(height: number, geom: RenderParaGeom): number {\r\n const v = geom.lineSpacingValue\r\n switch (geom.lineSpacingType) {\r\n case \"PERCENT\": return Math.round((height * v) / 100)\r\n case \"FIXED\": return v > 0 ? v : Math.round(height * 1.6) // 고정 줄높이(HWPUNIT)\r\n case \"AT_LEAST\": return Math.max(v, height)\r\n default: return Math.round(height * 1.6) // BETWEEN_LINES 등 — 기본 160% 근사\r\n }\r\n}\r\n\r\n/**\r\n * 문단 하나의 linesegarray를 합성해 p에 삽입.\r\n * @returns 세로 흐름 갱신값 (캐시 있어 건너뛴 경우 null)\r\n */\r\nfunction reflowPara(\r\n p: Element,\r\n doc: Document,\r\n styles: RenderStyles,\r\n areaW: number,\r\n startV: number,\r\n mode: WrapMode,\r\n): { paraBottom: number; spaceAfter: number } | null {\r\n const m = buildPara(p)\r\n if (m.segs.length > 0) return null // 이미 캐시 있음 — Tier-1 무회귀\r\n\r\n // 실텍스트 + UTF-16 유닛 → chars 슬롯 인덱스 매핑 (서로게이트 쌍은 슬롯 1개, 유닛 2개)\r\n const realIdx: number[] = []\r\n let text = \"\"\r\n for (let i = 0; i < m.chars.length; i++) {\r\n const ch = m.chars[i].ch\r\n if (ch === \"\") continue\r\n for (let u = 0; u < ch.length; u++) realIdx.push(i)\r\n text += ch\r\n }\r\n\r\n const geom = styles.paraGeom.get(m.paraPrId ?? \"\") ?? DEFAULT_PARA_GEOM\r\n // 문단 지배 charPr — 첫 실문자 우선(height/장평/자간)\r\n let domChar = DEFAULT_CHAR\r\n for (const c of m.chars) {\r\n if (c.ch !== \"\" && c.prId != null) {\r\n const st = styles.charPr.get(c.prId)\r\n if (st) { domChar = st; break }\r\n }\r\n }\r\n // 실문자가 없는 문단(빈 줄·개체 전용)도 run의 charPrIDRef가 줄높이를 지배한다 —\r\n // DEFAULT_CHAR(1000)로 재면 빈 문단 pitch가 본문(예: 1300→2080)보다 작아 이후\r\n // 문단 전체가 위로 밀린다 (seoul 코퍼스 36264961 실측 −480×빈줄 누적)\r\n if (domChar === DEFAULT_CHAR) {\r\n for (const run of elements(p)) {\r\n if (ln(run) !== \"run\") continue\r\n const st = styles.charPr.get(run.getAttribute(\"charPrIDRef\") ?? \"\")\r\n if (st) { domChar = st; break }\r\n }\r\n }\r\n const height = domChar.height || 1000\r\n const ratio = domChar.ratio || 100\r\n const spacingPct = domChar.spacing || 0\r\n\r\n const marginL = geom.marginLeft\r\n const avail = Math.max(1000, areaW - marginL - geom.marginRight)\r\n // hanging(intent<0): 둘째 줄부터 |intent| 더 들어감 → contWidth 축소·contHorz 우측 이동\r\n const firstWidth = avail\r\n const contWidth = Math.max(500, avail + Math.min(0, geom.marginIntent))\r\n const contHorz = marginL - Math.min(0, geom.marginIntent)\r\n\r\n // 문단 paraPr의 breakSetting이 있으면 그 선언(어절/글자)을 따르고, 없으면 호출자 모드\r\n const paraMode = geom.wrapMode ?? mode\r\n // 폭 테이블: 지배 charPr 글꼴이 고정폭(굴림체류)이면 전용 클래스 — 함초롬 테이블(한글\r\n // 0.97em)로 재면 줄당 1~2자 과대적재로 wrap이 어긋난다 (seoul 코퍼스 3건 실측)\r\n const faceClass = faceClassOf(domChar.face)\r\n const wrap = text.length === 0\r\n ? { lines: 1, starts: [0], lastLineWidth: 0 }\r\n : simulateWrap(text, firstWidth, contWidth, height, ratio, paraMode, { spacingPct, faceClass })\r\n\r\n const pitch = pitchFor(height, geom)\r\n const baseline = Math.round(height * BASELINE_RATIO)\r\n const spacing = Math.max(0, pitch - height)\r\n\r\n // ── 개체(표·이미지) 세로 흐름 분류 (Phase 3 — 한컴 실측 모델, 36264961) ──\r\n // · float(treatAsChar=0) + TOP_AND_BOTTOM + PARA 앵커: 개체가 문단 상단을 차지하고\r\n // 텍스트는 outTop+개체높이+outBottom 아래부터 흐른다 (두문 결재표 실측:\r\n // 140+16653+852=17645 정확 일치)\r\n // · BEHIND_TEXT/IN_FRONT_OF_TEXT 또는 PAGE/PAPER 앵커: 본문 흐름에 불참\r\n // (페이지 하단 직인 스탬프가 커서를 밀던 것 제거)\r\n // · inline(treatAsChar=1): 줄 하나로 취급 — outMargin 포함 높이 + 줄 leading(spacing)\r\n // 만큼 전진 (실측: 표끝→다음 문단 간격 = 성장 포함 표높이 + spacing)\r\n let floatBelow = 0 // 텍스트 시작을 아래로 미는 float 개체 바닥 (문단 로컬)\r\n let objBottom = startV\r\n for (const o of m.objs) {\r\n const effH = o.tag === \"tbl\" ? Math.max(o.height, measureTableHeight(o.el)) : o.height\r\n const pos = findChildByLocalName(o.el, \"pos\")\r\n const om = findChildByLocalName(o.el, \"outMargin\")\r\n const outT = num(om, \"top\"), outB = num(om, \"bottom\")\r\n if (o.inline) {\r\n objBottom = Math.max(objBottom, startV + outT + effH + outB + spacing)\r\n continue\r\n }\r\n const wrapAttr = o.el.getAttribute(\"textWrap\") ?? \"\"\r\n if (wrapAttr === \"BEHIND_TEXT\" || wrapAttr === \"IN_FRONT_OF_TEXT\") continue\r\n const vertRel = pos?.getAttribute(\"vertRelTo\") ?? \"PARA\"\r\n if (vertRel === \"PAGE\" || vertRel === \"PAPER\") continue\r\n const vo = Math.max(0, num(pos, \"vertOffset\"))\r\n if (wrapAttr === \"TOP_AND_BOTTOM\") {\r\n floatBelow = Math.max(floatBelow, vo + outT + effH + outB)\r\n } else {\r\n objBottom = Math.max(objBottom, startV + vo + outT + effH + outB)\r\n }\r\n }\r\n const textStartV = startV + floatBelow\r\n\r\n const lsa = doc.createElement(\"hp:linesegarray\")\r\n for (let li = 0; li < wrap.starts.length; li++) {\r\n const startReal = wrap.starts[li]\r\n // 실텍스트 없는 문단(인라인 표·개체만)의 textpos는 0 — chars.length로 폴백하면\r\n // planLines의 plan.start가 개체 index보다 커져 advanceTo 가로 전진에서 개체가\r\n // 전부 빠지고 같은 x에 겹쳐 그려진다 (공문 결재란 라벨표∩스탬프표 겹침의 원인)\r\n const textpos = startReal < realIdx.length ? realIdx[startReal] : 0\r\n const vertpos = textStartV + li * pitch\r\n const isFirst = li === 0\r\n const seg = doc.createElement(\"hp:lineseg\")\r\n seg.setAttribute(\"textpos\", String(textpos))\r\n seg.setAttribute(\"vertpos\", String(vertpos))\r\n seg.setAttribute(\"vertsize\", String(height))\r\n seg.setAttribute(\"textheight\", String(height))\r\n seg.setAttribute(\"baseline\", String(baseline))\r\n seg.setAttribute(\"spacing\", String(spacing))\r\n seg.setAttribute(\"horzpos\", String(isFirst ? marginL : contHorz))\r\n seg.setAttribute(\"horzsize\", String(isFirst ? firstWidth : contWidth))\r\n seg.setAttribute(\"flags\", LINESEG_FLAGS)\r\n lsa.appendChild(seg)\r\n }\r\n p.appendChild(lsa)\r\n\r\n // 문단 바닥 = 텍스트 줄 흐름(float 개체 아래부터)과 개체 바닥 중 큰 쪽.\r\n // 표는 셀 콘텐츠 성장으로 선언 sz보다 커질 수 있어 drawTable과 같은 실효 높이를 쓴다\r\n // (셀 lineseg 필요 — reflowBlockFlow가 reflowTablesIn을 먼저 돌린다).\r\n const textBottom = textStartV + wrap.starts.length * pitch\r\n return { paraBottom: Math.max(textBottom, objBottom), spaceAfter: geom.spaceAfter }\r\n}\r\n\r\n/** 문단 run 안의 표를 찾아 각 셀 subList를 셀 로컬로 reflow (중첩 재귀) */\r\nfunction reflowTablesIn(p: Element, doc: Document, styles: RenderStyles, mode: WrapMode, counter: { n: number }): void {\r\n for (const run of elements(p)) {\r\n if (ln(run) !== \"run\") continue\r\n for (const obj of elements(run)) {\r\n if (ln(obj) !== \"tbl\") continue\r\n for (const tr of elements(obj)) {\r\n if (ln(tr) !== \"tr\") continue\r\n for (const tc of elements(tr)) {\r\n if (ln(tc) !== \"tc\") continue\r\n const csz = findChildByLocalName(tc, \"cellSz\")\r\n const cm = findChildByLocalName(tc, \"cellMargin\")\r\n const cellW = num(csz, \"width\")\r\n const mL = cm ? num(cm, \"left\", 141) : 141\r\n const mR = cm ? num(cm, \"right\", 141) : 141\r\n const areaW = Math.max(500, cellW - mL - mR)\r\n const sub = findChildByLocalName(tc, \"subList\")\r\n if (sub) reflowBlockFlow(sub, doc, styles, areaW, mode, counter, 0)\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 한 블록 컨테이너(본문 root 또는 셀 subList) 안 문단들을 세로 흐름으로 reflow.\r\n * @param bodyH 최상위(본문)일 때만 >0 — 문단 단위 자동 페이지 나눔(vertpos 페이지 로컬 리셋).\r\n * 셀 subList는 0(페이지 나눔 없음).\r\n */\r\nfunction reflowBlockFlow(\r\n container: Element,\r\n doc: Document,\r\n styles: RenderStyles,\r\n areaW: number,\r\n mode: WrapMode,\r\n counter: { n: number },\r\n bodyH: number,\r\n): void {\r\n let cursorV = 0\r\n let prevSpaceAfter = 0\r\n for (const p of elements(container)) {\r\n if (ln(p) !== \"p\") continue\r\n // 문단 안 표 셀을 먼저 셀 로컬 좌표로 reflow (본문 세로 흐름과 무관) —\r\n // reflowPara의 표 실효높이 측정이 셀 lineseg를 읽으므로 호스트 문단보다 앞서야 한다\r\n reflowTablesIn(p, doc, styles, mode, counter)\r\n const g = styles.paraGeom.get(p.getAttribute(\"paraPrIDRef\") ?? \"\")\r\n const startV = cursorV + prevSpaceAfter + (g?.spaceBefore ?? 0)\r\n const res = reflowPara(p, doc, styles, areaW, startV, mode)\r\n if (!res) {\r\n // 캐시 보유 문단(Tier-1) — 한컴 실좌표로 커서를 전진시킨다. 혼합 캐시 문서\r\n // (한컴 저장본을 프로그램 편집해 일부 문단만 캐시가 없는 경우)에서 캐시 없는\r\n // 문단이 흐름 위치 대신 0에 겹쳐 그려지던 결함 수정 (36264961 실측: 캐시 문단\r\n // p18 57165+1300+780 → 캐시 없는 p19가 정확히 59245에 붙는다).\r\n const lsa = findChildByLocalName(p, \"linesegarray\")\r\n if (lsa) {\r\n let bottom = Number.NEGATIVE_INFINITY\r\n for (const seg of elements(lsa)) {\r\n if (ln(seg) !== \"lineseg\") continue\r\n const th = Math.max(num(seg, \"vertsize\", 1000), num(seg, \"textheight\", 1000))\r\n bottom = Math.max(bottom, num(seg, \"vertpos\") + th + num(seg, \"spacing\"))\r\n }\r\n if (Number.isFinite(bottom)) {\r\n cursorV = bottom\r\n prevSpaceAfter = g?.spaceAfter ?? 0\r\n }\r\n }\r\n continue\r\n }\r\n const paraH = res.paraBottom - startV\r\n // 페이지 넘김: 문단이 현재 페이지를 넘치고, 문단 자체는 한 페이지에 들어가면 다음 페이지로.\r\n if (bodyH > 0 && startV > 0 && res.paraBottom > bodyH && paraH <= bodyH) {\r\n shiftParaVert(p, -startV) // 새 페이지 상단(로컬 0)으로 이동 → 프리패스가 vertpos 역행 감지\r\n cursorV = paraH\r\n } else {\r\n cursorV = res.paraBottom\r\n }\r\n prevSpaceAfter = res.spaceAfter\r\n counter.n++\r\n }\r\n}\r\n\r\n/**\r\n * section root의 조판 캐시 없는 문단에 linesegarray를 합성 주입한다(표 셀 재귀 포함).\r\n * 반환: 합성한 문단 수.\r\n * (Phase 2~3: 최상위 텍스트 + 표 셀 내부. 개체 밀어내기·자동 페이지 분할은 후속.)\r\n */\r\nexport function reflowSection(\r\n root: Element,\r\n styles: RenderStyles,\r\n geom: ReflowGeom,\r\n mode: WrapMode = \"keep\",\r\n): number {\r\n const doc = root.ownerDocument as unknown as Document\r\n const counter = { n: 0 }\r\n reflowBlockFlow(root, doc, styles, geom.BODY_W, mode, counter, geom.BODY_H)\r\n return counter.n\r\n}\r\n"],"mappings":"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","names":["charW","ln","elements","num"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/form/seal.ts","../src/roundtrip/source-map.ts","../src/roundtrip/zip-patch.ts"],"sourcesContent":["/**\r\n * place_seal — 도장/서명 이미지를 앵커 문구(\"(인)\"·\"서명 또는 인\" 등) 위에\r\n * 부유(글 앞) 배치. 표/페이지 불확장 (P6, claw-hwp placeSeal 이식).\r\n *\r\n * 원리:\r\n * - 앵커 문구가 든 문단을 찾아 그 문단에 <hp:pic> 부유 개체를 anchor 한다.\r\n * 핵심 속성: treatAsChar=\"0\" + flowWithText=\"0\" + allowOverlap=\"1\" +\r\n * textWrap=\"IN_FRONT_OF_TEXT\" — flowWithText=\"1\"이면 한컴이 개체 높이만큼\r\n * 셀/페이지를 키운다 (claw-hwp GT 검증 규칙).\r\n * - 수평 위치는 폰트 메트릭으로 계산: 전각(한글/CJK)=1em, ASCII·반각=0.5em,\r\n * em = 앵커 run charPr 높이(1/100pt). 렌더 없이 배치하고 렌더는 검증용.\r\n * - 가운데/오른쪽 정렬 문단(서식 셀에 흔함)은 블록 전체가 밀리므로\r\n * (사용가능폭 − 문단폭)의 정렬 이동분을 x에 더한다.\r\n * - 위치 프레임은 PARA(문단 좌상단 기준) — 한컴은 문단 위로는 못 올라가게\r\n * 클램프하므로, 여유 공간이 있는 표 셀에서는 줄 세로 중앙에 앉고 본문\r\n * 최상단 줄에서는 상단 정렬로 눕는다.\r\n *\r\n * 적용 범위: 본문/표 셀/글상자 문단 (머리말·꼬리말·각주 내 앵커는 제외).\r\n * ZIP 재조립은 patchZipEntries — 비변경 엔트리는 1바이트도 건드리지 않고,\r\n * 도장 PNG는 BinData 신규 엔트리 + manifest(opf:item) 등재로 추가한다.\r\n */\r\n\r\nimport JSZip from \"jszip\"\r\nimport { KordocError } from \"../utils.js\"\r\nimport {\r\n scanSectionXml, applySplices,\r\n type ScanParagraph, type ScanCell, type ScanTable, type SectionScan, type SpliceEdit,\r\n} from \"../roundtrip/source-map.js\"\r\nimport { patchZipEntries } from \"../roundtrip/zip-patch.js\"\r\nimport { parseRenderStyles } from \"../render/head-styles.js\"\r\n\r\nconst HU_PER_MM = 7200 / 25.4\r\nconst mm2hu = (mm: number): number => Math.round(mm * HU_PER_MM)\r\n\r\n/** 도장 배치 요청 */\r\nexport interface SealOp {\r\n /** 앵커 문구 (예: \"(인)\", \"서명 또는 인\") */\r\n anchor: string\r\n /** 같은 앵커가 여럿일 때 0-based 선택 (기본 0) */\r\n occurrence?: number\r\n /** 도장/서명 이미지 바이트 (투명 배경 PNG 권장) */\r\n image: Uint8Array\r\n /** 이미지 확장자 (기본 png) */\r\n ext?: \"png\" | \"jpg\" | \"jpeg\" | \"bmp\" | \"gif\"\r\n /** 도장 한 변 크기 mm (기본: 줄높이×1.6, 7~18mm 클램프) */\r\n sizeMm?: number\r\n /** overlap=문구 위에 겹침, right=문구 오른쪽 옆, auto=공간 있으면 right (기본 auto) */\r\n mode?: \"overlap\" | \"right\" | \"auto\"\r\n /** 미세조정 mm */\r\n dxMm?: number\r\n dyMm?: number\r\n}\r\n\r\n/** 배치 결과 (도장 1개당 1건) */\r\nexport interface SealPlacement {\r\n anchor: string\r\n occurrence: number\r\n sectionIndex: number\r\n mode: \"overlap\" | \"right\"\r\n posXMm: number\r\n posYMm: number\r\n sizeMm: number\r\n /** ZIP에 추가된 이미지 파트 경로 (BinData/imageN.ext) */\r\n entry: string\r\n /** 근사 한계 경고 — 탭·줄바꿈 문단(seal-8), 중첩표(seal-2), 글상자(seal-3). 실측 전 근사. */\r\n warnings?: string[]\r\n}\r\n\r\nexport interface PlaceSealResult {\r\n buffer: ArrayBuffer\r\n placed: SealPlacement[]\r\n}\r\n\r\nconst MIME: Record<string, string> = {\r\n png: \"image/png\",\r\n jpg: \"image/jpeg\",\r\n jpeg: \"image/jpeg\",\r\n bmp: \"image/bmp\",\r\n gif: \"image/gif\",\r\n}\r\n\r\n/** 이미지 매직바이트가 확장자와 일치하는지 — .png 이름의 비-PNG(HTML 에러페이지·텍스트)가\r\n * BinData 에 매장되고 image/png 로 등재되는 것을 차단 */\r\nfunction imageMagicMatches(buf: Uint8Array, ext: string): boolean {\r\n switch (ext) {\r\n case \"png\": return buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47\r\n case \"jpg\": case \"jpeg\": return buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff\r\n case \"bmp\": return buf.length >= 2 && buf[0] === 0x42 && buf[1] === 0x4d\r\n case \"gif\": return buf.length >= 6 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46\r\n default: return false\r\n }\r\n}\r\n\r\n/** 코드 유닛 하나의 시각 폭(em) — CJK/한글/전각=1, ASCII·반각(공백 포함)=0.5, 제어=0 */\r\nfunction glyphEm(code: number): number {\r\n if (code < 0x20) return 0\r\n if (code <= 0x7e) return 0.5\r\n if (code >= 0xff61 && code <= 0xffdc) return 0.5 // 반각 가나/자모\r\n return 1.0\r\n}\r\n\r\n/** 문자열의 폰트 메트릭 폭(mm) */\r\nfunction measureMm(text: string, emMm: number): number {\r\n let w = 0\r\n for (const ch of text) w += glyphEm(ch.codePointAt(0) ?? 0) * emMm\r\n return w\r\n}\r\n\r\n/** 문단이 속한 셀·표 (앵커 문단 → 셀 폭·좌측 오프셋·정렬 이동 계산용) */\r\ninterface ParaSite {\r\n para: ScanParagraph\r\n cell?: ScanCell\r\n table?: ScanTable\r\n /** 중첩표(depth>0) 셀에 속한 문단 — seal-2 근사 한계 경고용 */\r\n nested?: boolean\r\n /** 바깥(조상) 셀 체인 — 중첩표 앵커 가로 원점에 바깥 셀 좌측 오프셋 가산용 (seal-2) */\r\n outer?: Array<{ cell: ScanCell; table: ScanTable }>\r\n}\r\n\r\n/** 섹션의 본문+셀+글상자 문단을 문서 순서로 평탄화 (머리말·각주 등 excluded 제외) */\r\nfunction collectSites(scan: SectionScan): ParaSite[] {\r\n const sites: ParaSite[] = []\r\n for (const p of scan.bodyParagraphs) sites.push({ para: p })\r\n const walkTables = (tables: ScanTable[], depth: number, outer: Array<{ cell: ScanCell; table: ScanTable }>): void => {\r\n if (depth > 16) return\r\n for (const t of tables) {\r\n for (const row of t.rows) {\r\n for (const cell of row) {\r\n for (const p of cell.paragraphs) sites.push({ para: p, cell, table: t, nested: depth > 0, outer: outer.length ? outer : undefined })\r\n walkTables(cell.tables, depth + 1, [...outer, { cell, table: t }])\r\n }\r\n }\r\n }\r\n }\r\n walkTables(scan.tables, 0, [])\r\n walkTables(scan.orphanTables, 0, [])\r\n sites.sort((a, b) => a.para.start - b.para.start)\r\n return sites\r\n}\r\n\r\n/** 여는 태그 문자열에서 속성값 추출 */\r\nfunction attrOf(openTag: string, name: string): string | undefined {\r\n const m = openTag.match(new RegExp(`\\\\b${name}=\"([^\"]*)\"`))\r\n return m?.[1]\r\n}\r\n\r\n/** para.start의 <hp:p ...> 여는 태그 전체 */\r\nfunction paraOpenTag(xml: string, para: ScanParagraph): string {\r\n const end = xml.indexOf(\">\", para.start)\r\n return end === -1 ? \"\" : xml.slice(para.start, end + 1)\r\n}\r\n\r\n/** 앵커가 든 run의 charPrIDRef·네임스페이스 프리픽스·run 닫는 위치 */\r\nfunction anchorRunInfo(\r\n xml: string,\r\n para: ScanParagraph,\r\n anchor: string,\r\n): { charPr: string; prefix: string; insertAt: number } | null {\r\n // 앵커 문자열이 통째로 들어있는 tRange 우선, 없으면 마지막 tRange (run 경계에 걸친 앵커)\r\n let tr = para.tRanges.find(r => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(anchor))\r\n if (!tr) {\r\n // 앵커가 run 경계에 걸침(부분 굵게·엔티티 분할) — 마지막 run 이 아니라 앵커 첫 글자가\r\n // 든 run(앵커 대표 크기)으로 폴백해 글자 크기 배율 드리프트를 줄인다.\r\n const head = anchor[0]\r\n tr = para.tRanges.find(r => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(head))\r\n ?? para.tRanges[para.tRanges.length - 1]\r\n }\r\n if (!tr) return null\r\n\r\n const before = xml.slice(Math.max(0, para.start), tr.contentStart)\r\n const runOpen = [...before.matchAll(/<([A-Za-z0-9]+):run\\b[^>]*>/g)].pop()\r\n const prefix = runOpen?.[1] ?? tr.prefix ?? \"hp\"\r\n const charPr = (runOpen && attrOf(runOpen[0], \"charPrIDRef\")) || \"0\"\r\n const close = xml.indexOf(`</${prefix}:run>`, tr.contentEnd)\r\n if (close === -1) return null\r\n return { charPr, prefix, insertAt: close + `</${prefix}:run>`.length }\r\n}\r\n\r\n/** 셀 <hp:tc> 안의 cellAddr 근접 창 — cellSz/cellMargin 은 cellAddr 의 형제\r\n * (한컴 저장본: addr→span→sz→margin→subList, kordoc 생성본: subList→addr→span→sz→margin) */\r\nfunction cellAddrWindow(xml: string, cell: ScanCell): string | null {\r\n if (!cell.addrTagRange) return null\r\n return xml.slice(cell.addrTagRange.end, cell.addrTagRange.end + 400)\r\n}\r\n\r\n/** 셀의 cellSz 폭(hwpunit) */\r\nfunction cellSzWidthHu(xml: string, cell: ScanCell): number | null {\r\n const win = cellAddrWindow(xml, cell)\r\n if (win) {\r\n const m = win.match(/<[A-Za-z0-9]+:cellSz\\b[^>]*\\bwidth=\"(\\d+)\"/)\r\n if (m) return Number(m[1])\r\n }\r\n // 폴백: 첫 문단 앞 역탐색 (addrTagRange 없는 변형 — 한컴 저장본 배치에서 유효)\r\n const firstPara = cell.paragraphs[0]\r\n if (!firstPara) return null\r\n const upto = xml.slice(0, firstPara.start)\r\n const szMatch = [...upto.matchAll(/<[A-Za-z0-9]+:cellSz\\b[^>]*\\bwidth=\"(\\d+)\"[^>]*>/g)].pop()\r\n return szMatch ? Number(szMatch[1]) : null\r\n}\r\n\r\n/** 셀의 콘텐츠 폭(mm) — cellSz − cellMargin 좌우 */\r\nfunction cellContentWidthMm(xml: string, cell: ScanCell): number | null {\r\n const width = cellSzWidthHu(xml, cell)\r\n if (width === null) return null\r\n let content = width\r\n const win = cellAddrWindow(xml, cell)\r\n const mg = win?.match(/<[A-Za-z0-9]+:cellMargin\\b[^>]*>/)\r\n if (mg) {\r\n content -= Number(attrOf(mg[0], \"left\") ?? 0) + Number(attrOf(mg[0], \"right\") ?? 0)\r\n }\r\n return content > 0 ? content / HU_PER_MM : null\r\n}\r\n\r\n/**\r\n * 앵커 셀의 표 내 좌측 오프셋(mm) — 같은 행에서 colAddr 이 앞서는 셀들의 cellSz 폭 합.\r\n *\r\n * 한컴은 셀 문단에 anchor 된 front 부유(flowWithText=0) 개체의 가로 오프셋을\r\n * 셀이 아니라 **단(컬럼) 원점**에서 잰다(P1 실측 — PARA/COLUMN 동일). 그래서\r\n * 셀 내부 좌표에 셀의 좌측 오프셋을 더해야 화면상 앵커 문구를 따라간다.\r\n * 한계: rowSpan 이 얽힌 복잡 그리드·들여쓴 표는 근사가 어긋날 수 있다(dx 로 보정).\r\n */\r\nfunction cellLeftOffsetMm(xml: string, table: ScanTable, cell: ScanCell): number {\r\n if (cell.colAddr === undefined) return 0\r\n // 표 전체 행을 훑어 colAddr별 열폭 테이블을 구성한다 — rowSpan 으로 덮인 행의 <hp:tr>\r\n // 에는 선행 열 셀 엔트리가 없어(그 행만 보면 오프셋 0), 결재란 세로병합에서 도장이 한\r\n // 열(약 77mm) 왼쪽에 찍혔다. 다른 행에서 같은 colAddr 의 열폭을 조회해 면역시킨다.\r\n const colW = new Map<number, number>()\r\n for (const row of table.rows) {\r\n for (const c of row) {\r\n if (c.colAddr === undefined || colW.has(c.colAddr)) continue\r\n // colSpan>1 병합 셀은 폭이 여러 열에 걸쳐 있어 한 colAddr 에 귀속되지 않는다 — 단일\r\n // 열 셀로만 열폭을 확정해 가로 병합 제목행에서의 이중계상(도장 표 밖)을 막는다 (seal-1 colspan).\r\n if (c.colSpan > 1) continue\r\n const w = cellSzWidthHu(xml, c)\r\n if (w) colW.set(c.colAddr, w)\r\n }\r\n }\r\n let sum = 0\r\n for (let col = 0; col < cell.colAddr; col++) sum += colW.get(col) ?? 0\r\n return sum / HU_PER_MM\r\n}\r\n\r\n/** 본문 단 폭(mm) — secPr pagePr(용지 − 좌우 여백), 실패 시 150mm */\r\nfunction bodyColumnWidthMm(xml: string): number {\r\n const page = xml.match(/<[A-Za-z0-9]+:pagePr\\b[^>]*\\bwidth=\"(\\d+)\"[^>]*>/)\r\n const margin = xml.match(/<[A-Za-z0-9]+:margin\\b[^>]*\\bleft=\"(\\d+)\"[^>]*\\bright=\"(\\d+)\"[^>]*>/)\r\n if (!page) return 150\r\n const w = Number(page[1]) - (margin ? Number(margin[1]) + Number(margin[2]) : 0)\r\n return w > 0 ? w / HU_PER_MM : 150\r\n}\r\n\r\n/** 부유(글 앞) hp:pic XML — claw-hwp buildPic 폴백 템플릿의 float 변형 */\r\nfunction buildFloatPicXml(\r\n itemId: string,\r\n sizeHu: number,\r\n posXHu: number,\r\n posYHu: number,\r\n ids: { id: number; instid: number },\r\n): string {\r\n const w = sizeHu\r\n const h = sizeHu\r\n // xmlns:hc 자체 선언 — kordoc 생성 section0.xml 은 hc 프리픽스를 선언하지 않아\r\n // (한컴 저장본은 선언), pic 요소에 지역 선언해야 웰폼드가 유지된다 (중복 선언 무해).\r\n return (\r\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\">` +\r\n `<hp:offset x=\"0\" y=\"0\"/><hp:orgSz width=\"${w}\" height=\"${h}\"/><hp:curSz width=\"${w}\" height=\"${h}\"/>` +\r\n `<hp:flip horizontal=\"0\" vertical=\"0\"/><hp:rotationInfo angle=\"0\" centerX=\"${Math.round(w / 2)}\" centerY=\"${Math.round(h / 2)}\" rotateimage=\"1\"/>` +\r\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>` +\r\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>` +\r\n `<hp:imgClip left=\"0\" right=\"${w}\" top=\"0\" bottom=\"${h}\"/><hp:inMargin left=\"0\" right=\"0\" top=\"0\" bottom=\"0\"/>` +\r\n `<hp:imgDim dimwidth=\"${w}\" dimheight=\"${h}\"/>` +\r\n `<hc:img binaryItemIDRef=\"${itemId}\" bright=\"0\" contrast=\"0\" effect=\"REAL_PIC\" alpha=\"0\"/><hp:effects/>` +\r\n `<hp:sz width=\"${w}\" widthRelTo=\"ABSOLUTE\" height=\"${h}\" heightRelTo=\"ABSOLUTE\" protect=\"0\"/>` +\r\n // horzRelTo=COLUMN: 셀 안에서는 셀 콘텐츠 영역이 원점(한컴 실측 — v3.10 사진대지),\r\n // 본문에서는 단 왼쪽이 원점이라 PARA와 동일하게 동작한다. PARA를 쓰면 셀 내부\r\n // 개체의 가로 원점을 한컴이 표를 담은 바깥 문단으로 잡아 도장이 옆 셀로 밀린다\r\n // (P1 시각검증 실측). 세로는 PARA 유지 — 앵커 줄 기준 배치.\r\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}\"/>` +\r\n `<hp:outMargin left=\"0\" right=\"0\" top=\"0\" bottom=\"0\"/><hp:shapeComment>kordoc seal</hp:shapeComment>` +\r\n `</hp:pic>`\r\n )\r\n}\r\n\r\n/**\r\n * HWPX에 도장/서명 이미지를 앵커 문구 기준으로 부유 배치한다.\r\n *\r\n * @param hwpxBuffer 원본 HWPX\r\n * @param ops 도장 배치 요청 (여러 개 가능 — 같은 이미지 재사용 시에도 op마다 파트가 추가됨)\r\n * @throws KordocError 앵커 미발견 (본문 내 등장 횟수를 메시지에 포함)\r\n */\r\nexport async function placeSealHwpx(hwpxBuffer: ArrayBuffer, ops: SealOp[]): Promise<PlaceSealResult> {\r\n if (ops.length === 0) throw new KordocError(\"place_seal: 배치할 도장이 없습니다\")\r\n const u8 = new Uint8Array(hwpxBuffer)\r\n const zip = await JSZip.loadAsync(hwpxBuffer)\r\n\r\n const sectionPaths = Object.keys(zip.files)\r\n .filter(name => /[Ss]ection\\d+\\.xml$/i.test(name))\r\n // 사전순 대신 숫자순 — section10 이 section2 앞에 오면 occurrence·sectionIndex 가\r\n // 파서(zip-sections compareSectionPaths)의 문서 순서와 어긋나 엉뚱한 위치에 날인된다.\r\n .sort((a, b) => (Number(a.match(/(\\d+)\\.xml$/i)?.[1] ?? 0)) - (Number(b.match(/(\\d+)\\.xml$/i)?.[1] ?? 0)))\r\n if (sectionPaths.length === 0) throw new KordocError(\"HWPX에서 섹션 파일을 찾을 수 없습니다\")\r\n\r\n const manifestPath = Object.keys(zip.files).find(name => /\\.hpf$/i.test(name))\r\n\r\n // 스타일 (charPr 높이 = 1/100pt, 문단 정렬)\r\n const headerPath = Object.keys(zip.files).find(name => /(^|\\/)header\\.xml$/i.test(name))\r\n const styles = headerPath ? parseRenderStyles(await zip.file(headerPath)!.async(\"text\")) : null\r\n\r\n const sectionXmls: string[] = []\r\n const scans: SectionScan[] = []\r\n for (let si = 0; si < sectionPaths.length; si++) {\r\n const xml = await zip.file(sectionPaths[si])!.async(\"text\")\r\n sectionXmls.push(xml)\r\n scans.push(scanSectionXml(xml, si))\r\n }\r\n const sitesBySection = scans.map(collectSites)\r\n\r\n // 기존 BinData 번호·manifest id와 충돌하지 않는 시작 번호\r\n const usedIds = new Set<string>()\r\n let manifestXml = \"\"\r\n if (manifestPath) {\r\n manifestXml = await zip.file(manifestPath)!.async(\"text\")\r\n for (const m of manifestXml.matchAll(/<opf:item\\b[^>]*\\bid=\"([^\"]+)\"/g)) usedIds.add(m[1])\r\n }\r\n const usedImageNums = new Set<number>()\r\n for (const name of Object.keys(zip.files)) {\r\n const m = name.match(/^BinData\\/(?:image|img)(\\d+)\\./i)\r\n if (m) usedImageNums.add(Number(m[1]))\r\n }\r\n const nextImageNum = (): number => {\r\n let n = 1\r\n while (usedImageNums.has(n) || usedIds.has(`image${n}`)) n++\r\n usedImageNums.add(n)\r\n return n\r\n }\r\n\r\n // 개체 id — 문서 내 기존 숫자 id 최댓값 다음부터 (충돌 방지·결정적)\r\n let maxId = 1_000_000\r\n for (const xml of sectionXmls) {\r\n for (const m of xml.matchAll(/\\b(?:id|instid)=\"(\\d+)\"/g)) {\r\n const v = Number(m[1])\r\n if (Number.isFinite(v) && v > maxId) maxId = v\r\n }\r\n }\r\n\r\n const splicesBySection: SpliceEdit[][] = sectionPaths.map(() => [])\r\n const additions = new Map<string, Uint8Array>()\r\n const manifestItems: string[] = []\r\n const placed: SealPlacement[] = []\r\n\r\n for (const op of ops) {\r\n if (!op.anchor) throw new KordocError(\"place_seal: anchor 문구가 필요합니다\")\r\n if (!op.image || op.image.length === 0) throw new KordocError(\"place_seal: 도장 이미지가 필요합니다\")\r\n const ext = (op.ext ?? \"png\").toLowerCase()\r\n if (!MIME[ext]) throw new KordocError(`place_seal: 지원하지 않는 이미지 확장자 .${ext} (png/jpg/bmp/gif)`)\r\n if (!imageMagicMatches(op.image, ext)) throw new KordocError(`place_seal: 이미지 내용이 .${ext} 형식이 아닙니다 (매직바이트 불일치)`)\r\n const wantOcc = op.occurrence ?? 0\r\n\r\n // 앵커 탐색 — 전 섹션 문서 순서, 문단 내 다중 등장 포함\r\n let found: { si: number; site: ParaSite; idxInText: number } | null = null\r\n let total = 0\r\n for (let si = 0; si < scans.length && !found; si++) {\r\n for (const site of sitesBySection[si]) {\r\n let from = 0\r\n for (;;) {\r\n const idx = site.para.text.indexOf(op.anchor, from)\r\n if (idx === -1) break\r\n if (total === wantOcc) {\r\n found = { si, site, idxInText: idx }\r\n break\r\n }\r\n total++\r\n from = idx + op.anchor.length\r\n }\r\n if (found) break\r\n }\r\n }\r\n if (!found) {\r\n // 전체 등장 수를 처음부터 다시 세어 에러 메시지에 (claw-hwp와 동일한 안내)\r\n total = 0\r\n for (let si = 0; si < scans.length; si++) {\r\n for (const site of sitesBySection[si]) {\r\n let from = 0\r\n for (;;) {\r\n const idx = site.para.text.indexOf(op.anchor, from)\r\n if (idx === -1) break\r\n total++\r\n from = idx + op.anchor.length\r\n }\r\n }\r\n }\r\n throw new KordocError(\r\n `place_seal: 앵커 \"${op.anchor}\" ${wantOcc}번째 등장을 찾지 못했습니다 ` +\r\n `(본문 내 ${total}회 등장 — occurrence 0..${Math.max(0, total - 1)})`,\r\n )\r\n }\r\n\r\n const { si, site, idxInText } = found\r\n const xml = sectionXmls[si]\r\n const run = anchorRunInfo(xml, site.para, op.anchor)\r\n if (!run) {\r\n throw new KordocError(`place_seal: 앵커 \"${op.anchor}\" 문단에서 run을 찾지 못했습니다`)\r\n }\r\n\r\n // 폰트 메트릭\r\n const charPrHeight = styles?.charPr.get(run.charPr)?.height ?? 1000 // 1/100pt\r\n const fontPt = charPrHeight / 100\r\n const emMm = (fontPt * 25.4) / 72\r\n const lineHMm = emMm\r\n const startXMm = measureMm(site.para.text.slice(0, idxInText), emMm)\r\n const anchorWMm = measureMm(op.anchor, emMm)\r\n // NaN·음수 sizeMm(CLI 비숫자·직접 호출)은 기본 크기로 폴백 — hp:sz width=\"NaN\" 등\r\n // OWPML 정수 속성에 NaN 문자열이 기록되던 것 원천 차단 (seal-4)\r\n const sizeMm = (op.sizeMm != null && Number.isFinite(op.sizeMm) && op.sizeMm > 0)\r\n ? op.sizeMm\r\n : Math.max(7, Math.min(18, lineHMm * 1.6))\r\n\r\n // 사용가능 폭 + 정렬 이동\r\n const availMm = (site.cell ? cellContentWidthMm(xml, site.cell) : null) ?? bodyColumnWidthMm(xml)\r\n const paraPrId = attrOf(paraOpenTag(xml, site.para), \"paraPrIDRef\") ?? \"0\"\r\n const align = styles?.paraAlign.get(paraPrId)\r\n let alignShiftMm = 0\r\n if (align === \"CENTER\" || align === \"RIGHT\") {\r\n const paraWMm = measureMm(site.para.text, emMm)\r\n alignShiftMm = align === \"CENTER\" ? (availMm - paraWMm) / 2 : availMm - paraWMm\r\n if (!Number.isFinite(alignShiftMm) || alignShiftMm < 0) alignShiftMm = 0\r\n }\r\n\r\n let mode = op.mode ?? \"auto\"\r\n if (mode === \"auto\") {\r\n mode = availMm - (alignShiftMm + startXMm + anchorWMm) >= sizeMm + 2 ? \"right\" : \"overlap\"\r\n }\r\n\r\n // 셀 앵커: 한컴이 front 부유 가로 오프셋을 단(컬럼) 원점에서 재므로 셀 좌측 오프셋 가산.\r\n // 중첩표는 바깥(조상) 셀 좌측 오프셋도 체인으로 더한다 — 실측 확정: 바깥 셀 미가산 시\r\n // 도장이 바깥 열 폭만큼 왼쪽(옆 셀)으로 밀린다 (seal-2, '항상 페이지 단' 모델).\r\n const cellShiftMm = site.cell && site.table\r\n ? cellLeftOffsetMm(xml, site.table, site.cell) +\r\n (site.outer ?? []).reduce((s, o) => s + cellLeftOffsetMm(xml, o.table, o.cell), 0)\r\n : 0\r\n const posXMm =\r\n (mode === \"right\" ? startXMm + anchorWMm + 2 : startXMm + anchorWMm / 2 - sizeMm / 2) +\r\n alignShiftMm + cellShiftMm + (op.dxMm ?? 0)\r\n // 줄 세로 중앙 — PARA 프레임은 문단 위로 클램프되므로 본문 최상단 줄에선 상단 정렬로 동작\r\n const posYMm = -(sizeMm - lineHMm) / 2 + (op.dyMm ?? 0)\r\n\r\n // 이미지 파트 + manifest 아이템\r\n const n = nextImageNum()\r\n const entry = `BinData/image${n}.${ext}`\r\n const itemId = `image${n}`\r\n usedIds.add(itemId)\r\n additions.set(entry, op.image)\r\n manifestItems.push(`<opf:item id=\"${itemId}\" href=\"${entry}\" media-type=\"${MIME[ext]}\" isEmbeded=\"1\"/>`)\r\n\r\n // 부유 pic run 삽입 (앵커 run 뒤 — PARA 프레임이라 run 위치는 배치에 영향 없음)\r\n const pic = buildFloatPicXml(itemId, mm2hu(sizeMm), mm2hu(posXMm), mm2hu(posYMm), {\r\n id: ++maxId,\r\n instid: ++maxId,\r\n })\r\n splicesBySection[si].push({\r\n start: run.insertAt,\r\n end: run.insertAt,\r\n replacement: `<${run.prefix}:run charPrIDRef=\"${run.charPr}\">${pic}</${run.prefix}:run>`,\r\n })\r\n\r\n // 근사 한계 경고 — 메트릭·원점 모델이 근사인 케이스를 결과에 명시(placed 소비자가 dx/dy 보정)\r\n const warnings: string[] = []\r\n const paraSeg = xml.slice(site.para.start, run.insertAt)\r\n if (/<[A-Za-z0-9]+:tab\\b/.test(paraSeg)) warnings.push(\"탭이 든 문단 — 도장 가로 위치가 근사값입니다 (dx 로 보정)\")\r\n if (/<[A-Za-z0-9]+:(lineBreak|br)\\b/.test(paraSeg)) warnings.push(\"줄바꿈이 든 문단 — 앵커가 2번째 줄 이하면 세로 위치가 어긋날 수 있습니다 (dy 로 보정)\")\r\n if (site.nested) warnings.push(\"중첩표(표 안 표) 셀 앵커 — 바깥 셀 오프셋을 체인으로 가산하나 셀 여백·정렬은 근사입니다 (필요 시 dx 로 보정)\")\r\n if (site.para.inTextbox) warnings.push(\"글상자 앵커 — 글상자 기하를 반영하지 않아 정렬 보정이 근사입니다 (dx/dy 로 보정)\")\r\n\r\n placed.push({\r\n anchor: op.anchor,\r\n occurrence: wantOcc,\r\n sectionIndex: si,\r\n mode,\r\n posXMm: Math.round(posXMm * 100) / 100,\r\n posYMm: Math.round(posYMm * 100) / 100,\r\n sizeMm: Math.round(sizeMm * 100) / 100,\r\n entry,\r\n ...(warnings.length > 0 ? { warnings } : {}),\r\n })\r\n }\r\n\r\n // 재조립 — 변경 섹션 + manifest 교체, 이미지 파트 추가\r\n const encoder = new TextEncoder()\r\n const replacements = new Map<string, Uint8Array>()\r\n for (let si = 0; si < sectionPaths.length; si++) {\r\n if (splicesBySection[si].length === 0) continue\r\n replacements.set(sectionPaths[si], encoder.encode(applySplices(sectionXmls[si], splicesBySection[si])))\r\n }\r\n if (manifestPath && manifestItems.length > 0) {\r\n const patched = manifestXml.includes(\"</opf:manifest>\")\r\n ? manifestXml.replace(\"</opf:manifest>\", `${manifestItems.join(\"\")}</opf:manifest>`)\r\n : manifestXml\r\n if (patched !== manifestXml) replacements.set(manifestPath, encoder.encode(patched))\r\n }\r\n\r\n const out = patchZipEntries(u8, replacements, additions)\r\n return {\r\n buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength) as ArrayBuffer,\r\n placed,\r\n }\r\n}\r\n","/**\r\n * HWPX 라운드트립 소스맵 — section XML 문자열에서 문단/표 셀의 문자 범위를 추적.\r\n *\r\n * DOM 재직렬화를 거치지 않고(속성 순서·공백·자기닫힘 표기까지 보존하기 위해)\r\n * 정규식 토크나이저로 태그를 순회하며, 각 <hp:p>가 소유한 <hp:t> 콘텐츠 범위를\r\n * 기록한다. 패치는 이 범위에 대한 문자열 splice로만 수행되어\r\n * 변경 문단 외 XML 바이트가 그대로 보존된다 (filler-hwpx.ts 패턴의 오프셋 버전).\r\n */\r\n\r\n// ─── 타입 ───────────────────────────────────────────\r\n\r\n/** <hp:t> 콘텐츠 범위 — [contentStart, contentEnd) 가 여는/닫는 태그 사이 */\r\nexport interface TRange {\r\n contentStart: number\r\n contentEnd: number\r\n /** 자기닫힘 <hp:t/> — 범위가 태그 전체이며, 치환 시 태그째 교체 */\r\n selfClosing?: boolean\r\n /** 자기닫힘 치환용 네임스페이스 프리픽스 (예: \"hp\") */\r\n prefix?: string\r\n}\r\n\r\nexport type ScanParaKind = \"body\" | \"cell\" | \"draw\" | \"excluded\"\r\n\r\n/** 스캔된 문단 — 소유한 hp:t 범위와 합산 텍스트 */\r\nexport interface ScanParagraph {\r\n sectionIndex: number\r\n kind: ScanParaKind\r\n /** <hp:p ...> 여는 태그 시작 위치 (문서 순서 정렬용) */\r\n start: number\r\n /** 소유 hp:t 콘텐츠 범위 (문서 순서) */\r\n tRanges: TRange[]\r\n /** hp:t 텍스트 합산 (엔티티 디코딩, 내부 태그는 공백화) */\r\n text: string\r\n /** hp:t가 하나도 없을 때 텍스트 삽입 가능한 위치 (첫 run 닫는 태그 앞) */\r\n runInsertPos?: number\r\n /** runInsertPos에 삽입할 때 쓸 t 태그 prefix (예: \"hp\") */\r\n runPrefix?: string\r\n /** 자기닫힘 <hp:run/> 태그 범위 — t 삽입 시 펼쳐서 사용 (한컴 빈 문단 패턴) */\r\n selfCloseRun?: { start: number; end: number }\r\n /** 글상자(drawText) 경유 문단 — 셀 라벨 판정 등에서 본문과 구분 (v3.1) */\r\n inTextbox?: boolean\r\n}\r\n\r\n/** 스캔된 표 셀 — 앵커 좌표와 셀 내부 문단 */\r\nexport interface ScanCell {\r\n rowAddr?: number\r\n colAddr?: number\r\n colSpan: number\r\n rowSpan: number\r\n paragraphs: ScanParagraph[]\r\n /** 셀 내부 중첩표 (문서 순서) */\r\n tables: ScanTable[]\r\n /** <hp:cellAddr> 태그 범위 (자기닫힘=태그 전체, 펼친형=여는 태그) — rowAddr 재작성용 */\r\n addrTagRange?: { start: number; end: number }\r\n}\r\n\r\n/** 스캔된 표 */\r\nexport interface ScanTable {\r\n sectionIndex: number\r\n start: number\r\n /** 최상위 표 여부 (다른 표/ctrl/캡션 내부가 아님) */\r\n topLevel: boolean\r\n /** 비어있지 않은 행들 (tr 순서) */\r\n rows: ScanCell[][]\r\n /** rows[i]의 <hp:tr>...</hp:tr> XML 범위 (행 추가/삭제 splice용, rows와 정렬) */\r\n rowRanges: { start: number; end: number }[]\r\n /** 앵커 좌표 → 셀 (\"r,c\") */\r\n cellByAnchor: Map<string, ScanCell>\r\n}\r\n\r\n/** 섹션 하나의 스캔 결과 */\r\nexport interface SectionScan {\r\n sectionIndex: number\r\n xml: string\r\n /** body + draw 문단 (문서 순서) — 본문 텍스트 매핑 대상 */\r\n bodyParagraphs: ScanParagraph[]\r\n /** 최상위 표 (문서 순서) */\r\n tables: ScanTable[]\r\n /** 머리말/꼬리말 문단 텍스트 (OB 앞/뒤 배치 블록 식별용) */\r\n headerTexts: string[]\r\n footerTexts: string[]\r\n /**\r\n * 파서 비가시 영역(머리말/꼬리말/각주/캡션/메모 등 ctrl 내부) 문단 — v3.1.\r\n * patcher 매핑 대상이 아니며, filler가 이 영역의 인라인 패턴을 채울 때 사용.\r\n */\r\n excludedParagraphs: ScanParagraph[]\r\n /**\r\n * 어느 셀에도 속하지 않는 비최상위 표(머리말/꼬리말 등 ctrl 내부) — v3.1.\r\n * filler 전용. patcher의 표 서수 매핑(tables)에는 포함되지 않는다.\r\n */\r\n orphanTables: ScanTable[]\r\n}\r\n\r\n/** 문자열 splice 편집 — [start, end) 를 replacement로 치환 */\r\nexport interface SpliceEdit {\r\n start: number\r\n end: number\r\n replacement: string\r\n}\r\n\r\n// ─── XML 텍스트 유틸 ─────────────────────────────────\r\n\r\n/** XML 텍스트 노드 이스케이프 (속성 아님 — & < > 만) */\r\nexport function escapeXmlText(text: string): string {\r\n return text.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\r\n}\r\n\r\n/** XML 엔티티 디코딩 */\r\nexport function decodeXmlEntities(text: string): string {\r\n return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent: string) => {\r\n switch (ent) {\r\n case \"lt\": return \"<\"\r\n case \"gt\": return \">\"\r\n case \"amp\": return \"&\"\r\n case \"quot\": return '\"'\r\n case \"apos\": return \"'\"\r\n }\r\n try {\r\n const code = ent[1] === \"x\" || ent[1] === \"X\"\r\n ? parseInt(ent.slice(2), 16)\r\n : parseInt(ent.slice(1), 10)\r\n if (!isNaN(code) && code >= 0 && code <= 0x10ffff) return String.fromCodePoint(code)\r\n } catch { /* 잘못된 참조는 원본 유지 */ }\r\n return m\r\n })\r\n}\r\n\r\n/** hp:t 콘텐츠에서 텍스트 추출 — 파서가 공백류로 취급하는 태그(tab/fwSpace/hwSpace/\r\n * br/lineBreak)만 공백, 나머지 태그(markpen 등 파서가 무시)는 제거 (파서 모델 정합) */\r\nfunction tContentToText(raw: string): string {\r\n return decodeXmlEntities(\r\n raw\r\n .replace(/<\\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\\s[^>]*)?\\/?>/g, \" \")\r\n .replace(/<[^>]*>/g, \"\"),\r\n )\r\n}\r\n\r\n// ─── 토크나이저 ──────────────────────────────────────\r\n\r\nconst TAG_RE = /<!--[\\s\\S]*?-->|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|<\\?[\\s\\S]*?\\?>|<!(?:\"[^\"]*\"|'[^']*'|[^>\"'])*>|<\\/([^\\s>]+)\\s*>|<([^\\s/>!?]+)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*?)(\\/?)>/g\r\n\r\n/** hp:t 소유권을 끊는 컨테이너 — 이 안의 t는 호스트 문단 텍스트가 아님 */\r\nconst T_BARRIER = new Set([\r\n \"tbl\", \"ctrl\", \"caption\", \"pic\", \"shape\", \"drawingObject\", \"drawText\",\r\n \"shapeComment\", \"memogroup\", \"memo\", \"hiddenComment\", \"equation\",\r\n \"parameters\", \"subList\", \"p\",\r\n])\r\n\r\n/** 문단 분류용 컨테이너 (가장 가까운 것 기준) */\r\nconst PARA_CONTAINER = new Set([\r\n \"tc\", \"ctrl\", \"caption\", \"drawText\", \"pic\", \"shape\", \"drawingObject\",\r\n \"memogroup\", \"memo\", \"hiddenComment\",\r\n \"footNote\", \"endNote\", \"fn\", \"en\", // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수\r\n])\r\n\r\n/** 표 분류 시 \"최상위\"가 아니게 만드는 조상 */\r\nconst TABLE_BARRIER = new Set([\r\n \"tbl\", \"ctrl\", \"caption\", \"memogroup\", \"memo\", \"hiddenComment\",\r\n])\r\n\r\nfunction localOf(qname: string): string {\r\n const i = qname.indexOf(\":\")\r\n return i >= 0 ? qname.slice(i + 1) : qname\r\n}\r\n\r\nfunction prefixOf(qname: string): string {\r\n const i = qname.indexOf(\":\")\r\n return i >= 0 ? qname.slice(0, i) : \"\"\r\n}\r\n\r\ninterface StackFrame {\r\n local: string\r\n qname: string\r\n /** 여는 태그가 끝나는 위치 (콘텐츠 시작) */\r\n contentStart: number\r\n}\r\n\r\ninterface OpenCtrlSub { kind: \"header\" | \"footer\"; texts: string[] }\r\n\r\n/**\r\n * section XML 한 개를 스캔하여 소스맵을 만든다.\r\n * 토크나이저는 xmldom과 무관하게 동작하며, 위치(오프셋) 정보가 핵심이다.\r\n */\r\nexport function scanSectionXml(xml: string, sectionIndex: number): SectionScan {\r\n const stack: StackFrame[] = []\r\n const bodyParagraphs: ScanParagraph[] = []\r\n const tables: ScanTable[] = []\r\n const headerTexts: string[] = []\r\n const footerTexts: string[] = []\r\n const excludedParagraphs: ScanParagraph[] = []\r\n const orphanTables: ScanTable[] = []\r\n\r\n // 진행 상태\r\n const paraStack: ScanParagraph[] = []\r\n const tableStack: ScanTable[] = []\r\n const rowStack: ScanCell[][] = [] // 테이블별 currentRow\r\n const trStartStack: number[] = [] // 테이블별 진행 중 <hp:tr> 시작 위치 (-1 = 없음)\r\n const cellStack: ScanCell[] = []\r\n let pendingT: { para: ScanParagraph; contentStart: number } | null = null\r\n const ctrlSubStack: OpenCtrlSub[] = []\r\n\r\n /** 스택 위에서 아래로 보며 가장 가까운 PARA_CONTAINER/장벽 판별.\r\n * drawText는 투과 — 바깥에 tc가 있으면(셀 안 글상자) 파서의 mergeBlocksIntoCell과\r\n * 동일하게 cell로 귀속, 아니면 draw(본문 글상자). */\r\n const classifyPara = (): { kind: ScanParaKind; inTextbox: boolean } => {\r\n let sawDrawText = false\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n const l = stack[i].local\r\n if (l === \"tc\") return { kind: \"cell\", inTextbox: sawDrawText }\r\n if (l === \"drawText\") { sawDrawText = true; continue }\r\n if (PARA_CONTAINER.has(l)) return { kind: \"excluded\", inTextbox: sawDrawText }\r\n }\r\n return sawDrawText ? { kind: \"draw\", inTextbox: true } : { kind: \"body\", inTextbox: false }\r\n }\r\n\r\n /** 현재 위치의 t/tab 등이 최상위 열린 문단 소유인지 (사이에 장벽 없음) */\r\n const owningPara = (): ScanParagraph | null => {\r\n if (paraStack.length === 0) return null\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n const l = stack[i].local\r\n if (l === \"p\") return paraStack[paraStack.length - 1]\r\n if (T_BARRIER.has(l)) return null\r\n }\r\n return null\r\n }\r\n\r\n const isTableTopLevel = (): boolean => {\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n if (TABLE_BARRIER.has(stack[i].local)) return false\r\n }\r\n return true\r\n }\r\n\r\n /** ctrl 내부 header/footer 수집 컨텍스트 */\r\n const currentCtrlSub = (): OpenCtrlSub | null =>\r\n ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null\r\n\r\n TAG_RE.lastIndex = 0\r\n let m: RegExpExecArray | null\r\n while ((m = TAG_RE.exec(xml)) !== null) {\r\n const [full, closeName, openName, , selfClose] = m\r\n\r\n // 주석/CDATA/PI/DOCTYPE\r\n if (closeName === undefined && openName === undefined) continue\r\n\r\n if (closeName !== undefined) {\r\n // ── 닫는 태그 ──\r\n const local = localOf(closeName)\r\n\r\n // 진행 중인 hp:t 닫힘\r\n if (local === \"t\" && pendingT) {\r\n const { para, contentStart } = pendingT\r\n para.tRanges.push({ contentStart, contentEnd: m.index })\r\n para.text += tContentToText(xml.slice(contentStart, m.index))\r\n pendingT = null\r\n }\r\n\r\n // 스택 pop (불일치 태그 관용 처리 — 일치하는 프레임까지 pop)\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n if (stack[i].local === local) {\r\n stack.length = i\r\n break\r\n }\r\n }\r\n\r\n if (local === \"p\") {\r\n const para = paraStack.pop()\r\n if (para && para.kind === \"excluded\") {\r\n const sub = currentCtrlSub()\r\n if (sub && para.text.trim()) sub.texts.push(para.text)\r\n }\r\n } else if (local === \"tc\") {\r\n const cell = cellStack.pop()\r\n const row = rowStack[rowStack.length - 1]\r\n if (cell && row) row.push(cell)\r\n } else if (local === \"tr\") {\r\n const row = rowStack[rowStack.length - 1]\r\n const table = tableStack[tableStack.length - 1]\r\n if (row && table && row.length > 0) {\r\n table.rows.push(row)\r\n const trStart = trStartStack[trStartStack.length - 1]\r\n if (trStart >= 0) table.rowRanges.push({ start: trStart, end: m.index + full.length })\r\n }\r\n if (rowStack.length > 0) rowStack[rowStack.length - 1] = []\r\n if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = -1\r\n } else if (local === \"tbl\") {\r\n const table = tableStack.pop()\r\n rowStack.pop()\r\n trStartStack.pop()\r\n if (table) {\r\n finalizeTable(table)\r\n // 중첩표는 둘러싼 셀에 부착 (재귀 패치용), 셀이 없으면(머리말 등\r\n // ctrl 내부) orphanTables로 — filler가 채우기 대상에 포함 (v3.1)\r\n if (!table.topLevel) {\r\n const cell = cellStack[cellStack.length - 1]\r\n if (cell) cell.tables.push(table)\r\n else orphanTables.push(table)\r\n }\r\n }\r\n } else if (local === \"header\" || local === \"footer\") {\r\n const sub = ctrlSubStack[ctrlSubStack.length - 1]\r\n if (sub) {\r\n ctrlSubStack.pop()\r\n // 파서(collectSubListText)는 문단들을 \"\\n\"으로 합쳐 한 블록으로 만든다\r\n const joined = sub.texts.join(\"\\n\").trim()\r\n if (joined) (sub.kind === \"header\" ? headerTexts : footerTexts).push(joined)\r\n }\r\n }\r\n continue\r\n }\r\n\r\n // ── 여는 태그 ──\r\n const qname = openName!\r\n const local = localOf(qname)\r\n const attrsRaw = m[3] || \"\"\r\n const isSelfClose = selfClose === \"/\"\r\n const contentStart = m.index + full.length\r\n\r\n if (isSelfClose) {\r\n // 자기닫힘 요소 처리\r\n if (local === \"t\") {\r\n const para = owningPara()\r\n if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) })\r\n } else if (local === \"tab\" || local === \"fwSpace\" || local === \"hwSpace\" || local === \"br\" || local === \"lineBreak\") {\r\n // pendingT 내부면 t 콘텐츠 추출이 공백 처리하므로 중복 방지\r\n if (!pendingT) {\r\n const para = owningPara()\r\n if (para) para.text += \" \"\r\n }\r\n } else if (local === \"run\" || local === \"r\") {\r\n // 한컴 빈 문단 패턴: <hp:run charPrIDRef=\"...\"/> — t 삽입 시 펼칠 수 있도록 기록\r\n const para = owningPara()\r\n if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length }\r\n } else if (local === \"cellAddr\") {\r\n const cell = cellStack[cellStack.length - 1]\r\n if (cell && insideCurrentTable(stack, tableStack)) {\r\n const ca = parseInt(getAttr(attrsRaw, \"colAddr\") || \"\", 10)\r\n const ra = parseInt(getAttr(attrsRaw, \"rowAddr\") || \"\", 10)\r\n if (!isNaN(ca)) cell.colAddr = ca\r\n if (!isNaN(ra)) cell.rowAddr = ra\r\n cell.addrTagRange = { start: m.index, end: m.index + full.length }\r\n }\r\n } else if (local === \"cellSpan\") {\r\n const cell = cellStack[cellStack.length - 1]\r\n if (cell && insideCurrentTable(stack, tableStack)) {\r\n const cs = parseInt(getAttr(attrsRaw, \"colSpan\") || \"1\", 10)\r\n const rs = parseInt(getAttr(attrsRaw, \"rowSpan\") || \"1\", 10)\r\n cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs\r\n cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs\r\n }\r\n }\r\n continue\r\n }\r\n\r\n // 일반 여는 태그 — 스택 push 전 처리\r\n if (local === \"t\") {\r\n const para = owningPara()\r\n if (para) pendingT = { para, contentStart }\r\n stack.push({ local, qname, contentStart })\r\n continue\r\n }\r\n\r\n stack.push({ local, qname, contentStart })\r\n\r\n if (local === \"p\") {\r\n const para: ScanParagraph = {\r\n sectionIndex,\r\n kind: \"excluded\", // 분류는 push 직후 스택 기준 (자기 자신 제외)\r\n start: m.index,\r\n tRanges: [],\r\n text: \"\",\r\n }\r\n // 자기 자신(p)을 제외하고 분류\r\n stack.pop()\r\n const cls = classifyPara()\r\n para.kind = cls.kind\r\n if (cls.inTextbox) para.inTextbox = true\r\n stack.push({ local, qname, contentStart })\r\n paraStack.push(para)\r\n if (para.kind === \"body\" || para.kind === \"draw\") bodyParagraphs.push(para)\r\n else if (para.kind === \"cell\") {\r\n const cell = cellStack[cellStack.length - 1]\r\n if (cell) cell.paragraphs.push(para)\r\n } else if (para.kind === \"excluded\") {\r\n excludedParagraphs.push(para)\r\n }\r\n } else if (local === \"run\" || local === \"r\") {\r\n // hp:t가 없는 문단의 삽입 지점 후보 — 첫 run의 닫는 태그 위치는 닫힐 때 알 수 있으므로\r\n // 여기서는 prefix만 기억해두고, 닫는 태그에서 채움 (아래 참조)\r\n const para = owningPara()\r\n if (para && para.runPrefix === undefined) para.runPrefix = prefixOf(qname)\r\n } else if (local === \"tbl\") {\r\n const table: ScanTable = {\r\n sectionIndex,\r\n start: m.index,\r\n topLevel: false,\r\n rows: [],\r\n rowRanges: [],\r\n cellByAnchor: new Map(),\r\n }\r\n // 자기 자신 제외하고 판별\r\n stack.pop()\r\n table.topLevel = isTableTopLevel()\r\n stack.push({ local, qname, contentStart })\r\n tableStack.push(table)\r\n rowStack.push([])\r\n trStartStack.push(-1)\r\n if (table.topLevel) tables.push(table)\r\n } else if (local === \"tr\") {\r\n if (rowStack.length > 0) rowStack[rowStack.length - 1] = []\r\n if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = m.index\r\n } else if (local === \"tc\") {\r\n cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] })\r\n } else if (local === \"cellAddr\" || local === \"cellSpan\") {\r\n // 자기닫힘이 일반적이지만 펼친 형태(<hp:cellAddr ...></hp:cellAddr>)도 동일 처리\r\n const cell = cellStack[cellStack.length - 1]\r\n if (cell && insideCurrentTable(stack, tableStack)) {\r\n if (local === \"cellAddr\") {\r\n const ca = parseInt(getAttr(attrsRaw, \"colAddr\") || \"\", 10)\r\n const ra = parseInt(getAttr(attrsRaw, \"rowAddr\") || \"\", 10)\r\n if (!isNaN(ca)) cell.colAddr = ca\r\n if (!isNaN(ra)) cell.rowAddr = ra\r\n cell.addrTagRange = { start: m.index, end: contentStart }\r\n } else {\r\n const cs = parseInt(getAttr(attrsRaw, \"colSpan\") || \"1\", 10)\r\n const rs = parseInt(getAttr(attrsRaw, \"rowSpan\") || \"1\", 10)\r\n cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs\r\n cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs\r\n }\r\n }\r\n } else if (local === \"header\" || local === \"footer\") {\r\n // ctrl 내부의 머리말/꼬리말만 수집\r\n if (stack.some(f => f.local === \"ctrl\")) {\r\n ctrlSubStack.push({ kind: local, texts: [] })\r\n }\r\n } else if (local === \"tab\" || local === \"fwSpace\" || local === \"hwSpace\" || local === \"br\" || local === \"lineBreak\") {\r\n const para = owningPara()\r\n if (para) para.text += \" \"\r\n }\r\n }\r\n\r\n // run 삽입 지점 보강: t가 없는 body/cell 문단에 대해 첫 run의 닫는 태그 위치 탐색\r\n for (const para of bodyParagraphs) fillRunInsertPos(para, xml)\r\n for (const para of excludedParagraphs) fillRunInsertPos(para, xml)\r\n const fillTableInsertPos = (table: ScanTable, depth = 0): void => {\r\n if (depth > 16) return\r\n for (const row of table.rows) {\r\n for (const cell of row) {\r\n for (const para of cell.paragraphs) fillRunInsertPos(para, xml)\r\n for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1)\r\n }\r\n }\r\n }\r\n for (const table of tables) fillTableInsertPos(table)\r\n for (const table of orphanTables) fillTableInsertPos(table)\r\n\r\n return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables }\r\n}\r\n\r\n/** 속성 문자열에서 값 추출 */\r\nfunction getAttr(attrsRaw: string, name: string): string | undefined {\r\n const re = new RegExp(`(?:^|\\\\s)${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)')`)\r\n const m = attrsRaw.match(re)\r\n return m ? (m[1] ?? m[2]) : undefined\r\n}\r\n\r\n/** 문단 스택 컨텍스트가 현재 테이블 셀 직속인지 — cellAddr/cellSpan 소유 확인 */\r\nfunction insideCurrentTable(stack: StackFrame[], tableStack: ScanTable[]): boolean {\r\n if (tableStack.length === 0) return false\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n const l = stack[i].local\r\n if (l === \"tc\") return true\r\n if (l === \"tbl\") return false\r\n }\r\n return false\r\n}\r\n\r\n/** hp:t 없는 문단의 삽입 위치 — 첫 <hp:run ...>...</hp:run>의 닫는 태그 직전 */\r\nfunction fillRunInsertPos(para: ScanParagraph, xml: string): void {\r\n if (para.tRanges.length > 0) return\r\n // 문단 범위 내 첫 run 탐색 (문단 닫는 태그까지 — 간단히 다음 </hp:p>까지로 한정)\r\n const pEnd = findElementEnd(xml, para.start)\r\n if (pEnd < 0) return\r\n const slice = xml.slice(para.start, pEnd)\r\n const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\\s(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?)?(\\/?)>/)\r\n if (!runOpen || runOpen.index === undefined) return\r\n if (runOpen[2] === \"/\") return // 자기닫힘 run — v1 미지원\r\n const qname = runOpen[1]\r\n const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index)\r\n if (closeIdx < 0) return\r\n para.runInsertPos = para.start + closeIdx\r\n para.runPrefix = prefixOf(qname)\r\n}\r\n\r\n/** start 위치의 여는 태그에 대응하는 닫는 태그 뒤 위치 (동일 로컬명 중첩 추적) */\r\nexport function findElementEnd(xml: string, start: number): number {\r\n const open = xml.slice(start).match(/^<([^\\s/>!?]+)/)\r\n if (!open) return -1\r\n const qname = open[1]\r\n const re = new RegExp(`<${qname}(?=[\\\\s/>])(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?(/?)>|</${qname}\\\\s*>`, \"g\")\r\n re.lastIndex = start\r\n let depth = 0\r\n let mm: RegExpExecArray | null\r\n while ((mm = re.exec(xml)) !== null) {\r\n if (mm[0].startsWith(\"</\")) {\r\n depth--\r\n if (depth === 0) return mm.index + mm[0].length\r\n } else if (mm[1] !== \"/\") {\r\n depth++\r\n }\r\n }\r\n return -1\r\n}\r\n\r\n/** 표 마무리 — 앵커 좌표 맵 구축 (cellAddr 없으면 first-fit, builder.ts buildTable과 동일 규칙) */\r\nfunction finalizeTable(table: ScanTable): void {\r\n const hasAddr = table.rows.some(row => row.some(c => c.colAddr !== undefined && c.rowAddr !== undefined))\r\n if (hasAddr) {\r\n for (const row of table.rows) {\r\n for (const cell of row) {\r\n if (cell.rowAddr !== undefined && cell.colAddr !== undefined) {\r\n table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell)\r\n }\r\n }\r\n }\r\n return\r\n }\r\n // first-fit 배치 (builder.ts buildTable Pass1/2와 동일한 점유 규칙)\r\n const numRows = table.rows.length\r\n const occupied: boolean[][] = Array.from({ length: numRows }, () => [])\r\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\r\n let colIdx = 0\r\n for (const cell of table.rows[rowIdx]) {\r\n while (occupied[rowIdx][colIdx]) colIdx++\r\n cell.rowAddr = rowIdx\r\n cell.colAddr = colIdx\r\n table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell)\r\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\r\n for (let c = colIdx; c < colIdx + cell.colSpan; c++) {\r\n occupied[r][c] = true\r\n }\r\n }\r\n colIdx += cell.colSpan\r\n }\r\n }\r\n}\r\n\r\n// ─── 치환 ────────────────────────────────────────────\r\n\r\n/**\r\n * 문단 텍스트 치환용 splice 생성 — filler-hwpx.ts replaceCellText와 동일 전략:\r\n * 첫 hp:t에 새 텍스트(이스케이프), 나머지 hp:t는 비움. run 구조/charPr 보존.\r\n * t가 없으면 첫 run 끝에 <hp:t> 삽입. 실패 시 null.\r\n *\r\n * IR 텍스트는 sanitize로 양끝 공백이 제거된 상태라, 통째 교체 시 원본의\r\n * 선행 공백(들여쓰기)/후행 공백이 소실된다 — 원본 t-도메인 텍스트에서\r\n * 양끝 공백을 복원해 새 텍스트에 입힌다 (재파싱 IR은 동일하게 유지됨).\r\n */\r\nexport function buildParagraphSplices(para: ScanParagraph, newText: string, xml?: string): SpliceEdit[] | null {\r\n if (newText && xml) {\r\n const orig = paraTText(para, xml)\r\n if (orig && orig.trim() !== \"\") {\r\n const lead = orig.match(/^\\s*/)![0]\r\n const trail = orig.match(/\\s*$/)![0]\r\n if ((lead || trail) && newText.trim() !== \"\") {\r\n newText = lead + newText.replace(/^\\s+|\\s+$/g, \"\") + trail\r\n }\r\n }\r\n }\r\n const escaped = escapeXmlText(newText)\r\n if (para.tRanges.length > 0) {\r\n const splices: SpliceEdit[] = []\r\n const first = para.tRanges[0]\r\n if (first.selfClosing) {\r\n // 자기닫힘 <hp:t/> — 태그째 <hp:t>텍스트</hp:t>로 교체\r\n const prefix = first.prefix ? first.prefix + \":\" : \"\"\r\n splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` })\r\n } else {\r\n splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped })\r\n }\r\n // 나머지 hp:t 비우기 (자기닫힘은 이미 빈 상태)\r\n for (let i = 1; i < para.tRanges.length; i++) {\r\n const r = para.tRanges[i]\r\n if (!r.selfClosing && r.contentStart < r.contentEnd) {\r\n splices.push({ start: r.contentStart, end: r.contentEnd, replacement: \"\" })\r\n }\r\n }\r\n return splices\r\n }\r\n // hp:t 없음 — 첫 run 끝에 삽입\r\n if (para.runInsertPos !== undefined) {\r\n if (!newText) return [] // 비우기인데 t도 없음 — 이미 빈 문단\r\n const prefix = para.runPrefix ? para.runPrefix + \":\" : \"\"\r\n return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }]\r\n }\r\n // 자기닫힘 <hp:run/> — 태그를 펼쳐서 t 삽입 (한컴 빈 문단 패턴, xml 필요)\r\n if (para.selfCloseRun && xml) {\r\n if (!newText) return []\r\n const { start, end } = para.selfCloseRun\r\n const tag = xml.slice(start, end)\r\n const qm = tag.match(/^<([^\\s/>]+)/)\r\n if (!qm || !tag.endsWith(\"/>\")) return null\r\n const qname = qm[1]\r\n const colon = qname.indexOf(\":\")\r\n const prefix = colon >= 0 ? qname.slice(0, colon) + \":\" : \"\"\r\n const opened = tag.slice(0, tag.length - 2).trimEnd() + \">\"\r\n return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }]\r\n }\r\n return newText ? null : []\r\n}\r\n\r\n/**\r\n * 문단의 t-도메인 텍스트 — hp:t 콘텐츠만 이어붙인 문자열 (v3.1).\r\n *\r\n * para.text와 달리 tab/br 등 비-t 요소의 공백 기여가 없어, 그 사이에 끼인\r\n * 요소를 건드리지 않는 정밀 치환(buildRangeSplices)의 좌표계로 쓴다.\r\n * hp:t 콘텐츠에 엔티티/내부 태그([<&])가 있으면 좌표가 어긋나므로 null.\r\n */\r\nexport function paraTText(para: ScanParagraph, xml: string): string | null {\r\n let text = \"\"\r\n for (const t of para.tRanges) {\r\n if (t.selfClosing) continue\r\n const raw = xml.slice(t.contentStart, t.contentEnd)\r\n if (/[<&]/.test(raw)) return null\r\n text += raw\r\n }\r\n return text\r\n}\r\n\r\n/** 문단 텍스트가 전부 hp:t에서 왔는지 — tab/br 등 비-t 기여가 있으면 false.\r\n * false면 문단 전체 재작성 시 비-t 요소와 텍스트가 중복/순서 역전되므로 금지. */\r\nexport function paraTextPureT(para: ScanParagraph, xml: string): boolean {\r\n let len = 0\r\n for (const t of para.tRanges) {\r\n if (t.selfClosing) continue\r\n len += tContentToText(xml.slice(t.contentStart, t.contentEnd)).length\r\n }\r\n return len === para.text.length\r\n}\r\n\r\n/**\r\n * t-도메인 텍스트(paraTText 좌표계)의 [start, end) 범위만 치환하는 정밀 splice\r\n * — run/탭 구조 보존 (v3.1).\r\n *\r\n * 좌표계가 t-도메인이므로 tab/br이 사이에 끼어 있어도 동작한다 (해당 요소는\r\n * 건드리지 않고 t 콘텐츠만 수술). hp:t 콘텐츠에 엔티티/내부 태그가 있으면\r\n * 오프셋 정합이 깨지므로 null을 반환한다 (호출자가 폴백 결정).\r\n */\r\nexport function buildRangeSplices(\r\n para: ScanParagraph, xml: string, start: number, end: number, replacement: string,\r\n): SpliceEdit[] | null {\r\n if (start < 0 || end < start) return null\r\n\r\n // t 세그먼트 ↔ t-도메인 오프셋 정렬 — 디코딩 불변 검증\r\n interface Seg { contentStart: number; from: number; to: number }\r\n const segs: Seg[] = []\r\n let offset = 0\r\n for (const t of para.tRanges) {\r\n if (t.selfClosing) continue // 텍스트 기여 없음\r\n const raw = xml.slice(t.contentStart, t.contentEnd)\r\n if (/[<&]/.test(raw)) return null // 엔티티/내부 태그 — 오프셋 불일치 가능\r\n segs.push({ contentStart: t.contentStart, from: offset, to: offset + raw.length })\r\n offset += raw.length\r\n }\r\n if (segs.length === 0 || end > offset) return null\r\n\r\n const escaped = escapeXmlText(replacement)\r\n\r\n // 삽입 (start === end) — 위치를 포함하는 첫 세그먼트에 삽입\r\n if (start === end) {\r\n for (const seg of segs) {\r\n if (start >= seg.from && start <= seg.to) {\r\n const at = seg.contentStart + (start - seg.from)\r\n return [{ start: at, end: at, replacement: escaped }]\r\n }\r\n }\r\n return null\r\n }\r\n\r\n // 치환 — 첫 겹침 세그먼트에 새 텍스트, 나머지 겹침 구간은 제거\r\n const splices: SpliceEdit[] = []\r\n let placed = false\r\n for (const seg of segs) {\r\n if (seg.to <= start || seg.from >= end) continue\r\n const localStart = Math.max(seg.from, start) - seg.from\r\n const localEnd = Math.min(seg.to, end) - seg.from\r\n splices.push({\r\n start: seg.contentStart + localStart,\r\n end: seg.contentStart + localEnd,\r\n replacement: placed ? \"\" : escaped,\r\n })\r\n placed = true\r\n }\r\n return placed ? splices : null\r\n}\r\n\r\n/** splice 일괄 적용 — 겹침 검증 후 뒤에서부터 치환 */\r\n/**\r\n * 섹션 내 모든 <hp:linesegarray> 제거 splice 목록.\r\n *\r\n * hp:linesegarray는 문단 각 줄의 레이아웃(위치·크기) 정보를 담는데, hp:t 텍스트만\r\n * 바꾸면 이 정보가 새 텍스트와 어긋난다. 한컴 한글은 이를 \"변조\"로 감지해 보안\r\n * 경고를 띄운다(한컴 개발자포럼 권고). 선택적(optional)인 요소라 제거해도 뷰어가\r\n * 레이아웃을 다시 계산하므로, 텍스트가 바뀐 섹션은 전체 linesegarray를 비운다\r\n * (일부만 비우면 한컴이 문서 전체 정합성 검사에서 여전히 경고를 띄움).\r\n */\r\nexport function allLinesegRemovalSplices(xml: string): SpliceEdit[] {\r\n const segRe = /<(\\w+:)?linesegarray\\b[^>]*?(?:\\/>|>[\\s\\S]*?<\\/\\1linesegarray>)/g\r\n const splices: SpliceEdit[] = []\r\n let m: RegExpExecArray | null\r\n while ((m = segRe.exec(xml)) !== null) {\r\n splices.push({ start: m.index, end: m.index + m[0].length, replacement: \"\" })\r\n }\r\n return splices\r\n}\r\n\r\nexport function applySplices(xml: string, splices: SpliceEdit[]): string {\r\n const sorted = [...splices].sort((a, b) => a.start - b.start)\r\n for (let i = 1; i < sorted.length; i++) {\r\n if (sorted[i].start < sorted[i - 1].end) {\r\n throw new Error(\"소스맵 splice 범위 겹침 — 내부 오류\")\r\n }\r\n }\r\n let result = xml\r\n for (let i = sorted.length - 1; i >= 0; i--) {\r\n const s = sorted[i]\r\n result = result.slice(0, s.start) + s.replacement + result.slice(s.end)\r\n }\r\n return result\r\n}\r\n","/**\r\n * ZIP in-place 패치 — 지정 엔트리만 교체하고 나머지는 원본 바이트 그대로 보존.\r\n *\r\n * JSZip generateAsync는 전 엔트리를 재압축/재직렬화하므로 바이트 보존이 깨진다.\r\n * 여기서는 Central Directory를 직접 파싱해 변경 엔트리의 로컬 레코드만 재작성하고,\r\n * 나머지 로컬 레코드·CD 엔트리·EOCD는 원본 바이트를 복사(오프셋 필드만 패치)한다.\r\n * mimetype 첫 엔트리 + 무압축(STORE) 같은 HWPX/OPC 규약은 원본 순서·메서드를\r\n * 그대로 따르므로 자동 보존된다.\r\n */\r\n\r\nimport { deflateRawSync } from \"zlib\"\r\nimport { KordocError } from \"../utils.js\"\r\n\r\nconst EOCD_SIG = 0x06054b50\r\nconst CD_SIG = 0x02014b50\r\nconst LOCAL_SIG = 0x04034b50\r\nconst ZIP64_EOCD_LOC_SIG = 0x07064b50\r\n\r\ninterface CdEntry {\r\n /** CD 레코드 원본 바이트 범위 [start, end) */\r\n cdStart: number\r\n cdEnd: number\r\n name: string\r\n flags: number\r\n method: number\r\n crc: number\r\n compSize: number\r\n uncompSize: number\r\n localOffset: number\r\n}\r\n\r\n/** ZIP 내 엔트리별 압축 해제 데이터 읽기 (검증용) */\r\nexport function readZipEntries(buf: Uint8Array): Map<string, { method: number; compData: Uint8Array }> {\r\n const { entries } = parseCentralDirectory(buf)\r\n const result = new Map<string, { method: number; compData: Uint8Array }>()\r\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\r\n for (const e of entries) {\r\n const dataStart = localDataStart(view, e.localOffset)\r\n result.set(e.name, { method: e.method, compData: buf.subarray(dataStart, dataStart + e.compSize) })\r\n }\r\n return result\r\n}\r\n\r\nfunction localDataStart(view: DataView, localOffset: number): number {\r\n if (view.getUint32(localOffset, true) !== LOCAL_SIG) {\r\n throw new KordocError(\"ZIP 로컬 헤더 시그니처 불일치\")\r\n }\r\n const nameLen = view.getUint16(localOffset + 26, true)\r\n const extraLen = view.getUint16(localOffset + 28, true)\r\n return localOffset + 30 + nameLen + extraLen\r\n}\r\n\r\n/** Buffer.prototype.slice는 view를 반환하므로 (Uint8Array와 달리) 명시적 복사 */\r\nfunction copyBytes(buf: Uint8Array, start: number, end?: number): Uint8Array {\r\n return new Uint8Array(buf.subarray(start, end))\r\n}\r\n\r\nfunction parseCentralDirectory(buf: Uint8Array): { entries: CdEntry[]; cdOffset: number; cdSize: number; eocdOffset: number } {\r\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\r\n // EOCD 탐색 (뒤에서부터, 주석 최대 64KB) — comment 길이가 파일 끝과 일치해야\r\n // 진짜 EOCD (엔트리 데이터/comment 안 가짜 시그니처 배제)\r\n const minEocd = Math.max(0, buf.length - 22 - 65535)\r\n let eocdOffset = -1\r\n for (let i = buf.length - 22; i >= minEocd; i--) {\r\n if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {\r\n eocdOffset = i\r\n break\r\n }\r\n }\r\n if (eocdOffset < 0) {\r\n // 폴백: EOCD 뒤 trailing 정크가 붙은 파일 (parse()는 수용) — comment 길이가\r\n // 파일 끝에 못 미치더라도 CD 시그니처가 검증되는 첫 후보를 채택\r\n for (let i = buf.length - 22; i >= minEocd; i--) {\r\n if (view.getUint32(i, true) !== EOCD_SIG) continue\r\n if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue\r\n const cand = view.getUint32(i + 16, true)\r\n if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) { eocdOffset = i; break }\r\n }\r\n }\r\n if (eocdOffset < 0) throw new KordocError(\"ZIP EOCD를 찾을 수 없습니다\")\r\n\r\n const totalEntries = view.getUint16(eocdOffset + 10, true)\r\n const cdSize = view.getUint32(eocdOffset + 12, true)\r\n const cdOffset = view.getUint32(eocdOffset + 16, true)\r\n if (cdOffset === 0xffffffff || totalEntries === 0xffff) throw new KordocError(\"ZIP64는 지원하지 않습니다\")\r\n // ZIP64 EOCD locator 존재 여부\r\n if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {\r\n throw new KordocError(\"ZIP64는 지원하지 않습니다\")\r\n }\r\n\r\n const decoder = new TextDecoder(\"utf-8\")\r\n const entries: CdEntry[] = []\r\n let pos = cdOffset\r\n for (let i = 0; i < totalEntries; i++) {\r\n if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError(\"ZIP Central Directory 손상\")\r\n const flags = view.getUint16(pos + 8, true)\r\n const method = view.getUint16(pos + 10, true)\r\n const crc = view.getUint32(pos + 16, true)\r\n const compSize = view.getUint32(pos + 20, true)\r\n const uncompSize = view.getUint32(pos + 24, true)\r\n const nameLen = view.getUint16(pos + 28, true)\r\n const extraLen = view.getUint16(pos + 30, true)\r\n const commentLen = view.getUint16(pos + 32, true)\r\n const localOffset = view.getUint32(pos + 42, true)\r\n if (compSize === 0xffffffff || uncompSize === 0xffffffff || localOffset === 0xffffffff) {\r\n throw new KordocError(\"ZIP64는 지원하지 않습니다\")\r\n }\r\n const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen))\r\n const cdEnd = pos + 46 + nameLen + extraLen + commentLen\r\n entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset })\r\n pos = cdEnd\r\n }\r\n return { entries, cdOffset, cdSize, eocdOffset }\r\n}\r\n\r\n// ─── CRC-32 ─────────────────────────────────────────\r\n\r\nconst CRC_TABLE = (() => {\r\n const table = new Uint32Array(256)\r\n for (let n = 0; n < 256; n++) {\r\n let c = n\r\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\r\n table[n] = c >>> 0\r\n }\r\n return table\r\n})()\r\n\r\nfunction crc32(data: Uint8Array): number {\r\n let crc = 0xffffffff\r\n for (let i = 0; i < data.length; i++) {\r\n crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8)\r\n }\r\n return (crc ^ 0xffffffff) >>> 0\r\n}\r\n\r\n// ─── 재조립 ──────────────────────────────────────────\r\n\r\n/**\r\n * ZIP에서 replacements에 지정된 엔트리만 새 데이터로 교체하고 나머지는\r\n * 원본 로컬 레코드 바이트를 그대로 복사하여 재조립한다.\r\n *\r\n * @param original 원본 ZIP 바이트\r\n * @param replacements 엔트리 이름 → 새 압축 전 데이터\r\n * @param additions 신규 엔트리 이름 → 데이터 (기존 로컬 레코드 뒤·CD 앞에 추가 —\r\n * 도장 이미지 등 BinData 파트 추가용. 기존 엔트리와 이름 충돌 시 에러)\r\n */\r\nexport function patchZipEntries(\r\n original: Uint8Array,\r\n replacements: Map<string, Uint8Array>,\r\n additions?: Map<string, Uint8Array>,\r\n): Uint8Array {\r\n const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original)\r\n const view = new DataView(original.buffer, original.byteOffset, original.byteLength)\r\n\r\n for (const name of replacements.keys()) {\r\n if (!entries.some(e => e.name === name)) throw new KordocError(`ZIP에 없는 엔트리: ${name}`)\r\n }\r\n if (additions) {\r\n for (const name of additions.keys()) {\r\n if (entries.some(e => e.name === name)) throw new KordocError(`ZIP에 이미 있는 엔트리: ${name}`)\r\n }\r\n }\r\n\r\n // 로컬 레코드 순서 = 원본 로컬 오프셋 순서 (mimetype 첫 엔트리 보존)\r\n const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset)\r\n const segments: Uint8Array[] = []\r\n const newLocalOffset = new Map<CdEntry, number>()\r\n const newMeta = new Map<CdEntry, { crc: number; compSize: number; uncompSize: number; flags: number }>()\r\n let offset = 0\r\n\r\n for (let i = 0; i < byLocal.length; i++) {\r\n const e = byLocal[i]\r\n const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset\r\n newLocalOffset.set(e, offset)\r\n\r\n const newData = replacements.get(e.name)\r\n if (newData === undefined) {\r\n // 원본 로컬 레코드 (데이터 디스크립터 포함) 바이트 그대로\r\n const seg = original.subarray(e.localOffset, segEnd)\r\n segments.push(seg)\r\n offset += seg.length\r\n continue\r\n }\r\n\r\n // 교체 엔트리 — 원본 로컬 헤더를 복사한 뒤 crc/size/flags만 패치\r\n if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError(\"ZIP 로컬 헤더 시그니처 불일치\")\r\n const nameLen = view.getUint16(e.localOffset + 26, true)\r\n const extraLen = view.getUint16(e.localOffset + 28, true)\r\n const headerLen = 30 + nameLen + extraLen\r\n const header = copyBytes(original, e.localOffset, e.localOffset + headerLen)\r\n const hview = new DataView(header.buffer, header.byteOffset, header.byteLength)\r\n\r\n const method = e.method\r\n const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData))\r\n const crc = crc32(newData)\r\n const flags = e.flags & ~0x0008 // 데이터 디스크립터 비트 해제 (사이즈를 헤더에 기록)\r\n\r\n hview.setUint16(6, flags, true)\r\n hview.setUint32(14, crc, true)\r\n hview.setUint32(18, compData.length, true)\r\n hview.setUint32(22, newData.length, true)\r\n\r\n segments.push(header, compData)\r\n offset += headerLen + compData.length\r\n newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags })\r\n }\r\n\r\n // 신규 엔트리 — 기존 로컬 레코드 뒤에 UTF-8 이름·고정 타임스탬프로 기록.\r\n // PNG 등 기압축 데이터는 deflate가 오히려 커질 수 있어 작아질 때만 압축(STORE 폴백).\r\n const added: Array<{\r\n nameBytes: Uint8Array\r\n crc: number\r\n compSize: number\r\n uncompSize: number\r\n method: number\r\n localOffset: number\r\n }> = []\r\n if (additions) {\r\n const encoder = new TextEncoder()\r\n for (const [name, data] of additions) {\r\n const nameBytes = encoder.encode(name)\r\n const deflated = new Uint8Array(deflateRawSync(data))\r\n const method = deflated.length < data.length ? 8 : 0\r\n const compData = method === 8 ? deflated : data\r\n const crc = crc32(data)\r\n const header = new Uint8Array(30 + nameBytes.length)\r\n const hv = new DataView(header.buffer)\r\n hv.setUint32(0, LOCAL_SIG, true)\r\n hv.setUint16(4, 20, true) // version needed\r\n hv.setUint16(6, 0x0800, true) // UTF-8 이름 플래그\r\n hv.setUint16(8, method, true)\r\n hv.setUint16(10, 0, true) // time\r\n hv.setUint16(12, 0x21, true) // date 1980-01-01 (결정적 출력)\r\n hv.setUint32(14, crc, true)\r\n hv.setUint32(18, compData.length, true)\r\n hv.setUint32(22, data.length, true)\r\n hv.setUint16(26, nameBytes.length, true)\r\n hv.setUint16(28, 0, true)\r\n header.set(nameBytes, 30)\r\n added.push({ nameBytes, crc, compSize: compData.length, uncompSize: data.length, method, localOffset: offset })\r\n segments.push(header, compData)\r\n offset += header.length + compData.length\r\n }\r\n }\r\n\r\n // Central Directory — 원본 CD 엔트리 순서 유지, 오프셋/메타 패치\r\n const newCdOffset = offset\r\n for (const e of entries) {\r\n const cd = copyBytes(original, e.cdStart, e.cdEnd)\r\n const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength)\r\n cview.setUint32(42, newLocalOffset.get(e)!, true)\r\n const meta = newMeta.get(e)\r\n if (meta) {\r\n cview.setUint16(8, meta.flags, true)\r\n cview.setUint32(16, meta.crc, true)\r\n cview.setUint32(20, meta.compSize, true)\r\n cview.setUint32(24, meta.uncompSize, true)\r\n }\r\n segments.push(cd)\r\n offset += cd.length\r\n }\r\n\r\n // 신규 엔트리 CD 레코드 (원본 CD 뒤)\r\n for (const a of added) {\r\n const cd = new Uint8Array(46 + a.nameBytes.length)\r\n const cv = new DataView(cd.buffer)\r\n cv.setUint32(0, CD_SIG, true)\r\n cv.setUint16(4, 20, true) // version made by\r\n cv.setUint16(6, 20, true) // version needed\r\n cv.setUint16(8, 0x0800, true) // UTF-8 이름 플래그\r\n cv.setUint16(10, a.method, true)\r\n cv.setUint16(12, 0, true) // time\r\n cv.setUint16(14, 0x21, true) // date 1980-01-01\r\n cv.setUint32(16, a.crc, true)\r\n cv.setUint32(20, a.compSize, true)\r\n cv.setUint32(24, a.uncompSize, true)\r\n cv.setUint16(28, a.nameBytes.length, true)\r\n cv.setUint32(42, a.localOffset, true)\r\n cd.set(a.nameBytes, 46)\r\n segments.push(cd)\r\n offset += cd.length\r\n }\r\n const newCdSize = offset - newCdOffset\r\n\r\n // EOCD — 원본 복사 후 CD 오프셋/크기 패치 (신규 엔트리만큼 카운트 증가)\r\n const eocd = copyBytes(original, eocdOffset)\r\n const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength)\r\n if (added.length > 0) {\r\n eview.setUint16(8, view.getUint16(eocdOffset + 8, true) + added.length, true)\r\n eview.setUint16(10, view.getUint16(eocdOffset + 10, true) + added.length, true)\r\n }\r\n eview.setUint32(12, newCdSize, true)\r\n eview.setUint32(16, newCdOffset, true)\r\n segments.push(eocd)\r\n offset += eocd.length\r\n\r\n const result = new Uint8Array(offset)\r\n let pos = 0\r\n for (const seg of segments) {\r\n result.set(seg, pos)\r\n pos += seg.length\r\n }\r\n return result\r\n}\r\n"],"mappings":"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","names":["local","contentStart","si","site"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hwpx/gongmun-surface.ts"],"sourcesContent":["/**\r\n * 공문서 옵션 표면 SSOT (v4.0.4, 인벤토리 영역1-1) — CLI(commander)와 MCP(zod)가\r\n * 각자 손배선하던 GongmunOptions 조립을 한 곳으로 모은다.\r\n *\r\n * 실측 드리프트의 뿌리는 두 가지였다:\r\n * 1. 값 집합(열거·중첩 키 목록·범위)이 세 곳(gongmun.ts 타입 / commander 검증 /\r\n * zod shape)에 각자 사경 — line_spacing이 MCP에 빠지고 sizes.bodyTitle이\r\n * zod에 누락되는 식. → 여기의 상수에서 양쪽이 파생한다.\r\n * 2. 조립 의미론(cover 우선순위, 조건부 대입)이 두 벌 복붙. → buildGongmunOptions\r\n * 하나로 통일 — 옵션 신설 시 이 파일과 표면별 플래그/파라미터 선언만 만지면 된다.\r\n *\r\n * 표면 문법(플래그 이름·설명문·kv 파싱)은 각 표면의 것 — 여기는 값과 의미만 담는다.\r\n */\r\n\r\nimport type { GongmunOptions, GongmunPreset } from \"./gongmun.js\"\r\n\r\n// ─── 값 집합 (CLI 검증·MCP zod 공통 파생원) ──────────────\r\n\r\nexport const BODY_FONTS = [\"myeongjo\", \"gothic\"] as const\r\nexport const H2_MARKERS = [\"box\", \"number\", \"none\"] as const\r\nexport const BULLET2_CHARS = [\"ㅇ\", \"○\"] as const\r\n\r\n/** 요소별 글꼴 오버라이드 역할 키 (GongmunOptions.fonts) */\r\nexport const FONT_ROLE_KEYS = [\"body\", \"heading\", \"ref\", \"table\"] as const\r\n/** 개조식 요소별 크기 키 (gaejosik.ts GaejosikSizeOverrides와 정합 — 드리프트 시 컴파일 에러) */\r\nexport const SIZE_KEYS = [\r\n \"dae\", \"cham\", \"chapter\", \"coverTitle\", \"coverSub\",\r\n \"tocLabel\", \"tocRoman\", \"tocItem\", \"table\", \"bodyTitle\",\r\n] as const\r\n/** 기안문 두문 키 (별지 제1호서식) */\r\nexport const DOC_HEAD_KEYS = [\"org\", \"to\", \"via\", \"title\"] as const\r\n/** 기안문 결문 키 */\r\nexport const DOC_FOOT_KEYS = [\r\n \"sender\", \"drafter\", \"reviewer\", \"approver\", \"cooperator\", \"docNum\", \"receive\",\r\n \"address\", \"site\", \"phone\", \"fax\", \"email\", \"disclosure\",\r\n] as const\r\n/** 공고문 두문·결문 키 */\r\nexport const NOTICE_HEAD_KEYS = [\"no\", \"date\", \"sender\"] as const\r\n/** 보도자료 담당 표 키 */\r\nexport const PRESS_CONTACT_KEYS = [\"dept\", \"manager\", \"phone\"] as const\r\n\r\n/** 수치 범위 — MCP zod와 문서화가 공유 (라이브러리 검증은 gongmun.ts 방어선) */\r\nexport const BODY_PT_RANGE = { min: 6, max: 40 } as const\r\nexport const LINE_SPACING_RANGE = { min: 50, max: 300 } as const\r\nexport const SIZE_PT_RANGE = { min: 6, max: 60 } as const\r\nexport const APPROVAL_MAX = 6\r\n\r\n// SIZE_KEYS ⊆ GaejosikSizeOverrides 컴파일 타임 잠금 — gaejosik.ts에 키가 늘면\r\n// 여기(와 이를 파생하는 MCP zod·CLI 안내문)도 갱신을 강제한다\r\ntype SizeKey = (typeof SIZE_KEYS)[number]\r\ntype _SizeKeysMatch = SizeKey extends keyof NonNullable<GongmunOptions[\"sizes\"]>\r\n ? (keyof NonNullable<GongmunOptions[\"sizes\"]> extends SizeKey ? true : never)\r\n : never\r\nconst _sizeKeysLock: _SizeKeysMatch = true\r\nvoid _sizeKeysLock\r\n\r\n// ─── 조립 (두 표면 공통 의미론) ──────────────────────────\r\n\r\n/**\r\n * 표면 중립 입력 — CLI는 플래그·kv 파싱 결과를, MCP는 zod 통과 파라미터를 이 모양으로\r\n * 넘긴다. 모든 필드가 \"명시된 것만 정의\"(미지정 undefined) 규약 — --no-x 기본값 같은\r\n * 표면 고유 사정은 어댑터가 undefined로 정돈해서 넘겨야 한다.\r\n */\r\nexport interface GongmunSurfaceInput {\r\n preset: GongmunPreset\r\n font?: (typeof BODY_FONTS)[number]\r\n bodyPt?: number\r\n lineSpacing?: number\r\n org?: string\r\n date?: string\r\n cover?: boolean\r\n toc?: boolean\r\n approval?: string[]\r\n pageNumbers?: boolean\r\n endMark?: boolean\r\n bodyTitleBox?: boolean\r\n h2Marker?: (typeof H2_MARKERS)[number]\r\n fonts?: NonNullable<GongmunOptions[\"fonts\"]>\r\n sizes?: NonNullable<GongmunOptions[\"sizes\"]>\r\n bullet2?: (typeof BULLET2_CHARS)[number]\r\n suppressSingle?: boolean\r\n docHead?: NonNullable<GongmunOptions[\"docHead\"]>\r\n docFoot?: NonNullable<GongmunOptions[\"docFoot\"]>\r\n reportInfo?: string\r\n noticeHead?: NonNullable<GongmunOptions[\"noticeHead\"]>\r\n press?: NonNullable<GongmunOptions[\"press\"]>\r\n}\r\n\r\n/**\r\n * 중립 입력 → GongmunOptions. 종전 CLI(cli.ts:535~612)·MCP(mcp.ts:792~814) 복붙\r\n * 두 벌의 의미론을 그대로 보존한 단일 조립:\r\n * - cover: false(끄기)가 최우선 → org/date 지정 시 객체(=켜짐) → true(강제 켜기)\r\n * → 미지정이면 프리셋 기본(resolveGongmun)\r\n * - 나머지는 \"명시된 것만 대입\" — 프리셋 기본값을 undefined로 덮지 않는다\r\n */\r\nexport function buildGongmunOptions(input: GongmunSurfaceInput): GongmunOptions {\r\n const g: GongmunOptions = { preset: input.preset }\r\n if (input.font) g.bodyFont = input.font\r\n // NaN도 대입 — CLI의 비숫자 플래그(--pt abc)가 라이브러리 방어선(assertFiniteRange)\r\n // 에서 명시적 에러로 죽는 기존 경로 보존 (truthiness로 거르면 무증상 폐기가 된다)\r\n if (input.bodyPt !== undefined) g.bodyPt = input.bodyPt\r\n if (input.lineSpacing !== undefined) g.lineSpacing = input.lineSpacing\r\n if (input.cover === false) {\r\n g.cover = false\r\n } else if (input.org || input.date) {\r\n g.cover = { ...(input.org ? { org: input.org } : {}), ...(input.date ? { date: input.date } : {}) }\r\n } else if (input.cover === true) {\r\n g.cover = true\r\n }\r\n if (input.toc !== undefined) g.toc = input.toc\r\n if (input.approval && input.approval.length > 0) g.approval = input.approval\r\n if (input.pageNumbers !== undefined) g.pageNumbers = input.pageNumbers\r\n if (input.endMark !== undefined) g.endMark = input.endMark\r\n if (input.bodyTitleBox !== undefined) g.bodyTitleBox = input.bodyTitleBox\r\n if (input.h2Marker) g.h2Marker = input.h2Marker\r\n if (input.fonts) g.fonts = input.fonts\r\n if (input.sizes) g.sizes = input.sizes\r\n if (input.bullet2) g.bullet2 = input.bullet2\r\n if (input.suppressSingle !== undefined) g.suppressSingle = input.suppressSingle\r\n if (input.docHead) g.docHead = input.docHead\r\n if (input.docFoot) g.docFoot = input.docFoot\r\n if (input.reportInfo) g.reportInfo = input.reportInfo\r\n if (input.noticeHead) g.noticeHead = input.noticeHead\r\n if (input.press) g.press = input.press\r\n return g\r\n}\r\n"],"mappings":";;;AAkBO,IAAM,aAAa,CAAC,YAAY,QAAQ;AACxC,IAAM,aAAa,CAAC,OAAO,UAAU,MAAM;AAC3C,IAAM,gBAAgB,CAAC,UAAK,QAAG;AAG/B,IAAM,iBAAiB,CAAC,QAAQ,WAAW,OAAO,OAAO;AAEzD,IAAM,YAAY;AAAA,EACvB;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAc;AAAA,EACxC;AAAA,EAAY;AAAA,EAAY;AAAA,EAAW;AAAA,EAAS;AAC9C;AAEO,IAAM,gBAAgB,CAAC,OAAO,MAAM,OAAO,OAAO;AAElD,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EAAU;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EAAc;AAAA,EAAU;AAAA,EACrE;AAAA,EAAW;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAO;AAAA,EAAS;AAC9C;AAEO,IAAM,mBAAmB,CAAC,MAAM,QAAQ,QAAQ;AAEhD,IAAM,qBAAqB,CAAC,QAAQ,WAAW,OAAO;AAGtD,IAAM,gBAAgB,EAAE,KAAK,GAAG,KAAK,GAAG;AACxC,IAAM,qBAAqB,EAAE,KAAK,IAAI,KAAK,IAAI;AAC/C,IAAM,gBAAgB,EAAE,KAAK,GAAG,KAAK,GAAG;AACxC,IAAM,eAAe;AAkDrB,SAAS,oBAAoB,OAA4C;AAC9E,QAAM,IAAoB,EAAE,QAAQ,MAAM,OAAO;AACjD,MAAI,MAAM,KAAM,GAAE,WAAW,MAAM;AAGnC,MAAI,MAAM,WAAW,OAAW,GAAE,SAAS,MAAM;AACjD,MAAI,MAAM,gBAAgB,OAAW,GAAE,cAAc,MAAM;AAC3D,MAAI,MAAM,UAAU,OAAO;AACzB,MAAE,QAAQ;AAAA,EACZ,WAAW,MAAM,OAAO,MAAM,MAAM;AAClC,MAAE,QAAQ,EAAE,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,GAAI,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC,EAAG;AAAA,EACpG,WAAW,MAAM,UAAU,MAAM;AAC/B,MAAE,QAAQ;AAAA,EACZ;AACA,MAAI,MAAM,QAAQ,OAAW,GAAE,MAAM,MAAM;AAC3C,MAAI,MAAM,YAAY,MAAM,SAAS,SAAS,EAAG,GAAE,WAAW,MAAM;AACpE,MAAI,MAAM,gBAAgB,OAAW,GAAE,cAAc,MAAM;AAC3D,MAAI,MAAM,YAAY,OAAW,GAAE,UAAU,MAAM;AACnD,MAAI,MAAM,iBAAiB,OAAW,GAAE,eAAe,MAAM;AAC7D,MAAI,MAAM,SAAU,GAAE,WAAW,MAAM;AACvC,MAAI,MAAM,MAAO,GAAE,QAAQ,MAAM;AACjC,MAAI,MAAM,MAAO,GAAE,QAAQ,MAAM;AACjC,MAAI,MAAM,QAAS,GAAE,UAAU,MAAM;AACrC,MAAI,MAAM,mBAAmB,OAAW,GAAE,iBAAiB,MAAM;AACjE,MAAI,MAAM,QAAS,GAAE,UAAU,MAAM;AACrC,MAAI,MAAM,QAAS,GAAE,UAAU,MAAM;AACrC,MAAI,MAAM,WAAY,GAAE,aAAa,MAAM;AAC3C,MAAI,MAAM,WAAY,GAAE,aAAa,MAAM;AAC3C,MAAI,MAAM,MAAO,GAAE,QAAQ,MAAM;AACjC,SAAO;AACT;","names":[]}
|