diff options
Diffstat (limited to 'tiny_skia/src/geometry.rs')
-rw-r--r-- | tiny_skia/src/geometry.rs | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs new file mode 100644 index 00000000..73fc1ebd --- /dev/null +++ b/tiny_skia/src/geometry.rs @@ -0,0 +1,287 @@ +use crate::{Point, Primitive, Rectangle, Size, Vector}; + +use iced_graphics::geometry::fill::{self, Fill}; +use iced_graphics::geometry::stroke::{self, Stroke}; +use iced_graphics::geometry::{Path, Style, Text}; +use iced_graphics::Gradient; + +pub struct Frame { + size: Size, + transform: tiny_skia::Transform, + stack: Vec<tiny_skia::Transform>, + primitives: Vec<Primitive>, +} + +impl Frame { + pub fn new(size: Size) -> Self { + Self { + size, + transform: tiny_skia::Transform::identity(), + stack: Vec::new(), + primitives: Vec::new(), + } + } + + pub fn width(&self) -> f32 { + self.size.width + } + + pub fn height(&self) -> f32 { + self.size.height + } + + pub fn size(&self) -> Size { + self.size + } + + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { + let path = convert_path(path); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ) { + let path = convert_path(&Path::rectangle(top_left, size)); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }, + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + let path = convert_path(path); + let stroke = stroke.into(); + let skia_stroke = into_stroke(&stroke); + + self.primitives.push(Primitive::Stroke { + path, + paint: into_paint(stroke.style), + stroke: skia_stroke, + transform: self.transform, + }); + } + + pub fn fill_text(&mut self, text: impl Into<Text>) { + let text = text.into(); + + let position = if self.transform.is_identity() { + text.position + } else { + let mut transformed = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut transformed); + + Point::new(transformed[0].x, transformed[0].y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + pub fn push_transform(&mut self) { + self.stack.push(self.transform); + } + + pub fn pop_transform(&mut self) { + self.transform = self.stack.pop().expect("Pop transform"); + } + + pub fn clip(&mut self, _frame: Self, _translation: Vector) {} + + pub fn translate(&mut self, translation: Vector) { + self.transform = + self.transform.pre_translate(translation.x, translation.y); + } + + pub fn rotate(&mut self, angle: f32) { + self.transform = self + .transform + .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); + } + + pub fn scale(&mut self, scale: f32) { + self.transform = self.transform.pre_scale(scale, scale); + } + + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +fn convert_path(path: &Path) -> tiny_skia::Path { + use iced_graphics::geometry::path::lyon_path; + + let mut builder = tiny_skia::PathBuilder::new(); + let mut last_point = Default::default(); + + for event in path.raw().iter() { + match event { + lyon_path::Event::Begin { at } => { + builder.move_to(at.x, at.y); + + last_point = at; + } + lyon_path::Event::Line { from, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.line_to(to.x, to.y); + + last_point = to; + } + lyon_path::Event::Quadratic { from, ctrl, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::Cubic { + from, + ctrl1, + ctrl2, + to, + } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder + .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::End { close, .. } => { + if close { + builder.close(); + } + } + } + } + + builder + .finish() + .expect("Convert lyon path to tiny_skia path") +} + +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { + tiny_skia::Paint { + shader: match style { + Style::Solid(color) => tiny_skia::Shader::SolidColor( + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Create color"), + ), + Style::Gradient(gradient) => match gradient { + Gradient::Linear(linear) => tiny_skia::LinearGradient::new( + tiny_skia::Point { + x: linear.start.x, + y: linear.start.y, + }, + tiny_skia::Point { + x: linear.end.x, + y: linear.end.y, + }, + linear + .color_stops + .into_iter() + .map(|stop| { + tiny_skia::GradientStop::new( + stop.offset, + tiny_skia::Color::from_rgba( + stop.color.b, + stop.color.g, + stop.color.r, + stop.color.a, + ) + .expect("Create color"), + ) + }) + .collect(), + tiny_skia::SpreadMode::Pad, + tiny_skia::Transform::identity(), + ) + .expect("Create linear gradient"), + }, + }, + anti_alias: true, + ..Default::default() + } +} + +pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { + match rule { + fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, + fill::Rule::NonZero => tiny_skia::FillRule::Winding, + } +} + +pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { + tiny_skia::Stroke { + width: stroke.width, + line_cap: match stroke.line_cap { + stroke::LineCap::Butt => tiny_skia::LineCap::Butt, + stroke::LineCap::Square => tiny_skia::LineCap::Square, + stroke::LineCap::Round => tiny_skia::LineCap::Round, + }, + line_join: match stroke.line_join { + stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, + stroke::LineJoin::Round => tiny_skia::LineJoin::Round, + stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, + }, + dash: if stroke.line_dash.segments.is_empty() { + None + } else { + tiny_skia::StrokeDash::new( + stroke.line_dash.segments.into(), + stroke.line_dash.offset as f32, + ) + }, + ..Default::default() + } +} |