1use std::fmt;
7use std::marker::PhantomData;
8use std::path::Path;
9
10#[cfg(feature = "png")]
11use std::fs::File;
12#[cfg(feature = "png")]
13use std::io::BufWriter;
14
15#[cfg(feature = "png")]
16use png::{ColorType, Encoder};
17use wasm_bindgen::JsCast;
18
19use piet::{ImageBuf, ImageFormat};
20#[doc(hidden)]
21pub use piet_web::*;
22
23pub type Piet<'a> = WebRenderContext<'a>;
24
25pub type Brush = piet_web::Brush;
29
30pub type PietText = WebText;
34
35pub type PietTextLayout = WebTextLayout;
39
40pub type PietTextLayoutBuilder = WebTextLayoutBuilder;
44
45pub type PietImage = WebImage;
49
50pub struct Device {
52 marker: std::marker::PhantomData<*const ()>,
55}
56
57unsafe impl Send for Device {}
58
59pub struct BitmapTarget<'a> {
61 canvas: web_sys::HtmlCanvasElement,
62 context: web_sys::CanvasRenderingContext2d,
63 phantom: PhantomData<&'a ()>,
64}
65
66impl Device {
67 pub fn new() -> Result<Device, piet::Error> {
69 Ok(Device {
70 marker: std::marker::PhantomData,
71 })
72 }
73
74 pub fn bitmap_target(
76 &mut self,
77 width: usize,
78 height: usize,
79 pix_scale: f64,
80 ) -> Result<BitmapTarget<'_>, piet::Error> {
81 let document = web_sys::window().unwrap().document().unwrap();
82 let canvas = document
83 .create_element("canvas")
84 .unwrap()
85 .dyn_into::<web_sys::HtmlCanvasElement>()
86 .unwrap();
87 let context = canvas
88 .get_context("2d")
89 .unwrap()
90 .unwrap()
91 .dyn_into::<web_sys::CanvasRenderingContext2d>()
92 .unwrap();
93
94 canvas.set_width(width as u32);
95 canvas.set_height(height as u32);
96 let _ = context.scale(pix_scale, pix_scale);
97
98 Ok(BitmapTarget {
99 canvas,
100 context,
101 phantom: Default::default(),
102 })
103 }
104}
105
106impl<'a> BitmapTarget<'a> {
107 pub fn render_context(&mut self) -> WebRenderContext<'_> {
109 WebRenderContext::new(self.context.clone(), web_sys::window().unwrap())
110 }
111
112 fn raw_pixels(&mut self, fmt: ImageFormat) -> Result<Vec<u8>, piet::Error> {
114 if fmt != ImageFormat::RgbaPremul {
118 return Err(piet::Error::NotSupported);
119 }
120
121 let width = self.canvas.width() as usize;
122 let height = self.canvas.height() as usize;
123
124 let img_data = self
125 .context
126 .get_image_data(0.0, 0.0, width as f64, height as f64)
127 .map_err(|jsv| piet::Error::BackendError(Box::new(JsError::new(jsv))))?;
128
129 Ok(img_data.data().0)
131 }
132
133 #[allow(clippy::wrong_self_convention)]
137 pub fn to_image_buf(&mut self, fmt: ImageFormat) -> Result<ImageBuf, piet::Error> {
138 let data = self.raw_pixels(fmt)?;
139 let width = self.canvas.width() as usize;
140 let height = self.canvas.height() as usize;
141 Ok(ImageBuf::from_raw(data, fmt, width, height))
142 }
143
144 pub fn copy_raw_pixels(
148 &mut self,
149 fmt: ImageFormat,
150 buf: &mut [u8],
151 ) -> Result<usize, piet::Error> {
152 let data = self.raw_pixels(fmt)?;
153 if data.len() > buf.len() {
154 return Err(piet::Error::InvalidInput);
155 }
156 buf.copy_from_slice(&data[..]);
157 Ok(data.len())
158 }
159
160 #[cfg(feature = "png")]
162 pub fn save_to_file<P: AsRef<Path>>(mut self, path: P) -> Result<(), piet::Error> {
163 let height = self.canvas.height();
164 let width = self.canvas.width();
165 let image = self.raw_pixels(ImageFormat::RgbaPremul)?;
166 let file = BufWriter::new(File::create(path).map_err(Into::<Box<_>>::into)?);
167 let mut encoder = Encoder::new(file, width, height);
168 encoder.set_color(ColorType::Rgba);
169 encoder
170 .write_header()
171 .map_err(Into::<Box<_>>::into)?
172 .write_image_data(&image)
173 .map_err(Into::<Box<_>>::into)?;
174 Ok(())
175 }
176
177 #[cfg(not(feature = "png"))]
179 pub fn save_to_file<P: AsRef<Path>>(self, _path: P) -> Result<(), piet::Error> {
180 Err(piet::Error::MissingFeature("png"))
181 }
182}
183
184#[derive(Clone, Debug)]
185struct JsError {
186 jsv: wasm_bindgen::JsValue,
187}
188
189impl JsError {
190 fn new(jsv: wasm_bindgen::JsValue) -> Self {
191 JsError { jsv }
192 }
193}
194
195impl fmt::Display for JsError {
196 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197 write!(f, "{:?}", self.jsv)
198 }
199}
200
201impl std::error::Error for JsError {}