mirror of
https://github.com/penpot/penpot.git
synced 2025-03-23 05:01:23 -05:00
🎉 Stroke caps support for wasm render
This commit is contained in:
parent
4bd1e32462
commit
13ec04dd65
5 changed files with 288 additions and 35 deletions
|
@ -219,21 +219,35 @@
|
|||
:mixed 3
|
||||
0))
|
||||
|
||||
(defn- translate-stroke-cap
|
||||
[stroke-cap]
|
||||
(case stroke-cap
|
||||
:line-arrow 1
|
||||
:triangle-arrow 2
|
||||
:square-marker 3
|
||||
:circle-marker 4
|
||||
:diamond-marker 5
|
||||
:round 6
|
||||
:square 7
|
||||
0))
|
||||
|
||||
(defn set-shape-strokes
|
||||
[strokes]
|
||||
(h/call internal-module "_clear_shape_strokes")
|
||||
(keep (fn [stroke]
|
||||
(let [opacity (or (:stroke-opacity stroke) 1.0)
|
||||
color (:stroke-color stroke)
|
||||
gradient (:stroke-color-gradient stroke)
|
||||
image (:stroke-image stroke)
|
||||
width (:stroke-width stroke)
|
||||
align (:stroke-alignment stroke)
|
||||
style (-> stroke :stroke-style translate-stroke-style)]
|
||||
(let [opacity (or (:stroke-opacity stroke) 1.0)
|
||||
color (:stroke-color stroke)
|
||||
gradient (:stroke-color-gradient stroke)
|
||||
image (:stroke-image stroke)
|
||||
width (:stroke-width stroke)
|
||||
align (:stroke-alignment stroke)
|
||||
style (-> stroke :stroke-style translate-stroke-style)
|
||||
cap-start (-> stroke :stroke-cap-start translate-stroke-cap)
|
||||
cap-end (-> stroke :stroke-cap-end translate-stroke-cap)]
|
||||
(case align
|
||||
:inner (h/call internal-module "_add_shape_inner_stroke" width style)
|
||||
:outer (h/call internal-module "_add_shape_outer_stroke" width style)
|
||||
(h/call internal-module "_add_shape_center_stroke" width style))
|
||||
:inner (h/call internal-module "_add_shape_inner_stroke" width style cap-start cap-end)
|
||||
:outer (h/call internal-module "_add_shape_outer_stroke" width style cap-start cap-end)
|
||||
(h/call internal-module "_add_shape_center_stroke" width style cap-start cap-end))
|
||||
|
||||
(cond
|
||||
(some? gradient)
|
||||
|
|
|
@ -39,3 +39,29 @@ Gradient stops are serialized in a `Uint8Array`, each stop taking **5 bytes**.
|
|||
**Red**, **Green**, **Blue** and **Alpha** are the RGBA components of the stop.
|
||||
|
||||
**Stop offset** is the offset, being integer values ranging from `0` to `100` (both inclusive).
|
||||
|
||||
## StrokeCap
|
||||
|
||||
Stroke caps are serialized as `u8`:
|
||||
|
||||
| Value | Field |
|
||||
| ----- | --------- |
|
||||
| 1 | Line |
|
||||
| 2 | Triangle |
|
||||
| 3 | Rectangle |
|
||||
| 4 | Circle |
|
||||
| 5 | Diamond |
|
||||
| 6 | Round |
|
||||
| 7 | Square |
|
||||
| _ | None |
|
||||
|
||||
## StrokeStyle
|
||||
|
||||
Stroke styles are serialized as `u8`:
|
||||
|
||||
| Value | Field |
|
||||
| ----- | ------ |
|
||||
| 1 | Dotted |
|
||||
| 2 | Dashed |
|
||||
| 3 | Mixed |
|
||||
| _ | Solid |
|
||||
|
|
|
@ -347,26 +347,32 @@ pub extern "C" fn set_shape_path_content() {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_center_stroke(width: f32, style: i32) {
|
||||
pub extern "C" fn add_shape_center_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.add_stroke(shapes::Stroke::new_center_stroke(width, style));
|
||||
shape.add_stroke(shapes::Stroke::new_center_stroke(
|
||||
width, style, cap_start, cap_end,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_inner_stroke(width: f32, style: i32) {
|
||||
pub extern "C" fn add_shape_inner_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.add_stroke(shapes::Stroke::new_inner_stroke(width, style))
|
||||
shape.add_stroke(shapes::Stroke::new_inner_stroke(
|
||||
width, style, cap_start, cap_end,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_outer_stroke(width: f32, style: i32) {
|
||||
pub extern "C" fn add_shape_outer_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape() {
|
||||
shape.add_stroke(shapes::Stroke::new_outer_stroke(width, style))
|
||||
shape.add_stroke(shapes::Stroke::new_outer_stroke(
|
||||
width, style, cap_start, cap_end,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use skia_safe as skia;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{Fill, Image, Kind, Path, Shape, Stroke, StrokeKind};
|
||||
use super::{Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
|
||||
use crate::math::Rect;
|
||||
use crate::render::{ImageStore, Renderable};
|
||||
|
||||
|
@ -155,6 +155,195 @@ fn draw_stroke_on_circle(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, se
|
|||
canvas.draw_oval(&stroke_rect, &stroke.to_paint(selrect));
|
||||
}
|
||||
|
||||
fn handle_stroke_cap(
|
||||
canvas: &skia::Canvas,
|
||||
cap: StrokeCap,
|
||||
width: f32,
|
||||
paint: &mut skia::Paint,
|
||||
p1: &skia::Point,
|
||||
p2: &skia::Point,
|
||||
) {
|
||||
paint.set_style(skia::PaintStyle::Fill);
|
||||
paint.set_blend_mode(skia::BlendMode::Src);
|
||||
match cap {
|
||||
StrokeCap::None => {}
|
||||
StrokeCap::Line => {
|
||||
paint.set_style(skia::PaintStyle::Stroke);
|
||||
draw_arrow_cap(canvas, &paint, p1, p2, width * 4.);
|
||||
}
|
||||
StrokeCap::Triangle => {
|
||||
draw_triangle_cap(canvas, &paint, p1, p2, width * 4.);
|
||||
}
|
||||
StrokeCap::Rectangle => {
|
||||
draw_square_cap(canvas, &paint, p1, p2, width * 4., 0.);
|
||||
}
|
||||
StrokeCap::Circle => {
|
||||
canvas.draw_circle((p1.x, p1.y), width * 2., &paint);
|
||||
}
|
||||
StrokeCap::Diamond => {
|
||||
draw_square_cap(canvas, &paint, p1, p2, width * 4., 45.);
|
||||
}
|
||||
StrokeCap::Round => {
|
||||
canvas.draw_circle((p1.x, p1.y), width / 2.0, &paint);
|
||||
}
|
||||
StrokeCap::Square => {
|
||||
draw_square_cap(canvas, &paint, p1, p2, width, 0.);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_stroke_caps(
|
||||
path: &mut skia::Path,
|
||||
stroke: &Stroke,
|
||||
selrect: &Rect,
|
||||
canvas: &skia::Canvas,
|
||||
is_open: bool,
|
||||
) {
|
||||
let points_count = path.count_points();
|
||||
let mut points = vec![skia::Point::default(); points_count];
|
||||
let c_points = path.get_points(&mut points);
|
||||
|
||||
// Closed shapes don't have caps
|
||||
if c_points >= 2 && is_open {
|
||||
let first_point = points.first().unwrap();
|
||||
let last_point = points.last().unwrap();
|
||||
|
||||
let kind = stroke.render_kind(is_open);
|
||||
let mut paint_stroke = stroke.to_stroked_paint(kind.clone(), selrect);
|
||||
|
||||
handle_stroke_cap(
|
||||
canvas,
|
||||
stroke.cap_start,
|
||||
stroke.width,
|
||||
&mut paint_stroke,
|
||||
first_point,
|
||||
&points[1],
|
||||
);
|
||||
handle_stroke_cap(
|
||||
canvas,
|
||||
stroke.cap_end,
|
||||
stroke.width,
|
||||
&mut paint_stroke,
|
||||
last_point,
|
||||
&points[points_count - 2],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_square_cap(
|
||||
canvas: &skia::Canvas,
|
||||
paint: &skia::Paint,
|
||||
center: &skia::Point,
|
||||
direction: &skia::Point,
|
||||
size: f32,
|
||||
extra_rotation: 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() + extra_rotation,
|
||||
skia::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 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()),
|
||||
];
|
||||
|
||||
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(transformed_points[0]);
|
||||
path.line_to(transformed_points[1]);
|
||||
path.line_to(transformed_points[2]);
|
||||
path.line_to(transformed_points[3]);
|
||||
path.close();
|
||||
canvas.draw_path(&path, paint);
|
||||
}
|
||||
|
||||
fn draw_arrow_cap(
|
||||
canvas: &skia::Canvas,
|
||||
paint: &skia::Paint,
|
||||
center: &skia::Point,
|
||||
direction: &skia::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 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),
|
||||
];
|
||||
|
||||
let mut transformed_points = points.clone();
|
||||
matrix.map_points(&mut transformed_points, &points);
|
||||
|
||||
let mut path = skia::Path::new();
|
||||
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.line_to(transformed_points[0]);
|
||||
|
||||
canvas.draw_path(&path, paint);
|
||||
}
|
||||
|
||||
fn draw_triangle_cap(
|
||||
canvas: &skia::Canvas,
|
||||
paint: &skia::Paint,
|
||||
center: &skia::Point,
|
||||
direction: &skia::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 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),
|
||||
];
|
||||
|
||||
let mut transformed_points = points.clone();
|
||||
matrix.map_points(&mut transformed_points, &points);
|
||||
|
||||
let mut path = skia::Path::new();
|
||||
path.move_to(transformed_points[0]);
|
||||
path.line_to(transformed_points[1]);
|
||||
path.line_to(transformed_points[2]);
|
||||
path.close();
|
||||
|
||||
canvas.draw_path(&path, paint);
|
||||
}
|
||||
|
||||
fn draw_stroke_on_path(
|
||||
canvas: &skia::Canvas,
|
||||
stroke: &Stroke,
|
||||
|
@ -177,6 +366,7 @@ fn draw_stroke_on_path(
|
|||
// For center stroke we don't need to do anything extra
|
||||
StrokeKind::CenterStroke => {
|
||||
canvas.draw_path(&skia_path, &paint_stroke);
|
||||
handle_stroke_caps(&mut skia_path, stroke, selrect, canvas, path.is_open());
|
||||
}
|
||||
// For outer stroke we draw a center stroke (with double width) and use another path with blend mode clear to remove the inner stroke added
|
||||
StrokeKind::OuterStroke => {
|
||||
|
@ -295,6 +485,7 @@ pub fn draw_image_stroke_in_container(
|
|||
}
|
||||
let paint = stroke.to_stroked_paint(stroke_kind, &outer_rect);
|
||||
canvas.draw_path(&path, &paint);
|
||||
handle_stroke_caps(&mut path, stroke, &outer_rect, canvas, p.is_open());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ pub enum StrokeStyle {
|
|||
Mixed,
|
||||
}
|
||||
|
||||
impl From<i32> for StrokeStyle {
|
||||
fn from(value: i32) -> Self {
|
||||
impl From<u8> for StrokeStyle {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => StrokeStyle::Dotted,
|
||||
2 => StrokeStyle::Dashed,
|
||||
|
@ -21,15 +21,31 @@ impl From<i32> for StrokeStyle {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum StrokeCap {
|
||||
None,
|
||||
// Line,
|
||||
// Triangle,
|
||||
// Circle,
|
||||
// Diamond,
|
||||
// Round,
|
||||
// Square,
|
||||
Line,
|
||||
Triangle,
|
||||
Rectangle,
|
||||
Circle,
|
||||
Diamond,
|
||||
Round,
|
||||
Square,
|
||||
}
|
||||
|
||||
impl From<u8> for StrokeCap {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => StrokeCap::Line,
|
||||
2 => StrokeCap::Triangle,
|
||||
3 => StrokeCap::Rectangle,
|
||||
4 => StrokeCap::Circle,
|
||||
5 => StrokeCap::Diamond,
|
||||
6 => StrokeCap::Round,
|
||||
7 => StrokeCap::Square,
|
||||
_ => StrokeCap::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
@ -59,38 +75,38 @@ impl Stroke {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_center_stroke(width: f32, style: i32) -> Self {
|
||||
pub fn new_center_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) -> Self {
|
||||
let transparent = skia::Color::from_argb(0, 0, 0, 0);
|
||||
Stroke {
|
||||
fill: Fill::Solid(transparent),
|
||||
width: width,
|
||||
style: StrokeStyle::from(style),
|
||||
cap_end: StrokeCap::None,
|
||||
cap_start: StrokeCap::None,
|
||||
cap_end: StrokeCap::from(cap_end),
|
||||
cap_start: StrokeCap::from(cap_start),
|
||||
kind: StrokeKind::CenterStroke,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_inner_stroke(width: f32, style: i32) -> Self {
|
||||
pub fn new_inner_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) -> Self {
|
||||
let transparent = skia::Color::from_argb(0, 0, 0, 0);
|
||||
Stroke {
|
||||
fill: Fill::Solid(transparent),
|
||||
width: width,
|
||||
style: StrokeStyle::from(style),
|
||||
cap_end: StrokeCap::None,
|
||||
cap_start: StrokeCap::None,
|
||||
cap_end: StrokeCap::from(cap_end),
|
||||
cap_start: StrokeCap::from(cap_start),
|
||||
kind: StrokeKind::InnerStroke,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_outer_stroke(width: f32, style: i32) -> Self {
|
||||
pub fn new_outer_stroke(width: f32, style: u8, cap_start: u8, cap_end: u8) -> Self {
|
||||
let transparent = skia::Color::from_argb(0, 0, 0, 0);
|
||||
Stroke {
|
||||
fill: Fill::Solid(transparent),
|
||||
width: width,
|
||||
style: StrokeStyle::from(style),
|
||||
cap_end: StrokeCap::None,
|
||||
cap_start: StrokeCap::None,
|
||||
cap_end: StrokeCap::from(cap_end),
|
||||
cap_start: StrokeCap::from(cap_start),
|
||||
kind: StrokeKind::OuterStroke,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue