kordoc 4.2.8 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +922 -885
  2. package/dist/{-SCTT2TBZ.js → -DYHT3HWH.js} +28 -20
  3. package/dist/{chunk-J2RZ444H.js → chunk-3FFPHTXD.js} +1 -1
  4. package/dist/chunk-3FFPHTXD.js.map +1 -0
  5. package/dist/{chunk-IJUAWDOS.js → chunk-4DBCUF3C.js} +38 -7
  6. package/dist/chunk-4DBCUF3C.js.map +1 -0
  7. package/dist/{chunk-5RPYBC2Q.js → chunk-6GBLFQMA.js} +1 -1
  8. package/dist/chunk-6GBLFQMA.js.map +1 -0
  9. package/dist/{chunk-JP5MNHUK.js → chunk-6RAYTNQT.js} +5 -5
  10. package/dist/chunk-6RAYTNQT.js.map +1 -0
  11. package/dist/{chunk-GEPINOL6.js → chunk-6XPXIG57.js} +2 -2
  12. package/dist/chunk-6XPXIG57.js.map +1 -0
  13. package/dist/{chunk-WP4VD5T7.js → chunk-7MFO4TKP.js} +4 -3
  14. package/dist/chunk-7MFO4TKP.js.map +1 -0
  15. package/dist/{chunk-CELJIGBQ.cjs → chunk-AL34DGS5.cjs} +6 -6
  16. package/dist/chunk-AL34DGS5.cjs.map +1 -0
  17. package/dist/{chunk-FS3XQ3ZQ.js → chunk-ARXNKC5D.js} +3 -3
  18. package/dist/chunk-ARXNKC5D.js.map +1 -0
  19. package/dist/{chunk-2BTKLDNX.cjs → chunk-AUOEZKJE.cjs} +21 -21
  20. package/dist/chunk-AUOEZKJE.cjs.map +1 -0
  21. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  22. package/dist/{chunk-7GEBDS6B.js → chunk-DRQUAYNT.js} +4 -3
  23. package/dist/chunk-DRQUAYNT.js.map +1 -0
  24. package/dist/{chunk-YNAPK4H6.js → chunk-DRZUJ2RK.js} +1521 -1144
  25. package/dist/chunk-DRZUJ2RK.js.map +1 -0
  26. package/dist/{chunk-GKSFNCVP.js → chunk-EFVAY5VE.js} +71 -29
  27. package/dist/chunk-EFVAY5VE.js.map +1 -0
  28. package/dist/{chunk-UKEZ46VL.js → chunk-EZHW3RNQ.js} +37 -6
  29. package/dist/chunk-EZHW3RNQ.js.map +1 -0
  30. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  31. package/dist/{chunk-4FTXKIGT.js → chunk-GSMPTZC7.js} +4 -4
  32. package/dist/chunk-GSMPTZC7.js.map +1 -0
  33. package/dist/{chunk-OQNFGKRY.cjs → chunk-JFV7QT5T.cjs} +4 -3
  34. package/dist/chunk-JFV7QT5T.cjs.map +1 -0
  35. package/dist/{chunk-ROKHZE76.cjs → chunk-LPQWKQQQ.cjs} +44 -13
  36. package/dist/chunk-LPQWKQQQ.cjs.map +1 -0
  37. package/dist/{chunk-H7OWEYH2.cjs → chunk-MGGU3ZRK.cjs} +1 -1
  38. package/dist/chunk-MGGU3ZRK.cjs.map +1 -0
  39. package/dist/chunk-MOL7MDBG.js +0 -0
  40. package/dist/{chunk-55QIELGV.js → chunk-NMWXBJ5B.js} +4 -3
  41. package/dist/chunk-NMWXBJ5B.js.map +1 -0
  42. package/dist/{chunk-DZIXKL7E.js → chunk-O5EK52TG.js} +1 -1
  43. package/dist/chunk-O5EK52TG.js.map +1 -0
  44. package/dist/{chunk-COHHXQSP.js → chunk-SPKBIFSK.js} +2 -2
  45. package/dist/chunk-SPKBIFSK.js.map +1 -0
  46. package/dist/{chunk-QZCAIFUW.js → chunk-US4WFSIQ.js} +4 -4
  47. package/dist/chunk-US4WFSIQ.js.map +1 -0
  48. package/dist/{chunk-EOVJ27WC.js → chunk-VXMLENCV.js} +15 -11
  49. package/dist/chunk-VXMLENCV.js.map +1 -0
  50. package/dist/{chunk-7FCNOKOR.js → chunk-W34QSJFF.js} +1 -1
  51. package/dist/chunk-W34QSJFF.js.map +1 -0
  52. package/dist/{chunk-URKX77GB.js → chunk-WFOQFJI4.js} +2 -2
  53. package/dist/chunk-WFOQFJI4.js.map +1 -0
  54. package/dist/{chunk-NNDVFNMJ.js → chunk-YNJDNP44.js} +1 -1
  55. package/dist/chunk-YNJDNP44.js.map +1 -0
  56. package/dist/{chunk-LM7C6HI2.js → chunk-ZSL6MQVE.js} +1 -1
  57. package/dist/chunk-ZSL6MQVE.js.map +1 -0
  58. package/dist/chunks-Q7T4SQGW.js +10 -0
  59. package/dist/cli.js +75 -39
  60. package/dist/cli.js.map +1 -1
  61. package/dist/{formula-SXC6LRRC.js → formula-7ZPC54JO.js} +2 -2
  62. package/dist/formula-7ZPC54JO.js.map +1 -0
  63. package/dist/{formula-KSNAEBZ2.js → formula-EQRHSMYW.js} +2 -2
  64. package/dist/formula-EQRHSMYW.js.map +1 -0
  65. package/dist/{formula-SB3NHDUM.cjs → formula-IWV5IJ3Y.cjs} +8 -8
  66. package/dist/formula-IWV5IJ3Y.cjs.map +1 -0
  67. package/dist/{image-ocr-ANSXR5KJ.js → image-ocr-G2LYTEUH.js} +8 -6
  68. package/dist/image-ocr-G2LYTEUH.js.map +1 -0
  69. package/dist/{image-ocr-ORATZW73.js → image-ocr-R44RYDBU.js} +10 -8
  70. package/dist/image-ocr-R44RYDBU.js.map +1 -0
  71. package/dist/{image-ocr-GHQ6YKJH.cjs → image-ocr-XEJFXH6M.cjs} +13 -11
  72. package/dist/image-ocr-XEJFXH6M.cjs.map +1 -0
  73. package/dist/index.cjs +2017 -1868
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.cts +75 -4
  76. package/dist/index.d.ts +75 -4
  77. package/dist/index.js +2139 -1990
  78. package/dist/index.js.map +1 -1
  79. package/dist/mcp.js +123 -78
  80. package/dist/mcp.js.map +1 -1
  81. package/dist/{models-KA4GLQK5.js → models-6VMAVZR6.js} +3 -3
  82. package/dist/page-range-737B4EZW.js +0 -0
  83. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  84. package/dist/{parser-BPVTYM5K.js → parser-2G245MMN.js} +6 -6
  85. package/dist/parser-2G245MMN.js.map +1 -0
  86. package/dist/{parser-GZDOZCHM.js → parser-W3POOYQQ.js} +8 -8
  87. package/dist/parser-W3POOYQQ.js.map +1 -0
  88. package/dist/{parser-KTNIUF2N.cjs → parser-W3ZGQUCQ.cjs} +25 -25
  89. package/dist/parser-W3ZGQUCQ.cjs.map +1 -0
  90. package/dist/pdf-ocr-3GDTJSHF.js +15 -0
  91. package/dist/pdf-ocr-N7IU23UV.cjs +13 -0
  92. package/dist/pdf-ocr-N7IU23UV.cjs.map +1 -0
  93. package/dist/pdf-ocr-U7MEGNBZ.js +12 -0
  94. package/dist/{profile-io-I2TH44EO.js → profile-io-72R3B7KO.js} +3 -3
  95. package/dist/{rasterize-QBA43YAQ.js → rasterize-GNJQVURE.js} +2 -2
  96. package/dist/rasterize-GNJQVURE.js.map +1 -0
  97. package/dist/{redact-7ILEAUTS.js → redact-W5K3ILQH.js} +2 -2
  98. package/dist/render-6OW6U4L4.js +10 -0
  99. package/dist/seal-GTNLVBJX.js +10 -0
  100. package/dist/{setup-GUQT5ELJ.js → setup-I4I75QJU.js} +1 -1
  101. package/dist/setup-I4I75QJU.js.map +1 -0
  102. package/dist/{watch-7V6X7LHV.js → watch-2ADAJLIJ.js} +12 -14
  103. package/dist/watch-2ADAJLIJ.js.map +1 -0
  104. package/package.json +101 -98
  105. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  106. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +9 -0
  107. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  108. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +25 -0
  109. package/dist/chunk-2BTKLDNX.cjs.map +0 -1
  110. package/dist/chunk-4FTXKIGT.js.map +0 -1
  111. package/dist/chunk-55QIELGV.js.map +0 -1
  112. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  113. package/dist/chunk-7FCNOKOR.js.map +0 -1
  114. package/dist/chunk-7GEBDS6B.js.map +0 -1
  115. package/dist/chunk-CELJIGBQ.cjs.map +0 -1
  116. package/dist/chunk-COHHXQSP.js.map +0 -1
  117. package/dist/chunk-DZIXKL7E.js.map +0 -1
  118. package/dist/chunk-EOVJ27WC.js.map +0 -1
  119. package/dist/chunk-FS3XQ3ZQ.js.map +0 -1
  120. package/dist/chunk-GEPINOL6.js.map +0 -1
  121. package/dist/chunk-GKSFNCVP.js.map +0 -1
  122. package/dist/chunk-H7OWEYH2.cjs.map +0 -1
  123. package/dist/chunk-IJUAWDOS.js.map +0 -1
  124. package/dist/chunk-J2RZ444H.js.map +0 -1
  125. package/dist/chunk-JP5MNHUK.js.map +0 -1
  126. package/dist/chunk-LM7C6HI2.js.map +0 -1
  127. package/dist/chunk-NNDVFNMJ.js.map +0 -1
  128. package/dist/chunk-OQNFGKRY.cjs.map +0 -1
  129. package/dist/chunk-QZCAIFUW.js.map +0 -1
  130. package/dist/chunk-ROKHZE76.cjs.map +0 -1
  131. package/dist/chunk-TCJRGFYY.js +0 -280
  132. package/dist/chunk-TCJRGFYY.js.map +0 -1
  133. package/dist/chunk-UKEZ46VL.js.map +0 -1
  134. package/dist/chunk-URKX77GB.js.map +0 -1
  135. package/dist/chunk-WP4VD5T7.js.map +0 -1
  136. package/dist/chunk-YNAPK4H6.js.map +0 -1
  137. package/dist/chunks-KKYJDJT7.js +0 -10
  138. package/dist/detect-FX2ZHKZP.js +0 -26
  139. package/dist/formula-KSNAEBZ2.js.map +0 -1
  140. package/dist/formula-SB3NHDUM.cjs.map +0 -1
  141. package/dist/formula-SXC6LRRC.js.map +0 -1
  142. package/dist/image-ocr-ANSXR5KJ.js.map +0 -1
  143. package/dist/image-ocr-GHQ6YKJH.cjs.map +0 -1
  144. package/dist/image-ocr-ORATZW73.js.map +0 -1
  145. package/dist/parser-BPVTYM5K.js.map +0 -1
  146. package/dist/parser-GZDOZCHM.js.map +0 -1
  147. package/dist/parser-KTNIUF2N.cjs.map +0 -1
  148. package/dist/pdf-ocr-6XKLXZTF.cjs +0 -13
  149. package/dist/pdf-ocr-6XKLXZTF.cjs.map +0 -1
  150. package/dist/pdf-ocr-HWPNMKKC.js +0 -12
  151. package/dist/pdf-ocr-W2PA2K3U.js +0 -15
  152. package/dist/rasterize-QBA43YAQ.js.map +0 -1
  153. package/dist/render-MDJDKKNH.js +0 -10
  154. package/dist/seal-Z52JR2GX.js +0 -10
  155. package/dist/seal-Z52JR2GX.js.map +0 -1
  156. package/dist/setup-GUQT5ELJ.js.map +0 -1
  157. package/dist/watch-7V6X7LHV.js.map +0 -1
  158. /package/dist/{-SCTT2TBZ.js.map → -DYHT3HWH.js.map} +0 -0
  159. /package/dist/{chunks-KKYJDJT7.js.map → chunks-Q7T4SQGW.js.map} +0 -0
  160. /package/dist/{detect-FX2ZHKZP.js.map → models-6VMAVZR6.js.map} +0 -0
  161. /package/dist/{models-KA4GLQK5.js.map → pdf-ocr-3GDTJSHF.js.map} +0 -0
  162. /package/dist/{pdf-ocr-HWPNMKKC.js.map → pdf-ocr-U7MEGNBZ.js.map} +0 -0
  163. /package/dist/{pdf-ocr-W2PA2K3U.js.map → profile-io-72R3B7KO.js.map} +0 -0
  164. /package/dist/{profile-io-I2TH44EO.js.map → redact-W5K3ILQH.js.map} +0 -0
  165. /package/dist/{redact-7ILEAUTS.js.map → render-6OW6U4L4.js.map} +0 -0
  166. /package/dist/{render-MDJDKKNH.js.map → seal-GTNLVBJX.js.map} +0 -0
@@ -7,11 +7,11 @@ import {
7
7
  createXmlParser,
8
8
  findChildByLocalName,
9
9
  parseRenderStyles
10
- } from "./chunk-GKSFNCVP.js";
10
+ } from "./chunk-EFVAY5VE.js";
11
11
  import {
12
12
  KordocError,
13
13
  precheckZipSize
14
- } from "./chunk-7GEBDS6B.js";
14
+ } from "./chunk-DRQUAYNT.js";
15
15
 
16
16
  // src/render/svg-render.ts
17
17
  import JSZip from "jszip";
@@ -559,7 +559,7 @@ function findFirst(el, name, depth = 0) {
559
559
  return null;
560
560
  }
561
561
  function escapeXml(s) {
562
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
562
+ return s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
563
563
  }
564
564
  var OBJ_TAGS = /* @__PURE__ */ new Set(["tbl", "pic", "container", "equation", "rect", "ellipse", "polygon", "curv", "line", "arc", "ole", "textart"]);
565
565
  var pt = (u) => String(Math.round(u) / 100);
@@ -616,13 +616,17 @@ function buildPara(p) {
616
616
  const pos = findChildByLocalName(ch, "pos");
617
617
  const w = num2(sz, "width") || num2(findChildByLocalName(ch, "curSz"), "width") || num2(findChildByLocalName(ch, "orgSz"), "width");
618
618
  const h = num2(sz, "height") || num2(findChildByLocalName(ch, "curSz"), "height") || num2(findChildByLocalName(ch, "orgSz"), "height");
619
+ const inline = pos?.getAttribute("treatAsChar") === "1";
620
+ const om = inline && cn === "tbl" ? findChildByLocalName(ch, "outMargin") : null;
619
621
  objs.push({
620
622
  el: ch,
621
623
  tag: cn,
622
624
  index: chars.length,
623
- inline: pos?.getAttribute("treatAsChar") === "1",
625
+ inline,
624
626
  width: w,
625
- height: h
627
+ height: h,
628
+ omL: num2(om, "left"),
629
+ omR: num2(om, "right")
626
630
  });
627
631
  pushFillers(chars, 8, prId);
628
632
  } else {
@@ -650,7 +654,7 @@ function lineNaturalWidth(m, styles, start, end) {
650
654
  let text = 0;
651
655
  for (let i = start; i < end && i < m.chars.length; i++) text += charW(m.chars[i], styles);
652
656
  let obj = 0;
653
- for (const o of m.objs) if (o.inline && o.index >= start && o.index < end) obj += o.width;
657
+ for (const o of m.objs) if (o.inline && o.index >= start && o.index < end) obj += o.omL + o.width + o.omR;
654
658
  return { text, obj };
655
659
  }
656
660
  function planLines(m, styles) {
@@ -681,7 +685,7 @@ function planLines(m, styles) {
681
685
  function advanceTo(m, styles, plan, upto) {
682
686
  let x = 0;
683
687
  for (let i = plan.start; i < upto && i < m.chars.length; i++) x += charW(m.chars[i], styles) * plan.scale;
684
- for (const o of m.objs) if (o.inline && o.index >= plan.start && o.index < upto) x += o.width;
688
+ for (const o of m.objs) if (o.inline && o.index >= plan.start && o.index < upto) x += o.omL + o.width + o.omR;
685
689
  return x;
686
690
  }
687
691
  function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
@@ -708,7 +712,7 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
708
712
  const y = oy + seg.vertpos + seg.baseline;
709
713
  while (i < plan.end && i < m.chars.length) {
710
714
  if (m.chars[i].ch === "") {
711
- for (const o of m.objs) if (o.inline && o.index === i) cursor += o.width;
715
+ for (const o of m.objs) if (o.inline && o.index === i) cursor += o.omL + o.width + o.omR;
712
716
  i++;
713
717
  continue;
714
718
  }
@@ -792,7 +796,7 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
792
796
  }
793
797
  const plan = plans[planIdx];
794
798
  if (segPages && segPages[planIdx] !== void 0) ctx.page = segPages[planIdx];
795
- const x = ox + plan.seg.horzpos + plan.xoff + advanceTo(m, ctx.styles, plan, o.index);
799
+ const x = ox + plan.seg.horzpos + plan.xoff + advanceTo(m, ctx.styles, plan, o.index) + o.omL;
796
800
  const yTop = oy + plan.seg.vertpos + Math.max(0, plan.seg.baseline - o.height);
797
801
  drawObject(o, x, yTop, baseV, areaW, ctx, depth);
798
802
  } else {
@@ -847,7 +851,7 @@ function drawObject(o, x, y, baseV, areaW, ctx, depth) {
847
851
  if (!OBJ_TAGS.has(tag)) continue;
848
852
  const sz = findChildByLocalName(ch, "sz");
849
853
  const off = findChildByLocalName(ch, "offset");
850
- const sub = { el: ch, tag, index: 0, inline: true, width: num2(sz, "width"), height: num2(sz, "height") };
854
+ const sub = { el: ch, tag, index: 0, inline: true, width: num2(sz, "width"), height: num2(sz, "height"), omL: 0, omR: 0 };
851
855
  drawObject(sub, x + num2(off, "x"), y + num2(off, "y"), baseV, areaW, ctx, depth + 1);
852
856
  }
853
857
  } else if (o.tag === "equation") {
@@ -1281,4 +1285,4 @@ export {
1281
1285
  fitRatioForFewerLines,
1282
1286
  renderHwpxToSvg
1283
1287
  };
1284
- //# sourceMappingURL=chunk-EOVJ27WC.js.map
1288
+ //# sourceMappingURL=chunk-VXMLENCV.js.map
@@ -0,0 +1 @@
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\nexport function escapeXml(s: string): string {\r\n return s\r\n // XML 1.0 금지 C0 제어문자 제거(탭 0x09·개행 0x0A·CR 0x0D는 유지) — 남기면 산출 SVG가\r\n // 불법 XML이라 뷰어·브라우저가 그 페이지 렌더를 통째로 중단한다(\"PCDATA invalid Char value\").\r\n // gen-ids.escapeXml·source-map.escapeXmlText와 같은 계약 (rhwp #3382 동종)\r\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g, \"\")\r\n .replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\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\n/** omL/omR: TAC 인라인 표의 outMargin 좌/우(HWPUNIT) — 한글은 TAC 표를 \"outMargin 포함\r\n * 폭의 문자\"로 배치한다: 가로 전진폭 = om좌 + 표폭 + om우, 괘선(표 자체)은 pen + om좌\r\n * (rhwp #3396 동종 — 오라클 실측이 표뿐이라 표 외 개체는 0). */\r\ninterface ParaObj { el: Element; tag: string; index: number; inline: boolean; width: number; height: number; omL: number; omR: 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 /** 셀/표 측정 메모 — 중첩표 지수 재계산 캡 (렌더 1회 공유) */\r\n extentMemo: ExtentMemo\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 const inline = pos?.getAttribute(\"treatAsChar\") === \"1\"\r\n // TAC 표만 outMargin 좌/우를 가로 배선 (ParaObj.omL 주석 — rhwp #3396 동종)\r\n const om = inline && cn === \"tbl\" ? findChildByLocalName(ch, \"outMargin\") : null\r\n objs.push({\r\n el: ch, tag: cn, index: chars.length,\r\n inline,\r\n width: w, height: h,\r\n omL: num(om, \"left\"), omR: num(om, \"right\"),\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/** 줄 자연폭 = 텍스트 조각 + 인라인 개체 폭 (개체 폭은 스케일 불변, TAC 표는 outMargin 좌/우 포함) */\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.omL + o.width + o.omR\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) 구간의 전진폭 (텍스트×스케일 + 인라인 개체 — TAC 표는 outMargin 좌/우 포함) */\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.omL + o.width + o.omR\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.omL + o.width + o.omR\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=\"${escapeXml(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 // TAC 표 괘선은 pen + outMargin좌 (advanceTo는 o.index 앞까지라 자신의 om 미포함)\r\n const x = ox + plan.seg.horzpos + plan.xoff + advanceTo(m, ctx.styles, plan, o.index) + o.omL\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\"), omL: 0, omR: 0 }\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/**\r\n * 셀/표 측정 메모 — drawTable(rowH·yoff)과 cellContentExtent↔measureTableHeight 상호재귀가\r\n * 중첩 단계마다 하위 표를 재측정해 지수적으로 불어나는 것을 캡. 렌더 1회 안에서만 공유\r\n * (reflow의 DOM 변형은 드로잉 시작 전에 끝나므로 캐시가 stale해지지 않는다).\r\n */\r\nexport interface ExtentMemo { cell: WeakMap<Element, number>; table: WeakMap<Element, number> }\r\n\r\n/** 셀 콘텐츠 세로 범위 — 줄(vp+th) + 인라인 개체(중첩표·treatAsChar) + PARA 앵커 개체(anchor+h) 최대값 */\r\nfunction cellContentExtent(cell: CellModel, memo?: ExtentMemo): number {\r\n if (!cell.sub) return 0\r\n const hit = memo?.cell.get(cell.el)\r\n if (hit !== undefined) return hit\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, memo)) : 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 memo?.cell.set(cell.el, ext)\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 // 음수 주소(uint32 역변환·손상 입력) 방어 — colX/rowY 인덱스 이탈로 NaN 좌표 방지\r\n ca: Math.max(0, num(addr, \"colAddr\")), ra: Math.max(0, 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, memo?: ExtentMemo): number {\r\n const hit = memo?.table.get(tbl)\r\n if (hit !== undefined) return hit\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, memo) : undefined })),\r\n nRows,\r\n )\r\n let sum = 0\r\n for (const h of rowH) sum += h\r\n memo?.table.set(tbl, sum)\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, ctx.extentMemo) : 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, ctx.extentMemo)\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\") || (bytes.length > 3 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)) return \"image/gif\"\r\n if (lower.endsWith(\".svg\")) return \"image/svg+xml\"\r\n if (bytes.length > 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return \"image/jpeg\"\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 extentMemo: { cell: new WeakMap(), table: new WeakMap() },\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에서 &lt;로 이스케이프되므로 리터럴 <…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 // u 플래그 필수 — 없으면 astral 문자(𝐀·이모지 등)가 서로게이트 반쪽 2개로 쪼개져 폭이 2배로 계산된다\r\n const units = text.match(mode === \"keep\" ? / +|[^ ]+/gu : / +|[^ ]/gu) ?? []\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"]}
@@ -141,4 +141,4 @@ export {
141
141
  encodePng,
142
142
  inlineImagesIntoMarkdown
143
143
  };
144
- //# sourceMappingURL=chunk-7FCNOKOR.js.map
144
+ //# sourceMappingURL=chunk-W34QSJFF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/image/transcode.ts"],"sourcesContent":["/**\r\n * 순수 JS BMP → PNG 트랜스코더 + 마크다운 이미지 인라이너.\r\n *\r\n * HWP5 BinData 임베드 이미지는 비압축 BMP(수 MB)인 경우가 많다. AI 에이전트가\r\n * 자체 완결형 마크다운을 받도록, BMP 를 PNG(무손실 압축)로 변환한 뒤 base64\r\n * data URI 로 마크다운에 인라인한다.\r\n *\r\n * 의존성 최소화 원칙 — Node 내장(node:zlib, Buffer)만 사용한다.\r\n */\r\n\r\nimport { deflateSync } from \"node:zlib\"\r\n\r\n// ─── CRC32 (PNG 청크용) ──────────────────────────────\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++) {\r\n c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1)\r\n }\r\n table[n] = c >>> 0\r\n }\r\n return table\r\n})()\r\n\r\n/** 표준 PNG CRC32 (다항식 0xEDB88320) */\r\nfunction crc32(bytes: Uint8Array): number {\r\n let c = 0xffffffff\r\n for (let i = 0; i < bytes.length; i++) {\r\n c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8)\r\n }\r\n return (c ^ 0xffffffff) >>> 0\r\n}\r\n\r\n// ─── BMP 디코드 ──────────────────────────────────────\r\n\r\nconst BI_RGB = 0\r\n/** 폭·높이 상한 — 비정상 헤더에 의한 과대 할당 방지 */\r\nconst MAX_DIM = 0x7fff\r\n/**\r\n * 총 픽셀(W×H) 상한 — MAX_DIM 은 각 변만 제한하므로 곱의 폭주(과대 할당 + 긴 deflateSync\r\n * 동기 블로킹)를 별도로 차단한다. raw 저장 BMP 는 상위 inflate 100MB 캡을 우회하므로 필요.\r\n * 36MP = A4 전면 600dpi 스캔(≈35MP)까지 허용 — 그 이상은 rgba+raw 로 300MB 를 넘겨\r\n * MCP 서버 이벤트루프를 수 초 세우므로 원본 바이트 폴백으로 돌린다.\r\n */\r\nconst MAX_PIXELS = 36_000_000\r\n\r\n/**\r\n * 비압축 BMP(24/32bpp, BI_RGB) → PNG(8bit RGBA).\r\n *\r\n * 지원: BITMAPFILEHEADER + BITMAPINFOHEADER(biSize>=40), biBitCount 24/32,\r\n * biCompression BI_RGB, bottom-up(양수 biHeight)/top-down(음수), 24bpp 4바이트 행 패딩.\r\n * 미지원 변형(팔레트/8bpp·RLE·BITFIELDS·OS/2 헤더 등)은 null 을 반환하여\r\n * 호출부가 원본 바이트로 폴백하게 한다.\r\n */\r\nexport function bmpToPng(bmp: Uint8Array): Uint8Array | null {\r\n // BITMAPFILEHEADER(14) + BITMAPINFOHEADER(40)\r\n if (bmp.length < 54) return null\r\n if (bmp[0] !== 0x42 || bmp[1] !== 0x4d) return null // \"BM\"\r\n\r\n const dv = new DataView(bmp.buffer, bmp.byteOffset, bmp.byteLength)\r\n const dataOffset = dv.getUint32(10, true)\r\n const headerSize = dv.getUint32(14, true)\r\n if (headerSize < 40) return null // OS/2 BITMAPCOREHEADER 등 미지원\r\n\r\n const width = dv.getInt32(18, true)\r\n const rawHeight = dv.getInt32(22, true)\r\n const bitCount = dv.getUint16(28, true)\r\n const compression = dv.getUint32(30, true)\r\n\r\n if (compression !== BI_RGB) return null // RLE / BITFIELDS 미지원\r\n if (bitCount !== 24 && bitCount !== 32) return null // 팔레트 / 8bpp 등 미지원\r\n if (width <= 0 || rawHeight === 0) return null\r\n if (width > MAX_DIM || Math.abs(rawHeight) > MAX_DIM) return null\r\n\r\n const topDown = rawHeight < 0\r\n const height = Math.abs(rawHeight)\r\n // 총 픽셀 상한 검사 — rgba/raw 할당·deflate 전에 조기 차단 (각 변은 MAX_DIM 이하여도 곱은 폭주 가능)\r\n if (width * height > MAX_PIXELS) return null\r\n const bytesPerPixel = bitCount >> 3\r\n // 행 스트라이드 — 4바이트 경계 패딩 (24bpp 에서 유효)\r\n const rowStride = (width * bytesPerPixel + 3) & ~3\r\n if (dataOffset + rowStride * height > bmp.length) return null // 잘린 파일\r\n\r\n const rgba = new Uint8Array(width * height * 4)\r\n let anyAlpha = 0\r\n\r\n for (let y = 0; y < height; y++) {\r\n // bottom-up 은 첫 행이 이미지 맨 아래 → 뒤집어 읽는다\r\n const srcRow = topDown ? y : height - 1 - y\r\n let src = dataOffset + srcRow * rowStride\r\n let dst = y * width * 4\r\n for (let x = 0; x < width; x++) {\r\n // BMP 픽셀은 BGR(A) 순\r\n rgba[dst] = bmp[src + 2] // R\r\n rgba[dst + 1] = bmp[src + 1] // G\r\n rgba[dst + 2] = bmp[src] // B\r\n const a = bitCount === 32 ? bmp[src + 3] : 255\r\n rgba[dst + 3] = a\r\n anyAlpha |= a\r\n src += bytesPerPixel\r\n dst += 4\r\n }\r\n }\r\n\r\n // 32bpp BI_RGB 의 4번째 바이트는 \"미정의\" — 전부 0 이면 전면 투명으로 오해되므로\r\n // 불투명(255)으로 승격한다. 실제 알파가 있으면(anyAlpha!=0) 그대로 보존.\r\n if (bitCount === 32 && anyAlpha === 0) {\r\n for (let i = 3; i < rgba.length; i += 4) rgba[i] = 255\r\n }\r\n\r\n return encodePng(width, height, rgba)\r\n}\r\n\r\n// ─── PNG 인코드 (8bit RGBA, color type 6) ────────────\r\n\r\nconst PNG_SIGNATURE = Uint8Array.of(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a)\r\n\r\n/** [4B 길이][4B 타입][데이터][4B CRC] 청크 조립. CRC 는 (타입+데이터) 기준. */\r\nfunction chunk(type: string, data: Uint8Array): Uint8Array {\r\n const body = new Uint8Array(4 + data.length)\r\n body[0] = type.charCodeAt(0)\r\n body[1] = type.charCodeAt(1)\r\n body[2] = type.charCodeAt(2)\r\n body[3] = type.charCodeAt(3)\r\n body.set(data, 4)\r\n\r\n const out = new Uint8Array(8 + data.length + 4)\r\n const dv = new DataView(out.buffer)\r\n dv.setUint32(0, data.length, false) // 길이(데이터만, big-endian)\r\n out.set(body, 4)\r\n dv.setUint32(8 + data.length, crc32(body), false)\r\n return out\r\n}\r\n\r\n/** 8bit RGBA 픽셀 버퍼 → PNG (PDF 이미지 XObject 추출에서도 사용) */\r\nexport function encodePng(width: number, height: number, rgba: Uint8Array): Uint8Array {\r\n // IHDR: width, height, bit depth 8, color type 6(RGBA), compression 0, filter 0, interlace 0\r\n const ihdr = new Uint8Array(13)\r\n const iv = new DataView(ihdr.buffer)\r\n iv.setUint32(0, width, false)\r\n iv.setUint32(4, height, false)\r\n ihdr[8] = 8\r\n ihdr[9] = 6\r\n\r\n // IDAT: 각 스캔라인 앞에 필터 바이트 0(None) 을 붙인 raw 데이터를 zlib deflate\r\n const stride = width * 4\r\n const raw = new Uint8Array((stride + 1) * height)\r\n for (let y = 0; y < height; y++) {\r\n const rowStart = y * (stride + 1)\r\n raw[rowStart] = 0 // filter: None\r\n raw.set(rgba.subarray(y * stride, y * stride + stride), rowStart + 1)\r\n }\r\n const idat = deflateSync(raw)\r\n\r\n const ihdrChunk = chunk(\"IHDR\", ihdr)\r\n const idatChunk = chunk(\"IDAT\", idat)\r\n const iendChunk = chunk(\"IEND\", new Uint8Array(0))\r\n\r\n const out = new Uint8Array(PNG_SIGNATURE.length + ihdrChunk.length + idatChunk.length + iendChunk.length)\r\n let o = 0\r\n out.set(PNG_SIGNATURE, o); o += PNG_SIGNATURE.length\r\n out.set(ihdrChunk, o); o += ihdrChunk.length\r\n out.set(idatChunk, o); o += idatChunk.length\r\n out.set(iendChunk, o)\r\n return out\r\n}\r\n\r\n// ─── 마크다운 이미지 인라이너 ────────────────────────\r\n\r\ninterface InlineImage {\r\n filename: string\r\n data: Uint8Array\r\n mimeType: string\r\n}\r\n\r\n/**\r\n * 마크다운의 이미지 참조를 base64 data URI 로 치환해 자체 완결형 마크다운을 만든다.\r\n * 두 방출 형태를 모두 커버한다:\r\n * - `![image](FILENAME)` / `![image](images/FILENAME)` — 일반 문단 (CLI 접두사 포함)\r\n * - `<img src=\"FILENAME\">` — 병합/중첩 표 셀 (table/builder.ts HTML 표 경로).\r\n * 이걸 빼면 CLI `--inline-images` 가 이미지 저장을 생략하면서 표 셀 이미지만\r\n * dangling 참조로 유실된다.\r\n *\r\n * @param opts.compress 기본 true. `image/bmp` 는 PNG 로 트랜스코딩 후 인라인하며,\r\n * 트랜스코딩 실패 시 원본 바이트+원본 MIME 으로 폴백한다.\r\n */\r\nexport function inlineImagesIntoMarkdown(\r\n markdown: string,\r\n images: ReadonlyArray<InlineImage>,\r\n opts?: { compress?: boolean },\r\n): string {\r\n const compress = opts?.compress !== false\r\n // 파일명 → data URI 사전 구축 후 단일 패스 치환 — 이미지 k개마다 전문 재스캔하던\r\n // O(k²) 제거. 맵에 없는 파일명 참조는 원문 그대로 둔다 (기존 계약 동일).\r\n const uris = new Map<string, string>()\r\n for (const img of images) {\r\n let bytes = img.data\r\n let mime = img.mimeType\r\n if (compress && mime === \"image/bmp\") {\r\n const png = bmpToPng(img.data)\r\n if (png) {\r\n bytes = png\r\n mime = \"image/png\"\r\n }\r\n }\r\n uris.set(img.filename, `data:${mime};base64,${Buffer.from(bytes).toString(\"base64\")}`)\r\n }\r\n if (uris.size === 0) return markdown\r\n // FILENAME 과 images/FILENAME 접두사를 모두 정확한 파일명 기준으로 치환\r\n let out = markdown.replace(/!\\[image\\]\\((?:images\\/)?([^()\\s]+)\\)/g, (m, name: string) => {\r\n const uri = uris.get(name)\r\n return uri !== undefined ? `![image](${uri})` : m\r\n })\r\n out = out.replace(/(<img\\b[^>]*\\bsrc=\")(?:images\\/)?([^\"]*)(\")/g, (m, pre: string, name: string, post: string) => {\r\n const uri = uris.get(name)\r\n return uri !== undefined ? `${pre}${uri}${post}` : m\r\n })\r\n return out\r\n}\r\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  KordocError
4
- } from "./chunk-7GEBDS6B.js";
4
+ } from "./chunk-DRQUAYNT.js";
5
5
 
6
6
  // src/hwpx/profile-io.ts
7
7
  import { z } from "zod";
@@ -89,4 +89,4 @@ export {
89
89
  formatProfileSchema,
90
90
  parseFormatProfileJson
91
91
  };
92
- //# sourceMappingURL=chunk-URKX77GB.js.map
92
+ //# sourceMappingURL=chunk-WFOQFJI4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hwpx/profile-io.ts"],"sourcesContent":["/**\r\n * FormatProfile JSON 경계 검증 — CLI(--profile)와 MCP(profile_path)가 공유하는 1벌\r\n * 스키마 (이슈 #41 표면 노출, v4.0.6). 라이브러리 API(markdownToHwpx options.profile)는\r\n * 타입 계약을 신뢰하고, 파일에서 들어오는 JSON(손편집 가능)만 여기서 검증한다.\r\n */\r\n\r\nimport { z } from \"zod\"\r\nimport { KordocError } from \"../utils.js\"\r\nimport type { FormatProfile } from \"./gen-profile.js\"\r\n\r\n// 괘선 값 검증 (v4.0.4) — type/width/color는 XML에 원문 방출되므로 손편집 오타가\r\n// 한컴 로드 실패·무시로 번지기 전에 여기서 잡는다.\r\n// type: HWPX LineType2 열거(KS X 6101). 실측 코퍼스 등장: NONE·SOLID·DASH·DOUBLE_SLIM·SLIM_THICK\r\nconst borderType = z.enum([\r\n \"NONE\", \"SOLID\", \"DASH\", \"DOT\", \"DASH_DOT\", \"DASH_DOT_DOT\", \"LONG_DASH\", \"CIRCLE\",\r\n \"DOUBLE_SLIM\", \"SLIM_THICK\", \"THICK_SLIM\", \"SLIM_THICK_SLIM\", \"WAVE\", \"DOUBLEWAVE\",\r\n])\r\nconst mmWidth = z.string().regex(/^\\d+(\\.\\d+)? ?mm$/, '\"0.12 mm\" 형식(mm 단위)이어야 합니다')\r\nconst hexColor = z.string().regex(/^(#[0-9A-Fa-f]{6}|none)$/i, '\"#RRGGBB\" 또는 \"none\"이어야 합니다')\r\n\r\nconst borderDef = z.object({\r\n type: borderType,\r\n width: mmWidth,\r\n color: hexColor,\r\n})\r\n\r\nconst borderFillDef = z.object({\r\n leftBorder: borderDef.optional(),\r\n rightBorder: borderDef.optional(),\r\n topBorder: borderDef.optional(),\r\n bottomBorder: borderDef.optional(),\r\n fill: z.object({ faceColor: hexColor }).optional(),\r\n})\r\n\r\nconst charPrDef = z.object({\r\n height_hwpunit: z.string().optional(),\r\n textColor: hexColor.optional(),\r\n bold: z.boolean().optional(),\r\n italic: z.boolean().optional(),\r\n underline: z.boolean().optional(),\r\n fontRef_hangul: z.string().optional(),\r\n fontName_hangul: z.string().optional(),\r\n})\r\n\r\nconst cellProfile = z.object({\r\n row: z.number().int().min(0),\r\n col: z.number().int().min(0),\r\n rowSpan: z.number().int().min(1).optional(),\r\n colSpan: z.number().int().min(1).optional(),\r\n width_hwpunit: z.string().optional(),\r\n height_hwpunit: z.string().optional(),\r\n borderFillIDRef: z.string().optional(),\r\n charPrIDRef: z.string().optional(),\r\n})\r\n\r\nconst tableProfile = z.object({\r\n table_index: z.number().int().min(0),\r\n rows: z.number().int().min(1),\r\n cols: z.number().int().min(1),\r\n anchor_text: z.string().optional(),\r\n anchor_row: z.string().optional(),\r\n width_hwpunit: z.string().optional(),\r\n col_widths_hwpunit: z.array(z.string()).optional(),\r\n cells: z.array(cellProfile),\r\n used_border_fills: z.record(borderFillDef),\r\n used_char_prs: z.record(charPrDef).optional(),\r\n})\r\n\r\nexport const formatProfileSchema = z.object({\r\n schema_version: z.string().optional(),\r\n tables: z.array(tableProfile),\r\n})\r\n\r\n/** 프로필 JSON 텍스트 → 검증된 FormatProfile. 실패 시 KordocError(위치·사유 요약) */\r\nexport function parseFormatProfileJson(text: string): FormatProfile {\r\n let raw: unknown\r\n try {\r\n raw = JSON.parse(text)\r\n } catch (e) {\r\n throw new KordocError(`프로필 JSON 파싱 실패: ${e instanceof Error ? e.message : String(e)}`)\r\n }\r\n const r = formatProfileSchema.safeParse(raw)\r\n if (!r.success) {\r\n const first = r.error.issues.slice(0, 3).map(i => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`).join(\" / \")\r\n throw new KordocError(`프로필 스키마 불일치: ${first}`)\r\n }\r\n return r.data as FormatProfile\r\n}\r\n"],"mappings":"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","names":[]}
@@ -243,4 +243,4 @@ export {
243
243
  ensureFormulaModels,
244
244
  ensureSingleModel
245
245
  };
246
- //# sourceMappingURL=chunk-NNDVFNMJ.js.map
246
+ //# sourceMappingURL=chunk-YNJDNP44.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pdf/formula/models.ts"],"sourcesContent":["/**\r\n * 수식 OCR 모델 자동 다운로드 + SHA-256 검증\r\n *\r\n * 캐시 위치: `~/.cache/kordoc/models/pix2text/`\r\n * 총 ~155MB (MFD 44MB + MFR encoder 87MB + MFR decoder 30MB + tokenizer 40KB)\r\n *\r\n * HF URL + SHA 는 Docufinder archive/rust-formula-ocr-wip 에서 검증 완료됨.\r\n * 모델 버전 갱신 시 URL + SHA 를 함께 갱신해야 한다.\r\n */\r\n\r\nimport { createHash } from \"crypto\"\r\nimport { createReadStream } from \"fs\"\r\nimport { mkdir, stat, unlink, rename } from \"fs/promises\"\r\nimport { createWriteStream } from \"fs\"\r\nimport { homedir } from \"os\"\r\nimport { join, dirname } from \"path\"\r\nimport { pipeline } from \"stream/promises\"\r\nimport { Readable } from \"stream\"\r\n\r\nexport interface ModelSpec {\r\n name: string\r\n filename: string\r\n url: string\r\n sha256: string\r\n sizeMb: number\r\n}\r\n\r\n/** 모델 스펙 (SHA-256 은 실제 다운로드로 검증됨 — 변경 금지) */\r\nexport const MFD_MODEL: ModelSpec = {\r\n name: \"Pix2Text MFD\",\r\n filename: \"mfd.onnx\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfd/resolve/main/mfd-v20240618.onnx\",\r\n sha256: \"51a8854743b17ae654729af8db82a630c1ccfa06debf4856c8b28055f87d02c1\",\r\n sizeMb: 42,\r\n}\r\n\r\nexport const MFR_ENCODER_MODEL: ModelSpec = {\r\n name: \"Pix2Text MFR encoder\",\r\n filename: \"encoder_model.onnx\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/encoder_model.onnx\",\r\n sha256: \"bd8d5c322792e9ec45793af5569e9748f82a3d728a9e00213dbfc56c1486f37d\",\r\n sizeMb: 87,\r\n}\r\n\r\nexport const MFR_DECODER_MODEL: ModelSpec = {\r\n name: \"Pix2Text MFR decoder\",\r\n filename: \"decoder_model.onnx\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/decoder_model.onnx\",\r\n sha256: \"fd0f92d7a012f3dae41e1ac79421aea0ea888b5a66cb3f9a004e424f82f3daed\",\r\n sizeMb: 30,\r\n}\r\n\r\nexport const MFR_TOKENIZER: ModelSpec = {\r\n name: \"Pix2Text MFR tokenizer\",\r\n filename: \"tokenizer.json\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/tokenizer.json\",\r\n sha256: \"3e2ab757277d22639bec28c9d7972e352d3d1dba223051fa674002dc5ab64df3\",\r\n sizeMb: 1,\r\n}\r\n\r\nexport const ALL_FORMULA_MODELS: ReadonlyArray<ModelSpec> = [\r\n MFD_MODEL,\r\n MFR_ENCODER_MODEL,\r\n MFR_DECODER_MODEL,\r\n MFR_TOKENIZER,\r\n]\r\n\r\n/**\r\n * 모델 캐시 하위 디렉토리 경로. 환경 변수 `KORDOC_MODEL_CACHE` 가 설정되면\r\n * 해당 경로를 우선 사용, 없으면 `~/.cache/kordoc/models/<subdir>/`.\r\n * (수식 OCR 외 텍스트 OCR 등 다른 모델군도 공용 — subdir 로 격리)\r\n */\r\nexport function getModelsDir(subdir: string): string {\r\n const override = process.env.KORDOC_MODEL_CACHE\r\n if (override && override.trim()) {\r\n return join(override, subdir)\r\n }\r\n return join(homedir(), \".cache\", \"kordoc\", \"models\", subdir)\r\n}\r\n\r\nexport function getFormulaModelsDir(): string {\r\n return getModelsDir(\"pix2text\")\r\n}\r\n\r\nexport interface ModelStatus {\r\n spec: ModelSpec\r\n localPath: string\r\n exists: boolean\r\n /** SHA 검증까지 끝났는가 (exists && valid) */\r\n verified: boolean\r\n /** 검증 실패 시 이유 */\r\n invalidReason?: string\r\n}\r\n\r\n/** 지정 모델군의 존재/유효성 상태 반환 (다운로드 없이 확인만) */\r\nexport async function getModelStatusIn(dir: string, specs: ReadonlyArray<ModelSpec>): Promise<ModelStatus[]> {\r\n const result: ModelStatus[] = []\r\n for (const spec of specs) {\r\n const localPath = join(dir, spec.filename)\r\n let exists = false\r\n try {\r\n const s = await stat(localPath)\r\n exists = s.isFile() && s.size > 0\r\n } catch {\r\n exists = false\r\n }\r\n if (!exists) {\r\n result.push({ spec, localPath, exists: false, verified: false })\r\n continue\r\n }\r\n try {\r\n const actual = await sha256OfFile(localPath)\r\n if (actual === spec.sha256) {\r\n result.push({ spec, localPath, exists: true, verified: true })\r\n } else {\r\n result.push({\r\n spec,\r\n localPath,\r\n exists: true,\r\n verified: false,\r\n invalidReason: `SHA256 mismatch: expected ${spec.sha256}, got ${actual}`,\r\n })\r\n }\r\n } catch (e) {\r\n result.push({\r\n spec,\r\n localPath,\r\n exists: true,\r\n verified: false,\r\n invalidReason: `SHA compute failed: ${(e as Error).message}`,\r\n })\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/** 모든 수식 모델의 존재/유효성 상태 반환 (다운로드 없이 확인만) */\r\nexport async function getFormulaModelStatus(): Promise<ModelStatus[]> {\r\n return getModelStatusIn(getFormulaModelsDir(), ALL_FORMULA_MODELS)\r\n}\r\n\r\nexport interface DownloadProgress {\r\n spec: ModelSpec\r\n /** 현재까지 다운로드한 바이트 */\r\n downloaded: number\r\n /** 전체 바이트 (알 수 있으면) */\r\n total: number | null\r\n /** \"download\" | \"verify\" | \"done\" | \"skip\" */\r\n phase: \"download\" | \"verify\" | \"done\" | \"skip\" | \"error\"\r\n message?: string\r\n}\r\n\r\nexport type ProgressHandler = (p: DownloadProgress) => void\r\n\r\n/**\r\n * 지정 모델군을 다운로드/검증한다.\r\n * - 이미 있고 SHA 일치 → skip\r\n * - 없음 → 다운로드 후 SHA 검증. 실패 시 파일 삭제.\r\n */\r\nexport async function ensureModelsIn(dir: string, specs: ReadonlyArray<ModelSpec>, onProgress?: ProgressHandler): Promise<void> {\r\n await mkdir(dir, { recursive: true })\r\n\r\n for (const spec of specs) {\r\n const localPath = join(dir, spec.filename)\r\n\r\n if (await isExistingValid(localPath, spec.sha256)) {\r\n onProgress?.({\r\n spec,\r\n downloaded: 0,\r\n total: null,\r\n phase: \"skip\",\r\n message: \"이미 존재 + SHA 일치\",\r\n })\r\n continue\r\n }\r\n\r\n // 기존 파일 있지만 SHA 불일치 → 삭제\r\n try {\r\n await unlink(localPath)\r\n } catch {\r\n // 없을 수 있음\r\n }\r\n\r\n await downloadToFile(spec, localPath, onProgress)\r\n }\r\n}\r\n\r\n/** 필요한 모든 수식 모델을 다운로드/검증한다. */\r\nexport async function ensureFormulaModels(onProgress?: ProgressHandler): Promise<void> {\r\n return ensureModelsIn(getFormulaModelsDir(), ALL_FORMULA_MODELS, onProgress)\r\n}\r\n\r\n/** 단일 모델만 확인/다운로드 (진단 UI 에서 개별 상태 새로고침용) */\r\nexport async function ensureSingleModel(spec: ModelSpec, onProgress?: ProgressHandler): Promise<void> {\r\n const dir = getFormulaModelsDir()\r\n await mkdir(dir, { recursive: true })\r\n const localPath = join(dir, spec.filename)\r\n if (await isExistingValid(localPath, spec.sha256)) {\r\n onProgress?.({ spec, downloaded: 0, total: null, phase: \"skip\" })\r\n return\r\n }\r\n try {\r\n await unlink(localPath)\r\n } catch {\r\n // 재다운로드 전 손상/잔존 파일 정리 best-effort — 애초에 없으면 실패해도 무방\r\n }\r\n await downloadToFile(spec, localPath, onProgress)\r\n}\r\n\r\nasync function isExistingValid(localPath: string, sha256Expected: string): Promise<boolean> {\r\n try {\r\n const s = await stat(localPath)\r\n if (!s.isFile() || s.size === 0) return false\r\n } catch {\r\n return false\r\n }\r\n try {\r\n const actual = await sha256OfFile(localPath)\r\n return actual === sha256Expected\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nasync function downloadToFile(\r\n spec: ModelSpec,\r\n localPath: string,\r\n onProgress?: ProgressHandler,\r\n): Promise<void> {\r\n // 먼저 .part 로 받고 검증 후 rename — 중단된 다운로드가 \"정상 파일\"로 오인되는 걸 방지\r\n const partPath = `${localPath}.part`\r\n await mkdir(dirname(localPath), { recursive: true })\r\n\r\n const resp = await fetch(spec.url, {\r\n headers: {\r\n // HF CDN 은 UA 없으면 가끔 403 을 뱉는다\r\n \"User-Agent\": \"kordoc-formula-ocr/1.0 (+https://github.com/chrisryugj/kordoc)\",\r\n },\r\n })\r\n if (!resp.ok || !resp.body) {\r\n throw new Error(\r\n `${spec.name} 다운로드 실패: HTTP ${resp.status} ${resp.statusText} (${spec.url})`,\r\n )\r\n }\r\n\r\n const lenHeader = resp.headers.get(\"content-length\")\r\n const total = lenHeader ? Number.parseInt(lenHeader, 10) : null\r\n let downloaded = 0\r\n\r\n const ws = createWriteStream(partPath)\r\n try {\r\n const reader = Readable.fromWeb(resp.body as unknown as import(\"stream/web\").ReadableStream)\r\n reader.on(\"data\", (chunk: Buffer | Uint8Array) => {\r\n downloaded += chunk.length\r\n onProgress?.({\r\n spec,\r\n downloaded,\r\n total,\r\n phase: \"download\",\r\n })\r\n })\r\n await pipeline(reader, ws)\r\n } catch (e) {\r\n try {\r\n await unlink(partPath)\r\n } catch {\r\n // .part 임시파일 정리 best-effort — 본 에러(스트리밍 실패)를 가리지 않도록 무시\r\n }\r\n throw new Error(`${spec.name} 스트리밍 실패: ${(e as Error).message}`)\r\n }\r\n\r\n onProgress?.({\r\n spec,\r\n downloaded,\r\n total,\r\n phase: \"verify\",\r\n })\r\n\r\n // SHA 검증\r\n let actual: string\r\n try {\r\n actual = await sha256OfFile(partPath)\r\n } catch (e) {\r\n try {\r\n await unlink(partPath)\r\n } catch {\r\n // .part 임시파일 정리 best-effort — 본 에러(SHA 계산 실패)를 가리지 않도록 무시\r\n }\r\n throw new Error(`${spec.name} SHA 계산 실패: ${(e as Error).message}`)\r\n }\r\n\r\n if (actual !== spec.sha256) {\r\n try {\r\n await unlink(partPath)\r\n } catch {\r\n // 오염된 .part 정리 best-effort — 본 에러(SHA mismatch)를 가리지 않도록 무시\r\n }\r\n throw new Error(\r\n `${spec.name} SHA256 mismatch: expected ${spec.sha256}, got ${actual} — 모델 URL 이 오염되었거나 전송 중 손상되었습니다.`,\r\n )\r\n }\r\n\r\n await rename(partPath, localPath)\r\n onProgress?.({\r\n spec,\r\n downloaded,\r\n total,\r\n phase: \"done\",\r\n })\r\n}\r\n\r\nasync function sha256OfFile(p: string): Promise<string> {\r\n const h = createHash(\"sha256\")\r\n const stream = createReadStream(p)\r\n await pipeline(stream, async function* (src) {\r\n for await (const chunk of src) {\r\n h.update(chunk)\r\n // yield 안 함 (소비만)\r\n }\r\n })\r\n return h.digest(\"hex\")\r\n}\r\n"],"mappings":"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","names":[]}
@@ -10,4 +10,4 @@ export {
10
10
  HEADING_RATIO_H2,
11
11
  HEADING_RATIO_H3
12
12
  };
13
- //# sourceMappingURL=chunk-LM7C6HI2.js.map
13
+ //# sourceMappingURL=chunk-ZSL6MQVE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["/** kordoc 공통 타입 정의 */\r\n\r\n// ─── 중간 표현 (Intermediate Representation) ─────────\r\n\r\nexport interface CellContext {\r\n text: string\r\n colSpan: number\r\n rowSpan: number\r\n /** HWP5 셀 열 주소 (0-based) — 병합 테이블 배치용 */\r\n colAddr?: number\r\n /** HWP5 셀 행 주소 (0-based) — 병합 테이블 배치용 */\r\n rowAddr?: number\r\n}\r\n\r\n/** 블록 타입 — v2.0에서 heading, list, image, separator 추가 */\r\nexport type IRBlockType = \"paragraph\" | \"table\" | \"heading\" | \"list\" | \"image\" | \"separator\"\r\n\r\n/** 인라인 강조 run-span — 문단 텍스트를 서식 단위로 쪼갠 조각 (텍스트 연결 = block.text) */\r\nexport interface IRSpan {\r\n text: string\r\n bold?: boolean\r\n italic?: boolean\r\n strike?: boolean\r\n code?: boolean\r\n}\r\n\r\nexport interface IRBlock {\r\n type: IRBlockType\r\n text?: string\r\n table?: IRTable\r\n /** 헤딩 레벨 (1-6), type=\"heading\"일 때 사용 */\r\n level?: number\r\n /** 원본 페이지 번호 (1-based) */\r\n pageNumber?: number\r\n /** 바운딩 박스 — PDF에서만 제공 */\r\n bbox?: BoundingBox\r\n /** 텍스트 스타일 정보 (선택) */\r\n style?: InlineStyle\r\n /** 리스트 타입, type=\"list\"일 때 사용 */\r\n listType?: \"ordered\" | \"unordered\"\r\n /** 중첩 리스트 아이템 */\r\n children?: IRBlock[]\r\n /** 하이퍼링크 URL */\r\n href?: string\r\n /** 각주/미주 텍스트 (인라인 삽입용) */\r\n footnoteText?: string\r\n /** 이미지 데이터 (type=\"image\"일 때) */\r\n imageData?: ImageData\r\n /** 인라인 강조 run-span (선택 — kordoc 생성 hwpx 왕복 채널 + 외래 실속성 볼드/이탤릭).\r\n * 존재하면 마크다운 변환이 **·*·` 마커를 재방출한다 */\r\n spans?: IRSpan[]\r\n /** 인용문 문단 (선택 — kordoc 생성 hwpx 왕복 채널) — 마크다운 변환이 \"> \" 접두 재방출 */\r\n quote?: boolean\r\n /**\r\n * 문단 들여쓰기(HWPUNIT, 선택) — HWPX paraPr `<hh:margin>` 자식요소형 hc:left\r\n * (+양수 hc:intent 첫줄분). 마크다운 방출엔 쓰지 않는 관찰 슬롯 — gongmun 리스트\r\n * depth 재유도·양식 분석 등 소비자 몫 (v4.0.4)\r\n */\r\n indent?: number\r\n /**\r\n * gongmun 리스트 단계(1~7, 선택) — indent를 levelIndent 단위로 역산한 소비 결과\r\n * (v4.0.5). md 리스트 문법과 충돌하는 부호('- '·'1) ') 문단에만 채워지며,\r\n * 마크다운 변환이 2칸/단계 선행 공백을 방출해 재생성 시 depth가 복원된다\r\n */\r\n listDepth?: number\r\n}\r\n\r\n/** 추출된 이미지 바이너리 데이터 */\r\nexport interface ImageData {\r\n /** 이미지 바이너리 */\r\n data: Uint8Array\r\n /** MIME 타입 (image/png, image/jpeg, image/gif, image/bmp, image/wmf, image/emf) */\r\n mimeType: string\r\n /** 원본 파일명 (있는 경우) */\r\n filename?: string\r\n}\r\n\r\n/** 바운딩 박스 — PDF 포인트 단위 (72pt = 1인치) */\r\nexport interface BoundingBox {\r\n page: number\r\n x: number\r\n y: number\r\n width: number\r\n height: number\r\n}\r\n\r\n/** 인라인 텍스트 스타일 */\r\nexport interface InlineStyle {\r\n bold?: boolean\r\n italic?: boolean\r\n /** 취소선 — 법령 개정문 등의 삭제 표시. 판정은 취소선 모양 whitelist (비트만 믿으면 오탐) */\r\n strike?: boolean\r\n fontSize?: number\r\n fontName?: string\r\n}\r\n\r\nexport interface IRTable {\r\n rows: number\r\n cols: number\r\n cells: IRCell[][]\r\n /** 첫 행을 헤더로 렌더링할지 여부 (현재: rows > 1이면 true — 의미적 감지가 아닌 레이아웃 힌트) */\r\n hasHeader: boolean\r\n /** 표 캡션 (예: \"표 1. 부서별 예산\") — v3.0 */\r\n caption?: string\r\n /**\r\n * 캡션 내부 블록 콘텐츠 — v4.2.8 (#55).\r\n * 캡션 안 중첩표·문단을 구조 그대로, 원문 순서대로 보존한다 (IRCell.blocks와 같은 계약).\r\n * 표가 있을 때만 채워지며, caption은 하위 호환용 평탄화 문자열로 계속 제공된다.\r\n */\r\n captionBlocks?: IRBlock[]\r\n}\r\n\r\nexport interface IRCell {\r\n text: string\r\n colSpan: number\r\n rowSpan: number\r\n /**\r\n * 셀 내부 블록 콘텐츠 — v3.0.\r\n * 중첩 표·이미지 등 구조 콘텐츠(또는 왕복 채널 span 문단)가 있는 셀에만 채워지며,\r\n * 그 안의 문단·표·이미지를 문서(원문) 순서대로 보존한다. 다중 문단뿐인 평문 셀은\r\n * blocks 없이 text 평탄화(문단을 `\\n`으로 결합)로만 제공된다 (blocks 무게 억제, #57).\r\n * 표와 텍스트가 한 줄에 번갈아 놓인 셀도 배치 순서를 따른다 (v4.2.3, #49).\r\n * blocks가 있으면 text는 blocks의 평탄화 텍스트(하위 호환용)다.\r\n */\r\n blocks?: IRBlock[]\r\n /** 제목 셀 여부 (HWP5 width_ref bit2 / HWPX header 속성) — v3.0 */\r\n isHeader?: boolean\r\n}\r\n\r\n// ─── 메타데이터 ─────────────────────────────────────\r\n\r\n/** 문서 메타데이터 — 각 포맷에서 추출 가능한 필드만 채워짐 */\r\nexport interface DocumentMetadata {\r\n /** 문서 제목 */\r\n title?: string\r\n /** 작성자 */\r\n author?: string\r\n /** 작성 프로그램 (예: \"한글 2020\", \"Adobe Acrobat\") */\r\n creator?: string\r\n /** 생성일시 (ISO 8601) */\r\n createdAt?: string\r\n /** 수정일시 (ISO 8601) */\r\n modifiedAt?: string\r\n /** 페이지/섹션 수 */\r\n pageCount?: number\r\n /** 문서 포맷 버전 (예: HWP \"5.1.0.1\") */\r\n version?: string\r\n /** 설명 */\r\n description?: string\r\n /** 키워드 */\r\n keywords?: string[]\r\n}\r\n\r\n// ─── 파싱 옵션 ──────────────────────────────────────\r\n\r\n/** 파싱 옵션 — parse() 함수에 전달 */\r\nexport interface ParseOptions {\r\n /**\r\n * 파싱할 페이지/섹션 범위 (1-based).\r\n * - 배열: [1, 2, 3]\r\n * - 문자열: \"1-3\", \"1,3,5-7\"\r\n *\r\n * PDF: 정확한 페이지 단위. HWP/HWPX: 섹션 단위 근사치.\r\n */\r\n pages?: number[] | string\r\n /** 이미지 기반 PDF OCR (선택).\r\n * - `true`: 내장 엔진(PP-OCRv5 korean, ~18MB 자동 다운로드)으로 OCR 필요 판정\r\n * 페이지만 인식 (스캔 페이지·글꼴 매핑 깨진 페이지). 정상 페이지는 파싱 결과 유지.\r\n * - `\"force\"`: 전 페이지를 내장 엔진으로 강제 OCR.\r\n * - 함수: 사용자 제공 OcrProvider (Claude Vision·Tesseract 등) — 판정은 `true`와 동일. */\r\n ocr?: boolean | \"force\" | OcrProvider\r\n /** 진행률 콜백 — current: 현재 페이지/섹션, total: 전체 수 */\r\n onProgress?: (current: number, total: number) => void\r\n /** PDF 머리글/바닥글 자동 제거 */\r\n removeHeaderFooter?: boolean\r\n /** 표 오른쪽 끝의 빈 열(서식 문서의 입력란) 보존 (#47).\r\n * 기본 false: 마크다운 가독성을 위해 후행 빈 열을 트림.\r\n * 양식 인식 경로(parse_form·fill)는 내부적으로 항상 켠다. */\r\n keepTrailingEmptyCols?: boolean\r\n /** 빈 문단(텍스트 없는 hp:p) 보존 (#57). 기본 false: 종전대로 빈 문단 제거.\r\n * 켜면 본문은 `text: \"\"` paragraph 블록으로, 표 셀은 빈 줄로 순서대로 보존해\r\n * \"원문 문단 수 = 줄 수\" 대응을 유지한다 (행 줄맞춤 서식 문서용).\r\n * 개체(표·이미지·글상자)만 있는 문단은 개체 출력이 따로 있어 대상이 아니다.\r\n * 현재 HWPX 경로 적용. */\r\n keepEmptyParagraphs?: boolean\r\n /** 원본 파일 경로 (DRM COM fallback에 필요, 내부 전용) */\r\n filePath?: string\r\n /**\r\n * PDF 수식 OCR 활성화 (기본 false).\r\n *\r\n * 활성화 시 각 PDF 페이지를 이미지로 렌더링 → YOLOv8 기반 수식 영역 검출 →\r\n * TrOCR 기반 LaTeX 인식. 감지된 수식은 `$...$` (inline) / `$$...$$` (display) 로\r\n * 블록 텍스트에 삽입된다.\r\n *\r\n * 필수 optional 의존성: `onnxruntime-node`, `@huggingface/transformers`,\r\n * `@hyzyla/pdfium`, `sharp`. 미설치 시 parse 에 실패하지 않고 **경고만** 남기고\r\n * 수식 인식은 skip 한다 (일반 텍스트 추출은 정상 동작).\r\n *\r\n * 모델(~155MB) 은 첫 사용 시 HuggingFace 에서 자동 다운로드 되어\r\n * `~/.cache/kordoc/models/pix2text/` 에 SHA-256 검증과 함께 저장된다.\r\n */\r\n formulaOcr?: boolean\r\n /**\r\n * 레이아웃 표 페이지 반복 헤더(러닝 헤더) 정리 휴리스틱 활성화 (기본 false).\r\n *\r\n * 구형 HWP5 문서가 페이지마다 재삽입한 짧은 번호매김 러닝 헤더\r\n * (\"2. 과제 구축 내용\")를 최초 1회만 남기고 이후 중복을 제거한다.\r\n * 다만 페이지 위치 정보가 없는 HWP5 특성상 이 휴리스틱은 정당하게 반복되는\r\n * 번호매김 문단(예: 붙임/별지별 재번호 \"1. 목적\")도 삭제할 수 있어 opt-in 으로\r\n * 둔다. 활성 시 제거가 발생하면 HIDDEN_TEXT_FILTERED 경고를 남긴다.\r\n */\r\n dedupeRunningHeaders?: boolean\r\n /**\r\n * 추출된 이미지를 마크다운에 base64 data URI 로 인라인 (기본 false).\r\n *\r\n * 활성화 시 `![image](image_001.bmp)` 참조를 `![image](data:image/png;base64,...)` 로\r\n * 치환한다. 임베드된 BMP 는 PNG 로 무손실 압축 후 인라인하여 용량을 크게 줄인다.\r\n * 별도 이미지 파일 없이 자체 완결형 마크다운이 되어 MCP/AI 에이전트 소비에 적합하다.\r\n * (현재 HWP5 경로 지원)\r\n */\r\n inlineImages?: boolean\r\n}\r\n\r\n// ─── 파싱 경고 ──────────────────────────────────────\r\n\r\n/** 파싱 중 스킵/실패한 요소 보고 */\r\nexport interface ParseWarning {\r\n /** 관련 페이지 번호 (알 수 있는 경우) */\r\n page?: number\r\n /** 경고 메시지 */\r\n message: string\r\n /** 구조화된 경고 코드 */\r\n code: WarningCode\r\n}\r\n\r\nexport type WarningCode =\r\n | \"SKIPPED_IMAGE\"\r\n | \"SKIPPED_OLE\"\r\n | \"TRUNCATED_TABLE\"\r\n | \"OCR_FALLBACK\"\r\n | \"UNSUPPORTED_ELEMENT\"\r\n | \"BROKEN_ZIP_RECOVERY\"\r\n | \"HIDDEN_TEXT_FILTERED\"\r\n | \"MALFORMED_XML\"\r\n | \"PARTIAL_PARSE\"\r\n | \"LENIENT_CFB_RECOVERY\"\r\n | \"NEEDS_OCR\"\r\n | \"OCR_FAILED\"\r\n | \"OCR_APPLIED\"\r\n | \"COM_EMPTY\"\r\n | \"DRM_COM_FALLBACK\"\r\n\r\n/** 문서 구조 (헤딩 트리) */\r\nexport interface OutlineItem {\r\n level: number\r\n text: string\r\n pageNumber?: number\r\n}\r\n\r\n// ─── 에러 코드 ──────────────────────────────────────\r\n\r\n/** 구조화된 에러 코드 — 프로그래밍적 에러 핸들링용 */\r\nexport type ErrorCode =\r\n | \"EMPTY_INPUT\"\r\n | \"UNSUPPORTED_FORMAT\"\r\n | \"ENCRYPTED\"\r\n | \"DRM_PROTECTED\"\r\n | \"CORRUPTED\"\r\n | \"DECOMPRESSION_BOMB\"\r\n | \"ZIP_BOMB\"\r\n | \"IMAGE_BASED_PDF\"\r\n | \"NO_SECTIONS\"\r\n | \"PARSE_ERROR\"\r\n | \"MISSING_DEPENDENCY\"\r\n\r\n// ─── 파싱 결과 (discriminated union) ────────────────\r\n\r\nexport type FileType = \"hwpx\" | \"hwp\" | \"hwp3\" | \"hwpml\" | \"pdf\" | \"xlsx\" | \"xls\" | \"docx\" | \"image\" | \"unknown\"\r\n\r\ninterface ParseResultBase {\r\n fileType: FileType\r\n /** 페이지/섹션 수 — PDF: 실제 페이지 수, HWP/HWPX: 섹션 수, XLSX: 시트 수 */\r\n pageCount?: number\r\n /** 이미지 기반 PDF 여부 (텍스트 추출 불가) */\r\n isImageBased?: boolean\r\n}\r\n\r\nexport interface ParseSuccess extends ParseResultBase {\r\n success: true\r\n /** 추출된 마크다운 텍스트 */\r\n markdown: string\r\n /**\r\n * 중간 표현 블록 (구조화된 데이터 접근용).\r\n * 블록은 문서(원문) 읽기 순서를 따른다 — 한 문단 안에 글자취급(treatAsChar)\r\n * 표와 텍스트가 섞여 있어도 배치 순서대로 방출된다 (v4.2.3, #50).\r\n */\r\n blocks: IRBlock[]\r\n /** 문서 메타데이터 */\r\n metadata?: DocumentMetadata\r\n /** 문서 구조 (헤딩 트리) — v2.0 */\r\n outline?: OutlineItem[]\r\n /** 파싱 중 발생한 경고 — v2.0 */\r\n warnings?: ParseWarning[]\r\n /** 추출된 이미지 목록 — 마크다운에서 파일명으로 참조됨 */\r\n images?: ExtractedImage[]\r\n /** 페이지별 텍스트 품질 신호 — PDF에서만 제공 */\r\n pageQuality?: PageQuality[]\r\n /** 문서 단위 품질 요약 — PDF에서만 제공 */\r\n qualitySummary?: DocumentQualitySummary\r\n}\r\n\r\n/** 페이지별 텍스트 품질 신호 (PDF 전용). 자세한 설명은 src/pdf/quality.ts */\r\nexport interface PageQuality {\r\n page: number\r\n textChars: number\r\n hangulRatio: number\r\n controlCharRatio: number\r\n replacementCharRatio: number\r\n puaRatio: number\r\n needsOcr: boolean\r\n ocrReason?: \"low_text\" | \"high_pua\" | \"high_control\" | \"high_replacement\" | \"garbled_hangul\"\r\n}\r\n\r\n/** 문서 단위 품질 요약 (PDF 전용). */\r\nexport interface DocumentQualitySummary {\r\n totalPages: number\r\n totalTextChars: number\r\n avgHangulRatio: number\r\n avgControlCharRatio: number\r\n avgReplacementCharRatio: number\r\n avgPuaRatio: number\r\n lowTextPageCount: number\r\n highPuaPageCount: number\r\n needsOcr: boolean\r\n ocrCandidatePages: number[]\r\n}\r\n\r\n/** 추출된 이미지 — ParseSuccess.images에 포함 */\r\nexport interface ExtractedImage {\r\n /** 마크다운에서 참조되는 파일명 (예: image_001.png) */\r\n filename: string\r\n /** 이미지 바이너리 */\r\n data: Uint8Array\r\n /** MIME 타입 */\r\n mimeType: string\r\n}\r\n\r\nexport interface ParseFailure extends ParseResultBase {\r\n success: false\r\n /** 오류 메시지 */\r\n error: string\r\n /** 구조화된 에러 코드 */\r\n code?: ErrorCode\r\n}\r\n\r\nexport type ParseResult = ParseSuccess | ParseFailure\r\n\r\n// ─── 문서 비교 (Diff) ───────────────────────────────\r\n\r\nexport type DiffChangeType = \"added\" | \"removed\" | \"modified\" | \"unchanged\"\r\n\r\nexport interface BlockDiff {\r\n type: DiffChangeType\r\n /** 원본 블록 (added이면 undefined) */\r\n before?: IRBlock\r\n /** 변경 후 블록 (removed이면 undefined) */\r\n after?: IRBlock\r\n /** modified 테이블의 셀 단위 diff */\r\n cellDiffs?: CellDiff[][]\r\n /** 유사도 (0-1) */\r\n similarity?: number\r\n}\r\n\r\nexport interface CellDiff {\r\n type: DiffChangeType\r\n before?: string\r\n after?: string\r\n}\r\n\r\nexport interface DiffResult {\r\n stats: { added: number; removed: number; modified: number; unchanged: number }\r\n diffs: BlockDiff[]\r\n}\r\n\r\n// ─── 라운드트립 패치 (v3.0) ─────────────────────────\r\n\r\n/** 패치 중 매핑 실패/미지원으로 건너뛴 항목 — silent 실패 금지 */\r\nexport interface PatchSkip {\r\n /** 건너뛴 사유 */\r\n reason: string\r\n /** 원본 쪽 내용 요약 (최대 80자) */\r\n before?: string\r\n /** 편집 쪽 내용 요약 (최대 80자) */\r\n after?: string\r\n /**\r\n * 부분 적용 표시 — 변경이 적용은 됐지만(applied 계상) 편집 원형 그대로는\r\n * 아님 (예: 셀 내 줄 추가를 마지막 문단에 병합). 완전 미적용 skip과 구분.\r\n */\r\n partial?: boolean\r\n}\r\n\r\n/** patchHwpx 옵션 */\r\nexport interface PatchOptions {\r\n /** 패치 후 재파싱 자동 검증 (기본 true) */\r\n verify?: boolean\r\n}\r\n\r\n/** patchHwpx / patchBlocks 결과 */\r\nexport interface PatchResult {\r\n success: boolean\r\n /** 패치된 HWPX (success=true) */\r\n data?: Uint8Array\r\n /** 적용된 변경 수 */\r\n applied: number\r\n /** 매핑 실패 항목 (이유 포함) */\r\n skipped: PatchSkip[]\r\n /**\r\n * 무손실 검증 (patchHwpx/patchHwp 전용): 패치본 재파싱 vs 편집 마크다운의\r\n * 잔차 diff — modified/added/removed가 0이어야 의도가 전부 반영된 것.\r\n * session.patchBlocks는 이 필드를 채우지 않는다 (changes 참조).\r\n */\r\n verification?: DiffResult\r\n /**\r\n * 변경 가시화 (session.patchBlocks 전용): 패치 전 → 후 문서 diff —\r\n * 적용된 편집 수만큼 modified가 나오는 것이 정상. verification과 의미가\r\n * 정반대이므로 혼용 금지.\r\n */\r\n changes?: DiffResult\r\n /** 실패 사유 (success=false) */\r\n error?: string\r\n}\r\n\r\n// ─── 양식 인식 ──────────────────────────────────────\r\n\r\nexport interface FormField {\r\n label: string\r\n value: string\r\n /** 0-based 소스 행 */\r\n row: number\r\n /** 0-based 소스 열 */\r\n col: number\r\n /** 매칭된 입력 키(정규화) — 모호 라벨 거부 가드(fillWithUniqueGuard)의 집계 기준 */\r\n key?: string\r\n}\r\n\r\nexport interface FormResult {\r\n fields: FormField[]\r\n /** 양식 확신도 (0-1) */\r\n confidence: number\r\n}\r\n\r\n// ─── OCR 프로바이더 ─────────────────────────────────\r\n\r\n/** 사용자 제공 OCR 함수 — 페이지 이미지를 받아 텍스트 반환.\r\n * PDF 경로는 항상 \"image/png\", 이미지 직접 입력 경로는 원본 mime 그대로 전달. */\r\nexport type OcrProvider = (\r\n pageImage: Uint8Array,\r\n pageNumber: number,\r\n mimeType: \"image/png\" | \"image/jpeg\" | \"image/webp\"\r\n) => Promise<string>\r\n\r\n// ─── Watch 모드 ─────────────────────────────────────\r\n\r\nexport interface WatchOptions {\r\n dir: string\r\n outDir?: string\r\n webhook?: string\r\n format?: \"markdown\" | \"json\"\r\n pages?: string\r\n silent?: boolean\r\n}\r\n\r\n// ─── 헤딩 감지 공통 임계값 ──────────────────────────\r\n\r\n/** 폰트 크기 비율 → heading level (전 파서 공통) */\r\nexport const HEADING_RATIO_H1 = 1.5\r\nexport const HEADING_RATIO_H2 = 1.3\r\nexport const HEADING_RATIO_H3 = 1.15\r\n\r\n// ─── 내부 파서 반환 타입 ─────────────────────────────\r\n\r\n/** 내부 파서가 index.ts에 반환하는 공통 타입 (HWP5/HWPX/PDF/XLSX/DOCX) */\r\nexport interface InternalParseResult {\r\n markdown: string\r\n blocks: IRBlock[]\r\n metadata?: DocumentMetadata\r\n outline?: OutlineItem[]\r\n warnings?: ParseWarning[]\r\n images?: ExtractedImage[]\r\n /** PDF 전용: 이미지 기반 PDF 여부 */\r\n isImageBased?: boolean\r\n /** PDF 전용: 페이지별 품질 신호 */\r\n pageQuality?: PageQuality[]\r\n /** PDF 전용: 문서 단위 품질 요약 */\r\n qualitySummary?: DocumentQualitySummary\r\n}\r\n"],"mappings":";;;AA2dO,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;","names":[]}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ blocksToChunks
4
+ } from "./chunk-SPKBIFSK.js";
5
+ import "./chunk-GSMPTZC7.js";
6
+ import "./chunk-DRQUAYNT.js";
7
+ export {
8
+ blocksToChunks
9
+ };
10
+ //# sourceMappingURL=chunks-Q7T4SQGW.js.map