Skip to main content

tesseract_ocr_static/
layout.rs

1use core::marker::PhantomData;
2use core::ops::Deref;
3use core::ops::DerefMut;
4use core::ptr::NonNull;
5
6use crate::BlockType;
7use crate::Image;
8use crate::LayoutLevel;
9use crate::Line;
10use crate::Orientation;
11use crate::OrientationParams;
12use crate::ParagraphInfo;
13use crate::ParagraphJustification;
14use crate::Rectangle;
15use crate::Tesseract;
16use crate::TextlineOrder;
17use crate::WritingDirection;
18
19/// Layout analysis engine.
20///
21/// Uses less memory than [`TextRecognizer`](crate::TextRecognizer) but can't recognize text, only
22/// analyze its layout.
23pub struct LayoutAnalyzer {
24    base: Tesseract,
25}
26
27impl LayoutAnalyzer {
28    /// Creates new layout analyzer with default parameters.
29    pub fn new() -> Self {
30        let ptr = unsafe { c::TessBaseAPICreate() };
31        let ptr = NonNull::new(ptr).expect("TessBaseAPICreate returned NULL");
32        unsafe { c::TessBaseAPIInitForAnalysePage(ptr.as_ptr()) };
33        let base = Tesseract { ptr };
34        Self { base }
35    }
36
37    /// Analyzes text layout in the provided image and returns an iterator over the results.
38    pub fn analyze(&mut self, image: &Image) -> LayoutIter<'_> {
39        unsafe { c::TessBaseAPISetImage2(self.as_ptr(), image.ptr.as_ptr()) };
40        let ptr = unsafe { c::TessBaseAPIAnalyseLayout(self.as_ptr()) };
41        let ptr = NonNull::new(ptr).expect("TessBaseAPIAnalyseLayout returned NULL");
42        unsafe { c::TessPageIteratorBegin(ptr.as_ptr()) };
43        LayoutIter {
44            ptr,
45            phantom: PhantomData,
46        }
47    }
48
49    #[inline]
50    fn as_ptr(&self) -> *mut c::TessBaseAPI {
51        self.base.ptr.as_ptr()
52    }
53}
54
55impl Default for LayoutAnalyzer {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl Deref for LayoutAnalyzer {
62    type Target = Tesseract;
63
64    fn deref(&self) -> &Self::Target {
65        &self.base
66    }
67}
68
69impl DerefMut for LayoutAnalyzer {
70    fn deref_mut(&mut self) -> &mut Self::Target {
71        &mut self.base
72    }
73}
74
75/// An iterator over text layout elements.
76pub struct LayoutIter<'a> {
77    pub(crate) ptr: NonNull<c::TessPageIterator>,
78    #[allow(unused)]
79    pub(crate) phantom: PhantomData<&'a Tesseract>,
80}
81
82impl<'a> LayoutIter<'a> {
83    /// Returns the next layout element at the specified level or
84    /// `None` if such an element doesn't exist.
85    #[must_use]
86    pub fn next(&mut self, level: LayoutLevel) -> Option<Element<'_>> {
87        let ret = unsafe { c::TessPageIteratorNext(self.ptr.as_ptr(), level as u32) };
88        (ret != 0).then_some(Element { iter: self })
89    }
90}
91
92impl Drop for LayoutIter<'_> {
93    fn drop(&mut self) {
94        unsafe { c::TessPageIteratorDelete(self.ptr.as_ptr()) };
95    }
96}
97
98impl Clone for LayoutIter<'_> {
99    fn clone(&self) -> Self {
100        let ptr = unsafe { c::TessPageIteratorCopy(self.ptr.as_ptr()) };
101        let ptr = NonNull::new(ptr).expect("TessPageIteratorCopy returned NULL");
102        Self {
103            ptr,
104            phantom: PhantomData,
105        }
106    }
107}
108
109/// Text layout element.
110pub struct Element<'a> {
111    iter: &'a LayoutIter<'a>,
112}
113
114impl Element<'_> {
115    /// Returns `true` if the element is at the start of the given level.
116    pub fn is_at_beginning_of(&self, level: LayoutLevel) -> bool {
117        let ret =
118            unsafe { c::TessPageIteratorIsAtBeginningOf(self.iter.ptr.as_ptr(), level as u32) };
119        ret != 0
120    }
121
122    /// Returns `true` if the element is at the end of the given level.
123    pub fn is_at_final_element(&self, level: LayoutLevel, element: LayoutLevel) -> bool {
124        let ret = unsafe {
125            c::TessPageIteratorIsAtFinalElement(
126                self.iter.ptr.as_ptr(),
127                level as u32,
128                element as u32,
129            )
130        };
131        ret != 0
132    }
133
134    /// Returns the bounds of the element at the given level of the layout.
135    ///
136    /// Returns `None` if the given level doesn't exist.
137    ///
138    /// The box position and dimensions should match the ones of
139    /// [`get_binary_image`](Self::get_binary_image)
140    /// but may not match the ones of [`get_image`](Self::get_image) due to padding.
141    pub fn bounding_box(&self, level: LayoutLevel) -> Option<Rectangle> {
142        let mut left = 0;
143        let mut top = 0;
144        let mut right = 0;
145        let mut bottom = 0;
146        let ret = unsafe {
147            c::TessPageIteratorBoundingBox(
148                self.iter.ptr.as_ptr(),
149                level as u32,
150                &mut left,
151                &mut top,
152                &mut right,
153                &mut bottom,
154            )
155        };
156        (ret != 0).then_some(Rectangle {
157            left: left as u32,
158            top: top as u32,
159            width: (right - left) as u32,
160            height: (bottom - top) as u32,
161        })
162    }
163
164    /// Returns block type.
165    pub fn block_type(&self) -> BlockType {
166        let ret = unsafe { c::TessPageIteratorBlockType(self.iter.ptr.as_ptr()) };
167        BlockType::from_raw(ret)
168    }
169
170    /// Returns a binary image of the element at the given layout level.
171    pub fn get_binary_image(&self, level: LayoutLevel) -> Image {
172        let ptr =
173            unsafe { c::TessPageIteratorGetBinaryImage(self.iter.ptr.as_ptr(), level as u32) };
174        let ptr = NonNull::new(ptr).expect("TessPageIteratorGetBinaryImage returned NULL");
175        Image { ptr }
176    }
177
178    /// Returns a grayscale image of the element at the given layout level.
179    ///
180    /// Padding is added to the left-top corner in both dimensions.
181    ///
182    /// If `padding` is zero, then the returned positions and dimensions should match
183    /// the ones of [`bounding_box`](Self::bounding_box).
184    pub fn get_image(
185        &self,
186        level: LayoutLevel,
187        padding: u32,
188        original: &Image,
189    ) -> (Image, i32, i32) {
190        let mut left: i32 = 0;
191        let mut top: i32 = 0;
192        let ptr = unsafe {
193            c::TessPageIteratorGetImage(
194                self.iter.ptr.as_ptr(),
195                level as u32,
196                padding as i32,
197                original.ptr.as_ptr(),
198                &mut left,
199                &mut top,
200            )
201        };
202        let ptr = NonNull::new(ptr).expect("TessPageIteratorGetImage returned NULL");
203        (Image { ptr }, left, top)
204    }
205
206    /// Returns the baseline of the element.
207    pub fn baseline(&self, level: LayoutLevel) -> Option<Line> {
208        let mut x1 = 0;
209        let mut y1 = 0;
210        let mut x2 = 0;
211        let mut y2 = 0;
212        let ret = unsafe {
213            c::TessPageIteratorBaseline(
214                self.iter.ptr.as_ptr(),
215                level as u32,
216                &mut x1,
217                &mut y1,
218                &mut x2,
219                &mut y2,
220            )
221        };
222        (ret != 0).then_some(Line {
223            x1: x1 as u32,
224            y1: y1 as u32,
225            x2: x2 as u32,
226            y2: y2 as u32,
227        })
228    }
229
230    /// Returns the element's block orientation.
231    pub fn orientation(&self) -> OrientationParams {
232        let mut orientation = 0;
233        let mut writing_direction = 0;
234        let mut textline_order = 0;
235        let mut deskew_angle: f32 = 0.0;
236        unsafe {
237            c::TessPageIteratorOrientation(
238                self.iter.ptr.as_ptr(),
239                &mut orientation,
240                &mut writing_direction,
241                &mut textline_order,
242                &mut deskew_angle,
243            )
244        };
245        OrientationParams {
246            orientation: Orientation::from_raw(orientation),
247            writing_direction: WritingDirection::from_raw(writing_direction),
248            textline_order: TextlineOrder::from_raw(textline_order),
249            deskew_angle,
250        }
251    }
252
253    /// Returns the element's paragraph information.
254    pub fn paragraph_info(&self) -> ParagraphInfo {
255        let mut justification = 0;
256        let mut is_list_item = 0;
257        let mut is_crown = 0;
258        let mut first_line_indent = 0;
259        unsafe {
260            c::TessPageIteratorParagraphInfo(
261                self.iter.ptr.as_ptr(),
262                &mut justification,
263                &mut is_list_item,
264                &mut is_crown,
265                &mut first_line_indent,
266            )
267        };
268        ParagraphInfo {
269            justification: ParagraphJustification::from_raw(justification),
270            is_list_item: is_list_item != 0,
271            is_crown: is_crown != 0,
272            first_line_indent,
273        }
274    }
275}