kordoc 4.7.1 → 4.7.3

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 (129) hide show
  1. package/README.md +19 -1
  2. package/dist/{-O42BMYNX.js → -WYNJOQ6P.js} +9 -9
  3. package/dist/{chunk-PLEGO474.js → chunk-3NKSSL44.js} +3 -3
  4. package/dist/{chunk-UZB3UGC5.js → chunk-3P5YCJQE.js} +2 -2
  5. package/dist/{chunk-IZEUWEEY.js → chunk-46F5RMU7.js} +3 -11
  6. package/dist/chunk-46F5RMU7.js.map +1 -0
  7. package/dist/{chunk-UAFSPWU7.js → chunk-4HZN7EZZ.js} +8 -1
  8. package/dist/chunk-4HZN7EZZ.js.map +1 -0
  9. package/dist/{chunk-4VNUQYUE.js → chunk-4KGGVCYX.js} +1 -1
  10. package/dist/chunk-4KGGVCYX.js.map +1 -0
  11. package/dist/{chunk-AFX6ZXER.js → chunk-CGJDCZA3.js} +2 -2
  12. package/dist/{chunk-DJKLOLBT.cjs → chunk-E6DONLTM.cjs} +3 -11
  13. package/dist/chunk-E6DONLTM.cjs.map +1 -0
  14. package/dist/chunk-EV53YE3W.cjs +11 -0
  15. package/dist/chunk-EV53YE3W.cjs.map +1 -0
  16. package/dist/{chunk-3H5X63BQ.js → chunk-HJV4BVQI.js} +27 -1
  17. package/dist/chunk-HJV4BVQI.js.map +1 -0
  18. package/dist/{chunk-J3W6Q3AX.js → chunk-IMFIACHP.js} +2 -2
  19. package/dist/{chunk-BQENBAUC.js → chunk-JKJEBDP7.js} +3 -3
  20. package/dist/{chunk-ZDWP3LLY.cjs → chunk-KE6DLZAW.cjs} +12 -10
  21. package/dist/chunk-KE6DLZAW.cjs.map +1 -0
  22. package/dist/{chunk-YWDN5PQC.js → chunk-KEGAEHWO.js} +2 -2
  23. package/dist/chunk-KSATSTC5.js +11 -0
  24. package/dist/chunk-KSATSTC5.js.map +1 -0
  25. package/dist/{chunk-3UIT4PPQ.js → chunk-LMHVMGEC.js} +5 -3
  26. package/dist/{chunk-3UIT4PPQ.js.map → chunk-LMHVMGEC.js.map} +1 -1
  27. package/dist/{chunk-VECVLIWE.js → chunk-NYLYIN55.js} +3 -3
  28. package/dist/{chunk-IMEMTJ76.js → chunk-P663PYWV.js} +2 -2
  29. package/dist/{chunk-ZV5OKQ3Z.cjs → chunk-QETZP25M.cjs} +28 -2
  30. package/dist/chunk-QETZP25M.cjs.map +1 -0
  31. package/dist/{chunk-TZ3WCAJZ.cjs → chunk-QGATKC4R.cjs} +6 -6
  32. package/dist/{chunk-TZ3WCAJZ.cjs.map → chunk-QGATKC4R.cjs.map} +1 -1
  33. package/dist/{chunk-USGC3ZKU.cjs → chunk-R4LXZRYP.cjs} +4 -4
  34. package/dist/{chunk-USGC3ZKU.cjs.map → chunk-R4LXZRYP.cjs.map} +1 -1
  35. package/dist/chunk-RQVFFSAO.js +52 -0
  36. package/dist/chunk-RQVFFSAO.js.map +1 -0
  37. package/dist/{chunk-GZDOWYJM.js → chunk-TXQQ7URG.js} +2 -2
  38. package/dist/{chunk-7ZIOZ6PU.js → chunk-VZ3AQVBN.js} +8 -3
  39. package/dist/chunk-VZ3AQVBN.js.map +1 -0
  40. package/dist/{chunk-5KPNXBNR.js → chunk-W5NZYPRQ.js} +386 -80
  41. package/dist/chunk-W5NZYPRQ.js.map +1 -0
  42. package/dist/{chunk-JEX5P66D.js → chunk-XOO6YEZE.js} +3 -3
  43. package/dist/{chunk-6663UXPE.js → chunk-Y7T5Z4DC.js} +3 -3
  44. package/dist/chunks-LWAOHTK2.js +10 -0
  45. package/dist/cli.js +57 -21
  46. package/dist/cli.js.map +1 -1
  47. package/dist/{formula-JLYS73QL.js → formula-IFKZZPD2.js} +4 -2
  48. package/dist/{formula-LIVQSCMM.js.map → formula-IFKZZPD2.js.map} +1 -1
  49. package/dist/{formula-CG4S7TWO.cjs → formula-PDMZMBIP.cjs} +9 -8
  50. package/dist/formula-PDMZMBIP.cjs.map +1 -0
  51. package/dist/{formula-LIVQSCMM.js → formula-V6EAYHMF.js} +3 -2
  52. package/dist/{image-ocr-MKD6MKWO.cjs → image-ocr-75WGMVU6.cjs} +12 -11
  53. package/dist/image-ocr-75WGMVU6.cjs.map +1 -0
  54. package/dist/{image-ocr-5QRZBNOF.js → image-ocr-B7TMIG3F.js} +10 -9
  55. package/dist/{image-ocr-5QRZBNOF.js.map → image-ocr-B7TMIG3F.js.map} +1 -1
  56. package/dist/{image-ocr-TAJHRZ5T.js → image-ocr-XHBZONFZ.js} +6 -5
  57. package/dist/{image-ocr-TAJHRZ5T.js.map → image-ocr-XHBZONFZ.js.map} +1 -1
  58. package/dist/index.cjs +865 -552
  59. package/dist/index.cjs.map +1 -1
  60. package/dist/index.d.cts +10 -2
  61. package/dist/index.d.ts +10 -2
  62. package/dist/index.js +390 -77
  63. package/dist/index.js.map +1 -1
  64. package/dist/mcp.js +36 -18
  65. package/dist/mcp.js.map +1 -1
  66. package/dist/model-bundle-Q4HQXJQG.js +104 -0
  67. package/dist/model-bundle-Q4HQXJQG.js.map +1 -0
  68. package/dist/{models-6SIKLKRH.js → models-QQA7OQHY.js} +5 -3
  69. package/dist/{parser-SWY4RKGD.cjs → parser-4CBOPTVY.cjs} +28 -27
  70. package/dist/parser-4CBOPTVY.cjs.map +1 -0
  71. package/dist/{parser-UMFBJG2D.js → parser-HT7GZOF2.js} +8 -8
  72. package/dist/{parser-RCEHB2EV.js.map → parser-HT7GZOF2.js.map} +1 -1
  73. package/dist/{parser-RCEHB2EV.js → parser-Z36MZUHS.js} +8 -7
  74. package/dist/{parser-UMFBJG2D.js.map → parser-Z36MZUHS.js.map} +1 -1
  75. package/dist/pdf-ocr-6ZNGDVC6.cjs +14 -0
  76. package/dist/pdf-ocr-6ZNGDVC6.cjs.map +1 -0
  77. package/dist/pdf-ocr-NTXRJI4T.js +13 -0
  78. package/dist/pdf-ocr-TYMCEUPH.js +16 -0
  79. package/dist/{profile-io-HT62OAEU.js → profile-io-ACYRLCSR.js} +3 -3
  80. package/dist/{rasterize-GL67HQC7.js → rasterize-QYCLEH2J.js} +2 -2
  81. package/dist/render-5Y2US4IM.js +10 -0
  82. package/dist/seal-VU6JS2QD.js +10 -0
  83. package/dist/{setup-GUQT5ELJ.js → setup-MG3YYKXU.js} +30 -7
  84. package/dist/setup-MG3YYKXU.js.map +1 -0
  85. package/dist/{watch-5ST75Q7Q.js → watch-AZIY6AWC.js} +13 -9
  86. package/dist/watch-AZIY6AWC.js.map +1 -0
  87. package/package.json +7 -3
  88. package/dist/chunk-3H5X63BQ.js.map +0 -1
  89. package/dist/chunk-4VNUQYUE.js.map +0 -1
  90. package/dist/chunk-5KPNXBNR.js.map +0 -1
  91. package/dist/chunk-7ZIOZ6PU.js.map +0 -1
  92. package/dist/chunk-DJKLOLBT.cjs.map +0 -1
  93. package/dist/chunk-IZEUWEEY.js.map +0 -1
  94. package/dist/chunk-UAFSPWU7.js.map +0 -1
  95. package/dist/chunk-ZDWP3LLY.cjs.map +0 -1
  96. package/dist/chunk-ZV5OKQ3Z.cjs.map +0 -1
  97. package/dist/chunks-C7DJL4RE.js +0 -10
  98. package/dist/formula-CG4S7TWO.cjs.map +0 -1
  99. package/dist/image-ocr-MKD6MKWO.cjs.map +0 -1
  100. package/dist/parser-SWY4RKGD.cjs.map +0 -1
  101. package/dist/pdf-ocr-3NTV3MYP.js +0 -12
  102. package/dist/pdf-ocr-7BOO4JA7.js +0 -15
  103. package/dist/pdf-ocr-JDHELYBY.cjs +0 -13
  104. package/dist/pdf-ocr-JDHELYBY.cjs.map +0 -1
  105. package/dist/render-IQ4IZLY4.js +0 -10
  106. package/dist/seal-AGSZ6MUR.js +0 -10
  107. package/dist/setup-GUQT5ELJ.js.map +0 -1
  108. package/dist/watch-5ST75Q7Q.js.map +0 -1
  109. /package/dist/{-O42BMYNX.js.map → -WYNJOQ6P.js.map} +0 -0
  110. /package/dist/{chunk-PLEGO474.js.map → chunk-3NKSSL44.js.map} +0 -0
  111. /package/dist/{chunk-UZB3UGC5.js.map → chunk-3P5YCJQE.js.map} +0 -0
  112. /package/dist/{chunk-AFX6ZXER.js.map → chunk-CGJDCZA3.js.map} +0 -0
  113. /package/dist/{chunk-J3W6Q3AX.js.map → chunk-IMFIACHP.js.map} +0 -0
  114. /package/dist/{chunk-BQENBAUC.js.map → chunk-JKJEBDP7.js.map} +0 -0
  115. /package/dist/{chunk-YWDN5PQC.js.map → chunk-KEGAEHWO.js.map} +0 -0
  116. /package/dist/{chunk-VECVLIWE.js.map → chunk-NYLYIN55.js.map} +0 -0
  117. /package/dist/{chunk-IMEMTJ76.js.map → chunk-P663PYWV.js.map} +0 -0
  118. /package/dist/{chunk-GZDOWYJM.js.map → chunk-TXQQ7URG.js.map} +0 -0
  119. /package/dist/{chunk-JEX5P66D.js.map → chunk-XOO6YEZE.js.map} +0 -0
  120. /package/dist/{chunk-6663UXPE.js.map → chunk-Y7T5Z4DC.js.map} +0 -0
  121. /package/dist/{chunks-C7DJL4RE.js.map → chunks-LWAOHTK2.js.map} +0 -0
  122. /package/dist/{formula-JLYS73QL.js.map → formula-V6EAYHMF.js.map} +0 -0
  123. /package/dist/{models-6SIKLKRH.js.map → models-QQA7OQHY.js.map} +0 -0
  124. /package/dist/{pdf-ocr-3NTV3MYP.js.map → pdf-ocr-NTXRJI4T.js.map} +0 -0
  125. /package/dist/{pdf-ocr-7BOO4JA7.js.map → pdf-ocr-TYMCEUPH.js.map} +0 -0
  126. /package/dist/{profile-io-HT62OAEU.js.map → profile-io-ACYRLCSR.js.map} +0 -0
  127. /package/dist/{rasterize-GL67HQC7.js.map → rasterize-QYCLEH2J.js.map} +0 -0
  128. /package/dist/{render-IQ4IZLY4.js.map → render-5Y2US4IM.js.map} +0 -0
  129. /package/dist/{seal-AGSZ6MUR.js.map → seal-VU6JS2QD.js.map} +0 -0
package/dist/index.js CHANGED
@@ -8,14 +8,16 @@ import {
8
8
  flattenLayoutTables,
9
9
  inlineImagesIntoMarkdown,
10
10
  mapPuaText
11
- } from "./chunk-IMEMTJ76.js";
11
+ } from "./chunk-P663PYWV.js";
12
12
  import {
13
13
  parsePageRange
14
14
  } from "./chunk-GE43BE46.js";
15
15
  import {
16
16
  HEADING_RATIO_H1,
17
17
  HEADING_RATIO_H2,
18
- HEADING_RATIO_H3,
18
+ HEADING_RATIO_H3
19
+ } from "./chunk-KSATSTC5.js";
20
+ import {
19
21
  KordocError,
20
22
  VERSION,
21
23
  classifyError,
@@ -27,7 +29,7 @@ import {
27
29
  sanitizeHref,
28
30
  stripDtd,
29
31
  toArrayBuffer
30
- } from "./chunk-IZEUWEEY.js";
32
+ } from "./chunk-46F5RMU7.js";
31
33
 
32
34
  // src/index.ts
33
35
  import { readFile } from "fs/promises";
@@ -393,7 +395,7 @@ function comResultToParseResult(pages, pageCount, warnings) {
393
395
  }
394
396
  }
395
397
  const markdown = lines.join("\n\n");
396
- const metadata = { pageCount };
398
+ const metadata = { pageCount, pageMode: "layout" };
397
399
  warnings.push({
398
400
  message: "DRM \uBB38\uC11C: \uD55C\uCEF4 COM API\uB85C \uD14D\uC2A4\uD2B8 \uCD94\uCD9C (\uC11C\uC2DD/\uD45C \uC815\uBCF4 \uC81C\uD55C\uC801)",
399
401
  code: "DRM_COM_FALLBACK"
@@ -489,7 +491,12 @@ function clampSpan(val, max) {
489
491
  return Math.max(1, Math.min(val, max));
490
492
  }
491
493
  function createSectionShared() {
492
- return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
494
+ return {
495
+ numState: /* @__PURE__ */ new Map(),
496
+ pageText: { headers: [], footers: [] },
497
+ track: { deleteDepth: 0, warned: false },
498
+ pageState: { base: 0, allUsable: true }
499
+ };
493
500
  }
494
501
  function createXmlParser(warnings) {
495
502
  return new DOMParser({
@@ -1480,11 +1487,11 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
1480
1487
  function completeTable(newTable, tableStack, blocks, ctx) {
1481
1488
  const parentTable = tableStack.length > 0 ? tableStack.pop() : null;
1482
1489
  if (newTable.rows.length === 0) {
1483
- if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.sectionNum });
1490
+ if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.page });
1484
1491
  return parentTable;
1485
1492
  }
1486
1493
  const ir = buildTableWithCellMeta(newTable, ctx.shared.keepTrailingEmptyCols, ctx.shared.keepEmptyParagraphs);
1487
- const block = { type: "table", table: ir, pageNumber: ctx.sectionNum };
1494
+ const block = { type: "table", table: ir, pageNumber: ctx.page };
1488
1495
  if (parentTable?.cell) {
1489
1496
  const cell2 = parentTable.cell;
1490
1497
  (cell2.blocks ??= []).push(block);
@@ -1502,6 +1509,133 @@ function completeTable(newTable, tableStack, blocks, ctx) {
1502
1509
  return parentTable;
1503
1510
  }
1504
1511
 
1512
+ // src/hwpx/page-boundary.ts
1513
+ var MIDPAGE_V = 2e3;
1514
+ var localName2 = (el) => {
1515
+ const n = el.nodeName;
1516
+ const i = n.indexOf(":");
1517
+ return i >= 0 ? n.slice(i + 1) : n;
1518
+ };
1519
+ var childElements = (parent) => {
1520
+ const out = [];
1521
+ for (let c = parent.firstChild; c; c = c.nextSibling) {
1522
+ if (c.nodeType === 1) out.push(c);
1523
+ }
1524
+ return out;
1525
+ };
1526
+ var numAttr = (el, name) => {
1527
+ const v = el.getAttribute(name);
1528
+ const n = v == null ? NaN : Number(v);
1529
+ return Number.isFinite(n) ? n : 0;
1530
+ };
1531
+ var findDescendant = (root, local, maxDepth = 8) => {
1532
+ if (maxDepth < 0) return null;
1533
+ for (const c of childElements(root)) {
1534
+ if (localName2(c) === local) return c;
1535
+ const found = findDescendant(c, local, maxDepth - 1);
1536
+ if (found) return found;
1537
+ }
1538
+ return null;
1539
+ };
1540
+ function linesegsOf(p) {
1541
+ const lsa = childElements(p).find((c) => localName2(c) === "linesegarray");
1542
+ return lsa ? childElements(lsa).filter((s) => localName2(s) === "lineseg") : [];
1543
+ }
1544
+ function cellFlowResets(subList) {
1545
+ let prevV = Number.NEGATIVE_INFINITY;
1546
+ let resets = 0;
1547
+ for (const p of childElements(subList)) {
1548
+ if (localName2(p) !== "p") continue;
1549
+ for (const s of linesegsOf(p)) {
1550
+ const v = numAttr(s, "vertpos");
1551
+ if (v < prevV) resets++;
1552
+ prevV = v;
1553
+ }
1554
+ }
1555
+ return resets;
1556
+ }
1557
+ function tableIntraBreaks(tbl2) {
1558
+ const rowMax = /* @__PURE__ */ new Map();
1559
+ for (const tr of childElements(tbl2)) {
1560
+ if (localName2(tr) !== "tr") continue;
1561
+ for (const tc2 of childElements(tr)) {
1562
+ if (localName2(tc2) !== "tc") continue;
1563
+ const addr = childElements(tc2).find((e) => localName2(e) === "cellAddr");
1564
+ const row = addr ? numAttr(addr, "rowAddr") : 0;
1565
+ const sub = childElements(tc2).find((e) => localName2(e) === "subList");
1566
+ if (!sub) continue;
1567
+ const r = cellFlowResets(sub);
1568
+ rowMax.set(row, Math.max(rowMax.get(row) ?? 0, r));
1569
+ }
1570
+ }
1571
+ let sum = 0;
1572
+ for (const v of rowMax.values()) sum += v;
1573
+ return sum;
1574
+ }
1575
+ function detectHwpxSectionPages(root) {
1576
+ const colPr = findDescendant(root, "colPr");
1577
+ const multiCol = colPr ? numAttr(colPr, "colCount") > 1 : false;
1578
+ const paraPage = /* @__PURE__ */ new Map();
1579
+ let cur = 0;
1580
+ let prevV = Number.NEGATIVE_INFINITY;
1581
+ let prevH = Number.NEGATIVE_INFINITY;
1582
+ let first = true;
1583
+ let suppressMidReset = false;
1584
+ let topParas = 0;
1585
+ let parasWithSegs = 0;
1586
+ for (const p of childElements(root)) {
1587
+ if (localName2(p) !== "p") continue;
1588
+ topParas++;
1589
+ const explicit = p.getAttribute("pageBreak") === "1";
1590
+ let brokeByExplicit = false;
1591
+ if (explicit && !first) {
1592
+ cur++;
1593
+ brokeByExplicit = true;
1594
+ suppressMidReset = false;
1595
+ }
1596
+ const segs = linesegsOf(p);
1597
+ if (segs.length > 0) parasWithSegs++;
1598
+ let paraFirst = true;
1599
+ let startPage = cur;
1600
+ for (const s of segs) {
1601
+ const v = numAttr(s, "vertpos");
1602
+ const h = numAttr(s, "horzpos");
1603
+ const brk = v < prevV ? !multiCol || h <= prevH : paraFirst && v === prevV && h <= prevH;
1604
+ if (brk && !(paraFirst && brokeByExplicit)) {
1605
+ if (paraFirst && suppressMidReset && v >= MIDPAGE_V) {
1606
+ } else {
1607
+ cur++;
1608
+ }
1609
+ }
1610
+ if (paraFirst) {
1611
+ suppressMidReset = false;
1612
+ startPage = cur;
1613
+ }
1614
+ paraFirst = false;
1615
+ prevV = v;
1616
+ prevH = h;
1617
+ }
1618
+ paraPage.set(p, startPage);
1619
+ first = false;
1620
+ let add = 0;
1621
+ for (const r of childElements(p)) {
1622
+ if (localName2(r) !== "run") continue;
1623
+ for (const t of childElements(r)) {
1624
+ if (localName2(t) === "tbl") add += tableIntraBreaks(t);
1625
+ }
1626
+ }
1627
+ if (add > 0) {
1628
+ cur += add;
1629
+ suppressMidReset = true;
1630
+ }
1631
+ }
1632
+ return {
1633
+ paraPage,
1634
+ pages: cur + 1,
1635
+ usable: topParas > 0 && parasWithSegs === topParas
1636
+ };
1637
+ }
1638
+
1505
1639
  // src/hwpx/section-walker.ts
1506
1640
  function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
1507
1641
  const parser = createXmlParser(warnings);
@@ -1509,6 +1643,13 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
1509
1643
  if (!doc.documentElement) return [];
1510
1644
  const ctx = { styleMap, warnings, sectionNum, shared: shared ?? createSectionShared() };
1511
1645
  ctx.shared.track.deleteDepth = 0;
1646
+ const detect = detectHwpxSectionPages(doc.documentElement);
1647
+ const ps = ctx.shared.pageState;
1648
+ if (!detect.usable) ps.allUsable = false;
1649
+ ctx.paraPage = detect.paraPage;
1650
+ ctx.pageBase = ps.base;
1651
+ ctx.page = ps.base + 1;
1652
+ ps.base += detect.pages;
1512
1653
  for (const tagName of ["hp:secPr", "secPr"]) {
1513
1654
  const els = doc.getElementsByTagName(tagName);
1514
1655
  if (els.length > 0) {
@@ -1555,7 +1696,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1555
1696
  const chan = kordocTableChannel(el, ctx);
1556
1697
  if (chan) {
1557
1698
  if (chan.kind === "heading" && chan.text) {
1558
- blocks.push({ type: "heading", level: chan.level, text: chan.text, pageNumber: ctx.sectionNum });
1699
+ blocks.push({ type: "heading", level: chan.level, text: chan.text, pageNumber: ctx.page });
1559
1700
  }
1560
1701
  break;
1561
1702
  }
@@ -1574,7 +1715,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1574
1715
  tableCtx.caption = (tableCtx.caption ? tableCtx.caption + "\n" : "") + cap.text;
1575
1716
  if (cap.hasStructure) (tableCtx.captionBlocks ??= []).push(...cap.blocks);
1576
1717
  } else {
1577
- blocks.push({ type: "paragraph", text: cap.text, pageNumber: ctx.sectionNum });
1718
+ blocks.push({ type: "paragraph", text: cap.text, pageNumber: ctx.page });
1578
1719
  }
1579
1720
  }
1580
1721
  break;
@@ -1617,6 +1758,8 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1617
1758
  }
1618
1759
  break;
1619
1760
  case "p": {
1761
+ const paraPg = ctx.paraPage?.get(el);
1762
+ if (paraPg !== void 0 && ctx.pageBase !== void 0) ctx.page = ctx.pageBase + paraPg + 1;
1620
1763
  const { text: rawText, href, footnote, style, segments } = extractParagraphInfo(el, ctx.styleMap, ctx);
1621
1764
  let text = rawText;
1622
1765
  let headingLevel;
@@ -1638,7 +1781,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1638
1781
  const flush = () => {
1639
1782
  const s = segs[segIdx++];
1640
1783
  if (!s) return;
1641
- const block = { type: "paragraph", text: s, pageNumber: ctx.sectionNum };
1784
+ const block = { type: "paragraph", text: s, pageNumber: ctx.page };
1642
1785
  if (first && !cell2) {
1643
1786
  first = false;
1644
1787
  if (style) block.style = style;
@@ -1662,7 +1805,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1662
1805
  if (footnote) text += ` (\uC8FC: ${footnote})`;
1663
1806
  cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + text;
1664
1807
  cell2.paraSeen = true;
1665
- const cellBlock = { type: "paragraph", text, pageNumber: ctx.sectionNum };
1808
+ const cellBlock = { type: "paragraph", text, pageNumber: ctx.page };
1666
1809
  const cellSpanMode = spanModeOf(ctx.shared.kordocLayout);
1667
1810
  if (cellSpanMode && !ph?.prefix) {
1668
1811
  const spans = extractRunSpans(el, ctx, cellSpanMode, cellSpanMode !== "kordoc");
@@ -1674,11 +1817,11 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1674
1817
  (cell2.blocks ??= []).push(cellBlock);
1675
1818
  } else if (!tableCtx) {
1676
1819
  if (ctx.shared.kordocLayout && /^─{10,}$/.test(text)) {
1677
- blocks.push({ type: "separator", pageNumber: ctx.sectionNum });
1820
+ blocks.push({ type: "separator", pageNumber: ctx.page });
1678
1821
  tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1);
1679
1822
  break;
1680
1823
  }
1681
- const block = { type: headingLevel ? "heading" : "paragraph", text, pageNumber: ctx.sectionNum };
1824
+ const block = { type: headingLevel ? "heading" : "paragraph", text, pageNumber: ctx.page };
1682
1825
  if (headingLevel) block.level = headingLevel;
1683
1826
  if (style) block.style = style;
1684
1827
  if (href) block.href = href;
@@ -1704,7 +1847,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1704
1847
  }
1705
1848
  blocks.push(block);
1706
1849
  } else {
1707
- blocks.push({ type: "paragraph", text, pageNumber: ctx.sectionNum });
1850
+ blocks.push({ type: "paragraph", text, pageNumber: ctx.page });
1708
1851
  }
1709
1852
  } else if (ctx.shared.keepEmptyParagraphs && !hasObjectDescendant(el)) {
1710
1853
  if (tableCtx?.cell) {
@@ -1712,9 +1855,9 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1712
1855
  if (cell2.text || cell2.paraSeen) cell2.text += "\n";
1713
1856
  cell2.paraSeen = true;
1714
1857
  cell2.lineOpen = false;
1715
- (cell2.blocks ??= []).push({ type: "paragraph", text: "", pageNumber: ctx.sectionNum });
1858
+ (cell2.blocks ??= []).push({ type: "paragraph", text: "", pageNumber: ctx.page });
1716
1859
  } else if (!tableCtx) {
1717
- blocks.push({ type: "paragraph", text: "", pageNumber: ctx.sectionNum });
1860
+ blocks.push({ type: "paragraph", text: "", pageNumber: ctx.page });
1718
1861
  }
1719
1862
  }
1720
1863
  tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1);
@@ -1737,7 +1880,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1737
1880
  case "memogroup":
1738
1881
  case "memo": {
1739
1882
  if (ctx.warnings && extractTextFromNode(el)) {
1740
- ctx.warnings.push({ page: ctx.sectionNum, message: "\uBA54\uBAA8 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38 \uC81C\uC678: memogroup", code: "HIDDEN_TEXT_FILTERED" });
1883
+ ctx.warnings.push({ page: ctx.page, message: "\uBA54\uBAA8 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38 \uC81C\uC678: memogroup", code: "HIDDEN_TEXT_FILTERED" });
1741
1884
  }
1742
1885
  break;
1743
1886
  }
@@ -1749,9 +1892,9 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
1749
1892
  }
1750
1893
  function handleShape(el, sink, ctx) {
1751
1894
  const imgRef = extractImageRef(el);
1752
- const drawTextChild = findDescendant(el, "drawText");
1895
+ const drawTextChild = findDescendant2(el, "drawText");
1753
1896
  if (imgRef) {
1754
- const block = { type: "image", text: imgRef, pageNumber: ctx.sectionNum };
1897
+ const block = { type: "image", text: imgRef, pageNumber: ctx.page };
1755
1898
  const alt = userShapeComment(el);
1756
1899
  if (alt) block.footnoteText = alt;
1757
1900
  sink.push(block);
@@ -1762,11 +1905,11 @@ function handleShape(el, sink, ctx) {
1762
1905
  const capEl = findChildByLocalName(el, "caption");
1763
1906
  if (capEl) {
1764
1907
  const capText = collectSubListText(capEl, ctx);
1765
- if (capText) sink.push({ type: "paragraph", text: capText, pageNumber: ctx.sectionNum });
1908
+ if (capText) sink.push({ type: "paragraph", text: capText, pageNumber: ctx.page });
1766
1909
  }
1767
1910
  if (!imgRef && !drawTextChild && ctx.warnings && ctx.sectionNum) {
1768
1911
  const localTag = (el.tagName || el.localName || "").replace(/^[^:]+:/, "");
1769
- ctx.warnings.push({ page: ctx.sectionNum, message: `\uC2A4\uD0B5\uB41C \uC694\uC18C: ${localTag}`, code: "SKIPPED_IMAGE" });
1912
+ ctx.warnings.push({ page: ctx.page, message: `\uC2A4\uD0B5\uB41C \uC694\uC18C: ${localTag}`, code: "SKIPPED_IMAGE" });
1770
1913
  }
1771
1914
  }
1772
1915
  function userShapeComment(el) {
@@ -1851,7 +1994,7 @@ function collectSubListContent(el, ctx, depth = 0) {
1851
1994
  const t = extractParagraphInfo(ch, ctx.styleMap, ctx).text;
1852
1995
  if (t) {
1853
1996
  parts.push(t);
1854
- out.blocks.push({ type: "paragraph", text: t, pageNumber: ctx.sectionNum });
1997
+ out.blocks.push({ type: "paragraph", text: t, pageNumber: ctx.page });
1855
1998
  }
1856
1999
  const tbls = [];
1857
2000
  findTopLevelTbls(ch, tbls);
@@ -1924,7 +2067,7 @@ function walkParagraphChildren(node, blocks, tableCtx, tableStack, ctx, depth =
1924
2067
  const chan = kordocTableChannel(el, ctx);
1925
2068
  if (chan) {
1926
2069
  if (chan.kind === "heading" && chan.text) {
1927
- blocks.push({ type: "heading", level: chan.level, text: chan.text, pageNumber: ctx.sectionNum });
2070
+ blocks.push({ type: "heading", level: chan.level, text: chan.text, pageNumber: ctx.page });
1928
2071
  }
1929
2072
  continue;
1930
2073
  }
@@ -1936,11 +2079,11 @@ function walkParagraphChildren(node, blocks, tableCtx, tableStack, ctx, depth =
1936
2079
  } else if (localTag === "caption" && !inShape) {
1937
2080
  const cap = collectSubListContent(el, ctx);
1938
2081
  if (cap.text) {
1939
- if (tableCtx?.cell) mergeBlocksIntoCell(tableCtx.cell, [{ type: "paragraph", text: cap.text, pageNumber: ctx.sectionNum }]);
2082
+ if (tableCtx?.cell) mergeBlocksIntoCell(tableCtx.cell, [{ type: "paragraph", text: cap.text, pageNumber: ctx.page }]);
1940
2083
  else if (tableCtx) {
1941
2084
  tableCtx.caption = (tableCtx.caption ? tableCtx.caption + "\n" : "") + cap.text;
1942
2085
  if (cap.hasStructure) (tableCtx.captionBlocks ??= []).push(...cap.blocks);
1943
- } else blocks.push({ type: "paragraph", text: cap.text, pageNumber: ctx.sectionNum });
2086
+ } else blocks.push({ type: "paragraph", text: cap.text, pageNumber: ctx.page });
1944
2087
  }
1945
2088
  } else if (localTag === "pic" || localTag === "shape" || localTag === "drawingObject") {
1946
2089
  if (tableCtx?.cell) {
@@ -1985,7 +2128,7 @@ function hasObjectDescendant(node, depth = 0) {
1985
2128
  function isInlineTbl(tbl2) {
1986
2129
  return findChildByLocalName(tbl2, "pos")?.getAttribute("treatAsChar") === "1";
1987
2130
  }
1988
- function findDescendant(node, targetTag, depth = 0) {
2131
+ function findDescendant2(node, targetTag, depth = 0) {
1989
2132
  if (depth > 5) return null;
1990
2133
  const children = node.childNodes;
1991
2134
  if (!children) return null;
@@ -1994,7 +2137,7 @@ function findDescendant(node, targetTag, depth = 0) {
1994
2137
  if (child.nodeType !== 1) continue;
1995
2138
  const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
1996
2139
  if (tag === targetTag) return child;
1997
- const found = findDescendant(child, targetTag, depth + 1);
2140
+ const found = findDescendant2(child, targetTag, depth + 1);
1998
2141
  if (found) return found;
1999
2142
  }
2000
2143
  return null;
@@ -2015,7 +2158,7 @@ function extractDrawTextBlocks(drawTextNode, blocks, ctx) {
2015
2158
  const ph = resolveParaHeading(child, ctx);
2016
2159
  if (text) {
2017
2160
  if (ph?.prefix) text = ph.prefix + " " + text;
2018
- const block = { type: "paragraph", text, style: info.style ?? void 0, pageNumber: ctx.sectionNum };
2161
+ const block = { type: "paragraph", text, style: info.style ?? void 0, pageNumber: ctx.page };
2019
2162
  if (info.href) block.href = info.href;
2020
2163
  if (info.footnote) block.footnoteText = info.footnote;
2021
2164
  blocks.push(block);
@@ -2118,7 +2261,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2118
2261
  // 숨은 설명 — 본문 혼입 차단
2119
2262
  case "hiddenComment": {
2120
2263
  if (ctx?.warnings && extractTextFromNode(k)) {
2121
- ctx.warnings.push({ page: ctx.sectionNum, message: "\uC228\uC740 \uC124\uBA85 \uD14D\uC2A4\uD2B8 \uC81C\uC678: hiddenComment", code: "HIDDEN_TEXT_FILTERED" });
2264
+ ctx.warnings.push({ page: ctx.page, message: "\uC228\uC740 \uC124\uBA85 \uD14D\uC2A4\uD2B8 \uC81C\uC678: hiddenComment", code: "HIDDEN_TEXT_FILTERED" });
2122
2265
  }
2123
2266
  break;
2124
2267
  }
@@ -2138,7 +2281,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2138
2281
  // 미지원 요소 — 텍스트를 가졌으면 무음 손실 대신 경고
2139
2282
  default: {
2140
2283
  if (ctx?.warnings && extractTextFromNode(k)) {
2141
- ctx.warnings.push({ page: ctx.sectionNum, message: `\uBBF8\uC9C0\uC6D0 \uC81C\uC5B4 \uC694\uC18C\uC758 \uD14D\uC2A4\uD2B8 \uC190\uC2E4: ${ktag}`, code: "UNSUPPORTED_ELEMENT" });
2284
+ ctx.warnings.push({ page: ctx.page, message: `\uBBF8\uC9C0\uC6D0 \uC81C\uC5B4 \uC694\uC18C\uC758 \uD14D\uC2A4\uD2B8 \uC190\uC2E4: ${ktag}`, code: "UNSUPPORTED_ELEMENT" });
2142
2285
  }
2143
2286
  }
2144
2287
  }
@@ -2155,7 +2298,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2155
2298
  if (isInDeletedRange(ctx)) {
2156
2299
  if (t && ctx && !ctx.shared.track.warned) {
2157
2300
  ctx.shared.track.warned = true;
2158
- ctx.warnings?.push({ page: ctx.sectionNum, message: "\uBCC0\uACBD\uCD94\uC801 \uC0AD\uC81C \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uC81C\uC678", code: "HIDDEN_TEXT_FILTERED" });
2301
+ ctx.warnings?.push({ page: ctx.page, message: "\uBCC0\uACBD\uCD94\uC801 \uC0AD\uC81C \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uC81C\uC678", code: "HIDDEN_TEXT_FILTERED" });
2159
2302
  }
2160
2303
  } else {
2161
2304
  text += t.replace(/\x1E/g, "");
@@ -2267,7 +2410,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
2267
2410
  } catch {
2268
2411
  if (ctx?.warnings) {
2269
2412
  ctx.warnings.push({
2270
- page: ctx.sectionNum,
2413
+ page: ctx.page,
2271
2414
  message: `\uC218\uC2DD LaTeX \uBCC0\uD658 \uC2E4\uD328 \u2014 \uC218\uC2DD \uD14D\uC2A4\uD2B8 \uC81C\uC678: ${raw.trim().slice(0, 40)}`,
2272
2415
  code: "PARTIAL_PARSE"
2273
2416
  });
@@ -2424,6 +2567,7 @@ import { inflateRawSync as inflateRawSync2, inflateSync } from "zlib";
2424
2567
  var TAG_PARA_HEADER = 66;
2425
2568
  var TAG_PARA_TEXT = 67;
2426
2569
  var TAG_CHAR_SHAPE = 68;
2570
+ var TAG_PARA_LINE_SEG = 69;
2427
2571
  var TAG_CTRL_HEADER = 71;
2428
2572
  var TAG_LIST_HEADER = 72;
2429
2573
  var TAG_TABLE = 77;
@@ -3058,7 +3202,9 @@ function extractFromBrokenZip(buffer) {
3058
3202
  totalDecompressed += content.length * 2;
3059
3203
  if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
3060
3204
  sectionNum++;
3061
- blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
3205
+ const secBlocks = parseSectionXml(content, void 0, warnings, sectionNum, shared);
3206
+ for (const b of secBlocks) b.pageNumber = sectionNum;
3207
+ blocks.push(...secBlocks);
3062
3208
  } catch {
3063
3209
  continue;
3064
3210
  }
@@ -3197,33 +3343,48 @@ async function parseHwpxDocument(buffer, options) {
3197
3343
  const warnings = [];
3198
3344
  const sectionPaths = await resolveSectionPaths(zip);
3199
3345
  if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
3200
- metadata.pageCount = sectionPaths.length;
3201
- const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
3202
- const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
3203
- const blocks = [];
3346
+ let allBlocks = [];
3204
3347
  const shared = createSectionShared();
3205
3348
  shared.kordocLayout = await readKordocLayout(zip);
3206
3349
  shared.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
3207
3350
  shared.keepEmptyParagraphs = options?.keepEmptyParagraphs;
3351
+ const sectionRanges = [];
3208
3352
  let parsedSections = 0;
3209
3353
  for (let si = 0; si < sectionPaths.length; si++) {
3210
- if (pageFilter && !pageFilter.has(si + 1)) continue;
3211
3354
  const file = zip.file(sectionPaths[si]);
3212
3355
  if (!file) continue;
3213
3356
  try {
3214
3357
  const xml = await file.async("text");
3215
3358
  decompressed.total += xml.length * 2;
3216
3359
  if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new ZipBombError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
3217
- blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
3360
+ const start = allBlocks.length;
3361
+ allBlocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
3362
+ sectionRanges.push({ sectionNum: si + 1, start, end: allBlocks.length });
3218
3363
  parsedSections++;
3219
- options?.onProgress?.(parsedSections, totalTarget);
3364
+ options?.onProgress?.(parsedSections, sectionPaths.length);
3220
3365
  } catch (secErr) {
3221
3366
  if (secErr instanceof ZipBombError) throw secErr;
3222
3367
  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" });
3223
3368
  }
3224
3369
  }
3370
+ const layoutPages = shared.pageState.allUsable && parsedSections > 0;
3371
+ metadata.pageMode = layoutPages ? "layout" : "section";
3372
+ metadata.pageCount = layoutPages ? Math.max(shared.pageState.base, 1) : sectionPaths.length;
3373
+ if (!layoutPages) {
3374
+ for (const r of sectionRanges) {
3375
+ for (let b = r.start; b < r.end; b++) allBlocks[b].pageNumber = r.sectionNum;
3376
+ }
3377
+ }
3378
+ if (options?.pages) {
3379
+ const pageFilter = parsePageRange(options.pages, metadata.pageCount);
3380
+ allBlocks = allBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
3381
+ if (!layoutPages) {
3382
+ 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" });
3383
+ }
3384
+ }
3385
+ const blocks = allBlocks;
3225
3386
  applyPageText(blocks, shared);
3226
- const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !pageFilter);
3387
+ const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !options?.pages);
3227
3388
  detectHwpxHeadings(blocks, styleMap);
3228
3389
  const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
3229
3390
  const markdown = blocksToMarkdown(blocks);
@@ -4244,6 +4405,112 @@ function assertSupportedEncryptVersion(version) {
4244
4405
  }
4245
4406
  }
4246
4407
 
4408
+ // src/hwp5/page-boundary.ts
4409
+ var MIDPAGE_V2 = 2e3;
4410
+ var CTRL_ID_TBL = 1952607264;
4411
+ function tableIntraBreaks2(records, start, end) {
4412
+ const rowMax = /* @__PURE__ */ new Map();
4413
+ let curRow = -1;
4414
+ let prevV = Number.NEGATIVE_INFINITY;
4415
+ let resets = 0;
4416
+ const flush = () => {
4417
+ if (curRow >= 0) rowMax.set(curRow, Math.max(rowMax.get(curRow) ?? 0, resets));
4418
+ resets = 0;
4419
+ prevV = Number.NEGATIVE_INFINITY;
4420
+ };
4421
+ for (let i = start; i < end; i++) {
4422
+ const rec = records[i];
4423
+ if (rec.tagId === TAG_LIST_HEADER && rec.level === 2 && rec.data.length >= 12) {
4424
+ flush();
4425
+ curRow = rec.data.readUInt16LE(10);
4426
+ } else if (rec.tagId === TAG_PARA_LINE_SEG && rec.level === 3 && curRow >= 0) {
4427
+ for (let off = 0; off + 36 <= rec.data.length; off += 36) {
4428
+ const v = rec.data.readInt32LE(off + 4);
4429
+ if (v < prevV) resets++;
4430
+ prevV = v;
4431
+ }
4432
+ }
4433
+ }
4434
+ flush();
4435
+ let sum = 0;
4436
+ for (const v of rowMax.values()) sum += v;
4437
+ return sum;
4438
+ }
4439
+ function detectHwp5SectionPages(records) {
4440
+ const pageAtPara = [];
4441
+ let cur = 0;
4442
+ let prevV = Number.NEGATIVE_INFINITY;
4443
+ let prevH = Number.NEGATIVE_INFINITY;
4444
+ let first = true;
4445
+ let suppressMidReset = false;
4446
+ let parasWithSegs = 0;
4447
+ let i = 0;
4448
+ while (i < records.length) {
4449
+ const rec = records[i];
4450
+ if (rec.tagId !== TAG_PARA_HEADER || rec.level !== 0) {
4451
+ i++;
4452
+ continue;
4453
+ }
4454
+ let j = i + 1;
4455
+ while (j < records.length && records[j].level > 0) j++;
4456
+ const divideSort = rec.data.length >= 12 ? rec.data.readUInt8(11) : 0;
4457
+ const explicit = (divideSort & 4) !== 0;
4458
+ let brokeByExplicit = false;
4459
+ if (explicit && !first) {
4460
+ cur++;
4461
+ brokeByExplicit = true;
4462
+ suppressMidReset = false;
4463
+ }
4464
+ let paraFirst = true;
4465
+ let startPage = cur;
4466
+ let hadSegs = false;
4467
+ let intraAdd = 0;
4468
+ for (let k = i + 1; k < j; k++) {
4469
+ const r = records[k];
4470
+ if (r.tagId === TAG_PARA_LINE_SEG && r.level === 1) {
4471
+ hadSegs = true;
4472
+ for (let off = 0; off + 36 <= r.data.length; off += 36) {
4473
+ const v = r.data.readInt32LE(off + 4);
4474
+ const h = r.data.readInt32LE(off + 24);
4475
+ const brk = v < prevV || paraFirst && v === prevV && h <= prevH;
4476
+ if (brk && !(paraFirst && brokeByExplicit)) {
4477
+ if (paraFirst && suppressMidReset && v >= MIDPAGE_V2) {
4478
+ } else {
4479
+ cur++;
4480
+ }
4481
+ }
4482
+ if (paraFirst) {
4483
+ suppressMidReset = false;
4484
+ startPage = cur;
4485
+ }
4486
+ paraFirst = false;
4487
+ prevV = v;
4488
+ prevH = h;
4489
+ }
4490
+ }
4491
+ if (r.tagId === TAG_CTRL_HEADER && r.level === 1 && r.data.length >= 4 && r.data.readUInt32LE(0) === CTRL_ID_TBL) {
4492
+ let ce = k + 1;
4493
+ while (ce < j && records[ce].level > 1) ce++;
4494
+ intraAdd += tableIntraBreaks2(records, k + 1, ce);
4495
+ k = ce - 1;
4496
+ }
4497
+ }
4498
+ if (hadSegs) parasWithSegs++;
4499
+ pageAtPara.push(startPage);
4500
+ first = false;
4501
+ if (intraAdd > 0) {
4502
+ cur += intraAdd;
4503
+ suppressMidReset = true;
4504
+ }
4505
+ i = j;
4506
+ }
4507
+ return {
4508
+ pageAtPara,
4509
+ pages: cur + 1,
4510
+ usable: pageAtPara.length > 0 && parasWithSegs === pageAtPara.length
4511
+ };
4512
+ }
4513
+
4247
4514
  // src/hwp5/parser.ts
4248
4515
  import { createRequire } from "module";
4249
4516
  var require2 = createRequire(import.meta.url);
@@ -4369,31 +4636,63 @@ function parseHwp5Document(buffer, options) {
4369
4636
  const docInfo = cfb && !encrypted ? parseDocInfoStream(cfb, compressed) : parseDocInfoFromStream(findStream("/DocInfo"), compressed);
4370
4637
  const sections = distribution ? cfb ? findViewTextSections(cfb, compressed) : findViewTextSectionsLenient(lenientCfb, compressed) : encrypted ? findSectionsVia(findStream) : cfb ? findSections(cfb) : findSectionsLenient(lenientCfb, compressed);
4371
4638
  if (sections.length === 0) throw new KordocError("\uC139\uC158 \uC2A4\uD2B8\uB9BC\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
4372
- metadata.pageCount = sections.length;
4373
- const pageFilter = options?.pages ? parsePageRange(options.pages, sections.length) : null;
4374
- const totalTarget = pageFilter ? pageFilter.size : sections.length;
4375
- const bodyBlocks = [];
4376
- const doc = createHwp5DocState();
4377
- doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
4639
+ const sectionRecords = [];
4640
+ const sectionDetects = [];
4378
4641
  let totalDecompressed = 0;
4379
- let parsedSections = 0;
4380
4642
  for (let si = 0; si < sections.length; si++) {
4381
- if (pageFilter && !pageFilter.has(si + 1)) continue;
4382
4643
  try {
4383
- const sectionData = sections[si];
4384
- const data = !distribution && compressed ? decompressStream(Buffer.from(sectionData)) : Buffer.from(sectionData);
4644
+ const data = !distribution && compressed ? decompressStream(Buffer.from(sections[si])) : Buffer.from(sections[si]);
4385
4645
  totalDecompressed += data.length;
4386
4646
  if (totalDecompressed > MAX_TOTAL_DECOMPRESS) throw new KordocError("\uCD1D \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (decompression bomb \uC758\uC2EC)");
4387
4647
  const records = readRecords(data);
4388
- const sectionBlocks = parseSection(records, docInfo, warnings, si + 1, doc);
4648
+ sectionRecords.push(records);
4649
+ sectionDetects.push(detectHwp5SectionPages(records));
4650
+ } catch (secErr) {
4651
+ if (secErr instanceof KordocError) throw secErr;
4652
+ sectionRecords.push(null);
4653
+ sectionDetects.push(null);
4654
+ 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" });
4655
+ }
4656
+ }
4657
+ const layoutPages = sectionDetects.length > 0 && sectionDetects.every((d) => d != null && d.usable);
4658
+ metadata.pageMode = layoutPages ? "layout" : "section";
4659
+ metadata.pageCount = layoutPages ? Math.max(sectionDetects.reduce((sum, d) => sum + (d?.pages ?? 0), 0), 1) : sections.length;
4660
+ const pageFilter = options?.pages ? parsePageRange(options.pages, metadata.pageCount) : null;
4661
+ if (pageFilter && !layoutPages) {
4662
+ 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" });
4663
+ }
4664
+ let bodyBlocks = [];
4665
+ const doc = createHwp5DocState();
4666
+ doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
4667
+ let parsedSections = 0;
4668
+ let pageBase = 0;
4669
+ for (let si = 0; si < sections.length; si++) {
4670
+ const records = sectionRecords[si];
4671
+ if (!records) continue;
4672
+ const detect = sectionDetects[si];
4673
+ if (!layoutPages && pageFilter && !pageFilter.has(si + 1)) continue;
4674
+ try {
4675
+ const sectionBlocks = parseSection(
4676
+ records,
4677
+ docInfo,
4678
+ warnings,
4679
+ si + 1,
4680
+ doc,
4681
+ layoutPages ? { base: pageBase, pageAtPara: detect.pageAtPara } : void 0
4682
+ );
4389
4683
  bodyBlocks.push(...sectionBlocks);
4390
4684
  parsedSections++;
4391
- options?.onProgress?.(parsedSections, totalTarget);
4685
+ options?.onProgress?.(parsedSections, sections.length);
4392
4686
  } catch (secErr) {
4393
4687
  if (secErr instanceof KordocError) throw secErr;
4394
4688
  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" });
4689
+ } finally {
4690
+ pageBase += detect.pages;
4395
4691
  }
4396
4692
  }
4693
+ if (pageFilter && layoutPages) {
4694
+ bodyBlocks = bodyBlocks.filter((b) => b.pageNumber != null && pageFilter.has(b.pageNumber));
4695
+ }
4397
4696
  const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
4398
4697
  const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
4399
4698
  let flatBlocks = flattenLayoutTables(blocks);
@@ -4630,8 +4929,17 @@ function createHwp5DocState() {
4630
4929
  footerBlocks: []
4631
4930
  };
4632
4931
  }
4633
- function parseSection(records, docInfo, warnings, sectionNum, doc) {
4634
- const ctx = { docInfo, warnings, sectionNum, doc: doc ?? createHwp5DocState(), depth: 0 };
4932
+ function parseSection(records, docInfo, warnings, sectionNum, doc, pageMap) {
4933
+ const ctx = {
4934
+ docInfo,
4935
+ warnings,
4936
+ sectionNum,
4937
+ doc: doc ?? createHwp5DocState(),
4938
+ depth: 0,
4939
+ page: pageMap ? pageMap.base + 1 : sectionNum,
4940
+ pageMap,
4941
+ topOrdinal: 0
4942
+ };
4635
4943
  return parseParagraphList(records, 0, records.length, ctx);
4636
4944
  }
4637
4945
  function parseParagraphList(records, start, end, ctx) {
@@ -4640,6 +4948,11 @@ function parseParagraphList(records, start, end, ctx) {
4640
4948
  while (i < end) {
4641
4949
  if (records[i].tagId === TAG_PARA_HEADER) {
4642
4950
  const baseLevel = records[i].level;
4951
+ if (baseLevel === 0 && ctx.depth === 0 && ctx.pageMap) {
4952
+ const pg = ctx.pageMap.pageAtPara[ctx.topOrdinal];
4953
+ if (pg !== void 0) ctx.page = ctx.pageMap.base + pg + 1;
4954
+ ctx.topOrdinal++;
4955
+ }
4643
4956
  let j = i + 1;
4644
4957
  while (j < end && records[j].level > baseLevel) j++;
4645
4958
  blocks.push(...parseParagraph(records, i, j, ctx));
@@ -4740,7 +5053,7 @@ function parseParagraph(records, start, end, ctx) {
4740
5053
  const block = {
4741
5054
  type: headingLevel > 0 ? "heading" : "paragraph",
4742
5055
  text: headMarker ? `${headMarker} ${trimmed}` : trimmed,
4743
- pageNumber: ctx.sectionNum
5056
+ pageNumber: ctx.page
4744
5057
  };
4745
5058
  if (headingLevel > 0) block.level = headingLevel;
4746
5059
  if (ctx.docInfo && charShapeIds.length > 0) {
@@ -4757,7 +5070,7 @@ function parseParagraph(records, start, end, ctx) {
4757
5070
  if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
4758
5071
  blocks.push(block);
4759
5072
  } else if (footnotes.length > 0) {
4760
- blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber: ctx.sectionNum });
5073
+ blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber: ctx.page });
4761
5074
  }
4762
5075
  for (const ctrl of ctrls) {
4763
5076
  if (ctrl.afterBlocks) blocks.push(...ctrl.afterBlocks);
@@ -4768,7 +5081,7 @@ function applyCtrlEffect(ctrl, records, ctx) {
4768
5081
  switch (ctrl.id) {
4769
5082
  case CTRL_TBL: {
4770
5083
  const table2 = parseTableControl(ctrl, records, ctx);
4771
- if (table2) ctrl.afterBlocks = [{ type: "table", table: table2, pageNumber: ctx.sectionNum }];
5084
+ if (table2) ctrl.afterBlocks = [{ type: "table", table: table2, pageNumber: ctx.page }];
4772
5085
  return;
4773
5086
  }
4774
5087
  case CTRL_GSO: {
@@ -4820,7 +5133,7 @@ function applyCtrlEffect(ctrl, records, ctx) {
4820
5133
  return;
4821
5134
  }
4822
5135
  case CTRL_OLE: {
4823
- ctx.warnings.push({ page: ctx.sectionNum, message: "\uC2A4\uD0B5\uB41C OLE \uAC1C\uCCB4", code: "SKIPPED_OLE" });
5136
+ ctx.warnings.push({ page: ctx.page, message: "\uC2A4\uD0B5\uB41C OLE \uAC1C\uCCB4", code: "SKIPPED_OLE" });
4824
5137
  return;
4825
5138
  }
4826
5139
  // 숨은 설명/단 정의/쪽번호 위치/감추기/찾아보기/책갈피/글자겹침/덧말 — 본문 텍스트 없음 또는 의도적 스킵
@@ -4897,7 +5210,7 @@ function applyHeaderFooterEffect(ctrl, records, ctx, isHeader) {
4897
5210
  const key = (isHeader ? "h:" : "f:") + text;
4898
5211
  if (ctx.doc.headerTexts.has(key)) return;
4899
5212
  ctx.doc.headerTexts.add(key);
4900
- const block = { type: "paragraph", text, pageNumber: ctx.sectionNum };
5213
+ const block = { type: "paragraph", text, pageNumber: ctx.page };
4901
5214
  if (isHeader) ctx.doc.headerBlocks.push(block);
4902
5215
  else ctx.doc.footerBlocks.push(block);
4903
5216
  }
@@ -5119,11 +5432,11 @@ function pictureToImageBlock(data, ctx) {
5119
5432
  if (binDataId === 0) return null;
5120
5433
  const item = ctx.docInfo?.binData[binDataId - 1];
5121
5434
  if (item?.kind === "link") {
5122
- ctx.warnings.push({ page: ctx.sectionNum, message: `\uC678\uBD80 \uC5F0\uACB0 \uC774\uBBF8\uC9C0 (binDataId ${binDataId})`, code: "SKIPPED_IMAGE" });
5435
+ ctx.warnings.push({ page: ctx.page, message: `\uC678\uBD80 \uC5F0\uACB0 \uC774\uBBF8\uC9C0 (binDataId ${binDataId})`, code: "SKIPPED_IMAGE" });
5123
5436
  return null;
5124
5437
  }
5125
5438
  const storageId = item && item.storageId > 0 ? item.storageId : binDataId;
5126
- return { type: "image", text: String(storageId), pageNumber: ctx.sectionNum };
5439
+ return { type: "image", text: String(storageId), pageNumber: ctx.page };
5127
5440
  }
5128
5441
  function resolveCharStyle(charShapeIds, docInfo) {
5129
5442
  if (charShapeIds.length === 0 || docInfo.charShapes.length === 0) return void 0;
@@ -19014,8 +19327,8 @@ function isDisplayMath(el) {
19014
19327
 
19015
19328
  // src/docx/parser.ts
19016
19329
  var MAX_DECOMPRESS_SIZE5 = 100 * 1024 * 1024;
19017
- function matchesLocal(el, localName2) {
19018
- return el.localName === localName2 || (el.tagName?.endsWith(`:${localName2}`) ?? false);
19330
+ function matchesLocal(el, localName3) {
19331
+ return el.localName === localName3 || (el.tagName?.endsWith(`:${localName3}`) ?? false);
19019
19332
  }
19020
19333
  function effectiveChildElements(parent) {
19021
19334
  const result = [];
@@ -19039,10 +19352,10 @@ function effectiveChildElements(parent) {
19039
19352
  }
19040
19353
  return result;
19041
19354
  }
19042
- function getChildElements(parent, localName2) {
19043
- return effectiveChildElements(parent).filter((el) => matchesLocal(el, localName2));
19355
+ function getChildElements(parent, localName3) {
19356
+ return effectiveChildElements(parent).filter((el) => matchesLocal(el, localName3));
19044
19357
  }
19045
- function findElements(parent, localName2) {
19358
+ function findElements(parent, localName3) {
19046
19359
  const result = [];
19047
19360
  const walk = (node) => {
19048
19361
  const children = node.childNodes;
@@ -19050,7 +19363,7 @@ function findElements(parent, localName2) {
19050
19363
  const child = children[i];
19051
19364
  if (child.nodeType === 1) {
19052
19365
  const el = child;
19053
- if (el.localName === localName2 || el.tagName?.endsWith(`:${localName2}`)) {
19366
+ if (el.localName === localName3 || el.tagName?.endsWith(`:${localName3}`)) {
19054
19367
  result.push(el);
19055
19368
  }
19056
19369
  walk(el);
@@ -19060,11 +19373,11 @@ function findElements(parent, localName2) {
19060
19373
  walk(parent);
19061
19374
  return result;
19062
19375
  }
19063
- function getAttr(el, localName2) {
19376
+ function getAttr(el, localName3) {
19064
19377
  const attrs = el.attributes;
19065
19378
  for (let i = 0; i < attrs.length; i++) {
19066
19379
  const attr = attrs[i];
19067
- if (attr.localName === localName2 || attr.name === localName2) return attr.value;
19380
+ if (attr.localName === localName3 || attr.name === localName3) return attr.value;
19068
19381
  }
19069
19382
  return null;
19070
19383
  }
@@ -19583,8 +19896,8 @@ async function parseDocxDocument(buffer, options) {
19583
19896
  const bodyEl = body[0];
19584
19897
  const topLevel = effectiveChildElements(bodyEl);
19585
19898
  for (const el of topLevel) {
19586
- const localName2 = el.localName ?? el.tagName?.split(":").pop();
19587
- if (localName2 === "p") {
19899
+ const localName3 = el.localName ?? el.tagName?.split(":").pop();
19900
+ if (localName3 === "p") {
19588
19901
  const block = parseParagraph2(el, styles, numbering, footnotes, rels);
19589
19902
  if (block) blocks.push(block);
19590
19903
  if (imageMap.size > 0) emitParagraphImages(el, imageMap, linkedImages, blocks);
@@ -19593,7 +19906,7 @@ async function parseDocxDocument(buffer, options) {
19593
19906
  if (tb) blocks.push(tb);
19594
19907
  if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
19595
19908
  }
19596
- } else if (localName2 === "tbl") {
19909
+ } else if (localName3 === "tbl") {
19597
19910
  const block = parseTable(el, styles, numbering, footnotes, rels, options?.keepTrailingEmptyCols);
19598
19911
  if (block) blocks.push(block);
19599
19912
  }
@@ -28676,7 +28989,7 @@ var Surgeon = class {
28676
28989
  // src/roundtrip/hwp5-patch.ts
28677
28990
  var require3 = createRequire2(import.meta.url);
28678
28991
  var CFB2 = require3("cfb");
28679
- var TAG_PARA_LINE_SEG = 69;
28992
+ var TAG_PARA_LINE_SEG2 = 69;
28680
28993
  function cid2(s) {
28681
28994
  return (s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3)) >>> 0;
28682
28995
  }
@@ -28757,7 +29070,7 @@ function scanSection(stream, sectionIndex, compressed) {
28757
29070
  textIdx = textIdx === -1 ? j : -2;
28758
29071
  appendParaText(state, records[j].data);
28759
29072
  } else if (t === TAG_CHAR_SHAPE && charShapeIdx === -1) charShapeIdx = j;
28760
- else if (t === TAG_PARA_LINE_SEG && lineSegIdx === -1) lineSegIdx = j;
29073
+ else if (t === TAG_PARA_LINE_SEG2 && lineSegIdx === -1) lineSegIdx = j;
28761
29074
  }
28762
29075
  let ctrlSeen = false, nonGso = false;
28763
29076
  for (let a = parent[i]; a >= 0; a = parent[a]) {
@@ -31324,7 +31637,7 @@ async function parse(input, options) {
31324
31637
  }
31325
31638
  async function parseImage(buffer, options) {
31326
31639
  try {
31327
- const { parseImageDocument } = await import("./image-ocr-TAJHRZ5T.js");
31640
+ const { parseImageDocument } = await import("./image-ocr-XHBZONFZ.js");
31328
31641
  const { blocks, warnings } = await parseImageDocument(buffer, options);
31329
31642
  return {
31330
31643
  success: true,
@@ -31383,7 +31696,7 @@ async function parseHwp(buffer, options) {
31383
31696
  async function parsePdf(buffer, options) {
31384
31697
  let parsePdfDocument;
31385
31698
  try {
31386
- const mod = await import("./parser-RCEHB2EV.js");
31699
+ const mod = await import("./parser-Z36MZUHS.js");
31387
31700
  parsePdfDocument = mod.parsePdfDocument;
31388
31701
  } catch {
31389
31702
  return {