Skip to main content

orinium_browser/engine/renderer_model/
draw_command.rs

1//! Draw command model: the rendering instructions produced from layout.
2//!
3//! The geometry primitives live in [`crate::engine::renderer_model::geom`],
4//! paths in [`crate::engine::renderer_model::path`], and the layout → command
5//! generation in [`crate::engine::renderer_model::box_model`].
6
7use std::sync::Arc;
8use std::sync::atomic::{AtomicU64, Ordering};
9
10use anyhow::{Context, Result};
11use smol_str::SmolStr;
12
13use crate::engine::layouter::types::{Color, Gradient, TextFlowStyle, TextStyle};
14use crate::engine::renderer_model::geom::{AffineTransform, Rect};
15use crate::engine::renderer_model::path::Path;
16
17/// Fill rule for path filling.
18///
19/// The GPU rasterizer currently only uses this to select the winding mode of
20/// the ear-clipping triangulation; a fully correct stencil-based fill for
21/// arbitrary self-intersecting paths is not implemented.
22#[derive(Debug, Clone, Copy)]
23pub enum FillRule {
24    NonZero,
25    EvenOdd,
26}
27
28/// A fill source: a solid color or a gradient.
29#[derive(Debug, Clone, PartialEq)]
30pub enum Brush {
31    Solid(Color),
32    Gradient(Gradient),
33    /// A decoded RGBA image sampled across the fill path's bounds.
34    Image(Image),
35}
36
37/// Decoded image pixels shared between the engine and platform renderer.
38#[derive(Debug, Clone, PartialEq)]
39pub struct Image {
40    id: u64,
41    width: u32,
42    height: u32,
43    rgba: Arc<[u8]>,
44}
45
46impl Image {
47    /// Decodes encoded image bytes into RGBA8 pixels.
48    ///
49    /// Returns an error when the bytes are not a supported image format.
50    pub fn decode(bytes: &[u8]) -> Result<Self> {
51        if let Ok(decoded) = image::load_from_memory(bytes) {
52            let rgba = decoded.to_rgba8();
53            let (width, height) = rgba.dimensions();
54            return Self::from_rgba(width, height, rgba.into_raw());
55        }
56
57        Self::decode_svg(bytes).context("failed to decode image")
58    }
59
60    fn decode_svg(bytes: &[u8]) -> Result<Self> {
61        let raster = crate::engine::svg::rasterize_from_bytes(bytes, None)
62            .map_err(|error| anyhow::anyhow!("invalid SVG image: {error}"))?;
63        Self::from_rgba(raster.width, raster.height, raster.rgba)
64    }
65
66    /// Creates an image from decoded RGBA8 pixels.
67    ///
68    /// Returns an error when the byte length does not match the dimensions.
69    pub fn from_rgba(width: u32, height: u32, rgba: Vec<u8>) -> Result<Self> {
70        static NEXT_IMAGE_ID: AtomicU64 = AtomicU64::new(1);
71
72        let expected_len = (width as usize)
73            .checked_mul(height as usize)
74            .and_then(|pixels| pixels.checked_mul(4))
75            .context("image dimensions exceed addressable memory")?;
76        if rgba.len() != expected_len {
77            anyhow::bail!(
78                "invalid RGBA byte length: expected {expected_len}, got {}",
79                rgba.len()
80            );
81        }
82        Ok(Self {
83            id: NEXT_IMAGE_ID.fetch_add(1, Ordering::Relaxed),
84            width,
85            height,
86            rgba: Arc::from(rgba),
87        })
88    }
89
90    /// Returns the renderer-unique image identifier.
91    pub fn id(&self) -> u64 {
92        self.id
93    }
94
95    /// Returns the decoded image width in pixels.
96    pub fn width(&self) -> u32 {
97        self.width
98    }
99
100    /// Returns the decoded image height in pixels.
101    pub fn height(&self) -> u32 {
102        self.height
103    }
104
105    /// Returns the decoded RGBA8 pixel bytes.
106    pub fn rgba(&self) -> &[u8] {
107        &self.rgba
108    }
109}
110
111/// How a path is painted: the brush plus an opacity multiplier.
112#[derive(Debug, Clone)]
113pub struct Paint {
114    pub brush: Brush,
115    pub opacity: f32,
116}
117
118/// A drawing instruction for the GPU renderer.
119#[derive(Debug, Clone)]
120pub enum DrawCommand {
121    /// Fill a path.
122    ///
123    /// The `opacity` is applied to solid color fills.
124    Fill {
125        path: Path,
126        paint: Paint,
127        rule: FillRule,
128    },
129    /// Draw a text run at `(x, y)`.
130    DrawText {
131        x: f32,
132        y: f32,
133        text: SmolStr,
134        style: TextStyle,
135        flow_style: TextFlowStyle,
136    },
137    /// Push a clip region given by a path.
138    ///
139    /// Non-rectangular paths are approximated by their bounding box.
140    PushClip {
141        path: Path,
142        rule: FillRule,
143    },
144    PopClip,
145    /// Push a coordinate transform.
146    PushTransform {
147        transform: AffineTransform,
148    },
149    PopTransform,
150
151    /// Delegate rendering to a platform-native system UI element.
152    ///
153    /// The renderer composites or renders the element identified by
154    /// [`SystemUiKind`] at the given rectangle within the current
155    /// coordinate space.
156    SystemUi {
157        kind: SystemUiKind,
158        rect: Rect,
159    },
160}
161
162/// Discriminator for [`DrawCommand::SystemUi`].
163/// Stub
164#[derive(Debug, Clone)]
165pub enum SystemUiKind {
166    /// Composite an external surface (WebView, iframe, …).
167    WebView { surface_id: usize },
168    /// Render a platform-native input widget.
169    Input {
170        value: SmolStr,
171        placeholder: SmolStr,
172    },
173}
174
175#[cfg(test)]
176mod image_tests {
177    use super::Image;
178
179    #[test]
180    fn image_decode_rasterizes_svg_assets() {
181        let image = Image::decode(
182            br##"<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8">
183                <rect width="12" height="8" fill="#ff0000"/>
184            </svg>"##,
185        )
186        .expect("SVG decodes");
187        assert_eq!(image.width(), 12);
188        assert_eq!(image.height(), 8);
189        assert_eq!(&image.rgba()[0..4], &[255, 0, 0, 255]);
190    }
191}