Skip to main content

iced_tiny_skia/
lib.rs

1#![allow(missing_docs)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3pub mod window;
4
5mod engine;
6mod layer;
7mod primitive;
8mod text;
9
10#[cfg(feature = "image")]
11mod raster;
12
13#[cfg(feature = "svg")]
14mod vector;
15
16#[cfg(feature = "geometry")]
17pub mod geometry;
18
19use iced_debug as debug;
20pub use iced_graphics as graphics;
21pub use iced_graphics::core;
22
23pub use layer::Layer;
24pub use primitive::Primitive;
25
26#[cfg(feature = "geometry")]
27pub use geometry::Geometry;
28
29use crate::core::renderer;
30use crate::core::{Background, Color, Font, Point, Rectangle, Size, Transformation};
31use crate::engine::Engine;
32use crate::graphics::Viewport;
33use crate::graphics::compositor;
34use crate::graphics::text::{Editor, Paragraph};
35
36/// A [`tiny-skia`] graphics renderer for [`iced`].
37///
38/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
39/// [`iced`]: https://github.com/iced-rs/iced
40#[derive(Debug)]
41pub struct Renderer {
42    settings: renderer::Settings,
43    layers: layer::Stack,
44    engine: Engine, // TODO: Shared engine
45}
46
47impl Renderer {
48    pub fn new(settings: renderer::Settings) -> Self {
49        Self {
50            settings,
51            layers: layer::Stack::new(),
52            engine: Engine::new(),
53        }
54    }
55
56    pub fn layers(&mut self) -> &[Layer] {
57        self.layers.flush();
58        self.layers.as_slice()
59    }
60
61    pub fn draw(
62        &mut self,
63        pixels: &mut tiny_skia::PixmapMut<'_>,
64        clip_mask: &mut tiny_skia::Mask,
65        viewport: &Viewport,
66        damage: &[Rectangle],
67        background_color: Color,
68    ) {
69        let scale_factor = viewport.scale_factor();
70        self.layers.flush();
71
72        for &damage_bounds in damage {
73            let damage_bounds = damage_bounds * scale_factor;
74
75            let path = tiny_skia::PathBuilder::from_rect(
76                tiny_skia::Rect::from_xywh(
77                    damage_bounds.x,
78                    damage_bounds.y,
79                    damage_bounds.width,
80                    damage_bounds.height,
81                )
82                .expect("Create damage rectangle"),
83            );
84
85            pixels.fill_path(
86                &path,
87                &tiny_skia::Paint {
88                    shader: tiny_skia::Shader::SolidColor(engine::into_color(background_color)),
89                    anti_alias: false,
90                    blend_mode: tiny_skia::BlendMode::Source,
91                    ..Default::default()
92                },
93                tiny_skia::FillRule::default(),
94                tiny_skia::Transform::identity(),
95                None,
96            );
97
98            for layer in self.layers.iter() {
99                let Some(layer_bounds) = damage_bounds.intersection(&(layer.bounds * scale_factor))
100                else {
101                    continue;
102                };
103
104                engine::adjust_clip_mask(clip_mask, layer_bounds);
105
106                if !layer.quads.is_empty() {
107                    let render_span = debug::render(debug::Primitive::Quad);
108                    for (quad, background) in &layer.quads {
109                        self.engine.draw_quad(
110                            quad,
111                            background,
112                            Transformation::scale(scale_factor),
113                            pixels,
114                            clip_mask,
115                            layer_bounds,
116                        );
117                    }
118                    render_span.finish();
119                }
120
121                if !layer.primitives.is_empty() {
122                    let render_span = debug::render(debug::Primitive::Triangle);
123
124                    for group in &layer.primitives {
125                        let Some(group_bounds) =
126                            (group.clip_bounds() * scale_factor).intersection(&layer_bounds)
127                        else {
128                            continue;
129                        };
130
131                        engine::adjust_clip_mask(clip_mask, group_bounds);
132
133                        for primitive in group.as_slice() {
134                            self.engine.draw_primitive(
135                                primitive,
136                                Transformation::scale(scale_factor) * group.transformation(),
137                                pixels,
138                                clip_mask,
139                                group_bounds,
140                            );
141                        }
142
143                        engine::adjust_clip_mask(clip_mask, layer_bounds);
144                    }
145
146                    render_span.finish();
147                }
148
149                if !layer.images.is_empty() {
150                    let render_span = debug::render(debug::Primitive::Image);
151
152                    for image in &layer.images {
153                        self.engine.draw_image(
154                            image,
155                            Transformation::scale(scale_factor),
156                            pixels,
157                            clip_mask,
158                            layer_bounds,
159                        );
160                    }
161
162                    render_span.finish();
163                }
164
165                if !layer.text.is_empty() {
166                    let render_span = debug::render(debug::Primitive::Image);
167
168                    for group in &layer.text {
169                        for text in group.as_slice() {
170                            self.engine.draw_text(
171                                text,
172                                Transformation::scale(scale_factor) * group.transformation(),
173                                pixels,
174                                clip_mask,
175                                layer_bounds,
176                            );
177                        }
178                    }
179
180                    render_span.finish();
181                }
182            }
183        }
184
185        self.engine.trim();
186    }
187}
188
189impl core::Renderer for Renderer {
190    fn start_layer(&mut self, bounds: Rectangle) {
191        self.layers.push_clip(bounds);
192    }
193
194    fn end_layer(&mut self) {
195        self.layers.pop_clip();
196    }
197
198    fn start_transformation(&mut self, transformation: Transformation) {
199        self.layers.push_transformation(transformation);
200    }
201
202    fn end_transformation(&mut self) {
203        self.layers.pop_transformation();
204    }
205
206    fn fill_quad(&mut self, quad: renderer::Quad, background: impl Into<Background>) {
207        let (layer, transformation) = self.layers.current_mut();
208        layer.draw_quad(quad, background.into(), transformation);
209    }
210
211    fn allocate_image(
212        &self,
213        _handle: &core::image::Handle,
214        callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>) + Send + 'static,
215    ) {
216        #[cfg(feature = "image")]
217        #[allow(unsafe_code)]
218        // TODO: Concurrency
219        callback(self.engine.raster_pipeline.load(_handle));
220
221        #[cfg(not(feature = "image"))]
222        callback(Err(core::image::Error::Unsupported));
223    }
224
225    fn hint(&mut self, _scale: renderer::Scale) {
226        // TODO: No hinting supported
227        // We'll replace `tiny-skia` with `vello_cpu` soon
228    }
229
230    fn scale(&self) -> Option<renderer::Scale> {
231        None
232    }
233
234    fn reset(&mut self, new_bounds: Rectangle) {
235        self.layers.reset(new_bounds);
236    }
237
238    fn settings(&self) -> renderer::Settings {
239        self.settings
240    }
241}
242
243impl core::text::Renderer for Renderer {
244    type Paragraph = Paragraph;
245    type Editor = Editor;
246
247    const ICON_FONT: Font = Font::new("Iced-Icons");
248    const CHECKMARK_ICON: char = '\u{f00c}';
249    const ARROW_DOWN_ICON: char = '\u{e800}';
250    const ICED_LOGO: char = '\u{e801}';
251    const SCROLL_UP_ICON: char = '\u{e802}';
252    const SCROLL_DOWN_ICON: char = '\u{e803}';
253    const SCROLL_LEFT_ICON: char = '\u{e804}';
254    const SCROLL_RIGHT_ICON: char = '\u{e805}';
255
256    fn fill_paragraph(
257        &mut self,
258        text: &Self::Paragraph,
259        position: Point,
260        color: Color,
261        clip_bounds: Rectangle,
262    ) {
263        let (layer, transformation) = self.layers.current_mut();
264
265        layer.draw_paragraph(text, position, color, clip_bounds, transformation);
266    }
267
268    fn fill_editor(
269        &mut self,
270        editor: &Self::Editor,
271        position: Point,
272        color: Color,
273        clip_bounds: Rectangle,
274    ) {
275        let (layer, transformation) = self.layers.current_mut();
276        layer.draw_editor(editor, position, color, clip_bounds, transformation);
277    }
278
279    fn fill_text(
280        &mut self,
281        text: core::Text,
282        position: Point,
283        color: Color,
284        clip_bounds: Rectangle,
285    ) {
286        let (layer, transformation) = self.layers.current_mut();
287        layer.draw_text(text, position, color, clip_bounds, transformation);
288    }
289}
290
291impl graphics::text::Renderer for Renderer {
292    fn fill_raw(&mut self, raw: graphics::text::Raw) {
293        let (layer, transformation) = self.layers.current_mut();
294        layer.draw_text_raw(raw, transformation);
295    }
296}
297
298#[cfg(feature = "geometry")]
299impl graphics::geometry::Renderer for Renderer {
300    type Geometry = Geometry;
301    type Frame = geometry::Frame;
302
303    fn new_frame(&self, bounds: Rectangle) -> Self::Frame {
304        geometry::Frame::new(bounds)
305    }
306
307    fn draw_geometry(&mut self, geometry: Self::Geometry) {
308        let (layer, transformation) = self.layers.current_mut();
309
310        match geometry {
311            Geometry::Live {
312                primitives,
313                images,
314                text,
315                clip_bounds,
316            } => {
317                layer.draw_primitive_group(primitives, clip_bounds, transformation);
318
319                for image in images {
320                    layer.draw_image(image, transformation);
321                }
322
323                layer.draw_text_group(text, clip_bounds, transformation);
324            }
325            Geometry::Cache(cache) => {
326                layer.draw_primitive_cache(cache.primitives, cache.clip_bounds, transformation);
327
328                for image in cache.images.iter() {
329                    layer.draw_image(image.clone(), transformation);
330                }
331
332                layer.draw_text_cache(cache.text, cache.clip_bounds, transformation);
333            }
334        }
335    }
336}
337
338impl graphics::mesh::Renderer for Renderer {
339    fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
340        log::warn!("iced_tiny_skia does not support drawing meshes");
341    }
342
343    fn draw_mesh_cache(&mut self, _cache: iced_graphics::mesh::Cache) {
344        log::warn!("iced_tiny_skia does not support drawing meshes");
345    }
346}
347
348#[cfg(feature = "image")]
349impl core::image::Renderer for Renderer {
350    type Handle = core::image::Handle;
351
352    fn load_image(
353        &self,
354        handle: &Self::Handle,
355    ) -> Result<core::image::Allocation, core::image::Error> {
356        self.engine.raster_pipeline.load(handle)
357    }
358
359    fn measure_image(&self, handle: &Self::Handle) -> Option<crate::core::Size<u32>> {
360        self.engine.raster_pipeline.dimensions(handle)
361    }
362
363    fn draw_image(&mut self, image: core::Image, bounds: Rectangle, clip_bounds: Rectangle) {
364        let (layer, transformation) = self.layers.current_mut();
365        layer.draw_raster(image, bounds, clip_bounds, transformation);
366    }
367}
368
369#[cfg(feature = "svg")]
370impl core::svg::Renderer for Renderer {
371    fn measure_svg(&self, handle: &core::svg::Handle) -> crate::core::Size<u32> {
372        self.engine.vector_pipeline.viewport_dimensions(handle)
373    }
374
375    fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle, clip_bounds: Rectangle) {
376        let (layer, transformation) = self.layers.current_mut();
377        layer.draw_svg(svg, bounds, clip_bounds, transformation);
378    }
379}
380
381impl compositor::Default for Renderer {
382    type Compositor = window::Compositor;
383}
384
385impl renderer::Headless for Renderer {
386    async fn new(settings: renderer::Settings, backend: Option<&str>) -> Option<Self> {
387        if backend.is_some_and(|backend| !["tiny-skia", "tiny_skia", "software"].contains(&backend))
388        {
389            return None;
390        }
391
392        Some(Self::new(settings))
393    }
394
395    fn name(&self) -> String {
396        "tiny-skia".to_owned()
397    }
398
399    fn screenshot(
400        &mut self,
401        size: Size<u32>,
402        scale_factor: f32,
403        background_color: Color,
404    ) -> Vec<u8> {
405        let viewport = Viewport::with_physical_size(
406            size,
407            renderer::Scale {
408                window: 1.0,
409                application: scale_factor,
410            },
411        );
412
413        window::compositor::screenshot(self, &viewport, background_color)
414    }
415}