summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-08 17:08:30 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-09 12:02:35 +0200
commit29eb13ca6214461a4b0deb63d589cd39ad6d41c2 (patch)
treec86797d440cfcc801c87a3c64f479e39f2c068b1 /src/eval
parent712c00ecb72b67da2c0788e5d3eb4dcc6366b2a7 (diff)
Sum color and length into stroke
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/layout.rs8
-rw-r--r--src/eval/ops.rs41
-rw-r--r--src/eval/raw.rs134
-rw-r--r--src/eval/styles.rs44
-rw-r--r--src/eval/value.rs70
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::*;