Add additional stroke properties (#582)

* Add aditional stroke properties

* Add comment explaining clones for closure

* Improve labels

* Fix doc test

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-04-09 09:28:29 +01:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 5c99cdef7f
commit 3e08802c44
3 changed files with 325 additions and 30 deletions
+129 -8
View File
@@ -5,7 +5,7 @@ use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use std::fmt::{self, Display, Write};
/// Precision of the opacity value in digits after the decimal point.
/// A value of 3 would correspond to a precision of 10^-3.
@@ -141,16 +141,57 @@ impl Fill {
/// The stroke (outline) style of an SVG element.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum LineCap {
Butt,
Round,
Square,
}
impl Display for LineCap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match &self {
LineCap::Butt => "butt",
LineCap::Round => "round",
LineCap::Square => "square",
})
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum LineJoin {
Miter,
Bevel,
Round,
}
impl Display for LineJoin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match &self {
LineJoin::Bevel => "bevel",
LineJoin::Miter => "miter",
LineJoin::Round => "round",
})
}
}
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Stroke {
/// Stroke color
color: Color,
/// Line thickness
width: f32,
dash_lengths: Vec<f32>,
dash_offset: f32,
line_cap: LineCap,
line_join: LineJoin,
miter_limit: f32,
}
impl Stroke {
pub const fn new(color: Color, width: f32) -> Self {
Self { color, width }
pub fn new(color: Color, width: f32) -> Self {
Self { color, width, ..Default::default() }
}
/// Get the current stroke color.
@@ -163,9 +204,84 @@ impl Stroke {
self.width
}
pub fn dash_lengths(&self) -> String {
self.dash_lengths.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", ")
}
pub fn dash_offset(&self) -> f32 {
self.dash_offset
}
pub fn line_cap_index(&self) -> u32 {
self.line_cap as u32
}
pub fn line_join_index(&self) -> u32 {
self.line_join as u32
}
pub fn miter_limit(&self) -> f32 {
self.miter_limit as f32
}
/// Provide the SVG attributes for the stroke.
pub fn render(&self) -> String {
format!(r##" stroke="#{}"{} stroke-width="{}""##, self.color.rgb_hex(), format_opacity("stroke", self.color.a()), self.width)
format!(
r##" stroke="#{}"{} stroke-width="{}" stroke-dasharray="{}" stroke-dashoffset="{}" stroke-linecap="{}" stroke-linejoin="{}" stroke-miterlimit="{}" "##,
self.color.rgb_hex(),
format_opacity("stroke", self.color.a()),
self.width,
self.dash_lengths(),
self.dash_offset,
self.line_cap,
self.line_join,
self.miter_limit
)
}
pub fn with_color(mut self, color: &str) -> Option<Self> {
Color::from_rgba_str(color).or_else(|| Color::from_rgb_str(color)).map(|color| {
self.color = color;
self
})
}
pub fn with_width(mut self, width: f32) -> Self {
self.width = width;
self
}
pub fn with_dash_lengths(mut self, dash_lengths: &str) -> Option<Self> {
dash_lengths
.split(&[',', ' '])
.filter(|x| !x.is_empty())
.map(str::parse::<f32>)
.collect::<Result<Vec<_>, _>>()
.ok()
.map(|lengths| {
self.dash_lengths = lengths;
self
})
}
pub fn with_dash_offset(mut self, dash_offset: f32) -> Self {
self.dash_offset = dash_offset;
self
}
pub fn with_line_cap(mut self, line_cap: LineCap) -> Self {
self.line_cap = line_cap;
self
}
pub fn with_line_join(mut self, line_join: LineJoin) -> Self {
self.line_join = line_join;
self
}
pub fn with_miter_limit(mut self, miter_limit: f32) -> Self {
self.miter_limit = miter_limit;
self
}
}
@@ -175,6 +291,11 @@ impl Default for Stroke {
Self {
width: 0.,
color: Color::from_rgba8(0, 0, 0, 255),
dash_lengths: vec![0.],
dash_offset: 0.,
line_cap: LineCap::Butt,
line_join: LineJoin::Miter,
miter_limit: 4.,
}
}
}
@@ -213,12 +334,12 @@ impl PathStyle {
/// # use graphite_graphene::layers::style::{Fill, Stroke, PathStyle};
/// # use graphite_graphene::color::Color;
/// let stroke = Stroke::new(Color::GREEN, 42.);
/// let style = PathStyle::new(Some(stroke), Fill::None);
/// let style = PathStyle::new(Some(stroke.clone()), Fill::None);
///
/// assert_eq!(style.stroke(), Some(stroke));
/// ```
pub fn stroke(&self) -> Option<Stroke> {
self.stroke
self.stroke.clone()
}
/// Replace the path's [Fill] with a provided one.
@@ -251,7 +372,7 @@ impl PathStyle {
/// assert_eq!(style.stroke(), None);
///
/// let stroke = Stroke::new(Color::GREEN, 42.);
/// style.set_stroke(stroke);
/// style.set_stroke(stroke.clone());
///
/// assert_eq!(style.stroke(), Some(stroke));
/// ```
@@ -300,7 +421,7 @@ impl PathStyle {
(ViewMode::Outline, _) => Fill::None.render(svg_defs),
(_, fill) => fill.render(svg_defs),
};
let stroke_attribute = match (view_mode, self.stroke) {
let stroke_attribute = match (view_mode, &self.stroke) {
(ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH).render(),
(_, Some(stroke)) => stroke.render(),
(_, None) => String::new(),