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