iced_tiny_skia/
geometry.rs

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            // TODO: Honor layering!
231            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}