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 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();
76
77        self.layers.flush();
78
79        for &damage_bounds in damage {
80            let damage_bounds = damage_bounds * scale_factor;
81
82            let path = tiny_skia::PathBuilder::from_rect(
83                tiny_skia::Rect::from_xywh(
84                    damage_bounds.x,
85                    damage_bounds.y,
86                    damage_bounds.width,
87                    damage_bounds.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(layer_bounds) =
109                    damage_bounds.intersection(&(layer.bounds * scale_factor))
110                else {
111                    continue;
112                };
113
114                engine::adjust_clip_mask(clip_mask, layer_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                            layer_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(group_bounds) = (group.clip_bounds()
136                            * group.transformation()
137                            * scale_factor)
138                            .intersection(&layer_bounds)
139                        else {
140                            continue;
141                        };
142
143                        engine::adjust_clip_mask(clip_mask, group_bounds);
144
145                        for primitive in group.as_slice() {
146                            self.engine.draw_primitive(
147                                primitive,
148                                group.transformation()
149                                    * Transformation::scale(scale_factor),
150                                pixels,
151                                clip_mask,
152                                group_bounds,
153                            );
154                        }
155
156                        engine::adjust_clip_mask(clip_mask, layer_bounds);
157                    }
158
159                    render_span.finish();
160                }
161
162                if !layer.images.is_empty() {
163                    let render_span = debug::render(debug::Primitive::Image);
164
165                    for image in &layer.images {
166                        self.engine.draw_image(
167                            image,
168                            Transformation::scale(scale_factor),
169                            pixels,
170                            clip_mask,
171                            layer_bounds,
172                        );
173                    }
174
175                    render_span.finish();
176                }
177
178                if !layer.text.is_empty() {
179                    let render_span = debug::render(debug::Primitive::Image);
180
181                    for group in &layer.text {
182                        for text in group.as_slice() {
183                            self.engine.draw_text(
184                                text,
185                                group.transformation()
186                                    * Transformation::scale(scale_factor),
187                                pixels,
188                                clip_mask,
189                                layer_bounds,
190                            );
191                        }
192                    }
193
194                    render_span.finish();
195                }
196            }
197        }
198
199        self.engine.trim();
200    }
201}
202
203impl core::Renderer for Renderer {
204    fn start_layer(&mut self, bounds: Rectangle) {
205        self.layers.push_clip(bounds);
206    }
207
208    fn end_layer(&mut self) {
209        self.layers.pop_clip();
210    }
211
212    fn start_transformation(&mut self, transformation: Transformation) {
213        self.layers.push_transformation(transformation);
214    }
215
216    fn end_transformation(&mut self) {
217        self.layers.pop_transformation();
218    }
219
220    fn fill_quad(
221        &mut self,
222        quad: renderer::Quad,
223        background: impl Into<Background>,
224    ) {
225        let (layer, transformation) = self.layers.current_mut();
226        layer.draw_quad(quad, background.into(), transformation);
227    }
228
229    fn reset(&mut self, new_bounds: Rectangle) {
230        self.layers.reset(new_bounds);
231    }
232
233    fn allocate_image(
234        &mut self,
235        _handle: &core::image::Handle,
236        callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>)
237        + Send
238        + 'static,
239    ) {
240        #[cfg(feature = "image")]
241        #[allow(unsafe_code)]
242        // TODO: Concurrency
243        callback(self.engine.raster_pipeline.load(_handle));
244
245        #[cfg(not(feature = "image"))]
246        callback(Err(core::image::Error::Unsupported));
247    }
248}
249
250impl core::text::Renderer for Renderer {
251    type Font = Font;
252    type Paragraph = Paragraph;
253    type Editor = Editor;
254
255    const ICON_FONT: Font = Font::with_name("Iced-Icons");
256    const CHECKMARK_ICON: char = '\u{f00c}';
257    const ARROW_DOWN_ICON: char = '\u{e800}';
258    const ICED_LOGO: char = '\u{e801}';
259    const SCROLL_UP_ICON: char = '\u{e802}';
260    const SCROLL_DOWN_ICON: char = '\u{e803}';
261    const SCROLL_LEFT_ICON: char = '\u{e804}';
262    const SCROLL_RIGHT_ICON: char = '\u{e805}';
263
264    fn default_font(&self) -> Self::Font {
265        self.default_font
266    }
267
268    fn default_size(&self) -> Pixels {
269        self.default_text_size
270    }
271
272    fn fill_paragraph(
273        &mut self,
274        text: &Self::Paragraph,
275        position: Point,
276        color: Color,
277        clip_bounds: Rectangle,
278    ) {
279        let (layer, transformation) = self.layers.current_mut();
280
281        layer.draw_paragraph(
282            text,
283            position,
284            color,
285            clip_bounds,
286            transformation,
287        );
288    }
289
290    fn fill_editor(
291        &mut self,
292        editor: &Self::Editor,
293        position: Point,
294        color: Color,
295        clip_bounds: Rectangle,
296    ) {
297        let (layer, transformation) = self.layers.current_mut();
298        layer.draw_editor(editor, position, color, clip_bounds, transformation);
299    }
300
301    fn fill_text(
302        &mut self,
303        text: core::Text,
304        position: Point,
305        color: Color,
306        clip_bounds: Rectangle,
307    ) {
308        let (layer, transformation) = self.layers.current_mut();
309        layer.draw_text(text, position, color, clip_bounds, transformation);
310    }
311}
312
313impl graphics::text::Renderer for Renderer {
314    fn fill_raw(&mut self, raw: graphics::text::Raw) {
315        let (layer, transformation) = self.layers.current_mut();
316        layer.draw_text_raw(raw, transformation);
317    }
318}
319
320#[cfg(feature = "geometry")]
321impl graphics::geometry::Renderer for Renderer {
322    type Geometry = Geometry;
323    type Frame = geometry::Frame;
324
325    fn new_frame(&self, bounds: Rectangle) -> Self::Frame {
326        geometry::Frame::new(bounds)
327    }
328
329    fn draw_geometry(&mut self, geometry: Self::Geometry) {
330        let (layer, transformation) = self.layers.current_mut();
331
332        match geometry {
333            Geometry::Live {
334                primitives,
335                images,
336                text,
337                clip_bounds,
338            } => {
339                layer.draw_primitive_group(
340                    primitives,
341                    clip_bounds,
342                    transformation,
343                );
344
345                for image in images {
346                    layer.draw_image(image, transformation);
347                }
348
349                layer.draw_text_group(text, clip_bounds, transformation);
350            }
351            Geometry::Cache(cache) => {
352                layer.draw_primitive_cache(
353                    cache.primitives,
354                    cache.clip_bounds,
355                    transformation,
356                );
357
358                for image in cache.images.iter() {
359                    layer.draw_image(image.clone(), transformation);
360                }
361
362                layer.draw_text_cache(
363                    cache.text,
364                    cache.clip_bounds,
365                    transformation,
366                );
367            }
368        }
369    }
370}
371
372impl graphics::mesh::Renderer for Renderer {
373    fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
374        log::warn!("iced_tiny_skia does not support drawing meshes");
375    }
376
377    fn draw_mesh_cache(&mut self, _cache: iced_graphics::mesh::Cache) {
378        log::warn!("iced_tiny_skia does not support drawing meshes");
379    }
380}
381
382#[cfg(feature = "image")]
383impl core::image::Renderer for Renderer {
384    type Handle = core::image::Handle;
385
386    fn load_image(
387        &self,
388        handle: &Self::Handle,
389    ) -> Result<core::image::Allocation, core::image::Error> {
390        self.engine.raster_pipeline.load(handle)
391    }
392
393    fn measure_image(
394        &self,
395        handle: &Self::Handle,
396    ) -> Option<crate::core::Size<u32>> {
397        self.engine.raster_pipeline.dimensions(handle)
398    }
399
400    fn draw_image(
401        &mut self,
402        image: core::Image,
403        bounds: Rectangle,
404        clip_bounds: Rectangle,
405    ) {
406        let (layer, transformation) = self.layers.current_mut();
407        layer.draw_raster(image, bounds, clip_bounds, transformation);
408    }
409}
410
411#[cfg(feature = "svg")]
412impl core::svg::Renderer for Renderer {
413    fn measure_svg(
414        &self,
415        handle: &core::svg::Handle,
416    ) -> crate::core::Size<u32> {
417        self.engine.vector_pipeline.viewport_dimensions(handle)
418    }
419
420    fn draw_svg(
421        &mut self,
422        svg: core::Svg,
423        bounds: Rectangle,
424        clip_bounds: Rectangle,
425    ) {
426        let (layer, transformation) = self.layers.current_mut();
427        layer.draw_svg(svg, bounds, clip_bounds, transformation);
428    }
429}
430
431impl compositor::Default for Renderer {
432    type Compositor = window::Compositor;
433}
434
435impl renderer::Headless for Renderer {
436    async fn new(
437        default_font: Font,
438        default_text_size: Pixels,
439        backend: Option<&str>,
440    ) -> Option<Self> {
441        if backend.is_some_and(|backend| {
442            !["tiny-skia", "tiny_skia"].contains(&backend)
443        }) {
444            return None;
445        }
446
447        Some(Self::new(default_font, default_text_size))
448    }
449
450    fn name(&self) -> String {
451        "tiny-skia".to_owned()
452    }
453
454    fn screenshot(
455        &mut self,
456        size: Size<u32>,
457        scale_factor: f32,
458        background_color: Color,
459    ) -> Vec<u8> {
460        let viewport = Viewport::with_physical_size(size, scale_factor);
461
462        window::compositor::screenshot(self, &viewport, background_color)
463    }
464}