kordoc 4.14.3 → 4.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +30 -4
  2. package/dist/{-7XBPXCXN.js → -WXTUDCBW.js} +11 -11
  3. package/dist/{chunk-UVVEBI22.js → chunk-23AJ65GX.js} +707 -62
  4. package/dist/chunk-23AJ65GX.js.map +1 -0
  5. package/dist/chunk-2FT2DHE6.js +2400 -0
  6. package/dist/chunk-2FT2DHE6.js.map +1 -0
  7. package/dist/{chunk-EBZUZC6F.cjs → chunk-3CWXYYCL.cjs} +38 -30
  8. package/dist/chunk-3CWXYYCL.cjs.map +1 -0
  9. package/dist/{chunk-A275ZQLX.js → chunk-4OLS5RDH.js} +707 -62
  10. package/dist/chunk-4OLS5RDH.js.map +1 -0
  11. package/dist/{chunk-NQCUAGLR.js → chunk-6BWU6SQX.js} +1 -1
  12. package/dist/{chunk-2LQGSDZ6.js.map → chunk-6BWU6SQX.js.map} +1 -1
  13. package/dist/{chunk-2LQGSDZ6.js → chunk-6CUIXJOC.js} +1 -1
  14. package/dist/{chunk-NQCUAGLR.js.map → chunk-6CUIXJOC.js.map} +1 -1
  15. package/dist/{chunk-L5EH2NVO.js → chunk-6Y6KUOA6.js} +14 -3
  16. package/dist/chunk-6Y6KUOA6.js.map +1 -0
  17. package/dist/{chunk-QGW7NMTX.js → chunk-AKWMQ5XT.js} +17 -9
  18. package/dist/chunk-AKWMQ5XT.js.map +1 -0
  19. package/dist/{chunk-O2PGQNYE.js → chunk-B3NAP4FA.js} +2 -2
  20. package/dist/{chunk-CVEERPMP.js → chunk-DTM6FV74.js} +1727 -1680
  21. package/dist/chunk-DTM6FV74.js.map +1 -0
  22. package/dist/{chunk-IW2CB755.cjs → chunk-EP4DTCVJ.cjs} +392 -42
  23. package/dist/chunk-EP4DTCVJ.cjs.map +1 -0
  24. package/dist/{chunk-5VVS2QIJ.cjs → chunk-FSQJJTHS.cjs} +3 -3
  25. package/dist/{chunk-5VVS2QIJ.cjs.map → chunk-FSQJJTHS.cjs.map} +1 -1
  26. package/dist/{chunk-D6KQKMU3.js → chunk-H5XLUEZG.js} +2 -2
  27. package/dist/{chunk-GTINVM5K.js → chunk-IMR5HSB5.js} +2 -2
  28. package/dist/{chunk-FAZR34CA.js → chunk-KNZUG3SV.js} +2 -2
  29. package/dist/{chunk-I4NNYWIR.js → chunk-KO5S25RD.js} +23 -3
  30. package/dist/chunk-KO5S25RD.js.map +1 -0
  31. package/dist/{chunk-EMGF6EEV.cjs → chunk-LBYKEJUJ.cjs} +17 -6
  32. package/dist/chunk-LBYKEJUJ.cjs.map +1 -0
  33. package/dist/{chunk-VYOGXNQ3.js → chunk-M26GSDV3.js} +16 -8
  34. package/dist/chunk-M26GSDV3.js.map +1 -0
  35. package/dist/{chunk-IHZEFW4L.js → chunk-MX36AA26.js} +2 -2
  36. package/dist/{chunk-4R67INKT.js → chunk-NJP76IZ5.js} +389 -39
  37. package/dist/chunk-NJP76IZ5.js.map +1 -0
  38. package/dist/{chunk-YGBZABI4.js → chunk-PJ2K6B3V.js} +78 -67
  39. package/dist/chunk-PJ2K6B3V.js.map +1 -0
  40. package/dist/{chunk-VNFJPTU4.js → chunk-QMUM25RB.js} +2 -2
  41. package/dist/{chunk-VJHAUMCF.js → chunk-S5PJ7ZE2.js} +389 -39
  42. package/dist/chunk-S5PJ7ZE2.js.map +1 -0
  43. package/dist/{chunk-JCVIOLBF.js → chunk-TDYGL267.js} +11 -13
  44. package/dist/{chunk-JCVIOLBF.js.map → chunk-TDYGL267.js.map} +1 -1
  45. package/dist/{chunk-WB5LW52Z.js → chunk-UCDOIHGW.js} +2 -2
  46. package/dist/{chunk-HX4UUF7Y.cjs → chunk-VWUYVEFN.cjs} +1 -1
  47. package/dist/{chunk-HX4UUF7Y.cjs.map → chunk-VWUYVEFN.cjs.map} +1 -1
  48. package/dist/{chunk-NOAQRCV6.cjs → chunk-W33QEDLY.cjs} +730 -85
  49. package/dist/chunk-W33QEDLY.cjs.map +1 -0
  50. package/dist/{chunk-4RJ5D7MT.js → chunk-YINJO3PR.js} +2 -2
  51. package/dist/chunks-S2XR4JDI.js +10 -0
  52. package/dist/cli.js +44 -45
  53. package/dist/cli.js.map +1 -1
  54. package/dist/{formula-CPO2TZGB.cjs → formula-RGK6WGD2.cjs} +9 -9
  55. package/dist/{formula-CPO2TZGB.cjs.map → formula-RGK6WGD2.cjs.map} +1 -1
  56. package/dist/{formula-X44GV7TN.js → formula-USQK6NHN.js} +3 -3
  57. package/dist/{formula-NOIDZEBA.js → formula-WGBYDRVU.js} +4 -4
  58. package/dist/{image-ocr-SUGT64ZM.cjs → image-ocr-JPP6JRFU.cjs} +13 -13
  59. package/dist/{image-ocr-SUGT64ZM.cjs.map → image-ocr-JPP6JRFU.cjs.map} +1 -1
  60. package/dist/{image-ocr-R6W6A75P.js → image-ocr-SLYPOW7G.js} +6 -6
  61. package/dist/{image-ocr-72RAR4WR.js → image-ocr-WCKYPPXY.js} +8 -8
  62. package/dist/index.cjs +4333 -2500
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +12 -19
  65. package/dist/index.d.ts +12 -19
  66. package/dist/index.js +3816 -1983
  67. package/dist/index.js.map +1 -1
  68. package/dist/mcp.js +46 -35
  69. package/dist/mcp.js.map +1 -1
  70. package/dist/{model-bundle-57CLEBCO.js → model-bundle-BIS5HNXA.js} +5 -5
  71. package/dist/{models-7ELIFTD7.js → models-APDLDMGJ.js} +5 -5
  72. package/dist/{parser-4NKAYWAX.js → parser-4D2YTTIH.js} +494 -50
  73. package/dist/parser-4D2YTTIH.js.map +1 -0
  74. package/dist/{parser-HWFQRSUQ.js → parser-6ZWJPQC2.js} +494 -50
  75. package/dist/parser-6ZWJPQC2.js.map +1 -0
  76. package/dist/{parser-RANGXGBD.cjs → parser-Y5NX55GC.cjs} +543 -99
  77. package/dist/parser-Y5NX55GC.cjs.map +1 -0
  78. package/dist/pdf-ocr-E3IZ3EI5.cjs +14 -0
  79. package/dist/{pdf-ocr-VHSFACH7.cjs.map → pdf-ocr-E3IZ3EI5.cjs.map} +1 -1
  80. package/dist/pdf-ocr-FNXYMNM6.js +16 -0
  81. package/dist/pdf-ocr-WSYPOBBU.js +13 -0
  82. package/dist/{profile-io-V52PL5ZO.js → profile-io-VJUZYOJA.js} +3 -3
  83. package/dist/{rasterize-ZIGLJDOG.js → rasterize-6U5NWILD.js} +3 -3
  84. package/dist/{redact-TIQEQI3M.js → redact-3CY3KGVD.js} +2 -2
  85. package/dist/{redact-doc-KXZ3TZAG.js → redact-doc-H56QYJGQ.js} +98 -30
  86. package/dist/redact-doc-H56QYJGQ.js.map +1 -0
  87. package/dist/{render-U5PE6V2E.js → render-HNMDLMI2.js} +7 -7
  88. package/dist/seal-VXNEOHZV.js +10 -0
  89. package/dist/{setup-YRDM4ODP.js → setup-RZVYAXAV.js} +3 -3
  90. package/dist/visual-NSZP2FWZ.js +19 -0
  91. package/dist/{watch-5IOLBATT.js → watch-VA6GK56C.js} +46 -23
  92. package/dist/watch-VA6GK56C.js.map +1 -0
  93. package/package.json +2 -2
  94. package/dist/chunk-4R67INKT.js.map +0 -1
  95. package/dist/chunk-A275ZQLX.js.map +0 -1
  96. package/dist/chunk-CVEERPMP.js.map +0 -1
  97. package/dist/chunk-EBZUZC6F.cjs.map +0 -1
  98. package/dist/chunk-EMGF6EEV.cjs.map +0 -1
  99. package/dist/chunk-I4NNYWIR.js.map +0 -1
  100. package/dist/chunk-IH56ZISC.js +0 -620
  101. package/dist/chunk-IH56ZISC.js.map +0 -1
  102. package/dist/chunk-IW2CB755.cjs.map +0 -1
  103. package/dist/chunk-L5EH2NVO.js.map +0 -1
  104. package/dist/chunk-NOAQRCV6.cjs.map +0 -1
  105. package/dist/chunk-QGW7NMTX.js.map +0 -1
  106. package/dist/chunk-UVVEBI22.js.map +0 -1
  107. package/dist/chunk-VJHAUMCF.js.map +0 -1
  108. package/dist/chunk-VYOGXNQ3.js.map +0 -1
  109. package/dist/chunk-YGBZABI4.js.map +0 -1
  110. package/dist/chunks-I7TEMJXH.js +0 -10
  111. package/dist/parser-4NKAYWAX.js.map +0 -1
  112. package/dist/parser-HWFQRSUQ.js.map +0 -1
  113. package/dist/parser-RANGXGBD.cjs.map +0 -1
  114. package/dist/pdf-ocr-BNC34MNU.js +0 -13
  115. package/dist/pdf-ocr-QX2GB33Q.js +0 -16
  116. package/dist/pdf-ocr-VHSFACH7.cjs +0 -14
  117. package/dist/redact-doc-KXZ3TZAG.js.map +0 -1
  118. package/dist/seal-DNXJBILR.js +0 -10
  119. package/dist/visual-5XWMA4EV.js +0 -19
  120. package/dist/watch-5IOLBATT.js.map +0 -1
  121. /package/dist/{-7XBPXCXN.js.map → -WXTUDCBW.js.map} +0 -0
  122. /package/dist/{chunk-O2PGQNYE.js.map → chunk-B3NAP4FA.js.map} +0 -0
  123. /package/dist/{chunk-D6KQKMU3.js.map → chunk-H5XLUEZG.js.map} +0 -0
  124. /package/dist/{chunk-GTINVM5K.js.map → chunk-IMR5HSB5.js.map} +0 -0
  125. /package/dist/{chunk-FAZR34CA.js.map → chunk-KNZUG3SV.js.map} +0 -0
  126. /package/dist/{chunk-IHZEFW4L.js.map → chunk-MX36AA26.js.map} +0 -0
  127. /package/dist/{chunk-VNFJPTU4.js.map → chunk-QMUM25RB.js.map} +0 -0
  128. /package/dist/{chunk-WB5LW52Z.js.map → chunk-UCDOIHGW.js.map} +0 -0
  129. /package/dist/{chunk-4RJ5D7MT.js.map → chunk-YINJO3PR.js.map} +0 -0
  130. /package/dist/{chunks-I7TEMJXH.js.map → chunks-S2XR4JDI.js.map} +0 -0
  131. /package/dist/{formula-X44GV7TN.js.map → formula-USQK6NHN.js.map} +0 -0
  132. /package/dist/{formula-NOIDZEBA.js.map → formula-WGBYDRVU.js.map} +0 -0
  133. /package/dist/{image-ocr-R6W6A75P.js.map → image-ocr-SLYPOW7G.js.map} +0 -0
  134. /package/dist/{image-ocr-72RAR4WR.js.map → image-ocr-WCKYPPXY.js.map} +0 -0
  135. /package/dist/{model-bundle-57CLEBCO.js.map → model-bundle-BIS5HNXA.js.map} +0 -0
  136. /package/dist/{models-7ELIFTD7.js.map → models-APDLDMGJ.js.map} +0 -0
  137. /package/dist/{pdf-ocr-BNC34MNU.js.map → pdf-ocr-FNXYMNM6.js.map} +0 -0
  138. /package/dist/{pdf-ocr-QX2GB33Q.js.map → pdf-ocr-WSYPOBBU.js.map} +0 -0
  139. /package/dist/{profile-io-V52PL5ZO.js.map → profile-io-VJUZYOJA.js.map} +0 -0
  140. /package/dist/{rasterize-ZIGLJDOG.js.map → rasterize-6U5NWILD.js.map} +0 -0
  141. /package/dist/{redact-TIQEQI3M.js.map → redact-3CY3KGVD.js.map} +0 -0
  142. /package/dist/{render-U5PE6V2E.js.map → render-HNMDLMI2.js.map} +0 -0
  143. /package/dist/{seal-DNXJBILR.js.map → seal-VXNEOHZV.js.map} +0 -0
  144. /package/dist/{setup-YRDM4ODP.js.map → setup-RZVYAXAV.js.map} +0 -0
  145. /package/dist/{visual-5XWMA4EV.js.map → visual-NSZP2FWZ.js.map} +0 -0
@@ -2,11 +2,11 @@ import {
2
2
  HEADING_RATIO_H1,
3
3
  HEADING_RATIO_H2,
4
4
  HEADING_RATIO_H3
5
- } from "./chunk-2LQGSDZ6.js";
5
+ } from "./chunk-6CUIXJOC.js";
6
6
  import {
7
7
  safeMax,
8
8
  safeMin
9
- } from "./chunk-L5EH2NVO.js";
9
+ } from "./chunk-6Y6KUOA6.js";
10
10
 
11
11
  // src/pdf/image-regions.ts
12
12
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -68,6 +68,8 @@ var MAX_LINE_WIDTH = 5;
68
68
  function extractLines(fnArray, argsArray) {
69
69
  const horizontals = [];
70
70
  const verticals = [];
71
+ const shortH = [];
72
+ const shortV = [];
71
73
  const clipRects = [];
72
74
  const fillRects = [];
73
75
  let pathRects = [];
@@ -116,7 +118,7 @@ function extractLines(fnArray, argsArray) {
116
118
  pendingClip = false;
117
119
  const effWidth = lineWidth * ctmScale();
118
120
  for (const seg of currentPath) {
119
- classifyAndAdd(seg, effWidth, horizontals, verticals, fromFill);
121
+ classifyAndAdd(seg, effWidth, horizontals, verticals, fromFill, fromFill ? void 0 : { h: shortH, v: shortV });
120
122
  }
121
123
  currentPath = [];
122
124
  }
@@ -247,7 +249,56 @@ function extractLines(fnArray, argsArray) {
247
249
  break;
248
250
  }
249
251
  }
250
- return { horizontals, verticals, clipRects, fillRects };
252
+ return { horizontals, verticals, clipRects, fillRects, shortH, shortV };
253
+ }
254
+ var SHORT_CHAIN_POS_TOL = 0.5;
255
+ var SHORT_CHAIN_GAP = 0.2;
256
+ function chainShortSegments(longs, shorts, dir) {
257
+ if (shorts.length === 0) return longs;
258
+ const pos = (l) => dir === "h" ? l.y1 : l.x1;
259
+ const lo = (l) => dir === "h" ? l.x1 : l.y1;
260
+ const hi = (l) => dir === "h" ? l.x2 : l.y2;
261
+ const shortSet = new Set(shorts);
262
+ const all = [...longs.filter((l) => !l.fromFill), ...shorts].sort((a, b) => pos(a) - pos(b) || lo(a) - lo(b));
263
+ const absorbed = /* @__PURE__ */ new Set();
264
+ const chained = [];
265
+ const flush = (chain) => {
266
+ if (!chain.some((l) => shortSet.has(l))) return;
267
+ let s = Infinity, e = -Infinity, p = 0, w = 0;
268
+ for (const l of chain) {
269
+ s = Math.min(s, lo(l));
270
+ e = Math.max(e, hi(l));
271
+ p += pos(l);
272
+ w = Math.max(w, l.lineWidth);
273
+ }
274
+ if (e - s < MIN_LINE_LENGTH) return;
275
+ p /= chain.length;
276
+ for (const l of chain) absorbed.add(l);
277
+ chained.push(dir === "h" ? { x1: s, y1: p, x2: e, y2: p, lineWidth: w } : { x1: p, y1: s, x2: p, y2: e, lineWidth: w });
278
+ };
279
+ let band = [];
280
+ const flushBand = () => {
281
+ band.sort((a, b) => lo(a) - lo(b));
282
+ let chain = [];
283
+ let end = -Infinity;
284
+ for (const l of band) {
285
+ if (chain.length && lo(l) > end + SHORT_CHAIN_GAP) {
286
+ flush(chain);
287
+ chain = [];
288
+ }
289
+ chain.push(l);
290
+ end = chain.length === 1 ? hi(l) : Math.max(end, hi(l));
291
+ }
292
+ if (chain.length) flush(chain);
293
+ band = [];
294
+ };
295
+ for (const l of all) {
296
+ if (band.length && pos(l) - pos(band[0]) > SHORT_CHAIN_POS_TOL) flushBand();
297
+ band.push(l);
298
+ }
299
+ if (band.length) flushBand();
300
+ if (chained.length === 0) return longs;
301
+ return [...longs.filter((l) => !absorbed.has(l)), ...chained];
251
302
  }
252
303
  var CLIP_MIN_W = 4;
253
304
  var CLIP_MIN_H = 2;
@@ -265,11 +316,21 @@ function captureClipRect(path, out, minW = CLIP_MIN_W, minH = CLIP_MIN_H) {
265
316
  if (x2 - x1 < minW || y2 - y1 < minH) return;
266
317
  out.push({ x1, y1, x2, y2 });
267
318
  }
268
- function classifyAndAdd(seg, lineWidth, horizontals, verticals, fromFill = false) {
319
+ function classifyAndAdd(seg, lineWidth, horizontals, verticals, fromFill = false, short) {
269
320
  const dx = Math.abs(seg.x2 - seg.x1);
270
321
  const dy = Math.abs(seg.y2 - seg.y1);
271
322
  const length = Math.sqrt(dx * dx + dy * dy);
272
- if (length < MIN_LINE_LENGTH) return;
323
+ if (length < MIN_LINE_LENGTH) {
324
+ if (!short || length <= 0) return;
325
+ if (dy <= ORIENTATION_TOL && dx > dy) {
326
+ const y = (seg.y1 + seg.y2) / 2;
327
+ short.h.push({ x1: Math.min(seg.x1, seg.x2), y1: y, x2: Math.max(seg.x1, seg.x2), y2: y, lineWidth });
328
+ } else if (dx <= ORIENTATION_TOL && dy > dx) {
329
+ const x = (seg.x1 + seg.x2) / 2;
330
+ short.v.push({ x1: x, y1: Math.min(seg.y1, seg.y2), x2: x, y2: Math.max(seg.y1, seg.y2), lineWidth });
331
+ }
332
+ return;
333
+ }
273
334
  if (dy <= ORIENTATION_TOL) {
274
335
  const y = (seg.y1 + seg.y2) / 2;
275
336
  const x1 = Math.min(seg.x1, seg.x2);
@@ -750,6 +811,23 @@ function buildTableGrids(horizontals, verticals) {
750
811
  }
751
812
  return mergeAdjacentGrids(grids);
752
813
  }
814
+ var SHADE_CLIP_TOL = 1.5;
815
+ var SHADE_CLIP_MAX_AREA = 0.5;
816
+ function dropShadingClipGrids(clipGrids, lineGrids, fillRects, verticals = []) {
817
+ if (clipGrids.length === 0 || lineGrids.length === 0 || fillRects.length === 0) return clipGrids;
818
+ const near = (a, b) => Math.abs(a - b) <= SHADE_CLIP_TOL;
819
+ const area = (b) => Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1);
820
+ const inter = (a, b) => area({ x1: Math.max(a.x1, b.x1), y1: Math.max(a.y1, b.y1), x2: Math.min(a.x2, b.x2), y2: Math.min(a.y2, b.y2) });
821
+ return clipGrids.filter((c) => {
822
+ if (!c.cells?.length || c.clipParent) return true;
823
+ const shaded = c.cells.every((cell) => fillRects.some((f) => near(f.x1, cell.bbox.x1) && near(f.x2, cell.bbox.x2) && near(f.y1, cell.bbox.y1) && near(f.y2, cell.bbox.y2)));
824
+ if (!shaded) return true;
825
+ const ruled = c.cells.every((cell) => cell.bbox.x2 >= c.bbox.x2 - SHADE_CLIP_TOL || verticals.some((v) => Math.abs(v.x1 - cell.bbox.x2) <= SHADE_CLIP_TOL * 2 && Math.min(v.y2, cell.bbox.y2) - Math.max(v.y1, cell.bbox.y1) >= (cell.bbox.y2 - cell.bbox.y1) * 0.75));
826
+ if (!ruled) return true;
827
+ const host = lineGrids.find((l) => c.bbox.x1 >= l.bbox.x1 - SHADE_CLIP_TOL && c.bbox.x2 <= l.bbox.x2 + SHADE_CLIP_TOL && c.bbox.y1 >= l.bbox.y1 - SHADE_CLIP_TOL && c.bbox.y2 <= l.bbox.y2 + SHADE_CLIP_TOL && c.colXs.every((x) => l.colXs.some((lx) => near(lx, x))) && clipGrids.reduce((s, o) => s + inter(o.bbox, l.bbox), 0) <= area(l.bbox) * SHADE_CLIP_MAX_AREA);
828
+ return !host;
829
+ });
830
+ }
753
831
  function enforceMinWidth(colXs, minWidth) {
754
832
  if (colXs.length <= 2) return colXs;
755
833
  const result = [colXs[0]];
@@ -972,6 +1050,7 @@ var CLIP_EDGE_TOL = 1.5;
972
1050
  var CLIP_ADJ_GAP = 0.15;
973
1051
  var CLIP_MIN_GROUP = 2;
974
1052
  var CLIP_MAX_PAGE_FRAC = 0.75;
1053
+ var STRADDLE_MIN = 0.1;
975
1054
  var TITLED_FRAME_MIN_TOP = 0.2;
976
1055
  var TITLED_FRAME_MIN_WIDTH = 0.6;
977
1056
  var HEADER_BAND = 0.08;
@@ -1023,7 +1102,8 @@ function edgeStroked(lines, dir, pos, a1, a2) {
1023
1102
  }
1024
1103
  return false;
1025
1104
  }
1026
- function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, textPoints = [], fillRects = []) {
1105
+ var CONT_X_TOL = 0.1;
1106
+ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, textPoints = [], fillRects = [], prev = { lastCells: [], clips: [] }) {
1027
1107
  const pageArea = pageWidth * pageHeight;
1028
1108
  const sameRect = (a, b) => Math.abs(a.x1 - b.x1) <= CLIP_EDGE_TOL && Math.abs(a.x2 - b.x2) <= CLIP_EDGE_TOL && Math.abs(a.y1 - b.y1) <= CLIP_EDGE_TOL && Math.abs(a.y2 - b.y2) <= CLIP_EDGE_TOL;
1029
1109
  const cells = [];
@@ -1032,16 +1112,24 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1032
1112
  if (pageArea > 0 && (r.x2 - r.x1) * (r.y2 - r.y1) >= pageArea * CLIP_MAX_PAGE_FRAC) continue;
1033
1113
  if (!cells.some((c) => sameRect(c, r))) cells.push(r);
1034
1114
  }
1035
- if (cells.length < 1) return { grids: [], containers: [] };
1115
+ if (cells.length < 1) return { grids: [], containers: [], page: { lastCells: [], clips: [] } };
1036
1116
  const parent = new Array(cells.length).fill(-1);
1037
1117
  const area = (r) => (r.x2 - r.x1) * (r.y2 - r.y1);
1118
+ const straddles = (a, b) => {
1119
+ const ix = overlap(a.x1, a.x2, b.x1, b.x2), iy = overlap(a.y1, a.y2, b.y1, b.y2);
1120
+ if (ix <= CLIP_EDGE_TOL || iy <= CLIP_EDGE_TOL || contains(a, b) || contains(b, a)) return false;
1121
+ const inside = ix * iy / area(b);
1122
+ return inside > STRADDLE_MIN && inside < 1 - STRADDLE_MIN;
1123
+ };
1124
+ const canParent = cells.map((a, i) => !cells.some((b, j) => i !== j && straddles(a, b)));
1038
1125
  for (let i = 0; i < cells.length; i++) {
1039
1126
  for (let j = 0; j < cells.length; j++) {
1040
- if (i === j || area(cells[j]) <= area(cells[i]) || !contains(cells[j], cells[i])) continue;
1127
+ if (i === j || !canParent[j] || area(cells[j]) <= area(cells[i]) || !contains(cells[j], cells[i])) continue;
1041
1128
  const cur = parent[i];
1042
1129
  if (cur < 0 || contains(cells[cur], cells[j])) parent[i] = j;
1043
1130
  }
1044
1131
  }
1132
+ const ruledGaps = findRuledGaps(cells, parent, strokedH, strokedV);
1045
1133
  const isContainer = new Array(cells.length).fill(false);
1046
1134
  for (const p of parent) if (p >= 0) isContainer[p] = true;
1047
1135
  const tileParent = new Array(cells.length).fill(false);
@@ -1068,7 +1156,7 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1068
1156
  const effParent = (i) => {
1069
1157
  let p = parent[i];
1070
1158
  for (let steps = 0; p >= 0 && steps <= cells.length; steps++) {
1071
- if (!tableClip[p] && (isGridMember(p) || loneFrame(p))) return p;
1159
+ if (!tableClip[p] && (isGridMember(p) || loneFrame(p) || continues(p))) return p;
1072
1160
  p = parent[p];
1073
1161
  }
1074
1162
  return -1;
@@ -1091,6 +1179,11 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1091
1179
  }
1092
1180
  }
1093
1181
  }
1182
+ for (const g of ruledGaps) {
1183
+ if (tableClip[g.i] || tableClip[g.j]) continue;
1184
+ const ra = find(g.i), rb = find(g.j);
1185
+ if (ra !== rb) root[ra] = rb;
1186
+ }
1094
1187
  const groups = /* @__PURE__ */ new Map();
1095
1188
  for (let i = 0; i < cells.length; i++) {
1096
1189
  const r = find(i);
@@ -1104,6 +1197,17 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1104
1197
  const framed = (r) => edgeStroked(strokedH, "h", r.y1, r.x1, r.x2) && edgeStroked(strokedH, "h", r.y2, r.x1, r.x2) && edgeStroked(strokedV, "v", r.x1, r.y1, r.y2) && edgeStroked(strokedV, "v", r.x2, r.y1, r.y2);
1105
1198
  const titledFrame = (r) => r.y1 >= pageHeight * TITLED_FRAME_MIN_TOP && r.x2 - r.x1 >= pageWidth * TITLED_FRAME_MIN_WIDTH && textPoints.some((p) => p.y < r.y1 && p.y > pageHeight * HEADER_BAND && p.x >= r.x1 - CLIP_EDGE_TOL && p.x <= r.x2 + CLIP_EDGE_TOL) && textPoints.some((p) => p.x > r.x1 && p.x < r.x2 && p.y > r.y1 && p.y < r.y2);
1106
1199
  const loneFrame = (i) => isContainer[i] && !isGridMember(i) && (framed(cells[i]) || titledFrame(cells[i]));
1200
+ const headY = pageHeight * (1 - HEADER_BAND);
1201
+ const continues = (i) => {
1202
+ if (!prev.lastCells.length || parent[i] >= 0 || tableClip[i] || isGridMember(i)) return void 0;
1203
+ const c = cells[i];
1204
+ const from = prev.lastCells.find((b) => Math.abs(b.x1 - c.x1) <= CONT_X_TOL && Math.abs(b.x2 - c.x2) <= CONT_X_TOL);
1205
+ if (!from) return void 0;
1206
+ if (prev.clips.some((k) => sameRect(k, c) && !sameRect(k, from))) return void 0;
1207
+ if (textPoints.some((p) => p.y > c.y2 && p.y < headY)) return void 0;
1208
+ if (cells.some((o) => o.y1 >= c.y2 - CLIP_EDGE_TOL && (o.y1 + o.y2) / 2 < headY)) return void 0;
1209
+ return from;
1210
+ };
1107
1211
  const parentAttachable = (i) => effParent(i) >= 0;
1108
1212
  const onlyChild = (i) => parent[i] >= 0 && parent.filter((p) => p === parent[i]).length === 1;
1109
1213
  const grids = [];
@@ -1112,7 +1216,8 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1112
1216
  if (tableClip[first]) continue;
1113
1217
  const parentRect = parentAttachable(first) ? cells[effParent(first)] : void 0;
1114
1218
  if (idxs.length < CLIP_MIN_GROUP) {
1115
- if (!loneFrame(first) && !((parentRect || onlyChild(first)) && framed(cells[first]))) continue;
1219
+ const from = continues(first);
1220
+ if (!from && !loneFrame(first) && !((parentRect || onlyChild(first)) && framed(cells[first]))) continue;
1116
1221
  const r = cells[first];
1117
1222
  grids.push({
1118
1223
  rowYs: [r.y2, r.y1],
@@ -1120,11 +1225,13 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1120
1225
  bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 },
1121
1226
  vertexRadius: 1,
1122
1227
  cells: [{ row: 0, col: 0, rowSpan: 1, colSpan: 1, bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 } }],
1123
- ...parentRect ? { clipParent: parentRect } : {}
1228
+ ...parentRect ? { clipParent: parentRect } : {},
1229
+ ...from ? { continues: from } : {}
1124
1230
  });
1125
1231
  continue;
1126
1232
  }
1127
- const members = idxs.map((i) => cells[i]);
1233
+ const gs = ruledGaps.filter((g) => find(g.i) === find(first));
1234
+ const members = gs.length ? closeGaps(idxs.map((i) => cells[i]), gs) : idxs.map((i) => cells[i]);
1128
1235
  let edges = 0, invisible = 0;
1129
1236
  for (const r of members) {
1130
1237
  edges += 4;
@@ -1169,7 +1276,130 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1169
1276
  ...parentRect ? { clipParent: parentRect } : {}
1170
1277
  });
1171
1278
  }
1172
- return { grids, containers };
1279
+ const tops = grids.filter((g) => !g.clipParent).flatMap((g) => g.cells.filter((c) => !c.filler).map((c) => c.bbox));
1280
+ let lastCells = [];
1281
+ if (tops.length) {
1282
+ const bottom = Math.min(...tops.map((b) => b.y1));
1283
+ const footY = pageHeight * HEADER_BAND;
1284
+ const below = (y) => y < bottom - CLIP_EDGE_TOL && y > footY;
1285
+ if (!textPoints.some((p) => below(p.y)) && !cells.some((o) => o.y2 < bottom && below((o.y1 + o.y2) / 2))) {
1286
+ lastCells = tops.filter((b) => Math.abs(b.y1 - bottom) <= CLIP_COORD_TOL && !cells.some((k) => Math.abs(k.x1 - b.x1) <= CONT_X_TOL && Math.abs(k.x2 - b.x2) <= CONT_X_TOL && k.y2 > b.y2 + CLIP_EDGE_TOL && contains(k, b)));
1287
+ }
1288
+ }
1289
+ return { grids, containers, page: { lastCells, clips: cells } };
1290
+ }
1291
+ var CLIP_SPACING_MAX = 3;
1292
+ function findRuledGaps(cells, parent, strokedH, strokedV) {
1293
+ const al = (u, v) => Math.abs(u - v) <= CLIP_COORD_TOL;
1294
+ const occupied = (i, j, x1, y1, x2, y2) => cells.some((k, n) => n !== i && n !== j && parent[n] === parent[i] && overlap(k.x1, k.x2, x1, x2) > CLIP_ADJ_GAP && overlap(k.y1, k.y2, y1, y2) > CLIP_ADJ_GAP);
1295
+ const gapBetween = (i, j) => {
1296
+ const a = cells[i], b = cells[j];
1297
+ const gv = a.y1 - b.y2;
1298
+ if (gv > CLIP_ADJ_GAP && gv <= CLIP_SPACING_MAX && overlap(a.x1, a.x2, b.x1, b.x2) > CLIP_EDGE_TOL && (al(a.x1, b.x1) || al(a.x2, b.x2)) && !occupied(i, j, Math.max(a.x1, b.x1), b.y2, Math.min(a.x2, b.x2), a.y1)) {
1299
+ const e1 = Math.max(a.x1, b.x1), e2 = Math.min(a.x2, b.x2);
1300
+ return { i, j, axis: "y", lo: b.y2, hi: a.y1, e1, e2, to: ruleEnd(strokedH, "h", b.y2, a.y1, e1, e2) };
1301
+ }
1302
+ const gh = b.x1 - a.x2;
1303
+ if (gh > CLIP_ADJ_GAP && gh <= CLIP_SPACING_MAX && overlap(a.y1, a.y2, b.y1, b.y2) > CLIP_EDGE_TOL && (al(a.y1, b.y1) || al(a.y2, b.y2)) && !occupied(i, j, a.x2, Math.max(a.y1, b.y1), b.x1, Math.min(a.y2, b.y2))) {
1304
+ const e1 = Math.max(a.y1, b.y1), e2 = Math.min(a.y2, b.y2);
1305
+ return { i, j, axis: "x", lo: a.x2, hi: b.x1, e1, e2, to: ruleEnd(strokedV, "v", a.x2, b.x1, e1, e2) };
1306
+ }
1307
+ return void 0;
1308
+ };
1309
+ const ruled = (g) => (g.axis === "y" ? strokedH : strokedV).some((l) => {
1310
+ const pos = g.axis === "y" ? l.y1 : l.x1;
1311
+ return (Math.abs(pos - g.lo) <= 0.5 || Math.abs(pos - g.hi) <= 0.5) && (g.axis === "y" ? overlap(l.x1, l.x2, g.e1, g.e2) : overlap(l.y1, l.y2, g.e1, g.e2)) >= (g.e2 - g.e1) * STROKE_COVER;
1312
+ });
1313
+ const root = cells.map((_, i) => i);
1314
+ const find = (i) => {
1315
+ while (root[i] !== i) {
1316
+ root[i] = root[root[i]];
1317
+ i = root[i];
1318
+ }
1319
+ return i;
1320
+ };
1321
+ const found = [];
1322
+ const widths = /* @__PURE__ */ new Map();
1323
+ const link = (g) => {
1324
+ found.push(g);
1325
+ const ri = find(g.i), rj = find(g.j);
1326
+ const w = [...widths.get(rj) ?? [], ...ri !== rj ? widths.get(ri) ?? [] : [], { axis: g.axis, w: g.hi - g.lo }];
1327
+ root[ri] = rj;
1328
+ widths.set(rj, w);
1329
+ };
1330
+ const cand = [];
1331
+ for (let i = 0; i < cells.length; i++) {
1332
+ for (let j = 0; j < cells.length; j++) {
1333
+ if (i === j || parent[i] !== parent[j]) continue;
1334
+ const g = gapBetween(i, j);
1335
+ if (!g) continue;
1336
+ const a = cells[i], b = cells[j];
1337
+ const both = g.axis === "y" ? al(a.x1, b.x1) && al(a.x2, b.x2) : al(a.y1, b.y1) && al(a.y2, b.y2);
1338
+ const isRuled = ruled(g);
1339
+ if (both && isRuled) link(g);
1340
+ else if (both || isRuled) cand.push([g, both]);
1341
+ }
1342
+ }
1343
+ if (!found.length) return [];
1344
+ for (let grew = true; grew; ) {
1345
+ grew = false;
1346
+ for (let k = cand.length - 1; k >= 0; k--) {
1347
+ const [g, both] = cand[k], w = g.hi - g.lo;
1348
+ if (![find(g.i), find(g.j)].some((r) => (widths.get(r) ?? []).some((s) => (both || s.axis === g.axis) && Math.abs(s.w - w) <= CLIP_COORD_TOL))) continue;
1349
+ link(g);
1350
+ cand.splice(k, 1);
1351
+ grew = true;
1352
+ }
1353
+ }
1354
+ return found;
1355
+ }
1356
+ function ruleEnd(lines, dir, lo, hi, e1, e2) {
1357
+ const at = (p) => lines.some((l) => Math.abs((dir === "h" ? l.y1 : l.x1) - p) <= 0.5 && (dir === "h" ? overlap(l.x1, l.x2, e1, e2) : overlap(l.y1, l.y2, e1, e2)) >= (e2 - e1) * STROKE_COVER);
1358
+ const atLo = at(lo), atHi = at(hi);
1359
+ return atLo && !atHi ? lo : atHi && !atLo ? hi : (lo + hi) / 2;
1360
+ }
1361
+ function closeGaps(members, gaps) {
1362
+ const al = (u, v) => Math.abs(u - v) <= CLIP_COORD_TOL;
1363
+ const lines = [];
1364
+ for (const g of gaps) {
1365
+ const l = lines.find((k) => k.axis === g.axis && al(k.lo, g.lo) && al(k.hi, g.hi));
1366
+ if (l) {
1367
+ l.e1 = Math.min(l.e1, g.e1);
1368
+ l.e2 = Math.max(l.e2, g.e2);
1369
+ } else lines.push({ ...g });
1370
+ }
1371
+ const out = members.map((r) => ({ ...r }));
1372
+ for (const l of lines) {
1373
+ const shared = (p) => members.some((u) => members.some((v) => u !== v && (l.axis === "y" ? Math.abs(u.y1 - v.y2) <= CLIP_ADJ_GAP && al(u.y1, p) && overlap(u.x1, u.x2, v.x1, v.x2) > CLIP_EDGE_TOL : Math.abs(u.x2 - v.x1) <= CLIP_ADJ_GAP && al(u.x2, p) && overlap(u.y1, u.y2, v.y1, v.y2) > CLIP_EDGE_TOL)));
1374
+ const atLo = shared(l.lo), atHi = shared(l.hi);
1375
+ const to = atLo && !atHi ? l.lo : atHi && !atLo ? l.hi : l.to;
1376
+ const onEdge = (r) => l.axis === "y" ? al(r.y2, l.lo) || al(r.y1, l.hi) : al(r.x2, l.lo) || al(r.x1, l.hi);
1377
+ const span = (r) => l.axis === "y" ? [r.x1, r.x2] : [r.y1, r.y2];
1378
+ const take = /* @__PURE__ */ new Set();
1379
+ for (let grew = true; grew; ) {
1380
+ grew = false;
1381
+ for (let n = 0; n < members.length; n++) {
1382
+ if (take.has(n) || !onEdge(members[n])) continue;
1383
+ const [a1, a2] = span(members[n]);
1384
+ if (overlap(a1, a2, l.e1, l.e2) <= -(CLIP_SPACING_MAX + CLIP_COORD_TOL)) continue;
1385
+ take.add(n);
1386
+ l.e1 = Math.min(l.e1, a1);
1387
+ l.e2 = Math.max(l.e2, a2);
1388
+ grew = true;
1389
+ }
1390
+ }
1391
+ for (const n of take) {
1392
+ const r = members[n], s = out[n];
1393
+ if (l.axis === "y") {
1394
+ if (al(r.y2, l.lo)) s.y2 = to;
1395
+ if (al(r.y1, l.hi)) s.y1 = to;
1396
+ } else {
1397
+ if (al(r.x2, l.lo)) s.x2 = to;
1398
+ if (al(r.x1, l.hi)) s.x1 = to;
1399
+ }
1400
+ }
1401
+ }
1402
+ return out;
1173
1403
  }
1174
1404
  function addNarrowEdgeCols(colXs, rowYs, out, fills) {
1175
1405
  if (!fills.length) return;
@@ -1322,6 +1552,203 @@ function hasHorizontalLine(horizontals, y, leftX, rightX, vertexRadius) {
1322
1552
  return false;
1323
1553
  }
1324
1554
 
1555
+ // src/pdf/line-wrap.ts
1556
+ var MARKUP = /<\/?u>|~~/g;
1557
+ var bump = (m, k) => {
1558
+ m.set(k, (m.get(k) ?? 0) + 1);
1559
+ };
1560
+ var pairKey = (a, b) => a * 65536 + b;
1561
+ var bump2 = (m, k1, k2) => {
1562
+ let inner = m.get(k1);
1563
+ if (!inner) m.set(k1, inner = /* @__PURE__ */ new Map());
1564
+ bump(inner, k2);
1565
+ };
1566
+ var WrapLexicon = class {
1567
+ joined1 = /* @__PURE__ */ new Map();
1568
+ joined2 = /* @__PURE__ */ new Map();
1569
+ spaced1 = /* @__PURE__ */ new Map();
1570
+ spaced2 = /* @__PURE__ */ new Map();
1571
+ /** 줄 안(줄 첫·끝 어절 제외 — 꺾인 조각일 수 있다) 홀로 선 어절 수 — 한 음절 조각이 낱말("등"·"및"·"그")인지 가른다 */
1572
+ words = /* @__PURE__ */ new Map();
1573
+ /** 줄 안에서 이웃한 두 어절("개인정보 처리") — 이어 붙인 꼴 증거(joinedWord)의 거부권 */
1574
+ pairs = /* @__PURE__ */ new Set();
1575
+ /** 줄 글 한 줄을 증거로 더한다 — 탭(큰 갭)으로 나뉜 조각은 다른 칸·단이라 조각 사이는 어절 경계 증거로 쓰지 않는다 */
1576
+ addLine(text) {
1577
+ for (const seg of text.replace(MARKUP, "").split(" ")) {
1578
+ const toks = seg.split(" ").filter((t) => t && !t.includes("]("));
1579
+ for (let i = 0; i < toks.length; i++) {
1580
+ const t = toks[i], n = t.length;
1581
+ if (i > 0 && i < toks.length - 1) bump(this.words, t);
1582
+ if (i + 1 < toks.length) this.pairs.add(t + " " + toks[i + 1]);
1583
+ for (let a = 0; a + 1 < n; a++) {
1584
+ const c1 = t.charCodeAt(a), c2 = t.charCodeAt(a + 1);
1585
+ bump(this.joined1, pairKey(c1, c2));
1586
+ if (a >= 1 && a + 2 < n) bump2(this.joined2, pairKey(t.charCodeAt(a - 1), c1), pairKey(c2, t.charCodeAt(a + 2)));
1587
+ }
1588
+ if (i + 1 < toks.length) {
1589
+ const u = toks[i + 1];
1590
+ bump(this.spaced1, pairKey(t.charCodeAt(n - 1), u.charCodeAt(0)));
1591
+ if (n >= 2 && u.length >= 2) bump2(this.spaced2, pairKey(t.charCodeAt(n - 2), t.charCodeAt(n - 1)), pairKey(u.charCodeAt(0), u.charCodeAt(1)));
1592
+ }
1593
+ }
1594
+ }
1595
+ }
1596
+ /** 문서 줄 안에서 홀로 선 어절로 나온 적 있나 */
1597
+ isWord(word) {
1598
+ return (this.words.get(word) ?? 0) > 0;
1599
+ }
1600
+ /** 꺾인 두 조각을 이어 붙인 꼴이 줄 안 한 어절로 나왔고, 두 조각이 줄 안에서 띄어 쓴 이웃 어절로는 나온 적 없나 */
1601
+ joinedWord(left, right) {
1602
+ return this.isWord(left + right) && !this.pairs.has(left + " " + right);
1603
+ }
1604
+ /** 꺾인 자리 증거: "" 붙음 · " " 띄움 · null 모름 (두 글자 증거가 갈리거나 없으면 한 글자) */
1605
+ evidence(left, right) {
1606
+ const n = left.length;
1607
+ if (n >= 2 && right.length >= 2) {
1608
+ const k1 = pairKey(left.charCodeAt(n - 2), left.charCodeAt(n - 1)), k2 = pairKey(right.charCodeAt(0), right.charCodeAt(1));
1609
+ const v = decideCounts(this.joined2.get(k1)?.get(k2) ?? 0, this.spaced2.get(k1)?.get(k2) ?? 0);
1610
+ if (v !== null) return v;
1611
+ }
1612
+ if (n && right.length) {
1613
+ const k = pairKey(left.charCodeAt(n - 1), right.charCodeAt(0));
1614
+ return decideCounts(this.joined1.get(k) ?? 0, this.spaced1.get(k) ?? 0);
1615
+ }
1616
+ return null;
1617
+ }
1618
+ };
1619
+ var decideCounts = (joined, spaced) => joined && !spaced ? "" : spaced && !joined ? " " : null;
1620
+ var hasBatchim = (c) => {
1621
+ const k = c.charCodeAt(0) - 44032;
1622
+ return k >= 0 && k < 11172 && k % 28 !== 0;
1623
+ };
1624
+ var CLOSE_TAIL = /[’”」』)\]〉》>]+$/;
1625
+ var TAIL = "[.,)\u300D\u300F\u2019\u201D]*";
1626
+ var PARTICLE = new RegExp(`^(?:\uB2E4\\.|\uC758|\uC5D0|\uC5D0\uC11C|\uC5D0\uB294|\uC5D0\uAC8C|\uC5D0\uB3C4|\uC5D0\uC11C\uB294|\uAE4C\uC9C0|\uBD80\uD130|\uCC98\uB7FC|\uB9C8\uB2E4|\uB9CC|\uB3C4|\uB4E4|\uB4E4\uC774|\uB4E4\uC758|\uB4E4\uC740|\uB4E4\uC744|\uB4E4\uC5D0\uAC8C|\uC11C\uB294|\uC11C\uB3C4|\uB77C\uACE0|\uB77C\uBA70|\uC740|\uB294|\uC744|\uB97C|\uC73C\uB85C|\uB85C|\uC73C\uB85C\uC11C|\uB85C\uC11C|\uC73C\uB85C\uC368|\uB85C\uC368)${TAIL}$`);
1627
+ var PARTICLE_AFTER_CONS = new RegExp(`^(?:\uC774|\uACFC|\uC774\uB2E4|\uC774\uBA70|\uC774\uACE0|\uC774\uB098|\uC774\uB77C\uB294|\uC774\uB77C\uACE0|\uC774\uB77C\uBA70|\uC778|\uC778\uB370|\uC778\uC9C0|\uC784|\uC774\uC5B4\uC57C|\uC774\uBBC0\uB85C|\uC774\uC9C0\uB9CC|\uC774\uBA74\uC11C)${TAIL}$`);
1628
+ var PARTICLE_AFTER_VOWEL = new RegExp(`^(?:\uAC00|\uC640|\uB77C\uB294)${TAIL}$`);
1629
+ var ENDING = new RegExp(`^(?:\uC5B4\uC57C|\uC544\uC57C|\uC5EC\uC57C|\uBBC0\uB85C|\uC73C\uBBC0\uB85C|\uC9C0\uB9CC|\uBA74\uC11C|\uC73C\uBA74\uC11C|\uC73C\uBA70|\uAC70\uB098|\uB354\uB77C\uB3C4|\uB4E0\uC9C0|\uC5C8\uB2E4|\uC558\uB2E4|\uC600\uB2E4|\uC5C8\uC73C\uBA70|\uC558\uC73C\uBA70|\uC600\uC73C\uBA70|\uC5C8\uACE0|\uC558\uACE0|\uC600\uACE0|\uACA0\uB2E4|\uACA0\uACE0|\uACA0\uC73C\uBA70|\uC2B5\uB2C8\uB2E4|\uB2C8\uB2E4|\uB2C8\uAE4C)${TAIL}$`);
1630
+ var ADNOMINAL_HAN = new RegExp(`^\uD55C${TAIL}$`);
1631
+ var SUFFIX_JEOK = new RegExp(`^\uC801(?:\uC778|\uC73C\uB85C|\uC774\uB2E4|\uC774\uBA70|\uC774\uACE0|\uC784|\uC778\uB370|\uC774\uB77C|\uC774\uC9C0\uB9CC|\uC73C\uB85C\uC11C)${TAIL}$`);
1632
+ var SPLIT_PARTICLE = [["\uC5D0", new RegExp(`^\uC11C(?:\uB294|\uB3C4|\uC758|\uBD80\uD130|\uB9CC)?${TAIL}$`)], ["\uC774", new RegExp(`^\uB098${TAIL}$`)]];
1633
+ var VERB_FORM = new RegExp(`^(?:\uD558\uB294|\uD558\uC5EC|\uD558\uACE0|\uD558\uBA70|\uD558\uC600\uB2E4|\uD558\uC600\uC73C\uBA70|\uD558\uC600\uACE0|\uD558\uC600\uB2E4\uACE0|\uD588\uB2E4|\uD588\uC73C\uBA70|\uD588\uACE0|\uD588\uB2E4\uACE0|\uD588\uB2E4\uBA70|\uD55C\uB2E4|\uD55C\uB2E4\uACE0|\uD55C\uB2E4\uBA70|\uD55C\uB2E4\uB294|\uD560|\uD568|\uD558\uACA0\uB2E4|\uD558\uACA0\uB2E4\uACE0|\uD558\uACA0\uC2B5\uB2C8\uB2E4|\uD558\uAE30\uB85C|\uD558\uBA74|\uD558\uBA74\uC11C|\uD558\uB3C4\uB85D|\uD574\uC57C|\uB418\uB294|\uB418\uC5B4|\uB41C|\uB41C\uB2E4|\uB41C\uB2E4\uACE0|\uB41C\uB2E4\uB294|\uB418\uC5C8\uB2E4|\uB418\uC5C8\uC73C\uBA70|\uB418\uC5C8\uACE0|\uB410\uB2E4|\uB410\uC73C\uBA70|\uB418\uBA70|\uB418\uACE0|\uB418\uBA74|\uB428|\uB3FC|\uD558\uAE30|\uD558\uAC8C|\uD558\uC9C0|\uD558\uAC70\uB098|\uD574\uC11C|\uD558\uB294\uB370|\uD568\uC73C\uB85C\uC368|\uD568\uC5D0|\uD568\uC744|\uD568\uC774|\uD558\uAE30\uC5D0|\uD558\uAE30\uB3C4|\uB418\uC9C0|\uB418\uAE30|\uB418\uB3C4\uB85D|\uB418\uAC70\uB098|\uB418\uBA74\uC11C)${TAIL}$`);
1634
+ var AUX_BEFORE = /(?:야|록|로|게|도|히|자|를|을|면|서|고|며|라|려|까지)$/;
1635
+ var BECOME = /^[되된됐됨돼]/;
1636
+ var STANDALONE_SYLLABLE = /^(?:및|등|수|것|그|이|저|더|또|각|약|총|중|간|때|뿐|듯|채|전|후|내|외|한|두|세|네|몇|새|첫|온|본|곧|꼭|잘|못|안|좀|늘|다|왜|뭐|시|할|데|뒤|된)$/;
1637
+ var DATE_DAY_END = /(?:^|\s)\d*월\s+\d*일$/;
1638
+ var COUNTER = /^(?:명|일|월|년|개|원|건|회|차|호|조|항|층|톤|대|곳|시|분|초|배|위|점|주|종|억|만|천|%|퍼센트)/;
1639
+ function endsAsWord(word) {
1640
+ const h = word.replace(CLOSE_TAIL, "");
1641
+ if (!/^[가-힣]{2,}$/.test(h)) return false;
1642
+ if (/(?:을|를|는|은|히|른|할|야|게|의)$/.test(h)) return true;
1643
+ const prev = h[h.length - 2];
1644
+ if (/(?:이|과)$/.test(h)) return hasBatchim(prev);
1645
+ if (/(?:가|와)$/.test(h)) return !hasBatchim(prev);
1646
+ return false;
1647
+ }
1648
+ function particleContinues(left, rightWord) {
1649
+ const right = rightWord.replace(/[”’"」』].+$/, "");
1650
+ const h = left.replace(CLOSE_TAIL, "");
1651
+ const last = h[h.length - 1];
1652
+ if (!last || !/[가-힣A-Za-z0-9]/.test(last)) return false;
1653
+ if (PARTICLE.test(right)) return true;
1654
+ if (!/[가-힣]/.test(last)) return false;
1655
+ if (SUFFIX_JEOK.test(right)) return true;
1656
+ if (ENDING.test(right)) return true;
1657
+ if (/^다[.;:!?]/.test(right)) return true;
1658
+ if (SPLIT_PARTICLE.some(([a, b]) => last === a && b.test(right))) return true;
1659
+ if (PARTICLE_AFTER_CONS.test(right)) return hasBatchim(last);
1660
+ if (PARTICLE_AFTER_VOWEL.test(right)) return !hasBatchim(last);
1661
+ if (ADNOMINAL_HAN.test(right)) return h.length >= 2 && !AUX_BEFORE.test(h) && !/(?:는|은|한|다|어|년)$/.test(h);
1662
+ return VERB_FORM.test(right) && !AUX_BEFORE.test(h) && !((last === "\uC774" || last === "\uAC00" && h.length >= 3) && BECOME.test(right));
1663
+ }
1664
+ var ITEM_HEAD = /^(?:[□■◆◇○●◎◦▪▫•※▶▷►❍❏❑✓✔➢➤☞]|[①-⑳]|[-–·∙ㆍ*](?=\s)|\(?\d{1,2}[.)](?!\d)|\(?[가-하][.)]|\([가-하\d]{1,2}\)|[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]|제\d+[조항호장절])/;
1665
+ function startsNewItem(prevText, nextText) {
1666
+ const t = nextText.replace(MARKUP, "").trimStart();
1667
+ return ITEM_HEAD.test(t) && !(/^다\./.test(t) && /[가-힣]$/.test(prevText.replace(MARKUP, "").trimEnd()));
1668
+ }
1669
+ function wrapJoiner(prevText, nextText, lex) {
1670
+ const prev = prevText.replace(MARKUP, "").trimEnd(), next = nextText.replace(MARKUP, "").trimStart();
1671
+ const a = prev[prev.length - 1], b = next[0];
1672
+ if (!a || !b || /[,;:!?]/.test(a) || DATE_DAY_END.test(prev) && b !== "(") return " ";
1673
+ let s = prev.length;
1674
+ while (s > 0 && !/\s/.test(prev[s - 1])) s--;
1675
+ const left = prev.slice(s), right = next.match(/^\S+/)[0];
1676
+ if (particleContinues(left, right)) return "";
1677
+ if (endsAsWord(left)) return " ";
1678
+ if (/[·ㆍ‧-]/.test(a) && !/[·ㆍ‧…]{2}$/.test(prev) && /[가-힣A-Za-z0-9]/.test(b)) return "";
1679
+ if (/[A-Za-z]/.test(a) && /[A-Za-z]/.test(b) || /\d/.test(a) && /\d/.test(b)) return " ";
1680
+ const ev = lex?.evidence(left, right);
1681
+ if (ev != null) return ev;
1682
+ if (lex && /[가-힣A-Za-z0-9]$/.test(left) && /^[가-힣A-Za-z0-9]/.test(right) && lex.joinedWord(left, right.replace(/[.,)」』’”;:]+$/, ""))) return "";
1683
+ if (/\d$/.test(left) && COUNTER.test(right)) return "";
1684
+ if (lex && /^[가-힣]$/.test(left) && /^[가-힣]/.test(right) && !STANDALONE_SYLLABLE.test(left) && ([...right].length === 1 || !lex.isWord(left))) return "";
1685
+ const r1 = right.replace(/[.,)」』’”]+$/, "");
1686
+ if (lex && /[가-힣]$/.test(left) && /^[가-힣]$/.test(r1) && !STANDALONE_SYLLABLE.test(r1) && !lex.isWord(r1) && !lex.isWord(right)) return "";
1687
+ return " ";
1688
+ }
1689
+ var PARA_LAST_LINE = /* @__PURE__ */ new WeakMap();
1690
+ function joinPageBreakWraps(blocks, lex) {
1691
+ const pageRight = /* @__PURE__ */ new Map();
1692
+ for (const b of blocks) {
1693
+ if (b.type !== "paragraph" || !b.bbox || !b.pageNumber) continue;
1694
+ pageRight.set(b.pageNumber, Math.max(pageRight.get(b.pageNumber) ?? -Infinity, b.bbox.x + b.bbox.width));
1695
+ }
1696
+ for (let i = blocks.length - 1; i > 0; i--) {
1697
+ const a = blocks[i - 1], b = blocks[i];
1698
+ if (!a.pageNumber || b.pageNumber !== a.pageNumber + 1 || !a.text || !b.text) continue;
1699
+ if (a.type !== "paragraph" && a.type !== "list" || b.type !== "paragraph") continue;
1700
+ const last = a.bbox && PARA_LAST_LINE.get(a.bbox), fs = last ? last.fontSize : 0;
1701
+ if (!last || fs <= 0 || (pageRight.get(a.pageNumber) ?? Infinity) - last.right >= BODY_FULL_TOL * fs) continue;
1702
+ if (last.width < BODY_MIN_WIDTH_EM * fs || Math.abs((b.style?.fontSize ?? 0) - fs) > 0.15 * fs) continue;
1703
+ if (startsNewItem(a.text, b.text)) continue;
1704
+ a.text += wrapJoiner(a.text, b.text, lex) + b.text;
1705
+ const bl = b.bbox && PARA_LAST_LINE.get(b.bbox);
1706
+ if (bl) PARA_LAST_LINE.set(a.bbox, bl);
1707
+ else PARA_LAST_LINE.delete(a.bbox);
1708
+ blocks.splice(i, 1);
1709
+ }
1710
+ }
1711
+ var BODY_FULL_TOL = 0.25;
1712
+ var BODY_MIN_WIDTH_EM = 12;
1713
+ var BODY_MAX_PITCH_EM = 2;
1714
+ var BODY_PITCH_REL = 1.05;
1715
+ var BODY_MAX_PITCH_ABS_EM = 3.5;
1716
+ function bodyLineJoins(lines, lex) {
1717
+ let right = -Infinity;
1718
+ for (const l of lines) if (l.right > right) right = l.right;
1719
+ const pitch = (k) => lines[k].y - lines[k + 1].y;
1720
+ const sameSize = (k) => Math.abs(lines[k + 1].fontSize - lines[k].fontSize) <= 0.15 * lines[k].fontSize;
1721
+ let p1 = Infinity, p2 = Infinity, i1 = -1;
1722
+ for (let k = 0; k + 1 < lines.length; k++) {
1723
+ const p = pitch(k);
1724
+ if (p <= 0 || !sameSize(k)) continue;
1725
+ if (p < p1) {
1726
+ p2 = p1;
1727
+ p1 = p;
1728
+ i1 = k;
1729
+ } else if (p < p2) p2 = p;
1730
+ }
1731
+ const out = [];
1732
+ for (let i = 0; i + 1 < lines.length; i++) {
1733
+ const a = lines[i], b = lines[i + 1];
1734
+ const fs = a.fontSize;
1735
+ const others = i === i1 ? p2 : p1;
1736
+ const maxPitch = Number.isFinite(others) ? Math.min(BODY_MAX_PITCH_ABS_EM * fs, Math.max(BODY_MAX_PITCH_EM * fs, others * BODY_PITCH_REL)) : BODY_MAX_PITCH_EM * fs;
1737
+ const wraps = fs > 0 && right - a.right < BODY_FULL_TOL * fs && a.right - a.left >= BODY_MIN_WIDTH_EM * fs && a.y - b.y > 0 && a.y - b.y < maxPitch && Math.abs(b.fontSize - fs) <= 0.15 * fs && !startsNewItem(a.text, b.text);
1738
+ out.push(wraps ? wrapJoiner(a.text, b.text, lex) : "\n");
1739
+ }
1740
+ return out;
1741
+ }
1742
+ var CELL_PAD_MAX = 6;
1743
+ function cellLineWraps(box, contentLeft, prevRight, fontSize, nextFirstCharW) {
1744
+ const pad = Math.min(CELL_PAD_MAX, Math.max(0, contentLeft - box.x1));
1745
+ return fontSize > 0 && prevRight + nextFirstCharW - (box.x2 - pad) > 0.5 * fontSize;
1746
+ }
1747
+ function cellLineFills(box, contentLeft, cellRight, lineRight, fontSize) {
1748
+ const pad = Math.min(CELL_PAD_MAX, Math.max(0, contentLeft - box.x1));
1749
+ return fontSize > 0 && cellRight - lineRight < BODY_FULL_TOL * fontSize && lineRight - contentLeft >= 0.8 * (box.x2 - box.x1 - 2 * pad);
1750
+ }
1751
+
1325
1752
  // src/pdf/cell-text.ts
1326
1753
  var CELL_PADDING = 2;
1327
1754
  var SPACE_GAP_RATIO = 0.17;
@@ -1355,9 +1782,47 @@ function mapTextToCells(items, cells) {
1355
1782
  result.get(bestCell).push(item);
1356
1783
  }
1357
1784
  }
1785
+ keepWordsInOneCell(result);
1358
1786
  return result;
1359
1787
  }
1360
- function cellTextToString(items) {
1788
+ function keepWordsInOneCell(result) {
1789
+ const owner = /* @__PURE__ */ new Map();
1790
+ for (const [cell, arr] of result) for (const it of arr) owner.set(it, cell);
1791
+ if (result.size < 2 || owner.size < 2) return;
1792
+ const all = [...owner.keys()].sort((a, b) => b.y - a.y || a.x - b.x);
1793
+ for (let i = 0; i < all.length; ) {
1794
+ let j = i + 1;
1795
+ while (j < all.length && Math.abs(all[j].y - all[i].y) <= 1) j++;
1796
+ const line = all.slice(i, j).sort((a, b) => a.x - b.x);
1797
+ for (let k = 0; k < line.length; ) {
1798
+ let e = k + 1;
1799
+ while (e < line.length && !line[e].hasSpaceBefore && line[e].x - (line[e - 1].x + line[e - 1].w) <= spaceGapThreshold((line[e].fontSize + line[e - 1].fontSize) / 2)) e++;
1800
+ const word = line.slice(k, e);
1801
+ const width = /* @__PURE__ */ new Map();
1802
+ for (const it of word) {
1803
+ const c = owner.get(it);
1804
+ width.set(c, (width.get(c) ?? 0) + it.w);
1805
+ }
1806
+ const crosses = word.some((it) => {
1807
+ const b = owner.get(it).bbox;
1808
+ return it.x < b.x1 - 1 || it.x + it.w > b.x2 + 1;
1809
+ });
1810
+ if (width.size > 1 && crosses) {
1811
+ const target = [...width].sort((a, b) => b[1] - a[1])[0][0];
1812
+ for (const it of word) {
1813
+ const c = owner.get(it);
1814
+ if (c === target) continue;
1815
+ result.set(c, result.get(c).filter((x) => x !== it));
1816
+ result.get(target).push(it);
1817
+ owner.set(it, target);
1818
+ }
1819
+ }
1820
+ k = e;
1821
+ }
1822
+ i = j;
1823
+ }
1824
+ }
1825
+ function cellTextToString(items, wrap) {
1361
1826
  if (items.length === 0) return "";
1362
1827
  if (items.length === 1) return items[0].text;
1363
1828
  const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x);
@@ -1398,7 +1863,20 @@ function cellTextToString(items) {
1398
1863
  }
1399
1864
  return result;
1400
1865
  });
1401
- return mergeCellTextLines(textLines);
1866
+ if (!wrap) return mergeCellTextLines(textLines);
1867
+ let contentLeft = Infinity;
1868
+ for (const it of items) if (it.x < contentLeft) contentLeft = it.x;
1869
+ const lineEnds = merged.map((line) => {
1870
+ const s = sortLineByX(line);
1871
+ let right = -Infinity;
1872
+ for (const it of s) if (it.x + it.w > right) right = it.x + it.w;
1873
+ const first = s[0];
1874
+ return { right, fontSize: first.fontSize, firstCharW: first.w / Math.max(1, [...first.text].length) };
1875
+ });
1876
+ let cellRight = -Infinity;
1877
+ for (const e of lineEnds) if (e.right > cellRight) cellRight = e.right;
1878
+ const wraps = lineEnds.slice(0, -1).map((a, i) => cellLineWraps(wrap.box, contentLeft, a.right, a.fontSize, lineEnds[i + 1].firstCharW) || lineEnds.length >= 3 && !textLines[i + 1].startsWith("(") && cellLineFills(wrap.box, contentLeft, cellRight, a.right, a.fontSize));
1879
+ return mergeCellTextLines(textLines, { wraps, lex: wrap.lex });
1402
1880
  }
1403
1881
  function mergeSuperscriptRows(lines) {
1404
1882
  if (lines.length <= 1) return lines;
@@ -1469,6 +1947,12 @@ function detectEvenSpacedItems(items) {
1469
1947
  return result;
1470
1948
  }
1471
1949
  function markEvenRun(items, result, start, end) {
1950
+ let dateOnly = true;
1951
+ for (let i = start; i < end; i++) if (!/^[년월일시분초]$/.test(items[i].text)) {
1952
+ dateOnly = false;
1953
+ break;
1954
+ }
1955
+ if (dateOnly) return;
1472
1956
  const gaps = [];
1473
1957
  for (let i = start + 1; i < end; i++) {
1474
1958
  gaps.push(items[i].x - (items[i - 1].x + items[i - 1].w));
@@ -1487,19 +1971,19 @@ function markEvenRun(items, result, start, end) {
1487
1971
  }
1488
1972
  }
1489
1973
  }
1490
- function mergeCellTextLines(textLines) {
1974
+ function mergeCellTextLines(textLines, wrap) {
1491
1975
  if (textLines.length <= 1) return textLines[0] || "";
1492
1976
  const merged = [textLines[0]];
1493
1977
  for (let i = 1; i < textLines.length; i++) {
1494
1978
  const prev = merged[merged.length - 1];
1495
1979
  const curr = textLines[i];
1496
- if (/[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(" ")) {
1980
+ if (wrap ? wrap.wraps[i - 1] && !startsNewItem(prev, curr) && wrapJoiner(prev, curr, wrap.lex) === "" : /[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(" ")) {
1497
1981
  merged[merged.length - 1] = prev + curr;
1498
1982
  } else if (curr.trim().length <= 3 && /^[)\]%}]/.test(curr.trim())) {
1499
1983
  merged[merged.length - 1] = prev + curr.trim();
1500
- } else if (/[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {
1984
+ } else if (!wrap && /[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {
1501
1985
  merged[merged.length - 1] = prev + curr.trim();
1502
- } else if (/[\d,]$/.test(prev) && /^[\d,]+[)\]]?$/.test(curr.trim()) && curr.trim().length <= 10 && !(/\d{1,3}(,\d{3})+$/.test(prev) && /^\d{1,3}(,\d{3})+$/.test(curr.trim()))) {
1986
+ } else if (/[\d,]$/.test(prev) && /^[\d,]+[)\]]?$/.test(curr.trim()) && curr.trim().length <= 10 && !(/\d,\d{3}$/.test(prev) && /^\d/.test(curr.trim())) && !(/(?:^|[^\d,])\d{1,3}$/.test(prev) && /^(\d{1,3}(,\d{3})+|\d{1,3})$/.test(curr.trim()))) {
1503
1987
  merged[merged.length - 1] = prev + curr.trim();
1504
1988
  } else {
1505
1989
  merged.push(curr);
@@ -1654,14 +2138,14 @@ function filterHiddenText(items, pageWidth, pageHeight) {
1654
2138
  }
1655
2139
  return { visible, hiddenCount };
1656
2140
  }
1657
- function collapseEvenSpacing(text) {
2141
+ function collapseEvenSpacing(text, whole = true) {
1658
2142
  const tokens = text.split(" ");
1659
2143
  const singleCharCount = tokens.filter((t) => t.length === 1).length;
1660
- if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7 && !isDateUnitBlank(tokens)) {
2144
+ if (whole && tokens.length >= 3 && singleCharCount / tokens.length >= 0.7 && !isDateUnitBlank(tokens)) {
1661
2145
  return tokens.join("");
1662
2146
  }
1663
2147
  return text.replace(
1664
- /(?<![가-힣])[가-힣](?: [가-힣\d]){2,}(?![가-힣])/g,
2148
+ /(?<![^\s>])[가-힣](?: [가-힣\d]){2,}(?![^\s<])/g,
1665
2149
  (match) => isDateUnitBlank(match.split(" ")) ? match : match.replace(/ /g, "")
1666
2150
  );
1667
2151
  }
@@ -1743,6 +2227,7 @@ function normalizeItems(rawItems) {
1743
2227
  }
1744
2228
  if (!isDup) deduped.push(sorted[i]);
1745
2229
  }
2230
+ splitOverlaidRuns(deduped);
1746
2231
  if (spacePositions.length > 0) {
1747
2232
  for (const sp of spacePositions) {
1748
2233
  let nearest = null;
@@ -1758,6 +2243,98 @@ function normalizeItems(rawItems) {
1758
2243
  }
1759
2244
  return deduped;
1760
2245
  }
2246
+ var WORD_SPACE_EM = 0.3;
2247
+ var OVERLAY_DUP_EM = 0.5;
2248
+ var OVERLAY_FIT_EM = 0.6;
2249
+ function glyphUnits(s) {
2250
+ let u = 0;
2251
+ for (const ch of s) u += (ch.codePointAt(0) ?? 0) >= 8592 ? 1 : 0.5;
2252
+ return u;
2253
+ }
2254
+ function splitOverlaidRuns(items) {
2255
+ let replaced = null;
2256
+ for (let ci = 0; ci < items.length; ci++) {
2257
+ const c = items[ci];
2258
+ if (c.w <= 0 || !/\S\s+\S/.test(c.text)) continue;
2259
+ const overlays = [];
2260
+ for (let j = ci - 1; j >= 0 && items[j].y - c.y <= 1; j--) if (isInside(items[j], c)) overlays.push(items[j]);
2261
+ for (let j = ci + 1; j < items.length && c.y - items[j].y <= 1; j++) if (isInside(items[j], c)) overlays.push(items[j]);
2262
+ if (overlays.length === 0) continue;
2263
+ const pieces = splitAroundOverlays(c, overlays);
2264
+ if (pieces) (replaced ??= /* @__PURE__ */ new Map()).set(c, pieces);
2265
+ }
2266
+ if (!replaced) return;
2267
+ const out = [];
2268
+ for (const it of items) {
2269
+ const p = replaced.get(it);
2270
+ if (p) out.push(...p);
2271
+ else out.push(it);
2272
+ }
2273
+ items.length = 0;
2274
+ items.push(...out.sort((a, b) => b.y - a.y || a.x - b.x));
2275
+ }
2276
+ function isInside(o, c) {
2277
+ return o.x >= c.x + 1 && o.x + o.w <= c.x + c.w + 1 && o.w < c.w;
2278
+ }
2279
+ function splitAroundOverlays(c, overlays) {
2280
+ const words = c.text.split(/\s+/);
2281
+ const fs = c.fontSize > 0 ? c.fontSize : c.h > 0 ? c.h : 10;
2282
+ const ws = WORD_SPACE_EM * fs;
2283
+ const totalU = words.reduce((s, w) => s + glyphUnits(w), 0);
2284
+ if (totalU <= 0) return null;
2285
+ const a0 = (c.w - (words.length - 1) * ws) / totalU;
2286
+ const ov = overlays.filter((o) => {
2287
+ for (let at = c.text.indexOf(o.text); at >= 0; at = c.text.indexOf(o.text, at + 1)) {
2288
+ const prefix = c.text.slice(0, at);
2289
+ const est = c.x + glyphUnits(prefix.replace(/\s+/g, "")) * a0 + (prefix.match(/\s+/g)?.length ?? 0) * ws;
2290
+ if (Math.abs(est - o.x) <= OVERLAY_DUP_EM * fs) return false;
2291
+ }
2292
+ return true;
2293
+ }).sort((p2, q) => p2.x - q.x);
2294
+ if (ov.length === 0) return null;
2295
+ const holes = Math.min(ov.length, words.length - 1);
2296
+ let ovW = 0;
2297
+ for (const o of ov) ovW += o.w;
2298
+ const a = (c.w - ovW - (words.length - 1 - holes) * ws) / totalU;
2299
+ if (!(a > 0)) return null;
2300
+ const starts = [], ends = [], holeAfter = [];
2301
+ let p = c.x;
2302
+ let k = 0;
2303
+ let assigned = 0;
2304
+ for (let i = 0; i < words.length; i++) {
2305
+ starts.push(p);
2306
+ p += glyphUnits(words[i]) * a;
2307
+ ends.push(p);
2308
+ if (i === words.length - 1) break;
2309
+ while (k < ov.length && ov[k].x < p - a * 0.6) k++;
2310
+ const half = glyphUnits(words[i + 1]) * a / 2;
2311
+ let hole = false;
2312
+ while (k < ov.length && ov[k].x < p + half) {
2313
+ hole = true;
2314
+ p = Math.max(p, ov[k].x + ov[k].w);
2315
+ k++;
2316
+ }
2317
+ holeAfter.push(hole);
2318
+ if (hole) assigned++;
2319
+ else p += ws;
2320
+ }
2321
+ if (assigned === 0 || Math.abs(p - (c.x + c.w)) > Math.max(3, OVERLAY_FIT_EM * fs)) return null;
2322
+ const pieces = [];
2323
+ for (let s = 0; s < words.length; ) {
2324
+ let e = s;
2325
+ while (e < words.length - 1 && !holeAfter[e]) e++;
2326
+ pieces.push({
2327
+ ...c,
2328
+ text: words.slice(s, e + 1).join(" "),
2329
+ x: Math.round(starts[s]),
2330
+ w: Math.max(1, Math.round(ends[e] - starts[s])),
2331
+ hasSpaceBefore: s === 0 ? c.hasSpaceBefore : false,
2332
+ seq: c.seq === void 0 ? void 0 : c.seq + pieces.length / 1e4
2333
+ });
2334
+ s = e + 1;
2335
+ }
2336
+ return pieces;
2337
+ }
1761
2338
  function splitEvenSpacedItem(text, itemX, itemW, fontSize) {
1762
2339
  if (!/^[가-힣\d](?: [가-힣\d]){2,}$/.test(text)) return null;
1763
2340
  const chars = text.split(" ");
@@ -1883,7 +2460,7 @@ function detectHeadings(blocks, medianFontSize) {
1883
2460
  if (level > 0) {
1884
2461
  block.type = "heading";
1885
2462
  block.level = level;
1886
- block.text = collapseEvenSpacing(text);
2463
+ block.text = collapseEvenSpacing(text, false);
1887
2464
  }
1888
2465
  }
1889
2466
  }
@@ -2247,6 +2824,7 @@ var TABLE_COLXS = /* @__PURE__ */ new WeakMap();
2247
2824
  var EMPTY_PARTS = /* @__PURE__ */ new WeakSet();
2248
2825
  var IMAGE_CELLS = /* @__PURE__ */ new WeakSet();
2249
2826
  var FILLER_CELLS = /* @__PURE__ */ new WeakSet();
2827
+ var CONT_PARTS = /* @__PURE__ */ new WeakMap();
2250
2828
  var CELL_LINES = /* @__PURE__ */ new WeakMap();
2251
2829
  function recordCellLines(cell, items) {
2252
2830
  const lines = [];
@@ -2802,7 +3380,7 @@ function buildClusterTable(rows, columns, pageNum) {
2802
3380
  }
2803
3381
  const assignments = assignRowItems(row.items, columns, numCols);
2804
3382
  for (const { col, items } of assignments) {
2805
- const text = items.map((i) => i.text).join(" ");
3383
+ const text = joinCellItems(items);
2806
3384
  const existing = cells[r][col].text;
2807
3385
  cells[r][col].text = existing ? existing + " " + text : text;
2808
3386
  for (const item of items) usedItems.add(item);
@@ -2825,6 +3403,7 @@ function buildClusterTable(rows, columns, pageNum) {
2825
3403
  if (/^[○●▶\-·]/.test(contentText)) continue;
2826
3404
  for (let pr = r - 1; pr >= 0; pr--) {
2827
3405
  if (cells[pr].some((c) => c.text.trim())) {
3406
+ if (cells[pr][0].colSpan > 1) break;
2828
3407
  for (let c = 0; c < numCols; c++) {
2829
3408
  const prev = cells[pr][c].text.trim();
2830
3409
  const curr = cells[r][c].text.trim();
@@ -2875,6 +3454,29 @@ function buildClusterTable(rows, columns, pageNum) {
2875
3454
  usedItems
2876
3455
  };
2877
3456
  }
3457
+ function joinCellItems(items) {
3458
+ const hOf = (i) => i.h > 0 ? i.h : i.fontSize;
3459
+ const lines = [];
3460
+ for (const it of [...items].sort((a, b) => b.y - a.y || a.x - b.x)) {
3461
+ const bottom = it.y, top = it.y + hOf(it);
3462
+ const line = lines.find((l) => Math.min(l.top, top) - Math.max(l.bottom, bottom) >= Math.min(l.top - l.bottom, top - bottom) * 0.5);
3463
+ if (line) {
3464
+ line.items.push(it);
3465
+ line.bottom = Math.min(line.bottom, bottom);
3466
+ line.top = Math.max(line.top, top);
3467
+ } else lines.push({ bottom, top, items: [it] });
3468
+ }
3469
+ return lines.sort((a, b) => b.top - a.top).map(({ items: line }) => {
3470
+ line.sort((a, b) => a.x - b.x);
3471
+ let s = line[0].text;
3472
+ for (let i = 1; i < line.length; i++) {
3473
+ const gap = line[i].x - (line[i - 1].x + line[i - 1].w);
3474
+ const fs = (line[i].fontSize + line[i - 1].fontSize) / 2;
3475
+ s += (line[i].hasSpaceBefore && gap >= fs * 0.05 || gap > spaceGapThreshold(fs) ? " " : "") + line[i].text;
3476
+ }
3477
+ return s;
3478
+ }).join(" ");
3479
+ }
2878
3480
 
2879
3481
  // src/pdf/xy-cut.ts
2880
3482
  var MAX_XYCUT_DEPTH = 50;
@@ -3207,7 +3809,10 @@ function detectColumns(yLines) {
3207
3809
  const xClusters = [];
3208
3810
  for (const line of tableYLines) {
3209
3811
  if (isProseSpread(line)) continue;
3210
- for (const item of line) {
3812
+ const byX = [...line].sort((a, b) => a.x - b.x);
3813
+ for (let k = 0; k < byX.length; k++) {
3814
+ const item = byX[k];
3815
+ if (k > 0 && item.x - (byX[k - 1].x + byX[k - 1].w) < Math.max(item.fontSize, byX[k - 1].fontSize)) continue;
3211
3816
  let found = false;
3212
3817
  for (const c of xClusters) {
3213
3818
  if (Math.abs(item.x - c.center) <= CLUSTER_TOL) {
@@ -3309,12 +3914,9 @@ function extractWithColumns(yLines, columns) {
3309
3914
  function buildGridTable(lines, columns) {
3310
3915
  const numCols = columns.length;
3311
3916
  const yRows = lines.map((items) => {
3312
- const row = Array(numCols).fill("");
3313
- for (const item of items) {
3314
- const col = findColumn(item.x, columns);
3315
- row[col] = row[col] ? row[col] + " " + item.text : item.text;
3316
- }
3317
- return row;
3917
+ const byCol = Array.from({ length: numCols }, () => []);
3918
+ for (const item of items) byCol[findColumn(item.x, columns)].push(item);
3919
+ return byCol.map((cell) => mergeLineSimple(cell));
3318
3920
  });
3319
3921
  const dataColStart = Math.max(2, Math.floor(numCols / 2));
3320
3922
  const merged = [];
@@ -3512,12 +4114,26 @@ function wrapUnderlineRuns(items) {
3512
4114
  }
3513
4115
 
3514
4116
  // src/pdf/page-blocks.ts
3515
- function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeight, extraLines, detectTables = true) {
3516
- if (items.length === 0) return [];
4117
+ function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeight, extraLines, detectTables = true, carry, lexicon) {
4118
+ if (items.length === 0) {
4119
+ if (carry) carry.clip = void 0;
4120
+ return [];
4121
+ }
4122
+ const lex = lexicon ?? new WrapLexicon();
4123
+ for (const line of streamLines(items)) lex.addLine(mergeLineSimple(line));
3517
4124
  const extracted = extractLines(opList.fnArray, opList.argsArray);
3518
4125
  let { horizontals, verticals } = extracted;
3519
- const clipResult = detectTables ? buildClipCellGrids(extracted.clipRects, horizontals, verticals, pageWidth, pageHeight, items.map((it) => ({ x: it.x + it.w / 2, y: it.y + it.h / 2 })), extracted.fillRects) : { grids: [], containers: [] };
4126
+ const prevPage = carry?.page === pageNum - 1 ? carry.clip : void 0;
4127
+ const clipResult = detectTables ? buildClipCellGrids(extracted.clipRects, horizontals, verticals, pageWidth, pageHeight, items.map((it) => ({ x: it.x + it.w / 2, y: it.y + it.h / 2 })), extracted.fillRects, prevPage) : { grids: [], containers: [], page: void 0 };
4128
+ if (carry) {
4129
+ carry.page = pageNum;
4130
+ carry.clip = clipResult.page;
4131
+ }
3520
4132
  const clipGrids = clipResult.grids;
4133
+ if (clipGrids.length === 0) {
4134
+ horizontals = chainShortSegments(horizontals, extracted.shortH, "h");
4135
+ verticals = chainShortSegments(verticals, extracted.shortV, "v");
4136
+ }
3521
4137
  if (extraLines) {
3522
4138
  horizontals = horizontals.concat(extraLines.horizontals);
3523
4139
  verticals = verticals.concat(extraLines.verticals);
@@ -3532,12 +4148,13 @@ function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeigh
3532
4148
  markUnderlineItems(items, horizontals, verticals);
3533
4149
  wrapUnderlineRuns(items);
3534
4150
  const lineGrids = detectTables ? buildTableGrids(horizontals, verticals) : [];
3535
- const grids = [...clipGrids, ...dropGridsInside(lineGrids, clipGrids, clipResult.containers)];
4151
+ const tableClipGrids = dropShadingClipGrids(clipGrids, lineGrids, extracted.fillRects, verticals);
4152
+ const grids = [...tableClipGrids, ...dropGridsInside(lineGrids, tableClipGrids, clipResult.containers)];
3536
4153
  if (grids.length > 0) {
3537
4154
  const imageRegions = extractImageRegions(opList.fnArray, opList.argsArray).filter((r) => r.x2 - r.x1 >= 8 && r.y2 - r.y1 >= 8);
3538
- return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, imageRegions);
4155
+ return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, imageRegions, lex);
3539
4156
  }
3540
- return extractPageBlocksFallback(items, pageNum, true, detectTables);
4157
+ return extractPageBlocksFallback(items, pageNum, true, detectTables, lex);
3541
4158
  }
3542
4159
  var STRIKE_MAX_THICKNESS = 2;
3543
4160
  var STRIKE_MAX_THICKNESS_RATIO = 0.25;
@@ -3646,13 +4263,13 @@ function takePendingNested(pending, cellBox) {
3646
4263
  }
3647
4264
  return out;
3648
4265
  }
3649
- function buildFrameCellBlocks(cellItems, nested, pageNum) {
4266
+ function buildFrameCellBlocks(cellItems, nested, pageNum, wrap) {
3650
4267
  const tables = [...nested].sort((a, b) => b.bbox.y + b.bbox.height - (a.bbox.y + a.bbox.height));
3651
4268
  const blocks = [];
3652
4269
  let rest = [...cellItems];
3653
4270
  const pushParagraphs = (items) => {
3654
4271
  if (items.length === 0) return;
3655
- for (const line of cleanCellText(cellTextToString(items)).split("\n")) {
4272
+ for (const line of cleanCellText(cellTextToString(items, wrap)).split("\n")) {
3656
4273
  const t = line.trim();
3657
4274
  if (t) blocks.push({ type: "paragraph", text: t, pageNumber: pageNum });
3658
4275
  }
@@ -3667,7 +4284,7 @@ function buildFrameCellBlocks(cellItems, nested, pageNum) {
3667
4284
  const text = blocks.map((b) => b.type === "table" && b.table ? b.table.cells.flat().map((c) => c.text).filter(Boolean).join("\n") : b.text ?? "").filter(Boolean).join("\n");
3668
4285
  return { blocks, text };
3669
4286
  }
3670
- function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, imageRegions = []) {
4287
+ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, imageRegions = [], lex) {
3671
4288
  const blocks = [];
3672
4289
  const usedItems = /* @__PURE__ */ new Set();
3673
4290
  const pendingNested = [];
@@ -3717,12 +4334,13 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, i
3717
4334
  const nested = grid.cells ? takePendingNested(pendingNested, cell.bbox) : [];
3718
4335
  if (nested.length > 0) {
3719
4336
  nestedAttached = true;
3720
- const built = buildFrameCellBlocks(cellItems, nested, pageNum);
4337
+ const built = buildFrameCellBlocks(cellItems, nested, pageNum, { box: cell.bbox, lex });
3721
4338
  irGrid[cell.row][cell.col] = { text: built.text, colSpan: cell.colSpan, rowSpan: cell.rowSpan, blocks: built.blocks };
4339
+ if (cellItems.length) recordCellLines(irGrid[cell.row][cell.col], cellItems);
3722
4340
  continue;
3723
4341
  }
3724
4342
  irGrid[cell.row][cell.col] = {
3725
- text: cleanCellText(cellTextToString(cellItems)),
4343
+ text: cleanCellText(cellTextToString(cellItems, { box: cell.bbox, lex })),
3726
4344
  colSpan: cell.colSpan,
3727
4345
  rowSpan: cell.rowSpan
3728
4346
  };
@@ -3760,6 +4378,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, i
3760
4378
  hasHeader: finalRows > 1
3761
4379
  };
3762
4380
  TABLE_COLXS.set(irTable, grid.colXs);
4381
+ if (grid.continues) CONT_PARTS.set(irTable, grid.continues);
3763
4382
  const hasContent = finalGrid.some((row) => row.some((cell) => cell.text.trim() !== ""));
3764
4383
  const emptyPart = !hasContent && !!grid.cells && !grid.clipParent && !nestedAttached;
3765
4384
  if (!hasContent && !emptyPart) continue;
@@ -3846,7 +4465,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, i
3846
4465
  if (side.length === 0) continue;
3847
4466
  for (const group of xyCutOrder(side, gapThreshold)) {
3848
4467
  if (group.length === 0) continue;
3849
- const groupBlocks = extractPageBlocksFallback(group, pageNum);
4468
+ const groupBlocks = extractPageBlocksFallback(group, pageNum, false, true, lex);
3850
4469
  for (const b of groupBlocks) textBlocks.push(b);
3851
4470
  groupSizes.push(groupBlocks.length);
3852
4471
  }
@@ -3859,7 +4478,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals, i
3859
4478
  const textBlocks = [];
3860
4479
  for (const group of groups) {
3861
4480
  if (group.length === 0) continue;
3862
- const groupBlocks = extractPageBlocksFallback(group, pageNum);
4481
+ const groupBlocks = extractPageBlocksFallback(group, pageNum, false, true, lex);
3863
4482
  for (const b of groupBlocks) textBlocks.push(b);
3864
4483
  groupSizes.push(groupBlocks.length);
3865
4484
  }
@@ -3969,7 +4588,42 @@ function splitTwoColumnProse(items, cutX) {
3969
4588
  }
3970
4589
  return groups;
3971
4590
  }
3972
- function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTables = true) {
4591
+ function streamLines(items) {
4592
+ if (items.some((i) => i.seq === void 0)) return groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x));
4593
+ const lines = [];
4594
+ let cur = [];
4595
+ for (const it of [...items].sort((a, b) => a.seq - b.seq)) {
4596
+ const last = cur[cur.length - 1];
4597
+ if (last && (Math.abs(it.y - last.y) > 3 || it.x < last.x - 1 || it.x - (last.x + last.w) > Math.max(2 * it.fontSize, 30))) {
4598
+ lines.push(cur);
4599
+ cur = [];
4600
+ }
4601
+ cur.push(it);
4602
+ }
4603
+ if (cur.length) lines.push(cur);
4604
+ return lines;
4605
+ }
4606
+ function pushLineParagraphs(out, yLines, pageNum, lex) {
4607
+ const lines = yLines.map((items) => ({ items, text: mergeLineSimple(items) })).filter((l) => l.text.trim());
4608
+ const geo = lines.map((l) => {
4609
+ const b = computeBBox(l.items, pageNum);
4610
+ return { text: l.text, left: b.x, right: b.x + b.width, y: l.items.reduce((s, i) => s + i.y, 0) / l.items.length, fontSize: dominantStyle(l.items)?.fontSize ?? 0 };
4611
+ });
4612
+ const joins = bodyLineJoins(geo, lex);
4613
+ for (let i = 0; i < lines.length; ) {
4614
+ let text = lines[i].text;
4615
+ const items = [...lines[i].items];
4616
+ for (; i + 1 < lines.length && joins[i] !== "\n"; i++) {
4617
+ text += joins[i] + lines[i + 1].text;
4618
+ items.push(...lines[i + 1].items);
4619
+ }
4620
+ const block = { type: "paragraph", text, pageNumber: pageNum, bbox: computeBBox(items, pageNum), style: dominantStyle(items) };
4621
+ PARA_LAST_LINE.set(block.bbox, { right: geo[i].right, width: geo[i].right - geo[i].left, fontSize: geo[i].fontSize });
4622
+ out.push(block);
4623
+ i++;
4624
+ }
4625
+ }
4626
+ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTables = true, lex) {
3973
4627
  if (items.length === 0) return [];
3974
4628
  const blocks = [];
3975
4629
  const clusterItems = items.map((i) => ({
@@ -3995,15 +4649,7 @@ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTable
3995
4649
  blocks.push({ type: "table", table: cr.table, pageNumber: pageNum, bbox: cr.bbox });
3996
4650
  }
3997
4651
  const remaining = items.filter((_, idx) => !usedIndices.has(idx));
3998
- if (remaining.length > 0) {
3999
- const yLines = mergeSuperscriptLines(groupByY(remaining));
4000
- for (const line of yLines) {
4001
- const text = mergeLineSimple(line);
4002
- if (!text.trim()) continue;
4003
- const bbox = computeBBox(line, pageNum);
4004
- blocks.push({ type: "paragraph", text, pageNumber: pageNum, bbox, style: dominantStyle(line) });
4005
- }
4006
- }
4652
+ if (remaining.length > 0) pushLineParagraphs(blocks, mergeSuperscriptLines(groupByY(remaining)), pageNum, lex);
4007
4653
  blocks.sort((a, b) => {
4008
4654
  const ay = a.bbox ? a.bbox.y + a.bbox.height : 0;
4009
4655
  const by = b.bbox ? b.bbox.y + b.bbox.height : 0;
@@ -4034,12 +4680,7 @@ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTable
4034
4680
  const bbox = computeBBox(group, pageNum);
4035
4681
  blocks.push({ type: "paragraph", text: tableText, pageNumber: pageNum, bbox, style: dominantStyle(group) });
4036
4682
  } else {
4037
- for (const line of yLines) {
4038
- const text = mergeLineSimple(line);
4039
- if (!text.trim()) continue;
4040
- const bbox = computeBBox(line, pageNum);
4041
- blocks.push({ type: "paragraph", text, pageNumber: pageNum, bbox, style: dominantStyle(line) });
4042
- }
4683
+ pushLineParagraphs(blocks, yLines, pageNum, lex);
4043
4684
  }
4044
4685
  }
4045
4686
  }
@@ -4052,6 +4693,9 @@ export {
4052
4693
  filterHiddenText,
4053
4694
  collapseEvenSpacing,
4054
4695
  normalizeItems,
4696
+ WrapLexicon,
4697
+ wrapJoiner,
4698
+ joinPageBreakWraps,
4055
4699
  computeMedianFontSizeFromFreq,
4056
4700
  detectHeadings,
4057
4701
  detectMarkerHeadings,
@@ -4063,8 +4707,9 @@ export {
4063
4707
  EMPTY_PARTS,
4064
4708
  IMAGE_CELLS,
4065
4709
  FILLER_CELLS,
4710
+ CONT_PARTS,
4066
4711
  CELL_LINES,
4067
4712
  trimTrailingEmptyTableCols,
4068
4713
  extractPageBlocksWithLines
4069
4714
  };
4070
- //# sourceMappingURL=chunk-UVVEBI22.js.map
4715
+ //# sourceMappingURL=chunk-23AJ65GX.js.map