1pub extern crate tesseract_plumbing as plumbing;
2extern crate tesseract_sys;
3extern crate thiserror;
4
5use self::thiserror::Error;
6use std::ffi::CString;
7use std::ffi::NulError;
8use std::os::raw::c_int;
9use std::str;
10mod page_seg_mode;
11
12pub use page_seg_mode::PageSegMode;
13
14use self::tesseract_sys::{
15 TessOcrEngineMode, TessOcrEngineMode_OEM_DEFAULT, TessOcrEngineMode_OEM_LSTM_ONLY,
16 TessOcrEngineMode_OEM_TESSERACT_LSTM_COMBINED, TessOcrEngineMode_OEM_TESSERACT_ONLY,
17};
18
19#[derive(Debug, Error)]
20pub enum InitializeError {
21 #[error("Conversion to CString failed")]
22 CStringError(#[from] NulError),
23 #[error("TessBaseApi failed to initialize")]
24 TessBaseAPIInitError(#[from] plumbing::TessBaseApiInitError),
25}
26
27#[derive(Debug, Error)]
28pub enum SetImageError {
29 #[error("Conversion to CString failed")]
30 CStringError(#[from] NulError),
31 #[error("Failed to read image")]
32 PixReadError(#[from] plumbing::leptonica_plumbing::PixReadError),
33}
34
35#[derive(Debug, Error)]
36pub enum SetVariableError {
37 #[error("Conversion to CString failed")]
38 CStringError(#[from] NulError),
39 #[error("TessBaseApi failed to set variable")]
40 TessBaseAPISetVariableError(#[from] plumbing::TessBaseApiSetVariableError),
41}
42
43#[derive(Debug, Error)]
44pub enum TesseractError {
45 #[error("Failed to set language")]
46 InitializeError(#[from] InitializeError),
47 #[error("Failed to set image")]
48 SetImageError(#[from] SetImageError),
49 #[error("Errored whilst recognizing")]
50 RecognizeError(#[from] plumbing::TessBaseApiRecogniseError),
51 #[error("Errored whilst getting text")]
52 GetTextError(#[from] plumbing::TessBaseApiGetUtf8TextError),
53 #[error("Errored whilst getting HOCR text")]
54 GetHOCRTextError(#[from] plumbing::TessBaseApiGetHocrTextError),
55 #[error("Errored whilst getting TSV text")]
56 GetTsvTextError(#[from] plumbing::TessBaseApiGetTsvTextError),
57 #[error("Errored whilst setting frame")]
58 SetFrameError(#[from] plumbing::TessBaseApiSetImageSafetyError),
59 #[error("Errored whilst setting image from mem")]
60 SetImgFromMemError(#[from] plumbing::leptonica_plumbing::PixReadMemError),
61 #[error("Errored whilst setting variable")]
62 SetVariableError(#[from] SetVariableError),
63}
64
65pub enum OcrEngineMode {
67 Default,
69 LstmOnly,
71 TesseractLstmCombined,
75 TesseractOnly,
83}
84
85impl OcrEngineMode {
86 fn to_value(&self) -> TessOcrEngineMode {
87 match *self {
88 OcrEngineMode::Default => TessOcrEngineMode_OEM_DEFAULT,
89 OcrEngineMode::LstmOnly => TessOcrEngineMode_OEM_LSTM_ONLY,
90 OcrEngineMode::TesseractLstmCombined => TessOcrEngineMode_OEM_TESSERACT_LSTM_COMBINED,
91 OcrEngineMode::TesseractOnly => TessOcrEngineMode_OEM_TESSERACT_ONLY,
92 }
93 }
94}
95
96pub struct Tesseract(plumbing::TessBaseApi);
97
98impl Tesseract {
99 pub fn new(datapath: Option<&str>, language: Option<&str>) -> Result<Self, InitializeError> {
100 let mut tess = Tesseract(plumbing::TessBaseApi::create());
101 let datapath = match datapath {
102 Some(i) => Some(CString::new(i)?),
103 None => None,
104 };
105 let language = match language {
106 Some(i) => Some(CString::new(i)?),
107 None => None,
108 };
109
110 tess.0.init_2(datapath.as_deref(), language.as_deref())?;
111 Ok(tess)
112 }
113
114 pub fn new_with_oem(
115 datapath: Option<&str>,
116 language: Option<&str>,
117 oem: OcrEngineMode,
118 ) -> Result<Self, InitializeError> {
119 let mut tess = Tesseract(plumbing::TessBaseApi::create());
120 let datapath = match datapath {
121 Some(i) => Some(CString::new(i)?),
122 None => None,
123 };
124 let language = match language {
125 Some(i) => Some(CString::new(i)?),
126 None => None,
127 };
128
129 tess.0
130 .init_4(datapath.as_deref(), language.as_deref(), oem.to_value())?;
131 Ok(tess)
132 }
133
134 #[cfg(feature = "tesseract_5_2")]
135 pub fn new_with_data(
136 data: &[u8],
137 language: Option<&str>,
138 oem: OcrEngineMode,
139 ) -> Result<Self, InitializeError> {
140 let mut tess = Tesseract(plumbing::TessBaseApi::create());
141 let language = match language {
142 Some(i) => Some(CString::new(i)?),
143 None => None,
144 };
145
146 tess.0.init_1(data, language.as_deref(), oem.to_value())?;
147 Ok(tess)
148 }
149
150 pub fn set_image(mut self, filename: &str) -> Result<Self, SetImageError> {
151 let pix = plumbing::leptonica_plumbing::Pix::read(&CString::new(filename)?)?;
152 self.0.set_image_2(&pix);
153 Ok(self)
154 }
155 pub fn set_frame(
156 mut self,
157 frame_data: &[u8],
158 width: i32,
159 height: i32,
160 bytes_per_pixel: i32,
161 bytes_per_line: i32,
162 ) -> Result<Self, plumbing::TessBaseApiSetImageSafetyError> {
163 self.0
164 .set_image(frame_data, width, height, bytes_per_pixel, bytes_per_line)?;
165 Ok(self)
166 }
167 pub fn set_image_from_mem(
168 mut self,
169 img: &[u8],
170 ) -> Result<Self, plumbing::leptonica_plumbing::PixReadMemError> {
171 let pix = plumbing::leptonica_plumbing::Pix::read_mem(img)?;
172 self.0.set_image_2(&pix);
173 Ok(self)
174 }
175
176 pub fn set_rectangle(mut self, left: i32, top: i32, width: i32, height: i32) -> Self {
177 self.0.set_rectangle(left, top, width, height);
178 self
179 }
180
181 pub fn set_source_resolution(mut self, ppi: i32) -> Self {
182 self.0.set_source_resolution(ppi);
183 self
184 }
185
186 pub fn set_variable(mut self, name: &str, value: &str) -> Result<Self, SetVariableError> {
187 self.0
188 .set_variable(&CString::new(name)?, &CString::new(value)?)?;
189 Ok(self)
190 }
191 pub fn recognize(mut self) -> Result<Self, plumbing::TessBaseApiRecogniseError> {
192 self.0.recognize()?;
193 Ok(self)
194 }
195 pub fn get_text(&mut self) -> Result<String, plumbing::TessBaseApiGetUtf8TextError> {
196 Ok(self
197 .0
198 .get_utf8_text()?
199 .as_ref()
200 .to_string_lossy()
201 .into_owned())
202 }
203 pub fn mean_text_conf(&mut self) -> i32 {
204 self.0.mean_text_conf()
205 }
206
207 pub fn get_hocr_text(
211 &mut self,
212 page: c_int,
213 ) -> Result<String, plumbing::TessBaseApiGetHocrTextError> {
214 Ok(self
215 .0
216 .get_hocr_text(page)?
217 .as_ref()
218 .to_string_lossy()
219 .into_owned())
220 }
221
222 pub fn get_tsv_text(
226 &mut self,
227 page: c_int,
228 ) -> Result<String, plumbing::TessBaseApiGetTsvTextError> {
229 Ok(self
230 .0
231 .get_tsv_text(page)?
232 .as_ref()
233 .to_string_lossy()
234 .into_owned())
235 }
236
237 pub fn set_page_seg_mode(&mut self, mode: PageSegMode) {
238 self.0.set_page_seg_mode(mode.as_tess_page_seg_mode());
239 }
240}
241
242pub fn ocr(filename: &str, language: &str) -> Result<String, TesseractError> {
243 Ok(Tesseract::new(None, Some(language))?
244 .set_image(filename)?
245 .recognize()?
246 .get_text()?)
247}
248
249pub fn ocr_from_frame(
250 frame_data: &[u8],
251 width: i32,
252 height: i32,
253 bytes_per_pixel: i32,
254 bytes_per_line: i32,
255 language: &str,
256) -> Result<String, TesseractError> {
257 Ok(Tesseract::new(None, Some(language))?
258 .set_frame(frame_data, width, height, bytes_per_pixel, bytes_per_line)?
259 .recognize()?
260 .get_text()?)
261}
262
263#[test]
264fn ocr_test() -> Result<(), TesseractError> {
265 assert_eq!(
266 ocr("img.png", "eng")?,
267 include_str!("../img.txt").to_string()
268 );
269 Ok(())
270}
271
272#[test]
273fn ocr_from_frame_test() -> Result<(), TesseractError> {
274 assert_eq!(
275 ocr_from_frame(include_bytes!("../img.tiff"), 2256, 324, 3, 2256 * 3, "eng")?,
276 include_str!("../img.txt").to_string()
277 );
278 Ok(())
279}
280
281#[test]
282fn ocr_from_mem_with_ppi() -> Result<(), TesseractError> {
283 let mut cube = Tesseract::new(None, Some("eng"))?
284 .set_image_from_mem(include_bytes!("../img.tiff"))?
285 .set_source_resolution(70);
286 assert_eq!(&cube.get_text()?, include_str!("../img.txt"));
287 Ok(())
288}
289
290#[test]
291fn expanded_test() -> Result<(), TesseractError> {
292 let mut cube = Tesseract::new(None, Some("eng"))?
293 .set_image("img.png")?
294 .set_variable("tessedit_char_blacklist", "z")?
295 .recognize()?;
296 assert_eq!(&cube.get_text()?, include_str!("../img.txt"));
297 Ok(())
298}
299
300#[test]
301fn hocr_test() -> Result<(), TesseractError> {
302 let mut cube = Tesseract::new(None, Some("eng"))?.set_image("img.png")?;
303 assert!(&cube.get_hocr_text(0)?.contains("<div class='ocr_page'"));
304 Ok(())
305}
306
307#[test]
308#[ignore] fn oem_test() -> Result<(), TesseractError> {
310 let only_tesseract_str =
311 Tesseract::new_with_oem(None, Some("eng"), OcrEngineMode::TesseractOnly)?
312 .set_image("img.png")?
313 .recognize()?
314 .get_text()?;
315
316 let only_lstm_str = Tesseract::new_with_oem(None, Some("eng"), OcrEngineMode::LstmOnly)?
317 .set_image("img.png")?
318 .recognize()?
319 .get_text()?;
320
321 assert_ne!(only_tesseract_str, only_lstm_str);
322 Ok(())
323}
324
325#[test]
326fn oem_ltsm_only_test() -> Result<(), TesseractError> {
327 let only_lstm_str = Tesseract::new_with_oem(None, Some("eng"), OcrEngineMode::LstmOnly)?
328 .set_image("img.png")?
329 .recognize()?
330 .get_text()?;
331
332 assert_eq!(only_lstm_str, include_str!("../img.txt"));
333 Ok(())
334}
335
336#[test]
337fn initialize_with_none() {
338 assert!(Tesseract::new(None, None).is_ok());
339}