use std::fmt;
use std::marker::PhantomData;
use std::path::Path;
#[cfg(feature = "png")]
use std::fs::File;
#[cfg(feature = "png")]
use std::io::BufWriter;
#[cfg(feature = "png")]
use png::{ColorType, Encoder};
use wasm_bindgen::JsCast;
use piet::{ImageBuf, ImageFormat};
#[doc(hidden)]
pub use piet_web::*;
pub type Piet<'a> = WebRenderContext<'a>;
pub type Brush = piet_web::Brush;
pub type PietText = WebText;
pub type PietTextLayout = WebTextLayout;
pub type PietTextLayoutBuilder = WebTextLayoutBuilder;
pub type PietImage = WebImage;
pub struct Device {
marker: std::marker::PhantomData<*const ()>,
}
unsafe impl Send for Device {}
pub struct BitmapTarget<'a> {
canvas: web_sys::HtmlCanvasElement,
context: web_sys::CanvasRenderingContext2d,
phantom: PhantomData<&'a ()>,
}
impl Device {
pub fn new() -> Result<Device, piet::Error> {
Ok(Device {
marker: std::marker::PhantomData,
})
}
pub fn bitmap_target(
&mut self,
width: usize,
height: usize,
pix_scale: f64,
) -> Result<BitmapTarget, piet::Error> {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document
.create_element("canvas")
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
let context = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
canvas.set_width(width as u32);
canvas.set_height(height as u32);
let _ = context.scale(pix_scale, pix_scale);
Ok(BitmapTarget {
canvas,
context,
phantom: Default::default(),
})
}
}
impl<'a> BitmapTarget<'a> {
pub fn render_context(&mut self) -> WebRenderContext {
WebRenderContext::new(self.context.clone(), web_sys::window().unwrap())
}
fn raw_pixels(&mut self, fmt: ImageFormat) -> Result<Vec<u8>, piet::Error> {
if fmt != ImageFormat::RgbaPremul {
return Err(piet::Error::NotSupported);
}
let width = self.canvas.width() as usize;
let height = self.canvas.height() as usize;
let img_data = self
.context
.get_image_data(0.0, 0.0, width as f64, height as f64)
.map_err(|jsv| piet::Error::BackendError(Box::new(JsError::new(jsv))))?;
Ok(img_data.data().0)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_image_buf(&mut self, fmt: ImageFormat) -> Result<ImageBuf, piet::Error> {
let data = self.raw_pixels(fmt)?;
let width = self.canvas.width() as usize;
let height = self.canvas.height() as usize;
Ok(ImageBuf::from_raw(data, fmt, width, height))
}
pub fn copy_raw_pixels(
&mut self,
fmt: ImageFormat,
buf: &mut [u8],
) -> Result<usize, piet::Error> {
let data = self.raw_pixels(fmt)?;
if data.len() > buf.len() {
return Err(piet::Error::InvalidInput);
}
buf.copy_from_slice(&data[..]);
Ok(data.len())
}
#[cfg(feature = "png")]
pub fn save_to_file<P: AsRef<Path>>(mut self, path: P) -> Result<(), piet::Error> {
let height = self.canvas.height();
let width = self.canvas.width();
let image = self.raw_pixels(ImageFormat::RgbaPremul)?;
let file = BufWriter::new(File::create(path).map_err(Into::<Box<_>>::into)?);
let mut encoder = Encoder::new(file, width, height);
encoder.set_color(ColorType::Rgba);
encoder
.write_header()
.map_err(Into::<Box<_>>::into)?
.write_image_data(&image)
.map_err(Into::<Box<_>>::into)?;
Ok(())
}
#[cfg(not(feature = "png"))]
pub fn save_to_file<P: AsRef<Path>>(self, _path: P) -> Result<(), piet::Error> {
Err(piet::Error::MissingFeature("png"))
}
}
#[derive(Clone, Debug)]
struct JsError {
jsv: wasm_bindgen::JsValue,
}
impl JsError {
fn new(jsv: wasm_bindgen::JsValue) -> Self {
JsError { jsv }
}
}
impl fmt::Display for JsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.jsv)
}
}
impl std::error::Error for JsError {}