kordoc 4.0.0 → 4.0.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 (44) hide show
  1. package/dist/{-URVKBXYK.js → -XMKVL3NX.js} +11 -7
  2. package/dist/{chunk-IA5UZZKV.js → chunk-3IYQYBUF.js} +2 -2
  3. package/dist/{chunk-THZVBYHE.js → chunk-67RANGFQ.js} +3 -3
  4. package/dist/{chunk-JUJEKFUF.js → chunk-6SD4X5PT.js} +3 -3
  5. package/dist/{chunk-7ETIIYGI.js → chunk-6YIYFWO2.js} +2 -2
  6. package/dist/{chunk-PGWAF755.js → chunk-AHWRAGXN.js} +477 -89
  7. package/dist/chunk-AHWRAGXN.js.map +1 -0
  8. package/dist/{chunk-DVUNWK6W.js → chunk-IKBIIEVJ.js} +2 -2
  9. package/dist/{chunk-ZKG6SBHV.js → chunk-STMLZDKR.js} +2 -2
  10. package/dist/{chunk-EBSCV3JC.cjs → chunk-WN7LZHII.cjs} +2 -2
  11. package/dist/{chunk-EBSCV3JC.cjs.map → chunk-WN7LZHII.cjs.map} +1 -1
  12. package/dist/cli.js +72 -14
  13. package/dist/cli.js.map +1 -1
  14. package/dist/index.cjs +730 -342
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +93 -6
  17. package/dist/index.d.ts +93 -6
  18. package/dist/index.js +472 -84
  19. package/dist/index.js.map +1 -1
  20. package/dist/mcp.js +60 -14
  21. package/dist/mcp.js.map +1 -1
  22. package/dist/{parser-NXHY37WH.js → parser-2Q2YKEJG.js} +2 -2
  23. package/dist/{parser-LHUO6UUA.js → parser-3HM2VYKK.js} +3 -3
  24. package/dist/{parser-6TV6JKHF.cjs → parser-6AYYGX6J.cjs} +14 -14
  25. package/dist/{parser-6TV6JKHF.cjs.map → parser-6AYYGX6J.cjs.map} +1 -1
  26. package/dist/render-W4VCJE6N.js +10 -0
  27. package/dist/seal-DRPTNHOP.js +10 -0
  28. package/dist/{watch-PNXTAWDR.js → watch-6OHF727O.js} +7 -7
  29. package/package.json +1 -1
  30. package/dist/chunk-PGWAF755.js.map +0 -1
  31. package/dist/render-T5O6QHBJ.js +0 -10
  32. package/dist/seal-7KNR5WFH.js +0 -10
  33. /package/dist/{-URVKBXYK.js.map → -XMKVL3NX.js.map} +0 -0
  34. /package/dist/{chunk-IA5UZZKV.js.map → chunk-3IYQYBUF.js.map} +0 -0
  35. /package/dist/{chunk-THZVBYHE.js.map → chunk-67RANGFQ.js.map} +0 -0
  36. /package/dist/{chunk-JUJEKFUF.js.map → chunk-6SD4X5PT.js.map} +0 -0
  37. /package/dist/{chunk-7ETIIYGI.js.map → chunk-6YIYFWO2.js.map} +0 -0
  38. /package/dist/{chunk-DVUNWK6W.js.map → chunk-IKBIIEVJ.js.map} +0 -0
  39. /package/dist/{chunk-ZKG6SBHV.js.map → chunk-STMLZDKR.js.map} +0 -0
  40. /package/dist/{parser-NXHY37WH.js.map → parser-2Q2YKEJG.js.map} +0 -0
  41. /package/dist/{parser-LHUO6UUA.js.map → parser-3HM2VYKK.js.map} +0 -0
  42. /package/dist/{render-T5O6QHBJ.js.map → render-W4VCJE6N.js.map} +0 -0
  43. /package/dist/{seal-7KNR5WFH.js.map → seal-DRPTNHOP.js.map} +0 -0
  44. /package/dist/{watch-PNXTAWDR.js.map → watch-6OHF727O.js.map} +0 -0
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  sanitizeHref,
21
21
  stripDtd,
22
22
  toArrayBuffer
23
- } from "./chunk-IA5UZZKV.js";
23
+ } from "./chunk-3IYQYBUF.js";
24
24
  import {
25
25
  parsePageRange
26
26
  } from "./chunk-GE43BE46.js";
@@ -20950,8 +20950,9 @@ function fitRatioForFewerLines(text, firstWidth, contWidth, height, baseRatio, m
20950
20950
  }
20951
20951
 
20952
20952
  // src/hwpx/gaejosik.ts
20953
- var GAEJOSIK_BULLETS = ["\u25A1", "\u25CB", "\u2015", "\u318D"];
20954
- function gaejosikMarker(depth) {
20953
+ var GAEJOSIK_BULLETS = ["\u25A1", "\u25CB", "-", "\u318D"];
20954
+ function gaejosikMarker(depth, bullet2 = "\u25CB") {
20955
+ if (depth === 1) return bullet2;
20955
20956
  return GAEJOSIK_BULLETS[Math.min(depth, GAEJOSIK_BULLETS.length - 1)];
20956
20957
  }
20957
20958
  function gaejosikSizes(bodyHeight, ov = {}) {
@@ -20987,11 +20988,11 @@ function gaejosikSpaceBefore(depth, bodyHeight) {
20987
20988
  const ui = [3e3, 2e3, 1200, 600][Math.min(depth, 3)];
20988
20989
  return Math.round(ui * s);
20989
20990
  }
20990
- function gaejosikLevelIndent(depth, bodyHeight, sizes = {}) {
20991
+ function gaejosikLevelIndent(depth, bodyHeight, sizes = {}, bullet2 = "\u25CB") {
20991
20992
  const lefts = [0, 1, 1.5, 2];
20992
20993
  const left = depth <= 3 ? Math.round(lefts[depth] * bodyHeight) : Math.round((2 + (depth - 3) * 0.5) * bodyHeight);
20993
20994
  const h = depth === 0 ? gaejosikSizes(bodyHeight, sizes).dae : bodyHeight;
20994
- return { left, indent: -markerWidth(gaejosikMarker(depth), h) };
20995
+ return { left, indent: -markerWidth(gaejosikMarker(depth, bullet2), h) };
20995
20996
  }
20996
20997
  function gaejosikChamIndent(bodyHeight, sizes = {}) {
20997
20998
  const cham = gaejosikSizes(bodyHeight, sizes).cham;
@@ -21059,7 +21060,7 @@ function bodyTitleGeom(bodyHeight, ov = {}) {
21059
21060
  }
21060
21061
 
21061
21062
  // src/hwpx/gongmun.ts
21062
- var OFFICIAL_MARGINS = { top: 20, bottom: 10, left: 20, right: 20 };
21063
+ var OFFICIAL_MARGINS = { top: 20, bottom: 15, left: 20, right: 15 };
21063
21064
  var GAEJOSIK_MARGINS = { top: 15, bottom: 15, left: 20, right: 20 };
21064
21065
  var GAEJOSIK_HEADER_FOOTER = 4251;
21065
21066
  var PRESET_DEFAULTS = {
@@ -21068,7 +21069,9 @@ var PRESET_DEFAULTS = {
21068
21069
  plan: { bodyPt: 15, lineSpacing: 160, numbering: "standard" },
21069
21070
  notice: { bodyPt: 15, lineSpacing: 160, numbering: "standard" },
21070
21071
  minutes: { bodyPt: 14, lineSpacing: 130, numbering: "standard" },
21071
- gaejosik: { bodyPt: 15, lineSpacing: 160, numbering: "gaejosik" }
21072
+ gaejosik: { bodyPt: 15, lineSpacing: 160, numbering: "gaejosik" },
21073
+ // 보도자료 — 실측(국토부 실물): 본문 바탕 14pt 160%, □→ㅇ→*(각주) 부호
21074
+ press: { bodyPt: 14, lineSpacing: 160, numbering: "report" }
21072
21075
  };
21073
21076
  var PRESET_ALIAS = {
21074
21077
  official: "official",
@@ -21091,12 +21094,17 @@ var PRESET_ALIAS = {
21091
21094
  \uAC1C\uC870\uC2DD: "gaejosik",
21092
21095
  \uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
21093
21096
  \uC815\uBD80\uBCF4\uACE0\uC11C: "gaejosik",
21094
- \uC815\uBD80\uD45C\uC900\uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik"
21097
+ \uC815\uBD80\uD45C\uC900\uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
21098
+ press: "press",
21099
+ \uBCF4\uB3C4\uC790\uB8CC: "press"
21095
21100
  };
21096
21101
  function normalizeGongmunPreset(preset) {
21097
21102
  if (!preset) return "official";
21098
21103
  return PRESET_ALIAS[preset.trim()] ?? "official";
21099
21104
  }
21105
+ function usesReportFonts(preset) {
21106
+ return preset === "gaejosik" || preset === "report" || preset === "plan";
21107
+ }
21100
21108
  function resolveGongmun(opts) {
21101
21109
  const preset = normalizeGongmunPreset(opts.preset);
21102
21110
  const d = PRESET_DEFAULTS[preset];
@@ -21105,13 +21113,14 @@ function resolveGongmun(opts) {
21105
21113
  const coverOn = opts.cover !== void 0 ? opts.cover !== false : preset === "gaejosik";
21106
21114
  const coverOpts = typeof opts.cover === "object" ? opts.cover : {};
21107
21115
  const gaejosik = preset === "gaejosik";
21116
+ const reportFamily = gaejosik || preset === "report" || preset === "plan" || preset === "notice" || preset === "press";
21108
21117
  return {
21109
21118
  preset,
21110
21119
  bodyFont: opts.bodyFont ?? "myeongjo",
21111
21120
  bodyHeight: Math.round(bodyPt * 100),
21112
21121
  lineSpacing: opts.lineSpacing ?? d.lineSpacing,
21113
21122
  numbering: opts.numbering ?? d.numbering,
21114
- margins: opts.margins ?? (gaejosik ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
21123
+ margins: opts.margins ?? (reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
21115
21124
  centerTitle: opts.centerTitle ?? true,
21116
21125
  autoFitMinRatio,
21117
21126
  cover: coverOn ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "" } : null,
@@ -21120,12 +21129,26 @@ function resolveGongmun(opts) {
21120
21129
  sizes: opts.sizes ?? {},
21121
21130
  // 쪽번호 — 보고서 계열 관행(실측: 2_보고서 양식·추진계획·공고문 전부 하단 중앙)
21122
21131
  pageNumbers: opts.pageNumbers ?? (gaejosik || preset === "report" || preset === "plan"),
21123
- headerFooter: gaejosik ? GAEJOSIK_HEADER_FOOTER : 0,
21132
+ // 머리말·꼬리말 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
21133
+ // 기안문 0(실결재 41/60건 h0/f0)
21134
+ headerFooter: usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
21124
21135
  // "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
21125
21136
  endMark: opts.endMark ?? preset === "official",
21126
21137
  approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
21127
21138
  // 본문 제목박스 — 실측(GT3·GT12): 목차 뒤 본문 시작에 제목 반복. 표지 켜진 개조식 기본
21128
- bodyTitleBox: opts.bodyTitleBox ?? (gaejosik && coverOn)
21139
+ bodyTitleBox: opts.bodyTitleBox ?? (gaejosik && coverOn),
21140
+ // h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
21141
+ h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "box" : preset === "notice" ? "number" : "none"),
21142
+ // 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
21143
+ bullet2: opts.bullet2 ?? (preset === "notice" || preset === "press" ? "\u3147" : "\u25CB"),
21144
+ // 단일 형제 부호 생략 — 규정이지만 부호 없는 계단이 실무 눈에 어색 (실무자 QA)
21145
+ suppressSingle: opts.suppressSingle ?? false,
21146
+ docHead: preset === "official" && opts.docHead ? opts.docHead : null,
21147
+ docFoot: preset === "official" && opts.docFoot ? opts.docFoot : null,
21148
+ reportInfo: opts.reportInfo?.trim() || null,
21149
+ noticeHead: preset === "notice" && opts.noticeHead ? opts.noticeHead : null,
21150
+ // 보도자료 머리박스는 프리셋 자체가 요구 — 옵션 미지정이어도 빈 객체
21151
+ press: preset === "press" ? opts.press ?? {} : null
21129
21152
  };
21130
21153
  }
21131
21154
  var HANGUL_INITIALS = [0, 2, 3, 5, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18];
@@ -21143,6 +21166,7 @@ function circledHangul(n) {
21143
21166
  return String.fromCodePoint(12910 + n % 14);
21144
21167
  }
21145
21168
  var REPORT_BULLETS = ["\u25A1", "\u25CB", "-", "\u318D"];
21169
+ var PRESS_BULLETS = ["\u25A1", "\u3147", "*", "\u318D"];
21146
21170
  function standardMarker(depth, n) {
21147
21171
  switch (depth) {
21148
21172
  case 0:
@@ -21165,17 +21189,19 @@ function standardMarker(depth, n) {
21165
21189
  return circledHangul(n);
21166
21190
  }
21167
21191
  }
21168
- function reportMarker(depth) {
21169
- return REPORT_BULLETS[Math.min(depth, REPORT_BULLETS.length - 1)];
21192
+ function reportMarker(depth, bullet2 = "\u25CB", press = false) {
21193
+ const bullets = press ? PRESS_BULLETS : REPORT_BULLETS;
21194
+ const m = bullets[Math.min(depth, bullets.length - 1)];
21195
+ return depth === 1 && !press ? bullet2 : m;
21170
21196
  }
21171
21197
  function markerWidth(marker, bodyHeight) {
21172
21198
  let em = SPACE_EM_FIXED;
21173
21199
  for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
21174
21200
  return Math.round(em / 1e3 * bodyHeight);
21175
21201
  }
21176
- function levelIndent(depth, bodyHeight, numbering, sizes = {}) {
21177
- if (numbering === "gaejosik") return gaejosikLevelIndent(depth, bodyHeight, sizes);
21178
- const marker = numbering === "report" ? reportMarker(depth) : standardMarker(depth, 0);
21202
+ function levelIndent(depth, bodyHeight, numbering, sizes = {}, bullet2 = "\u25CB", press = false) {
21203
+ if (numbering === "gaejosik") return gaejosikLevelIndent(depth, bodyHeight, sizes, bullet2);
21204
+ const marker = numbering === "report" ? reportMarker(depth, bullet2, press) : standardMarker(depth, 0);
21179
21205
  return { left: Math.round(depth * bodyHeight), indent: -markerWidth(marker, bodyHeight) };
21180
21206
  }
21181
21207
  function computeSuppression(depths) {
@@ -21192,8 +21218,10 @@ function computeSuppression(depths) {
21192
21218
  return keys.map((k) => (counts.get(k) ?? 0) <= 1);
21193
21219
  }
21194
21220
  var GongmunNumberer = class {
21195
- constructor(numbering) {
21221
+ constructor(numbering, bullet2 = "\u25CB", press = false) {
21196
21222
  this.numbering = numbering;
21223
+ this.bullet2 = bullet2;
21224
+ this.press = press;
21197
21225
  }
21198
21226
  counts = [];
21199
21227
  /** depth 항목 하나에 대한 마커. suppress=true면 빈 문자열(부호 없음) */
@@ -21202,8 +21230,8 @@ var GongmunNumberer = class {
21202
21230
  const n = this.counts[depth] ?? 0;
21203
21231
  this.counts[depth] = n + 1;
21204
21232
  if (suppress) return "";
21205
- if (this.numbering === "gaejosik") return gaejosikMarker(depth);
21206
- return this.numbering === "report" ? reportMarker(depth) : standardMarker(depth, n);
21233
+ if (this.numbering === "gaejosik") return gaejosikMarker(depth, this.bullet2);
21234
+ return this.numbering === "report" ? reportMarker(depth, this.bullet2, this.press) : standardMarker(depth, n);
21207
21235
  }
21208
21236
  reset() {
21209
21237
  this.counts = [];
@@ -21270,10 +21298,10 @@ function headingCharPrId(level) {
21270
21298
  if (level === 3) return CHAR_H3;
21271
21299
  return CHAR_H4;
21272
21300
  }
21273
- function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100, keepFontOnBold = false) {
21301
+ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100) {
21274
21302
  const boldAttr = bold ? ` bold="1"` : "";
21275
21303
  const italicAttr = italic ? ` italic="1"` : "";
21276
- const effFont = bold && !keepFontOnBold ? 2 : fontId;
21304
+ const effFont = fontId;
21277
21305
  const boldEl = bold ? `<hh:bold/>` : "";
21278
21306
  return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
21279
21307
  <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
@@ -21325,6 +21353,7 @@ var GJ_PARA_COVER = 21;
21325
21353
  var GJ_PARA_TOC_ITEM = 22;
21326
21354
  var GJ_PARA_CHAPTER = 23;
21327
21355
  var GJ_PARA_BAR = 24;
21356
+ var GONGMUN_LIST_PLAIN_BASE = 25;
21328
21357
  var GJ_BF_CHAPTER_NUM = 3;
21329
21358
  var GJ_BF_CHAPTER_GAP = 4;
21330
21359
  var GJ_BF_CHAPTER_TITLE = 5;
@@ -21363,9 +21392,13 @@ function newPageNumCtrl(num4 = 1) {
21363
21392
  function pageHidingCtrl(hideHeader = false) {
21364
21393
  return `<hp:ctrl><hp:pageHiding hideHeader="${hideHeader ? 1 : 0}" hideFooter="0" hideMasterPage="0" hideBorder="0" hideFill="0" hidePageNum="1"/></hp:ctrl>`;
21365
21394
  }
21395
+ var GONGMUN_TBL_CHAR = 11;
21396
+ var GONGMUN_TBL_CHAR_BOLD = 12;
21397
+ var GONGMUN_TBL_PT = 1200;
21366
21398
  var CHAR_VARIANT_BASE = 11;
21367
- function charVariantBase(gaejosik) {
21368
- return gaejosik ? CHAR_VARIANT_BASE + GJ_CHAR_COUNT : CHAR_VARIANT_BASE;
21399
+ function charVariantBase(measured, isGongmun = true) {
21400
+ if (!isGongmun) return CHAR_VARIANT_BASE;
21401
+ return measured ? 11 + GJ_CHAR_COUNT : 11 + 2;
21369
21402
  }
21370
21403
  var GONGMUN_BODY_RATIO = 95;
21371
21404
 
@@ -21613,7 +21646,7 @@ function generateManifest(chartParts = []) {
21613
21646
  </opf:package>`;
21614
21647
  }
21615
21648
  function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls = []) {
21616
- const gaejosik = gongmun?.preset === "gaejosik";
21649
+ const measured = !!gongmun && usesReportFonts(gongmun.preset);
21617
21650
  let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
21618
21651
  if (gongmun) {
21619
21652
  body = gongmun.bodyHeight;
@@ -21623,7 +21656,9 @@ function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls
21623
21656
  h3 = body;
21624
21657
  h4 = Math.max(body - 100, 1300);
21625
21658
  }
21626
- const bodyFont = gaejosik ? 4 : 0;
21659
+ const bodyFont = measured ? 4 : 0;
21660
+ const hFont = measured ? 3 : 1;
21661
+ const hBold = !measured;
21627
21662
  const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
21628
21663
  const rows = [
21629
21664
  charPr(0, body, false, false, bodyFont, theme.body, bodyRatio),
@@ -21631,25 +21666,31 @@ function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls
21631
21666
  charPr(2, body, false, true, bodyFont, theme.body, bodyRatio),
21632
21667
  charPr(3, body, true, true, bodyFont, theme.body, bodyRatio),
21633
21668
  charPr(4, code, false, false, 1),
21634
- charPr(5, h1, true, false, 1, theme.h1),
21635
- charPr(6, h2, true, false, 1, theme.h2),
21636
- charPr(7, h3, true, false, 1, theme.h3),
21637
- charPr(8, h4, true, false, 1, theme.h4),
21669
+ charPr(5, h1, hBold, false, hFont, theme.h1),
21670
+ charPr(6, h2, hBold, false, hFont, theme.h2),
21671
+ charPr(7, h3, hBold, false, hFont, theme.h3),
21672
+ charPr(8, h4, hBold, false, hFont, theme.h4),
21638
21673
  charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, bodyFont, theme.tableHeader),
21639
21674
  charPr(CHAR_QUOTE, body, false, true, bodyFont, theme.quote)
21640
21675
  ];
21641
- if (gaejosik) {
21676
+ if (gongmun && !measured) {
21677
+ rows.push(
21678
+ charPr(11, GONGMUN_TBL_PT, false, false, bodyFont, theme.body),
21679
+ charPr(12, GONGMUN_TBL_PT, true, false, bodyFont, theme.body)
21680
+ );
21681
+ }
21682
+ if (measured) {
21642
21683
  const sz = gaejosikSizes(body, gongmun.sizes);
21643
21684
  rows.push(
21644
21685
  charPr(11, sz.dae, false, false, 3),
21645
21686
  // □ HY헤드라인M
21646
- charPr(12, sz.dae, true, false, 3, "#000000", 100, true),
21687
+ charPr(12, sz.dae, true, false, 3),
21647
21688
  // □ bold
21648
21689
  charPr(13, sz.cham, false, false, 5),
21649
21690
  // ※ 한양중고딕
21650
- charPr(14, sz.cham, true, false, 5, "#000000", 100, true),
21691
+ charPr(14, sz.cham, true, false, 5),
21651
21692
  // ※ bold
21652
- charPr(15, sz.chapter, true, false, 4, "#FFFFFF", 100, true),
21693
+ charPr(15, sz.chapter, true, false, 4, "#FFFFFF"),
21653
21694
  // 장 로마숫자(흰)
21654
21695
  charPr(16, sz.chapter, false, false, 3),
21655
21696
  // 장 제목
@@ -21657,15 +21698,15 @@ function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls
21657
21698
  // 표지 제목
21658
21699
  charPr(18, sz.coverSub, false, false, 3),
21659
21700
  // 표지 날짜·기관명
21660
- charPr(19, sz.tocLabel, true, false, 3, "#000000", 100, true),
21701
+ charPr(19, sz.tocLabel, true, false, 3),
21661
21702
  // 목 차
21662
- charPr(20, sz.tocRoman, true, false, 6, "#000000", 100, true),
21703
+ charPr(20, sz.tocRoman, true, false, 6),
21663
21704
  // 목차 로마숫자(한양신명조)
21664
21705
  charPr(21, sz.tocItem, false, false, 3),
21665
21706
  // 목차 항목
21666
21707
  charPr(22, sz.table, false, false, 7),
21667
21708
  // 표 셀(맑은 고딕 12pt)
21668
- charPr(23, sz.table, true, false, 7, "#000000", 100, true),
21709
+ charPr(23, sz.table, true, false, 7),
21669
21710
  // 표 셀 bold
21670
21711
  charPr(24, sz.bar, false, false, 4),
21671
21712
  // 표지 바 셀 빈 문단(6pt)
@@ -21704,10 +21745,11 @@ ${base2.join("\n")}
21704
21745
  }
21705
21746
  const ls = gongmun.lineSpacing;
21706
21747
  const titleAlign = gongmun.centerTitle ? "CENTER" : "LEFT";
21748
+ const h2Geom = gongmun.h2Marker !== "none" ? { spaceBefore: Math.round(gongmun.bodyHeight * 2), spaceAfter: 0, indent: -markerWidth(gongmun.h2Marker === "box" ? "\u25A1" : "1.", 1600) } : { spaceBefore: 600, spaceAfter: 150 };
21707
21749
  const base = [
21708
21750
  paraPr(0, { lineSpacing: ls, keepWord: true }),
21709
21751
  paraPr(1, { align: titleAlign, spaceBefore: 400, spaceAfter: 400, lineSpacing: ls, keepWord: true }),
21710
- paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: ls, keepWord: true }),
21752
+ paraPr(2, { align: "LEFT", ...h2Geom, lineSpacing: ls, keepWord: true }),
21711
21753
  paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
21712
21754
  paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
21713
21755
  paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400, keepWord: true }),
@@ -21715,16 +21757,16 @@ ${base2.join("\n")}
21715
21757
  paraPr(7, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true })
21716
21758
  ];
21717
21759
  for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
21718
- const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes);
21719
- const sectionGap = gongmun.numbering === "gaejosik" ? gaejosikSpaceBefore(d, gongmun.bodyHeight) : gongmun.numbering === "report" && d === 0 ? Math.round(gongmun.bodyHeight * 0.5) : 0;
21720
- const keepNext = gongmun.numbering === "gaejosik" && d === 0;
21760
+ const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, gongmun.preset === "press");
21761
+ const sectionGap = gongmun.numbering === "gaejosik" || gongmun.numbering === "report" ? gaejosikSpaceBefore(d, gongmun.bodyHeight) : 0;
21762
+ const keepNext = (gongmun.numbering === "gaejosik" || gongmun.numbering === "report") && d === 0;
21721
21763
  base.push(paraPr(GONGMUN_LIST_BASE + d, { align: "JUSTIFY", lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true, keepWithNext: keepNext }));
21722
21764
  }
21723
21765
  base.push(paraPr(GONGMUN_CENTER, { align: "CENTER", lineSpacing: ls, keepWord: true }));
21724
21766
  base.push(paraPr(GONGMUN_RIGHT, { align: "RIGHT", lineSpacing: ls, keepWord: true }));
21725
21767
  base.push(paraPr(GONGMUN_TBL_CENTER, { align: "CENTER", lineSpacing: 130, keepWord: true }));
21726
21768
  base.push(paraPr(GONGMUN_TBL_LEFT, { align: "LEFT", lineSpacing: 130, keepWord: true }));
21727
- if (gongmun.preset === "gaejosik") {
21769
+ {
21728
21770
  const cham = gaejosikChamIndent(gongmun.bodyHeight, gongmun.sizes);
21729
21771
  const toc = gaejosikTocItemIndent(gongmun.bodyHeight, gongmun.sizes);
21730
21772
  base.push(
@@ -21737,6 +21779,11 @@ ${base2.join("\n")}
21737
21779
  paraPr(GJ_PARA_BAR, { align: "CENTER", lineSpacing: 70, keepWord: true })
21738
21780
  );
21739
21781
  }
21782
+ for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
21783
+ const { left } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, gongmun.preset === "press");
21784
+ const sectionGap = gongmun.numbering === "gaejosik" || gongmun.numbering === "report" ? gaejosikSpaceBefore(d, gongmun.bodyHeight) : 0;
21785
+ base.push(paraPr(GONGMUN_LIST_PLAIN_BASE + d, { align: "JUSTIFY", lineSpacing: ls, left, indent: 0, spaceBefore: sectionGap, keepWord: true }));
21786
+ }
21740
21787
  return `<hh:paraProperties itemCnt="${base.length}">
21741
21788
  ${base.join("\n")}
21742
21789
  </hh:paraProperties>`;
@@ -21758,14 +21805,15 @@ function fontEntry(id, face, weight = 6) {
21758
21805
  </hh:font>`;
21759
21806
  }
21760
21807
  function buildFontFaces(gongmun, bodyFace) {
21761
- if (gongmun?.preset === "gaejosik") {
21808
+ if (gongmun && usesReportFonts(gongmun.preset)) {
21762
21809
  const ov = gongmun.fonts;
21810
+ const bodyDefault = gongmun.preset !== "gaejosik" && gongmun.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD734\uBA3C\uBA85\uC870";
21763
21811
  const faces = [
21764
21812
  "\uD568\uCD08\uB86C\uBC14\uD0D5",
21765
21813
  "\uD568\uCD08\uB86C\uB3CB\uC6C0",
21766
21814
  "HY\uACAC\uACE0\uB515",
21767
21815
  ov.heading ?? "HY\uD5E4\uB4DC\uB77C\uC778M",
21768
- ov.body ?? "\uD734\uBA3C\uBA85\uC870",
21816
+ ov.body ?? bodyDefault,
21769
21817
  ov.ref ?? "\uD55C\uC591\uC911\uACE0\uB515",
21770
21818
  "\uD55C\uC591\uC2E0\uBA85\uC870",
21771
21819
  ov.table ?? "\uB9D1\uC740 \uACE0\uB515"
@@ -22115,7 +22163,9 @@ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
22115
22163
  if (gongmun.numbering === "gaejosik" && (depth === 0 || (block.text || "").trimStart().startsWith("\u203B"))) continue;
22116
22164
  const content = plainRenderText(block.text || "");
22117
22165
  text = marker ? `${marker} ${content}` : content;
22118
- const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
22166
+ const li = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, gongmun.preset === "press");
22167
+ const left = li.left;
22168
+ const indent = marker ? li.indent : 0;
22119
22169
  firstW = pageW - left - Math.max(indent, 0);
22120
22170
  contW = pageW - left - Math.max(-indent, 0);
22121
22171
  } else if (block.type === "paragraph") {
@@ -22143,7 +22193,7 @@ function variantMapper(fit, blockIdx, base = CHAR_VARIANT_BASE) {
22143
22193
  }
22144
22194
  function precomputeGongmunList(blocks, gongmun) {
22145
22195
  const result = /* @__PURE__ */ new Map();
22146
- const depthOffset = gongmun.numbering === "gaejosik" && blocks.some((b) => b.type === "heading" && b.level === 3) ? 1 : 0;
22196
+ const depthOffset = gongmun.numbering === "gaejosik" && blocks.some((b) => b.type === "heading" && b.level === 3) || gongmun.numbering === "standard" && gongmun.h2Marker === "number" && blocks.some((b) => b.type === "heading" && b.level === 2) ? 1 : 0;
22147
22197
  let i = 0;
22148
22198
  while (i < blocks.length) {
22149
22199
  if (blocks[i].type !== "list_item") {
@@ -22170,8 +22220,8 @@ function precomputeGongmunList(blocks, gongmun) {
22170
22220
  break;
22171
22221
  }
22172
22222
  const depths = run.map((bi) => Math.min(Math.max((blocks[bi].indent || 0) + depthOffset, 0), GONGMUN_LIST_LEVELS - 1));
22173
- const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
22174
- const numberer = new GongmunNumberer(gongmun.numbering);
22223
+ const suppress = gongmun.suppressSingle && gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
22224
+ const numberer = new GongmunNumberer(gongmun.numbering, gongmun.bullet2, gongmun.preset === "press");
22175
22225
  run.forEach((bi, k) => {
22176
22226
  const marker = numberer.next(depths[k], suppress[k]);
22177
22227
  result.set(bi, { marker, depth: depths[k] });
@@ -22201,7 +22251,8 @@ function cell(opts) {
22201
22251
  return `<hp:tc 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>`;
22202
22252
  }
22203
22253
  function table(rows, w, h, cols, tblBf = 1) {
22204
- 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="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>`;
22254
+ const outLR = w + 566 > 48e3 ? 0 : 283;
22255
+ 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>`;
22205
22256
  }
22206
22257
  function buildGaejosikCover(title, gongmun) {
22207
22258
  const g = coverGeom(gongmun.bodyHeight, gongmun.sizes);
@@ -22864,7 +22915,7 @@ function profileColWidths(tp, colCnt) {
22864
22915
  if (tp.width) return Array(colCnt).fill(Math.floor(tp.width / colCnt));
22865
22916
  return null;
22866
22917
  }
22867
- var CELL_PAD = 582;
22918
+ var CELL_PAD = 1200;
22868
22919
  function cellContentWidth(text, charHeight) {
22869
22920
  let max = 0;
22870
22921
  for (const seg of text.replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
@@ -22873,35 +22924,51 @@ function cellContentWidth(text, charHeight) {
22873
22924
  }
22874
22925
  return max;
22875
22926
  }
22876
- function computeColWidths(colMax, totalWidth) {
22927
+ function cellMinWordWidth(text, charHeight) {
22928
+ let max = 0;
22929
+ for (const seg of text.replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
22930
+ for (const word of seg.trim().split(/\s+/)) {
22931
+ const w = measureTextWidth(word, charHeight, 100);
22932
+ if (w > max) max = w;
22933
+ }
22934
+ }
22935
+ return max;
22936
+ }
22937
+ function computeColWidths(colMax, totalWidth, colMinWord = []) {
22877
22938
  const colCnt = colMax.length;
22878
22939
  const minW = Math.min(Math.max(2e3, Math.round(totalWidth * 0.06)), Math.floor(totalWidth / colCnt));
22879
22940
  const cap = Math.round(totalWidth * 0.8);
22880
22941
  const raw = colMax.map((w) => Math.min(Math.max(w + CELL_PAD, minW), cap));
22942
+ const floor = raw.map((r, i) => Math.min(Math.max(minW, (colMinWord[i] ?? 0) + CELL_PAD), r, cap));
22881
22943
  const widths = Array(colCnt).fill(0);
22882
22944
  const free = new Set(raw.map((_, i) => i));
22883
22945
  let budget = totalWidth;
22884
- const evenShare = Math.floor(totalWidth / colCnt);
22885
- for (let i = 0; i < colCnt; i++) {
22886
- const fixed = Math.min(Math.round(raw[i] * 1.12), evenShare);
22887
- if (raw[i] <= evenShare && free.size > 1) {
22888
- widths[i] = fixed;
22889
- free.delete(i);
22890
- budget -= fixed;
22891
- }
22892
- }
22893
- if (free.size === 0) {
22946
+ let sumFloorFree = floor.reduce((a, b) => a + b, 0);
22947
+ for (const i of [...raw.keys()].sort((a, b) => raw[a] - raw[b])) {
22948
+ if (free.size <= 1) break;
22949
+ const fixed = Math.round(raw[i] * 1.12);
22950
+ if (budget - fixed < sumFloorFree - floor[i]) break;
22951
+ widths[i] = fixed;
22952
+ free.delete(i);
22953
+ budget -= fixed;
22954
+ sumFloorFree -= floor[i];
22955
+ }
22956
+ const sumFreeRaw = [...free].reduce((a, i) => a + raw[i], 0);
22957
+ if (budget > sumFreeRaw * 1.6) {
22894
22958
  const sumRaw = raw.reduce((a, b) => a + b, 0);
22895
- for (let i = 0; i < colCnt; i++) widths[i] += Math.floor(raw[i] / sumRaw * (totalWidth - sumRaw));
22959
+ for (let i = 0; i < colCnt; i++) widths[i] = raw[i] + Math.floor(raw[i] / sumRaw * (totalWidth - sumRaw));
22960
+ free.clear();
22896
22961
  } else {
22897
22962
  for (; ; ) {
22898
22963
  const sum2 = [...free].reduce((a, i) => a + raw[i], 0);
22899
- const short = [...free].filter((i) => raw[i] / sum2 * budget < minW);
22964
+ const short = [...free].filter((i) => raw[i] / sum2 * budget < floor[i]);
22900
22965
  if (short.length === 0) break;
22966
+ const shortSum = short.reduce((a, i) => a + floor[i], 0);
22967
+ const scale = Math.min(1, (budget - (free.size - short.length) * minW) / shortSum);
22901
22968
  for (const i of short) {
22902
- widths[i] = minW;
22969
+ widths[i] = Math.max(minW, Math.floor(floor[i] * scale));
22903
22970
  free.delete(i);
22904
- budget -= minW;
22971
+ budget -= widths[i];
22905
22972
  }
22906
22973
  if (free.size === 0) break;
22907
22974
  }
@@ -22934,12 +23001,15 @@ function generateTable(rows, theme, style = null, remap = null, seq = 0) {
22934
23001
  const prof = takeProfile(remap, rowCnt, colCnt, anchorOfMarkdownCell(rows[0]?.[0] ?? ""), seq);
22935
23002
  const colMax = Array(colCnt).fill(0);
22936
23003
  const colMaxBody = Array(colCnt).fill(0);
23004
+ const colMinWord = Array(colCnt).fill(0);
22937
23005
  rows.forEach((row, r) => row.forEach((cell2, c) => {
22938
23006
  const w = cellContentWidth(cell2, measureH);
22939
23007
  if (w > colMax[c]) colMax[c] = w;
22940
23008
  if (r > 0 && w > colMaxBody[c]) colMaxBody[c] = w;
23009
+ const mw = cellMinWordWidth(cell2, measureH);
23010
+ if (mw > colMinWord[c]) colMinWord[c] = mw;
22941
23011
  }));
22942
- const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalW);
23012
+ const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalW, colMinWord);
22943
23013
  const colCentered = colWidths.map((w, c) => colMaxBody[c] + CELL_PAD <= w);
22944
23014
  const labelCol0 = !!reg && colCnt === 2 && colCentered[0] && rows.every((r) => (r[0] ?? "").replace(/\*\*|__|`/g, "").length <= 12);
22945
23015
  const tblId = nextTableId();
@@ -23018,16 +23088,19 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
23018
23088
  const prof = takeProfile(remap, rowCnt, colCnt, first ? anchorOfHtmlCell(first.inner) : "", seq);
23019
23089
  const colMax = Array(colCnt).fill(0);
23020
23090
  const colMaxBody = Array(colCnt).fill(0);
23091
+ const colMinWord = Array(colCnt).fill(0);
23021
23092
  const cellLines = placed.map((cell2) => htmlCellInnerToLines(cell2.inner).lines);
23022
23093
  placed.forEach((cell2, i) => {
23023
23094
  const w = Math.max(...cellLines[i].map((l) => measureTextWidth(unescapeHtml(l).trim(), measureH, 100)), 0) / cell2.colSpan;
23095
+ const mw = cell2.colSpan === 1 ? Math.max(...cellLines[i].map((l) => cellMinWordWidth(unescapeHtml(l), measureH)), 0) : 0;
23024
23096
  for (let dc = 0; dc < cell2.colSpan; dc++) {
23025
23097
  const c = cell2.c + dc;
23026
23098
  if (w > colMax[c]) colMax[c] = w;
23027
23099
  if (!cell2.isHeader && w > colMaxBody[c]) colMaxBody[c] = w;
23100
+ if (dc === 0 && mw > colMinWord[c]) colMinWord[c] = mw;
23028
23101
  }
23029
23102
  });
23030
- const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalWidth);
23103
+ const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalWidth, colMinWord);
23031
23104
  const colCentered = colWidths.map((w, c) => colMaxBody[c] + CELL_PAD <= w);
23032
23105
  const cellH = style ? Math.round(measureH * 1.6) + 282 : 1500;
23033
23106
  const tblW = colWidths.reduce((a, b) => a + b, 0);
@@ -23139,6 +23212,118 @@ function buildTitleBox(title, titleCharPr, bodyWidth, reg) {
23139
23212
  return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`;
23140
23213
  }
23141
23214
 
23215
+ // src/hwpx/gen-docframe.ts
23216
+ function docframeIds(base) {
23217
+ 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 };
23218
+ }
23219
+ function docframeActive(g) {
23220
+ return !!(g.docHead || g.docFoot || g.reportInfo || g.noticeHead || g.press);
23221
+ }
23222
+ function docframeCharPrXmls(base, measured) {
23223
+ const body = measured ? 4 : 0;
23224
+ const ids = docframeIds(base);
23225
+ return [
23226
+ charPr(ids.org, 1800, true, false, body),
23227
+ charPr(ids.sender, 2200, true, false, body),
23228
+ charPr(ids.foot, 900, false, false, body),
23229
+ charPr(ids.small, 1200, false, false, body),
23230
+ charPr(ids.pressLabel, 2e3, true, false, body),
23231
+ charPr(ids.pressHead, 1e3, true, false, body),
23232
+ charPr(ids.pressTitle, 2500, true, false, body),
23233
+ charPr(ids.pressSub, 1500, true, false, body),
23234
+ charPr(ids.pressContact, 1e3, false, false, body)
23235
+ ];
23236
+ }
23237
+ function labeled(label, value, paraPrId) {
23238
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_BOLD}"><hp:t>${escapeXml(label)}</hp:t></hp:run><hp:run charPrIDRef="${CHAR_NORMAL}"><hp:t>${escapeXml(value)}</hp:t></hp:run></hp:p>`;
23239
+ }
23240
+ function blank() {
23241
+ return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}"><hp:t></hp:t></hp:run></hp:p>`;
23242
+ }
23243
+ function buildDocHead(g, ids) {
23244
+ const h = g.docHead;
23245
+ const out = [];
23246
+ if (h.org) {
23247
+ out.push(para(h.org, GONGMUN_CENTER, ids.org), blank());
23248
+ }
23249
+ if (h.to !== void 0) out.push(labeled("\uC218\uC2E0 ", h.to, PARA_NORMAL));
23250
+ out.push(para(h.via ? `(\uACBD\uC720) ${h.via}` : "(\uACBD\uC720)", PARA_NORMAL, CHAR_NORMAL));
23251
+ if (h.title !== void 0) out.push(labeled("\uC81C\uBAA9 ", h.title, PARA_NORMAL));
23252
+ out.push(blank());
23253
+ return out;
23254
+ }
23255
+ var FOOT_SEP = "\u2500".repeat(46);
23256
+ function buildDocFoot(g, ids) {
23257
+ const f = g.docFoot;
23258
+ const out = [blank()];
23259
+ if (f.sender) {
23260
+ out.push(para(f.sender, GONGMUN_CENTER, ids.sender), blank());
23261
+ }
23262
+ const foot = (text) => para(text, GONGMUN_TBL_LEFT, ids.foot);
23263
+ out.push(foot(FOOT_SEP));
23264
+ if (f.drafter || f.reviewer || f.approver)
23265
+ out.push(foot(`\uAE30\uC548\uC790 ${f.drafter ?? ""} \uAC80\uD1A0\uC790 ${f.reviewer ?? ""} \uACB0\uC7AC\uAD8C\uC790 ${f.approver ?? ""}`));
23266
+ if (f.cooperator) out.push(foot(`\uD611\uC870\uC790 ${f.cooperator}`));
23267
+ if (f.docNum || f.receive) out.push(foot(`\uC2DC\uD589 ${f.docNum ?? ""} \uC811\uC218 ${f.receive ?? ""}`));
23268
+ if (f.address || f.site) out.push(foot(`${f.address ?? ""} / ${f.site ?? ""}`));
23269
+ if (f.phone || f.fax || f.email || f.disclosure)
23270
+ out.push(foot(`\uC804\uD654 ${f.phone ?? ""} \uC804\uC1A1 ${f.fax ?? ""} / ${f.email ?? ""} / ${f.disclosure ?? ""}`));
23271
+ return out;
23272
+ }
23273
+ function buildReportInfo(g, ids) {
23274
+ return para(g.reportInfo, GONGMUN_RIGHT, ids.small);
23275
+ }
23276
+ function buildNoticeHead(g) {
23277
+ const n = g.noticeHead;
23278
+ return n.no ? [para(n.no, PARA_NORMAL, CHAR_BOLD), blank()] : [];
23279
+ }
23280
+ function buildNoticeFoot(g) {
23281
+ const n = g.noticeHead;
23282
+ const out = [];
23283
+ if (n.date) out.push(blank(), para(n.date, GONGMUN_RIGHT, CHAR_NORMAL));
23284
+ if (n.sender) out.push(blank(), para(n.sender, GONGMUN_RIGHT, CHAR_BOLD));
23285
+ return out;
23286
+ }
23287
+ var PRESS_LABEL_H = 3200;
23288
+ var PRESS_HEAD_H = 900;
23289
+ function buildPressHead(g, ids, bodyWidth, reg) {
23290
+ const p = g.press;
23291
+ const w = bodyWidth - 280;
23292
+ const thick = { t: "thick", b: "thick", l: "thick", r: "thick" };
23293
+ const rows = [tc({ bf: reg.get({ ...thick, b: "thin" }), row: 0, col: 0, w, h: PRESS_LABEL_H, paras: para("\uBCF4\uB3C4\uC790\uB8CC", GONGMUN_TBL_CENTER, ids.pressLabel) })];
23294
+ const headLine = [
23295
+ p.release ? `\uBCF4\uB3C4\uC2DC\uC810 : ${p.release}` : "",
23296
+ p.distribute ? `\uBC30\uD3EC : ${p.distribute}` : ""
23297
+ ].filter(Boolean).join(" / ");
23298
+ if (headLine) rows.push(tc({ bf: reg.get({ ...thick, t: "thin" }), row: 1, col: 0, w, h: PRESS_HEAD_H, paras: para(headLine, GONGMUN_TBL_CENTER, ids.pressHead) }));
23299
+ const table2 = tbl(rows, w, PRESS_LABEL_H + (headLine ? PRESS_HEAD_H : 0), 1);
23300
+ return [`<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`, blank()];
23301
+ }
23302
+ function buildPressContact(g, ids, bodyWidth, reg) {
23303
+ const c = g.press?.contact;
23304
+ if (!c || !c.dept && !c.manager && !c.phone) return [];
23305
+ const w = bodyWidth - 280;
23306
+ const cells = [["\uB2F4\uB2F9 \uBD80\uC11C", c.dept ?? ""], ["\uB2F4\uB2F9\uC790", c.manager ?? ""], ["\uC5F0\uB77D\uCC98", c.phone ?? ""]];
23307
+ const labelW = Math.round(w * 0.14);
23308
+ const valueW = Math.round(w / 3) - labelW;
23309
+ let col = 0;
23310
+ const row = cells.flatMap(([label, value], i) => {
23311
+ const bf = (first) => reg.get({
23312
+ t: "thick",
23313
+ b: "thick",
23314
+ l: first && i === 0 ? "thick" : "thin",
23315
+ r: !first && i === cells.length - 1 ? "thick" : "thin",
23316
+ fill: first ? "#E6E6E6" : void 0
23317
+ });
23318
+ return [
23319
+ tc({ bf: bf(true), row: 0, col: col++, w: labelW, h: 1100, paras: para(label, GONGMUN_TBL_CENTER, ids.pressHead) }),
23320
+ tc({ bf: bf(false), row: 0, col: col++, w: valueW, h: 1100, paras: para(value, GONGMUN_TBL_CENTER, ids.pressContact) })
23321
+ ];
23322
+ });
23323
+ const table2 = tbl([row.join("")], w, 1100, cells.length * 2);
23324
+ return [blank(), `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`];
23325
+ }
23326
+
23142
23327
  // src/hwpx/equation-generate.ts
23143
23328
  var MAX_EQUATION_SOURCE = 1e4;
23144
23329
  var MAX_GROUP_DEPTH = 64;
@@ -23431,21 +23616,25 @@ function generateSecPr(gongmun) {
23431
23616
  m.footer = gongmun.headerFooter;
23432
23617
  }
23433
23618
  const secPr = `<hp:secPr textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" outlineShapeIDRef="1" memoShapeIDRef="0" textVerticalWidthHead="0" masterPageCnt="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="${m.header}" footer="${m.footer}" gutter="0" left="${m.left}" right="${m.right}" top="${m.top}" bottom="${m.bottom}"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr></hp:secPr>`;
23434
- return gongmun?.pageNumbers ? secPr + pageNumCtrl() : secPr;
23619
+ const colPr = `<hp:ctrl><hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/></hp:ctrl>`;
23620
+ return secPr + colPr + (gongmun?.pageNumbers ? pageNumCtrl() : "");
23435
23621
  }
23436
- function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null, chartParts = null, bfReg = null, remap = null) {
23622
+ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null, chartParts = null, bfReg = null, remap = null, dfIds = null) {
23437
23623
  const paraXmls = [];
23438
23624
  let isFirst = true;
23439
23625
  let tableSeq = 0;
23440
23626
  const orderedCounters = {};
23441
23627
  let prevWasOrdered = false;
23442
23628
  const gaejosik = gongmun?.preset === "gaejosik";
23443
- const vBase = charVariantBase(gaejosik);
23629
+ const measured = !!gongmun && usesReportFonts(gongmun.preset);
23630
+ const vBase = charVariantBase(measured, !!gongmun);
23444
23631
  const tableStyle = gongmun ? {
23445
23632
  totalWidth: mmToHwpunit(210 - gongmun.margins.left - gongmun.margins.right),
23446
- charPr: gaejosik ? GJ_CHAR_TABLE : CHAR_NORMAL,
23447
- boldCharPr: gaejosik ? GJ_CHAR_TABLE_BOLD : CHAR_BOLD,
23448
- charHeight: gaejosik ? gaejosikSizes(gongmun.bodyHeight, gongmun.sizes).table : gongmun.bodyHeight,
23633
+ // 크기 실측: 실측 프리셋 맑은 고딕 12pt(22·23), 비실측도 12pt(11·12,
23634
+ // 실결재 지배값 본문 크기 셀은 열폭 부족으로 서술 열 세로 신장, v4.0.2 QA)
23635
+ charPr: measured ? GJ_CHAR_TABLE : GONGMUN_TBL_CHAR,
23636
+ boldCharPr: measured ? GJ_CHAR_TABLE_BOLD : GONGMUN_TBL_CHAR_BOLD,
23637
+ charHeight: measured ? gaejosikSizes(gongmun.bodyHeight, gongmun.sizes).table : GONGMUN_TBL_PT,
23449
23638
  headerBf: gongmunTableHeaderBf(gaejosik),
23450
23639
  centerParaPr: GONGMUN_CENTER,
23451
23640
  tblCenterParaPr: GONGMUN_TBL_CENTER,
@@ -23455,15 +23644,29 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23455
23644
  } : null;
23456
23645
  const chamMap = (id) => id === CHAR_BOLD ? GJ_CHAR_CHAM_BOLD : id;
23457
23646
  let chapterNo = 0;
23647
+ let h2Seq = 0;
23458
23648
  let coverH1Idx = -1;
23459
23649
  let pendingPageBreak = false;
23460
23650
  let pendingNewNum = false;
23461
23651
  let titleBoxH1Idx = -1;
23652
+ let pressH1Idx = -1;
23462
23653
  let hasFrontPages = false;
23463
23654
  const preamble = [];
23655
+ if (gongmun?.reportInfo && dfIds) {
23656
+ preamble.push(buildReportInfo(gongmun, dfIds));
23657
+ }
23464
23658
  if (gongmun?.approval && bfReg) {
23465
23659
  preamble.push(buildApprovalTable(gongmun.approval, gongmun, bfReg));
23466
23660
  }
23661
+ if (gongmun?.docHead && dfIds) {
23662
+ preamble.push(...buildDocHead(gongmun, dfIds));
23663
+ }
23664
+ if (gongmun?.noticeHead && dfIds) {
23665
+ preamble.push(...buildNoticeHead(gongmun));
23666
+ }
23667
+ if (gongmun?.press && dfIds && bfReg && tableStyle) {
23668
+ preamble.push(...buildPressHead(gongmun, dfIds, tableStyle.totalWidth, bfReg));
23669
+ }
23467
23670
  if (gaejosik && gongmun) {
23468
23671
  const h1Idx = blocks.findIndex((b) => b.type === "heading" && (b.level ?? 1) === 1);
23469
23672
  const chapters = blocks.filter((b) => b.type === "heading" && b.level === 2).map((b) => stripChapterNumber(b.text || ""));
@@ -23488,6 +23691,8 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23488
23691
  }
23489
23692
  } else if (gongmun && !gaejosik && bfReg && (gongmun.preset === "report" || gongmun.preset === "plan" || gongmun.preset === "notice")) {
23490
23693
  titleBoxH1Idx = blocks.findIndex((b) => b.type === "heading" && (b.level ?? 1) === 1);
23694
+ } else if (gongmun?.press && dfIds) {
23695
+ pressH1Idx = blocks.findIndex((b) => b.type === "heading" && (b.level ?? 1) === 1);
23491
23696
  }
23492
23697
  if (preamble.length > 0) {
23493
23698
  preamble[0] = preamble[0].replace(` pageBreak="1"`, "").replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${generateSecPr(gongmun)}`);
@@ -23507,8 +23712,16 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23507
23712
  }
23508
23713
  switch (block.type) {
23509
23714
  case "heading": {
23715
+ if (gongmun && blockIdx === pressH1Idx && dfIds) {
23716
+ const parts = [generateParagraph((block.text || "").trim(), GONGMUN_CENTER, dfIds.pressTitle)];
23717
+ for (const s of gongmun.press?.sub ?? []) {
23718
+ parts.push(generateParagraph(`- ${s} -`, GONGMUN_CENTER, dfIds.pressSub));
23719
+ }
23720
+ xml = parts.join("\n ");
23721
+ break;
23722
+ }
23510
23723
  if (gongmun && blockIdx === titleBoxH1Idx && tableStyle && bfReg) {
23511
- xml = buildTitleBox((block.text || "").trim(), CHAR_H1, tableStyle.totalWidth, bfReg);
23724
+ xml = buildTitleBox((block.text || "").trim(), measured ? GJ_CHAR_BODY_TITLE : CHAR_H1, tableStyle.totalWidth, bfReg);
23512
23725
  break;
23513
23726
  }
23514
23727
  if (gaejosik) {
@@ -23525,18 +23738,24 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23525
23738
  (id) => id === CHAR_BOLD ? GJ_CHAR_DAE_BOLD : id
23526
23739
  );
23527
23740
  } else {
23528
- xml = generateParagraph(`\u25CB ${block.text || ""}`, GONGMUN_LIST_BASE + 1, CHAR_NORMAL);
23741
+ xml = generateParagraph(`${gongmun.bullet2} ${block.text || ""}`, GONGMUN_LIST_BASE + 1, CHAR_NORMAL);
23529
23742
  }
23530
23743
  break;
23531
23744
  }
23532
23745
  const pId = headingParaPrId(block.level || 1);
23533
23746
  const cId = headingCharPrId(block.level || 1);
23534
23747
  const styleId = gongmun ? Math.min(block.level || 1, 4) : 0;
23535
- xml = generateParagraph(block.text || "", pId, cId, void 0, styleId);
23748
+ let hText = block.text || "";
23749
+ if (gongmun && (block.level || 1) === 2 && gongmun.h2Marker !== "none") {
23750
+ const title = stripChapterNumber(hText);
23751
+ hText = gongmun.h2Marker === "box" ? `\u25A1 ${title}` : `${++h2Seq}. ${title}`;
23752
+ }
23753
+ xml = generateParagraph(hText, pId, cId, void 0, styleId);
23536
23754
  break;
23537
23755
  }
23538
23756
  case "paragraph": {
23539
- if (gaejosik && (block.text || "").trimStart().startsWith("\u203B")) {
23757
+ const pTrim = (block.text || "").trimStart();
23758
+ if (measured && (pTrim.startsWith("\u203B") || /^\*\s/.test(pTrim))) {
23540
23759
  xml = generateParagraph((block.text || "").trim(), GJ_PARA_CHAM, GJ_CHAR_CHAM, chamMap);
23541
23760
  break;
23542
23761
  }
@@ -23581,7 +23800,7 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23581
23800
  break;
23582
23801
  }
23583
23802
  case "blockquote": {
23584
- if (gaejosik) {
23803
+ if (measured) {
23585
23804
  const t = (block.text || "").trim();
23586
23805
  if (t) xml = generateParagraph(t.startsWith("\u203B") ? t : `\u203B ${t}`, GJ_PARA_CHAM, GJ_CHAR_CHAM, chamMap);
23587
23806
  break;
@@ -23599,18 +23818,30 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23599
23818
  const depth = info?.depth ?? 0;
23600
23819
  const marker2 = info?.marker ?? "";
23601
23820
  const content = block.text || "";
23602
- if (gaejosik && content.trimStart().startsWith("\u203B")) {
23821
+ if (measured && content.trimStart().startsWith("\u203B")) {
23603
23822
  xml = generateParagraph(content.trim(), GJ_PARA_CHAM, GJ_CHAR_CHAM, chamMap);
23604
23823
  break;
23605
23824
  }
23825
+ if (measured && block.marker === "*") {
23826
+ xml = generateParagraph(`* ${content.trim()}`, GJ_PARA_CHAM, GJ_CHAR_CHAM, chamMap);
23827
+ break;
23828
+ }
23606
23829
  const text = marker2 ? `${marker2} ${content}` : content;
23830
+ const listParaPr = marker2 ? GONGMUN_LIST_BASE + depth : GONGMUN_LIST_PLAIN_BASE + depth;
23607
23831
  let listCharPr = gongmun.numbering === "report" && depth === 0 ? CHAR_BOLD : CHAR_NORMAL;
23608
23832
  let mapId = fit ? variantMapper(fit, blockIdx, vBase) : void 0;
23609
- if (gaejosik && depth === 0) {
23833
+ if (measured && depth === 0 && gongmun.numbering !== "standard") {
23610
23834
  listCharPr = GJ_CHAR_DAE;
23611
23835
  mapId = (id) => id === CHAR_BOLD ? GJ_CHAR_DAE_BOLD : id;
23612
23836
  }
23613
- xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr, mapId);
23837
+ if (gongmun.preset === "press") {
23838
+ if (depth === 0) listCharPr = CHAR_NORMAL;
23839
+ if (depth >= 2 && dfIds) {
23840
+ listCharPr = dfIds.small;
23841
+ mapId = void 0;
23842
+ }
23843
+ }
23844
+ xml = generateParagraph(text, listParaPr, listCharPr, mapId);
23614
23845
  break;
23615
23846
  }
23616
23847
  const indent = block.indent || 0;
@@ -23684,10 +23915,19 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
23684
23915
  }
23685
23916
  paraXmls.push(xml);
23686
23917
  }
23918
+ if (gongmun?.noticeHead && dfIds) {
23919
+ paraXmls.push(...buildNoticeFoot(gongmun));
23920
+ }
23687
23921
  if (gongmun?.endMark && paraXmls.length > 0) {
23688
23922
  const lastText = [...blocks].reverse().find((b) => (b.type === "paragraph" || b.type === "list_item") && (b.text || "").trim())?.text ?? "";
23689
23923
  if (!hasEndMark(lastText)) paraXmls.push(buildEndMark());
23690
23924
  }
23925
+ if (gongmun?.docFoot && dfIds) {
23926
+ paraXmls.push(...buildDocFoot(gongmun, dfIds));
23927
+ }
23928
+ if (gongmun?.press && dfIds && bfReg && tableStyle) {
23929
+ paraXmls.push(...buildPressContact(gongmun, dfIds, tableStyle.totalWidth, bfReg));
23930
+ }
23691
23931
  if (paraXmls.length === 0) {
23692
23932
  paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run></hp:p>`);
23693
23933
  }
@@ -23702,14 +23942,18 @@ async function markdownToHwpx(markdown, options) {
23702
23942
  const theme = resolveTheme(options?.theme);
23703
23943
  const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
23704
23944
  const gaejosik = gongmun?.preset === "gaejosik";
23945
+ const measured = !!gongmun && usesReportFonts(gongmun.preset);
23705
23946
  const blocks = parseMarkdownToBlocks(markdown);
23706
23947
  const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
23707
23948
  const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
23708
23949
  const staticBfEnd = gongmun ? gaejosik ? 11 : 4 : 3;
23709
- const remap = options?.profile ? buildProfileRemap(options.profile, charVariantBase(gaejosik) + (fit?.variants?.length ?? 0) * 4, staticBfEnd) : null;
23950
+ const remap = options?.profile ? buildProfileRemap(options.profile, charVariantBase(measured, !!gongmun) + (fit?.variants?.length ?? 0) * 4, staticBfEnd) : null;
23710
23951
  const bfReg = gongmun ? new TableBfRegistry(staticBfEnd + (remap?.borderFillXmls.length ?? 0)) : null;
23952
+ const dfActive = !!gongmun && docframeActive(gongmun);
23953
+ const dfBase = charVariantBase(measured, !!gongmun) + (fit?.variants?.length ?? 0) * 4 + (remap?.charPrXmls.length ?? 0);
23954
+ const dfIds = dfActive ? docframeIds(dfBase) : null;
23711
23955
  const chartParts = [];
23712
- const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit, chartParts, bfReg, remap);
23956
+ const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit, chartParts, bfReg, remap, dfIds);
23713
23957
  if (remap && remap.tables.length > 0) {
23714
23958
  const unused = remap.tables.filter((t) => !t.used).length;
23715
23959
  if (unused === remap.tables.length) {
@@ -23721,7 +23965,13 @@ async function markdownToHwpx(markdown, options) {
23721
23965
  zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
23722
23966
  zip.file("META-INF/container.xml", generateContainerXml());
23723
23967
  zip.file("Contents/content.hpf", generateManifest(chartParts));
23724
- zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? [], extraBorderFills, remap?.charPrXmls ?? []));
23968
+ zip.file("Contents/header.xml", generateHeaderXml(
23969
+ theme,
23970
+ gongmun,
23971
+ fit?.variants ?? [],
23972
+ extraBorderFills,
23973
+ [...remap?.charPrXmls ?? [], ...dfActive ? docframeCharPrXmls(dfBase, measured) : []]
23974
+ ));
23725
23975
  zip.file("Contents/section0.xml", sectionXml);
23726
23976
  for (const part of chartParts) zip.file(part.name, part.xml);
23727
23977
  zip.file("Preview/PrvText.txt", buildPrvText(blocks));
@@ -24704,6 +24954,142 @@ function unknownFontWarnings(fonts) {
24704
24954
  return warnings;
24705
24955
  }
24706
24956
 
24957
+ // src/hwpx/gongmun-lint.ts
24958
+ var RULES = [
24959
+ // 날짜 ─ 온점 뒤 한 칸, 0 패딩 금지, 연도 4자리, 끝 마침표
24960
+ {
24961
+ code: "DATE_NO_SPACE",
24962
+ severity: "error",
24963
+ pattern: /\b\d{4}\.\d{1,2}\.\d{1,2}\.?/g,
24964
+ message: "\uB0A0\uC9DC \uC628\uC810 \uB4A4\uC5D0 \uD55C \uCE78\uC529 \uB744\uC6CC\uC57C \uD568",
24965
+ suggest: "\uC608) 2025. 1. 6."
24966
+ },
24967
+ {
24968
+ code: "DATE_ZERO_PAD",
24969
+ severity: "error",
24970
+ pattern: /\b\d{4}\.\s*0\d\.|\.\s*0\d\./g,
24971
+ message: "\uC6D4\xB7\uC77C \uC55E\uC758 '0'\uC740 \uD45C\uAE30\uD558\uC9C0 \uC54A\uC74C",
24972
+ suggest: "\uC608) 2025. 1. 6. (2025. 01. 06. \u2715)"
24973
+ },
24974
+ {
24975
+ code: "DATE_2DIGIT_YR",
24976
+ severity: "error",
24977
+ pattern: /(?<!\d)['’]\d{2}\.\s*\d/g,
24978
+ message: "\uC5F0\uB3C4\uB294 \uB124 \uC790\uB9AC\uB85C \uD45C\uAE30('24 \u2715)",
24979
+ suggest: "\uC608) 2025. 1. 6."
24980
+ },
24981
+ {
24982
+ code: "DATE_NO_END_DOT",
24983
+ severity: "warning",
24984
+ pattern: /\b\d{4}\.\s\d{1,2}\.\s\d{1,2}(?!\s*[.\d(])/g,
24985
+ message: "\uB0A0\uC9DC\uC758 '\uC77C' \uB2E4\uC74C\uC5D0 \uB9C8\uCE68\uD45C(.)\uB97C \uCC0D\uC5B4\uC57C \uD568",
24986
+ suggest: "\uC608) 2025. 1. 6."
24987
+ },
24988
+ // 시간 ─ 24시각제, 쌍점 붙여쓰기
24989
+ {
24990
+ code: "TIME_AMPM",
24991
+ severity: "error",
24992
+ pattern: /(오전|오후|아침|밤|낮)\s*\d{1,2}\s*시/g,
24993
+ message: "24\uC2DC\uAC01\uC81C \uC22B\uC790\uB85C \uD45C\uAE30(\uC624\uC804/\uC624\uD6C4 \uC0AC\uC6A9 \uC548 \uD568)",
24994
+ suggest: "\uC608) 09:00, 15:30"
24995
+ },
24996
+ {
24997
+ code: "TIME_24H",
24998
+ severity: "warning",
24999
+ pattern: /(?<!\d)24\s*시(?!각)/g,
25000
+ message: "'24\uC2DC'\uBCF4\uB2E4 \uC775\uC77C 00:00 \uB610\uB294 '18:00\uAE4C\uC9C0' \uAD8C\uC7A5",
25001
+ suggest: "\uC608) 18:00"
25002
+ },
25003
+ {
25004
+ code: "TIME_COLON_SP",
25005
+ severity: "error",
25006
+ pattern: /\b\d{1,2}\s+:\s*\d{2}\b|\b\d{1,2}:\s+\d{2}\b/g,
25007
+ message: "\uC2DC\uC640 \uBD84 \uC0AC\uC774 \uC30D\uC810\uC740 \uC591\uCABD\uC744 \uBD99\uC5EC \uC500",
25008
+ suggest: "\uC608) 13:20"
25009
+ },
25010
+ // 금액 ─ '천원' 금지, 금+숫자 붙여쓰기
25011
+ {
25012
+ code: "MONEY_CHEONWON",
25013
+ severity: "error",
25014
+ pattern: /\d+\s*천\s*원/g,
25015
+ message: "\uAE08\uC561\uC740 '\uCC9C\uC6D0'\uC73C\uB85C \uC904\uC774\uC9C0 \uC54A\uACE0 \uC544\uB77C\uBE44\uC544 \uC22B\uC790\uB85C",
25016
+ suggest: "\uC608) 345,000\uC6D0"
25017
+ },
25018
+ {
25019
+ code: "MONEY_GEUM_SP",
25020
+ severity: "warning",
25021
+ pattern: /금\s+\d/g,
25022
+ message: "'\uAE08'\uACFC \uC22B\uC790 \uC0AC\uC774\uB294 \uBD99\uC5EC \uC4F0\uB294 \uAC83\uC774 \uC6D0\uCE59",
25023
+ suggest: "\uC608) \uAE08113,560\uC6D0"
25024
+ },
25025
+ // 붙임 ─ 쌍점 금지(2타 띄움)
25026
+ {
25027
+ code: "BUNIM_COLON",
25028
+ severity: "error",
25029
+ pattern: /붙\s*임\s*:/g,
25030
+ message: "'\uBD99\uC784' \uB2E4\uC74C\uC5D0 \uC30D\uC810(:)\uC744 \uBD99\uC774\uC9C0 \uC54A\uC74C(2\uD0C0 \uB744\uC6C0)",
25031
+ suggest: "\uC608) \uBD99\uC784 \uACC4\uD68D\uC11C 1\uBD80."
25032
+ },
25033
+ // 표기 ─ 물결표+까지 중복, 한글 먼저, 쌍점 띄어쓰기
25034
+ {
25035
+ code: "KKAJI_DUP",
25036
+ severity: "error",
25037
+ pattern: /[∼~~][^\n]{0,20}?까지/g,
25038
+ message: "\uBB3C\uACB0\uD45C(\u223C)\uC640 '\uAE4C\uC9C0'\uB97C \uD568\uAED8 \uC4F0\uC9C0 \uC54A\uC74C",
25039
+ suggest: "\uC608) 2. 20.\u223C2. 24."
25040
+ },
25041
+ {
25042
+ code: "FOREIGN_FIRST",
25043
+ severity: "warning",
25044
+ pattern: /\b[A-Z]{2,5}\s*\([가-힣]/g,
25045
+ message: "\uD55C\uAE00\uC744 \uBA3C\uC800 \uC4F0\uACE0 \uAD04\uD638 \uC548\uC5D0 \uC678\uAD6D\uC5B4\uB97C \uBCD1\uAE30",
25046
+ suggest: "\uC608) \uC5C5\uBB34 \uD611\uC57D(MOU)"
25047
+ },
25048
+ // 쌍점 — URL(https:// 등)·시각(13:20)은 제외
25049
+ {
25050
+ code: "COLON_SPACE",
25051
+ severity: "warning",
25052
+ pattern: /\S\s+:\S|\S:(?!\/\/)[^\s\d]/g,
25053
+ message: "\uC30D\uC810\uC740 \uC55E\uB9D0\uC5D0 \uBD99\uC774\uACE0 \uB4A4\uB294 \uD55C \uCE78 \uB744\uC6C0",
25054
+ suggest: "\uC608) \uC6D0\uC7A5: \uAE40\uAC11\uB3D9"
25055
+ }
25056
+ ];
25057
+ function lintGongmunText(text) {
25058
+ const findings = [];
25059
+ let inFence = false;
25060
+ const lines = text.split(/\r?\n/);
25061
+ for (let i = 0; i < lines.length; i++) {
25062
+ const line = lines[i];
25063
+ if (/^\s*(```|~~~)/.test(line)) {
25064
+ inFence = !inFence;
25065
+ continue;
25066
+ }
25067
+ if (inFence) continue;
25068
+ for (const r of RULES) {
25069
+ r.pattern.lastIndex = 0;
25070
+ for (const m of line.matchAll(r.pattern)) {
25071
+ findings.push({
25072
+ line: i + 1,
25073
+ match: m[0].trim(),
25074
+ rule: r.code,
25075
+ severity: r.severity,
25076
+ message: r.message,
25077
+ suggest: r.suggest
25078
+ });
25079
+ }
25080
+ }
25081
+ }
25082
+ return findings;
25083
+ }
25084
+ function gongmunLintWarnings(text, limit = 10) {
25085
+ const findings = lintGongmunText(text);
25086
+ const shown = findings.slice(0, limit).map(
25087
+ (f) => `\uD45C\uAE30\uBC95 [${f.rule}] L${f.line} "${f.match}" \u2014 ${f.message}${f.suggest ? ` (${f.suggest})` : ""}`
25088
+ );
25089
+ if (findings.length > limit) shown.push(`\uD45C\uAE30\uBC95 \uACBD\uACE0 ${findings.length - limit}\uAC74 \uB354 \uC788\uC74C \u2014 kordoc lint\uB85C \uC804\uCCB4 \uD655\uC778`);
25090
+ return shown;
25091
+ }
25092
+
24707
25093
  // src/roundtrip/patcher.ts
24708
25094
  import JSZip10 from "jszip";
24709
25095
 
@@ -28393,7 +28779,7 @@ async function parseHwp(buffer, options) {
28393
28779
  async function parsePdf(buffer, options) {
28394
28780
  let parsePdfDocument;
28395
28781
  try {
28396
- const mod = await import("./parser-NXHY37WH.js");
28782
+ const mod = await import("./parser-2Q2YKEJG.js");
28397
28783
  parsePdfDocument = mod.parsePdfDocument;
28398
28784
  } catch {
28399
28785
  return {
@@ -28508,6 +28894,7 @@ export {
28508
28894
  fillWithUniqueGuard,
28509
28895
  fitRatioForFewerLines,
28510
28896
  formatFillValue,
28897
+ gongmunLintWarnings,
28511
28898
  hwpxToProfile,
28512
28899
  inferFieldType,
28513
28900
  isHwpxFile,
@@ -28516,6 +28903,7 @@ export {
28516
28903
  isOldHwpFile,
28517
28904
  isPdfFile,
28518
28905
  isZipFile,
28906
+ lintGongmunText,
28519
28907
  markdownToHwpx,
28520
28908
  markdownToPdf,
28521
28909
  measureTextWidth,