iced_tiny_skia/
lib.rs

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