diff --git a/src/model/mod.rs b/src/model/mod.rs index df39207ff..a9d1344a0 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,10 +5,14 @@ mod styles; mod collapse; mod content; mod layout; +mod property; +mod recipe; mod show; pub use collapse::*; pub use content::*; pub use layout::*; +pub use property::*; +pub use recipe::*; pub use show::*; pub use styles::*; diff --git a/src/model/property.rs b/src/model/property.rs new file mode 100644 index 000000000..b35ffba98 --- /dev/null +++ b/src/model/property.rs @@ -0,0 +1,299 @@ +use std::any::Any; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::sync::Arc; + +use super::{Interruption, NodeId, StyleChain}; +use crate::eval::{RawLength, Smart}; +use crate::geom::{Length, Numeric, Relative, Sides, Spec}; +use crate::library::layout::PageNode; +use crate::library::structure::{EnumNode, ListNode}; +use crate::library::text::ParNode; +use crate::util::{Prehashed, ReadableTypeId}; + +/// A style property originating from a set rule or constructor. +#[derive(Clone, Hash)] +pub struct Property { + /// The id of the property's [key](Key). + pub key: KeyId, + /// The id of the node the property belongs to. + pub node: NodeId, + /// Whether the property should only affects the first node down the + /// hierarchy. Used by constructors. + pub scoped: bool, + /// The property's value. + value: Arc>, + /// The name of the property. + #[cfg(debug_assertions)] + name: &'static str, +} + +impl Property { + /// Create a new property from a key-value pair. + pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { + Self { + key: KeyId::of::(), + node: K::node(), + value: Arc::new(Prehashed::new(value)), + scoped: false, + #[cfg(debug_assertions)] + name: K::NAME, + } + } + + /// Whether this property has the given key. + pub fn is<'a, K: Key<'a>>(&self) -> bool { + self.key == KeyId::of::() + } + + /// Whether this property belongs to the node `T`. + pub fn is_of(&self) -> bool { + self.node == NodeId::of::() + } + + /// Access the property's value if it is of the given key. + pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { + if self.key == KeyId::of::() { + (**self.value).as_any().downcast_ref() + } else { + None + } + } + + /// What kind of structure the property interrupts. + pub fn interruption(&self) -> Option { + if self.is_of::() { + Some(Interruption::Page) + } else if self.is_of::() { + Some(Interruption::Par) + } else if self.is_of::() || self.is_of::() { + Some(Interruption::List) + } else { + None + } + } +} + +impl Debug for Property { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + #[cfg(debug_assertions)] + write!(f, "{} = ", self.name)?; + write!(f, "{:?}", self.value)?; + if self.scoped { + write!(f, " [scoped]")?; + } + Ok(()) + } +} + +impl PartialEq for Property { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + && self.value.eq(&other.value) + && self.scoped == other.scoped + } +} + +trait Bounds: Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; +} + +impl Bounds for T +where + T: Debug + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } +} + +/// A unique identifier for a property key. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct KeyId(ReadableTypeId); + +impl KeyId { + /// The id of the given key. + pub fn of<'a, T: Key<'a>>() -> Self { + Self(ReadableTypeId::of::()) + } +} + +impl Debug for KeyId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[node]` proc-macro. +pub trait Key<'a>: Copy + 'static { + /// The unfolded type which this property is stored as in a style map. For + /// example, this is [`Toggle`](crate::geom::Length) for the + /// [`STRONG`](TextNode::STRONG) property. + type Value: Debug + Clone + Hash + Sync + Send + 'static; + + /// The folded type of value that is returned when reading this property + /// from a style chain. For example, this is [`bool`] for the + /// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding + /// properties this is a reference type. + type Output; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// The ids of the key and of the node the key belongs to. + fn node() -> NodeId; + + /// Compute an output value from a sequence of values belong to this key, + /// folding if necessary. + fn get( + chain: StyleChain<'a>, + values: impl Iterator, + ) -> Self::Output; +} + +/// A property that is resolved with other properties from the style chain. +pub trait Resolve { + /// The type of the resolved output. + type Output; + + /// Resolve the value using the style chain. + fn resolve(self, styles: StyleChain) -> Self::Output; +} + +impl Resolve for Option { + type Output = Option; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Smart { + type Output = Smart; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Spec { + type Output = Spec; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|v| v.resolve(styles)) + } +} + +impl Resolve for Sides { + type Output = Sides; + + fn resolve(self, styles: StyleChain) -> Self::Output { + Sides { + left: self.left.resolve(styles), + right: self.right.resolve(styles), + top: self.top.resolve(styles), + bottom: self.bottom.resolve(styles), + } + } +} + +impl Resolve for Relative +where + T: Resolve + Numeric, + ::Output: Numeric, +{ + type Output = Relative<::Output>; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.map(|abs| abs.resolve(styles)) + } +} + +/// 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 Fold for Option +where + T: Fold, + T::Output: Default, +{ + type Output = Option; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +impl Fold for Smart +where + T: Fold, + T::Output: Default, +{ + type Output = Smart; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.map(|inner| inner.fold(outer.unwrap_or_default())) + } +} + +impl Fold for Sides +where + T: Fold, +{ + type Output = Sides; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer, _| inner.fold(outer)) + } +} + +impl Fold for Sides>> { + type Output = Sides>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer, _| inner.unwrap_or(outer)) + } +} + +impl Fold for Sides>>> { + type Output = Sides>>; + + fn fold(self, outer: Self::Output) -> Self::Output { + self.zip(outer, |inner, outer, _| inner.unwrap_or(outer)) + } +} + +/// A scoped property barrier. +/// +/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style +/// can still be read through a single barrier (the one of the node it +/// _should_ apply to), but a second barrier will make it invisible. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct Barrier(NodeId); + +impl Barrier { + /// Create a new barrier for the given node. + pub fn new(node: NodeId) -> Self { + Self(node) + } + + /// Whether this barrier is for the node `T`. + pub fn is_for(&self, node: NodeId) -> bool { + self.0 == node + } +} + +impl Debug for Barrier { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Barrier for {:?}", self.0) + } +} diff --git a/src/model/recipe.rs b/src/model/recipe.rs new file mode 100644 index 000000000..2f5f7c097 --- /dev/null +++ b/src/model/recipe.rs @@ -0,0 +1,29 @@ +use std::fmt::{self, Debug, Formatter}; + +use super::NodeId; +use crate::eval::{Func, Node}; +use crate::syntax::Span; + +/// A show rule recipe. +#[derive(Clone, PartialEq, Hash)] +pub struct Recipe { + /// The affected node. + pub node: NodeId, + /// The function that defines the recipe. + pub func: Func, + /// The span to report all erros with. + pub span: Span, +} + +impl Recipe { + /// Create a new recipe for the node `T`. + pub fn new(func: Func, span: Span) -> Self { + Self { node: NodeId::of::(), func, span } + } +} + +impl Debug for Recipe { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Recipe for {:?} from {:?}", self.node, self.span) + } +} diff --git a/src/model/styles.rs b/src/model/styles.rs index ae4c1586e..b3020c880 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,19 +1,14 @@ -use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::iter; use std::marker::PhantomData; -use std::sync::Arc; -use super::{Content, Show, ShowNode}; +use super::{Barrier, Content, Key, Property, Recipe, Show, ShowNode}; use crate::diag::{At, TypResult}; -use crate::eval::{Args, Func, Node, RawLength, Smart, Value}; -use crate::geom::{Length, Numeric, Relative, Sides, Spec}; -use crate::library::layout::PageNode; -use crate::library::structure::{EnumNode, ListNode}; -use crate::library::text::{FontFamily, ParNode, TextNode}; +use crate::eval::{Args, Func, Node, Value}; +use crate::library::text::{FontFamily, TextNode}; use crate::syntax::Span; -use crate::util::{Prehashed, ReadableTypeId}; +use crate::util::ReadableTypeId; use crate::Context; /// A map of style properties. @@ -80,7 +75,7 @@ impl StyleMap { self.0 .iter() .filter_map(|entry| entry.property()) - .any(|property| property.key == KeyId::of::()) + .any(|property| property.is::()) } /// Make `self` the first link of the `tail` chain. @@ -171,21 +166,15 @@ impl Debug for NodeId { } } -/// A unique identifier for a property key. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct KeyId(ReadableTypeId); - -impl KeyId { - /// The id of the given key. - pub fn of<'a, T: Key<'a>>() -> Self { - Self(ReadableTypeId::of::()) - } -} - -impl Debug for KeyId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } +/// Determines whether a style could interrupt some composable structure. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Interruption { + /// The style forces a list break. + List, + /// The style forces a paragraph break. + Par, + /// The style forces a page break. + Page, } /// An entry for a single style property, recipe or barrier. @@ -222,7 +211,7 @@ impl StyleEntry { if !tail .entries() .filter_map(StyleEntry::property) - .any(|p| p.scoped && p.node == barrier.0) + .any(|p| p.scoped && barrier.is_for(p.node)) { return *tail; } @@ -247,301 +236,6 @@ impl Debug for StyleEntry { } } -/// A style property originating from a set rule or constructor. -#[derive(Clone, Hash)] -pub struct Property { - /// The id of the property's [key](Key). - key: KeyId, - /// The id of the node the property belongs to. - node: NodeId, - /// The name of the property. - #[cfg(debug_assertions)] - name: &'static str, - /// The property's value. - value: Arc>, - /// Whether the property should only affects the first node down the - /// hierarchy. Used by constructors. - scoped: bool, -} - -impl Property { - /// Create a new property from a key-value pair. - pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self { - Self { - key: KeyId::of::(), - node: K::node(), - #[cfg(debug_assertions)] - name: K::NAME, - value: Arc::new(Prehashed::new(value)), - scoped: false, - } - } - - /// What kind of structure the property interrupts. - pub fn interruption(&self) -> Option { - if self.is_of::() { - Some(Interruption::Page) - } else if self.is_of::() { - Some(Interruption::Par) - } else if self.is_of::() || self.is_of::() { - Some(Interruption::List) - } else { - None - } - } - - /// Access the property's value if it is of the given key. - pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> { - if self.key == KeyId::of::() { - (**self.value).as_any().downcast_ref() - } else { - None - } - } - - /// Whether this property belongs to the node `T`. - pub fn is_of(&self) -> bool { - self.node == NodeId::of::() - } -} - -impl Debug for Property { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - #[cfg(debug_assertions)] - write!(f, "{} = ", self.name)?; - write!(f, "{:?}", self.value)?; - if self.scoped { - write!(f, " [scoped]")?; - } - Ok(()) - } -} - -impl PartialEq for Property { - fn eq(&self, other: &Self) -> bool { - self.key == other.key - && self.value.eq(&other.value) - && self.scoped == other.scoped - } -} - -trait Bounds: Debug + Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; -} - -impl Bounds for T -where - T: Debug + Sync + Send + 'static, -{ - fn as_any(&self) -> &dyn Any { - self - } -} - -/// Style property keys. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[node]` proc-macro. -pub trait Key<'a>: Copy + 'static { - /// The unfolded type which this property is stored as in a style map. For - /// example, this is [`Toggle`](crate::geom::Length) for the - /// [`STRONG`](TextNode::STRONG) property. - type Value: Debug + Clone + Hash + Sync + Send + 'static; - - /// The folded type of value that is returned when reading this property - /// from a style chain. For example, this is [`bool`] for the - /// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding - /// properties this is a reference type. - type Output; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// The ids of the key and of the node the key belongs to. - fn node() -> NodeId; - - /// Compute an output value from a sequence of values belong to this key, - /// folding if necessary. - fn get( - chain: StyleChain<'a>, - values: impl Iterator, - ) -> Self::Output; -} - -/// A property that is resolved with other properties from the style chain. -pub trait Resolve { - /// The type of the resolved output. - type Output; - - /// Resolve the value using the style chain. - fn resolve(self, styles: StyleChain) -> Self::Output; -} - -impl Resolve for Option { - type Output = Option; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Smart { - type Output = Smart; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Spec { - type Output = Spec; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|v| v.resolve(styles)) - } -} - -impl Resolve for Sides { - type Output = Sides; - - fn resolve(self, styles: StyleChain) -> Self::Output { - Sides { - left: self.left.resolve(styles), - right: self.right.resolve(styles), - top: self.top.resolve(styles), - bottom: self.bottom.resolve(styles), - } - } -} - -impl Resolve for Relative -where - T: Resolve + Numeric, - ::Output: Numeric, -{ - type Output = Relative<::Output>; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.map(|abs| abs.resolve(styles)) - } -} - -/// 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 Fold for Option -where - T: Fold, - T::Output: Default, -{ - type Output = Option; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} - -impl Fold for Smart -where - T: Fold, - T::Output: Default, -{ - type Output = Smart; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.map(|inner| inner.fold(outer.unwrap_or_default())) - } -} - -impl Fold for Sides -where - T: Fold, -{ - type Output = Sides; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer, _| inner.fold(outer)) - } -} - -impl Fold for Sides>> { - type Output = Sides>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer, _| inner.unwrap_or(outer)) - } -} - -impl Fold for Sides>>> { - type Output = Sides>>; - - fn fold(self, outer: Self::Output) -> Self::Output { - self.zip(outer, |inner, outer, _| inner.unwrap_or(outer)) - } -} - -/// A scoped property barrier. -/// -/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style -/// can still be read through a single barrier (the one of the node it -/// _should_ apply to), but a second barrier will make it invisible. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Barrier(NodeId); - -impl Barrier { - /// Create a new barrier for the given node. - pub fn new(node: NodeId) -> Self { - Self(node) - } -} - -impl Debug for Barrier { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Barrier for {:?}", self.0) - } -} - -/// A show rule recipe. -#[derive(Clone, PartialEq, Hash)] -pub struct Recipe { - /// The affected node. - node: NodeId, - /// The function that defines the recipe. - func: Func, - /// The span to report all erros with. - span: Span, -} - -impl Recipe { - /// Create a new recipe for the node `T`. - pub fn new(func: Func, span: Span) -> Self { - Self { node: NodeId::of::(), func, span } - } -} - -impl Debug for Recipe { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Recipe for {:?} from {:?}", self.node, self.span) - } -} - -/// Determines whether a style could interrupt some composable structure. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Interruption { - /// The style forces a list break. - List, - /// The style forces a paragraph break. - Par, - /// The style forces a page break. - Page, -} - /// A chain of style maps, similar to a linked list. /// /// A style chain allows to combine properties from multiple style maps in a @@ -690,7 +384,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { } } StyleEntry::Barrier(barrier) => { - self.depth += (barrier.0 == K::node()) as usize; + self.depth += barrier.is_for(K::node()) as usize; } StyleEntry::Recipe(_) => {} }