typst/src/eval/styles.rs
2021-12-22 19:19:32 +01:00

293 lines
8.5 KiB
Rust

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>())
}
}