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