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}
82
83impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
84 pub fn new(area: &'a DrawingArea<DB, plotters::coord::Shift>, plot_area: Rect) -> Self {
85 let (w, h) = area.dim_in_pixel();
86 PlottersAdapter {
87 area,
88 plot_area,
89 total_area: Rect {
90 x: 0.0,
91 y: 0.0,
92 width: w as f64,
93 height: h as f64,
94 },
95 }
96 }
97}
98
99fn to_rgba(color: (u8, u8, u8), alpha: f64) -> RGBAColor {
100 RGBAColor(color.0, color.1, color.2, alpha)
101}
102
103fn clip_point(x: f64, y: f64, rect: &Rect) -> (f64, f64) {
105 (
106 x.clamp(rect.x, rect.x + rect.width),
107 y.clamp(rect.y, rect.y + rect.height),
108 )
109}
110
111fn point_in_rect(x: f64, y: f64, rect: &Rect) -> bool {
113 let margin = 2.0;
114 x >= rect.x - margin
115 && x <= rect.x + rect.width + margin
116 && y >= rect.y - margin
117 && y <= rect.y + rect.height + margin
118}
119
120fn clip_line_segment(
122 mut x0: f64,
123 mut y0: f64,
124 mut x1: f64,
125 mut y1: f64,
126 rect: &Rect,
127) -> Option<((f64, f64), (f64, f64))> {
128 let xmin = rect.x;
129 let xmax = rect.x + rect.width;
130 let ymin = rect.y;
131 let ymax = rect.y + rect.height;
132
133 const INSIDE: u8 = 0;
134 const LEFT: u8 = 1;
135 const RIGHT: u8 = 2;
136 const BOTTOM: u8 = 4;
137 const TOP: u8 = 8;
138
139 let outcode = |x: f64, y: f64| -> u8 {
140 let mut code = INSIDE;
141 if x < xmin {
142 code |= LEFT;
143 } else if x > xmax {
144 code |= RIGHT;
145 }
146 if y < ymin {
147 code |= TOP;
148 } else if y > ymax {
149 code |= BOTTOM;
150 }
151 code
152 };
153
154 let mut code0 = outcode(x0, y0);
155 let mut code1 = outcode(x1, y1);
156
157 for _ in 0..20 {
158 if (code0 | code1) == 0 {
159 return Some(((x0, y0), (x1, y1)));
160 }
161 if (code0 & code1) != 0 {
162 return None;
163 }
164
165 let code_out = if code0 != 0 { code0 } else { code1 };
166 let (x, y);
167
168 if code_out & TOP != 0 {
169 x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
170 y = ymin;
171 } else if code_out & BOTTOM != 0 {
172 x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
173 y = ymax;
174 } else if code_out & RIGHT != 0 {
175 y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
176 x = xmax;
177 } else {
178 y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
179 x = xmin;
180 }
181
182 if code_out == code0 {
183 x0 = x;
184 y0 = y;
185 code0 = outcode(x0, y0);
186 } else {
187 x1 = x;
188 y1 = y;
189 code1 = outcode(x1, y1);
190 }
191 }
192
193 None
194}
195
196fn map_err<E: std::fmt::Debug>(e: E) -> RenderError {
197 RenderError::BackendError(format!("{:?}", e))
198}
199
200fn segment_dashed(points: &[(f64, f64)], pattern: &[(f64, f64)]) -> Vec<Vec<(f64, f64)>> {
202 if pattern.is_empty() || points.len() < 2 {
203 return vec![points.to_vec()];
204 }
205
206 let mut segments: Vec<Vec<(f64, f64)>> = Vec::new();
207 let mut current_seg: Vec<(f64, f64)> = Vec::new();
208 let mut drawing = true;
209 let mut pat_idx = 0;
210 let mut remaining_in_pat = pattern[0].0; for window in points.windows(2) {
213 let (x0, y0) = window[0];
214 let (x1, y1) = window[1];
215 let dx = x1 - x0;
216 let dy = y1 - y0;
217 let seg_len = (dx * dx + dy * dy).sqrt();
218 if seg_len < 0.001 {
219 continue;
220 }
221 let ux = dx / seg_len;
222 let uy = dy / seg_len;
223 let mut consumed = 0.0;
224
225 while consumed < seg_len - 0.001 {
226 let available = seg_len - consumed;
227 let step = remaining_in_pat.min(available);
228 let px = x0 + ux * (consumed + step);
229 let py = y0 + uy * (consumed + step);
230
231 if drawing {
232 if current_seg.is_empty() {
233 current_seg.push((x0 + ux * consumed, y0 + uy * consumed));
234 }
235 current_seg.push((px, py));
236 }
237
238 consumed += step;
239 remaining_in_pat -= step;
240
241 if remaining_in_pat < 0.001 {
242 if drawing {
243 if current_seg.len() >= 2 {
244 segments.push(std::mem::take(&mut current_seg));
245 } else {
246 current_seg.clear();
247 }
248 drawing = false;
249 remaining_in_pat = pattern[pat_idx].1; } else {
251 drawing = true;
252 pat_idx = (pat_idx + 1) % pattern.len();
253 remaining_in_pat = pattern[pat_idx].0; }
255 }
256 }
257 }
258
259 if drawing && current_seg.len() >= 2 {
260 segments.push(current_seg);
261 }
262
263 segments
264}
265
266impl<'a, DB: DrawingBackend> DrawBackend for PlottersAdapter<'a, DB> {
267 fn draw_circle(
268 &mut self,
269 center: (f64, f64),
270 radius: f64,
271 style: &PointStyle,
272 ) -> Result<(), RenderError> {
273 if !point_in_rect(center.0, center.1, &self.plot_area) {
275 return Ok(());
276 }
277 let color = to_rgba(style.color, style.alpha);
278 if style.filled {
279 self.area
280 .draw(&Circle::new(
281 (center.0 as i32, center.1 as i32),
282 radius as i32,
283 color.filled(),
284 ))
285 .map_err(map_err)?;
286 } else {
287 self.area
288 .draw(&Circle::new(
289 (center.0 as i32, center.1 as i32),
290 radius as i32,
291 color.stroke_width(1),
292 ))
293 .map_err(map_err)?;
294 }
295 Ok(())
296 }
297
298 fn draw_line(&mut self, points: &[(f64, f64)], style: &LineStyle) -> Result<(), RenderError> {
299 if points.len() < 2 {
300 return Ok(());
301 }
302 let (pixel_width, alpha) = if style.width >= 1.0 {
304 (style.width as u32, style.alpha)
305 } else if style.width > 0.0 {
306 (1, style.alpha * style.width)
307 } else {
308 (0, style.alpha)
309 };
310 let color = to_rgba(style.color, alpha);
311 let stroke = color.stroke_width(pixel_width);
312
313 let pattern = style.linetype.dash_pattern();
314 let sub_paths = segment_dashed(points, &pattern);
315
316 for path in &sub_paths {
317 for window in path.windows(2) {
318 if let Some((p1, p2)) = clip_line_segment(
320 window[0].0,
321 window[0].1,
322 window[1].0,
323 window[1].1,
324 &self.plot_area,
325 ) {
326 self.area
327 .draw(&PathElement::new(
328 vec![(p1.0 as i32, p1.1 as i32), (p2.0 as i32, p2.1 as i32)],
329 stroke,
330 ))
331 .map_err(map_err)?;
332 }
333 }
334 }
335 Ok(())
336 }
337
338 fn draw_rect(
339 &mut self,
340 top_left: (f64, f64),
341 bottom_right: (f64, f64),
342 style: &RectStyle,
343 ) -> Result<(), RenderError> {
344 let (tl, br) = if style.clip {
345 let clamped_tl = clip_point(top_left.0, top_left.1, &self.plot_area);
347 let clamped_br = clip_point(bottom_right.0, bottom_right.1, &self.plot_area);
348
349 if (clamped_tl.0 - clamped_br.0).abs() < 0.5
351 && (clamped_tl.1 - clamped_br.1).abs() < 0.5
352 {
353 if (top_left.0 - bottom_right.0).abs() > 1.0
355 || (top_left.1 - bottom_right.1).abs() > 1.0
356 {
357 return Ok(());
358 }
359 }
360
361 (
362 (clamped_tl.0 as i32, clamped_tl.1 as i32),
363 (clamped_br.0 as i32, clamped_br.1 as i32),
364 )
365 } else {
366 (
368 (top_left.0 as i32, top_left.1 as i32),
369 (bottom_right.0 as i32, bottom_right.1 as i32),
370 )
371 };
372
373 if style.corner_radius > 0.0 {
374 let pts = super::backend::rounded_rect_points(
379 tl.0 as f64,
380 tl.1 as f64,
381 br.0 as f64,
382 br.1 as f64,
383 style.corner_radius,
384 );
385 let int_pts: Vec<(i32, i32)> =
386 pts.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
387 if let Some(fill) = style.fill {
388 let fill_color = to_rgba(fill, style.alpha);
389 self.area
390 .draw(&Polygon::new(int_pts.clone(), fill_color.filled()))
391 .map_err(map_err)?;
392 }
393 if let Some(stroke) = style.stroke {
394 let stroke_color = to_rgba(stroke, style.alpha);
395 let width = if style.stroke_width > 0.0 {
396 (style.stroke_width as u32).max(1)
397 } else {
398 0
399 };
400 let mut loop_pts = int_pts;
401 if let Some(first) = loop_pts.first().copied() {
402 loop_pts.push(first);
403 }
404 self.area
405 .draw(&PathElement::new(
406 loop_pts,
407 stroke_color.stroke_width(width),
408 ))
409 .map_err(map_err)?;
410 }
411 return Ok(());
412 }
413
414 if let Some(fill) = style.fill {
415 let fill_color = to_rgba(fill, style.alpha);
416 self.area
417 .draw(&plotters::prelude::Rectangle::new(
418 [tl, br],
419 fill_color.filled(),
420 ))
421 .map_err(map_err)?;
422 }
423
424 if let Some(stroke) = style.stroke {
425 let stroke_color = to_rgba(stroke, style.alpha);
426 self.area
427 .draw(&plotters::prelude::Rectangle::new(
428 [tl, br],
429 stroke_color.stroke_width(if style.stroke_width > 0.0 {
430 (style.stroke_width as u32).max(1)
431 } else {
432 0
433 }),
434 ))
435 .map_err(map_err)?;
436 }
437
438 Ok(())
439 }
440
441 fn draw_text(
442 &mut self,
443 text: &str,
444 pos: (f64, f64),
445 style: &TextStyle,
446 ) -> Result<(), RenderError> {
447 let color = to_rgba(style.color, 1.0);
448 let family = style.family.as_deref().unwrap_or("sans-serif");
449 ensure_font(family);
450 let font_style = match style.face {
451 crate::render::backend::FontFace::Plain => FontStyle::Normal,
452 crate::render::backend::FontFace::Bold => FontStyle::Bold,
453 crate::render::backend::FontFace::Italic => FontStyle::Italic,
454 };
455 let angle_i = style.angle.round() as i32;
456
457 if let Some(bg) = style.bg_colour {
463 let r = if style.bg_r.is_finite() {
464 style.bg_r
465 } else {
466 0.0
467 };
468 if r > 0.0 {
469 let halo_color = to_rgba(bg, 1.0);
470 for i in 0..8 {
471 let theta = i as f64 * std::f64::consts::FRAC_PI_4;
472 let offset = (pos.0 + r * theta.cos(), pos.1 + r * theta.sin());
473 self.draw_text_pass(
474 text,
475 offset,
476 style.anchor.clone(),
477 family,
478 style.size,
479 font_style,
480 angle_i,
481 halo_color,
482 )?;
483 }
484 }
485 }
486
487 self.draw_text_pass(
488 text,
489 pos,
490 style.anchor.clone(),
491 family,
492 style.size,
493 font_style,
494 angle_i,
495 color,
496 )?;
497 Ok(())
498 }
499
500 fn draw_polygon(
501 &mut self,
502 points: &[(f64, f64)],
503 style: &RectStyle,
504 ) -> Result<(), RenderError> {
505 if points.len() < 3 {
506 return Ok(());
507 }
508 let int_points: Vec<(i32, i32)> =
509 points.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
510
511 if let Some(fill) = style.fill {
512 let fill_color = to_rgba(fill, style.alpha);
513 self.area
514 .draw(&Polygon::new(int_points.clone(), fill_color.filled()))
515 .map_err(map_err)?;
516 }
517
518 Ok(())
519 }
520
521 fn draw_polygon_rings(
527 &mut self,
528 rings: &[Vec<(f64, f64)>],
529 style: &RectStyle,
530 ) -> Result<(), RenderError> {
531 if let Some(fill) = style.fill {
532 let color = to_rgba(fill, style.alpha);
533 let (mut y0, mut y1) = (f64::INFINITY, f64::NEG_INFINITY);
534 for ring in rings {
535 if ring.len() < 3 {
536 continue;
537 }
538 for p in ring {
539 y0 = y0.min(p.1);
540 y1 = y1.max(p.1);
541 }
542 }
543 if y0.is_finite() && y1.is_finite() {
544 let (y0, y1) = (y0.floor() as i32, y1.ceil() as i32);
545 for y in y0..=y1 {
546 let yc = y as f64 + 0.5;
547 let mut xs: Vec<f64> = Vec::new();
548 for ring in rings {
549 let n = ring.len();
550 if n < 3 {
551 continue;
552 }
553 for i in 0..n {
554 let (ax, ay) = ring[i];
555 let (bx, by) = ring[(i + 1) % n];
556 if (ay <= yc && by > yc) || (by <= yc && ay > yc) {
557 xs.push(ax + (yc - ay) / (by - ay) * (bx - ax));
558 }
559 }
560 }
561 xs.sort_by(|p, q| p.total_cmp(q));
562 for span in xs.chunks(2) {
563 if let [xa, xb] = span {
564 for x in xa.round() as i32..=xb.round() as i32 {
565 self.area.draw_pixel((x, y), &color).map_err(map_err)?;
566 }
567 }
568 }
569 }
570 }
571 }
572 if let Some(stroke) = style.stroke {
573 let stroke_color = to_rgba(stroke, style.alpha);
574 let width = if style.stroke_width > 0.0 {
575 (style.stroke_width as u32).max(1)
576 } else {
577 0
578 };
579 if width > 0 {
580 for ring in rings {
581 if ring.len() < 3 {
582 continue;
583 }
584 let mut pts: Vec<(i32, i32)> =
585 ring.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
586 pts.push(pts[0]);
587 self.area
588 .draw(&PathElement::new(pts, stroke_color.stroke_width(width)))
589 .map_err(map_err)?;
590 }
591 }
592 }
593 Ok(())
594 }
595
596 fn draw_shape(
597 &mut self,
598 center: (f64, f64),
599 radius: f64,
600 style: &PointStyle,
601 ) -> Result<(), RenderError> {
602 if !point_in_rect(center.0, center.1, &self.plot_area) {
604 return Ok(());
605 }
606 let color = to_rgba(style.color, style.alpha);
607 let (cx, cy) = (center.0 as i32, center.1 as i32);
608 let r = radius as i32;
609
610 match style.shape {
611 PointShape::Circle => self.draw_circle(center, radius, style),
612 PointShape::Square => {
613 let tl = (cx - r, cy - r);
614 let br = (cx + r, cy + r);
615 if style.filled {
616 self.area
617 .draw(&plotters::prelude::Rectangle::new([tl, br], color.filled()))
618 .map_err(map_err)?;
619 } else {
620 self.area
621 .draw(&plotters::prelude::Rectangle::new(
622 [tl, br],
623 color.stroke_width(1),
624 ))
625 .map_err(map_err)?;
626 }
627 Ok(())
628 }
629 PointShape::Triangle => {
630 let pts = vec![(cx, cy - r), (cx - r, cy + r), (cx + r, cy + r)];
631 if style.filled {
632 self.area
633 .draw(&Polygon::new(pts, color.filled()))
634 .map_err(map_err)?;
635 } else {
636 let outline = vec![
637 (cx, cy - r),
638 (cx - r, cy + r),
639 (cx + r, cy + r),
640 (cx, cy - r),
641 ];
642 self.area
643 .draw(&PathElement::new(outline, color.stroke_width(1)))
644 .map_err(map_err)?;
645 }
646 Ok(())
647 }
648 PointShape::Diamond => {
649 let pts = vec![(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)];
650 if style.filled {
651 self.area
652 .draw(&Polygon::new(pts, color.filled()))
653 .map_err(map_err)?;
654 } else {
655 let outline = vec![
656 (cx, cy - r),
657 (cx + r, cy),
658 (cx, cy + r),
659 (cx - r, cy),
660 (cx, cy - r),
661 ];
662 self.area
663 .draw(&PathElement::new(outline, color.stroke_width(1)))
664 .map_err(map_err)?;
665 }
666 Ok(())
667 }
668 PointShape::Cross => {
669 self.area
671 .draw(&PathElement::new(
672 vec![(cx - r, cy - r), (cx + r, cy + r)],
673 color.stroke_width(1),
674 ))
675 .map_err(map_err)?;
676 self.area
677 .draw(&PathElement::new(
678 vec![(cx - r, cy + r), (cx + r, cy - r)],
679 color.stroke_width(1),
680 ))
681 .map_err(map_err)?;
682 Ok(())
683 }
684 PointShape::Plus => {
685 self.area
687 .draw(&PathElement::new(
688 vec![(cx - r, cy), (cx + r, cy)],
689 color.stroke_width(1),
690 ))
691 .map_err(map_err)?;
692 self.area
693 .draw(&PathElement::new(
694 vec![(cx, cy - r), (cx, cy + r)],
695 color.stroke_width(1),
696 ))
697 .map_err(map_err)?;
698 Ok(())
699 }
700 }
701 }
702
703 fn plot_area(&self) -> Rect {
704 self.plot_area.clone()
705 }
706
707 fn total_area(&self) -> Rect {
708 self.total_area.clone()
709 }
710}
711
712impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
713 #[allow(clippy::too_many_arguments)]
722 fn draw_text_pass(
723 &mut self,
724 text: &str,
725 pos: (f64, f64),
726 anchor: TextAnchor,
727 family: &str,
728 size: f64,
729 font_style: FontStyle,
730 angle_i: i32,
731 color: RGBAColor,
732 ) -> Result<(), RenderError> {
733 let font = (family, size).into_font().style(font_style);
734
735 let pos_adj = match anchor {
736 TextAnchor::Start => plotters::style::text_anchor::Pos::new(
737 plotters::style::text_anchor::HPos::Left,
738 plotters::style::text_anchor::VPos::Center,
739 ),
740 TextAnchor::Middle => plotters::style::text_anchor::Pos::new(
741 plotters::style::text_anchor::HPos::Center,
742 plotters::style::text_anchor::VPos::Center,
743 ),
744 TextAnchor::End => plotters::style::text_anchor::Pos::new(
745 plotters::style::text_anchor::HPos::Right,
746 plotters::style::text_anchor::VPos::Center,
747 ),
748 };
749
750 if angle_i != 0 {
751 let (font_transform, rotated_pos) = match angle_i.rem_euclid(360) {
752 80..=100 => (
753 FontTransform::Rotate90,
754 plotters::style::text_anchor::Pos::new(
755 plotters::style::text_anchor::HPos::Center,
756 plotters::style::text_anchor::VPos::Center,
757 ),
758 ),
759 170..=190 => (
760 FontTransform::Rotate180,
761 plotters::style::text_anchor::Pos::new(
762 plotters::style::text_anchor::HPos::Center,
763 plotters::style::text_anchor::VPos::Center,
764 ),
765 ),
766 _ => (
768 FontTransform::Rotate270,
769 plotters::style::text_anchor::Pos::new(
770 plotters::style::text_anchor::HPos::Right,
771 plotters::style::text_anchor::VPos::Center,
772 ),
773 ),
774 };
775
776 let text_style = plotters::prelude::TextStyle::from((family, size).into_font())
777 .color(&color)
778 .transform(font_transform)
779 .pos(rotated_pos);
780
781 self.area
782 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
783 .map_err(map_err)?;
784 } else {
785 let text_style = plotters::prelude::TextStyle::from(font)
786 .color(&color)
787 .pos(pos_adj);
788 self.area
789 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
790 .map_err(map_err)?;
791 }
792
793 Ok(())
794 }
795}