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.
Files changed (119) hide show
  1. package/README.md +20 -2
  2. package/dist/{-UZDRUWTX.js → -S3PXW4LR.js} +13 -11
  3. package/dist/{chunk-LP5RTYFM.js → chunk-2TEH7CC5.js} +2 -2
  4. package/dist/{chunk-GWFX5ZD7.js → chunk-36FAAEN4.js} +2 -2
  5. package/dist/{chunk-IEJR23OA.js → chunk-5WZTB3OI.js} +3 -3
  6. package/dist/{chunk-OJV7HNGJ.cjs → chunk-7HOFM76G.cjs} +4 -4
  7. package/dist/{chunk-OJV7HNGJ.cjs.map → chunk-7HOFM76G.cjs.map} +1 -1
  8. package/dist/{chunk-2B45S7FM.js → chunk-ABGDU4KM.js} +2 -2
  9. package/dist/{chunk-AGBFCFQY.js → chunk-D6S64TR6.js} +2 -2
  10. package/dist/{chunk-2RREQZ44.js → chunk-F4DZU7VP.js} +286 -3312
  11. package/dist/chunk-F4DZU7VP.js.map +1 -0
  12. package/dist/{chunk-F3FEIYI7.js → chunk-FSEJ6OJO.js} +2 -2
  13. package/dist/{chunk-27QN27I3.js → chunk-H3HDX4JN.js} +2 -2
  14. package/dist/{chunk-OSLJ3A6D.js → chunk-IRIASTA5.js} +2 -2
  15. package/dist/{chunk-6JXMIX3G.js → chunk-K3KLZZC6.js} +3 -2
  16. package/dist/{chunk-CKMK42FV.cjs → chunk-KXCSR4XK.cjs} +7 -7
  17. package/dist/{chunk-CKMK42FV.cjs.map → chunk-KXCSR4XK.cjs.map} +1 -1
  18. package/dist/{chunk-S2VKZKUX.cjs → chunk-L5UQXOUL.cjs} +3 -3
  19. package/dist/{chunk-S2VKZKUX.cjs.map → chunk-L5UQXOUL.cjs.map} +1 -1
  20. package/dist/{chunk-TAYCQE6K.cjs → chunk-LBWJOJ2T.cjs} +2 -2
  21. package/dist/{chunk-TAYCQE6K.cjs.map → chunk-LBWJOJ2T.cjs.map} +1 -1
  22. package/dist/{chunk-ILDTN72S.js → chunk-Q4DUG3WU.js} +2 -2
  23. package/dist/{chunk-H5JWPWQP.js → chunk-Q7CQWGLN.js} +2 -2
  24. package/dist/{chunk-L3CEPNQS.js → chunk-QFXJAYA4.js} +2 -2
  25. package/dist/{chunk-6NLXGGNH.js → chunk-QJD3CLFG.js} +3 -1
  26. package/dist/chunk-QJD3CLFG.js.map +1 -0
  27. package/dist/{chunk-BQLZOQES.js → chunk-QOJTBOWX.js} +40 -6
  28. package/dist/chunk-QOJTBOWX.js.map +1 -0
  29. package/dist/{chunk-TA2LEFOF.js → chunk-R6NY4ZIS.js} +3 -3
  30. package/dist/{chunk-5VHLKNCE.js → chunk-SA2ECW7B.js} +2 -2
  31. package/dist/{chunk-3MCFJIN2.js → chunk-SHC6FGRY.js} +3 -3
  32. package/dist/{chunk-MCD45IHO.cjs → chunk-TS665MCV.cjs} +6 -6
  33. package/dist/{chunk-MCD45IHO.cjs.map → chunk-TS665MCV.cjs.map} +1 -1
  34. package/dist/{chunk-3ABHPKYA.js → chunk-VRXVNVLQ.js} +2 -2
  35. package/dist/chunk-WOAVPYT3.js +5770 -0
  36. package/dist/chunk-WOAVPYT3.js.map +1 -0
  37. package/dist/chunks-IZLHLZQD.js +10 -0
  38. package/dist/cli.js +35 -33
  39. package/dist/cli.js.map +1 -1
  40. package/dist/{formula-YALEMZMF.cjs → formula-DF6KHCIH.cjs} +9 -9
  41. package/dist/{formula-YALEMZMF.cjs.map → formula-DF6KHCIH.cjs.map} +1 -1
  42. package/dist/{formula-3T7HQA4O.js → formula-GL5HHTL5.js} +4 -4
  43. package/dist/{formula-FWVAOSIK.js → formula-Z6EVYSSI.js} +3 -3
  44. package/dist/{image-ocr-X3I2HUK2.js → image-ocr-SVA3XKWJ.js} +5 -5
  45. package/dist/{image-ocr-4EUZIF6M.cjs → image-ocr-X2D3G7GD.cjs} +11 -11
  46. package/dist/{image-ocr-4EUZIF6M.cjs.map → image-ocr-X2D3G7GD.cjs.map} +1 -1
  47. package/dist/{image-ocr-2M7K3CE5.js → image-ocr-ZE2VGIC3.js} +7 -7
  48. package/dist/index.cjs +1217 -559
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +54 -7
  51. package/dist/index.d.ts +54 -7
  52. package/dist/index.js +736 -78
  53. package/dist/index.js.map +1 -1
  54. package/dist/mcp.js +138 -69
  55. package/dist/mcp.js.map +1 -1
  56. package/dist/{model-bundle-XXYZ6ELQ.js → model-bundle-3TTBPL3Q.js} +5 -5
  57. package/dist/{models-APDSXJW7.js → models-6RICFYZZ.js} +5 -5
  58. package/dist/{parser-T5R4YVZS.js → parser-54OYFBL2.js} +6 -6
  59. package/dist/{parser-4KE2FR5F.cjs → parser-NMVZMF5W.cjs} +26 -26
  60. package/dist/{parser-4KE2FR5F.cjs.map → parser-NMVZMF5W.cjs.map} +1 -1
  61. package/dist/{parser-XKSOS6GY.js → parser-YKKSFY6Q.js} +6 -6
  62. package/dist/pdf-ocr-FFKINEB7.cjs +14 -0
  63. package/dist/{pdf-ocr-JZKCEKWX.cjs.map → pdf-ocr-FFKINEB7.cjs.map} +1 -1
  64. package/dist/pdf-ocr-MFJQP4XG.js +13 -0
  65. package/dist/pdf-ocr-WWYZE5L7.js +16 -0
  66. package/dist/{profile-io-Y24GOHWM.js → profile-io-PDOYD237.js} +3 -3
  67. package/dist/{rasterize-QCOBWHAG.js → rasterize-4ZSVJGDA.js} +3 -3
  68. package/dist/{render-DHFQQCSV.js → render-XRCZI3CY.js} +16 -6
  69. package/dist/seal-CFYSOI4V.js +10 -0
  70. package/dist/{setup-UWX3KH3G.js → setup-K7GW2TZM.js} +3 -3
  71. package/dist/visual-AUPPOHEP.js +19 -0
  72. package/dist/{watch-4ZUTMC3Q.js → watch-MR27M6VA.js} +12 -12
  73. package/package.json +1 -1
  74. package/dist/chunk-2RREQZ44.js.map +0 -1
  75. package/dist/chunk-6NLXGGNH.js.map +0 -1
  76. package/dist/chunk-BQLZOQES.js.map +0 -1
  77. package/dist/chunk-T7AFLXGA.js +0 -2056
  78. package/dist/chunk-T7AFLXGA.js.map +0 -1
  79. package/dist/chunks-G3GK536Y.js +0 -10
  80. package/dist/pdf-ocr-JZKCEKWX.cjs +0 -14
  81. package/dist/pdf-ocr-X62B7YDV.js +0 -13
  82. package/dist/pdf-ocr-YDJQEGDS.js +0 -16
  83. package/dist/seal-UTHFQPLO.js +0 -10
  84. package/dist/visual-DF2KSPEC.js +0 -19
  85. /package/dist/{-UZDRUWTX.js.map → -S3PXW4LR.js.map} +0 -0
  86. /package/dist/{chunk-LP5RTYFM.js.map → chunk-2TEH7CC5.js.map} +0 -0
  87. /package/dist/{chunk-GWFX5ZD7.js.map → chunk-36FAAEN4.js.map} +0 -0
  88. /package/dist/{chunk-IEJR23OA.js.map → chunk-5WZTB3OI.js.map} +0 -0
  89. /package/dist/{chunk-2B45S7FM.js.map → chunk-ABGDU4KM.js.map} +0 -0
  90. /package/dist/{chunk-AGBFCFQY.js.map → chunk-D6S64TR6.js.map} +0 -0
  91. /package/dist/{chunk-F3FEIYI7.js.map → chunk-FSEJ6OJO.js.map} +0 -0
  92. /package/dist/{chunk-27QN27I3.js.map → chunk-H3HDX4JN.js.map} +0 -0
  93. /package/dist/{chunk-OSLJ3A6D.js.map → chunk-IRIASTA5.js.map} +0 -0
  94. /package/dist/{chunk-6JXMIX3G.js.map → chunk-K3KLZZC6.js.map} +0 -0
  95. /package/dist/{chunk-ILDTN72S.js.map → chunk-Q4DUG3WU.js.map} +0 -0
  96. /package/dist/{chunk-H5JWPWQP.js.map → chunk-Q7CQWGLN.js.map} +0 -0
  97. /package/dist/{chunk-L3CEPNQS.js.map → chunk-QFXJAYA4.js.map} +0 -0
  98. /package/dist/{chunk-TA2LEFOF.js.map → chunk-R6NY4ZIS.js.map} +0 -0
  99. /package/dist/{chunk-5VHLKNCE.js.map → chunk-SA2ECW7B.js.map} +0 -0
  100. /package/dist/{chunk-3MCFJIN2.js.map → chunk-SHC6FGRY.js.map} +0 -0
  101. /package/dist/{chunk-3ABHPKYA.js.map → chunk-VRXVNVLQ.js.map} +0 -0
  102. /package/dist/{chunks-G3GK536Y.js.map → chunks-IZLHLZQD.js.map} +0 -0
  103. /package/dist/{formula-3T7HQA4O.js.map → formula-GL5HHTL5.js.map} +0 -0
  104. /package/dist/{formula-FWVAOSIK.js.map → formula-Z6EVYSSI.js.map} +0 -0
  105. /package/dist/{image-ocr-X3I2HUK2.js.map → image-ocr-SVA3XKWJ.js.map} +0 -0
  106. /package/dist/{image-ocr-2M7K3CE5.js.map → image-ocr-ZE2VGIC3.js.map} +0 -0
  107. /package/dist/{model-bundle-XXYZ6ELQ.js.map → model-bundle-3TTBPL3Q.js.map} +0 -0
  108. /package/dist/{models-APDSXJW7.js.map → models-6RICFYZZ.js.map} +0 -0
  109. /package/dist/{parser-T5R4YVZS.js.map → parser-54OYFBL2.js.map} +0 -0
  110. /package/dist/{parser-XKSOS6GY.js.map → parser-YKKSFY6Q.js.map} +0 -0
  111. /package/dist/{pdf-ocr-X62B7YDV.js.map → pdf-ocr-MFJQP4XG.js.map} +0 -0
  112. /package/dist/{pdf-ocr-YDJQEGDS.js.map → pdf-ocr-WWYZE5L7.js.map} +0 -0
  113. /package/dist/{profile-io-Y24GOHWM.js.map → profile-io-PDOYD237.js.map} +0 -0
  114. /package/dist/{rasterize-QCOBWHAG.js.map → rasterize-4ZSVJGDA.js.map} +0 -0
  115. /package/dist/{render-DHFQQCSV.js.map → render-XRCZI3CY.js.map} +0 -0
  116. /package/dist/{seal-UTHFQPLO.js.map → seal-CFYSOI4V.js.map} +0 -0
  117. /package/dist/{setup-UWX3KH3G.js.map → setup-K7GW2TZM.js.map} +0 -0
  118. /package/dist/{visual-DF2KSPEC.js.map → visual-AUPPOHEP.js.map} +0 -0
  119. /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-IEJR23OA.js";
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-T7AFLXGA.js";
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-6JXMIX3G.js";
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-GWFX5ZD7.js";
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-27QN27I3.js";
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(/&amp;/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 ? `![image](${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 collectImageBlocks2(blocks, out, ownerCell, depth = 0) {
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) collectImageBlocks2(cell2.blocks, out, cell2, depth + 1);
1808
+ if (cell2.blocks?.length) collectImageBlocks(cell2.blocks, out, cell2, depth + 1);
2750
1809
  }
2751
1810
  }
2752
1811
  }
2753
- if (block.children?.length) collectImageBlocks2(block.children, out, ownerCell, depth + 1);
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
- collectImageBlocks2(blocks, imageBlocks);
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 inflateRawSync3 } from "zlib";
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 = inflateRawSync3(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
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
- const blocks = parseListHeaderParagraphs(ctrl, records, ctx);
4891
- if (blocks.length > 0) ctrl.afterBlocks = blocks;
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 wcharAt(data, offset) {
4896
- const code = data.readUInt16LE(offset);
4897
- return code > 0 ? String.fromCharCode(code) : "";
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 parseListHeaderParagraphs(ctrl, records, ctx) {
4900
- if (ctx.depth >= MAX_NEST_DEPTH) return [];
4901
- for (let i = ctrl.childStart; i < ctrl.childEnd; i++) {
4902
- if (records[i].tagId === TAG_LIST_HEADER) {
4903
- return parseParagraphList(records, i + 1, ctrl.childEnd, { ...ctx, depth: ctx.depth + 1 });
4904
- }
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
- return [];
2005
+ const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
2006
+ return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
4907
2007
  }
4908
- function blocksPlainText(blocks, sep) {
4909
- const parts = [];
4910
- for (const b of blocks) {
4911
- if (b.type === "image") continue;
4912
- if (b.type === "table") continue;
4913
- if (b.text) {
4914
- let t = b.text;
4915
- if (b.footnoteText) t += ` (\uC8FC: ${b.footnoteText})`;
4916
- parts.push(t);
4917
- }
4918
- }
4919
- return parts.join(sep).trim();
4920
- }
4921
- function applyNoteEffect(ctrl, records, ctx, autoType) {
4922
- const num2 = ctx.doc.autoCounters.get(autoType) ?? 1;
4923
- let before = "";
4924
- let after = "";
4925
- let shape = 0;
4926
- if (ctrl.data.length >= 12) {
4927
- before = wcharAt(ctrl.data, 8);
4928
- after = wcharAt(ctrl.data, 10);
4929
- }
4930
- if (ctrl.data.length >= 16) {
4931
- shape = ctrl.data.readUInt32LE(12) & 255;
4932
- }
4933
- const formatted = formatNumber(num2, shapeFormatToNumFmt(shape));
4934
- const marker = before || after ? `${before}${formatted}${after}` : `${formatted})`;
4935
- const content = blocksPlainText(parseListHeaderParagraphs(ctrl, records, ctx), " ");
4936
- if ((ctx.doc.autoCounters.get(autoType) ?? 1) <= num2) {
4937
- ctx.doc.autoCounters.set(autoType, num2 + 1);
4938
- }
4939
- ctrl.inlineText = marker;
4940
- if (content) ctrl.footnote = content.startsWith(marker) ? content : `${marker} ${content}`;
4941
- }
4942
- function applyHeaderFooterEffect(ctrl, records, ctx, isHeader) {
4943
- const text = blocksPlainText(parseListHeaderParagraphs(ctrl, records, ctx), "\n");
4944
- if (!text) return;
4945
- const key = (isHeader ? "h:" : "f:") + text;
4946
- if (ctx.doc.headerTexts.has(key)) return;
4947
- ctx.doc.headerTexts.add(key);
4948
- const block = { type: "paragraph", text, pageNumber: ctx.page };
4949
- if (isHeader) ctx.doc.headerBlocks.push(block);
4950
- else ctx.doc.footerBlocks.push(block);
4951
- }
4952
- function applyFieldEffect(ctrl) {
4953
- if (ctrl.id === FIELD_HLK) {
4954
- const command = parseFieldCommand(ctrl.data);
4955
- if (command) {
4956
- const url = hyperlinkUrlFromCommand(command);
4957
- if (url) ctrl.href = url;
4958
- }
4959
- }
4960
- }
4961
- function parseFieldCommand(data) {
4962
- if (data.length < 11) return null;
4963
- const cmdLen = data.readUInt16LE(9);
4964
- if (cmdLen === 0) return null;
4965
- const start = 11;
4966
- const end = start + cmdLen * 2;
4967
- if (end > data.length) return null;
4968
- return data.subarray(start, end).toString("utf16le").replace(/\0+$/, "");
4969
- }
4970
- function hyperlinkUrlFromCommand(command) {
4971
- let url = "";
4972
- for (let i = 0; i < command.length; i++) {
4973
- const c = command[i];
4974
- if (c === "\\" && i + 1 < command.length) {
4975
- url += command[i + 1];
4976
- i++;
4977
- continue;
4978
- }
4979
- if (c === ";") break;
4980
- url += c;
4981
- }
4982
- url = url.trim();
4983
- return url.length > 0 && url.length < 2e3 ? url : null;
4984
- }
4985
- function parseTableControl(ctrl, records, ctx) {
4986
- if (ctx.depth >= MAX_NEST_DEPTH) return null;
4987
- const { childStart, childEnd } = ctrl;
4988
- let rows = 0;
4989
- let cols = 0;
4990
- let tableIdx = -1;
4991
- for (let i2 = childStart; i2 < childEnd; i2++) {
4992
- if (records[i2].tagId === TAG_TABLE && records[i2].data.length >= 8) {
4993
- rows = Math.min(records[i2].data.readUInt16LE(4), MAX_ROWS);
4994
- cols = Math.min(records[i2].data.readUInt16LE(6), MAX_COLS);
4995
- tableIdx = i2;
4996
- break;
4997
- }
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 (tableIdx < 0 || rows === 0 || cols === 0) return null;
5000
- let caption;
5001
- for (let i2 = childStart; i2 < tableIdx; i2++) {
5002
- if (records[i2].tagId === TAG_LIST_HEADER) {
5003
- const capBlocks = parseParagraphList(records, i2 + 1, tableIdx, { ...ctx, depth: ctx.depth + 1 });
5004
- const capText = blocksPlainText(capBlocks, " ");
5005
- if (capText) caption = capText;
5006
- break;
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
- const cells = [];
5010
- let i = tableIdx + 1;
5011
- while (i < childEnd) {
5012
- const rec = records[i];
5013
- if (rec.tagId === TAG_LIST_HEADER) {
5014
- const cellLevel = rec.level;
5015
- let j = i + 1;
5016
- while (j < childEnd) {
5017
- const r = records[j];
5018
- if (r.level < cellLevel) break;
5019
- if (r.level === cellLevel && (r.tagId === TAG_LIST_HEADER || r.tagId === TAG_TABLE)) break;
5020
- j++;
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
- cells.push(parseCell(records, i, j, ctx));
5023
- i = j;
5024
- continue;
2043
+ parseDublinCoreMetadata(xml, metadata);
2044
+ if (metadata.title || metadata.author) return;
5025
2045
  }
5026
- i++;
2046
+ } catch {
5027
2047
  }
5028
- if (cells.length === 0) return null;
5029
- const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
5030
- if (hasAddr) {
5031
- const cellRows2 = arrangeCells(rows, cols, cells);
5032
- const irCells = cellRows2.map((row) => row.map((c) => {
5033
- const ir = { text: c.text.trim(), colSpan: c.colSpan, rowSpan: c.rowSpan };
5034
- if (c.blocks?.length) ir.blocks = c.blocks;
5035
- if (c.isHeader) ir.isHeader = true;
5036
- return ir;
5037
- }));
5038
- const table3 = { rows, cols, cells: irCells, hasHeader: rows > 1 };
5039
- if (caption) table3.caption = caption;
5040
- return table3;
5041
- }
5042
- const cellRows = arrangeCells(rows, cols, cells);
5043
- const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: ctx.doc.keepTrailingEmptyCols });
5044
- if (caption && table2.rows > 0) table2.caption = caption;
5045
- return table2.rows > 0 ? table2 : null;
5046
- }
5047
- function parseCell(records, lhIdx, end, ctx) {
5048
- const rec = records[lhIdx];
5049
- let colSpan = 1;
5050
- let rowSpan = 1;
5051
- let colAddr;
5052
- let rowAddr;
5053
- let isHeader = false;
5054
- if (rec.data.length >= 16) {
5055
- isHeader = (rec.data.readUInt16LE(6) & 4) !== 0;
5056
- colAddr = rec.data.readUInt16LE(8);
5057
- rowAddr = rec.data.readUInt16LE(10);
5058
- const cs = rec.data.readUInt16LE(12);
5059
- const rs = rec.data.readUInt16LE(14);
5060
- if (cs > 0) colSpan = Math.min(cs, MAX_COLS);
5061
- if (rs > 0) rowSpan = Math.min(rs, MAX_ROWS);
5062
- }
5063
- const blocks = ctx.depth < MAX_NEST_DEPTH ? parseParagraphList(records, lhIdx + 1, end, { ...ctx, depth: ctx.depth + 1 }) : [];
5064
- const parts = [];
5065
- let hasStructure = false;
5066
- for (const b of blocks) {
5067
- if (b.type === "image" && b.text) {
5068
- parts.push(`![image](hwp5bin:${b.text})`);
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 arrangeCells(rows, cols, cells) {
5089
- const grid = Array.from({ length: rows }, () => Array(cols).fill(null));
5090
- const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
5091
- if (hasAddr) {
5092
- for (const cell2 of cells) {
5093
- const r = cell2.rowAddr ?? 0;
5094
- const c = cell2.colAddr ?? 0;
5095
- if (r >= rows || c >= cols) continue;
5096
- grid[r][c] = cell2;
5097
- for (let dr = 0; dr < cell2.rowSpan; dr++) {
5098
- for (let dc = 0; dc < cell2.colSpan; dc++) {
5099
- if (dr === 0 && dc === 0) continue;
5100
- if (r + dr < rows && c + dc < cols)
5101
- grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
5102
- }
5103
- }
5104
- }
5105
- } else {
5106
- let cellIdx = 0;
5107
- for (let r = 0; r < rows && cellIdx < cells.length; r++) {
5108
- for (let c = 0; c < cols && cellIdx < cells.length; c++) {
5109
- if (grid[r][c] !== null) continue;
5110
- const cell2 = cells[cellIdx++];
5111
- grid[r][c] = cell2;
5112
- for (let dr = 0; dr < cell2.rowSpan; dr++) {
5113
- for (let dc = 0; dc < cell2.colSpan; dc++) {
5114
- if (dr === 0 && dc === 0) continue;
5115
- if (r + dr < rows && c + dc < cols)
5116
- grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
5117
- }
5118
- }
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
- return grid.map((row) => row.map((c) => c || { text: "", colSpan: 1, rowSpan: 1 }));
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
- function parseGsoControl(ctrl, records, ctx) {
5125
- if (ctx.depth >= MAX_NEST_DEPTH) return [];
5126
- const { childStart, childEnd } = ctrl;
5127
- const blocks = [];
5128
- let scIdx = -1;
5129
- for (let i = childStart; i < childEnd; i++) {
5130
- const t = records[i].tagId;
5131
- if (t === TAG_SHAPE_COMPONENT || t === TAG_SHAPE_COMPONENT_CONTAINER) {
5132
- scIdx = i;
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
- if (scIdx > childStart) {
5137
- for (let i = childStart; i < scIdx; i++) {
5138
- if (records[i].tagId === TAG_LIST_HEADER) {
5139
- blocks.push(...parseParagraphList(records, i + 1, scIdx, { ...ctx, depth: ctx.depth + 1 }));
5140
- break;
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 scanStart = scIdx >= 0 ? scIdx + 1 : childStart;
5145
- let textListIdx = -1;
5146
- for (let i = scanStart; i < childEnd; i++) {
5147
- if (records[i].tagId === TAG_LIST_HEADER) {
5148
- textListIdx = i;
5149
- break;
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 picEnd = textListIdx >= 0 ? textListIdx : childEnd;
5153
- for (let i = scanStart; i < picEnd; i++) {
5154
- if (records[i].tagId === TAG_SHAPE_COMPONENT_PICTURE) {
5155
- const img = pictureToImageBlock(records[i].data, ctx);
5156
- if (img) blocks.push(img);
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 (textListIdx >= 0) {
5160
- blocks.push(...parseParagraphList(records, textListIdx + 1, childEnd, { ...ctx, depth: ctx.depth + 1 }));
5161
- }
5162
- return blocks;
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 cs = docInfo.charShapes[dominantId];
5189
- if (!cs) return void 0;
5190
- const style = {};
5191
- if (cs.fontSize > 0) style.fontSize = cs.fontSize / 10;
5192
- if (cs.attrFlags & 1) style.italic = true;
5193
- if (cs.attrFlags & 2) style.bold = true;
5194
- if (hasRealStrike(cs.attrFlags)) style.strike = true;
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 inflateRawSync5 } from "zlib";
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 inflateRawSync4 } from "zlib";
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 = inflateRawSync4(decrypted, { maxOutputLength: MAX_DECOMPRESSED_BYTES });
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 MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
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 = inflateRawSync5(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE3 });
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(${MAX_DECOMPRESS_SIZE3 / 1024 / 1024}MB)\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4`);
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
- parseParagraphList2(bodyReader, ctx);
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 parseParagraphList2(reader, ctx) {
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
- parseParagraphList2(reader, ctx);
14828
+ parseParagraphList(reader, ctx);
17862
14829
  break;
17863
14830
  }
17864
14831
  case 16: {
17865
14832
  reader.skip(10);
17866
- parseParagraphList2(reader, ctx);
14833
+ parseParagraphList(reader, ctx);
17867
14834
  break;
17868
14835
  }
17869
14836
  case 17: {
17870
14837
  reader.skip(14);
17871
- parseParagraphList2(reader, ctx);
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
- parseParagraphList2(reader, ctx);
14880
+ parseParagraphList(reader, ctx);
17914
14881
  }
17915
- parseParagraphList2(reader, ctx);
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
- parseParagraphList2(reader, ctx);
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
- parseParagraphList2(new Reader(list), ctx);
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 MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
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, MAX_DECOMPRESS_SIZE4);
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 MAX_RECORDS2 = 1e6;
18378
- function readRecords2(stream) {
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 < MAX_RECORDS2) {
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 = readRecords2(wb);
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 MAX_DECOMPRESS_SIZE5 = 100 * 1024 * 1024;
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 parseParagraph2(p, styles, numbering, footnotes, rels) {
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 = parseParagraph2(el, styles, numbering, footnotes, rels);
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 = parseParagraph2(tp, styles, numbering, footnotes, rels);
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, MAX_DECOMPRESS_SIZE5);
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 = parseParagraph2(el, styles, numbering, footnotes, rels);
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 = parseParagraph2(tp, styles, numbering, footnotes, rels);
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
- parseSection2(el, blocks, paraShapeMap, sectionIdx, warnings, options?.keepTrailingEmptyCols ?? false);
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 parseSection2(section, blocks, paraShapeMap, sectionNum, warnings, keep) {
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
- parseParagraph3(el, blocks, paraShapeMap, sectionNum);
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 parseParagraph3(el, blocks, paraShapeMap, sectionNum) {
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 as createRequire2 } from "module";
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 require3 = createRequire2(import.meta.url);
29373
- var CFB2 = require3("cfb");
29374
- var TAG_PARA_LINE_SEG2 = 69;
29375
- function cid2(s) {
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 CTRL_TBL2 = cid2("tbl ");
29379
- var CTRL_GSO2 = cid2("gso ");
29380
- function swap322(id) {
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 || swap322(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 === TAG_PARA_LINE_SEG2 && lineSegIdx === -1) lineSegIdx = j;
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], CTRL_GSO2)) nonGso = true;
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], CTRL_TBL2) || ancestorCtrl(i, CTRL_TBL2)) continue;
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 = CFB2.parse(originalBuf);
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 = CFB2.find(cfb, "/FileHeader");
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 = CFB2.find(cfb, sectionPaths[i]);
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-2M7K3CE5.js");
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-T5R4YVZS.js");
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(format === "hwp" ? "HWP5 \uB294 \uB808\uC774\uC544\uC6C3 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 region\xB7crop \uC5C6\uC74C(\uBD84\uB958\uB9CC)" : `${format} \uC740 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 region\xB7crop \uC5C6\uC74C`);
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-2RREQZ44.js.map
27928
+ //# sourceMappingURL=chunk-F4DZU7VP.js.map