kordoc 3.16.0 → 3.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/{-H7N62NZU.js → -YVONYEEN.js} +7 -7
  3. package/dist/{chunk-FGL7BH4P.js → chunk-7WUTJXKP.js} +49 -20
  4. package/dist/chunk-7WUTJXKP.js.map +1 -0
  5. package/dist/{chunk-5RHRQ7HK.js → chunk-CDFCA37S.js} +8 -4
  6. package/dist/chunk-CDFCA37S.js.map +1 -0
  7. package/dist/{chunk-LYL4IL3E.js → chunk-F62GOZFB.js} +13 -6
  8. package/dist/chunk-F62GOZFB.js.map +1 -0
  9. package/dist/{chunk-WD5NUUU6.js → chunk-I6T3DKYK.js} +315 -262
  10. package/dist/chunk-I6T3DKYK.js.map +1 -0
  11. package/dist/{chunk-JFNWJFKP.js → chunk-IY3NX4FT.js} +2 -2
  12. package/dist/{chunk-IC56KHBL.js → chunk-K4L563FA.js} +2 -2
  13. package/dist/{chunk-Q7EGGW3H.js → chunk-QOPY6URE.js} +13 -6
  14. package/dist/chunk-QOPY6URE.js.map +1 -0
  15. package/dist/{chunk-BIP4JAVG.cjs → chunk-VNIQK4AL.cjs} +13 -6
  16. package/dist/chunk-VNIQK4AL.cjs.map +1 -0
  17. package/dist/cli.js +56 -17
  18. package/dist/cli.js.map +1 -1
  19. package/dist/index.cjs +586 -502
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +11 -3
  22. package/dist/index.d.ts +11 -3
  23. package/dist/index.js +358 -274
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp.js +25 -16
  26. package/dist/mcp.js.map +1 -1
  27. package/dist/{parser-6V7F3GET.cjs → parser-3O2UF6VO.cjs} +35 -22
  28. package/dist/parser-3O2UF6VO.cjs.map +1 -0
  29. package/dist/{parser-HBHCD7TE.js → parser-B4O7CW5Q.js} +24 -11
  30. package/dist/parser-B4O7CW5Q.js.map +1 -0
  31. package/dist/{parser-5G247XYD.js → parser-BNEAMKUF.js} +23 -10
  32. package/dist/parser-BNEAMKUF.js.map +1 -0
  33. package/dist/render-QCNMRR7L.js +10 -0
  34. package/dist/seal-REDSMUI5.js +10 -0
  35. package/dist/{watch-HIYNOSFD.js → watch-RLF5F3TN.js} +7 -7
  36. package/package.json +2 -5
  37. package/dist/chunk-5RHRQ7HK.js.map +0 -1
  38. package/dist/chunk-BIP4JAVG.cjs.map +0 -1
  39. package/dist/chunk-FGL7BH4P.js.map +0 -1
  40. package/dist/chunk-LYL4IL3E.js.map +0 -1
  41. package/dist/chunk-Q7EGGW3H.js.map +0 -1
  42. package/dist/chunk-WD5NUUU6.js.map +0 -1
  43. package/dist/parser-5G247XYD.js.map +0 -1
  44. package/dist/parser-6V7F3GET.cjs.map +0 -1
  45. package/dist/parser-HBHCD7TE.js.map +0 -1
  46. package/dist/render-VA4NHROW.js +0 -10
  47. package/dist/seal-UVDRGHEF.js +0 -10
  48. /package/dist/{-H7N62NZU.js.map → -YVONYEEN.js.map} +0 -0
  49. /package/dist/{chunk-JFNWJFKP.js.map → chunk-IY3NX4FT.js.map} +0 -0
  50. /package/dist/{chunk-IC56KHBL.js.map → chunk-K4L563FA.js.map} +0 -0
  51. /package/dist/{render-VA4NHROW.js.map → render-QCNMRR7L.js.map} +0 -0
  52. /package/dist/{seal-UVDRGHEF.js.map → seal-REDSMUI5.js.map} +0 -0
  53. /package/dist/{watch-HIYNOSFD.js.map → watch-RLF5F3TN.js.map} +0 -0
package/README.md CHANGED
@@ -86,7 +86,7 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
86
86
  ## v3.16.0 변경사항
87
87
 
88
88
  - **📊 차트 생성**: 마크다운 ```chart 펜스(type/cat/계열 라인)가 한컴 네이티브 차트(OOXML chartSpace)로 생성됩니다 — 막대·선·원·도넛·영역·분산·방사형 등 20종, 계열/조각 색 지정, 잘못된 펜스는 코드블록 폴백.
89
- - **🔴 도장/서명 자동 날인**: `kordoc seal` — "(인)"·"서명 또는 인" 앵커를 찾아 도장 PNG를 글 앞 부유로 배치. 표/페이지를 키우지 않아 날인 후 서식이 밀리지 않습니다 (MCP `place_seal` 포함).
89
+ - **🔴 도장/서명 자동 날인**: `kordoc seal` — "(인)"·"서명 또는 인" 앵커를 찾아 도장 PNG를 글 앞 부유로 배치. 표/페이지를 키우지 않아 날인 후 서식이 밀리지 않습니다 (MCP `place_seal` 포함). 중첩표·글상자·탭/줄바꿈 문단은 위치가 근사이며 결과 `warnings` 로 고지됩니다 — 한컴에서 확인 후 `--dx`/`--dy`(dx_mm/dy_mm)로 미세조정하세요.
90
90
  - **🔌 Claude Code 플러그인**: `/plugin marketplace add chrisryugj/kordoc` → `.hwp`/`.hwpx`/공문서 요청에 kordoc 스킬 자동 활성화.
91
91
 
92
92
  ## v3.15.0 변경사항
@@ -33,10 +33,10 @@ import {
33
33
  patchHwpxBlocks,
34
34
  renderHtml,
35
35
  validateHwpx
36
- } from "./chunk-WD5NUUU6.js";
36
+ } from "./chunk-I6T3DKYK.js";
37
37
  import {
38
38
  blocksToMarkdown
39
- } from "./chunk-LYL4IL3E.js";
39
+ } from "./chunk-F62GOZFB.js";
40
40
  import {
41
41
  detectFormat,
42
42
  detectOle2Format,
@@ -53,7 +53,7 @@ import {
53
53
  buildRangeSplices,
54
54
  placeSealHwpx,
55
55
  scanSectionXml
56
- } from "./chunk-FGL7BH4P.js";
56
+ } from "./chunk-7WUTJXKP.js";
57
57
  import {
58
58
  SPACE_EM_FIXED,
59
59
  SPACE_EM_FONT,
@@ -63,11 +63,11 @@ import {
63
63
  renderHwpxToSvg,
64
64
  simulateWrap,
65
65
  simulateWrapKeepWord
66
- } from "./chunk-5RHRQ7HK.js";
67
- import "./chunk-JFNWJFKP.js";
66
+ } from "./chunk-CDFCA37S.js";
67
+ import "./chunk-IY3NX4FT.js";
68
68
  import {
69
69
  VERSION
70
- } from "./chunk-IC56KHBL.js";
70
+ } from "./chunk-K4L563FA.js";
71
71
  export {
72
72
  HwpxSession,
73
73
  PRESET_ALIAS,
@@ -125,4 +125,4 @@ export {
125
125
  simulateWrapKeepWord,
126
126
  validateHwpx
127
127
  };
128
- //# sourceMappingURL=-H7N62NZU.js.map
128
+ //# sourceMappingURL=-YVONYEEN.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  parseRenderStyles
4
- } from "./chunk-JFNWJFKP.js";
4
+ } from "./chunk-IY3NX4FT.js";
5
5
  import {
6
6
  KordocError
7
- } from "./chunk-IC56KHBL.js";
7
+ } from "./chunk-K4L563FA.js";
8
8
 
9
9
  // src/form/seal.ts
10
10
  import JSZip from "jszip";
@@ -744,6 +744,21 @@ var MIME = {
744
744
  bmp: "image/bmp",
745
745
  gif: "image/gif"
746
746
  };
747
+ function imageMagicMatches(buf, ext) {
748
+ switch (ext) {
749
+ case "png":
750
+ return buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71;
751
+ case "jpg":
752
+ case "jpeg":
753
+ return buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255;
754
+ case "bmp":
755
+ return buf.length >= 2 && buf[0] === 66 && buf[1] === 77;
756
+ case "gif":
757
+ return buf.length >= 6 && buf[0] === 71 && buf[1] === 73 && buf[2] === 70;
758
+ default:
759
+ return false;
760
+ }
761
+ }
747
762
  function glyphEm(code) {
748
763
  if (code < 32) return 0;
749
764
  if (code <= 126) return 0.5;
@@ -758,19 +773,19 @@ function measureMm(text, emMm) {
758
773
  function collectSites(scan) {
759
774
  const sites = [];
760
775
  for (const p of scan.bodyParagraphs) sites.push({ para: p });
761
- const walkTables = (tables, depth) => {
776
+ const walkTables = (tables, depth, outer) => {
762
777
  if (depth > 16) return;
763
778
  for (const t of tables) {
764
779
  for (const row of t.rows) {
765
780
  for (const cell of row) {
766
- for (const p of cell.paragraphs) sites.push({ para: p, cell, table: t });
767
- walkTables(cell.tables, depth + 1);
781
+ for (const p of cell.paragraphs) sites.push({ para: p, cell, table: t, nested: depth > 0, outer: outer.length ? outer : void 0 });
782
+ walkTables(cell.tables, depth + 1, [...outer, { cell, table: t }]);
768
783
  }
769
784
  }
770
785
  }
771
786
  };
772
- walkTables(scan.tables, 0);
773
- walkTables(scan.orphanTables, 0);
787
+ walkTables(scan.tables, 0, []);
788
+ walkTables(scan.orphanTables, 0, []);
774
789
  sites.sort((a, b) => a.para.start - b.para.start);
775
790
  return sites;
776
791
  }
@@ -784,7 +799,10 @@ function paraOpenTag(xml, para) {
784
799
  }
785
800
  function anchorRunInfo(xml, para, anchor) {
786
801
  let tr = para.tRanges.find((r) => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(anchor));
787
- if (!tr) tr = para.tRanges[para.tRanges.length - 1];
802
+ if (!tr) {
803
+ const head = anchor[0];
804
+ tr = para.tRanges.find((r) => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(head)) ?? para.tRanges[para.tRanges.length - 1];
805
+ }
788
806
  if (!tr) return null;
789
807
  const before = xml.slice(Math.max(0, para.start), tr.contentStart);
790
808
  const runOpen = [...before.matchAll(/<([A-Za-z0-9]+):run\b[^>]*>/g)].pop();
@@ -823,14 +841,17 @@ function cellContentWidthMm(xml, cell) {
823
841
  }
824
842
  function cellLeftOffsetMm(xml, table, cell) {
825
843
  if (cell.colAddr === void 0) return 0;
826
- const row = table.rows.find((r) => r.includes(cell));
827
- if (!row) return 0;
828
- let sum = 0;
829
- for (const c of row) {
830
- if (c === cell || c.colAddr === void 0 || c.colAddr >= cell.colAddr) continue;
831
- const w = cellSzWidthHu(xml, c);
832
- if (w) sum += w;
844
+ const colW = /* @__PURE__ */ new Map();
845
+ for (const row of table.rows) {
846
+ for (const c of row) {
847
+ if (c.colAddr === void 0 || colW.has(c.colAddr)) continue;
848
+ if (c.colSpan > 1) continue;
849
+ const w = cellSzWidthHu(xml, c);
850
+ if (w) colW.set(c.colAddr, w);
851
+ }
833
852
  }
853
+ let sum = 0;
854
+ for (let col = 0; col < cell.colAddr; col++) sum += colW.get(col) ?? 0;
834
855
  return sum / HU_PER_MM;
835
856
  }
836
857
  function bodyColumnWidthMm(xml) {
@@ -849,7 +870,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
849
870
  if (ops.length === 0) throw new KordocError("place_seal: \uBC30\uCE58\uD560 \uB3C4\uC7A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
850
871
  const u8 = new Uint8Array(hwpxBuffer);
851
872
  const zip = await JSZip.loadAsync(hwpxBuffer);
852
- const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
873
+ const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort((a, b) => Number(a.match(/(\d+)\.xml$/i)?.[1] ?? 0) - Number(b.match(/(\d+)\.xml$/i)?.[1] ?? 0));
853
874
  if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
854
875
  const manifestPath = Object.keys(zip.files).find((name) => /\.hpf$/i.test(name));
855
876
  const headerPath = Object.keys(zip.files).find((name) => /(^|\/)header\.xml$/i.test(name));
@@ -895,6 +916,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
895
916
  if (!op.image || op.image.length === 0) throw new KordocError("place_seal: \uB3C4\uC7A5 \uC774\uBBF8\uC9C0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
896
917
  const ext = (op.ext ?? "png").toLowerCase();
897
918
  if (!MIME[ext]) throw new KordocError(`place_seal: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC774\uBBF8\uC9C0 \uD655\uC7A5\uC790 .${ext} (png/jpg/bmp/gif)`);
919
+ if (!imageMagicMatches(op.image, ext)) throw new KordocError(`place_seal: \uC774\uBBF8\uC9C0 \uB0B4\uC6A9\uC774 .${ext} \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (\uB9E4\uC9C1\uBC14\uC774\uD2B8 \uBD88\uC77C\uCE58)`);
898
920
  const wantOcc = op.occurrence ?? 0;
899
921
  let found = null;
900
922
  let total = 0;
@@ -943,7 +965,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
943
965
  const lineHMm = emMm;
944
966
  const startXMm = measureMm(site.para.text.slice(0, idxInText), emMm);
945
967
  const anchorWMm = measureMm(op.anchor, emMm);
946
- const sizeMm = op.sizeMm ?? Math.max(7, Math.min(18, lineHMm * 1.6));
968
+ const sizeMm = op.sizeMm != null && Number.isFinite(op.sizeMm) && op.sizeMm > 0 ? op.sizeMm : Math.max(7, Math.min(18, lineHMm * 1.6));
947
969
  const availMm = (site.cell ? cellContentWidthMm(xml, site.cell) : null) ?? bodyColumnWidthMm(xml);
948
970
  const paraPrId = attrOf(paraOpenTag(xml, site.para), "paraPrIDRef") ?? "0";
949
971
  const align = styles?.paraAlign.get(paraPrId);
@@ -957,7 +979,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
957
979
  if (mode === "auto") {
958
980
  mode = availMm - (alignShiftMm + startXMm + anchorWMm) >= sizeMm + 2 ? "right" : "overlap";
959
981
  }
960
- const cellShiftMm = site.cell && site.table ? cellLeftOffsetMm(xml, site.table, site.cell) : 0;
982
+ const cellShiftMm = site.cell && site.table ? cellLeftOffsetMm(xml, site.table, site.cell) + (site.outer ?? []).reduce((s, o) => s + cellLeftOffsetMm(xml, o.table, o.cell), 0) : 0;
961
983
  const posXMm = (mode === "right" ? startXMm + anchorWMm + 2 : startXMm + anchorWMm / 2 - sizeMm / 2) + alignShiftMm + cellShiftMm + (op.dxMm ?? 0);
962
984
  const posYMm = -(sizeMm - lineHMm) / 2 + (op.dyMm ?? 0);
963
985
  const n = nextImageNum();
@@ -975,6 +997,12 @@ async function placeSealHwpx(hwpxBuffer, ops) {
975
997
  end: run.insertAt,
976
998
  replacement: `<${run.prefix}:run charPrIDRef="${run.charPr}">${pic}</${run.prefix}:run>`
977
999
  });
1000
+ const warnings = [];
1001
+ const paraSeg = xml.slice(site.para.start, run.insertAt);
1002
+ if (/<[A-Za-z0-9]+:tab\b/.test(paraSeg)) warnings.push("\uD0ED\uC774 \uB4E0 \uBB38\uB2E8 \u2014 \uB3C4\uC7A5 \uAC00\uB85C \uC704\uCE58\uAC00 \uADFC\uC0AC\uAC12\uC785\uB2C8\uB2E4 (dx \uB85C \uBCF4\uC815)");
1003
+ if (/<[A-Za-z0-9]+:(lineBreak|br)\b/.test(paraSeg)) warnings.push("\uC904\uBC14\uAFC8\uC774 \uB4E0 \uBB38\uB2E8 \u2014 \uC575\uCEE4\uAC00 2\uBC88\uC9F8 \uC904 \uC774\uD558\uBA74 \uC138\uB85C \uC704\uCE58\uAC00 \uC5B4\uAE0B\uB0A0 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (dy \uB85C \uBCF4\uC815)");
1004
+ if (site.nested) warnings.push("\uC911\uCCA9\uD45C(\uD45C \uC548 \uD45C) \uC140 \uC575\uCEE4 \u2014 \uBC14\uAE65 \uC140 \uC624\uD504\uC14B\uC744 \uCCB4\uC778\uC73C\uB85C \uAC00\uC0B0\uD558\uB098 \uC140 \uC5EC\uBC31\xB7\uC815\uB82C\uC740 \uADFC\uC0AC\uC785\uB2C8\uB2E4 (\uD544\uC694 \uC2DC dx \uB85C \uBCF4\uC815)");
1005
+ if (site.para.inTextbox) warnings.push("\uAE00\uC0C1\uC790 \uC575\uCEE4 \u2014 \uAE00\uC0C1\uC790 \uAE30\uD558\uB97C \uBC18\uC601\uD558\uC9C0 \uC54A\uC544 \uC815\uB82C \uBCF4\uC815\uC774 \uADFC\uC0AC\uC785\uB2C8\uB2E4 (dx/dy \uB85C \uBCF4\uC815)");
978
1006
  placed.push({
979
1007
  anchor: op.anchor,
980
1008
  occurrence: wantOcc,
@@ -983,7 +1011,8 @@ async function placeSealHwpx(hwpxBuffer, ops) {
983
1011
  posXMm: Math.round(posXMm * 100) / 100,
984
1012
  posYMm: Math.round(posYMm * 100) / 100,
985
1013
  sizeMm: Math.round(sizeMm * 100) / 100,
986
- entry
1014
+ entry,
1015
+ ...warnings.length > 0 ? { warnings } : {}
987
1016
  });
988
1017
  }
989
1018
  const encoder = new TextEncoder();
@@ -1016,4 +1045,4 @@ export {
1016
1045
  patchZipEntries,
1017
1046
  placeSealHwpx
1018
1047
  };
1019
- //# sourceMappingURL=chunk-FGL7BH4P.js.map
1048
+ //# sourceMappingURL=chunk-7WUTJXKP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/form/seal.ts","../src/roundtrip/source-map.ts","../src/roundtrip/zip-patch.ts"],"sourcesContent":["/**\n * place_seal — 도장/서명 이미지를 앵커 문구(\"(인)\"·\"서명 또는 인\" 등) 위에\n * 부유(글 앞) 배치. 표/페이지 불확장 (P6, claw-hwp placeSeal 이식).\n *\n * 원리:\n * - 앵커 문구가 든 문단을 찾아 그 문단에 <hp:pic> 부유 개체를 anchor 한다.\n * 핵심 속성: treatAsChar=\"0\" + flowWithText=\"0\" + allowOverlap=\"1\" +\n * textWrap=\"IN_FRONT_OF_TEXT\" — flowWithText=\"1\"이면 한컴이 개체 높이만큼\n * 셀/페이지를 키운다 (claw-hwp GT 검증 규칙).\n * - 수평 위치는 폰트 메트릭으로 계산: 전각(한글/CJK)=1em, ASCII·반각=0.5em,\n * em = 앵커 run charPr 높이(1/100pt). 렌더 없이 배치하고 렌더는 검증용.\n * - 가운데/오른쪽 정렬 문단(서식 셀에 흔함)은 블록 전체가 밀리므로\n * (사용가능폭 − 문단폭)의 정렬 이동분을 x에 더한다.\n * - 위치 프레임은 PARA(문단 좌상단 기준) — 한컴은 문단 위로는 못 올라가게\n * 클램프하므로, 여유 공간이 있는 표 셀에서는 줄 세로 중앙에 앉고 본문\n * 최상단 줄에서는 상단 정렬로 눕는다.\n *\n * 적용 범위: 본문/표 셀/글상자 문단 (머리말·꼬리말·각주 내 앵커는 제외).\n * ZIP 재조립은 patchZipEntries — 비변경 엔트리는 1바이트도 건드리지 않고,\n * 도장 PNG는 BinData 신규 엔트리 + manifest(opf:item) 등재로 추가한다.\n */\n\nimport JSZip from \"jszip\"\nimport { KordocError } from \"../utils.js\"\nimport {\n scanSectionXml, applySplices,\n type ScanParagraph, type ScanCell, type ScanTable, type SectionScan, type SpliceEdit,\n} from \"../roundtrip/source-map.js\"\nimport { patchZipEntries } from \"../roundtrip/zip-patch.js\"\nimport { parseRenderStyles } from \"../render/head-styles.js\"\n\nconst HU_PER_MM = 7200 / 25.4\nconst mm2hu = (mm: number): number => Math.round(mm * HU_PER_MM)\n\n/** 도장 배치 요청 */\nexport interface SealOp {\n /** 앵커 문구 (예: \"(인)\", \"서명 또는 인\") */\n anchor: string\n /** 같은 앵커가 여럿일 때 0-based 선택 (기본 0) */\n occurrence?: number\n /** 도장/서명 이미지 바이트 (투명 배경 PNG 권장) */\n image: Uint8Array\n /** 이미지 확장자 (기본 png) */\n ext?: \"png\" | \"jpg\" | \"jpeg\" | \"bmp\" | \"gif\"\n /** 도장 한 변 크기 mm (기본: 줄높이×1.6, 7~18mm 클램프) */\n sizeMm?: number\n /** overlap=문구 위에 겹침, right=문구 오른쪽 옆, auto=공간 있으면 right (기본 auto) */\n mode?: \"overlap\" | \"right\" | \"auto\"\n /** 미세조정 mm */\n dxMm?: number\n dyMm?: number\n}\n\n/** 배치 결과 (도장 1개당 1건) */\nexport interface SealPlacement {\n anchor: string\n occurrence: number\n sectionIndex: number\n mode: \"overlap\" | \"right\"\n posXMm: number\n posYMm: number\n sizeMm: number\n /** ZIP에 추가된 이미지 파트 경로 (BinData/imageN.ext) */\n entry: string\n /** 근사 한계 경고 — 탭·줄바꿈 문단(seal-8), 중첩표(seal-2), 글상자(seal-3). 실측 전 근사. */\n warnings?: string[]\n}\n\nexport interface PlaceSealResult {\n buffer: ArrayBuffer\n placed: SealPlacement[]\n}\n\nconst MIME: Record<string, string> = {\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n bmp: \"image/bmp\",\n gif: \"image/gif\",\n}\n\n/** 이미지 매직바이트가 확장자와 일치하는지 — .png 이름의 비-PNG(HTML 에러페이지·텍스트)가\n * BinData 에 매장되고 image/png 로 등재되는 것을 차단 */\nfunction imageMagicMatches(buf: Uint8Array, ext: string): boolean {\n switch (ext) {\n case \"png\": return buf.length >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47\n case \"jpg\": case \"jpeg\": return buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff\n case \"bmp\": return buf.length >= 2 && buf[0] === 0x42 && buf[1] === 0x4d\n case \"gif\": return buf.length >= 6 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46\n default: return false\n }\n}\n\n/** 코드 유닛 하나의 시각 폭(em) — CJK/한글/전각=1, ASCII·반각(공백 포함)=0.5, 제어=0 */\nfunction glyphEm(code: number): number {\n if (code < 0x20) return 0\n if (code <= 0x7e) return 0.5\n if (code >= 0xff61 && code <= 0xffdc) return 0.5 // 반각 가나/자모\n return 1.0\n}\n\n/** 문자열의 폰트 메트릭 폭(mm) */\nfunction measureMm(text: string, emMm: number): number {\n let w = 0\n for (const ch of text) w += glyphEm(ch.codePointAt(0) ?? 0) * emMm\n return w\n}\n\n/** 문단이 속한 셀·표 (앵커 문단 → 셀 폭·좌측 오프셋·정렬 이동 계산용) */\ninterface ParaSite {\n para: ScanParagraph\n cell?: ScanCell\n table?: ScanTable\n /** 중첩표(depth>0) 셀에 속한 문단 — seal-2 근사 한계 경고용 */\n nested?: boolean\n /** 바깥(조상) 셀 체인 — 중첩표 앵커 가로 원점에 바깥 셀 좌측 오프셋 가산용 (seal-2) */\n outer?: Array<{ cell: ScanCell; table: ScanTable }>\n}\n\n/** 섹션의 본문+셀+글상자 문단을 문서 순서로 평탄화 (머리말·각주 등 excluded 제외) */\nfunction collectSites(scan: SectionScan): ParaSite[] {\n const sites: ParaSite[] = []\n for (const p of scan.bodyParagraphs) sites.push({ para: p })\n const walkTables = (tables: ScanTable[], depth: number, outer: Array<{ cell: ScanCell; table: ScanTable }>): void => {\n if (depth > 16) return\n for (const t of tables) {\n for (const row of t.rows) {\n for (const cell of row) {\n for (const p of cell.paragraphs) sites.push({ para: p, cell, table: t, nested: depth > 0, outer: outer.length ? outer : undefined })\n walkTables(cell.tables, depth + 1, [...outer, { cell, table: t }])\n }\n }\n }\n }\n walkTables(scan.tables, 0, [])\n walkTables(scan.orphanTables, 0, [])\n sites.sort((a, b) => a.para.start - b.para.start)\n return sites\n}\n\n/** 여는 태그 문자열에서 속성값 추출 */\nfunction attrOf(openTag: string, name: string): string | undefined {\n const m = openTag.match(new RegExp(`\\\\b${name}=\"([^\"]*)\"`))\n return m?.[1]\n}\n\n/** para.start의 <hp:p ...> 여는 태그 전체 */\nfunction paraOpenTag(xml: string, para: ScanParagraph): string {\n const end = xml.indexOf(\">\", para.start)\n return end === -1 ? \"\" : xml.slice(para.start, end + 1)\n}\n\n/** 앵커가 든 run의 charPrIDRef·네임스페이스 프리픽스·run 닫는 위치 */\nfunction anchorRunInfo(\n xml: string,\n para: ScanParagraph,\n anchor: string,\n): { charPr: string; prefix: string; insertAt: number } | null {\n // 앵커 문자열이 통째로 들어있는 tRange 우선, 없으면 마지막 tRange (run 경계에 걸친 앵커)\n let tr = para.tRanges.find(r => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(anchor))\n if (!tr) {\n // 앵커가 run 경계에 걸침(부분 굵게·엔티티 분할) — 마지막 run 이 아니라 앵커 첫 글자가\n // 든 run(앵커 대표 크기)으로 폴백해 글자 크기 배율 드리프트를 줄인다.\n const head = anchor[0]\n tr = para.tRanges.find(r => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(head))\n ?? para.tRanges[para.tRanges.length - 1]\n }\n if (!tr) return null\n\n const before = xml.slice(Math.max(0, para.start), tr.contentStart)\n const runOpen = [...before.matchAll(/<([A-Za-z0-9]+):run\\b[^>]*>/g)].pop()\n const prefix = runOpen?.[1] ?? tr.prefix ?? \"hp\"\n const charPr = (runOpen && attrOf(runOpen[0], \"charPrIDRef\")) || \"0\"\n const close = xml.indexOf(`</${prefix}:run>`, tr.contentEnd)\n if (close === -1) return null\n return { charPr, prefix, insertAt: close + `</${prefix}:run>`.length }\n}\n\n/** 셀 <hp:tc> 안의 cellAddr 근접 창 — cellSz/cellMargin 은 cellAddr 의 형제\n * (한컴 저장본: addr→span→sz→margin→subList, kordoc 생성본: subList→addr→span→sz→margin) */\nfunction cellAddrWindow(xml: string, cell: ScanCell): string | null {\n if (!cell.addrTagRange) return null\n return xml.slice(cell.addrTagRange.end, cell.addrTagRange.end + 400)\n}\n\n/** 셀의 cellSz 폭(hwpunit) */\nfunction cellSzWidthHu(xml: string, cell: ScanCell): number | null {\n const win = cellAddrWindow(xml, cell)\n if (win) {\n const m = win.match(/<[A-Za-z0-9]+:cellSz\\b[^>]*\\bwidth=\"(\\d+)\"/)\n if (m) return Number(m[1])\n }\n // 폴백: 첫 문단 앞 역탐색 (addrTagRange 없는 변형 — 한컴 저장본 배치에서 유효)\n const firstPara = cell.paragraphs[0]\n if (!firstPara) return null\n const upto = xml.slice(0, firstPara.start)\n const szMatch = [...upto.matchAll(/<[A-Za-z0-9]+:cellSz\\b[^>]*\\bwidth=\"(\\d+)\"[^>]*>/g)].pop()\n return szMatch ? Number(szMatch[1]) : null\n}\n\n/** 셀의 콘텐츠 폭(mm) — cellSz − cellMargin 좌우 */\nfunction cellContentWidthMm(xml: string, cell: ScanCell): number | null {\n const width = cellSzWidthHu(xml, cell)\n if (width === null) return null\n let content = width\n const win = cellAddrWindow(xml, cell)\n const mg = win?.match(/<[A-Za-z0-9]+:cellMargin\\b[^>]*>/)\n if (mg) {\n content -= Number(attrOf(mg[0], \"left\") ?? 0) + Number(attrOf(mg[0], \"right\") ?? 0)\n }\n return content > 0 ? content / HU_PER_MM : null\n}\n\n/**\n * 앵커 셀의 표 내 좌측 오프셋(mm) — 같은 행에서 colAddr 이 앞서는 셀들의 cellSz 폭 합.\n *\n * 한컴은 셀 문단에 anchor 된 front 부유(flowWithText=0) 개체의 가로 오프셋을\n * 셀이 아니라 **단(컬럼) 원점**에서 잰다(P1 실측 — PARA/COLUMN 동일). 그래서\n * 셀 내부 좌표에 셀의 좌측 오프셋을 더해야 화면상 앵커 문구를 따라간다.\n * 한계: rowSpan 이 얽힌 복잡 그리드·들여쓴 표는 근사가 어긋날 수 있다(dx 로 보정).\n */\nfunction cellLeftOffsetMm(xml: string, table: ScanTable, cell: ScanCell): number {\n if (cell.colAddr === undefined) return 0\n // 표 전체 행을 훑어 colAddr별 열폭 테이블을 구성한다 — rowSpan 으로 덮인 행의 <hp:tr>\n // 에는 선행 열 셀 엔트리가 없어(그 행만 보면 오프셋 0), 결재란 세로병합에서 도장이 한\n // 열(약 77mm) 왼쪽에 찍혔다. 다른 행에서 같은 colAddr 의 열폭을 조회해 면역시킨다.\n const colW = new Map<number, number>()\n for (const row of table.rows) {\n for (const c of row) {\n if (c.colAddr === undefined || colW.has(c.colAddr)) continue\n // colSpan>1 병합 셀은 폭이 여러 열에 걸쳐 있어 한 colAddr 에 귀속되지 않는다 — 단일\n // 열 셀로만 열폭을 확정해 가로 병합 제목행에서의 이중계상(도장 표 밖)을 막는다 (seal-1 colspan).\n if (c.colSpan > 1) continue\n const w = cellSzWidthHu(xml, c)\n if (w) colW.set(c.colAddr, w)\n }\n }\n let sum = 0\n for (let col = 0; col < cell.colAddr; col++) sum += colW.get(col) ?? 0\n return sum / HU_PER_MM\n}\n\n/** 본문 단 폭(mm) — secPr pagePr(용지 − 좌우 여백), 실패 시 150mm */\nfunction bodyColumnWidthMm(xml: string): number {\n const page = xml.match(/<[A-Za-z0-9]+:pagePr\\b[^>]*\\bwidth=\"(\\d+)\"[^>]*>/)\n const margin = xml.match(/<[A-Za-z0-9]+:margin\\b[^>]*\\bleft=\"(\\d+)\"[^>]*\\bright=\"(\\d+)\"[^>]*>/)\n if (!page) return 150\n const w = Number(page[1]) - (margin ? Number(margin[1]) + Number(margin[2]) : 0)\n return w > 0 ? w / HU_PER_MM : 150\n}\n\n/** 부유(글 앞) hp:pic XML — claw-hwp buildPic 폴백 템플릿의 float 변형 */\nfunction buildFloatPicXml(\n itemId: string,\n sizeHu: number,\n posXHu: number,\n posYHu: number,\n ids: { id: number; instid: number },\n): string {\n const w = sizeHu\n const h = sizeHu\n // xmlns:hc 자체 선언 — kordoc 생성 section0.xml 은 hc 프리픽스를 선언하지 않아\n // (한컴 저장본은 선언), pic 요소에 지역 선언해야 웰폼드가 유지된다 (중복 선언 무해).\n return (\n `<hp:pic xmlns:hc=\"http://www.hancom.co.kr/hwpml/2011/core\" id=\"${ids.id}\" zOrder=\"0\" numberingType=\"PICTURE\" textWrap=\"IN_FRONT_OF_TEXT\" textFlow=\"BOTH_SIDES\" lock=\"0\" dropcapstyle=\"None\" href=\"\" groupLevel=\"0\" instid=\"${ids.instid}\" reverse=\"0\">` +\n `<hp:offset x=\"0\" y=\"0\"/><hp:orgSz width=\"${w}\" height=\"${h}\"/><hp:curSz width=\"${w}\" height=\"${h}\"/>` +\n `<hp:flip horizontal=\"0\" vertical=\"0\"/><hp:rotationInfo angle=\"0\" centerX=\"${Math.round(w / 2)}\" centerY=\"${Math.round(h / 2)}\" rotateimage=\"1\"/>` +\n `<hp:renderingInfo><hc:transMatrix e1=\"1\" e2=\"0\" e3=\"0\" e4=\"0\" e5=\"1\" e6=\"0\"/><hc:scaMatrix e1=\"1\" e2=\"0\" e3=\"0\" e4=\"0\" e5=\"1\" e6=\"0\"/><hc:rotMatrix e1=\"1\" e2=\"0\" e3=\"0\" e4=\"0\" e5=\"1\" e6=\"0\"/></hp:renderingInfo>` +\n `<hp:imgRect><hc:pt0 x=\"0\" y=\"0\"/><hc:pt1 x=\"${w}\" y=\"0\"/><hc:pt2 x=\"${w}\" y=\"${h}\"/><hc:pt3 x=\"0\" y=\"${h}\"/></hp:imgRect>` +\n `<hp:imgClip left=\"0\" right=\"${w}\" top=\"0\" bottom=\"${h}\"/><hp:inMargin left=\"0\" right=\"0\" top=\"0\" bottom=\"0\"/>` +\n `<hp:imgDim dimwidth=\"${w}\" dimheight=\"${h}\"/>` +\n `<hc:img binaryItemIDRef=\"${itemId}\" bright=\"0\" contrast=\"0\" effect=\"REAL_PIC\" alpha=\"0\"/><hp:effects/>` +\n `<hp:sz width=\"${w}\" widthRelTo=\"ABSOLUTE\" height=\"${h}\" heightRelTo=\"ABSOLUTE\" protect=\"0\"/>` +\n // horzRelTo=COLUMN: 셀 안에서는 셀 콘텐츠 영역이 원점(한컴 실측 — v3.10 사진대지),\n // 본문에서는 단 왼쪽이 원점이라 PARA와 동일하게 동작한다. PARA를 쓰면 셀 내부\n // 개체의 가로 원점을 한컴이 표를 담은 바깥 문단으로 잡아 도장이 옆 셀로 밀린다\n // (P1 시각검증 실측). 세로는 PARA 유지 — 앵커 줄 기준 배치.\n `<hp:pos treatAsChar=\"0\" affectLSpacing=\"0\" flowWithText=\"0\" allowOverlap=\"1\" holdAnchorAndSO=\"0\" vertRelTo=\"PARA\" horzRelTo=\"COLUMN\" vertAlign=\"TOP\" horzAlign=\"LEFT\" vertOffset=\"${posYHu}\" horzOffset=\"${posXHu}\"/>` +\n `<hp:outMargin left=\"0\" right=\"0\" top=\"0\" bottom=\"0\"/><hp:shapeComment>kordoc seal</hp:shapeComment>` +\n `</hp:pic>`\n )\n}\n\n/**\n * HWPX에 도장/서명 이미지를 앵커 문구 기준으로 부유 배치한다.\n *\n * @param hwpxBuffer 원본 HWPX\n * @param ops 도장 배치 요청 (여러 개 가능 — 같은 이미지 재사용 시에도 op마다 파트가 추가됨)\n * @throws KordocError 앵커 미발견 (본문 내 등장 횟수를 메시지에 포함)\n */\nexport async function placeSealHwpx(hwpxBuffer: ArrayBuffer, ops: SealOp[]): Promise<PlaceSealResult> {\n if (ops.length === 0) throw new KordocError(\"place_seal: 배치할 도장이 없습니다\")\n const u8 = new Uint8Array(hwpxBuffer)\n const zip = await JSZip.loadAsync(hwpxBuffer)\n\n const sectionPaths = Object.keys(zip.files)\n .filter(name => /[Ss]ection\\d+\\.xml$/i.test(name))\n // 사전순 대신 숫자순 — section10 이 section2 앞에 오면 occurrence·sectionIndex 가\n // 파서(zip-sections compareSectionPaths)의 문서 순서와 어긋나 엉뚱한 위치에 날인된다.\n .sort((a, b) => (Number(a.match(/(\\d+)\\.xml$/i)?.[1] ?? 0)) - (Number(b.match(/(\\d+)\\.xml$/i)?.[1] ?? 0)))\n if (sectionPaths.length === 0) throw new KordocError(\"HWPX에서 섹션 파일을 찾을 수 없습니다\")\n\n const manifestPath = Object.keys(zip.files).find(name => /\\.hpf$/i.test(name))\n\n // 스타일 (charPr 높이 = 1/100pt, 문단 정렬)\n const headerPath = Object.keys(zip.files).find(name => /(^|\\/)header\\.xml$/i.test(name))\n const styles = headerPath ? parseRenderStyles(await zip.file(headerPath)!.async(\"text\")) : null\n\n const sectionXmls: string[] = []\n const scans: SectionScan[] = []\n for (let si = 0; si < sectionPaths.length; si++) {\n const xml = await zip.file(sectionPaths[si])!.async(\"text\")\n sectionXmls.push(xml)\n scans.push(scanSectionXml(xml, si))\n }\n const sitesBySection = scans.map(collectSites)\n\n // 기존 BinData 번호·manifest id와 충돌하지 않는 시작 번호\n const usedIds = new Set<string>()\n let manifestXml = \"\"\n if (manifestPath) {\n manifestXml = await zip.file(manifestPath)!.async(\"text\")\n for (const m of manifestXml.matchAll(/<opf:item\\b[^>]*\\bid=\"([^\"]+)\"/g)) usedIds.add(m[1])\n }\n const usedImageNums = new Set<number>()\n for (const name of Object.keys(zip.files)) {\n const m = name.match(/^BinData\\/(?:image|img)(\\d+)\\./i)\n if (m) usedImageNums.add(Number(m[1]))\n }\n const nextImageNum = (): number => {\n let n = 1\n while (usedImageNums.has(n) || usedIds.has(`image${n}`)) n++\n usedImageNums.add(n)\n return n\n }\n\n // 개체 id — 문서 내 기존 숫자 id 최댓값 다음부터 (충돌 방지·결정적)\n let maxId = 1_000_000\n for (const xml of sectionXmls) {\n for (const m of xml.matchAll(/\\b(?:id|instid)=\"(\\d+)\"/g)) {\n const v = Number(m[1])\n if (Number.isFinite(v) && v > maxId) maxId = v\n }\n }\n\n const splicesBySection: SpliceEdit[][] = sectionPaths.map(() => [])\n const additions = new Map<string, Uint8Array>()\n const manifestItems: string[] = []\n const placed: SealPlacement[] = []\n\n for (const op of ops) {\n if (!op.anchor) throw new KordocError(\"place_seal: anchor 문구가 필요합니다\")\n if (!op.image || op.image.length === 0) throw new KordocError(\"place_seal: 도장 이미지가 필요합니다\")\n const ext = (op.ext ?? \"png\").toLowerCase()\n if (!MIME[ext]) throw new KordocError(`place_seal: 지원하지 않는 이미지 확장자 .${ext} (png/jpg/bmp/gif)`)\n if (!imageMagicMatches(op.image, ext)) throw new KordocError(`place_seal: 이미지 내용이 .${ext} 형식이 아닙니다 (매직바이트 불일치)`)\n const wantOcc = op.occurrence ?? 0\n\n // 앵커 탐색 — 전 섹션 문서 순서, 문단 내 다중 등장 포함\n let found: { si: number; site: ParaSite; idxInText: number } | null = null\n let total = 0\n for (let si = 0; si < scans.length && !found; si++) {\n for (const site of sitesBySection[si]) {\n let from = 0\n for (;;) {\n const idx = site.para.text.indexOf(op.anchor, from)\n if (idx === -1) break\n if (total === wantOcc) {\n found = { si, site, idxInText: idx }\n break\n }\n total++\n from = idx + op.anchor.length\n }\n if (found) break\n }\n }\n if (!found) {\n // 전체 등장 수를 처음부터 다시 세어 에러 메시지에 (claw-hwp와 동일한 안내)\n total = 0\n for (let si = 0; si < scans.length; si++) {\n for (const site of sitesBySection[si]) {\n let from = 0\n for (;;) {\n const idx = site.para.text.indexOf(op.anchor, from)\n if (idx === -1) break\n total++\n from = idx + op.anchor.length\n }\n }\n }\n throw new KordocError(\n `place_seal: 앵커 \"${op.anchor}\" ${wantOcc}번째 등장을 찾지 못했습니다 ` +\n `(본문 내 ${total}회 등장 — occurrence 0..${Math.max(0, total - 1)})`,\n )\n }\n\n const { si, site, idxInText } = found\n const xml = sectionXmls[si]\n const run = anchorRunInfo(xml, site.para, op.anchor)\n if (!run) {\n throw new KordocError(`place_seal: 앵커 \"${op.anchor}\" 문단에서 run을 찾지 못했습니다`)\n }\n\n // 폰트 메트릭\n const charPrHeight = styles?.charPr.get(run.charPr)?.height ?? 1000 // 1/100pt\n const fontPt = charPrHeight / 100\n const emMm = (fontPt * 25.4) / 72\n const lineHMm = emMm\n const startXMm = measureMm(site.para.text.slice(0, idxInText), emMm)\n const anchorWMm = measureMm(op.anchor, emMm)\n // NaN·음수 sizeMm(CLI 비숫자·직접 호출)은 기본 크기로 폴백 — hp:sz width=\"NaN\" 등\n // OWPML 정수 속성에 NaN 문자열이 기록되던 것 원천 차단 (seal-4)\n const sizeMm = (op.sizeMm != null && Number.isFinite(op.sizeMm) && op.sizeMm > 0)\n ? op.sizeMm\n : Math.max(7, Math.min(18, lineHMm * 1.6))\n\n // 사용가능 폭 + 정렬 이동\n const availMm = (site.cell ? cellContentWidthMm(xml, site.cell) : null) ?? bodyColumnWidthMm(xml)\n const paraPrId = attrOf(paraOpenTag(xml, site.para), \"paraPrIDRef\") ?? \"0\"\n const align = styles?.paraAlign.get(paraPrId)\n let alignShiftMm = 0\n if (align === \"CENTER\" || align === \"RIGHT\") {\n const paraWMm = measureMm(site.para.text, emMm)\n alignShiftMm = align === \"CENTER\" ? (availMm - paraWMm) / 2 : availMm - paraWMm\n if (!Number.isFinite(alignShiftMm) || alignShiftMm < 0) alignShiftMm = 0\n }\n\n let mode = op.mode ?? \"auto\"\n if (mode === \"auto\") {\n mode = availMm - (alignShiftMm + startXMm + anchorWMm) >= sizeMm + 2 ? \"right\" : \"overlap\"\n }\n\n // 셀 앵커: 한컴이 front 부유 가로 오프셋을 단(컬럼) 원점에서 재므로 셀 좌측 오프셋 가산.\n // 중첩표는 바깥(조상) 셀 좌측 오프셋도 체인으로 더한다 — 실측 확정: 바깥 셀 미가산 시\n // 도장이 바깥 열 폭만큼 왼쪽(옆 셀)으로 밀린다 (seal-2, '항상 페이지 단' 모델).\n const cellShiftMm = site.cell && site.table\n ? cellLeftOffsetMm(xml, site.table, site.cell) +\n (site.outer ?? []).reduce((s, o) => s + cellLeftOffsetMm(xml, o.table, o.cell), 0)\n : 0\n const posXMm =\n (mode === \"right\" ? startXMm + anchorWMm + 2 : startXMm + anchorWMm / 2 - sizeMm / 2) +\n alignShiftMm + cellShiftMm + (op.dxMm ?? 0)\n // 줄 세로 중앙 — PARA 프레임은 문단 위로 클램프되므로 본문 최상단 줄에선 상단 정렬로 동작\n const posYMm = -(sizeMm - lineHMm) / 2 + (op.dyMm ?? 0)\n\n // 이미지 파트 + manifest 아이템\n const n = nextImageNum()\n const entry = `BinData/image${n}.${ext}`\n const itemId = `image${n}`\n usedIds.add(itemId)\n additions.set(entry, op.image)\n manifestItems.push(`<opf:item id=\"${itemId}\" href=\"${entry}\" media-type=\"${MIME[ext]}\" isEmbeded=\"1\"/>`)\n\n // 부유 pic run 삽입 (앵커 run 뒤 — PARA 프레임이라 run 위치는 배치에 영향 없음)\n const pic = buildFloatPicXml(itemId, mm2hu(sizeMm), mm2hu(posXMm), mm2hu(posYMm), {\n id: ++maxId,\n instid: ++maxId,\n })\n splicesBySection[si].push({\n start: run.insertAt,\n end: run.insertAt,\n replacement: `<${run.prefix}:run charPrIDRef=\"${run.charPr}\">${pic}</${run.prefix}:run>`,\n })\n\n // 근사 한계 경고 — 메트릭·원점 모델이 근사인 케이스를 결과에 명시(placed 소비자가 dx/dy 보정)\n const warnings: string[] = []\n const paraSeg = xml.slice(site.para.start, run.insertAt)\n if (/<[A-Za-z0-9]+:tab\\b/.test(paraSeg)) warnings.push(\"탭이 든 문단 — 도장 가로 위치가 근사값입니다 (dx 로 보정)\")\n if (/<[A-Za-z0-9]+:(lineBreak|br)\\b/.test(paraSeg)) warnings.push(\"줄바꿈이 든 문단 — 앵커가 2번째 줄 이하면 세로 위치가 어긋날 수 있습니다 (dy 로 보정)\")\n if (site.nested) warnings.push(\"중첩표(표 안 표) 셀 앵커 — 바깥 셀 오프셋을 체인으로 가산하나 셀 여백·정렬은 근사입니다 (필요 시 dx 로 보정)\")\n if (site.para.inTextbox) warnings.push(\"글상자 앵커 — 글상자 기하를 반영하지 않아 정렬 보정이 근사입니다 (dx/dy 로 보정)\")\n\n placed.push({\n anchor: op.anchor,\n occurrence: wantOcc,\n sectionIndex: si,\n mode,\n posXMm: Math.round(posXMm * 100) / 100,\n posYMm: Math.round(posYMm * 100) / 100,\n sizeMm: Math.round(sizeMm * 100) / 100,\n entry,\n ...(warnings.length > 0 ? { warnings } : {}),\n })\n }\n\n // 재조립 — 변경 섹션 + manifest 교체, 이미지 파트 추가\n const encoder = new TextEncoder()\n const replacements = new Map<string, Uint8Array>()\n for (let si = 0; si < sectionPaths.length; si++) {\n if (splicesBySection[si].length === 0) continue\n replacements.set(sectionPaths[si], encoder.encode(applySplices(sectionXmls[si], splicesBySection[si])))\n }\n if (manifestPath && manifestItems.length > 0) {\n const patched = manifestXml.includes(\"</opf:manifest>\")\n ? manifestXml.replace(\"</opf:manifest>\", `${manifestItems.join(\"\")}</opf:manifest>`)\n : manifestXml\n if (patched !== manifestXml) replacements.set(manifestPath, encoder.encode(patched))\n }\n\n const out = patchZipEntries(u8, replacements, additions)\n return {\n buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength) as ArrayBuffer,\n placed,\n }\n}\n","/**\n * HWPX 라운드트립 소스맵 — section XML 문자열에서 문단/표 셀의 문자 범위를 추적.\n *\n * DOM 재직렬화를 거치지 않고(속성 순서·공백·자기닫힘 표기까지 보존하기 위해)\n * 정규식 토크나이저로 태그를 순회하며, 각 <hp:p>가 소유한 <hp:t> 콘텐츠 범위를\n * 기록한다. 패치는 이 범위에 대한 문자열 splice로만 수행되어\n * 변경 문단 외 XML 바이트가 그대로 보존된다 (filler-hwpx.ts 패턴의 오프셋 버전).\n */\n\n// ─── 타입 ───────────────────────────────────────────\n\n/** <hp:t> 콘텐츠 범위 — [contentStart, contentEnd) 가 여는/닫는 태그 사이 */\nexport interface TRange {\n contentStart: number\n contentEnd: number\n /** 자기닫힘 <hp:t/> — 범위가 태그 전체이며, 치환 시 태그째 교체 */\n selfClosing?: boolean\n /** 자기닫힘 치환용 네임스페이스 프리픽스 (예: \"hp\") */\n prefix?: string\n}\n\nexport type ScanParaKind = \"body\" | \"cell\" | \"draw\" | \"excluded\"\n\n/** 스캔된 문단 — 소유한 hp:t 범위와 합산 텍스트 */\nexport interface ScanParagraph {\n sectionIndex: number\n kind: ScanParaKind\n /** <hp:p ...> 여는 태그 시작 위치 (문서 순서 정렬용) */\n start: number\n /** 소유 hp:t 콘텐츠 범위 (문서 순서) */\n tRanges: TRange[]\n /** hp:t 텍스트 합산 (엔티티 디코딩, 내부 태그는 공백화) */\n text: string\n /** hp:t가 하나도 없을 때 텍스트 삽입 가능한 위치 (첫 run 닫는 태그 앞) */\n runInsertPos?: number\n /** runInsertPos에 삽입할 때 쓸 t 태그 prefix (예: \"hp\") */\n runPrefix?: string\n /** 자기닫힘 <hp:run/> 태그 범위 — t 삽입 시 펼쳐서 사용 (한컴 빈 문단 패턴) */\n selfCloseRun?: { start: number; end: number }\n /** 글상자(drawText) 경유 문단 — 셀 라벨 판정 등에서 본문과 구분 (v3.1) */\n inTextbox?: boolean\n}\n\n/** 스캔된 표 셀 — 앵커 좌표와 셀 내부 문단 */\nexport interface ScanCell {\n rowAddr?: number\n colAddr?: number\n colSpan: number\n rowSpan: number\n paragraphs: ScanParagraph[]\n /** 셀 내부 중첩표 (문서 순서) */\n tables: ScanTable[]\n /** <hp:cellAddr> 태그 범위 (자기닫힘=태그 전체, 펼친형=여는 태그) — rowAddr 재작성용 */\n addrTagRange?: { start: number; end: number }\n}\n\n/** 스캔된 표 */\nexport interface ScanTable {\n sectionIndex: number\n start: number\n /** 최상위 표 여부 (다른 표/ctrl/캡션 내부가 아님) */\n topLevel: boolean\n /** 비어있지 않은 행들 (tr 순서) */\n rows: ScanCell[][]\n /** rows[i]의 <hp:tr>...</hp:tr> XML 범위 (행 추가/삭제 splice용, rows와 정렬) */\n rowRanges: { start: number; end: number }[]\n /** 앵커 좌표 → 셀 (\"r,c\") */\n cellByAnchor: Map<string, ScanCell>\n}\n\n/** 섹션 하나의 스캔 결과 */\nexport interface SectionScan {\n sectionIndex: number\n xml: string\n /** body + draw 문단 (문서 순서) — 본문 텍스트 매핑 대상 */\n bodyParagraphs: ScanParagraph[]\n /** 최상위 표 (문서 순서) */\n tables: ScanTable[]\n /** 머리말/꼬리말 문단 텍스트 (OB 앞/뒤 배치 블록 식별용) */\n headerTexts: string[]\n footerTexts: string[]\n /**\n * 파서 비가시 영역(머리말/꼬리말/각주/캡션/메모 등 ctrl 내부) 문단 — v3.1.\n * patcher 매핑 대상이 아니며, filler가 이 영역의 인라인 패턴을 채울 때 사용.\n */\n excludedParagraphs: ScanParagraph[]\n /**\n * 어느 셀에도 속하지 않는 비최상위 표(머리말/꼬리말 등 ctrl 내부) — v3.1.\n * filler 전용. patcher의 표 서수 매핑(tables)에는 포함되지 않는다.\n */\n orphanTables: ScanTable[]\n}\n\n/** 문자열 splice 편집 — [start, end) 를 replacement로 치환 */\nexport interface SpliceEdit {\n start: number\n end: number\n replacement: string\n}\n\n// ─── XML 텍스트 유틸 ─────────────────────────────────\n\n/** XML 텍스트 노드 이스케이프 (속성 아님 — & < > 만) */\nexport function escapeXmlText(text: string): string {\n return text.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n}\n\n/** XML 엔티티 디코딩 */\nexport function decodeXmlEntities(text: string): string {\n return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent: string) => {\n switch (ent) {\n case \"lt\": return \"<\"\n case \"gt\": return \">\"\n case \"amp\": return \"&\"\n case \"quot\": return '\"'\n case \"apos\": return \"'\"\n }\n try {\n const code = ent[1] === \"x\" || ent[1] === \"X\"\n ? parseInt(ent.slice(2), 16)\n : parseInt(ent.slice(1), 10)\n if (!isNaN(code) && code >= 0 && code <= 0x10ffff) return String.fromCodePoint(code)\n } catch { /* 잘못된 참조는 원본 유지 */ }\n return m\n })\n}\n\n/** hp:t 콘텐츠에서 텍스트 추출 — 파서가 공백류로 취급하는 태그(tab/fwSpace/hwSpace/\n * br/lineBreak)만 공백, 나머지 태그(markpen 등 파서가 무시)는 제거 (파서 모델 정합) */\nfunction tContentToText(raw: string): string {\n return decodeXmlEntities(\n raw\n .replace(/<\\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\\s[^>]*)?\\/?>/g, \" \")\n .replace(/<[^>]*>/g, \"\"),\n )\n}\n\n// ─── 토크나이저 ──────────────────────────────────────\n\nconst TAG_RE = /<!--[\\s\\S]*?-->|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|<\\?[\\s\\S]*?\\?>|<!(?:\"[^\"]*\"|'[^']*'|[^>\"'])*>|<\\/([^\\s>]+)\\s*>|<([^\\s/>!?]+)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*?)(\\/?)>/g\n\n/** hp:t 소유권을 끊는 컨테이너 — 이 안의 t는 호스트 문단 텍스트가 아님 */\nconst T_BARRIER = new Set([\n \"tbl\", \"ctrl\", \"caption\", \"pic\", \"shape\", \"drawingObject\", \"drawText\",\n \"shapeComment\", \"memogroup\", \"memo\", \"hiddenComment\", \"equation\",\n \"parameters\", \"subList\", \"p\",\n])\n\n/** 문단 분류용 컨테이너 (가장 가까운 것 기준) */\nconst PARA_CONTAINER = new Set([\n \"tc\", \"ctrl\", \"caption\", \"drawText\", \"pic\", \"shape\", \"drawingObject\",\n \"memogroup\", \"memo\", \"hiddenComment\",\n \"footNote\", \"endNote\", \"fn\", \"en\", // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수\n])\n\n/** 표 분류 시 \"최상위\"가 아니게 만드는 조상 */\nconst TABLE_BARRIER = new Set([\n \"tbl\", \"ctrl\", \"caption\", \"memogroup\", \"memo\", \"hiddenComment\",\n])\n\nfunction localOf(qname: string): string {\n const i = qname.indexOf(\":\")\n return i >= 0 ? qname.slice(i + 1) : qname\n}\n\nfunction prefixOf(qname: string): string {\n const i = qname.indexOf(\":\")\n return i >= 0 ? qname.slice(0, i) : \"\"\n}\n\ninterface StackFrame {\n local: string\n qname: string\n /** 여는 태그가 끝나는 위치 (콘텐츠 시작) */\n contentStart: number\n}\n\ninterface OpenCtrlSub { kind: \"header\" | \"footer\"; texts: string[] }\n\n/**\n * section XML 한 개를 스캔하여 소스맵을 만든다.\n * 토크나이저는 xmldom과 무관하게 동작하며, 위치(오프셋) 정보가 핵심이다.\n */\nexport function scanSectionXml(xml: string, sectionIndex: number): SectionScan {\n const stack: StackFrame[] = []\n const bodyParagraphs: ScanParagraph[] = []\n const tables: ScanTable[] = []\n const headerTexts: string[] = []\n const footerTexts: string[] = []\n const excludedParagraphs: ScanParagraph[] = []\n const orphanTables: ScanTable[] = []\n\n // 진행 상태\n const paraStack: ScanParagraph[] = []\n const tableStack: ScanTable[] = []\n const rowStack: ScanCell[][] = [] // 테이블별 currentRow\n const trStartStack: number[] = [] // 테이블별 진행 중 <hp:tr> 시작 위치 (-1 = 없음)\n const cellStack: ScanCell[] = []\n let pendingT: { para: ScanParagraph; contentStart: number } | null = null\n const ctrlSubStack: OpenCtrlSub[] = []\n\n /** 스택 위에서 아래로 보며 가장 가까운 PARA_CONTAINER/장벽 판별.\n * drawText는 투과 — 바깥에 tc가 있으면(셀 안 글상자) 파서의 mergeBlocksIntoCell과\n * 동일하게 cell로 귀속, 아니면 draw(본문 글상자). */\n const classifyPara = (): { kind: ScanParaKind; inTextbox: boolean } => {\n let sawDrawText = false\n for (let i = stack.length - 1; i >= 0; i--) {\n const l = stack[i].local\n if (l === \"tc\") return { kind: \"cell\", inTextbox: sawDrawText }\n if (l === \"drawText\") { sawDrawText = true; continue }\n if (PARA_CONTAINER.has(l)) return { kind: \"excluded\", inTextbox: sawDrawText }\n }\n return sawDrawText ? { kind: \"draw\", inTextbox: true } : { kind: \"body\", inTextbox: false }\n }\n\n /** 현재 위치의 t/tab 등이 최상위 열린 문단 소유인지 (사이에 장벽 없음) */\n const owningPara = (): ScanParagraph | null => {\n if (paraStack.length === 0) return null\n for (let i = stack.length - 1; i >= 0; i--) {\n const l = stack[i].local\n if (l === \"p\") return paraStack[paraStack.length - 1]\n if (T_BARRIER.has(l)) return null\n }\n return null\n }\n\n const isTableTopLevel = (): boolean => {\n for (let i = stack.length - 1; i >= 0; i--) {\n if (TABLE_BARRIER.has(stack[i].local)) return false\n }\n return true\n }\n\n /** ctrl 내부 header/footer 수집 컨텍스트 */\n const currentCtrlSub = (): OpenCtrlSub | null =>\n ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null\n\n TAG_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = TAG_RE.exec(xml)) !== null) {\n const [full, closeName, openName, , selfClose] = m\n\n // 주석/CDATA/PI/DOCTYPE\n if (closeName === undefined && openName === undefined) continue\n\n if (closeName !== undefined) {\n // ── 닫는 태그 ──\n const local = localOf(closeName)\n\n // 진행 중인 hp:t 닫힘\n if (local === \"t\" && pendingT) {\n const { para, contentStart } = pendingT\n para.tRanges.push({ contentStart, contentEnd: m.index })\n para.text += tContentToText(xml.slice(contentStart, m.index))\n pendingT = null\n }\n\n // 스택 pop (불일치 태그 관용 처리 — 일치하는 프레임까지 pop)\n for (let i = stack.length - 1; i >= 0; i--) {\n if (stack[i].local === local) {\n stack.length = i\n break\n }\n }\n\n if (local === \"p\") {\n const para = paraStack.pop()\n if (para && para.kind === \"excluded\") {\n const sub = currentCtrlSub()\n if (sub && para.text.trim()) sub.texts.push(para.text)\n }\n } else if (local === \"tc\") {\n const cell = cellStack.pop()\n const row = rowStack[rowStack.length - 1]\n if (cell && row) row.push(cell)\n } else if (local === \"tr\") {\n const row = rowStack[rowStack.length - 1]\n const table = tableStack[tableStack.length - 1]\n if (row && table && row.length > 0) {\n table.rows.push(row)\n const trStart = trStartStack[trStartStack.length - 1]\n if (trStart >= 0) table.rowRanges.push({ start: trStart, end: m.index + full.length })\n }\n if (rowStack.length > 0) rowStack[rowStack.length - 1] = []\n if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = -1\n } else if (local === \"tbl\") {\n const table = tableStack.pop()\n rowStack.pop()\n trStartStack.pop()\n if (table) {\n finalizeTable(table)\n // 중첩표는 둘러싼 셀에 부착 (재귀 패치용), 셀이 없으면(머리말 등\n // ctrl 내부) orphanTables로 — filler가 채우기 대상에 포함 (v3.1)\n if (!table.topLevel) {\n const cell = cellStack[cellStack.length - 1]\n if (cell) cell.tables.push(table)\n else orphanTables.push(table)\n }\n }\n } else if (local === \"header\" || local === \"footer\") {\n const sub = ctrlSubStack[ctrlSubStack.length - 1]\n if (sub) {\n ctrlSubStack.pop()\n // 파서(collectSubListText)는 문단들을 \"\\n\"으로 합쳐 한 블록으로 만든다\n const joined = sub.texts.join(\"\\n\").trim()\n if (joined) (sub.kind === \"header\" ? headerTexts : footerTexts).push(joined)\n }\n }\n continue\n }\n\n // ── 여는 태그 ──\n const qname = openName!\n const local = localOf(qname)\n const attrsRaw = m[3] || \"\"\n const isSelfClose = selfClose === \"/\"\n const contentStart = m.index + full.length\n\n if (isSelfClose) {\n // 자기닫힘 요소 처리\n if (local === \"t\") {\n const para = owningPara()\n if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) })\n } else if (local === \"tab\" || local === \"fwSpace\" || local === \"hwSpace\" || local === \"br\" || local === \"lineBreak\") {\n // pendingT 내부면 t 콘텐츠 추출이 공백 처리하므로 중복 방지\n if (!pendingT) {\n const para = owningPara()\n if (para) para.text += \" \"\n }\n } else if (local === \"run\" || local === \"r\") {\n // 한컴 빈 문단 패턴: <hp:run charPrIDRef=\"...\"/> — t 삽입 시 펼칠 수 있도록 기록\n const para = owningPara()\n if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length }\n } else if (local === \"cellAddr\") {\n const cell = cellStack[cellStack.length - 1]\n if (cell && insideCurrentTable(stack, tableStack)) {\n const ca = parseInt(getAttr(attrsRaw, \"colAddr\") || \"\", 10)\n const ra = parseInt(getAttr(attrsRaw, \"rowAddr\") || \"\", 10)\n if (!isNaN(ca)) cell.colAddr = ca\n if (!isNaN(ra)) cell.rowAddr = ra\n cell.addrTagRange = { start: m.index, end: m.index + full.length }\n }\n } else if (local === \"cellSpan\") {\n const cell = cellStack[cellStack.length - 1]\n if (cell && insideCurrentTable(stack, tableStack)) {\n const cs = parseInt(getAttr(attrsRaw, \"colSpan\") || \"1\", 10)\n const rs = parseInt(getAttr(attrsRaw, \"rowSpan\") || \"1\", 10)\n cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs\n cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs\n }\n }\n continue\n }\n\n // 일반 여는 태그 — 스택 push 전 처리\n if (local === \"t\") {\n const para = owningPara()\n if (para) pendingT = { para, contentStart }\n stack.push({ local, qname, contentStart })\n continue\n }\n\n stack.push({ local, qname, contentStart })\n\n if (local === \"p\") {\n const para: ScanParagraph = {\n sectionIndex,\n kind: \"excluded\", // 분류는 push 직후 스택 기준 (자기 자신 제외)\n start: m.index,\n tRanges: [],\n text: \"\",\n }\n // 자기 자신(p)을 제외하고 분류\n stack.pop()\n const cls = classifyPara()\n para.kind = cls.kind\n if (cls.inTextbox) para.inTextbox = true\n stack.push({ local, qname, contentStart })\n paraStack.push(para)\n if (para.kind === \"body\" || para.kind === \"draw\") bodyParagraphs.push(para)\n else if (para.kind === \"cell\") {\n const cell = cellStack[cellStack.length - 1]\n if (cell) cell.paragraphs.push(para)\n } else if (para.kind === \"excluded\") {\n excludedParagraphs.push(para)\n }\n } else if (local === \"run\" || local === \"r\") {\n // hp:t가 없는 문단의 삽입 지점 후보 — 첫 run의 닫는 태그 위치는 닫힐 때 알 수 있으므로\n // 여기서는 prefix만 기억해두고, 닫는 태그에서 채움 (아래 참조)\n const para = owningPara()\n if (para && para.runPrefix === undefined) para.runPrefix = prefixOf(qname)\n } else if (local === \"tbl\") {\n const table: ScanTable = {\n sectionIndex,\n start: m.index,\n topLevel: false,\n rows: [],\n rowRanges: [],\n cellByAnchor: new Map(),\n }\n // 자기 자신 제외하고 판별\n stack.pop()\n table.topLevel = isTableTopLevel()\n stack.push({ local, qname, contentStart })\n tableStack.push(table)\n rowStack.push([])\n trStartStack.push(-1)\n if (table.topLevel) tables.push(table)\n } else if (local === \"tr\") {\n if (rowStack.length > 0) rowStack[rowStack.length - 1] = []\n if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = m.index\n } else if (local === \"tc\") {\n cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] })\n } else if (local === \"cellAddr\" || local === \"cellSpan\") {\n // 자기닫힘이 일반적이지만 펼친 형태(<hp:cellAddr ...></hp:cellAddr>)도 동일 처리\n const cell = cellStack[cellStack.length - 1]\n if (cell && insideCurrentTable(stack, tableStack)) {\n if (local === \"cellAddr\") {\n const ca = parseInt(getAttr(attrsRaw, \"colAddr\") || \"\", 10)\n const ra = parseInt(getAttr(attrsRaw, \"rowAddr\") || \"\", 10)\n if (!isNaN(ca)) cell.colAddr = ca\n if (!isNaN(ra)) cell.rowAddr = ra\n cell.addrTagRange = { start: m.index, end: contentStart }\n } else {\n const cs = parseInt(getAttr(attrsRaw, \"colSpan\") || \"1\", 10)\n const rs = parseInt(getAttr(attrsRaw, \"rowSpan\") || \"1\", 10)\n cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs\n cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs\n }\n }\n } else if (local === \"header\" || local === \"footer\") {\n // ctrl 내부의 머리말/꼬리말만 수집\n if (stack.some(f => f.local === \"ctrl\")) {\n ctrlSubStack.push({ kind: local, texts: [] })\n }\n } else if (local === \"tab\" || local === \"fwSpace\" || local === \"hwSpace\" || local === \"br\" || local === \"lineBreak\") {\n const para = owningPara()\n if (para) para.text += \" \"\n }\n }\n\n // run 삽입 지점 보강: t가 없는 body/cell 문단에 대해 첫 run의 닫는 태그 위치 탐색\n for (const para of bodyParagraphs) fillRunInsertPos(para, xml)\n for (const para of excludedParagraphs) fillRunInsertPos(para, xml)\n const fillTableInsertPos = (table: ScanTable, depth = 0): void => {\n if (depth > 16) return\n for (const row of table.rows) {\n for (const cell of row) {\n for (const para of cell.paragraphs) fillRunInsertPos(para, xml)\n for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1)\n }\n }\n }\n for (const table of tables) fillTableInsertPos(table)\n for (const table of orphanTables) fillTableInsertPos(table)\n\n return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables }\n}\n\n/** 속성 문자열에서 값 추출 */\nfunction getAttr(attrsRaw: string, name: string): string | undefined {\n const re = new RegExp(`(?:^|\\\\s)${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)')`)\n const m = attrsRaw.match(re)\n return m ? (m[1] ?? m[2]) : undefined\n}\n\n/** 문단 스택 컨텍스트가 현재 테이블 셀 직속인지 — cellAddr/cellSpan 소유 확인 */\nfunction insideCurrentTable(stack: StackFrame[], tableStack: ScanTable[]): boolean {\n if (tableStack.length === 0) return false\n for (let i = stack.length - 1; i >= 0; i--) {\n const l = stack[i].local\n if (l === \"tc\") return true\n if (l === \"tbl\") return false\n }\n return false\n}\n\n/** hp:t 없는 문단의 삽입 위치 — 첫 <hp:run ...>...</hp:run>의 닫는 태그 직전 */\nfunction fillRunInsertPos(para: ScanParagraph, xml: string): void {\n if (para.tRanges.length > 0) return\n // 문단 범위 내 첫 run 탐색 (문단 닫는 태그까지 — 간단히 다음 </hp:p>까지로 한정)\n const pEnd = findElementEnd(xml, para.start)\n if (pEnd < 0) return\n const slice = xml.slice(para.start, pEnd)\n const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\\s(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?)?(\\/?)>/)\n if (!runOpen || runOpen.index === undefined) return\n if (runOpen[2] === \"/\") return // 자기닫힘 run — v1 미지원\n const qname = runOpen[1]\n const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index)\n if (closeIdx < 0) return\n para.runInsertPos = para.start + closeIdx\n para.runPrefix = prefixOf(qname)\n}\n\n/** start 위치의 여는 태그에 대응하는 닫는 태그 뒤 위치 (동일 로컬명 중첩 추적) */\nexport function findElementEnd(xml: string, start: number): number {\n const open = xml.slice(start).match(/^<([^\\s/>!?]+)/)\n if (!open) return -1\n const qname = open[1]\n const re = new RegExp(`<${qname}(?=[\\\\s/>])(?:\"[^\"]*\"|'[^']*'|[^>\"'])*?(/?)>|</${qname}\\\\s*>`, \"g\")\n re.lastIndex = start\n let depth = 0\n let mm: RegExpExecArray | null\n while ((mm = re.exec(xml)) !== null) {\n if (mm[0].startsWith(\"</\")) {\n depth--\n if (depth === 0) return mm.index + mm[0].length\n } else if (mm[1] !== \"/\") {\n depth++\n }\n }\n return -1\n}\n\n/** 표 마무리 — 앵커 좌표 맵 구축 (cellAddr 없으면 first-fit, builder.ts buildTable과 동일 규칙) */\nfunction finalizeTable(table: ScanTable): void {\n const hasAddr = table.rows.some(row => row.some(c => c.colAddr !== undefined && c.rowAddr !== undefined))\n if (hasAddr) {\n for (const row of table.rows) {\n for (const cell of row) {\n if (cell.rowAddr !== undefined && cell.colAddr !== undefined) {\n table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell)\n }\n }\n }\n return\n }\n // first-fit 배치 (builder.ts buildTable Pass1/2와 동일한 점유 규칙)\n const numRows = table.rows.length\n const occupied: boolean[][] = Array.from({ length: numRows }, () => [])\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\n let colIdx = 0\n for (const cell of table.rows[rowIdx]) {\n while (occupied[rowIdx][colIdx]) colIdx++\n cell.rowAddr = rowIdx\n cell.colAddr = colIdx\n table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell)\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\n for (let c = colIdx; c < colIdx + cell.colSpan; c++) {\n occupied[r][c] = true\n }\n }\n colIdx += cell.colSpan\n }\n }\n}\n\n// ─── 치환 ────────────────────────────────────────────\n\n/**\n * 문단 텍스트 치환용 splice 생성 — filler-hwpx.ts replaceCellText와 동일 전략:\n * 첫 hp:t에 새 텍스트(이스케이프), 나머지 hp:t는 비움. run 구조/charPr 보존.\n * t가 없으면 첫 run 끝에 <hp:t> 삽입. 실패 시 null.\n *\n * IR 텍스트는 sanitize로 양끝 공백이 제거된 상태라, 통째 교체 시 원본의\n * 선행 공백(들여쓰기)/후행 공백이 소실된다 — 원본 t-도메인 텍스트에서\n * 양끝 공백을 복원해 새 텍스트에 입힌다 (재파싱 IR은 동일하게 유지됨).\n */\nexport function buildParagraphSplices(para: ScanParagraph, newText: string, xml?: string): SpliceEdit[] | null {\n if (newText && xml) {\n const orig = paraTText(para, xml)\n if (orig && orig.trim() !== \"\") {\n const lead = orig.match(/^\\s*/)![0]\n const trail = orig.match(/\\s*$/)![0]\n if ((lead || trail) && newText.trim() !== \"\") {\n newText = lead + newText.replace(/^\\s+|\\s+$/g, \"\") + trail\n }\n }\n }\n const escaped = escapeXmlText(newText)\n if (para.tRanges.length > 0) {\n const splices: SpliceEdit[] = []\n const first = para.tRanges[0]\n if (first.selfClosing) {\n // 자기닫힘 <hp:t/> — 태그째 <hp:t>텍스트</hp:t>로 교체\n const prefix = first.prefix ? first.prefix + \":\" : \"\"\n splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` })\n } else {\n splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped })\n }\n // 나머지 hp:t 비우기 (자기닫힘은 이미 빈 상태)\n for (let i = 1; i < para.tRanges.length; i++) {\n const r = para.tRanges[i]\n if (!r.selfClosing && r.contentStart < r.contentEnd) {\n splices.push({ start: r.contentStart, end: r.contentEnd, replacement: \"\" })\n }\n }\n return splices\n }\n // hp:t 없음 — 첫 run 끝에 삽입\n if (para.runInsertPos !== undefined) {\n if (!newText) return [] // 비우기인데 t도 없음 — 이미 빈 문단\n const prefix = para.runPrefix ? para.runPrefix + \":\" : \"\"\n return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }]\n }\n // 자기닫힘 <hp:run/> — 태그를 펼쳐서 t 삽입 (한컴 빈 문단 패턴, xml 필요)\n if (para.selfCloseRun && xml) {\n if (!newText) return []\n const { start, end } = para.selfCloseRun\n const tag = xml.slice(start, end)\n const qm = tag.match(/^<([^\\s/>]+)/)\n if (!qm || !tag.endsWith(\"/>\")) return null\n const qname = qm[1]\n const colon = qname.indexOf(\":\")\n const prefix = colon >= 0 ? qname.slice(0, colon) + \":\" : \"\"\n const opened = tag.slice(0, tag.length - 2).trimEnd() + \">\"\n return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }]\n }\n return newText ? null : []\n}\n\n/**\n * 문단의 t-도메인 텍스트 — hp:t 콘텐츠만 이어붙인 문자열 (v3.1).\n *\n * para.text와 달리 tab/br 등 비-t 요소의 공백 기여가 없어, 그 사이에 끼인\n * 요소를 건드리지 않는 정밀 치환(buildRangeSplices)의 좌표계로 쓴다.\n * hp:t 콘텐츠에 엔티티/내부 태그([<&])가 있으면 좌표가 어긋나므로 null.\n */\nexport function paraTText(para: ScanParagraph, xml: string): string | null {\n let text = \"\"\n for (const t of para.tRanges) {\n if (t.selfClosing) continue\n const raw = xml.slice(t.contentStart, t.contentEnd)\n if (/[<&]/.test(raw)) return null\n text += raw\n }\n return text\n}\n\n/** 문단 텍스트가 전부 hp:t에서 왔는지 — tab/br 등 비-t 기여가 있으면 false.\n * false면 문단 전체 재작성 시 비-t 요소와 텍스트가 중복/순서 역전되므로 금지. */\nexport function paraTextPureT(para: ScanParagraph, xml: string): boolean {\n let len = 0\n for (const t of para.tRanges) {\n if (t.selfClosing) continue\n len += tContentToText(xml.slice(t.contentStart, t.contentEnd)).length\n }\n return len === para.text.length\n}\n\n/**\n * t-도메인 텍스트(paraTText 좌표계)의 [start, end) 범위만 치환하는 정밀 splice\n * — run/탭 구조 보존 (v3.1).\n *\n * 좌표계가 t-도메인이므로 tab/br이 사이에 끼어 있어도 동작한다 (해당 요소는\n * 건드리지 않고 t 콘텐츠만 수술). hp:t 콘텐츠에 엔티티/내부 태그가 있으면\n * 오프셋 정합이 깨지므로 null을 반환한다 (호출자가 폴백 결정).\n */\nexport function buildRangeSplices(\n para: ScanParagraph, xml: string, start: number, end: number, replacement: string,\n): SpliceEdit[] | null {\n if (start < 0 || end < start) return null\n\n // t 세그먼트 ↔ t-도메인 오프셋 정렬 — 디코딩 불변 검증\n interface Seg { contentStart: number; from: number; to: number }\n const segs: Seg[] = []\n let offset = 0\n for (const t of para.tRanges) {\n if (t.selfClosing) continue // 텍스트 기여 없음\n const raw = xml.slice(t.contentStart, t.contentEnd)\n if (/[<&]/.test(raw)) return null // 엔티티/내부 태그 — 오프셋 불일치 가능\n segs.push({ contentStart: t.contentStart, from: offset, to: offset + raw.length })\n offset += raw.length\n }\n if (segs.length === 0 || end > offset) return null\n\n const escaped = escapeXmlText(replacement)\n\n // 삽입 (start === end) — 위치를 포함하는 첫 세그먼트에 삽입\n if (start === end) {\n for (const seg of segs) {\n if (start >= seg.from && start <= seg.to) {\n const at = seg.contentStart + (start - seg.from)\n return [{ start: at, end: at, replacement: escaped }]\n }\n }\n return null\n }\n\n // 치환 — 첫 겹침 세그먼트에 새 텍스트, 나머지 겹침 구간은 제거\n const splices: SpliceEdit[] = []\n let placed = false\n for (const seg of segs) {\n if (seg.to <= start || seg.from >= end) continue\n const localStart = Math.max(seg.from, start) - seg.from\n const localEnd = Math.min(seg.to, end) - seg.from\n splices.push({\n start: seg.contentStart + localStart,\n end: seg.contentStart + localEnd,\n replacement: placed ? \"\" : escaped,\n })\n placed = true\n }\n return placed ? splices : null\n}\n\n/** splice 일괄 적용 — 겹침 검증 후 뒤에서부터 치환 */\n/**\n * 섹션 내 모든 <hp:linesegarray> 제거 splice 목록.\n *\n * hp:linesegarray는 문단 각 줄의 레이아웃(위치·크기) 정보를 담는데, hp:t 텍스트만\n * 바꾸면 이 정보가 새 텍스트와 어긋난다. 한컴 한글은 이를 \"변조\"로 감지해 보안\n * 경고를 띄운다(한컴 개발자포럼 권고). 선택적(optional)인 요소라 제거해도 뷰어가\n * 레이아웃을 다시 계산하므로, 텍스트가 바뀐 섹션은 전체 linesegarray를 비운다\n * (일부만 비우면 한컴이 문서 전체 정합성 검사에서 여전히 경고를 띄움).\n */\nexport function allLinesegRemovalSplices(xml: string): SpliceEdit[] {\n const segRe = /<(\\w+:)?linesegarray\\b[^>]*?(?:\\/>|>[\\s\\S]*?<\\/\\1linesegarray>)/g\n const splices: SpliceEdit[] = []\n let m: RegExpExecArray | null\n while ((m = segRe.exec(xml)) !== null) {\n splices.push({ start: m.index, end: m.index + m[0].length, replacement: \"\" })\n }\n return splices\n}\n\nexport function applySplices(xml: string, splices: SpliceEdit[]): string {\n const sorted = [...splices].sort((a, b) => a.start - b.start)\n for (let i = 1; i < sorted.length; i++) {\n if (sorted[i].start < sorted[i - 1].end) {\n throw new Error(\"소스맵 splice 범위 겹침 — 내부 오류\")\n }\n }\n let result = xml\n for (let i = sorted.length - 1; i >= 0; i--) {\n const s = sorted[i]\n result = result.slice(0, s.start) + s.replacement + result.slice(s.end)\n }\n return result\n}\n","/**\n * ZIP in-place 패치 — 지정 엔트리만 교체하고 나머지는 원본 바이트 그대로 보존.\n *\n * JSZip generateAsync는 전 엔트리를 재압축/재직렬화하므로 바이트 보존이 깨진다.\n * 여기서는 Central Directory를 직접 파싱해 변경 엔트리의 로컬 레코드만 재작성하고,\n * 나머지 로컬 레코드·CD 엔트리·EOCD는 원본 바이트를 복사(오프셋 필드만 패치)한다.\n * mimetype 첫 엔트리 + 무압축(STORE) 같은 HWPX/OPC 규약은 원본 순서·메서드를\n * 그대로 따르므로 자동 보존된다.\n */\n\nimport { deflateRawSync } from \"zlib\"\nimport { KordocError } from \"../utils.js\"\n\nconst EOCD_SIG = 0x06054b50\nconst CD_SIG = 0x02014b50\nconst LOCAL_SIG = 0x04034b50\nconst ZIP64_EOCD_LOC_SIG = 0x07064b50\n\ninterface CdEntry {\n /** CD 레코드 원본 바이트 범위 [start, end) */\n cdStart: number\n cdEnd: number\n name: string\n flags: number\n method: number\n crc: number\n compSize: number\n uncompSize: number\n localOffset: number\n}\n\n/** ZIP 내 엔트리별 압축 해제 데이터 읽기 (검증용) */\nexport function readZipEntries(buf: Uint8Array): Map<string, { method: number; compData: Uint8Array }> {\n const { entries } = parseCentralDirectory(buf)\n const result = new Map<string, { method: number; compData: Uint8Array }>()\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n for (const e of entries) {\n const dataStart = localDataStart(view, e.localOffset)\n result.set(e.name, { method: e.method, compData: buf.subarray(dataStart, dataStart + e.compSize) })\n }\n return result\n}\n\nfunction localDataStart(view: DataView, localOffset: number): number {\n if (view.getUint32(localOffset, true) !== LOCAL_SIG) {\n throw new KordocError(\"ZIP 로컬 헤더 시그니처 불일치\")\n }\n const nameLen = view.getUint16(localOffset + 26, true)\n const extraLen = view.getUint16(localOffset + 28, true)\n return localOffset + 30 + nameLen + extraLen\n}\n\n/** Buffer.prototype.slice는 view를 반환하므로 (Uint8Array와 달리) 명시적 복사 */\nfunction copyBytes(buf: Uint8Array, start: number, end?: number): Uint8Array {\n return new Uint8Array(buf.subarray(start, end))\n}\n\nfunction parseCentralDirectory(buf: Uint8Array): { entries: CdEntry[]; cdOffset: number; cdSize: number; eocdOffset: number } {\n const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)\n // EOCD 탐색 (뒤에서부터, 주석 최대 64KB) — comment 길이가 파일 끝과 일치해야\n // 진짜 EOCD (엔트리 데이터/comment 안 가짜 시그니처 배제)\n const minEocd = Math.max(0, buf.length - 22 - 65535)\n let eocdOffset = -1\n for (let i = buf.length - 22; i >= minEocd; i--) {\n if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {\n eocdOffset = i\n break\n }\n }\n if (eocdOffset < 0) {\n // 폴백: EOCD 뒤 trailing 정크가 붙은 파일 (parse()는 수용) — comment 길이가\n // 파일 끝에 못 미치더라도 CD 시그니처가 검증되는 첫 후보를 채택\n for (let i = buf.length - 22; i >= minEocd; i--) {\n if (view.getUint32(i, true) !== EOCD_SIG) continue\n if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue\n const cand = view.getUint32(i + 16, true)\n if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) { eocdOffset = i; break }\n }\n }\n if (eocdOffset < 0) throw new KordocError(\"ZIP EOCD를 찾을 수 없습니다\")\n\n const totalEntries = view.getUint16(eocdOffset + 10, true)\n const cdSize = view.getUint32(eocdOffset + 12, true)\n const cdOffset = view.getUint32(eocdOffset + 16, true)\n if (cdOffset === 0xffffffff || totalEntries === 0xffff) throw new KordocError(\"ZIP64는 지원하지 않습니다\")\n // ZIP64 EOCD locator 존재 여부\n if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {\n throw new KordocError(\"ZIP64는 지원하지 않습니다\")\n }\n\n const decoder = new TextDecoder(\"utf-8\")\n const entries: CdEntry[] = []\n let pos = cdOffset\n for (let i = 0; i < totalEntries; i++) {\n if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError(\"ZIP Central Directory 손상\")\n const flags = view.getUint16(pos + 8, true)\n const method = view.getUint16(pos + 10, true)\n const crc = view.getUint32(pos + 16, true)\n const compSize = view.getUint32(pos + 20, true)\n const uncompSize = view.getUint32(pos + 24, true)\n const nameLen = view.getUint16(pos + 28, true)\n const extraLen = view.getUint16(pos + 30, true)\n const commentLen = view.getUint16(pos + 32, true)\n const localOffset = view.getUint32(pos + 42, true)\n if (compSize === 0xffffffff || uncompSize === 0xffffffff || localOffset === 0xffffffff) {\n throw new KordocError(\"ZIP64는 지원하지 않습니다\")\n }\n const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen))\n const cdEnd = pos + 46 + nameLen + extraLen + commentLen\n entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset })\n pos = cdEnd\n }\n return { entries, cdOffset, cdSize, eocdOffset }\n}\n\n// ─── CRC-32 ─────────────────────────────────────────\n\nconst CRC_TABLE = (() => {\n const table = new Uint32Array(256)\n for (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n table[n] = c >>> 0\n }\n return table\n})()\n\nfunction crc32(data: Uint8Array): number {\n let crc = 0xffffffff\n for (let i = 0; i < data.length; i++) {\n crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8)\n }\n return (crc ^ 0xffffffff) >>> 0\n}\n\n// ─── 재조립 ──────────────────────────────────────────\n\n/**\n * ZIP에서 replacements에 지정된 엔트리만 새 데이터로 교체하고 나머지는\n * 원본 로컬 레코드 바이트를 그대로 복사하여 재조립한다.\n *\n * @param original 원본 ZIP 바이트\n * @param replacements 엔트리 이름 → 새 압축 전 데이터\n * @param additions 신규 엔트리 이름 → 데이터 (기존 로컬 레코드 뒤·CD 앞에 추가 —\n * 도장 이미지 등 BinData 파트 추가용. 기존 엔트리와 이름 충돌 시 에러)\n */\nexport function patchZipEntries(\n original: Uint8Array,\n replacements: Map<string, Uint8Array>,\n additions?: Map<string, Uint8Array>,\n): Uint8Array {\n const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original)\n const view = new DataView(original.buffer, original.byteOffset, original.byteLength)\n\n for (const name of replacements.keys()) {\n if (!entries.some(e => e.name === name)) throw new KordocError(`ZIP에 없는 엔트리: ${name}`)\n }\n if (additions) {\n for (const name of additions.keys()) {\n if (entries.some(e => e.name === name)) throw new KordocError(`ZIP에 이미 있는 엔트리: ${name}`)\n }\n }\n\n // 로컬 레코드 순서 = 원본 로컬 오프셋 순서 (mimetype 첫 엔트리 보존)\n const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset)\n const segments: Uint8Array[] = []\n const newLocalOffset = new Map<CdEntry, number>()\n const newMeta = new Map<CdEntry, { crc: number; compSize: number; uncompSize: number; flags: number }>()\n let offset = 0\n\n for (let i = 0; i < byLocal.length; i++) {\n const e = byLocal[i]\n const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset\n newLocalOffset.set(e, offset)\n\n const newData = replacements.get(e.name)\n if (newData === undefined) {\n // 원본 로컬 레코드 (데이터 디스크립터 포함) 바이트 그대로\n const seg = original.subarray(e.localOffset, segEnd)\n segments.push(seg)\n offset += seg.length\n continue\n }\n\n // 교체 엔트리 — 원본 로컬 헤더를 복사한 뒤 crc/size/flags만 패치\n if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError(\"ZIP 로컬 헤더 시그니처 불일치\")\n const nameLen = view.getUint16(e.localOffset + 26, true)\n const extraLen = view.getUint16(e.localOffset + 28, true)\n const headerLen = 30 + nameLen + extraLen\n const header = copyBytes(original, e.localOffset, e.localOffset + headerLen)\n const hview = new DataView(header.buffer, header.byteOffset, header.byteLength)\n\n const method = e.method\n const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData))\n const crc = crc32(newData)\n const flags = e.flags & ~0x0008 // 데이터 디스크립터 비트 해제 (사이즈를 헤더에 기록)\n\n hview.setUint16(6, flags, true)\n hview.setUint32(14, crc, true)\n hview.setUint32(18, compData.length, true)\n hview.setUint32(22, newData.length, true)\n\n segments.push(header, compData)\n offset += headerLen + compData.length\n newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags })\n }\n\n // 신규 엔트리 — 기존 로컬 레코드 뒤에 UTF-8 이름·고정 타임스탬프로 기록.\n // PNG 등 기압축 데이터는 deflate가 오히려 커질 수 있어 작아질 때만 압축(STORE 폴백).\n const added: Array<{\n nameBytes: Uint8Array\n crc: number\n compSize: number\n uncompSize: number\n method: number\n localOffset: number\n }> = []\n if (additions) {\n const encoder = new TextEncoder()\n for (const [name, data] of additions) {\n const nameBytes = encoder.encode(name)\n const deflated = new Uint8Array(deflateRawSync(data))\n const method = deflated.length < data.length ? 8 : 0\n const compData = method === 8 ? deflated : data\n const crc = crc32(data)\n const header = new Uint8Array(30 + nameBytes.length)\n const hv = new DataView(header.buffer)\n hv.setUint32(0, LOCAL_SIG, true)\n hv.setUint16(4, 20, true) // version needed\n hv.setUint16(6, 0x0800, true) // UTF-8 이름 플래그\n hv.setUint16(8, method, true)\n hv.setUint16(10, 0, true) // time\n hv.setUint16(12, 0x21, true) // date 1980-01-01 (결정적 출력)\n hv.setUint32(14, crc, true)\n hv.setUint32(18, compData.length, true)\n hv.setUint32(22, data.length, true)\n hv.setUint16(26, nameBytes.length, true)\n hv.setUint16(28, 0, true)\n header.set(nameBytes, 30)\n added.push({ nameBytes, crc, compSize: compData.length, uncompSize: data.length, method, localOffset: offset })\n segments.push(header, compData)\n offset += header.length + compData.length\n }\n }\n\n // Central Directory — 원본 CD 엔트리 순서 유지, 오프셋/메타 패치\n const newCdOffset = offset\n for (const e of entries) {\n const cd = copyBytes(original, e.cdStart, e.cdEnd)\n const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength)\n cview.setUint32(42, newLocalOffset.get(e)!, true)\n const meta = newMeta.get(e)\n if (meta) {\n cview.setUint16(8, meta.flags, true)\n cview.setUint32(16, meta.crc, true)\n cview.setUint32(20, meta.compSize, true)\n cview.setUint32(24, meta.uncompSize, true)\n }\n segments.push(cd)\n offset += cd.length\n }\n\n // 신규 엔트리 CD 레코드 (원본 CD 뒤)\n for (const a of added) {\n const cd = new Uint8Array(46 + a.nameBytes.length)\n const cv = new DataView(cd.buffer)\n cv.setUint32(0, CD_SIG, true)\n cv.setUint16(4, 20, true) // version made by\n cv.setUint16(6, 20, true) // version needed\n cv.setUint16(8, 0x0800, true) // UTF-8 이름 플래그\n cv.setUint16(10, a.method, true)\n cv.setUint16(12, 0, true) // time\n cv.setUint16(14, 0x21, true) // date 1980-01-01\n cv.setUint32(16, a.crc, true)\n cv.setUint32(20, a.compSize, true)\n cv.setUint32(24, a.uncompSize, true)\n cv.setUint16(28, a.nameBytes.length, true)\n cv.setUint32(42, a.localOffset, true)\n cd.set(a.nameBytes, 46)\n segments.push(cd)\n offset += cd.length\n }\n const newCdSize = offset - newCdOffset\n\n // EOCD — 원본 복사 후 CD 오프셋/크기 패치 (신규 엔트리만큼 카운트 증가)\n const eocd = copyBytes(original, eocdOffset)\n const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength)\n if (added.length > 0) {\n eview.setUint16(8, view.getUint16(eocdOffset + 8, true) + added.length, true)\n eview.setUint16(10, view.getUint16(eocdOffset + 10, true) + added.length, true)\n }\n eview.setUint32(12, newCdSize, true)\n eview.setUint32(16, newCdOffset, true)\n segments.push(eocd)\n offset += eocd.length\n\n const result = new Uint8Array(offset)\n let pos = 0\n for (const seg of segments) {\n result.set(seg, pos)\n pos += seg.length\n }\n return result\n}\n"],"mappings":"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","names":["local","contentStart","si","site"]}
@@ -3,13 +3,15 @@ import {
3
3
  DEFAULT_CHAR,
4
4
  DEFAULT_PARA_GEOM,
5
5
  MAX_DECOMPRESS_SIZE,
6
+ MAX_ZIP_ENTRIES,
6
7
  createXmlParser,
7
8
  findChildByLocalName,
8
9
  parseRenderStyles
9
- } from "./chunk-JFNWJFKP.js";
10
+ } from "./chunk-IY3NX4FT.js";
10
11
  import {
11
- KordocError
12
- } from "./chunk-IC56KHBL.js";
12
+ KordocError,
13
+ precheckZipSize
14
+ } from "./chunk-K4L563FA.js";
13
15
 
14
16
  // src/render/svg-render.ts
15
17
  import JSZip from "jszip";
@@ -1021,6 +1023,8 @@ function sniffMime(name, bytes) {
1021
1023
  }
1022
1024
  async function renderHwpxToSvg(input, options) {
1023
1025
  const maxImg = options?.maxImageBytes ?? 40 * 1024 * 1024;
1026
+ const ab = input instanceof Uint8Array ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) : input;
1027
+ precheckZipSize(ab, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
1024
1028
  let zip;
1025
1029
  try {
1026
1030
  zip = await JSZip.loadAsync(input);
@@ -1156,4 +1160,4 @@ export {
1156
1160
  fitRatioForFewerLines,
1157
1161
  renderHwpxToSvg
1158
1162
  };
1159
- //# sourceMappingURL=chunk-5RHRQ7HK.js.map
1163
+ //# sourceMappingURL=chunk-CDFCA37S.js.map