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}