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
|
@@ -4,20 +4,43 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_OCR_MODELS,
|
|
6
6
|
UNRULED_REGION_TABLES,
|
|
7
|
+
bracketFeatures,
|
|
8
|
+
bracketShape,
|
|
9
|
+
circledAt,
|
|
10
|
+
copyOcrTextGaps,
|
|
11
|
+
dashRatio,
|
|
12
|
+
discAt,
|
|
13
|
+
edgeTrim,
|
|
7
14
|
ensureOcrModelsFor,
|
|
8
15
|
extractPageBlocksWithLines,
|
|
16
|
+
findOcrTextGaps,
|
|
17
|
+
gapGlyphs,
|
|
18
|
+
grayCrop,
|
|
19
|
+
inkBounds,
|
|
20
|
+
inkStats,
|
|
21
|
+
leaderRuns,
|
|
22
|
+
leadingBullet,
|
|
23
|
+
leadingTriangle,
|
|
9
24
|
ocrSelectionKey,
|
|
10
25
|
ocrSourcePath,
|
|
11
26
|
parseCharacterDict,
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
quoteHead,
|
|
28
|
+
rememberOcrTextGaps,
|
|
29
|
+
resolveOcrModels,
|
|
30
|
+
ringBullet,
|
|
31
|
+
romanStems,
|
|
32
|
+
serifOne,
|
|
33
|
+
splitRowBands,
|
|
34
|
+
starRun,
|
|
35
|
+
tallInkCount
|
|
36
|
+
} from "./chunk-O4RQCDN2.js";
|
|
14
37
|
import {
|
|
15
38
|
validateOcrOptions
|
|
16
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-BNIXSB6D.js";
|
|
17
40
|
import {
|
|
18
41
|
KordocError,
|
|
19
42
|
OPTIONAL_DEP_INSTALL_HINT
|
|
20
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-JKZY6FX5.js";
|
|
21
44
|
|
|
22
45
|
// src/ocr/ruling-lines.ts
|
|
23
46
|
var INK_LUMA_MAX = 205;
|
|
@@ -228,15 +251,15 @@ function dottedRuns(mask, base, step, span, minLenPx, maxGap, maxDash, maxPeriod
|
|
|
228
251
|
const flush = () => {
|
|
229
252
|
const cnt = chain.length >> 1;
|
|
230
253
|
if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {
|
|
231
|
-
const periods = [],
|
|
254
|
+
const periods = [], dashes2 = [], gaps2 = [];
|
|
232
255
|
for (let k = 0; k < cnt; k++) {
|
|
233
|
-
|
|
256
|
+
dashes2.push(chain[2 * k + 1] - chain[2 * k] + 1);
|
|
234
257
|
if (k) {
|
|
235
258
|
periods.push(chain[2 * k] - chain[2 * k - 2]);
|
|
236
259
|
gaps2.push(chain[2 * k] - chain[2 * k - 1] - 1);
|
|
237
260
|
}
|
|
238
261
|
}
|
|
239
|
-
if (median2(periods) <= maxPeriod || median2(
|
|
262
|
+
if (median2(periods) <= maxPeriod || median2(dashes2) >= 1.5 * median2(gaps2)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] });
|
|
240
263
|
}
|
|
241
264
|
chain.length = 0;
|
|
242
265
|
};
|
|
@@ -346,710 +369,8 @@ import { createHash } from "crypto";
|
|
|
346
369
|
import { readFile, stat } from "fs/promises";
|
|
347
370
|
import { basename } from "path";
|
|
348
371
|
|
|
349
|
-
// src/ocr/line-split.ts
|
|
350
|
-
function grayCrop(rgba, pageW, box) {
|
|
351
|
-
const out = new Uint8Array(box.w * box.h);
|
|
352
|
-
for (let y = 0; y < box.h; y++) {
|
|
353
|
-
let si = ((box.y + y) * pageW + box.x) * 4;
|
|
354
|
-
let di = y * box.w;
|
|
355
|
-
for (let x = 0; x < box.w; x++, si += 4, di++) {
|
|
356
|
-
out[di] = rgba[si] * 77 + rgba[si + 1] * 150 + rgba[si + 2] * 29 >> 8;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return out;
|
|
360
|
-
}
|
|
361
|
-
function inkStats(gray) {
|
|
362
|
-
const hist = new Uint32Array(256);
|
|
363
|
-
for (let i = 0; i < gray.length; i++) hist[gray[i]]++;
|
|
364
|
-
const total = gray.length;
|
|
365
|
-
let sumAll = 0;
|
|
366
|
-
for (let v = 0; v < 256; v++) sumAll += v * hist[v];
|
|
367
|
-
let wB = 0, sumB = 0, best = -1, thr = 127;
|
|
368
|
-
for (let t = 0; t < 256; t++) {
|
|
369
|
-
wB += hist[t];
|
|
370
|
-
if (wB === 0) continue;
|
|
371
|
-
const wF = total - wB;
|
|
372
|
-
if (wF === 0) break;
|
|
373
|
-
sumB += t * hist[t];
|
|
374
|
-
const mB = sumB / wB;
|
|
375
|
-
const mF = (sumAll - sumB) / wF;
|
|
376
|
-
const between = wB * wF * (mB - mF) * (mB - mF);
|
|
377
|
-
if (between > best) {
|
|
378
|
-
best = between;
|
|
379
|
-
thr = t;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
let nDark = 0, sDark = 0;
|
|
383
|
-
for (let v = 0; v <= thr; v++) {
|
|
384
|
-
nDark += hist[v];
|
|
385
|
-
sDark += v * hist[v];
|
|
386
|
-
}
|
|
387
|
-
const nLight = total - nDark;
|
|
388
|
-
const sLight = sumAll - sDark;
|
|
389
|
-
if (nDark === 0 || nLight === 0) return { threshold: thr, darkInk: true, contrast: 0, inkRatio: 0 };
|
|
390
|
-
const darkInk = nDark <= nLight;
|
|
391
|
-
return {
|
|
392
|
-
threshold: thr,
|
|
393
|
-
darkInk,
|
|
394
|
-
contrast: sLight / nLight - sDark / nDark,
|
|
395
|
-
inkRatio: (darkInk ? nDark : nLight) / total
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
function splitRowBands(gray, w, h, ink, minBandRatio) {
|
|
399
|
-
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
400
|
-
const colInk = new Uint32Array(w);
|
|
401
|
-
for (let y = 0; y < h; y++) {
|
|
402
|
-
const off = y * w;
|
|
403
|
-
for (let x = 0; x < w; x++) if (isInk(gray[off + x])) colInk[x]++;
|
|
404
|
-
}
|
|
405
|
-
const ruleCol = new Uint8Array(w);
|
|
406
|
-
let liveW = 0;
|
|
407
|
-
for (let x = 0; x < w; x++) {
|
|
408
|
-
if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
|
|
409
|
-
else liveW++;
|
|
410
|
-
}
|
|
411
|
-
const rowInk = new Uint32Array(h);
|
|
412
|
-
for (let y = 0; y < h; y++) {
|
|
413
|
-
let n = 0;
|
|
414
|
-
const off = y * w;
|
|
415
|
-
for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) n++;
|
|
416
|
-
rowInk[y] = n >= liveW * 0.85 ? 0 : n;
|
|
417
|
-
}
|
|
418
|
-
const blank = Math.max(1, Math.floor(w * 0.02));
|
|
419
|
-
let bands = [];
|
|
420
|
-
let start = -1;
|
|
421
|
-
for (let y = 0; y <= h; y++) {
|
|
422
|
-
const on = y < h && rowInk[y] > blank;
|
|
423
|
-
if (on && start < 0) start = y;
|
|
424
|
-
else if (!on && start >= 0) {
|
|
425
|
-
bands.push([start, y]);
|
|
426
|
-
start = -1;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
const inkCols = (y0, y1) => {
|
|
430
|
-
let x0 = w, x1 = -1;
|
|
431
|
-
for (let y = y0; y < y1; y++) {
|
|
432
|
-
const off = y * w;
|
|
433
|
-
for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[off + x])) {
|
|
434
|
-
if (x < x0) x0 = x;
|
|
435
|
-
if (x > x1) x1 = x;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return x1 >= x0 ? [x0, x1 + 1] : [0, w];
|
|
439
|
-
};
|
|
440
|
-
if (bands.length <= 1) return [{ y0: bands[0]?.[0] ?? 0, y1: bands[0]?.[1] ?? h, x0: 0, x1: w }];
|
|
441
|
-
const widths = bands.map(([y0, y1]) => {
|
|
442
|
-
const [a, b] = inkCols(y0, y1);
|
|
443
|
-
return b - a;
|
|
444
|
-
}).sort((a, b) => a - b);
|
|
445
|
-
const charSize = widths[widths.length >> 1];
|
|
446
|
-
const minBand = Math.max(3, minBandRatio * charSize);
|
|
447
|
-
for (let i = 0; i + 1 < bands.length; ) {
|
|
448
|
-
const gap = bands[i + 1][0] - bands[i][1];
|
|
449
|
-
if (gap <= Math.max(2, charSize * 0.15) && bands[i + 1][1] - bands[i][0] <= charSize * 1.3) {
|
|
450
|
-
bands.splice(i, 2, [bands[i][0], bands[i + 1][1]]);
|
|
451
|
-
} else i++;
|
|
452
|
-
}
|
|
453
|
-
while (bands.length > 1 && bands[0][0] <= 1 && bands[0][1] - bands[0][0] < minBand) bands.shift();
|
|
454
|
-
while (bands.length > 1 && bands[bands.length - 1][1] >= h - 1 && bands[bands.length - 1][1] - bands[bands.length - 1][0] < minBand) bands.pop();
|
|
455
|
-
for (; ; ) {
|
|
456
|
-
let idx = -1, minH = Infinity;
|
|
457
|
-
for (let i = 0; i < bands.length; i++) {
|
|
458
|
-
const bh = bands[i][1] - bands[i][0];
|
|
459
|
-
if (bh < minBand && bh < minH) {
|
|
460
|
-
minH = bh;
|
|
461
|
-
idx = i;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
if (idx < 0 || bands.length === 1) break;
|
|
465
|
-
const gapPrev = idx > 0 ? bands[idx][0] - bands[idx - 1][1] : Infinity;
|
|
466
|
-
const gapNext = idx < bands.length - 1 ? bands[idx + 1][0] - bands[idx][1] : Infinity;
|
|
467
|
-
const j = gapPrev <= gapNext ? idx - 1 : idx + 1;
|
|
468
|
-
const a = Math.min(idx, j), b = Math.max(idx, j);
|
|
469
|
-
bands = [...bands.slice(0, a), [bands[a][0], bands[b][1]], ...bands.slice(b + 1)];
|
|
470
|
-
}
|
|
471
|
-
if (bands.length === 1) return [{ y0: bands[0][0], y1: bands[0][1], x0: 0, x1: w }];
|
|
472
|
-
return bands.map(([y0, y1]) => {
|
|
473
|
-
const [x0, x1] = inkCols(y0, y1);
|
|
474
|
-
return { y0, y1, x0, x1 };
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
function components(gray, w, h, ink) {
|
|
478
|
-
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
479
|
-
const comps = [];
|
|
480
|
-
const label = new Int32Array(w * h);
|
|
481
|
-
const stack = [];
|
|
482
|
-
for (let p0 = 0; p0 < w * h; p0++) {
|
|
483
|
-
if (label[p0] || !isInk(gray[p0])) continue;
|
|
484
|
-
const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 };
|
|
485
|
-
label[p0] = c.id;
|
|
486
|
-
stack.push(p0);
|
|
487
|
-
while (stack.length) {
|
|
488
|
-
const p = stack.pop();
|
|
489
|
-
const x = p % w, y = p / w | 0;
|
|
490
|
-
if (x < c.x0) c.x0 = x;
|
|
491
|
-
if (x + 1 > c.x1) c.x1 = x + 1;
|
|
492
|
-
if (y < c.y0) c.y0 = y;
|
|
493
|
-
if (y + 1 > c.y1) c.y1 = y + 1;
|
|
494
|
-
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
|
495
|
-
const nx = x + dx, ny = y + dy;
|
|
496
|
-
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
|
|
497
|
-
const q = ny * w + nx;
|
|
498
|
-
if (!label[q] && isInk(gray[q])) {
|
|
499
|
-
label[q] = c.id;
|
|
500
|
-
stack.push(q);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c);
|
|
505
|
-
}
|
|
506
|
-
return { comps, label };
|
|
507
|
-
}
|
|
508
|
-
function leadingTriangle(gray, w, h, ink) {
|
|
509
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
510
|
-
let charH = 0;
|
|
511
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
512
|
-
const first = comps.filter((c) => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0];
|
|
513
|
-
if (!first || charH < 8) return null;
|
|
514
|
-
const cw = first.x1 - first.x0, ch = first.y1 - first.y0;
|
|
515
|
-
if (cw < ch * 0.8 || cw > ch * 1.8) return null;
|
|
516
|
-
const rowSpan = (y) => {
|
|
517
|
-
let n = 0, lo = -1, hi = -1, runs = 0, prev = false;
|
|
518
|
-
for (let x = first.x0; x < first.x1; x++) {
|
|
519
|
-
const on = label[y * w + x] === first.id;
|
|
520
|
-
if (on) {
|
|
521
|
-
n++;
|
|
522
|
-
if (lo < 0) lo = x - first.x0;
|
|
523
|
-
hi = x - first.x0;
|
|
524
|
-
if (!prev) runs++;
|
|
525
|
-
}
|
|
526
|
-
prev = on;
|
|
527
|
-
}
|
|
528
|
-
return { n, lo, hi, runs };
|
|
529
|
-
};
|
|
530
|
-
const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n);
|
|
531
|
-
if (base < cw * 0.7) return null;
|
|
532
|
-
for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {
|
|
533
|
-
const r = rowSpan(y);
|
|
534
|
-
if (r.n === 0) continue;
|
|
535
|
-
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;
|
|
536
|
-
}
|
|
537
|
-
const mid = rowSpan(first.y0 + Math.round(ch * 0.6));
|
|
538
|
-
if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null;
|
|
539
|
-
return mid.runs >= 2 ? "\u25B3" : "\u25B2";
|
|
540
|
-
}
|
|
541
|
-
function leaderRuns(gray, w, h, ink, minDots) {
|
|
542
|
-
const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0);
|
|
543
|
-
const blobs = [];
|
|
544
|
-
for (const c of comps) {
|
|
545
|
-
const last = blobs[blobs.length - 1];
|
|
546
|
-
if (last && c.x0 < last.x1) {
|
|
547
|
-
last.x1 = Math.max(last.x1, c.x1);
|
|
548
|
-
last.y0 = Math.min(last.y0, c.y0);
|
|
549
|
-
last.y1 = Math.max(last.y1, c.y1);
|
|
550
|
-
} else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 });
|
|
551
|
-
}
|
|
552
|
-
let charH = 0;
|
|
553
|
-
for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0);
|
|
554
|
-
if (charH < 8) return [];
|
|
555
|
-
const small = (c) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3;
|
|
556
|
-
let bandTop = h, bandBot = 0;
|
|
557
|
-
for (const c of comps) if (!small(c)) {
|
|
558
|
-
bandTop = Math.min(bandTop, c.y0);
|
|
559
|
-
bandBot = Math.max(bandBot, c.y1);
|
|
560
|
-
}
|
|
561
|
-
const cy = (c) => (c.y0 + c.y1) / 2;
|
|
562
|
-
const isDot = (b) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot;
|
|
563
|
-
const runs = [];
|
|
564
|
-
for (let i = 0; i < blobs.length; ) {
|
|
565
|
-
if (!isDot(blobs[i])) {
|
|
566
|
-
i++;
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
let j = i;
|
|
570
|
-
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++;
|
|
571
|
-
const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i]);
|
|
572
|
-
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));
|
|
573
|
-
const run = blobs.slice(i, j + 1).filter((b) => b.x0 > 0 && b.x1 < w);
|
|
574
|
-
const spread = (xs) => {
|
|
575
|
-
const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : void 0);
|
|
576
|
-
return v[v.length - 1] / Math.max(1, v[0]);
|
|
577
|
-
};
|
|
578
|
-
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;
|
|
579
|
-
if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1]);
|
|
580
|
-
i = j + 1;
|
|
581
|
-
}
|
|
582
|
-
return runs;
|
|
583
|
-
}
|
|
584
|
-
function inkBounds(gray, w, h, ink) {
|
|
585
|
-
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
586
|
-
const colInk = new Uint32Array(w);
|
|
587
|
-
const rowInk = new Uint32Array(h);
|
|
588
|
-
for (let y = 0; y < h; y++) {
|
|
589
|
-
const off = y * w;
|
|
590
|
-
for (let x = 0; x < w; x++) if (isInk(gray[off + x])) {
|
|
591
|
-
colInk[x]++;
|
|
592
|
-
rowInk[y]++;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
const ruleCol = new Uint8Array(w), ruleRow = new Uint8Array(h);
|
|
596
|
-
for (let x = 0; x < w; x++) if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
|
|
597
|
-
for (let y = 0; y < h; y++) if (rowInk[y] >= w * 0.85) ruleRow[y] = 1;
|
|
598
|
-
let x0 = w, x1 = -1, y0 = h, y1 = -1;
|
|
599
|
-
for (let y = 0; y < h; y++) {
|
|
600
|
-
if (ruleRow[y]) continue;
|
|
601
|
-
const off = y * w;
|
|
602
|
-
for (let x = 0; x < w; x++) {
|
|
603
|
-
if (ruleCol[x] || !isInk(gray[off + x])) continue;
|
|
604
|
-
if (x < x0) x0 = x;
|
|
605
|
-
if (x > x1) x1 = x;
|
|
606
|
-
if (y < y0) y0 = y;
|
|
607
|
-
if (y > y1) y1 = y;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
if (x1 < x0 || y1 < y0) return { x0: 0, y0: 0, x1: w, y1: h };
|
|
611
|
-
return { x0, y0, x1: x1 + 1, y1: y1 + 1 };
|
|
612
|
-
}
|
|
613
|
-
function bracketFeatures(gray, w, h, ink, cx) {
|
|
614
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
615
|
-
let charH = 0;
|
|
616
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
617
|
-
if (charH < 8) return null;
|
|
618
|
-
const tol = Math.max(2, charH * 0.3);
|
|
619
|
-
let best = null, bestD = Infinity;
|
|
620
|
-
for (const c of comps) {
|
|
621
|
-
if (c.y1 - c.y0 < charH * 0.3 || c.x1 < cx - tol || c.x0 > cx + tol) continue;
|
|
622
|
-
const d = Math.abs((c.x0 + c.x1) / 2 - cx);
|
|
623
|
-
if (d < bestD) {
|
|
624
|
-
bestD = d;
|
|
625
|
-
best = c;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
if (!best) return null;
|
|
629
|
-
const b = best;
|
|
630
|
-
const cw = b.x1 - b.x0, ch = b.y1 - b.y0;
|
|
631
|
-
if (cw < 3 || ch < 8) return null;
|
|
632
|
-
const spans = [];
|
|
633
|
-
for (let y = b.y0; y < b.y1; y++) {
|
|
634
|
-
let n = 0;
|
|
635
|
-
for (let x = b.x0; x < b.x1; x++) if (label[y * w + x] === b.id) n++;
|
|
636
|
-
spans.push(n);
|
|
637
|
-
}
|
|
638
|
-
const band = Math.max(1, Math.round(ch * 0.15));
|
|
639
|
-
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
640
|
-
const midRows = spans.slice(Math.floor(ch * 0.3), Math.ceil(ch * 0.7)).sort((p, q) => p - q);
|
|
641
|
-
const stroke = midRows[midRows.length >> 1] ?? 0;
|
|
642
|
-
const taper = spans.filter((n) => n > stroke && n < cw).length / ch;
|
|
643
|
-
const fill = spans.reduce((a, n) => a + n, 0) / (cw * ch);
|
|
644
|
-
return { top, bottom, taper, fill, cw, ch, charH };
|
|
645
|
-
}
|
|
646
|
-
function romanStems(gray, w, h, ink, prevX, nextX) {
|
|
647
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
648
|
-
let charH = 0;
|
|
649
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
650
|
-
if (charH < 10) return null;
|
|
651
|
-
const x0 = prevX + charH * 0.35, x1 = nextX - charH * 0.35;
|
|
652
|
-
const sel = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6 && (c.x0 + c.x1) / 2 >= x0 && (c.x0 + c.x1) / 2 < x1);
|
|
653
|
-
if (!sel.length) return null;
|
|
654
|
-
const ids = new Set(sel.map((c) => c.id));
|
|
655
|
-
const ux0 = Math.min(...sel.map((c) => c.x0)), ux1 = Math.max(...sel.map((c) => c.x1));
|
|
656
|
-
const uy0 = Math.min(...sel.map((c) => c.y0)), uy1 = Math.max(...sel.map((c) => c.y1));
|
|
657
|
-
const uh = uy1 - uy0;
|
|
658
|
-
const counts = /* @__PURE__ */ new Map();
|
|
659
|
-
let rows = 0;
|
|
660
|
-
for (let y = uy0 + Math.floor(uh * 0.25); y < uy1 - Math.floor(uh * 0.25); y++) {
|
|
661
|
-
let runs = 0, len = 0, thick = false;
|
|
662
|
-
for (let x = ux0; x <= ux1; x++) {
|
|
663
|
-
const on = x < ux1 && ids.has(label[y * w + x]);
|
|
664
|
-
if (on) len++;
|
|
665
|
-
else if (len) {
|
|
666
|
-
runs++;
|
|
667
|
-
if (len > uh * 0.35) thick = true;
|
|
668
|
-
len = 0;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
rows++;
|
|
672
|
-
if (!thick) counts.set(runs, (counts.get(runs) ?? 0) + 1);
|
|
673
|
-
}
|
|
674
|
-
const [n, k] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [0, 0];
|
|
675
|
-
return n >= 1 && n <= 3 && k >= rows * 0.7 ? n : null;
|
|
676
|
-
}
|
|
677
|
-
function circledAt(gray, w, h, ink, xs) {
|
|
678
|
-
const { comps } = components(gray, w, h, ink);
|
|
679
|
-
let charH = 0;
|
|
680
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
681
|
-
const rings = charH < 10 ? [] : comps.filter((r) => {
|
|
682
|
-
const rw = r.x1 - r.x0, rh = r.y1 - r.y0;
|
|
683
|
-
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);
|
|
684
|
-
});
|
|
685
|
-
return xs.map((x) => rings.some((r) => x >= r.x0 && x <= r.x1));
|
|
686
|
-
}
|
|
687
|
-
function quoteHead(gray, w, h, ink, cx) {
|
|
688
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
689
|
-
let charH = 0;
|
|
690
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
691
|
-
if (charH < 10) return null;
|
|
692
|
-
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
693
|
-
if (!tall.length) return null;
|
|
694
|
-
const mid = (Math.min(...tall.map((c) => c.y0)) + Math.max(...tall.map((c) => c.y1))) / 2;
|
|
695
|
-
let best = null, bestD = charH * 0.4;
|
|
696
|
-
for (const c of comps) {
|
|
697
|
-
const ch = c.y1 - c.y0, d = Math.abs((c.x0 + c.x1) / 2 - cx);
|
|
698
|
-
if (ch < charH * 0.12 || ch > charH * 0.5 || (c.y0 + c.y1) / 2 > mid || d > bestD) continue;
|
|
699
|
-
bestD = d;
|
|
700
|
-
best = c;
|
|
701
|
-
}
|
|
702
|
-
if (!best) return null;
|
|
703
|
-
let n = 0, sy = 0;
|
|
704
|
-
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) {
|
|
705
|
-
n++;
|
|
706
|
-
sy += y;
|
|
707
|
-
}
|
|
708
|
-
return n ? (sy / n - best.y0) / (best.y1 - best.y0) : null;
|
|
709
|
-
}
|
|
710
|
-
function edgeTrim(gray, w, h, ink) {
|
|
711
|
-
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
712
|
-
const colInk = new Uint32Array(w);
|
|
713
|
-
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (isInk(gray[y * w + x])) colInk[x]++;
|
|
714
|
-
const ruleCol = new Uint8Array(w);
|
|
715
|
-
let liveW = 0;
|
|
716
|
-
for (let x = 0; x < w; x++) {
|
|
717
|
-
if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
|
|
718
|
-
else liveW++;
|
|
719
|
-
}
|
|
720
|
-
const blank = Math.max(1, Math.floor(w * 0.02));
|
|
721
|
-
const bands = [];
|
|
722
|
-
let start = -1;
|
|
723
|
-
for (let y = 0; y <= h; y++) {
|
|
724
|
-
let n = 0;
|
|
725
|
-
if (y < h) {
|
|
726
|
-
for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[y * w + x])) n++;
|
|
727
|
-
}
|
|
728
|
-
const on = y < h && n > blank && n < liveW * 0.85;
|
|
729
|
-
if (on && start < 0) start = y;
|
|
730
|
-
else if (!on && start >= 0) {
|
|
731
|
-
bands.push([start, y]);
|
|
732
|
-
start = -1;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
let y0 = 0, y1 = h;
|
|
736
|
-
if (bands.length >= 2 && h >= 24) {
|
|
737
|
-
const mh = Math.max(...bands.map(([a, b]) => b - a));
|
|
738
|
-
const [top, next] = [bands[0], bands[1]], [bot, prev] = [bands[bands.length - 1], bands[bands.length - 2]];
|
|
739
|
-
if (top[0] <= 1 && top[1] - top[0] < mh * 0.45) y0 = top[1] + next[0] >> 1;
|
|
740
|
-
if (bot[1] >= h - 1 && bot[1] - bot[0] < mh * 0.45) y1 = bot[0] + prev[1] + 1 >> 1;
|
|
741
|
-
if (y0 > 0 || y1 < h) {
|
|
742
|
-
let runs = 0, len = 0;
|
|
743
|
-
for (let x = 0; x <= w; x++) {
|
|
744
|
-
let on = false;
|
|
745
|
-
if (x < w && !ruleCol[x]) for (let y = y0; y < y1 && !on; y++) on = isInk(gray[y * w + x]);
|
|
746
|
-
if (on) len++;
|
|
747
|
-
else {
|
|
748
|
-
if (len >= (y1 - y0) * 0.3) runs++;
|
|
749
|
-
len = 0;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
if (runs < 2) {
|
|
753
|
-
y0 = 0;
|
|
754
|
-
y1 = h;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
const textCol = (x) => {
|
|
759
|
-
for (let y = y0; y < y1; y++) if (isInk(gray[y * w + x])) return true;
|
|
760
|
-
return false;
|
|
761
|
-
};
|
|
762
|
-
const border = (x) => colInk[x] >= h * 0.95;
|
|
763
|
-
const gap = Math.max(2, Math.round(h * 0.1));
|
|
764
|
-
const clearFrom = (x, dir) => {
|
|
765
|
-
for (let k = 0; k < gap; k++) {
|
|
766
|
-
const c = x + dir * k;
|
|
767
|
-
if (c < 0 || c >= w || textCol(c)) return false;
|
|
768
|
-
}
|
|
769
|
-
return true;
|
|
770
|
-
};
|
|
771
|
-
let x0 = 0, x1 = w;
|
|
772
|
-
for (let x = 0; x < w * 0.2; x++) {
|
|
773
|
-
if (border(x)) {
|
|
774
|
-
let e = x;
|
|
775
|
-
while (e + 1 < w && border(e + 1)) e++;
|
|
776
|
-
if (clearFrom(e + 1, 1)) x0 = e + 1;
|
|
777
|
-
break;
|
|
778
|
-
}
|
|
779
|
-
if (textCol(x)) break;
|
|
780
|
-
}
|
|
781
|
-
for (let x = w - 1; x >= w * 0.8; x--) {
|
|
782
|
-
if (border(x)) {
|
|
783
|
-
let e = x;
|
|
784
|
-
while (e - 1 >= 0 && border(e - 1)) e--;
|
|
785
|
-
if (clearFrom(e - 1, -1)) x1 = e;
|
|
786
|
-
break;
|
|
787
|
-
}
|
|
788
|
-
if (textCol(x)) break;
|
|
789
|
-
}
|
|
790
|
-
return y0 > 0 || y1 < h || x0 > 0 || x1 < w ? { x0, x1, y0, y1 } : null;
|
|
791
|
-
}
|
|
792
|
-
function tallInkCount(gray, w, h, ink) {
|
|
793
|
-
const { comps } = components(gray, w, h, ink);
|
|
794
|
-
let charH = 0;
|
|
795
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
796
|
-
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.5).sort((a, b) => a.x0 - b.x0);
|
|
797
|
-
let n = 0, end = -1;
|
|
798
|
-
for (const c of tall) {
|
|
799
|
-
if (c.x0 >= end) n++;
|
|
800
|
-
end = Math.max(end, c.x1);
|
|
801
|
-
}
|
|
802
|
-
return n;
|
|
803
|
-
}
|
|
804
|
-
function gapGlyphs(gray, w, h, ink) {
|
|
805
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
806
|
-
let charH = 0;
|
|
807
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
808
|
-
if (charH < 10) return { glyphs: [], bandH: 0 };
|
|
809
|
-
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
810
|
-
const bandT = Math.min(...tall.map((c) => c.y0)), bandB = Math.max(...tall.map((c) => c.y1)), bh = bandB - bandT;
|
|
811
|
-
const inBand = (c) => c.y1 > bandT && c.y0 < bandB;
|
|
812
|
-
const out = [];
|
|
813
|
-
for (const c of comps) {
|
|
814
|
-
if (!inBand(c)) continue;
|
|
815
|
-
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
816
|
-
if (ch >= bh * 0.9 && cw <= bh * 0.15) continue;
|
|
817
|
-
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));
|
|
818
|
-
const apart = !others.some((o) => o.x0 < c.x1 + 1 && o.x1 > c.x0 - 1);
|
|
819
|
-
const nearlyApart = !others.some((o) => Math.min(o.x1, c.x1) - Math.max(o.x0, c.x0) > 2);
|
|
820
|
-
const spans = [], runs = [];
|
|
821
|
-
let n = 0;
|
|
822
|
-
for (let y = c.y0; y < c.y1; y++) {
|
|
823
|
-
let s = 0, r = 0, prev = false;
|
|
824
|
-
for (let x = c.x0; x < c.x1; x++) {
|
|
825
|
-
const on = label[y * w + x] === c.id;
|
|
826
|
-
if (on) {
|
|
827
|
-
s++;
|
|
828
|
-
if (!prev) r++;
|
|
829
|
-
}
|
|
830
|
-
prev = on;
|
|
831
|
-
}
|
|
832
|
-
spans.push(s);
|
|
833
|
-
runs.push(r);
|
|
834
|
-
n += s;
|
|
835
|
-
}
|
|
836
|
-
const fill = n / (cw * ch), cy = ((c.y0 + c.y1) / 2 - bandT) / bh;
|
|
837
|
-
const band = Math.max(1, Math.round(ch * 0.15));
|
|
838
|
-
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
839
|
-
let mark = null;
|
|
840
|
-
const speck = Math.max(3, bh * 0.08);
|
|
841
|
-
const dot = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= speck && cw >= speck && cw >= ch * 0.6 && cw <= ch * 1.6;
|
|
842
|
-
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;
|
|
843
|
-
if (!apart && !(nearlyApart && ch >= bh * 0.6)) {
|
|
844
|
-
if (small) out.push({ x0: c.x0, x1: c.x1, mark: "\xB7" });
|
|
845
|
-
continue;
|
|
846
|
-
}
|
|
847
|
-
if (!apart) {
|
|
848
|
-
} else if (dot && fill >= 0.6 && cy >= 0.3 && cy <= 0.7) mark = "\xB7";
|
|
849
|
-
else if (small) mark = "\xB7";
|
|
850
|
-
else if (dot && fill >= 0.6 && cy >= 0.75 && cw >= ch * 0.8) mark = ".";
|
|
851
|
-
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";
|
|
852
|
-
else if (ch <= bh * 0.15 && cw >= ch * 2 && cw <= bh * 0.8 && fill >= 0.7 && cy >= 0.35 && cy <= 0.75) mark = "-";
|
|
853
|
-
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";
|
|
854
|
-
else if (ch >= bh * 0.3 && ch <= bh * 0.65 && fill < 0.5) {
|
|
855
|
-
const stem = (from, to) => {
|
|
856
|
-
let sx = 0, k = 0;
|
|
857
|
-
for (let y = from; y < to; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
858
|
-
sx += x - c.x0;
|
|
859
|
-
k++;
|
|
860
|
-
}
|
|
861
|
-
return k ? sx / k / cw : 0.5;
|
|
862
|
-
};
|
|
863
|
-
if (c.y0 <= bandT + bh * 0.15 && top >= 0.7 && bottom <= 0.5 && stem(c.y0 + band, c.y1) <= 0.35) mark = "\u300C";
|
|
864
|
-
else if (c.y1 >= bandB - bh * 0.15 && bottom >= 0.7 && top <= 0.5 && stem(c.y0, c.y1 - band) >= 0.65) mark = "\u300D";
|
|
865
|
-
} 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";
|
|
866
|
-
if (!mark && ch >= bh * 0.6 && cw >= ch * 0.25 && cw <= ch * 0.7 && fill <= 0.4 && runs.every((r) => r <= 1)) {
|
|
867
|
-
const mx = (y) => {
|
|
868
|
-
let sx = 0, k = 0;
|
|
869
|
-
for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
870
|
-
sx += x;
|
|
871
|
-
k++;
|
|
872
|
-
}
|
|
873
|
-
return k ? sx / k : NaN;
|
|
874
|
-
};
|
|
875
|
-
if (mx(c.y0 + band) - mx(c.y1 - 1 - band) >= cw * 0.5) mark = "/";
|
|
876
|
-
}
|
|
877
|
-
if (mark) out.push({ x0: c.x0, x1: c.x1, mark });
|
|
878
|
-
}
|
|
879
|
-
return { glyphs: out, bandH: bh };
|
|
880
|
-
}
|
|
881
|
-
function ringBullet(gray, w, h, ink, cx) {
|
|
882
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
883
|
-
let charH = 0;
|
|
884
|
-
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
885
|
-
if (charH < 10) return null;
|
|
886
|
-
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
887
|
-
const bandT = Math.min(...tall.map((c2) => c2.y0)), bh = Math.max(...tall.map((c2) => c2.y1)) - bandT;
|
|
888
|
-
let best = null, bestD = bh * 0.6;
|
|
889
|
-
for (const c2 of comps) {
|
|
890
|
-
const d = Math.abs((c2.x0 + c2.x1) / 2 - cx);
|
|
891
|
-
if (c2.y1 - c2.y0 >= bh * 0.3 && d < bestD) {
|
|
892
|
-
bestD = d;
|
|
893
|
-
best = c2;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
if (!best) return null;
|
|
897
|
-
const c = best, cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
898
|
-
if (cw < ch * 0.85 || cw > ch * 1.2) return null;
|
|
899
|
-
const runs = (xs, at) => {
|
|
900
|
-
let n2 = 0, len = 0, mx2 = 0, mn = Infinity;
|
|
901
|
-
for (const k of [...xs, -1]) {
|
|
902
|
-
if (k >= 0 && at(k)) {
|
|
903
|
-
if (!len) n2++;
|
|
904
|
-
len++;
|
|
905
|
-
mx2 = Math.max(mx2, len);
|
|
906
|
-
} else {
|
|
907
|
-
if (len) mn = Math.min(mn, len);
|
|
908
|
-
len = 0;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
return { n: n2, mx: mx2, mn };
|
|
912
|
-
};
|
|
913
|
-
const my = c.y0 + c.y1 >> 1, mx = c.x0 + c.x1 >> 1;
|
|
914
|
-
const row = runs(Array.from({ length: cw }, (_, k) => c.x0 + k), (x) => label[my * w + x] === c.id);
|
|
915
|
-
const col = runs(Array.from({ length: ch }, (_, k) => c.y0 + k), (y) => label[y * w + mx] === c.id);
|
|
916
|
-
if (row.n !== 2 || col.n !== 2) return null;
|
|
917
|
-
let n = 0;
|
|
918
|
-
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++;
|
|
919
|
-
const fill = n / (cw * ch), rel = ch / bh;
|
|
920
|
-
if (rel >= 0.62 && (fill <= 0.23 ? row.mx <= cw * 0.1 : fill <= 0.33 && row.mn <= cw * 0.08)) return "\u25CB";
|
|
921
|
-
if (rel <= 0.58 && fill >= 0.24) return "\u3147";
|
|
922
|
-
if (rel >= 0.6 && rel <= 0.8 && fill >= 0.3 && ((c.y0 + c.y1) / 2 - bandT) / bh >= 0.55) return "o";
|
|
923
|
-
return null;
|
|
924
|
-
}
|
|
925
|
-
function discAt(gray, w, h, ink, cx) {
|
|
926
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
927
|
-
let charH = 0;
|
|
928
|
-
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
929
|
-
if (charH < 10) return false;
|
|
930
|
-
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
931
|
-
const bh = Math.max(...tall.map((c2) => c2.y1)) - Math.min(...tall.map((c2) => c2.y0));
|
|
932
|
-
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];
|
|
933
|
-
if (!c) return false;
|
|
934
|
-
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
935
|
-
if (ch < bh * 0.45 || ch > bh * 1.05 || cw < ch * 0.8 || cw > ch * 1.25) return false;
|
|
936
|
-
let n = 0;
|
|
937
|
-
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++;
|
|
938
|
-
const fill = n / (cw * ch);
|
|
939
|
-
return fill >= 0.65 && fill <= 0.9;
|
|
940
|
-
}
|
|
941
|
-
function serifOne(gray, w, h, ink, cx) {
|
|
942
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
943
|
-
let charH = 0;
|
|
944
|
-
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
945
|
-
if (charH < 10) return false;
|
|
946
|
-
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];
|
|
947
|
-
if (!c) return false;
|
|
948
|
-
const ch = c.y1 - c.y0;
|
|
949
|
-
const span = (a, b) => {
|
|
950
|
-
let mn = Infinity, mx = -1;
|
|
951
|
-
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) {
|
|
952
|
-
mn = Math.min(mn, x);
|
|
953
|
-
mx = Math.max(mx, x);
|
|
954
|
-
}
|
|
955
|
-
return [mn, mx];
|
|
956
|
-
};
|
|
957
|
-
const [m0, m1] = span(0.4, 0.6), [t0, t1] = span(0, 0.2), [b0, b1] = span(0.8, 1);
|
|
958
|
-
const sw = m1 - m0 + 1;
|
|
959
|
-
const even = (l, r) => l >= sw * 0.4 && r >= sw * 0.4 && Math.abs(l - r) <= Math.max(l, r) * 0.35;
|
|
960
|
-
return sw >= 2 && sw <= ch * 0.3 && even(m0 - t0, t1 - m1) && even(m0 - b0, b1 - m1);
|
|
961
|
-
}
|
|
962
|
-
function starRun(gray, w, h, ink, cx) {
|
|
963
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
964
|
-
let charH = 0;
|
|
965
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
966
|
-
if (charH < 10) return 0;
|
|
967
|
-
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
968
|
-
const bandT = Math.min(...tall.map((c) => c.y0)), bh = Math.max(...tall.map((c) => c.y1)) - bandT;
|
|
969
|
-
const star = (c) => {
|
|
970
|
-
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
971
|
-
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;
|
|
972
|
-
let n = 0;
|
|
973
|
-
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++;
|
|
974
|
-
const fill = n / (cw * ch);
|
|
975
|
-
return fill >= 0.25 && fill <= 0.65;
|
|
976
|
-
};
|
|
977
|
-
const stars2 = comps.filter(star).sort((a, b) => a.x0 - b.x0);
|
|
978
|
-
const k = stars2.findIndex((c) => cx >= c.x0 - (c.x1 - c.x0) && cx <= c.x1 + (c.x1 - c.x0));
|
|
979
|
-
if (k < 0) return 0;
|
|
980
|
-
let lo = k, hi = k;
|
|
981
|
-
while (lo > 0 && stars2[lo].x0 - stars2[lo - 1].x1 <= stars2[lo].x1 - stars2[lo].x0) lo--;
|
|
982
|
-
while (hi + 1 < stars2.length && stars2[hi + 1].x0 - stars2[hi].x1 <= stars2[hi].x1 - stars2[hi].x0) hi++;
|
|
983
|
-
return hi - lo + 1;
|
|
984
|
-
}
|
|
985
|
-
var BRACKET_TAPER = 0.16;
|
|
986
|
-
var BRACKET_SOLID = 0.4;
|
|
987
|
-
var BRACKET_WIDE = 0.265;
|
|
988
|
-
var BRACKET_LENS = 0.35;
|
|
989
|
-
function bracketShape(f, close) {
|
|
990
|
-
const [near, far] = close ? [f.bottom, f.top] : [f.top, f.bottom];
|
|
991
|
-
const paren = close ? ")" : "(";
|
|
992
|
-
if (near >= 0.7 && far <= 0.5) return f.taper < BRACKET_TAPER ? close ? "\u300D" : "\u300C" : paren;
|
|
993
|
-
if (near >= 0.7 && far >= 0.7 && f.taper >= BRACKET_TAPER) {
|
|
994
|
-
return f.fill >= BRACKET_SOLID && (f.cw >= f.ch * BRACKET_WIDE || f.taper >= BRACKET_LENS) ? close ? "\u3011" : "\u3010" : paren;
|
|
995
|
-
}
|
|
996
|
-
return null;
|
|
997
|
-
}
|
|
998
|
-
function leadingBullet(gray, w, h, ink, firstX) {
|
|
999
|
-
const { comps, label } = components(gray, w, h, ink);
|
|
1000
|
-
let charH = 0;
|
|
1001
|
-
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
1002
|
-
if (charH < 10) return null;
|
|
1003
|
-
const cand = comps.filter((c) => c.y1 - c.y0 >= charH * 0.15).sort((a, b) => a.x0 - b.x0);
|
|
1004
|
-
const first = cand[0];
|
|
1005
|
-
if (!first) return null;
|
|
1006
|
-
const cl = cand.filter((c) => c.x0 < first.x1 && c.x1 > first.x0);
|
|
1007
|
-
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));
|
|
1008
|
-
const cw = x1 - x0, ch = y1 - y0;
|
|
1009
|
-
if (cw < ch * 0.8 || cw > ch * 1.25) return null;
|
|
1010
|
-
const next = cand.find((c) => c.x0 >= x1);
|
|
1011
|
-
if (next && next.x0 - x1 < ch * 0.3) return null;
|
|
1012
|
-
let inkN = 0;
|
|
1013
|
-
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++;
|
|
1014
|
-
const fill = inkN / (cw * ch);
|
|
1015
|
-
const rel = ch / charH;
|
|
1016
|
-
const ring2 = (c) => {
|
|
1017
|
-
const a = c.x1 - c.x0, b = c.y1 - c.y0;
|
|
1018
|
-
return a >= b * 0.75 && a <= b * 1.33;
|
|
1019
|
-
};
|
|
1020
|
-
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 && ring2(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));
|
|
1021
|
-
const span = (y) => {
|
|
1022
|
-
let n = 0;
|
|
1023
|
-
for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) n++;
|
|
1024
|
-
return n;
|
|
1025
|
-
};
|
|
1026
|
-
const band = Math.max(1, Math.round(ch * 0.15));
|
|
1027
|
-
const edgeFull = (from, dir) => {
|
|
1028
|
-
let m = 0;
|
|
1029
|
-
for (let k = 0; k < band; k++) m = Math.max(m, span(from + dir * k));
|
|
1030
|
-
return m >= cw * 0.8;
|
|
1031
|
-
};
|
|
1032
|
-
let midRuns = 0;
|
|
1033
|
-
{
|
|
1034
|
-
let prev = false;
|
|
1035
|
-
const my = y0 + (ch >> 1);
|
|
1036
|
-
for (let x = x0; x < x1; x++) {
|
|
1037
|
-
const on = cl.some((c) => label[my * w + x] === c.id);
|
|
1038
|
-
if (on && !prev) midRuns++;
|
|
1039
|
-
prev = on;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
const hollowSquare = cl.length === 1 && fill < 0.5 && midRuns === 2 && edgeFull(y0, 1) && edgeFull(y1 - 1, -1);
|
|
1043
|
-
let mark = null;
|
|
1044
|
-
if (hollowSquare && rel >= 0.6 && rel <= 0.92) mark = "\u25A1";
|
|
1045
|
-
else if (nested && fill < 0.45 && rel >= 0.6) mark = "\u25CE";
|
|
1046
|
-
else if (cl.length === 1 && fill >= 0.9 && rel >= 0.28 && rel <= 0.6) mark = "\u25AA";
|
|
1047
|
-
else if (cl.length === 1 && fill >= 0.65 && fill <= 0.88 && rel >= 0.5 && rel <= 0.8) mark = "\u25CF";
|
|
1048
|
-
if (!mark) return null;
|
|
1049
|
-
return { mark, covers: firstX <= x1 + ch * 0.3 };
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
372
|
// src/ocr/glyph-restore.ts
|
|
373
|
+
var INKLESS_DELTA = 32;
|
|
1053
374
|
function restoreGlyphs(rgba, pageW, box, text, steps, stepPx, note = {}) {
|
|
1054
375
|
const chars = [...text];
|
|
1055
376
|
if (chars.length !== steps.length) return text;
|
|
@@ -1060,11 +381,37 @@ function restoreGlyphs(rgba, pageW, box, text, steps, stepPx, note = {}) {
|
|
|
1060
381
|
if (/(^|\D)[1-9](\D|$)/.test(text)) circled(c);
|
|
1061
382
|
if (text.includes("*")) stars(c);
|
|
1062
383
|
if (/[\u00b7\u2022]/.test(text)) discs(c);
|
|
384
|
+
if (/[-\u2013\u2014]/.test(text)) dashes(c);
|
|
1063
385
|
gaps(c);
|
|
1064
386
|
note.ringLead = ring(c);
|
|
1065
387
|
if (/[I1Ⅰ-Ⅲ]|^\s*\./.test(text)) roman(c);
|
|
1066
388
|
return chars.join("");
|
|
1067
389
|
}
|
|
390
|
+
function trimInklessEnds(rgba, pageW, box, text, steps, stepPx) {
|
|
391
|
+
const chars = [...text], { w, h } = box;
|
|
392
|
+
if (chars.length !== steps.length || steps.some((s) => (s + 0.5) * stepPx > w + stepPx)) return { text, steps };
|
|
393
|
+
const gray = grayCrop(rgba, pageW, box);
|
|
394
|
+
const hist = new Uint32Array(256);
|
|
395
|
+
for (const v of gray) hist[v]++;
|
|
396
|
+
let bg = 0;
|
|
397
|
+
for (let n = 0; bg < 255 && (n += hist[bg]) < gray.length / 2; ) bg++;
|
|
398
|
+
const cols = new Uint32Array(w);
|
|
399
|
+
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (Math.abs(gray[y * w + x] - bg) > INKLESS_DELTA) cols[x]++;
|
|
400
|
+
const half = Math.max(stepPx, h * 0.25);
|
|
401
|
+
const inkless = (i) => {
|
|
402
|
+
const cx = (steps[i] + 0.5) * stepPx;
|
|
403
|
+
for (let x = Math.max(0, Math.floor(cx - half)); x <= Math.min(w - 1, Math.ceil(cx + half)); x++) if (cols[x] && cols[x] < h * 0.9) return false;
|
|
404
|
+
return true;
|
|
405
|
+
};
|
|
406
|
+
const blank = (i) => !chars[i].trim();
|
|
407
|
+
const mark = (i) => /\p{P}/u.test(chars[i]);
|
|
408
|
+
let a = 0, b = chars.length;
|
|
409
|
+
while (a < b && (blank(a) || !mark(a) && inkless(a))) a++;
|
|
410
|
+
while (b > a && (blank(b - 1) || !mark(b - 1) && inkless(b - 1))) b--;
|
|
411
|
+
const cut = (from, to) => chars.slice(from, to).some((c) => c.trim());
|
|
412
|
+
if (a === b || !cut(0, a) && !cut(b, chars.length)) return { text, steps };
|
|
413
|
+
return { text: chars.slice(a, b).join(""), steps: steps.slice(a, b) };
|
|
414
|
+
}
|
|
1068
415
|
function brackets({ chars, cx, gray, w, h, ink }) {
|
|
1069
416
|
for (let i = 0; i < chars.length; i++) {
|
|
1070
417
|
if (chars[i] !== "[" && chars[i] !== "]") continue;
|
|
@@ -1097,6 +444,16 @@ function quotes({ chars, cx, gray, w, h, ink }) {
|
|
|
1097
444
|
}
|
|
1098
445
|
}
|
|
1099
446
|
}
|
|
447
|
+
function dashes({ chars, cx, gray, w, h, ink }) {
|
|
448
|
+
const head = chars.findIndex((c) => c.trim()), letters = chars.some((c) => /[가-힣A-Za-z]/.test(c));
|
|
449
|
+
for (let i = 0; i < chars.length; i++) {
|
|
450
|
+
if (!/^[-\u2013\u2014]$/.test(chars[i])) continue;
|
|
451
|
+
const r = dashRatio(gray, w, h, ink, cx(i));
|
|
452
|
+
if (r === null) continue;
|
|
453
|
+
if (r < 0.65) chars[i] = "-";
|
|
454
|
+
else if (r > 0.75 && i === head && letters) chars[i] = "\u2014";
|
|
455
|
+
}
|
|
456
|
+
}
|
|
1100
457
|
function circled({ chars, cx, gray, w, h, ink }) {
|
|
1101
458
|
const at = chars.flatMap((c, i) => /^[1-9]$/.test(c) && !/\d/.test(chars[i - 1] ?? "") && !/\d/.test(chars[i + 1] ?? "") ? [i] : []);
|
|
1102
459
|
if (!at.length) return;
|
|
@@ -1548,7 +905,9 @@ var OcrEngine = class _OcrEngine {
|
|
|
1548
905
|
if (!cur || score(job, r.confidence) > score(cur.job, cur.confidence)) best.set(job.group, { job, ...r });
|
|
1549
906
|
});
|
|
1550
907
|
let items = [];
|
|
1551
|
-
for (const
|
|
908
|
+
for (const r of best.values()) {
|
|
909
|
+
const { job, confidence, stepPx } = r;
|
|
910
|
+
const { text: read, steps } = tuning.postprocess && job.rot === 0 ? trimInklessEnds(rgba, width, job.box, r.text, r.steps, stepPx) : r;
|
|
1552
911
|
const note = {};
|
|
1553
912
|
const raw = tuning.postprocess && job.rot === 0 ? restoreGlyphs(rgba, width, job.box, read, steps, stepPx, note) : read;
|
|
1554
913
|
let text = tuning.postprocess ? restoreSymbols(raw.trim(), note.ringLead) : raw;
|
|
@@ -1584,7 +943,9 @@ var OcrEngine = class _OcrEngine {
|
|
|
1584
943
|
if (text) joins[job.join].parts.push({ x: job.box.x, text, dotsBefore: job.dotsBefore === true, confidence });
|
|
1585
944
|
continue;
|
|
1586
945
|
}
|
|
1587
|
-
|
|
946
|
+
const item = { text, ...this.itemBox(rgba, width, job.box, tuning), confidence };
|
|
947
|
+
if (job.rot === 0) rememberOcrTextGaps(item, findOcrTextGaps(rgba, width, job.box, read, text, steps, stepPx));
|
|
948
|
+
items.push(item);
|
|
1588
949
|
}
|
|
1589
950
|
const leaderEnds = /* @__PURE__ */ new Map();
|
|
1590
951
|
for (const j of joins) {
|
|
@@ -2111,7 +1472,11 @@ async function closerReads(rgba, rw, rh, pdfH, hi, regions, engine, tuning = REG
|
|
|
2111
1472
|
const crop = new Uint8Array(cw * ch * 4);
|
|
2112
1473
|
for (let y = 0; y < ch; y++) crop.set(rgba.subarray(((y0 + y) * rw + x0) * 4, ((y0 + y) * rw + x0 + cw) * 4), y * cw * 4);
|
|
2113
1474
|
const items = await engine.recognizePage(crop, cw, ch, void 0, tuning);
|
|
2114
|
-
out.push(items.map((it) =>
|
|
1475
|
+
out.push(items.map((it) => {
|
|
1476
|
+
const placed = { ...it, x: (it.x + x0) / 2, y: (it.y + y0) / 2, w: it.w / 2, h: it.h / 2 };
|
|
1477
|
+
copyOcrTextGaps(it, placed, 0.5, x0 / 2);
|
|
1478
|
+
return placed;
|
|
1479
|
+
}));
|
|
2115
1480
|
}
|
|
2116
1481
|
return out;
|
|
2117
1482
|
}
|
|
@@ -2176,7 +1541,7 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
|
|
|
2176
1541
|
}
|
|
2177
1542
|
const norm = items.map((it, i) => {
|
|
2178
1543
|
const h = (figureRegion ? Math.max(1, bottoms[i] - it.y) : it.h) / scale;
|
|
2179
|
-
|
|
1544
|
+
const normalized = {
|
|
2180
1545
|
text: it.text,
|
|
2181
1546
|
x: Math.round(xs[i].x / scale),
|
|
2182
1547
|
// NormItem.y 는 pdfjs transform[5] = 베이스라인 (bottom-up) — 잉크 하단으로 근사
|
|
@@ -2189,6 +1554,8 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
|
|
|
2189
1554
|
isHidden: false,
|
|
2190
1555
|
...xs[i].space ? { hasSpaceBefore: true } : {}
|
|
2191
1556
|
};
|
|
1557
|
+
copyOcrTextGaps(it, normalized, 1 / scale);
|
|
1558
|
+
return normalized;
|
|
2192
1559
|
});
|
|
2193
1560
|
return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
|
|
2194
1561
|
}
|
|
@@ -2243,4 +1610,4 @@ export {
|
|
|
2243
1610
|
groupOcrLines,
|
|
2244
1611
|
ocrItemsToBlocks
|
|
2245
1612
|
};
|
|
2246
|
-
//# sourceMappingURL=chunk-
|
|
1613
|
+
//# sourceMappingURL=chunk-GMKFXPEI.js.map
|