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