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