aidoku/imports/
canvas.rs

1//! Module for working with bitmap canvases.
2use super::{FFIResult, Ptr, Rid, std::destroy};
3use crate::alloc::Vec;
4use crate::imports::std::{encode, free_result, read_buffer};
5use serde::{Deserialize, Serialize};
6
7pub use crate::canvas::*;
8
9#[link(wasm_import_module = "canvas")]
10unsafe extern "C" {
11	fn new_context(width: f32, height: f32) -> Rid;
12
13	fn set_transform(
14		context: Rid,
15		translate_x: f32,
16		translate_y: f32,
17		scale_x: f32,
18		scale_y: f32,
19		rotate_angle: f32,
20	) -> FFIResult;
21	fn copy_image(
22		context: Rid,
23		image: Rid,
24		src_x: f32,
25		src_y: f32,
26		src_width: f32,
27		src_height: f32,
28		dst_x: f32,
29		dst_y: f32,
30		dst_width: f32,
31		dst_height: f32,
32	) -> FFIResult;
33	fn draw_image(
34		context: Rid,
35		image: Rid,
36		dst_x: f32,
37		dst_y: f32,
38		dst_width: f32,
39		dst_height: f32,
40	) -> FFIResult;
41	fn fill(context: Rid, path: Ptr, r: f32, g: f32, b: f32, a: f32) -> FFIResult;
42	fn stroke(context: Rid, path: Ptr, style: Ptr) -> FFIResult;
43	fn draw_text(
44		context: Rid,
45		text: *const u8,
46		text_len: usize,
47		size: f32,
48		x: f32,
49		y: f32,
50		font: Rid,
51		r: f32,
52		g: f32,
53		b: f32,
54		a: f32,
55	) -> FFIResult;
56	fn get_image(context: Rid) -> Rid;
57
58	fn new_font(name_ptr: *const u8, name_len: usize) -> FFIResult;
59	fn system_font(weight: u8) -> Rid;
60	fn load_font(url_ptr: *const u8, url_len: usize) -> FFIResult;
61
62	fn new_image(data_ptr: *const u8, data_len: usize) -> FFIResult;
63	fn get_image_data(image_rid: Rid) -> FFIResult;
64	fn get_image_width(image_rid: Rid) -> f32;
65	fn get_image_height(image_rid: Rid) -> f32;
66}
67
68/// Error type for canvas operations.
69#[derive(PartialEq, Eq, Debug, Clone)]
70pub enum CanvasError {
71	InvalidContext,
72	InvalidImagePointer,
73	InvalidImage,
74	InvalidSrcRect,
75	InvalidResult,
76	InvalidBounds,
77	InvalidPath,
78	InvalidStyle,
79	InvalidString,
80	InvalidFont,
81	FontLoadFailed,
82}
83
84impl CanvasError {
85	fn from(value: FFIResult) -> Option<Self> {
86		match value {
87			-1 => Some(Self::InvalidContext),
88			-2 => Some(Self::InvalidImagePointer),
89			-3 => Some(Self::InvalidImage),
90			-4 => Some(Self::InvalidSrcRect),
91			-5 => Some(Self::InvalidResult),
92			-6 => Some(Self::InvalidBounds),
93			-7 => Some(Self::InvalidPath),
94			-8 => Some(Self::InvalidStyle),
95			-9 => Some(Self::InvalidString),
96			-10 => Some(Self::InvalidFont),
97			-11 => Some(Self::FontLoadFailed),
98			_ => None,
99		}
100	}
101}
102
103/// A reference to an image.
104#[derive(Debug)]
105pub struct ImageRef {
106	/// The reference id of the stored image.
107	///
108	/// This property is exposed for the functions that the [register_source](crate::register_source)
109	/// macro generates and should not be used directly.
110	pub rid: Rid,
111	/// Whether the image is externally managed.
112	///
113	/// This property is exposed for the functions that the [register_source](crate::register_source)
114	/// macro generates and should not be used directly.
115	pub externally_managed: bool,
116}
117
118impl ImageRef {
119	pub(crate) fn from(rid: Rid, externally_managed: bool) -> Self {
120		ImageRef {
121			rid,
122			externally_managed,
123		}
124	}
125
126	pub fn new(data: &[u8]) -> Self {
127		let rid = unsafe { new_image(data.as_ptr(), data.len()) };
128		ImageRef::from(rid, false)
129	}
130
131	pub fn data(&self) -> Vec<u8> {
132		let result = unsafe { get_image_data(self.rid) };
133		if CanvasError::from(result).is_some() {
134			return Vec::new();
135		}
136		read_buffer(result).unwrap_or_default()
137	}
138
139	pub fn width(&self) -> f32 {
140		unsafe { get_image_width(self.rid) }
141	}
142
143	pub fn height(&self) -> f32 {
144		unsafe { get_image_height(self.rid) }
145	}
146}
147
148impl PartialEq for ImageRef {
149	fn eq(&self, other: &Self) -> bool {
150		self.rid == other.rid
151	}
152}
153
154impl Drop for ImageRef {
155	fn drop(&mut self) {
156		if !self.externally_managed {
157			unsafe { destroy(self.rid) }
158		}
159	}
160}
161
162impl Serialize for ImageRef {
163	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164	where
165		S: serde::Serializer,
166	{
167		self.rid.serialize(serializer)
168	}
169}
170
171impl<'de> Deserialize<'de> for ImageRef {
172	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
173	where
174		D: serde::Deserializer<'de>,
175	{
176		// when deserializing from a response struct, the image ref should be managed externally
177		Rid::deserialize(deserializer).map(|rid| ImageRef::from(rid, true))
178	}
179}
180
181impl Drop for Path {
182	fn drop(&mut self) {
183		if let Some(ptr) = self.ptr {
184			unsafe { free_result(ptr) };
185		}
186	}
187}
188
189/// A reference to a font.
190#[derive(Debug)]
191pub struct Font {
192	rid: Rid,
193}
194
195impl Font {
196	/// Creates a new font with the given family.
197	pub fn new(font_family: &str) -> Result<Self, CanvasError> {
198		let rid = unsafe { new_font(font_family.as_ptr(), font_family.len()) };
199		if let Some(err) = CanvasError::from(rid) {
200			return Err(err);
201		}
202		Ok(Self { rid })
203	}
204
205	/// Creates a new font with the system default family and the given weight.
206	pub fn system(weight: FontWeight) -> Self {
207		let rid = unsafe { system_font(weight as u8) };
208		Self { rid }
209	}
210
211	/// Loads a font from the given URL.
212	pub fn load(url: &str) -> Result<Self, CanvasError> {
213		let rid = unsafe { load_font(url.as_ptr(), url.len()) };
214		if let Some(err) = CanvasError::from(rid) {
215			return Err(err);
216		}
217		Ok(Self { rid })
218	}
219}
220
221/// A canvas for drawing images.
222#[derive(Debug)]
223pub struct Canvas {
224	rid: Rid,
225}
226
227impl Canvas {
228	/// Creates a new canvas with the given size.
229	pub fn new(width: f32, height: f32) -> Self {
230		let rid = unsafe { new_context(width, height) };
231		Canvas { rid }
232	}
233
234	/// Sets the transformation matrix for the canvas.
235	pub fn set_transform(&mut self, transform: &Transform) {
236		use num_traits::float::Float;
237
238		// convert transform matrix to translate, scale, and rotate values
239		let scale_x = (transform.m11 * transform.m11 + transform.m12 * transform.m12).sqrt();
240		let scale_y = (transform.m21 * transform.m21 + transform.m22 * transform.m22).sqrt();
241		let rotate_angle = transform.m12.atan2(transform.m11);
242		let translate_x = (transform.m31 * Float::cos(rotate_angle)
243			+ transform.m32 * Float::sin(rotate_angle))
244			/ scale_x;
245		let translate_y = (transform.m32 * Float::cos(rotate_angle)
246			- transform.m31 * Float::sin(rotate_angle))
247			/ scale_y;
248
249		unsafe {
250			set_transform(
251				self.rid,
252				translate_x,
253				translate_y,
254				scale_x,
255				scale_y,
256				rotate_angle,
257			);
258		}
259	}
260
261	/// Draws an image onto the canvas.
262	pub fn draw_image(&mut self, image: &ImageRef, dst_rect: Rect) {
263		unsafe {
264			draw_image(
265				self.rid,
266				image.rid,
267				dst_rect.x,
268				dst_rect.y,
269				dst_rect.width,
270				dst_rect.height,
271			);
272		}
273	}
274
275	/// Copies an area of the canvas onto another area.
276	pub fn copy_image(&mut self, image: &ImageRef, src_rect: Rect, dst_rect: Rect) {
277		unsafe {
278			copy_image(
279				self.rid,
280				image.rid,
281				src_rect.x,
282				src_rect.y,
283				src_rect.width,
284				src_rect.height,
285				dst_rect.x,
286				dst_rect.y,
287				dst_rect.width,
288				dst_rect.height,
289			);
290		}
291	}
292
293	/// Fills a path with a given color.
294	pub fn fill(&mut self, path: &Path, color: &Color) {
295		let Some(path_ptr) = path.ptr else { return };
296		unsafe {
297			fill(
298				self.rid,
299				path_ptr,
300				color.red,
301				color.green,
302				color.blue,
303				color.alpha,
304			);
305		}
306	}
307
308	/// Strokes a path with a given style.
309	pub fn stroke(&mut self, path: &Path, style: &StrokeStyle) {
310		let Some(path_ptr) = path.ptr else { return };
311		let style_ptr = unsafe { encode(style) };
312		unsafe {
313			stroke(self.rid, path_ptr, style_ptr);
314		}
315		unsafe {
316			free_result(style_ptr);
317		}
318	}
319
320	/// Draws text onto the canvas.
321	pub fn draw_text(&mut self, text: &str, size: f32, pos: &Point, font: &Font, color: &Color) {
322		unsafe {
323			draw_text(
324				self.rid,
325				text.as_ptr(),
326				text.len(),
327				size,
328				pos.x,
329				pos.y,
330				font.rid,
331				color.red,
332				color.green,
333				color.blue,
334				color.alpha,
335			);
336		}
337	}
338
339	/// Converts the internal canvas into an image.
340	pub fn get_image(self) -> ImageRef {
341		ImageRef::from(unsafe { get_image(self.rid) }, false)
342	}
343}
344
345impl Drop for Canvas {
346	fn drop(&mut self) {
347		unsafe { destroy(self.rid) }
348	}
349}