kordoc 4.15.6 → 4.16.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 (147) hide show
  1. package/README.md +50 -9
  2. package/dist/{-F73GANY7.js → -VQNLLLPE.js} +11 -11
  3. package/dist/{chunk-UKD7C2WS.js → chunk-3JWMDL3S.js} +5 -4
  4. package/dist/chunk-3JWMDL3S.js.map +1 -0
  5. package/dist/{chunk-FWT24USP.js → chunk-43FKGDDO.js} +354 -135
  6. package/dist/chunk-43FKGDDO.js.map +1 -0
  7. package/dist/{chunk-KJKMBV5K.js → chunk-46DZF3RK.js} +5 -4
  8. package/dist/chunk-46DZF3RK.js.map +1 -0
  9. package/dist/{chunk-NK5XFKL4.js → chunk-476OYHGZ.js} +24 -7
  10. package/dist/chunk-476OYHGZ.js.map +1 -0
  11. package/dist/{chunk-QC736WXT.js → chunk-6LMKQM7I.js} +3 -3
  12. package/dist/{chunk-U7J7BFV3.js → chunk-A3QDRMVD.js} +2 -2
  13. package/dist/{chunk-SQJ7Q2Q5.cjs → chunk-B5NEGP65.cjs} +61 -76
  14. package/dist/chunk-B5NEGP65.cjs.map +1 -0
  15. package/dist/{chunk-OQ3DY7RF.js → chunk-C75WE52C.js} +2 -2
  16. package/dist/{chunk-FA5JIHUV.js → chunk-CB5XDCSL.js} +272 -74
  17. package/dist/chunk-CB5XDCSL.js.map +1 -0
  18. package/dist/{chunk-2DLW4CMC.cjs → chunk-DRY3L5KQ.cjs} +57 -61
  19. package/dist/chunk-DRY3L5KQ.cjs.map +1 -0
  20. package/dist/{chunk-2OKMF2KY.js → chunk-DYENR3PX.js} +2 -2
  21. package/dist/{chunk-UGIESTA6.js → chunk-GOQBON77.js} +10 -2
  22. package/dist/{chunk-UGIESTA6.js.map → chunk-GOQBON77.js.map} +1 -1
  23. package/dist/{chunk-QJX4IR77.cjs → chunk-H6KMTYYZ.cjs} +3 -3
  24. package/dist/{chunk-QJX4IR77.cjs.map → chunk-H6KMTYYZ.cjs.map} +1 -1
  25. package/dist/{chunk-RSKR6A6S.js → chunk-HIVK5V27.js} +428 -128
  26. package/dist/chunk-HIVK5V27.js.map +1 -0
  27. package/dist/{chunk-ISJRNC2G.js → chunk-JB5X5NNL.js} +50 -12
  28. package/dist/chunk-JB5X5NNL.js.map +1 -0
  29. package/dist/{chunk-SOM7JIKG.cjs → chunk-MH6MAL64.cjs} +468 -168
  30. package/dist/chunk-MH6MAL64.cjs.map +1 -0
  31. package/dist/{chunk-DRQW5SYI.js → chunk-N6TPODYL.js} +15 -2
  32. package/dist/chunk-N6TPODYL.js.map +1 -0
  33. package/dist/{chunk-A63JPQEE.js → chunk-NNYF2AGC.js} +45 -49
  34. package/dist/chunk-NNYF2AGC.js.map +1 -0
  35. package/dist/{chunk-FBZCWSPN.cjs → chunk-NWODPYBD.cjs} +11 -3
  36. package/dist/chunk-NWODPYBD.cjs.map +1 -0
  37. package/dist/{chunk-765SAMGS.js → chunk-PIMB2YNO.js} +2 -2
  38. package/dist/{chunk-HT2TGO46.js → chunk-PQRP4NSF.js} +2 -2
  39. package/dist/{chunk-U6SS43O6.js → chunk-QJERJZJ6.js} +1 -1
  40. package/dist/chunk-QJERJZJ6.js.map +1 -0
  41. package/dist/{chunk-JX7URXOL.js → chunk-RC4C7Q33.js} +10 -2
  42. package/dist/{chunk-JX7URXOL.js.map → chunk-RC4C7Q33.js.map} +1 -1
  43. package/dist/{chunk-O6A6SK55.js → chunk-TGD7UFHD.js} +45 -49
  44. package/dist/chunk-TGD7UFHD.js.map +1 -0
  45. package/dist/{chunk-6OQTREJ5.js → chunk-UDX7TSOC.js} +2 -2
  46. package/dist/{chunk-3PDCVDSO.js → chunk-UQ6TZDW6.js} +1 -1
  47. package/dist/chunk-UQ6TZDW6.js.map +1 -0
  48. package/dist/{chunk-WXEXWGPT.js → chunk-XHOZB2WP.js} +59 -71
  49. package/dist/chunk-XHOZB2WP.js.map +1 -0
  50. package/dist/{chunk-CND4NRNF.cjs → chunk-ZY5K3LPM.cjs} +1 -1
  51. package/dist/chunk-ZY5K3LPM.cjs.map +1 -0
  52. package/dist/chunks-JD25E5PM.js +10 -0
  53. package/dist/cli.js +62 -39
  54. package/dist/cli.js.map +1 -1
  55. package/dist/{formula-HU2APQRR.js → formula-5775PTP2.js} +4 -4
  56. package/dist/{formula-P54CLKTH.js → formula-FNQYHMIY.js} +3 -3
  57. package/dist/{formula-LWU5I4NX.cjs → formula-VEFKYXPB.cjs} +9 -9
  58. package/dist/{formula-LWU5I4NX.cjs.map → formula-VEFKYXPB.cjs.map} +1 -1
  59. package/dist/{image-ocr-RCM3KL27.cjs → image-ocr-6JZR734G.cjs} +17 -15
  60. package/dist/image-ocr-6JZR734G.cjs.map +1 -0
  61. package/dist/{image-ocr-2UCO6DQD.js → image-ocr-PYZU4TO7.js} +14 -9
  62. package/dist/image-ocr-PYZU4TO7.js.map +1 -0
  63. package/dist/{image-ocr-I62V7QB2.js → image-ocr-YA2MQBRP.js} +9 -11
  64. package/dist/image-ocr-YA2MQBRP.js.map +1 -0
  65. package/dist/index.cjs +967 -751
  66. package/dist/index.cjs.map +1 -1
  67. package/dist/index.d.cts +12 -0
  68. package/dist/index.d.ts +12 -0
  69. package/dist/index.js +301 -85
  70. package/dist/index.js.map +1 -1
  71. package/dist/mcp.js +67 -50
  72. package/dist/mcp.js.map +1 -1
  73. package/dist/{model-bundle-MSYSZSDJ.js → model-bundle-O6J7ORVC.js} +5 -5
  74. package/dist/{models-BCUGEIQX.js → models-YMOKZGCI.js} +7 -5
  75. package/dist/{parser-SI4DSZ6J.js → parser-3B6I2XL7.js} +854 -39
  76. package/dist/parser-3B6I2XL7.js.map +1 -0
  77. package/dist/{parser-KZDC4LLY.js → parser-PXATL5DM.js} +850 -38
  78. package/dist/parser-PXATL5DM.js.map +1 -0
  79. package/dist/{parser-M26S76U2.cjs → parser-YAQV7FBO.cjs} +962 -150
  80. package/dist/parser-YAQV7FBO.cjs.map +1 -0
  81. package/dist/pdf-ocr-2TTO5PX2.js +15 -0
  82. package/dist/pdf-ocr-T2JXQATA.cjs +16 -0
  83. package/dist/{pdf-ocr-IOQA7ZQM.cjs.map → pdf-ocr-T2JXQATA.cjs.map} +1 -1
  84. package/dist/pdf-ocr-WF2NQA6X.js +18 -0
  85. package/dist/{profile-io-QHKLJKAM.js → profile-io-ZTV2MKRT.js} +3 -3
  86. package/dist/{rasterize-JY72CIBX.js → rasterize-MMUZ537K.js} +3 -3
  87. package/dist/{redact-doc-BWS4KRNH.js → redact-doc-S42XGV5D.js} +11 -11
  88. package/dist/{render-53DA6GD5.js → render-WCWPZKJ7.js} +7 -7
  89. package/dist/seal-WHK4GOHR.js +10 -0
  90. package/dist/{setup-GHLZX5VK.js → setup-D35LKZCH.js} +3 -3
  91. package/dist/visual-WL4TDE5B.js +20 -0
  92. package/dist/{watch-SYDX4ZLX.js → watch-AX64CIMN.js} +41 -19
  93. package/dist/watch-AX64CIMN.js.map +1 -0
  94. package/package.json +1 -1
  95. package/dist/chunk-2DLW4CMC.cjs.map +0 -1
  96. package/dist/chunk-3PDCVDSO.js.map +0 -1
  97. package/dist/chunk-A63JPQEE.js.map +0 -1
  98. package/dist/chunk-CND4NRNF.cjs.map +0 -1
  99. package/dist/chunk-DRQW5SYI.js.map +0 -1
  100. package/dist/chunk-FA5JIHUV.js.map +0 -1
  101. package/dist/chunk-FBZCWSPN.cjs.map +0 -1
  102. package/dist/chunk-FWT24USP.js.map +0 -1
  103. package/dist/chunk-ISJRNC2G.js.map +0 -1
  104. package/dist/chunk-KJKMBV5K.js.map +0 -1
  105. package/dist/chunk-NK5XFKL4.js.map +0 -1
  106. package/dist/chunk-O6A6SK55.js.map +0 -1
  107. package/dist/chunk-RSKR6A6S.js.map +0 -1
  108. package/dist/chunk-SOM7JIKG.cjs.map +0 -1
  109. package/dist/chunk-SQJ7Q2Q5.cjs.map +0 -1
  110. package/dist/chunk-U6SS43O6.js.map +0 -1
  111. package/dist/chunk-UKD7C2WS.js.map +0 -1
  112. package/dist/chunk-WXEXWGPT.js.map +0 -1
  113. package/dist/chunks-RJXLSR5O.js +0 -10
  114. package/dist/image-ocr-2UCO6DQD.js.map +0 -1
  115. package/dist/image-ocr-I62V7QB2.js.map +0 -1
  116. package/dist/image-ocr-RCM3KL27.cjs.map +0 -1
  117. package/dist/parser-KZDC4LLY.js.map +0 -1
  118. package/dist/parser-M26S76U2.cjs.map +0 -1
  119. package/dist/parser-SI4DSZ6J.js.map +0 -1
  120. package/dist/pdf-ocr-IOQA7ZQM.cjs +0 -16
  121. package/dist/pdf-ocr-KA6BQTAB.js +0 -18
  122. package/dist/pdf-ocr-TPTWVV57.js +0 -15
  123. package/dist/seal-TJX6N5EE.js +0 -10
  124. package/dist/visual-JSTROEWO.js +0 -20
  125. package/dist/watch-SYDX4ZLX.js.map +0 -1
  126. /package/dist/{-F73GANY7.js.map → -VQNLLLPE.js.map} +0 -0
  127. /package/dist/{chunk-QC736WXT.js.map → chunk-6LMKQM7I.js.map} +0 -0
  128. /package/dist/{chunk-U7J7BFV3.js.map → chunk-A3QDRMVD.js.map} +0 -0
  129. /package/dist/{chunk-OQ3DY7RF.js.map → chunk-C75WE52C.js.map} +0 -0
  130. /package/dist/{chunk-2OKMF2KY.js.map → chunk-DYENR3PX.js.map} +0 -0
  131. /package/dist/{chunk-765SAMGS.js.map → chunk-PIMB2YNO.js.map} +0 -0
  132. /package/dist/{chunk-HT2TGO46.js.map → chunk-PQRP4NSF.js.map} +0 -0
  133. /package/dist/{chunk-6OQTREJ5.js.map → chunk-UDX7TSOC.js.map} +0 -0
  134. /package/dist/{chunks-RJXLSR5O.js.map → chunks-JD25E5PM.js.map} +0 -0
  135. /package/dist/{formula-HU2APQRR.js.map → formula-5775PTP2.js.map} +0 -0
  136. /package/dist/{formula-P54CLKTH.js.map → formula-FNQYHMIY.js.map} +0 -0
  137. /package/dist/{model-bundle-MSYSZSDJ.js.map → model-bundle-O6J7ORVC.js.map} +0 -0
  138. /package/dist/{models-BCUGEIQX.js.map → models-YMOKZGCI.js.map} +0 -0
  139. /package/dist/{pdf-ocr-KA6BQTAB.js.map → pdf-ocr-2TTO5PX2.js.map} +0 -0
  140. /package/dist/{pdf-ocr-TPTWVV57.js.map → pdf-ocr-WF2NQA6X.js.map} +0 -0
  141. /package/dist/{profile-io-QHKLJKAM.js.map → profile-io-ZTV2MKRT.js.map} +0 -0
  142. /package/dist/{rasterize-JY72CIBX.js.map → rasterize-MMUZ537K.js.map} +0 -0
  143. /package/dist/{redact-doc-BWS4KRNH.js.map → redact-doc-S42XGV5D.js.map} +0 -0
  144. /package/dist/{render-53DA6GD5.js.map → render-WCWPZKJ7.js.map} +0 -0
  145. /package/dist/{seal-TJX6N5EE.js.map → seal-WHK4GOHR.js.map} +0 -0
  146. /package/dist/{setup-GHLZX5VK.js.map → setup-D35LKZCH.js.map} +0 -0
  147. /package/dist/{visual-JSTROEWO.js.map → visual-WL4TDE5B.js.map} +0 -0
@@ -3,76 +3,14 @@ import {
3
3
  HEADING_RATIO_H1,
4
4
  HEADING_RATIO_H2,
5
5
  HEADING_RATIO_H3
6
- } from "./chunk-3PDCVDSO.js";
6
+ } from "./chunk-UQ6TZDW6.js";
7
7
  import {
8
8
  safeMax,
9
9
  safeMin
10
- } from "./chunk-JX7URXOL.js";
11
-
12
- // src/pdf/image-regions.ts
13
- import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
14
- function multiplyTransform(m, t) {
15
- return [
16
- m[0] * t[0] + m[2] * t[1],
17
- m[1] * t[0] + m[3] * t[1],
18
- m[0] * t[2] + m[2] * t[3],
19
- m[1] * t[2] + m[3] * t[3],
20
- m[0] * t[4] + m[2] * t[5] + m[4],
21
- m[1] * t[4] + m[3] * t[5] + m[5]
22
- ];
23
- }
24
- function extractImageRegions(fnArray, argsArray) {
25
- const regions = [];
26
- let ctm = [1, 0, 0, 1, 0, 0];
27
- const stack = [];
28
- for (let i = 0; i < fnArray.length; i++) {
29
- const op = fnArray[i];
30
- switch (op) {
31
- case OPS.save:
32
- stack.push(ctm);
33
- break;
34
- case OPS.restore:
35
- ctm = stack.pop() || [1, 0, 0, 1, 0, 0];
36
- break;
37
- case OPS.transform: {
38
- const t = argsArray[i];
39
- if (Array.isArray(t) && t.length >= 6) ctm = multiplyTransform(ctm, t);
40
- break;
41
- }
42
- // Form XObject 는 /Matrix 공간에서 그린다 — pdfjs 인자 [matrix, bbox], End 에서 복원 (line-extract.ts 와 같음)
43
- case OPS.paintFormXObjectBegin: {
44
- stack.push(ctm);
45
- const m = argsArray[i]?.[0];
46
- if (Array.isArray(m) && m.length >= 6) ctm = multiplyTransform(ctm, m);
47
- break;
48
- }
49
- case OPS.paintFormXObjectEnd:
50
- ctm = stack.pop() || [1, 0, 0, 1, 0, 0];
51
- break;
52
- case OPS.paintImageXObject:
53
- case OPS.paintInlineImageXObject:
54
- case OPS.paintImageMaskXObject:
55
- case OPS.paintImageXObjectRepeat: {
56
- const corners = [[0, 0], [1, 0], [0, 1], [1, 1]];
57
- let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity;
58
- for (const [u, v] of corners) {
59
- const x = ctm[0] * u + ctm[2] * v + ctm[4];
60
- const y = ctm[1] * u + ctm[3] * v + ctm[5];
61
- if (x < x1) x1 = x;
62
- if (x > x2) x2 = x;
63
- if (y < y1) y1 = y;
64
- if (y > y2) y2 = y;
65
- }
66
- if (x2 - x1 > 0 && y2 - y1 > 0) regions.push({ x1, y1, x2, y2 });
67
- break;
68
- }
69
- }
70
- }
71
- return regions;
72
- }
10
+ } from "./chunk-RC4C7Q33.js";
73
11
 
74
12
  // src/pdf/line-extract.ts
75
- import { OPS as OPS2 } from "pdfjs-dist/legacy/build/pdf.mjs";
13
+ import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
76
14
  var ORIENTATION_TOL = 2;
77
15
  var MIN_LINE_LENGTH = 15;
78
16
  var MAX_LINE_WIDTH = 5;
@@ -185,20 +123,20 @@ function extractLines(fnArray, argsArray) {
185
123
  const op = fnArray[i];
186
124
  const args = argsArray[i];
187
125
  switch (op) {
188
- case OPS2.setLineWidth:
126
+ case OPS.setLineWidth:
189
127
  lineWidth = args[0] || 1;
190
128
  break;
191
- case OPS2.save:
129
+ case OPS.save:
192
130
  ctmStack.push(ctm.slice());
193
131
  colorStack.push(fillColor);
194
132
  alphaStack.push([fillAlpha, strokeAlpha]);
195
133
  break;
196
- case OPS2.restore:
134
+ case OPS.restore:
197
135
  ctm = ctmStack.pop() ?? [1, 0, 0, 1, 0, 0];
198
136
  fillColor = colorStack.pop() ?? 0;
199
137
  [fillAlpha, strokeAlpha] = alphaStack.pop() ?? [1, 1];
200
138
  break;
201
- case OPS2.setGState: {
139
+ case OPS.setGState: {
202
140
  const entries = args[0];
203
141
  if (!Array.isArray(entries)) break;
204
142
  for (const e of entries) {
@@ -208,15 +146,15 @@ function extractLines(fnArray, argsArray) {
208
146
  }
209
147
  break;
210
148
  }
211
- case OPS2.setFillRGBColor: {
149
+ case OPS.setFillRGBColor: {
212
150
  const c = args;
213
151
  fillColor = c[0] << 16 | c[1] << 8 | c[2];
214
152
  break;
215
153
  }
216
- case OPS2.transform:
217
- case OPS2.paintFormXObjectBegin: {
154
+ case OPS.transform:
155
+ case OPS.paintFormXObjectBegin: {
218
156
  let t = args;
219
- if (op === OPS2.paintFormXObjectBegin) {
157
+ if (op === OPS.paintFormXObjectBegin) {
220
158
  ctmStack.push(ctm.slice());
221
159
  colorStack.push(fillColor);
222
160
  alphaStack.push([fillAlpha, strokeAlpha]);
@@ -234,41 +172,41 @@ function extractLines(fnArray, argsArray) {
234
172
  ];
235
173
  break;
236
174
  }
237
- case OPS2.paintFormXObjectEnd:
175
+ case OPS.paintFormXObjectEnd:
238
176
  ctm = ctmStack.pop() ?? [1, 0, 0, 1, 0, 0];
239
177
  fillColor = colorStack.pop() ?? 0;
240
178
  [fillAlpha, strokeAlpha] = alphaStack.pop() ?? [1, 1];
241
179
  break;
242
- case OPS2.constructPath: {
180
+ case OPS.constructPath: {
243
181
  const arg0 = args[0];
244
182
  if (Array.isArray(arg0)) {
245
183
  const subOps = arg0;
246
184
  const coords = args[1];
247
185
  let ci = 0;
248
186
  for (const subOp of subOps) {
249
- if (subOp === OPS2.moveTo) {
187
+ if (subOp === OPS.moveTo) {
250
188
  curX = coords[ci++];
251
189
  curY = coords[ci++];
252
190
  pathStartX = curX;
253
191
  pathStartY = curY;
254
- } else if (subOp === OPS2.lineTo) {
192
+ } else if (subOp === OPS.lineTo) {
255
193
  const x2 = coords[ci++], y2 = coords[ci++];
256
194
  pushSeg(curX, curY, x2, y2);
257
195
  curX = x2;
258
196
  curY = y2;
259
- } else if (subOp === OPS2.rectangle) {
197
+ } else if (subOp === OPS.rectangle) {
260
198
  const rx = coords[ci++], ry = coords[ci++];
261
199
  const rw = coords[ci++], rh = coords[ci++];
262
200
  pushRectangle(rx, ry, rw, rh);
263
- } else if (subOp === OPS2.closePath) {
201
+ } else if (subOp === OPS.closePath) {
264
202
  if (curX !== pathStartX || curY !== pathStartY) {
265
203
  pushSeg(curX, curY, pathStartX, pathStartY);
266
204
  }
267
205
  curX = pathStartX;
268
206
  curY = pathStartY;
269
- } else if (subOp === OPS2.curveTo) {
207
+ } else if (subOp === OPS.curveTo) {
270
208
  ci += 6;
271
- } else if (subOp === OPS2.curveTo2 || subOp === OPS2.curveTo3) {
209
+ } else if (subOp === OPS.curveTo2 || subOp === OPS.curveTo3) {
272
210
  ci += 4;
273
211
  }
274
212
  }
@@ -306,37 +244,37 @@ function extractLines(fnArray, argsArray) {
306
244
  }
307
245
  }
308
246
  }
309
- if (afterOp === OPS2.stroke || afterOp === OPS2.closeStroke) {
247
+ if (afterOp === OPS.stroke || afterOp === OPS.closeStroke) {
310
248
  flushPath(true);
311
- } else if (afterOp === OPS2.fill || afterOp === OPS2.eoFill) {
249
+ } else if (afterOp === OPS.fill || afterOp === OPS.eoFill) {
312
250
  flushPath(true, true);
313
- } else if (afterOp === OPS2.fillStroke || afterOp === OPS2.eoFillStroke || afterOp === OPS2.closeFillStroke || afterOp === OPS2.closeEOFillStroke) {
251
+ } else if (afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke || afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {
314
252
  flushPath(true, false, true);
315
- } else if (afterOp === OPS2.endPath) {
253
+ } else if (afterOp === OPS.endPath) {
316
254
  flushPath(false);
317
255
  }
318
256
  }
319
257
  break;
320
258
  }
321
- case OPS2.stroke:
322
- case OPS2.closeStroke:
259
+ case OPS.stroke:
260
+ case OPS.closeStroke:
323
261
  flushPath(true);
324
262
  break;
325
- case OPS2.fill:
326
- case OPS2.eoFill:
263
+ case OPS.fill:
264
+ case OPS.eoFill:
327
265
  flushPath(true, true);
328
266
  break;
329
- case OPS2.fillStroke:
330
- case OPS2.eoFillStroke:
331
- case OPS2.closeFillStroke:
332
- case OPS2.closeEOFillStroke:
267
+ case OPS.fillStroke:
268
+ case OPS.eoFillStroke:
269
+ case OPS.closeFillStroke:
270
+ case OPS.closeEOFillStroke:
333
271
  flushPath(true, false, true);
334
272
  break;
335
- case OPS2.endPath:
273
+ case OPS.endPath:
336
274
  flushPath(false);
337
275
  break;
338
- case OPS2.clip:
339
- case OPS2.eoClip:
276
+ case OPS.clip:
277
+ case OPS.eoClip:
340
278
  pendingClip = true;
341
279
  break;
342
280
  }
@@ -672,6 +610,68 @@ function filterPageBorderLines(horizontals, verticals, pageWidth, pageHeight) {
672
610
  };
673
611
  }
674
612
 
613
+ // src/pdf/image-regions.ts
614
+ import { OPS as OPS2 } from "pdfjs-dist/legacy/build/pdf.mjs";
615
+ function multiplyTransform(m, t) {
616
+ return [
617
+ m[0] * t[0] + m[2] * t[1],
618
+ m[1] * t[0] + m[3] * t[1],
619
+ m[0] * t[2] + m[2] * t[3],
620
+ m[1] * t[2] + m[3] * t[3],
621
+ m[0] * t[4] + m[2] * t[5] + m[4],
622
+ m[1] * t[4] + m[3] * t[5] + m[5]
623
+ ];
624
+ }
625
+ function extractImageRegions(fnArray, argsArray) {
626
+ const regions = [];
627
+ let ctm = [1, 0, 0, 1, 0, 0];
628
+ const stack = [];
629
+ for (let i = 0; i < fnArray.length; i++) {
630
+ const op = fnArray[i];
631
+ switch (op) {
632
+ case OPS2.save:
633
+ stack.push(ctm);
634
+ break;
635
+ case OPS2.restore:
636
+ ctm = stack.pop() || [1, 0, 0, 1, 0, 0];
637
+ break;
638
+ case OPS2.transform: {
639
+ const t = argsArray[i];
640
+ if (Array.isArray(t) && t.length >= 6) ctm = multiplyTransform(ctm, t);
641
+ break;
642
+ }
643
+ // Form XObject 는 /Matrix 공간에서 그린다 — pdfjs 인자 [matrix, bbox], End 에서 복원 (line-extract.ts 와 같음)
644
+ case OPS2.paintFormXObjectBegin: {
645
+ stack.push(ctm);
646
+ const m = argsArray[i]?.[0];
647
+ if (Array.isArray(m) && m.length >= 6) ctm = multiplyTransform(ctm, m);
648
+ break;
649
+ }
650
+ case OPS2.paintFormXObjectEnd:
651
+ ctm = stack.pop() || [1, 0, 0, 1, 0, 0];
652
+ break;
653
+ case OPS2.paintImageXObject:
654
+ case OPS2.paintInlineImageXObject:
655
+ case OPS2.paintImageMaskXObject:
656
+ case OPS2.paintImageXObjectRepeat: {
657
+ const corners = [[0, 0], [1, 0], [0, 1], [1, 1]];
658
+ let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity;
659
+ for (const [u, v] of corners) {
660
+ const x = ctm[0] * u + ctm[2] * v + ctm[4];
661
+ const y = ctm[1] * u + ctm[3] * v + ctm[5];
662
+ if (x < x1) x1 = x;
663
+ if (x > x2) x2 = x;
664
+ if (y < y1) y1 = y;
665
+ if (y > y2) y2 = y;
666
+ }
667
+ if (x2 - x1 > 0 && y2 - y1 > 0) regions.push({ x1, y1, x2, y2 });
668
+ break;
669
+ }
670
+ }
671
+ }
672
+ return regions;
673
+ }
674
+
675
675
  // src/pdf/vertical-bridge.ts
676
676
  var BRIDGE_X_TOL = 1.5;
677
677
  var BRIDGE_MIN_GAP = 5;
@@ -791,6 +791,10 @@ var CUT_INTERIOR_MATCH_TOL = 8;
791
791
  var CUT_MAX_INTERIOR_OVERLAP = 0.5;
792
792
  var CUT_VCHAIN_X_TOL = 1.5;
793
793
  var CUT_VCHAIN_GAP = 1;
794
+ var NEST_STRICT_TOL = 0.5;
795
+ var NEST_MIN_GAP = 0.8;
796
+ var NEST_EDGE_COVER = 0.95;
797
+ var NEST_DOUBLE_RULE_GAP = 6;
794
798
  var VERTEX_BUCKET_CELL = 100;
795
799
  function lowerBound(sorted, key) {
796
800
  let lo = 0, hi = sorted.length;
@@ -894,9 +898,12 @@ function buildTableGrids(horizontals, verticals) {
894
898
  ...verticals.map((l, i) => ({ ...l, type: "v", id: i + horizontals.length }))
895
899
  ];
896
900
  const groups = [];
897
- for (const g of groupConnectedLines(allLines)) {
901
+ for (const g0 of groupConnectedLines(allLines)) {
902
+ const { outer: g, nested } = splitNestedBoxes(g0);
903
+ for (const n of nested) groups.push({ lines: n, fromSplit: true, nested: true });
904
+ if (g.length === 0) continue;
898
905
  const bands = splitStackedGroup(g);
899
- for (const b of bands) groups.push({ lines: b, fromSplit: bands.length > 1 });
906
+ for (const b of bands) groups.push({ lines: b, fromSplit: bands.length > 1 || nested.length > 0 });
900
907
  }
901
908
  const grids = [];
902
909
  const byY = [...vertices].sort((a, b) => a.y - b.y);
@@ -910,7 +917,7 @@ function buildTableGrids(horizontals, verticals) {
910
917
  }
911
918
  return lo;
912
919
  };
913
- for (const { lines: group, fromSplit } of groups) {
920
+ for (const { lines: group, fromSplit, nested } of groups) {
914
921
  const hLines = group.filter((l) => l.type === "h");
915
922
  const vLines = group.filter((l) => l.type === "v");
916
923
  if (hLines.length < 2 || vLines.length < 2) continue;
@@ -972,9 +979,9 @@ function buildTableGrids(horizontals, verticals) {
972
979
  x2: validColXs[validColXs.length - 1],
973
980
  y2: validRowYs[0]
974
981
  };
975
- grids.push({ rowYs: validRowYs, colXs: validColXs, bbox, vertexRadius: groupRadius });
982
+ grids.push({ rowYs: validRowYs, colXs: validColXs, bbox, vertexRadius: groupRadius, ...nested ? { lineNested: true } : {} });
976
983
  }
977
- return mergeAdjacentGrids(grids);
984
+ return [...mergeAdjacentGrids(grids.filter((g) => !g.lineNested)), ...grids.filter((g) => g.lineNested)];
978
985
  }
979
986
  var SHADE_CLIP_TOL = 1.5;
980
987
  var SHADE_CLIP_MAX_AREA = 0.5;
@@ -1153,8 +1160,52 @@ function splitStackedGroup(group) {
1153
1160
  }
1154
1161
  return bands.filter((b) => b.length > 0);
1155
1162
  }
1163
+ function splitNestedBoxes(group) {
1164
+ const comps = groupConnectedLines(group, NEST_STRICT_TOL);
1165
+ if (comps.length < 2) return { outer: group, nested: [] };
1166
+ const boxes = comps.map((c) => {
1167
+ const hs = c.filter((l) => l.type === "h"), vs = c.filter((l) => l.type === "v");
1168
+ if (hs.length < 2 || vs.length < 2) return null;
1169
+ const x1 = Math.min(...vs.map((v) => v.x1)), x2 = Math.max(...vs.map((v) => v.x1));
1170
+ const y1 = Math.min(...hs.map((h) => h.y1)), y2 = Math.max(...hs.map((h) => h.y1));
1171
+ if (x2 - x1 < 1 || y2 - y1 < 1) return null;
1172
+ const cover = (segs, a, b) => segs.reduce((s, [p, q]) => s + Math.max(0, Math.min(q, b) - Math.max(p, a)), 0) >= (b - a) * NEST_EDGE_COVER;
1173
+ const edgeH = (y) => cover(hs.filter((h) => Math.abs(h.y1 - y) <= NEST_STRICT_TOL).map((h) => [h.x1, h.x2]), x1, x2);
1174
+ const edgeV = (x) => cover(vs.filter((v) => Math.abs(v.x1 - x) <= NEST_STRICT_TOL).map((v) => [v.y1, v.y2]), y1, y2);
1175
+ return edgeH(y1) && edgeH(y2) && edgeV(x1) && edgeV(x2) ? { x1, y1, x2, y2 } : null;
1176
+ });
1177
+ const bboxOf = (c) => ({
1178
+ x1: Math.min(...c.map((l) => Math.min(l.x1, l.x2))),
1179
+ x2: Math.max(...c.map((l) => Math.max(l.x1, l.x2))),
1180
+ y1: Math.min(...c.map((l) => Math.min(l.y1, l.y2))),
1181
+ y2: Math.max(...c.map((l) => Math.max(l.y1, l.y2)))
1182
+ });
1183
+ const extents = comps.map(bboxOf);
1184
+ const nestedIdx = /* @__PURE__ */ new Set();
1185
+ comps.forEach((_, i) => {
1186
+ const b = boxes[i];
1187
+ if (!b) return;
1188
+ const e = extents[i];
1189
+ const uniq = (xs) => xs.filter((x, k) => xs.findIndex((y) => Math.abs(y - x) <= NEST_STRICT_TOL) === k).length;
1190
+ const bare = uniq(comps[i].filter((l) => l.type === "h").map((l) => l.y1)) === 2 && uniq(comps[i].filter((l) => l.type === "v").map((l) => l.x1)) === 2;
1191
+ const inside = comps.some((c, j) => {
1192
+ if (j === i || c.filter((l) => l.type === "v").length < 2 || !c.some((l) => l.type === "h")) return false;
1193
+ const p = extents[j];
1194
+ const gaps = [e.x1 - p.x1, p.x2 - e.x2, e.y1 - p.y1, p.y2 - e.y2];
1195
+ return gaps.every((g) => g >= NEST_MIN_GAP) && !(bare && gaps.every((g) => g <= NEST_DOUBLE_RULE_GAP));
1196
+ });
1197
+ if (!inside) return;
1198
+ const crossed = comps.some((c, j) => j !== i && c.some((l) => l.type === "h" ? l.y1 > e.y1 + NEST_MIN_GAP && l.y1 < e.y2 - NEST_MIN_GAP && Math.min(l.x2, e.x2) - Math.max(l.x1, e.x1) > NEST_MIN_GAP : l.x1 > e.x1 + NEST_MIN_GAP && l.x1 < e.x2 - NEST_MIN_GAP && Math.min(l.y2, e.y2) - Math.max(l.y1, e.y1) > NEST_MIN_GAP) && !(extents[j].x1 >= e.x1 && extents[j].x2 <= e.x2 && extents[j].y1 >= e.y1 && extents[j].y2 <= e.y2));
1199
+ if (!crossed) nestedIdx.add(i);
1200
+ });
1201
+ if (nestedIdx.size === 0) return { outer: group, nested: [] };
1202
+ return {
1203
+ outer: comps.filter((_, i) => !nestedIdx.has(i)).flat(),
1204
+ nested: comps.filter((_, i) => nestedIdx.has(i))
1205
+ };
1206
+ }
1156
1207
  var GROUP_BUCKET_CELL = 100;
1157
- function groupConnectedLines(lines) {
1208
+ function groupConnectedLines(lines, tol = CONNECT_TOL) {
1158
1209
  const parent = lines.map((_, i) => i);
1159
1210
  function find(x) {
1160
1211
  while (parent[x] !== x) {
@@ -1196,7 +1247,7 @@ function groupConnectedLines(lines) {
1196
1247
  const key = i * lines.length + j;
1197
1248
  if (tested.has(key)) continue;
1198
1249
  tested.add(key);
1199
- if (linesIntersect(lines[i], lines[j])) {
1250
+ if (linesIntersect(lines[i], lines[j], tol)) {
1200
1251
  union(i, j);
1201
1252
  }
1202
1253
  }
@@ -1210,19 +1261,18 @@ function groupConnectedLines(lines) {
1210
1261
  }
1211
1262
  return [...groups.values()];
1212
1263
  }
1213
- function linesIntersect(a, b) {
1264
+ function linesIntersect(a, b, tol = CONNECT_TOL) {
1214
1265
  if (a.type === b.type) {
1215
1266
  if (a.type === "h") {
1216
- if (Math.abs(a.y1 - b.y1) > CONNECT_TOL) return false;
1217
- return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - CONNECT_TOL;
1267
+ if (Math.abs(a.y1 - b.y1) > tol) return false;
1268
+ return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - tol;
1218
1269
  } else {
1219
- if (Math.abs(a.x1 - b.x1) > CONNECT_TOL) return false;
1220
- return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - CONNECT_TOL;
1270
+ if (Math.abs(a.x1 - b.x1) > tol) return false;
1271
+ return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - tol;
1221
1272
  }
1222
1273
  }
1223
1274
  const h = a.type === "h" ? a : b;
1224
1275
  const v = a.type === "h" ? b : a;
1225
- const tol = CONNECT_TOL;
1226
1276
  return v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol && h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol;
1227
1277
  }
1228
1278
 
@@ -1266,6 +1316,13 @@ function clusterCoords(values) {
1266
1316
  if (run.length > 0) out.push(run.reduce((s, x) => s + x, 0) / run.length);
1267
1317
  return out;
1268
1318
  }
1319
+ function dropSliverGaps(coords) {
1320
+ const out = [...coords];
1321
+ if (out.length > 2 && out[1] - out[0] < 1) out.splice(0, 2, (out[0] + out[1]) / 2);
1322
+ const n = out.length;
1323
+ if (n > 2 && out[n - 1] - out[n - 2] < 1) out.splice(n - 2, 2, (out[n - 2] + out[n - 1]) / 2);
1324
+ return out;
1325
+ }
1269
1326
  var nearestIndex = (coords, v) => {
1270
1327
  let best = 0;
1271
1328
  for (let i = 1; i < coords.length; i++) if (Math.abs(coords[i] - v) < Math.abs(coords[best] - v)) best = i;
@@ -1463,7 +1520,7 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, te
1463
1520
  if (!edgeStroked(strokedV, "v", r.x2, r.y1, r.y2)) invisible++;
1464
1521
  }
1465
1522
  if (invisible / edges < CLIP_MIN_INVISIBLE) continue;
1466
- const colXs = clusterCoords(members.flatMap((r) => [r.x1, r.x2]));
1523
+ const colXs = dropSliverGaps(clusterCoords(members.flatMap((r) => [r.x1, r.x2])));
1467
1524
  const rowYs = clusterCoords(members.flatMap((r) => [r.y1, r.y2])).reverse();
1468
1525
  const numRows = rowYs.length - 1, numCols = colXs.length - 1;
1469
1526
  if (numRows < 1 || numCols < 1) continue;
@@ -1860,6 +1917,7 @@ var hasBatchim = (c) => {
1860
1917
  const k = c.charCodeAt(0) - 44032;
1861
1918
  return k >= 0 && k < 11172 && k % 28 !== 0;
1862
1919
  };
1920
+ var CJK = /[\u3000-\u303F\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
1863
1921
  var CLOSE_TAIL = /[’”」』)\]〉》>]+$/;
1864
1922
  var TAIL = "[.,)\u300D\u300F\u2019\u201D]*";
1865
1923
  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}$`);
@@ -1909,6 +1967,7 @@ function wrapJoiner(prevText, nextText, lex) {
1909
1967
  const prev = prevText.replace(MARKUP, "").trimEnd(), next = nextText.replace(MARKUP, "").trimStart();
1910
1968
  const a = prev[prev.length - 1], b = next[0];
1911
1969
  if (!a || !b || /[,;:!?]/.test(a) || DATE_DAY_END.test(prev) && b !== "(") return " ";
1970
+ if (CJK.test(a) && CJK.test(b) && !/[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(prev + next)) return "";
1912
1971
  let s = prev.length;
1913
1972
  while (s > 0 && !/\s/.test(prev[s - 1])) s--;
1914
1973
  const left = prev.slice(s), right = next.match(/^\S+/)[0];
@@ -1950,6 +2009,7 @@ function joinPageBreakWraps(blocks, lex) {
1950
2009
  blocks.splice(i, 1);
1951
2010
  }
1952
2011
  }
2012
+ var HANGING = /[,。、:;!?)」』】〕》〉.]\s*$/;
1953
2013
  var BODY_FULL_TOL = 0.25;
1954
2014
  var BODY_MIN_WIDTH_EM = 12;
1955
2015
  var BODY_MAX_PITCH_EM = 2;
@@ -1958,12 +2018,15 @@ var BODY_MAX_PITCH_ABS_EM = 3.5;
1958
2018
  function bodyLineJoins(lines, lex) {
1959
2019
  let right = -Infinity;
1960
2020
  for (const l of lines) if (l.right > right) right = l.right;
2021
+ let inner = -Infinity;
2022
+ for (const l of lines) if (!HANGING.test(l.text) && l.right > inner) inner = l.right;
2023
+ const full = (l) => right - l.right < BODY_FULL_TOL * l.fontSize || right - inner <= 1.2 * l.fontSize && inner - l.right < BODY_FULL_TOL * l.fontSize;
1961
2024
  const pitch = (k) => lines[k].y - lines[k + 1].y;
1962
2025
  const sameSize = (k) => Math.abs(lines[k + 1].fontSize - lines[k].fontSize) <= 0.15 * lines[k].fontSize;
1963
2026
  let p1 = Infinity, p2 = Infinity, i1 = -1;
1964
2027
  for (let k = 0; k + 1 < lines.length; k++) {
1965
2028
  const p = pitch(k);
1966
- if (p <= 0 || !sameSize(k)) continue;
2029
+ if (p <= 0 || !sameSize(k) || Math.min(lines[k].right - lines[k].left, lines[k + 1].right - lines[k + 1].left) < 3 * lines[k].fontSize) continue;
1967
2030
  if (p < p1) {
1968
2031
  p2 = p1;
1969
2032
  p1 = p;
@@ -1976,7 +2039,7 @@ function bodyLineJoins(lines, lex) {
1976
2039
  const fs = a.fontSize;
1977
2040
  const others = i === i1 ? p2 : p1;
1978
2041
  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;
1979
- 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);
2042
+ const wraps = fs > 0 && full(a) && 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);
1980
2043
  out.push(wraps ? wrapJoiner(a.text, b.text, lex) : "\n");
1981
2044
  }
1982
2045
  return out;
@@ -2086,6 +2149,7 @@ function cellTextToString(items, wrap) {
2086
2149
  const textLines = merged.map((line) => {
2087
2150
  const s = sortLineByX(line);
2088
2151
  if (s.length === 1) return s[0].text;
2152
+ if (s.length === 2 && /^[가-힣]$/.test(s[0].text) && /^[가-힣]$/.test(s[1].text) && !!wrap?.lex && (wrap.lex.isWord(s[0].text + s[1].text) || wrap.lex.evidence(s[0].text, s[1].text) === "") && s[1].x - (s[0].x + s[0].w) >= Math.max(s[0].fontSize, s[1].fontSize) * 1.5 && s[1].x + s[1].w - s[0].x >= wrap.box.x2 - wrap.box.x1 - Math.max(s[0].fontSize, s[1].fontSize) * 2) return s[0].text + s[1].text;
2089
2153
  const evenSpaced = detectEvenSpacedItems(s);
2090
2154
  let result = s[0].text;
2091
2155
  for (let j = 1; j < s.length; j++) {
@@ -2095,7 +2159,9 @@ function cellTextToString(items, wrap) {
2095
2159
  }
2096
2160
  const gap = s[j].x - (s[j - 1].x + s[j - 1].w);
2097
2161
  const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2;
2098
- if (s[j].hasSpaceBefore && gap >= avgFs * 0.05) {
2162
+ if (isCjkLatinAutospace(s[j - 1].text, s[j].text, gap, avgFs)) {
2163
+ result += s[j].text;
2164
+ } else if (s[j].hasSpaceBefore && gap >= avgFs * 0.05) {
2099
2165
  result += " " + s[j].text;
2100
2166
  } else if (gap > spaceGapThreshold(avgFs)) {
2101
2167
  result += " " + s[j].text;
@@ -2225,7 +2291,7 @@ function mergeCellTextLines(textLines, wrap) {
2225
2291
  merged[merged.length - 1] = prev + curr.trim();
2226
2292
  } else if (!wrap && /[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {
2227
2293
  merged[merged.length - 1] = prev + curr.trim();
2228
- } 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()))) {
2294
+ } 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()) && !wrap?.wraps[i - 1]) && !(/(?:^|\s)\d{1,7}$/.test(prev) && /^\d{1,7}$/.test(curr.trim()) && !wrap?.wraps[i - 1])) {
2229
2295
  merged[merged.length - 1] = prev + curr.trim();
2230
2296
  } else {
2231
2297
  merged.push(curr);
@@ -2388,6 +2454,12 @@ function sortLineByX(items) {
2388
2454
  }
2389
2455
  return items;
2390
2456
  }
2457
+ function isCjkLatinAutospace(prevText, nextText, gap, fontSize) {
2458
+ if (!(gap < fontSize * 0.3)) return false;
2459
+ const a = prevText.slice(-1), b = nextText[0] ?? "";
2460
+ const cjk = /[\u3040-\u30ff\u3400-\u9fff\uf900-\ufaff]/, latin = /[A-Za-z0-9]/;
2461
+ return cjk.test(a) && latin.test(b) || latin.test(a) && cjk.test(b);
2462
+ }
2391
2463
  function filterHiddenText(items, pageWidth, pageHeight, originX = 0, originY = 0) {
2392
2464
  let hiddenCount = 0;
2393
2465
  const visible = [];
@@ -2463,10 +2535,14 @@ function normalizeItems(rawItems) {
2463
2535
  const scaleX = Math.hypot(i.transform[0], i.transform[1]);
2464
2536
  const scaleY = Math.hypot(i.transform[2], i.transform[3]);
2465
2537
  const fontSize = Math.round(Math.max(scaleY, scaleX));
2466
- const w = Math.round(i.width);
2538
+ let w = Math.round(i.width);
2467
2539
  const h = Math.round(i.height);
2540
+ if (/[^\u0000]\u0000+$/.test(i.str)) {
2541
+ w = Math.max(1, Math.round(i.width - Math.round(Math.max(Math.hypot(i.transform[2], i.transform[3]), Math.hypot(i.transform[0], i.transform[1]))) * 0.3));
2542
+ spacePositions.push({ x: x + w, y });
2543
+ }
2468
2544
  const isHidden = fontSize === 0 || i.width === 0 && i.str.trim().length > 0;
2469
- let text = i.str.trim();
2545
+ let text = i.str.trim().replace(/[\u2F00-\u2FD5]/g, (c) => c.normalize("NFKC"));
2470
2546
  if (/^[\d\s\-().·,☎]+$/.test(text) && /\d/.test(text) && / /.test(text)) {
2471
2547
  text = text.replace(/ /g, "");
2472
2548
  }
@@ -2687,6 +2763,10 @@ function mergeLineSimple(items) {
2687
2763
  result += sorted[i].text;
2688
2764
  continue;
2689
2765
  }
2766
+ if (isCjkLatinAutospace(sorted[i - 1].text, sorted[i].text, gap, avgFs)) {
2767
+ result += sorted[i].text;
2768
+ continue;
2769
+ }
2690
2770
  if (sorted[i].hasSpaceBefore && gap >= avgFs * 0.05) {
2691
2771
  result += " ";
2692
2772
  result += sorted[i].text;
@@ -2844,6 +2924,9 @@ function pushLineParagraphs(out, yLines, pageNum, lex) {
2844
2924
  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 };
2845
2925
  });
2846
2926
  const joins = bodyLineJoins(geo, lex);
2927
+ for (let i = 0; i + 1 < geo.length; i++) {
2928
+ if (/^\d{1,2}$/.test(geo[i].text.trim()) && geo[i].fontSize >= geo[i + 1].fontSize * 1.2) joins[i] = "\n";
2929
+ }
2847
2930
  if (hasNumberedStyledTitle(lines.map((line) => line.items))) {
2848
2931
  joins[0] = "\n";
2849
2932
  joins[1] = "\n";
@@ -2880,11 +2963,12 @@ function computeMedianFontSizeFromFreq(freq) {
2880
2963
  return sorted[sorted.length - 1][0];
2881
2964
  }
2882
2965
  function detectHeadings(blocks, medianFontSize) {
2883
- for (const block of blocks) {
2966
+ for (let bi = 0; bi < blocks.length; bi++) {
2967
+ const block = blocks[bi];
2884
2968
  if (block.type !== "paragraph" || !block.text || !block.style?.fontSize) continue;
2885
2969
  const text = block.text.trim();
2886
2970
  if (text.length === 0 || text.length > 200) continue;
2887
- if (/^\d+$/.test(text)) continue;
2971
+ if (/^\d+$/.test(text) && !isChapterNumber(block, blocks[bi + 1], medianFontSize)) continue;
2888
2972
  const ratio = block.style.fontSize / medianFontSize;
2889
2973
  let level = 0;
2890
2974
  if (ratio >= HEADING_RATIO_H1) level = 1;
@@ -2897,6 +2981,13 @@ function detectHeadings(blocks, medianFontSize) {
2897
2981
  }
2898
2982
  }
2899
2983
  }
2984
+ function isChapterNumber(block, next, medianFontSize) {
2985
+ const size = block.style?.fontSize ?? 0;
2986
+ const nextSize = next?.style?.fontSize ?? 0;
2987
+ const text = next?.text?.trim() ?? "";
2988
+ const a = block.bbox, b = next?.bbox;
2989
+ return /^\d{1,2}$/.test(block.text?.trim() ?? "") && size >= medianFontSize * 2.5 && !!next && (next.type === "paragraph" || next.type === "heading") && next.pageNumber === block.pageNumber && nextSize >= medianFontSize * HEADING_RATIO_H1 && text.length > 0 && text.length <= 80 && !/^[\d\s.]+$/.test(text) && !!a && !!b && a.y > b.y && a.y - (b.y + b.height) <= size * 2;
2990
+ }
2900
2991
  function mergeStackedHeadingLines(blocks, medianFontSize) {
2901
2992
  for (let i = 0; i < blocks.length - 1; ) {
2902
2993
  const a = blocks[i], b = blocks[i + 1];
@@ -2907,7 +2998,7 @@ function mergeStackedHeadingLines(blocks, medianFontSize) {
2907
2998
  const sameAnchor = ab && bb && Math.abs(ab.x - bb.x) <= 5;
2908
2999
  const wrapped = sameAnchor && a.style?.fontName === b.style?.fontName && af === bf && ab.width >= bb.width * 0.95 && (a.text?.trim().split(/\s+/).length ?? 0) >= 3;
2909
3000
  const displayStack = centered && gap <= Math.min(af, bf) * 0.3 && Math.max(af, bf) <= Math.min(af, bf) * 1.45;
2910
- if (a.type !== "heading" || b.type !== "heading" || !a.text || !b.text || !ab || !bb || ab.page !== bb.page || !(sameAnchor && af >= medianFontSize * 2 && bf >= medianFontSize * 2) && !wrapped && !(centered && af >= medianFontSize * 1.25 && bf >= medianFontSize * 1.25) || Math.abs(af - bf) > Math.max(af, bf) * 0.15 && !displayStack || gap < -2 || gap > Math.max(af, bf) * 0.45 || a.text.length > (centered ? 120 : 50) || b.text.length > (centered ? 120 : 50)) {
3001
+ if (a.type !== "heading" || b.type !== "heading" || !a.text || !b.text || /^\d+$/.test(a.text.trim()) || !ab || !bb || ab.page !== bb.page || !(sameAnchor && af >= medianFontSize * 2 && bf >= medianFontSize * 2) && !wrapped && !(centered && af >= medianFontSize * 1.25 && bf >= medianFontSize * 1.25) || Math.abs(af - bf) > Math.max(af, bf) * 0.15 && !displayStack || gap < -2 || gap > Math.max(af, bf) * 0.45 || a.text.length > (centered ? 120 : 50) || b.text.length > (centered ? 120 : 50)) {
2911
3002
  i++;
2912
3003
  continue;
2913
3004
  }
@@ -3089,7 +3180,7 @@ function detectPageLeadHeadings(blocks) {
3089
3180
  byPage.set(b.pageNumber ?? 0, page);
3090
3181
  }
3091
3182
  for (const page of byPage.values()) {
3092
- const content = page.filter((b) => b.type !== "image" && b.type !== "separator");
3183
+ const content = page.filter((b) => b.type !== "image" && b.type !== "separator" && !(b.type === "paragraph" && !b.style));
3093
3184
  const [first, second, third] = content;
3094
3185
  if (!first?.bbox || !first.text || !first.style?.fontSize || !second) continue;
3095
3186
  const firstText = first.text.trim();
@@ -3450,16 +3541,23 @@ function removeHeaderFooterBlocks(blocks, pageHeights, warnings) {
3450
3541
  if (entries.length === 0) continue;
3451
3542
  const patternCount = /* @__PURE__ */ new Map();
3452
3543
  const patternPages = /* @__PURE__ */ new Map();
3544
+ const patternNumbers = /* @__PURE__ */ new Map();
3453
3545
  for (const e of entries) {
3454
3546
  const norm = e.text.replace(/\d+/g, "#");
3455
3547
  patternCount.set(norm, (patternCount.get(norm) || 0) + 1);
3548
+ const nums = patternNumbers.get(norm) || /* @__PURE__ */ new Set();
3549
+ nums.add((e.text.match(/\d+/g) ?? []).join(","));
3550
+ patternNumbers.set(norm, nums);
3456
3551
  const pages = patternPages.get(norm) || /* @__PURE__ */ new Set();
3457
3552
  pages.add(e.page);
3458
3553
  patternPages.set(norm, pages);
3459
3554
  }
3460
3555
  const repeatedPatterns = /* @__PURE__ */ new Set();
3461
3556
  for (const [p, count] of patternCount) {
3462
- if (count >= MIN_REPEAT && (patternPages.get(p)?.size ?? 0) >= MIN_REPEAT) {
3557
+ const pages = [...patternPages.get(p) ?? []];
3558
+ const span = pages.length ? Math.max(...pages) - Math.min(...pages) + 1 : 0;
3559
+ const pageNumbered = (patternNumbers.get(p)?.size ?? 0) > 1;
3560
+ if (count >= MIN_REPEAT && pages.length >= MIN_REPEAT && (pages.length >= span * 0.4 || pageNumbered)) {
3463
3561
  repeatedPatterns.add(p);
3464
3562
  }
3465
3563
  }
@@ -3664,6 +3762,12 @@ function isFormulaTable(table) {
3664
3762
  const math = texts.filter((text) => /^[a-z]{1,2}$/.test(text) || /[=∂∑∫∣Γ∇√∞]/.test(text)).length;
3665
3763
  return math >= texts.length * 0.6;
3666
3764
  }
3765
+ function isExamLayoutTable(table) {
3766
+ const texts = table.cells.flat().map((cell) => cell.text.trim()).filter(Boolean);
3767
+ if (texts.length < 3 || !texts.some((text) => /[①-⑤]|문\d+)/.test(text))) return false;
3768
+ const layout = texts.filter((text) => /\$|[①-⑤]|문\d+)/.test(text) || !/[\p{L}\p{N}]/u.test(text)).length;
3769
+ return layout >= texts.length * 0.6;
3770
+ }
3667
3771
 
3668
3772
  // src/pdf/cluster-detector.ts
3669
3773
  var Y_TOL = 3;
@@ -4433,7 +4537,7 @@ function joinCellItems(items) {
4433
4537
  for (let i = 1; i < line.length; i++) {
4434
4538
  const gap = line[i].x - (line[i - 1].x + line[i - 1].w);
4435
4539
  const fs = (line[i].fontSize + line[i - 1].fontSize) / 2;
4436
- s += (line[i].hasSpaceBefore && gap >= fs * 0.05 || gap > spaceGapThreshold(fs) ? " " : "") + line[i].text;
4540
+ s += (!isCjkLatinAutospace(line[i - 1].text, line[i].text, gap, fs) && (line[i].hasSpaceBefore && gap >= fs * 0.05 || gap > spaceGapThreshold(fs)) ? " " : "") + line[i].text;
4437
4541
  }
4438
4542
  return s;
4439
4543
  }).join(" ");
@@ -4486,9 +4590,9 @@ function xyCutOrder(items, gapThreshold, depth = 0) {
4486
4590
  const hCut = findHorizontalCut(items);
4487
4591
  const vCut = findVerticalCutWithOutlierFilter(items, minGap);
4488
4592
  const hValid = hCut.gap >= minGap;
4489
- const vValid = vCut.gap >= minGap || vCut.gap >= PROSE_GUTTER_MIN_GAP && isProseGutter(items, vCut.position);
4593
+ const vValid = (vCut.gap >= minGap || vCut.gap >= PROSE_GUTTER_MIN_GAP && isProseGutter(items, vCut.position)) && !splitsSpacedLabel(items, vCut.position);
4490
4594
  let useHorizontal;
4491
- if (hValid && vValid) useHorizontal = vCut.gap <= hCut.gap * 1.5;
4595
+ if (hValid && vValid) useHorizontal = vCut.gap <= hCut.gap * 1.5 || staggeredSides(items, vCut.position);
4492
4596
  else if (hValid) useHorizontal = true;
4493
4597
  else if (vValid) useHorizontal = false;
4494
4598
  else return splitEdgeSpannedColumns(items, gapThreshold, depth) ?? [items];
@@ -4507,6 +4611,33 @@ function xyCutOrder(items, gapThreshold, depth = 0) {
4507
4611
  }
4508
4612
  return [items];
4509
4613
  }
4614
+ function splitsSpacedLabel(items, cutX) {
4615
+ const lone = (i) => /^[가-힣]$/.test(i.text.trim());
4616
+ const sameRow = (a, b) => Math.min(a.y, b.y) > Math.max(a.y - a.h, b.y - b.h);
4617
+ const left = items.filter((i) => i.x + i.w / 2 < cutX);
4618
+ const right = items.filter((i) => i.x + i.w / 2 >= cutX);
4619
+ let spaced = 0;
4620
+ for (const r of right) {
4621
+ if (right.some((o) => o !== r && o.x < r.x && sameRow(o, r))) continue;
4622
+ const row = left.filter((l2) => sameRow(l2, r));
4623
+ if (row.length === 0) continue;
4624
+ const l = row.reduce((a, b) => b.x + b.w > a.x + a.w ? b : a);
4625
+ if (!lone(l) || !lone(r)) return false;
4626
+ spaced++;
4627
+ }
4628
+ return spaced > 0;
4629
+ }
4630
+ function staggeredSides(items, cutX) {
4631
+ const left = items.filter((i) => i.x + i.w / 2 < cutX);
4632
+ const right = items.filter((i) => i.x + i.w / 2 >= cutX);
4633
+ if (left.length * right.length > 2e5) return false;
4634
+ const shareRow = (a, b) => Math.min(a.y, b.y) > Math.max(a.y - a.h, b.y - b.h);
4635
+ if (left.some((a) => right.some((b) => shareRow(a, b)))) return false;
4636
+ const sides = [...items].sort((a, b) => b.y - a.y).map((i) => i.x + i.w / 2 < cutX);
4637
+ let switches = 0;
4638
+ for (let k = 1; k < sides.length; k++) if (sides[k] !== sides[k - 1]) switches++;
4639
+ return switches >= 3;
4640
+ }
4510
4641
  function identifyCrossLayoutItems(items) {
4511
4642
  const cross = /* @__PURE__ */ new Set();
4512
4643
  if (items.length < 3) return cross;
@@ -4669,6 +4800,27 @@ function splitEdgeSpannedColumns(items, gapThreshold, depth) {
4669
4800
  ];
4670
4801
  }
4671
4802
 
4803
+ // src/pdf/blank-fills.ts
4804
+ var FILL_EDGE_GAP = 6;
4805
+ var FILL_MAX_THICKNESS = 2;
4806
+ function fillBlanks(items, horizontals, verticals = []) {
4807
+ const widened = /* @__PURE__ */ new Map();
4808
+ const used = /* @__PURE__ */ new Set();
4809
+ for (const h of horizontals) {
4810
+ if (h.lineWidth > FILL_MAX_THICKNESS || Math.abs(h.y1 - h.y2) > 1) continue;
4811
+ const x1 = Math.min(h.x1, h.x2), x2 = Math.max(h.x1, h.x2);
4812
+ const row = items.filter((i) => i.y >= h.y1 && i.y - h.y1 <= Math.max(3, i.fontSize * 0.5));
4813
+ const left = row.find((i) => i.x + i.w <= x1 + 1 && x1 - (i.x + i.w) <= FILL_EDGE_GAP);
4814
+ const right = row.find((i) => i.x >= x2 - 1 && i.x - x2 <= FILL_EDGE_GAP);
4815
+ if (!left || !right || widened.has(left) || row.some((i) => i.x < x2 && i.x + i.w > x1) || x2 - x1 < 2 * left.fontSize) continue;
4816
+ if (verticals.some((v) => (Math.abs(v.x1 - x1) <= 3 || Math.abs(v.x1 - x2) <= 3) && Math.min(v.y1, v.y2) - 3 <= h.y1 && Math.max(v.y1, v.y2) + 3 >= h.y1)) continue;
4817
+ widened.set(left, { ...left, text: left.text.trimEnd() + " ", w: x2 - left.x });
4818
+ used.add(h);
4819
+ }
4820
+ if (!used.size) return { items, horizontals };
4821
+ return { items: items.map((i) => widened.get(i) ?? i), horizontals: horizontals.filter((h) => !used.has(h)) };
4822
+ }
4823
+
4672
4824
  // src/pdf/two-column.ts
4673
4825
  var GUTTER_SCAN_LO = 0.3;
4674
4826
  var GUTTER_SCAN_HI = 0.7;
@@ -5273,6 +5425,23 @@ function headerLineAbove(free, colXs, top) {
5273
5425
  return cols;
5274
5426
  }
5275
5427
 
5428
+ // src/pdf/page-frame.ts
5429
+ function isPageFrameGrid(grid, cells, pageWidth, pageHeight, items = []) {
5430
+ const b = grid.bbox;
5431
+ if (grid.cells || b.x2 - b.x1 < pageWidth * 0.85 || b.y2 - b.y1 < pageHeight * 0.75) return false;
5432
+ const pageArea = pageWidth * pageHeight;
5433
+ const area = (c) => (c.bbox.x2 - c.bbox.x1) * (c.bbox.y2 - c.bbox.y1);
5434
+ const big = cells.filter((c) => area(c) >= pageArea * 0.15);
5435
+ if (big.reduce((s, c) => s + area(c), 0) < pageArea * 0.5) return false;
5436
+ const inner = grid.colXs.slice(1, -1);
5437
+ const inBig = items.filter((it) => big.some((c) => {
5438
+ const cx = it.x + it.w / 2, cy = it.y + it.h / 2;
5439
+ return cx > c.bbox.x1 && cx < c.bbox.x2 && cy > c.bbox.y1 && cy < c.bbox.y2;
5440
+ }));
5441
+ const crossing = inBig.filter((it) => inner.some((x) => x > it.x + 1 && x < it.x + it.w - 1)).length;
5442
+ return inner.length === 0 || crossing >= 1 && crossing >= inBig.length * 0.01;
5443
+ }
5444
+
5276
5445
  // src/pdf/open-table-ends.ts
5277
5446
  var ALIGN_TOL = 3;
5278
5447
  var MIN_RULES = 3;
@@ -5306,6 +5475,19 @@ function commonEnd(ends) {
5306
5475
  }
5307
5476
  return bestN >= MIN_REACHING ? best : null;
5308
5477
  }
5478
+ function splitBodies(g, chained) {
5479
+ if (g.length < MIN_RULES * 2) return [g];
5480
+ const x1 = Math.min(...g.map((r) => r.x1)), x2 = Math.max(...g.map((r) => r.x2));
5481
+ const sorted = [...g].sort((a, b) => b.y1 - a.y1);
5482
+ const parts = [[sorted[0]]];
5483
+ for (let i = 1; i < sorted.length; i++) {
5484
+ const upper = sorted[i - 1].y1, lower = sorted[i].y1;
5485
+ const joined = chained.some((v) => v.x1 >= x1 - ALIGN_TOL && v.x1 <= x2 + ALIGN_TOL && v.y2 >= upper - TOUCH_TOL && v.y1 <= lower + TOUCH_TOL);
5486
+ if (joined) parts[parts.length - 1].push(sorted[i]);
5487
+ else parts.push([sorted[i]]);
5488
+ }
5489
+ return parts.filter((p) => p.length >= MIN_RULES).length >= 2 ? parts : [g];
5490
+ }
5309
5491
  function closeOpenTableEnds(horizontals, verticals, textLayer = false) {
5310
5492
  if (horizontals.length < MIN_RULES || verticals.length < MIN_REACHING) return horizontals;
5311
5493
  const groups = [];
@@ -5316,7 +5498,7 @@ function closeOpenTableEnds(horizontals, verticals, textLayer = false) {
5316
5498
  }
5317
5499
  const chained = chainVerticals2(verticals);
5318
5500
  const added = [];
5319
- for (const g of groups) {
5501
+ for (const g of groups.flatMap((gr) => splitBodies(gr, chained))) {
5320
5502
  if (g.length < MIN_RULES) continue;
5321
5503
  const x1 = Math.min(...g.map((r) => r.x1)), x2 = Math.max(...g.map((r) => r.x2));
5322
5504
  const yLo = Math.min(...g.map((r) => r.y1)), yHi = Math.max(...g.map((r) => r.y1));
@@ -5924,6 +6106,9 @@ function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeigh
5924
6106
  for (const line of streamLines(items)) lex.addLine(mergeLineSimple(line));
5925
6107
  const extracted = extractLines(opList.fnArray, opList.argsArray);
5926
6108
  let { horizontals, verticals } = extracted;
6109
+ const filled = fillBlanks(items, horizontals, verticals);
6110
+ items = filled.items;
6111
+ horizontals = filled.horizontals;
5927
6112
  const prevPage = carry?.page === pageNum - 1 ? carry.clip : void 0;
5928
6113
  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 };
5929
6114
  if (carry) {
@@ -5989,6 +6174,7 @@ function markStrikethroughItems(items, horizontals) {
5989
6174
  if (line.lineWidth > STRIKE_MAX_THICKNESS) continue;
5990
6175
  const matches = [];
5991
6176
  for (const item of items) {
6177
+ if (item.fontName === "ocr") continue;
5992
6178
  const h = item.h > 0 ? item.h : item.fontSize;
5993
6179
  if (h <= 0 || item.w <= 0) continue;
5994
6180
  if (line.lineWidth > h * STRIKE_MAX_THICKNESS_RATIO) continue;
@@ -6069,16 +6255,25 @@ function isProseBoxGrid(grid, verticals, table) {
6069
6255
  if (longCells < PROSEBOX_LONG_CELL_MIN || longCells < texts.length * PROSEBOX_LONG_CELL_RATIO) return false;
6070
6256
  return true;
6071
6257
  }
6258
+ function isSparseProseGrid(table) {
6259
+ if (table.rows < 4) return false;
6260
+ const texts = table.cells.flat().map((c) => c.text.trim());
6261
+ const filled = texts.filter(Boolean);
6262
+ if (filled.length === 0 || filled.length > texts.length * 0.25) return false;
6263
+ const chars = filled.reduce((n, t) => n + t.length, 0);
6264
+ const prose = filled.filter((t) => t.length >= 60 && /[.!?。]/.test(t)).reduce((n, t) => n + t.length, 0);
6265
+ return prose >= chars * 0.6;
6266
+ }
6072
6267
  function cleanCellText(text) {
6073
6268
  const stripped = text.replace(/^[\s]*[-–—]\s*\d+\s*[-–—][\s]*$/gm, "").trim();
6074
6269
  return stripped.split("\n").map((line) => collapseEvenSpacing(line)).join("\n");
6075
6270
  }
6076
6271
  var FRAME_RECT_TOL = 1.5;
6077
- function takePendingNested(pending, cellBox) {
6272
+ function takePendingNested(pending, cellBox, clipCell) {
6078
6273
  const out = [];
6079
6274
  for (let i = pending.length - 1; i >= 0; i--) {
6080
6275
  const p = pending[i].parent;
6081
- if (Math.abs(p.x1 - cellBox.x1) <= FRAME_RECT_TOL && Math.abs(p.x2 - cellBox.x2) <= FRAME_RECT_TOL && Math.abs(p.y1 - cellBox.y1) <= FRAME_RECT_TOL && Math.abs(p.y2 - cellBox.y2) <= FRAME_RECT_TOL) {
6276
+ if (pending[i].contained ? p.x1 >= cellBox.x1 - FRAME_RECT_TOL && p.x2 <= cellBox.x2 + FRAME_RECT_TOL && p.y1 >= cellBox.y1 - FRAME_RECT_TOL && p.y2 <= cellBox.y2 + FRAME_RECT_TOL : clipCell && Math.abs(p.x1 - cellBox.x1) <= FRAME_RECT_TOL && Math.abs(p.x2 - cellBox.x2) <= FRAME_RECT_TOL && Math.abs(p.y1 - cellBox.y1) <= FRAME_RECT_TOL && Math.abs(p.y2 - cellBox.y2) <= FRAME_RECT_TOL) {
6082
6277
  out.push(pending[i].block);
6083
6278
  pending.splice(i, 1);
6084
6279
  }
@@ -6107,6 +6302,7 @@ function buildFrameCellBlocks(cellItems, nested, pageNum, wrap) {
6107
6302
  return { blocks, text };
6108
6303
  }
6109
6304
  function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, horizontals, verticals, imageRegions = [], lex, ruled = []) {
6305
+ const ocrPage = items.length > 0 && items.every((i) => i.fontName === "ocr");
6110
6306
  const blocks = [];
6111
6307
  const usedItems = /* @__PURE__ */ new Set();
6112
6308
  for (const r of ruled) {
@@ -6116,13 +6312,15 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
6116
6312
  const proseSidebars = /* @__PURE__ */ new Set();
6117
6313
  const pendingNested = [];
6118
6314
  const gridArea = (g) => (g.bbox.x2 - g.bbox.x1) * (g.bbox.y2 - g.bbox.y1);
6119
- const sortedGrids = [...grids].sort((a, b) => (b.cells ? 1 : 0) - (a.cells ? 1 : 0) || (a.cells && b.cells ? gridArea(a) - gridArea(b) : 0) || b.bbox.y2 - a.bbox.y2);
6315
+ const sortedGrids = [...grids].sort((a, b) => (b.cells ? 1 : 0) - (a.cells ? 1 : 0) || (a.cells && b.cells ? gridArea(a) - gridArea(b) : 0) || (b.lineNested ? 1 : 0) - (a.lineNested ? 1 : 0) || (a.lineNested && b.lineNested ? gridArea(a) - gridArea(b) : 0) || b.bbox.y2 - a.bbox.y2);
6120
6316
  for (const grid of sortedGrids) {
6121
6317
  const numGridRows = grid.rowYs.length - 1;
6122
6318
  const numGridCols = grid.colXs.length - 1;
6123
6319
  const gridW = grid.bbox.x2 - grid.bbox.x1;
6124
- if (!grid.cells && numGridRows === 1 && numGridCols >= 2) continue;
6125
- if (!grid.cells && numGridCols === 1 && numGridRows >= 2 && (numGridRows < 5 || gridW > pageWidth * 0.7)) continue;
6320
+ const holdsNested = !grid.cells && pendingNested.some((p) => p.contained && p.parent.x1 >= grid.bbox.x1 - FRAME_RECT_TOL && p.parent.x2 <= grid.bbox.x2 + FRAME_RECT_TOL && p.parent.y1 >= grid.bbox.y1 - FRAME_RECT_TOL && p.parent.y2 <= grid.bbox.y2 + FRAME_RECT_TOL);
6321
+ if (!grid.cells && !holdsNested && numGridRows === 1 && numGridCols >= 2) continue;
6322
+ if (!grid.cells && !holdsNested && numGridCols === 1 && numGridRows >= 2 && (numGridRows < 5 || gridW > pageWidth * 0.7)) continue;
6323
+ if (!holdsNested && isPageFrameGrid(grid, extractCells(grid, horizontals, verticals), pageWidth, pageHeight, items)) continue;
6126
6324
  const tableItems = [];
6127
6325
  const pad = 3;
6128
6326
  for (const item of items) {
@@ -6158,7 +6356,7 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
6158
6356
  let nestedAttached = false;
6159
6357
  for (const cell of cells) {
6160
6358
  const cellItems = cellTextMap.get(cell) || [];
6161
- const nested = grid.cells ? takePendingNested(pendingNested, cell.bbox) : [];
6359
+ const nested = pendingNested.length ? takePendingNested(pendingNested, cell.bbox, !!grid.cells) : [];
6162
6360
  if (nested.length > 0) {
6163
6361
  nestedAttached = true;
6164
6362
  const built = buildFrameCellBlocks(cellItems, nested, pageNum, { box: cell.bbox, lex });
@@ -6216,6 +6414,18 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
6216
6414
  finalRows++;
6217
6415
  for (const item of unitLine) usedItems.add(item);
6218
6416
  }
6417
+ const filledCells = finalGrid.flat().filter((c) => c.text.trim()).length;
6418
+ if (!rebuiltUsed && filledCells * 2 >= finalGrid.flat().length && !/^\s*\(\s*단위\s*[::]/.test(finalGrid[0]?.[0]?.text ?? "")) {
6419
+ const above = items.filter((it) => !usedItems.has(it) && it.y >= grid.bbox.y2 && it.y - grid.bbox.y2 <= 8 && it.x >= grid.bbox.x1 - 3 && it.x + it.w <= grid.bbox.x2 + 3);
6420
+ const line = above.filter((it) => Math.abs(it.y - Math.min(...above.map((a) => a.y))) <= 1).sort((a, b) => a.x - b.x);
6421
+ const text = line.map((it) => it.text).join("");
6422
+ const inset = line.length ? grid.bbox.x2 - (line[line.length - 1].x + line[line.length - 1].w) : 0;
6423
+ if (line.length && /^\s*\(\s*단위\s*[::]/.test(text) && inset >= 4.6 && inset <= 5.6) {
6424
+ finalGrid.unshift(Array.from({ length: numCols }, (_, c) => ({ text: c === 0 ? cleanCellText(text) : "", colSpan: c === 0 ? numCols : 1, rowSpan: 1 })));
6425
+ finalRows++;
6426
+ for (const item of line) usedItems.add(item);
6427
+ }
6428
+ }
6219
6429
  let semanticOneColumn = false;
6220
6430
  if (!grid.cells && numCols === 1 && numGridRows >= 2) {
6221
6431
  const populatedRows = finalGrid.filter((row) => row[0]?.text.trim());
@@ -6276,11 +6486,11 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
6276
6486
  pendingNested.push({ parent: grid.clipParent, block: { type: "table", table: irTable, pageNumber: pageNum, bbox: nb } });
6277
6487
  continue;
6278
6488
  }
6279
- if (!grid.cells && isProseBoxGrid(grid, verticals, irTable)) {
6489
+ if (!grid.cells && !nestedAttached && (isProseBoxGrid(grid, verticals, irTable) || ocrPage && isSparseProseGrid(irTable))) {
6280
6490
  for (const it of tableItems) usedItems.delete(it);
6281
6491
  continue;
6282
6492
  }
6283
- if (!grid.cells && isChartTable(irTable)) {
6493
+ if (!grid.cells && !nestedAttached && isChartTable(irTable)) {
6284
6494
  blocks.push(chartBlock(tableItems, pageNum, { page: pageNum, x: grid.bbox.x1, y: grid.bbox.y1, width: gridW, height: grid.bbox.y2 - grid.bbox.y1 }));
6285
6495
  continue;
6286
6496
  }
@@ -6318,6 +6528,10 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
6318
6528
  }
6319
6529
  continue;
6320
6530
  }
6531
+ if (grid.lineNested) {
6532
+ pendingNested.push({ parent: grid.bbox, block: { type: "table", table: irTable, pageNumber: pageNum, bbox: tableBbox }, contained: true });
6533
+ continue;
6534
+ }
6321
6535
  blocks.push({ type: "table", table: irTable, pageNumber: pageNum, bbox: tableBbox });
6322
6536
  }
6323
6537
  for (const p of pendingNested) blocks.push(p.block);
@@ -6339,7 +6553,10 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
6339
6553
  hasSpaceBefore: i.hasSpaceBefore
6340
6554
  }));
6341
6555
  const proseColumns = findTwoColumnProseCutX(clusterItems) !== null || detectPersistentColumnGutter(remaining.map((i) => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }))) !== null;
6342
- const clusterResults = proseColumns ? [] : detectClusterTables(clusterItems, pageNum);
6556
+ const clusterResults = (proseColumns ? [] : detectClusterTables(clusterItems, pageNum)).filter((cr) => {
6557
+ const b = cr.bbox;
6558
+ return !blocks.some((x) => x.type === "paragraph" && x.bbox && (x.text?.match(/\p{L}/gu)?.length ?? 0) >= 10 && Math.min(x.bbox.x + x.bbox.width, b.x + b.width) - Math.max(x.bbox.x, b.x) >= 0.8 * b.width && x.bbox.y >= b.y - 2 && x.bbox.y + x.bbox.height <= b.y + b.height + 2);
6559
+ });
6343
6560
  if (clusterResults.length > 0) {
6344
6561
  const ciToIdx = /* @__PURE__ */ new Map();
6345
6562
  for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci);
@@ -6503,6 +6720,7 @@ function clusterTableBlock(cr, source, pageNum, horizontals = []) {
6503
6720
  if (!ruled && isTableOfContents(cr.table)) return tocBlock(cr.table, pageNum, cr.bbox, dominantStyle(source));
6504
6721
  if (isChartTable(cr.table)) return chartBlock(source, pageNum, cr.bbox);
6505
6722
  if (isFormulaTable(cr.table)) return chartBlock(source, pageNum, cr.bbox);
6723
+ if (isExamLayoutTable(cr.table)) return { ...chartBlock(source, pageNum, cr.bbox), text: groupByY(source).map((line) => mergeLineSimple(line).trim()).filter(Boolean).join("\n") };
6506
6724
  return { type: "table", table: cr.table, pageNumber: pageNum, bbox: cr.bbox };
6507
6725
  }
6508
6726
  function chartBlock(source, pageNum, bbox) {
@@ -6725,6 +6943,7 @@ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTable
6725
6943
  }
6726
6944
 
6727
6945
  export {
6946
+ extractLines,
6728
6947
  extractImageRegions,
6729
6948
  filterHiddenText,
6730
6949
  collapseEvenSpacing,
@@ -6757,4 +6976,4 @@ export {
6757
6976
  trimTrailingEmptyTableCols,
6758
6977
  extractPageBlocksWithLines
6759
6978
  };
6760
- //# sourceMappingURL=chunk-FWT24USP.js.map
6979
+ //# sourceMappingURL=chunk-43FKGDDO.js.map