kordoc 4.1.0 → 4.2.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 +38 -11
- package/dist/{-AS4IABL2.js → -BSO3CDQQ.js} +13 -10
- package/dist/chunk-35U3MVA2.cjs +560 -0
- package/dist/chunk-35U3MVA2.cjs.map +1 -0
- package/dist/chunk-54WVWWT4.js +13 -0
- package/dist/chunk-54WVWWT4.js.map +1 -0
- package/dist/{chunk-UOO7LSDG.js → chunk-7FCNOKOR.js} +1 -9
- package/dist/chunk-7FCNOKOR.js.map +1 -0
- package/dist/{chunk-4KI23VHA.js → chunk-7IMNZ2BL.js} +16 -130
- package/dist/chunk-7IMNZ2BL.js.map +1 -0
- package/dist/{chunk-GQVSHGG4.js → chunk-BHILWIE6.js} +2 -2
- package/dist/{chunk-WO52HVQJ.js → chunk-BKBYTPSL.js} +2 -2
- package/dist/{parser-XX4QTDFQ.js → chunk-EZHWPKCH.js} +1343 -2020
- package/dist/chunk-EZHWPKCH.js.map +1 -0
- package/dist/chunk-F5ECB53I.js +505 -0
- package/dist/chunk-F5ECB53I.js.map +1 -0
- package/dist/chunk-GEPINOL6.js +83 -0
- package/dist/chunk-GEPINOL6.js.map +1 -0
- package/dist/chunk-H7OWEYH2.cjs +243 -0
- package/dist/chunk-H7OWEYH2.cjs.map +1 -0
- package/dist/chunk-HTCKGSSW.js +130 -0
- package/dist/chunk-HTCKGSSW.js.map +1 -0
- package/dist/chunk-J2RZ444H.js +243 -0
- package/dist/chunk-J2RZ444H.js.map +1 -0
- package/dist/chunk-KRAYUNFM.js +560 -0
- package/dist/chunk-KRAYUNFM.js.map +1 -0
- package/dist/{parser-PEOYBXBO.js → chunk-L5JWKIOT.js} +1345 -2024
- package/dist/chunk-L5JWKIOT.js.map +1 -0
- package/dist/{chunk-37VKMUST.cjs → chunk-LSXXGUWA.cjs} +34 -148
- package/dist/chunk-LSXXGUWA.cjs.map +1 -0
- package/dist/chunk-NNDVFNMJ.js +246 -0
- package/dist/chunk-NNDVFNMJ.js.map +1 -0
- package/dist/{chunk-37HJHCPA.js → chunk-PLTG6ZRF.js} +3 -3
- package/dist/{chunk-F2ZU3IZG.js → chunk-QOC7S5VE.js} +2 -2
- package/dist/{chunk-SSTK6IKK.js → chunk-QSB2RTX5.js} +14 -5
- package/dist/chunk-QSB2RTX5.js.map +1 -0
- package/dist/{parser-N4A7UGDK.cjs → chunk-R22KDUKW.cjs} +1309 -1989
- package/dist/chunk-R22KDUKW.cjs.map +1 -0
- package/dist/chunk-RBUBZ2TA.cjs +130 -0
- package/dist/chunk-RBUBZ2TA.cjs.map +1 -0
- package/dist/{chunk-YOO6ET6M.js → chunk-TCJRGFYY.js} +11 -1
- package/dist/chunk-TCJRGFYY.js.map +1 -0
- package/dist/{chunk-AX2R5Q2N.js → chunk-WJSGYVFD.js} +3 -3
- package/dist/{chunk-L2XQQTOY.js → chunk-WWYAYZ5U.js} +146 -47
- package/dist/chunk-WWYAYZ5U.js.map +1 -0
- package/dist/{chunk-O5VS7RIR.js → chunk-Z6LPEWIX.js} +3 -3
- package/dist/{chunk-O5VS7RIR.js.map → chunk-Z6LPEWIX.js.map} +1 -1
- package/dist/chunks-LRFS5NJE.js +10 -0
- package/dist/cli.js +77 -20
- package/dist/cli.js.map +1 -1
- package/dist/{detect-YEZX2XCF.js → detect-FX2ZHKZP.js} +4 -2
- package/dist/{formula-RXVSQPXI.js → formula-KSNAEBZ2.js} +23 -232
- package/dist/formula-KSNAEBZ2.js.map +1 -0
- package/dist/{formula-5NKVS2LR.cjs → formula-SB3NHDUM.cjs} +30 -240
- package/dist/formula-SB3NHDUM.cjs.map +1 -0
- package/dist/{formula-JCNF43NE.js → formula-SXC6LRRC.js} +23 -233
- package/dist/formula-SXC6LRRC.js.map +1 -0
- package/dist/image-ocr-DALT7PFQ.js +65 -0
- package/dist/image-ocr-DALT7PFQ.js.map +1 -0
- package/dist/image-ocr-N5Z32R4U.cjs +66 -0
- package/dist/image-ocr-N5Z32R4U.cjs.map +1 -0
- package/dist/image-ocr-SLUEM3FA.js +69 -0
- package/dist/image-ocr-SLUEM3FA.js.map +1 -0
- package/dist/index.cjs +555 -447
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -5
- package/dist/index.d.ts +11 -5
- package/dist/index.js +157 -49
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +24 -22
- package/dist/mcp.js.map +1 -1
- package/dist/models-KA4GLQK5.js +25 -0
- package/dist/parser-33TK5SMH.js +800 -0
- package/dist/parser-33TK5SMH.js.map +1 -0
- package/dist/parser-F5KY3YSD.js +804 -0
- package/dist/parser-F5KY3YSD.js.map +1 -0
- package/dist/parser-R4CXIXRN.cjs +803 -0
- package/dist/parser-R4CXIXRN.cjs.map +1 -0
- package/dist/pdf-ocr-34F5AQKK.js +12 -0
- package/dist/pdf-ocr-BXQZKU3N.js +15 -0
- package/dist/pdf-ocr-WGWZDHMV.cjs +13 -0
- package/dist/pdf-ocr-WGWZDHMV.cjs.map +1 -0
- package/dist/{profile-io-PLDQFPJA.js → profile-io-CBYHKLFU.js} +3 -3
- package/dist/profile-io-CBYHKLFU.js.map +1 -0
- package/dist/{rasterize-WWNQLKYW.js → rasterize-AWMM2CSW.js} +2 -2
- package/dist/render-Y3IE2FIK.js +10 -0
- package/dist/render-Y3IE2FIK.js.map +1 -0
- package/dist/seal-7RXFCRMT.js +10 -0
- package/dist/seal-7RXFCRMT.js.map +1 -0
- package/dist/{watch-SFHXZALC.js → watch-HKXCISVO.js} +12 -11
- package/dist/watch-HKXCISVO.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-37VKMUST.cjs.map +0 -1
- package/dist/chunk-4KI23VHA.js.map +0 -1
- package/dist/chunk-L2XQQTOY.js.map +0 -1
- package/dist/chunk-SSTK6IKK.js.map +0 -1
- package/dist/chunk-UOO7LSDG.js.map +0 -1
- package/dist/chunk-YOO6ET6M.js.map +0 -1
- package/dist/chunks-WVMGF7JL.js +0 -10
- package/dist/formula-5NKVS2LR.cjs.map +0 -1
- package/dist/formula-JCNF43NE.js.map +0 -1
- package/dist/formula-RXVSQPXI.js.map +0 -1
- package/dist/parser-N4A7UGDK.cjs.map +0 -1
- package/dist/parser-PEOYBXBO.js.map +0 -1
- package/dist/parser-XX4QTDFQ.js.map +0 -1
- package/dist/provider-4FAYHJ6N.cjs +0 -39
- package/dist/provider-4FAYHJ6N.cjs.map +0 -1
- package/dist/provider-C6IOGIS5.js +0 -37
- package/dist/provider-C6IOGIS5.js.map +0 -1
- package/dist/provider-UIBW2MIH.js +0 -39
- package/dist/provider-UIBW2MIH.js.map +0 -1
- package/dist/render-T7S6H6AN.js +0 -10
- package/dist/seal-7PTL6MXH.js +0 -10
- package/dist/watch-SFHXZALC.js.map +0 -1
- /package/dist/{-AS4IABL2.js.map → -BSO3CDQQ.js.map} +0 -0
- /package/dist/{chunk-GQVSHGG4.js.map → chunk-BHILWIE6.js.map} +0 -0
- /package/dist/{chunk-WO52HVQJ.js.map → chunk-BKBYTPSL.js.map} +0 -0
- /package/dist/{chunk-37HJHCPA.js.map → chunk-PLTG6ZRF.js.map} +0 -0
- /package/dist/{chunk-F2ZU3IZG.js.map → chunk-QOC7S5VE.js.map} +0 -0
- /package/dist/{chunk-AX2R5Q2N.js.map → chunk-WJSGYVFD.js.map} +0 -0
- /package/dist/{chunks-WVMGF7JL.js.map → chunks-LRFS5NJE.js.map} +0 -0
- /package/dist/{detect-YEZX2XCF.js.map → detect-FX2ZHKZP.js.map} +0 -0
- /package/dist/{profile-io-PLDQFPJA.js.map → models-KA4GLQK5.js.map} +0 -0
- /package/dist/{render-T7S6H6AN.js.map → pdf-ocr-34F5AQKK.js.map} +0 -0
- /package/dist/{seal-7PTL6MXH.js.map → pdf-ocr-BXQZKU3N.js.map} +0 -0
- /package/dist/{rasterize-WWNQLKYW.js.map → rasterize-AWMM2CSW.js.map} +0 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkH7OWEYH2cjs = require('./chunk-H7OWEYH2.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkR22KDUKWcjs = require('./chunk-R22KDUKW.cjs');
|
|
8
|
+
|
|
9
|
+
// src/ocr/ruling-lines.ts
|
|
10
|
+
var INK_LUMA_MAX = 190;
|
|
11
|
+
var MIN_LINE_LENGTH_PT = 20;
|
|
12
|
+
var MAX_LINE_WIDTH_PT = 2.5;
|
|
13
|
+
var GAP_TOL_PX = 2;
|
|
14
|
+
var BAND_OVERLAP_RATIO = 0.5;
|
|
15
|
+
var SURROUND_INK_RATIO = 0.35;
|
|
16
|
+
function detectRulingLines(rgba, width, height, scale) {
|
|
17
|
+
const minLenPx = Math.round(MIN_LINE_LENGTH_PT * scale);
|
|
18
|
+
const maxThickPx = Math.max(1, Math.floor(MAX_LINE_WIDTH_PT * scale));
|
|
19
|
+
const ink = new Uint8Array(width * height);
|
|
20
|
+
for (let p = 0, i = 0; p < ink.length; p++, i += 4) {
|
|
21
|
+
const luma = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
|
|
22
|
+
if (luma <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1;
|
|
23
|
+
}
|
|
24
|
+
const horizontals = detectBands(ink, width, height, minLenPx, maxThickPx, false);
|
|
25
|
+
const verticals = detectBands(ink, width, height, minLenPx, maxThickPx, true);
|
|
26
|
+
return { horizontals, verticals };
|
|
27
|
+
}
|
|
28
|
+
function detectBands(ink, width, height, minLenPx, maxThickPx, transpose) {
|
|
29
|
+
const lines = transpose ? width : height;
|
|
30
|
+
const span = transpose ? height : width;
|
|
31
|
+
const at = transpose ? (line, pos) => ink[pos * width + line] : (line, pos) => ink[line * width + pos];
|
|
32
|
+
const out = [];
|
|
33
|
+
let open = [];
|
|
34
|
+
const surroundInkRatio = (b, side) => {
|
|
35
|
+
if (side < 0 || side >= lines) return 0;
|
|
36
|
+
let dark = 0, total = 0;
|
|
37
|
+
for (let pos = b.lo; pos <= b.hi; pos += 3) {
|
|
38
|
+
total++;
|
|
39
|
+
if (at(side, pos) === 1) dark++;
|
|
40
|
+
}
|
|
41
|
+
return total > 0 ? dark / total : 0;
|
|
42
|
+
};
|
|
43
|
+
const emit = (b) => {
|
|
44
|
+
const thick = b.last - b.first + 1;
|
|
45
|
+
if (thick > maxThickPx) return;
|
|
46
|
+
if (surroundInkRatio(b, b.first - 2) >= SURROUND_INK_RATIO && surroundInkRatio(b, b.last + 2) >= SURROUND_INK_RATIO) return;
|
|
47
|
+
const center = (b.first + b.last) / 2;
|
|
48
|
+
out.push(
|
|
49
|
+
transpose ? { x1: center, y1: b.lo, x2: center, y2: b.hi, thicknessPx: thick } : { x1: b.lo, y1: center, x2: b.hi, y2: center, thicknessPx: thick }
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
for (let line = 0; line < lines; line++) {
|
|
53
|
+
const runs = [];
|
|
54
|
+
let runStart = -1;
|
|
55
|
+
let gap = 0;
|
|
56
|
+
for (let pos = 0; pos <= span; pos++) {
|
|
57
|
+
const on = pos < span && at(line, pos) === 1;
|
|
58
|
+
if (on) {
|
|
59
|
+
if (runStart < 0) runStart = pos;
|
|
60
|
+
gap = 0;
|
|
61
|
+
} else if (runStart >= 0) {
|
|
62
|
+
if (++gap > GAP_TOL_PX || pos >= span) {
|
|
63
|
+
const hi = pos - gap;
|
|
64
|
+
if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
|
|
65
|
+
runStart = -1;
|
|
66
|
+
gap = 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const next = [];
|
|
71
|
+
const used = /* @__PURE__ */ new Set();
|
|
72
|
+
for (const band of open) {
|
|
73
|
+
if (band.last !== line - 1) {
|
|
74
|
+
emit(band);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let merged = false;
|
|
78
|
+
for (let r = 0; r < runs.length; r++) {
|
|
79
|
+
if (used.has(r)) continue;
|
|
80
|
+
const run = runs[r];
|
|
81
|
+
const overlap = Math.min(band.hi, run.hi) - Math.max(band.lo, run.lo) + 1;
|
|
82
|
+
const shorter = Math.min(band.hi - band.lo, run.hi - run.lo) + 1;
|
|
83
|
+
if (overlap >= shorter * BAND_OVERLAP_RATIO) {
|
|
84
|
+
band.lo = Math.min(band.lo, run.lo);
|
|
85
|
+
band.hi = Math.max(band.hi, run.hi);
|
|
86
|
+
band.last = line;
|
|
87
|
+
next.push(band);
|
|
88
|
+
used.add(r);
|
|
89
|
+
merged = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!merged) emit(band);
|
|
94
|
+
}
|
|
95
|
+
for (let r = 0; r < runs.length; r++) {
|
|
96
|
+
if (!used.has(r)) next.push({ lo: runs[r].lo, hi: runs[r].hi, first: line, last: line });
|
|
97
|
+
}
|
|
98
|
+
open = next;
|
|
99
|
+
}
|
|
100
|
+
for (const band of open) emit(band);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function rulingToPdfLines(ruling, scale, pdfHeight) {
|
|
104
|
+
const horizontals = ruling.horizontals.map((s) => {
|
|
105
|
+
const y = pdfHeight - (s.y1 + s.y2) / 2 / scale;
|
|
106
|
+
return {
|
|
107
|
+
x1: Math.min(s.x1, s.x2) / scale,
|
|
108
|
+
y1: y,
|
|
109
|
+
x2: Math.max(s.x1, s.x2) / scale,
|
|
110
|
+
y2: y,
|
|
111
|
+
lineWidth: s.thicknessPx / scale
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
const verticals = ruling.verticals.map((s) => {
|
|
115
|
+
const x = (s.x1 + s.x2) / 2 / scale;
|
|
116
|
+
const yA = pdfHeight - Math.max(s.y1, s.y2) / scale;
|
|
117
|
+
const yB = pdfHeight - Math.min(s.y1, s.y2) / scale;
|
|
118
|
+
return { x1: x, y1: yA, x2: x, y2: yB, lineWidth: s.thicknessPx / scale };
|
|
119
|
+
});
|
|
120
|
+
return { horizontals, verticals };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/ocr/engine.ts
|
|
124
|
+
var _promises = require('fs/promises');
|
|
125
|
+
var _path = require('path');
|
|
126
|
+
|
|
127
|
+
// src/ocr/models.ts
|
|
128
|
+
var OCR_DET_MODEL = {
|
|
129
|
+
name: "PP-OCRv5 mobile det",
|
|
130
|
+
filename: "det.onnx",
|
|
131
|
+
url: "https://huggingface.co/PaddlePaddle/PP-OCRv5_mobile_det_onnx/resolve/main/inference.onnx",
|
|
132
|
+
sha256: "a431985659dc921974177a95adcfbb90fd9e51989a5e04d70d0b75f597b6e61d",
|
|
133
|
+
sizeMb: 5
|
|
134
|
+
};
|
|
135
|
+
var OCR_REC_MODEL = {
|
|
136
|
+
name: "PP-OCRv5 korean rec",
|
|
137
|
+
filename: "rec_korean.onnx",
|
|
138
|
+
url: "https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.onnx",
|
|
139
|
+
sha256: "92f0b7785e64fc9090106a241cf4c1eb97472824558272751b88a2a4476d3a08",
|
|
140
|
+
sizeMb: 13
|
|
141
|
+
};
|
|
142
|
+
var OCR_REC_DICT = {
|
|
143
|
+
name: "PP-OCRv5 korean dict",
|
|
144
|
+
filename: "rec_korean.yml",
|
|
145
|
+
url: "https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.yml",
|
|
146
|
+
sha256: "f757fa1c40e99edcf27e9cce879b93eb2a51fa46f5ef39095689b8c37dd75998",
|
|
147
|
+
sizeMb: 1
|
|
148
|
+
};
|
|
149
|
+
var ALL_OCR_MODELS = [OCR_DET_MODEL, OCR_REC_MODEL, OCR_REC_DICT];
|
|
150
|
+
function getOcrModelsDir() {
|
|
151
|
+
return _chunkH7OWEYH2cjs.getModelsDir.call(void 0, "ppocr");
|
|
152
|
+
}
|
|
153
|
+
async function ensureOcrModels(onProgress) {
|
|
154
|
+
return _chunkH7OWEYH2cjs.ensureModelsIn.call(void 0, getOcrModelsDir(), ALL_OCR_MODELS, onProgress);
|
|
155
|
+
}
|
|
156
|
+
function parseCharacterDict(yml) {
|
|
157
|
+
const lines = yml.split("\n");
|
|
158
|
+
const chars = [];
|
|
159
|
+
let inDict = false;
|
|
160
|
+
let dictIndent = -1;
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
if (!inDict) {
|
|
163
|
+
const m2 = /^(\s*)character_dict:\s*$/.exec(line);
|
|
164
|
+
if (m2) {
|
|
165
|
+
inDict = true;
|
|
166
|
+
dictIndent = m2[1].length;
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const m = /^(\s*)- (.*)$/.exec(line);
|
|
171
|
+
if (m && m[1].length >= dictIndent) {
|
|
172
|
+
let v = m[2];
|
|
173
|
+
if (v.length >= 2 && (v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"'))) {
|
|
174
|
+
v = v.slice(1, -1).replace(/''/g, "'");
|
|
175
|
+
}
|
|
176
|
+
chars.push(v);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (line.trim() !== "") break;
|
|
180
|
+
}
|
|
181
|
+
return chars;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/ocr/engine.ts
|
|
185
|
+
var DET_LONG_SIDE = 960;
|
|
186
|
+
var DET_THRESH = 0.3;
|
|
187
|
+
var DET_BOX_THRESH = 0.6;
|
|
188
|
+
var DET_UNCLIP_RATIO = 1.5;
|
|
189
|
+
var DET_MIN_SIZE = 3;
|
|
190
|
+
var DET_MAX_BOXES = 1e3;
|
|
191
|
+
var REC_HEIGHT = 48;
|
|
192
|
+
var REC_MIN_WIDTH = 320;
|
|
193
|
+
var REC_MAX_WIDTH = 3200;
|
|
194
|
+
var TEXT_SCORE = 0.5;
|
|
195
|
+
var DET_MEAN = [0.485, 0.456, 0.406];
|
|
196
|
+
var DET_STD = [0.229, 0.224, 0.225];
|
|
197
|
+
var OcrEngine = class _OcrEngine {
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
constructor(parts) {
|
|
204
|
+
this.det = parts.det;
|
|
205
|
+
this.rec = parts.rec;
|
|
206
|
+
this.dict = parts.dict;
|
|
207
|
+
this.ort = parts.ort;
|
|
208
|
+
this.sharp = parts.sharp;
|
|
209
|
+
}
|
|
210
|
+
static async create() {
|
|
211
|
+
const [ortMod, sharpModRaw] = await Promise.all([
|
|
212
|
+
tryImport("onnxruntime-node", () => Promise.resolve().then(() => _interopRequireWildcard(require("onnxruntime-node")))),
|
|
213
|
+
tryImport(
|
|
214
|
+
"sharp",
|
|
215
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("sharp")))
|
|
216
|
+
)
|
|
217
|
+
]);
|
|
218
|
+
const sharpAny = sharpModRaw;
|
|
219
|
+
const sharpMod = typeof sharpAny === "function" ? sharpAny : _nullishCoalesce(sharpAny.default, () => ( sharpAny));
|
|
220
|
+
const dir = getOcrModelsDir();
|
|
221
|
+
const sessionOpts = {
|
|
222
|
+
graphOptimizationLevel: "all",
|
|
223
|
+
executionProviders: ["cpu"],
|
|
224
|
+
logSeverityLevel: 3
|
|
225
|
+
// paddle2onnx 변환 잔여물 W 로그 폭주 억제
|
|
226
|
+
};
|
|
227
|
+
const [det, rec, dictYml] = await Promise.all([
|
|
228
|
+
ortMod.InferenceSession.create(_path.join.call(void 0, dir, OCR_DET_MODEL.filename), sessionOpts),
|
|
229
|
+
ortMod.InferenceSession.create(_path.join.call(void 0, dir, OCR_REC_MODEL.filename), sessionOpts),
|
|
230
|
+
_promises.readFile.call(void 0, _path.join.call(void 0, dir, OCR_REC_DICT.filename), "utf-8")
|
|
231
|
+
]);
|
|
232
|
+
const dict = parseCharacterDict(dictYml);
|
|
233
|
+
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");
|
|
234
|
+
return new _OcrEngine({ det, rec, dict, ort: ortMod, sharp: sharpMod });
|
|
235
|
+
}
|
|
236
|
+
/** onnxruntime-node 1.14+ InferenceSession.release() — 구버전은 무시 */
|
|
237
|
+
async destroy() {
|
|
238
|
+
for (const s of [this.det, this.rec]) {
|
|
239
|
+
const rel = s.release;
|
|
240
|
+
if (typeof rel === "function") {
|
|
241
|
+
try {
|
|
242
|
+
await rel.call(s);
|
|
243
|
+
} catch (e2) {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* 페이지 RGBA 픽셀 → 텍스트 라인 인식.
|
|
250
|
+
* 반환 좌표는 입력 픽셀 기준. 라인은 위→아래, 좌→우 정렬.
|
|
251
|
+
*/
|
|
252
|
+
async recognizePage(rgba, width, height) {
|
|
253
|
+
if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return [];
|
|
254
|
+
const boxes = await this.detect(rgba, width, height);
|
|
255
|
+
const items = [];
|
|
256
|
+
for (const b of boxes) {
|
|
257
|
+
const r = await this.recognizeLine(rgba, width, height, b);
|
|
258
|
+
if (r && r.confidence >= TEXT_SCORE && r.text.trim()) items.push(r);
|
|
259
|
+
}
|
|
260
|
+
items.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
261
|
+
return items;
|
|
262
|
+
}
|
|
263
|
+
// ─── det ─────────────────────────────────────────────
|
|
264
|
+
async detect(rgba, width, height) {
|
|
265
|
+
const ratio = DET_LONG_SIDE / Math.max(width, height);
|
|
266
|
+
const dw = Math.max(32, Math.round(width * ratio / 32) * 32);
|
|
267
|
+
const dh = Math.max(32, Math.round(height * ratio / 32) * 32);
|
|
268
|
+
const rgb = await this.sharp(rgba, { raw: { width, height, channels: 4 } }).resize(dw, dh, { fit: "fill" }).removeAlpha().raw().toBuffer();
|
|
269
|
+
const plane = dw * dh;
|
|
270
|
+
const input = new Float32Array(3 * plane);
|
|
271
|
+
for (let i = 0; i < plane; i++) {
|
|
272
|
+
const r = rgb[i * 3] / 255;
|
|
273
|
+
const g = rgb[i * 3 + 1] / 255;
|
|
274
|
+
const b = rgb[i * 3 + 2] / 255;
|
|
275
|
+
input[i] = (b - DET_MEAN[0]) / DET_STD[0];
|
|
276
|
+
input[plane + i] = (g - DET_MEAN[1]) / DET_STD[1];
|
|
277
|
+
input[2 * plane + i] = (r - DET_MEAN[2]) / DET_STD[2];
|
|
278
|
+
}
|
|
279
|
+
const tensor = new this.ort.Tensor("float32", input, [1, 3, dh, dw]);
|
|
280
|
+
const out = await this.det.run({ [this.det.inputNames[0]]: tensor });
|
|
281
|
+
const probMap = out[this.det.outputNames[0]].data;
|
|
282
|
+
const rawBoxes = componentBoxes(probMap, dw, dh);
|
|
283
|
+
const sx = width / dw;
|
|
284
|
+
const sy = height / dh;
|
|
285
|
+
const boxes = [];
|
|
286
|
+
for (const rb of rawBoxes.slice(0, DET_MAX_BOXES)) {
|
|
287
|
+
const bw = rb.x2 - rb.x1 + 1;
|
|
288
|
+
const bh = rb.y2 - rb.y1 + 1;
|
|
289
|
+
const delta = bw * bh * DET_UNCLIP_RATIO / (2 * (bw + bh));
|
|
290
|
+
const x1 = Math.max(0, Math.floor((rb.x1 - delta) * sx));
|
|
291
|
+
const y1 = Math.max(0, Math.floor((rb.y1 - delta) * sy));
|
|
292
|
+
const x2 = Math.min(width, Math.ceil((rb.x2 + 1 + delta) * sx));
|
|
293
|
+
const y2 = Math.min(height, Math.ceil((rb.y2 + 1 + delta) * sy));
|
|
294
|
+
if (x2 - x1 < DET_MIN_SIZE || y2 - y1 < DET_MIN_SIZE) continue;
|
|
295
|
+
boxes.push({ x: x1, y: y1, w: x2 - x1, h: y2 - y1 });
|
|
296
|
+
}
|
|
297
|
+
return boxes;
|
|
298
|
+
}
|
|
299
|
+
// ─── rec ─────────────────────────────────────────────
|
|
300
|
+
async recognizeLine(rgba, width, height, box) {
|
|
301
|
+
const rw = Math.min(REC_MAX_WIDTH, Math.max(16, Math.round(box.w * REC_HEIGHT / box.h)));
|
|
302
|
+
const padded = Math.max(REC_MIN_WIDTH, rw);
|
|
303
|
+
const rgb = await this.sharp(rgba, { raw: { width, height, channels: 4 } }).extract({ left: box.x, top: box.y, width: box.w, height: box.h }).resize(rw, REC_HEIGHT, { fit: "fill" }).removeAlpha().raw().toBuffer();
|
|
304
|
+
const plane = padded * REC_HEIGHT;
|
|
305
|
+
const input = new Float32Array(3 * plane);
|
|
306
|
+
for (let y = 0; y < REC_HEIGHT; y++) {
|
|
307
|
+
for (let x = 0; x < rw; x++) {
|
|
308
|
+
const src = (y * rw + x) * 3;
|
|
309
|
+
const dst = y * padded + x;
|
|
310
|
+
input[dst] = rgb[src + 2] / 255 / 0.5 - 1;
|
|
311
|
+
input[plane + dst] = rgb[src + 1] / 255 / 0.5 - 1;
|
|
312
|
+
input[2 * plane + dst] = rgb[src] / 255 / 0.5 - 1;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const tensor = new this.ort.Tensor("float32", input, [1, 3, REC_HEIGHT, padded]);
|
|
316
|
+
const out = await this.rec.run({ [this.rec.inputNames[0]]: tensor });
|
|
317
|
+
const logits = out[this.rec.outputNames[0]];
|
|
318
|
+
const [, T, C] = logits.dims;
|
|
319
|
+
const data = logits.data;
|
|
320
|
+
const decoded = ctcDecode(data, T, C, this.dict);
|
|
321
|
+
if (!decoded) return null;
|
|
322
|
+
return { text: decoded.text, x: box.x, y: box.y, w: box.w, h: box.h, confidence: decoded.confidence };
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
function ctcDecode(data, T, C, dict) {
|
|
326
|
+
let text = "";
|
|
327
|
+
let confSum = 0;
|
|
328
|
+
let confCount = 0;
|
|
329
|
+
let prev = -1;
|
|
330
|
+
for (let t = 0; t < T; t++) {
|
|
331
|
+
const off = t * C;
|
|
332
|
+
let best = 0;
|
|
333
|
+
let bestV = data[off];
|
|
334
|
+
for (let c = 1; c < C; c++) {
|
|
335
|
+
const v = data[off + c];
|
|
336
|
+
if (v > bestV) {
|
|
337
|
+
bestV = v;
|
|
338
|
+
best = c;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const repeat = best === prev;
|
|
342
|
+
prev = best;
|
|
343
|
+
if (best === 0 || repeat) continue;
|
|
344
|
+
let p = bestV;
|
|
345
|
+
if (p > 1.0001 || p < 0) {
|
|
346
|
+
let denom = 0;
|
|
347
|
+
for (let c = 0; c < C; c++) denom += Math.exp(data[off + c] - bestV);
|
|
348
|
+
p = 1 / denom;
|
|
349
|
+
}
|
|
350
|
+
confSum += p;
|
|
351
|
+
confCount++;
|
|
352
|
+
if (best >= 1 && best <= dict.length) text += dict[best - 1];
|
|
353
|
+
else if (best === dict.length + 1) text += " ";
|
|
354
|
+
}
|
|
355
|
+
if (!text) return null;
|
|
356
|
+
return { text, confidence: confCount > 0 ? confSum / confCount : 0 };
|
|
357
|
+
}
|
|
358
|
+
function componentBoxes(prob, w, h) {
|
|
359
|
+
const visited = new Uint8Array(w * h);
|
|
360
|
+
const boxes = [];
|
|
361
|
+
const stack = [];
|
|
362
|
+
for (let start = 0; start < w * h; start++) {
|
|
363
|
+
if (visited[start] || prob[start] <= DET_THRESH) continue;
|
|
364
|
+
let x1 = start % w, x2 = x1, y1 = start / w | 0, y2 = y1;
|
|
365
|
+
let sum = 0;
|
|
366
|
+
let count = 0;
|
|
367
|
+
stack.length = 0;
|
|
368
|
+
stack.push(start);
|
|
369
|
+
visited[start] = 1;
|
|
370
|
+
while (stack.length) {
|
|
371
|
+
const p = stack.pop();
|
|
372
|
+
const px = p % w;
|
|
373
|
+
const py = p / w | 0;
|
|
374
|
+
sum += prob[p];
|
|
375
|
+
count++;
|
|
376
|
+
if (px < x1) x1 = px;
|
|
377
|
+
if (px > x2) x2 = px;
|
|
378
|
+
if (py < y1) y1 = py;
|
|
379
|
+
if (py > y2) y2 = py;
|
|
380
|
+
if (px > 0 && !visited[p - 1] && prob[p - 1] > DET_THRESH) {
|
|
381
|
+
visited[p - 1] = 1;
|
|
382
|
+
stack.push(p - 1);
|
|
383
|
+
}
|
|
384
|
+
if (px < w - 1 && !visited[p + 1] && prob[p + 1] > DET_THRESH) {
|
|
385
|
+
visited[p + 1] = 1;
|
|
386
|
+
stack.push(p + 1);
|
|
387
|
+
}
|
|
388
|
+
if (py > 0 && !visited[p - w] && prob[p - w] > DET_THRESH) {
|
|
389
|
+
visited[p - w] = 1;
|
|
390
|
+
stack.push(p - w);
|
|
391
|
+
}
|
|
392
|
+
if (py < h - 1 && !visited[p + w] && prob[p + w] > DET_THRESH) {
|
|
393
|
+
visited[p + w] = 1;
|
|
394
|
+
stack.push(p + w);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (x2 - x1 + 1 < DET_MIN_SIZE && y2 - y1 + 1 < DET_MIN_SIZE) continue;
|
|
398
|
+
boxes.push({ x1, y1, x2, y2, score: sum / count });
|
|
399
|
+
}
|
|
400
|
+
return boxes.filter((b) => b.score >= DET_BOX_THRESH).sort((a, b) => a.y1 - b.y1 || a.x1 - b.x1);
|
|
401
|
+
}
|
|
402
|
+
async function tryImport(name, loader) {
|
|
403
|
+
try {
|
|
404
|
+
return await loader();
|
|
405
|
+
} catch (e) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
`\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. \uC6D0\uC778: ${e.message}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
var enginePromise = null;
|
|
412
|
+
function getOcrEngine() {
|
|
413
|
+
if (!enginePromise) {
|
|
414
|
+
enginePromise = OcrEngine.create().catch((err) => {
|
|
415
|
+
enginePromise = null;
|
|
416
|
+
throw err;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return enginePromise;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/ocr/pdf-ocr.ts
|
|
423
|
+
var OCR_RENDER_SCALE = 3;
|
|
424
|
+
var PAGE_TIMEOUT_MS = 12e4;
|
|
425
|
+
async function runPdfOcr(buffer, targets, mode, warnings, onProgress) {
|
|
426
|
+
const result = /* @__PURE__ */ new Map();
|
|
427
|
+
if (targets.size === 0) return result;
|
|
428
|
+
const pdfiumMod = await tryImport2(
|
|
429
|
+
"@hyzyla/pdfium",
|
|
430
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("@hyzyla/pdfium")))
|
|
431
|
+
);
|
|
432
|
+
if (mode === "builtin") {
|
|
433
|
+
await ensureOcrModels((p) => {
|
|
434
|
+
if (p.phase === "download" && p.downloaded === 0) {
|
|
435
|
+
process.stderr.write(`[kordoc-ocr] ${p.spec.name} \uB2E4\uC6B4\uB85C\uB4DC \uC911 (~${p.spec.sizeMb}MB)...
|
|
436
|
+
`);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
const engine = mode === "builtin" ? await getOcrEngine() : null;
|
|
441
|
+
const pdfium = await pdfiumMod.PDFiumLibrary.init();
|
|
442
|
+
const doc = await pdfium.loadDocument(new Uint8Array(buffer));
|
|
443
|
+
try {
|
|
444
|
+
let done = 0;
|
|
445
|
+
for (const page of doc.pages()) {
|
|
446
|
+
const pageNo = page.number + 1;
|
|
447
|
+
if (!targets.has(pageNo)) continue;
|
|
448
|
+
_optionalChain([onProgress, 'optionalCall', _2 => _2(++done, targets.size)]);
|
|
449
|
+
try {
|
|
450
|
+
const blocks = await withTimeout(
|
|
451
|
+
ocrOnePage(page, pageNo, mode, engine, warnings),
|
|
452
|
+
PAGE_TIMEOUT_MS,
|
|
453
|
+
`OCR \uD398\uC774\uC9C0 ${pageNo} \uD0C0\uC784\uC544\uC6C3 (${PAGE_TIMEOUT_MS / 1e3}\uCD08)`
|
|
454
|
+
);
|
|
455
|
+
result.set(pageNo, blocks);
|
|
456
|
+
} catch (e) {
|
|
457
|
+
warnings.push({
|
|
458
|
+
page: pageNo,
|
|
459
|
+
message: `\uD398\uC774\uC9C0 ${pageNo} OCR \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`,
|
|
460
|
+
code: "OCR_FAILED"
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
doc.destroy();
|
|
466
|
+
pdfium.destroy();
|
|
467
|
+
}
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
async function ocrOnePage(page, pageNo, mode, engine, warnings) {
|
|
471
|
+
const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize();
|
|
472
|
+
const rendered = await page.render({
|
|
473
|
+
scale: OCR_RENDER_SCALE,
|
|
474
|
+
render: async ({ data }) => data
|
|
475
|
+
});
|
|
476
|
+
const { data: bgra, width: rw, height: rh } = rendered;
|
|
477
|
+
const rgba = bgraToRgba(bgra);
|
|
478
|
+
if (mode === "builtin") {
|
|
479
|
+
const items = await engine.recognizePage(rgba, rw, rh);
|
|
480
|
+
const scale = rh / pdfH;
|
|
481
|
+
const ruling = detectRulingLines(rgba, rw, rh, scale);
|
|
482
|
+
const extraLines = rulingToPdfLines(ruling, scale, pdfH);
|
|
483
|
+
return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines);
|
|
484
|
+
}
|
|
485
|
+
const sharpModRaw = await tryImport2(
|
|
486
|
+
"sharp",
|
|
487
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("sharp")))
|
|
488
|
+
);
|
|
489
|
+
const sharpAny = sharpModRaw;
|
|
490
|
+
const sharp = typeof sharpAny === "function" ? sharpAny : _nullishCoalesce(sharpAny.default, () => ( sharpAny));
|
|
491
|
+
const png = await sharp(rgba, { raw: { width: rw, height: rh, channels: 4 } }).png().toBuffer();
|
|
492
|
+
const text = await mode(new Uint8Array(png), pageNo, "image/png");
|
|
493
|
+
if (!text.trim()) {
|
|
494
|
+
warnings.push({ page: pageNo, message: `\uD398\uC774\uC9C0 ${pageNo} OCR \uACB0\uACFC \uC5C6\uC74C`, code: "OCR_FAILED" });
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
return [{ type: "paragraph", text: text.trim(), pageNumber: pageNo }];
|
|
498
|
+
}
|
|
499
|
+
function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines) {
|
|
500
|
+
const norm = items.map((it) => {
|
|
501
|
+
const h = it.h / scale;
|
|
502
|
+
return {
|
|
503
|
+
text: it.text,
|
|
504
|
+
x: Math.round(it.x / scale),
|
|
505
|
+
// NormItem.y 는 pdfjs transform[5] = 베이스라인 (bottom-up) — 박스 하단으로 근사
|
|
506
|
+
y: Math.round(pdfH - (it.y + it.h) / scale),
|
|
507
|
+
w: Math.round(it.w / scale),
|
|
508
|
+
h: Math.round(h),
|
|
509
|
+
fontSize: Math.max(1, Math.round(h * 0.8)),
|
|
510
|
+
// 박스높이는 어센더+디센더 포함 — 글자크기 근사
|
|
511
|
+
fontName: "ocr",
|
|
512
|
+
isHidden: false
|
|
513
|
+
};
|
|
514
|
+
});
|
|
515
|
+
return _chunkR22KDUKWcjs.extractPageBlocksWithLines.call(void 0, norm, pageNumber, { fnArray: [], argsArray: [] }, pdfW, pdfH, extraLines);
|
|
516
|
+
}
|
|
517
|
+
async function tryImport2(name, loader) {
|
|
518
|
+
try {
|
|
519
|
+
return await loader();
|
|
520
|
+
} catch (e) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
`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. \uC6D0\uC778: ${e.message}`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async function withTimeout(promise, ms, msg) {
|
|
527
|
+
promise.catch(() => {
|
|
528
|
+
});
|
|
529
|
+
let timer;
|
|
530
|
+
try {
|
|
531
|
+
return await Promise.race([
|
|
532
|
+
promise,
|
|
533
|
+
new Promise((_, reject) => {
|
|
534
|
+
timer = setTimeout(() => reject(new Error(msg)), ms);
|
|
535
|
+
})
|
|
536
|
+
]);
|
|
537
|
+
} finally {
|
|
538
|
+
if (timer) clearTimeout(timer);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function bgraToRgba(bgra) {
|
|
542
|
+
const out = new Uint8Array(bgra.length);
|
|
543
|
+
for (let i = 0; i < bgra.length; i += 4) {
|
|
544
|
+
out[i] = bgra[i + 2];
|
|
545
|
+
out[i + 1] = bgra[i + 1];
|
|
546
|
+
out[i + 2] = bgra[i];
|
|
547
|
+
out[i + 3] = bgra[i + 3];
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
exports.ensureOcrModels = ensureOcrModels; exports.getOcrEngine = getOcrEngine; exports.detectRulingLines = detectRulingLines; exports.rulingToPdfLines = rulingToPdfLines; exports.runPdfOcr = runPdfOcr; exports.ocrItemsToBlocks = ocrItemsToBlocks;
|
|
560
|
+
//# sourceMappingURL=chunk-35U3MVA2.cjs.map
|