use crate::style::{Color, FontDesc, FontError, RGBAColor, ShapeStyle};
use std::error::Error;
pub type BackendCoord = (i32, i32);
#[derive(Debug)]
pub enum DrawingErrorKind<E: Error + Send + Sync>
where
FontError: Send + Sync,
{
DrawingError(E),
FontError(FontError),
}
impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e),
DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e),
}
}
}
impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {}
pub trait BackendStyle {
type ColorType: Color;
fn as_color(&self) -> RGBAColor;
fn stroke_width(&self) -> u32 {
1
}
}
impl<T: Color> BackendStyle for T {
type ColorType = T;
fn as_color(&self) -> RGBAColor {
self.to_rgba()
}
}
impl BackendStyle for ShapeStyle {
type ColorType = RGBAColor;
fn as_color(&self) -> RGBAColor {
self.color.clone()
}
fn stroke_width(&self) -> u32 {
self.stroke_width
}
}
pub trait DrawingBackend: Sized {
type ErrorType: Error + Send + Sync;
fn get_size(&self) -> (u32, u32);
fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
fn draw_pixel(
&mut self,
point: BackendCoord,
color: &RGBAColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
fn draw_line<S: BackendStyle>(
&mut self,
from: BackendCoord,
to: BackendCoord,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
super::rasterizer::draw_line(self, from, to, style)
}
fn draw_rect<S: BackendStyle>(
&mut self,
upper_left: BackendCoord,
bottom_right: BackendCoord,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
super::rasterizer::draw_rect(self, upper_left, bottom_right, style, fill)
}
fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if style.as_color().alpha() == 0.0 {
return Ok(());
}
if style.stroke_width() == 1 {
let mut begin: Option<BackendCoord> = None;
for end in path.into_iter() {
if let Some(begin) = begin {
let result = self.draw_line(begin, end, style);
if result.is_err() {
return result;
}
}
begin = Some(end);
}
} else {
let p: Vec<_> = path.into_iter().collect();
let v = super::rasterizer::polygonize(&p[..], style.stroke_width());
return self.fill_polygon(v, &style.as_color());
}
Ok(())
}
fn draw_circle<S: BackendStyle>(
&mut self,
center: BackendCoord,
radius: u32,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
super::rasterizer::draw_circle(self, center, radius, style, fill)
}
fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
vert: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
let vert_buf: Vec<_> = vert.into_iter().collect();
super::rasterizer::fill_polygon(self, &vert_buf[..], style)
}
fn draw_text<'a>(
&mut self,
text: &str,
font: &FontDesc<'a>,
pos: BackendCoord,
color: &RGBAColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if color.alpha() == 0.0 {
return Ok(());
}
match font.draw(text, (pos.0, pos.1), |x, y, v| {
self.draw_pixel((x as i32, y as i32), &color.mix(f64::from(v)))
}) {
Ok(drawing_result) => drawing_result,
Err(font_error) => Err(DrawingErrorKind::FontError(font_error)),
}
}
fn estimate_text_size<'a>(
&self,
text: &str,
font: &FontDesc<'a>,
) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
Ok(font.box_size(text).map_err(DrawingErrorKind::FontError)?)
}
#[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
fn blit_bitmap<'a>(
&mut self,
pos: BackendCoord,
src: &'a image::ImageBuffer<image::Rgb<u8>, &'a [u8]>,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
let (w, h) = self.get_size();
let (iw, ih) = src.dimensions();
for dx in 0..iw {
if pos.0 + dx as i32 >= w as i32 {
break;
}
for dy in 0..ih {
if pos.1 + dy as i32 >= h as i32 {
break;
}
let pixel = src.get_pixel(dx, dy);
let color = crate::style::RGBColor(pixel.0[0], pixel.0[1], pixel.0[2]);
let result =
self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), &color.to_rgba());
if result.is_err() {
return result;
}
}
}
Ok(())
}
}