Skip to main content

snapr/drawing/geometry/
point.rs

1//! Contains [`Drawable`] implementations and [`Styles`](Style) for [`geo::Point`]` primitives.
2
3use std::fmt;
4
5use geo::MapCoords;
6use tiny_skia::{FillRule, Paint, Path, PathBuilder, Pixmap, Shader, Stroke, Transform};
7
8use crate::drawing::{
9    style::{ColorOptions, Effect, Styleable, Styled},
10    Context, Drawable,
11};
12
13use super::macros::impl_styled_geo;
14
15/// Represents a _shape_ that can be transformed into a [`Path`] via the [`Shape::to_path`] method.
16#[derive(Clone, Debug, PartialEq)]
17pub enum Shape {
18    Circle { radius: f32 },
19}
20
21impl Shape {
22    /// Converts the [`Shape`] to a [`Path`] modeling the selected variant.
23    pub fn to_path(&self, x: f32, y: f32) -> Result<Path, crate::Error> {
24        let mut path_builder = PathBuilder::new();
25
26        match self {
27            Self::Circle { radius } => {
28                path_builder.push_circle(x, y, *radius);
29            }
30        }
31
32        path_builder.finish().ok_or(crate::Error::PathConstruction)
33    }
34}
35
36impl Default for Shape {
37    fn default() -> Self {
38        Self::Circle { radius: 4.0 }
39    }
40}
41
42/// Controls how a [`geo::Point`] will be visualized when drawn.
43#[derive(Clone, Debug, PartialEq)]
44pub enum Representation {
45    Shape(Shape),
46
47    #[cfg(feature = "svg")]
48    Svg(crate::drawing::svg::Svg),
49}
50
51impl Default for Representation {
52    fn default() -> Self {
53        Self::Shape(Shape::default())
54    }
55}
56
57/// A [`Style`] that can be applied to [`geo::Point`] primitives.
58#[derive(Clone, Default)]
59pub struct PointStyle<'a> {
60    pub color_options: ColorOptions,
61    pub representation: Representation,
62    pub effect: Option<Effect<'a, geo::Point<f64>, Self>>,
63
64    #[cfg(feature = "svg")]
65    pub label: Option<crate::drawing::svg::Label>,
66}
67
68impl<'a> fmt::Debug for PointStyle<'a> {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.debug_struct("PointStyle")
71            .field("color_options", &self.color_options)
72            .field("representation", &self.representation)
73            .field("label", &self.label)
74            .finish()
75    }
76}
77
78impl_styled_geo!(
79    Point,
80    PointStyle<'_>,
81    fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
82        let style = match &self.style.effect {
83            Some(effect) => {
84                &(effect
85                    .clone()
86                    .apply(self.style.clone(), self.inner, context))
87            }
88
89            None => &self.style,
90        };
91
92        let point = self
93            .inner
94            .map_coords(|coord| context.epsg_4326_to_pixel(&coord));
95
96        let shape = match &style.representation {
97            Representation::Shape(shape) => shape,
98
99            #[cfg(feature = "svg")]
100            Representation::Svg(svg) => {
101                let svg = svg.try_as_svg((point.x(), point.y()))?;
102                svg.draw(pixmap, context)?;
103
104                return Ok(());
105            }
106        };
107
108        #[cfg(feature = "tracing")]
109        {
110            tracing::trace!(position = ?point, "rendering `Point` to `pixmap`");
111        }
112
113        let shape = shape.to_path(point.x() as f32, point.y() as f32)?;
114
115        pixmap.fill_path(
116            &shape,
117            &Paint {
118                shader: Shader::SolidColor(style.color_options.foreground),
119                anti_alias: style.color_options.anti_alias,
120                ..Paint::default()
121            },
122            FillRule::default(),
123            Transform::default(),
124            None,
125        );
126
127        if let Some(border) = style.color_options.border {
128            pixmap.stroke_path(
129                &shape,
130                &Paint {
131                    shader: Shader::SolidColor(style.color_options.background),
132                    anti_alias: style.color_options.anti_alias,
133                    ..Paint::default()
134                },
135                &Stroke {
136                    width: border,
137                    ..Stroke::default()
138                },
139                Transform::default(),
140                None,
141            );
142        }
143
144        #[cfg(feature = "svg")]
145        if let Some(label) = &style.label {
146            let svg = label.try_as_svg((point.x(), point.y()))?;
147            svg.draw(pixmap, context)?;
148        }
149
150        Ok(())
151    }
152);
153
154impl_styled_geo!(
155    MultiPoint,
156    PointStyle<'_>,
157    fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
158        self.inner
159            .iter()
160            .map(|point| point.as_styled(self.style.clone()))
161            .try_for_each(|point| point.draw(pixmap, context))
162    }
163);