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