Skip to main content

apple_vision/text_rectangles/
mod.rs

1#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
2#![allow(clippy::too_long_first_doc_paragraph)]
3//! `VNDetectTextRectanglesRequest` — text-region detection (no OCR).
4
5use std::ffi::{CStr, CString};
6use std::path::Path;
7use std::ptr;
8
9use crate::error::VisionError;
10use crate::ffi;
11
12/// A detected text rectangle in normalised (0..1) image coordinates,
13/// origin bottom-left (Vision convention).
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct TextRect {
16    pub x: f64,
17    pub y: f64,
18    pub w: f64,
19    pub h: f64,
20    pub confidence: f32,
21}
22
23/// Detect text-region rectangles in the image at `path`. Set
24/// `report_character_boxes` to true if you want the per-character
25/// bounding boxes (currently unused by this binding, kept for
26/// parity with Apple's flag).
27///
28/// # Errors
29///
30/// Returns [`VisionError`] when the image fails to load or the
31/// Vision request errors.
32pub fn detect_text_rectangles(
33    path: impl AsRef<Path>,
34    report_character_boxes: bool,
35) -> Result<Vec<TextRect>, VisionError> {
36    let path_str = path.as_ref().to_str().ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 path".into()))?;
37    let cpath = CString::new(path_str).map_err(|e| VisionError::InvalidArgument(format!("path NUL byte: {e}")))?;
38    let mut rects_ptr: *mut ffi::SimpleRectRaw = ptr::null_mut();
39    let mut count: isize = 0;
40    let mut err: *mut std::ffi::c_char = ptr::null_mut();
41    let status = unsafe {
42        ffi::vn_detect_text_rectangles_in_path(
43            cpath.as_ptr(),
44            report_character_boxes,
45            &mut rects_ptr,
46            &mut count,
47            &mut err,
48        )
49    };
50    if status != ffi::status::OK {
51        let msg = unsafe { take_err(err) };
52        return Err(VisionError::RequestFailed(msg));
53    }
54    let mut out = Vec::with_capacity(count.max(0) as usize);
55    for i in 0..count {
56        let r = unsafe { rects_ptr.offset(i).read() };
57        out.push(TextRect {
58            x: r.x,
59            y: r.y,
60            w: r.w,
61            h: r.h,
62            confidence: r.confidence,
63        });
64    }
65    if !rects_ptr.is_null() {
66        unsafe { ffi::vn_simple_rects_free(rects_ptr, count) };
67    }
68    Ok(out)
69}
70
71unsafe fn take_err(p: *mut std::ffi::c_char) -> String {
72    if p.is_null() {
73        return String::new();
74    }
75    let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned();
76    unsafe { libc::free(p.cast()) };
77    s
78}