diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-07 15:17:13 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-07 15:17:13 +0100 |
| commit | 25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch) | |
| tree | 2fbb4650903123da047a1f1f11a0abda95286e12 /src/geom | |
| parent | 6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff) | |
Fully untyped model
Diffstat (limited to 'src/geom')
| -rw-r--r-- | src/geom/abs.rs | 4 | ||||
| -rw-r--r-- | src/geom/align.rs | 48 | ||||
| -rw-r--r-- | src/geom/axes.rs | 35 | ||||
| -rw-r--r-- | src/geom/corners.rs | 97 | ||||
| -rw-r--r-- | src/geom/dir.rs | 4 | ||||
| -rw-r--r-- | src/geom/em.rs | 16 | ||||
| -rw-r--r-- | src/geom/length.rs | 8 | ||||
| -rw-r--r-- | src/geom/mod.rs | 40 | ||||
| -rw-r--r-- | src/geom/paint.rs | 23 | ||||
| -rw-r--r-- | src/geom/rel.rs | 28 | ||||
| -rw-r--r-- | src/geom/shape.rs | 35 | ||||
| -rw-r--r-- | src/geom/sides.rs | 85 | ||||
| -rw-r--r-- | src/geom/smart.rs | 59 | ||||
| -rw-r--r-- | src/geom/stroke.rs | 34 |
14 files changed, 474 insertions, 42 deletions
diff --git a/src/geom/abs.rs b/src/geom/abs.rs index 4429e46d..34c3d010 100644 --- a/src/geom/abs.rs +++ b/src/geom/abs.rs @@ -214,6 +214,10 @@ impl<'a> Sum<&'a Self> for Abs { } } +cast_to_value! { + v: Abs => Value::Length(v.into()) +} + /// Different units of absolute measurement. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum AbsUnit { diff --git a/src/geom/align.rs b/src/geom/align.rs index 1e9bde52..b14e6775 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -115,3 +115,51 @@ impl Debug for GenAlign { } } } + +cast_from_value! { + GenAlign: "alignment", +} + +cast_from_value! { + Axes<GenAlign>: "2d alignment", +} + +cast_from_value! { + Axes<Option<GenAlign>>, + align: GenAlign => { + let mut aligns = Axes::default(); + aligns.set(align.axis(), Some(align)); + aligns + }, + aligns: Axes<GenAlign> => aligns.map(Some), +} + +cast_to_value! { + v: Axes<Option<GenAlign>> => match (v.x, v.y) { + (Some(x), Some(y)) => Axes::new(x, y).into(), + (Some(x), None) => x.into(), + (None, Some(y)) => y.into(), + (None, None) => Value::None, + } +} + +impl Resolve for GenAlign { + type Output = Align; + + fn resolve(self, styles: StyleChain) -> Self::Output { + let dir = item!(dir)(styles); + match self { + Self::Start => dir.start().into(), + Self::End => dir.end().into(), + Self::Specific(align) => align, + } + } +} + +impl Fold for GenAlign { + type Output = Self; + + fn fold(self, _: Self::Output) -> Self::Output { + self + } +} diff --git a/src/geom/axes.rs b/src/geom/axes.rs index 48f8c0e8..92bd0388 100644 --- a/src/geom/axes.rs +++ b/src/geom/axes.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; use super::*; +use crate::eval::Array; /// A container with a horizontal and vertical component. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] @@ -272,3 +273,37 @@ impl BitAndAssign for Axes<bool> { self.y &= rhs.y; } } + +cast_from_value! { + Axes<Rel<Length>>, + array: Array => { + let mut iter = array.into_iter(); + match (iter.next(), iter.next(), iter.next()) { + (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), + _ => Err("point array must contain exactly two entries")?, + } + }, +} + +cast_to_value! { + v: Axes<Rel<Length>> => Value::Array(array![v.x, v.y]) +} + +impl<T: Resolve> Resolve for Axes<T> { + type Output = Axes<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl<T: Fold> Fold for Axes<Option<T>> { + type Output = Axes<T::Output>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer).map(|(inner, outer)| match inner { + Some(value) => value.fold(outer), + None => outer, + }) + } +} diff --git a/src/geom/corners.rs b/src/geom/corners.rs index 386acbfb..844d3047 100644 --- a/src/geom/corners.rs +++ b/src/geom/corners.rs @@ -107,3 +107,100 @@ pub enum Corner { /// The bottom left corner. BottomLeft, } + +impl<T> Cast for Corners<Option<T>> +where + T: Cast + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult<Self> { + if let Value::Dict(dict) = &mut value { + let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); + + let rest = take("rest")?; + let left = take("left")?.or(rest); + let top = take("top")?.or(rest); + let right = take("right")?.or(rest); + let bottom = take("bottom")?.or(rest); + let corners = Corners { + top_left: take("top-left")?.or(top).or(left), + top_right: take("top-right")?.or(top).or(right), + bottom_right: take("bottom-right")?.or(bottom).or(right), + bottom_left: take("bottom-left")?.or(bottom).or(left), + }; + + dict.finish(&[ + "top-left", + "top-right", + "bottom-right", + "bottom-left", + "left", + "top", + "right", + "bottom", + "rest", + ])?; + + Ok(corners) + } else if T::is(&value) { + Ok(Self::splat(Some(T::cast(value)?))) + } else { + <Self as Cast>::error(value) + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("dictionary") + } +} + +impl<T: Resolve> Resolve for Corners<T> { + type Output = Corners<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl<T: Fold> Fold for Corners<Option<T>> { + type Output = Corners<T::Output>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer).map(|(inner, outer)| match inner { + Some(value) => value.fold(outer), + None => outer, + }) + } +} + +impl<T> From<Corners<Option<T>>> for Value +where + T: PartialEq + Into<Value>, +{ + fn from(corners: Corners<Option<T>>) -> Self { + if corners.is_uniform() { + if let Some(value) = corners.top_left { + return value.into(); + } + } + + let mut dict = Dict::new(); + if let Some(top_left) = corners.top_left { + dict.insert("top-left".into(), top_left.into()); + } + if let Some(top_right) = corners.top_right { + dict.insert("top-right".into(), top_right.into()); + } + if let Some(bottom_right) = corners.bottom_right { + dict.insert("bottom-right".into(), bottom_right.into()); + } + if let Some(bottom_left) = corners.bottom_left { + dict.insert("bottom-left".into(), bottom_left.into()); + } + + Value::Dict(dict) + } +} diff --git a/src/geom/dir.rs b/src/geom/dir.rs index b2fd6e5a..bc4d66e1 100644 --- a/src/geom/dir.rs +++ b/src/geom/dir.rs @@ -73,3 +73,7 @@ impl Debug for Dir { }) } } + +cast_from_value! { + Dir: "direction", +} diff --git a/src/geom/em.rs b/src/geom/em.rs index 9f5aff39..2c63c81d 100644 --- a/src/geom/em.rs +++ b/src/geom/em.rs @@ -134,3 +134,19 @@ impl Sum for Em { Self(iter.map(|s| s.0).sum()) } } + +cast_to_value! { + v: Em => Value::Length(v.into()) +} + +impl Resolve for Em { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + if self.is_zero() { + Abs::zero() + } else { + self.at(item!(em)(styles)) + } + } +} diff --git a/src/geom/length.rs b/src/geom/length.rs index ae615f14..f70ea263 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -124,3 +124,11 @@ assign_impl!(Length += Length); assign_impl!(Length -= Length); assign_impl!(Length *= f64); assign_impl!(Length /= f64); + +impl Resolve for Length { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.abs + self.em.resolve(styles) + } +} diff --git a/src/geom/mod.rs b/src/geom/mod.rs index ebe4436c..b7daaa1b 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -19,6 +19,7 @@ mod ratio; mod rel; mod rounded; mod scalar; +mod shape; mod sides; mod size; mod smart; @@ -42,6 +43,7 @@ pub use self::ratio::*; pub use self::rel::*; pub use self::rounded::*; pub use self::scalar::*; +pub use self::shape::*; pub use self::sides::*; pub use self::size::*; pub use self::smart::*; @@ -55,6 +57,10 @@ use std::hash::{Hash, Hasher}; use std::iter::Sum; use std::ops::*; +use crate::diag::StrResult; +use crate::eval::{array, cast_from_value, cast_to_value, Cast, CastInfo, Dict, Value}; +use crate::model::{Fold, Resolve, StyleChain}; + /// Generic access to a structure's components. pub trait Get<Index> { /// The structure's component type. @@ -72,40 +78,6 @@ pub trait Get<Index> { } } -/// A geometric shape with optional fill and stroke. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Shape { - /// The shape's geometry. - pub geometry: Geometry, - /// The shape's background fill. - pub fill: Option<Paint>, - /// The shape's border stroke. - pub stroke: Option<Stroke>, -} - -/// A shape's geometry. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Geometry { - /// A line to a point (relative to its position). - Line(Point), - /// A rectangle with its origin in the topleft corner. - Rect(Size), - /// A bezier path. - Path(Path), -} - -impl Geometry { - /// Fill the geometry without a stroke. - pub fn filled(self, fill: Paint) -> Shape { - Shape { geometry: self, fill: Some(fill), stroke: None } - } - - /// Stroke the geometry without a fill. - pub fn stroked(self, stroke: Stroke) -> Shape { - Shape { geometry: self, fill: None, stroke: Some(stroke) } - } -} - /// A numeric type. pub trait Numeric: Sized diff --git a/src/geom/paint.rs b/src/geom/paint.rs index b4064438..c01b21da 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -9,10 +9,7 @@ pub enum Paint { Solid(Color), } -impl<T> From<T> for Paint -where - T: Into<Color>, -{ +impl<T: Into<Color>> From<T> for Paint { fn from(t: T) -> Self { Self::Solid(t.into()) } @@ -26,6 +23,15 @@ impl Debug for Paint { } } +cast_from_value! { + Paint, + color: Color => Self::Solid(color), +} + +cast_to_value! { + Paint::Solid(color): Paint => Value::Color(color) +} + /// A color in a dynamic format. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum Color { @@ -274,15 +280,16 @@ impl Debug for RgbaColor { } } -impl<T> From<T> for Color -where - T: Into<RgbaColor>, -{ +impl<T: Into<RgbaColor>> From<T> for Color { fn from(rgba: T) -> Self { Self::Rgba(rgba.into()) } } +cast_to_value! { + v: RgbaColor => Value::Color(v.into()) +} + /// An 8-bit CMYK color. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct CmykColor { diff --git a/src/geom/rel.rs b/src/geom/rel.rs index a8e75d1c..7288f380 100644 --- a/src/geom/rel.rs +++ b/src/geom/rel.rs @@ -199,3 +199,31 @@ impl<T: Numeric> Add<Ratio> for Rel<T> { self + Rel::from(other) } } + +impl<T> Resolve for Rel<T> +where + T: Resolve + Numeric, + <T as Resolve>::Output: Numeric, +{ + type Output = Rel<<T as Resolve>::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|abs| abs.resolve(styles)) + } +} + +impl Fold for Rel<Abs> { + type Output = Self; + + fn fold(self, _: Self::Output) -> Self::Output { + self + } +} + +impl Fold for Rel<Length> { + type Output = Self; + + fn fold(self, _: Self::Output) -> Self::Output { + self + } +} diff --git a/src/geom/shape.rs b/src/geom/shape.rs new file mode 100644 index 00000000..5658c21f --- /dev/null +++ b/src/geom/shape.rs @@ -0,0 +1,35 @@ +use super::*; + +/// A geometric shape with optional fill and stroke. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Shape { + /// The shape's geometry. + pub geometry: Geometry, + /// The shape's background fill. + pub fill: Option<Paint>, + /// The shape's border stroke. + pub stroke: Option<Stroke>, +} + +/// A shape's geometry. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum Geometry { + /// A line to a point (relative to its position). + Line(Point), + /// A rectangle with its origin in the topleft corner. + Rect(Size), + /// A bezier path. + Path(Path), +} + +impl Geometry { + /// Fill the geometry without a stroke. + pub fn filled(self, fill: Paint) -> Shape { + Shape { geometry: self, fill: Some(fill), stroke: None } + } + + /// Stroke the geometry without a fill. + pub fn stroked(self, stroke: Stroke) -> Shape { + Shape { geometry: self, fill: None, stroke: Some(stroke) } + } +} diff --git a/src/geom/sides.rs b/src/geom/sides.rs index 40327a42..247d9a98 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -177,3 +177,88 @@ impl Side { } } } + +impl<T> Cast for Sides<Option<T>> +where + T: Default + Cast + Copy, +{ + fn is(value: &Value) -> bool { + matches!(value, Value::Dict(_)) || T::is(value) + } + + fn cast(mut value: Value) -> StrResult<Self> { + if let Value::Dict(dict) = &mut value { + let mut take = |key| dict.take(key).ok().map(T::cast).transpose(); + + let rest = take("rest")?; + let x = take("x")?.or(rest); + let y = take("y")?.or(rest); + let sides = Sides { + left: take("left")?.or(x), + top: take("top")?.or(y), + right: take("right")?.or(x), + bottom: take("bottom")?.or(y), + }; + + dict.finish(&["left", "top", "right", "bottom", "x", "y", "rest"])?; + + Ok(sides) + } else if T::is(&value) { + Ok(Self::splat(Some(T::cast(value)?))) + } else { + <Self as Cast>::error(value) + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("dictionary") + } +} + +impl<T> From<Sides<Option<T>>> for Value +where + T: PartialEq + Into<Value>, +{ + fn from(sides: Sides<Option<T>>) -> Self { + if sides.is_uniform() { + if let Some(value) = sides.left { + return value.into(); + } + } + + let mut dict = Dict::new(); + if let Some(left) = sides.left { + dict.insert("left".into(), left.into()); + } + if let Some(top) = sides.top { + dict.insert("top".into(), top.into()); + } + if let Some(right) = sides.right { + dict.insert("right".into(), right.into()); + } + if let Some(bottom) = sides.bottom { + dict.insert("bottom".into(), bottom.into()); + } + + Value::Dict(dict) + } +} + +impl<T: Resolve> Resolve for Sides<T> { + type Output = Sides<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl<T: Fold> Fold for Sides<Option<T>> { + type Output = Sides<T::Output>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer).map(|(inner, outer)| match inner { + Some(value) => value.fold(outer), + None => outer, + }) + } +} diff --git a/src/geom/smart.rs b/src/geom/smart.rs index e115e99d..c977d651 100644 --- a/src/geom/smart.rs +++ b/src/geom/smart.rs @@ -31,6 +31,18 @@ impl<T> Smart<T> { } } + /// Map the contained custom value with `f` if it contains a custom value, + /// otherwise returns `default`. + pub fn map_or<F, U>(self, default: U, f: F) -> U + where + F: FnOnce(T) -> U, + { + match self { + Self::Auto => default, + Self::Custom(x) => f(x), + } + } + /// Keeps `self` if it contains a custom value, otherwise returns `other`. pub fn or(self, other: Smart<T>) -> Self { match self { @@ -72,3 +84,50 @@ impl<T> Default for Smart<T> { Self::Auto } } + +impl<T: Cast> Cast for Smart<T> { + fn is(value: &Value) -> bool { + matches!(value, Value::Auto) || T::is(value) + } + + fn cast(value: Value) -> StrResult<Self> { + match value { + Value::Auto => Ok(Self::Auto), + v if T::is(&v) => Ok(Self::Custom(T::cast(v)?)), + _ => <Self as Cast>::error(value), + } + } + + fn describe() -> CastInfo { + T::describe() + CastInfo::Type("auto") + } +} + +impl<T: Resolve> Resolve for Smart<T> { + type Output = Smart<T::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +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())) + } +} + +impl<T: Into<Value>> From<Smart<T>> for Value { + fn from(v: Smart<T>) -> Self { + match v { + Smart::Custom(v) => v.into(), + Smart::Auto => Value::Auto, + } + } +} diff --git a/src/geom/stroke.rs b/src/geom/stroke.rs index 86191d33..500a4c10 100644 --- a/src/geom/stroke.rs +++ b/src/geom/stroke.rs @@ -58,3 +58,37 @@ impl<T: Debug> Debug for PartialStroke<T> { } } } + +cast_from_value! { + PartialStroke: "stroke", + thickness: Length => Self { + paint: Smart::Auto, + thickness: Smart::Custom(thickness), + }, + color: Color => Self { + paint: Smart::Custom(color.into()), + thickness: Smart::Auto, + }, +} + +impl Resolve for PartialStroke { + type Output = PartialStroke<Abs>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + PartialStroke { + paint: self.paint, + thickness: self.thickness.resolve(styles), + } + } +} + +impl Fold for PartialStroke<Abs> { + type Output = Self; + + fn fold(self, outer: Self::Output) -> Self::Output { + Self { + paint: self.paint.or(outer.paint), + thickness: self.thickness.or(outer.thickness), + } + } +} |
