summaryrefslogtreecommitdiff
path: root/src/geom
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
commit25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch)
tree2fbb4650903123da047a1f1f11a0abda95286e12 /src/geom
parent6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff)
Fully untyped model
Diffstat (limited to 'src/geom')
-rw-r--r--src/geom/abs.rs4
-rw-r--r--src/geom/align.rs48
-rw-r--r--src/geom/axes.rs35
-rw-r--r--src/geom/corners.rs97
-rw-r--r--src/geom/dir.rs4
-rw-r--r--src/geom/em.rs16
-rw-r--r--src/geom/length.rs8
-rw-r--r--src/geom/mod.rs40
-rw-r--r--src/geom/paint.rs23
-rw-r--r--src/geom/rel.rs28
-rw-r--r--src/geom/shape.rs35
-rw-r--r--src/geom/sides.rs85
-rw-r--r--src/geom/smart.rs59
-rw-r--r--src/geom/stroke.rs34
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),
+ }
+ }
+}