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(image, bounds));
311    }
312
313    fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
314        let mut svg = svg.into();
315
316        let (bounds, external_rotation) =
317            transform_rectangle(bounds, self.transform);
318
319        svg.rotation += external_rotation;
320
321        self.images.push(Image::Vector(svg, bounds));
322    }
323}
324
325fn transform_rectangle(
326    rectangle: Rectangle,
327    transform: tiny_skia::Transform,
328) -> (Rectangle, Radians) {
329    let mut top_left = tiny_skia::Point {
330        x: rectangle.x,
331        y: rectangle.y,
332    };
333
334    let mut top_right = tiny_skia::Point {
335        x: rectangle.x + rectangle.width,
336        y: rectangle.y,
337    };
338
339    let mut bottom_left = tiny_skia::Point {
340        x: rectangle.x,
341        y: rectangle.y + rectangle.height,
342    };
343
344    transform.map_point(&mut top_left);
345    transform.map_point(&mut top_right);
346    transform.map_point(&mut bottom_left);
347
348    Rectangle::with_vertices(
349        Point::new(top_left.x, top_left.y),
350        Point::new(top_right.x, top_right.y),
351        Point::new(bottom_left.x, bottom_left.y),
352    )
353}
354
355fn convert_path(path: &Path) -> Option<tiny_skia::Path> {
356    use iced_graphics::geometry::path::lyon_path;
357
358    let mut builder = tiny_skia::PathBuilder::new();
359    let mut last_point = lyon_path::math::Point::default();
360
361    for event in path.raw() {
362        match event {
363            lyon_path::Event::Begin { at } => {
364                builder.move_to(at.x, at.y);
365
366                last_point = at;
367            }
368            lyon_path::Event::Line { from, to } => {
369                if last_point != from {
370                    builder.move_to(from.x, from.y);
371                }
372
373                builder.line_to(to.x, to.y);
374
375                last_point = to;
376            }
377            lyon_path::Event::Quadratic { from, ctrl, to } => {
378                if last_point != from {
379                    builder.move_to(from.x, from.y);
380                }
381
382                builder.quad_to(ctrl.x, ctrl.y, to.x, to.y);
383
384                last_point = to;
385            }
386            lyon_path::Event::Cubic {
387                from,
388                ctrl1,
389                ctrl2,
390                to,
391            } => {
392                if last_point != from {
393                    builder.move_to(from.x, from.y);
394                }
395
396                builder
397                    .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y);
398
399                last_point = to;
400            }
401            lyon_path::Event::End { close, .. } => {
402                if close {
403                    builder.close();
404                }
405            }
406        }
407    }
408
409    let result = builder.finish();
410
411    #[cfg(debug_assertions)]
412    if result.is_none() {
413        log::warn!("Invalid path: {:?}", path.raw());
414    }
415
416    result
417}
418
419pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
420    tiny_skia::Paint {
421        shader: match style {
422            Style::Solid(color) => tiny_skia::Shader::SolidColor(
423                tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
424                    .expect("Create color"),
425            ),
426            Style::Gradient(gradient) => match gradient {
427                Gradient::Linear(linear) => {
428                    let stops: Vec<tiny_skia::GradientStop> = linear
429                        .stops
430                        .into_iter()
431                        .flatten()
432                        .map(|stop| {
433                            tiny_skia::GradientStop::new(
434                                stop.offset,
435                                tiny_skia::Color::from_rgba(
436                                    stop.color.b,
437                                    stop.color.g,
438                                    stop.color.r,
439                                    stop.color.a,
440                                )
441                                .expect("Create color"),
442                            )
443                        })
444                        .collect();
445
446                    tiny_skia::LinearGradient::new(
447                        tiny_skia::Point {
448                            x: linear.start.x,
449                            y: linear.start.y,
450                        },
451                        tiny_skia::Point {
452                            x: linear.end.x,
453                            y: linear.end.y,
454                        },
455                        if stops.is_empty() {
456                            vec![tiny_skia::GradientStop::new(
457                                0.0,
458                                tiny_skia::Color::BLACK,
459                            )]
460                        } else {
461                            stops
462                        },
463                        tiny_skia::SpreadMode::Pad,
464                        tiny_skia::Transform::identity(),
465                    )
466                    .expect("Create linear gradient")
467                }
468            },
469        },
470        anti_alias: true,
471        ..Default::default()
472    }
473}
474
475pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule {
476    match rule {
477        fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd,
478        fill::Rule::NonZero => tiny_skia::FillRule::Winding,
479    }
480}
481
482pub fn into_stroke(stroke: &Stroke<'_>) -> tiny_skia::Stroke {
483    tiny_skia::Stroke {
484        width: stroke.width,
485        line_cap: match stroke.line_cap {
486            stroke::LineCap::Butt => tiny_skia::LineCap::Butt,
487            stroke::LineCap::Square => tiny_skia::LineCap::Square,
488            stroke::LineCap::Round => tiny_skia::LineCap::Round,
489        },
490        line_join: match stroke.line_join {
491            stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter,
492            stroke::LineJoin::Round => tiny_skia::LineJoin::Round,
493            stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel,
494        },
495        dash: if stroke.line_dash.segments.is_empty() {
496            None
497        } else {
498            tiny_skia::StrokeDash::new(
499                stroke.line_dash.segments.into(),
500                stroke.line_dash.offset as f32,
501            )
502        },
503        ..Default::default()
504    }
505}