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
|
@@ -6,18 +6,28 @@ import {
|
|
|
6
6
|
ensureOcrModels,
|
|
7
7
|
getOcrModelsDir,
|
|
8
8
|
parseCharacterDict
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7SE4EZ45.js";
|
|
10
10
|
import {
|
|
11
11
|
extractPageBlocksWithLines
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YAVHVFRY.js";
|
|
13
13
|
|
|
14
14
|
// src/ocr/ruling-lines.ts
|
|
15
|
-
var INK_LUMA_MAX =
|
|
15
|
+
var INK_LUMA_MAX = 205;
|
|
16
16
|
var MIN_LINE_LENGTH_PT = 20;
|
|
17
17
|
var MAX_LINE_WIDTH_PT = 2.5;
|
|
18
18
|
var GAP_TOL_PX = 2;
|
|
19
19
|
var BAND_OVERLAP_RATIO = 0.5;
|
|
20
20
|
var SURROUND_INK_RATIO = 0.35;
|
|
21
|
+
var DOT_MAX_GAP_PT = 1.3;
|
|
22
|
+
var DOT_MAX_DASH_PT = 4;
|
|
23
|
+
var DOT_MAX_PERIOD_PT = 2.2;
|
|
24
|
+
var DOT_MIN_COUNT = 8;
|
|
25
|
+
var DOT_TOUCH_PT = 1.5;
|
|
26
|
+
var FILL_DELTA = 10;
|
|
27
|
+
var FILL_MIN_LENGTH_PT = 10;
|
|
28
|
+
var FILL_MIN_DEPTH_PT = 4;
|
|
29
|
+
var FILL_FLAT_TOL = 12;
|
|
30
|
+
var FILL_FLAT_RATIO = 0.8;
|
|
21
31
|
function detectRulingLines(rgba, width, height, scale) {
|
|
22
32
|
const minLenPx = Math.round(MIN_LINE_LENGTH_PT * scale);
|
|
23
33
|
const maxThickPx = Math.max(1, Math.floor(MAX_LINE_WIDTH_PT * scale));
|
|
@@ -26,11 +36,164 @@ function detectRulingLines(rgba, width, height, scale) {
|
|
|
26
36
|
const luma = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
|
|
27
37
|
if (luma <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1;
|
|
28
38
|
}
|
|
29
|
-
const
|
|
30
|
-
const
|
|
39
|
+
const solid = (at, line, span) => solidRuns(at, line, span, minLenPx);
|
|
40
|
+
const dot = {
|
|
41
|
+
maxGap: Math.max(2, Math.round(DOT_MAX_GAP_PT * scale)),
|
|
42
|
+
maxDash: Math.max(2, Math.round(DOT_MAX_DASH_PT * scale)),
|
|
43
|
+
maxPeriod: DOT_MAX_PERIOD_PT * scale
|
|
44
|
+
};
|
|
45
|
+
const dotted = (at, line, span) => dottedRuns(at, line, span, minLenPx, dot.maxGap, dot.maxDash, dot.maxPeriod);
|
|
46
|
+
const horizontals = detectBands(ink, width, height, maxThickPx, false, solid);
|
|
47
|
+
const verticals = detectBands(ink, width, height, maxThickPx, true, solid);
|
|
48
|
+
const dotH = detectBands(ink, width, height, maxThickPx, false, dotted);
|
|
49
|
+
const dotV = detectBands(ink, width, height, maxThickPx, true, dotted);
|
|
50
|
+
const tol = Math.max(2, Math.round(DOT_TOUCH_PT * scale));
|
|
51
|
+
const allH = [...horizontals, ...dotH], allV = [...verticals, ...dotV];
|
|
52
|
+
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);
|
|
53
|
+
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);
|
|
54
|
+
horizontals.push(...dotH.filter(touchesV));
|
|
55
|
+
verticals.push(...dotV.filter(touchesH));
|
|
56
|
+
const inkV = [...verticals];
|
|
57
|
+
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));
|
|
58
|
+
const fill = fillEdges(rgba, width, height, scale);
|
|
59
|
+
const fillH = fill.horizontals.filter((a) => ruleH.some((p) => Math.abs(p.x1 - a.x1) <= tol && Math.abs(p.x2 - a.x2) <= tol));
|
|
60
|
+
horizontals.push(...fillH);
|
|
61
|
+
const edgeH = [...ruleH, ...fillH];
|
|
62
|
+
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)));
|
|
31
63
|
return { horizontals, verticals };
|
|
32
64
|
}
|
|
33
|
-
function
|
|
65
|
+
function fillEdges(rgba, width, height, scale) {
|
|
66
|
+
const k = Math.max(1, Math.round(scale));
|
|
67
|
+
const w = Math.floor(width / k), h = Math.floor(height / k), n = w * h;
|
|
68
|
+
const luma = new Uint8Array(n);
|
|
69
|
+
const hist = new Uint32Array(256);
|
|
70
|
+
for (let y = 0; y < h; y++) {
|
|
71
|
+
for (let x = 0; x < w; x++) {
|
|
72
|
+
let mx = 0;
|
|
73
|
+
for (let by = 0; by < k; by++) {
|
|
74
|
+
let i = ((y * k + by) * width + x * k) * 4;
|
|
75
|
+
for (let bx = 0; bx < k; bx++, i += 4) {
|
|
76
|
+
const l = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
|
|
77
|
+
if (l > mx) mx = l;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
luma[y * w + x] = mx;
|
|
81
|
+
hist[mx]++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let bg = 255;
|
|
85
|
+
for (let v = 0; v < 256; v++) if (hist[v] > hist[bg]) bg = v;
|
|
86
|
+
const tint = new Uint8Array(n);
|
|
87
|
+
for (let p = 0; p < n; p++) if (luma[p] < bg - FILL_DELTA) tint[p] = 1;
|
|
88
|
+
const depth = Math.max(2, Math.round(FILL_MIN_DEPTH_PT * scale / k));
|
|
89
|
+
const minLen = Math.round(FILL_MIN_LENGTH_PT * scale / k);
|
|
90
|
+
const solid = (at, line, span) => solidRuns(at, line, span, minLen);
|
|
91
|
+
const run = new Uint16Array(n);
|
|
92
|
+
const mask = new Uint8Array(n);
|
|
93
|
+
const out = { horizontals: [], verticals: [] };
|
|
94
|
+
for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0]]) {
|
|
95
|
+
const step = dy * w + dx;
|
|
96
|
+
const ys = dy > 0 ? [h - 1, -1, -1] : [0, h, 1];
|
|
97
|
+
const xs = dx > 0 ? [w - 1, -1, -1] : [0, w, 1];
|
|
98
|
+
for (let y = ys[0]; y !== ys[1]; y += ys[2]) {
|
|
99
|
+
for (let x = xs[0]; x !== xs[1]; x += xs[2]) {
|
|
100
|
+
const p = y * w + x;
|
|
101
|
+
const nx = x + dx, ny = y + dy;
|
|
102
|
+
run[p] = tint[p] ? nx >= 0 && nx < w && ny >= 0 && ny < h ? Math.min(65535, run[p + step] + 1) : 1 : 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
mask.fill(0);
|
|
106
|
+
for (let y = 0; y < h; y++) {
|
|
107
|
+
for (let x = 0; x < w; x++) {
|
|
108
|
+
const p = y * w + x;
|
|
109
|
+
if (run[p] < depth) continue;
|
|
110
|
+
const ox = x - dx, oy = y - dy;
|
|
111
|
+
if (ox < 0 || oy < 0 || ox >= w || oy >= h || !tint[p - step]) mask[p] = 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const vertical = dx !== 0;
|
|
115
|
+
const flat = (s) => {
|
|
116
|
+
const d = depth >> 1;
|
|
117
|
+
const vals = [];
|
|
118
|
+
if (vertical) {
|
|
119
|
+
const x = Math.round(s.x1) + dx * d;
|
|
120
|
+
for (let y = Math.round(s.y1); y <= s.y2; y++) if (x >= 0 && x < w) vals.push(luma[y * w + x]);
|
|
121
|
+
} else {
|
|
122
|
+
const y = Math.round(s.y1) + dy * d;
|
|
123
|
+
for (let x = Math.round(s.x1); x <= s.x2; x++) if (y >= 0 && y < h) vals.push(luma[y * w + x]);
|
|
124
|
+
}
|
|
125
|
+
if (!vals.length) return false;
|
|
126
|
+
const med = [...vals].sort((a, b) => a - b)[vals.length >> 1];
|
|
127
|
+
return vals.filter((v) => Math.abs(v - med) <= FILL_FLAT_TOL).length >= vals.length * FILL_FLAT_RATIO;
|
|
128
|
+
};
|
|
129
|
+
for (const s of detectBands(mask, w, h, 1, vertical, solid)) {
|
|
130
|
+
if (!flat(s)) continue;
|
|
131
|
+
const c = (v) => v * k + (k - 1) / 2;
|
|
132
|
+
(vertical ? out.verticals : out.horizontals).push({ x1: c(s.x1), y1: c(s.y1), x2: c(s.x2), y2: c(s.y2), thicknessPx: 1 });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
function solidRuns(at, line, span, minLenPx) {
|
|
138
|
+
const runs = [];
|
|
139
|
+
let runStart = -1;
|
|
140
|
+
let gap = 0;
|
|
141
|
+
for (let pos = 0; pos <= span; pos++) {
|
|
142
|
+
const on = pos < span && at(line, pos) === 1;
|
|
143
|
+
if (on) {
|
|
144
|
+
if (runStart < 0) runStart = pos;
|
|
145
|
+
gap = 0;
|
|
146
|
+
} else if (runStart >= 0) {
|
|
147
|
+
if (++gap > GAP_TOL_PX || pos >= span) {
|
|
148
|
+
const hi = pos - gap;
|
|
149
|
+
if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
|
|
150
|
+
runStart = -1;
|
|
151
|
+
gap = 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return runs;
|
|
156
|
+
}
|
|
157
|
+
function dottedRuns(at, line, span, minLenPx, maxGap, maxDash, maxPeriod) {
|
|
158
|
+
const runs = [];
|
|
159
|
+
const chain = [];
|
|
160
|
+
const median = (xs) => xs.sort((a, b) => a - b)[xs.length >> 1];
|
|
161
|
+
const flush = () => {
|
|
162
|
+
const cnt = chain.length >> 1;
|
|
163
|
+
if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {
|
|
164
|
+
const periods = [], dashes = [], gaps = [];
|
|
165
|
+
for (let k = 0; k < cnt; k++) {
|
|
166
|
+
dashes.push(chain[2 * k + 1] - chain[2 * k] + 1);
|
|
167
|
+
if (k) {
|
|
168
|
+
periods.push(chain[2 * k] - chain[2 * k - 2]);
|
|
169
|
+
gaps.push(chain[2 * k] - chain[2 * k - 1] - 1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (median(periods) <= maxPeriod || median(dashes) >= 1.5 * median(gaps)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] });
|
|
173
|
+
}
|
|
174
|
+
chain.length = 0;
|
|
175
|
+
};
|
|
176
|
+
let s = -1;
|
|
177
|
+
for (let pos = 0; pos <= span; pos++) {
|
|
178
|
+
const on = pos < span && at(line, pos) === 1;
|
|
179
|
+
if (on) {
|
|
180
|
+
if (s < 0) s = pos;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (s < 0) continue;
|
|
184
|
+
const lo = s, hi = pos - 1;
|
|
185
|
+
s = -1;
|
|
186
|
+
if (hi - lo + 1 > maxDash) {
|
|
187
|
+
flush();
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (chain.length && lo - chain[chain.length - 1] - 1 > maxGap) flush();
|
|
191
|
+
chain.push(lo, hi);
|
|
192
|
+
}
|
|
193
|
+
flush();
|
|
194
|
+
return runs;
|
|
195
|
+
}
|
|
196
|
+
function detectBands(ink, width, height, maxThickPx, transpose, readRuns) {
|
|
34
197
|
const lines = transpose ? width : height;
|
|
35
198
|
const span = transpose ? height : width;
|
|
36
199
|
const at = transpose ? (line, pos) => ink[pos * width + line] : (line, pos) => ink[line * width + pos];
|
|
@@ -55,23 +218,7 @@ function detectBands(ink, width, height, minLenPx, maxThickPx, transpose) {
|
|
|
55
218
|
);
|
|
56
219
|
};
|
|
57
220
|
for (let line = 0; line < lines; line++) {
|
|
58
|
-
const runs =
|
|
59
|
-
let runStart = -1;
|
|
60
|
-
let gap = 0;
|
|
61
|
-
for (let pos = 0; pos <= span; pos++) {
|
|
62
|
-
const on = pos < span && at(line, pos) === 1;
|
|
63
|
-
if (on) {
|
|
64
|
-
if (runStart < 0) runStart = pos;
|
|
65
|
-
gap = 0;
|
|
66
|
-
} else if (runStart >= 0) {
|
|
67
|
-
if (++gap > GAP_TOL_PX || pos >= span) {
|
|
68
|
-
const hi = pos - gap;
|
|
69
|
-
if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
|
|
70
|
-
runStart = -1;
|
|
71
|
-
gap = 0;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
221
|
+
const runs = readRuns(at, line, span);
|
|
75
222
|
const next = [];
|
|
76
223
|
const used = /* @__PURE__ */ new Set();
|
|
77
224
|
for (const band of open) {
|
|
@@ -257,6 +404,113 @@ function splitRowBands(gray, w, h, ink, minBandRatio) {
|
|
|
257
404
|
return { y0, y1, x0, x1 };
|
|
258
405
|
});
|
|
259
406
|
}
|
|
407
|
+
function components(gray, w, h, ink) {
|
|
408
|
+
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
409
|
+
const comps = [];
|
|
410
|
+
const label = new Int32Array(w * h);
|
|
411
|
+
const stack = [];
|
|
412
|
+
for (let p0 = 0; p0 < w * h; p0++) {
|
|
413
|
+
if (label[p0] || !isInk(gray[p0])) continue;
|
|
414
|
+
const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 };
|
|
415
|
+
label[p0] = c.id;
|
|
416
|
+
stack.push(p0);
|
|
417
|
+
while (stack.length) {
|
|
418
|
+
const p = stack.pop();
|
|
419
|
+
const x = p % w, y = p / w | 0;
|
|
420
|
+
if (x < c.x0) c.x0 = x;
|
|
421
|
+
if (x + 1 > c.x1) c.x1 = x + 1;
|
|
422
|
+
if (y < c.y0) c.y0 = y;
|
|
423
|
+
if (y + 1 > c.y1) c.y1 = y + 1;
|
|
424
|
+
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
|
425
|
+
const nx = x + dx, ny = y + dy;
|
|
426
|
+
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
|
|
427
|
+
const q = ny * w + nx;
|
|
428
|
+
if (!label[q] && isInk(gray[q])) {
|
|
429
|
+
label[q] = c.id;
|
|
430
|
+
stack.push(q);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c);
|
|
435
|
+
}
|
|
436
|
+
return { comps, label };
|
|
437
|
+
}
|
|
438
|
+
function leadingTriangle(gray, w, h, ink) {
|
|
439
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
440
|
+
let charH = 0;
|
|
441
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
442
|
+
const first = comps.filter((c) => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0];
|
|
443
|
+
if (!first || charH < 8) return null;
|
|
444
|
+
const cw = first.x1 - first.x0, ch = first.y1 - first.y0;
|
|
445
|
+
if (cw < ch * 0.8 || cw > ch * 1.8) return null;
|
|
446
|
+
const rowSpan = (y) => {
|
|
447
|
+
let n = 0, lo = -1, hi = -1, runs = 0, prev = false;
|
|
448
|
+
for (let x = first.x0; x < first.x1; x++) {
|
|
449
|
+
const on = label[y * w + x] === first.id;
|
|
450
|
+
if (on) {
|
|
451
|
+
n++;
|
|
452
|
+
if (lo < 0) lo = x - first.x0;
|
|
453
|
+
hi = x - first.x0;
|
|
454
|
+
if (!prev) runs++;
|
|
455
|
+
}
|
|
456
|
+
prev = on;
|
|
457
|
+
}
|
|
458
|
+
return { n, lo, hi, runs };
|
|
459
|
+
};
|
|
460
|
+
const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n);
|
|
461
|
+
if (base < cw * 0.7) return null;
|
|
462
|
+
for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {
|
|
463
|
+
const r = rowSpan(y);
|
|
464
|
+
if (r.n === 0) continue;
|
|
465
|
+
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;
|
|
466
|
+
}
|
|
467
|
+
const mid = rowSpan(first.y0 + Math.round(ch * 0.6));
|
|
468
|
+
if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null;
|
|
469
|
+
return mid.runs >= 2 ? "\u25B3" : "\u25B2";
|
|
470
|
+
}
|
|
471
|
+
function leaderRuns(gray, w, h, ink, minDots) {
|
|
472
|
+
const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0);
|
|
473
|
+
const blobs = [];
|
|
474
|
+
for (const c of comps) {
|
|
475
|
+
const last = blobs[blobs.length - 1];
|
|
476
|
+
if (last && c.x0 < last.x1) {
|
|
477
|
+
last.x1 = Math.max(last.x1, c.x1);
|
|
478
|
+
last.y0 = Math.min(last.y0, c.y0);
|
|
479
|
+
last.y1 = Math.max(last.y1, c.y1);
|
|
480
|
+
} else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 });
|
|
481
|
+
}
|
|
482
|
+
let charH = 0;
|
|
483
|
+
for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0);
|
|
484
|
+
if (charH < 8) return [];
|
|
485
|
+
const small = (c) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3;
|
|
486
|
+
let bandTop = h, bandBot = 0;
|
|
487
|
+
for (const c of comps) if (!small(c)) {
|
|
488
|
+
bandTop = Math.min(bandTop, c.y0);
|
|
489
|
+
bandBot = Math.max(bandBot, c.y1);
|
|
490
|
+
}
|
|
491
|
+
const cy = (c) => (c.y0 + c.y1) / 2;
|
|
492
|
+
const isDot = (b) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot;
|
|
493
|
+
const runs = [];
|
|
494
|
+
for (let i = 0; i < blobs.length; ) {
|
|
495
|
+
if (!isDot(blobs[i])) {
|
|
496
|
+
i++;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
let j = i;
|
|
500
|
+
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++;
|
|
501
|
+
const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i]);
|
|
502
|
+
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));
|
|
503
|
+
const run = blobs.slice(i, j + 1).filter((b) => b.x0 > 0 && b.x1 < w);
|
|
504
|
+
const spread = (xs) => {
|
|
505
|
+
const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : void 0);
|
|
506
|
+
return v[v.length - 1] / Math.max(1, v[0]);
|
|
507
|
+
};
|
|
508
|
+
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;
|
|
509
|
+
if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1]);
|
|
510
|
+
i = j + 1;
|
|
511
|
+
}
|
|
512
|
+
return runs;
|
|
513
|
+
}
|
|
260
514
|
function inkBounds(gray, w, h, ink) {
|
|
261
515
|
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
262
516
|
const colInk = new Uint32Array(w);
|
|
@@ -294,6 +548,7 @@ function restoreSymbols(text) {
|
|
|
294
548
|
s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
|
|
295
549
|
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);
|
|
296
550
|
s = s.replace(/[\u2206\u0394]/g, "\u25B3");
|
|
551
|
+
s = s.replace(/\(cid:\d*\)/g, "");
|
|
297
552
|
return smartQuotes(joinDigitGroups(s));
|
|
298
553
|
}
|
|
299
554
|
function joinDigitGroups(s) {
|
|
@@ -329,6 +584,53 @@ function smartQuotes(s) {
|
|
|
329
584
|
function isDotFragment(text) {
|
|
330
585
|
return /^[\s.\u00b7\u2024\u2025\u2026\u2027\u2219\u22c5\u318d]+$/.test(text);
|
|
331
586
|
}
|
|
587
|
+
function joinLeaderItems(items, ends) {
|
|
588
|
+
const gone = /* @__PURE__ */ new Set();
|
|
589
|
+
const sameLine = (a, b) => Math.abs(a.y + a.h / 2 - (b.y + b.h / 2)) <= Math.max(a.h, b.h) / 2;
|
|
590
|
+
const merge = (left, right) => {
|
|
591
|
+
const x2 = Math.max(left.x + left.w, right.x + right.w), y2 = Math.max(left.y + left.h, right.y + right.h);
|
|
592
|
+
left.text = left.text.replace(/\s*\u2026$/, "") + " \u2026 " + right.text.replace(/^\u2026\s*/, "");
|
|
593
|
+
left.x = Math.min(left.x, right.x);
|
|
594
|
+
left.y = Math.min(left.y, right.y);
|
|
595
|
+
left.w = x2 - left.x;
|
|
596
|
+
left.h = y2 - left.y;
|
|
597
|
+
left.confidence = Math.min(left.confidence, right.confidence);
|
|
598
|
+
gone.add(right);
|
|
599
|
+
};
|
|
600
|
+
const neighbor = (it, toLeft) => {
|
|
601
|
+
let best = null;
|
|
602
|
+
for (const o of items) {
|
|
603
|
+
if (o === it || gone.has(o) || !sameLine(o, it)) continue;
|
|
604
|
+
if (toLeft ? o.x + o.w > it.x + 2 : o.x < it.x + it.w - 2) continue;
|
|
605
|
+
if (!best || (toLeft ? o.x + o.w > best.x + best.w : o.x < best.x)) best = o;
|
|
606
|
+
}
|
|
607
|
+
return best;
|
|
608
|
+
};
|
|
609
|
+
for (const it of [...items].sort((a, b) => b.x - a.x)) {
|
|
610
|
+
if (gone.has(it) || !ends.get(it)?.lead) continue;
|
|
611
|
+
const left = neighbor(it, true);
|
|
612
|
+
if (left) merge(left, it);
|
|
613
|
+
}
|
|
614
|
+
for (const it of [...items].sort((a, b) => a.x - b.x)) {
|
|
615
|
+
if (gone.has(it) || !ends.get(it)?.trail) continue;
|
|
616
|
+
const right = neighbor(it, false);
|
|
617
|
+
if (right) merge(it, right);
|
|
618
|
+
}
|
|
619
|
+
const toc = items.filter((it) => !gone.has(it) && /\s\u2026\s\d{1,4}$/.test(it.text));
|
|
620
|
+
if (toc.length >= 2) {
|
|
621
|
+
for (const it of items) {
|
|
622
|
+
if (gone.has(it) || toc.includes(it) || !/^[\s.:\u00b7\u2026]*\d{1,4}$/.test(it.text)) continue;
|
|
623
|
+
const x2 = it.x + it.w;
|
|
624
|
+
if (toc.filter((t) => Math.abs(t.x + t.w - x2) <= Math.max(t.h, it.h)).length < 2) continue;
|
|
625
|
+
const left = neighbor(it, true);
|
|
626
|
+
if (!left || toc.includes(left)) continue;
|
|
627
|
+
left.text = left.text.replace(/[\s.:\u00b7\u2026]+$/, "");
|
|
628
|
+
it.text = it.text.replace(/^[\s.:\u00b7\u2026]+/, "");
|
|
629
|
+
merge(left, it);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return items.filter((it) => !gone.has(it));
|
|
633
|
+
}
|
|
332
634
|
function restoreBulletItems(items) {
|
|
333
635
|
for (const it of items) {
|
|
334
636
|
if (it.text !== "O" && it.text !== "o") continue;
|
|
@@ -419,7 +721,8 @@ var DEFAULT_OCR_TUNING = Object.freeze({
|
|
|
419
721
|
splitTall: true,
|
|
420
722
|
minInkContrast: 35,
|
|
421
723
|
postprocess: true,
|
|
422
|
-
tightBoxes: true
|
|
724
|
+
tightBoxes: true,
|
|
725
|
+
splitLeaders: true
|
|
423
726
|
});
|
|
424
727
|
var DET_MIN_SIZE = 3;
|
|
425
728
|
var DET_MAX_BOXES = 1e3;
|
|
@@ -427,6 +730,9 @@ var REC_MIN_WIDTH = 320;
|
|
|
427
730
|
var REC_BATCH_MAX_PIXELS = 48 * 16e3;
|
|
428
731
|
var TALL_RATIO = 1.5;
|
|
429
732
|
var ROTATE_RATIO = 3;
|
|
733
|
+
var LEADER_MIN_DOTS = 4;
|
|
734
|
+
var LEADER_CONTEXT = 0.5;
|
|
735
|
+
var MIN_INK_RATIO = 0.01;
|
|
430
736
|
var DET_MEAN = [0.485, 0.456, 0.406];
|
|
431
737
|
var DET_STD = [0.229, 0.224, 0.225];
|
|
432
738
|
var OcrEngine = class _OcrEngine {
|
|
@@ -489,11 +795,13 @@ var OcrEngine = class _OcrEngine {
|
|
|
489
795
|
if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return [];
|
|
490
796
|
const boxes = await this.detect(rgba, width, height, tuning);
|
|
491
797
|
const jobs = [];
|
|
798
|
+
const joins = [];
|
|
492
799
|
let group = 0;
|
|
493
800
|
for (const b of boxes) {
|
|
494
801
|
const gray = grayCrop(rgba, width, b);
|
|
495
802
|
const ink = inkStats(gray);
|
|
496
803
|
if (tuning.minInkContrast > 0 && ink.contrast < tuning.minInkContrast) continue;
|
|
804
|
+
if (tuning.minInkContrast > 0 && ink.inkRatio < MIN_INK_RATIO) continue;
|
|
497
805
|
if (tuning.splitTall && b.h >= b.w * TALL_RATIO) {
|
|
498
806
|
const bands = splitRowBands(gray, b.w, b.h, ink, 0.45);
|
|
499
807
|
if (bands.length >= 2) {
|
|
@@ -506,6 +814,24 @@ var OcrEngine = class _OcrEngine {
|
|
|
506
814
|
continue;
|
|
507
815
|
}
|
|
508
816
|
}
|
|
817
|
+
const leaders = tuning.splitLeaders ? leaderRuns(gray, b.w, b.h, ink, LEADER_MIN_DOTS) : [];
|
|
818
|
+
if (leaders.length) {
|
|
819
|
+
const join2 = joins.length;
|
|
820
|
+
joins.push({ box: b, parts: [], trailDots: false });
|
|
821
|
+
let x0 = 0, dots = false;
|
|
822
|
+
for (const [a, c] of [...leaders, [b.w, b.w]]) {
|
|
823
|
+
const bare = { x: b.x + x0, y: b.y, w: a - x0, h: b.h };
|
|
824
|
+
const end = c > a ? Math.min(c, a + Math.round(b.h * LEADER_CONTEXT)) : a;
|
|
825
|
+
x0 = c;
|
|
826
|
+
if (bare.w >= DET_MIN_SIZE && inkStats(grayCrop(rgba, width, bare)).contrast >= Math.max(1, tuning.minInkContrast)) {
|
|
827
|
+
jobs.push({ box: { ...bare, w: end - (bare.x - b.x) }, rot: 0, group: group++, join: join2, dotsBefore: dots, trimDots: end > a });
|
|
828
|
+
dots = false;
|
|
829
|
+
}
|
|
830
|
+
if (c > a) dots = true;
|
|
831
|
+
}
|
|
832
|
+
joins[join2].trailDots = dots;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
509
835
|
jobs.push({ box: b, rot: 0, group: group++ });
|
|
510
836
|
}
|
|
511
837
|
const results = await this.recognizeJobs(rgba, width, jobs, tuning.recBatch);
|
|
@@ -516,27 +842,50 @@ var OcrEngine = class _OcrEngine {
|
|
|
516
842
|
const cur = best.get(job.group);
|
|
517
843
|
if (!cur || r.confidence > cur.confidence) best.set(job.group, { job, ...r });
|
|
518
844
|
});
|
|
519
|
-
|
|
845
|
+
let items = [];
|
|
520
846
|
for (const { job, text: raw, confidence } of best.values()) {
|
|
521
|
-
|
|
847
|
+
let text = tuning.postprocess ? restoreSymbols(raw.trim()) : raw;
|
|
522
848
|
if (!text.trim()) continue;
|
|
849
|
+
if (tuning.postprocess && /^\d/.test(text) && job.rot === 0) {
|
|
850
|
+
const g = grayCrop(rgba, width, job.box);
|
|
851
|
+
const tri = leadingTriangle(g, job.box.w, job.box.h, inkStats(g));
|
|
852
|
+
if (tri) text = tri + text;
|
|
853
|
+
}
|
|
523
854
|
if (tuning.postprocess && isDotFragment(text)) continue;
|
|
524
855
|
if (confidence < tuning.textScore) {
|
|
525
856
|
if (stats) stats.droppedLowConf++;
|
|
526
857
|
continue;
|
|
527
858
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
b = { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 };
|
|
859
|
+
if (job.join !== void 0) {
|
|
860
|
+
if (job.trimDots) text = text.replace(/[\s.:\u00b7\u2022\u2024\u2025\u2026\u2219\u22c5\u318d]+$/u, "");
|
|
861
|
+
if (text) joins[job.join].parts.push({ x: job.box.x, text, dotsBefore: job.dotsBefore === true, confidence });
|
|
862
|
+
continue;
|
|
533
863
|
}
|
|
534
|
-
items.push({ text,
|
|
864
|
+
items.push({ text, ...this.itemBox(rgba, width, job.box, tuning), confidence });
|
|
535
865
|
}
|
|
866
|
+
const leaderEnds = /* @__PURE__ */ new Map();
|
|
867
|
+
for (const j of joins) {
|
|
868
|
+
if (!j.parts.length) continue;
|
|
869
|
+
j.parts.sort((a, b) => a.x - b.x);
|
|
870
|
+
let text = "";
|
|
871
|
+
for (const p of j.parts) text += (p.dotsBefore ? text ? " \u2026 " : "\u2026" : text ? " " : "") + p.text;
|
|
872
|
+
if (j.trailDots) text += " \u2026";
|
|
873
|
+
const item = { text, ...this.itemBox(rgba, width, j.box, tuning), confidence: Math.min(...j.parts.map((p) => p.confidence)) };
|
|
874
|
+
items.push(item);
|
|
875
|
+
leaderEnds.set(item, { lead: j.parts[0].dotsBefore, trail: j.trailDots });
|
|
876
|
+
}
|
|
877
|
+
if (leaderEnds.size) items = joinLeaderItems(items, leaderEnds);
|
|
536
878
|
if (tuning.postprocess) restoreBulletItems(items);
|
|
537
879
|
items.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
538
880
|
return items;
|
|
539
881
|
}
|
|
882
|
+
/** 결과 좌표 — det 박스 그대로 또는 박스 안 잉크 외곽 (tightBoxes) */
|
|
883
|
+
itemBox(rgba, width, b, tuning) {
|
|
884
|
+
if (!tuning.tightBoxes) return b;
|
|
885
|
+
const gray = grayCrop(rgba, width, b);
|
|
886
|
+
const t = inkBounds(gray, b.w, b.h, inkStats(gray));
|
|
887
|
+
return { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 };
|
|
888
|
+
}
|
|
540
889
|
// ─── det ─────────────────────────────────────────────
|
|
541
890
|
async detect(rgba, width, height, tuning) {
|
|
542
891
|
const ratio = tuning.detLongSide / Math.max(width, height);
|
|
@@ -802,7 +1151,7 @@ function rotateRgba(rgba, width, height, deg) {
|
|
|
802
1151
|
// src/ocr/pdf-ocr.ts
|
|
803
1152
|
var OCR_RENDER_SCALE = 3;
|
|
804
1153
|
var PAGE_TIMEOUT_MS = 12e4;
|
|
805
|
-
async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTables = true) {
|
|
1154
|
+
async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTables = true, vectorOps) {
|
|
806
1155
|
const result = /* @__PURE__ */ new Map();
|
|
807
1156
|
if (targets.size === 0) return result;
|
|
808
1157
|
const pdfiumMod = await tryImport2(
|
|
@@ -828,7 +1177,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
|
|
|
828
1177
|
onProgress?.(++done, targets.size);
|
|
829
1178
|
try {
|
|
830
1179
|
const blocks = await withTimeout(
|
|
831
|
-
ocrOnePage(page, pageNo, mode, engine, warnings, detectTables),
|
|
1180
|
+
ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps?.get(pageNo)),
|
|
832
1181
|
PAGE_TIMEOUT_MS,
|
|
833
1182
|
`OCR \uD398\uC774\uC9C0 ${pageNo} \uD0C0\uC784\uC544\uC6C3 (${PAGE_TIMEOUT_MS / 1e3}\uCD08)`
|
|
834
1183
|
);
|
|
@@ -847,7 +1196,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
|
|
|
847
1196
|
}
|
|
848
1197
|
return result;
|
|
849
1198
|
}
|
|
850
|
-
async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
1199
|
+
async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps) {
|
|
851
1200
|
const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize();
|
|
852
1201
|
const rendered = await page.render({
|
|
853
1202
|
scale: OCR_RENDER_SCALE,
|
|
@@ -856,7 +1205,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
|
856
1205
|
const { data: bgra, width: rw, height: rh } = rendered;
|
|
857
1206
|
const rgba = bgraToRgba(bgra);
|
|
858
1207
|
if (mode === "builtin") {
|
|
859
|
-
const
|
|
1208
|
+
const upright = vectorOps ? rgba : deskewPage(rgba, rw, rh).rgba;
|
|
860
1209
|
const stats = { droppedLowConf: 0 };
|
|
861
1210
|
const items = await engine.recognizePage(upright, rw, rh, stats);
|
|
862
1211
|
if (stats.droppedLowConf > 0) {
|
|
@@ -867,6 +1216,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
|
867
1216
|
});
|
|
868
1217
|
}
|
|
869
1218
|
const scale = rh / pdfH;
|
|
1219
|
+
if (vectorOps) return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, void 0, detectTables, vectorOps);
|
|
870
1220
|
const ruling = detectRulingLines(upright, rw, rh, scale);
|
|
871
1221
|
const extraLines = rulingToPdfLines(ruling, scale, pdfH);
|
|
872
1222
|
return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines, detectTables);
|
|
@@ -885,7 +1235,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
|
|
|
885
1235
|
}
|
|
886
1236
|
return [{ type: "paragraph", text: text.trim(), pageNumber: pageNo }];
|
|
887
1237
|
}
|
|
888
|
-
function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true) {
|
|
1238
|
+
function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true, opList = { fnArray: [], argsArray: [] }) {
|
|
889
1239
|
const norm = items.map((it) => {
|
|
890
1240
|
const h = it.h / scale;
|
|
891
1241
|
return {
|
|
@@ -901,7 +1251,7 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
|
|
|
901
1251
|
isHidden: false
|
|
902
1252
|
};
|
|
903
1253
|
});
|
|
904
|
-
return extractPageBlocksWithLines(norm, pageNumber,
|
|
1254
|
+
return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
|
|
905
1255
|
}
|
|
906
1256
|
async function tryImport2(name, loader) {
|
|
907
1257
|
try {
|
|
@@ -946,4 +1296,4 @@ export {
|
|
|
946
1296
|
runPdfOcr,
|
|
947
1297
|
ocrItemsToBlocks
|
|
948
1298
|
};
|
|
949
|
-
//# sourceMappingURL=chunk-
|
|
1299
|
+
//# sourceMappingURL=chunk-MG6DJEX4.js.map
|