kordoc 4.14.0 → 4.14.2

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 (135) hide show
  1. package/README.md +18 -2
  2. package/dist/{-SLX6IB6Y.js → -AQKXLFIM.js} +10 -10
  3. package/dist/{chunk-WAPKUYGP.cjs → chunk-247ANM72.cjs} +11 -11
  4. package/dist/chunk-247ANM72.cjs.map +1 -0
  5. package/dist/{chunk-EOTAPX2M.js → chunk-3DUADAY2.js} +3 -3
  6. package/dist/{chunk-B7MOYHVR.cjs → chunk-422427XJ.cjs} +6 -6
  7. package/dist/chunk-422427XJ.cjs.map +1 -0
  8. package/dist/{chunk-HG5LXXCC.js → chunk-7IAQ4HOK.js} +1 -1
  9. package/dist/{chunk-XVIJLHCW.js.map → chunk-7IAQ4HOK.js.map} +1 -1
  10. package/dist/{chunk-JIQWI7S2.js → chunk-A7QC2ZVP.js} +3 -3
  11. package/dist/{chunk-HCHA3Y2T.js → chunk-C7FQBSON.js} +2 -2
  12. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  13. package/dist/{chunk-7VUHZFGQ.js → chunk-DOGPOY4B.js} +2 -2
  14. package/dist/{chunk-6T6MB5ON.js → chunk-EDNJLUSE.js} +2 -2
  15. package/dist/{chunk-EMQZX5QQ.js → chunk-EISO3POT.js} +301 -124
  16. package/dist/chunk-EISO3POT.js.map +1 -0
  17. package/dist/{chunk-LCGAO5LF.js → chunk-EP7KOI2V.js} +3 -3
  18. package/dist/{chunk-WRIHDGGY.cjs → chunk-EQZI77PP.cjs} +3 -3
  19. package/dist/chunk-EQZI77PP.cjs.map +1 -0
  20. package/dist/{chunk-XVIJLHCW.js → chunk-FOJZNMMK.js} +1 -1
  21. package/dist/{chunk-HG5LXXCC.js.map → chunk-FOJZNMMK.js.map} +1 -1
  22. package/dist/{chunk-PLXNW3J4.js → chunk-GDW4YP7K.js} +2 -2
  23. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  24. package/dist/{chunk-ANKDK5DF.cjs → chunk-HR2XNERB.cjs} +2 -2
  25. package/dist/chunk-HR2XNERB.cjs.map +1 -0
  26. package/dist/{chunk-3U3PJGVO.js → chunk-ILU76UF3.js} +13 -2
  27. package/dist/chunk-ILU76UF3.js.map +1 -0
  28. package/dist/{chunk-J3SM7VLM.js → chunk-JQEFP45L.js} +2 -2
  29. package/dist/{chunk-M5NRJMA4.js → chunk-KVWV4ULP.js} +3 -3
  30. package/dist/{chunk-COUGOFT3.cjs → chunk-L7DMSMAG.cjs} +1 -1
  31. package/dist/chunk-L7DMSMAG.cjs.map +1 -0
  32. package/dist/{chunk-GMX6EKCY.js → chunk-NS55SM7A.js} +2 -2
  33. package/dist/{chunk-XH34WJKB.js → chunk-QC5T6D4J.js} +2 -2
  34. package/dist/{chunk-STLNL53H.cjs → chunk-QOMNEWCQ.cjs} +4 -4
  35. package/dist/chunk-QOMNEWCQ.cjs.map +1 -0
  36. package/dist/{chunk-CDQJKMZ5.js → chunk-TNCKOL2O.js} +2 -2
  37. package/dist/{chunk-HH7T6SMB.js → chunk-TSFS5VKD.js} +3072 -1721
  38. package/dist/chunk-TSFS5VKD.js.map +1 -0
  39. package/dist/{chunk-RCTWZYAM.js → chunk-UET462HB.js} +2 -2
  40. package/dist/{chunk-A2JDWBKG.js → chunk-VQ5W4MYO.js} +2 -2
  41. package/dist/{chunk-ZWTEE63A.js → chunk-ZWD734EY.js} +2 -2
  42. package/dist/{chunk-UBNJXMYX.js → chunk-ZWOFQRIB.js} +14 -6
  43. package/dist/chunk-ZWOFQRIB.js.map +1 -0
  44. package/dist/chunks-SX77P5CV.js +10 -0
  45. package/dist/cli.js +1066 -1038
  46. package/dist/cli.js.map +1 -1
  47. package/dist/{formula-VSZIJZXP.js → formula-5S7WWXGH.js} +4 -4
  48. package/dist/{formula-VYDTA27M.js → formula-JGFTXWYT.js} +3 -3
  49. package/dist/{formula-TAYYDV73.cjs → formula-PJJ6S3Z2.cjs} +9 -9
  50. package/dist/formula-PJJ6S3Z2.cjs.map +1 -0
  51. package/dist/{image-ocr-5WBAIBPQ.js → image-ocr-FLIL7MGN.js} +6 -6
  52. package/dist/{image-ocr-N6IBZJAK.js → image-ocr-WCAYXLOA.js} +8 -8
  53. package/dist/{image-ocr-ZX4W2RBS.cjs → image-ocr-ZTYAGV3W.cjs} +12 -12
  54. package/dist/image-ocr-ZTYAGV3W.cjs.map +1 -0
  55. package/dist/index.cjs +3010 -1465
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +11 -6
  58. package/dist/index.d.ts +11 -6
  59. package/dist/index.js +2547 -1002
  60. package/dist/index.js.map +1 -1
  61. package/dist/mcp.js +973 -932
  62. package/dist/mcp.js.map +1 -1
  63. package/dist/{model-bundle-5CBFVIXA.js → model-bundle-FSPZZ2EM.js} +5 -5
  64. package/dist/{models-L74RBKK3.js → models-CJPAQK5R.js} +5 -5
  65. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  66. package/dist/{parser-66NMJUOG.js → parser-A4TMPJAM.js} +7 -7
  67. package/dist/{parser-BK66Y5JD.js → parser-SCUTZAIJ.js} +7 -7
  68. package/dist/{parser-WM2DMVLY.cjs → parser-VCXE4I5A.cjs} +27 -27
  69. package/dist/parser-VCXE4I5A.cjs.map +1 -0
  70. package/dist/pdf-ocr-76SAUXMZ.js +13 -0
  71. package/dist/pdf-ocr-CPU55CVF.js +16 -0
  72. package/dist/pdf-ocr-MBZZ57LD.cjs +14 -0
  73. package/dist/pdf-ocr-MBZZ57LD.cjs.map +1 -0
  74. package/dist/{profile-io-JZ3FDHDK.js → profile-io-LBM263K7.js} +3 -3
  75. package/dist/{rasterize-33WOOVSM.js → rasterize-46RG4ZF6.js} +3 -3
  76. package/dist/{render-MLN4ZLVD.js → render-NGP5T24S.js} +7 -7
  77. package/dist/seal-RGUPOLXD.js +10 -0
  78. package/dist/{setup-G6AEN4J7.js → setup-AU4OCHNH.js} +3 -3
  79. package/dist/visual-NZLYIYEM.js +19 -0
  80. package/dist/{watch-OY6XKGC5.js → watch-MKPUCPXM.js} +11 -11
  81. package/package.json +2 -2
  82. package/dist/chunk-3U3PJGVO.js.map +0 -1
  83. package/dist/chunk-ANKDK5DF.cjs.map +0 -1
  84. package/dist/chunk-B7MOYHVR.cjs.map +0 -1
  85. package/dist/chunk-COUGOFT3.cjs.map +0 -1
  86. package/dist/chunk-EMQZX5QQ.js.map +0 -1
  87. package/dist/chunk-HH7T6SMB.js.map +0 -1
  88. package/dist/chunk-STLNL53H.cjs.map +0 -1
  89. package/dist/chunk-UBNJXMYX.js.map +0 -1
  90. package/dist/chunk-WAPKUYGP.cjs.map +0 -1
  91. package/dist/chunk-WRIHDGGY.cjs.map +0 -1
  92. package/dist/chunks-PEHRXW2P.js +0 -10
  93. package/dist/formula-TAYYDV73.cjs.map +0 -1
  94. package/dist/image-ocr-ZX4W2RBS.cjs.map +0 -1
  95. package/dist/parser-WM2DMVLY.cjs.map +0 -1
  96. package/dist/pdf-ocr-322MBLDK.js +0 -16
  97. package/dist/pdf-ocr-A4UBXLM2.js +0 -13
  98. package/dist/pdf-ocr-G4TBDUYL.cjs +0 -14
  99. package/dist/pdf-ocr-G4TBDUYL.cjs.map +0 -1
  100. package/dist/seal-JQWSNHRG.js +0 -10
  101. package/dist/visual-VZ7QGAUC.js +0 -19
  102. /package/dist/{-SLX6IB6Y.js.map → -AQKXLFIM.js.map} +0 -0
  103. /package/dist/{chunk-EOTAPX2M.js.map → chunk-3DUADAY2.js.map} +0 -0
  104. /package/dist/{chunk-JIQWI7S2.js.map → chunk-A7QC2ZVP.js.map} +0 -0
  105. /package/dist/{chunk-HCHA3Y2T.js.map → chunk-C7FQBSON.js.map} +0 -0
  106. /package/dist/{chunk-7VUHZFGQ.js.map → chunk-DOGPOY4B.js.map} +0 -0
  107. /package/dist/{chunk-6T6MB5ON.js.map → chunk-EDNJLUSE.js.map} +0 -0
  108. /package/dist/{chunk-LCGAO5LF.js.map → chunk-EP7KOI2V.js.map} +0 -0
  109. /package/dist/{chunk-PLXNW3J4.js.map → chunk-GDW4YP7K.js.map} +0 -0
  110. /package/dist/{chunk-J3SM7VLM.js.map → chunk-JQEFP45L.js.map} +0 -0
  111. /package/dist/{chunk-M5NRJMA4.js.map → chunk-KVWV4ULP.js.map} +0 -0
  112. /package/dist/{chunk-GMX6EKCY.js.map → chunk-NS55SM7A.js.map} +0 -0
  113. /package/dist/{chunk-XH34WJKB.js.map → chunk-QC5T6D4J.js.map} +0 -0
  114. /package/dist/{chunk-CDQJKMZ5.js.map → chunk-TNCKOL2O.js.map} +0 -0
  115. /package/dist/{chunk-RCTWZYAM.js.map → chunk-UET462HB.js.map} +0 -0
  116. /package/dist/{chunk-A2JDWBKG.js.map → chunk-VQ5W4MYO.js.map} +0 -0
  117. /package/dist/{chunk-ZWTEE63A.js.map → chunk-ZWD734EY.js.map} +0 -0
  118. /package/dist/{chunks-PEHRXW2P.js.map → chunks-SX77P5CV.js.map} +0 -0
  119. /package/dist/{formula-VSZIJZXP.js.map → formula-5S7WWXGH.js.map} +0 -0
  120. /package/dist/{formula-VYDTA27M.js.map → formula-JGFTXWYT.js.map} +0 -0
  121. /package/dist/{image-ocr-5WBAIBPQ.js.map → image-ocr-FLIL7MGN.js.map} +0 -0
  122. /package/dist/{image-ocr-N6IBZJAK.js.map → image-ocr-WCAYXLOA.js.map} +0 -0
  123. /package/dist/{model-bundle-5CBFVIXA.js.map → model-bundle-FSPZZ2EM.js.map} +0 -0
  124. /package/dist/{models-L74RBKK3.js.map → models-CJPAQK5R.js.map} +0 -0
  125. /package/dist/{parser-66NMJUOG.js.map → parser-A4TMPJAM.js.map} +0 -0
  126. /package/dist/{parser-BK66Y5JD.js.map → parser-SCUTZAIJ.js.map} +0 -0
  127. /package/dist/{pdf-ocr-322MBLDK.js.map → pdf-ocr-76SAUXMZ.js.map} +0 -0
  128. /package/dist/{pdf-ocr-A4UBXLM2.js.map → pdf-ocr-CPU55CVF.js.map} +0 -0
  129. /package/dist/{profile-io-JZ3FDHDK.js.map → profile-io-LBM263K7.js.map} +0 -0
  130. /package/dist/{rasterize-33WOOVSM.js.map → rasterize-46RG4ZF6.js.map} +0 -0
  131. /package/dist/{render-MLN4ZLVD.js.map → render-NGP5T24S.js.map} +0 -0
  132. /package/dist/{seal-JQWSNHRG.js.map → seal-RGUPOLXD.js.map} +0 -0
  133. /package/dist/{setup-G6AEN4J7.js.map → setup-AU4OCHNH.js.map} +0 -0
  134. /package/dist/{visual-VZ7QGAUC.js.map → visual-NZLYIYEM.js.map} +0 -0
  135. /package/dist/{watch-OY6XKGC5.js.map → watch-MKPUCPXM.js.map} +0 -0
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  hasStructuredCellContent,
10
10
  inlineImagesIntoMarkdown,
11
11
  mapPuaText
12
- } from "./chunk-A2JDWBKG.js";
12
+ } from "./chunk-VQ5W4MYO.js";
13
13
  import {
14
14
  parsePageRange
15
15
  } from "./chunk-GE43BE46.js";
@@ -17,7 +17,7 @@ import {
17
17
  HEADING_RATIO_H1,
18
18
  HEADING_RATIO_H2,
19
19
  HEADING_RATIO_H3
20
- } from "./chunk-XVIJLHCW.js";
20
+ } from "./chunk-FOJZNMMK.js";
21
21
  import {
22
22
  KordocError,
23
23
  VERSION,
@@ -30,7 +30,7 @@ import {
30
30
  sanitizeHref,
31
31
  stripDtd,
32
32
  toArrayBuffer
33
- } from "./chunk-J3SM7VLM.js";
33
+ } from "./chunk-JQEFP45L.js";
34
34
 
35
35
  // src/index.ts
36
36
  import { readFile as readFile3 } from "fs/promises";
@@ -290,6 +290,7 @@ async function detectZipFormat(buffer) {
290
290
  const zip = await JSZip.loadAsync(buffer);
291
291
  if (zip.file("xl/workbook.xml")) return "xlsx";
292
292
  if (zip.file("word/document.xml")) return "docx";
293
+ if (zip.file("ppt/presentation.xml")) return "pptx";
293
294
  if (zip.file("Contents/content.hpf") || zip.file("mimetype")) return "hwpx";
294
295
  const hasSection = Object.keys(zip.files).some((f) => f.startsWith("Contents/"));
295
296
  if (hasSection) return "hwpx";
@@ -2350,8 +2351,10 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2350
2351
  text += "\n";
2351
2352
  break;
2352
2353
  // 강제 줄바꿈 — ref 추출기·소스맵 스캐너와 동일 모델
2354
+ // 고정폭·묶음 빈칸 — 묶음 빈칸(nbSpace)을 빠뜨리면 "2026.<nbSpace/>9." 가 "2026.9." 로 붙는다
2353
2355
  case "fwSpace":
2354
2356
  case "hwSpace":
2357
+ case "nbSpace":
2355
2358
  text += " ";
2356
2359
  break;
2357
2360
  // 테이블 자체는 walkSection에서 처리 — 글자취급(inline) 표만 경계 마커를 남겨
@@ -2527,7 +2530,11 @@ function extractRunSpans(para2, ctx, mode, requireMixed) {
2527
2530
  for (let k = 0; k < (tkids?.length ?? 0); k++) {
2528
2531
  const tk = tkids[k];
2529
2532
  if (tk.nodeType === 3 || tk.nodeType === 4) text += tk.textContent || "";
2530
- else if (tk.nodeType === 1) return null;
2533
+ else if (tk.nodeType === 1) {
2534
+ const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
2535
+ if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
2536
+ else return null;
2537
+ }
2531
2538
  }
2532
2539
  } else if (rtag === "secPr" || rtag === "colPr") {
2533
2540
  } else if (rtag === "ctrl") {
@@ -3345,6 +3352,20 @@ function parseDublinCoreMetadata(xml, metadata) {
3345
3352
  }
3346
3353
 
3347
3354
  // src/hwpx/parser.ts
3355
+ function assignSectionPage(block, sectionNum) {
3356
+ const pending = [block];
3357
+ while (pending.length > 0) {
3358
+ const current = pending.pop();
3359
+ current.pageNumber = sectionNum;
3360
+ for (const child of current.children ?? []) pending.push(child);
3361
+ for (const caption of current.table?.captionBlocks ?? []) pending.push(caption);
3362
+ for (const row of current.table?.cells ?? []) {
3363
+ for (const cell2 of row) {
3364
+ for (const child of cell2.blocks ?? []) pending.push(child);
3365
+ }
3366
+ }
3367
+ }
3368
+ }
3348
3369
  async function parseHwpxDocument(buffer, options) {
3349
3370
  precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
3350
3371
  let zip;
@@ -3412,7 +3433,7 @@ async function parseHwpxDocument(buffer, options) {
3412
3433
  metadata.pageCount = layoutPages ? Math.max(shared.pageState.base, 1) : sectionPaths.length;
3413
3434
  if (!layoutPages) {
3414
3435
  for (const r of sectionRanges) {
3415
- for (let b = r.start; b < r.end; b++) allBlocks[b].pageNumber = r.sectionNum;
3436
+ for (let b = r.start; b < r.end; b++) assignSectionPage(allBlocks[b], r.sectionNum);
3416
3437
  }
3417
3438
  }
3418
3439
  if (options?.pages) {
@@ -3431,167 +3452,6 @@ async function parseHwpxDocument(buffer, options) {
3431
3452
  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 };
3432
3453
  }
3433
3454
 
3434
- // src/hwp5/numbering.ts
3435
- var NumberingState = class {
3436
- currentId = 0;
3437
- counters = [0, 0, 0, 0, 0, 0, 0];
3438
- history = /* @__PURE__ */ new Map();
3439
- /** 번호 문단 처리: 카운터 갱신 후 수준별 카운터 스냅샷 반환 */
3440
- advance(numberingId, level) {
3441
- const lv = Math.min(Math.max(level, 0), 6);
3442
- if (this.currentId !== numberingId) {
3443
- if (this.currentId !== 0) this.history.set(this.currentId, [...this.counters]);
3444
- const saved = this.history.get(numberingId);
3445
- if (saved) {
3446
- this.counters = [...saved];
3447
- } else {
3448
- const prev = this.counters;
3449
- this.counters = [0, 0, 0, 0, 0, 0, 0];
3450
- for (let i = 0; i < lv; i++) this.counters[i] = prev[i];
3451
- }
3452
- this.currentId = numberingId;
3453
- }
3454
- this.counters[lv]++;
3455
- for (let i = lv + 1; i < 7; i++) this.counters[i] = 0;
3456
- return [...this.counters];
3457
- }
3458
- };
3459
- function headFormatToNumFmt(code) {
3460
- switch (code) {
3461
- case 1:
3462
- return "circled";
3463
- case 2:
3464
- return "romanUpper";
3465
- case 3:
3466
- return "romanLower";
3467
- case 4:
3468
- return "latinUpper";
3469
- case 5:
3470
- return "latinLower";
3471
- case 8:
3472
- return "ganada";
3473
- case 9:
3474
- return "circledGanada";
3475
- case 10:
3476
- return "jamo";
3477
- case 11:
3478
- return "circledJamo";
3479
- case 12:
3480
- return "hangulNum";
3481
- case 13:
3482
- return "hanjaNum";
3483
- default:
3484
- return "digit";
3485
- }
3486
- }
3487
- function shapeFormatToNumFmt(code) {
3488
- switch (code) {
3489
- case 1:
3490
- return "circled";
3491
- case 2:
3492
- return "romanUpper";
3493
- case 3:
3494
- return "romanLower";
3495
- case 4:
3496
- return "latinUpper";
3497
- case 5:
3498
- return "latinLower";
3499
- case 6:
3500
- return "ganada";
3501
- case 7:
3502
- return "hangulNum";
3503
- case 8:
3504
- return "hanjaNum";
3505
- default:
3506
- return "digit";
3507
- }
3508
- }
3509
- var JAMO = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
3510
- var CIRCLED_JAMO = "\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326A\u326B\u326C\u326D";
3511
- function fromTable(n, table2) {
3512
- return n >= 1 && n <= table2.length ? table2[n - 1] : String(n);
3513
- }
3514
- function formatLatin(n, upper) {
3515
- if (n <= 0) return "";
3516
- let result = "";
3517
- let num4 = n;
3518
- while (num4 > 0) {
3519
- num4--;
3520
- result = String.fromCharCode((upper ? 65 : 97) + num4 % 26) + result;
3521
- num4 = Math.floor(num4 / 26);
3522
- }
3523
- return result;
3524
- }
3525
- function formatEastAsianNumber(n, digits, units, zero) {
3526
- if (n === 0) return zero;
3527
- if (n < 0 || n > 99999) return String(n);
3528
- let result = "";
3529
- let num4 = n;
3530
- let unit = 0;
3531
- while (num4 > 0) {
3532
- const d = num4 % 10;
3533
- if (d > 0) {
3534
- const digitStr = d === 1 && unit > 0 ? "" : digits[d];
3535
- result = digitStr + units[unit] + result;
3536
- }
3537
- num4 = Math.floor(num4 / 10);
3538
- unit++;
3539
- }
3540
- return result;
3541
- }
3542
- var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
3543
- var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
3544
- var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
3545
- var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
3546
- function formatNumber(n, fmt) {
3547
- switch (fmt) {
3548
- case "circled":
3549
- return n >= 1 ? circledNumber(n - 1) : String(n);
3550
- case "romanUpper":
3551
- return romanNumeral(n, true);
3552
- case "romanLower":
3553
- return romanNumeral(n, false);
3554
- case "latinUpper":
3555
- return formatLatin(n, true) || String(n);
3556
- case "latinLower":
3557
- return formatLatin(n, false) || String(n);
3558
- case "ganada":
3559
- return n >= 1 ? hangulOrdinal(n - 1) : String(n);
3560
- case "circledGanada":
3561
- return n >= 1 ? circledHangul(n - 1) : String(n);
3562
- case "jamo":
3563
- return fromTable(n, JAMO);
3564
- case "circledJamo":
3565
- return fromTable(n, CIRCLED_JAMO);
3566
- case "hangulNum":
3567
- return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
3568
- case "hanjaNum":
3569
- return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
3570
- default:
3571
- return String(n);
3572
- }
3573
- }
3574
- function expandNumberingFormat(formatStr, counters, numbering) {
3575
- let result = "";
3576
- let i = 0;
3577
- while (i < formatStr.length) {
3578
- const ch = formatStr[i];
3579
- if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
3580
- const levelRef = formatStr.charCodeAt(i + 1) - 48;
3581
- const idx = levelRef - 1;
3582
- const counterVal = counters[idx] ?? 0;
3583
- const start = numbering.startNumbers[idx] ?? 1;
3584
- const num4 = counterVal > 0 ? start - 1 + counterVal : start;
3585
- result += formatNumber(num4, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
3586
- i += 2;
3587
- continue;
3588
- }
3589
- result += ch;
3590
- i++;
3591
- }
3592
- return result;
3593
- }
3594
-
3595
3455
  // src/hwp5/aes.ts
3596
3456
  var S_BOX = new Uint8Array([
3597
3457
  99,
@@ -4295,78 +4155,6 @@ function decryptViewText(viewTextRaw, compressed) {
4295
4155
  return Buffer.from(decrypted);
4296
4156
  }
4297
4157
 
4298
- // src/hwp5/equation.ts
4299
- var WORD_ALIASES = {
4300
- // 삼각/로그 함수 — 정본 맵 부재
4301
- sin: "\\sin",
4302
- cos: "\\cos",
4303
- tan: "\\tan",
4304
- sec: "\\sec",
4305
- csc: "\\csc",
4306
- cot: "\\cot",
4307
- log: "\\log",
4308
- ln: "\\ln",
4309
- // 연산자·관계 기호 (정본은 대문자 키만 보유)
4310
- divide: "\\div",
4311
- div: "\\div",
4312
- le: "\\leq",
4313
- ge: "\\geq",
4314
- geq: "\\geq",
4315
- deg: "^\\circ",
4316
- rarrow: "\\rightarrow",
4317
- lrarrow: "\\leftrightarrow",
4318
- rightarrow: "\\rightarrow",
4319
- leftarrow: "\\leftarrow",
4320
- in: "\\in",
4321
- notin: "\\notin",
4322
- emptyset: "\\emptyset",
4323
- subset: "\\subset",
4324
- nsubset: "\\nsubseteq",
4325
- cup: "\\cup",
4326
- cap: "\\cap",
4327
- smallinter: "\\cap",
4328
- smallsum: "\\sum",
4329
- sim: "\\sim",
4330
- circ: "\\circ",
4331
- bot: "\\perp",
4332
- partial: "\\partial",
4333
- nabla: "\\nabla",
4334
- angle: "\\angle",
4335
- triangle: "\\triangle",
4336
- inf: "\\infty",
4337
- left: "\\left",
4338
- right: "\\right",
4339
- // 글꼴 지시자 — 제거
4340
- rm: "",
4341
- it: ""
4342
- };
4343
- var STRUCTURAL_FOLD = /* @__PURE__ */ new Set(["over", "root", "of"]);
4344
- function normalizeWords(input) {
4345
- return input.replace(/(?<![\\A-Za-z0-9])([A-Za-z][A-Za-z0-9]*)(?![A-Za-z0-9])/g, (word) => {
4346
- if (Object.hasOwn(CONVERT_MAP, word) || Object.hasOwn(MIDDLE_CONVERT_MAP, word)) return word;
4347
- const lower = word.toLowerCase();
4348
- if (STRUCTURAL_FOLD.has(lower)) return lower;
4349
- if (Object.hasOwn(WORD_ALIASES, lower)) return WORD_ALIASES[lower];
4350
- if (lower !== word && (Object.hasOwn(CONVERT_MAP, lower) || Object.hasOwn(MIDDLE_CONVERT_MAP, lower))) return lower;
4351
- return word;
4352
- });
4353
- }
4354
- function normalizeScripts(input) {
4355
- return input.replace(/\s*\^\s*/g, "^").replace(/\s*_\s*/g, "_").replace(/\^(?!\{)([^\s{}_^]+)/g, "^{$1}").replace(/_(?!\{)([^\s{}_^]+)/g, "_{$1}").replace(/([_^])/g, " $1 ");
4356
- }
4357
- function normalizeOperators(input) {
4358
- 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 ");
4359
- }
4360
- function hwpEquationToLatex(equation) {
4361
- let s = equation.replace(/\0/g, "").replace(/\s+/g, " ").trim();
4362
- if (!s) return "";
4363
- s = normalizeScripts(s);
4364
- s = normalizeOperators(s);
4365
- s = normalizeWords(s);
4366
- s = s.replace(/\broot\s+(?!\{)(\S+)\s+of(?![A-Za-z0-9])/gi, "root { $1 } of");
4367
- return hmlToLatex(s).replace(/\s+/g, " ").trim();
4368
- }
4369
-
4370
4158
  // src/hwp5/pw-crypto.ts
4371
4159
  import { createCipheriv, createHash as createHash2 } from "crypto";
4372
4160
  var ENCRYPT_VERSION_OFFSET = 44;
@@ -4573,12 +4361,240 @@ function indexHwp5Tables(records, base = 0) {
4573
4361
  return { ids, count: n };
4574
4362
  }
4575
4363
 
4576
- // src/hwp5/parser.ts
4577
- import { createRequire } from "module";
4578
- var require2 = createRequire(import.meta.url);
4579
- var CFB = require2("cfb");
4580
- var MAX_SECTIONS = 100;
4581
- var MAX_TOTAL_DECOMPRESS = 100 * 1024 * 1024;
4364
+ // src/hwp5/numbering.ts
4365
+ var NumberingState = class {
4366
+ currentId = 0;
4367
+ counters = [0, 0, 0, 0, 0, 0, 0];
4368
+ history = /* @__PURE__ */ new Map();
4369
+ /** 번호 문단 처리: 카운터 갱신 후 수준별 카운터 스냅샷 반환 */
4370
+ advance(numberingId, level) {
4371
+ const lv = Math.min(Math.max(level, 0), 6);
4372
+ if (this.currentId !== numberingId) {
4373
+ if (this.currentId !== 0) this.history.set(this.currentId, [...this.counters]);
4374
+ const saved = this.history.get(numberingId);
4375
+ if (saved) {
4376
+ this.counters = [...saved];
4377
+ } else {
4378
+ const prev = this.counters;
4379
+ this.counters = [0, 0, 0, 0, 0, 0, 0];
4380
+ for (let i = 0; i < lv; i++) this.counters[i] = prev[i];
4381
+ }
4382
+ this.currentId = numberingId;
4383
+ }
4384
+ this.counters[lv]++;
4385
+ for (let i = lv + 1; i < 7; i++) this.counters[i] = 0;
4386
+ return [...this.counters];
4387
+ }
4388
+ };
4389
+ function headFormatToNumFmt(code) {
4390
+ switch (code) {
4391
+ case 1:
4392
+ return "circled";
4393
+ case 2:
4394
+ return "romanUpper";
4395
+ case 3:
4396
+ return "romanLower";
4397
+ case 4:
4398
+ return "latinUpper";
4399
+ case 5:
4400
+ return "latinLower";
4401
+ case 8:
4402
+ return "ganada";
4403
+ case 9:
4404
+ return "circledGanada";
4405
+ case 10:
4406
+ return "jamo";
4407
+ case 11:
4408
+ return "circledJamo";
4409
+ case 12:
4410
+ return "hangulNum";
4411
+ case 13:
4412
+ return "hanjaNum";
4413
+ default:
4414
+ return "digit";
4415
+ }
4416
+ }
4417
+ function shapeFormatToNumFmt(code) {
4418
+ switch (code) {
4419
+ case 1:
4420
+ return "circled";
4421
+ case 2:
4422
+ return "romanUpper";
4423
+ case 3:
4424
+ return "romanLower";
4425
+ case 4:
4426
+ return "latinUpper";
4427
+ case 5:
4428
+ return "latinLower";
4429
+ case 6:
4430
+ return "ganada";
4431
+ case 7:
4432
+ return "hangulNum";
4433
+ case 8:
4434
+ return "hanjaNum";
4435
+ default:
4436
+ return "digit";
4437
+ }
4438
+ }
4439
+ var JAMO = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
4440
+ var CIRCLED_JAMO = "\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326A\u326B\u326C\u326D";
4441
+ function fromTable(n, table2) {
4442
+ return n >= 1 && n <= table2.length ? table2[n - 1] : String(n);
4443
+ }
4444
+ function formatLatin(n, upper) {
4445
+ if (n <= 0) return "";
4446
+ let result = "";
4447
+ let num4 = n;
4448
+ while (num4 > 0) {
4449
+ num4--;
4450
+ result = String.fromCharCode((upper ? 65 : 97) + num4 % 26) + result;
4451
+ num4 = Math.floor(num4 / 26);
4452
+ }
4453
+ return result;
4454
+ }
4455
+ function formatEastAsianNumber(n, digits, units, zero) {
4456
+ if (n === 0) return zero;
4457
+ if (n < 0 || n > 99999) return String(n);
4458
+ let result = "";
4459
+ let num4 = n;
4460
+ let unit = 0;
4461
+ while (num4 > 0) {
4462
+ const d = num4 % 10;
4463
+ if (d > 0) {
4464
+ const digitStr = d === 1 && unit > 0 ? "" : digits[d];
4465
+ result = digitStr + units[unit] + result;
4466
+ }
4467
+ num4 = Math.floor(num4 / 10);
4468
+ unit++;
4469
+ }
4470
+ return result;
4471
+ }
4472
+ var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
4473
+ var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
4474
+ var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
4475
+ var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
4476
+ function formatNumber(n, fmt) {
4477
+ switch (fmt) {
4478
+ case "circled":
4479
+ return n >= 1 ? circledNumber(n - 1) : String(n);
4480
+ case "romanUpper":
4481
+ return romanNumeral(n, true);
4482
+ case "romanLower":
4483
+ return romanNumeral(n, false);
4484
+ case "latinUpper":
4485
+ return formatLatin(n, true) || String(n);
4486
+ case "latinLower":
4487
+ return formatLatin(n, false) || String(n);
4488
+ case "ganada":
4489
+ return n >= 1 ? hangulOrdinal(n - 1) : String(n);
4490
+ case "circledGanada":
4491
+ return n >= 1 ? circledHangul(n - 1) : String(n);
4492
+ case "jamo":
4493
+ return fromTable(n, JAMO);
4494
+ case "circledJamo":
4495
+ return fromTable(n, CIRCLED_JAMO);
4496
+ case "hangulNum":
4497
+ return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
4498
+ case "hanjaNum":
4499
+ return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
4500
+ default:
4501
+ return String(n);
4502
+ }
4503
+ }
4504
+ function expandNumberingFormat(formatStr, counters, numbering) {
4505
+ let result = "";
4506
+ let i = 0;
4507
+ while (i < formatStr.length) {
4508
+ const ch = formatStr[i];
4509
+ if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
4510
+ const levelRef = formatStr.charCodeAt(i + 1) - 48;
4511
+ const idx = levelRef - 1;
4512
+ const counterVal = counters[idx] ?? 0;
4513
+ const start = numbering.startNumbers[idx] ?? 1;
4514
+ const num4 = counterVal > 0 ? start - 1 + counterVal : start;
4515
+ result += formatNumber(num4, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
4516
+ i += 2;
4517
+ continue;
4518
+ }
4519
+ result += ch;
4520
+ i++;
4521
+ }
4522
+ return result;
4523
+ }
4524
+
4525
+ // src/hwp5/equation.ts
4526
+ var WORD_ALIASES = {
4527
+ // 삼각/로그 함수 — 정본 맵 부재
4528
+ sin: "\\sin",
4529
+ cos: "\\cos",
4530
+ tan: "\\tan",
4531
+ sec: "\\sec",
4532
+ csc: "\\csc",
4533
+ cot: "\\cot",
4534
+ log: "\\log",
4535
+ ln: "\\ln",
4536
+ // 연산자·관계 기호 (정본은 대문자 키만 보유)
4537
+ divide: "\\div",
4538
+ div: "\\div",
4539
+ le: "\\leq",
4540
+ ge: "\\geq",
4541
+ geq: "\\geq",
4542
+ deg: "^\\circ",
4543
+ rarrow: "\\rightarrow",
4544
+ lrarrow: "\\leftrightarrow",
4545
+ rightarrow: "\\rightarrow",
4546
+ leftarrow: "\\leftarrow",
4547
+ in: "\\in",
4548
+ notin: "\\notin",
4549
+ emptyset: "\\emptyset",
4550
+ subset: "\\subset",
4551
+ nsubset: "\\nsubseteq",
4552
+ cup: "\\cup",
4553
+ cap: "\\cap",
4554
+ smallinter: "\\cap",
4555
+ smallsum: "\\sum",
4556
+ sim: "\\sim",
4557
+ circ: "\\circ",
4558
+ bot: "\\perp",
4559
+ partial: "\\partial",
4560
+ nabla: "\\nabla",
4561
+ angle: "\\angle",
4562
+ triangle: "\\triangle",
4563
+ inf: "\\infty",
4564
+ left: "\\left",
4565
+ right: "\\right",
4566
+ // 글꼴 지시자 — 제거
4567
+ rm: "",
4568
+ it: ""
4569
+ };
4570
+ var STRUCTURAL_FOLD = /* @__PURE__ */ new Set(["over", "root", "of"]);
4571
+ function normalizeWords(input) {
4572
+ return input.replace(/(?<![\\A-Za-z0-9])([A-Za-z][A-Za-z0-9]*)(?![A-Za-z0-9])/g, (word) => {
4573
+ if (Object.hasOwn(CONVERT_MAP, word) || Object.hasOwn(MIDDLE_CONVERT_MAP, word)) return word;
4574
+ const lower = word.toLowerCase();
4575
+ if (STRUCTURAL_FOLD.has(lower)) return lower;
4576
+ if (Object.hasOwn(WORD_ALIASES, lower)) return WORD_ALIASES[lower];
4577
+ if (lower !== word && (Object.hasOwn(CONVERT_MAP, lower) || Object.hasOwn(MIDDLE_CONVERT_MAP, lower))) return lower;
4578
+ return word;
4579
+ });
4580
+ }
4581
+ function normalizeScripts(input) {
4582
+ return input.replace(/\s*\^\s*/g, "^").replace(/\s*_\s*/g, "_").replace(/\^(?!\{)([^\s{}_^]+)/g, "^{$1}").replace(/_(?!\{)([^\s{}_^]+)/g, "_{$1}").replace(/([_^])/g, " $1 ");
4583
+ }
4584
+ function normalizeOperators(input) {
4585
+ 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 ");
4586
+ }
4587
+ function hwpEquationToLatex(equation) {
4588
+ let s = equation.replace(/\0/g, "").replace(/\s+/g, " ").trim();
4589
+ if (!s) return "";
4590
+ s = normalizeScripts(s);
4591
+ s = normalizeOperators(s);
4592
+ s = normalizeWords(s);
4593
+ s = s.replace(/\broot\s+(?!\{)(\S+)\s+of(?![A-Za-z0-9])/gi, "root { $1 } of");
4594
+ return hmlToLatex(s).replace(/\s+/g, " ").trim();
4595
+ }
4596
+
4597
+ // src/hwp5/body.ts
4582
4598
  var MAX_NEST_DEPTH = 8;
4583
4599
  function cid(s) {
4584
4600
  return (s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3)) >>> 0;
@@ -4639,366 +4655,6 @@ function normalizeCtrlId(raw) {
4639
4655
  if (KNOWN_CTRL_IDS.has(sw) || isFieldCtrlId(sw)) return sw;
4640
4656
  return raw;
4641
4657
  }
4642
- function openHwp5Container(buffer, options) {
4643
- let cfb = null;
4644
- let lenientCfb = null;
4645
- const warnings = [];
4646
- try {
4647
- cfb = CFB.parse(buffer);
4648
- } catch {
4649
- try {
4650
- lenientCfb = parseLenientCfb(buffer);
4651
- warnings.push({ message: "\uC190\uC0C1\uB41C CFB \uCEE8\uD14C\uC774\uB108 \u2014 lenient \uBAA8\uB4DC\uB85C \uBCF5\uAD6C", code: "LENIENT_CFB_RECOVERY" });
4652
- } catch {
4653
- throw new KordocError("CFB \uCEE8\uD14C\uC774\uB108 \uD30C\uC2F1 \uC2E4\uD328 (strict \uBC0F lenient \uBAA8\uB450)");
4654
- }
4655
- }
4656
- const readRawStream = (path) => {
4657
- if (cfb) {
4658
- const entry = CFB.find(cfb, path);
4659
- return entry?.content ? Buffer.from(entry.content) : null;
4660
- }
4661
- return lenientCfb.findStream(path);
4662
- };
4663
- const headerData = readRawStream("/FileHeader");
4664
- if (!headerData) throw new KordocError("FileHeader \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
4665
- const header = parseFileHeader(headerData);
4666
- if (header.signature !== "HWP Document File") throw new KordocError("HWP \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
4667
- if (header.flags & FLAG_DRM) throw new KordocError("DRM \uBCF4\uD638\uB41C HWP\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
4668
- const encrypted = (header.flags & FLAG_ENCRYPTED) !== 0;
4669
- if (encrypted) {
4670
- if (!options?.password) {
4671
- 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.");
4672
- }
4673
- assertSupportedEncryptVersion(readEncryptVersion(headerData));
4674
- }
4675
- const password = encrypted ? options.password : void 0;
4676
- const findStream = (path) => {
4677
- const raw = readRawStream(path);
4678
- if (!raw || !password) return raw;
4679
- return decryptPasswordStream(raw, password);
4680
- };
4681
- const compressed = (header.flags & FLAG_COMPRESSED) !== 0;
4682
- const distribution = (header.flags & FLAG_DISTRIBUTION) !== 0;
4683
- if (encrypted) {
4684
- const rawDocInfo = findStream("/DocInfo");
4685
- if (!rawDocInfo) throw new KordocError("DocInfo \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
4686
- let docInfoData;
4687
- try {
4688
- docInfoData = compressed ? decompressStream(rawDocInfo) : rawDocInfo;
4689
- } catch {
4690
- 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.");
4691
- }
4692
- assertDecryptedDocInfo(docInfoData, readRecords);
4693
- }
4694
- return { cfb, lenientCfb, header, compressed, distribution, encrypted, findStream, warnings };
4695
- }
4696
- function readHwp5DocInfoRecords(c) {
4697
- try {
4698
- const raw = c.findStream("/DocInfo");
4699
- if (!raw) return null;
4700
- return readRecords(c.compressed ? decompressStream(raw) : raw);
4701
- } catch {
4702
- return null;
4703
- }
4704
- }
4705
- function readHwp5SectionStreams(c) {
4706
- return c.distribution ? c.cfb ? findViewTextSections(c.cfb, c.compressed) : findViewTextSectionsLenient(c.lenientCfb, c.compressed) : c.encrypted ? findSectionsVia(c.findStream) : c.cfb ? findSections(c.cfb) : findSectionsLenient(c.lenientCfb, c.compressed);
4707
- }
4708
- function readHwp5SectionRecords(c, sections, warnings) {
4709
- const out = [];
4710
- let totalDecompressed = 0;
4711
- for (let si = 0; si < sections.length; si++) {
4712
- try {
4713
- const data = !c.distribution && c.compressed ? decompressStream(Buffer.from(sections[si])) : Buffer.from(sections[si]);
4714
- totalDecompressed += data.length;
4715
- if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
4716
- out.push(readRecords(data));
4717
- } catch (secErr) {
4718
- if (secErr instanceof KordocError) throw secErr;
4719
- out.push(null);
4720
- 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" });
4721
- }
4722
- }
4723
- return out;
4724
- }
4725
- function readHwp5BinData(c) {
4726
- return c.cfb ? collectHwp5BinData(c.cfb.FileIndex) : collectHwp5BinDataLenient(c.lenientCfb);
4727
- }
4728
- function parseHwp5Document(buffer, options) {
4729
- const c = openHwp5Container(buffer, options);
4730
- const { cfb, lenientCfb, compressed, encrypted, findStream, warnings } = c;
4731
- const metadata = {
4732
- version: `${c.header.versionMajor}.x`
4733
- };
4734
- if (cfb) extractHwp5Metadata(cfb, metadata);
4735
- const docInfo = cfb && !encrypted ? parseDocInfoStream(cfb, compressed) : parseDocInfoFromStream(findStream("/DocInfo"), compressed);
4736
- const sections = readHwp5SectionStreams(c);
4737
- if (sections.length === 0) throw new KordocError("\uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
4738
- const sectionRecords = readHwp5SectionRecords(c, sections, warnings);
4739
- const sectionDetects = sectionRecords.map((r) => r ? detectHwp5SectionPages(r) : null);
4740
- const sectionTableIds = [];
4741
- {
4742
- let base = 0;
4743
- for (const r of sectionRecords) {
4744
- const ix = indexHwp5Tables(r ?? [], base);
4745
- sectionTableIds.push(ix.ids);
4746
- base += ix.count;
4747
- }
4748
- }
4749
- const layoutPages = sectionDetects.length > 0 && sectionDetects.every((d) => d != null && d.usable);
4750
- metadata.pageMode = layoutPages ? "layout" : "section";
4751
- metadata.pageCount = layoutPages ? Math.max(sectionDetects.reduce((sum, d) => sum + (d?.pages ?? 0), 0), 1) : sections.length;
4752
- const pageFilter = options?.pages ? parsePageRange(options.pages, metadata.pageCount) : null;
4753
- if (pageFilter && !layoutPages) {
4754
- 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" });
4755
- }
4756
- let bodyBlocks = [];
4757
- const doc = createHwp5DocState();
4758
- doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
4759
- let parsedSections = 0;
4760
- let pageBase = 0;
4761
- for (let si = 0; si < sections.length; si++) {
4762
- const records = sectionRecords[si];
4763
- if (!records) continue;
4764
- const detect = sectionDetects[si];
4765
- if (!layoutPages && pageFilter && !pageFilter.has(si + 1)) continue;
4766
- try {
4767
- const sectionBlocks = parseSection(
4768
- records,
4769
- docInfo,
4770
- warnings,
4771
- si + 1,
4772
- doc,
4773
- layoutPages ? { base: pageBase, pageAtPara: detect.pageAtPara } : void 0,
4774
- sectionTableIds[si]
4775
- );
4776
- bodyBlocks.push(...sectionBlocks);
4777
- parsedSections++;
4778
- options?.onProgress?.(parsedSections, sections.length);
4779
- } catch (secErr) {
4780
- if (secErr instanceof KordocError) throw secErr;
4781
- 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" });
4782
- } finally {
4783
- pageBase += detect.pages;
4784
- }
4785
- }
4786
- if (pageFilter && layoutPages) {
4787
- bodyBlocks = bodyBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
4788
- }
4789
- const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
4790
- const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
4791
- let flatBlocks = flattenLayoutTables(blocks);
4792
- if (options?.dedupeRunningHeaders) {
4793
- const deduped = dedupeRunningHeaders(flatBlocks);
4794
- const removed = flatBlocks.length - deduped.length;
4795
- if (removed > 0) warnings.push({ message: `\uBC18\uBCF5 \uB7EC\uB2DD \uD5E4\uB354 ${removed}\uAC1C \uC81C\uAC70`, code: "HIDDEN_TEXT_FILTERED" });
4796
- flatBlocks = deduped;
4797
- }
4798
- if (docInfo) {
4799
- detectHwp5Headings(flatBlocks, docInfo);
4800
- }
4801
- const outline = flatBlocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
4802
- let markdown = blocksToMarkdown(flatBlocks);
4803
- if (options?.inlineImages && images.length > 0) {
4804
- try {
4805
- markdown = inlineImagesIntoMarkdown(markdown, images, { compress: true });
4806
- } catch (inlineErr) {
4807
- 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" });
4808
- }
4809
- }
4810
- 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 };
4811
- }
4812
- function parseDocInfoStream(cfb, compressed) {
4813
- try {
4814
- const entry = CFB.find(cfb, "/DocInfo");
4815
- if (!entry?.content) return null;
4816
- const data = compressed ? decompressStream(Buffer.from(entry.content)) : Buffer.from(entry.content);
4817
- const records = readRecords(data);
4818
- return parseDocInfo(records);
4819
- } catch {
4820
- return null;
4821
- }
4822
- }
4823
- function parseDocInfoFromStream(raw, compressed) {
4824
- if (!raw) return null;
4825
- try {
4826
- const data = compressed ? decompressStream(raw) : raw;
4827
- return parseDocInfo(readRecords(data));
4828
- } catch {
4829
- return null;
4830
- }
4831
- }
4832
- function detectHwp5Headings(blocks, docInfo) {
4833
- let baseFontSize = 0;
4834
- const sizeFreq = /* @__PURE__ */ new Map();
4835
- for (const b of blocks) {
4836
- if (b.style?.fontSize && b.text) {
4837
- sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + b.text.length);
4838
- }
4839
- }
4840
- let maxWeight = 0;
4841
- for (const [size, weight] of sizeFreq) {
4842
- if (weight > maxWeight) {
4843
- maxWeight = weight;
4844
- baseFontSize = size;
4845
- }
4846
- }
4847
- if (baseFontSize === 0) {
4848
- for (const style of docInfo.styles) {
4849
- const name = (style.nameKo || style.name).toLowerCase();
4850
- if (name.includes("\uBC14\uD0D5") || name.includes("\uBCF8\uBB38") || name === "normal" || name === "body") {
4851
- const cs = docInfo.charShapes[style.charShapeId];
4852
- if (cs?.fontSize > 0) {
4853
- baseFontSize = cs.fontSize / 10;
4854
- break;
4855
- }
4856
- }
4857
- }
4858
- }
4859
- if (baseFontSize <= 0) return;
4860
- for (const block of blocks) {
4861
- if (block.type === "heading") continue;
4862
- if (block.type !== "paragraph" || !block.text) continue;
4863
- const text = block.text.trim();
4864
- if (text.length === 0 || text.length > 200) continue;
4865
- if (/^\d+$/.test(text)) continue;
4866
- let level = 0;
4867
- if (block.style?.fontSize && baseFontSize > 0) {
4868
- const ratio = block.style.fontSize / baseFontSize;
4869
- if (ratio >= HEADING_RATIO_H1) level = 1;
4870
- else if (ratio >= HEADING_RATIO_H2) level = 2;
4871
- else if (ratio >= HEADING_RATIO_H3) level = 3;
4872
- }
4873
- if (/^제\d+[장절편]\s/.test(text) && text.length <= 50) {
4874
- if (level === 0) level = 2;
4875
- } else if (/^제\d+(조의?\d*)\s*[\((]/.test(text) && text.length <= 80) {
4876
- if (level === 0) level = 3;
4877
- }
4878
- if (level > 0) {
4879
- block.type = "heading";
4880
- block.level = level;
4881
- }
4882
- }
4883
- }
4884
- function extractHwp5Metadata(cfb, metadata) {
4885
- try {
4886
- const summaryEntry = CFB.find(cfb, "/HwpSummaryInformation") || CFB.find(cfb, "/SummaryInformation");
4887
- if (!summaryEntry?.content) return;
4888
- const data = Buffer.from(summaryEntry.content);
4889
- if (data.length < 48) return;
4890
- const numSets = data.readUInt32LE(24);
4891
- if (numSets === 0) return;
4892
- const setOffset = data.readUInt32LE(44);
4893
- if (setOffset >= data.length - 8) return;
4894
- const numProps = data.readUInt32LE(setOffset + 4);
4895
- if (numProps === 0 || numProps > 100) return;
4896
- for (let i = 0; i < numProps; i++) {
4897
- const entryOffset = setOffset + 8 + i * 8;
4898
- if (entryOffset + 8 > data.length) break;
4899
- const propId = data.readUInt32LE(entryOffset);
4900
- const propOffset = setOffset + data.readUInt32LE(entryOffset + 4);
4901
- if (propOffset + 8 > data.length) continue;
4902
- if (propId !== 2 && propId !== 4 && propId !== 6) continue;
4903
- const propType = data.readUInt32LE(propOffset);
4904
- if (propType !== 30) continue;
4905
- const strLen = data.readUInt32LE(propOffset + 4);
4906
- if (strLen === 0 || strLen > 1e4 || propOffset + 8 + strLen > data.length) continue;
4907
- const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString("utf8").replace(/\0+$/, "").trim();
4908
- if (!str) continue;
4909
- if (propId === 2) metadata.title = str;
4910
- else if (propId === 4) metadata.author = str;
4911
- else if (propId === 6) metadata.description = str;
4912
- }
4913
- } catch {
4914
- }
4915
- }
4916
- function findViewTextSections(cfb, compressed) {
4917
- const sections = [];
4918
- for (let i = 0; i < MAX_SECTIONS; i++) {
4919
- const entry = CFB.find(cfb, `/ViewText/Section${i}`);
4920
- if (!entry?.content) break;
4921
- try {
4922
- const decrypted = decryptViewText(Buffer.from(entry.content), compressed);
4923
- sections.push({ idx: i, content: decrypted });
4924
- } catch {
4925
- break;
4926
- }
4927
- }
4928
- return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
4929
- }
4930
- function findSectionsVia(read) {
4931
- const sections = [];
4932
- for (let i = 0; i < MAX_SECTIONS; i++) {
4933
- const raw = read(`/BodyText/Section${i}`);
4934
- if (!raw) break;
4935
- sections.push(raw);
4936
- }
4937
- return sections;
4938
- }
4939
- function findSections(cfb) {
4940
- const sections = [];
4941
- for (let i = 0; i < MAX_SECTIONS; i++) {
4942
- const entry = CFB.find(cfb, `/BodyText/Section${i}`);
4943
- if (!entry?.content) break;
4944
- sections.push({ idx: i, content: Buffer.from(entry.content) });
4945
- }
4946
- if (sections.length === 0 && cfb.FileIndex) {
4947
- for (const entry of cfb.FileIndex) {
4948
- if (sections.length >= MAX_SECTIONS) break;
4949
- if (entry.name?.startsWith("Section") && entry.content) {
4950
- const idx = parseInt(entry.name.replace("Section", ""), 10) || 0;
4951
- sections.push({ idx, content: Buffer.from(entry.content) });
4952
- }
4953
- }
4954
- }
4955
- return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
4956
- }
4957
- function findSectionsLenient(lcfb, compressed) {
4958
- const sections = [];
4959
- let totalDecompressed = 0;
4960
- for (let i = 0; i < MAX_SECTIONS; i++) {
4961
- const raw = lcfb.findStream(`/BodyText/Section${i}`) ?? lcfb.findStream(`Section${i}`);
4962
- if (!raw) break;
4963
- const content = compressed ? decompressStream(raw) : raw;
4964
- totalDecompressed += content.length;
4965
- if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
4966
- sections.push({ idx: i, content });
4967
- }
4968
- if (sections.length === 0) {
4969
- for (const e of lcfb.entries()) {
4970
- if (sections.length >= MAX_SECTIONS) break;
4971
- if (e.name.startsWith("Section")) {
4972
- const idx = parseInt(e.name.replace("Section", ""), 10) || 0;
4973
- const raw = lcfb.findStream(e.name);
4974
- if (raw) {
4975
- const content = compressed ? decompressStream(raw) : raw;
4976
- totalDecompressed += content.length;
4977
- if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
4978
- sections.push({ idx, content });
4979
- }
4980
- }
4981
- }
4982
- }
4983
- return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
4984
- }
4985
- function findViewTextSectionsLenient(lcfb, compressed) {
4986
- const sections = [];
4987
- let totalDecompressed = 0;
4988
- for (let i = 0; i < MAX_SECTIONS; i++) {
4989
- const raw = lcfb.findStream(`/ViewText/Section${i}`) ?? lcfb.findStream(`Section${i}`);
4990
- if (!raw) break;
4991
- try {
4992
- const content = decryptViewText(raw, compressed);
4993
- totalDecompressed += content.length;
4994
- if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
4995
- sections.push({ idx: i, content });
4996
- } catch {
4997
- break;
4998
- }
4999
- }
5000
- return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
5001
- }
5002
4658
  function formatEquationForMarkdown(equation) {
5003
4659
  const normalized = hwpEquationToLatex(equation);
5004
4660
  if (!normalized) return "";
@@ -5566,6 +5222,373 @@ function hasRealUnderline(attrFlags) {
5566
5222
  return (attrFlags >> 2 & 3) === 1;
5567
5223
  }
5568
5224
 
5225
+ // src/hwp5/parser.ts
5226
+ import { createRequire } from "module";
5227
+ var require2 = createRequire(import.meta.url);
5228
+ var CFB = require2("cfb");
5229
+ var MAX_SECTIONS = 100;
5230
+ var MAX_TOTAL_DECOMPRESS = 100 * 1024 * 1024;
5231
+ function openHwp5Container(buffer, options) {
5232
+ let cfb = null;
5233
+ let lenientCfb = null;
5234
+ const warnings = [];
5235
+ try {
5236
+ cfb = CFB.parse(buffer);
5237
+ } catch {
5238
+ try {
5239
+ lenientCfb = parseLenientCfb(buffer);
5240
+ warnings.push({ message: "\uC190\uC0C1\uB41C CFB \uCEE8\uD14C\uC774\uB108 \u2014 lenient \uBAA8\uB4DC\uB85C \uBCF5\uAD6C", code: "LENIENT_CFB_RECOVERY" });
5241
+ } catch {
5242
+ throw new KordocError("CFB \uCEE8\uD14C\uC774\uB108 \uD30C\uC2F1 \uC2E4\uD328 (strict \uBC0F lenient \uBAA8\uB450)");
5243
+ }
5244
+ }
5245
+ const readRawStream = (path) => {
5246
+ if (cfb) {
5247
+ const entry = CFB.find(cfb, path);
5248
+ return entry?.content ? Buffer.from(entry.content) : null;
5249
+ }
5250
+ return lenientCfb.findStream(path);
5251
+ };
5252
+ const headerData = readRawStream("/FileHeader");
5253
+ if (!headerData) throw new KordocError("FileHeader \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
5254
+ const header = parseFileHeader(headerData);
5255
+ if (header.signature !== "HWP Document File") throw new KordocError("HWP \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
5256
+ if (header.flags & FLAG_DRM) throw new KordocError("DRM \uBCF4\uD638\uB41C HWP\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
5257
+ const encrypted = (header.flags & FLAG_ENCRYPTED) !== 0;
5258
+ if (encrypted) {
5259
+ if (!options?.password) {
5260
+ 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.");
5261
+ }
5262
+ assertSupportedEncryptVersion(readEncryptVersion(headerData));
5263
+ }
5264
+ const password = encrypted ? options.password : void 0;
5265
+ const findStream = (path) => {
5266
+ const raw = readRawStream(path);
5267
+ if (!raw || !password) return raw;
5268
+ return decryptPasswordStream(raw, password);
5269
+ };
5270
+ const compressed = (header.flags & FLAG_COMPRESSED) !== 0;
5271
+ const distribution = (header.flags & FLAG_DISTRIBUTION) !== 0;
5272
+ if (encrypted) {
5273
+ const rawDocInfo = findStream("/DocInfo");
5274
+ if (!rawDocInfo) throw new KordocError("DocInfo \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
5275
+ let docInfoData;
5276
+ try {
5277
+ docInfoData = compressed ? decompressStream(rawDocInfo) : rawDocInfo;
5278
+ } catch {
5279
+ 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.");
5280
+ }
5281
+ assertDecryptedDocInfo(docInfoData, readRecords);
5282
+ }
5283
+ return { cfb, lenientCfb, header, compressed, distribution, encrypted, findStream, warnings };
5284
+ }
5285
+ function readHwp5DocInfoRecords(c) {
5286
+ try {
5287
+ const raw = c.findStream("/DocInfo");
5288
+ if (!raw) return null;
5289
+ return readRecords(c.compressed ? decompressStream(raw) : raw);
5290
+ } catch {
5291
+ return null;
5292
+ }
5293
+ }
5294
+ function readHwp5SectionStreams(c) {
5295
+ return c.distribution ? c.cfb ? findViewTextSections(c.cfb, c.compressed) : findViewTextSectionsLenient(c.lenientCfb, c.compressed) : c.encrypted ? findSectionsVia(c.findStream) : c.cfb ? findSections(c.cfb) : findSectionsLenient(c.lenientCfb, c.compressed);
5296
+ }
5297
+ function readHwp5SectionRecords(c, sections, warnings) {
5298
+ const out = [];
5299
+ let totalDecompressed = 0;
5300
+ for (let si = 0; si < sections.length; si++) {
5301
+ try {
5302
+ const data = !c.distribution && c.compressed ? decompressStream(Buffer.from(sections[si])) : Buffer.from(sections[si]);
5303
+ totalDecompressed += data.length;
5304
+ if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
5305
+ out.push(readRecords(data));
5306
+ } catch (secErr) {
5307
+ if (secErr instanceof KordocError) throw secErr;
5308
+ out.push(null);
5309
+ 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" });
5310
+ }
5311
+ }
5312
+ return out;
5313
+ }
5314
+ function readHwp5BinData(c) {
5315
+ return c.cfb ? collectHwp5BinData(c.cfb.FileIndex) : collectHwp5BinDataLenient(c.lenientCfb);
5316
+ }
5317
+ function parseHwp5Document(buffer, options) {
5318
+ const c = openHwp5Container(buffer, options);
5319
+ const { cfb, lenientCfb, compressed, encrypted, findStream, warnings } = c;
5320
+ const metadata = {
5321
+ version: `${c.header.versionMajor}.x`
5322
+ };
5323
+ if (cfb) extractHwp5Metadata(cfb, metadata);
5324
+ const docInfo = cfb && !encrypted ? parseDocInfoStream(cfb, compressed) : parseDocInfoFromStream(findStream("/DocInfo"), compressed);
5325
+ const sections = readHwp5SectionStreams(c);
5326
+ if (sections.length === 0) throw new KordocError("\uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
5327
+ const sectionRecords = readHwp5SectionRecords(c, sections, warnings);
5328
+ const sectionDetects = sectionRecords.map((r) => r ? detectHwp5SectionPages(r) : null);
5329
+ const sectionTableIds = [];
5330
+ {
5331
+ let base = 0;
5332
+ for (const r of sectionRecords) {
5333
+ const ix = indexHwp5Tables(r ?? [], base);
5334
+ sectionTableIds.push(ix.ids);
5335
+ base += ix.count;
5336
+ }
5337
+ }
5338
+ const layoutPages = sectionDetects.length > 0 && sectionDetects.every((d) => d != null && d.usable);
5339
+ metadata.pageMode = layoutPages ? "layout" : "section";
5340
+ metadata.pageCount = layoutPages ? Math.max(sectionDetects.reduce((sum, d) => sum + (d?.pages ?? 0), 0), 1) : sections.length;
5341
+ const pageFilter = options?.pages ? parsePageRange(options.pages, metadata.pageCount) : null;
5342
+ if (pageFilter && !layoutPages) {
5343
+ 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" });
5344
+ }
5345
+ let bodyBlocks = [];
5346
+ const doc = createHwp5DocState();
5347
+ doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
5348
+ let parsedSections = 0;
5349
+ let pageBase = 0;
5350
+ for (let si = 0; si < sections.length; si++) {
5351
+ const records = sectionRecords[si];
5352
+ if (!records) continue;
5353
+ const detect = sectionDetects[si];
5354
+ if (!layoutPages && pageFilter && !pageFilter.has(si + 1)) continue;
5355
+ try {
5356
+ const sectionBlocks = parseSection(
5357
+ records,
5358
+ docInfo,
5359
+ warnings,
5360
+ si + 1,
5361
+ doc,
5362
+ layoutPages ? { base: pageBase, pageAtPara: detect.pageAtPara } : void 0,
5363
+ sectionTableIds[si]
5364
+ );
5365
+ bodyBlocks.push(...sectionBlocks);
5366
+ parsedSections++;
5367
+ options?.onProgress?.(parsedSections, sections.length);
5368
+ } catch (secErr) {
5369
+ if (secErr instanceof KordocError) throw secErr;
5370
+ 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" });
5371
+ } finally {
5372
+ pageBase += detect.pages;
5373
+ }
5374
+ }
5375
+ if (pageFilter && layoutPages) {
5376
+ bodyBlocks = bodyBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
5377
+ }
5378
+ const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
5379
+ const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
5380
+ let flatBlocks = flattenLayoutTables(blocks);
5381
+ if (options?.dedupeRunningHeaders) {
5382
+ const deduped = dedupeRunningHeaders(flatBlocks);
5383
+ const removed = flatBlocks.length - deduped.length;
5384
+ if (removed > 0) warnings.push({ message: `\uBC18\uBCF5 \uB7EC\uB2DD \uD5E4\uB354 ${removed}\uAC1C \uC81C\uAC70`, code: "HIDDEN_TEXT_FILTERED" });
5385
+ flatBlocks = deduped;
5386
+ }
5387
+ if (docInfo) {
5388
+ detectHwp5Headings(flatBlocks, docInfo);
5389
+ }
5390
+ const outline = flatBlocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
5391
+ let markdown = blocksToMarkdown(flatBlocks);
5392
+ if (options?.inlineImages && images.length > 0) {
5393
+ try {
5394
+ markdown = inlineImagesIntoMarkdown(markdown, images, { compress: true });
5395
+ } catch (inlineErr) {
5396
+ 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" });
5397
+ }
5398
+ }
5399
+ 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 };
5400
+ }
5401
+ function parseDocInfoStream(cfb, compressed) {
5402
+ try {
5403
+ const entry = CFB.find(cfb, "/DocInfo");
5404
+ if (!entry?.content) return null;
5405
+ const data = compressed ? decompressStream(Buffer.from(entry.content)) : Buffer.from(entry.content);
5406
+ const records = readRecords(data);
5407
+ return parseDocInfo(records);
5408
+ } catch {
5409
+ return null;
5410
+ }
5411
+ }
5412
+ function parseDocInfoFromStream(raw, compressed) {
5413
+ if (!raw) return null;
5414
+ try {
5415
+ const data = compressed ? decompressStream(raw) : raw;
5416
+ return parseDocInfo(readRecords(data));
5417
+ } catch {
5418
+ return null;
5419
+ }
5420
+ }
5421
+ function detectHwp5Headings(blocks, docInfo) {
5422
+ let baseFontSize = 0;
5423
+ const sizeFreq = /* @__PURE__ */ new Map();
5424
+ for (const b of blocks) {
5425
+ if (b.style?.fontSize && b.text) {
5426
+ sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + b.text.length);
5427
+ }
5428
+ }
5429
+ let maxWeight = 0;
5430
+ for (const [size, weight] of sizeFreq) {
5431
+ if (weight > maxWeight) {
5432
+ maxWeight = weight;
5433
+ baseFontSize = size;
5434
+ }
5435
+ }
5436
+ if (baseFontSize === 0) {
5437
+ for (const style of docInfo.styles) {
5438
+ const name = (style.nameKo || style.name).toLowerCase();
5439
+ if (name.includes("\uBC14\uD0D5") || name.includes("\uBCF8\uBB38") || name === "normal" || name === "body") {
5440
+ const cs = docInfo.charShapes[style.charShapeId];
5441
+ if (cs?.fontSize > 0) {
5442
+ baseFontSize = cs.fontSize / 10;
5443
+ break;
5444
+ }
5445
+ }
5446
+ }
5447
+ }
5448
+ if (baseFontSize <= 0) return;
5449
+ for (const block of blocks) {
5450
+ if (block.type === "heading") continue;
5451
+ if (block.type !== "paragraph" || !block.text) continue;
5452
+ const text = block.text.trim();
5453
+ if (text.length === 0 || text.length > 200) continue;
5454
+ if (/^\d+$/.test(text)) continue;
5455
+ let level = 0;
5456
+ if (block.style?.fontSize && baseFontSize > 0) {
5457
+ const ratio = block.style.fontSize / baseFontSize;
5458
+ if (ratio >= HEADING_RATIO_H1) level = 1;
5459
+ else if (ratio >= HEADING_RATIO_H2) level = 2;
5460
+ else if (ratio >= HEADING_RATIO_H3) level = 3;
5461
+ }
5462
+ if (/^제\d+[장절편]\s/.test(text) && text.length <= 50) {
5463
+ if (level === 0) level = 2;
5464
+ } else if (/^제\d+(조의?\d*)\s*[\((]/.test(text) && text.length <= 80) {
5465
+ if (level === 0) level = 3;
5466
+ }
5467
+ if (level > 0) {
5468
+ block.type = "heading";
5469
+ block.level = level;
5470
+ }
5471
+ }
5472
+ }
5473
+ function extractHwp5Metadata(cfb, metadata) {
5474
+ try {
5475
+ const summaryEntry = CFB.find(cfb, "/HwpSummaryInformation") || CFB.find(cfb, "/SummaryInformation");
5476
+ if (!summaryEntry?.content) return;
5477
+ const data = Buffer.from(summaryEntry.content);
5478
+ if (data.length < 48) return;
5479
+ const numSets = data.readUInt32LE(24);
5480
+ if (numSets === 0) return;
5481
+ const setOffset = data.readUInt32LE(44);
5482
+ if (setOffset >= data.length - 8) return;
5483
+ const numProps = data.readUInt32LE(setOffset + 4);
5484
+ if (numProps === 0 || numProps > 100) return;
5485
+ for (let i = 0; i < numProps; i++) {
5486
+ const entryOffset = setOffset + 8 + i * 8;
5487
+ if (entryOffset + 8 > data.length) break;
5488
+ const propId = data.readUInt32LE(entryOffset);
5489
+ const propOffset = setOffset + data.readUInt32LE(entryOffset + 4);
5490
+ if (propOffset + 8 > data.length) continue;
5491
+ if (propId !== 2 && propId !== 4 && propId !== 6) continue;
5492
+ const propType = data.readUInt32LE(propOffset);
5493
+ if (propType !== 30) continue;
5494
+ const strLen = data.readUInt32LE(propOffset + 4);
5495
+ if (strLen === 0 || strLen > 1e4 || propOffset + 8 + strLen > data.length) continue;
5496
+ const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString("utf8").replace(/\0+$/, "").trim();
5497
+ if (!str) continue;
5498
+ if (propId === 2) metadata.title = str;
5499
+ else if (propId === 4) metadata.author = str;
5500
+ else if (propId === 6) metadata.description = str;
5501
+ }
5502
+ } catch {
5503
+ }
5504
+ }
5505
+ function findViewTextSections(cfb, compressed) {
5506
+ const sections = [];
5507
+ for (let i = 0; i < MAX_SECTIONS; i++) {
5508
+ const entry = CFB.find(cfb, `/ViewText/Section${i}`);
5509
+ if (!entry?.content) break;
5510
+ try {
5511
+ const decrypted = decryptViewText(Buffer.from(entry.content), compressed);
5512
+ sections.push({ idx: i, content: decrypted });
5513
+ } catch {
5514
+ break;
5515
+ }
5516
+ }
5517
+ return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
5518
+ }
5519
+ function findSectionsVia(read) {
5520
+ const sections = [];
5521
+ for (let i = 0; i < MAX_SECTIONS; i++) {
5522
+ const raw = read(`/BodyText/Section${i}`);
5523
+ if (!raw) break;
5524
+ sections.push(raw);
5525
+ }
5526
+ return sections;
5527
+ }
5528
+ function findSections(cfb) {
5529
+ const sections = [];
5530
+ for (let i = 0; i < MAX_SECTIONS; i++) {
5531
+ const entry = CFB.find(cfb, `/BodyText/Section${i}`);
5532
+ if (!entry?.content) break;
5533
+ sections.push({ idx: i, content: Buffer.from(entry.content) });
5534
+ }
5535
+ if (sections.length === 0 && cfb.FileIndex) {
5536
+ for (const entry of cfb.FileIndex) {
5537
+ if (sections.length >= MAX_SECTIONS) break;
5538
+ if (entry.name?.startsWith("Section") && entry.content) {
5539
+ const idx = parseInt(entry.name.replace("Section", ""), 10) || 0;
5540
+ sections.push({ idx, content: Buffer.from(entry.content) });
5541
+ }
5542
+ }
5543
+ }
5544
+ return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
5545
+ }
5546
+ function findSectionsLenient(lcfb, compressed) {
5547
+ const sections = [];
5548
+ let totalDecompressed = 0;
5549
+ for (let i = 0; i < MAX_SECTIONS; i++) {
5550
+ const raw = lcfb.findStream(`/BodyText/Section${i}`) ?? lcfb.findStream(`Section${i}`);
5551
+ if (!raw) break;
5552
+ const content = compressed ? decompressStream(raw) : raw;
5553
+ totalDecompressed += content.length;
5554
+ if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
5555
+ sections.push({ idx: i, content });
5556
+ }
5557
+ if (sections.length === 0) {
5558
+ for (const e of lcfb.entries()) {
5559
+ if (sections.length >= MAX_SECTIONS) break;
5560
+ if (e.name.startsWith("Section")) {
5561
+ const idx = parseInt(e.name.replace("Section", ""), 10) || 0;
5562
+ const raw = lcfb.findStream(e.name);
5563
+ if (raw) {
5564
+ const content = compressed ? decompressStream(raw) : raw;
5565
+ totalDecompressed += content.length;
5566
+ if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
5567
+ sections.push({ idx, content });
5568
+ }
5569
+ }
5570
+ }
5571
+ }
5572
+ return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
5573
+ }
5574
+ function findViewTextSectionsLenient(lcfb, compressed) {
5575
+ const sections = [];
5576
+ let totalDecompressed = 0;
5577
+ for (let i = 0; i < MAX_SECTIONS; i++) {
5578
+ const raw = lcfb.findStream(`/ViewText/Section${i}`) ?? lcfb.findStream(`Section${i}`);
5579
+ if (!raw) break;
5580
+ try {
5581
+ const content = decryptViewText(raw, compressed);
5582
+ totalDecompressed += content.length;
5583
+ if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
5584
+ sections.push({ idx: i, content });
5585
+ } catch {
5586
+ break;
5587
+ }
5588
+ }
5589
+ return sections.sort((a, b) => a.idx - b.idx).map((s) => s.content);
5590
+ }
5591
+
5569
5592
  // src/hwp3/parser.ts
5570
5593
  import { inflateRawSync as inflateRawSync5 } from "zlib";
5571
5594
 
@@ -21524,7 +21547,7 @@ function decodeXmlEntities(text) {
21524
21547
  }
21525
21548
  function tContentToText(raw) {
21526
21549
  return decodeXmlEntities(
21527
- raw.replace(/<\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\s[^>]*)?\/?>/g, " ").replace(/<[^>]*>/g, "")
21550
+ raw.replace(/<\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|nbSpace|br|lineBreak)(?:\s[^>]*)?\/?>/g, " ").replace(/<[^>]*>/g, "")
21528
21551
  );
21529
21552
  }
21530
21553
  var TAG_RE = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<!(?:"[^"]*"|'[^']*'|[^>"'])*>|<\/([^\s>]+)\s*>|<([^\s/>!?]+)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
@@ -21692,7 +21715,7 @@ function scanSectionXml(xml, sectionIndex) {
21692
21715
  if (local === "t") {
21693
21716
  const para2 = owningPara();
21694
21717
  if (para2) para2.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) });
21695
- } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
21718
+ } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "nbSpace" || local === "br" || local === "lineBreak") {
21696
21719
  if (!pendingT) {
21697
21720
  const para2 = owningPara();
21698
21721
  if (para2) para2.text += " ";
@@ -21793,7 +21816,7 @@ function scanSectionXml(xml, sectionIndex) {
21793
21816
  if (stack.some((f) => f.local === "ctrl")) {
21794
21817
  ctrlSubStack.push({ kind: local, texts: [] });
21795
21818
  }
21796
- } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
21819
+ } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "nbSpace" || local === "br" || local === "lineBreak") {
21797
21820
  const para2 = owningPara();
21798
21821
  if (para2) para2.text += " ";
21799
21822
  }
@@ -21937,6 +21960,13 @@ function buildParagraphSplices(para2, newText, xml) {
21937
21960
  }
21938
21961
  return newText ? null : [];
21939
21962
  }
21963
+ function markerRunSplices(para2, xml, newText) {
21964
+ const first = para2.tRanges[0];
21965
+ if (!first || first.selfClosing || para2.tRanges.length < 2) return null;
21966
+ const m = /^([^<&\s]{1,6})<(?:\w+:)?tab\b[^>]*\/>$/.exec(xml.slice(first.contentStart, first.contentEnd));
21967
+ if (!m || !newText.startsWith(m[1] + " ")) return null;
21968
+ return buildParagraphSplices({ ...para2, tRanges: para2.tRanges.slice(1) }, newText.slice(m[1].length + 1), xml);
21969
+ }
21940
21970
  function paraTText(para2, xml) {
21941
21971
  let text = "";
21942
21972
  for (const t of para2.tRanges) {
@@ -22836,6 +22866,1277 @@ function blocksToPages(blocks) {
22836
22866
  // src/hwpx/generator.ts
22837
22867
  import JSZip8 from "jszip";
22838
22868
 
22869
+ // src/hwpx/font-metrics.ts
22870
+ var FONTS = {
22871
+ "\uD55C\uCEF4\uB3CB\uC6C0": {
22872
+ cjk: 1e3,
22873
+ ascii: [
22874
+ 500,
22875
+ 334,
22876
+ 415,
22877
+ 625,
22878
+ 500,
22879
+ 915,
22880
+ 709,
22881
+ 290,
22882
+ 375,
22883
+ 375,
22884
+ 583,
22885
+ 580,
22886
+ 375,
22887
+ 590,
22888
+ 375,
22889
+ 415,
22890
+ 583,
22891
+ 583,
22892
+ 583,
22893
+ 583,
22894
+ 583,
22895
+ 583,
22896
+ 583,
22897
+ 583,
22898
+ 583,
22899
+ 583,
22900
+ 340,
22901
+ 340,
22902
+ 625,
22903
+ 584,
22904
+ 625,
22905
+ 583,
22906
+ 1e3,
22907
+ 665,
22908
+ 680,
22909
+ 720,
22910
+ 725,
22911
+ 629,
22912
+ 600,
22913
+ 750,
22914
+ 724,
22915
+ 259,
22916
+ 484,
22917
+ 655,
22918
+ 540,
22919
+ 810,
22920
+ 695,
22921
+ 750,
22922
+ 644,
22923
+ 750,
22924
+ 668,
22925
+ 640,
22926
+ 583,
22927
+ 725,
22928
+ 600,
22929
+ 894,
22930
+ 604,
22931
+ 600,
22932
+ 604,
22933
+ 500,
22934
+ 333,
22935
+ 500,
22936
+ 625,
22937
+ 500,
22938
+ 330,
22939
+ 583,
22940
+ 600,
22941
+ 560,
22942
+ 600,
22943
+ 560,
22944
+ 344,
22945
+ 599,
22946
+ 560,
22947
+ 229,
22948
+ 229,
22949
+ 510,
22950
+ 230,
22951
+ 800,
22952
+ 568,
22953
+ 600,
22954
+ 600,
22955
+ 600,
22956
+ 320,
22957
+ 515,
22958
+ 313,
22959
+ 568,
22960
+ 475,
22961
+ 725,
22962
+ 479,
22963
+ 480,
22964
+ 480,
22965
+ 500,
22966
+ 500,
22967
+ 500,
22968
+ 790
22969
+ ],
22970
+ ex: {
22971
+ 167: 561,
22972
+ 176: 405,
22973
+ 177: 833,
22974
+ 180: 333,
22975
+ 183: 331,
22976
+ 215: 833,
22977
+ 247: 833,
22978
+ 8208: 261,
22979
+ 8209: 314,
22980
+ 8210: 431,
22981
+ 8211: 500,
22982
+ 8212: 687,
22983
+ 8214: 429,
22984
+ 8215: 548,
22985
+ 8216: 291,
22986
+ 8217: 291,
22987
+ 8218: 291,
22988
+ 8219: 291,
22989
+ 8220: 458,
22990
+ 8221: 458,
22991
+ 8222: 458,
22992
+ 8223: 458,
22993
+ 8224: 562,
22994
+ 8225: 562,
22995
+ 8226: 463,
22996
+ 8227: 466,
22997
+ 8228: 174,
22998
+ 8229: 566,
22999
+ 8230: 830,
23000
+ 8231: 301,
23001
+ 8239: 500,
23002
+ 8242: 335,
23003
+ 8243: 563,
23004
+ 8244: 734,
23005
+ 8245: 335,
23006
+ 8246: 563,
23007
+ 8247: 734,
23008
+ 8248: 408,
23009
+ 8249: 375,
23010
+ 8250: 375,
23011
+ 8252: 625,
23012
+ 8253: 465,
23013
+ 8254: 500,
23014
+ 8255: 804,
23015
+ 8256: 804,
23016
+ 8257: 356,
23017
+ 8259: 224,
23018
+ 8260: 734,
23019
+ 8261: 320,
23020
+ 8262: 320,
23021
+ 8264: 656,
23022
+ 8265: 656,
23023
+ 8266: 456,
23024
+ 8267: 562,
23025
+ 8268: 562,
23026
+ 8269: 562,
23027
+ 8272: 804,
23028
+ 8728: 500,
23029
+ 8729: 500
23030
+ }
23031
+ },
23032
+ "HY\uACAC\uACE0\uB515": {
23033
+ cjk: 1e3,
23034
+ ascii: [
23035
+ 500,
23036
+ 416,
23037
+ 583,
23038
+ 708,
23039
+ 625,
23040
+ 958,
23041
+ 791,
23042
+ 333,
23043
+ 458,
23044
+ 458,
23045
+ 541,
23046
+ 666,
23047
+ 375,
23048
+ 666,
23049
+ 375,
23050
+ 416,
23051
+ 625,
23052
+ 625,
23053
+ 625,
23054
+ 625,
23055
+ 625,
23056
+ 625,
23057
+ 625,
23058
+ 625,
23059
+ 625,
23060
+ 625,
23061
+ 375,
23062
+ 375,
23063
+ 583,
23064
+ 666,
23065
+ 583,
23066
+ 708,
23067
+ 1e3,
23068
+ 791,
23069
+ 791,
23070
+ 791,
23071
+ 791,
23072
+ 708,
23073
+ 666,
23074
+ 833,
23075
+ 791,
23076
+ 375,
23077
+ 583,
23078
+ 833,
23079
+ 666,
23080
+ 875,
23081
+ 791,
23082
+ 833,
23083
+ 708,
23084
+ 833,
23085
+ 791,
23086
+ 750,
23087
+ 708,
23088
+ 791,
23089
+ 708,
23090
+ 1e3,
23091
+ 750,
23092
+ 750,
23093
+ 708,
23094
+ 458,
23095
+ 416,
23096
+ 458,
23097
+ 541,
23098
+ 500,
23099
+ 250,
23100
+ 625,
23101
+ 666,
23102
+ 625,
23103
+ 666,
23104
+ 625,
23105
+ 375,
23106
+ 666,
23107
+ 666,
23108
+ 375,
23109
+ 333,
23110
+ 666,
23111
+ 375,
23112
+ 958,
23113
+ 666,
23114
+ 666,
23115
+ 666,
23116
+ 666,
23117
+ 458,
23118
+ 625,
23119
+ 416,
23120
+ 666,
23121
+ 625,
23122
+ 833,
23123
+ 625,
23124
+ 625,
23125
+ 583,
23126
+ 500,
23127
+ 375,
23128
+ 500,
23129
+ 666
23130
+ ],
23131
+ ex: {}
23132
+ },
23133
+ "\uD734\uBA3C\uBA85\uC870": {
23134
+ cjk: 1e3,
23135
+ ascii: [
23136
+ 500,
23137
+ 203,
23138
+ 500,
23139
+ 656,
23140
+ 504,
23141
+ 750,
23142
+ 770,
23143
+ 289,
23144
+ 313,
23145
+ 313,
23146
+ 500,
23147
+ 500,
23148
+ 270,
23149
+ 500,
23150
+ 270,
23151
+ 313,
23152
+ 500,
23153
+ 500,
23154
+ 500,
23155
+ 500,
23156
+ 500,
23157
+ 500,
23158
+ 500,
23159
+ 500,
23160
+ 500,
23161
+ 500,
23162
+ 270,
23163
+ 270,
23164
+ 500,
23165
+ 500,
23166
+ 500,
23167
+ 430,
23168
+ 777,
23169
+ 660,
23170
+ 602,
23171
+ 641,
23172
+ 680,
23173
+ 625,
23174
+ 582,
23175
+ 703,
23176
+ 680,
23177
+ 258,
23178
+ 395,
23179
+ 645,
23180
+ 578,
23181
+ 781,
23182
+ 691,
23183
+ 715,
23184
+ 574,
23185
+ 688,
23186
+ 652,
23187
+ 496,
23188
+ 625,
23189
+ 676,
23190
+ 664,
23191
+ 934,
23192
+ 711,
23193
+ 672,
23194
+ 598,
23195
+ 320,
23196
+ 313,
23197
+ 320,
23198
+ 363,
23199
+ 500,
23200
+ 324,
23201
+ 508,
23202
+ 543,
23203
+ 484,
23204
+ 508,
23205
+ 523,
23206
+ 391,
23207
+ 516,
23208
+ 555,
23209
+ 250,
23210
+ 266,
23211
+ 523,
23212
+ 250,
23213
+ 797,
23214
+ 551,
23215
+ 512,
23216
+ 539,
23217
+ 539,
23218
+ 402,
23219
+ 410,
23220
+ 340,
23221
+ 539,
23222
+ 539,
23223
+ 723,
23224
+ 488,
23225
+ 512,
23226
+ 453,
23227
+ 293,
23228
+ 203,
23229
+ 293,
23230
+ 500
23231
+ ],
23232
+ ex: {}
23233
+ },
23234
+ "HY\uD5E4\uB4DC\uB77C\uC778M": {
23235
+ cjk: 1e3,
23236
+ ascii: [
23237
+ 500,
23238
+ 250,
23239
+ 375,
23240
+ 833,
23241
+ 584,
23242
+ 798,
23243
+ 583,
23244
+ 208,
23245
+ 291,
23246
+ 294,
23247
+ 480,
23248
+ 587,
23249
+ 238,
23250
+ 503,
23251
+ 242,
23252
+ 384,
23253
+ 583,
23254
+ 583,
23255
+ 583,
23256
+ 583,
23257
+ 583,
23258
+ 583,
23259
+ 583,
23260
+ 583,
23261
+ 583,
23262
+ 583,
23263
+ 265,
23264
+ 265,
23265
+ 582,
23266
+ 587,
23267
+ 588,
23268
+ 583,
23269
+ 853,
23270
+ 583,
23271
+ 629,
23272
+ 629,
23273
+ 629,
23274
+ 506,
23275
+ 486,
23276
+ 636,
23277
+ 635,
23278
+ 290,
23279
+ 509,
23280
+ 605,
23281
+ 461,
23282
+ 806,
23283
+ 614,
23284
+ 621,
23285
+ 566,
23286
+ 621,
23287
+ 596,
23288
+ 592,
23289
+ 520,
23290
+ 625,
23291
+ 625,
23292
+ 917,
23293
+ 583,
23294
+ 552,
23295
+ 485,
23296
+ 323,
23297
+ 384,
23298
+ 322,
23299
+ 542,
23300
+ 500,
23301
+ 324,
23302
+ 583,
23303
+ 583,
23304
+ 579,
23305
+ 583,
23306
+ 617,
23307
+ 423,
23308
+ 592,
23309
+ 601,
23310
+ 283,
23311
+ 338,
23312
+ 542,
23313
+ 281,
23314
+ 847,
23315
+ 589,
23316
+ 625,
23317
+ 594,
23318
+ 594,
23319
+ 405,
23320
+ 542,
23321
+ 418,
23322
+ 599,
23323
+ 542,
23324
+ 792,
23325
+ 500,
23326
+ 579,
23327
+ 447,
23328
+ 417,
23329
+ 292,
23330
+ 417,
23331
+ 708
23332
+ ],
23333
+ ex: {}
23334
+ },
23335
+ "\uAD74\uB9BC\uCCB4": {
23336
+ cjk: 1e3,
23337
+ ascii: [
23338
+ 500,
23339
+ 500,
23340
+ 500,
23341
+ 500,
23342
+ 500,
23343
+ 500,
23344
+ 500,
23345
+ 500,
23346
+ 500,
23347
+ 500,
23348
+ 500,
23349
+ 500,
23350
+ 500,
23351
+ 500,
23352
+ 500,
23353
+ 500,
23354
+ 500,
23355
+ 500,
23356
+ 500,
23357
+ 500,
23358
+ 500,
23359
+ 500,
23360
+ 500,
23361
+ 500,
23362
+ 500,
23363
+ 500,
23364
+ 500,
23365
+ 500,
23366
+ 500,
23367
+ 500,
23368
+ 500,
23369
+ 500,
23370
+ 500,
23371
+ 500,
23372
+ 500,
23373
+ 500,
23374
+ 500,
23375
+ 500,
23376
+ 500,
23377
+ 500,
23378
+ 500,
23379
+ 500,
23380
+ 500,
23381
+ 500,
23382
+ 500,
23383
+ 500,
23384
+ 500,
23385
+ 500,
23386
+ 500,
23387
+ 500,
23388
+ 500,
23389
+ 500,
23390
+ 500,
23391
+ 500,
23392
+ 500,
23393
+ 500,
23394
+ 500,
23395
+ 500,
23396
+ 500,
23397
+ 500,
23398
+ 500,
23399
+ 500,
23400
+ 500,
23401
+ 500,
23402
+ 500,
23403
+ 500,
23404
+ 500,
23405
+ 500,
23406
+ 500,
23407
+ 500,
23408
+ 500,
23409
+ 500,
23410
+ 500,
23411
+ 500,
23412
+ 500,
23413
+ 500,
23414
+ 500,
23415
+ 500,
23416
+ 500,
23417
+ 500,
23418
+ 500,
23419
+ 500,
23420
+ 500,
23421
+ 500,
23422
+ 500,
23423
+ 500,
23424
+ 500,
23425
+ 500,
23426
+ 500,
23427
+ 500,
23428
+ 500,
23429
+ 500,
23430
+ 500,
23431
+ 500,
23432
+ 500
23433
+ ],
23434
+ ex: {
23435
+ 8211: 500,
23436
+ 8212: 500,
23437
+ 8215: 500,
23438
+ 8218: 500,
23439
+ 8219: 500,
23440
+ 8222: 500,
23441
+ 8226: 500,
23442
+ 8249: 500,
23443
+ 8250: 500,
23444
+ 8252: 500,
23445
+ 8254: 500,
23446
+ 8260: 500,
23447
+ 8453: 500,
23448
+ 8494: 500,
23449
+ 8616: 500,
23450
+ 8710: 500,
23451
+ 8722: 500,
23452
+ 8725: 500,
23453
+ 8729: 500,
23454
+ 8735: 500,
23455
+ 8776: 500,
23456
+ 9552: 500,
23457
+ 9553: 500,
23458
+ 9554: 500,
23459
+ 9555: 500,
23460
+ 9556: 500,
23461
+ 9557: 500,
23462
+ 9558: 500,
23463
+ 9559: 500,
23464
+ 9560: 500,
23465
+ 9561: 500,
23466
+ 9562: 500,
23467
+ 9563: 500,
23468
+ 9564: 500,
23469
+ 9565: 500,
23470
+ 9566: 500,
23471
+ 9567: 500,
23472
+ 9568: 500,
23473
+ 9569: 500,
23474
+ 9570: 500,
23475
+ 9571: 500,
23476
+ 9572: 500,
23477
+ 9573: 500,
23478
+ 9574: 500,
23479
+ 9575: 500,
23480
+ 9576: 500,
23481
+ 9577: 500,
23482
+ 9578: 500,
23483
+ 9579: 500,
23484
+ 9580: 500,
23485
+ 9600: 500,
23486
+ 9604: 500,
23487
+ 9608: 500,
23488
+ 9612: 500,
23489
+ 9616: 500,
23490
+ 9617: 500,
23491
+ 9619: 500,
23492
+ 9642: 500,
23493
+ 9643: 500,
23494
+ 9644: 500,
23495
+ 9658: 500,
23496
+ 9668: 500,
23497
+ 9674: 500,
23498
+ 9688: 500,
23499
+ 9689: 500,
23500
+ 9702: 500,
23501
+ 9786: 500,
23502
+ 9787: 500,
23503
+ 9788: 500,
23504
+ 9830: 500,
23505
+ 9835: 500
23506
+ }
23507
+ },
23508
+ "\uAD74\uB9BC": {
23509
+ cjk: 1e3,
23510
+ ascii: [
23511
+ 500,
23512
+ 333,
23513
+ 375,
23514
+ 750,
23515
+ 598,
23516
+ 875,
23517
+ 688,
23518
+ 283,
23519
+ 375,
23520
+ 375,
23521
+ 500,
23522
+ 625,
23523
+ 334,
23524
+ 625,
23525
+ 334,
23526
+ 417,
23527
+ 574,
23528
+ 574,
23529
+ 574,
23530
+ 574,
23531
+ 574,
23532
+ 574,
23533
+ 574,
23534
+ 574,
23535
+ 574,
23536
+ 574,
23537
+ 334,
23538
+ 334,
23539
+ 625,
23540
+ 625,
23541
+ 625,
23542
+ 542,
23543
+ 1e3,
23544
+ 646,
23545
+ 677,
23546
+ 718,
23547
+ 722,
23548
+ 625,
23549
+ 596,
23550
+ 770,
23551
+ 730,
23552
+ 270,
23553
+ 500,
23554
+ 635,
23555
+ 543,
23556
+ 813,
23557
+ 699,
23558
+ 770,
23559
+ 637,
23560
+ 773,
23561
+ 666,
23562
+ 633,
23563
+ 582,
23564
+ 711,
23565
+ 600,
23566
+ 895,
23567
+ 625,
23568
+ 625,
23569
+ 625,
23570
+ 500,
23571
+ 917,
23572
+ 500,
23573
+ 542,
23574
+ 500,
23575
+ 333,
23576
+ 563,
23577
+ 605,
23578
+ 572,
23579
+ 605,
23580
+ 567,
23581
+ 342,
23582
+ 605,
23583
+ 571,
23584
+ 241,
23585
+ 241,
23586
+ 500,
23587
+ 231,
23588
+ 861,
23589
+ 580,
23590
+ 605,
23591
+ 605,
23592
+ 605,
23593
+ 333,
23594
+ 525,
23595
+ 313,
23596
+ 570,
23597
+ 500,
23598
+ 750,
23599
+ 500,
23600
+ 500,
23601
+ 500,
23602
+ 500,
23603
+ 500,
23604
+ 500,
23605
+ 792
23606
+ ],
23607
+ ex: {
23608
+ 167: 563,
23609
+ 176: 375,
23610
+ 177: 833,
23611
+ 180: 375,
23612
+ 183: 375,
23613
+ 215: 833,
23614
+ 247: 833,
23615
+ 8211: 500,
23616
+ 8213: 688,
23617
+ 8215: 500,
23618
+ 8216: 333,
23619
+ 8217: 333,
23620
+ 8218: 333,
23621
+ 8219: 333,
23622
+ 8220: 500,
23623
+ 8221: 500,
23624
+ 8222: 500,
23625
+ 8224: 563,
23626
+ 8225: 563,
23627
+ 8226: 375,
23628
+ 8242: 375,
23629
+ 8243: 500,
23630
+ 8249: 375,
23631
+ 8250: 375,
23632
+ 8252: 625,
23633
+ 8254: 500,
23634
+ 8260: 417,
23635
+ 8453: 846,
23636
+ 8467: 500,
23637
+ 8486: 750,
23638
+ 8494: 750,
23639
+ 8539: 563,
23640
+ 8540: 563,
23641
+ 8541: 563,
23642
+ 8542: 563,
23643
+ 8593: 750,
23644
+ 8595: 750,
23645
+ 8597: 750,
23646
+ 8616: 750,
23647
+ 8706: 563,
23648
+ 8710: 613,
23649
+ 8719: 688,
23650
+ 8721: 813,
23651
+ 8722: 833,
23652
+ 8725: 417,
23653
+ 8729: 250,
23654
+ 8730: 549,
23655
+ 8734: 913,
23656
+ 8735: 982,
23657
+ 8745: 719,
23658
+ 8747: 375,
23659
+ 8776: 833,
23660
+ 8800: 833,
23661
+ 8801: 833,
23662
+ 8804: 833,
23663
+ 8805: 833,
23664
+ 9632: 890,
23665
+ 9633: 890,
23666
+ 9642: 375,
23667
+ 9643: 375,
23668
+ 9650: 914,
23669
+ 9658: 750,
23670
+ 9660: 914,
23671
+ 9668: 750,
23672
+ 9674: 774,
23673
+ 9702: 375,
23674
+ 9788: 916,
23675
+ 9792: 625,
23676
+ 9794: 625,
23677
+ 9824: 750,
23678
+ 9827: 844,
23679
+ 9829: 793,
23680
+ 9830: 774,
23681
+ 9834: 688,
23682
+ 9835: 763
23683
+ }
23684
+ },
23685
+ "\uB9D1\uC740 \uACE0\uB515": {
23686
+ cjk: 1e3,
23687
+ ascii: [
23688
+ 500,
23689
+ 289,
23690
+ 395,
23691
+ 606,
23692
+ 551,
23693
+ 836,
23694
+ 818,
23695
+ 232,
23696
+ 305,
23697
+ 305,
23698
+ 425,
23699
+ 701,
23700
+ 219,
23701
+ 410,
23702
+ 219,
23703
+ 396,
23704
+ 551,
23705
+ 551,
23706
+ 551,
23707
+ 551,
23708
+ 551,
23709
+ 551,
23710
+ 551,
23711
+ 551,
23712
+ 551,
23713
+ 551,
23714
+ 219,
23715
+ 219,
23716
+ 701,
23717
+ 701,
23718
+ 701,
23719
+ 460,
23720
+ 979,
23721
+ 658,
23722
+ 583,
23723
+ 635,
23724
+ 717,
23725
+ 517,
23726
+ 499,
23727
+ 702,
23728
+ 725,
23729
+ 270,
23730
+ 360,
23731
+ 590,
23732
+ 480,
23733
+ 917,
23734
+ 765,
23735
+ 773,
23736
+ 571,
23737
+ 773,
23738
+ 610,
23739
+ 543,
23740
+ 534,
23741
+ 703,
23742
+ 634,
23743
+ 954,
23744
+ 601,
23745
+ 563,
23746
+ 583,
23747
+ 305,
23748
+ 764,
23749
+ 305,
23750
+ 701,
23751
+ 426,
23752
+ 272,
23753
+ 520,
23754
+ 601,
23755
+ 473,
23756
+ 602,
23757
+ 535,
23758
+ 316,
23759
+ 602,
23760
+ 579,
23761
+ 246,
23762
+ 246,
23763
+ 506,
23764
+ 246,
23765
+ 880,
23766
+ 578,
23767
+ 599,
23768
+ 601,
23769
+ 602,
23770
+ 354,
23771
+ 433,
23772
+ 345,
23773
+ 578,
23774
+ 487,
23775
+ 736,
23776
+ 465,
23777
+ 493,
23778
+ 462,
23779
+ 305,
23780
+ 239,
23781
+ 305,
23782
+ 701
23783
+ ],
23784
+ ex: {
23785
+ 167: 458,
23786
+ 176: 387,
23787
+ 177: 701,
23788
+ 180: 288,
23789
+ 183: 219,
23790
+ 215: 701,
23791
+ 247: 701,
23792
+ 8211: 513,
23793
+ 8212: 1026,
23794
+ 8216: 231,
23795
+ 8217: 231,
23796
+ 8218: 231,
23797
+ 8220: 379,
23798
+ 8221: 379,
23799
+ 8222: 379,
23800
+ 8224: 382,
23801
+ 8225: 382,
23802
+ 8226: 417,
23803
+ 8229: 437,
23804
+ 8230: 740,
23805
+ 8240: 1238,
23806
+ 8242: 231,
23807
+ 8243: 379,
23808
+ 8249: 322,
23809
+ 8250: 322,
23810
+ 8251: 800,
23811
+ 8252: 578,
23812
+ 8263: 920,
23813
+ 8264: 749,
23814
+ 8265: 749,
23815
+ 8451: 950,
23816
+ 8457: 950,
23817
+ 8467: 332,
23818
+ 8470: 1146,
23819
+ 8481: 950,
23820
+ 8482: 793,
23821
+ 8486: 774,
23822
+ 8491: 696,
23823
+ 8531: 950,
23824
+ 8532: 950,
23825
+ 8539: 943,
23826
+ 8540: 981,
23827
+ 8541: 982,
23828
+ 8542: 913,
23829
+ 8544: 950,
23830
+ 8545: 950,
23831
+ 8546: 950,
23832
+ 8547: 950,
23833
+ 8548: 950,
23834
+ 8549: 950,
23835
+ 8550: 950,
23836
+ 8551: 950,
23837
+ 8552: 950,
23838
+ 8553: 950,
23839
+ 8560: 950,
23840
+ 8561: 950,
23841
+ 8562: 950,
23842
+ 8563: 950,
23843
+ 8564: 950,
23844
+ 8565: 950,
23845
+ 8566: 950,
23846
+ 8567: 950,
23847
+ 8568: 950,
23848
+ 8569: 950,
23849
+ 8592: 950,
23850
+ 8593: 950,
23851
+ 8594: 950,
23852
+ 8595: 950,
23853
+ 8596: 950,
23854
+ 8597: 950,
23855
+ 8598: 950,
23856
+ 8599: 950,
23857
+ 8600: 950,
23858
+ 8601: 950,
23859
+ 8658: 950,
23860
+ 8660: 950,
23861
+ 8704: 696,
23862
+ 8706: 546,
23863
+ 8707: 548,
23864
+ 8711: 713,
23865
+ 8712: 950,
23866
+ 8715: 950,
23867
+ 8719: 771,
23868
+ 8721: 631,
23869
+ 8729: 315,
23870
+ 8730: 667,
23871
+ 8733: 838,
23872
+ 8734: 839,
23873
+ 8736: 667,
23874
+ 8741: 684,
23875
+ 8743: 684,
23876
+ 8744: 684,
23877
+ 8745: 950,
23878
+ 8746: 950,
23879
+ 8747: 551,
23880
+ 8748: 950,
23881
+ 8750: 545,
23882
+ 8756: 667,
23883
+ 8757: 667,
23884
+ 8764: 684,
23885
+ 8765: 684,
23886
+ 8786: 684,
23887
+ 8800: 701,
23888
+ 8801: 684,
23889
+ 8804: 701,
23890
+ 8805: 701,
23891
+ 8810: 950,
23892
+ 8811: 950,
23893
+ 8834: 950,
23894
+ 8835: 950,
23895
+ 8838: 950,
23896
+ 8839: 950,
23897
+ 8857: 950,
23898
+ 8869: 950,
23899
+ 9676: 594,
23900
+ 9792: 625,
23901
+ 9794: 715,
23902
+ 9833: 538,
23903
+ 9834: 688,
23904
+ 9836: 932,
23905
+ 9837: 573,
23906
+ 12296: 542,
23907
+ 12297: 542,
23908
+ 12298: 596,
23909
+ 12299: 596,
23910
+ 12300: 571,
23911
+ 12301: 571,
23912
+ 12302: 517,
23913
+ 12303: 517,
23914
+ 12304: 518,
23915
+ 12305: 518,
23916
+ 12308: 488,
23917
+ 12309: 488,
23918
+ 12334: 333,
23919
+ 12335: 333
23920
+ }
23921
+ },
23922
+ "\uD568\uCD08\uB86C\uB3CB\uC6C0": {
23923
+ cjk: 970,
23924
+ ascii: [
23925
+ 500,
23926
+ 333,
23927
+ 339,
23928
+ 686,
23929
+ 626,
23930
+ 853,
23931
+ 761,
23932
+ 260,
23933
+ 313,
23934
+ 313,
23935
+ 498,
23936
+ 548,
23937
+ 258,
23938
+ 466,
23939
+ 270,
23940
+ 374,
23941
+ 550,
23942
+ 550,
23943
+ 550,
23944
+ 550,
23945
+ 550,
23946
+ 550,
23947
+ 550,
23948
+ 550,
23949
+ 550,
23950
+ 550,
23951
+ 312,
23952
+ 312,
23953
+ 600,
23954
+ 534,
23955
+ 600,
23956
+ 570,
23957
+ 865,
23958
+ 654,
23959
+ 664,
23960
+ 633,
23961
+ 701,
23962
+ 625,
23963
+ 609,
23964
+ 645,
23965
+ 694,
23966
+ 317,
23967
+ 515,
23968
+ 664,
23969
+ 584,
23970
+ 826,
23971
+ 728,
23972
+ 676,
23973
+ 660,
23974
+ 681,
23975
+ 683,
23976
+ 617,
23977
+ 573,
23978
+ 702,
23979
+ 594,
23980
+ 897,
23981
+ 607,
23982
+ 581,
23983
+ 584,
23984
+ 338,
23985
+ 373,
23986
+ 329,
23987
+ 562,
23988
+ 532,
23989
+ 373,
23990
+ 563,
23991
+ 581,
23992
+ 513,
23993
+ 578,
23994
+ 567,
23995
+ 336,
23996
+ 563,
23997
+ 594,
23998
+ 271,
23999
+ 268,
24000
+ 559,
24001
+ 258,
24002
+ 919,
24003
+ 595,
24004
+ 582,
24005
+ 552,
24006
+ 552,
24007
+ 427,
24008
+ 513,
24009
+ 349,
24010
+ 595,
24011
+ 469,
24012
+ 760,
24013
+ 483,
24014
+ 466,
24015
+ 479,
24016
+ 372,
24017
+ 342,
24018
+ 372,
24019
+ 546
24020
+ ],
24021
+ ex: {
24022
+ 167: 486,
24023
+ 176: 306,
24024
+ 177: 782,
24025
+ 180: 240,
24026
+ 183: 294,
24027
+ 215: 587,
24028
+ 247: 670,
24029
+ 8208: 237,
24030
+ 8209: 300,
24031
+ 8210: 398,
24032
+ 8211: 348,
24033
+ 8212: 522,
24034
+ 8213: 672,
24035
+ 8214: 368,
24036
+ 8215: 419,
24037
+ 8216: 288,
24038
+ 8217: 288,
24039
+ 8218: 288,
24040
+ 8219: 288,
24041
+ 8220: 451,
24042
+ 8221: 453,
24043
+ 8222: 451,
24044
+ 8223: 488,
24045
+ 8224: 518,
24046
+ 8225: 448,
24047
+ 8226: 586,
24048
+ 8227: 590,
24049
+ 8228: 288,
24050
+ 8229: 544,
24051
+ 8230: 702,
24052
+ 8231: 288,
24053
+ 8240: 892,
24054
+ 8241: 956,
24055
+ 8242: 304,
24056
+ 8243: 488,
24057
+ 8244: 550,
24058
+ 8245: 304,
24059
+ 8246: 488,
24060
+ 8247: 544,
24061
+ 8248: 494,
24062
+ 8249: 448,
24063
+ 8250: 448,
24064
+ 8251: 690,
24065
+ 8252: 502,
24066
+ 8253: 556,
24067
+ 8254: 348,
24068
+ 8255: 941,
24069
+ 8256: 941,
24070
+ 8257: 517,
24071
+ 8258: 764,
24072
+ 8259: 681,
24073
+ 8260: 359,
24074
+ 8261: 343,
24075
+ 8262: 343,
24076
+ 8263: 930,
24077
+ 8264: 688,
24078
+ 8265: 712,
24079
+ 8266: 549,
24080
+ 8267: 543,
24081
+ 8268: 573,
24082
+ 8269: 573,
24083
+ 8270: 551,
24084
+ 8271: 297,
24085
+ 8272: 941,
24086
+ 8273: 436,
24087
+ 8274: 610,
24088
+ 8275: 523,
24089
+ 8276: 941,
24090
+ 8277: 468,
24091
+ 8278: 595,
24092
+ 8279: 625,
24093
+ 8280: 690,
24094
+ 8281: 690,
24095
+ 8282: 494,
24096
+ 8283: 630,
24097
+ 8284: 678,
24098
+ 8285: 494,
24099
+ 8286: 494,
24100
+ 8501: 520,
24101
+ 8502: 526,
24102
+ 8503: 320,
24103
+ 8504: 471,
24104
+ 9756: 1012,
24105
+ 9758: 1012,
24106
+ 12296: 500,
24107
+ 12297: 500,
24108
+ 12298: 500,
24109
+ 12299: 500,
24110
+ 12300: 500,
24111
+ 12301: 500,
24112
+ 12302: 500,
24113
+ 12303: 500,
24114
+ 12304: 500,
24115
+ 12305: 500,
24116
+ 12308: 500,
24117
+ 12309: 500,
24118
+ 12310: 500,
24119
+ 12311: 500,
24120
+ 12312: 500,
24121
+ 12313: 500,
24122
+ 12314: 500,
24123
+ 12315: 500,
24124
+ 12332: 246,
24125
+ 12333: 246,
24126
+ 12351: 485
24127
+ }
24128
+ }
24129
+ };
24130
+ function hasFontMetrics(face) {
24131
+ return !!face && Object.prototype.hasOwnProperty.call(FONTS, face.trim());
24132
+ }
24133
+ function fontAdvanceEm1000(face, cp) {
24134
+ const m = FONTS[face.trim()];
24135
+ if (!m) return null;
24136
+ if (cp >= 32 && cp <= 126) return m.ascii[cp - 32];
24137
+ return m.ex[cp] ?? m.cjk;
24138
+ }
24139
+
22839
24140
  // src/hwpx/text-metrics.ts
22840
24141
  var ASCII_W = [
22841
24142
  300,
@@ -22995,11 +24296,13 @@ function charWidthEm1000(cp) {
22995
24296
  var SPACE_EM_FIXED = 500;
22996
24297
  var SPACE_EM_FONT = 300;
22997
24298
  function faceClassOf(face) {
24299
+ if (face && hasFontMetrics(face)) return `font:${face.trim()}`;
22998
24300
  return face && /^(굴림체|돋움체|바탕체|궁서체)$/.test(face.trim()) ? "fixedPitch" : "hcr";
22999
24301
  }
23000
24302
  function faceClassForGen(face) {
23001
24303
  if (!face) return "hcr";
23002
24304
  const f = face.trim();
24305
+ if (hasFontMetrics(f)) return `font:${f}`;
23003
24306
  if (/^(굴림체|돋움체|바탕체|궁서체)$/.test(f)) return "fixedPitch";
23004
24307
  if (/^(한컴돋움|맑은 고딕|HY견고딕|HY헤드라인M|HY중고딕|한양중고딕|나눔고딕|나눔스퀘어|돋움|굴림)$/.test(f)) return "gothic";
23005
24308
  return "hcr";
@@ -23016,14 +24319,26 @@ function gothicWidthEm1000(cp) {
23016
24319
  function fixedPitchWidthEm1000(cp) {
23017
24320
  return cp < 128 ? 500 : 1e3;
23018
24321
  }
24322
+ function widthFnOf(faceClass) {
24323
+ if (faceClass === "fixedPitch") return fixedPitchWidthEm1000;
24324
+ if (faceClass === "gothic") return gothicWidthEm1000;
24325
+ if (faceClass?.startsWith("font:")) {
24326
+ const face = faceClass.slice(5);
24327
+ return (cp) => fontAdvanceEm1000(face, cp) ?? gothicWidthEm1000(cp);
24328
+ }
24329
+ return charWidthEm1000;
24330
+ }
24331
+ function isSpaceCp(cp) {
24332
+ return cp === 32 || cp === 160;
24333
+ }
23019
24334
  function measureTextWidth(text, height, ratioPct, opts) {
23020
24335
  const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
23021
24336
  const spacing = opts?.spacingPct ?? 0;
23022
- const widthEm = opts?.faceClass === "fixedPitch" ? fixedPitchWidthEm1000 : opts?.faceClass === "gothic" ? gothicWidthEm1000 : charWidthEm1000;
24337
+ const widthEm = widthFnOf(opts?.faceClass);
23023
24338
  let em = 0;
23024
24339
  for (const ch of text) {
23025
24340
  const cp = ch.codePointAt(0);
23026
- const w = cp === 32 ? spaceEm : widthEm(cp);
24341
+ const w = isSpaceCp(cp) ? spaceEm : widthEm(cp);
23027
24342
  em += w * (1 + spacing / 100);
23028
24343
  }
23029
24344
  return em / 1e3 * height * (ratioPct / 100);
@@ -23034,16 +24349,24 @@ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "kee
23034
24349
  const EPS = 0.5;
23035
24350
  const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
23036
24351
  const spacing = opts?.spacingPct ?? 0;
23037
- const widthEm = opts?.faceClass === "fixedPitch" ? fixedPitchWidthEm1000 : opts?.faceClass === "gothic" ? gothicWidthEm1000 : charWidthEm1000;
24352
+ const widthEm = widthFnOf(opts?.faceClass);
23038
24353
  const k = height * ratioPct / 100 / 1e3;
23039
- const cwCp = (cp) => (cp === 32 ? spaceEm : widthEm(cp)) * (1 + spacing / 100) * k;
23040
- const charW2 = (ch) => cwCp(ch.codePointAt(0));
24354
+ const unitW = opts?.widths;
24355
+ const charWAt = (i, ch) => {
24356
+ if (unitW) return (unitW[i] ?? 0) + (ch.length === 2 ? unitW[i + 1] ?? 0 : 0);
24357
+ const cp = ch.codePointAt(0);
24358
+ return (isSpaceCp(cp) ? spaceEm : widthEm(cp)) * (1 + spacing / 100) * k;
24359
+ };
23041
24360
  const rangeW = (from, to) => {
23042
24361
  let w = 0;
23043
- for (const ch of text.slice(from, to)) w += charW2(ch);
24362
+ for (let i = from; i < to; ) {
24363
+ const ch = String.fromCodePoint(text.codePointAt(i));
24364
+ w += charWAt(i, ch);
24365
+ i += ch.length;
24366
+ }
23044
24367
  return w;
23045
24368
  };
23046
- const units = text.match(mode === "keep" ? / +|[^ ]+/gu : / +|[^ ]/gu) ?? [];
24369
+ const units = text.match(mode === "keep" ? / +|[^ ]+/gu : / +|(?:[\x21-\x7e]+|[^ ])(?:\u00a0(?:[\x21-\x7e]+|[^ \u00a0])?)*/gu) ?? [];
23047
24370
  const starts = [0];
23048
24371
  let lineW = 0;
23049
24372
  let avail = firstWidth;
@@ -23061,7 +24384,7 @@ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "kee
23061
24384
  };
23062
24385
  for (const u of units) {
23063
24386
  if (u[0] === " ") {
23064
- lineW += charW2(" ") * u.length;
24387
+ lineW += rangeW(pos, pos + u.length);
23065
24388
  pos += u.length;
23066
24389
  continue;
23067
24390
  }
@@ -23072,9 +24395,10 @@ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "kee
23072
24395
  continue;
23073
24396
  }
23074
24397
  if (lineW === 0 || w > contWidth + EPS) {
24398
+ if (mode === "keep" && lineW > 0) breakBefore(pos, 0);
23075
24399
  let sub = 0;
23076
24400
  for (const ch of u) {
23077
- const c = charW2(ch);
24401
+ const c = charWAt(pos + sub, ch);
23078
24402
  if (lineW + c > avail + EPS && lineW > 0) breakBefore(pos + sub, 0);
23079
24403
  lineW += c;
23080
24404
  sub += ch.length;
@@ -23572,6 +24896,9 @@ function resolveTheme(theme) {
23572
24896
  function escapeXml(text) {
23573
24897
  return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
23574
24898
  }
24899
+ function escapeTextXml(text) {
24900
+ return escapeXml(text).replace(/\u00a0/g, "<hp:nbSpace/>");
24901
+ }
23575
24902
  function headingParaPrId(level) {
23576
24903
  if (level === 1) return PARA_H1;
23577
24904
  if (level === 2) return PARA_H2;
@@ -23598,14 +24925,14 @@ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_C
23598
24925
  </hh:charPr>`;
23599
24926
  }
23600
24927
  function paraPr(id, opts = {}) {
23601
- const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, right = 0, keepWord = false, keepWithNext = false, outlineLevel } = opts;
24928
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, right = 0, keepWord = false, keepWithNext = false, outlineLevel, autoTab = false, widowOrphan = false } = opts;
23602
24929
  const breakNonLatin = keepWord ? "BREAK_WORD" : "KEEP_WORD";
23603
24930
  const snapGrid = keepWord ? "0" : "1";
23604
24931
  const heading = outlineLevel !== void 0 ? `<hh:heading type="OUTLINE" idRef="0" level="${outlineLevel}"/>` : `<hh:heading type="NONE" idRef="0" level="0"/>`;
23605
- return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="${snapGrid}" suppressLineNumbers="0" checked="0" textDir="AUTO">
24932
+ return ` <hh:paraPr id="${id}" tabPrIDRef="${autoTab ? 1 : 0}" condense="0" fontLineHeight="0" snapToGrid="${snapGrid}" suppressLineNumbers="0" checked="0" textDir="AUTO">
23606
24933
  <hh:align horizontal="${align}" vertical="BASELINE"/>
23607
24934
  ${heading}
23608
- <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="${keepWithNext ? 1 : 0}" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
24935
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="${widowOrphan ? 1 : 0}" keepWithNext="${keepWithNext ? 1 : 0}" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
23609
24936
  <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
23610
24937
  <hh:margin><hc:intent value="${indent}" unit="HWPUNIT"/><hc:left value="${left}" unit="HWPUNIT"/><hc:right value="${right}" unit="HWPUNIT"/><hc:prev value="${spaceBefore}" unit="HWPUNIT"/><hc:next value="${spaceAfter}" unit="HWPUNIT"/></hh:margin>
23611
24938
  <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
@@ -24064,7 +25391,7 @@ function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
24064
25391
  }
24065
25392
  }
24066
25393
  const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
24067
- out.push(`<hp:run charPrIDRef="${mapped(charId)}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`);
25394
+ out.push(`<hp:run charPrIDRef="${mapped(charId)}"><hp:t>${escapeTextXml(span.text)}</hp:t></hp:run>`);
24068
25395
  }
24069
25396
  closeField();
24070
25397
  return out.join("");
@@ -24291,7 +25618,7 @@ ${base2.join("\n")}
24291
25618
  const ls = gongmun.lineSpacing;
24292
25619
  const titleAlign = gongmun.centerTitle ? "CENTER" : "LEFT";
24293
25620
  const h2MarkerWidth = markerWidth(gongmun.h2Marker === "box" ? "\u25A1" : "1.", 1600);
24294
- const h2Geom = gongmun.h2Marker !== "none" ? { spaceBefore: Math.round(gongmun.bodyHeight * 2), spaceAfter: 0, indent: -h2MarkerWidth } : { spaceBefore: 600, spaceAfter: 150 };
25621
+ const h2Geom = gongmun.h2Marker !== "none" ? { spaceBefore: Math.round(gongmun.bodyHeight * 2), spaceAfter: 0, indent: -h2MarkerWidth, autoTab: true } : { spaceBefore: 600, spaceAfter: 150 };
24295
25622
  const h3Left = gongmun.h2Marker !== "none" ? h2MarkerWidth : 0;
24296
25623
  const base = [
24297
25624
  paraPr(0, { lineSpacing: ls, keepWord: true }),
@@ -24311,7 +25638,7 @@ ${base2.join("\n")}
24311
25638
  const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, usesAsteriskThird(gongmun.preset), levelMarkerHeight(gongmun, d));
24312
25639
  const sectionGap = gongmun.numbering === "gaejosik" || gongmun.numbering === "report" ? gaejosikSpaceBefore(d, gongmun.bodyHeight) : 0;
24313
25640
  const keepNext = (gongmun.numbering === "gaejosik" || gongmun.numbering === "report") && d === 0;
24314
- base.push(paraPr(GONGMUN_LIST_BASE + d, { align: listAlign, lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true, keepWithNext: keepNext }));
25641
+ base.push(paraPr(GONGMUN_LIST_BASE + d, { align: listAlign, lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true, keepWithNext: keepNext, autoTab: true }));
24315
25642
  }
24316
25643
  base.push(paraPr(GONGMUN_CENTER, { align: "CENTER", lineSpacing: ls, keepWord: true }));
24317
25644
  base.push(paraPr(GONGMUN_RIGHT, { align: "RIGHT", lineSpacing: ls, keepWord: true }));
@@ -24321,7 +25648,7 @@ ${base2.join("\n")}
24321
25648
  const cham = gaejosikChamIndent(gongmun.bodyHeight, gongmun.sizes);
24322
25649
  const toc = gaejosikTocItemIndent(gongmun.bodyHeight, gongmun.sizes);
24323
25650
  base.push(
24324
- paraPr(GJ_PARA_CHAM, { align: "LEFT", lineSpacing: ls, left: cham.left, indent: cham.indent, spaceBefore: gaejosikSpaceBefore(3, gongmun.bodyHeight), keepWord: true }),
25651
+ paraPr(GJ_PARA_CHAM, { align: "LEFT", lineSpacing: ls, left: cham.left, indent: cham.indent, spaceBefore: gaejosikSpaceBefore(3, gongmun.bodyHeight), keepWord: true, autoTab: true }),
24325
25652
  paraPr(GJ_PARA_COVER, { align: "CENTER", lineSpacing: 130, keepWord: true }),
24326
25653
  paraPr(GJ_PARA_TOC_ITEM, { align: "LEFT", lineSpacing: 160, left: toc.left, indent: toc.indent, spaceBefore: 1800, keepWord: true }),
24327
25654
  // 장 헤더는 다음 문단과 같은 쪽에 — 쪽 하단 고아 헤더 방지 (실렌더 확인 이슈)
@@ -24342,7 +25669,7 @@ ${base2.join("\n")}
24342
25669
  const { left } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering, gongmun.sizes, gongmun.bullet2, usesAsteriskThird(gongmun.preset));
24343
25670
  const sectionGap = gongmun.numbering === "gaejosik" || gongmun.numbering === "report" ? gaejosikSpaceBefore(depth, gongmun.bodyHeight) : 0;
24344
25671
  const keepNext = (gongmun.numbering === "gaejosik" || gongmun.numbering === "report") && depth === 0;
24345
- base.push(paraPr(GONGMUN_LIST_VARIANT_BASE + vi, { align: listAlign, lineSpacing: ls, left, indent: -widthHu, spaceBefore: sectionGap, keepWord: true, keepWithNext: keepNext }));
25672
+ base.push(paraPr(GONGMUN_LIST_VARIANT_BASE + vi, { align: listAlign, lineSpacing: ls, left, indent: -widthHu, spaceBefore: sectionGap, keepWord: true, keepWithNext: keepNext, autoTab: true }));
24346
25673
  }
24347
25674
  assertSequentialIds(base, "paraPr", 0);
24348
25675
  return `<hh:paraProperties itemCnt="${base.length}">
@@ -24475,6 +25802,10 @@ function buildStyles(gongmun) {
24475
25802
  ${items.join("\n ")}
24476
25803
  </hh:styles>`;
24477
25804
  }
25805
+ var TAB_PROPS_GONGMUN = `<hh:tabProperties itemCnt="2">
25806
+ <hh:tabPr id="0" autoTabLeft="0" autoTabRight="0"/>
25807
+ <hh:tabPr id="1" autoTabLeft="1" autoTabRight="0"/>
25808
+ </hh:tabProperties>`;
24478
25809
  function generateHeaderXml(theme, gongmun, ratioVariants = [], extraBorderFills = [], extraCharPrXmls = [], listIndentVariants = [], extraFonts = [], v5 = null) {
24479
25810
  const bodyFace = v5?.bodyFace ?? gongmun?.fonts.body ?? (gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5");
24480
25811
  const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants, extraCharPrXmls, !!v5);
@@ -24486,7 +25817,7 @@ function generateHeaderXml(theme, gongmun, ratioVariants = [], extraBorderFills
24486
25817
  ${buildFontFaces(gongmun, bodyFace, extraFonts, !!v5)}
24487
25818
  ${buildBorderFills(gongmun, extraBorderFills, !!v5)}
24488
25819
  ${charPropsXml}
24489
- <hh:tabProperties itemCnt="0"/>
25820
+ ${v5 || gongmun ? TAB_PROPS_GONGMUN : `<hh:tabProperties itemCnt="0"/>`}
24490
25821
  ${buildNumberings()}
24491
25822
  <hh:bullets itemCnt="0"/>
24492
25823
  ${paraPropsXml}
@@ -24882,7 +26213,7 @@ function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
24882
26213
  const sz = gaejosikSizes(gongmun.bodyHeight, gongmun.sizes);
24883
26214
  const innerW = g.totalW - 282;
24884
26215
  const titleCharPr = simulateWrap(title, innerW, innerW, sz.coverTitle, 100, "keep").lines > 2 ? GJ_CHAR_COVER_SUB : GJ_CHAR_COVER_TITLE;
24885
- const titlePara = `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${titleCharPr}"><hp:t>${escapeXml(title)}</hp:t></hp:run></hp:p>`;
26216
+ const titlePara = `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${titleCharPr}"><hp:t>${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
24886
26217
  const barEmpty = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
24887
26218
  const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g.topDarkW, h: g.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g.topLightW, h: g.barH, paras: barEmpty });
24888
26219
  const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g.totalW, h: g.titleH, paras: titlePara, name: "__kordoc_h1" });
@@ -24891,7 +26222,7 @@ function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
24891
26222
  const host2 = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
24892
26223
  const date = gongmun.cover?.date ?? formatGaejosikDate(/* @__PURE__ */ new Date());
24893
26224
  const org = gongmun.cover?.org ?? "";
24894
- const sub = (t) => `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_COVER_SUB}"><hp:t>${escapeXml(t)}</hp:t></hp:run></hp:p>`;
26225
+ const sub = (t) => `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_COVER_SUB}"><hp:t>${escapeTextXml(t)}</hp:t></hp:run></hp:p>`;
24895
26226
  const subEmpty = emptyPara(GJ_PARA_COVER, false, GJ_CHAR_COVER_SUB);
24896
26227
  return [
24897
26228
  ...Array(5).fill(emptyPara()),
@@ -24916,7 +26247,7 @@ function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
24916
26247
  const boxW = Math.round(TOC_GEOM.boxW * bodyWidth / GAEJOSIK_BASE_WIDTH);
24917
26248
  const label = buildTocBanner(gongmun, bodyWidth);
24918
26249
  const items = chapters.map(
24919
- (title, i) => `<hp:p paraPrIDRef="${GJ_PARA_TOC_ITEM}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_TOC_ROMAN}"><hp:t>${chapterRoman(i + 1)}</hp:t></hp:run><hp:run charPrIDRef="${GJ_CHAR_TOC_ITEM}"><hp:t>. ${escapeXml(title)}</hp:t></hp:run></hp:p>`
26250
+ (title, i) => `<hp:p paraPrIDRef="${GJ_PARA_TOC_ITEM}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_TOC_ROMAN}"><hp:t>${chapterRoman(i + 1)}</hp:t></hp:run><hp:run charPrIDRef="${GJ_CHAR_TOC_ITEM}"><hp:t>. ${escapeTextXml(title)}</hp:t></hp:run></hp:p>`
24920
26251
  ).join("");
24921
26252
  const box = table(
24922
26253
  [cell({ bf: GJ_BF_TOC_BOX, col: 0, w: boxW, h: TOC_GEOM.boxH, paras: items, name: "__kordoc_toc" })],
@@ -24933,7 +26264,7 @@ function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH)
24933
26264
  const g = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
24934
26265
  const bt = bodyTitleGeom(gongmun.bodyHeight, gongmun.sizes);
24935
26266
  const barEmpty = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
24936
- const titlePara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_BODY_TITLE}"><hp:t>${escapeXml(title)}</hp:t></hp:run></hp:p>`;
26267
+ const titlePara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_BODY_TITLE}"><hp:t>${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
24937
26268
  const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g.topDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g.topLightW, h: bt.barH, paras: barEmpty });
24938
26269
  const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g.totalW, h: bt.titleH, paras: titlePara, name: "__kordoc_skip" });
24939
26270
  const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g.botDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g.botLightW, h: bt.barH, paras: barEmpty });
@@ -24943,7 +26274,7 @@ function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH)
24943
26274
  function buildGaejosikChapter(n, title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH, srcLevel = 2) {
24944
26275
  const g = gongmun ? chapterGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth) : chapterGeom(1500, {}, bodyWidth);
24945
26276
  const numPara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_NUM}"><hp:t>${chapterRoman(n)}</hp:t></hp:run></hp:p>`;
24946
- const titlePara = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_TITLE}"><hp:t> ${escapeXml(title)}</hp:t></hp:run></hp:p>`;
26277
+ const titlePara = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_TITLE}"><hp:t> ${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
24947
26278
  const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w: g.numW, h: g.rowH, paras: numPara }) + cell({ bf: GJ_BF_CHAPTER_GAP, col: 1, w: g.gapW, h: g.rowH }) + cell({ bf: GJ_BF_CHAPTER_TITLE, col: 2, w: g.titleW, h: g.rowH, paras: titlePara, name: `__kordoc_h${Math.min(Math.max(srcLevel, 1), 6)}` });
24948
26279
  const tbl2 = table([row], g.numW + g.gapW + g.titleW, g.rowH, 3, 1, bodyWidth);
24949
26280
  return `<hp:p paraPrIDRef="${GJ_PARA_CHAPTER}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
@@ -25910,7 +27241,7 @@ function cellContentWidth(text, charHeight) {
25910
27241
  function cellMinWordWidth(text, charHeight) {
25911
27242
  let max = 0;
25912
27243
  for (const seg of text.replace(/!\[[^\]]*\]\([^)\s]+\)/g, "").replace(/\*\*|__|`/g, "").split(/<br\s*\/?>/i)) {
25913
- for (const word of seg.trim().split(/\s+/)) {
27244
+ for (const word of seg.trim().split(/[ \t]+/)) {
25914
27245
  const w = measureTextWidth(word, charHeight, 100, { faceClass: curFace });
25915
27246
  if (w > max) max = w;
25916
27247
  }
@@ -25928,7 +27259,10 @@ function colRoles(headers) {
25928
27259
  if (roles.length >= 3 && roles.includes("content") && roles[last] === null) roles[last] = "remark";
25929
27260
  return roles;
25930
27261
  }
25931
- function computeColWidths(colMax, totalWidth, colMinWord = [], roles = []) {
27262
+ function colSlack(faceClass) {
27263
+ return faceClass.startsWith("font:") ? 1.03 : 1.12;
27264
+ }
27265
+ function computeColWidths(colMax, totalWidth, colMinWord = [], roles = [], slack = 1.12) {
25932
27266
  const colCnt = colMax.length;
25933
27267
  const minW = Math.min(Math.max(2e3, Math.round(totalWidth * 0.06)), Math.floor(totalWidth / colCnt));
25934
27268
  const cap = Math.round(totalWidth * 0.8);
@@ -25942,7 +27276,7 @@ function computeColWidths(colMax, totalWidth, colMinWord = [], roles = []) {
25942
27276
  let sumFloorFree = floor.reduce((a, b) => a + b, 0);
25943
27277
  for (const i of [...raw.keys()].sort((a, b) => raw[a] - raw[b])) {
25944
27278
  if (free.size <= 1) break;
25945
- const fixed = roles[i] === "remark" ? Math.min(Math.round(raw[i] * 1.12), capOf[i]) : Math.round(raw[i] * 1.12);
27279
+ const fixed = roles[i] === "remark" ? Math.min(Math.round(raw[i] * slack), capOf[i]) : Math.round(raw[i] * slack);
25946
27280
  if (budget - fixed < sumFloorFree - floor[i]) break;
25947
27281
  widths[i] = fixed;
25948
27282
  free.delete(i);
@@ -26015,7 +27349,7 @@ function requiredTableWidth(rows, charHeight, faceClass = "hcr") {
26015
27349
  if (w > colMax[c]) colMax[c] = w;
26016
27350
  });
26017
27351
  curFace = prev;
26018
- return colMax.reduce((a, w) => a + Math.round((w + CELL_PAD) * 1.12), 0);
27352
+ return colMax.reduce((a, w) => a + Math.round((w + CELL_PAD) * colSlack(faceClass)), 0);
26019
27353
  }
26020
27354
  function generateTable(rows, theme, style = null, remap = null, seq = 0, images = null) {
26021
27355
  curFace = style?.faceClass ?? "hcr";
@@ -26037,7 +27371,7 @@ function generateTable(rows, theme, style = null, remap = null, seq = 0, images
26037
27371
  if (mw > colMinWord[c]) colMinWord[c] = mw;
26038
27372
  }));
26039
27373
  const roles = style ? colRoles(rows[0] ?? []) : [];
26040
- const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalW, colMinWord, roles);
27374
+ const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalW, colMinWord, roles, colSlack(curFace));
26041
27375
  const colCentered = colWidths.map((w, c) => roles[c] !== "content" && colMaxBody[c] + CELL_PAD <= w);
26042
27376
  const labelCol0 = !!reg && colCnt === 2 && colCentered[0] && rows.every((r) => (r[0] ?? "").replace(/\*\*|__|`/g, "").length <= 12);
26043
27377
  const tblId = nextTableId();
@@ -26137,6 +27471,7 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
26137
27471
  const { placed, rowCnt, colCnt } = layoutHtmlRows(rows);
26138
27472
  if (rowCnt === 0 || colCnt === 0) return null;
26139
27473
  const measureH = style?.charHeight ?? 1e3;
27474
+ curFace = style?.faceClass ?? "hcr";
26140
27475
  const first = placed.find((p) => p.r === 0 && p.c === 0) ?? placed[0];
26141
27476
  const row0 = Array.from({ length: colCnt }, () => "");
26142
27477
  for (const cell2 of placed) {
@@ -26149,7 +27484,7 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
26149
27484
  const cellParsed = placed.map((cell2) => htmlCellInnerToLines(cell2.inner));
26150
27485
  const cellLines = cellParsed.map((p) => p.lines);
26151
27486
  placed.forEach((cell2, i) => {
26152
- const w = Math.max(...cellLines[i].map((l) => measureTextWidth(unescapeHtml(l).trim(), measureH, 100)), 0) / cell2.colSpan;
27487
+ const w = Math.max(...cellLines[i].map((l) => measureTextWidth(unescapeHtml(l).trim(), measureH, 100, { faceClass: curFace })), 0) / cell2.colSpan;
26153
27488
  const mw = cell2.colSpan === 1 ? Math.max(...cellLines[i].map((l) => cellMinWordWidth(unescapeHtml(l), measureH)), 0) : 0;
26154
27489
  for (let dc = 0; dc < cell2.colSpan; dc++) {
26155
27490
  const c = cell2.c + dc;
@@ -26161,7 +27496,7 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
26161
27496
  const headers = Array.from({ length: colCnt }, () => "");
26162
27497
  for (const [i, cell2] of placed.entries()) if (cell2.r === 0 && cell2.c < colCnt) headers[cell2.c] = unescapeHtml(cellLines[i].join(" "));
26163
27498
  const roles = style ? colRoles(headers) : [];
26164
- const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalWidth, colMinWord, roles);
27499
+ const colWidths = profileColWidths(prof, colCnt) ?? computeColWidths(colMax, totalWidth, colMinWord, roles, colSlack(curFace));
26165
27500
  const colCentered = colWidths.map((w, c) => roles[c] !== "content" && colMaxBody[c] + CELL_PAD <= w);
26166
27501
  const cellH = style ? Math.round(measureH * 1.6) + 282 : 1500;
26167
27502
  const tblW = colWidths.reduce((a, b) => a + b, 0);
@@ -26190,7 +27525,7 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
26190
27525
  const segLines = segTexts.map((t) => htmlCellInnerToLines(t).lines);
26191
27526
  const usable = Math.max(spanW(cell2) - CELL_PAD, 1e3);
26192
27527
  let wrapLines = 0;
26193
- for (const line of lines) wrapLines += Math.max(1, Math.ceil(measureTextWidth(unescapeHtml(line).trim(), measureH, 100) / usable));
27528
+ for (const line of lines) wrapLines += Math.max(1, Math.ceil(measureTextWidth(unescapeHtml(line).trim(), measureH, 100, { faceClass: curFace }) / usable));
26194
27529
  const lineH = style ? Math.round(measureH * 1.6) : 800;
26195
27530
  const contentH = Math.max(cellH * rowSpan, Math.max(wrapLines, 1) * lineH + nestedH);
26196
27531
  const cellHeight = Math.max(prof?.cellH.get(`${cell2.r},${cell2.c}`) ?? 0, contentH);
@@ -26219,7 +27554,7 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3, style = null, r
26219
27554
  }
26220
27555
  }
26221
27556
  if (!images || text.trim() || picUrls.length === 0) {
26222
- paras.push(`<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`);
27557
+ paras.push(`<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeTextXml(text)}</hp:t></hp:run></hp:p>`);
26223
27558
  }
26224
27559
  };
26225
27560
  segLines.forEach((seg, si) => {
@@ -26278,7 +27613,7 @@ function tc(opts) {
26278
27613
  return `<hp:tc name="${opts.name ?? ""}" header="0" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="${opts.bf}"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="${opts.vAlign ?? "CENTER"}" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${opts.paras}</hp:subList><hp:cellAddr colAddr="${opts.col}" rowAddr="${opts.row}"/><hp:cellSpan colSpan="${opts.colSpan ?? 1}" rowSpan="${opts.rowSpan ?? 1}"/><hp:cellSz width="${opts.w}" height="${opts.h}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
26279
27614
  }
26280
27615
  function para(text, paraPrId, charPrId) {
26281
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
27616
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${charPrId}"><hp:t>${escapeTextXml(text)}</hp:t></hp:run></hp:p>`;
26282
27617
  }
26283
27618
  var APPROVAL_COL_W = 7085;
26284
27619
  var APPROVAL_LABEL_H = 1300;
@@ -26854,6 +28189,16 @@ function buildPreamble(blocks, ctx) {
26854
28189
  ctx.paraXmls.push(...preamble);
26855
28190
  }
26856
28191
  }
28192
+ function markedParagraph(marker, content, paraPrId, charPrId, halfEm, mapCharId, styleId = 0) {
28193
+ const cid4 = mapCharId ? mapCharId(charPrId) : charPrId;
28194
+ const run = `<hp:run charPrIDRef="${cid4}"><hp:t>${escapeTextXml(marker)}<hp:tab width="${Math.round(halfEm)}" leader="0" type="1"/></hp:t></hp:run>`;
28195
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="${styleId}">${run}${generateRuns(content, charPrId, mapCharId)}</hp:p>`;
28196
+ }
28197
+ function refParagraph(text, ctx) {
28198
+ const m = /^(※|\*(?=\s))\s*/.exec(text);
28199
+ const [mk, rest] = m ? [m[1], text.slice(m[0].length)] : ["\u203B", text];
28200
+ return markedParagraph(mk, rest, GJ_PARA_CHAM, GJ_CHAR_CHAM, ctx.gongmun.bodyHeight / 2, ctx.chamMap);
28201
+ }
26857
28202
  function renderHeading(block, blockIdx, ctx) {
26858
28203
  const { gongmun, dfIds, tableStyle, bfReg, measured, richAssets, gjBodyW } = ctx;
26859
28204
  if (gongmun && blockIdx === ctx.coverH1Idx) return "";
@@ -26880,24 +28225,27 @@ function renderHeading(block, blockIdx, ctx) {
26880
28225
  ctx.chapterNo = no >= 0 ? no + 1 : ctx.chapterNo + 1;
26881
28226
  return buildGaejosikChapter(ctx.chapterNo, stripChapterNumber(block.text || ""), gongmun, gjBodyW, lvl);
26882
28227
  } else if (lvl === 3) {
26883
- return generateParagraph(
26884
- `\u25A1 ${block.text || ""}`,
28228
+ return markedParagraph(
28229
+ "\u25A1",
28230
+ block.text || "",
26885
28231
  GONGMUN_LIST_BASE,
26886
28232
  GJ_CHAR_DAE,
28233
+ gongmun.bodyHeight / 2,
26887
28234
  (id) => id === CHAR_BOLD ? GJ_CHAR_DAE_BOLD : id
26888
28235
  );
26889
28236
  }
26890
- return generateParagraph(`${gongmun.bullet2} ${block.text || ""}`, GONGMUN_LIST_BASE + 1, CHAR_NORMAL);
28237
+ return markedParagraph(gongmun.bullet2, block.text || "", GONGMUN_LIST_BASE + 1, CHAR_NORMAL, gongmun.bodyHeight / 2);
26891
28238
  }
26892
28239
  const pId = headingParaPrId(block.level || 1);
26893
28240
  const cId = headingCharPrId(block.level || 1);
26894
28241
  const styleId = gongmun ? Math.min(block.level || 1, 4) : 0;
26895
- let hText = block.text || "";
26896
28242
  if (gongmun && (block.level || 1) === 2 && gongmun.h2Marker !== "none") {
26897
- const title = stripChapterNumber(hText);
26898
- hText = gongmun.h2Marker === "box" ? `\u25A1 ${title}` : `${++ctx.h2Seq}. ${title}`;
28243
+ const title = stripChapterNumber(block.text || "");
28244
+ const mk = gongmun.h2Marker === "box" ? "\u25A1" : `${++ctx.h2Seq}.`;
28245
+ if (mk.length > 2) return generateParagraph(`${mk} ${title}`, pId, cId, void 0, styleId);
28246
+ return markedParagraph(mk, title, pId, cId, gongmun.bodyHeight / 2, void 0, styleId);
26899
28247
  }
26900
- return generateParagraph(hText, pId, cId, void 0, styleId);
28248
+ return generateParagraph(block.text || "", pId, cId, void 0, styleId);
26901
28249
  }
26902
28250
  function renderParagraph(block, blockIdx, ctx) {
26903
28251
  const { gongmun, measured, fit, vBase } = ctx;
@@ -26915,7 +28263,7 @@ function renderParagraph(block, blockIdx, ctx) {
26915
28263
  }
26916
28264
  const pTrim = (block.text || "").trimStart();
26917
28265
  if (measured && (pTrim.startsWith("\u203B") || /^\*\s/.test(pTrim))) {
26918
- return generateParagraph((block.text || "").trim(), GJ_PARA_CHAM, GJ_CHAR_CHAM, ctx.chamMap);
28266
+ return refParagraph((block.text || "").trim(), ctx);
26919
28267
  }
26920
28268
  const ctr = gongmun && /^<center>([\s\S]*)<\/center>$/i.exec((block.text || "").trim());
26921
28269
  const rgt = gongmun && /^<right>([\s\S]*)<\/right>$/i.exec((block.text || "").trim());
@@ -26954,7 +28302,7 @@ function renderEquation(block, blockIdx, ctx) {
26954
28302
  function renderBlockquote(block, ctx) {
26955
28303
  if (ctx.measured) {
26956
28304
  const t = (block.text || "").replace(/\n+/g, " ").trim();
26957
- return t ? generateParagraph(t.startsWith("\u203B") ? t : `\u203B ${t}`, GJ_PARA_CHAM, GJ_CHAR_CHAM, ctx.chamMap) : "";
28305
+ return t ? refParagraph(t, ctx) : "";
26958
28306
  }
26959
28307
  const quoteChar = ctx.theme.hasQuoteOption ? CHAR_QUOTE : CHAR_NORMAL;
26960
28308
  return (block.text || "").split("\n").map((line) => generateParagraph(line, PARA_QUOTE, quoteChar)).join("\n ");
@@ -26967,12 +28315,11 @@ function renderListItem(block, blockIdx, ctx) {
26967
28315
  const marker2 = info?.marker ?? "";
26968
28316
  const content = block.text || "";
26969
28317
  if (measured && content.trimStart().startsWith("\u203B")) {
26970
- return generateParagraph(content.trim(), GJ_PARA_CHAM, GJ_CHAR_CHAM, ctx.chamMap);
28318
+ return refParagraph(content.trim(), ctx);
26971
28319
  }
26972
28320
  if (measured && block.marker === "*") {
26973
- return generateParagraph(`* ${content.trim()}`, GJ_PARA_CHAM, GJ_CHAR_CHAM, ctx.chamMap);
28321
+ return refParagraph(`* ${content.trim()}`, ctx);
26974
28322
  }
26975
- const text = marker2 ? `${marker2} ${content}` : content;
26976
28323
  const listParaPr = marker2 ? info?.indentVariant !== void 0 ? GONGMUN_LIST_VARIANT_BASE + info.indentVariant : GONGMUN_LIST_BASE + depth : GONGMUN_LIST_PLAIN_BASE + depth;
26977
28324
  let listCharPr = gongmun.numbering === "report" && depth === 0 ? CHAR_BOLD : CHAR_NORMAL;
26978
28325
  let mapId = fit ? variantMapper(fit, blockIdx, vBase) : void 0;
@@ -26992,7 +28339,7 @@ function renderListItem(block, blockIdx, ctx) {
26992
28339
  listCharPr = lv.normal;
26993
28340
  mapId = (id) => id === CHAR_BOLD ? lv.bold : id === CHAR_NORMAL ? lv.normal : id;
26994
28341
  }
26995
- return generateParagraph(text, listParaPr, listCharPr, mapId);
28342
+ return marker2 ? markedParagraph(marker2, content, listParaPr, listCharPr, gongmun.bodyHeight / 2, mapId) : generateParagraph(content, listParaPr, listCharPr, mapId);
26996
28343
  }
26997
28344
  const indent = block.indent || 0;
26998
28345
  let marker;
@@ -27249,7 +28596,7 @@ var StyleRegistry = class {
27249
28596
  return id;
27250
28597
  }
27251
28598
  para(spec) {
27252
- const key = `${spec.align ?? "JUSTIFY"}|${spec.left ?? 0}|${spec.right ?? 0}|${spec.indent ?? 0}|${spec.before ?? 0}|${spec.after ?? 0}|${spec.lineSp ?? 160}|${spec.keepWithNext ? 1 : 0}|${spec.keepWord === false ? 0 : 1}`;
28599
+ const key = `${spec.align ?? "JUSTIFY"}|${spec.left ?? 0}|${spec.right ?? 0}|${spec.indent ?? 0}|${spec.before ?? 0}|${spec.after ?? 0}|${spec.lineSp ?? 160}|${spec.keepWithNext ? 1 : 0}|${spec.keepWord === false ? 0 : 1}|${spec.autoTab ? 1 : 0}|${spec.widowOrphan ? 1 : 0}`;
27253
28600
  const hit = this.paras.get(key);
27254
28601
  if (hit !== void 0) return hit;
27255
28602
  const id = this.paraBase + this.paraXmls.length;
@@ -27262,7 +28609,9 @@ var StyleRegistry = class {
27262
28609
  spaceAfter: spec.after ?? 0,
27263
28610
  lineSpacing: spec.lineSp ?? 160,
27264
28611
  keepWithNext: !!spec.keepWithNext,
27265
- keepWord: spec.keepWord !== false
28612
+ keepWord: spec.keepWord !== false,
28613
+ autoTab: !!spec.autoTab,
28614
+ widowOrphan: !!spec.widowOrphan
27266
28615
  }));
27267
28616
  this.paras.set(key, id);
27268
28617
  return id;
@@ -27416,11 +28765,6 @@ function applySchemeOverrides(s, g) {
27416
28765
  function taHu(pt2) {
27417
28766
  return Math.round(pt2 * 50);
27418
28767
  }
27419
- function levelGeometry(style, marker) {
27420
- const left = style.leadTa * taHu(style.pt);
27421
- const indent = marker ? -markerWidth(marker, style.pt * 100) : 0;
27422
- return { left, indent };
27423
- }
27424
28768
  function pickScheme(g, bodyHasBoxMarkers) {
27425
28769
  if (g.preset === "ministry") {
27426
28770
  return applySchemeOverrides(ministryScheme(g.bodyPtExplicit ? g.bodyHeight / 100 : 15, g.lineSpacingExplicit ? g.lineSpacing : 145), g);
@@ -27645,9 +28989,12 @@ function buildOutline(blocks, opts) {
27645
28989
  // src/hwpx/fit-line.ts
27646
28990
  var RATIOS = [100, 97, 95, 92, 90, 87, 85];
27647
28991
  var SPACINGS = [0, -3, -5];
28992
+ function safety(faceClass, approx) {
28993
+ return faceClass.startsWith("font:") ? 0.98 : approx;
28994
+ }
27648
28995
  function fitOneLine(text, font, pt2, availHu, minPt = Math.max(pt2 - 3, 10), minRatio = 85) {
27649
28996
  const faceClass = faceClassForGen(font);
27650
- const avail = availHu * 0.98;
28997
+ const avail = availHu * safety(faceClass, 0.98);
27651
28998
  const fits = (p, r, s) => measureTextWidth(text, p * 100, r, { spacingPct: s, faceClass }) <= avail;
27652
28999
  if (fits(pt2, 100, 0)) return { pt: pt2, ratio: 100, spacing: 0, overflow: false };
27653
29000
  const ratios = RATIOS.filter((r) => r >= minRatio);
@@ -27658,27 +29005,139 @@ function fitOneLine(text, font, pt2, availHu, minPt = Math.max(pt2 - 3, 10), min
27658
29005
  }
27659
29006
  return { pt: minPt, ratio: ratios[ratios.length - 1], spacing: SPACINGS[SPACINGS.length - 1], overflow: true };
27660
29007
  }
27661
- function fitOrphanLine(text, font, pt2, firstW, contW, orphanRatio = 0.2) {
29008
+ var SQUEEZE = (() => {
29009
+ const out = [];
29010
+ for (let sp = -1; sp >= -12; sp--) out.push([100, sp]);
29011
+ for (const r of [97, 95, 92, 90, 88]) for (let sp = 0; sp >= -12; sp--) out.push([r, sp]);
29012
+ return out;
29013
+ })();
29014
+ var LOOSE_LIMIT = 1;
29015
+ var PULL_TAIL = 0.5;
29016
+ var PULL_MAX = 0.15;
29017
+ var squeezeOf = (r, sp) => 1 - r / 100 * (1 + sp / 100);
29018
+ var ORPHAN_RATIO = 0.22;
29019
+ var ORPHAN_COST = 1.5;
29020
+ var COMPRESS_COST = 10;
29021
+ function worstLooseness(text, starts, firstW, contW, h, ratio, spacing, faceClass) {
29022
+ const spaceW = 0.5 * h * (ratio / 100) * (1 + spacing / 100);
29023
+ let worst = 0;
29024
+ for (let i = 0; i + 1 < starts.length; i++) {
29025
+ const seg = text.slice(starts[i], starts[i + 1]).replace(/ +$/, "");
29026
+ const slack = (i === 0 ? firstW : contW) - measureTextWidth(seg, h, ratio, { faceClass, spacingPct: spacing });
29027
+ const spaces = (seg.match(/ /g) ?? []).length;
29028
+ worst = Math.max(worst, slack / (Math.max(spaces, 1) * spaceW));
29029
+ }
29030
+ return worst;
29031
+ }
29032
+ function fitParagraph(text, font, pt2, firstW, contW, minRatio = 88, orphanRatio = ORPHAN_RATIO) {
27662
29033
  const faceClass = faceClassForGen(font);
27663
29034
  const h = pt2 * 100;
27664
- const SAFE = 0.9;
27665
- const width = (r, sp) => measureTextWidth(text, h, r, { faceClass, spacingPct: sp });
27666
- const est = width(100, 0);
27667
- if (est <= firstW * SAFE) return null;
27668
- const combos = [];
27669
- for (let sp = -1; sp >= -12; sp--) combos.push([100, sp]);
27670
- for (const r of [97, 95, 92, 90, 88]) for (let sp = 0; sp >= -12; sp--) combos.push([r, sp]);
27671
- if (est <= firstW * 1.22) {
27672
- for (const [r, sp] of combos) if (width(r, sp) <= firstW * SAFE) return { ratio: r, spacing: sp };
27673
- return null;
29035
+ const k = safety(faceClass, 0.95);
29036
+ const f = firstW * k, c = contW * k;
29037
+ const ladder = [[100, 0], ...SQUEEZE.filter(([r]) => r >= minRatio)];
29038
+ const base = simulateWrap(text, f, c, h, 100, "keep", { faceClass });
29039
+ if (base.lines < 2) return null;
29040
+ if (base.lastLineWidth <= c * PULL_TAIL) {
29041
+ let pull = null;
29042
+ for (const [r, sp] of ladder) {
29043
+ const amt = squeezeOf(r, sp);
29044
+ if (amt > PULL_MAX + 1e-9 || pull && amt >= pull.amt - 1e-9) continue;
29045
+ const w = simulateWrap(text, f, c, h, r, "keep", { faceClass, spacingPct: sp });
29046
+ if (w.lines < base.lines && worstLooseness(text, w.starts, f, c, h, r, sp, faceClass) <= LOOSE_LIMIT) pull = { r, sp, amt };
29047
+ }
29048
+ if (pull) return { ratio: pull.r, spacing: pull.sp };
29049
+ }
29050
+ let baseLines = 0;
29051
+ let best = null;
29052
+ for (const [r, sp] of ladder) {
29053
+ const w = simulateWrap(text, f, c, h, r, "keep", { faceClass, spacingPct: sp });
29054
+ if (!best) {
29055
+ if (w.lines < 2) return null;
29056
+ baseLines = w.lines;
29057
+ }
29058
+ if (w.lines > baseLines) continue;
29059
+ const orphan = w.lines > 1 && w.lastLineWidth <= contW * orphanRatio;
29060
+ const loose = worstLooseness(text, w.starts, f, c, h, r, sp, faceClass);
29061
+ const cost = Math.max(0, loose - LOOSE_LIMIT) + (orphan ? ORPHAN_COST : 0) + squeezeOf(r, sp) * COMPRESS_COST;
29062
+ if (!best || cost < best.cost - 1e-9) best = { r, sp, cost };
29063
+ }
29064
+ return best && (best.r !== 100 || best.sp !== 0) ? { ratio: best.r, spacing: best.sp } : null;
29065
+ }
29066
+ var LIST_BREAK = /* @__PURE__ */ new Set([..."\xB7,\u3001/"]);
29067
+ function fitCharBreaks(text, font, pt2, firstW, contW, minRatio = 88) {
29068
+ const faceClass = faceClassForGen(font);
29069
+ const h = pt2 * 100;
29070
+ const k = safety(faceClass, 0.95);
29071
+ if (!text.split(/ +/).some((w) => measureTextWidth(w, h, 100, { faceClass }) > contW * k)) return null;
29072
+ let best = null;
29073
+ for (const [r, sp] of [[100, 0], ...SQUEEZE.filter(([r2]) => r2 >= minRatio)]) {
29074
+ const amt = squeezeOf(r, sp);
29075
+ if (amt > PULL_MAX + 1e-9 || best && amt >= best.amt - 1e-9) continue;
29076
+ const wrapAt = (s) => simulateWrap(text, firstW * s, contW * s, h, r, "charAll", { faceClass, spacingPct: sp }).starts;
29077
+ const starts = wrapAt(1);
29078
+ if (wrapAt(0.99).join() !== starts.join() || wrapAt(1.01).join() !== starts.join()) continue;
29079
+ if (!starts.slice(1).every((s) => text[s - 1] === " " || LIST_BREAK.has(text[s - 1]))) continue;
29080
+ if (worstLooseness(text, starts, firstW, contW, h, r, sp, faceClass) > LOOSE_LIMIT) continue;
29081
+ best = { r, sp, amt };
29082
+ }
29083
+ return best ? { ratio: best.r, spacing: best.sp } : null;
29084
+ }
29085
+
29086
+ // src/hwpx/gen-marker.ts
29087
+ function markerLayout(font, pt2, leadTa, marker) {
29088
+ const left = leadTa * taHu(pt2);
29089
+ if (!marker) return { left, hang: 0, markerW: 0 };
29090
+ const markerW = Math.round(measureTextWidth(marker, pt2 * 100, 100, { faceClass: faceClassForGen(font) }));
29091
+ return { left, hang: markerW + taHu(pt2), markerW };
29092
+ }
29093
+ function markerRunXml(marker, charPr2, lay) {
29094
+ return `<hp:run charPrIDRef="${charPr2}"><hp:t>${escapeTextXml(marker)}<hp:tab width="${lay.hang - lay.markerW}" leader="0" type="1"/></hp:t></hp:run>`;
29095
+ }
29096
+
29097
+ // src/hwpx/gongmun-typo.ts
29098
+ var NBSP = "\xA0";
29099
+ var OPENER_BEFORE = /[\s(\[{>「『〈《【〔‘“·/:,~—–-]/u;
29100
+ function bindSpaces(s) {
29101
+ return s.replace(/(\d{4}|['’]\d{2})\. (\d{1,2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.${NBSP}$3.`).replace(/(\d{4}|['’]\d{2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.`).replace(/(?<![\d.])(\d{1,2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.`).replace(/(\d{1,2}:\d{2}) ([~∼-]) (\d{1,2}:\d{2})/g, `$1${NBSP}$2${NBSP}$3`).replace(/(\d(?:조|억|만|천)?) 원/g, `$1${NBSP}\uC6D0`);
29102
+ }
29103
+ function neighbor(s, i, dir) {
29104
+ for (let k = i + dir; k >= 0 && k < s.length; k += dir) if (!/[*_~]/.test(s[k])) return s[k];
29105
+ return "";
29106
+ }
29107
+ function curlQuotes(s, before = "") {
29108
+ let out = "";
29109
+ for (let i = 0; i < s.length; i++) {
29110
+ const c = s[i];
29111
+ if (c !== "'" && c !== '"') {
29112
+ out += c;
29113
+ continue;
29114
+ }
29115
+ const prev = neighbor(s, i, -1) || before;
29116
+ const opening = prev === "" || OPENER_BEFORE.test(prev);
29117
+ if (c === "'") {
29118
+ if (opening && /^\d{2}(?![\d'])/.test(s.slice(i + 1))) {
29119
+ out += "\u2019";
29120
+ continue;
29121
+ }
29122
+ out += opening ? "\u2018" : "\u2019";
29123
+ } else {
29124
+ out += opening ? "\u201C" : "\u201D";
29125
+ }
27674
29126
  }
27675
- const f = firstW * 0.95, c = contW * 0.95;
27676
- const base = simulateWrap(text, f, c, h, 100, "charAll", { faceClass });
27677
- if (base.lines < 2 || base.lastLineWidth > c * orphanRatio) return null;
27678
- for (const [r, sp] of combos) {
27679
- if (simulateWrap(text, f, c, h, r, "charAll", { faceClass, spacingPct: sp }).lines < base.lines) return { ratio: r, spacing: sp };
29127
+ return out;
29128
+ }
29129
+ function polishGongmunText(text) {
29130
+ if (!text) return text;
29131
+ let out = "";
29132
+ for (const [i, seg] of text.split(/(`[^`]*`|\]\([^)]*\))/).entries()) {
29133
+ out += i % 2 === 1 ? seg : curlQuotes(bindSpaces(seg), out.replace(/[*_~]+$/, "").slice(-1));
27680
29134
  }
27681
- return null;
29135
+ return out;
29136
+ }
29137
+ function polishGongmunBlock(b) {
29138
+ if (b.type === "table") return b.rows ? { ...b, rows: b.rows.map((r) => r.map(polishGongmunText)) } : b;
29139
+ if (b.type === "paragraph" || b.type === "heading" || b.type === "list_item" || b.type === "blockquote") return b.text ? { ...b, text: polishGongmunText(b.text) } : b;
29140
+ return b;
27682
29141
  }
27683
29142
 
27684
29143
  // src/hwpx/gen-frame-seoul.ts
@@ -27895,13 +29354,13 @@ function buildSummaryBox(text, ctx) {
27895
29354
  const { reg, bf, frame } = ctx;
27896
29355
  const w = ctx.W - 566;
27897
29356
  const avail = w - 280 - SUMMARY_PAD * 2;
27898
- const p = reg.para({ align: "JUSTIFY", lineSp: 160, left: SUMMARY_PAD, right: SUMMARY_PAD, keepWord: false });
29357
+ const p = reg.para({ align: "JUSTIFY", lineSp: 160, left: SUMMARY_PAD, right: SUMMARY_PAD });
27899
29358
  const bodies = text.split("\n").map((l) => l.trim().replace(/^[□■○ㅇ◦●\-–ㆍ·•]\s*/u, "")).filter(Boolean);
27900
29359
  let lines = 0;
27901
29360
  const paras = bodies.map((b) => {
27902
- const f = fitOrphanLine(b, frame.summaryFont, frame.summaryPt, avail, avail);
29361
+ const f = fitParagraph(b, frame.summaryFont, frame.summaryPt, avail, avail);
27903
29362
  const c = reg.char({ font: frame.summaryFont, pt: frame.summaryPt, bold: true, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 });
27904
- lines += simulateWrap(b, avail * 0.95, avail * 0.95, frame.summaryPt * 100, f?.ratio ?? 100, "charAll", { faceClass: "gothic", spacingPct: f?.spacing ?? 0 }).lines;
29363
+ lines += simulateWrap(b, avail * 0.995, avail * 0.995, frame.summaryPt * 100, f?.ratio ?? 100, "keep", { faceClass: faceClassForGen(frame.summaryFont), spacingPct: f?.spacing ?? 0 }).lines;
27905
29364
  return para(b, p, c);
27906
29365
  });
27907
29366
  const h = lines * Math.round(frame.summaryPt * 100 * 1.6) + 280 + 600;
@@ -27994,11 +29453,11 @@ function ministryRuns(marker, text, ctx, base, keywordColor, emphasisColor) {
27994
29453
  const { reg } = ctx;
27995
29454
  const baseId = reg.char(base);
27996
29455
  const mapper = inlineMapper(reg, base, emphasisColor);
27997
- let out = marker ? `<hp:run charPrIDRef="${baseId}"><hp:t>${escapeXml(marker + " ")}</hp:t></hp:run>` : "";
29456
+ let out = marker ? `<hp:run charPrIDRef="${baseId}"><hp:t>${escapeTextXml(marker + " ")}</hp:t></hp:run>` : "";
27998
29457
  const m = KEYWORD_RE.exec(text);
27999
29458
  if (m) {
28000
29459
  const kw = reg.char({ ...base, bold: true, color: keywordColor ?? base.color });
28001
- out += `<hp:run charPrIDRef="${kw}"><hp:t>${escapeXml(m[1] + " ")}</hp:t></hp:run>`;
29460
+ out += `<hp:run charPrIDRef="${kw}"><hp:t>${escapeTextXml(m[1] + " ")}</hp:t></hp:run>`;
28002
29461
  text = text.slice(m[0].length);
28003
29462
  }
28004
29463
  return out + generateRuns(text, baseId, mapper);
@@ -28055,8 +29514,8 @@ function buildMinistryToc(chapters, ctx) {
28055
29514
  const cCh = reg.char({ font: MINISTRY.headFont, pt: t.chapterPt });
28056
29515
  const cSub = reg.char({ font: MINISTRY.headFont, pt: t.subPt });
28057
29516
  const pLabel = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 1200, after: 2400 });
28058
- const pCh = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 2e3, keepWord: false });
28059
- const pSub = reg.para({ align: "LEFT", lineSp: 130, left: pad + 1400, before: 1200, keepWord: false });
29517
+ const pCh = reg.para({ align: "LEFT", lineSp: 130, left: pad, before: 2e3 });
29518
+ const pSub = reg.para({ align: "LEFT", lineSp: 130, left: pad + 1400, before: 1200 });
28060
29519
  const paras = [para("\uBAA9 \uCC28", pLabel, cLabel)];
28061
29520
  let h = 1200 + Math.round(t.labelPt * 130) + 2400;
28062
29521
  let roman = 0;
@@ -28136,10 +29595,10 @@ function buildMinistryItemBand(n, text, ctx, before) {
28136
29595
  const w = ctx.W;
28137
29596
  const band = bf.get({ t: s.line, b: s.line, l: NONE, r: NONE, fill: s.fill });
28138
29597
  const base = { font: MINISTRY.bandFont, pt: s.pt, bold: false };
28139
- const f = fitOrphanLine(plain(text), MINISTRY.bandFont, s.pt, w - 1800, w - 1800);
28140
- const styled = f ? { ...base, ratio: f.ratio, spacing: f.spacing } : base;
29598
+ const f = fitOneLine(plain(text), MINISTRY.bandFont, s.pt, w - 1800, s.pt);
29599
+ const styled = f.overflow || f.ratio === 100 && f.spacing === 0 ? base : { ...base, ratio: f.ratio, spacing: f.spacing };
28141
29600
  const runs = ministryRuns(circledNumber(n - 1), text, ctx, styled, MINISTRY.blue, MINISTRY.blue);
28142
- const p = reg.para({ align: "LEFT", lineSp: 130, left: 300, keepWord: false });
29601
+ const p = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
28143
29602
  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" });
28144
29603
  return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), reg.char(base));
28145
29604
  }
@@ -28148,20 +29607,21 @@ function buildMinistrySummaryBox(text, ctx) {
28148
29607
  const s = MINISTRY.summary;
28149
29608
  const w = ctx.W - 600;
28150
29609
  const avail = w - 280 - 600;
28151
- const pItem = reg.para({ align: "LEFT", lineSp: 140, left: 300, right: 300, indent: -Math.round(s.pt * 100 * 0.9), keepWord: false });
28152
- const pRef = reg.para({ align: "LEFT", lineSp: 130, left: 300 + Math.round(s.pt * 100 * 0.9), right: 300, keepWord: false });
29610
+ const lay = markerLayout(MINISTRY.bodyFont, s.pt, 0, "\u25AA");
29611
+ const pItem = reg.para({ align: "LEFT", lineSp: 140, left: 300, right: 300, indent: -lay.hang, autoTab: true });
29612
+ const pRef = reg.para({ align: "LEFT", lineSp: 130, left: 300 + lay.hang, right: 300 });
28153
29613
  const baseItem = { font: MINISTRY.bodyFont, pt: s.pt, bold: false };
28154
29614
  const cRef = reg.char({ font: MINISTRY.refFont, pt: s.refPt });
28155
29615
  let h = 0;
28156
29616
  const paras = text.split("\n").map((raw) => raw.trim()).filter(Boolean).map((line) => {
28157
29617
  const isRef = /^(\*|※)/.test(line);
28158
29618
  if (isRef) {
28159
- h += Math.round(s.refPt * 100 * 1.3) * simulateWrap(line, avail, avail, s.refPt * 100, 100, "charAll", { faceClass: "gothic" }).lines;
29619
+ h += Math.round(s.refPt * 100 * 1.3) * simulateWrap(line, avail - lay.hang, avail - lay.hang, s.refPt * 100, 100, "keep", { faceClass: faceClassForGen(MINISTRY.refFont) }).lines;
28160
29620
  return `<hp:p paraPrIDRef="${pRef}" styleIDRef="0">${generateRuns(line, cRef, inlineMapper(reg, { font: MINISTRY.refFont, pt: s.refPt, bold: false }))}</hp:p>`;
28161
29621
  }
28162
29622
  const body = line.replace(/^[▪■□○ㅇ●\-–ㆍ·•]\s*/u, "");
28163
- h += Math.round(s.pt * 100 * 1.4) * simulateWrap(plain(body), avail, avail, s.pt * 100, 100, "charAll").lines;
28164
- return `<hp:p paraPrIDRef="${pItem}" styleIDRef="0">${ministryRuns("\u25AA", body, ctx, baseItem, MINISTRY.blue)}</hp:p>`;
29623
+ h += Math.round(s.pt * 100 * 1.4) * simulateWrap(plain(body), avail - lay.hang, avail - lay.hang, s.pt * 100, 100, "keep", { faceClass: faceClassForGen(MINISTRY.bodyFont) }).lines;
29624
+ return `<hp:p paraPrIDRef="${pItem}" styleIDRef="0">${markerRunXml("\u25AA", reg.char(baseItem), lay)}${ministryRuns("", body, ctx, baseItem, MINISTRY.blue)}</hp:p>`;
28165
29625
  });
28166
29626
  h += 280 + 600;
28167
29627
  const box = bf.get({ t: "thin", b: "thin", l: "thin", r: "thin", fill: s.fill });
@@ -28213,7 +29673,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28213
29673
  const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
28214
29674
  const scheme = pickScheme(g, pre.hasBoxMarkers);
28215
29675
  const gaejosik = scheme.kind === "gaejosik";
28216
- const outline = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
29676
+ const raw = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
29677
+ const outline = { ...raw, title: raw.title && polishGongmunText(raw.title), nodes: raw.nodes.map(polishNode) };
28217
29678
  const frame = { reg, bf: bfReg, frame: scheme.frame, W };
28218
29679
  const lineHu = (st) => Math.round(st.pt * 100 * ((st.lineSp ?? scheme.lineSp) / 100));
28219
29680
  const paras = [];
@@ -28237,15 +29698,16 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28237
29698
  let frontKind = "start";
28238
29699
  const pushSummary = (t) => {
28239
29700
  frontKind = "summary";
28240
- const box = buildSummaryBox(t, frame);
29701
+ const box = buildSummaryBox(polishGongmunText(t), frame);
28241
29702
  if (box.lines > 3) warnings.push(`\uC694\uC57D\uBC15\uC2A4\uAC00 ${box.lines}\uC904\uC785\uB2C8\uB2E4 \u2014 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uC904\uC774\uC138\uC694`);
28242
29703
  paras.push(box.xml);
28243
29704
  };
28244
- const docTitle = g.docHead?.title ?? outline.title ?? "";
29705
+ const docTitle = polishGongmunText(g.docHead?.title ?? outline.title ?? "");
29706
+ const reportInfo = g.reportInfo && polishGongmunText(g.reportInfo);
28245
29707
  if (preset === "official") {
28246
29708
  if (g.docHead) paras.push(buildDocHeadTable({ ...g.docHead, title: docTitle }, frame));
28247
29709
  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));
28248
- if (g.reportInfo) paras.push(generateParagraph(g.reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
29710
+ if (reportInfo) paras.push(generateParagraph(reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
28249
29711
  if (g.approval) paras.push(buildApprovalSeoul(g.approval, null, frame));
28250
29712
  } else if (isMinistry) {
28251
29713
  if (g.cover) {
@@ -28284,7 +29746,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28284
29746
  paras.push(buildApprovalSeoul(g.approval, null, frame));
28285
29747
  }
28286
29748
  if (docTitle) {
28287
- const t = buildReportTitleTable(docTitle, g.reportInfo, frame);
29749
+ const t = buildReportTitleTable(docTitle, reportInfo, frame);
28288
29750
  if (t.overflow) warnings.push(`\uC81C\uBAA9\uC774 \uAE38\uC5B4 \uD55C \uC904\uC5D0 \uB2F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(20pt\xB7\uC7A5\uD3C9 85%\uAE4C\uC9C0 \uCD95\uC18C) \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${docTitle.slice(0, 30)}\u2026"`);
28289
29751
  if (pendingPageBreak) {
28290
29752
  paras.push(t.xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `).replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${newPageNumCtrl(1)}`));
@@ -28292,7 +29754,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28292
29754
  } else paras.push(t.xml);
28293
29755
  frontKind = "title";
28294
29756
  }
28295
- if (!docTitle && g.reportInfo) paras.push(generateParagraph(g.reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
29757
+ if (!docTitle && reportInfo) paras.push(generateParagraph(reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
28296
29758
  if (g.summary) pushSummary(g.summary);
28297
29759
  else if (!outline.nodes.some((n) => n.kind === "summary")) warnings.push('\uBCF4\uACE0\uC11C \uC694\uC57D\uBC15\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9 \uC9C1\uD6C4 \uC778\uC6A9\uBB38(>)\uC5D0 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uB123\uAC70\uB098 summary \uC635\uC158\uC744 \uC9C0\uC815\uD558\uC138\uC694');
28298
29760
  } else {
@@ -28331,51 +29793,55 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28331
29793
  marker = numberer.next(depth, sup);
28332
29794
  itemSeq++;
28333
29795
  }
28334
- const text = marker ? `${marker} ${node.text}` : node.text;
28335
- const geom = levelGeometry(st, marker);
28336
- const availFirst = W - geom.left;
28337
- const availCont = W - geom.left + geom.indent;
28338
- let pt2 = st.pt, ratio = 100, spacing = 0;
29796
+ const mk = isMinistry && node.marker ? node.marker : marker;
29797
+ const lay = markerLayout(st.font, st.pt, st.leadTa, mk);
29798
+ const textW2 = W - lay.left - lay.hang;
29799
+ let ratio = 100, spacing = 0, charBreaks = false;
28339
29800
  if (st.oneLine) {
28340
- const fit = fitOneLine(plain2(text), st.font, st.pt, availFirst, st.pt, 90);
29801
+ const fit = fitOneLine(plain2(node.text), st.font, st.pt, textW2, st.pt, 90);
28341
29802
  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"`);
28342
29803
  else {
28343
29804
  ratio = fit.ratio;
28344
29805
  spacing = fit.spacing;
28345
29806
  }
28346
- } else if (g.autoFitMinRatio !== null) {
28347
- const f = fitOrphanLine(plain2(text), st.font, st.pt, availFirst, availCont);
29807
+ } else {
29808
+ const t = plain2(node.text);
29809
+ const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio ?? 101);
29810
+ const f = cb ?? (g.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio) : null);
28348
29811
  if (f) {
28349
29812
  ratio = f.ratio;
28350
29813
  spacing = f.spacing;
28351
29814
  }
29815
+ charBreaks = !!cb;
28352
29816
  }
28353
- const base = { font: st.font, pt: pt2, bold: st.bold, ratio, spacing };
28354
- const geom2 = pt2 !== st.pt ? levelGeometry({ ...st, pt: pt2 }, marker) : geom;
29817
+ const base = { font: st.font, pt: st.pt, bold: st.bold, ratio, spacing };
28355
29818
  const consecutiveBox = prevKind === "item" && prevItemDepth === 0 && depth === 0;
28356
29819
  const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
28357
29820
  const before = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
28358
29821
  const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
29822
+ const paraSpec = { align: "JUSTIFY", left: lay.left, indent: -lay.hang, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
29823
+ const markerRun = mk ? markerRunXml(mk, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay) : "";
28359
29824
  if (isMinistry) {
28360
- const mk = node.marker ?? marker;
28361
- const g2 = node.marker ? levelGeometry(st, mk) : geom2;
28362
- const paraId2 = reg.para({ align: "JUSTIFY", left: g2.left, indent: g2.indent, before, after: 600, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
28363
- const runs = ministryRuns(mk, node.text, frame, base, depth === 0 ? MINISTRY.blue : null);
29825
+ const paraId2 = reg.para({ ...paraSpec, after: 600 });
29826
+ const runs = markerRun + ministryRuns("", node.text, frame, base, depth === 0 ? MINISTRY.blue : null);
28364
29827
  return `<hp:p paraPrIDRef="${paraId2}" styleIDRef="${styleId}">${runs}</hp:p>`;
28365
29828
  }
28366
- const paraId = reg.para({ align: "JUSTIFY", left: geom2.left, indent: geom2.indent, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: false });
28367
- return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), styleId);
29829
+ const paraId = reg.para(paraSpec);
29830
+ return `<hp:p paraPrIDRef="${paraId}" styleIDRef="${styleId}">${markerRun}${generateRuns(node.text, reg.char(base), inlineMapper(reg, base))}</hp:p>`;
28368
29831
  };
28369
29832
  const renderRef = (node) => {
28370
29833
  const st = scheme.ref;
28371
29834
  const marker = isMinistry ? "*" : "\u203B";
29835
+ const lay = markerLayout(st.font, st.pt, 0, marker);
28372
29836
  const left = isMinistry ? st.leadTa * taHu(st.pt) : leadLeft(node.depth, st.pt);
28373
- const geom = levelGeometry({ ...st, leadTa: 0 }, marker);
28374
- const base = { font: st.font, pt: st.pt, bold: st.bold };
28375
- const f = fitOrphanLine(plain2(`${marker} ${node.text}`), st.font, st.pt, W - left, W - left + geom.indent);
28376
- const baseF = f ? { ...base, ratio: f.ratio, spacing: f.spacing } : base;
28377
- const paraId = reg.para({ align: "JUSTIFY", left, indent: geom.indent, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: false });
28378
- return generateParagraph(`${marker} ${node.text}`, paraId, reg.char(baseF), inlineMapper(reg, baseF));
29837
+ const textW2 = W - left - lay.hang;
29838
+ const t = plain2(node.text);
29839
+ const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio ?? 101);
29840
+ const f = cb ?? (g.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g.autoFitMinRatio) : null);
29841
+ const base = { font: st.font, pt: st.pt, bold: st.bold, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 };
29842
+ const paraId = reg.para({ align: "JUSTIFY", left, indent: -lay.hang, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: !cb, autoTab: true, widowOrphan: true });
29843
+ const markerRun = markerRunXml(marker, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay);
29844
+ return `<hp:p paraPrIDRef="${paraId}" styleIDRef="0">${markerRun}${generateRuns(node.text, reg.char(base), inlineMapper(reg, base))}</hp:p>`;
28379
29845
  };
28380
29846
  const renderPara = (node) => {
28381
29847
  const st = scheme.body;
@@ -28389,16 +29855,19 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28389
29855
  if (pics.every(Boolean)) return `<hp:p paraPrIDRef="${reg.para({ align: "CENTER", lineSp: scheme.lineSp })}" styleIDRef="0"><hp:run charPrIDRef="${reg.char({ font: st.font, pt: st.pt })}">${pics.join("")}</hp:run></hp:p>`;
28390
29856
  }
28391
29857
  }
28392
- let ratio = 100, spacing = 0;
28393
- if (!node.align && g.autoFitMinRatio !== null) {
28394
- const f = fitOrphanLine(plain2(node.text), st.font, st.pt, W, W);
29858
+ let ratio = 100, spacing = 0, charBreaks = false;
29859
+ if (!node.align) {
29860
+ const t = plain2(node.text);
29861
+ const cb = fitCharBreaks(t, st.font, st.pt, W, W, g.autoFitMinRatio ?? 101);
29862
+ const f = cb ?? (g.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, W, W, g.autoFitMinRatio) : null);
28395
29863
  if (f) {
28396
29864
  ratio = f.ratio;
28397
29865
  spacing = f.spacing;
28398
29866
  }
29867
+ charBreaks = !!cb;
28399
29868
  }
28400
29869
  const base = { font: st.font, pt: st.pt, bold: st.bold, ratio, spacing };
28401
- const paraId = reg.para({ align: node.align ?? "JUSTIFY", lineSp: scheme.lineSp, keepWord: false });
29870
+ const paraId = reg.para({ align: node.align ?? "JUSTIFY", lineSp: scheme.lineSp, keepWord: !charBreaks, widowOrphan: true });
28402
29871
  return generateParagraph(node.text, paraId, reg.char(base), inlineMapper(reg, base));
28403
29872
  };
28404
29873
  const renderChapter = (node) => {
@@ -28426,7 +29895,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28426
29895
  const fit = fitOneLine(plain2(text), st.font, st.pt, W, st.pt - 2);
28427
29896
  const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
28428
29897
  const before = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
28429
- const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true, keepWord: false });
29898
+ const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true });
28430
29899
  return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), 2);
28431
29900
  };
28432
29901
  const renderAttach = (node, first) => {
@@ -28434,7 +29903,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28434
29903
  let text = node.text;
28435
29904
  if (/^\s*붙\s*임/.test(text)) text = text.replace(/^\s*붙\s*임\s*[::]?\s*/, "\uBD99\uC784 ");
28436
29905
  else text = text.replace(/\s+$/, "");
28437
- const paraId = reg.para({ align: "LEFT", before: first ? lineHu(scheme.body) : 0, lineSp: st.lineSp ?? scheme.lineSp, keepWord: false });
29906
+ const paraId = reg.para({ align: "LEFT", before: first ? lineHu(scheme.body) : 0, lineSp: st.lineSp ?? scheme.lineSp });
28438
29907
  const base = { font: st.font, pt: st.pt, bold: st.bold };
28439
29908
  return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base));
28440
29909
  };
@@ -28604,6 +30073,13 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
28604
30073
  scheme
28605
30074
  };
28606
30075
  }
30076
+ function polishNode(n) {
30077
+ if (n.kind === "block") {
30078
+ const b = n.block;
30079
+ return b.type === "table" && b.rows ? { ...n, block: { ...b, rows: b.rows.map((r) => r.map(polishGongmunText)) } } : n;
30080
+ }
30081
+ return { ...n, text: polishGongmunText(n.text) };
30082
+ }
28607
30083
  function legalDepthOf(marker) {
28608
30084
  if (/^\d{1,2}\.$/.test(marker)) return 0;
28609
30085
  if (/^[가-힣]\.$/.test(marker)) return 1;
@@ -28660,7 +30136,7 @@ async function markdownToHwpx(markdown, options) {
28660
30136
  }
28661
30137
  const measured = !!gongmun && usesReportFonts(gongmun.preset);
28662
30138
  const richAssets = !!gongmun && needsGaejosikAssets(gongmun);
28663
- const blocks = parseMarkdownToBlocks(md2);
30139
+ const blocks = gongmun ? parseMarkdownToBlocks(md2).map(polishGongmunBlock) : parseMarkdownToBlocks(md2);
28664
30140
  const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
28665
30141
  const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
28666
30142
  const staticBfEnd = staticBorderFillNext(gongmun);
@@ -29040,6 +30516,8 @@ function parseRenderStyles(headXml) {
29040
30516
  const root = doc.documentElement;
29041
30517
  if (!root) return styles;
29042
30518
  const hangulFonts = collectHangulFonts(root);
30519
+ const autoTabPr = /* @__PURE__ */ new Set();
30520
+ const paraTabRef = /* @__PURE__ */ new Map();
29043
30521
  const walk = (el) => {
29044
30522
  const tag = (el.tagName || "").replace(/^[^:]+:/, "");
29045
30523
  if (tag === "charPr") {
@@ -29064,12 +30542,17 @@ function parseRenderStyles(headXml) {
29064
30542
  face
29065
30543
  });
29066
30544
  }
30545
+ } else if (tag === "tabPr") {
30546
+ const id = el.getAttribute("id");
30547
+ if (id != null && el.getAttribute("autoTabLeft") === "1") autoTabPr.add(id);
29067
30548
  } else if (tag === "paraPr") {
29068
30549
  const id = el.getAttribute("id");
29069
30550
  if (id != null) {
29070
30551
  const align = findChildByLocalName(el, "align");
29071
30552
  styles.paraAlign.set(id, align?.getAttribute("horizontal") || "JUSTIFY");
29072
30553
  styles.paraGeom.set(id, parseParaGeom(el));
30554
+ const tab = el.getAttribute("tabPrIDRef");
30555
+ if (tab != null) paraTabRef.set(id, tab);
29073
30556
  }
29074
30557
  } else if (tag === "borderFill") {
29075
30558
  const id = el.getAttribute("id");
@@ -29094,6 +30577,10 @@ function parseRenderStyles(headXml) {
29094
30577
  }
29095
30578
  };
29096
30579
  walk(root);
30580
+ for (const [id, tab] of paraTabRef) {
30581
+ const g = styles.paraGeom.get(id);
30582
+ if (g && autoTabPr.has(tab)) g.autoTabLeft = true;
30583
+ }
29097
30584
  return styles;
29098
30585
  }
29099
30586
 
@@ -31241,7 +32728,8 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
31241
32728
  if (sanitizeText(newPlain) !== newPlain) {
31242
32729
  return skip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uD328\uCE58 \uC2DC \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
31243
32730
  }
31244
- const splices = buildParagraphSplices(mapping.para, newPlain, ctx.scans[mapping.para.sectionIndex]?.xml);
32731
+ const xml = ctx.scans[mapping.para.sectionIndex]?.xml;
32732
+ const splices = (xml ? markerRunSplices(mapping.para, xml, newPlain) : null) ?? buildParagraphSplices(mapping.para, newPlain, xml);
31245
32733
  if (splices === null) return skip("\uBB38\uB2E8\uC5D0 \uD14D\uC2A4\uD2B8 \uB178\uB4DC\uB97C \uB9CC\uB4E4 \uC218 \uC5C6\uC74C");
31246
32734
  ctx.sectionSplices[mapping.para.sectionIndex].push(...splices);
31247
32735
  return 1;
@@ -33335,28 +34823,214 @@ function solveRowHeights(cells, rowCount) {
33335
34823
  return h;
33336
34824
  }
33337
34825
 
34826
+ // src/render/para-model.ts
34827
+ function ln(el) {
34828
+ return (el.tagName || "").replace(/^[^:]+:/, "");
34829
+ }
34830
+ function elements(el) {
34831
+ const out = [];
34832
+ const children = el.childNodes;
34833
+ if (!children) return out;
34834
+ for (let i = 0; i < children.length; i++) {
34835
+ if (children[i].nodeType === 1) out.push(children[i]);
34836
+ }
34837
+ return out;
34838
+ }
34839
+ function num2(el, attr, fallback = 0) {
34840
+ return el ? toInt32(el.getAttribute(attr) ?? void 0, fallback) : fallback;
34841
+ }
34842
+ function findFirst(el, name, depth = 0) {
34843
+ if (depth > 64) return null;
34844
+ for (const ch of elements(el)) {
34845
+ if (ln(ch) === name) return ch;
34846
+ const found = findFirst(ch, name, depth + 1);
34847
+ if (found) return found;
34848
+ }
34849
+ return null;
34850
+ }
34851
+ var OBJ_TAGS = /* @__PURE__ */ new Set(["tbl", "pic", "container", "equation", "rect", "ellipse", "polygon", "curv", "line", "arc", "ole", "textart"]);
34852
+ var CHAR_CTRL_1SLOT = /* @__PURE__ */ new Set(["lineBreak", "hyphen", "nbSpace", "fwSpace"]);
34853
+ function pushFillers(chars, n, prId) {
34854
+ for (let i = 0; i < n; i++) chars.push({ ch: "", prId });
34855
+ }
34856
+ function pushTextSlots(t, chars, prId, depth) {
34857
+ if (depth > 32) return;
34858
+ const kids = t.childNodes;
34859
+ if (!kids) return;
34860
+ for (let i = 0; i < kids.length; i++) {
34861
+ const c = kids[i];
34862
+ if (c.nodeType === 3 || c.nodeType === 4) {
34863
+ for (const cp of c.textContent ?? "") {
34864
+ chars.push({ ch: cp, prId });
34865
+ if (cp.length === 2) chars.push({ ch: "", prId });
34866
+ }
34867
+ } else if (c.nodeType === 1) {
34868
+ const el = c;
34869
+ const tag = ln(el);
34870
+ if (tag === "tab") {
34871
+ chars.push({ ch: "", prId, tab: true, tabW: num2(el, "width") });
34872
+ pushFillers(chars, 7, prId);
34873
+ } else if (CHAR_CTRL_1SLOT.has(tag)) {
34874
+ chars.push(tag === "nbSpace" ? { ch: " ", prId, nb: true } : { ch: tag === "fwSpace" ? " " : "", prId });
34875
+ } else {
34876
+ pushTextSlots(el, chars, prId, depth + 1);
34877
+ }
34878
+ }
34879
+ }
34880
+ }
34881
+ function buildPara(p) {
34882
+ const chars = [];
34883
+ const objs = [];
34884
+ let segs = [];
34885
+ for (const runEl of elements(p)) {
34886
+ const tag = ln(runEl);
34887
+ if (tag === "run") {
34888
+ const prId = runEl.getAttribute("charPrIDRef");
34889
+ for (const ch of elements(runEl)) {
34890
+ const cn = ln(ch);
34891
+ if (cn === "t") {
34892
+ pushTextSlots(ch, chars, prId, 0);
34893
+ } else if (OBJ_TAGS.has(cn)) {
34894
+ const sz = findChildByLocalName(ch, "sz");
34895
+ const pos = findChildByLocalName(ch, "pos");
34896
+ const w = num2(sz, "width") || num2(findChildByLocalName(ch, "curSz"), "width") || num2(findChildByLocalName(ch, "orgSz"), "width");
34897
+ const h = num2(sz, "height") || num2(findChildByLocalName(ch, "curSz"), "height") || num2(findChildByLocalName(ch, "orgSz"), "height");
34898
+ const inline = pos?.getAttribute("treatAsChar") === "1";
34899
+ const om = inline && cn === "tbl" ? findChildByLocalName(ch, "outMargin") : null;
34900
+ objs.push({
34901
+ el: ch,
34902
+ tag: cn,
34903
+ index: chars.length,
34904
+ inline,
34905
+ width: w,
34906
+ height: h,
34907
+ omL: num2(om, "left"),
34908
+ omR: num2(om, "right")
34909
+ });
34910
+ pushFillers(chars, 8, prId);
34911
+ } else {
34912
+ pushFillers(chars, 8, prId);
34913
+ }
34914
+ }
34915
+ } else if (tag === "linesegarray") {
34916
+ segs = elements(runEl).filter((s) => ln(s) === "lineseg").map((s) => ({
34917
+ textpos: num2(s, "textpos"),
34918
+ vertpos: num2(s, "vertpos"),
34919
+ horzpos: num2(s, "horzpos"),
34920
+ horzsize: num2(s, "horzsize"),
34921
+ textheight: num2(s, "textheight", 1e3),
34922
+ baseline: num2(s, "baseline", 850)
34923
+ }));
34924
+ }
34925
+ }
34926
+ return { chars, segs, objs, paraPrId: p.getAttribute("paraPrIDRef") };
34927
+ }
34928
+ var DEFAULT_TAB_HU = 4e3;
34929
+ function tabAdvance(x, firstLine, geom, saved = 0) {
34930
+ const hang = geom && geom.marginIntent < 0 ? -geom.marginIntent : 0;
34931
+ if (firstLine && geom?.autoTabLeft && hang > 0 && x < hang - 0.5) return hang - x;
34932
+ if (saved > 0) return saved;
34933
+ return (Math.floor(x / DEFAULT_TAB_HU) + 1) * DEFAULT_TAB_HU - x;
34934
+ }
34935
+ function cellContentExtent(cell2, memo) {
34936
+ if (!cell2.sub) return 0;
34937
+ const hit = memo?.cell.get(cell2.el);
34938
+ if (hit !== void 0) return hit;
34939
+ let ext = 0;
34940
+ for (const p of elements(cell2.sub)) {
34941
+ if (ln(p) !== "p") continue;
34942
+ const m = buildPara(p);
34943
+ for (const s of m.segs) ext = Math.max(ext, s.vertpos + s.textheight);
34944
+ const baseV = m.segs[0]?.vertpos ?? 0;
34945
+ for (const o of m.objs) {
34946
+ if (o.inline) {
34947
+ const h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el, memo)) : o.height;
34948
+ ext = Math.max(ext, baseV + h);
34949
+ continue;
34950
+ }
34951
+ const pos = findChildByLocalName(o.el, "pos");
34952
+ if ((pos?.getAttribute("vertRelTo") ?? "PARA") !== "PARA") continue;
34953
+ const om = findChildByLocalName(o.el, "outMargin");
34954
+ const pushed = baseV - (num2(om, "top") + o.height + num2(om, "bottom"));
34955
+ const anchor = pushed >= -100 ? pushed : baseV;
34956
+ ext = Math.max(ext, anchor + num2(om, "top") + num2(pos, "vertOffset") + o.height);
34957
+ }
34958
+ }
34959
+ memo?.cell.set(cell2.el, ext);
34960
+ return ext;
34961
+ }
34962
+ function collectCells(tbl2) {
34963
+ const inMargin = findChildByLocalName(tbl2, "inMargin");
34964
+ const defL = num2(inMargin, "left", 141), defR = num2(inMargin, "right", 141);
34965
+ const defT = num2(inMargin, "top", 141), defB = num2(inMargin, "bottom", 141);
34966
+ const cells = [];
34967
+ for (const tr of elements(tbl2)) {
34968
+ if (ln(tr) !== "tr") continue;
34969
+ for (const tc2 of elements(tr)) {
34970
+ if (ln(tc2) !== "tc") continue;
34971
+ const addr = findChildByLocalName(tc2, "cellAddr");
34972
+ const span = findChildByLocalName(tc2, "cellSpan");
34973
+ const csz = findChildByLocalName(tc2, "cellSz");
34974
+ const cm = findChildByLocalName(tc2, "cellMargin");
34975
+ if (!addr || !csz) continue;
34976
+ cells.push({
34977
+ el: tc2,
34978
+ // 음수 주소(uint32 역변환·손상 입력) 방어 — colX/rowY 인덱스 이탈로 NaN 좌표 방지
34979
+ ca: Math.max(0, num2(addr, "colAddr")),
34980
+ ra: Math.max(0, num2(addr, "rowAddr")),
34981
+ cs: Math.max(1, num2(span, "colSpan", 1)),
34982
+ rs: Math.max(1, num2(span, "rowSpan", 1)),
34983
+ w: num2(csz, "width"),
34984
+ h: num2(csz, "height"),
34985
+ bfId: tc2.getAttribute("borderFillIDRef"),
34986
+ sub: findChildByLocalName(tc2, "subList"),
34987
+ marginL: cm ? num2(cm, "left", defL) : defL,
34988
+ marginR: cm ? num2(cm, "right", defR) : defR,
34989
+ marginT: cm ? num2(cm, "top", defT) : defT,
34990
+ marginB: cm ? num2(cm, "bottom", defB) : defB
34991
+ });
34992
+ }
34993
+ }
34994
+ return cells;
34995
+ }
34996
+ function measureTableHeight(tbl2, memo) {
34997
+ const hit = memo?.table.get(tbl2);
34998
+ if (hit !== void 0) return hit;
34999
+ const cells = collectCells(tbl2);
35000
+ if (cells.length === 0 || cells.length > 4096) return 0;
35001
+ const nRows = Math.max(...cells.map((c) => c.ra + c.rs));
35002
+ const rowH = solveRowHeights(
35003
+ cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c, memo) : void 0 })),
35004
+ nRows
35005
+ );
35006
+ let sum = 0;
35007
+ for (const h of rowH) sum += h;
35008
+ memo?.table.set(tbl2, sum);
35009
+ return sum;
35010
+ }
35011
+
33338
35012
  // src/render/reflow.ts
33339
35013
  var LINESEG_FLAGS = "393216";
33340
35014
  var BASELINE_RATIO = 0.85;
33341
- function ln(el) {
35015
+ function ln2(el) {
33342
35016
  return (el.tagName || "").replace(/^[^:]+:/, "");
33343
35017
  }
33344
- function elements(el) {
35018
+ function elements2(el) {
33345
35019
  const out = [];
33346
35020
  const kids = el.childNodes;
33347
35021
  if (!kids) return out;
33348
35022
  for (let i = 0; i < kids.length; i++) if (kids[i].nodeType === 1) out.push(kids[i]);
33349
35023
  return out;
33350
35024
  }
33351
- function num2(el, attr, fallback = 0) {
35025
+ function num3(el, attr, fallback = 0) {
33352
35026
  return el ? toInt32(el.getAttribute(attr) ?? void 0, fallback) : fallback;
33353
35027
  }
33354
35028
  function shiftParaVert(p, delta) {
33355
- for (const lsa of elements(p)) {
33356
- if (ln(lsa) !== "linesegarray") continue;
33357
- for (const seg of elements(lsa)) {
33358
- if (ln(seg) !== "lineseg") continue;
33359
- seg.setAttribute("vertpos", String(num2(seg, "vertpos") + delta));
35029
+ for (const lsa of elements2(p)) {
35030
+ if (ln2(lsa) !== "linesegarray") continue;
35031
+ for (const seg of elements2(lsa)) {
35032
+ if (ln2(seg) !== "lineseg") continue;
35033
+ seg.setAttribute("vertpos", String(num3(seg, "vertpos") + delta));
33360
35034
  }
33361
35035
  }
33362
35036
  }
@@ -33377,15 +35051,31 @@ function pitchFor(height, geom) {
33377
35051
  function reflowPara(p, doc, styles, areaW, startV, mode) {
33378
35052
  const m = buildPara(p);
33379
35053
  if (m.segs.length > 0) return null;
35054
+ const geom = styles.paraGeom.get(m.paraPrId ?? "") ?? DEFAULT_PARA_GEOM;
33380
35055
  const realIdx = [];
35056
+ const widths = [];
33381
35057
  let text = "";
35058
+ let x = Math.max(geom.marginIntent, 0);
33382
35059
  for (let i = 0; i < m.chars.length; i++) {
33383
- const ch = m.chars[i].ch;
33384
- if (ch === "") continue;
33385
- for (let u = 0; u < ch.length; u++) realIdx.push(i);
33386
- text += ch;
35060
+ const c = m.chars[i];
35061
+ if (c.tab) {
35062
+ const adv = tabAdvance(x, true, geom, c.tabW);
35063
+ realIdx.push(i);
35064
+ widths.push(adv);
35065
+ text += " ";
35066
+ x += adv;
35067
+ continue;
35068
+ }
35069
+ if (c.ch === "") continue;
35070
+ const st = (c.prId != null ? styles.charPr.get(c.prId) : void 0) ?? DEFAULT_CHAR;
35071
+ const w = measureTextWidth(c.ch, st.height, st.ratio, { spacingPct: st.spacing, faceClass: faceClassOf(st.face) });
35072
+ for (let u = 0; u < c.ch.length; u++) {
35073
+ realIdx.push(i);
35074
+ widths.push(u === 0 ? w : 0);
35075
+ }
35076
+ text += c.nb ? "\xA0" : c.ch;
35077
+ x += w;
33387
35078
  }
33388
- const geom = styles.paraGeom.get(m.paraPrId ?? "") ?? DEFAULT_PARA_GEOM;
33389
35079
  let domChar = DEFAULT_CHAR;
33390
35080
  for (const c of m.chars) {
33391
35081
  if (c.ch !== "" && c.prId != null) {
@@ -33397,8 +35087,8 @@ function reflowPara(p, doc, styles, areaW, startV, mode) {
33397
35087
  }
33398
35088
  }
33399
35089
  if (domChar === DEFAULT_CHAR) {
33400
- for (const run of elements(p)) {
33401
- if (ln(run) !== "run") continue;
35090
+ for (const run of elements2(p)) {
35091
+ if (ln2(run) !== "run") continue;
33402
35092
  const st = styles.charPr.get(run.getAttribute("charPrIDRef") ?? "");
33403
35093
  if (st) {
33404
35094
  domChar = st;
@@ -33407,16 +35097,13 @@ function reflowPara(p, doc, styles, areaW, startV, mode) {
33407
35097
  }
33408
35098
  }
33409
35099
  const height = domChar.height || 1e3;
33410
- const ratio = domChar.ratio || 100;
33411
- const spacingPct = domChar.spacing || 0;
33412
35100
  const marginL = geom.marginLeft;
33413
35101
  const avail = Math.max(1e3, areaW - marginL - geom.marginRight);
33414
35102
  const firstWidth = avail;
33415
35103
  const contWidth = Math.max(500, avail + Math.min(0, geom.marginIntent));
33416
35104
  const contHorz = marginL - Math.min(0, geom.marginIntent);
33417
35105
  const paraMode = geom.wrapMode ?? mode;
33418
- const faceClass = faceClassOf(domChar.face);
33419
- const wrap = text.length === 0 ? { lines: 1, starts: [0], lastLineWidth: 0 } : simulateWrap(text, firstWidth, contWidth, height, ratio, paraMode, { spacingPct, faceClass });
35106
+ const wrap = text.length === 0 ? { lines: 1, starts: [0], lastLineWidth: 0 } : simulateWrap(text, firstWidth, contWidth, height, 100, paraMode, { widths });
33420
35107
  const pitch = pitchFor(height, geom);
33421
35108
  const baseline = Math.round(height * BASELINE_RATIO);
33422
35109
  const spacing = Math.max(0, pitch - height);
@@ -33426,7 +35113,7 @@ function reflowPara(p, doc, styles, areaW, startV, mode) {
33426
35113
  const effH = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el)) : o.height;
33427
35114
  const pos = findChildByLocalName(o.el, "pos");
33428
35115
  const om = findChildByLocalName(o.el, "outMargin");
33429
- const outT = num2(om, "top"), outB = num2(om, "bottom");
35116
+ const outT = num3(om, "top"), outB = num3(om, "bottom");
33430
35117
  if (o.inline) {
33431
35118
  objBottom = Math.max(objBottom, startV + outT + effH + outB + spacing);
33432
35119
  continue;
@@ -33435,7 +35122,7 @@ function reflowPara(p, doc, styles, areaW, startV, mode) {
33435
35122
  if (wrapAttr === "BEHIND_TEXT" || wrapAttr === "IN_FRONT_OF_TEXT") continue;
33436
35123
  const vertRel = pos?.getAttribute("vertRelTo") ?? "PARA";
33437
35124
  if (vertRel === "PAGE" || vertRel === "PAPER") continue;
33438
- const vo = Math.max(0, num2(pos, "vertOffset"));
35125
+ const vo = Math.max(0, num3(pos, "vertOffset"));
33439
35126
  if (wrapAttr === "TOP_AND_BOTTOM") {
33440
35127
  floatBelow = Math.max(floatBelow, vo + outT + effH + outB);
33441
35128
  } else {
@@ -33466,19 +35153,19 @@ function reflowPara(p, doc, styles, areaW, startV, mode) {
33466
35153
  return { paraBottom: Math.max(textBottom, objBottom), spaceAfter: geom.spaceAfter };
33467
35154
  }
33468
35155
  function reflowTablesIn(p, doc, styles, mode, counter) {
33469
- for (const run of elements(p)) {
33470
- if (ln(run) !== "run") continue;
33471
- for (const obj of elements(run)) {
33472
- if (ln(obj) !== "tbl") continue;
33473
- for (const tr of elements(obj)) {
33474
- if (ln(tr) !== "tr") continue;
33475
- for (const tc2 of elements(tr)) {
33476
- if (ln(tc2) !== "tc") continue;
35156
+ for (const run of elements2(p)) {
35157
+ if (ln2(run) !== "run") continue;
35158
+ for (const obj of elements2(run)) {
35159
+ if (ln2(obj) !== "tbl") continue;
35160
+ for (const tr of elements2(obj)) {
35161
+ if (ln2(tr) !== "tr") continue;
35162
+ for (const tc2 of elements2(tr)) {
35163
+ if (ln2(tc2) !== "tc") continue;
33477
35164
  const csz = findChildByLocalName(tc2, "cellSz");
33478
35165
  const cm = findChildByLocalName(tc2, "cellMargin");
33479
- const cellW = num2(csz, "width");
33480
- const mL = cm ? num2(cm, "left", 141) : 141;
33481
- const mR = cm ? num2(cm, "right", 141) : 141;
35166
+ const cellW = num3(csz, "width");
35167
+ const mL = cm ? num3(cm, "left", 141) : 141;
35168
+ const mR = cm ? num3(cm, "right", 141) : 141;
33482
35169
  const areaW = Math.max(500, cellW - mL - mR);
33483
35170
  const sub = findChildByLocalName(tc2, "subList");
33484
35171
  if (sub) reflowBlockFlow(sub, doc, styles, areaW, mode, counter, 0);
@@ -33490,8 +35177,8 @@ function reflowTablesIn(p, doc, styles, mode, counter) {
33490
35177
  function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
33491
35178
  let cursorV = 0;
33492
35179
  let prevSpaceAfter = 0;
33493
- for (const p of elements(container)) {
33494
- if (ln(p) !== "p") continue;
35180
+ for (const p of elements2(container)) {
35181
+ if (ln2(p) !== "p") continue;
33495
35182
  reflowTablesIn(p, doc, styles, mode, counter);
33496
35183
  const g = styles.paraGeom.get(p.getAttribute("paraPrIDRef") ?? "");
33497
35184
  const startV = cursorV + prevSpaceAfter + (g?.spaceBefore ?? 0);
@@ -33500,10 +35187,10 @@ function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
33500
35187
  const lsa = findChildByLocalName(p, "linesegarray");
33501
35188
  if (lsa) {
33502
35189
  let bottom = Number.NEGATIVE_INFINITY;
33503
- for (const seg of elements(lsa)) {
33504
- if (ln(seg) !== "lineseg") continue;
33505
- const th = Math.max(num2(seg, "vertsize", 1e3), num2(seg, "textheight", 1e3));
33506
- bottom = Math.max(bottom, num2(seg, "vertpos") + th + num2(seg, "spacing"));
35190
+ for (const seg of elements2(lsa)) {
35191
+ if (ln2(seg) !== "lineseg") continue;
35192
+ const th = Math.max(num3(seg, "vertsize", 1e3), num3(seg, "textheight", 1e3));
35193
+ bottom = Math.max(bottom, num3(seg, "vertpos") + th + num3(seg, "spacing"));
33507
35194
  }
33508
35195
  if (Number.isFinite(bottom)) {
33509
35196
  cursorV = bottom;
@@ -33562,34 +35249,9 @@ var RegionCollector = class {
33562
35249
  };
33563
35250
 
33564
35251
  // src/render/svg-render.ts
33565
- function ln2(el) {
33566
- return (el.tagName || "").replace(/^[^:]+:/, "");
33567
- }
33568
- function elements2(el) {
33569
- const out = [];
33570
- const children = el.childNodes;
33571
- if (!children) return out;
33572
- for (let i = 0; i < children.length; i++) {
33573
- if (children[i].nodeType === 1) out.push(children[i]);
33574
- }
33575
- return out;
33576
- }
33577
- function num3(el, attr, fallback = 0) {
33578
- return el ? toInt32(el.getAttribute(attr) ?? void 0, fallback) : fallback;
33579
- }
33580
- function findFirst(el, name, depth = 0) {
33581
- if (depth > 64) return null;
33582
- for (const ch of elements2(el)) {
33583
- if (ln2(ch) === name) return ch;
33584
- const found = findFirst(ch, name, depth + 1);
33585
- if (found) return found;
33586
- }
33587
- return null;
33588
- }
33589
- function escapeXml4(s) {
35252
+ function escapeXml7(s) {
33590
35253
  return s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
33591
35254
  }
33592
- var OBJ_TAGS = /* @__PURE__ */ new Set(["tbl", "pic", "container", "equation", "rect", "ellipse", "polygon", "curv", "line", "arc", "ole", "textart"]);
33593
35255
  var pt = (u) => String(Math.round(u) / 100);
33594
35256
  var ptNum = (u) => Math.round(u) / 100;
33595
35257
  function pageNo(ctx) {
@@ -33613,84 +35275,9 @@ function warnOnce(ctx, key, msg2) {
33613
35275
  ctx.warned.add(key);
33614
35276
  ctx.warnings.push(msg2);
33615
35277
  }
33616
- var CHAR_CTRL_1SLOT = /* @__PURE__ */ new Set(["lineBreak", "hyphen", "nbSpace", "fwSpace"]);
33617
- function pushFillers(chars, n, prId) {
33618
- for (let i = 0; i < n; i++) chars.push({ ch: "", prId });
33619
- }
33620
- function pushTextSlots(t, chars, prId, depth) {
33621
- if (depth > 32) return;
33622
- const kids = t.childNodes;
33623
- if (!kids) return;
33624
- for (let i = 0; i < kids.length; i++) {
33625
- const c = kids[i];
33626
- if (c.nodeType === 3 || c.nodeType === 4) {
33627
- for (const cp of c.textContent ?? "") {
33628
- chars.push({ ch: cp, prId });
33629
- if (cp.length === 2) chars.push({ ch: "", prId });
33630
- }
33631
- } else if (c.nodeType === 1) {
33632
- const el = c;
33633
- const tag = ln2(el);
33634
- if (tag === "tab") {
33635
- pushFillers(chars, 8, prId);
33636
- } else if (CHAR_CTRL_1SLOT.has(tag)) {
33637
- chars.push({ ch: tag === "nbSpace" || tag === "fwSpace" ? " " : "", prId });
33638
- } else {
33639
- pushTextSlots(el, chars, prId, depth + 1);
33640
- }
33641
- }
33642
- }
33643
- }
33644
- function buildPara(p) {
33645
- const chars = [];
33646
- const objs = [];
33647
- let segs = [];
33648
- for (const runEl of elements2(p)) {
33649
- const tag = ln2(runEl);
33650
- if (tag === "run") {
33651
- const prId = runEl.getAttribute("charPrIDRef");
33652
- for (const ch of elements2(runEl)) {
33653
- const cn = ln2(ch);
33654
- if (cn === "t") {
33655
- pushTextSlots(ch, chars, prId, 0);
33656
- } else if (OBJ_TAGS.has(cn)) {
33657
- const sz = findChildByLocalName(ch, "sz");
33658
- const pos = findChildByLocalName(ch, "pos");
33659
- const w = num3(sz, "width") || num3(findChildByLocalName(ch, "curSz"), "width") || num3(findChildByLocalName(ch, "orgSz"), "width");
33660
- const h = num3(sz, "height") || num3(findChildByLocalName(ch, "curSz"), "height") || num3(findChildByLocalName(ch, "orgSz"), "height");
33661
- const inline = pos?.getAttribute("treatAsChar") === "1";
33662
- const om = inline && cn === "tbl" ? findChildByLocalName(ch, "outMargin") : null;
33663
- objs.push({
33664
- el: ch,
33665
- tag: cn,
33666
- index: chars.length,
33667
- inline,
33668
- width: w,
33669
- height: h,
33670
- omL: num3(om, "left"),
33671
- omR: num3(om, "right")
33672
- });
33673
- pushFillers(chars, 8, prId);
33674
- } else {
33675
- pushFillers(chars, 8, prId);
33676
- }
33677
- }
33678
- } else if (tag === "linesegarray") {
33679
- segs = elements2(runEl).filter((s) => ln2(s) === "lineseg").map((s) => ({
33680
- textpos: num3(s, "textpos"),
33681
- vertpos: num3(s, "vertpos"),
33682
- horzpos: num3(s, "horzpos"),
33683
- horzsize: num3(s, "horzsize"),
33684
- textheight: num3(s, "textheight", 1e3),
33685
- baseline: num3(s, "baseline", 850)
33686
- }));
33687
- }
33688
- }
33689
- return { chars, segs, objs, paraPrId: p.getAttribute("paraPrIDRef") };
33690
- }
33691
35278
  function charW(c, styles) {
33692
35279
  const st = (c.prId != null ? styles.charPr.get(c.prId) : void 0) ?? DEFAULT_CHAR;
33693
- return measureTextWidth(c.ch, st.height, st.ratio, { spacingPct: st.spacing });
35280
+ return measureTextWidth(c.ch, st.height, st.ratio, { spacingPct: st.spacing, faceClass: faceClassOf(st.face) });
33694
35281
  }
33695
35282
  function lineNaturalWidth(m, styles, start, end) {
33696
35283
  let text = 0;
@@ -33701,12 +35288,35 @@ function lineNaturalWidth(m, styles, start, end) {
33701
35288
  }
33702
35289
  function planLines(m, styles) {
33703
35290
  const align = (m.paraPrId != null ? styles.paraAlign.get(m.paraPrId) : void 0) ?? "JUSTIFY";
35291
+ const geom = m.paraPrId != null ? styles.paraGeom.get(m.paraPrId) : void 0;
35292
+ const hang = geom && geom.marginIntent < 0 ? -geom.marginIntent : 0;
33704
35293
  const plans = [];
33705
35294
  for (let i = 0; i < m.segs.length; i++) {
33706
35295
  const seg = m.segs[i];
33707
35296
  const start = seg.textpos;
33708
35297
  const end = i + 1 < m.segs.length ? m.segs[i + 1].textpos : Math.max(m.chars.length, start);
33709
- const nat = lineNaturalWidth(m, styles, start, end);
35298
+ const tabs = /* @__PURE__ */ new Map();
35299
+ let x = i === 0 ? Math.max(geom?.marginIntent ?? 0, 0) : hang;
35300
+ let fixedTo = start;
35301
+ let fixedW = 0;
35302
+ for (let k = start; k < end && k < m.chars.length; k++) {
35303
+ const c = m.chars[k];
35304
+ if (!c.tab) {
35305
+ x += charW(c, styles);
35306
+ continue;
35307
+ }
35308
+ const adv = tabAdvance(x, i === 0, geom, c.tabW);
35309
+ tabs.set(k, adv);
35310
+ x += adv;
35311
+ fixedTo = k + 1;
35312
+ }
35313
+ if (fixedTo > start) fixedW = lineNaturalWidth(m, styles, start, fixedTo).text + [...tabs.values()].reduce((a, b) => a + b, 0);
35314
+ let textEnd = Math.min(end, m.chars.length);
35315
+ while (textEnd > fixedTo && m.chars[textEnd - 1].ch === " ") textEnd--;
35316
+ const nat = lineNaturalWidth(m, styles, fixedTo, textEnd);
35317
+ for (const o of m.objs) if (o.inline && o.index >= textEnd && o.index < end) nat.obj += o.omL + o.width + o.omR;
35318
+ nat.obj += fixedW;
35319
+ for (const o of m.objs) if (o.inline && o.index >= start && o.index < fixedTo) nat.obj += o.omL + o.width + o.omR;
33710
35320
  const isLast = i === m.segs.length - 1;
33711
35321
  let xoff = 0;
33712
35322
  let scale2 = 1;
@@ -33720,13 +35330,15 @@ function planLines(m, styles) {
33720
35330
  }
33721
35331
  if (!Number.isFinite(scale2) || scale2 <= 0) scale2 = 1;
33722
35332
  scale2 = Math.min(4, Math.max(0.25, scale2));
33723
- plans.push({ seg, xoff, scale: scale2, start, end });
35333
+ plans.push({ seg, xoff, scale: scale2, start, end, tabs, fixedTo, textEnd });
33724
35334
  }
33725
35335
  return plans;
33726
35336
  }
33727
35337
  function advanceTo(m, styles, plan, upto) {
33728
35338
  let x = 0;
33729
- for (let i = plan.start; i < upto && i < m.chars.length; i++) x += charW(m.chars[i], styles) * plan.scale;
35339
+ for (let i = plan.start; i < upto && i < m.chars.length; i++) {
35340
+ x += m.chars[i].tab ? plan.tabs.get(i) ?? 0 : charW(m.chars[i], styles) * (i < plan.fixedTo ? 1 : plan.scale);
35341
+ }
33730
35342
  for (const o of m.objs) if (o.inline && o.index >= plan.start && o.index < upto) x += o.omL + o.width + o.omR;
33731
35343
  return x;
33732
35344
  }
@@ -33766,15 +35378,21 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
33766
35378
  let cursor = ox + seg.horzpos + plan.xoff;
33767
35379
  const y = oy + seg.vertpos + seg.baseline;
33768
35380
  while (i < plan.end && i < m.chars.length) {
35381
+ if (i >= plan.textEnd && m.chars[i].ch === " ") {
35382
+ i++;
35383
+ continue;
35384
+ }
33769
35385
  if (m.chars[i].ch === "") {
35386
+ if (m.chars[i].tab) cursor += plan.tabs.get(i) ?? 0;
33770
35387
  for (const o of m.objs) if (o.inline && o.index === i) cursor += o.omL + o.width + o.omR;
33771
35388
  i++;
33772
35389
  continue;
33773
35390
  }
35391
+ const scale2 = i < plan.fixedTo ? 1 : plan.scale;
33774
35392
  const prId = m.chars[i].prId;
33775
35393
  let j = i;
33776
35394
  let piece = "";
33777
- while (j < plan.end && j < m.chars.length && m.chars[j].prId === prId && m.chars[j].ch !== "") {
35395
+ while (j < plan.end && j < m.chars.length && m.chars[j].prId === prId && m.chars[j].ch !== "" && !(j >= plan.textEnd && m.chars[j].ch === " ")) {
33778
35396
  piece += m.chars[j].ch;
33779
35397
  j++;
33780
35398
  }
@@ -33791,21 +35409,21 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
33791
35409
  }
33792
35410
  const st = (prId != null ? ctx.styles.charPr.get(prId) : void 0) ?? DEFAULT_CHAR;
33793
35411
  const renderSeg = (text, cx, hit) => {
33794
- const sw = measureTextWidth(text, st.height, st.ratio, { spacingPct: st.spacing }) * plan.scale;
35412
+ const sw = measureTextWidth(text, st.height, st.ratio, { spacingPct: st.spacing, faceClass: faceClassOf(st.face) }) * scale2;
33795
35413
  if (hit) {
33796
35414
  emit(ctx, `<rect x="${pt(cx)}" y="${pt(oy + seg.vertpos)}" width="${pt(sw)}" height="${pt(seg.textheight)}" fill="#ffd54f" fill-opacity="0.45"/>`);
33797
35415
  }
33798
35416
  if (text.trim().length > 0) {
33799
35417
  const attrs = [`x="${pt(cx)}"`, `y="${pt(y)}"`, `font-size="${pt(st.height)}"`];
33800
- if (st.fontFamily) attrs.push(`font-family="${escapeXml4(st.fontFamily)}"`);
35418
+ if (st.fontFamily) attrs.push(`font-family="${escapeXml7(st.fontFamily)}"`);
33801
35419
  if ([...text].length > 1 && sw > 50) {
33802
- attrs.push(`textLength="${pt(sw)}"`, `lengthAdjust="${plan.scale < 1 ? "spacingAndGlyphs" : "spacing"}"`);
35420
+ attrs.push(`textLength="${pt(sw)}"`, `lengthAdjust="${scale2 < 1 ? "spacingAndGlyphs" : "spacing"}"`);
33803
35421
  }
33804
35422
  if (st.bold) attrs.push(`font-weight="bold"`);
33805
35423
  if (st.italic) attrs.push(`font-style="italic"`);
33806
35424
  if (st.underline) attrs.push(`text-decoration="underline"`);
33807
- if (st.color) attrs.push(`fill="${escapeXml4(st.color)}"`);
33808
- emit(ctx, `<text ${attrs.join(" ")}>${escapeXml4(text)}</text>`);
35425
+ if (st.color) attrs.push(`fill="${escapeXml7(st.color)}"`);
35426
+ emit(ctx, `<text ${attrs.join(" ")}>${escapeXml7(text)}</text>`);
33809
35427
  ctx.stats.texts++;
33810
35428
  }
33811
35429
  return sw;
@@ -33868,11 +35486,11 @@ function anchorObject(o, ox, oy, baseV, areaW, ctx) {
33868
35486
  const { PW, PH, ML, MT, BODY_W, BODY_H } = ctx.geom;
33869
35487
  const pos = findChildByLocalName(o.el, "pos");
33870
35488
  const om = findChildByLocalName(o.el, "outMargin");
33871
- const omT = num3(om, "top"), omB = num3(om, "bottom");
35489
+ const omT = num2(om, "top"), omB = num2(om, "bottom");
33872
35490
  const w = o.width, h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el, ctx.extentMemo)) : o.height;
33873
35491
  if (!pos) return { x: ox, y: oy + baseV };
33874
- const vo = num3(pos, "vertOffset");
33875
- const ho = num3(pos, "horzOffset");
35492
+ const vo = num2(pos, "vertOffset");
35493
+ const ho = num2(pos, "horzOffset");
33876
35494
  const vrel = pos.getAttribute("vertRelTo") ?? "PARA";
33877
35495
  const hrel = pos.getAttribute("horzRelTo") ?? "PARA";
33878
35496
  const va = pos.getAttribute("vertAlign") ?? "TOP";
@@ -33904,13 +35522,13 @@ function drawObject(o, x, y, baseV, areaW, ctx, depth) {
33904
35522
  if (o.tag === "tbl") drawTable(o.el, x, y, ctx, depth + 1);
33905
35523
  else if (o.tag === "pic") drawPic(o.el, x, y, ctx);
33906
35524
  else if (o.tag === "container") {
33907
- for (const ch of elements2(o.el)) {
33908
- const tag = ln2(ch);
35525
+ for (const ch of elements(o.el)) {
35526
+ const tag = ln(ch);
33909
35527
  if (!OBJ_TAGS.has(tag)) continue;
33910
35528
  const sz = findChildByLocalName(ch, "sz");
33911
35529
  const off = findChildByLocalName(ch, "offset");
33912
- const sub = { el: ch, tag, index: 0, inline: true, width: num3(sz, "width"), height: num3(sz, "height"), omL: 0, omR: 0 };
33913
- drawObject(sub, x + num3(off, "x"), y + num3(off, "y"), baseV, areaW, ctx, depth + 1);
35530
+ const sub = { el: ch, tag, index: 0, inline: true, width: num2(sz, "width"), height: num2(sz, "height"), omL: 0, omR: 0 };
35531
+ drawObject(sub, x + num2(off, "x"), y + num2(off, "y"), baseV, areaW, ctx, depth + 1);
33914
35532
  }
33915
35533
  } else if (o.tag === "equation") {
33916
35534
  warnOnce(ctx, "equation", "\uC218\uC2DD \uAC1C\uCCB4\uB294 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 \uC0DD\uB7B5");
@@ -33928,23 +35546,23 @@ function drawShape(o, x, y, ctx, depth) {
33928
35546
  const el = o.el;
33929
35547
  const orgSz = findChildByLocalName(el, "orgSz");
33930
35548
  const curSz = findChildByLocalName(el, "curSz");
33931
- const ow = num3(orgSz, "width"), oh = num3(orgSz, "height");
33932
- const w = num3(curSz, "width") || ow || o.width;
33933
- const h = num3(curSz, "height") || oh || o.height;
35549
+ const ow = num2(orgSz, "width"), oh = num2(orgSz, "height");
35550
+ const w = num2(curSz, "width") || ow || o.width;
35551
+ const h = num2(curSz, "height") || oh || o.height;
33934
35552
  const sx = ow > 0 ? w / ow : 1;
33935
35553
  const sy = oh > 0 ? h / oh : 1;
33936
35554
  const lineShape = findChildByLocalName(el, "lineShape");
33937
35555
  const lstyle = lineShape?.getAttribute("style") ?? "SOLID";
33938
35556
  const strokeCol = lineShape?.getAttribute("color") || "#000000";
33939
35557
  const hasStroke = lstyle !== "NONE";
33940
- const strokeW = hasStroke ? shapeStrokePt(lineShape ? num3(lineShape, "width") : 33) : 0;
35558
+ const strokeW = hasStroke ? shapeStrokePt(lineShape ? num2(lineShape, "width") : 33) : 0;
33941
35559
  const dash = /DASH|DOT/.test(lstyle) ? ` stroke-dasharray="${lstyle.includes("DOT") ? "1,1.5" : "3,1.5"}"` : "";
33942
- const strokeAttr = hasStroke ? ` stroke="${escapeXml4(strokeCol)}" stroke-width="${strokeW.toFixed(2)}"${dash}` : "";
35560
+ const strokeAttr = hasStroke ? ` stroke="${escapeXml7(strokeCol)}" stroke-width="${strokeW.toFixed(2)}"${dash}` : "";
33943
35561
  const fillBrush = findChildByLocalName(el, "fillBrush");
33944
35562
  const winBrush = fillBrush ? findChildByLocalName(fillBrush, "winBrush") : null;
33945
35563
  const face = winBrush?.getAttribute("faceColor");
33946
35564
  const fill = face && face.toLowerCase() !== "none" ? face : "none";
33947
- const fillAttr = ` fill="${fill === "none" ? "none" : escapeXml4(fill)}"`;
35565
+ const fillAttr = ` fill="${fill === "none" ? "none" : escapeXml7(fill)}"`;
33948
35566
  ctx.stats.shapes++;
33949
35567
  const shapeId = ctx.regions.add("shape", bboxOf(ctx, x, y, w, h), { parentId: parentId(ctx) });
33950
35568
  emit(ctx, regionOpenTag(shapeId, "shape", pageNo(ctx)));
@@ -33955,103 +35573,27 @@ function drawShape(o, x, y, ctx, depth) {
33955
35573
  emit(ctx, `<ellipse cx="${pt(x + w / 2)}" cy="${pt(y + h / 2)}" rx="${pt(w / 2)}" ry="${pt(h / 2)}"${fillAttr}${strokeAttr}/>`);
33956
35574
  } else if (o.tag === "line") {
33957
35575
  const s = findChildByLocalName(el, "startPt"), e = findChildByLocalName(el, "endPt");
33958
- const x1 = x + num3(s, "x") * sx, y1 = y + num3(s, "y") * sy;
33959
- const x2 = x + num3(e, "x") * sx, y2 = y + num3(e, "y") * sy;
33960
- emit(ctx, `<line x1="${pt(x1)}" y1="${pt(y1)}" x2="${pt(x2)}" y2="${pt(y2)}" stroke="${escapeXml4(strokeCol)}" stroke-width="${(strokeW || 0.3).toFixed(2)}"${dash}/>`);
35576
+ const x1 = x + num2(s, "x") * sx, y1 = y + num2(s, "y") * sy;
35577
+ const x2 = x + num2(e, "x") * sx, y2 = y + num2(e, "y") * sy;
35578
+ emit(ctx, `<line x1="${pt(x1)}" y1="${pt(y1)}" x2="${pt(x2)}" y2="${pt(y2)}" stroke="${escapeXml7(strokeCol)}" stroke-width="${(strokeW || 0.3).toFixed(2)}"${dash}/>`);
33961
35579
  } else if (o.tag === "polygon" || o.tag === "curv") {
33962
35580
  const pts = [];
33963
- for (const c of elements2(el)) if (ln2(c) === "pt") pts.push(`${pt(x + num3(c, "x") * sx)},${pt(y + num3(c, "y") * sy)}`);
35581
+ for (const c of elements(el)) if (ln(c) === "pt") pts.push(`${pt(x + num2(c, "x") * sx)},${pt(y + num2(c, "y") * sy)}`);
33964
35582
  if (pts.length >= 2) emit(ctx, `<polygon points="${pts.join(" ")}"${fillAttr}${strokeAttr}/>`);
33965
35583
  } else if (o.tag === "arc") {
33966
- emit(ctx, `<ellipse cx="${pt(x + w / 2)}" cy="${pt(y + h / 2)}" rx="${pt(w / 2)}" ry="${pt(h / 2)}" fill="none"${strokeAttr || ` stroke="${escapeXml4(strokeCol)}" stroke-width="0.3"`}/>`);
35584
+ emit(ctx, `<ellipse cx="${pt(x + w / 2)}" cy="${pt(y + h / 2)}" rx="${pt(w / 2)}" ry="${pt(h / 2)}" fill="none"${strokeAttr || ` stroke="${escapeXml7(strokeCol)}" stroke-width="0.3"`}/>`);
33967
35585
  }
33968
35586
  const dt = findChildByLocalName(el, "drawText");
33969
35587
  const sub = dt ? findChildByLocalName(dt, "subList") : null;
33970
35588
  if (sub) {
33971
- for (const p of elements2(sub)) if (ln2(p) === "p") drawPara(p, x, y, w, ctx, depth + 1);
35589
+ for (const p of elements(sub)) if (ln(p) === "p") drawPara(p, x, y, w, ctx, depth + 1);
33972
35590
  }
33973
35591
  ctx.parentStack.pop();
33974
35592
  emit(ctx, "</g>");
33975
35593
  }
33976
- function cellContentExtent(cell2, memo) {
33977
- if (!cell2.sub) return 0;
33978
- const hit = memo?.cell.get(cell2.el);
33979
- if (hit !== void 0) return hit;
33980
- let ext = 0;
33981
- for (const p of elements2(cell2.sub)) {
33982
- if (ln2(p) !== "p") continue;
33983
- const m = buildPara(p);
33984
- for (const s of m.segs) ext = Math.max(ext, s.vertpos + s.textheight);
33985
- const baseV = m.segs[0]?.vertpos ?? 0;
33986
- for (const o of m.objs) {
33987
- if (o.inline) {
33988
- const h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el, memo)) : o.height;
33989
- ext = Math.max(ext, baseV + h);
33990
- continue;
33991
- }
33992
- const pos = findChildByLocalName(o.el, "pos");
33993
- if ((pos?.getAttribute("vertRelTo") ?? "PARA") !== "PARA") continue;
33994
- const om = findChildByLocalName(o.el, "outMargin");
33995
- const pushed = baseV - (num3(om, "top") + o.height + num3(om, "bottom"));
33996
- const anchor = pushed >= -100 ? pushed : baseV;
33997
- ext = Math.max(ext, anchor + num3(om, "top") + num3(pos, "vertOffset") + o.height);
33998
- }
33999
- }
34000
- memo?.cell.set(cell2.el, ext);
34001
- return ext;
34002
- }
34003
35594
  function edgeLine(x1, y1, x2, y2, e) {
34004
35595
  const dash = /DASH|DOT/.test(e.type) ? ` stroke-dasharray="${e.type.includes("DOT") ? "1,1.5" : "3,1.5"}"` : "";
34005
- return `<line x1="${pt(x1)}" y1="${pt(y1)}" x2="${pt(x2)}" y2="${pt(y2)}" stroke="${escapeXml4(e.color)}" stroke-width="${e.widthPt.toFixed(2)}"${dash}/>`;
34006
- }
34007
- function collectCells(tbl2) {
34008
- const inMargin = findChildByLocalName(tbl2, "inMargin");
34009
- const defL = num3(inMargin, "left", 141), defR = num3(inMargin, "right", 141);
34010
- const defT = num3(inMargin, "top", 141), defB = num3(inMargin, "bottom", 141);
34011
- const cells = [];
34012
- for (const tr of elements2(tbl2)) {
34013
- if (ln2(tr) !== "tr") continue;
34014
- for (const tc2 of elements2(tr)) {
34015
- if (ln2(tc2) !== "tc") continue;
34016
- const addr = findChildByLocalName(tc2, "cellAddr");
34017
- const span = findChildByLocalName(tc2, "cellSpan");
34018
- const csz = findChildByLocalName(tc2, "cellSz");
34019
- const cm = findChildByLocalName(tc2, "cellMargin");
34020
- if (!addr || !csz) continue;
34021
- cells.push({
34022
- el: tc2,
34023
- // 음수 주소(uint32 역변환·손상 입력) 방어 — colX/rowY 인덱스 이탈로 NaN 좌표 방지
34024
- ca: Math.max(0, num3(addr, "colAddr")),
34025
- ra: Math.max(0, num3(addr, "rowAddr")),
34026
- cs: Math.max(1, num3(span, "colSpan", 1)),
34027
- rs: Math.max(1, num3(span, "rowSpan", 1)),
34028
- w: num3(csz, "width"),
34029
- h: num3(csz, "height"),
34030
- bfId: tc2.getAttribute("borderFillIDRef"),
34031
- sub: findChildByLocalName(tc2, "subList"),
34032
- marginL: cm ? num3(cm, "left", defL) : defL,
34033
- marginR: cm ? num3(cm, "right", defR) : defR,
34034
- marginT: cm ? num3(cm, "top", defT) : defT,
34035
- marginB: cm ? num3(cm, "bottom", defB) : defB
34036
- });
34037
- }
34038
- }
34039
- return cells;
34040
- }
34041
- function measureTableHeight(tbl2, memo) {
34042
- const hit = memo?.table.get(tbl2);
34043
- if (hit !== void 0) return hit;
34044
- const cells = collectCells(tbl2);
34045
- if (cells.length === 0 || cells.length > 4096) return 0;
34046
- const nRows = Math.max(...cells.map((c) => c.ra + c.rs));
34047
- const rowH = solveRowHeights(
34048
- cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c, memo) : void 0 })),
34049
- nRows
34050
- );
34051
- let sum = 0;
34052
- for (const h of rowH) sum += h;
34053
- memo?.table.set(tbl2, sum);
34054
- return sum;
35596
+ return `<line x1="${pt(x1)}" y1="${pt(y1)}" x2="${pt(x2)}" y2="${pt(y2)}" stroke="${escapeXml7(e.color)}" stroke-width="${e.widthPt.toFixed(2)}"${dash}/>`;
34055
35597
  }
34056
35598
  function drawTable(tbl2, tx, ty, ctx, depth) {
34057
35599
  if (depth > 16) {
@@ -34065,7 +35607,7 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
34065
35607
  const nCols = Math.max(...cells.map((c) => c.ca + c.cs));
34066
35608
  const nRows = Math.max(...cells.map((c) => c.ra + c.rs));
34067
35609
  const colCons = cells.map((c) => ({ a: c.ca, b: c.ca + c.cs, size: c.w }));
34068
- const colX = solveBoundaries(colCons, nCols, num3(tblSz, "width") || void 0);
35610
+ const colX = solveBoundaries(colCons, nCols, num2(tblSz, "width") || void 0);
34069
35611
  const rowH = solveRowHeights(
34070
35612
  cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c, ctx.extentMemo) : void 0 })),
34071
35613
  nRows
@@ -34086,7 +35628,7 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
34086
35628
  }));
34087
35629
  for (const g of geom) {
34088
35630
  const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : void 0;
34089
- if (bf?.fill) emit(ctx, `<rect x="${pt(g.x)}" y="${pt(g.y)}" width="${pt(g.w)}" height="${pt(g.h)}" fill="${escapeXml4(bf.fill)}"/>`);
35631
+ if (bf?.fill) emit(ctx, `<rect x="${pt(g.x)}" y="${pt(g.y)}" width="${pt(g.w)}" height="${pt(g.h)}" fill="${escapeXml7(bf.fill)}"/>`);
34090
35632
  }
34091
35633
  for (const g of geom) {
34092
35634
  const { c } = g;
@@ -34097,8 +35639,8 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
34097
35639
  let yoff = 0;
34098
35640
  if (va === "CENTER") yoff = Math.max(0, (innerH - extent) / 2);
34099
35641
  else if (va === "BOTTOM") yoff = Math.max(0, innerH - extent);
34100
- for (const p of elements2(c.sub)) {
34101
- if (ln2(p) !== "p") continue;
35642
+ for (const p of elements(c.sub)) {
35643
+ if (ln(p) !== "p") continue;
34102
35644
  drawPara(p, g.x + c.marginL, g.y + c.marginT + yoff, g.w - c.marginL - c.marginR, ctx, depth + 1);
34103
35645
  }
34104
35646
  }
@@ -34124,7 +35666,7 @@ function imageSymbol(loaded, ctx) {
34124
35666
  }
34125
35667
  function drawPic(pic, x, y, ctx) {
34126
35668
  const sz = findChildByLocalName(pic, "sz");
34127
- const w = num3(sz, "width", 5669), h = num3(sz, "height", 5669);
35669
+ const w = num2(sz, "width", 5669), h = num2(sz, "height", 5669);
34128
35670
  const img = findFirst(pic, "img");
34129
35671
  const ref = img?.getAttribute("binaryItemIDRef");
34130
35672
  const loaded = ref != null ? ctx.images.get(ref) : void 0;
@@ -34140,11 +35682,11 @@ function drawPic(pic, x, y, ctx) {
34140
35682
  const clip = findChildByLocalName(pic, "imgClip");
34141
35683
  const imgDim = findChildByLocalName(pic, "imgDim");
34142
35684
  const orgSz = findChildByLocalName(pic, "orgSz");
34143
- const dimW = num3(imgDim, "dimwidth"), dimH = num3(imgDim, "dimheight");
34144
- const refW = dimW > 0 ? dimW : num3(orgSz, "width");
34145
- const refH = dimH > 0 ? dimH : num3(orgSz, "height");
34146
- const cl = num3(clip, "left"), ct = num3(clip, "top");
34147
- const cr = num3(clip, "right", refW), cb = num3(clip, "bottom", refH);
35685
+ const dimW = num2(imgDim, "dimwidth"), dimH = num2(imgDim, "dimheight");
35686
+ const refW = dimW > 0 ? dimW : num2(orgSz, "width");
35687
+ const refH = dimH > 0 ? dimH : num2(orgSz, "height");
35688
+ const cl = num2(clip, "left"), ct = num2(clip, "top");
35689
+ const cr = num2(clip, "right", refW), cb = num2(clip, "bottom", refH);
34148
35690
  const cropped = refW > 0 && refH > 0 && clip != null && (cl > 0 || ct > 0 || cr < refW || cb < refH) && cr > cl && cb > ct;
34149
35691
  const symId = imageSymbol(loaded, ctx);
34150
35692
  if (cropped) {
@@ -34169,23 +35711,23 @@ function sniffMime(name, bytes) {
34169
35711
  function readSectionGeom(root) {
34170
35712
  const pagePr = findFirst(root, "pagePr");
34171
35713
  const margin = pagePr ? findChildByLocalName(pagePr, "margin") : null;
34172
- let PW = num3(pagePr, "width", 59528), PH = num3(pagePr, "height", 84188);
35714
+ let PW = num2(pagePr, "width", 59528), PH = num2(pagePr, "height", 84188);
34173
35715
  if (pagePr?.getAttribute("landscape") === "NARROWLY" && PW < PH) {
34174
35716
  const t = PW;
34175
35717
  PW = PH;
34176
35718
  PH = t;
34177
35719
  }
34178
- const ML = num3(margin, "left", 8504);
34179
- const MT = num3(margin, "top", 5668) + num3(margin, "header", 0);
34180
- const BODY_H = PH - MT - num3(margin, "bottom", 4252) - num3(margin, "footer", 0);
34181
- const BODY_W = PW - ML - num3(margin, "right", 8504);
35720
+ const ML = num2(margin, "left", 8504);
35721
+ const MT = num2(margin, "top", 5668) + num2(margin, "header", 0);
35722
+ const BODY_H = PH - MT - num2(margin, "bottom", 4252) - num2(margin, "footer", 0);
35723
+ const BODY_W = PW - ML - num2(margin, "right", 8504);
34182
35724
  return { PW, PH, ML, MT, BODY_W, BODY_H };
34183
35725
  }
34184
35726
  function renderSectionToPages(root, geom, ctxBase, doReflow, reflowMode) {
34185
35727
  const { PW, PH, ML, MT, BODY_W, BODY_H } = geom;
34186
35728
  if (doReflow) reflowSection(root, ctxBase.styles, { BODY_W, BODY_H }, reflowMode);
34187
35729
  const colPr = findFirst(root, "colPr");
34188
- const multiCol = num3(colPr, "colCount", 1) > 1;
35730
+ const multiCol = num2(colPr, "colCount", 1) > 1;
34189
35731
  const paraSegPages = /* @__PURE__ */ new Map();
34190
35732
  let nPages = 1;
34191
35733
  let maxTopV = 0;
@@ -34193,20 +35735,20 @@ function renderSectionToPages(root, geom, ctxBase, doReflow, reflowMode) {
34193
35735
  let prevV = Number.NEGATIVE_INFINITY;
34194
35736
  let prevH = Number.NEGATIVE_INFINITY;
34195
35737
  let cur = 0;
34196
- for (const p of elements2(root)) {
34197
- if (ln2(p) !== "p") continue;
35738
+ for (const p of elements(root)) {
35739
+ if (ln(p) !== "p") continue;
34198
35740
  const lsa = findChildByLocalName(p, "linesegarray");
34199
- const segEls = lsa ? elements2(lsa).filter((s) => ln2(s) === "lineseg") : [];
35741
+ const segEls = lsa ? elements(lsa).filter((s) => ln(s) === "lineseg") : [];
34200
35742
  const pagesOf = [];
34201
35743
  let paraFirst = true;
34202
35744
  for (const s of segEls) {
34203
- const v = num3(s, "vertpos");
34204
- const h = num3(s, "horzpos");
35745
+ const v = num2(s, "vertpos");
35746
+ const h = num2(s, "horzpos");
34205
35747
  const brk = v < prevV ? !multiCol || h <= prevH : paraFirst && v === prevV && h <= prevH;
34206
35748
  if (brk) cur++;
34207
35749
  paraFirst = false;
34208
35750
  pagesOf.push(cur);
34209
- maxTopV = Math.max(maxTopV, v + num3(s, "textheight", 1e3));
35751
+ maxTopV = Math.max(maxTopV, v + num2(s, "textheight", 1e3));
34210
35752
  prevV = v;
34211
35753
  prevH = h;
34212
35754
  }
@@ -34222,8 +35764,8 @@ function renderSectionToPages(root, geom, ctxBase, doReflow, reflowMode) {
34222
35764
  geom,
34223
35765
  pageH
34224
35766
  };
34225
- for (const p of elements2(root)) {
34226
- if (ln2(p) !== "p") continue;
35767
+ for (const p of elements(root)) {
35768
+ if (ln(p) !== "p") continue;
34227
35769
  drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p));
34228
35770
  }
34229
35771
  return { pages: ctx.pages, pageH };
@@ -34635,7 +36177,7 @@ function paragraphXml(records, i, j, level, ctx, depth) {
34635
36177
  let textBuf = "";
34636
36178
  const flushText = () => {
34637
36179
  if (textBuf) {
34638
- run.push(`<hp:t>${escapeXml4(textBuf)}</hp:t>`);
36180
+ run.push(`<hp:t>${escapeXml7(textBuf)}</hp:t>`);
34639
36181
  textBuf = "";
34640
36182
  }
34641
36183
  };
@@ -34954,7 +36496,7 @@ import { readFile } from "fs/promises";
34954
36496
 
34955
36497
  // src/render/html.ts
34956
36498
  function renderSceneToHtml(scene, pageSvgs, options = {}) {
34957
- const title = escapeXml4(options.title ?? "kordoc");
36499
+ const title = escapeXml7(options.title ?? "kordoc");
34958
36500
  const first = scene.pages.find((p) => pageSvgs.has(p.page)) ?? scene.pages[0];
34959
36501
  const pageSize = first ? `${first.width}pt ${first.height}pt` : "auto";
34960
36502
  const pages = [...pageSvgs.keys()].sort((a, b) => a - b).map((n) => {
@@ -35280,6 +36822,9 @@ async function dispatch(format, buffer, opts) {
35280
36822
  const zipFormat = await detectZipFormat(buffer);
35281
36823
  if (zipFormat === "xlsx") return parseXlsx(buffer, opts);
35282
36824
  if (zipFormat === "docx") return parseDocx(buffer, opts);
36825
+ if (zipFormat === "pptx") {
36826
+ return { success: false, fileType: "pptx", error: "PPTX \uD30C\uC77C\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4.", code: "UNSUPPORTED_FORMAT" };
36827
+ }
35283
36828
  return parseHwpx(buffer, opts);
35284
36829
  }
35285
36830
  case "hwp": {
@@ -35301,7 +36846,7 @@ async function dispatch(format, buffer, opts) {
35301
36846
  }
35302
36847
  async function parseImage(buffer, options) {
35303
36848
  try {
35304
- const { parseImageDocument } = await import("./image-ocr-5WBAIBPQ.js");
36849
+ const { parseImageDocument } = await import("./image-ocr-FLIL7MGN.js");
35305
36850
  const { blocks, warnings } = await parseImageDocument(buffer, options);
35306
36851
  return {
35307
36852
  success: true,
@@ -35360,7 +36905,7 @@ async function parseHwp(buffer, options) {
35360
36905
  async function parsePdf(buffer, options) {
35361
36906
  let parsePdfDocument;
35362
36907
  try {
35363
- const mod = await import("./parser-66NMJUOG.js");
36908
+ const mod = await import("./parser-A4TMPJAM.js");
35364
36909
  parsePdfDocument = mod.parsePdfDocument;
35365
36910
  } catch {
35366
36911
  return {