kordoc 4.24.6 → 4.24.7
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.
- package/README.md +3 -2
- package/dist/{-7J7Y3XPH.js → -GQDQXRQN.js} +11 -11
- package/dist/{chunk-AM5ALISV.cjs → chunk-7BZBMXWD.cjs} +6 -6
- package/dist/chunk-7BZBMXWD.cjs.map +1 -0
- package/dist/{chunk-VMRH6UVA.js → chunk-7ICC55XY.js} +2 -2
- package/dist/{chunk-435ECXNG.cjs → chunk-AYGMF6H5.cjs} +3 -3
- package/dist/chunk-AYGMF6H5.cjs.map +1 -0
- package/dist/{chunk-3ZLS54V4.js → chunk-BLWDQEBY.js} +2 -2
- package/dist/{chunk-WNOWRBM4.js → chunk-BNIXSB6D.js} +2 -2
- package/dist/{chunk-5XBZJ3O4.js → chunk-C4AY47C5.js} +2 -2
- package/dist/{chunk-PKCOOSDT.js → chunk-CG2Z6NB3.js} +2 -2
- package/dist/{chunk-G343MTBX.js → chunk-DEDUWANG.js} +43 -15
- package/dist/chunk-DEDUWANG.js.map +1 -0
- package/dist/{chunk-TLJJF3J6.js → chunk-DZRXJEOF.js} +3 -3
- package/dist/{chunk-TZVVK5MI.js → chunk-E35YPWOX.js} +2 -2
- package/dist/{chunk-W75FRCAP.js → chunk-EIGCAAVM.js} +909 -36
- package/dist/chunk-EIGCAAVM.js.map +1 -0
- package/dist/{chunk-BKTEGPEO.js → chunk-EIUXBJZP.js} +3 -3
- package/dist/{chunk-XVTGFK2F.js → chunk-EJPPVB2Y.js} +2 -2
- package/dist/{chunk-EVBSSU3N.js → chunk-FIS3WY5Q.js} +2 -2
- package/dist/{chunk-GI5LWQ3S.js → chunk-FQVWYJNM.js} +2 -2
- package/dist/{chunk-BZW2WS44.js → chunk-GMKFXPEI.js} +82 -715
- package/dist/chunk-GMKFXPEI.js.map +1 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/chunk-J55UQC5H.cjs.map +1 -1
- package/dist/{chunk-DFRQD254.js → chunk-JKZY6FX5.js} +2 -2
- package/dist/{chunk-XM2Y4DB5.js → chunk-MLFPEETP.js} +2 -2
- package/dist/{chunk-JIU7E7Z6.cjs → chunk-MNG25FK4.cjs} +1082 -209
- package/dist/chunk-MNG25FK4.cjs.map +1 -0
- package/dist/{chunk-5RZBLPVP.js → chunk-N2JZGYFK.js} +2 -2
- package/dist/{chunk-GYX26HZR.js → chunk-O4RQCDN2.js} +910 -37
- package/dist/chunk-O4RQCDN2.js.map +1 -0
- package/dist/{chunk-BBKRJE57.js → chunk-OMJPCWKS.js} +2 -2
- package/dist/{chunk-RMUDX5Q2.js → chunk-OPNMXBUR.js} +2 -2
- package/dist/chunk-TK3FDAEE.cjs.map +1 -1
- package/dist/{chunk-HAMIOQYO.js → chunk-UDUEOBS5.js} +2 -2
- package/dist/{chunk-J4XCQNVH.cjs → chunk-V4PS653P.cjs} +140 -773
- package/dist/chunk-V4PS653P.cjs.map +1 -0
- package/dist/{chunk-ISTIOEIL.cjs → chunk-VHAOAQZZ.cjs} +3 -3
- package/dist/chunk-VHAOAQZZ.cjs.map +1 -0
- package/dist/{chunk-EBJWBGWF.js → chunk-WVZXPEHX.js} +82 -715
- package/dist/chunk-WVZXPEHX.js.map +1 -0
- package/dist/{chunk-2JDVPQ33.cjs → chunk-YQB3LPKQ.cjs} +2 -2
- package/dist/chunk-YQB3LPKQ.cjs.map +1 -0
- package/dist/{chunk-KR4UKOX2.js → chunk-ZQQGV7K2.js} +6 -6
- package/dist/chunks-XURCANPP.js +10 -0
- package/dist/cli.js +30 -30
- package/dist/{formula-DKNZKJ5Z.js → formula-6XLWGUCB.js} +4 -4
- package/dist/{formula-D7LBREWP.cjs → formula-C55TRBHH.cjs} +9 -9
- package/dist/formula-C55TRBHH.cjs.map +1 -0
- package/dist/{formula-T7D5JIZR.js → formula-DCC5EOSX.js} +3 -3
- package/dist/{image-ocr-N3I3MUKF.cjs → image-ocr-EV7GVD3W.cjs} +17 -17
- package/dist/image-ocr-EV7GVD3W.cjs.map +1 -0
- package/dist/{image-ocr-7NCSIZGA.js → image-ocr-K3ZXDUNU.js} +6 -6
- package/dist/{image-ocr-5POXVGN2.js → image-ocr-L736MGYD.js} +9 -9
- package/dist/index.cjs +729 -701
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +23 -23
- package/dist/{model-bundle-XT7G2IJU.js → model-bundle-OOOJFMGR.js} +5 -5
- package/dist/{models-AB2VO45L.js → models-ELFZII4R.js} +5 -5
- package/dist/page-range-QHHFTL7X.cjs.map +1 -1
- package/dist/{parse-worker-v2-YSSAUPWU.js → parse-worker-v2-WS35WZY5.js} +12 -12
- package/dist/{parser-KGDUPSA7.js → parser-CNRI55MG.js} +9 -9
- package/dist/{parser-ES3I54QL.js → parser-K7Y44QHS.js} +12 -12
- package/dist/{parser-AGDY3ZOA.cjs → parser-X6P3JBT2.cjs} +159 -159
- package/dist/parser-X6P3JBT2.cjs.map +1 -0
- package/dist/{pdf-ocr-7QSP7JLG.js → pdf-ocr-MVNHXGPS.js} +6 -6
- package/dist/{pdf-ocr-53WUTX6H.js → pdf-ocr-QQPZLIHM.js} +9 -9
- package/dist/pdf-ocr-S6GSKOPT.cjs +31 -0
- package/dist/pdf-ocr-S6GSKOPT.cjs.map +1 -0
- package/dist/{profile-io-GGEJ3MZW.js → profile-io-GSRLMU4D.js} +3 -3
- package/dist/{rasterize-YXICEI5S.js → rasterize-3H5QKDH2.js} +3 -3
- package/dist/{redact-doc-OYXBNTWI.js → redact-doc-TLBF7J7W.js} +11 -11
- package/dist/{render-4PBNDLK2.js → render-FTCOQ6WO.js} +7 -7
- package/dist/seal-74R7KDRY.js +10 -0
- package/dist/{setup-WHTMPK74.js → setup-WVZ47AKV.js} +3 -3
- package/dist/visual-TEW6Y3NH.js +21 -0
- package/dist/{watch-P6UWW52D.js → watch-CA32Z7XB.js} +12 -12
- package/package.json +1 -1
- package/dist/chunk-2JDVPQ33.cjs.map +0 -1
- package/dist/chunk-435ECXNG.cjs.map +0 -1
- package/dist/chunk-AM5ALISV.cjs.map +0 -1
- package/dist/chunk-BZW2WS44.js.map +0 -1
- package/dist/chunk-EBJWBGWF.js.map +0 -1
- package/dist/chunk-G343MTBX.js.map +0 -1
- package/dist/chunk-GYX26HZR.js.map +0 -1
- package/dist/chunk-ISTIOEIL.cjs.map +0 -1
- package/dist/chunk-J4XCQNVH.cjs.map +0 -1
- package/dist/chunk-JIU7E7Z6.cjs.map +0 -1
- package/dist/chunk-W75FRCAP.js.map +0 -1
- package/dist/chunks-3MWJ7HOZ.js +0 -10
- package/dist/formula-D7LBREWP.cjs.map +0 -1
- package/dist/image-ocr-N3I3MUKF.cjs.map +0 -1
- package/dist/parser-AGDY3ZOA.cjs.map +0 -1
- package/dist/pdf-ocr-X6OAHK46.cjs +0 -31
- package/dist/pdf-ocr-X6OAHK46.cjs.map +0 -1
- package/dist/seal-KJYAU23C.js +0 -10
- package/dist/visual-PHKJTSFR.js +0 -21
- /package/dist/{-7J7Y3XPH.js.map → -GQDQXRQN.js.map} +0 -0
- /package/dist/{chunk-VMRH6UVA.js.map → chunk-7ICC55XY.js.map} +0 -0
- /package/dist/{chunk-3ZLS54V4.js.map → chunk-BLWDQEBY.js.map} +0 -0
- /package/dist/{chunk-WNOWRBM4.js.map → chunk-BNIXSB6D.js.map} +0 -0
- /package/dist/{chunk-5XBZJ3O4.js.map → chunk-C4AY47C5.js.map} +0 -0
- /package/dist/{chunk-PKCOOSDT.js.map → chunk-CG2Z6NB3.js.map} +0 -0
- /package/dist/{chunk-TLJJF3J6.js.map → chunk-DZRXJEOF.js.map} +0 -0
- /package/dist/{chunk-TZVVK5MI.js.map → chunk-E35YPWOX.js.map} +0 -0
- /package/dist/{chunk-BKTEGPEO.js.map → chunk-EIUXBJZP.js.map} +0 -0
- /package/dist/{chunk-XVTGFK2F.js.map → chunk-EJPPVB2Y.js.map} +0 -0
- /package/dist/{chunk-EVBSSU3N.js.map → chunk-FIS3WY5Q.js.map} +0 -0
- /package/dist/{chunk-GI5LWQ3S.js.map → chunk-FQVWYJNM.js.map} +0 -0
- /package/dist/{chunk-DFRQD254.js.map → chunk-JKZY6FX5.js.map} +0 -0
- /package/dist/{chunk-XM2Y4DB5.js.map → chunk-MLFPEETP.js.map} +0 -0
- /package/dist/{chunk-5RZBLPVP.js.map → chunk-N2JZGYFK.js.map} +0 -0
- /package/dist/{chunk-BBKRJE57.js.map → chunk-OMJPCWKS.js.map} +0 -0
- /package/dist/{chunk-RMUDX5Q2.js.map → chunk-OPNMXBUR.js.map} +0 -0
- /package/dist/{chunk-HAMIOQYO.js.map → chunk-UDUEOBS5.js.map} +0 -0
- /package/dist/{chunk-KR4UKOX2.js.map → chunk-ZQQGV7K2.js.map} +0 -0
- /package/dist/{chunks-3MWJ7HOZ.js.map → chunks-XURCANPP.js.map} +0 -0
- /package/dist/{formula-DKNZKJ5Z.js.map → formula-6XLWGUCB.js.map} +0 -0
- /package/dist/{formula-T7D5JIZR.js.map → formula-DCC5EOSX.js.map} +0 -0
- /package/dist/{image-ocr-7NCSIZGA.js.map → image-ocr-K3ZXDUNU.js.map} +0 -0
- /package/dist/{image-ocr-5POXVGN2.js.map → image-ocr-L736MGYD.js.map} +0 -0
- /package/dist/{model-bundle-XT7G2IJU.js.map → model-bundle-OOOJFMGR.js.map} +0 -0
- /package/dist/{models-AB2VO45L.js.map → models-ELFZII4R.js.map} +0 -0
- /package/dist/{parse-worker-v2-YSSAUPWU.js.map → parse-worker-v2-WS35WZY5.js.map} +0 -0
- /package/dist/{parser-KGDUPSA7.js.map → parser-CNRI55MG.js.map} +0 -0
- /package/dist/{parser-ES3I54QL.js.map → parser-K7Y44QHS.js.map} +0 -0
- /package/dist/{pdf-ocr-53WUTX6H.js.map → pdf-ocr-MVNHXGPS.js.map} +0 -0
- /package/dist/{pdf-ocr-7QSP7JLG.js.map → pdf-ocr-QQPZLIHM.js.map} +0 -0
- /package/dist/{profile-io-GGEJ3MZW.js.map → profile-io-GSRLMU4D.js.map} +0 -0
- /package/dist/{rasterize-YXICEI5S.js.map → rasterize-3H5QKDH2.js.map} +0 -0
- /package/dist/{redact-doc-OYXBNTWI.js.map → redact-doc-TLBF7J7W.js.map} +0 -0
- /package/dist/{render-4PBNDLK2.js.map → render-FTCOQ6WO.js.map} +0 -0
- /package/dist/{seal-KJYAU23C.js.map → seal-74R7KDRY.js.map} +0 -0
- /package/dist/{setup-WHTMPK74.js.map → setup-WVZ47AKV.js.map} +0 -0
- /package/dist/{visual-PHKJTSFR.js.map → visual-TEW6Y3NH.js.map} +0 -0
- /package/dist/{watch-P6UWW52D.js.map → watch-CA32Z7XB.js.map} +0 -0
|
@@ -6,11 +6,772 @@ import {
|
|
|
6
6
|
HEADING_RATIO_H3,
|
|
7
7
|
tidyScriptTags,
|
|
8
8
|
toTex
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BLWDQEBY.js";
|
|
10
10
|
import {
|
|
11
11
|
safeMax,
|
|
12
12
|
safeMin
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-E35YPWOX.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 nearlyApart = !others.some((o) => Math.min(o.x1, c.x1) - Math.max(o.x0, c.x0) > 2);
|
|
486
|
+
const spans = [], runs = [];
|
|
487
|
+
let n = 0;
|
|
488
|
+
for (let y = c.y0; y < c.y1; y++) {
|
|
489
|
+
let s = 0, r = 0, prev = false;
|
|
490
|
+
for (let x = c.x0; x < c.x1; x++) {
|
|
491
|
+
const on = label[y * w + x] === c.id;
|
|
492
|
+
if (on) {
|
|
493
|
+
s++;
|
|
494
|
+
if (!prev) r++;
|
|
495
|
+
}
|
|
496
|
+
prev = on;
|
|
497
|
+
}
|
|
498
|
+
spans.push(s);
|
|
499
|
+
runs.push(r);
|
|
500
|
+
n += s;
|
|
501
|
+
}
|
|
502
|
+
const fill = n / (cw * ch), cy = ((c.y0 + c.y1) / 2 - bandT) / bh;
|
|
503
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
504
|
+
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
505
|
+
let mark = null;
|
|
506
|
+
const speck = Math.max(3, bh * 0.08);
|
|
507
|
+
const dot = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= speck && cw >= speck && cw >= ch * 0.6 && cw <= ch * 1.6;
|
|
508
|
+
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;
|
|
509
|
+
if (!apart && !(nearlyApart && ch >= bh * 0.6)) {
|
|
510
|
+
if (small) out.push({ x0: c.x0, x1: c.x1, mark: "\xB7" });
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (!apart) {
|
|
514
|
+
} else if (dot && fill >= 0.6 && cy >= 0.3 && cy <= 0.7) mark = "\xB7";
|
|
515
|
+
else if (small) mark = "\xB7";
|
|
516
|
+
else if (dot && fill >= 0.6 && cy >= 0.75 && cw >= ch * 0.8) mark = ".";
|
|
517
|
+
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";
|
|
518
|
+
else if (ch <= bh * 0.15 && cw >= ch * 2 && cw <= bh * 0.8 && fill >= 0.7 && cy >= 0.35 && cy <= 0.75) mark = "-";
|
|
519
|
+
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";
|
|
520
|
+
else if (ch >= bh * 0.3 && ch <= bh * 0.65 && fill < 0.5) {
|
|
521
|
+
const stem = (from, to) => {
|
|
522
|
+
let sx = 0, k = 0;
|
|
523
|
+
for (let y = from; y < to; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
524
|
+
sx += x - c.x0;
|
|
525
|
+
k++;
|
|
526
|
+
}
|
|
527
|
+
return k ? sx / k / cw : 0.5;
|
|
528
|
+
};
|
|
529
|
+
if (c.y0 <= bandT + bh * 0.15 && top >= 0.7 && bottom <= 0.5 && stem(c.y0 + band, c.y1) <= 0.35) mark = "\u300C";
|
|
530
|
+
else if (c.y1 >= bandB - bh * 0.15 && bottom >= 0.7 && top <= 0.5 && stem(c.y0, c.y1 - band) >= 0.65) mark = "\u300D";
|
|
531
|
+
} 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";
|
|
532
|
+
if (!mark && ch >= bh * 0.6 && cw >= ch * 0.25 && cw <= ch * 0.7 && fill <= 0.4 && runs.every((r) => r <= 1)) {
|
|
533
|
+
const mx = (y) => {
|
|
534
|
+
let sx = 0, k = 0;
|
|
535
|
+
for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
536
|
+
sx += x;
|
|
537
|
+
k++;
|
|
538
|
+
}
|
|
539
|
+
return k ? sx / k : NaN;
|
|
540
|
+
};
|
|
541
|
+
if (mx(c.y0 + band) - mx(c.y1 - 1 - band) >= cw * 0.5) mark = "/";
|
|
542
|
+
}
|
|
543
|
+
if (mark) out.push({ x0: c.x0, x1: c.x1, mark });
|
|
544
|
+
}
|
|
545
|
+
return { glyphs: out, bandH: bh };
|
|
546
|
+
}
|
|
547
|
+
function ringBullet(gray, w, h, ink, cx) {
|
|
548
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
549
|
+
let charH = 0;
|
|
550
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
551
|
+
if (charH < 10) return null;
|
|
552
|
+
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
553
|
+
const bandT = Math.min(...tall.map((c2) => c2.y0)), bh = Math.max(...tall.map((c2) => c2.y1)) - bandT;
|
|
554
|
+
let best = null, bestD = bh * 0.6;
|
|
555
|
+
for (const c2 of comps) {
|
|
556
|
+
const d = Math.abs((c2.x0 + c2.x1) / 2 - cx);
|
|
557
|
+
if (c2.y1 - c2.y0 >= bh * 0.3 && d < bestD) {
|
|
558
|
+
bestD = d;
|
|
559
|
+
best = c2;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (!best) return null;
|
|
563
|
+
const c = best, cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
564
|
+
if (cw < ch * 0.85 || cw > ch * 1.2) return null;
|
|
565
|
+
const runs = (xs, at) => {
|
|
566
|
+
let n2 = 0, len = 0, mx2 = 0, mn = Infinity;
|
|
567
|
+
for (const k of [...xs, -1]) {
|
|
568
|
+
if (k >= 0 && at(k)) {
|
|
569
|
+
if (!len) n2++;
|
|
570
|
+
len++;
|
|
571
|
+
mx2 = Math.max(mx2, len);
|
|
572
|
+
} else {
|
|
573
|
+
if (len) mn = Math.min(mn, len);
|
|
574
|
+
len = 0;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return { n: n2, mx: mx2, mn };
|
|
578
|
+
};
|
|
579
|
+
const my = c.y0 + c.y1 >> 1, mx = c.x0 + c.x1 >> 1;
|
|
580
|
+
const row = runs(Array.from({ length: cw }, (_, k) => c.x0 + k), (x) => label[my * w + x] === c.id);
|
|
581
|
+
const col = runs(Array.from({ length: ch }, (_, k) => c.y0 + k), (y) => label[y * w + mx] === c.id);
|
|
582
|
+
if (row.n !== 2 || col.n !== 2) return null;
|
|
583
|
+
let n = 0;
|
|
584
|
+
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++;
|
|
585
|
+
const fill = n / (cw * ch), rel = ch / bh;
|
|
586
|
+
if (rel >= 0.62 && (fill <= 0.23 ? row.mx <= cw * 0.1 : fill <= 0.33 && row.mn <= cw * 0.08)) return "\u25CB";
|
|
587
|
+
if (rel <= 0.58 && fill >= 0.24) return "\u3147";
|
|
588
|
+
if (rel >= 0.6 && rel <= 0.8 && fill >= 0.3 && ((c.y0 + c.y1) / 2 - bandT) / bh >= 0.55) return "o";
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
function dashRatio(gray, w, h, ink, cx) {
|
|
592
|
+
const { comps } = components(gray, w, h, ink);
|
|
593
|
+
let charH = 0;
|
|
594
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
595
|
+
if (charH < 10) return null;
|
|
596
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
597
|
+
const bh = Math.max(...tall.map((c) => c.y1)) - Math.min(...tall.map((c) => c.y0));
|
|
598
|
+
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];
|
|
599
|
+
return dash ? (dash.x1 - dash.x0) / bh : null;
|
|
600
|
+
}
|
|
601
|
+
function discAt(gray, w, h, ink, cx) {
|
|
602
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
603
|
+
let charH = 0;
|
|
604
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
605
|
+
if (charH < 10) return false;
|
|
606
|
+
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
607
|
+
const bh = Math.max(...tall.map((c2) => c2.y1)) - Math.min(...tall.map((c2) => c2.y0));
|
|
608
|
+
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];
|
|
609
|
+
if (!c) return false;
|
|
610
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
611
|
+
if (ch < bh * 0.45 || ch > bh * 1.05 || cw < ch * 0.8 || cw > ch * 1.25) return false;
|
|
612
|
+
let n = 0;
|
|
613
|
+
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++;
|
|
614
|
+
const fill = n / (cw * ch);
|
|
615
|
+
return fill >= 0.65 && fill <= 0.9;
|
|
616
|
+
}
|
|
617
|
+
function serifOne(gray, w, h, ink, cx) {
|
|
618
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
619
|
+
let charH = 0;
|
|
620
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
621
|
+
if (charH < 10) return false;
|
|
622
|
+
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];
|
|
623
|
+
if (!c) return false;
|
|
624
|
+
const ch = c.y1 - c.y0;
|
|
625
|
+
const span2 = (a, b) => {
|
|
626
|
+
let mn = Infinity, mx = -1;
|
|
627
|
+
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) {
|
|
628
|
+
mn = Math.min(mn, x);
|
|
629
|
+
mx = Math.max(mx, x);
|
|
630
|
+
}
|
|
631
|
+
return [mn, mx];
|
|
632
|
+
};
|
|
633
|
+
const [m0, m1] = span2(0.4, 0.6), [t0, t1] = span2(0, 0.2), [b0, b1] = span2(0.8, 1);
|
|
634
|
+
const sw = m1 - m0 + 1;
|
|
635
|
+
const even = (l, r) => l >= sw * 0.4 && r >= sw * 0.4 && Math.abs(l - r) <= Math.max(l, r) * 0.35;
|
|
636
|
+
return sw >= 2 && sw <= ch * 0.3 && even(m0 - t0, t1 - m1) && even(m0 - b0, b1 - m1);
|
|
637
|
+
}
|
|
638
|
+
function starRun(gray, w, h, ink, cx) {
|
|
639
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
640
|
+
let charH = 0;
|
|
641
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
642
|
+
if (charH < 10) return 0;
|
|
643
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
644
|
+
const bandT = Math.min(...tall.map((c) => c.y0)), bh = Math.max(...tall.map((c) => c.y1)) - bandT;
|
|
645
|
+
const star = (c) => {
|
|
646
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
647
|
+
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;
|
|
648
|
+
let n = 0;
|
|
649
|
+
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++;
|
|
650
|
+
const fill = n / (cw * ch);
|
|
651
|
+
return fill >= 0.25 && fill <= 0.65;
|
|
652
|
+
};
|
|
653
|
+
const stars = comps.filter(star).sort((a, b) => a.x0 - b.x0);
|
|
654
|
+
const k = stars.findIndex((c) => cx >= c.x0 - (c.x1 - c.x0) && cx <= c.x1 + (c.x1 - c.x0));
|
|
655
|
+
if (k < 0) return 0;
|
|
656
|
+
let lo = k, hi = k;
|
|
657
|
+
while (lo > 0 && stars[lo].x0 - stars[lo - 1].x1 <= stars[lo].x1 - stars[lo].x0) lo--;
|
|
658
|
+
while (hi + 1 < stars.length && stars[hi + 1].x0 - stars[hi].x1 <= stars[hi].x1 - stars[hi].x0) hi++;
|
|
659
|
+
return hi - lo + 1;
|
|
660
|
+
}
|
|
661
|
+
var BRACKET_TAPER = 0.16;
|
|
662
|
+
var BRACKET_SOLID = 0.4;
|
|
663
|
+
var BRACKET_WIDE = 0.265;
|
|
664
|
+
var BRACKET_LENS = 0.35;
|
|
665
|
+
function bracketShape(f, close) {
|
|
666
|
+
const [near2, far] = close ? [f.bottom, f.top] : [f.top, f.bottom];
|
|
667
|
+
const paren = close ? ")" : "(";
|
|
668
|
+
if (near2 >= 0.7 && far <= 0.5) return f.taper < BRACKET_TAPER ? close ? "\u300D" : "\u300C" : paren;
|
|
669
|
+
if (near2 >= 0.7 && far >= 0.7 && f.taper >= BRACKET_TAPER) {
|
|
670
|
+
return f.fill >= BRACKET_SOLID && (f.cw >= f.ch * BRACKET_WIDE || f.taper >= BRACKET_LENS) ? close ? "\u3011" : "\u3010" : paren;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function leadingBullet(gray, w, h, ink, firstX) {
|
|
675
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
676
|
+
let charH = 0;
|
|
677
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
678
|
+
if (charH < 10) return null;
|
|
679
|
+
const cand = comps.filter((c) => c.y1 - c.y0 >= charH * 0.15).sort((a, b) => a.x0 - b.x0);
|
|
680
|
+
const first = cand[0];
|
|
681
|
+
if (!first) return null;
|
|
682
|
+
const cl = cand.filter((c) => c.x0 < first.x1 && c.x1 > first.x0);
|
|
683
|
+
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));
|
|
684
|
+
const cw = x1 - x0, ch = y1 - y0;
|
|
685
|
+
if (cw < ch * 0.8 || cw > ch * 1.25) return null;
|
|
686
|
+
const next = cand.find((c) => c.x0 >= x1);
|
|
687
|
+
if (next && next.x0 - x1 < ch * 0.3) return null;
|
|
688
|
+
let inkN = 0;
|
|
689
|
+
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++;
|
|
690
|
+
const fill = inkN / (cw * ch);
|
|
691
|
+
const rel = ch / charH;
|
|
692
|
+
const ring = (c) => {
|
|
693
|
+
const a = c.x1 - c.x0, b = c.y1 - c.y0;
|
|
694
|
+
return a >= b * 0.75 && a <= b * 1.33;
|
|
695
|
+
};
|
|
696
|
+
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));
|
|
697
|
+
const span2 = (y) => {
|
|
698
|
+
let n = 0;
|
|
699
|
+
for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) n++;
|
|
700
|
+
return n;
|
|
701
|
+
};
|
|
702
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
703
|
+
const edgeFull = (from, dir) => {
|
|
704
|
+
let m = 0;
|
|
705
|
+
for (let k = 0; k < band; k++) m = Math.max(m, span2(from + dir * k));
|
|
706
|
+
return m >= cw * 0.8;
|
|
707
|
+
};
|
|
708
|
+
let midRuns = 0;
|
|
709
|
+
{
|
|
710
|
+
let prev = false;
|
|
711
|
+
const my = y0 + (ch >> 1);
|
|
712
|
+
for (let x = x0; x < x1; x++) {
|
|
713
|
+
const on = cl.some((c) => label[my * w + x] === c.id);
|
|
714
|
+
if (on && !prev) midRuns++;
|
|
715
|
+
prev = on;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const hollowSquare = cl.length === 1 && fill < 0.5 && midRuns === 2 && edgeFull(y0, 1) && edgeFull(y1 - 1, -1);
|
|
719
|
+
let mark = null;
|
|
720
|
+
if (hollowSquare && rel >= 0.6 && rel <= 0.92) mark = "\u25A1";
|
|
721
|
+
else if (nested && fill < 0.45 && rel >= 0.6) mark = "\u25CE";
|
|
722
|
+
else if (cl.length === 1 && fill >= 0.9 && rel >= 0.28 && rel <= 0.6) mark = "\u25AA";
|
|
723
|
+
else if (cl.length === 1 && fill >= 0.65 && fill <= 0.88 && rel >= 0.5 && rel <= 0.8) mark = "\u25CF";
|
|
724
|
+
if (!mark) return null;
|
|
725
|
+
return { mark, covers: firstX <= x1 + ch * 0.3 };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/ocr/text-gaps.ts
|
|
729
|
+
var evidence = /* @__PURE__ */ new WeakMap();
|
|
730
|
+
var NO_GAPS = [];
|
|
731
|
+
function rememberOcrTextGaps(item, gaps) {
|
|
732
|
+
if (gaps.length) evidence.set(item, { text: item.text, gaps });
|
|
733
|
+
}
|
|
734
|
+
function ocrTextGaps(item) {
|
|
735
|
+
const saved = evidence.get(item);
|
|
736
|
+
return saved?.text === item.text ? saved.gaps : NO_GAPS;
|
|
737
|
+
}
|
|
738
|
+
function copyOcrTextGaps(source, target, scale = 1, offsetX = 0) {
|
|
739
|
+
const gaps = ocrTextGaps(source);
|
|
740
|
+
if (!gaps.length || source.text !== target.text) return;
|
|
741
|
+
rememberOcrTextGaps(target, scale === 1 && offsetX === 0 ? gaps : gaps.map((g) => ({ ...g, left: g.left * scale + offsetX, right: g.right * scale + offsetX })));
|
|
742
|
+
}
|
|
743
|
+
function findOcrTextGaps(rgba, pageW, box, read, text, steps, stepPx) {
|
|
744
|
+
if (read.replace(/\s/g, "") !== text.replace(/\s/g, "") || [...read].length !== steps.length) return [];
|
|
745
|
+
const visible = [...read].flatMap((c, i) => /\s/.test(c) ? [] : [(steps[i] + 0.5) * stepPx]);
|
|
746
|
+
if (visible.length < 2) return [];
|
|
747
|
+
const gray = grayCrop(rgba, pageW, box), ink = inkStats(gray);
|
|
748
|
+
if (!ink.darkInk || ink.contrast < 35) return [];
|
|
749
|
+
const counts = new Uint32Array(box.w);
|
|
750
|
+
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]++;
|
|
751
|
+
const out = [];
|
|
752
|
+
let offset = 0, index = 0;
|
|
753
|
+
for (const c of text) {
|
|
754
|
+
if (!/\s/.test(c)) {
|
|
755
|
+
if (index > 0) {
|
|
756
|
+
const from = Math.max(0, Math.floor(visible[index - 1]) + 1), end = Math.min(box.w, Math.ceil(visible[index]));
|
|
757
|
+
let start = from, left = from, right = from;
|
|
758
|
+
for (let x = from; x <= end; x++) {
|
|
759
|
+
if (x === end || counts[x] > Math.max(1, Math.floor(box.h * 0.03))) {
|
|
760
|
+
if (x - start > right - left) {
|
|
761
|
+
left = start;
|
|
762
|
+
right = x;
|
|
763
|
+
}
|
|
764
|
+
start = x + 1;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (right - left >= Math.max(3, box.h * 0.25)) out.push({ at: offset, left: box.x + left, right: box.x + right });
|
|
768
|
+
}
|
|
769
|
+
index++;
|
|
770
|
+
}
|
|
771
|
+
offset += c.length;
|
|
772
|
+
}
|
|
773
|
+
return out;
|
|
774
|
+
}
|
|
14
775
|
|
|
15
776
|
// src/pdf/line-extract.ts
|
|
16
777
|
import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
|
|
@@ -3564,6 +4325,59 @@ function joinClusterCellLines(lines, box, lex) {
|
|
|
3564
4325
|
return out;
|
|
3565
4326
|
}
|
|
3566
4327
|
|
|
4328
|
+
// src/pdf/ocr-column-cuts.ts
|
|
4329
|
+
var OCR_NUMERIC_CELL = /^[+-]?\d+(?:[,.]\d+)*%?$/;
|
|
4330
|
+
var OCR_GROUPED_NUMBER = /^[+-]?\d{1,3}(?:\s+\d{3})+(?:[.,]\d+)?%?$/;
|
|
4331
|
+
function supportedOcrColumnCuts(rows) {
|
|
4332
|
+
const cuts = /* @__PURE__ */ new Map();
|
|
4333
|
+
for (const head of rows[0] ?? []) {
|
|
4334
|
+
if (head.items.length !== 1 || head.items[0].fontName !== "ocr") continue;
|
|
4335
|
+
const candidates = [];
|
|
4336
|
+
for (const gap of ocrTextGaps(head.items[0])) {
|
|
4337
|
+
const text = head.items[0].text;
|
|
4338
|
+
if (!/\p{L}/u.test(text.slice(0, gap.at)) || !/\p{L}/u.test(text.slice(gap.at))) continue;
|
|
4339
|
+
const intervals = [];
|
|
4340
|
+
for (const row of rows.slice(1)) {
|
|
4341
|
+
const cell = row.find((c) => c.col === head.col);
|
|
4342
|
+
if (!cell || cell.items.some((it) => it.fontName !== "ocr")) continue;
|
|
4343
|
+
let interval;
|
|
4344
|
+
if (cell.items.length === 1) {
|
|
4345
|
+
const it = cell.items[0];
|
|
4346
|
+
interval = ocrTextGaps(it).find((g) => isOcrNumericSplit(it.text, g.at));
|
|
4347
|
+
} else if (isOcrNumericPair(cell.items)) {
|
|
4348
|
+
const [a, b] = [...cell.items].sort((a2, b2) => a2.x - b2.x);
|
|
4349
|
+
interval = { left: a.x + a.w / 2, right: b.x + b.w / 2 };
|
|
4350
|
+
}
|
|
4351
|
+
if (interval) {
|
|
4352
|
+
const left = Math.max(gap.left, interval.left), right = Math.min(gap.right, interval.right);
|
|
4353
|
+
if (right > left) intervals.push({ left, right });
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
const events = intervals.flatMap((i) => [{ x: i.left, delta: 1 }, { x: i.right, delta: -1 }]).sort((a, b) => a.x - b.x);
|
|
4357
|
+
let active = 0, bestCount = 1, bestWidth = 0, cut;
|
|
4358
|
+
for (let i = 0; i < events.length; ) {
|
|
4359
|
+
const x = events[i].x;
|
|
4360
|
+
while (i < events.length && events[i].x === x) active += events[i++].delta;
|
|
4361
|
+
const width = i < events.length ? events[i].x - x : 0;
|
|
4362
|
+
if (active >= 2 && width > 0 && (active > bestCount || active === bestCount && width > bestWidth)) {
|
|
4363
|
+
bestCount = active;
|
|
4364
|
+
bestWidth = width;
|
|
4365
|
+
cut = x + width / 2;
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
if (cut !== void 0) candidates.push(cut);
|
|
4369
|
+
}
|
|
4370
|
+
if (candidates.length === 1) cuts.set(head.col, candidates[0]);
|
|
4371
|
+
}
|
|
4372
|
+
return cuts;
|
|
4373
|
+
}
|
|
4374
|
+
function isOcrNumericPair(items) {
|
|
4375
|
+
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(" "));
|
|
4376
|
+
}
|
|
4377
|
+
function isOcrNumericSplit(text, at) {
|
|
4378
|
+
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());
|
|
4379
|
+
}
|
|
4380
|
+
|
|
3567
4381
|
// src/pdf/cluster-detector.ts
|
|
3568
4382
|
var Y_TOL = 3;
|
|
3569
4383
|
var COL_CLUSTER_TOL = 15;
|
|
@@ -4328,17 +5142,21 @@ function assignRowItems(items, columns, numCols, headerFace) {
|
|
|
4328
5142
|
return result;
|
|
4329
5143
|
}
|
|
4330
5144
|
function buildClusterTable(rows, columns, pageNum, lex) {
|
|
4331
|
-
const numCols = columns.length;
|
|
4332
5145
|
const numRows = rows.length;
|
|
4333
|
-
if (numRows < MIN_ROWS ||
|
|
5146
|
+
if (numRows < MIN_ROWS || columns.length < MIN_COLS) return null;
|
|
5147
|
+
const headFaces = new Set(rows[0]?.items.map((i) => i.fontName));
|
|
5148
|
+
const headerFace = headFaces.size === 1 && rows.slice(1).every((row) => row.items.every((i) => !headFaces.has(i.fontName))) ? [...headFaces][0] : void 0;
|
|
5149
|
+
const assigned = rows.map((row, r) => assignRowItems(row.items, columns, columns.length, r === 0 ? headerFace : void 0));
|
|
5150
|
+
const cuts = supportedOcrColumnCuts(assigned);
|
|
5151
|
+
const starts = columns.map((_, c) => c + [...cuts.keys()].filter((k) => k < c).length);
|
|
5152
|
+
const numCols = columns.length + cuts.size;
|
|
4334
5153
|
const cells = Array.from(
|
|
4335
5154
|
{ length: numRows },
|
|
4336
5155
|
() => Array.from({ length: numCols }, () => ({ text: "", colSpan: 1, rowSpan: 1 }))
|
|
4337
5156
|
);
|
|
4338
5157
|
const usedItems = /* @__PURE__ */ new Set();
|
|
4339
5158
|
const lines = cells.map((row) => row.map(() => []));
|
|
4340
|
-
const
|
|
4341
|
-
const headerFace = headFaces.size === 1 && rows.slice(1).every((row) => row.items.every((i) => !headFaces.has(i.fontName))) ? [...headFaces][0] : void 0;
|
|
5159
|
+
const origins = /* @__PURE__ */ new Map();
|
|
4342
5160
|
for (let r = 0; r < numRows; r++) {
|
|
4343
5161
|
const row = rows[r];
|
|
4344
5162
|
if (row.items.length === 1 && numCols > 1) {
|
|
@@ -4346,15 +5164,39 @@ function buildClusterTable(rows, columns, pageNum, lex) {
|
|
|
4346
5164
|
usedItems.add(row.items[0]);
|
|
4347
5165
|
continue;
|
|
4348
5166
|
}
|
|
4349
|
-
const assignments =
|
|
4350
|
-
|
|
5167
|
+
const assignments = assigned[r].flatMap(({ col, items }) => {
|
|
5168
|
+
const target = starts[col], cut = cuts.get(col);
|
|
5169
|
+
if (cut === void 0) return [{ col: target, items, span: 1 }];
|
|
5170
|
+
const gapAt = (it) => ocrTextGaps(it).find((g) => g.left < cut && g.right > cut && (r === 0 || isOcrNumericSplit(it.text, g.at)));
|
|
5171
|
+
const groupedNumber = OCR_GROUPED_NUMBER.test([...items].sort((a, b) => a.x - b.x).map((it) => it.text.trim()).join(" "));
|
|
5172
|
+
if (groupedNumber || !isOcrNumericPair(items) && items.some((it) => it.x < cut && it.x + it.w > cut && !gapAt(it))) return [{ col: target, items, span: 2 }];
|
|
5173
|
+
const parts = [[], []];
|
|
5174
|
+
for (const it of items) {
|
|
5175
|
+
const g = gapAt(it);
|
|
5176
|
+
if (!g) {
|
|
5177
|
+
parts[it.x + it.w / 2 < cut ? 0 : 1].push(it);
|
|
5178
|
+
continue;
|
|
5179
|
+
}
|
|
5180
|
+
const fragments = [
|
|
5181
|
+
{ ...it, text: it.text.slice(0, g.at).trimEnd(), w: g.left - it.x },
|
|
5182
|
+
{ ...it, text: it.text.slice(g.at).trimStart(), x: g.right, w: it.x + it.w - g.right }
|
|
5183
|
+
];
|
|
5184
|
+
fragments.forEach((fragment, side) => {
|
|
5185
|
+
origins.set(fragment, it);
|
|
5186
|
+
parts[side].push(fragment);
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5189
|
+
return parts.flatMap((part, side) => part.length ? [{ col: target + side, items: part, span: 1 }] : []);
|
|
5190
|
+
});
|
|
5191
|
+
for (const { col, items, span: span2 } of assignments) {
|
|
5192
|
+
cells[r][col].colSpan = span2;
|
|
4351
5193
|
const cell = lines[r][col];
|
|
4352
5194
|
if (cell.length) cell[cell.length - 1].spaceAfter = true;
|
|
4353
5195
|
cell.push(...clusterCellLines(items));
|
|
4354
|
-
for (const item of items) usedItems.add(item);
|
|
5196
|
+
for (const item of items) usedItems.add(origins.get(item) ?? item);
|
|
4355
5197
|
}
|
|
4356
5198
|
}
|
|
4357
|
-
const boxes =
|
|
5199
|
+
const boxes = Array.from({ length: numCols }, (_, c) => {
|
|
4358
5200
|
const col = lines.flatMap((row) => row[c]);
|
|
4359
5201
|
return { x1: Math.min(...col.map((l) => l.left)), x2: Math.max(...col.map((l) => l.right)) };
|
|
4360
5202
|
});
|
|
@@ -4377,7 +5219,7 @@ function buildClusterTable(rows, columns, pageNum, lex) {
|
|
|
4377
5219
|
if (/^[○●▶\-·]/.test(contentText)) continue;
|
|
4378
5220
|
for (let pr = r - 1; pr >= 0; pr--) {
|
|
4379
5221
|
if (cells[pr].some((c) => c.text.trim())) {
|
|
4380
|
-
if (cells[pr]
|
|
5222
|
+
if (cells[pr].some((c) => c.colSpan > 1)) break;
|
|
4381
5223
|
for (let c = 0; c < numCols; c++) {
|
|
4382
5224
|
if (!cells[r][c].text.trim()) continue;
|
|
4383
5225
|
lines[pr][c].push(...lines[r][c]);
|
|
@@ -8571,18 +9413,22 @@ function extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, ho
|
|
|
8571
9413
|
let panels = null;
|
|
8572
9414
|
if (remaining.length > 0) {
|
|
8573
9415
|
remaining.sort((a, b) => b.y - a.y || a.x - b.x);
|
|
8574
|
-
const clusterItems = remaining.map((i) =>
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
9416
|
+
const clusterItems = remaining.map((i) => {
|
|
9417
|
+
const item = {
|
|
9418
|
+
text: i.text,
|
|
9419
|
+
x: i.x,
|
|
9420
|
+
y: i.y,
|
|
9421
|
+
w: i.w,
|
|
9422
|
+
h: i.h,
|
|
9423
|
+
fontSize: i.fontSize,
|
|
9424
|
+
fontName: i.fontName,
|
|
9425
|
+
hasSpaceBefore: i.hasSpaceBefore,
|
|
9426
|
+
syntheticSpace: i.syntheticSpace,
|
|
9427
|
+
spaceAfter: i.spaceAfter
|
|
9428
|
+
};
|
|
9429
|
+
copyOcrTextGaps(i, item);
|
|
9430
|
+
return item;
|
|
9431
|
+
});
|
|
8586
9432
|
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
9433
|
const clusterResults = (proseColumns ? [] : detectClusterTables(clusterItems, pageNum, void 0, lex)).filter((cr) => {
|
|
8588
9434
|
const b = cr.bbox;
|
|
@@ -8898,18 +9744,22 @@ function extractPageBlocksFallback(items, pageNum, fullPage = false, detectTable
|
|
|
8898
9744
|
if (cards) return cards.flatMap((group) => extractPageBlocksFallback(group, pageNum, false, detectTables, lex));
|
|
8899
9745
|
}
|
|
8900
9746
|
const blocks = [];
|
|
8901
|
-
const clusterItems = items.map((i) =>
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
9747
|
+
const clusterItems = items.map((i) => {
|
|
9748
|
+
const item = {
|
|
9749
|
+
text: i.text,
|
|
9750
|
+
x: i.x,
|
|
9751
|
+
y: i.y,
|
|
9752
|
+
w: i.w,
|
|
9753
|
+
h: i.h,
|
|
9754
|
+
fontSize: i.fontSize,
|
|
9755
|
+
fontName: i.fontName,
|
|
9756
|
+
hasSpaceBefore: i.hasSpaceBefore,
|
|
9757
|
+
syntheticSpace: i.syntheticSpace,
|
|
9758
|
+
spaceAfter: i.spaceAfter
|
|
9759
|
+
};
|
|
9760
|
+
copyOcrTextGaps(i, item);
|
|
9761
|
+
return item;
|
|
9762
|
+
});
|
|
8913
9763
|
const textRects = items.map((i) => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }));
|
|
8914
9764
|
const imagePanels = fullPage ? splitImagePanels(items, figures) : null;
|
|
8915
9765
|
if (imagePanels) return imagePanels.flatMap((group) => extractPageBlocksFallback(group, pageNum, false, detectTables, lex));
|
|
@@ -9123,6 +9973,29 @@ function rowParagraphs(block) {
|
|
|
9123
9973
|
}
|
|
9124
9974
|
|
|
9125
9975
|
export {
|
|
9976
|
+
grayCrop,
|
|
9977
|
+
inkStats,
|
|
9978
|
+
splitRowBands,
|
|
9979
|
+
leadingTriangle,
|
|
9980
|
+
leaderRuns,
|
|
9981
|
+
inkBounds,
|
|
9982
|
+
bracketFeatures,
|
|
9983
|
+
romanStems,
|
|
9984
|
+
circledAt,
|
|
9985
|
+
quoteHead,
|
|
9986
|
+
edgeTrim,
|
|
9987
|
+
tallInkCount,
|
|
9988
|
+
gapGlyphs,
|
|
9989
|
+
ringBullet,
|
|
9990
|
+
dashRatio,
|
|
9991
|
+
discAt,
|
|
9992
|
+
serifOne,
|
|
9993
|
+
starRun,
|
|
9994
|
+
bracketShape,
|
|
9995
|
+
leadingBullet,
|
|
9996
|
+
rememberOcrTextGaps,
|
|
9997
|
+
copyOcrTextGaps,
|
|
9998
|
+
findOcrTextGaps,
|
|
9126
9999
|
extractLines,
|
|
9127
10000
|
extractImageRegions,
|
|
9128
10001
|
NO_EDGES,
|
|
@@ -9177,4 +10050,4 @@ export {
|
|
|
9177
10050
|
UNRULED_REGION_TABLES,
|
|
9178
10051
|
mergeOcrImageRegions
|
|
9179
10052
|
};
|
|
9180
|
-
//# sourceMappingURL=chunk-
|
|
10053
|
+
//# sourceMappingURL=chunk-EIGCAAVM.js.map
|