1use crate::Primitive;
2use crate::core::text::LineHeight;
3use crate::core::{self, Pixels, Point, Radians, Rectangle, Size, Svg, Vector};
4use crate::graphics::cache::{self, Cached};
5use crate::graphics::geometry::fill::{self, Fill};
6use crate::graphics::geometry::stroke::{self, Stroke};
7use crate::graphics::geometry::{self, Path, Style};
8use crate::graphics::{self, Gradient, Image, Text};
9
10use std::sync::Arc;
11
12#[derive(Debug)]
13pub enum Geometry {
14 Live {
15 text: Vec<Text>,
16 images: Vec<graphics::Image>,
17 primitives: Vec<Primitive>,
18 clip_bounds: Rectangle,
19 },
20 Cache(Cache),
21}
22
23#[derive(Debug, Clone)]
24pub struct Cache {
25 pub text: Arc<[Text]>,
26 pub images: Arc<[graphics::Image]>,
27 pub primitives: Arc<[Primitive]>,
28 pub clip_bounds: Rectangle,
29}
30
31impl Cached for Geometry {
32 type Cache = Cache;
33
34 fn load(cache: &Cache) -> Self {
35 Self::Cache(cache.clone())
36 }
37
38 fn cache(self, _group: cache::Group, _previous: Option<Cache>) -> Cache {
39 match self {
40 Self::Live {
41 primitives,
42 images,
43 text,
44 clip_bounds,
45 } => Cache {
46 primitives: Arc::from(primitives),
47 images: Arc::from(images),
48 text: Arc::from(text),
49 clip_bounds,
50 },
51 Self::Cache(cache) => cache,
52 }
53 }
54}
55
56#[derive(Debug)]
57pub struct Frame {
58 clip_bounds: Rectangle,
59 transform: tiny_skia::Transform,
60 stack: Vec<tiny_skia::Transform>,
61 primitives: Vec<Primitive>,
62 images: Vec<graphics::Image>,
63 text: Vec<Text>,
64}
65
66impl Frame {
67 pub fn new(size: Size) -> Self {
68 Self::with_clip(Rectangle::with_size(size))
69 }
70
71 pub fn with_clip(clip_bounds: Rectangle) -> Self {
72 Self {
73 clip_bounds,
74 stack: Vec::new(),
75 primitives: Vec::new(),
76 images: Vec::new(),
77 text: Vec::new(),
78 transform: tiny_skia::Transform::from_translate(
79 clip_bounds.x,
80 clip_bounds.y,
81 ),
82 }
83 }
84}
85
86impl geometry::frame::Backend for Frame {
87 type Geometry = Geometry;
88
89 fn width(&self) -> f32 {
90 self.clip_bounds.width
91 }
92
93 fn height(&self) -> f32 {
94 self.clip_bounds.height
95 }
96
97 fn size(&self) -> Size {
98 self.clip_bounds.size()
99 }
100
101 fn center(&self) -> Point {
102 Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
103 }
104
105 fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
106 let Some(path) =
107 convert_path(path).and_then(|path| path.transform(self.transform))
108 else {
109 return;
110 };
111
112 let fill = fill.into();
113
114 let mut paint = into_paint(fill.style);
115 paint.shader.transform(self.transform);
116
117 self.primitives.push(Primitive::Fill {
118 path,
119 paint,
120 rule: into_fill_rule(fill.rule),
121 });
122 }
123
124 fn fill_rectangle(
125 &mut self,
126 top_left: Point,
127 size: Size,
128 fill: impl Into<Fill>,
129 ) {
130 let Some(path) = convert_path(&Path::rectangle(top_left, size))
131 .and_then(|path| path.transform(self.transform))
132 else {
133 return;
134 };
135
136 let fill = fill.into();
137
138 let mut paint = tiny_skia::Paint {
139 anti_alias: false,
140 ..into_paint(fill.style)
141 };
142 paint.shader.transform(self.transform);
143
144 self.primitives.push(Primitive::Fill {
145 path,
146 paint,
147 rule: into_fill_rule(fill.rule),
148 });
149 }
150
151 fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
152 let Some(path) =
153 convert_path(path).and_then(|path| path.transform(self.transform))
154 else {
155 return;
156 };
157
158 let stroke = stroke.into();
159 let skia_stroke = into_stroke(&stroke);
160
161 let mut paint = into_paint(stroke.style);
162 paint.shader.transform(self.transform);
163
164 self.primitives.push(Primitive::Stroke {
165 path,
166 paint,
167 stroke: skia_stroke,
168 });
169 }
170
171 fn stroke_rectangle<'a>(
172 &mut self,
173 top_left: Point,
174 size: Size,
175 stroke: impl Into<Stroke<'a>>,
176 ) {
177 self.stroke(&Path::rectangle(top_left, size), stroke);
178 }
179
180 fn fill_text(&mut self, text: impl Into<geometry::Text>) {
181 let text = text.into();
182
183 let (scale_x, scale_y) = self.transform.get_scale();
184
185 if !self.transform.has_skew()
186 && scale_x == scale_y
187 && scale_x > 0.0
188 && scale_y > 0.0
189 {
190 let (bounds, size, line_height) = if self.transform.is_identity() {
191 (
192 Rectangle::new(
193 text.position,
194 Size::new(text.max_width, f32::INFINITY),
195 ),
196 text.size,
197 text.line_height,
198 )
199 } else {
200 let mut position = [tiny_skia::Point {
201 x: text.position.x,
202 y: text.position.y,
203 }];
204
205 self.transform.map_points(&mut position);
206
207 let size = text.size.0 * scale_y;
208
209 let line_height = match text.line_height {
210 LineHeight::Absolute(size) => {
211 LineHeight::Absolute(Pixels(size.0 * scale_y))
212 }
213 LineHeight::Relative(factor) => {
214 LineHeight::Relative(factor)
215 }
216 };
217
218 (
219 Rectangle {
220 x: position[0].x,
221 y: position[0].y,
222 width: text.max_width * scale_x,
223 height: f32::INFINITY,
224 },
225 size.into(),
226 line_height,
227 )
228 };
229
230 self.text.push(Text::Cached {
232 content: text.content,
233 bounds,
234 color: text.color,
235 size,
236 line_height: line_height.to_absolute(size),
237 font: text.font,
238 align_x: text.align_x,
239 align_y: text.align_y,
240 shaping: text.shaping,
241 clip_bounds: Rectangle::with_size(Size::INFINITY),
242 });
243 } else {
244 text.draw_with(|path, color| self.fill(&path, color));
245 }
246 }
247
248 fn stroke_text<'a>(
249 &mut self,
250 text: impl Into<geometry::Text>,
251 stroke: impl Into<Stroke<'a>>,
252 ) {
253 let text = text.into();
254 let stroke = stroke.into();
255
256 text.draw_with(|path, _color| self.stroke(&path, stroke));
257 }
258
259 fn push_transform(&mut self) {
260 self.stack.push(self.transform);
261 }
262
263 fn pop_transform(&mut self) {
264 self.transform = self.stack.pop().expect("Pop transform");
265 }
266
267 fn draft(&mut self, clip_bounds: Rectangle) -> Self {
268 Self::with_clip(clip_bounds)
269 }
270
271 fn paste(&mut self, frame: Self) {
272 self.primitives.extend(frame.primitives);
273 self.text.extend(frame.text);
274 self.images.extend(frame.images);
275 }
276
277 fn translate(&mut self, translation: Vector) {
278 self.transform =
279 self.transform.pre_translate(translation.x, translation.y);
280 }
281
282 fn rotate(&mut self, angle: impl Into<Radians>) {
283 self.transform = self.transform.pre_concat(
284 tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
285 );
286 }
287
288 fn scale(&mut self, scale: impl Into<f32>) {
289 let scale = scale.into();
290
291 self.scale_nonuniform(Vector { x: scale, y: scale });
292 }
293
294 fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
295 let scale = scale.into();
296
297 self.transform = self.transform.pre_scale(scale.x, scale.y);
298 }
299
300 fn into_geometry(self) -> Geometry {
301 Geometry::Live {
302 primitives: self.primitives,
303 images: self.images,
304 text: self.text,
305 clip_bounds: self.clip_bounds,
306 }
307 }
308
309 fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
310 let mut image = image.into();
311
312 let (bounds, external_rotation) =
313 transform_rectangle(bounds, self.transform);
314
315 image.rotation += external_rotation;
316
317 self.images.push(graphics::Image::Raster(image, bounds));
318 }
319
320 fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
321 let mut svg = svg.into();
322
323 let (bounds, external_rotation) =
324 transform_rectangle(bounds, self.transform);
325
326 svg.rotation += external_rotation;
327
328 self.images.push(Image::Vector(svg, bounds));
329 }
330}
331
332fn transform_rectangle(
333 rectangle: Rectangle,
334 transform: tiny_skia::Transform,
335) -> (Rectangle, Radians) {
336 let mut top_left = tiny_skia::Point {
337 x: rectangle.x,
338 y: rectangle.y,
339 };
340
341 let mut top_right = tiny_skia::Point {
342 x: rectangle.x + rectangle.width,
343 y: rectangle.y,
344 };
345
346 let mut bottom_left = tiny_skia::Point {
347 x: rectangle.x,
348 y: rectangle.y + rectangle.height,
349 };
350
351 transform.map_point(&mut top_left);
352 transform.map_point(&mut top_right);
353 transform.map_point(&mut bottom_left);
354
355 Rectangle::with_vertices(
356 Point::new(top_left.x, top_left.y),
357 Point::new(top_right.x, top_right.y),
358 Point::new(bottom_left.x, bottom_left.y),
359 )
360}
361
362fn convert_path(path: &Path) -> Option<tiny_skia::Path> {
363 use iced_graphics::geometry::path::lyon_path;
364
365 let mut builder = tiny_skia::PathBuilder::new();
366 let mut last_point = lyon_path::math::Point::default();
367
368 for event in path.raw() {
369 match event {
370 lyon_path::Event::Begin { at } => {
371 builder.move_to(at.x, at.y);
372
373 last_point = at;
374 }
375 lyon_path::Event::Line { from, to } => {
376 if last_point != from {
377 builder.move_to(from.x, from.y);
378 }
379
380 builder.line_to(to.x, to.y);
381
382 last_point = to;
383 }
384 lyon_path::Event::Quadratic { from, ctrl, to } => {
385 if last_point != from {
386 builder.move_to(from.x, from.y);
387 }
388
389 builder.quad_to(ctrl.x, ctrl.y, to.x, to.y);
390
391 last_point = to;
392 }
393 lyon_path::Event::Cubic {
394 from,
395 ctrl1,
396 ctrl2,
397 to,
398 } => {
399 if last_point != from {
400 builder.move_to(from.x, from.y);
401 }
402
403 builder
404 .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y);
405
406 last_point = to;
407 }
408 lyon_path::Event::End { close, .. } => {
409 if close {
410 builder.close();
411 }
412 }
413 }
414 }
415
416 let result = builder.finish();
417
418 #[cfg(debug_assertions)]
419 if result.is_none() {
420 log::warn!("Invalid path: {:?}", path.raw());
421 }
422
423 result
424}
425
426pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
427 tiny_skia::Paint {
428 shader: match style {
429 Style::Solid(color) => tiny_skia::Shader::SolidColor(
430 tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
431 .expect("Create color"),
432 ),
433 Style::Gradient(gradient) => match gradient {
434 Gradient::Linear(linear) => {
435 let stops: Vec<tiny_skia::GradientStop> = linear
436 .stops
437 .into_iter()
438 .flatten()
439 .map(|stop| {
440 tiny_skia::GradientStop::new(
441 stop.offset,
442 tiny_skia::Color::from_rgba(
443 stop.color.b,
444 stop.color.g,
445 stop.color.r,
446 stop.color.a,
447 )
448 .expect("Create color"),
449 )
450 })
451 .collect();
452
453 tiny_skia::LinearGradient::new(
454 tiny_skia::Point {
455 x: linear.start.x,
456 y: linear.start.y,
457 },
458 tiny_skia::Point {
459 x: linear.end.x,
460 y: linear.end.y,
461 },
462 if stops.is_empty() {
463 vec![tiny_skia::GradientStop::new(
464 0.0,
465 tiny_skia::Color::BLACK,
466 )]
467 } else {
468 stops
469 },
470 tiny_skia::SpreadMode::Pad,
471 tiny_skia::Transform::identity(),
472 )
473 .expect("Create linear gradient")
474 }
475 },
476 },
477 anti_alias: true,
478 ..Default::default()
479 }
480}
481
482pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule {
483 match rule {
484 fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd,
485 fill::Rule::NonZero => tiny_skia::FillRule::Winding,
486 }
487}
488
489pub fn into_stroke(stroke: &Stroke<'_>) -> tiny_skia::Stroke {
490 tiny_skia::Stroke {
491 width: stroke.width,
492 line_cap: match stroke.line_cap {
493 stroke::LineCap::Butt => tiny_skia::LineCap::Butt,
494 stroke::LineCap::Square => tiny_skia::LineCap::Square,
495 stroke::LineCap::Round => tiny_skia::LineCap::Round,
496 },
497 line_join: match stroke.line_join {
498 stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter,
499 stroke::LineJoin::Round => tiny_skia::LineJoin::Round,
500 stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel,
501 },
502 dash: if stroke.line_dash.segments.is_empty() {
503 None
504 } else {
505 tiny_skia::StrokeDash::new(
506 stroke.line_dash.segments.into(),
507 stroke.line_dash.offset as f32,
508 )
509 },
510 ..Default::default()
511 }
512}