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;
5use std::sync::OnceLock;
6
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9
10use crate::error::VisionKitError;
11use crate::ffi;
12use crate::image_analysis::ImageAnalysis;
13use crate::private::{
14    error_from_status, json_cstring, parse_json_ptr, path_to_cstring, string_from_ptr,
15    vec_from_buffer_ptr,
16};
17
18type BoolQueryFn = unsafe extern "C" fn(
19    token: *mut c_void,
20    out_value: *mut i32,
21    out_error_message: *mut *mut c_char,
22) -> i32;
23type TypesQueryFn = unsafe extern "C" fn(
24    token: *mut c_void,
25    out_types_raw: *mut u64,
26    out_error_message: *mut *mut c_char,
27) -> i32;
28type BoolSetterFn = unsafe extern "C" fn(
29    token: *mut c_void,
30    value: i32,
31    out_error_message: *mut *mut c_char,
32) -> i32;
33type PointBoolQueryFn = unsafe extern "C" fn(
34    token: *mut c_void,
35    x: f64,
36    y: f64,
37    out_value: *mut i32,
38    out_error_message: *mut *mut c_char,
39) -> i32;
40type RectQueryFn = unsafe extern "C" fn(
41    token: *mut c_void,
42    out_x: *mut f64,
43    out_y: *mut f64,
44    out_width: *mut f64,
45    out_height: *mut f64,
46    out_error_message: *mut *mut c_char,
47) -> i32;
48type JsonQueryFn = unsafe extern "C" fn(
49    token: *mut c_void,
50    out_json: *mut *mut c_char,
51    out_error_message: *mut *mut c_char,
52) -> i32;
53type JsonSetterFn = unsafe extern "C" fn(
54    token: *mut c_void,
55    json: *const c_char,
56    out_error_message: *mut *mut c_char,
57) -> i32;
58type OptionalTokenQueryFn = unsafe extern "C" fn(
59    token: *mut c_void,
60    out_token: *mut *mut c_void,
61    out_error_message: *mut *mut c_char,
62) -> i32;
63#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
64/// Represents a point exchanged with VisionKit.
65pub struct Point {
66    /// Stores the VisionKit x value.
67    pub x: f64,
68    /// Stores the VisionKit y value.
69    pub y: f64,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
73/// Represents a rectangle exchanged with VisionKit.
74pub struct Rect {
75    /// Stores the VisionKit x value.
76    pub x: f64,
77    /// Stores the VisionKit y value.
78    pub y: f64,
79    /// Stores the VisionKit width value.
80    pub width: f64,
81    /// Stores the VisionKit height value.
82    pub height: f64,
83}
84
85impl Rect {
86    #[must_use]
87    /// Returns whether this VisionKit `Rect` value is empty.
88    pub fn is_empty(self) -> bool {
89        self.width <= 0.0 || self.height <= 0.0
90    }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
94/// Represents a size reported by VisionKit.
95pub struct Size {
96    /// Stores the VisionKit width value.
97    pub width: f64,
98    /// Stores the VisionKit height value.
99    pub height: f64,
100}
101
102impl Size {
103    #[must_use]
104    /// Returns whether this VisionKit `Size` value is empty.
105    pub fn is_empty(self) -> bool {
106        self.width <= 0.0 || self.height <= 0.0
107    }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
111/// Represents edge insets used by VisionKit.
112pub struct EdgeInsets {
113    /// Stores the VisionKit top value.
114    pub top: f64,
115    /// Stores the VisionKit left value.
116    pub left: f64,
117    /// Stores the VisionKit bottom value.
118    pub bottom: f64,
119    /// Stores the VisionKit right value.
120    pub right: f64,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
124#[serde(rename_all = "camelCase")]
125/// Represents a text range reported by VisionKit.
126pub struct LiveTextTextRange {
127    /// Stores the VisionKit location value.
128    pub location: usize,
129    /// Stores the VisionKit length value.
130    pub length: usize,
131}
132
133impl LiveTextTextRange {
134    #[must_use]
135    /// Creates the VisionKit `LiveTextTextRange` wrapper.
136    pub const fn new(location: usize, length: usize) -> Self {
137        Self { location, length }
138    }
139
140    #[must_use]
141    /// Returns the end offset derived from the VisionKit text range.
142    pub const fn end(self) -> usize {
143        self.location.saturating_add(self.length)
144    }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149/// Represents a VisionKit attributed-text attribute.
150pub struct LiveTextAttributedTextAttribute {
151    /// Stores the VisionKit name value.
152    pub name: String,
153    /// Stores the VisionKit value value.
154    pub value: String,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159/// Represents a VisionKit attributed-text run.
160pub struct LiveTextAttributedTextRun {
161    /// Stores the VisionKit range value.
162    pub range: LiveTextTextRange,
163    /// Stores the VisionKit attributes value.
164    pub attributes: Vec<LiveTextAttributedTextAttribute>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169/// Represents VisionKit selected attributed text.
170pub struct LiveTextAttributedText {
171    /// Stores the VisionKit text value.
172    pub text: String,
173    /// Stores the VisionKit runs value.
174    pub runs: Vec<LiveTextAttributedTextRun>,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
178#[serde(transparent)]
179/// Wraps a VisionKit live text menu tag.
180pub struct LiveTextMenuTag(i64);
181
182impl LiveTextMenuTag {
183    #[must_use]
184    /// Creates the VisionKit `LiveTextMenuTag` wrapper.
185    pub const fn new(raw_value: i64) -> Self {
186        Self(raw_value)
187    }
188
189    #[must_use]
190    /// Returns the raw VisionKit menu-tag value.
191    pub const fn raw_value(self) -> i64 {
192        self.0
193    }
194
195    /// Returns the VisionKit menu tag for copying an image.
196    pub fn copy_image() -> Result<Self, VisionKitError> {
197        Ok(Self(live_text_menu_tag_constants()?.copy_image))
198    }
199
200    /// Returns the VisionKit menu tag for sharing an image.
201    pub fn share_image() -> Result<Self, VisionKitError> {
202        Ok(Self(live_text_menu_tag_constants()?.share_image))
203    }
204
205    /// Returns the VisionKit menu tag for copying a subject.
206    pub fn copy_subject() -> Result<Self, VisionKitError> {
207        Ok(Self(live_text_menu_tag_constants()?.copy_subject))
208    }
209
210    /// Returns the VisionKit menu tag for sharing a subject.
211    pub fn share_subject() -> Result<Self, VisionKitError> {
212        Ok(Self(live_text_menu_tag_constants()?.share_subject))
213    }
214
215    /// Returns the VisionKit menu tag for looking up an item.
216    pub fn lookup_item() -> Result<Self, VisionKitError> {
217        Ok(Self(live_text_menu_tag_constants()?.lookup_item))
218    }
219
220    /// Returns the VisionKit menu tag for recommended app items.
221    pub fn recommended_app_items() -> Result<Self, VisionKitError> {
222        Ok(Self(live_text_menu_tag_constants()?.recommended_app_items))
223    }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228/// Represents a VisionKit live text menu item.
229pub struct LiveTextMenuItem {
230    /// Stores the VisionKit title value.
231    pub title: String,
232    /// Stores the VisionKit tag value.
233    pub tag: i64,
234    /// Indicates whether VisionKit reports this value is separator.
235    pub is_separator: bool,
236    /// Indicates whether VisionKit reports this value is enabled.
237    pub is_enabled: bool,
238    /// Indicates whether VisionKit reports this value is hidden.
239    pub is_hidden: bool,
240    /// Stores the VisionKit state value.
241    pub state: i64,
242    /// Stores the VisionKit submenu value.
243    pub submenu: Option<Box<LiveTextMenu>>,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248/// Represents a VisionKit live text menu.
249pub struct LiveTextMenu {
250    /// Stores the VisionKit title value.
251    pub title: String,
252    /// Stores the VisionKit items value.
253    pub items: Vec<LiveTextMenuItem>,
254}
255
256#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258/// Represents VisionKit event details captured by the live text delegate.
259pub struct LiveTextEventInfo {
260    /// Stores the VisionKit type name value.
261    pub type_name: String,
262    /// Stores the VisionKit location in window value.
263    pub location_in_window: Point,
264    /// Stores the VisionKit modifier flags value.
265    pub modifier_flags: u64,
266    /// Stores the VisionKit key code value.
267    pub key_code: u16,
268    /// Stores the VisionKit characters value.
269    pub characters: Option<String>,
270    /// Stores the VisionKit characters ignoring modifiers value.
271    pub characters_ignoring_modifiers: Option<String>,
272    /// Stores the VisionKit click count value.
273    pub click_count: i64,
274}
275
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278/// Represents a VisionKit live text delegate callback event.
279pub struct LiveTextDelegateEvent {
280    /// Stores the VisionKit kind value.
281    pub kind: String,
282    /// Stores the VisionKit point value.
283    pub point: Option<Point>,
284    /// Stores the VisionKit analysis type raw value.
285    pub analysis_type_raw: Option<u64>,
286    /// Stores the VisionKit decision value.
287    pub decision: Option<bool>,
288    /// Stores the VisionKit rect value.
289    pub rect: Option<Rect>,
290    /// Stores the VisionKit event value.
291    pub event: Option<LiveTextEventInfo>,
292    /// Stores the VisionKit menu value.
293    pub menu: Option<LiveTextMenu>,
294    /// Stores the VisionKit menu item value.
295    pub menu_item: Option<LiveTextMenuItem>,
296    /// Stores the VisionKit visible value.
297    pub visible: Option<bool>,
298    /// Stores the VisionKit highlighted value.
299    pub highlighted: Option<bool>,
300    /// Indicates whether VisionKit reports this value has content view.
301    pub has_content_view: Option<bool>,
302}
303
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306/// Represents a VisionKit font value.
307pub struct LiveTextFont {
308    /// Stores the VisionKit name value.
309    pub name: String,
310    /// Stores the VisionKit point size value.
311    pub point_size: f64,
312}
313
314#[derive(Debug, Clone, PartialEq)]
315/// Represents image data returned by VisionKit.
316pub struct LiveTextImageData {
317    /// Stores the VisionKit size value.
318    pub size: Size,
319    /// Stores the VisionKit png data value.
320    pub png_data: Vec<u8>,
321}
322
323impl LiveTextImageData {
324    #[must_use]
325    /// Returns whether this VisionKit `LiveTextImageData` value is empty.
326    pub fn is_empty(&self) -> bool {
327        self.size.is_empty() || self.png_data.is_empty()
328    }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333struct LiveTextMenuTagConstants {
334    copy_image: i64,
335    share_image: i64,
336    copy_subject: i64,
337    share_subject: i64,
338    lookup_item: i64,
339    recommended_app_items: i64,
340}
341
342static LIVE_TEXT_MENU_TAGS: OnceLock<Result<LiveTextMenuTagConstants, VisionKitError>> =
343    OnceLock::new();
344
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346#[serde(rename_all = "camelCase")]
347struct LiveTextInteractionDelegateConfigPayload {
348    should_begin: bool,
349    contents_rect: Option<Rect>,
350    should_handle_key_down_event: bool,
351    should_show_menu_for_event: bool,
352    updated_menu: Option<LiveTextMenu>,
353}
354
355impl Default for LiveTextInteractionDelegateConfigPayload {
356    fn default() -> Self {
357        Self {
358            should_begin: true,
359            contents_rect: None,
360            should_handle_key_down_event: true,
361            should_show_menu_for_event: true,
362            updated_menu: None,
363        }
364    }
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
368/// Wraps VisionKit live text interaction type flags.
369pub struct LiveTextInteractionTypes(u64);
370
371impl LiveTextInteractionTypes {
372    /// Matches the empty VisionKit `LiveTextInteractionTypes` flag set.
373    pub const NONE: Self = Self(0);
374    /// Matches the VisionKit `AUTOMATIC` flag.
375    pub const AUTOMATIC: Self = Self(1);
376    /// Matches the VisionKit `TEXT_SELECTION` flag.
377    pub const TEXT_SELECTION: Self = Self(2);
378    /// Matches the VisionKit `DATA_DETECTORS` flag.
379    pub const DATA_DETECTORS: Self = Self(4);
380    /// Matches the VisionKit `IMAGE_SUBJECT` flag.
381    pub const IMAGE_SUBJECT: Self = Self(8);
382    /// Matches the VisionKit `VISUAL_LOOK_UP` flag.
383    pub const VISUAL_LOOK_UP: Self = Self(16);
384    /// Matches the VisionKit `AUTOMATIC_TEXT_ONLY` flag.
385    pub const AUTOMATIC_TEXT_ONLY: Self = Self(32);
386
387    #[must_use]
388    /// Creates the VisionKit `LiveTextInteractionTypes` wrapper.
389    pub const fn new(raw: u64) -> Self {
390        Self(raw)
391    }
392
393    #[must_use]
394    /// Returns the raw VisionKit `LiveTextInteractionTypes` value.
395    pub const fn bits(self) -> u64 {
396        self.0
397    }
398
399    #[must_use]
400    /// Returns whether this VisionKit `LiveTextInteractionTypes` value contains `other`.
401    pub const fn contains(self, other: Self) -> bool {
402        (self.0 & other.0) == other.0
403    }
404}
405
406impl BitOr for LiveTextInteractionTypes {
407    type Output = Self;
408
409    fn bitor(self, rhs: Self) -> Self::Output {
410        Self(self.0 | rhs.0)
411    }
412}
413
414impl BitOrAssign for LiveTextInteractionTypes {
415    fn bitor_assign(&mut self, rhs: Self) {
416        self.0 |= rhs.0;
417    }
418}
419
420impl Default for LiveTextInteractionTypes {
421    fn default() -> Self {
422        Self::NONE
423    }
424}
425
426/// Wraps the VisionKit content view counterpart used with live text.
427pub struct LiveTextContentView {
428    token: *mut c_void,
429}
430
431impl Drop for LiveTextContentView {
432    fn drop(&mut self) {
433        if !self.token.is_null() {
434            unsafe { ffi::live_text_interaction::vk_live_text_content_view_release(self.token) };
435            self.token = ptr::null_mut();
436        }
437    }
438}
439
440impl LiveTextContentView {
441    /// Creates the VisionKit `LiveTextContentView` wrapper.
442    pub fn new() -> Result<Self, VisionKitError> {
443        let token = unsafe { ffi::live_text_interaction::vk_live_text_content_view_new() };
444        if token.is_null() {
445            return Err(VisionKitError::Unknown(
446                "failed to allocate LiveTextContentView".to_owned(),
447            ));
448        }
449        Ok(Self { token })
450    }
451
452    /// Returns the VisionKit frame value.
453    pub fn frame(&self) -> Result<Rect, VisionKitError> {
454        query_rect_call(
455            "live text content view frame",
456            |out_x, out_y, out_width, out_height, out_error_message| unsafe {
457                ffi::live_text_interaction::vk_live_text_content_view_frame(
458                    self.token,
459                    out_x,
460                    out_y,
461                    out_width,
462                    out_height,
463                    out_error_message,
464                )
465            },
466        )
467    }
468
469    /// Sets the VisionKit frame value.
470    pub fn set_frame(&self, frame: Rect) -> Result<(), VisionKitError> {
471        let mut err_msg: *mut c_char = ptr::null_mut();
472        let status = unsafe {
473            ffi::live_text_interaction::vk_live_text_content_view_set_frame(
474                self.token,
475                frame.x,
476                frame.y,
477                frame.width,
478                frame.height,
479                &mut err_msg,
480            )
481        };
482        status_to_unit(status, err_msg)
483    }
484
485    pub(crate) fn raw_token(&self) -> *mut c_void {
486        self.token
487    }
488
489    fn from_token(token: *mut c_void) -> Self {
490        Self { token }
491    }
492}
493
494/// Wraps the VisionKit tracking image view counterpart.
495pub struct LiveTextTrackingImageView {
496    token: *mut c_void,
497}
498
499impl Drop for LiveTextTrackingImageView {
500    fn drop(&mut self) {
501        if !self.token.is_null() {
502            unsafe {
503                ffi::live_text_interaction::vk_live_text_tracking_image_view_release(self.token);
504            }
505            self.token = ptr::null_mut();
506        }
507    }
508}
509
510impl LiveTextTrackingImageView {
511    /// Creates the VisionKit `LiveTextTrackingImageView` wrapper.
512    pub fn new() -> Result<Self, VisionKitError> {
513        let token = unsafe { ffi::live_text_interaction::vk_live_text_tracking_image_view_new() };
514        if token.is_null() {
515            return Err(VisionKitError::Unknown(
516                "failed to allocate LiveTextTrackingImageView".to_owned(),
517            ));
518        }
519        Ok(Self { token })
520    }
521
522    /// Returns the VisionKit frame value.
523    pub fn frame(&self) -> Result<Rect, VisionKitError> {
524        query_rect_call(
525            "live text tracking image view frame",
526            |out_x, out_y, out_width, out_height, out_error_message| unsafe {
527                ffi::live_text_interaction::vk_live_text_tracking_image_view_frame(
528                    self.token,
529                    out_x,
530                    out_y,
531                    out_width,
532                    out_height,
533                    out_error_message,
534                )
535            },
536        )
537    }
538
539    /// Sets the VisionKit frame value.
540    pub fn set_frame(&self, frame: Rect) -> Result<(), VisionKitError> {
541        let mut err_msg: *mut c_char = ptr::null_mut();
542        let status = unsafe {
543            ffi::live_text_interaction::vk_live_text_tracking_image_view_set_frame(
544                self.token,
545                frame.x,
546                frame.y,
547                frame.width,
548                frame.height,
549                &mut err_msg,
550            )
551        };
552        status_to_unit(status, err_msg)
553    }
554
555    /// Sets the VisionKit image at path value.
556    pub fn set_image_at_path<P: AsRef<Path>>(&self, path: P) -> Result<(), VisionKitError> {
557        let path = path_to_cstring(path.as_ref())?;
558        let mut err_msg: *mut c_char = ptr::null_mut();
559        let status = unsafe {
560            ffi::live_text_interaction::vk_live_text_tracking_image_view_set_image_at_path(
561                self.token,
562                path.as_ptr(),
563                &mut err_msg,
564            )
565        };
566        status_to_unit(status, err_msg)
567    }
568
569    /// Returns the VisionKit image size value.
570    pub fn image_size(&self) -> Result<Option<Size>, VisionKitError> {
571        let mut has_image = 0;
572        let mut width = 0.0;
573        let mut height = 0.0;
574        let mut err_msg: *mut c_char = ptr::null_mut();
575        let status = unsafe {
576            ffi::live_text_interaction::vk_live_text_tracking_image_view_image_size(
577                self.token,
578                &mut has_image,
579                &mut width,
580                &mut height,
581                &mut err_msg,
582            )
583        };
584        if status == ffi::status::OK {
585            Ok((has_image != 0).then_some(Size { width, height }))
586        } else {
587            Err(unsafe { error_from_status(status, err_msg) })
588        }
589    }
590
591    pub(crate) fn raw_token(&self) -> *mut c_void {
592        self.token
593    }
594
595    fn from_token(token: *mut c_void) -> Self {
596        Self { token }
597    }
598}
599
600/// Wraps the VisionKit live text interaction delegate counterpart.
601pub struct LiveTextInteractionDelegate {
602    token: *mut c_void,
603}
604
605impl Drop for LiveTextInteractionDelegate {
606    fn drop(&mut self) {
607        if !self.token.is_null() {
608            unsafe {
609                ffi::live_text_interaction::vk_live_text_interaction_delegate_release(self.token);
610            }
611            self.token = ptr::null_mut();
612        }
613    }
614}
615
616impl LiveTextInteractionDelegate {
617    /// Creates the VisionKit `LiveTextInteractionDelegate` wrapper.
618    pub fn new() -> Result<Self, VisionKitError> {
619        let token = unsafe { ffi::live_text_interaction::vk_live_text_interaction_delegate_new() };
620        if token.is_null() {
621            return Err(VisionKitError::UnavailableOnThisMacOS(
622                "LiveTextInteractionDelegate requires macOS 13+".to_owned(),
623            ));
624        }
625        Ok(Self { token })
626    }
627
628    /// Returns whether VisionKit should begin.
629    pub fn should_begin(&self) -> Result<bool, VisionKitError> {
630        Ok(self.config()?.should_begin)
631    }
632
633    /// Sets the VisionKit should begin value.
634    pub fn set_should_begin(&self, value: bool) -> Result<(), VisionKitError> {
635        let mut config = self.config()?;
636        config.should_begin = value;
637        self.set_config(&config)
638    }
639
640    /// Returns the VisionKit contents rect override value.
641    pub fn contents_rect_override(&self) -> Result<Option<Rect>, VisionKitError> {
642        Ok(self.config()?.contents_rect)
643    }
644
645    /// Sets the VisionKit contents rect override value.
646    pub fn set_contents_rect_override(&self, value: Option<Rect>) -> Result<(), VisionKitError> {
647        let mut config = self.config()?;
648        config.contents_rect = value;
649        self.set_config(&config)
650    }
651
652    /// Returns the VisionKit content view value.
653    pub fn content_view(&self) -> Result<Option<LiveTextContentView>, VisionKitError> {
654        optional_token_call(|out_token, out_error_message| unsafe {
655            ffi::live_text_interaction::vk_live_text_interaction_delegate_content_view(
656                self.token,
657                out_token,
658                out_error_message,
659            )
660        })
661        .map(|token| token.map(LiveTextContentView::from_token))
662    }
663
664    /// Sets the VisionKit content view value.
665    pub fn set_content_view(
666        &self,
667        value: Option<&LiveTextContentView>,
668    ) -> Result<(), VisionKitError> {
669        let mut err_msg: *mut c_char = ptr::null_mut();
670        let status = unsafe {
671            ffi::live_text_interaction::vk_live_text_interaction_delegate_set_content_view(
672                self.token,
673                value.map_or(ptr::null_mut(), LiveTextContentView::raw_token),
674                &mut err_msg,
675            )
676        };
677        status_to_unit(status, err_msg)
678    }
679
680    /// Returns whether VisionKit should handle key down event.
681    pub fn should_handle_key_down_event(&self) -> Result<bool, VisionKitError> {
682        Ok(self.config()?.should_handle_key_down_event)
683    }
684
685    /// Sets the VisionKit should handle key down event value.
686    pub fn set_should_handle_key_down_event(&self, value: bool) -> Result<(), VisionKitError> {
687        let mut config = self.config()?;
688        config.should_handle_key_down_event = value;
689        self.set_config(&config)
690    }
691
692    /// Returns whether VisionKit should show menu for event.
693    pub fn should_show_menu_for_event(&self) -> Result<bool, VisionKitError> {
694        Ok(self.config()?.should_show_menu_for_event)
695    }
696
697    /// Sets the VisionKit should show menu for event value.
698    pub fn set_should_show_menu_for_event(&self, value: bool) -> Result<(), VisionKitError> {
699        let mut config = self.config()?;
700        config.should_show_menu_for_event = value;
701        self.set_config(&config)
702    }
703
704    /// Returns the VisionKit updated menu value.
705    pub fn updated_menu(&self) -> Result<Option<LiveTextMenu>, VisionKitError> {
706        Ok(self.config()?.updated_menu)
707    }
708
709    /// Sets the VisionKit updated menu value.
710    pub fn set_updated_menu(&self, value: Option<&LiveTextMenu>) -> Result<(), VisionKitError> {
711        let mut config = self.config()?;
712        config.updated_menu = value.cloned();
713        self.set_config(&config)
714    }
715
716    /// Returns the VisionKit delegate events recorded by this wrapper.
717    pub fn recorded_events(&self) -> Result<Vec<LiveTextDelegateEvent>, VisionKitError> {
718        parse_json_call(
719            |out_json, out_error_message| unsafe {
720                ffi::live_text_interaction::vk_live_text_interaction_delegate_recorded_events_json(
721                    self.token,
722                    out_json,
723                    out_error_message,
724                )
725            },
726            "live text interaction delegate recorded events",
727        )
728    }
729
730    /// Clears the recorded VisionKit delegate events.
731    pub fn clear_recorded_events(&self) -> Result<(), VisionKitError> {
732        let mut err_msg: *mut c_char = ptr::null_mut();
733        let status = unsafe {
734            ffi::live_text_interaction::vk_live_text_interaction_delegate_clear_recorded_events(
735                self.token,
736                &mut err_msg,
737            )
738        };
739        status_to_unit(status, err_msg)
740    }
741
742    pub(crate) fn raw_token(&self) -> *mut c_void {
743        self.token
744    }
745
746    fn from_token(token: *mut c_void) -> Self {
747        Self { token }
748    }
749
750    fn config(&self) -> Result<LiveTextInteractionDelegateConfigPayload, VisionKitError> {
751        parse_json_call(
752            |out_json, out_error_message| unsafe {
753                ffi::live_text_interaction::vk_live_text_interaction_delegate_config_json(
754                    self.token,
755                    out_json,
756                    out_error_message,
757                )
758            },
759            "live text interaction delegate config",
760        )
761    }
762
763    fn set_config(
764        &self,
765        config: &LiveTextInteractionDelegateConfigPayload,
766    ) -> Result<(), VisionKitError> {
767        let config_json = json_cstring(config)?;
768        let mut err_msg: *mut c_char = ptr::null_mut();
769        let status = unsafe {
770            ffi::live_text_interaction::vk_live_text_interaction_delegate_set_config_json(
771                self.token,
772                config_json.as_ptr(),
773                &mut err_msg,
774            )
775        };
776        status_to_unit(status, err_msg)
777    }
778}
779
780/// Wraps a VisionKit image subject.
781pub struct LiveTextSubject {
782    token: *mut c_void,
783}
784
785impl Drop for LiveTextSubject {
786    fn drop(&mut self) {
787        if !self.token.is_null() {
788            unsafe { ffi::live_text_interaction::vk_live_text_subject_release(self.token) };
789            self.token = ptr::null_mut();
790        }
791    }
792}
793
794impl LiveTextSubject {
795    /// Returns the VisionKit bounds value.
796    pub fn bounds(&self) -> Result<Rect, VisionKitError> {
797        query_rect_call(
798            "live text subject bounds",
799            |out_x, out_y, out_width, out_height, out_error_message| unsafe {
800                ffi::live_text_interaction::vk_live_text_subject_bounds(
801                    self.token,
802                    out_x,
803                    out_y,
804                    out_width,
805                    out_height,
806                    out_error_message,
807                )
808            },
809        )
810    }
811
812    /// Returns the VisionKit image value.
813    pub fn image(&self) -> Result<LiveTextImageData, VisionKitError> {
814        query_image_data_call(
815            |out_bytes, out_len, out_width, out_height, out_error_message| unsafe {
816                ffi::live_text_interaction::vk_live_text_subject_png_data(
817                    self.token,
818                    out_bytes,
819                    out_len,
820                    out_width,
821                    out_height,
822                    out_error_message,
823                )
824            },
825            "live text subject image",
826        )
827    }
828
829    pub(crate) fn raw_token(&self) -> *mut c_void {
830        self.token
831    }
832
833    fn from_token(token: *mut c_void) -> Self {
834        Self { token }
835    }
836}
837
838/// Wraps the VisionKit image analysis overlay view counterpart.
839pub struct LiveTextInteraction {
840    token: *mut c_void,
841}
842
843impl Drop for LiveTextInteraction {
844    fn drop(&mut self) {
845        if !self.token.is_null() {
846            unsafe { ffi::live_text_interaction::vk_live_text_interaction_release(self.token) };
847            self.token = ptr::null_mut();
848        }
849    }
850}
851
852impl LiveTextInteraction {
853    /// Creates the VisionKit `LiveTextInteraction` wrapper.
854    pub fn new() -> Result<Self, VisionKitError> {
855        let token = unsafe { ffi::live_text_interaction::vk_live_text_interaction_new() };
856        if token.is_null() {
857            return Err(VisionKitError::UnavailableOnThisMacOS(
858                "LiveTextInteraction requires macOS 13+".to_owned(),
859            ));
860        }
861        Ok(Self { token })
862    }
863
864    pub(crate) fn raw_token(&self) -> *mut c_void {
865        self.token
866    }
867
868    /// Creates the VisionKit live text interaction wrapper with the provided delegate.
869    pub fn with_delegate(delegate: &LiveTextInteractionDelegate) -> Result<Self, VisionKitError> {
870        let token = unsafe {
871            ffi::live_text_interaction::vk_live_text_interaction_new_with_delegate(
872                delegate.raw_token(),
873            )
874        };
875        if token.is_null() {
876            return Err(VisionKitError::UnavailableOnThisMacOS(
877                "LiveTextInteraction requires macOS 13+".to_owned(),
878            ));
879        }
880        Ok(Self { token })
881    }
882
883    /// Sets the VisionKit analysis value.
884    pub fn set_analysis(&self, analysis: &ImageAnalysis) -> Result<(), VisionKitError> {
885        let mut err_msg: *mut c_char = ptr::null_mut();
886        let status = unsafe {
887            ffi::live_text_interaction::vk_live_text_interaction_set_analysis(
888                self.token,
889                analysis.raw_token(),
890                &mut err_msg,
891            )
892        };
893        status_to_unit(status, err_msg)
894    }
895
896    /// Tracks the image at `path` for the VisionKit live text interaction.
897    pub fn track_image_at_path<P: AsRef<Path>>(&self, path: P) -> Result<(), VisionKitError> {
898        let path = path_to_cstring(path.as_ref())?;
899        let mut err_msg: *mut c_char = ptr::null_mut();
900        let status = unsafe {
901            ffi::live_text_interaction::vk_live_text_interaction_track_image_at_path(
902                self.token,
903                path.as_ptr(),
904                &mut err_msg,
905            )
906        };
907        status_to_unit(status, err_msg)
908    }
909
910    /// Returns the VisionKit delegate value.
911    pub fn delegate(&self) -> Result<Option<LiveTextInteractionDelegate>, VisionKitError> {
912        self.query_optional_token(ffi::live_text_interaction::vk_live_text_interaction_delegate)
913            .map(|token| token.map(LiveTextInteractionDelegate::from_token))
914    }
915
916    /// Sets the VisionKit delegate value.
917    pub fn set_delegate(
918        &self,
919        delegate: Option<&LiveTextInteractionDelegate>,
920    ) -> Result<(), VisionKitError> {
921        let mut err_msg: *mut c_char = ptr::null_mut();
922        let status = unsafe {
923            ffi::live_text_interaction::vk_live_text_interaction_set_delegate(
924                self.token,
925                delegate.map_or(ptr::null_mut(), LiveTextInteractionDelegate::raw_token),
926                &mut err_msg,
927            )
928        };
929        status_to_unit(status, err_msg)
930    }
931
932    /// Returns the VisionKit preferred interaction types value.
933    pub fn preferred_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
934        self.query_types(
935            ffi::live_text_interaction::vk_live_text_interaction_preferred_interaction_types,
936        )
937    }
938
939    /// Sets the VisionKit preferred interaction types value.
940    pub fn set_preferred_interaction_types(
941        &self,
942        interaction_types: LiveTextInteractionTypes,
943    ) -> Result<(), VisionKitError> {
944        let mut err_msg: *mut c_char = ptr::null_mut();
945        let status = unsafe {
946            ffi::live_text_interaction::vk_live_text_interaction_set_preferred_interaction_types(
947                self.token,
948                interaction_types.bits(),
949                &mut err_msg,
950            )
951        };
952        status_to_unit(status, err_msg)
953    }
954
955    /// Returns the VisionKit active interaction types value.
956    pub fn active_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
957        self.query_types(
958            ffi::live_text_interaction::vk_live_text_interaction_active_interaction_types,
959        )
960    }
961
962    /// Returns the VisionKit selectable items highlighted value.
963    pub fn selectable_items_highlighted(&self) -> Result<bool, VisionKitError> {
964        self.query_bool(
965            ffi::live_text_interaction::vk_live_text_interaction_selectable_items_highlighted,
966        )
967    }
968
969    /// Sets the VisionKit selectable items highlighted value.
970    pub fn set_selectable_items_highlighted(&self, value: bool) -> Result<(), VisionKitError> {
971        self.set_bool(
972            value,
973            ffi::live_text_interaction::vk_live_text_interaction_set_selectable_items_highlighted,
974        )
975    }
976
977    /// Returns the VisionKit tracking image view value.
978    pub fn tracking_image_view(&self) -> Result<Option<LiveTextTrackingImageView>, VisionKitError> {
979        self.query_optional_token(
980            ffi::live_text_interaction::vk_live_text_interaction_tracking_image_view,
981        )
982        .map(|token| token.map(LiveTextTrackingImageView::from_token))
983    }
984
985    /// Sets the VisionKit tracking image view value.
986    pub fn set_tracking_image_view(
987        &self,
988        view: Option<&LiveTextTrackingImageView>,
989    ) -> Result<(), VisionKitError> {
990        let mut err_msg: *mut c_char = ptr::null_mut();
991        let status = unsafe {
992            ffi::live_text_interaction::vk_live_text_interaction_set_tracking_image_view(
993                self.token,
994                view.map_or(ptr::null_mut(), LiveTextTrackingImageView::raw_token),
995                &mut err_msg,
996            )
997        };
998        status_to_unit(status, err_msg)
999    }
1000
1001    /// Returns whether VisionKit reports active text selection.
1002    pub fn has_active_text_selection(&self) -> Result<bool, VisionKitError> {
1003        self.query_bool(
1004            ffi::live_text_interaction::vk_live_text_interaction_has_active_text_selection,
1005        )
1006    }
1007
1008    /// Resets the VisionKit selection state.
1009    pub fn reset_selection(&self) -> Result<(), VisionKitError> {
1010        let mut err_msg: *mut c_char = ptr::null_mut();
1011        let status = unsafe {
1012            ffi::live_text_interaction::vk_live_text_interaction_reset_selection(
1013                self.token,
1014                &mut err_msg,
1015            )
1016        };
1017        status_to_unit(status, err_msg)
1018    }
1019
1020    /// Returns the VisionKit text value.
1021    pub fn text(&self) -> Result<String, VisionKitError> {
1022        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_text)
1023    }
1024
1025    /// Returns the VisionKit selected text value.
1026    pub fn selected_text(&self) -> Result<String, VisionKitError> {
1027        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_selected_text)
1028    }
1029
1030    /// Returns the VisionKit selected attributed text value.
1031    pub fn selected_attributed_text(&self) -> Result<LiveTextAttributedText, VisionKitError> {
1032        self.query_json(
1033            ffi::live_text_interaction::vk_live_text_interaction_selected_attributed_text_json,
1034            "live text interaction selected attributed text",
1035        )
1036    }
1037
1038    /// Returns the VisionKit selected ranges value.
1039    pub fn selected_ranges(&self) -> Result<Vec<LiveTextTextRange>, VisionKitError> {
1040        self.query_json(
1041            ffi::live_text_interaction::vk_live_text_interaction_selected_ranges_json,
1042            "live text interaction selected ranges",
1043        )
1044    }
1045
1046    /// Sets the VisionKit selected ranges value.
1047    pub fn set_selected_ranges(&self, ranges: &[LiveTextTextRange]) -> Result<(), VisionKitError> {
1048        self.set_json(
1049            ranges,
1050            ffi::live_text_interaction::vk_live_text_interaction_set_selected_ranges_json,
1051        )
1052    }
1053
1054    /// Returns the VisionKit contents rect value.
1055    pub fn contents_rect(&self) -> Result<Rect, VisionKitError> {
1056        self.query_rect(ffi::live_text_interaction::vk_live_text_interaction_contents_rect)
1057    }
1058
1059    /// Marks the VisionKit contents rectangle as needing an update.
1060    pub fn set_contents_rect_needs_update(&self) -> Result<(), VisionKitError> {
1061        let mut err_msg: *mut c_char = ptr::null_mut();
1062        let status = unsafe {
1063            ffi::live_text_interaction::vk_live_text_interaction_set_contents_rect_needs_update(
1064                self.token,
1065                &mut err_msg,
1066            )
1067        };
1068        status_to_unit(status, err_msg)
1069    }
1070
1071    /// Returns whether VisionKit reports interactive item at point.
1072    pub fn has_interactive_item_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1073        self.query_point_bool(
1074            x,
1075            y,
1076            ffi::live_text_interaction::vk_live_text_interaction_has_interactive_item_at_point,
1077        )
1078    }
1079
1080    /// Returns whether VisionKit reports text at point.
1081    pub fn has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1082        self.query_point_bool(
1083            x,
1084            y,
1085            ffi::live_text_interaction::vk_live_text_interaction_has_text_at_point,
1086        )
1087    }
1088
1089    /// Returns whether VisionKit reports data detector at point.
1090    pub fn has_data_detector_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1091        self.query_point_bool(
1092            x,
1093            y,
1094            ffi::live_text_interaction::vk_live_text_interaction_has_data_detector_at_point,
1095        )
1096    }
1097
1098    /// Returns whether VisionKit reports supplementary interface at point.
1099    pub fn has_supplementary_interface_at_point(
1100        &self,
1101        x: f64,
1102        y: f64,
1103    ) -> Result<bool, VisionKitError> {
1104        self.query_point_bool(
1105            x,
1106            y,
1107            ffi::live_text_interaction::vk_live_text_interaction_has_supplementary_interface_at_point,
1108        )
1109    }
1110
1111    /// Returns the VisionKit analysis has text at point value.
1112    pub fn analysis_has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1113        self.query_point_bool(
1114            x,
1115            y,
1116            ffi::live_text_interaction::vk_live_text_interaction_analysis_has_text_at_point,
1117        )
1118    }
1119
1120    /// Returns the VisionKit live text button visible value.
1121    pub fn live_text_button_visible(&self) -> Result<bool, VisionKitError> {
1122        self.query_bool(
1123            ffi::live_text_interaction::vk_live_text_interaction_live_text_button_visible,
1124        )
1125    }
1126
1127    /// Returns whether VisionKit reports supplementary interface hidden.
1128    pub fn is_supplementary_interface_hidden(&self) -> Result<bool, VisionKitError> {
1129        self.query_bool(
1130            ffi::live_text_interaction::vk_live_text_interaction_is_supplementary_interface_hidden,
1131        )
1132    }
1133
1134    /// Sets the VisionKit supplementary interface hidden value.
1135    pub fn set_supplementary_interface_hidden(
1136        &self,
1137        hidden: bool,
1138        animated: bool,
1139    ) -> Result<(), VisionKitError> {
1140        let mut err_msg: *mut c_char = ptr::null_mut();
1141        let status = unsafe {
1142            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_hidden(
1143                self.token,
1144                i32::from(hidden),
1145                i32::from(animated),
1146                &mut err_msg,
1147            )
1148        };
1149        status_to_unit(status, err_msg)
1150    }
1151
1152    /// Returns the VisionKit supplementary interface content insets value.
1153    pub fn supplementary_interface_content_insets(&self) -> Result<EdgeInsets, VisionKitError> {
1154        let rect = self.query_rect(
1155            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_content_insets,
1156        )?;
1157        Ok(EdgeInsets {
1158            top: rect.x,
1159            left: rect.y,
1160            bottom: rect.width,
1161            right: rect.height,
1162        })
1163    }
1164
1165    /// Sets the VisionKit supplementary interface content insets value.
1166    pub fn set_supplementary_interface_content_insets(
1167        &self,
1168        insets: EdgeInsets,
1169    ) -> Result<(), VisionKitError> {
1170        let mut err_msg: *mut c_char = ptr::null_mut();
1171        let status = unsafe {
1172            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_content_insets(
1173                self.token,
1174                insets.top,
1175                insets.left,
1176                insets.bottom,
1177                insets.right,
1178                &mut err_msg,
1179            )
1180        };
1181        status_to_unit(status, err_msg)
1182    }
1183
1184    /// Returns the VisionKit supplementary interface font value.
1185    pub fn supplementary_interface_font(&self) -> Result<Option<LiveTextFont>, VisionKitError> {
1186        self.query_json(
1187            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_font_json,
1188            "live text interaction supplementary interface font",
1189        )
1190    }
1191
1192    /// Sets the VisionKit supplementary interface font value.
1193    pub fn set_supplementary_interface_font(
1194        &self,
1195        font: Option<&LiveTextFont>,
1196    ) -> Result<(), VisionKitError> {
1197        let font_json = json_cstring(&font)?;
1198        let mut err_msg: *mut c_char = ptr::null_mut();
1199        let status = unsafe {
1200            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_font_json(
1201                self.token,
1202                font_json.as_ptr(),
1203                &mut err_msg,
1204            )
1205        };
1206        status_to_unit(status, err_msg)
1207    }
1208
1209    /// Begins VisionKit subject analysis when the overlay requires it.
1210    pub fn begin_subject_analysis_if_necessary(&self) -> Result<(), VisionKitError> {
1211        let mut err_msg: *mut c_char = ptr::null_mut();
1212        let status = unsafe {
1213            ffi::live_text_interaction::vk_live_text_interaction_begin_subject_analysis_if_necessary(
1214                self.token,
1215                &mut err_msg,
1216            )
1217        };
1218        status_to_unit(status, err_msg)
1219    }
1220
1221    /// Returns the VisionKit subjects value.
1222    pub fn subjects(&self) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1223        self.query_subjects(ffi::live_text_interaction::vk_live_text_interaction_subjects_json)
1224    }
1225
1226    /// Returns the VisionKit highlighted subjects value.
1227    pub fn highlighted_subjects(&self) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1228        self.query_subjects(
1229            ffi::live_text_interaction::vk_live_text_interaction_highlighted_subjects_json,
1230        )
1231    }
1232
1233    /// Sets the VisionKit highlighted subjects value.
1234    pub fn set_highlighted_subjects(
1235        &self,
1236        subjects: &[LiveTextSubject],
1237    ) -> Result<(), VisionKitError> {
1238        let subjects_json = json_cstring(&subject_tokens(subjects))?;
1239        let mut err_msg: *mut c_char = ptr::null_mut();
1240        let status = unsafe {
1241            ffi::live_text_interaction::vk_live_text_interaction_set_highlighted_subjects_json(
1242                self.token,
1243                subjects_json.as_ptr(),
1244                &mut err_msg,
1245            )
1246        };
1247        status_to_unit(status, err_msg)
1248    }
1249
1250    /// Returns the VisionKit subject at point value.
1251    pub fn subject_at_point(
1252        &self,
1253        x: f64,
1254        y: f64,
1255    ) -> Result<Option<LiveTextSubject>, VisionKitError> {
1256        let mut subject_json: *mut c_char = ptr::null_mut();
1257        let mut err_msg: *mut c_char = ptr::null_mut();
1258        let status = unsafe {
1259            ffi::live_text_interaction::vk_live_text_interaction_subject_at_json(
1260                self.token,
1261                x,
1262                y,
1263                &mut subject_json,
1264                &mut err_msg,
1265            )
1266        };
1267        if status == ffi::status::OK {
1268            let token: Option<u64> =
1269                unsafe { parse_json_ptr(subject_json, "live text interaction subject lookup") }?;
1270            Ok(token.map(token_from_u64).map(LiveTextSubject::from_token))
1271        } else {
1272            Err(unsafe { error_from_status(status, err_msg) })
1273        }
1274    }
1275
1276    /// Returns the VisionKit image extracted for the provided subjects.
1277    pub fn image_for_subjects(
1278        &self,
1279        subjects: &[LiveTextSubject],
1280    ) -> Result<LiveTextImageData, VisionKitError> {
1281        let subjects_json = json_cstring(&subject_tokens(subjects))?;
1282        let mut bytes: *mut c_void = ptr::null_mut();
1283        let mut len = 0;
1284        let mut width = 0.0;
1285        let mut height = 0.0;
1286        let mut err_msg: *mut c_char = ptr::null_mut();
1287        let status = unsafe {
1288            ffi::live_text_interaction::vk_live_text_interaction_image_for_subjects_png_data(
1289                self.token,
1290                subjects_json.as_ptr(),
1291                &mut bytes,
1292                &mut len,
1293                &mut width,
1294                &mut height,
1295                &mut err_msg,
1296            )
1297        };
1298        if status == ffi::status::OK {
1299            Ok(LiveTextImageData {
1300                size: Size { width, height },
1301                png_data: unsafe {
1302                    vec_from_buffer_ptr(
1303                        bytes.cast::<u8>(),
1304                        u64_to_usize(len, "live text interaction subject image")?,
1305                        "live text interaction subject image",
1306                    )
1307                }?,
1308            })
1309        } else {
1310            Err(unsafe { error_from_status(status, err_msg) })
1311        }
1312    }
1313
1314    fn query_bool(&self, query: BoolQueryFn) -> Result<bool, VisionKitError> {
1315        let mut value = 0;
1316        let mut err_msg: *mut c_char = ptr::null_mut();
1317        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1318        if status == ffi::status::OK {
1319            Ok(value != 0)
1320        } else {
1321            Err(unsafe { error_from_status(status, err_msg) })
1322        }
1323    }
1324
1325    fn set_bool(&self, value: bool, setter: BoolSetterFn) -> Result<(), VisionKitError> {
1326        let mut err_msg: *mut c_char = ptr::null_mut();
1327        let status = unsafe { setter(self.token, i32::from(value), &mut err_msg) };
1328        status_to_unit(status, err_msg)
1329    }
1330
1331    fn query_types(&self, query: TypesQueryFn) -> Result<LiveTextInteractionTypes, VisionKitError> {
1332        let mut raw = 0;
1333        let mut err_msg: *mut c_char = ptr::null_mut();
1334        let status = unsafe { query(self.token, &mut raw, &mut err_msg) };
1335        if status == ffi::status::OK {
1336            Ok(LiveTextInteractionTypes::new(raw))
1337        } else {
1338            Err(unsafe { error_from_status(status, err_msg) })
1339        }
1340    }
1341
1342    fn query_string(
1343        &self,
1344        query: unsafe extern "C" fn(*mut c_void, *mut *mut c_char, *mut *mut c_char) -> i32,
1345    ) -> Result<String, VisionKitError> {
1346        let mut value: *mut c_char = ptr::null_mut();
1347        let mut err_msg: *mut c_char = ptr::null_mut();
1348        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1349        if status == ffi::status::OK {
1350            unsafe { string_from_ptr(value, "live text interaction string") }
1351        } else {
1352            Err(unsafe { error_from_status(status, err_msg) })
1353        }
1354    }
1355
1356    fn query_json<T>(&self, query: JsonQueryFn, context: &str) -> Result<T, VisionKitError>
1357    where
1358        T: DeserializeOwned,
1359    {
1360        let mut value: *mut c_char = ptr::null_mut();
1361        let mut err_msg: *mut c_char = ptr::null_mut();
1362        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1363        if status == ffi::status::OK {
1364            unsafe { parse_json_ptr(value, context) }
1365        } else {
1366            Err(unsafe { error_from_status(status, err_msg) })
1367        }
1368    }
1369
1370    fn set_json<T>(&self, value: &T, setter: JsonSetterFn) -> Result<(), VisionKitError>
1371    where
1372        T: Serialize + ?Sized,
1373    {
1374        let json = json_cstring(value)?;
1375        let mut err_msg: *mut c_char = ptr::null_mut();
1376        let status = unsafe { setter(self.token, json.as_ptr(), &mut err_msg) };
1377        status_to_unit(status, err_msg)
1378    }
1379
1380    fn query_rect(&self, query: RectQueryFn) -> Result<Rect, VisionKitError> {
1381        query_rect_call(
1382            "live text interaction rect",
1383            |out_x, out_y, out_width, out_height, out_error_message| unsafe {
1384                query(
1385                    self.token,
1386                    out_x,
1387                    out_y,
1388                    out_width,
1389                    out_height,
1390                    out_error_message,
1391                )
1392            },
1393        )
1394    }
1395
1396    fn query_point_bool(
1397        &self,
1398        x: f64,
1399        y: f64,
1400        query: PointBoolQueryFn,
1401    ) -> Result<bool, VisionKitError> {
1402        let mut value = 0;
1403        let mut err_msg: *mut c_char = ptr::null_mut();
1404        let status = unsafe { query(self.token, x, y, &mut value, &mut err_msg) };
1405        if status == ffi::status::OK {
1406            Ok(value != 0)
1407        } else {
1408            Err(unsafe { error_from_status(status, err_msg) })
1409        }
1410    }
1411
1412    fn query_optional_token(
1413        &self,
1414        query: OptionalTokenQueryFn,
1415    ) -> Result<Option<*mut c_void>, VisionKitError> {
1416        optional_token_call(|out_token, out_error_message| unsafe {
1417            query(self.token, out_token, out_error_message)
1418        })
1419    }
1420
1421    fn query_subjects(&self, query: JsonQueryFn) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1422        let tokens: Vec<u64> = self.query_json(query, "live text interaction subjects")?;
1423        Ok(tokens
1424            .into_iter()
1425            .map(token_from_u64)
1426            .map(LiveTextSubject::from_token)
1427            .collect())
1428    }
1429}
1430
1431fn parse_json_call<T, F>(mut call: F, context: &str) -> Result<T, VisionKitError>
1432where
1433    T: DeserializeOwned,
1434    F: FnMut(*mut *mut c_char, *mut *mut c_char) -> i32,
1435{
1436    let mut json: *mut c_char = ptr::null_mut();
1437    let mut err_msg: *mut c_char = ptr::null_mut();
1438    let status = call(&mut json, &mut err_msg);
1439    if status == ffi::status::OK {
1440        unsafe { parse_json_ptr(json, context) }
1441    } else {
1442        Err(unsafe { error_from_status(status, err_msg) })
1443    }
1444}
1445
1446fn optional_token_call<F>(mut call: F) -> Result<Option<*mut c_void>, VisionKitError>
1447where
1448    F: FnMut(*mut *mut c_void, *mut *mut c_char) -> i32,
1449{
1450    let mut token: *mut c_void = ptr::null_mut();
1451    let mut err_msg: *mut c_char = ptr::null_mut();
1452    let status = call(&mut token, &mut err_msg);
1453    if status == ffi::status::OK {
1454        Ok((!token.is_null()).then_some(token))
1455    } else {
1456        Err(unsafe { error_from_status(status, err_msg) })
1457    }
1458}
1459
1460fn query_rect_call<F>(context: &str, mut call: F) -> Result<Rect, VisionKitError>
1461where
1462    F: FnMut(*mut f64, *mut f64, *mut f64, *mut f64, *mut *mut c_char) -> i32,
1463{
1464    let mut x = 0.0;
1465    let mut y = 0.0;
1466    let mut width = 0.0;
1467    let mut height = 0.0;
1468    let mut err_msg: *mut c_char = ptr::null_mut();
1469    let status = call(&mut x, &mut y, &mut width, &mut height, &mut err_msg);
1470    if status == ffi::status::OK {
1471        Ok(Rect {
1472            x,
1473            y,
1474            width,
1475            height,
1476        })
1477    } else {
1478        let _ = context;
1479        Err(unsafe { error_from_status(status, err_msg) })
1480    }
1481}
1482
1483fn query_image_data_call<F>(mut call: F, context: &str) -> Result<LiveTextImageData, VisionKitError>
1484where
1485    F: FnMut(*mut *mut c_void, *mut u64, *mut f64, *mut f64, *mut *mut c_char) -> i32,
1486{
1487    let mut bytes: *mut c_void = ptr::null_mut();
1488    let mut len = 0;
1489    let mut width = 0.0;
1490    let mut height = 0.0;
1491    let mut err_msg: *mut c_char = ptr::null_mut();
1492    let status = call(&mut bytes, &mut len, &mut width, &mut height, &mut err_msg);
1493    if status == ffi::status::OK {
1494        Ok(LiveTextImageData {
1495            size: Size { width, height },
1496            png_data: unsafe {
1497                vec_from_buffer_ptr(bytes.cast::<u8>(), u64_to_usize(len, context)?, context)
1498            }?,
1499        })
1500    } else {
1501        Err(unsafe { error_from_status(status, err_msg) })
1502    }
1503}
1504
1505fn live_text_menu_tag_constants() -> Result<LiveTextMenuTagConstants, VisionKitError> {
1506    LIVE_TEXT_MENU_TAGS
1507        .get_or_init(|| {
1508            parse_json_call(
1509                |out_json, out_error_message| unsafe {
1510                    ffi::live_text_interaction::vk_live_text_menu_tags_json(
1511                        out_json,
1512                        out_error_message,
1513                    )
1514                },
1515                "live text menu tags",
1516            )
1517        })
1518        .clone()
1519}
1520
1521fn status_to_unit(status: i32, err_msg: *mut c_char) -> Result<(), VisionKitError> {
1522    if status == ffi::status::OK {
1523        Ok(())
1524    } else {
1525        Err(unsafe { error_from_status(status, err_msg) })
1526    }
1527}
1528
1529fn subject_tokens(subjects: &[LiveTextSubject]) -> Vec<u64> {
1530    subjects
1531        .iter()
1532        .map(|subject| token_to_u64(subject.raw_token()))
1533        .collect()
1534}
1535
1536fn token_to_u64(token: *mut c_void) -> u64 {
1537    token as usize as u64
1538}
1539
1540fn token_from_u64(token: u64) -> *mut c_void {
1541    usize::try_from(token).map_or(ptr::null_mut(), |value| value as *mut c_void)
1542}
1543
1544fn u64_to_usize(value: u64, context: &str) -> Result<usize, VisionKitError> {
1545    usize::try_from(value).map_err(|_| {
1546        VisionKitError::Unknown(format!(
1547            "{context} length exceeded this platform's address width"
1548        ))
1549    })
1550}