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 BUNDLED_FONT: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans.ttf");
16const BUNDLED_FONT_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans-Bold.ttf");
17const BUNDLED_FONT_OBLIQUE: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans-Oblique.ttf");
18
19fn registered_families() -> &'static Mutex<HashSet<String>> {
20 static REGISTERED: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
21 REGISTERED.get_or_init(|| Mutex::new(HashSet::new()))
22}
23
24fn ensure_font(family: &str) {
31 let mut set = registered_families().lock().unwrap();
32 if set.insert(family.to_string()) {
33 for (style, bytes) in [
36 (FontStyle::Normal, BUNDLED_FONT),
37 (FontStyle::Bold, BUNDLED_FONT_BOLD),
38 (FontStyle::Italic, BUNDLED_FONT_OBLIQUE),
39 (FontStyle::Oblique, BUNDLED_FONT_OBLIQUE),
40 ] {
41 let _ = plotters::style::register_font(family, style, bytes);
43 }
44 }
45}
46
47pub struct PlottersAdapter<'a, DB: DrawingBackend> {
49 area: &'a DrawingArea<DB, plotters::coord::Shift>,
50 plot_area: Rect,
51 total_area: Rect,
52}
53
54impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
55 pub fn new(area: &'a DrawingArea<DB, plotters::coord::Shift>, plot_area: Rect) -> Self {
56 let (w, h) = area.dim_in_pixel();
57 PlottersAdapter {
58 area,
59 plot_area,
60 total_area: Rect {
61 x: 0.0,
62 y: 0.0,
63 width: w as f64,
64 height: h as f64,
65 },
66 }
67 }
68}
69
70fn to_rgba(color: (u8, u8, u8), alpha: f64) -> RGBAColor {
71 RGBAColor(color.0, color.1, color.2, alpha)
72}
73
74fn clip_point(x: f64, y: f64, rect: &Rect) -> (f64, f64) {
76 (
77 x.clamp(rect.x, rect.x + rect.width),
78 y.clamp(rect.y, rect.y + rect.height),
79 )
80}
81
82fn point_in_rect(x: f64, y: f64, rect: &Rect) -> bool {
84 let margin = 2.0;
85 x >= rect.x - margin
86 && x <= rect.x + rect.width + margin
87 && y >= rect.y - margin
88 && y <= rect.y + rect.height + margin
89}
90
91fn clip_line_segment(
93 mut x0: f64,
94 mut y0: f64,
95 mut x1: f64,
96 mut y1: f64,
97 rect: &Rect,
98) -> Option<((f64, f64), (f64, f64))> {
99 let xmin = rect.x;
100 let xmax = rect.x + rect.width;
101 let ymin = rect.y;
102 let ymax = rect.y + rect.height;
103
104 const INSIDE: u8 = 0;
105 const LEFT: u8 = 1;
106 const RIGHT: u8 = 2;
107 const BOTTOM: u8 = 4;
108 const TOP: u8 = 8;
109
110 let outcode = |x: f64, y: f64| -> u8 {
111 let mut code = INSIDE;
112 if x < xmin {
113 code |= LEFT;
114 } else if x > xmax {
115 code |= RIGHT;
116 }
117 if y < ymin {
118 code |= TOP;
119 } else if y > ymax {
120 code |= BOTTOM;
121 }
122 code
123 };
124
125 let mut code0 = outcode(x0, y0);
126 let mut code1 = outcode(x1, y1);
127
128 for _ in 0..20 {
129 if (code0 | code1) == 0 {
130 return Some(((x0, y0), (x1, y1)));
131 }
132 if (code0 & code1) != 0 {
133 return None;
134 }
135
136 let code_out = if code0 != 0 { code0 } else { code1 };
137 let (x, y);
138
139 if code_out & TOP != 0 {
140 x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
141 y = ymin;
142 } else if code_out & BOTTOM != 0 {
143 x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
144 y = ymax;
145 } else if code_out & RIGHT != 0 {
146 y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
147 x = xmax;
148 } else {
149 y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
150 x = xmin;
151 }
152
153 if code_out == code0 {
154 x0 = x;
155 y0 = y;
156 code0 = outcode(x0, y0);
157 } else {
158 x1 = x;
159 y1 = y;
160 code1 = outcode(x1, y1);
161 }
162 }
163
164 None
165}
166
167fn map_err<E: std::fmt::Debug>(e: E) -> RenderError {
168 RenderError::BackendError(format!("{:?}", e))
169}
170
171fn segment_dashed(points: &[(f64, f64)], pattern: &[(f64, f64)]) -> Vec<Vec<(f64, f64)>> {
173 if pattern.is_empty() || points.len() < 2 {
174 return vec![points.to_vec()];
175 }
176
177 let mut segments: Vec<Vec<(f64, f64)>> = Vec::new();
178 let mut current_seg: Vec<(f64, f64)> = Vec::new();
179 let mut drawing = true;
180 let mut pat_idx = 0;
181 let mut remaining_in_pat = pattern[0].0; for window in points.windows(2) {
184 let (x0, y0) = window[0];
185 let (x1, y1) = window[1];
186 let dx = x1 - x0;
187 let dy = y1 - y0;
188 let seg_len = (dx * dx + dy * dy).sqrt();
189 if seg_len < 0.001 {
190 continue;
191 }
192 let ux = dx / seg_len;
193 let uy = dy / seg_len;
194 let mut consumed = 0.0;
195
196 while consumed < seg_len - 0.001 {
197 let available = seg_len - consumed;
198 let step = remaining_in_pat.min(available);
199 let px = x0 + ux * (consumed + step);
200 let py = y0 + uy * (consumed + step);
201
202 if drawing {
203 if current_seg.is_empty() {
204 current_seg.push((x0 + ux * consumed, y0 + uy * consumed));
205 }
206 current_seg.push((px, py));
207 }
208
209 consumed += step;
210 remaining_in_pat -= step;
211
212 if remaining_in_pat < 0.001 {
213 if drawing {
214 if current_seg.len() >= 2 {
215 segments.push(std::mem::take(&mut current_seg));
216 } else {
217 current_seg.clear();
218 }
219 drawing = false;
220 remaining_in_pat = pattern[pat_idx].1; } else {
222 drawing = true;
223 pat_idx = (pat_idx + 1) % pattern.len();
224 remaining_in_pat = pattern[pat_idx].0; }
226 }
227 }
228 }
229
230 if drawing && current_seg.len() >= 2 {
231 segments.push(current_seg);
232 }
233
234 segments
235}
236
237impl<'a, DB: DrawingBackend> DrawBackend for PlottersAdapter<'a, DB> {
238 fn draw_circle(
239 &mut self,
240 center: (f64, f64),
241 radius: f64,
242 style: &PointStyle,
243 ) -> Result<(), RenderError> {
244 if !point_in_rect(center.0, center.1, &self.plot_area) {
246 return Ok(());
247 }
248 let color = to_rgba(style.color, style.alpha);
249 if style.filled {
250 self.area
251 .draw(&Circle::new(
252 (center.0 as i32, center.1 as i32),
253 radius as i32,
254 color.filled(),
255 ))
256 .map_err(map_err)?;
257 } else {
258 self.area
259 .draw(&Circle::new(
260 (center.0 as i32, center.1 as i32),
261 radius as i32,
262 color.stroke_width(1),
263 ))
264 .map_err(map_err)?;
265 }
266 Ok(())
267 }
268
269 fn draw_line(&mut self, points: &[(f64, f64)], style: &LineStyle) -> Result<(), RenderError> {
270 if points.len() < 2 {
271 return Ok(());
272 }
273 let (pixel_width, alpha) = if style.width >= 1.0 {
275 (style.width as u32, style.alpha)
276 } else if style.width > 0.0 {
277 (1, style.alpha * style.width)
278 } else {
279 (0, style.alpha)
280 };
281 let color = to_rgba(style.color, alpha);
282 let stroke = color.stroke_width(pixel_width);
283
284 let pattern = style.linetype.pattern();
285 let sub_paths = segment_dashed(points, pattern);
286
287 for path in &sub_paths {
288 for window in path.windows(2) {
289 if let Some((p1, p2)) = clip_line_segment(
291 window[0].0,
292 window[0].1,
293 window[1].0,
294 window[1].1,
295 &self.plot_area,
296 ) {
297 self.area
298 .draw(&PathElement::new(
299 vec![(p1.0 as i32, p1.1 as i32), (p2.0 as i32, p2.1 as i32)],
300 stroke,
301 ))
302 .map_err(map_err)?;
303 }
304 }
305 }
306 Ok(())
307 }
308
309 fn draw_rect(
310 &mut self,
311 top_left: (f64, f64),
312 bottom_right: (f64, f64),
313 style: &RectStyle,
314 ) -> Result<(), RenderError> {
315 let (tl, br) = if style.clip {
316 let clamped_tl = clip_point(top_left.0, top_left.1, &self.plot_area);
318 let clamped_br = clip_point(bottom_right.0, bottom_right.1, &self.plot_area);
319
320 if (clamped_tl.0 - clamped_br.0).abs() < 0.5
322 && (clamped_tl.1 - clamped_br.1).abs() < 0.5
323 {
324 if (top_left.0 - bottom_right.0).abs() > 1.0
326 || (top_left.1 - bottom_right.1).abs() > 1.0
327 {
328 return Ok(());
329 }
330 }
331
332 (
333 (clamped_tl.0 as i32, clamped_tl.1 as i32),
334 (clamped_br.0 as i32, clamped_br.1 as i32),
335 )
336 } else {
337 (
339 (top_left.0 as i32, top_left.1 as i32),
340 (bottom_right.0 as i32, bottom_right.1 as i32),
341 )
342 };
343
344 if let Some(fill) = style.fill {
345 let fill_color = to_rgba(fill, style.alpha);
346 self.area
347 .draw(&plotters::prelude::Rectangle::new(
348 [tl, br],
349 fill_color.filled(),
350 ))
351 .map_err(map_err)?;
352 }
353
354 if let Some(stroke) = style.stroke {
355 let stroke_color = to_rgba(stroke, style.alpha);
356 self.area
357 .draw(&plotters::prelude::Rectangle::new(
358 [tl, br],
359 stroke_color.stroke_width(if style.stroke_width > 0.0 {
360 (style.stroke_width as u32).max(1)
361 } else {
362 0
363 }),
364 ))
365 .map_err(map_err)?;
366 }
367
368 Ok(())
369 }
370
371 fn draw_text(
372 &mut self,
373 text: &str,
374 pos: (f64, f64),
375 style: &TextStyle,
376 ) -> Result<(), RenderError> {
377 let color = to_rgba(style.color, 1.0);
378 let family = style.family.as_deref().unwrap_or("sans-serif");
379 ensure_font(family);
380 let font_style = match style.face {
381 crate::render::backend::FontFace::Plain => FontStyle::Normal,
382 crate::render::backend::FontFace::Bold => FontStyle::Bold,
383 crate::render::backend::FontFace::Italic => FontStyle::Italic,
384 };
385 let font = (family, style.size).into_font().style(font_style);
386
387 let pos_adj = match style.anchor {
388 TextAnchor::Start => plotters::style::text_anchor::Pos::new(
389 plotters::style::text_anchor::HPos::Left,
390 plotters::style::text_anchor::VPos::Center,
391 ),
392 TextAnchor::Middle => plotters::style::text_anchor::Pos::new(
393 plotters::style::text_anchor::HPos::Center,
394 plotters::style::text_anchor::VPos::Center,
395 ),
396 TextAnchor::End => plotters::style::text_anchor::Pos::new(
397 plotters::style::text_anchor::HPos::Right,
398 plotters::style::text_anchor::VPos::Center,
399 ),
400 };
401
402 let angle_i = style.angle.round() as i32;
403 if angle_i != 0 {
404 let (font_transform, rotated_pos) = match angle_i.rem_euclid(360) {
405 80..=100 => (
406 FontTransform::Rotate90,
407 plotters::style::text_anchor::Pos::new(
408 plotters::style::text_anchor::HPos::Center,
409 plotters::style::text_anchor::VPos::Center,
410 ),
411 ),
412 170..=190 => (
413 FontTransform::Rotate180,
414 plotters::style::text_anchor::Pos::new(
415 plotters::style::text_anchor::HPos::Center,
416 plotters::style::text_anchor::VPos::Center,
417 ),
418 ),
419 _ => (
421 FontTransform::Rotate270,
422 plotters::style::text_anchor::Pos::new(
423 plotters::style::text_anchor::HPos::Right,
424 plotters::style::text_anchor::VPos::Center,
425 ),
426 ),
427 };
428
429 let text_style = plotters::prelude::TextStyle::from((family, style.size).into_font())
430 .color(&color)
431 .transform(font_transform)
432 .pos(rotated_pos);
433
434 self.area
435 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
436 .map_err(map_err)?;
437 } else {
438 let text_style = plotters::prelude::TextStyle::from(font)
439 .color(&color)
440 .pos(pos_adj);
441 self.area
442 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
443 .map_err(map_err)?;
444 }
445
446 Ok(())
447 }
448
449 fn draw_polygon(
450 &mut self,
451 points: &[(f64, f64)],
452 style: &RectStyle,
453 ) -> Result<(), RenderError> {
454 if points.len() < 3 {
455 return Ok(());
456 }
457 let int_points: Vec<(i32, i32)> =
458 points.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
459
460 if let Some(fill) = style.fill {
461 let fill_color = to_rgba(fill, style.alpha);
462 self.area
463 .draw(&Polygon::new(int_points.clone(), fill_color.filled()))
464 .map_err(map_err)?;
465 }
466
467 Ok(())
468 }
469
470 fn draw_shape(
471 &mut self,
472 center: (f64, f64),
473 radius: f64,
474 style: &PointStyle,
475 ) -> Result<(), RenderError> {
476 if !point_in_rect(center.0, center.1, &self.plot_area) {
478 return Ok(());
479 }
480 let color = to_rgba(style.color, style.alpha);
481 let (cx, cy) = (center.0 as i32, center.1 as i32);
482 let r = radius as i32;
483
484 match style.shape {
485 PointShape::Circle => self.draw_circle(center, radius, style),
486 PointShape::Square => {
487 let tl = (cx - r, cy - r);
488 let br = (cx + r, cy + r);
489 if style.filled {
490 self.area
491 .draw(&plotters::prelude::Rectangle::new([tl, br], color.filled()))
492 .map_err(map_err)?;
493 } else {
494 self.area
495 .draw(&plotters::prelude::Rectangle::new(
496 [tl, br],
497 color.stroke_width(1),
498 ))
499 .map_err(map_err)?;
500 }
501 Ok(())
502 }
503 PointShape::Triangle => {
504 let pts = vec![(cx, cy - r), (cx - r, cy + r), (cx + r, cy + r)];
505 if style.filled {
506 self.area
507 .draw(&Polygon::new(pts, color.filled()))
508 .map_err(map_err)?;
509 } else {
510 let outline = vec![
511 (cx, cy - r),
512 (cx - r, cy + r),
513 (cx + r, cy + r),
514 (cx, cy - r),
515 ];
516 self.area
517 .draw(&PathElement::new(outline, color.stroke_width(1)))
518 .map_err(map_err)?;
519 }
520 Ok(())
521 }
522 PointShape::Diamond => {
523 let pts = vec![(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)];
524 if style.filled {
525 self.area
526 .draw(&Polygon::new(pts, color.filled()))
527 .map_err(map_err)?;
528 } else {
529 let outline = vec![
530 (cx, cy - r),
531 (cx + r, cy),
532 (cx, cy + r),
533 (cx - r, cy),
534 (cx, cy - r),
535 ];
536 self.area
537 .draw(&PathElement::new(outline, color.stroke_width(1)))
538 .map_err(map_err)?;
539 }
540 Ok(())
541 }
542 PointShape::Cross => {
543 self.area
545 .draw(&PathElement::new(
546 vec![(cx - r, cy - r), (cx + r, cy + r)],
547 color.stroke_width(1),
548 ))
549 .map_err(map_err)?;
550 self.area
551 .draw(&PathElement::new(
552 vec![(cx - r, cy + r), (cx + r, cy - r)],
553 color.stroke_width(1),
554 ))
555 .map_err(map_err)?;
556 Ok(())
557 }
558 PointShape::Plus => {
559 self.area
561 .draw(&PathElement::new(
562 vec![(cx - r, cy), (cx + r, cy)],
563 color.stroke_width(1),
564 ))
565 .map_err(map_err)?;
566 self.area
567 .draw(&PathElement::new(
568 vec![(cx, cy - r), (cx, cy + r)],
569 color.stroke_width(1),
570 ))
571 .map_err(map_err)?;
572 Ok(())
573 }
574 }
575 }
576
577 fn plot_area(&self) -> Rect {
578 self.plot_area.clone()
579 }
580
581 fn total_area(&self) -> Rect {
582 self.total_area.clone()
583 }
584}