Skip to main content

easypdf_markdown/ocr/
engine.rs

1//! OCR 引擎抽象与结果类型。
2
3use crate::render::RenderedImage;
4use easypdf_core::CapabilityLevel;
5
6/// OCR 识别的输入图像。
7///
8/// 保存原始 RGBA 像素数据及尺寸。可从 [`RenderedImage`]
9/// 或 `image::DynamicImage` 构造。
10///
11/// # Examples
12///
13/// ```
14/// use easypdf_markdown::ocr::OcrImage;
15/// use image::DynamicImage;
16///
17/// let img = DynamicImage::new_rgba8(100, 50);
18/// let ocr_img = OcrImage::from_dynamic_image(&img);
19/// assert_eq!(ocr_img.width, 100);
20/// assert_eq!(ocr_img.height, 50);
21/// ```
22#[derive(Debug, Clone)]
23pub struct OcrImage {
24    /// 图像宽度(像素)。
25    pub width: u32,
26    /// 图像高度(像素)。
27    pub height: u32,
28    /// 原始 RGBA 像素数据(每像素 4 字节,行优先,从上到下)。
29    pub pixels: Vec<u8>,
30}
31
32impl OcrImage {
33    /// 从已渲染的 PDF 页面图像创建 `OcrImage`。
34    ///
35    /// # Panics
36    ///
37    /// 当像素缓冲区长度不等于 `width * height * 4` 时 panic。
38    #[must_use]
39    pub fn from_rendered(rendered: &RenderedImage) -> Self {
40        Self {
41            width: rendered.width,
42            height: rendered.height,
43            pixels: rendered.pixels.clone(),
44        }
45    }
46
47    /// 从 `image::DynamicImage` 创建 `OcrImage`。
48    ///
49    /// 内部转换为 RGBA8 格式。
50    #[must_use]
51    pub fn from_dynamic_image(image: &image::DynamicImage) -> Self {
52        let rgba = image.to_rgba8();
53        Self {
54            width: rgba.width(),
55            height: rgba.height(),
56            pixels: rgba.into_raw(),
57        }
58    }
59
60    /// 从原始 RGBA 像素数据创建 `OcrImage`。
61    #[must_use]
62    pub const fn new(width: u32, height: u32, pixels: Vec<u8>) -> Self {
63        Self {
64            width,
65            height,
66            pixels,
67        }
68    }
69}
70
71/// OCR 文本识别结果。
72///
73/// 包含提取的文本、可选的置信度分数以及可选的词级边界框(用于空间布局保留)。
74#[derive(Debug, Clone)]
75pub struct OcrResult {
76    /// 从图像中提取的文本。
77    pub text: String,
78    /// 总体置信度分数(0.0 到 1.0),由引擎提供时存在。
79    pub confidence: Option<f32>,
80    /// 逐词边界框,由引擎提供时存在。
81    pub word_boxes: Vec<WordBox>,
82}
83
84/// 单个识别词的边界框。
85///
86/// 坐标为相对于输入图像左上角的像素值。
87#[derive(Debug, Clone)]
88pub struct WordBox {
89    /// 识别出的词文本。
90    pub text: String,
91    /// 左上角 X 坐标(像素)。
92    pub x: u32,
93    /// 左上角 Y 坐标(像素)。
94    pub y: u32,
95    /// 边界框宽度(像素)。
96    pub width: u32,
97    /// 边界框高度(像素)。
98    pub height: u32,
99    /// 逐词置信度分数(0.0 到 1.0),可用时存在。
100    pub confidence: Option<f32>,
101}
102
103/// OCR 引擎抽象。
104///
105/// 实现者使用不同后端(本地 ML 模型、云端 API、mock)从图像中提供文本识别。
106/// 该 trait 是对象安全的,且要求 `Send + Sync` 以便跨线程使用。
107///
108/// # 实现自定义引擎
109///
110/// ```
111/// use easypdf_markdown::ocr::{OcrEngine, OcrImage, OcrResult};
112/// use easypdf_core::CapabilityLevel;
113///
114/// struct MyEngine;
115///
116/// impl OcrEngine for MyEngine {
117///     fn recognize(&self, image: &OcrImage) -> std::result::Result<OcrResult, Box<dyn std::error::Error + Send + Sync>> {
118///         Ok(OcrResult {
119///             text: format!("OCR of {}x{} image", image.width, image.height),
120///             confidence: Some(0.95),
121///             word_boxes: vec![],
122///         })
123///     }
124///
125///     fn name(&self) -> &'static str { "my-engine" }
126///     fn languages(&self) -> &[&str] { &["en"] }
127///     fn level(&self) -> CapabilityLevel { CapabilityLevel::Heuristic }
128/// }
129/// ```
130pub trait OcrEngine: Send + Sync {
131    /// 对给定图像执行 OCR 并返回识别文本。
132    ///
133    /// # Errors
134    ///
135    /// 当引擎处理图像失败时(模型加载失败、网络超时、格式不支持等)返回错误。
136    fn recognize(
137        &self,
138        image: &OcrImage,
139    ) -> std::result::Result<OcrResult, Box<dyn std::error::Error + Send + Sync>>;
140
141    /// 此 OCR 引擎的可读名称(例如 `"ocrs"`、`"llm-gpt-4o"`)。
142    fn name(&self) -> &'static str;
143
144    /// 支持的语言代码(例如 `["en", "zh"]`)。
145    fn languages(&self) -> &[&str];
146
147    /// 此引擎的能力等级。
148    ///
149    /// - [`CapabilityLevel::Heuristic`]:本地 ML 模型(例如 ocrs)
150    /// - [`CapabilityLevel::Cloud`]:云端 API(例如 LLM Vision)
151    fn level(&self) -> CapabilityLevel;
152}