use core::ffi::CStr;
use core::marker::PhantomData;
use core::ops::Deref;
use core::ops::DerefMut;
use core::ptr::NonNull;
use std::os::raw::c_void;
use std::time::Duration;
use crate::Element;
use crate::FontAttrs;
use crate::Image;
use crate::InitFailed;
use crate::LayoutIter;
use crate::LayoutLevel;
use crate::OcrEngineMode;
use crate::RecognitionFailed;
use crate::Rectangle;
use crate::Tesseract;
use crate::Text;
use crate::Utf8Text;
const ENGLISH: &CStr = c"eng";
#[derive(Debug)]
pub struct Config<'a, 'b> {
pub data_dir: Option<&'a CStr>,
pub languages: &'b CStr,
pub ocr_engine_mode: OcrEngineMode,
}
impl Default for Config<'static, 'static> {
fn default() -> Self {
Self {
data_dir: None,
languages: ENGLISH,
ocr_engine_mode: OcrEngineMode::LstmOnly,
}
}
}
pub struct TextRecognizer {
base: Tesseract,
}
impl TextRecognizer {
pub fn new() -> Result<Self, InitFailed> {
Self::with_languages(ENGLISH)
}
pub fn with_languages(languages: &CStr) -> Result<Self, InitFailed> {
Self::with_config(Config {
languages,
..Default::default()
})
}
pub fn with_config(config: Config<'_, '_>) -> Result<Self, InitFailed> {
let ptr = unsafe { c::TessBaseAPICreate() };
let ptr = NonNull::new(ptr).expect("TessBaseAPICreate returned NULL");
let ret = unsafe {
c::TessBaseAPIInit2(
ptr.as_ptr(),
config
.data_dir
.map(|x| x.as_ptr())
.unwrap_or(core::ptr::null_mut()),
config.languages.as_ptr(),
config.ocr_engine_mode as u32,
)
};
if ret < 0 {
return Err(InitFailed);
}
let base = Tesseract { ptr };
Ok(Self { base })
}
pub fn recognize_text<'a>(
&'a mut self,
image: &Image,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
let monitor: Option<Monitor<()>> = None;
self.do_recognize_text(image, monitor)
}
pub fn recognize_text_with_timeout<'a>(
&'a mut self,
image: &Image,
timeout: Duration,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
let mut monitor = Monitor::<()>::new();
monitor.set_timeout(timeout);
self.do_recognize_text(image, Some(monitor))
}
pub fn recognize_text_with_monitor<'a, C>(
&'a mut self,
image: &Image,
monitor: Monitor<C>,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
self.do_recognize_text(image, Some(monitor))
}
fn do_recognize_text<'a, C>(
&'a mut self,
image: &Image,
monitor: Option<Monitor<C>>,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
unsafe { c::TessBaseAPISetImage2(self.as_ptr(), image.ptr.as_ptr()) };
let monitor_ptr = monitor
.map(|m| m.ptr.as_ptr())
.unwrap_or(core::ptr::null_mut());
let ret = unsafe { c::TessBaseAPIRecognize(self.as_ptr(), monitor_ptr) };
if ret < 0 {
return Err(RecognitionFailed);
}
Ok(RecognitionResults { inner: self })
}
pub fn recognize_text_in_rect<'a>(
&'a mut self,
image: &Image,
rect: &Rectangle,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
let monitor: Option<Monitor<()>> = None;
self.do_recognize_text_in_rect(image, rect, monitor)
}
pub fn recognize_text_in_rect_with_timeout<'a>(
&'a mut self,
image: &Image,
rect: &Rectangle,
timeout: Duration,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
let mut monitor = Monitor::<()>::new();
monitor.set_timeout(timeout);
self.do_recognize_text_in_rect(image, rect, Some(monitor))
}
pub fn recognize_text_in_rect_with_monitor<'a, C>(
&'a mut self,
image: &Image,
rect: &Rectangle,
monitor: Monitor<C>,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
self.do_recognize_text_in_rect(image, rect, Some(monitor))
}
fn do_recognize_text_in_rect<'a, C>(
&'a mut self,
image: &Image,
rect: &Rectangle,
monitor: Option<Monitor<C>>,
) -> Result<RecognitionResults<'a>, RecognitionFailed> {
unsafe { c::TessBaseAPISetImage2(self.as_ptr(), image.ptr.as_ptr()) };
unsafe {
c::TessBaseAPISetRectangle(
self.as_ptr(),
rect.left as i32,
rect.top as i32,
rect.width as i32,
rect.height as i32,
)
};
let monitor_ptr = monitor
.map(|m| m.ptr.as_ptr())
.unwrap_or(core::ptr::null_mut());
let ret = unsafe { c::TessBaseAPIRecognize(self.as_ptr(), monitor_ptr) };
if ret < 0 {
return Err(RecognitionFailed);
}
Ok(RecognitionResults { inner: self })
}
pub fn analyze_layout<'a>(&'a self, image: &Image) -> LayoutIter<'a> {
unsafe { c::TessBaseAPISetImage2(self.as_ptr(), image.ptr.as_ptr()) };
let ptr = unsafe { c::TessBaseAPIAnalyseLayout(self.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIAnalyseLayout returned NULL");
unsafe { c::TessPageIteratorBegin(ptr.as_ptr()) };
LayoutIter {
ptr,
phantom: PhantomData,
}
}
pub fn num_dawgs(&self) -> u32 {
let ret = unsafe { c::TessBaseAPIGetPageSegMode(self.ptr.as_ptr()) };
ret as u32
}
#[inline]
fn as_ptr(&self) -> *mut c::TessBaseAPI {
self.base.ptr.as_ptr()
}
}
impl Deref for TextRecognizer {
type Target = Tesseract;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for TextRecognizer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
pub struct RecognitionResults<'a> {
inner: &'a TextRecognizer,
}
impl<'a> RecognitionResults<'a> {
pub fn get_utf8_text(&self) -> Utf8Text {
let ptr = unsafe { c::TessBaseAPIGetUTF8Text(self.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetUTF8Text returned NULL");
Utf8Text(Text { ptr })
}
pub fn get_hocr_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetHOCRText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetHOCRText returned NULL");
Text { ptr }
}
pub fn get_alto_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetAltoText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetAltoText returned NULL");
Text { ptr }
}
#[doc(hidden)]
pub fn get_page_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetPAGEText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetPAGEText returned NULL");
Text { ptr }
}
pub fn get_tsv_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetTsvText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetTsvText returned NULL");
Text { ptr }
}
pub fn get_box_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetBoxText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetBoxText returned NULL");
Text { ptr }
}
pub fn get_lstm_box_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetLSTMBoxText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetLSTMBoxText returned NULL");
Text { ptr }
}
pub fn get_word_str_box_text(&self, page: u32) -> Text {
let ptr = unsafe { c::TessBaseAPIGetWordStrBoxText(self.as_ptr(), page as i32) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetWordStrBoxText returned NULL");
Text { ptr }
}
pub fn get_unlv_text(&self) -> Text {
let ptr = unsafe { c::TessBaseAPIGetUNLVText(self.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetUNLVText returned NULL");
Text { ptr }
}
pub fn iter(&self) -> ResultIter<'a> {
let ptr = unsafe { c::TessBaseAPIGetIterator(self.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetIterator returned NULL");
ResultIter {
ptr,
phantom: PhantomData,
}
}
pub fn get_thresholded_image(&self) -> Image {
let ptr = unsafe { c::TessBaseAPIGetThresholdedImage(self.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessBaseAPIGetThresholdedImage returned NULL");
Image { ptr }
}
pub fn get_thresholded_image_scale_factor(&self) -> u32 {
let ret = unsafe { c::TessBaseAPIGetThresholdedImageScaleFactor(self.as_ptr()) };
ret as u32
}
pub fn get_gradient(&self) -> f32 {
unsafe { c::TessBaseAPIGetGradient(self.as_ptr()) }
}
#[doc(hidden)]
pub fn is_valid_word(&self, word: &CStr) -> bool {
let ret = unsafe { c::TessBaseAPIIsValidWord(self.as_ptr(), word.as_ptr()) };
ret != 0
}
#[doc(hidden)]
pub fn get_text_direction(&self) -> Option<(u32, f32)> {
let mut offset: i32 = 0;
let mut slope: f32 = 0.0;
let ret = unsafe { c::TessBaseAPIGetTextDirection(self.as_ptr(), &mut offset, &mut slope) };
if ret == 0 {
return None;
}
Some((offset as u32, slope))
}
#[inline]
fn as_ptr(&self) -> *mut c::TessBaseAPI {
self.inner.as_ptr()
}
}
pub struct ResultIter<'a> {
ptr: NonNull<c::TessResultIterator>,
#[allow(unused)]
phantom: PhantomData<&'a Tesseract>,
}
impl<'a> ResultIter<'a> {
#[must_use]
pub fn next(&mut self, level: LayoutLevel) -> Option<TextElement<'_>> {
let ret = unsafe { c::TessResultIteratorNext(self.ptr.as_ptr(), level as u32) };
(ret != 0).then_some(TextElement { iter: self })
}
pub fn as_layout_iter(&self) -> LayoutIter<'a> {
let ptr = unsafe { c::TessResultIteratorGetPageIterator(self.ptr.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessResultIteratorGetPageIterator returned NULL");
LayoutIter {
ptr,
phantom: PhantomData,
}
}
}
impl Drop for ResultIter<'_> {
fn drop(&mut self) {
unsafe { c::TessResultIteratorDelete(self.ptr.as_ptr()) };
}
}
impl Clone for ResultIter<'_> {
fn clone(&self) -> Self {
let ptr = unsafe { c::TessResultIteratorCopy(self.ptr.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessResultIteratorCopy returned NULL");
Self {
ptr,
phantom: PhantomData,
}
}
}
pub struct TextElement<'a> {
iter: &'a ResultIter<'a>,
}
impl<'a> TextElement<'a> {
pub fn get_utf8_text(&self, level: LayoutLevel) -> Utf8Text {
let ptr = unsafe { c::TessResultIteratorGetUTF8Text(self.iter.ptr.as_ptr(), level as u32) };
let ptr = NonNull::new(ptr).expect("TessResultIteratorGetUTF8Text returned NULL");
Utf8Text(Text { ptr })
}
pub fn confidence(&self, level: LayoutLevel) -> f32 {
unsafe { c::TessResultIteratorConfidence(self.iter.ptr.as_ptr(), level as u32) }
}
pub fn word_recognition_language(&self) -> Option<&CStr> {
let ptr = unsafe { c::TessResultIteratorWordRecognitionLanguage(self.iter.ptr.as_ptr()) };
if ptr.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(ptr) })
}
pub fn word_is_from_dictionary(&self) -> bool {
let ret = unsafe { c::TessResultIteratorWordIsFromDictionary(self.iter.ptr.as_ptr()) };
ret != 0
}
pub fn word_is_numeric(&self) -> bool {
let ret = unsafe { c::TessResultIteratorWordIsNumeric(self.iter.ptr.as_ptr()) };
ret != 0
}
pub fn word_font_attributes(&self) -> Option<(FontAttrs, &CStr)> {
let mut is_bold = 0;
let mut is_italic = 0;
let mut is_underlined = 0;
let mut is_monospace = 0;
let mut is_serif = 0;
let mut is_smallcaps = 0;
let mut point_size = 0;
let mut font_id = 0;
let ptr = unsafe {
c::TessResultIteratorWordFontAttributes(
self.iter.ptr.as_ptr(),
&mut is_bold,
&mut is_italic,
&mut is_underlined,
&mut is_monospace,
&mut is_serif,
&mut is_smallcaps,
&mut point_size,
&mut font_id,
)
};
if ptr.is_null() {
return None;
}
let font = unsafe { CStr::from_ptr(ptr) };
let attrs = FontAttrs {
is_bold: is_bold != 0,
is_italic: is_italic != 0,
is_underlined: is_underlined != 0,
is_monospace: is_monospace != 0,
is_serif: is_serif != 0,
is_smallcaps: is_smallcaps != 0,
point_size: point_size as u32,
font_id,
};
Some((attrs, font))
}
pub fn symbol_is_superscript(&self) -> bool {
let ret = unsafe { c::TessResultIteratorSymbolIsSuperscript(self.iter.ptr.as_ptr()) };
ret != 0
}
pub fn symbol_is_subscript(&self) -> bool {
let ret = unsafe { c::TessResultIteratorSymbolIsSubscript(self.iter.ptr.as_ptr()) };
ret != 0
}
pub fn symbol_is_dropcap(&self) -> bool {
let ret = unsafe { c::TessResultIteratorSymbolIsDropcap(self.iter.ptr.as_ptr()) };
ret != 0
}
pub fn choices(&self) -> ChoiceIterator<'a> {
let ptr = unsafe { c::TessResultIteratorGetChoiceIterator(self.iter.ptr.as_ptr()) };
let ptr = NonNull::new(ptr).expect("TessResultIteratorGetChoiceIterator returned NULL");
ChoiceIterator {
ptr,
results: self.iter,
}
}
}
impl<'a> Deref for TextElement<'a> {
type Target = Element<'a>;
fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute(self) }
}
}
pub struct ClassifierChoice<'a> {
iter: &'a ChoiceIterator<'a>,
}
impl ClassifierChoice<'_> {
pub fn get_utf8_text(&self) -> &str {
let ptr = unsafe { c::TessChoiceIteratorGetUTF8Text(self.iter.ptr.as_ptr()) };
assert!(!ptr.is_null());
let c_str = unsafe { CStr::from_ptr(ptr) };
unsafe { core::str::from_utf8_unchecked(c_str.to_bytes()) }
}
pub fn confidence(&self) -> f32 {
unsafe { c::TessChoiceIteratorConfidence(self.iter.ptr.as_ptr()) }
}
}
impl AsRef<str> for ClassifierChoice<'_> {
fn as_ref(&self) -> &str {
self.get_utf8_text()
}
}
pub struct ChoiceIterator<'a> {
ptr: NonNull<c::TessChoiceIterator>,
#[allow(unused)]
results: &'a ResultIter<'a>,
}
impl ChoiceIterator<'_> {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<ClassifierChoice<'_>> {
let ret = unsafe { c::TessChoiceIteratorNext(self.ptr.as_ptr()) };
(ret != 0).then_some(ClassifierChoice { iter: self })
}
}
impl Drop for ChoiceIterator<'_> {
fn drop(&mut self) {
unsafe { c::TessChoiceIteratorDelete(self.ptr.as_ptr()) };
}
}
unsafe extern "C" fn cancel_callback<C: FnMut(i32) -> bool>(
cancel_this: *mut c_void,
words: i32,
) -> bool {
let func: *mut C = cancel_this.cast();
let func: &mut C = unsafe { &mut *func };
func(words)
}
pub struct Monitor<C> {
ptr: NonNull<c::ETEXT_DESC>,
#[allow(unused)]
cancel: Option<Box<C>>,
}
impl Monitor<()> {
pub fn new() -> Self {
let ptr = unsafe { c::TessMonitorCreate() };
let ptr = NonNull::new(ptr).expect("TessMonitorCreate returned NULL");
Self { ptr, cancel: None }
}
}
impl Default for Monitor<()> {
fn default() -> Self {
Self::new()
}
}
impl<C: FnMut(i32) -> bool> Monitor<C> {
pub fn with_cancel_callback(cancel: C) -> Self {
let ptr = unsafe { c::TessMonitorCreate() };
let ptr = NonNull::new(ptr).expect("TessMonitorCreate returned NULL");
unsafe { c::TessMonitorSetCancelFunc(ptr.as_ptr(), Some(cancel_callback::<C>)) };
let cancel = Box::new(cancel);
let cancel_raw = Box::into_raw(cancel);
unsafe { c::TessMonitorSetCancelThis(ptr.as_ptr(), cancel_raw as *mut c_void) };
let cancel = Some(unsafe { Box::from_raw(cancel_raw) });
Self { ptr, cancel }
}
pub fn get_cancel_callback(&mut self) -> &mut C {
self.cancel
.as_mut()
.expect("Set in the constructor")
.deref_mut()
}
}
impl<C> Monitor<C> {
pub fn set_progress_callback_raw(&mut self, callback: c::TessProgressFunc) {
unsafe { c::TessMonitorSetProgressFunc(self.ptr.as_ptr(), callback) }
}
pub fn get_progress(&self) -> u32 {
let ret = unsafe { c::TessMonitorGetProgress(self.ptr.as_ptr()) };
ret as u32
}
pub fn set_timeout(&mut self, timeout: Duration) {
let millis = timeout.as_millis().try_into().unwrap_or(i32::MAX);
unsafe { c::TessMonitorSetDeadlineMSecs(self.ptr.as_ptr(), millis) };
}
}
impl<C> Drop for Monitor<C> {
fn drop(&mut self) {
unsafe { c::TessMonitorDelete(self.ptr.as_ptr()) }
}
}