diff options
| author | Martin <mhaug@live.de> | 2021-12-22 20:37:34 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-22 20:37:34 +0100 |
| commit | f6c7a8292dc1ab0560408fca9d74505e9d7cf13a (patch) | |
| tree | badd3076f6146cec34c55764600df5124c408521 /src/eval/styles.rs | |
| parent | 738ff7e1f573bef678932b313be9969a17af8d22 (diff) | |
| parent | 438255519e88bb790480306b9a9b452aaf054519 (diff) | |
Merge pull request #51 from typst/set-rules
Set rules
Diffstat (limited to 'src/eval/styles.rs')
| -rw-r--r-- | src/eval/styles.rs | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/src/eval/styles.rs b/src/eval/styles.rs new file mode 100644 index 00000000..1c4b17ae --- /dev/null +++ b/src/eval/styles.rs @@ -0,0 +1,292 @@ +use std::any::{Any, TypeId}; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::rc::Rc; + +// TODO(style): Possible optimizations: +// - Ref-count map for cheaper cloning and smaller footprint +// - Store map in `Option` to make empty maps non-allocating +// - Store small properties inline + +/// A map of style properties. +#[derive(Default, Clone, Hash)] +pub struct Styles { + map: Vec<(StyleId, Entry)>, +} + +impl Styles { + /// Create a new, empty style map. + pub fn new() -> Self { + Self { map: vec![] } + } + + /// Whether this map contains no styles. + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Create a style map with a single property-value pair. + pub fn one<P: Property>(key: P, value: P::Value) -> Self { + let mut styles = Self::new(); + styles.set(key, value); + 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); + return; + } + } + + self.map.push((id, Entry::new(key, value))); + } + + /// Set a value for a style property if it is `Some(_)`. + pub fn set_opt<P: Property>(&mut self, key: P, value: Option<P::Value>) { + if let Some(value) = value { + self.set(key, value); + } + } + + /// Toggle a boolean style property. + 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); + 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()) + } + + /// 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`. + /// + /// 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 + } + + /// Apply styles from `outer` in-place. + /// + /// Properties from `self` take precedence over the ones from `outer`. + 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); + continue 'outer; + } + } + + self.map.push(pair.clone()); + } + } + + /// Keep only those styles that are not also in `other`. + pub fn erase(&mut self, other: &Self) { + self.map.retain(|a| other.map.iter().all(|b| a != b)); + } + + /// Keep only those styles that are also in `other`. + pub fn intersect(&mut self, other: &Self) { + self.map.retain(|a| other.map.iter().any(|b| a == b)); + } + + /// 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, + { + // 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)) + } +} + +impl Debug for Styles { + 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() + } + } +} + +impl PartialEq for Styles { + 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) + } +} + +impl Hash for Entry { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.0.hash64()); + } +} + +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 + } + + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { + if f.alternate() { + write!(f, "#[{} = {:?}]", P::NAME, self.1) + } else { + write!(f, "{}: {:?}", P::NAME, self.1) + } + } + + fn dyn_eq(&self, other: &Entry) -> bool { + if let Some(other) = other.downcast::<P::Value>() { + &self.1 == other + } else { + false + } + } + + fn hash64(&self) -> u64 { + // No need to hash the TypeId since there's only one + // valid value type per property. + fxhash::hash64(&self.1) + } + + 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) + } +} + +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[properties]` proc-macro. +pub trait Property: Copy + '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 + 'static; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// 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 + /// `#[properties]` 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 combine a relative font size with an outer + /// absolute font size. + #[allow(unused_variables)] + fn combine(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); + +impl StyleId { + /// The style id of the property. + pub fn of<P: Property>() -> Self { + Self(TypeId::of::<P>()) + } +} |
