diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/class.rs | 2 | ||||
| -rw-r--r-- | src/eval/node.rs | 6 | ||||
| -rw-r--r-- | src/eval/styles.rs | 286 | ||||
| -rw-r--r-- | src/layout/mod.rs | 2 |
4 files changed, 188 insertions, 108 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs index b7b4d012..307bebcc 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -66,7 +66,7 @@ impl Class { let mut styles = StyleMap::new(); self.set(args, &mut styles)?; let node = (self.construct)(ctx, args)?; - Ok(node.styled_with_map(styles)) + Ok(node.styled_with_map(styles.scoped())) } /// Execute the class's set rule. diff --git a/src/eval/node.rs b/src/eval/node.rs index 37f230ee..ecbee8ee 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -302,7 +302,7 @@ impl Packer { if let Some(Styled { item: ParChild::Text(left), map }) = self.par.children.last_mut() { - if child.map.compatible(map, TextNode::has_property) { + if child.map.compatible::<TextNode>(map) { left.0.push_str(&right.0); return; } @@ -380,7 +380,7 @@ impl Packer { return; } - if !self.par.styles.compatible(styles, ParNode::has_property) { + if !self.par.styles.compatible::<ParNode>(styles) { self.parbreak(None); self.par.styles = styles.clone(); return; @@ -397,7 +397,7 @@ impl Packer { return; } - if self.top && !self.flow.styles.compatible(styles, PageNode::has_property) { + if self.top && !self.flow.styles.compatible::<PageNode>(styles) { self.pagebreak(); self.flow.styles = styles.clone(); return; diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 0f3d694a..bdc01f1f 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -100,6 +100,16 @@ impl StyleMap { self.0.push(Entry::new(key, true)); } + /// 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 { + entry.scoped = true; + } + self + } + /// Make `self` the first link of the style chain `outer`. /// /// The resulting style chain contains styles from `self` as well as @@ -109,7 +119,10 @@ impl StyleMap { if self.is_empty() { *outer } else { - StyleChain { inner: self, outer: Some(outer) } + StyleChain { + first: Link::Map(self), + outer: Some(outer), + } } } @@ -122,9 +135,7 @@ impl StyleMap { /// immutable style maps from different levels of the hierarchy. pub fn apply(&mut self, outer: &Self) { for outer in &outer.0 { - if let Some(inner) = - self.0.iter_mut().find(|inner| inner.style_id() == outer.style_id()) - { + if let Some(inner) = self.0.iter_mut().find(|inner| inner.is_same(outer)) { *inner = inner.fold(outer); continue; } @@ -145,13 +156,10 @@ impl StyleMap { self.0.retain(|x| other.0.contains(x)); } - /// Whether two style maps are equal when filtered down to the given - /// properties. - pub fn compatible<F>(&self, other: &Self, filter: F) -> bool - where - F: Fn(StyleId) -> bool, - { - let f = |entry: &&Entry| filter(entry.style_id()); + /// Whether two style maps are equal when filtered down to properties of the + /// node `T`. + pub fn compatible<T: 'static>(&self, other: &Self) -> bool { + let f = |entry: &&Entry| entry.is_of::<T>(); self.0.iter().filter(f).count() == other.0.iter().filter(f).count() && self.0.iter().filter(f).all(|x| other.0.contains(x)) } @@ -168,7 +176,7 @@ impl Debug for StyleMap { impl PartialEq for StyleMap { fn eq(&self, other: &Self) -> bool { - self.compatible(other, |_| true) + self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x)) } } @@ -182,15 +190,22 @@ impl PartialEq for StyleMap { #[derive(Clone, Copy, Hash)] pub struct StyleChain<'a> { /// The first map in the chain. - inner: &'a StyleMap, + first: Link<'a>, /// The remaining maps in the chain. outer: Option<&'a Self>, } +/// The two kinds of links in the chain. +#[derive(Clone, Copy, Hash)] +enum Link<'a> { + Map(&'a StyleMap), + Barrier(TypeId), +} + impl<'a> StyleChain<'a> { /// Start a new style chain with a root map. pub fn new(map: &'a StyleMap) -> Self { - Self { inner: map, outer: None } + Self { first: Link::Map(map), outer: None } } /// Get the (folded) value of a copyable style property. @@ -205,7 +220,7 @@ impl<'a> StyleChain<'a> { where P::Value: Copy, { - self.get_cloned(key) + self.get_impl(key, 0) } /// Get a reference to a style property's value. @@ -220,13 +235,7 @@ impl<'a> StyleChain<'a> { where P: Nonfolding, { - if let Some(value) = self.find(key) { - value - } else if let Some(outer) = self.outer { - outer.get_ref(key) - } else { - P::default_ref() - } + self.get_ref_impl(key, 0) } /// Get the (folded) value of any style property. @@ -238,35 +247,87 @@ impl<'a> StyleChain<'a> { /// Returns the property's default value if no map in the chain contains an /// entry for it. pub fn get_cloned<P: Property>(self, key: P) -> P::Value { - if let Some(value) = self.find(key).cloned() { - if !P::FOLDABLE { - return value; + self.get_impl(key, 0) + } + + /// Insert a barrier into the style chain. + /// + /// 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. + pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> { + if self.needs_barrier(node) { + StyleChain { + first: Link::Barrier(node), + outer: Some(self), } + } else { + *self + } + } +} - match self.outer { - Some(outer) => P::fold(value, outer.get_cloned(key)), - None => P::fold(value, P::default()), +impl<'a> StyleChain<'a> { + fn get_impl<P: Property>(self, key: P, depth: usize) -> P::Value { + let (value, depth) = self.process(key, depth); + if let Some(value) = value.cloned() { + if P::FOLDABLE { + if let Some(outer) = self.outer { + P::fold(value, outer.get_cloned(key)) + } else { + P::fold(value, P::default()) + } + } else { + value } } else if let Some(outer) = self.outer { - outer.get_cloned(key) + outer.get_impl(key, depth) } else { P::default() } } - /// Find a property directly in the localmost map. - fn find<P: Property>(self, _: P) -> Option<&'a P::Value> { - self.inner - .0 - .iter() - .find(|entry| entry.is::<P>()) - .and_then(|entry| entry.downcast::<P>()) + fn get_ref_impl<P: Property>(self, key: P, depth: usize) -> &'a P::Value + where + P: Nonfolding, + { + let (value, depth) = self.process(key, depth); + if let Some(value) = value { + value + } else if let Some(outer) = self.outer { + outer.get_ref_impl(key, depth) + } else { + P::default_ref() + } + } + + fn process<P: Property>(self, _: P, depth: usize) -> (Option<&'a P::Value>, usize) { + match self.first { + Link::Map(map) => ( + map.0 + .iter() + .find(|entry| entry.is::<P>() && (!entry.scoped || depth <= 1)) + .and_then(|entry| entry.downcast::<P>()), + depth, + ), + Link::Barrier(node) => (None, depth + (P::node_id() == node) as usize), + } + } + + fn needs_barrier(self, node: TypeId) -> bool { + if let Link::Map(map) = self.first { + if map.0.iter().any(|entry| entry.is_of_same(node)) { + return true; + } + } + + self.outer.map_or(false, |outer| outer.needs_barrier(node)) } } impl Debug for StyleChain<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.inner.fmt(f)?; + self.first.fmt(f)?; if let Some(outer) = self.outer { outer.fmt(f)?; } @@ -274,14 +335,67 @@ impl Debug for StyleChain<'_> { } } -/// A unique identifier for a style property. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StyleId(TypeId); +impl Debug for Link<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Map(map) => map.fmt(f), + Self::Barrier(id) => writeln!(f, "Barrier({:?})", id), + } + } +} + +/// An entry for a single style property. +#[derive(Clone)] +struct Entry { + p: Rc<dyn Bounds>, + scoped: bool, +} + +impl Entry { + fn new<P: Property>(key: P, value: P::Value) -> Self { + Self { p: Rc::new((key, value)), scoped: false } + } + + fn is<P: Property>(&self) -> bool { + self.p.style_id() == TypeId::of::<P>() + } + + fn is_same(&self, other: &Self) -> bool { + self.p.style_id() == other.p.style_id() + } + + fn is_of<T: 'static>(&self) -> bool { + self.p.node_id() == TypeId::of::<T>() + } + + fn is_of_same(&self, node: TypeId) -> bool { + self.p.node_id() == node + } + + fn downcast<P: Property>(&self) -> Option<&P::Value> { + self.p.as_any().downcast_ref() + } + + fn fold(&self, outer: &Self) -> Self { + self.p.fold(outer) + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.p.dyn_fmt(f) + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.p.dyn_eq(other) + } +} -impl StyleId { - /// The style id of the property. - pub fn of<P: Property>() -> Self { - Self(TypeId::of::<P>()) +impl Hash for Entry { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.p.hash64()); } } @@ -298,6 +412,12 @@ pub trait Property: Copy + 'static { /// The name of the property, used for debug printing. const NAME: &'static str; + /// Whether the property needs folding. + const FOLDABLE: 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; @@ -308,9 +428,6 @@ pub trait Property: Copy + 'static { /// recreated all the time. fn default_ref() -> &'static Self::Value; - /// Whether the property needs folding. - const FOLDABLE: bool = false; - /// Fold the property with an outer value. /// /// For example, this would fold a relative font size with an outer @@ -324,74 +441,21 @@ pub trait Property: Copy + 'static { /// 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(Rc<dyn Bounds>); - -impl Entry { - fn new<P: Property>(key: P, value: P::Value) -> Self { - Self(Rc::new((key, value))) - } - - fn style_id(&self) -> StyleId { - self.0.style_id() - } - - fn is<P: Property>(&self) -> bool { - self.style_id() == StyleId::of::<P>() - } - - fn downcast<P: Property>(&self) -> Option<&P::Value> { - self.0.as_any().downcast_ref() - } - - fn fold(&self, outer: &Self) -> Self { - self.0.fold(outer) - } -} - -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.dyn_fmt(f) - } -} - -impl PartialEq for Entry { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other) - } -} - -impl Hash for Entry { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u64(self.0.hash64()); - } -} - /// 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: 'static { - fn style_id(&self) -> StyleId; - fn fold(&self, outer: &Entry) -> Entry; 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; + fn fold(&self, outer: &Entry) -> Entry; } impl<P: Property> Bounds for (P, P::Value) { - fn style_id(&self) -> StyleId { - StyleId::of::<P>() - } - - fn fold(&self, outer: &Entry) -> Entry { - let outer = outer.downcast::<P>().unwrap(); - let combined = P::fold(self.1.clone(), outer.clone()); - Entry::new(self.0, combined) - } - fn as_any(&self) -> &dyn Any { &self.1 } @@ -401,7 +465,7 @@ impl<P: Property> Bounds for (P, P::Value) { } fn dyn_eq(&self, other: &Entry) -> bool { - self.style_id() == other.style_id() + self.style_id() == other.p.style_id() && if let Some(other) = other.downcast::<P>() { &self.1 == other } else { @@ -415,4 +479,18 @@ impl<P: Property> Bounds for (P, P::Value) { self.1.hash(&mut state); state.finish() } + + fn node_id(&self) -> TypeId { + P::node_id() + } + + fn style_id(&self) -> TypeId { + TypeId::of::<P>() + } + + fn fold(&self, outer: &Entry) -> Entry { + let outer = outer.downcast::<P>().unwrap(); + let combined = P::fold(self.1.clone(), outer.clone()); + Entry::new(self.0, combined) + } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a00687eb..6935afc2 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -197,6 +197,8 @@ impl Layout for PackedNode { regions: &Regions, styles: StyleChain, ) -> Vec<Constrained<Rc<Frame>>> { + let styles = styles.barred(self.node.as_any().type_id()); + #[cfg(not(feature = "layout-cache"))] return self.node.layout(ctx, regions, styles); |
