use crate::error::Error;
use crate::utils::{CvtPoint, cvt_color, cvt_stroke};
use iced_graphics::core::text::Paragraph;
use iced_widget::text::LineHeight;
use iced_widget::{
canvas,
core::{Font, Size, alignment::Vertical, font, text},
text::Alignment,
text::Shaping,
};
use once_cell::unsync::Lazy;
use plotters_backend::{
BackendColor,
BackendCoord,
BackendStyle,
BackendTextStyle,
DrawingBackend,
DrawingErrorKind,
FontFamily,
FontStyle,
text_anchor,
};
use std::collections::HashSet;
pub(crate) struct IcedChartBackend<'a, B> {
frame: &'a mut canvas::Frame,
backend: &'a B,
shaping: Shaping,
}
impl<'a, B> IcedChartBackend<'a, B>
where
B: text::Renderer<Font = Font>,
{
pub fn new(frame: &'a mut canvas::Frame, backend: &'a B, shaping: Shaping) -> Self {
Self {
frame,
backend,
shaping,
}
}
}
impl<B> DrawingBackend for IcedChartBackend<'_, B>
where
B: text::Renderer<Font = Font>,
{
type ErrorType = Error;
fn get_size(&self) -> (u32, u32) {
let Size { width, height } = self.frame.size();
(width as u32, height as u32)
}
fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>> {
Ok(())
}
fn present(&mut self) -> Result<(), DrawingErrorKind<Error>> {
Ok(())
}
#[inline]
fn draw_pixel(
&mut self,
point: BackendCoord,
color: BackendColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if color.alpha == 0.0 {
return Ok(());
}
self.frame
.fill_rectangle(point.cvt_point(), Size::new(1.0, 1.0), cvt_color(&color));
Ok(())
}
#[inline]
fn draw_line<S: BackendStyle>(
&mut self,
from: BackendCoord,
to: BackendCoord,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.color().alpha == 0.0 {
return Ok(());
}
let line = canvas::Path::line(from.cvt_point(), to.cvt_point());
self.frame.stroke(&line, cvt_stroke(style));
Ok(())
}
#[inline]
fn draw_rect<S: BackendStyle>(
&mut self,
upper_left: BackendCoord,
bottom_right: BackendCoord,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.color().alpha == 0.0 {
return Ok(());
}
let height = (bottom_right.1 - upper_left.1) as f32;
let width = (bottom_right.0 - upper_left.0) as f32;
let upper_left = upper_left.cvt_point();
if fill {
self.frame.fill_rectangle(
upper_left,
Size::new(width, height),
cvt_color(&style.color()),
);
} else {
let rect = canvas::Path::rectangle(upper_left, Size::new(width, height));
self.frame.stroke(&rect, cvt_stroke(style));
}
Ok(())
}
#[inline]
fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.color().alpha == 0.0 {
return Ok(());
}
let path = canvas::Path::new(move |builder| {
for (i, point) in path.into_iter().enumerate() {
if i > 0 {
builder.line_to(point.cvt_point());
} else {
builder.move_to(point.cvt_point());
}
}
});
self.frame.stroke(&path, cvt_stroke(style));
Ok(())
}
#[inline]
fn draw_circle<S: BackendStyle>(
&mut self,
center: BackendCoord,
radius: u32,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.color().alpha == 0.0 {
return Ok(());
}
let circle = canvas::Path::circle(center.cvt_point(), radius as f32);
if fill {
self.frame.fill(&circle, cvt_color(&style.color()));
} else {
self.frame.stroke(&circle, cvt_stroke(style));
}
Ok(())
}
#[inline]
fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
vert: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.color().alpha == 0.0 {
return Ok(());
}
let path = canvas::Path::new(move |builder| {
for (i, point) in vert.into_iter().enumerate() {
if i > 0 {
builder.line_to(point.cvt_point());
} else {
builder.move_to(point.cvt_point());
}
}
builder.close();
});
self.frame.fill(&path, cvt_color(&style.color()));
Ok(())
}
#[inline]
fn draw_text<S: BackendTextStyle>(
&mut self,
text: &str,
style: &S,
pos: BackendCoord,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.color().alpha == 0.0 {
return Ok(());
}
let align_x = match style.anchor().h_pos {
text_anchor::HPos::Left => Alignment::Left,
text_anchor::HPos::Right => Alignment::Right,
text_anchor::HPos::Center => Alignment::Center,
};
let align_y = match style.anchor().v_pos {
text_anchor::VPos::Top => Vertical::Top,
text_anchor::VPos::Center => Vertical::Center,
text_anchor::VPos::Bottom => Vertical::Bottom,
};
let font = style_to_font(style);
let pos = pos.cvt_point();
let text = canvas::Text {
content: text.to_owned(),
position: pos,
max_width: f32::INFINITY,
color: cvt_color(&style.color()),
size: (style.size() as f32).into(),
line_height: LineHeight::default(),
font,
align_x,
align_y,
shaping: self.shaping,
};
self.frame.fill_text(text);
Ok(())
}
#[inline]
fn estimate_text_size<S: BackendTextStyle>(
&self,
text: &str,
style: &S,
) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
let font = style_to_font(style);
let bounds = self.frame.size();
let align_x = match style.anchor().h_pos {
text_anchor::HPos::Left => Alignment::Left,
text_anchor::HPos::Right => Alignment::Right,
text_anchor::HPos::Center => Alignment::Center,
};
let align_y = match style.anchor().v_pos {
text_anchor::VPos::Top => Vertical::Top,
text_anchor::VPos::Center => Vertical::Center,
text_anchor::VPos::Bottom => Vertical::Bottom,
};
let p = B::Paragraph::with_text(iced_widget::core::text::Text {
content: text,
bounds,
size: self.backend.default_size(),
line_height: LineHeight::default(),
font,
align_x,
align_y,
shaping: self.shaping,
wrapping: iced_widget::core::text::Wrapping::Word,
});
let size = p.min_bounds();
Ok((size.width as u32, size.height as u32))
}
#[inline]
fn blit_bitmap(
&mut self,
_pos: BackendCoord,
(_iw, _ih): (u32, u32),
_src: &[u8],
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
Ok(())
}
}
#[allow(static_mut_refs)]
fn style_to_font<S: BackendTextStyle>(style: &S) -> Font {
static mut FONTS: Lazy<HashSet<String>> = Lazy::new(HashSet::new);
Font {
family: match style.family() {
FontFamily::Serif => font::Family::Serif,
FontFamily::SansSerif => font::Family::SansSerif,
FontFamily::Monospace => font::Family::Monospace,
FontFamily::Name(s) => {
let s = unsafe {
if !FONTS.contains(s) {
FONTS.insert(String::from(s));
}
FONTS.get(s).unwrap().as_str()
};
font::Family::Name(s)
}
},
weight: match style.style() {
FontStyle::Bold => font::Weight::Bold,
_ => font::Weight::Normal,
},
..Font::DEFAULT
}
}