kordoc 4.13.0 → 4.13.1
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 +20 -2
- package/dist/{-UZDRUWTX.js → -S3PXW4LR.js} +13 -11
- package/dist/{chunk-LP5RTYFM.js → chunk-2TEH7CC5.js} +2 -2
- package/dist/{chunk-GWFX5ZD7.js → chunk-36FAAEN4.js} +2 -2
- package/dist/{chunk-IEJR23OA.js → chunk-5WZTB3OI.js} +3 -3
- package/dist/{chunk-OJV7HNGJ.cjs → chunk-7HOFM76G.cjs} +4 -4
- package/dist/{chunk-OJV7HNGJ.cjs.map → chunk-7HOFM76G.cjs.map} +1 -1
- package/dist/{chunk-2B45S7FM.js → chunk-ABGDU4KM.js} +2 -2
- package/dist/{chunk-AGBFCFQY.js → chunk-D6S64TR6.js} +2 -2
- package/dist/{chunk-2RREQZ44.js → chunk-F4DZU7VP.js} +286 -3312
- package/dist/chunk-F4DZU7VP.js.map +1 -0
- package/dist/{chunk-F3FEIYI7.js → chunk-FSEJ6OJO.js} +2 -2
- package/dist/{chunk-27QN27I3.js → chunk-H3HDX4JN.js} +2 -2
- package/dist/{chunk-OSLJ3A6D.js → chunk-IRIASTA5.js} +2 -2
- package/dist/{chunk-6JXMIX3G.js → chunk-K3KLZZC6.js} +3 -2
- package/dist/{chunk-CKMK42FV.cjs → chunk-KXCSR4XK.cjs} +7 -7
- package/dist/{chunk-CKMK42FV.cjs.map → chunk-KXCSR4XK.cjs.map} +1 -1
- package/dist/{chunk-S2VKZKUX.cjs → chunk-L5UQXOUL.cjs} +3 -3
- package/dist/{chunk-S2VKZKUX.cjs.map → chunk-L5UQXOUL.cjs.map} +1 -1
- package/dist/{chunk-TAYCQE6K.cjs → chunk-LBWJOJ2T.cjs} +2 -2
- package/dist/{chunk-TAYCQE6K.cjs.map → chunk-LBWJOJ2T.cjs.map} +1 -1
- package/dist/{chunk-ILDTN72S.js → chunk-Q4DUG3WU.js} +2 -2
- package/dist/{chunk-H5JWPWQP.js → chunk-Q7CQWGLN.js} +2 -2
- package/dist/{chunk-L3CEPNQS.js → chunk-QFXJAYA4.js} +2 -2
- package/dist/{chunk-6NLXGGNH.js → chunk-QJD3CLFG.js} +3 -1
- package/dist/chunk-QJD3CLFG.js.map +1 -0
- package/dist/{chunk-BQLZOQES.js → chunk-QOJTBOWX.js} +40 -6
- package/dist/chunk-QOJTBOWX.js.map +1 -0
- package/dist/{chunk-TA2LEFOF.js → chunk-R6NY4ZIS.js} +3 -3
- package/dist/{chunk-5VHLKNCE.js → chunk-SA2ECW7B.js} +2 -2
- package/dist/{chunk-3MCFJIN2.js → chunk-SHC6FGRY.js} +3 -3
- package/dist/{chunk-MCD45IHO.cjs → chunk-TS665MCV.cjs} +6 -6
- package/dist/{chunk-MCD45IHO.cjs.map → chunk-TS665MCV.cjs.map} +1 -1
- package/dist/{chunk-3ABHPKYA.js → chunk-VRXVNVLQ.js} +2 -2
- package/dist/chunk-WOAVPYT3.js +5770 -0
- package/dist/chunk-WOAVPYT3.js.map +1 -0
- package/dist/chunks-IZLHLZQD.js +10 -0
- package/dist/cli.js +35 -33
- package/dist/cli.js.map +1 -1
- package/dist/{formula-YALEMZMF.cjs → formula-DF6KHCIH.cjs} +9 -9
- package/dist/{formula-YALEMZMF.cjs.map → formula-DF6KHCIH.cjs.map} +1 -1
- package/dist/{formula-3T7HQA4O.js → formula-GL5HHTL5.js} +4 -4
- package/dist/{formula-FWVAOSIK.js → formula-Z6EVYSSI.js} +3 -3
- package/dist/{image-ocr-X3I2HUK2.js → image-ocr-SVA3XKWJ.js} +5 -5
- package/dist/{image-ocr-4EUZIF6M.cjs → image-ocr-X2D3G7GD.cjs} +11 -11
- package/dist/{image-ocr-4EUZIF6M.cjs.map → image-ocr-X2D3G7GD.cjs.map} +1 -1
- package/dist/{image-ocr-2M7K3CE5.js → image-ocr-ZE2VGIC3.js} +7 -7
- package/dist/index.cjs +1217 -559
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -7
- package/dist/index.d.ts +54 -7
- package/dist/index.js +736 -78
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +138 -69
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-XXYZ6ELQ.js → model-bundle-3TTBPL3Q.js} +5 -5
- package/dist/{models-APDSXJW7.js → models-6RICFYZZ.js} +5 -5
- package/dist/{parser-T5R4YVZS.js → parser-54OYFBL2.js} +6 -6
- package/dist/{parser-4KE2FR5F.cjs → parser-NMVZMF5W.cjs} +26 -26
- package/dist/{parser-4KE2FR5F.cjs.map → parser-NMVZMF5W.cjs.map} +1 -1
- package/dist/{parser-XKSOS6GY.js → parser-YKKSFY6Q.js} +6 -6
- package/dist/pdf-ocr-FFKINEB7.cjs +14 -0
- package/dist/{pdf-ocr-JZKCEKWX.cjs.map → pdf-ocr-FFKINEB7.cjs.map} +1 -1
- package/dist/pdf-ocr-MFJQP4XG.js +13 -0
- package/dist/pdf-ocr-WWYZE5L7.js +16 -0
- package/dist/{profile-io-Y24GOHWM.js → profile-io-PDOYD237.js} +3 -3
- package/dist/{rasterize-QCOBWHAG.js → rasterize-4ZSVJGDA.js} +3 -3
- package/dist/{render-DHFQQCSV.js → render-XRCZI3CY.js} +16 -6
- package/dist/seal-CFYSOI4V.js +10 -0
- package/dist/{setup-UWX3KH3G.js → setup-K7GW2TZM.js} +3 -3
- package/dist/visual-AUPPOHEP.js +19 -0
- package/dist/{watch-4ZUTMC3Q.js → watch-MR27M6VA.js} +12 -12
- package/package.json +1 -1
- 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-T7AFLXGA.js +0 -2056
- package/dist/chunk-T7AFLXGA.js.map +0 -1
- package/dist/chunks-G3GK536Y.js +0 -10
- package/dist/pdf-ocr-JZKCEKWX.cjs +0 -14
- 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 → -S3PXW4LR.js.map} +0 -0
- /package/dist/{chunk-LP5RTYFM.js.map → chunk-2TEH7CC5.js.map} +0 -0
- /package/dist/{chunk-GWFX5ZD7.js.map → chunk-36FAAEN4.js.map} +0 -0
- /package/dist/{chunk-IEJR23OA.js.map → chunk-5WZTB3OI.js.map} +0 -0
- /package/dist/{chunk-2B45S7FM.js.map → chunk-ABGDU4KM.js.map} +0 -0
- /package/dist/{chunk-AGBFCFQY.js.map → chunk-D6S64TR6.js.map} +0 -0
- /package/dist/{chunk-F3FEIYI7.js.map → chunk-FSEJ6OJO.js.map} +0 -0
- /package/dist/{chunk-27QN27I3.js.map → chunk-H3HDX4JN.js.map} +0 -0
- /package/dist/{chunk-OSLJ3A6D.js.map → chunk-IRIASTA5.js.map} +0 -0
- /package/dist/{chunk-6JXMIX3G.js.map → chunk-K3KLZZC6.js.map} +0 -0
- /package/dist/{chunk-ILDTN72S.js.map → chunk-Q4DUG3WU.js.map} +0 -0
- /package/dist/{chunk-H5JWPWQP.js.map → chunk-Q7CQWGLN.js.map} +0 -0
- /package/dist/{chunk-L3CEPNQS.js.map → chunk-QFXJAYA4.js.map} +0 -0
- /package/dist/{chunk-TA2LEFOF.js.map → chunk-R6NY4ZIS.js.map} +0 -0
- /package/dist/{chunk-5VHLKNCE.js.map → chunk-SA2ECW7B.js.map} +0 -0
- /package/dist/{chunk-3MCFJIN2.js.map → chunk-SHC6FGRY.js.map} +0 -0
- /package/dist/{chunk-3ABHPKYA.js.map → chunk-VRXVNVLQ.js.map} +0 -0
- /package/dist/{chunks-G3GK536Y.js.map → chunks-IZLHLZQD.js.map} +0 -0
- /package/dist/{formula-3T7HQA4O.js.map → formula-GL5HHTL5.js.map} +0 -0
- /package/dist/{formula-FWVAOSIK.js.map → formula-Z6EVYSSI.js.map} +0 -0
- /package/dist/{image-ocr-X3I2HUK2.js.map → image-ocr-SVA3XKWJ.js.map} +0 -0
- /package/dist/{image-ocr-2M7K3CE5.js.map → image-ocr-ZE2VGIC3.js.map} +0 -0
- /package/dist/{model-bundle-XXYZ6ELQ.js.map → model-bundle-3TTBPL3Q.js.map} +0 -0
- /package/dist/{models-APDSXJW7.js.map → models-6RICFYZZ.js.map} +0 -0
- /package/dist/{parser-T5R4YVZS.js.map → parser-54OYFBL2.js.map} +0 -0
- /package/dist/{parser-XKSOS6GY.js.map → parser-YKKSFY6Q.js.map} +0 -0
- /package/dist/{pdf-ocr-X62B7YDV.js.map → pdf-ocr-MFJQP4XG.js.map} +0 -0
- /package/dist/{pdf-ocr-YDJQEGDS.js.map → pdf-ocr-WWYZE5L7.js.map} +0 -0
- /package/dist/{profile-io-Y24GOHWM.js.map → profile-io-PDOYD237.js.map} +0 -0
- /package/dist/{rasterize-QCOBWHAG.js.map → rasterize-4ZSVJGDA.js.map} +0 -0
- /package/dist/{render-DHFQQCSV.js.map → render-XRCZI3CY.js.map} +0 -0
- /package/dist/{seal-UTHFQPLO.js.map → seal-CFYSOI4V.js.map} +0 -0
- /package/dist/{setup-UWX3KH3G.js.map → setup-K7GW2TZM.js.map} +0 -0
- /package/dist/{visual-DF2KSPEC.js.map → visual-AUPPOHEP.js.map} +0 -0
- /package/dist/{watch-4ZUTMC3Q.js.map → watch-MR27M6VA.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-5WZTB3OI.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-WOAVPYT3.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-K3KLZZC6.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-36FAAEN4.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-H3HDX4JN.js";
|
|
79
99
|
|
|
80
100
|
// src/table/visual.ts
|
|
81
101
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -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);
|
|
@@ -2897,2315 +1956,223 @@ function extractFromBrokenZip(buffer) {
|
|
|
2897
1956
|
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
2898
1957
|
pos = fileStart + compSize;
|
|
2899
1958
|
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
2900
|
-
try {
|
|
2901
|
-
let content;
|
|
2902
|
-
if (method === 0) {
|
|
2903
|
-
content = new TextDecoder().decode(fileData);
|
|
2904
|
-
} else if (method === 8) {
|
|
2905
|
-
const decompressed =
|
|
2906
|
-
content = new TextDecoder().decode(decompressed);
|
|
2907
|
-
} else {
|
|
2908
|
-
continue;
|
|
2909
|
-
}
|
|
2910
|
-
totalDecompressed += content.length * 2;
|
|
2911
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2912
|
-
sectionNum++;
|
|
2913
|
-
const secBlocks = parseSectionXml(content, void 0, warnings, sectionNum, shared);
|
|
2914
|
-
for (const b of secBlocks) b.pageNumber = sectionNum;
|
|
2915
|
-
blocks.push(...secBlocks);
|
|
2916
|
-
} catch {
|
|
2917
|
-
continue;
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
if (blocks.length === 0) throw new KordocError("\uC190\uC0C1\uB41C HWPX\uC5D0\uC11C \uC139\uC158 \uB370\uC774\uD130\uB97C \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2921
|
-
applyPageText(blocks, shared);
|
|
2922
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2923
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2924
|
-
}
|
|
2925
|
-
async function readKordocLayout(zip) {
|
|
2926
|
-
const file = zip.file("Contents/content.hpf") ?? zip.file("content.hpf");
|
|
2927
|
-
if (!file) return null;
|
|
2928
|
-
try {
|
|
2929
|
-
const xml = await file.async("text");
|
|
2930
|
-
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2931
|
-
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2932
|
-
} catch {
|
|
2933
|
-
return null;
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
async function resolveSectionPaths(zip) {
|
|
2937
|
-
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
2938
|
-
for (const mp of manifestPaths) {
|
|
2939
|
-
const mpLower = mp.toLowerCase();
|
|
2940
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2941
|
-
if (!file) continue;
|
|
2942
|
-
const xml = await file.async("text");
|
|
2943
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
2944
|
-
if (paths.length > 0) return paths;
|
|
2945
|
-
}
|
|
2946
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2947
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2948
|
-
}
|
|
2949
|
-
function parseSectionPathsFromManifest(xml) {
|
|
2950
|
-
const parser = createXmlParser();
|
|
2951
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2952
|
-
const items = doc.getElementsByTagName("opf:item");
|
|
2953
|
-
const spine = doc.getElementsByTagName("opf:itemref");
|
|
2954
|
-
const idToHref = /* @__PURE__ */ new Map();
|
|
2955
|
-
for (let i = 0; i < items.length; i++) {
|
|
2956
|
-
const item = items[i];
|
|
2957
|
-
const id = item.getAttribute("id") || "";
|
|
2958
|
-
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
2959
|
-
if (id && href) idToHref.set(id, href);
|
|
2960
|
-
}
|
|
2961
|
-
if (spine.length > 0) {
|
|
2962
|
-
const ordered = [];
|
|
2963
|
-
for (let i = 0; i < spine.length; i++) {
|
|
2964
|
-
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
2965
|
-
if (href) ordered.push(href);
|
|
2966
|
-
}
|
|
2967
|
-
if (ordered.length > 0) return ordered;
|
|
2968
|
-
}
|
|
2969
|
-
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
// src/hwpx/metadata.ts
|
|
2973
|
-
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
2974
|
-
try {
|
|
2975
|
-
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
2976
|
-
for (const mp of metaPaths) {
|
|
2977
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2978
|
-
if (!file) continue;
|
|
2979
|
-
const xml = await file.async("text");
|
|
2980
|
-
if (decompressed) {
|
|
2981
|
-
decompressed.total += xml.length * 2;
|
|
2982
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2983
|
-
}
|
|
2984
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
2985
|
-
if (metadata.title || metadata.author) return;
|
|
2986
|
-
}
|
|
2987
|
-
} catch {
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
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;
|
|
1959
|
+
try {
|
|
1960
|
+
let content;
|
|
1961
|
+
if (method === 0) {
|
|
1962
|
+
content = new TextDecoder().decode(fileData);
|
|
1963
|
+
} else if (method === 8) {
|
|
1964
|
+
const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1965
|
+
content = new TextDecoder().decode(decompressed);
|
|
1966
|
+
} else {
|
|
1967
|
+
continue;
|
|
4889
1968
|
}
|
|
4890
|
-
|
|
4891
|
-
if (
|
|
1969
|
+
totalDecompressed += content.length * 2;
|
|
1970
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1971
|
+
sectionNum++;
|
|
1972
|
+
const secBlocks = parseSectionXml(content, void 0, warnings, sectionNum, shared);
|
|
1973
|
+
for (const b of secBlocks) b.pageNumber = sectionNum;
|
|
1974
|
+
blocks.push(...secBlocks);
|
|
1975
|
+
} catch {
|
|
1976
|
+
continue;
|
|
4892
1977
|
}
|
|
4893
1978
|
}
|
|
1979
|
+
if (blocks.length === 0) throw new KordocError("\uC190\uC0C1\uB41C HWPX\uC5D0\uC11C \uC139\uC158 \uB370\uC774\uD130\uB97C \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1980
|
+
applyPageText(blocks, shared);
|
|
1981
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1982
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
4894
1983
|
}
|
|
4895
|
-
function
|
|
4896
|
-
const
|
|
4897
|
-
|
|
1984
|
+
async function readKordocLayout(zip) {
|
|
1985
|
+
const file = zip.file("Contents/content.hpf") ?? zip.file("content.hpf");
|
|
1986
|
+
if (!file) return null;
|
|
1987
|
+
try {
|
|
1988
|
+
const xml = await file.async("text");
|
|
1989
|
+
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
1990
|
+
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
1991
|
+
} catch {
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
4898
1994
|
}
|
|
4899
|
-
function
|
|
4900
|
-
|
|
4901
|
-
for (
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
1995
|
+
async function resolveSectionPaths(zip) {
|
|
1996
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1997
|
+
for (const mp of manifestPaths) {
|
|
1998
|
+
const mpLower = mp.toLowerCase();
|
|
1999
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2000
|
+
if (!file) continue;
|
|
2001
|
+
const xml = await file.async("text");
|
|
2002
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
2003
|
+
if (paths.length > 0) return paths;
|
|
4905
2004
|
}
|
|
4906
|
-
|
|
2005
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2006
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
4907
2007
|
}
|
|
4908
|
-
function
|
|
4909
|
-
const
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
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
|
-
}
|
|
2008
|
+
function parseSectionPathsFromManifest(xml) {
|
|
2009
|
+
const parser = createXmlParser();
|
|
2010
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2011
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
2012
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
2013
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
2014
|
+
for (let i = 0; i < items.length; i++) {
|
|
2015
|
+
const item = items[i];
|
|
2016
|
+
const id = item.getAttribute("id") || "";
|
|
2017
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
2018
|
+
if (id && href) idToHref.set(id, href);
|
|
4998
2019
|
}
|
|
4999
|
-
if (
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
const capText = blocksPlainText(capBlocks, " ");
|
|
5005
|
-
if (capText) caption = capText;
|
|
5006
|
-
break;
|
|
2020
|
+
if (spine.length > 0) {
|
|
2021
|
+
const ordered = [];
|
|
2022
|
+
for (let i = 0; i < spine.length; i++) {
|
|
2023
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
2024
|
+
if (href) ordered.push(href);
|
|
5007
2025
|
}
|
|
2026
|
+
if (ordered.length > 0) return ordered;
|
|
5008
2027
|
}
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
2028
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// src/hwpx/metadata.ts
|
|
2032
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
2033
|
+
try {
|
|
2034
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
2035
|
+
for (const mp of metaPaths) {
|
|
2036
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2037
|
+
if (!file) continue;
|
|
2038
|
+
const xml = await file.async("text");
|
|
2039
|
+
if (decompressed) {
|
|
2040
|
+
decompressed.total += xml.length * 2;
|
|
2041
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
5021
2042
|
}
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
continue;
|
|
2043
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
2044
|
+
if (metadata.title || metadata.author) return;
|
|
5025
2045
|
}
|
|
5026
|
-
|
|
2046
|
+
} catch {
|
|
5027
2047
|
}
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
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;
|
|
2048
|
+
}
|
|
2049
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
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;
|
|
5079
2059
|
}
|
|
5080
|
-
parts.push(t);
|
|
5081
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);
|
|
5082
2071
|
}
|
|
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
2072
|
}
|
|
5088
|
-
function
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
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
|
-
}
|
|
5119
|
-
}
|
|
5120
|
-
}
|
|
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");
|
|
5121
2079
|
}
|
|
5122
|
-
|
|
2080
|
+
const metadata = {};
|
|
2081
|
+
await extractHwpxMetadata(zip, metadata);
|
|
2082
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2083
|
+
metadata.pageCount = sectionPaths.length;
|
|
2084
|
+
return metadata;
|
|
5123
2085
|
}
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
let
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
break;
|
|
5134
|
-
}
|
|
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);
|
|
5135
2095
|
}
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
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 {
|
|
@@ -17607,7 +14574,7 @@ function readHeader(reader) {
|
|
|
17607
14574
|
|
|
17608
14575
|
// src/hwp3/crypto.ts
|
|
17609
14576
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
17610
|
-
import { inflateRawSync as
|
|
14577
|
+
import { inflateRawSync as inflateRawSync3 } from "zlib";
|
|
17611
14578
|
var FIXED_HEADER_BYTES = SIGNATURE_LEN + DOC_INFO_SIZE + DOC_SUMMARY_SIZE;
|
|
17612
14579
|
var PASSWORD_FLAG_OFFSET = SIGNATURE_LEN + 96;
|
|
17613
14580
|
var COMPRESSION_FLAG_OFFSET = SIGNATURE_LEN + 124;
|
|
@@ -17667,7 +14634,7 @@ function decryptHwp3Document(input, password) {
|
|
|
17667
14634
|
const decrypted = desEcbDecrypt(aligned, deriveDesKey(password));
|
|
17668
14635
|
let inflated;
|
|
17669
14636
|
try {
|
|
17670
|
-
inflated =
|
|
14637
|
+
inflated = inflateRawSync3(decrypted, { maxOutputLength: MAX_DECOMPRESSED_BYTES });
|
|
17671
14638
|
} catch (err) {
|
|
17672
14639
|
if (err?.code === "ERR_BUFFER_TOO_LARGE") {
|
|
17673
14640
|
throw new KordocError(
|
|
@@ -17687,7 +14654,7 @@ function decryptHwp3Document(input, password) {
|
|
|
17687
14654
|
}
|
|
17688
14655
|
|
|
17689
14656
|
// src/hwp3/parser.ts
|
|
17690
|
-
var
|
|
14657
|
+
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
17691
14658
|
var PIC_TYPE_DRAWING = 3;
|
|
17692
14659
|
var PARA_SHAPE_SIZE = 187;
|
|
17693
14660
|
var LINE_INFO_SIZE = 14;
|
|
@@ -17730,10 +14697,10 @@ function parseHwp3Document(buffer, options) {
|
|
|
17730
14697
|
const warnings = [];
|
|
17731
14698
|
if (header.compressed !== 0) {
|
|
17732
14699
|
try {
|
|
17733
|
-
body =
|
|
14700
|
+
body = inflateRawSync4(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE2 });
|
|
17734
14701
|
} catch (err) {
|
|
17735
14702
|
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(${
|
|
14703
|
+
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
14704
|
}
|
|
17738
14705
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
17739
14706
|
throw new KordocError(`HWP3 \uC555\uCD95 \uD574\uC81C \uC2E4\uD328: ${msg2}`);
|
|
@@ -17745,7 +14712,7 @@ function parseHwp3Document(buffer, options) {
|
|
|
17745
14712
|
const ctx = { paragraphs: [], warnings };
|
|
17746
14713
|
try {
|
|
17747
14714
|
skipFontFacesAndStyles(bodyReader);
|
|
17748
|
-
|
|
14715
|
+
parseParagraphList(bodyReader, ctx);
|
|
17749
14716
|
} catch (err) {
|
|
17750
14717
|
warnings.push({
|
|
17751
14718
|
code: "PARTIAL_PARSE",
|
|
@@ -17777,7 +14744,7 @@ function skipFontFacesAndStyles(reader) {
|
|
|
17777
14744
|
const nStyles = reader.readU16();
|
|
17778
14745
|
reader.skip(nStyles * STYLE_RECORD_SIZE);
|
|
17779
14746
|
}
|
|
17780
|
-
function
|
|
14747
|
+
function parseParagraphList(reader, ctx) {
|
|
17781
14748
|
for (; ; ) {
|
|
17782
14749
|
if (reader.eof()) return;
|
|
17783
14750
|
const followPrev = reader.readU8();
|
|
@@ -17858,17 +14825,17 @@ function parseCharStream(reader, charCount, ctx) {
|
|
|
17858
14825
|
break;
|
|
17859
14826
|
case 15: {
|
|
17860
14827
|
reader.skip(8);
|
|
17861
|
-
|
|
14828
|
+
parseParagraphList(reader, ctx);
|
|
17862
14829
|
break;
|
|
17863
14830
|
}
|
|
17864
14831
|
case 16: {
|
|
17865
14832
|
reader.skip(10);
|
|
17866
|
-
|
|
14833
|
+
parseParagraphList(reader, ctx);
|
|
17867
14834
|
break;
|
|
17868
14835
|
}
|
|
17869
14836
|
case 17: {
|
|
17870
14837
|
reader.skip(14);
|
|
17871
|
-
|
|
14838
|
+
parseParagraphList(reader, ctx);
|
|
17872
14839
|
break;
|
|
17873
14840
|
}
|
|
17874
14841
|
case 5:
|
|
@@ -17910,9 +14877,9 @@ function parseTableLike(reader, ctx) {
|
|
|
17910
14877
|
}
|
|
17911
14878
|
reader.skip(27 * cellCount);
|
|
17912
14879
|
for (let i = 0; i < cellCount; i++) {
|
|
17913
|
-
|
|
14880
|
+
parseParagraphList(reader, ctx);
|
|
17914
14881
|
}
|
|
17915
|
-
|
|
14882
|
+
parseParagraphList(reader, ctx);
|
|
17916
14883
|
return "";
|
|
17917
14884
|
}
|
|
17918
14885
|
function parsePicture(reader, ctx) {
|
|
@@ -17921,7 +14888,7 @@ function parsePicture(reader, ctx) {
|
|
|
17921
14888
|
let ext = null;
|
|
17922
14889
|
if (nExt > 0 && nExt < 100 * 1024 * 1024) ext = reader.readBytes(nExt);
|
|
17923
14890
|
if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx);
|
|
17924
|
-
|
|
14891
|
+
parseParagraphList(reader, ctx);
|
|
17925
14892
|
}
|
|
17926
14893
|
function parseDrawingObject(ext, ctx) {
|
|
17927
14894
|
let lists;
|
|
@@ -17933,7 +14900,7 @@ function parseDrawingObject(ext, ctx) {
|
|
|
17933
14900
|
}
|
|
17934
14901
|
for (const list of lists) {
|
|
17935
14902
|
try {
|
|
17936
|
-
|
|
14903
|
+
parseParagraphList(new Reader(list), ctx);
|
|
17937
14904
|
} catch (err) {
|
|
17938
14905
|
pushDrawingWarning(ctx, `HWP3 \uAE00\uC0C1\uC790 \uBB38\uB2E8 \uB9AC\uC2A4\uD2B8 \uD30C\uC2F1 \uC2E4\uD328: ${errText(err)}`);
|
|
17939
14906
|
}
|
|
@@ -17965,7 +14932,7 @@ function isDistributionSentinel(markdown) {
|
|
|
17965
14932
|
import JSZip3 from "jszip";
|
|
17966
14933
|
import { DOMParser } from "@xmldom/xmldom";
|
|
17967
14934
|
var MAX_SHEETS = 100;
|
|
17968
|
-
var
|
|
14935
|
+
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
17969
14936
|
var MAX_ROWS2 = 1e4;
|
|
17970
14937
|
var MAX_COLS2 = 200;
|
|
17971
14938
|
function cleanNumericValue(raw) {
|
|
@@ -18246,7 +15213,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex, keep
|
|
|
18246
15213
|
return blocks;
|
|
18247
15214
|
}
|
|
18248
15215
|
async function parseXlsxDocument(buffer, options) {
|
|
18249
|
-
precheckZipSize(buffer,
|
|
15216
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
18250
15217
|
const zip = await JSZip3.loadAsync(buffer);
|
|
18251
15218
|
const warnings = [];
|
|
18252
15219
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
@@ -18374,11 +15341,11 @@ var OP_SHRFMLA = 1212;
|
|
|
18374
15341
|
var OP_ARRAY = 545;
|
|
18375
15342
|
var DT_GLOBALS = 5;
|
|
18376
15343
|
var DT_WORKSHEET = 16;
|
|
18377
|
-
var
|
|
18378
|
-
function
|
|
15344
|
+
var MAX_RECORDS = 1e6;
|
|
15345
|
+
function readRecords(stream) {
|
|
18379
15346
|
const out = [];
|
|
18380
15347
|
let offset = 0;
|
|
18381
|
-
while (offset + 4 <= stream.length && out.length <
|
|
15348
|
+
while (offset + 4 <= stream.length && out.length < MAX_RECORDS) {
|
|
18382
15349
|
const recOffset = offset;
|
|
18383
15350
|
const opcode = stream.readUInt16LE(offset);
|
|
18384
15351
|
const length = stream.readUInt16LE(offset + 2);
|
|
@@ -18955,7 +15922,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18955
15922
|
if (!wb) {
|
|
18956
15923
|
throw new KordocError("XLS: Workbook \uC2A4\uD2B8\uB9BC\uC774 \uC5C6\uC74C (BIFF5 \uB610\uB294 \uBE44\uD45C\uC900 \uD30C\uC77C)");
|
|
18957
15924
|
}
|
|
18958
|
-
const records =
|
|
15925
|
+
const records = readRecords(wb);
|
|
18959
15926
|
if (records.length === 0) {
|
|
18960
15927
|
throw new KordocError("XLS: \uC2DC\uADF8\uB2C8\uCC98 \uB808\uCF54\uB4DC\uAC00 \uC5C6\uC74C (Workbook \uC2A4\uD2B8\uB9BC \uC190\uC0C1)");
|
|
18961
15928
|
}
|
|
@@ -19391,7 +16358,7 @@ function isDisplayMath(el) {
|
|
|
19391
16358
|
}
|
|
19392
16359
|
|
|
19393
16360
|
// src/docx/parser.ts
|
|
19394
|
-
var
|
|
16361
|
+
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
19395
16362
|
function matchesLocal(el, localName3) {
|
|
19396
16363
|
return el.localName === localName3 || (el.tagName?.endsWith(`:${localName3}`) ?? false);
|
|
19397
16364
|
}
|
|
@@ -19688,7 +16655,7 @@ function collectInline(p, footnotes, rels) {
|
|
|
19688
16655
|
const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
|
|
19689
16656
|
return { text, bold, italic, footnoteText };
|
|
19690
16657
|
}
|
|
19691
|
-
function
|
|
16658
|
+
function parseParagraph(p, styles, numbering, footnotes, rels) {
|
|
19692
16659
|
const pPrEls = getChildElements(p, "pPr");
|
|
19693
16660
|
let styleId = "";
|
|
19694
16661
|
let numId = "";
|
|
@@ -19808,10 +16775,10 @@ function collectCellText(tc2, styles, numbering, footnotes, rels, depth) {
|
|
|
19808
16775
|
if (depth > 20) return parts;
|
|
19809
16776
|
for (const el of effectiveChildElements(tc2)) {
|
|
19810
16777
|
if (matchesLocal(el, "p")) {
|
|
19811
|
-
const block =
|
|
16778
|
+
const block = parseParagraph(el, styles, numbering, footnotes, rels);
|
|
19812
16779
|
if (block?.text) parts.push(block.text);
|
|
19813
16780
|
for (const tp of collectTextboxParagraphs(el)) {
|
|
19814
|
-
const tb =
|
|
16781
|
+
const tb = parseParagraph(tp, styles, numbering, footnotes, rels);
|
|
19815
16782
|
if (tb?.text) parts.push(tb.text);
|
|
19816
16783
|
}
|
|
19817
16784
|
} else if (matchesLocal(el, "tbl")) {
|
|
@@ -19905,7 +16872,7 @@ function emitParagraphImages(p, imageMap, linked, out) {
|
|
|
19905
16872
|
}
|
|
19906
16873
|
}
|
|
19907
16874
|
async function parseDocxDocument(buffer, options) {
|
|
19908
|
-
precheckZipSize(buffer,
|
|
16875
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
19909
16876
|
const zip = await JSZip4.loadAsync(buffer);
|
|
19910
16877
|
const warnings = [];
|
|
19911
16878
|
const docFile = zip.file("word/document.xml");
|
|
@@ -19967,11 +16934,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19967
16934
|
for (const el of topLevel) {
|
|
19968
16935
|
const localName3 = el.localName ?? el.tagName?.split(":").pop();
|
|
19969
16936
|
if (localName3 === "p") {
|
|
19970
|
-
const block =
|
|
16937
|
+
const block = parseParagraph(el, styles, numbering, footnotes, rels);
|
|
19971
16938
|
if (block) blocks.push(block);
|
|
19972
16939
|
if (imageMap.size > 0) emitParagraphImages(el, imageMap, linkedImages, blocks);
|
|
19973
16940
|
for (const tp of collectTextboxParagraphs(el)) {
|
|
19974
|
-
const tb =
|
|
16941
|
+
const tb = parseParagraph(tp, styles, numbering, footnotes, rels);
|
|
19975
16942
|
if (tb) blocks.push(tb);
|
|
19976
16943
|
if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
|
|
19977
16944
|
}
|
|
@@ -20067,7 +17034,7 @@ function parseHwpmlDocument(buffer, options) {
|
|
|
20067
17034
|
if (localName(el) !== "SECTION") continue;
|
|
20068
17035
|
sectionIdx++;
|
|
20069
17036
|
if (pageFilter && !pageFilter.has(sectionIdx)) continue;
|
|
20070
|
-
|
|
17037
|
+
parseSection(el, blocks, paraShapeMap, sectionIdx, warnings, options?.keepTrailingEmptyCols ?? false);
|
|
20071
17038
|
}
|
|
20072
17039
|
const outline = blocks.filter((b) => b.type === "heading" && b.text).map((b) => ({ level: b.level ?? 1, text: b.text, pageNumber: b.pageNumber }));
|
|
20073
17040
|
const markdown = blocksToMarkdown(blocks);
|
|
@@ -20103,7 +17070,7 @@ function buildParaShapeMap(root) {
|
|
|
20103
17070
|
}
|
|
20104
17071
|
return map;
|
|
20105
17072
|
}
|
|
20106
|
-
function
|
|
17073
|
+
function parseSection(section, blocks, paraShapeMap, sectionNum, warnings, keep) {
|
|
20107
17074
|
walkContent(section, blocks, paraShapeMap, sectionNum, warnings, false, keep);
|
|
20108
17075
|
}
|
|
20109
17076
|
function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth = 0) {
|
|
@@ -20118,7 +17085,7 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
|
|
|
20118
17085
|
}
|
|
20119
17086
|
if (tag === "P") {
|
|
20120
17087
|
if (!inHeaderFooter) {
|
|
20121
|
-
|
|
17088
|
+
parseParagraph2(el, blocks, paraShapeMap, sectionNum);
|
|
20122
17089
|
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
20123
17090
|
}
|
|
20124
17091
|
continue;
|
|
@@ -20151,7 +17118,7 @@ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, keep, d
|
|
|
20151
17118
|
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep, depth + 1);
|
|
20152
17119
|
}
|
|
20153
17120
|
}
|
|
20154
|
-
function
|
|
17121
|
+
function parseParagraph2(el, blocks, paraShapeMap, sectionNum) {
|
|
20155
17122
|
const paraShapeId = el.getAttribute("ParaShape") ?? "";
|
|
20156
17123
|
const shapeInfo = paraShapeMap.get(paraShapeId);
|
|
20157
17124
|
const text = extractParagraphText(el);
|
|
@@ -22016,6 +18983,12 @@ function incompatibleGongmunWarnings(opts) {
|
|
|
22016
18983
|
}
|
|
22017
18984
|
return warns;
|
|
22018
18985
|
}
|
|
18986
|
+
function hexColorOption(name, value) {
|
|
18987
|
+
if (value === void 0) return void 0;
|
|
18988
|
+
const v = value.trim();
|
|
18989
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(v)) throw new KordocError(`${name} must be a #RRGGBB color (got "${value}")`);
|
|
18990
|
+
return v.toUpperCase();
|
|
18991
|
+
}
|
|
22019
18992
|
function resolveGongmun(opts) {
|
|
22020
18993
|
validateGongmunOptions(opts);
|
|
22021
18994
|
const preset = normalizeGongmunPreset(opts.preset);
|
|
@@ -22060,6 +19033,9 @@ function resolveGongmun(opts) {
|
|
|
22060
19033
|
// h2 말머리 — 실측: 보고서 양식 □ 대항목(QA-2), 공고문 아라비아("1. 사업개요", 바이오헬스 실측)
|
|
22061
19034
|
// v5 라운드 3: 보고서·계획서 기본 band(띠 표) — 서울 plan 7/19·교육청 7/18 실측, 실무자 요청
|
|
22062
19035
|
h2Marker: opts.h2Marker ?? (preset === "report" || preset === "plan" ? "band" : preset === "notice" ? "number" : "none"),
|
|
19036
|
+
// 띠 제목 색 — 실측 최다 #003366/흰 글자(계획서 띠 표 14개). 교육청형 밝은 띠는 옵션으로
|
|
19037
|
+
bandColor: hexColorOption("bandColor", opts.bandColor) ?? "#003366",
|
|
19038
|
+
bandTextColor: hexColorOption("bandTextColor", opts.bandTextColor) ?? "#FFFFFF",
|
|
22063
19039
|
// 2단계 부호 — 실결재 기안문·공고문 ㅇ 지배(60건 중 ㅇ134:○5), 보고서 양식 계열 ○
|
|
22064
19040
|
// v5: 서울 실결재 ㅇ(이응) 지배 — 보고서·계획서·통지·보도자료 ㅇ, 중앙부처 개조식 양식만 ○
|
|
22065
19041
|
bullet2: opts.bullet2 ?? (preset === "gaejosik" ? "\u25CB" : "\u3147"),
|
|
@@ -26670,7 +23646,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
26670
23646
|
const st = scheme.chapter;
|
|
26671
23647
|
if (chapterStyle === "band") {
|
|
26672
23648
|
const before2 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
|
|
26673
|
-
const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain(node.text), frame, { ...CHAPTER_BAND_DEFAULT, titleFont: st.font, titlePt: st.pt }, before2);
|
|
23649
|
+
const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g.bandColor, textColor: g.bandTextColor, titleFont: st.font, titlePt: st.pt }, before2);
|
|
26674
23650
|
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
23651
|
return band.xml;
|
|
26676
23652
|
}
|
|
@@ -29039,7 +26015,7 @@ async function resolveHeaderEntryName(zip) {
|
|
|
29039
26015
|
|
|
29040
26016
|
// src/roundtrip/hwp5-patch.ts
|
|
29041
26017
|
import { deflateRawSync } from "zlib";
|
|
29042
|
-
import { createRequire
|
|
26018
|
+
import { createRequire } from "module";
|
|
29043
26019
|
|
|
29044
26020
|
// src/roundtrip/ole-surgeon.ts
|
|
29045
26021
|
var SECTOR = 512;
|
|
@@ -29369,21 +26345,21 @@ var Surgeon = class {
|
|
|
29369
26345
|
};
|
|
29370
26346
|
|
|
29371
26347
|
// src/roundtrip/hwp5-patch.ts
|
|
29372
|
-
var
|
|
29373
|
-
var
|
|
29374
|
-
var
|
|
29375
|
-
function
|
|
26348
|
+
var require2 = createRequire(import.meta.url);
|
|
26349
|
+
var CFB = require2("cfb");
|
|
26350
|
+
var TAG_PARA_LINE_SEG = 69;
|
|
26351
|
+
function cid(s) {
|
|
29376
26352
|
return (s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3)) >>> 0;
|
|
29377
26353
|
}
|
|
29378
|
-
var
|
|
29379
|
-
var
|
|
29380
|
-
function
|
|
26354
|
+
var CTRL_TBL = cid("tbl ");
|
|
26355
|
+
var CTRL_GSO = cid("gso ");
|
|
26356
|
+
function swap32(id) {
|
|
29381
26357
|
return ((id & 255) << 24 | (id >>> 8 & 255) << 16 | (id >>> 16 & 255) << 8 | id >>> 24 & 255) >>> 0;
|
|
29382
26358
|
}
|
|
29383
26359
|
function isCtrl(rec, id) {
|
|
29384
26360
|
if (rec.tagId !== TAG_CTRL_HEADER || rec.data.length < 4) return false;
|
|
29385
26361
|
const raw = rec.data.readUInt32LE(0);
|
|
29386
|
-
return raw === id ||
|
|
26362
|
+
return raw === id || swap32(raw) === id;
|
|
29387
26363
|
}
|
|
29388
26364
|
function readRecordsStrict(stream) {
|
|
29389
26365
|
const recs = [];
|
|
@@ -29452,13 +26428,13 @@ function scanSection(stream, sectionIndex, compressed) {
|
|
|
29452
26428
|
textIdx = textIdx === -1 ? j : -2;
|
|
29453
26429
|
appendParaText(state, records[j].data);
|
|
29454
26430
|
} else if (t === TAG_CHAR_SHAPE && charShapeIdx === -1) charShapeIdx = j;
|
|
29455
|
-
else if (t ===
|
|
26431
|
+
else if (t === TAG_PARA_LINE_SEG && lineSegIdx === -1) lineSegIdx = j;
|
|
29456
26432
|
}
|
|
29457
26433
|
let ctrlSeen = false, nonGso = false;
|
|
29458
26434
|
for (let a = parent[i]; a >= 0; a = parent[a]) {
|
|
29459
26435
|
if (records[a].tagId === TAG_CTRL_HEADER) {
|
|
29460
26436
|
ctrlSeen = true;
|
|
29461
|
-
if (!isCtrl(records[a],
|
|
26437
|
+
if (!isCtrl(records[a], CTRL_GSO)) nonGso = true;
|
|
29462
26438
|
}
|
|
29463
26439
|
}
|
|
29464
26440
|
const kind = !ctrlSeen || !nonGso ? "body" : "other";
|
|
@@ -29479,7 +26455,7 @@ function scanSection(stream, sectionIndex, compressed) {
|
|
|
29479
26455
|
}
|
|
29480
26456
|
const tables = [];
|
|
29481
26457
|
for (let i = 0; i < records.length; i++) {
|
|
29482
|
-
if (!isCtrl(records[i],
|
|
26458
|
+
if (!isCtrl(records[i], CTRL_TBL) || ancestorCtrl(i, CTRL_TBL)) continue;
|
|
29483
26459
|
const ctrlLevel = records[i].level;
|
|
29484
26460
|
let rows = 0, cols = 0, tableIdx = -1;
|
|
29485
26461
|
for (let j2 = i + 1; j2 < records.length && records[j2].level > ctrlLevel; j2++) {
|
|
@@ -29530,11 +26506,11 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
29530
26506
|
const originalBuf = Buffer.from(original.buffer, original.byteOffset, original.byteLength);
|
|
29531
26507
|
let cfb;
|
|
29532
26508
|
try {
|
|
29533
|
-
cfb =
|
|
26509
|
+
cfb = CFB.parse(originalBuf);
|
|
29534
26510
|
} catch (err) {
|
|
29535
26511
|
return fail(`CFB \uCEE8\uD14C\uC774\uB108 \uD30C\uC2F1 \uC2E4\uD328: ${msg(err)}`);
|
|
29536
26512
|
}
|
|
29537
|
-
const fhEntry =
|
|
26513
|
+
const fhEntry = CFB.find(cfb, "/FileHeader");
|
|
29538
26514
|
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
26515
|
let flags;
|
|
29540
26516
|
try {
|
|
@@ -29556,7 +26532,7 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
29556
26532
|
if (sectionPaths.length === 0) return fail("BodyText \uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
29557
26533
|
const scans = [];
|
|
29558
26534
|
for (let i = 0; i < sectionPaths.length; i++) {
|
|
29559
|
-
const entry =
|
|
26535
|
+
const entry = CFB.find(cfb, sectionPaths[i]);
|
|
29560
26536
|
if (!entry?.content) return fail(`\uC139\uC158 \uC2A4\uD2B8\uB9BC \uC77D\uAE30 \uC2E4\uD328: ${sectionPaths[i]}`);
|
|
29561
26537
|
let stream;
|
|
29562
26538
|
try {
|
|
@@ -30680,7 +27656,7 @@ async function dispatch(format, buffer, opts) {
|
|
|
30680
27656
|
}
|
|
30681
27657
|
async function parseImage(buffer, options) {
|
|
30682
27658
|
try {
|
|
30683
|
-
const { parseImageDocument } = await import("./image-ocr-
|
|
27659
|
+
const { parseImageDocument } = await import("./image-ocr-ZE2VGIC3.js");
|
|
30684
27660
|
const { blocks, warnings } = await parseImageDocument(buffer, options);
|
|
30685
27661
|
return {
|
|
30686
27662
|
success: true,
|
|
@@ -30739,7 +27715,7 @@ async function parseHwp(buffer, options) {
|
|
|
30739
27715
|
async function parsePdf(buffer, options) {
|
|
30740
27716
|
let parsePdfDocument;
|
|
30741
27717
|
try {
|
|
30742
|
-
const mod = await import("./parser-
|
|
27718
|
+
const mod = await import("./parser-54OYFBL2.js");
|
|
30743
27719
|
parsePdfDocument = mod.parsePdfDocument;
|
|
30744
27720
|
} catch {
|
|
30745
27721
|
return {
|
|
@@ -30854,8 +27830,8 @@ async function extractTables(input, options = {}) {
|
|
|
30854
27830
|
}));
|
|
30855
27831
|
if (out.length === 0) return out;
|
|
30856
27832
|
const format = detectFormat(buffer);
|
|
30857
|
-
if (format !== "hwpx") {
|
|
30858
|
-
for (const t of out) t.warnings.push(
|
|
27833
|
+
if (format !== "hwpx" && format !== "hwp") {
|
|
27834
|
+
for (const t of out) t.warnings.push(`${format} \uC740 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 region\xB7crop \uC5C6\uC74C`);
|
|
30859
27835
|
return out;
|
|
30860
27836
|
}
|
|
30861
27837
|
let scene;
|
|
@@ -30896,9 +27872,7 @@ async function extractTables(input, options = {}) {
|
|
|
30896
27872
|
}
|
|
30897
27873
|
|
|
30898
27874
|
export {
|
|
30899
|
-
detectImageMime,
|
|
30900
27875
|
extractHwpxMetadataOnly,
|
|
30901
|
-
extractHwp5MetadataOnly,
|
|
30902
27876
|
formatFillValue,
|
|
30903
27877
|
ValueCursor,
|
|
30904
27878
|
fillWithUniqueGuard,
|
|
@@ -30951,4 +27925,4 @@ export {
|
|
|
30951
27925
|
parseHwpml,
|
|
30952
27926
|
fillForm
|
|
30953
27927
|
};
|
|
30954
|
-
//# sourceMappingURL=chunk-
|
|
27928
|
+
//# sourceMappingURL=chunk-F4DZU7VP.js.map
|