kordoc 4.14.3 → 4.14.4
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 +16 -4
- package/dist/{-7XBPXCXN.js → -Z4TJ3QGK.js} +11 -11
- package/dist/{chunk-EBZUZC6F.cjs → chunk-5LAZQCUJ.cjs} +4 -4
- package/dist/{chunk-EBZUZC6F.cjs.map → chunk-5LAZQCUJ.cjs.map} +1 -1
- package/dist/{chunk-NQCUAGLR.js → chunk-6BWU6SQX.js} +1 -1
- package/dist/{chunk-2LQGSDZ6.js.map → chunk-6BWU6SQX.js.map} +1 -1
- package/dist/{chunk-2LQGSDZ6.js → chunk-6CUIXJOC.js} +1 -1
- package/dist/{chunk-NQCUAGLR.js.map → chunk-6CUIXJOC.js.map} +1 -1
- package/dist/{chunk-D6KQKMU3.js → chunk-7SE4EZ45.js} +2 -2
- package/dist/{chunk-YGBZABI4.js → chunk-D4U2TXDC.js} +6 -6
- package/dist/chunk-DPWWXUOG.js +2222 -0
- package/dist/chunk-DPWWXUOG.js.map +1 -0
- package/dist/{chunk-FAZR34CA.js → chunk-GLSBEEZJ.js} +2 -2
- package/dist/{chunk-L5EH2NVO.js → chunk-GUJ4Q73I.js} +2 -2
- package/dist/{chunk-JCVIOLBF.js → chunk-HIJUKZHM.js} +3 -3
- package/dist/{chunk-IW2CB755.cjs → chunk-IMAAFSAX.cjs} +392 -42
- package/dist/chunk-IMAAFSAX.cjs.map +1 -0
- package/dist/{chunk-GTINVM5K.js → chunk-LCW7SEPG.js} +2 -2
- package/dist/{chunk-4R67INKT.js → chunk-LFSQAMSM.js} +389 -39
- package/dist/chunk-LFSQAMSM.js.map +1 -0
- package/dist/{chunk-VJHAUMCF.js → chunk-MG6DJEX4.js} +389 -39
- package/dist/chunk-MG6DJEX4.js.map +1 -0
- package/dist/{chunk-QGW7NMTX.js → chunk-NLU6LBLY.js} +2 -2
- package/dist/{chunk-5VVS2QIJ.cjs → chunk-QEJUY6SK.cjs} +3 -3
- package/dist/{chunk-5VVS2QIJ.cjs.map → chunk-QEJUY6SK.cjs.map} +1 -1
- package/dist/{chunk-VYOGXNQ3.js → chunk-QIF2VXBQ.js} +2 -2
- package/dist/{chunk-I4NNYWIR.js → chunk-TITDJWVZ.js} +2 -2
- package/dist/{chunk-NOAQRCV6.cjs → chunk-USHQCF5T.cjs} +676 -75
- package/dist/chunk-USHQCF5T.cjs.map +1 -0
- package/dist/{chunk-VNFJPTU4.js → chunk-V3DX4DUF.js} +2 -2
- package/dist/{chunk-WB5LW52Z.js → chunk-VDN7LMPL.js} +2 -2
- package/dist/{chunk-IHZEFW4L.js → chunk-VJIL6PIL.js} +2 -2
- package/dist/{chunk-HX4UUF7Y.cjs → chunk-VWUYVEFN.cjs} +1 -1
- package/dist/{chunk-HX4UUF7Y.cjs.map → chunk-VWUYVEFN.cjs.map} +1 -1
- package/dist/{chunk-EMGF6EEV.cjs → chunk-WJHAU73B.cjs} +2 -2
- package/dist/{chunk-EMGF6EEV.cjs.map → chunk-WJHAU73B.cjs.map} +1 -1
- package/dist/{chunk-O2PGQNYE.js → chunk-WJJGW5PL.js} +2 -2
- package/dist/{chunk-CVEERPMP.js → chunk-XVXHXQIO.js} +9 -9
- package/dist/{chunk-4RJ5D7MT.js → chunk-YAKC46HZ.js} +2 -2
- package/dist/{chunk-A275ZQLX.js → chunk-YAVHVFRY.js} +653 -52
- package/dist/chunk-YAVHVFRY.js.map +1 -0
- package/dist/{chunk-UVVEBI22.js → chunk-YT3GC62E.js} +653 -52
- package/dist/chunk-YT3GC62E.js.map +1 -0
- package/dist/chunks-CCMRL4MJ.js +10 -0
- package/dist/cli.js +28 -28
- package/dist/cli.js.map +1 -1
- package/dist/{formula-CPO2TZGB.cjs → formula-5LOVHAKE.cjs} +9 -9
- package/dist/{formula-CPO2TZGB.cjs.map → formula-5LOVHAKE.cjs.map} +1 -1
- package/dist/{formula-X44GV7TN.js → formula-7RANCW5L.js} +3 -3
- package/dist/{formula-NOIDZEBA.js → formula-7UYJOVTB.js} +4 -4
- package/dist/{image-ocr-72RAR4WR.js → image-ocr-I6JOCATS.js} +8 -8
- package/dist/{image-ocr-SUGT64ZM.cjs → image-ocr-NYBJEOH2.cjs} +13 -13
- package/dist/{image-ocr-SUGT64ZM.cjs.map → image-ocr-NYBJEOH2.cjs.map} +1 -1
- package/dist/{image-ocr-R6W6A75P.js → image-ocr-ZV6PDBUQ.js} +6 -6
- package/dist/index.cjs +2137 -537
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1851 -251
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +24 -24
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-57CLEBCO.js → model-bundle-2C5IYDLE.js} +5 -5
- package/dist/{models-7ELIFTD7.js → models-SEQZHZFT.js} +5 -5
- package/dist/{parser-4NKAYWAX.js → parser-3FNXEP6T.js} +485 -47
- package/dist/parser-3FNXEP6T.js.map +1 -0
- package/dist/{parser-HWFQRSUQ.js → parser-FJBJXRJK.js} +485 -47
- package/dist/parser-FJBJXRJK.js.map +1 -0
- package/dist/{parser-RANGXGBD.cjs → parser-HVEKF73B.cjs} +535 -97
- package/dist/parser-HVEKF73B.cjs.map +1 -0
- package/dist/pdf-ocr-JRDBJ6TA.cjs +14 -0
- package/dist/{pdf-ocr-VHSFACH7.cjs.map → pdf-ocr-JRDBJ6TA.cjs.map} +1 -1
- package/dist/pdf-ocr-UUBWBEHL.js +16 -0
- package/dist/pdf-ocr-VJSF2SQC.js +13 -0
- package/dist/{profile-io-V52PL5ZO.js → profile-io-RUFZOZO6.js} +3 -3
- package/dist/{rasterize-ZIGLJDOG.js → rasterize-IOYXR5SI.js} +3 -3
- package/dist/{redact-TIQEQI3M.js → redact-NMIKVHMP.js} +2 -2
- package/dist/{redact-doc-KXZ3TZAG.js → redact-doc-QBHNJ2BU.js} +98 -30
- package/dist/redact-doc-QBHNJ2BU.js.map +1 -0
- package/dist/{render-U5PE6V2E.js → render-YZ2TEC3N.js} +7 -7
- package/dist/seal-ECV44TUH.js +10 -0
- package/dist/{setup-YRDM4ODP.js → setup-MPIEP6XT.js} +3 -3
- package/dist/visual-ICMOWUCR.js +19 -0
- package/dist/{watch-5IOLBATT.js → watch-RYO7MN5B.js} +12 -12
- package/package.json +2 -2
- package/dist/chunk-4R67INKT.js.map +0 -1
- package/dist/chunk-A275ZQLX.js.map +0 -1
- package/dist/chunk-IH56ZISC.js +0 -620
- package/dist/chunk-IH56ZISC.js.map +0 -1
- package/dist/chunk-IW2CB755.cjs.map +0 -1
- package/dist/chunk-NOAQRCV6.cjs.map +0 -1
- package/dist/chunk-UVVEBI22.js.map +0 -1
- package/dist/chunk-VJHAUMCF.js.map +0 -1
- package/dist/chunks-I7TEMJXH.js +0 -10
- package/dist/parser-4NKAYWAX.js.map +0 -1
- package/dist/parser-HWFQRSUQ.js.map +0 -1
- package/dist/parser-RANGXGBD.cjs.map +0 -1
- package/dist/pdf-ocr-BNC34MNU.js +0 -13
- package/dist/pdf-ocr-QX2GB33Q.js +0 -16
- package/dist/pdf-ocr-VHSFACH7.cjs +0 -14
- package/dist/redact-doc-KXZ3TZAG.js.map +0 -1
- package/dist/seal-DNXJBILR.js +0 -10
- package/dist/visual-5XWMA4EV.js +0 -19
- /package/dist/{-7XBPXCXN.js.map → -Z4TJ3QGK.js.map} +0 -0
- /package/dist/{chunk-D6KQKMU3.js.map → chunk-7SE4EZ45.js.map} +0 -0
- /package/dist/{chunk-YGBZABI4.js.map → chunk-D4U2TXDC.js.map} +0 -0
- /package/dist/{chunk-FAZR34CA.js.map → chunk-GLSBEEZJ.js.map} +0 -0
- /package/dist/{chunk-L5EH2NVO.js.map → chunk-GUJ4Q73I.js.map} +0 -0
- /package/dist/{chunk-JCVIOLBF.js.map → chunk-HIJUKZHM.js.map} +0 -0
- /package/dist/{chunk-GTINVM5K.js.map → chunk-LCW7SEPG.js.map} +0 -0
- /package/dist/{chunk-QGW7NMTX.js.map → chunk-NLU6LBLY.js.map} +0 -0
- /package/dist/{chunk-VYOGXNQ3.js.map → chunk-QIF2VXBQ.js.map} +0 -0
- /package/dist/{chunk-I4NNYWIR.js.map → chunk-TITDJWVZ.js.map} +0 -0
- /package/dist/{chunk-VNFJPTU4.js.map → chunk-V3DX4DUF.js.map} +0 -0
- /package/dist/{chunk-WB5LW52Z.js.map → chunk-VDN7LMPL.js.map} +0 -0
- /package/dist/{chunk-IHZEFW4L.js.map → chunk-VJIL6PIL.js.map} +0 -0
- /package/dist/{chunk-O2PGQNYE.js.map → chunk-WJJGW5PL.js.map} +0 -0
- /package/dist/{chunk-CVEERPMP.js.map → chunk-XVXHXQIO.js.map} +0 -0
- /package/dist/{chunk-4RJ5D7MT.js.map → chunk-YAKC46HZ.js.map} +0 -0
- /package/dist/{chunks-I7TEMJXH.js.map → chunks-CCMRL4MJ.js.map} +0 -0
- /package/dist/{formula-X44GV7TN.js.map → formula-7RANCW5L.js.map} +0 -0
- /package/dist/{formula-NOIDZEBA.js.map → formula-7UYJOVTB.js.map} +0 -0
- /package/dist/{image-ocr-72RAR4WR.js.map → image-ocr-I6JOCATS.js.map} +0 -0
- /package/dist/{image-ocr-R6W6A75P.js.map → image-ocr-ZV6PDBUQ.js.map} +0 -0
- /package/dist/{model-bundle-57CLEBCO.js.map → model-bundle-2C5IYDLE.js.map} +0 -0
- /package/dist/{models-7ELIFTD7.js.map → models-SEQZHZFT.js.map} +0 -0
- /package/dist/{pdf-ocr-BNC34MNU.js.map → pdf-ocr-UUBWBEHL.js.map} +0 -0
- /package/dist/{pdf-ocr-QX2GB33Q.js.map → pdf-ocr-VJSF2SQC.js.map} +0 -0
- /package/dist/{profile-io-V52PL5ZO.js.map → profile-io-RUFZOZO6.js.map} +0 -0
- /package/dist/{rasterize-ZIGLJDOG.js.map → rasterize-IOYXR5SI.js.map} +0 -0
- /package/dist/{redact-TIQEQI3M.js.map → redact-NMIKVHMP.js.map} +0 -0
- /package/dist/{render-U5PE6V2E.js.map → render-YZ2TEC3N.js.map} +0 -0
- /package/dist/{seal-DNXJBILR.js.map → seal-ECV44TUH.js.map} +0 -0
- /package/dist/{setup-YRDM4ODP.js.map → setup-MPIEP6XT.js.map} +0 -0
- /package/dist/{visual-5XWMA4EV.js.map → visual-ICMOWUCR.js.map} +0 -0
- /package/dist/{watch-5IOLBATT.js.map → watch-RYO7MN5B.js.map} +0 -0
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureModelsIn,
|
|
3
3
|
getModelsDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VDN7LMPL.js";
|
|
5
5
|
import {
|
|
6
6
|
extractPageBlocksWithLines
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-YT3GC62E.js";
|
|
8
8
|
|
|
9
9
|
// src/ocr/ruling-lines.ts
|
|
10
|
-
var INK_LUMA_MAX =
|
|
10
|
+
var INK_LUMA_MAX = 205;
|
|
11
11
|
var MIN_LINE_LENGTH_PT = 20;
|
|
12
12
|
var MAX_LINE_WIDTH_PT = 2.5;
|
|
13
13
|
var GAP_TOL_PX = 2;
|
|
14
14
|
var BAND_OVERLAP_RATIO = 0.5;
|
|
15
15
|
var SURROUND_INK_RATIO = 0.35;
|
|
16
|
+
var DOT_MAX_GAP_PT = 1.3;
|
|
17
|
+
var DOT_MAX_DASH_PT = 4;
|
|
18
|
+
var DOT_MAX_PERIOD_PT = 2.2;
|
|
19
|
+
var DOT_MIN_COUNT = 8;
|
|
20
|
+
var DOT_TOUCH_PT = 1.5;
|
|
21
|
+
var FILL_DELTA = 10;
|
|
22
|
+
var FILL_MIN_LENGTH_PT = 10;
|
|
23
|
+
var FILL_MIN_DEPTH_PT = 4;
|
|
24
|
+
var FILL_FLAT_TOL = 12;
|
|
25
|
+
var FILL_FLAT_RATIO = 0.8;
|
|
16
26
|
function detectRulingLines(rgba, width, height, scale) {
|
|
17
27
|
const minLenPx = Math.round(MIN_LINE_LENGTH_PT * scale);
|
|
18
28
|
const maxThickPx = Math.max(1, Math.floor(MAX_LINE_WIDTH_PT * scale));
|
|
@@ -21,11 +31,164 @@ function detectRulingLines(rgba, width, height, scale) {
|
|
|
21
31
|
const luma = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
|
|
22
32
|
if (luma <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1;
|
|
23
33
|
}
|
|
24
|
-
const
|
|
25
|
-
const
|
|
34
|
+
const solid = (at, line, span) => solidRuns(at, line, span, minLenPx);
|
|
35
|
+
const dot = {
|
|
36
|
+
maxGap: Math.max(2, Math.round(DOT_MAX_GAP_PT * scale)),
|
|
37
|
+
maxDash: Math.max(2, Math.round(DOT_MAX_DASH_PT * scale)),
|
|
38
|
+
maxPeriod: DOT_MAX_PERIOD_PT * scale
|
|
39
|
+
};
|
|
40
|
+
const dotted = (at, line, span) => dottedRuns(at, line, span, minLenPx, dot.maxGap, dot.maxDash, dot.maxPeriod);
|
|
41
|
+
const horizontals = detectBands(ink, width, height, maxThickPx, false, solid);
|
|
42
|
+
const verticals = detectBands(ink, width, height, maxThickPx, true, solid);
|
|
43
|
+
const dotH = detectBands(ink, width, height, maxThickPx, false, dotted);
|
|
44
|
+
const dotV = detectBands(ink, width, height, maxThickPx, true, dotted);
|
|
45
|
+
const tol = Math.max(2, Math.round(DOT_TOUCH_PT * scale));
|
|
46
|
+
const allH = [...horizontals, ...dotH], allV = [...verticals, ...dotV];
|
|
47
|
+
const touchesV = (a) => allV.some((p) => p.x1 >= a.x1 - tol && p.x1 <= a.x2 + tol && a.y1 >= p.y1 - tol && a.y1 <= p.y2 + tol);
|
|
48
|
+
const touchesH = (a) => allH.some((p) => p.y1 >= a.y1 - tol && p.y1 <= a.y2 + tol && a.x1 >= p.x1 - tol && a.x1 <= p.x2 + tol);
|
|
49
|
+
horizontals.push(...dotH.filter(touchesV));
|
|
50
|
+
verticals.push(...dotV.filter(touchesH));
|
|
51
|
+
const inkV = [...verticals];
|
|
52
|
+
const ruleH = horizontals.filter((p) => inkV.some((v) => v.x1 >= p.x1 - tol && v.x1 <= p.x2 + tol && p.y1 >= v.y1 - tol && p.y1 <= v.y2 + tol));
|
|
53
|
+
const fill = fillEdges(rgba, width, height, scale);
|
|
54
|
+
const fillH = fill.horizontals.filter((a) => ruleH.some((p) => Math.abs(p.x1 - a.x1) <= tol && Math.abs(p.x2 - a.x2) <= tol));
|
|
55
|
+
horizontals.push(...fillH);
|
|
56
|
+
const edgeH = [...ruleH, ...fillH];
|
|
57
|
+
verticals.push(...fill.verticals.filter((a) => inkV.some((p) => Math.abs(p.x1 - a.x1) <= tol && p.y1 <= a.y2 + tol && p.y2 >= a.y1 - tol) || edgeH.some((p) => (Math.abs(p.x1 - a.x1) <= tol || Math.abs(p.x2 - a.x1) <= tol) && p.y1 >= a.y1 - tol && p.y1 <= a.y2 + tol)));
|
|
26
58
|
return { horizontals, verticals };
|
|
27
59
|
}
|
|
28
|
-
function
|
|
60
|
+
function fillEdges(rgba, width, height, scale) {
|
|
61
|
+
const k = Math.max(1, Math.round(scale));
|
|
62
|
+
const w = Math.floor(width / k), h = Math.floor(height / k), n = w * h;
|
|
63
|
+
const luma = new Uint8Array(n);
|
|
64
|
+
const hist = new Uint32Array(256);
|
|
65
|
+
for (let y = 0; y < h; y++) {
|
|
66
|
+
for (let x = 0; x < w; x++) {
|
|
67
|
+
let mx = 0;
|
|
68
|
+
for (let by = 0; by < k; by++) {
|
|
69
|
+
let i = ((y * k + by) * width + x * k) * 4;
|
|
70
|
+
for (let bx = 0; bx < k; bx++, i += 4) {
|
|
71
|
+
const l = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
|
|
72
|
+
if (l > mx) mx = l;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
luma[y * w + x] = mx;
|
|
76
|
+
hist[mx]++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
let bg = 255;
|
|
80
|
+
for (let v = 0; v < 256; v++) if (hist[v] > hist[bg]) bg = v;
|
|
81
|
+
const tint = new Uint8Array(n);
|
|
82
|
+
for (let p = 0; p < n; p++) if (luma[p] < bg - FILL_DELTA) tint[p] = 1;
|
|
83
|
+
const depth = Math.max(2, Math.round(FILL_MIN_DEPTH_PT * scale / k));
|
|
84
|
+
const minLen = Math.round(FILL_MIN_LENGTH_PT * scale / k);
|
|
85
|
+
const solid = (at, line, span) => solidRuns(at, line, span, minLen);
|
|
86
|
+
const run = new Uint16Array(n);
|
|
87
|
+
const mask = new Uint8Array(n);
|
|
88
|
+
const out = { horizontals: [], verticals: [] };
|
|
89
|
+
for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0]]) {
|
|
90
|
+
const step = dy * w + dx;
|
|
91
|
+
const ys = dy > 0 ? [h - 1, -1, -1] : [0, h, 1];
|
|
92
|
+
const xs = dx > 0 ? [w - 1, -1, -1] : [0, w, 1];
|
|
93
|
+
for (let y = ys[0]; y !== ys[1]; y += ys[2]) {
|
|
94
|
+
for (let x = xs[0]; x !== xs[1]; x += xs[2]) {
|
|
95
|
+
const p = y * w + x;
|
|
96
|
+
const nx = x + dx, ny = y + dy;
|
|
97
|
+
run[p] = tint[p] ? nx >= 0 && nx < w && ny >= 0 && ny < h ? Math.min(65535, run[p + step] + 1) : 1 : 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
mask.fill(0);
|
|
101
|
+
for (let y = 0; y < h; y++) {
|
|
102
|
+
for (let x = 0; x < w; x++) {
|
|
103
|
+
const p = y * w + x;
|
|
104
|
+
if (run[p] < depth) continue;
|
|
105
|
+
const ox = x - dx, oy = y - dy;
|
|
106
|
+
if (ox < 0 || oy < 0 || ox >= w || oy >= h || !tint[p - step]) mask[p] = 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const vertical = dx !== 0;
|
|
110
|
+
const flat = (s) => {
|
|
111
|
+
const d = depth >> 1;
|
|
112
|
+
const vals = [];
|
|
113
|
+
if (vertical) {
|
|
114
|
+
const x = Math.round(s.x1) + dx * d;
|
|
115
|
+
for (let y = Math.round(s.y1); y <= s.y2; y++) if (x >= 0 && x < w) vals.push(luma[y * w + x]);
|
|
116
|
+
} else {
|
|
117
|
+
const y = Math.round(s.y1) + dy * d;
|
|
118
|
+
for (let x = Math.round(s.x1); x <= s.x2; x++) if (y >= 0 && y < h) vals.push(luma[y * w + x]);
|
|
119
|
+
}
|
|
120
|
+
if (!vals.length) return false;
|
|
121
|
+
const med = [...vals].sort((a, b) => a - b)[vals.length >> 1];
|
|
122
|
+
return vals.filter((v) => Math.abs(v - med) <= FILL_FLAT_TOL).length >= vals.length * FILL_FLAT_RATIO;
|
|
123
|
+
};
|
|
124
|
+
for (const s of detectBands(mask, w, h, 1, vertical, solid)) {
|
|
125
|
+
if (!flat(s)) continue;
|
|
126
|
+
const c = (v) => v * k + (k - 1) / 2;
|
|
127
|
+
(vertical ? out.verticals : out.horizontals).push({ x1: c(s.x1), y1: c(s.y1), x2: c(s.x2), y2: c(s.y2), thicknessPx: 1 });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function solidRuns(at, line, span, minLenPx) {
|
|
133
|
+
const runs = [];
|
|
134
|
+
let runStart = -1;
|
|
135
|
+
let gap = 0;
|
|
136
|
+
for (let pos = 0; pos <= span; pos++) {
|
|
137
|
+
const on = pos < span && at(line, pos) === 1;
|
|
138
|
+
if (on) {
|
|
139
|
+
if (runStart < 0) runStart = pos;
|
|
140
|
+
gap = 0;
|
|
141
|
+
} else if (runStart >= 0) {
|
|
142
|
+
if (++gap > GAP_TOL_PX || pos >= span) {
|
|
143
|
+
const hi = pos - gap;
|
|
144
|
+
if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
|
|
145
|
+
runStart = -1;
|
|
146
|
+
gap = 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return runs;
|
|
151
|
+
}
|
|
152
|
+
function dottedRuns(at, line, span, minLenPx, maxGap, maxDash, maxPeriod) {
|
|
153
|
+
const runs = [];
|
|
154
|
+
const chain = [];
|
|
155
|
+
const median = (xs) => xs.sort((a, b) => a - b)[xs.length >> 1];
|
|
156
|
+
const flush = () => {
|
|
157
|
+
const cnt = chain.length >> 1;
|
|
158
|
+
if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {
|
|
159
|
+
const periods = [], dashes = [], gaps = [];
|
|
160
|
+
for (let k = 0; k < cnt; k++) {
|
|
161
|
+
dashes.push(chain[2 * k + 1] - chain[2 * k] + 1);
|
|
162
|
+
if (k) {
|
|
163
|
+
periods.push(chain[2 * k] - chain[2 * k - 2]);
|
|
164
|
+
gaps.push(chain[2 * k] - chain[2 * k - 1] - 1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (median(periods) <= maxPeriod || median(dashes) >= 1.5 * median(gaps)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] });
|
|
168
|
+
}
|
|
169
|
+
chain.length = 0;
|
|
170
|
+
};
|
|
171
|
+
let s = -1;
|
|
172
|
+
for (let pos = 0; pos <= span; pos++) {
|
|
173
|
+
const on = pos < span && at(line, pos) === 1;
|
|
174
|
+
if (on) {
|
|
175
|
+
if (s < 0) s = pos;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (s < 0) continue;
|
|
179
|
+
const lo = s, hi = pos - 1;
|
|
180
|
+
s = -1;
|
|
181
|
+
if (hi - lo + 1 > maxDash) {
|
|
182
|
+
flush();
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (chain.length && lo - chain[chain.length - 1] - 1 > maxGap) flush();
|
|
186
|
+
chain.push(lo, hi);
|
|
187
|
+
}
|
|
188
|
+
flush();
|
|
189
|
+
return runs;
|
|
190
|
+
}
|
|
191
|
+
function detectBands(ink, width, height, maxThickPx, transpose, readRuns) {
|
|
29
192
|
const lines = transpose ? width : height;
|
|
30
193
|
const span = transpose ? height : width;
|
|
31
194
|
const at = transpose ? (line, pos) => ink[pos * width + line] : (line, pos) => ink[line * width + pos];
|
|
@@ -50,23 +213,7 @@ function detectBands(ink, width, height, minLenPx, maxThickPx, transpose) {
|
|
|
50
213
|
);
|
|
51
214
|
};
|
|
52
215
|
for (let line = 0; line < lines; line++) {
|
|
53
|
-
const runs =
|
|
54
|
-
let runStart = -1;
|
|
55
|
-
let gap = 0;
|
|
56
|
-
for (let pos = 0; pos <= span; pos++) {
|
|
57
|
-
const on = pos < span && at(line, pos) === 1;
|
|
58
|
-
if (on) {
|
|
59
|
-
if (runStart < 0) runStart = pos;
|
|
60
|
-
gap = 0;
|
|
61
|
-
} else if (runStart >= 0) {
|
|
62
|
-
if (++gap > GAP_TOL_PX || pos >= span) {
|
|
63
|
-
const hi = pos - gap;
|
|
64
|
-
if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
|
|
65
|
-
runStart = -1;
|
|
66
|
-
gap = 0;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
216
|
+
const runs = readRuns(at, line, span);
|
|
70
217
|
const next = [];
|
|
71
218
|
const used = /* @__PURE__ */ new Set();
|
|
72
219
|
for (const band of open) {
|
|
@@ -309,6 +456,113 @@ function splitRowBands(gray, w, h, ink, minBandRatio) {
|
|
|
309
456
|
return { y0, y1, x0, x1 };
|
|
310
457
|
});
|
|
311
458
|
}
|
|
459
|
+
function components(gray, w, h, ink) {
|
|
460
|
+
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
461
|
+
const comps = [];
|
|
462
|
+
const label = new Int32Array(w * h);
|
|
463
|
+
const stack = [];
|
|
464
|
+
for (let p0 = 0; p0 < w * h; p0++) {
|
|
465
|
+
if (label[p0] || !isInk(gray[p0])) continue;
|
|
466
|
+
const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 };
|
|
467
|
+
label[p0] = c.id;
|
|
468
|
+
stack.push(p0);
|
|
469
|
+
while (stack.length) {
|
|
470
|
+
const p = stack.pop();
|
|
471
|
+
const x = p % w, y = p / w | 0;
|
|
472
|
+
if (x < c.x0) c.x0 = x;
|
|
473
|
+
if (x + 1 > c.x1) c.x1 = x + 1;
|
|
474
|
+
if (y < c.y0) c.y0 = y;
|
|
475
|
+
if (y + 1 > c.y1) c.y1 = y + 1;
|
|
476
|
+
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
|
477
|
+
const nx = x + dx, ny = y + dy;
|
|
478
|
+
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
|
|
479
|
+
const q = ny * w + nx;
|
|
480
|
+
if (!label[q] && isInk(gray[q])) {
|
|
481
|
+
label[q] = c.id;
|
|
482
|
+
stack.push(q);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c);
|
|
487
|
+
}
|
|
488
|
+
return { comps, label };
|
|
489
|
+
}
|
|
490
|
+
function leadingTriangle(gray, w, h, ink) {
|
|
491
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
492
|
+
let charH = 0;
|
|
493
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
494
|
+
const first = comps.filter((c) => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0];
|
|
495
|
+
if (!first || charH < 8) return null;
|
|
496
|
+
const cw = first.x1 - first.x0, ch = first.y1 - first.y0;
|
|
497
|
+
if (cw < ch * 0.8 || cw > ch * 1.8) return null;
|
|
498
|
+
const rowSpan = (y) => {
|
|
499
|
+
let n = 0, lo = -1, hi = -1, runs = 0, prev = false;
|
|
500
|
+
for (let x = first.x0; x < first.x1; x++) {
|
|
501
|
+
const on = label[y * w + x] === first.id;
|
|
502
|
+
if (on) {
|
|
503
|
+
n++;
|
|
504
|
+
if (lo < 0) lo = x - first.x0;
|
|
505
|
+
hi = x - first.x0;
|
|
506
|
+
if (!prev) runs++;
|
|
507
|
+
}
|
|
508
|
+
prev = on;
|
|
509
|
+
}
|
|
510
|
+
return { n, lo, hi, runs };
|
|
511
|
+
};
|
|
512
|
+
const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n);
|
|
513
|
+
if (base < cw * 0.7) return null;
|
|
514
|
+
for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {
|
|
515
|
+
const r = rowSpan(y);
|
|
516
|
+
if (r.n === 0) continue;
|
|
517
|
+
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;
|
|
518
|
+
}
|
|
519
|
+
const mid = rowSpan(first.y0 + Math.round(ch * 0.6));
|
|
520
|
+
if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null;
|
|
521
|
+
return mid.runs >= 2 ? "\u25B3" : "\u25B2";
|
|
522
|
+
}
|
|
523
|
+
function leaderRuns(gray, w, h, ink, minDots) {
|
|
524
|
+
const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0);
|
|
525
|
+
const blobs = [];
|
|
526
|
+
for (const c of comps) {
|
|
527
|
+
const last = blobs[blobs.length - 1];
|
|
528
|
+
if (last && c.x0 < last.x1) {
|
|
529
|
+
last.x1 = Math.max(last.x1, c.x1);
|
|
530
|
+
last.y0 = Math.min(last.y0, c.y0);
|
|
531
|
+
last.y1 = Math.max(last.y1, c.y1);
|
|
532
|
+
} else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 });
|
|
533
|
+
}
|
|
534
|
+
let charH = 0;
|
|
535
|
+
for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0);
|
|
536
|
+
if (charH < 8) return [];
|
|
537
|
+
const small = (c) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3;
|
|
538
|
+
let bandTop = h, bandBot = 0;
|
|
539
|
+
for (const c of comps) if (!small(c)) {
|
|
540
|
+
bandTop = Math.min(bandTop, c.y0);
|
|
541
|
+
bandBot = Math.max(bandBot, c.y1);
|
|
542
|
+
}
|
|
543
|
+
const cy = (c) => (c.y0 + c.y1) / 2;
|
|
544
|
+
const isDot = (b) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot;
|
|
545
|
+
const runs = [];
|
|
546
|
+
for (let i = 0; i < blobs.length; ) {
|
|
547
|
+
if (!isDot(blobs[i])) {
|
|
548
|
+
i++;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
let j = i;
|
|
552
|
+
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++;
|
|
553
|
+
const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i]);
|
|
554
|
+
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));
|
|
555
|
+
const run = blobs.slice(i, j + 1).filter((b) => b.x0 > 0 && b.x1 < w);
|
|
556
|
+
const spread = (xs) => {
|
|
557
|
+
const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : void 0);
|
|
558
|
+
return v[v.length - 1] / Math.max(1, v[0]);
|
|
559
|
+
};
|
|
560
|
+
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;
|
|
561
|
+
if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1]);
|
|
562
|
+
i = j + 1;
|
|
563
|
+
}
|
|
564
|
+
return runs;
|
|
565
|
+
}
|
|
312
566
|
function inkBounds(gray, w, h, ink) {
|
|
313
567
|
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
314
568
|
const colInk = new Uint32Array(w);
|
|
@@ -346,6 +600,7 @@ function restoreSymbols(text) {
|
|
|
346
600
|
s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
|
|
347
601
|
s = s.replace(/(?<![A-Za-z])O{2,}(?![A-Za-z])/g, (m, i) => /[\uac00-\ud7a3]$/.test(s.slice(Math.max(0, i - 2), i).trimEnd()) || /^\s?[\uac00-\ud7a3]/.test(s.slice(i + m.length)) ? "\u25CB".repeat(m.length) : m);
|
|
348
602
|
s = s.replace(/[\u2206\u0394]/g, "\u25B3");
|
|
603
|
+
s = s.replace(/\(cid:\d*\)/g, "");
|
|
349
604
|
return smartQuotes(joinDigitGroups(s));
|
|
350
605
|
}
|
|
351
606
|
function joinDigitGroups(s) {
|
|
@@ -381,6 +636,53 @@ function smartQuotes(s) {
|
|
|
381
636
|
function isDotFragment(text) {
|
|
382
637
|
return /^[\s.\u00b7\u2024\u2025\u2026\u2027\u2219\u22c5\u318d]+$/.test(text);
|
|
383
638
|
}
|
|
639
|
+
function joinLeaderItems(items, ends) {
|
|
640
|
+
const gone = /* @__PURE__ */ new Set();
|
|
641
|
+
const sameLine = (a, b) => Math.abs(a.y + a.h / 2 - (b.y + b.h / 2)) <= Math.max(a.h, b.h) / 2;
|
|
642
|
+
const merge = (left, right) => {
|
|
643
|
+
const x2 = Math.max(left.x + left.w, right.x + right.w), y2 = Math.max(left.y + left.h, right.y + right.h);
|
|
644
|
+
left.text = left.text.replace(/\s*\u2026$/, "") + " \u2026 " + right.text.replace(/^\u2026\s*/, "");
|
|
645
|
+
left.x = Math.min(left.x, right.x);
|
|
646
|
+
left.y = Math.min(left.y, right.y);
|
|
647
|
+
left.w = x2 - left.x;
|
|
648
|
+
left.h = y2 - left.y;
|
|
649
|
+
left.confidence = Math.min(left.confidence, right.confidence);
|
|
650
|
+
gone.add(right);
|
|
651
|
+
};
|
|
652
|
+
const neighbor = (it, toLeft) => {
|
|
653
|
+
let best = null;
|
|
654
|
+
for (const o of items) {
|
|
655
|
+
if (o === it || gone.has(o) || !sameLine(o, it)) continue;
|
|
656
|
+
if (toLeft ? o.x + o.w > it.x + 2 : o.x < it.x + it.w - 2) continue;
|
|
657
|
+
if (!best || (toLeft ? o.x + o.w > best.x + best.w : o.x < best.x)) best = o;
|
|
658
|
+
}
|
|
659
|
+
return best;
|
|
660
|
+
};
|
|
661
|
+
for (const it of [...items].sort((a, b) => b.x - a.x)) {
|
|
662
|
+
if (gone.has(it) || !ends.get(it)?.lead) continue;
|
|
663
|
+
const left = neighbor(it, true);
|
|
664
|
+
if (left) merge(left, it);
|
|
665
|
+
}
|
|
666
|
+
for (const it of [...items].sort((a, b) => a.x - b.x)) {
|
|
667
|
+
if (gone.has(it) || !ends.get(it)?.trail) continue;
|
|
668
|
+
const right = neighbor(it, false);
|
|
669
|
+
if (right) merge(it, right);
|
|
670
|
+
}
|
|
671
|
+
const toc = items.filter((it) => !gone.has(it) && /\s\u2026\s\d{1,4}$/.test(it.text));
|
|
672
|
+
if (toc.length >= 2) {
|
|
673
|
+
for (const it of items) {
|
|
674
|
+
if (gone.has(it) || toc.includes(it) || !/^[\s.:\u00b7\u2026]*\d{1,4}$/.test(it.text)) continue;
|
|
675
|
+
const x2 = it.x + it.w;
|
|
676
|
+
if (toc.filter((t) => Math.abs(t.x + t.w - x2) <= Math.max(t.h, it.h)).length < 2) continue;
|
|
677
|
+
const left = neighbor(it, true);
|
|
678
|
+
if (!left || toc.includes(left)) continue;
|
|
679
|
+
left.text = left.text.replace(/[\s.:\u00b7\u2026]+$/, "");
|
|
680
|
+
it.text = it.text.replace(/^[\s.:\u00b7\u2026]+/, "");
|
|
681
|
+
merge(left, it);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return items.filter((it) => !gone.has(it));
|
|
685
|
+
}
|
|
384
686
|
function restoreBulletItems(items) {
|
|
385
687
|
for (const it of items) {
|
|
386
688
|
if (it.text !== "O" && it.text !== "o") continue;
|
|
@@ -471,7 +773,8 @@ var DEFAULT_OCR_TUNING = Object.freeze({
|
|
|
471
773
|
splitTall: true,
|
|
472
774
|
minInkContrast: 35,
|
|
473
775
|
postprocess: true,
|
|
474
|
-
tightBoxes: true
|
|
776
|
+
tightBoxes: true,
|
|
777
|
+
splitLeaders: true
|
|
475
778
|
});
|
|
476
779
|
var DET_MIN_SIZE = 3;
|
|
477
780
|
var DET_MAX_BOXES = 1e3;
|
|
@@ -479,6 +782,9 @@ var REC_MIN_WIDTH = 320;
|
|
|
479
782
|
var REC_BATCH_MAX_PIXELS = 48 * 16e3;
|
|
480
783
|
var TALL_RATIO = 1.5;
|
|
481
784
|
var ROTATE_RATIO = 3;
|
|
785
|
+
var LEADER_MIN_DOTS = 4;
|
|
786
|
+
var LEADER_CONTEXT = 0.5;
|
|
787
|
+
var MIN_INK_RATIO = 0.01;
|
|
482
788
|
var DET_MEAN = [0.485, 0.456, 0.406];
|
|
483
789
|
var DET_STD = [0.229, 0.224, 0.225];
|
|
484
790
|
var OcrEngine = class _OcrEngine {
|
|
@@ -541,11 +847,13 @@ var OcrEngine = class _OcrEngine {
|
|
|
541
847
|
if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return [];
|
|
542
848
|
const boxes = await this.detect(rgba, width, height, tuning);
|
|
543
849
|
const jobs = [];
|
|
850
|
+
const joins = [];
|
|
544
851
|
let group = 0;
|
|
545
852
|
for (const b of boxes) {
|
|
546
853
|
const gray = grayCrop(rgba, width, b);
|
|
547
854
|
const ink = inkStats(gray);
|
|
548
855
|
if (tuning.minInkContrast > 0 && ink.contrast < tuning.minInkContrast) continue;
|
|
856
|
+
if (tuning.minInkContrast > 0 && ink.inkRatio < MIN_INK_RATIO) continue;
|
|
549
857
|
if (tuning.splitTall && b.h >= b.w * TALL_RATIO) {
|
|
550
858
|
const bands = splitRowBands(gray, b.w, b.h, ink, 0.45);
|
|
551
859
|
if (bands.length >= 2) {
|
|
@@ -558,6 +866,24 @@ var OcrEngine = class _OcrEngine {
|
|
|
558
866
|
continue;
|
|
559
867
|
}
|
|
560
868
|
}
|
|
869
|
+
const leaders = tuning.splitLeaders ? leaderRuns(gray, b.w, b.h, ink, LEADER_MIN_DOTS) : [];
|
|
870
|
+
if (leaders.length) {
|
|
871
|
+
const join2 = joins.length;
|
|
872
|
+
joins.push({ box: b, parts: [], trailDots: false });
|
|
873
|
+
let x0 = 0, dots = false;
|
|
874
|
+
for (const [a, c] of [...leaders, [b.w, b.w]]) {
|
|
875
|
+
const bare = { x: b.x + x0, y: b.y, w: a - x0, h: b.h };
|
|
876
|
+
const end = c > a ? Math.min(c, a + Math.round(b.h * LEADER_CONTEXT)) : a;
|
|
877
|
+
x0 = c;
|
|
878
|
+
if (bare.w >= DET_MIN_SIZE && inkStats(grayCrop(rgba, width, bare)).contrast >= Math.max(1, tuning.minInkContrast)) {
|
|
879
|
+
jobs.push({ box: { ...bare, w: end - (bare.x - b.x) }, rot: 0, group: group++, join: join2, dotsBefore: dots, trimDots: end > a });
|
|
880
|
+
dots = false;
|
|
881
|
+
}
|
|
882
|
+
if (c > a) dots = true;
|
|
883
|
+
}
|
|
884
|
+
joins[join2].trailDots = dots;
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
561
887
|
jobs.push({ box: b, rot: 0, group: group++ });
|
|
562
888
|
}
|
|
563
889
|
const results = await this.recognizeJobs(rgba, width, jobs, tuning.recBatch);
|
|
@@ -568,27 +894,50 @@ var OcrEngine = class _OcrEngine {
|
|
|
568
894
|
const cur = best.get(job.group);
|
|
569
895
|
if (!cur || r.confidence > cur.confidence) best.set(job.group, { job, ...r });
|
|
570
896
|
});
|
|
571
|
-
|
|
897
|
+
let items = [];
|
|
572
898
|
for (const { job, text: raw, confidence } of best.values()) {
|
|
573
|
-
|
|
899
|
+
let text = tuning.postprocess ? restoreSymbols(raw.trim()) : raw;
|
|
574
900
|
if (!text.trim()) continue;
|
|
901
|
+
if (tuning.postprocess && /^\d/.test(text) && job.rot === 0) {
|
|
902
|
+
const g = grayCrop(rgba, width, job.box);
|
|
903
|
+
const tri = leadingTriangle(g, job.box.w, job.box.h, inkStats(g));
|
|
904
|
+
if (tri) text = tri + text;
|
|
905
|
+
}
|
|
575
906
|
if (tuning.postprocess && isDotFragment(text)) continue;
|
|
576
907
|
if (confidence < tuning.textScore) {
|
|
577
908
|
if (stats) stats.droppedLowConf++;
|
|
578
909
|
continue;
|
|
579
910
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
b = { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 };
|
|
911
|
+
if (job.join !== void 0) {
|
|
912
|
+
if (job.trimDots) text = text.replace(/[\s.:\u00b7\u2022\u2024\u2025\u2026\u2219\u22c5\u318d]+$/u, "");
|
|
913
|
+
if (text) joins[job.join].parts.push({ x: job.box.x, text, dotsBefore: job.dotsBefore === true, confidence });
|
|
914
|
+
continue;
|
|
585
915
|
}
|
|
586
|
-
items.push({ text,
|
|
916
|
+
items.push({ text, ...this.itemBox(rgba, width, job.box, tuning), confidence });
|
|
587
917
|
}
|
|
918
|
+
const leaderEnds = /* @__PURE__ */ new Map();
|
|
919
|
+
for (const j of joins) {
|
|
920
|
+
if (!j.parts.length) continue;
|
|
921
|
+
j.parts.sort((a, b) => a.x - b.x);
|
|
922
|
+
let text = "";
|
|
923
|
+
for (const p of j.parts) text += (p.dotsBefore ? text ? " \u2026 " : "\u2026" : text ? " " : "") + p.text;
|
|
924
|
+
if (j.trailDots) text += " \u2026";
|
|
925
|
+
const item = { text, ...this.itemBox(rgba, width, j.box, tuning), confidence: Math.min(...j.parts.map((p) => p.confidence)) };
|
|
926
|
+
items.push(item);
|
|
927
|
+
leaderEnds.set(item, { lead: j.parts[0].dotsBefore, trail: j.trailDots });
|
|
928
|
+
}
|
|
929
|
+
if (leaderEnds.size) items = joinLeaderItems(items, leaderEnds);
|
|
588
930
|
if (tuning.postprocess) restoreBulletItems(items);
|
|
589
931
|
items.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
590
932
|
return items;
|
|
591
933
|
}
|
|
934
|
+
/** 결과 좌표 — det 박스 그대로 또는 박스 안 잉크 외곽 (tightBoxes) */
|
|
935
|
+
itemBox(rgba, width, b, tuning) {
|
|
936
|
+
if (!tuning.tightBoxes) return b;
|
|
937
|
+
const gray = grayCrop(rgba, width, b);
|
|
938
|
+
const t = inkBounds(gray, b.w, b.h, inkStats(gray));
|
|
939
|
+
return { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 };
|
|
940
|
+
}
|
|
592
941
|
// ─── det ─────────────────────────────────────────────
|
|
593
942
|
async detect(rgba, width, height, tuning) {
|
|
594
943
|
const ratio = tuning.detLongSide / Math.max(width, height);
|
|
@@ -854,7 +1203,7 @@ function rotateRgba(rgba, width, height, deg) {
|
|
|
854
1203
|
// src/ocr/pdf-ocr.ts
|
|
855
1204
|
var OCR_RENDER_SCALE = 3;
|
|
856
1205
|
var PAGE_TIMEOUT_MS = 12e4;
|
|
857
|
-
async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTables = true) {
|
|
1206
|
+
async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTables = true, vectorOps) {
|
|
858
1207
|
const result = /* @__PURE__ */ new Map();
|
|
859
1208
|
if (targets.size === 0) return result;
|
|
860
1209
|
const pdfiumMod = await tryImport2(
|
|
@@ -880,7 +1229,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
|
|
|
880
1229
|
onProgress?.(++done, targets.size);
|
|
881
1230
|
try {
|
|
882
1231
|
const blocks = await withTimeout(
|
|
883
|
-
ocrOnePage(page, pageNo, mode, engine, warnings, detectTables),
|
|
1232
|
+
ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps?.get(pageNo)),
|
|
884
1233
|
PAGE_TIMEOUT_MS,
|
|
885
1234
|
`OCR \uD398\uC774\uC9C0 ${pageNo} \uD0C0\uC784\uC544\uC6C3 (${PAGE_TIMEOUT_MS / 1e3}\uCD08)`
|
|
886
1235
|
);
|
|
@@ -899,7 +1248,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
|
|
|
899
1248
|
}
|
|
900
1249
|
return result;
|
|
901
1250
|
}
|
|
902
|
-
async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
1251
|
+
async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps) {
|
|
903
1252
|
const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize();
|
|
904
1253
|
const rendered = await page.render({
|
|
905
1254
|
scale: OCR_RENDER_SCALE,
|
|
@@ -908,7 +1257,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
|
908
1257
|
const { data: bgra, width: rw, height: rh } = rendered;
|
|
909
1258
|
const rgba = bgraToRgba(bgra);
|
|
910
1259
|
if (mode === "builtin") {
|
|
911
|
-
const
|
|
1260
|
+
const upright = vectorOps ? rgba : deskewPage(rgba, rw, rh).rgba;
|
|
912
1261
|
const stats = { droppedLowConf: 0 };
|
|
913
1262
|
const items = await engine.recognizePage(upright, rw, rh, stats);
|
|
914
1263
|
if (stats.droppedLowConf > 0) {
|
|
@@ -919,6 +1268,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
|
919
1268
|
});
|
|
920
1269
|
}
|
|
921
1270
|
const scale = rh / pdfH;
|
|
1271
|
+
if (vectorOps) return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, void 0, detectTables, vectorOps);
|
|
922
1272
|
const ruling = detectRulingLines(upright, rw, rh, scale);
|
|
923
1273
|
const extraLines = rulingToPdfLines(ruling, scale, pdfH);
|
|
924
1274
|
return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines, detectTables);
|
|
@@ -937,7 +1287,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
|
937
1287
|
}
|
|
938
1288
|
return [{ type: "paragraph", text: text.trim(), pageNumber: pageNo }];
|
|
939
1289
|
}
|
|
940
|
-
function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true) {
|
|
1290
|
+
function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true, opList = { fnArray: [], argsArray: [] }) {
|
|
941
1291
|
const norm = items.map((it) => {
|
|
942
1292
|
const h = it.h / scale;
|
|
943
1293
|
return {
|
|
@@ -953,7 +1303,7 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
|
|
|
953
1303
|
isHidden: false
|
|
954
1304
|
};
|
|
955
1305
|
});
|
|
956
|
-
return extractPageBlocksWithLines(norm, pageNumber,
|
|
1306
|
+
return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
|
|
957
1307
|
}
|
|
958
1308
|
async function tryImport2(name, loader) {
|
|
959
1309
|
try {
|
|
@@ -999,4 +1349,4 @@ export {
|
|
|
999
1349
|
runPdfOcr,
|
|
1000
1350
|
ocrItemsToBlocks
|
|
1001
1351
|
};
|
|
1002
|
-
//# sourceMappingURL=chunk-
|
|
1352
|
+
//# sourceMappingURL=chunk-LFSQAMSM.js.map
|