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, Pixels, 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        &mut 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_factor: f32) {
226        // TODO: No hinting supported
227        // We'll replace `tiny-skia` with `vello_cpu` soon
228    }
229
230    fn scale_factor(&self) -> Option<f32> {
231        None
232    }
233
234    fn reset(&mut self, new_bounds: Rectangle) {
235        self.layers.reset(new_bounds);
236    }
237}
238
239impl core::text::Renderer for Renderer {
240    type Font = Font;
241    type Paragraph = Paragraph;
242    type Editor = Editor;
243
244    const ICON_FONT: Font = Font::new("Iced-Icons");
245    const CHECKMARK_ICON: char = '\u{f00c}';
246    const ARROW_DOWN_ICON: char = '\u{e800}';
247    const ICED_LOGO: char = '\u{e801}';
248    const SCROLL_UP_ICON: char = '\u{e802}';
249    const SCROLL_DOWN_ICON: char = '\u{e803}';
250    const SCROLL_LEFT_ICON: char = '\u{e804}';
251    const SCROLL_RIGHT_ICON: char = '\u{e805}';
252
253    fn default_font(&self) -> Self::Font {
254        self.settings.default_font
255    }
256
257    fn default_size(&self) -> Pixels {
258        self.settings.default_text_size
259    }
260
261    fn fill_paragraph(
262        &mut self,
263        text: &Self::Paragraph,
264        position: Point,
265        color: Color,
266        clip_bounds: Rectangle,
267    ) {
268        let (layer, transformation) = self.layers.current_mut();
269
270        layer.draw_paragraph(text, position, color, clip_bounds, transformation);
271    }
272
273    fn fill_editor(
274        &mut self,
275        editor: &Self::Editor,
276        position: Point,
277        color: Color,
278        clip_bounds: Rectangle,
279    ) {
280        let (layer, transformation) = self.layers.current_mut();
281        layer.draw_editor(editor, position, color, clip_bounds, transformation);
282    }
283
284    fn fill_text(
285        &mut self,
286        text: core::Text,
287        position: Point,
288        color: Color,
289        clip_bounds: Rectangle,
290    ) {
291        let (layer, transformation) = self.layers.current_mut();
292        layer.draw_text(text, position, color, clip_bounds, transformation);
293    }
294}
295
296impl graphics::text::Renderer for Renderer {
297    fn fill_raw(&mut self, raw: graphics::text::Raw) {
298        let (layer, transformation) = self.layers.current_mut();
299        layer.draw_text_raw(raw, transformation);
300    }
301}
302
303#[cfg(feature = "geometry")]
304impl graphics::geometry::Renderer for Renderer {
305    type Geometry = Geometry;
306    type Frame = geometry::Frame;
307
308    fn new_frame(&self, bounds: Rectangle) -> Self::Frame {
309        geometry::Frame::new(bounds)
310    }
311
312    fn draw_geometry(&mut self, geometry: Self::Geometry) {
313        let (layer, transformation) = self.layers.current_mut();
314
315        match geometry {
316            Geometry::Live {
317                primitives,
318                images,
319                text,
320                clip_bounds,
321            } => {
322                layer.draw_primitive_group(primitives, clip_bounds, transformation);
323
324                for image in images {
325                    layer.draw_image(image, transformation);
326                }
327
328                layer.draw_text_group(text, clip_bounds, transformation);
329            }
330            Geometry::Cache(cache) => {
331                layer.draw_primitive_cache(cache.primitives, cache.clip_bounds, transformation);
332
333                for image in cache.images.iter() {
334                    layer.draw_image(image.clone(), transformation);
335                }
336
337                layer.draw_text_cache(cache.text, cache.clip_bounds, transformation);
338            }
339        }
340    }
341}
342
343impl graphics::mesh::Renderer for Renderer {
344    fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
345        log::warn!("iced_tiny_skia does not support drawing meshes");
346    }
347
348    fn draw_mesh_cache(&mut self, _cache: iced_graphics::mesh::Cache) {
349        log::warn!("iced_tiny_skia does not support drawing meshes");
350    }
351}
352
353#[cfg(feature = "image")]
354impl core::image::Renderer for Renderer {
355    type Handle = core::image::Handle;
356
357    fn load_image(
358        &self,
359        handle: &Self::Handle,
360    ) -> Result<core::image::Allocation, core::image::Error> {
361        self.engine.raster_pipeline.load(handle)
362    }
363
364    fn measure_image(&self, handle: &Self::Handle) -> Option<crate::core::Size<u32>> {
365        self.engine.raster_pipeline.dimensions(handle)
366    }
367
368    fn draw_image(&mut self, image: core::Image, bounds: Rectangle, clip_bounds: Rectangle) {
369        let (layer, transformation) = self.layers.current_mut();
370        layer.draw_raster(image, bounds, clip_bounds, transformation);
371    }
372}
373
374#[cfg(feature = "svg")]
375impl core::svg::Renderer for Renderer {
376    fn measure_svg(&self, handle: &core::svg::Handle) -> crate::core::Size<u32> {
377        self.engine.vector_pipeline.viewport_dimensions(handle)
378    }
379
380    fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle, clip_bounds: Rectangle) {
381        let (layer, transformation) = self.layers.current_mut();
382        layer.draw_svg(svg, bounds, clip_bounds, transformation);
383    }
384}
385
386impl compositor::Default for Renderer {
387    type Compositor = window::Compositor;
388}
389
390impl renderer::Headless for Renderer {
391    async fn new(settings: renderer::Settings, backend: Option<&str>) -> Option<Self> {
392        if backend.is_some_and(|backend| !["tiny-skia", "tiny_skia", "software"].contains(&backend))
393        {
394            return None;
395        }
396
397        Some(Self::new(settings))
398    }
399
400    fn name(&self) -> String {
401        "tiny-skia".to_owned()
402    }
403
404    fn screenshot(
405        &mut self,
406        size: Size<u32>,
407        scale_factor: f32,
408        background_color: Color,
409    ) -> Vec<u8> {
410        let viewport = Viewport::with_physical_size(size, scale_factor);
411
412        window::compositor::screenshot(self, &viewport, background_color)
413    }
414}