Skip to main content

05_live_text_interaction/
05_live_text_interaction.rs

1use std::hint::black_box;
2use std::path::PathBuf;
3
4use visionkit::prelude::*;
5
6fn asset_path() -> PathBuf {
7    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8        .join("examples")
9        .join("assets")
10        .join("live_text.png")
11}
12
13fn build_delegate() -> Result<LiveTextInteractionDelegate, Box<dyn std::error::Error>> {
14    let delegate = LiveTextInteractionDelegate::new()?;
15    delegate.set_should_begin(true)?;
16    delegate.set_should_handle_key_down_event(true)?;
17    delegate.set_should_show_menu_for_event(true)?;
18    delegate.set_contents_rect_override(Some(Rect::default()))?;
19
20    let content_view = LiveTextContentView::new()?;
21    content_view.set_frame(Rect {
22        x: 0.0,
23        y: 0.0,
24        width: 32.0,
25        height: 32.0,
26    })?;
27    delegate.set_content_view(Some(&content_view))?;
28
29    let updated_menu = LiveTextMenu {
30        title: "VisionKit".to_owned(),
31        items: vec![LiveTextMenuItem {
32            title: "Copy".to_owned(),
33            tag: LiveTextMenuTag::copy_image().map_or(0, LiveTextMenuTag::raw_value),
34            is_separator: false,
35            is_enabled: true,
36            is_hidden: false,
37            state: 0,
38            submenu: None,
39        }],
40    };
41    delegate.set_updated_menu(Some(&updated_menu))?;
42    Ok(delegate)
43}
44
45fn print_selection_state(
46    interaction: &LiveTextInteraction,
47) -> Result<(), Box<dyn std::error::Error>> {
48    match interaction.selected_ranges() {
49        Ok(ranges) => {
50            interaction.set_selected_ranges(&ranges)?;
51            println!("selected ranges: {}", ranges.len());
52        }
53        Err(error) => println!("selected ranges: {error}"),
54    }
55    match interaction.selected_attributed_text() {
56        Ok(text) => println!("selected attributed text runs: {}", text.runs.len()),
57        Err(error) => println!("selected attributed text runs: {error}"),
58    }
59    match interaction.supplementary_interface_font() {
60        Ok(font) => {
61            interaction.set_supplementary_interface_font(font.as_ref())?;
62            println!("supplementary font set: {}", font.is_some());
63        }
64        Err(error) => println!("supplementary font set: {error}"),
65    }
66    Ok(())
67}
68
69fn print_subject_state(
70    interaction: &LiveTextInteraction,
71) -> Result<(), Box<dyn std::error::Error>> {
72    println!(
73        "subject unavailable case: {:?}",
74        LiveTextSubjectUnavailable::ImageUnavailable
75    );
76    match interaction.begin_subject_analysis_if_necessary() {
77        Ok(()) => println!("subject analysis started"),
78        Err(error) => println!("subject analysis started: {error}"),
79    }
80    match interaction.subjects() {
81        Ok(subjects) => {
82            println!("subjects: {}", subjects.len());
83            println!(
84                "highlighted subjects: {}",
85                interaction.highlighted_subjects()?.len()
86            );
87            if let Some(subject) = subjects.first() {
88                println!("subject bounds: {:?}", subject.bounds()?);
89            }
90            match interaction.image_for_subjects(&subjects) {
91                Ok(image) => println!("subject image bytes: {}", image.png_data.len()),
92                Err(error) => println!("subject image bytes: {error}"),
93            }
94        }
95        Err(error) => println!("subjects: {error}"),
96    }
97    Ok(())
98}
99
100fn main() -> Result<(), Box<dyn std::error::Error>> {
101    if !ImageAnalyzer::is_supported() {
102        println!("ImageAnalyzer is not supported on this Mac");
103        return Ok(());
104    }
105
106    let asset_path = asset_path();
107    let analyzer = ImageAnalyzer::new()?;
108    let analysis = analyzer.analyze_image_at_path(
109        &asset_path,
110        ImageOrientation::Up,
111        &ImageAnalyzerConfiguration::new(ImageAnalysisTypes::TEXT),
112    )?;
113
114    let delegate = build_delegate()?;
115    let interaction = LiveTextInteraction::with_delegate(&delegate)?;
116    let tracking_view = LiveTextTrackingImageView::new()?;
117    tracking_view.set_image_at_path(&asset_path)?;
118    interaction.set_tracking_image_view(Some(&tracking_view))?;
119    interaction.track_image_at_path(&asset_path)?;
120    interaction.set_analysis(&analysis)?;
121    interaction.set_preferred_interaction_types(LiveTextInteractionTypes::AUTOMATIC_TEXT_ONLY)?;
122    interaction.set_selectable_items_highlighted(true)?;
123    interaction.set_contents_rect_needs_update()?;
124
125    println!("contents rect: {:?}", interaction.contents_rect()?);
126    println!("overlay text: {:?}", interaction.text());
127    println!("delegate events: {}", delegate.recorded_events()?.len());
128    println!(
129        "delegate content view set: {}",
130        interaction.delegate()?.and_then(|value| value.content_view().ok()).flatten().is_some()
131    );
132    match interaction.tracking_image_view()? {
133        Some(view) => println!("tracking image size: {:?}", view.image_size()?),
134        None => println!("tracking image size: none"),
135    }
136    match LiveTextMenuTag::copy_image() {
137        Ok(tag) => println!("copy image tag: {}", tag.raw_value()),
138        Err(error) => println!("copy image tag: {error}"),
139    }
140    print_selection_state(&interaction)?;
141    print_subject_state(&interaction)?;
142    println!(
143        "live text button visible: {}",
144        interaction.live_text_button_visible()?
145    );
146    println!(
147        "supplementary hidden: {}",
148        interaction.is_supplementary_interface_hidden()?
149    );
150
151    let image_for_subjects_fn: fn(&LiveTextInteraction, &[LiveTextSubject]) -> Result<
152        LiveTextImageData,
153        VisionKitError,
154    > = LiveTextInteraction::image_for_subjects;
155    black_box(image_for_subjects_fn);
156    Ok(())
157}