diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-04-08 17:08:30 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-04-09 12:02:35 +0200 |
| commit | 29eb13ca6214461a4b0deb63d589cd39ad6d41c2 (patch) | |
| tree | c86797d440cfcc801c87a3c64f479e39f2c068b1 /src/eval | |
| parent | 712c00ecb72b67da2c0788e5d3eb4dcc6366b2a7 (diff) | |
Sum color and length into stroke
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/layout.rs | 8 | ||||
| -rw-r--r-- | src/eval/ops.rs | 41 | ||||
| -rw-r--r-- | src/eval/raw.rs | 134 | ||||
| -rw-r--r-- | src/eval/styles.rs | 44 | ||||
| -rw-r--r-- | src/eval/value.rs | 70 |
5 files changed, 250 insertions, 47 deletions
diff --git a/src/eval/layout.rs b/src/eval/layout.rs index f92a31f5..117c269a 100644 --- a/src/eval/layout.rs +++ b/src/eval/layout.rs @@ -7,8 +7,8 @@ use std::sync::Arc; use super::{Barrier, RawAlign, RawLength, Resolve, StyleChain}; use crate::diag::TypResult; -use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; -use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec}; +use crate::frame::{Element, Frame, Geometry}; +use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke}; use crate::library::graphics::MoveNode; use crate::library::layout::{AlignNode, PadNode}; use crate::util::Prehashed; @@ -349,7 +349,7 @@ impl Layout for FillNode { ) -> TypResult<Vec<Arc<Frame>>> { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let shape = Shape::filled(Geometry::Rect(frame.size), self.fill); + let shape = Geometry::Rect(frame.size).filled(self.fill); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } Ok(frames) @@ -374,7 +374,7 @@ impl Layout for StrokeNode { ) -> TypResult<Vec<Arc<Frame>>> { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke); + let shape = Geometry::Rect(frame.size).stroked(self.stroke); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } Ok(frames) diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 89802949..4796e042 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use super::{Dynamic, RawAlign, StrExt, Value}; +use super::{Dynamic, RawAlign, RawStroke, Smart, StrExt, Value}; use crate::diag::StrResult; use crate::geom::{Numeric, Spec, SpecAxis}; use Value::*; @@ -90,25 +90,32 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), - (a, b) => { - if let (Dyn(a), Dyn(b)) = (&a, &b) { - // 1D alignments can be summed into 2D alignments. - if let (Some(&a), Some(&b)) = - (a.downcast::<RawAlign>(), b.downcast::<RawAlign>()) - { - return if a.axis() != b.axis() { - Ok(Dyn(Dynamic::new(match a.axis() { - SpecAxis::Horizontal => Spec { x: a, y: b }, - SpecAxis::Vertical => Spec { x: b, y: a }, - }))) - } else { - Err(format!("cannot add two {:?} alignments", a.axis())) - }; + (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { + Dyn(Dynamic::new(RawStroke { + paint: Smart::Custom(color.into()), + thickness: Smart::Custom(thickness), + })) + } + + (Dyn(a), Dyn(b)) => { + // 1D alignments can be summed into 2D alignments. + if let (Some(&a), Some(&b)) = + (a.downcast::<RawAlign>(), b.downcast::<RawAlign>()) + { + if a.axis() != b.axis() { + Dyn(Dynamic::new(match a.axis() { + SpecAxis::Horizontal => Spec { x: a, y: b }, + SpecAxis::Vertical => Spec { x: b, y: a }, + })) + } else { + return Err(format!("cannot add two {:?} alignments", a.axis())); } + } else { + mismatch!("cannot add {} and {}", a, b); } - - mismatch!("cannot add {} and {}", a, b); } + + (a, b) => mismatch!("cannot add {} and {}", a, b), }) } diff --git a/src/eval/raw.rs b/src/eval/raw.rs index 622a0562..b0f46fc9 100644 --- a/src/eval/raw.rs +++ b/src/eval/raw.rs @@ -1,8 +1,11 @@ +use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::ops::{Add, Div, Mul, Neg}; -use super::{Resolve, StyleChain}; -use crate::geom::{Align, Em, Length, Numeric, Relative, SpecAxis}; +use super::{Fold, Resolve, Smart, StyleChain, Value}; +use crate::geom::{ + Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke, +}; use crate::library::text::{ParNode, TextNode}; /// The unresolved alignment representation. @@ -49,6 +52,101 @@ impl Debug for RawAlign { } } +dynamic! { + RawAlign: "alignment", +} + +dynamic! { + Spec<RawAlign>: "2d alignment", +} + +castable! { + Spec<Option<RawAlign>>, + Expected: "1d or 2d alignment", + @align: RawAlign => { + let mut aligns = Spec::default(); + aligns.set(align.axis(), Some(*align)); + aligns + }, + @aligns: Spec<RawAlign> => aligns.map(Some), +} + +/// The unresolved 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, Copy, Clone, Eq, PartialEq, Hash)] +pub struct RawStroke<T = RawLength> { + /// The stroke's paint. + pub paint: Smart<Paint>, + /// The stroke's thickness. + pub thickness: Smart<T>, +} + +impl RawStroke<Length> { + /// Unpack the stroke, filling missing fields with `default`. + pub fn unwrap_or(self, default: Stroke) -> Stroke { + Stroke { + paint: self.paint.unwrap_or(default.paint), + thickness: self.thickness.unwrap_or(default.thickness), + } + } + + /// Unpack the stroke, filling missing fields with the default values. + pub fn unwrap_or_default(self) -> Stroke { + self.unwrap_or(Stroke::default()) + } +} + +impl Resolve for RawStroke { + type Output = RawStroke<Length>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + RawStroke { + paint: self.paint, + thickness: self.thickness.resolve(styles), + } + } +} + +// This faciliates RawStroke => Stroke. +impl Fold for RawStroke<Length> { + type Output = Self; + + fn fold(self, outer: Self::Output) -> Self::Output { + Self { + paint: self.paint.or(outer.paint), + thickness: self.thickness.or(outer.thickness), + } + } +} + +impl<T: Debug> Debug for RawStroke<T> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + 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("<stroke>"), + } + } +} + +dynamic! { + RawStroke: "stroke", + Value::Length(thickness) => Self { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + }, + Value::Color(color) => Self { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + }, +} + /// The unresolved length representation. /// /// Currently supports absolute and em units, but support could quite easily be @@ -56,7 +154,7 @@ impl Debug for RawAlign { /// Probably, it would be a good idea to then move to an enum representation /// that has a small footprint and allocates for the rare case that units are /// mixed. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct RawLength { /// The absolute part. pub length: Length, @@ -101,6 +199,26 @@ impl Resolve for RawLength { } } +impl Numeric for RawLength { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.length.is_finite() && self.em.is_finite() + } +} + +impl PartialOrd for RawLength { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + if self.em.is_zero() && other.em.is_zero() { + self.length.partial_cmp(&other.length) + } else { + None + } + } +} + impl From<Length> for RawLength { fn from(length: Length) -> Self { Self { length, em: Em::zero() } @@ -119,16 +237,6 @@ impl From<Length> for Relative<RawLength> { } } -impl Numeric for RawLength { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.length.is_finite() && self.em.is_finite() - } -} - impl Neg for RawLength { type Output = Self; diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 575518f5..71293f40 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -287,15 +287,6 @@ pub trait Key<'a>: 'static { ) -> Self::Output; } -/// A property that is folded to determine its final value. -pub trait Fold { - /// The type of the folded output. - type Output; - - /// Fold this inner value with an outer folded value. - fn fold(self, outer: Self::Output) -> Self::Output; -} - /// A property that is resolved with other properties from the style chain. pub trait Resolve { /// The type of the resolved output. @@ -354,6 +345,39 @@ where } } +/// A property that is folded to determine its final value. +pub trait Fold { + /// The type of the folded output. + type Output; + + /// Fold this inner value with an outer folded value. + fn fold(self, outer: Self::Output) -> Self::Output; +} + +impl<T> Fold for Option<T> +where + T: Fold, + T::Output: Default, +{ + type Output = Option<T::Output>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +impl<T> Fold for Smart<T> +where + T: Fold, + T::Output: Default, +{ + type Output = Smart<T::Output>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] struct Recipe { @@ -472,7 +496,7 @@ impl<'a> StyleChain<'a> { /// Get the output value of a style property. /// /// Returns the property's default value if no map in the chain contains an - /// entry for it. Also takes care of folding and resolving and returns + /// entry for it. Also takes care of resolving and folding and returns /// references where applicable. pub fn get<K: Key<'a>>(self, key: K) -> K::Output { K::get(self, self.values(key)) diff --git a/src/eval/value.rs b/src/eval/value.rs index 1851cf28..cc312c5a 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -2,11 +2,16 @@ use std::any::Any; use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; +use std::num::NonZeroUsize; use std::sync::Arc; -use super::{ops, Args, Array, Content, Context, Dict, Func, Layout, RawLength, StrExt}; +use super::{ + ops, Args, Array, Content, Context, Dict, Func, Layout, LayoutNode, RawLength, StrExt, +}; use crate::diag::{with_alternative, At, StrResult, TypResult}; -use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor}; +use crate::geom::{ + Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, +}; use crate::library::text::RawNode; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -526,7 +531,7 @@ macro_rules! castable { $(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)* ) => { impl $crate::eval::Cast<$crate::eval::Value> for $type { - fn is(value: &Value) -> bool { + fn is(value: &$crate::eval::Value) -> bool { #[allow(unused_variables)] match value { $($pattern => true,)* @@ -637,6 +642,14 @@ impl<T> Smart<T> { } } + /// Keeps `self` if it contains a custom value, otherwise returns `other`. + pub fn or(self, other: Smart<T>) -> Self { + match self { + Self::Custom(x) => Self::Custom(x), + Self::Auto => other, + } + } + /// Returns the contained custom value or a provided default value. pub fn unwrap_or(self, default: T) -> T { match self { @@ -655,6 +668,14 @@ impl<T> Smart<T> { Self::Custom(x) => x, } } + + /// Returns the contained custom value or the default value. + pub fn unwrap_or_default(self) -> T + where + T: Default, + { + self.unwrap_or_else(T::default) + } } impl<T> Default for Smart<T> { @@ -678,6 +699,49 @@ impl<T: Cast> Cast for Smart<T> { } } +dynamic! { + Dir: "direction", +} + +castable! { + usize, + Expected: "non-negative integer", + Value::Int(int) => int.try_into().map_err(|_| { + if int < 0 { + "must be at least zero" + } else { + "number too large" + } + })?, +} + +castable! { + NonZeroUsize, + Expected: "positive integer", + Value::Int(int) => Value::Int(int) + .cast::<usize>()? + .try_into() + .map_err(|_| "must be positive")?, +} + +castable! { + Paint, + Expected: "color", + Value::Color(color) => Paint::Solid(color), +} + +castable! { + String, + Expected: "string", + Value::Str(string) => string.into(), +} + +castable! { + LayoutNode, + Expected: "content", + Value::Content(content) => content.pack(), +} + #[cfg(test)] mod tests { use super::*; |
