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