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