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    #[cfg(feature = "async")]
865    #[allow(dead_code, reason = "used by the optional async API surface")]
866    pub(crate) fn raw_token(&self) -> *mut c_void {
867        self.token
868    }
869
870    /// Creates the VisionKit live text interaction wrapper with the provided delegate.
871    pub fn with_delegate(delegate: &LiveTextInteractionDelegate) -> Result<Self, VisionKitError> {
872        let token = unsafe {
873            ffi::live_text_interaction::vk_live_text_interaction_new_with_delegate(
874                delegate.raw_token(),
875            )
876        };
877        if token.is_null() {
878            return Err(VisionKitError::UnavailableOnThisMacOS(
879                "LiveTextInteraction requires macOS 13+".to_owned(),
880            ));
881        }
882        Ok(Self { token })
883    }
884
885    /// Sets the VisionKit analysis value.
886    pub fn set_analysis(&self, analysis: &ImageAnalysis) -> Result<(), VisionKitError> {
887        let mut err_msg: *mut c_char = ptr::null_mut();
888        let status = unsafe {
889            ffi::live_text_interaction::vk_live_text_interaction_set_analysis(
890                self.token,
891                analysis.raw_token(),
892                &mut err_msg,
893            )
894        };
895        status_to_unit(status, err_msg)
896    }
897
898    /// Tracks the image at `path` for the VisionKit live text interaction.
899    pub fn track_image_at_path<P: AsRef<Path>>(&self, path: P) -> Result<(), VisionKitError> {
900        let path = path_to_cstring(path.as_ref())?;
901        let mut err_msg: *mut c_char = ptr::null_mut();
902        let status = unsafe {
903            ffi::live_text_interaction::vk_live_text_interaction_track_image_at_path(
904                self.token,
905                path.as_ptr(),
906                &mut err_msg,
907            )
908        };
909        status_to_unit(status, err_msg)
910    }
911
912    /// Returns the VisionKit delegate value.
913    pub fn delegate(&self) -> Result<Option<LiveTextInteractionDelegate>, VisionKitError> {
914        self.query_optional_token(ffi::live_text_interaction::vk_live_text_interaction_delegate)
915            .map(|token| token.map(LiveTextInteractionDelegate::from_token))
916    }
917
918    /// Sets the VisionKit delegate value.
919    pub fn set_delegate(
920        &self,
921        delegate: Option<&LiveTextInteractionDelegate>,
922    ) -> Result<(), VisionKitError> {
923        let mut err_msg: *mut c_char = ptr::null_mut();
924        let status = unsafe {
925            ffi::live_text_interaction::vk_live_text_interaction_set_delegate(
926                self.token,
927                delegate.map_or(ptr::null_mut(), LiveTextInteractionDelegate::raw_token),
928                &mut err_msg,
929            )
930        };
931        status_to_unit(status, err_msg)
932    }
933
934    /// Returns the VisionKit preferred interaction types value.
935    pub fn preferred_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
936        self.query_types(
937            ffi::live_text_interaction::vk_live_text_interaction_preferred_interaction_types,
938        )
939    }
940
941    /// Sets the VisionKit preferred interaction types value.
942    pub fn set_preferred_interaction_types(
943        &self,
944        interaction_types: LiveTextInteractionTypes,
945    ) -> Result<(), VisionKitError> {
946        let mut err_msg: *mut c_char = ptr::null_mut();
947        let status = unsafe {
948            ffi::live_text_interaction::vk_live_text_interaction_set_preferred_interaction_types(
949                self.token,
950                interaction_types.bits(),
951                &mut err_msg,
952            )
953        };
954        status_to_unit(status, err_msg)
955    }
956
957    /// Returns the VisionKit active interaction types value.
958    pub fn active_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
959        self.query_types(
960            ffi::live_text_interaction::vk_live_text_interaction_active_interaction_types,
961        )
962    }
963
964    /// Returns the VisionKit selectable items highlighted value.
965    pub fn selectable_items_highlighted(&self) -> Result<bool, VisionKitError> {
966        self.query_bool(
967            ffi::live_text_interaction::vk_live_text_interaction_selectable_items_highlighted,
968        )
969    }
970
971    /// Sets the VisionKit selectable items highlighted value.
972    pub fn set_selectable_items_highlighted(&self, value: bool) -> Result<(), VisionKitError> {
973        self.set_bool(
974            value,
975            ffi::live_text_interaction::vk_live_text_interaction_set_selectable_items_highlighted,
976        )
977    }
978
979    /// Returns the VisionKit tracking image view value.
980    pub fn tracking_image_view(&self) -> Result<Option<LiveTextTrackingImageView>, VisionKitError> {
981        self.query_optional_token(
982            ffi::live_text_interaction::vk_live_text_interaction_tracking_image_view,
983        )
984        .map(|token| token.map(LiveTextTrackingImageView::from_token))
985    }
986
987    /// Sets the VisionKit tracking image view value.
988    pub fn set_tracking_image_view(
989        &self,
990        view: Option<&LiveTextTrackingImageView>,
991    ) -> Result<(), VisionKitError> {
992        let mut err_msg: *mut c_char = ptr::null_mut();
993        let status = unsafe {
994            ffi::live_text_interaction::vk_live_text_interaction_set_tracking_image_view(
995                self.token,
996                view.map_or(ptr::null_mut(), LiveTextTrackingImageView::raw_token),
997                &mut err_msg,
998            )
999        };
1000        status_to_unit(status, err_msg)
1001    }
1002
1003    /// Returns whether VisionKit reports active text selection.
1004    pub fn has_active_text_selection(&self) -> Result<bool, VisionKitError> {
1005        self.query_bool(
1006            ffi::live_text_interaction::vk_live_text_interaction_has_active_text_selection,
1007        )
1008    }
1009
1010    /// Resets the VisionKit selection state.
1011    pub fn reset_selection(&self) -> Result<(), VisionKitError> {
1012        let mut err_msg: *mut c_char = ptr::null_mut();
1013        let status = unsafe {
1014            ffi::live_text_interaction::vk_live_text_interaction_reset_selection(
1015                self.token,
1016                &mut err_msg,
1017            )
1018        };
1019        status_to_unit(status, err_msg)
1020    }
1021
1022    /// Returns the VisionKit text value.
1023    pub fn text(&self) -> Result<String, VisionKitError> {
1024        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_text)
1025    }
1026
1027    /// Returns the VisionKit selected text value.
1028    pub fn selected_text(&self) -> Result<String, VisionKitError> {
1029        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_selected_text)
1030    }
1031
1032    /// Returns the VisionKit selected attributed text value.
1033    pub fn selected_attributed_text(&self) -> Result<LiveTextAttributedText, VisionKitError> {
1034        self.query_json(
1035            ffi::live_text_interaction::vk_live_text_interaction_selected_attributed_text_json,
1036            "live text interaction selected attributed text",
1037        )
1038    }
1039
1040    /// Returns the VisionKit selected ranges value.
1041    pub fn selected_ranges(&self) -> Result<Vec<LiveTextTextRange>, VisionKitError> {
1042        self.query_json(
1043            ffi::live_text_interaction::vk_live_text_interaction_selected_ranges_json,
1044            "live text interaction selected ranges",
1045        )
1046    }
1047
1048    /// Sets the VisionKit selected ranges value.
1049    pub fn set_selected_ranges(&self, ranges: &[LiveTextTextRange]) -> Result<(), VisionKitError> {
1050        self.set_json(
1051            ranges,
1052            ffi::live_text_interaction::vk_live_text_interaction_set_selected_ranges_json,
1053        )
1054    }
1055
1056    /// Returns the VisionKit contents rect value.
1057    pub fn contents_rect(&self) -> Result<Rect, VisionKitError> {
1058        self.query_rect(ffi::live_text_interaction::vk_live_text_interaction_contents_rect)
1059    }
1060
1061    /// Marks the VisionKit contents rectangle as needing an update.
1062    pub fn set_contents_rect_needs_update(&self) -> Result<(), VisionKitError> {
1063        let mut err_msg: *mut c_char = ptr::null_mut();
1064        let status = unsafe {
1065            ffi::live_text_interaction::vk_live_text_interaction_set_contents_rect_needs_update(
1066                self.token,
1067                &mut err_msg,
1068            )
1069        };
1070        status_to_unit(status, err_msg)
1071    }
1072
1073    /// Returns whether VisionKit reports interactive item at point.
1074    pub fn has_interactive_item_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1075        self.query_point_bool(
1076            x,
1077            y,
1078            ffi::live_text_interaction::vk_live_text_interaction_has_interactive_item_at_point,
1079        )
1080    }
1081
1082    /// Returns whether VisionKit reports text at point.
1083    pub fn has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1084        self.query_point_bool(
1085            x,
1086            y,
1087            ffi::live_text_interaction::vk_live_text_interaction_has_text_at_point,
1088        )
1089    }
1090
1091    /// Returns whether VisionKit reports data detector at point.
1092    pub fn has_data_detector_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1093        self.query_point_bool(
1094            x,
1095            y,
1096            ffi::live_text_interaction::vk_live_text_interaction_has_data_detector_at_point,
1097        )
1098    }
1099
1100    /// Returns whether VisionKit reports supplementary interface at point.
1101    pub fn has_supplementary_interface_at_point(
1102        &self,
1103        x: f64,
1104        y: f64,
1105    ) -> Result<bool, VisionKitError> {
1106        self.query_point_bool(
1107            x,
1108            y,
1109            ffi::live_text_interaction::vk_live_text_interaction_has_supplementary_interface_at_point,
1110        )
1111    }
1112
1113    /// Returns the VisionKit analysis has text at point value.
1114    pub fn analysis_has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
1115        self.query_point_bool(
1116            x,
1117            y,
1118            ffi::live_text_interaction::vk_live_text_interaction_analysis_has_text_at_point,
1119        )
1120    }
1121
1122    /// Returns the VisionKit live text button visible value.
1123    pub fn live_text_button_visible(&self) -> Result<bool, VisionKitError> {
1124        self.query_bool(
1125            ffi::live_text_interaction::vk_live_text_interaction_live_text_button_visible,
1126        )
1127    }
1128
1129    /// Returns whether VisionKit reports supplementary interface hidden.
1130    pub fn is_supplementary_interface_hidden(&self) -> Result<bool, VisionKitError> {
1131        self.query_bool(
1132            ffi::live_text_interaction::vk_live_text_interaction_is_supplementary_interface_hidden,
1133        )
1134    }
1135
1136    /// Sets the VisionKit supplementary interface hidden value.
1137    pub fn set_supplementary_interface_hidden(
1138        &self,
1139        hidden: bool,
1140        animated: bool,
1141    ) -> Result<(), VisionKitError> {
1142        let mut err_msg: *mut c_char = ptr::null_mut();
1143        let status = unsafe {
1144            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_hidden(
1145                self.token,
1146                i32::from(hidden),
1147                i32::from(animated),
1148                &mut err_msg,
1149            )
1150        };
1151        status_to_unit(status, err_msg)
1152    }
1153
1154    /// Returns the VisionKit supplementary interface content insets value.
1155    pub fn supplementary_interface_content_insets(&self) -> Result<EdgeInsets, VisionKitError> {
1156        let rect = self.query_rect(
1157            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_content_insets,
1158        )?;
1159        Ok(EdgeInsets {
1160            top: rect.x,
1161            left: rect.y,
1162            bottom: rect.width,
1163            right: rect.height,
1164        })
1165    }
1166
1167    /// Sets the VisionKit supplementary interface content insets value.
1168    pub fn set_supplementary_interface_content_insets(
1169        &self,
1170        insets: EdgeInsets,
1171    ) -> Result<(), VisionKitError> {
1172        let mut err_msg: *mut c_char = ptr::null_mut();
1173        let status = unsafe {
1174            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_content_insets(
1175                self.token,
1176                insets.top,
1177                insets.left,
1178                insets.bottom,
1179                insets.right,
1180                &mut err_msg,
1181            )
1182        };
1183        status_to_unit(status, err_msg)
1184    }
1185
1186    /// Returns the VisionKit supplementary interface font value.
1187    pub fn supplementary_interface_font(&self) -> Result<Option<LiveTextFont>, VisionKitError> {
1188        self.query_json(
1189            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_font_json,
1190            "live text interaction supplementary interface font",
1191        )
1192    }
1193
1194    /// Sets the VisionKit supplementary interface font value.
1195    pub fn set_supplementary_interface_font(
1196        &self,
1197        font: Option<&LiveTextFont>,
1198    ) -> Result<(), VisionKitError> {
1199        let font_json = json_cstring(&font)?;
1200        let mut err_msg: *mut c_char = ptr::null_mut();
1201        let status = unsafe {
1202            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_font_json(
1203                self.token,
1204                font_json.as_ptr(),
1205                &mut err_msg,
1206            )
1207        };
1208        status_to_unit(status, err_msg)
1209    }
1210
1211    /// Begins VisionKit subject analysis when the overlay requires it.
1212    pub fn begin_subject_analysis_if_necessary(&self) -> Result<(), VisionKitError> {
1213        let mut err_msg: *mut c_char = ptr::null_mut();
1214        let status = unsafe {
1215            ffi::live_text_interaction::vk_live_text_interaction_begin_subject_analysis_if_necessary(
1216                self.token,
1217                &mut err_msg,
1218            )
1219        };
1220        status_to_unit(status, err_msg)
1221    }
1222
1223    /// Returns the VisionKit subjects value.
1224    pub fn subjects(&self) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1225        self.query_subjects(ffi::live_text_interaction::vk_live_text_interaction_subjects_json)
1226    }
1227
1228    /// Returns the VisionKit highlighted subjects value.
1229    pub fn highlighted_subjects(&self) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1230        self.query_subjects(
1231            ffi::live_text_interaction::vk_live_text_interaction_highlighted_subjects_json,
1232        )
1233    }
1234
1235    /// Sets the VisionKit highlighted subjects value.
1236    pub fn set_highlighted_subjects(
1237        &self,
1238        subjects: &[LiveTextSubject],
1239    ) -> Result<(), VisionKitError> {
1240        let subjects_json = json_cstring(&subject_tokens(subjects))?;
1241        let mut err_msg: *mut c_char = ptr::null_mut();
1242        let status = unsafe {
1243            ffi::live_text_interaction::vk_live_text_interaction_set_highlighted_subjects_json(
1244                self.token,
1245                subjects_json.as_ptr(),
1246                &mut err_msg,
1247            )
1248        };
1249        status_to_unit(status, err_msg)
1250    }
1251
1252    /// Returns the VisionKit subject at point value.
1253    pub fn subject_at_point(
1254        &self,
1255        x: f64,
1256        y: f64,
1257    ) -> Result<Option<LiveTextSubject>, VisionKitError> {
1258        let mut subject_json: *mut c_char = ptr::null_mut();
1259        let mut err_msg: *mut c_char = ptr::null_mut();
1260        let status = unsafe {
1261            ffi::live_text_interaction::vk_live_text_interaction_subject_at_json(
1262                self.token,
1263                x,
1264                y,
1265                &mut subject_json,
1266                &mut err_msg,
1267            )
1268        };
1269        if status == ffi::status::OK {
1270            let token: Option<u64> =
1271                unsafe { parse_json_ptr(subject_json, "live text interaction subject lookup") }?;
1272            Ok(token.map(token_from_u64).map(LiveTextSubject::from_token))
1273        } else {
1274            Err(unsafe { error_from_status(status, err_msg) })
1275        }
1276    }
1277
1278    /// Returns the VisionKit image extracted for the provided subjects.
1279    pub fn image_for_subjects(
1280        &self,
1281        subjects: &[LiveTextSubject],
1282    ) -> Result<LiveTextImageData, VisionKitError> {
1283        let subjects_json = json_cstring(&subject_tokens(subjects))?;
1284        let mut bytes: *mut c_void = ptr::null_mut();
1285        let mut len = 0;
1286        let mut width = 0.0;
1287        let mut height = 0.0;
1288        let mut err_msg: *mut c_char = ptr::null_mut();
1289        let status = unsafe {
1290            ffi::live_text_interaction::vk_live_text_interaction_image_for_subjects_png_data(
1291                self.token,
1292                subjects_json.as_ptr(),
1293                &mut bytes,
1294                &mut len,
1295                &mut width,
1296                &mut height,
1297                &mut err_msg,
1298            )
1299        };
1300        if status == ffi::status::OK {
1301            Ok(LiveTextImageData {
1302                size: Size { width, height },
1303                png_data: unsafe {
1304                    vec_from_buffer_ptr(
1305                        bytes.cast::<u8>(),
1306                        u64_to_usize(len, "live text interaction subject image")?,
1307                        "live text interaction subject image",
1308                    )
1309                }?,
1310            })
1311        } else {
1312            Err(unsafe { error_from_status(status, err_msg) })
1313        }
1314    }
1315
1316    fn query_bool(&self, query: BoolQueryFn) -> Result<bool, VisionKitError> {
1317        let mut value = 0;
1318        let mut err_msg: *mut c_char = ptr::null_mut();
1319        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1320        if status == ffi::status::OK {
1321            Ok(value != 0)
1322        } else {
1323            Err(unsafe { error_from_status(status, err_msg) })
1324        }
1325    }
1326
1327    fn set_bool(&self, value: bool, setter: BoolSetterFn) -> Result<(), VisionKitError> {
1328        let mut err_msg: *mut c_char = ptr::null_mut();
1329        let status = unsafe { setter(self.token, i32::from(value), &mut err_msg) };
1330        status_to_unit(status, err_msg)
1331    }
1332
1333    fn query_types(&self, query: TypesQueryFn) -> Result<LiveTextInteractionTypes, VisionKitError> {
1334        let mut raw = 0;
1335        let mut err_msg: *mut c_char = ptr::null_mut();
1336        let status = unsafe { query(self.token, &mut raw, &mut err_msg) };
1337        if status == ffi::status::OK {
1338            Ok(LiveTextInteractionTypes::new(raw))
1339        } else {
1340            Err(unsafe { error_from_status(status, err_msg) })
1341        }
1342    }
1343
1344    fn query_string(
1345        &self,
1346        query: unsafe extern "C" fn(*mut c_void, *mut *mut c_char, *mut *mut c_char) -> i32,
1347    ) -> Result<String, VisionKitError> {
1348        let mut value: *mut c_char = ptr::null_mut();
1349        let mut err_msg: *mut c_char = ptr::null_mut();
1350        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1351        if status == ffi::status::OK {
1352            unsafe { string_from_ptr(value, "live text interaction string") }
1353        } else {
1354            Err(unsafe { error_from_status(status, err_msg) })
1355        }
1356    }
1357
1358    fn query_json<T>(&self, query: JsonQueryFn, context: &str) -> Result<T, VisionKitError>
1359    where
1360        T: DeserializeOwned,
1361    {
1362        let mut value: *mut c_char = ptr::null_mut();
1363        let mut err_msg: *mut c_char = ptr::null_mut();
1364        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1365        if status == ffi::status::OK {
1366            unsafe { parse_json_ptr(value, context) }
1367        } else {
1368            Err(unsafe { error_from_status(status, err_msg) })
1369        }
1370    }
1371
1372    fn set_json<T>(&self, value: &T, setter: JsonSetterFn) -> Result<(), VisionKitError>
1373    where
1374        T: Serialize + ?Sized,
1375    {
1376        let json = json_cstring(value)?;
1377        let mut err_msg: *mut c_char = ptr::null_mut();
1378        let status = unsafe { setter(self.token, json.as_ptr(), &mut err_msg) };
1379        status_to_unit(status, err_msg)
1380    }
1381
1382    fn query_rect(&self, query: RectQueryFn) -> Result<Rect, VisionKitError> {
1383        query_rect_call(
1384            "live text interaction rect",
1385            |out_x, out_y, out_width, out_height, out_error_message| unsafe {
1386                query(
1387                    self.token,
1388                    out_x,
1389                    out_y,
1390                    out_width,
1391                    out_height,
1392                    out_error_message,
1393                )
1394            },
1395        )
1396    }
1397
1398    fn query_point_bool(
1399        &self,
1400        x: f64,
1401        y: f64,
1402        query: PointBoolQueryFn,
1403    ) -> Result<bool, VisionKitError> {
1404        let mut value = 0;
1405        let mut err_msg: *mut c_char = ptr::null_mut();
1406        let status = unsafe { query(self.token, x, y, &mut value, &mut err_msg) };
1407        if status == ffi::status::OK {
1408            Ok(value != 0)
1409        } else {
1410            Err(unsafe { error_from_status(status, err_msg) })
1411        }
1412    }
1413
1414    fn query_optional_token(
1415        &self,
1416        query: OptionalTokenQueryFn,
1417    ) -> Result<Option<*mut c_void>, VisionKitError> {
1418        optional_token_call(|out_token, out_error_message| unsafe {
1419            query(self.token, out_token, out_error_message)
1420        })
1421    }
1422
1423    fn query_subjects(&self, query: JsonQueryFn) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1424        let tokens: Vec<u64> = self.query_json(query, "live text interaction subjects")?;
1425        Ok(tokens
1426            .into_iter()
1427            .map(token_from_u64)
1428            .map(LiveTextSubject::from_token)
1429            .collect())
1430    }
1431}
1432
1433fn parse_json_call<T, F>(mut call: F, context: &str) -> Result<T, VisionKitError>
1434where
1435    T: DeserializeOwned,
1436    F: FnMut(*mut *mut c_char, *mut *mut c_char) -> i32,
1437{
1438    let mut json: *mut c_char = ptr::null_mut();
1439    let mut err_msg: *mut c_char = ptr::null_mut();
1440    let status = call(&mut json, &mut err_msg);
1441    if status == ffi::status::OK {
1442        unsafe { parse_json_ptr(json, context) }
1443    } else {
1444        Err(unsafe { error_from_status(status, err_msg) })
1445    }
1446}
1447
1448fn optional_token_call<F>(mut call: F) -> Result<Option<*mut c_void>, VisionKitError>
1449where
1450    F: FnMut(*mut *mut c_void, *mut *mut c_char) -> i32,
1451{
1452    let mut token: *mut c_void = ptr::null_mut();
1453    let mut err_msg: *mut c_char = ptr::null_mut();
1454    let status = call(&mut token, &mut err_msg);
1455    if status == ffi::status::OK {
1456        Ok((!token.is_null()).then_some(token))
1457    } else {
1458        Err(unsafe { error_from_status(status, err_msg) })
1459    }
1460}
1461
1462fn query_rect_call<F>(context: &str, mut call: F) -> Result<Rect, VisionKitError>
1463where
1464    F: FnMut(*mut f64, *mut f64, *mut f64, *mut f64, *mut *mut c_char) -> i32,
1465{
1466    let mut x = 0.0;
1467    let mut y = 0.0;
1468    let mut width = 0.0;
1469    let mut height = 0.0;
1470    let mut err_msg: *mut c_char = ptr::null_mut();
1471    let status = call(&mut x, &mut y, &mut width, &mut height, &mut err_msg);
1472    if status == ffi::status::OK {
1473        Ok(Rect {
1474            x,
1475            y,
1476            width,
1477            height,
1478        })
1479    } else {
1480        let _ = context;
1481        Err(unsafe { error_from_status(status, err_msg) })
1482    }
1483}
1484
1485fn query_image_data_call<F>(mut call: F, context: &str) -> Result<LiveTextImageData, VisionKitError>
1486where
1487    F: FnMut(*mut *mut c_void, *mut u64, *mut f64, *mut f64, *mut *mut c_char) -> i32,
1488{
1489    let mut bytes: *mut c_void = ptr::null_mut();
1490    let mut len = 0;
1491    let mut width = 0.0;
1492    let mut height = 0.0;
1493    let mut err_msg: *mut c_char = ptr::null_mut();
1494    let status = call(&mut bytes, &mut len, &mut width, &mut height, &mut err_msg);
1495    if status == ffi::status::OK {
1496        Ok(LiveTextImageData {
1497            size: Size { width, height },
1498            png_data: unsafe {
1499                vec_from_buffer_ptr(bytes.cast::<u8>(), u64_to_usize(len, context)?, context)
1500            }?,
1501        })
1502    } else {
1503        Err(unsafe { error_from_status(status, err_msg) })
1504    }
1505}
1506
1507fn live_text_menu_tag_constants() -> Result<LiveTextMenuTagConstants, VisionKitError> {
1508    LIVE_TEXT_MENU_TAGS
1509        .get_or_init(|| {
1510            parse_json_call(
1511                |out_json, out_error_message| unsafe {
1512                    ffi::live_text_interaction::vk_live_text_menu_tags_json(
1513                        out_json,
1514                        out_error_message,
1515                    )
1516                },
1517                "live text menu tags",
1518            )
1519        })
1520        .clone()
1521}
1522
1523fn status_to_unit(status: i32, err_msg: *mut c_char) -> Result<(), VisionKitError> {
1524    if status == ffi::status::OK {
1525        Ok(())
1526    } else {
1527        Err(unsafe { error_from_status(status, err_msg) })
1528    }
1529}
1530
1531fn subject_tokens(subjects: &[LiveTextSubject]) -> Vec<u64> {
1532    subjects
1533        .iter()
1534        .map(|subject| token_to_u64(subject.raw_token()))
1535        .collect()
1536}
1537
1538fn token_to_u64(token: *mut c_void) -> u64 {
1539    token as usize as u64
1540}
1541
1542fn token_from_u64(token: u64) -> *mut c_void {
1543    usize::try_from(token).map_or(ptr::null_mut(), |value| value as *mut c_void)
1544}
1545
1546fn u64_to_usize(value: u64, context: &str) -> Result<usize, VisionKitError> {
1547    usize::try_from(value).map_err(|_| {
1548        VisionKitError::Unknown(format!(
1549            "{context} length exceeded this platform's address width"
1550        ))
1551    })
1552}