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