Skip to main content

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}