kordoc 4.13.0 → 4.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -4
- package/dist/{-UZDRUWTX.js → -SLX6IB6Y.js} +13 -11
- package/dist/{chunk-6JXMIX3G.js → chunk-3U3PJGVO.js} +3 -2
- package/dist/{chunk-OSLJ3A6D.js → chunk-6T6MB5ON.js} +2 -2
- package/dist/{chunk-LP5RTYFM.js → chunk-7VUHZFGQ.js} +2 -2
- package/dist/{chunk-AGBFCFQY.js → chunk-A2JDWBKG.js} +2 -2
- package/dist/{chunk-TAYCQE6K.cjs → chunk-ANKDK5DF.cjs} +2 -2
- package/dist/chunk-ANKDK5DF.cjs.map +1 -0
- package/dist/{chunk-MCD45IHO.cjs → chunk-B7MOYHVR.cjs} +6 -6
- package/dist/chunk-B7MOYHVR.cjs.map +1 -0
- package/dist/{chunk-F3FEIYI7.js → chunk-CDQJKMZ5.js} +2 -2
- package/dist/chunk-COUGOFT3.cjs.map +1 -1
- package/dist/chunk-DCZVOIEO.cjs.map +1 -1
- package/dist/{chunk-2RREQZ44.js → chunk-EMQZX5QQ.js} +676 -3296
- package/dist/chunk-EMQZX5QQ.js.map +1 -0
- package/dist/{chunk-3ABHPKYA.js → chunk-EOTAPX2M.js} +2 -2
- package/dist/{chunk-5VHLKNCE.js → chunk-GMX6EKCY.js} +2 -2
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/{chunk-BQLZOQES.js → chunk-HCHA3Y2T.js} +40 -6
- package/dist/chunk-HCHA3Y2T.js.map +1 -0
- package/dist/chunk-HH7T6SMB.js +5771 -0
- package/dist/chunk-HH7T6SMB.js.map +1 -0
- package/dist/{chunk-2B45S7FM.js → chunk-J3SM7VLM.js} +2 -2
- package/dist/{chunk-ILDTN72S.js → chunk-JIQWI7S2.js} +2 -2
- package/dist/{chunk-TA2LEFOF.js → chunk-LCGAO5LF.js} +3 -3
- package/dist/{chunk-3MCFJIN2.js → chunk-M5NRJMA4.js} +3 -3
- package/dist/{chunk-L3CEPNQS.js → chunk-PLXNW3J4.js} +2 -2
- package/dist/{chunk-27QN27I3.js → chunk-RCTWZYAM.js} +2 -2
- package/dist/{chunk-OJV7HNGJ.cjs → chunk-STLNL53H.cjs} +4 -4
- package/dist/chunk-STLNL53H.cjs.map +1 -0
- package/dist/{chunk-IEJR23OA.js → chunk-UBNJXMYX.js} +3 -3
- package/dist/{chunk-CKMK42FV.cjs → chunk-WAPKUYGP.cjs} +7 -7
- package/dist/chunk-WAPKUYGP.cjs.map +1 -0
- package/dist/{chunk-S2VKZKUX.cjs → chunk-WRIHDGGY.cjs} +3 -3
- package/dist/chunk-WRIHDGGY.cjs.map +1 -0
- package/dist/{chunk-GWFX5ZD7.js → chunk-XH34WJKB.js} +2 -2
- package/dist/{chunk-6NLXGGNH.js → chunk-XWA625AN.js} +10 -3
- package/dist/chunk-XWA625AN.js.map +1 -0
- package/dist/{chunk-H5JWPWQP.js → chunk-ZWTEE63A.js} +2 -2
- package/dist/chunks-PEHRXW2P.js +10 -0
- package/dist/cli.js +37 -34
- package/dist/cli.js.map +1 -1
- package/dist/{formula-YALEMZMF.cjs → formula-TAYYDV73.cjs} +9 -9
- package/dist/formula-TAYYDV73.cjs.map +1 -0
- package/dist/{formula-3T7HQA4O.js → formula-VSZIJZXP.js} +4 -4
- package/dist/{formula-FWVAOSIK.js → formula-VYDTA27M.js} +3 -3
- package/dist/{image-ocr-X3I2HUK2.js → image-ocr-5WBAIBPQ.js} +5 -5
- package/dist/{image-ocr-2M7K3CE5.js → image-ocr-N6IBZJAK.js} +7 -7
- package/dist/{image-ocr-4EUZIF6M.cjs → image-ocr-ZX4W2RBS.cjs} +11 -11
- package/dist/image-ocr-ZX4W2RBS.cjs.map +1 -0
- package/dist/index.cjs +1685 -620
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -10
- package/dist/index.d.ts +59 -10
- package/dist/index.js +1204 -139
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +143 -72
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-XXYZ6ELQ.js → model-bundle-5CBFVIXA.js} +5 -5
- package/dist/{models-APDSXJW7.js → models-L74RBKK3.js} +5 -5
- package/dist/page-range-P7SDW6LR.cjs.map +1 -1
- package/dist/{parser-XKSOS6GY.js → parser-66NMJUOG.js} +6 -6
- package/dist/{parser-T5R4YVZS.js → parser-BK66Y5JD.js} +6 -6
- package/dist/{parser-4KE2FR5F.cjs → parser-WM2DMVLY.cjs} +26 -26
- package/dist/parser-WM2DMVLY.cjs.map +1 -0
- package/dist/pdf-ocr-322MBLDK.js +16 -0
- package/dist/pdf-ocr-A4UBXLM2.js +13 -0
- package/dist/pdf-ocr-G4TBDUYL.cjs +14 -0
- package/dist/pdf-ocr-G4TBDUYL.cjs.map +1 -0
- package/dist/{profile-io-Y24GOHWM.js → profile-io-JZ3FDHDK.js} +3 -3
- package/dist/{rasterize-QCOBWHAG.js → rasterize-33WOOVSM.js} +3 -3
- package/dist/{render-DHFQQCSV.js → render-MLN4ZLVD.js} +16 -6
- package/dist/seal-JQWSNHRG.js +10 -0
- package/dist/{setup-UWX3KH3G.js → setup-G6AEN4J7.js} +3 -3
- package/dist/visual-VZ7QGAUC.js +19 -0
- package/dist/{watch-4ZUTMC3Q.js → watch-OY6XKGC5.js} +12 -12
- package/package.json +4 -3
- package/dist/chunk-2RREQZ44.js.map +0 -1
- package/dist/chunk-6NLXGGNH.js.map +0 -1
- package/dist/chunk-BQLZOQES.js.map +0 -1
- package/dist/chunk-CKMK42FV.cjs.map +0 -1
- package/dist/chunk-MCD45IHO.cjs.map +0 -1
- package/dist/chunk-OJV7HNGJ.cjs.map +0 -1
- package/dist/chunk-S2VKZKUX.cjs.map +0 -1
- package/dist/chunk-T7AFLXGA.js +0 -2056
- package/dist/chunk-T7AFLXGA.js.map +0 -1
- package/dist/chunk-TAYCQE6K.cjs.map +0 -1
- package/dist/chunks-G3GK536Y.js +0 -10
- package/dist/formula-YALEMZMF.cjs.map +0 -1
- package/dist/image-ocr-4EUZIF6M.cjs.map +0 -1
- package/dist/parser-4KE2FR5F.cjs.map +0 -1
- package/dist/pdf-ocr-JZKCEKWX.cjs +0 -14
- package/dist/pdf-ocr-JZKCEKWX.cjs.map +0 -1
- package/dist/pdf-ocr-X62B7YDV.js +0 -13
- package/dist/pdf-ocr-YDJQEGDS.js +0 -16
- package/dist/seal-UTHFQPLO.js +0 -10
- package/dist/visual-DF2KSPEC.js +0 -19
- /package/dist/{-UZDRUWTX.js.map → -SLX6IB6Y.js.map} +0 -0
- /package/dist/{chunk-6JXMIX3G.js.map → chunk-3U3PJGVO.js.map} +0 -0
- /package/dist/{chunk-OSLJ3A6D.js.map → chunk-6T6MB5ON.js.map} +0 -0
- /package/dist/{chunk-LP5RTYFM.js.map → chunk-7VUHZFGQ.js.map} +0 -0
- /package/dist/{chunk-AGBFCFQY.js.map → chunk-A2JDWBKG.js.map} +0 -0
- /package/dist/{chunk-F3FEIYI7.js.map → chunk-CDQJKMZ5.js.map} +0 -0
- /package/dist/{chunk-3ABHPKYA.js.map → chunk-EOTAPX2M.js.map} +0 -0
- /package/dist/{chunk-5VHLKNCE.js.map → chunk-GMX6EKCY.js.map} +0 -0
- /package/dist/{chunk-2B45S7FM.js.map → chunk-J3SM7VLM.js.map} +0 -0
- /package/dist/{chunk-ILDTN72S.js.map → chunk-JIQWI7S2.js.map} +0 -0
- /package/dist/{chunk-TA2LEFOF.js.map → chunk-LCGAO5LF.js.map} +0 -0
- /package/dist/{chunk-3MCFJIN2.js.map → chunk-M5NRJMA4.js.map} +0 -0
- /package/dist/{chunk-L3CEPNQS.js.map → chunk-PLXNW3J4.js.map} +0 -0
- /package/dist/{chunk-27QN27I3.js.map → chunk-RCTWZYAM.js.map} +0 -0
- /package/dist/{chunk-IEJR23OA.js.map → chunk-UBNJXMYX.js.map} +0 -0
- /package/dist/{chunk-GWFX5ZD7.js.map → chunk-XH34WJKB.js.map} +0 -0
- /package/dist/{chunk-H5JWPWQP.js.map → chunk-ZWTEE63A.js.map} +0 -0
- /package/dist/{chunks-G3GK536Y.js.map → chunks-PEHRXW2P.js.map} +0 -0
- /package/dist/{formula-3T7HQA4O.js.map → formula-VSZIJZXP.js.map} +0 -0
- /package/dist/{formula-FWVAOSIK.js.map → formula-VYDTA27M.js.map} +0 -0
- /package/dist/{image-ocr-X3I2HUK2.js.map → image-ocr-5WBAIBPQ.js.map} +0 -0
- /package/dist/{image-ocr-2M7K3CE5.js.map → image-ocr-N6IBZJAK.js.map} +0 -0
- /package/dist/{model-bundle-XXYZ6ELQ.js.map → model-bundle-5CBFVIXA.js.map} +0 -0
- /package/dist/{models-APDSXJW7.js.map → models-L74RBKK3.js.map} +0 -0
- /package/dist/{parser-XKSOS6GY.js.map → parser-66NMJUOG.js.map} +0 -0
- /package/dist/{parser-T5R4YVZS.js.map → parser-BK66Y5JD.js.map} +0 -0
- /package/dist/{pdf-ocr-X62B7YDV.js.map → pdf-ocr-322MBLDK.js.map} +0 -0
- /package/dist/{pdf-ocr-YDJQEGDS.js.map → pdf-ocr-A4UBXLM2.js.map} +0 -0
- /package/dist/{profile-io-Y24GOHWM.js.map → profile-io-JZ3FDHDK.js.map} +0 -0
- /package/dist/{rasterize-QCOBWHAG.js.map → rasterize-33WOOVSM.js.map} +0 -0
- /package/dist/{render-DHFQQCSV.js.map → render-MLN4ZLVD.js.map} +0 -0
- /package/dist/{seal-UTHFQPLO.js.map → seal-JQWSNHRG.js.map} +0 -0
- /package/dist/{setup-UWX3KH3G.js.map → setup-G6AEN4J7.js.map} +0 -0
- /package/dist/{visual-DF2KSPEC.js.map → visual-VZ7QGAUC.js.map} +0 -0
- /package/dist/{watch-4ZUTMC3Q.js.map → watch-OY6XKGC5.js.map} +0 -0
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
inlineImagesIntoMarkdown
|
|
4
|
-
} from "./chunk-7FCNOKOR.js";
|
|
5
|
-
import {
|
|
6
|
-
HEADING_RATIO_H1,
|
|
7
|
-
HEADING_RATIO_H2,
|
|
8
|
-
HEADING_RATIO_H3
|
|
9
|
-
} from "./chunk-HG5LXXCC.js";
|
|
10
2
|
import {
|
|
11
3
|
allLinesegRemovalSplices,
|
|
12
4
|
applySplices,
|
|
@@ -19,21 +11,51 @@ import {
|
|
|
19
11
|
paraTextPureT,
|
|
20
12
|
patchZipEntries,
|
|
21
13
|
scanSectionXml
|
|
22
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-UBNJXMYX.js";
|
|
23
15
|
import {
|
|
16
|
+
CONVERT_MAP,
|
|
17
|
+
FLAG_COMPRESSED,
|
|
18
|
+
FLAG_DISTRIBUTION,
|
|
19
|
+
FLAG_DRM,
|
|
20
|
+
FLAG_ENCRYPTED,
|
|
21
|
+
MIDDLE_CONVERT_MAP,
|
|
24
22
|
SPACE_EM_FIXED,
|
|
23
|
+
TAG_CHAR_SHAPE,
|
|
24
|
+
TAG_CTRL_HEADER,
|
|
25
|
+
TAG_LIST_HEADER,
|
|
26
|
+
TAG_PARA_HEADER,
|
|
27
|
+
TAG_PARA_TEXT,
|
|
28
|
+
TAG_TABLE,
|
|
29
|
+
appendParaText,
|
|
25
30
|
charWidthEm1000,
|
|
31
|
+
circledHangul,
|
|
32
|
+
circledNumber,
|
|
33
|
+
createParaTextState,
|
|
34
|
+
decompressStream,
|
|
26
35
|
detectFormat,
|
|
36
|
+
detectImageMime,
|
|
27
37
|
detectOle2Format,
|
|
28
38
|
detectZipFormat,
|
|
29
39
|
extractRenderedRegions,
|
|
30
40
|
faceClassForGen,
|
|
31
41
|
fitRatioForFewerLines,
|
|
42
|
+
hangulAmount,
|
|
43
|
+
hangulOrdinal,
|
|
44
|
+
hmlToLatex,
|
|
45
|
+
isExtendedOnlyCtrlChar,
|
|
32
46
|
measureTextWidth,
|
|
47
|
+
parseFileHeader,
|
|
48
|
+
parseHwp5Document,
|
|
33
49
|
parseLenientCfb,
|
|
34
50
|
renderDocumentToScene,
|
|
51
|
+
romanNumeral,
|
|
35
52
|
simulateWrap
|
|
36
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-HH7T6SMB.js";
|
|
54
|
+
import {
|
|
55
|
+
HEADING_RATIO_H1,
|
|
56
|
+
HEADING_RATIO_H2,
|
|
57
|
+
HEADING_RATIO_H3
|
|
58
|
+
} from "./chunk-HG5LXXCC.js";
|
|
37
59
|
import {
|
|
38
60
|
parsePageRange
|
|
39
61
|
} from "./chunk-MOL7MDBG.js";
|
|
@@ -52,18 +74,16 @@ import {
|
|
|
52
74
|
findChildByLocalName,
|
|
53
75
|
localName,
|
|
54
76
|
rawTextContent
|
|
55
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-3U3PJGVO.js";
|
|
56
78
|
import {
|
|
57
79
|
MAX_COLS,
|
|
58
80
|
MAX_ROWS,
|
|
59
81
|
blocksToMarkdown,
|
|
60
82
|
buildTable,
|
|
61
83
|
convertTableToText,
|
|
62
|
-
dedupeRunningHeaders,
|
|
63
|
-
flattenLayoutTables,
|
|
64
84
|
hasStructuredCellContent,
|
|
65
85
|
mapPuaText
|
|
66
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-XH34WJKB.js";
|
|
67
87
|
import {
|
|
68
88
|
KordocError,
|
|
69
89
|
classifyError,
|
|
@@ -75,7 +95,7 @@ import {
|
|
|
75
95
|
sanitizeHref,
|
|
76
96
|
stripDtd,
|
|
77
97
|
toArrayBuffer
|
|
78
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-RCTWZYAM.js";
|
|
79
99
|
|
|
80
100
|
// src/table/visual.ts
|
|
81
101
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -249,9 +269,9 @@ function decryptEntry(cipher, entry, startKey) {
|
|
|
249
269
|
const decipher = createDecipheriv("aes-256-cbc", key, entry.iv);
|
|
250
270
|
decipher.setAutoPadding(false);
|
|
251
271
|
const raw = Buffer.concat([decipher.update(cipher), decipher.final()]);
|
|
252
|
-
const
|
|
253
|
-
const sum = createHash("sha256").update(
|
|
254
|
-
if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return
|
|
272
|
+
const plain4 = inflateRawSync(raw, { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
273
|
+
const sum = createHash("sha256").update(plain4.subarray(0, Math.min(CHECKSUM_PREFIX_BYTES, plain4.length))).digest();
|
|
274
|
+
if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return plain4;
|
|
255
275
|
} catch {
|
|
256
276
|
}
|
|
257
277
|
}
|
|
@@ -267,13 +287,13 @@ async function decryptHwpxInPlace(zip, manifestXml, password) {
|
|
|
267
287
|
if (!file) {
|
|
268
288
|
throw new KordocError(`\uC554\uD638 \uBAA9\uB85D\uC5D0 \uC788\uB294 \uD30C\uC77C\uC774 \uBB38\uC11C\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4: ${entry.path}`);
|
|
269
289
|
}
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
290
|
+
const plain4 = decryptEntry(await file.async("nodebuffer"), entry, startKey);
|
|
291
|
+
if (!plain4) {
|
|
272
292
|
throw new KordocError("\uBE44\uBC00\uBC88\uD638\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uAC70\uB098 \uC554\uD638\uD654 \uB370\uC774\uD130\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
273
293
|
}
|
|
274
|
-
decrypted.push({ path: entry.path, plain:
|
|
294
|
+
decrypted.push({ path: entry.path, plain: plain4 });
|
|
275
295
|
}
|
|
276
|
-
for (const { path, plain:
|
|
296
|
+
for (const { path, plain: plain4 } of decrypted) zip.file(path, plain4);
|
|
277
297
|
zip.file("META-INF/manifest.xml", stripEncryptionData(manifestXml));
|
|
278
298
|
return decrypted.length;
|
|
279
299
|
}
|
|
@@ -524,527 +544,6 @@ function detectHwpxHeadings(blocks, styleMap) {
|
|
|
524
544
|
}
|
|
525
545
|
}
|
|
526
546
|
|
|
527
|
-
// src/hwpx/equation.ts
|
|
528
|
-
var CONVERT_MAP = {
|
|
529
|
-
TIMES: "\\times",
|
|
530
|
-
times: "\\times",
|
|
531
|
-
LEFT: "\\left",
|
|
532
|
-
RIGHT: "\\right",
|
|
533
|
-
under: "\\underline",
|
|
534
|
-
SMALLSUM: "\\sum",
|
|
535
|
-
sum: "\\sum",
|
|
536
|
-
SMALLPROD: "\\prod",
|
|
537
|
-
prod: "\\prod",
|
|
538
|
-
SMALLINTER: "\\cap",
|
|
539
|
-
CUP: "\\cup",
|
|
540
|
-
OPLUS: "\\oplus",
|
|
541
|
-
OMINUS: "\\ominus",
|
|
542
|
-
OTIMES: "\\otimes",
|
|
543
|
-
ODIV: "\\oslash",
|
|
544
|
-
ODOT: "\\odot",
|
|
545
|
-
LOR: "\\lor",
|
|
546
|
-
LAND: "\\land",
|
|
547
|
-
SUBSET: "\\subset",
|
|
548
|
-
SUPERSET: "\\supset",
|
|
549
|
-
SUBSETEQ: "\\subseteq",
|
|
550
|
-
SUPSETEQ: "\\supseteq",
|
|
551
|
-
IN: "\\in",
|
|
552
|
-
OWNS: "\\owns",
|
|
553
|
-
NOTIN: "\\notin",
|
|
554
|
-
LEQ: "\\leq",
|
|
555
|
-
GEQ: "\\geq",
|
|
556
|
-
"<<": "\\ll",
|
|
557
|
-
">>": "\\gg",
|
|
558
|
-
"<<<": "\\lll",
|
|
559
|
-
">>>": "\\ggg",
|
|
560
|
-
PREC: "\\prec",
|
|
561
|
-
SUCC: "\\succ",
|
|
562
|
-
UPLUS: "\\uplus",
|
|
563
|
-
"\xB1": "\\pm",
|
|
564
|
-
"+-": "\\pm",
|
|
565
|
-
"-+": "\\mp",
|
|
566
|
-
"\xF7": "\\div",
|
|
567
|
-
cdot: "\\cdot",
|
|
568
|
-
CIRC: "\\circ",
|
|
569
|
-
BULLET: "\\bullet",
|
|
570
|
-
DEG: " ^\\circ",
|
|
571
|
-
AST: "\\ast",
|
|
572
|
-
STAR: "\\bigstar",
|
|
573
|
-
BIGCIRC: "\\bigcirc",
|
|
574
|
-
EMPTYSET: "\\emptyset",
|
|
575
|
-
THEREFORE: "\\therefore",
|
|
576
|
-
BECAUSE: "\\because",
|
|
577
|
-
EXIST: "\\exists",
|
|
578
|
-
"!=": "\\neq",
|
|
579
|
-
SMCOPROD: "\\coprod",
|
|
580
|
-
coprod: "\\coprod",
|
|
581
|
-
SQCAP: "\\sqcap",
|
|
582
|
-
SQCUP: "\\sqcup",
|
|
583
|
-
SQSUBSET: "\\sqsubset",
|
|
584
|
-
SQSUBSETEQ: "\\sqsubseteq",
|
|
585
|
-
BIGSQCUP: "\\bigsqcup",
|
|
586
|
-
BIGOPLUS: "\\bigoplus",
|
|
587
|
-
BIGOTIMES: "\\bigotimes",
|
|
588
|
-
BIGODOT: "\\bigodot",
|
|
589
|
-
BIGUPLUS: "\\biguplus",
|
|
590
|
-
inter: "\\bigcap",
|
|
591
|
-
union: "\\bigcup",
|
|
592
|
-
BIGOMINUS: "{\\Large\\ominus}",
|
|
593
|
-
BIGODIV: "{\\Large\\oslash}",
|
|
594
|
-
UNDEROVER: "",
|
|
595
|
-
SIM: "\\sim",
|
|
596
|
-
APPROX: "\\approx",
|
|
597
|
-
SIMEQ: "\\simeq",
|
|
598
|
-
CONG: "\\cong",
|
|
599
|
-
"==": "\\equiv",
|
|
600
|
-
DIAMOND: "\\diamond",
|
|
601
|
-
FORALL: "\\forall",
|
|
602
|
-
prime: "'",
|
|
603
|
-
Partial: "\\partial",
|
|
604
|
-
INF: "\\infty",
|
|
605
|
-
PROPTO: "\\propto",
|
|
606
|
-
lim: "\\lim",
|
|
607
|
-
Lim: "\\lim",
|
|
608
|
-
larrow: "\\leftarrow",
|
|
609
|
-
"->": "\\rightarrow",
|
|
610
|
-
uparrow: "\\uparrow",
|
|
611
|
-
downarrow: "\\downarrow",
|
|
612
|
-
LARROW: "\\Leftarrow",
|
|
613
|
-
RARROW: "\\Rightarrow",
|
|
614
|
-
UPARROW: "\\Uparrow",
|
|
615
|
-
DOWNARROW: "\\Downarrow",
|
|
616
|
-
udarrow: "\\updownarrow",
|
|
617
|
-
"<->": "\\leftrightarrow",
|
|
618
|
-
UDARROW: "\\Updownarrow",
|
|
619
|
-
LRARROW: "\\Leftrightarrow",
|
|
620
|
-
NWARROW: "\\nwarrow",
|
|
621
|
-
SEARROW: "\\searrow",
|
|
622
|
-
NEARROW: "\\nearrow",
|
|
623
|
-
SWARROW: "\\swarrow",
|
|
624
|
-
HOOKLEFT: "\\hookleftarrow",
|
|
625
|
-
HOOKRIGHT: "\\hookrightarrow",
|
|
626
|
-
PVER: "\\|",
|
|
627
|
-
MAPSTO: "\\mapsto",
|
|
628
|
-
CDOTS: "\\cdots",
|
|
629
|
-
LDOTS: "\\ldots",
|
|
630
|
-
VDOTS: "\\vdots",
|
|
631
|
-
DDOTS: "\\ddots",
|
|
632
|
-
DAGGER: "\\dagger",
|
|
633
|
-
DDAGGER: "\\ddagger",
|
|
634
|
-
DOTEQ: "\\doteq",
|
|
635
|
-
image: "\\fallingdotseq",
|
|
636
|
-
REIMAGE: "\\risingdotseq",
|
|
637
|
-
ASYMP: "\\asymp",
|
|
638
|
-
ISO: "\\Bumpeq",
|
|
639
|
-
DSUM: "\\dotplus",
|
|
640
|
-
XOR: "\\veebar",
|
|
641
|
-
TRIANGLE: "\\triangle",
|
|
642
|
-
NABLA: "\\nabla",
|
|
643
|
-
ANGLE: "\\angle",
|
|
644
|
-
MSANGLE: "\\measuredangle",
|
|
645
|
-
SANGLE: "\\sphericalangle",
|
|
646
|
-
VDASH: "\\vdash",
|
|
647
|
-
DASHV: "\\dashv",
|
|
648
|
-
BOT: "\\bot",
|
|
649
|
-
TOP: "\\top",
|
|
650
|
-
MODELS: "\\models",
|
|
651
|
-
LAPLACE: "\\mathcal{L}",
|
|
652
|
-
CENTIGRADE: "^{\\circ}C",
|
|
653
|
-
FAHRENHEIT: "^{\\circ}F",
|
|
654
|
-
LSLANT: "\\diagup",
|
|
655
|
-
RSLANT: "\\diagdown",
|
|
656
|
-
sqrt: "\\sqrt",
|
|
657
|
-
int: "\\int",
|
|
658
|
-
dint: "\\iint",
|
|
659
|
-
tint: "\\iiint",
|
|
660
|
-
oint: "\\oint",
|
|
661
|
-
alpha: "\\alpha",
|
|
662
|
-
beta: "\\beta",
|
|
663
|
-
gamma: "\\gamma",
|
|
664
|
-
delta: "\\delta",
|
|
665
|
-
epsilon: "\\epsilon",
|
|
666
|
-
zeta: "\\zeta",
|
|
667
|
-
eta: "\\eta",
|
|
668
|
-
theta: "\\theta",
|
|
669
|
-
iota: "\\iota",
|
|
670
|
-
kappa: "\\kappa",
|
|
671
|
-
lambda: "\\lambda",
|
|
672
|
-
mu: "\\mu",
|
|
673
|
-
nu: "\\nu",
|
|
674
|
-
xi: "\\xi",
|
|
675
|
-
omicron: "\\omicron",
|
|
676
|
-
pi: "\\pi",
|
|
677
|
-
rho: "\\rho",
|
|
678
|
-
sigma: "\\sigma",
|
|
679
|
-
tau: "\\tau",
|
|
680
|
-
upsilon: "\\upsilon",
|
|
681
|
-
phi: "\\phi",
|
|
682
|
-
chi: "\\chi",
|
|
683
|
-
psi: "\\psi",
|
|
684
|
-
omega: "\\omega",
|
|
685
|
-
ALPHA: "A",
|
|
686
|
-
BETA: "B",
|
|
687
|
-
GAMMA: "\\Gamma",
|
|
688
|
-
DELTA: "\\Delta",
|
|
689
|
-
EPSILON: "E",
|
|
690
|
-
ZETA: "Z",
|
|
691
|
-
ETA: "H",
|
|
692
|
-
THETA: "\\Theta",
|
|
693
|
-
IOTA: "I",
|
|
694
|
-
KAPPA: "K",
|
|
695
|
-
LAMBDA: "\\Lambda",
|
|
696
|
-
MU: "M",
|
|
697
|
-
NU: "N",
|
|
698
|
-
XI: "\\Xi",
|
|
699
|
-
OMICRON: "O",
|
|
700
|
-
PI: "\\Pi",
|
|
701
|
-
RHO: "P",
|
|
702
|
-
SIGMA: "\\Sigma",
|
|
703
|
-
TAU: "T",
|
|
704
|
-
UPSILON: "\\Upsilon",
|
|
705
|
-
PHI: "\\Phi",
|
|
706
|
-
CHI: "X",
|
|
707
|
-
PSI: "\\Psi",
|
|
708
|
-
OMEGA: "\\Omega",
|
|
709
|
-
"\u2308": "\\lceil",
|
|
710
|
-
"\u2309": "\\rceil",
|
|
711
|
-
"\u230A": "\\lfloor",
|
|
712
|
-
"\u230B": "\\rfloor",
|
|
713
|
-
"\u2225": "\\|",
|
|
714
|
-
"\u2290": "\\sqsupset",
|
|
715
|
-
"\u2292": "\\sqsupseteq",
|
|
716
|
-
odint: "\\mathop \u222F",
|
|
717
|
-
otint: "\\mathop \u2230",
|
|
718
|
-
BIGSQCAP: "\\mathop \u2A05",
|
|
719
|
-
ATT: "\\mathop \u203B",
|
|
720
|
-
HUND: "\\mathop \u2030",
|
|
721
|
-
THOU: "\\mathop \u2031",
|
|
722
|
-
IDENTICAL: "\\mathop \u2237",
|
|
723
|
-
RTANGLE: "\\mathop \u22BE",
|
|
724
|
-
BASE: "\\mathop \u2302",
|
|
725
|
-
BENZENE: "\\mathop \u232C"
|
|
726
|
-
};
|
|
727
|
-
var MIDDLE_CONVERT_MAP = {
|
|
728
|
-
matrix: "HULKMATRIX",
|
|
729
|
-
pmatrix: "HULKPMATRIX",
|
|
730
|
-
bmatrix: "HULKBMATRIX",
|
|
731
|
-
dmatrix: "HULKDMATRIX",
|
|
732
|
-
eqalign: "HULKEQALIGN",
|
|
733
|
-
cases: "HULKCASE",
|
|
734
|
-
vec: "HULKVEC",
|
|
735
|
-
dyad: "HULKDYAD",
|
|
736
|
-
acute: "HULKACUTE",
|
|
737
|
-
grave: "HULKGRAVE",
|
|
738
|
-
dot: "HULKDOT",
|
|
739
|
-
ddot: "HULKDDOT",
|
|
740
|
-
bar: "HULKBAR",
|
|
741
|
-
hat: "HULKHAT",
|
|
742
|
-
check: "HULKCHECK",
|
|
743
|
-
arch: "HULKARCH",
|
|
744
|
-
tilde: "HULKTILDE",
|
|
745
|
-
BOX: "HULKBOX",
|
|
746
|
-
OVERBRACE: "HULKOVERBRACE",
|
|
747
|
-
UNDERBRACE: "HULKUNDERBRACE"
|
|
748
|
-
};
|
|
749
|
-
var BAR_CONVERT_MAP = {
|
|
750
|
-
HULKVEC: "\\overrightarrow",
|
|
751
|
-
HULKDYAD: "\\overleftrightarrow",
|
|
752
|
-
HULKACUTE: "\\acute",
|
|
753
|
-
HULKGRAVE: "\\grave",
|
|
754
|
-
HULKDOT: "\\dot",
|
|
755
|
-
HULKDDOT: "\\ddot",
|
|
756
|
-
HULKBAR: "\\overline",
|
|
757
|
-
HULKHAT: "\\widehat",
|
|
758
|
-
HULKCHECK: "\\check",
|
|
759
|
-
HULKARCH: "\\overset{\\frown}",
|
|
760
|
-
HULKTILDE: "\\widetilde",
|
|
761
|
-
HULKBOX: "\\boxed"
|
|
762
|
-
};
|
|
763
|
-
var MATRIX_CONVERT_MAP = {
|
|
764
|
-
HULKMATRIX: { begin: "\\begin{matrix}", end: "\\end{matrix}", removeOutterBrackets: true },
|
|
765
|
-
HULKPMATRIX: { begin: "\\begin{pmatrix}", end: "\\end{pmatrix}", removeOutterBrackets: true },
|
|
766
|
-
HULKBMATRIX: { begin: "\\begin{bmatrix}", end: "\\end{bmatrix}", removeOutterBrackets: true },
|
|
767
|
-
HULKDMATRIX: { begin: "\\begin{vmatrix}", end: "\\end{vmatrix}", removeOutterBrackets: true },
|
|
768
|
-
HULKCASE: { begin: "\\begin{cases}", end: "\\end{cases}", removeOutterBrackets: true },
|
|
769
|
-
HULKEQALIGN: { begin: "\\eqalign{", end: "}", removeOutterBrackets: false }
|
|
770
|
-
};
|
|
771
|
-
var BRACE_CONVERT_MAP = {
|
|
772
|
-
HULKOVERBRACE: "\\overbrace",
|
|
773
|
-
HULKUNDERBRACE: "\\underbrace"
|
|
774
|
-
};
|
|
775
|
-
function findBrackets(eqString, startIdx, direction) {
|
|
776
|
-
if (direction === 1) {
|
|
777
|
-
const startCur = eqString.indexOf("{", startIdx);
|
|
778
|
-
if (startCur === -1) throw new Error("cannot find bracket");
|
|
779
|
-
let bracketCount = 1;
|
|
780
|
-
for (let i = startCur + 1; i < eqString.length; i++) {
|
|
781
|
-
const ch = eqString[i];
|
|
782
|
-
if (ch === "{") bracketCount += 1;
|
|
783
|
-
else if (ch === "}") bracketCount -= 1;
|
|
784
|
-
if (bracketCount === 0) return [startCur, i + 1];
|
|
785
|
-
}
|
|
786
|
-
throw new Error("cannot find bracket");
|
|
787
|
-
}
|
|
788
|
-
const reversed = Array.from(eqString).reverse();
|
|
789
|
-
for (let i = 0; i < reversed.length; i++) {
|
|
790
|
-
if (reversed[i] === "{") reversed[i] = "}";
|
|
791
|
-
else if (reversed[i] === "}") reversed[i] = "{";
|
|
792
|
-
}
|
|
793
|
-
const flipped = reversed.join("");
|
|
794
|
-
const newStartIdx = flipped.length - (startIdx + 1);
|
|
795
|
-
const [s, e] = findBrackets(flipped, newStartIdx, 1);
|
|
796
|
-
return [flipped.length - e, flipped.length - s];
|
|
797
|
-
}
|
|
798
|
-
function findEnclosingBrackets(eqString, startIdx) {
|
|
799
|
-
let depth = 0;
|
|
800
|
-
for (let idx = startIdx - 1; idx >= 0; idx--) {
|
|
801
|
-
const ch = eqString[idx];
|
|
802
|
-
if (ch === "}") {
|
|
803
|
-
depth += 1;
|
|
804
|
-
} else if (ch === "{") {
|
|
805
|
-
if (depth > 0) {
|
|
806
|
-
depth -= 1;
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
try {
|
|
810
|
-
const [start, end] = findBrackets(eqString, idx, 1);
|
|
811
|
-
if (start === idx && end > startIdx) return [start, end];
|
|
812
|
-
} catch {
|
|
813
|
-
return null;
|
|
814
|
-
}
|
|
815
|
-
return null;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return null;
|
|
819
|
-
}
|
|
820
|
-
function maskLiteralSpans(eqString) {
|
|
821
|
-
return eqString.replace(/"[^"]*"/g, (m) => "\uFFFF".repeat(m.length)).replace(/\\text\{[^}]*\}/g, (m) => "\uFFFF".repeat(m.length));
|
|
822
|
-
}
|
|
823
|
-
function findKeywordToken(eqString, word, from = 0) {
|
|
824
|
-
const masked = maskLiteralSpans(eqString);
|
|
825
|
-
for (let i = masked.indexOf(word, from); i !== -1; i = masked.indexOf(word, i + 1)) {
|
|
826
|
-
const okL = i === 0 || /\s/.test(masked[i - 1]);
|
|
827
|
-
const okR = i + word.length === masked.length || /\s/.test(masked[i + word.length]);
|
|
828
|
-
if (okL && okR) return i;
|
|
829
|
-
}
|
|
830
|
-
return -1;
|
|
831
|
-
}
|
|
832
|
-
function replaceFrac(eqString) {
|
|
833
|
-
const hmlFrac = "over";
|
|
834
|
-
while (true) {
|
|
835
|
-
const cursor = findKeywordToken(eqString, hmlFrac);
|
|
836
|
-
if (cursor === -1) break;
|
|
837
|
-
try {
|
|
838
|
-
let end = cursor;
|
|
839
|
-
while (end > 0 && /\s/.test(eqString[end - 1])) end--;
|
|
840
|
-
let numStart, numEnd, wrapped;
|
|
841
|
-
if (end > 0 && eqString[end - 1] === "}") {
|
|
842
|
-
[numStart, numEnd] = findBrackets(eqString, end - 1, 0);
|
|
843
|
-
wrapped = eqString.slice(numStart, numEnd);
|
|
844
|
-
} else {
|
|
845
|
-
numEnd = end;
|
|
846
|
-
numStart = end;
|
|
847
|
-
while (numStart > 0 && !/\s/.test(eqString[numStart - 1])) numStart--;
|
|
848
|
-
if (numStart === numEnd) throw new Error("empty numerator");
|
|
849
|
-
wrapped = "{" + eqString.slice(numStart, numEnd) + "}";
|
|
850
|
-
}
|
|
851
|
-
const beforeFrac = eqString.slice(0, numStart);
|
|
852
|
-
const afterFrac = eqString.slice(cursor + hmlFrac.length);
|
|
853
|
-
eqString = beforeFrac + "\\frac" + wrapped + afterFrac;
|
|
854
|
-
} catch {
|
|
855
|
-
return eqString;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return eqString;
|
|
859
|
-
}
|
|
860
|
-
function replaceRootOf(eqString) {
|
|
861
|
-
while (true) {
|
|
862
|
-
const rootCursor = findKeywordToken(eqString, "root");
|
|
863
|
-
if (rootCursor === -1) break;
|
|
864
|
-
try {
|
|
865
|
-
const elem1 = findBrackets(eqString, rootCursor, 1);
|
|
866
|
-
const ofCursor = findKeywordToken(eqString, "of", elem1[1]);
|
|
867
|
-
if (ofCursor === -1) return eqString;
|
|
868
|
-
const elem2 = findBrackets(eqString, ofCursor, 1);
|
|
869
|
-
const e1 = eqString.slice(elem1[0] + 1, elem1[1] - 1);
|
|
870
|
-
const e2 = eqString.slice(elem2[0] + 1, elem2[1] - 1);
|
|
871
|
-
eqString = eqString.slice(0, rootCursor) + "\\sqrt[" + e1 + "]{" + e2 + "}" + eqString.slice(elem2[1] + 1);
|
|
872
|
-
} catch {
|
|
873
|
-
return eqString;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
return eqString;
|
|
877
|
-
}
|
|
878
|
-
function replaceAllMatrix(eqString) {
|
|
879
|
-
const replaceElements = (bracketStr) => {
|
|
880
|
-
let inner = bracketStr.slice(1, -1);
|
|
881
|
-
inner = inner.replace(/#/g, " \\\\ ");
|
|
882
|
-
inner = inner.replace(/&/g, "&");
|
|
883
|
-
return inner;
|
|
884
|
-
};
|
|
885
|
-
const replaceMatrix = (input, matStr, matElem) => {
|
|
886
|
-
while (true) {
|
|
887
|
-
const cursor = input.indexOf(matStr);
|
|
888
|
-
if (cursor === -1) break;
|
|
889
|
-
try {
|
|
890
|
-
const [eStart, eEnd] = findBrackets(input, cursor, 1);
|
|
891
|
-
const elem = replaceElements(input.slice(eStart, eEnd));
|
|
892
|
-
let beforeMat;
|
|
893
|
-
let afterMat;
|
|
894
|
-
const outer = matElem.removeOutterBrackets ? findEnclosingBrackets(input, cursor) : null;
|
|
895
|
-
if (outer && outer[1] >= eEnd) {
|
|
896
|
-
const [bStart, bEnd] = outer;
|
|
897
|
-
beforeMat = input.slice(0, bStart);
|
|
898
|
-
afterMat = input.slice(bEnd);
|
|
899
|
-
} else {
|
|
900
|
-
beforeMat = input.slice(0, cursor);
|
|
901
|
-
afterMat = input.slice(eEnd);
|
|
902
|
-
}
|
|
903
|
-
input = beforeMat + matElem.begin + elem + matElem.end + afterMat;
|
|
904
|
-
} catch {
|
|
905
|
-
return input;
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
return input;
|
|
909
|
-
};
|
|
910
|
-
for (const [matKey, matElem] of Object.entries(MATRIX_CONVERT_MAP)) {
|
|
911
|
-
eqString = replaceMatrix(eqString, matKey, matElem);
|
|
912
|
-
}
|
|
913
|
-
return eqString;
|
|
914
|
-
}
|
|
915
|
-
function replaceAllBar(eqString) {
|
|
916
|
-
const replaceBar = (input, barStr, barElem) => {
|
|
917
|
-
while (true) {
|
|
918
|
-
const cursor = input.indexOf(barStr);
|
|
919
|
-
if (cursor === -1) break;
|
|
920
|
-
try {
|
|
921
|
-
const [eStart, eEnd] = findBrackets(input, cursor, 1);
|
|
922
|
-
const elem = input.slice(eStart, eEnd);
|
|
923
|
-
const outer = findEnclosingBrackets(input, cursor);
|
|
924
|
-
const [replaceStart, replaceEnd] = outer && outer[1] >= eEnd ? outer : [cursor, eEnd];
|
|
925
|
-
const beforeBar = input.slice(0, replaceStart);
|
|
926
|
-
const afterBar = input.slice(replaceEnd);
|
|
927
|
-
input = beforeBar + barElem + elem + afterBar;
|
|
928
|
-
} catch {
|
|
929
|
-
return input;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
return input;
|
|
933
|
-
};
|
|
934
|
-
for (const [barKey, barElem] of Object.entries(BAR_CONVERT_MAP)) {
|
|
935
|
-
eqString = replaceBar(eqString, barKey, barElem);
|
|
936
|
-
}
|
|
937
|
-
return eqString;
|
|
938
|
-
}
|
|
939
|
-
function replaceAllBrace(eqString) {
|
|
940
|
-
const replaceBrace = (input, braceStr, braceElem) => {
|
|
941
|
-
while (true) {
|
|
942
|
-
const cursor = input.indexOf(braceStr);
|
|
943
|
-
if (cursor === -1) break;
|
|
944
|
-
try {
|
|
945
|
-
const [eStart1, eEnd1] = findBrackets(input, cursor, 1);
|
|
946
|
-
const [eStart2, eEnd2] = findBrackets(input, eEnd1, 1);
|
|
947
|
-
const elem1 = input.slice(eStart1, eEnd1);
|
|
948
|
-
const elem2 = input.slice(eStart2, eEnd2);
|
|
949
|
-
const beforeBrace = input.slice(0, cursor);
|
|
950
|
-
const afterBrace = input.slice(eEnd2);
|
|
951
|
-
input = beforeBrace + braceElem + elem1 + "^" + elem2 + afterBrace;
|
|
952
|
-
} catch {
|
|
953
|
-
return input;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return input;
|
|
957
|
-
};
|
|
958
|
-
for (const [braceKey, braceElem] of Object.entries(BRACE_CONVERT_MAP)) {
|
|
959
|
-
eqString = replaceBrace(eqString, braceKey, braceElem);
|
|
960
|
-
}
|
|
961
|
-
return eqString;
|
|
962
|
-
}
|
|
963
|
-
function replaceBracket(strList) {
|
|
964
|
-
for (let i = 0; i < strList.length; i++) {
|
|
965
|
-
if (strList[i] === "{" && i > 0 && strList[i - 1] === "\\left") strList[i] = "\\{";
|
|
966
|
-
if (strList[i] === "}" && i > 0 && strList[i - 1] === "\\right") strList[i] = "\\}";
|
|
967
|
-
}
|
|
968
|
-
return strList;
|
|
969
|
-
}
|
|
970
|
-
function hmlToLatex(hmlEqStr) {
|
|
971
|
-
if (!hmlEqStr) return "";
|
|
972
|
-
let s = hmlEqStr.replace(/`/g, " ");
|
|
973
|
-
s = s.replace(/\{/g, " { ").replace(/\}/g, " } ").replace(/&/g, " & ");
|
|
974
|
-
let tokens = s.split(" ");
|
|
975
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
976
|
-
const t = tokens[i];
|
|
977
|
-
if (Object.hasOwn(CONVERT_MAP, t)) tokens[i] = CONVERT_MAP[t];
|
|
978
|
-
else if (Object.hasOwn(MIDDLE_CONVERT_MAP, t)) tokens[i] = MIDDLE_CONVERT_MAP[t];
|
|
979
|
-
else {
|
|
980
|
-
const quoted = /^"(.+)"$/.exec(t);
|
|
981
|
-
if (quoted) tokens[i] = `\\text{${quoted[1]}}`;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
tokens = tokens.filter((tok) => tok.length !== 0);
|
|
985
|
-
tokens = replaceBracket(tokens);
|
|
986
|
-
let out = tokens.join(" ");
|
|
987
|
-
out = replaceFrac(out);
|
|
988
|
-
out = replaceRootOf(out);
|
|
989
|
-
out = replaceAllMatrix(out);
|
|
990
|
-
out = replaceAllBar(out);
|
|
991
|
-
out = replaceAllBrace(out);
|
|
992
|
-
return out;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// src/shared/numbering.ts
|
|
996
|
-
var HANGUL_INITIALS = [0, 2, 3, 5, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18];
|
|
997
|
-
var HANGUL_MEDIALS = [0, 4, 8, 13, 18, 20];
|
|
998
|
-
function hangulOrdinal(n) {
|
|
999
|
-
const cols = HANGUL_INITIALS.length;
|
|
1000
|
-
const vowel = HANGUL_MEDIALS[Math.min(Math.floor(n / cols), HANGUL_MEDIALS.length - 1)];
|
|
1001
|
-
const init = HANGUL_INITIALS[n % cols];
|
|
1002
|
-
return String.fromCodePoint(44032 + init * 588 + vowel * 28);
|
|
1003
|
-
}
|
|
1004
|
-
function circledNumber(n) {
|
|
1005
|
-
if (n < 20) return String.fromCodePoint(9312 + n);
|
|
1006
|
-
if (n < 35) return String.fromCodePoint(12881 + (n - 20));
|
|
1007
|
-
if (n < 50) return String.fromCodePoint(12977 + (n - 35));
|
|
1008
|
-
return `(${n + 1})`;
|
|
1009
|
-
}
|
|
1010
|
-
function circledHangul(n) {
|
|
1011
|
-
return n < 14 ? String.fromCodePoint(12910 + n) : hangulOrdinal(n);
|
|
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
|
-
}
|
|
1033
|
-
function romanNumeral(n, upper) {
|
|
1034
|
-
if (n <= 0 || n > 3999) return String(n);
|
|
1035
|
-
const values = [1e3, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
|
|
1036
|
-
const symbols = upper ? ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] : ["m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"];
|
|
1037
|
-
let result = "";
|
|
1038
|
-
let num2 = n;
|
|
1039
|
-
for (let i = 0; i < values.length; i++) {
|
|
1040
|
-
while (num2 >= values[i]) {
|
|
1041
|
-
result += symbols[i];
|
|
1042
|
-
num2 -= values[i];
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
return result;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
547
|
// src/hwpx/para-heading.ts
|
|
1049
548
|
var HANGUL_JAMO_SEQ = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
1050
549
|
function formatHeadNumber(n, numFormat) {
|
|
@@ -2262,446 +1761,6 @@ function gongmunDepthFromIndent(para2, left, ctx) {
|
|
|
2262
1761
|
return depth >= 1 && depth < 8 ? depth : null;
|
|
2263
1762
|
}
|
|
2264
1763
|
|
|
2265
|
-
// src/hwp5/record.ts
|
|
2266
|
-
import { inflateRawSync as inflateRawSync2, inflateSync } from "zlib";
|
|
2267
|
-
var TAG_PARA_HEADER = 66;
|
|
2268
|
-
var TAG_PARA_TEXT = 67;
|
|
2269
|
-
var TAG_CHAR_SHAPE = 68;
|
|
2270
|
-
var TAG_PARA_LINE_SEG = 69;
|
|
2271
|
-
var TAG_CTRL_HEADER = 71;
|
|
2272
|
-
var TAG_LIST_HEADER = 72;
|
|
2273
|
-
var TAG_TABLE = 77;
|
|
2274
|
-
var TAG_SHAPE_COMPONENT = 76;
|
|
2275
|
-
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2276
|
-
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2277
|
-
var TAG_EQEDIT = 88;
|
|
2278
|
-
var TAG_BIN_DATA = 18;
|
|
2279
|
-
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2280
|
-
var TAG_NUMBERING = 23;
|
|
2281
|
-
var TAG_BULLET = 24;
|
|
2282
|
-
var TAG_DOC_PARA_SHAPE = 25;
|
|
2283
|
-
var TAG_DOC_STYLE = 26;
|
|
2284
|
-
var CHAR_LINE = 0;
|
|
2285
|
-
var CHAR_SECTION_BREAK = 10;
|
|
2286
|
-
var CHAR_PARA = 13;
|
|
2287
|
-
var CHAR_TAB = 9;
|
|
2288
|
-
var CHAR_HYPHEN = 24;
|
|
2289
|
-
var CHAR_NBSP = 30;
|
|
2290
|
-
var CHAR_FIXED_WIDTH = 31;
|
|
2291
|
-
var FLAG_COMPRESSED = 1 << 0;
|
|
2292
|
-
var FLAG_ENCRYPTED = 1 << 1;
|
|
2293
|
-
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2294
|
-
var FLAG_DRM = 1 << 4;
|
|
2295
|
-
var MAX_RECORDS = 5e5;
|
|
2296
|
-
function readRecords(data) {
|
|
2297
|
-
const records = [];
|
|
2298
|
-
let offset = 0;
|
|
2299
|
-
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2300
|
-
const header = data.readUInt32LE(offset);
|
|
2301
|
-
offset += 4;
|
|
2302
|
-
const tagId = header & 1023;
|
|
2303
|
-
const level = header >> 10 & 1023;
|
|
2304
|
-
let size = header >> 20 & 4095;
|
|
2305
|
-
if (size === 4095) {
|
|
2306
|
-
if (offset + 4 > data.length) break;
|
|
2307
|
-
size = data.readUInt32LE(offset);
|
|
2308
|
-
offset += 4;
|
|
2309
|
-
}
|
|
2310
|
-
if (offset + size > data.length) break;
|
|
2311
|
-
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2312
|
-
offset += size;
|
|
2313
|
-
}
|
|
2314
|
-
return records;
|
|
2315
|
-
}
|
|
2316
|
-
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
2317
|
-
function decompressStream(data) {
|
|
2318
|
-
const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE2 };
|
|
2319
|
-
if (data.length >= 2 && data[0] === 120) {
|
|
2320
|
-
try {
|
|
2321
|
-
return inflateSync(data, opts);
|
|
2322
|
-
} catch {
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
return inflateRawSync2(data, opts);
|
|
2326
|
-
}
|
|
2327
|
-
function parseFileHeader(data) {
|
|
2328
|
-
if (data.length < 40) throw new KordocError("FileHeader\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 40\uBC14\uC774\uD2B8)");
|
|
2329
|
-
const sig = data.subarray(0, 32).toString("utf8").replace(/\0+$/, "");
|
|
2330
|
-
return {
|
|
2331
|
-
signature: sig,
|
|
2332
|
-
versionMajor: data[35],
|
|
2333
|
-
flags: data.readUInt32LE(36)
|
|
2334
|
-
};
|
|
2335
|
-
}
|
|
2336
|
-
function readHwpString(data, offset) {
|
|
2337
|
-
if (offset + 2 > data.length) return { value: "", next: data.length };
|
|
2338
|
-
const len = data.readUInt16LE(offset);
|
|
2339
|
-
const start = offset + 2;
|
|
2340
|
-
const end = start + len * 2;
|
|
2341
|
-
if (len === 0 || end > data.length) return { value: "", next: start };
|
|
2342
|
-
return { value: data.subarray(start, end).toString("utf16le"), next: end };
|
|
2343
|
-
}
|
|
2344
|
-
function parseDocInfo(records) {
|
|
2345
|
-
const charShapes = [];
|
|
2346
|
-
const paraShapes = [];
|
|
2347
|
-
const styles = [];
|
|
2348
|
-
const binData = [];
|
|
2349
|
-
const numberings = [];
|
|
2350
|
-
const bullets = [];
|
|
2351
|
-
for (const rec of records) {
|
|
2352
|
-
if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {
|
|
2353
|
-
const attr1 = rec.data.readUInt32LE(0);
|
|
2354
|
-
const headType = attr1 >>> 23 & 3;
|
|
2355
|
-
const paraLevel = attr1 >>> 25 & 7;
|
|
2356
|
-
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2357
|
-
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2358
|
-
}
|
|
2359
|
-
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2360
|
-
const attr = rec.data.readUInt16LE(0);
|
|
2361
|
-
const typeBits = attr & 15;
|
|
2362
|
-
if (typeBits === 0) {
|
|
2363
|
-
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2364
|
-
} else {
|
|
2365
|
-
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2366
|
-
const { value: extension } = readHwpString(rec.data, 4);
|
|
2367
|
-
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
if (rec.tagId === TAG_NUMBERING && rec.data.length >= 14) {
|
|
2371
|
-
const levelFormats = [];
|
|
2372
|
-
const numberFormats = [];
|
|
2373
|
-
const startNumbers = [1, 1, 1, 1, 1, 1, 1];
|
|
2374
|
-
let offset = 0;
|
|
2375
|
-
for (let level = 0; level < 7; level++) {
|
|
2376
|
-
if (offset + 12 > rec.data.length) {
|
|
2377
|
-
levelFormats.push("");
|
|
2378
|
-
numberFormats.push(0);
|
|
2379
|
-
continue;
|
|
2380
|
-
}
|
|
2381
|
-
const attr = rec.data.readUInt32LE(offset);
|
|
2382
|
-
numberFormats.push(attr >>> 5 & 15);
|
|
2383
|
-
offset += 12;
|
|
2384
|
-
const { value, next } = readHwpString(rec.data, offset);
|
|
2385
|
-
levelFormats.push(value);
|
|
2386
|
-
offset = next;
|
|
2387
|
-
}
|
|
2388
|
-
let baseStart = 1;
|
|
2389
|
-
if (offset + 2 <= rec.data.length) {
|
|
2390
|
-
baseStart = rec.data.readUInt16LE(offset) || 1;
|
|
2391
|
-
offset += 2;
|
|
2392
|
-
}
|
|
2393
|
-
for (let level = 0; level < 7; level++) {
|
|
2394
|
-
if (offset + 4 <= rec.data.length) {
|
|
2395
|
-
startNumbers[level] = rec.data.readUInt32LE(offset) || 1;
|
|
2396
|
-
offset += 4;
|
|
2397
|
-
} else {
|
|
2398
|
-
startNumbers[level] = baseStart;
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
2402
|
-
}
|
|
2403
|
-
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
2404
|
-
const code = rec.data.readUInt16LE(12);
|
|
2405
|
-
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
2406
|
-
}
|
|
2407
|
-
if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {
|
|
2408
|
-
if (rec.data.length >= 50) {
|
|
2409
|
-
const fontSize = rec.data.readUInt32LE(42);
|
|
2410
|
-
const attrFlags = rec.data.readUInt32LE(46);
|
|
2411
|
-
charShapes.push({ fontSize, attrFlags });
|
|
2412
|
-
} else {
|
|
2413
|
-
charShapes.push({ fontSize: 0, attrFlags: 0 });
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {
|
|
2417
|
-
try {
|
|
2418
|
-
let offset = 0;
|
|
2419
|
-
const nameLen = rec.data.readUInt16LE(offset);
|
|
2420
|
-
offset += 2;
|
|
2421
|
-
const nameBytes = nameLen * 2;
|
|
2422
|
-
const name = nameBytes > 0 && offset + nameBytes <= rec.data.length ? rec.data.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
2423
|
-
offset += nameBytes;
|
|
2424
|
-
let nameKo = "";
|
|
2425
|
-
if (offset + 2 <= rec.data.length) {
|
|
2426
|
-
const nameKoLen = rec.data.readUInt16LE(offset);
|
|
2427
|
-
offset += 2;
|
|
2428
|
-
const nameKoBytes = nameKoLen * 2;
|
|
2429
|
-
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
2430
|
-
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
2431
|
-
}
|
|
2432
|
-
offset += nameKoBytes;
|
|
2433
|
-
}
|
|
2434
|
-
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
2435
|
-
offset += 1;
|
|
2436
|
-
offset += 1;
|
|
2437
|
-
offset += 2;
|
|
2438
|
-
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2439
|
-
offset += 2;
|
|
2440
|
-
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2441
|
-
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
2442
|
-
} catch {
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2447
|
-
}
|
|
2448
|
-
function createParaTextState() {
|
|
2449
|
-
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2450
|
-
}
|
|
2451
|
-
var HANCOM_FOLDED_PUA_START = 40960;
|
|
2452
|
-
var HANCOM_FOLDED_PUA_END = 42124;
|
|
2453
|
-
var HANCOM_FOLDED_PUA_SHIFT = 983040 - 40960;
|
|
2454
|
-
function isExtendedOnlyCtrlChar(ch) {
|
|
2455
|
-
return ch >= 1 && ch <= 3 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 18 || ch >= 21 && ch <= 23;
|
|
2456
|
-
}
|
|
2457
|
-
function appendParaText(state, data, resolveControl) {
|
|
2458
|
-
let result = "";
|
|
2459
|
-
let i = 0;
|
|
2460
|
-
const base = state.text.length;
|
|
2461
|
-
const resolveAt = (byteOffset, extended) => {
|
|
2462
|
-
const ctrlId = data.readUInt32LE(byteOffset);
|
|
2463
|
-
const idx = extended ? state.ctrlIdx : -1;
|
|
2464
|
-
const replacement = resolveControl?.(idx, ctrlId);
|
|
2465
|
-
if (replacement) result += replacement;
|
|
2466
|
-
if (extended) state.ctrlIdx++;
|
|
2467
|
-
};
|
|
2468
|
-
while (i + 1 < data.length) {
|
|
2469
|
-
const ch = data.readUInt16LE(i);
|
|
2470
|
-
i += 2;
|
|
2471
|
-
switch (ch) {
|
|
2472
|
-
// ── char 타입 (2바이트만, 확장 데이터 없음) ──
|
|
2473
|
-
case CHAR_LINE:
|
|
2474
|
-
result += "\n";
|
|
2475
|
-
break;
|
|
2476
|
-
case CHAR_SECTION_BREAK: {
|
|
2477
|
-
if (i + 16 <= data.length && data.readUInt16LE(i) === 11) {
|
|
2478
|
-
resolveAt(i + 2, true);
|
|
2479
|
-
i += 16;
|
|
2480
|
-
break;
|
|
2481
|
-
}
|
|
2482
|
-
result += "\n";
|
|
2483
|
-
break;
|
|
2484
|
-
}
|
|
2485
|
-
case CHAR_PARA:
|
|
2486
|
-
break;
|
|
2487
|
-
// 문단 끝
|
|
2488
|
-
// 하이픈(24)은 한컴이 그리지 않는다 — 한컴 PDF·rhwp export-hwpx 모두 "60g/㎡"(licbyl2 17855137,
|
|
2489
|
-
// 전 코퍼스 유일 출현). 실렌더가 진실이므로 방출하지 않는다 (v4.12.3, 종전 "-")
|
|
2490
|
-
case CHAR_HYPHEN:
|
|
2491
|
-
break;
|
|
2492
|
-
case CHAR_NBSP:
|
|
2493
|
-
result += "\xA0";
|
|
2494
|
-
break;
|
|
2495
|
-
// 진짜 NBSP
|
|
2496
|
-
case CHAR_FIXED_WIDTH:
|
|
2497
|
-
result += " ";
|
|
2498
|
-
break;
|
|
2499
|
-
// 고정폭 공백
|
|
2500
|
-
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
2501
|
-
case CHAR_TAB:
|
|
2502
|
-
result += " ";
|
|
2503
|
-
if (i + 14 <= data.length) i += 14;
|
|
2504
|
-
break;
|
|
2505
|
-
default:
|
|
2506
|
-
if (ch >= 1 && ch <= 31) {
|
|
2507
|
-
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
2508
|
-
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
2509
|
-
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
2510
|
-
if (ch === 3) {
|
|
2511
|
-
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
2512
|
-
} else if (ch === 4) {
|
|
2513
|
-
const open = state.fieldStack.pop();
|
|
2514
|
-
if (open) {
|
|
2515
|
-
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
resolveAt(i, isExtended);
|
|
2519
|
-
i += 14;
|
|
2520
|
-
}
|
|
2521
|
-
} else if (ch >= 32) {
|
|
2522
|
-
if (ch >= HANCOM_FOLDED_PUA_START && ch <= HANCOM_FOLDED_PUA_END) {
|
|
2523
|
-
result += String.fromCodePoint(ch + HANCOM_FOLDED_PUA_SHIFT);
|
|
2524
|
-
break;
|
|
2525
|
-
}
|
|
2526
|
-
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2527
|
-
const lo = data.readUInt16LE(i);
|
|
2528
|
-
if (lo >= 56320 && lo <= 57343) {
|
|
2529
|
-
i += 2;
|
|
2530
|
-
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2531
|
-
result += String.fromCodePoint(codePoint);
|
|
2532
|
-
break;
|
|
2533
|
-
}
|
|
2534
|
-
}
|
|
2535
|
-
result += String.fromCharCode(ch);
|
|
2536
|
-
}
|
|
2537
|
-
break;
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
state.text += result;
|
|
2541
|
-
}
|
|
2542
|
-
function extractEquationText(data) {
|
|
2543
|
-
if (data.length < 6) return null;
|
|
2544
|
-
const scriptLength = data.readUInt16LE(4);
|
|
2545
|
-
const scriptStart = 6;
|
|
2546
|
-
const scriptEnd = scriptStart + scriptLength * 2;
|
|
2547
|
-
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
2548
|
-
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
2549
|
-
return equation || null;
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
// src/hwp5/images.ts
|
|
2553
|
-
function detectImageMime(data) {
|
|
2554
|
-
if (data.length < 4) return null;
|
|
2555
|
-
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
2556
|
-
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
2557
|
-
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
2558
|
-
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
2559
|
-
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
2560
|
-
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
2561
|
-
return null;
|
|
2562
|
-
}
|
|
2563
|
-
function normalizeBinPayload(data) {
|
|
2564
|
-
if (detectImageMime(data)) return data;
|
|
2565
|
-
try {
|
|
2566
|
-
const inflated = decompressStream(data);
|
|
2567
|
-
if (inflated.length > 0) return inflated;
|
|
2568
|
-
} catch {
|
|
2569
|
-
}
|
|
2570
|
-
return data;
|
|
2571
|
-
}
|
|
2572
|
-
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
2573
|
-
var MAX_BLOCK_DEPTH = 200;
|
|
2574
|
-
function collectImageBlocks(blocks, out, depth = 0) {
|
|
2575
|
-
if (depth > MAX_BLOCK_DEPTH) return;
|
|
2576
|
-
for (const b of blocks) {
|
|
2577
|
-
if (b.type === "image") out.push(b);
|
|
2578
|
-
if (b.table) {
|
|
2579
|
-
for (const row of b.table.cells) {
|
|
2580
|
-
for (const cell2 of row) {
|
|
2581
|
-
if (cell2.blocks) collectImageBlocks(cell2.blocks, out, depth + 1);
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
if (b.children) collectImageBlocks(b.children, out, depth + 1);
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
function forEachTableCell(blocks, fn) {
|
|
2589
|
-
for (const b of blocks) {
|
|
2590
|
-
if (b.table) {
|
|
2591
|
-
for (const row of b.table.cells) {
|
|
2592
|
-
for (const cell2 of row) {
|
|
2593
|
-
fn(cell2);
|
|
2594
|
-
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
if (b.children) forEachTableCell(b.children, fn);
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
2602
|
-
function resolveCellImageSentinels(blocks, renamed) {
|
|
2603
|
-
forEachTableCell(blocks, (cell2) => {
|
|
2604
|
-
if (!cell2.text.includes("hwp5bin:")) return;
|
|
2605
|
-
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
2606
|
-
const filename = renamed.get(Number(idStr));
|
|
2607
|
-
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
2608
|
-
});
|
|
2609
|
-
});
|
|
2610
|
-
}
|
|
2611
|
-
function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
2612
|
-
const imageBlocks = [];
|
|
2613
|
-
collectImageBlocks(blocks, imageBlocks);
|
|
2614
|
-
const images = [];
|
|
2615
|
-
const renamed = /* @__PURE__ */ new Map();
|
|
2616
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
2617
|
-
let imageIndex = 0;
|
|
2618
|
-
for (const block of imageBlocks) {
|
|
2619
|
-
if (!block.text) continue;
|
|
2620
|
-
const storageId = parseInt(block.text, 10);
|
|
2621
|
-
if (isNaN(storageId)) continue;
|
|
2622
|
-
let img = resolved.get(storageId);
|
|
2623
|
-
if (img === void 0) {
|
|
2624
|
-
const bin = binDataMap.get(storageId);
|
|
2625
|
-
if (!bin) {
|
|
2626
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
2627
|
-
resolved.set(storageId, null);
|
|
2628
|
-
} else {
|
|
2629
|
-
const mime = detectImageMime(bin.data);
|
|
2630
|
-
if (!mime) {
|
|
2631
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2632
|
-
resolved.set(storageId, null);
|
|
2633
|
-
} else {
|
|
2634
|
-
imageIndex++;
|
|
2635
|
-
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2636
|
-
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
2637
|
-
resolved.set(storageId, img);
|
|
2638
|
-
images.push({ filename: img.filename, data: img.data, mimeType: img.mime, source: bin.name });
|
|
2639
|
-
renamed.set(storageId, img.filename);
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
img = resolved.get(storageId);
|
|
2643
|
-
}
|
|
2644
|
-
if (!img) {
|
|
2645
|
-
const bin = binDataMap.get(storageId);
|
|
2646
|
-
block.type = "paragraph";
|
|
2647
|
-
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
2648
|
-
continue;
|
|
2649
|
-
}
|
|
2650
|
-
block.text = img.filename;
|
|
2651
|
-
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
2652
|
-
}
|
|
2653
|
-
if (sweepUnreferenced) {
|
|
2654
|
-
for (const [storageId, bin] of [...binDataMap.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2655
|
-
if (resolved.has(storageId)) continue;
|
|
2656
|
-
const mime = detectImageMime(bin.data);
|
|
2657
|
-
if (!mime) continue;
|
|
2658
|
-
imageIndex++;
|
|
2659
|
-
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2660
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
2661
|
-
const data = new Uint8Array(bin.data);
|
|
2662
|
-
images.push({ filename, data, mimeType: mime, source: bin.name });
|
|
2663
|
-
blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2666
|
-
resolveCellImageSentinels(blocks, renamed);
|
|
2667
|
-
return images;
|
|
2668
|
-
}
|
|
2669
|
-
function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
2670
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
2671
|
-
if (fileIndex) {
|
|
2672
|
-
for (const entry of fileIndex) {
|
|
2673
|
-
if (!entry?.name || !entry.content) continue;
|
|
2674
|
-
const match = entry.name.match(BIN_ENTRY_RE);
|
|
2675
|
-
if (!match) continue;
|
|
2676
|
-
const idx = parseInt(match[1], 16);
|
|
2677
|
-
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
2678
|
-
binDataMap.set(idx, { data, name: entry.name });
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
2681
|
-
if (binDataMap.size === 0) {
|
|
2682
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
2683
|
-
return [];
|
|
2684
|
-
}
|
|
2685
|
-
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
2686
|
-
}
|
|
2687
|
-
function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
|
|
2688
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
2689
|
-
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
2690
|
-
for (const e of lcfb.entries()) {
|
|
2691
|
-
const match = e.name.match(binRe);
|
|
2692
|
-
if (!match) continue;
|
|
2693
|
-
const idx = parseInt(match[1], 16);
|
|
2694
|
-
const raw = lcfb.findStream(e.name);
|
|
2695
|
-
if (!raw) continue;
|
|
2696
|
-
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
2697
|
-
}
|
|
2698
|
-
if (binDataMap.size === 0) {
|
|
2699
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
2700
|
-
return [];
|
|
2701
|
-
}
|
|
2702
|
-
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
2703
|
-
}
|
|
2704
|
-
|
|
2705
1764
|
// src/hwpx/images.ts
|
|
2706
1765
|
function imageExtToMime(ext) {
|
|
2707
1766
|
switch (ext.toLowerCase()) {
|
|
@@ -2738,7 +1797,7 @@ function mimeToExt(mime) {
|
|
|
2738
1797
|
if (mime.includes("svg")) return "svg";
|
|
2739
1798
|
return "bin";
|
|
2740
1799
|
}
|
|
2741
|
-
function
|
|
1800
|
+
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
2742
1801
|
if (depth > MAX_XML_DEPTH) return;
|
|
2743
1802
|
for (const block of blocks) {
|
|
2744
1803
|
if (block.type === "image") {
|
|
@@ -2746,11 +1805,11 @@ function collectImageBlocks2(blocks, out, ownerCell, depth = 0) {
|
|
|
2746
1805
|
} else if (block.type === "table" && block.table) {
|
|
2747
1806
|
for (const row of block.table.cells) {
|
|
2748
1807
|
for (const cell2 of row) {
|
|
2749
|
-
if (cell2.blocks?.length)
|
|
1808
|
+
if (cell2.blocks?.length) collectImageBlocks(cell2.blocks, out, cell2, depth + 1);
|
|
2750
1809
|
}
|
|
2751
1810
|
}
|
|
2752
1811
|
}
|
|
2753
|
-
if (block.children?.length)
|
|
1812
|
+
if (block.children?.length) collectImageBlocks(block.children, out, ownerCell, depth + 1);
|
|
2754
1813
|
}
|
|
2755
1814
|
}
|
|
2756
1815
|
async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUnreferenced) {
|
|
@@ -2758,7 +1817,7 @@ async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUn
|
|
|
2758
1817
|
let imageIndex = 0;
|
|
2759
1818
|
const usedPaths = /* @__PURE__ */ new Set();
|
|
2760
1819
|
const imageBlocks = [];
|
|
2761
|
-
|
|
1820
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
2762
1821
|
const resolved = /* @__PURE__ */ new Map();
|
|
2763
1822
|
for (const { block, ownerCell } of imageBlocks) {
|
|
2764
1823
|
if (block.type !== "image" || !block.text) continue;
|
|
@@ -2851,7 +1910,7 @@ async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUn
|
|
|
2851
1910
|
import JSZip from "jszip";
|
|
2852
1911
|
|
|
2853
1912
|
// src/hwpx/zip-sections.ts
|
|
2854
|
-
import { inflateRawSync as
|
|
1913
|
+
import { inflateRawSync as inflateRawSync2 } from "zlib";
|
|
2855
1914
|
function extractFromBrokenZip(buffer) {
|
|
2856
1915
|
const data = new Uint8Array(buffer);
|
|
2857
1916
|
const view = new DataView(buffer);
|
|
@@ -2902,7 +1961,7 @@ function extractFromBrokenZip(buffer) {
|
|
|
2902
1961
|
if (method === 0) {
|
|
2903
1962
|
content = new TextDecoder().decode(fileData);
|
|
2904
1963
|
} else if (method === 8) {
|
|
2905
|
-
const decompressed =
|
|
1964
|
+
const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
2906
1965
|
content = new TextDecoder().decode(decompressed);
|
|
2907
1966
|
} else {
|
|
2908
1967
|
continue;
|
|
@@ -2988,2224 +2047,132 @@ async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
|
2988
2047
|
}
|
|
2989
2048
|
}
|
|
2990
2049
|
function parseDublinCoreMetadata(xml, metadata) {
|
|
2991
|
-
const parser = createXmlParser();
|
|
2992
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2993
|
-
if (!doc.documentElement) return;
|
|
2994
|
-
const getText = (tagNames) => {
|
|
2995
|
-
for (const tag of tagNames) {
|
|
2996
|
-
const els = doc.getElementsByTagName(tag);
|
|
2997
|
-
if (els.length > 0) {
|
|
2998
|
-
const text = els[0].textContent?.trim();
|
|
2999
|
-
if (text) return text;
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
return void 0;
|
|
3003
|
-
};
|
|
3004
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
3005
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
3006
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
3007
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
3008
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
3009
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
3010
|
-
if (keywords && !metadata.keywords) {
|
|
3011
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
async function extractHwpxMetadataOnly(buffer) {
|
|
3015
|
-
let zip;
|
|
3016
|
-
try {
|
|
3017
|
-
zip = await JSZip.loadAsync(buffer);
|
|
3018
|
-
} catch {
|
|
3019
|
-
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3020
|
-
}
|
|
3021
|
-
const metadata = {};
|
|
3022
|
-
await extractHwpxMetadata(zip, metadata);
|
|
3023
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
3024
|
-
metadata.pageCount = sectionPaths.length;
|
|
3025
|
-
return metadata;
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3028
|
-
// src/hwpx/parser.ts
|
|
3029
|
-
async function parseHwpxDocument(buffer, options) {
|
|
3030
|
-
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
3031
|
-
let zip;
|
|
3032
|
-
try {
|
|
3033
|
-
zip = await JSZip2.loadAsync(buffer);
|
|
3034
|
-
} catch {
|
|
3035
|
-
return extractFromBrokenZip(buffer);
|
|
3036
|
-
}
|
|
3037
|
-
const actualEntryCount = Object.keys(zip.files).length;
|
|
3038
|
-
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
3039
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3040
|
-
}
|
|
3041
|
-
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
3042
|
-
if (manifestFile) {
|
|
3043
|
-
const manifestXml = await manifestFile.async("text");
|
|
3044
|
-
if (isEncryptedHwpx(manifestXml)) {
|
|
3045
|
-
if (options?.password) {
|
|
3046
|
-
await decryptHwpxInPlace(zip, manifestXml, options.password);
|
|
3047
|
-
} else {
|
|
3048
|
-
if (isComFallbackAvailable() && options?.filePath) {
|
|
3049
|
-
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
3050
|
-
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
3051
|
-
return comResultToParseResult(pages, pageCount, warnings2);
|
|
3052
|
-
}
|
|
3053
|
-
}
|
|
3054
|
-
throw new KordocError(
|
|
3055
|
-
"\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWPX \uD30C\uC77C\uC785\uB2C8\uB2E4. password \uC635\uC158\uC5D0 \uC5F4\uAE30 \uC554\uD638\uB97C \uC9C0\uC815\uD558\uC138\uC694."
|
|
3056
|
-
);
|
|
3057
|
-
}
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
const decompressed = { total: 0 };
|
|
3061
|
-
const metadata = {};
|
|
3062
|
-
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
3063
|
-
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
3064
|
-
const warnings = [];
|
|
3065
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
3066
|
-
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3067
|
-
let allBlocks = [];
|
|
3068
|
-
const shared = createSectionShared();
|
|
3069
|
-
shared.kordocLayout = await readKordocLayout(zip);
|
|
3070
|
-
shared.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
3071
|
-
shared.keepEmptyParagraphs = options?.keepEmptyParagraphs;
|
|
3072
|
-
const sectionRanges = [];
|
|
3073
|
-
let parsedSections = 0;
|
|
3074
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3075
|
-
const file = zip.file(sectionPaths[si]);
|
|
3076
|
-
if (!file) continue;
|
|
3077
|
-
try {
|
|
3078
|
-
const xml = await file.async("text");
|
|
3079
|
-
decompressed.total += xml.length * 2;
|
|
3080
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new ZipBombError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3081
|
-
const start = allBlocks.length;
|
|
3082
|
-
allBlocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3083
|
-
sectionRanges.push({ sectionNum: si + 1, start, end: allBlocks.length });
|
|
3084
|
-
parsedSections++;
|
|
3085
|
-
options?.onProgress?.(parsedSections, sectionPaths.length);
|
|
3086
|
-
} catch (secErr) {
|
|
3087
|
-
if (secErr instanceof ZipBombError) throw secErr;
|
|
3088
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
const layoutPages = shared.pageState.allUsable && parsedSections > 0;
|
|
3092
|
-
metadata.pageMode = layoutPages ? "layout" : "section";
|
|
3093
|
-
metadata.pageCount = layoutPages ? Math.max(shared.pageState.base, 1) : sectionPaths.length;
|
|
3094
|
-
if (!layoutPages) {
|
|
3095
|
-
for (const r of sectionRanges) {
|
|
3096
|
-
for (let b = r.start; b < r.end; b++) allBlocks[b].pageNumber = r.sectionNum;
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
if (options?.pages) {
|
|
3100
|
-
const pageFilter = parsePageRange(options.pages, metadata.pageCount);
|
|
3101
|
-
allBlocks = allBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
|
|
3102
|
-
if (!layoutPages) {
|
|
3103
|
-
warnings.push({ code: "PAGE_BOUNDARY_APPROXIMATE", message: "\uC870\uD310 \uCE90\uC2DC\uAC00 \uC5C6\uC5B4 pages \uD544\uD130\uB97C \uC139\uC158 \uB2E8\uC704 \uADFC\uC0AC\uB85C \uC801\uC6A9\uD588\uC2B5\uB2C8\uB2E4" });
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
const blocks = allBlocks;
|
|
3107
|
-
applyPageText(blocks, shared);
|
|
3108
|
-
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !options?.pages);
|
|
3109
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
3110
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3111
|
-
const markdown = blocksToMarkdown(blocks);
|
|
3112
|
-
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
3113
|
-
}
|
|
3114
|
-
|
|
3115
|
-
// src/hwp5/numbering.ts
|
|
3116
|
-
var NumberingState = class {
|
|
3117
|
-
currentId = 0;
|
|
3118
|
-
counters = [0, 0, 0, 0, 0, 0, 0];
|
|
3119
|
-
history = /* @__PURE__ */ new Map();
|
|
3120
|
-
/** 번호 문단 처리: 카운터 갱신 후 수준별 카운터 스냅샷 반환 */
|
|
3121
|
-
advance(numberingId, level) {
|
|
3122
|
-
const lv = Math.min(Math.max(level, 0), 6);
|
|
3123
|
-
if (this.currentId !== numberingId) {
|
|
3124
|
-
if (this.currentId !== 0) this.history.set(this.currentId, [...this.counters]);
|
|
3125
|
-
const saved = this.history.get(numberingId);
|
|
3126
|
-
if (saved) {
|
|
3127
|
-
this.counters = [...saved];
|
|
3128
|
-
} else {
|
|
3129
|
-
const prev = this.counters;
|
|
3130
|
-
this.counters = [0, 0, 0, 0, 0, 0, 0];
|
|
3131
|
-
for (let i = 0; i < lv; i++) this.counters[i] = prev[i];
|
|
3132
|
-
}
|
|
3133
|
-
this.currentId = numberingId;
|
|
3134
|
-
}
|
|
3135
|
-
this.counters[lv]++;
|
|
3136
|
-
for (let i = lv + 1; i < 7; i++) this.counters[i] = 0;
|
|
3137
|
-
return [...this.counters];
|
|
3138
|
-
}
|
|
3139
|
-
};
|
|
3140
|
-
function headFormatToNumFmt(code) {
|
|
3141
|
-
switch (code) {
|
|
3142
|
-
case 1:
|
|
3143
|
-
return "circled";
|
|
3144
|
-
case 2:
|
|
3145
|
-
return "romanUpper";
|
|
3146
|
-
case 3:
|
|
3147
|
-
return "romanLower";
|
|
3148
|
-
case 4:
|
|
3149
|
-
return "latinUpper";
|
|
3150
|
-
case 5:
|
|
3151
|
-
return "latinLower";
|
|
3152
|
-
case 8:
|
|
3153
|
-
return "ganada";
|
|
3154
|
-
case 9:
|
|
3155
|
-
return "circledGanada";
|
|
3156
|
-
case 10:
|
|
3157
|
-
return "jamo";
|
|
3158
|
-
case 11:
|
|
3159
|
-
return "circledJamo";
|
|
3160
|
-
case 12:
|
|
3161
|
-
return "hangulNum";
|
|
3162
|
-
case 13:
|
|
3163
|
-
return "hanjaNum";
|
|
3164
|
-
default:
|
|
3165
|
-
return "digit";
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3168
|
-
function shapeFormatToNumFmt(code) {
|
|
3169
|
-
switch (code) {
|
|
3170
|
-
case 1:
|
|
3171
|
-
return "circled";
|
|
3172
|
-
case 2:
|
|
3173
|
-
return "romanUpper";
|
|
3174
|
-
case 3:
|
|
3175
|
-
return "romanLower";
|
|
3176
|
-
case 4:
|
|
3177
|
-
return "latinUpper";
|
|
3178
|
-
case 5:
|
|
3179
|
-
return "latinLower";
|
|
3180
|
-
case 6:
|
|
3181
|
-
return "ganada";
|
|
3182
|
-
case 7:
|
|
3183
|
-
return "hangulNum";
|
|
3184
|
-
case 8:
|
|
3185
|
-
return "hanjaNum";
|
|
3186
|
-
default:
|
|
3187
|
-
return "digit";
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
var JAMO = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
3191
|
-
var CIRCLED_JAMO = "\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326A\u326B\u326C\u326D";
|
|
3192
|
-
function fromTable(n, table2) {
|
|
3193
|
-
return n >= 1 && n <= table2.length ? table2[n - 1] : String(n);
|
|
3194
|
-
}
|
|
3195
|
-
function formatLatin(n, upper) {
|
|
3196
|
-
if (n <= 0) return "";
|
|
3197
|
-
let result = "";
|
|
3198
|
-
let num2 = n;
|
|
3199
|
-
while (num2 > 0) {
|
|
3200
|
-
num2--;
|
|
3201
|
-
result = String.fromCharCode((upper ? 65 : 97) + num2 % 26) + result;
|
|
3202
|
-
num2 = Math.floor(num2 / 26);
|
|
3203
|
-
}
|
|
3204
|
-
return result;
|
|
3205
|
-
}
|
|
3206
|
-
function formatEastAsianNumber(n, digits, units, zero) {
|
|
3207
|
-
if (n === 0) return zero;
|
|
3208
|
-
if (n < 0 || n > 99999) return String(n);
|
|
3209
|
-
let result = "";
|
|
3210
|
-
let num2 = n;
|
|
3211
|
-
let unit = 0;
|
|
3212
|
-
while (num2 > 0) {
|
|
3213
|
-
const d = num2 % 10;
|
|
3214
|
-
if (d > 0) {
|
|
3215
|
-
const digitStr = d === 1 && unit > 0 ? "" : digits[d];
|
|
3216
|
-
result = digitStr + units[unit] + result;
|
|
3217
|
-
}
|
|
3218
|
-
num2 = Math.floor(num2 / 10);
|
|
3219
|
-
unit++;
|
|
3220
|
-
}
|
|
3221
|
-
return result;
|
|
3222
|
-
}
|
|
3223
|
-
var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
|
|
3224
|
-
var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
|
|
3225
|
-
var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
|
|
3226
|
-
var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
|
|
3227
|
-
function formatNumber(n, fmt) {
|
|
3228
|
-
switch (fmt) {
|
|
3229
|
-
case "circled":
|
|
3230
|
-
return n >= 1 ? circledNumber(n - 1) : String(n);
|
|
3231
|
-
case "romanUpper":
|
|
3232
|
-
return romanNumeral(n, true);
|
|
3233
|
-
case "romanLower":
|
|
3234
|
-
return romanNumeral(n, false);
|
|
3235
|
-
case "latinUpper":
|
|
3236
|
-
return formatLatin(n, true) || String(n);
|
|
3237
|
-
case "latinLower":
|
|
3238
|
-
return formatLatin(n, false) || String(n);
|
|
3239
|
-
case "ganada":
|
|
3240
|
-
return n >= 1 ? hangulOrdinal(n - 1) : String(n);
|
|
3241
|
-
case "circledGanada":
|
|
3242
|
-
return n >= 1 ? circledHangul(n - 1) : String(n);
|
|
3243
|
-
case "jamo":
|
|
3244
|
-
return fromTable(n, JAMO);
|
|
3245
|
-
case "circledJamo":
|
|
3246
|
-
return fromTable(n, CIRCLED_JAMO);
|
|
3247
|
-
case "hangulNum":
|
|
3248
|
-
return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
|
|
3249
|
-
case "hanjaNum":
|
|
3250
|
-
return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
|
|
3251
|
-
default:
|
|
3252
|
-
return String(n);
|
|
3253
|
-
}
|
|
3254
|
-
}
|
|
3255
|
-
function expandNumberingFormat(formatStr, counters, numbering) {
|
|
3256
|
-
let result = "";
|
|
3257
|
-
let i = 0;
|
|
3258
|
-
while (i < formatStr.length) {
|
|
3259
|
-
const ch = formatStr[i];
|
|
3260
|
-
if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
|
|
3261
|
-
const levelRef = formatStr.charCodeAt(i + 1) - 48;
|
|
3262
|
-
const idx = levelRef - 1;
|
|
3263
|
-
const counterVal = counters[idx] ?? 0;
|
|
3264
|
-
const start = numbering.startNumbers[idx] ?? 1;
|
|
3265
|
-
const num2 = counterVal > 0 ? start - 1 + counterVal : start;
|
|
3266
|
-
result += formatNumber(num2, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
|
|
3267
|
-
i += 2;
|
|
3268
|
-
continue;
|
|
3269
|
-
}
|
|
3270
|
-
result += ch;
|
|
3271
|
-
i++;
|
|
3272
|
-
}
|
|
3273
|
-
return result;
|
|
3274
|
-
}
|
|
3275
|
-
|
|
3276
|
-
// src/hwp5/aes.ts
|
|
3277
|
-
var S_BOX = new Uint8Array([
|
|
3278
|
-
99,
|
|
3279
|
-
124,
|
|
3280
|
-
119,
|
|
3281
|
-
123,
|
|
3282
|
-
242,
|
|
3283
|
-
107,
|
|
3284
|
-
111,
|
|
3285
|
-
197,
|
|
3286
|
-
48,
|
|
3287
|
-
1,
|
|
3288
|
-
103,
|
|
3289
|
-
43,
|
|
3290
|
-
254,
|
|
3291
|
-
215,
|
|
3292
|
-
171,
|
|
3293
|
-
118,
|
|
3294
|
-
202,
|
|
3295
|
-
130,
|
|
3296
|
-
201,
|
|
3297
|
-
125,
|
|
3298
|
-
250,
|
|
3299
|
-
89,
|
|
3300
|
-
71,
|
|
3301
|
-
240,
|
|
3302
|
-
173,
|
|
3303
|
-
212,
|
|
3304
|
-
162,
|
|
3305
|
-
175,
|
|
3306
|
-
156,
|
|
3307
|
-
164,
|
|
3308
|
-
114,
|
|
3309
|
-
192,
|
|
3310
|
-
183,
|
|
3311
|
-
253,
|
|
3312
|
-
147,
|
|
3313
|
-
38,
|
|
3314
|
-
54,
|
|
3315
|
-
63,
|
|
3316
|
-
247,
|
|
3317
|
-
204,
|
|
3318
|
-
52,
|
|
3319
|
-
165,
|
|
3320
|
-
229,
|
|
3321
|
-
241,
|
|
3322
|
-
113,
|
|
3323
|
-
216,
|
|
3324
|
-
49,
|
|
3325
|
-
21,
|
|
3326
|
-
4,
|
|
3327
|
-
199,
|
|
3328
|
-
35,
|
|
3329
|
-
195,
|
|
3330
|
-
24,
|
|
3331
|
-
150,
|
|
3332
|
-
5,
|
|
3333
|
-
154,
|
|
3334
|
-
7,
|
|
3335
|
-
18,
|
|
3336
|
-
128,
|
|
3337
|
-
226,
|
|
3338
|
-
235,
|
|
3339
|
-
39,
|
|
3340
|
-
178,
|
|
3341
|
-
117,
|
|
3342
|
-
9,
|
|
3343
|
-
131,
|
|
3344
|
-
44,
|
|
3345
|
-
26,
|
|
3346
|
-
27,
|
|
3347
|
-
110,
|
|
3348
|
-
90,
|
|
3349
|
-
160,
|
|
3350
|
-
82,
|
|
3351
|
-
59,
|
|
3352
|
-
214,
|
|
3353
|
-
179,
|
|
3354
|
-
41,
|
|
3355
|
-
227,
|
|
3356
|
-
47,
|
|
3357
|
-
132,
|
|
3358
|
-
83,
|
|
3359
|
-
209,
|
|
3360
|
-
0,
|
|
3361
|
-
237,
|
|
3362
|
-
32,
|
|
3363
|
-
252,
|
|
3364
|
-
177,
|
|
3365
|
-
91,
|
|
3366
|
-
106,
|
|
3367
|
-
203,
|
|
3368
|
-
190,
|
|
3369
|
-
57,
|
|
3370
|
-
74,
|
|
3371
|
-
76,
|
|
3372
|
-
88,
|
|
3373
|
-
207,
|
|
3374
|
-
208,
|
|
3375
|
-
239,
|
|
3376
|
-
170,
|
|
3377
|
-
251,
|
|
3378
|
-
67,
|
|
3379
|
-
77,
|
|
3380
|
-
51,
|
|
3381
|
-
133,
|
|
3382
|
-
69,
|
|
3383
|
-
249,
|
|
3384
|
-
2,
|
|
3385
|
-
127,
|
|
3386
|
-
80,
|
|
3387
|
-
60,
|
|
3388
|
-
159,
|
|
3389
|
-
168,
|
|
3390
|
-
81,
|
|
3391
|
-
163,
|
|
3392
|
-
64,
|
|
3393
|
-
143,
|
|
3394
|
-
146,
|
|
3395
|
-
157,
|
|
3396
|
-
56,
|
|
3397
|
-
245,
|
|
3398
|
-
188,
|
|
3399
|
-
182,
|
|
3400
|
-
218,
|
|
3401
|
-
33,
|
|
3402
|
-
16,
|
|
3403
|
-
255,
|
|
3404
|
-
243,
|
|
3405
|
-
210,
|
|
3406
|
-
205,
|
|
3407
|
-
12,
|
|
3408
|
-
19,
|
|
3409
|
-
236,
|
|
3410
|
-
95,
|
|
3411
|
-
151,
|
|
3412
|
-
68,
|
|
3413
|
-
23,
|
|
3414
|
-
196,
|
|
3415
|
-
167,
|
|
3416
|
-
126,
|
|
3417
|
-
61,
|
|
3418
|
-
100,
|
|
3419
|
-
93,
|
|
3420
|
-
25,
|
|
3421
|
-
115,
|
|
3422
|
-
96,
|
|
3423
|
-
129,
|
|
3424
|
-
79,
|
|
3425
|
-
220,
|
|
3426
|
-
34,
|
|
3427
|
-
42,
|
|
3428
|
-
144,
|
|
3429
|
-
136,
|
|
3430
|
-
70,
|
|
3431
|
-
238,
|
|
3432
|
-
184,
|
|
3433
|
-
20,
|
|
3434
|
-
222,
|
|
3435
|
-
94,
|
|
3436
|
-
11,
|
|
3437
|
-
219,
|
|
3438
|
-
224,
|
|
3439
|
-
50,
|
|
3440
|
-
58,
|
|
3441
|
-
10,
|
|
3442
|
-
73,
|
|
3443
|
-
6,
|
|
3444
|
-
36,
|
|
3445
|
-
92,
|
|
3446
|
-
194,
|
|
3447
|
-
211,
|
|
3448
|
-
172,
|
|
3449
|
-
98,
|
|
3450
|
-
145,
|
|
3451
|
-
149,
|
|
3452
|
-
228,
|
|
3453
|
-
121,
|
|
3454
|
-
231,
|
|
3455
|
-
200,
|
|
3456
|
-
55,
|
|
3457
|
-
109,
|
|
3458
|
-
141,
|
|
3459
|
-
213,
|
|
3460
|
-
78,
|
|
3461
|
-
169,
|
|
3462
|
-
108,
|
|
3463
|
-
86,
|
|
3464
|
-
244,
|
|
3465
|
-
234,
|
|
3466
|
-
101,
|
|
3467
|
-
122,
|
|
3468
|
-
174,
|
|
3469
|
-
8,
|
|
3470
|
-
186,
|
|
3471
|
-
120,
|
|
3472
|
-
37,
|
|
3473
|
-
46,
|
|
3474
|
-
28,
|
|
3475
|
-
166,
|
|
3476
|
-
180,
|
|
3477
|
-
198,
|
|
3478
|
-
232,
|
|
3479
|
-
221,
|
|
3480
|
-
116,
|
|
3481
|
-
31,
|
|
3482
|
-
75,
|
|
3483
|
-
189,
|
|
3484
|
-
139,
|
|
3485
|
-
138,
|
|
3486
|
-
112,
|
|
3487
|
-
62,
|
|
3488
|
-
181,
|
|
3489
|
-
102,
|
|
3490
|
-
72,
|
|
3491
|
-
3,
|
|
3492
|
-
246,
|
|
3493
|
-
14,
|
|
3494
|
-
97,
|
|
3495
|
-
53,
|
|
3496
|
-
87,
|
|
3497
|
-
185,
|
|
3498
|
-
134,
|
|
3499
|
-
193,
|
|
3500
|
-
29,
|
|
3501
|
-
158,
|
|
3502
|
-
225,
|
|
3503
|
-
248,
|
|
3504
|
-
152,
|
|
3505
|
-
17,
|
|
3506
|
-
105,
|
|
3507
|
-
217,
|
|
3508
|
-
142,
|
|
3509
|
-
148,
|
|
3510
|
-
155,
|
|
3511
|
-
30,
|
|
3512
|
-
135,
|
|
3513
|
-
233,
|
|
3514
|
-
206,
|
|
3515
|
-
85,
|
|
3516
|
-
40,
|
|
3517
|
-
223,
|
|
3518
|
-
140,
|
|
3519
|
-
161,
|
|
3520
|
-
137,
|
|
3521
|
-
13,
|
|
3522
|
-
191,
|
|
3523
|
-
230,
|
|
3524
|
-
66,
|
|
3525
|
-
104,
|
|
3526
|
-
65,
|
|
3527
|
-
153,
|
|
3528
|
-
45,
|
|
3529
|
-
15,
|
|
3530
|
-
176,
|
|
3531
|
-
84,
|
|
3532
|
-
187,
|
|
3533
|
-
22
|
|
3534
|
-
]);
|
|
3535
|
-
var INV_S_BOX = new Uint8Array([
|
|
3536
|
-
82,
|
|
3537
|
-
9,
|
|
3538
|
-
106,
|
|
3539
|
-
213,
|
|
3540
|
-
48,
|
|
3541
|
-
54,
|
|
3542
|
-
165,
|
|
3543
|
-
56,
|
|
3544
|
-
191,
|
|
3545
|
-
64,
|
|
3546
|
-
163,
|
|
3547
|
-
158,
|
|
3548
|
-
129,
|
|
3549
|
-
243,
|
|
3550
|
-
215,
|
|
3551
|
-
251,
|
|
3552
|
-
124,
|
|
3553
|
-
227,
|
|
3554
|
-
57,
|
|
3555
|
-
130,
|
|
3556
|
-
155,
|
|
3557
|
-
47,
|
|
3558
|
-
255,
|
|
3559
|
-
135,
|
|
3560
|
-
52,
|
|
3561
|
-
142,
|
|
3562
|
-
67,
|
|
3563
|
-
68,
|
|
3564
|
-
196,
|
|
3565
|
-
222,
|
|
3566
|
-
233,
|
|
3567
|
-
203,
|
|
3568
|
-
84,
|
|
3569
|
-
123,
|
|
3570
|
-
148,
|
|
3571
|
-
50,
|
|
3572
|
-
166,
|
|
3573
|
-
194,
|
|
3574
|
-
35,
|
|
3575
|
-
61,
|
|
3576
|
-
238,
|
|
3577
|
-
76,
|
|
3578
|
-
149,
|
|
3579
|
-
11,
|
|
3580
|
-
66,
|
|
3581
|
-
250,
|
|
3582
|
-
195,
|
|
3583
|
-
78,
|
|
3584
|
-
8,
|
|
3585
|
-
46,
|
|
3586
|
-
161,
|
|
3587
|
-
102,
|
|
3588
|
-
40,
|
|
3589
|
-
217,
|
|
3590
|
-
36,
|
|
3591
|
-
178,
|
|
3592
|
-
118,
|
|
3593
|
-
91,
|
|
3594
|
-
162,
|
|
3595
|
-
73,
|
|
3596
|
-
109,
|
|
3597
|
-
139,
|
|
3598
|
-
209,
|
|
3599
|
-
37,
|
|
3600
|
-
114,
|
|
3601
|
-
248,
|
|
3602
|
-
246,
|
|
3603
|
-
100,
|
|
3604
|
-
134,
|
|
3605
|
-
104,
|
|
3606
|
-
152,
|
|
3607
|
-
22,
|
|
3608
|
-
212,
|
|
3609
|
-
164,
|
|
3610
|
-
92,
|
|
3611
|
-
204,
|
|
3612
|
-
93,
|
|
3613
|
-
101,
|
|
3614
|
-
182,
|
|
3615
|
-
146,
|
|
3616
|
-
108,
|
|
3617
|
-
112,
|
|
3618
|
-
72,
|
|
3619
|
-
80,
|
|
3620
|
-
253,
|
|
3621
|
-
237,
|
|
3622
|
-
185,
|
|
3623
|
-
218,
|
|
3624
|
-
94,
|
|
3625
|
-
21,
|
|
3626
|
-
70,
|
|
3627
|
-
87,
|
|
3628
|
-
167,
|
|
3629
|
-
141,
|
|
3630
|
-
157,
|
|
3631
|
-
132,
|
|
3632
|
-
144,
|
|
3633
|
-
216,
|
|
3634
|
-
171,
|
|
3635
|
-
0,
|
|
3636
|
-
140,
|
|
3637
|
-
188,
|
|
3638
|
-
211,
|
|
3639
|
-
10,
|
|
3640
|
-
247,
|
|
3641
|
-
228,
|
|
3642
|
-
88,
|
|
3643
|
-
5,
|
|
3644
|
-
184,
|
|
3645
|
-
179,
|
|
3646
|
-
69,
|
|
3647
|
-
6,
|
|
3648
|
-
208,
|
|
3649
|
-
44,
|
|
3650
|
-
30,
|
|
3651
|
-
143,
|
|
3652
|
-
202,
|
|
3653
|
-
63,
|
|
3654
|
-
15,
|
|
3655
|
-
2,
|
|
3656
|
-
193,
|
|
3657
|
-
175,
|
|
3658
|
-
189,
|
|
3659
|
-
3,
|
|
3660
|
-
1,
|
|
3661
|
-
19,
|
|
3662
|
-
138,
|
|
3663
|
-
107,
|
|
3664
|
-
58,
|
|
3665
|
-
145,
|
|
3666
|
-
17,
|
|
3667
|
-
65,
|
|
3668
|
-
79,
|
|
3669
|
-
103,
|
|
3670
|
-
220,
|
|
3671
|
-
234,
|
|
3672
|
-
151,
|
|
3673
|
-
242,
|
|
3674
|
-
207,
|
|
3675
|
-
206,
|
|
3676
|
-
240,
|
|
3677
|
-
180,
|
|
3678
|
-
230,
|
|
3679
|
-
115,
|
|
3680
|
-
150,
|
|
3681
|
-
172,
|
|
3682
|
-
116,
|
|
3683
|
-
34,
|
|
3684
|
-
231,
|
|
3685
|
-
173,
|
|
3686
|
-
53,
|
|
3687
|
-
133,
|
|
3688
|
-
226,
|
|
3689
|
-
249,
|
|
3690
|
-
55,
|
|
3691
|
-
232,
|
|
3692
|
-
28,
|
|
3693
|
-
117,
|
|
3694
|
-
223,
|
|
3695
|
-
110,
|
|
3696
|
-
71,
|
|
3697
|
-
241,
|
|
3698
|
-
26,
|
|
3699
|
-
113,
|
|
3700
|
-
29,
|
|
3701
|
-
41,
|
|
3702
|
-
197,
|
|
3703
|
-
137,
|
|
3704
|
-
111,
|
|
3705
|
-
183,
|
|
3706
|
-
98,
|
|
3707
|
-
14,
|
|
3708
|
-
170,
|
|
3709
|
-
24,
|
|
3710
|
-
190,
|
|
3711
|
-
27,
|
|
3712
|
-
252,
|
|
3713
|
-
86,
|
|
3714
|
-
62,
|
|
3715
|
-
75,
|
|
3716
|
-
198,
|
|
3717
|
-
210,
|
|
3718
|
-
121,
|
|
3719
|
-
32,
|
|
3720
|
-
154,
|
|
3721
|
-
219,
|
|
3722
|
-
192,
|
|
3723
|
-
254,
|
|
3724
|
-
120,
|
|
3725
|
-
205,
|
|
3726
|
-
90,
|
|
3727
|
-
244,
|
|
3728
|
-
31,
|
|
3729
|
-
221,
|
|
3730
|
-
168,
|
|
3731
|
-
51,
|
|
3732
|
-
136,
|
|
3733
|
-
7,
|
|
3734
|
-
199,
|
|
3735
|
-
49,
|
|
3736
|
-
177,
|
|
3737
|
-
18,
|
|
3738
|
-
16,
|
|
3739
|
-
89,
|
|
3740
|
-
39,
|
|
3741
|
-
128,
|
|
3742
|
-
236,
|
|
3743
|
-
95,
|
|
3744
|
-
96,
|
|
3745
|
-
81,
|
|
3746
|
-
127,
|
|
3747
|
-
169,
|
|
3748
|
-
25,
|
|
3749
|
-
181,
|
|
3750
|
-
74,
|
|
3751
|
-
13,
|
|
3752
|
-
45,
|
|
3753
|
-
229,
|
|
3754
|
-
122,
|
|
3755
|
-
159,
|
|
3756
|
-
147,
|
|
3757
|
-
201,
|
|
3758
|
-
156,
|
|
3759
|
-
239,
|
|
3760
|
-
160,
|
|
3761
|
-
224,
|
|
3762
|
-
59,
|
|
3763
|
-
77,
|
|
3764
|
-
174,
|
|
3765
|
-
42,
|
|
3766
|
-
245,
|
|
3767
|
-
176,
|
|
3768
|
-
200,
|
|
3769
|
-
235,
|
|
3770
|
-
187,
|
|
3771
|
-
60,
|
|
3772
|
-
131,
|
|
3773
|
-
83,
|
|
3774
|
-
153,
|
|
3775
|
-
97,
|
|
3776
|
-
23,
|
|
3777
|
-
43,
|
|
3778
|
-
4,
|
|
3779
|
-
126,
|
|
3780
|
-
186,
|
|
3781
|
-
119,
|
|
3782
|
-
214,
|
|
3783
|
-
38,
|
|
3784
|
-
225,
|
|
3785
|
-
105,
|
|
3786
|
-
20,
|
|
3787
|
-
99,
|
|
3788
|
-
85,
|
|
3789
|
-
33,
|
|
3790
|
-
12,
|
|
3791
|
-
125
|
|
3792
|
-
]);
|
|
3793
|
-
var RCON = new Uint8Array([1, 2, 4, 8, 16, 32, 64, 128, 27, 54]);
|
|
3794
|
-
function gmul(a, b) {
|
|
3795
|
-
let p = 0;
|
|
3796
|
-
for (let i = 0; i < 8; i++) {
|
|
3797
|
-
if (b & 1) p ^= a;
|
|
3798
|
-
const hi = a & 128;
|
|
3799
|
-
a = a << 1 & 255;
|
|
3800
|
-
if (hi) a ^= 27;
|
|
3801
|
-
b >>= 1;
|
|
3802
|
-
}
|
|
3803
|
-
return p;
|
|
3804
|
-
}
|
|
3805
|
-
function expandKey(key) {
|
|
3806
|
-
const w = new Uint32Array(44);
|
|
3807
|
-
for (let i = 0; i < 4; i++) {
|
|
3808
|
-
w[i] = key[4 * i] << 24 | key[4 * i + 1] << 16 | key[4 * i + 2] << 8 | key[4 * i + 3];
|
|
3809
|
-
}
|
|
3810
|
-
for (let i = 4; i < 44; i++) {
|
|
3811
|
-
let temp = w[i - 1];
|
|
3812
|
-
if (i % 4 === 0) {
|
|
3813
|
-
temp = (temp << 8 | temp >>> 24) >>> 0;
|
|
3814
|
-
temp = S_BOX[temp >>> 24 & 255] << 24 | S_BOX[temp >>> 16 & 255] << 16 | S_BOX[temp >>> 8 & 255] << 8 | S_BOX[temp & 255];
|
|
3815
|
-
temp = (temp ^ RCON[i / 4 - 1] << 24) >>> 0;
|
|
3816
|
-
}
|
|
3817
|
-
w[i] = (w[i - 4] ^ temp) >>> 0;
|
|
3818
|
-
}
|
|
3819
|
-
return w;
|
|
3820
|
-
}
|
|
3821
|
-
function decryptBlock(block, roundKeys) {
|
|
3822
|
-
const s = new Uint8Array(16);
|
|
3823
|
-
for (let i = 0; i < 16; i++) s[i] = block[i];
|
|
3824
|
-
addRoundKey(s, roundKeys, 10);
|
|
3825
|
-
for (let round = 9; round >= 1; round--) {
|
|
3826
|
-
invShiftRows(s);
|
|
3827
|
-
invSubBytes(s);
|
|
3828
|
-
addRoundKey(s, roundKeys, round);
|
|
3829
|
-
invMixColumns(s);
|
|
3830
|
-
}
|
|
3831
|
-
invShiftRows(s);
|
|
3832
|
-
invSubBytes(s);
|
|
3833
|
-
addRoundKey(s, roundKeys, 0);
|
|
3834
|
-
return s;
|
|
3835
|
-
}
|
|
3836
|
-
function addRoundKey(s, w, round) {
|
|
3837
|
-
const base = round * 4;
|
|
3838
|
-
for (let c = 0; c < 4; c++) {
|
|
3839
|
-
const k = w[base + c];
|
|
3840
|
-
s[c * 4] ^= k >>> 24 & 255;
|
|
3841
|
-
s[c * 4 + 1] ^= k >>> 16 & 255;
|
|
3842
|
-
s[c * 4 + 2] ^= k >>> 8 & 255;
|
|
3843
|
-
s[c * 4 + 3] ^= k & 255;
|
|
3844
|
-
}
|
|
3845
|
-
}
|
|
3846
|
-
function invSubBytes(s) {
|
|
3847
|
-
for (let i = 0; i < 16; i++) s[i] = INV_S_BOX[s[i]];
|
|
3848
|
-
}
|
|
3849
|
-
function invShiftRows(s) {
|
|
3850
|
-
let t = s[13];
|
|
3851
|
-
s[13] = s[9];
|
|
3852
|
-
s[9] = s[5];
|
|
3853
|
-
s[5] = s[1];
|
|
3854
|
-
s[1] = t;
|
|
3855
|
-
t = s[2];
|
|
3856
|
-
s[2] = s[10];
|
|
3857
|
-
s[10] = t;
|
|
3858
|
-
t = s[6];
|
|
3859
|
-
s[6] = s[14];
|
|
3860
|
-
s[14] = t;
|
|
3861
|
-
t = s[3];
|
|
3862
|
-
s[3] = s[7];
|
|
3863
|
-
s[7] = s[11];
|
|
3864
|
-
s[11] = s[15];
|
|
3865
|
-
s[15] = t;
|
|
3866
|
-
}
|
|
3867
|
-
function invMixColumns(s) {
|
|
3868
|
-
for (let c = 0; c < 4; c++) {
|
|
3869
|
-
const i = c * 4;
|
|
3870
|
-
const a0 = s[i], a1 = s[i + 1], a2 = s[i + 2], a3 = s[i + 3];
|
|
3871
|
-
s[i] = gmul(a0, 14) ^ gmul(a1, 11) ^ gmul(a2, 13) ^ gmul(a3, 9);
|
|
3872
|
-
s[i + 1] = gmul(a0, 9) ^ gmul(a1, 14) ^ gmul(a2, 11) ^ gmul(a3, 13);
|
|
3873
|
-
s[i + 2] = gmul(a0, 13) ^ gmul(a1, 9) ^ gmul(a2, 14) ^ gmul(a3, 11);
|
|
3874
|
-
s[i + 3] = gmul(a0, 11) ^ gmul(a1, 13) ^ gmul(a2, 9) ^ gmul(a3, 14);
|
|
3875
|
-
}
|
|
3876
|
-
}
|
|
3877
|
-
function aes128EcbDecrypt(data, key) {
|
|
3878
|
-
if (key.length !== 16) throw new Error("AES-128 \uD0A4\uB294 16\uBC14\uC774\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
3879
|
-
if (data.length % 16 !== 0) throw new Error("AES ECB \uC785\uB825\uC740 16\uBC14\uC774\uD2B8\uC758 \uBC30\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4");
|
|
3880
|
-
const roundKeys = expandKey(key);
|
|
3881
|
-
const out = new Uint8Array(data.length);
|
|
3882
|
-
for (let offset = 0; offset < data.length; offset += 16) {
|
|
3883
|
-
const block = data.subarray(offset, offset + 16);
|
|
3884
|
-
const decrypted = decryptBlock(block, roundKeys);
|
|
3885
|
-
out.set(decrypted, offset);
|
|
3886
|
-
}
|
|
3887
|
-
return out;
|
|
3888
|
-
}
|
|
3889
|
-
|
|
3890
|
-
// src/hwp5/crypto.ts
|
|
3891
|
-
var MsvcLcg = class {
|
|
3892
|
-
seed;
|
|
3893
|
-
constructor(seed) {
|
|
3894
|
-
this.seed = seed >>> 0;
|
|
3895
|
-
}
|
|
3896
|
-
/** 0 ~ 0x7FFF 범위 난수 반환 (MSVC rand() 호환) */
|
|
3897
|
-
rand() {
|
|
3898
|
-
this.seed = Math.imul(this.seed, 214013) + 2531011 >>> 0;
|
|
3899
|
-
return this.seed >>> 16 & 32767;
|
|
3900
|
-
}
|
|
3901
|
-
};
|
|
3902
|
-
function decryptDistributePayload(payload) {
|
|
3903
|
-
if (payload.length < 256) throw new Error("\uBC30\uD3EC\uC6A9 payload\uAC00 256\uBC14\uC774\uD2B8 \uBBF8\uB9CC\uC785\uB2C8\uB2E4");
|
|
3904
|
-
const seed = (payload[0] | payload[1] << 8 | payload[2] << 16 | payload[3] << 24) >>> 0;
|
|
3905
|
-
const lcg = new MsvcLcg(seed);
|
|
3906
|
-
const result = new Uint8Array(payload.subarray(0, 256));
|
|
3907
|
-
let i = 0;
|
|
3908
|
-
let n = 0;
|
|
3909
|
-
let key = 0;
|
|
3910
|
-
while (i < 256) {
|
|
3911
|
-
if (n === 0) {
|
|
3912
|
-
key = lcg.rand() & 255;
|
|
3913
|
-
n = (lcg.rand() & 15) + 1;
|
|
3914
|
-
}
|
|
3915
|
-
if (i >= 4) {
|
|
3916
|
-
result[i] ^= key;
|
|
3917
|
-
}
|
|
3918
|
-
i++;
|
|
3919
|
-
n--;
|
|
3920
|
-
}
|
|
3921
|
-
return result;
|
|
3922
|
-
}
|
|
3923
|
-
function extractAesKey(decryptedPayload) {
|
|
3924
|
-
const offset = 4 + (decryptedPayload[0] & 15);
|
|
3925
|
-
if (offset + 16 > decryptedPayload.length) {
|
|
3926
|
-
throw new Error("AES \uD0A4 \uCD94\uCD9C \uC2E4\uD328: \uC624\uD504\uC14B\uC774 payload \uBC94\uC704\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4");
|
|
3927
|
-
}
|
|
3928
|
-
return decryptedPayload.slice(offset, offset + 16);
|
|
3929
|
-
}
|
|
3930
|
-
function parseRecordHeader(data, offset) {
|
|
3931
|
-
if (offset + 4 > data.length) throw new Error("\uB808\uCF54\uB4DC \uD5E4\uB354 \uD30C\uC2F1 \uC2E4\uD328: \uB370\uC774\uD130 \uBD80\uC871");
|
|
3932
|
-
const header = (data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16 | data[offset + 3] << 24) >>> 0;
|
|
3933
|
-
const tagId = header & 1023;
|
|
3934
|
-
let size = header >>> 20 & 4095;
|
|
3935
|
-
let headerSize = 4;
|
|
3936
|
-
if (size === 4095) {
|
|
3937
|
-
if (offset + 8 > data.length) throw new Error("\uD655\uC7A5 \uB808\uCF54\uB4DC \uD06C\uAE30 \uD30C\uC2F1 \uC2E4\uD328: \uB370\uC774\uD130 \uBD80\uC871");
|
|
3938
|
-
size = (data[offset + 4] | data[offset + 5] << 8 | data[offset + 6] << 16 | data[offset + 7] << 24) >>> 0;
|
|
3939
|
-
headerSize = 8;
|
|
3940
|
-
}
|
|
3941
|
-
return { tagId, size, headerSize };
|
|
3942
|
-
}
|
|
3943
|
-
var TAG_DISTRIBUTE_DOC_DATA = 16 + 12;
|
|
3944
|
-
function decryptViewText(viewTextRaw, compressed) {
|
|
3945
|
-
const data = new Uint8Array(viewTextRaw);
|
|
3946
|
-
const rec = parseRecordHeader(data, 0);
|
|
3947
|
-
if (rec.tagId !== TAG_DISTRIBUTE_DOC_DATA) {
|
|
3948
|
-
throw new Error(`\uBC30\uD3EC\uC6A9 \uBB38\uC11C\uC758 \uCCAB \uB808\uCF54\uB4DC\uAC00 DISTRIBUTE_DOC_DATA(${TAG_DISTRIBUTE_DOC_DATA})\uAC00 \uC544\uB2D9\uB2C8\uB2E4 (\uC2E4\uC81C: ${rec.tagId})`);
|
|
3949
|
-
}
|
|
3950
|
-
const payloadStart = rec.headerSize;
|
|
3951
|
-
const payloadEnd = payloadStart + rec.size;
|
|
3952
|
-
if (payloadEnd > data.length || rec.size < 256) {
|
|
3953
|
-
throw new Error("\uBC30\uD3EC\uC6A9 payload\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
|
|
3954
|
-
}
|
|
3955
|
-
const payload = data.subarray(payloadStart, payloadStart + 256);
|
|
3956
|
-
const decryptedPayload = decryptDistributePayload(payload);
|
|
3957
|
-
const aesKey = extractAesKey(decryptedPayload);
|
|
3958
|
-
const encryptedStart = payloadEnd;
|
|
3959
|
-
const encryptedData = data.subarray(encryptedStart);
|
|
3960
|
-
if (encryptedData.length === 0) {
|
|
3961
|
-
throw new Error("\uBC30\uD3EC\uC6A9 \uBB38\uC11C\uC5D0 \uC554\uD638\uD654\uB41C \uBCF8\uBB38 \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3962
|
-
}
|
|
3963
|
-
const alignedLen = encryptedData.length - encryptedData.length % 16;
|
|
3964
|
-
if (alignedLen === 0) {
|
|
3965
|
-
throw new Error("\uC554\uD638\uD654\uB41C \uB370\uC774\uD130\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (16\uBC14\uC774\uD2B8 \uBBF8\uB9CC)");
|
|
3966
|
-
}
|
|
3967
|
-
const alignedData = encryptedData.subarray(0, alignedLen);
|
|
3968
|
-
const decrypted = aes128EcbDecrypt(alignedData, aesKey);
|
|
3969
|
-
if (compressed) {
|
|
3970
|
-
try {
|
|
3971
|
-
return decompressStream(Buffer.from(decrypted));
|
|
3972
|
-
} catch {
|
|
3973
|
-
return Buffer.from(decrypted);
|
|
3974
|
-
}
|
|
3975
|
-
}
|
|
3976
|
-
return Buffer.from(decrypted);
|
|
3977
|
-
}
|
|
3978
|
-
|
|
3979
|
-
// src/hwp5/equation.ts
|
|
3980
|
-
var WORD_ALIASES = {
|
|
3981
|
-
// 삼각/로그 함수 — 정본 맵 부재
|
|
3982
|
-
sin: "\\sin",
|
|
3983
|
-
cos: "\\cos",
|
|
3984
|
-
tan: "\\tan",
|
|
3985
|
-
sec: "\\sec",
|
|
3986
|
-
csc: "\\csc",
|
|
3987
|
-
cot: "\\cot",
|
|
3988
|
-
log: "\\log",
|
|
3989
|
-
ln: "\\ln",
|
|
3990
|
-
// 연산자·관계 기호 (정본은 대문자 키만 보유)
|
|
3991
|
-
divide: "\\div",
|
|
3992
|
-
div: "\\div",
|
|
3993
|
-
le: "\\leq",
|
|
3994
|
-
ge: "\\geq",
|
|
3995
|
-
geq: "\\geq",
|
|
3996
|
-
deg: "^\\circ",
|
|
3997
|
-
rarrow: "\\rightarrow",
|
|
3998
|
-
lrarrow: "\\leftrightarrow",
|
|
3999
|
-
rightarrow: "\\rightarrow",
|
|
4000
|
-
leftarrow: "\\leftarrow",
|
|
4001
|
-
in: "\\in",
|
|
4002
|
-
notin: "\\notin",
|
|
4003
|
-
emptyset: "\\emptyset",
|
|
4004
|
-
subset: "\\subset",
|
|
4005
|
-
nsubset: "\\nsubseteq",
|
|
4006
|
-
cup: "\\cup",
|
|
4007
|
-
cap: "\\cap",
|
|
4008
|
-
smallinter: "\\cap",
|
|
4009
|
-
smallsum: "\\sum",
|
|
4010
|
-
sim: "\\sim",
|
|
4011
|
-
circ: "\\circ",
|
|
4012
|
-
bot: "\\perp",
|
|
4013
|
-
partial: "\\partial",
|
|
4014
|
-
nabla: "\\nabla",
|
|
4015
|
-
angle: "\\angle",
|
|
4016
|
-
triangle: "\\triangle",
|
|
4017
|
-
inf: "\\infty",
|
|
4018
|
-
left: "\\left",
|
|
4019
|
-
right: "\\right",
|
|
4020
|
-
// 글꼴 지시자 — 제거
|
|
4021
|
-
rm: "",
|
|
4022
|
-
it: ""
|
|
4023
|
-
};
|
|
4024
|
-
var STRUCTURAL_FOLD = /* @__PURE__ */ new Set(["over", "root", "of"]);
|
|
4025
|
-
function normalizeWords(input) {
|
|
4026
|
-
return input.replace(/(?<![\\A-Za-z0-9])([A-Za-z][A-Za-z0-9]*)(?![A-Za-z0-9])/g, (word) => {
|
|
4027
|
-
if (Object.hasOwn(CONVERT_MAP, word) || Object.hasOwn(MIDDLE_CONVERT_MAP, word)) return word;
|
|
4028
|
-
const lower = word.toLowerCase();
|
|
4029
|
-
if (STRUCTURAL_FOLD.has(lower)) return lower;
|
|
4030
|
-
if (Object.hasOwn(WORD_ALIASES, lower)) return WORD_ALIASES[lower];
|
|
4031
|
-
if (lower !== word && (Object.hasOwn(CONVERT_MAP, lower) || Object.hasOwn(MIDDLE_CONVERT_MAP, lower))) return lower;
|
|
4032
|
-
return word;
|
|
4033
|
-
});
|
|
4034
|
-
}
|
|
4035
|
-
function normalizeScripts(input) {
|
|
4036
|
-
return input.replace(/\s*\^\s*/g, "^").replace(/\s*_\s*/g, "_").replace(/\^(?!\{)([^\s{}_^]+)/g, "^{$1}").replace(/_(?!\{)([^\s{}_^]+)/g, "_{$1}").replace(/([_^])/g, " $1 ");
|
|
4037
|
-
}
|
|
4038
|
-
function normalizeOperators(input) {
|
|
4039
|
-
return input.replace(/\+-/g, " \\pm ").replace(/-\+/g, " \\mp ").replace(/\/\//g, " \\parallel ").replace(/!=/g, " \\neq ").replace(/<=/g, " \\leq ").replace(/>=/g, " \\geq ").replace(/==/g, " \\equiv ").replace(/△/g, " \\triangle ").replace(/□/g, " \\square ").replace(/‧/g, " \\cdot ");
|
|
4040
|
-
}
|
|
4041
|
-
function hwpEquationToLatex(equation) {
|
|
4042
|
-
let s = equation.replace(/\0/g, "").replace(/\s+/g, " ").trim();
|
|
4043
|
-
if (!s) return "";
|
|
4044
|
-
s = normalizeScripts(s);
|
|
4045
|
-
s = normalizeOperators(s);
|
|
4046
|
-
s = normalizeWords(s);
|
|
4047
|
-
s = s.replace(/\broot\s+(?!\{)(\S+)\s+of(?![A-Za-z0-9])/gi, "root { $1 } of");
|
|
4048
|
-
return hmlToLatex(s).replace(/\s+/g, " ").trim();
|
|
4049
|
-
}
|
|
4050
|
-
|
|
4051
|
-
// src/hwp5/pw-crypto.ts
|
|
4052
|
-
import { createCipheriv, createHash as createHash2 } from "crypto";
|
|
4053
|
-
var ENCRYPT_VERSION_OFFSET = 44;
|
|
4054
|
-
var SUPPORTED_ENCRYPT_VERSION = 4;
|
|
4055
|
-
var KEY_SEED = 236;
|
|
4056
|
-
function readEncryptVersion(fileHeader) {
|
|
4057
|
-
if (fileHeader.length < ENCRYPT_VERSION_OFFSET + 4) return 0;
|
|
4058
|
-
return (fileHeader[ENCRYPT_VERSION_OFFSET] | fileHeader[ENCRYPT_VERSION_OFFSET + 1] << 8 | fileHeader[ENCRYPT_VERSION_OFFSET + 2] << 16 | fileHeader[ENCRYPT_VERSION_OFFSET + 3] << 24) >>> 0;
|
|
4059
|
-
}
|
|
4060
|
-
function derivePasswordKey(password) {
|
|
4061
|
-
const buf = Buffer.alloc(password.length * 2);
|
|
4062
|
-
for (let i = 0; i < password.length; i++) {
|
|
4063
|
-
const prev = i === 0 ? KEY_SEED : password[i - 1];
|
|
4064
|
-
buf[i * 2] = (prev << 1 | prev >> 7) & 255;
|
|
4065
|
-
buf[i * 2 + 1] = password[i];
|
|
4066
|
-
}
|
|
4067
|
-
return createHash2("sha1").update(buf).digest().subarray(0, 16);
|
|
4068
|
-
}
|
|
4069
|
-
function padToBlock(data) {
|
|
4070
|
-
const rem = data.length % 16;
|
|
4071
|
-
if (rem === 0) return Buffer.from(data);
|
|
4072
|
-
const amount = 16 - rem;
|
|
4073
|
-
return Buffer.concat([Buffer.from(data), Buffer.alloc(amount, amount)]);
|
|
4074
|
-
}
|
|
4075
|
-
function shiftRegister(reg, feedbackBit) {
|
|
4076
|
-
for (let i = 0; i < 15; i++) {
|
|
4077
|
-
reg[i] = (reg[i] << 1 | reg[i + 1] >> 7) & 255;
|
|
4078
|
-
}
|
|
4079
|
-
reg[15] = (reg[15] << 1 | feedbackBit & 1) & 255;
|
|
4080
|
-
}
|
|
4081
|
-
function decryptAesCfb1(data, key) {
|
|
4082
|
-
const cipher = createCipheriv("aes-128-ecb", key, null);
|
|
4083
|
-
cipher.setAutoPadding(false);
|
|
4084
|
-
const register = new Uint8Array(16);
|
|
4085
|
-
const registerBuf = Buffer.alloc(16);
|
|
4086
|
-
const out = Buffer.alloc(data.length);
|
|
4087
|
-
for (let offset = 0; offset < data.length; offset += 16) {
|
|
4088
|
-
const len = Math.min(16, data.length - offset);
|
|
4089
|
-
const chunk = Buffer.alloc(16);
|
|
4090
|
-
data.copy(chunk, 0, offset, offset + len);
|
|
4091
|
-
for (let i = 0; i < 128; i++) {
|
|
4092
|
-
registerBuf.set(register);
|
|
4093
|
-
const keystreamMsb = cipher.update(registerBuf)[0];
|
|
4094
|
-
const byteIdx = i >> 3;
|
|
4095
|
-
const bitInByte = i & 7;
|
|
4096
|
-
const cipherBit = chunk[byteIdx] >> 7 - bitInByte & 1;
|
|
4097
|
-
shiftRegister(register, cipherBit);
|
|
4098
|
-
chunk[byteIdx] ^= (keystreamMsb & 128) >> bitInByte;
|
|
4099
|
-
}
|
|
4100
|
-
chunk.copy(out, offset, 0, len);
|
|
4101
|
-
}
|
|
4102
|
-
return out;
|
|
4103
|
-
}
|
|
4104
|
-
function decryptPasswordStream(raw, password) {
|
|
4105
|
-
const key = derivePasswordKey(Buffer.from(password, "utf8"));
|
|
4106
|
-
return decryptAesCfb1(padToBlock(raw), key).subarray(0, raw.length);
|
|
4107
|
-
}
|
|
4108
|
-
var TAG_DOCUMENT_PROPERTIES = 16;
|
|
4109
|
-
var TAG_ID_MAPPINGS = 17;
|
|
4110
|
-
function assertDecryptedDocInfo(data, readRecords3) {
|
|
4111
|
-
const wrong = () => 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.");
|
|
4112
|
-
let records;
|
|
4113
|
-
try {
|
|
4114
|
-
records = readRecords3(data);
|
|
4115
|
-
} catch {
|
|
4116
|
-
throw wrong();
|
|
4117
|
-
}
|
|
4118
|
-
if (records[0]?.tagId !== TAG_DOCUMENT_PROPERTIES) throw wrong();
|
|
4119
|
-
if (!records.some((r) => r.tagId === TAG_ID_MAPPINGS)) throw wrong();
|
|
4120
|
-
}
|
|
4121
|
-
function assertSupportedEncryptVersion(version) {
|
|
4122
|
-
if (version !== SUPPORTED_ENCRYPT_VERSION) {
|
|
4123
|
-
throw new KordocError(
|
|
4124
|
-
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWP \uC554\uD638\uD654 \uBC84\uC804\uC785\uB2C8\uB2E4 (EncryptVersion=${version}, \uC9C0\uC6D0: ${SUPPORTED_ENCRYPT_VERSION})`
|
|
4125
|
-
);
|
|
4126
|
-
}
|
|
4127
|
-
}
|
|
4128
|
-
|
|
4129
|
-
// src/hwp5/page-boundary.ts
|
|
4130
|
-
var MIDPAGE_V2 = 2e3;
|
|
4131
|
-
var CTRL_ID_TBL = 1952607264;
|
|
4132
|
-
function tableIntraBreaks2(records, start, end) {
|
|
4133
|
-
const rowMax = /* @__PURE__ */ new Map();
|
|
4134
|
-
let curRow = -1;
|
|
4135
|
-
let prevV = Number.NEGATIVE_INFINITY;
|
|
4136
|
-
let resets = 0;
|
|
4137
|
-
const flush = () => {
|
|
4138
|
-
if (curRow >= 0) rowMax.set(curRow, Math.max(rowMax.get(curRow) ?? 0, resets));
|
|
4139
|
-
resets = 0;
|
|
4140
|
-
prevV = Number.NEGATIVE_INFINITY;
|
|
4141
|
-
};
|
|
4142
|
-
for (let i = start; i < end; i++) {
|
|
4143
|
-
const rec = records[i];
|
|
4144
|
-
if (rec.tagId === TAG_LIST_HEADER && rec.level === 2 && rec.data.length >= 12) {
|
|
4145
|
-
flush();
|
|
4146
|
-
curRow = rec.data.readUInt16LE(10);
|
|
4147
|
-
} else if (rec.tagId === TAG_PARA_LINE_SEG && rec.level === 3 && curRow >= 0) {
|
|
4148
|
-
for (let off = 0; off + 36 <= rec.data.length; off += 36) {
|
|
4149
|
-
const v = rec.data.readInt32LE(off + 4);
|
|
4150
|
-
if (v < prevV) resets++;
|
|
4151
|
-
prevV = v;
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
4154
|
-
}
|
|
4155
|
-
flush();
|
|
4156
|
-
let sum = 0;
|
|
4157
|
-
for (const v of rowMax.values()) sum += v;
|
|
4158
|
-
return sum;
|
|
4159
|
-
}
|
|
4160
|
-
function detectHwp5SectionPages(records) {
|
|
4161
|
-
const pageAtPara = [];
|
|
4162
|
-
let cur = 0;
|
|
4163
|
-
let prevV = Number.NEGATIVE_INFINITY;
|
|
4164
|
-
let prevH = Number.NEGATIVE_INFINITY;
|
|
4165
|
-
let first = true;
|
|
4166
|
-
let suppressMidReset = false;
|
|
4167
|
-
let parasWithSegs = 0;
|
|
4168
|
-
let i = 0;
|
|
4169
|
-
while (i < records.length) {
|
|
4170
|
-
const rec = records[i];
|
|
4171
|
-
if (rec.tagId !== TAG_PARA_HEADER || rec.level !== 0) {
|
|
4172
|
-
i++;
|
|
4173
|
-
continue;
|
|
4174
|
-
}
|
|
4175
|
-
let j = i + 1;
|
|
4176
|
-
while (j < records.length && records[j].level > 0) j++;
|
|
4177
|
-
const divideSort = rec.data.length >= 12 ? rec.data.readUInt8(11) : 0;
|
|
4178
|
-
const explicit = (divideSort & 4) !== 0;
|
|
4179
|
-
let brokeByExplicit = false;
|
|
4180
|
-
if (explicit && !first) {
|
|
4181
|
-
cur++;
|
|
4182
|
-
brokeByExplicit = true;
|
|
4183
|
-
suppressMidReset = false;
|
|
4184
|
-
}
|
|
4185
|
-
let paraFirst = true;
|
|
4186
|
-
let startPage = cur;
|
|
4187
|
-
let hadSegs = false;
|
|
4188
|
-
let intraAdd = 0;
|
|
4189
|
-
for (let k = i + 1; k < j; k++) {
|
|
4190
|
-
const r = records[k];
|
|
4191
|
-
if (r.tagId === TAG_PARA_LINE_SEG && r.level === 1) {
|
|
4192
|
-
hadSegs = true;
|
|
4193
|
-
for (let off = 0; off + 36 <= r.data.length; off += 36) {
|
|
4194
|
-
const v = r.data.readInt32LE(off + 4);
|
|
4195
|
-
const h = r.data.readInt32LE(off + 24);
|
|
4196
|
-
const brk = v < prevV || paraFirst && v === prevV && h <= prevH;
|
|
4197
|
-
if (brk && !(paraFirst && brokeByExplicit)) {
|
|
4198
|
-
if (paraFirst && suppressMidReset && v >= MIDPAGE_V2) {
|
|
4199
|
-
} else {
|
|
4200
|
-
cur++;
|
|
4201
|
-
}
|
|
4202
|
-
}
|
|
4203
|
-
if (paraFirst) {
|
|
4204
|
-
suppressMidReset = false;
|
|
4205
|
-
startPage = cur;
|
|
4206
|
-
}
|
|
4207
|
-
paraFirst = false;
|
|
4208
|
-
prevV = v;
|
|
4209
|
-
prevH = h;
|
|
4210
|
-
}
|
|
4211
|
-
}
|
|
4212
|
-
if (r.tagId === TAG_CTRL_HEADER && r.level === 1 && r.data.length >= 4 && r.data.readUInt32LE(0) === CTRL_ID_TBL) {
|
|
4213
|
-
let ce = k + 1;
|
|
4214
|
-
while (ce < j && records[ce].level > 1) ce++;
|
|
4215
|
-
intraAdd += tableIntraBreaks2(records, k + 1, ce);
|
|
4216
|
-
k = ce - 1;
|
|
4217
|
-
}
|
|
4218
|
-
}
|
|
4219
|
-
if (hadSegs) parasWithSegs++;
|
|
4220
|
-
pageAtPara.push(startPage);
|
|
4221
|
-
first = false;
|
|
4222
|
-
if (intraAdd > 0) {
|
|
4223
|
-
cur += intraAdd;
|
|
4224
|
-
suppressMidReset = true;
|
|
4225
|
-
}
|
|
4226
|
-
i = j;
|
|
4227
|
-
}
|
|
4228
|
-
return {
|
|
4229
|
-
pageAtPara,
|
|
4230
|
-
pages: cur + 1,
|
|
4231
|
-
usable: pageAtPara.length > 0 && parasWithSegs === pageAtPara.length
|
|
4232
|
-
};
|
|
4233
|
-
}
|
|
4234
|
-
|
|
4235
|
-
// src/hwp5/parser.ts
|
|
4236
|
-
import { createRequire } from "module";
|
|
4237
|
-
var require2 = createRequire(import.meta.url);
|
|
4238
|
-
var CFB = require2("cfb");
|
|
4239
|
-
var MAX_SECTIONS = 100;
|
|
4240
|
-
var MAX_TOTAL_DECOMPRESS = 100 * 1024 * 1024;
|
|
4241
|
-
var MAX_NEST_DEPTH = 8;
|
|
4242
|
-
function cid(s) {
|
|
4243
|
-
return (s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3)) >>> 0;
|
|
4244
|
-
}
|
|
4245
|
-
var CTRL_TBL = cid("tbl ");
|
|
4246
|
-
var CTRL_GSO = cid("gso ");
|
|
4247
|
-
var CTRL_EQED = cid("eqed");
|
|
4248
|
-
var CTRL_HEAD = cid("head");
|
|
4249
|
-
var CTRL_FOOT = cid("foot");
|
|
4250
|
-
var CTRL_FN = cid("fn ");
|
|
4251
|
-
var CTRL_EN = cid("en ");
|
|
4252
|
-
var CTRL_ATNO = cid("atno");
|
|
4253
|
-
var CTRL_NWNO = cid("nwno");
|
|
4254
|
-
var CTRL_PGNP = cid("pgnp");
|
|
4255
|
-
var CTRL_PGHD = cid("pghd");
|
|
4256
|
-
var CTRL_IDXM = cid("idxm");
|
|
4257
|
-
var CTRL_BOKM = cid("bokm");
|
|
4258
|
-
var CTRL_TCPS = cid("tcps");
|
|
4259
|
-
var CTRL_TDUT = cid("tdut");
|
|
4260
|
-
var CTRL_TCMT = cid("tcmt");
|
|
4261
|
-
var CTRL_SECD = cid("secd");
|
|
4262
|
-
var CTRL_COLD = cid("cold");
|
|
4263
|
-
var CTRL_FORM = cid("form");
|
|
4264
|
-
var CTRL_OLE = cid("ole ");
|
|
4265
|
-
var FIELD_HLK = cid("%hlk");
|
|
4266
|
-
var FIELD_CLK = cid("%clk");
|
|
4267
|
-
var KNOWN_CTRL_IDS = /* @__PURE__ */ new Set([
|
|
4268
|
-
CTRL_TBL,
|
|
4269
|
-
CTRL_GSO,
|
|
4270
|
-
CTRL_EQED,
|
|
4271
|
-
CTRL_HEAD,
|
|
4272
|
-
CTRL_FOOT,
|
|
4273
|
-
CTRL_FN,
|
|
4274
|
-
CTRL_EN,
|
|
4275
|
-
CTRL_ATNO,
|
|
4276
|
-
CTRL_NWNO,
|
|
4277
|
-
CTRL_PGNP,
|
|
4278
|
-
CTRL_PGHD,
|
|
4279
|
-
CTRL_IDXM,
|
|
4280
|
-
CTRL_BOKM,
|
|
4281
|
-
CTRL_TCPS,
|
|
4282
|
-
CTRL_TDUT,
|
|
4283
|
-
CTRL_TCMT,
|
|
4284
|
-
CTRL_SECD,
|
|
4285
|
-
CTRL_COLD,
|
|
4286
|
-
CTRL_FORM,
|
|
4287
|
-
CTRL_OLE
|
|
4288
|
-
]);
|
|
4289
|
-
function isFieldCtrlId(id) {
|
|
4290
|
-
return id >>> 24 === 37;
|
|
4291
|
-
}
|
|
4292
|
-
function swap32(id) {
|
|
4293
|
-
return ((id & 255) << 24 | (id >>> 8 & 255) << 16 | (id >>> 16 & 255) << 8 | id >>> 24 & 255) >>> 0;
|
|
4294
|
-
}
|
|
4295
|
-
function normalizeCtrlId(raw) {
|
|
4296
|
-
if (KNOWN_CTRL_IDS.has(raw) || isFieldCtrlId(raw)) return raw;
|
|
4297
|
-
const sw = swap32(raw);
|
|
4298
|
-
if (KNOWN_CTRL_IDS.has(sw) || isFieldCtrlId(sw)) return sw;
|
|
4299
|
-
return raw;
|
|
4300
|
-
}
|
|
4301
|
-
function parseHwp5Document(buffer, options) {
|
|
4302
|
-
let cfb = null;
|
|
4303
|
-
let lenientCfb = null;
|
|
4304
|
-
const warnings = [];
|
|
4305
|
-
try {
|
|
4306
|
-
cfb = CFB.parse(buffer);
|
|
4307
|
-
} catch {
|
|
4308
|
-
try {
|
|
4309
|
-
lenientCfb = parseLenientCfb(buffer);
|
|
4310
|
-
warnings.push({ message: "\uC190\uC0C1\uB41C CFB \uCEE8\uD14C\uC774\uB108 \u2014 lenient \uBAA8\uB4DC\uB85C \uBCF5\uAD6C", code: "LENIENT_CFB_RECOVERY" });
|
|
4311
|
-
} catch {
|
|
4312
|
-
throw new KordocError("CFB \uCEE8\uD14C\uC774\uB108 \uD30C\uC2F1 \uC2E4\uD328 (strict \uBC0F lenient \uBAA8\uB450)");
|
|
4313
|
-
}
|
|
4314
|
-
}
|
|
4315
|
-
const readRawStream = (path) => {
|
|
4316
|
-
if (cfb) {
|
|
4317
|
-
const entry = CFB.find(cfb, path);
|
|
4318
|
-
return entry?.content ? Buffer.from(entry.content) : null;
|
|
4319
|
-
}
|
|
4320
|
-
return lenientCfb.findStream(path);
|
|
4321
|
-
};
|
|
4322
|
-
const headerData = readRawStream("/FileHeader");
|
|
4323
|
-
if (!headerData) throw new KordocError("FileHeader \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
|
|
4324
|
-
const header = parseFileHeader(headerData);
|
|
4325
|
-
if (header.signature !== "HWP Document File") throw new KordocError("HWP \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
|
|
4326
|
-
if (header.flags & FLAG_DRM) throw new KordocError("DRM \uBCF4\uD638\uB41C HWP\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
|
|
4327
|
-
const encrypted = (header.flags & FLAG_ENCRYPTED) !== 0;
|
|
4328
|
-
if (encrypted) {
|
|
4329
|
-
if (!options?.password) {
|
|
4330
|
-
throw new KordocError("\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWP \uBB38\uC11C\uC785\uB2C8\uB2E4. password \uC635\uC158\uC5D0 \uC5F4\uAE30 \uC554\uD638\uB97C \uC9C0\uC815\uD558\uC138\uC694.");
|
|
4331
|
-
}
|
|
4332
|
-
assertSupportedEncryptVersion(readEncryptVersion(headerData));
|
|
4333
|
-
}
|
|
4334
|
-
const password = encrypted ? options.password : void 0;
|
|
4335
|
-
const findStream = (path) => {
|
|
4336
|
-
const raw = readRawStream(path);
|
|
4337
|
-
if (!raw || !password) return raw;
|
|
4338
|
-
return decryptPasswordStream(raw, password);
|
|
4339
|
-
};
|
|
4340
|
-
const compressed = (header.flags & FLAG_COMPRESSED) !== 0;
|
|
4341
|
-
const distribution = (header.flags & FLAG_DISTRIBUTION) !== 0;
|
|
4342
|
-
const metadata = {
|
|
4343
|
-
version: `${header.versionMajor}.x`
|
|
4344
|
-
};
|
|
4345
|
-
if (cfb) extractHwp5Metadata(cfb, metadata);
|
|
4346
|
-
if (encrypted) {
|
|
4347
|
-
const rawDocInfo = findStream("/DocInfo");
|
|
4348
|
-
if (!rawDocInfo) throw new KordocError("DocInfo \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
|
|
4349
|
-
let docInfoData;
|
|
4350
|
-
try {
|
|
4351
|
-
docInfoData = compressed ? decompressStream(rawDocInfo) : rawDocInfo;
|
|
4352
|
-
} catch {
|
|
4353
|
-
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.");
|
|
4354
|
-
}
|
|
4355
|
-
assertDecryptedDocInfo(docInfoData, readRecords);
|
|
4356
|
-
}
|
|
4357
|
-
const docInfo = cfb && !encrypted ? parseDocInfoStream(cfb, compressed) : parseDocInfoFromStream(findStream("/DocInfo"), compressed);
|
|
4358
|
-
const sections = distribution ? cfb ? findViewTextSections(cfb, compressed) : findViewTextSectionsLenient(lenientCfb, compressed) : encrypted ? findSectionsVia(findStream) : cfb ? findSections(cfb) : findSectionsLenient(lenientCfb, compressed);
|
|
4359
|
-
if (sections.length === 0) throw new KordocError("\uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
4360
|
-
const sectionRecords = [];
|
|
4361
|
-
const sectionDetects = [];
|
|
4362
|
-
let totalDecompressed = 0;
|
|
4363
|
-
for (let si = 0; si < sections.length; si++) {
|
|
4364
|
-
try {
|
|
4365
|
-
const data = !distribution && compressed ? decompressStream(Buffer.from(sections[si])) : Buffer.from(sections[si]);
|
|
4366
|
-
totalDecompressed += data.length;
|
|
4367
|
-
if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
|
|
4368
|
-
const records = readRecords(data);
|
|
4369
|
-
sectionRecords.push(records);
|
|
4370
|
-
sectionDetects.push(detectHwp5SectionPages(records));
|
|
4371
|
-
} catch (secErr) {
|
|
4372
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
4373
|
-
sectionRecords.push(null);
|
|
4374
|
-
sectionDetects.push(null);
|
|
4375
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
4376
|
-
}
|
|
4377
|
-
}
|
|
4378
|
-
const layoutPages = sectionDetects.length > 0 && sectionDetects.every((d) => d != null && d.usable);
|
|
4379
|
-
metadata.pageMode = layoutPages ? "layout" : "section";
|
|
4380
|
-
metadata.pageCount = layoutPages ? Math.max(sectionDetects.reduce((sum, d) => sum + (d?.pages ?? 0), 0), 1) : sections.length;
|
|
4381
|
-
const pageFilter = options?.pages ? parsePageRange(options.pages, metadata.pageCount) : null;
|
|
4382
|
-
if (pageFilter && !layoutPages) {
|
|
4383
|
-
warnings.push({ code: "PAGE_BOUNDARY_APPROXIMATE", message: "\uC870\uD310 \uCE90\uC2DC\uAC00 \uC5C6\uC5B4 pages \uD544\uD130\uB97C \uC139\uC158 \uB2E8\uC704 \uADFC\uC0AC\uB85C \uC801\uC6A9\uD588\uC2B5\uB2C8\uB2E4" });
|
|
4384
|
-
}
|
|
4385
|
-
let bodyBlocks = [];
|
|
4386
|
-
const doc = createHwp5DocState();
|
|
4387
|
-
doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
4388
|
-
let parsedSections = 0;
|
|
4389
|
-
let pageBase = 0;
|
|
4390
|
-
for (let si = 0; si < sections.length; si++) {
|
|
4391
|
-
const records = sectionRecords[si];
|
|
4392
|
-
if (!records) continue;
|
|
4393
|
-
const detect = sectionDetects[si];
|
|
4394
|
-
if (!layoutPages && pageFilter && !pageFilter.has(si + 1)) continue;
|
|
4395
|
-
try {
|
|
4396
|
-
const sectionBlocks = parseSection(
|
|
4397
|
-
records,
|
|
4398
|
-
docInfo,
|
|
4399
|
-
warnings,
|
|
4400
|
-
si + 1,
|
|
4401
|
-
doc,
|
|
4402
|
-
layoutPages ? { base: pageBase, pageAtPara: detect.pageAtPara } : void 0
|
|
4403
|
-
);
|
|
4404
|
-
bodyBlocks.push(...sectionBlocks);
|
|
4405
|
-
parsedSections++;
|
|
4406
|
-
options?.onProgress?.(parsedSections, sections.length);
|
|
4407
|
-
} catch (secErr) {
|
|
4408
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
4409
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
4410
|
-
} finally {
|
|
4411
|
-
pageBase += detect.pages;
|
|
4412
|
-
}
|
|
4413
|
-
}
|
|
4414
|
-
if (pageFilter && layoutPages) {
|
|
4415
|
-
bodyBlocks = bodyBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
|
|
4416
|
-
}
|
|
4417
|
-
const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
|
|
4418
|
-
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
|
|
4419
|
-
let flatBlocks = flattenLayoutTables(blocks);
|
|
4420
|
-
if (options?.dedupeRunningHeaders) {
|
|
4421
|
-
const deduped = dedupeRunningHeaders(flatBlocks);
|
|
4422
|
-
const removed = flatBlocks.length - deduped.length;
|
|
4423
|
-
if (removed > 0) warnings.push({ message: `\uBC18\uBCF5 \uB7EC\uB2DD \uD5E4\uB354 ${removed}\uAC1C \uC81C\uAC70`, code: "HIDDEN_TEXT_FILTERED" });
|
|
4424
|
-
flatBlocks = deduped;
|
|
4425
|
-
}
|
|
4426
|
-
if (docInfo) {
|
|
4427
|
-
detectHwp5Headings(flatBlocks, docInfo);
|
|
4428
|
-
}
|
|
4429
|
-
const outline = flatBlocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
4430
|
-
let markdown = blocksToMarkdown(flatBlocks);
|
|
4431
|
-
if (options?.inlineImages && images.length > 0) {
|
|
4432
|
-
try {
|
|
4433
|
-
markdown = inlineImagesIntoMarkdown(markdown, images, { compress: true });
|
|
4434
|
-
} catch (inlineErr) {
|
|
4435
|
-
warnings.push({ message: `\uC774\uBBF8\uC9C0 \uC778\uB77C\uC778 \uC2E4\uD328 \u2014 \uC6D0\uBCF8 \uD30C\uC77C \uCC38\uC870\uB85C \uD3F4\uBC31: ${inlineErr instanceof Error ? inlineErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "SKIPPED_IMAGE" });
|
|
4436
|
-
}
|
|
4437
|
-
}
|
|
4438
|
-
return { markdown, blocks: flatBlocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
4439
|
-
}
|
|
4440
|
-
function parseDocInfoStream(cfb, compressed) {
|
|
4441
|
-
try {
|
|
4442
|
-
const entry = CFB.find(cfb, "/DocInfo");
|
|
4443
|
-
if (!entry?.content) return null;
|
|
4444
|
-
const data = compressed ? decompressStream(Buffer.from(entry.content)) : Buffer.from(entry.content);
|
|
4445
|
-
const records = readRecords(data);
|
|
4446
|
-
return parseDocInfo(records);
|
|
4447
|
-
} catch {
|
|
4448
|
-
return null;
|
|
4449
|
-
}
|
|
4450
|
-
}
|
|
4451
|
-
function parseDocInfoFromStream(raw, compressed) {
|
|
4452
|
-
if (!raw) return null;
|
|
4453
|
-
try {
|
|
4454
|
-
const data = compressed ? decompressStream(raw) : raw;
|
|
4455
|
-
return parseDocInfo(readRecords(data));
|
|
4456
|
-
} catch {
|
|
4457
|
-
return null;
|
|
4458
|
-
}
|
|
4459
|
-
}
|
|
4460
|
-
function detectHwp5Headings(blocks, docInfo) {
|
|
4461
|
-
let baseFontSize = 0;
|
|
4462
|
-
const sizeFreq = /* @__PURE__ */ new Map();
|
|
4463
|
-
for (const b of blocks) {
|
|
4464
|
-
if (b.style?.fontSize && b.text) {
|
|
4465
|
-
sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + b.text.length);
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
let maxWeight = 0;
|
|
4469
|
-
for (const [size, weight] of sizeFreq) {
|
|
4470
|
-
if (weight > maxWeight) {
|
|
4471
|
-
maxWeight = weight;
|
|
4472
|
-
baseFontSize = size;
|
|
4473
|
-
}
|
|
4474
|
-
}
|
|
4475
|
-
if (baseFontSize === 0) {
|
|
4476
|
-
for (const style of docInfo.styles) {
|
|
4477
|
-
const name = (style.nameKo || style.name).toLowerCase();
|
|
4478
|
-
if (name.includes("\uBC14\uD0D5") || name.includes("\uBCF8\uBB38") || name === "normal" || name === "body") {
|
|
4479
|
-
const cs = docInfo.charShapes[style.charShapeId];
|
|
4480
|
-
if (cs?.fontSize > 0) {
|
|
4481
|
-
baseFontSize = cs.fontSize / 10;
|
|
4482
|
-
break;
|
|
4483
|
-
}
|
|
4484
|
-
}
|
|
4485
|
-
}
|
|
4486
|
-
}
|
|
4487
|
-
if (baseFontSize <= 0) return;
|
|
4488
|
-
for (const block of blocks) {
|
|
4489
|
-
if (block.type === "heading") continue;
|
|
4490
|
-
if (block.type !== "paragraph" || !block.text) continue;
|
|
4491
|
-
const text = block.text.trim();
|
|
4492
|
-
if (text.length === 0 || text.length > 200) continue;
|
|
4493
|
-
if (/^\d+$/.test(text)) continue;
|
|
4494
|
-
let level = 0;
|
|
4495
|
-
if (block.style?.fontSize && baseFontSize > 0) {
|
|
4496
|
-
const ratio = block.style.fontSize / baseFontSize;
|
|
4497
|
-
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
4498
|
-
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
4499
|
-
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
4500
|
-
}
|
|
4501
|
-
if (/^제\d+[장절편]\s/.test(text) && text.length <= 50) {
|
|
4502
|
-
if (level === 0) level = 2;
|
|
4503
|
-
} else if (/^제\d+(조의?\d*)\s*[\((]/.test(text) && text.length <= 80) {
|
|
4504
|
-
if (level === 0) level = 3;
|
|
4505
|
-
}
|
|
4506
|
-
if (level > 0) {
|
|
4507
|
-
block.type = "heading";
|
|
4508
|
-
block.level = level;
|
|
4509
|
-
}
|
|
4510
|
-
}
|
|
4511
|
-
}
|
|
4512
|
-
function extractHwp5Metadata(cfb, metadata) {
|
|
4513
|
-
try {
|
|
4514
|
-
const summaryEntry = CFB.find(cfb, "/HwpSummaryInformation") || CFB.find(cfb, "/SummaryInformation");
|
|
4515
|
-
if (!summaryEntry?.content) return;
|
|
4516
|
-
const data = Buffer.from(summaryEntry.content);
|
|
4517
|
-
if (data.length < 48) return;
|
|
4518
|
-
const numSets = data.readUInt32LE(24);
|
|
4519
|
-
if (numSets === 0) return;
|
|
4520
|
-
const setOffset = data.readUInt32LE(44);
|
|
4521
|
-
if (setOffset >= data.length - 8) return;
|
|
4522
|
-
const numProps = data.readUInt32LE(setOffset + 4);
|
|
4523
|
-
if (numProps === 0 || numProps > 100) return;
|
|
4524
|
-
for (let i = 0; i < numProps; i++) {
|
|
4525
|
-
const entryOffset = setOffset + 8 + i * 8;
|
|
4526
|
-
if (entryOffset + 8 > data.length) break;
|
|
4527
|
-
const propId = data.readUInt32LE(entryOffset);
|
|
4528
|
-
const propOffset = setOffset + data.readUInt32LE(entryOffset + 4);
|
|
4529
|
-
if (propOffset + 8 > data.length) continue;
|
|
4530
|
-
if (propId !== 2 && propId !== 4 && propId !== 6) continue;
|
|
4531
|
-
const propType = data.readUInt32LE(propOffset);
|
|
4532
|
-
if (propType !== 30) continue;
|
|
4533
|
-
const strLen = data.readUInt32LE(propOffset + 4);
|
|
4534
|
-
if (strLen === 0 || strLen > 1e4 || propOffset + 8 + strLen > data.length) continue;
|
|
4535
|
-
const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString("utf8").replace(/\0+$/, "").trim();
|
|
4536
|
-
if (!str) continue;
|
|
4537
|
-
if (propId === 2) metadata.title = str;
|
|
4538
|
-
else if (propId === 4) metadata.author = str;
|
|
4539
|
-
else if (propId === 6) metadata.description = str;
|
|
4540
|
-
}
|
|
4541
|
-
} catch {
|
|
4542
|
-
}
|
|
4543
|
-
}
|
|
4544
|
-
function extractHwp5MetadataOnly(buffer) {
|
|
4545
|
-
const cfb = CFB.parse(buffer);
|
|
4546
|
-
const headerEntry = CFB.find(cfb, "/FileHeader");
|
|
4547
|
-
if (!headerEntry?.content) throw new KordocError("FileHeader \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
|
|
4548
|
-
const header = parseFileHeader(Buffer.from(headerEntry.content));
|
|
4549
|
-
if (header.signature !== "HWP Document File") throw new KordocError("HWP \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
|
|
4550
|
-
const metadata = {
|
|
4551
|
-
version: `${header.versionMajor}.x`
|
|
4552
|
-
};
|
|
4553
|
-
extractHwp5Metadata(cfb, metadata);
|
|
4554
|
-
const sections = findSections(cfb);
|
|
4555
|
-
metadata.pageCount = sections.length;
|
|
4556
|
-
return metadata;
|
|
4557
|
-
}
|
|
4558
|
-
function findViewTextSections(cfb, compressed) {
|
|
4559
|
-
const sections = [];
|
|
4560
|
-
for (let i = 0; i < MAX_SECTIONS; i++) {
|
|
4561
|
-
const entry = CFB.find(cfb, `/ViewText/Section${i}`);
|
|
4562
|
-
if (!entry?.content) break;
|
|
4563
|
-
try {
|
|
4564
|
-
const decrypted = decryptViewText(Buffer.from(entry.content), compressed);
|
|
4565
|
-
sections.push({ idx: i, content: decrypted });
|
|
4566
|
-
} catch {
|
|
4567
|
-
break;
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
|
|
4571
|
-
}
|
|
4572
|
-
function findSectionsVia(read) {
|
|
4573
|
-
const sections = [];
|
|
4574
|
-
for (let i = 0; i < MAX_SECTIONS; i++) {
|
|
4575
|
-
const raw = read(`/BodyText/Section${i}`);
|
|
4576
|
-
if (!raw) break;
|
|
4577
|
-
sections.push(raw);
|
|
4578
|
-
}
|
|
4579
|
-
return sections;
|
|
4580
|
-
}
|
|
4581
|
-
function findSections(cfb) {
|
|
4582
|
-
const sections = [];
|
|
4583
|
-
for (let i = 0; i < MAX_SECTIONS; i++) {
|
|
4584
|
-
const entry = CFB.find(cfb, `/BodyText/Section${i}`);
|
|
4585
|
-
if (!entry?.content) break;
|
|
4586
|
-
sections.push({ idx: i, content: Buffer.from(entry.content) });
|
|
4587
|
-
}
|
|
4588
|
-
if (sections.length === 0 && cfb.FileIndex) {
|
|
4589
|
-
for (const entry of cfb.FileIndex) {
|
|
4590
|
-
if (sections.length >= MAX_SECTIONS) break;
|
|
4591
|
-
if (entry.name?.startsWith("Section") && entry.content) {
|
|
4592
|
-
const idx = parseInt(entry.name.replace("Section", ""), 10) || 0;
|
|
4593
|
-
sections.push({ idx, content: Buffer.from(entry.content) });
|
|
4594
|
-
}
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
|
|
4598
|
-
}
|
|
4599
|
-
function findSectionsLenient(lcfb, compressed) {
|
|
4600
|
-
const sections = [];
|
|
4601
|
-
let totalDecompressed = 0;
|
|
4602
|
-
for (let i = 0; i < MAX_SECTIONS; i++) {
|
|
4603
|
-
const raw = lcfb.findStream(`/BodyText/Section${i}`) ?? lcfb.findStream(`Section${i}`);
|
|
4604
|
-
if (!raw) break;
|
|
4605
|
-
const content = compressed ? decompressStream(raw) : raw;
|
|
4606
|
-
totalDecompressed += content.length;
|
|
4607
|
-
if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
|
|
4608
|
-
sections.push({ idx: i, content });
|
|
4609
|
-
}
|
|
4610
|
-
if (sections.length === 0) {
|
|
4611
|
-
for (const e of lcfb.entries()) {
|
|
4612
|
-
if (sections.length >= MAX_SECTIONS) break;
|
|
4613
|
-
if (e.name.startsWith("Section")) {
|
|
4614
|
-
const idx = parseInt(e.name.replace("Section", ""), 10) || 0;
|
|
4615
|
-
const raw = lcfb.findStream(e.name);
|
|
4616
|
-
if (raw) {
|
|
4617
|
-
const content = compressed ? decompressStream(raw) : raw;
|
|
4618
|
-
totalDecompressed += content.length;
|
|
4619
|
-
if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
|
|
4620
|
-
sections.push({ idx, content });
|
|
4621
|
-
}
|
|
4622
|
-
}
|
|
4623
|
-
}
|
|
4624
|
-
}
|
|
4625
|
-
return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
|
|
4626
|
-
}
|
|
4627
|
-
function findViewTextSectionsLenient(lcfb, compressed) {
|
|
4628
|
-
const sections = [];
|
|
4629
|
-
let totalDecompressed = 0;
|
|
4630
|
-
for (let i = 0; i < MAX_SECTIONS; i++) {
|
|
4631
|
-
const raw = lcfb.findStream(`/ViewText/Section${i}`) ?? lcfb.findStream(`Section${i}`);
|
|
4632
|
-
if (!raw) break;
|
|
4633
|
-
try {
|
|
4634
|
-
const content = decryptViewText(raw, compressed);
|
|
4635
|
-
totalDecompressed += content.length;
|
|
4636
|
-
if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
|
|
4637
|
-
sections.push({ idx: i, content });
|
|
4638
|
-
} catch {
|
|
4639
|
-
break;
|
|
4640
|
-
}
|
|
4641
|
-
}
|
|
4642
|
-
return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
|
|
4643
|
-
}
|
|
4644
|
-
function formatEquationForMarkdown(equation) {
|
|
4645
|
-
const normalized = hwpEquationToLatex(equation);
|
|
4646
|
-
if (!normalized) return "";
|
|
4647
|
-
return `$${normalized.replace(/\$/g, "\\$")}$`;
|
|
4648
|
-
}
|
|
4649
|
-
function extractEquationFromSlice(records, start, end) {
|
|
4650
|
-
for (let i = start; i < end; i++) {
|
|
4651
|
-
if (records[i].tagId !== TAG_EQEDIT) continue;
|
|
4652
|
-
const equation = extractEquationText(records[i].data);
|
|
4653
|
-
return equation ? formatEquationForMarkdown(equation) : null;
|
|
4654
|
-
}
|
|
4655
|
-
return null;
|
|
4656
|
-
}
|
|
4657
|
-
function createHwp5DocState() {
|
|
4658
|
-
return {
|
|
4659
|
-
numbering: new NumberingState(),
|
|
4660
|
-
outlineNumberingId: 0,
|
|
4661
|
-
autoCounters: /* @__PURE__ */ new Map(),
|
|
4662
|
-
headerTexts: /* @__PURE__ */ new Set(),
|
|
4663
|
-
headerBlocks: [],
|
|
4664
|
-
footerBlocks: []
|
|
4665
|
-
};
|
|
4666
|
-
}
|
|
4667
|
-
function parseSection(records, docInfo, warnings, sectionNum, doc, pageMap) {
|
|
4668
|
-
const ctx = {
|
|
4669
|
-
docInfo,
|
|
4670
|
-
warnings,
|
|
4671
|
-
sectionNum,
|
|
4672
|
-
doc: doc ?? createHwp5DocState(),
|
|
4673
|
-
depth: 0,
|
|
4674
|
-
page: pageMap ? pageMap.base + 1 : sectionNum,
|
|
4675
|
-
pageMap,
|
|
4676
|
-
topOrdinal: 0
|
|
4677
|
-
};
|
|
4678
|
-
return parseParagraphList(records, 0, records.length, ctx);
|
|
4679
|
-
}
|
|
4680
|
-
function parseParagraphList(records, start, end, ctx) {
|
|
4681
|
-
const blocks = [];
|
|
4682
|
-
let i = start;
|
|
4683
|
-
while (i < end) {
|
|
4684
|
-
if (records[i].tagId === TAG_PARA_HEADER) {
|
|
4685
|
-
const baseLevel = records[i].level;
|
|
4686
|
-
if (baseLevel === 0 && ctx.depth === 0 && ctx.pageMap) {
|
|
4687
|
-
const pg = ctx.pageMap.pageAtPara[ctx.topOrdinal];
|
|
4688
|
-
if (pg !== void 0) ctx.page = ctx.pageMap.base + pg + 1;
|
|
4689
|
-
ctx.topOrdinal++;
|
|
4690
|
-
}
|
|
4691
|
-
let j = i + 1;
|
|
4692
|
-
while (j < end && records[j].level > baseLevel) j++;
|
|
4693
|
-
blocks.push(...parseParagraph(records, i, j, ctx));
|
|
4694
|
-
i = j;
|
|
4695
|
-
} else {
|
|
4696
|
-
i++;
|
|
4697
|
-
}
|
|
4698
|
-
}
|
|
4699
|
-
return blocks;
|
|
4700
|
-
}
|
|
4701
|
-
function parseParagraph(records, start, end, ctx) {
|
|
4702
|
-
const header = records[start];
|
|
4703
|
-
const baseLevel = header.level;
|
|
4704
|
-
const paraShapeId = header.data.length >= 10 ? header.data.readUInt16LE(8) : -1;
|
|
4705
|
-
const textRecords = [];
|
|
4706
|
-
const charShapeIds = [];
|
|
4707
|
-
const ctrls = [];
|
|
4708
|
-
let i = start + 1;
|
|
4709
|
-
while (i < end) {
|
|
4710
|
-
const rec = records[i];
|
|
4711
|
-
if (rec.tagId === TAG_CTRL_HEADER && rec.level === baseLevel + 1 && rec.data.length >= 4) {
|
|
4712
|
-
const childStart = i + 1;
|
|
4713
|
-
let j = childStart;
|
|
4714
|
-
while (j < end && records[j].level > baseLevel + 1) j++;
|
|
4715
|
-
const idRaw = rec.data.readUInt32LE(0);
|
|
4716
|
-
ctrls.push({ id: normalizeCtrlId(idRaw), idRaw, data: rec.data, childStart, childEnd: j });
|
|
4717
|
-
i = j;
|
|
4718
|
-
continue;
|
|
4719
|
-
}
|
|
4720
|
-
if (rec.tagId === TAG_PARA_TEXT && rec.level === baseLevel + 1) {
|
|
4721
|
-
textRecords.push(rec.data);
|
|
4722
|
-
} else if (rec.tagId === TAG_CHAR_SHAPE && rec.level === baseLevel + 1 && rec.data.length >= 8) {
|
|
4723
|
-
for (let offset = 0; offset + 7 < rec.data.length; offset += 8) {
|
|
4724
|
-
charShapeIds.push(rec.data.readUInt32LE(offset + 4));
|
|
4725
|
-
}
|
|
4726
|
-
}
|
|
4727
|
-
i++;
|
|
4728
|
-
}
|
|
4729
|
-
for (const ctrl of ctrls) {
|
|
4730
|
-
applyCtrlEffect(ctrl, records, ctx);
|
|
4731
|
-
}
|
|
4732
|
-
const state = createParaTextState();
|
|
4733
|
-
const resolver = (idx, id) => {
|
|
4734
|
-
let ctrl = idx >= 0 && idx < ctrls.length ? ctrls[idx] : void 0;
|
|
4735
|
-
if (!ctrl || ctrl.idRaw !== id && ctrl.id !== id) {
|
|
4736
|
-
ctrl = ctrls.find((c) => !c.resolved && (c.idRaw === id || c.id === id));
|
|
4737
|
-
}
|
|
4738
|
-
if (!ctrl) return null;
|
|
4739
|
-
ctrl.resolved = true;
|
|
4740
|
-
return ctrl.inlineText ?? null;
|
|
4741
|
-
};
|
|
4742
|
-
for (const data of textRecords) {
|
|
4743
|
-
appendParaText(state, data, resolver);
|
|
4744
|
-
}
|
|
4745
|
-
let text = state.text;
|
|
4746
|
-
if (state.fieldRanges.length > 0) {
|
|
4747
|
-
const ranges = [...state.fieldRanges].sort((a, b) => b.start - a.start);
|
|
4748
|
-
const applied = [];
|
|
4749
|
-
for (const r of ranges) {
|
|
4750
|
-
const ctrl = ctrls[r.ctrlIdx];
|
|
4751
|
-
if (!ctrl?.href || r.end <= r.start) continue;
|
|
4752
|
-
if (applied.some(([s, e]) => r.start < e && r.end > s)) continue;
|
|
4753
|
-
const href = sanitizeHref(ctrl.href);
|
|
4754
|
-
if (!href) continue;
|
|
4755
|
-
const anchor = text.slice(r.start, r.end);
|
|
4756
|
-
if (!anchor.trim()) continue;
|
|
4757
|
-
text = text.slice(0, r.start) + `[${anchor}](${href})` + text.slice(r.end);
|
|
4758
|
-
applied.push([r.start, r.end]);
|
|
4759
|
-
}
|
|
4760
|
-
}
|
|
4761
|
-
const trimmed = text.replace(/\$\$/g, "$ $").trim();
|
|
4762
|
-
let headingLevel = 0;
|
|
4763
|
-
let headMarker = null;
|
|
4764
|
-
const ps = ctx.docInfo && paraShapeId >= 0 && paraShapeId < ctx.docInfo.paraShapes.length ? ctx.docInfo.paraShapes[paraShapeId] : null;
|
|
4765
|
-
if (ps && ps.headType > 0) {
|
|
4766
|
-
if (ps.headType === 1) {
|
|
4767
|
-
headingLevel = Math.min(ps.paraLevel + 1, 6);
|
|
4768
|
-
}
|
|
4769
|
-
if (ps.headType === 1 || ps.headType === 2) {
|
|
4770
|
-
const nid = ps.numberingId || (ps.headType === 1 ? ctx.doc.outlineNumberingId : 0);
|
|
4771
|
-
const numbering = nid >= 1 ? ctx.docInfo?.numberings[nid - 1] : void 0;
|
|
4772
|
-
if (numbering) {
|
|
4773
|
-
const counters = ctx.doc.numbering.advance(nid, ps.paraLevel);
|
|
4774
|
-
const fmt = numbering.levelFormats[Math.min(ps.paraLevel, 6)];
|
|
4775
|
-
if (fmt) {
|
|
4776
|
-
const headText = expandNumberingFormat(fmt, counters, numbering);
|
|
4777
|
-
if (headText) headMarker = headText;
|
|
4778
|
-
}
|
|
4779
|
-
}
|
|
4780
|
-
} else if (ps.headType === 3) {
|
|
4781
|
-
const bullet = ps.numberingId >= 1 ? ctx.docInfo?.bullets[ps.numberingId - 1] : void 0;
|
|
4782
|
-
if (bullet && bullet.char !== "\uFFFF") headMarker = bullet.char;
|
|
4783
|
-
}
|
|
4784
|
-
}
|
|
4785
|
-
const blocks = [];
|
|
4786
|
-
const footnotes = ctrls.filter((c) => c.footnote).map((c) => c.footnote);
|
|
4787
|
-
if (trimmed) {
|
|
4788
|
-
const block = {
|
|
4789
|
-
type: headingLevel > 0 ? "heading" : "paragraph",
|
|
4790
|
-
text: headMarker ? `${headMarker} ${trimmed}` : trimmed,
|
|
4791
|
-
pageNumber: ctx.page
|
|
4792
|
-
};
|
|
4793
|
-
if (headingLevel > 0) block.level = headingLevel;
|
|
4794
|
-
if (ctx.docInfo && charShapeIds.length > 0) {
|
|
4795
|
-
const style = resolveCharStyle(charShapeIds, ctx.docInfo);
|
|
4796
|
-
if (style) {
|
|
4797
|
-
block.style = style;
|
|
4798
|
-
if (style.strike || style.underline) {
|
|
4799
|
-
let deco = style.strike ? `~~${trimmed}~~` : trimmed;
|
|
4800
|
-
if (style.underline) deco = `<u>${deco}</u>`;
|
|
4801
|
-
block.text = headMarker ? `${headMarker} ${deco}` : deco;
|
|
4802
|
-
}
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
|
|
4806
|
-
blocks.push(block);
|
|
4807
|
-
} else if (footnotes.length > 0) {
|
|
4808
|
-
blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber: ctx.page });
|
|
4809
|
-
}
|
|
4810
|
-
for (const ctrl of ctrls) {
|
|
4811
|
-
if (ctrl.afterBlocks) blocks.push(...ctrl.afterBlocks);
|
|
4812
|
-
}
|
|
4813
|
-
return blocks;
|
|
4814
|
-
}
|
|
4815
|
-
function applyCtrlEffect(ctrl, records, ctx) {
|
|
4816
|
-
switch (ctrl.id) {
|
|
4817
|
-
case CTRL_TBL: {
|
|
4818
|
-
const table2 = parseTableControl(ctrl, records, ctx);
|
|
4819
|
-
if (table2) ctrl.afterBlocks = [{ type: "table", table: table2, pageNumber: ctx.page }];
|
|
4820
|
-
return;
|
|
4821
|
-
}
|
|
4822
|
-
case CTRL_GSO: {
|
|
4823
|
-
const blocks = parseGsoControl(ctrl, records, ctx);
|
|
4824
|
-
if (blocks.length > 0) ctrl.afterBlocks = blocks;
|
|
4825
|
-
return;
|
|
4826
|
-
}
|
|
4827
|
-
case CTRL_EQED: {
|
|
4828
|
-
const eq = extractEquationFromSlice(records, ctrl.childStart, ctrl.childEnd);
|
|
4829
|
-
if (eq) ctrl.inlineText = eq;
|
|
4830
|
-
return;
|
|
4831
|
-
}
|
|
4832
|
-
case CTRL_FN:
|
|
4833
|
-
case CTRL_EN: {
|
|
4834
|
-
applyNoteEffect(ctrl, records, ctx, ctrl.id === CTRL_FN ? 1 : 2);
|
|
4835
|
-
return;
|
|
4836
|
-
}
|
|
4837
|
-
case CTRL_HEAD:
|
|
4838
|
-
case CTRL_FOOT: {
|
|
4839
|
-
applyHeaderFooterEffect(ctrl, records, ctx, ctrl.id === CTRL_HEAD);
|
|
4840
|
-
return;
|
|
4841
|
-
}
|
|
4842
|
-
case CTRL_ATNO: {
|
|
4843
|
-
if (ctrl.data.length >= 8) {
|
|
4844
|
-
const attr = ctrl.data.readUInt32LE(4);
|
|
4845
|
-
const type = attr & 15;
|
|
4846
|
-
const format = attr >>> 4 & 255;
|
|
4847
|
-
const num2 = ctx.doc.autoCounters.get(type) ?? 1;
|
|
4848
|
-
ctx.doc.autoCounters.set(type, num2 + 1);
|
|
4849
|
-
const prefix = ctrl.data.length >= 14 ? wcharAt(ctrl.data, 12) : "";
|
|
4850
|
-
const suffix = ctrl.data.length >= 16 ? wcharAt(ctrl.data, 14) : "";
|
|
4851
|
-
ctrl.inlineText = `${prefix}${formatNumber(num2, shapeFormatToNumFmt(format))}${suffix}`;
|
|
4852
|
-
}
|
|
4853
|
-
return;
|
|
4854
|
-
}
|
|
4855
|
-
case CTRL_NWNO: {
|
|
4856
|
-
if (ctrl.data.length >= 10) {
|
|
4857
|
-
const attr = ctrl.data.readUInt32LE(4);
|
|
4858
|
-
const type = attr & 15;
|
|
4859
|
-
const num2 = ctrl.data.readUInt16LE(8);
|
|
4860
|
-
if (num2 > 0) ctx.doc.autoCounters.set(type, num2);
|
|
4861
|
-
}
|
|
4862
|
-
return;
|
|
4863
|
-
}
|
|
4864
|
-
case CTRL_SECD: {
|
|
4865
|
-
if (ctrl.data.length >= 20) {
|
|
4866
|
-
ctx.doc.outlineNumberingId = ctrl.data.readUInt16LE(18);
|
|
4867
|
-
}
|
|
4868
|
-
return;
|
|
4869
|
-
}
|
|
4870
|
-
case CTRL_OLE: {
|
|
4871
|
-
ctx.warnings.push({ page: ctx.page, message: "\uC2A4\uD0B5\uB41C OLE \uAC1C\uCCB4", code: "SKIPPED_OLE" });
|
|
4872
|
-
return;
|
|
4873
|
-
}
|
|
4874
|
-
// 숨은 설명/단 정의/쪽번호 위치/감추기/찾아보기/책갈피/글자겹침/덧말 — 본문 텍스트 없음 또는 의도적 스킵
|
|
4875
|
-
case CTRL_TCMT:
|
|
4876
|
-
case CTRL_COLD:
|
|
4877
|
-
case CTRL_PGNP:
|
|
4878
|
-
case CTRL_PGHD:
|
|
4879
|
-
case CTRL_IDXM:
|
|
4880
|
-
case CTRL_BOKM:
|
|
4881
|
-
case CTRL_TCPS:
|
|
4882
|
-
case CTRL_TDUT:
|
|
4883
|
-
case CTRL_FORM:
|
|
4884
|
-
return;
|
|
4885
|
-
default: {
|
|
4886
|
-
if (isFieldCtrlId(ctrl.id)) {
|
|
4887
|
-
applyFieldEffect(ctrl);
|
|
4888
|
-
return;
|
|
4889
|
-
}
|
|
4890
|
-
const blocks = parseListHeaderParagraphs(ctrl, records, ctx);
|
|
4891
|
-
if (blocks.length > 0) ctrl.afterBlocks = blocks;
|
|
4892
|
-
}
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
function wcharAt(data, offset) {
|
|
4896
|
-
const code = data.readUInt16LE(offset);
|
|
4897
|
-
return code > 0 ? String.fromCharCode(code) : "";
|
|
4898
|
-
}
|
|
4899
|
-
function parseListHeaderParagraphs(ctrl, records, ctx) {
|
|
4900
|
-
if (ctx.depth >= MAX_NEST_DEPTH) return [];
|
|
4901
|
-
for (let i = ctrl.childStart; i < ctrl.childEnd; i++) {
|
|
4902
|
-
if (records[i].tagId === TAG_LIST_HEADER) {
|
|
4903
|
-
return parseParagraphList(records, i + 1, ctrl.childEnd, { ...ctx, depth: ctx.depth + 1 });
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
return [];
|
|
4907
|
-
}
|
|
4908
|
-
function blocksPlainText(blocks, sep) {
|
|
4909
|
-
const parts = [];
|
|
4910
|
-
for (const b of blocks) {
|
|
4911
|
-
if (b.type === "image") continue;
|
|
4912
|
-
if (b.type === "table") continue;
|
|
4913
|
-
if (b.text) {
|
|
4914
|
-
let t = b.text;
|
|
4915
|
-
if (b.footnoteText) t += ` (\uC8FC: ${b.footnoteText})`;
|
|
4916
|
-
parts.push(t);
|
|
4917
|
-
}
|
|
4918
|
-
}
|
|
4919
|
-
return parts.join(sep).trim();
|
|
4920
|
-
}
|
|
4921
|
-
function applyNoteEffect(ctrl, records, ctx, autoType) {
|
|
4922
|
-
const num2 = ctx.doc.autoCounters.get(autoType) ?? 1;
|
|
4923
|
-
let before = "";
|
|
4924
|
-
let after = "";
|
|
4925
|
-
let shape = 0;
|
|
4926
|
-
if (ctrl.data.length >= 12) {
|
|
4927
|
-
before = wcharAt(ctrl.data, 8);
|
|
4928
|
-
after = wcharAt(ctrl.data, 10);
|
|
4929
|
-
}
|
|
4930
|
-
if (ctrl.data.length >= 16) {
|
|
4931
|
-
shape = ctrl.data.readUInt32LE(12) & 255;
|
|
4932
|
-
}
|
|
4933
|
-
const formatted = formatNumber(num2, shapeFormatToNumFmt(shape));
|
|
4934
|
-
const marker = before || after ? `${before}${formatted}${after}` : `${formatted})`;
|
|
4935
|
-
const content = blocksPlainText(parseListHeaderParagraphs(ctrl, records, ctx), " ");
|
|
4936
|
-
if ((ctx.doc.autoCounters.get(autoType) ?? 1) <= num2) {
|
|
4937
|
-
ctx.doc.autoCounters.set(autoType, num2 + 1);
|
|
4938
|
-
}
|
|
4939
|
-
ctrl.inlineText = marker;
|
|
4940
|
-
if (content) ctrl.footnote = content.startsWith(marker) ? content : `${marker} ${content}`;
|
|
4941
|
-
}
|
|
4942
|
-
function applyHeaderFooterEffect(ctrl, records, ctx, isHeader) {
|
|
4943
|
-
const text = blocksPlainText(parseListHeaderParagraphs(ctrl, records, ctx), "\n");
|
|
4944
|
-
if (!text) return;
|
|
4945
|
-
const key = (isHeader ? "h:" : "f:") + text;
|
|
4946
|
-
if (ctx.doc.headerTexts.has(key)) return;
|
|
4947
|
-
ctx.doc.headerTexts.add(key);
|
|
4948
|
-
const block = { type: "paragraph", text, pageNumber: ctx.page };
|
|
4949
|
-
if (isHeader) ctx.doc.headerBlocks.push(block);
|
|
4950
|
-
else ctx.doc.footerBlocks.push(block);
|
|
4951
|
-
}
|
|
4952
|
-
function applyFieldEffect(ctrl) {
|
|
4953
|
-
if (ctrl.id === FIELD_HLK) {
|
|
4954
|
-
const command = parseFieldCommand(ctrl.data);
|
|
4955
|
-
if (command) {
|
|
4956
|
-
const url = hyperlinkUrlFromCommand(command);
|
|
4957
|
-
if (url) ctrl.href = url;
|
|
4958
|
-
}
|
|
4959
|
-
}
|
|
4960
|
-
}
|
|
4961
|
-
function parseFieldCommand(data) {
|
|
4962
|
-
if (data.length < 11) return null;
|
|
4963
|
-
const cmdLen = data.readUInt16LE(9);
|
|
4964
|
-
if (cmdLen === 0) return null;
|
|
4965
|
-
const start = 11;
|
|
4966
|
-
const end = start + cmdLen * 2;
|
|
4967
|
-
if (end > data.length) return null;
|
|
4968
|
-
return data.subarray(start, end).toString("utf16le").replace(/\0+$/, "");
|
|
4969
|
-
}
|
|
4970
|
-
function hyperlinkUrlFromCommand(command) {
|
|
4971
|
-
let url = "";
|
|
4972
|
-
for (let i = 0; i < command.length; i++) {
|
|
4973
|
-
const c = command[i];
|
|
4974
|
-
if (c === "\\" && i + 1 < command.length) {
|
|
4975
|
-
url += command[i + 1];
|
|
4976
|
-
i++;
|
|
4977
|
-
continue;
|
|
4978
|
-
}
|
|
4979
|
-
if (c === ";") break;
|
|
4980
|
-
url += c;
|
|
4981
|
-
}
|
|
4982
|
-
url = url.trim();
|
|
4983
|
-
return url.length > 0 && url.length < 2e3 ? url : null;
|
|
4984
|
-
}
|
|
4985
|
-
function parseTableControl(ctrl, records, ctx) {
|
|
4986
|
-
if (ctx.depth >= MAX_NEST_DEPTH) return null;
|
|
4987
|
-
const { childStart, childEnd } = ctrl;
|
|
4988
|
-
let rows = 0;
|
|
4989
|
-
let cols = 0;
|
|
4990
|
-
let tableIdx = -1;
|
|
4991
|
-
for (let i2 = childStart; i2 < childEnd; i2++) {
|
|
4992
|
-
if (records[i2].tagId === TAG_TABLE && records[i2].data.length >= 8) {
|
|
4993
|
-
rows = Math.min(records[i2].data.readUInt16LE(4), MAX_ROWS);
|
|
4994
|
-
cols = Math.min(records[i2].data.readUInt16LE(6), MAX_COLS);
|
|
4995
|
-
tableIdx = i2;
|
|
4996
|
-
break;
|
|
4997
|
-
}
|
|
4998
|
-
}
|
|
4999
|
-
if (tableIdx < 0 || rows === 0 || cols === 0) return null;
|
|
5000
|
-
let caption;
|
|
5001
|
-
for (let i2 = childStart; i2 < tableIdx; i2++) {
|
|
5002
|
-
if (records[i2].tagId === TAG_LIST_HEADER) {
|
|
5003
|
-
const capBlocks = parseParagraphList(records, i2 + 1, tableIdx, { ...ctx, depth: ctx.depth + 1 });
|
|
5004
|
-
const capText = blocksPlainText(capBlocks, " ");
|
|
5005
|
-
if (capText) caption = capText;
|
|
5006
|
-
break;
|
|
5007
|
-
}
|
|
5008
|
-
}
|
|
5009
|
-
const cells = [];
|
|
5010
|
-
let i = tableIdx + 1;
|
|
5011
|
-
while (i < childEnd) {
|
|
5012
|
-
const rec = records[i];
|
|
5013
|
-
if (rec.tagId === TAG_LIST_HEADER) {
|
|
5014
|
-
const cellLevel = rec.level;
|
|
5015
|
-
let j = i + 1;
|
|
5016
|
-
while (j < childEnd) {
|
|
5017
|
-
const r = records[j];
|
|
5018
|
-
if (r.level < cellLevel) break;
|
|
5019
|
-
if (r.level === cellLevel && (r.tagId === TAG_LIST_HEADER || r.tagId === TAG_TABLE)) break;
|
|
5020
|
-
j++;
|
|
5021
|
-
}
|
|
5022
|
-
cells.push(parseCell(records, i, j, ctx));
|
|
5023
|
-
i = j;
|
|
5024
|
-
continue;
|
|
5025
|
-
}
|
|
5026
|
-
i++;
|
|
5027
|
-
}
|
|
5028
|
-
if (cells.length === 0) return null;
|
|
5029
|
-
const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
|
|
5030
|
-
if (hasAddr) {
|
|
5031
|
-
const cellRows2 = arrangeCells(rows, cols, cells);
|
|
5032
|
-
const irCells = cellRows2.map((row) => row.map((c) => {
|
|
5033
|
-
const ir = { text: c.text.trim(), colSpan: c.colSpan, rowSpan: c.rowSpan };
|
|
5034
|
-
if (c.blocks?.length) ir.blocks = c.blocks;
|
|
5035
|
-
if (c.isHeader) ir.isHeader = true;
|
|
5036
|
-
return ir;
|
|
5037
|
-
}));
|
|
5038
|
-
const table3 = { rows, cols, cells: irCells, hasHeader: rows > 1 };
|
|
5039
|
-
if (caption) table3.caption = caption;
|
|
5040
|
-
return table3;
|
|
5041
|
-
}
|
|
5042
|
-
const cellRows = arrangeCells(rows, cols, cells);
|
|
5043
|
-
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: ctx.doc.keepTrailingEmptyCols });
|
|
5044
|
-
if (caption && table2.rows > 0) table2.caption = caption;
|
|
5045
|
-
return table2.rows > 0 ? table2 : null;
|
|
5046
|
-
}
|
|
5047
|
-
function parseCell(records, lhIdx, end, ctx) {
|
|
5048
|
-
const rec = records[lhIdx];
|
|
5049
|
-
let colSpan = 1;
|
|
5050
|
-
let rowSpan = 1;
|
|
5051
|
-
let colAddr;
|
|
5052
|
-
let rowAddr;
|
|
5053
|
-
let isHeader = false;
|
|
5054
|
-
if (rec.data.length >= 16) {
|
|
5055
|
-
isHeader = (rec.data.readUInt16LE(6) & 4) !== 0;
|
|
5056
|
-
colAddr = rec.data.readUInt16LE(8);
|
|
5057
|
-
rowAddr = rec.data.readUInt16LE(10);
|
|
5058
|
-
const cs = rec.data.readUInt16LE(12);
|
|
5059
|
-
const rs = rec.data.readUInt16LE(14);
|
|
5060
|
-
if (cs > 0) colSpan = Math.min(cs, MAX_COLS);
|
|
5061
|
-
if (rs > 0) rowSpan = Math.min(rs, MAX_ROWS);
|
|
5062
|
-
}
|
|
5063
|
-
const blocks = ctx.depth < MAX_NEST_DEPTH ? parseParagraphList(records, lhIdx + 1, end, { ...ctx, depth: ctx.depth + 1 }) : [];
|
|
5064
|
-
const parts = [];
|
|
5065
|
-
let hasStructure = false;
|
|
5066
|
-
for (const b of blocks) {
|
|
5067
|
-
if (b.type === "image" && b.text) {
|
|
5068
|
-
parts.push(``);
|
|
5069
|
-
hasStructure = true;
|
|
5070
|
-
} else if (b.type === "table" && b.table) {
|
|
5071
|
-
const flat = convertTableToText(b.table.cells);
|
|
5072
|
-
if (flat) parts.push(flat);
|
|
5073
|
-
hasStructure = true;
|
|
5074
|
-
} else if (b.text) {
|
|
5075
|
-
let t = b.text;
|
|
5076
|
-
if (b.footnoteText) {
|
|
5077
|
-
t += ` (\uC8FC: ${b.footnoteText})`;
|
|
5078
|
-
hasStructure = true;
|
|
5079
|
-
}
|
|
5080
|
-
parts.push(t);
|
|
5081
|
-
}
|
|
5082
|
-
}
|
|
5083
|
-
const cell2 = { text: parts.join("\n"), colSpan, rowSpan, colAddr, rowAddr };
|
|
5084
|
-
if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
|
|
5085
|
-
if (isHeader) cell2.isHeader = true;
|
|
5086
|
-
return cell2;
|
|
5087
|
-
}
|
|
5088
|
-
function arrangeCells(rows, cols, cells) {
|
|
5089
|
-
const grid = Array.from({ length: rows }, () => Array(cols).fill(null));
|
|
5090
|
-
const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
|
|
5091
|
-
if (hasAddr) {
|
|
5092
|
-
for (const cell2 of cells) {
|
|
5093
|
-
const r = cell2.rowAddr ?? 0;
|
|
5094
|
-
const c = cell2.colAddr ?? 0;
|
|
5095
|
-
if (r >= rows || c >= cols) continue;
|
|
5096
|
-
grid[r][c] = cell2;
|
|
5097
|
-
for (let dr = 0; dr < cell2.rowSpan; dr++) {
|
|
5098
|
-
for (let dc = 0; dc < cell2.colSpan; dc++) {
|
|
5099
|
-
if (dr === 0 && dc === 0) continue;
|
|
5100
|
-
if (r + dr < rows && c + dc < cols)
|
|
5101
|
-
grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
|
|
5102
|
-
}
|
|
5103
|
-
}
|
|
5104
|
-
}
|
|
5105
|
-
} else {
|
|
5106
|
-
let cellIdx = 0;
|
|
5107
|
-
for (let r = 0; r < rows && cellIdx < cells.length; r++) {
|
|
5108
|
-
for (let c = 0; c < cols && cellIdx < cells.length; c++) {
|
|
5109
|
-
if (grid[r][c] !== null) continue;
|
|
5110
|
-
const cell2 = cells[cellIdx++];
|
|
5111
|
-
grid[r][c] = cell2;
|
|
5112
|
-
for (let dr = 0; dr < cell2.rowSpan; dr++) {
|
|
5113
|
-
for (let dc = 0; dc < cell2.colSpan; dc++) {
|
|
5114
|
-
if (dr === 0 && dc === 0) continue;
|
|
5115
|
-
if (r + dr < rows && c + dc < cols)
|
|
5116
|
-
grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
2050
|
+
const parser = createXmlParser();
|
|
2051
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2052
|
+
if (!doc.documentElement) return;
|
|
2053
|
+
const getText = (tagNames) => {
|
|
2054
|
+
for (const tag of tagNames) {
|
|
2055
|
+
const els = doc.getElementsByTagName(tag);
|
|
2056
|
+
if (els.length > 0) {
|
|
2057
|
+
const text = els[0].textContent?.trim();
|
|
2058
|
+
if (text) return text;
|
|
5119
2059
|
}
|
|
5120
2060
|
}
|
|
2061
|
+
return void 0;
|
|
2062
|
+
};
|
|
2063
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2064
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2065
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2066
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2067
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2068
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2069
|
+
if (keywords && !metadata.keywords) {
|
|
2070
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
5121
2071
|
}
|
|
5122
|
-
return grid.map((row) => row.map((c) => c || { text: "", colSpan: 1, rowSpan: 1 }));
|
|
5123
2072
|
}
|
|
5124
|
-
function
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
const t = records[i].tagId;
|
|
5131
|
-
if (t === TAG_SHAPE_COMPONENT || t === TAG_SHAPE_COMPONENT_CONTAINER) {
|
|
5132
|
-
scIdx = i;
|
|
5133
|
-
break;
|
|
5134
|
-
}
|
|
2073
|
+
async function extractHwpxMetadataOnly(buffer) {
|
|
2074
|
+
let zip;
|
|
2075
|
+
try {
|
|
2076
|
+
zip = await JSZip.loadAsync(buffer);
|
|
2077
|
+
} catch {
|
|
2078
|
+
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
5135
2079
|
}
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
2080
|
+
const metadata = {};
|
|
2081
|
+
await extractHwpxMetadata(zip, metadata);
|
|
2082
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2083
|
+
metadata.pageCount = sectionPaths.length;
|
|
2084
|
+
return metadata;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// src/hwpx/parser.ts
|
|
2088
|
+
async function parseHwpxDocument(buffer, options) {
|
|
2089
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
2090
|
+
let zip;
|
|
2091
|
+
try {
|
|
2092
|
+
zip = await JSZip2.loadAsync(buffer);
|
|
2093
|
+
} catch {
|
|
2094
|
+
return extractFromBrokenZip(buffer);
|
|
2095
|
+
}
|
|
2096
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
2097
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
2098
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2099
|
+
}
|
|
2100
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
2101
|
+
if (manifestFile) {
|
|
2102
|
+
const manifestXml = await manifestFile.async("text");
|
|
2103
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
2104
|
+
if (options?.password) {
|
|
2105
|
+
await decryptHwpxInPlace(zip, manifestXml, options.password);
|
|
2106
|
+
} else {
|
|
2107
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
2108
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
2109
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
2110
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
throw new KordocError(
|
|
2114
|
+
"\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWPX \uD30C\uC77C\uC785\uB2C8\uB2E4. password \uC635\uC158\uC5D0 \uC5F4\uAE30 \uC554\uD638\uB97C \uC9C0\uC815\uD558\uC138\uC694."
|
|
2115
|
+
);
|
|
5141
2116
|
}
|
|
5142
2117
|
}
|
|
5143
2118
|
}
|
|
5144
|
-
const
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
2119
|
+
const decompressed = { total: 0 };
|
|
2120
|
+
const metadata = {};
|
|
2121
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
2122
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
2123
|
+
const warnings = [];
|
|
2124
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2125
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2126
|
+
let allBlocks = [];
|
|
2127
|
+
const shared = createSectionShared();
|
|
2128
|
+
shared.kordocLayout = await readKordocLayout(zip);
|
|
2129
|
+
shared.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
2130
|
+
shared.keepEmptyParagraphs = options?.keepEmptyParagraphs;
|
|
2131
|
+
const sectionRanges = [];
|
|
2132
|
+
let parsedSections = 0;
|
|
2133
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
2134
|
+
const file = zip.file(sectionPaths[si]);
|
|
2135
|
+
if (!file) continue;
|
|
2136
|
+
try {
|
|
2137
|
+
const xml = await file.async("text");
|
|
2138
|
+
decompressed.total += xml.length * 2;
|
|
2139
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new ZipBombError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2140
|
+
const start = allBlocks.length;
|
|
2141
|
+
allBlocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
2142
|
+
sectionRanges.push({ sectionNum: si + 1, start, end: allBlocks.length });
|
|
2143
|
+
parsedSections++;
|
|
2144
|
+
options?.onProgress?.(parsedSections, sectionPaths.length);
|
|
2145
|
+
} catch (secErr) {
|
|
2146
|
+
if (secErr instanceof ZipBombError) throw secErr;
|
|
2147
|
+
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
5150
2148
|
}
|
|
5151
2149
|
}
|
|
5152
|
-
const
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
2150
|
+
const layoutPages = shared.pageState.allUsable && parsedSections > 0;
|
|
2151
|
+
metadata.pageMode = layoutPages ? "layout" : "section";
|
|
2152
|
+
metadata.pageCount = layoutPages ? Math.max(shared.pageState.base, 1) : sectionPaths.length;
|
|
2153
|
+
if (!layoutPages) {
|
|
2154
|
+
for (const r of sectionRanges) {
|
|
2155
|
+
for (let b = r.start; b < r.end; b++) allBlocks[b].pageNumber = r.sectionNum;
|
|
5157
2156
|
}
|
|
5158
2157
|
}
|
|
5159
|
-
if (
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
}
|
|
5164
|
-
function pictureToImageBlock(data, ctx) {
|
|
5165
|
-
if (data.length < 73) return null;
|
|
5166
|
-
const binDataId = data.readUInt16LE(71);
|
|
5167
|
-
if (binDataId === 0) return null;
|
|
5168
|
-
const item = ctx.docInfo?.binData[binDataId - 1];
|
|
5169
|
-
if (item?.kind === "link") {
|
|
5170
|
-
ctx.warnings.push({ page: ctx.page, message: `\uC678\uBD80 \uC5F0\uACB0 \uC774\uBBF8\uC9C0 (binDataId ${binDataId})`, code: "SKIPPED_IMAGE" });
|
|
5171
|
-
return null;
|
|
5172
|
-
}
|
|
5173
|
-
const storageId = item && item.storageId > 0 ? item.storageId : binDataId;
|
|
5174
|
-
return { type: "image", text: String(storageId), pageNumber: ctx.page };
|
|
5175
|
-
}
|
|
5176
|
-
function resolveCharStyle(charShapeIds, docInfo) {
|
|
5177
|
-
if (charShapeIds.length === 0 || docInfo.charShapes.length === 0) return void 0;
|
|
5178
|
-
const freq = /* @__PURE__ */ new Map();
|
|
5179
|
-
let maxCount = 0, dominantId = charShapeIds[0];
|
|
5180
|
-
for (const id of charShapeIds) {
|
|
5181
|
-
const count = (freq.get(id) || 0) + 1;
|
|
5182
|
-
freq.set(id, count);
|
|
5183
|
-
if (count > maxCount) {
|
|
5184
|
-
maxCount = count;
|
|
5185
|
-
dominantId = id;
|
|
2158
|
+
if (options?.pages) {
|
|
2159
|
+
const pageFilter = parsePageRange(options.pages, metadata.pageCount);
|
|
2160
|
+
allBlocks = allBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
|
|
2161
|
+
if (!layoutPages) {
|
|
2162
|
+
warnings.push({ code: "PAGE_BOUNDARY_APPROXIMATE", message: "\uC870\uD310 \uCE90\uC2DC\uAC00 \uC5C6\uC5B4 pages \uD544\uD130\uB97C \uC139\uC158 \uB2E8\uC704 \uADFC\uC0AC\uB85C \uC801\uC6A9\uD588\uC2B5\uB2C8\uB2E4" });
|
|
5186
2163
|
}
|
|
5187
2164
|
}
|
|
5188
|
-
const
|
|
5189
|
-
|
|
5190
|
-
const
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
if (hasRealUnderline(cs.attrFlags)) style.underline = true;
|
|
5196
|
-
return style.fontSize || style.bold || style.italic || style.strike || style.underline ? style : void 0;
|
|
5197
|
-
}
|
|
5198
|
-
function hasRealStrike(attrFlags) {
|
|
5199
|
-
const strikeBits = attrFlags >> 18 & 7;
|
|
5200
|
-
const shapeId = attrFlags >> 26 & 15;
|
|
5201
|
-
return strikeBits !== 0 && shapeId <= 12;
|
|
5202
|
-
}
|
|
5203
|
-
function hasRealUnderline(attrFlags) {
|
|
5204
|
-
return (attrFlags >> 2 & 3) === 1;
|
|
2165
|
+
const blocks = allBlocks;
|
|
2166
|
+
applyPageText(blocks, shared);
|
|
2167
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !options?.pages);
|
|
2168
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
2169
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
2170
|
+
const markdown = blocksToMarkdown(blocks);
|
|
2171
|
+
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
5205
2172
|
}
|
|
5206
2173
|
|
|
5207
2174
|
// src/hwp3/parser.ts
|
|
5208
|
-
import { inflateRawSync as
|
|
2175
|
+
import { inflateRawSync as inflateRawSync4 } from "zlib";
|
|
5209
2176
|
|
|
5210
2177
|
// src/hwp3/reader.ts
|
|
5211
2178
|
var InsufficientDataError = class extends Error {
|
|
@@ -5215,12 +2182,15 @@ var InsufficientDataError = class extends Error {
|
|
|
5215
2182
|
this.available = available;
|
|
5216
2183
|
this.name = "InsufficientDataError";
|
|
5217
2184
|
}
|
|
2185
|
+
requested;
|
|
2186
|
+
available;
|
|
5218
2187
|
};
|
|
5219
2188
|
var Reader = class {
|
|
5220
2189
|
constructor(buf, start = 0) {
|
|
5221
2190
|
this.buf = buf;
|
|
5222
2191
|
this.pos = start;
|
|
5223
2192
|
}
|
|
2193
|
+
buf;
|
|
5224
2194
|
pos;
|
|
5225
2195
|
position() {
|
|
5226
2196
|
return this.pos;
|
|
@@ -17607,7 +14577,7 @@ function readHeader(reader) {
|
|
|
17607
14577
|
|
|
17608
14578
|
// src/hwp3/crypto.ts
|
|
17609
14579
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
17610
|
-
import { inflateRawSync as
|
|
14580
|
+
import { inflateRawSync as inflateRawSync3 } from "zlib";
|
|
17611
14581
|
var FIXED_HEADER_BYTES = SIGNATURE_LEN + DOC_INFO_SIZE + DOC_SUMMARY_SIZE;
|
|
17612
14582
|
var PASSWORD_FLAG_OFFSET = SIGNATURE_LEN + 96;
|
|
17613
14583
|
var COMPRESSION_FLAG_OFFSET = SIGNATURE_LEN + 124;
|
|
@@ -17667,7 +14637,7 @@ function decryptHwp3Document(input, password) {
|
|
|
17667
14637
|
const decrypted = desEcbDecrypt(aligned, deriveDesKey(password));
|
|
17668
14638
|
let inflated;
|
|
17669
14639
|
try {
|
|
17670
|
-
inflated =
|
|
14640
|
+
inflated = inflateRawSync3(decrypted, { maxOutputLength: MAX_DECOMPRESSED_BYTES });
|
|
17671
14641
|
} catch (err) {
|
|
17672
14642
|
if (err?.code === "ERR_BUFFER_TOO_LARGE") {
|
|
17673
14643
|
throw new KordocError(
|
|
@@ -17687,7 +14657,7 @@ function decryptHwp3Document(input, password) {
|
|
|
17687
14657
|
}
|
|
17688
14658
|
|
|
17689
14659
|
// src/hwp3/parser.ts
|
|
17690
|
-
var
|
|
14660
|
+
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
17691
14661
|
var PIC_TYPE_DRAWING = 3;
|
|
17692
14662
|
var PARA_SHAPE_SIZE = 187;
|
|
17693
14663
|
var LINE_INFO_SIZE = 14;
|
|
@@ -17730,10 +14700,10 @@ function parseHwp3Document(buffer, options) {
|
|
|
17730
14700
|
const warnings = [];
|
|
17731
14701
|
if (header.compressed !== 0) {
|
|
17732
14702
|
try {
|
|
17733
|
-
body =
|
|
14703
|
+
body = inflateRawSync4(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE2 });
|
|
17734
14704
|
} catch (err) {
|
|
17735
14705
|
if (err?.code === "ERR_BUFFER_TOO_LARGE") {
|
|
17736
|
-
throw new KordocError(`HWP3 \uC555\uCD95 \uD574\uC81C \uACB0\uACFC\uAC00 \uCD5C\uB300 \uD5C8\uC6A9 \uD06C\uAE30(${
|
|
14706
|
+
throw new KordocError(`HWP3 \uC555\uCD95 \uD574\uC81C \uACB0\uACFC\uAC00 \uCD5C\uB300 \uD5C8\uC6A9 \uD06C\uAE30(${MAX_DECOMPRESS_SIZE2 / 1024 / 1024}MB)\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4`);
|
|
17737
14707
|
}
|
|
17738
14708
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
17739
14709
|
throw new KordocError(`HWP3 \uC555\uCD95 \uD574\uC81C \uC2E4\uD328: ${msg2}`);
|
|
@@ -17745,7 +14715,7 @@ function parseHwp3Document(buffer, options) {
|
|
|
17745
14715
|
const ctx = { paragraphs: [], warnings };
|
|
17746
14716
|
try {
|
|
17747
14717
|
skipFontFacesAndStyles(bodyReader);
|
|
17748
|
-
|
|
14718
|
+
parseParagraphList(bodyReader, ctx);
|
|
17749
14719
|
} catch (err) {
|
|
17750
14720
|
warnings.push({
|
|
17751
14721
|
code: "PARTIAL_PARSE",
|
|
@@ -17777,7 +14747,7 @@ function skipFontFacesAndStyles(reader) {
|
|
|
17777
14747
|
const nStyles = reader.readU16();
|
|
17778
14748
|
reader.skip(nStyles * STYLE_RECORD_SIZE);
|
|
17779
14749
|
}
|
|
17780
|
-
function
|
|
14750
|
+
function parseParagraphList(reader, ctx) {
|
|
17781
14751
|
for (; ; ) {
|
|
17782
14752
|
if (reader.eof()) return;
|
|
17783
14753
|
const followPrev = reader.readU8();
|
|
@@ -17858,17 +14828,17 @@ function parseCharStream(reader, charCount, ctx) {
|
|
|
17858
14828
|
break;
|
|
17859
14829
|
case 15: {
|
|
17860
14830
|
reader.skip(8);
|
|
17861
|
-
|
|
14831
|
+
parseParagraphList(reader, ctx);
|
|
17862
14832
|
break;
|
|
17863
14833
|
}
|
|
17864
14834
|
case 16: {
|
|
17865
14835
|
reader.skip(10);
|
|
17866
|
-
|
|
14836
|
+
parseParagraphList(reader, ctx);
|
|
17867
14837
|
break;
|
|
17868
14838
|
}
|
|
17869
14839
|
case 17: {
|
|
17870
14840
|
reader.skip(14);
|
|
17871
|
-
|
|
14841
|
+
parseParagraphList(reader, ctx);
|
|
17872
14842
|
break;
|
|
17873
14843
|
}
|
|
17874
14844
|
case 5:
|
|
@@ -17910,9 +14880,9 @@ function parseTableLike(reader, ctx) {
|
|
|
17910
14880
|
}
|
|
17911
14881
|
reader.skip(27 * cellCount);
|
|
17912
14882
|
for (let i = 0; i < cellCount; i++) {
|
|
17913
|
-
|
|
14883
|
+
parseParagraphList(reader, ctx);
|
|
17914
14884
|
}
|
|
17915
|
-
|
|
14885
|
+
parseParagraphList(reader, ctx);
|
|
17916
14886
|
return "";
|
|
17917
14887
|
}
|
|
17918
14888
|
function parsePicture(reader, ctx) {
|
|
@@ -17921,7 +14891,7 @@ function parsePicture(reader, ctx) {
|
|
|
17921
14891
|
let ext = null;
|
|
17922
14892
|
if (nExt > 0 && nExt < 100 * 1024 * 1024) ext = reader.readBytes(nExt);
|
|
17923
14893
|
if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx);
|
|
17924
|
-
|
|
14894
|
+
parseParagraphList(reader, ctx);
|
|
17925
14895
|
}
|
|
17926
14896
|
function parseDrawingObject(ext, ctx) {
|
|
17927
14897
|
let lists;
|
|
@@ -17933,7 +14903,7 @@ function parseDrawingObject(ext, ctx) {
|
|
|
17933
14903
|
}
|
|
17934
14904
|
for (const list of lists) {
|
|
17935
14905
|
try {
|
|
17936
|
-
|
|
14906
|
+
parseParagraphList(new Reader(list), ctx);
|
|
17937
14907
|
} catch (err) {
|
|
17938
14908
|
pushDrawingWarning(ctx, `HWP3 \uAE00\uC0C1\uC790 \uBB38\uB2E8 \uB9AC\uC2A4\uD2B8 \uD30C\uC2F1 \uC2E4\uD328: ${errText(err)}`);
|
|
17939
14909
|
}
|
|
@@ -17965,7 +14935,7 @@ function isDistributionSentinel(markdown) {
|
|
|
17965
14935
|
import JSZip3 from "jszip";
|
|
17966
14936
|
import { DOMParser } from "@xmldom/xmldom";
|
|
17967
14937
|
var MAX_SHEETS = 100;
|
|
17968
|
-
var
|
|
14938
|
+
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
17969
14939
|
var MAX_ROWS2 = 1e4;
|
|
17970
14940
|
var MAX_COLS2 = 200;
|
|
17971
14941
|
function cleanNumericValue(raw) {
|
|
@@ -18246,7 +15216,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex, keep
|
|
|
18246
15216
|
return blocks;
|
|
18247
15217
|
}
|
|
18248
15218
|
async function parseXlsxDocument(buffer, options) {
|
|
18249
|
-
precheckZipSize(buffer,
|
|
15219
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
18250
15220
|
const zip = await JSZip3.loadAsync(buffer);
|
|
18251
15221
|
const warnings = [];
|
|
18252
15222
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
@@ -18374,11 +15344,11 @@ var OP_SHRFMLA = 1212;
|
|
|
18374
15344
|
var OP_ARRAY = 545;
|
|
18375
15345
|
var DT_GLOBALS = 5;
|
|
18376
15346
|
var DT_WORKSHEET = 16;
|
|
18377
|
-
var
|
|
18378
|
-
function
|
|
15347
|
+
var MAX_RECORDS = 1e6;
|
|
15348
|
+
function readRecords(stream) {
|
|
18379
15349
|
const out = [];
|
|
18380
15350
|
let offset = 0;
|
|
18381
|
-
while (offset + 4 <= stream.length && out.length <
|
|
15351
|
+
while (offset + 4 <= stream.length && out.length < MAX_RECORDS) {
|
|
18382
15352
|
const recOffset = offset;
|
|
18383
15353
|
const opcode = stream.readUInt16LE(offset);
|
|
18384
15354
|
const length = stream.readUInt16LE(offset + 2);
|
|
@@ -18955,7 +15925,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18955
15925
|
if (!wb) {
|
|
18956
15926
|
throw new KordocError("XLS: Workbook \uC2A4\uD2B8\uB9BC\uC774 \uC5C6\uC74C (BIFF5 \uB610\uB294 \uBE44\uD45C\uC900 \uD30C\uC77C)");
|
|
18957
15927
|
}
|
|
18958
|
-
const records =
|
|
15928
|
+
const records = readRecords(wb);
|
|
18959
15929
|
if (records.length === 0) {
|
|
18960
15930
|
throw new KordocError("XLS: \uC2DC\uADF8\uB2C8\uCC98 \uB808\uCF54\uB4DC\uAC00 \uC5C6\uC74C (Workbook \uC2A4\uD2B8\uB9BC \uC190\uC0C1)");
|
|
18961
15931
|
}
|
|
@@ -19391,7 +16361,7 @@ function isDisplayMath(el) {
|
|
|
19391
16361
|
}
|
|
19392
16362
|
|
|
19393
16363
|
// src/docx/parser.ts
|
|
19394
|
-
var
|
|
16364
|
+
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
19395
16365
|
function matchesLocal(el, localName3) {
|
|
19396
16366
|
return el.localName === localName3 || (el.tagName?.endsWith(`:${localName3}`) ?? false);
|
|
19397
16367
|
}
|
|
@@ -19688,7 +16658,7 @@ function collectInline(p, footnotes, rels) {
|
|
|
19688
16658
|
const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
|
|
19689
16659
|
return { text, bold, italic, footnoteText };
|
|
19690
16660
|
}
|
|
19691
|
-
function
|
|
16661
|
+
function parseParagraph(p, styles, numbering, footnotes, rels) {
|
|
19692
16662
|
const pPrEls = getChildElements(p, "pPr");
|
|
19693
16663
|
let styleId = "";
|
|
19694
16664
|
let numId = "";
|
|
@@ -19808,10 +16778,10 @@ function collectCellText(tc2, styles, numbering, footnotes, rels, depth) {
|
|
|
19808
16778
|
if (depth > 20) return parts;
|
|
19809
16779
|
for (const el of effectiveChildElements(tc2)) {
|
|
19810
16780
|
if (matchesLocal(el, "p")) {
|
|
19811
|
-
const block =
|
|
16781
|
+
const block = parseParagraph(el, styles, numbering, footnotes, rels);
|
|
19812
16782
|
if (block?.text) parts.push(block.text);
|
|
19813
16783
|
for (const tp of collectTextboxParagraphs(el)) {
|
|
19814
|
-
const tb =
|
|
16784
|
+
const tb = parseParagraph(tp, styles, numbering, footnotes, rels);
|
|
19815
16785
|
if (tb?.text) parts.push(tb.text);
|
|
19816
16786
|
}
|
|
19817
16787
|
} else if (matchesLocal(el, "tbl")) {
|
|
@@ -19905,7 +16875,7 @@ function emitParagraphImages(p, imageMap, linked, out) {
|
|
|
19905
16875
|
}
|
|
19906
16876
|
}
|
|
19907
16877
|
async function parseDocxDocument(buffer, options) {
|
|
19908
|
-
precheckZipSize(buffer,
|
|
16878
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
19909
16879
|
const zip = await JSZip4.loadAsync(buffer);
|
|
19910
16880
|
const warnings = [];
|
|
19911
16881
|
const docFile = zip.file("word/document.xml");
|
|
@@ -19967,11 +16937,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19967
16937
|
for (const el of topLevel) {
|
|
19968
16938
|
const localName3 = el.localName ?? el.tagName?.split(":").pop();
|
|
19969
16939
|
if (localName3 === "p") {
|
|
19970
|
-
const block =
|
|
16940
|
+
const block = parseParagraph(el, styles, numbering, footnotes, rels);
|
|
19971
16941
|
if (block) blocks.push(block);
|
|
19972
16942
|
if (imageMap.size > 0) emitParagraphImages(el, imageMap, linkedImages, blocks);
|
|
19973
16943
|
for (const tp of collectTextboxParagraphs(el)) {
|
|
19974
|
-
const tb =
|
|
16944
|
+
const tb = parseParagraph(tp, styles, numbering, footnotes, rels);
|
|
19975
16945
|
if (tb) blocks.push(tb);
|
|
19976
16946
|
if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
|
|
19977
16947
|
}
|
|
@@ -20067,7 +17037,7 @@ function parseHwpmlDocument(buffer, options) {
|
|
|
20067
17037
|
if (localName(el) !== "SECTION") continue;
|
|
20068
17038
|
sectionIdx++;
|
|
20069
17039
|
if (pageFilter && !pageFilter.has(sectionIdx)) continue;
|
|
20070
|
-
|
|
17040
|
+
parseSection(el, blocks, paraShapeMap, sectionIdx, warnings, options?.keepTrailingEmptyCols ?? false);
|
|
20071
17041
|
}
|
|
20072
17042
|
const outline = blocks.filter((b) => b.type === "heading" && b.text).map((b) => ({ level: b.level ?? 1, text: b.text, pageNumber: b.pageNumber }));
|
|
20073
17043
|
const markdown = blocksToMarkdown(blocks);
|
|
@@ -20103,7 +17073,7 @@ function buildParaShapeMap(root) {
|
|
|
20103
17073
|
}
|
|
20104
17074
|
return map;
|
|
20105
17075
|
}
|
|
20106
|
-
function
|
|
17076
|
+
function parseSection(section, blocks, paraShapeMap, sectionNum, warnings, keep) {
|
|
20107
17077
|
walkContent(section, blocks, paraShapeMap, sectionNum, warnings, false, keep);
|
|
20108
17078
|
}
|
|
20109
17079
|
function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth = 0) {
|
|
@@ -20118,7 +17088,7 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
|
|
|
20118
17088
|
}
|
|
20119
17089
|
if (tag === "P") {
|
|
20120
17090
|
if (!inHeaderFooter) {
|
|
20121
|
-
|
|
17091
|
+
parseParagraph2(el, blocks, paraShapeMap, sectionNum);
|
|
20122
17092
|
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
20123
17093
|
}
|
|
20124
17094
|
continue;
|
|
@@ -20151,7 +17121,7 @@ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, keep, d
|
|
|
20151
17121
|
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep, depth + 1);
|
|
20152
17122
|
}
|
|
20153
17123
|
}
|
|
20154
|
-
function
|
|
17124
|
+
function parseParagraph2(el, blocks, paraShapeMap, sectionNum) {
|
|
20155
17125
|
const paraShapeId = el.getAttribute("ParaShape") ?? "";
|
|
20156
17126
|
const shapeInfo = paraShapeMap.get(paraShapeId);
|
|
20157
17127
|
const text = extractParagraphText(el);
|
|
@@ -20470,6 +17440,7 @@ var ValueCursor = class {
|
|
|
20470
17440
|
constructor(values) {
|
|
20471
17441
|
this.values = values;
|
|
20472
17442
|
}
|
|
17443
|
+
values;
|
|
20473
17444
|
nextIdx = /* @__PURE__ */ new Map();
|
|
20474
17445
|
keys() {
|
|
20475
17446
|
return this.values.keys();
|
|
@@ -21886,6 +18857,8 @@ var GAEJOSIK_MARGINS = { top: 15, bottom: 15, left: 20, right: 20 };
|
|
|
21886
18857
|
var SEOUL_REPORT_MARGINS = { top: 13, bottom: 13, left: 18, right: 18 };
|
|
21887
18858
|
var SEOUL_REPORT_HEADER_FOOTER = 3600;
|
|
21888
18859
|
var GAEJOSIK_HEADER_FOOTER = 4251;
|
|
18860
|
+
var MINISTRY_MARGINS = { top: 10, bottom: 10, left: 20, right: 20 };
|
|
18861
|
+
var MINISTRY_HEADER_FOOTER = 2835;
|
|
21889
18862
|
var PRESET_DEFAULTS = {
|
|
21890
18863
|
// 서울시 실결재 104건 중 12pt 64건(13pt 30, 15pt 5) — 지배값을 기본으로 사용.
|
|
21891
18864
|
official: { bodyPt: 12, lineSpacing: 160, numbering: "standard" },
|
|
@@ -21896,7 +18869,9 @@ var PRESET_DEFAULTS = {
|
|
|
21896
18869
|
minutes: { bodyPt: 14, lineSpacing: 130, numbering: "standard" },
|
|
21897
18870
|
gaejosik: { bodyPt: 15, lineSpacing: 160, numbering: "gaejosik" },
|
|
21898
18871
|
// 보도자료 — 실측(국토부 실물): 본문 바탕 14pt 160%, □→ㅇ→*(각주) 부호
|
|
21899
|
-
press: { bodyPt: 14, lineSpacing: 160, numbering: "report" }
|
|
18872
|
+
press: { bodyPt: 14, lineSpacing: 160, numbering: "report" },
|
|
18873
|
+
// 중앙부처 업무보고 — 실측(재경부 2차 업무보고): 함초롬바탕 15pt, 줄피치 21.7pt(≈145%), □→ㅇ→-→*(각주)
|
|
18874
|
+
ministry: { bodyPt: 15, lineSpacing: 145, numbering: "report" }
|
|
21900
18875
|
};
|
|
21901
18876
|
var PRESET_ALIAS = {
|
|
21902
18877
|
official: "official",
|
|
@@ -21921,7 +18896,11 @@ var PRESET_ALIAS = {
|
|
|
21921
18896
|
\uC815\uBD80\uBCF4\uACE0\uC11C: "gaejosik",
|
|
21922
18897
|
\uC815\uBD80\uD45C\uC900\uAC1C\uC870\uC2DD\uBCF4\uACE0\uC11C: "gaejosik",
|
|
21923
18898
|
press: "press",
|
|
21924
|
-
\uBCF4\uB3C4\uC790\uB8CC: "press"
|
|
18899
|
+
\uBCF4\uB3C4\uC790\uB8CC: "press",
|
|
18900
|
+
ministry: "ministry",
|
|
18901
|
+
\uC5C5\uBB34\uBCF4\uACE0: "ministry",
|
|
18902
|
+
\uBD80\uCC98\uC5C5\uBB34\uBCF4\uACE0: "ministry",
|
|
18903
|
+
\uC911\uC559\uBD80\uCC98\uBCF4\uACE0\uC11C: "ministry"
|
|
21925
18904
|
};
|
|
21926
18905
|
function normalizeGongmunPreset(preset) {
|
|
21927
18906
|
if (!preset) return "official";
|
|
@@ -22016,13 +18995,20 @@ function incompatibleGongmunWarnings(opts) {
|
|
|
22016
18995
|
}
|
|
22017
18996
|
return warns;
|
|
22018
18997
|
}
|
|
18998
|
+
function hexColorOption(name, value) {
|
|
18999
|
+
if (value === void 0) return void 0;
|
|
19000
|
+
const v = value.trim();
|
|
19001
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(v)) throw new KordocError(`${name} must be a #RRGGBB color (got "${value}")`);
|
|
19002
|
+
return v.toUpperCase();
|
|
19003
|
+
}
|
|
22019
19004
|
function resolveGongmun(opts) {
|
|
22020
19005
|
validateGongmunOptions(opts);
|
|
22021
19006
|
const preset = normalizeGongmunPreset(opts.preset);
|
|
22022
19007
|
const d = PRESET_DEFAULTS[preset];
|
|
22023
19008
|
const bodyPt = opts.bodyPt ?? d.bodyPt;
|
|
22024
19009
|
const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
|
|
22025
|
-
const
|
|
19010
|
+
const ministry = preset === "ministry";
|
|
19011
|
+
const coverOn = opts.cover !== void 0 ? opts.cover !== false : preset === "gaejosik" || ministry;
|
|
22026
19012
|
const coverOpts = typeof opts.cover === "object" ? opts.cover : {};
|
|
22027
19013
|
const gaejosik = preset === "gaejosik";
|
|
22028
19014
|
const reportFamily = gaejosik || preset === "report" || preset === "plan" || preset === "notice" || preset === "press";
|
|
@@ -22032,26 +19018,26 @@ function resolveGongmun(opts) {
|
|
|
22032
19018
|
bodyHeight: Math.round(bodyPt * 100),
|
|
22033
19019
|
lineSpacing: opts.lineSpacing ?? d.lineSpacing,
|
|
22034
19020
|
numbering: opts.numbering ?? d.numbering,
|
|
22035
|
-
margins: opts.margins ?? (preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
|
|
19021
|
+
margins: opts.margins ?? (ministry ? MINISTRY_MARGINS : preset === "report" || preset === "plan" ? SEOUL_REPORT_MARGINS : reportFamily ? GAEJOSIK_MARGINS : OFFICIAL_MARGINS),
|
|
22036
19022
|
centerTitle: opts.centerTitle ?? true,
|
|
22037
19023
|
autoFitMinRatio,
|
|
22038
19024
|
// 보도자료는 머리박스가 1페이지 최상단을 차지하는 서식이라 표지·목차와 양립 불가 —
|
|
22039
19025
|
// 켜면 머리박스가 표지에 얹히고 25pt 제목·부제가 유실된다 (docHead 프리셋 게이팅과 동일 관례)
|
|
22040
|
-
cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {} } : null,
|
|
19026
|
+
cover: coverOn && preset !== "press" ? { date: coverOpts.date ?? null, org: coverOpts.org ?? "", ...coverOpts.dept ? { dept: coverOpts.dept } : {}, ...coverOpts.label ? { label: coverOpts.label } : {} } : null,
|
|
22041
19027
|
bodyPtExplicit: opts.bodyPt !== void 0,
|
|
22042
19028
|
lineSpacingExplicit: opts.lineSpacing !== void 0,
|
|
22043
19029
|
bodyFontExplicit: opts.bodyFont !== void 0,
|
|
22044
19030
|
summary: opts.summary?.trim() || null,
|
|
22045
19031
|
docInfo: opts.docInfo ?? null,
|
|
22046
|
-
toc: preset !== "press" && (opts.toc ?? gaejosik),
|
|
19032
|
+
toc: preset !== "press" && (opts.toc ?? (gaejosik || ministry)),
|
|
22047
19033
|
fonts: opts.fonts ?? {},
|
|
22048
19034
|
sizes: opts.sizes ?? {},
|
|
22049
19035
|
levels: resolveLevels(opts.levels, Math.round(bodyPt * 100)),
|
|
22050
19036
|
// 쪽번호 — 보고서 계열 관행(실측: 2_보고서 양식·추진계획·공고문 전부 하단 중앙)
|
|
22051
|
-
pageNumbers: opts.pageNumbers ?? (gaejosik || preset === "report" || preset === "plan"),
|
|
19037
|
+
pageNumbers: opts.pageNumbers ?? (gaejosik || ministry || preset === "report" || preset === "plan"),
|
|
22052
19038
|
// 머리말·꼬리말 — 실측: 보고서 계열 15mm(GT3·t2·춘천·브라더), 공고·보도 10mm,
|
|
22053
19039
|
// 기안문 0(실결재 41/60건 h0/f0)
|
|
22054
|
-
headerFooter: preset === "report" || preset === "plan" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
|
|
19040
|
+
headerFooter: ministry ? MINISTRY_HEADER_FOOTER : preset === "report" || preset === "plan" ? SEOUL_REPORT_HEADER_FOOTER : usesReportFonts(preset) ? GAEJOSIK_HEADER_FOOTER : preset === "notice" || preset === "press" ? 2835 : 0,
|
|
22055
19041
|
// "끝." — 기안문 규정(본문 끝 2타+"끝."). 그 외는 opt-in
|
|
22056
19042
|
endMark: opts.endMark ?? preset === "official",
|
|
22057
19043
|
approval: opts.approval && opts.approval.length > 0 ? opts.approval : null,
|
|
@@ -22060,6 +19046,9 @@ function resolveGongmun(opts) {
|
|
|
22060
19046
|
// h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
|
|
22061
19047
|
// v5 라운드 3: 보고서·계획서 기본 band(띠 표) — 서울 plan 7/19·교육청 7/18 실측, 실무자 요청
|
|
22062
19048
|
h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "band" : preset === "notice" ? "number" : "none"),
|
|
19049
|
+
// 띠 제목 색 — 실측 최다 #003366/흰 글자(계획서 띠 표 14개). 교육청형 밝은 띠는 옵션으로
|
|
19050
|
+
bandColor: hexColorOption("bandColor", opts.bandColor) ?? "#003366",
|
|
19051
|
+
bandTextColor: hexColorOption("bandTextColor", opts.bandTextColor) ?? "#FFFFFF",
|
|
22063
19052
|
// 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
|
|
22064
19053
|
// v5: 서울 실결재 ㅇ(이응) 지배 — 보고서·계획서·통지·보도자료 ㅇ, 중앙부처 개조식 양식만 ○
|
|
22065
19054
|
bullet2: opts.bullet2 ?? (preset === "gaejosik" ? "\u25CB" : "\u3147"),
|
|
@@ -22131,6 +19120,9 @@ var GongmunNumberer = class {
|
|
|
22131
19120
|
this.bullet2 = bullet2;
|
|
22132
19121
|
this.asteriskThird = asteriskThird;
|
|
22133
19122
|
}
|
|
19123
|
+
numbering;
|
|
19124
|
+
bullet2;
|
|
19125
|
+
asteriskThird;
|
|
22134
19126
|
counts = [];
|
|
22135
19127
|
/** depth 항목 하나에 대한 마커. suppress=true면 빈 문자열(부호 없음) */
|
|
22136
19128
|
next(depth, suppress) {
|
|
@@ -22283,8 +19275,10 @@ function borderFillEntry(id, b, fill) {
|
|
|
22283
19275
|
brush = `
|
|
22284
19276
|
<hc:fillBrush><hc:winBrush faceColor="${fill}" hatchColor="#000000" alpha="0"/></hc:fillBrush>`;
|
|
22285
19277
|
} else if (fill) {
|
|
19278
|
+
const type = fill.type ?? "RADIAL";
|
|
19279
|
+
const colors = fill.gradient.slice(0, 2).map((c) => `<hc:color value="${c}"/>`).join("");
|
|
22286
19280
|
brush = `
|
|
22287
|
-
<hc:fillBrush><hc:gradation type="
|
|
19281
|
+
<hc:fillBrush><hc:gradation type="${type}" angle="${fill.angle ?? 0}" centerX="0" centerY="0" step="50" colorNum="2" stepCenter="50" alpha="0">${colors}</hc:gradation></hc:fillBrush>`;
|
|
22288
19282
|
}
|
|
22289
19283
|
return ` <hh:borderFill id="${id}" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
|
|
22290
19284
|
<hh:slash type="NONE" Crooked="0" isCounter="0"/>
|
|
@@ -24027,16 +21021,22 @@ var DOUBLE = ["0.5 mm", "#000000", "DOUBLE_SLIM"];
|
|
|
24027
21021
|
var MID = ["0.15 mm", "#000000"];
|
|
24028
21022
|
var EDGE = { thin: THIN, thick: THICK, double: DOUBLE, mid: MID };
|
|
24029
21023
|
function sideOf(kind) {
|
|
21024
|
+
if (Array.isArray(kind)) return kind;
|
|
24030
21025
|
return kind === "none" ? void 0 : EDGE[kind];
|
|
24031
21026
|
}
|
|
21027
|
+
function edgeKey(kind) {
|
|
21028
|
+
return Array.isArray(kind) ? kind.join("/") : kind;
|
|
21029
|
+
}
|
|
24032
21030
|
var TableBfRegistry = class {
|
|
24033
21031
|
constructor(nextId) {
|
|
24034
21032
|
this.nextId = nextId;
|
|
24035
21033
|
}
|
|
21034
|
+
nextId;
|
|
24036
21035
|
map = /* @__PURE__ */ new Map();
|
|
24037
21036
|
xmls = [];
|
|
24038
21037
|
get(spec) {
|
|
24039
|
-
const
|
|
21038
|
+
const fillKey = typeof spec.fill === "string" ? spec.fill : spec.fill ? `${spec.fill.type ?? "RADIAL"}@${spec.fill.angle ?? 0}:${spec.fill.gradient.join("-")}` : "";
|
|
21039
|
+
const key = `${edgeKey(spec.t)}|${edgeKey(spec.b)}|${edgeKey(spec.l)}|${edgeKey(spec.r)}|${fillKey}`;
|
|
24040
21040
|
const hit = this.map.get(key);
|
|
24041
21041
|
if (hit !== void 0) return hit;
|
|
24042
21042
|
const id = this.nextId++;
|
|
@@ -24385,6 +21385,7 @@ var ImageRegistry = class {
|
|
|
24385
21385
|
constructor(supplied) {
|
|
24386
21386
|
this.supplied = supplied;
|
|
24387
21387
|
}
|
|
21388
|
+
supplied;
|
|
24388
21389
|
byUrl = /* @__PURE__ */ new Map();
|
|
24389
21390
|
ids = /* @__PURE__ */ new Set();
|
|
24390
21391
|
parts = [];
|
|
@@ -24486,6 +21487,14 @@ function resetTableIds() {
|
|
|
24486
21487
|
}
|
|
24487
21488
|
var curFace = "hcr";
|
|
24488
21489
|
var DATA_TABLE_INSET = 1800;
|
|
21490
|
+
function tablePosXml(float, right) {
|
|
21491
|
+
return float ? `<hp:pos treatAsChar="0" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="COLUMN" vertAlign="TOP" horzAlign="${right ? "RIGHT" : "LEFT"}" vertOffset="0" horzOffset="0"/>` : `<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>`;
|
|
21492
|
+
}
|
|
21493
|
+
function tableOutMarginXml(float, charHeight) {
|
|
21494
|
+
const bottom = float ? Math.round(charHeight * 1.6) : 0;
|
|
21495
|
+
return `<hp:outMargin left="0" right="0" top="0" bottom="${bottom}"/>`;
|
|
21496
|
+
}
|
|
21497
|
+
var FLOAT_TBL_ATTRS = ` textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None"`;
|
|
24489
21498
|
function anchorOfMarkdownCell(cell2) {
|
|
24490
21499
|
return normalizeAnchor(cell2.replace(/!\[[^\]]*\]\([^)]*\)/g, ""));
|
|
24491
21500
|
}
|
|
@@ -24695,9 +21704,10 @@ function generateTable(rows, theme, style = null, remap = null, seq = 0, images
|
|
|
24695
21704
|
}).join("");
|
|
24696
21705
|
const tblW = colWidths.reduce((a, b) => a + b, 0);
|
|
24697
21706
|
const tblH = rowHeights.reduce((a, b) => a + b, 0);
|
|
24698
|
-
const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
|
|
24699
|
-
const tbl2 = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="${style ? 1 : 0}" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0">${tblInner}</hp:tbl>`;
|
|
24700
21707
|
const hostPr = reg && style?.rightParaPr !== void 0 ? style.rightParaPr : 0;
|
|
21708
|
+
const float = rowCnt >= 2;
|
|
21709
|
+
const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/>` + tablePosXml(float, hostPr !== 0) + tableOutMarginXml(float, measureH) + `<hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
|
|
21710
|
+
const tbl2 = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE"${float ? FLOAT_TBL_ATTRS : ""} pageBreak="CELL" repeatHeader="${style ? 1 : 0}" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0">${tblInner}</hp:tbl>`;
|
|
24701
21711
|
return `<hp:p paraPrIDRef="${hostPr}" styleIDRef="0"><hp:run charPrIDRef="0">${tbl2}</hp:run></hp:p>`;
|
|
24702
21712
|
}
|
|
24703
21713
|
function layoutHtmlRows(rows) {
|
|
@@ -24732,7 +21742,7 @@ function layoutHtmlRows(rows) {
|
|
|
24732
21742
|
function unescapeHtml(s) {
|
|
24733
21743
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
24734
21744
|
}
|
|
24735
|
-
function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, remap = null, seq = 0, images = null) {
|
|
21745
|
+
function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, remap = null, seq = 0, images = null, nested = false) {
|
|
24736
21746
|
const rows = parseHtmlTable(rawHtml);
|
|
24737
21747
|
if (!rows || rows.length === 0) return null;
|
|
24738
21748
|
const { placed, rowCnt, colCnt } = layoutHtmlRows(rows);
|
|
@@ -24778,13 +21788,13 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
|
|
|
24778
21788
|
const lines = cellLines[i];
|
|
24779
21789
|
let nestedH = 0;
|
|
24780
21790
|
const { texts: segTexts, tables: nestedTables } = splitCellByTopLevelTables(cell2.inner);
|
|
24781
|
-
const nestedXmls = nestedTables.map((
|
|
21791
|
+
const nestedXmls = nestedTables.map((nested2) => {
|
|
24782
21792
|
const sw = spanW(cell2);
|
|
24783
21793
|
const nestedW = Math.max(Math.min(Math.max(sw - 1020, 4e3), sw - 282), 500);
|
|
24784
|
-
const nestedXml = generateHtmlTableXml(
|
|
21794
|
+
const nestedXml = generateHtmlTableXml(nested2, theme, nestedW, style ? { ...style, totalWidth: nestedW } : null, null, 0, null, true);
|
|
24785
21795
|
if (nestedXml) {
|
|
24786
21796
|
const szH = nestedXml.match(/<hp:sz [^>]*height="(\d+)"/)?.[1];
|
|
24787
|
-
nestedH += (szH ? Number(szH) : (
|
|
21797
|
+
nestedH += (szH ? Number(szH) : (nested2.match(/<tr[\s>]/gi) ?? []).length * cellH) + 300;
|
|
24788
21798
|
}
|
|
24789
21799
|
return nestedXml;
|
|
24790
21800
|
});
|
|
@@ -24863,7 +21873,8 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
|
|
|
24863
21873
|
trXmls.push(`<hp:tr>${rowTcs.join("")}</hp:tr>`);
|
|
24864
21874
|
}
|
|
24865
21875
|
const tableH = tableRowHeights.reduce((sum, height) => sum + height, 0);
|
|
24866
|
-
|
|
21876
|
+
const float = !nested && rowCnt >= 2;
|
|
21877
|
+
return `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE"${float ? FLOAT_TBL_ATTRS : ""} pageBreak="CELL" repeatHeader="${style ? 1 : 0}" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0"><hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tableH}" heightRelTo="ABSOLUTE" protect="0"/>` + tablePosXml(float, !!reg && style?.rightParaPr !== void 0) + tableOutMarginXml(float, measureH) + `<hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trXmls.join("") + `</hp:tbl>`;
|
|
24867
21878
|
}
|
|
24868
21879
|
|
|
24869
21880
|
// src/hwpx/gen-gongmun-extra.ts
|
|
@@ -25371,6 +22382,8 @@ var SectionOpener = class {
|
|
|
25371
22382
|
this.gongmun = gongmun;
|
|
25372
22383
|
this.page = page;
|
|
25373
22384
|
}
|
|
22385
|
+
gongmun;
|
|
22386
|
+
page;
|
|
25374
22387
|
pending = true;
|
|
25375
22388
|
/** 아직 secPr를 나를 첫 문단이 안 나왔는가 */
|
|
25376
22389
|
get isFirst() {
|
|
@@ -25630,8 +22643,8 @@ function renderHtmlTable(block, ctx) {
|
|
|
25630
22643
|
ctx.opener.emitCarrier(ctx.paraXmls);
|
|
25631
22644
|
return `<hp:p paraPrIDRef="${tableStyle && bfReg ? GONGMUN_RIGHT : 0}" styleIDRef="0"><hp:run charPrIDRef="0">${tbl2}</hp:run></hp:p>`;
|
|
25632
22645
|
}
|
|
25633
|
-
const
|
|
25634
|
-
return
|
|
22646
|
+
const plain4 = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
22647
|
+
return plain4 ? generateParagraph(plain4) : "";
|
|
25635
22648
|
}
|
|
25636
22649
|
function appendPostamble(blocks, ctx) {
|
|
25637
22650
|
const { gongmun, dfIds, bfReg, tableStyle, paraXmls } = ctx;
|
|
@@ -25819,6 +22832,9 @@ var StyleRegistry = class {
|
|
|
25819
22832
|
if (!this.fonts.has(f)) this.fonts.set(f, i);
|
|
25820
22833
|
});
|
|
25821
22834
|
}
|
|
22835
|
+
charBase;
|
|
22836
|
+
paraBase;
|
|
22837
|
+
fontBase;
|
|
25822
22838
|
chars = /* @__PURE__ */ new Map();
|
|
25823
22839
|
charXmls = [];
|
|
25824
22840
|
paras = /* @__PURE__ */ new Map();
|
|
@@ -25873,6 +22889,17 @@ var StyleRegistry = class {
|
|
|
25873
22889
|
return this.fontList;
|
|
25874
22890
|
}
|
|
25875
22891
|
};
|
|
22892
|
+
function inlineMapper(reg, base, emphasisColor) {
|
|
22893
|
+
const norm2 = reg.char({ font: base.font, pt: base.pt, bold: base.bold, ratio: base.ratio, spacing: base.spacing, color: base.color });
|
|
22894
|
+
const emColor = emphasisColor ?? base.color;
|
|
22895
|
+
return (id) => {
|
|
22896
|
+
if (id === CHAR_BOLD) return reg.char({ font: base.font, pt: base.pt, bold: true, ratio: base.ratio, spacing: base.spacing, color: emColor });
|
|
22897
|
+
if (id === CHAR_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: base.bold, italic: true, ratio: base.ratio, spacing: base.spacing, color: base.color });
|
|
22898
|
+
if (id === CHAR_BOLD_ITALIC) return reg.char({ font: base.font, pt: base.pt, bold: true, italic: true, ratio: base.ratio, spacing: base.spacing, color: emColor });
|
|
22899
|
+
if (id === CHAR_NORMAL) return norm2;
|
|
22900
|
+
return id;
|
|
22901
|
+
};
|
|
22902
|
+
}
|
|
25876
22903
|
|
|
25877
22904
|
// src/hwpx/gongmun-scheme.ts
|
|
25878
22905
|
var SEOUL_FRAME = {
|
|
@@ -25917,6 +22944,45 @@ function seoulGaejosikScheme(bodyPt = 15, lineSp = 180, bullet2 = "\u3147") {
|
|
|
25917
22944
|
blankBetweenTop: false
|
|
25918
22945
|
};
|
|
25919
22946
|
}
|
|
22947
|
+
var MINISTRY_FRAME = {
|
|
22948
|
+
font: "\uD568\uCD08\uB86C\uBC14\uD0D5",
|
|
22949
|
+
titleFont: "HY\uD5E4\uB4DC\uB77C\uC778M",
|
|
22950
|
+
titlePt: 32,
|
|
22951
|
+
contactFont: "\uD568\uCD08\uB86C\uBC14\uD0D5",
|
|
22952
|
+
contactPt: 12,
|
|
22953
|
+
summaryFont: "\uD568\uCD08\uB86C\uBC14\uD0D5",
|
|
22954
|
+
summaryPt: 13,
|
|
22955
|
+
summaryFill: "#FFF7CC"
|
|
22956
|
+
};
|
|
22957
|
+
function ministryScheme(bodyPt = 15, lineSp = 145) {
|
|
22958
|
+
const d = bodyPt - 15;
|
|
22959
|
+
const lv = (font, pt, bold, leadTa, extra = {}) => ({ font, pt: pt + d, bold, leadTa, ...extra });
|
|
22960
|
+
return {
|
|
22961
|
+
kind: "gaejosik",
|
|
22962
|
+
lineSp,
|
|
22963
|
+
body: lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 0),
|
|
22964
|
+
levels: [
|
|
22965
|
+
// □ 는 한 줄 강제 안 함 — 실측 □ 문장의 절반이 두 줄(pg3·pg6). 앞 빈 줄은 스킴 공통 로직(BOX_BLANK_HU)
|
|
22966
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 0, { keepWithNext: true, blankBefore: true }),
|
|
22967
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 1),
|
|
22968
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 3),
|
|
22969
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 4),
|
|
22970
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 5),
|
|
22971
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 6),
|
|
22972
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 7),
|
|
22973
|
+
lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 8)
|
|
22974
|
+
],
|
|
22975
|
+
// * 각주 — 맑은 고딕 12(n=124), 선두 4칸 공백 ≈ ㅇ 본문 시작(x80)에 맞춘다
|
|
22976
|
+
ref: lv("\uB9D1\uC740 \uACE0\uB515", 12, false, 3),
|
|
22977
|
+
sub: lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 2),
|
|
22978
|
+
attach: lv("\uD568\uCD08\uB86C\uBC14\uD0D5", 15, false, 0),
|
|
22979
|
+
chapter: lv("HY\uD5E4\uB4DC\uB77C\uC778M", 16, false, 0, { oneLine: true, keepWithNext: true }),
|
|
22980
|
+
marker: (depth) => depth === 0 ? "\u25A1" : depth === 1 ? "\u3147" : depth === 2 ? "-" : "\u318D",
|
|
22981
|
+
table: { font: "\uB9D1\uC740 \uACE0\uB515", pt: 12 + d, headerFill: "#DFE6F7", labelFill: "#F2F2F2" },
|
|
22982
|
+
frame: MINISTRY_FRAME,
|
|
22983
|
+
blankBetweenTop: false
|
|
22984
|
+
};
|
|
22985
|
+
}
|
|
25920
22986
|
function seoulLegalScheme(bodyFont = "\uAD74\uB9BC\uCCB4", bodyPt = 12, lineSp = 160) {
|
|
25921
22987
|
const lv = (leadTa, extra = {}) => ({ font: bodyFont, pt: bodyPt, bold: false, leadTa, ...extra });
|
|
25922
22988
|
return {
|
|
@@ -25967,6 +23033,9 @@ function levelGeometry(style, marker) {
|
|
|
25967
23033
|
return { left, indent };
|
|
25968
23034
|
}
|
|
25969
23035
|
function pickScheme(g, bodyHasBoxMarkers) {
|
|
23036
|
+
if (g.preset === "ministry") {
|
|
23037
|
+
return applySchemeOverrides(ministryScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 145), g);
|
|
23038
|
+
}
|
|
25970
23039
|
const gaejosik = g.numbering !== "standard" || bodyHasBoxMarkers;
|
|
25971
23040
|
const base = gaejosik ? seoulGaejosikScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 180, g.bullet2) : seoulLegalScheme(g.fonts.body ?? (g.bodyFontExplicit ? g.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5" : "\uAD74\uB9BC\uCCB4"), g.bodyPtExplicit ? g.bodyHeight / 100 : 12, g.lineSpacingExplicit ? g.lineSpacing : 160);
|
|
25972
23041
|
return applySchemeOverrides(base, g);
|
|
@@ -25999,6 +23068,8 @@ var BOX_MARKERS = {
|
|
|
25999
23068
|
"\u2219": 3
|
|
26000
23069
|
};
|
|
26001
23070
|
var LEGAL_RE = /^(\d{1,2}\.|[가-힣]\.|\d{1,2}\)|[가-힣]\)|\(\d{1,2}\)|\([가-힣]\)|[①-⑳]|[㉮-㉻])\s+/u;
|
|
23071
|
+
var KEEP_MARKERS = { "\u21D2": 0, "\u21B3": 1, "\u261E": 0 };
|
|
23072
|
+
var KEEP_RE = /^([❶-❿➊-➓⇒↳☞])\s*/u;
|
|
26002
23073
|
var BOX_RE = /^([□■❑❏ㅁ○ㅇ◦●❍◎\-–―—ㅡ‣▪▫ㆍ·•∙])\s+/u;
|
|
26003
23074
|
var REF_RE = /^(※|*|\*(?=\s))\s*/u;
|
|
26004
23075
|
var BUNIM_RE = /^붙\s*임(?:\s|:|$)/;
|
|
@@ -26017,10 +23088,14 @@ function legalMarkerDepth(marker) {
|
|
|
26017
23088
|
if (/^[①-⑳]$/u.test(marker)) return 6;
|
|
26018
23089
|
return 7;
|
|
26019
23090
|
}
|
|
26020
|
-
function parseLeadingMarker(text) {
|
|
23091
|
+
function parseLeadingMarker(text, keepMarkers = false) {
|
|
26021
23092
|
const t = text.replace(/^[\s ]+/, "");
|
|
26022
23093
|
const ref = REF_RE.exec(t);
|
|
26023
23094
|
if (ref) return { kind: "ref", depth: 0, marker: ref[1], rest: t.slice(ref[0].length).trim() };
|
|
23095
|
+
if (keepMarkers) {
|
|
23096
|
+
const keep = KEEP_RE.exec(t);
|
|
23097
|
+
if (keep) return { kind: "keep", depth: KEEP_MARKERS[keep[1]] ?? 0, marker: keep[1], rest: t.slice(keep[0].length).trim() };
|
|
23098
|
+
}
|
|
26024
23099
|
const box = BOX_RE.exec(t);
|
|
26025
23100
|
if (box) return { kind: "box", depth: BOX_MARKERS[box[1]] ?? 3, marker: box[1], rest: t.slice(box[0].length).trim() };
|
|
26026
23101
|
const legal = LEGAL_RE.exec(t);
|
|
@@ -26035,11 +23110,18 @@ function buildOutline(blocks, opts) {
|
|
|
26035
23110
|
let lastItemDepth = -1;
|
|
26036
23111
|
let seenBody = false;
|
|
26037
23112
|
let hasBoxMarkers = false;
|
|
26038
|
-
|
|
26039
|
-
|
|
23113
|
+
let listBase = -1;
|
|
23114
|
+
let lastExplicitDepth = -1;
|
|
23115
|
+
const pushItem = (depth, text, legalMarker, fromHeading = false, marker, explicit = true) => {
|
|
23116
|
+
nodes.push({ kind: "item", depth, text, ...legalMarker ? { legalMarker } : {}, ...fromHeading ? { fromHeading: true } : {}, ...marker ? { marker } : {} });
|
|
26040
23117
|
lastItemDepth = depth;
|
|
26041
23118
|
seenBody = true;
|
|
23119
|
+
if (explicit) {
|
|
23120
|
+
lastExplicitDepth = depth;
|
|
23121
|
+
listBase = -1;
|
|
23122
|
+
}
|
|
26042
23123
|
};
|
|
23124
|
+
const keep = !!opts.keepMarkers;
|
|
26043
23125
|
for (let i = 0; i < blocks.length; i++) {
|
|
26044
23126
|
const b = blocks[i];
|
|
26045
23127
|
switch (b.type) {
|
|
@@ -26052,11 +23134,22 @@ function buildOutline(blocks, opts) {
|
|
|
26052
23134
|
}
|
|
26053
23135
|
if (lvl <= 2) {
|
|
26054
23136
|
nodes.push({ kind: "chapter", text: stripChapterNumber(raw) || raw, index: ++chapters });
|
|
23137
|
+
lastExplicitDepth = -1;
|
|
23138
|
+
listBase = -1;
|
|
26055
23139
|
headingDepth = opts.gaejosik ? -1 : 0;
|
|
26056
23140
|
lastItemDepth = opts.gaejosik ? -1 : 0;
|
|
26057
23141
|
seenBody = true;
|
|
26058
23142
|
break;
|
|
26059
23143
|
}
|
|
23144
|
+
if (opts.headingFrames) {
|
|
23145
|
+
const lm2 = parseLeadingMarker(raw, keep);
|
|
23146
|
+
nodes.push({ kind: "heading", level: lvl, text: lm2.kind === "legal" || lm2.kind === "keep" ? lm2.rest : raw });
|
|
23147
|
+
lastItemDepth = -1;
|
|
23148
|
+
lastExplicitDepth = -1;
|
|
23149
|
+
listBase = -1;
|
|
23150
|
+
seenBody = true;
|
|
23151
|
+
break;
|
|
23152
|
+
}
|
|
26060
23153
|
const depth = Math.min(lvl - 3, 7);
|
|
26061
23154
|
const lm = parseLeadingMarker(raw);
|
|
26062
23155
|
pushItem(depth, lm.kind ? lm.rest : raw, void 0, true);
|
|
@@ -26065,7 +23158,7 @@ function buildOutline(blocks, opts) {
|
|
|
26065
23158
|
}
|
|
26066
23159
|
case "list_item": {
|
|
26067
23160
|
const text = stripLawCodes((b.text ?? "").trim());
|
|
26068
|
-
const lm = parseLeadingMarker(text);
|
|
23161
|
+
const lm = parseLeadingMarker(text, keep);
|
|
26069
23162
|
if (SOURCE_RE.test(lm.kind === "box" || lm.kind === "legal" ? lm.rest : text)) {
|
|
26070
23163
|
nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.kind ? lm.rest : text });
|
|
26071
23164
|
break;
|
|
@@ -26083,12 +23176,20 @@ function buildOutline(blocks, opts) {
|
|
|
26083
23176
|
pushItem(lm.depth, lm.rest);
|
|
26084
23177
|
break;
|
|
26085
23178
|
}
|
|
23179
|
+
if (lm.kind === "keep") {
|
|
23180
|
+
pushItem(lm.depth, lm.rest, void 0, false, lm.marker);
|
|
23181
|
+
break;
|
|
23182
|
+
}
|
|
26086
23183
|
if (lm.kind === "legal" && opts.gaejosik) {
|
|
26087
23184
|
pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
|
|
26088
23185
|
break;
|
|
26089
23186
|
}
|
|
26090
|
-
|
|
26091
|
-
|
|
23187
|
+
let base = headingDepth >= 0 ? headingDepth + 1 : 0;
|
|
23188
|
+
if (opts.headingFrames) {
|
|
23189
|
+
if (listBase < 0) listBase = lastExplicitDepth + 1;
|
|
23190
|
+
base = listBase;
|
|
23191
|
+
}
|
|
23192
|
+
pushItem(Math.min(base + (b.indent ?? 0), 7), lm.kind === "legal" ? lm.rest : text, void 0, false, void 0, false);
|
|
26092
23193
|
break;
|
|
26093
23194
|
}
|
|
26094
23195
|
case "paragraph": {
|
|
@@ -26108,7 +23209,7 @@ function buildOutline(blocks, opts) {
|
|
|
26108
23209
|
nodes.push({ kind: "para", depth: 0, text: rgt[1].trim(), align: "RIGHT" });
|
|
26109
23210
|
break;
|
|
26110
23211
|
}
|
|
26111
|
-
const lm = parseLeadingMarker(text);
|
|
23212
|
+
const lm = parseLeadingMarker(text, keep);
|
|
26112
23213
|
if (lm.kind === "ref") {
|
|
26113
23214
|
nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lm.rest });
|
|
26114
23215
|
break;
|
|
@@ -26122,6 +23223,10 @@ function buildOutline(blocks, opts) {
|
|
|
26122
23223
|
pushItem(lm.depth, lm.rest);
|
|
26123
23224
|
break;
|
|
26124
23225
|
}
|
|
23226
|
+
if (lm.kind === "keep") {
|
|
23227
|
+
pushItem(lm.depth, lm.rest, void 0, false, lm.marker);
|
|
23228
|
+
break;
|
|
23229
|
+
}
|
|
26125
23230
|
if (lm.kind === "legal") {
|
|
26126
23231
|
if (opts.gaejosik) pushItem(Math.max(lastItemDepth, 0), lm.rest, lm.marker);
|
|
26127
23232
|
else pushItem(lm.depth, lm.rest);
|
|
@@ -26129,12 +23234,14 @@ function buildOutline(blocks, opts) {
|
|
|
26129
23234
|
}
|
|
26130
23235
|
nodes.push({ kind: "para", depth: 0, text });
|
|
26131
23236
|
seenBody = true;
|
|
23237
|
+
lastExplicitDepth = -1;
|
|
23238
|
+
listBase = -1;
|
|
26132
23239
|
break;
|
|
26133
23240
|
}
|
|
26134
23241
|
case "blockquote": {
|
|
26135
23242
|
const lines = stripLawCodes(b.text ?? "").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
26136
23243
|
if (!lines.length) break;
|
|
26137
|
-
if (opts.summaryFromQuote && !seenBody) nodes.push({ kind: "summary", text: lines.join("\n") });
|
|
23244
|
+
if (opts.quoteBox || opts.summaryFromQuote && !seenBody) nodes.push({ kind: "summary", text: lines.join("\n") });
|
|
26138
23245
|
else nodes.push({ kind: "ref", depth: lastItemDepth + 1, text: lines.join(" ").replace(/^※\s*/, "") });
|
|
26139
23246
|
break;
|
|
26140
23247
|
}
|
|
@@ -26475,8 +23582,225 @@ function buildReportCover(inp, ctx) {
|
|
|
26475
23582
|
return out;
|
|
26476
23583
|
}
|
|
26477
23584
|
|
|
23585
|
+
// src/hwpx/gen-frame-ministry.ts
|
|
23586
|
+
var MINISTRY = {
|
|
23587
|
+
blue: "#0000FF",
|
|
23588
|
+
headFont: "HY\uD5E4\uB4DC\uB77C\uC778M",
|
|
23589
|
+
bandFont: "HY\uC911\uACE0\uB515",
|
|
23590
|
+
bodyFont: "\uD568\uCD08\uB86C\uBC14\uD0D5",
|
|
23591
|
+
refFont: "\uB9D1\uC740 \uACE0\uB515",
|
|
23592
|
+
cover: { barDark: "#0C3DCA", barLight: "#0A33A9", barH: 590, titleH: 9500, titlePt: 32, datePt: 24, labelW: 7470, labelH: 2240, labelPt: 14 },
|
|
23593
|
+
toc: { labelPt: 24, labelColor: "#000080", chapterPt: 17, subPt: 15, border: ["0.7 mm", "#999999"] },
|
|
23594
|
+
// 원본은 위·아래 #D6EAFE·가운데 흰 3색 그라데이션 — 한글 gradation 은 2색만 안전(gen-ids)해서 위→아래 2색으로
|
|
23595
|
+
chapter: { h: 2650, pt: 16, fill: ["#D6EAFE", "#FFFFFF"], line: ["0.5 mm", "#0000FF", "DOUBLE_SLIM"] },
|
|
23596
|
+
section: { numW: 2970, h: 2340, pt: 15, fill: "#3057B9", border: ["0.4 mm", "#000000"], underline: ["0.5 mm", "#000000"] },
|
|
23597
|
+
subhead: { h: 2410, pt: 15, fill: "#203A7B" },
|
|
23598
|
+
item: { h: 2350, pt: 15, fill: "#E8F7FC", line: ["0.4 mm", "#00ACFF"] },
|
|
23599
|
+
summary: { fill: "#FFF7CC", pt: 13, refPt: 11 },
|
|
23600
|
+
attach: { labelW: 6130, gap: 590, h: 2630, pt: 16, fill: "#0066FF", thick: ["0.4 mm", "#000000"] }
|
|
23601
|
+
};
|
|
23602
|
+
var NONE = "none";
|
|
23603
|
+
var KEYWORD_RE = /^(\([^()\n]{1,24}\)|\[[^[\]\n]{1,24}\])\s*/u;
|
|
23604
|
+
function ministryRuns(marker, text, ctx, base, keywordColor, emphasisColor) {
|
|
23605
|
+
const { reg } = ctx;
|
|
23606
|
+
const baseId = reg.char(base);
|
|
23607
|
+
const mapper = inlineMapper(reg, base, emphasisColor);
|
|
23608
|
+
let out = marker ? `<hp:run charPrIDRef="${baseId}"><hp:t>${escapeXml(marker + " ")}</hp:t></hp:run>` : "";
|
|
23609
|
+
const m = KEYWORD_RE.exec(text);
|
|
23610
|
+
if (m) {
|
|
23611
|
+
const kw = reg.char({ ...base, bold: true, color: keywordColor ?? base.color });
|
|
23612
|
+
out += `<hp:run charPrIDRef="${kw}"><hp:t>${escapeXml(m[1] + " ")}</hp:t></hp:run>`;
|
|
23613
|
+
text = text.slice(m[0].length);
|
|
23614
|
+
}
|
|
23615
|
+
return out + generateRuns(text, baseId, mapper);
|
|
23616
|
+
}
|
|
23617
|
+
function plain(text) {
|
|
23618
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1");
|
|
23619
|
+
}
|
|
23620
|
+
function textW(text, font, pt) {
|
|
23621
|
+
return measureTextWidth(text, pt * 100, 100, { faceClass: faceClassForGen(font) });
|
|
23622
|
+
}
|
|
23623
|
+
function buildMinistryCover(inp, ctx) {
|
|
23624
|
+
const { reg, bf } = ctx;
|
|
23625
|
+
const c = MINISTRY.cover;
|
|
23626
|
+
const out = [];
|
|
23627
|
+
const blank2 = (pt = 15) => para("", reg.para({ align: "LEFT", lineSp: 145 }), reg.char({ font: MINISTRY.bodyFont, pt }));
|
|
23628
|
+
const center = reg.para({ align: "CENTER", lineSp: 130 });
|
|
23629
|
+
if (inp.label) {
|
|
23630
|
+
const red = ["0.12 mm", "#FF0000"];
|
|
23631
|
+
const box = bf.get({ t: red, b: red, l: red, r: red });
|
|
23632
|
+
const cL = reg.char({ font: MINISTRY.bodyFont, pt: c.labelPt, color: "#FF0000" });
|
|
23633
|
+
const cell2 = tc({ bf: box, row: 0, col: 0, w: c.labelW, h: c.labelH, paras: para(inp.label, center, cL) });
|
|
23634
|
+
out.push(host(ftbl([cell2], c.labelW, c.labelH, 1), reg.para({ align: "RIGHT", lineSp: 100 }), cL));
|
|
23635
|
+
} else {
|
|
23636
|
+
out.push(blank2());
|
|
23637
|
+
}
|
|
23638
|
+
for (let i = 0; i < 6; i++) out.push(blank2());
|
|
23639
|
+
const w = ctx.W - 566;
|
|
23640
|
+
const barTop = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE, fill: c.barDark });
|
|
23641
|
+
const barBot = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE, fill: c.barLight });
|
|
23642
|
+
const fit = fitOneLine(inp.title, MINISTRY.headFont, c.titlePt, w - 800, 24);
|
|
23643
|
+
const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
|
|
23644
|
+
const c1 = reg.char({ font: MINISTRY.bodyFont, pt: 1 });
|
|
23645
|
+
const barP = reg.para({ align: "CENTER", lineSp: 70 });
|
|
23646
|
+
const rows = [
|
|
23647
|
+
tc({ bf: barTop, row: 0, col: 0, w, h: c.barH, paras: para("", barP, c1) }),
|
|
23648
|
+
tc({ bf: 1, row: 1, col: 0, w, h: c.titleH, paras: para(inp.title, center, cT), name: "__kordoc_h1" }),
|
|
23649
|
+
tc({ bf: barBot, row: 2, col: 0, w, h: c.barH, paras: para("", barP, c1) })
|
|
23650
|
+
];
|
|
23651
|
+
out.push(host(ftbl(rows, w, c.barH * 2 + c.titleH, 1), reg.para({ align: "CENTER", lineSp: 100 }), cT));
|
|
23652
|
+
for (let i = 0; i < 13; i++) out.push(blank2());
|
|
23653
|
+
out.push(para(inp.date, center, reg.char({ font: MINISTRY.bodyFont, pt: c.datePt })));
|
|
23654
|
+
if (inp.org) {
|
|
23655
|
+
for (let i = 0; i < 3; i++) out.push(blank2());
|
|
23656
|
+
out.push(para(inp.org, center, reg.char({ font: MINISTRY.headFont, pt: c.datePt })));
|
|
23657
|
+
}
|
|
23658
|
+
return out;
|
|
23659
|
+
}
|
|
23660
|
+
function buildMinistryToc(chapters, ctx) {
|
|
23661
|
+
const { reg, bf } = ctx;
|
|
23662
|
+
const t = MINISTRY.toc;
|
|
23663
|
+
const w = ctx.W - 1200;
|
|
23664
|
+
const pad = 2e3;
|
|
23665
|
+
const cLabel = reg.char({ font: MINISTRY.headFont, pt: t.labelPt, color: t.labelColor });
|
|
23666
|
+
const cCh = reg.char({ font: MINISTRY.headFont, pt: t.chapterPt });
|
|
23667
|
+
const cSub = reg.char({ font: MINISTRY.headFont, pt: t.subPt });
|
|
23668
|
+
const pLabel = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 1200, after: 2400 });
|
|
23669
|
+
const pCh = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 2e3, keepWord: false });
|
|
23670
|
+
const pSub = reg.para({ align: "LEFT", lineSp: 130, left: pad + 1400, before: 1200, keepWord: false });
|
|
23671
|
+
const paras = [para("\uBAA9 \uCC28", pLabel, cLabel)];
|
|
23672
|
+
let h = 1200 + Math.round(t.labelPt * 130) + 2400;
|
|
23673
|
+
let roman = 0;
|
|
23674
|
+
for (const ch of chapters) {
|
|
23675
|
+
if (ch.attach) {
|
|
23676
|
+
paras.push(para(`[\uBCC4\uCCA8] ${stripAttachPrefix(ch.title)}`, pCh, cCh));
|
|
23677
|
+
} else {
|
|
23678
|
+
roman++;
|
|
23679
|
+
paras.push(para(`${romanOf(roman)}. ${ch.title}`, pCh, cCh));
|
|
23680
|
+
}
|
|
23681
|
+
h += 2e3 + Math.round(t.chapterPt * 130);
|
|
23682
|
+
for (let i = 0; i < ch.subs.length; i++) {
|
|
23683
|
+
paras.push(para(`${i + 1}. ${ch.subs[i]}`, pSub, cSub));
|
|
23684
|
+
h += 1200 + Math.round(t.subPt * 130);
|
|
23685
|
+
}
|
|
23686
|
+
}
|
|
23687
|
+
h = Math.max(h + 3e3, TOC_MIN_H);
|
|
23688
|
+
const box = bf.get({ t: t.border, b: t.border, l: t.border, r: t.border });
|
|
23689
|
+
const cell2 = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), vAlign: "TOP", name: "__kordoc_toc" });
|
|
23690
|
+
const hostP = reg.para({ align: "CENTER", lineSp: 100 });
|
|
23691
|
+
return [host(ftbl([cell2], w, h, 1), hostP, cCh, ` pageBreak="1"`)];
|
|
23692
|
+
}
|
|
23693
|
+
var TOC_MIN_H = 62e3;
|
|
23694
|
+
function stripAttachPrefix(title) {
|
|
23695
|
+
return title.replace(/^\[?\s*별\s*첨\s*\]?\s*[::]?\s*/u, "");
|
|
23696
|
+
}
|
|
23697
|
+
function romanOf(n) {
|
|
23698
|
+
if (n >= 1 && n <= 12) return String.fromCodePoint(8543 + n);
|
|
23699
|
+
const t = [[10, "\u2169"], [9, "\u2168"], [5, "\u2164"], [4, "\u2163"], [1, "\u2160"]];
|
|
23700
|
+
let s = "";
|
|
23701
|
+
for (const [v, r] of t) while (n >= v) {
|
|
23702
|
+
s += r;
|
|
23703
|
+
n -= v;
|
|
23704
|
+
}
|
|
23705
|
+
return s;
|
|
23706
|
+
}
|
|
23707
|
+
function buildMinistryChapterBand(roman, title, ctx, before) {
|
|
23708
|
+
const { reg, bf } = ctx;
|
|
23709
|
+
const c = MINISTRY.chapter;
|
|
23710
|
+
const w = ctx.W;
|
|
23711
|
+
const text = `${roman}. ${plain(title)}`;
|
|
23712
|
+
const fit = fitOneLine(text, MINISTRY.headFont, c.pt, w - 1400, c.pt - 2);
|
|
23713
|
+
const band = bf.get({ t: c.line, b: c.line, l: NONE, r: NONE, fill: { gradient: [...c.fill], type: "LINEAR", angle: 90 } });
|
|
23714
|
+
const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
|
|
23715
|
+
const p = reg.para({ align: "LEFT", lineSp: 130, left: 500 });
|
|
23716
|
+
const cell2 = tc({ bf: band, row: 0, col: 0, w, h: c.h, paras: para(text, p, cT), name: "__kordoc_h2" });
|
|
23717
|
+
return { xml: host(ftbl([cell2], w, c.h, 1, { bottomGap: 800 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT), overflow: fit.overflow };
|
|
23718
|
+
}
|
|
23719
|
+
function buildMinistrySectionBand(n, title, ctx, before) {
|
|
23720
|
+
const { reg, bf } = ctx;
|
|
23721
|
+
const s = MINISTRY.section;
|
|
23722
|
+
const t = plain(title);
|
|
23723
|
+
const titleW = Math.min(ctx.W - s.numW, Math.max(6e3, textW(t, MINISTRY.headFont, s.pt) + 1600));
|
|
23724
|
+
const numBf = bf.get({ t: s.border, b: s.border, l: s.border, r: s.border, fill: s.fill });
|
|
23725
|
+
const titleBf = bf.get({ t: NONE, b: s.underline, l: NONE, r: NONE });
|
|
23726
|
+
const cNum = reg.char({ font: MINISTRY.headFont, pt: s.pt, color: "#FFFFFF" });
|
|
23727
|
+
const cT = reg.char({ font: MINISTRY.headFont, pt: s.pt });
|
|
23728
|
+
const center = reg.para({ align: "CENTER", lineSp: 130 });
|
|
23729
|
+
const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
|
|
23730
|
+
const row = tc({ bf: numBf, row: 0, col: 0, w: s.numW, h: s.h, paras: para(String(n), center, cNum) }) + tc({ bf: titleBf, row: 0, col: 1, w: titleW, h: s.h, paras: para(t, left, cT), name: "__kordoc_h3" });
|
|
23731
|
+
return host(ftbl([row], s.numW + titleW, s.h, 2, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
|
|
23732
|
+
}
|
|
23733
|
+
function buildMinistrySubheadBox(n, title, ctx, before) {
|
|
23734
|
+
const { reg, bf } = ctx;
|
|
23735
|
+
const s = MINISTRY.subhead;
|
|
23736
|
+
const text = `${n}. ${plain(title)}`;
|
|
23737
|
+
const w = Math.min(ctx.W, textW(text, MINISTRY.headFont, s.pt) + 1400);
|
|
23738
|
+
const box = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE, fill: s.fill });
|
|
23739
|
+
const cT = reg.char({ font: MINISTRY.headFont, pt: s.pt, color: "#FFFFFF" });
|
|
23740
|
+
const p = reg.para({ align: "LEFT", lineSp: 130, left: 400 });
|
|
23741
|
+
const cell2 = tc({ bf: box, row: 0, col: 0, w, h: s.h, paras: para(text, p, cT), name: "__kordoc_h4" });
|
|
23742
|
+
return host(ftbl([cell2], w, s.h, 1, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
|
|
23743
|
+
}
|
|
23744
|
+
function buildMinistryItemBand(n, text, ctx, before) {
|
|
23745
|
+
const { reg, bf } = ctx;
|
|
23746
|
+
const s = MINISTRY.item;
|
|
23747
|
+
const w = ctx.W;
|
|
23748
|
+
const band = bf.get({ t: s.line, b: s.line, l: NONE, r: NONE, fill: s.fill });
|
|
23749
|
+
const base = { font: MINISTRY.bandFont, pt: s.pt, bold: false };
|
|
23750
|
+
const f = fitOrphanLine(plain(text), MINISTRY.bandFont, s.pt, w - 1800, w - 1800);
|
|
23751
|
+
const styled = f ? { ...base, ratio: f.ratio, spacing: f.spacing } : base;
|
|
23752
|
+
const runs = ministryRuns(circledNumber(n - 1), text, ctx, styled, MINISTRY.blue, MINISTRY.blue);
|
|
23753
|
+
const p = reg.para({ align: "LEFT", lineSp: 130, left: 300, keepWord: false });
|
|
23754
|
+
const cell2 = tc({ bf: band, row: 0, col: 0, w, h: s.h, paras: `<hp:p paraPrIDRef="${p}" styleIDRef="0">${runs}</hp:p>`, name: "__kordoc_h5" });
|
|
23755
|
+
return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), reg.char(base));
|
|
23756
|
+
}
|
|
23757
|
+
function buildMinistrySummaryBox(text, ctx) {
|
|
23758
|
+
const { reg, bf } = ctx;
|
|
23759
|
+
const s = MINISTRY.summary;
|
|
23760
|
+
const w = ctx.W - 600;
|
|
23761
|
+
const avail = w - 280 - 600;
|
|
23762
|
+
const pItem = reg.para({ align: "LEFT", lineSp: 140, left: 300, right: 300, indent: -Math.round(s.pt * 100 * 0.9), keepWord: false });
|
|
23763
|
+
const pRef = reg.para({ align: "LEFT", lineSp: 130, left: 300 + Math.round(s.pt * 100 * 0.9), right: 300, keepWord: false });
|
|
23764
|
+
const baseItem = { font: MINISTRY.bodyFont, pt: s.pt, bold: false };
|
|
23765
|
+
const cRef = reg.char({ font: MINISTRY.refFont, pt: s.refPt });
|
|
23766
|
+
let h = 0;
|
|
23767
|
+
const paras = text.split("\n").map((raw) => raw.trim()).filter(Boolean).map((line) => {
|
|
23768
|
+
const isRef = /^(\*|※)/.test(line);
|
|
23769
|
+
if (isRef) {
|
|
23770
|
+
h += Math.round(s.refPt * 100 * 1.3) * simulateWrap(line, avail, avail, s.refPt * 100, 100, "charAll", { faceClass: "gothic" }).lines;
|
|
23771
|
+
return `<hp:p paraPrIDRef="${pRef}" styleIDRef="0">${generateRuns(line, cRef, inlineMapper(reg, { font: MINISTRY.refFont, pt: s.refPt, bold: false }))}</hp:p>`;
|
|
23772
|
+
}
|
|
23773
|
+
const body = line.replace(/^[▪■□○ㅇ●\-–ㆍ·•]\s*/u, "");
|
|
23774
|
+
h += Math.round(s.pt * 100 * 1.4) * simulateWrap(plain(body), avail, avail, s.pt * 100, 100, "charAll").lines;
|
|
23775
|
+
return `<hp:p paraPrIDRef="${pItem}" styleIDRef="0">${ministryRuns("\u25AA", body, ctx, baseItem, MINISTRY.blue)}</hp:p>`;
|
|
23776
|
+
});
|
|
23777
|
+
h += 280 + 600;
|
|
23778
|
+
const box = bf.get({ t: "thin", b: "thin", l: "thin", r: "thin", fill: s.fill });
|
|
23779
|
+
const cell2 = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), vAlign: "TOP", name: "__kordoc_summary" });
|
|
23780
|
+
return host(ftbl([cell2], w, h, 1, { bottomGap: 900 }), reg.para({ align: "CENTER", lineSp: 100, before: 400 }), reg.char(baseItem));
|
|
23781
|
+
}
|
|
23782
|
+
function buildMinistryAttachBand(title, ctx, before) {
|
|
23783
|
+
const { reg, bf } = ctx;
|
|
23784
|
+
const a = MINISTRY.attach;
|
|
23785
|
+
const w = ctx.W;
|
|
23786
|
+
const titleW = w - a.labelW - a.gap;
|
|
23787
|
+
const labelBf = bf.get({ t: "thin", b: a.thick, l: "thin", r: a.thick, fill: a.fill });
|
|
23788
|
+
const gapBf = bf.get({ t: NONE, b: NONE, l: NONE, r: NONE });
|
|
23789
|
+
const titleBf = bf.get({ t: "thin", b: a.thick, l: "thin", r: a.thick });
|
|
23790
|
+
const t = stripAttachPrefix(plain(title));
|
|
23791
|
+
const fit = fitOneLine(t, MINISTRY.headFont, a.pt, titleW - 800, a.pt - 3);
|
|
23792
|
+
const cLabel = reg.char({ font: MINISTRY.headFont, pt: a.pt, color: "#FFFFFF" });
|
|
23793
|
+
const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
|
|
23794
|
+
const center = reg.para({ align: "CENTER", lineSp: 130 });
|
|
23795
|
+
const row = tc({ bf: labelBf, row: 0, col: 0, w: a.labelW, h: a.h, paras: para("\uBCC4 \uCCA8", center, cLabel) }) + tc({ bf: gapBf, row: 0, col: 1, w: a.gap, h: a.h, paras: para("", center, cT) }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h: a.h, paras: para(t, center, cT), name: "__kordoc_h2" });
|
|
23796
|
+
return host(ftbl([row], w, a.h, 3, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
|
|
23797
|
+
}
|
|
23798
|
+
function isAttachHeading(text) {
|
|
23799
|
+
return /^\[?\s*별\s*첨\s*\]?/u.test(text.trim());
|
|
23800
|
+
}
|
|
23801
|
+
|
|
26478
23802
|
// src/hwpx/gen-gongmun.ts
|
|
26479
|
-
var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes"]);
|
|
23803
|
+
var V5_PRESETS = /* @__PURE__ */ new Set(["official", "report", "plan", "notice", "minutes", "ministry"]);
|
|
26480
23804
|
function usesV5Engine(preset) {
|
|
26481
23805
|
return V5_PRESETS.has(preset);
|
|
26482
23806
|
}
|
|
@@ -26484,17 +23808,7 @@ var ROMAN = ["\u2160", "\u2161", "\u2162", "\u2163", "\u2164", "\u2165", "\u2166
|
|
|
26484
23808
|
function chapterLabel(index, style) {
|
|
26485
23809
|
return style === "roman" ? `${ROMAN[(index - 1) % 12]}.` : `${index}.`;
|
|
26486
23810
|
}
|
|
26487
|
-
function
|
|
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) {
|
|
23811
|
+
function plain2(text) {
|
|
26498
23812
|
return text.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/!\[[^\]]*\]\([^)]*\)/g, "");
|
|
26499
23813
|
}
|
|
26500
23814
|
function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
@@ -26505,8 +23819,9 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26505
23819
|
const g = gongmun;
|
|
26506
23820
|
const preset = g.preset;
|
|
26507
23821
|
const isReport = preset === "report" || preset === "plan";
|
|
23822
|
+
const isMinistry = preset === "ministry";
|
|
26508
23823
|
const W = mmToHwpunit(210 - g.margins.left - g.margins.right);
|
|
26509
|
-
const pre = buildOutline(blocks, { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
|
|
23824
|
+
const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
|
|
26510
23825
|
const scheme = pickScheme(g, pre.hasBoxMarkers);
|
|
26511
23826
|
const gaejosik = scheme.kind === "gaejosik";
|
|
26512
23827
|
const outline = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
|
|
@@ -26543,6 +23858,28 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26543
23858
|
else if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.body.font, pt: scheme.body.pt + 2, bold: true }), void 0, 1));
|
|
26544
23859
|
if (g.reportInfo) paras.push(generateParagraph(g.reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
|
|
26545
23860
|
if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
|
|
23861
|
+
} else if (isMinistry) {
|
|
23862
|
+
if (g.cover) {
|
|
23863
|
+
const cover = buildMinistryCover({ title: docTitle, date: g.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()), org: g.cover.org || void 0, label: g.cover.label }, frame);
|
|
23864
|
+
if (g.pageNumbers) cover[0] = cover[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
|
|
23865
|
+
paras.push(...cover);
|
|
23866
|
+
pendingPageBreak = true;
|
|
23867
|
+
}
|
|
23868
|
+
if (g.toc && outline.chapters > 0) {
|
|
23869
|
+
const tocChapters = [];
|
|
23870
|
+
for (const n of outline.nodes) {
|
|
23871
|
+
if (n.kind === "chapter") tocChapters.push({ title: plain2(n.text), subs: [], attach: isAttachHeading(n.text) });
|
|
23872
|
+
else if (n.kind === "heading" && n.level === 3 && tocChapters.length) tocChapters[tocChapters.length - 1].subs.push(plain2(n.text));
|
|
23873
|
+
}
|
|
23874
|
+
const toc = buildMinistryToc(tocChapters, frame);
|
|
23875
|
+
if (g.pageNumbers) toc[0] = toc[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
|
|
23876
|
+
paras.push(...toc);
|
|
23877
|
+
pendingPageBreak = true;
|
|
23878
|
+
}
|
|
23879
|
+
if (!g.cover && !g.toc && docTitle) {
|
|
23880
|
+
paras.push(generateParagraph(docTitle, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20 }), void 0, 1));
|
|
23881
|
+
frontKind = "title";
|
|
23882
|
+
}
|
|
26546
23883
|
} else if (isReport) {
|
|
26547
23884
|
if (g.cover) {
|
|
26548
23885
|
paras.push(...buildReportCover({
|
|
@@ -26585,6 +23922,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26585
23922
|
let prevItemDepth = -1;
|
|
26586
23923
|
let nextNode;
|
|
26587
23924
|
let tableSeq = 0;
|
|
23925
|
+
let sectionSeq = 0, subheadSeq = 0, itemBandSeq = 0;
|
|
23926
|
+
let pendingNewPageNum = isMinistry && (!!g.cover || !!g.toc);
|
|
26588
23927
|
let chapterStyle = g.h2Marker === "box" || g.h2Marker === "number" || g.h2Marker === "none" || g.h2Marker === "band" ? g.h2Marker : "roman";
|
|
26589
23928
|
if (!gaejosik) chapterStyle = "number";
|
|
26590
23929
|
const leadLeft = (depth, pt) => (scheme.levels[Math.min(depth, 7)]?.leadTa ?? 0) * taHu(pt);
|
|
@@ -26609,14 +23948,14 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26609
23948
|
const availCont = W - geom.left + geom.indent;
|
|
26610
23949
|
let pt = st.pt, ratio = 100, spacing = 0;
|
|
26611
23950
|
if (st.oneLine) {
|
|
26612
|
-
const fit = fitOneLine(
|
|
23951
|
+
const fit = fitOneLine(plain2(text), st.font, st.pt, availFirst, st.pt, 90);
|
|
26613
23952
|
if (fit.overflow) warnings.push(`\u25A1 \uD56D\uBAA9\uC774 \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uB450 \uC904\uB85C \uAEBE\uC785\uB2C8\uB2E4 \u2014 \uBB38\uC7A5\uC744 \uC904\uC774\uC138\uC694(\uC7A5\uD3C9 90%\xB7\uC790\uAC04 -5 \uB85C\uB3C4 \uCD08\uACFC): "${node.text.slice(0, 30)}\u2026"`);
|
|
26614
23953
|
else {
|
|
26615
23954
|
ratio = fit.ratio;
|
|
26616
23955
|
spacing = fit.spacing;
|
|
26617
23956
|
}
|
|
26618
23957
|
} else if (g.autoFitMinRatio !== null) {
|
|
26619
|
-
const f = fitOrphanLine(
|
|
23958
|
+
const f = fitOrphanLine(plain2(text), st.font, st.pt, availFirst, availCont);
|
|
26620
23959
|
if (f) {
|
|
26621
23960
|
ratio = f.ratio;
|
|
26622
23961
|
spacing = f.spacing;
|
|
@@ -26628,18 +23967,25 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26628
23967
|
const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
|
|
26629
23968
|
const before = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
|
|
26630
23969
|
const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
|
|
23970
|
+
if (isMinistry) {
|
|
23971
|
+
const mk = node.marker ?? marker;
|
|
23972
|
+
const g2 = node.marker ? levelGeometry(st, mk) : geom2;
|
|
23973
|
+
const paraId2 = reg.para({ align: "JUSTIFY", left: g2.left, indent: g2.indent, before, after: 600, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
|
|
23974
|
+
const runs = ministryRuns(mk, node.text, frame, base, depth === 0 ? MINISTRY.blue : null);
|
|
23975
|
+
return `<hp:p paraPrIDRef="${paraId2}" styleIDRef="${styleId}">${runs}</hp:p>`;
|
|
23976
|
+
}
|
|
26631
23977
|
const paraId = reg.para({ align: "JUSTIFY", left: geom2.left, indent: geom2.indent, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
|
|
26632
23978
|
return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), styleId);
|
|
26633
23979
|
};
|
|
26634
23980
|
const renderRef = (node) => {
|
|
26635
23981
|
const st = scheme.ref;
|
|
26636
|
-
const marker = "\u203B";
|
|
26637
|
-
const left = leadLeft(node.depth, st.pt);
|
|
23982
|
+
const marker = isMinistry ? "*" : "\u203B";
|
|
23983
|
+
const left = isMinistry ? st.leadTa * taHu(st.pt) : leadLeft(node.depth, st.pt);
|
|
26638
23984
|
const geom = levelGeometry({ ...st, leadTa: 0 }, marker);
|
|
26639
23985
|
const base = { font: st.font, pt: st.pt, bold: st.bold };
|
|
26640
|
-
const f = fitOrphanLine(
|
|
23986
|
+
const f = fitOrphanLine(plain2(`${marker} ${node.text}`), st.font, st.pt, W - left, W - left + geom.indent);
|
|
26641
23987
|
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 });
|
|
23988
|
+
const paraId = reg.para({ align: "JUSTIFY", left, indent: geom.indent, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: false });
|
|
26643
23989
|
return generateParagraph(`${marker} ${node.text}`, paraId, reg.char(baseF), inlineMapper(reg, baseF));
|
|
26644
23990
|
};
|
|
26645
23991
|
const renderPara = (node) => {
|
|
@@ -26656,7 +24002,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26656
24002
|
}
|
|
26657
24003
|
let ratio = 100, spacing = 0;
|
|
26658
24004
|
if (!node.align && g.autoFitMinRatio !== null) {
|
|
26659
|
-
const f = fitOrphanLine(
|
|
24005
|
+
const f = fitOrphanLine(plain2(node.text), st.font, st.pt, W, W);
|
|
26660
24006
|
if (f) {
|
|
26661
24007
|
ratio = f.ratio;
|
|
26662
24008
|
spacing = f.spacing;
|
|
@@ -26668,15 +24014,27 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26668
24014
|
};
|
|
26669
24015
|
const renderChapter = (node) => {
|
|
26670
24016
|
const st = scheme.chapter;
|
|
24017
|
+
if (isMinistry) {
|
|
24018
|
+
sectionSeq = 0;
|
|
24019
|
+
subheadSeq = 0;
|
|
24020
|
+
itemBandSeq = 0;
|
|
24021
|
+
const first = prevKind === "start" || prevKind === "title";
|
|
24022
|
+
const xml = isAttachHeading(node.text) ? buildMinistryAttachBand(node.text, frame, 0) : (() => {
|
|
24023
|
+
const band = buildMinistryChapterBand(romanOf(node.index), node.text, frame, 0);
|
|
24024
|
+
if (band.overflow) warnings.push(`\uC7A5 \uC81C\uBAA9\uC774 \uB760 \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uCD95\uC18C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${node.text.slice(0, 30)}\u2026"`);
|
|
24025
|
+
return band.xml;
|
|
24026
|
+
})();
|
|
24027
|
+
return first ? xml : xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
|
|
24028
|
+
}
|
|
26671
24029
|
if (chapterStyle === "band") {
|
|
26672
24030
|
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(/\.$/, ""),
|
|
24031
|
+
const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g.bandColor, textColor: g.bandTextColor, titleFont: st.font, titlePt: st.pt }, before2);
|
|
26674
24032
|
if (band.overflow) warnings.push(`\uC7A5 \uC81C\uBAA9\uC774 \uB760 \uD45C \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uCD95\uC18C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${node.text.slice(0, 30)}\u2026"`);
|
|
26675
24033
|
return band.xml;
|
|
26676
24034
|
}
|
|
26677
24035
|
const label = chapterStyle === "none" ? "" : chapterLabel(node.index, chapterStyle === "number" ? "number" : "roman");
|
|
26678
24036
|
const text = label ? `${label} ${node.text}` : node.text;
|
|
26679
|
-
const fit = fitOneLine(
|
|
24037
|
+
const fit = fitOneLine(plain2(text), st.font, st.pt, W, st.pt - 2);
|
|
26680
24038
|
const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
|
|
26681
24039
|
const before = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
|
|
26682
24040
|
const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true, keepWord: false });
|
|
@@ -26744,6 +24102,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26744
24102
|
case "title":
|
|
26745
24103
|
break;
|
|
26746
24104
|
case "summary":
|
|
24105
|
+
if (isMinistry) {
|
|
24106
|
+
xml = buildMinistrySummaryBox(node.text, frame);
|
|
24107
|
+
break;
|
|
24108
|
+
}
|
|
26747
24109
|
if (isReport && !g.summary) {
|
|
26748
24110
|
pushSummary(node.text);
|
|
26749
24111
|
prevKind = node.kind;
|
|
@@ -26751,6 +24113,20 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26751
24113
|
}
|
|
26752
24114
|
xml = renderRef({ kind: "ref", depth: 0, text: node.text });
|
|
26753
24115
|
break;
|
|
24116
|
+
case "heading": {
|
|
24117
|
+
const before = prevKind === "chapter" || prevKind === "heading" || prevKind === "start" ? 0 : BAND_BEFORE_HU;
|
|
24118
|
+
if (node.level <= 3) {
|
|
24119
|
+
subheadSeq = 0;
|
|
24120
|
+
itemBandSeq = 0;
|
|
24121
|
+
xml = buildMinistrySectionBand(++sectionSeq, node.text, frame, before);
|
|
24122
|
+
} else if (node.level === 4) {
|
|
24123
|
+
itemBandSeq = 0;
|
|
24124
|
+
xml = buildMinistrySubheadBox(++subheadSeq, node.text, frame, before);
|
|
24125
|
+
} else xml = buildMinistryItemBand(++itemBandSeq, node.text, frame, before);
|
|
24126
|
+
lastItemFromHeading = true;
|
|
24127
|
+
lastTextNode = node;
|
|
24128
|
+
break;
|
|
24129
|
+
}
|
|
26754
24130
|
case "chapter":
|
|
26755
24131
|
lastItemFromHeading = true;
|
|
26756
24132
|
if (!gaejosik) {
|
|
@@ -26791,6 +24167,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26791
24167
|
xml = xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
|
|
26792
24168
|
pendingPageBreak = false;
|
|
26793
24169
|
}
|
|
24170
|
+
if (pendingNewPageNum) {
|
|
24171
|
+
xml = xml.replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${newPageNumCtrl(1)}`);
|
|
24172
|
+
pendingNewPageNum = false;
|
|
24173
|
+
}
|
|
26794
24174
|
paras.push(xml);
|
|
26795
24175
|
prevKind = node.kind;
|
|
26796
24176
|
if (node.kind === "item") prevItemDepth = node.depth;
|
|
@@ -27822,7 +25202,7 @@ function gongmunLintWarnings(text, limit = 10) {
|
|
|
27822
25202
|
// src/hwpx/munche-lint.ts
|
|
27823
25203
|
function usesGaejosikMunche(preset) {
|
|
27824
25204
|
const p = normalizeGongmunPreset(preset);
|
|
27825
|
-
return p === "gaejosik" || p === "report" || p === "plan";
|
|
25205
|
+
return p === "gaejosik" || p === "report" || p === "plan" || p === "ministry";
|
|
27826
25206
|
}
|
|
27827
25207
|
var ITEM_MAX = 70;
|
|
27828
25208
|
var CONCL_MAX = 60;
|
|
@@ -27841,7 +25221,7 @@ function classify(raw) {
|
|
|
27841
25221
|
if (li) return { kind: indent >= 2 ? "sub" : "item", body: li[1].trim() };
|
|
27842
25222
|
return { kind: "para", body: t };
|
|
27843
25223
|
}
|
|
27844
|
-
function
|
|
25224
|
+
function plain3(s) {
|
|
27845
25225
|
return s.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/(\*\*|__|~~|`)/g, "").trim();
|
|
27846
25226
|
}
|
|
27847
25227
|
function stripQuoted(s) {
|
|
@@ -27896,7 +25276,9 @@ function lintMuncheText(text) {
|
|
|
27896
25276
|
if (!leadBuf.length) return;
|
|
27897
25277
|
const block = leadBuf.map((l) => l.body).join(" ").trim();
|
|
27898
25278
|
const at = leadBuf[0].line;
|
|
25279
|
+
const bulleted = leadBuf.every((l) => /^[▪■□○ㅇ●\-–ㆍ·•*※]/u.test(l.body));
|
|
27899
25280
|
leadBuf = [];
|
|
25281
|
+
if (bulleted) return;
|
|
27900
25282
|
if (block.length < 20) return;
|
|
27901
25283
|
if (!LEAD_ENDING.test(block)) {
|
|
27902
25284
|
add(
|
|
@@ -27944,11 +25326,11 @@ function lintMuncheText(text) {
|
|
|
27944
25326
|
continue;
|
|
27945
25327
|
}
|
|
27946
25328
|
if (c.kind === "lead") {
|
|
27947
|
-
leadBuf.push({ line: no, body:
|
|
25329
|
+
leadBuf.push({ line: no, body: plain3(c.body) });
|
|
27948
25330
|
continue;
|
|
27949
25331
|
}
|
|
27950
25332
|
flushLead();
|
|
27951
|
-
const b =
|
|
25333
|
+
const b = plain3(c.body);
|
|
27952
25334
|
if (!b) continue;
|
|
27953
25335
|
const q = stripQuoted(b);
|
|
27954
25336
|
if (c.kind === "item" || c.kind === "sub" || c.kind === "concl" || c.kind === "para") {
|
|
@@ -29039,7 +26421,7 @@ async function resolveHeaderEntryName(zip) {
|
|
|
29039
26421
|
|
|
29040
26422
|
// src/roundtrip/hwp5-patch.ts
|
|
29041
26423
|
import { deflateRawSync } from "zlib";
|
|
29042
|
-
import { createRequire
|
|
26424
|
+
import { createRequire } from "module";
|
|
29043
26425
|
|
|
29044
26426
|
// src/roundtrip/ole-surgeon.ts
|
|
29045
26427
|
var SECTOR = 512;
|
|
@@ -29369,21 +26751,21 @@ var Surgeon = class {
|
|
|
29369
26751
|
};
|
|
29370
26752
|
|
|
29371
26753
|
// src/roundtrip/hwp5-patch.ts
|
|
29372
|
-
var
|
|
29373
|
-
var
|
|
29374
|
-
var
|
|
29375
|
-
function
|
|
26754
|
+
var require2 = createRequire(import.meta.url);
|
|
26755
|
+
var CFB = require2("cfb");
|
|
26756
|
+
var TAG_PARA_LINE_SEG = 69;
|
|
26757
|
+
function cid(s) {
|
|
29376
26758
|
return (s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3)) >>> 0;
|
|
29377
26759
|
}
|
|
29378
|
-
var
|
|
29379
|
-
var
|
|
29380
|
-
function
|
|
26760
|
+
var CTRL_TBL = cid("tbl ");
|
|
26761
|
+
var CTRL_GSO = cid("gso ");
|
|
26762
|
+
function swap32(id) {
|
|
29381
26763
|
return ((id & 255) << 24 | (id >>> 8 & 255) << 16 | (id >>> 16 & 255) << 8 | id >>> 24 & 255) >>> 0;
|
|
29382
26764
|
}
|
|
29383
26765
|
function isCtrl(rec, id) {
|
|
29384
26766
|
if (rec.tagId !== TAG_CTRL_HEADER || rec.data.length < 4) return false;
|
|
29385
26767
|
const raw = rec.data.readUInt32LE(0);
|
|
29386
|
-
return raw === id ||
|
|
26768
|
+
return raw === id || swap32(raw) === id;
|
|
29387
26769
|
}
|
|
29388
26770
|
function readRecordsStrict(stream) {
|
|
29389
26771
|
const recs = [];
|
|
@@ -29452,13 +26834,13 @@ function scanSection(stream, sectionIndex, compressed) {
|
|
|
29452
26834
|
textIdx = textIdx === -1 ? j : -2;
|
|
29453
26835
|
appendParaText(state, records[j].data);
|
|
29454
26836
|
} else if (t === TAG_CHAR_SHAPE && charShapeIdx === -1) charShapeIdx = j;
|
|
29455
|
-
else if (t ===
|
|
26837
|
+
else if (t === TAG_PARA_LINE_SEG && lineSegIdx === -1) lineSegIdx = j;
|
|
29456
26838
|
}
|
|
29457
26839
|
let ctrlSeen = false, nonGso = false;
|
|
29458
26840
|
for (let a = parent[i]; a >= 0; a = parent[a]) {
|
|
29459
26841
|
if (records[a].tagId === TAG_CTRL_HEADER) {
|
|
29460
26842
|
ctrlSeen = true;
|
|
29461
|
-
if (!isCtrl(records[a],
|
|
26843
|
+
if (!isCtrl(records[a], CTRL_GSO)) nonGso = true;
|
|
29462
26844
|
}
|
|
29463
26845
|
}
|
|
29464
26846
|
const kind = !ctrlSeen || !nonGso ? "body" : "other";
|
|
@@ -29479,7 +26861,7 @@ function scanSection(stream, sectionIndex, compressed) {
|
|
|
29479
26861
|
}
|
|
29480
26862
|
const tables = [];
|
|
29481
26863
|
for (let i = 0; i < records.length; i++) {
|
|
29482
|
-
if (!isCtrl(records[i],
|
|
26864
|
+
if (!isCtrl(records[i], CTRL_TBL) || ancestorCtrl(i, CTRL_TBL)) continue;
|
|
29483
26865
|
const ctrlLevel = records[i].level;
|
|
29484
26866
|
let rows = 0, cols = 0, tableIdx = -1;
|
|
29485
26867
|
for (let j2 = i + 1; j2 < records.length && records[j2].level > ctrlLevel; j2++) {
|
|
@@ -29530,11 +26912,11 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
29530
26912
|
const originalBuf = Buffer.from(original.buffer, original.byteOffset, original.byteLength);
|
|
29531
26913
|
let cfb;
|
|
29532
26914
|
try {
|
|
29533
|
-
cfb =
|
|
26915
|
+
cfb = CFB.parse(originalBuf);
|
|
29534
26916
|
} catch (err) {
|
|
29535
26917
|
return fail(`CFB \uCEE8\uD14C\uC774\uB108 \uD30C\uC2F1 \uC2E4\uD328: ${msg(err)}`);
|
|
29536
26918
|
}
|
|
29537
|
-
const fhEntry =
|
|
26919
|
+
const fhEntry = CFB.find(cfb, "/FileHeader");
|
|
29538
26920
|
if (!fhEntry?.content) return fail("FileHeader \uC2A4\uD2B8\uB9BC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 HWP 5.x \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4");
|
|
29539
26921
|
let flags;
|
|
29540
26922
|
try {
|
|
@@ -29556,7 +26938,7 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
29556
26938
|
if (sectionPaths.length === 0) return fail("BodyText \uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
29557
26939
|
const scans = [];
|
|
29558
26940
|
for (let i = 0; i < sectionPaths.length; i++) {
|
|
29559
|
-
const entry =
|
|
26941
|
+
const entry = CFB.find(cfb, sectionPaths[i]);
|
|
29560
26942
|
if (!entry?.content) return fail(`\uC139\uC158 \uC2A4\uD2B8\uB9BC \uC77D\uAE30 \uC2E4\uD328: ${sectionPaths[i]}`);
|
|
29561
26943
|
let stream;
|
|
29562
26944
|
try {
|
|
@@ -30680,7 +28062,7 @@ async function dispatch(format, buffer, opts) {
|
|
|
30680
28062
|
}
|
|
30681
28063
|
async function parseImage(buffer, options) {
|
|
30682
28064
|
try {
|
|
30683
|
-
const { parseImageDocument } = await import("./image-ocr-
|
|
28065
|
+
const { parseImageDocument } = await import("./image-ocr-N6IBZJAK.js");
|
|
30684
28066
|
const { blocks, warnings } = await parseImageDocument(buffer, options);
|
|
30685
28067
|
return {
|
|
30686
28068
|
success: true,
|
|
@@ -30739,7 +28121,7 @@ async function parseHwp(buffer, options) {
|
|
|
30739
28121
|
async function parsePdf(buffer, options) {
|
|
30740
28122
|
let parsePdfDocument;
|
|
30741
28123
|
try {
|
|
30742
|
-
const mod = await import("./parser-
|
|
28124
|
+
const mod = await import("./parser-BK66Y5JD.js");
|
|
30743
28125
|
parsePdfDocument = mod.parsePdfDocument;
|
|
30744
28126
|
} catch {
|
|
30745
28127
|
return {
|
|
@@ -30854,8 +28236,8 @@ async function extractTables(input, options = {}) {
|
|
|
30854
28236
|
}));
|
|
30855
28237
|
if (out.length === 0) return out;
|
|
30856
28238
|
const format = detectFormat(buffer);
|
|
30857
|
-
if (format !== "hwpx") {
|
|
30858
|
-
for (const t of out) t.warnings.push(
|
|
28239
|
+
if (format !== "hwpx" && format !== "hwp") {
|
|
28240
|
+
for (const t of out) t.warnings.push(`${format} \uC740 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 region\xB7crop \uC5C6\uC74C`);
|
|
30859
28241
|
return out;
|
|
30860
28242
|
}
|
|
30861
28243
|
let scene;
|
|
@@ -30896,9 +28278,7 @@ async function extractTables(input, options = {}) {
|
|
|
30896
28278
|
}
|
|
30897
28279
|
|
|
30898
28280
|
export {
|
|
30899
|
-
detectImageMime,
|
|
30900
28281
|
extractHwpxMetadataOnly,
|
|
30901
|
-
extractHwp5MetadataOnly,
|
|
30902
28282
|
formatFillValue,
|
|
30903
28283
|
ValueCursor,
|
|
30904
28284
|
fillWithUniqueGuard,
|
|
@@ -30951,4 +28331,4 @@ export {
|
|
|
30951
28331
|
parseHwpml,
|
|
30952
28332
|
fillForm
|
|
30953
28333
|
};
|
|
30954
|
-
//# sourceMappingURL=chunk-
|
|
28334
|
+
//# sourceMappingURL=chunk-EMQZX5QQ.js.map
|