plotters_solstice/
lib.rs

1use plotters_backend::{
2    BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
3};
4use solstice_2d::{Draw, Stroke};
5
6pub struct SolsticeBackend {
7    ctx: solstice_2d::solstice::Context,
8    gfx: solstice_2d::Graphics,
9    font_id: solstice_2d::FontId,
10    draw_list: solstice_2d::DrawList<'static>,
11}
12
13impl SolsticeBackend {
14    #[cfg(target_arch = "wasm32")]
15    pub fn with_webgl1(
16        webgl1: web_sys::WebGlRenderingContext,
17        font_data: solstice_2d::text::FontVec,
18        width: f32,
19        height: f32,
20        line_buffer_capacity: usize,
21        mesh_buffer_capacity: usize,
22    ) -> Result<Self, solstice_2d::GraphicsError> {
23        let ctx = solstice_2d::solstice::glow::Context::from_webgl1_context(webgl1);
24        let mut ctx = solstice_2d::solstice::Context::new(ctx);
25        ctx.set_viewport(0, 0, width as _, height as _);
26        let mut gfx = solstice_2d::Graphics::with_config(
27            &mut ctx,
28            &solstice_2d::Config {
29                width,
30                height,
31                line_capacity: line_buffer_capacity,
32                mesh_capacity: mesh_buffer_capacity,
33            },
34        )?;
35        let font_id = gfx.add_font(font_data);
36        Ok(Self {
37            ctx,
38            gfx,
39            font_id,
40            draw_list: Default::default(),
41        })
42    }
43
44    pub fn resize(&mut self, width: f32, height: f32) {
45        self.ctx.set_viewport(0, 0, width as _, height as _);
46        self.gfx.set_width_height(width, height);
47    }
48}
49
50fn color_into(color: BackendColor) -> solstice_2d::Color {
51    let (r, g, b) = color.rgb;
52    solstice_2d::Color {
53        red: r as f32 / 255.,
54        green: g as f32 / 255.,
55        blue: b as f32 / 255.,
56        alpha: color.alpha as _,
57    }
58}
59
60impl DrawingBackend for SolsticeBackend {
61    type ErrorType = solstice_2d::GraphicsError;
62
63    fn get_size(&self) -> (u32, u32) {
64        let vw = self.ctx.viewport();
65        let (w, h) = vw.dimensions();
66        (w as _, h as _)
67    }
68
69    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
70        Ok(())
71    }
72
73    fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
74        self.gfx.process(&mut self.ctx, &self.draw_list);
75        self.draw_list = solstice_2d::DrawList::default();
76        log::trace!("Presented.");
77        Ok(())
78    }
79
80    fn draw_pixel(
81        &mut self,
82        point: BackendCoord,
83        color: BackendColor,
84    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
85        let size = self.get_size();
86        if point.0 < 0 || point.1 < 0 || point.0 as u32 >= size.0 || point.1 as u32 >= size.1 {
87            return Ok(());
88        }
89
90        let (x, y) = point;
91        self.draw_list.draw_with_color(
92            solstice_2d::Rectangle {
93                x: x as _,
94                y: y as _,
95                width: 1.0,
96                height: 1.0,
97            },
98            color_into(color),
99        );
100        Ok(())
101    }
102
103    fn draw_line<S: BackendStyle>(
104        &mut self,
105        (x1, y1): BackendCoord,
106        (x2, y2): BackendCoord,
107        style: &S,
108    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
109        let width = style.stroke_width() as f32;
110        let color = color_into(style.color()).into();
111        self.draw_list.line_2d(vec![
112            solstice_2d::LineVertex {
113                position: [x1 as _, y1 as _, 0.],
114                width,
115                color,
116            },
117            solstice_2d::LineVertex {
118                position: [x2 as _, y2 as _, 0.],
119                width,
120                color,
121            },
122        ]);
123        Ok(())
124    }
125
126    fn draw_rect<S: BackendStyle>(
127        &mut self,
128        (x1, y1): BackendCoord,
129        (x2, y2): BackendCoord,
130        style: &S,
131        fill: bool,
132    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
133        let geometry = solstice_2d::Rectangle {
134            x: x1 as f32,
135            y: y1 as f32,
136            width: x2 as f32 - x1 as f32,
137            height: y2 as f32 - y1 as f32,
138        };
139        let color = color_into(style.color());
140        self.draw_list.set_line_width(style.stroke_width() as _);
141        match fill {
142            true => self.draw_list.draw_with_color(geometry, color),
143            false => self.draw_list.stroke_with_color(geometry, color),
144        }
145        Ok(())
146    }
147
148    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
149        &mut self,
150        path: I,
151        style: &S,
152    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
153        let width = style.stroke_width() as f32;
154        let color = color_into(style.color()).into();
155        self.draw_list.line_2d(
156            path.into_iter()
157                .map(|(x, y)| solstice_2d::LineVertex {
158                    position: [x as f32, y as f32, 0.],
159                    width,
160                    color,
161                })
162                .collect::<Vec<_>>(),
163        );
164        Ok(())
165    }
166
167    fn draw_circle<S: BackendStyle>(
168        &mut self,
169        (x, y): BackendCoord,
170        radius: u32,
171        style: &S,
172        fill: bool,
173    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
174        let geometry = solstice_2d::Circle {
175            x: x as f32,
176            y: y as f32,
177            radius: radius as f32,
178            segments: radius.max(10),
179        };
180        let color = color_into(style.color());
181        self.draw_list.set_line_width(style.stroke_width() as _);
182        match fill {
183            true => self.draw_list.draw_with_color(geometry, color),
184            false => self.draw_list.stroke_with_color(geometry, color),
185        }
186        Ok(())
187    }
188
189    fn draw_text<TStyle: BackendTextStyle>(
190        &mut self,
191        text: &str,
192        style: &TStyle,
193        (x, y): BackendCoord,
194    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
195        let color = style.color();
196        if color.alpha == 0.0 {
197            return Ok(());
198        }
199
200        let layout = style
201            .layout_box(text)
202            .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?;
203        let ((min_x, min_y), (max_x, max_y)) = layout;
204        let width = (max_x - min_x) as i32;
205        let height = (max_y - min_y) as i32;
206        let dx = match style.anchor().h_pos {
207            plotters_backend::text_anchor::HPos::Left => 0,
208            plotters_backend::text_anchor::HPos::Right => -width,
209            plotters_backend::text_anchor::HPos::Center => -width / 2,
210        };
211        let dy = match style.anchor().v_pos {
212            plotters_backend::text_anchor::VPos::Top => 0,
213            plotters_backend::text_anchor::VPos::Center => -height / 2,
214            plotters_backend::text_anchor::VPos::Bottom => -height,
215        };
216        let trans = style.transform();
217        let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y);
218        let scale = style.size() as f32;
219        let bounds = solstice_2d::Rectangle {
220            x: x as f32,
221            y: y as f32,
222            width: self.ctx.viewport().width() as f32,
223            height: self.ctx.viewport().height() as f32,
224        };
225        let text = text.to_owned();
226        self.draw_list.set_color(color_into(style.color()));
227        self.draw_list.print(text, self.font_id, scale, bounds);
228        self.draw_list.set_color([1., 1., 1., 1.]);
229        Ok(())
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    #[test]
236    fn it_works() {
237        let result = 2 + 2;
238        assert_eq!(result, 4);
239    }
240}