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