summaryrefslogtreecommitdiffstats
path: root/tiny_skia/src/backend.rs
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2023-05-11 16:45:08 +0200
committerLibravatar GitHub <noreply@github.com>2023-05-11 16:45:08 +0200
commit669f7cc74b2e7918e86a8197916f503f2d3d9b93 (patch)
treeacb365358235be6ce115b50db9404d890b6e77a6 /tiny_skia/src/backend.rs
parentbc62013b6cde52174bf4c4286939cf170bfa7760 (diff)
parent63d3fc6996b848e10e77e6924bfebdf6ba82852e (diff)
downloadiced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.tar.gz
iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.tar.bz2
iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.zip
Merge pull request #1830 from iced-rs/advanced-text
Advanced text
Diffstat (limited to 'tiny_skia/src/backend.rs')
-rw-r--r--tiny_skia/src/backend.rs698
1 files changed, 698 insertions, 0 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
new file mode 100644
index 00000000..d481bacd
--- /dev/null
+++ b/tiny_skia/src/backend.rs
@@ -0,0 +1,698 @@
+use crate::core::text;
+use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
+use crate::graphics::backend;
+use crate::graphics::{Primitive, Viewport};
+use crate::Settings;
+
+use std::borrow::Cow;
+
+pub struct Backend {
+ default_font: Font,
+ default_text_size: f32,
+ text_pipeline: crate::text::Pipeline,
+
+ #[cfg(feature = "image")]
+ raster_pipeline: crate::raster::Pipeline,
+
+ #[cfg(feature = "svg")]
+ vector_pipeline: crate::vector::Pipeline,
+}
+
+impl Backend {
+ pub fn new(settings: Settings) -> Self {
+ Self {
+ default_font: settings.default_font,
+ default_text_size: settings.default_text_size,
+ text_pipeline: crate::text::Pipeline::new(),
+
+ #[cfg(feature = "image")]
+ raster_pipeline: crate::raster::Pipeline::new(),
+
+ #[cfg(feature = "svg")]
+ vector_pipeline: crate::vector::Pipeline::new(),
+ }
+ }
+
+ pub fn draw<T: AsRef<str>>(
+ &mut self,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ primitives: &[Primitive],
+ viewport: &Viewport,
+ damage: &[Rectangle],
+ background_color: Color,
+ overlay: &[T],
+ ) {
+ let physical_size = viewport.physical_size();
+ let scale_factor = viewport.scale_factor() as f32;
+
+ if !overlay.is_empty() {
+ let path = tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ 0.0,
+ 0.0,
+ physical_size.width as f32,
+ physical_size.height as f32,
+ )
+ .expect("Create damage rectangle"),
+ );
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(Color {
+ a: 0.1,
+ ..background_color
+ })),
+ anti_alias: false,
+ ..Default::default()
+ },
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
+ None,
+ );
+ }
+
+ for &region in damage {
+ let path = tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ region.x,
+ region.y,
+ region.width,
+ region.height,
+ )
+ .expect("Create damage rectangle"),
+ );
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ background_color,
+ )),
+ anti_alias: false,
+ ..Default::default()
+ },
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
+ None,
+ );
+
+ adjust_clip_mask(clip_mask, region);
+
+ for primitive in primitives {
+ self.draw_primitive(
+ primitive,
+ pixels,
+ clip_mask,
+ region,
+ scale_factor,
+ Vector::ZERO,
+ );
+ }
+
+ if !overlay.is_empty() {
+ pixels.stroke_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ Color::from_rgb(1.0, 0.0, 0.0),
+ )),
+ anti_alias: false,
+ ..tiny_skia::Paint::default()
+ },
+ &tiny_skia::Stroke {
+ width: 1.0,
+ ..tiny_skia::Stroke::default()
+ },
+ tiny_skia::Transform::identity(),
+ None,
+ );
+ }
+ }
+
+ self.text_pipeline.trim_cache();
+
+ #[cfg(feature = "image")]
+ self.raster_pipeline.trim_cache();
+
+ #[cfg(feature = "svg")]
+ self.vector_pipeline.trim_cache();
+ }
+
+ fn draw_primitive(
+ &mut self,
+ primitive: &Primitive,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ clip_bounds: Rectangle,
+ scale_factor: f32,
+ translation: Vector,
+ ) {
+ match primitive {
+ Primitive::Quad {
+ bounds,
+ background,
+ border_radius,
+ border_width,
+ border_color,
+ } => {
+ let physical_bounds = (*bounds + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ let transform = tiny_skia::Transform::from_translate(
+ translation.x,
+ translation.y,
+ )
+ .post_scale(scale_factor, scale_factor);
+
+ let path = rounded_rectangle(*bounds, *border_radius);
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: match background {
+ Background::Color(color) => {
+ tiny_skia::Shader::SolidColor(into_color(
+ *color,
+ ))
+ }
+ },
+ anti_alias: true,
+ ..tiny_skia::Paint::default()
+ },
+ tiny_skia::FillRule::EvenOdd,
+ transform,
+ clip_mask,
+ );
+
+ if *border_width > 0.0 {
+ pixels.stroke_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ *border_color,
+ )),
+ anti_alias: true,
+ ..tiny_skia::Paint::default()
+ },
+ &tiny_skia::Stroke {
+ width: *border_width,
+ ..tiny_skia::Stroke::default()
+ },
+ transform,
+ clip_mask,
+ );
+ }
+ }
+ Primitive::Text {
+ content,
+ bounds,
+ color,
+ size,
+ line_height,
+ font,
+ horizontal_alignment,
+ vertical_alignment,
+ shaping,
+ } => {
+ let physical_bounds =
+ (primitive.bounds() + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.text_pipeline.draw(
+ content,
+ *bounds + translation,
+ *color,
+ *size,
+ *line_height,
+ *font,
+ *horizontal_alignment,
+ *vertical_alignment,
+ *shaping,
+ scale_factor,
+ pixels,
+ clip_mask,
+ );
+ }
+ #[cfg(feature = "image")]
+ Primitive::Image { handle, bounds } => {
+ let physical_bounds = (*bounds + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ let transform = tiny_skia::Transform::from_translate(
+ translation.x,
+ translation.y,
+ )
+ .post_scale(scale_factor, scale_factor);
+
+ self.raster_pipeline
+ .draw(handle, *bounds, pixels, transform, clip_mask);
+ }
+ #[cfg(feature = "svg")]
+ Primitive::Svg {
+ handle,
+ bounds,
+ color,
+ } => {
+ let physical_bounds = (*bounds + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ self.vector_pipeline.draw(
+ handle,
+ *color,
+ (*bounds + translation) * scale_factor,
+ pixels,
+ clip_mask,
+ );
+ }
+ Primitive::Fill {
+ path,
+ paint,
+ rule,
+ transform,
+ } => {
+ let bounds = path.bounds();
+
+ let physical_bounds = (Rectangle {
+ x: bounds.x(),
+ y: bounds.y(),
+ width: bounds.width(),
+ height: bounds.height(),
+ } + translation)
+ * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ pixels.fill_path(
+ path,
+ paint,
+ *rule,
+ transform
+ .post_translate(translation.x, translation.y)
+ .post_scale(scale_factor, scale_factor),
+ clip_mask,
+ );
+ }
+ Primitive::Stroke {
+ path,
+ paint,
+ stroke,
+ transform,
+ } => {
+ let bounds = path.bounds();
+
+ let physical_bounds = (Rectangle {
+ x: bounds.x(),
+ y: bounds.y(),
+ width: bounds.width(),
+ height: bounds.height(),
+ } + translation)
+ * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ pixels.stroke_path(
+ path,
+ paint,
+ stroke,
+ transform
+ .post_translate(translation.x, translation.y)
+ .post_scale(scale_factor, scale_factor),
+ clip_mask,
+ );
+ }
+ Primitive::Group { primitives } => {
+ for primitive in primitives {
+ self.draw_primitive(
+ primitive,
+ pixels,
+ clip_mask,
+ clip_bounds,
+ scale_factor,
+ translation,
+ );
+ }
+ }
+ Primitive::Translate {
+ translation: offset,
+ content,
+ } => {
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ clip_bounds,
+ scale_factor,
+ translation + *offset,
+ );
+ }
+ Primitive::Clip { bounds, content } => {
+ let bounds = (*bounds + translation) * scale_factor;
+
+ if bounds == clip_bounds {
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ bounds,
+ scale_factor,
+ translation,
+ );
+ } else if let Some(bounds) = clip_bounds.intersection(&bounds) {
+ if bounds.x + bounds.width <= 0.0
+ || bounds.y + bounds.height <= 0.0
+ || bounds.x as u32 >= pixels.width()
+ || bounds.y as u32 >= pixels.height()
+ || bounds.width <= 1.0
+ || bounds.height <= 1.0
+ {
+ return;
+ }
+
+ adjust_clip_mask(clip_mask, bounds);
+
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ bounds,
+ scale_factor,
+ translation,
+ );
+
+ adjust_clip_mask(clip_mask, clip_bounds);
+ }
+ }
+ Primitive::Cache { content } => {
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ clip_bounds,
+ scale_factor,
+ translation,
+ );
+ }
+ Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {
+ // Not supported!
+ // TODO: Draw a placeholder (?)
+ log::warn!(
+ "Unsupported primitive in `iced_tiny_skia`: {:?}",
+ primitive
+ );
+ }
+ _ => {
+ // Not supported!
+ log::warn!(
+ "Unsupported primitive in `iced_tiny_skia`: {:?}",
+ primitive
+ );
+ }
+ }
+ }
+}
+
+fn into_color(color: Color) -> tiny_skia::Color {
+ tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
+ .expect("Convert color from iced to tiny_skia")
+}
+
+fn rounded_rectangle(
+ bounds: Rectangle,
+ border_radius: [f32; 4],
+) -> tiny_skia::Path {
+ let [top_left, top_right, bottom_right, bottom_left] = border_radius;
+
+ if top_left == 0.0
+ && top_right == 0.0
+ && bottom_right == 0.0
+ && bottom_left == 0.0
+ {
+ return tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ )
+ .expect("Build quad rectangle"),
+ );
+ }
+
+ if top_left == top_right
+ && top_left == bottom_right
+ && top_left == bottom_left
+ && top_left == bounds.width / 2.0
+ && top_left == bounds.height / 2.0
+ {
+ return tiny_skia::PathBuilder::from_circle(
+ bounds.x + bounds.width / 2.0,
+ bounds.y + bounds.height / 2.0,
+ top_left,
+ )
+ .expect("Build circle path");
+ }
+
+ let mut builder = tiny_skia::PathBuilder::new();
+
+ builder.move_to(bounds.x + top_left, bounds.y);
+ builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
+
+ if top_right > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x + bounds.width - top_right,
+ bounds.y,
+ bounds.x + bounds.width,
+ bounds.y + top_right,
+ top_right,
+ );
+ }
+
+ maybe_line_to(
+ &mut builder,
+ bounds.x + bounds.width,
+ bounds.y + bounds.height - bottom_right,
+ );
+
+ if bottom_right > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x + bounds.width,
+ bounds.y + bounds.height - bottom_right,
+ bounds.x + bounds.width - bottom_right,
+ bounds.y + bounds.height,
+ bottom_right,
+ );
+ }
+
+ maybe_line_to(
+ &mut builder,
+ bounds.x + bottom_left,
+ bounds.y + bounds.height,
+ );
+
+ if bottom_left > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x + bottom_left,
+ bounds.y + bounds.height,
+ bounds.x,
+ bounds.y + bounds.height - bottom_left,
+ bottom_left,
+ );
+ }
+
+ maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
+
+ if top_left > 0.0 {
+ arc_to(
+ &mut builder,
+ bounds.x,
+ bounds.y + top_left,
+ bounds.x + top_left,
+ bounds.y,
+ top_left,
+ );
+ }
+
+ builder.finish().expect("Build rounded rectangle path")
+}
+
+fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
+ if path.last_point() != Some(tiny_skia::Point { x, y }) {
+ path.line_to(x, y);
+ }
+}
+
+fn arc_to(
+ path: &mut tiny_skia::PathBuilder,
+ x_from: f32,
+ y_from: f32,
+ x_to: f32,
+ y_to: f32,
+ radius: f32,
+) {
+ let svg_arc = kurbo::SvgArc {
+ from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
+ to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
+ radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
+ x_rotation: 0.0,
+ large_arc: false,
+ sweep: true,
+ };
+
+ match kurbo::Arc::from_svg_arc(&svg_arc) {
+ Some(arc) => {
+ arc.to_cubic_beziers(0.1, |p1, p2, p| {
+ path.cubic_to(
+ p1.x as f32,
+ p1.y as f32,
+ p2.x as f32,
+ p2.y as f32,
+ p.x as f32,
+ p.y as f32,
+ );
+ });
+ }
+ None => {
+ path.line_to(x_to, y_to);
+ }
+ }
+}
+
+fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
+ clip_mask.clear();
+
+ let path = {
+ let mut builder = tiny_skia::PathBuilder::new();
+ builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height);
+
+ builder.finish().unwrap()
+ };
+
+ clip_mask.fill_path(
+ &path,
+ tiny_skia::FillRule::EvenOdd,
+ false,
+ tiny_skia::Transform::default(),
+ );
+}
+
+impl iced_graphics::Backend for Backend {
+ fn trim_measurements(&mut self) {
+ self.text_pipeline.trim_measurement_cache();
+ }
+}
+
+impl backend::Text for Backend {
+ const ICON_FONT: Font = Font::with_name("Iced-Icons");
+ const CHECKMARK_ICON: char = '\u{f00c}';
+ const ARROW_DOWN_ICON: char = '\u{e800}';
+
+ fn default_font(&self) -> Font {
+ self.default_font
+ }
+
+ fn default_size(&self) -> f32 {
+ self.default_text_size
+ }
+
+ fn measure(
+ &self,
+ contents: &str,
+ size: f32,
+ line_height: text::LineHeight,
+ font: Font,
+ bounds: Size,
+ shaping: text::Shaping,
+ ) -> (f32, f32) {
+ self.text_pipeline.measure(
+ contents,
+ size,
+ line_height,
+ font,
+ bounds,
+ shaping,
+ )
+ }
+
+ fn hit_test(
+ &self,
+ contents: &str,
+ size: f32,
+ line_height: text::LineHeight,
+ font: Font,
+ bounds: Size,
+ shaping: text::Shaping,
+ point: Point,
+ nearest_only: bool,
+ ) -> Option<text::Hit> {
+ self.text_pipeline.hit_test(
+ contents,
+ size,
+ line_height,
+ font,
+ bounds,
+ shaping,
+ point,
+ nearest_only,
+ )
+ }
+
+ fn load_font(&mut self, font: Cow<'static, [u8]>) {
+ self.text_pipeline.load_font(font);
+ }
+}
+
+#[cfg(feature = "image")]
+impl backend::Image for Backend {
+ fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
+ self.raster_pipeline.dimensions(handle)
+ }
+}
+
+#[cfg(feature = "svg")]
+impl backend::Svg for Backend {
+ fn viewport_dimensions(
+ &self,
+ handle: &crate::core::svg::Handle,
+ ) -> Size<u32> {
+ self.vector_pipeline.viewport_dimensions(handle)
+ }
+}