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 fn with_delegate(delegate: &LiveTextInteractionDelegate) -> Result<Self, VisionKitError> {
735        let token = unsafe {
736            ffi::live_text_interaction::vk_live_text_interaction_new_with_delegate(delegate.raw_token())
737        };
738        if token.is_null() {
739            return Err(VisionKitError::UnavailableOnThisMacOS(
740                "LiveTextInteraction requires macOS 13+".to_owned(),
741            ));
742        }
743        Ok(Self { token })
744    }
745
746    pub fn set_analysis(&self, analysis: &ImageAnalysis) -> Result<(), VisionKitError> {
747        let mut err_msg: *mut c_char = ptr::null_mut();
748        let status = unsafe {
749            ffi::live_text_interaction::vk_live_text_interaction_set_analysis(
750                self.token,
751                analysis.raw_token(),
752                &mut err_msg,
753            )
754        };
755        status_to_unit(status, err_msg)
756    }
757
758    pub fn track_image_at_path<P: AsRef<Path>>(&self, path: P) -> Result<(), VisionKitError> {
759        let path = path_to_cstring(path.as_ref())?;
760        let mut err_msg: *mut c_char = ptr::null_mut();
761        let status = unsafe {
762            ffi::live_text_interaction::vk_live_text_interaction_track_image_at_path(
763                self.token,
764                path.as_ptr(),
765                &mut err_msg,
766            )
767        };
768        status_to_unit(status, err_msg)
769    }
770
771    pub fn delegate(&self) -> Result<Option<LiveTextInteractionDelegate>, VisionKitError> {
772        self.query_optional_token(ffi::live_text_interaction::vk_live_text_interaction_delegate)
773            .map(|token| token.map(LiveTextInteractionDelegate::from_token))
774    }
775
776    pub fn set_delegate(
777        &self,
778        delegate: Option<&LiveTextInteractionDelegate>,
779    ) -> Result<(), VisionKitError> {
780        let mut err_msg: *mut c_char = ptr::null_mut();
781        let status = unsafe {
782            ffi::live_text_interaction::vk_live_text_interaction_set_delegate(
783                self.token,
784                delegate.map_or(ptr::null_mut(), LiveTextInteractionDelegate::raw_token),
785                &mut err_msg,
786            )
787        };
788        status_to_unit(status, err_msg)
789    }
790
791    pub fn preferred_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
792        self.query_types(
793            ffi::live_text_interaction::vk_live_text_interaction_preferred_interaction_types,
794        )
795    }
796
797    pub fn set_preferred_interaction_types(
798        &self,
799        interaction_types: LiveTextInteractionTypes,
800    ) -> Result<(), VisionKitError> {
801        let mut err_msg: *mut c_char = ptr::null_mut();
802        let status = unsafe {
803            ffi::live_text_interaction::vk_live_text_interaction_set_preferred_interaction_types(
804                self.token,
805                interaction_types.bits(),
806                &mut err_msg,
807            )
808        };
809        status_to_unit(status, err_msg)
810    }
811
812    pub fn active_interaction_types(&self) -> Result<LiveTextInteractionTypes, VisionKitError> {
813        self.query_types(
814            ffi::live_text_interaction::vk_live_text_interaction_active_interaction_types,
815        )
816    }
817
818    pub fn selectable_items_highlighted(&self) -> Result<bool, VisionKitError> {
819        self.query_bool(
820            ffi::live_text_interaction::vk_live_text_interaction_selectable_items_highlighted,
821        )
822    }
823
824    pub fn set_selectable_items_highlighted(&self, value: bool) -> Result<(), VisionKitError> {
825        self.set_bool(
826            value,
827            ffi::live_text_interaction::vk_live_text_interaction_set_selectable_items_highlighted,
828        )
829    }
830
831    pub fn tracking_image_view(&self) -> Result<Option<LiveTextTrackingImageView>, VisionKitError> {
832        self.query_optional_token(
833            ffi::live_text_interaction::vk_live_text_interaction_tracking_image_view,
834        )
835        .map(|token| token.map(LiveTextTrackingImageView::from_token))
836    }
837
838    pub fn set_tracking_image_view(
839        &self,
840        view: Option<&LiveTextTrackingImageView>,
841    ) -> Result<(), VisionKitError> {
842        let mut err_msg: *mut c_char = ptr::null_mut();
843        let status = unsafe {
844            ffi::live_text_interaction::vk_live_text_interaction_set_tracking_image_view(
845                self.token,
846                view.map_or(ptr::null_mut(), LiveTextTrackingImageView::raw_token),
847                &mut err_msg,
848            )
849        };
850        status_to_unit(status, err_msg)
851    }
852
853    pub fn has_active_text_selection(&self) -> Result<bool, VisionKitError> {
854        self.query_bool(
855            ffi::live_text_interaction::vk_live_text_interaction_has_active_text_selection,
856        )
857    }
858
859    pub fn reset_selection(&self) -> Result<(), VisionKitError> {
860        let mut err_msg: *mut c_char = ptr::null_mut();
861        let status = unsafe {
862            ffi::live_text_interaction::vk_live_text_interaction_reset_selection(
863                self.token,
864                &mut err_msg,
865            )
866        };
867        status_to_unit(status, err_msg)
868    }
869
870    pub fn text(&self) -> Result<String, VisionKitError> {
871        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_text)
872    }
873
874    pub fn selected_text(&self) -> Result<String, VisionKitError> {
875        self.query_string(ffi::live_text_interaction::vk_live_text_interaction_selected_text)
876    }
877
878    pub fn selected_attributed_text(&self) -> Result<LiveTextAttributedText, VisionKitError> {
879        self.query_json(
880            ffi::live_text_interaction::vk_live_text_interaction_selected_attributed_text_json,
881            "live text interaction selected attributed text",
882        )
883    }
884
885    pub fn selected_ranges(&self) -> Result<Vec<LiveTextTextRange>, VisionKitError> {
886        self.query_json(
887            ffi::live_text_interaction::vk_live_text_interaction_selected_ranges_json,
888            "live text interaction selected ranges",
889        )
890    }
891
892    pub fn set_selected_ranges(&self, ranges: &[LiveTextTextRange]) -> Result<(), VisionKitError> {
893        self.set_json(
894            ranges,
895            ffi::live_text_interaction::vk_live_text_interaction_set_selected_ranges_json,
896        )
897    }
898
899    pub fn contents_rect(&self) -> Result<Rect, VisionKitError> {
900        self.query_rect(ffi::live_text_interaction::vk_live_text_interaction_contents_rect)
901    }
902
903    pub fn set_contents_rect_needs_update(&self) -> Result<(), VisionKitError> {
904        let mut err_msg: *mut c_char = ptr::null_mut();
905        let status = unsafe {
906            ffi::live_text_interaction::vk_live_text_interaction_set_contents_rect_needs_update(
907                self.token,
908                &mut err_msg,
909            )
910        };
911        status_to_unit(status, err_msg)
912    }
913
914    pub fn has_interactive_item_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
915        self.query_point_bool(
916            x,
917            y,
918            ffi::live_text_interaction::vk_live_text_interaction_has_interactive_item_at_point,
919        )
920    }
921
922    pub fn has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
923        self.query_point_bool(
924            x,
925            y,
926            ffi::live_text_interaction::vk_live_text_interaction_has_text_at_point,
927        )
928    }
929
930    pub fn has_data_detector_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
931        self.query_point_bool(
932            x,
933            y,
934            ffi::live_text_interaction::vk_live_text_interaction_has_data_detector_at_point,
935        )
936    }
937
938    pub fn has_supplementary_interface_at_point(
939        &self,
940        x: f64,
941        y: f64,
942    ) -> Result<bool, VisionKitError> {
943        self.query_point_bool(
944            x,
945            y,
946            ffi::live_text_interaction::vk_live_text_interaction_has_supplementary_interface_at_point,
947        )
948    }
949
950    pub fn analysis_has_text_at_point(&self, x: f64, y: f64) -> Result<bool, VisionKitError> {
951        self.query_point_bool(
952            x,
953            y,
954            ffi::live_text_interaction::vk_live_text_interaction_analysis_has_text_at_point,
955        )
956    }
957
958    pub fn live_text_button_visible(&self) -> Result<bool, VisionKitError> {
959        self.query_bool(ffi::live_text_interaction::vk_live_text_interaction_live_text_button_visible)
960    }
961
962    pub fn is_supplementary_interface_hidden(&self) -> Result<bool, VisionKitError> {
963        self.query_bool(
964            ffi::live_text_interaction::vk_live_text_interaction_is_supplementary_interface_hidden,
965        )
966    }
967
968    pub fn set_supplementary_interface_hidden(
969        &self,
970        hidden: bool,
971        animated: bool,
972    ) -> Result<(), VisionKitError> {
973        let mut err_msg: *mut c_char = ptr::null_mut();
974        let status = unsafe {
975            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_hidden(
976                self.token,
977                i32::from(hidden),
978                i32::from(animated),
979                &mut err_msg,
980            )
981        };
982        status_to_unit(status, err_msg)
983    }
984
985    pub fn supplementary_interface_content_insets(&self) -> Result<EdgeInsets, VisionKitError> {
986        let rect = self.query_rect(
987            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_content_insets,
988        )?;
989        Ok(EdgeInsets {
990            top: rect.x,
991            left: rect.y,
992            bottom: rect.width,
993            right: rect.height,
994        })
995    }
996
997    pub fn set_supplementary_interface_content_insets(
998        &self,
999        insets: EdgeInsets,
1000    ) -> Result<(), VisionKitError> {
1001        let mut err_msg: *mut c_char = ptr::null_mut();
1002        let status = unsafe {
1003            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_content_insets(
1004                self.token,
1005                insets.top,
1006                insets.left,
1007                insets.bottom,
1008                insets.right,
1009                &mut err_msg,
1010            )
1011        };
1012        status_to_unit(status, err_msg)
1013    }
1014
1015    pub fn supplementary_interface_font(&self) -> Result<Option<LiveTextFont>, VisionKitError> {
1016        self.query_json(
1017            ffi::live_text_interaction::vk_live_text_interaction_supplementary_interface_font_json,
1018            "live text interaction supplementary interface font",
1019        )
1020    }
1021
1022    pub fn set_supplementary_interface_font(
1023        &self,
1024        font: Option<&LiveTextFont>,
1025    ) -> Result<(), VisionKitError> {
1026        let font_json = json_cstring(&font)?;
1027        let mut err_msg: *mut c_char = ptr::null_mut();
1028        let status = unsafe {
1029            ffi::live_text_interaction::vk_live_text_interaction_set_supplementary_interface_font_json(
1030                self.token,
1031                font_json.as_ptr(),
1032                &mut err_msg,
1033            )
1034        };
1035        status_to_unit(status, err_msg)
1036    }
1037
1038    pub fn begin_subject_analysis_if_necessary(&self) -> Result<(), VisionKitError> {
1039        let mut err_msg: *mut c_char = ptr::null_mut();
1040        let status = unsafe {
1041            ffi::live_text_interaction::vk_live_text_interaction_begin_subject_analysis_if_necessary(
1042                self.token,
1043                &mut err_msg,
1044            )
1045        };
1046        status_to_unit(status, err_msg)
1047    }
1048
1049    pub fn subjects(&self) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1050        self.query_subjects(ffi::live_text_interaction::vk_live_text_interaction_subjects_json)
1051    }
1052
1053    pub fn highlighted_subjects(&self) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1054        self.query_subjects(
1055            ffi::live_text_interaction::vk_live_text_interaction_highlighted_subjects_json,
1056        )
1057    }
1058
1059    pub fn set_highlighted_subjects(
1060        &self,
1061        subjects: &[LiveTextSubject],
1062    ) -> Result<(), VisionKitError> {
1063        let subjects_json = json_cstring(&subject_tokens(subjects))?;
1064        let mut err_msg: *mut c_char = ptr::null_mut();
1065        let status = unsafe {
1066            ffi::live_text_interaction::vk_live_text_interaction_set_highlighted_subjects_json(
1067                self.token,
1068                subjects_json.as_ptr(),
1069                &mut err_msg,
1070            )
1071        };
1072        status_to_unit(status, err_msg)
1073    }
1074
1075    pub fn subject_at_point(&self, x: f64, y: f64) -> Result<Option<LiveTextSubject>, VisionKitError> {
1076        let mut subject_json: *mut c_char = ptr::null_mut();
1077        let mut err_msg: *mut c_char = ptr::null_mut();
1078        let status = unsafe {
1079            ffi::live_text_interaction::vk_live_text_interaction_subject_at_json(
1080                self.token,
1081                x,
1082                y,
1083                &mut subject_json,
1084                &mut err_msg,
1085            )
1086        };
1087        if status == ffi::status::OK {
1088            let token: Option<u64> = unsafe {
1089                parse_json_ptr(subject_json, "live text interaction subject lookup")
1090            }?;
1091            Ok(token.map(token_from_u64).map(LiveTextSubject::from_token))
1092        } else {
1093            Err(unsafe { error_from_status(status, err_msg) })
1094        }
1095    }
1096
1097    pub fn image_for_subjects(
1098        &self,
1099        subjects: &[LiveTextSubject],
1100    ) -> Result<LiveTextImageData, VisionKitError> {
1101        let subjects_json = json_cstring(&subject_tokens(subjects))?;
1102        let mut bytes: *mut c_void = ptr::null_mut();
1103        let mut len = 0;
1104        let mut width = 0.0;
1105        let mut height = 0.0;
1106        let mut err_msg: *mut c_char = ptr::null_mut();
1107        let status = unsafe {
1108            ffi::live_text_interaction::vk_live_text_interaction_image_for_subjects_png_data(
1109                self.token,
1110                subjects_json.as_ptr(),
1111                &mut bytes,
1112                &mut len,
1113                &mut width,
1114                &mut height,
1115                &mut err_msg,
1116            )
1117        };
1118        if status == ffi::status::OK {
1119            Ok(LiveTextImageData {
1120                size: Size { width, height },
1121                png_data: unsafe {
1122                    vec_from_buffer_ptr(
1123                        bytes.cast::<u8>(),
1124                        u64_to_usize(len, "live text interaction subject image")?,
1125                        "live text interaction subject image",
1126                    )
1127                }?,
1128            })
1129        } else {
1130            Err(unsafe { error_from_status(status, err_msg) })
1131        }
1132    }
1133
1134    fn query_bool(&self, query: BoolQueryFn) -> Result<bool, VisionKitError> {
1135        let mut value = 0;
1136        let mut err_msg: *mut c_char = ptr::null_mut();
1137        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1138        if status == ffi::status::OK {
1139            Ok(value != 0)
1140        } else {
1141            Err(unsafe { error_from_status(status, err_msg) })
1142        }
1143    }
1144
1145    fn set_bool(&self, value: bool, setter: BoolSetterFn) -> Result<(), VisionKitError> {
1146        let mut err_msg: *mut c_char = ptr::null_mut();
1147        let status = unsafe { setter(self.token, i32::from(value), &mut err_msg) };
1148        status_to_unit(status, err_msg)
1149    }
1150
1151    fn query_types(&self, query: TypesQueryFn) -> Result<LiveTextInteractionTypes, VisionKitError> {
1152        let mut raw = 0;
1153        let mut err_msg: *mut c_char = ptr::null_mut();
1154        let status = unsafe { query(self.token, &mut raw, &mut err_msg) };
1155        if status == ffi::status::OK {
1156            Ok(LiveTextInteractionTypes::new(raw))
1157        } else {
1158            Err(unsafe { error_from_status(status, err_msg) })
1159        }
1160    }
1161
1162    fn query_string(
1163        &self,
1164        query: unsafe extern "C" fn(*mut c_void, *mut *mut c_char, *mut *mut c_char) -> i32,
1165    ) -> Result<String, VisionKitError> {
1166        let mut value: *mut c_char = ptr::null_mut();
1167        let mut err_msg: *mut c_char = ptr::null_mut();
1168        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1169        if status == ffi::status::OK {
1170            unsafe { string_from_ptr(value, "live text interaction string") }
1171        } else {
1172            Err(unsafe { error_from_status(status, err_msg) })
1173        }
1174    }
1175
1176    fn query_json<T>(&self, query: JsonQueryFn, context: &str) -> Result<T, VisionKitError>
1177    where
1178        T: DeserializeOwned,
1179    {
1180        let mut value: *mut c_char = ptr::null_mut();
1181        let mut err_msg: *mut c_char = ptr::null_mut();
1182        let status = unsafe { query(self.token, &mut value, &mut err_msg) };
1183        if status == ffi::status::OK {
1184            unsafe { parse_json_ptr(value, context) }
1185        } else {
1186            Err(unsafe { error_from_status(status, err_msg) })
1187        }
1188    }
1189
1190    fn set_json<T>(&self, value: &T, setter: JsonSetterFn) -> Result<(), VisionKitError>
1191    where
1192        T: Serialize + ?Sized,
1193    {
1194        let json = json_cstring(value)?;
1195        let mut err_msg: *mut c_char = ptr::null_mut();
1196        let status = unsafe { setter(self.token, json.as_ptr(), &mut err_msg) };
1197        status_to_unit(status, err_msg)
1198    }
1199
1200    fn query_rect(&self, query: RectQueryFn) -> Result<Rect, VisionKitError> {
1201        query_rect_call("live text interaction rect", |out_x, out_y, out_width, out_height, out_error_message| unsafe {
1202            query(
1203                self.token,
1204                out_x,
1205                out_y,
1206                out_width,
1207                out_height,
1208                out_error_message,
1209            )
1210        })
1211    }
1212
1213    fn query_point_bool(
1214        &self,
1215        x: f64,
1216        y: f64,
1217        query: PointBoolQueryFn,
1218    ) -> Result<bool, VisionKitError> {
1219        let mut value = 0;
1220        let mut err_msg: *mut c_char = ptr::null_mut();
1221        let status = unsafe { query(self.token, x, y, &mut value, &mut err_msg) };
1222        if status == ffi::status::OK {
1223            Ok(value != 0)
1224        } else {
1225            Err(unsafe { error_from_status(status, err_msg) })
1226        }
1227    }
1228
1229    fn query_optional_token(
1230        &self,
1231        query: OptionalTokenQueryFn,
1232    ) -> Result<Option<*mut c_void>, VisionKitError> {
1233        optional_token_call(|out_token, out_error_message| unsafe {
1234            query(self.token, out_token, out_error_message)
1235        })
1236    }
1237
1238    fn query_subjects(&self, query: JsonQueryFn) -> Result<Vec<LiveTextSubject>, VisionKitError> {
1239        let tokens: Vec<u64> = self.query_json(query, "live text interaction subjects")?;
1240        Ok(tokens
1241            .into_iter()
1242            .map(token_from_u64)
1243            .map(LiveTextSubject::from_token)
1244            .collect())
1245    }
1246}
1247
1248fn parse_json_call<T, F>(mut call: F, context: &str) -> Result<T, VisionKitError>
1249where
1250    T: DeserializeOwned,
1251    F: FnMut(*mut *mut c_char, *mut *mut c_char) -> i32,
1252{
1253    let mut json: *mut c_char = ptr::null_mut();
1254    let mut err_msg: *mut c_char = ptr::null_mut();
1255    let status = call(&mut json, &mut err_msg);
1256    if status == ffi::status::OK {
1257        unsafe { parse_json_ptr(json, context) }
1258    } else {
1259        Err(unsafe { error_from_status(status, err_msg) })
1260    }
1261}
1262
1263fn optional_token_call<F>(mut call: F) -> Result<Option<*mut c_void>, VisionKitError>
1264where
1265    F: FnMut(*mut *mut c_void, *mut *mut c_char) -> i32,
1266{
1267    let mut token: *mut c_void = ptr::null_mut();
1268    let mut err_msg: *mut c_char = ptr::null_mut();
1269    let status = call(&mut token, &mut err_msg);
1270    if status == ffi::status::OK {
1271        Ok((!token.is_null()).then_some(token))
1272    } else {
1273        Err(unsafe { error_from_status(status, err_msg) })
1274    }
1275}
1276
1277fn query_rect_call<F>(context: &str, mut call: F) -> Result<Rect, VisionKitError>
1278where
1279    F: FnMut(*mut f64, *mut f64, *mut f64, *mut f64, *mut *mut c_char) -> i32,
1280{
1281    let mut x = 0.0;
1282    let mut y = 0.0;
1283    let mut width = 0.0;
1284    let mut height = 0.0;
1285    let mut err_msg: *mut c_char = ptr::null_mut();
1286    let status = call(&mut x, &mut y, &mut width, &mut height, &mut err_msg);
1287    if status == ffi::status::OK {
1288        Ok(Rect {
1289            x,
1290            y,
1291            width,
1292            height,
1293        })
1294    } else {
1295        let _ = context;
1296        Err(unsafe { error_from_status(status, err_msg) })
1297    }
1298}
1299
1300fn query_image_data_call<F>(mut call: F, context: &str) -> Result<LiveTextImageData, VisionKitError>
1301where
1302    F: FnMut(*mut *mut c_void, *mut u64, *mut f64, *mut f64, *mut *mut c_char) -> i32,
1303{
1304    let mut bytes: *mut c_void = ptr::null_mut();
1305    let mut len = 0;
1306    let mut width = 0.0;
1307    let mut height = 0.0;
1308    let mut err_msg: *mut c_char = ptr::null_mut();
1309    let status = call(&mut bytes, &mut len, &mut width, &mut height, &mut err_msg);
1310    if status == ffi::status::OK {
1311        Ok(LiveTextImageData {
1312            size: Size { width, height },
1313            png_data: unsafe {
1314                vec_from_buffer_ptr(bytes.cast::<u8>(), u64_to_usize(len, context)?, context)
1315            }?,
1316        })
1317    } else {
1318        Err(unsafe { error_from_status(status, err_msg) })
1319    }
1320}
1321
1322fn live_text_menu_tag_constants() -> Result<LiveTextMenuTagConstants, VisionKitError> {
1323    LIVE_TEXT_MENU_TAGS
1324        .get_or_init(|| {
1325            parse_json_call(
1326                |out_json, out_error_message| unsafe {
1327                    ffi::live_text_interaction::vk_live_text_menu_tags_json(
1328                        out_json,
1329                        out_error_message,
1330                    )
1331                },
1332                "live text menu tags",
1333            )
1334        })
1335        .clone()
1336}
1337
1338fn status_to_unit(status: i32, err_msg: *mut c_char) -> Result<(), VisionKitError> {
1339    if status == ffi::status::OK {
1340        Ok(())
1341    } else {
1342        Err(unsafe { error_from_status(status, err_msg) })
1343    }
1344}
1345
1346fn subject_tokens(subjects: &[LiveTextSubject]) -> Vec<u64> {
1347    subjects.iter().map(|subject| token_to_u64(subject.raw_token())).collect()
1348}
1349
1350fn token_to_u64(token: *mut c_void) -> u64 {
1351    token as usize as u64
1352}
1353
1354fn token_from_u64(token: u64) -> *mut c_void {
1355    usize::try_from(token).map_or(ptr::null_mut(), |value| value as *mut c_void)
1356}
1357
1358fn u64_to_usize(value: u64, context: &str) -> Result<usize, VisionKitError> {
1359    usize::try_from(value).map_err(|_| {
1360        VisionKitError::Unknown(format!(
1361            "{context} length exceeded this platform's address width"
1362        ))
1363    })
1364}