1use std::collections::HashSet;
2use std::sync::{Mutex, OnceLock};
3
4use plotters::prelude::*;
5use plotters::style::FontStyle;
6
7use super::backend::{
8 DrawBackend, LineStyle, PointShape, PointStyle, RectStyle, TextAnchor, TextStyle,
9};
10use super::{Rect, RenderError};
11
12const SANS: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans.ttf");
16const SANS_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans-Bold.ttf");
17const SANS_OBLIQUE: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans-Oblique.ttf");
18const SERIF: &[u8] = include_bytes!("../../assets/fonts/DejaVuSerif.ttf");
19const SERIF_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSerif-Bold.ttf");
20const SERIF_ITALIC: &[u8] = include_bytes!("../../assets/fonts/DejaVuSerif-Italic.ttf");
21const MONO: &[u8] = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf");
22const MONO_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSansMono-Bold.ttf");
23
24fn faces_for(family: &str) -> [(FontStyle, &'static [u8]); 4] {
28 let f = family.to_lowercase();
29 if f.contains("mono") || f.contains("courier") {
30 [
31 (FontStyle::Normal, MONO),
32 (FontStyle::Bold, MONO_BOLD),
33 (FontStyle::Italic, MONO),
34 (FontStyle::Oblique, MONO),
35 ]
36 } else if f == "serif" || f.contains("times") || (f.contains("serif") && !f.contains("sans")) {
37 [
38 (FontStyle::Normal, SERIF),
39 (FontStyle::Bold, SERIF_BOLD),
40 (FontStyle::Italic, SERIF_ITALIC),
41 (FontStyle::Oblique, SERIF_ITALIC),
42 ]
43 } else {
44 [
45 (FontStyle::Normal, SANS),
46 (FontStyle::Bold, SANS_BOLD),
47 (FontStyle::Italic, SANS_OBLIQUE),
48 (FontStyle::Oblique, SANS_OBLIQUE),
49 ]
50 }
51}
52
53fn registered_families() -> &'static Mutex<HashSet<String>> {
54 static REGISTERED: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
55 REGISTERED.get_or_init(|| Mutex::new(HashSet::new()))
56}
57
58fn ensure_font(family: &str) {
65 let mut set = registered_families().lock().unwrap();
66 if set.insert(family.to_string()) {
67 for (style, bytes) in faces_for(family) {
70 let _ = plotters::style::register_font(family, style, bytes);
72 }
73 }
74}
75
76pub struct PlottersAdapter<'a, DB: DrawingBackend> {
78 area: &'a DrawingArea<DB, plotters::coord::Shift>,
79 plot_area: Rect,
80 total_area: Rect,
81 corner_claims: Vec<Rect>,
84 clip: bool,
87 clips: Vec<Rect>,
90}
91
92impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
93 pub fn new(area: &'a DrawingArea<DB, plotters::coord::Shift>, plot_area: Rect) -> Self {
94 let (w, h) = area.dim_in_pixel();
95 PlottersAdapter {
96 area,
97 plot_area,
98 total_area: Rect {
99 x: 0.0,
100 y: 0.0,
101 width: w as f64,
102 height: h as f64,
103 },
104 corner_claims: Vec::new(),
105 clip: true,
106 clips: Vec::new(),
107 }
108 }
109}
110
111fn to_rgba(color: (u8, u8, u8), alpha: f64) -> RGBAColor {
112 RGBAColor(color.0, color.1, color.2, alpha)
113}
114
115fn clip_point(x: f64, y: f64, rect: &Rect) -> (f64, f64) {
117 (
118 x.clamp(rect.x, rect.x + rect.width),
119 y.clamp(rect.y, rect.y + rect.height),
120 )
121}
122
123fn point_in_rect(x: f64, y: f64, rect: &Rect) -> bool {
125 let margin = 2.0;
126 x >= rect.x - margin
127 && x <= rect.x + rect.width + margin
128 && y >= rect.y - margin
129 && y <= rect.y + rect.height + margin
130}
131
132fn clip_polygon(points: &[(f64, f64)], rect: &Rect) -> Vec<(f64, f64)> {
134 let (x0, y0) = (rect.x, rect.y);
135 let (x1, y1) = (rect.x + rect.width, rect.y + rect.height);
136 type Side = (fn(&(f64, f64), f64) -> bool, bool, f64);
138 let sides: [Side; 4] = [
139 (|p, v| p.0 >= v, true, x0),
140 (|p, v| p.0 <= v, true, x1),
141 (|p, v| p.1 >= v, false, y0),
142 (|p, v| p.1 <= v, false, y1),
143 ];
144 let mut out: Vec<(f64, f64)> = points.to_vec();
145 for (inside, vertical, v) in sides {
146 let input = std::mem::take(&mut out);
147 let Some(&last) = input.last() else {
148 break;
149 };
150 let cross = |a: (f64, f64), b: (f64, f64)| {
151 if vertical {
152 let t = (v - a.0) / (b.0 - a.0);
153 (v, a.1 + t * (b.1 - a.1))
154 } else {
155 let t = (v - a.1) / (b.1 - a.1);
156 (a.0 + t * (b.0 - a.0), v)
157 }
158 };
159 let mut prev = last;
160 for &cur in &input {
161 match (inside(&cur, v), inside(&prev, v)) {
162 (true, true) => out.push(cur),
163 (true, false) => {
164 out.push(cross(prev, cur));
165 out.push(cur);
166 }
167 (false, true) => out.push(cross(prev, cur)),
168 (false, false) => {}
169 }
170 prev = cur;
171 }
172 }
173 out
174}
175
176fn clip_line_segment(
178 mut x0: f64,
179 mut y0: f64,
180 mut x1: f64,
181 mut y1: f64,
182 rect: &Rect,
183) -> Option<((f64, f64), (f64, f64))> {
184 let xmin = rect.x;
185 let xmax = rect.x + rect.width;
186 let ymin = rect.y;
187 let ymax = rect.y + rect.height;
188
189 const INSIDE: u8 = 0;
190 const LEFT: u8 = 1;
191 const RIGHT: u8 = 2;
192 const BOTTOM: u8 = 4;
193 const TOP: u8 = 8;
194
195 let outcode = |x: f64, y: f64| -> u8 {
196 let mut code = INSIDE;
197 if x < xmin {
198 code |= LEFT;
199 } else if x > xmax {
200 code |= RIGHT;
201 }
202 if y < ymin {
203 code |= TOP;
204 } else if y > ymax {
205 code |= BOTTOM;
206 }
207 code
208 };
209
210 let mut code0 = outcode(x0, y0);
211 let mut code1 = outcode(x1, y1);
212
213 for _ in 0..20 {
214 if (code0 | code1) == 0 {
215 return Some(((x0, y0), (x1, y1)));
216 }
217 if (code0 & code1) != 0 {
218 return None;
219 }
220
221 let code_out = if code0 != 0 { code0 } else { code1 };
222 let (x, y);
223
224 if code_out & TOP != 0 {
225 x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
226 y = ymin;
227 } else if code_out & BOTTOM != 0 {
228 x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
229 y = ymax;
230 } else if code_out & RIGHT != 0 {
231 y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
232 x = xmax;
233 } else {
234 y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
235 x = xmin;
236 }
237
238 if code_out == code0 {
239 x0 = x;
240 y0 = y;
241 code0 = outcode(x0, y0);
242 } else {
243 x1 = x;
244 y1 = y;
245 code1 = outcode(x1, y1);
246 }
247 }
248
249 None
250}
251
252fn map_err<E: std::fmt::Debug>(e: E) -> RenderError {
253 RenderError::BackendError(format!("{:?}", e))
254}
255
256fn segment_dashed(points: &[(f64, f64)], pattern: &[(f64, f64)]) -> Vec<Vec<(f64, f64)>> {
258 if pattern.is_empty() || points.len() < 2 {
259 return vec![points.to_vec()];
260 }
261
262 let mut segments: Vec<Vec<(f64, f64)>> = Vec::new();
263 let mut current_seg: Vec<(f64, f64)> = Vec::new();
264 let mut drawing = true;
265 let mut pat_idx = 0;
266 let mut remaining_in_pat = pattern[0].0; for window in points.windows(2) {
269 let (x0, y0) = window[0];
270 let (x1, y1) = window[1];
271 let dx = x1 - x0;
272 let dy = y1 - y0;
273 let seg_len = (dx * dx + dy * dy).sqrt();
274 if seg_len < 0.001 {
275 continue;
276 }
277 let ux = dx / seg_len;
278 let uy = dy / seg_len;
279 let mut consumed = 0.0;
280
281 while consumed < seg_len - 0.001 {
282 let available = seg_len - consumed;
283 let step = remaining_in_pat.min(available);
284 let px = x0 + ux * (consumed + step);
285 let py = y0 + uy * (consumed + step);
286
287 if drawing {
288 if current_seg.is_empty() {
289 current_seg.push((x0 + ux * consumed, y0 + uy * consumed));
290 }
291 current_seg.push((px, py));
292 }
293
294 consumed += step;
295 remaining_in_pat -= step;
296
297 if remaining_in_pat < 0.001 {
298 if drawing {
299 if current_seg.len() >= 2 {
300 segments.push(std::mem::take(&mut current_seg));
301 } else {
302 current_seg.clear();
303 }
304 drawing = false;
305 remaining_in_pat = pattern[pat_idx].1; } else {
307 drawing = true;
308 pat_idx = (pat_idx + 1) % pattern.len();
309 remaining_in_pat = pattern[pat_idx].0; }
311 }
312 }
313 }
314
315 if drawing && current_seg.len() >= 2 {
316 segments.push(current_seg);
317 }
318
319 segments
320}
321
322impl<DB: DrawingBackend> PlottersAdapter<'_, DB> {
323 fn clip_area(&self) -> Rect {
325 match (self.clip, self.clips.last()) {
326 (true, Some(r)) => r.clone(),
327 (true, None) => self.plot_area.clone(),
328 (false, _) => self.total_area.clone(),
329 }
330 }
331
332 fn clip_group(&self) -> Option<Rect> {
334 self.clip.then(|| self.clips.last().cloned()).flatten()
335 }
336}
337
338impl<'a, DB: DrawingBackend> DrawBackend for PlottersAdapter<'a, DB> {
339 fn set_clipping(&mut self, on: bool) {
340 self.clip = on;
341 }
342
343 fn begin_clip(&mut self, rect: &Rect) {
344 self.clips.push(rect.clone());
345 }
346
347 fn end_clip(&mut self) {
348 self.clips.pop();
349 }
350
351 fn draw_circle(
352 &mut self,
353 center: (f64, f64),
354 radius: f64,
355 style: &PointStyle,
356 ) -> Result<(), RenderError> {
357 if !point_in_rect(center.0, center.1, &self.clip_area()) {
359 return Ok(());
360 }
361 let color = to_rgba(style.color, style.alpha);
362 if style.filled {
363 self.area
364 .draw(&Circle::new(
365 (center.0 as i32, center.1 as i32),
366 radius as i32,
367 color.filled(),
368 ))
369 .map_err(map_err)?;
370 } else {
371 self.area
372 .draw(&Circle::new(
373 (center.0 as i32, center.1 as i32),
374 radius as i32,
375 color.stroke_width(1),
376 ))
377 .map_err(map_err)?;
378 }
379 Ok(())
380 }
381
382 fn draw_line(&mut self, points: &[(f64, f64)], style: &LineStyle) -> Result<(), RenderError> {
383 if points.len() < 2 {
384 return Ok(());
385 }
386 let (pixel_width, alpha) = if style.width >= 1.0 {
388 (style.width as u32, style.alpha)
389 } else if style.width > 0.0 {
390 (1, style.alpha * style.width)
391 } else {
392 (0, style.alpha)
393 };
394 let color = to_rgba(style.color, alpha);
395 let stroke = color.stroke_width(pixel_width);
396
397 let pattern = style.linetype.dash_pattern();
398 let sub_paths = segment_dashed(points, &pattern);
399
400 for path in &sub_paths {
401 for window in path.windows(2) {
402 if let Some((p1, p2)) = clip_line_segment(
404 window[0].0,
405 window[0].1,
406 window[1].0,
407 window[1].1,
408 &self.clip_area(),
409 ) {
410 self.area
411 .draw(&PathElement::new(
412 vec![(p1.0 as i32, p1.1 as i32), (p2.0 as i32, p2.1 as i32)],
413 stroke,
414 ))
415 .map_err(map_err)?;
416 }
417 }
418 }
419 Ok(())
420 }
421
422 fn draw_rect(
423 &mut self,
424 top_left: (f64, f64),
425 bottom_right: (f64, f64),
426 style: &RectStyle,
427 ) -> Result<(), RenderError> {
428 let (tl, br) = if style.clip {
429 let area = self.clip_area();
431 let clamped_tl = clip_point(top_left.0, top_left.1, &area);
432 let clamped_br = clip_point(bottom_right.0, bottom_right.1, &area);
433
434 if (clamped_tl.0 - clamped_br.0).abs() < 0.5
436 && (clamped_tl.1 - clamped_br.1).abs() < 0.5
437 {
438 if (top_left.0 - bottom_right.0).abs() > 1.0
440 || (top_left.1 - bottom_right.1).abs() > 1.0
441 {
442 return Ok(());
443 }
444 }
445
446 (
447 (clamped_tl.0 as i32, clamped_tl.1 as i32),
448 (clamped_br.0 as i32, clamped_br.1 as i32),
449 )
450 } else {
451 (
453 (top_left.0 as i32, top_left.1 as i32),
454 (bottom_right.0 as i32, bottom_right.1 as i32),
455 )
456 };
457
458 if style.corner_radius > 0.0 {
459 let pts = super::backend::rounded_rect_points(
464 tl.0 as f64,
465 tl.1 as f64,
466 br.0 as f64,
467 br.1 as f64,
468 style.corner_radius,
469 );
470 let int_pts: Vec<(i32, i32)> =
471 pts.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
472 if let Some(fill) = style.fill {
473 let fill_color = to_rgba(fill, style.alpha);
474 self.area
475 .draw(&Polygon::new(int_pts.clone(), fill_color.filled()))
476 .map_err(map_err)?;
477 }
478 if let Some(stroke) = style.stroke {
479 let stroke_color = to_rgba(stroke, style.alpha);
480 let width = if style.stroke_width > 0.0 {
481 (style.stroke_width as u32).max(1)
482 } else {
483 0
484 };
485 let mut loop_pts = int_pts;
486 if let Some(first) = loop_pts.first().copied() {
487 loop_pts.push(first);
488 }
489 self.area
490 .draw(&PathElement::new(
491 loop_pts,
492 stroke_color.stroke_width(width),
493 ))
494 .map_err(map_err)?;
495 }
496 return Ok(());
497 }
498
499 if let Some(fill) = style.fill {
500 let fill_color = to_rgba(fill, style.alpha);
501 self.area
502 .draw(&plotters::prelude::Rectangle::new(
503 [tl, br],
504 fill_color.filled(),
505 ))
506 .map_err(map_err)?;
507 }
508
509 if let Some(stroke) = style.stroke {
510 let stroke_color = to_rgba(stroke, style.alpha);
511 self.area
512 .draw(&plotters::prelude::Rectangle::new(
513 [tl, br],
514 stroke_color.stroke_width(if style.stroke_width > 0.0 {
515 (style.stroke_width as u32).max(1)
516 } else {
517 0
518 }),
519 ))
520 .map_err(map_err)?;
521 }
522
523 Ok(())
524 }
525
526 fn draw_text(
527 &mut self,
528 text: &str,
529 pos: (f64, f64),
530 style: &TextStyle,
531 ) -> Result<(), RenderError> {
532 let color = to_rgba(style.color, 1.0);
533 let family = style.family.as_deref().unwrap_or("sans-serif");
534 ensure_font(family);
535 let font_style = match style.face {
536 crate::render::backend::FontFace::Plain => FontStyle::Normal,
537 crate::render::backend::FontFace::Bold => FontStyle::Bold,
538 crate::render::backend::FontFace::Italic => FontStyle::Italic,
539 };
540 let angle_i = style.angle.round() as i32;
541
542 if let Some(bg) = style.bg_colour {
548 let r = if style.bg_r.is_finite() {
549 style.bg_r
550 } else {
551 0.0
552 };
553 if r > 0.0 {
554 let halo_color = to_rgba(bg, 1.0);
555 for i in 0..8 {
556 let theta = i as f64 * std::f64::consts::FRAC_PI_4;
557 let offset = (pos.0 + r * theta.cos(), pos.1 + r * theta.sin());
558 self.draw_text_pass(
559 text,
560 offset,
561 style.anchor.clone(),
562 family,
563 style.size,
564 font_style,
565 angle_i,
566 halo_color,
567 )?;
568 }
569 }
570 }
571
572 self.draw_text_pass(
573 text,
574 pos,
575 style.anchor.clone(),
576 family,
577 style.size,
578 font_style,
579 angle_i,
580 color,
581 )?;
582 Ok(())
583 }
584
585 fn draw_polygon(
586 &mut self,
587 points: &[(f64, f64)],
588 style: &RectStyle,
589 ) -> Result<(), RenderError> {
590 if points.len() < 3 {
591 return Ok(());
592 }
593 if let Some(rect) = self.clip_group() {
597 let clipped = clip_polygon(points, &rect);
598 if let (Some(fill), true) = (style.fill, clipped.len() >= 3) {
599 let pts: Vec<(i32, i32)> = clipped
600 .iter()
601 .map(|(x, y)| (*x as i32, *y as i32))
602 .collect();
603 self.area
604 .draw(&Polygon::new(pts, to_rgba(fill, style.alpha).filled()))
605 .map_err(map_err)?;
606 }
607 if let (Some(stroke), true) = (style.stroke, style.stroke_width > 0.0) {
608 let width = (style.stroke_width as u32).max(1);
609 let n = points.len();
610 for i in 0..n {
611 let (a, b) = (points[i], points[(i + 1) % n]);
612 if let Some((p, q)) = clip_line_segment(a.0, a.1, b.0, b.1, &rect) {
613 self.area
614 .draw(&PathElement::new(
615 vec![(p.0 as i32, p.1 as i32), (q.0 as i32, q.1 as i32)],
616 to_rgba(stroke, style.alpha).stroke_width(width),
617 ))
618 .map_err(map_err)?;
619 }
620 }
621 }
622 return Ok(());
623 }
624 let int_points: Vec<(i32, i32)> =
625 points.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
626
627 if let Some(fill) = style.fill {
628 let fill_color = to_rgba(fill, style.alpha);
629 self.area
630 .draw(&Polygon::new(int_points.clone(), fill_color.filled()))
631 .map_err(map_err)?;
632 }
633 if let (Some(stroke), true) = (style.stroke, style.stroke_width > 0.0) {
637 let mut pts = int_points;
638 pts.push(pts[0]);
639 let width = (style.stroke_width as u32).max(1);
640 self.area
641 .draw(&PathElement::new(
642 pts,
643 to_rgba(stroke, style.alpha).stroke_width(width),
644 ))
645 .map_err(map_err)?;
646 }
647
648 Ok(())
649 }
650
651 fn draw_polygon_rings(
657 &mut self,
658 rings: &[Vec<(f64, f64)>],
659 style: &RectStyle,
660 ) -> Result<(), RenderError> {
661 if let Some(fill) = style.fill {
662 let color = to_rgba(fill, style.alpha);
663 let (mut y0, mut y1) = (f64::INFINITY, f64::NEG_INFINITY);
664 for ring in rings {
665 if ring.len() < 3 {
666 continue;
667 }
668 for p in ring {
669 y0 = y0.min(p.1);
670 y1 = y1.max(p.1);
671 }
672 }
673 if y0.is_finite() && y1.is_finite() {
674 let (y0, y1) = (y0.floor() as i32, y1.ceil() as i32);
675 for y in y0..=y1 {
676 let yc = y as f64 + 0.5;
677 let mut xs: Vec<f64> = Vec::new();
678 for ring in rings {
679 let n = ring.len();
680 if n < 3 {
681 continue;
682 }
683 for i in 0..n {
684 let (ax, ay) = ring[i];
685 let (bx, by) = ring[(i + 1) % n];
686 if (ay <= yc && by > yc) || (by <= yc && ay > yc) {
687 xs.push(ax + (yc - ay) / (by - ay) * (bx - ax));
688 }
689 }
690 }
691 xs.sort_by(|p, q| p.total_cmp(q));
692 for span in xs.chunks(2) {
693 if let [xa, xb] = span {
694 for x in xa.round() as i32..=xb.round() as i32 {
695 self.area.draw_pixel((x, y), &color).map_err(map_err)?;
696 }
697 }
698 }
699 }
700 }
701 }
702 if let Some(stroke) = style.stroke {
703 let stroke_color = to_rgba(stroke, style.alpha);
704 let width = if style.stroke_width > 0.0 {
705 (style.stroke_width as u32).max(1)
706 } else {
707 0
708 };
709 if width > 0 {
710 for ring in rings {
711 if ring.len() < 3 {
712 continue;
713 }
714 let mut pts: Vec<(i32, i32)> =
715 ring.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
716 pts.push(pts[0]);
717 self.area
718 .draw(&PathElement::new(pts, stroke_color.stroke_width(width)))
719 .map_err(map_err)?;
720 }
721 }
722 }
723 Ok(())
724 }
725
726 fn draw_shape(
727 &mut self,
728 center: (f64, f64),
729 radius: f64,
730 style: &PointStyle,
731 ) -> Result<(), RenderError> {
732 if !point_in_rect(center.0, center.1, &self.clip_area()) {
734 return Ok(());
735 }
736 let color = to_rgba(style.color, style.alpha);
737 let (cx, cy) = (center.0 as i32, center.1 as i32);
738 let r = radius as i32;
739
740 match style.shape {
741 PointShape::Circle => self.draw_circle(center, radius, style),
742 PointShape::Square => {
743 let tl = (cx - r, cy - r);
744 let br = (cx + r, cy + r);
745 if style.filled {
746 self.area
747 .draw(&plotters::prelude::Rectangle::new([tl, br], color.filled()))
748 .map_err(map_err)?;
749 } else {
750 self.area
751 .draw(&plotters::prelude::Rectangle::new(
752 [tl, br],
753 color.stroke_width(1),
754 ))
755 .map_err(map_err)?;
756 }
757 Ok(())
758 }
759 PointShape::Triangle => {
760 let pts = vec![(cx, cy - r), (cx - r, cy + r), (cx + r, cy + r)];
761 if style.filled {
762 self.area
763 .draw(&Polygon::new(pts, color.filled()))
764 .map_err(map_err)?;
765 } else {
766 let outline = vec![
767 (cx, cy - r),
768 (cx - r, cy + r),
769 (cx + r, cy + r),
770 (cx, cy - r),
771 ];
772 self.area
773 .draw(&PathElement::new(outline, color.stroke_width(1)))
774 .map_err(map_err)?;
775 }
776 Ok(())
777 }
778 PointShape::Diamond => {
779 let pts = vec![(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)];
780 if style.filled {
781 self.area
782 .draw(&Polygon::new(pts, color.filled()))
783 .map_err(map_err)?;
784 } else {
785 let outline = vec![
786 (cx, cy - r),
787 (cx + r, cy),
788 (cx, cy + r),
789 (cx - r, cy),
790 (cx, cy - r),
791 ];
792 self.area
793 .draw(&PathElement::new(outline, color.stroke_width(1)))
794 .map_err(map_err)?;
795 }
796 Ok(())
797 }
798 PointShape::Cross => {
799 self.area
801 .draw(&PathElement::new(
802 vec![(cx - r, cy - r), (cx + r, cy + r)],
803 color.stroke_width(1),
804 ))
805 .map_err(map_err)?;
806 self.area
807 .draw(&PathElement::new(
808 vec![(cx - r, cy + r), (cx + r, cy - r)],
809 color.stroke_width(1),
810 ))
811 .map_err(map_err)?;
812 Ok(())
813 }
814 PointShape::Plus => {
815 self.area
817 .draw(&PathElement::new(
818 vec![(cx - r, cy), (cx + r, cy)],
819 color.stroke_width(1),
820 ))
821 .map_err(map_err)?;
822 self.area
823 .draw(&PathElement::new(
824 vec![(cx, cy - r), (cx, cy + r)],
825 color.stroke_width(1),
826 ))
827 .map_err(map_err)?;
828 Ok(())
829 }
830 }
831 }
832
833 fn draw_image(
836 &mut self,
837 bytes: &[u8],
838 rect: &Rect,
839 clip: &Rect,
840 _alpha: f64,
841 ) -> Result<(), RenderError> {
842 let (w, h) = self.area.dim_in_pixel();
843 let Some((pos, img)) = crate::render::raster::decode_visible(bytes, rect, clip, w, h)
844 else {
845 return Ok(());
846 };
847 let rgb = image::DynamicImage::ImageRgba8(img).to_rgb8();
848 let element: plotters::element::BitMapElement<(i32, i32)> =
849 (pos, image::DynamicImage::ImageRgb8(rgb)).into();
850 self.area.draw(&element).map_err(map_err)?;
851 Ok(())
852 }
853
854 fn plot_area(&self) -> Rect {
855 self.plot_area.clone()
856 }
857
858 fn total_area(&self) -> Rect {
859 self.total_area.clone()
860 }
861
862 fn corner_claims(&self) -> Vec<Rect> {
863 self.corner_claims.clone()
864 }
865 fn claim_corner_box(&mut self, rect: Rect) {
866 self.corner_claims.push(rect);
867 }
868 fn clear_corner_claims(&mut self) {
869 self.corner_claims.clear();
870 }
871}
872
873impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
874 #[allow(clippy::too_many_arguments)]
883 fn draw_text_pass(
884 &mut self,
885 text: &str,
886 pos: (f64, f64),
887 anchor: TextAnchor,
888 family: &str,
889 size: f64,
890 font_style: FontStyle,
891 angle_i: i32,
892 color: RGBAColor,
893 ) -> Result<(), RenderError> {
894 let font = (family, size).into_font().style(font_style);
895
896 let pos_adj = match anchor {
897 TextAnchor::Start => plotters::style::text_anchor::Pos::new(
898 plotters::style::text_anchor::HPos::Left,
899 plotters::style::text_anchor::VPos::Center,
900 ),
901 TextAnchor::Middle => plotters::style::text_anchor::Pos::new(
902 plotters::style::text_anchor::HPos::Center,
903 plotters::style::text_anchor::VPos::Center,
904 ),
905 TextAnchor::End => plotters::style::text_anchor::Pos::new(
906 plotters::style::text_anchor::HPos::Right,
907 plotters::style::text_anchor::VPos::Center,
908 ),
909 };
910
911 if angle_i != 0 {
912 let (font_transform, rotated_pos) = match angle_i.rem_euclid(360) {
913 80..=100 => (
914 FontTransform::Rotate90,
915 plotters::style::text_anchor::Pos::new(
916 plotters::style::text_anchor::HPos::Center,
917 plotters::style::text_anchor::VPos::Center,
918 ),
919 ),
920 170..=190 => (
921 FontTransform::Rotate180,
922 plotters::style::text_anchor::Pos::new(
923 plotters::style::text_anchor::HPos::Center,
924 plotters::style::text_anchor::VPos::Center,
925 ),
926 ),
927 _ => (
929 FontTransform::Rotate270,
930 plotters::style::text_anchor::Pos::new(
931 plotters::style::text_anchor::HPos::Right,
932 plotters::style::text_anchor::VPos::Center,
933 ),
934 ),
935 };
936
937 let text_style = plotters::prelude::TextStyle::from((family, size).into_font())
938 .color(&color)
939 .transform(font_transform)
940 .pos(rotated_pos);
941
942 self.area
943 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
944 .map_err(map_err)?;
945 } else {
946 let text_style = plotters::prelude::TextStyle::from(font)
947 .color(&color)
948 .pos(pos_adj);
949 self.area
950 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
951 .map_err(map_err)?;
952 }
953
954 Ok(())
955 }
956}
957
958#[cfg(test)]
959mod clip_polygon_tests {
960 use super::*;
961
962 fn rect() -> Rect {
963 Rect {
964 x: 0.0,
965 y: 0.0,
966 width: 10.0,
967 height: 10.0,
968 }
969 }
970
971 #[test]
973 fn cuts_a_polygon_at_the_rect() {
974 let tri = [(2.0, 2.0), (8.0, 2.0), (5.0, -6.0)];
975 let out = clip_polygon(&tri, &rect());
976 assert!(out.len() >= 3);
977 assert!(out
978 .iter()
979 .all(|&(x, y)| (0.0..=10.0).contains(&x) && (0.0..=10.0).contains(&y)));
980 assert!(out.iter().any(|&(_, y)| y == 0.0), "{out:?}");
981 }
982
983 #[test]
984 fn keeps_an_inside_polygon_and_drops_an_outside_one() {
985 let inside = [(1.0, 1.0), (4.0, 1.0), (4.0, 4.0)];
986 assert_eq!(clip_polygon(&inside, &rect()), inside.to_vec());
987 let outside = [(20.0, 20.0), (30.0, 20.0), (30.0, 30.0)];
988 assert!(clip_polygon(&outside, &rect()).is_empty());
989 }
990}