ps_anyrender/lib.rs
1//! 2D drawing abstraction that allows applications/frameworks to support many rendering backends through
2//! a unified API.
3//!
4//! ### Painting a scene
5//!
6//! The core abstraction in AnyRender is the [`PaintScene`] trait.
7//!
8//! [`PaintScene`] is a "sink" which accepts drawing commands:
9//!
10//! - Applications and libraries draw by pushing commands into a [`PaintScene`]
11//! - Backends execute those commands to produce an output
12//!
13//! ### Rendering to surface or buffer
14//!
15//! In addition to PaintScene, there is:
16//!
17//! - The [`ImageRenderer`] trait which provides an abstraction for rendering to a `Vec<u8>` RGBA8 buffer.
18//! - The [`WindowRenderer`] trait which provides an abstraction for rendering to a surface/window
19//!
20//! ### SVG
21//!
22//! The [anyrender_svg](https://docs.rs/anyrender_svg) crate allows SVGs to be rendered using AnyRender
23//!
24//! ### WASM support
25//!
26//! Wgpu adapter/device/surface initialization is fundamentally async on the web. To avoid
27//! deadlocking the JS event loop, [`WindowRenderer::resume`] takes an `on_ready` callback:
28//! GPU backends spawn the init on `wasm_bindgen_futures::spawn_local` and invoke the callback
29//! once the surface is live. The embedder then calls [`WindowRenderer::complete_resume`] to
30//! transition the renderer to the active state. On native targets the same code path runs
31//! inline (`pollster::block_on` on the GPU backends), so callers see no behavioural difference.
32//!
33//! ### Backends
34//!
35//! Currently existing backends are:
36//!
37//! - [anyrender_vello_hybrid](https://docs.rs/anyrender_vello_hybrid) which draws using [vello_hybrid](https://docs.rs/vello_hybrid)
38//! - [anyrender_vello_cpu](https://docs.rs/anyrender_vello_cpu) which draws using [vello_cpu](https://docs.rs/vello_cpu)
39//! - [anyrender_vello](https://docs.rs/anyrender_vello) which draws using [vello](https://docs.rs/vello)
40//! - [anyrender_skia](https://crates.io/crates/anyrender_skia) which draws using Skia (via the [skia-safe](https://github.com/rust-skia/rust-skia) crate)
41
42#![allow(clippy::collapsible_if)]
43
44use kurbo::{Affine, Rect, Shape, Stroke};
45use peniko::{BlendMode, Color, Fill, FontData, ImageBrushRef, StyleRef};
46use recording::RenderCommand;
47use std::{any::Any, sync::Arc};
48
49pub mod filters;
50pub use filters::Filter;
51pub mod wasm_send_sync;
52pub use wasm_send_sync::*;
53pub mod types;
54pub use types::*;
55mod null_backend;
56pub use null_backend::*;
57pub mod recording;
58pub use recording::Scene;
59
60mod resource_id;
61pub use resource_id::ResourceId;
62
63#[cfg(feature = "serde")]
64mod svg_path_parser;
65
66#[derive(Debug, Copy, Clone, Eq, PartialEq)]
67pub enum RegisterResourceErrorKind {
68 /// The `RenderContext` you tried to register the resource with does not support the kind of resource
69 UnsupportedResourceKind,
70 /// Some other kind of error occured
71 Other,
72 /// This backend has not implemented resource registration
73 Unimplemented,
74 /// The `RenderContext` you tried to register the resource is not currently active
75 NotActive,
76}
77
78#[derive(Debug, Clone)]
79pub struct RegisterResourceError {
80 /// The kind of error that occurred when registering the resource
81 pub kind: RegisterResourceErrorKind,
82 /// An optional detailed error message
83 pub message: Option<String>,
84}
85
86impl From<RegisterResourceErrorKind> for RegisterResourceError {
87 fn from(kind: RegisterResourceErrorKind) -> Self {
88 Self {
89 kind,
90 message: None,
91 }
92 }
93}
94
95pub trait RenderContext {
96 fn try_register_custom_resource(
97 &mut self,
98 resource: Box<dyn Any>,
99 ) -> Result<ResourceId, RegisterResourceError> {
100 let _ = resource;
101 Err(RegisterResourceErrorKind::Unimplemented.into())
102 }
103 fn unregister_resource(&mut self, resource_id: ResourceId) {
104 let _ = resource_id;
105 }
106
107 /// Return a type-erased context type that is passed to custom widgets
108 /// in order to enable them to render renderer-specific content
109 fn renderer_specific_context(&self) -> Option<Box<dyn Any>> {
110 None
111 }
112}
113
114/// Abstraction for rendering a scene to a window
115pub trait WindowRenderer: RenderContext {
116 type ScenePainter<'a>: PaintScene
117 where
118 Self: 'a;
119
120 /// Begin resuming the renderer. `on_ready` fires when initialization completes —
121 /// synchronously inside `resume` on native, asynchronously (via
122 /// `wasm_bindgen_futures::spawn_local`) on `wasm32-unknown-unknown`. After it
123 /// fires, the embedder must call [`complete_resume`](Self::complete_resume) to
124 /// transition the renderer to the active state.
125 fn resume<F: FnOnce() + 'static>(
126 &mut self,
127 window: Arc<dyn WindowHandle>,
128 width: u32,
129 height: u32,
130 on_ready: F,
131 );
132
133 /// Finalize a previously-initiated resume. Returns `true` once the renderer is
134 /// active and ready to render. Idempotent on already-active renderers; returns
135 /// `false` if a pending init has not yet produced a result.
136 ///
137 /// Backends whose `resume` finishes synchronously inline should return `true`
138 /// directly. There is intentionally no default: forgetting to override this on
139 /// an async-init backend would silently no-op rendering.
140 fn complete_resume(&mut self) -> bool;
141
142 fn suspend(&mut self);
143 fn is_active(&self) -> bool;
144
145 /// Returns `true` while an asynchronous resume is in flight (after `resume`
146 /// but before `complete_resume` has succeeded). Defaults to `false` for
147 /// backends with synchronous initialization.
148 fn is_pending(&self) -> bool {
149 false
150 }
151 fn set_size(&mut self, width: u32, height: u32);
152 fn render<F: FnOnce(&mut Self::ScenePainter<'_>)>(&mut self, draw_fn: F);
153}
154
155/// Abstraction for rendering a scene to an image buffer
156pub trait ImageRenderer: RenderContext {
157 type ScenePainter<'a>: PaintScene
158 where
159 Self: 'a;
160 fn new(width: u32, height: u32) -> Self;
161 fn resize(&mut self, width: u32, height: u32);
162 fn reset(&mut self);
163 fn render_to_vec<F: FnOnce(&mut Self::ScenePainter<'_>)>(
164 &mut self,
165 draw_fn: F,
166 vec: &mut Vec<u8>,
167 );
168 fn render<F: FnOnce(&mut Self::ScenePainter<'_>)>(&mut self, draw_fn: F, buffer: &mut [u8]);
169}
170
171/// Draw a scene to a buffer using an `ImageRenderer`
172pub fn render_to_buffer<R: ImageRenderer, F: FnOnce(&mut R::ScenePainter<'_>)>(
173 draw_fn: F,
174 width: u32,
175 height: u32,
176) -> Vec<u8> {
177 let mut buf = Vec::with_capacity((width * height * 4) as usize);
178 let mut renderer = R::new(width, height);
179 renderer.render_to_vec(draw_fn, &mut buf);
180
181 buf
182}
183
184/// Abstraction for drawing a 2D scene
185pub trait PaintScene: RenderContext {
186 /// Removes all content from the scene
187 fn reset(&mut self);
188
189 /// Pushes a new layer clipped by the specified shape and composed with previous layers using the specified blend mode.
190 /// Every drawing command after this call will be clipped by the shape until the layer is popped.
191 /// However, the transforms are not saved or modified by the layer stack.
192 fn push_layer(
193 &mut self,
194 blend: impl Into<BlendMode>,
195 alpha: f32,
196 transform: Affine,
197 clip: &impl Shape,
198 filter: Option<Arc<Filter>>,
199 backdrop_filter: Option<Arc<Filter>>,
200 );
201
202 /// Pushes a new clip layer clipped by the specified shape.
203 /// Every drawing command after this call will be clipped by the shape until the layer is popped.
204 /// However, the transforms are not saved or modified by the layer stack.
205 fn push_clip_layer(&mut self, transform: Affine, clip: &impl Shape);
206
207 /// Pops the current layer.
208 fn pop_layer(&mut self);
209
210 /// Strokes a shape using the specified style and brush.
211 fn stroke<'a>(
212 &mut self,
213 style: &Stroke,
214 transform: Affine,
215 brush: impl Into<PaintRef<'a>>,
216 brush_transform: Option<Affine>,
217 shape: &impl Shape,
218 );
219
220 /// Fills a shape using the specified style and brush.
221 fn fill<'a>(
222 &mut self,
223 style: Fill,
224 transform: Affine,
225 brush: impl Into<PaintRef<'a>>,
226 brush_transform: Option<Affine>,
227 shape: &impl Shape,
228 );
229
230 /// Draws a run of glyphs
231 #[allow(clippy::too_many_arguments)]
232 fn draw_glyphs<'a, 's: 'a>(
233 &'s mut self,
234 font: &'a FontData,
235 font_size: f32,
236 hint: bool,
237 normalized_coords: &'a [NormalizedCoord],
238 embolden: kurbo::Vec2,
239 style: impl Into<StyleRef<'a>>,
240 brush: impl Into<PaintRef<'a>>,
241 brush_alpha: f32,
242 transform: Affine,
243 glyph_transform: Option<Affine>,
244 glyphs: impl Iterator<Item = Glyph> + Clone,
245 );
246
247 /// Draw a rounded rectangle blurred with a gaussian filter.
248 fn draw_box_shadow(
249 &mut self,
250 transform: Affine,
251 rect: Rect,
252 brush: Color,
253 radius: f64,
254 std_dev: f64,
255 );
256
257 // --- Provided methods
258
259 /// Append a recorded Scene Fragment to the current scene
260 fn append_scene(&mut self, scene: Scene, scene_transform: Affine) {
261 for cmd in scene.commands {
262 match cmd {
263 RenderCommand::PushLayer(cmd) => self.push_layer(
264 cmd.blend,
265 cmd.alpha,
266 scene_transform * cmd.transform,
267 &cmd.clip,
268 cmd.filter,
269 cmd.backdrop_filter,
270 ),
271 RenderCommand::PushClipLayer(cmd) => {
272 self.push_clip_layer(scene_transform * cmd.transform, &cmd.clip)
273 }
274 RenderCommand::PopLayer => self.pop_layer(),
275 RenderCommand::Stroke(cmd) => self.stroke(
276 &cmd.style,
277 scene_transform * cmd.transform,
278 match cmd.brush {
279 Paint::Solid(alpha_color) => Paint::Solid(alpha_color),
280 Paint::Gradient(ref gradient) => Paint::Gradient(gradient),
281 Paint::Image(ref image) => Paint::Image(image.as_ref()),
282 Paint::Resource(id) => Paint::Resource(id),
283 Paint::Custom(ref custom) => Paint::Custom(custom.as_ref()),
284 },
285 cmd.brush_transform,
286 &cmd.shape,
287 ),
288 RenderCommand::Fill(cmd) => self.fill(
289 cmd.fill,
290 scene_transform * cmd.transform,
291 match cmd.brush {
292 Paint::Solid(alpha_color) => Paint::Solid(alpha_color),
293 Paint::Gradient(ref gradient) => Paint::Gradient(gradient),
294 Paint::Image(ref image) => Paint::Image(image.as_ref()),
295 Paint::Resource(id) => Paint::Resource(id),
296 Paint::Custom(ref custom) => Paint::Custom(custom.as_ref()),
297 },
298 cmd.brush_transform,
299 &cmd.shape,
300 ),
301 RenderCommand::GlyphRun(cmd) => self.draw_glyphs(
302 &cmd.font_data,
303 cmd.font_size,
304 cmd.hint,
305 &cmd.normalized_coords,
306 cmd.embolden,
307 &cmd.style,
308 match cmd.brush {
309 Paint::Solid(alpha_color) => Paint::Solid(alpha_color),
310 Paint::Gradient(ref gradient) => Paint::Gradient(gradient),
311 Paint::Image(ref image) => Paint::Image(image.as_ref()),
312 Paint::Resource(id) => Paint::Resource(id),
313 Paint::Custom(ref custom) => Paint::Custom(custom.as_ref()),
314 },
315 cmd.brush_alpha,
316 scene_transform * cmd.transform,
317 cmd.glyph_transform,
318 cmd.glyphs.into_iter(),
319 ),
320 RenderCommand::BoxShadow(cmd) => self.draw_box_shadow(
321 scene_transform * cmd.transform,
322 cmd.rect,
323 cmd.brush,
324 cmd.radius,
325 cmd.std_dev,
326 ),
327 }
328 }
329 }
330
331 /// Utility method to draw an image at it's natural size. For more advanced image drawing use the `fill` method
332 fn draw_image(&mut self, image: ImageBrushRef, transform: Affine) {
333 self.fill(
334 Fill::NonZero,
335 transform,
336 image,
337 None,
338 &Rect::new(
339 0.0,
340 0.0,
341 image.image.width as f64,
342 image.image.height as f64,
343 ),
344 );
345 }
346}