mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-26 02:28:12 +08:00
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:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
5c99cdef7f
commit
3e08802c44
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user