Skip to main content

ps_anyrender/
recording.rs

1use std::sync::Arc;
2
3use crate::{Filter, Glyph, NormalizedCoord, Paint, PaintRef, PaintScene, RenderContext};
4use kurbo::{Affine, BezPath, Rect, Shape, Stroke};
5use peniko::{BlendMode, Color, Fill, FontData, Style, StyleRef};
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10const DEFAULT_TOLERANCE: f64 = 0.1;
11
12#[derive(Clone, Debug, PartialEq)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub enum RenderCommand<Font = FontData, Brush = Paint> {
15    /// Pushes a new layer clipped by the specified shape and composed with previous layers using the specified blend mode.
16    /// Every drawing command after this call will be clipped by the shape until the layer is popped.
17    /// However, the transforms are not saved or modified by the layer stack.
18    PushLayer(LayerCommand),
19    /// Pushes a new clip layer clipped by the specified shape.
20    /// Every drawing command after this call will be clipped by the shape until the layer is popped.
21    /// However, the transforms are not saved or modified by the layer stack.
22    PushClipLayer(ClipCommand),
23    /// Pops the current layer.
24    PopLayer,
25    /// Strokes a shape using the specified style and brush.
26    Stroke(StrokeCommand<Brush>),
27    /// Fills a shape using the specified style and brush.
28    Fill(FillCommand<Brush>),
29    /// Draws a run of glyphs
30    GlyphRun(GlyphRunCommand<Font, Brush>),
31    /// Draw a rounded rectangle blurred with a gaussian filter.
32    BoxShadow(BoxShadowCommand),
33}
34
35impl RenderCommand {
36    /// Apply the specific transform to the command
37    fn apply_transform(mut self, transform: Affine) -> Self {
38        match &mut self {
39            RenderCommand::PushLayer(cmd) => cmd.transform = transform * cmd.transform,
40            RenderCommand::PushClipLayer(cmd) => cmd.transform = transform * cmd.transform,
41            RenderCommand::PopLayer => {}
42            RenderCommand::Stroke(cmd) => cmd.transform = transform * cmd.transform,
43            RenderCommand::Fill(cmd) => cmd.transform = transform * cmd.transform,
44            RenderCommand::GlyphRun(cmd) => cmd.transform = transform * cmd.transform,
45            RenderCommand::BoxShadow(cmd) => cmd.transform = transform * cmd.transform,
46        };
47
48        self
49    }
50}
51
52/// Pushes a new layer clipped by the specified shape and composed with previous layers using the specified blend mode.
53/// Every drawing command after this call will be clipped by the shape until the layer is popped.
54/// However, the transforms are not saved or modified by the layer stack.
55#[derive(Clone, Debug, PartialEq)]
56#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
57pub struct LayerCommand {
58    pub blend: BlendMode,
59    pub alpha: f32,
60    pub transform: Affine,
61    #[cfg_attr(feature = "serde", serde(with = "svg_path"))]
62    pub clip: BezPath, // TODO: more shape options
63    pub filter: Option<Arc<Filter>>,
64    pub backdrop_filter: Option<Arc<Filter>>,
65}
66
67/// Pushes a new clip layer clipped by the specified shape.
68/// Every drawing command after this call will be clipped by the shape until the layer is popped.
69/// However, the transforms are not saved or modified by the layer stack.
70#[derive(Clone, Debug, PartialEq)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72pub struct ClipCommand {
73    pub transform: Affine,
74    #[cfg_attr(feature = "serde", serde(with = "svg_path"))]
75    pub clip: BezPath, // TODO: more shape options
76}
77
78/// Strokes a shape using the specified style and brush.
79#[derive(Clone, Debug, PartialEq)]
80#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
81pub struct StrokeCommand<Brush = Paint> {
82    pub style: Stroke,
83    pub transform: Affine,
84    pub brush: Brush, // TODO: review ownership to avoid cloning. Should brushes be a "resource"?
85    pub brush_transform: Option<Affine>,
86    #[cfg_attr(feature = "serde", serde(with = "svg_path"))]
87    pub shape: BezPath, // TODO: more shape options
88}
89
90/// Fills a shape using the specified style and brush.
91#[derive(Clone, Debug, PartialEq)]
92#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
93pub struct FillCommand<Brush = Paint> {
94    pub fill: Fill,
95    pub transform: Affine,
96    pub brush: Brush, // TODO: review ownership to avoid cloning. Should brushes be a "resource"?
97    pub brush_transform: Option<Affine>,
98    #[cfg_attr(feature = "serde", serde(with = "svg_path"))]
99    pub shape: BezPath, // TODO: more shape options
100}
101
102/// Draws a run of glyphs
103#[derive(Clone, Debug, PartialEq)]
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
105pub struct GlyphRunCommand<Font = FontData, Brush = Paint> {
106    pub font_data: Font,
107    pub font_size: f32,
108    pub hint: bool,
109    pub normalized_coords: Vec<NormalizedCoord>,
110    #[cfg_attr(feature = "serde", serde(default = "Default::default"))]
111    pub embolden: kurbo::Vec2,
112    pub style: Style,
113    pub brush: Brush,
114    pub brush_alpha: f32,
115    pub transform: Affine,
116    pub glyph_transform: Option<Affine>,
117    pub glyphs: Vec<Glyph>,
118}
119
120/// Draw a box shadow around a box
121#[derive(Clone, Debug, PartialEq)]
122#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
123pub struct BoxShadowCommand {
124    pub transform: Affine,
125    pub rect: Rect,
126    pub brush: Color,
127    pub radius: f64,
128    pub std_dev: f64,
129}
130
131/// A recording of a Scene or Scene Fragment stored as plain data types that can be stored
132/// and passed around.
133#[derive(Clone, Debug, PartialEq)]
134pub struct Scene {
135    pub tolerance: f64,
136    pub commands: Vec<RenderCommand>,
137}
138
139impl Default for Scene {
140    fn default() -> Self {
141        Self {
142            tolerance: DEFAULT_TOLERANCE,
143            commands: Vec::new(),
144        }
145    }
146}
147
148impl Scene {
149    /// Create a new empty
150    pub fn new() -> Self {
151        Self::default()
152    }
153
154    pub fn with_tolerance(tolerance: f64) -> Self {
155        Self {
156            tolerance,
157            commands: Vec::new(),
158        }
159    }
160
161    fn convert_paint(&mut self, paint_ref: PaintRef<'_>) -> Paint {
162        match paint_ref {
163            Paint::Solid(color) => Paint::Solid(color),
164            Paint::Gradient(gradient) => Paint::Gradient(gradient.clone()),
165            Paint::Image(image) => Paint::Image(image.to_owned()),
166            // TODO: handle this somehow
167            Paint::Resource(id) => Paint::Resource(id),
168            Paint::Custom(_) => Paint::Solid(Color::TRANSPARENT),
169        }
170    }
171}
172
173impl RenderContext for Scene {}
174impl PaintScene for Scene {
175    fn reset(&mut self) {
176        self.commands.clear()
177    }
178
179    fn push_layer(
180        &mut self,
181        blend: impl Into<BlendMode>,
182        alpha: f32,
183        transform: Affine,
184        clip: &impl Shape,
185        filter: Option<Arc<Filter>>,
186        backdrop_filter: Option<Arc<Filter>>,
187    ) {
188        let blend = blend.into();
189        let clip = clip.into_path(self.tolerance);
190        let layer = LayerCommand {
191            blend,
192            alpha,
193            transform,
194            clip,
195            filter,
196            backdrop_filter,
197        };
198        self.commands.push(RenderCommand::PushLayer(layer));
199    }
200
201    fn push_clip_layer(&mut self, transform: Affine, clip: &impl Shape) {
202        let clip = clip.into_path(self.tolerance);
203        let layer = ClipCommand { transform, clip };
204        self.commands.push(RenderCommand::PushClipLayer(layer));
205    }
206
207    fn pop_layer(&mut self) {
208        self.commands.push(RenderCommand::PopLayer);
209    }
210
211    fn stroke<'a>(
212        &mut self,
213        style: &Stroke,
214        transform: Affine,
215        paint_ref: impl Into<PaintRef<'a>>,
216        brush_transform: Option<Affine>,
217        shape: &impl Shape,
218    ) {
219        let shape = shape.into_path(self.tolerance);
220        let brush = self.convert_paint(paint_ref.into());
221        let stroke = StrokeCommand {
222            style: style.clone(),
223            transform,
224            brush,
225            brush_transform,
226            shape,
227        };
228        self.commands.push(RenderCommand::Stroke(stroke));
229    }
230
231    fn fill<'a>(
232        &mut self,
233        style: Fill,
234        transform: Affine,
235        paint: impl Into<PaintRef<'a>>,
236        brush_transform: Option<Affine>,
237        shape: &impl Shape,
238    ) {
239        let shape = shape.into_path(self.tolerance);
240        let brush = self.convert_paint(paint.into());
241        let fill = FillCommand {
242            fill: style,
243            transform,
244            brush,
245            brush_transform,
246            shape,
247        };
248        self.commands.push(RenderCommand::Fill(fill));
249    }
250
251    fn draw_glyphs<'a, 's: 'a>(
252        &'a mut self,
253        font: &'a FontData,
254        font_size: f32,
255        hint: bool,
256        normalized_coords: &'a [NormalizedCoord],
257        embolden: kurbo::Vec2,
258        style: impl Into<StyleRef<'a>>,
259        paint_ref: impl Into<PaintRef<'a>>,
260        brush_alpha: f32,
261        transform: Affine,
262        glyph_transform: Option<Affine>,
263        glyphs: impl Iterator<Item = Glyph>,
264    ) {
265        let brush = self.convert_paint(paint_ref.into());
266        let glyph_run = GlyphRunCommand {
267            font_data: font.clone(),
268            font_size,
269            hint,
270            normalized_coords: normalized_coords.to_vec(),
271            embolden,
272            style: style.into().to_owned(),
273            brush,
274            brush_alpha,
275            transform,
276            glyph_transform,
277            glyphs: glyphs.into_iter().collect(),
278        };
279        self.commands.push(RenderCommand::GlyphRun(glyph_run));
280    }
281
282    fn draw_box_shadow(
283        &mut self,
284        transform: Affine,
285        rect: Rect,
286        brush: Color,
287        radius: f64,
288        std_dev: f64,
289    ) {
290        let box_shadow = BoxShadowCommand {
291            transform,
292            rect,
293            brush,
294            radius,
295            std_dev,
296        };
297        self.commands.push(RenderCommand::BoxShadow(box_shadow));
298    }
299
300    fn append_scene(&mut self, scene: Scene, scene_transform: Affine) {
301        self.commands.extend(
302            scene
303                .commands
304                .into_iter()
305                .map(|cmd| cmd.apply_transform(scene_transform)),
306        );
307    }
308}
309
310/// Serde helper for serializing `BezPath` as an SVG path string.
311#[cfg(feature = "serde")]
312mod svg_path {
313    use kurbo::BezPath;
314    use serde::{self, Deserialize, Deserializer, Serializer};
315
316    use crate::svg_path_parser;
317
318    pub fn serialize<S>(path: &BezPath, serializer: S) -> Result<S::Ok, S::Error>
319    where
320        S: Serializer,
321    {
322        serializer.serialize_str(&path.to_svg())
323    }
324
325    pub fn deserialize<'de, D>(deserializer: D) -> Result<BezPath, D::Error>
326    where
327        D: Deserializer<'de>,
328    {
329        let s = String::deserialize(deserializer)?;
330        svg_path_parser::parse_svg_path(&s).map_err(serde::de::Error::custom)
331    }
332}