Serialize layout data

This commit is contained in:
alonso.torres 2025-02-21 14:54:11 +01:00 committed by Alonso Torres
parent b4f6177be7
commit 80d5272248
17 changed files with 1006 additions and 375 deletions

View file

@ -1,7 +1,8 @@
use crate::shapes::{Fill, ImageFill, Kind, Shape};
use skia_safe::{self as skia, RRect, Rect};
use crate::shapes::{Fill, ImageFill, Shape, Type};
use skia_safe::{self as skia, RRect};
use super::RenderState;
use crate::math::Rect;
fn draw_image_fill_in_container(
render_state: &mut RenderState,
@ -16,7 +17,6 @@ fn draw_image_fill_in_container(
let size = image_fill.size();
let canvas = render_state.surfaces.shape.canvas();
let kind = &shape.kind;
let container = &shape.selrect;
let path_transform = shape.to_path_transform();
let paint = fill.to_paint(container);
@ -54,28 +54,33 @@ fn draw_image_fill_in_container(
canvas.save();
// Set the clipping rectangle to the container bounds
match kind {
Kind::Rect(_, _) => {
match &shape.shape_type {
Type::Rect(_) | Type::Frame(_) => {
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
}
Kind::Circle(_) => {
Type::Circle => {
let mut oval_path = skia::Path::new();
oval_path.add_oval(container, None);
canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true);
}
Kind::Path(path) | Kind::Bool(_, path) => {
if let Some(path_transform) = path_transform {
canvas.clip_path(
&path.to_skia_path().transform(&path_transform),
skia::ClipOp::Intersect,
true,
);
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
if let Some(path) = shape_type.path() {
if let Some(path_transform) = path_transform {
canvas.clip_path(
&path.to_skia_path().transform(&path_transform),
skia::ClipOp::Intersect,
true,
);
}
}
}
Kind::SVGRaw(_) => {
Type::SVGRaw(_) => {
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
}
Kind::Group(_) => unreachable!("A group should not have fills"),
Type::Text => {
// TODO: Text fill
}
Type::Group(_) => unreachable!("A group should not have fills"),
}
// Draw the image with the calculated destination rectangle
@ -94,31 +99,34 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) {
let canvas = render_state.surfaces.shape.canvas();
let selrect = shape.selrect;
let path_transform = shape.to_path_transform();
let kind = &shape.kind;
match (fill, kind) {
match (fill, &shape.shape_type) {
(Fill::Image(image_fill), _) => {
draw_image_fill_in_container(render_state, shape, fill, image_fill);
}
(_, Kind::Rect(rect, None)) => {
canvas.draw_rect(rect, &fill.to_paint(&selrect));
(_, Type::Rect(_) | Type::Frame(_)) => {
if let Some(corners) = shape.shape_type.corners() {
let rrect = RRect::new_rect_radii(selrect, &corners);
canvas.draw_rrect(rrect, &fill.to_paint(&selrect));
} else {
canvas.draw_rect(selrect, &fill.to_paint(&selrect));
}
}
(_, Kind::Rect(rect, Some(corners))) => {
let rrect = RRect::new_rect_radii(rect, &corners);
canvas.draw_rrect(rrect, &fill.to_paint(&selrect));
(_, Type::Circle) => {
canvas.draw_oval(selrect, &fill.to_paint(&selrect));
}
(_, Kind::Circle(rect)) => {
canvas.draw_oval(rect, &fill.to_paint(&selrect));
}
(_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => {
let svg_attrs = &shape.svg_attrs;
let mut skia_path = &mut path.to_skia_path();
(_, Type::Path(_)) | (_, Type::Bool(_)) => {
if let Some(path) = &shape.shape_type.path() {
let svg_attrs = &shape.svg_attrs;
let mut skia_path = &mut path.to_skia_path();
if let Some(path_transform) = path_transform {
skia_path = skia_path.transform(&path_transform);
if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) {
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
if let Some(path_transform) = path_transform {
skia_path = skia_path.transform(&path_transform);
if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) {
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
}
canvas.draw_path(&skia_path, &fill.to_paint(&selrect));
}
canvas.draw_path(&skia_path, &fill.to_paint(&selrect));
}
}
(_, _) => unreachable!("This shape should not have fills"),

View file

@ -1,7 +1,8 @@
use std::collections::HashMap;
use crate::shapes::{Corners, Fill, ImageFill, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
use skia::Rect;
use crate::math::{Matrix, Point, Rect};
use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type};
use skia_safe::{self as skia, RRect};
use super::RenderState;
@ -55,7 +56,7 @@ fn draw_stroke_on_path(
stroke: &Stroke,
path: &Path,
selrect: &Rect,
path_transform: Option<&skia::Matrix>,
path_transform: Option<&Matrix>,
svg_attrs: &HashMap<String, String>,
scale: f32,
) {
@ -110,8 +111,8 @@ fn handle_stroke_cap(
cap: StrokeCap,
width: f32,
paint: &mut skia::Paint,
p1: &skia::Point,
p2: &skia::Point,
p1: &Point,
p2: &Point,
) {
paint.set_style(skia::PaintStyle::Fill);
paint.set_blend_mode(skia::BlendMode::Src);
@ -154,7 +155,7 @@ fn handle_stroke_caps(
dpr_scale: f32,
) {
let points_count = path.count_points();
let mut points = vec![skia::Point::default(); points_count];
let mut points = vec![Point::default(); points_count];
let c_points = path.get_points(&mut points);
// Closed shapes don't have caps
@ -186,8 +187,8 @@ fn handle_stroke_caps(
fn draw_square_cap(
canvas: &skia::Canvas,
paint: &skia::Paint,
center: &skia::Point,
direction: &skia::Point,
center: &Point,
direction: &Point,
size: f32,
extra_rotation: f32,
) {
@ -195,27 +196,27 @@ fn draw_square_cap(
let dy = direction.y - center.y;
let angle = dy.atan2(dx);
let mut matrix = skia::Matrix::new_identity();
let mut matrix = Matrix::new_identity();
matrix.pre_rotate(
angle.to_degrees() + extra_rotation,
skia::Point::new(center.x, center.y),
Point::new(center.x, center.y),
);
let half_size = size / 2.0;
let rect = skia::Rect::from_xywh(center.x - half_size, center.y - half_size, size, size);
let rect = Rect::from_xywh(center.x - half_size, center.y - half_size, size, size);
let points = [
skia::Point::new(rect.left(), rect.top()),
skia::Point::new(rect.right(), rect.top()),
skia::Point::new(rect.right(), rect.bottom()),
skia::Point::new(rect.left(), rect.bottom()),
Point::new(rect.left(), rect.top()),
Point::new(rect.right(), rect.top()),
Point::new(rect.right(), rect.bottom()),
Point::new(rect.left(), rect.bottom()),
];
let mut transformed_points = points.clone();
matrix.map_points(&mut transformed_points, &points);
let mut path = skia::Path::new();
path.move_to(skia::Point::new(center.x, center.y));
path.move_to(Point::new(center.x, center.y));
path.move_to(transformed_points[0]);
path.line_to(transformed_points[1]);
path.line_to(transformed_points[2]);
@ -227,25 +228,22 @@ fn draw_square_cap(
fn draw_arrow_cap(
canvas: &skia::Canvas,
paint: &skia::Paint,
center: &skia::Point,
direction: &skia::Point,
center: &Point,
direction: &Point,
size: f32,
) {
let dx = direction.x - center.x;
let dy = direction.y - center.y;
let angle = dy.atan2(dx);
let mut matrix = skia::Matrix::new_identity();
matrix.pre_rotate(
angle.to_degrees() - 90.,
skia::Point::new(center.x, center.y),
);
let mut matrix = Matrix::new_identity();
matrix.pre_rotate(angle.to_degrees() - 90., Point::new(center.x, center.y));
let half_height = size / 2.;
let points = [
skia::Point::new(center.x, center.y - half_height),
skia::Point::new(center.x - size, center.y + half_height),
skia::Point::new(center.x + size, center.y + half_height),
Point::new(center.x, center.y - half_height),
Point::new(center.x - size, center.y + half_height),
Point::new(center.x + size, center.y + half_height),
];
let mut transformed_points = points.clone();
@ -255,7 +253,7 @@ fn draw_arrow_cap(
path.move_to(transformed_points[1]);
path.line_to(transformed_points[0]);
path.line_to(transformed_points[2]);
path.move_to(skia::Point::new(center.x, center.y));
path.move_to(Point::new(center.x, center.y));
path.line_to(transformed_points[0]);
canvas.draw_path(&path, paint);
@ -264,25 +262,22 @@ fn draw_arrow_cap(
fn draw_triangle_cap(
canvas: &skia::Canvas,
paint: &skia::Paint,
center: &skia::Point,
direction: &skia::Point,
center: &Point,
direction: &Point,
size: f32,
) {
let dx = direction.x - center.x;
let dy = direction.y - center.y;
let angle = dy.atan2(dx);
let mut matrix = skia::Matrix::new_identity();
matrix.pre_rotate(
angle.to_degrees() - 90.,
skia::Point::new(center.x, center.y),
);
let mut matrix = Matrix::new_identity();
matrix.pre_rotate(angle.to_degrees() - 90., Point::new(center.x, center.y));
let half_height = size / 2.;
let points = [
skia::Point::new(center.x, center.y - half_height),
skia::Point::new(center.x - size, center.y + half_height),
skia::Point::new(center.x + size, center.y + half_height),
Point::new(center.x, center.y - half_height),
Point::new(center.x - size, center.y + half_height),
Point::new(center.x + size, center.y + half_height),
];
let mut transformed_points = points.clone();
@ -336,7 +331,6 @@ fn draw_image_stroke_in_container(
let size = image_fill.size();
let canvas = render_state.surfaces.shape.canvas();
let kind = &shape.kind;
let container = &shape.selrect;
let path_transform = shape.to_path_transform();
let svg_attrs = &shape.svg_attrs;
@ -349,56 +343,65 @@ fn draw_image_stroke_in_container(
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb);
canvas.save_layer(&layer_rec);
// Draw the stroke based on the kind, we are using this stroke as a "selector" of the area of the image we want to show.
// Draw the stroke based on the shape type, we are using this stroke as
// a "selector" of the area of the image we want to show.
let outer_rect = stroke.outer_rect(container);
match kind {
Kind::Rect(rect, corners) => draw_stroke_on_rect(
canvas,
stroke,
rect,
&outer_rect,
corners,
svg_attrs,
dpr_scale,
),
Kind::Circle(rect) => {
draw_stroke_on_circle(canvas, stroke, rect, &outer_rect, svg_attrs, dpr_scale)
}
Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"),
Kind::Path(p) | Kind::Bool(_, p) => {
canvas.save();
let mut path = p.to_skia_path();
path.transform(&path_transform.unwrap());
let stroke_kind = stroke.render_kind(p.is_open());
match stroke_kind {
StrokeKind::InnerStroke => {
canvas.clip_path(&path, skia::ClipOp::Intersect, true);
}
StrokeKind::CenterStroke => {}
StrokeKind::OuterStroke => {
canvas.clip_path(&path, skia::ClipOp::Difference, true);
}
}
let is_open = p.is_open();
let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale);
canvas.draw_path(&path, &paint);
canvas.restore();
if stroke.render_kind(is_open) == StrokeKind::OuterStroke {
// Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts
paint.set_stroke_width(1. / dpr_scale);
canvas.draw_path(&path, &paint);
}
handle_stroke_caps(
&mut path,
stroke,
&outer_rect,
match &shape.shape_type {
shape_type @ (Type::Rect(_) | Type::Frame(_)) => {
draw_stroke_on_rect(
canvas,
is_open,
stroke,
container,
&outer_rect,
&shape_type.corners(),
svg_attrs,
dpr_scale,
);
}
Type::Circle => {
draw_stroke_on_circle(canvas, stroke, container, &outer_rect, svg_attrs, dpr_scale)
}
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
if let Some(p) = shape_type.path() {
canvas.save();
let mut path = p.to_skia_path();
path.transform(&path_transform.unwrap());
let stroke_kind = stroke.render_kind(p.is_open());
match stroke_kind {
StrokeKind::InnerStroke => {
canvas.clip_path(&path, skia::ClipOp::Intersect, true);
}
StrokeKind::CenterStroke => {}
StrokeKind::OuterStroke => {
canvas.clip_path(&path, skia::ClipOp::Difference, true);
}
}
let is_open = p.is_open();
let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, dpr_scale);
canvas.draw_path(&path, &paint);
canvas.restore();
if stroke.render_kind(is_open) == StrokeKind::OuterStroke {
// Small extra inner stroke to overlap with the fill and avoid unnecesary artifacts
paint.set_stroke_width(1. / dpr_scale);
canvas.draw_path(&path, &paint);
}
handle_stroke_caps(
&mut path,
stroke,
&outer_rect,
canvas,
is_open,
svg_attrs,
dpr_scale,
);
}
}
_ => unreachable!("This shape should not have strokes"),
}
// Draw the image. We are using now the SrcIn blend mode, so the rendered piece of image will the area of the stroke over the image.
let mut image_paint = skia::Paint::default();
image_paint.set_blend_mode(skia::BlendMode::SrcIn);
@ -410,7 +413,7 @@ fn draw_image_stroke_in_container(
canvas.draw_image_rect(image.unwrap(), None, dest_rect, &image_paint);
// Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area.
if let Kind::Path(p) = kind {
if let Type::Path(p) = &shape.shape_type {
if stroke.render_kind(p.is_open()) == StrokeKind::OuterStroke {
let mut path = p.to_skia_path();
path.transform(&path_transform.unwrap());
@ -433,31 +436,41 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) {
let dpr_scale = render_state.viewbox.zoom * render_state.options.dpr();
let selrect = shape.selrect;
let path_transform = shape.to_path_transform();
let kind = &shape.kind;
let svg_attrs = &shape.svg_attrs;
if let Fill::Image(image_fill) = &stroke.fill {
draw_image_stroke_in_container(render_state, shape, stroke, image_fill);
} else {
match kind {
Kind::Rect(rect, corners) => draw_stroke_on_rect(
canvas, stroke, rect, &selrect, corners, svg_attrs, dpr_scale,
),
Kind::Circle(rect) => {
draw_stroke_on_circle(canvas, stroke, rect, &selrect, &svg_attrs, dpr_scale)
}
Kind::Path(path) | Kind::Bool(_, path) => {
let svg_attrs = &shape.svg_attrs;
draw_stroke_on_path(
match &shape.shape_type {
shape_type @ (Type::Rect(_) | Type::Frame(_)) => {
draw_stroke_on_rect(
canvas,
stroke,
path,
&selrect,
path_transform.as_ref(),
&selrect,
&shape_type.corners(),
svg_attrs,
dpr_scale,
);
}
Kind::SVGRaw(_) | Kind::Group(_) => unreachable!("This shape should not have strokes"),
Type::Circle => {
draw_stroke_on_circle(canvas, stroke, &selrect, &selrect, &svg_attrs, dpr_scale)
}
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
if let Some(path) = shape_type.path() {
let svg_attrs = &shape.svg_attrs;
draw_stroke_on_path(
canvas,
stroke,
path,
&selrect,
path_transform.as_ref(),
svg_attrs,
dpr_scale,
);
}
}
_ => unreachable!("This shape should not have strokes"),
}
}
}