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 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}