diff options
Diffstat (limited to 'src/eval/styles.rs')
| -rw-r--r-- | src/eval/styles.rs | 387 |
1 files changed, 220 insertions, 167 deletions
diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 2bf3f239..346eb784 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -3,21 +3,28 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; +use super::{Args, Func, Span, Template, Value, Vm}; +use crate::diag::{At, TypResult}; use crate::library::{PageNode, ParNode}; /// A map of style properties. #[derive(Default, Clone, PartialEq, Hash)] -pub struct StyleMap(Vec<Entry>); +pub struct StyleMap { + /// Settable properties. + props: Vec<Entry>, + /// Show rule recipes. + recipes: Vec<Recipe>, +} impl StyleMap { /// Create a new, empty style map. pub fn new() -> Self { - Self(vec![]) + Self { props: vec![], recipes: vec![] } } /// Whether this map contains no styles. pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.props.is_empty() && self.recipes.is_empty() } /// Create a style map from a single property-value pair. @@ -29,7 +36,7 @@ impl StyleMap { /// Set the value for a style property. pub fn set<P: Property>(&mut self, key: P, value: P::Value) { - self.0.push(Entry::new(key, value)); + self.props.push(Entry::new(key, value)); } /// Set a value for a style property if it is `Some(_)`. @@ -39,11 +46,16 @@ impl StyleMap { } } + /// Set a recipe. + pub fn set_recipe(&mut self, node: TypeId, func: Func, span: Span) { + self.recipes.push(Recipe { node, func, span }); + } + /// Mark all contained properties as _scoped_. This means that they only /// apply to the first descendant node (of their type) in the hierarchy and /// not its children, too. This is used by class constructors. pub fn scoped(mut self) -> Self { - for entry in &mut self.0 { + for entry in &mut self.props { entry.scoped = true; } self @@ -51,7 +63,7 @@ impl StyleMap { /// Whether this map contains scoped styles. pub fn has_scoped(&self) -> bool { - self.0.iter().any(|e| e.scoped) + self.props.iter().any(|e| e.scoped) } /// Make `self` the first link of the style chain `outer`. @@ -78,20 +90,24 @@ impl StyleMap { /// lifetime, for example, because you want to store the style map inside a /// packed node. pub fn apply(&mut self, outer: &Self) { - self.0.splice(0 .. 0, outer.0.clone()); + self.props.splice(0 .. 0, outer.props.iter().cloned()); + self.recipes.splice(0 .. 0, outer.recipes.iter().cloned()); } /// The highest-level interruption of the map. pub fn interruption(&self) -> Option<Interruption> { - self.0.iter().filter_map(|entry| entry.interruption()).max() + self.props.iter().filter_map(|entry| entry.interruption()).max() } } impl Debug for StyleMap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for entry in self.0.iter().rev() { + for entry in self.props.iter().rev() { writeln!(f, "{:#?}", entry)?; } + for recipe in self.recipes.iter().rev() { + writeln!(f, "#[Recipe for {:?} from {:?}]", recipe.node, recipe.span)?; + } Ok(()) } } @@ -105,6 +121,169 @@ pub enum Interruption { Page, } +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[class]` proc-macro. +pub trait Property: Sync + Send + 'static { + /// The type of value that is returned when getting this property from a + /// style map. For example, this could be [`Length`](crate::geom::Length) + /// for a `WIDTH` property. + type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// Whether the property needs folding. + const FOLDING: bool = false; + + /// The type id of the node this property belongs to. + fn node_id() -> TypeId; + + /// The default value of the property. + fn default() -> Self::Value; + + /// A static reference to the default value of the property. + /// + /// This is automatically implemented through lazy-initialization in the + /// `#[class]` macro. This way, expensive defaults don't need to be + /// recreated all the time. + fn default_ref() -> &'static Self::Value; + + /// Fold the property with an outer value. + /// + /// For example, this would fold a relative font size with an outer + /// absolute font size. + #[allow(unused_variables)] + fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { + inner + } +} + +/// Marker trait that indicates that a property doesn't need folding. +pub trait Nonfolding {} + +/// An entry for a single style property. +#[derive(Clone)] +struct Entry { + pair: Arc<dyn Bounds>, + scoped: bool, +} + +impl Entry { + fn new<P: Property>(key: P, value: P::Value) -> Self { + Self { + pair: Arc::new((key, value)), + scoped: false, + } + } + + fn is<P: Property>(&self) -> bool { + self.pair.style_id() == TypeId::of::<P>() + } + + fn is_of<T: 'static>(&self) -> bool { + self.pair.node_id() == TypeId::of::<T>() + } + + fn is_of_id(&self, node: TypeId) -> bool { + self.pair.node_id() == node + } + + fn downcast<P: Property>(&self) -> Option<&P::Value> { + self.pair.as_any().downcast_ref() + } + + fn interruption(&self) -> Option<Interruption> { + if self.is_of::<PageNode>() { + Some(Interruption::Page) + } else if self.is_of::<ParNode>() { + Some(Interruption::Par) + } else { + None + } + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("#[")?; + self.pair.dyn_fmt(f)?; + if self.scoped { + f.write_str(" (scoped)")?; + } + f.write_str("]") + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.pair.dyn_eq(other) && self.scoped == other.scoped + } +} + +impl Hash for Entry { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.pair.hash64()); + state.write_u8(self.scoped as u8); + } +} + +/// This trait is implemented for pairs of zero-sized property keys and their +/// value types below. Although it is zero-sized, the property `P` must be part +/// of the implementing type so that we can use it in the methods (it must be a +/// constrained type parameter). +trait Bounds: Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; + fn dyn_eq(&self, other: &Entry) -> bool; + fn hash64(&self) -> u64; + fn node_id(&self) -> TypeId; + fn style_id(&self) -> TypeId; +} + +impl<P: Property> Bounds for (P, P::Value) { + fn as_any(&self) -> &dyn Any { + &self.1 + } + + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} = {:?}", P::NAME, self.1) + } + + fn dyn_eq(&self, other: &Entry) -> bool { + self.style_id() == other.pair.style_id() + && if let Some(other) = other.downcast::<P>() { + &self.1 == other + } else { + false + } + } + + fn hash64(&self) -> u64 { + let mut state = fxhash::FxHasher64::default(); + self.style_id().hash(&mut state); + self.1.hash(&mut state); + state.finish() + } + + fn node_id(&self) -> TypeId { + P::node_id() + } + + fn style_id(&self) -> TypeId { + TypeId::of::<P>() + } +} + +/// A show rule recipe. +#[derive(Debug, Clone, PartialEq, Hash)] +struct Recipe { + node: TypeId, + func: Func, + span: Span, +} + /// A chain of style maps, similar to a linked list. /// /// A style chain allows to conceptually merge (and fold) properties from @@ -162,11 +341,18 @@ impl<'a> StyleChain<'a> { *self = self.outer.copied().unwrap_or_default(); } + /// Return the span of a recipe for the given node. + pub(crate) fn recipe_span(self, node: TypeId) -> Option<Span> { + self.recipes(node).next().map(|recipe| recipe.span) + } + /// Return the chain, but without the last link if that one contains only /// scoped styles. This is a hack. pub(crate) fn unscoped(mut self, node: TypeId) -> Self { if let Some(Link::Map(map)) = self.link { - if map.0.iter().all(|e| e.scoped && e.is_of_id(node)) { + if map.props.iter().all(|e| e.scoped && e.is_of_id(node)) + && map.recipes.is_empty() + { self.pop(); } } @@ -225,6 +411,21 @@ impl<'a> StyleChain<'a> { } } + /// Execute a user recipe for a node. + pub fn show( + self, + node: &dyn Any, + vm: &mut Vm, + values: impl IntoIterator<Item = Value>, + ) -> TypResult<Option<Template>> { + Ok(if let Some(recipe) = self.recipes(node.type_id()).next() { + let args = Args::from_values(recipe.span, values); + Some(recipe.func.call(vm, args)?.cast().at(recipe.span)?) + } else { + None + }) + } + /// Insert a barrier into the style chain. /// /// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style @@ -233,7 +434,7 @@ impl<'a> StyleChain<'a> { pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> { if self .maps() - .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_id(node))) + .any(|map| map.props.iter().any(|entry| entry.scoped && entry.is_of_id(node))) { StyleChain { link: Some(Link::Barrier(node)), @@ -252,7 +453,7 @@ impl<'a> StyleChain<'a> { self.links().flat_map(move |link| { let mut entries: &[Entry] = &[]; match link { - Link::Map(map) => entries = &map.0, + Link::Map(map) => entries = &map.props, Link::Barrier(id) => depth += (id == P::node_id()) as usize, } entries @@ -263,6 +464,13 @@ impl<'a> StyleChain<'a> { }) } + /// Iterate over the recipes for the given node. + fn recipes(self, node: TypeId) -> impl Iterator<Item = &'a Recipe> { + self.maps() + .flat_map(|map| map.recipes.iter().rev()) + .filter(move |recipe| recipe.node == node) + } + /// Iterate over the map links of the chain. fn maps(self) -> impl Iterator<Item = &'a StyleMap> { self.links().filter_map(|link| match link { @@ -443,158 +651,3 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> { Self::new() } } - -/// Style property keys. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[class]` proc-macro. -pub trait Property: Sync + Send + 'static { - /// The type of value that is returned when getting this property from a - /// style map. For example, this could be [`Length`](crate::geom::Length) - /// for a `WIDTH` property. - type Value: Debug + Clone + PartialEq + Hash + Sync + Send + 'static; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// Whether the property needs folding. - const FOLDING: bool = false; - - /// The type id of the node this property belongs to. - fn node_id() -> TypeId; - - /// The default value of the property. - fn default() -> Self::Value; - - /// A static reference to the default value of the property. - /// - /// This is automatically implemented through lazy-initialization in the - /// `#[class]` macro. This way, expensive defaults don't need to be - /// recreated all the time. - fn default_ref() -> &'static Self::Value; - - /// Fold the property with an outer value. - /// - /// For example, this would fold a relative font size with an outer - /// absolute font size. - #[allow(unused_variables)] - fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { - inner - } -} - -/// Marker trait that indicates that a property doesn't need folding. -pub trait Nonfolding {} - -/// An entry for a single style property. -#[derive(Clone)] -struct Entry { - pair: Arc<dyn Bounds>, - scoped: bool, -} - -impl Entry { - fn new<P: Property>(key: P, value: P::Value) -> Self { - Self { - pair: Arc::new((key, value)), - scoped: false, - } - } - - fn is<P: Property>(&self) -> bool { - self.pair.style_id() == TypeId::of::<P>() - } - - fn is_of<T: 'static>(&self) -> bool { - self.pair.node_id() == TypeId::of::<T>() - } - - fn is_of_id(&self, node: TypeId) -> bool { - self.pair.node_id() == node - } - - fn downcast<P: Property>(&self) -> Option<&P::Value> { - self.pair.as_any().downcast_ref() - } - - fn interruption(&self) -> Option<Interruption> { - if self.is_of::<PageNode>() { - Some(Interruption::Page) - } else if self.is_of::<ParNode>() { - Some(Interruption::Par) - } else { - None - } - } -} - -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("#[")?; - self.pair.dyn_fmt(f)?; - if self.scoped { - f.write_str(" (scoped)")?; - } - f.write_str("]") - } -} - -impl PartialEq for Entry { - fn eq(&self, other: &Self) -> bool { - self.pair.dyn_eq(other) && self.scoped == other.scoped - } -} - -impl Hash for Entry { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u64(self.pair.hash64()); - state.write_u8(self.scoped as u8); - } -} - -/// This trait is implemented for pairs of zero-sized property keys and their -/// value types below. Although it is zero-sized, the property `P` must be part -/// of the implementing type so that we can use it in the methods (it must be a -/// constrained type parameter). -trait Bounds: Sync + Send + 'static { - fn as_any(&self) -> &dyn Any; - fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; - fn dyn_eq(&self, other: &Entry) -> bool; - fn hash64(&self) -> u64; - fn node_id(&self) -> TypeId; - fn style_id(&self) -> TypeId; -} - -impl<P: Property> Bounds for (P, P::Value) { - fn as_any(&self) -> &dyn Any { - &self.1 - } - - fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{} = {:?}", P::NAME, self.1) - } - - fn dyn_eq(&self, other: &Entry) -> bool { - self.style_id() == other.pair.style_id() - && if let Some(other) = other.downcast::<P>() { - &self.1 == other - } else { - false - } - } - - fn hash64(&self) -> u64 { - let mut state = fxhash::FxHasher64::default(); - self.style_id().hash(&mut state); - self.1.hash(&mut state); - state.finish() - } - - fn node_id(&self) -> TypeId { - P::node_id() - } - - fn style_id(&self) -> TypeId { - TypeId::of::<P>() - } -} |
