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