Skip to main content

visionkit/
live_text_interaction.rs

1use core::ffi::{c_char, c_void};
2use core::ops::{BitOr, BitOrAssign};
3use core::ptr;
4use std::path::Path;
5
6use crate::error::VisionKitError;
7use crate::ffi;
8use crate::image_analysis::ImageAnalysis;
9use crate::private::{error_from_status, path_to_cstring, string_from_ptr};
10
11type BoolQueryFn = unsafe extern "C" fn(
12    token: *mut c_void,
13    out_value: *mut i32,
14    out_error_message: *mut *mut c_char,
15) -> i32;
16type TypesQueryFn = unsafe extern "C" fn(
17    token: *mut c_void,
18    out_types_raw: *mut u64,
19    out_error_message: *mut *mut c_char,
20) -> i32;
21type BoolSetterFn = unsafe extern "C" fn(
22    token: *mut c_void,
23    value: i32,
24    out_error_message: *mut *mut c_char,
25) -> i32;
26type PointBoolQueryFn = unsafe extern "C" fn(
27    token: *mut c_void,
28    x: f64,
29    y: f64,
30    out_value: *mut i32,
31    out_error_message: *mut *mut c_char,
32) -> i32;
33type RectQueryFn = unsafe extern "C" fn(
34    token: *mut c_void,
35    out_x: *mut f64,
36    out_y: *mut f64,
37    out_width: *mut f64,
38    out_height: *mut f64,
39    out_error_message: *mut *mut c_char,
40) -> i32;
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub struct Rect {
44    pub x: f64,
45    pub y: f64,
46    pub width: f64,
47    pub height: f64,
48}
49
50impl Rect {
51    #[must_use]
52    pub fn is_empty(self) -> bool {
53        self.width <= 0.0 || self.height <= 0.0
54    }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq)]
58pub struct EdgeInsets {
59    pub top: f64,
60    pub left: f64,
61    pub bottom: f64,
62    pub right: f64,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct LiveTextInteractionTypes(u64);
67
68impl LiveTextInteractionTypes {
69    pub const NONE: Self = Self(0);
70    pub const AUTOMATIC: Self = Self(1);
71    pub const TEXT_SELECTION: Self = Self(2);
72    pub const DATA_DETECTORS: Self = Self(4);
73    pub const IMAGE_SUBJECT: Self = Self(8);
74    pub const VISUAL_LOOK_UP: Self = Self(16);
75    pub const AUTOMATIC_TEXT_ONLY: Self = Self(32);
76
77    #[must_use]
78    pub const fn new(raw: u64) -> Self {
79        Self(raw)
80    }
81
82    #[must_use]
83    pub const fn bits(self) -> u64 {
84        self.0
85    }
86
87    #[must_use]
88    pub const fn contains(self, other: Self) -> bool {
89        (self.0 & other.0) == other.0
90    }
91}
92
93impl BitOr for LiveTextInteractionTypes {
94    type Output = Self;
95
96    fn bitor(self, rhs: Self) -> Self::Output {
97        Self(self.0 | rhs.0)
98    }
99}
100
101impl BitOrAssign for LiveTextInteractionTypes {
102    fn bitor_assign(&mut self, rhs: Self) {
103        self.0 |= rhs.0;
104    }
105}
106
107impl Default for LiveTextInteractionTypes {
108    fn default() -> Self {
109        Self::NONE
110    }
111}
112
113pub struct LiveTextInteraction {
114    token: *mut c_void,
115}
116
117impl Drop for LiveTextInteraction {
118    fn drop(&mut self) {
119        if !self.token.is_null() {
120            unsafe { ffi::live_text_interaction::vk_live_text_interaction_release(self.token) };
121            self.token = ptr::null_mut();
122        }
123    }
124}
125
126impl LiveTextInteraction {
127    pub fn new() -> Result<Self, VisionKitError> {
128        let token = unsafe { ffi::live_text_interaction::vk_live_text_interaction_new() };
129        if token.is_null() {
130            return Err(VisionKitError::UnavailableOnThisMacOS(
131                "LiveTextInteraction requires macOS 13+".to_owned(),
132            ));
133        }
134        Ok(Self { token })
135    }
136
137    pub fn set_analysis(&self, analysis: &ImageAnalysis) -> Result<(), VisionKitError> {
138        let mut err_msg: *mut c_char = ptr::null_mut();
139        let status = unsafe {
140            ffi::live_text_interaction::vk_live_text_interaction_set_analysis(
141                self.token,
142                analysis.raw_token(),
143                &mut err_msg,
144            )
145        };
146        Self::status_to_unit(status, err_msg)
147    }
148
149    pub fn track_image_at_path<P: AsRef<Path>>(&self, path: P) -> Result<(), VisionKitError> {
150        let path = path_to_cstring(path.as_ref())?;
151        let mut err_msg: *mut c_char = ptr::null_mut();
152        let status = unsafe {
153            ffi::live_text_interaction::vk_live_text_interaction_track_image_at_path(
154                self.token,
155                path.as_ptr(),
156                &mut err_msg,
157            )
158        };
159        Self::status_to_unit(status, err_msg)
160    }
161
162    pub fn preferred_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
163        self.query_types(
164            ffi::live_text_interaction::vk_live_text_interaction_preferred_interaction_types,
165        )
166    }
167
168    pub fn set_preferred_interaction_types(
169        &self,
170        interaction_types: LiveTextInteractionTypes,
171    ) -> Result<(), VisionKitError> {
172        let mut err_msg: *mut c_char = ptr::null_mut();
173        let status = unsafe {
174            ffi::live_text_interaction::vk_live_text_interaction_set_preferred_interaction_types(
175                self.token,
176                interaction_types.bits(),
177                &mut err_msg,
178            )
179        };
180        Self::status_to_unit(status, err_msg)
181    }
182
183    pub fn active_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
184        self.query_types(
185            ffi::live_text_interaction::vk_live_text_interaction_active_interaction_types,
186        )
187    }
188
189    pub fn selectable_items_highlighted(&self) -> Result<bool, VisionKitError> {
190        self.query_bool(
191            ffi::live_text_interaction::vk_live_text_interaction_selectable_items_highlighted,
192        )
193    }
194
195    pub fn set_selectable_items_highlighted(&self, value: bool) -> Result<(), VisionKitError> {
196        self.set_bool(
197            value,
198            ffi::live_text_interaction::vk_live_text_interaction_set_selectable_items_highlighted,
199        )
200    }
201
202    pub fn has_active_text_selection(&self) -> Result<bool, VisionKitError> {
203        self.query_bool(
204            ffi::live_text_interaction::vk_live_text_interaction_has_active_text_selection,
205        )
206    }
207
208    pub fn reset_selection(&self) -> Result<(), VisionKitError> {
209        let mut err_msg: *mut c_char = ptr::null_mut();
210        let status = unsafe {
211            ffi::live_text_interaction::vk_live_text_interaction_reset_selection(
212                self.token,
213                &mut err_msg,
214            )
215        };
216        Self::status_to_unit(status, err_msg)
217    }
218
219    pub fn text(&self) -> Result<String, VisionKitError> {
220        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_text)
221    }
222
223    pub fn selected_text(&self) -> Result<String, VisionKitError> {
224        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_selected_text)
225    }
226
227    pub fn contents_rect(&self) -> Result<Rect, VisionKitError> {
228        self.query_rect(ffi::live_text_interaction::vk_live_text_interaction_contents_rect)
229    }
230
231    pub fn has_interactive_item_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
232        self.query_point_bool(
233            x,
234            y,
235            ffi::live_text_interaction::vk_live_text_interaction_has_interactive_item_at_point,
236        )
237    }
238
239    pub fn has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
240        self.query_point_bool(
241            x,
242            y,
243            ffi::live_text_interaction::vk_live_text_interaction_has_text_at_point,
244        )
245    }
246
247    pub fn has_data_detector_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
248        self.query_point_bool(
249            x,
250            y,
251            ffi::live_text_interaction::vk_live_text_interaction_has_data_detector_at_point,
252        )
253    }
254
255    pub fn has_supplementary_interface_at_point(
256        &self,
257        x: f64,
258        y: f64,
259    ) -> Result<bool, VisionKitError> {
260        self.query_point_bool(
261            x,
262            y,
263            ffi::live_text_interaction::vk_live_text_interaction_has_supplementary_interface_at_point,
264        )
265    }
266
267    pub fn analysis_has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
268        self.query_point_bool(
269            x,
270            y,
271            ffi::live_text_interaction::vk_live_text_interaction_analysis_has_text_at_point,
272        )
273    }
274
275    pub fn live_text_button_visible(&self) -> Result<bool, VisionKitError> {
276        self.query_bool(
277            ffi::live_text_interaction::vk_live_text_interaction_live_text_button_visible,
278        )
279    }
280
281    pub fn is_supplementary_interface_hidden(&self) -> Result<bool, VisionKitError> {
282        self.query_bool(
283            ffi::live_text_interaction::vk_live_text_interaction_is_supplementary_interface_hidden,
284        )
285    }
286
287    pub fn set_supplementary_interface_hidden(
288        &self,
289        hidden: bool,
290        animated: bool,
291    ) -> Result<(), VisionKitError> {
292        let mut err_msg: *mut c_char = ptr::null_mut();
293        let status = unsafe {
294            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_hidden(
295                self.token,
296                i32::from(hidden),
297                i32::from(animated),
298                &mut err_msg,
299            )
300        };
301        Self::status_to_unit(status, err_msg)
302    }
303
304    pub fn supplementary_interface_content_insets(&self) -> Result<EdgeInsets, VisionKitError> {
305        let rect = self.query_rect(
306            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_content_insets,
307        )?;
308        Ok(EdgeInsets {
309            top: rect.x,
310            left: rect.y,
311            bottom: rect.width,
312            right: rect.height,
313        })
314    }
315
316    pub fn set_supplementary_interface_content_insets(
317        &self,
318        insets: EdgeInsets,
319    ) -> Result<(), VisionKitError> {
320        let mut err_msg: *mut c_char = ptr::null_mut();
321        let status = unsafe {
322            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_content_insets(
323                self.token,
324                insets.top,
325                insets.left,
326                insets.bottom,
327                insets.right,
328                &mut err_msg,
329            )
330        };
331        Self::status_to_unit(status, err_msg)
332    }
333
334    fn status_to_unit(status: i32, err_msg: *mut c_char) -> Result<(), VisionKitError> {
335        if status == ffi::status::OK {
336            Ok(())
337        } else {
338            Err(unsafe { error_from_status(status, err_msg) })
339        }
340    }
341
342    fn query_bool(&self, query: BoolQueryFn) -> Result<bool, VisionKitError> {
343        let mut value = 0;
344        let mut err_msg: *mut c_char = ptr::null_mut();
345        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
346        if status == ffi::status::OK {
347            Ok(value != 0)
348        } else {
349            Err(unsafe { error_from_status(status, err_msg) })
350        }
351    }
352
353    fn set_bool(&self, value: bool, setter: BoolSetterFn) -> Result<(), VisionKitError> {
354        let mut err_msg: *mut c_char = ptr::null_mut();
355        let status = unsafe { setter(self.token, i32::from(value), &mut err_msg) };
356        Self::status_to_unit(status, err_msg)
357    }
358
359    fn query_types(&self, query: TypesQueryFn) -> Result<LiveTextInteractionTypes, VisionKitError> {
360        let mut raw = 0;
361        let mut err_msg: *mut c_char = ptr::null_mut();
362        let status = unsafe { query(self.token, &mut raw, &mut err_msg) };
363        if status == ffi::status::OK {
364            Ok(LiveTextInteractionTypes::new(raw))
365        } else {
366            Err(unsafe { error_from_status(status, err_msg) })
367        }
368    }
369
370    fn query_string(
371        &self,
372        query: unsafe extern "C" fn(*mut c_void, *mut *mut c_char, *mut *mut c_char) -> i32,
373    ) -> Result<String, VisionKitError> {
374        let mut value: *mut c_char = ptr::null_mut();
375        let mut err_msg: *mut c_char = ptr::null_mut();
376        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
377        if status == ffi::status::OK {
378            unsafe { string_from_ptr(value, "live text interaction string") }
379        } else {
380            Err(unsafe { error_from_status(status, err_msg) })
381        }
382    }
383
384    fn query_rect(&self, query: RectQueryFn) -> Result<Rect, VisionKitError> {
385        let mut x = 0.0;
386        let mut y = 0.0;
387        let mut width = 0.0;
388        let mut height = 0.0;
389        let mut err_msg: *mut c_char = ptr::null_mut();
390        let status = unsafe {
391            query(
392                self.token,
393                &mut x,
394                &mut y,
395                &mut width,
396                &mut height,
397                &mut err_msg,
398            )
399        };
400        if status == ffi::status::OK {
401            Ok(Rect {
402                x,
403                y,
404                width,
405                height,
406            })
407        } else {
408            Err(unsafe { error_from_status(status, err_msg) })
409        }
410    }
411
412    fn query_point_bool(
413        &self,
414        x: f64,
415        y: f64,
416        query: PointBoolQueryFn,
417    ) -> Result<bool, VisionKitError> {
418        let mut value = 0;
419        let mut err_msg: *mut c_char = ptr::null_mut();
420        let status = unsafe { query(self.token, x, y, &mut value, &mut err_msg) };
421        if status == ffi::status::OK {
422            Ok(value != 0)
423        } else {
424            Err(unsafe { error_from_status(status, err_msg) })
425        }
426    }
427}