diff --git a/src/main/capabilities/ocr-caps.ts b/src/main/capabilities/ocr-caps.ts index 1bd4552..ed68487 100644 --- a/src/main/capabilities/ocr-caps.ts +++ b/src/main/capabilities/ocr-caps.ts @@ -3,21 +3,47 @@ import fs from "node:fs"; import { register } from "../engine/registry"; import { RuntimeError } from "../engine/types"; +interface OcrWord { text: string; x: number; y: number; width: number; height: number; confidence: number } + function requireImage(args: Record, nodeId: string): string { - for (const key of ["image_path", "imagePath", "file_path", "filePath", "path", "image"]) { + for (const key of ["image_path", "imagePath", "file_path", "filePath", "path", "image", "pictureSource"]) { const v = String(args[key] ?? "").trim(); if (v) return v; } throw new RuntimeError("validation_error", "图片路径为空", { nodeId, componentId: nodeId, recommendation: "填写本地图片路径(png/jpg/bmp)" }); } -async function recognize(imagePath: string, nodeId: string) { +async function recognizeWithLayout(imagePath: string, nodeId: string): Promise<{ text: string; words: OcrWord[]; textLines: string[] }> { if (!fs.existsSync(imagePath)) { throw new RuntimeError("file_not_found", "图片不存在:" + imagePath, { nodeId, componentId: nodeId }); } const worker = await Tesseract.createWorker("chi_sim+eng"); try { - return await worker.recognize(imagePath); + const result = await worker.recognize(imagePath, {}, { text: true, blocks: true }); + const data = result.data as { + text?: string; + blocks?: Array<{ + paragraphs?: Array<{ + lines?: Array<{ + words?: Array<{ text: string; confidence: number; bbox: { x0: number; y0: number; x1: number; y1: number } }>; + }>; + }>; + }>; + }; + const words: OcrWord[] = []; + const textLines: string[] = []; + for (const block of data.blocks ?? []) { + for (const para of block.paragraphs ?? []) { + for (const line of para.lines ?? []) { + const lineWords = line.words ?? []; + textLines.push(lineWords.map(w => w.text).join(" ")); + for (const w of lineWords) { + words.push({ text: w.text, x: w.bbox.x0, y: w.bbox.y0, width: w.bbox.x1 - w.bbox.x0, height: w.bbox.y1 - w.bbox.y0, confidence: w.confidence }); + } + } + } + } + return { text: (data.text ?? "").trim(), words, textLines }; } finally { await worker.terminate(); } @@ -25,27 +51,22 @@ async function recognize(imagePath: string, nodeId: string) { register("OcrOffline_GetOcrTextNew", async (ctx, node, args) => { const imagePath = requireImage(args, node.id); - const result = await recognize(imagePath, node.id); - const data = result.data as Tesseract.Page & { words?: Array<{ text: string; bbox: { x0: number; y0: number; x1: number; y1: number }; confidence: number }>; lines?: Array<{ words: Array<{ text: string }> }> }; - const text = data.text.trim(); - ctx.log("OCR 识别完成:" + text.length + " 字符", "info", node.id); - return text; + const result = await recognizeWithLayout(imagePath, node.id); + ctx.log("OCR 识别完成:" + result.text.length + " 字符 / " + result.words.length + " 词", "info", node.id); + return result.text; }); register("OcrOffline_GetOcrTextPosition", async (ctx, node, args) => { const imagePath = requireImage(args, node.id); - const result = await recognize(imagePath, node.id); - const data = result.data as Tesseract.Page & { words?: Array<{ text: string; bbox: { x0: number; y0: number; x1: number; y1: number }; confidence: number }>; lines?: Array<{ words: Array<{ text: string }> }> }; - const words = (data.words ?? []).map(w => ({ text: w.text, x: w.bbox.x0, y: w.bbox.y0, width: w.bbox.x1 - w.bbox.x0, height: w.bbox.y1 - w.bbox.y0, confidence: w.confidence })); - ctx.log("OCR 定位完成:" + words.length + " 个词", "info", node.id); - return words; + const result = await recognizeWithLayout(imagePath, node.id); + ctx.log("OCR 定位完成:" + result.words.length + " 个词", "info", node.id); + return result.words; }); register("OcrOffline_GetOcrTable", async (ctx, node, args) => { const imagePath = requireImage(args, node.id); - const result = await recognize(imagePath, node.id); - const data = result.data as Tesseract.Page & { words?: Array<{ text: string; bbox: { x0: number; y0: number; x1: number; y1: number }; confidence: number }>; lines?: Array<{ words: Array<{ text: string }> }> }; - const lines = (data.lines ?? []).map((line: { words: Array<{ text: string }> }) => line.words.map((w: { text: string }) => w.text).join("\t")); - ctx.log("OCR 表格识别完成:" + lines.length + " 行", "info", node.id); - return lines; -}); + const result = await recognizeWithLayout(imagePath, node.id); + const rows = result.textLines.map(line => line.split(/ {2,}/).filter(Boolean).join("\t")); + ctx.log("OCR 表格识别完成:" + rows.length + " 行", "info", node.id); + return rows; +}); \ No newline at end of file