1use std::fmt;
4
5use geo::MapCoords;
6use tiny_skia::{Color, Paint, 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, point::PointStyle};
14
15macro_rules! impl_line_style {
16 ($style: ident, $line: ident) => {
17 #[derive(Clone)]
18 #[doc = concat!("A style that can be applied to the [`geo::", stringify!($line), "`] primitive.")]
19 pub struct $style<'a> {
20 pub color_options: ColorOptions,
21 pub point_style: PointStyle<'a>,
22 pub width: f32,
23 pub effect: Option<Effect<'a, geo::$line<f64>, Self>>,
24 }
25
26 impl<'a> fmt::Debug for $style<'a> {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 f.debug_struct(stringify!($style))
29 .field("color_options", &self.color_options)
30 .field("point_style", &self.point_style)
31 .field("width", &self.width)
32 .finish()
33 }
34 }
35
36 impl<'a> Default for $style<'a> {
37 fn default() -> Self {
38 Self {
39 color_options: ColorOptions {
40 foreground: Color::from_rgba8(196, 196, 196, 255),
41 border: Some(4.0),
42 ..ColorOptions::default()
43 },
44 point_style: PointStyle::default(),
45 width: 3.0,
46 effect: None,
47 }
48 }
49 }
50 };
51}
52
53impl_line_style!(LineStyle, Line);
54impl_line_style!(LineStringStyle, LineString);
55
56impl_styled_geo!(
57 Line,
58 LineStyle<'_>,
59 fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
60 let style = match &self.style.effect {
61 Some(effect) => {
62 &(effect
63 .clone()
64 .apply(self.style.clone(), self.inner, context))
65 }
66
67 None => &self.style,
68 };
69
70 let line = self
71 .inner
72 .map_coords(|coord| context.epsg_4326_to_pixel(&coord));
73
74 #[cfg(feature = "tracing")]
75 {
76 tracing::trace!(start = ?line.start, end = ?line.end, "rendering `Line` to `pixmap`");
77 }
78
79 let mut path_builder = PathBuilder::new();
80 path_builder.move_to(line.start.x as f32, line.start.y as f32);
81 path_builder.line_to(line.end.x as f32, line.end.y as f32);
82
83 let line = path_builder
84 .finish()
85 .ok_or(crate::Error::PathConstruction)?;
86
87 if let Some(border) = style.color_options.border {
88 pixmap.stroke_path(
89 &line,
90 &Paint {
91 shader: Shader::SolidColor(style.color_options.background),
92 anti_alias: style.color_options.anti_alias,
93 ..Paint::default()
94 },
95 &Stroke {
96 width: border,
97 ..Stroke::default()
98 },
99 Transform::default(),
100 None,
101 );
102 }
103
104 pixmap.stroke_path(
105 &line,
106 &Paint {
107 shader: Shader::SolidColor(style.color_options.foreground),
108 anti_alias: style.color_options.anti_alias,
109 ..Paint::default()
110 },
111 &Stroke {
112 width: style.width,
113 ..Stroke::default()
114 },
115 Transform::default(),
116 None,
117 );
118
119 self.inner
120 .start_point()
121 .as_styled(style.point_style.clone())
122 .draw(
123 pixmap,
124 &Context {
125 index: 0,
126 ..context.clone()
127 },
128 )?;
129
130 self.inner
131 .end_point()
132 .as_styled(style.point_style.clone())
133 .draw(
134 pixmap,
135 &Context {
136 index: 1,
137 ..context.clone()
138 },
139 )?;
140
141 Ok(())
142 }
143);
144
145impl_styled_geo!(
146 LineString,
147 LineStringStyle<'_>,
148 fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
149 let style = match &self.style.effect {
150 Some(effect) => {
151 &(effect
152 .clone()
153 .apply(self.style.clone(), self.inner, context))
154 }
155
156 None => &self.style,
157 };
158
159 let mut path_builder = PathBuilder::new();
160
161 let line_string = self
162 .inner
163 .map_coords(|coord| context.epsg_4326_to_pixel(&coord));
164
165 #[cfg(feature = "tracing")]
166 {
167 tracing::trace!("rendering `LineString` to `pixmap`");
168 }
169
170 for (index, point) in line_string.points().enumerate() {
171 if index == 0 {
172 path_builder.move_to(point.x() as f32, point.y() as f32);
173 } else {
174 path_builder.line_to(point.x() as f32, point.y() as f32);
175 }
176 }
177
178 if let Some(lines) = path_builder.finish() {
179 if let Some(border) = style.color_options.border {
180 pixmap.stroke_path(
181 &lines,
182 &Paint {
183 shader: Shader::SolidColor(style.color_options.background),
184 anti_alias: style.color_options.anti_alias,
185 ..Paint::default()
186 },
187 &Stroke {
188 width: border,
189 ..Stroke::default()
190 },
191 Transform::default(),
192 None,
193 );
194 }
195
196 pixmap.stroke_path(
197 &lines,
198 &Paint {
199 shader: Shader::SolidColor(style.color_options.foreground),
200 anti_alias: style.color_options.anti_alias,
201 ..Paint::default()
202 },
203 &Stroke {
204 width: style.width,
205 ..Stroke::default()
206 },
207 Transform::default(),
208 None,
209 );
210 }
211
212 self.inner
213 .points()
214 .enumerate()
215 .try_for_each(|(index, point)| {
216 let context = &Context {
217 index,
218 ..context.clone()
219 };
220
221 point
222 .as_styled(style.point_style.clone())
223 .draw(pixmap, context)
224 })?;
225
226 Ok(())
227 }
228);
229
230impl_styled_geo!(
231 MultiLineString,
232 LineStringStyle<'_>,
233 fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
234 self.inner
235 .iter()
236 .map(|line_string| line_string.as_styled(self.style.clone()))
237 .try_for_each(|line_string| line_string.draw(pixmap, context))
238 }
239);