1use tiny_skia::*;
2
3use crate::element::{Element, FreeDrawElement, LineElement, ShapeElement, TextElement};
4use crate::geometry;
5use crate::style::{FillStyle, FillType};
6
7use super::path::*;
8use super::{
9 CORNER_RADIUS, GRID_ALPHA, GRID_B, GRID_G, GRID_MIN_SCREEN_PX, GRID_R, GRID_SIZE,
10 HACHURE_ALPHA, HACHURE_LINE_WIDTH, parse_color, stroke_from_style,
11};
12
13impl super::Renderer {
14 pub(super) fn draw_element(&self, pixmap: &mut Pixmap, el: &Element, transform: &Transform) {
15 let opacity = el.opacity() as f32;
16 match el {
17 Element::Rectangle(e) => self.draw_rectangle(pixmap, e, transform, opacity),
18 Element::Ellipse(e) => self.draw_ellipse(pixmap, e, transform, opacity),
19 Element::Diamond(e) => self.draw_diamond(pixmap, e, transform, opacity),
20 Element::Line(e) => self.draw_line(pixmap, e, transform, opacity),
21 Element::Arrow(e) => self.draw_arrow(pixmap, e, transform, opacity),
22 Element::FreeDraw(e) => self.draw_freedraw(pixmap, e, transform, opacity),
23 Element::Text(e) => self.draw_text(pixmap, e, transform, opacity),
24 }
25 }
26
27 fn draw_rectangle(
28 &self,
29 pixmap: &mut Pixmap,
30 el: &ShapeElement,
31 transform: &Transform,
32 opacity: f32,
33 ) {
34 let (nx, ny, nw, nh) = geometry::normalize_bounds(el.x, el.y, el.width, el.height);
35 let (x, y, w, h) = (nx as f32, ny as f32, nw as f32, nh as f32);
36 if w < 0.5 && h < 0.5 {
37 return;
38 }
39
40 let radius = CORNER_RADIUS.min(w / 3.0).min(h / 3.0);
41 if let Some(path) = build_rounded_rect_path(x, y, w, h, radius) {
42 self.fill_shape(pixmap, &path, &el.fill, x, y, w, h, transform, opacity);
43 let (paint, stroke) = stroke_from_style(&el.stroke, opacity);
44 pixmap.stroke_path(&path, &paint, &stroke, *transform, None);
45 }
46 }
47
48 fn draw_ellipse(
49 &self,
50 pixmap: &mut Pixmap,
51 el: &ShapeElement,
52 transform: &Transform,
53 opacity: f32,
54 ) {
55 let rx = (el.width).abs() as f32 / 2.0;
56 let ry = (el.height).abs() as f32 / 2.0;
57 if rx < 0.5 && ry < 0.5 {
58 return;
59 }
60
61 let cx = el.x as f32 + el.width as f32 / 2.0;
62 let cy = el.y as f32 + el.height as f32 / 2.0;
63 let safe_rx = rx.max(0.1);
64 let safe_ry = ry.max(0.1);
65
66 if let Some(path) = build_ellipse_path(cx, cy, safe_rx, safe_ry) {
67 let (nx, ny, nw, nh) = geometry::normalize_bounds(el.x, el.y, el.width, el.height);
68 let (bx, by, w, h) = (nx as f32, ny as f32, nw as f32, nh as f32);
69
70 self.fill_shape(pixmap, &path, &el.fill, bx, by, w, h, transform, opacity);
71 let (paint, stroke) = stroke_from_style(&el.stroke, opacity);
72 pixmap.stroke_path(&path, &paint, &stroke, *transform, None);
73 }
74 }
75
76 fn draw_diamond(
77 &self,
78 pixmap: &mut Pixmap,
79 el: &ShapeElement,
80 transform: &Transform,
81 opacity: f32,
82 ) {
83 let (nx, ny, nw, nh) = geometry::normalize_bounds(el.x, el.y, el.width, el.height);
84 let (x, y, w, h) = (nx as f32, ny as f32, nw as f32, nh as f32);
85 if w < 0.5 && h < 0.5 {
86 return;
87 }
88
89 if let Some(path) = build_diamond_path(x, y, w, h) {
90 self.fill_shape(pixmap, &path, &el.fill, x, y, w, h, transform, opacity);
91 let (paint, stroke) = stroke_from_style(&el.stroke, opacity);
92 pixmap.stroke_path(&path, &paint, &stroke, *transform, None);
93 }
94 }
95
96 fn draw_line(
97 &self,
98 pixmap: &mut Pixmap,
99 el: &LineElement,
100 transform: &Transform,
101 opacity: f32,
102 ) {
103 if el.points.len() < 2 {
104 return;
105 }
106 if let Some(path) = build_polyline_path(el) {
107 let (paint, stroke) = stroke_from_style(&el.stroke, opacity);
108 pixmap.stroke_path(&path, &paint, &stroke, *transform, None);
109 }
110 }
111
112 fn draw_arrow(
113 &self,
114 pixmap: &mut Pixmap,
115 el: &LineElement,
116 transform: &Transform,
117 opacity: f32,
118 ) {
119 self.draw_line(pixmap, el, transform, opacity);
120 if el.points.len() < 2 {
121 return;
122 }
123
124 let color = parse_color(&el.stroke.color, opacity);
125 let mut paint = Paint::default();
126 paint.set_color(color);
127 paint.anti_alias = true;
128 let arrowhead_stroke = Stroke {
129 width: (el.stroke.width as f32) * 0.5,
130 line_cap: LineCap::Round,
131 line_join: LineJoin::Round,
132 ..Stroke::default()
133 };
134
135 let last = el.points.last().unwrap();
136 let prev = &el.points[el.points.len() - 2];
137 let end_ah = geometry::compute_arrowhead(
138 last.x + el.x,
139 last.y + el.y,
140 prev.x + el.x,
141 prev.y + el.y,
142 geometry::ARROWHEAD_LENGTH,
143 geometry::ARROWHEAD_ANGLE,
144 );
145 draw_arrowhead_path(pixmap, &end_ah, &paint, &arrowhead_stroke, transform);
146
147 if el.start_arrowhead.is_some() {
148 let first = &el.points[0];
149 let next = &el.points[1];
150 let start_ah = geometry::compute_arrowhead(
151 first.x + el.x,
152 first.y + el.y,
153 next.x + el.x,
154 next.y + el.y,
155 geometry::ARROWHEAD_LENGTH,
156 geometry::ARROWHEAD_ANGLE,
157 );
158 draw_arrowhead_path(pixmap, &start_ah, &paint, &arrowhead_stroke, transform);
159 }
160 }
161
162 fn draw_freedraw(
163 &self,
164 pixmap: &mut Pixmap,
165 el: &FreeDrawElement,
166 transform: &Transform,
167 opacity: f32,
168 ) {
169 if el.points.len() < 2 {
170 return;
171 }
172 if let Some(path) = build_freedraw_path(el) {
173 let (paint, stroke) = stroke_from_style(&el.stroke, opacity);
174 pixmap.stroke_path(&path, &paint, &stroke, *transform, None);
175 }
176 }
177
178 fn draw_text(
179 &self,
180 _pixmap: &mut Pixmap,
181 _el: &TextElement,
182 _transform: &Transform,
183 _opacity: f32,
184 ) {
185 }
188
189 #[allow(clippy::too_many_arguments)]
192 fn fill_shape(
193 &self,
194 pixmap: &mut Pixmap,
195 clip_path: &Path,
196 fill: &FillStyle,
197 x: f32,
198 y: f32,
199 w: f32,
200 h: f32,
201 transform: &Transform,
202 opacity: f32,
203 ) {
204 match fill.style {
205 FillType::None => {}
206 FillType::Solid => {
207 let color = parse_color(&fill.color, opacity);
208 let mut paint = Paint::default();
209 paint.set_color(color);
210 paint.anti_alias = true;
211 pixmap.fill_path(clip_path, &paint, FillRule::Winding, *transform, None);
212 }
213 FillType::Hachure => {
214 self.draw_hachure_fill(
215 pixmap,
216 clip_path,
217 &fill.color,
218 fill.gap as f32,
219 fill.angle as f32,
220 x,
221 y,
222 w,
223 h,
224 transform,
225 opacity,
226 );
227 }
228 FillType::CrossHatch => {
229 self.draw_hachure_fill(
230 pixmap,
231 clip_path,
232 &fill.color,
233 fill.gap as f32,
234 fill.angle as f32,
235 x,
236 y,
237 w,
238 h,
239 transform,
240 opacity,
241 );
242 self.draw_hachure_fill(
243 pixmap,
244 clip_path,
245 &fill.color,
246 fill.gap as f32,
247 fill.angle as f32 + std::f32::consts::FRAC_PI_2,
248 x,
249 y,
250 w,
251 h,
252 transform,
253 opacity,
254 );
255 }
256 }
257 }
258
259 #[allow(clippy::too_many_arguments)]
260 fn draw_hachure_fill(
261 &self,
262 pixmap: &mut Pixmap,
263 clip_path: &Path,
264 color: &str,
265 gap: f32,
266 angle: f32,
267 x: f32,
268 y: f32,
269 w: f32,
270 h: f32,
271 transform: &Transform,
272 opacity: f32,
273 ) {
274 let Some(mut clip_mask) = Mask::new(pixmap.width(), pixmap.height()) else {
275 return;
276 };
277 clip_mask.fill_path(clip_path, FillRule::Winding, true, *transform);
278
279 let parsed_color = parse_color(color, opacity * HACHURE_ALPHA);
280 let mut paint = Paint::default();
281 paint.set_color(parsed_color);
282 paint.anti_alias = true;
283
284 let stroke = Stroke {
285 width: HACHURE_LINE_WIDTH,
286 line_cap: LineCap::Round,
287 ..Stroke::default()
288 };
289
290 let cx = (x + w / 2.0) as f64;
291 let cy = (y + h / 2.0) as f64;
292 for line in
293 geometry::generate_hachure_lines(cx, cy, w as f64, h as f64, gap as f64, angle as f64)
294 {
295 let mut pb = PathBuilder::new();
296 pb.move_to(line.x1 as f32, line.y1 as f32);
297 pb.line_to(line.x2 as f32, line.y2 as f32);
298 if let Some(line_path) = pb.finish() {
299 pixmap.stroke_path(&line_path, &paint, &stroke, *transform, Some(&clip_mask));
300 }
301 }
302 }
303
304 pub(super) fn draw_grid(&self, pixmap: &mut Pixmap, viewport: &crate::point::ViewState) {
307 let zoom = viewport.zoom as f32 * self.config.pixel_ratio;
308 let gs = GRID_SIZE * zoom;
309 if gs < GRID_MIN_SCREEN_PX {
310 return;
311 }
312
313 let w = pixmap.width() as f32;
314 let h = pixmap.height() as f32;
315 let off_x = (viewport.scroll_x as f32 * self.config.pixel_ratio) % gs;
316 let off_y = (viewport.scroll_y as f32 * self.config.pixel_ratio) % gs;
317
318 let color = Color::from_rgba(
319 GRID_R as f32 / 255.0,
320 GRID_G as f32 / 255.0,
321 GRID_B as f32 / 255.0,
322 GRID_ALPHA,
323 )
324 .unwrap_or(Color::TRANSPARENT);
325 let mut paint = Paint::default();
326 paint.set_color(color);
327 paint.anti_alias = false;
328
329 let stroke = Stroke {
330 width: 1.0,
331 ..Stroke::default()
332 };
333 let identity = Transform::identity();
334
335 let mut x = off_x;
336 while x < w {
337 let mut pb = PathBuilder::new();
338 pb.move_to(x, 0.0);
339 pb.line_to(x, h);
340 if let Some(path) = pb.finish() {
341 pixmap.stroke_path(&path, &paint, &stroke, identity, None);
342 }
343 x += gs;
344 }
345
346 let mut y = off_y;
347 while y < h {
348 let mut pb = PathBuilder::new();
349 pb.move_to(0.0, y);
350 pb.line_to(w, y);
351 if let Some(path) = pb.finish() {
352 pixmap.stroke_path(&path, &paint, &stroke, identity, None);
353 }
354 y += gs;
355 }
356 }
357}
358
359impl super::Renderer {
360 pub fn draw_snap_indicator(
362 &self,
363 pixmap: &mut Pixmap,
364 viewport: &crate::point::ViewState,
365 wx: f64,
366 wy: f64,
367 ) {
368 let vt = super::viewport_transform(viewport, self.config.pixel_ratio);
369 let scale = viewport.zoom as f32 * self.config.pixel_ratio;
370 let radius = super::HANDLE_RADIUS / scale;
371
372 let accent = Color::from_rgba8(super::ACCENT_R, super::ACCENT_G, super::ACCENT_B, 255);
373 let mut fill_paint = Paint::default();
374 fill_paint.set_color(accent);
375 fill_paint.anti_alias = true;
376
377 if let Some(path) = super::path::build_circle_path(wx as f32, wy as f32, radius) {
378 pixmap.fill_path(&path, &fill_paint, FillRule::Winding, vt, None);
379 }
380 }
381}
382
383fn draw_arrowhead_path(
384 pixmap: &mut Pixmap,
385 ah: &geometry::ArrowheadPoints,
386 paint: &Paint,
387 stroke: &Stroke,
388 transform: &Transform,
389) {
390 let mut pb = PathBuilder::new();
391 pb.move_to(ah.tip_x as f32, ah.tip_y as f32);
392 pb.line_to(ah.left_x as f32, ah.left_y as f32);
393 pb.line_to(ah.right_x as f32, ah.right_y as f32);
394 pb.close();
395 if let Some(path) = pb.finish() {
396 pixmap.fill_path(&path, paint, FillRule::Winding, *transform, None);
397 pixmap.stroke_path(&path, paint, stroke, *transform, None);
398 }
399}