diff options
| author | Martin <mhaug@live.de> | 2022-01-01 12:56:03 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-01-01 12:56:03 +0100 |
| commit | 28fc2893e873d44aa31a64a87cb3e2e975977a70 (patch) | |
| tree | bad022650bb488492386e4fc079dde3807b89304 /src/eval | |
| parent | 5d5d8a21cfc041ab08d30229f4ecb4cbb415cbc5 (diff) | |
| parent | 179a9f479831c4941253c0517fccdec3acd8f2ff (diff) | |
Merge pull request #53 from typst/style-chains
Diffstat (limited to 'src/eval')
| -rw-r--r-- | src/eval/class.rs | 12 | ||||
| -rw-r--r-- | src/eval/mod.rs | 4 | ||||
| -rw-r--r-- | src/eval/node.rs | 30 | ||||
| -rw-r--r-- | src/eval/styles.rs | 398 |
4 files changed, 267 insertions, 177 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs index c4393b8a..15e1f249 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::marker::PhantomData; use std::rc::Rc; -use super::{Args, EvalContext, Node, Styles}; +use super::{Args, EvalContext, Node, StyleMap}; use crate::diag::TypResult; use crate::util::EcoString; @@ -66,7 +66,7 @@ impl Class { /// This parses both property and data arguments (in this order) and styles /// the node constructed from the data with the style properties. pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> { - let mut styles = Styles::new(); + let mut styles = StyleMap::new(); self.set(args, &mut styles)?; let node = self.0.shim.construct(ctx, args)?; Ok(node.styled(styles)) @@ -76,7 +76,7 @@ impl Class { /// /// This parses property arguments and writes the resulting styles into the /// given style map. There are no further side effects. - pub fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()> { + pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { self.0.shim.set(args, styles) } } @@ -113,14 +113,14 @@ pub trait Construct { pub trait Set { /// Parse the arguments and insert style properties of this class into the /// given style map. - fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()>; + fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()>; } /// Rewires the operations available on a class in an object-safe way. This is /// only implemented by the zero-sized `Shim` struct. trait Bounds { fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>; - fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()>; + fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()>; } struct Shim<T>(PhantomData<T>); @@ -133,7 +133,7 @@ where T::construct(ctx, args) } - fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()> { + fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { T::set(args, styles) } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 17cc46ef..06218752 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -86,7 +86,7 @@ pub struct EvalContext<'a> { /// The active scopes. pub scopes: Scopes<'a>, /// The active styles. - pub styles: Styles, + pub styles: StyleMap, } impl<'a> EvalContext<'a> { @@ -99,7 +99,7 @@ impl<'a> EvalContext<'a> { route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), - styles: Styles::new(), + styles: StyleMap::new(), } } diff --git a/src/eval/node.rs b/src/eval/node.rs index ffa1eee6..54d4104d 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -5,7 +5,7 @@ use std::iter::Sum; use std::mem; use std::ops::{Add, AddAssign}; -use super::Styles; +use super::StyleMap; use crate::diag::StrResult; use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode, RootNode}; @@ -63,7 +63,7 @@ pub enum Node { /// nesting doesn't hurt since we can just recurse into the nested sequences /// during packing. Also, in theory, this allows better complexity when /// adding (large) sequence nodes (just like for a text rope). - Sequence(Vec<(Self, Styles)>), + Sequence(Vec<(Self, StyleMap)>), } impl Node { @@ -89,7 +89,7 @@ impl Node { } /// Style this node. - pub fn styled(self, styles: Styles) -> Self { + pub fn styled(self, styles: StyleMap) -> Self { match self { Self::Inline(inline) => Self::Inline(inline.styled(styles)), Self::Block(block) => Self::Block(block.styled(styles)), @@ -100,7 +100,7 @@ impl Node { /// Style this node in monospace. pub fn monospaced(self) -> Self { - self.styled(Styles::one(TextNode::MONOSPACE, true)) + self.styled(StyleMap::with(TextNode::MONOSPACE, true)) } /// Lift to a type-erased block-level node. @@ -109,7 +109,7 @@ impl Node { packed } else { let mut packer = Packer::new(false); - packer.walk(self, Styles::new()); + packer.walk(self, StyleMap::new()); packer.into_block() } } @@ -117,7 +117,7 @@ impl Node { /// Lift to a root layout tree node. pub fn into_root(self) -> RootNode { let mut packer = Packer::new(true); - packer.walk(self, Styles::new()); + packer.walk(self, StyleMap::new()); packer.into_root() } @@ -127,7 +127,7 @@ impl Node { .map_err(|_| format!("cannot repeat this template {} times", n))?; // TODO(style): Make more efficient. - Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count])) + Ok(Self::Sequence(vec![(self.clone(), StyleMap::new()); count])) } } @@ -142,7 +142,7 @@ impl Add for Node { fn add(self, rhs: Self) -> Self::Output { // TODO(style): Make more efficient. - Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())]) + Self::Sequence(vec![(self, StyleMap::new()), (rhs, StyleMap::new())]) } } @@ -154,7 +154,7 @@ impl AddAssign for Node { impl Sum for Node { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self::Sequence(iter.map(|n| (n, Styles::new())).collect()) + Self::Sequence(iter.map(|n| (n, StyleMap::new())).collect()) } } @@ -194,7 +194,7 @@ impl Packer { } /// Consider a node with the given styles. - fn walk(&mut self, node: Node, styles: Styles) { + fn walk(&mut self, node: Node, styles: StyleMap) { match node { Node::Space => { // A text space is "soft", meaning that it can be eaten up by @@ -321,7 +321,7 @@ impl Packer { } /// Advance to the next paragraph. - fn parbreak(&mut self, break_styles: Option<Styles>) { + fn parbreak(&mut self, break_styles: Option<StyleMap>) { // Erase any styles that will be inherited anyway. let Builder { mut children, styles, .. } = mem::take(&mut self.par); for child in &mut children { @@ -366,7 +366,7 @@ impl Packer { /// Break to a new paragraph if the `styles` contain paragraph styles that /// are incompatible with the current paragraph. - fn make_par_compatible(&mut self, styles: &Styles) { + fn make_par_compatible(&mut self, styles: &StyleMap) { if self.par.children.is_empty() { self.par.styles = styles.clone(); return; @@ -383,7 +383,7 @@ impl Packer { /// Break to a new page if the `styles` contain page styles that are /// incompatible with the current flow. - fn make_flow_compatible(&mut self, styles: &Styles) { + fn make_flow_compatible(&mut self, styles: &StyleMap) { if self.flow.children.is_empty() && self.par.children.is_empty() { self.flow.styles = styles.clone(); return; @@ -402,7 +402,7 @@ impl Packer { /// Container for building a flow or paragraph. struct Builder<T> { /// The intersection of the style properties of all `children`. - styles: Styles, + styles: StyleMap, /// The accumulated flow or paragraph children. children: Vec<T>, /// The kind of thing that was last added. @@ -412,7 +412,7 @@ struct Builder<T> { impl<T> Default for Builder<T> { fn default() -> Self { Self { - styles: Styles::new(), + styles: StyleMap::new(), children: vec![], last: Last::None, } diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 1c4b17ae..f8413feb 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -10,23 +10,21 @@ use std::rc::Rc; /// A map of style properties. #[derive(Default, Clone, Hash)] -pub struct Styles { - map: Vec<(StyleId, Entry)>, -} +pub struct StyleMap(Vec<Entry>); -impl Styles { +impl StyleMap { /// Create a new, empty style map. pub fn new() -> Self { - Self { map: vec![] } + Self(vec![]) } /// Whether this map contains no styles. pub fn is_empty(&self) -> bool { - self.map.is_empty() + self.0.is_empty() } - /// Create a style map with a single property-value pair. - pub fn one<P: Property>(key: P, value: P::Value) -> Self { + /// Create a style map from a single property-value pair. + pub fn with<P: Property>(key: P, value: P::Value) -> Self { let mut styles = Self::new(); styles.set(key, value); styles @@ -34,17 +32,16 @@ impl Styles { /// Set the value for a style property. pub fn set<P: Property>(&mut self, key: P, value: P::Value) { - let id = StyleId::of::<P>(); - for pair in &mut self.map { - if pair.0 == id { - let prev = pair.1.downcast::<P::Value>().unwrap(); - let folded = P::combine(value, prev.clone()); - pair.1 = Entry::new(key, folded); + for entry in &mut self.0 { + if entry.is::<P>() { + let prev = entry.downcast::<P>().unwrap(); + let folded = P::fold(value, prev.clone()); + *entry = Entry::new(key, folded); return; } } - self.map.push((id, Entry::new(key, value))); + self.0.push(Entry::new(key, value)); } /// Set a value for a style property if it is `Some(_)`. @@ -54,81 +51,63 @@ impl Styles { } } - /// Toggle a boolean style property. + /// Toggle a boolean style property, removing it if it exists and inserting + /// it with `true` if it doesn't. pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) { - let id = StyleId::of::<P>(); - for (i, pair) in self.map.iter_mut().enumerate() { - if pair.0 == id { - self.map.swap_remove(i); + for (i, entry) in self.0.iter_mut().enumerate() { + if entry.is::<P>() { + self.0.swap_remove(i); return; } } - self.map.push((id, Entry::new(key, true))); - } - - /// Get the value of a copyable style property. - /// - /// Returns the property's default value if the map does not contain an - /// entry for it. - pub fn get<P: Property>(&self, key: P) -> P::Value - where - P::Value: Copy, - { - self.get_direct(key) - .map(|&v| P::combine(v, P::default())) - .unwrap_or_else(P::default) - } - - /// Get a reference to a style property. - /// - /// Returns a reference to the property's default value if the map does not - /// contain an entry for it. - pub fn get_ref<P: Property>(&self, key: P) -> &P::Value { - self.get_direct(key).unwrap_or_else(|| P::default_ref()) + self.0.push(Entry::new(key, true)); } - /// Get a reference to a style directly in this map (no default value). - fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> { - self.map - .iter() - .find(|pair| pair.0 == StyleId::of::<P>()) - .and_then(|pair| pair.1.downcast()) - } - - /// Create new styles combining `self` with `outer`. + /// Make `self` the first link of the style chain `outer`. /// - /// Properties from `self` take precedence over the ones from `outer`. - pub fn chain(&self, outer: &Self) -> Self { - let mut styles = self.clone(); - styles.apply(outer); - styles + /// The resulting style chain contains styles from `self` as well as + /// `outer`. The ones from `self` take precedence over the ones from + /// `outer`. For folded properties `self` contributes the inner value. + pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> { + if self.is_empty() { + // No need to chain an empty map. + *outer + } else { + StyleChain { inner: self, outer: Some(outer) } + } } - /// Apply styles from `outer` in-place. + /// Apply styles from `outer` in-place. The resulting style map is + /// equivalent to the style chain created by + /// `self.chain(StyleChain::new(outer))`. /// - /// Properties from `self` take precedence over the ones from `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. pub fn apply(&mut self, outer: &Self) { - 'outer: for pair in &outer.map { - for (id, entry) in &mut self.map { - if pair.0 == *id { - entry.apply(&pair.1); + 'outer: for outer in &outer.0 { + for inner in &mut self.0 { + if inner.style_id() == outer.style_id() { + inner.fold(outer); continue 'outer; } } - self.map.push(pair.clone()); + self.0.push(outer.clone()); } } - /// Keep only those styles that are not also in `other`. + /// 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.map.retain(|a| other.map.iter().all(|b| a != b)); + self.0.retain(|x| !other.0.contains(x)); } - /// Keep only those styles that are also in `other`. + /// Intersect `self` with `other` in-place, keeping only styles that are + /// both in `self` and `other`. pub fn intersect(&mut self, other: &Self) { - self.map.retain(|a| other.map.iter().any(|b| a == b)); + self.0.retain(|x| other.0.contains(x)); } /// Whether two style maps are equal when filtered down to the given @@ -137,113 +116,136 @@ impl Styles { where F: Fn(StyleId) -> bool, { - // TODO(style): Filtered length + one direction equal should suffice. - let f = |e: &&(StyleId, Entry)| filter(e.0); - self.map.iter().filter(f).all(|pair| other.map.contains(pair)) - && other.map.iter().filter(f).all(|pair| self.map.contains(pair)) + let f = |entry: &&Entry| filter(entry.style_id()); + self.0.iter().filter(f).count() == other.0.iter().filter(f).count() + && self.0.iter().filter(f).all(|x| other.0.contains(x)) } } -impl Debug for Styles { +impl Debug for StyleMap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - for pair in &self.map { - writeln!(f, "{:#?}", pair.1)?; - } - Ok(()) - } else { - f.write_str("Styles ")?; - f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish() + for entry in &self.0 { + writeln!(f, "{:#?}", entry)?; } + Ok(()) } } -impl PartialEq for Styles { +impl PartialEq for StyleMap { fn eq(&self, other: &Self) -> bool { self.compatible(other, |_| true) } } -/// An entry for a single style property. -#[derive(Clone)] -pub(crate) struct Entry(Rc<dyn Bounds>); - -impl Entry { - fn new<P: Property>(key: P, value: P::Value) -> Self { - Self(Rc::new((key, value))) - } - - fn downcast<T: 'static>(&self) -> Option<&T> { - self.0.as_any().downcast_ref() - } - - fn apply(&mut self, outer: &Self) { - *self = self.0.combine(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) - } +/// A chain of style maps, similar to a linked list. +/// +/// A style chain allows to conceptually merge (and fold) properties from +/// multiple style maps in a node hierarchy in a non-allocating way. Rather than +/// 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)] +pub struct StyleChain<'a> { + inner: &'a StyleMap, + outer: Option<&'a Self>, } -impl Hash for Entry { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_u64(self.0.hash64()); +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 } } -} - -trait Bounds: '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 combine(&self, outer: &Entry) -> Entry; -} -// `P` is always zero-sized. We only implement the trait for a pair of key and -// associated value so that `P` is a constrained type parameter that we can use -// in `dyn_fmt` to access the property's name. This way, we can effectively -// store the property's name in its vtable instead of having an actual runtime -// string somewhere in `Entry`. -impl<P: Property> Bounds for (P, P::Value) { - fn as_any(&self) -> &dyn Any { - &self.1 + /// Get the (folded) value of a copyable style property. + /// + /// Returns the property's default value if no map in the chain contains an + /// entry for it. + pub fn get<P>(self, key: P) -> P::Value + where + P: Property, + P::Value: Copy, + { + // This exists separately to `get_cloned` for `Copy` types so that + // people don't just naively use `get` / `get_cloned` where they should + // use `get_ref`. + self.get_cloned(key) } - fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { - if f.alternate() { - write!(f, "#[{} = {:?}]", P::NAME, self.1) + /// Get a reference to a style property's value. + /// + /// This is naturally only possible for properties that don't need folding. + /// Prefer `get` if possible or resort to `get_cloned` for non-`Copy` + /// properties that need folding. + /// + /// Returns a reference to the property's default value if no map in the + /// chain contains an entry for it. + pub fn get_ref<P>(self, key: P) -> &'a P::Value + where + P: Property + Nonfolding, + { + if let Some(value) = self.get_locally(key) { + value + } else if let Some(outer) = self.outer { + outer.get_ref(key) } else { - write!(f, "{}: {:?}", P::NAME, self.1) + P::default_ref() } } - fn dyn_eq(&self, other: &Entry) -> bool { - if let Some(other) = other.downcast::<P::Value>() { - &self.1 == other + /// Get the (folded) value of any style property. + /// + /// While this works for all properties, you should prefer `get` or + /// `get_ref` where possible. This is only needed for non-`Copy` properties + /// that need folding. + pub fn get_cloned<P>(self, key: P) -> P::Value + where + P: Property, + { + if let Some(value) = self.get_locally(key).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) } else { - false + P::default() } } - fn hash64(&self) -> u64 { - // No need to hash the TypeId since there's only one - // valid value type per property. - fxhash::hash64(&self.1) + /// Find a property directly in the most local map. + fn get_locally<P: Property>(&self, _: P) -> Option<&'a P::Value> { + self.inner + .0 + .iter() + .find(|entry| entry.is::<P>()) + .and_then(|entry| entry.downcast::<P>()) } +} - fn combine(&self, outer: &Entry) -> Entry { - let outer = outer.downcast::<P::Value>().unwrap(); - let combined = P::combine(self.1.clone(), outer.clone()); - Entry::new(self.0, combined) +impl Debug for StyleChain<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.inner.fmt(f)?; + if let Some(outer) = self.outer { + outer.fmt(f)?; + } + Ok(()) + } +} + +/// A unique identifier for a style property. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct StyleId(TypeId); + +impl StyleId { + /// The style id of the property. + pub fn of<P: Property>() -> Self { + Self(TypeId::of::<P>()) } } @@ -270,23 +272,111 @@ 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 combine a relative font size with an outer + /// For example, this would fold a relative font size with an outer /// absolute font size. #[allow(unused_variables)] - fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value { + fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { inner } } -/// A unique identifier for a style property. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StyleId(TypeId); +/// Marker trait that indicates that a property doesn't need folding. +pub trait Nonfolding {} -impl StyleId { - /// The style id of the property. - pub fn of<P: Property>() -> Self { - Self(TypeId::of::<P>()) +/// 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(&mut 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; +} + +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 + } + + 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.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() } } |
