kordoc 4.16.0 → 4.16.2
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 +344 -1135
- package/dist/{-VQNLLLPE.js → -4G6MA4X6.js} +11 -11
- package/dist/chunk-3IB7LBHC.js +57 -0
- package/dist/chunk-3IB7LBHC.js.map +1 -0
- package/dist/{chunk-6LMKQM7I.js → chunk-4VODJKFL.js} +3 -3
- package/dist/{chunk-UDX7TSOC.js → chunk-5KS5PWJR.js} +2 -2
- package/dist/chunk-62W7QCEX.cjs +57 -0
- package/dist/chunk-62W7QCEX.cjs.map +1 -0
- package/dist/{chunk-NNYF2AGC.js → chunk-7KNGSQU7.js} +4 -4
- package/dist/chunk-7KNGSQU7.js.map +1 -0
- package/dist/{chunk-DYENR3PX.js → chunk-AUWGDP4G.js} +2 -2
- package/dist/{chunk-H6KMTYYZ.cjs → chunk-CEIATRDW.cjs} +3 -3
- package/dist/{chunk-H6KMTYYZ.cjs.map → chunk-CEIATRDW.cjs.map} +1 -1
- package/dist/{chunk-N6TPODYL.js → chunk-DES2LGFZ.js} +2 -2
- package/dist/{chunk-PQRP4NSF.js → chunk-F4NRLLY4.js} +2 -2
- package/dist/{chunk-B5NEGP65.cjs → chunk-FKUDAIJI.cjs} +692 -33
- package/dist/chunk-FKUDAIJI.cjs.map +1 -0
- package/dist/{chunk-XWA625AN.js → chunk-FSDJLAOW.js} +3 -1
- package/dist/chunk-FSDJLAOW.js.map +1 -0
- package/dist/{chunk-PIMB2YNO.js → chunk-GO5IFOUF.js} +2 -2
- package/dist/{chunk-476OYHGZ.js → chunk-IQGIT2VX.js} +37 -7
- package/dist/chunk-IQGIT2VX.js.map +1 -0
- package/dist/chunk-J3IF6KGW.js +59 -0
- package/dist/chunk-J3IF6KGW.js.map +1 -0
- package/dist/{chunk-C75WE52C.js → chunk-JELRWAZC.js} +2 -2
- package/dist/{chunk-46DZF3RK.js → chunk-JFLAM3MM.js} +2 -2
- package/dist/{chunk-A3QDRMVD.js → chunk-KIOQ4N37.js} +2 -2
- package/dist/{chunk-TGD7UFHD.js → chunk-LEIAEWZ7.js} +4 -4
- package/dist/chunk-LEIAEWZ7.js.map +1 -0
- package/dist/{chunk-GOQBON77.js → chunk-MLIS253L.js} +2 -2
- package/dist/{chunk-JB5X5NNL.js → chunk-OXLPVQYK.js} +680 -21
- package/dist/chunk-OXLPVQYK.js.map +1 -0
- package/dist/{chunk-RC4C7Q33.js → chunk-P6ZDXTV6.js} +2 -2
- package/dist/{chunk-CB5XDCSL.js → chunk-QOCS7W7R.js} +5034 -4569
- package/dist/chunk-QOCS7W7R.js.map +1 -0
- package/dist/{chunk-3JWMDL3S.js → chunk-S2PL227Q.js} +2 -2
- package/dist/{chunk-HIVK5V27.js → chunk-SFEQYKYJ.js} +133 -110
- package/dist/chunk-SFEQYKYJ.js.map +1 -0
- package/dist/{chunk-NWODPYBD.cjs → chunk-STM3ORY7.cjs} +2 -2
- package/dist/{chunk-NWODPYBD.cjs.map → chunk-STM3ORY7.cjs.map} +1 -1
- package/dist/{chunk-43FKGDDO.js → chunk-TXUKTOG2.js} +213 -30
- package/dist/chunk-TXUKTOG2.js.map +1 -0
- package/dist/{chunk-XHOZB2WP.js → chunk-UTP2WCHS.js} +681 -22
- package/dist/chunk-UTP2WCHS.js.map +1 -0
- package/dist/{chunk-MH6MAL64.cjs → chunk-WDQTFAXY.cjs} +217 -114
- package/dist/chunk-WDQTFAXY.cjs.map +1 -0
- package/dist/{chunk-DRY3L5KQ.cjs → chunk-XTFN5WKS.cjs} +7 -7
- package/dist/chunk-XTFN5WKS.cjs.map +1 -0
- package/dist/chunks-NUNIKS2J.js +10 -0
- package/dist/cli.js +33 -30
- package/dist/cli.js.map +1 -1
- package/dist/{formula-FNQYHMIY.js → formula-EPKLPDU7.js} +3 -3
- package/dist/{formula-5775PTP2.js → formula-NARUOXRX.js} +4 -4
- package/dist/{formula-VEFKYXPB.cjs → formula-WIMV5TUX.cjs} +9 -9
- package/dist/{formula-VEFKYXPB.cjs.map → formula-WIMV5TUX.cjs.map} +1 -1
- package/dist/{image-ocr-PYZU4TO7.js → image-ocr-3WJ4QH3T.js} +8 -8
- package/dist/{image-ocr-YA2MQBRP.js → image-ocr-5BUJQ4TK.js} +6 -6
- package/dist/{image-ocr-6JZR734G.cjs → image-ocr-AXWXR6JV.cjs} +15 -15
- package/dist/{image-ocr-6JZR734G.cjs.map → image-ocr-AXWXR6JV.cjs.map} +1 -1
- package/dist/index.cjs +1900 -1406
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -6
- package/dist/index.d.ts +24 -6
- package/dist/index.js +1282 -788
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +34 -28
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-O6J7ORVC.js → model-bundle-IMBW7D2Z.js} +5 -5
- package/dist/{models-YMOKZGCI.js → models-SY75IBOE.js} +5 -5
- package/dist/{parser-YAQV7FBO.cjs → parser-2LU4EVU3.cjs} +360 -219
- package/dist/parser-2LU4EVU3.cjs.map +1 -0
- package/dist/{parser-3B6I2XL7.js → parser-N6476XAC.js} +225 -84
- package/dist/parser-N6476XAC.js.map +1 -0
- package/dist/{parser-PXATL5DM.js → parser-ZQM3664Z.js} +222 -81
- package/dist/parser-ZQM3664Z.js.map +1 -0
- package/dist/pdf-ocr-6DEE23IP.js +15 -0
- package/dist/pdf-ocr-GHEJVPX2.cjs +16 -0
- package/dist/{pdf-ocr-T2JXQATA.cjs.map → pdf-ocr-GHEJVPX2.cjs.map} +1 -1
- package/dist/pdf-ocr-MQTWODVU.js +18 -0
- package/dist/{profile-io-ZTV2MKRT.js → profile-io-GJPKCYPT.js} +3 -3
- package/dist/{rasterize-MMUZ537K.js → rasterize-UOALJQDS.js} +3 -3
- package/dist/{redact-doc-S42XGV5D.js → redact-doc-22SYW3KH.js} +11 -11
- package/dist/{render-WCWPZKJ7.js → render-MJ4H3R23.js} +7 -7
- package/dist/seal-XWMRUTI4.js +10 -0
- package/dist/{setup-D35LKZCH.js → setup-2URYVUEO.js} +3 -3
- package/dist/visual-USPS4QRC.js +20 -0
- package/dist/{watch-AX64CIMN.js → watch-ALXZGTOB.js} +12 -12
- package/package.json +1 -1
- package/dist/chunk-43FKGDDO.js.map +0 -1
- package/dist/chunk-476OYHGZ.js.map +0 -1
- package/dist/chunk-B5NEGP65.cjs.map +0 -1
- package/dist/chunk-CB5XDCSL.js.map +0 -1
- package/dist/chunk-DRY3L5KQ.cjs.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-NNYF2AGC.js.map +0 -1
- package/dist/chunk-QJERJZJ6.js +0 -11
- package/dist/chunk-QJERJZJ6.js.map +0 -1
- package/dist/chunk-TGD7UFHD.js.map +0 -1
- package/dist/chunk-UQ6TZDW6.js +0 -13
- 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 +0 -11
- 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 → -4G6MA4X6.js.map} +0 -0
- /package/dist/{chunk-6LMKQM7I.js.map → chunk-4VODJKFL.js.map} +0 -0
- /package/dist/{chunk-UDX7TSOC.js.map → chunk-5KS5PWJR.js.map} +0 -0
- /package/dist/{chunk-DYENR3PX.js.map → chunk-AUWGDP4G.js.map} +0 -0
- /package/dist/{chunk-N6TPODYL.js.map → chunk-DES2LGFZ.js.map} +0 -0
- /package/dist/{chunk-PQRP4NSF.js.map → chunk-F4NRLLY4.js.map} +0 -0
- /package/dist/{chunk-PIMB2YNO.js.map → chunk-GO5IFOUF.js.map} +0 -0
- /package/dist/{chunk-C75WE52C.js.map → chunk-JELRWAZC.js.map} +0 -0
- /package/dist/{chunk-46DZF3RK.js.map → chunk-JFLAM3MM.js.map} +0 -0
- /package/dist/{chunk-A3QDRMVD.js.map → chunk-KIOQ4N37.js.map} +0 -0
- /package/dist/{chunk-GOQBON77.js.map → chunk-MLIS253L.js.map} +0 -0
- /package/dist/{chunk-RC4C7Q33.js.map → chunk-P6ZDXTV6.js.map} +0 -0
- /package/dist/{chunk-3JWMDL3S.js.map → chunk-S2PL227Q.js.map} +0 -0
- /package/dist/{chunks-JD25E5PM.js.map → chunks-NUNIKS2J.js.map} +0 -0
- /package/dist/{formula-FNQYHMIY.js.map → formula-EPKLPDU7.js.map} +0 -0
- /package/dist/{formula-5775PTP2.js.map → formula-NARUOXRX.js.map} +0 -0
- /package/dist/{image-ocr-PYZU4TO7.js.map → image-ocr-3WJ4QH3T.js.map} +0 -0
- /package/dist/{image-ocr-YA2MQBRP.js.map → image-ocr-5BUJQ4TK.js.map} +0 -0
- /package/dist/{model-bundle-O6J7ORVC.js.map → model-bundle-IMBW7D2Z.js.map} +0 -0
- /package/dist/{models-YMOKZGCI.js.map → models-SY75IBOE.js.map} +0 -0
- /package/dist/{pdf-ocr-2TTO5PX2.js.map → pdf-ocr-6DEE23IP.js.map} +0 -0
- /package/dist/{pdf-ocr-WF2NQA6X.js.map → pdf-ocr-MQTWODVU.js.map} +0 -0
- /package/dist/{profile-io-ZTV2MKRT.js.map → profile-io-GJPKCYPT.js.map} +0 -0
- /package/dist/{rasterize-MMUZ537K.js.map → rasterize-UOALJQDS.js.map} +0 -0
- /package/dist/{redact-doc-S42XGV5D.js.map → redact-doc-22SYW3KH.js.map} +0 -0
- /package/dist/{render-WCWPZKJ7.js.map → render-MJ4H3R23.js.map} +0 -0
- /package/dist/{seal-WHK4GOHR.js.map → seal-XWMRUTI4.js.map} +0 -0
- /package/dist/{setup-D35LKZCH.js.map → setup-2URYVUEO.js.map} +0 -0
- /package/dist/{visual-WL4TDE5B.js.map → visual-USPS4QRC.js.map} +0 -0
- /package/dist/{watch-AX64CIMN.js.map → watch-ALXZGTOB.js.map} +0 -0
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
var
|
|
9
|
+
var _chunkWDQTFAXYcjs = require('./chunk-WDQTFAXY.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
var
|
|
12
|
+
var _chunkSTM3ORY7cjs = require('./chunk-STM3ORY7.cjs');
|
|
13
13
|
|
|
14
14
|
// src/ocr/ruling-lines.ts
|
|
15
15
|
var INK_LUMA_MAX = 205;
|
|
@@ -185,19 +185,19 @@ function solidRuns(mask, base, step, span, minLenPx) {
|
|
|
185
185
|
function dottedRuns(mask, base, step, span, minLenPx, maxGap, maxDash, maxPeriod) {
|
|
186
186
|
const runs = [];
|
|
187
187
|
const chain = [];
|
|
188
|
-
const
|
|
188
|
+
const median2 = (xs) => xs.sort((a, b) => a - b)[xs.length >> 1];
|
|
189
189
|
const flush = () => {
|
|
190
190
|
const cnt = chain.length >> 1;
|
|
191
191
|
if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {
|
|
192
|
-
const periods = [], dashes = [],
|
|
192
|
+
const periods = [], dashes = [], gaps2 = [];
|
|
193
193
|
for (let k = 0; k < cnt; k++) {
|
|
194
194
|
dashes.push(chain[2 * k + 1] - chain[2 * k] + 1);
|
|
195
195
|
if (k) {
|
|
196
196
|
periods.push(chain[2 * k] - chain[2 * k - 2]);
|
|
197
|
-
|
|
197
|
+
gaps2.push(chain[2 * k] - chain[2 * k - 1] - 1);
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
-
if (
|
|
200
|
+
if (median2(periods) <= maxPeriod || median2(dashes) >= 1.5 * median2(gaps2)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] });
|
|
201
201
|
}
|
|
202
202
|
chain.length = 0;
|
|
203
203
|
};
|
|
@@ -570,12 +570,614 @@ function inkBounds(gray, w, h, ink) {
|
|
|
570
570
|
if (x1 < x0 || y1 < y0) return { x0: 0, y0: 0, x1: w, y1: h };
|
|
571
571
|
return { x0, y0, x1: x1 + 1, y1: y1 + 1 };
|
|
572
572
|
}
|
|
573
|
+
function bracketFeatures(gray, w, h, ink, cx) {
|
|
574
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
575
|
+
let charH = 0;
|
|
576
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
577
|
+
if (charH < 8) return null;
|
|
578
|
+
const tol = Math.max(2, charH * 0.3);
|
|
579
|
+
let best = null, bestD = Infinity;
|
|
580
|
+
for (const c of comps) {
|
|
581
|
+
if (c.y1 - c.y0 < charH * 0.3 || c.x1 < cx - tol || c.x0 > cx + tol) continue;
|
|
582
|
+
const d = Math.abs((c.x0 + c.x1) / 2 - cx);
|
|
583
|
+
if (d < bestD) {
|
|
584
|
+
bestD = d;
|
|
585
|
+
best = c;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (!best) return null;
|
|
589
|
+
const b = best;
|
|
590
|
+
const cw = b.x1 - b.x0, ch = b.y1 - b.y0;
|
|
591
|
+
if (cw < 3 || ch < 8) return null;
|
|
592
|
+
const spans = [];
|
|
593
|
+
for (let y = b.y0; y < b.y1; y++) {
|
|
594
|
+
let n = 0;
|
|
595
|
+
for (let x = b.x0; x < b.x1; x++) if (label[y * w + x] === b.id) n++;
|
|
596
|
+
spans.push(n);
|
|
597
|
+
}
|
|
598
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
599
|
+
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
600
|
+
const midRows = spans.slice(Math.floor(ch * 0.3), Math.ceil(ch * 0.7)).sort((p, q) => p - q);
|
|
601
|
+
const stroke = _nullishCoalesce(midRows[midRows.length >> 1], () => ( 0));
|
|
602
|
+
const taper = spans.filter((n) => n > stroke && n < cw).length / ch;
|
|
603
|
+
const fill = spans.reduce((a, n) => a + n, 0) / (cw * ch);
|
|
604
|
+
return { top, bottom, taper, fill, cw, ch, charH };
|
|
605
|
+
}
|
|
606
|
+
function romanStems(gray, w, h, ink, prevX, nextX) {
|
|
607
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
608
|
+
let charH = 0;
|
|
609
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
610
|
+
if (charH < 10) return null;
|
|
611
|
+
const x0 = prevX + charH * 0.35, x1 = nextX - charH * 0.35;
|
|
612
|
+
const sel = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6 && (c.x0 + c.x1) / 2 >= x0 && (c.x0 + c.x1) / 2 < x1);
|
|
613
|
+
if (!sel.length) return null;
|
|
614
|
+
const ids = new Set(sel.map((c) => c.id));
|
|
615
|
+
const ux0 = Math.min(...sel.map((c) => c.x0)), ux1 = Math.max(...sel.map((c) => c.x1));
|
|
616
|
+
const uy0 = Math.min(...sel.map((c) => c.y0)), uy1 = Math.max(...sel.map((c) => c.y1));
|
|
617
|
+
const uh = uy1 - uy0;
|
|
618
|
+
const counts = /* @__PURE__ */ new Map();
|
|
619
|
+
let rows = 0;
|
|
620
|
+
for (let y = uy0 + Math.floor(uh * 0.25); y < uy1 - Math.floor(uh * 0.25); y++) {
|
|
621
|
+
let runs = 0, len = 0, thick = false;
|
|
622
|
+
for (let x = ux0; x <= ux1; x++) {
|
|
623
|
+
const on = x < ux1 && ids.has(label[y * w + x]);
|
|
624
|
+
if (on) len++;
|
|
625
|
+
else if (len) {
|
|
626
|
+
runs++;
|
|
627
|
+
if (len > uh * 0.35) thick = true;
|
|
628
|
+
len = 0;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
rows++;
|
|
632
|
+
if (!thick) counts.set(runs, (_nullishCoalesce(counts.get(runs), () => ( 0))) + 1);
|
|
633
|
+
}
|
|
634
|
+
const [n, k] = _nullishCoalesce([...counts].sort((a, b) => b[1] - a[1])[0], () => ( [0, 0]));
|
|
635
|
+
return n >= 1 && n <= 3 && k >= rows * 0.7 ? n : null;
|
|
636
|
+
}
|
|
637
|
+
function circledAt(gray, w, h, ink, xs) {
|
|
638
|
+
const { comps } = components(gray, w, h, ink);
|
|
639
|
+
let charH = 0;
|
|
640
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
641
|
+
const rings = charH < 10 ? [] : comps.filter((r) => {
|
|
642
|
+
const rw = r.x1 - r.x0, rh = r.y1 - r.y0;
|
|
643
|
+
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);
|
|
644
|
+
});
|
|
645
|
+
return xs.map((x) => rings.some((r) => x >= r.x0 && x <= r.x1));
|
|
646
|
+
}
|
|
647
|
+
function quoteHead(gray, w, h, ink, cx) {
|
|
648
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
649
|
+
let charH = 0;
|
|
650
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
651
|
+
if (charH < 10) return null;
|
|
652
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
653
|
+
if (!tall.length) return null;
|
|
654
|
+
const mid = (Math.min(...tall.map((c) => c.y0)) + Math.max(...tall.map((c) => c.y1))) / 2;
|
|
655
|
+
let best = null, bestD = charH * 0.4;
|
|
656
|
+
for (const c of comps) {
|
|
657
|
+
const ch = c.y1 - c.y0, d = Math.abs((c.x0 + c.x1) / 2 - cx);
|
|
658
|
+
if (ch < charH * 0.12 || ch > charH * 0.5 || (c.y0 + c.y1) / 2 > mid || d > bestD) continue;
|
|
659
|
+
bestD = d;
|
|
660
|
+
best = c;
|
|
661
|
+
}
|
|
662
|
+
if (!best) return null;
|
|
663
|
+
let n = 0, sy = 0;
|
|
664
|
+
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) {
|
|
665
|
+
n++;
|
|
666
|
+
sy += y;
|
|
667
|
+
}
|
|
668
|
+
return n ? (sy / n - best.y0) / (best.y1 - best.y0) : null;
|
|
669
|
+
}
|
|
670
|
+
function edgeTrim(gray, w, h, ink) {
|
|
671
|
+
const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
|
|
672
|
+
const colInk = new Uint32Array(w);
|
|
673
|
+
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (isInk(gray[y * w + x])) colInk[x]++;
|
|
674
|
+
const ruleCol = new Uint8Array(w);
|
|
675
|
+
let liveW = 0;
|
|
676
|
+
for (let x = 0; x < w; x++) {
|
|
677
|
+
if (colInk[x] >= h * 0.85) ruleCol[x] = 1;
|
|
678
|
+
else liveW++;
|
|
679
|
+
}
|
|
680
|
+
const blank = Math.max(1, Math.floor(w * 0.02));
|
|
681
|
+
const bands = [];
|
|
682
|
+
let start = -1;
|
|
683
|
+
for (let y = 0; y <= h; y++) {
|
|
684
|
+
let n = 0;
|
|
685
|
+
if (y < h) {
|
|
686
|
+
for (let x = 0; x < w; x++) if (!ruleCol[x] && isInk(gray[y * w + x])) n++;
|
|
687
|
+
}
|
|
688
|
+
const on = y < h && n > blank && n < liveW * 0.85;
|
|
689
|
+
if (on && start < 0) start = y;
|
|
690
|
+
else if (!on && start >= 0) {
|
|
691
|
+
bands.push([start, y]);
|
|
692
|
+
start = -1;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
let y0 = 0, y1 = h;
|
|
696
|
+
if (bands.length >= 2 && h >= 24) {
|
|
697
|
+
const mh = Math.max(...bands.map(([a, b]) => b - a));
|
|
698
|
+
const [top, next] = [bands[0], bands[1]], [bot, prev] = [bands[bands.length - 1], bands[bands.length - 2]];
|
|
699
|
+
if (top[0] <= 1 && top[1] - top[0] < mh * 0.45) y0 = top[1] + next[0] >> 1;
|
|
700
|
+
if (bot[1] >= h - 1 && bot[1] - bot[0] < mh * 0.45) y1 = bot[0] + prev[1] + 1 >> 1;
|
|
701
|
+
if (y0 > 0 || y1 < h) {
|
|
702
|
+
let runs = 0, len = 0;
|
|
703
|
+
for (let x = 0; x <= w; x++) {
|
|
704
|
+
let on = false;
|
|
705
|
+
if (x < w && !ruleCol[x]) for (let y = y0; y < y1 && !on; y++) on = isInk(gray[y * w + x]);
|
|
706
|
+
if (on) len++;
|
|
707
|
+
else {
|
|
708
|
+
if (len >= (y1 - y0) * 0.3) runs++;
|
|
709
|
+
len = 0;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (runs < 2) {
|
|
713
|
+
y0 = 0;
|
|
714
|
+
y1 = h;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const textCol = (x) => {
|
|
719
|
+
for (let y = y0; y < y1; y++) if (isInk(gray[y * w + x])) return true;
|
|
720
|
+
return false;
|
|
721
|
+
};
|
|
722
|
+
const border = (x) => colInk[x] >= h * 0.95;
|
|
723
|
+
const gap = Math.max(2, Math.round(h * 0.1));
|
|
724
|
+
const clearFrom = (x, dir) => {
|
|
725
|
+
for (let k = 0; k < gap; k++) {
|
|
726
|
+
const c = x + dir * k;
|
|
727
|
+
if (c < 0 || c >= w || textCol(c)) return false;
|
|
728
|
+
}
|
|
729
|
+
return true;
|
|
730
|
+
};
|
|
731
|
+
let x0 = 0, x1 = w;
|
|
732
|
+
for (let x = 0; x < w * 0.2; x++) {
|
|
733
|
+
if (border(x)) {
|
|
734
|
+
let e = x;
|
|
735
|
+
while (e + 1 < w && border(e + 1)) e++;
|
|
736
|
+
if (clearFrom(e + 1, 1)) x0 = e + 1;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
if (textCol(x)) break;
|
|
740
|
+
}
|
|
741
|
+
for (let x = w - 1; x >= w * 0.8; x--) {
|
|
742
|
+
if (border(x)) {
|
|
743
|
+
let e = x;
|
|
744
|
+
while (e - 1 >= 0 && border(e - 1)) e--;
|
|
745
|
+
if (clearFrom(e - 1, -1)) x1 = e;
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
if (textCol(x)) break;
|
|
749
|
+
}
|
|
750
|
+
return y0 > 0 || y1 < h || x0 > 0 || x1 < w ? { x0, x1, y0, y1 } : null;
|
|
751
|
+
}
|
|
752
|
+
function tallInkCount(gray, w, h, ink) {
|
|
753
|
+
const { comps } = components(gray, w, h, ink);
|
|
754
|
+
let charH = 0;
|
|
755
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
756
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.5).sort((a, b) => a.x0 - b.x0);
|
|
757
|
+
let n = 0, end = -1;
|
|
758
|
+
for (const c of tall) {
|
|
759
|
+
if (c.x0 >= end) n++;
|
|
760
|
+
end = Math.max(end, c.x1);
|
|
761
|
+
}
|
|
762
|
+
return n;
|
|
763
|
+
}
|
|
764
|
+
function gapGlyphs(gray, w, h, ink) {
|
|
765
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
766
|
+
let charH = 0;
|
|
767
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
768
|
+
if (charH < 10) return { glyphs: [], bandH: 0 };
|
|
769
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
770
|
+
const bandT = Math.min(...tall.map((c) => c.y0)), bandB = Math.max(...tall.map((c) => c.y1)), bh = bandB - bandT;
|
|
771
|
+
const inBand = (c) => c.y1 > bandT && c.y0 < bandB;
|
|
772
|
+
const out = [];
|
|
773
|
+
for (const c of comps) {
|
|
774
|
+
if (!inBand(c)) continue;
|
|
775
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
776
|
+
if (ch >= bh * 0.9 && cw <= bh * 0.15) continue;
|
|
777
|
+
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));
|
|
778
|
+
const apart = !others.some((o) => o.x0 < c.x1 + 1 && o.x1 > c.x0 - 1);
|
|
779
|
+
const nearlyApart = !others.some((o) => Math.min(o.x1, c.x1) - Math.max(o.x0, c.x0) > 2);
|
|
780
|
+
const spans = [], runs = [];
|
|
781
|
+
let n = 0;
|
|
782
|
+
for (let y = c.y0; y < c.y1; y++) {
|
|
783
|
+
let s = 0, r = 0, prev = false;
|
|
784
|
+
for (let x = c.x0; x < c.x1; x++) {
|
|
785
|
+
const on = label[y * w + x] === c.id;
|
|
786
|
+
if (on) {
|
|
787
|
+
s++;
|
|
788
|
+
if (!prev) r++;
|
|
789
|
+
}
|
|
790
|
+
prev = on;
|
|
791
|
+
}
|
|
792
|
+
spans.push(s);
|
|
793
|
+
runs.push(r);
|
|
794
|
+
n += s;
|
|
795
|
+
}
|
|
796
|
+
const fill = n / (cw * ch), cy = ((c.y0 + c.y1) / 2 - bandT) / bh;
|
|
797
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
798
|
+
const top = Math.max(...spans.slice(0, band)) / cw, bottom = Math.max(...spans.slice(ch - band)) / cw;
|
|
799
|
+
let mark = null;
|
|
800
|
+
const speck = Math.max(3, bh * 0.08);
|
|
801
|
+
const dot = ch <= bh * 0.25 && cw <= bh * 0.25 && ch >= speck && cw >= speck && cw >= ch * 0.6 && cw <= ch * 1.6;
|
|
802
|
+
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;
|
|
803
|
+
if (!apart && !(nearlyApart && ch >= bh * 0.6)) {
|
|
804
|
+
if (small) out.push({ x0: c.x0, x1: c.x1, mark: "\xB7" });
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (!apart) {
|
|
808
|
+
} else if (dot && fill >= 0.6 && cy >= 0.3 && cy <= 0.7) mark = "\xB7";
|
|
809
|
+
else if (small) mark = "\xB7";
|
|
810
|
+
else if (dot && fill >= 0.6 && cy >= 0.75 && cw >= ch * 0.8) mark = ".";
|
|
811
|
+
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";
|
|
812
|
+
else if (ch <= bh * 0.15 && cw >= ch * 2 && cw <= bh * 0.8 && fill >= 0.7 && cy >= 0.35 && cy <= 0.75) mark = "-";
|
|
813
|
+
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";
|
|
814
|
+
else if (ch >= bh * 0.3 && ch <= bh * 0.65 && fill < 0.5) {
|
|
815
|
+
const stem = (from, to) => {
|
|
816
|
+
let sx = 0, k = 0;
|
|
817
|
+
for (let y = from; y < to; y++) for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
818
|
+
sx += x - c.x0;
|
|
819
|
+
k++;
|
|
820
|
+
}
|
|
821
|
+
return k ? sx / k / cw : 0.5;
|
|
822
|
+
};
|
|
823
|
+
if (c.y0 <= bandT + bh * 0.15 && top >= 0.7 && bottom <= 0.5 && stem(c.y0 + band, c.y1) <= 0.35) mark = "\u300C";
|
|
824
|
+
else if (c.y1 >= bandB - bh * 0.15 && bottom >= 0.7 && top <= 0.5 && stem(c.y0, c.y1 - band) >= 0.65) mark = "\u300D";
|
|
825
|
+
} 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";
|
|
826
|
+
if (!mark && ch >= bh * 0.6 && cw >= ch * 0.25 && cw <= ch * 0.7 && fill <= 0.4 && runs.every((r) => r <= 1)) {
|
|
827
|
+
const mx = (y) => {
|
|
828
|
+
let sx = 0, k = 0;
|
|
829
|
+
for (let x = c.x0; x < c.x1; x++) if (label[y * w + x] === c.id) {
|
|
830
|
+
sx += x;
|
|
831
|
+
k++;
|
|
832
|
+
}
|
|
833
|
+
return k ? sx / k : NaN;
|
|
834
|
+
};
|
|
835
|
+
if (mx(c.y0 + band) - mx(c.y1 - 1 - band) >= cw * 0.5) mark = "/";
|
|
836
|
+
}
|
|
837
|
+
if (mark) out.push({ x0: c.x0, x1: c.x1, mark });
|
|
838
|
+
}
|
|
839
|
+
return { glyphs: out, bandH: bh };
|
|
840
|
+
}
|
|
841
|
+
function ringBullet(gray, w, h, ink, cx) {
|
|
842
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
843
|
+
let charH = 0;
|
|
844
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
845
|
+
if (charH < 10) return null;
|
|
846
|
+
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
847
|
+
const bandT = Math.min(...tall.map((c2) => c2.y0)), bh = Math.max(...tall.map((c2) => c2.y1)) - bandT;
|
|
848
|
+
let best = null, bestD = bh * 0.6;
|
|
849
|
+
for (const c2 of comps) {
|
|
850
|
+
const d = Math.abs((c2.x0 + c2.x1) / 2 - cx);
|
|
851
|
+
if (c2.y1 - c2.y0 >= bh * 0.3 && d < bestD) {
|
|
852
|
+
bestD = d;
|
|
853
|
+
best = c2;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (!best) return null;
|
|
857
|
+
const c = best, cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
858
|
+
if (cw < ch * 0.85 || cw > ch * 1.2) return null;
|
|
859
|
+
const runs = (xs, at) => {
|
|
860
|
+
let n2 = 0, len = 0, mx2 = 0, mn = Infinity;
|
|
861
|
+
for (const k of [...xs, -1]) {
|
|
862
|
+
if (k >= 0 && at(k)) {
|
|
863
|
+
if (!len) n2++;
|
|
864
|
+
len++;
|
|
865
|
+
mx2 = Math.max(mx2, len);
|
|
866
|
+
} else {
|
|
867
|
+
if (len) mn = Math.min(mn, len);
|
|
868
|
+
len = 0;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return { n: n2, mx: mx2, mn };
|
|
872
|
+
};
|
|
873
|
+
const my = c.y0 + c.y1 >> 1, mx = c.x0 + c.x1 >> 1;
|
|
874
|
+
const row = runs(Array.from({ length: cw }, (_, k) => c.x0 + k), (x) => label[my * w + x] === c.id);
|
|
875
|
+
const col = runs(Array.from({ length: ch }, (_, k) => c.y0 + k), (y) => label[y * w + mx] === c.id);
|
|
876
|
+
if (row.n !== 2 || col.n !== 2) return null;
|
|
877
|
+
let n = 0;
|
|
878
|
+
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++;
|
|
879
|
+
const fill = n / (cw * ch), rel = ch / bh;
|
|
880
|
+
if (rel >= 0.62 && (fill <= 0.23 ? row.mx <= cw * 0.1 : fill <= 0.33 && row.mn <= cw * 0.08)) return "\u25CB";
|
|
881
|
+
if (rel <= 0.58 && fill >= 0.24) return "\u3147";
|
|
882
|
+
if (rel >= 0.6 && rel <= 0.8 && fill >= 0.3 && ((c.y0 + c.y1) / 2 - bandT) / bh >= 0.55) return "o";
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
function discAt(gray, w, h, ink, cx) {
|
|
886
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
887
|
+
let charH = 0;
|
|
888
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
889
|
+
if (charH < 10) return false;
|
|
890
|
+
const tall = comps.filter((c2) => c2.y1 - c2.y0 >= charH * 0.6);
|
|
891
|
+
const bh = Math.max(...tall.map((c2) => c2.y1)) - Math.min(...tall.map((c2) => c2.y0));
|
|
892
|
+
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];
|
|
893
|
+
if (!c) return false;
|
|
894
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
895
|
+
if (ch < bh * 0.45 || ch > bh * 1.05 || cw < ch * 0.8 || cw > ch * 1.25) return false;
|
|
896
|
+
let n = 0;
|
|
897
|
+
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++;
|
|
898
|
+
const fill = n / (cw * ch);
|
|
899
|
+
return fill >= 0.65 && fill <= 0.9;
|
|
900
|
+
}
|
|
901
|
+
function serifOne(gray, w, h, ink, cx) {
|
|
902
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
903
|
+
let charH = 0;
|
|
904
|
+
for (const c2 of comps) charH = Math.max(charH, c2.y1 - c2.y0);
|
|
905
|
+
if (charH < 10) return false;
|
|
906
|
+
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];
|
|
907
|
+
if (!c) return false;
|
|
908
|
+
const ch = c.y1 - c.y0;
|
|
909
|
+
const span = (a, b) => {
|
|
910
|
+
let mn = Infinity, mx = -1;
|
|
911
|
+
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) {
|
|
912
|
+
mn = Math.min(mn, x);
|
|
913
|
+
mx = Math.max(mx, x);
|
|
914
|
+
}
|
|
915
|
+
return [mn, mx];
|
|
916
|
+
};
|
|
917
|
+
const [m0, m1] = span(0.4, 0.6), [t0, t1] = span(0, 0.2), [b0, b1] = span(0.8, 1);
|
|
918
|
+
const sw = m1 - m0 + 1;
|
|
919
|
+
const even = (l, r) => l >= sw * 0.4 && r >= sw * 0.4 && Math.abs(l - r) <= Math.max(l, r) * 0.35;
|
|
920
|
+
return sw >= 2 && sw <= ch * 0.3 && even(m0 - t0, t1 - m1) && even(m0 - b0, b1 - m1);
|
|
921
|
+
}
|
|
922
|
+
function starRun(gray, w, h, ink, cx) {
|
|
923
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
924
|
+
let charH = 0;
|
|
925
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
926
|
+
if (charH < 10) return 0;
|
|
927
|
+
const tall = comps.filter((c) => c.y1 - c.y0 >= charH * 0.6);
|
|
928
|
+
const bandT = Math.min(...tall.map((c) => c.y0)), bh = Math.max(...tall.map((c) => c.y1)) - bandT;
|
|
929
|
+
const star = (c) => {
|
|
930
|
+
const cw = c.x1 - c.x0, ch = c.y1 - c.y0;
|
|
931
|
+
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;
|
|
932
|
+
let n = 0;
|
|
933
|
+
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++;
|
|
934
|
+
const fill = n / (cw * ch);
|
|
935
|
+
return fill >= 0.25 && fill <= 0.65;
|
|
936
|
+
};
|
|
937
|
+
const stars2 = comps.filter(star).sort((a, b) => a.x0 - b.x0);
|
|
938
|
+
const k = stars2.findIndex((c) => cx >= c.x0 - (c.x1 - c.x0) && cx <= c.x1 + (c.x1 - c.x0));
|
|
939
|
+
if (k < 0) return 0;
|
|
940
|
+
let lo = k, hi = k;
|
|
941
|
+
while (lo > 0 && stars2[lo].x0 - stars2[lo - 1].x1 <= stars2[lo].x1 - stars2[lo].x0) lo--;
|
|
942
|
+
while (hi + 1 < stars2.length && stars2[hi + 1].x0 - stars2[hi].x1 <= stars2[hi].x1 - stars2[hi].x0) hi++;
|
|
943
|
+
return hi - lo + 1;
|
|
944
|
+
}
|
|
945
|
+
var BRACKET_TAPER = 0.16;
|
|
946
|
+
var BRACKET_SOLID = 0.4;
|
|
947
|
+
var BRACKET_WIDE = 0.265;
|
|
948
|
+
var BRACKET_LENS = 0.35;
|
|
949
|
+
function bracketShape(f, close) {
|
|
950
|
+
const [near, far] = close ? [f.bottom, f.top] : [f.top, f.bottom];
|
|
951
|
+
const paren = close ? ")" : "(";
|
|
952
|
+
if (near >= 0.7 && far <= 0.5) return f.taper < BRACKET_TAPER ? close ? "\u300D" : "\u300C" : paren;
|
|
953
|
+
if (near >= 0.7 && far >= 0.7 && f.taper >= BRACKET_TAPER) {
|
|
954
|
+
return f.fill >= BRACKET_SOLID && (f.cw >= f.ch * BRACKET_WIDE || f.taper >= BRACKET_LENS) ? close ? "\u3011" : "\u3010" : paren;
|
|
955
|
+
}
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
function leadingBullet(gray, w, h, ink, firstX) {
|
|
959
|
+
const { comps, label } = components(gray, w, h, ink);
|
|
960
|
+
let charH = 0;
|
|
961
|
+
for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
|
|
962
|
+
if (charH < 10) return null;
|
|
963
|
+
const cand = comps.filter((c) => c.y1 - c.y0 >= charH * 0.15).sort((a, b) => a.x0 - b.x0);
|
|
964
|
+
const first = cand[0];
|
|
965
|
+
if (!first) return null;
|
|
966
|
+
const cl = cand.filter((c) => c.x0 < first.x1 && c.x1 > first.x0);
|
|
967
|
+
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));
|
|
968
|
+
const cw = x1 - x0, ch = y1 - y0;
|
|
969
|
+
if (cw < ch * 0.8 || cw > ch * 1.25) return null;
|
|
970
|
+
const next = cand.find((c) => c.x0 >= x1);
|
|
971
|
+
if (next && next.x0 - x1 < ch * 0.3) return null;
|
|
972
|
+
let inkN = 0;
|
|
973
|
+
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++;
|
|
974
|
+
const fill = inkN / (cw * ch);
|
|
975
|
+
const rel = ch / charH;
|
|
976
|
+
const ring2 = (c) => {
|
|
977
|
+
const a = c.x1 - c.x0, b = c.y1 - c.y0;
|
|
978
|
+
return a >= b * 0.75 && a <= b * 1.33;
|
|
979
|
+
};
|
|
980
|
+
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));
|
|
981
|
+
const span = (y) => {
|
|
982
|
+
let n = 0;
|
|
983
|
+
for (let x = x0; x < x1; x++) if (cl.some((c) => label[y * w + x] === c.id)) n++;
|
|
984
|
+
return n;
|
|
985
|
+
};
|
|
986
|
+
const band = Math.max(1, Math.round(ch * 0.15));
|
|
987
|
+
const edgeFull = (from, dir) => {
|
|
988
|
+
let m = 0;
|
|
989
|
+
for (let k = 0; k < band; k++) m = Math.max(m, span(from + dir * k));
|
|
990
|
+
return m >= cw * 0.8;
|
|
991
|
+
};
|
|
992
|
+
let midRuns = 0;
|
|
993
|
+
{
|
|
994
|
+
let prev = false;
|
|
995
|
+
const my = y0 + (ch >> 1);
|
|
996
|
+
for (let x = x0; x < x1; x++) {
|
|
997
|
+
const on = cl.some((c) => label[my * w + x] === c.id);
|
|
998
|
+
if (on && !prev) midRuns++;
|
|
999
|
+
prev = on;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const hollowSquare = cl.length === 1 && fill < 0.5 && midRuns === 2 && edgeFull(y0, 1) && edgeFull(y1 - 1, -1);
|
|
1003
|
+
let mark = null;
|
|
1004
|
+
if (hollowSquare && rel >= 0.6 && rel <= 0.92) mark = "\u25A1";
|
|
1005
|
+
else if (nested && fill < 0.45 && rel >= 0.6) mark = "\u25CE";
|
|
1006
|
+
else if (cl.length === 1 && fill >= 0.9 && rel >= 0.28 && rel <= 0.6) mark = "\u25AA";
|
|
1007
|
+
else if (cl.length === 1 && fill >= 0.65 && fill <= 0.88 && rel >= 0.5 && rel <= 0.8) mark = "\u25CF";
|
|
1008
|
+
if (!mark) return null;
|
|
1009
|
+
return { mark, covers: firstX <= x1 + ch * 0.3 };
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/ocr/glyph-restore.ts
|
|
1013
|
+
function restoreGlyphs(rgba, pageW, box, text, steps, stepPx, note = {}) {
|
|
1014
|
+
const chars = [...text];
|
|
1015
|
+
if (chars.length !== steps.length) return text;
|
|
1016
|
+
const gray = grayCrop(rgba, pageW, box);
|
|
1017
|
+
const c = { chars, cx: (i) => (steps[i] + 0.5) * stepPx, gray, w: box.w, h: box.h, ink: inkStats(gray) };
|
|
1018
|
+
if (/[[\]]/.test(text)) brackets(c);
|
|
1019
|
+
if (/['"]/.test(text)) quotes(c);
|
|
1020
|
+
if (/(^|\D)[1-9](\D|$)/.test(text)) circled(c);
|
|
1021
|
+
if (text.includes("*")) stars(c);
|
|
1022
|
+
if (/[\u00b7\u2022]/.test(text)) discs(c);
|
|
1023
|
+
gaps(c);
|
|
1024
|
+
note.ringLead = ring(c);
|
|
1025
|
+
if (/[I1Ⅰ-Ⅲ]|^\s*\./.test(text)) roman(c);
|
|
1026
|
+
return chars.join("");
|
|
1027
|
+
}
|
|
1028
|
+
function brackets({ chars, cx, gray, w, h, ink }) {
|
|
1029
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1030
|
+
if (chars[i] !== "[" && chars[i] !== "]") continue;
|
|
1031
|
+
const f = bracketFeatures(gray, w, h, ink, cx(i));
|
|
1032
|
+
const shape = f && bracketShape(f, chars[i] === "]");
|
|
1033
|
+
if (shape) chars[i] = shape;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function quotes({ chars, cx, gray, w, h, ink }) {
|
|
1037
|
+
const year = (i) => chars[i] === "'" && !/[\p{L}\p{N}]/u.test(_nullishCoalesce(chars[i - 1], () => ( ""))) && /^\d{2}(?!\d)/.test(chars.slice(i + 1, i + 4).join(""));
|
|
1038
|
+
const single = { "'": chars.filter((c, i) => c === "'" && !year(i)).length === 1, '"': chars.filter((c) => c === '"').length === 1 };
|
|
1039
|
+
const open = { "'": false, '"': false };
|
|
1040
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1041
|
+
const c = chars[i];
|
|
1042
|
+
const kind = c === "'" || c === "\u2018" || c === "\u2019" ? "'" : c === '"' || c === "\u201C" || c === "\u201D" ? '"' : null;
|
|
1043
|
+
if (!kind) continue;
|
|
1044
|
+
if (c !== kind) {
|
|
1045
|
+
open[kind] = c === "\u2018" || c === "\u201C";
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (open[kind]) {
|
|
1049
|
+
open[kind] = false;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (!year(i) && !single[kind]) continue;
|
|
1053
|
+
const r = quoteHead(gray, w, h, ink, cx(i));
|
|
1054
|
+
if (r !== null && r >= 0.47) {
|
|
1055
|
+
chars[i] = kind === "'" ? "\u2018" : "\u201C";
|
|
1056
|
+
open[kind] = true;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function circled({ chars, cx, gray, w, h, ink }) {
|
|
1061
|
+
const at = chars.flatMap((c, i) => /^[1-9]$/.test(c) && !/\d/.test(_nullishCoalesce(chars[i - 1], () => ( ""))) && !/\d/.test(_nullishCoalesce(chars[i + 1], () => ( ""))) ? [i] : []);
|
|
1062
|
+
if (!at.length) return;
|
|
1063
|
+
const hit = circledAt(gray, w, h, ink, at.map(cx));
|
|
1064
|
+
at.forEach((i, k) => {
|
|
1065
|
+
if (hit[k]) chars[i] = String.fromCharCode(9311 + Number(chars[i]));
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
function stars({ chars, cx, gray, w, h, ink }) {
|
|
1069
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1070
|
+
if (chars[i] !== "*" || chars[i - 1] === "*") continue;
|
|
1071
|
+
let e = i;
|
|
1072
|
+
while (chars[e + 1] === "*") e++;
|
|
1073
|
+
const n = starRun(gray, w, h, ink, cx(i));
|
|
1074
|
+
if (n > e - i + 1 && n <= 4) chars[e] += "*".repeat(n - (e - i + 1));
|
|
1075
|
+
i = e;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function discs({ chars, cx, gray, w, h, ink }) {
|
|
1079
|
+
for (let i = 0; i < chars.length; i++) if (/^[\u00b7\u2022]$/.test(chars[i]) && discAt(gray, w, h, ink, cx(i))) chars[i] = "\u25CF";
|
|
1080
|
+
}
|
|
1081
|
+
function gaps({ chars, cx, gray, w, h, ink }) {
|
|
1082
|
+
const { glyphs, bandH } = gapGlyphs(gray, w, h, ink);
|
|
1083
|
+
if (!glyphs.length) return;
|
|
1084
|
+
const live = chars.map((c, i) => i).filter((i) => chars[i].trim());
|
|
1085
|
+
if (!live.length) return;
|
|
1086
|
+
const d = [];
|
|
1087
|
+
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]));
|
|
1088
|
+
const pitch = d.length >= 2 ? d.sort((a, b) => a - b)[d.length >> 1] : bandH;
|
|
1089
|
+
const nearTo = (g) => (i) => Math.abs(cx(i) - (g.x0 + g.x1) / 2) <= Math.max(pitch * 0.45, (g.x1 - g.x0) / 2 + 1);
|
|
1090
|
+
const loose = glyphs.filter((g) => /^[\u00b7.]/.test(g.mark) && !live.some(nearTo(g)));
|
|
1091
|
+
for (const g of glyphs.sort((a, b) => a.x0 - b.x0)) {
|
|
1092
|
+
const mid = (g.x0 + g.x1) / 2;
|
|
1093
|
+
const near = nearTo(g);
|
|
1094
|
+
const cover = live.filter(near);
|
|
1095
|
+
if (g.mark === "\xB7") {
|
|
1096
|
+
const sp = chars.findIndex((c, i) => c === " " && near(i));
|
|
1097
|
+
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;
|
|
1098
|
+
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";
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (cover.length) {
|
|
1102
|
+
if (g.mark.startsWith("\u25A1") && cover.length === 1 && /^[0Oo\u25cb]$/.test(chars[cover[0]])) chars[cover[0]] = "\u25A1";
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (g.mark === "\u25A1\0") continue;
|
|
1106
|
+
const before = live.filter((i) => cx(i) < mid), after = live.filter((i) => cx(i) > mid);
|
|
1107
|
+
if (!before.length) {
|
|
1108
|
+
if (after.length && /^[\u3002\u00b7\u300c\u25b2]$/.test(g.mark) && /^[가-힣]/.test(chars[after[0]])) chars[after[0]] = g.mark + chars[after[0]];
|
|
1109
|
+
else if (g.mark === "-" && /^[\dⅰ-ⅹivx]+\s*-$/.test(chars.join("").trim())) chars[after[0]] = "-" + chars[after[0]];
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (!after.length && g.mark !== ".") continue;
|
|
1113
|
+
if (g.mark === "/" && !(/\d$/.test(chars[before[before.length - 1]]) && /^\d/.test(chars[after[0]]))) continue;
|
|
1114
|
+
const prev = chars[before[before.length - 1]], next = after.length ? chars[after[0]] : "";
|
|
1115
|
+
if (g.mark === "\xB7" || g.mark === ".") {
|
|
1116
|
+
if (loose.some((o) => o !== g && Math.abs((o.x0 + o.x1) / 2 - mid) <= pitch * 1.5)) continue;
|
|
1117
|
+
if (/[\u2026.\u00b7]$/.test(prev) || /^[\u2026.\u00b7]/.test(next)) continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (g.mark === "\xB7") {
|
|
1120
|
+
if (/^\d/.test(next)) continue;
|
|
1121
|
+
const sp = chars.findIndex((c, i) => c === " " && near(i));
|
|
1122
|
+
if (sp >= 0) {
|
|
1123
|
+
chars[sp] = g.mark;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
chars[before[before.length - 1]] += g.mark;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function ring({ chars, cx, gray, w, h, ink }) {
|
|
1131
|
+
const k = chars.findIndex((c) => c.trim());
|
|
1132
|
+
if (k < 0 || !/^[\u3147Oo0\u25cb]$/.test(chars[k])) return false;
|
|
1133
|
+
const rest = chars.slice(k + 1).join("").trimStart();
|
|
1134
|
+
if (!/^[(\[「【<]?[가-힣]/.test(rest)) return false;
|
|
1135
|
+
const r = ringBullet(gray, w, h, ink, cx(k));
|
|
1136
|
+
if (r) chars[k] = r;
|
|
1137
|
+
return r !== null;
|
|
1138
|
+
}
|
|
1139
|
+
function roman({ chars, cx, gray, w, h, ink }) {
|
|
1140
|
+
const ROMAN = /^[IⅠ-Ⅲ]$/, LATIN = /[A-Za-z]$/;
|
|
1141
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1142
|
+
if (chars[i] !== "1" || /[\dA-Za-z]$/.test(_nullishCoalesce(chars[i - 1], () => ( ""))) || /^[\dA-Za-z,]/.test(_nullishCoalesce(chars[i + 1], () => ( "")))) continue;
|
|
1143
|
+
if (serifOne(gray, w, h, ink, cx(i))) chars[i] = "\u2160";
|
|
1144
|
+
}
|
|
1145
|
+
const inRoman = (i) => ROMAN.test(chars[i]) || chars[i] === "1" && (ROMAN.test(_nullishCoalesce(chars[i - 1], () => ( ""))) || ROMAN.test(_nullishCoalesce(chars[i + 1], () => ( ""))));
|
|
1146
|
+
const k = chars.findIndex((c) => c.trim());
|
|
1147
|
+
if (chars[k] === "." && chars.slice(k + 1).some((c) => /[가-힣A-Za-z0-9]/.test(c))) {
|
|
1148
|
+
const n = romanStems(gray, w, h, ink, -Infinity, cx(k));
|
|
1149
|
+
if (n) chars[k] = String.fromCharCode(8543 + n) + chars[k];
|
|
1150
|
+
}
|
|
1151
|
+
for (let s = 0; s < chars.length; ) {
|
|
1152
|
+
if (!inRoman(s) || s > 0 && LATIN.test(chars[s - 1])) {
|
|
1153
|
+
s++;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
let e = s;
|
|
1157
|
+
while (e < chars.length && inRoman(e)) e++;
|
|
1158
|
+
const tok = chars.slice(s, e).join("");
|
|
1159
|
+
if (e < chars.length && /^[A-Za-z]/.test(chars[e])) {
|
|
1160
|
+
s = e;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
let p = s - 1, q = e;
|
|
1164
|
+
while (p >= 0 && !chars[p].trim()) p--;
|
|
1165
|
+
while (q < chars.length && !chars[q].trim()) q++;
|
|
1166
|
+
const n = romanStems(gray, w, h, ink, p >= 0 ? cx(p) : -Infinity, q < chars.length ? cx(q) : Infinity);
|
|
1167
|
+
const canon = n ? [["\u2160", "I"], ["\u2161", "II"], ["\u2162", "III"]][n - 1] : null;
|
|
1168
|
+
if (canon && !canon.includes(tok)) {
|
|
1169
|
+
chars[s] = canon[0];
|
|
1170
|
+
for (let i = s + 1; i < e; i++) chars[i] = "";
|
|
1171
|
+
}
|
|
1172
|
+
s = e;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
573
1175
|
|
|
574
1176
|
// src/ocr/postprocess.ts
|
|
575
1177
|
var HANGUL_START = /^[\uac00-\ud7a3]/;
|
|
576
|
-
function restoreSymbols(text) {
|
|
1178
|
+
function restoreSymbols(text, ringDecided = false) {
|
|
577
1179
|
let s = text;
|
|
578
|
-
s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
|
|
1180
|
+
if (!ringDecided) s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
|
|
579
1181
|
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);
|
|
580
1182
|
s = s.replace(/[\u2206\u0394]/g, "\u25B3");
|
|
581
1183
|
s = s.replace(/\(cid:\d*\)/g, "");
|
|
@@ -695,7 +1297,7 @@ function bandBoxes(b, bands, pageH) {
|
|
|
695
1297
|
}
|
|
696
1298
|
return out;
|
|
697
1299
|
}
|
|
698
|
-
function lineCrop(rgba, pageW, box, rot) {
|
|
1300
|
+
function lineCrop(rgba, pageW, box, rot, keep) {
|
|
699
1301
|
const srcW = rot === 0 ? box.w : box.h;
|
|
700
1302
|
const srcH = rot === 0 ? box.h : box.w;
|
|
701
1303
|
const rw = Math.min(REC_MAX_WIDTH, Math.max(16, Math.round(srcW * REC_HEIGHT / srcH)));
|
|
@@ -730,6 +1332,10 @@ function lineCrop(rgba, pageW, box, rot) {
|
|
|
730
1332
|
const wx = sx - x0;
|
|
731
1333
|
const i00 = at(x0, y0), i01 = at(x1, y0), i10 = at(x0, y1), i11 = at(x1, y1);
|
|
732
1334
|
const o = (dy * rw + dx) * 3;
|
|
1335
|
+
if (keep && rot === 0 && (sx < keep.x0 || sx >= keep.x1 || sy < keep.y0 || sy >= keep.y1)) {
|
|
1336
|
+
rgb[o] = rgb[o + 1] = rgb[o + 2] = keep.bg;
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
733
1339
|
for (let c = 0; c < 3; c++) {
|
|
734
1340
|
const top = rgba[i00 + c] * (1 - wx) + rgba[i01 + c] * wx;
|
|
735
1341
|
const bottom = rgba[i10 + c] * (1 - wx) + rgba[i11 + c] * wx;
|
|
@@ -763,6 +1369,7 @@ var DEFAULT_OCR_TUNING = Object.freeze({
|
|
|
763
1369
|
textScore: 0.5,
|
|
764
1370
|
recBatch: 1,
|
|
765
1371
|
splitTall: true,
|
|
1372
|
+
trimEdges: true,
|
|
766
1373
|
minInkContrast: 35,
|
|
767
1374
|
postprocess: true,
|
|
768
1375
|
tightBoxes: true,
|
|
@@ -774,6 +1381,7 @@ var REC_MIN_WIDTH = 320;
|
|
|
774
1381
|
var REC_BATCH_MAX_PIXELS = 48 * 16e3;
|
|
775
1382
|
var TALL_RATIO = 1.5;
|
|
776
1383
|
var ROTATE_RATIO = 3;
|
|
1384
|
+
var KEEP_MARGIN = 0.06;
|
|
777
1385
|
var LEADER_MIN_DOTS = 4;
|
|
778
1386
|
var LEADER_CONTEXT = 0.5;
|
|
779
1387
|
var MIN_INK_RATIO = 0.01;
|
|
@@ -802,7 +1410,7 @@ var OcrEngine = class _OcrEngine {
|
|
|
802
1410
|
]);
|
|
803
1411
|
const sharpAny = sharpModRaw;
|
|
804
1412
|
const sharpMod = typeof sharpAny === "function" ? sharpAny : _nullishCoalesce(sharpAny.default, () => ( sharpAny));
|
|
805
|
-
const dir =
|
|
1413
|
+
const dir = _chunkWDQTFAXYcjs.getOcrModelsDir.call(void 0, );
|
|
806
1414
|
const sessionOpts = {
|
|
807
1415
|
graphOptimizationLevel: "all",
|
|
808
1416
|
executionProviders: ["cpu"],
|
|
@@ -810,11 +1418,11 @@ var OcrEngine = class _OcrEngine {
|
|
|
810
1418
|
// paddle2onnx 변환 잔여물 W 로그 폭주 억제
|
|
811
1419
|
};
|
|
812
1420
|
const [det, rec, dictYml] = await Promise.all([
|
|
813
|
-
ortMod.InferenceSession.create(_path.join.call(void 0, dir,
|
|
814
|
-
ortMod.InferenceSession.create(_path.join.call(void 0, dir,
|
|
815
|
-
_promises.readFile.call(void 0, _path.join.call(void 0, dir,
|
|
1421
|
+
ortMod.InferenceSession.create(_path.join.call(void 0, dir, _chunkWDQTFAXYcjs.OCR_DET_MODEL.filename), sessionOpts),
|
|
1422
|
+
ortMod.InferenceSession.create(_path.join.call(void 0, dir, _chunkWDQTFAXYcjs.OCR_REC_MODEL.filename), sessionOpts),
|
|
1423
|
+
_promises.readFile.call(void 0, _path.join.call(void 0, dir, _chunkWDQTFAXYcjs.OCR_REC_DICT.filename), "utf-8")
|
|
816
1424
|
]);
|
|
817
|
-
const dict =
|
|
1425
|
+
const dict = _chunkWDQTFAXYcjs.parseCharacterDict.call(void 0, dictYml);
|
|
818
1426
|
if (dict.length === 0) throw new Error("OCR \uC0AC\uC804 \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uBAA8\uB378 \uCE90\uC2DC\uB97C \uC0AD\uC81C \uD6C4 \uC7AC\uB2E4\uC6B4\uB85C\uB4DC\uD558\uC138\uC694");
|
|
819
1427
|
return new _OcrEngine({ det, rec, dict, ort: ortMod, sharp: sharpMod });
|
|
820
1428
|
}
|
|
@@ -859,6 +1467,8 @@ var OcrEngine = class _OcrEngine {
|
|
|
859
1467
|
continue;
|
|
860
1468
|
}
|
|
861
1469
|
}
|
|
1470
|
+
const trim = tuning.trimEdges ? edgeTrim(gray, b.w, b.h, ink) : null;
|
|
1471
|
+
const keep = trim ? { ...trim, bg: median(gray) } : void 0;
|
|
862
1472
|
const leaders = tuning.splitLeaders ? leaderRuns(gray, b.w, b.h, ink, LEADER_MIN_DOTS) : [];
|
|
863
1473
|
if (leaders.length) {
|
|
864
1474
|
const join2 = joins.length;
|
|
@@ -877,20 +1487,37 @@ var OcrEngine = class _OcrEngine {
|
|
|
877
1487
|
joins[join2].trailDots = dots;
|
|
878
1488
|
continue;
|
|
879
1489
|
}
|
|
1490
|
+
if (keep) jobs.push({ box: b, rot: 0, group, keep });
|
|
880
1491
|
jobs.push({ box: b, rot: 0, group: group++ });
|
|
881
1492
|
}
|
|
882
1493
|
const results = await this.recognizeJobs(rgba, width, jobs, tuning.recBatch);
|
|
883
1494
|
const best = /* @__PURE__ */ new Map();
|
|
1495
|
+
const score = (job, confidence) => confidence - (job.keep ? KEEP_MARGIN : 0);
|
|
884
1496
|
jobs.forEach((job, i) => {
|
|
885
1497
|
const r = results[i];
|
|
886
1498
|
if (!r) return;
|
|
887
1499
|
const cur = best.get(job.group);
|
|
888
|
-
if (!cur || r.confidence > cur.confidence) best.set(job.group, { job, ...r });
|
|
1500
|
+
if (!cur || score(job, r.confidence) > score(cur.job, cur.confidence)) best.set(job.group, { job, ...r });
|
|
889
1501
|
});
|
|
890
1502
|
let items = [];
|
|
891
|
-
for (const { job, text:
|
|
892
|
-
|
|
1503
|
+
for (const { job, text: read, confidence, steps, stepPx } of best.values()) {
|
|
1504
|
+
const note = {};
|
|
1505
|
+
const raw = tuning.postprocess && job.rot === 0 ? restoreGlyphs(rgba, width, job.box, read, steps, stepPx, note) : read;
|
|
1506
|
+
let text = tuning.postprocess ? restoreSymbols(raw.trim(), note.ringLead) : raw;
|
|
893
1507
|
if (!text.trim()) continue;
|
|
1508
|
+
if (tuning.postprocess && job.rot === 0 && !/^[◎●▪□■○ㅇ]/.test(text)) {
|
|
1509
|
+
const chars = [...read], k = chars.findIndex((c) => c.trim());
|
|
1510
|
+
if (k >= 0 && steps.length === chars.length) {
|
|
1511
|
+
const g = grayCrop(rgba, width, job.box);
|
|
1512
|
+
const b = leadingBullet(g, job.box.w, job.box.h, inkStats(g), (steps[k] + 0.5) * stepPx);
|
|
1513
|
+
const body = (_nullishCoalesce(text.match(/[가-힣]/g), () => ( []))).length >= 4;
|
|
1514
|
+
const miss = /^[Oo0•·ㆍ∙‧○]/.test(text);
|
|
1515
|
+
if (_optionalChain([b, 'optionalAccess', _12 => _12.mark]) === "\u25CE" && b.covers && /^[Oo0○]/.test(text)) text = b.mark + " " + text.slice(1).trimStart();
|
|
1516
|
+
else if (_optionalChain([b, 'optionalAccess', _13 => _13.mark]) === "\u25A1") {
|
|
1517
|
+
if (!b.covers && body) text = b.mark + " " + text;
|
|
1518
|
+
} else if (b && b.mark !== "\u25CE" && body) text = b.covers ? miss ? b.mark + " " + text.slice(1).trimStart() : text : b.mark + " " + text;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
894
1521
|
if (tuning.postprocess && /^\d/.test(text) && job.rot === 0) {
|
|
895
1522
|
const g = grayCrop(rgba, width, job.box);
|
|
896
1523
|
const tri = leadingTriangle(g, job.box.w, job.box.h, inkStats(g));
|
|
@@ -971,7 +1598,7 @@ var OcrEngine = class _OcrEngine {
|
|
|
971
1598
|
// ─── rec ─────────────────────────────────────────────
|
|
972
1599
|
/** 라인 작업들을 폭 비율 순으로 배치 인식 — 결과는 jobs 순서 */
|
|
973
1600
|
async recognizeJobs(rgba, pageW, jobs, batchSize) {
|
|
974
|
-
const crops = jobs.map((j) => lineCrop(rgba, pageW, j.box, j.rot));
|
|
1601
|
+
const crops = jobs.map((j) => lineCrop(rgba, pageW, j.box, j.rot, j.keep));
|
|
975
1602
|
const order = crops.map((_, i) => i).sort((a, b) => crops[a].w - crops[b].w);
|
|
976
1603
|
const results = new Array(jobs.length).fill(null);
|
|
977
1604
|
const plane = REC_HEIGHT;
|
|
@@ -1003,18 +1630,39 @@ var OcrEngine = class _OcrEngine {
|
|
|
1003
1630
|
const [, T, C] = logits.dims;
|
|
1004
1631
|
const data = logits.data;
|
|
1005
1632
|
idx.forEach((ci, k) => {
|
|
1006
|
-
|
|
1633
|
+
const r = ctcDecode(data.subarray(k * T * C, (k + 1) * T * C), T, C, this.dict);
|
|
1634
|
+
const job = jobs[ci], src = job.rot === 0 ? job.box.w : job.box.h;
|
|
1635
|
+
let kept = r;
|
|
1636
|
+
const last = crops[ci].w * T / bw + 1;
|
|
1637
|
+
const chars = r ? [...r.text] : [];
|
|
1638
|
+
if (r && job.rot === 0 && chars.length === r.steps.length && r.steps.some((t) => t > last) && !r.text.includes("(cid:")) {
|
|
1639
|
+
const tallCh = (c) => !/[\s.,:;\u00b7\u2026'"\u2018-\u201d`\-_~]/.test(c);
|
|
1640
|
+
const g = grayCrop(rgba, pageW, job.box);
|
|
1641
|
+
const inside = chars.filter((c, i) => r.steps[i] <= last && tallCh(c)).length;
|
|
1642
|
+
let budget = inside <= 2 ? tallInkCount(g, job.box.w, job.box.h, inkStats(g)) - inside : Infinity;
|
|
1643
|
+
const keep = chars.map((c, i) => r.steps[i] <= last || !tallCh(c) || budget-- > 0);
|
|
1644
|
+
if (!keep.every(Boolean)) kept = { ...r, text: chars.filter((_, i) => keep[i]).join(""), steps: r.steps.filter((_, i) => keep[i]) };
|
|
1645
|
+
}
|
|
1646
|
+
results[ci] = kept && { ...kept, stepPx: bw / T * (src / crops[ci].w) };
|
|
1007
1647
|
});
|
|
1008
1648
|
s = e;
|
|
1009
1649
|
}
|
|
1010
1650
|
return results;
|
|
1011
1651
|
}
|
|
1012
1652
|
};
|
|
1653
|
+
function median(gray) {
|
|
1654
|
+
const hist = new Uint32Array(256);
|
|
1655
|
+
for (let i = 0; i < gray.length; i++) hist[gray[i]]++;
|
|
1656
|
+
for (let v = 0, n = 0; v < 256; v++) if ((n += hist[v]) * 2 >= gray.length) return v;
|
|
1657
|
+
return 255;
|
|
1658
|
+
}
|
|
1013
1659
|
function ctcDecode(data, T, C, dict) {
|
|
1014
1660
|
let text = "";
|
|
1015
1661
|
let confSum = 0;
|
|
1016
1662
|
let confCount = 0;
|
|
1017
1663
|
let prev = -1;
|
|
1664
|
+
const runs = [];
|
|
1665
|
+
let open = [];
|
|
1018
1666
|
for (let t = 0; t < T; t++) {
|
|
1019
1667
|
const off = t * C;
|
|
1020
1668
|
let best = 0;
|
|
@@ -1028,7 +1676,12 @@ function ctcDecode(data, T, C, dict) {
|
|
|
1028
1676
|
}
|
|
1029
1677
|
const repeat = best === prev;
|
|
1030
1678
|
prev = best;
|
|
1031
|
-
if (best
|
|
1679
|
+
if (repeat && best !== 0) {
|
|
1680
|
+
for (const r of open) r[1] = t;
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
open = [];
|
|
1684
|
+
if (best === 0) continue;
|
|
1032
1685
|
let p = bestV;
|
|
1033
1686
|
if (p > 1.0001 || p < 0) {
|
|
1034
1687
|
let denom = 0;
|
|
@@ -1037,11 +1690,16 @@ function ctcDecode(data, T, C, dict) {
|
|
|
1037
1690
|
}
|
|
1038
1691
|
confSum += p;
|
|
1039
1692
|
confCount++;
|
|
1040
|
-
|
|
1041
|
-
|
|
1693
|
+
const tok = best >= 1 && best <= dict.length ? dict[best - 1] : best === dict.length + 1 ? " " : "";
|
|
1694
|
+
text += tok;
|
|
1695
|
+
for (const _ of tok) {
|
|
1696
|
+
const r = [t, t];
|
|
1697
|
+
runs.push(r);
|
|
1698
|
+
open.push(r);
|
|
1699
|
+
}
|
|
1042
1700
|
}
|
|
1043
1701
|
if (!text) return null;
|
|
1044
|
-
return { text, confidence: confCount > 0 ? confSum / confCount : 0 };
|
|
1702
|
+
return { text, confidence: confCount > 0 ? confSum / confCount : 0, steps: runs.map(([a, b]) => (a + b) / 2) };
|
|
1045
1703
|
}
|
|
1046
1704
|
function componentBoxes(prob, w, h, thresh = DEFAULT_OCR_TUNING.detThresh, boxThresh = DEFAULT_OCR_TUNING.detBoxThresh) {
|
|
1047
1705
|
const visited = new Uint8Array(w * h);
|
|
@@ -1092,7 +1750,7 @@ async function tryImport(name, loader) {
|
|
|
1092
1750
|
return await loader();
|
|
1093
1751
|
} catch (e) {
|
|
1094
1752
|
throw new Error(
|
|
1095
|
-
`\uB0B4\uC7A5 OCR \uC744 \uC0AC\uC6A9\uD558\uB824\uBA74 optional dependency '${name}' \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \`npm install ${name}\` \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.${
|
|
1753
|
+
`\uB0B4\uC7A5 OCR \uC744 \uC0AC\uC6A9\uD558\uB824\uBA74 optional dependency '${name}' \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \`npm install ${name}\` \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.${_chunkSTM3ORY7cjs.OPTIONAL_DEP_INSTALL_HINT} \uC6D0\uC778: ${e.message}`
|
|
1096
1754
|
);
|
|
1097
1755
|
}
|
|
1098
1756
|
}
|
|
@@ -1206,7 +1864,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
|
|
|
1206
1864
|
() => Promise.resolve().then(() => _interopRequireWildcard(require("@hyzyla/pdfium")))
|
|
1207
1865
|
);
|
|
1208
1866
|
if (mode === "builtin") {
|
|
1209
|
-
await
|
|
1867
|
+
await _chunkWDQTFAXYcjs.ensureOcrModels.call(void 0, (p) => {
|
|
1210
1868
|
if (p.phase === "download" && p.downloaded === 0) {
|
|
1211
1869
|
process.stderr.write(`[kordoc-ocr] ${p.spec.name} \uB2E4\uC6B4\uB85C\uB4DC \uC911 (~${p.spec.sizeMb}MB)...
|
|
1212
1870
|
`);
|
|
@@ -1221,10 +1879,10 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
|
|
|
1221
1879
|
const count = doc.getPageCount();
|
|
1222
1880
|
for (const pageNo of [...targets].filter((p) => p >= 1 && p <= count).sort((a, b) => a - b)) {
|
|
1223
1881
|
const page = doc.getPage(pageNo - 1);
|
|
1224
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
1882
|
+
_optionalChain([onProgress, 'optionalCall', _14 => _14(++done, targets.size)]);
|
|
1225
1883
|
try {
|
|
1226
1884
|
const blocks = await withTimeout(
|
|
1227
|
-
ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, _optionalChain([vectorOps, 'optionalAccess',
|
|
1885
|
+
ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, _optionalChain([vectorOps, 'optionalAccess', _15 => _15.get, 'call', _16 => _16(pageNo)]), _optionalChain([imageRegions, 'optionalAccess', _17 => _17.get, 'call', _18 => _18(pageNo)])),
|
|
1228
1886
|
PAGE_TIMEOUT_MS,
|
|
1229
1887
|
`OCR \uD398\uC774\uC9C0 ${pageNo} \uD0C0\uC784\uC544\uC6C3 (${PAGE_TIMEOUT_MS / 1e3}\uCD08)`
|
|
1230
1888
|
);
|
|
@@ -1260,7 +1918,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, ve
|
|
|
1260
1918
|
const upright = vectorOps ? rgba : deskewPage(rgba, rw, rh).rgba;
|
|
1261
1919
|
const stats = { droppedLowConf: 0 };
|
|
1262
1920
|
const ruling = vectorOps ? void 0 : detectRulingLines(upright, rw, rh, rh / pdfH);
|
|
1263
|
-
const items = await engine.recognizePage(upright, rw, rh, stats, void 0, _optionalChain([ruling, 'optionalAccess',
|
|
1921
|
+
const items = await engine.recognizePage(upright, rw, rh, stats, regions ? REGION_TUNING : void 0, _optionalChain([ruling, 'optionalAccess', _19 => _19.cellDividers]));
|
|
1264
1922
|
if (stats.droppedLowConf > 0) {
|
|
1265
1923
|
warnings.push({
|
|
1266
1924
|
page: pageNo,
|
|
@@ -1281,7 +1939,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, ve
|
|
|
1281
1939
|
return regions.flatMap((r, k) => {
|
|
1282
1940
|
let own = items.filter((it) => inside(it, r));
|
|
1283
1941
|
const near = reads[k];
|
|
1284
|
-
if (_optionalChain([near, 'optionalAccess',
|
|
1942
|
+
if (_optionalChain([near, 'optionalAccess', _20 => _20.length]) && meanConfidence(near) > meanConfidence(own)) own = near;
|
|
1285
1943
|
return own.length ? ocrItemsToBlocks(own, pageNo, pdfW, pdfH, scale, ruling && rulingToPdfLines(ruling, scale, pdfH), detectTables) : [];
|
|
1286
1944
|
});
|
|
1287
1945
|
}
|
|
@@ -1312,6 +1970,7 @@ function halve(src, w, h) {
|
|
|
1312
1970
|
}
|
|
1313
1971
|
return { rgba: out, width: W, height: H };
|
|
1314
1972
|
}
|
|
1973
|
+
var REGION_TUNING = Object.freeze({ ...DEFAULT_OCR_TUNING, trimEdges: false });
|
|
1315
1974
|
function meanConfidence(items) {
|
|
1316
1975
|
let n = 0, sum = 0;
|
|
1317
1976
|
for (const it of items) {
|
|
@@ -1332,7 +1991,7 @@ async function closerReads(rgba, rw, rh, pdfH, hi, regions, engine) {
|
|
|
1332
1991
|
}
|
|
1333
1992
|
const crop = new Uint8Array(cw * ch * 4);
|
|
1334
1993
|
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);
|
|
1335
|
-
const items = await engine.recognizePage(crop, cw, ch);
|
|
1994
|
+
const items = await engine.recognizePage(crop, cw, ch, void 0, REGION_TUNING);
|
|
1336
1995
|
out.push(items.map((it) => ({ ...it, x: (it.x + x0) / 2, y: (it.y + y0) / 2, w: it.w / 2, h: it.h / 2 })));
|
|
1337
1996
|
}
|
|
1338
1997
|
return out;
|
|
@@ -1353,14 +2012,14 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
|
|
|
1353
2012
|
isHidden: false
|
|
1354
2013
|
};
|
|
1355
2014
|
});
|
|
1356
|
-
return
|
|
2015
|
+
return _chunkWDQTFAXYcjs.extractPageBlocksWithLines.call(void 0, norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
|
|
1357
2016
|
}
|
|
1358
2017
|
async function tryImport2(name, loader) {
|
|
1359
2018
|
try {
|
|
1360
2019
|
return await loader();
|
|
1361
2020
|
} catch (e) {
|
|
1362
2021
|
throw new Error(
|
|
1363
|
-
`OCR \uC744 \uC0AC\uC6A9\uD558\uB824\uBA74 optional dependency '${name}' \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \`npm install ${name}\` \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.${
|
|
2022
|
+
`OCR \uC744 \uC0AC\uC6A9\uD558\uB824\uBA74 optional dependency '${name}' \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \`npm install ${name}\` \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.${_chunkSTM3ORY7cjs.OPTIONAL_DEP_INSTALL_HINT} \uC6D0\uC778: ${e.message}`
|
|
1364
2023
|
);
|
|
1365
2024
|
}
|
|
1366
2025
|
}
|
|
@@ -1399,4 +2058,4 @@ function bgraToRgba(bgra) {
|
|
|
1399
2058
|
|
|
1400
2059
|
|
|
1401
2060
|
exports.getOcrEngine = getOcrEngine; exports.detectRulingLines = detectRulingLines; exports.rulingToPdfLines = rulingToPdfLines; exports.deskewPage = deskewPage; exports.MAX_OCR_PIXELS = MAX_OCR_PIXELS; exports.runPdfOcr = runPdfOcr; exports.ocrItemsToBlocks = ocrItemsToBlocks;
|
|
1402
|
-
//# sourceMappingURL=chunk-
|
|
2061
|
+
//# sourceMappingURL=chunk-FKUDAIJI.cjs.map
|