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}