summaryrefslogtreecommitdiff
path: root/src/eval/styles.rs
diff options
context:
space:
mode:
authorMartin <mhaug@live.de>2021-12-22 20:37:34 +0100
committerGitHub <noreply@github.com>2021-12-22 20:37:34 +0100
commitf6c7a8292dc1ab0560408fca9d74505e9d7cf13a (patch)
treebadd3076f6146cec34c55764600df5124c408521 /src/eval/styles.rs
parent738ff7e1f573bef678932b313be9969a17af8d22 (diff)
parent438255519e88bb790480306b9a9b452aaf054519 (diff)
Merge pull request #51 from typst/set-rules
Set rules
Diffstat (limited to 'src/eval/styles.rs')
-rw-r--r--src/eval/styles.rs292
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>())
+ }
+}