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 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}