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