use crate::eval::{Cast, FromValue}; use super::*; /// A stroke of a geometric shape. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Stroke { /// The stroke's paint. pub paint: Paint, /// The stroke's thickness. pub thickness: Abs, /// The stroke's line cap. pub line_cap: LineCap, /// The stroke's line join. pub line_join: LineJoin, /// The stroke's line dash pattern. pub dash_pattern: Option>, /// The miter limit. Defaults to 4.0, same as `tiny-skia`. pub miter_limit: Scalar, } impl Default for Stroke { fn default() -> Self { Self { paint: Paint::Solid(Color::BLACK), thickness: Abs::pt(1.0), line_cap: LineCap::Butt, line_join: LineJoin::Miter, dash_pattern: None, miter_limit: Scalar(4.0), } } } /// A partial stroke representation. /// /// In this representation, both fields are optional so that you can pass either /// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where /// this is expected. #[derive(Default, Clone, Eq, PartialEq, Hash)] pub struct PartialStroke { /// The stroke's paint. pub paint: Smart, /// The stroke's thickness. pub thickness: Smart, /// The stroke's line cap. pub line_cap: Smart, /// The stroke's line join. pub line_join: Smart, /// The stroke's line dash pattern. pub dash_pattern: Smart>>, /// The miter limit. pub miter_limit: Smart, } impl PartialStroke { /// Map the contained lengths with `f`. pub fn map(self, f: F) -> PartialStroke where F: Fn(T) -> U, { PartialStroke { paint: self.paint, thickness: self.thickness.map(&f), line_cap: self.line_cap, line_join: self.line_join, dash_pattern: self.dash_pattern.map(|pattern| { pattern.map(|pattern| DashPattern { array: pattern .array .into_iter() .map(|l| match l { DashLength::Length(v) => DashLength::Length(f(v)), DashLength::LineWidth => DashLength::LineWidth, }) .collect(), phase: f(pattern.phase), }) }), miter_limit: self.miter_limit, } } } impl PartialStroke { /// Unpack the stroke, filling missing fields from the `default`. pub fn unwrap_or(self, default: Stroke) -> Stroke { let thickness = self.thickness.unwrap_or(default.thickness); let dash_pattern = self .dash_pattern .map(|pattern| { pattern.map(|pattern| DashPattern { array: pattern .array .into_iter() .map(|l| l.finish(thickness)) .collect(), phase: pattern.phase, }) }) .unwrap_or(default.dash_pattern); Stroke { paint: self.paint.unwrap_or(default.paint), thickness, line_cap: self.line_cap.unwrap_or(default.line_cap), line_join: self.line_join.unwrap_or(default.line_join), dash_pattern, miter_limit: self.miter_limit.unwrap_or(default.miter_limit), } } /// Unpack the stroke, filling missing fields with the default values. pub fn unwrap_or_default(self) -> Stroke { self.unwrap_or(Stroke::default()) } } impl Debug for PartialStroke { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let Self { paint, thickness, line_cap, line_join, dash_pattern, miter_limit, } = &self; if line_cap.is_auto() && line_join.is_auto() && dash_pattern.is_auto() && miter_limit.is_auto() { match (&self.paint, &self.thickness) { (Smart::Custom(paint), Smart::Custom(thickness)) => { write!(f, "{thickness:?} + {paint:?}") } (Smart::Custom(paint), Smart::Auto) => paint.fmt(f), (Smart::Auto, Smart::Custom(thickness)) => thickness.fmt(f), (Smart::Auto, Smart::Auto) => f.pad("1pt + black"), } } else { write!(f, "(")?; let mut sep = ""; if let Smart::Custom(paint) = &paint { write!(f, "{}paint: {:?}", sep, paint)?; sep = ", "; } if let Smart::Custom(thickness) = &thickness { write!(f, "{}thickness: {:?}", sep, thickness)?; sep = ", "; } if let Smart::Custom(cap) = &line_cap { write!(f, "{}cap: {:?}", sep, cap)?; sep = ", "; } if let Smart::Custom(join) = &line_join { write!(f, "{}join: {:?}", sep, join)?; sep = ", "; } if let Smart::Custom(dash) = &dash_pattern { write!(f, "{}dash: {:?}", sep, dash)?; sep = ", "; } if let Smart::Custom(miter_limit) = &miter_limit { write!(f, "{}miter-limit: {:?}", sep, miter_limit)?; } write!(f, ")")?; Ok(()) } } } impl Resolve for PartialStroke { type Output = PartialStroke; fn resolve(self, styles: StyleChain) -> Self::Output { PartialStroke { paint: self.paint, thickness: self.thickness.resolve(styles), line_cap: self.line_cap, line_join: self.line_join, dash_pattern: self.dash_pattern.resolve(styles), miter_limit: self.miter_limit, } } } impl Fold for PartialStroke { type Output = Self; fn fold(self, outer: Self::Output) -> Self::Output { Self { paint: self.paint.or(outer.paint), thickness: self.thickness.or(outer.thickness), line_cap: self.line_cap.or(outer.line_cap), line_join: self.line_join.or(outer.line_join), dash_pattern: self.dash_pattern.or(outer.dash_pattern), miter_limit: self.miter_limit.or(outer.miter_limit), } } } cast! { type PartialStroke: "stroke", thickness: Length => Self { thickness: Smart::Custom(thickness), ..Default::default() }, color: Color => Self { paint: Smart::Custom(color.into()), ..Default::default() }, mut dict: Dict => { fn take(dict: &mut Dict, key: &str) -> StrResult> { Ok(dict.take(key).ok().map(T::from_value) .transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto)) } let paint = take::(&mut dict, "paint")?; let thickness = take::(&mut dict, "thickness")?; let line_cap = take::(&mut dict, "cap")?; let line_join = take::(&mut dict, "join")?; let dash_pattern = take::>(&mut dict, "dash")?; let miter_limit = take::(&mut dict, "miter-limit")?; dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?; Self { paint, thickness, line_cap, line_join, dash_pattern, miter_limit: miter_limit.map(Scalar), } }, } cast! { PartialStroke, self => self.map(Length::from).into_value(), } /// The line cap of a stroke #[derive(Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum LineCap { Butt, Round, Square, } impl Debug for LineCap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { LineCap::Butt => write!(f, "\"butt\""), LineCap::Round => write!(f, "\"round\""), LineCap::Square => write!(f, "\"square\""), } } } /// The line join of a stroke #[derive(Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum LineJoin { Miter, Round, Bevel, } impl Debug for LineJoin { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { LineJoin::Miter => write!(f, "\"miter\""), LineJoin::Round => write!(f, "\"round\""), LineJoin::Bevel => write!(f, "\"bevel\""), } } } /// A line dash pattern. #[derive(Clone, Eq, PartialEq, Hash)] pub struct DashPattern> { /// The dash array. pub array: Vec
, /// The dash phase. pub phase: T, } impl Debug for DashPattern { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "(array: (")?; for (i, elem) in self.array.iter().enumerate() { if i == 0 { write!(f, "{:?}", elem)?; } else { write!(f, ", {:?}", elem)?; } } write!(f, "), phase: {:?})", self.phase)?; Ok(()) } } impl From>> for DashPattern { fn from(array: Vec>) -> Self { Self { array, phase: T::default() } } } impl Resolve for DashPattern { type Output = DashPattern; fn resolve(self, styles: StyleChain) -> Self::Output { DashPattern { array: self.array.into_iter().map(|l| l.resolve(styles)).collect(), phase: self.phase.resolve(styles), } } } // Same names as tikz: // https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns cast! { DashPattern, "solid" => Vec::new().into(), "dotted" => vec![DashLength::LineWidth, Abs::pt(2.0).into()].into(), "densely-dotted" => vec![DashLength::LineWidth, Abs::pt(1.0).into()].into(), "loosely-dotted" => vec![DashLength::LineWidth, Abs::pt(4.0).into()].into(), "dashed" => vec![Abs::pt(3.0).into(), Abs::pt(3.0).into()].into(), "densely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into()].into(), "loosely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(6.0).into()].into(), "dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into(), DashLength::LineWidth, Abs::pt(2.0).into()].into(), "densely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(1.0).into(), DashLength::LineWidth, Abs::pt(1.0).into()].into(), "loosely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(4.0).into(), DashLength::LineWidth, Abs::pt(4.0).into()].into(), array: Vec => Self { array, phase: Length::zero() }, mut dict: Dict => { let array: Vec = dict.take("array")?.cast()?; let phase = dict.take("phase").ok().map(Value::cast) .transpose()?.unwrap_or(Length::zero()); dict.finish(&["array", "phase"])?; Self { array, phase, } }, } /// The length of a dash in a line dash pattern #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum DashLength { LineWidth, Length(T), } impl From for DashLength { fn from(l: Abs) -> Self { DashLength::Length(l.into()) } } impl DashLength { fn finish(self, line_width: T) -> T { match self { Self::LineWidth => line_width, Self::Length(l) => l, } } } impl Resolve for DashLength { type Output = DashLength; fn resolve(self, styles: StyleChain) -> Self::Output { match self { Self::LineWidth => DashLength::LineWidth, Self::Length(v) => DashLength::Length(v.resolve(styles)), } } } cast! { DashLength, "dot" => Self::LineWidth, v: Length => Self::Length(v), }