orinium_browser/engine/renderer_model/
draw_command.rs1use 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#[derive(Debug, Clone, Copy)]
23pub enum FillRule {
24 NonZero,
25 EvenOdd,
26}
27
28#[derive(Debug, Clone)]
30pub enum Brush {
31 Solid(Color),
32 Gradient(Gradient),
33 Image(Image),
35}
36
37#[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 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 options = resvg::usvg::Options::default();
62 let tree = resvg::usvg::Tree::from_data(bytes, &options).context("invalid SVG image")?;
63 let size = tree.size();
64 let width = size.width().ceil().max(1.0) as u32;
65 let height = size.height().ceil().max(1.0) as u32;
66 let mut pixmap = resvg::tiny_skia::Pixmap::new(width, height)
67 .context("failed to allocate SVG image pixels")?;
68 let transform = resvg::tiny_skia::Transform::from_scale(
69 width as f32 / size.width(),
70 height as f32 / size.height(),
71 );
72 resvg::render(&tree, transform, &mut pixmap.as_mut());
73
74 let mut rgba = pixmap.data().to_vec();
77 for pixel in rgba.as_chunks_mut::<4>().0 {
78 let alpha = pixel[3] as u16;
79 if alpha == 0 {
80 pixel[0] = 0;
81 pixel[1] = 0;
82 pixel[2] = 0;
83 } else if alpha < 255 {
84 pixel[0] = ((pixel[0] as u16 * 255 + alpha / 2) / alpha).min(255) as u8;
85 pixel[1] = ((pixel[1] as u16 * 255 + alpha / 2) / alpha).min(255) as u8;
86 pixel[2] = ((pixel[2] as u16 * 255 + alpha / 2) / alpha).min(255) as u8;
87 }
88 }
89 Self::from_rgba(width, height, rgba)
90 }
91
92 pub fn from_rgba(width: u32, height: u32, rgba: Vec<u8>) -> Result<Self> {
96 static NEXT_IMAGE_ID: AtomicU64 = AtomicU64::new(1);
97
98 let expected_len = (width as usize)
99 .checked_mul(height as usize)
100 .and_then(|pixels| pixels.checked_mul(4))
101 .context("image dimensions exceed addressable memory")?;
102 if rgba.len() != expected_len {
103 anyhow::bail!(
104 "invalid RGBA byte length: expected {expected_len}, got {}",
105 rgba.len()
106 );
107 }
108 Ok(Self {
109 id: NEXT_IMAGE_ID.fetch_add(1, Ordering::Relaxed),
110 width,
111 height,
112 rgba: Arc::from(rgba),
113 })
114 }
115
116 pub fn id(&self) -> u64 {
118 self.id
119 }
120
121 pub fn width(&self) -> u32 {
123 self.width
124 }
125
126 pub fn height(&self) -> u32 {
128 self.height
129 }
130
131 pub fn rgba(&self) -> &[u8] {
133 &self.rgba
134 }
135}
136
137#[cfg(test)]
138mod image_tests {
139 use super::Image;
140
141 #[test]
142 fn image_decode_rasterizes_svg_assets() {
143 let image = Image::decode(
144 br##"<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8">
145 <rect width="12" height="8" fill="#ff0000"/>
146 </svg>"##,
147 )
148 .expect("SVG decodes");
149 assert_eq!(image.width(), 12);
150 assert_eq!(image.height(), 8);
151 assert_eq!(&image.rgba()[0..4], &[255, 0, 0, 255]);
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct Paint {
158 pub brush: Brush,
159 pub opacity: f32,
160}
161
162#[derive(Debug, Clone)]
164pub enum DrawCommand {
165 Fill {
169 path: Path,
170 paint: Paint,
171 rule: FillRule,
172 },
173 DrawText {
175 x: f32,
176 y: f32,
177 text: SmolStr,
178 style: TextStyle,
179 flow_style: TextFlowStyle,
180 },
181 PushClip {
185 path: Path,
186 rule: FillRule,
187 },
188 PopClip,
189 PushTransform {
191 transform: AffineTransform,
192 },
193 PopTransform,
194
195 SystemUi {
201 kind: SystemUiKind,
202 rect: Rect,
203 },
204}
205
206#[derive(Debug, Clone)]
209pub enum SystemUiKind {
210 WebView { surface_id: usize },
212 Input {
214 value: SmolStr,
215 placeholder: SmolStr,
216 },
217}