1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::let_underscore_untyped)]

mod document_widget;
mod engine;
mod frame_history;
mod painters;
mod render_data;
pub mod viewer;
#[cfg(target_arch = "wasm32")]
pub mod web_handle;

use eframe::CreationContext;
use std::sync::Arc;
pub use viewer::Viewer;

pub use crate::document_widget::DocumentWidget;

/// Export of core dependencies in addition to what vsvg already re-exports.
pub mod exports {
    pub use ::eframe;
    pub use ::egui;
    pub use ::wgpu;
}

/// Viewer app for [`show()`] and [`show_tolerance()`].
struct ShowViewerApp {
    document: Arc<vsvg::Document>,
    tolerance: f64,
}

impl ViewerApp for ShowViewerApp {
    fn setup(
        &mut self,
        _cc: &CreationContext,
        document_widget: &mut DocumentWidget,
    ) -> anyhow::Result<()> {
        document_widget.set_tolerance(self.tolerance);
        document_widget.set_document(self.document.clone());
        Ok(())
    }
}

const DEFAULT_RENDERER_TOLERANCE: f64 = 0.01;

/// Show a document in a window.
///
/// For native use only.
#[cfg(not(target_arch = "wasm32"))]
pub fn show(document: Arc<vsvg::Document>) -> anyhow::Result<()> {
    show_tolerance(document, DEFAULT_RENDERER_TOLERANCE)
}

/// Show a document in a window with a custom renderer tolerance.
///
/// The renderer tolerance is used to convert curves into line segments before rendering. Smaller
/// values yield less visible artifacts but require more CPU to render.
///
/// For native use only.
#[cfg(not(target_arch = "wasm32"))]
pub fn show_tolerance(document: Arc<vsvg::Document>, tolerance: f64) -> anyhow::Result<()> {
    let native_options = eframe::NativeOptions::default();

    eframe::run_native(
        "vsvg-viewer",
        native_options,
        Box::new(move |cc| {
            let style = egui::Style {
                visuals: egui::Visuals::light(),
                ..egui::Style::default()
            };
            cc.egui_ctx.set_style(style);

            Box::new(
                Viewer::new(
                    cc,
                    Box::new(ShowViewerApp {
                        document: document.clone(),
                        tolerance,
                    }),
                )
                .expect("viewer requires wgpu backend"),
            )
        }),
    )?;

    Ok(())
}

/// Implement this trait to build a custom viewer app based on [`Viewer`].
pub trait ViewerApp {
    fn setup(
        &mut self,
        _cc: &eframe::CreationContext,
        _document_widget: &mut DocumentWidget,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    /// Handle input
    ///
    /// This is call very early in the frame loop to allow consuming input before egui.
    fn handle_input(&mut self, _ctx: &egui::Context, _document_widget: &mut DocumentWidget) {}

    /// Hook to show side panels
    ///
    /// This hook is called before the central panel is drawn, as per the [`egui`] documentation.
    fn show_panels(
        &mut self,
        _ctx: &egui::Context,
        _document_widget: &mut DocumentWidget,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    /// Hook to show the central panel.
    ///
    /// This is call after the wgpu render callback that displays the document.
    fn show_central_panel(
        &mut self,
        _ui: &mut egui::Ui,
        _document_widget: &mut DocumentWidget,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    /// Hook to modify the native options before starting the app.
    #[cfg(not(target_arch = "wasm32"))]
    fn native_options(&self) -> eframe::NativeOptions {
        eframe::NativeOptions::default()
    }

    /// Window title
    fn title(&self) -> String {
        "vsvg ViewerApp".to_owned()
    }

    /// Hook to load persistent data.
    ///
    /// Use [`eframe::get_value`] to retrieve the data.
    fn load(&mut self, _storage: &dyn eframe::Storage) {}

    /// Hook to save persistent data.
    ///
    /// Use [`eframe::set_value`] to store the data.
    fn save(&self, _storage: &mut dyn eframe::Storage) {}

    /// Hook executed before shutting down the app.
    fn on_exit(&mut self) {}
}

/// Show a custom [`ViewerApp`].
///
/// For native use only.
#[cfg(not(target_arch = "wasm32"))]
pub fn show_with_viewer_app(viewer_app: impl ViewerApp + 'static) -> anyhow::Result<()> {
    vsvg::trace_function!();

    let viewer_app = Box::new(viewer_app);

    let native_options = viewer_app.native_options();

    eframe::run_native(
        viewer_app.title().as_str(),
        native_options,
        Box::new(move |cc| {
            let style = egui::Style {
                visuals: egui::Visuals::light(),
                ..egui::Style::default()
            };
            cc.egui_ctx.set_style(style);

            Box::new(Viewer::new(cc, viewer_app).expect("viewer requires wgpu backend"))
        }),
    )?;

    Ok(())
}