kordoc 4.12.2 → 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.
- package/README.md +32 -1
- package/dist/{-EZH6NKTE.js → -UZDRUWTX.js} +43 -22
- package/dist/{chunk-FBLMVHDK.js → chunk-27QN27I3.js} +2 -2
- package/dist/{chunk-MUYU4JI5.js → chunk-2B45S7FM.js} +2 -2
- package/dist/{chunk-LD2K4K5V.js → chunk-2RREQZ44.js} +1662 -652
- package/dist/chunk-2RREQZ44.js.map +1 -0
- package/dist/{chunk-OEYECZGV.js → chunk-3ABHPKYA.js} +10 -6
- package/dist/chunk-3ABHPKYA.js.map +1 -0
- package/dist/{chunk-QM2RSJ2S.js → chunk-3MCFJIN2.js} +5 -5
- package/dist/{chunk-JCMBQC5N.js → chunk-5VHLKNCE.js} +2 -2
- package/dist/{chunk-FVBR7SC3.js → chunk-6JXMIX3G.js} +2 -2
- package/dist/chunk-6JXMIX3G.js.map +1 -0
- package/dist/{chunk-5RPYBC2Q.js → chunk-6NLXGGNH.js} +37 -6
- package/dist/chunk-6NLXGGNH.js.map +1 -0
- package/dist/{chunk-2B7OIEJJ.js → chunk-AGBFCFQY.js} +10 -6
- package/dist/chunk-AGBFCFQY.js.map +1 -0
- package/dist/{rasterize-NRWP2C6L.js → chunk-BQLZOQES.js} +22 -6
- package/dist/chunk-BQLZOQES.js.map +1 -0
- package/dist/{chunk-ONNAHSPV.cjs → chunk-CKMK42FV.cjs} +18 -14
- package/dist/chunk-CKMK42FV.cjs.map +1 -0
- package/dist/{chunk-T7GRHAIW.cjs → chunk-COUGOFT3.cjs} +1 -1
- package/dist/chunk-COUGOFT3.cjs.map +1 -0
- package/dist/{chunk-2LO525TL.js → chunk-F3FEIYI7.js} +2 -2
- package/dist/{chunk-Q6ETEJFK.js → chunk-GWFX5ZD7.js} +11 -7
- package/dist/chunk-GWFX5ZD7.js.map +1 -0
- package/dist/{chunk-2HBYWA5X.js → chunk-H5JWPWQP.js} +2 -2
- package/dist/{chunk-N3DLQ2YH.js → chunk-HG5LXXCC.js} +1 -1
- package/dist/chunk-HG5LXXCC.js.map +1 -0
- package/dist/{chunk-KX7IYCCZ.js → chunk-IEJR23OA.js} +3 -3
- package/dist/{chunk-7XUXSDH5.js → chunk-ILDTN72S.js} +10 -6
- package/dist/chunk-ILDTN72S.js.map +1 -0
- package/dist/{chunk-QBCMFNHS.js → chunk-L3CEPNQS.js} +2 -2
- package/dist/{chunk-XXDZ2KTW.js → chunk-LP5RTYFM.js} +2 -2
- package/dist/{chunk-2LKYJ4G3.cjs → chunk-MCD45IHO.cjs} +6 -6
- package/dist/{chunk-2LKYJ4G3.cjs.map → chunk-MCD45IHO.cjs.map} +1 -1
- package/dist/{chunk-V5BUPZ6I.cjs → chunk-OJV7HNGJ.cjs} +13 -9
- package/dist/chunk-OJV7HNGJ.cjs.map +1 -0
- package/dist/{chunk-DBGEUDBY.js → chunk-OSLJ3A6D.js} +2 -2
- package/dist/{chunk-2LPBIW2B.cjs → chunk-S2VKZKUX.cjs} +3 -3
- package/dist/{chunk-2LPBIW2B.cjs.map → chunk-S2VKZKUX.cjs.map} +1 -1
- package/dist/{chunk-IOJX4Q63.js → chunk-T7AFLXGA.js} +786 -19
- package/dist/chunk-T7AFLXGA.js.map +1 -0
- package/dist/{chunk-UL2MDA5C.js → chunk-TA2LEFOF.js} +3 -3
- package/dist/{chunk-62IU4NV3.cjs → chunk-TAYCQE6K.cjs} +2 -2
- package/dist/{chunk-62IU4NV3.cjs.map → chunk-TAYCQE6K.cjs.map} +1 -1
- package/dist/{chunk-ONQHGZTS.js → chunk-XVIJLHCW.js} +1 -1
- package/dist/chunk-XVIJLHCW.js.map +1 -0
- package/dist/chunks-G3GK536Y.js +10 -0
- package/dist/cli.js +187 -38
- package/dist/cli.js.map +1 -1
- package/dist/{formula-6RQX4NMJ.js → formula-3T7HQA4O.js} +4 -4
- package/dist/{formula-NW33S4TA.js → formula-FWVAOSIK.js} +3 -3
- package/dist/{formula-NBEO6CFC.cjs → formula-YALEMZMF.cjs} +9 -9
- package/dist/{formula-NBEO6CFC.cjs.map → formula-YALEMZMF.cjs.map} +1 -1
- package/dist/{image-ocr-SO56VETS.js → image-ocr-2M7K3CE5.js} +8 -8
- package/dist/{image-ocr-V4KY373B.cjs → image-ocr-4EUZIF6M.cjs} +12 -12
- package/dist/{image-ocr-V4KY373B.cjs.map → image-ocr-4EUZIF6M.cjs.map} +1 -1
- package/dist/{image-ocr-CAVU2TNZ.js → image-ocr-X3I2HUK2.js} +6 -6
- package/dist/index.cjs +2349 -576
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -12
- package/dist/index.d.ts +305 -12
- package/dist/index.js +1980 -207
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +109 -35
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-EIJF34DR.js → model-bundle-XXYZ6ELQ.js} +5 -5
- package/dist/{models-6V4CPQXC.js → models-APDSXJW7.js} +5 -5
- package/dist/{parser-OMT25WH2.cjs → parser-4KE2FR5F.cjs} +60 -39
- package/dist/parser-4KE2FR5F.cjs.map +1 -0
- package/dist/{parser-OBHNXEQM.js → parser-T5R4YVZS.js} +32 -11
- package/dist/parser-T5R4YVZS.js.map +1 -0
- package/dist/{parser-GIREMEPD.js → parser-XKSOS6GY.js} +32 -11
- package/dist/parser-XKSOS6GY.js.map +1 -0
- package/dist/pdf-ocr-JZKCEKWX.cjs +14 -0
- package/dist/{pdf-ocr-TFYVK4FI.cjs.map → pdf-ocr-JZKCEKWX.cjs.map} +1 -1
- package/dist/pdf-ocr-X62B7YDV.js +13 -0
- package/dist/pdf-ocr-YDJQEGDS.js +16 -0
- package/dist/{profile-io-PZVMM66V.js → profile-io-Y24GOHWM.js} +3 -3
- package/dist/rasterize-QCOBWHAG.js +13 -0
- package/dist/render-DHFQQCSV.js +38 -0
- package/dist/seal-UTHFQPLO.js +10 -0
- package/dist/seal-UTHFQPLO.js.map +1 -0
- package/dist/{setup-V55AJP6H.js → setup-UWX3KH3G.js} +3 -3
- package/dist/visual-DF2KSPEC.js +19 -0
- package/dist/visual-DF2KSPEC.js.map +1 -0
- package/dist/{watch-7CQSMDDP.js → watch-4ZUTMC3Q.js} +15 -13
- package/dist/{watch-7CQSMDDP.js.map → watch-4ZUTMC3Q.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-2B7OIEJJ.js.map +0 -1
- package/dist/chunk-5RPYBC2Q.js.map +0 -1
- package/dist/chunk-7XUXSDH5.js.map +0 -1
- package/dist/chunk-FVBR7SC3.js.map +0 -1
- package/dist/chunk-IOJX4Q63.js.map +0 -1
- package/dist/chunk-LD2K4K5V.js.map +0 -1
- package/dist/chunk-N3DLQ2YH.js.map +0 -1
- package/dist/chunk-OEYECZGV.js.map +0 -1
- package/dist/chunk-ONNAHSPV.cjs.map +0 -1
- package/dist/chunk-ONQHGZTS.js.map +0 -1
- package/dist/chunk-Q6ETEJFK.js.map +0 -1
- package/dist/chunk-T7GRHAIW.cjs.map +0 -1
- package/dist/chunk-V5BUPZ6I.cjs.map +0 -1
- package/dist/chunks-TT7ZE5QQ.js +0 -10
- package/dist/parser-GIREMEPD.js.map +0 -1
- package/dist/parser-OBHNXEQM.js.map +0 -1
- package/dist/parser-OMT25WH2.cjs.map +0 -1
- package/dist/pdf-ocr-IRXQ5GBM.js +0 -13
- package/dist/pdf-ocr-SL4GCOGS.js +0 -16
- package/dist/pdf-ocr-TFYVK4FI.cjs +0 -14
- package/dist/rasterize-NRWP2C6L.js.map +0 -1
- package/dist/render-V5ZPTZQ7.js +0 -10
- package/dist/seal-LVG2DGAB.js +0 -10
- /package/dist/{-EZH6NKTE.js.map → -UZDRUWTX.js.map} +0 -0
- /package/dist/{chunk-FBLMVHDK.js.map → chunk-27QN27I3.js.map} +0 -0
- /package/dist/{chunk-MUYU4JI5.js.map → chunk-2B45S7FM.js.map} +0 -0
- /package/dist/{chunk-QM2RSJ2S.js.map → chunk-3MCFJIN2.js.map} +0 -0
- /package/dist/{chunk-JCMBQC5N.js.map → chunk-5VHLKNCE.js.map} +0 -0
- /package/dist/{chunk-2LO525TL.js.map → chunk-F3FEIYI7.js.map} +0 -0
- /package/dist/{chunk-2HBYWA5X.js.map → chunk-H5JWPWQP.js.map} +0 -0
- /package/dist/{chunk-KX7IYCCZ.js.map → chunk-IEJR23OA.js.map} +0 -0
- /package/dist/{chunk-QBCMFNHS.js.map → chunk-L3CEPNQS.js.map} +0 -0
- /package/dist/{chunk-XXDZ2KTW.js.map → chunk-LP5RTYFM.js.map} +0 -0
- /package/dist/{chunk-DBGEUDBY.js.map → chunk-OSLJ3A6D.js.map} +0 -0
- /package/dist/{chunk-UL2MDA5C.js.map → chunk-TA2LEFOF.js.map} +0 -0
- /package/dist/{chunks-TT7ZE5QQ.js.map → chunks-G3GK536Y.js.map} +0 -0
- /package/dist/{formula-6RQX4NMJ.js.map → formula-3T7HQA4O.js.map} +0 -0
- /package/dist/{formula-NW33S4TA.js.map → formula-FWVAOSIK.js.map} +0 -0
- /package/dist/{image-ocr-SO56VETS.js.map → image-ocr-2M7K3CE5.js.map} +0 -0
- /package/dist/{image-ocr-CAVU2TNZ.js.map → image-ocr-X3I2HUK2.js.map} +0 -0
- /package/dist/{model-bundle-EIJF34DR.js.map → model-bundle-XXYZ6ELQ.js.map} +0 -0
- /package/dist/{models-6V4CPQXC.js.map → models-APDSXJW7.js.map} +0 -0
- /package/dist/{pdf-ocr-IRXQ5GBM.js.map → pdf-ocr-X62B7YDV.js.map} +0 -0
- /package/dist/{pdf-ocr-SL4GCOGS.js.map → pdf-ocr-YDJQEGDS.js.map} +0 -0
- /package/dist/{profile-io-PZVMM66V.js.map → profile-io-Y24GOHWM.js.map} +0 -0
- /package/dist/{render-V5ZPTZQ7.js.map → rasterize-QCOBWHAG.js.map} +0 -0
- /package/dist/{seal-LVG2DGAB.js.map → render-DHFQQCSV.js.map} +0 -0
- /package/dist/{setup-V55AJP6H.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-
|
|
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-
|
|
26
|
-
import {
|
|
27
|
-
MAX_COLS,
|
|
28
|
-
MAX_ROWS,
|
|
29
|
-
blocksToMarkdown,
|
|
30
|
-
buildTable,
|
|
31
|
-
convertTableToText,
|
|
32
|
-
dedupeRunningHeaders,
|
|
33
|
-
flattenLayoutTables,
|
|
34
|
-
mapPuaText
|
|
35
|
-
} from "./chunk-Q6ETEJFK.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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
506
|
-
const sum = createHash("sha256").update(
|
|
507
|
-
if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return
|
|
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
|
|
524
|
-
if (!
|
|
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:
|
|
274
|
+
decrypted.push({ path: entry.path, plain: plain3 });
|
|
528
275
|
}
|
|
529
|
-
for (const { path, plain:
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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) {
|
|
@@ -2717,8 +2485,9 @@ function appendParaText(state, data, resolveControl) {
|
|
|
2717
2485
|
case CHAR_PARA:
|
|
2718
2486
|
break;
|
|
2719
2487
|
// 문단 끝
|
|
2488
|
+
// 하이픈(24)은 한컴이 그리지 않는다 — 한컴 PDF·rhwp export-hwpx 모두 "60g/㎡"(licbyl2 17855137,
|
|
2489
|
+
// 전 코퍼스 유일 출현). 실렌더가 진실이므로 방출하지 않는다 (v4.12.3, 종전 "-")
|
|
2720
2490
|
case CHAR_HYPHEN:
|
|
2721
|
-
result += "-";
|
|
2722
2491
|
break;
|
|
2723
2492
|
case CHAR_NBSP:
|
|
2724
2493
|
result += "\xA0";
|
|
@@ -3079,7 +2848,7 @@ async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUn
|
|
|
3079
2848
|
}
|
|
3080
2849
|
|
|
3081
2850
|
// src/hwpx/metadata.ts
|
|
3082
|
-
import
|
|
2851
|
+
import JSZip from "jszip";
|
|
3083
2852
|
|
|
3084
2853
|
// src/hwpx/zip-sections.ts
|
|
3085
2854
|
import { inflateRawSync as inflateRawSync3 } from "zlib";
|
|
@@ -3245,7 +3014,7 @@ function parseDublinCoreMetadata(xml, metadata) {
|
|
|
3245
3014
|
async function extractHwpxMetadataOnly(buffer) {
|
|
3246
3015
|
let zip;
|
|
3247
3016
|
try {
|
|
3248
|
-
zip = await
|
|
3017
|
+
zip = await JSZip.loadAsync(buffer);
|
|
3249
3018
|
} catch {
|
|
3250
3019
|
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3251
3020
|
}
|
|
@@ -3261,7 +3030,7 @@ async function parseHwpxDocument(buffer, options) {
|
|
|
3261
3030
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
3262
3031
|
let zip;
|
|
3263
3032
|
try {
|
|
3264
|
-
zip = await
|
|
3033
|
+
zip = await JSZip2.loadAsync(buffer);
|
|
3265
3034
|
} catch {
|
|
3266
3035
|
return extractFromBrokenZip(buffer);
|
|
3267
3036
|
}
|
|
@@ -18193,7 +17962,7 @@ function isDistributionSentinel(markdown) {
|
|
|
18193
17962
|
}
|
|
18194
17963
|
|
|
18195
17964
|
// src/xlsx/parser.ts
|
|
18196
|
-
import
|
|
17965
|
+
import JSZip3 from "jszip";
|
|
18197
17966
|
import { DOMParser } from "@xmldom/xmldom";
|
|
18198
17967
|
var MAX_SHEETS = 100;
|
|
18199
17968
|
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
@@ -18478,7 +18247,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex, keep
|
|
|
18478
18247
|
}
|
|
18479
18248
|
async function parseXlsxDocument(buffer, options) {
|
|
18480
18249
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
18481
|
-
const zip = await
|
|
18250
|
+
const zip = await JSZip3.loadAsync(buffer);
|
|
18482
18251
|
const warnings = [];
|
|
18483
18252
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
18484
18253
|
if (!workbookFile) {
|
|
@@ -19258,7 +19027,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
19258
19027
|
}
|
|
19259
19028
|
|
|
19260
19029
|
// src/docx/parser.ts
|
|
19261
|
-
import
|
|
19030
|
+
import JSZip4 from "jszip";
|
|
19262
19031
|
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
19263
19032
|
|
|
19264
19033
|
// src/docx/equation.ts
|
|
@@ -20137,7 +19906,7 @@ function emitParagraphImages(p, imageMap, linked, out) {
|
|
|
20137
19906
|
}
|
|
20138
19907
|
async function parseDocxDocument(buffer, options) {
|
|
20139
19908
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE5);
|
|
20140
|
-
const zip = await
|
|
19909
|
+
const zip = await JSZip4.loadAsync(buffer);
|
|
20141
19910
|
const warnings = [];
|
|
20142
19911
|
const docFile = zip.file("word/document.xml");
|
|
20143
19912
|
if (!docFile) {
|
|
@@ -21356,10 +21125,10 @@ function fillInlineFields(text, values, filled, matchedLabels, blockedLabels) {
|
|
|
21356
21125
|
}
|
|
21357
21126
|
|
|
21358
21127
|
// src/form/filler-hwpx.ts
|
|
21359
|
-
import
|
|
21128
|
+
import JSZip6 from "jszip";
|
|
21360
21129
|
|
|
21361
21130
|
// src/form/click-here.ts
|
|
21362
|
-
import
|
|
21131
|
+
import JSZip5 from "jszip";
|
|
21363
21132
|
var FIELD_BEGIN_RE = /<([A-Za-z0-9_]+:)?fieldBegin\b([^>]*?)(\/?)>/g;
|
|
21364
21133
|
function attrOf2(attrs, name) {
|
|
21365
21134
|
const m = attrs.match(new RegExp(`(?:^|\\s)${name}="([^"]*)"`));
|
|
@@ -21466,7 +21235,7 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
|
|
|
21466
21235
|
}
|
|
21467
21236
|
async function extractClickHereFields(hwpxBuffer) {
|
|
21468
21237
|
precheckZipSize(hwpxBuffer);
|
|
21469
|
-
const zip = await
|
|
21238
|
+
const zip = await JSZip5.loadAsync(hwpxBuffer);
|
|
21470
21239
|
const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
|
|
21471
21240
|
const fields = [];
|
|
21472
21241
|
for (let si = 0; si < sectionPaths.length; si++) {
|
|
@@ -21487,7 +21256,7 @@ async function extractClickHereFields(hwpxBuffer) {
|
|
|
21487
21256
|
async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
21488
21257
|
const u8 = new Uint8Array(hwpxBuffer);
|
|
21489
21258
|
precheckZipSize(hwpxBuffer);
|
|
21490
|
-
const zip = await
|
|
21259
|
+
const zip = await JSZip6.loadAsync(hwpxBuffer);
|
|
21491
21260
|
const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
|
|
21492
21261
|
if (sectionPaths.length === 0) {
|
|
21493
21262
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -21785,6 +21554,166 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21785
21554
|
};
|
|
21786
21555
|
}
|
|
21787
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
|
+
|
|
21788
21717
|
// src/page-markdown.ts
|
|
21789
21718
|
function blocksToPages(blocks) {
|
|
21790
21719
|
const firstNumbered = blocks.find((b) => typeof b.pageNumber === "number")?.pageNumber;
|
|
@@ -21809,7 +21738,7 @@ function blocksToPages(blocks) {
|
|
|
21809
21738
|
}
|
|
21810
21739
|
|
|
21811
21740
|
// src/hwpx/generator.ts
|
|
21812
|
-
import
|
|
21741
|
+
import JSZip7 from "jszip";
|
|
21813
21742
|
|
|
21814
21743
|
// src/hwpx/geometry.ts
|
|
21815
21744
|
var A4_W_HU = 59528;
|
|
@@ -21954,6 +21883,8 @@ function bodyTitleGeom(bodyHeight, ov = {}) {
|
|
|
21954
21883
|
// src/hwpx/gongmun.ts
|
|
21955
21884
|
var OFFICIAL_MARGINS = { top: 20, bottom: 15, left: 20, right: 15 };
|
|
21956
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;
|
|
21957
21888
|
var GAEJOSIK_HEADER_FOOTER = 4251;
|
|
21958
21889
|
var PRESET_DEFAULTS = {
|
|
21959
21890
|
// 서울시 실결재 104건 중 12pt 64건(13pt 30, 15pt 5) — 지배값을 기본으로 사용.
|
|
@@ -22036,6 +21967,36 @@ function validateGongmunOptions(opts) {
|
|
|
22036
21967
|
if (opts.approval && opts.approval.length > 6) {
|
|
22037
21968
|
throw new KordocError("approval must contain at most 6 labels");
|
|
22038
21969
|
}
|
|
21970
|
+
if (opts.levels) {
|
|
21971
|
+
for (const [key, st] of Object.entries(opts.levels)) {
|
|
21972
|
+
const depth = Number(key);
|
|
21973
|
+
if (!Number.isInteger(depth) || depth < 0 || depth >= GONGMUN_LEVEL_COUNT) {
|
|
21974
|
+
throw new KordocError(`levels: depth must be an integer between 0 and ${GONGMUN_LEVEL_COUNT - 1} (got "${key}")`);
|
|
21975
|
+
}
|
|
21976
|
+
if (!st || typeof st !== "object") throw new KordocError(`levels.${key} must be an object {font, pt, bold}`);
|
|
21977
|
+
if (st.pt !== void 0) assertFiniteRange(`levels.${key}.pt`, st.pt, 6, 60);
|
|
21978
|
+
if (st.font !== void 0 && (typeof st.font !== "string" || !st.font.trim())) {
|
|
21979
|
+
throw new KordocError(`levels.${key}.font must be a non-empty string`);
|
|
21980
|
+
}
|
|
21981
|
+
}
|
|
21982
|
+
}
|
|
21983
|
+
}
|
|
21984
|
+
var GONGMUN_LEVEL_COUNT = 8;
|
|
21985
|
+
function resolveLevels(levels, bodyHeight) {
|
|
21986
|
+
if (!levels) return null;
|
|
21987
|
+
const out = {};
|
|
21988
|
+
for (const [key, st] of Object.entries(levels)) {
|
|
21989
|
+
if (st.font === void 0 && st.pt === void 0 && st.bold === void 0) continue;
|
|
21990
|
+
out[Number(key)] = {
|
|
21991
|
+
font: st.font?.trim() || null,
|
|
21992
|
+
height: st.pt !== void 0 ? Math.round(st.pt * 100) : bodyHeight,
|
|
21993
|
+
bold: !!st.bold
|
|
21994
|
+
};
|
|
21995
|
+
}
|
|
21996
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
21997
|
+
}
|
|
21998
|
+
function levelMarkerHeight(g, depth) {
|
|
21999
|
+
return g.levels?.[depth]?.height ?? g.bodyHeight;
|
|
22039
22000
|
}
|
|
22040
22001
|
function incompatibleGongmunWarnings(opts) {
|
|
22041
22002
|
const warns = [];
|
|
@@ -22071,29 +22032,37 @@ function resolveGongmun(opts) {
|
|
|
22071
22032
|
bodyHeight: Math.round(bodyPt * 100),
|
|
22072
22033
|
lineSpacing: opts.lineSpacing ?? d.lineSpacing,
|
|
22073
22034
|
numbering: opts.numbering ?? d.numbering,
|
|
22074
|
-
margins: opts.margins ?? (reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
|
|
22035
|
+
margins: opts.margins ?? (preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
|
|
22075
22036
|
centerTitle: opts.centerTitle ?? true,
|
|
22076
22037
|
autoFitMinRatio,
|
|
22077
22038
|
// 보도자료는 머리박스가 1페이지 최상단을 차지하는 서식이라 표지·목차와 양립 불가 —
|
|
22078
22039
|
// 켜면 머리박스가 표지에 얹히고 25pt 제목·부제가 유실된다 (docHead 프리셋 게이팅과 동일 관례)
|
|
22079
|
-
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,
|
|
22080
22046
|
toc: preset !== "press" && (opts.toc ?? gaejosik),
|
|
22081
22047
|
fonts: opts.fonts ?? {},
|
|
22082
22048
|
sizes: opts.sizes ?? {},
|
|
22049
|
+
levels: resolveLevels(opts.levels, Math.round(bodyPt * 100)),
|
|
22083
22050
|
// 쪽번호 — 보고서 계열 관행(실측: 2_보고서 양식·추진계획·공고문 전부 하단 중앙)
|
|
22084
22051
|
pageNumbers: opts.pageNumbers ?? (gaejosik || preset === "report" || preset === "plan"),
|
|
22085
22052
|
// 머리말·꼬리말 — 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
|
|
22086
22053
|
// 기안문 0(실결재 41/60건 h0/f0)
|
|
22087
|
-
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,
|
|
22088
22055
|
// "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
|
|
22089
22056
|
endMark: opts.endMark ?? preset === "official",
|
|
22090
22057
|
approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
|
|
22091
22058
|
// 본문 제목박스 — 실측(GT3·GT12): 목차 뒤 본문 시작에 제목 반복. 표지 켜진 개조식 기본
|
|
22092
22059
|
bodyTitleBox: opts.bodyTitleBox ?? (gaejosik && coverOn),
|
|
22093
22060
|
// h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
|
|
22094
|
-
|
|
22061
|
+
// v5 라운드 3: 보고서·계획서 기본 band(띠 표) — 서울 plan 7/19·교육청 7/18 실측, 실무자 요청
|
|
22062
|
+
h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "band" : preset === "notice" ? "number" : "none"),
|
|
22095
22063
|
// 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
|
|
22096
|
-
|
|
22064
|
+
// v5: 서울 실결재 ㅇ(이응) 지배 — 보고서·계획서·통지·보도자료 ㅇ, 중앙부처 개조식 양식만 ○
|
|
22065
|
+
bullet2: opts.bullet2 ?? (preset === "gaejosik" ? "\u25CB" : "\u3147"),
|
|
22097
22066
|
// 단일 형제 부호 생략 — 규정이지만 부호 없는 계단이 실무 눈에 어색 (실무자 QA)
|
|
22098
22067
|
suppressSingle: opts.suppressSingle ?? false,
|
|
22099
22068
|
docHead: preset === "official" && opts.docHead ? opts.docHead : null,
|
|
@@ -22138,10 +22107,10 @@ function markerWidth(marker, bodyHeight) {
|
|
|
22138
22107
|
for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
|
|
22139
22108
|
return Math.round(em / 1e3 * bodyHeight);
|
|
22140
22109
|
}
|
|
22141
|
-
function levelIndent(depth, bodyHeight, numbering, sizes = {}, bullet2 = "\u25CB", asteriskThird = false) {
|
|
22110
|
+
function levelIndent(depth, bodyHeight, numbering, sizes = {}, bullet2 = "\u25CB", asteriskThird = false, markerHeight = bodyHeight) {
|
|
22142
22111
|
if (numbering === "gaejosik") return gaejosikLevelIndent(depth, bodyHeight, sizes, bullet2);
|
|
22143
22112
|
const marker = numbering === "report" ? reportMarker(depth, bullet2, asteriskThird) : standardMarker(depth, 0);
|
|
22144
|
-
return { left: Math.round(depth * bodyHeight), indent: -markerWidth(marker,
|
|
22113
|
+
return { left: Math.round(depth * bodyHeight), indent: -markerWidth(marker, markerHeight) };
|
|
22145
22114
|
}
|
|
22146
22115
|
function computeSuppression(depths) {
|
|
22147
22116
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -22234,21 +22203,21 @@ function headingCharPrId(level) {
|
|
|
22234
22203
|
if (level === 3) return CHAR_H3;
|
|
22235
22204
|
return CHAR_H4;
|
|
22236
22205
|
}
|
|
22237
|
-
function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100) {
|
|
22206
|
+
function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100, restFont = fontId, spacingPct = 0) {
|
|
22238
22207
|
const boldAttr = bold ? ` bold="1"` : "";
|
|
22239
22208
|
const italicAttr = italic ? ` italic="1"` : "";
|
|
22240
22209
|
const effFont = fontId;
|
|
22241
22210
|
const boldEl = bold ? `<hh:bold/>` : "";
|
|
22242
22211
|
return ` <hh:charPr id="${id}" height="${height}" textColor="${escapeXml(textColor)}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
|
|
22243
|
-
<hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${
|
|
22212
|
+
<hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${restFont}" japanese="${restFont}" other="${restFont}" symbol="${restFont}" user="${restFont}"/>
|
|
22244
22213
|
<hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
|
|
22245
|
-
<hh:spacing hangul="
|
|
22214
|
+
<hh:spacing hangul="${spacingPct}" latin="${spacingPct}" hanja="${spacingPct}" japanese="0" other="0" symbol="0" user="0"/>
|
|
22246
22215
|
<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
|
|
22247
22216
|
<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>${boldEl}
|
|
22248
22217
|
</hh:charPr>`;
|
|
22249
22218
|
}
|
|
22250
22219
|
function paraPr(id, opts = {}) {
|
|
22251
|
-
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;
|
|
22252
22221
|
const breakNonLatin = keepWord ? "BREAK_WORD" : "KEEP_WORD";
|
|
22253
22222
|
const snapGrid = keepWord ? "0" : "1";
|
|
22254
22223
|
const heading = outlineLevel !== void 0 ? `<hh:heading type="OUTLINE" idRef="0" level="${outlineLevel}"/>` : `<hh:heading type="NONE" idRef="0" level="0"/>`;
|
|
@@ -22257,7 +22226,7 @@ function paraPr(id, opts = {}) {
|
|
|
22257
22226
|
${heading}
|
|
22258
22227
|
<hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="${keepWithNext ? 1 : 0}" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
|
|
22259
22228
|
<hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
|
|
22260
|
-
<hh:margin><hc:intent value="${indent}" unit="HWPUNIT"/><hc:left value="${left}" unit="HWPUNIT"/><hc:right value="
|
|
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>
|
|
22261
22230
|
<hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
|
|
22262
22231
|
<hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
|
|
22263
22232
|
</hh:paraPr>`;
|
|
@@ -22363,15 +22332,15 @@ function beginInlineDoc(footnotes) {
|
|
|
22363
22332
|
function endInlineDoc() {
|
|
22364
22333
|
inlineDoc = null;
|
|
22365
22334
|
}
|
|
22366
|
-
function extractFootnoteDefs(
|
|
22335
|
+
function extractFootnoteDefs(md) {
|
|
22367
22336
|
const defs = /* @__PURE__ */ new Map();
|
|
22368
|
-
const out =
|
|
22337
|
+
const out = md.replace(/\r\n?/g, "\n").split("\n").filter((line) => {
|
|
22369
22338
|
const m = /^\[\^([^\]\s]+)\]:\s?(.*)$/.exec(line);
|
|
22370
22339
|
if (!m) return true;
|
|
22371
22340
|
defs.set(m[1], m[2].trim());
|
|
22372
22341
|
return false;
|
|
22373
22342
|
}).join("\n");
|
|
22374
|
-
return { md: defs.size > 0 ? out :
|
|
22343
|
+
return { md: defs.size > 0 ? out : md, defs };
|
|
22375
22344
|
}
|
|
22376
22345
|
function buildPrvText(blocks) {
|
|
22377
22346
|
const lines = [];
|
|
@@ -22398,8 +22367,8 @@ function findMathDelim(s, from) {
|
|
|
22398
22367
|
}
|
|
22399
22368
|
return i;
|
|
22400
22369
|
}
|
|
22401
|
-
function parseMarkdownToBlocks(
|
|
22402
|
-
const lines =
|
|
22370
|
+
function parseMarkdownToBlocks(md) {
|
|
22371
|
+
const lines = md.replace(/\r\n?/g, "\n").split("\n");
|
|
22403
22372
|
const blocks = [];
|
|
22404
22373
|
let i = 0;
|
|
22405
22374
|
const listStack = [];
|
|
@@ -22816,9 +22785,9 @@ function assertSequentialIds(rows, label, startId) {
|
|
|
22816
22785
|
}
|
|
22817
22786
|
}
|
|
22818
22787
|
}
|
|
22819
|
-
function buildCharProperties(theme, gongmun, ratioVariants = [], extraCharPrXmls = []) {
|
|
22820
|
-
const measured = !!gongmun && usesReportFonts(gongmun.preset);
|
|
22821
|
-
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);
|
|
22822
22791
|
let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
|
|
22823
22792
|
if (gongmun) {
|
|
22824
22793
|
body = gongmun.bodyHeight;
|
|
@@ -22956,7 +22925,7 @@ ${base2.join("\n")}
|
|
|
22956
22925
|
];
|
|
22957
22926
|
const listAlign = gongmun.numbering === "gaejosik" || gongmun.numbering === "report" ? "LEFT" : "JUSTIFY";
|
|
22958
22927
|
for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
|
|
22959
|
-
const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, usesAsteriskThird(gongmun.preset));
|
|
22928
|
+
const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, usesAsteriskThird(gongmun.preset), levelMarkerHeight(gongmun, d));
|
|
22960
22929
|
const sectionGap = gongmun.numbering === "gaejosik" || gongmun.numbering === "report" ? gaejosikSpaceBefore(d, gongmun.bodyHeight) : 0;
|
|
22961
22930
|
const keepNext = (gongmun.numbering === "gaejosik" || gongmun.numbering === "report") && d === 0;
|
|
22962
22931
|
base.push(paraPr(GONGMUN_LIST_BASE + d, { align: listAlign, lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true, keepWithNext: keepNext }));
|
|
@@ -22997,6 +22966,24 @@ ${base2.join("\n")}
|
|
|
22997
22966
|
${base.join("\n")}
|
|
22998
22967
|
</hh:paraProperties>`;
|
|
22999
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
|
+
}
|
|
23000
22987
|
function buildNumberings() {
|
|
23001
22988
|
const heads = Array.from(
|
|
23002
22989
|
{ length: 7 },
|
|
@@ -23013,11 +23000,11 @@ function fontEntry(id, face, weight = 6) {
|
|
|
23013
23000
|
<hh:typeInfo familyType="FCAT_GOTHIC" weight="${weight}" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
|
|
23014
23001
|
</hh:font>`;
|
|
23015
23002
|
}
|
|
23016
|
-
function staticFontNext(gongmun) {
|
|
23017
|
-
return gongmun && needsGaejosikAssets(gongmun) ? 8 : 3;
|
|
23003
|
+
function staticFontNext(gongmun, v5 = false) {
|
|
23004
|
+
return !v5 && gongmun && needsGaejosikAssets(gongmun) ? 8 : 3;
|
|
23018
23005
|
}
|
|
23019
|
-
function buildFontFaces(gongmun, bodyFace, extraFonts = []) {
|
|
23020
|
-
if (gongmun && needsGaejosikAssets(gongmun)) {
|
|
23006
|
+
function buildFontFaces(gongmun, bodyFace, extraFonts = [], v5 = false) {
|
|
23007
|
+
if (!v5 && gongmun && needsGaejosikAssets(gongmun)) {
|
|
23021
23008
|
const ov = gongmun.fonts;
|
|
23022
23009
|
const bodyDefault = usesReportFonts(gongmun.preset) ? gongmun.preset !== "gaejosik" && gongmun.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD734\uBA3C\uBA85\uC870" : bodyFace;
|
|
23023
23010
|
const faces = [
|
|
@@ -23033,58 +23020,29 @@ function buildFontFaces(gongmun, bodyFace, extraFonts = []) {
|
|
|
23033
23020
|
if (faces.length !== staticFontNext(gongmun)) {
|
|
23034
23021
|
throw new Error(`fontface \uC815\uC801 \uAC1C\uC218 \uB4DC\uB9AC\uD504\uD2B8: ${faces.length} \u2260 staticFontNext ${staticFontNext(gongmun)}`);
|
|
23035
23022
|
}
|
|
23036
|
-
const
|
|
23037
|
-
const
|
|
23038
|
-
return `<hh:fontfaces itemCnt="${
|
|
23039
|
-
` +
|
|
23040
|
-
${
|
|
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}
|
|
23041
23028
|
</hh:fontface>`).join("\n") + `
|
|
23042
23029
|
</hh:fontfaces>`;
|
|
23043
23030
|
}
|
|
23044
|
-
const
|
|
23045
|
-
const
|
|
23046
|
-
|
|
23047
|
-
|
|
23048
|
-
|
|
23049
|
-
|
|
23050
|
-
${fontEntry(1, "\uD568\uCD08\uB86C\uB3CB\uC6C0")}
|
|
23051
|
-
${fontEntry(2, "HY\uACAC\uACE0\uB515", 9)}${extraBlock}
|
|
23052
|
-
</hh:fontface>
|
|
23053
|
-
<hh:fontface lang="LATIN" fontCnt="${3 + extraFonts.length}">
|
|
23054
|
-
<hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
|
|
23055
|
-
<hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
|
|
23056
|
-
</hh:font>
|
|
23057
|
-
<hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
|
|
23058
|
-
<hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
|
|
23059
|
-
</hh:font>
|
|
23060
|
-
<hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
|
|
23061
|
-
<hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
|
|
23062
|
-
</hh:font>${extraBlock}
|
|
23063
|
-
</hh:fontface>
|
|
23064
|
-
<hh:fontface lang="HANJA" fontCnt="1">
|
|
23065
|
-
${fontEntry(0, "\uD568\uCD08\uB86C\uBC14\uD0D5")}
|
|
23066
|
-
</hh:fontface>
|
|
23067
|
-
<hh:fontface lang="JAPANESE" fontCnt="1">
|
|
23068
|
-
${fontEntry(0, "\uAD74\uB9BC")}
|
|
23069
|
-
</hh:fontface>
|
|
23070
|
-
<hh:fontface lang="OTHER" fontCnt="1">
|
|
23071
|
-
${fontEntry(0, "\uAD74\uB9BC")}
|
|
23072
|
-
</hh:fontface>
|
|
23073
|
-
<hh:fontface lang="SYMBOL" fontCnt="1">
|
|
23074
|
-
${fontEntry(0, "Symbol")}
|
|
23075
|
-
</hh:fontface>
|
|
23076
|
-
<hh:fontface lang="USER" fontCnt="1">
|
|
23077
|
-
${fontEntry(0, "\uAD74\uB9BC")}
|
|
23078
|
-
</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") + `
|
|
23079
23037
|
</hh:fontfaces>`;
|
|
23080
23038
|
}
|
|
23081
|
-
function staticBorderFillItems(gongmun) {
|
|
23039
|
+
function staticBorderFillItems(gongmun, v5 = false) {
|
|
23082
23040
|
const thin = ["0.12 mm", "#000000"];
|
|
23083
23041
|
const items = [
|
|
23084
23042
|
borderFillEntry(1, {}),
|
|
23085
23043
|
borderFillEntry(2, { l: thin, r: thin, t: thin, b: thin })
|
|
23086
23044
|
];
|
|
23087
|
-
if (gongmun && needsGaejosikAssets(gongmun)) {
|
|
23045
|
+
if (!v5 && gongmun && needsGaejosikAssets(gongmun)) {
|
|
23088
23046
|
const c = GAEJOSIK_COLORS;
|
|
23089
23047
|
const edge = ["0.12 mm", c.border];
|
|
23090
23048
|
const grayLine = ["0.1 mm", c.titleLine];
|
|
@@ -23111,11 +23069,11 @@ function staticBorderFillItems(gongmun) {
|
|
|
23111
23069
|
}
|
|
23112
23070
|
return items;
|
|
23113
23071
|
}
|
|
23114
|
-
function staticBorderFillNext(gongmun) {
|
|
23115
|
-
return staticBorderFillItems(gongmun).length + 1;
|
|
23072
|
+
function staticBorderFillNext(gongmun, v5 = false) {
|
|
23073
|
+
return staticBorderFillItems(gongmun, v5).length + 1;
|
|
23116
23074
|
}
|
|
23117
|
-
function buildBorderFills(gongmun, extra = []) {
|
|
23118
|
-
const items = [...staticBorderFillItems(gongmun), ...extra];
|
|
23075
|
+
function buildBorderFills(gongmun, extra = [], v5 = false) {
|
|
23076
|
+
const items = [...staticBorderFillItems(gongmun, v5), ...extra];
|
|
23119
23077
|
assertSequentialIds(items, "borderFill", 1);
|
|
23120
23078
|
return `<hh:borderFills itemCnt="${items.length}">
|
|
23121
23079
|
${items.join("\n")}
|
|
@@ -23123,27 +23081,27 @@ ${items.join("\n")}
|
|
|
23123
23081
|
}
|
|
23124
23082
|
function buildStyles(gongmun) {
|
|
23125
23083
|
const items = [
|
|
23126
|
-
`<hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0"
|
|
23084
|
+
`<hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langID="1042" lockForm="0"/>`
|
|
23127
23085
|
];
|
|
23128
23086
|
if (gongmun) {
|
|
23129
23087
|
for (let lvl = 1; lvl <= 4; lvl++) {
|
|
23130
|
-
items.push(`<hh:style id="${lvl}" type="PARA" name="\uAC1C\uC694 ${lvl}" engName="Outline ${lvl}" paraPrIDRef="${lvl}" charPrIDRef="${4 + lvl}" nextStyleIDRef="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"/>`);
|
|
23131
23089
|
}
|
|
23132
23090
|
}
|
|
23133
23091
|
return `<hh:styles itemCnt="${items.length}">
|
|
23134
23092
|
${items.join("\n ")}
|
|
23135
23093
|
</hh:styles>`;
|
|
23136
23094
|
}
|
|
23137
|
-
function generateHeaderXml(theme, gongmun, ratioVariants = [], extraBorderFills = [], extraCharPrXmls = [], listIndentVariants = [], extraFonts = []) {
|
|
23138
|
-
const bodyFace = gongmun?.fonts.body ?? (gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5");
|
|
23139
|
-
const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants, extraCharPrXmls);
|
|
23140
|
-
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 : []);
|
|
23141
23099
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
|
23142
23100
|
<hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
|
|
23143
23101
|
<hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
|
|
23144
23102
|
<hh:refList>
|
|
23145
|
-
${buildFontFaces(gongmun, bodyFace, extraFonts)}
|
|
23146
|
-
${buildBorderFills(gongmun, extraBorderFills)}
|
|
23103
|
+
${buildFontFaces(gongmun, bodyFace, extraFonts, !!v5)}
|
|
23104
|
+
${buildBorderFills(gongmun, extraBorderFills, !!v5)}
|
|
23147
23105
|
${charPropsXml}
|
|
23148
23106
|
<hh:tabProperties itemCnt="0"/>
|
|
23149
23107
|
${buildNumberings()}
|
|
@@ -23385,6 +23343,7 @@ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
|
|
|
23385
23343
|
if (block.type === "list_item" && gongmunList.items.has(i)) {
|
|
23386
23344
|
const { marker, depth, indentVariant } = gongmunList.items.get(i);
|
|
23387
23345
|
if (gongmun.numbering === "gaejosik" && (depth === 0 || (block.text || "").trimStart().startsWith("\u203B"))) continue;
|
|
23346
|
+
if (gongmun.levels?.[depth]) continue;
|
|
23388
23347
|
const content = plainRenderText(block.text || "");
|
|
23389
23348
|
text = marker ? `${marker} ${content}` : content;
|
|
23390
23349
|
const li = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, usesAsteriskThird(gongmun.preset));
|
|
@@ -23450,8 +23409,9 @@ function precomputeGongmunList(blocks, gongmun) {
|
|
|
23450
23409
|
const variantIdx = /* @__PURE__ */ new Map();
|
|
23451
23410
|
const variantOf = (marker, depth) => {
|
|
23452
23411
|
if (!marker || gongmun.numbering !== "standard") return void 0;
|
|
23453
|
-
const
|
|
23454
|
-
const
|
|
23412
|
+
const h = levelMarkerHeight(gongmun, depth);
|
|
23413
|
+
const w = markerWidth(marker, h);
|
|
23414
|
+
const rep = -levelIndent(depth, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, usesAsteriskThird(gongmun.preset), h).indent;
|
|
23455
23415
|
if (w === rep) return void 0;
|
|
23456
23416
|
const key = `${depth}:${w}`;
|
|
23457
23417
|
let vi = variantIdx.get(key);
|
|
@@ -23545,14 +23505,14 @@ function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
|
23545
23505
|
const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g.totalW, h: g.titleH, paras: titlePara, name: "__kordoc_h1" });
|
|
23546
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 });
|
|
23547
23507
|
const tbl2 = table([topRow, titleRow, botRow], g.totalW, g.barH + g.titleH + g.botBarH, 3, 1, bodyWidth);
|
|
23548
|
-
const
|
|
23508
|
+
const host2 = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
|
|
23549
23509
|
const date = gongmun.cover?.date ?? formatGaejosikDate(/* @__PURE__ */ new Date());
|
|
23550
23510
|
const org = gongmun.cover?.org ?? "";
|
|
23551
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>`;
|
|
23552
23512
|
const subEmpty = emptyPara(GJ_PARA_COVER, false, GJ_CHAR_COVER_SUB);
|
|
23553
23513
|
return [
|
|
23554
23514
|
...Array(5).fill(emptyPara()),
|
|
23555
|
-
|
|
23515
|
+
host2,
|
|
23556
23516
|
...Array(5).fill(emptyPara()),
|
|
23557
23517
|
sub(date),
|
|
23558
23518
|
...org ? [...Array(4).fill(subEmpty), sub(org)] : []
|
|
@@ -23583,8 +23543,8 @@ function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
|
23583
23543
|
1,
|
|
23584
23544
|
bodyWidth
|
|
23585
23545
|
);
|
|
23586
|
-
const
|
|
23587
|
-
return [label, emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR),
|
|
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];
|
|
23588
23548
|
}
|
|
23589
23549
|
function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
23590
23550
|
const g = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
|
|
@@ -23663,8 +23623,8 @@ function levenshtein(a, b) {
|
|
|
23663
23623
|
}
|
|
23664
23624
|
|
|
23665
23625
|
// src/roundtrip/markdown-units.ts
|
|
23666
|
-
function splitMarkdownUnits(
|
|
23667
|
-
const lines =
|
|
23626
|
+
function splitMarkdownUnits(md) {
|
|
23627
|
+
const lines = md.split("\n");
|
|
23668
23628
|
const units = [];
|
|
23669
23629
|
let i = 0;
|
|
23670
23630
|
while (i < lines.length) {
|
|
@@ -24064,7 +24024,8 @@ function removeNestedTables(html) {
|
|
|
24064
24024
|
var THIN = ["0.12 mm", "#000000"];
|
|
24065
24025
|
var THICK = ["0.4 mm", "#000000"];
|
|
24066
24026
|
var DOUBLE = ["0.5 mm", "#000000", "DOUBLE_SLIM"];
|
|
24067
|
-
var
|
|
24027
|
+
var MID = ["0.15 mm", "#000000"];
|
|
24028
|
+
var EDGE = { thin: THIN, thick: THICK, double: DOUBLE, mid: MID };
|
|
24068
24029
|
function sideOf(kind) {
|
|
24069
24030
|
return kind === "none" ? void 0 : EDGE[kind];
|
|
24070
24031
|
}
|
|
@@ -24161,6 +24122,7 @@ function profileCharPrXml(id, def, namedFontId) {
|
|
|
24161
24122
|
let restFont = 0;
|
|
24162
24123
|
if (namedFontId != null) {
|
|
24163
24124
|
font = namedFontId;
|
|
24125
|
+
restFont = font;
|
|
24164
24126
|
} else {
|
|
24165
24127
|
const rawFont = def.fontRef_hangul != null ? parseInt(def.fontRef_hangul, 10) || 0 : 0;
|
|
24166
24128
|
font = rawFont >= 0 && rawFont <= LEGACY_PROFILE_FONT_MAX ? rawFont : 0;
|
|
@@ -24522,6 +24484,7 @@ function nextTableId() {
|
|
|
24522
24484
|
function resetTableIds() {
|
|
24523
24485
|
tableIdCounter = TABLE_ID_BASE;
|
|
24524
24486
|
}
|
|
24487
|
+
var curFace = "hcr";
|
|
24525
24488
|
var DATA_TABLE_INSET = 1800;
|
|
24526
24489
|
function anchorOfMarkdownCell(cell2) {
|
|
24527
24490
|
return normalizeAnchor(cell2.replace(/!\[[^\]]*\]\([^)]*\)/g, ""));
|
|
@@ -24541,7 +24504,7 @@ var CELL_PAD = 1200;
|
|
|
24541
24504
|
function cellContentWidth(text, charHeight) {
|
|
24542
24505
|
let max = 0;
|
|
24543
24506
|
for (const seg of text.replace(/!\[[^\]]*\]\([^)\s]+\)/g, "").replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
|
|
24544
|
-
const w = measureTextWidth(seg.trim(), charHeight, 100);
|
|
24507
|
+
const w = measureTextWidth(seg.trim(), charHeight, 100, { faceClass: curFace });
|
|
24545
24508
|
if (w > max) max = w;
|
|
24546
24509
|
}
|
|
24547
24510
|
return max;
|
|
@@ -24550,25 +24513,38 @@ function cellMinWordWidth(text, charHeight) {
|
|
|
24550
24513
|
let max = 0;
|
|
24551
24514
|
for (const seg of text.replace(/!\[[^\]]*\]\([^)\s]+\)/g, "").replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
|
|
24552
24515
|
for (const word of seg.trim().split(/\s+/)) {
|
|
24553
|
-
const w = measureTextWidth(word, charHeight, 100);
|
|
24516
|
+
const w = measureTextWidth(word, charHeight, 100, { faceClass: curFace });
|
|
24554
24517
|
if (w > max) max = w;
|
|
24555
24518
|
}
|
|
24556
24519
|
}
|
|
24557
24520
|
return max;
|
|
24558
24521
|
}
|
|
24559
|
-
|
|
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 = []) {
|
|
24560
24534
|
const colCnt = colMax.length;
|
|
24561
24535
|
const minW = Math.min(Math.max(2e3, Math.round(totalWidth * 0.06)), Math.floor(totalWidth / colCnt));
|
|
24562
24536
|
const cap = Math.round(totalWidth * 0.8);
|
|
24563
|
-
const
|
|
24564
|
-
const
|
|
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]));
|
|
24565
24541
|
const widths = Array(colCnt).fill(0);
|
|
24566
24542
|
const free = new Set(raw.map((_, i) => i));
|
|
24567
24543
|
let budget = totalWidth;
|
|
24568
24544
|
let sumFloorFree = floor.reduce((a, b) => a + b, 0);
|
|
24569
24545
|
for (const i of [...raw.keys()].sort((a, b) => raw[a] - raw[b])) {
|
|
24570
24546
|
if (free.size <= 1) break;
|
|
24571
|
-
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);
|
|
24572
24548
|
if (budget - fixed < sumFloorFree - floor[i]) break;
|
|
24573
24549
|
widths[i] = fixed;
|
|
24574
24550
|
free.delete(i);
|
|
@@ -24578,29 +24554,30 @@ function computeColWidths(colMax, totalWidth, colMinWord = []) {
|
|
|
24578
24554
|
const sumFreeRaw = [...free].reduce((a, i) => a + raw[i], 0);
|
|
24579
24555
|
if (budget > sumFreeRaw * 1.6) {
|
|
24580
24556
|
const sumRaw = raw.reduce((a, b) => a + b, 0);
|
|
24581
|
-
|
|
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)));
|
|
24582
24559
|
free.clear();
|
|
24583
24560
|
} else {
|
|
24584
24561
|
for (; ; ) {
|
|
24585
|
-
const sum2 = [...free].reduce((a, i) => a +
|
|
24586
|
-
const short = [...free].filter((i) =>
|
|
24562
|
+
const sum2 = [...free].reduce((a, i) => a + wgt[i], 0);
|
|
24563
|
+
const short = [...free].filter((i) => wgt[i] / sum2 * budget < floor[i]);
|
|
24587
24564
|
if (short.length === 0) break;
|
|
24588
24565
|
const shortSum = short.reduce((a, i) => a + floor[i], 0);
|
|
24589
|
-
const
|
|
24566
|
+
const scale2 = Math.min(1, (budget - (free.size - short.length) * minW) / shortSum);
|
|
24590
24567
|
for (const i of short) {
|
|
24591
|
-
widths[i] = Math.max(minW, Math.floor(floor[i] *
|
|
24568
|
+
widths[i] = Math.max(minW, Math.floor(floor[i] * scale2));
|
|
24592
24569
|
free.delete(i);
|
|
24593
24570
|
budget -= widths[i];
|
|
24594
24571
|
}
|
|
24595
24572
|
if (free.size === 0) break;
|
|
24596
24573
|
}
|
|
24597
|
-
const sum = [...free].reduce((a, i) => a +
|
|
24598
|
-
for (const i of free) widths[i] = Math.floor(
|
|
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));
|
|
24599
24576
|
}
|
|
24600
24577
|
let rem = totalWidth - widths.reduce((a, b) => a + b, 0);
|
|
24601
|
-
const order = [...raw.keys()].sort((a, b) =>
|
|
24578
|
+
const order = [...raw.keys()].sort((a, b) => wgt[b] - wgt[a]);
|
|
24602
24579
|
for (let k = 0, skipped = 0; rem > 0; k = (k + 1) % colCnt, rem--) {
|
|
24603
|
-
while (widths[order[k]] >=
|
|
24580
|
+
while (widths[order[k]] >= capOf[order[k]] && skipped < colCnt) {
|
|
24604
24581
|
skipped++;
|
|
24605
24582
|
k = (k + 1) % colCnt;
|
|
24606
24583
|
}
|
|
@@ -24624,13 +24601,26 @@ function estimateRowHeight(cells, widths, charHeight) {
|
|
|
24624
24601
|
const usable = Math.max((widths[c] ?? widths[widths.length - 1]) - CELL_PAD, 1e3);
|
|
24625
24602
|
let lines = 0;
|
|
24626
24603
|
for (const seg of cell2.replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
|
|
24627
|
-
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));
|
|
24628
24605
|
}
|
|
24629
24606
|
if (lines > maxLines) maxLines = lines;
|
|
24630
24607
|
});
|
|
24631
24608
|
return maxLines * Math.round(charHeight * 1.6) + 282;
|
|
24632
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
|
+
}
|
|
24633
24622
|
function generateTable(rows, theme, style = null, remap = null, seq = 0, images = null) {
|
|
24623
|
+
curFace = style?.faceClass ?? "hcr";
|
|
24634
24624
|
const rowCnt = rows.length;
|
|
24635
24625
|
const colCnt = Math.max(...rows.map((r) => r.length), 1);
|
|
24636
24626
|
const reg = style?.bfRegistry ?? null;
|
|
@@ -24648,8 +24638,9 @@ function generateTable(rows, theme, style = null, remap = null, seq = 0, images
|
|
|
24648
24638
|
const mw = cellMinWordWidth(cell2, measureH);
|
|
24649
24639
|
if (mw > colMinWord[c]) colMinWord[c] = mw;
|
|
24650
24640
|
}));
|
|
24651
|
-
const
|
|
24652
|
-
const
|
|
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);
|
|
24653
24644
|
const labelCol0 = !!reg && colCnt === 2 && colCentered[0] && rows.every((r) => (r[0] ?? "").replace(/\*\*|__|`/g, "").length <= 12);
|
|
24654
24645
|
const tblId = nextTableId();
|
|
24655
24646
|
const useHeaderStyle = !style && (theme.tableHeader !== theme.body || theme.tableHeaderBold);
|
|
@@ -24768,8 +24759,11 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
|
|
|
24768
24759
|
if (dc === 0 && mw > colMinWord[c]) colMinWord[c] = mw;
|
|
24769
24760
|
}
|
|
24770
24761
|
});
|
|
24771
|
-
const
|
|
24772
|
-
const
|
|
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);
|
|
24773
24767
|
const cellH = style ? Math.round(measureH * 1.6) + 282 : 1500;
|
|
24774
24768
|
const tblW = colWidths.reduce((a, b) => a + b, 0);
|
|
24775
24769
|
const tblId = nextTableId();
|
|
@@ -24881,7 +24875,7 @@ function tbl(rows, w, h, cols) {
|
|
|
24881
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>`;
|
|
24882
24876
|
}
|
|
24883
24877
|
function tc(opts) {
|
|
24884
|
-
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>`;
|
|
24885
24879
|
}
|
|
24886
24880
|
function para(text, paraPrId, charPrId) {
|
|
24887
24881
|
return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
|
|
@@ -24952,48 +24946,12 @@ function docframeCharPrXmls(base, richAssets) {
|
|
|
24952
24946
|
charPr(ids.pressContact, 1e3, false, false, body)
|
|
24953
24947
|
];
|
|
24954
24948
|
}
|
|
24955
|
-
function labeled(label, value, paraPrId) {
|
|
24956
|
-
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>`;
|
|
24957
|
-
}
|
|
24958
24949
|
function blank() {
|
|
24959
24950
|
return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}"><hp:t></hp:t></hp:run></hp:p>`;
|
|
24960
24951
|
}
|
|
24961
|
-
function buildDocHead(g, ids) {
|
|
24962
|
-
const h = g.docHead;
|
|
24963
|
-
const out = [];
|
|
24964
|
-
if (h.org) {
|
|
24965
|
-
out.push(para(h.org, GONGMUN_CENTER, ids.org), blank());
|
|
24966
|
-
}
|
|
24967
|
-
if (h.to !== void 0) out.push(labeled("\uC218\uC2E0 ", h.to, PARA_NORMAL));
|
|
24968
|
-
out.push(para(h.via ? `(\uACBD\uC720) ${h.via}` : "(\uACBD\uC720)", PARA_NORMAL, CHAR_NORMAL));
|
|
24969
|
-
if (h.title !== void 0) out.push(labeled("\uC81C\uBAA9 ", h.title, PARA_NORMAL));
|
|
24970
|
-
out.push(blank());
|
|
24971
|
-
return out;
|
|
24972
|
-
}
|
|
24973
|
-
function footSep(g) {
|
|
24974
|
-
const bodyMm = 210 - g.margins.left - g.margins.right;
|
|
24975
|
-
return "\u2500".repeat(Math.max(10, Math.round(bodyMm * (46 / 175))));
|
|
24976
|
-
}
|
|
24977
24952
|
function isInternalApproval(to) {
|
|
24978
24953
|
return !!to && /^\s*내\s*부\s*결\s*재/.test(to);
|
|
24979
24954
|
}
|
|
24980
|
-
function buildDocFoot(g, ids) {
|
|
24981
|
-
const f = g.docFoot;
|
|
24982
|
-
const out = [blank()];
|
|
24983
|
-
if (f.sender && !isInternalApproval(g.docHead?.to)) {
|
|
24984
|
-
out.push(para(f.sender, GONGMUN_CENTER, ids.sender), blank());
|
|
24985
|
-
}
|
|
24986
|
-
const foot = (text) => para(text, GONGMUN_TBL_LEFT, ids.foot);
|
|
24987
|
-
out.push(foot(footSep(g)));
|
|
24988
|
-
if (f.drafter || f.reviewer || f.approver)
|
|
24989
|
-
out.push(foot(`\uAE30\uC548\uC790 ${f.drafter ?? ""} \uAC80\uD1A0\uC790 ${f.reviewer ?? ""} \uACB0\uC7AC\uAD8C\uC790 ${f.approver ?? ""}`));
|
|
24990
|
-
if (f.cooperator) out.push(foot(`\uD611\uC870\uC790 ${f.cooperator}`));
|
|
24991
|
-
if (f.docNum || f.receive) out.push(foot(`\uC2DC\uD589 ${f.docNum ?? ""} \uC811\uC218 ${f.receive ?? ""}`));
|
|
24992
|
-
if (f.address || f.site) out.push(foot(`${f.address ?? ""} / ${f.site ?? ""}`));
|
|
24993
|
-
if (f.phone || f.fax || f.email || f.disclosure)
|
|
24994
|
-
out.push(foot(`\uC804\uD654 ${f.phone ?? ""} \uC804\uC1A1 ${f.fax ?? ""} / ${f.email ?? ""} / ${f.disclosure ?? ""}`));
|
|
24995
|
-
return out;
|
|
24996
|
-
}
|
|
24997
24955
|
function buildReportInfo(g, ids) {
|
|
24998
24956
|
return para(g.reportInfo, GONGMUN_RIGHT, ids.small);
|
|
24999
24957
|
}
|
|
@@ -25452,9 +25410,6 @@ function buildPreamble(blocks, ctx) {
|
|
|
25452
25410
|
if (gongmun?.approval && bfReg) {
|
|
25453
25411
|
preamble.push(buildApprovalTable(gongmun.approval, bfReg, richAssets ? GJ_CHAR_APPROVAL : GONGMUN_APPROVAL_CHAR));
|
|
25454
25412
|
}
|
|
25455
|
-
if (gongmun?.docHead && dfIds) {
|
|
25456
|
-
preamble.push(...buildDocHead(gongmun, dfIds));
|
|
25457
|
-
}
|
|
25458
25413
|
if (gongmun?.noticeHead && dfIds) {
|
|
25459
25414
|
preamble.push(...buildNoticeHead(gongmun));
|
|
25460
25415
|
}
|
|
@@ -25630,6 +25585,11 @@ function renderListItem(block, blockIdx, ctx) {
|
|
|
25630
25585
|
mapId = void 0;
|
|
25631
25586
|
}
|
|
25632
25587
|
}
|
|
25588
|
+
const lv = ctx.levelIds?.[depth];
|
|
25589
|
+
if (lv) {
|
|
25590
|
+
listCharPr = lv.normal;
|
|
25591
|
+
mapId = (id) => id === CHAR_BOLD ? lv.bold : id === CHAR_NORMAL ? lv.normal : id;
|
|
25592
|
+
}
|
|
25633
25593
|
return generateParagraph(text, listParaPr, listCharPr, mapId);
|
|
25634
25594
|
}
|
|
25635
25595
|
const indent = block.indent || 0;
|
|
@@ -25670,8 +25630,8 @@ function renderHtmlTable(block, ctx) {
|
|
|
25670
25630
|
ctx.opener.emitCarrier(ctx.paraXmls);
|
|
25671
25631
|
return `<hp:p paraPrIDRef="${tableStyle && bfReg ? GONGMUN_RIGHT : 0}" styleIDRef="0"><hp:run charPrIDRef="0">${tbl2}</hp:run></hp:p>`;
|
|
25672
25632
|
}
|
|
25673
|
-
const
|
|
25674
|
-
return
|
|
25633
|
+
const plain3 = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
25634
|
+
return plain3 ? generateParagraph(plain3) : "";
|
|
25675
25635
|
}
|
|
25676
25636
|
function appendPostamble(blocks, ctx) {
|
|
25677
25637
|
const { gongmun, dfIds, bfReg, tableStyle, paraXmls } = ctx;
|
|
@@ -25683,14 +25643,11 @@ function appendPostamble(blocks, ctx) {
|
|
|
25683
25643
|
const lastText = lastBlock && (lastBlock.type === "paragraph" || lastBlock.type === "list_item") ? lastBlock.text || "" : "";
|
|
25684
25644
|
if (!hasEndMark(lastText)) paraXmls.push(buildEndMark());
|
|
25685
25645
|
}
|
|
25686
|
-
if (gongmun?.docFoot && dfIds) {
|
|
25687
|
-
paraXmls.push(...buildDocFoot(gongmun, dfIds));
|
|
25688
|
-
}
|
|
25689
25646
|
if (gongmun?.press && dfIds && bfReg && tableStyle) {
|
|
25690
25647
|
paraXmls.push(...buildPressContact(gongmun, dfIds, tableStyle.totalWidth, bfReg));
|
|
25691
25648
|
}
|
|
25692
25649
|
}
|
|
25693
|
-
function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null, chartParts = null, bfReg = null, remap = null, dfIds = null, images = null, page = null) {
|
|
25650
|
+
function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null, chartParts = null, bfReg = null, remap = null, dfIds = null, images = null, page = null, levelIds = null) {
|
|
25694
25651
|
resetTableIds();
|
|
25695
25652
|
resetGjTableIds();
|
|
25696
25653
|
resetExtraTableIds();
|
|
@@ -25706,6 +25663,7 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
|
|
|
25706
25663
|
remap,
|
|
25707
25664
|
dfIds,
|
|
25708
25665
|
images,
|
|
25666
|
+
levelIds,
|
|
25709
25667
|
gaejosik: gongmun?.preset === "gaejosik",
|
|
25710
25668
|
measured,
|
|
25711
25669
|
richAssets,
|
|
@@ -25810,17 +25768,1130 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
|
|
|
25810
25768
|
</hs:sec>`;
|
|
25811
25769
|
}
|
|
25812
25770
|
|
|
25771
|
+
// src/hwpx/gen-levels.ts
|
|
25772
|
+
function levelDepths(g) {
|
|
25773
|
+
return g.levels ? Object.keys(g.levels).map(Number).sort((a, b) => a - b) : [];
|
|
25774
|
+
}
|
|
25775
|
+
function levelFontFaces(g) {
|
|
25776
|
+
const out = [];
|
|
25777
|
+
for (const d of levelDepths(g)) {
|
|
25778
|
+
const f = g.levels[d].font;
|
|
25779
|
+
if (f && !out.includes(f)) out.push(f);
|
|
25780
|
+
}
|
|
25781
|
+
return out;
|
|
25782
|
+
}
|
|
25783
|
+
function levelCharIds(g, base) {
|
|
25784
|
+
const depths = levelDepths(g);
|
|
25785
|
+
if (depths.length === 0) return null;
|
|
25786
|
+
const ids = {};
|
|
25787
|
+
depths.forEach((d, i) => {
|
|
25788
|
+
ids[d] = { normal: base + i * 2, bold: base + i * 2 + 1 };
|
|
25789
|
+
});
|
|
25790
|
+
return ids;
|
|
25791
|
+
}
|
|
25792
|
+
function levelCharPrXmls(g, base, fontBase, bodyFontId) {
|
|
25793
|
+
const ids = levelCharIds(g, base);
|
|
25794
|
+
if (!ids) return [];
|
|
25795
|
+
const faces = levelFontFaces(g);
|
|
25796
|
+
const out = [];
|
|
25797
|
+
for (const d of levelDepths(g)) {
|
|
25798
|
+
const st = g.levels[d];
|
|
25799
|
+
const fontId = st.font ? fontBase + faces.indexOf(st.font) : 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
|
+
}
|
|
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);
|
|
26204
|
+
return out;
|
|
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
|
+
}
|
|
26847
|
+
|
|
25813
26848
|
// src/hwpx/generator.ts
|
|
25814
26849
|
async function markdownToHwpx(markdown, options) {
|
|
25815
26850
|
const theme = resolveTheme(options?.theme);
|
|
25816
26851
|
const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
|
|
25817
26852
|
const page = resolvePage(options?.page);
|
|
25818
|
-
const { md
|
|
26853
|
+
const { md, defs } = extractFootnoteDefs(markdown);
|
|
25819
26854
|
beginInlineDoc(defs);
|
|
25820
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
|
+
}
|
|
25821
26892
|
const measured = !!gongmun && usesReportFonts(gongmun.preset);
|
|
25822
26893
|
const richAssets = !!gongmun && needsGaejosikAssets(gongmun);
|
|
25823
|
-
const blocks = parseMarkdownToBlocks(
|
|
26894
|
+
const blocks = parseMarkdownToBlocks(md);
|
|
25824
26895
|
const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
|
|
25825
26896
|
const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
|
|
25826
26897
|
const staticBfEnd = staticBorderFillNext(gongmun);
|
|
@@ -25829,10 +26900,15 @@ async function markdownToHwpx(markdown, options) {
|
|
|
25829
26900
|
const dfActive = !!gongmun && docframeActive(gongmun);
|
|
25830
26901
|
const dfBase = charVariantBase(richAssets, !!gongmun) + (fit?.variants?.length ?? 0) * 4 + (remap?.charPrXmls.length ?? 0);
|
|
25831
26902
|
const dfIds = dfActive ? docframeIds(dfBase) : null;
|
|
26903
|
+
const dfXmls = dfActive ? docframeCharPrXmls(dfBase, richAssets) : [];
|
|
26904
|
+
const lvBase = dfBase + dfXmls.length;
|
|
26905
|
+
const lvIds = gongmun ? levelCharIds(gongmun, lvBase) : null;
|
|
26906
|
+
const lvFonts = gongmun ? levelFontFaces(gongmun) : [];
|
|
26907
|
+
const lvXmls = gongmun ? levelCharPrXmls(gongmun, lvBase, staticFontNext(gongmun) + (remap?.fontFaces.length ?? 0), richAssets ? 4 : 0) : [];
|
|
25832
26908
|
const chartParts = [];
|
|
25833
26909
|
const supplied = options?.images ? new Map(Object.entries(options.images).map(([k, v]) => [k, v instanceof Uint8Array ? v : new Uint8Array(v)])) : void 0;
|
|
25834
26910
|
const images = new ImageRegistry(supplied);
|
|
25835
|
-
const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit, chartParts, bfReg, remap, dfIds, images, page);
|
|
26911
|
+
const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit, chartParts, bfReg, remap, dfIds, images, page, lvIds);
|
|
25836
26912
|
if (remap && remap.tables.length > 0) {
|
|
25837
26913
|
const unused = remap.tables.filter((t) => !t.used).length;
|
|
25838
26914
|
if (unused === remap.tables.length) {
|
|
@@ -25840,7 +26916,7 @@ async function markdownToHwpx(markdown, options) {
|
|
|
25840
26916
|
}
|
|
25841
26917
|
}
|
|
25842
26918
|
const extraBorderFills = [...remap?.borderFillXmls ?? [], ...bfReg?.emit() ?? []];
|
|
25843
|
-
const zip = new
|
|
26919
|
+
const zip = new JSZip7();
|
|
25844
26920
|
zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
|
|
25845
26921
|
zip.file("META-INF/container.xml", generateContainerXml());
|
|
25846
26922
|
zip.file("Contents/content.hpf", generateManifest(chartParts, images.manifestItems(), gongmun ? "gongmun" : "default"));
|
|
@@ -25850,9 +26926,9 @@ async function markdownToHwpx(markdown, options) {
|
|
|
25850
26926
|
gongmun,
|
|
25851
26927
|
fit?.variants ?? [],
|
|
25852
26928
|
extraBorderFills,
|
|
25853
|
-
[...remap?.charPrXmls ?? [], ...
|
|
26929
|
+
[...remap?.charPrXmls ?? [], ...dfXmls, ...lvXmls],
|
|
25854
26930
|
gongmunList?.indentVariants ?? [],
|
|
25855
|
-
remap?.fontFaces ?? []
|
|
26931
|
+
[...remap?.fontFaces ?? [], ...lvFonts]
|
|
25856
26932
|
));
|
|
25857
26933
|
zip.file("Contents/section0.xml", sectionXml);
|
|
25858
26934
|
for (const part of chartParts) zip.file(part.name, part.xml);
|
|
@@ -26053,7 +27129,7 @@ function readBuiltinTemplateSample(t) {
|
|
|
26053
27129
|
}
|
|
26054
27130
|
|
|
26055
27131
|
// src/hwpx/extract-profile.ts
|
|
26056
|
-
import
|
|
27132
|
+
import JSZip8 from "jszip";
|
|
26057
27133
|
function elemsByLocal(root, name) {
|
|
26058
27134
|
const all = root.getElementsByTagName("*");
|
|
26059
27135
|
const out = [];
|
|
@@ -26256,7 +27332,7 @@ function pick(map, keys) {
|
|
|
26256
27332
|
}
|
|
26257
27333
|
async function hwpxToProfile(input) {
|
|
26258
27334
|
const buf = input instanceof ArrayBuffer ? input : toArrayBuffer(input);
|
|
26259
|
-
const zip = await
|
|
27335
|
+
const zip = await JSZip8.loadAsync(buf);
|
|
26260
27336
|
const parser = createXmlParser();
|
|
26261
27337
|
const headerFile = zip.file("Contents/header.xml") ?? zip.file(/[Hh]eader\.xml$/)?.[0];
|
|
26262
27338
|
let headerXml = "<root/>";
|
|
@@ -26505,10 +27581,12 @@ var DISCRIM_FIXES = [
|
|
|
26505
27581
|
var DISCRIM_RE = new RegExp(DISCRIM_FIXES.map(([w]) => w).sort((a, b) => b.length - a.length).join("|"), "g");
|
|
26506
27582
|
var RULES = [
|
|
26507
27583
|
// 날짜 ─ 온점 뒤 한 칸, 0 패딩 금지, 연도 4자리, 끝 마침표
|
|
27584
|
+
// 법령 연혁 표기 `<개정 2012.2.14>`·`삭제 <2016.2.29.>`·`[시행일:2017.9.8.]` 는 법제처 정본 형식(별지서식 표제) —
|
|
27585
|
+
// 편람 날짜 규칙 대상이 아니다 (v4.12.3: 서식 595건 DATE 156건 전부 이 꼴, 기안문 0건)
|
|
26508
27586
|
{
|
|
26509
27587
|
code: "DATE_NO_SPACE",
|
|
26510
27588
|
severity: "error",
|
|
26511
|
-
pattern:
|
|
27589
|
+
pattern: /(?<!<(?:개정|신설|전문개정|전부개정|일부개정|제정)\s*)(?<!삭제\s*<)(?<!시행일\s*:\s*)\b\d{4}\.\d{1,2}\.\d{1,2}\.?/g,
|
|
26512
27590
|
message: "\uB0A0\uC9DC \uC628\uC810 \uB4A4\uC5D0 \uD55C \uCE78\uC529 \uB744\uC6CC\uC57C \uD568",
|
|
26513
27591
|
suggest: "\uC608) 2025. 1. 6."
|
|
26514
27592
|
},
|
|
@@ -26533,6 +27611,16 @@ var RULES = [
|
|
|
26533
27611
|
message: "\uB0A0\uC9DC\uC758 '\uC77C' \uB2E4\uC74C\uC5D0 \uB9C8\uCE68\uD45C(.)\uB97C \uCC0D\uC5B4\uC57C \uD568",
|
|
26534
27612
|
suggest: "\uC608) 2025. 1. 6."
|
|
26535
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
|
+
},
|
|
26536
27624
|
// 시간 ─ 24시각제, 쌍점 붙여쓰기
|
|
26537
27625
|
{
|
|
26538
27626
|
code: "TIME_AMPM",
|
|
@@ -26594,10 +27682,15 @@ var RULES = [
|
|
|
26594
27682
|
suggest: "\uC608) \uC5C5\uBB34 \uD611\uC57D(MOU)"
|
|
26595
27683
|
},
|
|
26596
27684
|
// 쌍점 — URL(https:// 등)·시각(13:20)은 제외
|
|
27685
|
+
// 편람: 쌍점은 앞말에 붙이고 뒤는 1타(원장: 김갑동). 표 셀은 제외(skipTable) — 법정 별지서식
|
|
27686
|
+
// 자체가 "성 명 :"·"주 소 :" 라벨 셀을 쓰므로 서식 라벨은 이 규칙의 대상이 아니다(v4.12.3 실측:
|
|
27687
|
+
// 서식 595건의 표 셀 801건 전부 라벨, 기안문 206건은 본문 414건이 "일 시 : 값" 꼴 — 편람상 위반).
|
|
27688
|
+
// 뒤 글자에서 `<`(HTML 태그)·`*`·`_`(마크다운 강조 닫힘)는 제외 — "성명:</td>"·"과정:** 붙임" 오탐
|
|
26597
27689
|
{
|
|
26598
27690
|
code: "COLON_SPACE",
|
|
26599
27691
|
severity: "warning",
|
|
26600
|
-
|
|
27692
|
+
skipTable: true,
|
|
27693
|
+
pattern: /\S\s+:(?!\/\/)|\S:(?!\/\/)[^\s\d<*_]/g,
|
|
26601
27694
|
message: "\uC30D\uC810\uC740 \uC55E\uB9D0\uC5D0 \uBD99\uC774\uACE0 \uB4A4\uB294 \uD55C \uCE78 \uB744\uC6C0",
|
|
26602
27695
|
suggest: "\uC608) \uC6D0\uC7A5: \uAE40\uAC11\uB3D9"
|
|
26603
27696
|
},
|
|
@@ -26677,7 +27770,9 @@ function lintGongmunText(text, opts) {
|
|
|
26677
27770
|
continue;
|
|
26678
27771
|
}
|
|
26679
27772
|
if (fenceMarker !== null) continue;
|
|
27773
|
+
const tableLine = /^\s*\|/.test(line) || /<t[dhr][\s>]/i.test(line);
|
|
26680
27774
|
for (const r of RULES) {
|
|
27775
|
+
if (r.skipTable && tableLine) continue;
|
|
26681
27776
|
r.pattern.lastIndex = 0;
|
|
26682
27777
|
for (const m of line.matchAll(r.pattern)) {
|
|
26683
27778
|
findings.push({
|
|
@@ -26704,6 +27799,12 @@ function lintGongmunText(text, opts) {
|
|
|
26704
27799
|
return findings;
|
|
26705
27800
|
}
|
|
26706
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
|
+
}
|
|
26707
27808
|
const table2 = code === "LOANWORD_ERROR" ? LOANWORD_FIXES : code === "DISCRIMINATORY_TERM" ? DISCRIM_FIXES : null;
|
|
26708
27809
|
if (!table2) return void 0;
|
|
26709
27810
|
const hit = table2.find(([w]) => w === match.trim());
|
|
@@ -26740,7 +27841,7 @@ function classify(raw) {
|
|
|
26740
27841
|
if (li) return { kind: indent >= 2 ? "sub" : "item", body: li[1].trim() };
|
|
26741
27842
|
return { kind: "para", body: t };
|
|
26742
27843
|
}
|
|
26743
|
-
function
|
|
27844
|
+
function plain2(s) {
|
|
26744
27845
|
return s.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/(\*\*|__|~~|`)/g, "").trim();
|
|
26745
27846
|
}
|
|
26746
27847
|
function stripQuoted(s) {
|
|
@@ -26843,11 +27944,11 @@ function lintMuncheText(text) {
|
|
|
26843
27944
|
continue;
|
|
26844
27945
|
}
|
|
26845
27946
|
if (c.kind === "lead") {
|
|
26846
|
-
leadBuf.push({ line: no, body:
|
|
27947
|
+
leadBuf.push({ line: no, body: plain2(c.body) });
|
|
26847
27948
|
continue;
|
|
26848
27949
|
}
|
|
26849
27950
|
flushLead();
|
|
26850
|
-
const b =
|
|
27951
|
+
const b = plain2(c.body);
|
|
26851
27952
|
if (!b) continue;
|
|
26852
27953
|
const q = stripQuoted(b);
|
|
26853
27954
|
if (c.kind === "item" || c.kind === "sub" || c.kind === "concl" || c.kind === "para") {
|
|
@@ -26946,7 +28047,7 @@ function muncheLintWarnings(text, limit = 10) {
|
|
|
26946
28047
|
}
|
|
26947
28048
|
|
|
26948
28049
|
// src/roundtrip/patcher.ts
|
|
26949
|
-
import
|
|
28050
|
+
import JSZip9 from "jszip";
|
|
26950
28051
|
|
|
26951
28052
|
// src/roundtrip/table-rows.ts
|
|
26952
28053
|
var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
|
|
@@ -27260,7 +28361,7 @@ function gfmRenderStable(editedRows, cols) {
|
|
|
27260
28361
|
hasHeader: editedRows.length > 1,
|
|
27261
28362
|
cells: editedRows.map((row) => {
|
|
27262
28363
|
const padded = row.length < cols ? [...row, ...Array(cols - row.length).fill("")] : row;
|
|
27263
|
-
return padded.map((
|
|
28364
|
+
return padded.map((md) => ({ text: unescapeGfmCell(md), colSpan: 1, rowSpan: 1 }));
|
|
27264
28365
|
})
|
|
27265
28366
|
};
|
|
27266
28367
|
const replica = replicateGfmTable(sim);
|
|
@@ -27596,7 +28697,7 @@ async function patchHwpx(original, editedMarkdown, options) {
|
|
|
27596
28697
|
}
|
|
27597
28698
|
let zip;
|
|
27598
28699
|
try {
|
|
27599
|
-
zip = await
|
|
28700
|
+
zip = await JSZip9.loadAsync(original);
|
|
27600
28701
|
} catch {
|
|
27601
28702
|
return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
|
|
27602
28703
|
}
|
|
@@ -28871,8 +29972,8 @@ function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before,
|
|
|
28871
29972
|
}
|
|
28872
29973
|
return staged > 0 ? 1 : 0;
|
|
28873
29974
|
}
|
|
28874
|
-
function gfmCellToPlain(
|
|
28875
|
-
let t =
|
|
29975
|
+
function gfmCellToPlain(md) {
|
|
29976
|
+
let t = md.trim();
|
|
28876
29977
|
const bold = t.match(/^\*\*([\s\S]+)\*\*$/);
|
|
28877
29978
|
if (bold) t = bold[1];
|
|
28878
29979
|
if (/[*`]|!\[|\]\(/.test(t)) return null;
|
|
@@ -29093,7 +30194,7 @@ function stageParaPatch(scan, para2, newPlain, skip) {
|
|
|
29093
30194
|
}
|
|
29094
30195
|
|
|
29095
30196
|
// src/validate.ts
|
|
29096
|
-
import
|
|
30197
|
+
import JSZip10 from "jszip";
|
|
29097
30198
|
import { DOMParser as DOMParser4 } from "@xmldom/xmldom";
|
|
29098
30199
|
var REQUIRED_FILES = [
|
|
29099
30200
|
"mimetype",
|
|
@@ -29111,7 +30212,7 @@ async function validateHwpx(buffer) {
|
|
|
29111
30212
|
const issues = [];
|
|
29112
30213
|
let zip;
|
|
29113
30214
|
try {
|
|
29114
|
-
zip = await
|
|
30215
|
+
zip = await JSZip10.loadAsync(buffer);
|
|
29115
30216
|
} catch (err) {
|
|
29116
30217
|
return {
|
|
29117
30218
|
ok: false,
|
|
@@ -29179,10 +30280,10 @@ async function validateHwpx(buffer) {
|
|
|
29179
30280
|
}
|
|
29180
30281
|
|
|
29181
30282
|
// src/roundtrip/session.ts
|
|
29182
|
-
import
|
|
30283
|
+
import JSZip11 from "jszip";
|
|
29183
30284
|
async function buildState(bytes) {
|
|
29184
30285
|
const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
|
|
29185
|
-
const zip = await
|
|
30286
|
+
const zip = await JSZip11.loadAsync(bytes);
|
|
29186
30287
|
const sectionPaths = await resolveSectionEntryNames(zip);
|
|
29187
30288
|
if (sectionPaths.length === 0) {
|
|
29188
30289
|
throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -29523,159 +30624,6 @@ async function patchHwpxBlocks(original, edits, options) {
|
|
|
29523
30624
|
return session.patchBlocks(edits, options);
|
|
29524
30625
|
}
|
|
29525
30626
|
|
|
29526
|
-
// src/print/renderer.ts
|
|
29527
|
-
import { existsSync as existsSync2 } from "fs";
|
|
29528
|
-
import MarkdownIt from "markdown-it";
|
|
29529
|
-
var PRESETS = {
|
|
29530
|
-
default: `
|
|
29531
|
-
@page { size: A4; margin: 20mm; }
|
|
29532
|
-
body { font-family: 'Pretendard', 'Malgun Gothic', '\uB9D1\uC740 \uACE0\uB515', sans-serif; font-size: 11pt; line-height: 1.6; color: #111; }
|
|
29533
|
-
h1 { font-size: 20pt; margin: 1em 0 0.5em; }
|
|
29534
|
-
h2 { font-size: 16pt; margin: 1em 0 0.4em; }
|
|
29535
|
-
h3 { font-size: 13pt; margin: 0.8em 0 0.3em; }
|
|
29536
|
-
p { margin: 0.4em 0; }
|
|
29537
|
-
table { border-collapse: collapse; margin: 0.6em 0; width: 100%; }
|
|
29538
|
-
th, td { border: 1px solid #555; padding: 4px 8px; text-align: left; vertical-align: top; }
|
|
29539
|
-
th { background: #f0f0f0; }
|
|
29540
|
-
code { background: #f5f5f5; padding: 1px 4px; border-radius: 2px; font-family: 'D2Coding', Consolas, monospace; }
|
|
29541
|
-
pre { background: #f5f5f5; padding: 8px; border-radius: 4px; overflow-x: auto; }
|
|
29542
|
-
blockquote { border-left: 3px solid #ccc; padding-left: 12px; color: #555; margin: 0.6em 0; }
|
|
29543
|
-
img { max-width: 100%; }
|
|
29544
|
-
`,
|
|
29545
|
-
"gov-formal": `
|
|
29546
|
-
@page { size: A4; margin: 25mm 20mm; }
|
|
29547
|
-
body { font-family: '\uD568\uCD08\uB86C\uBC14\uD0D5', 'HCR Batang', 'Batang', 'Malgun Gothic', serif; font-size: 11pt; line-height: 1.7; color: #000; }
|
|
29548
|
-
h1 { font-size: 18pt; text-align: center; margin: 0.5em 0 1em; letter-spacing: 0.05em; }
|
|
29549
|
-
h2 { font-size: 14pt; margin: 1em 0 0.4em; border-bottom: 1px solid #999; padding-bottom: 2px; }
|
|
29550
|
-
h3 { font-size: 12pt; margin: 0.8em 0 0.3em; }
|
|
29551
|
-
p { margin: 0.3em 0; text-indent: 1em; }
|
|
29552
|
-
table { border-collapse: collapse; margin: 0.8em 0; width: 100%; }
|
|
29553
|
-
th, td { border: 1px solid #000; padding: 5px 8px; vertical-align: top; }
|
|
29554
|
-
th { background: #e8e8e8; font-weight: normal; }
|
|
29555
|
-
blockquote { border-left: 2px solid #555; padding-left: 12px; margin: 0.6em 0; }
|
|
29556
|
-
`,
|
|
29557
|
-
compact: `
|
|
29558
|
-
@page { size: A4; margin: 10mm; }
|
|
29559
|
-
body { font-family: 'Pretendard', 'Malgun Gothic', sans-serif; font-size: 9pt; line-height: 1.4; color: #111; }
|
|
29560
|
-
h1 { font-size: 14pt; margin: 0.5em 0 0.3em; }
|
|
29561
|
-
h2 { font-size: 12pt; margin: 0.5em 0 0.3em; }
|
|
29562
|
-
h3 { font-size: 10pt; margin: 0.4em 0 0.2em; }
|
|
29563
|
-
p { margin: 0.2em 0; }
|
|
29564
|
-
table { border-collapse: collapse; margin: 0.3em 0; width: 100%; font-size: 8pt; }
|
|
29565
|
-
th, td { border: 1px solid #777; padding: 2px 4px; }
|
|
29566
|
-
th { background: #f0f0f0; }
|
|
29567
|
-
`
|
|
29568
|
-
};
|
|
29569
|
-
var md = new MarkdownIt({
|
|
29570
|
-
html: true,
|
|
29571
|
-
linkify: true,
|
|
29572
|
-
breaks: false
|
|
29573
|
-
});
|
|
29574
|
-
function renderHtml(markdown, options) {
|
|
29575
|
-
const preset = options?.preset ?? "default";
|
|
29576
|
-
const css = PRESETS[preset] + (options?.extraCss ?? "");
|
|
29577
|
-
const body = md.render(markdown);
|
|
29578
|
-
const watermark = options?.watermark ? `<div class="watermark">${escapeHtml(options.watermark)}</div>` : "";
|
|
29579
|
-
const watermarkCss = options?.watermark ? `
|
|
29580
|
-
.watermark {
|
|
29581
|
-
position: fixed;
|
|
29582
|
-
top: 50%; left: 50%;
|
|
29583
|
-
transform: translate(-50%, -50%) rotate(-30deg);
|
|
29584
|
-
font-size: 80pt;
|
|
29585
|
-
color: rgba(0,0,0,0.08);
|
|
29586
|
-
pointer-events: none;
|
|
29587
|
-
z-index: 9999;
|
|
29588
|
-
white-space: nowrap;
|
|
29589
|
-
}` : "";
|
|
29590
|
-
return `<!DOCTYPE html>
|
|
29591
|
-
<html lang="ko">
|
|
29592
|
-
<head>
|
|
29593
|
-
<meta charset="utf-8">
|
|
29594
|
-
<style>${css}${watermarkCss}</style>
|
|
29595
|
-
</head>
|
|
29596
|
-
<body>
|
|
29597
|
-
${watermark}
|
|
29598
|
-
${body}
|
|
29599
|
-
</body>
|
|
29600
|
-
</html>`;
|
|
29601
|
-
}
|
|
29602
|
-
function escapeHtml(s) {
|
|
29603
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
29604
|
-
}
|
|
29605
|
-
async function htmlToPdf(html, options) {
|
|
29606
|
-
let puppeteer;
|
|
29607
|
-
try {
|
|
29608
|
-
puppeteer = await import("puppeteer-core");
|
|
29609
|
-
} catch {
|
|
29610
|
-
throw new KordocError(
|
|
29611
|
-
"PDF \uC0DD\uC131\uC5D0 puppeteer-core\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uCE58: npm install puppeteer-core"
|
|
29612
|
-
);
|
|
29613
|
-
}
|
|
29614
|
-
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ?? findChromiumPath();
|
|
29615
|
-
if (!executablePath) {
|
|
29616
|
-
throw new KordocError(
|
|
29617
|
-
"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."
|
|
29618
|
-
);
|
|
29619
|
-
}
|
|
29620
|
-
const browser = await puppeteer.default.launch({
|
|
29621
|
-
executablePath,
|
|
29622
|
-
headless: true,
|
|
29623
|
-
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
29624
|
-
});
|
|
29625
|
-
try {
|
|
29626
|
-
const page = await browser.newPage();
|
|
29627
|
-
await page.setContent(html, { waitUntil: "networkidle0" });
|
|
29628
|
-
const margin = options?.margin;
|
|
29629
|
-
const pdf = await page.pdf({
|
|
29630
|
-
format: options?.pageSize ?? "A4",
|
|
29631
|
-
landscape: options?.orientation === "landscape",
|
|
29632
|
-
printBackground: true,
|
|
29633
|
-
margin: margin ? {
|
|
29634
|
-
top: toCss(margin.top),
|
|
29635
|
-
right: toCss(margin.right),
|
|
29636
|
-
bottom: toCss(margin.bottom),
|
|
29637
|
-
left: toCss(margin.left)
|
|
29638
|
-
} : void 0,
|
|
29639
|
-
displayHeaderFooter: !!(options?.header || options?.footer),
|
|
29640
|
-
headerTemplate: options?.header ?? "<div></div>",
|
|
29641
|
-
footerTemplate: options?.footer ?? '<div style="font-size:8pt;width:100%;text-align:center;color:#777;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>'
|
|
29642
|
-
});
|
|
29643
|
-
return Buffer.from(pdf);
|
|
29644
|
-
} finally {
|
|
29645
|
-
await browser.close();
|
|
29646
|
-
}
|
|
29647
|
-
}
|
|
29648
|
-
function toCss(v) {
|
|
29649
|
-
return typeof v === "number" ? `${v}mm` : v;
|
|
29650
|
-
}
|
|
29651
|
-
function findChromiumPath() {
|
|
29652
|
-
const win = [
|
|
29653
|
-
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
29654
|
-
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
29655
|
-
process.env.LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe",
|
|
29656
|
-
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
29657
|
-
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
|
|
29658
|
-
];
|
|
29659
|
-
const mac = [
|
|
29660
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
29661
|
-
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
|
|
29662
|
-
];
|
|
29663
|
-
const linux = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"];
|
|
29664
|
-
const candidates = process.platform === "win32" ? win : process.platform === "darwin" ? mac : linux;
|
|
29665
|
-
for (const p of candidates) {
|
|
29666
|
-
if (p && existsSync2(p)) return p;
|
|
29667
|
-
}
|
|
29668
|
-
return null;
|
|
29669
|
-
}
|
|
29670
|
-
async function markdownToPdf(markdown, options) {
|
|
29671
|
-
const html = renderHtml(markdown, options);
|
|
29672
|
-
return htmlToPdf(html, options);
|
|
29673
|
-
}
|
|
29674
|
-
async function blocksToPdf(blocks, options) {
|
|
29675
|
-
const markdown = blocksToMarkdown(blocks);
|
|
29676
|
-
return markdownToPdf(markdown, options);
|
|
29677
|
-
}
|
|
29678
|
-
|
|
29679
30627
|
// src/index.ts
|
|
29680
30628
|
async function parse(input, options) {
|
|
29681
30629
|
let buffer;
|
|
@@ -29698,6 +30646,7 @@ async function parse(input, options) {
|
|
|
29698
30646
|
}
|
|
29699
30647
|
const format = detectFormat(buffer);
|
|
29700
30648
|
const result = await dispatch(format, buffer, opts);
|
|
30649
|
+
if (result.success && opts?.classifyTables) classifyTableTree(result.blocks);
|
|
29701
30650
|
if (result.success && !result.pages) {
|
|
29702
30651
|
const pages = blocksToPages(result.blocks);
|
|
29703
30652
|
if (pages) return { ...result, pages };
|
|
@@ -29731,7 +30680,7 @@ async function dispatch(format, buffer, opts) {
|
|
|
29731
30680
|
}
|
|
29732
30681
|
async function parseImage(buffer, options) {
|
|
29733
30682
|
try {
|
|
29734
|
-
const { parseImageDocument } = await import("./image-ocr-
|
|
30683
|
+
const { parseImageDocument } = await import("./image-ocr-2M7K3CE5.js");
|
|
29735
30684
|
const { blocks, warnings } = await parseImageDocument(buffer, options);
|
|
29736
30685
|
return {
|
|
29737
30686
|
success: true,
|
|
@@ -29790,7 +30739,7 @@ async function parseHwp(buffer, options) {
|
|
|
29790
30739
|
async function parsePdf(buffer, options) {
|
|
29791
30740
|
let parsePdfDocument;
|
|
29792
30741
|
try {
|
|
29793
|
-
const mod = await import("./parser-
|
|
30742
|
+
const mod = await import("./parser-T5R4YVZS.js");
|
|
29794
30743
|
parsePdfDocument = mod.parsePdfDocument;
|
|
29795
30744
|
} catch {
|
|
29796
30745
|
return {
|
|
@@ -29880,14 +30829,73 @@ async function fillForm(input, values, outputFormat = "markdown") {
|
|
|
29880
30829
|
return { output: markdown, format: "markdown", fill };
|
|
29881
30830
|
}
|
|
29882
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
|
+
|
|
29883
30898
|
export {
|
|
29884
|
-
isZipFile,
|
|
29885
|
-
isHwpxFile,
|
|
29886
|
-
isOldHwpFile,
|
|
29887
|
-
isPdfFile,
|
|
29888
|
-
detectFormat,
|
|
29889
|
-
detectOle2Format,
|
|
29890
|
-
detectZipFormat,
|
|
29891
30899
|
detectImageMime,
|
|
29892
30900
|
extractHwpxMetadataOnly,
|
|
29893
30901
|
extractHwp5MetadataOnly,
|
|
@@ -29901,6 +30909,10 @@ export {
|
|
|
29901
30909
|
fillFormFields,
|
|
29902
30910
|
extractClickHereFields,
|
|
29903
30911
|
fillHwpx,
|
|
30912
|
+
classifyTable,
|
|
30913
|
+
collectTableBlocks,
|
|
30914
|
+
classifyTableTree,
|
|
30915
|
+
chooseTableRepresentation,
|
|
29904
30916
|
blocksToPages,
|
|
29905
30917
|
PRESET_ALIAS,
|
|
29906
30918
|
normalizeGongmunPreset,
|
|
@@ -29926,9 +30938,7 @@ export {
|
|
|
29926
30938
|
HwpxSession,
|
|
29927
30939
|
openHwpxDocument,
|
|
29928
30940
|
patchHwpxBlocks,
|
|
29929
|
-
|
|
29930
|
-
markdownToPdf,
|
|
29931
|
-
blocksToPdf,
|
|
30941
|
+
extractTables,
|
|
29932
30942
|
parse,
|
|
29933
30943
|
parseImage,
|
|
29934
30944
|
parseHwp3,
|
|
@@ -29941,4 +30951,4 @@ export {
|
|
|
29941
30951
|
parseHwpml,
|
|
29942
30952
|
fillForm
|
|
29943
30953
|
};
|
|
29944
|
-
//# sourceMappingURL=chunk-
|
|
30954
|
+
//# sourceMappingURL=chunk-2RREQZ44.js.map
|