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