use super::OCREngine; use crate::types::TextLocation; use anyhow::Result; use async_trait::async_trait; /// Tesseract OCR engine (fallback/cross-platform) pub struct TesseractOCR; impl TesseractOCR { pub fn new() -> Result { // Check if tesseract is available let tesseract_check = std::process::Command::new("which") .arg("tesseract") .output(); if tesseract_check.is_err() || !tesseract_check.as_ref().unwrap().status.success() { anyhow::bail!("Tesseract OCR is not installed on your system.\n\n\ To install tesseract:\n macOS: brew install tesseract\n \ Linux: sudo apt-get install tesseract-ocr (Ubuntu/Debian)\n \ sudo yum install tesseract (RHEL/CentOS)\n \ Windows: Download from https://github.com/UB-Mannheim/tesseract/wiki\n\n\ After installation, restart your terminal and try again."); } Ok(Self) } } #[async_trait] impl OCREngine for TesseractOCR { async fn extract_text_with_locations(&self, path: &str) -> Result> { // Use tesseract CLI with TSV output to get bounding boxes let output = std::process::Command::new("tesseract") .arg(path) .arg("stdout") .arg("tsv") .output() .map_err(|e| anyhow::anyhow!("Failed to run tesseract: {}", e))?; if !output.status.success() { anyhow::bail!("Tesseract failed: {}", String::from_utf8_lossy(&output.stderr)); } let tsv_text = String::from_utf8_lossy(&output.stdout); let mut locations = Vec::new(); // Parse TSV output (skip header line) for (i, line) in tsv_text.lines().enumerate() { if i == 0 { continue; } // Skip header let parts: Vec<&str> = line.split('\t').collect(); if parts.len() >= 12 { // TSV format: level, page_num, block_num, par_num, line_num, word_num, // left, top, width, height, conf, text if let (Ok(x), Ok(y), Ok(w), Ok(h), Ok(conf), text) = ( parts[6].parse::(), parts[7].parse::(), parts[8].parse::(), parts[9].parse::(), parts[10].parse::(), parts[11], ) { let trimmed = text.trim(); if !trimmed.is_empty() && conf > 0.0 { locations.push(TextLocation { text: trimmed.to_string(), x, y, width: w, height: h, confidence: conf / 100.0, // Convert from 0-100 to 0-1 }); } } } } Ok(locations) } fn name(&self) -> &str { "Tesseract OCR" } }