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#[derive(Debug)]
41pub struct Renderer {
42 settings: renderer::Settings,
43 layers: layer::Stack,
44 engine: Engine, }
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 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 }
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}