#![allow(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg))] pub mod window; mod engine; mod layer; mod primitive; mod settings; mod text; #[cfg(feature = "image")] mod raster; #[cfg(feature = "svg")] mod vector; #[cfg(feature = "geometry")] pub mod geometry; use iced_debug as debug; pub use iced_graphics as graphics; pub use iced_graphics::core; pub use layer::Layer; pub use primitive::Primitive; pub use settings::Settings; #[cfg(feature = "geometry")] pub use geometry::Geometry; use crate::core::renderer; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; use crate::engine::Engine; use crate::graphics::Viewport; use crate::graphics::compositor; use crate::graphics::text::{Editor, Paragraph}; /// A [`tiny-skia`] graphics renderer for [`iced`]. /// /// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia /// [`iced`]: https://github.com/iced-rs/iced #[derive(Debug)] pub struct Renderer { default_font: Font, default_text_size: Pixels, layers: layer::Stack, engine: Engine, // TODO: Shared engine } impl Renderer { pub fn new(default_font: Font, default_text_size: Pixels) -> Self { Self { default_font, default_text_size, layers: layer::Stack::new(), engine: Engine::new(), } } pub fn layers(&mut self) -> &[Layer] { self.layers.flush(); self.layers.as_slice() } pub fn draw( &mut self, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: &mut tiny_skia::Mask, viewport: &Viewport, damage: &[Rectangle], background_color: Color, ) { let scale_factor = viewport.scale_factor(); self.layers.flush(); for &damage_bounds in damage { let damage_bounds = damage_bounds * scale_factor; let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( damage_bounds.x, damage_bounds.y, damage_bounds.width, damage_bounds.height, ) .expect("Create damage rectangle"), ); pixels.fill_path( &path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor(engine::into_color( background_color, )), anti_alias: false, blend_mode: tiny_skia::BlendMode::Source, ..Default::default() }, tiny_skia::FillRule::default(), tiny_skia::Transform::identity(), None, ); for layer in self.layers.iter() { let Some(layer_bounds) = damage_bounds.intersection(&(layer.bounds * scale_factor)) else { continue; }; engine::adjust_clip_mask(clip_mask, layer_bounds); if !layer.quads.is_empty() { let render_span = debug::render(debug::Primitive::Quad); for (quad, background) in &layer.quads { self.engine.draw_quad( quad, background, Transformation::scale(scale_factor), pixels, clip_mask, layer_bounds, ); } render_span.finish(); } if !layer.primitives.is_empty() { let render_span = debug::render(debug::Primitive::Triangle); for group in &layer.primitives { let Some(group_bounds) = (group.clip_bounds() * group.transformation() * scale_factor) .intersection(&layer_bounds) else { continue; }; engine::adjust_clip_mask(clip_mask, group_bounds); for primitive in group.as_slice() { self.engine.draw_primitive( primitive, group.transformation() * Transformation::scale(scale_factor), pixels, clip_mask, group_bounds, ); } engine::adjust_clip_mask(clip_mask, layer_bounds); } render_span.finish(); } if !layer.images.is_empty() { let render_span = debug::render(debug::Primitive::Image); for image in &layer.images { self.engine.draw_image( image, Transformation::scale(scale_factor), pixels, clip_mask, layer_bounds, ); } render_span.finish(); } if !layer.text.is_empty() { let render_span = debug::render(debug::Primitive::Image); for group in &layer.text { for text in group.as_slice() { self.engine.draw_text( text, group.transformation() * Transformation::scale(scale_factor), pixels, clip_mask, layer_bounds, ); } } render_span.finish(); } } } self.engine.trim(); } } impl core::Renderer for Renderer { fn start_layer(&mut self, bounds: Rectangle) { self.layers.push_clip(bounds); } fn end_layer(&mut self) { self.layers.pop_clip(); } fn start_transformation(&mut self, transformation: Transformation) { self.layers.push_transformation(transformation); } fn end_transformation(&mut self) { self.layers.pop_transformation(); } fn fill_quad( &mut self, quad: renderer::Quad, background: impl Into, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_quad(quad, background.into(), transformation); } fn reset(&mut self, new_bounds: Rectangle) { self.layers.reset(new_bounds); } fn allocate_image( &mut self, _handle: &core::image::Handle, callback: impl FnOnce(Result) + Send + 'static, ) { #[cfg(feature = "image")] #[allow(unsafe_code)] // TODO: Concurrency callback(self.engine.raster_pipeline.load(_handle)); #[cfg(not(feature = "image"))] callback(Err(core::image::Error::Unsupported)) } } impl core::text::Renderer for Renderer { type Font = Font; type Paragraph = Paragraph; type Editor = Editor; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; const ARROW_DOWN_ICON: char = '\u{e800}'; const ICED_LOGO: char = '\u{e801}'; const SCROLL_UP_ICON: char = '\u{e802}'; const SCROLL_DOWN_ICON: char = '\u{e803}'; const SCROLL_LEFT_ICON: char = '\u{e804}'; const SCROLL_RIGHT_ICON: char = '\u{e805}'; fn default_font(&self) -> Self::Font { self.default_font } fn default_size(&self) -> Pixels { self.default_text_size } fn fill_paragraph( &mut self, text: &Self::Paragraph, position: Point, color: Color, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_paragraph( text, position, color, clip_bounds, transformation, ); } fn fill_editor( &mut self, editor: &Self::Editor, position: Point, color: Color, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_editor(editor, position, color, clip_bounds, transformation); } fn fill_text( &mut self, text: core::Text, position: Point, color: Color, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_text(text, position, color, clip_bounds, transformation); } } impl graphics::text::Renderer for Renderer { fn fill_raw(&mut self, raw: graphics::text::Raw) { let (layer, transformation) = self.layers.current_mut(); layer.draw_text_raw(raw, transformation); } } #[cfg(feature = "geometry")] impl graphics::geometry::Renderer for Renderer { type Geometry = Geometry; type Frame = geometry::Frame; fn new_frame(&self, bounds: Rectangle) -> Self::Frame { geometry::Frame::new(bounds) } fn draw_geometry(&mut self, geometry: Self::Geometry) { let (layer, transformation) = self.layers.current_mut(); match geometry { Geometry::Live { primitives, images, text, clip_bounds, } => { layer.draw_primitive_group( primitives, clip_bounds, transformation, ); for image in images { layer.draw_image(image, transformation); } layer.draw_text_group(text, clip_bounds, transformation); } Geometry::Cache(cache) => { layer.draw_primitive_cache( cache.primitives, cache.clip_bounds, transformation, ); for image in cache.images.iter() { layer.draw_image(image.clone(), transformation); } layer.draw_text_cache( cache.text, cache.clip_bounds, transformation, ); } } } } impl graphics::mesh::Renderer for Renderer { fn draw_mesh(&mut self, _mesh: graphics::Mesh) { log::warn!("iced_tiny_skia does not support drawing meshes"); } } #[cfg(feature = "image")] impl core::image::Renderer for Renderer { type Handle = core::image::Handle; fn load_image( &self, handle: &Self::Handle, ) -> Result { self.engine.raster_pipeline.load(handle) } fn measure_image( &self, handle: &Self::Handle, ) -> Option> { self.engine.raster_pipeline.dimensions(handle) } fn draw_image( &mut self, image: core::Image, bounds: Rectangle, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_raster(image, bounds, clip_bounds, transformation); } } #[cfg(feature = "svg")] impl core::svg::Renderer for Renderer { fn measure_svg( &self, handle: &core::svg::Handle, ) -> crate::core::Size { self.engine.vector_pipeline.viewport_dimensions(handle) } fn draw_svg( &mut self, svg: core::Svg, bounds: Rectangle, clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_svg(svg, bounds, clip_bounds, transformation); } } impl compositor::Default for Renderer { type Compositor = window::Compositor; } impl renderer::Headless for Renderer { async fn new( default_font: Font, default_text_size: Pixels, backend: Option<&str>, ) -> Option { if backend.is_some_and(|backend| { !["tiny-skia", "tiny_skia"].contains(&backend) }) { return None; } Some(Self::new(default_font, default_text_size)) } fn name(&self) -> String { "tiny-skia".to_owned() } fn screenshot( &mut self, size: Size, scale_factor: f32, background_color: Color, ) -> Vec { let viewport = Viewport::with_physical_size(size, scale_factor); window::compositor::screenshot(self, &viewport, background_color) } }