kordoc 4.13.0 → 4.14.0

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