kordoc 4.16.0 → 4.16.1
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 +345 -1135
- package/dist/{-VQNLLLPE.js → -3DURLXSO.js} +11 -11
- package/dist/{chunk-3JWMDL3S.js → chunk-24N4IJ5I.js} +2 -2
- package/dist/{chunk-H6KMTYYZ.cjs → chunk-2BZFKIRB.cjs} +3 -3
- package/dist/{chunk-H6KMTYYZ.cjs.map → chunk-2BZFKIRB.cjs.map} +1 -1
- package/dist/{chunk-RC4C7Q33.js → chunk-3VLVNKJV.js} +2 -2
- package/dist/{chunk-XHOZB2WP.js → chunk-3WIQSM3Z.js} +681 -22
- package/dist/chunk-3WIQSM3Z.js.map +1 -0
- package/dist/{chunk-DYENR3PX.js → chunk-63YGGAQW.js} +2 -2
- package/dist/{chunk-43FKGDDO.js → chunk-7FKPPIDZ.js} +50 -19
- package/dist/chunk-7FKPPIDZ.js.map +1 -0
- package/dist/{chunk-MH6MAL64.cjs → chunk-7IRHSRPU.cjs} +136 -105
- package/dist/chunk-7IRHSRPU.cjs.map +1 -0
- package/dist/{chunk-A3QDRMVD.js → chunk-7XAGIPOX.js} +2 -2
- package/dist/{chunk-NNYF2AGC.js → chunk-ANZTLTKU.js} +2 -2
- package/dist/{chunk-N6TPODYL.js → chunk-BIHS4VWR.js} +2 -2
- package/dist/{chunk-476OYHGZ.js → chunk-BTO7ZEE4.js} +6 -6
- package/dist/{chunk-46DZF3RK.js → chunk-CXWWX6BQ.js} +2 -2
- package/dist/{chunk-DRY3L5KQ.cjs → chunk-D2QCXK2X.cjs} +6 -6
- package/dist/{chunk-DRY3L5KQ.cjs.map → chunk-D2QCXK2X.cjs.map} +1 -1
- package/dist/{chunk-JB5X5NNL.js → chunk-DJ2BBQ5P.js} +680 -21
- package/dist/chunk-DJ2BBQ5P.js.map +1 -0
- package/dist/{chunk-ZY5K3LPM.cjs → chunk-DO74GYSM.cjs} +1 -1
- package/dist/chunk-DO74GYSM.cjs.map +1 -0
- package/dist/{chunk-6LMKQM7I.js → chunk-EEVAPRPD.js} +3 -3
- package/dist/{chunk-XWA625AN.js → chunk-FSDJLAOW.js} +3 -1
- package/dist/chunk-FSDJLAOW.js.map +1 -0
- package/dist/{chunk-QJERJZJ6.js → chunk-KBAQLAEL.js} +1 -1
- package/dist/chunk-KBAQLAEL.js.map +1 -0
- package/dist/{chunk-PQRP4NSF.js → chunk-N65ZXWNQ.js} +2 -2
- package/dist/{chunk-UDX7TSOC.js → chunk-NCXRSQNE.js} +2 -2
- package/dist/{chunk-GOQBON77.js → chunk-NUPXPKIQ.js} +2 -2
- package/dist/{chunk-B5NEGP65.cjs → chunk-NV37CQFN.cjs} +692 -33
- package/dist/chunk-NV37CQFN.cjs.map +1 -0
- package/dist/{chunk-CB5XDCSL.js → chunk-OBRPYCIS.js} +1377 -947
- package/dist/chunk-OBRPYCIS.js.map +1 -0
- package/dist/{chunk-HIVK5V27.js → chunk-PVMROO3A.js} +51 -20
- package/dist/chunk-PVMROO3A.js.map +1 -0
- package/dist/{chunk-UQ6TZDW6.js → chunk-T77M2QP7.js} +1 -1
- package/dist/chunk-T77M2QP7.js.map +1 -0
- package/dist/{chunk-TGD7UFHD.js → chunk-UZHT3LBF.js} +2 -2
- package/dist/{chunk-PIMB2YNO.js → chunk-VCRBR72A.js} +2 -2
- package/dist/{chunk-C75WE52C.js → chunk-VNOQTDTE.js} +2 -2
- package/dist/{chunk-NWODPYBD.cjs → chunk-YRRBA4HW.cjs} +2 -2
- package/dist/{chunk-NWODPYBD.cjs.map → chunk-YRRBA4HW.cjs.map} +1 -1
- package/dist/chunks-PYYQURNO.js +10 -0
- package/dist/cli.js +31 -29
- package/dist/cli.js.map +1 -1
- package/dist/{formula-FNQYHMIY.js → formula-IPLFHEBD.js} +3 -3
- package/dist/{formula-VEFKYXPB.cjs → formula-TRTONZMA.cjs} +9 -9
- package/dist/{formula-VEFKYXPB.cjs.map → formula-TRTONZMA.cjs.map} +1 -1
- package/dist/{formula-5775PTP2.js → formula-U4BTEZ6I.js} +4 -4
- package/dist/{image-ocr-PYZU4TO7.js → image-ocr-3IJ25PS4.js} +8 -8
- package/dist/{image-ocr-YA2MQBRP.js → image-ocr-GTDG4SAJ.js} +6 -6
- package/dist/{image-ocr-6JZR734G.cjs → image-ocr-THFLQ3DC.cjs} +15 -15
- package/dist/{image-ocr-6JZR734G.cjs.map → image-ocr-THFLQ3DC.cjs.map} +1 -1
- package/dist/index.cjs +1555 -1125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1105 -675
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +31 -27
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-O6J7ORVC.js → model-bundle-PVWIAVEX.js} +5 -5
- package/dist/{models-YMOKZGCI.js → models-VCRF3JMT.js} +5 -5
- package/dist/{parser-YAQV7FBO.cjs → parser-2CN2DIPV.cjs} +356 -218
- package/dist/parser-2CN2DIPV.cjs.map +1 -0
- package/dist/{parser-3B6I2XL7.js → parser-T4RITZDP.js} +221 -83
- package/dist/parser-T4RITZDP.js.map +1 -0
- package/dist/{parser-PXATL5DM.js → parser-UEROAPYZ.js} +218 -80
- package/dist/parser-UEROAPYZ.js.map +1 -0
- package/dist/pdf-ocr-NARV2AQI.cjs +16 -0
- package/dist/{pdf-ocr-T2JXQATA.cjs.map → pdf-ocr-NARV2AQI.cjs.map} +1 -1
- package/dist/pdf-ocr-SYLMM727.js +18 -0
- package/dist/pdf-ocr-YMQZDJBO.js +15 -0
- package/dist/{profile-io-ZTV2MKRT.js → profile-io-YD54CDXY.js} +3 -3
- package/dist/{rasterize-MMUZ537K.js → rasterize-3THYOOUN.js} +3 -3
- package/dist/{redact-doc-S42XGV5D.js → redact-doc-65L2YJN6.js} +11 -11
- package/dist/{render-WCWPZKJ7.js → render-YU7QJVZ7.js} +7 -7
- package/dist/seal-6CQGFTML.js +10 -0
- package/dist/{setup-D35LKZCH.js → setup-CFHKXDY4.js} +3 -3
- package/dist/visual-ED2H75Q5.js +20 -0
- package/dist/{watch-AX64CIMN.js → watch-D6FCAYKJ.js} +12 -12
- package/package.json +1 -1
- package/dist/chunk-43FKGDDO.js.map +0 -1
- package/dist/chunk-B5NEGP65.cjs.map +0 -1
- package/dist/chunk-CB5XDCSL.js.map +0 -1
- package/dist/chunk-HIVK5V27.js.map +0 -1
- package/dist/chunk-JB5X5NNL.js.map +0 -1
- package/dist/chunk-MH6MAL64.cjs.map +0 -1
- package/dist/chunk-QJERJZJ6.js.map +0 -1
- package/dist/chunk-UQ6TZDW6.js.map +0 -1
- package/dist/chunk-XHOZB2WP.js.map +0 -1
- package/dist/chunk-XWA625AN.js.map +0 -1
- package/dist/chunk-ZY5K3LPM.cjs.map +0 -1
- package/dist/chunks-JD25E5PM.js +0 -10
- package/dist/parser-3B6I2XL7.js.map +0 -1
- package/dist/parser-PXATL5DM.js.map +0 -1
- package/dist/parser-YAQV7FBO.cjs.map +0 -1
- package/dist/pdf-ocr-2TTO5PX2.js +0 -15
- package/dist/pdf-ocr-T2JXQATA.cjs +0 -16
- package/dist/pdf-ocr-WF2NQA6X.js +0 -18
- package/dist/seal-WHK4GOHR.js +0 -10
- package/dist/visual-WL4TDE5B.js +0 -20
- /package/dist/{-VQNLLLPE.js.map → -3DURLXSO.js.map} +0 -0
- /package/dist/{chunk-3JWMDL3S.js.map → chunk-24N4IJ5I.js.map} +0 -0
- /package/dist/{chunk-RC4C7Q33.js.map → chunk-3VLVNKJV.js.map} +0 -0
- /package/dist/{chunk-DYENR3PX.js.map → chunk-63YGGAQW.js.map} +0 -0
- /package/dist/{chunk-A3QDRMVD.js.map → chunk-7XAGIPOX.js.map} +0 -0
- /package/dist/{chunk-NNYF2AGC.js.map → chunk-ANZTLTKU.js.map} +0 -0
- /package/dist/{chunk-N6TPODYL.js.map → chunk-BIHS4VWR.js.map} +0 -0
- /package/dist/{chunk-476OYHGZ.js.map → chunk-BTO7ZEE4.js.map} +0 -0
- /package/dist/{chunk-46DZF3RK.js.map → chunk-CXWWX6BQ.js.map} +0 -0
- /package/dist/{chunk-6LMKQM7I.js.map → chunk-EEVAPRPD.js.map} +0 -0
- /package/dist/{chunk-PQRP4NSF.js.map → chunk-N65ZXWNQ.js.map} +0 -0
- /package/dist/{chunk-UDX7TSOC.js.map → chunk-NCXRSQNE.js.map} +0 -0
- /package/dist/{chunk-GOQBON77.js.map → chunk-NUPXPKIQ.js.map} +0 -0
- /package/dist/{chunk-TGD7UFHD.js.map → chunk-UZHT3LBF.js.map} +0 -0
- /package/dist/{chunk-PIMB2YNO.js.map → chunk-VCRBR72A.js.map} +0 -0
- /package/dist/{chunk-C75WE52C.js.map → chunk-VNOQTDTE.js.map} +0 -0
- /package/dist/{chunks-JD25E5PM.js.map → chunks-PYYQURNO.js.map} +0 -0
- /package/dist/{formula-FNQYHMIY.js.map → formula-IPLFHEBD.js.map} +0 -0
- /package/dist/{formula-5775PTP2.js.map → formula-U4BTEZ6I.js.map} +0 -0
- /package/dist/{image-ocr-PYZU4TO7.js.map → image-ocr-3IJ25PS4.js.map} +0 -0
- /package/dist/{image-ocr-YA2MQBRP.js.map → image-ocr-GTDG4SAJ.js.map} +0 -0
- /package/dist/{model-bundle-O6J7ORVC.js.map → model-bundle-PVWIAVEX.js.map} +0 -0
- /package/dist/{models-YMOKZGCI.js.map → models-VCRF3JMT.js.map} +0 -0
- /package/dist/{pdf-ocr-2TTO5PX2.js.map → pdf-ocr-SYLMM727.js.map} +0 -0
- /package/dist/{pdf-ocr-WF2NQA6X.js.map → pdf-ocr-YMQZDJBO.js.map} +0 -0
- /package/dist/{profile-io-ZTV2MKRT.js.map → profile-io-YD54CDXY.js.map} +0 -0
- /package/dist/{rasterize-MMUZ537K.js.map → rasterize-3THYOOUN.js.map} +0 -0
- /package/dist/{redact-doc-S42XGV5D.js.map → redact-doc-65L2YJN6.js.map} +0 -0
- /package/dist/{render-WCWPZKJ7.js.map → render-YU7QJVZ7.js.map} +0 -0
- /package/dist/{seal-WHK4GOHR.js.map → seal-6CQGFTML.js.map} +0 -0
- /package/dist/{setup-D35LKZCH.js.map → setup-CFHKXDY4.js.map} +0 -0
- /package/dist/{visual-WL4TDE5B.js.map → visual-ED2H75Q5.js.map} +0 -0
- /package/dist/{watch-AX64CIMN.js.map → watch-D6FCAYKJ.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
extractPageBlocksWithLines
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7FKPPIDZ.js";
|
|
5
5
|
import {
|
|
6
6
|
OCR_DET_MODEL,
|
|
7
7
|
OCR_REC_DICT,
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
ensureOcrModels,
|
|
10
10
|
getOcrModelsDir,
|
|
11
11
|
parseCharacterDict
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BIHS4VWR.js";
|
|
13
13
|
import {
|
|
14
14
|
OPTIONAL_DEP_INSTALL_HINT
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-3VLVNKJV.js";
|
|
16
16
|
|
|
17
17
|
// src/ocr/ruling-lines.ts
|
|
18
18
|
var INK_LUMA_MAX = 205;
|
|
@@ -188,19 +188,19 @@ function solidRuns(mask, base, step, span, minLenPx) {
|
|
|
188
188
|
function dottedRuns(mask, base, step, span, minLenPx, maxGap, maxDash, maxPeriod) {
|
|
189
189
|
const runs = [];
|
|
190
190
|
const chain = [];
|
|
191
|
-
const
|
|
191
|
+
const median2 = (xs) => xs.sort((a, b) => a - b)[xs.length >> 1];
|
|
192
192
|
const flush = () => {
|
|
193
193
|
const cnt = chain.length >> 1;
|
|
194
194
|
if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {
|
|
195
|
-
const periods = [], dashes = [],
|
|
195
|
+
const periods = [], dashes = [], gaps2 = [];
|
|
196
196
|
for (let k = 0; k < cnt; k++) {
|
|
197
197
|
dashes.push(chain[2 * k + 1] - chain[2 * k] + 1);
|
|
198
198
|
if (k) {
|
|
199
199
|
periods.push(chain[2 * k] - chain[2 * k - 2]);
|
|
200
|
-
|
|
200
|
+
gaps2.push(chain[2 * k] - chain[2 * k - 1] - 1);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
if (
|
|
203
|
+
if (median2(periods) <= maxPeriod || median2(dashes) >= 1.5 * median2(gaps2)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] });
|
|
204
204
|
}
|
|
205
205
|
chain.length = 0;
|
|
206
206
|
};
|
|
@@ -573,12 +573,614 @@ function inkBounds(gray, w, h, ink) {
|
|
|
573
573
|
if (x1 < x0 || y1 < y0) return { x0: 0, y0: 0, x1: w, y1: h };
|
|
574
574
|
return { x0, y0, x1: x1 + 1, y1: y1 + 1 };
|
|
575
575
|
}
|
|
576
|
+
function bracketFeatures(gray, w, h, ink, cx) {
|
|
577
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
578
|
+
let charH = 0;
|
|
579
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
580
|
+
if (charH < 8) return null;
|
|
581
|
+
const tol = Math.max(2, charH * 0.3);
|
|
582
|
+
let best = null, bestD = Infinity;
|
|
583
|
+
for (const c of comps) {
|
|
584
|
+
if (c.y1 - c.y0 < charH * 0.3 || c.x1 < cx - tol || c.x0 > cx + tol) continue;
|
|
585
|
+
const d = Math.abs((c.x0 + c.x1) / 2 - cx);
|
|
586
|
+
if (d < bestD) {
|
|
587
|
+
bestD = d;
|
|
588
|
+
best = c;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (!best) return null;
|
|
592
|
+
const b = best;
|
|
593
|
+
const cw = b.x1 - b.x0, ch = b.y1 - b.y0;
|
|
594
|
+
if (cw < 3 || ch < 8) return null;
|
|
595
|
+
const spans = [];
|
|
596
|
+
for (let y = b.y0; y < b.y1; y++) {
|
|
597
|
+
let n = 0;
|
|
598
|
+
for (let x = b.x0; x < b.x1; x++) if (label[y * w + x] === b.id) n++;
|
|
599
|
+
spans.push(n);
|
|
600
|
+
}
|
|
601
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
602
|
+
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
603
|
+
const midRows = spans.slice(Math.floor(ch * 0.3), Math.ceil(ch * 0.7)).sort((p, q) => p - q);
|
|
604
|
+
const stroke = midRows[midRows.length >> 1] ?? 0;
|
|
605
|
+
const taper = spans.filter((n) => n > stroke && n < cw).length / ch;
|
|
606
|
+
const fill = spans.reduce((a, n) => a + n, 0) / (cw * ch);
|
|
607
|
+
return { top, bottom, taper, fill, cw, ch, charH };
|
|
608
|
+
}
|
|
609
|
+
function romanStems(gray, w, h, ink, prevX, nextX) {
|
|
610
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
611
|
+
let charH = 0;
|
|
612
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
613
|
+
if (charH < 10) return null;
|
|
614
|
+
const x0 = prevX + charH * 0.35, x1 = nextX - charH * 0.35;
|
|
615
|
+
const sel = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6 && (c.x0 + c.x1) / 2 >= x0 && (c.x0 + c.x1) / 2 < x1);
|
|
616
|
+
if (!sel.length) return null;
|
|
617
|
+
const ids = new Set(sel.map((c) => c.id));
|
|
618
|
+
const ux0 = Math.min(...sel.map((c) => c.x0)), ux1 = Math.max(...sel.map((c) => c.x1));
|
|
619
|
+
const uy0 = Math.min(...sel.map((c) => c.y0)), uy1 = Math.max(...sel.map((c) => c.y1));
|
|
620
|
+
const uh = uy1 - uy0;
|
|
621
|
+
const counts = /* @__PURE__ */ new Map();
|
|
622
|
+
let rows = 0;
|
|
623
|
+
for (let y = uy0 + Math.floor(uh * 0.25); y < uy1 - Math.floor(uh * 0.25); y++) {
|
|
624
|
+
let runs = 0, len = 0, thick = false;
|
|
625
|
+
for (let x = ux0; x <= ux1; x++) {
|
|
626
|
+
const on = x < ux1 && ids.has(label[y * w + x]);
|
|
627
|
+
if (on) len++;
|
|
628
|
+
else if (len) {
|
|
629
|
+
runs++;
|
|
630
|
+
if (len > uh * 0.35) thick = true;
|
|
631
|
+
len = 0;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
rows++;
|
|
635
|
+
if (!thick) counts.set(runs, (counts.get(runs) ?? 0) + 1);
|
|
636
|
+
}
|
|
637
|
+
const [n, k] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [0, 0];
|
|
638
|
+
return n >= 1 && n <= 3 && k >= rows * 0.7 ? n : null;
|
|
639
|
+
}
|
|
640
|
+
function circledAt(gray, w, h, ink, xs) {
|
|
641
|
+
const { comps } = components(gray, w, h, ink);
|
|
642
|
+
let charH = 0;
|
|
643
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
644
|
+
const rings = charH < 10 ? [] : comps.filter((r) => {
|
|
645
|
+
const rw = r.x1 - r.x0, rh = r.y1 - r.y0;
|
|
646
|
+
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);
|
|
647
|
+
});
|
|
648
|
+
return xs.map((x) => rings.some((r) => x >= r.x0 && x <= r.x1));
|
|
649
|
+
}
|
|
650
|
+
function quoteHead(gray, w, h, ink, cx) {
|
|
651
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
652
|
+
let charH = 0;
|
|
653
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
654
|
+
if (charH < 10) return null;
|
|
655
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
656
|
+
if (!tall.length) return null;
|
|
657
|
+
const mid = (Math.min(...tall.map((c) => c.y0)) + Math.max(...tall.map((c) => c.y1))) / 2;
|
|
658
|
+
let best = null, bestD = charH * 0.4;
|
|
659
|
+
for (const c of comps) {
|
|
660
|
+
const ch = c.y1 - c.y0, d = Math.abs((c.x0 + c.x1) / 2 - cx);
|
|
661
|
+
if (ch < charH * 0.12 || ch > charH * 0.5 || (c.y0 + c.y1) / 2 > mid || d > bestD) continue;
|
|
662
|
+
bestD = d;
|
|
663
|
+
best = c;
|
|
664
|
+
}
|
|
665
|
+
if (!best) return null;
|
|
666
|
+
let n = 0, sy = 0;
|
|
667
|
+
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) {
|
|
668
|
+
n++;
|
|
669
|
+
sy += y;
|
|
670
|
+
}
|
|
671
|
+
return n ? (sy / n - best.y0) / (best.y1 - best.y0) : null;
|
|
672
|
+
}
|
|
673
|
+
function edgeTrim(gray, w, h, ink) {
|
|
674
|
+
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
675
|
+
const colInk = new Uint32Array(w);
|
|
676
|
+
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (isInk(gray[y * w + x])) colInk[x]++;
|
|
677
|
+
const ruleCol = new Uint8Array(w);
|
|
678
|
+
let liveW = 0;
|
|
679
|
+
for (let x = 0; x < w; x++) {
|
|
680
|
+
if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
|
|
681
|
+
else liveW++;
|
|
682
|
+
}
|
|
683
|
+
const blank = Math.max(1, Math.floor(w * 0.02));
|
|
684
|
+
const bands = [];
|
|
685
|
+
let start = -1;
|
|
686
|
+
for (let y = 0; y <= h; y++) {
|
|
687
|
+
let n = 0;
|
|
688
|
+
if (y < h) {
|
|
689
|
+
for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[y * w + x])) n++;
|
|
690
|
+
}
|
|
691
|
+
const on = y < h && n > blank && n < liveW * 0.85;
|
|
692
|
+
if (on && start < 0) start = y;
|
|
693
|
+
else if (!on && start >= 0) {
|
|
694
|
+
bands.push([start, y]);
|
|
695
|
+
start = -1;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
let y0 = 0, y1 = h;
|
|
699
|
+
if (bands.length >= 2 && h >= 24) {
|
|
700
|
+
const mh = Math.max(...bands.map(([a, b]) => b - a));
|
|
701
|
+
const [top, next] = [bands[0], bands[1]], [bot, prev] = [bands[bands.length - 1], bands[bands.length - 2]];
|
|
702
|
+
if (top[0] <= 1 && top[1] - top[0] < mh * 0.45) y0 = top[1] + next[0] >> 1;
|
|
703
|
+
if (bot[1] >= h - 1 && bot[1] - bot[0] < mh * 0.45) y1 = bot[0] + prev[1] + 1 >> 1;
|
|
704
|
+
if (y0 > 0 || y1 < h) {
|
|
705
|
+
let runs = 0, len = 0;
|
|
706
|
+
for (let x = 0; x <= w; x++) {
|
|
707
|
+
let on = false;
|
|
708
|
+
if (x < w && !ruleCol[x]) for (let y = y0; y < y1 && !on; y++) on = isInk(gray[y * w + x]);
|
|
709
|
+
if (on) len++;
|
|
710
|
+
else {
|
|
711
|
+
if (len >= (y1 - y0) * 0.3) runs++;
|
|
712
|
+
len = 0;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (runs < 2) {
|
|
716
|
+
y0 = 0;
|
|
717
|
+
y1 = h;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const textCol = (x) => {
|
|
722
|
+
for (let y = y0; y < y1; y++) if (isInk(gray[y * w + x])) return true;
|
|
723
|
+
return false;
|
|
724
|
+
};
|
|
725
|
+
const border = (x) => colInk[x] >= h * 0.95;
|
|
726
|
+
const gap = Math.max(2, Math.round(h * 0.1));
|
|
727
|
+
const clearFrom = (x, dir) => {
|
|
728
|
+
for (let k = 0; k < gap; k++) {
|
|
729
|
+
const c = x + dir * k;
|
|
730
|
+
if (c < 0 || c >= w || textCol(c)) return false;
|
|
731
|
+
}
|
|
732
|
+
return true;
|
|
733
|
+
};
|
|
734
|
+
let x0 = 0, x1 = w;
|
|
735
|
+
for (let x = 0; x < w * 0.2; x++) {
|
|
736
|
+
if (border(x)) {
|
|
737
|
+
let e = x;
|
|
738
|
+
while (e + 1 < w && border(e + 1)) e++;
|
|
739
|
+
if (clearFrom(e + 1, 1)) x0 = e + 1;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
if (textCol(x)) break;
|
|
743
|
+
}
|
|
744
|
+
for (let x = w - 1; x >= w * 0.8; x--) {
|
|
745
|
+
if (border(x)) {
|
|
746
|
+
let e = x;
|
|
747
|
+
while (e - 1 >= 0 && border(e - 1)) e--;
|
|
748
|
+
if (clearFrom(e - 1, -1)) x1 = e;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
if (textCol(x)) break;
|
|
752
|
+
}
|
|
753
|
+
return y0 > 0 || y1 < h || x0 > 0 || x1 < w ? { x0, x1, y0, y1 } : null;
|
|
754
|
+
}
|
|
755
|
+
function tallInkCount(gray, w, h, ink) {
|
|
756
|
+
const { comps } = components(gray, w, h, ink);
|
|
757
|
+
let charH = 0;
|
|
758
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
759
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.5).sort((a, b) => a.x0 - b.x0);
|
|
760
|
+
let n = 0, end = -1;
|
|
761
|
+
for (const c of tall) {
|
|
762
|
+
if (c.x0 >= end) n++;
|
|
763
|
+
end = Math.max(end, c.x1);
|
|
764
|
+
}
|
|
765
|
+
return n;
|
|
766
|
+
}
|
|
767
|
+
function gapGlyphs(gray, w, h, ink) {
|
|
768
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
769
|
+
let charH = 0;
|
|
770
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
771
|
+
if (charH < 10) return { glyphs: [], bandH: 0 };
|
|
772
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
773
|
+
const bandT = Math.min(...tall.map((c) => c.y0)), bandB = Math.max(...tall.map((c) => c.y1)), bh = bandB - bandT;
|
|
774
|
+
const inBand = (c) => c.y1 > bandT && c.y0 < bandB;
|
|
775
|
+
const out = [];
|
|
776
|
+
for (const c of comps) {
|
|
777
|
+
if (!inBand(c)) continue;
|
|
778
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
779
|
+
if (ch >= bh * 0.9 && cw <= bh * 0.15) continue;
|
|
780
|
+
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));
|
|
781
|
+
const apart = !others.some((o) => o.x0 < c.x1 + 1 && o.x1 > c.x0 - 1);
|
|
782
|
+
const nearlyApart = !others.some((o) => Math.min(o.x1, c.x1) - Math.max(o.x0, c.x0) > 2);
|
|
783
|
+
const spans = [], runs = [];
|
|
784
|
+
let n = 0;
|
|
785
|
+
for (let y = c.y0; y < c.y1; y++) {
|
|
786
|
+
let s = 0, r = 0, prev = false;
|
|
787
|
+
for (let x = c.x0; x < c.x1; x++) {
|
|
788
|
+
const on = label[y * w + x] === c.id;
|
|
789
|
+
if (on) {
|
|
790
|
+
s++;
|
|
791
|
+
if (!prev) r++;
|
|
792
|
+
}
|
|
793
|
+
prev = on;
|
|
794
|
+
}
|
|
795
|
+
spans.push(s);
|
|
796
|
+
runs.push(r);
|
|
797
|
+
n += s;
|
|
798
|
+
}
|
|
799
|
+
const fill = n / (cw * ch), cy = ((c.y0 + c.y1) / 2 - bandT) / bh;
|
|
800
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
801
|
+
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
802
|
+
let mark = null;
|
|
803
|
+
const speck = Math.max(3, bh * 0.08);
|
|
804
|
+
const dot = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= speck && cw >= speck && cw >= ch * 0.6 && cw <= ch * 1.6;
|
|
805
|
+
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;
|
|
806
|
+
if (!apart && !(nearlyApart && ch >= bh * 0.6)) {
|
|
807
|
+
if (small) out.push({ x0: c.x0, x1: c.x1, mark: "\xB7" });
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (!apart) {
|
|
811
|
+
} else if (dot && fill >= 0.6 && cy >= 0.3 && cy <= 0.7) mark = "\xB7";
|
|
812
|
+
else if (small) mark = "\xB7";
|
|
813
|
+
else if (dot && fill >= 0.6 && cy >= 0.75 && cw >= ch * 0.8) mark = ".";
|
|
814
|
+
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";
|
|
815
|
+
else if (ch <= bh * 0.15 && cw >= ch * 2 && cw <= bh * 0.8 && fill >= 0.7 && cy >= 0.35 && cy <= 0.75) mark = "-";
|
|
816
|
+
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";
|
|
817
|
+
else if (ch >= bh * 0.3 && ch <= bh * 0.65 && fill < 0.5) {
|
|
818
|
+
const stem = (from, to) => {
|
|
819
|
+
let sx = 0, k = 0;
|
|
820
|
+
for (let y = from; y < to; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
821
|
+
sx += x - c.x0;
|
|
822
|
+
k++;
|
|
823
|
+
}
|
|
824
|
+
return k ? sx / k / cw : 0.5;
|
|
825
|
+
};
|
|
826
|
+
if (c.y0 <= bandT + bh * 0.15 && top >= 0.7 && bottom <= 0.5 && stem(c.y0 + band, c.y1) <= 0.35) mark = "\u300C";
|
|
827
|
+
else if (c.y1 >= bandB - bh * 0.15 && bottom >= 0.7 && top <= 0.5 && stem(c.y0, c.y1 - band) >= 0.65) mark = "\u300D";
|
|
828
|
+
} 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";
|
|
829
|
+
if (!mark && ch >= bh * 0.6 && cw >= ch * 0.25 && cw <= ch * 0.7 && fill <= 0.4 && runs.every((r) => r <= 1)) {
|
|
830
|
+
const mx = (y) => {
|
|
831
|
+
let sx = 0, k = 0;
|
|
832
|
+
for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
833
|
+
sx += x;
|
|
834
|
+
k++;
|
|
835
|
+
}
|
|
836
|
+
return k ? sx / k : NaN;
|
|
837
|
+
};
|
|
838
|
+
if (mx(c.y0 + band) - mx(c.y1 - 1 - band) >= cw * 0.5) mark = "/";
|
|
839
|
+
}
|
|
840
|
+
if (mark) out.push({ x0: c.x0, x1: c.x1, mark });
|
|
841
|
+
}
|
|
842
|
+
return { glyphs: out, bandH: bh };
|
|
843
|
+
}
|
|
844
|
+
function ringBullet(gray, w, h, ink, cx) {
|
|
845
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
846
|
+
let charH = 0;
|
|
847
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
848
|
+
if (charH < 10) return null;
|
|
849
|
+
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
850
|
+
const bandT = Math.min(...tall.map((c2) => c2.y0)), bh = Math.max(...tall.map((c2) => c2.y1)) - bandT;
|
|
851
|
+
let best = null, bestD = bh * 0.6;
|
|
852
|
+
for (const c2 of comps) {
|
|
853
|
+
const d = Math.abs((c2.x0 + c2.x1) / 2 - cx);
|
|
854
|
+
if (c2.y1 - c2.y0 >= bh * 0.3 && d < bestD) {
|
|
855
|
+
bestD = d;
|
|
856
|
+
best = c2;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (!best) return null;
|
|
860
|
+
const c = best, cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
861
|
+
if (cw < ch * 0.85 || cw > ch * 1.2) return null;
|
|
862
|
+
const runs = (xs, at) => {
|
|
863
|
+
let n2 = 0, len = 0, mx2 = 0, mn = Infinity;
|
|
864
|
+
for (const k of [...xs, -1]) {
|
|
865
|
+
if (k >= 0 && at(k)) {
|
|
866
|
+
if (!len) n2++;
|
|
867
|
+
len++;
|
|
868
|
+
mx2 = Math.max(mx2, len);
|
|
869
|
+
} else {
|
|
870
|
+
if (len) mn = Math.min(mn, len);
|
|
871
|
+
len = 0;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return { n: n2, mx: mx2, mn };
|
|
875
|
+
};
|
|
876
|
+
const my = c.y0 + c.y1 >> 1, mx = c.x0 + c.x1 >> 1;
|
|
877
|
+
const row = runs(Array.from({ length: cw }, (_, k) => c.x0 + k), (x) => label[my * w + x] === c.id);
|
|
878
|
+
const col = runs(Array.from({ length: ch }, (_, k) => c.y0 + k), (y) => label[y * w + mx] === c.id);
|
|
879
|
+
if (row.n !== 2 || col.n !== 2) return null;
|
|
880
|
+
let n = 0;
|
|
881
|
+
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++;
|
|
882
|
+
const fill = n / (cw * ch), rel = ch / bh;
|
|
883
|
+
if (rel >= 0.62 && (fill <= 0.23 ? row.mx <= cw * 0.1 : fill <= 0.33 && row.mn <= cw * 0.08)) return "\u25CB";
|
|
884
|
+
if (rel <= 0.58 && fill >= 0.24) return "\u3147";
|
|
885
|
+
if (rel >= 0.6 && rel <= 0.8 && fill >= 0.3 && ((c.y0 + c.y1) / 2 - bandT) / bh >= 0.55) return "o";
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
function discAt(gray, w, h, ink, cx) {
|
|
889
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
890
|
+
let charH = 0;
|
|
891
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
892
|
+
if (charH < 10) return false;
|
|
893
|
+
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
894
|
+
const bh = Math.max(...tall.map((c2) => c2.y1)) - Math.min(...tall.map((c2) => c2.y0));
|
|
895
|
+
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];
|
|
896
|
+
if (!c) return false;
|
|
897
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
898
|
+
if (ch < bh * 0.45 || ch > bh * 1.05 || cw < ch * 0.8 || cw > ch * 1.25) return false;
|
|
899
|
+
let n = 0;
|
|
900
|
+
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++;
|
|
901
|
+
const fill = n / (cw * ch);
|
|
902
|
+
return fill >= 0.65 && fill <= 0.9;
|
|
903
|
+
}
|
|
904
|
+
function serifOne(gray, w, h, ink, cx) {
|
|
905
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
906
|
+
let charH = 0;
|
|
907
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
908
|
+
if (charH < 10) return false;
|
|
909
|
+
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];
|
|
910
|
+
if (!c) return false;
|
|
911
|
+
const ch = c.y1 - c.y0;
|
|
912
|
+
const span = (a, b) => {
|
|
913
|
+
let mn = Infinity, mx = -1;
|
|
914
|
+
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) {
|
|
915
|
+
mn = Math.min(mn, x);
|
|
916
|
+
mx = Math.max(mx, x);
|
|
917
|
+
}
|
|
918
|
+
return [mn, mx];
|
|
919
|
+
};
|
|
920
|
+
const [m0, m1] = span(0.4, 0.6), [t0, t1] = span(0, 0.2), [b0, b1] = span(0.8, 1);
|
|
921
|
+
const sw = m1 - m0 + 1;
|
|
922
|
+
const even = (l, r) => l >= sw * 0.4 && r >= sw * 0.4 && Math.abs(l - r) <= Math.max(l, r) * 0.35;
|
|
923
|
+
return sw >= 2 && sw <= ch * 0.3 && even(m0 - t0, t1 - m1) && even(m0 - b0, b1 - m1);
|
|
924
|
+
}
|
|
925
|
+
function starRun(gray, w, h, ink, cx) {
|
|
926
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
927
|
+
let charH = 0;
|
|
928
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
929
|
+
if (charH < 10) return 0;
|
|
930
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
931
|
+
const bandT = Math.min(...tall.map((c) => c.y0)), bh = Math.max(...tall.map((c) => c.y1)) - bandT;
|
|
932
|
+
const star = (c) => {
|
|
933
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
934
|
+
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;
|
|
935
|
+
let n = 0;
|
|
936
|
+
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++;
|
|
937
|
+
const fill = n / (cw * ch);
|
|
938
|
+
return fill >= 0.25 && fill <= 0.65;
|
|
939
|
+
};
|
|
940
|
+
const stars2 = comps.filter(star).sort((a, b) => a.x0 - b.x0);
|
|
941
|
+
const k = stars2.findIndex((c) => cx >= c.x0 - (c.x1 - c.x0) && cx <= c.x1 + (c.x1 - c.x0));
|
|
942
|
+
if (k < 0) return 0;
|
|
943
|
+
let lo = k, hi = k;
|
|
944
|
+
while (lo > 0 && stars2[lo].x0 - stars2[lo - 1].x1 <= stars2[lo].x1 - stars2[lo].x0) lo--;
|
|
945
|
+
while (hi + 1 < stars2.length && stars2[hi + 1].x0 - stars2[hi].x1 <= stars2[hi].x1 - stars2[hi].x0) hi++;
|
|
946
|
+
return hi - lo + 1;
|
|
947
|
+
}
|
|
948
|
+
var BRACKET_TAPER = 0.16;
|
|
949
|
+
var BRACKET_SOLID = 0.4;
|
|
950
|
+
var BRACKET_WIDE = 0.265;
|
|
951
|
+
var BRACKET_LENS = 0.35;
|
|
952
|
+
function bracketShape(f, close) {
|
|
953
|
+
const [near, far] = close ? [f.bottom, f.top] : [f.top, f.bottom];
|
|
954
|
+
const paren = close ? ")" : "(";
|
|
955
|
+
if (near >= 0.7 && far <= 0.5) return f.taper < BRACKET_TAPER ? close ? "\u300D" : "\u300C" : paren;
|
|
956
|
+
if (near >= 0.7 && far >= 0.7 && f.taper >= BRACKET_TAPER) {
|
|
957
|
+
return f.fill >= BRACKET_SOLID && (f.cw >= f.ch * BRACKET_WIDE || f.taper >= BRACKET_LENS) ? close ? "\u3011" : "\u3010" : paren;
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
function leadingBullet(gray, w, h, ink, firstX) {
|
|
962
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
963
|
+
let charH = 0;
|
|
964
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
965
|
+
if (charH < 10) return null;
|
|
966
|
+
const cand = comps.filter((c) => c.y1 - c.y0 >= charH * 0.15).sort((a, b) => a.x0 - b.x0);
|
|
967
|
+
const first = cand[0];
|
|
968
|
+
if (!first) return null;
|
|
969
|
+
const cl = cand.filter((c) => c.x0 < first.x1 && c.x1 > first.x0);
|
|
970
|
+
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));
|
|
971
|
+
const cw = x1 - x0, ch = y1 - y0;
|
|
972
|
+
if (cw < ch * 0.8 || cw > ch * 1.25) return null;
|
|
973
|
+
const next = cand.find((c) => c.x0 >= x1);
|
|
974
|
+
if (next && next.x0 - x1 < ch * 0.3) return null;
|
|
975
|
+
let inkN = 0;
|
|
976
|
+
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++;
|
|
977
|
+
const fill = inkN / (cw * ch);
|
|
978
|
+
const rel = ch / charH;
|
|
979
|
+
const ring2 = (c) => {
|
|
980
|
+
const a = c.x1 - c.x0, b = c.y1 - c.y0;
|
|
981
|
+
return a >= b * 0.75 && a <= b * 1.33;
|
|
982
|
+
};
|
|
983
|
+
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));
|
|
984
|
+
const span = (y) => {
|
|
985
|
+
let n = 0;
|
|
986
|
+
for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) n++;
|
|
987
|
+
return n;
|
|
988
|
+
};
|
|
989
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
990
|
+
const edgeFull = (from, dir) => {
|
|
991
|
+
let m = 0;
|
|
992
|
+
for (let k = 0; k < band; k++) m = Math.max(m, span(from + dir * k));
|
|
993
|
+
return m >= cw * 0.8;
|
|
994
|
+
};
|
|
995
|
+
let midRuns = 0;
|
|
996
|
+
{
|
|
997
|
+
let prev = false;
|
|
998
|
+
const my = y0 + (ch >> 1);
|
|
999
|
+
for (let x = x0; x < x1; x++) {
|
|
1000
|
+
const on = cl.some((c) => label[my * w + x] === c.id);
|
|
1001
|
+
if (on && !prev) midRuns++;
|
|
1002
|
+
prev = on;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const hollowSquare = cl.length === 1 && fill < 0.5 && midRuns === 2 && edgeFull(y0, 1) && edgeFull(y1 - 1, -1);
|
|
1006
|
+
let mark = null;
|
|
1007
|
+
if (hollowSquare && rel >= 0.6 && rel <= 0.92) mark = "\u25A1";
|
|
1008
|
+
else if (nested && fill < 0.45 && rel >= 0.6) mark = "\u25CE";
|
|
1009
|
+
else if (cl.length === 1 && fill >= 0.9 && rel >= 0.28 && rel <= 0.6) mark = "\u25AA";
|
|
1010
|
+
else if (cl.length === 1 && fill >= 0.65 && fill <= 0.88 && rel >= 0.5 && rel <= 0.8) mark = "\u25CF";
|
|
1011
|
+
if (!mark) return null;
|
|
1012
|
+
return { mark, covers: firstX <= x1 + ch * 0.3 };
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/ocr/glyph-restore.ts
|
|
1016
|
+
function restoreGlyphs(rgba, pageW, box, text, steps, stepPx, note = {}) {
|
|
1017
|
+
const chars = [...text];
|
|
1018
|
+
if (chars.length !== steps.length) return text;
|
|
1019
|
+
const gray = grayCrop(rgba, pageW, box);
|
|
1020
|
+
const c = { chars, cx: (i) => (steps[i] + 0.5) * stepPx, gray, w: box.w, h: box.h, ink: inkStats(gray) };
|
|
1021
|
+
if (/[[\]]/.test(text)) brackets(c);
|
|
1022
|
+
if (/['"]/.test(text)) quotes(c);
|
|
1023
|
+
if (/(^|\D)[1-9](\D|$)/.test(text)) circled(c);
|
|
1024
|
+
if (text.includes("*")) stars(c);
|
|
1025
|
+
if (/[\u00b7\u2022]/.test(text)) discs(c);
|
|
1026
|
+
gaps(c);
|
|
1027
|
+
note.ringLead = ring(c);
|
|
1028
|
+
if (/[I1Ⅰ-Ⅲ]|^\s*\./.test(text)) roman(c);
|
|
1029
|
+
return chars.join("");
|
|
1030
|
+
}
|
|
1031
|
+
function brackets({ chars, cx, gray, w, h, ink }) {
|
|
1032
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1033
|
+
if (chars[i] !== "[" && chars[i] !== "]") continue;
|
|
1034
|
+
const f = bracketFeatures(gray, w, h, ink, cx(i));
|
|
1035
|
+
const shape = f && bracketShape(f, chars[i] === "]");
|
|
1036
|
+
if (shape) chars[i] = shape;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function quotes({ chars, cx, gray, w, h, ink }) {
|
|
1040
|
+
const year = (i) => chars[i] === "'" && !/[\p{L}\p{N}]/u.test(chars[i - 1] ?? "") && /^\d{2}(?!\d)/.test(chars.slice(i + 1, i + 4).join(""));
|
|
1041
|
+
const single = { "'": chars.filter((c, i) => c === "'" && !year(i)).length === 1, '"': chars.filter((c) => c === '"').length === 1 };
|
|
1042
|
+
const open = { "'": false, '"': false };
|
|
1043
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1044
|
+
const c = chars[i];
|
|
1045
|
+
const kind = c === "'" || c === "\u2018" || c === "\u2019" ? "'" : c === '"' || c === "\u201C" || c === "\u201D" ? '"' : null;
|
|
1046
|
+
if (!kind) continue;
|
|
1047
|
+
if (c !== kind) {
|
|
1048
|
+
open[kind] = c === "\u2018" || c === "\u201C";
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (open[kind]) {
|
|
1052
|
+
open[kind] = false;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (!year(i) && !single[kind]) continue;
|
|
1056
|
+
const r = quoteHead(gray, w, h, ink, cx(i));
|
|
1057
|
+
if (r !== null && r >= 0.47) {
|
|
1058
|
+
chars[i] = kind === "'" ? "\u2018" : "\u201C";
|
|
1059
|
+
open[kind] = true;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function circled({ chars, cx, gray, w, h, ink }) {
|
|
1064
|
+
const at = chars.flatMap((c, i) => /^[1-9]$/.test(c) && !/\d/.test(chars[i - 1] ?? "") && !/\d/.test(chars[i + 1] ?? "") ? [i] : []);
|
|
1065
|
+
if (!at.length) return;
|
|
1066
|
+
const hit = circledAt(gray, w, h, ink, at.map(cx));
|
|
1067
|
+
at.forEach((i, k) => {
|
|
1068
|
+
if (hit[k]) chars[i] = String.fromCharCode(9311 + Number(chars[i]));
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
function stars({ chars, cx, gray, w, h, ink }) {
|
|
1072
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1073
|
+
if (chars[i] !== "*" || chars[i - 1] === "*") continue;
|
|
1074
|
+
let e = i;
|
|
1075
|
+
while (chars[e + 1] === "*") e++;
|
|
1076
|
+
const n = starRun(gray, w, h, ink, cx(i));
|
|
1077
|
+
if (n > e - i + 1 && n <= 4) chars[e] += "*".repeat(n - (e - i + 1));
|
|
1078
|
+
i = e;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function discs({ chars, cx, gray, w, h, ink }) {
|
|
1082
|
+
for (let i = 0; i < chars.length; i++) if (/^[\u00b7\u2022]$/.test(chars[i]) && discAt(gray, w, h, ink, cx(i))) chars[i] = "\u25CF";
|
|
1083
|
+
}
|
|
1084
|
+
function gaps({ chars, cx, gray, w, h, ink }) {
|
|
1085
|
+
const { glyphs, bandH } = gapGlyphs(gray, w, h, ink);
|
|
1086
|
+
if (!glyphs.length) return;
|
|
1087
|
+
const live = chars.map((c, i) => i).filter((i) => chars[i].trim());
|
|
1088
|
+
if (!live.length) return;
|
|
1089
|
+
const d = [];
|
|
1090
|
+
for (let k = 0; k + 1 < live.length; k++) if (/[가-힣]$/.test(chars[live[k]]) && /^[가-힣]/.test(chars[live[k + 1]])) d.push(cx(live[k + 1]) - cx(live[k]));
|
|
1091
|
+
const pitch = d.length >= 2 ? d.sort((a, b) => a - b)[d.length >> 1] : bandH;
|
|
1092
|
+
const nearTo = (g) => (i) => Math.abs(cx(i) - (g.x0 + g.x1) / 2) <= Math.max(pitch * 0.45, (g.x1 - g.x0) / 2 + 1);
|
|
1093
|
+
const loose = glyphs.filter((g) => /^[\u00b7.]/.test(g.mark) && !live.some(nearTo(g)));
|
|
1094
|
+
for (const g of glyphs.sort((a, b) => a.x0 - b.x0)) {
|
|
1095
|
+
const mid = (g.x0 + g.x1) / 2;
|
|
1096
|
+
const near = nearTo(g);
|
|
1097
|
+
const cover = live.filter(near);
|
|
1098
|
+
if (g.mark === "\xB7") {
|
|
1099
|
+
const sp = chars.findIndex((c, i) => c === " " && near(i));
|
|
1100
|
+
const p = sp >= 0 ? chars.slice(0, sp).reverse().find((c) => c.trim()) : void 0, q = sp >= 0 ? chars.slice(sp + 1).find((c) => c.trim()) : void 0;
|
|
1101
|
+
if (p && q && /[가-힣]$/.test(p) && /^[가-힣]/.test(q) && !loose.some((o) => o !== g && Math.abs((o.x0 + o.x1) / 2 - mid) <= pitch * 1.5)) chars[sp] = "\xB7";
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
if (cover.length) {
|
|
1105
|
+
if (g.mark.startsWith("\u25A1") && cover.length === 1 && /^[0Oo\u25cb]$/.test(chars[cover[0]])) chars[cover[0]] = "\u25A1";
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
if (g.mark === "\u25A1\0") continue;
|
|
1109
|
+
const before = live.filter((i) => cx(i) < mid), after = live.filter((i) => cx(i) > mid);
|
|
1110
|
+
if (!before.length) {
|
|
1111
|
+
if (after.length && /^[\u3002\u00b7\u300c\u25b2]$/.test(g.mark) && /^[가-힣]/.test(chars[after[0]])) chars[after[0]] = g.mark + chars[after[0]];
|
|
1112
|
+
else if (g.mark === "-" && /^[\dⅰ-ⅹivx]+\s*-$/.test(chars.join("").trim())) chars[after[0]] = "-" + chars[after[0]];
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (!after.length && g.mark !== ".") continue;
|
|
1116
|
+
if (g.mark === "/" && !(/\d$/.test(chars[before[before.length - 1]]) && /^\d/.test(chars[after[0]]))) continue;
|
|
1117
|
+
const prev = chars[before[before.length - 1]], next = after.length ? chars[after[0]] : "";
|
|
1118
|
+
if (g.mark === "\xB7" || g.mark === ".") {
|
|
1119
|
+
if (loose.some((o) => o !== g && Math.abs((o.x0 + o.x1) / 2 - mid) <= pitch * 1.5)) continue;
|
|
1120
|
+
if (/[\u2026.\u00b7]$/.test(prev) || /^[\u2026.\u00b7]/.test(next)) continue;
|
|
1121
|
+
}
|
|
1122
|
+
if (g.mark === "\xB7") {
|
|
1123
|
+
if (/^\d/.test(next)) continue;
|
|
1124
|
+
const sp = chars.findIndex((c, i) => c === " " && near(i));
|
|
1125
|
+
if (sp >= 0) {
|
|
1126
|
+
chars[sp] = g.mark;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
chars[before[before.length - 1]] += g.mark;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function ring({ chars, cx, gray, w, h, ink }) {
|
|
1134
|
+
const k = chars.findIndex((c) => c.trim());
|
|
1135
|
+
if (k < 0 || !/^[\u3147Oo0\u25cb]$/.test(chars[k])) return false;
|
|
1136
|
+
const rest = chars.slice(k + 1).join("").trimStart();
|
|
1137
|
+
if (!/^[(\[「【<]?[가-힣]/.test(rest)) return false;
|
|
1138
|
+
const r = ringBullet(gray, w, h, ink, cx(k));
|
|
1139
|
+
if (r) chars[k] = r;
|
|
1140
|
+
return r !== null;
|
|
1141
|
+
}
|
|
1142
|
+
function roman({ chars, cx, gray, w, h, ink }) {
|
|
1143
|
+
const ROMAN = /^[IⅠ-Ⅲ]$/, LATIN = /[A-Za-z]$/;
|
|
1144
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1145
|
+
if (chars[i] !== "1" || /[\dA-Za-z]$/.test(chars[i - 1] ?? "") || /^[\dA-Za-z,]/.test(chars[i + 1] ?? "")) continue;
|
|
1146
|
+
if (serifOne(gray, w, h, ink, cx(i))) chars[i] = "\u2160";
|
|
1147
|
+
}
|
|
1148
|
+
const inRoman = (i) => ROMAN.test(chars[i]) || chars[i] === "1" && (ROMAN.test(chars[i - 1] ?? "") || ROMAN.test(chars[i + 1] ?? ""));
|
|
1149
|
+
const k = chars.findIndex((c) => c.trim());
|
|
1150
|
+
if (chars[k] === "." && chars.slice(k + 1).some((c) => /[가-힣A-Za-z0-9]/.test(c))) {
|
|
1151
|
+
const n = romanStems(gray, w, h, ink, -Infinity, cx(k));
|
|
1152
|
+
if (n) chars[k] = String.fromCharCode(8543 + n) + chars[k];
|
|
1153
|
+
}
|
|
1154
|
+
for (let s = 0; s < chars.length; ) {
|
|
1155
|
+
if (!inRoman(s) || s > 0 && LATIN.test(chars[s - 1])) {
|
|
1156
|
+
s++;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
let e = s;
|
|
1160
|
+
while (e < chars.length && inRoman(e)) e++;
|
|
1161
|
+
const tok = chars.slice(s, e).join("");
|
|
1162
|
+
if (e < chars.length && /^[A-Za-z]/.test(chars[e])) {
|
|
1163
|
+
s = e;
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
let p = s - 1, q = e;
|
|
1167
|
+
while (p >= 0 && !chars[p].trim()) p--;
|
|
1168
|
+
while (q < chars.length && !chars[q].trim()) q++;
|
|
1169
|
+
const n = romanStems(gray, w, h, ink, p >= 0 ? cx(p) : -Infinity, q < chars.length ? cx(q) : Infinity);
|
|
1170
|
+
const canon = n ? [["\u2160", "I"], ["\u2161", "II"], ["\u2162", "III"]][n - 1] : null;
|
|
1171
|
+
if (canon && !canon.includes(tok)) {
|
|
1172
|
+
chars[s] = canon[0];
|
|
1173
|
+
for (let i = s + 1; i < e; i++) chars[i] = "";
|
|
1174
|
+
}
|
|
1175
|
+
s = e;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
576
1178
|
|
|
577
1179
|
// src/ocr/postprocess.ts
|
|
578
1180
|
var HANGUL_START = /^[\uac00-\ud7a3]/;
|
|
579
|
-
function restoreSymbols(text) {
|
|
1181
|
+
function restoreSymbols(text, ringDecided = false) {
|
|
580
1182
|
let s = text;
|
|
581
|
-
s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
|
|
1183
|
+
if (!ringDecided) s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
|
|
582
1184
|
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);
|
|
583
1185
|
s = s.replace(/[\u2206\u0394]/g, "\u25B3");
|
|
584
1186
|
s = s.replace(/\(cid:\d*\)/g, "");
|
|
@@ -698,7 +1300,7 @@ function bandBoxes(b, bands, pageH) {
|
|
|
698
1300
|
}
|
|
699
1301
|
return out;
|
|
700
1302
|
}
|
|
701
|
-
function lineCrop(rgba, pageW, box, rot) {
|
|
1303
|
+
function lineCrop(rgba, pageW, box, rot, keep) {
|
|
702
1304
|
const srcW = rot === 0 ? box.w : box.h;
|
|
703
1305
|
const srcH = rot === 0 ? box.h : box.w;
|
|
704
1306
|
const rw = Math.min(REC_MAX_WIDTH, Math.max(16, Math.round(srcW * REC_HEIGHT / srcH)));
|
|
@@ -733,6 +1335,10 @@ function lineCrop(rgba, pageW, box, rot) {
|
|
|
733
1335
|
const wx = sx - x0;
|
|
734
1336
|
const i00 = at(x0, y0), i01 = at(x1, y0), i10 = at(x0, y1), i11 = at(x1, y1);
|
|
735
1337
|
const o = (dy * rw + dx) * 3;
|
|
1338
|
+
if (keep && rot === 0 && (sx < keep.x0 || sx >= keep.x1 || sy < keep.y0 || sy >= keep.y1)) {
|
|
1339
|
+
rgb[o] = rgb[o + 1] = rgb[o + 2] = keep.bg;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
736
1342
|
for (let c = 0; c < 3; c++) {
|
|
737
1343
|
const top = rgba[i00 + c] * (1 - wx) + rgba[i01 + c] * wx;
|
|
738
1344
|
const bottom = rgba[i10 + c] * (1 - wx) + rgba[i11 + c] * wx;
|
|
@@ -766,6 +1372,7 @@ var DEFAULT_OCR_TUNING = Object.freeze({
|
|
|
766
1372
|
textScore: 0.5,
|
|
767
1373
|
recBatch: 1,
|
|
768
1374
|
splitTall: true,
|
|
1375
|
+
trimEdges: true,
|
|
769
1376
|
minInkContrast: 35,
|
|
770
1377
|
postprocess: true,
|
|
771
1378
|
tightBoxes: true,
|
|
@@ -777,6 +1384,7 @@ var REC_MIN_WIDTH = 320;
|
|
|
777
1384
|
var REC_BATCH_MAX_PIXELS = 48 * 16e3;
|
|
778
1385
|
var TALL_RATIO = 1.5;
|
|
779
1386
|
var ROTATE_RATIO = 3;
|
|
1387
|
+
var KEEP_MARGIN = 0.06;
|
|
780
1388
|
var LEADER_MIN_DOTS = 4;
|
|
781
1389
|
var LEADER_CONTEXT = 0.5;
|
|
782
1390
|
var MIN_INK_RATIO = 0.01;
|
|
@@ -862,6 +1470,8 @@ var OcrEngine = class _OcrEngine {
|
|
|
862
1470
|
continue;
|
|
863
1471
|
}
|
|
864
1472
|
}
|
|
1473
|
+
const trim = tuning.trimEdges ? edgeTrim(gray, b.w, b.h, ink) : null;
|
|
1474
|
+
const keep = trim ? { ...trim, bg: median(gray) } : void 0;
|
|
865
1475
|
const leaders = tuning.splitLeaders ? leaderRuns(gray, b.w, b.h, ink, LEADER_MIN_DOTS) : [];
|
|
866
1476
|
if (leaders.length) {
|
|
867
1477
|
const join2 = joins.length;
|
|
@@ -880,20 +1490,37 @@ var OcrEngine = class _OcrEngine {
|
|
|
880
1490
|
joins[join2].trailDots = dots;
|
|
881
1491
|
continue;
|
|
882
1492
|
}
|
|
1493
|
+
if (keep) jobs.push({ box: b, rot: 0, group, keep });
|
|
883
1494
|
jobs.push({ box: b, rot: 0, group: group++ });
|
|
884
1495
|
}
|
|
885
1496
|
const results = await this.recognizeJobs(rgba, width, jobs, tuning.recBatch);
|
|
886
1497
|
const best = /* @__PURE__ */ new Map();
|
|
1498
|
+
const score = (job, confidence) => confidence - (job.keep ? KEEP_MARGIN : 0);
|
|
887
1499
|
jobs.forEach((job, i) => {
|
|
888
1500
|
const r = results[i];
|
|
889
1501
|
if (!r) return;
|
|
890
1502
|
const cur = best.get(job.group);
|
|
891
|
-
if (!cur || r.confidence > cur.confidence) best.set(job.group, { job, ...r });
|
|
1503
|
+
if (!cur || score(job, r.confidence) > score(cur.job, cur.confidence)) best.set(job.group, { job, ...r });
|
|
892
1504
|
});
|
|
893
1505
|
let items = [];
|
|
894
|
-
for (const { job, text:
|
|
895
|
-
|
|
1506
|
+
for (const { job, text: read, confidence, steps, stepPx } of best.values()) {
|
|
1507
|
+
const note = {};
|
|
1508
|
+
const raw = tuning.postprocess && job.rot === 0 ? restoreGlyphs(rgba, width, job.box, read, steps, stepPx, note) : read;
|
|
1509
|
+
let text = tuning.postprocess ? restoreSymbols(raw.trim(), note.ringLead) : raw;
|
|
896
1510
|
if (!text.trim()) continue;
|
|
1511
|
+
if (tuning.postprocess && job.rot === 0 && !/^[◎●▪□■○ㅇ]/.test(text)) {
|
|
1512
|
+
const chars = [...read], k = chars.findIndex((c) => c.trim());
|
|
1513
|
+
if (k >= 0 && steps.length === chars.length) {
|
|
1514
|
+
const g = grayCrop(rgba, width, job.box);
|
|
1515
|
+
const b = leadingBullet(g, job.box.w, job.box.h, inkStats(g), (steps[k] + 0.5) * stepPx);
|
|
1516
|
+
const body = (text.match(/[가-힣]/g) ?? []).length >= 4;
|
|
1517
|
+
const miss = /^[Oo0•·ㆍ∙‧○]/.test(text);
|
|
1518
|
+
if (b?.mark === "\u25CE" && b.covers && /^[Oo0○]/.test(text)) text = b.mark + " " + text.slice(1).trimStart();
|
|
1519
|
+
else if (b?.mark === "\u25A1") {
|
|
1520
|
+
if (!b.covers && body) text = b.mark + " " + text;
|
|
1521
|
+
} else if (b && b.mark !== "\u25CE" && body) text = b.covers ? miss ? b.mark + " " + text.slice(1).trimStart() : text : b.mark + " " + text;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
897
1524
|
if (tuning.postprocess && /^\d/.test(text) && job.rot === 0) {
|
|
898
1525
|
const g = grayCrop(rgba, width, job.box);
|
|
899
1526
|
const tri = leadingTriangle(g, job.box.w, job.box.h, inkStats(g));
|
|
@@ -974,7 +1601,7 @@ var OcrEngine = class _OcrEngine {
|
|
|
974
1601
|
// ─── rec ─────────────────────────────────────────────
|
|
975
1602
|
/** 라인 작업들을 폭 비율 순으로 배치 인식 — 결과는 jobs 순서 */
|
|
976
1603
|
async recognizeJobs(rgba, pageW, jobs, batchSize) {
|
|
977
|
-
const crops = jobs.map((j) => lineCrop(rgba, pageW, j.box, j.rot));
|
|
1604
|
+
const crops = jobs.map((j) => lineCrop(rgba, pageW, j.box, j.rot, j.keep));
|
|
978
1605
|
const order = crops.map((_, i) => i).sort((a, b) => crops[a].w - crops[b].w);
|
|
979
1606
|
const results = new Array(jobs.length).fill(null);
|
|
980
1607
|
const plane = REC_HEIGHT;
|
|
@@ -1006,18 +1633,39 @@ var OcrEngine = class _OcrEngine {
|
|
|
1006
1633
|
const [, T, C] = logits.dims;
|
|
1007
1634
|
const data = logits.data;
|
|
1008
1635
|
idx.forEach((ci, k) => {
|
|
1009
|
-
|
|
1636
|
+
const r = ctcDecode(data.subarray(k * T * C, (k + 1) * T * C), T, C, this.dict);
|
|
1637
|
+
const job = jobs[ci], src = job.rot === 0 ? job.box.w : job.box.h;
|
|
1638
|
+
let kept = r;
|
|
1639
|
+
const last = crops[ci].w * T / bw + 1;
|
|
1640
|
+
const chars = r ? [...r.text] : [];
|
|
1641
|
+
if (r && job.rot === 0 && chars.length === r.steps.length && r.steps.some((t) => t > last) && !r.text.includes("(cid:")) {
|
|
1642
|
+
const tallCh = (c) => !/[\s.,:;\u00b7\u2026'"\u2018-\u201d`\-_~]/.test(c);
|
|
1643
|
+
const g = grayCrop(rgba, pageW, job.box);
|
|
1644
|
+
const inside = chars.filter((c, i) => r.steps[i] <= last && tallCh(c)).length;
|
|
1645
|
+
let budget = inside <= 2 ? tallInkCount(g, job.box.w, job.box.h, inkStats(g)) - inside : Infinity;
|
|
1646
|
+
const keep = chars.map((c, i) => r.steps[i] <= last || !tallCh(c) || budget-- > 0);
|
|
1647
|
+
if (!keep.every(Boolean)) kept = { ...r, text: chars.filter((_, i) => keep[i]).join(""), steps: r.steps.filter((_, i) => keep[i]) };
|
|
1648
|
+
}
|
|
1649
|
+
results[ci] = kept && { ...kept, stepPx: bw / T * (src / crops[ci].w) };
|
|
1010
1650
|
});
|
|
1011
1651
|
s = e;
|
|
1012
1652
|
}
|
|
1013
1653
|
return results;
|
|
1014
1654
|
}
|
|
1015
1655
|
};
|
|
1656
|
+
function median(gray) {
|
|
1657
|
+
const hist = new Uint32Array(256);
|
|
1658
|
+
for (let i = 0; i < gray.length; i++) hist[gray[i]]++;
|
|
1659
|
+
for (let v = 0, n = 0; v < 256; v++) if ((n += hist[v]) * 2 >= gray.length) return v;
|
|
1660
|
+
return 255;
|
|
1661
|
+
}
|
|
1016
1662
|
function ctcDecode(data, T, C, dict) {
|
|
1017
1663
|
let text = "";
|
|
1018
1664
|
let confSum = 0;
|
|
1019
1665
|
let confCount = 0;
|
|
1020
1666
|
let prev = -1;
|
|
1667
|
+
const runs = [];
|
|
1668
|
+
let open = [];
|
|
1021
1669
|
for (let t = 0; t < T; t++) {
|
|
1022
1670
|
const off = t * C;
|
|
1023
1671
|
let best = 0;
|
|
@@ -1031,7 +1679,12 @@ function ctcDecode(data, T, C, dict) {
|
|
|
1031
1679
|
}
|
|
1032
1680
|
const repeat = best === prev;
|
|
1033
1681
|
prev = best;
|
|
1034
|
-
if (best
|
|
1682
|
+
if (repeat && best !== 0) {
|
|
1683
|
+
for (const r of open) r[1] = t;
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
open = [];
|
|
1687
|
+
if (best === 0) continue;
|
|
1035
1688
|
let p = bestV;
|
|
1036
1689
|
if (p > 1.0001 || p < 0) {
|
|
1037
1690
|
let denom = 0;
|
|
@@ -1040,11 +1693,16 @@ function ctcDecode(data, T, C, dict) {
|
|
|
1040
1693
|
}
|
|
1041
1694
|
confSum += p;
|
|
1042
1695
|
confCount++;
|
|
1043
|
-
|
|
1044
|
-
|
|
1696
|
+
const tok = best >= 1 && best <= dict.length ? dict[best - 1] : best === dict.length + 1 ? " " : "";
|
|
1697
|
+
text += tok;
|
|
1698
|
+
for (const _ of tok) {
|
|
1699
|
+
const r = [t, t];
|
|
1700
|
+
runs.push(r);
|
|
1701
|
+
open.push(r);
|
|
1702
|
+
}
|
|
1045
1703
|
}
|
|
1046
1704
|
if (!text) return null;
|
|
1047
|
-
return { text, confidence: confCount > 0 ? confSum / confCount : 0 };
|
|
1705
|
+
return { text, confidence: confCount > 0 ? confSum / confCount : 0, steps: runs.map(([a, b]) => (a + b) / 2) };
|
|
1048
1706
|
}
|
|
1049
1707
|
function componentBoxes(prob, w, h, thresh = DEFAULT_OCR_TUNING.detThresh, boxThresh = DEFAULT_OCR_TUNING.detBoxThresh) {
|
|
1050
1708
|
const visited = new Uint8Array(w * h);
|
|
@@ -1263,7 +1921,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, ve
|
|
|
1263
1921
|
const upright = vectorOps ? rgba : deskewPage(rgba, rw, rh).rgba;
|
|
1264
1922
|
const stats = { droppedLowConf: 0 };
|
|
1265
1923
|
const ruling = vectorOps ? void 0 : detectRulingLines(upright, rw, rh, rh / pdfH);
|
|
1266
|
-
const items = await engine.recognizePage(upright, rw, rh, stats, void 0, ruling?.cellDividers);
|
|
1924
|
+
const items = await engine.recognizePage(upright, rw, rh, stats, regions ? REGION_TUNING : void 0, ruling?.cellDividers);
|
|
1267
1925
|
if (stats.droppedLowConf > 0) {
|
|
1268
1926
|
warnings.push({
|
|
1269
1927
|
page: pageNo,
|
|
@@ -1315,6 +1973,7 @@ function halve(src, w, h) {
|
|
|
1315
1973
|
}
|
|
1316
1974
|
return { rgba: out, width: W, height: H };
|
|
1317
1975
|
}
|
|
1976
|
+
var REGION_TUNING = Object.freeze({ ...DEFAULT_OCR_TUNING, trimEdges: false });
|
|
1318
1977
|
function meanConfidence(items) {
|
|
1319
1978
|
let n = 0, sum = 0;
|
|
1320
1979
|
for (const it of items) {
|
|
@@ -1335,7 +1994,7 @@ async function closerReads(rgba, rw, rh, pdfH, hi, regions, engine) {
|
|
|
1335
1994
|
}
|
|
1336
1995
|
const crop = new Uint8Array(cw * ch * 4);
|
|
1337
1996
|
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);
|
|
1338
|
-
const items = await engine.recognizePage(crop, cw, ch);
|
|
1997
|
+
const items = await engine.recognizePage(crop, cw, ch, void 0, REGION_TUNING);
|
|
1339
1998
|
out.push(items.map((it) => ({ ...it, x: (it.x + x0) / 2, y: (it.y + y0) / 2, w: it.w / 2, h: it.h / 2 })));
|
|
1340
1999
|
}
|
|
1341
2000
|
return out;
|
|
@@ -1402,4 +2061,4 @@ export {
|
|
|
1402
2061
|
runPdfOcr,
|
|
1403
2062
|
ocrItemsToBlocks
|
|
1404
2063
|
};
|
|
1405
|
-
//# sourceMappingURL=chunk-
|
|
2064
|
+
//# sourceMappingURL=chunk-3WIQSM3Z.js.map
|