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
@@ -6,11 +6,778 @@ import {
6
6
  HEADING_RATIO_H3,
7
7
  tidyScriptTags,
8
8
  toTex
9
- } from "./chunk-3ZLS54V4.js";
9
+ } from "./chunk-TDB27K2Z.js";
10
10
  import {
11
11
  safeMax,
12
12
  safeMin
13
- } from "./chunk-TZVVK5MI.js";
13
+ } from "./chunk-5MQG3GJI.js";
14
+
15
+ // src/ocr/line-split.ts
16
+ function grayCrop(rgba, pageW, box) {
17
+ const out = new Uint8Array(box.w * box.h);
18
+ for (let y = 0; y < box.h; y++) {
19
+ let si = ((box.y + y) * pageW + box.x) * 4;
20
+ let di = y * box.w;
21
+ for (let x = 0; x < box.w; x++, si += 4, di++) {
22
+ out[di] = rgba[si] * 77 + rgba[si + 1] * 150 + rgba[si + 2] * 29 >> 8;
23
+ }
24
+ }
25
+ return out;
26
+ }
27
+ function inkStats(gray) {
28
+ const hist = new Uint32Array(256);
29
+ for (let i = 0; i < gray.length; i++) hist[gray[i]]++;
30
+ const total = gray.length;
31
+ let sumAll = 0;
32
+ for (let v = 0; v < 256; v++) sumAll += v * hist[v];
33
+ let wB = 0, sumB = 0, best = -1, thr = 127;
34
+ for (let t = 0; t < 256; t++) {
35
+ wB += hist[t];
36
+ if (wB === 0) continue;
37
+ const wF = total - wB;
38
+ if (wF === 0) break;
39
+ sumB += t * hist[t];
40
+ const mB = sumB / wB;
41
+ const mF = (sumAll - sumB) / wF;
42
+ const between = wB * wF * (mB - mF) * (mB - mF);
43
+ if (between > best) {
44
+ best = between;
45
+ thr = t;
46
+ }
47
+ }
48
+ let nDark = 0, sDark = 0;
49
+ for (let v = 0; v <= thr; v++) {
50
+ nDark += hist[v];
51
+ sDark += v * hist[v];
52
+ }
53
+ const nLight = total - nDark;
54
+ const sLight = sumAll - sDark;
55
+ if (nDark === 0 || nLight === 0) return { threshold: thr, darkInk: true, contrast: 0, inkRatio: 0 };
56
+ const darkInk = nDark <= nLight;
57
+ return {
58
+ threshold: thr,
59
+ darkInk,
60
+ contrast: sLight / nLight - sDark / nDark,
61
+ inkRatio: (darkInk ? nDark : nLight) / total
62
+ };
63
+ }
64
+ function splitRowBands(gray, w, h, ink, minBandRatio) {
65
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
66
+ const colInk = new Uint32Array(w);
67
+ for (let y = 0; y < h; y++) {
68
+ const off = y * w;
69
+ for (let x = 0; x < w; x++) if (isInk(gray[off + x])) colInk[x]++;
70
+ }
71
+ const ruleCol = new Uint8Array(w);
72
+ let liveW = 0;
73
+ for (let x = 0; x < w; x++) {
74
+ if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
75
+ else liveW++;
76
+ }
77
+ const rowInk = new Uint32Array(h);
78
+ for (let y = 0; y < h; y++) {
79
+ let n = 0;
80
+ const off = y * w;
81
+ for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) n++;
82
+ rowInk[y] = n >= liveW * 0.85 ? 0 : n;
83
+ }
84
+ const blank = Math.max(1, Math.floor(w * 0.02));
85
+ let bands = [];
86
+ let start = -1;
87
+ for (let y = 0; y <= h; y++) {
88
+ const on = y < h && rowInk[y] > blank;
89
+ if (on && start < 0) start = y;
90
+ else if (!on && start >= 0) {
91
+ bands.push([start, y]);
92
+ start = -1;
93
+ }
94
+ }
95
+ const inkCols = (y0, y1) => {
96
+ let x0 = w, x1 = -1;
97
+ for (let y = y0; y < y1; y++) {
98
+ const off = y * w;
99
+ for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) {
100
+ if (x < x0) x0 = x;
101
+ if (x > x1) x1 = x;
102
+ }
103
+ }
104
+ return x1 >= x0 ? [x0, x1 + 1] : [0, w];
105
+ };
106
+ if (bands.length <= 1) return [{ y0: bands[0]?.[0] ?? 0, y1: bands[0]?.[1] ?? h, x0: 0, x1: w }];
107
+ const widths = bands.map(([y0, y1]) => {
108
+ const [a, b] = inkCols(y0, y1);
109
+ return b - a;
110
+ }).sort((a, b) => a - b);
111
+ const charSize = widths[widths.length >> 1];
112
+ const minBand = Math.max(3, minBandRatio * charSize);
113
+ for (let i = 0; i + 1 < bands.length; ) {
114
+ const gap = bands[i + 1][0] - bands[i][1];
115
+ if (gap <= Math.max(2, charSize * 0.15) && bands[i + 1][1] - bands[i][0] <= charSize * 1.3) {
116
+ bands.splice(i, 2, [bands[i][0], bands[i + 1][1]]);
117
+ } else i++;
118
+ }
119
+ while (bands.length > 1 && bands[0][0] <= 1 && bands[0][1] - bands[0][0] < minBand) bands.shift();
120
+ while (bands.length > 1 && bands[bands.length - 1][1] >= h - 1 && bands[bands.length - 1][1] - bands[bands.length - 1][0] < minBand) bands.pop();
121
+ for (; ; ) {
122
+ let idx = -1, minH = Infinity;
123
+ for (let i = 0; i < bands.length; i++) {
124
+ const bh = bands[i][1] - bands[i][0];
125
+ if (bh < minBand && bh < minH) {
126
+ minH = bh;
127
+ idx = i;
128
+ }
129
+ }
130
+ if (idx < 0 || bands.length === 1) break;
131
+ const gapPrev = idx > 0 ? bands[idx][0] - bands[idx - 1][1] : Infinity;
132
+ const gapNext = idx < bands.length - 1 ? bands[idx + 1][0] - bands[idx][1] : Infinity;
133
+ const j = gapPrev <= gapNext ? idx - 1 : idx + 1;
134
+ const a = Math.min(idx, j), b = Math.max(idx, j);
135
+ bands = [...bands.slice(0, a), [bands[a][0], bands[b][1]], ...bands.slice(b + 1)];
136
+ }
137
+ if (bands.length === 1) return [{ y0: bands[0][0], y1: bands[0][1], x0: 0, x1: w }];
138
+ return bands.map(([y0, y1]) => {
139
+ const [x0, x1] = inkCols(y0, y1);
140
+ return { y0, y1, x0, x1 };
141
+ });
142
+ }
143
+ function components(gray, w, h, ink) {
144
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
145
+ const comps = [];
146
+ const label = new Int32Array(w * h);
147
+ const stack = [];
148
+ for (let p0 = 0; p0 < w * h; p0++) {
149
+ if (label[p0] || !isInk(gray[p0])) continue;
150
+ const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 };
151
+ label[p0] = c.id;
152
+ stack.push(p0);
153
+ while (stack.length) {
154
+ const p = stack.pop();
155
+ const x = p % w, y = p / w | 0;
156
+ if (x < c.x0) c.x0 = x;
157
+ if (x + 1 > c.x1) c.x1 = x + 1;
158
+ if (y < c.y0) c.y0 = y;
159
+ if (y + 1 > c.y1) c.y1 = y + 1;
160
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
161
+ const nx = x + dx, ny = y + dy;
162
+ if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
163
+ const q = ny * w + nx;
164
+ if (!label[q] && isInk(gray[q])) {
165
+ label[q] = c.id;
166
+ stack.push(q);
167
+ }
168
+ }
169
+ }
170
+ if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c);
171
+ }
172
+ return { comps, label };
173
+ }
174
+ function leadingTriangle(gray, w, h, ink) {
175
+ const { comps, label } = components(gray, w, h, ink);
176
+ let charH = 0;
177
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
178
+ const first = comps.filter((c) => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0];
179
+ if (!first || charH < 8) return null;
180
+ const cw = first.x1 - first.x0, ch = first.y1 - first.y0;
181
+ if (cw < ch * 0.8 || cw > ch * 1.8) return null;
182
+ const rowSpan = (y) => {
183
+ let n = 0, lo = -1, hi = -1, runs = 0, prev = false;
184
+ for (let x = first.x0; x < first.x1; x++) {
185
+ const on = label[y * w + x] === first.id;
186
+ if (on) {
187
+ n++;
188
+ if (lo < 0) lo = x - first.x0;
189
+ hi = x - first.x0;
190
+ if (!prev) runs++;
191
+ }
192
+ prev = on;
193
+ }
194
+ return { n, lo, hi, runs };
195
+ };
196
+ const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n);
197
+ if (base < cw * 0.7) return null;
198
+ for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {
199
+ const r = rowSpan(y);
200
+ if (r.n === 0) continue;
201
+ 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;
202
+ }
203
+ const mid = rowSpan(first.y0 + Math.round(ch * 0.6));
204
+ if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null;
205
+ return mid.runs >= 2 ? "\u25B3" : "\u25B2";
206
+ }
207
+ function leaderRuns(gray, w, h, ink, minDots) {
208
+ const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0);
209
+ const blobs = [];
210
+ for (const c of comps) {
211
+ const last = blobs[blobs.length - 1];
212
+ if (last && c.x0 < last.x1) {
213
+ last.x1 = Math.max(last.x1, c.x1);
214
+ last.y0 = Math.min(last.y0, c.y0);
215
+ last.y1 = Math.max(last.y1, c.y1);
216
+ } else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 });
217
+ }
218
+ let charH = 0;
219
+ for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0);
220
+ if (charH < 8) return [];
221
+ const small = (c) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3;
222
+ let bandTop = h, bandBot = 0;
223
+ for (const c of comps) if (!small(c)) {
224
+ bandTop = Math.min(bandTop, c.y0);
225
+ bandBot = Math.max(bandBot, c.y1);
226
+ }
227
+ const cy = (c) => (c.y0 + c.y1) / 2;
228
+ const isDot = (b) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot;
229
+ const runs = [];
230
+ for (let i = 0; i < blobs.length; ) {
231
+ if (!isDot(blobs[i])) {
232
+ i++;
233
+ continue;
234
+ }
235
+ let j = i;
236
+ 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++;
237
+ const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i]);
238
+ 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));
239
+ const run = blobs.slice(i, j + 1).filter((b) => b.x0 > 0 && b.x1 < w);
240
+ const spread = (xs) => {
241
+ const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : void 0);
242
+ return v[v.length - 1] / Math.max(1, v[0]);
243
+ };
244
+ 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;
245
+ if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1]);
246
+ i = j + 1;
247
+ }
248
+ return runs;
249
+ }
250
+ function inkBounds(gray, w, h, ink) {
251
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
252
+ const colInk = new Uint32Array(w);
253
+ const rowInk = new Uint32Array(h);
254
+ for (let y = 0; y < h; y++) {
255
+ const off = y * w;
256
+ for (let x = 0; x < w; x++) if (isInk(gray[off + x])) {
257
+ colInk[x]++;
258
+ rowInk[y]++;
259
+ }
260
+ }
261
+ const ruleCol = new Uint8Array(w), ruleRow = new Uint8Array(h);
262
+ for (let x = 0; x < w; x++) if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
263
+ for (let y = 0; y < h; y++) if (rowInk[y] >= w * 0.85) ruleRow[y] = 1;
264
+ let x0 = w, x1 = -1, y0 = h, y1 = -1;
265
+ for (let y = 0; y < h; y++) {
266
+ if (ruleRow[y]) continue;
267
+ const off = y * w;
268
+ for (let x = 0; x < w; x++) {
269
+ if (ruleCol[x] || !isInk(gray[off + x])) continue;
270
+ if (x < x0) x0 = x;
271
+ if (x > x1) x1 = x;
272
+ if (y < y0) y0 = y;
273
+ if (y > y1) y1 = y;
274
+ }
275
+ }
276
+ if (x1 < x0 || y1 < y0) return { x0: 0, y0: 0, x1: w, y1: h };
277
+ return { x0, y0, x1: x1 + 1, y1: y1 + 1 };
278
+ }
279
+ function bracketFeatures(gray, w, h, ink, cx) {
280
+ const { comps, label } = components(gray, w, h, ink);
281
+ let charH = 0;
282
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
283
+ if (charH < 8) return null;
284
+ const tol = Math.max(2, charH * 0.3);
285
+ let best = null, bestD = Infinity;
286
+ for (const c of comps) {
287
+ if (c.y1 - c.y0 < charH * 0.3 || c.x1 < cx - tol || c.x0 > cx + tol) continue;
288
+ const d = Math.abs((c.x0 + c.x1) / 2 - cx);
289
+ if (d < bestD) {
290
+ bestD = d;
291
+ best = c;
292
+ }
293
+ }
294
+ if (!best) return null;
295
+ const b = best;
296
+ const cw = b.x1 - b.x0, ch = b.y1 - b.y0;
297
+ if (cw < 3 || ch < 8) return null;
298
+ const spans = [];
299
+ for (let y = b.y0; y < b.y1; y++) {
300
+ let n = 0;
301
+ for (let x = b.x0; x < b.x1; x++) if (label[y * w + x] === b.id) n++;
302
+ spans.push(n);
303
+ }
304
+ const band = Math.max(1, Math.round(ch * 0.15));
305
+ const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
306
+ const midRows = spans.slice(Math.floor(ch * 0.3), Math.ceil(ch * 0.7)).sort((p, q) => p - q);
307
+ const stroke = midRows[midRows.length >> 1] ?? 0;
308
+ const taper = spans.filter((n) => n > stroke && n < cw).length / ch;
309
+ const fill = spans.reduce((a, n) => a + n, 0) / (cw * ch);
310
+ return { top, bottom, taper, fill, cw, ch, charH };
311
+ }
312
+ function romanStems(gray, w, h, ink, prevX, nextX) {
313
+ const { comps, label } = components(gray, w, h, ink);
314
+ let charH = 0;
315
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
316
+ if (charH < 10) return null;
317
+ const x0 = prevX + charH * 0.35, x1 = nextX - charH * 0.35;
318
+ const sel = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6 && (c.x0 + c.x1) / 2 >= x0 && (c.x0 + c.x1) / 2 < x1);
319
+ if (!sel.length) return null;
320
+ const ids = new Set(sel.map((c) => c.id));
321
+ const ux0 = Math.min(...sel.map((c) => c.x0)), ux1 = Math.max(...sel.map((c) => c.x1));
322
+ const uy0 = Math.min(...sel.map((c) => c.y0)), uy1 = Math.max(...sel.map((c) => c.y1));
323
+ const uh = uy1 - uy0;
324
+ const counts = /* @__PURE__ */ new Map();
325
+ let rows = 0;
326
+ for (let y = uy0 + Math.floor(uh * 0.25); y < uy1 - Math.floor(uh * 0.25); y++) {
327
+ let runs = 0, len = 0, thick = false;
328
+ for (let x = ux0; x <= ux1; x++) {
329
+ const on = x < ux1 && ids.has(label[y * w + x]);
330
+ if (on) len++;
331
+ else if (len) {
332
+ runs++;
333
+ if (len > uh * 0.35) thick = true;
334
+ len = 0;
335
+ }
336
+ }
337
+ rows++;
338
+ if (!thick) counts.set(runs, (counts.get(runs) ?? 0) + 1);
339
+ }
340
+ const [n, k] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [0, 0];
341
+ return n >= 1 && n <= 3 && k >= rows * 0.7 ? n : null;
342
+ }
343
+ function circledAt(gray, w, h, ink, xs) {
344
+ const { comps } = components(gray, w, h, ink);
345
+ let charH = 0;
346
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
347
+ const rings = charH < 10 ? [] : comps.filter((r) => {
348
+ const rw = r.x1 - r.x0, rh = r.y1 - r.y0;
349
+ 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);
350
+ });
351
+ return xs.map((x) => rings.some((r) => x >= r.x0 && x <= r.x1));
352
+ }
353
+ function quoteHead(gray, w, h, ink, cx) {
354
+ const { comps, label } = components(gray, w, h, ink);
355
+ let charH = 0;
356
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
357
+ if (charH < 10) return null;
358
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
359
+ if (!tall.length) return null;
360
+ const mid = (Math.min(...tall.map((c) => c.y0)) + Math.max(...tall.map((c) => c.y1))) / 2;
361
+ let best = null, bestD = charH * 0.4;
362
+ for (const c of comps) {
363
+ const ch = c.y1 - c.y0, d = Math.abs((c.x0 + c.x1) / 2 - cx);
364
+ if (ch < charH * 0.12 || ch > charH * 0.5 || (c.y0 + c.y1) / 2 > mid || d > bestD) continue;
365
+ bestD = d;
366
+ best = c;
367
+ }
368
+ if (!best) return null;
369
+ let n = 0, sy = 0;
370
+ 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) {
371
+ n++;
372
+ sy += y;
373
+ }
374
+ return n ? (sy / n - best.y0) / (best.y1 - best.y0) : null;
375
+ }
376
+ function edgeTrim(gray, w, h, ink) {
377
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
378
+ const colInk = new Uint32Array(w);
379
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (isInk(gray[y * w + x])) colInk[x]++;
380
+ const ruleCol = new Uint8Array(w);
381
+ let liveW = 0;
382
+ for (let x = 0; x < w; x++) {
383
+ if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
384
+ else liveW++;
385
+ }
386
+ const blank = Math.max(1, Math.floor(w * 0.02));
387
+ const bands = [];
388
+ let start = -1;
389
+ for (let y = 0; y <= h; y++) {
390
+ let n = 0;
391
+ if (y < h) {
392
+ for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[y * w + x])) n++;
393
+ }
394
+ const on = y < h && n > blank && n < liveW * 0.85;
395
+ if (on && start < 0) start = y;
396
+ else if (!on && start >= 0) {
397
+ bands.push([start, y]);
398
+ start = -1;
399
+ }
400
+ }
401
+ let y0 = 0, y1 = h;
402
+ if (bands.length >= 2 && h >= 24) {
403
+ const mh = Math.max(...bands.map(([a, b]) => b - a));
404
+ const [top, next] = [bands[0], bands[1]], [bot, prev] = [bands[bands.length - 1], bands[bands.length - 2]];
405
+ if (top[0] <= 1 && top[1] - top[0] < mh * 0.45) y0 = top[1] + next[0] >> 1;
406
+ if (bot[1] >= h - 1 && bot[1] - bot[0] < mh * 0.45) y1 = bot[0] + prev[1] + 1 >> 1;
407
+ if (y0 > 0 || y1 < h) {
408
+ let runs = 0, len = 0;
409
+ for (let x = 0; x <= w; x++) {
410
+ let on = false;
411
+ if (x < w && !ruleCol[x]) for (let y = y0; y < y1 && !on; y++) on = isInk(gray[y * w + x]);
412
+ if (on) len++;
413
+ else {
414
+ if (len >= (y1 - y0) * 0.3) runs++;
415
+ len = 0;
416
+ }
417
+ }
418
+ if (runs < 2) {
419
+ y0 = 0;
420
+ y1 = h;
421
+ }
422
+ }
423
+ }
424
+ const textCol = (x) => {
425
+ for (let y = y0; y < y1; y++) if (isInk(gray[y * w + x])) return true;
426
+ return false;
427
+ };
428
+ const border = (x) => colInk[x] >= h * 0.95;
429
+ const gap = Math.max(2, Math.round(h * 0.1));
430
+ const clearFrom = (x, dir) => {
431
+ for (let k = 0; k < gap; k++) {
432
+ const c = x + dir * k;
433
+ if (c < 0 || c >= w || textCol(c)) return false;
434
+ }
435
+ return true;
436
+ };
437
+ let x0 = 0, x1 = w;
438
+ for (let x = 0; x < w * 0.2; x++) {
439
+ if (border(x)) {
440
+ let e = x;
441
+ while (e + 1 < w && border(e + 1)) e++;
442
+ if (clearFrom(e + 1, 1)) x0 = e + 1;
443
+ break;
444
+ }
445
+ if (textCol(x)) break;
446
+ }
447
+ for (let x = w - 1; x >= w * 0.8; x--) {
448
+ if (border(x)) {
449
+ let e = x;
450
+ while (e - 1 >= 0 && border(e - 1)) e--;
451
+ if (clearFrom(e - 1, -1)) x1 = e;
452
+ break;
453
+ }
454
+ if (textCol(x)) break;
455
+ }
456
+ return y0 > 0 || y1 < h || x0 > 0 || x1 < w ? { x0, x1, y0, y1 } : null;
457
+ }
458
+ function tallInkCount(gray, w, h, ink) {
459
+ const { comps } = components(gray, w, h, ink);
460
+ let charH = 0;
461
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
462
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.5).sort((a, b) => a.x0 - b.x0);
463
+ let n = 0, end = -1;
464
+ for (const c of tall) {
465
+ if (c.x0 >= end) n++;
466
+ end = Math.max(end, c.x1);
467
+ }
468
+ return n;
469
+ }
470
+ function gapGlyphs(gray, w, h, ink) {
471
+ const { comps, label } = components(gray, w, h, ink);
472
+ let charH = 0;
473
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
474
+ if (charH < 10) return { glyphs: [], bandH: 0 };
475
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
476
+ const bandT = Math.min(...tall.map((c) => c.y0)), bandB = Math.max(...tall.map((c) => c.y1)), bh = bandB - bandT;
477
+ const inBand = (c) => c.y1 > bandT && c.y0 < bandB;
478
+ const out = [];
479
+ for (const c of comps) {
480
+ if (!inBand(c)) continue;
481
+ const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
482
+ if (ch >= bh * 0.9 && cw <= bh * 0.15) continue;
483
+ 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));
484
+ const apart = !others.some((o) => o.x0 < c.x1 + 1 && o.x1 > c.x0 - 1);
485
+ const abut = !apart && ch >= bh * 0.3 && ch <= bh * 0.65 && !others.some((o) => o.x0 < c.x1 && o.x1 > c.x0);
486
+ const nearlyApart = !others.some((o) => Math.min(o.x1, c.x1) - Math.max(o.x0, c.x0) > 2);
487
+ const spans = [], runs = [];
488
+ let n = 0;
489
+ for (let y = c.y0; y < c.y1; y++) {
490
+ let s = 0, r = 0, prev = false;
491
+ for (let x = c.x0; x < c.x1; x++) {
492
+ const on = label[y * w + x] === c.id;
493
+ if (on) {
494
+ s++;
495
+ if (!prev) r++;
496
+ }
497
+ prev = on;
498
+ }
499
+ spans.push(s);
500
+ runs.push(r);
501
+ n += s;
502
+ }
503
+ const fill = n / (cw * ch), cy = ((c.y0 + c.y1) / 2 - bandT) / bh;
504
+ const band = Math.max(1, Math.round(ch * 0.15));
505
+ const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
506
+ let mark = null;
507
+ const speck = Math.max(3, bh * 0.08);
508
+ const dot = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= speck && cw >= speck && cw >= ch * 0.6 && cw <= ch * 1.6;
509
+ 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;
510
+ if (!apart && !abut && !(nearlyApart && ch >= bh * 0.6)) {
511
+ if (small) out.push({ x0: c.x0, x1: c.x1, mark: "\xB7" });
512
+ continue;
513
+ }
514
+ const bracket = () => {
515
+ const stem = (from, to) => {
516
+ let sx = 0, k = 0;
517
+ for (let y = from; y < to; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
518
+ sx += x - c.x0;
519
+ k++;
520
+ }
521
+ return k ? sx / k / cw : 0.5;
522
+ };
523
+ if (c.y0 <= bandT + bh * 0.15 && top >= 0.7 && bottom <= 0.5 && stem(c.y0 + band, c.y1) <= 0.35) return "\u300C";
524
+ if (c.y1 >= bandB - bh * 0.15 && bottom >= 0.7 && top <= 0.5 && stem(c.y0, c.y1 - band) >= 0.65) return "\u300D";
525
+ return null;
526
+ };
527
+ if (!apart && !abut) {
528
+ } else if (abut) {
529
+ if (fill < 0.5) mark = bracket();
530
+ } else if (dot && fill >= 0.6 && cy >= 0.3 && cy <= 0.7) mark = "\xB7";
531
+ else if (small) mark = "\xB7";
532
+ else if (dot && fill >= 0.6 && cy >= 0.75 && cw >= ch * 0.8) mark = ".";
533
+ 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";
534
+ else if (ch <= bh * 0.15 && cw >= ch * 2 && cw <= bh * 0.8 && fill >= 0.7 && cy >= 0.35 && cy <= 0.75) mark = "-";
535
+ 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";
536
+ else if (ch >= bh * 0.3 && ch <= bh * 0.65 && fill < 0.5) mark = bracket();
537
+ 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";
538
+ if (!mark && ch >= bh * 0.6 && cw >= ch * 0.25 && cw <= ch * 0.7 && fill <= 0.4 && runs.every((r) => r <= 1)) {
539
+ const mx = (y) => {
540
+ let sx = 0, k = 0;
541
+ for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
542
+ sx += x;
543
+ k++;
544
+ }
545
+ return k ? sx / k : NaN;
546
+ };
547
+ if (mx(c.y0 + band) - mx(c.y1 - 1 - band) >= cw * 0.5) mark = "/";
548
+ }
549
+ if (mark) out.push({ x0: c.x0, x1: c.x1, mark });
550
+ }
551
+ return { glyphs: out, bandH: bh };
552
+ }
553
+ function ringBullet(gray, w, h, ink, cx) {
554
+ const { comps, label } = components(gray, w, h, ink);
555
+ let charH = 0;
556
+ for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
557
+ if (charH < 10) return null;
558
+ const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
559
+ const bandT = Math.min(...tall.map((c2) => c2.y0)), bh = Math.max(...tall.map((c2) => c2.y1)) - bandT;
560
+ let best = null, bestD = bh * 0.6;
561
+ for (const c2 of comps) {
562
+ const d = Math.abs((c2.x0 + c2.x1) / 2 - cx);
563
+ if (c2.y1 - c2.y0 >= bh * 0.3 && d < bestD) {
564
+ bestD = d;
565
+ best = c2;
566
+ }
567
+ }
568
+ if (!best) return null;
569
+ const c = best, cw = c.x1 - c.x0, ch = c.y1 - c.y0;
570
+ if (cw < ch * 0.85 || cw > ch * 1.2) return null;
571
+ const runs = (xs, at) => {
572
+ let n2 = 0, len = 0, mx2 = 0, mn = Infinity;
573
+ for (const k of [...xs, -1]) {
574
+ if (k >= 0 && at(k)) {
575
+ if (!len) n2++;
576
+ len++;
577
+ mx2 = Math.max(mx2, len);
578
+ } else {
579
+ if (len) mn = Math.min(mn, len);
580
+ len = 0;
581
+ }
582
+ }
583
+ return { n: n2, mx: mx2, mn };
584
+ };
585
+ const my = c.y0 + c.y1 >> 1, mx = c.x0 + c.x1 >> 1;
586
+ const row = runs(Array.from({ length: cw }, (_, k) => c.x0 + k), (x) => label[my * w + x] === c.id);
587
+ const col = runs(Array.from({ length: ch }, (_, k) => c.y0 + k), (y) => label[y * w + mx] === c.id);
588
+ if (row.n !== 2 || col.n !== 2) return null;
589
+ let n = 0;
590
+ 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++;
591
+ const fill = n / (cw * ch), rel = ch / bh;
592
+ if (rel >= 0.62 && (fill <= 0.23 ? row.mx <= cw * 0.1 : fill <= 0.33 && row.mn <= cw * 0.08)) return "\u25CB";
593
+ if (rel <= 0.58 && fill >= 0.24) return "\u3147";
594
+ if (rel >= 0.6 && rel <= 0.8 && fill >= 0.3 && ((c.y0 + c.y1) / 2 - bandT) / bh >= 0.55) return "o";
595
+ return null;
596
+ }
597
+ function dashRatio(gray, w, h, ink, cx) {
598
+ const { comps } = components(gray, w, h, ink);
599
+ let charH = 0;
600
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
601
+ if (charH < 10) return null;
602
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
603
+ const bh = Math.max(...tall.map((c) => c.y1)) - Math.min(...tall.map((c) => c.y0));
604
+ 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];
605
+ return dash ? (dash.x1 - dash.x0) / bh : null;
606
+ }
607
+ function discAt(gray, w, h, ink, cx) {
608
+ const { comps, label } = components(gray, w, h, ink);
609
+ let charH = 0;
610
+ for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
611
+ if (charH < 10) return false;
612
+ const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
613
+ const bh = Math.max(...tall.map((c2) => c2.y1)) - Math.min(...tall.map((c2) => c2.y0));
614
+ 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];
615
+ if (!c) return false;
616
+ const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
617
+ if (ch < bh * 0.45 || ch > bh * 1.05 || cw < ch * 0.8 || cw > ch * 1.25) return false;
618
+ let n = 0;
619
+ 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++;
620
+ const fill = n / (cw * ch);
621
+ return fill >= 0.65 && fill <= 0.9;
622
+ }
623
+ function serifOne(gray, w, h, ink, cx) {
624
+ const { comps, label } = components(gray, w, h, ink);
625
+ let charH = 0;
626
+ for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
627
+ if (charH < 10) return false;
628
+ 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];
629
+ if (!c) return false;
630
+ const ch = c.y1 - c.y0;
631
+ const span2 = (a, b) => {
632
+ let mn = Infinity, mx = -1;
633
+ 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) {
634
+ mn = Math.min(mn, x);
635
+ mx = Math.max(mx, x);
636
+ }
637
+ return [mn, mx];
638
+ };
639
+ const [m0, m1] = span2(0.4, 0.6), [t0, t1] = span2(0, 0.2), [b0, b1] = span2(0.8, 1);
640
+ const sw = m1 - m0 + 1;
641
+ const even = (l, r) => l >= sw * 0.4 && r >= sw * 0.4 && Math.abs(l - r) <= Math.max(l, r) * 0.35;
642
+ return sw >= 2 && sw <= ch * 0.3 && even(m0 - t0, t1 - m1) && even(m0 - b0, b1 - m1);
643
+ }
644
+ function starRun(gray, w, h, ink, cx) {
645
+ const { comps, label } = components(gray, w, h, ink);
646
+ let charH = 0;
647
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
648
+ if (charH < 10) return 0;
649
+ const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
650
+ const bandT = Math.min(...tall.map((c) => c.y0)), bh = Math.max(...tall.map((c) => c.y1)) - bandT;
651
+ const star = (c) => {
652
+ const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
653
+ 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;
654
+ let n = 0;
655
+ 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++;
656
+ const fill = n / (cw * ch);
657
+ return fill >= 0.25 && fill <= 0.65;
658
+ };
659
+ const stars = comps.filter(star).sort((a, b) => a.x0 - b.x0);
660
+ const k = stars.findIndex((c) => cx >= c.x0 - (c.x1 - c.x0) && cx <= c.x1 + (c.x1 - c.x0));
661
+ if (k < 0) return 0;
662
+ let lo = k, hi = k;
663
+ while (lo > 0 && stars[lo].x0 - stars[lo - 1].x1 <= stars[lo].x1 - stars[lo].x0) lo--;
664
+ while (hi + 1 < stars.length && stars[hi + 1].x0 - stars[hi].x1 <= stars[hi].x1 - stars[hi].x0) hi++;
665
+ return hi - lo + 1;
666
+ }
667
+ var BRACKET_TAPER = 0.16;
668
+ var BRACKET_SOLID = 0.4;
669
+ var BRACKET_WIDE = 0.265;
670
+ var BRACKET_LENS = 0.35;
671
+ function bracketShape(f, close) {
672
+ const [near2, far] = close ? [f.bottom, f.top] : [f.top, f.bottom];
673
+ const paren = close ? ")" : "(";
674
+ if (near2 >= 0.7 && far <= 0.5) return f.taper < BRACKET_TAPER ? close ? "\u300D" : "\u300C" : paren;
675
+ if (near2 >= 0.7 && far >= 0.7 && f.taper >= BRACKET_TAPER) {
676
+ return f.fill >= BRACKET_SOLID && (f.cw >= f.ch * BRACKET_WIDE || f.taper >= BRACKET_LENS) ? close ? "\u3011" : "\u3010" : paren;
677
+ }
678
+ return null;
679
+ }
680
+ function leadingBullet(gray, w, h, ink, firstX) {
681
+ const { comps, label } = components(gray, w, h, ink);
682
+ let charH = 0;
683
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
684
+ if (charH < 10) return null;
685
+ const cand = comps.filter((c) => c.y1 - c.y0 >= charH * 0.15).sort((a, b) => a.x0 - b.x0);
686
+ const first = cand[0];
687
+ if (!first) return null;
688
+ const cl = cand.filter((c) => c.x0 < first.x1 && c.x1 > first.x0);
689
+ 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));
690
+ const cw = x1 - x0, ch = y1 - y0;
691
+ if (cw < ch * 0.8 || cw > ch * 1.25) return null;
692
+ const next = cand.find((c) => c.x0 >= x1);
693
+ if (next && next.x0 - x1 < ch * 0.3) return null;
694
+ let inkN = 0;
695
+ 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++;
696
+ const fill = inkN / (cw * ch);
697
+ const rel = ch / charH;
698
+ const ring = (c) => {
699
+ const a = c.x1 - c.x0, b = c.y1 - c.y0;
700
+ return a >= b * 0.75 && a <= b * 1.33;
701
+ };
702
+ 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));
703
+ const span2 = (y) => {
704
+ let n = 0;
705
+ for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) n++;
706
+ return n;
707
+ };
708
+ const band = Math.max(1, Math.round(ch * 0.15));
709
+ const edgeFull = (from, dir) => {
710
+ let m = 0;
711
+ for (let k = 0; k < band; k++) m = Math.max(m, span2(from + dir * k));
712
+ return m >= cw * 0.8;
713
+ };
714
+ let midRuns = 0;
715
+ {
716
+ let prev = false;
717
+ const my = y0 + (ch >> 1);
718
+ for (let x = x0; x < x1; x++) {
719
+ const on = cl.some((c) => label[my * w + x] === c.id);
720
+ if (on && !prev) midRuns++;
721
+ prev = on;
722
+ }
723
+ }
724
+ const hollowSquare = cl.length === 1 && fill < 0.5 && midRuns === 2 && edgeFull(y0, 1) && edgeFull(y1 - 1, -1);
725
+ let mark = null;
726
+ if (hollowSquare && rel >= 0.6 && rel <= 0.92) mark = "\u25A1";
727
+ else if (nested && fill < 0.45 && rel >= 0.6) mark = "\u25CE";
728
+ else if (cl.length === 1 && fill >= 0.9 && rel >= 0.28 && rel <= 0.6) mark = "\u25AA";
729
+ else if (cl.length === 1 && fill >= 0.65 && fill <= 0.88 && rel >= 0.5 && rel <= 0.8) mark = "\u25CF";
730
+ if (!mark) return null;
731
+ return { mark, covers: firstX <= x1 + ch * 0.3 };
732
+ }
733
+
734
+ // src/ocr/text-gaps.ts
735
+ var evidence = /* @__PURE__ */ new WeakMap();
736
+ var NO_GAPS = [];
737
+ function rememberOcrTextGaps(item, gaps) {
738
+ if (gaps.length) evidence.set(item, { text: item.text, gaps });
739
+ }
740
+ function ocrTextGaps(item) {
741
+ const saved = evidence.get(item);
742
+ return saved?.text === item.text ? saved.gaps : NO_GAPS;
743
+ }
744
+ function copyOcrTextGaps(source, target, scale = 1, offsetX = 0) {
745
+ const gaps = ocrTextGaps(source);
746
+ if (!gaps.length || source.text !== target.text) return;
747
+ rememberOcrTextGaps(target, scale === 1 && offsetX === 0 ? gaps : gaps.map((g) => ({ ...g, left: g.left * scale + offsetX, right: g.right * scale + offsetX })));
748
+ }
749
+ function findOcrTextGaps(rgba, pageW, box, read, text, steps, stepPx) {
750
+ if (read.replace(/\s/g, "") !== text.replace(/\s/g, "") || [...read].length !== steps.length) return [];
751
+ const visible = [...read].flatMap((c, i) => /\s/.test(c) ? [] : [(steps[i] + 0.5) * stepPx]);
752
+ if (visible.length < 2) return [];
753
+ const gray = grayCrop(rgba, pageW, box), ink = inkStats(gray);
754
+ if (!ink.darkInk || ink.contrast < 35) return [];
755
+ const counts = new Uint32Array(box.w);
756
+ 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]++;
757
+ const out = [];
758
+ let offset = 0, index = 0;
759
+ for (const c of text) {
760
+ if (!/\s/.test(c)) {
761
+ if (index > 0) {
762
+ const from = Math.max(0, Math.floor(visible[index - 1]) + 1), end = Math.min(box.w, Math.ceil(visible[index]));
763
+ let start = from, left = from, right = from;
764
+ for (let x = from; x <= end; x++) {
765
+ if (x === end || counts[x] > Math.max(1, Math.floor(box.h * 0.03))) {
766
+ if (x - start > right - left) {
767
+ left = start;
768
+ right = x;
769
+ }
770
+ start = x + 1;
771
+ }
772
+ }
773
+ if (right - left >= Math.max(3, box.h * 0.25)) out.push({ at: offset, left: box.x + left, right: box.x + right });
774
+ }
775
+ index++;
776
+ }
777
+ offset += c.length;
778
+ }
779
+ return out;
780
+ }
14
781
 
15
782
  // src/pdf/line-extract.ts
16
783
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -1112,7 +1879,17 @@ function dropHeadBandClipGrids(clipGrids, lineGrids) {
1112
1879
  if (clipGrids.length === 0 || lineGrids.length === 0) return clipGrids;
1113
1880
  const near2 = (a, b) => Math.abs(a - b) <= SHADE_CLIP_TOL;
1114
1881
  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);
1115
- 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))));
1882
+ 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))));
1883
+ return kept.filter((c) => !lineGrids.some((l) => headBandTiles(kept, l, near2, overlaps).includes(c)));
1884
+ }
1885
+ function headBandTiles(clips, l, near2, overlaps) {
1886
+ const onRow = (y) => l.rowYs.some((r) => near2(r, y));
1887
+ 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);
1888
+ if (band.length < 2 || !near2(band[0].bbox.x1, l.bbox.x1) || !near2(band[band.length - 1].bbox.x2, l.bbox.x2)) return [];
1889
+ if (band.some((c, k) => k > 0 && c.bbox.x1 > band[k - 1].bbox.x2 + SHADE_CLIP_TOL)) return [];
1890
+ const bottom = Math.min(...band.map((c) => c.bbox.y1));
1891
+ const body = { ...l.bbox, y2: bottom };
1892
+ return clips.some((o) => !band.includes(o) && overlaps(o.bbox, body)) ? [] : band;
1116
1893
  }
1117
1894
  function enforceMinWidth(colXs, minWidth, narrowRuns = true) {
1118
1895
  if (colXs.length <= 2) return colXs;
@@ -3564,6 +4341,59 @@ function joinClusterCellLines(lines, box, lex) {
3564
4341
  return out;
3565
4342
  }
3566
4343
 
4344
+ // src/pdf/ocr-column-cuts.ts
4345
+ var OCR_NUMERIC_CELL = /^[+-]?\d+(?:[,.]\d+)*%?$/;
4346
+ var OCR_GROUPED_NUMBER = /^[+-]?\d{1,3}(?:\s+\d{3})+(?:[.,]\d+)?%?$/;
4347
+ function supportedOcrColumnCuts(rows) {
4348
+ const cuts = /* @__PURE__ */ new Map();
4349
+ for (const head of rows[0] ?? []) {
4350
+ if (head.items.length !== 1 || head.items[0].fontName !== "ocr") continue;
4351
+ const candidates = [];
4352
+ for (const gap of ocrTextGaps(head.items[0])) {
4353
+ const text = head.items[0].text;
4354
+ if (!/\p{L}/u.test(text.slice(0, gap.at)) || !/\p{L}/u.test(text.slice(gap.at))) continue;
4355
+ const intervals = [];
4356
+ for (const row of rows.slice(1)) {
4357
+ const cell = row.find((c) => c.col === head.col);
4358
+ if (!cell || cell.items.some((it) => it.fontName !== "ocr")) continue;
4359
+ let interval;
4360
+ if (cell.items.length === 1) {
4361
+ const it = cell.items[0];
4362
+ interval = ocrTextGaps(it).find((g) => isOcrNumericSplit(it.text, g.at));
4363
+ } else if (isOcrNumericPair(cell.items)) {
4364
+ const [a, b] = [...cell.items].sort((a2, b2) => a2.x - b2.x);
4365
+ interval = { left: a.x + a.w / 2, right: b.x + b.w / 2 };
4366
+ }
4367
+ if (interval) {
4368
+ const left = Math.max(gap.left, interval.left), right = Math.min(gap.right, interval.right);
4369
+ if (right > left) intervals.push({ left, right });
4370
+ }
4371
+ }
4372
+ const events = intervals.flatMap((i) => [{ x: i.left, delta: 1 }, { x: i.right, delta: -1 }]).sort((a, b) => a.x - b.x);
4373
+ let active = 0, bestCount = 1, bestWidth = 0, cut;
4374
+ for (let i = 0; i < events.length; ) {
4375
+ const x = events[i].x;
4376
+ while (i < events.length && events[i].x === x) active += events[i++].delta;
4377
+ const width = i < events.length ? events[i].x - x : 0;
4378
+ if (active >= 2 && width > 0 && (active > bestCount || active === bestCount && width > bestWidth)) {
4379
+ bestCount = active;
4380
+ bestWidth = width;
4381
+ cut = x + width / 2;
4382
+ }
4383
+ }
4384
+ if (cut !== void 0) candidates.push(cut);
4385
+ }
4386
+ if (candidates.length === 1) cuts.set(head.col, candidates[0]);
4387
+ }
4388
+ return cuts;
4389
+ }
4390
+ function isOcrNumericPair(items) {
4391
+ 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(" "));
4392
+ }
4393
+ function isOcrNumericSplit(text, at) {
4394
+ 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());
4395
+ }
4396
+
3567
4397
  // src/pdf/cluster-detector.ts
3568
4398
  var Y_TOL = 3;
3569
4399
  var COL_CLUSTER_TOL = 15;
@@ -4328,17 +5158,21 @@ function assignRowItems(items, columns, numCols, headerFace) {
4328
5158
  return result;
4329
5159
  }
4330
5160
  function buildClusterTable(rows, columns, pageNum, lex) {
4331
- const numCols = columns.length;
4332
5161
  const numRows = rows.length;
4333
- if (numRows < MIN_ROWS || numCols < MIN_COLS) return null;
5162
+ if (numRows < MIN_ROWS || columns.length < MIN_COLS) return null;
5163
+ const headFaces = new Set(rows[0]?.items.map((i) => i.fontName));
5164
+ const headerFace = headFaces.size === 1 && rows.slice(1).every((row) => row.items.every((i) => !headFaces.has(i.fontName))) ? [...headFaces][0] : void 0;
5165
+ const assigned = rows.map((row, r) => assignRowItems(row.items, columns, columns.length, r === 0 ? headerFace : void 0));
5166
+ const cuts = supportedOcrColumnCuts(assigned);
5167
+ const starts = columns.map((_, c) => c + [...cuts.keys()].filter((k) => k < c).length);
5168
+ const numCols = columns.length + cuts.size;
4334
5169
  const cells = Array.from(
4335
5170
  { length: numRows },
4336
5171
  () => Array.from({ length: numCols }, () => ({ text: "", colSpan: 1, rowSpan: 1 }))
4337
5172
  );
4338
5173
  const usedItems = /* @__PURE__ */ new Set();
4339
5174
  const lines = cells.map((row) => row.map(() => []));
4340
- const headFaces = new Set(rows[0]?.items.map((i) => i.fontName));
4341
- const headerFace = headFaces.size === 1 && rows.slice(1).every((row) => row.items.every((i) => !headFaces.has(i.fontName))) ? [...headFaces][0] : void 0;
5175
+ const origins = /* @__PURE__ */ new Map();
4342
5176
  for (let r = 0; r < numRows; r++) {
4343
5177
  const row = rows[r];
4344
5178
  if (row.items.length === 1 && numCols > 1) {
@@ -4346,15 +5180,39 @@ function buildClusterTable(rows, columns, pageNum, lex) {
4346
5180
  usedItems.add(row.items[0]);
4347
5181
  continue;
4348
5182
  }
4349
- const assignments = assignRowItems(row.items, columns, numCols, r === 0 ? headerFace : void 0);
4350
- for (const { col, items } of assignments) {
5183
+ const assignments = assigned[r].flatMap(({ col, items }) => {
5184
+ const target = starts[col], cut = cuts.get(col);
5185
+ if (cut === void 0) return [{ col: target, items, span: 1 }];
5186
+ const gapAt = (it) => ocrTextGaps(it).find((g) => g.left < cut && g.right > cut && (r === 0 || isOcrNumericSplit(it.text, g.at)));
5187
+ const groupedNumber = OCR_GROUPED_NUMBER.test([...items].sort((a, b) => a.x - b.x).map((it) => it.text.trim()).join(" "));
5188
+ if (groupedNumber || !isOcrNumericPair(items) && items.some((it) => it.x < cut && it.x + it.w > cut && !gapAt(it))) return [{ col: target, items, span: 2 }];
5189
+ const parts = [[], []];
5190
+ for (const it of items) {
5191
+ const g = gapAt(it);
5192
+ if (!g) {
5193
+ parts[it.x + it.w / 2 < cut ? 0 : 1].push(it);
5194
+ continue;
5195
+ }
5196
+ const fragments = [
5197
+ { ...it, text: it.text.slice(0, g.at).trimEnd(), w: g.left - it.x },
5198
+ { ...it, text: it.text.slice(g.at).trimStart(), x: g.right, w: it.x + it.w - g.right }
5199
+ ];
5200
+ fragments.forEach((fragment, side) => {
5201
+ origins.set(fragment, it);
5202
+ parts[side].push(fragment);
5203
+ });
5204
+ }
5205
+ return parts.flatMap((part, side) => part.length ? [{ col: target + side, items: part, span: 1 }] : []);
5206
+ });
5207
+ for (const { col, items, span: span2 } of assignments) {
5208
+ cells[r][col].colSpan = span2;
4351
5209
  const cell = lines[r][col];
4352
5210
  if (cell.length) cell[cell.length - 1].spaceAfter = true;
4353
5211
  cell.push(...clusterCellLines(items));
4354
- for (const item of items) usedItems.add(item);
5212
+ for (const item of items) usedItems.add(origins.get(item) ?? item);
4355
5213
  }
4356
5214
  }
4357
- const boxes = columns.map((_, c) => {
5215
+ const boxes = Array.from({ length: numCols }, (_, c) => {
4358
5216
  const col = lines.flatMap((row) => row[c]);
4359
5217
  return { x1: Math.min(...col.map((l) => l.left)), x2: Math.max(...col.map((l) => l.right)) };
4360
5218
  });
@@ -4377,7 +5235,7 @@ function buildClusterTable(rows, columns, pageNum, lex) {
4377
5235
  if (/^[○●▶\-·]/.test(contentText)) continue;
4378
5236
  for (let pr = r - 1; pr >= 0; pr--) {
4379
5237
  if (cells[pr].some((c) => c.text.trim())) {
4380
- if (cells[pr][0].colSpan > 1) break;
5238
+ if (cells[pr].some((c) => c.colSpan > 1)) break;
4381
5239
  for (let c = 0; c < numCols; c++) {
4382
5240
  if (!cells[r][c].text.trim()) continue;
4383
5241
  lines[pr][c].push(...lines[r][c]);
@@ -8571,18 +9429,22 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
8571
9429
  let panels = null;
8572
9430
  if (remaining.length > 0) {
8573
9431
  remaining.sort((a, b) => b.y - a.y || a.x - b.x);
8574
- const clusterItems = remaining.map((i) => ({
8575
- text: i.text,
8576
- x: i.x,
8577
- y: i.y,
8578
- w: i.w,
8579
- h: i.h,
8580
- fontSize: i.fontSize,
8581
- fontName: i.fontName,
8582
- hasSpaceBefore: i.hasSpaceBefore,
8583
- syntheticSpace: i.syntheticSpace,
8584
- spaceAfter: i.spaceAfter
8585
- }));
9432
+ const clusterItems = remaining.map((i) => {
9433
+ const item = {
9434
+ text: i.text,
9435
+ x: i.x,
9436
+ y: i.y,
9437
+ w: i.w,
9438
+ h: i.h,
9439
+ fontSize: i.fontSize,
9440
+ fontName: i.fontName,
9441
+ hasSpaceBefore: i.hasSpaceBefore,
9442
+ syntheticSpace: i.syntheticSpace,
9443
+ spaceAfter: i.spaceAfter
9444
+ };
9445
+ copyOcrTextGaps(i, item);
9446
+ return item;
9447
+ });
8586
9448
  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;
8587
9449
  const clusterResults = (proseColumns ? [] : detectClusterTables(clusterItems, pageNum, void 0, lex)).filter((cr) => {
8588
9450
  const b = cr.bbox;
@@ -8898,18 +9760,22 @@ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTable
8898
9760
  if (cards) return cards.flatMap((group) => extractPageBlocksFallback(group, pageNum, false, detectTables, lex));
8899
9761
  }
8900
9762
  const blocks = [];
8901
- const clusterItems = items.map((i) => ({
8902
- text: i.text,
8903
- x: i.x,
8904
- y: i.y,
8905
- w: i.w,
8906
- h: i.h,
8907
- fontSize: i.fontSize,
8908
- fontName: i.fontName,
8909
- hasSpaceBefore: i.hasSpaceBefore,
8910
- syntheticSpace: i.syntheticSpace,
8911
- spaceAfter: i.spaceAfter
8912
- }));
9763
+ const clusterItems = items.map((i) => {
9764
+ const item = {
9765
+ text: i.text,
9766
+ x: i.x,
9767
+ y: i.y,
9768
+ w: i.w,
9769
+ h: i.h,
9770
+ fontSize: i.fontSize,
9771
+ fontName: i.fontName,
9772
+ hasSpaceBefore: i.hasSpaceBefore,
9773
+ syntheticSpace: i.syntheticSpace,
9774
+ spaceAfter: i.spaceAfter
9775
+ };
9776
+ copyOcrTextGaps(i, item);
9777
+ return item;
9778
+ });
8913
9779
  const textRects = items.map((i) => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }));
8914
9780
  const imagePanels = fullPage ? splitImagePanels(items, figures) : null;
8915
9781
  if (imagePanels) return imagePanels.flatMap((group) => extractPageBlocksFallback(group, pageNum, false, detectTables, lex));
@@ -9123,6 +9989,29 @@ function rowParagraphs(block) {
9123
9989
  }
9124
9990
 
9125
9991
  export {
9992
+ grayCrop,
9993
+ inkStats,
9994
+ splitRowBands,
9995
+ leadingTriangle,
9996
+ leaderRuns,
9997
+ inkBounds,
9998
+ bracketFeatures,
9999
+ romanStems,
10000
+ circledAt,
10001
+ quoteHead,
10002
+ edgeTrim,
10003
+ tallInkCount,
10004
+ gapGlyphs,
10005
+ ringBullet,
10006
+ dashRatio,
10007
+ discAt,
10008
+ serifOne,
10009
+ starRun,
10010
+ bracketShape,
10011
+ leadingBullet,
10012
+ rememberOcrTextGaps,
10013
+ copyOcrTextGaps,
10014
+ findOcrTextGaps,
9126
10015
  extractLines,
9127
10016
  extractImageRegions,
9128
10017
  NO_EDGES,
@@ -9177,4 +10066,4 @@ export {
9177
10066
  UNRULED_REGION_TABLES,
9178
10067
  mergeOcrImageRegions
9179
10068
  };
9180
- //# sourceMappingURL=chunk-W75FRCAP.js.map
10069
+ //# sourceMappingURL=chunk-IT5XO3UG.js.map