kordoc 4.16.0 → 4.16.2

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 (145) hide show
  1. package/README.md +344 -1135
  2. package/dist/{-VQNLLLPE.js → -4G6MA4X6.js} +11 -11
  3. package/dist/chunk-3IB7LBHC.js +57 -0
  4. package/dist/chunk-3IB7LBHC.js.map +1 -0
  5. package/dist/{chunk-6LMKQM7I.js → chunk-4VODJKFL.js} +3 -3
  6. package/dist/{chunk-UDX7TSOC.js → chunk-5KS5PWJR.js} +2 -2
  7. package/dist/chunk-62W7QCEX.cjs +57 -0
  8. package/dist/chunk-62W7QCEX.cjs.map +1 -0
  9. package/dist/{chunk-NNYF2AGC.js → chunk-7KNGSQU7.js} +4 -4
  10. package/dist/chunk-7KNGSQU7.js.map +1 -0
  11. package/dist/{chunk-DYENR3PX.js → chunk-AUWGDP4G.js} +2 -2
  12. package/dist/{chunk-H6KMTYYZ.cjs → chunk-CEIATRDW.cjs} +3 -3
  13. package/dist/{chunk-H6KMTYYZ.cjs.map → chunk-CEIATRDW.cjs.map} +1 -1
  14. package/dist/{chunk-N6TPODYL.js → chunk-DES2LGFZ.js} +2 -2
  15. package/dist/{chunk-PQRP4NSF.js → chunk-F4NRLLY4.js} +2 -2
  16. package/dist/{chunk-B5NEGP65.cjs → chunk-FKUDAIJI.cjs} +692 -33
  17. package/dist/chunk-FKUDAIJI.cjs.map +1 -0
  18. package/dist/{chunk-XWA625AN.js → chunk-FSDJLAOW.js} +3 -1
  19. package/dist/chunk-FSDJLAOW.js.map +1 -0
  20. package/dist/{chunk-PIMB2YNO.js → chunk-GO5IFOUF.js} +2 -2
  21. package/dist/{chunk-476OYHGZ.js → chunk-IQGIT2VX.js} +37 -7
  22. package/dist/chunk-IQGIT2VX.js.map +1 -0
  23. package/dist/chunk-J3IF6KGW.js +59 -0
  24. package/dist/chunk-J3IF6KGW.js.map +1 -0
  25. package/dist/{chunk-C75WE52C.js → chunk-JELRWAZC.js} +2 -2
  26. package/dist/{chunk-46DZF3RK.js → chunk-JFLAM3MM.js} +2 -2
  27. package/dist/{chunk-A3QDRMVD.js → chunk-KIOQ4N37.js} +2 -2
  28. package/dist/{chunk-TGD7UFHD.js → chunk-LEIAEWZ7.js} +4 -4
  29. package/dist/chunk-LEIAEWZ7.js.map +1 -0
  30. package/dist/{chunk-GOQBON77.js → chunk-MLIS253L.js} +2 -2
  31. package/dist/{chunk-JB5X5NNL.js → chunk-OXLPVQYK.js} +680 -21
  32. package/dist/chunk-OXLPVQYK.js.map +1 -0
  33. package/dist/{chunk-RC4C7Q33.js → chunk-P6ZDXTV6.js} +2 -2
  34. package/dist/{chunk-CB5XDCSL.js → chunk-QOCS7W7R.js} +5034 -4569
  35. package/dist/chunk-QOCS7W7R.js.map +1 -0
  36. package/dist/{chunk-3JWMDL3S.js → chunk-S2PL227Q.js} +2 -2
  37. package/dist/{chunk-HIVK5V27.js → chunk-SFEQYKYJ.js} +133 -110
  38. package/dist/chunk-SFEQYKYJ.js.map +1 -0
  39. package/dist/{chunk-NWODPYBD.cjs → chunk-STM3ORY7.cjs} +2 -2
  40. package/dist/{chunk-NWODPYBD.cjs.map → chunk-STM3ORY7.cjs.map} +1 -1
  41. package/dist/{chunk-43FKGDDO.js → chunk-TXUKTOG2.js} +213 -30
  42. package/dist/chunk-TXUKTOG2.js.map +1 -0
  43. package/dist/{chunk-XHOZB2WP.js → chunk-UTP2WCHS.js} +681 -22
  44. package/dist/chunk-UTP2WCHS.js.map +1 -0
  45. package/dist/{chunk-MH6MAL64.cjs → chunk-WDQTFAXY.cjs} +217 -114
  46. package/dist/chunk-WDQTFAXY.cjs.map +1 -0
  47. package/dist/{chunk-DRY3L5KQ.cjs → chunk-XTFN5WKS.cjs} +7 -7
  48. package/dist/chunk-XTFN5WKS.cjs.map +1 -0
  49. package/dist/chunks-NUNIKS2J.js +10 -0
  50. package/dist/cli.js +33 -30
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-FNQYHMIY.js → formula-EPKLPDU7.js} +3 -3
  53. package/dist/{formula-5775PTP2.js → formula-NARUOXRX.js} +4 -4
  54. package/dist/{formula-VEFKYXPB.cjs → formula-WIMV5TUX.cjs} +9 -9
  55. package/dist/{formula-VEFKYXPB.cjs.map → formula-WIMV5TUX.cjs.map} +1 -1
  56. package/dist/{image-ocr-PYZU4TO7.js → image-ocr-3WJ4QH3T.js} +8 -8
  57. package/dist/{image-ocr-YA2MQBRP.js → image-ocr-5BUJQ4TK.js} +6 -6
  58. package/dist/{image-ocr-6JZR734G.cjs → image-ocr-AXWXR6JV.cjs} +15 -15
  59. package/dist/{image-ocr-6JZR734G.cjs.map → image-ocr-AXWXR6JV.cjs.map} +1 -1
  60. package/dist/index.cjs +1900 -1406
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +24 -6
  63. package/dist/index.d.ts +24 -6
  64. package/dist/index.js +1282 -788
  65. package/dist/index.js.map +1 -1
  66. package/dist/mcp.js +34 -28
  67. package/dist/mcp.js.map +1 -1
  68. package/dist/{model-bundle-O6J7ORVC.js → model-bundle-IMBW7D2Z.js} +5 -5
  69. package/dist/{models-YMOKZGCI.js → models-SY75IBOE.js} +5 -5
  70. package/dist/{parser-YAQV7FBO.cjs → parser-2LU4EVU3.cjs} +360 -219
  71. package/dist/parser-2LU4EVU3.cjs.map +1 -0
  72. package/dist/{parser-3B6I2XL7.js → parser-N6476XAC.js} +225 -84
  73. package/dist/parser-N6476XAC.js.map +1 -0
  74. package/dist/{parser-PXATL5DM.js → parser-ZQM3664Z.js} +222 -81
  75. package/dist/parser-ZQM3664Z.js.map +1 -0
  76. package/dist/pdf-ocr-6DEE23IP.js +15 -0
  77. package/dist/pdf-ocr-GHEJVPX2.cjs +16 -0
  78. package/dist/{pdf-ocr-T2JXQATA.cjs.map → pdf-ocr-GHEJVPX2.cjs.map} +1 -1
  79. package/dist/pdf-ocr-MQTWODVU.js +18 -0
  80. package/dist/{profile-io-ZTV2MKRT.js → profile-io-GJPKCYPT.js} +3 -3
  81. package/dist/{rasterize-MMUZ537K.js → rasterize-UOALJQDS.js} +3 -3
  82. package/dist/{redact-doc-S42XGV5D.js → redact-doc-22SYW3KH.js} +11 -11
  83. package/dist/{render-WCWPZKJ7.js → render-MJ4H3R23.js} +7 -7
  84. package/dist/seal-XWMRUTI4.js +10 -0
  85. package/dist/{setup-D35LKZCH.js → setup-2URYVUEO.js} +3 -3
  86. package/dist/visual-USPS4QRC.js +20 -0
  87. package/dist/{watch-AX64CIMN.js → watch-ALXZGTOB.js} +12 -12
  88. package/package.json +1 -1
  89. package/dist/chunk-43FKGDDO.js.map +0 -1
  90. package/dist/chunk-476OYHGZ.js.map +0 -1
  91. package/dist/chunk-B5NEGP65.cjs.map +0 -1
  92. package/dist/chunk-CB5XDCSL.js.map +0 -1
  93. package/dist/chunk-DRY3L5KQ.cjs.map +0 -1
  94. package/dist/chunk-HIVK5V27.js.map +0 -1
  95. package/dist/chunk-JB5X5NNL.js.map +0 -1
  96. package/dist/chunk-MH6MAL64.cjs.map +0 -1
  97. package/dist/chunk-NNYF2AGC.js.map +0 -1
  98. package/dist/chunk-QJERJZJ6.js +0 -11
  99. package/dist/chunk-QJERJZJ6.js.map +0 -1
  100. package/dist/chunk-TGD7UFHD.js.map +0 -1
  101. package/dist/chunk-UQ6TZDW6.js +0 -13
  102. package/dist/chunk-UQ6TZDW6.js.map +0 -1
  103. package/dist/chunk-XHOZB2WP.js.map +0 -1
  104. package/dist/chunk-XWA625AN.js.map +0 -1
  105. package/dist/chunk-ZY5K3LPM.cjs +0 -11
  106. package/dist/chunk-ZY5K3LPM.cjs.map +0 -1
  107. package/dist/chunks-JD25E5PM.js +0 -10
  108. package/dist/parser-3B6I2XL7.js.map +0 -1
  109. package/dist/parser-PXATL5DM.js.map +0 -1
  110. package/dist/parser-YAQV7FBO.cjs.map +0 -1
  111. package/dist/pdf-ocr-2TTO5PX2.js +0 -15
  112. package/dist/pdf-ocr-T2JXQATA.cjs +0 -16
  113. package/dist/pdf-ocr-WF2NQA6X.js +0 -18
  114. package/dist/seal-WHK4GOHR.js +0 -10
  115. package/dist/visual-WL4TDE5B.js +0 -20
  116. /package/dist/{-VQNLLLPE.js.map → -4G6MA4X6.js.map} +0 -0
  117. /package/dist/{chunk-6LMKQM7I.js.map → chunk-4VODJKFL.js.map} +0 -0
  118. /package/dist/{chunk-UDX7TSOC.js.map → chunk-5KS5PWJR.js.map} +0 -0
  119. /package/dist/{chunk-DYENR3PX.js.map → chunk-AUWGDP4G.js.map} +0 -0
  120. /package/dist/{chunk-N6TPODYL.js.map → chunk-DES2LGFZ.js.map} +0 -0
  121. /package/dist/{chunk-PQRP4NSF.js.map → chunk-F4NRLLY4.js.map} +0 -0
  122. /package/dist/{chunk-PIMB2YNO.js.map → chunk-GO5IFOUF.js.map} +0 -0
  123. /package/dist/{chunk-C75WE52C.js.map → chunk-JELRWAZC.js.map} +0 -0
  124. /package/dist/{chunk-46DZF3RK.js.map → chunk-JFLAM3MM.js.map} +0 -0
  125. /package/dist/{chunk-A3QDRMVD.js.map → chunk-KIOQ4N37.js.map} +0 -0
  126. /package/dist/{chunk-GOQBON77.js.map → chunk-MLIS253L.js.map} +0 -0
  127. /package/dist/{chunk-RC4C7Q33.js.map → chunk-P6ZDXTV6.js.map} +0 -0
  128. /package/dist/{chunk-3JWMDL3S.js.map → chunk-S2PL227Q.js.map} +0 -0
  129. /package/dist/{chunks-JD25E5PM.js.map → chunks-NUNIKS2J.js.map} +0 -0
  130. /package/dist/{formula-FNQYHMIY.js.map → formula-EPKLPDU7.js.map} +0 -0
  131. /package/dist/{formula-5775PTP2.js.map → formula-NARUOXRX.js.map} +0 -0
  132. /package/dist/{image-ocr-PYZU4TO7.js.map → image-ocr-3WJ4QH3T.js.map} +0 -0
  133. /package/dist/{image-ocr-YA2MQBRP.js.map → image-ocr-5BUJQ4TK.js.map} +0 -0
  134. /package/dist/{model-bundle-O6J7ORVC.js.map → model-bundle-IMBW7D2Z.js.map} +0 -0
  135. /package/dist/{models-YMOKZGCI.js.map → models-SY75IBOE.js.map} +0 -0
  136. /package/dist/{pdf-ocr-2TTO5PX2.js.map → pdf-ocr-6DEE23IP.js.map} +0 -0
  137. /package/dist/{pdf-ocr-WF2NQA6X.js.map → pdf-ocr-MQTWODVU.js.map} +0 -0
  138. /package/dist/{profile-io-ZTV2MKRT.js.map → profile-io-GJPKCYPT.js.map} +0 -0
  139. /package/dist/{rasterize-MMUZ537K.js.map → rasterize-UOALJQDS.js.map} +0 -0
  140. /package/dist/{redact-doc-S42XGV5D.js.map → redact-doc-22SYW3KH.js.map} +0 -0
  141. /package/dist/{render-WCWPZKJ7.js.map → render-MJ4H3R23.js.map} +0 -0
  142. /package/dist/{seal-WHK4GOHR.js.map → seal-XWMRUTI4.js.map} +0 -0
  143. /package/dist/{setup-D35LKZCH.js.map → setup-2URYVUEO.js.map} +0 -0
  144. /package/dist/{visual-WL4TDE5B.js.map → visual-USPS4QRC.js.map} +0 -0
  145. /package/dist/{watch-AX64CIMN.js.map → watch-ALXZGTOB.js.map} +0 -0
package/dist/index.js CHANGED
@@ -16,15 +16,19 @@ import {
16
16
  mapPuaText,
17
17
  markNonLayoutTable,
18
18
  noteSuffix
19
- } from "./chunk-NNYF2AGC.js";
19
+ } from "./chunk-7KNGSQU7.js";
20
20
  import {
21
21
  parsePageRange
22
22
  } from "./chunk-V4BH5SRQ.js";
23
23
  import {
24
24
  HEADING_RATIO_H1,
25
25
  HEADING_RATIO_H2,
26
- HEADING_RATIO_H3
27
- } from "./chunk-QJERJZJ6.js";
26
+ HEADING_RATIO_H3,
27
+ plainScripts,
28
+ stripScriptTags,
29
+ tidyScriptTags,
30
+ wrapScript
31
+ } from "./chunk-3IB7LBHC.js";
28
32
  import {
29
33
  KordocError,
30
34
  OPTIONAL_DEP_INSTALL_HINT,
@@ -41,17 +45,17 @@ import {
41
45
  toArrayBuffer,
42
46
  unescapeHtml,
43
47
  unzipLimitBytes
44
- } from "./chunk-GOQBON77.js";
48
+ } from "./chunk-MLIS253L.js";
45
49
 
46
50
  // src/plain-markdown.ts
47
51
  function toPlainMarkdown(md2) {
48
- return md2.replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, "").replace(/<img\b[^>]*>/gi, "").replace(/(?<!!)\[([^\]\n]*)\]\((?:https?|mailto|ftp):[^)\s]*\)/g, "$1").replace(/<\/?u>/g, "").replace(/(?<!\\)\*\*(?=\S)([^\n]*?\S)(?<!\\)\*\*/g, "$1").replace(/(<br>)+(?=<\/t[dh]>)/g, "").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
52
+ return plainScripts(md2).replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, "").replace(/<img\b[^>]*>/gi, "").replace(/(?<!!)\[([^\]\n]*)\]\((?:https?|mailto|ftp):[^)\s]*\)/g, "$1").replace(/<\/?u>/g, "").replace(/(?<!\\)\*\*(?=\S)([^\n]*?\S)(?<!\\)\*\*/g, "$1").replace(/(<br>)+(?=<\/t[dh]>)/g, "").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
49
53
  }
50
54
 
51
55
  // src/html-tables.ts
52
56
  var TABLE_TAG = /(<\/?(?:table|tr|th|td)\b[^>]*>)/i;
53
57
  function pipeCellToHtml(cell2) {
54
- return cell2.split(/(<br>|<\/?u>|<img\b[^>]*>)/).map((part, i) => i % 2 ? part : part.replace(/\\([\\`*_{}[\]()#+\-.!|~<>$])/g, "$1").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
58
+ return cell2.split(/(<br>|<\/?(?:u|sup|sub)>|<img\b[^>]*>)/).map((part, i) => i % 2 ? part : part.replace(/\\([\\`*_{}[\]()#+\-.!|~<>$])/g, "$1").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")).join("");
55
59
  }
56
60
  function splitPipeRow(line) {
57
61
  const t = line.trim().replace(/^\|/, "").replace(/(?<!\\)\|$/, "");
@@ -770,7 +774,9 @@ function parseCharProperties(doc, map) {
770
774
  else if (localTag === "strikeout") {
771
775
  const shape = k.getAttribute("shape") || "";
772
776
  if (isRealStrikeShape(shape)) prop.strike = true;
773
- } else if (localTag === "underline") {
777
+ } else if (localTag === "supscript") prop.script = "sup";
778
+ else if (localTag === "subscript") prop.script = "sub";
779
+ else if (localTag === "underline") {
774
780
  if (k.getAttribute("type") === "BOTTOM") prop.underline = true;
775
781
  }
776
782
  }
@@ -1848,6 +1854,7 @@ function extractRunSpans(para2, ctx, mode, requireMixed) {
1848
1854
  const span2 = { text };
1849
1855
  if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span2.code = true;
1850
1856
  else {
1857
+ if (cp?.script) span2.text = wrapScript(text, cp.script);
1851
1858
  if (cp?.bold) span2.bold = true;
1852
1859
  if (cp?.italic) span2.italic = true;
1853
1860
  if (cp?.strike) span2.strike = true;
@@ -1867,6 +1874,7 @@ function extractRunSpans(para2, ctx, mode, requireMixed) {
1867
1874
  }
1868
1875
  }
1869
1876
  if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
1877
+ for (const s of merged) s.text = tidyScriptTags(s.text);
1870
1878
  return merged;
1871
1879
  }
1872
1880
  function gongmunDepthFromIndent(para2, left, ctx) {
@@ -2516,6 +2524,16 @@ function extractHyperlinkHref(fieldBegin) {
2516
2524
  function isInDeletedRange(ctx) {
2517
2525
  return (ctx?.shared.track.deleteDepth ?? 0) > 0;
2518
2526
  }
2527
+ function runHasOnlyText(run) {
2528
+ const kids = run.childNodes;
2529
+ for (let i = 0; i < (kids?.length ?? 0); i++) {
2530
+ const k = kids[i];
2531
+ if (k.nodeType !== 1) continue;
2532
+ const t = (k.tagName || k.localName || "").replace(/^[^:]+:/, "");
2533
+ if (t !== "t" && t !== "linesegarray") return false;
2534
+ }
2535
+ return true;
2536
+ }
2519
2537
  function extractParagraphInfo(para2, styleMap, ctx) {
2520
2538
  let text = "";
2521
2539
  let href;
@@ -2772,11 +2790,19 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2772
2790
  }
2773
2791
  break;
2774
2792
  }
2775
- // run 요소에서 charPrIDRef 추출
2776
- case "r": {
2793
+ // run 요소 — hp:r 은 charPrIDRef 를 문단 대표 스타일로. 첨자 글자 모양이면 이 run 이 더한 글을 <sup>·<sub> 로
2794
+ // (개체·각주 등 컨트롤을 품은 run·필드 경계가 걸친 run 은 글 위치가 섞여 감싸지 않는다)
2795
+ case "r":
2796
+ case "run": {
2777
2797
  const runCharPr = child.getAttribute("charPrIDRef");
2778
- if (runCharPr && !charPrId) charPrId = runCharPr;
2798
+ if (tag === "r" && runCharPr && !charPrId) charPrId = runCharPr;
2799
+ const script = runCharPr ? styleMap?.charProperties.get(runCharPr)?.script : void 0;
2800
+ const start = text.length, nLinks = linkRanges.length, nOpen = openFields.length;
2779
2801
  walk(child, depth + 1);
2802
+ if (script && linkRanges.length === nLinks && openFields.length === nOpen && runHasOnlyText(child)) {
2803
+ const added = text.slice(start);
2804
+ if (added && !/[\x1E\x1F\n$]/.test(added)) text = text.slice(0, start) + wrapScript(added, script);
2805
+ }
2780
2806
  break;
2781
2807
  }
2782
2808
  default:
@@ -2800,6 +2826,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2800
2826
  }
2801
2827
  const leaderIdx = text.indexOf("");
2802
2828
  if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
2829
+ text = tidyScriptTags(text);
2803
2830
  const cleanParaText = (raw) => {
2804
2831
  let t = raw.replace(/[ \t]+/g, " ").trim();
2805
2832
  if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(t)) t = "";
@@ -3043,6 +3070,14 @@ function isExtendedOnlyCtrlChar(ch) {
3043
3070
  function appendParaText(state, data, resolveControl) {
3044
3071
  let result = "";
3045
3072
  let i = 0;
3073
+ const wbase = state.wpos ?? 0;
3074
+ const setScript = (want) => {
3075
+ const cur = state.script ?? null;
3076
+ if (cur === want) return;
3077
+ if (cur) result += `</${cur}>`;
3078
+ if (want) result += `<${want}>`;
3079
+ state.script = want;
3080
+ };
3046
3081
  const base = state.text.length;
3047
3082
  const resolveAt = (byteOffset, extended) => {
3048
3083
  const ctrlId = data.readUInt32LE(byteOffset);
@@ -3053,6 +3088,7 @@ function appendParaText(state, data, resolveControl) {
3053
3088
  };
3054
3089
  while (i + 1 < data.length) {
3055
3090
  const ch = data.readUInt16LE(i);
3091
+ if (state.scriptAt) setScript(ch >= 32 ? state.scriptAt(wbase + i / 2) : null);
3056
3092
  i += 2;
3057
3093
  switch (ch) {
3058
3094
  // ── char 타입 (2바이트만, 확장 데이터 없음) ──
@@ -3124,6 +3160,8 @@ function appendParaText(state, data, resolveControl) {
3124
3160
  break;
3125
3161
  }
3126
3162
  }
3163
+ if (state.scriptAt) setScript(null);
3164
+ state.wpos = wbase + Math.floor(data.length / 2);
3127
3165
  state.text += result;
3128
3166
  }
3129
3167
  function extractEquationText(data) {
@@ -5210,6 +5248,7 @@ function parseParagraph(records, start, end, ctx) {
5210
5248
  const paraShapeId = header.data.length >= 10 ? header.data.readUInt16LE(8) : -1;
5211
5249
  const textRecords = [];
5212
5250
  const charShapeIds = [];
5251
+ const charShapeRuns = [];
5213
5252
  const ctrls = [];
5214
5253
  let i = start + 1;
5215
5254
  while (i < end) {
@@ -5228,6 +5267,7 @@ function parseParagraph(records, start, end, ctx) {
5228
5267
  } else if (rec.tagId === TAG_CHAR_SHAPE && rec.level === baseLevel + 1 && rec.data.length >= 8) {
5229
5268
  for (let offset = 0; offset + 7 < rec.data.length; offset += 8) {
5230
5269
  charShapeIds.push(rec.data.readUInt32LE(offset + 4));
5270
+ charShapeRuns.push([rec.data.readUInt32LE(offset), rec.data.readUInt32LE(offset + 4)]);
5231
5271
  }
5232
5272
  }
5233
5273
  i++;
@@ -5241,6 +5281,7 @@ function parseParagraph(records, start, end, ctx) {
5241
5281
  const state = createParaTextState();
5242
5282
  state.leaderMark = true;
5243
5283
  state.dollarMark = true;
5284
+ state.scriptAt = scriptLookup(charShapeRuns, ctx.docInfo);
5244
5285
  const resolver = (idx, id) => {
5245
5286
  let ctrl = idx >= 0 && idx < ctrls.length ? ctrls[idx] : void 0;
5246
5287
  if (!ctrl || ctrl.idRaw !== id && ctrl.id !== id) {
@@ -5282,6 +5323,7 @@ function parseParagraph(records, start, end, ctx) {
5282
5323
  const leaderAt = text.indexOf(LEADER_TAB_MARK);
5283
5324
  if (leaderAt >= 0) text = text.slice(0, leaderAt);
5284
5325
  if (text.includes(LITERAL_DOLLAR_MARK)) text = text.replaceAll(LITERAL_DOLLAR_MARK, "\\$");
5326
+ text = tidyScriptTags(text);
5285
5327
  let headingLevel = 0;
5286
5328
  let headMarker = null;
5287
5329
  const ps = ctx.docInfo && paraShapeId >= 0 && paraShapeId < ctx.docInfo.paraShapes.length ? ctx.docInfo.paraShapes[paraShapeId] : null;
@@ -5647,6 +5689,20 @@ function pictureToImageBlock(data, ctx) {
5647
5689
  const storageId = item && item.storageId > 0 ? item.storageId : binDataId;
5648
5690
  return { type: "image", text: String(storageId), pageNumber: ctx.page };
5649
5691
  }
5692
+ function scriptLookup(runs, docInfo) {
5693
+ if (!docInfo || !runs.length) return void 0;
5694
+ const kindOf = (id) => {
5695
+ const f = docInfo.charShapes[id]?.attrFlags ?? 0;
5696
+ return f & 1 << 15 ? "sup" : f & 1 << 16 ? "sub" : null;
5697
+ };
5698
+ if (!runs.some(([, id]) => kindOf(id))) return void 0;
5699
+ let k = -1;
5700
+ return (pos) => {
5701
+ if (k >= 0 && runs[k][0] > pos) k = -1;
5702
+ while (k + 1 < runs.length && runs[k + 1][0] <= pos) k++;
5703
+ return k >= 0 ? kindOf(runs[k][1]) : null;
5704
+ };
5705
+ }
5650
5706
  function resolveCharStyle(charShapeIds, docInfo) {
5651
5707
  if (charShapeIds.length === 0 || docInfo.charShapes.length === 0) return void 0;
5652
5708
  const freq = /* @__PURE__ */ new Map();
@@ -20734,6 +20790,9 @@ function extractRun(r) {
20734
20790
  if (rPrEls.length > 0) {
20735
20791
  bold = getChildElements(rPrEls[0], "b").length > 0;
20736
20792
  italic = getChildElements(rPrEls[0], "i").length > 0;
20793
+ const va = getChildElements(rPrEls[0], "vertAlign")[0];
20794
+ const v = va ? getAttr(va, "val") : null;
20795
+ if (v === "superscript" || v === "subscript") text = wrapScript(text, v === "superscript" ? "sup" : "sub");
20737
20796
  }
20738
20797
  return { text, bold, italic };
20739
20798
  }
@@ -20831,7 +20890,7 @@ function collectInline(p, footnotes, rels) {
20831
20890
  if (isDisplayMath(om)) parts.push(" $$" + latex + "$$ ");
20832
20891
  else parts.push(" $" + latex + "$ ");
20833
20892
  }
20834
- const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
20893
+ const text = tidyScriptTags(parts.join("")).replace(/[ \t]{2,}/g, " ").trim();
20835
20894
  return { text, bold, italic, footnoteText };
20836
20895
  }
20837
20896
  var COUNTERS = /* @__PURE__ */ new WeakMap();
@@ -21704,7 +21763,7 @@ var ValueCursor = class {
21704
21763
  }
21705
21764
  };
21706
21765
  function normalizeLabel(label) {
21707
- return label.trim().replace(/[::\s()()·]/g, "");
21766
+ return label.trim().replace(/<\/?su[bp]>/g, "").replace(/[::\s()()·]/g, "");
21708
21767
  }
21709
21768
  function findMatchingKey(cellLabel, values) {
21710
21769
  if (values.has(cellLabel)) return cellLabel;
@@ -25357,293 +25416,6 @@ function bodyTitleGeom(bodyHeight, ov = {}) {
25357
25416
  return { barH: BODY_TITLE_GEOM.barH, titleH: Math.round(BODY_TITLE_GEOM.titleH * s) };
25358
25417
  }
25359
25418
 
25360
- // src/hwpx/gongmun.ts
25361
- var OFFICIAL_MARGINS = { top: 20, bottom: 15, left: 20, right: 15 };
25362
- var GAEJOSIK_MARGINS = { top: 15, bottom: 15, left: 20, right: 20 };
25363
- var SEOUL_REPORT_MARGINS = { top: 13, bottom: 13, left: 18, right: 18 };
25364
- var SEOUL_REPORT_HEADER_FOOTER = 3600;
25365
- var GAEJOSIK_HEADER_FOOTER = 4251;
25366
- var MINISTRY_MARGINS = { top: 10, bottom: 10, left: 20, right: 20 };
25367
- var MINISTRY_HEADER_FOOTER = 2835;
25368
- var PRESET_DEFAULTS = {
25369
- // 서울시 실결재 104건 중 12pt 64건(13pt 30, 15pt 5) — 지배값을 기본으로 사용.
25370
- official: { bodyPt: 12, lineSpacing: 160, numbering: "standard" },
25371
- report: { bodyPt: 15, lineSpacing: 160, numbering: "report" },
25372
- // 실측 추진계획안: □ → ㅇ → * 계층.
25373
- plan: { bodyPt: 15, lineSpacing: 160, numbering: "report" },
25374
- notice: { bodyPt: 15, lineSpacing: 160, numbering: "standard" },
25375
- minutes: { bodyPt: 14, lineSpacing: 130, numbering: "standard" },
25376
- gaejosik: { bodyPt: 15, lineSpacing: 160, numbering: "gaejosik" },
25377
- // 보도자료 — 실측(국토부 실물): 본문 바탕 14pt 160%, □→ㅇ→*(각주) 부호
25378
- press: { bodyPt: 14, lineSpacing: 160, numbering: "report" },
25379
- // 중앙부처 업무보고 — 실측(재경부 2차 업무보고): 함초롬바탕 15pt, 줄피치 21.7pt(≈145%), □→ㅇ→-→*(각주)
25380
- ministry: { bodyPt: 15, lineSpacing: 145, numbering: "report" }
25381
- };
25382
- var PRESET_ALIAS = {
25383
- official: "official",
25384
- \uAE30\uC548\uBB38: "official",
25385
- \uC2DC\uD589\uBB38: "official",
25386
- \uACF5\uBB38: "official",
25387
- \uACF5\uBB38\uC11C: "official",
25388
- report: "report",
25389
- \uBCF4\uACE0\uC11C: "report",
25390
- plan: "plan",
25391
- \uACC4\uD68D\uC11C: "plan",
25392
- \uACC4\uD68D: "plan",
25393
- notice: "notice",
25394
- \uD1B5\uC9C0: "notice",
25395
- \uC54C\uB9BC: "notice",
25396
- \uC548\uB0B4: "notice",
25397
- minutes: "minutes",
25398
- \uD68C\uC758\uB85D: "minutes",
25399
- gaejosik: "gaejosik",
25400
- \uAC1C\uC870\uC2DD: "gaejosik",
25401
- \uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
25402
- \uC815\uBD80\uBCF4\uACE0\uC11C: "gaejosik",
25403
- \uC815\uBD80\uD45C\uC900\uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
25404
- press: "press",
25405
- \uBCF4\uB3C4\uC790\uB8CC: "press",
25406
- ministry: "ministry",
25407
- \uC5C5\uBB34\uBCF4\uACE0: "ministry",
25408
- \uBD80\uCC98\uC5C5\uBB34\uBCF4\uACE0: "ministry",
25409
- \uC911\uC559\uBD80\uCC98\uBCF4\uACE0\uC11C: "ministry"
25410
- };
25411
- function normalizeGongmunPreset(preset) {
25412
- if (!preset) return "official";
25413
- return PRESET_ALIAS[preset.trim()] ?? "official";
25414
- }
25415
- function usesReportFonts(preset) {
25416
- return preset === "gaejosik" || preset === "report" || preset === "plan";
25417
- }
25418
- function usesAsteriskThird(preset) {
25419
- return preset === "plan" || preset === "press";
25420
- }
25421
- function needsGaejosikAssets(gongmun) {
25422
- return usesReportFonts(gongmun.preset) || gongmun.cover !== null || gongmun.toc;
25423
- }
25424
- function assertFiniteRange(name, value, min, max) {
25425
- if (!Number.isFinite(value) || value < min || value > max) {
25426
- throw new KordocError(`${name} must be a finite number between ${min} and ${max}`);
25427
- }
25428
- }
25429
- function validateGongmunOptions(opts) {
25430
- if (opts.bodyPt !== void 0) assertFiniteRange("bodyPt", opts.bodyPt, 6, 40);
25431
- if (opts.lineSpacing !== void 0) assertFiniteRange("lineSpacing", opts.lineSpacing, 50, 300);
25432
- if (typeof opts.autoFit === "object" && opts.autoFit.minRatio !== void 0) {
25433
- assertFiniteRange("autoFit.minRatio", opts.autoFit.minRatio, 50, 99);
25434
- }
25435
- if (opts.margins) {
25436
- for (const side of ["top", "bottom", "left", "right"]) {
25437
- const value = opts.margins[side];
25438
- assertFiniteRange(`margins.${side}`, value, 0, 200);
25439
- }
25440
- if (opts.margins.left + opts.margins.right >= 210) {
25441
- throw new KordocError("margins.left + margins.right must be less than 210");
25442
- }
25443
- if (opts.margins.top + opts.margins.bottom >= 297) {
25444
- throw new KordocError("margins.top + margins.bottom must be less than 297");
25445
- }
25446
- }
25447
- if (opts.sizes) {
25448
- for (const [key, value] of Object.entries(opts.sizes)) {
25449
- if (value !== void 0) assertFiniteRange(`sizes.${key}`, value, 6, 60);
25450
- }
25451
- }
25452
- if (opts.approval && opts.approval.length > 6) {
25453
- throw new KordocError("approval must contain at most 6 labels");
25454
- }
25455
- if (opts.levels) {
25456
- for (const [key, st] of Object.entries(opts.levels)) {
25457
- const depth = Number(key);
25458
- if (!Number.isInteger(depth) || depth < 0 || depth >= GONGMUN_LEVEL_COUNT) {
25459
- throw new KordocError(`levels: depth must be an integer between 0 and ${GONGMUN_LEVEL_COUNT - 1} (got "${key}")`);
25460
- }
25461
- if (!st || typeof st !== "object") throw new KordocError(`levels.${key} must be an object {font, pt, bold}`);
25462
- if (st.pt !== void 0) assertFiniteRange(`levels.${key}.pt`, st.pt, 6, 60);
25463
- if (st.font !== void 0 && (typeof st.font !== "string" || !st.font.trim())) {
25464
- throw new KordocError(`levels.${key}.font must be a non-empty string`);
25465
- }
25466
- }
25467
- }
25468
- }
25469
- var GONGMUN_LEVEL_COUNT = 8;
25470
- function resolveLevels(levels, bodyHeight) {
25471
- if (!levels) return null;
25472
- const out = {};
25473
- for (const [key, st] of Object.entries(levels)) {
25474
- if (st.font === void 0 && st.pt === void 0 && st.bold === void 0) continue;
25475
- out[Number(key)] = {
25476
- font: st.font?.trim() || null,
25477
- height: st.pt !== void 0 ? Math.round(st.pt * 100) : bodyHeight,
25478
- bold: !!st.bold
25479
- };
25480
- }
25481
- return Object.keys(out).length > 0 ? out : null;
25482
- }
25483
- function levelMarkerHeight(g3, depth) {
25484
- return g3.levels?.[depth]?.height ?? g3.bodyHeight;
25485
- }
25486
- function incompatibleGongmunWarnings(opts) {
25487
- const warns = [];
25488
- const preset = normalizeGongmunPreset(opts.preset);
25489
- if (opts.docHead && preset !== "official") warns.push(`doc_head(\uB450\uBB38)\uB294 \uAE30\uC548\uBB38(official) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
25490
- if (opts.docFoot && preset !== "official") warns.push(`doc_foot(\uACB0\uBB38)\uB294 \uAE30\uC548\uBB38(official) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
25491
- if (opts.noticeHead && preset !== "notice") warns.push(`notice_head(\uACF5\uACE0\uBC88\uD638\xB7\uBC1C\uC2E0\uBA85\uC758)\uB294 \uD1B5\uC9C0(notice) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
25492
- if (opts.press && preset !== "press") warns.push(`press(\uBA38\uB9AC\uBC15\uC2A4\xB7\uBD80\uC81C\xB7\uB2F4\uB2F9)\uB294 \uBCF4\uB3C4\uC790\uB8CC(press) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
25493
- if (preset === "press" && (opts.cover === true || typeof opts.cover === "object" || opts.toc === true)) {
25494
- warns.push("\uBCF4\uB3C4\uC790\uB8CC\uB294 \uBA38\uB9AC\uBC15\uC2A4 \uC11C\uC2DD\uACFC \uC591\uB9BD \uBD88\uAC00\uB77C \uD45C\uC9C0\xB7\uBAA9\uCC28\uAC00 \uBB34\uC2DC\uB428");
25495
- }
25496
- if (opts.sizes && Object.keys(opts.sizes).length > 0 && !usesReportFonts(preset)) {
25497
- warns.push(`sizes(\uAC1C\uC870\uC2DD \uC694\uC18C \uD06C\uAE30)\uB294 \uAC1C\uC870\uC2DD\xB7\uBCF4\uACE0\uC11C\xB7\uACC4\uD68D\uC11C \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
25498
- }
25499
- if (opts.suppressSingle && (opts.numbering ?? PRESET_DEFAULTS[preset].numbering) !== "standard") {
25500
- warns.push(`suppress_single(\uB2E8\uC77C \uD615\uC81C \uBD80\uD638 \uC0DD\uB7B5)\uC740 \uBC95\uC815 \uBC88\uD638(standard) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B(\uBD88\uB9BF \uCCB4\uACC4)\uC5D0\uC11C \uBB34\uB3D9\uC791`);
25501
- }
25502
- return warns;
25503
- }
25504
- function hexColorOption(name, value) {
25505
- if (value === void 0) return void 0;
25506
- const v = value.trim();
25507
- if (!/^#[0-9a-fA-F]{6}$/.test(v)) throw new KordocError(`${name} must be a #RRGGBB color (got "${value}")`);
25508
- return v.toUpperCase();
25509
- }
25510
- function resolveGongmun(opts) {
25511
- validateGongmunOptions(opts);
25512
- const preset = normalizeGongmunPreset(opts.preset);
25513
- const d = PRESET_DEFAULTS[preset];
25514
- const bodyPt = opts.bodyPt ?? d.bodyPt;
25515
- const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
25516
- const ministry = preset === "ministry";
25517
- const coverOn = opts.cover !== void 0 ? opts.cover !== false : preset === "gaejosik" || ministry;
25518
- const coverOpts = typeof opts.cover === "object" ? opts.cover : {};
25519
- const gaejosik = preset === "gaejosik";
25520
- const reportFamily = gaejosik || preset === "report" || preset === "plan" || preset === "notice" || preset === "press";
25521
- return {
25522
- preset,
25523
- bodyFont: opts.bodyFont ?? "myeongjo",
25524
- bodyHeight: Math.round(bodyPt * 100),
25525
- lineSpacing: opts.lineSpacing ?? d.lineSpacing,
25526
- numbering: opts.numbering ?? d.numbering,
25527
- margins: opts.margins ?? (ministry ? MINISTRY_MARGINS : preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
25528
- centerTitle: opts.centerTitle ?? true,
25529
- autoFitMinRatio,
25530
- // 보도자료는 머리박스가 1페이지 최상단을 차지하는 서식이라 표지·목차와 양립 불가 —
25531
- // 켜면 머리박스가 표지에 얹히고 25pt 제목·부제가 유실된다 (docHead 프리셋 게이팅과 동일 관례)
25532
- cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {}, ...coverOpts.label ? { label: coverOpts.label } : {} } : null,
25533
- bodyPtExplicit: opts.bodyPt !== void 0,
25534
- lineSpacingExplicit: opts.lineSpacing !== void 0,
25535
- bodyFontExplicit: opts.bodyFont !== void 0,
25536
- summary: opts.summary?.trim() || null,
25537
- docInfo: opts.docInfo ?? null,
25538
- toc: preset !== "press" && (opts.toc ?? (gaejosik || ministry)),
25539
- fonts: opts.fonts ?? {},
25540
- sizes: opts.sizes ?? {},
25541
- levels: resolveLevels(opts.levels, Math.round(bodyPt * 100)),
25542
- // 쪽번호 — 보고서 계열 관행(실측: 2_보고서 양식·추진계획·공고문 전부 하단 중앙)
25543
- pageNumbers: opts.pageNumbers ?? (gaejosik || ministry || preset === "report" || preset === "plan"),
25544
- // 머리말·꼬리말 — 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
25545
- // 기안문 0(실결재 41/60건 h0/f0)
25546
- headerFooter: ministry ? MINISTRY_HEADER_FOOTER : preset === "report" || preset === "plan" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
25547
- // "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
25548
- endMark: opts.endMark ?? preset === "official",
25549
- approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
25550
- // 본문 제목박스 — 실측(GT3·GT12): 목차 뒤 본문 시작에 제목 반복. 표지 켜진 개조식 기본
25551
- bodyTitleBox: opts.bodyTitleBox ?? (gaejosik && coverOn),
25552
- // h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
25553
- // v5 라운드 3: 보고서·계획서 기본 band(띠 표) — 서울 plan 7/19·교육청 7/18 실측, 실무자 요청
25554
- h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "band" : preset === "notice" ? "number" : "none"),
25555
- // 띠 제목 색 — 실측 최다 #003366/흰 글자(계획서 띠 표 14개). 교육청형 밝은 띠는 옵션으로
25556
- bandColor: hexColorOption("bandColor", opts.bandColor) ?? "#003366",
25557
- bandTextColor: hexColorOption("bandTextColor", opts.bandTextColor) ?? "#FFFFFF",
25558
- // 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
25559
- // v5: 서울 실결재 ㅇ(이응) 지배 — 보고서·계획서·통지·보도자료 ㅇ, 중앙부처 개조식 양식만 ○
25560
- bullet2: opts.bullet2 ?? (preset === "gaejosik" ? "\u25CB" : "\u3147"),
25561
- // 단일 형제 부호 생략 — 규정이지만 부호 없는 계단이 실무 눈에 어색 (실무자 QA)
25562
- suppressSingle: opts.suppressSingle ?? false,
25563
- docHead: preset === "official" && opts.docHead ? opts.docHead : null,
25564
- docFoot: preset === "official" && opts.docFoot ? opts.docFoot : null,
25565
- reportInfo: opts.reportInfo?.trim() || null,
25566
- noticeHead: preset === "notice" && opts.noticeHead ? opts.noticeHead : null,
25567
- // 보도자료 머리박스는 프리셋 자체가 요구 — 옵션 미지정이어도 빈 객체
25568
- press: preset === "press" ? opts.press ?? {} : null
25569
- };
25570
- }
25571
- var REPORT_BULLETS = ["\u25A1", "\u25CB", "-", "\u318D"];
25572
- var ASTERISK_BULLETS = ["\u25A1", "\u25CB", "*", "\u318D"];
25573
- function standardMarker(depth, n) {
25574
- switch (depth) {
25575
- case 0:
25576
- return `${n + 1}.`;
25577
- case 1:
25578
- return `${hangulOrdinal(n)}.`;
25579
- case 2:
25580
- return `${n + 1})`;
25581
- case 3:
25582
- return `${hangulOrdinal(n)})`;
25583
- case 4:
25584
- return `(${n + 1})`;
25585
- case 5:
25586
- return `(${hangulOrdinal(n)})`;
25587
- case 6:
25588
- return circledNumber(n);
25589
- case 7:
25590
- return circledHangul(n);
25591
- default:
25592
- return circledHangul(n);
25593
- }
25594
- }
25595
- function reportMarker(depth, bullet2 = "\u25CB", asteriskThird = false) {
25596
- const bullets = asteriskThird ? ASTERISK_BULLETS : REPORT_BULLETS;
25597
- const m = bullets[Math.min(depth, bullets.length - 1)];
25598
- return depth === 1 ? bullet2 : m;
25599
- }
25600
- function markerWidth(marker, bodyHeight) {
25601
- let em = SPACE_EM_FIXED;
25602
- for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
25603
- return Math.round(em / 1e3 * bodyHeight);
25604
- }
25605
- function levelIndent(depth, bodyHeight, numbering, sizes = {}, bullet2 = "\u25CB", asteriskThird = false, markerHeight = bodyHeight) {
25606
- if (numbering === "gaejosik") return gaejosikLevelIndent(depth, bodyHeight, sizes, bullet2);
25607
- const marker = numbering === "report" ? reportMarker(depth, bullet2, asteriskThird) : standardMarker(depth, 0);
25608
- return { left: Math.round(depth * bodyHeight), indent: -markerWidth(marker, markerHeight) };
25609
- }
25610
- function computeSuppression(depths) {
25611
- const counts = /* @__PURE__ */ new Map();
25612
- const keys = [];
25613
- const path = [];
25614
- for (const depth of depths) {
25615
- path.length = depth + 1;
25616
- path[depth] = (path[depth] ?? 0) + 1;
25617
- const parentKey = path.slice(0, depth).join(".") + "|" + depth;
25618
- keys.push(parentKey);
25619
- counts.set(parentKey, (counts.get(parentKey) ?? 0) + 1);
25620
- }
25621
- return keys.map((k) => (counts.get(k) ?? 0) <= 1);
25622
- }
25623
- var GongmunNumberer = class {
25624
- constructor(numbering, bullet2 = "\u25CB", asteriskThird = false) {
25625
- this.numbering = numbering;
25626
- this.bullet2 = bullet2;
25627
- this.asteriskThird = asteriskThird;
25628
- }
25629
- numbering;
25630
- bullet2;
25631
- asteriskThird;
25632
- counts = [];
25633
- /** depth 항목 하나에 대한 마커. suppress=true면 빈 문자열(부호 없음) */
25634
- next(depth, suppress) {
25635
- this.counts.length = depth + 1;
25636
- const n = this.counts[depth] ?? 0;
25637
- this.counts[depth] = n + 1;
25638
- if (suppress) return "";
25639
- if (this.numbering === "gaejosik") return gaejosikMarker(depth, this.bullet2);
25640
- return this.numbering === "report" ? reportMarker(depth, this.bullet2, this.asteriskThird) : standardMarker(depth, n);
25641
- }
25642
- reset() {
25643
- this.counts = [];
25644
- }
25645
- };
25646
-
25647
25419
  // src/hwpx/gen-ids.ts
25648
25420
  var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
25649
25421
  var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
@@ -25824,6 +25596,1019 @@ function charVariantBase(richAssets, isGongmun = true) {
25824
25596
  }
25825
25597
  var GONGMUN_BODY_RATIO = 95;
25826
25598
 
25599
+ // src/hwpx/gen-gongmun-extra.ts
25600
+ var extraTableId = EXTRA_TABLE_ID_BASE;
25601
+ function resetExtraTableIds() {
25602
+ extraTableId = EXTRA_TABLE_ID_BASE;
25603
+ }
25604
+ function tbl(rows, w, h, cols) {
25605
+ return `<hp:tbl id="${++extraTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="1" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="283" right="283" top="283" bottom="283"/><hp:inMargin left="141" right="141" top="141" bottom="141"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
25606
+ }
25607
+ function tc(opts) {
25608
+ return `<hp:tc name="${opts.name ?? ""}" header="0" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="${opts.bf}"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="${opts.vAlign ?? "CENTER"}" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${opts.paras}</hp:subList><hp:cellAddr colAddr="${opts.col}" rowAddr="${opts.row}"/><hp:cellSpan colSpan="${opts.colSpan ?? 1}" rowSpan="${opts.rowSpan ?? 1}"/><hp:cellSz width="${opts.w}" height="${opts.h}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
25609
+ }
25610
+ function para(text, paraPrId, charPrId) {
25611
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeTextXml(text)}</hp:t></hp:run></hp:p>`;
25612
+ }
25613
+ var APPROVAL_COL_W = 7085;
25614
+ var APPROVAL_LABEL_H = 1300;
25615
+ var APPROVAL_SIGN_H = 4200;
25616
+ function buildApprovalTable(labels, reg, approvalCharPr) {
25617
+ const n = labels.length;
25618
+ const w = APPROVAL_COL_W * n;
25619
+ const bfAt = (row, col) => reg.get({
25620
+ t: row === 0 ? "thick" : "thin",
25621
+ b: row === 1 ? "thick" : "thin",
25622
+ l: col === 0 ? "thick" : "thin",
25623
+ r: col === n - 1 ? "thick" : "thin"
25624
+ });
25625
+ const labelRow = labels.map(
25626
+ (label, c) => tc({ bf: bfAt(0, c), row: 0, col: c, w: APPROVAL_COL_W, h: APPROVAL_LABEL_H, paras: para(label, GONGMUN_PARA_APPROVAL, approvalCharPr) })
25627
+ ).join("");
25628
+ const signRow = labels.map(
25629
+ (_, c) => tc({ bf: bfAt(1, c), row: 1, col: c, w: APPROVAL_COL_W, h: APPROVAL_SIGN_H, paras: para("", GONGMUN_PARA_APPROVAL, approvalCharPr) })
25630
+ ).join("");
25631
+ const table2 = tbl([labelRow, signRow], w, APPROVAL_LABEL_H + APPROVAL_SIGN_H, n);
25632
+ return `<hp:p paraPrIDRef="${GONGMUN_RIGHT}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`;
25633
+ }
25634
+ function hasEndMark(lastText) {
25635
+ return /(^|\s)끝\s*\.\s*$/.test(lastText.trim());
25636
+ }
25637
+ function buildEndMark() {
25638
+ return para(" \uB05D.", PARA_NORMAL, CHAR_NORMAL);
25639
+ }
25640
+ var TITLE_BOX = { barH: 382, titleH: 2850, color: "#0080C0", gradient: ["#0080C0", "#3CBFFF"] };
25641
+ function buildTitleBox(title, titleCharPr, barCharPr, bodyWidth, reg) {
25642
+ const w = bodyWidth - 280;
25643
+ const outer = { t: "thin", b: "thin", l: "thin", r: "thin" };
25644
+ const barTop = reg.get({ ...outer, fill: TITLE_BOX.color });
25645
+ const barBottom = reg.get({ ...outer, fill: { gradient: TITLE_BOX.gradient } });
25646
+ const mid = reg.get(outer);
25647
+ const barPara = para("", GJ_PARA_BAR, barCharPr);
25648
+ const rows = [
25649
+ tc({ bf: barTop, row: 0, col: 0, w, h: TITLE_BOX.barH, paras: barPara }),
25650
+ tc({ bf: mid, row: 1, col: 0, w, h: TITLE_BOX.titleH, paras: para(title, GONGMUN_TBL_CENTER, titleCharPr), name: "__kordoc_h1" }),
25651
+ tc({ bf: barBottom, row: 2, col: 0, w, h: TITLE_BOX.barH, paras: barPara })
25652
+ ];
25653
+ const table2 = tbl(rows, w, TITLE_BOX.barH * 2 + TITLE_BOX.titleH, 1);
25654
+ return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`;
25655
+ }
25656
+
25657
+ // src/hwpx/fit-line.ts
25658
+ var RATIOS = [100, 97, 95, 92, 90, 87, 85];
25659
+ var SPACINGS = [0, -3, -5];
25660
+ function safety(faceClass, approx) {
25661
+ return faceClass.startsWith("font:") ? 0.98 : approx;
25662
+ }
25663
+ function fitOneLine(text, font, pt2, availHu, minPt = Math.max(pt2 - 3, 10), minRatio = 85) {
25664
+ const faceClass = faceClassForGen(font);
25665
+ const avail = availHu * safety(faceClass, 0.98);
25666
+ const fits = (p, r, s) => measureTextWidth(text, p * 100, r, { spacingPct: s, faceClass }) <= avail;
25667
+ if (fits(pt2, 100, 0)) return { pt: pt2, ratio: 100, spacing: 0, overflow: false };
25668
+ const ratios = RATIOS.filter((r) => r >= minRatio);
25669
+ for (let p = pt2; p >= minPt; p--) {
25670
+ for (const r of ratios) for (const s of SPACINGS) {
25671
+ if (fits(p, r, s)) return { pt: p, ratio: r, spacing: s, overflow: false };
25672
+ }
25673
+ }
25674
+ return { pt: minPt, ratio: ratios[ratios.length - 1], spacing: SPACINGS[SPACINGS.length - 1], overflow: true };
25675
+ }
25676
+ var SQUEEZE = (() => {
25677
+ const out = [];
25678
+ for (let sp = -1; sp >= -12; sp--) out.push([100, sp]);
25679
+ for (const r of [97, 95, 92, 90, 88]) for (let sp = 0; sp >= -12; sp--) out.push([r, sp]);
25680
+ return out;
25681
+ })();
25682
+ var LOOSE_LIMIT = 1;
25683
+ var PULL_TAIL = 0.5;
25684
+ var PULL_MAX = 0.15;
25685
+ var squeezeOf = (r, sp) => 1 - r / 100 * (1 + sp / 100);
25686
+ var ORPHAN_RATIO = 0.22;
25687
+ var ORPHAN_COST = 1.5;
25688
+ var COMPRESS_COST = 10;
25689
+ function worstLooseness(text, starts, firstW, contW, h, ratio, spacing, faceClass) {
25690
+ const spaceW = 0.5 * h * (ratio / 100) * (1 + spacing / 100);
25691
+ let worst = 0;
25692
+ for (let i = 0; i + 1 < starts.length; i++) {
25693
+ const seg = text.slice(starts[i], starts[i + 1]).replace(/ +$/, "");
25694
+ const slack = (i === 0 ? firstW : contW) - measureTextWidth(seg, h, ratio, { faceClass, spacingPct: spacing });
25695
+ const spaces = (seg.match(/ /g) ?? []).length;
25696
+ worst = Math.max(worst, slack / (Math.max(spaces, 1) * spaceW));
25697
+ }
25698
+ return worst;
25699
+ }
25700
+ function fitParagraph(text, font, pt2, firstW, contW, minRatio = 88, orphanRatio = ORPHAN_RATIO) {
25701
+ const faceClass = faceClassForGen(font);
25702
+ const h = pt2 * 100;
25703
+ const k = safety(faceClass, 0.95);
25704
+ const f = firstW * k, c = contW * k;
25705
+ const ladder = [[100, 0], ...SQUEEZE.filter(([r]) => r >= minRatio)];
25706
+ const base = simulateWrap(text, f, c, h, 100, "keep", { faceClass });
25707
+ if (base.lines < 2) return null;
25708
+ if (base.lastLineWidth <= c * PULL_TAIL) {
25709
+ let pull = null;
25710
+ for (const [r, sp] of ladder) {
25711
+ const amt = squeezeOf(r, sp);
25712
+ if (amt > PULL_MAX + 1e-9 || pull && amt >= pull.amt - 1e-9) continue;
25713
+ const w = simulateWrap(text, f, c, h, r, "keep", { faceClass, spacingPct: sp });
25714
+ if (w.lines < base.lines && worstLooseness(text, w.starts, f, c, h, r, sp, faceClass) <= LOOSE_LIMIT) pull = { r, sp, amt };
25715
+ }
25716
+ if (pull) return { ratio: pull.r, spacing: pull.sp };
25717
+ }
25718
+ let baseLines = 0;
25719
+ let best = null;
25720
+ for (const [r, sp] of ladder) {
25721
+ const w = simulateWrap(text, f, c, h, r, "keep", { faceClass, spacingPct: sp });
25722
+ if (!best) {
25723
+ if (w.lines < 2) return null;
25724
+ baseLines = w.lines;
25725
+ }
25726
+ if (w.lines > baseLines) continue;
25727
+ const orphan = w.lines > 1 && w.lastLineWidth <= contW * orphanRatio;
25728
+ const loose = worstLooseness(text, w.starts, f, c, h, r, sp, faceClass);
25729
+ const cost = Math.max(0, loose - LOOSE_LIMIT) + (orphan ? ORPHAN_COST : 0) + squeezeOf(r, sp) * COMPRESS_COST;
25730
+ if (!best || cost < best.cost - 1e-9) best = { r, sp, cost };
25731
+ }
25732
+ return best && (best.r !== 100 || best.sp !== 0) ? { ratio: best.r, spacing: best.sp } : null;
25733
+ }
25734
+ var LIST_BREAK = /* @__PURE__ */ new Set([..."\xB7,\u3001/"]);
25735
+ function fitCharBreaks(text, font, pt2, firstW, contW, minRatio = 88) {
25736
+ const faceClass = faceClassForGen(font);
25737
+ const h = pt2 * 100;
25738
+ const k = safety(faceClass, 0.95);
25739
+ if (!text.split(/ +/).some((w) => measureTextWidth(w, h, 100, { faceClass }) > contW * k)) return null;
25740
+ let best = null;
25741
+ for (const [r, sp] of [[100, 0], ...SQUEEZE.filter(([r2]) => r2 >= minRatio)]) {
25742
+ const amt = squeezeOf(r, sp);
25743
+ if (amt > PULL_MAX + 1e-9 || best && amt >= best.amt - 1e-9) continue;
25744
+ const wrapAt = (s) => simulateWrap(text, firstW * s, contW * s, h, r, "charAll", { faceClass, spacingPct: sp }).starts;
25745
+ const starts = wrapAt(1);
25746
+ if (wrapAt(0.99).join() !== starts.join() || wrapAt(1.01).join() !== starts.join()) continue;
25747
+ if (!starts.slice(1).every((s) => text[s - 1] === " " || LIST_BREAK.has(text[s - 1]))) continue;
25748
+ if (worstLooseness(text, starts, firstW, contW, h, r, sp, faceClass) > LOOSE_LIMIT) continue;
25749
+ best = { r, sp, amt };
25750
+ }
25751
+ return best ? { ratio: best.r, spacing: best.sp } : null;
25752
+ }
25753
+
25754
+ // src/hwpx/gen-frame-seoul.ts
25755
+ var frameTableId = EXTRA_TABLE_ID_BASE + 500;
25756
+ function resetFrameTableIds() {
25757
+ frameTableId = EXTRA_TABLE_ID_BASE + 500;
25758
+ }
25759
+ var BF_NONE = 1;
25760
+ function ftbl(rows, w, h, cols, opts = {}) {
25761
+ return `<hp:tbl id="${++frameTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="${opts.bf ?? BF_NONE}" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="${opts.bottomGap ?? 0}"/><hp:inMargin left="140" right="140" top="140" bottom="140"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
25762
+ }
25763
+ function host(tableXml2, paraPrId, charPrId, extra = "") {
25764
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"${extra}><hp:run charPrIDRef="${charPrId}">${tableXml2}</hp:run></hp:p>`;
25765
+ }
25766
+ function scale(cols, w) {
25767
+ const sum = cols.reduce((a, b) => a + b, 0);
25768
+ const out = cols.map((c) => Math.round(c / sum * w));
25769
+ out[out.length - 1] += w - out.reduce((a, b) => a + b, 0);
25770
+ return out;
25771
+ }
25772
+ function spacedOrgName(org) {
25773
+ const t = org.trim();
25774
+ if (/\s/.test(t) || [...t].length > 6) return t;
25775
+ return [...t].join(" ");
25776
+ }
25777
+ function buildDocHeadTable(h, ctx) {
25778
+ const { reg, bf, frame } = ctx;
25779
+ const w = ctx.W - 366;
25780
+ const [cLabel, cValue, cPad] = scale([6949, 35068, 7223], w);
25781
+ const center = reg.para({ align: "CENTER", lineSp: 160 });
25782
+ const left = reg.para({ align: "LEFT", lineSp: 160 });
25783
+ const c10 = reg.char({ font: frame.font, pt: 10 });
25784
+ const c10b = reg.char({ font: frame.font, pt: 10, bold: true });
25785
+ const c20b = reg.char({ font: frame.font, pt: 20, bold: true });
25786
+ const c14 = reg.char({ font: frame.font, pt: 14 });
25787
+ const bottomLine = bf.get({ t: "none", b: "thin", l: "none", r: "none" });
25788
+ const rows = [];
25789
+ let r = 0;
25790
+ if (h.slogan) {
25791
+ rows.push(tc({ bf: BF_NONE, row: r++, col: 0, w, h: 2416, colSpan: 3, paras: para(h.slogan, center, c10) }));
25792
+ }
25793
+ rows.push(
25794
+ tc({ bf: BF_NONE, row: r, col: 0, w: cLabel, h: 5608, paras: para("", center, c10) }) + tc({ bf: BF_NONE, row: r, col: 1, w: cValue, h: 5608, paras: para(spacedOrgName(h.org ?? ""), center, c20b) }) + tc({ bf: BF_NONE, row: r, col: 2, w: cPad, h: 5608, paras: para("", center, c10) })
25795
+ );
25796
+ r++;
25797
+ rows.push(tc({ bf: BF_NONE, row: r++, col: 0, w, h: 1563, colSpan: 3, paras: para("", left, c10b) }));
25798
+ const labeled = (label, value, height2, bfId) => tc({ bf: bfId, row: r, col: 0, w: cLabel, h: height2, paras: para(label, left, c14) }) + tc({ bf: bfId, row: r, col: 1, w: cValue + cPad, h: height2, colSpan: 2, paras: para(value, left, c14) });
25799
+ rows.push(labeled(" \uC218\uC2E0 ", h.to ?? "", 2097, BF_NONE));
25800
+ r++;
25801
+ rows.push(labeled("(\uACBD\uC720)", h.via ?? "", 2380, BF_NONE));
25802
+ r++;
25803
+ rows.push(labeled(" \uC81C\uBAA9 ", h.title ?? "", 2248, bottomLine));
25804
+ r++;
25805
+ const height = rows.length === 6 ? 16312 : 13896;
25806
+ return host(ftbl(rows, w, height, 3, { bottomGap: 852 }), reg.para({ align: "LEFT", lineSp: 100 }), c10);
25807
+ }
25808
+ function splitTitleName(s) {
25809
+ const t = s.trim();
25810
+ const i = t.lastIndexOf(" ");
25811
+ return i > 0 ? { title: t.slice(0, i).trim(), name: t.slice(i + 1).trim() } : { title: "", name: t };
25812
+ }
25813
+ function buildDocFootTable(f, ctx, internal) {
25814
+ const { reg, bf, frame } = ctx;
25815
+ const w = ctx.W - 86;
25816
+ const GRID = 48;
25817
+ const unit = Math.floor(w / GRID);
25818
+ const c10 = reg.char({ font: frame.font, pt: 10 });
25819
+ const c11b = reg.char({ font: frame.font, pt: 11, bold: true });
25820
+ const c9b = reg.char({ font: frame.font, pt: 9, bold: true });
25821
+ const c18b = reg.char({ font: frame.font, pt: 18, bold: true });
25822
+ const c3 = reg.char({ font: frame.font, pt: 3 });
25823
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
25824
+ const left = reg.para({ align: "LEFT", lineSp: 100 });
25825
+ const gray = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: "#CCCCCC" });
25826
+ const spans = (widths) => {
25827
+ const sum = widths.reduce((a, b) => a + b, 0);
25828
+ const out = widths.map((x) => Math.max(1, Math.round(x / sum * GRID)));
25829
+ let diff = GRID - out.reduce((a, b) => a + b, 0);
25830
+ for (let i = out.length - 1; diff !== 0 && i >= 0; i = (i - 1 + out.length) % out.length) {
25831
+ if (diff > 0) {
25832
+ out[i]++;
25833
+ diff--;
25834
+ } else if (out[i] > 1) {
25835
+ out[i]--;
25836
+ diff++;
25837
+ }
25838
+ }
25839
+ return out;
25840
+ };
25841
+ const rows = [];
25842
+ let r = 0;
25843
+ const row = (h, specs) => {
25844
+ let col = 0;
25845
+ let xml = "";
25846
+ specs.forEach((sp, i) => {
25847
+ const last = i === specs.length - 1;
25848
+ const span2 = last ? GRID - col : sp.span;
25849
+ const cw = last ? w - col * unit : span2 * unit;
25850
+ xml += tc({ bf: sp.bf ?? BF_NONE, row: r, col, w: cw, h, colSpan: span2, rowSpan: sp.rowSpan, vAlign: sp.vAlign, paras: para(sp.text, sp.pp ?? left, sp.cp ?? c10) });
25851
+ col += span2;
25852
+ });
25853
+ rows.push(xml);
25854
+ r++;
25855
+ };
25856
+ {
25857
+ const [a, b, c] = spans([7981, 32099, 9440]);
25858
+ row(4042, [{ span: a, text: "" }, { span: b, text: internal ? "" : f.sender ?? "", cp: c18b, pp: center }, { span: c, text: "" }]);
25859
+ }
25860
+ if (f.recipients) {
25861
+ const [a] = spans([4843, 44677]);
25862
+ row(2880, [{ span: a, text: "\uC218\uC2E0\uC790", vAlign: "TOP" }, { span: GRID - a, text: f.recipients, vAlign: "TOP" }]);
25863
+ } else {
25864
+ row(1280, [{ span: GRID, text: "" }]);
25865
+ }
25866
+ row(1146, [{ span: GRID, text: "", cp: c3, pp: center, bf: gray }]);
25867
+ {
25868
+ const n = Math.max(f.approvers.length, 1);
25869
+ const unitW = [6909, 4625, 983];
25870
+ const titleW = (t) => Math.max(unitW[0], Math.round(measureTextWidth(t, 1e3, 100, { faceClass: "fixedPitch" })) + 280 + unit + 300);
25871
+ const widths = [];
25872
+ for (let i = 0; i < n; i++) {
25873
+ widths.push(titleW(f.approvers[i]?.title ?? ""), unitW[1]);
25874
+ if (i < n - 1) widths.push(unitW[2]);
25875
+ }
25876
+ const used = widths.reduce((a, b) => a + b, 0);
25877
+ if (used < w) widths.push(w - used);
25878
+ const sp = spans(widths);
25879
+ const top = [], bottom = [];
25880
+ let k = 0;
25881
+ for (let i = 0; i < n; i++) {
25882
+ const ap = f.approvers[i] ?? { title: "", name: "" };
25883
+ top.push({ span: sp[k++], text: ap.title, pp: center, rowSpan: 2 });
25884
+ const signSpan = sp[k++];
25885
+ top.push({ span: signSpan, text: ap.date ?? "", cp: c9b, pp: center });
25886
+ bottom.push({ span: signSpan, text: ap.name, cp: c11b, pp: center });
25887
+ if (i < n - 1) top.push({ span: sp[k++], text: "", rowSpan: 2 });
25888
+ }
25889
+ if (k < sp.length) top.push({ span: sp[k], text: "", rowSpan: 2 });
25890
+ let col = 0, xmlTop = "", xmlBottom = "";
25891
+ let bi = 0;
25892
+ top.forEach((s, i) => {
25893
+ const last = i === top.length - 1;
25894
+ const span2 = last ? GRID - col : s.span;
25895
+ const cw = last ? w - col * unit : span2 * unit;
25896
+ xmlTop += tc({ bf: BF_NONE, row: r, col, w: cw, h: s.rowSpan ? 2198 : 1100, colSpan: span2, rowSpan: s.rowSpan, paras: para(s.text, s.pp ?? left, s.cp ?? c10) });
25897
+ if (!s.rowSpan) {
25898
+ const b = bottom[bi++];
25899
+ xmlBottom += tc({ bf: BF_NONE, row: r + 1, col, w: cw, h: 1098, colSpan: span2, paras: para(b.text, b.pp ?? center, b.cp ?? c11b) });
25900
+ }
25901
+ col += span2;
25902
+ });
25903
+ rows.push(xmlTop, xmlBottom);
25904
+ r += 2;
25905
+ }
25906
+ {
25907
+ const [a] = spans([4455, 45065]);
25908
+ const coop = f.cooperators.map((c) => `${c.title} ${c.name}`.trim()).join(" ");
25909
+ row(2700, [{ span: a, text: "\uD611\uC870\uC790" }, { span: GRID - a, text: coop }]);
25910
+ }
25911
+ row(1400, [{ span: GRID, text: "" }]);
25912
+ {
25913
+ const [a, b, c] = spans([3340, 21532, 3301, 21347]);
25914
+ row(2280, [{ span: a, text: "\uC2DC\uD589" }, { span: b, text: f.docNum ?? "" }, { span: c, text: "\uC811\uC218" }, { span: 0, text: f.receive ?? "" }]);
25915
+ }
25916
+ {
25917
+ const [a, b, c, d] = spans([1597, 3902, 21934, 761, 21326]);
25918
+ row(1280, [{ span: a, text: "\uC6B0" }, { span: b, text: f.zip ?? "" }, { span: c, text: f.address ?? "" }, { span: d, text: "/" }, { span: 0, text: f.site ?? "" }]);
25919
+ }
25920
+ {
25921
+ const sp = spans([2750, 8006, 3196, 8311, 899, 14327, 792, 11239]);
25922
+ const vals = ["\uC804\uD654", f.phone ?? "", "/\uC804\uC1A1", f.fax ?? "", "/", f.email ?? "", "/", f.disclosure ?? ""];
25923
+ row(2e3, vals.map((v, i) => ({ span: sp[i], text: v })));
25924
+ }
25925
+ row(1e3, [{ span: GRID, text: "" }]);
25926
+ const height = rows.length * 1500;
25927
+ return host(ftbl(rows, w, height, GRID), reg.para({ align: "LEFT", lineSp: 100 }), c10);
25928
+ }
25929
+ function buildReportTitleTable(title, contact, ctx) {
25930
+ const { reg, bf, frame } = ctx;
25931
+ const w = ctx.W - 566;
25932
+ const fit = fitOneLine(title, frame.titleFont, frame.titlePt, w - 2 * 140 - 400, 20);
25933
+ const cTitle = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
25934
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
25935
+ const rows = [];
25936
+ const titleBf = bf.get({ t: "thick", b: contact ? "mid" : "thick", l: "none", r: "none" });
25937
+ rows.push(tc({ bf: titleBf, row: 0, col: 0, w, h: 4084, paras: para(title, center, cTitle), name: "__kordoc_h1" }));
25938
+ let h = 4084;
25939
+ if (contact) {
25940
+ const cContact = reg.char({ font: frame.contactFont, pt: frame.contactPt });
25941
+ rows.push(tc({ bf: bf.get({ t: "mid", b: "thick", l: "none", r: "none" }), row: 1, col: 0, w, h: 1943, paras: para(contact, center, cContact) }));
25942
+ h += 1943;
25943
+ }
25944
+ return { xml: host(ftbl(rows, w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), cTitle), overflow: fit.overflow };
25945
+ }
25946
+ var CHAPTER_BAND_DEFAULT = { color: "#003366", textColor: "#FFFFFF", numFont: "HY\uD5E4\uB4DC\uB77C\uC778M", numPt: 17 };
25947
+ function buildChapterBand(roman, title, ctx, spec, before2) {
25948
+ const { reg, bf } = ctx;
25949
+ const w = ctx.W;
25950
+ const numW = 3100, gap = 400, h = 2600;
25951
+ const titleW = w - numW - gap;
25952
+ const fit = fitOneLine(title, spec.titleFont, spec.titlePt, titleW - 700, spec.titlePt - 3);
25953
+ const numBf = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: spec.color });
25954
+ const gapBf = bf.get({ t: "none", b: "none", l: "none", r: "none" });
25955
+ const titleBf = bf.get({ t: "none", b: "thick", l: "none", r: "none" });
25956
+ const cNum = reg.char({ font: spec.numFont, pt: spec.numPt, bold: true, color: spec.textColor });
25957
+ const cTitle = reg.char({ font: spec.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
25958
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
25959
+ const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
25960
+ const row = tc({ bf: numBf, row: 0, col: 0, w: numW, h, paras: para(roman, center, cNum), vAlign: "CENTER" }) + tc({ bf: gapBf, row: 0, col: 1, w: gap, h, paras: para("", center, cNum), vAlign: "CENTER" }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h, paras: para(title, left, cTitle), name: "__kordoc_h2", vAlign: "CENTER" });
25961
+ return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cTitle), overflow: fit.overflow };
25962
+ }
25963
+ var MM05 = ["0.5 mm", "#000000"];
25964
+ var BANGCHIM_BLUE = "#0000FF";
25965
+ function buildBangchimTitleTable(title, subtitle, contact, ctx) {
25966
+ const { reg, bf, frame } = ctx;
25967
+ const w = ctx.W - 566;
25968
+ const fit = fitOneLine(title, frame.titleFont, frame.titlePt, w - 2 * 141 - 400, 20);
25969
+ const cTitle = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
25970
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
25971
+ let paras = para(title, center, cTitle);
25972
+ let h = Math.round(fit.pt * 100) + 1400;
25973
+ if (subtitle) {
25974
+ paras += para(subtitle, center, reg.char({ font: frame.titleFont, pt: 18, bold: true, color: BANGCHIM_BLUE }));
25975
+ h += 1800;
25976
+ }
25977
+ const rows = [tc({ bf: bf.get({ t: "thick", b: contact ? "mid" : "thick", l: "none", r: "none" }), row: 0, col: 0, w, h, paras, name: "__kordoc_h1" })];
25978
+ if (contact) {
25979
+ const p = reg.para({ align: "DISTRIBUTE", lineSp: 160, left: 300 });
25980
+ rows.push(tc({ bf: bf.get({ t: "mid", b: "thick", l: "none", r: "none" }), row: 1, col: 0, w, h: 1482, paras: para(contact, p, reg.char({ font: frame.contactFont, pt: frame.contactPt })) }));
25981
+ h += 1482;
25982
+ }
25983
+ return { xml: host(ftbl(rows, w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), cTitle), overflow: fit.overflow };
25984
+ }
25985
+ function buildBangchimSummary(text, ctx) {
25986
+ const { reg, bf, frame } = ctx;
25987
+ const w = ctx.W - 566;
25988
+ const pad = 566, left = 500;
25989
+ const avail = w - 2 * pad - left;
25990
+ const p = reg.para({ align: "DISTRIBUTE", lineSp: 140, left });
25991
+ const c = reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true });
25992
+ const bodies = text.split("\n").map((l) => l.trim().replace(/^[□■○ㅇ◦●\-–ㆍ·•]\s*/u, "")).filter(Boolean);
25993
+ let lines = 0;
25994
+ for (const b of bodies) lines += simulateWrap(b, avail, avail, frame.summaryPt * 100, 100, "keep", { faceClass: faceClassForGen(frame.summaryFont) }).lines;
25995
+ const h = lines * Math.round(frame.summaryPt * 100 * 1.4) + 2 * pad;
25996
+ const row = tc({ bf: bf.get({ t: "thick", b: "thick", l: "thick", r: "thick", fill: frame.summaryFill }), row: 0, col: 0, w, h, paras: bodies.map((b) => para(b, p, c)).join(""), name: "__kordoc_summary" }).replace('<hp:cellMargin left="141" right="141" top="141" bottom="141"/>', `<hp:cellMargin left="${pad}" right="${pad}" top="${pad}" bottom="${pad}"/>`);
25997
+ return { xml: host(ftbl([row], w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), c), lines };
25998
+ }
25999
+ function buildSquareChapter(label, title, ctx, st, before2) {
26000
+ const { reg, bf } = ctx;
26001
+ const numW = 3223, gapW = 1414, h = 3014;
26002
+ const maxTitle = ctx.W - numW - gapW;
26003
+ const fit = fitOneLine(title, st.font, st.pt, maxTitle - 1200, st.pt - 3);
26004
+ const textW2 = measureTextWidth(title, Math.round(fit.pt * 100), fit.ratio, { faceClass: faceClassForGen(st.font), spacingPct: fit.spacing });
26005
+ const titleW = Math.min(maxTitle, Math.max(14e3, Math.round(textW2) + 1200));
26006
+ const cNum = /^[Ⅰ-Ⅻ]+$/u.test(label) ? reg.char({ font: "HY\uACAC\uBA85\uC870", pt: 19, bold: true }) : reg.char({ font: st.font, pt: st.pt });
26007
+ const cTitle = reg.char({ font: st.font, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
26008
+ const center = reg.para({ align: "CENTER", lineSp: 150, left: 300 });
26009
+ const row = tc({ bf: bf.get({ t: MM05, b: MM05, l: MM05, r: MM05 }), row: 0, col: 0, w: numW, h, paras: para(label, center, cNum) }) + tc({ bf: bf.get({ t: "none", b: "none", l: MM05, r: "none" }), row: 0, col: 1, w: gapW, h, paras: para("", reg.para({ align: "JUSTIFY", lineSp: 160 }), cTitle) }) + tc({ bf: bf.get({ t: MM05, b: MM05, l: "none", r: "none" }), row: 0, col: 2, w: titleW, h, paras: para(title, center, cTitle), name: "__kordoc_h2" });
26010
+ const w = numW + gapW + titleW;
26011
+ return { xml: host(ftbl([row], w, h, 3, { bottomGap: 600 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cTitle), overflow: fit.overflow };
26012
+ }
26013
+ function buildBangchimSectionBand(n, title, ctx, before2) {
26014
+ const { reg, bf } = ctx;
26015
+ const w = ctx.W - 720, numW = 3400, gapW = 565, h = 2551;
26016
+ const titleW = w - numW - gapW;
26017
+ const fit = fitOneLine(title, "HY\uACAC\uACE0\uB515", 17, titleW - 700, 14);
26018
+ const cNum = reg.char({ font: "HY\uACAC\uACE0\uB515", pt: 16, color: "#FFFFFF" });
26019
+ const cTitle = reg.char({ font: "HY\uACAC\uACE0\uB515", pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
26020
+ const row = tc({ bf: bf.get({ t: "none", b: ["0.1 mm", "#CCCCCC"], l: "none", r: "none", fill: "#437FC1" }), row: 0, col: 0, w: numW, h, paras: para(String(n), reg.para({ align: "CENTER", lineSp: 160 }), cNum) }) + tc({ bf: bf.get({ t: "none", b: "none", l: "none", r: "none" }), row: 0, col: 1, w: gapW, h, paras: para("", reg.para({ align: "CENTER", lineSp: 160 }), cNum) }) + tc({ bf: bf.get({ t: "none", b: "none", l: "none", r: "none", fill: "#E5E5E5" }), row: 0, col: 2, w: titleW, h, paras: para(` ${title}`, reg.para({ align: "JUSTIFY", lineSp: 190 }), cTitle), name: "__kordoc_h3" });
26021
+ return { xml: host(ftbl([row], w, h, 3, { bottomGap: 600 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cTitle), overflow: fit.overflow };
26022
+ }
26023
+ function buildBangchimSubhead(n, title, ctx, before2) {
26024
+ const { reg } = ctx;
26025
+ const mark = n >= 1 && n <= 10 ? String.fromCodePoint(10101 + n) : `${n}.`;
26026
+ const p = reg.para({ align: "JUSTIFY", lineSp: 200, before: before2, keepWithNext: true });
26027
+ return `<hp:p paraPrIDRef="${p}" styleIDRef="0"><hp:run charPrIDRef="${reg.char({ font: "HY\uACAC\uACE0\uB515", pt: 16, bold: true })}"><hp:t>${escapeXml(mark)} </hp:t></hp:run><hp:run charPrIDRef="${reg.char({ font: "HY\uACAC\uACE0\uB515", pt: 16 })}"><hp:t>${escapeXml(title)}</hp:t></hp:run></hp:p>`;
26028
+ }
26029
+ var SUMMARY_PAD = 1e3;
26030
+ function buildSummaryBox(text, ctx) {
26031
+ const { reg, bf, frame } = ctx;
26032
+ const w = ctx.W - 566;
26033
+ const avail = w - 280 - SUMMARY_PAD * 2;
26034
+ const p = reg.para({ align: "JUSTIFY", lineSp: 160, left: SUMMARY_PAD, right: SUMMARY_PAD });
26035
+ const bodies = text.split("\n").map((l) => l.trim().replace(/^[□■○ㅇ◦●\-–ㆍ·•]\s*/u, "")).filter(Boolean);
26036
+ let lines = 0;
26037
+ const paras = bodies.map((b) => {
26038
+ const f = fitParagraph(b, frame.summaryFont, frame.summaryPt, avail, avail);
26039
+ const c = reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 });
26040
+ lines += simulateWrap(b, avail * 0.995, avail * 0.995, frame.summaryPt * 100, f?.ratio ?? 100, "keep", { faceClass: faceClassForGen(frame.summaryFont), spacingPct: f?.spacing ?? 0 }).lines;
26041
+ return para(b, p, c);
26042
+ });
26043
+ const h = lines * Math.round(frame.summaryPt * 100 * 1.6) + 280 + 600;
26044
+ const box = bf.get({ t: "thick", b: "thick", l: "thick", r: "thick", fill: frame.summaryFill });
26045
+ const row = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), name: "__kordoc_summary" });
26046
+ return { xml: host(ftbl([row], w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true })), lines };
26047
+ }
26048
+ function buildApprovalSeoul(labels, names, ctx) {
26049
+ const { reg, bf } = ctx;
26050
+ const n = labels.length;
26051
+ const colW = Math.min(8600, Math.floor(ctx.W * 0.55 / n));
26052
+ const w = colW * n;
26053
+ const c12 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12 });
26054
+ const c12b = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12, bold: true });
26055
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
26056
+ const edge = (row, col) => bf.get({
26057
+ t: row === 0 ? "thick" : "thin",
26058
+ b: row === 1 ? "thick" : "thin",
26059
+ l: col === 0 ? "thick" : "thin",
26060
+ r: col === n - 1 ? "thick" : "thin"
26061
+ });
26062
+ const top = labels.map((l, c) => tc({ bf: edge(0, c), row: 0, col: c, w: colW, h: 1765, paras: para(l, center, c12) })).join("");
26063
+ const sign = labels.map((_, c) => tc({ bf: edge(1, c), row: 1, col: c, w: colW, h: 4200, paras: para(names?.[c] ?? "", center, c12b) })).join("");
26064
+ return host(ftbl([top, sign], w, 5965, n, { bottomGap: 600 }), reg.para({ align: "RIGHT", lineSp: 100 }), c12);
26065
+ }
26066
+ function buildReportCover(inp, ctx) {
26067
+ const { reg, bf, frame } = ctx;
26068
+ const out = [];
26069
+ const cv = frame.cover;
26070
+ const c12 = reg.char({ font: cv?.infoFont ?? "\uD55C\uCEF4\uB3CB\uC6C0", pt: cv?.infoValuePt ?? 12 });
26071
+ const c12b = reg.char({ font: cv?.infoFont ?? "\uD55C\uCEF4\uB3CB\uC6C0", pt: cv?.infoLabelPt ?? 12, bold: true });
26072
+ const cAppr = cv ? reg.char({ font: cv.infoFont, pt: cv.approvalPt }) : c12;
26073
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
26074
+ const centerAt = (k) => cv ? reg.para({ align: "CENTER", lineSp: cv.lineSp[k] }) : center;
26075
+ const blank2 = (pt2 = 15) => para("", reg.para({ align: "LEFT", lineSp: 160 }), reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: pt2 }));
26076
+ const infoRows = [["\uBB38\uC11C\uBC88\uD638", inp.docInfo?.docNum ?? ""], ["\uACB0\uC7AC\uC77C\uC790", inp.docInfo?.date ?? ""], ["\uACF5\uAC1C\uC5EC\uBD80", inp.docInfo?.disclosure ?? ""], ["\uBC29\uCE68\uBC88\uD638", inp.docInfo?.policyNo ?? ""]];
26077
+ const infoW = Math.round(ctx.W * 0.36);
26078
+ const [lw, vw] = scale([4200, 12800], infoW);
26079
+ const infoXml = ftbl(infoRows.map(
26080
+ ([l, v], r) => tc({ bf: bf.get({ t: r === 0 ? "thick" : "thin", b: r === 3 ? "thick" : "thin", l: "thick", r: "thin" }), row: r, col: 0, w: lw, h: 1850, paras: para(l, centerAt("info"), c12b) }) + tc({ bf: bf.get({ t: r === 0 ? "thick" : "thin", b: r === 3 ? "thick" : "thin", l: "thin", r: "thick" }), row: r, col: 1, w: vw, h: 1850, paras: para(v, centerAt("info"), c12) })
26081
+ ), infoW, 1850 * 4, 2);
26082
+ let line = infoXml;
26083
+ if (inp.approval && inp.approval.length) {
26084
+ const n = inp.approval.length;
26085
+ const colW = Math.min(7600, Math.floor(ctx.W * 0.5 / n));
26086
+ const edge = (row, col) => bf.get({ t: row === 0 ? "thick" : "thin", b: row === 1 ? "thick" : "thin", l: col === 0 ? "thick" : "thin", r: col === n - 1 ? "thick" : "thin" });
26087
+ const top = inp.approval.map((l, c) => tc({ bf: edge(0, c), row: 0, col: c, w: colW, h: 1765, paras: para(l, centerAt("approval"), cAppr) })).join("");
26088
+ const sign = inp.approval.map((_, c) => tc({ bf: edge(1, c), row: 1, col: c, w: colW, h: 3600, paras: para("", center, c12b) })).join("");
26089
+ line += `</hp:run><hp:run charPrIDRef="${c12}"><hp:t> </hp:t></hp:run><hp:run charPrIDRef="${c12}">` + ftbl([top, sign], colW * n, 5365, n);
26090
+ }
26091
+ out.push(`<hp:p paraPrIDRef="${reg.para({ align: "DISTRIBUTE", lineSp: 100 })}" styleIDRef="0"><hp:run charPrIDRef="${c12}">${line}</hp:run></hp:p>`);
26092
+ for (let i = 0; i < 4; i++) out.push(blank2(20));
26093
+ const w = ctx.W - 1200;
26094
+ const bar = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: cv ? { gradient: [...cv.bar.gradient], type: "LINEAR", angle: 90 } : "#1F2FD6" });
26095
+ const barH = cv?.bar.h ?? 200;
26096
+ const fit = fitOneLine(inp.title, frame.titleFont, 27, w - 800, 22);
26097
+ const cT = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
26098
+ const c1 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 1 });
26099
+ const barP = reg.para({ align: "CENTER", lineSp: 70 });
26100
+ const rows = [
26101
+ tc({ bf: bar, row: 0, col: 0, w, h: barH, paras: para("", barP, c1) }),
26102
+ tc({ bf: BF_NONE, row: 1, col: 0, w, h: 6e3, paras: para(inp.title, centerAt("title"), cT), name: "__kordoc_skip" }),
26103
+ tc({ bf: bar, row: 2, col: 0, w, h: barH, paras: para("", barP, c1) })
26104
+ ];
26105
+ out.push(host(ftbl(rows, w, 6e3 + 2 * barH, 1), reg.para({ align: "CENTER", lineSp: 100 }), cT));
26106
+ for (let i = 0; i < 2; i++) out.push(blank2(20));
26107
+ out.push(para(inp.date, centerAt("date"), reg.char(cv ? cv.date : { font: frame.titleFont, pt: 22, bold: true })));
26108
+ for (let i = 0; i < 8; i++) out.push(blank2(20));
26109
+ if (inp.org) out.push(para(spacedOrgName(inp.org), centerAt("org"), reg.char(cv ? cv.org : { font: frame.titleFont, pt: 24, bold: true })));
26110
+ if (inp.dept) out.push(para(`(${inp.dept.replace(/^\(|\)$/g, "")})`, centerAt("dept"), reg.char(cv ? cv.dept : { font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 22, bold: true })));
26111
+ return out;
26112
+ }
26113
+
26114
+ // src/hwpx/gen-frame-seoul-front.ts
26115
+ var FONT = "\uB9D1\uC740 \uACE0\uB515";
26116
+ var ORANGE = "#FF9D19";
26117
+ var BLUE = "#0000FF";
26118
+ var O = (t) => [t, 4, 100, ORANGE];
26119
+ var HEAD1 = (gap = 4) => [[" ", 4], O("\u25C6"), [" ", gap]];
26120
+ var GROUPS = [
26121
+ {
26122
+ title: "1. \uC77C\uBC18\uC0AC\uD56D",
26123
+ h: 2553,
26124
+ cats: [{
26125
+ label: "\uC815\uCC45\uC77C\uBC18",
26126
+ spacing: 0,
26127
+ items: [
26128
+ { h: 3914, indent: -2076, lineSp: 150, paras: [[...HEAD1(3), ["\uC81C\uBC18 \uBC95\uADDC\uC640 \uC2E4\uD0DC, \uC2E4\uC81C \uD604\uC7A5\uC758 \uC758\uACAC \uB4F1\uC744 \uAC80\uD1A0\uD558\uC600\uC2B5\uB2C8\uAE4C?", -5, 85], ["(\uBC95\uB839, \uADDC\uCE59, \uD1B5\uACC4\xB7\uBE45\uB370\uC774\uD130, \uC2DC\uBBFC\uC758\uACAC \uB4F1)", 1, 85]]] },
26129
+ { h: 5031, indent: -2080, lineSp: 160, paras: [[...HEAD1(), ["\uC815\uCC45(\uC0AC\uC5C5) \uC9D1\uD589\uC758 \uC9C1\xB7\uAC04\uC811\uC801 \uC601\uD5A5 \uBC0F \uD6A8\uACFC\uC131\uC744 \uBD84\uC11D\uD558\uC600\uC2B5\uB2C8\uAE4C?(\uAC08\uB4F1, \uC57D\uC790, \uC77C\uC790\uB9AC, \uC548\uC804, \uD0C4\uC18C \uAC10\uCD95 \uB4F1)", -1, 95]], [[" \u203B \uC57D\uC790 : \uD558\uB2E8 \uC138\uBD80 \uAC80\uD1A0\uD56D\uBAA9 \uC810\uAC80", -13, 100, BLUE, 9]]] },
26130
+ { h: 3914, indent: -2080, lineSp: 150, paras: [[...HEAD1(), ["\uC815\uCC45\xB7\uACC4\uD68D\xB7\uC804\uC2DC\uBB3C\xB7\uD64D\uBCF4\uBB3C \uB4F1\uC774 \uC5ED\uC0AC\uC801 \uC0AC\uC2E4\uC5D0 \uBD80\uD569\uD558\uB294\uC9C0 \uAC80\uD1A0\uD558\uC600\uC2B5\uB2C8\uAE4C?", 1, 90]]] },
26131
+ { h: 2431, indent: 0, lineSp: 150, paras: [[...HEAD1(), ["\uBD88\uD544\uC694\uD55C \uC678\uAD6D\uC5B4\xB7\uC678\uB798\uC5B4 \uD45C\uD604 \uB300\uC2E0 \uBC14\uB978 \uC6B0\uB9AC\uB9D0\uC744 \uC0AC\uC6A9\uD558\uC600\uC2B5\uB2C8\uAE4C?", -2, 93]]] },
26132
+ { h: 2431, indent: 0, lineSp: 150, paras: [[...HEAD1(), ["\uC815\uCC45\xB7\uACC4\uD68D \uB4F1\uC758 \uC9C0\uC18D\uAC00\uB2A5\uC131\uC744 \uAC80\uD1A0\uD558\uC600\uC2B5\uB2C8\uAE4C?(\uACBD\uC81C\xB7\uC0AC\uD68C\xB7\uD658\uACBD \uB4F1)", -1, 95]]] }
26133
+ ]
26134
+ }]
26135
+ },
26136
+ {
26137
+ title: "2. \uC57D\uC790\uC640\uC758 \uB3D9\uD589",
26138
+ h: 3380,
26139
+ cats: [
26140
+ {
26141
+ label: "\uBAA9\uC801",
26142
+ spacing: -6,
26143
+ items: [
26144
+ { h: 2431, indent: -2212, lineSp: 150, paras: [[O("\u25C6 "), ["\uACBD\uC81C\uC801\uC73C\uB85C \uCDE8\uC57D\uD55C \uC57D\uC790\uB97C \uC9C0\uC6D0\uD558\uAE30 \uC704\uD55C \uC0AC\uC5C5\uC778\uAC00?", -4, 96]]] },
26145
+ { h: 3731, indent: -1544, lineSp: 150, paras: [[O("\u25C6"), [" ", -8], ["\uC8FC\uAC70, \uC0DD\uACC4, \uC758\uB8CC, \uAD50\uC721, \uAE30\uD0C0 \uBD84\uC57C \uC911 \uC5B4\uB290 \uAC83\uC5D0 \uD574\uB2F9\uD558\uB294\uAC00?", -11]]], note: ["\uC8FC\uAC70/\uC0DD\uACC4/\uC758\uB8CC/\uAD50\uC721/\uAE30\uD0C0", -14, 86] },
26146
+ { h: 4014, indent: -1568, indent2: -1568, lineSp: 150, paras: [[O("\u25C6"), [" ", -4], ["\uB2E8\uC21C\uC9C0\uC6D0 \uC0AC\uC5C5\uACFC \uC0AC\uB2E4\uB9AC\uC9C0\uC6D0 \uC0AC\uC5C5 \uC911 \uC5B4\uB290 \uAC83\uC5D0 \uD574\uB2F9\uD558\uB294\uAC00?", -9, 101]], [[" ", -16, 101], ["(\uB2E8\uC21C : \uC5B4\uB824\uC6C0 \uC989\uC2DC \uAC1C\uC120 / \uC0AC\uB2E4\uB9AC : \uACC4\uCE35 \uC0C1\uD5A5 \uB610\uB294 \uC548\uC815 \uC720\uB3C4)", -11, 101]]], note: ["\uB2E8\uC21C\uC9C0\uC6D0/\uC0AC\uB2E4\uB9AC\uC9C0\uC6D0"] },
26147
+ { h: 3914, indent: -1560, lineSp: 150, paras: [[O("\u25C6 "), ["\uACBD\uC81C\uC801\uC73C\uB85C \uCDE8\uC57D\uD55C \uC57D\uC790\uAC00 \uC544\uB2C8\uB77C \uD558\uB354\uB77C\uB3C4", -7], [", \uB2E4\uC591\uD55C \uBD84\uC57C\uC758 \uC0AC\uD68C\uC801 \uC57D\uC790\uB97C \uBC30\uB824\uD560 \uC218 \uC788\uB294 \uC218\uB2E8\uC744 \uD3EC\uD568\uD558\uC600\uB294\uAC00?", -4]]] }
26148
+ ]
26149
+ },
26150
+ {
26151
+ label: "\uACC4\uD68D\uC218\uB9BD",
26152
+ spacing: -6,
26153
+ items: [
26154
+ { h: 3914, indent: -1560, lineSp: 150, paras: [[O("\u25C6 "), ["\uC0AC\uC5C5 \uB300\uC0C1\uC790\uAC00 \uC27D\uACE0 \uD3B8\uB9AC\uD558\uAC8C \uC11C\uBE44\uC2A4\uB97C \uD65C\uC6A9\uD560 \uC218 \uC788\uB3C4\uB85D \uC0AC\uC5C5\uC744 \uAD6C\uC131\uD558\uC600\uB294\uAC00?", -8]]] },
26155
+ { h: 4014, indent: -1560, lineSp: 160, paras: [[O("\u25C6"), [" ", 4], ["\uCD95\uC81C\u2027\uD589\uC0AC\uACC4\uD68D\uC5D0 \uC57D\uC790 \uCC38\uC5EC \uAE30\uD68C \uD655\uB300 \uBC0F \uC57D\uC790 \uCE5C\uD654\uC801 \uD658\uACBD \uC870\uC131 \uB4F1 \uC57D\uC790\uB3D9\uD589 \uAD00\uC810\uC744 \uBC18\uC601\uD558\uC600\uB294\uAC00? ", -8], ["\u203B ", -13, 100, void 0, 9], ["\uCD95\uC81C\xB7\uD589\uC0AC\uC5D0 \uD55C\uD568", -8]]] }
26156
+ ]
26157
+ },
26158
+ {
26159
+ label: "\uC9D1\uD589\xB7\uD64D\uBCF4",
26160
+ spacing: -6,
26161
+ items: [
26162
+ { h: 3914, indent: -1560, lineSp: 150, paras: [[O("\u25C6 "), ["\uC9D1\uD589\uACFC\uC815\uC5D0\uC11C \uBC1C\uC0DD\uD560 \uC218 \uC788\uB294 \uC5ED\uD6A8\uACFC\uB97C \uCD5C\uC18C\uD654\uD558\uAE30 \uC704\uD55C \uB300\uC548\uC744 \uACE0\uB824\uD558\uC600\uB294\uAC00?(\u2018\uC57D\uC790\u2019 \uB099\uC778\uD6A8\uACFC, \uC5ED\uCC28\uBCC4 \uB4F1)", -5]]] },
26163
+ { h: 2431, indent: -1560, lineSp: 150, paras: [[O("\u25C6 "), ["\uC0AC\uC5C5 \uB300\uC0C1\uC790\uC5D0\uAC8C \uC801\uC808\uD55C \uD64D\uBCF4 \uC218\uB2E8\uC744 \uCC44\uD0DD\uD558\uC600\uB294\uAC00?", -5]]] }
26164
+ ]
26165
+ },
26166
+ {
26167
+ label: "\uD3C9\uAC00\xB7\uD658\uB958",
26168
+ spacing: -6,
26169
+ items: [
26170
+ { h: 3914, indent: -1560, lineSp: 150, paras: [[O("\u25C6 "), ["\uC0AC\uC5C5\uD6A8\uACFC\uB97C \uCE21\uC815\uD560 \uC218 \uC788\uB294 \uC9C0\uD45C(\uC0B0\uCD9C, \uACFC\uC815, \uC131\uACFC \uB4F1)\uB294 \uB9C8\uB828\uD558\uC600\uB294\uAC00?", -12], ["\uC544\uB2D0 \uACBD\uC6B0, \uC131\uACFC\uB97C \uD3C9\uAC00\uD560 \uC218 \uC788\uB294 \uB300\uCCB4\xB7\uBCF4\uC644 \uC218\uB2E8\uC774 \uC874\uC7AC\uD558\uB294\uAC00? ", -5]]] }
26171
+ ]
26172
+ }
26173
+ ]
26174
+ }
26175
+ ];
26176
+ var CHECKLIST_ITEMS = GROUPS.reduce((n, g3) => n + g3.cats.reduce((m, c) => m + c.items.length, 0), 0);
26177
+ var COLS = [5781, 30517, 3130, 3130, 5960];
26178
+ var W = 48518;
26179
+ var LINE = (w, c) => [w, c];
26180
+ var OR01 = LINE("0.1 mm", "#FFA500");
26181
+ var OR07 = LINE("0.7 mm", "#FFA500");
26182
+ var OR10 = LINE("1.0 mm", "#FFA500");
26183
+ var OR025 = LINE("0.25 mm", "#FFA500");
26184
+ var WH01 = LINE("0.1 mm", "#FFFFFF");
26185
+ var WH02 = LINE("0.2 mm", "#FFFFFF");
26186
+ var GOLD01 = LINE("0.1 mm", "#DE9E17");
26187
+ var GRAY = "#E5E5E5";
26188
+ var WHITE = "#FFFFFF";
26189
+ function buildSeoulChecklist(inp, ctx) {
26190
+ const { reg, bf } = ctx;
26191
+ const runXml = (r, bold = false) => `<hp:run charPrIDRef="${reg.char({ font: FONT, pt: r[4] ?? 10, bold, spacing: r[1] ?? 0, ratio: r[2] ?? 100, ...r[3] ? { color: r[3] } : {} })}"><hp:t>${escapeTextXml(r[0])}</hp:t></hp:run>`;
26192
+ const p = (runs, spec) => `<hp:p paraPrIDRef="${reg.para(spec)}" styleIDRef="0">${runs}</hp:p>`;
26193
+ const center = (lineSp = 160) => ({ align: "CENTER", lineSp });
26194
+ const rows = [];
26195
+ const banner = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: { gradient: ["#FED12E", "#FC8F00"], type: "LINEAR", angle: 90 } });
26196
+ rows.push(tc({ bf: banner, row: 0, col: 0, colSpan: 5, w: W, h: 3814, paras: p(runXml(["\u2611 \uC0AC\uC804 \uAC80\uD1A0\uD56D\uBAA9", 0, 100, "#3A2A00", 20], true), center()) }));
26197
+ const guide = [["\u261E \uD574\uB2F9\uC0AC\uD56D\uC774 \uC788\uB294 \uBD80\uBD84\uC5D0 \u2018 ", 0, 95, BLUE, 12], ["\u2019 \uD45C\uC2DC\uD558\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. ", 0, 95, BLUE, 12]];
26198
+ const guideXml = runXml(guide[0]) + runXml(["\u25A0 ", -10, 85, void 0, 12], true) + runXml(guide[1]) + `<hp:run charPrIDRef="${reg.char({ font: FONT, pt: 12, italic: true, spacing: -2, ratio: 90, color: BLUE })}"><hp:t>(</hp:t></hp:run><hp:run charPrIDRef="${reg.char({ font: FONT, pt: 12, italic: true, spacing: -2, ratio: 90, color: "#FF0000" })}"><hp:t>\u203B \uBE44\uACE0 : \uD544\uC694\uC2DC \uAC80\uD1A0\uB0B4\uC6A9 \uAE30\uC7AC</hp:t></hp:run><hp:run charPrIDRef="${reg.char({ font: FONT, pt: 12, italic: true, spacing: -2, ratio: 90, color: BLUE })}"><hp:t>)</hp:t></hp:run>`;
26199
+ rows.push(tc({ bf: bf.get({ t: "none", b: OR10, l: "none", r: "none" }), row: 1, col: 0, colSpan: 5, w: W, h: 2382, paras: p(guideXml, { align: "LEFT", lineSp: 180 }) }));
26200
+ const hdr = [
26201
+ ["\uAD6C \uBD84", 11, true, ORANGE, "DISTRIBUTE", bf.get({ t: "none", r: WH01, b: OR07, l: "none", fill: GRAY })],
26202
+ ["\uC0AC\uC804 \uAC80\uD1A0\uD56D\uBAA9 \uC810\uAC80 \uC0AC\uD56D", 15, false, void 0, "CENTER", bf.get({ l: WH01, r: GOLD01, t: OR025, b: OR07, fill: WHITE })],
26203
+ ["\uAC80\uD1A0\uC644\uB8CC", 12, false, void 0, "CENTER", bf.get({ l: GOLD01, r: GOLD01, t: "none", b: OR07, fill: WHITE })],
26204
+ ["\uD574\uB2F9\uC5C6\uC74C", 12, false, void 0, "CENTER", bf.get({ l: GOLD01, r: GOLD01, t: "none", b: OR07, fill: WHITE })],
26205
+ ["\uBE44 \uACE0", 12, false, void 0, "CENTER", bf.get({ l: GOLD01, r: "none", t: "none", b: OR07, fill: WHITE })]
26206
+ ];
26207
+ rows.push(hdr.map(([t, pt2, bold, color, align, b], c) => tc({
26208
+ bf: b,
26209
+ row: 2,
26210
+ col: c,
26211
+ w: COLS[c],
26212
+ h: 3142,
26213
+ paras: p(runXml([t, 0, 100, color, pt2], bold), align === "DISTRIBUTE" ? { align: "DISTRIBUTE", left: 500, right: 500, lineSp: 160 } : center(c === 1 ? 160 : 110))
26214
+ })).join(""));
26215
+ let row = 3, no = 0;
26216
+ const last = GROUPS.at(-1).cats.at(-1).items.at(-1);
26217
+ for (const g3 of GROUPS) {
26218
+ rows.push(tc({
26219
+ bf: bf.get({ t: WH02, b: OR01, l: "none", r: "none", fill: GRAY }),
26220
+ row,
26221
+ col: 0,
26222
+ colSpan: 5,
26223
+ w: W,
26224
+ h: g3.h,
26225
+ paras: p(runXml([g3.title, 0, 100, void 0, 14], true), { align: "LEFT", left: 500, right: 500, lineSp: 160 })
26226
+ }));
26227
+ row++;
26228
+ for (const cat of g3.cats) {
26229
+ cat.items.forEach((it, k) => {
26230
+ no++;
26231
+ const end = it === last;
26232
+ const bottom = end ? OR10 : OR01;
26233
+ const cells = [];
26234
+ if (k === 0) {
26235
+ const h2 = cat.items.reduce((s, x) => s + x.h, 0);
26236
+ cells.push(tc({
26237
+ bf: bf.get({ t: WH02, b: cat.items.at(-1) === last ? OR10 : WH02, l: "none", r: "none", fill: GRAY }),
26238
+ row,
26239
+ col: 0,
26240
+ rowSpan: cat.items.length,
26241
+ w: COLS[0],
26242
+ h: h2,
26243
+ paras: p(runXml([cat.label, cat.spacing, 100, ORANGE, 11], true), { align: "DISTRIBUTE", left: 500, right: 500, lineSp: 160 })
26244
+ }));
26245
+ }
26246
+ const q = it.paras.map((runs, i) => p(runs.map((r) => runXml(r)).join(""), { align: "LEFT", indent: i === 0 ? it.indent : it.indent2 ?? 0, lineSp: it.lineSp })).join("");
26247
+ cells.push(tc({ bf: bf.get({ t: OR01, b: bottom, l: "none", r: OR01, fill: WHITE }), row, col: 1, w: COLS[1], h: it.h, paras: q }));
26248
+ const mark = inp.marks.get(no);
26249
+ for (const [c, on] of [[2, mark === "done"], [3, mark === "na"]]) {
26250
+ cells.push(tc({
26251
+ bf: bf.get({ t: OR01, b: bottom, l: OR01, r: OR01, fill: WHITE }),
26252
+ row,
26253
+ col: c,
26254
+ w: COLS[c],
26255
+ h: it.h,
26256
+ paras: p(on ? runXml(["\u25A0", -5, 80, void 0, 13]) : runXml(["\u25A1", 0, 100, void 0, 13]), center())
26257
+ }));
26258
+ }
26259
+ const note = inp.notes.has(no) ? [inp.notes.get(no)] : it.note ?? [""];
26260
+ cells.push(tc({ bf: bf.get({ t: OR01, b: bottom, l: OR01, r: "none", fill: WHITE }), row, col: 4, w: COLS[4], h: it.h, paras: p(runXml(note), center()) }));
26261
+ rows.push(cells.join(""));
26262
+ row++;
26263
+ });
26264
+ }
26265
+ }
26266
+ const h = 3814 + 2382 + 3142 + GROUPS.reduce((s, g3) => s + g3.h + g3.cats.reduce((m, c) => m + c.items.reduce((a, x) => a + x.h, 0), 0), 0);
26267
+ return host(ftbl(rows, W, h, 5), reg.para({ align: "CENTER", lineSp: 100 }), reg.char({ font: FONT, pt: 10 }));
26268
+ }
26269
+ function buildBangchimToc(chapters, ctx) {
26270
+ const { reg, bf } = ctx;
26271
+ const cols = [3650, 39336, 4464], w = 47450;
26272
+ const thin = ["0.12 mm", "#000000"];
26273
+ const none = bf.get({ t: "none", b: "none", l: "none", r: "none" });
26274
+ const under = bf.get({ t: "none", b: thin, l: "none", r: "none" });
26275
+ const pageBox = bf.get({ t: "none", b: thin, l: "none", r: thin });
26276
+ const pCenter = (lineSp) => reg.para({ align: "CENTER", lineSp });
26277
+ const blank2 = reg.char({ font: "HY\uD5E4\uB4DC\uB77C\uC778M", pt: 10 });
26278
+ const spacer = (row, h2) => cols.map((cw, c) => tc({ bf: none, row, col: c, w: cw, h: h2, paras: para("", pCenter(160), blank2) })).join("");
26279
+ const rows = [spacer(0, 1729)];
26280
+ let h = 1729;
26281
+ chapters.forEach((ch, i) => {
26282
+ if (i > 0) {
26283
+ rows.push(spacer(rows.length, 3112));
26284
+ h += 3112;
26285
+ }
26286
+ const roman = /^[Ⅰ-Ⅻ]+$/u.test(ch.label);
26287
+ const cNum = roman ? reg.char({ font: "HY\uACAC\uBA85\uC870", pt: 20, bold: true }) : reg.char({ font: "HY\uACAC\uACE0\uB515", pt: 18 });
26288
+ const titleFont = roman ? "HY\uD5E4\uB4DC\uB77C\uC778M" : "HY\uACAC\uACE0\uB515";
26289
+ const fit = fitOneLine(ch.title, titleFont, 18, cols[1] - 400, 15);
26290
+ const cTitle = reg.char({ font: titleFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
26291
+ const row = rows.length;
26292
+ rows.push(tc({ bf: none, row, col: 0, w: cols[0], h: 5859, paras: para(roman ? `${ch.label}.` : ch.label, pCenter(roman ? 150 : 160), cNum) }) + tc({ bf: under, row, col: 1, w: cols[1], h: 5859, paras: para(ch.title, reg.para({ align: roman ? "JUSTIFY" : "LEFT", lineSp: 160 }), cTitle) }) + tc({ bf: pageBox, row, col: 2, w: cols[2], h: 5859, paras: para("", pCenter(160), reg.char({ font: "HY\uACAC\uACE0\uB515", pt: 18 })) }));
26293
+ h += 5859;
26294
+ });
26295
+ const head = para("\uBAA9 \uCC28", reg.para({ align: "JUSTIFY", left: 3e3, before: 5e3, lineSp: 70 }), reg.char({ font: "HY\uD5E4\uB4DC\uB77C\uC778M", pt: 29, italic: true }));
26296
+ return [head, host(ftbl(rows, w, h, 3), reg.para({ align: "CENTER", lineSp: 100 }), blank2)];
26297
+ }
26298
+
26299
+ // src/hwpx/gongmun.ts
26300
+ var OFFICIAL_MARGINS = { top: 20, bottom: 15, left: 20, right: 15 };
26301
+ var GAEJOSIK_MARGINS = { top: 15, bottom: 15, left: 20, right: 20 };
26302
+ var SEOUL_REPORT_MARGINS = { top: 13, bottom: 13, left: 18, right: 18 };
26303
+ var SEOUL_REPORT_HEADER_FOOTER = 3600;
26304
+ var GAEJOSIK_HEADER_FOOTER = 4251;
26305
+ var MINISTRY_MARGINS = { top: 10, bottom: 10, left: 20, right: 20 };
26306
+ var MINISTRY_HEADER_FOOTER = 2835;
26307
+ var PRESET_DEFAULTS = {
26308
+ // 서울시 실결재 104건 중 12pt 64건(13pt 30, 15pt 5) — 지배값을 기본으로 사용.
26309
+ official: { bodyPt: 12, lineSpacing: 160, numbering: "standard" },
26310
+ report: { bodyPt: 15, lineSpacing: 160, numbering: "report" },
26311
+ // 실측 추진계획안: □ → ㅇ → * 계층.
26312
+ plan: { bodyPt: 15, lineSpacing: 160, numbering: "report" },
26313
+ notice: { bodyPt: 15, lineSpacing: 160, numbering: "standard" },
26314
+ minutes: { bodyPt: 14, lineSpacing: 130, numbering: "standard" },
26315
+ gaejosik: { bodyPt: 15, lineSpacing: 160, numbering: "gaejosik" },
26316
+ // 보도자료 — 실측(국토부 실물): 본문 바탕 14pt 160%, □→ㅇ→*(각주) 부호
26317
+ press: { bodyPt: 14, lineSpacing: 160, numbering: "report" },
26318
+ // 중앙부처 업무보고 — 실측(재경부 2차 업무보고): 함초롬바탕 15pt, 줄피치 21.7pt(≈145%), □→ㅇ→-→*(각주)
26319
+ ministry: { bodyPt: 15, lineSpacing: 145, numbering: "report" },
26320
+ // 서울 방침서 — 실측(시장방침 「청년취업사관학교 2.0」 추진계획 외 4건): □ HY견고딕 17 · ㅇ 한컴돋움 15b · - 휴먼명조 14, 줄간격 200%
26321
+ bangchim: { bodyPt: 15, lineSpacing: 190, numbering: "report" }
26322
+ };
26323
+ var PRESET_ALIAS = {
26324
+ official: "official",
26325
+ \uAE30\uC548\uBB38: "official",
26326
+ \uC2DC\uD589\uBB38: "official",
26327
+ \uACF5\uBB38: "official",
26328
+ \uACF5\uBB38\uC11C: "official",
26329
+ report: "report",
26330
+ \uBCF4\uACE0\uC11C: "report",
26331
+ plan: "plan",
26332
+ \uACC4\uD68D\uC11C: "plan",
26333
+ \uACC4\uD68D: "plan",
26334
+ notice: "notice",
26335
+ \uD1B5\uC9C0: "notice",
26336
+ \uC54C\uB9BC: "notice",
26337
+ \uC548\uB0B4: "notice",
26338
+ minutes: "minutes",
26339
+ \uD68C\uC758\uB85D: "minutes",
26340
+ gaejosik: "gaejosik",
26341
+ \uAC1C\uC870\uC2DD: "gaejosik",
26342
+ \uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
26343
+ \uC815\uBD80\uBCF4\uACE0\uC11C: "gaejosik",
26344
+ \uC815\uBD80\uD45C\uC900\uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
26345
+ press: "press",
26346
+ \uBCF4\uB3C4\uC790\uB8CC: "press",
26347
+ ministry: "ministry",
26348
+ \uC5C5\uBB34\uBCF4\uACE0: "ministry",
26349
+ \uBD80\uCC98\uC5C5\uBB34\uBCF4\uACE0: "ministry",
26350
+ \uC911\uC559\uBD80\uCC98\uBCF4\uACE0\uC11C: "ministry",
26351
+ bangchim: "bangchim",
26352
+ \uC11C\uC6B8\uBC29\uCE68: "bangchim",
26353
+ \uBC29\uCE68\uC11C: "bangchim",
26354
+ \uBC29\uCE68: "bangchim"
26355
+ };
26356
+ function normalizeGongmunPreset(preset) {
26357
+ if (!preset) return "official";
26358
+ return PRESET_ALIAS[preset.trim()] ?? "official";
26359
+ }
26360
+ function usesReportFonts(preset) {
26361
+ return preset === "gaejosik" || preset === "report" || preset === "plan" || preset === "bangchim";
26362
+ }
26363
+ function usesAsteriskThird(preset) {
26364
+ return preset === "plan" || preset === "press";
26365
+ }
26366
+ function needsGaejosikAssets(gongmun) {
26367
+ return usesReportFonts(gongmun.preset) || gongmun.cover !== null || gongmun.toc;
26368
+ }
26369
+ function assertFiniteRange(name, value, min, max) {
26370
+ if (!Number.isFinite(value) || value < min || value > max) {
26371
+ throw new KordocError(`${name} must be a finite number between ${min} and ${max}`);
26372
+ }
26373
+ }
26374
+ function validateGongmunOptions(opts) {
26375
+ if (opts.bodyPt !== void 0) assertFiniteRange("bodyPt", opts.bodyPt, 6, 40);
26376
+ if (opts.lineSpacing !== void 0) assertFiniteRange("lineSpacing", opts.lineSpacing, 50, 300);
26377
+ if (typeof opts.autoFit === "object" && opts.autoFit.minRatio !== void 0) {
26378
+ assertFiniteRange("autoFit.minRatio", opts.autoFit.minRatio, 50, 99);
26379
+ }
26380
+ if (opts.margins) {
26381
+ for (const side of ["top", "bottom", "left", "right"]) {
26382
+ const value = opts.margins[side];
26383
+ assertFiniteRange(`margins.${side}`, value, 0, 200);
26384
+ }
26385
+ if (opts.margins.left + opts.margins.right >= 210) {
26386
+ throw new KordocError("margins.left + margins.right must be less than 210");
26387
+ }
26388
+ if (opts.margins.top + opts.margins.bottom >= 297) {
26389
+ throw new KordocError("margins.top + margins.bottom must be less than 297");
26390
+ }
26391
+ }
26392
+ if (opts.sizes) {
26393
+ for (const [key, value] of Object.entries(opts.sizes)) {
26394
+ if (value !== void 0) assertFiniteRange(`sizes.${key}`, value, 6, 60);
26395
+ }
26396
+ }
26397
+ if (opts.approval && opts.approval.length > 6) {
26398
+ throw new KordocError("approval must contain at most 6 labels");
26399
+ }
26400
+ if (opts.checklist && typeof opts.checklist === "object") {
26401
+ const nums = [...opts.checklist.na ?? [], ...Object.keys(opts.checklist.notes ?? {}).map(Number)];
26402
+ for (const n of nums) {
26403
+ if (!Number.isInteger(n) || n < 1 || n > CHECKLIST_ITEMS) throw new KordocError(`checklist: item number must be an integer between 1 and ${CHECKLIST_ITEMS} (got ${n})`);
26404
+ }
26405
+ }
26406
+ if (opts.levels) {
26407
+ for (const [key, st] of Object.entries(opts.levels)) {
26408
+ const depth = Number(key);
26409
+ if (!Number.isInteger(depth) || depth < 0 || depth >= GONGMUN_LEVEL_COUNT) {
26410
+ throw new KordocError(`levels: depth must be an integer between 0 and ${GONGMUN_LEVEL_COUNT - 1} (got "${key}")`);
26411
+ }
26412
+ if (!st || typeof st !== "object") throw new KordocError(`levels.${key} must be an object {font, pt, bold}`);
26413
+ if (st.pt !== void 0) assertFiniteRange(`levels.${key}.pt`, st.pt, 6, 60);
26414
+ if (st.font !== void 0 && (typeof st.font !== "string" || !st.font.trim())) {
26415
+ throw new KordocError(`levels.${key}.font must be a non-empty string`);
26416
+ }
26417
+ }
26418
+ }
26419
+ }
26420
+ var GONGMUN_LEVEL_COUNT = 8;
26421
+ function resolveLevels(levels, bodyHeight) {
26422
+ if (!levels) return null;
26423
+ const out = {};
26424
+ for (const [key, st] of Object.entries(levels)) {
26425
+ if (st.font === void 0 && st.pt === void 0 && st.bold === void 0) continue;
26426
+ out[Number(key)] = {
26427
+ font: st.font?.trim() || null,
26428
+ height: st.pt !== void 0 ? Math.round(st.pt * 100) : bodyHeight,
26429
+ bold: !!st.bold
26430
+ };
26431
+ }
26432
+ return Object.keys(out).length > 0 ? out : null;
26433
+ }
26434
+ function levelMarkerHeight(g3, depth) {
26435
+ return g3.levels?.[depth]?.height ?? g3.bodyHeight;
26436
+ }
26437
+ function incompatibleGongmunWarnings(opts) {
26438
+ const warns = [];
26439
+ const preset = normalizeGongmunPreset(opts.preset);
26440
+ if (opts.docHead && preset !== "official") warns.push(`doc_head(\uB450\uBB38)\uB294 \uAE30\uC548\uBB38(official) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
26441
+ if (opts.docFoot && preset !== "official") warns.push(`doc_foot(\uACB0\uBB38)\uB294 \uAE30\uC548\uBB38(official) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
26442
+ if (opts.noticeHead && preset !== "notice") warns.push(`notice_head(\uACF5\uACE0\uBC88\uD638\xB7\uBC1C\uC2E0\uBA85\uC758)\uB294 \uD1B5\uC9C0(notice) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
26443
+ if (opts.press && preset !== "press") warns.push(`press(\uBA38\uB9AC\uBC15\uC2A4\xB7\uBD80\uC81C\xB7\uB2F4\uB2F9)\uB294 \uBCF4\uB3C4\uC790\uB8CC(press) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
26444
+ if (opts.checklist && !SEOUL_REPORT_PRESETS.has(preset)) warns.push(`checklist(\uC0AC\uC804 \uAC80\uD1A0\uD56D\uBAA9 \uC810\uAC80\uD45C)\uB294 \uBCF4\uACE0\uC11C\xB7\uACC4\uD68D\uC11C\xB7\uBC29\uCE68\uC11C \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
26445
+ if (preset === "press" && (opts.cover === true || typeof opts.cover === "object" || opts.toc === true)) {
26446
+ warns.push("\uBCF4\uB3C4\uC790\uB8CC\uB294 \uBA38\uB9AC\uBC15\uC2A4 \uC11C\uC2DD\uACFC \uC591\uB9BD \uBD88\uAC00\uB77C \uD45C\uC9C0\xB7\uBAA9\uCC28\uAC00 \uBB34\uC2DC\uB428");
26447
+ }
26448
+ if (opts.sizes && Object.keys(opts.sizes).length > 0 && !usesReportFonts(preset)) {
26449
+ warns.push(`sizes(\uAC1C\uC870\uC2DD \uC694\uC18C \uD06C\uAE30)\uB294 \uAC1C\uC870\uC2DD\xB7\uBCF4\uACE0\uC11C\xB7\uACC4\uD68D\uC11C \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B\uC5D0\uC11C \uBB34\uC2DC\uB428`);
26450
+ }
26451
+ if (opts.suppressSingle && (opts.numbering ?? PRESET_DEFAULTS[preset].numbering) !== "standard") {
26452
+ warns.push(`suppress_single(\uB2E8\uC77C \uD615\uC81C \uBD80\uD638 \uC0DD\uB7B5)\uC740 \uBC95\uC815 \uBC88\uD638(standard) \uC804\uC6A9 \u2014 '${preset}' \uD504\uB9AC\uC14B(\uBD88\uB9BF \uCCB4\uACC4)\uC5D0\uC11C \uBB34\uB3D9\uC791`);
26453
+ }
26454
+ return warns;
26455
+ }
26456
+ var SEOUL_REPORT_PRESETS = /* @__PURE__ */ new Set(["report", "plan", "bangchim"]);
26457
+ function resolveChecklist(c, preset) {
26458
+ if (!c || !SEOUL_REPORT_PRESETS.has(preset)) return null;
26459
+ const marks = /* @__PURE__ */ new Map(), notes = /* @__PURE__ */ new Map();
26460
+ if (typeof c === "object") {
26461
+ const na = new Set(c.na ?? []);
26462
+ for (let i = 1; i <= CHECKLIST_ITEMS; i++) marks.set(i, na.has(i) ? "na" : "done");
26463
+ for (const [k, v] of Object.entries(c.notes ?? {})) notes.set(Number(k), String(v));
26464
+ }
26465
+ return { marks, notes };
26466
+ }
26467
+ function hexColorOption(name, value) {
26468
+ if (value === void 0) return void 0;
26469
+ const v = value.trim();
26470
+ if (!/^#[0-9a-fA-F]{6}$/.test(v)) throw new KordocError(`${name} must be a #RRGGBB color (got "${value}")`);
26471
+ return v.toUpperCase();
26472
+ }
26473
+ function resolveGongmun(opts) {
26474
+ validateGongmunOptions(opts);
26475
+ const preset = normalizeGongmunPreset(opts.preset);
26476
+ const d = PRESET_DEFAULTS[preset];
26477
+ const bodyPt = opts.bodyPt ?? d.bodyPt;
26478
+ const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
26479
+ const ministry = preset === "ministry";
26480
+ const coverOn = opts.cover !== void 0 ? opts.cover !== false : preset === "gaejosik" || ministry;
26481
+ const coverOpts = typeof opts.cover === "object" ? opts.cover : {};
26482
+ const gaejosik = preset === "gaejosik";
26483
+ const reportFamily = gaejosik || preset === "report" || preset === "plan" || preset === "notice" || preset === "press";
26484
+ return {
26485
+ preset,
26486
+ bodyFont: opts.bodyFont ?? "myeongjo",
26487
+ bodyHeight: Math.round(bodyPt * 100),
26488
+ lineSpacing: opts.lineSpacing ?? d.lineSpacing,
26489
+ numbering: opts.numbering ?? d.numbering,
26490
+ margins: opts.margins ?? (ministry ? MINISTRY_MARGINS : preset === "report" || preset === "plan" || preset === "bangchim" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
26491
+ centerTitle: opts.centerTitle ?? true,
26492
+ autoFitMinRatio,
26493
+ // 보도자료는 머리박스가 1페이지 최상단을 차지하는 서식이라 표지·목차와 양립 불가 —
26494
+ // 켜면 머리박스가 표지에 얹히고 25pt 제목·부제가 유실된다 (docHead 프리셋 게이팅과 동일 관례)
26495
+ cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {}, ...coverOpts.label ? { label: coverOpts.label } : {} } : null,
26496
+ bodyPtExplicit: opts.bodyPt !== void 0,
26497
+ lineSpacingExplicit: opts.lineSpacing !== void 0,
26498
+ bodyFontExplicit: opts.bodyFont !== void 0,
26499
+ summary: opts.summary?.trim() || null,
26500
+ docInfo: opts.docInfo ?? null,
26501
+ checklist: resolveChecklist(opts.checklist, preset),
26502
+ toc: preset !== "press" && (opts.toc ?? (gaejosik || ministry)),
26503
+ fonts: opts.fonts ?? {},
26504
+ sizes: opts.sizes ?? {},
26505
+ levels: resolveLevels(opts.levels, Math.round(bodyPt * 100)),
26506
+ // 쪽번호 — 보고서 계열 관행(실측: 2_보고서 양식·추진계획·공고문 전부 하단 중앙)
26507
+ pageNumbers: opts.pageNumbers ?? (gaejosik || ministry || preset === "report" || preset === "plan" || preset === "bangchim"),
26508
+ // 머리말·꼬리말 — 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
26509
+ // 기안문 0(실결재 41/60건 h0/f0)
26510
+ headerFooter: ministry ? MINISTRY_HEADER_FOOTER : preset === "report" || preset === "plan" || preset === "bangchim" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
26511
+ // "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
26512
+ endMark: opts.endMark ?? preset === "official",
26513
+ approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
26514
+ // 본문 제목박스 — 실측(GT3·GT12): 목차 뒤 본문 시작에 제목 반복. 표지 켜진 개조식 기본
26515
+ bodyTitleBox: opts.bodyTitleBox ?? (gaejosik && coverOn),
26516
+ chapterFit: opts.chapterFit ?? false,
26517
+ // h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
26518
+ // v5 라운드 3: 보고서·계획서 기본 band(띠 표) — 서울 plan 7/19·교육청 7/18 실측, 실무자 요청
26519
+ h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "band" : preset === "bangchim" ? "square" : preset === "notice" ? "number" : "none"),
26520
+ // 띠 제목 색 — 실측 최다 #003366/흰 글자(계획서 띠 표 14개). 교육청형 밝은 띠는 옵션으로
26521
+ bandColor: hexColorOption("bandColor", opts.bandColor) ?? "#003366",
26522
+ bandTextColor: hexColorOption("bandTextColor", opts.bandTextColor) ?? "#FFFFFF",
26523
+ // 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
26524
+ // v5: 서울 실결재 ㅇ(이응) 지배 — 보고서·계획서·통지·보도자료 ㅇ, 중앙부처 개조식 양식만 ○
26525
+ bullet2: opts.bullet2 ?? (preset === "gaejosik" ? "\u25CB" : "\u3147"),
26526
+ // 단일 형제 부호 생략 — 규정이지만 부호 없는 계단이 실무 눈에 어색 (실무자 QA)
26527
+ suppressSingle: opts.suppressSingle ?? false,
26528
+ docHead: preset === "official" && opts.docHead ? opts.docHead : null,
26529
+ docFoot: preset === "official" && opts.docFoot ? opts.docFoot : null,
26530
+ reportInfo: opts.reportInfo?.trim() || null,
26531
+ noticeHead: preset === "notice" && opts.noticeHead ? opts.noticeHead : null,
26532
+ // 보도자료 머리박스는 프리셋 자체가 요구 — 옵션 미지정이어도 빈 객체
26533
+ press: preset === "press" ? opts.press ?? {} : null
26534
+ };
26535
+ }
26536
+ var REPORT_BULLETS = ["\u25A1", "\u25CB", "-", "\u318D"];
26537
+ var ASTERISK_BULLETS = ["\u25A1", "\u25CB", "*", "\u318D"];
26538
+ function standardMarker(depth, n) {
26539
+ switch (depth) {
26540
+ case 0:
26541
+ return `${n + 1}.`;
26542
+ case 1:
26543
+ return `${hangulOrdinal(n)}.`;
26544
+ case 2:
26545
+ return `${n + 1})`;
26546
+ case 3:
26547
+ return `${hangulOrdinal(n)})`;
26548
+ case 4:
26549
+ return `(${n + 1})`;
26550
+ case 5:
26551
+ return `(${hangulOrdinal(n)})`;
26552
+ case 6:
26553
+ return circledNumber(n);
26554
+ case 7:
26555
+ return circledHangul(n);
26556
+ default:
26557
+ return circledHangul(n);
26558
+ }
26559
+ }
26560
+ function reportMarker(depth, bullet2 = "\u25CB", asteriskThird = false) {
26561
+ const bullets = asteriskThird ? ASTERISK_BULLETS : REPORT_BULLETS;
26562
+ const m = bullets[Math.min(depth, bullets.length - 1)];
26563
+ return depth === 1 ? bullet2 : m;
26564
+ }
26565
+ function markerWidth(marker, bodyHeight) {
26566
+ let em = SPACE_EM_FIXED;
26567
+ for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
26568
+ return Math.round(em / 1e3 * bodyHeight);
26569
+ }
26570
+ function levelIndent(depth, bodyHeight, numbering, sizes = {}, bullet2 = "\u25CB", asteriskThird = false, markerHeight = bodyHeight) {
26571
+ if (numbering === "gaejosik") return gaejosikLevelIndent(depth, bodyHeight, sizes, bullet2);
26572
+ const marker = numbering === "report" ? reportMarker(depth, bullet2, asteriskThird) : standardMarker(depth, 0);
26573
+ return { left: Math.round(depth * bodyHeight), indent: -markerWidth(marker, markerHeight) };
26574
+ }
26575
+ function computeSuppression(depths) {
26576
+ const counts = /* @__PURE__ */ new Map();
26577
+ const keys = [];
26578
+ const path = [];
26579
+ for (const depth of depths) {
26580
+ path.length = depth + 1;
26581
+ path[depth] = (path[depth] ?? 0) + 1;
26582
+ const parentKey = path.slice(0, depth).join(".") + "|" + depth;
26583
+ keys.push(parentKey);
26584
+ counts.set(parentKey, (counts.get(parentKey) ?? 0) + 1);
26585
+ }
26586
+ return keys.map((k) => (counts.get(k) ?? 0) <= 1);
26587
+ }
26588
+ var GongmunNumberer = class {
26589
+ constructor(numbering, bullet2 = "\u25CB", asteriskThird = false) {
26590
+ this.numbering = numbering;
26591
+ this.bullet2 = bullet2;
26592
+ this.asteriskThird = asteriskThird;
26593
+ }
26594
+ numbering;
26595
+ bullet2;
26596
+ asteriskThird;
26597
+ counts = [];
26598
+ /** depth 항목 하나에 대한 마커. suppress=true면 빈 문자열(부호 없음) */
26599
+ next(depth, suppress) {
26600
+ this.counts.length = depth + 1;
26601
+ const n = this.counts[depth] ?? 0;
26602
+ this.counts[depth] = n + 1;
26603
+ if (suppress) return "";
26604
+ if (this.numbering === "gaejosik") return gaejosikMarker(depth, this.bullet2);
26605
+ return this.numbering === "report" ? reportMarker(depth, this.bullet2, this.asteriskThird) : standardMarker(depth, n);
26606
+ }
26607
+ reset() {
26608
+ this.counts = [];
26609
+ }
26610
+ };
26611
+
25827
26612
  // src/hwpx/md-runs.ts
25828
26613
  var HYPERLINK_FIELDID = 627600491;
25829
26614
  var FIELD_ID_BASE = 18e8;
@@ -26054,6 +26839,7 @@ function parseInlineMarkdown(text) {
26054
26839
  text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
26055
26840
  text = text.replace(/~~([^~]+)~~/g, "$1");
26056
26841
  text = text.replace(/<u>([^<]*)<\/u>/g, "$1");
26842
+ text = text.replace(/<(sup|sub)>([^<]*)<\/\1>/g, "$2");
26057
26843
  let spans;
26058
26844
  if (inlineDoc) {
26059
26845
  spans = [];
@@ -26997,8 +27783,8 @@ function cell(opts) {
26997
27783
  const paras = opts.paras ?? emptyPara();
26998
27784
  return `<hp:tc name="${opts.name ?? ""}" header="0" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="${opts.bf}"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="${opts.vertAlign ?? "CENTER"}" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${paras}</hp:subList><hp:cellAddr colAddr="${opts.col}" rowAddr="${opts.row ?? 0}"/><hp:cellSpan colSpan="${opts.colSpan ?? 1}" rowSpan="1"/><hp:cellSz width="${opts.w}" height="${opts.h}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
26999
27785
  }
27000
- function table(rows, w, h, cols, tblBf = 1, bodyWidth = GAEJOSIK_BASE_WIDTH) {
27001
- const outLR = w + 566 > bodyWidth ? 0 : 283;
27786
+ function table(rows, w, h, cols, tblBf = 1, bodyWidth = GAEJOSIK_BASE_WIDTH, marginW = w) {
27787
+ const outLR = marginW + 566 > bodyWidth ? 0 : 283;
27002
27788
  return `<hp:tbl id="${++gjTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="${tblBf}" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="${outLR}" right="${outLR}" top="283" bottom="283"/><hp:inMargin left="141" right="141" top="141" bottom="141"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
27003
27789
  }
27004
27790
  function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
@@ -27064,12 +27850,19 @@ function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH)
27064
27850
  const box = table([topRow, titleRow, botRow], g3.totalW, bt.barH * 2 + bt.titleH, 3, 1, bodyWidth);
27065
27851
  return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${box}</hp:run></hp:p>`;
27066
27852
  }
27853
+ var CHAPTER_FIT_PAD = 282 + 300;
27067
27854
  function buildGaejosikChapter(n, title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH, srcLevel = 2) {
27068
27855
  const g3 = gongmun ? chapterGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth) : chapterGeom(1500, {}, bodyWidth);
27856
+ const titleW = gongmun?.chapterFit ? Math.min(g3.titleW, Math.ceil(measureTextWidth(
27857
+ ` ${title} `,
27858
+ gaejosikSizes(gongmun.bodyHeight, gongmun.sizes).chapter,
27859
+ 100,
27860
+ { faceClass: faceClassForGen(gongmun.fonts.heading ?? "HY\uD5E4\uB4DC\uB77C\uC778M") }
27861
+ )) + CHAPTER_FIT_PAD) : g3.titleW;
27069
27862
  const numPara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_NUM}"><hp:t>${chapterRoman(n)}</hp:t></hp:run></hp:p>`;
27070
27863
  const titlePara = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_TITLE}"><hp:t> ${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
27071
- const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w: g3.numW, h: g3.rowH, paras: numPara }) + cell({ bf: GJ_BF_CHAPTER_GAP, col: 1, w: g3.gapW, h: g3.rowH }) + cell({ bf: GJ_BF_CHAPTER_TITLE, col: 2, w: g3.titleW, h: g3.rowH, paras: titlePara, name: `__kordoc_h${Math.min(Math.max(srcLevel, 1), 6)}` });
27072
- const tbl2 = table([row], g3.numW + g3.gapW + g3.titleW, g3.rowH, 3, 1, bodyWidth);
27864
+ const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w: g3.numW, h: g3.rowH, paras: numPara }) + cell({ bf: GJ_BF_CHAPTER_GAP, col: 1, w: g3.gapW, h: g3.rowH }) + cell({ bf: GJ_BF_CHAPTER_TITLE, col: 2, w: titleW, h: g3.rowH, paras: titlePara, name: `__kordoc_h${Math.min(Math.max(srcLevel, 1), 6)}` });
27865
+ const tbl2 = table([row], g3.numW + g3.gapW + titleW, g3.rowH, 3, 1, bodyWidth, g3.numW + g3.gapW + g3.titleW);
27073
27866
  return `<hp:p paraPrIDRef="${GJ_PARA_CHAPTER}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
27074
27867
  }
27075
27868
 
@@ -28399,64 +29192,6 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
28399
29192
  return `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE"${float ? FLOAT_TBL_ATTRS : ""} pageBreak="CELL" repeatHeader="${style ? 1 : 0}" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0"><hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tableH}" heightRelTo="ABSOLUTE" protect="0"/>` + tablePosXml(float, !!reg && style?.rightParaPr !== void 0) + tableOutMarginXml(float, measureH) + `<hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trXmls.join("") + `</hp:tbl>`;
28400
29193
  }
28401
29194
 
28402
- // src/hwpx/gen-gongmun-extra.ts
28403
- var extraTableId = EXTRA_TABLE_ID_BASE;
28404
- function resetExtraTableIds() {
28405
- extraTableId = EXTRA_TABLE_ID_BASE;
28406
- }
28407
- function tbl(rows, w, h, cols) {
28408
- return `<hp:tbl id="${++extraTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="1" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="283" right="283" top="283" bottom="283"/><hp:inMargin left="141" right="141" top="141" bottom="141"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
28409
- }
28410
- function tc(opts) {
28411
- return `<hp:tc name="${opts.name ?? ""}" header="0" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="${opts.bf}"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="${opts.vAlign ?? "CENTER"}" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${opts.paras}</hp:subList><hp:cellAddr colAddr="${opts.col}" rowAddr="${opts.row}"/><hp:cellSpan colSpan="${opts.colSpan ?? 1}" rowSpan="${opts.rowSpan ?? 1}"/><hp:cellSz width="${opts.w}" height="${opts.h}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
28412
- }
28413
- function para(text, paraPrId, charPrId) {
28414
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeTextXml(text)}</hp:t></hp:run></hp:p>`;
28415
- }
28416
- var APPROVAL_COL_W = 7085;
28417
- var APPROVAL_LABEL_H = 1300;
28418
- var APPROVAL_SIGN_H = 4200;
28419
- function buildApprovalTable(labels, reg, approvalCharPr) {
28420
- const n = labels.length;
28421
- const w = APPROVAL_COL_W * n;
28422
- const bfAt = (row, col) => reg.get({
28423
- t: row === 0 ? "thick" : "thin",
28424
- b: row === 1 ? "thick" : "thin",
28425
- l: col === 0 ? "thick" : "thin",
28426
- r: col === n - 1 ? "thick" : "thin"
28427
- });
28428
- const labelRow = labels.map(
28429
- (label, c) => tc({ bf: bfAt(0, c), row: 0, col: c, w: APPROVAL_COL_W, h: APPROVAL_LABEL_H, paras: para(label, GONGMUN_PARA_APPROVAL, approvalCharPr) })
28430
- ).join("");
28431
- const signRow = labels.map(
28432
- (_, c) => tc({ bf: bfAt(1, c), row: 1, col: c, w: APPROVAL_COL_W, h: APPROVAL_SIGN_H, paras: para("", GONGMUN_PARA_APPROVAL, approvalCharPr) })
28433
- ).join("");
28434
- const table2 = tbl([labelRow, signRow], w, APPROVAL_LABEL_H + APPROVAL_SIGN_H, n);
28435
- return `<hp:p paraPrIDRef="${GONGMUN_RIGHT}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`;
28436
- }
28437
- function hasEndMark(lastText) {
28438
- return /(^|\s)끝\s*\.\s*$/.test(lastText.trim());
28439
- }
28440
- function buildEndMark() {
28441
- return para(" \uB05D.", PARA_NORMAL, CHAR_NORMAL);
28442
- }
28443
- var TITLE_BOX = { barH: 382, titleH: 2850, color: "#0080C0", gradient: ["#0080C0", "#3CBFFF"] };
28444
- function buildTitleBox(title, titleCharPr, barCharPr, bodyWidth, reg) {
28445
- const w = bodyWidth - 280;
28446
- const outer = { t: "thin", b: "thin", l: "thin", r: "thin" };
28447
- const barTop = reg.get({ ...outer, fill: TITLE_BOX.color });
28448
- const barBottom = reg.get({ ...outer, fill: { gradient: TITLE_BOX.gradient } });
28449
- const mid = reg.get(outer);
28450
- const barPara = para("", GJ_PARA_BAR, barCharPr);
28451
- const rows = [
28452
- tc({ bf: barTop, row: 0, col: 0, w, h: TITLE_BOX.barH, paras: barPara }),
28453
- tc({ bf: mid, row: 1, col: 0, w, h: TITLE_BOX.titleH, paras: para(title, GONGMUN_TBL_CENTER, titleCharPr), name: "__kordoc_h1" }),
28454
- tc({ bf: barBottom, row: 2, col: 0, w, h: TITLE_BOX.barH, paras: barPara })
28455
- ];
28456
- const table2 = tbl(rows, w, TITLE_BOX.barH * 2 + TITLE_BOX.titleH, 1);
28457
- return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`;
28458
- }
28459
-
28460
29195
  // src/hwpx/gen-docframe.ts
28461
29196
  function docframeIds(base) {
28462
29197
  return { org: base, sender: base + 1, foot: base + 2, small: base + 3, pressLabel: base + 4, pressHead: base + 5, pressTitle: base + 6, pressSub: base + 7, pressContact: base + 8 };
@@ -29519,6 +30254,63 @@ function ministryScheme(bodyPt = 15, lineSp = 145) {
29519
30254
  blankBetweenTop: false
29520
30255
  };
29521
30256
  }
30257
+ var BANGCHIM_FRAME = {
30258
+ font: "\uD55C\uCEF4\uB3CB\uC6C0",
30259
+ titleFont: "HY\uD5E4\uB4DC\uB77C\uC778M",
30260
+ titlePt: 26,
30261
+ contactFont: "\uD734\uBA3C\uBA85\uC870",
30262
+ contactPt: 12,
30263
+ summaryFont: "\uD55C\uCEF4\uB3CB\uC6C0",
30264
+ summaryPt: 15,
30265
+ summaryFill: "#DFE6F7",
30266
+ // 간이기안 표지 — 방침서 16건: 문서정보 굴림체(16건) 이름칸 10 굵게(11건)·값칸 9, 결재선 굴림체 9(15건 중 11)·8,
30267
+ // 제목 띠 그라데이션 #3057B9→#A0B4E6(15건) 높이 663(11건), 날짜 HY견명조 21b(13건), 기관 HY헤드라인M 26b(11건), 부서 HY견명조 22(14건 중 12)
30268
+ cover: {
30269
+ infoFont: "\uAD74\uB9BC\uCCB4",
30270
+ infoLabelPt: 10,
30271
+ infoValuePt: 9,
30272
+ approvalPt: 9,
30273
+ bar: { gradient: ["#3057B9", "#A0B4E6"], h: 663 },
30274
+ date: { font: "HY\uACAC\uBA85\uC870", pt: 21, bold: true },
30275
+ org: { font: "HY\uD5E4\uB4DC\uB77C\uC778M", pt: 26, bold: true },
30276
+ dept: { font: "HY\uACAC\uBA85\uC870", pt: 22, bold: false },
30277
+ // 줄간격 — 문서정보 160(16건)·결재선 130(16건)·제목 120(최다 5건)·날짜 170(11)·기관 170(12)·부서 190(11)
30278
+ lineSp: { info: 160, approval: 130, title: 120, date: 170, org: 170, dept: 190 }
30279
+ }
30280
+ };
30281
+ function seoulBangchimScheme(bodyPt = 15, lineSp = 190) {
30282
+ const d = bodyPt - 15;
30283
+ const lv = (font, pt2, bold, leadTa, extra = {}) => ({ font, pt: pt2 + d, bold, leadTa, ...extra });
30284
+ return {
30285
+ kind: "gaejosik",
30286
+ lineSp,
30287
+ body: lv("HY\uACAC\uACE0\uB515", 16, true, 0),
30288
+ levels: [
30289
+ lv("HY\uACAC\uACE0\uB515", 17, false, 0, { oneLine: true, keepWithNext: true, blankBefore: true }),
30290
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 15, true, 1),
30291
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 3),
30292
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 4, { align: "LEFT", lineSp: 180 }),
30293
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 5, { align: "LEFT", lineSp: 180 }),
30294
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 6, { align: "LEFT", lineSp: 180 }),
30295
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 7, { align: "LEFT", lineSp: 180 }),
30296
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 8, { align: "LEFT", lineSp: 180 })
30297
+ ],
30298
+ ref: lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 0, { align: "LEFT" }),
30299
+ sub: lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 2),
30300
+ attach: lv("\uD55C\uCEF4\uB3CB\uC6C0", 15, true, 0),
30301
+ chapter: lv("HY\uACAC\uACE0\uB515", 20, false, 0, { oneLine: true, keepWithNext: true }),
30302
+ marker: (depth) => depth === 0 ? "\u25A1" : depth === 1 ? "\u3147" : depth === 2 ? "-" : "\u25B8",
30303
+ table: { ...SEOUL_TABLE, pt: SEOUL_TABLE.pt + d },
30304
+ frame: BANGCHIM_FRAME,
30305
+ blankBetweenTop: false
30306
+ };
30307
+ }
30308
+ function bangchimNumbered(s) {
30309
+ const levels = s.levels.map((l) => ({ ...l }));
30310
+ levels[2] = { ...levels[2], leadTa: 2 };
30311
+ levels[3] = { ...levels[2], leadTa: 4 };
30312
+ return { ...s, levels, marker: (depth, n) => depth >= 2 ? "-" : s.marker(depth, n) };
30313
+ }
29522
30314
  function seoulLegalScheme(bodyFont = "\uAD74\uB9BC\uCCB4", bodyPt = 12, lineSp = 160) {
29523
30315
  const lv = (leadTa, extra = {}) => ({ font: bodyFont, pt: bodyPt, bold: false, leadTa, ...extra });
29524
30316
  return {
@@ -29563,7 +30355,11 @@ function applySchemeOverrides(s, g3) {
29563
30355
  function taHu(pt2) {
29564
30356
  return Math.round(pt2 * 50);
29565
30357
  }
29566
- function pickScheme(g3, bodyHasBoxMarkers) {
30358
+ function pickScheme(g3, bodyHasBoxMarkers, numbered = false) {
30359
+ if (g3.preset === "bangchim") {
30360
+ const s = seoulBangchimScheme(g3.bodyPtExplicit ? g3.bodyHeight / 100 : 15, g3.lineSpacingExplicit ? g3.lineSpacing : 190);
30361
+ return applySchemeOverrides(numbered ? bangchimNumbered(s) : s, g3);
30362
+ }
29567
30363
  if (g3.preset === "ministry") {
29568
30364
  return applySchemeOverrides(ministryScheme(g3.bodyPtExplicit ? g3.bodyHeight / 100 : 15, g3.lineSpacingExplicit ? g3.lineSpacing : 145), g3);
29569
30365
  }
@@ -29599,12 +30395,14 @@ var BOX_MARKERS = {
29599
30395
  "\u2219": 3
29600
30396
  };
29601
30397
  var LEGAL_RE = /^(\d{1,2}\.|[가-힣]\.|\d{1,2}\)|[가-힣]\)|\(\d{1,2}\)|\([가-힣]\)|[①-⑳]|[㉮-㉻])\s+/u;
29602
- var KEEP_MARKERS = { "\u21D2": 0, "\u21B3": 1, "\u261E": 0 };
29603
- var KEEP_RE = /^([❶-❿➊-➓⇒↳☞])\s*/u;
30398
+ var KEEP_MARKERS = { "\u21D2": 0, "\u21B3": 1, "\u261E": 0, "\u25B8": 3 };
30399
+ var KEEP_RE = /^([❶-❿➊-➓⇒↳☞▸])\s*/u;
29604
30400
  var BOX_RE = /^([□■❑❏ㅁ○ㅇ◦●❍◎\-–―—ㅡ‣▪▫ㆍ·•∙])\s+/u;
30401
+ var BOX_TIGHT_RE = /^([□■ㅇ○])(?=[「『‘“(([【])/u;
29605
30402
  var REF_RE = /^(※|*|\*(?=\s))\s*/u;
29606
30403
  var BUNIM_RE = /^붙\s*임(?:\s|:|$)/;
29607
30404
  var SOURCE_RE = /^(출처|자료|근거|참고)\s*[::]/;
30405
+ var CHAPTER_LABEL_RE = /^([ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+|\d{1,2}|[가-하])[.)]\s+/u;
29608
30406
  var LAW_CODE_RE = /((?:법률|기본법|특별법|법|령|규칙|조례|규정|고시|훈령|예규|지침)[」』]?)\s*\((\d{5,8})\)/gu;
29609
30407
  function stripLawCodes(text) {
29610
30408
  return text.replace(LAW_CODE_RE, "$1").replace(/[,,]?\s*\(?\bDT_[A-Z0-9_]+\)?/g, "").replace(/\s*\((?:법정동|행정동|시군구|지역)?\s*코드\s*[\d-]+\)/g, "").replace(/[,,]?\s*[가-힣A-Za-z·]*\s*MCP\s*(?:조회|호출|응답)?/g, "").replace(/\s+([,,.])/g, "$1").replace(/[,,]\s*$/g, "").replace(/ {2,}/g, " ").trim();
@@ -29620,19 +30418,33 @@ function legalMarkerDepth(marker) {
29620
30418
  return 7;
29621
30419
  }
29622
30420
  function parseLeadingMarker(text, keepMarkers = false) {
29623
- const t = text.replace(/^[\s ]+/, "");
30421
+ const t = text.replace(/^[\s ]+/, "").replace(/^\*\*([□■ㅇ○◦●❍◎▸※-])\s*(?=\S)/u, "$1 **");
29624
30422
  const ref = REF_RE.exec(t);
29625
30423
  if (ref) return { kind: "ref", depth: 0, marker: ref[1], rest: t.slice(ref[0].length).trim() };
29626
30424
  if (keepMarkers) {
29627
30425
  const keep = KEEP_RE.exec(t);
29628
30426
  if (keep) return { kind: "keep", depth: KEEP_MARKERS[keep[1]] ?? 0, marker: keep[1], rest: t.slice(keep[0].length).trim() };
29629
30427
  }
29630
- const box = BOX_RE.exec(t);
30428
+ const box = BOX_RE.exec(t) ?? BOX_TIGHT_RE.exec(t);
29631
30429
  if (box) return { kind: "box", depth: BOX_MARKERS[box[1]] ?? 3, marker: box[1], rest: t.slice(box[0].length).trim() };
29632
30430
  const legal = LEGAL_RE.exec(t);
29633
30431
  if (legal) return { kind: "legal", depth: legalMarkerDepth(legal[1]), marker: legal[1], rest: t.slice(legal[0].length).trim() };
29634
30432
  return { kind: null, depth: 0, marker: "", rest: t };
29635
30433
  }
30434
+ function numberClass(marker) {
30435
+ return marker.replace(/\d{1,2}/, "1").replace(/[가-힣]/, "\uAC00").replace(/[①-⑳]/u, "\u2460").replace(/[㉮-㉻]/u, "\u326E");
30436
+ }
30437
+ function numberRanks(blocks) {
30438
+ const order = [];
30439
+ for (const b of blocks) {
30440
+ if (b.type !== "list_item" && b.type !== "paragraph") continue;
30441
+ const lm = parseLeadingMarker((b.text ?? "").trim());
30442
+ if (lm.kind === "box" && lm.depth <= 1) return null;
30443
+ const m = b.type === "list_item" && b.ordered ? b.marker : lm.kind === "legal" ? lm.marker : null;
30444
+ if (m && !order.includes(numberClass(m))) order.push(numberClass(m));
30445
+ }
30446
+ return order.length >= 2 ? new Map(order.map((c, i) => [c, i])) : null;
30447
+ }
29636
30448
  function buildOutline(blocks, opts) {
29637
30449
  const nodes = [];
29638
30450
  let title = null;
@@ -29653,6 +30465,7 @@ function buildOutline(blocks, opts) {
29653
30465
  }
29654
30466
  };
29655
30467
  const keep = !!opts.keepMarkers;
30468
+ const ranks = opts.numbered ? numberRanks(blocks) : null;
29656
30469
  for (let i = 0; i < blocks.length; i++) {
29657
30470
  const b = blocks[i];
29658
30471
  switch (b.type) {
@@ -29664,7 +30477,8 @@ function buildOutline(blocks, opts) {
29664
30477
  break;
29665
30478
  }
29666
30479
  if (lvl <= 2) {
29667
- nodes.push({ kind: "chapter", text: stripChapterNumber(raw) || raw, index: ++chapters });
30480
+ const label = CHAPTER_LABEL_RE.exec(raw)?.[1];
30481
+ nodes.push({ kind: "chapter", text: stripChapterNumber(raw) || raw, index: ++chapters, ...label ? { label } : {} });
29668
30482
  lastExplicitDepth = -1;
29669
30483
  listBase = -1;
29670
30484
  headingDepth = opts.gaejosik ? -1 : 0;
@@ -29702,6 +30516,10 @@ function buildOutline(blocks, opts) {
29702
30516
  nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.rest });
29703
30517
  break;
29704
30518
  }
30519
+ if (ranks && b.ordered && b.marker) {
30520
+ pushItem(Math.min(ranks.get(numberClass(b.marker)) ?? 0, 7), text, void 0, false, b.marker);
30521
+ break;
30522
+ }
29705
30523
  if (lm.kind === "box") {
29706
30524
  hasBoxMarkers = true;
29707
30525
  pushItem(lm.depth, lm.rest);
@@ -29716,7 +30534,7 @@ function buildOutline(blocks, opts) {
29716
30534
  break;
29717
30535
  }
29718
30536
  let base = headingDepth >= 0 ? headingDepth + 1 : 0;
29719
- if (opts.headingFrames) {
30537
+ if (opts.headingFrames || opts.listUnderMarker) {
29720
30538
  if (listBase < 0) listBase = lastExplicitDepth + 1;
29721
30539
  base = listBase;
29722
30540
  }
@@ -29724,7 +30542,7 @@ function buildOutline(blocks, opts) {
29724
30542
  break;
29725
30543
  }
29726
30544
  case "paragraph": {
29727
- const text = stripLawCodes((b.text ?? "").trim());
30545
+ const text = stripLawCodes((b.text ?? "").trim()).replace(/^\*\*(붙\s*임[^*]*)\*\*$/, "$1");
29728
30546
  if (!text) break;
29729
30547
  if (BUNIM_RE.test(text) || nodes.length && nodes[nodes.length - 1].kind === "attach" && /^\s/.test(b.text ?? "")) {
29730
30548
  nodes.push({ kind: "attach", text: b.text ?? "" });
@@ -29759,7 +30577,8 @@ function buildOutline(blocks, opts) {
29759
30577
  break;
29760
30578
  }
29761
30579
  if (lm.kind === "legal") {
29762
- if (opts.gaejosik) pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
30580
+ if (ranks) pushItem(Math.min(ranks.get(numberClass(lm.marker)) ?? 0, 7), lm.rest, void 0, false, lm.marker);
30581
+ else if (opts.gaejosik) pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
29763
30582
  else pushItem(lm.depth, lm.rest);
29764
30583
  break;
29765
30584
  }
@@ -29781,104 +30600,7 @@ function buildOutline(blocks, opts) {
29781
30600
  seenBody = true;
29782
30601
  }
29783
30602
  }
29784
- return { title, nodes, hasBoxMarkers, chapters };
29785
- }
29786
-
29787
- // src/hwpx/fit-line.ts
29788
- var RATIOS = [100, 97, 95, 92, 90, 87, 85];
29789
- var SPACINGS = [0, -3, -5];
29790
- function safety(faceClass, approx) {
29791
- return faceClass.startsWith("font:") ? 0.98 : approx;
29792
- }
29793
- function fitOneLine(text, font, pt2, availHu, minPt = Math.max(pt2 - 3, 10), minRatio = 85) {
29794
- const faceClass = faceClassForGen(font);
29795
- const avail = availHu * safety(faceClass, 0.98);
29796
- const fits = (p, r, s) => measureTextWidth(text, p * 100, r, { spacingPct: s, faceClass }) <= avail;
29797
- if (fits(pt2, 100, 0)) return { pt: pt2, ratio: 100, spacing: 0, overflow: false };
29798
- const ratios = RATIOS.filter((r) => r >= minRatio);
29799
- for (let p = pt2; p >= minPt; p--) {
29800
- for (const r of ratios) for (const s of SPACINGS) {
29801
- if (fits(p, r, s)) return { pt: p, ratio: r, spacing: s, overflow: false };
29802
- }
29803
- }
29804
- return { pt: minPt, ratio: ratios[ratios.length - 1], spacing: SPACINGS[SPACINGS.length - 1], overflow: true };
29805
- }
29806
- var SQUEEZE = (() => {
29807
- const out = [];
29808
- for (let sp = -1; sp >= -12; sp--) out.push([100, sp]);
29809
- for (const r of [97, 95, 92, 90, 88]) for (let sp = 0; sp >= -12; sp--) out.push([r, sp]);
29810
- return out;
29811
- })();
29812
- var LOOSE_LIMIT = 1;
29813
- var PULL_TAIL = 0.5;
29814
- var PULL_MAX = 0.15;
29815
- var squeezeOf = (r, sp) => 1 - r / 100 * (1 + sp / 100);
29816
- var ORPHAN_RATIO = 0.22;
29817
- var ORPHAN_COST = 1.5;
29818
- var COMPRESS_COST = 10;
29819
- function worstLooseness(text, starts, firstW, contW, h, ratio, spacing, faceClass) {
29820
- const spaceW = 0.5 * h * (ratio / 100) * (1 + spacing / 100);
29821
- let worst = 0;
29822
- for (let i = 0; i + 1 < starts.length; i++) {
29823
- const seg = text.slice(starts[i], starts[i + 1]).replace(/ +$/, "");
29824
- const slack = (i === 0 ? firstW : contW) - measureTextWidth(seg, h, ratio, { faceClass, spacingPct: spacing });
29825
- const spaces = (seg.match(/ /g) ?? []).length;
29826
- worst = Math.max(worst, slack / (Math.max(spaces, 1) * spaceW));
29827
- }
29828
- return worst;
29829
- }
29830
- function fitParagraph(text, font, pt2, firstW, contW, minRatio = 88, orphanRatio = ORPHAN_RATIO) {
29831
- const faceClass = faceClassForGen(font);
29832
- const h = pt2 * 100;
29833
- const k = safety(faceClass, 0.95);
29834
- const f = firstW * k, c = contW * k;
29835
- const ladder = [[100, 0], ...SQUEEZE.filter(([r]) => r >= minRatio)];
29836
- const base = simulateWrap(text, f, c, h, 100, "keep", { faceClass });
29837
- if (base.lines < 2) return null;
29838
- if (base.lastLineWidth <= c * PULL_TAIL) {
29839
- let pull = null;
29840
- for (const [r, sp] of ladder) {
29841
- const amt = squeezeOf(r, sp);
29842
- if (amt > PULL_MAX + 1e-9 || pull && amt >= pull.amt - 1e-9) continue;
29843
- const w = simulateWrap(text, f, c, h, r, "keep", { faceClass, spacingPct: sp });
29844
- if (w.lines < base.lines && worstLooseness(text, w.starts, f, c, h, r, sp, faceClass) <= LOOSE_LIMIT) pull = { r, sp, amt };
29845
- }
29846
- if (pull) return { ratio: pull.r, spacing: pull.sp };
29847
- }
29848
- let baseLines = 0;
29849
- let best = null;
29850
- for (const [r, sp] of ladder) {
29851
- const w = simulateWrap(text, f, c, h, r, "keep", { faceClass, spacingPct: sp });
29852
- if (!best) {
29853
- if (w.lines < 2) return null;
29854
- baseLines = w.lines;
29855
- }
29856
- if (w.lines > baseLines) continue;
29857
- const orphan = w.lines > 1 && w.lastLineWidth <= contW * orphanRatio;
29858
- const loose = worstLooseness(text, w.starts, f, c, h, r, sp, faceClass);
29859
- const cost = Math.max(0, loose - LOOSE_LIMIT) + (orphan ? ORPHAN_COST : 0) + squeezeOf(r, sp) * COMPRESS_COST;
29860
- if (!best || cost < best.cost - 1e-9) best = { r, sp, cost };
29861
- }
29862
- return best && (best.r !== 100 || best.sp !== 0) ? { ratio: best.r, spacing: best.sp } : null;
29863
- }
29864
- var LIST_BREAK = /* @__PURE__ */ new Set([..."\xB7,\u3001/"]);
29865
- function fitCharBreaks(text, font, pt2, firstW, contW, minRatio = 88) {
29866
- const faceClass = faceClassForGen(font);
29867
- const h = pt2 * 100;
29868
- const k = safety(faceClass, 0.95);
29869
- if (!text.split(/ +/).some((w) => measureTextWidth(w, h, 100, { faceClass }) > contW * k)) return null;
29870
- let best = null;
29871
- for (const [r, sp] of [[100, 0], ...SQUEEZE.filter(([r2]) => r2 >= minRatio)]) {
29872
- const amt = squeezeOf(r, sp);
29873
- if (amt > PULL_MAX + 1e-9 || best && amt >= best.amt - 1e-9) continue;
29874
- const wrapAt = (s) => simulateWrap(text, firstW * s, contW * s, h, r, "charAll", { faceClass, spacingPct: sp }).starts;
29875
- const starts = wrapAt(1);
29876
- if (wrapAt(0.99).join() !== starts.join() || wrapAt(1.01).join() !== starts.join()) continue;
29877
- if (!starts.slice(1).every((s) => text[s - 1] === " " || LIST_BREAK.has(text[s - 1]))) continue;
29878
- if (worstLooseness(text, starts, firstW, contW, h, r, sp, faceClass) > LOOSE_LIMIT) continue;
29879
- best = { r, sp, amt };
29880
- }
29881
- return best ? { ratio: best.r, spacing: best.sp } : null;
30603
+ return { title, nodes, hasBoxMarkers, numbered: !!ranks, chapters };
29882
30604
  }
29883
30605
 
29884
30606
  // src/hwpx/gen-marker.ts
@@ -29938,296 +30660,6 @@ function polishGongmunBlock(b) {
29938
30660
  return b;
29939
30661
  }
29940
30662
 
29941
- // src/hwpx/gen-frame-seoul.ts
29942
- var frameTableId = EXTRA_TABLE_ID_BASE + 500;
29943
- function resetFrameTableIds() {
29944
- frameTableId = EXTRA_TABLE_ID_BASE + 500;
29945
- }
29946
- var BF_NONE = 1;
29947
- function ftbl(rows, w, h, cols, opts = {}) {
29948
- return `<hp:tbl id="${++frameTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="${opts.bf ?? BF_NONE}" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="${opts.bottomGap ?? 0}"/><hp:inMargin left="140" right="140" top="140" bottom="140"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
29949
- }
29950
- function host(tableXml2, paraPrId, charPrId, extra = "") {
29951
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"${extra}><hp:run charPrIDRef="${charPrId}">${tableXml2}</hp:run></hp:p>`;
29952
- }
29953
- function scale(cols, w) {
29954
- const sum = cols.reduce((a, b) => a + b, 0);
29955
- const out = cols.map((c) => Math.round(c / sum * w));
29956
- out[out.length - 1] += w - out.reduce((a, b) => a + b, 0);
29957
- return out;
29958
- }
29959
- function spacedOrgName(org) {
29960
- const t = org.trim();
29961
- if (/\s/.test(t) || [...t].length > 6) return t;
29962
- return [...t].join(" ");
29963
- }
29964
- function buildDocHeadTable(h, ctx) {
29965
- const { reg, bf, frame } = ctx;
29966
- const w = ctx.W - 366;
29967
- const [cLabel, cValue, cPad] = scale([6949, 35068, 7223], w);
29968
- const center = reg.para({ align: "CENTER", lineSp: 160 });
29969
- const left = reg.para({ align: "LEFT", lineSp: 160 });
29970
- const c10 = reg.char({ font: frame.font, pt: 10 });
29971
- const c10b = reg.char({ font: frame.font, pt: 10, bold: true });
29972
- const c20b = reg.char({ font: frame.font, pt: 20, bold: true });
29973
- const c14 = reg.char({ font: frame.font, pt: 14 });
29974
- const bottomLine = bf.get({ t: "none", b: "thin", l: "none", r: "none" });
29975
- const rows = [];
29976
- let r = 0;
29977
- if (h.slogan) {
29978
- rows.push(tc({ bf: BF_NONE, row: r++, col: 0, w, h: 2416, colSpan: 3, paras: para(h.slogan, center, c10) }));
29979
- }
29980
- rows.push(
29981
- tc({ bf: BF_NONE, row: r, col: 0, w: cLabel, h: 5608, paras: para("", center, c10) }) + tc({ bf: BF_NONE, row: r, col: 1, w: cValue, h: 5608, paras: para(spacedOrgName(h.org ?? ""), center, c20b) }) + tc({ bf: BF_NONE, row: r, col: 2, w: cPad, h: 5608, paras: para("", center, c10) })
29982
- );
29983
- r++;
29984
- rows.push(tc({ bf: BF_NONE, row: r++, col: 0, w, h: 1563, colSpan: 3, paras: para("", left, c10b) }));
29985
- const labeled = (label, value, height2, bfId) => tc({ bf: bfId, row: r, col: 0, w: cLabel, h: height2, paras: para(label, left, c14) }) + tc({ bf: bfId, row: r, col: 1, w: cValue + cPad, h: height2, colSpan: 2, paras: para(value, left, c14) });
29986
- rows.push(labeled(" \uC218\uC2E0 ", h.to ?? "", 2097, BF_NONE));
29987
- r++;
29988
- rows.push(labeled("(\uACBD\uC720)", h.via ?? "", 2380, BF_NONE));
29989
- r++;
29990
- rows.push(labeled(" \uC81C\uBAA9 ", h.title ?? "", 2248, bottomLine));
29991
- r++;
29992
- const height = rows.length === 6 ? 16312 : 13896;
29993
- return host(ftbl(rows, w, height, 3, { bottomGap: 852 }), reg.para({ align: "LEFT", lineSp: 100 }), c10);
29994
- }
29995
- function splitTitleName(s) {
29996
- const t = s.trim();
29997
- const i = t.lastIndexOf(" ");
29998
- return i > 0 ? { title: t.slice(0, i).trim(), name: t.slice(i + 1).trim() } : { title: "", name: t };
29999
- }
30000
- function buildDocFootTable(f, ctx, internal) {
30001
- const { reg, bf, frame } = ctx;
30002
- const w = ctx.W - 86;
30003
- const GRID = 48;
30004
- const unit = Math.floor(w / GRID);
30005
- const c10 = reg.char({ font: frame.font, pt: 10 });
30006
- const c11b = reg.char({ font: frame.font, pt: 11, bold: true });
30007
- const c9b = reg.char({ font: frame.font, pt: 9, bold: true });
30008
- const c18b = reg.char({ font: frame.font, pt: 18, bold: true });
30009
- const c3 = reg.char({ font: frame.font, pt: 3 });
30010
- const center = reg.para({ align: "CENTER", lineSp: 100 });
30011
- const left = reg.para({ align: "LEFT", lineSp: 100 });
30012
- const gray = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: "#CCCCCC" });
30013
- const spans = (widths) => {
30014
- const sum = widths.reduce((a, b) => a + b, 0);
30015
- const out = widths.map((x) => Math.max(1, Math.round(x / sum * GRID)));
30016
- let diff = GRID - out.reduce((a, b) => a + b, 0);
30017
- for (let i = out.length - 1; diff !== 0 && i >= 0; i = (i - 1 + out.length) % out.length) {
30018
- if (diff > 0) {
30019
- out[i]++;
30020
- diff--;
30021
- } else if (out[i] > 1) {
30022
- out[i]--;
30023
- diff++;
30024
- }
30025
- }
30026
- return out;
30027
- };
30028
- const rows = [];
30029
- let r = 0;
30030
- const row = (h, specs) => {
30031
- let col = 0;
30032
- let xml = "";
30033
- specs.forEach((sp, i) => {
30034
- const last = i === specs.length - 1;
30035
- const span2 = last ? GRID - col : sp.span;
30036
- const cw = last ? w - col * unit : span2 * unit;
30037
- xml += tc({ bf: sp.bf ?? BF_NONE, row: r, col, w: cw, h, colSpan: span2, rowSpan: sp.rowSpan, vAlign: sp.vAlign, paras: para(sp.text, sp.pp ?? left, sp.cp ?? c10) });
30038
- col += span2;
30039
- });
30040
- rows.push(xml);
30041
- r++;
30042
- };
30043
- {
30044
- const [a, b, c] = spans([7981, 32099, 9440]);
30045
- row(4042, [{ span: a, text: "" }, { span: b, text: internal ? "" : f.sender ?? "", cp: c18b, pp: center }, { span: c, text: "" }]);
30046
- }
30047
- if (f.recipients) {
30048
- const [a] = spans([4843, 44677]);
30049
- row(2880, [{ span: a, text: "\uC218\uC2E0\uC790", vAlign: "TOP" }, { span: GRID - a, text: f.recipients, vAlign: "TOP" }]);
30050
- } else {
30051
- row(1280, [{ span: GRID, text: "" }]);
30052
- }
30053
- row(1146, [{ span: GRID, text: "", cp: c3, pp: center, bf: gray }]);
30054
- {
30055
- const n = Math.max(f.approvers.length, 1);
30056
- const unitW = [6909, 4625, 983];
30057
- const titleW = (t) => Math.max(unitW[0], Math.round(measureTextWidth(t, 1e3, 100, { faceClass: "fixedPitch" })) + 280 + unit + 300);
30058
- const widths = [];
30059
- for (let i = 0; i < n; i++) {
30060
- widths.push(titleW(f.approvers[i]?.title ?? ""), unitW[1]);
30061
- if (i < n - 1) widths.push(unitW[2]);
30062
- }
30063
- const used = widths.reduce((a, b) => a + b, 0);
30064
- if (used < w) widths.push(w - used);
30065
- const sp = spans(widths);
30066
- const top = [], bottom = [];
30067
- let k = 0;
30068
- for (let i = 0; i < n; i++) {
30069
- const ap = f.approvers[i] ?? { title: "", name: "" };
30070
- top.push({ span: sp[k++], text: ap.title, pp: center, rowSpan: 2 });
30071
- const signSpan = sp[k++];
30072
- top.push({ span: signSpan, text: ap.date ?? "", cp: c9b, pp: center });
30073
- bottom.push({ span: signSpan, text: ap.name, cp: c11b, pp: center });
30074
- if (i < n - 1) top.push({ span: sp[k++], text: "", rowSpan: 2 });
30075
- }
30076
- if (k < sp.length) top.push({ span: sp[k], text: "", rowSpan: 2 });
30077
- let col = 0, xmlTop = "", xmlBottom = "";
30078
- let bi = 0;
30079
- top.forEach((s, i) => {
30080
- const last = i === top.length - 1;
30081
- const span2 = last ? GRID - col : s.span;
30082
- const cw = last ? w - col * unit : span2 * unit;
30083
- xmlTop += tc({ bf: BF_NONE, row: r, col, w: cw, h: s.rowSpan ? 2198 : 1100, colSpan: span2, rowSpan: s.rowSpan, paras: para(s.text, s.pp ?? left, s.cp ?? c10) });
30084
- if (!s.rowSpan) {
30085
- const b = bottom[bi++];
30086
- xmlBottom += tc({ bf: BF_NONE, row: r + 1, col, w: cw, h: 1098, colSpan: span2, paras: para(b.text, b.pp ?? center, b.cp ?? c11b) });
30087
- }
30088
- col += span2;
30089
- });
30090
- rows.push(xmlTop, xmlBottom);
30091
- r += 2;
30092
- }
30093
- {
30094
- const [a] = spans([4455, 45065]);
30095
- const coop = f.cooperators.map((c) => `${c.title} ${c.name}`.trim()).join(" ");
30096
- row(2700, [{ span: a, text: "\uD611\uC870\uC790" }, { span: GRID - a, text: coop }]);
30097
- }
30098
- row(1400, [{ span: GRID, text: "" }]);
30099
- {
30100
- const [a, b, c] = spans([3340, 21532, 3301, 21347]);
30101
- row(2280, [{ span: a, text: "\uC2DC\uD589" }, { span: b, text: f.docNum ?? "" }, { span: c, text: "\uC811\uC218" }, { span: 0, text: f.receive ?? "" }]);
30102
- }
30103
- {
30104
- const [a, b, c, d] = spans([1597, 3902, 21934, 761, 21326]);
30105
- row(1280, [{ span: a, text: "\uC6B0" }, { span: b, text: f.zip ?? "" }, { span: c, text: f.address ?? "" }, { span: d, text: "/" }, { span: 0, text: f.site ?? "" }]);
30106
- }
30107
- {
30108
- const sp = spans([2750, 8006, 3196, 8311, 899, 14327, 792, 11239]);
30109
- const vals = ["\uC804\uD654", f.phone ?? "", "/\uC804\uC1A1", f.fax ?? "", "/", f.email ?? "", "/", f.disclosure ?? ""];
30110
- row(2e3, vals.map((v, i) => ({ span: sp[i], text: v })));
30111
- }
30112
- row(1e3, [{ span: GRID, text: "" }]);
30113
- const height = rows.length * 1500;
30114
- return host(ftbl(rows, w, height, GRID), reg.para({ align: "LEFT", lineSp: 100 }), c10);
30115
- }
30116
- function buildReportTitleTable(title, contact, ctx) {
30117
- const { reg, bf, frame } = ctx;
30118
- const w = ctx.W - 566;
30119
- const fit = fitOneLine(title, frame.titleFont, frame.titlePt, w - 2 * 140 - 400, 20);
30120
- const cTitle = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
30121
- const center = reg.para({ align: "CENTER", lineSp: 130 });
30122
- const rows = [];
30123
- const titleBf = bf.get({ t: "thick", b: contact ? "mid" : "thick", l: "none", r: "none" });
30124
- rows.push(tc({ bf: titleBf, row: 0, col: 0, w, h: 4084, paras: para(title, center, cTitle), name: "__kordoc_h1" }));
30125
- let h = 4084;
30126
- if (contact) {
30127
- const cContact = reg.char({ font: frame.contactFont, pt: frame.contactPt });
30128
- rows.push(tc({ bf: bf.get({ t: "mid", b: "thick", l: "none", r: "none" }), row: 1, col: 0, w, h: 1943, paras: para(contact, center, cContact) }));
30129
- h += 1943;
30130
- }
30131
- return { xml: host(ftbl(rows, w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), cTitle), overflow: fit.overflow };
30132
- }
30133
- var CHAPTER_BAND_DEFAULT = { color: "#003366", textColor: "#FFFFFF", numFont: "HY\uD5E4\uB4DC\uB77C\uC778M", numPt: 17 };
30134
- function buildChapterBand(roman, title, ctx, spec, before2) {
30135
- const { reg, bf } = ctx;
30136
- const w = ctx.W;
30137
- const numW = 3100, gap = 400, h = 2600;
30138
- const titleW = w - numW - gap;
30139
- const fit = fitOneLine(title, spec.titleFont, spec.titlePt, titleW - 700, spec.titlePt - 3);
30140
- const numBf = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: spec.color });
30141
- const gapBf = bf.get({ t: "none", b: "none", l: "none", r: "none" });
30142
- const titleBf = bf.get({ t: "none", b: "thick", l: "none", r: "none" });
30143
- const cNum = reg.char({ font: spec.numFont, pt: spec.numPt, bold: true, color: spec.textColor });
30144
- const cTitle = reg.char({ font: spec.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
30145
- const center = reg.para({ align: "CENTER", lineSp: 130 });
30146
- const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
30147
- const row = tc({ bf: numBf, row: 0, col: 0, w: numW, h, paras: para(roman, center, cNum), vAlign: "CENTER" }) + tc({ bf: gapBf, row: 0, col: 1, w: gap, h, paras: para("", center, cNum), vAlign: "CENTER" }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h, paras: para(title, left, cTitle), name: "__kordoc_h2", vAlign: "CENTER" });
30148
- return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cTitle), overflow: fit.overflow };
30149
- }
30150
- var SUMMARY_PAD = 1e3;
30151
- function buildSummaryBox(text, ctx) {
30152
- const { reg, bf, frame } = ctx;
30153
- const w = ctx.W - 566;
30154
- const avail = w - 280 - SUMMARY_PAD * 2;
30155
- const p = reg.para({ align: "JUSTIFY", lineSp: 160, left: SUMMARY_PAD, right: SUMMARY_PAD });
30156
- const bodies = text.split("\n").map((l) => l.trim().replace(/^[□■○ㅇ◦●\-–ㆍ·•]\s*/u, "")).filter(Boolean);
30157
- let lines = 0;
30158
- const paras = bodies.map((b) => {
30159
- const f = fitParagraph(b, frame.summaryFont, frame.summaryPt, avail, avail);
30160
- const c = reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 });
30161
- lines += simulateWrap(b, avail * 0.995, avail * 0.995, frame.summaryPt * 100, f?.ratio ?? 100, "keep", { faceClass: faceClassForGen(frame.summaryFont), spacingPct: f?.spacing ?? 0 }).lines;
30162
- return para(b, p, c);
30163
- });
30164
- const h = lines * Math.round(frame.summaryPt * 100 * 1.6) + 280 + 600;
30165
- const box = bf.get({ t: "thick", b: "thick", l: "thick", r: "thick", fill: frame.summaryFill });
30166
- const row = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), name: "__kordoc_summary" });
30167
- return { xml: host(ftbl([row], w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true })), lines };
30168
- }
30169
- function buildApprovalSeoul(labels, names, ctx) {
30170
- const { reg, bf } = ctx;
30171
- const n = labels.length;
30172
- const colW = Math.min(8600, Math.floor(ctx.W * 0.55 / n));
30173
- const w = colW * n;
30174
- const c12 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12 });
30175
- const c12b = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12, bold: true });
30176
- const center = reg.para({ align: "CENTER", lineSp: 100 });
30177
- const edge = (row, col) => bf.get({
30178
- t: row === 0 ? "thick" : "thin",
30179
- b: row === 1 ? "thick" : "thin",
30180
- l: col === 0 ? "thick" : "thin",
30181
- r: col === n - 1 ? "thick" : "thin"
30182
- });
30183
- const top = labels.map((l, c) => tc({ bf: edge(0, c), row: 0, col: c, w: colW, h: 1765, paras: para(l, center, c12) })).join("");
30184
- const sign = labels.map((_, c) => tc({ bf: edge(1, c), row: 1, col: c, w: colW, h: 4200, paras: para(names?.[c] ?? "", center, c12b) })).join("");
30185
- return host(ftbl([top, sign], w, 5965, n, { bottomGap: 600 }), reg.para({ align: "RIGHT", lineSp: 100 }), c12);
30186
- }
30187
- function buildReportCover(inp, ctx) {
30188
- const { reg, bf, frame } = ctx;
30189
- const out = [];
30190
- const c12 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12 });
30191
- const c12b = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12, bold: true });
30192
- const center = reg.para({ align: "CENTER", lineSp: 100 });
30193
- const blank2 = (pt2 = 15) => para("", reg.para({ align: "LEFT", lineSp: 160 }), reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: pt2 }));
30194
- const infoRows = [["\uBB38\uC11C\uBC88\uD638", inp.docInfo?.docNum ?? ""], ["\uACB0\uC7AC\uC77C\uC790", inp.docInfo?.date ?? ""], ["\uACF5\uAC1C\uC5EC\uBD80", inp.docInfo?.disclosure ?? ""], ["\uBC29\uCE68\uBC88\uD638", inp.docInfo?.policyNo ?? ""]];
30195
- const infoW = Math.round(ctx.W * 0.36);
30196
- const [lw, vw] = scale([4200, 12800], infoW);
30197
- const infoXml = ftbl(infoRows.map(
30198
- ([l, v], r) => tc({ bf: bf.get({ t: r === 0 ? "thick" : "thin", b: r === 3 ? "thick" : "thin", l: "thick", r: "thin" }), row: r, col: 0, w: lw, h: 1850, paras: para(l, center, c12b) }) + tc({ bf: bf.get({ t: r === 0 ? "thick" : "thin", b: r === 3 ? "thick" : "thin", l: "thin", r: "thick" }), row: r, col: 1, w: vw, h: 1850, paras: para(v, center, c12) })
30199
- ), infoW, 1850 * 4, 2);
30200
- let line = infoXml;
30201
- if (inp.approval && inp.approval.length) {
30202
- const n = inp.approval.length;
30203
- const colW = Math.min(7600, Math.floor(ctx.W * 0.5 / n));
30204
- const edge = (row, col) => bf.get({ t: row === 0 ? "thick" : "thin", b: row === 1 ? "thick" : "thin", l: col === 0 ? "thick" : "thin", r: col === n - 1 ? "thick" : "thin" });
30205
- const top = inp.approval.map((l, c) => tc({ bf: edge(0, c), row: 0, col: c, w: colW, h: 1765, paras: para(l, center, c12) })).join("");
30206
- const sign = inp.approval.map((_, c) => tc({ bf: edge(1, c), row: 1, col: c, w: colW, h: 3600, paras: para("", center, c12b) })).join("");
30207
- line += `</hp:run><hp:run charPrIDRef="${c12}"><hp:t> </hp:t></hp:run><hp:run charPrIDRef="${c12}">` + ftbl([top, sign], colW * n, 5365, n);
30208
- }
30209
- out.push(`<hp:p paraPrIDRef="${reg.para({ align: "DISTRIBUTE", lineSp: 100 })}" styleIDRef="0"><hp:run charPrIDRef="${c12}">${line}</hp:run></hp:p>`);
30210
- for (let i = 0; i < 4; i++) out.push(blank2(20));
30211
- const w = ctx.W - 1200;
30212
- const bar = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: "#1F2FD6" });
30213
- const fit = fitOneLine(inp.title, frame.titleFont, 27, w - 800, 22);
30214
- const cT = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
30215
- const c1 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 1 });
30216
- const barP = reg.para({ align: "CENTER", lineSp: 70 });
30217
- const rows = [
30218
- tc({ bf: bar, row: 0, col: 0, w, h: 200, paras: para("", barP, c1) }),
30219
- tc({ bf: BF_NONE, row: 1, col: 0, w, h: 6e3, paras: para(inp.title, center, cT), name: "__kordoc_skip" }),
30220
- tc({ bf: bar, row: 2, col: 0, w, h: 200, paras: para("", barP, c1) })
30221
- ];
30222
- out.push(host(ftbl(rows, w, 6400, 1), reg.para({ align: "CENTER", lineSp: 100 }), cT));
30223
- for (let i = 0; i < 2; i++) out.push(blank2(20));
30224
- out.push(para(inp.date, center, reg.char({ font: frame.titleFont, pt: 22, bold: true })));
30225
- for (let i = 0; i < 8; i++) out.push(blank2(20));
30226
- if (inp.org) out.push(para(spacedOrgName(inp.org), center, reg.char({ font: frame.titleFont, pt: 24, bold: true })));
30227
- if (inp.dept) out.push(para(`(${inp.dept.replace(/^\(|\)$/g, "")})`, center, reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 22, bold: true })));
30228
- return out;
30229
- }
30230
-
30231
30663
  // src/hwpx/gen-frame-ministry.ts
30232
30664
  var MINISTRY = {
30233
30665
  blue: "#0000FF",
@@ -30447,7 +30879,7 @@ function isAttachHeading(text) {
30447
30879
  }
30448
30880
 
30449
30881
  // src/hwpx/gen-gongmun.ts
30450
- var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes", "ministry"]);
30882
+ var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes", "ministry", "bangchim"]);
30451
30883
  function usesV5Engine(preset) {
30452
30884
  return V5_PRESETS.has(preset);
30453
30885
  }
@@ -30465,21 +30897,37 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30465
30897
  const warnings = [];
30466
30898
  const g3 = gongmun;
30467
30899
  const preset = g3.preset;
30468
- const isReport = preset === "report" || preset === "plan";
30900
+ const isBangchim = preset === "bangchim";
30901
+ const isReport = preset === "report" || preset === "plan" || isBangchim;
30469
30902
  const isMinistry = preset === "ministry";
30470
- const W = mmToHwpunit(210 - g3.margins.left - g3.margins.right);
30471
- const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
30472
- const scheme = pickScheme(g3, pre.hasBoxMarkers);
30903
+ const W2 = mmToHwpunit(210 - g3.margins.left - g3.margins.right);
30904
+ let bangchimSub = null, bangchimContact = null;
30905
+ if (isBangchim) {
30906
+ const h1 = blocks.findIndex((b) => b.type === "heading" && (b.level ?? 1) === 1);
30907
+ let k = h1 + 1;
30908
+ while (h1 >= 0 && k < blocks.length && k <= h1 + 2) {
30909
+ const b = blocks[k], t = (b.text ?? "").trim();
30910
+ if (b.type !== "paragraph" && b.type !== "list_item") break;
30911
+ const sub = b.type === "list_item" ? `- ${t}` : t;
30912
+ if (!bangchimSub && /^-\s*\S.*\s-$/.test(sub) && sub.length < 60) bangchimSub = sub;
30913
+ else if (!bangchimContact && /☎/.test(t)) bangchimContact = t;
30914
+ else break;
30915
+ k++;
30916
+ }
30917
+ if (k > h1 + 1) blocks = [...blocks.slice(0, h1 + 1), ...blocks.slice(k)];
30918
+ }
30919
+ const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport, ...isBangchim ? { headingFrames: true, keepMarkers: true, numbered: true } : {} });
30920
+ const scheme = pickScheme(g3, pre.hasBoxMarkers, pre.numbered);
30473
30921
  const gaejosik = scheme.kind === "gaejosik";
30474
30922
  const raw = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
30475
30923
  const outline = { ...raw, title: raw.title && polishGongmunText(raw.title), nodes: raw.nodes.map(polishNode) };
30476
- const frame = { reg, bf: bfReg, frame: scheme.frame, W };
30924
+ const frame = { reg, bf: bfReg, frame: scheme.frame, W: W2 };
30477
30925
  const lineHu = (st) => Math.round(st.pt * 100 * ((st.lineSp ?? scheme.lineSp) / 100));
30478
30926
  const paras = [];
30479
30927
  let pendingPageBreak = false;
30480
30928
  const tblCharPr = reg.char({ font: scheme.table.font, pt: scheme.table.pt });
30481
30929
  const tableStyle = {
30482
- totalWidth: W,
30930
+ totalWidth: W2,
30483
30931
  charPr: tblCharPr,
30484
30932
  boldCharPr: reg.char({ font: scheme.table.font, pt: scheme.table.pt, bold: true }),
30485
30933
  charHeight: scheme.table.pt * 100,
@@ -30496,7 +30944,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30496
30944
  let frontKind = "start";
30497
30945
  const pushSummary = (t) => {
30498
30946
  frontKind = "summary";
30499
- const box = buildSummaryBox(polishGongmunText(t), frame);
30947
+ const box = (isBangchim ? buildBangchimSummary : buildSummaryBox)(polishGongmunText(t), frame);
30500
30948
  if (box.lines > 3) warnings.push(`\uC694\uC57D\uBC15\uC2A4\uAC00 ${box.lines}\uC904\uC785\uB2C8\uB2E4 \u2014 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uC904\uC774\uC138\uC694`);
30501
30949
  paras.push(box.xml);
30502
30950
  };
@@ -30543,8 +30991,23 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30543
30991
  } else if (g3.approval) {
30544
30992
  paras.push(buildApprovalSeoul(g3.approval, null, frame));
30545
30993
  }
30994
+ if (g3.checklist) {
30995
+ let xml = buildSeoulChecklist(g3.checklist, frame);
30996
+ if (g3.pageNumbers) xml = xml.replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
30997
+ paras.push(pendingPageBreak ? xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `) : xml);
30998
+ pendingPageBreak = true;
30999
+ }
31000
+ if (isBangchim && g3.toc && outline.chapters > 0) {
31001
+ const chs = outline.nodes.filter((n) => n.kind === "chapter").map((n) => ({ label: n.label ?? chapterLabel(n.index, "roman").replace(/\.$/, ""), title: n.label ? plain2(n.text).replace(CHAPTER_LABEL_RE, "") : plain2(n.text) }));
31002
+ const toc = buildBangchimToc(chs, frame);
31003
+ if (g3.pageNumbers) toc[0] = toc[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
31004
+ if (pendingPageBreak) toc[0] = toc[0].replace(/^<hp:p /, `<hp:p pageBreak="1" `);
31005
+ paras.push(...toc);
31006
+ warnings.push("\uBAA9\uCC28 \uCABD\uBC88\uD638 \uCE78\uC740 \uBE44\uC6CC \uB461\uB2C8\uB2E4 \u2014 \uCABD\uC740 \uC870\uD310 \uB4A4\uC5D0 \uC815\uD574\uC9C0\uBBC0\uB85C \uD55C\uAE00\uC5D0\uC11C \uCC44\uC6B0\uC138\uC694");
31007
+ pendingPageBreak = true;
31008
+ }
30546
31009
  if (docTitle) {
30547
- const t = buildReportTitleTable(docTitle, reportInfo, frame);
31010
+ const t = isBangchim ? buildBangchimTitleTable(docTitle, bangchimSub, reportInfo ?? bangchimContact, frame) : buildReportTitleTable(docTitle, reportInfo, frame);
30548
31011
  if (t.overflow) warnings.push(`\uC81C\uBAA9\uC774 \uAE38\uC5B4 \uD55C \uC904\uC5D0 \uB2F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(20pt\xB7\uC7A5\uD3C9 85%\uAE4C\uC9C0 \uCD95\uC18C) \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${docTitle.slice(0, 30)}\u2026"`);
30549
31012
  if (pendingPageBreak) {
30550
31013
  paras.push(t.xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `).replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${newPageNumCtrl(1)}`));
@@ -30573,7 +31036,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30573
31036
  let tableSeq = 0;
30574
31037
  let sectionSeq = 0, subheadSeq = 0, itemBandSeq = 0;
30575
31038
  let pendingNewPageNum = isMinistry && (!!g3.cover || !!g3.toc);
30576
- let chapterStyle = g3.h2Marker === "box" || g3.h2Marker === "number" || g3.h2Marker === "none" || g3.h2Marker === "band" ? g3.h2Marker : "roman";
31039
+ let chapterStyle = g3.h2Marker === "box" || g3.h2Marker === "number" || g3.h2Marker === "none" || g3.h2Marker === "band" || g3.h2Marker === "square" ? g3.h2Marker : "roman";
30577
31040
  if (!gaejosik) chapterStyle = "number";
30578
31041
  const leadLeft = (depth, pt2) => (scheme.levels[Math.min(depth, 7)]?.leadTa ?? 0) * taHu(pt2);
30579
31042
  const renderItem = (node, styleId = 0) => {
@@ -30591,9 +31054,9 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30591
31054
  marker = numberer.next(depth, sup);
30592
31055
  itemSeq++;
30593
31056
  }
30594
- const mk = isMinistry && node.marker ? node.marker : marker;
31057
+ const mk = (isMinistry || isBangchim) && node.marker ? node.marker : marker;
30595
31058
  const lay = markerLayout(st.font, st.pt, st.leadTa, mk);
30596
- const textW2 = W - lay.left - lay.hang;
31059
+ const textW2 = W2 - lay.left - lay.hang;
30597
31060
  let ratio = 100, spacing = 0, charBreaks = false;
30598
31061
  if (st.oneLine) {
30599
31062
  const fit = fitOneLine(plain2(node.text), st.font, st.pt, textW2, st.pt, 90);
@@ -30614,10 +31077,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30614
31077
  }
30615
31078
  const base = { font: st.font, pt: st.pt, bold: st.bold, ratio, spacing };
30616
31079
  const consecutiveBox = prevKind === "item" && prevItemDepth === 0 && depth === 0;
30617
- const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
31080
+ const afterBand = depth === 0 && (prevKind === "chapter" && (chapterStyle === "band" || chapterStyle === "square") || prevKind === "title" && !!st.blankBefore);
30618
31081
  const before2 = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
30619
31082
  const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
30620
- const paraSpec = { align: "JUSTIFY", left: lay.left, indent: -lay.hang, before: before2, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
31083
+ const paraSpec = { align: st.align ?? "JUSTIFY", left: lay.left, indent: -lay.hang, before: before2, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
30621
31084
  const markerRun = mk ? markerRunXml(mk, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay) : "";
30622
31085
  if (isMinistry) {
30623
31086
  const paraId2 = reg.para({ ...paraSpec, after: 600 });
@@ -30632,12 +31095,12 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30632
31095
  const marker = isMinistry ? "*" : "\u203B";
30633
31096
  const lay = markerLayout(st.font, st.pt, 0, marker);
30634
31097
  const left = isMinistry ? st.leadTa * taHu(st.pt) : leadLeft(node.depth, st.pt);
30635
- const textW2 = W - left - lay.hang;
31098
+ const textW2 = W2 - left - lay.hang;
30636
31099
  const t = plain2(node.text);
30637
31100
  const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g3.autoFitMinRatio ?? 101);
30638
31101
  const f = cb ?? (g3.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g3.autoFitMinRatio) : null);
30639
31102
  const base = { font: st.font, pt: st.pt, bold: st.bold, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 };
30640
- const paraId = reg.para({ align: "JUSTIFY", left, indent: -lay.hang, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: !cb, autoTab: true, widowOrphan: true });
31103
+ const paraId = reg.para({ align: st.align ?? "JUSTIFY", left, indent: -lay.hang, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: !cb, autoTab: true, widowOrphan: true });
30641
31104
  const markerRun = markerRunXml(marker, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay);
30642
31105
  return `<hp:p paraPrIDRef="${paraId}" styleIDRef="0">${markerRun}${generateRuns(node.text, reg.char(base), inlineMapper(reg, base))}</hp:p>`;
30643
31106
  };
@@ -30653,11 +31116,19 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30653
31116
  if (pics.every(Boolean)) return `<hp:p paraPrIDRef="${reg.para({ align: "CENTER", lineSp: scheme.lineSp })}" styleIDRef="0"><hp:run charPrIDRef="${reg.char({ font: st.font, pt: st.pt })}">${pics.join("")}</hp:run></hp:p>`;
30654
31117
  }
30655
31118
  }
31119
+ if (isBangchim && !node.align) {
31120
+ const t = plain2(node.text);
31121
+ const cap = /^<\s*[^<>]{2,60}\s*>$/.test(t) ? { font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 13, bold: true, align: "CENTER" } : /^【[^】]{1,30}】/.test(t) ? { font: "HY\uD5E4\uB4DC\uB77C\uC778M", pt: 15, bold: false, align: "LEFT" } : null;
31122
+ if (cap) {
31123
+ const base2 = { font: cap.font, pt: cap.pt, bold: cap.bold };
31124
+ return generateParagraph(node.text, reg.para({ align: cap.align, lineSp: scheme.lineSp, keepWithNext: true }), reg.char(base2), inlineMapper(reg, base2));
31125
+ }
31126
+ }
30656
31127
  let ratio = 100, spacing = 0, charBreaks = false;
30657
31128
  if (!node.align) {
30658
31129
  const t = plain2(node.text);
30659
- const cb = fitCharBreaks(t, st.font, st.pt, W, W, g3.autoFitMinRatio ?? 101);
30660
- const f = cb ?? (g3.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, W, W, g3.autoFitMinRatio) : null);
31130
+ const cb = fitCharBreaks(t, st.font, st.pt, W2, W2, g3.autoFitMinRatio ?? 101);
31131
+ const f = cb ?? (g3.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, W2, W2, g3.autoFitMinRatio) : null);
30661
31132
  if (f) {
30662
31133
  ratio = f.ratio;
30663
31134
  spacing = f.spacing;
@@ -30682,6 +31153,15 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30682
31153
  })();
30683
31154
  return first ? xml : xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
30684
31155
  }
31156
+ if (chapterStyle === "square") {
31157
+ sectionSeq = 0;
31158
+ subheadSeq = 0;
31159
+ const before3 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
31160
+ const title = node.label ? plain2(node.text).replace(CHAPTER_LABEL_RE, "") : plain2(node.text);
31161
+ const box = buildSquareChapter(node.label ?? chapterLabel(node.index, "roman").replace(/\.$/, ""), title, frame, st, before3);
31162
+ if (box.overflow) warnings.push(`\uC7A5 \uC81C\uBAA9\uC774 \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uCD95\uC18C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${node.text.slice(0, 30)}\u2026"`);
31163
+ return box.xml;
31164
+ }
30685
31165
  if (chapterStyle === "band") {
30686
31166
  const before3 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
30687
31167
  const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g3.bandColor, textColor: g3.bandTextColor, titleFont: st.font, titlePt: st.pt }, before3);
@@ -30690,7 +31170,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30690
31170
  }
30691
31171
  const label = chapterStyle === "none" ? "" : chapterLabel(node.index, chapterStyle === "number" ? "number" : "roman");
30692
31172
  const text = label ? `${label} ${node.text}` : node.text;
30693
- const fit = fitOneLine(plain2(text), st.font, st.pt, W, st.pt - 2);
31173
+ const fit = fitOneLine(plain2(text), st.font, st.pt, W2, st.pt - 2);
30694
31174
  const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
30695
31175
  const before2 = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
30696
31176
  const paraId = reg.para({ align: "LEFT", before: before2, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true });
@@ -30712,7 +31192,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30712
31192
  if (!b.rows) return "";
30713
31193
  let style = tableStyle;
30714
31194
  const face = faceClassForGen(scheme.table.font);
30715
- const avail = W - DATA_TABLE_INSET;
31195
+ const avail = W2 - DATA_TABLE_INSET;
30716
31196
  for (const pt2 of [scheme.table.pt, scheme.table.pt - 1, scheme.table.pt - 2]) {
30717
31197
  if (pt2 < 9) break;
30718
31198
  if (requiredTableWidth(b.rows, pt2 * 100, face) <= avail || pt2 === scheme.table.pt - 2) {
@@ -30771,6 +31251,16 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
30771
31251
  break;
30772
31252
  case "heading": {
30773
31253
  const before2 = prevKind === "chapter" || prevKind === "heading" || prevKind === "start" ? 0 : BAND_BEFORE_HU;
31254
+ if (isBangchim) {
31255
+ if (node.level <= 3) {
31256
+ const band = buildBangchimSectionBand(++sectionSeq, plain2(node.text), frame, before2);
31257
+ if (band.overflow) warnings.push(`\uC808 \uC81C\uBAA9\uC774 \uB760 \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uCD95\uC18C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${node.text.slice(0, 30)}\u2026"`);
31258
+ xml = band.xml;
31259
+ } else xml = buildBangchimSubhead(++subheadSeq, node.text, frame, prevKind === "heading" ? 0 : Math.round(before2 / 2));
31260
+ lastItemFromHeading = true;
31261
+ lastTextNode = node;
31262
+ break;
31263
+ }
30774
31264
  if (node.level <= 3) {
30775
31265
  subheadSeq = 0;
30776
31266
  itemBandSeq = 0;
@@ -39927,14 +40417,14 @@ async function renderDocument(input, options) {
39927
40417
 
39928
40418
  // src/render/regions.ts
39929
40419
  function cropRect(bbox, scale2, pageWidthPx, pageHeightPx, paddingPt = 0) {
39930
- const W = Math.max(1, Math.floor(pageWidthPx)), H = Math.max(1, Math.floor(pageHeightPx));
40420
+ const W2 = Math.max(1, Math.floor(pageWidthPx)), H = Math.max(1, Math.floor(pageHeightPx));
39931
40421
  const clamp = (v, hi) => Math.min(Math.max(0, v), hi);
39932
- let left = clamp(Math.floor((bbox.x - paddingPt) * scale2), W - 1);
40422
+ let left = clamp(Math.floor((bbox.x - paddingPt) * scale2), W2 - 1);
39933
40423
  let top = clamp(Math.floor((bbox.y - paddingPt) * scale2), H - 1);
39934
- const right = clamp(Math.ceil((bbox.x + bbox.width + paddingPt) * scale2), W);
40424
+ const right = clamp(Math.ceil((bbox.x + bbox.width + paddingPt) * scale2), W2);
39935
40425
  const bottom = clamp(Math.ceil((bbox.y + bbox.height + paddingPt) * scale2), H);
39936
40426
  const width = Math.max(1, right - left), height = Math.max(1, bottom - top);
39937
- if (left + width > W) left = W - width;
40427
+ if (left + width > W2) left = W2 - width;
39938
40428
  if (top + height > H) top = H - height;
39939
40429
  return { left, top, width, height };
39940
40430
  }
@@ -40110,10 +40600,14 @@ async function dispatch(format2, buffer, opts) {
40110
40600
  return { success: false, fileType: "unknown", error: "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4.", code: "UNSUPPORTED_FORMAT" };
40111
40601
  }
40112
40602
  }
40603
+ function scriptsOff(r, off) {
40604
+ return off ? stripScriptTags(r) : r;
40605
+ }
40113
40606
  async function parseImage(buffer, options) {
40114
40607
  try {
40115
- const { parseImageDocument } = await import("./image-ocr-YA2MQBRP.js");
40608
+ const { parseImageDocument } = await import("./image-ocr-5BUJQ4TK.js");
40116
40609
  const { blocks, warnings } = await parseImageDocument(buffer, options);
40610
+ stripScriptTags({ blocks });
40117
40611
  return {
40118
40612
  success: true,
40119
40613
  fileType: "image",
@@ -40137,7 +40631,7 @@ async function parseHwp3(buffer, options) {
40137
40631
  async function parseHwpx(buffer, options) {
40138
40632
  try {
40139
40633
  const { markdown, blocks, metadata, outline, warnings, images } = await parseHwpxDocument(buffer, options);
40140
- return { success: true, fileType: "hwpx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount };
40634
+ return scriptsOff({ success: true, fileType: "hwpx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount }, options?.scriptTags === false);
40141
40635
  } catch (err) {
40142
40636
  return { success: false, fileType: "hwpx", error: sanitizeError(err), code: classifyError(err) };
40143
40637
  }
@@ -40163,7 +40657,7 @@ async function parseHwp(buffer, options) {
40163
40657
  } catch {
40164
40658
  }
40165
40659
  }
40166
- return { success: true, fileType: "hwp", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount };
40660
+ return scriptsOff({ success: true, fileType: "hwp", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount }, options?.scriptTags === false);
40167
40661
  } catch (err) {
40168
40662
  return { success: false, fileType: "hwp", error: sanitizeError(err), code: classifyError(err) };
40169
40663
  }
@@ -40171,7 +40665,7 @@ async function parseHwp(buffer, options) {
40171
40665
  async function parsePdf(buffer, options) {
40172
40666
  let parsePdfDocument;
40173
40667
  try {
40174
- const mod = await import("./parser-PXATL5DM.js");
40668
+ const mod = await import("./parser-ZQM3664Z.js");
40175
40669
  parsePdfDocument = mod.parsePdfDocument;
40176
40670
  } catch {
40177
40671
  return {
@@ -40183,7 +40677,7 @@ async function parsePdf(buffer, options) {
40183
40677
  }
40184
40678
  try {
40185
40679
  const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images, pages } = await parsePdfDocument(buffer, options);
40186
- return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images, pages, pageCount: metadata?.pageCount };
40680
+ return scriptsOff({ success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images, pages, pageCount: metadata?.pageCount }, options?.scriptTags !== true);
40187
40681
  } catch (err) {
40188
40682
  const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
40189
40683
  return { success: false, fileType: "pdf", error: sanitizeError(err), code: classifyError(err), isImageBased };
@@ -40208,7 +40702,7 @@ async function parseXls(buffer, options) {
40208
40702
  async function parseDocx(buffer, options) {
40209
40703
  try {
40210
40704
  const { markdown, blocks, metadata, outline, warnings, images } = await parseDocxDocument(buffer, options);
40211
- return { success: true, fileType: "docx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount };
40705
+ return scriptsOff({ success: true, fileType: "docx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0, pageCount: metadata?.pageCount }, options?.scriptTags === false);
40212
40706
  } catch (err) {
40213
40707
  return { success: false, fileType: "docx", error: sanitizeError(err), code: classifyError(err) };
40214
40708
  }