kordoc 4.24.6 → 4.24.8

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 (128) hide show
  1. package/README.md +7 -7
  2. package/dist/{-7J7Y3XPH.js → -TLCDUU22.js} +11 -11
  3. package/dist/{chunk-TZVVK5MI.js → chunk-5MQG3GJI.js} +2 -2
  4. package/dist/{chunk-JIU7E7Z6.cjs → chunk-73MRAQXA.cjs} +1098 -209
  5. package/dist/chunk-73MRAQXA.cjs.map +1 -0
  6. package/dist/{chunk-ISTIOEIL.cjs → chunk-7F35EPMM.cjs} +3 -3
  7. package/dist/{chunk-ISTIOEIL.cjs.map → chunk-7F35EPMM.cjs.map} +1 -1
  8. package/dist/{chunk-GI5LWQ3S.js → chunk-7JUF6G6Z.js} +2 -2
  9. package/dist/{chunk-BBKRJE57.js → chunk-7XPTKW76.js} +2 -2
  10. package/dist/{chunk-XM2Y4DB5.js → chunk-BJABLQMB.js} +2 -2
  11. package/dist/{chunk-2JDVPQ33.cjs → chunk-C3BOYTY5.cjs} +2 -2
  12. package/dist/{chunk-2JDVPQ33.cjs.map → chunk-C3BOYTY5.cjs.map} +1 -1
  13. package/dist/{chunk-EVBSSU3N.js → chunk-CB57N2MG.js} +2 -2
  14. package/dist/{chunk-WNOWRBM4.js → chunk-D3EHIHYN.js} +2 -2
  15. package/dist/{chunk-GYX26HZR.js → chunk-DPB6ZVQ5.js} +927 -38
  16. package/dist/chunk-DPB6ZVQ5.js.map +1 -0
  17. package/dist/{chunk-DFRQD254.js → chunk-F7UWIMUA.js} +2 -2
  18. package/dist/{chunk-BKTEGPEO.js → chunk-FSWJC5RR.js} +3 -3
  19. package/dist/{chunk-W75FRCAP.js → chunk-IT5XO3UG.js} +926 -37
  20. package/dist/chunk-IT5XO3UG.js.map +1 -0
  21. package/dist/{chunk-HAMIOQYO.js → chunk-J7MFKS73.js} +2 -2
  22. package/dist/{chunk-435ECXNG.cjs → chunk-JM7PZM6E.cjs} +3 -3
  23. package/dist/{chunk-435ECXNG.cjs.map → chunk-JM7PZM6E.cjs.map} +1 -1
  24. package/dist/{chunk-G343MTBX.js → chunk-K2FGQ5D5.js} +43 -15
  25. package/dist/chunk-K2FGQ5D5.js.map +1 -0
  26. package/dist/{chunk-BZW2WS44.js → chunk-KA6JZZIW.js} +83 -716
  27. package/dist/chunk-KA6JZZIW.js.map +1 -0
  28. package/dist/{chunk-RMUDX5Q2.js → chunk-N2XN6GAQ.js} +2 -2
  29. package/dist/{chunk-XVTGFK2F.js → chunk-NE6G6LCP.js} +2 -2
  30. package/dist/{chunk-AM5ALISV.cjs → chunk-NW2CKOFL.cjs} +6 -6
  31. package/dist/{chunk-AM5ALISV.cjs.map → chunk-NW2CKOFL.cjs.map} +1 -1
  32. package/dist/{chunk-5RZBLPVP.js → chunk-OUJIV2AM.js} +4 -4
  33. package/dist/chunk-OUJIV2AM.js.map +1 -0
  34. package/dist/{chunk-VMRH6UVA.js → chunk-RCDRTQ37.js} +2 -2
  35. package/dist/{chunk-KR4UKOX2.js → chunk-RCT5MO53.js} +6 -6
  36. package/dist/{chunk-3ZLS54V4.js → chunk-TDB27K2Z.js} +2 -2
  37. package/dist/{chunk-EBJWBGWF.js → chunk-TTUAJYTC.js} +83 -716
  38. package/dist/chunk-TTUAJYTC.js.map +1 -0
  39. package/dist/{chunk-5XBZJ3O4.js → chunk-WTSID6AI.js} +2 -2
  40. package/dist/{chunk-PKCOOSDT.js → chunk-XOUZC7MX.js} +2 -2
  41. package/dist/{chunk-TLJJF3J6.js → chunk-Z5DT55TW.js} +3 -3
  42. package/dist/{chunk-J4XCQNVH.cjs → chunk-ZJYNTA5R.cjs} +141 -774
  43. package/dist/chunk-ZJYNTA5R.cjs.map +1 -0
  44. package/dist/chunks-S2GGFN6W.js +10 -0
  45. package/dist/cli.js +30 -30
  46. package/dist/{formula-DKNZKJ5Z.js → formula-DZMBNPWT.js} +4 -4
  47. package/dist/{formula-T7D5JIZR.js → formula-PNMQ5JFJ.js} +3 -3
  48. package/dist/{formula-D7LBREWP.cjs → formula-ZLNSVWS6.cjs} +9 -9
  49. package/dist/{formula-D7LBREWP.cjs.map → formula-ZLNSVWS6.cjs.map} +1 -1
  50. package/dist/{image-ocr-5POXVGN2.js → image-ocr-BPH25SFW.js} +9 -9
  51. package/dist/{image-ocr-7NCSIZGA.js → image-ocr-P5Q6YXTO.js} +6 -6
  52. package/dist/{image-ocr-N3I3MUKF.cjs → image-ocr-YUXCVOOP.cjs} +17 -17
  53. package/dist/{image-ocr-N3I3MUKF.cjs.map → image-ocr-YUXCVOOP.cjs.map} +1 -1
  54. package/dist/index.cjs +729 -701
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.js +38 -10
  57. package/dist/index.js.map +1 -1
  58. package/dist/mcp.js +23 -23
  59. package/dist/{model-bundle-XT7G2IJU.js → model-bundle-XAM6JFZV.js} +5 -5
  60. package/dist/{models-AB2VO45L.js → models-6Q5ULSTP.js} +5 -5
  61. package/dist/{parse-worker-v2-YSSAUPWU.js → parse-worker-v2-ONX5QLJZ.js} +12 -12
  62. package/dist/{parser-KGDUPSA7.js → parser-54R7ENH6.js} +9 -9
  63. package/dist/{parser-ES3I54QL.js → parser-JHXN4S6L.js} +12 -12
  64. package/dist/{parser-AGDY3ZOA.cjs → parser-Z352BN56.cjs} +159 -159
  65. package/dist/{parser-AGDY3ZOA.cjs.map → parser-Z352BN56.cjs.map} +1 -1
  66. package/dist/{pdf-ocr-53WUTX6H.js → pdf-ocr-FQ4LAXM6.js} +9 -9
  67. package/dist/{pdf-ocr-7QSP7JLG.js → pdf-ocr-LFVER7IE.js} +6 -6
  68. package/dist/pdf-ocr-SMFFJYTD.cjs +31 -0
  69. package/dist/{pdf-ocr-X6OAHK46.cjs.map → pdf-ocr-SMFFJYTD.cjs.map} +1 -1
  70. package/dist/{profile-io-GGEJ3MZW.js → profile-io-YW5QWKBB.js} +3 -3
  71. package/dist/{rasterize-YXICEI5S.js → rasterize-T4S3NMQ7.js} +3 -3
  72. package/dist/{redact-doc-OYXBNTWI.js → redact-doc-CAVBR5XO.js} +11 -11
  73. package/dist/{render-4PBNDLK2.js → render-ZW52ZX6R.js} +7 -7
  74. package/dist/seal-TQ3RLVVT.js +10 -0
  75. package/dist/{setup-WHTMPK74.js → setup-7UGSJPJP.js} +3 -3
  76. package/dist/visual-6GXHKPCJ.js +21 -0
  77. package/dist/{watch-P6UWW52D.js → watch-M4A7B5WS.js} +12 -12
  78. package/package.json +4 -4
  79. package/dist/chunk-5RZBLPVP.js.map +0 -1
  80. package/dist/chunk-BZW2WS44.js.map +0 -1
  81. package/dist/chunk-EBJWBGWF.js.map +0 -1
  82. package/dist/chunk-G343MTBX.js.map +0 -1
  83. package/dist/chunk-GYX26HZR.js.map +0 -1
  84. package/dist/chunk-J4XCQNVH.cjs.map +0 -1
  85. package/dist/chunk-JIU7E7Z6.cjs.map +0 -1
  86. package/dist/chunk-W75FRCAP.js.map +0 -1
  87. package/dist/chunks-3MWJ7HOZ.js +0 -10
  88. package/dist/pdf-ocr-X6OAHK46.cjs +0 -31
  89. package/dist/seal-KJYAU23C.js +0 -10
  90. package/dist/visual-PHKJTSFR.js +0 -21
  91. /package/dist/{-7J7Y3XPH.js.map → -TLCDUU22.js.map} +0 -0
  92. /package/dist/{chunk-TZVVK5MI.js.map → chunk-5MQG3GJI.js.map} +0 -0
  93. /package/dist/{chunk-GI5LWQ3S.js.map → chunk-7JUF6G6Z.js.map} +0 -0
  94. /package/dist/{chunk-BBKRJE57.js.map → chunk-7XPTKW76.js.map} +0 -0
  95. /package/dist/{chunk-XM2Y4DB5.js.map → chunk-BJABLQMB.js.map} +0 -0
  96. /package/dist/{chunk-EVBSSU3N.js.map → chunk-CB57N2MG.js.map} +0 -0
  97. /package/dist/{chunk-WNOWRBM4.js.map → chunk-D3EHIHYN.js.map} +0 -0
  98. /package/dist/{chunk-DFRQD254.js.map → chunk-F7UWIMUA.js.map} +0 -0
  99. /package/dist/{chunk-BKTEGPEO.js.map → chunk-FSWJC5RR.js.map} +0 -0
  100. /package/dist/{chunk-HAMIOQYO.js.map → chunk-J7MFKS73.js.map} +0 -0
  101. /package/dist/{chunk-RMUDX5Q2.js.map → chunk-N2XN6GAQ.js.map} +0 -0
  102. /package/dist/{chunk-XVTGFK2F.js.map → chunk-NE6G6LCP.js.map} +0 -0
  103. /package/dist/{chunk-VMRH6UVA.js.map → chunk-RCDRTQ37.js.map} +0 -0
  104. /package/dist/{chunk-KR4UKOX2.js.map → chunk-RCT5MO53.js.map} +0 -0
  105. /package/dist/{chunk-3ZLS54V4.js.map → chunk-TDB27K2Z.js.map} +0 -0
  106. /package/dist/{chunk-5XBZJ3O4.js.map → chunk-WTSID6AI.js.map} +0 -0
  107. /package/dist/{chunk-PKCOOSDT.js.map → chunk-XOUZC7MX.js.map} +0 -0
  108. /package/dist/{chunk-TLJJF3J6.js.map → chunk-Z5DT55TW.js.map} +0 -0
  109. /package/dist/{chunks-3MWJ7HOZ.js.map → chunks-S2GGFN6W.js.map} +0 -0
  110. /package/dist/{formula-DKNZKJ5Z.js.map → formula-DZMBNPWT.js.map} +0 -0
  111. /package/dist/{formula-T7D5JIZR.js.map → formula-PNMQ5JFJ.js.map} +0 -0
  112. /package/dist/{image-ocr-5POXVGN2.js.map → image-ocr-BPH25SFW.js.map} +0 -0
  113. /package/dist/{image-ocr-7NCSIZGA.js.map → image-ocr-P5Q6YXTO.js.map} +0 -0
  114. /package/dist/{model-bundle-XT7G2IJU.js.map → model-bundle-XAM6JFZV.js.map} +0 -0
  115. /package/dist/{models-AB2VO45L.js.map → models-6Q5ULSTP.js.map} +0 -0
  116. /package/dist/{parse-worker-v2-YSSAUPWU.js.map → parse-worker-v2-ONX5QLJZ.js.map} +0 -0
  117. /package/dist/{parser-KGDUPSA7.js.map → parser-54R7ENH6.js.map} +0 -0
  118. /package/dist/{parser-ES3I54QL.js.map → parser-JHXN4S6L.js.map} +0 -0
  119. /package/dist/{pdf-ocr-53WUTX6H.js.map → pdf-ocr-FQ4LAXM6.js.map} +0 -0
  120. /package/dist/{pdf-ocr-7QSP7JLG.js.map → pdf-ocr-LFVER7IE.js.map} +0 -0
  121. /package/dist/{profile-io-GGEJ3MZW.js.map → profile-io-YW5QWKBB.js.map} +0 -0
  122. /package/dist/{rasterize-YXICEI5S.js.map → rasterize-T4S3NMQ7.js.map} +0 -0
  123. /package/dist/{redact-doc-OYXBNTWI.js.map → redact-doc-CAVBR5XO.js.map} +0 -0
  124. /package/dist/{render-4PBNDLK2.js.map → render-ZW52ZX6R.js.map} +0 -0
  125. /package/dist/{seal-KJYAU23C.js.map → seal-TQ3RLVVT.js.map} +0 -0
  126. /package/dist/{setup-WHTMPK74.js.map → setup-7UGSJPJP.js.map} +0 -0
  127. /package/dist/{visual-PHKJTSFR.js.map → visual-6GXHKPCJ.js.map} +0 -0
  128. /package/dist/{watch-P6UWW52D.js.map → watch-M4A7B5WS.js.map} +0 -0
@@ -5,15 +5,15 @@ import {
5
5
  HEADING_RATIO_H3,
6
6
  tidyScriptTags,
7
7
  toTex
8
- } from "./chunk-WNOWRBM4.js";
8
+ } from "./chunk-D3EHIHYN.js";
9
9
  import {
10
10
  ensureModelsIn,
11
11
  getModelsDir
12
- } from "./chunk-XVTGFK2F.js";
12
+ } from "./chunk-NE6G6LCP.js";
13
13
  import {
14
14
  safeMax,
15
15
  safeMin
16
- } from "./chunk-DFRQD254.js";
16
+ } from "./chunk-F7UWIMUA.js";
17
17
 
18
18
  // src/ocr/models.ts
19
19
  import { join } from "path";
@@ -146,6 +146,773 @@ function ocrModelPath(spec) {
146
146
  return join(getOcrModelsDir(), spec.filename);
147
147
  }
148
148
 
149
+ // src/ocr/line-split.ts
150
+ function grayCrop(rgba, pageW, box) {
151
+ const out = new Uint8Array(box.w * box.h);
152
+ for (let y = 0; y < box.h; y++) {
153
+ let si = ((box.y + y) * pageW + box.x) * 4;
154
+ let di = y * box.w;
155
+ for (let x = 0; x < box.w; x++, si += 4, di++) {
156
+ out[di] = rgba[si] * 77 + rgba[si + 1] * 150 + rgba[si + 2] * 29 >> 8;
157
+ }
158
+ }
159
+ return out;
160
+ }
161
+ function inkStats(gray) {
162
+ const hist = new Uint32Array(256);
163
+ for (let i = 0; i < gray.length; i++) hist[gray[i]]++;
164
+ const total = gray.length;
165
+ let sumAll = 0;
166
+ for (let v = 0; v < 256; v++) sumAll += v * hist[v];
167
+ let wB = 0, sumB = 0, best = -1, thr = 127;
168
+ for (let t = 0; t < 256; t++) {
169
+ wB += hist[t];
170
+ if (wB === 0) continue;
171
+ const wF = total - wB;
172
+ if (wF === 0) break;
173
+ sumB += t * hist[t];
174
+ const mB = sumB / wB;
175
+ const mF = (sumAll - sumB) / wF;
176
+ const between = wB * wF * (mB - mF) * (mB - mF);
177
+ if (between > best) {
178
+ best = between;
179
+ thr = t;
180
+ }
181
+ }
182
+ let nDark = 0, sDark = 0;
183
+ for (let v = 0; v <= thr; v++) {
184
+ nDark += hist[v];
185
+ sDark += v * hist[v];
186
+ }
187
+ const nLight = total - nDark;
188
+ const sLight = sumAll - sDark;
189
+ if (nDark === 0 || nLight === 0) return { threshold: thr, darkInk: true, contrast: 0, inkRatio: 0 };
190
+ const darkInk = nDark <= nLight;
191
+ return {
192
+ threshold: thr,
193
+ darkInk,
194
+ contrast: sLight / nLight - sDark / nDark,
195
+ inkRatio: (darkInk ? nDark : nLight) / total
196
+ };
197
+ }
198
+ function splitRowBands(gray, w, h, ink, minBandRatio) {
199
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
200
+ const colInk = new Uint32Array(w);
201
+ for (let y = 0; y < h; y++) {
202
+ const off = y * w;
203
+ for (let x = 0; x < w; x++) if (isInk(gray[off + x])) colInk[x]++;
204
+ }
205
+ const ruleCol = new Uint8Array(w);
206
+ let liveW = 0;
207
+ for (let x = 0; x < w; x++) {
208
+ if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
209
+ else liveW++;
210
+ }
211
+ const rowInk = new Uint32Array(h);
212
+ for (let y = 0; y < h; y++) {
213
+ let n = 0;
214
+ const off = y * w;
215
+ for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) n++;
216
+ rowInk[y] = n >= liveW * 0.85 ? 0 : n;
217
+ }
218
+ const blank = Math.max(1, Math.floor(w * 0.02));
219
+ let bands = [];
220
+ let start = -1;
221
+ for (let y = 0; y <= h; y++) {
222
+ const on = y < h && rowInk[y] > blank;
223
+ if (on && start < 0) start = y;
224
+ else if (!on && start >= 0) {
225
+ bands.push([start, y]);
226
+ start = -1;
227
+ }
228
+ }
229
+ const inkCols = (y0, y1) => {
230
+ let x0 = w, x1 = -1;
231
+ for (let y = y0; y < y1; y++) {
232
+ const off = y * w;
233
+ for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) {
234
+ if (x < x0) x0 = x;
235
+ if (x > x1) x1 = x;
236
+ }
237
+ }
238
+ return x1 >= x0 ? [x0, x1 + 1] : [0, w];
239
+ };
240
+ if (bands.length <= 1) return [{ y0: bands[0]?.[0] ?? 0, y1: bands[0]?.[1] ?? h, x0: 0, x1: w }];
241
+ const widths = bands.map(([y0, y1]) => {
242
+ const [a, b] = inkCols(y0, y1);
243
+ return b - a;
244
+ }).sort((a, b) => a - b);
245
+ const charSize = widths[widths.length >> 1];
246
+ const minBand = Math.max(3, minBandRatio * charSize);
247
+ for (let i = 0; i + 1 < bands.length; ) {
248
+ const gap = bands[i + 1][0] - bands[i][1];
249
+ if (gap <= Math.max(2, charSize * 0.15) && bands[i + 1][1] - bands[i][0] <= charSize * 1.3) {
250
+ bands.splice(i, 2, [bands[i][0], bands[i + 1][1]]);
251
+ } else i++;
252
+ }
253
+ while (bands.length > 1 && bands[0][0] <= 1 && bands[0][1] - bands[0][0] < minBand) bands.shift();
254
+ while (bands.length > 1 && bands[bands.length - 1][1] >= h - 1 && bands[bands.length - 1][1] - bands[bands.length - 1][0] < minBand) bands.pop();
255
+ for (; ; ) {
256
+ let idx = -1, minH = Infinity;
257
+ for (let i = 0; i < bands.length; i++) {
258
+ const bh = bands[i][1] - bands[i][0];
259
+ if (bh < minBand && bh < minH) {
260
+ minH = bh;
261
+ idx = i;
262
+ }
263
+ }
264
+ if (idx < 0 || bands.length === 1) break;
265
+ const gapPrev = idx > 0 ? bands[idx][0] - bands[idx - 1][1] : Infinity;
266
+ const gapNext = idx < bands.length - 1 ? bands[idx + 1][0] - bands[idx][1] : Infinity;
267
+ const j = gapPrev <= gapNext ? idx - 1 : idx + 1;
268
+ const a = Math.min(idx, j), b = Math.max(idx, j);
269
+ bands = [...bands.slice(0, a), [bands[a][0], bands[b][1]], ...bands.slice(b + 1)];
270
+ }
271
+ if (bands.length === 1) return [{ y0: bands[0][0], y1: bands[0][1], x0: 0, x1: w }];
272
+ return bands.map(([y0, y1]) => {
273
+ const [x0, x1] = inkCols(y0, y1);
274
+ return { y0, y1, x0, x1 };
275
+ });
276
+ }
277
+ function components(gray, w, h, ink) {
278
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
279
+ const comps = [];
280
+ const label = new Int32Array(w * h);
281
+ const stack = [];
282
+ for (let p0 = 0; p0 < w * h; p0++) {
283
+ if (label[p0] || !isInk(gray[p0])) continue;
284
+ const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 };
285
+ label[p0] = c.id;
286
+ stack.push(p0);
287
+ while (stack.length) {
288
+ const p = stack.pop();
289
+ const x = p % w, y = p / w | 0;
290
+ if (x < c.x0) c.x0 = x;
291
+ if (x + 1 > c.x1) c.x1 = x + 1;
292
+ if (y < c.y0) c.y0 = y;
293
+ if (y + 1 > c.y1) c.y1 = y + 1;
294
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
295
+ const nx = x + dx, ny = y + dy;
296
+ if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
297
+ const q = ny * w + nx;
298
+ if (!label[q] && isInk(gray[q])) {
299
+ label[q] = c.id;
300
+ stack.push(q);
301
+ }
302
+ }
303
+ }
304
+ if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c);
305
+ }
306
+ return { comps, label };
307
+ }
308
+ function leadingTriangle(gray, w, h, ink) {
309
+ const { comps, label } = components(gray, w, h, ink);
310
+ let charH = 0;
311
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
312
+ const first = comps.filter((c) => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0];
313
+ if (!first || charH < 8) return null;
314
+ const cw = first.x1 - first.x0, ch = first.y1 - first.y0;
315
+ if (cw < ch * 0.8 || cw > ch * 1.8) return null;
316
+ const rowSpan = (y) => {
317
+ let n = 0, lo = -1, hi = -1, runs = 0, prev = false;
318
+ for (let x = first.x0; x < first.x1; x++) {
319
+ const on = label[y * w + x] === first.id;
320
+ if (on) {
321
+ n++;
322
+ if (lo < 0) lo = x - first.x0;
323
+ hi = x - first.x0;
324
+ if (!prev) runs++;
325
+ }
326
+ prev = on;
327
+ }
328
+ return { n, lo, hi, runs };
329
+ };
330
+ const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n);
331
+ if (base < cw * 0.7) return null;
332
+ for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {
333
+ const r = rowSpan(y);
334
+ if (r.n === 0) continue;
335
+ if (r.hi - r.lo + 1 > cw * 0.4 || (r.lo + r.hi) / 2 < cw * 0.25 || (r.lo + r.hi) / 2 > cw * 0.75) return null;
336
+ }
337
+ const mid = rowSpan(first.y0 + Math.round(ch * 0.6));
338
+ if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null;
339
+ return mid.runs >= 2 ? "\u25B3" : "\u25B2";
340
+ }
341
+ function leaderRuns(gray, w, h, ink, minDots) {
342
+ const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0);
343
+ const blobs = [];
344
+ for (const c of comps) {
345
+ const last = blobs[blobs.length - 1];
346
+ if (last && c.x0 < last.x1) {
347
+ last.x1 = Math.max(last.x1, c.x1);
348
+ last.y0 = Math.min(last.y0, c.y0);
349
+ last.y1 = Math.max(last.y1, c.y1);
350
+ } else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 });
351
+ }
352
+ let charH = 0;
353
+ for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0);
354
+ if (charH < 8) return [];
355
+ const small = (c) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3;
356
+ let bandTop = h, bandBot = 0;
357
+ for (const c of comps) if (!small(c)) {
358
+ bandTop = Math.min(bandTop, c.y0);
359
+ bandBot = Math.max(bandBot, c.y1);
360
+ }
361
+ const cy = (c) => (c.y0 + c.y1) / 2;
362
+ const isDot = (b) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot;
363
+ const runs = [];
364
+ for (let i = 0; i < blobs.length; ) {
365
+ if (!isDot(blobs[i])) {
366
+ i++;
367
+ continue;
368
+ }
369
+ let j = i;
370
+ while (j + 1 < blobs.length && isDot(blobs[j + 1]) && blobs[j + 1].x0 - blobs[j].x1 <= charH && Math.abs(cy(blobs[j + 1]) - cy(blobs[i])) <= charH * 0.2) j++;
371
+ const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i]);
372
+ const onLine = comps.some((c) => !small(c) && c.y0 <= y && c.y1 >= y && (c.x1 <= x0 + 2 && c.x1 >= x0 - 2 * charH || c.x0 >= x1 - 2 && c.x0 <= x1 + 2 * charH));
373
+ const run = blobs.slice(i, j + 1).filter((b) => b.x0 > 0 && b.x1 < w);
374
+ const spread = (xs) => {
375
+ const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : void 0);
376
+ return v[v.length - 1] / Math.max(1, v[0]);
377
+ };
378
+ const even = run.length >= 2 && spread(run.map((b) => b.x1 - b.x0)) <= 2.5 && spread(run.map((b) => b.y1 - b.y0)) <= 2.5 && spread(run.slice(1).map((b, k) => b.x0 - run[k].x0)) <= 1.8;
379
+ if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1]);
380
+ i = j + 1;
381
+ }
382
+ return runs;
383
+ }
384
+ function inkBounds(gray, w, h, ink) {
385
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
386
+ const colInk = new Uint32Array(w);
387
+ const rowInk = new Uint32Array(h);
388
+ for (let y = 0; y < h; y++) {
389
+ const off = y * w;
390
+ for (let x = 0; x < w; x++) if (isInk(gray[off + x])) {
391
+ colInk[x]++;
392
+ rowInk[y]++;
393
+ }
394
+ }
395
+ const ruleCol = new Uint8Array(w), ruleRow = new Uint8Array(h);
396
+ for (let x = 0; x < w; x++) if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
397
+ for (let y = 0; y < h; y++) if (rowInk[y] >= w * 0.85) ruleRow[y] = 1;
398
+ let x0 = w, x1 = -1, y0 = h, y1 = -1;
399
+ for (let y = 0; y < h; y++) {
400
+ if (ruleRow[y]) continue;
401
+ const off = y * w;
402
+ for (let x = 0; x < w; x++) {
403
+ if (ruleCol[x] || !isInk(gray[off + x])) continue;
404
+ if (x < x0) x0 = x;
405
+ if (x > x1) x1 = x;
406
+ if (y < y0) y0 = y;
407
+ if (y > y1) y1 = y;
408
+ }
409
+ }
410
+ if (x1 < x0 || y1 < y0) return { x0: 0, y0: 0, x1: w, y1: h };
411
+ return { x0, y0, x1: x1 + 1, y1: y1 + 1 };
412
+ }
413
+ function bracketFeatures(gray, w, h, ink, cx) {
414
+ const { comps, label } = components(gray, w, h, ink);
415
+ let charH = 0;
416
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
417
+ if (charH < 8) return null;
418
+ const tol = Math.max(2, charH * 0.3);
419
+ let best = null, bestD = Infinity;
420
+ for (const c of comps) {
421
+ if (c.y1 - c.y0 < charH * 0.3 || c.x1 < cx - tol || c.x0 > cx + tol) continue;
422
+ const d = Math.abs((c.x0 + c.x1) / 2 - cx);
423
+ if (d < bestD) {
424
+ bestD = d;
425
+ best = c;
426
+ }
427
+ }
428
+ if (!best) return null;
429
+ const b = best;
430
+ const cw = b.x1 - b.x0, ch = b.y1 - b.y0;
431
+ if (cw < 3 || ch < 8) return null;
432
+ const spans = [];
433
+ for (let y = b.y0; y < b.y1; y++) {
434
+ let n = 0;
435
+ for (let x = b.x0; x < b.x1; x++) if (label[y * w + x] === b.id) n++;
436
+ spans.push(n);
437
+ }
438
+ const band = Math.max(1, Math.round(ch * 0.15));
439
+ const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
440
+ const midRows = spans.slice(Math.floor(ch * 0.3), Math.ceil(ch * 0.7)).sort((p, q) => p - q);
441
+ const stroke = midRows[midRows.length >> 1] ?? 0;
442
+ const taper = spans.filter((n) => n > stroke && n < cw).length / ch;
443
+ const fill = spans.reduce((a, n) => a + n, 0) / (cw * ch);
444
+ return { top, bottom, taper, fill, cw, ch, charH };
445
+ }
446
+ function romanStems(gray, w, h, ink, prevX, nextX) {
447
+ const { comps, label } = components(gray, w, h, ink);
448
+ let charH = 0;
449
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
450
+ if (charH < 10) return null;
451
+ const x0 = prevX + charH * 0.35, x1 = nextX - charH * 0.35;
452
+ const sel = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6 && (c.x0 + c.x1) / 2 >= x0 && (c.x0 + c.x1) / 2 < x1);
453
+ if (!sel.length) return null;
454
+ const ids = new Set(sel.map((c) => c.id));
455
+ const ux0 = Math.min(...sel.map((c) => c.x0)), ux1 = Math.max(...sel.map((c) => c.x1));
456
+ const uy0 = Math.min(...sel.map((c) => c.y0)), uy1 = Math.max(...sel.map((c) => c.y1));
457
+ const uh = uy1 - uy0;
458
+ const counts = /* @__PURE__ */ new Map();
459
+ let rows = 0;
460
+ for (let y = uy0 + Math.floor(uh * 0.25); y < uy1 - Math.floor(uh * 0.25); y++) {
461
+ let runs = 0, len = 0, thick = false;
462
+ for (let x = ux0; x <= ux1; x++) {
463
+ const on = x < ux1 && ids.has(label[y * w + x]);
464
+ if (on) len++;
465
+ else if (len) {
466
+ runs++;
467
+ if (len > uh * 0.35) thick = true;
468
+ len = 0;
469
+ }
470
+ }
471
+ rows++;
472
+ if (!thick) counts.set(runs, (counts.get(runs) ?? 0) + 1);
473
+ }
474
+ const [n, k] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [0, 0];
475
+ return n >= 1 && n <= 3 && k >= rows * 0.7 ? n : null;
476
+ }
477
+ function circledAt(gray, w, h, ink, xs) {
478
+ const { comps } = components(gray, w, h, ink);
479
+ let charH = 0;
480
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
481
+ const rings = charH < 10 ? [] : comps.filter((r) => {
482
+ const rw = r.x1 - r.x0, rh = r.y1 - r.y0;
483
+ return rh >= charH * 0.55 && rw >= rh * 0.8 && rw <= rh * 1.25 && comps.some((d) => d !== r && d.x0 > r.x0 && d.x1 < r.x1 && d.y0 > r.y0 && d.y1 < r.y1 && d.y1 - d.y0 >= rh * 0.35);
484
+ });
485
+ return xs.map((x) => rings.some((r) => x >= r.x0 && x <= r.x1));
486
+ }
487
+ function quoteHead(gray, w, h, ink, cx) {
488
+ const { comps, label } = components(gray, w, h, ink);
489
+ let charH = 0;
490
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
491
+ if (charH < 10) return null;
492
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
493
+ if (!tall.length) return null;
494
+ const mid = (Math.min(...tall.map((c) => c.y0)) + Math.max(...tall.map((c) => c.y1))) / 2;
495
+ let best = null, bestD = charH * 0.4;
496
+ for (const c of comps) {
497
+ const ch = c.y1 - c.y0, d = Math.abs((c.x0 + c.x1) / 2 - cx);
498
+ if (ch < charH * 0.12 || ch > charH * 0.5 || (c.y0 + c.y1) / 2 > mid || d > bestD) continue;
499
+ bestD = d;
500
+ best = c;
501
+ }
502
+ if (!best) return null;
503
+ let n = 0, sy = 0;
504
+ for (let y = best.y0; y < best.y1; y++) for (let x = best.x0; x < best.x1; x++) if (label[y * w + x] === best.id) {
505
+ n++;
506
+ sy += y;
507
+ }
508
+ return n ? (sy / n - best.y0) / (best.y1 - best.y0) : null;
509
+ }
510
+ function edgeTrim(gray, w, h, ink) {
511
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
512
+ const colInk = new Uint32Array(w);
513
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (isInk(gray[y * w + x])) colInk[x]++;
514
+ const ruleCol = new Uint8Array(w);
515
+ let liveW = 0;
516
+ for (let x = 0; x < w; x++) {
517
+ if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
518
+ else liveW++;
519
+ }
520
+ const blank = Math.max(1, Math.floor(w * 0.02));
521
+ const bands = [];
522
+ let start = -1;
523
+ for (let y = 0; y <= h; y++) {
524
+ let n = 0;
525
+ if (y < h) {
526
+ for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[y * w + x])) n++;
527
+ }
528
+ const on = y < h && n > blank && n < liveW * 0.85;
529
+ if (on && start < 0) start = y;
530
+ else if (!on && start >= 0) {
531
+ bands.push([start, y]);
532
+ start = -1;
533
+ }
534
+ }
535
+ let y0 = 0, y1 = h;
536
+ if (bands.length >= 2 && h >= 24) {
537
+ const mh = Math.max(...bands.map(([a, b]) => b - a));
538
+ const [top, next] = [bands[0], bands[1]], [bot, prev] = [bands[bands.length - 1], bands[bands.length - 2]];
539
+ if (top[0] <= 1 && top[1] - top[0] < mh * 0.45) y0 = top[1] + next[0] >> 1;
540
+ if (bot[1] >= h - 1 && bot[1] - bot[0] < mh * 0.45) y1 = bot[0] + prev[1] + 1 >> 1;
541
+ if (y0 > 0 || y1 < h) {
542
+ let runs = 0, len = 0;
543
+ for (let x = 0; x <= w; x++) {
544
+ let on = false;
545
+ if (x < w && !ruleCol[x]) for (let y = y0; y < y1 && !on; y++) on = isInk(gray[y * w + x]);
546
+ if (on) len++;
547
+ else {
548
+ if (len >= (y1 - y0) * 0.3) runs++;
549
+ len = 0;
550
+ }
551
+ }
552
+ if (runs < 2) {
553
+ y0 = 0;
554
+ y1 = h;
555
+ }
556
+ }
557
+ }
558
+ const textCol = (x) => {
559
+ for (let y = y0; y < y1; y++) if (isInk(gray[y * w + x])) return true;
560
+ return false;
561
+ };
562
+ const border = (x) => colInk[x] >= h * 0.95;
563
+ const gap = Math.max(2, Math.round(h * 0.1));
564
+ const clearFrom = (x, dir) => {
565
+ for (let k = 0; k < gap; k++) {
566
+ const c = x + dir * k;
567
+ if (c < 0 || c >= w || textCol(c)) return false;
568
+ }
569
+ return true;
570
+ };
571
+ let x0 = 0, x1 = w;
572
+ for (let x = 0; x < w * 0.2; x++) {
573
+ if (border(x)) {
574
+ let e = x;
575
+ while (e + 1 < w && border(e + 1)) e++;
576
+ if (clearFrom(e + 1, 1)) x0 = e + 1;
577
+ break;
578
+ }
579
+ if (textCol(x)) break;
580
+ }
581
+ for (let x = w - 1; x >= w * 0.8; x--) {
582
+ if (border(x)) {
583
+ let e = x;
584
+ while (e - 1 >= 0 && border(e - 1)) e--;
585
+ if (clearFrom(e - 1, -1)) x1 = e;
586
+ break;
587
+ }
588
+ if (textCol(x)) break;
589
+ }
590
+ return y0 > 0 || y1 < h || x0 > 0 || x1 < w ? { x0, x1, y0, y1 } : null;
591
+ }
592
+ function tallInkCount(gray, w, h, ink) {
593
+ const { comps } = components(gray, w, h, ink);
594
+ let charH = 0;
595
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
596
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.5).sort((a, b) => a.x0 - b.x0);
597
+ let n = 0, end = -1;
598
+ for (const c of tall) {
599
+ if (c.x0 >= end) n++;
600
+ end = Math.max(end, c.x1);
601
+ }
602
+ return n;
603
+ }
604
+ function gapGlyphs(gray, w, h, ink) {
605
+ const { comps, label } = components(gray, w, h, ink);
606
+ let charH = 0;
607
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
608
+ if (charH < 10) return { glyphs: [], bandH: 0 };
609
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
610
+ const bandT = Math.min(...tall.map((c) => c.y0)), bandB = Math.max(...tall.map((c) => c.y1)), bh = bandB - bandT;
611
+ const inBand = (c) => c.y1 > bandT && c.y0 < bandB;
612
+ const out = [];
613
+ for (const c of comps) {
614
+ if (!inBand(c)) continue;
615
+ const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
616
+ if (ch >= bh * 0.9 && cw <= bh * 0.15) continue;
617
+ const others = comps.filter((o) => o !== c && inBand(o) && !(o.x0 >= c.x0 && o.x1 <= c.x1 && o.y0 >= c.y0 && o.y1 <= c.y1));
618
+ const apart = !others.some((o) => o.x0 < c.x1 + 1 && o.x1 > c.x0 - 1);
619
+ const abut = !apart && ch >= bh * 0.3 && ch <= bh * 0.65 && !others.some((o) => o.x0 < c.x1 && o.x1 > c.x0);
620
+ const nearlyApart = !others.some((o) => Math.min(o.x1, c.x1) - Math.max(o.x0, c.x0) > 2);
621
+ const spans = [], runs = [];
622
+ let n = 0;
623
+ for (let y = c.y0; y < c.y1; y++) {
624
+ let s = 0, r = 0, prev = false;
625
+ for (let x = c.x0; x < c.x1; x++) {
626
+ const on = label[y * w + x] === c.id;
627
+ if (on) {
628
+ s++;
629
+ if (!prev) r++;
630
+ }
631
+ prev = on;
632
+ }
633
+ spans.push(s);
634
+ runs.push(r);
635
+ n += s;
636
+ }
637
+ const fill = n / (cw * ch), cy = ((c.y0 + c.y1) / 2 - bandT) / bh;
638
+ const band = Math.max(1, Math.round(ch * 0.15));
639
+ const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
640
+ let mark = null;
641
+ const speck = Math.max(3, bh * 0.08);
642
+ const dot = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= speck && cw >= speck && cw >= ch * 0.6 && cw <= ch * 1.6;
643
+ const small = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= 2 && cw >= 2 && cw >= ch * 0.6 && cw <= ch * 1.6 && fill >= 0.6 && cy >= 0.3 && cy <= 0.7;
644
+ if (!apart && !abut && !(nearlyApart && ch >= bh * 0.6)) {
645
+ if (small) out.push({ x0: c.x0, x1: c.x1, mark: "\xB7" });
646
+ continue;
647
+ }
648
+ const bracket = () => {
649
+ const stem = (from, to) => {
650
+ let sx = 0, k = 0;
651
+ for (let y = from; y < to; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
652
+ sx += x - c.x0;
653
+ k++;
654
+ }
655
+ return k ? sx / k / cw : 0.5;
656
+ };
657
+ if (c.y0 <= bandT + bh * 0.15 && top >= 0.7 && bottom <= 0.5 && stem(c.y0 + band, c.y1) <= 0.35) return "\u300C";
658
+ if (c.y1 >= bandB - bh * 0.15 && bottom >= 0.7 && top <= 0.5 && stem(c.y0, c.y1 - band) >= 0.65) return "\u300D";
659
+ return null;
660
+ };
661
+ if (!apart && !abut) {
662
+ } else if (abut) {
663
+ if (fill < 0.5) mark = bracket();
664
+ } else if (dot && fill >= 0.6 && cy >= 0.3 && cy <= 0.7) mark = "\xB7";
665
+ else if (small) mark = "\xB7";
666
+ else if (dot && fill >= 0.6 && cy >= 0.75 && cw >= ch * 0.8) mark = ".";
667
+ else if (ch <= bh * 0.4 && ch >= speck && cw >= ch * 0.75 && cw <= ch * 1.3 && fill < 0.6 && runs[ch >> 1] === 2 && cy >= 0.6) mark = "\u3002";
668
+ else if (ch <= bh * 0.15 && cw >= ch * 2 && cw <= bh * 0.8 && fill >= 0.7 && cy >= 0.35 && cy <= 0.75) mark = "-";
669
+ else if (ch >= bh * 0.5 && cw >= ch * 0.9 && cw <= ch * 1.4 && fill >= 0.4 && runs.every((r) => r <= 1) && top <= 0.4 && bottom >= 0.8) mark = "\u25B2";
670
+ else if (ch >= bh * 0.3 && ch <= bh * 0.65 && fill < 0.5) mark = bracket();
671
+ else if (ch >= charH * 0.6 && cw >= ch * 0.8 && cw <= ch * 1.25 && fill < 0.5 && top >= 0.8 && bottom >= 0.8 && runs[ch >> 1] === 2) mark = ch <= charH * 0.92 ? "\u25A1" : "\u25A1\0";
672
+ if (!mark && ch >= bh * 0.6 && cw >= ch * 0.25 && cw <= ch * 0.7 && fill <= 0.4 && runs.every((r) => r <= 1)) {
673
+ const mx = (y) => {
674
+ let sx = 0, k = 0;
675
+ for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
676
+ sx += x;
677
+ k++;
678
+ }
679
+ return k ? sx / k : NaN;
680
+ };
681
+ if (mx(c.y0 + band) - mx(c.y1 - 1 - band) >= cw * 0.5) mark = "/";
682
+ }
683
+ if (mark) out.push({ x0: c.x0, x1: c.x1, mark });
684
+ }
685
+ return { glyphs: out, bandH: bh };
686
+ }
687
+ function ringBullet(gray, w, h, ink, cx) {
688
+ const { comps, label } = components(gray, w, h, ink);
689
+ let charH = 0;
690
+ for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
691
+ if (charH < 10) return null;
692
+ const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
693
+ const bandT = Math.min(...tall.map((c2) => c2.y0)), bh = Math.max(...tall.map((c2) => c2.y1)) - bandT;
694
+ let best = null, bestD = bh * 0.6;
695
+ for (const c2 of comps) {
696
+ const d = Math.abs((c2.x0 + c2.x1) / 2 - cx);
697
+ if (c2.y1 - c2.y0 >= bh * 0.3 && d < bestD) {
698
+ bestD = d;
699
+ best = c2;
700
+ }
701
+ }
702
+ if (!best) return null;
703
+ const c = best, cw = c.x1 - c.x0, ch = c.y1 - c.y0;
704
+ if (cw < ch * 0.85 || cw > ch * 1.2) return null;
705
+ const runs = (xs, at) => {
706
+ let n2 = 0, len = 0, mx2 = 0, mn = Infinity;
707
+ for (const k of [...xs, -1]) {
708
+ if (k >= 0 && at(k)) {
709
+ if (!len) n2++;
710
+ len++;
711
+ mx2 = Math.max(mx2, len);
712
+ } else {
713
+ if (len) mn = Math.min(mn, len);
714
+ len = 0;
715
+ }
716
+ }
717
+ return { n: n2, mx: mx2, mn };
718
+ };
719
+ const my = c.y0 + c.y1 >> 1, mx = c.x0 + c.x1 >> 1;
720
+ const row = runs(Array.from({ length: cw }, (_, k) => c.x0 + k), (x) => label[my * w + x] === c.id);
721
+ const col = runs(Array.from({ length: ch }, (_, k) => c.y0 + k), (y) => label[y * w + mx] === c.id);
722
+ if (row.n !== 2 || col.n !== 2) return null;
723
+ let n = 0;
724
+ for (let y = c.y0; y < c.y1; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) n++;
725
+ const fill = n / (cw * ch), rel = ch / bh;
726
+ if (rel >= 0.62 && (fill <= 0.23 ? row.mx <= cw * 0.1 : fill <= 0.33 && row.mn <= cw * 0.08)) return "\u25CB";
727
+ if (rel <= 0.58 && fill >= 0.24) return "\u3147";
728
+ if (rel >= 0.6 && rel <= 0.8 && fill >= 0.3 && ((c.y0 + c.y1) / 2 - bandT) / bh >= 0.55) return "o";
729
+ return null;
730
+ }
731
+ function dashRatio(gray, w, h, ink, cx) {
732
+ const { comps } = components(gray, w, h, ink);
733
+ let charH = 0;
734
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
735
+ if (charH < 10) return null;
736
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
737
+ const bh = Math.max(...tall.map((c) => c.y1)) - Math.min(...tall.map((c) => c.y0));
738
+ const dash = comps.filter((c) => c.y1 - c.y0 <= bh * 0.25 && c.x1 - c.x0 >= (c.y1 - c.y0) * 2 && Math.abs((c.x0 + c.x1) / 2 - cx) <= bh * 0.6).sort((a, b) => Math.abs((a.x0 + a.x1) / 2 - cx) - Math.abs((b.x0 + b.x1) / 2 - cx))[0];
739
+ return dash ? (dash.x1 - dash.x0) / bh : null;
740
+ }
741
+ function discAt(gray, w, h, ink, cx) {
742
+ const { comps, label } = components(gray, w, h, ink);
743
+ let charH = 0;
744
+ for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
745
+ if (charH < 10) return false;
746
+ const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
747
+ const bh = Math.max(...tall.map((c2) => c2.y1)) - Math.min(...tall.map((c2) => c2.y0));
748
+ const c = comps.filter((o) => Math.abs((o.x0 + o.x1) / 2 - cx) <= bh * 0.4).sort((a, b) => Math.abs((a.x0 + a.x1) / 2 - cx) - Math.abs((b.x0 + b.x1) / 2 - cx))[0];
749
+ if (!c) return false;
750
+ const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
751
+ if (ch < bh * 0.45 || ch > bh * 1.05 || cw < ch * 0.8 || cw > ch * 1.25) return false;
752
+ let n = 0;
753
+ for (let y = c.y0; y < c.y1; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) n++;
754
+ const fill = n / (cw * ch);
755
+ return fill >= 0.65 && fill <= 0.9;
756
+ }
757
+ function serifOne(gray, w, h, ink, cx) {
758
+ const { comps, label } = components(gray, w, h, ink);
759
+ let charH = 0;
760
+ for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
761
+ if (charH < 10) return false;
762
+ const c = comps.filter((o) => o.x0 - 3 <= cx && o.x1 + 3 >= cx && o.y1 - o.y0 >= charH * 0.6).sort((a, b) => b.y1 - b.y0 - (a.y1 - a.y0))[0];
763
+ if (!c) return false;
764
+ const ch = c.y1 - c.y0;
765
+ const span2 = (a, b) => {
766
+ let mn = Infinity, mx = -1;
767
+ for (let y = c.y0 + Math.floor(ch * a); y < c.y0 + Math.ceil(ch * b); y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
768
+ mn = Math.min(mn, x);
769
+ mx = Math.max(mx, x);
770
+ }
771
+ return [mn, mx];
772
+ };
773
+ const [m0, m1] = span2(0.4, 0.6), [t0, t1] = span2(0, 0.2), [b0, b1] = span2(0.8, 1);
774
+ const sw = m1 - m0 + 1;
775
+ const even = (l, r) => l >= sw * 0.4 && r >= sw * 0.4 && Math.abs(l - r) <= Math.max(l, r) * 0.35;
776
+ return sw >= 2 && sw <= ch * 0.3 && even(m0 - t0, t1 - m1) && even(m0 - b0, b1 - m1);
777
+ }
778
+ function starRun(gray, w, h, ink, cx) {
779
+ const { comps, label } = components(gray, w, h, ink);
780
+ let charH = 0;
781
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
782
+ if (charH < 10) return 0;
783
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
784
+ const bandT = Math.min(...tall.map((c) => c.y0)), bh = Math.max(...tall.map((c) => c.y1)) - bandT;
785
+ const star = (c) => {
786
+ const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
787
+ if (ch < bh * 0.2 || ch > bh * 0.5 || cw < ch * 0.7 || cw > ch * 1.4 || ((c.y0 + c.y1) / 2 - bandT) / bh > 0.55) return false;
788
+ let n = 0;
789
+ for (let y = c.y0; y < c.y1; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) n++;
790
+ const fill = n / (cw * ch);
791
+ return fill >= 0.25 && fill <= 0.65;
792
+ };
793
+ const stars = comps.filter(star).sort((a, b) => a.x0 - b.x0);
794
+ const k = stars.findIndex((c) => cx >= c.x0 - (c.x1 - c.x0) && cx <= c.x1 + (c.x1 - c.x0));
795
+ if (k < 0) return 0;
796
+ let lo = k, hi = k;
797
+ while (lo > 0 && stars[lo].x0 - stars[lo - 1].x1 <= stars[lo].x1 - stars[lo].x0) lo--;
798
+ while (hi + 1 < stars.length && stars[hi + 1].x0 - stars[hi].x1 <= stars[hi].x1 - stars[hi].x0) hi++;
799
+ return hi - lo + 1;
800
+ }
801
+ var BRACKET_TAPER = 0.16;
802
+ var BRACKET_SOLID = 0.4;
803
+ var BRACKET_WIDE = 0.265;
804
+ var BRACKET_LENS = 0.35;
805
+ function bracketShape(f, close) {
806
+ const [near2, far] = close ? [f.bottom, f.top] : [f.top, f.bottom];
807
+ const paren = close ? ")" : "(";
808
+ if (near2 >= 0.7 && far <= 0.5) return f.taper < BRACKET_TAPER ? close ? "\u300D" : "\u300C" : paren;
809
+ if (near2 >= 0.7 && far >= 0.7 && f.taper >= BRACKET_TAPER) {
810
+ return f.fill >= BRACKET_SOLID && (f.cw >= f.ch * BRACKET_WIDE || f.taper >= BRACKET_LENS) ? close ? "\u3011" : "\u3010" : paren;
811
+ }
812
+ return null;
813
+ }
814
+ function leadingBullet(gray, w, h, ink, firstX) {
815
+ const { comps, label } = components(gray, w, h, ink);
816
+ let charH = 0;
817
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
818
+ if (charH < 10) return null;
819
+ const cand = comps.filter((c) => c.y1 - c.y0 >= charH * 0.15).sort((a, b) => a.x0 - b.x0);
820
+ const first = cand[0];
821
+ if (!first) return null;
822
+ const cl = cand.filter((c) => c.x0 < first.x1 && c.x1 > first.x0);
823
+ const x0 = Math.min(...cl.map((c) => c.x0)), x1 = Math.max(...cl.map((c) => c.x1)), y0 = Math.min(...cl.map((c) => c.y0)), y1 = Math.max(...cl.map((c) => c.y1));
824
+ const cw = x1 - x0, ch = y1 - y0;
825
+ if (cw < ch * 0.8 || cw > ch * 1.25) return null;
826
+ const next = cand.find((c) => c.x0 >= x1);
827
+ if (next && next.x0 - x1 < ch * 0.3) return null;
828
+ let inkN = 0;
829
+ for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) inkN++;
830
+ const fill = inkN / (cw * ch);
831
+ const rel = ch / charH;
832
+ const ring = (c) => {
833
+ const a = c.x1 - c.x0, b = c.y1 - c.y0;
834
+ return a >= b * 0.75 && a <= b * 1.33;
835
+ };
836
+ const nested = cl.length >= 2 && cl.some((a) => cl.some((b) => a !== b && b.x0 > a.x0 && b.x1 < a.x1 && b.y0 > a.y0 && b.y1 < a.y1 && ring(b) && Math.abs(b.x0 + b.x1 - (a.x0 + a.x1)) <= (a.x1 - a.x0) * 0.2 && Math.abs(b.y0 + b.y1 - (a.y0 + a.y1)) <= (a.y1 - a.y0) * 0.2));
837
+ const span2 = (y) => {
838
+ let n = 0;
839
+ for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) n++;
840
+ return n;
841
+ };
842
+ const band = Math.max(1, Math.round(ch * 0.15));
843
+ const edgeFull = (from, dir) => {
844
+ let m = 0;
845
+ for (let k = 0; k < band; k++) m = Math.max(m, span2(from + dir * k));
846
+ return m >= cw * 0.8;
847
+ };
848
+ let midRuns = 0;
849
+ {
850
+ let prev = false;
851
+ const my = y0 + (ch >> 1);
852
+ for (let x = x0; x < x1; x++) {
853
+ const on = cl.some((c) => label[my * w + x] === c.id);
854
+ if (on && !prev) midRuns++;
855
+ prev = on;
856
+ }
857
+ }
858
+ const hollowSquare = cl.length === 1 && fill < 0.5 && midRuns === 2 && edgeFull(y0, 1) && edgeFull(y1 - 1, -1);
859
+ let mark = null;
860
+ if (hollowSquare && rel >= 0.6 && rel <= 0.92) mark = "\u25A1";
861
+ else if (nested && fill < 0.45 && rel >= 0.6) mark = "\u25CE";
862
+ else if (cl.length === 1 && fill >= 0.9 && rel >= 0.28 && rel <= 0.6) mark = "\u25AA";
863
+ else if (cl.length === 1 && fill >= 0.65 && fill <= 0.88 && rel >= 0.5 && rel <= 0.8) mark = "\u25CF";
864
+ if (!mark) return null;
865
+ return { mark, covers: firstX <= x1 + ch * 0.3 };
866
+ }
867
+
868
+ // src/ocr/text-gaps.ts
869
+ var evidence = /* @__PURE__ */ new WeakMap();
870
+ var NO_GAPS = [];
871
+ function rememberOcrTextGaps(item, gaps) {
872
+ if (gaps.length) evidence.set(item, { text: item.text, gaps });
873
+ }
874
+ function ocrTextGaps(item) {
875
+ const saved = evidence.get(item);
876
+ return saved?.text === item.text ? saved.gaps : NO_GAPS;
877
+ }
878
+ function copyOcrTextGaps(source, target, scale = 1, offsetX = 0) {
879
+ const gaps = ocrTextGaps(source);
880
+ if (!gaps.length || source.text !== target.text) return;
881
+ rememberOcrTextGaps(target, scale === 1 && offsetX === 0 ? gaps : gaps.map((g) => ({ ...g, left: g.left * scale + offsetX, right: g.right * scale + offsetX })));
882
+ }
883
+ function findOcrTextGaps(rgba, pageW, box, read, text, steps, stepPx) {
884
+ if (read.replace(/\s/g, "") !== text.replace(/\s/g, "") || [...read].length !== steps.length) return [];
885
+ const visible = [...read].flatMap((c, i) => /\s/.test(c) ? [] : [(steps[i] + 0.5) * stepPx]);
886
+ if (visible.length < 2) return [];
887
+ const gray = grayCrop(rgba, pageW, box), ink = inkStats(gray);
888
+ if (!ink.darkInk || ink.contrast < 35) return [];
889
+ const counts = new Uint32Array(box.w);
890
+ for (let y = 0; y < box.h; y++) for (let x = 0; x < box.w; x++) if (gray[y * box.w + x] <= ink.threshold) counts[x]++;
891
+ const out = [];
892
+ let offset = 0, index = 0;
893
+ for (const c of text) {
894
+ if (!/\s/.test(c)) {
895
+ if (index > 0) {
896
+ const from = Math.max(0, Math.floor(visible[index - 1]) + 1), end = Math.min(box.w, Math.ceil(visible[index]));
897
+ let start = from, left = from, right = from;
898
+ for (let x = from; x <= end; x++) {
899
+ if (x === end || counts[x] > Math.max(1, Math.floor(box.h * 0.03))) {
900
+ if (x - start > right - left) {
901
+ left = start;
902
+ right = x;
903
+ }
904
+ start = x + 1;
905
+ }
906
+ }
907
+ if (right - left >= Math.max(3, box.h * 0.25)) out.push({ at: offset, left: box.x + left, right: box.x + right });
908
+ }
909
+ index++;
910
+ }
911
+ offset += c.length;
912
+ }
913
+ return out;
914
+ }
915
+
149
916
  // src/pdf/line-extract.ts
150
917
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
151
918
  var ORIENTATION_TOL = 2;
@@ -1246,7 +2013,17 @@ function dropHeadBandClipGrids(clipGrids, lineGrids) {
1246
2013
  if (clipGrids.length === 0 || lineGrids.length === 0) return clipGrids;
1247
2014
  const near2 = (a, b) => Math.abs(a - b) <= SHADE_CLIP_TOL;
1248
2015
  const overlaps = (a, b) => Math.min(a.x2, b.x2) > Math.max(a.x1, b.x1) && Math.min(a.y2, b.y2) > Math.max(a.y1, b.y1);
1249
- return clipGrids.filter((c) => c.clipParent || !c.cells?.length || !lineGrids.some((l) => l.rowYs.length > c.rowYs.length && l.colXs.length === c.colXs.length && c.colXs.every((x, k) => near2(x, l.colXs[k])) && c.rowYs.every((y, k) => near2(y, l.rowYs[k])) && !clipGrids.some((o) => o !== c && overlaps(o.bbox, l.bbox))));
2016
+ const kept = clipGrids.filter((c) => c.clipParent || !c.cells?.length || !lineGrids.some((l) => l.rowYs.length > c.rowYs.length && l.colXs.length === c.colXs.length && c.colXs.every((x, k) => near2(x, l.colXs[k])) && c.rowYs.every((y, k) => near2(y, l.rowYs[k])) && !clipGrids.some((o) => o !== c && overlaps(o.bbox, l.bbox))));
2017
+ return kept.filter((c) => !lineGrids.some((l) => headBandTiles(kept, l, near2, overlaps).includes(c)));
2018
+ }
2019
+ function headBandTiles(clips, l, near2, overlaps) {
2020
+ const onRow = (y) => l.rowYs.some((r) => near2(r, y));
2021
+ const band = clips.filter((c) => !c.clipParent && c.cells?.length && near2(c.bbox.y2, l.bbox.y2) && c.bbox.y1 > l.bbox.y1 + SHADE_CLIP_TOL && c.bbox.x1 >= l.bbox.x1 - SHADE_CLIP_TOL && c.bbox.x2 <= l.bbox.x2 + SHADE_CLIP_TOL && c.rowYs.every(onRow)).sort((a, b) => a.bbox.x1 - b.bbox.x1);
2022
+ if (band.length < 2 || !near2(band[0].bbox.x1, l.bbox.x1) || !near2(band[band.length - 1].bbox.x2, l.bbox.x2)) return [];
2023
+ if (band.some((c, k) => k > 0 && c.bbox.x1 > band[k - 1].bbox.x2 + SHADE_CLIP_TOL)) return [];
2024
+ const bottom = Math.min(...band.map((c) => c.bbox.y1));
2025
+ const body = { ...l.bbox, y2: bottom };
2026
+ return clips.some((o) => !band.includes(o) && overlaps(o.bbox, body)) ? [] : band;
1250
2027
  }
1251
2028
  function enforceMinWidth(colXs, minWidth, narrowRuns = true) {
1252
2029
  if (colXs.length <= 2) return colXs;
@@ -3698,6 +4475,59 @@ function joinClusterCellLines(lines, box, lex) {
3698
4475
  return out;
3699
4476
  }
3700
4477
 
4478
+ // src/pdf/ocr-column-cuts.ts
4479
+ var OCR_NUMERIC_CELL = /^[+-]?\d+(?:[,.]\d+)*%?$/;
4480
+ var OCR_GROUPED_NUMBER = /^[+-]?\d{1,3}(?:\s+\d{3})+(?:[.,]\d+)?%?$/;
4481
+ function supportedOcrColumnCuts(rows) {
4482
+ const cuts = /* @__PURE__ */ new Map();
4483
+ for (const head of rows[0] ?? []) {
4484
+ if (head.items.length !== 1 || head.items[0].fontName !== "ocr") continue;
4485
+ const candidates = [];
4486
+ for (const gap of ocrTextGaps(head.items[0])) {
4487
+ const text = head.items[0].text;
4488
+ if (!/\p{L}/u.test(text.slice(0, gap.at)) || !/\p{L}/u.test(text.slice(gap.at))) continue;
4489
+ const intervals = [];
4490
+ for (const row of rows.slice(1)) {
4491
+ const cell = row.find((c) => c.col === head.col);
4492
+ if (!cell || cell.items.some((it) => it.fontName !== "ocr")) continue;
4493
+ let interval;
4494
+ if (cell.items.length === 1) {
4495
+ const it = cell.items[0];
4496
+ interval = ocrTextGaps(it).find((g) => isOcrNumericSplit(it.text, g.at));
4497
+ } else if (isOcrNumericPair(cell.items)) {
4498
+ const [a, b] = [...cell.items].sort((a2, b2) => a2.x - b2.x);
4499
+ interval = { left: a.x + a.w / 2, right: b.x + b.w / 2 };
4500
+ }
4501
+ if (interval) {
4502
+ const left = Math.max(gap.left, interval.left), right = Math.min(gap.right, interval.right);
4503
+ if (right > left) intervals.push({ left, right });
4504
+ }
4505
+ }
4506
+ const events = intervals.flatMap((i) => [{ x: i.left, delta: 1 }, { x: i.right, delta: -1 }]).sort((a, b) => a.x - b.x);
4507
+ let active = 0, bestCount = 1, bestWidth = 0, cut;
4508
+ for (let i = 0; i < events.length; ) {
4509
+ const x = events[i].x;
4510
+ while (i < events.length && events[i].x === x) active += events[i++].delta;
4511
+ const width = i < events.length ? events[i].x - x : 0;
4512
+ if (active >= 2 && width > 0 && (active > bestCount || active === bestCount && width > bestWidth)) {
4513
+ bestCount = active;
4514
+ bestWidth = width;
4515
+ cut = x + width / 2;
4516
+ }
4517
+ }
4518
+ if (cut !== void 0) candidates.push(cut);
4519
+ }
4520
+ if (candidates.length === 1) cuts.set(head.col, candidates[0]);
4521
+ }
4522
+ return cuts;
4523
+ }
4524
+ function isOcrNumericPair(items) {
4525
+ return items.length === 2 && items.every((it) => it.fontName === "ocr" && OCR_NUMERIC_CELL.test(it.text.trim())) && !OCR_GROUPED_NUMBER.test([...items].sort((a, b) => a.x - b.x).map((it) => it.text.trim()).join(" "));
4526
+ }
4527
+ function isOcrNumericSplit(text, at) {
4528
+ return !OCR_GROUPED_NUMBER.test(text.trim()) && /\s/.test((text[at - 1] ?? "") + (text[at] ?? "")) && OCR_NUMERIC_CELL.test(text.slice(0, at).trim()) && OCR_NUMERIC_CELL.test(text.slice(at).trim());
4529
+ }
4530
+
3701
4531
  // src/pdf/cluster-detector.ts
3702
4532
  var Y_TOL = 3;
3703
4533
  var COL_CLUSTER_TOL = 15;
@@ -4462,17 +5292,21 @@ function assignRowItems(items, columns, numCols, headerFace) {
4462
5292
  return result;
4463
5293
  }
4464
5294
  function buildClusterTable(rows, columns, pageNum, lex) {
4465
- const numCols = columns.length;
4466
5295
  const numRows = rows.length;
4467
- if (numRows < MIN_ROWS || numCols < MIN_COLS) return null;
5296
+ if (numRows < MIN_ROWS || columns.length < MIN_COLS) return null;
5297
+ const headFaces = new Set(rows[0]?.items.map((i) => i.fontName));
5298
+ const headerFace = headFaces.size === 1 && rows.slice(1).every((row) => row.items.every((i) => !headFaces.has(i.fontName))) ? [...headFaces][0] : void 0;
5299
+ const assigned = rows.map((row, r) => assignRowItems(row.items, columns, columns.length, r === 0 ? headerFace : void 0));
5300
+ const cuts = supportedOcrColumnCuts(assigned);
5301
+ const starts = columns.map((_, c) => c + [...cuts.keys()].filter((k) => k < c).length);
5302
+ const numCols = columns.length + cuts.size;
4468
5303
  const cells = Array.from(
4469
5304
  { length: numRows },
4470
5305
  () => Array.from({ length: numCols }, () => ({ text: "", colSpan: 1, rowSpan: 1 }))
4471
5306
  );
4472
5307
  const usedItems = /* @__PURE__ */ new Set();
4473
5308
  const lines = cells.map((row) => row.map(() => []));
4474
- const headFaces = new Set(rows[0]?.items.map((i) => i.fontName));
4475
- const headerFace = headFaces.size === 1 && rows.slice(1).every((row) => row.items.every((i) => !headFaces.has(i.fontName))) ? [...headFaces][0] : void 0;
5309
+ const origins = /* @__PURE__ */ new Map();
4476
5310
  for (let r = 0; r < numRows; r++) {
4477
5311
  const row = rows[r];
4478
5312
  if (row.items.length === 1 && numCols > 1) {
@@ -4480,15 +5314,39 @@ function buildClusterTable(rows, columns, pageNum, lex) {
4480
5314
  usedItems.add(row.items[0]);
4481
5315
  continue;
4482
5316
  }
4483
- const assignments = assignRowItems(row.items, columns, numCols, r === 0 ? headerFace : void 0);
4484
- for (const { col, items } of assignments) {
5317
+ const assignments = assigned[r].flatMap(({ col, items }) => {
5318
+ const target = starts[col], cut = cuts.get(col);
5319
+ if (cut === void 0) return [{ col: target, items, span: 1 }];
5320
+ const gapAt = (it) => ocrTextGaps(it).find((g) => g.left < cut && g.right > cut && (r === 0 || isOcrNumericSplit(it.text, g.at)));
5321
+ const groupedNumber = OCR_GROUPED_NUMBER.test([...items].sort((a, b) => a.x - b.x).map((it) => it.text.trim()).join(" "));
5322
+ if (groupedNumber || !isOcrNumericPair(items) && items.some((it) => it.x < cut && it.x + it.w > cut && !gapAt(it))) return [{ col: target, items, span: 2 }];
5323
+ const parts = [[], []];
5324
+ for (const it of items) {
5325
+ const g = gapAt(it);
5326
+ if (!g) {
5327
+ parts[it.x + it.w / 2 < cut ? 0 : 1].push(it);
5328
+ continue;
5329
+ }
5330
+ const fragments = [
5331
+ { ...it, text: it.text.slice(0, g.at).trimEnd(), w: g.left - it.x },
5332
+ { ...it, text: it.text.slice(g.at).trimStart(), x: g.right, w: it.x + it.w - g.right }
5333
+ ];
5334
+ fragments.forEach((fragment, side) => {
5335
+ origins.set(fragment, it);
5336
+ parts[side].push(fragment);
5337
+ });
5338
+ }
5339
+ return parts.flatMap((part, side) => part.length ? [{ col: target + side, items: part, span: 1 }] : []);
5340
+ });
5341
+ for (const { col, items, span: span2 } of assignments) {
5342
+ cells[r][col].colSpan = span2;
4485
5343
  const cell = lines[r][col];
4486
5344
  if (cell.length) cell[cell.length - 1].spaceAfter = true;
4487
5345
  cell.push(...clusterCellLines(items));
4488
- for (const item of items) usedItems.add(item);
5346
+ for (const item of items) usedItems.add(origins.get(item) ?? item);
4489
5347
  }
4490
5348
  }
4491
- const boxes = columns.map((_, c) => {
5349
+ const boxes = Array.from({ length: numCols }, (_, c) => {
4492
5350
  const col = lines.flatMap((row) => row[c]);
4493
5351
  return { x1: Math.min(...col.map((l) => l.left)), x2: Math.max(...col.map((l) => l.right)) };
4494
5352
  });
@@ -4511,7 +5369,7 @@ function buildClusterTable(rows, columns, pageNum, lex) {
4511
5369
  if (/^[○●▶\-·]/.test(contentText)) continue;
4512
5370
  for (let pr = r - 1; pr >= 0; pr--) {
4513
5371
  if (cells[pr].some((c) => c.text.trim())) {
4514
- if (cells[pr][0].colSpan > 1) break;
5372
+ if (cells[pr].some((c) => c.colSpan > 1)) break;
4515
5373
  for (let c = 0; c < numCols; c++) {
4516
5374
  if (!cells[r][c].text.trim()) continue;
4517
5375
  lines[pr][c].push(...lines[r][c]);
@@ -8705,18 +9563,22 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
8705
9563
  let panels = null;
8706
9564
  if (remaining.length > 0) {
8707
9565
  remaining.sort((a, b) => b.y - a.y || a.x - b.x);
8708
- const clusterItems = remaining.map((i) => ({
8709
- text: i.text,
8710
- x: i.x,
8711
- y: i.y,
8712
- w: i.w,
8713
- h: i.h,
8714
- fontSize: i.fontSize,
8715
- fontName: i.fontName,
8716
- hasSpaceBefore: i.hasSpaceBefore,
8717
- syntheticSpace: i.syntheticSpace,
8718
- spaceAfter: i.spaceAfter
8719
- }));
9566
+ const clusterItems = remaining.map((i) => {
9567
+ const item = {
9568
+ text: i.text,
9569
+ x: i.x,
9570
+ y: i.y,
9571
+ w: i.w,
9572
+ h: i.h,
9573
+ fontSize: i.fontSize,
9574
+ fontName: i.fontName,
9575
+ hasSpaceBefore: i.hasSpaceBefore,
9576
+ syntheticSpace: i.syntheticSpace,
9577
+ spaceAfter: i.spaceAfter
9578
+ };
9579
+ copyOcrTextGaps(i, item);
9580
+ return item;
9581
+ });
8720
9582
  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;
8721
9583
  const clusterResults = (proseColumns ? [] : detectClusterTables(clusterItems, pageNum, void 0, lex)).filter((cr) => {
8722
9584
  const b = cr.bbox;
@@ -9032,18 +9894,22 @@ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTable
9032
9894
  if (cards) return cards.flatMap((group) => extractPageBlocksFallback(group, pageNum, false, detectTables, lex));
9033
9895
  }
9034
9896
  const blocks = [];
9035
- const clusterItems = items.map((i) => ({
9036
- text: i.text,
9037
- x: i.x,
9038
- y: i.y,
9039
- w: i.w,
9040
- h: i.h,
9041
- fontSize: i.fontSize,
9042
- fontName: i.fontName,
9043
- hasSpaceBefore: i.hasSpaceBefore,
9044
- syntheticSpace: i.syntheticSpace,
9045
- spaceAfter: i.spaceAfter
9046
- }));
9897
+ const clusterItems = items.map((i) => {
9898
+ const item = {
9899
+ text: i.text,
9900
+ x: i.x,
9901
+ y: i.y,
9902
+ w: i.w,
9903
+ h: i.h,
9904
+ fontSize: i.fontSize,
9905
+ fontName: i.fontName,
9906
+ hasSpaceBefore: i.hasSpaceBefore,
9907
+ syntheticSpace: i.syntheticSpace,
9908
+ spaceAfter: i.spaceAfter
9909
+ };
9910
+ copyOcrTextGaps(i, item);
9911
+ return item;
9912
+ });
9047
9913
  const textRects = items.map((i) => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }));
9048
9914
  const imagePanels = fullPage ? splitImagePanels(items, figures) : null;
9049
9915
  if (imagePanels) return imagePanels.flatMap((group) => extractPageBlocksFallback(group, pageNum, false, detectTables, lex));
@@ -9264,6 +10130,29 @@ export {
9264
10130
  ensureOcrModelsFor,
9265
10131
  ocrModelsCached,
9266
10132
  parseCharacterDict,
10133
+ grayCrop,
10134
+ inkStats,
10135
+ splitRowBands,
10136
+ leadingTriangle,
10137
+ leaderRuns,
10138
+ inkBounds,
10139
+ bracketFeatures,
10140
+ romanStems,
10141
+ circledAt,
10142
+ quoteHead,
10143
+ edgeTrim,
10144
+ tallInkCount,
10145
+ gapGlyphs,
10146
+ ringBullet,
10147
+ dashRatio,
10148
+ discAt,
10149
+ serifOne,
10150
+ starRun,
10151
+ bracketShape,
10152
+ leadingBullet,
10153
+ rememberOcrTextGaps,
10154
+ copyOcrTextGaps,
10155
+ findOcrTextGaps,
9267
10156
  extractLines,
9268
10157
  extractImageRegions,
9269
10158
  NO_EDGES,
@@ -9318,4 +10207,4 @@ export {
9318
10207
  UNRULED_REGION_TABLES,
9319
10208
  mergeOcrImageRegions
9320
10209
  };
9321
- //# sourceMappingURL=chunk-GYX26HZR.js.map
10210
+ //# sourceMappingURL=chunk-DPB6ZVQ5.js.map