kordoc 4.3.0 → 4.4.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 (169) hide show
  1. package/NOTICE +176 -0
  2. package/README.md +936 -922
  3. package/THIRD_PARTY/apache-2.0.LICENSE +201 -0
  4. package/THIRD_PARTY/claw-hwp.LICENSE +21 -0
  5. package/THIRD_PARTY/hml-equation-parser.LICENSE +201 -0
  6. package/THIRD_PARTY/hml-equation-parser.txt +35 -0
  7. package/THIRD_PARTY/opendataloader-pdf.txt +32 -0
  8. package/THIRD_PARTY/pix2text.txt +17 -0
  9. package/THIRD_PARTY/rhwp-forms.txt +35 -0
  10. package/dist/{-DYHT3HWH.js → -R6NUMPRV.js} +11 -11
  11. package/dist/{chunk-3FFPHTXD.js → chunk-3H5X63BQ.js} +1 -1
  12. package/dist/chunk-3H5X63BQ.js.map +1 -0
  13. package/dist/{chunk-EFVAY5VE.js → chunk-5KLE3U23.js} +7 -4
  14. package/dist/chunk-5KLE3U23.js.map +1 -0
  15. package/dist/{chunk-6GBLFQMA.js → chunk-5RPYBC2Q.js} +1 -1
  16. package/dist/chunk-5RPYBC2Q.js.map +1 -0
  17. package/dist/{chunk-VXMLENCV.js → chunk-65DYCXGC.js} +4 -4
  18. package/dist/chunk-65DYCXGC.js.map +1 -0
  19. package/dist/{chunk-W34QSJFF.js → chunk-7FCNOKOR.js} +1 -1
  20. package/dist/chunk-7FCNOKOR.js.map +1 -0
  21. package/dist/{chunk-ZSL6MQVE.js → chunk-A73RMJ6B.js} +1 -1
  22. package/dist/chunk-A73RMJ6B.js.map +1 -0
  23. package/dist/{chunk-AL34DGS5.cjs → chunk-AB322K3O.cjs} +6 -6
  24. package/dist/chunk-AB322K3O.cjs.map +1 -0
  25. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  26. package/dist/{chunk-O5EK52TG.js → chunk-DZIXKL7E.js} +1 -1
  27. package/dist/chunk-DZIXKL7E.js.map +1 -0
  28. package/dist/{chunk-EZHW3RNQ.js → chunk-GHGZU2LQ.js} +2 -2
  29. package/dist/chunk-GHGZU2LQ.js.map +1 -0
  30. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  31. package/dist/{chunk-6XPXIG57.js → chunk-GZDOWYJM.js} +2 -2
  32. package/dist/chunk-GZDOWYJM.js.map +1 -0
  33. package/dist/{chunk-JFV7QT5T.cjs → chunk-H2KMJOB6.cjs} +2 -2
  34. package/dist/chunk-H2KMJOB6.cjs.map +1 -0
  35. package/dist/{chunk-6RAYTNQT.js → chunk-HFJF477F.js} +3 -3
  36. package/dist/chunk-HFJF477F.js.map +1 -0
  37. package/dist/{chunk-SPKBIFSK.js → chunk-HNRULYUH.js} +2 -2
  38. package/dist/chunk-HNRULYUH.js.map +1 -0
  39. package/dist/chunk-MOL7MDBG.js +0 -0
  40. package/dist/{chunk-7MFO4TKP.js → chunk-ONS7JKVA.js} +2 -2
  41. package/dist/chunk-ONS7JKVA.js.map +1 -0
  42. package/dist/{chunk-DRQUAYNT.js → chunk-P7YPCK7C.js} +2 -2
  43. package/dist/chunk-P7YPCK7C.js.map +1 -0
  44. package/dist/{chunk-GSMPTZC7.js → chunk-QXYB74QF.js} +24 -3
  45. package/dist/chunk-QXYB74QF.js.map +1 -0
  46. package/dist/{chunk-NMWXBJ5B.js → chunk-RZURSA4N.js} +6 -4
  47. package/dist/chunk-RZURSA4N.js.map +1 -0
  48. package/dist/{chunk-LPQWKQQQ.cjs → chunk-SKM6KZUH.cjs} +9 -9
  49. package/dist/chunk-SKM6KZUH.cjs.map +1 -0
  50. package/dist/{chunk-YNJDNP44.js → chunk-UAFSPWU7.js} +1 -1
  51. package/dist/chunk-UAFSPWU7.js.map +1 -0
  52. package/dist/{chunk-AUOEZKJE.cjs → chunk-UDGPM5R5.cjs} +26 -5
  53. package/dist/chunk-UDGPM5R5.cjs.map +1 -0
  54. package/dist/{chunk-WFOQFJI4.js → chunk-V35DRUJC.js} +2 -2
  55. package/dist/chunk-V35DRUJC.js.map +1 -0
  56. package/dist/{chunk-4DBCUF3C.js → chunk-W6CUD7V3.js} +3 -3
  57. package/dist/chunk-W6CUD7V3.js.map +1 -0
  58. package/dist/{chunk-US4WFSIQ.js → chunk-XCIC7U6Y.js} +24 -3
  59. package/dist/chunk-XCIC7U6Y.js.map +1 -0
  60. package/dist/{chunk-DRZUJ2RK.js → chunk-YOZ7ZRCD.js} +366 -51
  61. package/dist/chunk-YOZ7ZRCD.js.map +1 -0
  62. package/dist/{chunk-ARXNKC5D.js → chunk-YVYU267E.js} +3 -3
  63. package/dist/chunk-YVYU267E.js.map +1 -0
  64. package/dist/{chunk-MGGU3ZRK.cjs → chunk-ZV5OKQ3Z.cjs} +1 -1
  65. package/dist/chunk-ZV5OKQ3Z.cjs.map +1 -0
  66. package/dist/chunks-QMGCI5DG.js +10 -0
  67. package/dist/cli.js +28 -24
  68. package/dist/cli.js.map +1 -1
  69. package/dist/{formula-IWV5IJ3Y.cjs → formula-CG4S7TWO.cjs} +8 -8
  70. package/dist/formula-CG4S7TWO.cjs.map +1 -0
  71. package/dist/{formula-7ZPC54JO.js → formula-JLYS73QL.js} +2 -2
  72. package/dist/formula-JLYS73QL.js.map +1 -0
  73. package/dist/{formula-EQRHSMYW.js → formula-LIVQSCMM.js} +2 -2
  74. package/dist/formula-LIVQSCMM.js.map +1 -0
  75. package/dist/{image-ocr-XEJFXH6M.cjs → image-ocr-F2BQA26V.cjs} +11 -11
  76. package/dist/image-ocr-F2BQA26V.cjs.map +1 -0
  77. package/dist/{image-ocr-G2LYTEUH.js → image-ocr-U2DRRFL3.js} +5 -5
  78. package/dist/image-ocr-U2DRRFL3.js.map +1 -0
  79. package/dist/{image-ocr-R44RYDBU.js → image-ocr-XC52AQDV.js} +7 -7
  80. package/dist/image-ocr-XC52AQDV.js.map +1 -0
  81. package/dist/index.cjs +858 -537
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.d.cts +7 -0
  84. package/dist/index.d.ts +7 -0
  85. package/dist/index.js +373 -52
  86. package/dist/index.js.map +1 -1
  87. package/dist/mcp.js +24 -22
  88. package/dist/mcp.js.map +1 -1
  89. package/dist/{models-6VMAVZR6.js → models-6SIKLKRH.js} +3 -3
  90. package/dist/page-range-737B4EZW.js +0 -0
  91. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  92. package/dist/{parser-W3POOYQQ.js → parser-ESGFWWCT.js} +8 -8
  93. package/dist/parser-ESGFWWCT.js.map +1 -0
  94. package/dist/{parser-W3ZGQUCQ.cjs → parser-FFQN67O6.cjs} +25 -25
  95. package/dist/parser-FFQN67O6.cjs.map +1 -0
  96. package/dist/{parser-2G245MMN.js → parser-TFPGQWCC.js} +6 -6
  97. package/dist/parser-TFPGQWCC.js.map +1 -0
  98. package/dist/pdf-ocr-4R646OO6.js +12 -0
  99. package/dist/pdf-ocr-UQBZH3HE.cjs +13 -0
  100. package/dist/pdf-ocr-UQBZH3HE.cjs.map +1 -0
  101. package/dist/pdf-ocr-WE5TMRFU.js +15 -0
  102. package/dist/{profile-io-72R3B7KO.js → profile-io-O6BDCUJN.js} +3 -3
  103. package/dist/{rasterize-GNJQVURE.js → rasterize-V22VCCE7.js} +2 -2
  104. package/dist/rasterize-V22VCCE7.js.map +1 -0
  105. package/dist/{redact-W5K3ILQH.js → redact-7ILEAUTS.js} +2 -2
  106. package/dist/render-3BSVTTZE.js +10 -0
  107. package/dist/seal-6ZSEJVOD.js +10 -0
  108. package/dist/{setup-I4I75QJU.js → setup-GUQT5ELJ.js} +1 -1
  109. package/dist/setup-GUQT5ELJ.js.map +1 -0
  110. package/dist/{watch-2ADAJLIJ.js → watch-HGKBICED.js} +11 -11
  111. package/dist/watch-HGKBICED.js.map +1 -0
  112. package/package.json +103 -101
  113. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +9 -9
  114. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  115. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +24 -24
  116. package/dist/chunk-3FFPHTXD.js.map +0 -1
  117. package/dist/chunk-4DBCUF3C.js.map +0 -1
  118. package/dist/chunk-6GBLFQMA.js.map +0 -1
  119. package/dist/chunk-6RAYTNQT.js.map +0 -1
  120. package/dist/chunk-6XPXIG57.js.map +0 -1
  121. package/dist/chunk-7MFO4TKP.js.map +0 -1
  122. package/dist/chunk-AL34DGS5.cjs.map +0 -1
  123. package/dist/chunk-ARXNKC5D.js.map +0 -1
  124. package/dist/chunk-AUOEZKJE.cjs.map +0 -1
  125. package/dist/chunk-DRQUAYNT.js.map +0 -1
  126. package/dist/chunk-DRZUJ2RK.js.map +0 -1
  127. package/dist/chunk-EFVAY5VE.js.map +0 -1
  128. package/dist/chunk-EZHW3RNQ.js.map +0 -1
  129. package/dist/chunk-GSMPTZC7.js.map +0 -1
  130. package/dist/chunk-JFV7QT5T.cjs.map +0 -1
  131. package/dist/chunk-LPQWKQQQ.cjs.map +0 -1
  132. package/dist/chunk-MGGU3ZRK.cjs.map +0 -1
  133. package/dist/chunk-NMWXBJ5B.js.map +0 -1
  134. package/dist/chunk-O5EK52TG.js.map +0 -1
  135. package/dist/chunk-SPKBIFSK.js.map +0 -1
  136. package/dist/chunk-US4WFSIQ.js.map +0 -1
  137. package/dist/chunk-VXMLENCV.js.map +0 -1
  138. package/dist/chunk-W34QSJFF.js.map +0 -1
  139. package/dist/chunk-WFOQFJI4.js.map +0 -1
  140. package/dist/chunk-YNJDNP44.js.map +0 -1
  141. package/dist/chunk-ZSL6MQVE.js.map +0 -1
  142. package/dist/chunks-Q7T4SQGW.js +0 -10
  143. package/dist/formula-7ZPC54JO.js.map +0 -1
  144. package/dist/formula-EQRHSMYW.js.map +0 -1
  145. package/dist/formula-IWV5IJ3Y.cjs.map +0 -1
  146. package/dist/image-ocr-G2LYTEUH.js.map +0 -1
  147. package/dist/image-ocr-R44RYDBU.js.map +0 -1
  148. package/dist/image-ocr-XEJFXH6M.cjs.map +0 -1
  149. package/dist/parser-2G245MMN.js.map +0 -1
  150. package/dist/parser-W3POOYQQ.js.map +0 -1
  151. package/dist/parser-W3ZGQUCQ.cjs.map +0 -1
  152. package/dist/pdf-ocr-3GDTJSHF.js +0 -15
  153. package/dist/pdf-ocr-N7IU23UV.cjs +0 -13
  154. package/dist/pdf-ocr-N7IU23UV.cjs.map +0 -1
  155. package/dist/pdf-ocr-U7MEGNBZ.js +0 -12
  156. package/dist/rasterize-GNJQVURE.js.map +0 -1
  157. package/dist/render-6OW6U4L4.js +0 -10
  158. package/dist/seal-GTNLVBJX.js +0 -10
  159. package/dist/setup-I4I75QJU.js.map +0 -1
  160. package/dist/watch-2ADAJLIJ.js.map +0 -1
  161. /package/dist/{-DYHT3HWH.js.map → -R6NUMPRV.js.map} +0 -0
  162. /package/dist/{chunks-Q7T4SQGW.js.map → chunks-QMGCI5DG.js.map} +0 -0
  163. /package/dist/{models-6VMAVZR6.js.map → models-6SIKLKRH.js.map} +0 -0
  164. /package/dist/{pdf-ocr-3GDTJSHF.js.map → pdf-ocr-4R646OO6.js.map} +0 -0
  165. /package/dist/{pdf-ocr-U7MEGNBZ.js.map → pdf-ocr-WE5TMRFU.js.map} +0 -0
  166. /package/dist/{profile-io-72R3B7KO.js.map → profile-io-O6BDCUJN.js.map} +0 -0
  167. /package/dist/{redact-W5K3ILQH.js.map → redact-7ILEAUTS.js.map} +0 -0
  168. /package/dist/{render-6OW6U4L4.js.map → render-3BSVTTZE.js.map} +0 -0
  169. /package/dist/{seal-GTNLVBJX.js.map → seal-6ZSEJVOD.js.map} +0 -0
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  flattenLayoutTables,
9
9
  inlineImagesIntoMarkdown,
10
10
  mapPuaText
11
- } from "./chunk-US4WFSIQ.js";
11
+ } from "./chunk-XCIC7U6Y.js";
12
12
  import {
13
13
  parsePageRange
14
14
  } from "./chunk-GE43BE46.js";
@@ -27,7 +27,7 @@ import {
27
27
  sanitizeHref,
28
28
  stripDtd,
29
29
  toArrayBuffer
30
- } from "./chunk-7MFO4TKP.js";
30
+ } from "./chunk-ONS7JKVA.js";
31
31
 
32
32
  // src/index.ts
33
33
  import { readFile } from "fs/promises";
@@ -406,6 +406,10 @@ function comResultToParseResult(pages, pageCount, warnings) {
406
406
  };
407
407
  }
408
408
 
409
+ // src/hwpx/crypto.ts
410
+ import { createDecipheriv, createHash, pbkdf2Sync, timingSafeEqual } from "crypto";
411
+ import { inflateRawSync } from "zlib";
412
+
409
413
  // src/hwpx/parser-shared.ts
410
414
  import { DOMParser } from "@xmldom/xmldom";
411
415
 
@@ -444,6 +448,9 @@ function elementChildren(parent) {
444
448
  }
445
449
  return out;
446
450
  }
451
+ function isTextNode(node) {
452
+ return node.nodeType === 3 || node.nodeType === 4;
453
+ }
447
454
  function extractTextFromNode(node, depth = 0) {
448
455
  let result = "";
449
456
  if (depth > MAX_XML_DEPTH) return result;
@@ -451,7 +458,7 @@ function extractTextFromNode(node, depth = 0) {
451
458
  if (!children) return result;
452
459
  for (let i = 0; i < children.length; i++) {
453
460
  const child = children[i];
454
- if (child.nodeType === 3) result += child.textContent || "";
461
+ if (isTextNode(child)) result += child.textContent || "";
455
462
  else if (child.nodeType === 1) result += extractTextFromNode(child, depth + 1);
456
463
  }
457
464
  return result.trim();
@@ -463,7 +470,7 @@ function rawTextContent(node, depth = 0) {
463
470
  const parts = [];
464
471
  for (let i = 0; i < children.length; i++) {
465
472
  const child = children[i];
466
- if (child.nodeType === 3) parts.push(child.nodeValue || "");
473
+ if (isTextNode(child)) parts.push(child.nodeValue || "");
467
474
  else if (child.nodeType === 1) parts.push(rawTextContent(child, depth + 1));
468
475
  }
469
476
  return parts.join("");
@@ -502,6 +509,89 @@ function applyPageText(blocks, shared) {
502
509
  }
503
510
  }
504
511
 
512
+ // src/hwpx/crypto.ts
513
+ var MAX_ITERATION_COUNT = 1e6;
514
+ var AES256_CBC = "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
515
+ var START_KEY_SHA256 = "http://www.w3.org/2000/09/xmldsig#sha256";
516
+ var CHECKSUM_SHA256_1K = "sha256-1k";
517
+ var CHECKSUM_PREFIX_BYTES = 1024;
518
+ var attrOf = (xml, name) => xml.match(new RegExp(`${name}="([^"]*)"`))?.[1];
519
+ function parseEncryptionManifest(manifestXml) {
520
+ const entries = [];
521
+ const entryRe = /<[^:>\s]*:?file-entry\b([^>]*)>([\s\S]*?)<\/[^:>\s]*:?file-entry>/g;
522
+ let m;
523
+ while ((m = entryRe.exec(manifestXml)) !== null) {
524
+ const [, attrs, inner] = m;
525
+ if (!inner.includes("encryption-data")) continue;
526
+ const path = attrOf(attrs, "full-path");
527
+ if (!path) continue;
528
+ const algorithm = attrOf(inner, "algorithm-name");
529
+ const startKey = attrOf(inner, "start-key-generation-name");
530
+ const checksumType = attrOf(inner, "checksum-type");
531
+ if (algorithm !== AES256_CBC) {
532
+ throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWPX \uC554\uD638\uD654 \uBC29\uC2DD\uC785\uB2C8\uB2E4: ${algorithm ?? "(\uBBF8\uC0C1)"}`);
533
+ }
534
+ if (startKey !== START_KEY_SHA256) {
535
+ throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWPX \uC2DC\uC791\uD0A4 \uC0DD\uC131 \uBC29\uC2DD\uC785\uB2C8\uB2E4: ${startKey ?? "(\uBBF8\uC0C1)"}`);
536
+ }
537
+ if (!checksumType?.endsWith(CHECKSUM_SHA256_1K)) {
538
+ throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWPX \uCCB4\uD06C\uC12C \uBC29\uC2DD\uC785\uB2C8\uB2E4: ${checksumType ?? "(\uBBF8\uC0C1)"}`);
539
+ }
540
+ const iterations = parseInt(attrOf(inner, "iteration-count") ?? "", 10);
541
+ const keySize = parseInt(attrOf(inner, "key-size") ?? "", 10);
542
+ const iv = Buffer.from(attrOf(inner, "initialisation-vector") ?? "", "base64");
543
+ const salt = Buffer.from(attrOf(inner, "salt") ?? "", "base64");
544
+ const checksum = Buffer.from(attrOf(inner, "checksum") ?? "", "base64");
545
+ if (!(iterations > 0 && iterations <= MAX_ITERATION_COUNT)) {
546
+ throw new KordocError(`HWPX \uC554\uD638\uD654 \uBC18\uBCF5 \uD69F\uC218\uAC00 \uD5C8\uC6A9 \uBC94\uC704\uB97C \uBC97\uC5B4\uB0AC\uC2B5\uB2C8\uB2E4: ${iterations}`);
547
+ }
548
+ if (keySize !== 32 || iv.length !== 16 || salt.length === 0 || checksum.length !== 32) {
549
+ throw new KordocError(`HWPX \uC554\uD638\uD654 \uD30C\uB77C\uBBF8\uD130\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (${path})`);
550
+ }
551
+ entries.push({ path, checksum, iv, salt, iterations, keySize });
552
+ }
553
+ return entries;
554
+ }
555
+ function stripEncryptionData(manifestXml) {
556
+ return manifestXml.replace(/<([^:>\s]*:?)encryption-data\b[\s\S]*?<\/\1encryption-data>/g, "");
557
+ }
558
+ function decryptEntry(cipher, entry, startKey) {
559
+ if (cipher.length === 0 || cipher.length % 16 !== 0) return void 0;
560
+ for (const prf of ["sha1", "sha256"]) {
561
+ try {
562
+ const key = pbkdf2Sync(startKey, entry.salt, entry.iterations, entry.keySize, prf);
563
+ const decipher = createDecipheriv("aes-256-cbc", key, entry.iv);
564
+ decipher.setAutoPadding(false);
565
+ const raw = Buffer.concat([decipher.update(cipher), decipher.final()]);
566
+ const plain = inflateRawSync(raw, { maxOutputLength: MAX_DECOMPRESS_SIZE });
567
+ const sum = createHash("sha256").update(plain.subarray(0, Math.min(CHECKSUM_PREFIX_BYTES, plain.length))).digest();
568
+ if (sum.length === entry.checksum.length && timingSafeEqual(sum, entry.checksum)) return plain;
569
+ } catch {
570
+ }
571
+ }
572
+ return void 0;
573
+ }
574
+ async function decryptHwpxInPlace(zip, manifestXml, password) {
575
+ const entries = parseEncryptionManifest(manifestXml);
576
+ if (entries.length === 0) return 0;
577
+ const startKey = createHash("sha256").update(Buffer.from(password, "utf8")).digest();
578
+ const decrypted = [];
579
+ for (const entry of entries) {
580
+ const file = zip.file(entry.path);
581
+ if (!file) {
582
+ throw new KordocError(`\uC554\uD638 \uBAA9\uB85D\uC5D0 \uC788\uB294 \uD30C\uC77C\uC774 \uBB38\uC11C\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4: ${entry.path}`);
583
+ }
584
+ const plain = decryptEntry(await file.async("nodebuffer"), entry, startKey);
585
+ if (!plain) {
586
+ 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.");
587
+ }
588
+ decrypted.push({ path: entry.path, plain });
589
+ }
590
+ for (const { path, plain } of decrypted) zip.file(path, plain);
591
+ zip.file("META-INF/manifest.xml", stripEncryptionData(manifestXml));
592
+ return decrypted.length;
593
+ }
594
+
505
595
  // src/hwpx/styles.ts
506
596
  function isRealStrikeShape(shape) {
507
597
  switch (shape) {
@@ -1196,8 +1286,8 @@ function hmlToLatex(hmlEqStr) {
1196
1286
  let tokens = s.split(" ");
1197
1287
  for (let i = 0; i < tokens.length; i++) {
1198
1288
  const t = tokens[i];
1199
- if (t in CONVERT_MAP) tokens[i] = CONVERT_MAP[t];
1200
- else if (t in MIDDLE_CONVERT_MAP) tokens[i] = MIDDLE_CONVERT_MAP[t];
1289
+ if (Object.hasOwn(CONVERT_MAP, t)) tokens[i] = CONVERT_MAP[t];
1290
+ else if (Object.hasOwn(MIDDLE_CONVERT_MAP, t)) tokens[i] = MIDDLE_CONVERT_MAP[t];
1201
1291
  else {
1202
1292
  const quoted = /^"(.+)"$/.exec(t);
1203
1293
  if (quoted) tokens[i] = `\\text{${quoted[1]}}`;
@@ -2058,7 +2148,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2058
2148
  if (!children) return;
2059
2149
  for (let i = 0; i < children.length; i++) {
2060
2150
  const child = children[i];
2061
- if (child.nodeType === 3) {
2151
+ if (child.nodeType === 3 || child.nodeType === 4) {
2062
2152
  const t = child.textContent || "";
2063
2153
  if (isInDeletedRange(ctx)) {
2064
2154
  if (t && ctx && !ctx.shared.track.warned) {
@@ -2269,7 +2359,7 @@ function extractRunSpans(para2, ctx, mode, requireMixed) {
2269
2359
  const tkids = rc.childNodes;
2270
2360
  for (let k = 0; k < (tkids?.length ?? 0); k++) {
2271
2361
  const tk = tkids[k];
2272
- if (tk.nodeType === 3) text += tk.textContent || "";
2362
+ if (tk.nodeType === 3 || tk.nodeType === 4) text += tk.textContent || "";
2273
2363
  else if (tk.nodeType === 1) return null;
2274
2364
  }
2275
2365
  } else if (rtag === "secPr" || rtag === "colPr") {
@@ -2327,7 +2417,7 @@ function gongmunDepthFromIndent(para2, left, ctx) {
2327
2417
  }
2328
2418
 
2329
2419
  // src/hwp5/record.ts
2330
- import { inflateRawSync, inflateSync } from "zlib";
2420
+ import { inflateRawSync as inflateRawSync2, inflateSync } from "zlib";
2331
2421
  var TAG_PARA_HEADER = 66;
2332
2422
  var TAG_PARA_TEXT = 67;
2333
2423
  var TAG_CHAR_SHAPE = 68;
@@ -2385,7 +2475,7 @@ function decompressStream(data) {
2385
2475
  } catch {
2386
2476
  }
2387
2477
  }
2388
- return inflateRawSync(data, opts);
2478
+ return inflateRawSync2(data, opts);
2389
2479
  }
2390
2480
  function parseFileHeader(data) {
2391
2481
  if (data.length < 40) throw new KordocError("FileHeader\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 40\uBC14\uC774\uD2B8)");
@@ -2906,7 +2996,7 @@ async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUn
2906
2996
  import JSZip2 from "jszip";
2907
2997
 
2908
2998
  // src/hwpx/zip-sections.ts
2909
- import { inflateRawSync as inflateRawSync2 } from "zlib";
2999
+ import { inflateRawSync as inflateRawSync3 } from "zlib";
2910
3000
  function extractFromBrokenZip(buffer) {
2911
3001
  const data = new Uint8Array(buffer);
2912
3002
  const view = new DataView(buffer);
@@ -2957,7 +3047,7 @@ function extractFromBrokenZip(buffer) {
2957
3047
  if (method === 0) {
2958
3048
  content = new TextDecoder().decode(fileData);
2959
3049
  } else if (method === 8) {
2960
- const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
3050
+ const decompressed = inflateRawSync3(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
2961
3051
  content = new TextDecoder().decode(decompressed);
2962
3052
  } else {
2963
3053
  continue;
@@ -3082,13 +3172,19 @@ async function parseHwpxDocument(buffer, options) {
3082
3172
  if (manifestFile) {
3083
3173
  const manifestXml = await manifestFile.async("text");
3084
3174
  if (isEncryptedHwpx(manifestXml)) {
3085
- if (isComFallbackAvailable() && options?.filePath) {
3086
- const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
3087
- if (pages.some((p) => p && p.trim().length > 0)) {
3088
- return comResultToParseResult(pages, pageCount, warnings2);
3175
+ if (options?.password) {
3176
+ await decryptHwpxInPlace(zip, manifestXml, options.password);
3177
+ } else {
3178
+ if (isComFallbackAvailable() && options?.filePath) {
3179
+ const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
3180
+ if (pages.some((p) => p && p.trim().length > 0)) {
3181
+ return comResultToParseResult(pages, pageCount, warnings2);
3182
+ }
3089
3183
  }
3184
+ throw new KordocError(
3185
+ "\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."
3186
+ );
3090
3187
  }
3091
- throw new KordocError("DRM \uC554\uD638\uD654\uB41C HWPX \uD30C\uC77C\uC785\uB2C8\uB2E4. Windows + \uD55C\uCEF4 \uC624\uD53C\uC2A4 \uC124\uCE58 \uC2DC \uC790\uB3D9 \uCD94\uCD9C\uB429\uB2C8\uB2E4.");
3092
3188
  }
3093
3189
  }
3094
3190
  const decompressed = { total: 0 };
@@ -4043,11 +4139,11 @@ var WORD_ALIASES = {
4043
4139
  var STRUCTURAL_FOLD = /* @__PURE__ */ new Set(["over", "root", "of"]);
4044
4140
  function normalizeWords(input) {
4045
4141
  return input.replace(/(?<![\\A-Za-z0-9])([A-Za-z][A-Za-z0-9]*)(?![A-Za-z0-9])/g, (word) => {
4046
- if (word in CONVERT_MAP || word in MIDDLE_CONVERT_MAP) return word;
4142
+ if (Object.hasOwn(CONVERT_MAP, word) || Object.hasOwn(MIDDLE_CONVERT_MAP, word)) return word;
4047
4143
  const lower = word.toLowerCase();
4048
4144
  if (STRUCTURAL_FOLD.has(lower)) return lower;
4049
- if (lower in WORD_ALIASES) return WORD_ALIASES[lower];
4050
- if (lower !== word && (lower in CONVERT_MAP || lower in MIDDLE_CONVERT_MAP)) return lower;
4145
+ if (Object.hasOwn(WORD_ALIASES, lower)) return WORD_ALIASES[lower];
4146
+ if (lower !== word && (Object.hasOwn(CONVERT_MAP, lower) || Object.hasOwn(MIDDLE_CONVERT_MAP, lower))) return lower;
4051
4147
  return word;
4052
4148
  });
4053
4149
  }
@@ -4067,6 +4163,84 @@ function hwpEquationToLatex(equation) {
4067
4163
  return hmlToLatex(s).replace(/\s+/g, " ").trim();
4068
4164
  }
4069
4165
 
4166
+ // src/hwp5/pw-crypto.ts
4167
+ import { createCipheriv, createHash as createHash2 } from "crypto";
4168
+ var ENCRYPT_VERSION_OFFSET = 44;
4169
+ var SUPPORTED_ENCRYPT_VERSION = 4;
4170
+ var KEY_SEED = 236;
4171
+ function readEncryptVersion(fileHeader) {
4172
+ if (fileHeader.length < ENCRYPT_VERSION_OFFSET + 4) return 0;
4173
+ return (fileHeader[ENCRYPT_VERSION_OFFSET] | fileHeader[ENCRYPT_VERSION_OFFSET + 1] << 8 | fileHeader[ENCRYPT_VERSION_OFFSET + 2] << 16 | fileHeader[ENCRYPT_VERSION_OFFSET + 3] << 24) >>> 0;
4174
+ }
4175
+ function derivePasswordKey(password) {
4176
+ const buf = Buffer.alloc(password.length * 2);
4177
+ for (let i = 0; i < password.length; i++) {
4178
+ const prev = i === 0 ? KEY_SEED : password[i - 1];
4179
+ buf[i * 2] = (prev << 1 | prev >> 7) & 255;
4180
+ buf[i * 2 + 1] = password[i];
4181
+ }
4182
+ return createHash2("sha1").update(buf).digest().subarray(0, 16);
4183
+ }
4184
+ function padToBlock(data) {
4185
+ const rem = data.length % 16;
4186
+ if (rem === 0) return Buffer.from(data);
4187
+ const amount = 16 - rem;
4188
+ return Buffer.concat([Buffer.from(data), Buffer.alloc(amount, amount)]);
4189
+ }
4190
+ function shiftRegister(reg, feedbackBit) {
4191
+ for (let i = 0; i < 15; i++) {
4192
+ reg[i] = (reg[i] << 1 | reg[i + 1] >> 7) & 255;
4193
+ }
4194
+ reg[15] = (reg[15] << 1 | feedbackBit & 1) & 255;
4195
+ }
4196
+ function decryptAesCfb1(data, key) {
4197
+ const cipher = createCipheriv("aes-128-ecb", key, null);
4198
+ cipher.setAutoPadding(false);
4199
+ const register = new Uint8Array(16);
4200
+ const registerBuf = Buffer.alloc(16);
4201
+ const out = Buffer.alloc(data.length);
4202
+ for (let offset = 0; offset < data.length; offset += 16) {
4203
+ const len = Math.min(16, data.length - offset);
4204
+ const chunk = Buffer.alloc(16);
4205
+ data.copy(chunk, 0, offset, offset + len);
4206
+ for (let i = 0; i < 128; i++) {
4207
+ registerBuf.set(register);
4208
+ const keystreamMsb = cipher.update(registerBuf)[0];
4209
+ const byteIdx = i >> 3;
4210
+ const bitInByte = i & 7;
4211
+ const cipherBit = chunk[byteIdx] >> 7 - bitInByte & 1;
4212
+ shiftRegister(register, cipherBit);
4213
+ chunk[byteIdx] ^= (keystreamMsb & 128) >> bitInByte;
4214
+ }
4215
+ chunk.copy(out, offset, 0, len);
4216
+ }
4217
+ return out;
4218
+ }
4219
+ function decryptPasswordStream(raw, password) {
4220
+ const key = derivePasswordKey(Buffer.from(password, "utf8"));
4221
+ return decryptAesCfb1(padToBlock(raw), key).subarray(0, raw.length);
4222
+ }
4223
+ var TAG_DOCUMENT_PROPERTIES = 16;
4224
+ var TAG_ID_MAPPINGS = 17;
4225
+ function assertDecryptedDocInfo(data, readRecords3) {
4226
+ 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.");
4227
+ let records;
4228
+ try {
4229
+ records = readRecords3(data);
4230
+ } catch {
4231
+ throw wrong();
4232
+ }
4233
+ if (records[0]?.tagId !== TAG_DOCUMENT_PROPERTIES) throw wrong();
4234
+ if (!records.some((r) => r.tagId === TAG_ID_MAPPINGS)) throw wrong();
4235
+ }
4236
+ function assertSupportedEncryptVersion(version) {
4237
+ if (version !== SUPPORTED_ENCRYPT_VERSION) {
4238
+ throw new KordocError(
4239
+ `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWP \uC554\uD638\uD654 \uBC84\uC804\uC785\uB2C8\uB2E4 (EncryptVersion=${version}, \uC9C0\uC6D0: ${SUPPORTED_ENCRYPT_VERSION})`
4240
+ );
4241
+ }
4242
+ }
4243
+
4070
4244
  // src/hwp5/parser.ts
4071
4245
  import { createRequire } from "module";
4072
4246
  var require2 = createRequire(import.meta.url);
@@ -4147,27 +4321,50 @@ function parseHwp5Document(buffer, options) {
4147
4321
  throw new KordocError("CFB \uCEE8\uD14C\uC774\uB108 \uD30C\uC2F1 \uC2E4\uD328 (strict \uBC0F lenient \uBAA8\uB450)");
4148
4322
  }
4149
4323
  }
4150
- const findStream = (path) => {
4324
+ const readRawStream = (path) => {
4151
4325
  if (cfb) {
4152
4326
  const entry = CFB.find(cfb, path);
4153
4327
  return entry?.content ? Buffer.from(entry.content) : null;
4154
4328
  }
4155
4329
  return lenientCfb.findStream(path);
4156
4330
  };
4157
- const headerData = findStream("/FileHeader");
4331
+ const headerData = readRawStream("/FileHeader");
4158
4332
  if (!headerData) throw new KordocError("FileHeader \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
4159
4333
  const header = parseFileHeader(headerData);
4160
4334
  if (header.signature !== "HWP Document File") throw new KordocError("HWP \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
4161
- if (header.flags & FLAG_ENCRYPTED) throw new KordocError("\uC554\uD638\uD654\uB41C HWP\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
4162
4335
  if (header.flags & FLAG_DRM) throw new KordocError("DRM \uBCF4\uD638\uB41C HWP\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
4336
+ const encrypted = (header.flags & FLAG_ENCRYPTED) !== 0;
4337
+ if (encrypted) {
4338
+ if (!options?.password) {
4339
+ 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.");
4340
+ }
4341
+ assertSupportedEncryptVersion(readEncryptVersion(headerData));
4342
+ }
4343
+ const password = encrypted ? options.password : void 0;
4344
+ const findStream = (path) => {
4345
+ const raw = readRawStream(path);
4346
+ if (!raw || !password) return raw;
4347
+ return decryptPasswordStream(raw, password);
4348
+ };
4163
4349
  const compressed = (header.flags & FLAG_COMPRESSED) !== 0;
4164
4350
  const distribution = (header.flags & FLAG_DISTRIBUTION) !== 0;
4165
4351
  const metadata = {
4166
4352
  version: `${header.versionMajor}.x`
4167
4353
  };
4168
4354
  if (cfb) extractHwp5Metadata(cfb, metadata);
4169
- const docInfo = cfb ? parseDocInfoStream(cfb, compressed) : parseDocInfoFromStream(findStream("/DocInfo"), compressed);
4170
- const sections = distribution ? cfb ? findViewTextSections(cfb, compressed) : findViewTextSectionsLenient(lenientCfb, compressed) : cfb ? findSections(cfb) : findSectionsLenient(lenientCfb, compressed);
4355
+ if (encrypted) {
4356
+ const rawDocInfo = findStream("/DocInfo");
4357
+ if (!rawDocInfo) throw new KordocError("DocInfo \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
4358
+ let docInfoData;
4359
+ try {
4360
+ docInfoData = compressed ? decompressStream(rawDocInfo) : rawDocInfo;
4361
+ } catch {
4362
+ 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.");
4363
+ }
4364
+ assertDecryptedDocInfo(docInfoData, readRecords);
4365
+ }
4366
+ const docInfo = cfb && !encrypted ? parseDocInfoStream(cfb, compressed) : parseDocInfoFromStream(findStream("/DocInfo"), compressed);
4367
+ const sections = distribution ? cfb ? findViewTextSections(cfb, compressed) : findViewTextSectionsLenient(lenientCfb, compressed) : encrypted ? findSectionsVia(findStream) : cfb ? findSections(cfb) : findSectionsLenient(lenientCfb, compressed);
4171
4368
  if (sections.length === 0) throw new KordocError("\uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
4172
4369
  metadata.pageCount = sections.length;
4173
4370
  const pageFilter = options?.pages ? parsePageRange(options.pages, sections.length) : null;
@@ -4335,6 +4532,15 @@ function findViewTextSections(cfb, compressed) {
4335
4532
  }
4336
4533
  return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
4337
4534
  }
4535
+ function findSectionsVia(read) {
4536
+ const sections = [];
4537
+ for (let i = 0; i < MAX_SECTIONS; i++) {
4538
+ const raw = read(`/BodyText/Section${i}`);
4539
+ if (!raw) break;
4540
+ sections.push(raw);
4541
+ }
4542
+ return sections;
4543
+ }
4338
4544
  function findSections(cfb) {
4339
4545
  const sections = [];
4340
4546
  for (let i = 0; i < MAX_SECTIONS; i++) {
@@ -4940,7 +5146,7 @@ function hasRealStrike(attrFlags) {
4940
5146
  }
4941
5147
 
4942
5148
  // src/hwp3/parser.ts
4943
- import { inflateRawSync as inflateRawSync3 } from "zlib";
5149
+ import { inflateRawSync as inflateRawSync5 } from "zlib";
4944
5150
 
4945
5151
  // src/hwp3/johab-symbols.ts
4946
5152
  var JOHAB_SYMBOLS = Object.freeze([
@@ -16848,6 +17054,23 @@ function lookupSymbol(ch) {
16848
17054
  return null;
16849
17055
  }
16850
17056
  var JOHAB_UNMAPPED = -1;
17057
+ var JUNG_ARAEA = 30;
17058
+ var ARAEA_JAMO = 4510;
17059
+ function decodeAraeaSyllable(ch) {
17060
+ if (ch < 32768) return null;
17061
+ if ((ch >> 5 & 31) !== JUNG_ARAEA) return null;
17062
+ const cho = CHO_MAP[ch >> 10 & 31];
17063
+ const jong = JONG_MAP[ch & 31];
17064
+ if (cho === -1 || jong === -1) return null;
17065
+ const out = String.fromCodePoint(4352 + cho) + String.fromCodePoint(ARAEA_JAMO);
17066
+ return jong === 0 ? out : out + String.fromCodePoint(4519 + jong);
17067
+ }
17068
+ function decodeJohabText(ch) {
17069
+ const araea = decodeAraeaSyllable(ch);
17070
+ if (araea) return araea;
17071
+ const cp = decodeJohab(ch);
17072
+ return cp === JOHAB_UNMAPPED ? null : String.fromCodePoint(cp);
17073
+ }
16851
17074
  function decodeJohab(ch) {
16852
17075
  if (ch < 128) return ch;
16853
17076
  if (ch >= 32768) {
@@ -16869,6 +17092,7 @@ function decodeJohab(ch) {
16869
17092
  function decodeHwp3Extra(ch) {
16870
17093
  if (ch >= 13712 && ch <= 13721) return 8544 + (ch - 13712);
16871
17094
  if (ch >= 14055 && ch <= 14064) return 9312 + (ch - 14055);
17095
+ if (ch >= 14272 && ch <= 14277) return "\uD55C\uAE00\uACFC\uCEF4\uD4E8\uD130".codePointAt(ch - 14272);
16872
17096
  switch (ch) {
16873
17097
  case 129:
16874
17098
  return 8220;
@@ -16903,6 +17127,9 @@ function decodeHwp3Extra(ch) {
16903
17127
  case 13434:
16904
17128
  return 9654;
16905
17129
  // ▶
17130
+ case 12135:
17131
+ return 9656;
17132
+ // ▸ 표 셀 글머리표 (rhwp 16db8260 — HWP5 변환본·한컴 PDF 대조)
16906
17133
  default:
16907
17134
  return JOHAB_UNMAPPED;
16908
17135
  }
@@ -16913,8 +17140,7 @@ function decodeHcharString(bytes) {
16913
17140
  while (i + 1 < bytes.length) {
16914
17141
  const ch = bytes[i] | bytes[i + 1] << 8;
16915
17142
  if (ch === 0) break;
16916
- const cp = decodeJohab(ch);
16917
- if (cp !== JOHAB_UNMAPPED) out += String.fromCodePoint(cp);
17143
+ out += decodeJohabText(ch) ?? "";
16918
17144
  i += 2;
16919
17145
  }
16920
17146
  return out;
@@ -17019,6 +17245,87 @@ function readHeader(reader) {
17019
17245
  return { compressed, encrypted, infoBlockLength, title, subject, author, date };
17020
17246
  }
17021
17247
 
17248
+ // src/hwp3/crypto.ts
17249
+ import { createDecipheriv as createDecipheriv2 } from "crypto";
17250
+ import { inflateRawSync as inflateRawSync4 } from "zlib";
17251
+ var FIXED_HEADER_BYTES = SIGNATURE_LEN + DOC_INFO_SIZE + DOC_SUMMARY_SIZE;
17252
+ var PASSWORD_FLAG_OFFSET = SIGNATURE_LEN + 96;
17253
+ var COMPRESSION_FLAG_OFFSET = SIGNATURE_LEN + 124;
17254
+ var INFO_BLOCK_LENGTH_OFFSET = SIGNATURE_LEN + 126;
17255
+ var PASSWORD_PREFIX_BYTES = 256;
17256
+ var MAX_DECOMPRESSED_BYTES = 100 * 1024 * 1024;
17257
+ function isEncryptedHwp3(data) {
17258
+ if (data.length < FIXED_HEADER_BYTES) return false;
17259
+ return (data[PASSWORD_FLAG_OFFSET] | data[PASSWORD_FLAG_OFFSET + 1] << 8) !== 0;
17260
+ }
17261
+ function deriveDesKey(password) {
17262
+ const rolling = new Uint8Array(7);
17263
+ let index = 0;
17264
+ for (let i = 0; i < password.length; i++) {
17265
+ const unit = password.charCodeAt(i);
17266
+ for (const byte of [unit & 255, unit >> 8 & 255]) {
17267
+ const slot = index % rolling.length;
17268
+ const mixed = rolling[slot] ^ byte;
17269
+ rolling[slot] = (mixed << 1 | mixed >> 7) & 255;
17270
+ index++;
17271
+ }
17272
+ }
17273
+ const key = Buffer.alloc(8);
17274
+ for (let bit = 0; bit < 56; bit++) {
17275
+ const sourceBit = rolling[Math.floor(bit / 8)] >> 7 - bit % 8 & 1;
17276
+ key[Math.floor(bit / 7)] |= sourceBit << 7 - bit % 7;
17277
+ }
17278
+ return key;
17279
+ }
17280
+ function desEcbDecrypt(data, key) {
17281
+ const decipher = createDecipheriv2("des-ede3-ecb", Buffer.concat([key, key, key]), null);
17282
+ decipher.setAutoPadding(false);
17283
+ return Buffer.concat([decipher.update(data), decipher.final()]);
17284
+ }
17285
+ function readLayout(data) {
17286
+ if (data.length < FIXED_HEADER_BYTES) {
17287
+ throw new KordocError("HWP3 \uC554\uD638 \uBB38\uC11C: \uD5E4\uB354\uAC00 \uC798\uB838\uC2B5\uB2C8\uB2E4");
17288
+ }
17289
+ const infoBlockLength = data[INFO_BLOCK_LENGTH_OFFSET] | data[INFO_BLOCK_LENGTH_OFFSET + 1] << 8;
17290
+ const payloadOffset = FIXED_HEADER_BYTES + infoBlockLength;
17291
+ if (payloadOffset >= data.length) {
17292
+ throw new KordocError("HWP3 \uC554\uD638 \uBB38\uC11C: \uBCF8\uBB38\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
17293
+ }
17294
+ return { payloadOffset, compressed: data[COMPRESSION_FLAG_OFFSET] !== 0 };
17295
+ }
17296
+ function decryptHwp3Document(input, password) {
17297
+ const { payloadOffset, compressed } = readLayout(input);
17298
+ if (!compressed) {
17299
+ throw new KordocError("\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWP3 \uC554\uD638\uD654 \uBC29\uC2DD\uC785\uB2C8\uB2E4 (\uC555\uCD95\uB418\uC9C0 \uC54A\uC740 \uC554\uD638 \uBCF8\uBB38)");
17300
+ }
17301
+ const source = Buffer.from(input);
17302
+ const payload = source.subarray(payloadOffset);
17303
+ const aligned = payload.subarray(0, payload.length - payload.length % 8);
17304
+ if (aligned.length === 0) {
17305
+ throw new KordocError("HWP3 \uC554\uD638 \uBB38\uC11C: \uBCF8\uBB38\uC774 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4");
17306
+ }
17307
+ const decrypted = desEcbDecrypt(aligned, deriveDesKey(password));
17308
+ let inflated;
17309
+ try {
17310
+ inflated = inflateRawSync4(decrypted, { maxOutputLength: MAX_DECOMPRESSED_BYTES });
17311
+ } catch (err) {
17312
+ if (err?.code === "ERR_BUFFER_TOO_LARGE") {
17313
+ throw new KordocError(
17314
+ `HWP3 \uC554\uD638 \uBCF8\uBB38\uC758 \uC555\uCD95 \uD574\uC81C \uACB0\uACFC\uAC00 \uCD5C\uB300 \uD5C8\uC6A9 \uD06C\uAE30(${MAX_DECOMPRESSED_BYTES / 1024 / 1024}MB)\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4`
17315
+ );
17316
+ }
17317
+ 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.");
17318
+ }
17319
+ if (inflated.length <= PASSWORD_PREFIX_BYTES) {
17320
+ 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.");
17321
+ }
17322
+ const body = inflated.subarray(PASSWORD_PREFIX_BYTES);
17323
+ const out = Buffer.concat([source.subarray(0, payloadOffset), body]);
17324
+ out.writeUInt16LE(0, PASSWORD_FLAG_OFFSET);
17325
+ out[COMPRESSION_FLAG_OFFSET] = 0;
17326
+ return out;
17327
+ }
17328
+
17022
17329
  // src/hwp3/parser.ts
17023
17330
  var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
17024
17331
  var PARA_SHAPE_SIZE = 187;
@@ -17041,19 +17348,25 @@ var SIMPLE_CTRL = /* @__PURE__ */ new Map([
17041
17348
  [30, { extraBytes: 2, extraHchar: 1, emit: "\xA0" }],
17042
17349
  [31, { extraBytes: 2, extraHchar: 1, emit: " " }]
17043
17350
  ]);
17044
- function parseHwp3Document(buffer, _options) {
17045
- const headReader = new Reader(Buffer.from(buffer));
17046
- const header = readHeader(headReader);
17047
- if (header.encrypted !== 0) {
17048
- throw new KordocError("\uC554\uD638\uD654\uB41C HWP3 \uBB38\uC11C \u2014 \uBCF8\uBB38\uC774 \uC554\uD638\uB85C \uBCF4\uD638\uB418\uC5B4 \uC788\uC5B4 \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
17351
+ function parseHwp3Document(buffer, options) {
17352
+ let source = Buffer.from(buffer);
17353
+ if (isEncryptedHwp3(source)) {
17354
+ if (!options?.password) {
17355
+ throw new KordocError(
17356
+ "\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWP3 \uBB38\uC11C\uC785\uB2C8\uB2E4. password \uC635\uC158\uC5D0 \uC5F4\uAE30 \uC554\uD638\uB97C \uC9C0\uC815\uD558\uC138\uC694."
17357
+ );
17358
+ }
17359
+ source = decryptHwp3Document(source, options.password);
17049
17360
  }
17361
+ const headReader = new Reader(source);
17362
+ const header = readHeader(headReader);
17050
17363
  headReader.skip(header.infoBlockLength);
17051
17364
  const tail = headReader.readToEnd();
17052
17365
  let body;
17053
17366
  const warnings = [];
17054
17367
  if (header.compressed !== 0) {
17055
17368
  try {
17056
- body = inflateRawSync3(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE3 });
17369
+ body = inflateRawSync5(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE3 });
17057
17370
  } catch (err) {
17058
17371
  if (err?.code === "ERR_BUFFER_TOO_LARGE") {
17059
17372
  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`);
@@ -17156,8 +17469,7 @@ function parseCharStream(reader, charCount, ctx) {
17156
17469
  continue;
17157
17470
  }
17158
17471
  if (ch >= 32) {
17159
- const cp = decodeJohab(ch);
17160
- if (cp !== JOHAB_UNMAPPED) out += String.fromCodePoint(cp);
17472
+ out += decodeJohabText(ch) ?? "";
17161
17473
  continue;
17162
17474
  }
17163
17475
  const simple = SIMPLE_CTRL.get(ch);
@@ -17225,7 +17537,7 @@ function parseCharStream(reader, charCount, ctx) {
17225
17537
  function parseTableLike(reader, ctx) {
17226
17538
  const info = reader.readBytes(84);
17227
17539
  const cellCount = info.readUInt16LE(80) || 1;
17228
- if (cellCount > 256) {
17540
+ if (27 * cellCount > reader.remaining()) {
17229
17541
  ctx.warnings.push({
17230
17542
  code: "PARTIAL_PARSE",
17231
17543
  message: `HWP3 \uD45C cell_count=${cellCount} \uBE44\uC815\uC0C1 \u2014 \uD45C \uBCF8\uBB38 \uCD94\uCD9C \uD3EC\uAE30`
@@ -17239,10 +17551,11 @@ function parseTableLike(reader, ctx) {
17239
17551
  parseParagraphList2(reader, ctx);
17240
17552
  return "";
17241
17553
  }
17242
- function parsePicture(reader, _ctx) {
17554
+ function parsePicture(reader, ctx) {
17243
17555
  const info = reader.readBytes(348);
17244
17556
  const nExt = info.readUInt32LE(0);
17245
17557
  if (nExt > 0 && nExt < 100 * 1024 * 1024) reader.skip(nExt);
17558
+ parseParagraphList2(reader, ctx);
17246
17559
  }
17247
17560
 
17248
17561
  // src/hwp5/sentinel.ts
@@ -19951,7 +20264,9 @@ async function fillWithUniqueGuard(values, run) {
19951
20264
  const first = await run(values);
19952
20265
  const counts = /* @__PURE__ */ new Map();
19953
20266
  for (const f of first.filled) {
19954
- if (f.key) counts.set(f.key, (counts.get(f.key) ?? 0) + 1);
20267
+ if (f.key && f.source !== "clickhere") {
20268
+ counts.set(f.key, (counts.get(f.key) ?? 0) + 1);
20269
+ }
19955
20270
  }
19956
20271
  const isArrayValue = (normKey) => {
19957
20272
  for (const [label, raw] of Object.entries(values)) {
@@ -20934,7 +21249,7 @@ function applySplices(xml, splices) {
20934
21249
 
20935
21250
  // src/form/click-here.ts
20936
21251
  var FIELD_BEGIN_RE = /<([A-Za-z0-9_]+:)?fieldBegin\b([^>]*?)(\/?)>/g;
20937
- function attrOf(attrs, name) {
21252
+ function attrOf2(attrs, name) {
20938
21253
  const m = attrs.match(new RegExp(`(?:^|\\s)${name}="([^"]*)"`));
20939
21254
  return m ? m[1] : void 0;
20940
21255
  }
@@ -20960,10 +21275,10 @@ function scanClickHereRegions(xml) {
20960
21275
  const prefix = m[1] ?? "";
20961
21276
  const attrs = m[2];
20962
21277
  const selfClosed = m[3] === "/";
20963
- if ((attrOf(attrs, "type") ?? "").toUpperCase() !== "CLICK_HERE") continue;
20964
- const name = attrOf(attrs, "name");
21278
+ if ((attrOf2(attrs, "type") ?? "").toUpperCase() !== "CLICK_HERE") continue;
21279
+ const name = attrOf2(attrs, "name");
20965
21280
  if (name === void 0) continue;
20966
- const id = attrOf(attrs, "id");
21281
+ const id = attrOf2(attrs, "id");
20967
21282
  let beginElemEnd;
20968
21283
  if (selfClosed) {
20969
21284
  beginElemEnd = FIELD_BEGIN_RE.lastIndex;
@@ -20986,7 +21301,7 @@ function scanClickHereRegions(xml) {
20986
21301
  let end = -1;
20987
21302
  let fe;
20988
21303
  while ((fe = feRe.exec(xml)) !== null) {
20989
- const ref = attrOf(fe[0], "beginIDRef");
21304
+ const ref = attrOf2(fe[0], "beginIDRef");
20990
21305
  if (id !== void 0 && ref !== void 0 && ref !== id) continue;
20991
21306
  end = fe.index;
20992
21307
  break;
@@ -21027,7 +21342,7 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
21027
21342
  const replacement = valueXml(value, region.prefix);
21028
21343
  const current = xml.slice(region.start, region.end);
21029
21344
  matchedKeys.add(key);
21030
- filled.push({ label: region.name, value, row: -1, col: -1, key });
21345
+ filled.push({ label: region.name, value, row: -1, col: -1, key, source: "clickhere" });
21031
21346
  if (current === replacement) continue;
21032
21347
  splices.push({ start: region.start, end: region.end, replacement });
21033
21348
  }
@@ -21038,6 +21353,7 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
21038
21353
  };
21039
21354
  }
21040
21355
  async function extractClickHereFields(hwpxBuffer) {
21356
+ precheckZipSize(hwpxBuffer);
21041
21357
  const zip = await JSZip6.loadAsync(hwpxBuffer);
21042
21358
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
21043
21359
  const fields = [];
@@ -21266,6 +21582,7 @@ function patchZipEntries(original, replacements, additions) {
21266
21582
  // src/form/filler-hwpx.ts
21267
21583
  async function fillHwpx(hwpxBuffer, values, blockedLabels) {
21268
21584
  const u8 = new Uint8Array(hwpxBuffer);
21585
+ precheckZipSize(hwpxBuffer);
21269
21586
  const zip = await JSZip7.loadAsync(hwpxBuffer);
21270
21587
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
21271
21588
  if (sectionPaths.length === 0) {
@@ -26010,7 +26327,7 @@ function collectSites(scan) {
26010
26327
  sites.sort((a, b) => a.para.start - b.para.start);
26011
26328
  return sites;
26012
26329
  }
26013
- function attrOf2(openTag, name) {
26330
+ function attrOf3(openTag, name) {
26014
26331
  const m = openTag.match(new RegExp(`\\b${name}="([^"]*)"`));
26015
26332
  return m?.[1];
26016
26333
  }
@@ -26028,7 +26345,7 @@ function anchorRunInfo(xml, para2, anchor) {
26028
26345
  const before = xml.slice(Math.max(0, para2.start), tr.contentStart);
26029
26346
  const runOpen = [...before.matchAll(/<([A-Za-z0-9]+):run\b[^>]*>/g)].pop();
26030
26347
  const prefix = runOpen?.[1] ?? tr.prefix ?? "hp";
26031
- const charPr2 = runOpen && attrOf2(runOpen[0], "charPrIDRef") || "0";
26348
+ const charPr2 = runOpen && attrOf3(runOpen[0], "charPrIDRef") || "0";
26032
26349
  const close = xml.indexOf(`</${prefix}:run>`, tr.contentEnd);
26033
26350
  if (close === -1) return null;
26034
26351
  return { charPr: charPr2, prefix, insertAt: close + `</${prefix}:run>`.length };
@@ -26056,7 +26373,7 @@ function cellContentWidthMm(xml, cell2) {
26056
26373
  const win = cellAddrWindow(xml, cell2);
26057
26374
  const mg = win?.match(/<[A-Za-z0-9]+:cellMargin\b[^>]*>/);
26058
26375
  if (mg) {
26059
- content -= Number(attrOf2(mg[0], "left") ?? 0) + Number(attrOf2(mg[0], "right") ?? 0);
26376
+ content -= Number(attrOf3(mg[0], "left") ?? 0) + Number(attrOf3(mg[0], "right") ?? 0);
26060
26377
  }
26061
26378
  return content > 0 ? content / HU_PER_MM2 : null;
26062
26379
  }
@@ -26090,6 +26407,7 @@ function buildFloatPicXml(itemId, sizeHu, posXHu, posYHu, ids) {
26090
26407
  async function placeSealHwpx(hwpxBuffer, ops) {
26091
26408
  if (ops.length === 0) throw new KordocError("place_seal: \uBC30\uCE58\uD560 \uB3C4\uC7A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
26092
26409
  const u8 = new Uint8Array(hwpxBuffer);
26410
+ precheckZipSize(hwpxBuffer);
26093
26411
  const zip = await JSZip9.loadAsync(hwpxBuffer);
26094
26412
  const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort((a, b) => Number(a.match(/(\d+)\.xml$/i)?.[1] ?? 0) - Number(b.match(/(\d+)\.xml$/i)?.[1] ?? 0));
26095
26413
  if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
@@ -26188,7 +26506,7 @@ async function placeSealHwpx(hwpxBuffer, ops) {
26188
26506
  const anchorWMm = measureMm(op.anchor, emMm);
26189
26507
  const sizeMm = op.sizeMm != null && Number.isFinite(op.sizeMm) && op.sizeMm > 0 ? op.sizeMm : Math.max(7, Math.min(18, lineHMm * 1.6));
26190
26508
  const availMm = (site.cell ? cellContentWidthMm(xml, site.cell) : null) ?? bodyColumnWidthMm(xml);
26191
- const paraPrId = attrOf2(paraOpenTag(xml, site.para), "paraPrIDRef") ?? "0";
26509
+ const paraPrId = attrOf3(paraOpenTag(xml, site.para), "paraPrIDRef") ?? "0";
26192
26510
  const align = styles?.paraAlign.get(paraPrId);
26193
26511
  let alignShiftMm = 0;
26194
26512
  if (align === "CENTER" || align === "RIGHT") {
@@ -27275,6 +27593,9 @@ function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before, a
27275
27593
  const xml = ctx.scans[target.sectionIndex]?.xml;
27276
27594
  const rawT = xml ? paraTText(target, xml) : null;
27277
27595
  let sp;
27596
+ if (xml && rawT === null) {
27597
+ return skip("\uBE48 \uC140 \uBB38\uB2E8\uC5D0 \uC5D4\uD2F0\uD2F0\xB7\uD0DC\uADF8\uAC00 \uC788\uC5B4 \uC0BD\uC785 \uACBD\uACC4\uB97C \uD2B9\uC815\uD560 \uC218 \uC5C6\uC74C \u2014 \uC6D0\uBB38 \uC190\uC2E4 \uBC29\uC9C0\uB85C \uBBF8\uC801\uC6A9");
27598
+ }
27278
27599
  if (xml && rawT !== null && rawT.length > 0 && rawT.trim() === "") {
27279
27600
  sp = buildRangeSplices(target, xml, 0, 0, value);
27280
27601
  if (sp === null) return skip("\uBE48 \uC140 \uACF5\uBC31 \uBCF4\uC874 \uC0BD\uC785 \uACBD\uACC4 \uBAA8\uD638 \u2014 \uC6D0\uBB38 \uC190\uC2E4 \uBC29\uC9C0\uB85C \uBBF8\uC801\uC6A9");
@@ -30000,7 +30321,7 @@ function pushTextSlots(t, chars, prId, depth) {
30000
30321
  if (!kids) return;
30001
30322
  for (let i = 0; i < kids.length; i++) {
30002
30323
  const c = kids[i];
30003
- if (c.nodeType === 3) {
30324
+ if (c.nodeType === 3 || c.nodeType === 4) {
30004
30325
  for (const cp of c.textContent ?? "") {
30005
30326
  chars.push({ ch: cp, prId });
30006
30327
  if (cp.length === 2) chars.push({ ch: "", prId });
@@ -30741,7 +31062,7 @@ async function parse(input, options) {
30741
31062
  }
30742
31063
  async function parseImage(buffer, options) {
30743
31064
  try {
30744
- const { parseImageDocument } = await import("./image-ocr-G2LYTEUH.js");
31065
+ const { parseImageDocument } = await import("./image-ocr-U2DRRFL3.js");
30745
31066
  const { blocks, warnings } = await parseImageDocument(buffer, options);
30746
31067
  return {
30747
31068
  success: true,
@@ -30800,7 +31121,7 @@ async function parseHwp(buffer, options) {
30800
31121
  async function parsePdf(buffer, options) {
30801
31122
  let parsePdfDocument;
30802
31123
  try {
30803
- const mod = await import("./parser-2G245MMN.js");
31124
+ const mod = await import("./parser-TFPGQWCC.js");
30804
31125
  parsePdfDocument = mod.parsePdfDocument;
30805
31126
  } catch {
30806
31127
  return {