kordoc 4.13.1 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +7 -2
  2. package/dist/{-S3PXW4LR.js → -SLX6IB6Y.js} +9 -9
  3. package/dist/{chunk-K3KLZZC6.js → chunk-3U3PJGVO.js} +2 -2
  4. package/dist/{chunk-IRIASTA5.js → chunk-6T6MB5ON.js} +2 -2
  5. package/dist/{chunk-2TEH7CC5.js → chunk-7VUHZFGQ.js} +2 -2
  6. package/dist/{chunk-D6S64TR6.js → chunk-A2JDWBKG.js} +2 -2
  7. package/dist/{chunk-LBWJOJ2T.cjs → chunk-ANKDK5DF.cjs} +2 -2
  8. package/dist/chunk-ANKDK5DF.cjs.map +1 -0
  9. package/dist/{chunk-TS665MCV.cjs → chunk-B7MOYHVR.cjs} +6 -6
  10. package/dist/chunk-B7MOYHVR.cjs.map +1 -0
  11. package/dist/{chunk-FSEJ6OJO.js → chunk-CDQJKMZ5.js} +2 -2
  12. package/dist/chunk-COUGOFT3.cjs.map +1 -1
  13. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  14. package/dist/{chunk-F4DZU7VP.js → chunk-EMQZX5QQ.js} +474 -68
  15. package/dist/chunk-EMQZX5QQ.js.map +1 -0
  16. package/dist/{chunk-VRXVNVLQ.js → chunk-EOTAPX2M.js} +2 -2
  17. package/dist/{chunk-SA2ECW7B.js → chunk-GMX6EKCY.js} +2 -2
  18. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  19. package/dist/{chunk-QOJTBOWX.js → chunk-HCHA3Y2T.js} +2 -2
  20. package/dist/{chunk-WOAVPYT3.js → chunk-HH7T6SMB.js} +8 -7
  21. package/dist/chunk-HH7T6SMB.js.map +1 -0
  22. package/dist/{chunk-ABGDU4KM.js → chunk-J3SM7VLM.js} +2 -2
  23. package/dist/{chunk-Q4DUG3WU.js → chunk-JIQWI7S2.js} +2 -2
  24. package/dist/{chunk-R6NY4ZIS.js → chunk-LCGAO5LF.js} +3 -3
  25. package/dist/{chunk-SHC6FGRY.js → chunk-M5NRJMA4.js} +3 -3
  26. package/dist/{chunk-QFXJAYA4.js → chunk-PLXNW3J4.js} +2 -2
  27. package/dist/{chunk-H3HDX4JN.js → chunk-RCTWZYAM.js} +2 -2
  28. package/dist/{chunk-7HOFM76G.cjs → chunk-STLNL53H.cjs} +4 -4
  29. package/dist/chunk-STLNL53H.cjs.map +1 -0
  30. package/dist/{chunk-5WZTB3OI.js → chunk-UBNJXMYX.js} +3 -3
  31. package/dist/{chunk-KXCSR4XK.cjs → chunk-WAPKUYGP.cjs} +7 -7
  32. package/dist/chunk-WAPKUYGP.cjs.map +1 -0
  33. package/dist/{chunk-L5UQXOUL.cjs → chunk-WRIHDGGY.cjs} +3 -3
  34. package/dist/chunk-WRIHDGGY.cjs.map +1 -0
  35. package/dist/{chunk-36FAAEN4.js → chunk-XH34WJKB.js} +2 -2
  36. package/dist/{chunk-QJD3CLFG.js → chunk-XWA625AN.js} +8 -3
  37. package/dist/chunk-XWA625AN.js.map +1 -0
  38. package/dist/{chunk-Q7CQWGLN.js → chunk-ZWTEE63A.js} +2 -2
  39. package/dist/chunks-PEHRXW2P.js +10 -0
  40. package/dist/cli.js +28 -27
  41. package/dist/cli.js.map +1 -1
  42. package/dist/{formula-DF6KHCIH.cjs → formula-TAYYDV73.cjs} +9 -9
  43. package/dist/formula-TAYYDV73.cjs.map +1 -0
  44. package/dist/{formula-GL5HHTL5.js → formula-VSZIJZXP.js} +4 -4
  45. package/dist/{formula-Z6EVYSSI.js → formula-VYDTA27M.js} +3 -3
  46. package/dist/{image-ocr-SVA3XKWJ.js → image-ocr-5WBAIBPQ.js} +5 -5
  47. package/dist/{image-ocr-ZE2VGIC3.js → image-ocr-N6IBZJAK.js} +7 -7
  48. package/dist/{image-ocr-X2D3G7GD.cjs → image-ocr-ZX4W2RBS.cjs} +11 -11
  49. package/dist/image-ocr-ZX4W2RBS.cjs.map +1 -0
  50. package/dist/index.cjs +827 -420
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +5 -3
  53. package/dist/index.d.ts +5 -3
  54. package/dist/index.js +473 -66
  55. package/dist/index.js.map +1 -1
  56. package/dist/mcp.js +26 -24
  57. package/dist/mcp.js.map +1 -1
  58. package/dist/{model-bundle-3TTBPL3Q.js → model-bundle-5CBFVIXA.js} +5 -5
  59. package/dist/{models-6RICFYZZ.js → models-L74RBKK3.js} +5 -5
  60. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  61. package/dist/{parser-YKKSFY6Q.js → parser-66NMJUOG.js} +6 -6
  62. package/dist/{parser-54OYFBL2.js → parser-BK66Y5JD.js} +6 -6
  63. package/dist/{parser-NMVZMF5W.cjs → parser-WM2DMVLY.cjs} +26 -26
  64. package/dist/parser-WM2DMVLY.cjs.map +1 -0
  65. package/dist/pdf-ocr-322MBLDK.js +16 -0
  66. package/dist/pdf-ocr-A4UBXLM2.js +13 -0
  67. package/dist/pdf-ocr-G4TBDUYL.cjs +14 -0
  68. package/dist/pdf-ocr-G4TBDUYL.cjs.map +1 -0
  69. package/dist/{profile-io-PDOYD237.js → profile-io-JZ3FDHDK.js} +3 -3
  70. package/dist/{rasterize-4ZSVJGDA.js → rasterize-33WOOVSM.js} +3 -3
  71. package/dist/{render-XRCZI3CY.js → render-MLN4ZLVD.js} +6 -6
  72. package/dist/seal-JQWSNHRG.js +10 -0
  73. package/dist/{setup-K7GW2TZM.js → setup-G6AEN4J7.js} +3 -3
  74. package/dist/visual-VZ7QGAUC.js +19 -0
  75. package/dist/{watch-MR27M6VA.js → watch-OY6XKGC5.js} +10 -10
  76. package/package.json +4 -3
  77. package/dist/chunk-7HOFM76G.cjs.map +0 -1
  78. package/dist/chunk-F4DZU7VP.js.map +0 -1
  79. package/dist/chunk-KXCSR4XK.cjs.map +0 -1
  80. package/dist/chunk-L5UQXOUL.cjs.map +0 -1
  81. package/dist/chunk-LBWJOJ2T.cjs.map +0 -1
  82. package/dist/chunk-QJD3CLFG.js.map +0 -1
  83. package/dist/chunk-TS665MCV.cjs.map +0 -1
  84. package/dist/chunk-WOAVPYT3.js.map +0 -1
  85. package/dist/chunks-IZLHLZQD.js +0 -10
  86. package/dist/formula-DF6KHCIH.cjs.map +0 -1
  87. package/dist/image-ocr-X2D3G7GD.cjs.map +0 -1
  88. package/dist/parser-NMVZMF5W.cjs.map +0 -1
  89. package/dist/pdf-ocr-FFKINEB7.cjs +0 -14
  90. package/dist/pdf-ocr-FFKINEB7.cjs.map +0 -1
  91. package/dist/pdf-ocr-MFJQP4XG.js +0 -13
  92. package/dist/pdf-ocr-WWYZE5L7.js +0 -16
  93. package/dist/seal-CFYSOI4V.js +0 -10
  94. package/dist/visual-AUPPOHEP.js +0 -19
  95. /package/dist/{-S3PXW4LR.js.map → -SLX6IB6Y.js.map} +0 -0
  96. /package/dist/{chunk-K3KLZZC6.js.map → chunk-3U3PJGVO.js.map} +0 -0
  97. /package/dist/{chunk-IRIASTA5.js.map → chunk-6T6MB5ON.js.map} +0 -0
  98. /package/dist/{chunk-2TEH7CC5.js.map → chunk-7VUHZFGQ.js.map} +0 -0
  99. /package/dist/{chunk-D6S64TR6.js.map → chunk-A2JDWBKG.js.map} +0 -0
  100. /package/dist/{chunk-FSEJ6OJO.js.map → chunk-CDQJKMZ5.js.map} +0 -0
  101. /package/dist/{chunk-VRXVNVLQ.js.map → chunk-EOTAPX2M.js.map} +0 -0
  102. /package/dist/{chunk-SA2ECW7B.js.map → chunk-GMX6EKCY.js.map} +0 -0
  103. /package/dist/{chunk-QOJTBOWX.js.map → chunk-HCHA3Y2T.js.map} +0 -0
  104. /package/dist/{chunk-ABGDU4KM.js.map → chunk-J3SM7VLM.js.map} +0 -0
  105. /package/dist/{chunk-Q4DUG3WU.js.map → chunk-JIQWI7S2.js.map} +0 -0
  106. /package/dist/{chunk-R6NY4ZIS.js.map → chunk-LCGAO5LF.js.map} +0 -0
  107. /package/dist/{chunk-SHC6FGRY.js.map → chunk-M5NRJMA4.js.map} +0 -0
  108. /package/dist/{chunk-QFXJAYA4.js.map → chunk-PLXNW3J4.js.map} +0 -0
  109. /package/dist/{chunk-H3HDX4JN.js.map → chunk-RCTWZYAM.js.map} +0 -0
  110. /package/dist/{chunk-5WZTB3OI.js.map → chunk-UBNJXMYX.js.map} +0 -0
  111. /package/dist/{chunk-36FAAEN4.js.map → chunk-XH34WJKB.js.map} +0 -0
  112. /package/dist/{chunk-Q7CQWGLN.js.map → chunk-ZWTEE63A.js.map} +0 -0
  113. /package/dist/{chunks-IZLHLZQD.js.map → chunks-PEHRXW2P.js.map} +0 -0
  114. /package/dist/{formula-GL5HHTL5.js.map → formula-VSZIJZXP.js.map} +0 -0
  115. /package/dist/{formula-Z6EVYSSI.js.map → formula-VYDTA27M.js.map} +0 -0
  116. /package/dist/{image-ocr-SVA3XKWJ.js.map → image-ocr-5WBAIBPQ.js.map} +0 -0
  117. /package/dist/{image-ocr-ZE2VGIC3.js.map → image-ocr-N6IBZJAK.js.map} +0 -0
  118. /package/dist/{model-bundle-3TTBPL3Q.js.map → model-bundle-5CBFVIXA.js.map} +0 -0
  119. /package/dist/{models-6RICFYZZ.js.map → models-L74RBKK3.js.map} +0 -0
  120. /package/dist/{parser-YKKSFY6Q.js.map → parser-66NMJUOG.js.map} +0 -0
  121. /package/dist/{parser-54OYFBL2.js.map → parser-BK66Y5JD.js.map} +0 -0
  122. /package/dist/{pdf-ocr-MFJQP4XG.js.map → pdf-ocr-322MBLDK.js.map} +0 -0
  123. /package/dist/{pdf-ocr-WWYZE5L7.js.map → pdf-ocr-A4UBXLM2.js.map} +0 -0
  124. /package/dist/{profile-io-PDOYD237.js.map → profile-io-JZ3FDHDK.js.map} +0 -0
  125. /package/dist/{rasterize-4ZSVJGDA.js.map → rasterize-33WOOVSM.js.map} +0 -0
  126. /package/dist/{render-XRCZI3CY.js.map → render-MLN4ZLVD.js.map} +0 -0
  127. /package/dist/{seal-CFYSOI4V.js.map → seal-JQWSNHRG.js.map} +0 -0
  128. /package/dist/{setup-K7GW2TZM.js.map → setup-G6AEN4J7.js.map} +0 -0
  129. /package/dist/{visual-AUPPOHEP.js.map → visual-VZ7QGAUC.js.map} +0 -0
  130. /package/dist/{watch-MR27M6VA.js.map → watch-OY6XKGC5.js.map} +0 -0
@@ -11,7 +11,7 @@ import {
11
11
  paraTextPureT,
12
12
  patchZipEntries,
13
13
  scanSectionXml
14
- } from "./chunk-5WZTB3OI.js";
14
+ } from "./chunk-UBNJXMYX.js";
15
15
  import {
16
16
  CONVERT_MAP,
17
17
  FLAG_COMPRESSED,
@@ -50,7 +50,7 @@ import {
50
50
  renderDocumentToScene,
51
51
  romanNumeral,
52
52
  simulateWrap
53
- } from "./chunk-WOAVPYT3.js";
53
+ } from "./chunk-HH7T6SMB.js";
54
54
  import {
55
55
  HEADING_RATIO_H1,
56
56
  HEADING_RATIO_H2,
@@ -74,7 +74,7 @@ import {
74
74
  findChildByLocalName,
75
75
  localName,
76
76
  rawTextContent
77
- } from "./chunk-K3KLZZC6.js";
77
+ } from "./chunk-3U3PJGVO.js";
78
78
  import {
79
79
  MAX_COLS,
80
80
  MAX_ROWS,
@@ -83,7 +83,7 @@ import {
83
83
  convertTableToText,
84
84
  hasStructuredCellContent,
85
85
  mapPuaText
86
- } from "./chunk-36FAAEN4.js";
86
+ } from "./chunk-XH34WJKB.js";
87
87
  import {
88
88
  KordocError,
89
89
  classifyError,
@@ -95,7 +95,7 @@ import {
95
95
  sanitizeHref,
96
96
  stripDtd,
97
97
  toArrayBuffer
98
- } from "./chunk-H3HDX4JN.js";
98
+ } from "./chunk-RCTWZYAM.js";
99
99
 
100
100
  // src/table/visual.ts
101
101
  import { readFile as readFile2 } from "fs/promises";
@@ -269,9 +269,9 @@ function decryptEntry(cipher, entry, startKey) {
269
269
  const decipher = createDecipheriv("aes-256-cbc", key, entry.iv);
270
270
  decipher.setAutoPadding(false);
271
271
  const raw = Buffer.concat([decipher.update(cipher), decipher.final()]);
272
- const plain3 = inflateRawSync(raw, { maxOutputLength: MAX_DECOMPRESS_SIZE });
273
- const sum = createHash("sha256").update(plain3.subarray(0, Math.min(CHECKSUM_PREFIX_BYTES, plain3.length))).digest();
274
- if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return plain3;
272
+ const plain4 = inflateRawSync(raw, { maxOutputLength: MAX_DECOMPRESS_SIZE });
273
+ const sum = createHash("sha256").update(plain4.subarray(0, Math.min(CHECKSUM_PREFIX_BYTES, plain4.length))).digest();
274
+ if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return plain4;
275
275
  } catch {
276
276
  }
277
277
  }
@@ -287,13 +287,13 @@ async function decryptHwpxInPlace(zip, manifestXml, password) {
287
287
  if (!file) {
288
288
  throw new KordocError(`\uC554\uD638 \uBAA9\uB85D\uC5D0 \uC788\uB294 \uD30C\uC77C\uC774 \uBB38\uC11C\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4: ${entry.path}`);
289
289
  }
290
- const plain3 = decryptEntry(await file.async("nodebuffer"), entry, startKey);
291
- if (!plain3) {
290
+ const plain4 = decryptEntry(await file.async("nodebuffer"), entry, startKey);
291
+ if (!plain4) {
292
292
  throw new KordocError("\uBE44\uBC00\uBC88\uD638\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uAC70\uB098 \uC554\uD638\uD654 \uB370\uC774\uD130\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
293
293
  }
294
- decrypted.push({ path: entry.path, plain: plain3 });
294
+ decrypted.push({ path: entry.path, plain: plain4 });
295
295
  }
296
- for (const { path, plain: plain3 } of decrypted) zip.file(path, plain3);
296
+ for (const { path, plain: plain4 } of decrypted) zip.file(path, plain4);
297
297
  zip.file("META-INF/manifest.xml", stripEncryptionData(manifestXml));
298
298
  return decrypted.length;
299
299
  }
@@ -2182,12 +2182,15 @@ var InsufficientDataError = class extends Error {
2182
2182
  this.available = available;
2183
2183
  this.name = "InsufficientDataError";
2184
2184
  }
2185
+ requested;
2186
+ available;
2185
2187
  };
2186
2188
  var Reader = class {
2187
2189
  constructor(buf, start = 0) {
2188
2190
  this.buf = buf;
2189
2191
  this.pos = start;
2190
2192
  }
2193
+ buf;
2191
2194
  pos;
2192
2195
  position() {
2193
2196
  return this.pos;
@@ -17437,6 +17440,7 @@ var ValueCursor = class {
17437
17440
  constructor(values) {
17438
17441
  this.values = values;
17439
17442
  }
17443
+ values;
17440
17444
  nextIdx = /* @__PURE__ */ new Map();
17441
17445
  keys() {
17442
17446
  return this.values.keys();
@@ -18853,6 +18857,8 @@ var GAEJOSIK_MARGINS = { top: 15, bottom: 15, left: 20, right: 20 };
18853
18857
  var SEOUL_REPORT_MARGINS = { top: 13, bottom: 13, left: 18, right: 18 };
18854
18858
  var SEOUL_REPORT_HEADER_FOOTER = 3600;
18855
18859
  var GAEJOSIK_HEADER_FOOTER = 4251;
18860
+ var MINISTRY_MARGINS = { top: 10, bottom: 10, left: 20, right: 20 };
18861
+ var MINISTRY_HEADER_FOOTER = 2835;
18856
18862
  var PRESET_DEFAULTS = {
18857
18863
  // 서울시 실결재 104건 중 12pt 64건(13pt 30, 15pt 5) — 지배값을 기본으로 사용.
18858
18864
  official: { bodyPt: 12, lineSpacing: 160, numbering: "standard" },
@@ -18863,7 +18869,9 @@ var PRESET_DEFAULTS = {
18863
18869
  minutes: { bodyPt: 14, lineSpacing: 130, numbering: "standard" },
18864
18870
  gaejosik: { bodyPt: 15, lineSpacing: 160, numbering: "gaejosik" },
18865
18871
  // 보도자료 — 실측(국토부 실물): 본문 바탕 14pt 160%, □→ㅇ→*(각주) 부호
18866
- press: { bodyPt: 14, lineSpacing: 160, numbering: "report" }
18872
+ press: { bodyPt: 14, lineSpacing: 160, numbering: "report" },
18873
+ // 중앙부처 업무보고 — 실측(재경부 2차 업무보고): 함초롬바탕 15pt, 줄피치 21.7pt(≈145%), □→ㅇ→-→*(각주)
18874
+ ministry: { bodyPt: 15, lineSpacing: 145, numbering: "report" }
18867
18875
  };
18868
18876
  var PRESET_ALIAS = {
18869
18877
  official: "official",
@@ -18888,7 +18896,11 @@ var PRESET_ALIAS = {
18888
18896
  \uC815\uBD80\uBCF4\uACE0\uC11C: "gaejosik",
18889
18897
  \uC815\uBD80\uD45C\uC900\uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
18890
18898
  press: "press",
18891
- \uBCF4\uB3C4\uC790\uB8CC: "press"
18899
+ \uBCF4\uB3C4\uC790\uB8CC: "press",
18900
+ ministry: "ministry",
18901
+ \uC5C5\uBB34\uBCF4\uACE0: "ministry",
18902
+ \uBD80\uCC98\uC5C5\uBB34\uBCF4\uACE0: "ministry",
18903
+ \uC911\uC559\uBD80\uCC98\uBCF4\uACE0\uC11C: "ministry"
18892
18904
  };
18893
18905
  function normalizeGongmunPreset(preset) {
18894
18906
  if (!preset) return "official";
@@ -18995,7 +19007,8 @@ function resolveGongmun(opts) {
18995
19007
  const d = PRESET_DEFAULTS[preset];
18996
19008
  const bodyPt = opts.bodyPt ?? d.bodyPt;
18997
19009
  const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
18998
- const coverOn = opts.cover !== void 0 ? opts.cover !== false : preset === "gaejosik";
19010
+ const ministry = preset === "ministry";
19011
+ const coverOn = opts.cover !== void 0 ? opts.cover !== false : preset === "gaejosik" || ministry;
18999
19012
  const coverOpts = typeof opts.cover === "object" ? opts.cover : {};
19000
19013
  const gaejosik = preset === "gaejosik";
19001
19014
  const reportFamily = gaejosik || preset === "report" || preset === "plan" || preset === "notice" || preset === "press";
@@ -19005,26 +19018,26 @@ function resolveGongmun(opts) {
19005
19018
  bodyHeight: Math.round(bodyPt * 100),
19006
19019
  lineSpacing: opts.lineSpacing ?? d.lineSpacing,
19007
19020
  numbering: opts.numbering ?? d.numbering,
19008
- margins: opts.margins ?? (preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
19021
+ margins: opts.margins ?? (ministry ? MINISTRY_MARGINS : preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
19009
19022
  centerTitle: opts.centerTitle ?? true,
19010
19023
  autoFitMinRatio,
19011
19024
  // 보도자료는 머리박스가 1페이지 최상단을 차지하는 서식이라 표지·목차와 양립 불가 —
19012
19025
  // 켜면 머리박스가 표지에 얹히고 25pt 제목·부제가 유실된다 (docHead 프리셋 게이팅과 동일 관례)
19013
- cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {} } : null,
19026
+ cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {}, ...coverOpts.label ? { label: coverOpts.label } : {} } : null,
19014
19027
  bodyPtExplicit: opts.bodyPt !== void 0,
19015
19028
  lineSpacingExplicit: opts.lineSpacing !== void 0,
19016
19029
  bodyFontExplicit: opts.bodyFont !== void 0,
19017
19030
  summary: opts.summary?.trim() || null,
19018
19031
  docInfo: opts.docInfo ?? null,
19019
- toc: preset !== "press" && (opts.toc ?? gaejosik),
19032
+ toc: preset !== "press" && (opts.toc ?? (gaejosik || ministry)),
19020
19033
  fonts: opts.fonts ?? {},
19021
19034
  sizes: opts.sizes ?? {},
19022
19035
  levels: resolveLevels(opts.levels, Math.round(bodyPt * 100)),
19023
19036
  // 쪽번호 — 보고서 계열 관행(실측: 2_보고서 양식·추진계획·공고문 전부 하단 중앙)
19024
- pageNumbers: opts.pageNumbers ?? (gaejosik || preset === "report" || preset === "plan"),
19037
+ pageNumbers: opts.pageNumbers ?? (gaejosik || ministry || preset === "report" || preset === "plan"),
19025
19038
  // 머리말·꼬리말 — 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
19026
19039
  // 기안문 0(실결재 41/60건 h0/f0)
19027
- headerFooter: preset === "report" || preset === "plan" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
19040
+ headerFooter: ministry ? MINISTRY_HEADER_FOOTER : preset === "report" || preset === "plan" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
19028
19041
  // "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
19029
19042
  endMark: opts.endMark ?? preset === "official",
19030
19043
  approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
@@ -19107,6 +19120,9 @@ var GongmunNumberer = class {
19107
19120
  this.bullet2 = bullet2;
19108
19121
  this.asteriskThird = asteriskThird;
19109
19122
  }
19123
+ numbering;
19124
+ bullet2;
19125
+ asteriskThird;
19110
19126
  counts = [];
19111
19127
  /** depth 항목 하나에 대한 마커. suppress=true면 빈 문자열(부호 없음) */
19112
19128
  next(depth, suppress) {
@@ -19259,8 +19275,10 @@ function borderFillEntry(id, b, fill) {
19259
19275
  brush = `
19260
19276
  <hc:fillBrush><hc:winBrush faceColor="${fill}" hatchColor="#000000" alpha="0"/></hc:fillBrush>`;
19261
19277
  } else if (fill) {
19278
+ const type = fill.type ?? "RADIAL";
19279
+ const colors = fill.gradient.slice(0, 2).map((c) => `<hc:color value="${c}"/>`).join("");
19262
19280
  brush = `
19263
- <hc:fillBrush><hc:gradation type="RADIAL" angle="0" centerX="0" centerY="0" step="50" colorNum="2" stepCenter="50" alpha="0"><hc:color value="${fill.gradient[0]}"/><hc:color value="${fill.gradient[1]}"/></hc:gradation></hc:fillBrush>`;
19281
+ <hc:fillBrush><hc:gradation type="${type}" angle="${fill.angle ?? 0}" centerX="0" centerY="0" step="50" colorNum="2" stepCenter="50" alpha="0">${colors}</hc:gradation></hc:fillBrush>`;
19264
19282
  }
19265
19283
  return ` <hh:borderFill id="${id}" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
19266
19284
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
@@ -21003,16 +21021,22 @@ var DOUBLE = ["0.5 mm", "#000000", "DOUBLE_SLIM"];
21003
21021
  var MID = ["0.15 mm", "#000000"];
21004
21022
  var EDGE = { thin: THIN, thick: THICK, double: DOUBLE, mid: MID };
21005
21023
  function sideOf(kind) {
21024
+ if (Array.isArray(kind)) return kind;
21006
21025
  return kind === "none" ? void 0 : EDGE[kind];
21007
21026
  }
21027
+ function edgeKey(kind) {
21028
+ return Array.isArray(kind) ? kind.join("/") : kind;
21029
+ }
21008
21030
  var TableBfRegistry = class {
21009
21031
  constructor(nextId) {
21010
21032
  this.nextId = nextId;
21011
21033
  }
21034
+ nextId;
21012
21035
  map = /* @__PURE__ */ new Map();
21013
21036
  xmls = [];
21014
21037
  get(spec) {
21015
- const key = `${spec.t}|${spec.b}|${spec.l}|${spec.r}|${typeof spec.fill === "string" ? spec.fill : spec.fill ? spec.fill.gradient.join("-") : ""}`;
21038
+ const fillKey = typeof spec.fill === "string" ? spec.fill : spec.fill ? `${spec.fill.type ?? "RADIAL"}@${spec.fill.angle ?? 0}:${spec.fill.gradient.join("-")}` : "";
21039
+ const key = `${edgeKey(spec.t)}|${edgeKey(spec.b)}|${edgeKey(spec.l)}|${edgeKey(spec.r)}|${fillKey}`;
21016
21040
  const hit = this.map.get(key);
21017
21041
  if (hit !== void 0) return hit;
21018
21042
  const id = this.nextId++;
@@ -21361,6 +21385,7 @@ var ImageRegistry = class {
21361
21385
  constructor(supplied) {
21362
21386
  this.supplied = supplied;
21363
21387
  }
21388
+ supplied;
21364
21389
  byUrl = /* @__PURE__ */ new Map();
21365
21390
  ids = /* @__PURE__ */ new Set();
21366
21391
  parts = [];
@@ -21462,6 +21487,14 @@ function resetTableIds() {
21462
21487
  }
21463
21488
  var curFace = "hcr";
21464
21489
  var DATA_TABLE_INSET = 1800;
21490
+ function tablePosXml(float, right) {
21491
+ return float ? `<hp:pos treatAsChar="0" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="COLUMN" vertAlign="TOP" horzAlign="${right ? "RIGHT" : "LEFT"}" vertOffset="0" horzOffset="0"/>` : `<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>`;
21492
+ }
21493
+ function tableOutMarginXml(float, charHeight) {
21494
+ const bottom = float ? Math.round(charHeight * 1.6) : 0;
21495
+ return `<hp:outMargin left="0" right="0" top="0" bottom="${bottom}"/>`;
21496
+ }
21497
+ var FLOAT_TBL_ATTRS = ` textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None"`;
21465
21498
  function anchorOfMarkdownCell(cell2) {
21466
21499
  return normalizeAnchor(cell2.replace(/!\[[^\]]*\]\([^)]*\)/g, ""));
21467
21500
  }
@@ -21671,9 +21704,10 @@ function generateTable(rows, theme, style = null, remap = null, seq = 0, images
21671
21704
  }).join("");
21672
21705
  const tblW = colWidths.reduce((a, b) => a + b, 0);
21673
21706
  const tblH = rowHeights.reduce((a, b) => a + b, 0);
21674
- const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" 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="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
21675
- const tbl2 = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="${style ? 1 : 0}" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0">${tblInner}</hp:tbl>`;
21676
21707
  const hostPr = reg && style?.rightParaPr !== void 0 ? style.rightParaPr : 0;
21708
+ const float = rowCnt >= 2;
21709
+ const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/>` + tablePosXml(float, hostPr !== 0) + tableOutMarginXml(float, measureH) + `<hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
21710
+ const tbl2 = `<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">${tblInner}</hp:tbl>`;
21677
21711
  return `<hp:p paraPrIDRef="${hostPr}" styleIDRef="0"><hp:run charPrIDRef="0">${tbl2}</hp:run></hp:p>`;
21678
21712
  }
21679
21713
  function layoutHtmlRows(rows) {
@@ -21708,7 +21742,7 @@ function layoutHtmlRows(rows) {
21708
21742
  function unescapeHtml(s) {
21709
21743
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
21710
21744
  }
21711
- function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, remap = null, seq = 0, images = null) {
21745
+ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, remap = null, seq = 0, images = null, nested = false) {
21712
21746
  const rows = parseHtmlTable(rawHtml);
21713
21747
  if (!rows || rows.length === 0) return null;
21714
21748
  const { placed, rowCnt, colCnt } = layoutHtmlRows(rows);
@@ -21754,13 +21788,13 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
21754
21788
  const lines = cellLines[i];
21755
21789
  let nestedH = 0;
21756
21790
  const { texts: segTexts, tables: nestedTables } = splitCellByTopLevelTables(cell2.inner);
21757
- const nestedXmls = nestedTables.map((nested) => {
21791
+ const nestedXmls = nestedTables.map((nested2) => {
21758
21792
  const sw = spanW(cell2);
21759
21793
  const nestedW = Math.max(Math.min(Math.max(sw - 1020, 4e3), sw - 282), 500);
21760
- const nestedXml = generateHtmlTableXml(nested, theme, nestedW, style ? { ...style, totalWidth: nestedW } : null);
21794
+ const nestedXml = generateHtmlTableXml(nested2, theme, nestedW, style ? { ...style, totalWidth: nestedW } : null, null, 0, null, true);
21761
21795
  if (nestedXml) {
21762
21796
  const szH = nestedXml.match(/<hp:sz [^>]*height="(\d+)"/)?.[1];
21763
- nestedH += (szH ? Number(szH) : (nested.match(/<tr[\s>]/gi) ?? []).length * cellH) + 300;
21797
+ nestedH += (szH ? Number(szH) : (nested2.match(/<tr[\s>]/gi) ?? []).length * cellH) + 300;
21764
21798
  }
21765
21799
  return nestedXml;
21766
21800
  });
@@ -21839,7 +21873,8 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
21839
21873
  trXmls.push(`<hp:tr>${rowTcs.join("")}</hp:tr>`);
21840
21874
  }
21841
21875
  const tableH = tableRowHeights.reduce((sum, height) => sum + height, 0);
21842
- return `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" 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"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" 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="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trXmls.join("") + `</hp:tbl>`;
21876
+ const float = !nested && rowCnt >= 2;
21877
+ 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>`;
21843
21878
  }
21844
21879
 
21845
21880
  // src/hwpx/gen-gongmun-extra.ts
@@ -22347,6 +22382,8 @@ var SectionOpener = class {
22347
22382
  this.gongmun = gongmun;
22348
22383
  this.page = page;
22349
22384
  }
22385
+ gongmun;
22386
+ page;
22350
22387
  pending = true;
22351
22388
  /** 아직 secPr를 나를 첫 문단이 안 나왔는가 */
22352
22389
  get isFirst() {
@@ -22606,8 +22643,8 @@ function renderHtmlTable(block, ctx) {
22606
22643
  ctx.opener.emitCarrier(ctx.paraXmls);
22607
22644
  return `<hp:p paraPrIDRef="${tableStyle && bfReg ? GONGMUN_RIGHT : 0}" styleIDRef="0"><hp:run charPrIDRef="0">${tbl2}</hp:run></hp:p>`;
22608
22645
  }
22609
- const plain3 = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
22610
- return plain3 ? generateParagraph(plain3) : "";
22646
+ const plain4 = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
22647
+ return plain4 ? generateParagraph(plain4) : "";
22611
22648
  }
22612
22649
  function appendPostamble(blocks, ctx) {
22613
22650
  const { gongmun, dfIds, bfReg, tableStyle, paraXmls } = ctx;
@@ -22795,6 +22832,9 @@ var StyleRegistry = class {
22795
22832
  if (!this.fonts.has(f)) this.fonts.set(f, i);
22796
22833
  });
22797
22834
  }
22835
+ charBase;
22836
+ paraBase;
22837
+ fontBase;
22798
22838
  chars = /* @__PURE__ */ new Map();
22799
22839
  charXmls = [];
22800
22840
  paras = /* @__PURE__ */ new Map();
@@ -22849,6 +22889,17 @@ var StyleRegistry = class {
22849
22889
  return this.fontList;
22850
22890
  }
22851
22891
  };
22892
+ function inlineMapper(reg, base, emphasisColor) {
22893
+ const norm2 = reg.char({ font: base.font, pt: base.pt, bold: base.bold, ratio: base.ratio, spacing: base.spacing, color: base.color });
22894
+ const emColor = emphasisColor ?? base.color;
22895
+ return (id) => {
22896
+ if (id === CHAR_BOLD) return reg.char({ font: base.font, pt: base.pt, bold: true, ratio: base.ratio, spacing: base.spacing, color: emColor });
22897
+ if (id === CHAR_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: base.bold, italic: true, ratio: base.ratio, spacing: base.spacing, color: base.color });
22898
+ if (id === CHAR_BOLD_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: true, italic: true, ratio: base.ratio, spacing: base.spacing, color: emColor });
22899
+ if (id === CHAR_NORMAL) return norm2;
22900
+ return id;
22901
+ };
22902
+ }
22852
22903
 
22853
22904
  // src/hwpx/gongmun-scheme.ts
22854
22905
  var SEOUL_FRAME = {
@@ -22893,6 +22944,45 @@ function seoulGaejosikScheme(bodyPt = 15, lineSp = 180, bullet2 = "\u3147") {
22893
22944
  blankBetweenTop: false
22894
22945
  };
22895
22946
  }
22947
+ var MINISTRY_FRAME = {
22948
+ font: "\uD568\uCD08\uB86C\uBC14\uD0D5",
22949
+ titleFont: "HY\uD5E4\uB4DC\uB77C\uC778M",
22950
+ titlePt: 32,
22951
+ contactFont: "\uD568\uCD08\uB86C\uBC14\uD0D5",
22952
+ contactPt: 12,
22953
+ summaryFont: "\uD568\uCD08\uB86C\uBC14\uD0D5",
22954
+ summaryPt: 13,
22955
+ summaryFill: "#FFF7CC"
22956
+ };
22957
+ function ministryScheme(bodyPt = 15, lineSp = 145) {
22958
+ const d = bodyPt - 15;
22959
+ const lv = (font, pt, bold, leadTa, extra = {}) => ({ font, pt: pt + d, bold, leadTa, ...extra });
22960
+ return {
22961
+ kind: "gaejosik",
22962
+ lineSp,
22963
+ body: lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 0),
22964
+ levels: [
22965
+ // □ 는 한 줄 강제 안 함 — 실측 □ 문장의 절반이 두 줄(pg3·pg6). 앞 빈 줄은 스킴 공통 로직(BOX_BLANK_HU)
22966
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 0, { keepWithNext: true, blankBefore: true }),
22967
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 1),
22968
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 3),
22969
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 4),
22970
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 5),
22971
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 6),
22972
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 7),
22973
+ lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 8)
22974
+ ],
22975
+ // * 각주 — 맑은 고딕 12(n=124), 선두 4칸 공백 ≈ ㅇ 본문 시작(x80)에 맞춘다
22976
+ ref: lv("\uB9D1\uC740 \uACE0\uB515", 12, false, 3),
22977
+ sub: lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 2),
22978
+ attach: lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 0),
22979
+ chapter: lv("HY\uD5E4\uB4DC\uB77C\uC778M", 16, false, 0, { oneLine: true, keepWithNext: true }),
22980
+ marker: (depth) => depth === 0 ? "\u25A1" : depth === 1 ? "\u3147" : depth === 2 ? "-" : "\u318D",
22981
+ table: { font: "\uB9D1\uC740 \uACE0\uB515", pt: 12 + d, headerFill: "#DFE6F7", labelFill: "#F2F2F2" },
22982
+ frame: MINISTRY_FRAME,
22983
+ blankBetweenTop: false
22984
+ };
22985
+ }
22896
22986
  function seoulLegalScheme(bodyFont = "\uAD74\uB9BC\uCCB4", bodyPt = 12, lineSp = 160) {
22897
22987
  const lv = (leadTa, extra = {}) => ({ font: bodyFont, pt: bodyPt, bold: false, leadTa, ...extra });
22898
22988
  return {
@@ -22943,6 +23033,9 @@ function levelGeometry(style, marker) {
22943
23033
  return { left, indent };
22944
23034
  }
22945
23035
  function pickScheme(g, bodyHasBoxMarkers) {
23036
+ if (g.preset === "ministry") {
23037
+ return applySchemeOverrides(ministryScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 145), g);
23038
+ }
22946
23039
  const gaejosik = g.numbering !== "standard" || bodyHasBoxMarkers;
22947
23040
  const base = gaejosik ? seoulGaejosikScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 180, g.bullet2) : seoulLegalScheme(g.fonts.body ?? (g.bodyFontExplicit ? g.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5" : "\uAD74\uB9BC\uCCB4"), g.bodyPtExplicit ? g.bodyHeight / 100 : 12, g.lineSpacingExplicit ? g.lineSpacing : 160);
22948
23041
  return applySchemeOverrides(base, g);
@@ -22975,6 +23068,8 @@ var BOX_MARKERS = {
22975
23068
  "\u2219": 3
22976
23069
  };
22977
23070
  var LEGAL_RE = /^(\d{1,2}\.|[가-힣]\.|\d{1,2}\)|[가-힣]\)|\(\d{1,2}\)|\([가-힣]\)|[①-⑳]|[㉮-㉻])\s+/u;
23071
+ var KEEP_MARKERS = { "\u21D2": 0, "\u21B3": 1, "\u261E": 0 };
23072
+ var KEEP_RE = /^([❶-❿➊-➓⇒↳☞])\s*/u;
22978
23073
  var BOX_RE = /^([□■❑❏ㅁ○ㅇ◦●❍◎\-–―—ㅡ‣▪▫ㆍ·•∙])\s+/u;
22979
23074
  var REF_RE = /^(※|*|\*(?=\s))\s*/u;
22980
23075
  var BUNIM_RE = /^붙\s*임(?:\s|:|$)/;
@@ -22993,10 +23088,14 @@ function legalMarkerDepth(marker) {
22993
23088
  if (/^[①-⑳]$/u.test(marker)) return 6;
22994
23089
  return 7;
22995
23090
  }
22996
- function parseLeadingMarker(text) {
23091
+ function parseLeadingMarker(text, keepMarkers = false) {
22997
23092
  const t = text.replace(/^[\s ]+/, "");
22998
23093
  const ref = REF_RE.exec(t);
22999
23094
  if (ref) return { kind: "ref", depth: 0, marker: ref[1], rest: t.slice(ref[0].length).trim() };
23095
+ if (keepMarkers) {
23096
+ const keep = KEEP_RE.exec(t);
23097
+ if (keep) return { kind: "keep", depth: KEEP_MARKERS[keep[1]] ?? 0, marker: keep[1], rest: t.slice(keep[0].length).trim() };
23098
+ }
23000
23099
  const box = BOX_RE.exec(t);
23001
23100
  if (box) return { kind: "box", depth: BOX_MARKERS[box[1]] ?? 3, marker: box[1], rest: t.slice(box[0].length).trim() };
23002
23101
  const legal = LEGAL_RE.exec(t);
@@ -23011,11 +23110,18 @@ function buildOutline(blocks, opts) {
23011
23110
  let lastItemDepth = -1;
23012
23111
  let seenBody = false;
23013
23112
  let hasBoxMarkers = false;
23014
- const pushItem = (depth, text, legalMarker, fromHeading = false) => {
23015
- nodes.push({ kind: "item", depth, text, ...legalMarker ? { legalMarker } : {}, ...fromHeading ? { fromHeading: true } : {} });
23113
+ let listBase = -1;
23114
+ let lastExplicitDepth = -1;
23115
+ const pushItem = (depth, text, legalMarker, fromHeading = false, marker, explicit = true) => {
23116
+ nodes.push({ kind: "item", depth, text, ...legalMarker ? { legalMarker } : {}, ...fromHeading ? { fromHeading: true } : {}, ...marker ? { marker } : {} });
23016
23117
  lastItemDepth = depth;
23017
23118
  seenBody = true;
23119
+ if (explicit) {
23120
+ lastExplicitDepth = depth;
23121
+ listBase = -1;
23122
+ }
23018
23123
  };
23124
+ const keep = !!opts.keepMarkers;
23019
23125
  for (let i = 0; i < blocks.length; i++) {
23020
23126
  const b = blocks[i];
23021
23127
  switch (b.type) {
@@ -23028,11 +23134,22 @@ function buildOutline(blocks, opts) {
23028
23134
  }
23029
23135
  if (lvl <= 2) {
23030
23136
  nodes.push({ kind: "chapter", text: stripChapterNumber(raw) || raw, index: ++chapters });
23137
+ lastExplicitDepth = -1;
23138
+ listBase = -1;
23031
23139
  headingDepth = opts.gaejosik ? -1 : 0;
23032
23140
  lastItemDepth = opts.gaejosik ? -1 : 0;
23033
23141
  seenBody = true;
23034
23142
  break;
23035
23143
  }
23144
+ if (opts.headingFrames) {
23145
+ const lm2 = parseLeadingMarker(raw, keep);
23146
+ nodes.push({ kind: "heading", level: lvl, text: lm2.kind === "legal" || lm2.kind === "keep" ? lm2.rest : raw });
23147
+ lastItemDepth = -1;
23148
+ lastExplicitDepth = -1;
23149
+ listBase = -1;
23150
+ seenBody = true;
23151
+ break;
23152
+ }
23036
23153
  const depth = Math.min(lvl - 3, 7);
23037
23154
  const lm = parseLeadingMarker(raw);
23038
23155
  pushItem(depth, lm.kind ? lm.rest : raw, void 0, true);
@@ -23041,7 +23158,7 @@ function buildOutline(blocks, opts) {
23041
23158
  }
23042
23159
  case "list_item": {
23043
23160
  const text = stripLawCodes((b.text ?? "").trim());
23044
- const lm = parseLeadingMarker(text);
23161
+ const lm = parseLeadingMarker(text, keep);
23045
23162
  if (SOURCE_RE.test(lm.kind === "box" || lm.kind === "legal" ? lm.rest : text)) {
23046
23163
  nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.kind ? lm.rest : text });
23047
23164
  break;
@@ -23059,12 +23176,20 @@ function buildOutline(blocks, opts) {
23059
23176
  pushItem(lm.depth, lm.rest);
23060
23177
  break;
23061
23178
  }
23179
+ if (lm.kind === "keep") {
23180
+ pushItem(lm.depth, lm.rest, void 0, false, lm.marker);
23181
+ break;
23182
+ }
23062
23183
  if (lm.kind === "legal" && opts.gaejosik) {
23063
23184
  pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
23064
23185
  break;
23065
23186
  }
23066
- const base = headingDepth >= 0 ? headingDepth + 1 : 0;
23067
- pushItem(Math.min(base + (b.indent ?? 0), 7), lm.kind === "legal" ? lm.rest : text);
23187
+ let base = headingDepth >= 0 ? headingDepth + 1 : 0;
23188
+ if (opts.headingFrames) {
23189
+ if (listBase < 0) listBase = lastExplicitDepth + 1;
23190
+ base = listBase;
23191
+ }
23192
+ pushItem(Math.min(base + (b.indent ?? 0), 7), lm.kind === "legal" ? lm.rest : text, void 0, false, void 0, false);
23068
23193
  break;
23069
23194
  }
23070
23195
  case "paragraph": {
@@ -23084,7 +23209,7 @@ function buildOutline(blocks, opts) {
23084
23209
  nodes.push({ kind: "para", depth: 0, text: rgt[1].trim(), align: "RIGHT" });
23085
23210
  break;
23086
23211
  }
23087
- const lm = parseLeadingMarker(text);
23212
+ const lm = parseLeadingMarker(text, keep);
23088
23213
  if (lm.kind === "ref") {
23089
23214
  nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.rest });
23090
23215
  break;
@@ -23098,6 +23223,10 @@ function buildOutline(blocks, opts) {
23098
23223
  pushItem(lm.depth, lm.rest);
23099
23224
  break;
23100
23225
  }
23226
+ if (lm.kind === "keep") {
23227
+ pushItem(lm.depth, lm.rest, void 0, false, lm.marker);
23228
+ break;
23229
+ }
23101
23230
  if (lm.kind === "legal") {
23102
23231
  if (opts.gaejosik) pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
23103
23232
  else pushItem(lm.depth, lm.rest);
@@ -23105,12 +23234,14 @@ function buildOutline(blocks, opts) {
23105
23234
  }
23106
23235
  nodes.push({ kind: "para", depth: 0, text });
23107
23236
  seenBody = true;
23237
+ lastExplicitDepth = -1;
23238
+ listBase = -1;
23108
23239
  break;
23109
23240
  }
23110
23241
  case "blockquote": {
23111
23242
  const lines = stripLawCodes(b.text ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
23112
23243
  if (!lines.length) break;
23113
- if (opts.summaryFromQuote && !seenBody) nodes.push({ kind: "summary", text: lines.join("\n") });
23244
+ if (opts.quoteBox || opts.summaryFromQuote && !seenBody) nodes.push({ kind: "summary", text: lines.join("\n") });
23114
23245
  else nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lines.join(" ").replace(/^※\s*/, "") });
23115
23246
  break;
23116
23247
  }
@@ -23451,8 +23582,225 @@ function buildReportCover(inp, ctx) {
23451
23582
  return out;
23452
23583
  }
23453
23584
 
23585
+ // src/hwpx/gen-frame-ministry.ts
23586
+ var MINISTRY = {
23587
+ blue: "#0000FF",
23588
+ headFont: "HY\uD5E4\uB4DC\uB77C\uC778M",
23589
+ bandFont: "HY\uC911\uACE0\uB515",
23590
+ bodyFont: "\uD568\uCD08\uB86C\uBC14\uD0D5",
23591
+ refFont: "\uB9D1\uC740 \uACE0\uB515",
23592
+ cover: { barDark: "#0C3DCA", barLight: "#0A33A9", barH: 590, titleH: 9500, titlePt: 32, datePt: 24, labelW: 7470, labelH: 2240, labelPt: 14 },
23593
+ toc: { labelPt: 24, labelColor: "#000080", chapterPt: 17, subPt: 15, border: ["0.7 mm", "#999999"] },
23594
+ // 원본은 위·아래 #D6EAFE·가운데 흰 3색 그라데이션 — 한글 gradation 은 2색만 안전(gen-ids)해서 위→아래 2색으로
23595
+ chapter: { h: 2650, pt: 16, fill: ["#D6EAFE", "#FFFFFF"], line: ["0.5 mm", "#0000FF", "DOUBLE_SLIM"] },
23596
+ section: { numW: 2970, h: 2340, pt: 15, fill: "#3057B9", border: ["0.4 mm", "#000000"], underline: ["0.5 mm", "#000000"] },
23597
+ subhead: { h: 2410, pt: 15, fill: "#203A7B" },
23598
+ item: { h: 2350, pt: 15, fill: "#E8F7FC", line: ["0.4 mm", "#00ACFF"] },
23599
+ summary: { fill: "#FFF7CC", pt: 13, refPt: 11 },
23600
+ attach: { labelW: 6130, gap: 590, h: 2630, pt: 16, fill: "#0066FF", thick: ["0.4 mm", "#000000"] }
23601
+ };
23602
+ var NONE = "none";
23603
+ var KEYWORD_RE = /^(\([^()\n]{1,24}\)|\[[^[\]\n]{1,24}\])\s*/u;
23604
+ function ministryRuns(marker, text, ctx, base, keywordColor, emphasisColor) {
23605
+ const { reg } = ctx;
23606
+ const baseId = reg.char(base);
23607
+ const mapper = inlineMapper(reg, base, emphasisColor);
23608
+ let out = marker ? `<hp:run charPrIDRef="${baseId}"><hp:t>${escapeXml(marker + " ")}</hp:t></hp:run>` : "";
23609
+ const m = KEYWORD_RE.exec(text);
23610
+ if (m) {
23611
+ const kw = reg.char({ ...base, bold: true, color: keywordColor ?? base.color });
23612
+ out += `<hp:run charPrIDRef="${kw}"><hp:t>${escapeXml(m[1] + " ")}</hp:t></hp:run>`;
23613
+ text = text.slice(m[0].length);
23614
+ }
23615
+ return out + generateRuns(text, baseId, mapper);
23616
+ }
23617
+ function plain(text) {
23618
+ return text.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1");
23619
+ }
23620
+ function textW(text, font, pt) {
23621
+ return measureTextWidth(text, pt * 100, 100, { faceClass: faceClassForGen(font) });
23622
+ }
23623
+ function buildMinistryCover(inp, ctx) {
23624
+ const { reg, bf } = ctx;
23625
+ const c = MINISTRY.cover;
23626
+ const out = [];
23627
+ const blank2 = (pt = 15) => para("", reg.para({ align: "LEFT", lineSp: 145 }), reg.char({ font: MINISTRY.bodyFont, pt }));
23628
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
23629
+ if (inp.label) {
23630
+ const red = ["0.12 mm", "#FF0000"];
23631
+ const box = bf.get({ t: red, b: red, l: red, r: red });
23632
+ const cL = reg.char({ font: MINISTRY.bodyFont, pt: c.labelPt, color: "#FF0000" });
23633
+ const cell2 = tc({ bf: box, row: 0, col: 0, w: c.labelW, h: c.labelH, paras: para(inp.label, center, cL) });
23634
+ out.push(host(ftbl([cell2], c.labelW, c.labelH, 1), reg.para({ align: "RIGHT", lineSp: 100 }), cL));
23635
+ } else {
23636
+ out.push(blank2());
23637
+ }
23638
+ for (let i = 0; i < 6; i++) out.push(blank2());
23639
+ const w = ctx.W - 566;
23640
+ const barTop = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE, fill: c.barDark });
23641
+ const barBot = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE, fill: c.barLight });
23642
+ const fit = fitOneLine(inp.title, MINISTRY.headFont, c.titlePt, w - 800, 24);
23643
+ const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
23644
+ const c1 = reg.char({ font: MINISTRY.bodyFont, pt: 1 });
23645
+ const barP = reg.para({ align: "CENTER", lineSp: 70 });
23646
+ const rows = [
23647
+ tc({ bf: barTop, row: 0, col: 0, w, h: c.barH, paras: para("", barP, c1) }),
23648
+ tc({ bf: 1, row: 1, col: 0, w, h: c.titleH, paras: para(inp.title, center, cT), name: "__kordoc_h1" }),
23649
+ tc({ bf: barBot, row: 2, col: 0, w, h: c.barH, paras: para("", barP, c1) })
23650
+ ];
23651
+ out.push(host(ftbl(rows, w, c.barH * 2 + c.titleH, 1), reg.para({ align: "CENTER", lineSp: 100 }), cT));
23652
+ for (let i = 0; i < 13; i++) out.push(blank2());
23653
+ out.push(para(inp.date, center, reg.char({ font: MINISTRY.bodyFont, pt: c.datePt })));
23654
+ if (inp.org) {
23655
+ for (let i = 0; i < 3; i++) out.push(blank2());
23656
+ out.push(para(inp.org, center, reg.char({ font: MINISTRY.headFont, pt: c.datePt })));
23657
+ }
23658
+ return out;
23659
+ }
23660
+ function buildMinistryToc(chapters, ctx) {
23661
+ const { reg, bf } = ctx;
23662
+ const t = MINISTRY.toc;
23663
+ const w = ctx.W - 1200;
23664
+ const pad = 2e3;
23665
+ const cLabel = reg.char({ font: MINISTRY.headFont, pt: t.labelPt, color: t.labelColor });
23666
+ const cCh = reg.char({ font: MINISTRY.headFont, pt: t.chapterPt });
23667
+ const cSub = reg.char({ font: MINISTRY.headFont, pt: t.subPt });
23668
+ const pLabel = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 1200, after: 2400 });
23669
+ const pCh = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 2e3, keepWord: false });
23670
+ const pSub = reg.para({ align: "LEFT", lineSp: 130, left: pad + 1400, before: 1200, keepWord: false });
23671
+ const paras = [para("\uBAA9 \uCC28", pLabel, cLabel)];
23672
+ let h = 1200 + Math.round(t.labelPt * 130) + 2400;
23673
+ let roman = 0;
23674
+ for (const ch of chapters) {
23675
+ if (ch.attach) {
23676
+ paras.push(para(`[\uBCC4\uCCA8] ${stripAttachPrefix(ch.title)}`, pCh, cCh));
23677
+ } else {
23678
+ roman++;
23679
+ paras.push(para(`${romanOf(roman)}. ${ch.title}`, pCh, cCh));
23680
+ }
23681
+ h += 2e3 + Math.round(t.chapterPt * 130);
23682
+ for (let i = 0; i < ch.subs.length; i++) {
23683
+ paras.push(para(`${i + 1}. ${ch.subs[i]}`, pSub, cSub));
23684
+ h += 1200 + Math.round(t.subPt * 130);
23685
+ }
23686
+ }
23687
+ h = Math.max(h + 3e3, TOC_MIN_H);
23688
+ const box = bf.get({ t: t.border, b: t.border, l: t.border, r: t.border });
23689
+ const cell2 = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), vAlign: "TOP", name: "__kordoc_toc" });
23690
+ const hostP = reg.para({ align: "CENTER", lineSp: 100 });
23691
+ return [host(ftbl([cell2], w, h, 1), hostP, cCh, ` pageBreak="1"`)];
23692
+ }
23693
+ var TOC_MIN_H = 62e3;
23694
+ function stripAttachPrefix(title) {
23695
+ return title.replace(/^\[?\s*별\s*첨\s*\]?\s*[::]?\s*/u, "");
23696
+ }
23697
+ function romanOf(n) {
23698
+ if (n >= 1 && n <= 12) return String.fromCodePoint(8543 + n);
23699
+ const t = [[10, "\u2169"], [9, "\u2168"], [5, "\u2164"], [4, "\u2163"], [1, "\u2160"]];
23700
+ let s = "";
23701
+ for (const [v, r] of t) while (n >= v) {
23702
+ s += r;
23703
+ n -= v;
23704
+ }
23705
+ return s;
23706
+ }
23707
+ function buildMinistryChapterBand(roman, title, ctx, before) {
23708
+ const { reg, bf } = ctx;
23709
+ const c = MINISTRY.chapter;
23710
+ const w = ctx.W;
23711
+ const text = `${roman}. ${plain(title)}`;
23712
+ const fit = fitOneLine(text, MINISTRY.headFont, c.pt, w - 1400, c.pt - 2);
23713
+ const band = bf.get({ t: c.line, b: c.line, l: NONE, r: NONE, fill: { gradient: [...c.fill], type: "LINEAR", angle: 90 } });
23714
+ const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
23715
+ const p = reg.para({ align: "LEFT", lineSp: 130, left: 500 });
23716
+ const cell2 = tc({ bf: band, row: 0, col: 0, w, h: c.h, paras: para(text, p, cT), name: "__kordoc_h2" });
23717
+ return { xml: host(ftbl([cell2], w, c.h, 1, { bottomGap: 800 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT), overflow: fit.overflow };
23718
+ }
23719
+ function buildMinistrySectionBand(n, title, ctx, before) {
23720
+ const { reg, bf } = ctx;
23721
+ const s = MINISTRY.section;
23722
+ const t = plain(title);
23723
+ const titleW = Math.min(ctx.W - s.numW, Math.max(6e3, textW(t, MINISTRY.headFont, s.pt) + 1600));
23724
+ const numBf = bf.get({ t: s.border, b: s.border, l: s.border, r: s.border, fill: s.fill });
23725
+ const titleBf = bf.get({ t: NONE, b: s.underline, l: NONE, r: NONE });
23726
+ const cNum = reg.char({ font: MINISTRY.headFont, pt: s.pt, color: "#FFFFFF" });
23727
+ const cT = reg.char({ font: MINISTRY.headFont, pt: s.pt });
23728
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
23729
+ const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
23730
+ const row = tc({ bf: numBf, row: 0, col: 0, w: s.numW, h: s.h, paras: para(String(n), center, cNum) }) + tc({ bf: titleBf, row: 0, col: 1, w: titleW, h: s.h, paras: para(t, left, cT), name: "__kordoc_h3" });
23731
+ return host(ftbl([row], s.numW + titleW, s.h, 2, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
23732
+ }
23733
+ function buildMinistrySubheadBox(n, title, ctx, before) {
23734
+ const { reg, bf } = ctx;
23735
+ const s = MINISTRY.subhead;
23736
+ const text = `${n}. ${plain(title)}`;
23737
+ const w = Math.min(ctx.W, textW(text, MINISTRY.headFont, s.pt) + 1400);
23738
+ const box = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE, fill: s.fill });
23739
+ const cT = reg.char({ font: MINISTRY.headFont, pt: s.pt, color: "#FFFFFF" });
23740
+ const p = reg.para({ align: "LEFT", lineSp: 130, left: 400 });
23741
+ const cell2 = tc({ bf: box, row: 0, col: 0, w, h: s.h, paras: para(text, p, cT), name: "__kordoc_h4" });
23742
+ return host(ftbl([cell2], w, s.h, 1, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
23743
+ }
23744
+ function buildMinistryItemBand(n, text, ctx, before) {
23745
+ const { reg, bf } = ctx;
23746
+ const s = MINISTRY.item;
23747
+ const w = ctx.W;
23748
+ const band = bf.get({ t: s.line, b: s.line, l: NONE, r: NONE, fill: s.fill });
23749
+ const base = { font: MINISTRY.bandFont, pt: s.pt, bold: false };
23750
+ const f = fitOrphanLine(plain(text), MINISTRY.bandFont, s.pt, w - 1800, w - 1800);
23751
+ const styled = f ? { ...base, ratio: f.ratio, spacing: f.spacing } : base;
23752
+ const runs = ministryRuns(circledNumber(n - 1), text, ctx, styled, MINISTRY.blue, MINISTRY.blue);
23753
+ const p = reg.para({ align: "LEFT", lineSp: 130, left: 300, keepWord: false });
23754
+ const cell2 = tc({ bf: band, row: 0, col: 0, w, h: s.h, paras: `<hp:p paraPrIDRef="${p}" styleIDRef="0">${runs}</hp:p>`, name: "__kordoc_h5" });
23755
+ return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), reg.char(base));
23756
+ }
23757
+ function buildMinistrySummaryBox(text, ctx) {
23758
+ const { reg, bf } = ctx;
23759
+ const s = MINISTRY.summary;
23760
+ const w = ctx.W - 600;
23761
+ const avail = w - 280 - 600;
23762
+ const pItem = reg.para({ align: "LEFT", lineSp: 140, left: 300, right: 300, indent: -Math.round(s.pt * 100 * 0.9), keepWord: false });
23763
+ const pRef = reg.para({ align: "LEFT", lineSp: 130, left: 300 + Math.round(s.pt * 100 * 0.9), right: 300, keepWord: false });
23764
+ const baseItem = { font: MINISTRY.bodyFont, pt: s.pt, bold: false };
23765
+ const cRef = reg.char({ font: MINISTRY.refFont, pt: s.refPt });
23766
+ let h = 0;
23767
+ const paras = text.split("\n").map((raw) => raw.trim()).filter(Boolean).map((line) => {
23768
+ const isRef = /^(\*|※)/.test(line);
23769
+ if (isRef) {
23770
+ h += Math.round(s.refPt * 100 * 1.3) * simulateWrap(line, avail, avail, s.refPt * 100, 100, "charAll", { faceClass: "gothic" }).lines;
23771
+ return `<hp:p paraPrIDRef="${pRef}" styleIDRef="0">${generateRuns(line, cRef, inlineMapper(reg, { font: MINISTRY.refFont, pt: s.refPt, bold: false }))}</hp:p>`;
23772
+ }
23773
+ const body = line.replace(/^[▪■□○ㅇ●\-–ㆍ·•]\s*/u, "");
23774
+ h += Math.round(s.pt * 100 * 1.4) * simulateWrap(plain(body), avail, avail, s.pt * 100, 100, "charAll").lines;
23775
+ return `<hp:p paraPrIDRef="${pItem}" styleIDRef="0">${ministryRuns("\u25AA", body, ctx, baseItem, MINISTRY.blue)}</hp:p>`;
23776
+ });
23777
+ h += 280 + 600;
23778
+ const box = bf.get({ t: "thin", b: "thin", l: "thin", r: "thin", fill: s.fill });
23779
+ const cell2 = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), vAlign: "TOP", name: "__kordoc_summary" });
23780
+ return host(ftbl([cell2], w, h, 1, { bottomGap: 900 }), reg.para({ align: "CENTER", lineSp: 100, before: 400 }), reg.char(baseItem));
23781
+ }
23782
+ function buildMinistryAttachBand(title, ctx, before) {
23783
+ const { reg, bf } = ctx;
23784
+ const a = MINISTRY.attach;
23785
+ const w = ctx.W;
23786
+ const titleW = w - a.labelW - a.gap;
23787
+ const labelBf = bf.get({ t: "thin", b: a.thick, l: "thin", r: a.thick, fill: a.fill });
23788
+ const gapBf = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE });
23789
+ const titleBf = bf.get({ t: "thin", b: a.thick, l: "thin", r: a.thick });
23790
+ const t = stripAttachPrefix(plain(title));
23791
+ const fit = fitOneLine(t, MINISTRY.headFont, a.pt, titleW - 800, a.pt - 3);
23792
+ const cLabel = reg.char({ font: MINISTRY.headFont, pt: a.pt, color: "#FFFFFF" });
23793
+ const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
23794
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
23795
+ const row = tc({ bf: labelBf, row: 0, col: 0, w: a.labelW, h: a.h, paras: para("\uBCC4 \uCCA8", center, cLabel) }) + tc({ bf: gapBf, row: 0, col: 1, w: a.gap, h: a.h, paras: para("", center, cT) }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h: a.h, paras: para(t, center, cT), name: "__kordoc_h2" });
23796
+ return host(ftbl([row], w, a.h, 3, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
23797
+ }
23798
+ function isAttachHeading(text) {
23799
+ return /^\[?\s*별\s*첨\s*\]?/u.test(text.trim());
23800
+ }
23801
+
23454
23802
  // src/hwpx/gen-gongmun.ts
23455
- var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes"]);
23803
+ var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes", "ministry"]);
23456
23804
  function usesV5Engine(preset) {
23457
23805
  return V5_PRESETS.has(preset);
23458
23806
  }
@@ -23460,17 +23808,7 @@ var ROMAN = ["\u2160", "\u2161", "\u2162", "\u2163", "\u2164", "\u2165", "\u2166
23460
23808
  function chapterLabel(index, style) {
23461
23809
  return style === "roman" ? `${ROMAN[(index - 1) % 12]}.` : `${index}.`;
23462
23810
  }
23463
- function inlineMapper(reg, base) {
23464
- const norm2 = reg.char({ font: base.font, pt: base.pt, bold: base.bold, ratio: base.ratio, spacing: base.spacing });
23465
- return (id) => {
23466
- if (id === CHAR_BOLD) return reg.char({ font: base.font, pt: base.pt, bold: true, ratio: base.ratio, spacing: base.spacing });
23467
- if (id === CHAR_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: base.bold, italic: true, ratio: base.ratio, spacing: base.spacing });
23468
- if (id === CHAR_BOLD_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: true, italic: true, ratio: base.ratio, spacing: base.spacing });
23469
- if (id === CHAR_NORMAL) return norm2;
23470
- return id;
23471
- };
23472
- }
23473
- function plain(text) {
23811
+ function plain2(text) {
23474
23812
  return text.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/!\[[^\]]*\]\([^)]*\)/g, "");
23475
23813
  }
23476
23814
  function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
@@ -23481,8 +23819,9 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23481
23819
  const g = gongmun;
23482
23820
  const preset = g.preset;
23483
23821
  const isReport = preset === "report" || preset === "plan";
23822
+ const isMinistry = preset === "ministry";
23484
23823
  const W = mmToHwpunit(210 - g.margins.left - g.margins.right);
23485
- const pre = buildOutline(blocks, { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
23824
+ const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
23486
23825
  const scheme = pickScheme(g, pre.hasBoxMarkers);
23487
23826
  const gaejosik = scheme.kind === "gaejosik";
23488
23827
  const outline = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
@@ -23519,6 +23858,28 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23519
23858
  else if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.body.font, pt: scheme.body.pt + 2, bold: true }), void 0, 1));
23520
23859
  if (g.reportInfo) paras.push(generateParagraph(g.reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
23521
23860
  if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
23861
+ } else if (isMinistry) {
23862
+ if (g.cover) {
23863
+ const cover = buildMinistryCover({ title: docTitle, date: g.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()), org: g.cover.org || void 0, label: g.cover.label }, frame);
23864
+ if (g.pageNumbers) cover[0] = cover[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
23865
+ paras.push(...cover);
23866
+ pendingPageBreak = true;
23867
+ }
23868
+ if (g.toc && outline.chapters > 0) {
23869
+ const tocChapters = [];
23870
+ for (const n of outline.nodes) {
23871
+ if (n.kind === "chapter") tocChapters.push({ title: plain2(n.text), subs: [], attach: isAttachHeading(n.text) });
23872
+ else if (n.kind === "heading" && n.level === 3 && tocChapters.length) tocChapters[tocChapters.length - 1].subs.push(plain2(n.text));
23873
+ }
23874
+ const toc = buildMinistryToc(tocChapters, frame);
23875
+ if (g.pageNumbers) toc[0] = toc[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
23876
+ paras.push(...toc);
23877
+ pendingPageBreak = true;
23878
+ }
23879
+ if (!g.cover && !g.toc && docTitle) {
23880
+ paras.push(generateParagraph(docTitle, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20 }), void 0, 1));
23881
+ frontKind = "title";
23882
+ }
23522
23883
  } else if (isReport) {
23523
23884
  if (g.cover) {
23524
23885
  paras.push(...buildReportCover({
@@ -23561,6 +23922,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23561
23922
  let prevItemDepth = -1;
23562
23923
  let nextNode;
23563
23924
  let tableSeq = 0;
23925
+ let sectionSeq = 0, subheadSeq = 0, itemBandSeq = 0;
23926
+ let pendingNewPageNum = isMinistry && (!!g.cover || !!g.toc);
23564
23927
  let chapterStyle = g.h2Marker === "box" || g.h2Marker === "number" || g.h2Marker === "none" || g.h2Marker === "band" ? g.h2Marker : "roman";
23565
23928
  if (!gaejosik) chapterStyle = "number";
23566
23929
  const leadLeft = (depth, pt) => (scheme.levels[Math.min(depth, 7)]?.leadTa ?? 0) * taHu(pt);
@@ -23585,14 +23948,14 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23585
23948
  const availCont = W - geom.left + geom.indent;
23586
23949
  let pt = st.pt, ratio = 100, spacing = 0;
23587
23950
  if (st.oneLine) {
23588
- const fit = fitOneLine(plain(text), st.font, st.pt, availFirst, st.pt, 90);
23951
+ const fit = fitOneLine(plain2(text), st.font, st.pt, availFirst, st.pt, 90);
23589
23952
  if (fit.overflow) warnings.push(`\u25A1 \uD56D\uBAA9\uC774 \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uB450 \uC904\uB85C \uAEBE\uC785\uB2C8\uB2E4 \u2014 \uBB38\uC7A5\uC744 \uC904\uC774\uC138\uC694(\uC7A5\uD3C9 90%\xB7\uC790\uAC04 -5 \uB85C\uB3C4 \uCD08\uACFC): "${node.text.slice(0, 30)}\u2026"`);
23590
23953
  else {
23591
23954
  ratio = fit.ratio;
23592
23955
  spacing = fit.spacing;
23593
23956
  }
23594
23957
  } else if (g.autoFitMinRatio !== null) {
23595
- const f = fitOrphanLine(plain(text), st.font, st.pt, availFirst, availCont);
23958
+ const f = fitOrphanLine(plain2(text), st.font, st.pt, availFirst, availCont);
23596
23959
  if (f) {
23597
23960
  ratio = f.ratio;
23598
23961
  spacing = f.spacing;
@@ -23604,18 +23967,25 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23604
23967
  const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
23605
23968
  const before = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
23606
23969
  const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
23970
+ if (isMinistry) {
23971
+ const mk = node.marker ?? marker;
23972
+ const g2 = node.marker ? levelGeometry(st, mk) : geom2;
23973
+ const paraId2 = reg.para({ align: "JUSTIFY", left: g2.left, indent: g2.indent, before, after: 600, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
23974
+ const runs = ministryRuns(mk, node.text, frame, base, depth === 0 ? MINISTRY.blue : null);
23975
+ return `<hp:p paraPrIDRef="${paraId2}" styleIDRef="${styleId}">${runs}</hp:p>`;
23976
+ }
23607
23977
  const paraId = reg.para({ align: "JUSTIFY", left: geom2.left, indent: geom2.indent, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
23608
23978
  return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), styleId);
23609
23979
  };
23610
23980
  const renderRef = (node) => {
23611
23981
  const st = scheme.ref;
23612
- const marker = "\u203B";
23613
- const left = leadLeft(node.depth, st.pt);
23982
+ const marker = isMinistry ? "*" : "\u203B";
23983
+ const left = isMinistry ? st.leadTa * taHu(st.pt) : leadLeft(node.depth, st.pt);
23614
23984
  const geom = levelGeometry({ ...st, leadTa: 0 }, marker);
23615
23985
  const base = { font: st.font, pt: st.pt, bold: st.bold };
23616
- const f = fitOrphanLine(plain(`${marker} ${node.text}`), st.font, st.pt, W - left, W - left + geom.indent);
23986
+ const f = fitOrphanLine(plain2(`${marker} ${node.text}`), st.font, st.pt, W - left, W - left + geom.indent);
23617
23987
  const baseF = f ? { ...base, ratio: f.ratio, spacing: f.spacing } : base;
23618
- const paraId = reg.para({ align: "JUSTIFY", left, indent: geom.indent, lineSp: st.lineSp ?? scheme.lineSp, keepWord: false });
23988
+ const paraId = reg.para({ align: "JUSTIFY", left, indent: geom.indent, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: false });
23619
23989
  return generateParagraph(`${marker} ${node.text}`, paraId, reg.char(baseF), inlineMapper(reg, baseF));
23620
23990
  };
23621
23991
  const renderPara = (node) => {
@@ -23632,7 +24002,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23632
24002
  }
23633
24003
  let ratio = 100, spacing = 0;
23634
24004
  if (!node.align && g.autoFitMinRatio !== null) {
23635
- const f = fitOrphanLine(plain(node.text), st.font, st.pt, W, W);
24005
+ const f = fitOrphanLine(plain2(node.text), st.font, st.pt, W, W);
23636
24006
  if (f) {
23637
24007
  ratio = f.ratio;
23638
24008
  spacing = f.spacing;
@@ -23644,15 +24014,27 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23644
24014
  };
23645
24015
  const renderChapter = (node) => {
23646
24016
  const st = scheme.chapter;
24017
+ if (isMinistry) {
24018
+ sectionSeq = 0;
24019
+ subheadSeq = 0;
24020
+ itemBandSeq = 0;
24021
+ const first = prevKind === "start" || prevKind === "title";
24022
+ const xml = isAttachHeading(node.text) ? buildMinistryAttachBand(node.text, frame, 0) : (() => {
24023
+ const band = buildMinistryChapterBand(romanOf(node.index), node.text, frame, 0);
24024
+ if (band.overflow) warnings.push(`\uC7A5 \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"`);
24025
+ return band.xml;
24026
+ })();
24027
+ return first ? xml : xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
24028
+ }
23647
24029
  if (chapterStyle === "band") {
23648
24030
  const before2 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
23649
- const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g.bandColor, textColor: g.bandTextColor, titleFont: st.font, titlePt: st.pt }, before2);
24031
+ const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g.bandColor, textColor: g.bandTextColor, titleFont: st.font, titlePt: st.pt }, before2);
23650
24032
  if (band.overflow) warnings.push(`\uC7A5 \uC81C\uBAA9\uC774 \uB760 \uD45C \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"`);
23651
24033
  return band.xml;
23652
24034
  }
23653
24035
  const label = chapterStyle === "none" ? "" : chapterLabel(node.index, chapterStyle === "number" ? "number" : "roman");
23654
24036
  const text = label ? `${label} ${node.text}` : node.text;
23655
- const fit = fitOneLine(plain(text), st.font, st.pt, W, st.pt - 2);
24037
+ const fit = fitOneLine(plain2(text), st.font, st.pt, W, st.pt - 2);
23656
24038
  const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
23657
24039
  const before = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
23658
24040
  const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true, keepWord: false });
@@ -23720,6 +24102,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23720
24102
  case "title":
23721
24103
  break;
23722
24104
  case "summary":
24105
+ if (isMinistry) {
24106
+ xml = buildMinistrySummaryBox(node.text, frame);
24107
+ break;
24108
+ }
23723
24109
  if (isReport && !g.summary) {
23724
24110
  pushSummary(node.text);
23725
24111
  prevKind = node.kind;
@@ -23727,6 +24113,20 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23727
24113
  }
23728
24114
  xml = renderRef({ kind: "ref", depth: 0, text: node.text });
23729
24115
  break;
24116
+ case "heading": {
24117
+ const before = prevKind === "chapter" || prevKind === "heading" || prevKind === "start" ? 0 : BAND_BEFORE_HU;
24118
+ if (node.level <= 3) {
24119
+ subheadSeq = 0;
24120
+ itemBandSeq = 0;
24121
+ xml = buildMinistrySectionBand(++sectionSeq, node.text, frame, before);
24122
+ } else if (node.level === 4) {
24123
+ itemBandSeq = 0;
24124
+ xml = buildMinistrySubheadBox(++subheadSeq, node.text, frame, before);
24125
+ } else xml = buildMinistryItemBand(++itemBandSeq, node.text, frame, before);
24126
+ lastItemFromHeading = true;
24127
+ lastTextNode = node;
24128
+ break;
24129
+ }
23730
24130
  case "chapter":
23731
24131
  lastItemFromHeading = true;
23732
24132
  if (!gaejosik) {
@@ -23767,6 +24167,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
23767
24167
  xml = xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
23768
24168
  pendingPageBreak = false;
23769
24169
  }
24170
+ if (pendingNewPageNum) {
24171
+ xml = xml.replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${newPageNumCtrl(1)}`);
24172
+ pendingNewPageNum = false;
24173
+ }
23770
24174
  paras.push(xml);
23771
24175
  prevKind = node.kind;
23772
24176
  if (node.kind === "item") prevItemDepth = node.depth;
@@ -24798,7 +25202,7 @@ function gongmunLintWarnings(text, limit = 10) {
24798
25202
  // src/hwpx/munche-lint.ts
24799
25203
  function usesGaejosikMunche(preset) {
24800
25204
  const p = normalizeGongmunPreset(preset);
24801
- return p === "gaejosik" || p === "report" || p === "plan";
25205
+ return p === "gaejosik" || p === "report" || p === "plan" || p === "ministry";
24802
25206
  }
24803
25207
  var ITEM_MAX = 70;
24804
25208
  var CONCL_MAX = 60;
@@ -24817,7 +25221,7 @@ function classify(raw) {
24817
25221
  if (li) return { kind: indent >= 2 ? "sub" : "item", body: li[1].trim() };
24818
25222
  return { kind: "para", body: t };
24819
25223
  }
24820
- function plain2(s) {
25224
+ function plain3(s) {
24821
25225
  return s.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/(\*\*|__|~~|`)/g, "").trim();
24822
25226
  }
24823
25227
  function stripQuoted(s) {
@@ -24872,7 +25276,9 @@ function lintMuncheText(text) {
24872
25276
  if (!leadBuf.length) return;
24873
25277
  const block = leadBuf.map((l) => l.body).join(" ").trim();
24874
25278
  const at = leadBuf[0].line;
25279
+ const bulleted = leadBuf.every((l) => /^[▪■□○ㅇ●\-–ㆍ·•*※]/u.test(l.body));
24875
25280
  leadBuf = [];
25281
+ if (bulleted) return;
24876
25282
  if (block.length < 20) return;
24877
25283
  if (!LEAD_ENDING.test(block)) {
24878
25284
  add(
@@ -24920,11 +25326,11 @@ function lintMuncheText(text) {
24920
25326
  continue;
24921
25327
  }
24922
25328
  if (c.kind === "lead") {
24923
- leadBuf.push({ line: no, body: plain2(c.body) });
25329
+ leadBuf.push({ line: no, body: plain3(c.body) });
24924
25330
  continue;
24925
25331
  }
24926
25332
  flushLead();
24927
- const b = plain2(c.body);
25333
+ const b = plain3(c.body);
24928
25334
  if (!b) continue;
24929
25335
  const q = stripQuoted(b);
24930
25336
  if (c.kind === "item" || c.kind === "sub" || c.kind === "concl" || c.kind === "para") {
@@ -27656,7 +28062,7 @@ async function dispatch(format, buffer, opts) {
27656
28062
  }
27657
28063
  async function parseImage(buffer, options) {
27658
28064
  try {
27659
- const { parseImageDocument } = await import("./image-ocr-ZE2VGIC3.js");
28065
+ const { parseImageDocument } = await import("./image-ocr-N6IBZJAK.js");
27660
28066
  const { blocks, warnings } = await parseImageDocument(buffer, options);
27661
28067
  return {
27662
28068
  success: true,
@@ -27715,7 +28121,7 @@ async function parseHwp(buffer, options) {
27715
28121
  async function parsePdf(buffer, options) {
27716
28122
  let parsePdfDocument;
27717
28123
  try {
27718
- const mod = await import("./parser-54OYFBL2.js");
28124
+ const mod = await import("./parser-BK66Y5JD.js");
27719
28125
  parsePdfDocument = mod.parsePdfDocument;
27720
28126
  } catch {
27721
28127
  return {
@@ -27925,4 +28331,4 @@ export {
27925
28331
  parseHwpml,
27926
28332
  fillForm
27927
28333
  };
27928
- //# sourceMappingURL=chunk-F4DZU7VP.js.map
28334
+ //# sourceMappingURL=chunk-EMQZX5QQ.js.map