summaryrefslogtreecommitdiff
path: root/src/eval/styles.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval/styles.rs')
-rw-r--r--src/eval/styles.rs380
1 files changed, 266 insertions, 114 deletions
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 14826aa8..863dcc6f 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -3,44 +3,10 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
-/// An item with associated styles.
-#[derive(PartialEq, Clone, Hash)]
-pub struct Styled<T> {
- /// The item to apply styles to.
- pub item: T,
- /// The associated style map.
- pub map: StyleMap,
-}
-
-impl<T> Styled<T> {
- /// Create a new instance from an item and a style map.
- pub fn new(item: T, map: StyleMap) -> Self {
- Self { item, map }
- }
-
- /// Create a new instance with empty style map.
- pub fn bare(item: T) -> Self {
- Self { item, map: StyleMap::new() }
- }
-
- /// Map the item with `f`.
- pub fn map<F, U>(self, f: F) -> Styled<U>
- where
- F: FnOnce(T) -> U,
- {
- Styled { item: f(self.item), map: self.map }
- }
-}
-
-impl<T: Debug> Debug for Styled<T> {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- self.map.fmt(f)?;
- self.item.fmt(f)
- }
-}
+use crate::library::{PageNode, ParNode};
/// A map of style properties.
-#[derive(Default, Clone, Hash)]
+#[derive(Default, Clone, PartialEq, Hash)]
pub struct StyleMap(Vec<Entry>);
impl StyleMap {
@@ -93,7 +59,7 @@ impl StyleMap {
*outer
} else {
StyleChain {
- first: Link::Map(self),
+ link: Some(Link::Map(self)),
outer: Some(outer),
}
}
@@ -103,31 +69,16 @@ impl StyleMap {
/// equivalent to the style chain created by
/// `self.chain(StyleChain::new(outer))`.
///
- /// This is useful in the evaluation phase while building nodes and their
- /// style maps, whereas `chain` would be used during layouting to combine
- /// immutable style maps from different levels of the hierarchy.
+ /// This is useful over `chain` when you need an owned map without a
+ /// 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());
}
- /// Subtract `other` from `self` in-place, keeping only styles that are in
- /// `self` but not in `other`.
- pub fn erase(&mut self, other: &Self) {
- self.0.retain(|x| !other.0.contains(x));
- }
-
- /// Intersect `self` with `other` in-place, keeping only styles that are
- /// both in `self` and `other`.
- pub fn intersect(&mut self, other: &Self) {
- self.0.retain(|x| other.0.contains(x));
- }
-
- /// 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))
+ /// The highest-level interruption of the map.
+ pub fn interruption(&self) -> Option<Interruption> {
+ self.0.iter().filter_map(|entry| entry.interruption()).max()
}
}
@@ -140,10 +91,13 @@ impl Debug for StyleMap {
}
}
-impl PartialEq for StyleMap {
- fn eq(&self, other: &Self) -> bool {
- self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x))
- }
+/// Determines whether a style could interrupt some composable structure.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum Interruption {
+ /// The style forces a paragraph break.
+ Par,
+ /// The style forces a page break.
+ Page,
}
/// A chain of style maps, similar to a linked list.
@@ -153,10 +107,10 @@ impl PartialEq for StyleMap {
/// eagerly merging the maps, each access walks the hierarchy from the innermost
/// to the outermost map, trying to find a match and then folding it with
/// matches further up the chain.
-#[derive(Clone, Copy, Hash)]
+#[derive(Default, Clone, Copy, Hash)]
pub struct StyleChain<'a> {
- /// The first link in the chain.
- first: Link<'a>,
+ /// The first link of this chain.
+ link: Option<Link<'a>>,
/// The remaining links in the chain.
outer: Option<&'a Self>,
}
@@ -173,10 +127,52 @@ enum Link<'a> {
impl<'a> StyleChain<'a> {
/// Start a new style chain with a root map.
- pub fn new(first: &'a StyleMap) -> Self {
- Self { first: Link::Map(first), outer: None }
+ pub fn new(map: &'a StyleMap) -> Self {
+ Self { link: Some(Link::Map(map)), outer: None }
+ }
+
+ /// The number of links in the chain.
+ pub fn len(self) -> usize {
+ self.links().count()
+ }
+
+ /// Convert to an owned style map.
+ ///
+ /// Panics if the chain contains barrier links.
+ pub fn to_map(self) -> StyleMap {
+ let mut suffix = StyleMap::new();
+ for link in self.links() {
+ match link {
+ Link::Map(map) => suffix.apply(map),
+ Link::Barrier(_) => panic!("chain contains barrier"),
+ }
+ }
+ suffix
+ }
+
+ /// Build a style map from the suffix (all links beyond the `len`) of the
+ /// chain.
+ ///
+ /// Panics if the suffix contains barrier links.
+ pub fn suffix(self, len: usize) -> StyleMap {
+ let mut suffix = StyleMap::new();
+ let remove = self.len().saturating_sub(len);
+ for link in self.links().take(remove) {
+ match link {
+ Link::Map(map) => suffix.apply(map),
+ Link::Barrier(_) => panic!("suffix contains barrier"),
+ }
+ }
+ suffix
}
+ /// Remove the last link from the chain.
+ pub fn pop(&mut self) {
+ *self = self.outer.copied().unwrap_or_default();
+ }
+}
+
+impl<'a> StyleChain<'a> {
/// Get the (folded) value of a copyable style property.
///
/// This is the method you should reach for first. If it doesn't work
@@ -235,17 +231,19 @@ 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_same(node)))
+ .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_id(node)))
{
StyleChain {
- first: Link::Barrier(node),
+ link: Some(Link::Barrier(node)),
outer: Some(self),
}
} else {
*self
}
}
+}
+impl<'a> StyleChain<'a> {
/// Iterate over all values for the given property in the chain.
fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> {
let mut depth = 0;
@@ -263,16 +261,6 @@ impl<'a> StyleChain<'a> {
})
}
- /// Iterate over the links of the chain.
- fn links(self) -> impl Iterator<Item = Link<'a>> {
- let mut cursor = Some(self);
- std::iter::from_fn(move || {
- let Self { first, outer } = cursor?;
- cursor = outer.copied();
- Some(first)
- })
- }
-
/// Iterate over the map links of the chain.
fn maps(self) -> impl Iterator<Item = &'a StyleMap> {
self.links().filter_map(|link| match link {
@@ -280,6 +268,16 @@ impl<'a> StyleChain<'a> {
Link::Barrier(_) => None,
})
}
+
+ /// Iterate over the links of the chain.
+ fn links(self) -> impl Iterator<Item = Link<'a>> {
+ let mut cursor = Some(self);
+ std::iter::from_fn(move || {
+ let Self { link, outer } = cursor?;
+ cursor = outer.copied();
+ link
+ })
+ }
}
impl Debug for StyleChain<'_> {
@@ -300,66 +298,154 @@ impl Debug for Link<'_> {
}
}
-/// An entry for a single style property.
-#[derive(Clone)]
-struct Entry {
- pair: Arc<dyn Bounds>,
- scoped: bool,
+impl PartialEq for StyleChain<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ let as_ptr = |s| s as *const _;
+ self.link == other.link && self.outer.map(as_ptr) == other.outer.map(as_ptr)
+ }
}
-impl Entry {
- fn new<P: Property>(key: P, value: P::Value) -> Self {
- Self {
- pair: Arc::new((key, value)),
- scoped: false,
+impl PartialEq for Link<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ match (*self, *other) {
+ (Self::Map(a), Self::Map(b)) => std::ptr::eq(a, b),
+ (Self::Barrier(a), Self::Barrier(b)) => a == b,
+ _ => false,
}
}
+}
- fn is<P: Property>(&self) -> bool {
- self.pair.style_id() == TypeId::of::<P>()
+/// A sequence of items with associated styles.
+#[derive(Hash)]
+pub struct StyleVec<T> {
+ items: Vec<T>,
+ maps: Vec<(StyleMap, usize)>,
+}
+
+impl<T> StyleVec<T> {
+ /// Whether there are any items in the sequence.
+ pub fn is_empty(&self) -> bool {
+ self.items.is_empty()
}
- fn is_of<T: 'static>(&self) -> bool {
- self.pair.node_id() == TypeId::of::<T>()
+ /// Iterate over the contained items.
+ pub fn items(&self) -> std::slice::Iter<'_, T> {
+ self.items.iter()
}
- fn is_of_same(&self, node: TypeId) -> bool {
- self.pair.node_id() == node
+ /// Iterate over the contained items and associated style maps.
+ pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
+ let styles = self
+ .maps
+ .iter()
+ .flat_map(|(map, count)| std::iter::repeat(map).take(*count));
+ self.items().zip(styles)
}
+}
- fn downcast<P: Property>(&self) -> Option<&P::Value> {
- self.pair.as_any().downcast_ref()
+impl<T> Default for StyleVec<T> {
+ fn default() -> Self {
+ Self { items: vec![], maps: vec![] }
}
}
-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<T: Debug> Debug for StyleVec<T> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.debug_list()
+ .entries(self.iter().map(|(item, map)| {
+ crate::util::debug(|f| {
+ map.fmt(f)?;
+ item.fmt(f)
+ })
+ }))
+ .finish()
}
}
-impl PartialEq for Entry {
- fn eq(&self, other: &Self) -> bool {
- self.pair.dyn_eq(other) && self.scoped == other.scoped
+/// Assists in the construction of a [`StyleVec`].
+pub struct StyleVecBuilder<'a, T> {
+ items: Vec<T>,
+ chains: Vec<(StyleChain<'a>, usize)>,
+}
+
+impl<'a, T> StyleVecBuilder<'a, T> {
+ /// Create a new style-vec builder.
+ pub fn new() -> Self {
+ Self { items: vec![], chains: vec![] }
+ }
+
+ /// Push a new item into the style vector.
+ pub fn push(&mut self, item: T, styles: StyleChain<'a>) {
+ self.items.push(item);
+
+ if let Some((prev, count)) = self.chains.last_mut() {
+ if *prev == styles {
+ *count += 1;
+ return;
+ }
+ }
+
+ self.chains.push((styles, 1));
+ }
+
+ /// Access the last item mutably and its chain by value.
+ pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> {
+ let item = self.items.last_mut()?;
+ let chain = self.chains.last()?.0;
+ Some((item, chain))
+ }
+
+ /// Finish building, returning a pair of two things:
+ /// - a style vector of items with the non-shared styles
+ /// - a shared prefix chain of styles that apply to all items
+ pub fn finish(self) -> (StyleVec<T>, StyleChain<'a>) {
+ let mut iter = self.chains.iter();
+ let mut trunk = match iter.next() {
+ Some(&(chain, _)) => chain,
+ None => return Default::default(),
+ };
+
+ let mut shared = trunk.len();
+ for &(mut chain, _) in iter {
+ let len = chain.len();
+ if len < shared {
+ for _ in 0 .. shared - len {
+ trunk.pop();
+ }
+ shared = len;
+ } else if len > shared {
+ for _ in 0 .. len - shared {
+ chain.pop();
+ }
+ }
+
+ while shared > 0 && chain != trunk {
+ trunk.pop();
+ chain.pop();
+ shared -= 1;
+ }
+ }
+
+ let maps = self
+ .chains
+ .into_iter()
+ .map(|(chain, count)| (chain.suffix(shared), count))
+ .collect();
+
+ (StyleVec { items: self.items, maps }, trunk)
}
}
-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);
+impl<'a, T> Default for StyleVecBuilder<'a, T> {
+ fn default() -> Self {
+ Self::new()
}
}
/// Style property keys.
///
/// This trait is not intended to be implemented manually, but rather through
-/// the `#[properties]` proc-macro.
+/// 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)
@@ -381,7 +467,7 @@ pub trait Property: Sync + Send + 'static {
/// A static reference to the default value of the property.
///
/// This is automatically implemented through lazy-initialization in the
- /// `#[properties]` macro. This way, expensive defaults don't need to be
+ /// `#[class]` macro. This way, expensive defaults don't need to be
/// recreated all the time.
fn default_ref() -> &'static Self::Value;
@@ -398,6 +484,72 @@ pub trait Property: Sync + Send + '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 {
+ 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