kordoc 4.12.3 → 4.13.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 +21 -1
  2. package/dist/{-FWGDIOV4.js → -UZDRUWTX.js} +43 -22
  3. package/dist/{chunk-PUZZFYLV.js → chunk-27QN27I3.js} +2 -2
  4. package/dist/{chunk-KRE2SREV.js → chunk-2B45S7FM.js} +2 -2
  5. package/dist/{chunk-INFYDSZL.js → chunk-2RREQZ44.js} +1562 -641
  6. package/dist/chunk-2RREQZ44.js.map +1 -0
  7. package/dist/{chunk-2DAFWDV2.js → chunk-3ABHPKYA.js} +3 -3
  8. package/dist/{chunk-TWKXX343.js → chunk-3MCFJIN2.js} +5 -5
  9. package/dist/{chunk-BAZMKQZW.js → chunk-5VHLKNCE.js} +2 -2
  10. package/dist/{chunk-YBZJMAE2.js → chunk-6JXMIX3G.js} +2 -2
  11. package/dist/chunk-6JXMIX3G.js.map +1 -0
  12. package/dist/{chunk-3KQOI6R6.js → chunk-6NLXGGNH.js} +11 -5
  13. package/dist/chunk-6NLXGGNH.js.map +1 -0
  14. package/dist/{chunk-HB56YANA.js → chunk-AGBFCFQY.js} +7 -6
  15. package/dist/chunk-AGBFCFQY.js.map +1 -0
  16. package/dist/{rasterize-ALCNU53T.js → chunk-BQLZOQES.js} +22 -6
  17. package/dist/chunk-BQLZOQES.js.map +1 -0
  18. package/dist/{chunk-7BIXTQZ3.cjs → chunk-CKMK42FV.cjs} +11 -11
  19. package/dist/{chunk-7BIXTQZ3.cjs.map → chunk-CKMK42FV.cjs.map} +1 -1
  20. package/dist/{chunk-T7GRHAIW.cjs → chunk-COUGOFT3.cjs} +1 -1
  21. package/dist/chunk-COUGOFT3.cjs.map +1 -0
  22. package/dist/{chunk-SJEPQOVW.js → chunk-F3FEIYI7.js} +2 -2
  23. package/dist/{chunk-ZASGH5EN.js → chunk-GWFX5ZD7.js} +8 -7
  24. package/dist/{chunk-ZASGH5EN.js.map → chunk-GWFX5ZD7.js.map} +1 -1
  25. package/dist/{chunk-IC6HHXFL.js → chunk-H5JWPWQP.js} +2 -2
  26. package/dist/{chunk-N3DLQ2YH.js → chunk-HG5LXXCC.js} +1 -1
  27. package/dist/chunk-HG5LXXCC.js.map +1 -0
  28. package/dist/{chunk-CKYDP34A.js → chunk-IEJR23OA.js} +3 -3
  29. package/dist/{chunk-DUGUCM7K.js → chunk-ILDTN72S.js} +3 -3
  30. package/dist/{chunk-OJDMMZ5Z.js → chunk-L3CEPNQS.js} +2 -2
  31. package/dist/{chunk-KLO6RHX2.js → chunk-LP5RTYFM.js} +2 -2
  32. package/dist/{chunk-FFITS3NY.cjs → chunk-MCD45IHO.cjs} +6 -6
  33. package/dist/{chunk-FFITS3NY.cjs.map → chunk-MCD45IHO.cjs.map} +1 -1
  34. package/dist/{chunk-BOATAEXU.cjs → chunk-OJV7HNGJ.cjs} +10 -9
  35. package/dist/chunk-OJV7HNGJ.cjs.map +1 -0
  36. package/dist/{chunk-6L3B44DH.js → chunk-OSLJ3A6D.js} +2 -2
  37. package/dist/{chunk-W3H2H55O.cjs → chunk-S2VKZKUX.cjs} +3 -3
  38. package/dist/{chunk-W3H2H55O.cjs.map → chunk-S2VKZKUX.cjs.map} +1 -1
  39. package/dist/{chunk-B63QEHYL.js → chunk-T7AFLXGA.js} +786 -19
  40. package/dist/chunk-T7AFLXGA.js.map +1 -0
  41. package/dist/{chunk-PL4V523B.js → chunk-TA2LEFOF.js} +3 -3
  42. package/dist/{chunk-ISCI5LKN.cjs → chunk-TAYCQE6K.cjs} +2 -2
  43. package/dist/{chunk-ISCI5LKN.cjs.map → chunk-TAYCQE6K.cjs.map} +1 -1
  44. package/dist/{chunk-ONQHGZTS.js → chunk-XVIJLHCW.js} +1 -1
  45. package/dist/chunk-XVIJLHCW.js.map +1 -0
  46. package/dist/chunks-G3GK536Y.js +10 -0
  47. package/dist/cli.js +179 -37
  48. package/dist/cli.js.map +1 -1
  49. package/dist/{formula-YJWKEPUK.js → formula-3T7HQA4O.js} +4 -4
  50. package/dist/{formula-R7IWAAYR.js → formula-FWVAOSIK.js} +3 -3
  51. package/dist/{formula-ZYHUXTK5.cjs → formula-YALEMZMF.cjs} +9 -9
  52. package/dist/{formula-ZYHUXTK5.cjs.map → formula-YALEMZMF.cjs.map} +1 -1
  53. package/dist/{image-ocr-G4JVF2UH.js → image-ocr-2M7K3CE5.js} +8 -8
  54. package/dist/{image-ocr-6O6ZXKUD.cjs → image-ocr-4EUZIF6M.cjs} +12 -12
  55. package/dist/{image-ocr-6O6ZXKUD.cjs.map → image-ocr-4EUZIF6M.cjs.map} +1 -1
  56. package/dist/{image-ocr-OLTSKQWD.js → image-ocr-X3I2HUK2.js} +6 -6
  57. package/dist/index.cjs +2259 -575
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/index.d.cts +288 -12
  60. package/dist/index.d.ts +288 -12
  61. package/dist/index.js +1880 -196
  62. package/dist/index.js.map +1 -1
  63. package/dist/mcp.js +106 -35
  64. package/dist/mcp.js.map +1 -1
  65. package/dist/{model-bundle-IFD2D5CW.js → model-bundle-XXYZ6ELQ.js} +5 -5
  66. package/dist/{models-FJ3IUNTB.js → models-APDSXJW7.js} +5 -5
  67. package/dist/{parser-YLLIYOIZ.cjs → parser-4KE2FR5F.cjs} +27 -27
  68. package/dist/{parser-YLLIYOIZ.cjs.map → parser-4KE2FR5F.cjs.map} +1 -1
  69. package/dist/{parser-I7TC35V6.js → parser-T5R4YVZS.js} +7 -7
  70. package/dist/{parser-JRT3B6OK.js → parser-XKSOS6GY.js} +7 -7
  71. package/dist/pdf-ocr-JZKCEKWX.cjs +14 -0
  72. package/dist/{pdf-ocr-ADUP2LPU.cjs.map → pdf-ocr-JZKCEKWX.cjs.map} +1 -1
  73. package/dist/pdf-ocr-X62B7YDV.js +13 -0
  74. package/dist/pdf-ocr-YDJQEGDS.js +16 -0
  75. package/dist/{profile-io-BPJQQZLQ.js → profile-io-Y24GOHWM.js} +3 -3
  76. package/dist/rasterize-QCOBWHAG.js +13 -0
  77. package/dist/render-DHFQQCSV.js +38 -0
  78. package/dist/seal-UTHFQPLO.js +10 -0
  79. package/dist/seal-UTHFQPLO.js.map +1 -0
  80. package/dist/{setup-O2DALZKF.js → setup-UWX3KH3G.js} +3 -3
  81. package/dist/visual-DF2KSPEC.js +19 -0
  82. package/dist/visual-DF2KSPEC.js.map +1 -0
  83. package/dist/{watch-GPWYJAS2.js → watch-4ZUTMC3Q.js} +15 -13
  84. package/dist/{watch-GPWYJAS2.js.map → watch-4ZUTMC3Q.js.map} +1 -1
  85. package/package.json +1 -1
  86. package/dist/chunk-3KQOI6R6.js.map +0 -1
  87. package/dist/chunk-B63QEHYL.js.map +0 -1
  88. package/dist/chunk-BOATAEXU.cjs.map +0 -1
  89. package/dist/chunk-HB56YANA.js.map +0 -1
  90. package/dist/chunk-INFYDSZL.js.map +0 -1
  91. package/dist/chunk-N3DLQ2YH.js.map +0 -1
  92. package/dist/chunk-ONQHGZTS.js.map +0 -1
  93. package/dist/chunk-T7GRHAIW.cjs.map +0 -1
  94. package/dist/chunk-YBZJMAE2.js.map +0 -1
  95. package/dist/chunks-VICNQMNE.js +0 -10
  96. package/dist/pdf-ocr-ADUP2LPU.cjs +0 -14
  97. package/dist/pdf-ocr-ATJTB2B3.js +0 -13
  98. package/dist/pdf-ocr-P75KSZFV.js +0 -16
  99. package/dist/rasterize-ALCNU53T.js.map +0 -1
  100. package/dist/render-MDLTQUUV.js +0 -10
  101. package/dist/seal-K4K3KPE3.js +0 -10
  102. /package/dist/{-FWGDIOV4.js.map → -UZDRUWTX.js.map} +0 -0
  103. /package/dist/{chunk-PUZZFYLV.js.map → chunk-27QN27I3.js.map} +0 -0
  104. /package/dist/{chunk-KRE2SREV.js.map → chunk-2B45S7FM.js.map} +0 -0
  105. /package/dist/{chunk-2DAFWDV2.js.map → chunk-3ABHPKYA.js.map} +0 -0
  106. /package/dist/{chunk-TWKXX343.js.map → chunk-3MCFJIN2.js.map} +0 -0
  107. /package/dist/{chunk-BAZMKQZW.js.map → chunk-5VHLKNCE.js.map} +0 -0
  108. /package/dist/{chunk-SJEPQOVW.js.map → chunk-F3FEIYI7.js.map} +0 -0
  109. /package/dist/{chunk-IC6HHXFL.js.map → chunk-H5JWPWQP.js.map} +0 -0
  110. /package/dist/{chunk-CKYDP34A.js.map → chunk-IEJR23OA.js.map} +0 -0
  111. /package/dist/{chunk-DUGUCM7K.js.map → chunk-ILDTN72S.js.map} +0 -0
  112. /package/dist/{chunk-OJDMMZ5Z.js.map → chunk-L3CEPNQS.js.map} +0 -0
  113. /package/dist/{chunk-KLO6RHX2.js.map → chunk-LP5RTYFM.js.map} +0 -0
  114. /package/dist/{chunk-6L3B44DH.js.map → chunk-OSLJ3A6D.js.map} +0 -0
  115. /package/dist/{chunk-PL4V523B.js.map → chunk-TA2LEFOF.js.map} +0 -0
  116. /package/dist/{chunks-VICNQMNE.js.map → chunks-G3GK536Y.js.map} +0 -0
  117. /package/dist/{formula-YJWKEPUK.js.map → formula-3T7HQA4O.js.map} +0 -0
  118. /package/dist/{formula-R7IWAAYR.js.map → formula-FWVAOSIK.js.map} +0 -0
  119. /package/dist/{image-ocr-G4JVF2UH.js.map → image-ocr-2M7K3CE5.js.map} +0 -0
  120. /package/dist/{image-ocr-OLTSKQWD.js.map → image-ocr-X3I2HUK2.js.map} +0 -0
  121. /package/dist/{model-bundle-IFD2D5CW.js.map → model-bundle-XXYZ6ELQ.js.map} +0 -0
  122. /package/dist/{models-FJ3IUNTB.js.map → models-APDSXJW7.js.map} +0 -0
  123. /package/dist/{parser-I7TC35V6.js.map → parser-T5R4YVZS.js.map} +0 -0
  124. /package/dist/{parser-JRT3B6OK.js.map → parser-XKSOS6GY.js.map} +0 -0
  125. /package/dist/{pdf-ocr-ATJTB2B3.js.map → pdf-ocr-X62B7YDV.js.map} +0 -0
  126. /package/dist/{pdf-ocr-P75KSZFV.js.map → pdf-ocr-YDJQEGDS.js.map} +0 -0
  127. /package/dist/{profile-io-BPJQQZLQ.js.map → profile-io-Y24GOHWM.js.map} +0 -0
  128. /package/dist/{render-MDLTQUUV.js.map → rasterize-QCOBWHAG.js.map} +0 -0
  129. /package/dist/{seal-K4K3KPE3.js.map → render-DHFQQCSV.js.map} +0 -0
  130. /package/dist/{setup-O2DALZKF.js.map → setup-UWX3KH3G.js.map} +0 -0
@@ -6,10 +6,7 @@ import {
6
6
  HEADING_RATIO_H1,
7
7
  HEADING_RATIO_H2,
8
8
  HEADING_RATIO_H3
9
- } from "./chunk-N3DLQ2YH.js";
10
- import {
11
- parsePageRange
12
- } from "./chunk-MOL7MDBG.js";
9
+ } from "./chunk-HG5LXXCC.js";
13
10
  import {
14
11
  allLinesegRemovalSplices,
15
12
  applySplices,
@@ -22,24 +19,24 @@ import {
22
19
  paraTextPureT,
23
20
  patchZipEntries,
24
21
  scanSectionXml
25
- } from "./chunk-CKYDP34A.js";
26
- import {
27
- MAX_COLS,
28
- MAX_ROWS,
29
- blocksToMarkdown,
30
- buildTable,
31
- convertTableToText,
32
- dedupeRunningHeaders,
33
- flattenLayoutTables,
34
- mapPuaText
35
- } from "./chunk-ZASGH5EN.js";
22
+ } from "./chunk-IEJR23OA.js";
36
23
  import {
37
24
  SPACE_EM_FIXED,
38
25
  charWidthEm1000,
26
+ detectFormat,
27
+ detectOle2Format,
28
+ detectZipFormat,
29
+ extractRenderedRegions,
30
+ faceClassForGen,
39
31
  fitRatioForFewerLines,
40
32
  measureTextWidth,
33
+ parseLenientCfb,
34
+ renderDocumentToScene,
41
35
  simulateWrap
42
- } from "./chunk-B63QEHYL.js";
36
+ } from "./chunk-T7AFLXGA.js";
37
+ import {
38
+ parsePageRange
39
+ } from "./chunk-MOL7MDBG.js";
43
40
  import {
44
41
  MAX_DECOMPRESS_SIZE,
45
42
  MAX_XML_DEPTH,
@@ -55,7 +52,18 @@ import {
55
52
  findChildByLocalName,
56
53
  localName,
57
54
  rawTextContent
58
- } from "./chunk-YBZJMAE2.js";
55
+ } from "./chunk-6JXMIX3G.js";
56
+ import {
57
+ MAX_COLS,
58
+ MAX_ROWS,
59
+ blocksToMarkdown,
60
+ buildTable,
61
+ convertTableToText,
62
+ dedupeRunningHeaders,
63
+ flattenLayoutTables,
64
+ hasStructuredCellContent,
65
+ mapPuaText
66
+ } from "./chunk-GWFX5ZD7.js";
59
67
  import {
60
68
  KordocError,
61
69
  classifyError,
@@ -67,277 +75,16 @@ import {
67
75
  sanitizeHref,
68
76
  stripDtd,
69
77
  toArrayBuffer
70
- } from "./chunk-PUZZFYLV.js";
78
+ } from "./chunk-27QN27I3.js";
79
+
80
+ // src/table/visual.ts
81
+ import { readFile as readFile2 } from "fs/promises";
71
82
 
72
83
  // src/index.ts
73
84
  import { readFile } from "fs/promises";
74
85
 
75
- // src/detect.ts
76
- import JSZip from "jszip";
77
-
78
- // src/hwp5/cfb-lenient.ts
79
- var CFB_MAGIC = Buffer.from([208, 207, 17, 224, 161, 177, 26, 225]);
80
- var END_OF_CHAIN = 4294967294;
81
- var FREE_SECT = 4294967295;
82
- var MAX_CHAIN_LENGTH = 1e6;
83
- var MAX_DIR_ENTRIES = 1e5;
84
- var MAX_STREAM_SIZE = 100 * 1024 * 1024;
85
- function parseLenientCfb(data) {
86
- if (data.length < 512) throw new Error("CFB \uD30C\uC77C\uC774 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 512\uBC14\uC774\uD2B8)");
87
- if (!data.subarray(0, 8).equals(CFB_MAGIC)) throw new Error("CFB \uB9E4\uC9C1 \uBC14\uC774\uD2B8 \uBD88\uC77C\uCE58");
88
- const sectorSizeShift = data.readUInt16LE(30);
89
- if (sectorSizeShift < 7 || sectorSizeShift > 16) throw new Error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC139\uD130 \uD06C\uAE30 \uC2DC\uD504\uD2B8: " + sectorSizeShift);
90
- const sectorSize = 1 << sectorSizeShift;
91
- const miniSectorSizeShift = data.readUInt16LE(32);
92
- if (miniSectorSizeShift > 16) throw new Error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBBF8\uB2C8 \uC139\uD130 \uD06C\uAE30 \uC2DC\uD504\uD2B8: " + miniSectorSizeShift);
93
- const miniSectorSize = 1 << miniSectorSizeShift;
94
- const headerFatSectorCount = data.readUInt32LE(44);
95
- if (headerFatSectorCount > 1e4) throw new Error("FAT \uC139\uD130 \uC218\uAC00 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4: " + headerFatSectorCount);
96
- const maxRealSectors = Math.ceil(Math.max(0, data.length - 512) / sectorSize);
97
- const maxFatSectors = Math.max(1, Math.ceil(maxRealSectors / (sectorSize / 4)));
98
- const fatSectorCount = Math.min(headerFatSectorCount, maxFatSectors);
99
- const firstDirSector = data.readUInt32LE(48);
100
- const miniStreamCutoff = data.readUInt32LE(56);
101
- const firstMiniFatSector = data.readUInt32LE(60);
102
- const miniFatSectorCount = data.readUInt32LE(64);
103
- const firstDifatSector = data.readUInt32LE(68);
104
- const difatSectorCount = data.readUInt32LE(72);
105
- function sectorOffset(id) {
106
- return 512 + id * sectorSize;
107
- }
108
- function readSectorData(id) {
109
- const off = sectorOffset(id);
110
- if (off + sectorSize > data.length) return Buffer.alloc(0);
111
- return data.subarray(off, off + sectorSize);
112
- }
113
- const fatSectors = [];
114
- for (let i = 0; i < 109 && fatSectors.length < fatSectorCount; i++) {
115
- const sid = data.readUInt32LE(76 + i * 4);
116
- if (sid === FREE_SECT || sid === END_OF_CHAIN) break;
117
- fatSectors.push(sid);
118
- }
119
- let difatSector = firstDifatSector;
120
- const visitedDifat = /* @__PURE__ */ new Set();
121
- for (let d = 0; d < difatSectorCount && difatSector !== END_OF_CHAIN && difatSector !== FREE_SECT; d++) {
122
- if (visitedDifat.has(difatSector)) break;
123
- visitedDifat.add(difatSector);
124
- const buf = readSectorData(difatSector);
125
- if (buf.length < sectorSize) break;
126
- const entriesPerSector = sectorSize / 4 - 1;
127
- for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {
128
- const sid = buf.readUInt32LE(i * 4);
129
- if (sid === FREE_SECT || sid === END_OF_CHAIN) continue;
130
- fatSectors.push(sid);
131
- }
132
- difatSector = buf.readUInt32LE(entriesPerSector * 4);
133
- }
134
- const entriesPerFatSector = sectorSize / 4;
135
- const fatTable = new Uint32Array(fatSectors.length * entriesPerFatSector);
136
- for (let fi = 0; fi < fatSectors.length; fi++) {
137
- const buf = readSectorData(fatSectors[fi]);
138
- for (let i = 0; i < entriesPerFatSector; i++) {
139
- fatTable[fi * entriesPerFatSector + i] = i * 4 + 3 < buf.length ? buf.readUInt32LE(i * 4) : FREE_SECT;
140
- }
141
- }
142
- function readChain(startSector, maxBytes) {
143
- if (startSector === END_OF_CHAIN || startSector === FREE_SECT) return Buffer.alloc(0);
144
- if (maxBytes > MAX_STREAM_SIZE) throw new Error("\uC2A4\uD2B8\uB9BC\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4");
145
- const chunks = [];
146
- let current = startSector;
147
- let totalRead = 0;
148
- const visited = /* @__PURE__ */ new Set();
149
- while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < maxBytes) {
150
- if (visited.has(current)) break;
151
- if (visited.size > MAX_CHAIN_LENGTH) break;
152
- visited.add(current);
153
- const buf = readSectorData(current);
154
- const remaining = maxBytes - totalRead;
155
- chunks.push(remaining < sectorSize ? buf.subarray(0, remaining) : buf);
156
- totalRead += Math.min(buf.length, remaining);
157
- current = current < fatTable.length ? fatTable[current] : END_OF_CHAIN;
158
- }
159
- return Buffer.concat(chunks);
160
- }
161
- let miniFatTable = null;
162
- function getMiniFatTable() {
163
- if (miniFatTable) return miniFatTable;
164
- if (miniFatSectorCount === 0 || firstMiniFatSector === END_OF_CHAIN) {
165
- miniFatTable = new Uint32Array(0);
166
- return miniFatTable;
167
- }
168
- const miniFatData = readChain(firstMiniFatSector, miniFatSectorCount * sectorSize);
169
- const entries = miniFatData.length / 4;
170
- miniFatTable = new Uint32Array(entries);
171
- for (let i = 0; i < entries; i++) {
172
- miniFatTable[i] = miniFatData.readUInt32LE(i * 4);
173
- }
174
- return miniFatTable;
175
- }
176
- const dirData = readChain(firstDirSector, MAX_DIR_ENTRIES * 128);
177
- const dirEntries = [];
178
- for (let offset = 0; offset + 128 <= dirData.length && dirEntries.length < MAX_DIR_ENTRIES; offset += 128) {
179
- const nameLen = dirData.readUInt16LE(offset + 64);
180
- if (nameLen <= 0 || nameLen > 64) {
181
- dirEntries.push({ name: "", type: 0, startSector: 0, size: 0 });
182
- continue;
183
- }
184
- const nameBytes = nameLen - 2;
185
- const name = nameBytes > 0 ? dirData.subarray(offset, offset + nameBytes).toString("utf16le") : "";
186
- const type = dirData[offset + 66];
187
- const startSector = dirData.readUInt32LE(offset + 116);
188
- const size = dirData.readUInt32LE(offset + 120);
189
- dirEntries.push({ name, type, startSector, size });
190
- }
191
- let miniStreamData = null;
192
- function getMiniStream() {
193
- if (miniStreamData) return miniStreamData;
194
- const root = dirEntries[0];
195
- if (!root || root.type !== 5) {
196
- miniStreamData = Buffer.alloc(0);
197
- return miniStreamData;
198
- }
199
- miniStreamData = readChain(root.startSector, root.size || MAX_STREAM_SIZE);
200
- return miniStreamData;
201
- }
202
- function readMiniStream(startSector, size) {
203
- const mft = getMiniFatTable();
204
- const ms = getMiniStream();
205
- if (mft.length === 0 || ms.length === 0) return Buffer.alloc(0);
206
- const chunks = [];
207
- let current = startSector;
208
- let totalRead = 0;
209
- const visited = /* @__PURE__ */ new Set();
210
- while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < size) {
211
- if (visited.has(current)) break;
212
- if (visited.size > MAX_CHAIN_LENGTH) break;
213
- visited.add(current);
214
- const off = current * miniSectorSize;
215
- const remaining = size - totalRead;
216
- const chunkSize = Math.min(miniSectorSize, remaining);
217
- if (off + chunkSize <= ms.length) {
218
- chunks.push(ms.subarray(off, off + chunkSize));
219
- }
220
- totalRead += chunkSize;
221
- current = current < mft.length ? mft[current] : END_OF_CHAIN;
222
- }
223
- return Buffer.concat(chunks);
224
- }
225
- function readStreamData(entry) {
226
- if (entry.size === 0) return Buffer.alloc(0);
227
- if (entry.size < miniStreamCutoff) {
228
- const miniResult = readMiniStream(entry.startSector, entry.size);
229
- if (miniResult.length > 0) return miniResult;
230
- }
231
- return readChain(entry.startSector, entry.size);
232
- }
233
- function findEntryByPath(path) {
234
- const parts = path.replace(/^\//, "").split("/");
235
- if (parts.length === 1) {
236
- return dirEntries.find((e) => e.name === parts[0] && e.type === 2) ?? null;
237
- }
238
- const storageName = parts[0];
239
- const streamName = parts.slice(1).join("/");
240
- for (const e of dirEntries) {
241
- if (e.type === 2 && e.name === streamName) {
242
- return e;
243
- }
244
- }
245
- const lastPart = parts[parts.length - 1];
246
- return dirEntries.find((e) => e.type === 2 && e.name === lastPart) ?? null;
247
- }
248
- return {
249
- findStream(path) {
250
- const normalized = path.replace(/^\//, "");
251
- const entry = findEntryByPath(normalized);
252
- if (!entry || entry.type !== 2) return null;
253
- const stream = readStreamData(entry);
254
- return stream.length > 0 ? stream : null;
255
- },
256
- entries() {
257
- return dirEntries.filter((e) => e.type === 2);
258
- }
259
- };
260
- }
261
-
262
- // src/detect.ts
263
- function magicBytes(buffer) {
264
- return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength));
265
- }
266
- function isZipFile(buffer) {
267
- const b = magicBytes(buffer);
268
- return b[0] === 80 && b[1] === 75 && b[2] === 3 && b[3] === 4;
269
- }
270
- function isHwpxFile(buffer) {
271
- return isZipFile(buffer);
272
- }
273
- function isOldHwpFile(buffer) {
274
- const b = magicBytes(buffer);
275
- return b[0] === 208 && b[1] === 207 && b[2] === 17 && b[3] === 224;
276
- }
277
- var HWP3_PREFIX = new TextEncoder().encode("HWP Document File V3.00");
278
- function isHwp3File(buffer) {
279
- if (buffer.byteLength < HWP3_PREFIX.length) return false;
280
- const head = new Uint8Array(buffer, 0, HWP3_PREFIX.length);
281
- for (let i = 0; i < HWP3_PREFIX.length; i++) {
282
- if (head[i] !== HWP3_PREFIX[i]) return false;
283
- }
284
- return true;
285
- }
286
- function isPdfFile(buffer) {
287
- const b = magicBytes(buffer);
288
- return b[0] === 37 && b[1] === 80 && b[2] === 68 && b[3] === 70;
289
- }
290
- function isImageFile(buffer) {
291
- if (buffer.byteLength < 12) return false;
292
- const b = new Uint8Array(buffer, 0, 12);
293
- if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return true;
294
- if (b[0] === 255 && b[1] === 216 && b[2] === 255) return true;
295
- if (b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) return true;
296
- return false;
297
- }
298
- function isHwpmlFile(buffer) {
299
- const bytes = new Uint8Array(buffer, 0, Math.min(512, buffer.byteLength));
300
- const head = new TextDecoder("utf-8", { fatal: false }).decode(bytes).replace(/^\uFEFF/, "");
301
- return head.trimStart().startsWith("<?xml") && head.includes("<HWPML");
302
- }
303
- function detectFormat(buffer) {
304
- if (buffer.byteLength < 4) return "unknown";
305
- if (isHwp3File(buffer)) return "hwp3";
306
- if (isZipFile(buffer)) return "hwpx";
307
- if (isOldHwpFile(buffer)) return "hwp";
308
- if (isPdfFile(buffer)) return "pdf";
309
- if (isHwpmlFile(buffer)) return "hwpml";
310
- if (isImageFile(buffer)) return "image";
311
- return "unknown";
312
- }
313
- function detectOle2Format(buffer) {
314
- try {
315
- const cfb = parseLenientCfb(Buffer.from(buffer));
316
- const names = cfb.entries().map((e) => e.name);
317
- if (names.includes("Workbook") || names.includes("Book")) return "xls";
318
- if (names.includes("FileHeader")) return "hwp";
319
- if (names.some((n) => n === "DocInfo" || n.startsWith("Section"))) return "hwp";
320
- return "unknown";
321
- } catch {
322
- return "unknown";
323
- }
324
- }
325
- async function detectZipFormat(buffer) {
326
- try {
327
- const zip = await JSZip.loadAsync(buffer);
328
- if (zip.file("xl/workbook.xml")) return "xlsx";
329
- if (zip.file("word/document.xml")) return "docx";
330
- if (zip.file("Contents/content.hpf") || zip.file("mimetype")) return "hwpx";
331
- const hasSection = Object.keys(zip.files).some((f) => f.startsWith("Contents/"));
332
- if (hasSection) return "hwpx";
333
- return "unknown";
334
- } catch {
335
- return "unknown";
336
- }
337
- }
338
-
339
86
  // src/hwpx/parser.ts
340
- import JSZip3 from "jszip";
87
+ import JSZip2 from "jszip";
341
88
 
342
89
  // src/hwpx/com-fallback.ts
343
90
  import { execFileSync } from "child_process";
@@ -502,9 +249,9 @@ function decryptEntry(cipher, entry, startKey) {
502
249
  const decipher = createDecipheriv("aes-256-cbc", key, entry.iv);
503
250
  decipher.setAutoPadding(false);
504
251
  const raw = Buffer.concat([decipher.update(cipher), decipher.final()]);
505
- const plain2 = inflateRawSync(raw, { maxOutputLength: MAX_DECOMPRESS_SIZE });
506
- const sum = createHash("sha256").update(plain2.subarray(0, Math.min(CHECKSUM_PREFIX_BYTES, plain2.length))).digest();
507
- if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return plain2;
252
+ const plain3 = inflateRawSync(raw, { maxOutputLength: MAX_DECOMPRESS_SIZE });
253
+ const sum = createHash("sha256").update(plain3.subarray(0, Math.min(CHECKSUM_PREFIX_BYTES, plain3.length))).digest();
254
+ if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return plain3;
508
255
  } catch {
509
256
  }
510
257
  }
@@ -520,13 +267,13 @@ async function decryptHwpxInPlace(zip, manifestXml, password) {
520
267
  if (!file) {
521
268
  throw new KordocError(`\uC554\uD638 \uBAA9\uB85D\uC5D0 \uC788\uB294 \uD30C\uC77C\uC774 \uBB38\uC11C\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4: ${entry.path}`);
522
269
  }
523
- const plain2 = decryptEntry(await file.async("nodebuffer"), entry, startKey);
524
- if (!plain2) {
270
+ const plain3 = decryptEntry(await file.async("nodebuffer"), entry, startKey);
271
+ if (!plain3) {
525
272
  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.");
526
273
  }
527
- decrypted.push({ path: entry.path, plain: plain2 });
274
+ decrypted.push({ path: entry.path, plain: plain3 });
528
275
  }
529
- for (const { path, plain: plain2 } of decrypted) zip.file(path, plain2);
276
+ for (const { path, plain: plain3 } of decrypted) zip.file(path, plain3);
530
277
  zip.file("META-INF/manifest.xml", stripEncryptionData(manifestXml));
531
278
  return decrypted.length;
532
279
  }
@@ -1263,6 +1010,26 @@ function circledNumber(n) {
1263
1010
  function circledHangul(n) {
1264
1011
  return n < 14 ? String.fromCodePoint(12910 + n) : hangulOrdinal(n);
1265
1012
  }
1013
+ var AMOUNT_DIGITS = "\uC601\uC77C\uC774\uC0BC\uC0AC\uC624\uC721\uCE60\uD314\uAD6C";
1014
+ var AMOUNT_SMALL = ["", "\uC2ED", "\uBC31", "\uCC9C"];
1015
+ var AMOUNT_BIG = ["", "\uB9CC", "\uC5B5", "\uC870", "\uACBD", "\uD574"];
1016
+ function hangulAmount(n) {
1017
+ const digits = String(n).replace(/\D/g, "").replace(/^0+(?=\d)/, "");
1018
+ if (!digits || digits === "0") return "\uC601";
1019
+ const groups = [];
1020
+ for (let end = digits.length; end > 0; end -= 4) groups.unshift(digits.slice(Math.max(0, end - 4), end));
1021
+ let out = "";
1022
+ groups.forEach((g, i) => {
1023
+ let part = "";
1024
+ const padded = g.padStart(4, "0");
1025
+ for (let k = 0; k < 4; k++) {
1026
+ const d = Number(padded[k]);
1027
+ if (d) part += AMOUNT_DIGITS[d] + AMOUNT_SMALL[3 - k];
1028
+ }
1029
+ if (part) out += part + (AMOUNT_BIG[groups.length - 1 - i] ?? "");
1030
+ });
1031
+ return out;
1032
+ }
1266
1033
  function romanNumeral(n, upper) {
1267
1034
  if (n <= 0 || n > 3999) return String(n);
1268
1035
  const values = [1e3, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
@@ -1359,8 +1126,9 @@ function resolveParaHeading(paraEl, ctx) {
1359
1126
  function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraphs) {
1360
1127
  const table2 = buildTable(state.rows, { keepAnchoredEmptyCols, keepEmptyParagraphs });
1361
1128
  if (state.caption) table2.caption = state.caption;
1129
+ if (state.sourceId) table2.sourceId = state.sourceId;
1362
1130
  if (state.captionBlocks?.length) table2.captionBlocks = state.captionBlocks;
1363
- const anchors = [];
1131
+ const anchors2 = [];
1364
1132
  {
1365
1133
  const covered = /* @__PURE__ */ new Set();
1366
1134
  for (let r = 0; r < table2.rows; r++) {
@@ -1374,13 +1142,13 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
1374
1142
  if (r + dr < table2.rows && c + dc < table2.cols) covered.add(`${r + dr},${c + dc}`);
1375
1143
  }
1376
1144
  }
1377
- anchors.push(cell2);
1145
+ anchors2.push(cell2);
1378
1146
  c += cell2.colSpan - 1;
1379
1147
  }
1380
1148
  }
1381
1149
  }
1382
1150
  const srcCount = state.rows.reduce((s, r) => s + r.length, 0);
1383
- const ordinalReliable = anchors.length === srcCount;
1151
+ const ordinalReliable = anchors2.length === srcCount;
1384
1152
  const claimed = /* @__PURE__ */ new Set();
1385
1153
  let flatIdx = -1;
1386
1154
  for (const row of state.rows) {
@@ -1405,7 +1173,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
1405
1173
  }
1406
1174
  }
1407
1175
  if (!target && ordinalReliable) {
1408
- const cand = anchors[flatIdx];
1176
+ const cand = anchors2[flatIdx];
1409
1177
  if (cand && !claimed.has(cand)) target = cand;
1410
1178
  }
1411
1179
  if (!target) continue;
@@ -1634,7 +1402,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1634
1402
  }
1635
1403
  }
1636
1404
  if (tableCtx) tableStack.push(tableCtx);
1637
- const newTable = { rows: [], currentRow: [], cell: null };
1405
+ const newTable = { rows: [], currentRow: [], cell: null, sourceId: el.getAttribute("id") ?? void 0 };
1638
1406
  walkSection(el, blocks, newTable, tableStack, ctx, depth + 1);
1639
1407
  tableCtx = completeTable(newTable, tableStack, blocks, ctx);
1640
1408
  break;
@@ -1957,7 +1725,7 @@ function collectSubListContent(el, ctx, depth = 0) {
1957
1725
  }
1958
1726
  function buildSubListTable(el, ctx, depth) {
1959
1727
  const sink = [];
1960
- const st = { rows: [], currentRow: [], cell: null };
1728
+ const st = { rows: [], currentRow: [], cell: null, sourceId: el.getAttribute("id") ?? void 0 };
1961
1729
  walkSection(el, sink, st, [], ctx, depth + 1);
1962
1730
  let flat = convertTableToText(st.rows);
1963
1731
  if (st.caption) flat = st.caption + (flat ? "\n" + flat : "");
@@ -2005,7 +1773,7 @@ function walkParagraphChildren(node, blocks, tableCtx, tableStack, ctx, depth =
2005
1773
  }
2006
1774
  }
2007
1775
  if (tableCtx) tableStack.push(tableCtx);
2008
- const newTable = { rows: [], currentRow: [], cell: null, inline: isInlineTbl(el) };
1776
+ const newTable = { rows: [], currentRow: [], cell: null, inline: isInlineTbl(el), sourceId: el.getAttribute("id") ?? void 0 };
2009
1777
  walkSection(el, blocks, newTable, tableStack, ctx, d + 1);
2010
1778
  tableCtx = completeTable(newTable, tableStack, blocks, ctx);
2011
1779
  } else if (localTag === "caption" && !inShape) {
@@ -3080,7 +2848,7 @@ async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUn
3080
2848
  }
3081
2849
 
3082
2850
  // src/hwpx/metadata.ts
3083
- import JSZip2 from "jszip";
2851
+ import JSZip from "jszip";
3084
2852
 
3085
2853
  // src/hwpx/zip-sections.ts
3086
2854
  import { inflateRawSync as inflateRawSync3 } from "zlib";
@@ -3246,7 +3014,7 @@ function parseDublinCoreMetadata(xml, metadata) {
3246
3014
  async function extractHwpxMetadataOnly(buffer) {
3247
3015
  let zip;
3248
3016
  try {
3249
- zip = await JSZip2.loadAsync(buffer);
3017
+ zip = await JSZip.loadAsync(buffer);
3250
3018
  } catch {
3251
3019
  throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
3252
3020
  }
@@ -3262,7 +3030,7 @@ async function parseHwpxDocument(buffer, options) {
3262
3030
  precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
3263
3031
  let zip;
3264
3032
  try {
3265
- zip = await JSZip3.loadAsync(buffer);
3033
+ zip = await JSZip2.loadAsync(buffer);
3266
3034
  } catch {
3267
3035
  return extractFromBrokenZip(buffer);
3268
3036
  }
@@ -18194,7 +17962,7 @@ function isDistributionSentinel(markdown) {
18194
17962
  }
18195
17963
 
18196
17964
  // src/xlsx/parser.ts
18197
- import JSZip4 from "jszip";
17965
+ import JSZip3 from "jszip";
18198
17966
  import { DOMParser } from "@xmldom/xmldom";
18199
17967
  var MAX_SHEETS = 100;
18200
17968
  var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
@@ -18479,7 +18247,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex, keep
18479
18247
  }
18480
18248
  async function parseXlsxDocument(buffer, options) {
18481
18249
  precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
18482
- const zip = await JSZip4.loadAsync(buffer);
18250
+ const zip = await JSZip3.loadAsync(buffer);
18483
18251
  const warnings = [];
18484
18252
  const workbookFile = zip.file("xl/workbook.xml");
18485
18253
  if (!workbookFile) {
@@ -19259,7 +19027,7 @@ async function parseXlsDocument(buffer, options) {
19259
19027
  }
19260
19028
 
19261
19029
  // src/docx/parser.ts
19262
- import JSZip5 from "jszip";
19030
+ import JSZip4 from "jszip";
19263
19031
  import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
19264
19032
 
19265
19033
  // src/docx/equation.ts
@@ -20138,7 +19906,7 @@ function emitParagraphImages(p, imageMap, linked, out) {
20138
19906
  }
20139
19907
  async function parseDocxDocument(buffer, options) {
20140
19908
  precheckZipSize(buffer, MAX_DECOMPRESS_SIZE5);
20141
- const zip = await JSZip5.loadAsync(buffer);
19909
+ const zip = await JSZip4.loadAsync(buffer);
20142
19910
  const warnings = [];
20143
19911
  const docFile = zip.file("word/document.xml");
20144
19912
  if (!docFile) {
@@ -21357,10 +21125,10 @@ function fillInlineFields(text, values, filled, matchedLabels, blockedLabels) {
21357
21125
  }
21358
21126
 
21359
21127
  // src/form/filler-hwpx.ts
21360
- import JSZip7 from "jszip";
21128
+ import JSZip6 from "jszip";
21361
21129
 
21362
21130
  // src/form/click-here.ts
21363
- import JSZip6 from "jszip";
21131
+ import JSZip5 from "jszip";
21364
21132
  var FIELD_BEGIN_RE = /<([A-Za-z0-9_]+:)?fieldBegin\b([^>]*?)(\/?)>/g;
21365
21133
  function attrOf2(attrs, name) {
21366
21134
  const m = attrs.match(new RegExp(`(?:^|\\s)${name}="([^"]*)"`));
@@ -21467,7 +21235,7 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
21467
21235
  }
21468
21236
  async function extractClickHereFields(hwpxBuffer) {
21469
21237
  precheckZipSize(hwpxBuffer);
21470
- const zip = await JSZip6.loadAsync(hwpxBuffer);
21238
+ const zip = await JSZip5.loadAsync(hwpxBuffer);
21471
21239
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
21472
21240
  const fields = [];
21473
21241
  for (let si = 0; si < sectionPaths.length; si++) {
@@ -21488,7 +21256,7 @@ async function extractClickHereFields(hwpxBuffer) {
21488
21256
  async function fillHwpx(hwpxBuffer, values, blockedLabels) {
21489
21257
  const u8 = new Uint8Array(hwpxBuffer);
21490
21258
  precheckZipSize(hwpxBuffer);
21491
- const zip = await JSZip7.loadAsync(hwpxBuffer);
21259
+ const zip = await JSZip6.loadAsync(hwpxBuffer);
21492
21260
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
21493
21261
  if (sectionPaths.length === 0) {
21494
21262
  throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
@@ -21786,6 +21554,166 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
21786
21554
  };
21787
21555
  }
21788
21556
 
21557
+ // src/table/classifier.ts
21558
+ var DIAGRAM_KEYWORDS = /조직도|비상\s*연락망|연락망|체계도|업무\s*체계도|기구표|배치도|흐름도|추진\s*체계/u;
21559
+ var KEYWORD_GATE = 0.3;
21560
+ function cellType(cell2) {
21561
+ const t = cell2.text.trim();
21562
+ if (!t && !cell2.blocks?.length) return "empty";
21563
+ if (!t) return "short";
21564
+ if (/^[-+]?[\d,]+(\.\d+)?\s*(%|원|명|건|개|천원|백만원|㎡|㎢|kg|km|시간|일)?$/u.test(t)) return "number";
21565
+ if (/^\d{2,4}[.\-/년]\s*\d{1,2}[.\-/월]?(\s*\d{1,2}[.일]?)?\.?$/u.test(t)) return "date";
21566
+ return [...t].length <= 12 ? "short" : "long";
21567
+ }
21568
+ function anchors(table2) {
21569
+ const out = [];
21570
+ const covered = /* @__PURE__ */ new Set();
21571
+ for (let r = 0; r < table2.rows; r++) {
21572
+ for (let c = 0; c < table2.cols; c++) {
21573
+ if (covered.has(`${r},${c}`)) continue;
21574
+ const cell2 = table2.cells[r]?.[c];
21575
+ if (!cell2) continue;
21576
+ for (let dr = 0; dr < cell2.rowSpan; dr++) for (let dc = 0; dc < cell2.colSpan; dc++) if (dr || dc) covered.add(`${r + dr},${c + dc}`);
21577
+ out.push({ r, c, cell: cell2, type: cellType(cell2) });
21578
+ }
21579
+ }
21580
+ return out;
21581
+ }
21582
+ function isWrapper(table2) {
21583
+ if (table2.rows !== 1 || table2.cols !== 1) return false;
21584
+ const cell2 = table2.cells[0]?.[0];
21585
+ return !!cell2?.blocks?.some((b) => b.type === "table" && b.table);
21586
+ }
21587
+ function round2(v) {
21588
+ return Math.round(v * 100) / 100;
21589
+ }
21590
+ function classifyTable(table2, ctx = {}) {
21591
+ const reasons = [];
21592
+ const done = (kind, semantic2, nonTabular2) => ({ kind, confidence: round2(Math.min(1, Math.abs(semantic2 - nonTabular2))), semanticScore: round2(semantic2), nonTabularScore: round2(nonTabular2), reasons });
21593
+ if (isWrapper(table2)) {
21594
+ reasons.push("nested-structure-wrapper");
21595
+ return done("non-tabular-layout", 0.05, 0.85);
21596
+ }
21597
+ const slots = table2.rows * table2.cols;
21598
+ const A = anchors(table2);
21599
+ if (slots <= 4 || A.length <= 2) {
21600
+ reasons.push("low-evidence");
21601
+ return done("uncertain", 0.2, 0.2);
21602
+ }
21603
+ const activeSlots = A.filter((a) => a.type !== "empty").reduce((s, a) => s + a.cell.colSpan * a.cell.rowSpan, 0);
21604
+ const activeRatio = activeSlots / slots;
21605
+ const rowHasContent = Array.from({ length: table2.rows }, () => false), colHasContent = Array.from({ length: table2.cols }, () => false);
21606
+ for (const a of A) if (a.type !== "empty") {
21607
+ for (let dr = 0; dr < a.cell.rowSpan; dr++) rowHasContent[a.r + dr] = true;
21608
+ for (let dc = 0; dc < a.cell.colSpan; dc++) colHasContent[a.c + dc] = true;
21609
+ }
21610
+ const spacerRows = rowHasContent.filter((x) => !x).length, spacerCols = colHasContent.filter((x) => !x).length;
21611
+ const spacerBands = (spacerRows + spacerCols) / (table2.rows + table2.cols);
21612
+ const body = A.filter((a) => a.r > 0);
21613
+ const bodyMerged = body.filter((a) => a.cell.colSpan > 1 || a.cell.rowSpan > 1).length;
21614
+ const anchorsPerRow = /* @__PURE__ */ new Map();
21615
+ for (const a of A) anchorsPerRow.set(a.r, (anchorsPerRow.get(a.r) ?? 0) + 1);
21616
+ const counts = [...anchorsPerRow.values()];
21617
+ const meanCnt = counts.reduce((s, v) => s + v, 0) / Math.max(1, counts.length);
21618
+ const rowVariance = counts.length > 1 ? counts.reduce((s, v) => s + Math.abs(v - meanCnt), 0) / counts.length / Math.max(1, meanCnt) : 0;
21619
+ const spanIrregularity = Math.min(1, (body.length ? bodyMerged / body.length : 0) * 0.7 + Math.min(1, rowVariance) * 0.6);
21620
+ const gridRegularity = A.length ? A.filter((a) => a.cell.colSpan === 1 && a.cell.rowSpan === 1 || a.r === 0 && a.cell.rowSpan === 1).length / A.length : 0;
21621
+ let repeatedRowSchema = 0;
21622
+ if (table2.rows >= 3) {
21623
+ const sigs = /* @__PURE__ */ new Map();
21624
+ let n = 0;
21625
+ for (let r = 1; r < table2.rows; r++) {
21626
+ const rowA = A.filter((a) => a.r === r && a.type !== "empty");
21627
+ if (!rowA.length) continue;
21628
+ const sig = rowA.map((a) => `${a.c}:${a.type === "long" ? "short" : a.type}`).join("|");
21629
+ sigs.set(sig, (sigs.get(sig) ?? 0) + 1);
21630
+ n++;
21631
+ }
21632
+ repeatedRowSchema = n ? Math.max(...sigs.values()) / n : 0;
21633
+ if (n < 2) repeatedRowSchema *= 0.5;
21634
+ }
21635
+ let columnTypeConsistency = 0;
21636
+ {
21637
+ const cols = [];
21638
+ for (let c = 0; c < table2.cols; c++) {
21639
+ const types = A.filter((a) => a.r > 0 && a.c === c && a.cell.colSpan === 1 && a.type !== "empty").map((a) => a.type);
21640
+ if (types.length < 2) continue;
21641
+ const freq = /* @__PURE__ */ new Map();
21642
+ for (const t of types) freq.set(t, (freq.get(t) ?? 0) + 1);
21643
+ cols.push(Math.max(...freq.values()) / types.length);
21644
+ }
21645
+ columnTypeConsistency = cols.length ? cols.reduce((s, v) => s + v, 0) / cols.length : 0;
21646
+ }
21647
+ const sparsity = Math.max(0, (0.6 - activeRatio) / 0.6);
21648
+ const emptyShare = 1 - activeRatio;
21649
+ let semantic = (0.35 * repeatedRowSchema + 0.25 * gridRegularity + 0.2 * Math.min(1, activeRatio / 0.7) + 0.2 * columnTypeConsistency) * (1 - 0.6 * Math.max(0, emptyShare - 0.2) / 0.8) * (1 - spacerBands) * (1 - 0.5 * spanIrregularity);
21650
+ let nonTabular = 0.6 * spanIrregularity + 0.4 * spacerBands + 0.5 * sparsity;
21651
+ const nestedCells = A.filter((a) => a.cell.blocks?.some((b) => b.type === "table" && b.table)).length;
21652
+ if (nestedCells && nestedCells >= A.length * 0.5) {
21653
+ nonTabular += 0.25;
21654
+ reasons.push("nested-structure-wrapper");
21655
+ }
21656
+ if (repeatedRowSchema >= 0.6) reasons.push("repeated-row-schema");
21657
+ if (gridRegularity >= 0.9) reasons.push("grid-regularity");
21658
+ if (activeRatio >= 0.7) reasons.push("high-active-density");
21659
+ if (columnTypeConsistency >= 0.75) reasons.push("column-type-consistency");
21660
+ if (spanIrregularity >= 0.35) reasons.push("span-irregularity");
21661
+ if (spacerBands >= 0.25) reasons.push("spacer-bands");
21662
+ if (sparsity >= 0.5) reasons.push("extreme-sparsity");
21663
+ const text = [table2.caption ?? "", ...ctx.nearbyText ?? []].join("\n");
21664
+ if (DIAGRAM_KEYWORDS.test(text) && nonTabular >= KEYWORD_GATE) {
21665
+ nonTabular += 0.15;
21666
+ reasons.push("diagram-context-keyword");
21667
+ }
21668
+ semantic = Math.min(1, semantic);
21669
+ nonTabular = Math.min(1, nonTabular);
21670
+ if (semantic >= 0.55 && semantic - nonTabular >= 0.2) return done("semantic-table", semantic, nonTabular);
21671
+ if (nonTabular >= 0.45 && nonTabular - semantic >= 0.15) return done("non-tabular-layout", semantic, nonTabular);
21672
+ reasons.push(Math.max(semantic, nonTabular) < 0.35 ? "low-evidence" : "ambiguous-scores");
21673
+ return done("uncertain", semantic, nonTabular);
21674
+ }
21675
+ function collectTableBlocks(blocks, out = []) {
21676
+ for (const b of blocks) {
21677
+ if (b.type === "table" && b.table) {
21678
+ out.push(b);
21679
+ for (const row of b.table.cells) for (const cell2 of row) if (cell2.blocks) collectTableBlocks(cell2.blocks, out);
21680
+ if (b.table.captionBlocks) collectTableBlocks(b.table.captionBlocks, out);
21681
+ }
21682
+ if (b.children) collectTableBlocks(b.children, out);
21683
+ }
21684
+ return out;
21685
+ }
21686
+
21687
+ // src/table/analyze.ts
21688
+ function hasMerged(table2) {
21689
+ return table2.cells.some((row) => row.some((c) => c.colSpan > 1 || c.rowSpan > 1));
21690
+ }
21691
+ function classifyInList(blocks) {
21692
+ for (let i = 0; i < blocks.length; i++) {
21693
+ const b = blocks[i];
21694
+ if (b.type === "table" && b.table) {
21695
+ const near = [];
21696
+ for (const j of [i - 2, i - 1, i + 1]) {
21697
+ const n = blocks[j];
21698
+ if (n && n.type !== "table" && n.text) near.push(n.text);
21699
+ }
21700
+ b.table.classification = classifyTable(b.table, { nearbyText: near });
21701
+ for (const row of b.table.cells) for (const cell2 of row) if (cell2.blocks) classifyInList(cell2.blocks);
21702
+ if (b.table.captionBlocks) classifyInList(b.table.captionBlocks);
21703
+ }
21704
+ if (b.children) classifyInList(b.children);
21705
+ }
21706
+ }
21707
+ function classifyTableTree(blocks) {
21708
+ classifyInList(blocks);
21709
+ return blocks;
21710
+ }
21711
+ function chooseTableRepresentation(table2, opts = {}) {
21712
+ const structured = hasMerged(table2) || hasStructuredCellContent(table2);
21713
+ if (opts.smartVisual && table2.classification?.kind === "non-tabular-layout") return "visual";
21714
+ return structured ? "html" : "gfm";
21715
+ }
21716
+
21789
21717
  // src/page-markdown.ts
21790
21718
  function blocksToPages(blocks) {
21791
21719
  const firstNumbered = blocks.find((b) => typeof b.pageNumber === "number")?.pageNumber;
@@ -21810,7 +21738,7 @@ function blocksToPages(blocks) {
21810
21738
  }
21811
21739
 
21812
21740
  // src/hwpx/generator.ts
21813
- import JSZip8 from "jszip";
21741
+ import JSZip7 from "jszip";
21814
21742
 
21815
21743
  // src/hwpx/geometry.ts
21816
21744
  var A4_W_HU = 59528;
@@ -21955,6 +21883,8 @@ function bodyTitleGeom(bodyHeight, ov = {}) {
21955
21883
  // src/hwpx/gongmun.ts
21956
21884
  var OFFICIAL_MARGINS = { top: 20, bottom: 15, left: 20, right: 15 };
21957
21885
  var GAEJOSIK_MARGINS = { top: 15, bottom: 15, left: 20, right: 20 };
21886
+ var SEOUL_REPORT_MARGINS = { top: 13, bottom: 13, left: 18, right: 18 };
21887
+ var SEOUL_REPORT_HEADER_FOOTER = 3600;
21958
21888
  var GAEJOSIK_HEADER_FOOTER = 4251;
21959
21889
  var PRESET_DEFAULTS = {
21960
21890
  // 서울시 실결재 104건 중 12pt 64건(13pt 30, 15pt 5) — 지배값을 기본으로 사용.
@@ -22102,12 +22032,17 @@ function resolveGongmun(opts) {
22102
22032
  bodyHeight: Math.round(bodyPt * 100),
22103
22033
  lineSpacing: opts.lineSpacing ?? d.lineSpacing,
22104
22034
  numbering: opts.numbering ?? d.numbering,
22105
- margins: opts.margins ?? (reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
22035
+ margins: opts.margins ?? (preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
22106
22036
  centerTitle: opts.centerTitle ?? true,
22107
22037
  autoFitMinRatio,
22108
22038
  // 보도자료는 머리박스가 1페이지 최상단을 차지하는 서식이라 표지·목차와 양립 불가 —
22109
22039
  // 켜면 머리박스가 표지에 얹히고 25pt 제목·부제가 유실된다 (docHead 프리셋 게이팅과 동일 관례)
22110
- cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "" } : null,
22040
+ cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {} } : null,
22041
+ bodyPtExplicit: opts.bodyPt !== void 0,
22042
+ lineSpacingExplicit: opts.lineSpacing !== void 0,
22043
+ bodyFontExplicit: opts.bodyFont !== void 0,
22044
+ summary: opts.summary?.trim() || null,
22045
+ docInfo: opts.docInfo ?? null,
22111
22046
  toc: preset !== "press" && (opts.toc ?? gaejosik),
22112
22047
  fonts: opts.fonts ?? {},
22113
22048
  sizes: opts.sizes ?? {},
@@ -22116,16 +22051,18 @@ function resolveGongmun(opts) {
22116
22051
  pageNumbers: opts.pageNumbers ?? (gaejosik || preset === "report" || preset === "plan"),
22117
22052
  // 머리말·꼬리말 — 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
22118
22053
  // 기안문 0(실결재 41/60건 h0/f0)
22119
- headerFooter: usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
22054
+ headerFooter: preset === "report" || preset === "plan" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
22120
22055
  // "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
22121
22056
  endMark: opts.endMark ?? preset === "official",
22122
22057
  approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
22123
22058
  // 본문 제목박스 — 실측(GT3·GT12): 목차 뒤 본문 시작에 제목 반복. 표지 켜진 개조식 기본
22124
22059
  bodyTitleBox: opts.bodyTitleBox ?? (gaejosik && coverOn),
22125
22060
  // h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
22126
- h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "box" : preset === "notice" ? "number" : "none"),
22061
+ // v5 라운드 3: 보고서·계획서 기본 band(띠 표) — 서울 plan 7/19·교육청 7/18 실측, 실무자 요청
22062
+ h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "band" : preset === "notice" ? "number" : "none"),
22127
22063
  // 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
22128
- bullet2: opts.bullet2 ?? (preset === "plan" || preset === "notice" || preset === "press" ? "\u3147" : "\u25CB"),
22064
+ // v5: 서울 실결재 ㅇ(이응) 지배 — 보고서·계획서·통지·보도자료 ㅇ, 중앙부처 개조식 양식만 ○
22065
+ bullet2: opts.bullet2 ?? (preset === "gaejosik" ? "\u25CB" : "\u3147"),
22129
22066
  // 단일 형제 부호 생략 — 규정이지만 부호 없는 계단이 실무 눈에 어색 (실무자 QA)
22130
22067
  suppressSingle: opts.suppressSingle ?? false,
22131
22068
  docHead: preset === "official" && opts.docHead ? opts.docHead : null,
@@ -22266,7 +22203,7 @@ function headingCharPrId(level) {
22266
22203
  if (level === 3) return CHAR_H3;
22267
22204
  return CHAR_H4;
22268
22205
  }
22269
- function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100, restFont = fontId) {
22206
+ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100, restFont = fontId, spacingPct = 0) {
22270
22207
  const boldAttr = bold ? ` bold="1"` : "";
22271
22208
  const italicAttr = italic ? ` italic="1"` : "";
22272
22209
  const effFont = fontId;
@@ -22274,13 +22211,13 @@ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_C
22274
22211
  return ` <hh:charPr id="${id}" height="${height}" textColor="${escapeXml(textColor)}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
22275
22212
  <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${restFont}" japanese="${restFont}" other="${restFont}" symbol="${restFont}" user="${restFont}"/>
22276
22213
  <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
22277
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
22214
+ <hh:spacing hangul="${spacingPct}" latin="${spacingPct}" hanja="${spacingPct}" japanese="0" other="0" symbol="0" user="0"/>
22278
22215
  <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
22279
22216
  <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>${boldEl}
22280
22217
  </hh:charPr>`;
22281
22218
  }
22282
22219
  function paraPr(id, opts = {}) {
22283
- const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, keepWord = false, keepWithNext = false, outlineLevel } = opts;
22220
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, right = 0, keepWord = false, keepWithNext = false, outlineLevel } = opts;
22284
22221
  const breakNonLatin = keepWord ? "BREAK_WORD" : "KEEP_WORD";
22285
22222
  const snapGrid = keepWord ? "0" : "1";
22286
22223
  const heading = outlineLevel !== void 0 ? `<hh:heading type="OUTLINE" idRef="0" level="${outlineLevel}"/>` : `<hh:heading type="NONE" idRef="0" level="0"/>`;
@@ -22289,7 +22226,7 @@ function paraPr(id, opts = {}) {
22289
22226
  ${heading}
22290
22227
  <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="${keepWithNext ? 1 : 0}" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
22291
22228
  <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
22292
- <hh:margin><hc:intent value="${indent}" unit="HWPUNIT"/><hc:left value="${left}" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="${spaceBefore}" unit="HWPUNIT"/><hc:next value="${spaceAfter}" unit="HWPUNIT"/></hh:margin>
22229
+ <hh:margin><hc:intent value="${indent}" unit="HWPUNIT"/><hc:left value="${left}" unit="HWPUNIT"/><hc:right value="${right}" unit="HWPUNIT"/><hc:prev value="${spaceBefore}" unit="HWPUNIT"/><hc:next value="${spaceAfter}" unit="HWPUNIT"/></hh:margin>
22293
22230
  <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
22294
22231
  <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
22295
22232
  </hh:paraPr>`;
@@ -22395,15 +22332,15 @@ function beginInlineDoc(footnotes) {
22395
22332
  function endInlineDoc() {
22396
22333
  inlineDoc = null;
22397
22334
  }
22398
- function extractFootnoteDefs(md2) {
22335
+ function extractFootnoteDefs(md) {
22399
22336
  const defs = /* @__PURE__ */ new Map();
22400
- const out = md2.replace(/\r\n?/g, "\n").split("\n").filter((line) => {
22337
+ const out = md.replace(/\r\n?/g, "\n").split("\n").filter((line) => {
22401
22338
  const m = /^\[\^([^\]\s]+)\]:\s?(.*)$/.exec(line);
22402
22339
  if (!m) return true;
22403
22340
  defs.set(m[1], m[2].trim());
22404
22341
  return false;
22405
22342
  }).join("\n");
22406
- return { md: defs.size > 0 ? out : md2, defs };
22343
+ return { md: defs.size > 0 ? out : md, defs };
22407
22344
  }
22408
22345
  function buildPrvText(blocks) {
22409
22346
  const lines = [];
@@ -22430,8 +22367,8 @@ function findMathDelim(s, from) {
22430
22367
  }
22431
22368
  return i;
22432
22369
  }
22433
- function parseMarkdownToBlocks(md2) {
22434
- const lines = md2.replace(/\r\n?/g, "\n").split("\n");
22370
+ function parseMarkdownToBlocks(md) {
22371
+ const lines = md.replace(/\r\n?/g, "\n").split("\n");
22435
22372
  const blocks = [];
22436
22373
  let i = 0;
22437
22374
  const listStack = [];
@@ -22848,9 +22785,9 @@ function assertSequentialIds(rows, label, startId) {
22848
22785
  }
22849
22786
  }
22850
22787
  }
22851
- function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls = []) {
22852
- const measured = !!gongmun && usesReportFonts(gongmun.preset);
22853
- const richAssets = !!gongmun && needsGaejosikAssets(gongmun);
22788
+ function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls = [], v5 = false) {
22789
+ const measured = !v5 && !!gongmun && usesReportFonts(gongmun.preset);
22790
+ const richAssets = !v5 && !!gongmun && needsGaejosikAssets(gongmun);
22854
22791
  let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
22855
22792
  if (gongmun) {
22856
22793
  body = gongmun.bodyHeight;
@@ -23029,6 +22966,24 @@ ${base2.join("\n")}
23029
22966
  ${base.join("\n")}
23030
22967
  </hh:paraProperties>`;
23031
22968
  }
22969
+ function buildParaPropertiesV5(gongmun, extra) {
22970
+ const ls = gongmun.lineSpacing;
22971
+ const base = [
22972
+ paraPr(0, { lineSpacing: ls, keepWord: true }),
22973
+ paraPr(1, { align: gongmun.centerTitle ? "CENTER" : "LEFT", spaceBefore: 400, spaceAfter: 400, lineSpacing: ls, keepWord: true }),
22974
+ paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: ls, keepWord: true }),
22975
+ paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
22976
+ paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
22977
+ paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400, keepWord: true }),
22978
+ paraPr(6, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true }),
22979
+ paraPr(7, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true }),
22980
+ ...extra
22981
+ ];
22982
+ assertSequentialIds(base, "paraPr", 0);
22983
+ return `<hh:paraProperties itemCnt="${base.length}">
22984
+ ${base.join("\n")}
22985
+ </hh:paraProperties>`;
22986
+ }
23032
22987
  function buildNumberings() {
23033
22988
  const heads = Array.from(
23034
22989
  { length: 7 },
@@ -23045,11 +23000,11 @@ function fontEntry(id, face, weight = 6) {
23045
23000
  <hh:typeInfo familyType="FCAT_GOTHIC" weight="${weight}" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
23046
23001
  </hh:font>`;
23047
23002
  }
23048
- function staticFontNext(gongmun) {
23049
- return gongmun && needsGaejosikAssets(gongmun) ? 8 : 3;
23003
+ function staticFontNext(gongmun, v5 = false) {
23004
+ return !v5 && gongmun && needsGaejosikAssets(gongmun) ? 8 : 3;
23050
23005
  }
23051
- function buildFontFaces(gongmun, bodyFace, extraFonts = []) {
23052
- if (gongmun && needsGaejosikAssets(gongmun)) {
23006
+ function buildFontFaces(gongmun, bodyFace, extraFonts = [], v5 = false) {
23007
+ if (!v5 && gongmun && needsGaejosikAssets(gongmun)) {
23053
23008
  const ov = gongmun.fonts;
23054
23009
  const bodyDefault = usesReportFonts(gongmun.preset) ? gongmun.preset !== "gaejosik" && gongmun.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD734\uBA3C\uBA85\uC870" : bodyFace;
23055
23010
  const faces = [
@@ -23065,58 +23020,29 @@ function buildFontFaces(gongmun, bodyFace, extraFonts = []) {
23065
23020
  if (faces.length !== staticFontNext(gongmun)) {
23066
23021
  throw new Error(`fontface \uC815\uC801 \uAC1C\uC218 \uB4DC\uB9AC\uD504\uD2B8: ${faces.length} \u2260 staticFontNext ${staticFontNext(gongmun)}`);
23067
23022
  }
23068
- const list = faces.map((f, i) => fontEntry(i, f, i === 2 || i === 3 ? 9 : 6)).concat(extraFonts.map((f, i) => fontEntry(faces.length + i, f))).join("\n");
23069
- const langs = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
23070
- return `<hh:fontfaces itemCnt="${langs.length}">
23071
- ` + langs.map((l) => ` <hh:fontface lang="${l}" fontCnt="${faces.length + extraFonts.length}">
23072
- ${list}
23023
+ const list2 = faces.map((f, i) => fontEntry(i, f, i === 2 || i === 3 ? 9 : 6)).concat(extraFonts.map((f, i) => fontEntry(faces.length + i, f))).join("\n");
23024
+ const langs2 = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
23025
+ return `<hh:fontfaces itemCnt="${langs2.length}">
23026
+ ` + langs2.map((l) => ` <hh:fontface lang="${l}" fontCnt="${faces.length + extraFonts.length}">
23027
+ ${list2}
23073
23028
  </hh:fontface>`).join("\n") + `
23074
23029
  </hh:fontfaces>`;
23075
23030
  }
23076
- const extraH = extraFonts.map((f, i) => fontEntry(3 + i, f)).join("\n");
23077
- const extraBlock = extraH ? `
23078
- ${extraH}` : "";
23079
- return `<hh:fontfaces itemCnt="7">
23080
- <hh:fontface lang="HANGUL" fontCnt="${3 + extraFonts.length}">
23081
- ${fontEntry(0, bodyFace)}
23082
- ${fontEntry(1, "\uD568\uCD08\uB86C\uB3CB\uC6C0")}
23083
- ${fontEntry(2, "HY\uACAC\uACE0\uB515", 9)}${extraBlock}
23084
- </hh:fontface>
23085
- <hh:fontface lang="LATIN" fontCnt="${3 + extraFonts.length}">
23086
- <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
23087
- <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
23088
- </hh:font>
23089
- <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
23090
- <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
23091
- </hh:font>
23092
- <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
23093
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
23094
- </hh:font>${extraBlock}
23095
- </hh:fontface>
23096
- <hh:fontface lang="HANJA" fontCnt="1">
23097
- ${fontEntry(0, "\uD568\uCD08\uB86C\uBC14\uD0D5")}
23098
- </hh:fontface>
23099
- <hh:fontface lang="JAPANESE" fontCnt="1">
23100
- ${fontEntry(0, "\uAD74\uB9BC")}
23101
- </hh:fontface>
23102
- <hh:fontface lang="OTHER" fontCnt="1">
23103
- ${fontEntry(0, "\uAD74\uB9BC")}
23104
- </hh:fontface>
23105
- <hh:fontface lang="SYMBOL" fontCnt="1">
23106
- ${fontEntry(0, "Symbol")}
23107
- </hh:fontface>
23108
- <hh:fontface lang="USER" fontCnt="1">
23109
- ${fontEntry(0, "\uAD74\uB9BC")}
23110
- </hh:fontface>
23031
+ const list = [fontEntry(0, bodyFace), fontEntry(1, "\uD568\uCD08\uB86C\uB3CB\uC6C0"), fontEntry(2, "HY\uACAC\uACE0\uB515", 9)].concat(extraFonts.map((f, i) => fontEntry(3 + i, f))).join("\n");
23032
+ const langs = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
23033
+ return `<hh:fontfaces itemCnt="${langs.length}">
23034
+ ` + langs.map((l) => ` <hh:fontface lang="${l}" fontCnt="${3 + extraFonts.length}">
23035
+ ${list}
23036
+ </hh:fontface>`).join("\n") + `
23111
23037
  </hh:fontfaces>`;
23112
23038
  }
23113
- function staticBorderFillItems(gongmun) {
23039
+ function staticBorderFillItems(gongmun, v5 = false) {
23114
23040
  const thin = ["0.12 mm", "#000000"];
23115
23041
  const items = [
23116
23042
  borderFillEntry(1, {}),
23117
23043
  borderFillEntry(2, { l: thin, r: thin, t: thin, b: thin })
23118
23044
  ];
23119
- if (gongmun && needsGaejosikAssets(gongmun)) {
23045
+ if (!v5 && gongmun && needsGaejosikAssets(gongmun)) {
23120
23046
  const c = GAEJOSIK_COLORS;
23121
23047
  const edge = ["0.12 mm", c.border];
23122
23048
  const grayLine = ["0.1 mm", c.titleLine];
@@ -23143,11 +23069,11 @@ function staticBorderFillItems(gongmun) {
23143
23069
  }
23144
23070
  return items;
23145
23071
  }
23146
- function staticBorderFillNext(gongmun) {
23147
- return staticBorderFillItems(gongmun).length + 1;
23072
+ function staticBorderFillNext(gongmun, v5 = false) {
23073
+ return staticBorderFillItems(gongmun, v5).length + 1;
23148
23074
  }
23149
- function buildBorderFills(gongmun, extra = []) {
23150
- const items = [...staticBorderFillItems(gongmun), ...extra];
23075
+ function buildBorderFills(gongmun, extra = [], v5 = false) {
23076
+ const items = [...staticBorderFillItems(gongmun, v5), ...extra];
23151
23077
  assertSequentialIds(items, "borderFill", 1);
23152
23078
  return `<hh:borderFills itemCnt="${items.length}">
23153
23079
  ${items.join("\n")}
@@ -23155,27 +23081,27 @@ ${items.join("\n")}
23155
23081
  }
23156
23082
  function buildStyles(gongmun) {
23157
23083
  const items = [
23158
- `<hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>`
23084
+ `<hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langID="1042" lockForm="0"/>`
23159
23085
  ];
23160
23086
  if (gongmun) {
23161
23087
  for (let lvl = 1; lvl <= 4; lvl++) {
23162
- items.push(`<hh:style id="${lvl}" type="PARA" name="\uAC1C\uC694 ${lvl}" engName="Outline ${lvl}" paraPrIDRef="${lvl}" charPrIDRef="${4 + lvl}" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>`);
23088
+ items.push(`<hh:style id="${lvl}" type="PARA" name="\uAC1C\uC694 ${lvl}" engName="Outline ${lvl}" paraPrIDRef="${lvl}" charPrIDRef="${4 + lvl}" nextStyleIDRef="0" langID="1042" lockForm="0"/>`);
23163
23089
  }
23164
23090
  }
23165
23091
  return `<hh:styles itemCnt="${items.length}">
23166
23092
  ${items.join("\n ")}
23167
23093
  </hh:styles>`;
23168
23094
  }
23169
- function generateHeaderXml(theme, gongmun, ratioVariants = [], extraBorderFills = [], extraCharPrXmls = [], listIndentVariants = [], extraFonts = []) {
23170
- const bodyFace = gongmun?.fonts.body ?? (gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5");
23171
- const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants, extraCharPrXmls);
23172
- const paraPropsXml = buildParaProperties(gongmun, gongmun ? listIndentVariants : []);
23095
+ function generateHeaderXml(theme, gongmun, ratioVariants = [], extraBorderFills = [], extraCharPrXmls = [], listIndentVariants = [], extraFonts = [], v5 = null) {
23096
+ const bodyFace = v5?.bodyFace ?? gongmun?.fonts.body ?? (gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5");
23097
+ const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants, extraCharPrXmls, !!v5);
23098
+ const paraPropsXml = v5 ? buildParaPropertiesV5(gongmun, v5.paraPrXmls) : buildParaProperties(gongmun, gongmun ? listIndentVariants : []);
23173
23099
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
23174
23100
  <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
23175
23101
  <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
23176
23102
  <hh:refList>
23177
- ${buildFontFaces(gongmun, bodyFace, extraFonts)}
23178
- ${buildBorderFills(gongmun, extraBorderFills)}
23103
+ ${buildFontFaces(gongmun, bodyFace, extraFonts, !!v5)}
23104
+ ${buildBorderFills(gongmun, extraBorderFills, !!v5)}
23179
23105
  ${charPropsXml}
23180
23106
  <hh:tabProperties itemCnt="0"/>
23181
23107
  ${buildNumberings()}
@@ -23579,14 +23505,14 @@ function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
23579
23505
  const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g.totalW, h: g.titleH, paras: titlePara, name: "__kordoc_h1" });
23580
23506
  const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g.botDarkW, h: g.botBarH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g.botLightW, h: g.botBarH, paras: barEmpty });
23581
23507
  const tbl2 = table([topRow, titleRow, botRow], g.totalW, g.barH + g.titleH + g.botBarH, 3, 1, bodyWidth);
23582
- const host = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
23508
+ const host2 = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
23583
23509
  const date = gongmun.cover?.date ?? formatGaejosikDate(/* @__PURE__ */ new Date());
23584
23510
  const org = gongmun.cover?.org ?? "";
23585
23511
  const sub = (t) => `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_COVER_SUB}"><hp:t>${escapeXml(t)}</hp:t></hp:run></hp:p>`;
23586
23512
  const subEmpty = emptyPara(GJ_PARA_COVER, false, GJ_CHAR_COVER_SUB);
23587
23513
  return [
23588
23514
  ...Array(5).fill(emptyPara()),
23589
- host,
23515
+ host2,
23590
23516
  ...Array(5).fill(emptyPara()),
23591
23517
  sub(date),
23592
23518
  ...org ? [...Array(4).fill(subEmpty), sub(org)] : []
@@ -23617,8 +23543,8 @@ function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
23617
23543
  1,
23618
23544
  bodyWidth
23619
23545
  );
23620
- const host = `<hp:p paraPrIDRef="${GJ_PARA_BAR}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${box}</hp:run></hp:p>`;
23621
- return [label, emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR), host];
23546
+ const host2 = `<hp:p paraPrIDRef="${GJ_PARA_BAR}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${box}</hp:run></hp:p>`;
23547
+ return [label, emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR), host2];
23622
23548
  }
23623
23549
  function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
23624
23550
  const g = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
@@ -23697,8 +23623,8 @@ function levenshtein(a, b) {
23697
23623
  }
23698
23624
 
23699
23625
  // src/roundtrip/markdown-units.ts
23700
- function splitMarkdownUnits(md2) {
23701
- const lines = md2.split("\n");
23626
+ function splitMarkdownUnits(md) {
23627
+ const lines = md.split("\n");
23702
23628
  const units = [];
23703
23629
  let i = 0;
23704
23630
  while (i < lines.length) {
@@ -24098,7 +24024,8 @@ function removeNestedTables(html) {
24098
24024
  var THIN = ["0.12 mm", "#000000"];
24099
24025
  var THICK = ["0.4 mm", "#000000"];
24100
24026
  var DOUBLE = ["0.5 mm", "#000000", "DOUBLE_SLIM"];
24101
- var EDGE = { thin: THIN, thick: THICK, double: DOUBLE };
24027
+ var MID = ["0.15 mm", "#000000"];
24028
+ var EDGE = { thin: THIN, thick: THICK, double: DOUBLE, mid: MID };
24102
24029
  function sideOf(kind) {
24103
24030
  return kind === "none" ? void 0 : EDGE[kind];
24104
24031
  }
@@ -24195,6 +24122,7 @@ function profileCharPrXml(id, def, namedFontId) {
24195
24122
  let restFont = 0;
24196
24123
  if (namedFontId != null) {
24197
24124
  font = namedFontId;
24125
+ restFont = font;
24198
24126
  } else {
24199
24127
  const rawFont = def.fontRef_hangul != null ? parseInt(def.fontRef_hangul, 10) || 0 : 0;
24200
24128
  font = rawFont >= 0 && rawFont <= LEGACY_PROFILE_FONT_MAX ? rawFont : 0;
@@ -24556,6 +24484,7 @@ function nextTableId() {
24556
24484
  function resetTableIds() {
24557
24485
  tableIdCounter = TABLE_ID_BASE;
24558
24486
  }
24487
+ var curFace = "hcr";
24559
24488
  var DATA_TABLE_INSET = 1800;
24560
24489
  function anchorOfMarkdownCell(cell2) {
24561
24490
  return normalizeAnchor(cell2.replace(/!\[[^\]]*\]\([^)]*\)/g, ""));
@@ -24575,7 +24504,7 @@ var CELL_PAD = 1200;
24575
24504
  function cellContentWidth(text, charHeight) {
24576
24505
  let max = 0;
24577
24506
  for (const seg of text.replace(/!\[[^\]]*\]\([^)\s]+\)/g, "").replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
24578
- const w = measureTextWidth(seg.trim(), charHeight, 100);
24507
+ const w = measureTextWidth(seg.trim(), charHeight, 100, { faceClass: curFace });
24579
24508
  if (w > max) max = w;
24580
24509
  }
24581
24510
  return max;
@@ -24584,25 +24513,38 @@ function cellMinWordWidth(text, charHeight) {
24584
24513
  let max = 0;
24585
24514
  for (const seg of text.replace(/!\[[^\]]*\]\([^)\s]+\)/g, "").replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
24586
24515
  for (const word of seg.trim().split(/\s+/)) {
24587
- const w = measureTextWidth(word, charHeight, 100);
24516
+ const w = measureTextWidth(word, charHeight, 100, { faceClass: curFace });
24588
24517
  if (w > max) max = w;
24589
24518
  }
24590
24519
  }
24591
24520
  return max;
24592
24521
  }
24593
- function computeColWidths(colMax, totalWidth, colMinWord = []) {
24522
+ var REMARK_HEAD = /^(비고|비고란|비고사항|참고|참고사항|출처|(관련|법적|추진)?근거|근거법령|담당(자|부서)?|연락처)$/u;
24523
+ var CONTENT_HEAD = /(내용|사항|실적|계획|방안|사유|개요|설명|임무|역할|현황|성과)$/u;
24524
+ var REMARK_SHARE = 0.25;
24525
+ var CONTENT_WEIGHT = 2;
24526
+ function colRoles(headers) {
24527
+ const clean = headers.map((h) => (h ?? "").replace(/!\[[^\]]*\]\([^)\s]+\)/g, "").replace(/\*\*|__|`|\s+/g, ""));
24528
+ const roles = clean.map((h) => REMARK_HEAD.test(h) ? "remark" : h.length <= 12 && CONTENT_HEAD.test(h) ? "content" : null);
24529
+ const last = roles.length - 1;
24530
+ if (roles.length >= 3 && roles.includes("content") && roles[last] === null) roles[last] = "remark";
24531
+ return roles;
24532
+ }
24533
+ function computeColWidths(colMax, totalWidth, colMinWord = [], roles = []) {
24594
24534
  const colCnt = colMax.length;
24595
24535
  const minW = Math.min(Math.max(2e3, Math.round(totalWidth * 0.06)), Math.floor(totalWidth / colCnt));
24596
24536
  const cap = Math.round(totalWidth * 0.8);
24597
- const raw = colMax.map((w) => Math.min(Math.max(w + CELL_PAD, minW), cap));
24598
- const floor = raw.map((r, i) => Math.min(Math.max(minW, (colMinWord[i] ?? 0) + CELL_PAD), r, cap));
24537
+ const capOf = colMax.map((_, i) => roles[i] === "remark" ? Math.min(cap, Math.max(Math.round(totalWidth * REMARK_SHARE), (colMinWord[i] ?? 0) + CELL_PAD)) : cap);
24538
+ const raw = colMax.map((w, i) => Math.min(Math.max(w + CELL_PAD, minW), capOf[i]));
24539
+ const wgt = raw.map((r, i) => roles[i] === "content" ? r * CONTENT_WEIGHT : r);
24540
+ const floor = raw.map((r, i) => Math.min(Math.max(minW, (colMinWord[i] ?? 0) + CELL_PAD), r, capOf[i]));
24599
24541
  const widths = Array(colCnt).fill(0);
24600
24542
  const free = new Set(raw.map((_, i) => i));
24601
24543
  let budget = totalWidth;
24602
24544
  let sumFloorFree = floor.reduce((a, b) => a + b, 0);
24603
24545
  for (const i of [...raw.keys()].sort((a, b) => raw[a] - raw[b])) {
24604
24546
  if (free.size <= 1) break;
24605
- const fixed = Math.round(raw[i] * 1.12);
24547
+ const fixed = roles[i] === "remark" ? Math.min(Math.round(raw[i] * 1.12), capOf[i]) : Math.round(raw[i] * 1.12);
24606
24548
  if (budget - fixed < sumFloorFree - floor[i]) break;
24607
24549
  widths[i] = fixed;
24608
24550
  free.delete(i);
@@ -24612,29 +24554,30 @@ function computeColWidths(colMax, totalWidth, colMinWord = []) {
24612
24554
  const sumFreeRaw = [...free].reduce((a, i) => a + raw[i], 0);
24613
24555
  if (budget > sumFreeRaw * 1.6) {
24614
24556
  const sumRaw = raw.reduce((a, b) => a + b, 0);
24615
- for (let i = 0; i < colCnt; i++) widths[i] = Math.min(cap, Math.round(raw[i]) + Math.floor(raw[i] / sumRaw * (totalWidth - sumRaw)));
24557
+ const sumWgt = wgt.reduce((a, b) => a + b, 0);
24558
+ for (let i = 0; i < colCnt; i++) widths[i] = Math.min(capOf[i], Math.round(raw[i]) + Math.floor(wgt[i] / sumWgt * (totalWidth - sumRaw)));
24616
24559
  free.clear();
24617
24560
  } else {
24618
24561
  for (; ; ) {
24619
- const sum2 = [...free].reduce((a, i) => a + raw[i], 0);
24620
- const short = [...free].filter((i) => raw[i] / sum2 * budget < floor[i]);
24562
+ const sum2 = [...free].reduce((a, i) => a + wgt[i], 0);
24563
+ const short = [...free].filter((i) => wgt[i] / sum2 * budget < floor[i]);
24621
24564
  if (short.length === 0) break;
24622
24565
  const shortSum = short.reduce((a, i) => a + floor[i], 0);
24623
- const scale = Math.min(1, (budget - (free.size - short.length) * minW) / shortSum);
24566
+ const scale2 = Math.min(1, (budget - (free.size - short.length) * minW) / shortSum);
24624
24567
  for (const i of short) {
24625
- widths[i] = Math.max(minW, Math.floor(floor[i] * scale));
24568
+ widths[i] = Math.max(minW, Math.floor(floor[i] * scale2));
24626
24569
  free.delete(i);
24627
24570
  budget -= widths[i];
24628
24571
  }
24629
24572
  if (free.size === 0) break;
24630
24573
  }
24631
- const sum = [...free].reduce((a, i) => a + raw[i], 0);
24632
- for (const i of free) widths[i] = Math.floor(raw[i] / sum * budget);
24574
+ const sum = [...free].reduce((a, i) => a + wgt[i], 0);
24575
+ for (const i of free) widths[i] = Math.min(capOf[i], Math.floor(wgt[i] / sum * budget));
24633
24576
  }
24634
24577
  let rem = totalWidth - widths.reduce((a, b) => a + b, 0);
24635
- const order = [...raw.keys()].sort((a, b) => raw[b] - raw[a]);
24578
+ const order = [...raw.keys()].sort((a, b) => wgt[b] - wgt[a]);
24636
24579
  for (let k = 0, skipped = 0; rem > 0; k = (k + 1) % colCnt, rem--) {
24637
- while (widths[order[k]] >= cap && skipped < colCnt) {
24580
+ while (widths[order[k]] >= capOf[order[k]] && skipped < colCnt) {
24638
24581
  skipped++;
24639
24582
  k = (k + 1) % colCnt;
24640
24583
  }
@@ -24658,13 +24601,26 @@ function estimateRowHeight(cells, widths, charHeight) {
24658
24601
  const usable = Math.max((widths[c] ?? widths[widths.length - 1]) - CELL_PAD, 1e3);
24659
24602
  let lines = 0;
24660
24603
  for (const seg of cell2.replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
24661
- lines += Math.max(1, Math.ceil(measureTextWidth(seg.trim(), charHeight, 100) / usable));
24604
+ lines += Math.max(1, Math.ceil(measureTextWidth(seg.trim(), charHeight, 100, { faceClass: curFace }) / usable));
24662
24605
  }
24663
24606
  if (lines > maxLines) maxLines = lines;
24664
24607
  });
24665
24608
  return maxLines * Math.round(charHeight * 1.6) + 282;
24666
24609
  }
24610
+ function requiredTableWidth(rows, charHeight, faceClass = "hcr") {
24611
+ const prev = curFace;
24612
+ curFace = faceClass;
24613
+ const colCnt = Math.max(...rows.map((r) => r.length), 1);
24614
+ const colMax = Array(colCnt).fill(0);
24615
+ for (const row of rows) row.forEach((cell2, c) => {
24616
+ const w = cellContentWidth(cell2, charHeight);
24617
+ if (w > colMax[c]) colMax[c] = w;
24618
+ });
24619
+ curFace = prev;
24620
+ return colMax.reduce((a, w) => a + Math.round((w + CELL_PAD) * 1.12), 0);
24621
+ }
24667
24622
  function generateTable(rows, theme, style = null, remap = null, seq = 0, images = null) {
24623
+ curFace = style?.faceClass ?? "hcr";
24668
24624
  const rowCnt = rows.length;
24669
24625
  const colCnt = Math.max(...rows.map((r) => r.length), 1);
24670
24626
  const reg = style?.bfRegistry ?? null;
@@ -24682,8 +24638,9 @@ function generateTable(rows, theme, style = null, remap = null, seq = 0, images
24682
24638
  const mw = cellMinWordWidth(cell2, measureH);
24683
24639
  if (mw > colMinWord[c]) colMinWord[c] = mw;
24684
24640
  }));
24685
- const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalW, colMinWord);
24686
- const colCentered = colWidths.map((w, c) => colMaxBody[c] + CELL_PAD <= w);
24641
+ const roles = style ? colRoles(rows[0] ?? []) : [];
24642
+ const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalW, colMinWord, roles);
24643
+ const colCentered = colWidths.map((w, c) => roles[c] !== "content" && colMaxBody[c] + CELL_PAD <= w);
24687
24644
  const labelCol0 = !!reg && colCnt === 2 && colCentered[0] && rows.every((r) => (r[0] ?? "").replace(/\*\*|__|`/g, "").length <= 12);
24688
24645
  const tblId = nextTableId();
24689
24646
  const useHeaderStyle = !style && (theme.tableHeader !== theme.body || theme.tableHeaderBold);
@@ -24802,8 +24759,11 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
24802
24759
  if (dc === 0 && mw > colMinWord[c]) colMinWord[c] = mw;
24803
24760
  }
24804
24761
  });
24805
- const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalWidth, colMinWord);
24806
- const colCentered = colWidths.map((w, c) => colMaxBody[c] + CELL_PAD <= w);
24762
+ const headers = Array.from({ length: colCnt }, () => "");
24763
+ for (const [i, cell2] of placed.entries()) if (cell2.r === 0 && cell2.c < colCnt) headers[cell2.c] = unescapeHtml(cellLines[i].join(" "));
24764
+ const roles = style ? colRoles(headers) : [];
24765
+ const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalWidth, colMinWord, roles);
24766
+ const colCentered = colWidths.map((w, c) => roles[c] !== "content" && colMaxBody[c] + CELL_PAD <= w);
24807
24767
  const cellH = style ? Math.round(measureH * 1.6) + 282 : 1500;
24808
24768
  const tblW = colWidths.reduce((a, b) => a + b, 0);
24809
24769
  const tblId = nextTableId();
@@ -24915,7 +24875,7 @@ function tbl(rows, w, h, cols) {
24915
24875
  return `<hp:tbl id="${++extraTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="1" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="283" right="283" top="283" bottom="283"/><hp:inMargin left="141" right="141" top="141" bottom="141"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
24916
24876
  }
24917
24877
  function tc(opts) {
24918
- return `<hp:tc name="${opts.name ?? ""}" header="0" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="${opts.bf}"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${opts.paras}</hp:subList><hp:cellAddr colAddr="${opts.col}" rowAddr="${opts.row}"/><hp:cellSpan colSpan="${opts.colSpan ?? 1}" rowSpan="1"/><hp:cellSz width="${opts.w}" height="${opts.h}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
24878
+ return `<hp:tc name="${opts.name ?? ""}" header="0" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="${opts.bf}"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="${opts.vAlign ?? "CENTER"}" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${opts.paras}</hp:subList><hp:cellAddr colAddr="${opts.col}" rowAddr="${opts.row}"/><hp:cellSpan colSpan="${opts.colSpan ?? 1}" rowSpan="${opts.rowSpan ?? 1}"/><hp:cellSz width="${opts.w}" height="${opts.h}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
24919
24879
  }
24920
24880
  function para(text, paraPrId, charPrId) {
24921
24881
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
@@ -24986,48 +24946,12 @@ function docframeCharPrXmls(base, richAssets) {
24986
24946
  charPr(ids.pressContact, 1e3, false, false, body)
24987
24947
  ];
24988
24948
  }
24989
- function labeled(label, value, paraPrId) {
24990
- 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>`;
24991
- }
24992
24949
  function blank() {
24993
24950
  return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}"><hp:t></hp:t></hp:run></hp:p>`;
24994
24951
  }
24995
- function buildDocHead(g, ids) {
24996
- const h = g.docHead;
24997
- const out = [];
24998
- if (h.org) {
24999
- out.push(para(h.org, GONGMUN_CENTER, ids.org), blank());
25000
- }
25001
- if (h.to !== void 0) out.push(labeled("\uC218\uC2E0 ", h.to, PARA_NORMAL));
25002
- out.push(para(h.via ? `(\uACBD\uC720) ${h.via}` : "(\uACBD\uC720)", PARA_NORMAL, CHAR_NORMAL));
25003
- if (h.title !== void 0) out.push(labeled("\uC81C\uBAA9 ", h.title, PARA_NORMAL));
25004
- out.push(blank());
25005
- return out;
25006
- }
25007
- function footSep(g) {
25008
- const bodyMm = 210 - g.margins.left - g.margins.right;
25009
- return "\u2500".repeat(Math.max(10, Math.round(bodyMm * (46 / 175))));
25010
- }
25011
24952
  function isInternalApproval(to) {
25012
24953
  return !!to && /^\s*내\s*부\s*결\s*재/.test(to);
25013
24954
  }
25014
- function buildDocFoot(g, ids) {
25015
- const f = g.docFoot;
25016
- const out = [blank()];
25017
- if (f.sender && !isInternalApproval(g.docHead?.to)) {
25018
- out.push(para(f.sender, GONGMUN_CENTER, ids.sender), blank());
25019
- }
25020
- const foot = (text) => para(text, GONGMUN_TBL_LEFT, ids.foot);
25021
- out.push(foot(footSep(g)));
25022
- if (f.drafter || f.reviewer || f.approver)
25023
- out.push(foot(`\uAE30\uC548\uC790 ${f.drafter ?? ""} \uAC80\uD1A0\uC790 ${f.reviewer ?? ""} \uACB0\uC7AC\uAD8C\uC790 ${f.approver ?? ""}`));
25024
- if (f.cooperator) out.push(foot(`\uD611\uC870\uC790 ${f.cooperator}`));
25025
- if (f.docNum || f.receive) out.push(foot(`\uC2DC\uD589 ${f.docNum ?? ""} \uC811\uC218 ${f.receive ?? ""}`));
25026
- if (f.address || f.site) out.push(foot(`${f.address ?? ""} / ${f.site ?? ""}`));
25027
- if (f.phone || f.fax || f.email || f.disclosure)
25028
- out.push(foot(`\uC804\uD654 ${f.phone ?? ""} \uC804\uC1A1 ${f.fax ?? ""} / ${f.email ?? ""} / ${f.disclosure ?? ""}`));
25029
- return out;
25030
- }
25031
24955
  function buildReportInfo(g, ids) {
25032
24956
  return para(g.reportInfo, GONGMUN_RIGHT, ids.small);
25033
24957
  }
@@ -25486,9 +25410,6 @@ function buildPreamble(blocks, ctx) {
25486
25410
  if (gongmun?.approval && bfReg) {
25487
25411
  preamble.push(buildApprovalTable(gongmun.approval, bfReg, richAssets ? GJ_CHAR_APPROVAL : GONGMUN_APPROVAL_CHAR));
25488
25412
  }
25489
- if (gongmun?.docHead && dfIds) {
25490
- preamble.push(...buildDocHead(gongmun, dfIds));
25491
- }
25492
25413
  if (gongmun?.noticeHead && dfIds) {
25493
25414
  preamble.push(...buildNoticeHead(gongmun));
25494
25415
  }
@@ -25709,8 +25630,8 @@ function renderHtmlTable(block, ctx) {
25709
25630
  ctx.opener.emitCarrier(ctx.paraXmls);
25710
25631
  return `<hp:p paraPrIDRef="${tableStyle && bfReg ? GONGMUN_RIGHT : 0}" styleIDRef="0"><hp:run charPrIDRef="0">${tbl2}</hp:run></hp:p>`;
25711
25632
  }
25712
- const plain2 = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
25713
- return plain2 ? generateParagraph(plain2) : "";
25633
+ const plain3 = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
25634
+ return plain3 ? generateParagraph(plain3) : "";
25714
25635
  }
25715
25636
  function appendPostamble(blocks, ctx) {
25716
25637
  const { gongmun, dfIds, bfReg, tableStyle, paraXmls } = ctx;
@@ -25722,9 +25643,6 @@ function appendPostamble(blocks, ctx) {
25722
25643
  const lastText = lastBlock && (lastBlock.type === "paragraph" || lastBlock.type === "list_item") ? lastBlock.text || "" : "";
25723
25644
  if (!hasEndMark(lastText)) paraXmls.push(buildEndMark());
25724
25645
  }
25725
- if (gongmun?.docFoot && dfIds) {
25726
- paraXmls.push(...buildDocFoot(gongmun, dfIds));
25727
- }
25728
25646
  if (gongmun?.press && dfIds && bfReg && tableStyle) {
25729
25647
  paraXmls.push(...buildPressContact(gongmun, dfIds, tableStyle.totalWidth, bfReg));
25730
25648
  }
@@ -25879,23 +25797,1101 @@ function levelCharPrXmls(g, base, fontBase, bodyFontId) {
25879
25797
  for (const d of levelDepths(g)) {
25880
25798
  const st = g.levels[d];
25881
25799
  const fontId = st.font ? fontBase + faces.indexOf(st.font) : bodyFontId;
25882
- out.push(charPr(ids[d].normal, st.height, st.bold, false, fontId, void 0, GONGMUN_BODY_RATIO, bodyFontId));
25883
- out.push(charPr(ids[d].bold, st.height, true, false, fontId, void 0, GONGMUN_BODY_RATIO, bodyFontId));
25800
+ out.push(charPr(ids[d].normal, st.height, st.bold, false, fontId, void 0, GONGMUN_BODY_RATIO, fontId));
25801
+ out.push(charPr(ids[d].bold, st.height, true, false, fontId, void 0, GONGMUN_BODY_RATIO, fontId));
25802
+ }
25803
+ return out;
25804
+ }
25805
+
25806
+ // src/hwpx/style-registry.ts
25807
+ var StyleRegistry = class {
25808
+ /**
25809
+ * @param charBase 첫 charPr id (정적 charPr 블록 다음)
25810
+ * @param paraBase 첫 paraPr id
25811
+ * @param fontBase 첫 append 글꼴 id (정적 fontface 다음 — HANGUL·LATIN에만 append)
25812
+ * @param staticFonts 정적 fontface 목록 (id 순) — 같은 이름이면 정적 id 재사용
25813
+ */
25814
+ constructor(charBase, paraBase, fontBase, staticFonts = []) {
25815
+ this.charBase = charBase;
25816
+ this.paraBase = paraBase;
25817
+ this.fontBase = fontBase;
25818
+ staticFonts.forEach((f, i) => {
25819
+ if (!this.fonts.has(f)) this.fonts.set(f, i);
25820
+ });
25821
+ }
25822
+ chars = /* @__PURE__ */ new Map();
25823
+ charXmls = [];
25824
+ paras = /* @__PURE__ */ new Map();
25825
+ paraXmls = [];
25826
+ fonts = /* @__PURE__ */ new Map();
25827
+ fontList = [];
25828
+ font(face) {
25829
+ const hit = this.fonts.get(face);
25830
+ if (hit !== void 0) return hit;
25831
+ const id = this.fontBase + this.fontList.length;
25832
+ this.fonts.set(face, id);
25833
+ this.fontList.push(face);
25834
+ return id;
25835
+ }
25836
+ char(spec) {
25837
+ const key = `${spec.font}|${spec.pt}|${spec.bold ? 1 : 0}|${spec.italic ? 1 : 0}|${spec.ratio ?? 100}|${spec.spacing ?? 0}|${spec.color ?? "#000000"}`;
25838
+ const hit = this.chars.get(key);
25839
+ if (hit !== void 0) return hit;
25840
+ const id = this.charBase + this.charXmls.length;
25841
+ const fontId = this.font(spec.font);
25842
+ this.charXmls.push(charPr(id, Math.round(spec.pt * 100), !!spec.bold, !!spec.italic, fontId, spec.color ?? "#000000", spec.ratio ?? 100, fontId, spec.spacing ?? 0));
25843
+ this.chars.set(key, id);
25844
+ return id;
25845
+ }
25846
+ para(spec) {
25847
+ const key = `${spec.align ?? "JUSTIFY"}|${spec.left ?? 0}|${spec.right ?? 0}|${spec.indent ?? 0}|${spec.before ?? 0}|${spec.after ?? 0}|${spec.lineSp ?? 160}|${spec.keepWithNext ? 1 : 0}|${spec.keepWord === false ? 0 : 1}`;
25848
+ const hit = this.paras.get(key);
25849
+ if (hit !== void 0) return hit;
25850
+ const id = this.paraBase + this.paraXmls.length;
25851
+ this.paraXmls.push(paraPr(id, {
25852
+ align: spec.align ?? "JUSTIFY",
25853
+ left: spec.left ?? 0,
25854
+ right: spec.right ?? 0,
25855
+ indent: spec.indent ?? 0,
25856
+ spaceBefore: spec.before ?? 0,
25857
+ spaceAfter: spec.after ?? 0,
25858
+ lineSpacing: spec.lineSp ?? 160,
25859
+ keepWithNext: !!spec.keepWithNext,
25860
+ keepWord: spec.keepWord !== false
25861
+ }));
25862
+ this.paras.set(key, id);
25863
+ return id;
25864
+ }
25865
+ get charPrXmls() {
25866
+ return this.charXmls;
25867
+ }
25868
+ get paraPrXmls() {
25869
+ return this.paraXmls;
25870
+ }
25871
+ /** append 글꼴(정적 목록 제외) — fontBase부터 순서대로 */
25872
+ get extraFonts() {
25873
+ return this.fontList;
25874
+ }
25875
+ };
25876
+
25877
+ // src/hwpx/gongmun-scheme.ts
25878
+ var SEOUL_FRAME = {
25879
+ font: "\uAD74\uB9BC\uCCB4",
25880
+ titleFont: "HY\uD5E4\uB4DC\uB77C\uC778M",
25881
+ titlePt: 25,
25882
+ contactFont: "\uD734\uBA3C\uBA85\uC870",
25883
+ contactPt: 12,
25884
+ summaryFont: "\uD55C\uCEF4\uB3CB\uC6C0",
25885
+ summaryPt: 15,
25886
+ summaryFill: "#DFE6F7"
25887
+ };
25888
+ var SEOUL_TABLE = { font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12, headerFill: "#DFE6F7", labelFill: "#F2F2F2" };
25889
+ function gaejosikMarkerOf(bullet2) {
25890
+ return (depth) => depth === 0 ? "\u25A1" : depth === 1 ? bullet2 : depth === 2 ? "-" : "\u318D";
25891
+ }
25892
+ function seoulGaejosikScheme(bodyPt = 15, lineSp = 180, bullet2 = "\u3147") {
25893
+ const d = bodyPt - 15;
25894
+ const lv = (font, pt, bold, leadTa, extra = {}) => ({ font, pt: pt + d, bold, leadTa, ...extra });
25895
+ return {
25896
+ kind: "gaejosik",
25897
+ lineSp,
25898
+ body: lv("\uD55C\uCEF4\uB3CB\uC6C0", 15, false, 0),
25899
+ levels: [
25900
+ lv("HY\uACAC\uACE0\uB515", 17, true, 0, { oneLine: true, keepWithNext: true, blankBefore: true }),
25901
+ lv("\uD55C\uCEF4\uB3CB\uC6C0", 15, true, 1),
25902
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 3),
25903
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 4),
25904
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 5),
25905
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 6),
25906
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 7),
25907
+ lv("\uD734\uBA3C\uBA85\uC870", 14, false, 8)
25908
+ ],
25909
+ // ※·출처는 본문보다 작게(실측 14~15 혼재, 실무자 요청으로 13 — 한 단계 아래로 확실히 구분)
25910
+ ref: lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 0),
25911
+ sub: lv("\uD55C\uCEF4\uB3CB\uC6C0", 13, false, 2),
25912
+ attach: lv("\uD55C\uCEF4\uB3CB\uC6C0", 15, false, 0),
25913
+ chapter: lv("HY\uD5E4\uB4DC\uB77C\uC778M", 20, false, 0, { oneLine: true, keepWithNext: true }),
25914
+ marker: gaejosikMarkerOf(bullet2),
25915
+ table: { ...SEOUL_TABLE, pt: SEOUL_TABLE.pt + d },
25916
+ frame: SEOUL_FRAME,
25917
+ blankBetweenTop: false
25918
+ };
25919
+ }
25920
+ function seoulLegalScheme(bodyFont = "\uAD74\uB9BC\uCCB4", bodyPt = 12, lineSp = 160) {
25921
+ const lv = (leadTa, extra = {}) => ({ font: bodyFont, pt: bodyPt, bold: false, leadTa, ...extra });
25922
+ return {
25923
+ kind: "legal",
25924
+ lineSp,
25925
+ body: lv(0),
25926
+ levels: [0, 2, 4, 6, 8, 10, 12, 14].map((t) => lv(t)),
25927
+ ref: lv(0),
25928
+ sub: lv(2),
25929
+ attach: lv(0),
25930
+ chapter: { font: bodyFont, pt: bodyPt + 2, bold: true, leadTa: 0, keepWithNext: true },
25931
+ marker: (depth, n) => standardMarker(depth, n),
25932
+ table: { ...SEOUL_TABLE, pt: Math.min(SEOUL_TABLE.pt, bodyPt) },
25933
+ frame: SEOUL_FRAME,
25934
+ blankBetweenTop: false
25935
+ };
25936
+ }
25937
+ function applySchemeOverrides(s, g) {
25938
+ const out = { ...s, levels: s.levels.map((l) => ({ ...l })), body: { ...s.body }, ref: { ...s.ref }, table: { ...s.table } };
25939
+ if (g.fonts.heading) {
25940
+ out.levels[0].font = g.fonts.heading;
25941
+ out.chapter = { ...out.chapter, font: g.fonts.heading };
25942
+ }
25943
+ if (g.fonts.body) {
25944
+ out.body.font = g.fonts.body;
25945
+ if (s.kind === "legal") for (const l of out.levels) l.font = g.fonts.body;
25946
+ else out.levels[1].font = g.fonts.body;
25947
+ }
25948
+ if (g.fonts.ref) out.ref.font = g.fonts.ref;
25949
+ if (g.fonts.table) out.table.font = g.fonts.table;
25950
+ if (g.levels) {
25951
+ for (const [k, st] of Object.entries(g.levels)) {
25952
+ const l = out.levels[Number(k)];
25953
+ if (!l) continue;
25954
+ if (st.font) l.font = st.font;
25955
+ if (st.height !== g.bodyHeight) l.pt = st.height / 100;
25956
+ l.bold = st.bold;
25957
+ }
25958
+ }
25959
+ return out;
25960
+ }
25961
+ function taHu(pt) {
25962
+ return Math.round(pt * 50);
25963
+ }
25964
+ function levelGeometry(style, marker) {
25965
+ const left = style.leadTa * taHu(style.pt);
25966
+ const indent = marker ? -markerWidth(marker, style.pt * 100) : 0;
25967
+ return { left, indent };
25968
+ }
25969
+ function pickScheme(g, bodyHasBoxMarkers) {
25970
+ const gaejosik = g.numbering !== "standard" || bodyHasBoxMarkers;
25971
+ 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);
25972
+ return applySchemeOverrides(base, g);
25973
+ }
25974
+
25975
+ // src/hwpx/outline.ts
25976
+ var BOX_MARKERS = {
25977
+ "\u25A1": 0,
25978
+ "\u25A0": 0,
25979
+ "\u2751": 0,
25980
+ "\u274F": 0,
25981
+ "\u3141": 0,
25982
+ "\u25CB": 1,
25983
+ "\u3147": 1,
25984
+ "\u25E6": 1,
25985
+ "\u25CF": 1,
25986
+ "\u274D": 1,
25987
+ "\u25CE": 1,
25988
+ "-": 2,
25989
+ "\u2013": 2,
25990
+ "\u2015": 2,
25991
+ "\u2014": 2,
25992
+ "\u3161": 2,
25993
+ "\u2023": 2,
25994
+ "\u25AA": 2,
25995
+ "\u25AB": 2,
25996
+ "\u318D": 3,
25997
+ "\xB7": 3,
25998
+ "\u2022": 3,
25999
+ "\u2219": 3
26000
+ };
26001
+ var LEGAL_RE = /^(\d{1,2}\.|[가-힣]\.|\d{1,2}\)|[가-힣]\)|\(\d{1,2}\)|\([가-힣]\)|[①-⑳]|[㉮-㉻])\s+/u;
26002
+ var BOX_RE = /^([□■❑❏ㅁ○ㅇ◦●❍◎\-–―—ㅡ‣▪▫ㆍ·•∙])\s+/u;
26003
+ var REF_RE = /^(※|*|\*(?=\s))\s*/u;
26004
+ var BUNIM_RE = /^붙\s*임(?:\s|:|$)/;
26005
+ var SOURCE_RE = /^(출처|자료|근거|참고)\s*[::]/;
26006
+ var LAW_CODE_RE = /((?:법률|기본법|특별법|법|령|규칙|조례|규정|고시|훈령|예규|지침)[」』]?)\s*\((\d{5,8})\)/gu;
26007
+ function stripLawCodes(text) {
26008
+ return text.replace(LAW_CODE_RE, "$1").replace(/[,,]?\s*\(?\bDT_[A-Z0-9_]+\)?/g, "").replace(/\s*\((?:법정동|행정동|시군구|지역)?\s*코드\s*[\d-]+\)/g, "").replace(/[,,]?\s*[가-힣A-Za-z·]*\s*MCP\s*(?:조회|호출|응답)?/g, "").replace(/\s+([,,.])/g, "$1").replace(/[,,]\s*$/g, "").replace(/ {2,}/g, " ").trim();
26009
+ }
26010
+ function legalMarkerDepth(marker) {
26011
+ if (/^\d{1,2}\.$/.test(marker)) return 0;
26012
+ if (/^[가-힣]\.$/.test(marker)) return 1;
26013
+ if (/^\d{1,2}\)$/.test(marker)) return 2;
26014
+ if (/^[가-힣]\)$/.test(marker)) return 3;
26015
+ if (/^\(\d{1,2}\)$/.test(marker)) return 4;
26016
+ if (/^\([가-힣]\)$/.test(marker)) return 5;
26017
+ if (/^[①-⑳]$/u.test(marker)) return 6;
26018
+ return 7;
26019
+ }
26020
+ function parseLeadingMarker(text) {
26021
+ const t = text.replace(/^[\s ]+/, "");
26022
+ const ref = REF_RE.exec(t);
26023
+ if (ref) return { kind: "ref", depth: 0, marker: ref[1], rest: t.slice(ref[0].length).trim() };
26024
+ const box = BOX_RE.exec(t);
26025
+ if (box) return { kind: "box", depth: BOX_MARKERS[box[1]] ?? 3, marker: box[1], rest: t.slice(box[0].length).trim() };
26026
+ const legal = LEGAL_RE.exec(t);
26027
+ if (legal) return { kind: "legal", depth: legalMarkerDepth(legal[1]), marker: legal[1], rest: t.slice(legal[0].length).trim() };
26028
+ return { kind: null, depth: 0, marker: "", rest: t };
26029
+ }
26030
+ function buildOutline(blocks, opts) {
26031
+ const nodes = [];
26032
+ let title = null;
26033
+ let chapters = 0;
26034
+ let headingDepth = -1;
26035
+ let lastItemDepth = -1;
26036
+ let seenBody = false;
26037
+ let hasBoxMarkers = false;
26038
+ const pushItem = (depth, text, legalMarker, fromHeading = false) => {
26039
+ nodes.push({ kind: "item", depth, text, ...legalMarker ? { legalMarker } : {}, ...fromHeading ? { fromHeading: true } : {} });
26040
+ lastItemDepth = depth;
26041
+ seenBody = true;
26042
+ };
26043
+ for (let i = 0; i < blocks.length; i++) {
26044
+ const b = blocks[i];
26045
+ switch (b.type) {
26046
+ case "heading": {
26047
+ const lvl = b.level ?? 1;
26048
+ const raw = stripLawCodes((b.text ?? "").trim());
26049
+ if (lvl === 1 && title === null && opts.consumeTitle) {
26050
+ title = stripChapterNumber(raw) || raw;
26051
+ break;
26052
+ }
26053
+ if (lvl <= 2) {
26054
+ nodes.push({ kind: "chapter", text: stripChapterNumber(raw) || raw, index: ++chapters });
26055
+ headingDepth = opts.gaejosik ? -1 : 0;
26056
+ lastItemDepth = opts.gaejosik ? -1 : 0;
26057
+ seenBody = true;
26058
+ break;
26059
+ }
26060
+ const depth = Math.min(lvl - 3, 7);
26061
+ const lm = parseLeadingMarker(raw);
26062
+ pushItem(depth, lm.kind ? lm.rest : raw, void 0, true);
26063
+ headingDepth = depth;
26064
+ break;
26065
+ }
26066
+ case "list_item": {
26067
+ const text = stripLawCodes((b.text ?? "").trim());
26068
+ const lm = parseLeadingMarker(text);
26069
+ if (SOURCE_RE.test(lm.kind === "box" || lm.kind === "legal" ? lm.rest : text)) {
26070
+ nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.kind ? lm.rest : text });
26071
+ break;
26072
+ }
26073
+ if (b.marker === "*" && !/^\*/.test(text) && opts.gaejosik) {
26074
+ nodes.push({ kind: "ref", depth: lastItemDepth + 1, text });
26075
+ break;
26076
+ }
26077
+ if (lm.kind === "ref") {
26078
+ nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.rest });
26079
+ break;
26080
+ }
26081
+ if (lm.kind === "box") {
26082
+ hasBoxMarkers = true;
26083
+ pushItem(lm.depth, lm.rest);
26084
+ break;
26085
+ }
26086
+ if (lm.kind === "legal" && opts.gaejosik) {
26087
+ pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
26088
+ break;
26089
+ }
26090
+ const base = headingDepth >= 0 ? headingDepth + 1 : 0;
26091
+ pushItem(Math.min(base + (b.indent ?? 0), 7), lm.kind === "legal" ? lm.rest : text);
26092
+ break;
26093
+ }
26094
+ case "paragraph": {
26095
+ const text = stripLawCodes((b.text ?? "").trim());
26096
+ if (!text) break;
26097
+ if (BUNIM_RE.test(text) || nodes.length && nodes[nodes.length - 1].kind === "attach" && /^\s/.test(b.text ?? "")) {
26098
+ nodes.push({ kind: "attach", text: b.text ?? "" });
26099
+ break;
26100
+ }
26101
+ const ctr = /^<center>([\s\S]*)<\/center>$/i.exec(text);
26102
+ if (ctr) {
26103
+ nodes.push({ kind: "para", depth: 0, text: ctr[1].trim(), align: "CENTER" });
26104
+ break;
26105
+ }
26106
+ const rgt = /^<right>([\s\S]*)<\/right>$/i.exec(text);
26107
+ if (rgt) {
26108
+ nodes.push({ kind: "para", depth: 0, text: rgt[1].trim(), align: "RIGHT" });
26109
+ break;
26110
+ }
26111
+ const lm = parseLeadingMarker(text);
26112
+ if (lm.kind === "ref") {
26113
+ nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.rest });
26114
+ break;
26115
+ }
26116
+ if (SOURCE_RE.test(lm.kind ? lm.rest : text)) {
26117
+ nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.kind ? lm.rest : text });
26118
+ break;
26119
+ }
26120
+ if (lm.kind === "box") {
26121
+ hasBoxMarkers = true;
26122
+ pushItem(lm.depth, lm.rest);
26123
+ break;
26124
+ }
26125
+ if (lm.kind === "legal") {
26126
+ if (opts.gaejosik) pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
26127
+ else pushItem(lm.depth, lm.rest);
26128
+ break;
26129
+ }
26130
+ nodes.push({ kind: "para", depth: 0, text });
26131
+ seenBody = true;
26132
+ break;
26133
+ }
26134
+ case "blockquote": {
26135
+ const lines = stripLawCodes(b.text ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
26136
+ if (!lines.length) break;
26137
+ if (opts.summaryFromQuote && !seenBody) nodes.push({ kind: "summary", text: lines.join("\n") });
26138
+ else nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lines.join(" ").replace(/^※\s*/, "") });
26139
+ break;
26140
+ }
26141
+ default:
26142
+ nodes.push({ kind: "block", depth: lastItemDepth, block: b });
26143
+ seenBody = true;
26144
+ }
25884
26145
  }
26146
+ return { title, nodes, hasBoxMarkers, chapters };
26147
+ }
26148
+
26149
+ // src/hwpx/fit-line.ts
26150
+ var RATIOS = [100, 97, 95, 92, 90, 87, 85];
26151
+ var SPACINGS = [0, -3, -5];
26152
+ function fitOneLine(text, font, pt, availHu, minPt = Math.max(pt - 3, 10), minRatio = 85) {
26153
+ const faceClass = faceClassForGen(font);
26154
+ const avail = availHu * 0.98;
26155
+ const fits = (p, r, s) => measureTextWidth(text, p * 100, r, { spacingPct: s, faceClass }) <= avail;
26156
+ if (fits(pt, 100, 0)) return { pt, ratio: 100, spacing: 0, overflow: false };
26157
+ const ratios = RATIOS.filter((r) => r >= minRatio);
26158
+ for (let p = pt; p >= minPt; p--) {
26159
+ for (const r of ratios) for (const s of SPACINGS) {
26160
+ if (fits(p, r, s)) return { pt: p, ratio: r, spacing: s, overflow: false };
26161
+ }
26162
+ }
26163
+ return { pt: minPt, ratio: ratios[ratios.length - 1], spacing: SPACINGS[SPACINGS.length - 1], overflow: true };
26164
+ }
26165
+ function fitOrphanLine(text, font, pt, firstW, contW, orphanRatio = 0.2) {
26166
+ const faceClass = faceClassForGen(font);
26167
+ const h = pt * 100;
26168
+ const SAFE = 0.9;
26169
+ const width = (r, sp) => measureTextWidth(text, h, r, { faceClass, spacingPct: sp });
26170
+ const est = width(100, 0);
26171
+ if (est <= firstW * SAFE) return null;
26172
+ const combos = [];
26173
+ for (let sp = -1; sp >= -12; sp--) combos.push([100, sp]);
26174
+ for (const r of [97, 95, 92, 90, 88]) for (let sp = 0; sp >= -12; sp--) combos.push([r, sp]);
26175
+ if (est <= firstW * 1.22) {
26176
+ for (const [r, sp] of combos) if (width(r, sp) <= firstW * SAFE) return { ratio: r, spacing: sp };
26177
+ return null;
26178
+ }
26179
+ const f = firstW * 0.95, c = contW * 0.95;
26180
+ const base = simulateWrap(text, f, c, h, 100, "charAll", { faceClass });
26181
+ if (base.lines < 2 || base.lastLineWidth > c * orphanRatio) return null;
26182
+ for (const [r, sp] of combos) {
26183
+ if (simulateWrap(text, f, c, h, r, "charAll", { faceClass, spacingPct: sp }).lines < base.lines) return { ratio: r, spacing: sp };
26184
+ }
26185
+ return null;
26186
+ }
26187
+
26188
+ // src/hwpx/gen-frame-seoul.ts
26189
+ var frameTableId = EXTRA_TABLE_ID_BASE + 500;
26190
+ function resetFrameTableIds() {
26191
+ frameTableId = EXTRA_TABLE_ID_BASE + 500;
26192
+ }
26193
+ var BF_NONE = 1;
26194
+ function ftbl(rows, w, h, cols, opts = {}) {
26195
+ return `<hp:tbl id="${++frameTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="${opts.bf ?? BF_NONE}" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="${opts.bottomGap ?? 0}"/><hp:inMargin left="140" right="140" top="140" bottom="140"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
26196
+ }
26197
+ function host(tableXml, paraPrId, charPrId, extra = "") {
26198
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"${extra}><hp:run charPrIDRef="${charPrId}">${tableXml}</hp:run></hp:p>`;
26199
+ }
26200
+ function scale(cols, w) {
26201
+ const sum = cols.reduce((a, b) => a + b, 0);
26202
+ const out = cols.map((c) => Math.round(c / sum * w));
26203
+ out[out.length - 1] += w - out.reduce((a, b) => a + b, 0);
25885
26204
  return out;
25886
26205
  }
26206
+ function spacedOrgName(org) {
26207
+ const t = org.trim();
26208
+ if (/\s/.test(t) || [...t].length > 6) return t;
26209
+ return [...t].join(" ");
26210
+ }
26211
+ function buildDocHeadTable(h, ctx) {
26212
+ const { reg, bf, frame } = ctx;
26213
+ const w = ctx.W - 366;
26214
+ const [cLabel, cValue, cPad] = scale([6949, 35068, 7223], w);
26215
+ const center = reg.para({ align: "CENTER", lineSp: 160 });
26216
+ const left = reg.para({ align: "LEFT", lineSp: 160 });
26217
+ const c10 = reg.char({ font: frame.font, pt: 10 });
26218
+ const c10b = reg.char({ font: frame.font, pt: 10, bold: true });
26219
+ const c20b = reg.char({ font: frame.font, pt: 20, bold: true });
26220
+ const c14 = reg.char({ font: frame.font, pt: 14 });
26221
+ const bottomLine = bf.get({ t: "none", b: "thin", l: "none", r: "none" });
26222
+ const rows = [];
26223
+ let r = 0;
26224
+ if (h.slogan) {
26225
+ rows.push(tc({ bf: BF_NONE, row: r++, col: 0, w, h: 2416, colSpan: 3, paras: para(h.slogan, center, c10) }));
26226
+ }
26227
+ rows.push(
26228
+ tc({ bf: BF_NONE, row: r, col: 0, w: cLabel, h: 5608, paras: para("", center, c10) }) + tc({ bf: BF_NONE, row: r, col: 1, w: cValue, h: 5608, paras: para(spacedOrgName(h.org ?? ""), center, c20b) }) + tc({ bf: BF_NONE, row: r, col: 2, w: cPad, h: 5608, paras: para("", center, c10) })
26229
+ );
26230
+ r++;
26231
+ rows.push(tc({ bf: BF_NONE, row: r++, col: 0, w, h: 1563, colSpan: 3, paras: para("", left, c10b) }));
26232
+ const labeled = (label, value, height2, bfId) => tc({ bf: bfId, row: r, col: 0, w: cLabel, h: height2, paras: para(label, left, c14) }) + tc({ bf: bfId, row: r, col: 1, w: cValue + cPad, h: height2, colSpan: 2, paras: para(value, left, c14) });
26233
+ rows.push(labeled(" \uC218\uC2E0 ", h.to ?? "", 2097, BF_NONE));
26234
+ r++;
26235
+ rows.push(labeled("(\uACBD\uC720)", h.via ?? "", 2380, BF_NONE));
26236
+ r++;
26237
+ rows.push(labeled(" \uC81C\uBAA9 ", h.title ?? "", 2248, bottomLine));
26238
+ r++;
26239
+ const height = rows.length === 6 ? 16312 : 13896;
26240
+ return host(ftbl(rows, w, height, 3, { bottomGap: 852 }), reg.para({ align: "LEFT", lineSp: 100 }), c10);
26241
+ }
26242
+ function splitTitleName(s) {
26243
+ const t = s.trim();
26244
+ const i = t.lastIndexOf(" ");
26245
+ return i > 0 ? { title: t.slice(0, i).trim(), name: t.slice(i + 1).trim() } : { title: "", name: t };
26246
+ }
26247
+ function buildDocFootTable(f, ctx, internal) {
26248
+ const { reg, bf, frame } = ctx;
26249
+ const w = ctx.W - 86;
26250
+ const GRID = 48;
26251
+ const unit = Math.floor(w / GRID);
26252
+ const c10 = reg.char({ font: frame.font, pt: 10 });
26253
+ const c11b = reg.char({ font: frame.font, pt: 11, bold: true });
26254
+ const c9b = reg.char({ font: frame.font, pt: 9, bold: true });
26255
+ const c18b = reg.char({ font: frame.font, pt: 18, bold: true });
26256
+ const c3 = reg.char({ font: frame.font, pt: 3 });
26257
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
26258
+ const left = reg.para({ align: "LEFT", lineSp: 100 });
26259
+ const gray = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: "#CCCCCC" });
26260
+ const spans = (widths) => {
26261
+ const sum = widths.reduce((a, b) => a + b, 0);
26262
+ const out = widths.map((x) => Math.max(1, Math.round(x / sum * GRID)));
26263
+ let diff = GRID - out.reduce((a, b) => a + b, 0);
26264
+ for (let i = out.length - 1; diff !== 0 && i >= 0; i = (i - 1 + out.length) % out.length) {
26265
+ if (diff > 0) {
26266
+ out[i]++;
26267
+ diff--;
26268
+ } else if (out[i] > 1) {
26269
+ out[i]--;
26270
+ diff++;
26271
+ }
26272
+ }
26273
+ return out;
26274
+ };
26275
+ const rows = [];
26276
+ let r = 0;
26277
+ const row = (h, specs) => {
26278
+ let col = 0;
26279
+ let xml = "";
26280
+ specs.forEach((sp, i) => {
26281
+ const last = i === specs.length - 1;
26282
+ const span = last ? GRID - col : sp.span;
26283
+ const cw = last ? w - col * unit : span * unit;
26284
+ xml += tc({ bf: sp.bf ?? BF_NONE, row: r, col, w: cw, h, colSpan: span, rowSpan: sp.rowSpan, vAlign: sp.vAlign, paras: para(sp.text, sp.pp ?? left, sp.cp ?? c10) });
26285
+ col += span;
26286
+ });
26287
+ rows.push(xml);
26288
+ r++;
26289
+ };
26290
+ {
26291
+ const [a, b, c] = spans([7981, 32099, 9440]);
26292
+ row(4042, [{ span: a, text: "" }, { span: b, text: internal ? "" : f.sender ?? "", cp: c18b, pp: center }, { span: c, text: "" }]);
26293
+ }
26294
+ if (f.recipients) {
26295
+ const [a] = spans([4843, 44677]);
26296
+ row(2880, [{ span: a, text: "\uC218\uC2E0\uC790", vAlign: "TOP" }, { span: GRID - a, text: f.recipients, vAlign: "TOP" }]);
26297
+ } else {
26298
+ row(1280, [{ span: GRID, text: "" }]);
26299
+ }
26300
+ row(1146, [{ span: GRID, text: "", cp: c3, pp: center, bf: gray }]);
26301
+ {
26302
+ const n = Math.max(f.approvers.length, 1);
26303
+ const unitW = [6909, 4625, 983];
26304
+ const titleW = (t) => Math.max(unitW[0], Math.round(measureTextWidth(t, 1e3, 100, { faceClass: "fixedPitch" })) + 280 + unit + 300);
26305
+ const widths = [];
26306
+ for (let i = 0; i < n; i++) {
26307
+ widths.push(titleW(f.approvers[i]?.title ?? ""), unitW[1]);
26308
+ if (i < n - 1) widths.push(unitW[2]);
26309
+ }
26310
+ const used = widths.reduce((a, b) => a + b, 0);
26311
+ if (used < w) widths.push(w - used);
26312
+ const sp = spans(widths);
26313
+ const top = [], bottom = [];
26314
+ let k = 0;
26315
+ for (let i = 0; i < n; i++) {
26316
+ const ap = f.approvers[i] ?? { title: "", name: "" };
26317
+ top.push({ span: sp[k++], text: ap.title, pp: center, rowSpan: 2 });
26318
+ const signSpan = sp[k++];
26319
+ top.push({ span: signSpan, text: ap.date ?? "", cp: c9b, pp: center });
26320
+ bottom.push({ span: signSpan, text: ap.name, cp: c11b, pp: center });
26321
+ if (i < n - 1) top.push({ span: sp[k++], text: "", rowSpan: 2 });
26322
+ }
26323
+ if (k < sp.length) top.push({ span: sp[k], text: "", rowSpan: 2 });
26324
+ let col = 0, xmlTop = "", xmlBottom = "";
26325
+ let bi = 0;
26326
+ top.forEach((s, i) => {
26327
+ const last = i === top.length - 1;
26328
+ const span = last ? GRID - col : s.span;
26329
+ const cw = last ? w - col * unit : span * unit;
26330
+ xmlTop += tc({ bf: BF_NONE, row: r, col, w: cw, h: s.rowSpan ? 2198 : 1100, colSpan: span, rowSpan: s.rowSpan, paras: para(s.text, s.pp ?? left, s.cp ?? c10) });
26331
+ if (!s.rowSpan) {
26332
+ const b = bottom[bi++];
26333
+ xmlBottom += tc({ bf: BF_NONE, row: r + 1, col, w: cw, h: 1098, colSpan: span, paras: para(b.text, b.pp ?? center, b.cp ?? c11b) });
26334
+ }
26335
+ col += span;
26336
+ });
26337
+ rows.push(xmlTop, xmlBottom);
26338
+ r += 2;
26339
+ }
26340
+ {
26341
+ const [a] = spans([4455, 45065]);
26342
+ const coop = f.cooperators.map((c) => `${c.title} ${c.name}`.trim()).join(" ");
26343
+ row(2700, [{ span: a, text: "\uD611\uC870\uC790" }, { span: GRID - a, text: coop }]);
26344
+ }
26345
+ row(1400, [{ span: GRID, text: "" }]);
26346
+ {
26347
+ const [a, b, c] = spans([3340, 21532, 3301, 21347]);
26348
+ row(2280, [{ span: a, text: "\uC2DC\uD589" }, { span: b, text: f.docNum ?? "" }, { span: c, text: "\uC811\uC218" }, { span: 0, text: f.receive ?? "" }]);
26349
+ }
26350
+ {
26351
+ const [a, b, c, d] = spans([1597, 3902, 21934, 761, 21326]);
26352
+ row(1280, [{ span: a, text: "\uC6B0" }, { span: b, text: f.zip ?? "" }, { span: c, text: f.address ?? "" }, { span: d, text: "/" }, { span: 0, text: f.site ?? "" }]);
26353
+ }
26354
+ {
26355
+ const sp = spans([2750, 8006, 3196, 8311, 899, 14327, 792, 11239]);
26356
+ const vals = ["\uC804\uD654", f.phone ?? "", "/\uC804\uC1A1", f.fax ?? "", "/", f.email ?? "", "/", f.disclosure ?? ""];
26357
+ row(2e3, vals.map((v, i) => ({ span: sp[i], text: v })));
26358
+ }
26359
+ row(1e3, [{ span: GRID, text: "" }]);
26360
+ const height = rows.length * 1500;
26361
+ return host(ftbl(rows, w, height, GRID), reg.para({ align: "LEFT", lineSp: 100 }), c10);
26362
+ }
26363
+ function buildReportTitleTable(title, contact, ctx) {
26364
+ const { reg, bf, frame } = ctx;
26365
+ const w = ctx.W - 566;
26366
+ const fit = fitOneLine(title, frame.titleFont, frame.titlePt, w - 2 * 140 - 400, 20);
26367
+ const cTitle = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
26368
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
26369
+ const rows = [];
26370
+ const titleBf = bf.get({ t: "thick", b: contact ? "mid" : "thick", l: "none", r: "none" });
26371
+ rows.push(tc({ bf: titleBf, row: 0, col: 0, w, h: 4084, paras: para(title, center, cTitle), name: "__kordoc_h1" }));
26372
+ let h = 4084;
26373
+ if (contact) {
26374
+ const cContact = reg.char({ font: frame.contactFont, pt: frame.contactPt });
26375
+ rows.push(tc({ bf: bf.get({ t: "mid", b: "thick", l: "none", r: "none" }), row: 1, col: 0, w, h: 1943, paras: para(contact, center, cContact) }));
26376
+ h += 1943;
26377
+ }
26378
+ return { xml: host(ftbl(rows, w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), cTitle), overflow: fit.overflow };
26379
+ }
26380
+ var CHAPTER_BAND_DEFAULT = { color: "#003366", textColor: "#FFFFFF", numFont: "HY\uD5E4\uB4DC\uB77C\uC778M", numPt: 17 };
26381
+ function buildChapterBand(roman, title, ctx, spec, before) {
26382
+ const { reg, bf } = ctx;
26383
+ const w = ctx.W;
26384
+ const numW = 3100, gap = 400, h = 2600;
26385
+ const titleW = w - numW - gap;
26386
+ const fit = fitOneLine(title, spec.titleFont, spec.titlePt, titleW - 700, spec.titlePt - 3);
26387
+ const numBf = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: spec.color });
26388
+ const gapBf = bf.get({ t: "none", b: "none", l: "none", r: "none" });
26389
+ const titleBf = bf.get({ t: "none", b: "thick", l: "none", r: "none" });
26390
+ const cNum = reg.char({ font: spec.numFont, pt: spec.numPt, bold: true, color: spec.textColor });
26391
+ const cTitle = reg.char({ font: spec.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
26392
+ const center = reg.para({ align: "CENTER", lineSp: 130 });
26393
+ const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
26394
+ const row = tc({ bf: numBf, row: 0, col: 0, w: numW, h, paras: para(roman, center, cNum), vAlign: "CENTER" }) + tc({ bf: gapBf, row: 0, col: 1, w: gap, h, paras: para("", center, cNum), vAlign: "CENTER" }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h, paras: para(title, left, cTitle), name: "__kordoc_h2", vAlign: "CENTER" });
26395
+ return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cTitle), overflow: fit.overflow };
26396
+ }
26397
+ var SUMMARY_PAD = 1e3;
26398
+ function buildSummaryBox(text, ctx) {
26399
+ const { reg, bf, frame } = ctx;
26400
+ const w = ctx.W - 566;
26401
+ const avail = w - 280 - SUMMARY_PAD * 2;
26402
+ const p = reg.para({ align: "JUSTIFY", lineSp: 160, left: SUMMARY_PAD, right: SUMMARY_PAD, keepWord: false });
26403
+ const bodies = text.split("\n").map((l) => l.trim().replace(/^[□■○ㅇ◦●\-–ㆍ·•]\s*/u, "")).filter(Boolean);
26404
+ let lines = 0;
26405
+ const paras = bodies.map((b) => {
26406
+ const f = fitOrphanLine(b, frame.summaryFont, frame.summaryPt, avail, avail);
26407
+ const c = reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 });
26408
+ lines += simulateWrap(b, avail * 0.95, avail * 0.95, frame.summaryPt * 100, f?.ratio ?? 100, "charAll", { faceClass: "gothic", spacingPct: f?.spacing ?? 0 }).lines;
26409
+ return para(b, p, c);
26410
+ });
26411
+ const h = lines * Math.round(frame.summaryPt * 100 * 1.6) + 280 + 600;
26412
+ const box = bf.get({ t: "thick", b: "thick", l: "thick", r: "thick", fill: frame.summaryFill });
26413
+ const row = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), name: "__kordoc_summary" });
26414
+ return { xml: host(ftbl([row], w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true })), lines };
26415
+ }
26416
+ function buildApprovalSeoul(labels, names, ctx) {
26417
+ const { reg, bf } = ctx;
26418
+ const n = labels.length;
26419
+ const colW = Math.min(8600, Math.floor(ctx.W * 0.55 / n));
26420
+ const w = colW * n;
26421
+ const c12 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12 });
26422
+ const c12b = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12, bold: true });
26423
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
26424
+ const edge = (row, col) => bf.get({
26425
+ t: row === 0 ? "thick" : "thin",
26426
+ b: row === 1 ? "thick" : "thin",
26427
+ l: col === 0 ? "thick" : "thin",
26428
+ r: col === n - 1 ? "thick" : "thin"
26429
+ });
26430
+ const top = labels.map((l, c) => tc({ bf: edge(0, c), row: 0, col: c, w: colW, h: 1765, paras: para(l, center, c12) })).join("");
26431
+ const sign = labels.map((_, c) => tc({ bf: edge(1, c), row: 1, col: c, w: colW, h: 4200, paras: para(names?.[c] ?? "", center, c12b) })).join("");
26432
+ return host(ftbl([top, sign], w, 5965, n, { bottomGap: 600 }), reg.para({ align: "RIGHT", lineSp: 100 }), c12);
26433
+ }
26434
+ function buildReportCover(inp, ctx) {
26435
+ const { reg, bf, frame } = ctx;
26436
+ const out = [];
26437
+ const c12 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12 });
26438
+ const c12b = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 12, bold: true });
26439
+ const center = reg.para({ align: "CENTER", lineSp: 100 });
26440
+ const blank2 = (pt = 15) => para("", reg.para({ align: "LEFT", lineSp: 160 }), reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt }));
26441
+ const infoRows = [["\uBB38\uC11C\uBC88\uD638", inp.docInfo?.docNum ?? ""], ["\uACB0\uC7AC\uC77C\uC790", inp.docInfo?.date ?? ""], ["\uACF5\uAC1C\uC5EC\uBD80", inp.docInfo?.disclosure ?? ""], ["\uBC29\uCE68\uBC88\uD638", inp.docInfo?.policyNo ?? ""]];
26442
+ const infoW = Math.round(ctx.W * 0.36);
26443
+ const [lw, vw] = scale([4200, 12800], infoW);
26444
+ const infoXml = ftbl(infoRows.map(
26445
+ ([l, v], r) => tc({ bf: bf.get({ t: r === 0 ? "thick" : "thin", b: r === 3 ? "thick" : "thin", l: "thick", r: "thin" }), row: r, col: 0, w: lw, h: 1850, paras: para(l, center, c12b) }) + tc({ bf: bf.get({ t: r === 0 ? "thick" : "thin", b: r === 3 ? "thick" : "thin", l: "thin", r: "thick" }), row: r, col: 1, w: vw, h: 1850, paras: para(v, center, c12) })
26446
+ ), infoW, 1850 * 4, 2);
26447
+ let line = infoXml;
26448
+ if (inp.approval && inp.approval.length) {
26449
+ const n = inp.approval.length;
26450
+ const colW = Math.min(7600, Math.floor(ctx.W * 0.5 / n));
26451
+ const edge = (row, col) => bf.get({ t: row === 0 ? "thick" : "thin", b: row === 1 ? "thick" : "thin", l: col === 0 ? "thick" : "thin", r: col === n - 1 ? "thick" : "thin" });
26452
+ const top = inp.approval.map((l, c) => tc({ bf: edge(0, c), row: 0, col: c, w: colW, h: 1765, paras: para(l, center, c12) })).join("");
26453
+ const sign = inp.approval.map((_, c) => tc({ bf: edge(1, c), row: 1, col: c, w: colW, h: 3600, paras: para("", center, c12b) })).join("");
26454
+ line += `</hp:run><hp:run charPrIDRef="${c12}"><hp:t> </hp:t></hp:run><hp:run charPrIDRef="${c12}">` + ftbl([top, sign], colW * n, 5365, n);
26455
+ }
26456
+ out.push(`<hp:p paraPrIDRef="${reg.para({ align: "DISTRIBUTE", lineSp: 100 })}" styleIDRef="0"><hp:run charPrIDRef="${c12}">${line}</hp:run></hp:p>`);
26457
+ for (let i = 0; i < 4; i++) out.push(blank2(20));
26458
+ const w = ctx.W - 1200;
26459
+ const bar = bf.get({ t: "none", b: "none", l: "none", r: "none", fill: "#1F2FD6" });
26460
+ const fit = fitOneLine(inp.title, frame.titleFont, 27, w - 800, 22);
26461
+ const cT = reg.char({ font: frame.titleFont, pt: fit.pt, bold: true, ratio: fit.ratio, spacing: fit.spacing });
26462
+ const c1 = reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 1 });
26463
+ const barP = reg.para({ align: "CENTER", lineSp: 70 });
26464
+ const rows = [
26465
+ tc({ bf: bar, row: 0, col: 0, w, h: 200, paras: para("", barP, c1) }),
26466
+ tc({ bf: BF_NONE, row: 1, col: 0, w, h: 6e3, paras: para(inp.title, center, cT), name: "__kordoc_skip" }),
26467
+ tc({ bf: bar, row: 2, col: 0, w, h: 200, paras: para("", barP, c1) })
26468
+ ];
26469
+ out.push(host(ftbl(rows, w, 6400, 1), reg.para({ align: "CENTER", lineSp: 100 }), cT));
26470
+ for (let i = 0; i < 2; i++) out.push(blank2(20));
26471
+ out.push(para(inp.date, center, reg.char({ font: frame.titleFont, pt: 22, bold: true })));
26472
+ for (let i = 0; i < 8; i++) out.push(blank2(20));
26473
+ if (inp.org) out.push(para(spacedOrgName(inp.org), center, reg.char({ font: frame.titleFont, pt: 24, bold: true })));
26474
+ if (inp.dept) out.push(para(`(${inp.dept.replace(/^\(|\)$/g, "")})`, center, reg.char({ font: "\uD55C\uCEF4\uB3CB\uC6C0", pt: 22, bold: true })));
26475
+ return out;
26476
+ }
26477
+
26478
+ // src/hwpx/gen-gongmun.ts
26479
+ var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes"]);
26480
+ function usesV5Engine(preset) {
26481
+ return V5_PRESETS.has(preset);
26482
+ }
26483
+ var ROMAN = ["\u2160", "\u2161", "\u2162", "\u2163", "\u2164", "\u2165", "\u2166", "\u2167", "\u2168", "\u2169", "\u216A", "\u216B"];
26484
+ function chapterLabel(index, style) {
26485
+ return style === "roman" ? `${ROMAN[(index - 1) % 12]}.` : `${index}.`;
26486
+ }
26487
+ function inlineMapper(reg, base) {
26488
+ const norm2 = reg.char({ font: base.font, pt: base.pt, bold: base.bold, ratio: base.ratio, spacing: base.spacing });
26489
+ return (id) => {
26490
+ if (id === CHAR_BOLD) return reg.char({ font: base.font, pt: base.pt, bold: true, ratio: base.ratio, spacing: base.spacing });
26491
+ if (id === CHAR_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: base.bold, italic: true, ratio: base.ratio, spacing: base.spacing });
26492
+ if (id === CHAR_BOLD_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: true, italic: true, ratio: base.ratio, spacing: base.spacing });
26493
+ if (id === CHAR_NORMAL) return norm2;
26494
+ return id;
26495
+ };
26496
+ }
26497
+ function plain(text) {
26498
+ return text.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/!\[[^\]]*\]\([^)]*\)/g, "");
26499
+ }
26500
+ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
26501
+ resetTableIds();
26502
+ resetFrameTableIds();
26503
+ const { reg, bfReg, remap, images, page } = deps;
26504
+ const warnings = [];
26505
+ const g = gongmun;
26506
+ const preset = g.preset;
26507
+ const isReport = preset === "report" || preset === "plan";
26508
+ const W = mmToHwpunit(210 - g.margins.left - g.margins.right);
26509
+ const pre = buildOutline(blocks, { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
26510
+ const scheme = pickScheme(g, pre.hasBoxMarkers);
26511
+ const gaejosik = scheme.kind === "gaejosik";
26512
+ const outline = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
26513
+ const frame = { reg, bf: bfReg, frame: scheme.frame, W };
26514
+ const lineHu = (st) => Math.round(st.pt * 100 * ((st.lineSp ?? scheme.lineSp) / 100));
26515
+ const paras = [];
26516
+ let pendingPageBreak = false;
26517
+ const tblCharPr = reg.char({ font: scheme.table.font, pt: scheme.table.pt });
26518
+ const tableStyle = {
26519
+ totalWidth: W,
26520
+ charPr: tblCharPr,
26521
+ boldCharPr: reg.char({ font: scheme.table.font, pt: scheme.table.pt, bold: true }),
26522
+ charHeight: scheme.table.pt * 100,
26523
+ headerBf: bfReg.get({ t: "thin", b: "thin", l: "thin", r: "thin", fill: scheme.table.headerFill }),
26524
+ centerParaPr: reg.para({ align: "CENTER", lineSp: scheme.lineSp }),
26525
+ tblCenterParaPr: reg.para({ align: "CENTER", lineSp: 130 }),
26526
+ tblLeftParaPr: reg.para({ align: "LEFT", lineSp: 130 }),
26527
+ bfRegistry: bfReg,
26528
+ rightParaPr: reg.para({ align: "RIGHT", lineSp: scheme.lineSp }),
26529
+ headerFill: scheme.table.headerFill,
26530
+ labelFill: scheme.table.labelFill,
26531
+ faceClass: faceClassForGen(scheme.table.font)
26532
+ };
26533
+ let frontKind = "start";
26534
+ const pushSummary = (t) => {
26535
+ frontKind = "summary";
26536
+ const box = buildSummaryBox(t, frame);
26537
+ if (box.lines > 3) warnings.push(`\uC694\uC57D\uBC15\uC2A4\uAC00 ${box.lines}\uC904\uC785\uB2C8\uB2E4 \u2014 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uC904\uC774\uC138\uC694`);
26538
+ paras.push(box.xml);
26539
+ };
26540
+ const docTitle = g.docHead?.title ?? outline.title ?? "";
26541
+ if (preset === "official") {
26542
+ if (g.docHead) paras.push(buildDocHeadTable({ ...g.docHead, title: docTitle }, frame));
26543
+ 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));
26544
+ if (g.reportInfo) paras.push(generateParagraph(g.reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
26545
+ if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
26546
+ } else if (isReport) {
26547
+ if (g.cover) {
26548
+ paras.push(...buildReportCover({
26549
+ title: docTitle,
26550
+ date: g.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()).replace(/\s\d+\.$/, "."),
26551
+ org: g.cover.org || void 0,
26552
+ dept: g.cover.dept,
26553
+ docInfo: g.docInfo ?? void 0,
26554
+ approval: g.approval ?? void 0
26555
+ }, frame));
26556
+ pendingPageBreak = true;
26557
+ } else if (g.approval) {
26558
+ paras.push(buildApprovalSeoul(g.approval, null, frame));
26559
+ }
26560
+ if (docTitle) {
26561
+ const t = buildReportTitleTable(docTitle, g.reportInfo, frame);
26562
+ if (t.overflow) warnings.push(`\uC81C\uBAA9\uC774 \uAE38\uC5B4 \uD55C \uC904\uC5D0 \uB2F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(20pt\xB7\uC7A5\uD3C9 85%\uAE4C\uC9C0 \uCD95\uC18C) \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${docTitle.slice(0, 30)}\u2026"`);
26563
+ if (pendingPageBreak) {
26564
+ paras.push(t.xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `).replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${newPageNumCtrl(1)}`));
26565
+ pendingPageBreak = false;
26566
+ } else paras.push(t.xml);
26567
+ frontKind = "title";
26568
+ }
26569
+ if (!docTitle && g.reportInfo) paras.push(generateParagraph(g.reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
26570
+ if (g.summary) pushSummary(g.summary);
26571
+ else if (!outline.nodes.some((n) => n.kind === "summary")) warnings.push('\uBCF4\uACE0\uC11C \uC694\uC57D\uBC15\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9 \uC9C1\uD6C4 \uC778\uC6A9\uBB38(>)\uC5D0 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uB123\uAC70\uB098 summary \uC635\uC158\uC744 \uC9C0\uC815\uD558\uC138\uC694');
26572
+ } else {
26573
+ if (g.noticeHead) paras.push(...buildNoticeHead(g));
26574
+ if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
26575
+ if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, before: 400, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20, bold: true }), void 0, 1));
26576
+ }
26577
+ const numberer = new GongmunNumberer("standard", g.bullet2);
26578
+ const itemDepths = outline.nodes.filter((n) => n.kind === "item" && !n.legalMarker).map((n) => n.depth);
26579
+ const suppress = !gaejosik && g.suppressSingle ? computeSuppression(itemDepths) : null;
26580
+ let itemSeq = 0;
26581
+ const BOX_BLANK_HU = 1e3;
26582
+ const BOX_GAP_AFTER_BAND = 1200;
26583
+ const BAND_BEFORE_HU = 2e3;
26584
+ let prevKind = frontKind;
26585
+ let prevItemDepth = -1;
26586
+ let nextNode;
26587
+ let tableSeq = 0;
26588
+ let chapterStyle = g.h2Marker === "box" || g.h2Marker === "number" || g.h2Marker === "none" || g.h2Marker === "band" ? g.h2Marker : "roman";
26589
+ if (!gaejosik) chapterStyle = "number";
26590
+ const leadLeft = (depth, pt) => (scheme.levels[Math.min(depth, 7)]?.leadTa ?? 0) * taHu(pt);
26591
+ const renderItem = (node, styleId = 0) => {
26592
+ const depth = Math.min(node.depth, 7);
26593
+ const sub = gaejosik && node.legalMarker;
26594
+ const st = sub ? { ...scheme.sub, leadTa: (() => {
26595
+ const d = node.legalMarker ? legalDepthOf(node.legalMarker) : 0;
26596
+ return 2 * d;
26597
+ })() } : scheme.levels[depth];
26598
+ let marker;
26599
+ if (sub) marker = node.legalMarker;
26600
+ else if (gaejosik) marker = scheme.marker(depth, 0);
26601
+ else {
26602
+ const sup = suppress ? suppress[itemSeq] : false;
26603
+ marker = numberer.next(depth, sup);
26604
+ itemSeq++;
26605
+ }
26606
+ const text = marker ? `${marker} ${node.text}` : node.text;
26607
+ const geom = levelGeometry(st, marker);
26608
+ const availFirst = W - geom.left;
26609
+ const availCont = W - geom.left + geom.indent;
26610
+ let pt = st.pt, ratio = 100, spacing = 0;
26611
+ if (st.oneLine) {
26612
+ const fit = fitOneLine(plain(text), st.font, st.pt, availFirst, st.pt, 90);
26613
+ 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"`);
26614
+ else {
26615
+ ratio = fit.ratio;
26616
+ spacing = fit.spacing;
26617
+ }
26618
+ } else if (g.autoFitMinRatio !== null) {
26619
+ const f = fitOrphanLine(plain(text), st.font, st.pt, availFirst, availCont);
26620
+ if (f) {
26621
+ ratio = f.ratio;
26622
+ spacing = f.spacing;
26623
+ }
26624
+ }
26625
+ const base = { font: st.font, pt, bold: st.bold, ratio, spacing };
26626
+ const geom2 = pt !== st.pt ? levelGeometry({ ...st, pt }, marker) : geom;
26627
+ const consecutiveBox = prevKind === "item" && prevItemDepth === 0 && depth === 0;
26628
+ const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
26629
+ const before = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
26630
+ const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
26631
+ const paraId = reg.para({ align: "JUSTIFY", left: geom2.left, indent: geom2.indent, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
26632
+ return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), styleId);
26633
+ };
26634
+ const renderRef = (node) => {
26635
+ const st = scheme.ref;
26636
+ const marker = "\u203B";
26637
+ const left = leadLeft(node.depth, st.pt);
26638
+ const geom = levelGeometry({ ...st, leadTa: 0 }, marker);
26639
+ const base = { font: st.font, pt: st.pt, bold: st.bold };
26640
+ const f = fitOrphanLine(plain(`${marker} ${node.text}`), st.font, st.pt, W - left, W - left + geom.indent);
26641
+ const baseF = f ? { ...base, ratio: f.ratio, spacing: f.spacing } : base;
26642
+ const paraId = reg.para({ align: "JUSTIFY", left, indent: geom.indent, lineSp: st.lineSp ?? scheme.lineSp, keepWord: false });
26643
+ return generateParagraph(`${marker} ${node.text}`, paraId, reg.char(baseF), inlineMapper(reg, baseF));
26644
+ };
26645
+ const renderPara = (node) => {
26646
+ const st = scheme.body;
26647
+ if (images) {
26648
+ const { text: rest, urls } = splitImageRefs(node.text);
26649
+ if (urls.length > 0 && !rest.trim()) {
26650
+ const pics = urls.map((u) => {
26651
+ const part = images.take(u);
26652
+ return part ? images.inlinePicXml(part) : null;
26653
+ });
26654
+ if (pics.every(Boolean)) return `<hp:p paraPrIDRef="${reg.para({ align: "CENTER", lineSp: scheme.lineSp })}" styleIDRef="0"><hp:run charPrIDRef="${reg.char({ font: st.font, pt: st.pt })}">${pics.join("")}</hp:run></hp:p>`;
26655
+ }
26656
+ }
26657
+ let ratio = 100, spacing = 0;
26658
+ if (!node.align && g.autoFitMinRatio !== null) {
26659
+ const f = fitOrphanLine(plain(node.text), st.font, st.pt, W, W);
26660
+ if (f) {
26661
+ ratio = f.ratio;
26662
+ spacing = f.spacing;
26663
+ }
26664
+ }
26665
+ const base = { font: st.font, pt: st.pt, bold: st.bold, ratio, spacing };
26666
+ const paraId = reg.para({ align: node.align ?? "JUSTIFY", lineSp: scheme.lineSp, keepWord: false });
26667
+ return generateParagraph(node.text, paraId, reg.char(base), inlineMapper(reg, base));
26668
+ };
26669
+ const renderChapter = (node) => {
26670
+ const st = scheme.chapter;
26671
+ if (chapterStyle === "band") {
26672
+ const before2 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
26673
+ const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain(node.text), frame, { ...CHAPTER_BAND_DEFAULT, titleFont: st.font, titlePt: st.pt }, before2);
26674
+ 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"`);
26675
+ return band.xml;
26676
+ }
26677
+ const label = chapterStyle === "none" ? "" : chapterLabel(node.index, chapterStyle === "number" ? "number" : "roman");
26678
+ const text = label ? `${label} ${node.text}` : node.text;
26679
+ const fit = fitOneLine(plain(text), st.font, st.pt, W, st.pt - 2);
26680
+ const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
26681
+ const before = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
26682
+ const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true, keepWord: false });
26683
+ return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), 2);
26684
+ };
26685
+ const renderAttach = (node, first) => {
26686
+ const st = scheme.attach;
26687
+ let text = node.text;
26688
+ if (/^\s*붙\s*임/.test(text)) text = text.replace(/^\s*붙\s*임\s*[::]?\s*/, "\uBD99\uC784 ");
26689
+ else text = text.replace(/\s+$/, "");
26690
+ const paraId = reg.para({ align: "LEFT", before: first ? lineHu(scheme.body) : 0, lineSp: st.lineSp ?? scheme.lineSp, keepWord: false });
26691
+ const base = { font: st.font, pt: st.pt, bold: st.bold };
26692
+ return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base));
26693
+ };
26694
+ const renderBlock = (node, idx) => {
26695
+ const b = node.block;
26696
+ switch (b.type) {
26697
+ case "table": {
26698
+ if (!b.rows) return "";
26699
+ let style = tableStyle;
26700
+ const face = faceClassForGen(scheme.table.font);
26701
+ const avail = W - DATA_TABLE_INSET;
26702
+ for (const pt of [scheme.table.pt, scheme.table.pt - 1, scheme.table.pt - 2]) {
26703
+ if (pt < 9) break;
26704
+ if (requiredTableWidth(b.rows, pt * 100, face) <= avail || pt === scheme.table.pt - 2) {
26705
+ if (pt !== scheme.table.pt) style = { ...tableStyle, charPr: reg.char({ font: scheme.table.font, pt }), boldCharPr: reg.char({ font: scheme.table.font, pt, bold: true }), charHeight: pt * 100 };
26706
+ break;
26707
+ }
26708
+ }
26709
+ return generateTable(b.rows, theme, style, remap, tableSeq++, images);
26710
+ }
26711
+ case "html_table": {
26712
+ const tbl2 = generateHtmlTableXml(b.text || "", theme, tableStyle.totalWidth - DATA_TABLE_INSET, tableStyle, remap, tableSeq++, images);
26713
+ if (tbl2) return `<hp:p paraPrIDRef="${tableStyle.rightParaPr}" styleIDRef="0"><hp:run charPrIDRef="${tblCharPr}">${tbl2}</hp:run></hp:p>`;
26714
+ const txt = (b.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
26715
+ return txt ? renderPara({ kind: "para", depth: 0, text: txt }) : "";
26716
+ }
26717
+ case "code_block": {
26718
+ if ((b.lang || "").toLowerCase() === "chart") {
26719
+ const fence = parseChartFence(b.text || "");
26720
+ if (fence) {
26721
+ const partName = `Chart/chart${deps.chartParts.length + 1}.xml`;
26722
+ deps.chartParts.push({ name: partName, xml: buildChartSpaceXml(fence) });
26723
+ const el = buildChartElementXml(partName, fence.widthHu, fence.heightHu, CHART_TABLE_ID_BASE + idx);
26724
+ return `<hp:p paraPrIDRef="${tableStyle.centerParaPr}" styleIDRef="0"><hp:run charPrIDRef="${tblCharPr}">${el}</hp:run></hp:p>`;
26725
+ }
26726
+ }
26727
+ return (b.text || "").split("\n").map((line) => `<hp:p paraPrIDRef="${PARA_CODE}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(line || " ")}</hp:t></hp:run></hp:p>`).join("\n ");
26728
+ }
26729
+ case "equation":
26730
+ return generateEquationParagraph(b.text || "", idx);
26731
+ case "hr":
26732
+ return `<hp:p paraPrIDRef="${reg.para({ lineSp: scheme.lineSp })}" styleIDRef="0"><hp:run charPrIDRef="${reg.char({ font: scheme.body.font, pt: scheme.body.pt })}"><hp:t></hp:t></hp:run></hp:p>`;
26733
+ default:
26734
+ return "";
26735
+ }
26736
+ };
26737
+ let lastTextNode = null;
26738
+ let prevAttach = false;
26739
+ let lastItemFromHeading = false;
26740
+ outline.nodes.forEach((node, idx) => {
26741
+ nextNode = outline.nodes[idx + 1];
26742
+ let xml = "";
26743
+ switch (node.kind) {
26744
+ case "title":
26745
+ break;
26746
+ case "summary":
26747
+ if (isReport && !g.summary) {
26748
+ pushSummary(node.text);
26749
+ prevKind = node.kind;
26750
+ return;
26751
+ }
26752
+ xml = renderRef({ kind: "ref", depth: 0, text: node.text });
26753
+ break;
26754
+ case "chapter":
26755
+ lastItemFromHeading = true;
26756
+ if (!gaejosik) {
26757
+ xml = renderItem({ kind: "item", depth: 0, text: node.text }, 2);
26758
+ } else if (chapterStyle === "box") {
26759
+ xml = renderItem({ kind: "item", depth: 0, text: node.text });
26760
+ } else xml = renderChapter(node);
26761
+ lastTextNode = node;
26762
+ break;
26763
+ case "item":
26764
+ xml = renderItem(node);
26765
+ lastTextNode = node;
26766
+ lastItemFromHeading = !!node.fromHeading;
26767
+ break;
26768
+ case "ref":
26769
+ xml = renderRef(node);
26770
+ lastTextNode = node;
26771
+ break;
26772
+ // 마크다운 리스트 사이에 본문 문단이 끼면 번호 재시작(리스트 run 종료). 헤딩(##/###)에서 온
26773
+ // 항목은 그 아래 서술 문단이 내용이므로 번호를 잇는다 (기안문 "1. 개요 / 내용 / 2. 성과")
26774
+ case "para":
26775
+ xml = renderPara(node);
26776
+ if (!node.align && !lastItemFromHeading) numberer.reset();
26777
+ lastTextNode = node;
26778
+ break;
26779
+ case "attach":
26780
+ xml = renderAttach(node, !prevAttach);
26781
+ lastTextNode = node;
26782
+ break;
26783
+ case "block":
26784
+ xml = renderBlock(node, idx);
26785
+ if (node.block.type === "table" || node.block.type === "html_table") lastTextNode = node;
26786
+ break;
26787
+ }
26788
+ prevAttach = node.kind === "attach";
26789
+ if (!xml) return;
26790
+ if (pendingPageBreak) {
26791
+ xml = xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
26792
+ pendingPageBreak = false;
26793
+ }
26794
+ paras.push(xml);
26795
+ prevKind = node.kind;
26796
+ if (node.kind === "item") prevItemDepth = node.depth;
26797
+ else if (node.kind === "chapter" && gaejosik && chapterStyle === "box") prevItemDepth = 0;
26798
+ });
26799
+ if (g.noticeHead) paras.push(...buildNoticeFoot(g));
26800
+ if (g.endMark && paras.length > 0) {
26801
+ const lastText = lastTextNode && (lastTextNode.kind === "item" || lastTextNode.kind === "para" || lastTextNode.kind === "attach" || lastTextNode.kind === "ref") ? lastTextNode.text : "";
26802
+ if (!hasEndMark(lastText)) {
26803
+ const st = scheme.body;
26804
+ paras.push(generateParagraph(" \uB05D.", reg.para({ align: "LEFT", lineSp: scheme.lineSp }), reg.char({ font: st.font, pt: st.pt })));
26805
+ }
26806
+ }
26807
+ if (preset === "official" && g.docFoot) {
26808
+ const f = g.docFoot;
26809
+ const approvers = (f.approvers ?? [f.drafter, f.reviewer, f.approver].filter((x) => !!x)).map(splitTitleName);
26810
+ const cooperators = (f.cooperator ? f.cooperator.split(/[,、·]/).map((s) => s.trim()).filter(Boolean) : []).map(splitTitleName);
26811
+ paras.push(buildDocFootTable({
26812
+ sender: f.sender,
26813
+ approvers,
26814
+ cooperators,
26815
+ recipients: f.recipients,
26816
+ docNum: f.docNum,
26817
+ receive: f.receive,
26818
+ zip: f.zip,
26819
+ address: f.address,
26820
+ site: f.site,
26821
+ phone: f.phone,
26822
+ fax: f.fax,
26823
+ email: f.email,
26824
+ disclosure: f.disclosure
26825
+ }, frame, isInternalApproval(g.docHead?.to)));
26826
+ }
26827
+ if (paras.length === 0) paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t></hp:t></hp:run></hp:p>`);
26828
+ paras[0] = paras[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${generateSecPr(g, page)}`);
26829
+ return {
26830
+ xml: `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
26831
+ <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
26832
+ ${paras.join("\n ")}
26833
+ </hs:sec>`,
26834
+ warnings,
26835
+ scheme
26836
+ };
26837
+ }
26838
+ function legalDepthOf(marker) {
26839
+ if (/^\d{1,2}\.$/.test(marker)) return 0;
26840
+ if (/^[가-힣]\.$/.test(marker)) return 1;
26841
+ if (/^\d{1,2}\)$/.test(marker)) return 2;
26842
+ if (/^[가-힣]\)$/.test(marker)) return 3;
26843
+ if (/^\(\d{1,2}\)$/.test(marker)) return 4;
26844
+ if (/^\([가-힣]\)$/.test(marker)) return 5;
26845
+ return 6;
26846
+ }
25887
26847
 
25888
26848
  // src/hwpx/generator.ts
25889
26849
  async function markdownToHwpx(markdown, options) {
25890
26850
  const theme = resolveTheme(options?.theme);
25891
26851
  const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
25892
26852
  const page = resolvePage(options?.page);
25893
- const { md: md2, defs } = extractFootnoteDefs(markdown);
26853
+ const { md, defs } = extractFootnoteDefs(markdown);
25894
26854
  beginInlineDoc(defs);
25895
26855
  try {
26856
+ if (gongmun && usesV5Engine(gongmun.preset)) {
26857
+ const blocks2 = parseMarkdownToBlocks(md);
26858
+ const staticBfEnd2 = staticBorderFillNext(gongmun, true);
26859
+ const charBase = charVariantBase(false, true);
26860
+ const fontBase = staticFontNext(gongmun, true);
26861
+ const remap2 = options?.profile ? buildProfileRemap(options.profile, charBase, staticBfEnd2, fontBase) : null;
26862
+ const bfReg2 = new TableBfRegistry(staticBfEnd2 + (remap2?.borderFillXmls.length ?? 0));
26863
+ const reg = new StyleRegistry(charBase + (remap2?.charPrXmls.length ?? 0), 8, fontBase + (remap2?.fontFaces.length ?? 0), ["\uD568\uCD08\uB86C\uBC14\uD0D5", "\uD568\uCD08\uB86C\uB3CB\uC6C0", "HY\uACAC\uACE0\uB515"]);
26864
+ const chartParts2 = [];
26865
+ const supplied2 = options?.images ? new Map(Object.entries(options.images).map(([k, v]) => [k, v instanceof Uint8Array ? v : new Uint8Array(v)])) : void 0;
26866
+ const images2 = new ImageRegistry(supplied2);
26867
+ const res = buildGongmunSectionV5(blocks2, gongmun, { reg, bfReg: bfReg2, remap: remap2, images: images2, page, chartParts: chartParts2 }, theme);
26868
+ if (options?.warnings) options.warnings.push(...res.warnings);
26869
+ if (remap2 && remap2.tables.length > 0 && remap2.tables.every((t) => !t.used)) {
26870
+ console.warn(`[kordoc] format profile: \uD504\uB85C\uD544 \uD45C ${remap2.tables.length}\uAC1C\uAC00 \uBB38\uC11C \uD45C\uC640 \uB9E4\uCE6D\uB418\uC9C0 \uC54A\uC544 \uBBF8\uC801\uC6A9 (\uD589\xB7\uC5F4/\uCCAB \uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58)`);
26871
+ }
26872
+ const zip2 = new JSZip7();
26873
+ zip2.file("mimetype", "application/hwp+zip", { compression: "STORE" });
26874
+ zip2.file("META-INF/container.xml", generateContainerXml());
26875
+ zip2.file("Contents/content.hpf", generateManifest(chartParts2, images2.manifestItems(), "gongmun"));
26876
+ for (const part of images2.parts) zip2.file(part.name, part.data);
26877
+ zip2.file("Contents/header.xml", generateHeaderXml(
26878
+ theme,
26879
+ gongmun,
26880
+ [],
26881
+ [...remap2?.borderFillXmls ?? [], ...bfReg2.emit()],
26882
+ [...remap2?.charPrXmls ?? [], ...reg.charPrXmls],
26883
+ [],
26884
+ [...remap2?.fontFaces ?? [], ...reg.extraFonts],
26885
+ { paraPrXmls: reg.paraPrXmls, bodyFace: "\uD568\uCD08\uB86C\uBC14\uD0D5" }
26886
+ ));
26887
+ zip2.file("Contents/section0.xml", res.xml);
26888
+ for (const part of chartParts2) zip2.file(part.name, part.xml);
26889
+ zip2.file("Preview/PrvText.txt", buildPrvText(blocks2));
26890
+ return await zip2.generateAsync({ type: "arraybuffer" });
26891
+ }
25896
26892
  const measured = !!gongmun && usesReportFonts(gongmun.preset);
25897
26893
  const richAssets = !!gongmun && needsGaejosikAssets(gongmun);
25898
- const blocks = parseMarkdownToBlocks(md2);
26894
+ const blocks = parseMarkdownToBlocks(md);
25899
26895
  const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
25900
26896
  const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
25901
26897
  const staticBfEnd = staticBorderFillNext(gongmun);
@@ -25920,7 +26916,7 @@ async function markdownToHwpx(markdown, options) {
25920
26916
  }
25921
26917
  }
25922
26918
  const extraBorderFills = [...remap?.borderFillXmls ?? [], ...bfReg?.emit() ?? []];
25923
- const zip = new JSZip8();
26919
+ const zip = new JSZip7();
25924
26920
  zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
25925
26921
  zip.file("META-INF/container.xml", generateContainerXml());
25926
26922
  zip.file("Contents/content.hpf", generateManifest(chartParts, images.manifestItems(), gongmun ? "gongmun" : "default"));
@@ -26133,7 +27129,7 @@ function readBuiltinTemplateSample(t) {
26133
27129
  }
26134
27130
 
26135
27131
  // src/hwpx/extract-profile.ts
26136
- import JSZip9 from "jszip";
27132
+ import JSZip8 from "jszip";
26137
27133
  function elemsByLocal(root, name) {
26138
27134
  const all = root.getElementsByTagName("*");
26139
27135
  const out = [];
@@ -26336,7 +27332,7 @@ function pick(map, keys) {
26336
27332
  }
26337
27333
  async function hwpxToProfile(input) {
26338
27334
  const buf = input instanceof ArrayBuffer ? input : toArrayBuffer(input);
26339
- const zip = await JSZip9.loadAsync(buf);
27335
+ const zip = await JSZip8.loadAsync(buf);
26340
27336
  const parser = createXmlParser();
26341
27337
  const headerFile = zip.file("Contents/header.xml") ?? zip.file(/[Hh]eader\.xml$/)?.[0];
26342
27338
  let headerXml = "<root/>";
@@ -26615,6 +27611,16 @@ var RULES = [
26615
27611
  message: "\uB0A0\uC9DC\uC758 '\uC77C' \uB2E4\uC74C\uC5D0 \uB9C8\uCE68\uD45C(.)\uB97C \uCC0D\uC5B4\uC57C \uD568",
26616
27612
  suggest: "\uC608) 2025. 1. 6."
26617
27613
  },
27614
+ // 하이픈·ISO 날짜(2026-07-18) — 편람은 온점 구분만 인정. URL 경로·파일명·코드 안(앞뒤에 / = & ? # % . - 영숫자)은
27615
+ // 제외, 표 셀은 서식 기입란 관행이 섞여 건너뜀. 제안은 실제 변환값(0 패딩 제거)
27616
+ {
27617
+ code: "DATE_HYPHEN",
27618
+ severity: "warning",
27619
+ skipTable: true,
27620
+ pattern: /(?<![\/=&?#%.\w-])(?:19|20)\d{2}-\d{1,2}-\d{1,2}(?![\/\w-])/g,
27621
+ message: "\uB0A0\uC9DC\uB294 \uD558\uC774\uD508(-) \uB300\uC2E0 \uC628\uC810\uC73C\uB85C \uAD6C\uBD84\uD558\uACE0 \uC628\uC810 \uB4A4 \uD55C \uCE78 \uB744\uC6C0",
27622
+ suggest: "\uC608) 2026. 7. 18."
27623
+ },
26618
27624
  // 시간 ─ 24시각제, 쌍점 붙여쓰기
26619
27625
  {
26620
27626
  code: "TIME_AMPM",
@@ -26793,6 +27799,12 @@ function lintGongmunText(text, opts) {
26793
27799
  return findings;
26794
27800
  }
26795
27801
  function dictSuggest(code, match) {
27802
+ const m = match.trim();
27803
+ if (code === "MONEY_NO_HANGUL") return `${m} \u2192 ${m}(\uAE08${hangulAmount(m)}\uC6D0)`;
27804
+ if (code === "DATE_HYPHEN") {
27805
+ const d = m.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
27806
+ if (d) return `${m} \u2192 ${d[1]}. ${Number(d[2])}. ${Number(d[3])}.`;
27807
+ }
26796
27808
  const table2 = code === "LOANWORD_ERROR" ? LOANWORD_FIXES : code === "DISCRIMINATORY_TERM" ? DISCRIM_FIXES : null;
26797
27809
  if (!table2) return void 0;
26798
27810
  const hit = table2.find(([w]) => w === match.trim());
@@ -26829,7 +27841,7 @@ function classify(raw) {
26829
27841
  if (li) return { kind: indent >= 2 ? "sub" : "item", body: li[1].trim() };
26830
27842
  return { kind: "para", body: t };
26831
27843
  }
26832
- function plain(s) {
27844
+ function plain2(s) {
26833
27845
  return s.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/(\*\*|__|~~|`)/g, "").trim();
26834
27846
  }
26835
27847
  function stripQuoted(s) {
@@ -26932,11 +27944,11 @@ function lintMuncheText(text) {
26932
27944
  continue;
26933
27945
  }
26934
27946
  if (c.kind === "lead") {
26935
- leadBuf.push({ line: no, body: plain(c.body) });
27947
+ leadBuf.push({ line: no, body: plain2(c.body) });
26936
27948
  continue;
26937
27949
  }
26938
27950
  flushLead();
26939
- const b = plain(c.body);
27951
+ const b = plain2(c.body);
26940
27952
  if (!b) continue;
26941
27953
  const q = stripQuoted(b);
26942
27954
  if (c.kind === "item" || c.kind === "sub" || c.kind === "concl" || c.kind === "para") {
@@ -27035,7 +28047,7 @@ function muncheLintWarnings(text, limit = 10) {
27035
28047
  }
27036
28048
 
27037
28049
  // src/roundtrip/patcher.ts
27038
- import JSZip10 from "jszip";
28050
+ import JSZip9 from "jszip";
27039
28051
 
27040
28052
  // src/roundtrip/table-rows.ts
27041
28053
  var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
@@ -27349,7 +28361,7 @@ function gfmRenderStable(editedRows, cols) {
27349
28361
  hasHeader: editedRows.length > 1,
27350
28362
  cells: editedRows.map((row) => {
27351
28363
  const padded = row.length < cols ? [...row, ...Array(cols - row.length).fill("")] : row;
27352
- return padded.map((md2) => ({ text: unescapeGfmCell(md2), colSpan: 1, rowSpan: 1 }));
28364
+ return padded.map((md) => ({ text: unescapeGfmCell(md), colSpan: 1, rowSpan: 1 }));
27353
28365
  })
27354
28366
  };
27355
28367
  const replica = replicateGfmTable(sim);
@@ -27685,7 +28697,7 @@ async function patchHwpx(original, editedMarkdown, options) {
27685
28697
  }
27686
28698
  let zip;
27687
28699
  try {
27688
- zip = await JSZip10.loadAsync(original);
28700
+ zip = await JSZip9.loadAsync(original);
27689
28701
  } catch {
27690
28702
  return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
27691
28703
  }
@@ -28960,8 +29972,8 @@ function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before,
28960
29972
  }
28961
29973
  return staged > 0 ? 1 : 0;
28962
29974
  }
28963
- function gfmCellToPlain(md2) {
28964
- let t = md2.trim();
29975
+ function gfmCellToPlain(md) {
29976
+ let t = md.trim();
28965
29977
  const bold = t.match(/^\*\*([\s\S]+)\*\*$/);
28966
29978
  if (bold) t = bold[1];
28967
29979
  if (/[*`]|!\[|\]\(/.test(t)) return null;
@@ -29182,7 +30194,7 @@ function stageParaPatch(scan, para2, newPlain, skip) {
29182
30194
  }
29183
30195
 
29184
30196
  // src/validate.ts
29185
- import JSZip11 from "jszip";
30197
+ import JSZip10 from "jszip";
29186
30198
  import { DOMParser as DOMParser4 } from "@xmldom/xmldom";
29187
30199
  var REQUIRED_FILES = [
29188
30200
  "mimetype",
@@ -29200,7 +30212,7 @@ async function validateHwpx(buffer) {
29200
30212
  const issues = [];
29201
30213
  let zip;
29202
30214
  try {
29203
- zip = await JSZip11.loadAsync(buffer);
30215
+ zip = await JSZip10.loadAsync(buffer);
29204
30216
  } catch (err) {
29205
30217
  return {
29206
30218
  ok: false,
@@ -29268,10 +30280,10 @@ async function validateHwpx(buffer) {
29268
30280
  }
29269
30281
 
29270
30282
  // src/roundtrip/session.ts
29271
- import JSZip12 from "jszip";
30283
+ import JSZip11 from "jszip";
29272
30284
  async function buildState(bytes) {
29273
30285
  const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
29274
- const zip = await JSZip12.loadAsync(bytes);
30286
+ const zip = await JSZip11.loadAsync(bytes);
29275
30287
  const sectionPaths = await resolveSectionEntryNames(zip);
29276
30288
  if (sectionPaths.length === 0) {
29277
30289
  throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
@@ -29612,159 +30624,6 @@ async function patchHwpxBlocks(original, edits, options) {
29612
30624
  return session.patchBlocks(edits, options);
29613
30625
  }
29614
30626
 
29615
- // src/print/renderer.ts
29616
- import { existsSync as existsSync2 } from "fs";
29617
- import MarkdownIt from "markdown-it";
29618
- var PRESETS = {
29619
- default: `
29620
- @page { size: A4; margin: 20mm; }
29621
- body { font-family: 'Pretendard', 'Malgun Gothic', '\uB9D1\uC740 \uACE0\uB515', sans-serif; font-size: 11pt; line-height: 1.6; color: #111; }
29622
- h1 { font-size: 20pt; margin: 1em 0 0.5em; }
29623
- h2 { font-size: 16pt; margin: 1em 0 0.4em; }
29624
- h3 { font-size: 13pt; margin: 0.8em 0 0.3em; }
29625
- p { margin: 0.4em 0; }
29626
- table { border-collapse: collapse; margin: 0.6em 0; width: 100%; }
29627
- th, td { border: 1px solid #555; padding: 4px 8px; text-align: left; vertical-align: top; }
29628
- th { background: #f0f0f0; }
29629
- code { background: #f5f5f5; padding: 1px 4px; border-radius: 2px; font-family: 'D2Coding', Consolas, monospace; }
29630
- pre { background: #f5f5f5; padding: 8px; border-radius: 4px; overflow-x: auto; }
29631
- blockquote { border-left: 3px solid #ccc; padding-left: 12px; color: #555; margin: 0.6em 0; }
29632
- img { max-width: 100%; }
29633
- `,
29634
- "gov-formal": `
29635
- @page { size: A4; margin: 25mm 20mm; }
29636
- body { font-family: '\uD568\uCD08\uB86C\uBC14\uD0D5', 'HCR Batang', 'Batang', 'Malgun Gothic', serif; font-size: 11pt; line-height: 1.7; color: #000; }
29637
- h1 { font-size: 18pt; text-align: center; margin: 0.5em 0 1em; letter-spacing: 0.05em; }
29638
- h2 { font-size: 14pt; margin: 1em 0 0.4em; border-bottom: 1px solid #999; padding-bottom: 2px; }
29639
- h3 { font-size: 12pt; margin: 0.8em 0 0.3em; }
29640
- p { margin: 0.3em 0; text-indent: 1em; }
29641
- table { border-collapse: collapse; margin: 0.8em 0; width: 100%; }
29642
- th, td { border: 1px solid #000; padding: 5px 8px; vertical-align: top; }
29643
- th { background: #e8e8e8; font-weight: normal; }
29644
- blockquote { border-left: 2px solid #555; padding-left: 12px; margin: 0.6em 0; }
29645
- `,
29646
- compact: `
29647
- @page { size: A4; margin: 10mm; }
29648
- body { font-family: 'Pretendard', 'Malgun Gothic', sans-serif; font-size: 9pt; line-height: 1.4; color: #111; }
29649
- h1 { font-size: 14pt; margin: 0.5em 0 0.3em; }
29650
- h2 { font-size: 12pt; margin: 0.5em 0 0.3em; }
29651
- h3 { font-size: 10pt; margin: 0.4em 0 0.2em; }
29652
- p { margin: 0.2em 0; }
29653
- table { border-collapse: collapse; margin: 0.3em 0; width: 100%; font-size: 8pt; }
29654
- th, td { border: 1px solid #777; padding: 2px 4px; }
29655
- th { background: #f0f0f0; }
29656
- `
29657
- };
29658
- var md = new MarkdownIt({
29659
- html: true,
29660
- linkify: true,
29661
- breaks: false
29662
- });
29663
- function renderHtml(markdown, options) {
29664
- const preset = options?.preset ?? "default";
29665
- const css = PRESETS[preset] + (options?.extraCss ?? "");
29666
- const body = md.render(markdown);
29667
- const watermark = options?.watermark ? `<div class="watermark">${escapeHtml(options.watermark)}</div>` : "";
29668
- const watermarkCss = options?.watermark ? `
29669
- .watermark {
29670
- position: fixed;
29671
- top: 50%; left: 50%;
29672
- transform: translate(-50%, -50%) rotate(-30deg);
29673
- font-size: 80pt;
29674
- color: rgba(0,0,0,0.08);
29675
- pointer-events: none;
29676
- z-index: 9999;
29677
- white-space: nowrap;
29678
- }` : "";
29679
- return `<!DOCTYPE html>
29680
- <html lang="ko">
29681
- <head>
29682
- <meta charset="utf-8">
29683
- <style>${css}${watermarkCss}</style>
29684
- </head>
29685
- <body>
29686
- ${watermark}
29687
- ${body}
29688
- </body>
29689
- </html>`;
29690
- }
29691
- function escapeHtml(s) {
29692
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
29693
- }
29694
- async function htmlToPdf(html, options) {
29695
- let puppeteer;
29696
- try {
29697
- puppeteer = await import("puppeteer-core");
29698
- } catch {
29699
- throw new KordocError(
29700
- "PDF \uC0DD\uC131\uC5D0 puppeteer-core\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uCE58: npm install puppeteer-core"
29701
- );
29702
- }
29703
- const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ?? findChromiumPath();
29704
- if (!executablePath) {
29705
- throw new KordocError(
29706
- "Chromium \uC2E4\uD589 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. PUPPETEER_EXECUTABLE_PATH \uD658\uACBD\uBCC0\uC218\uB97C \uC124\uC815\uD558\uC138\uC694."
29707
- );
29708
- }
29709
- const browser = await puppeteer.default.launch({
29710
- executablePath,
29711
- headless: true,
29712
- args: ["--no-sandbox", "--disable-setuid-sandbox"]
29713
- });
29714
- try {
29715
- const page = await browser.newPage();
29716
- await page.setContent(html, { waitUntil: "networkidle0" });
29717
- const margin = options?.margin;
29718
- const pdf = await page.pdf({
29719
- format: options?.pageSize ?? "A4",
29720
- landscape: options?.orientation === "landscape",
29721
- printBackground: true,
29722
- margin: margin ? {
29723
- top: toCss(margin.top),
29724
- right: toCss(margin.right),
29725
- bottom: toCss(margin.bottom),
29726
- left: toCss(margin.left)
29727
- } : void 0,
29728
- displayHeaderFooter: !!(options?.header || options?.footer),
29729
- headerTemplate: options?.header ?? "<div></div>",
29730
- footerTemplate: options?.footer ?? '<div style="font-size:8pt;width:100%;text-align:center;color:#777;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>'
29731
- });
29732
- return Buffer.from(pdf);
29733
- } finally {
29734
- await browser.close();
29735
- }
29736
- }
29737
- function toCss(v) {
29738
- return typeof v === "number" ? `${v}mm` : v;
29739
- }
29740
- function findChromiumPath() {
29741
- const win = [
29742
- "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
29743
- "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
29744
- process.env.LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe",
29745
- "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
29746
- "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
29747
- ];
29748
- const mac = [
29749
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
29750
- "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
29751
- ];
29752
- const linux = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"];
29753
- const candidates = process.platform === "win32" ? win : process.platform === "darwin" ? mac : linux;
29754
- for (const p of candidates) {
29755
- if (p && existsSync2(p)) return p;
29756
- }
29757
- return null;
29758
- }
29759
- async function markdownToPdf(markdown, options) {
29760
- const html = renderHtml(markdown, options);
29761
- return htmlToPdf(html, options);
29762
- }
29763
- async function blocksToPdf(blocks, options) {
29764
- const markdown = blocksToMarkdown(blocks);
29765
- return markdownToPdf(markdown, options);
29766
- }
29767
-
29768
30627
  // src/index.ts
29769
30628
  async function parse(input, options) {
29770
30629
  let buffer;
@@ -29787,6 +30646,7 @@ async function parse(input, options) {
29787
30646
  }
29788
30647
  const format = detectFormat(buffer);
29789
30648
  const result = await dispatch(format, buffer, opts);
30649
+ if (result.success && opts?.classifyTables) classifyTableTree(result.blocks);
29790
30650
  if (result.success && !result.pages) {
29791
30651
  const pages = blocksToPages(result.blocks);
29792
30652
  if (pages) return { ...result, pages };
@@ -29820,7 +30680,7 @@ async function dispatch(format, buffer, opts) {
29820
30680
  }
29821
30681
  async function parseImage(buffer, options) {
29822
30682
  try {
29823
- const { parseImageDocument } = await import("./image-ocr-G4JVF2UH.js");
30683
+ const { parseImageDocument } = await import("./image-ocr-2M7K3CE5.js");
29824
30684
  const { blocks, warnings } = await parseImageDocument(buffer, options);
29825
30685
  return {
29826
30686
  success: true,
@@ -29879,7 +30739,7 @@ async function parseHwp(buffer, options) {
29879
30739
  async function parsePdf(buffer, options) {
29880
30740
  let parsePdfDocument;
29881
30741
  try {
29882
- const mod = await import("./parser-I7TC35V6.js");
30742
+ const mod = await import("./parser-T5R4YVZS.js");
29883
30743
  parsePdfDocument = mod.parsePdfDocument;
29884
30744
  } catch {
29885
30745
  return {
@@ -29969,14 +30829,73 @@ async function fillForm(input, values, outputFormat = "markdown") {
29969
30829
  return { output: markdown, format: "markdown", fill };
29970
30830
  }
29971
30831
 
30832
+ // src/table/visual.ts
30833
+ function wantsCrop(kind, policy) {
30834
+ if (policy === "none") return false;
30835
+ if (policy === "all") return true;
30836
+ if (kind === "non-tabular-layout") return true;
30837
+ return policy === "non-tabular-and-uncertain" && kind === "uncertain";
30838
+ }
30839
+ async function extractTables(input, options = {}) {
30840
+ const policy = options.policy ?? "non-tabular-and-uncertain";
30841
+ const buffer = typeof input === "string" ? toArrayBuffer(await readFile2(input)) : Buffer.isBuffer(input) ? toArrayBuffer(input) : input;
30842
+ const parsed = await parse(buffer, { classifyTables: true });
30843
+ if (!parsed.success) throw new KordocError(`\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}`);
30844
+ const blocks = collectTableBlocks(parsed.blocks);
30845
+ const out = blocks.map((b, i) => ({
30846
+ id: b.table.sourceId ?? `tbl-${i + 1}`,
30847
+ page: b.pageNumber ?? 1,
30848
+ table: b.table,
30849
+ classification: b.table.classification,
30850
+ sourceId: b.table.sourceId,
30851
+ regions: [],
30852
+ crops: [],
30853
+ warnings: []
30854
+ }));
30855
+ if (out.length === 0) return out;
30856
+ const format = detectFormat(buffer);
30857
+ if (format !== "hwpx") {
30858
+ for (const t of out) t.warnings.push(format === "hwp" ? "HWP5 \uB294 \uB808\uC774\uC544\uC6C3 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 region\xB7crop \uC5C6\uC74C(\uBD84\uB958\uB9CC)" : `${format} \uC740 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 region\xB7crop \uC5C6\uC74C`);
30859
+ return out;
30860
+ }
30861
+ let scene;
30862
+ try {
30863
+ scene = (await renderDocumentToScene(buffer, { reflow: true })).scene;
30864
+ } catch (e) {
30865
+ for (const t of out) t.warnings.push(`\uB80C\uB354 \uC2E4\uD328 \u2014 region \uC5C6\uC74C: ${e instanceof Error ? e.message : String(e)}`);
30866
+ return out;
30867
+ }
30868
+ const bySource = /* @__PURE__ */ new Map();
30869
+ for (const r of scene.regions) if (r.type === "table" && r.sourceId) (bySource.get(r.sourceId) ?? bySource.set(r.sourceId, []).get(r.sourceId)).push(r);
30870
+ const cropIds = /* @__PURE__ */ new Set();
30871
+ for (const t of out) {
30872
+ if (!t.sourceId) {
30873
+ t.warnings.push("sourceId \uC5C6\uC74C \u2014 region \uC870\uC778 \uBD88\uAC00");
30874
+ continue;
30875
+ }
30876
+ const hits = bySource.get(t.sourceId) ?? [];
30877
+ if (hits.length === 0) {
30878
+ t.warnings.push(`\uB80C\uB354 region \uBBF8\uB9E4\uCE6D(sourceId ${t.sourceId})`);
30879
+ continue;
30880
+ }
30881
+ if (hits.length > 1) {
30882
+ t.warnings.push(`\uB80C\uB354 region \uC911\uBCF5 \uB9E4\uCE6D(sourceId ${t.sourceId}, ${hits.length}\uAC1C) \u2014 \uCCAB \uAC1C\uCCB4\uB9CC`);
30883
+ }
30884
+ t.regions = hits[0].regions.map((b) => ({ ...b }));
30885
+ t.page = hits[0].page;
30886
+ t.table.regions = t.regions.map((b) => ({ page: b.page, x: b.x, y: b.y, width: b.width, height: b.height }));
30887
+ if (wantsCrop(t.classification.kind, policy)) cropIds.add(hits[0].id);
30888
+ }
30889
+ if (cropIds.size === 0) return out;
30890
+ const assets = await extractRenderedRegions(buffer, { types: ["table"], format: options.format, paddingPt: options.paddingPt, maxWidthPx: options.maxWidthPx, reflow: true, filter: (r) => cropIds.has(r.id) });
30891
+ for (const t of out) {
30892
+ if (!t.sourceId) continue;
30893
+ for (const a of assets) if (a.region.sourceId === t.sourceId) t.crops.push({ page: a.page, bbox: a.bbox, mimeType: a.mimeType, data: a.data });
30894
+ }
30895
+ return out;
30896
+ }
30897
+
29972
30898
  export {
29973
- isZipFile,
29974
- isHwpxFile,
29975
- isOldHwpFile,
29976
- isPdfFile,
29977
- detectFormat,
29978
- detectOle2Format,
29979
- detectZipFormat,
29980
30899
  detectImageMime,
29981
30900
  extractHwpxMetadataOnly,
29982
30901
  extractHwp5MetadataOnly,
@@ -29990,6 +30909,10 @@ export {
29990
30909
  fillFormFields,
29991
30910
  extractClickHereFields,
29992
30911
  fillHwpx,
30912
+ classifyTable,
30913
+ collectTableBlocks,
30914
+ classifyTableTree,
30915
+ chooseTableRepresentation,
29993
30916
  blocksToPages,
29994
30917
  PRESET_ALIAS,
29995
30918
  normalizeGongmunPreset,
@@ -30015,9 +30938,7 @@ export {
30015
30938
  HwpxSession,
30016
30939
  openHwpxDocument,
30017
30940
  patchHwpxBlocks,
30018
- renderHtml,
30019
- markdownToPdf,
30020
- blocksToPdf,
30941
+ extractTables,
30021
30942
  parse,
30022
30943
  parseImage,
30023
30944
  parseHwp3,
@@ -30030,4 +30951,4 @@ export {
30030
30951
  parseHwpml,
30031
30952
  fillForm
30032
30953
  };
30033
- //# sourceMappingURL=chunk-INFYDSZL.js.map
30954
+ //# sourceMappingURL=chunk-2RREQZ44.js.map