diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 3b1d05488..55e597b1c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -47,7 +47,7 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result { mod #module { use std::marker::PhantomData; use once_cell::sync::Lazy; - use crate::eval::{Property, StyleId}; + use crate::eval::{Nonfolding, Property, StyleId}; use super::*; #impl_block @@ -106,17 +106,24 @@ fn process_const( // initialization value of the const. let default = &item.expr; + let mut folder = None; + let mut nonfolding = Some(quote! { + impl Nonfolding for Key {} + }); + // Look for a folding function like `#[fold(u64::add)]`. - let mut combinator = None; for attr in &item.attrs { if attr.path.is_ident("fold") { - let fold: syn::Expr = attr.parse_args()?; - combinator = Some(quote! { - fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value { - let f: fn(Self::Value, Self::Value) -> Self::Value = #fold; + let func: syn::Expr = attr.parse_args()?; + folder = Some(quote! { + const FOLDABLE: bool = true; + + fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { + let f: fn(Self::Value, Self::Value) -> Self::Value = #func; f(inner, outer) } }); + nonfolding = None; } } @@ -136,8 +143,10 @@ fn process_const( &*LAZY } - #combinator + #folder } + + #nonfolding }; // The module that will contain the `Key` type. diff --git a/src/eval/class.rs b/src/eval/class.rs index c4393b8a2..15e1f2499 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 { - 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; - fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()>; + fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()>; } struct Shim(PhantomData); @@ -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 17cc46ef3..062187524 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 ffa1eee6c..54d4104df 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>(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) { + fn parbreak(&mut self, break_styles: Option) { // 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 { /// The intersection of the style properties of all `children`. - styles: Styles, + styles: StyleMap, /// The accumulated flow or paragraph children. children: Vec, /// The kind of thing that was last added. @@ -412,7 +412,7 @@ struct Builder { impl Default for Builder { 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 1c4b17aec..f8413feb1 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); -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(key: P, value: P::Value) -> Self { + /// Create a style map from a single property-value pair. + pub fn with(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(&mut self, key: P, value: P::Value) { - let id = StyleId::of::

(); - for pair in &mut self.map { - if pair.0 == id { - let prev = pair.1.downcast::().unwrap(); - let folded = P::combine(value, prev.clone()); - pair.1 = Entry::new(key, folded); + for entry in &mut self.0 { + if entry.is::

() { + let prev = entry.downcast::

().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>(&mut self, key: P) { - let id = StyleId::of::

(); - 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::

() { + self.0.swap_remove(i); return; } } - self.map.push((id, Entry::new(key, true))); + self.0.push(Entry::new(key, true)); } - /// Get the value of a copyable style property. + /// Make `self` the first link of the style chain `outer`. /// - /// Returns the property's default value if the map does not contain an - /// entry for it. - pub fn get(&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) + /// 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) } + } } - /// Get a reference to a style property. + /// Apply styles from `outer` in-place. The resulting style map is + /// equivalent to the style chain created by + /// `self.chain(StyleChain::new(outer))`. /// - /// Returns a reference to the property's default value if the map does not - /// contain an entry for it. - pub fn get_ref(&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(&self, _: P) -> Option<&P::Value> { - self.map - .iter() - .find(|pair| pair.0 == StyleId::of::

()) - .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`. + /// 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); +/// 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 Entry { - fn new(key: P, value: P::Value) -> Self { - Self(Rc::new((key, value))) +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 } } - fn downcast(&self) -> Option<&T> { - self.0.as_any().downcast_ref() + /// 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

(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 apply(&mut self, outer: &Self) { - *self = self.0.combine(outer); + /// 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

(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 { + P::default_ref() + } + } + + /// 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

(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 { + P::default() + } + } + + /// Find a property directly in the most local map. + fn get_locally(&self, _: P) -> Option<&'a P::Value> { + self.inner + .0 + .iter() + .find(|entry| entry.is::

()) + .and_then(|entry| entry.downcast::

()) } } -impl Debug for Entry { +impl Debug for StyleChain<'_> { 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(&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 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) + self.inner.fmt(f)?; + if let Some(outer) = self.outer { + outer.fmt(f)?; } + Ok(()) } +} - fn dyn_eq(&self, other: &Entry) -> bool { - if let Some(other) = other.downcast::() { - &self.1 == other - } else { - false - } - } +/// A unique identifier for a style property. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct StyleId(TypeId); - 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::().unwrap(); - let combined = P::combine(self.1.clone(), outer.clone()); - Entry::new(self.0, combined) +impl StyleId { + /// The style id of the property. + pub fn of() -> Self { + Self(TypeId::of::

()) } } @@ -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() -> Self { - Self(TypeId::of::

()) +/// An entry for a single style property. +#[derive(Clone)] +struct Entry(Rc); + +impl Entry { + fn new(key: P, value: P::Value) -> Self { + Self(Rc::new((key, value))) + } + + fn style_id(&self) -> StyleId { + self.0.style_id() + } + + fn is(&self) -> bool { + self.style_id() == StyleId::of::

() + } + + fn downcast(&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(&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 Bounds for (P, P::Value) { + fn style_id(&self) -> StyleId { + StyleId::of::

() + } + + fn fold(&self, outer: &Entry) -> Entry { + let outer = outer.downcast::

().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::

() { + &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() } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 4c982bb85..e1df3d11c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -15,7 +15,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::rc::Rc; -use crate::eval::Styles; +use crate::eval::{StyleChain, StyleMap}; use crate::font::FontStore; use crate::frame::Frame; use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform}; @@ -30,8 +30,8 @@ pub struct RootNode(pub Vec); impl RootNode { /// Layout the document into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut Context) -> Vec> { - let mut ctx = LayoutContext::new(ctx); - self.0.iter().flat_map(|node| node.layout(&mut ctx)).collect() + let (mut ctx, styles) = LayoutContext::new(ctx); + self.0.iter().flat_map(|node| node.layout(&mut ctx, styles)).collect() } } @@ -52,6 +52,7 @@ pub trait Layout { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>>; /// Convert to a packed node. @@ -63,7 +64,7 @@ pub trait Layout { #[cfg(feature = "layout-cache")] hash: self.hash64(), node: Rc::new(self), - styles: Styles::new(), + styles: StyleMap::new(), } } } @@ -77,26 +78,23 @@ pub struct LayoutContext<'a> { /// Caches layouting artifacts. #[cfg(feature = "layout-cache")] pub layouts: &'a mut LayoutCache, - /// The inherited style properties. - // TODO(style): This probably shouldn't be here. - pub styles: Styles, /// How deeply nested the current layout tree position is. #[cfg(feature = "layout-cache")] - pub level: usize, + level: usize, } impl<'a> LayoutContext<'a> { /// Create a new layout context. - pub fn new(ctx: &'a mut Context) -> Self { - Self { + fn new(ctx: &'a mut Context) -> (Self, StyleChain<'a>) { + let this = Self { fonts: &mut ctx.fonts, images: &mut ctx.images, #[cfg(feature = "layout-cache")] layouts: &mut ctx.layouts, - styles: ctx.styles.clone(), #[cfg(feature = "layout-cache")] level: 0, - } + }; + (this, StyleChain::new(&ctx.styles)) } } @@ -111,6 +109,7 @@ impl Layout for EmptyNode { &self, _: &mut LayoutContext, regions: &Regions, + _: StyleChain, ) -> Vec>> { let size = regions.expand.select(regions.current, Size::zero()); let mut cts = Constraints::new(regions.expand); @@ -128,7 +127,7 @@ pub struct PackedNode { #[cfg(feature = "layout-cache")] hash: u64, /// The node's styles. - pub styles: Styles, + pub styles: StyleMap, } impl PackedNode { @@ -146,7 +145,7 @@ impl PackedNode { } /// Style the node with styles from a style map. - pub fn styled(mut self, styles: Styles) -> Self { + pub fn styled(mut self, styles: StyleMap) -> Self { if self.styles.is_empty() { self.styles = styles; } else { @@ -206,22 +205,25 @@ impl Layout for PackedNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { + let chained = self.styles.chain(&styles); + #[cfg(not(feature = "layout-cache"))] - return self.layout_impl(ctx, regions); + return self.node.layout(ctx, regions, chained); #[cfg(feature = "layout-cache")] let hash = { let mut state = fxhash::FxHasher64::default(); self.hash(&mut state); - ctx.styles.hash(&mut state); + styles.hash(&mut state); state.finish() }; #[cfg(feature = "layout-cache")] ctx.layouts.get(hash, regions).unwrap_or_else(|| { ctx.level += 1; - let frames = self.layout_impl(ctx, regions); + let frames = self.node.layout(ctx, regions, chained); ctx.level -= 1; let entry = FramesEntry::new(frames.clone(), ctx.level); @@ -250,21 +252,6 @@ impl Layout for PackedNode { } } -impl PackedNode { - /// Layout the node without checking the cache. - fn layout_impl( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - ) -> Vec>> { - let new = self.styles.chain(&ctx.styles); - let prev = std::mem::replace(&mut ctx.styles, new); - let frames = self.node.layout(ctx, regions); - ctx.styles = prev; - frames - } -} - impl Default for PackedNode { fn default() -> Self { EmptyNode.pack() diff --git a/src/lib.rs b/src/lib.rs index fd1291ed3..39507d79f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub mod syntax; use std::rc::Rc; use crate::diag::TypResult; -use crate::eval::{Eval, EvalContext, Module, Scope, Styles}; +use crate::eval::{Eval, EvalContext, Module, Scope, StyleMap}; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; @@ -80,7 +80,7 @@ pub struct Context { /// The standard library scope. std: Scope, /// The default styles. - styles: Styles, + styles: StyleMap, } impl Context { @@ -100,7 +100,7 @@ impl Context { } /// A read-only reference to the styles. - pub fn styles(&self) -> &Styles { + pub fn styles(&self) -> &StyleMap { &self.styles } @@ -140,7 +140,7 @@ impl Context { /// This struct is created by [`Context::builder`]. pub struct ContextBuilder { std: Option, - styles: Option, + styles: Option, #[cfg(feature = "layout-cache")] policy: EvictionPolicy, #[cfg(feature = "layout-cache")] @@ -156,7 +156,7 @@ impl ContextBuilder { } /// The default properties for page size, font selection and so on. - pub fn styles(mut self, styles: Styles) -> Self { + pub fn styles(mut self, styles: StyleMap) -> Self { self.styles = Some(styles); self } diff --git a/src/library/align.rs b/src/library/align.rs index 5f21fa378..327352445 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -8,7 +8,7 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult { let aligns: Spec<_> = args.find().unwrap_or_default(); let body: PackedNode = args.expect("body")?; - let mut styles = Styles::new(); + let mut styles = StyleMap::new(); if let Some(align) = aligns.x { styles.set(ParNode::ALIGN, align); } @@ -50,13 +50,14 @@ impl Layout for AlignNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { // The child only needs to expand along an axis if there's no alignment. let mut pod = regions.clone(); pod.expand &= self.aligns.map_is_none(); // Layout the child. - let mut frames = self.child.layout(ctx, &pod); + let mut frames = self.child.layout(ctx, &pod, styles); for ((current, base), Constrained { item: frame, cts }) in regions.iter().zip(&mut frames) diff --git a/src/library/columns.rs b/src/library/columns.rs index f5d06e563..17ae6058f 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -34,6 +34,7 @@ impl Layout for ColumnsNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { let columns = self.columns.get(); @@ -41,7 +42,7 @@ impl Layout for ColumnsNode { // much sense. Note that this line assumes that no infinitely wide // region will follow if the first region's width is finite. if regions.current.x.is_infinite() { - return self.child.layout(ctx, regions); + return self.child.layout(ctx, regions, styles); } // Gutter width for each region. (Can be different because the relative @@ -81,9 +82,9 @@ impl Layout for ColumnsNode { pod.backlog = sizes.into_iter(); let mut finished = vec![]; - let mut frames = self.child.layout(ctx, &pod).into_iter(); + let mut frames = self.child.layout(ctx, &pod, styles).into_iter(); - let dir = ctx.styles.get(ParNode::DIR); + let dir = styles.get(ParNode::DIR); let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; // Stitch together the columns for each region. diff --git a/src/library/flow.rs b/src/library/flow.rs index 10d570224..8656efca4 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -17,8 +17,9 @@ impl Layout for FlowNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { - FlowLayouter::new(self, regions.clone()).layout(ctx) + FlowLayouter::new(self, regions.clone(), styles).layout(ctx) } } @@ -37,14 +38,14 @@ pub enum FlowChild { /// An arbitrary node. Node(PackedNode), /// A paragraph/block break. - Break(Styles), + Break(StyleMap), /// Skip the rest of the region and move to the next. Skip, } impl FlowChild { /// A reference to the child's styles. - pub fn styles(&self) -> Option<&Styles> { + pub fn styles(&self) -> Option<&StyleMap> { match self { Self::Spacing(node) => Some(&node.styles), Self::Node(node) => Some(&node.styles), @@ -54,7 +55,7 @@ impl FlowChild { } /// A mutable reference to the child's styles. - pub fn styles_mut(&mut self) -> Option<&mut Styles> { + pub fn styles_mut(&mut self) -> Option<&mut StyleMap> { match self { Self::Spacing(node) => Some(&mut node.styles), Self::Node(node) => Some(&mut node.styles), @@ -86,6 +87,8 @@ struct FlowLayouter<'a> { children: &'a [FlowChild], /// The regions to layout children into. regions: Regions, + /// The inherited styles. + styles: StyleChain<'a>, /// Whether the flow should expand to fill the region. expand: Spec, /// The full size of `regions.current` that was available before we started @@ -115,7 +118,7 @@ enum FlowItem { impl<'a> FlowLayouter<'a> { /// Create a new flow layouter. - fn new(flow: &'a FlowNode, mut regions: Regions) -> Self { + fn new(flow: &'a FlowNode, mut regions: Regions, styles: StyleChain<'a>) -> Self { let expand = regions.expand; let full = regions.current; @@ -125,6 +128,7 @@ impl<'a> FlowLayouter<'a> { Self { children: &flow.0, regions, + styles, expand, full, used: Size::zero(), @@ -149,7 +153,7 @@ impl<'a> FlowLayouter<'a> { self.layout_node(ctx, node); } FlowChild::Break(styles) => { - let chain = styles.chain(&ctx.styles); + let chain = styles.chain(&self.styles); let em = chain.get(TextNode::SIZE).abs; let amount = chain.get(ParNode::SPACING).resolve(em); self.layout_absolute(amount.into()); @@ -191,7 +195,7 @@ impl<'a> FlowLayouter<'a> { // aligned later. if let Some(placed) = node.downcast::() { if placed.out_of_flow() { - let frame = node.layout(ctx, &self.regions).remove(0); + let frame = node.layout(ctx, &self.regions, self.styles).remove(0); self.items.push(FlowItem::Placed(frame.item)); return; } @@ -202,7 +206,7 @@ impl<'a> FlowLayouter<'a> { // For non-expanding paragraphs it is crucial that we align the // whole paragraph according to its internal alignment. if node.is::() { - node.styles.chain(&ctx.styles).get(ParNode::ALIGN) + node.styles.chain(&self.styles).get(ParNode::ALIGN) } else { Align::Left }, @@ -212,7 +216,7 @@ impl<'a> FlowLayouter<'a> { .unwrap_or(Align::Top), ); - let frames = node.layout(ctx, &self.regions); + let frames = node.layout(ctx, &self.regions, self.styles); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. diff --git a/src/library/grid.rs b/src/library/grid.rs index cc636afc9..ffadf9c24 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -35,9 +35,10 @@ impl Layout for GridNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { // Prepare grid layout by unifying content and gutter tracks. - let mut layouter = GridLayouter::new(self, regions.clone()); + let mut layouter = GridLayouter::new(self, regions.clone(), styles); // Determine all column sizes. layouter.measure_columns(ctx); @@ -93,6 +94,8 @@ struct GridLayouter<'a> { rows: Vec, /// The regions to layout children into. regions: Regions, + /// The inherited styles. + styles: StyleChain<'a>, /// Resolved column sizes. rcols: Vec, /// Rows in the current region. @@ -123,7 +126,7 @@ enum Row { impl<'a> GridLayouter<'a> { /// Prepare grid layout by unifying content and gutter tracks. - fn new(grid: &'a GridNode, mut regions: Regions) -> Self { + fn new(grid: &'a GridNode, mut regions: Regions, styles: StyleChain<'a>) -> Self { let mut cols = vec![]; let mut rows = vec![]; @@ -175,6 +178,7 @@ impl<'a> GridLayouter<'a> { cols, rows, regions, + styles, rcols, lrows, expand, @@ -296,7 +300,7 @@ impl<'a> GridLayouter<'a> { pod.base.y = v.resolve(self.regions.base.y); } - let frame = node.layout(ctx, &pod).remove(0).item; + let frame = node.layout(ctx, &pod, self.styles).remove(0).item; resolved.set_max(frame.size.x); } } @@ -387,8 +391,10 @@ impl<'a> GridLayouter<'a> { pod.base.x = self.regions.base.x; } - let mut sizes = - node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.y); + let mut sizes = node + .layout(ctx, &pod, self.styles) + .into_iter() + .map(|frame| frame.item.size.y); // For each region, we want to know the maximum height any // column requires. @@ -479,7 +485,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Spec::splat(true)); - let frame = node.layout(ctx, &pod).remove(0); + let frame = node.layout(ctx, &pod, self.styles).remove(0); output.push_frame(pos, frame.item); } @@ -522,7 +528,7 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let frames = node.layout(ctx, &pod); + let frames = node.layout(ctx, &pod, self.styles); for (output, frame) in outputs.iter_mut().zip(frames) { output.push_frame(pos, frame.item); } diff --git a/src/library/heading.rs b/src/library/heading.rs index f2ed3194a..111472f44 100644 --- a/src/library/heading.rs +++ b/src/library/heading.rs @@ -32,7 +32,7 @@ impl Construct for HeadingNode { } impl Set for HeadingNode { - fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> { + fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { styles.set_opt(Self::FAMILY, args.named("family")?); styles.set_opt(Self::FILL, args.named("fill")?); Ok(()) @@ -44,23 +44,28 @@ impl Layout for HeadingNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { let upscale = (1.6 - 0.1 * self.level as f64).max(0.75); - ctx.styles.set(TextNode::STRONG, true); - ctx.styles.set(TextNode::SIZE, Relative::new(upscale).into()); - if let Smart::Custom(family) = ctx.styles.get_ref(Self::FAMILY) { - let list = std::iter::once(family) - .chain(ctx.styles.get_ref(TextNode::FAMILY_LIST)) - .cloned() - .collect(); - ctx.styles.set(TextNode::FAMILY_LIST, list); + let mut local = StyleMap::new(); + local.set(TextNode::STRONG, true); + local.set(TextNode::SIZE, Relative::new(upscale).into()); + + if let Smart::Custom(family) = styles.get_ref(Self::FAMILY) { + local.set( + TextNode::FAMILY_LIST, + std::iter::once(family) + .chain(styles.get_ref(TextNode::FAMILY_LIST)) + .cloned() + .collect(), + ); } - if let Smart::Custom(fill) = ctx.styles.get(Self::FILL) { - ctx.styles.set(TextNode::FILL, fill); + if let Smart::Custom(fill) = styles.get(Self::FILL) { + local.set(TextNode::FILL, fill); } - self.child.layout(ctx, regions) + self.child.layout(ctx, regions, local.chain(&styles)) } } diff --git a/src/library/image.rs b/src/library/image.rs index 4e5acdc65..2b91b2fca 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -42,6 +42,7 @@ impl Layout for ImageNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { let img = ctx.images.get(self.id); let pxw = img.width() as f64; @@ -89,7 +90,7 @@ impl Layout for ImageNode { } // Apply link if it exists. - if let Some(url) = ctx.styles.get_ref(LinkNode::URL) { + if let Some(url) = styles.get_ref(LinkNode::URL) { frame.link(url); } diff --git a/src/library/link.rs b/src/library/link.rs index 0f3417936..d1d7842df 100644 --- a/src/library/link.rs +++ b/src/library/link.rs @@ -15,7 +15,7 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult { }); Ok(Value::Node( - body.styled(Styles::one(LinkNode::URL, Some(url))), + body.styled(StyleMap::with(LinkNode::URL, Some(url))), )) } diff --git a/src/library/list.rs b/src/library/list.rs index 765cc22bc..a9dfbec50 100644 --- a/src/library/list.rs +++ b/src/library/list.rs @@ -32,7 +32,7 @@ impl Construct for ListNode { } impl Set for ListNode { - fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> { + fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?); styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?); Ok(()) @@ -44,10 +44,11 @@ impl Layout for ListNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { - let em = ctx.styles.get(TextNode::SIZE).abs; - let label_indent = ctx.styles.get(Self::LABEL_INDENT).resolve(em); - let body_indent = ctx.styles.get(Self::BODY_INDENT).resolve(em); + let em = styles.get(TextNode::SIZE).abs; + let label_indent = styles.get(Self::LABEL_INDENT).resolve(em); + let body_indent = styles.get(Self::BODY_INDENT).resolve(em); let columns = vec![ TrackSizing::Linear(label_indent.into()), @@ -68,7 +69,7 @@ impl Layout for ListNode { gutter: Spec::default(), children, } - .layout(ctx, regions) + .layout(ctx, regions, styles) } } diff --git a/src/library/mod.rs b/src/library/mod.rs index 0cdabcb0b..9033b3a76 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -61,7 +61,8 @@ prelude! { pub use crate::diag::{At, TypResult}; pub use crate::eval::{ - Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value, + Args, Construct, EvalContext, Node, Property, Set, Smart, StyleChain, StyleMap, + Value, }; pub use crate::frame::*; pub use crate::geom::*; diff --git a/src/library/pad.rs b/src/library/pad.rs index 96da7a0a2..e2969fd64 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -34,10 +34,11 @@ impl Layout for PadNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { // Layout child into padded regions. let pod = regions.map(|size| shrink(size, self.padding)); - let mut frames = self.child.layout(ctx, &pod); + let mut frames = self.child.layout(ctx, &pod, styles); for ((current, base), Constrained { item: frame, cts }) in regions.iter().zip(&mut frames) diff --git a/src/library/page.rs b/src/library/page.rs index 520cdaef0..69be2bb7b 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -12,7 +12,7 @@ pub struct PageNode { /// The node producing the content. pub child: PackedNode, /// The page's styles. - pub styles: Styles, + pub styles: StyleMap, } #[properties] @@ -44,14 +44,14 @@ impl PageNode { impl Construct for PageNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult { Ok(Node::Page(Self { - styles: Styles::new(), child: args.expect("body")?, + styles: StyleMap::new(), })) } } impl Set for PageNode { - fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> { + fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { if let Some(paper) = args.named::("paper")?.or_else(|| args.find()) { styles.set(Self::CLASS, paper.class()); styles.set(Self::WIDTH, Smart::Custom(paper.width())); @@ -85,41 +85,40 @@ impl Set for PageNode { impl PageNode { /// Style the node with styles from a style map. - pub fn styled(mut self, styles: Styles) -> Self { + pub fn styled(mut self, styles: StyleMap) -> Self { self.styles.apply(&styles); self } /// Layout the page run into a sequence of frames, one per page. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { - let prev = ctx.styles.clone(); - ctx.styles = self.styles.chain(&ctx.styles); + pub fn layout(&self, ctx: &mut LayoutContext, styles: StyleChain) -> Vec> { + let styles = self.styles.chain(&styles); // When one of the lengths is infinite the page fits its content along // that axis. - let width = ctx.styles.get(Self::WIDTH).unwrap_or(Length::inf()); - let height = ctx.styles.get(Self::HEIGHT).unwrap_or(Length::inf()); + let width = styles.get(Self::WIDTH).unwrap_or(Length::inf()); + let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf()); let mut size = Size::new(width, height); - if ctx.styles.get(Self::FLIPPED) { + if styles.get(Self::FLIPPED) { std::mem::swap(&mut size.x, &mut size.y); } // Determine the margins. - let class = ctx.styles.get(Self::CLASS); + let class = styles.get(Self::CLASS); let default = class.default_margins(); let padding = Sides { - left: ctx.styles.get(Self::LEFT).unwrap_or(default.left), - right: ctx.styles.get(Self::RIGHT).unwrap_or(default.right), - top: ctx.styles.get(Self::TOP).unwrap_or(default.top), - bottom: ctx.styles.get(Self::BOTTOM).unwrap_or(default.bottom), + left: styles.get(Self::LEFT).unwrap_or(default.left), + right: styles.get(Self::RIGHT).unwrap_or(default.right), + top: styles.get(Self::TOP).unwrap_or(default.top), + bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom), }; // Realize columns with columns node. - let columns = ctx.styles.get(Self::COLUMNS); + let columns = styles.get(Self::COLUMNS); let child = if columns.get() > 1 { ColumnsNode { columns, - gutter: ctx.styles.get(Self::COLUMN_GUTTER), + gutter: styles.get(Self::COLUMN_GUTTER), child: self.child.clone(), } .pack() @@ -133,18 +132,20 @@ impl PageNode { // Layout the child. let expand = size.map(Length::is_finite); let regions = Regions::repeat(size, size, expand); - let mut frames: Vec<_> = - child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect(); + let mut frames: Vec<_> = child + .layout(ctx, ®ions, styles) + .into_iter() + .map(|c| c.item) + .collect(); // Add background fill if requested. - if let Some(fill) = ctx.styles.get(Self::FILL) { + if let Some(fill) = styles.get(Self::FILL) { for frame in &mut frames { let shape = Shape::filled(Geometry::Rect(frame.size), fill); Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } } - ctx.styles = prev; frames } } diff --git a/src/library/par.rs b/src/library/par.rs index 2bedc98b8..65a541f66 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -35,7 +35,7 @@ impl Construct for ParNode { } impl Set for ParNode { - fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> { + fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { let spacing = args.named("spacing")?; let leading = args.named("leading")?; @@ -78,17 +78,18 @@ impl Layout for ParNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { // Collect all text into one string used for BiDi analysis. let text = self.collect_text(); // Find out the BiDi embedding levels. - let level = Level::from_dir(ctx.styles.get(Self::DIR)); + let level = Level::from_dir(styles.get(Self::DIR)); let bidi = BidiInfo::new(&text, level); // Prepare paragraph layout by building a representation on which we can // do line breaking without layouting each and every line from scratch. - let layouter = ParLayouter::new(self, ctx, regions, bidi); + let layouter = ParLayouter::new(self, ctx, regions, &styles, bidi); // Find suitable linebreaks. layouter.layout(ctx, regions.clone()) @@ -147,12 +148,12 @@ pub enum ParChild { impl ParChild { /// Create a text child. - pub fn text(text: impl Into, styles: Styles) -> Self { + pub fn text(text: impl Into, styles: StyleMap) -> Self { Self::Text(TextNode { text: text.into(), styles }) } /// A reference to the child's styles. - pub fn styles(&self) -> &Styles { + pub fn styles(&self) -> &StyleMap { match self { Self::Spacing(node) => &node.styles, Self::Text(node) => &node.styles, @@ -161,7 +162,7 @@ impl ParChild { } /// A mutable reference to the child's styles. - pub fn styles_mut(&mut self) -> &mut Styles { + pub fn styles_mut(&mut self) -> &mut StyleMap { match self { Self::Spacing(node) => &mut node.styles, Self::Text(node) => &mut node.styles, @@ -226,6 +227,7 @@ impl<'a> ParLayouter<'a> { par: &'a ParNode, ctx: &mut LayoutContext, regions: &Regions, + styles: &'a StyleChain<'a>, bidi: BidiInfo<'a>, ) -> Self { let mut items = vec![]; @@ -253,7 +255,7 @@ impl<'a> ParLayouter<'a> { cursor += group.len(); let subrange = start .. cursor; let text = &bidi.text[subrange.clone()]; - let styles = node.styles.chain(&ctx.styles); + let styles = node.styles.chain(styles); let shaped = shape(ctx.fonts, text, styles, level.dir()); items.push(ParItem::Text(shaped)); ranges.push(subrange); @@ -262,16 +264,16 @@ impl<'a> ParLayouter<'a> { ParChild::Node(node) => { let size = Size::new(regions.current.x, regions.base.y); let pod = Regions::one(size, regions.base, Spec::splat(false)); - let frame = node.layout(ctx, &pod).remove(0); + let frame = node.layout(ctx, &pod, *styles).remove(0); items.push(ParItem::Frame(Rc::take(frame.item))); ranges.push(range); } } } - let em = ctx.styles.get(TextNode::SIZE).abs; - let align = ctx.styles.get(ParNode::ALIGN); - let leading = ctx.styles.get(ParNode::LEADING).resolve(em); + let em = styles.get(TextNode::SIZE).abs; + let align = styles.get(ParNode::ALIGN); + let leading = styles.get(ParNode::LEADING).resolve(em); Self { align, leading, bidi, items, ranges } } diff --git a/src/library/placed.rs b/src/library/placed.rs index 5d98edc4d..8a4f46af8 100644 --- a/src/library/placed.rs +++ b/src/library/placed.rs @@ -34,6 +34,7 @@ impl Layout for PlacedNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { let out_of_flow = self.out_of_flow(); @@ -45,7 +46,7 @@ impl Layout for PlacedNode { Regions::one(regions.base, regions.base, expand) }; - let mut frames = self.0.layout(ctx, &pod); + let mut frames = self.0.layout(ctx, &pod, styles); let Constrained { item: frame, cts } = &mut frames[0]; // If expansion is off, zero all sizes so that we don't take up any diff --git a/src/library/shape.rs b/src/library/shape.rs index 15a857ef6..76c03edbd 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -106,11 +106,12 @@ impl Layout for ShapeNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { let mut frames; if let Some(child) = &self.child { let mut pod = Regions::one(regions.current, regions.base, regions.expand); - frames = child.layout(ctx, &pod); + frames = child.layout(ctx, &pod, styles); // Relayout with full expansion into square region to make sure // the result is really a square or circle. @@ -126,7 +127,7 @@ impl Layout for ShapeNode { pod.current = Size::splat(length); pod.expand = Spec::splat(true); - frames = child.layout(ctx, &pod); + frames = child.layout(ctx, &pod, styles); frames[0].cts = Constraints::tight(regions); } } else { @@ -169,7 +170,7 @@ impl Layout for ShapeNode { } // Apply link if it exists. - if let Some(url) = ctx.styles.get_ref(LinkNode::URL) { + if let Some(url) = styles.get_ref(LinkNode::URL) { frame.link(url); } diff --git a/src/library/sized.rs b/src/library/sized.rs index 81ca69c2e..2400971ab 100644 --- a/src/library/sized.rs +++ b/src/library/sized.rs @@ -30,6 +30,7 @@ impl Layout for SizedNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { let is_auto = self.sizing.map_is_none(); let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative)); @@ -51,7 +52,7 @@ impl Layout for SizedNode { Regions::one(size, base, expand) }; - let mut frames = self.child.layout(ctx, &pod); + let mut frames = self.child.layout(ctx, &pod, styles); let Constrained { item: frame, cts } = &mut frames[0]; // Ensure frame size matches regions size if expansion is on. diff --git a/src/library/spacing.rs b/src/library/spacing.rs index e552828ce..a58884855 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -24,7 +24,7 @@ pub struct SpacingNode { /// The kind of spacing. pub kind: SpacingKind, /// The spacing's styles. - pub styles: Styles, + pub styles: StyleMap, } impl Debug for SpacingNode { diff --git a/src/library/stack.rs b/src/library/stack.rs index d7748378c..d36c2e15c 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -28,8 +28,9 @@ impl Layout for StackNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { - StackLayouter::new(self, regions.clone()).layout(ctx) + StackLayouter::new(self, regions.clone(), styles).layout(ctx) } } @@ -44,7 +45,7 @@ pub enum StackChild { impl From for StackChild { fn from(kind: SpacingKind) -> Self { - Self::Spacing(SpacingNode { kind, styles: Styles::new() }) + Self::Spacing(SpacingNode { kind, styles: StyleMap::new() }) } } @@ -79,6 +80,8 @@ struct StackLayouter<'a> { spacing: Option, /// The regions to layout children into. regions: Regions, + /// The inherited styles. + styles: StyleChain<'a>, /// Whether the stack should expand to fill the region. expand: Spec, /// The full size of `regions.current` that was available before we started @@ -106,7 +109,7 @@ enum StackItem { impl<'a> StackLayouter<'a> { /// Create a new stack layouter. - fn new(stack: &'a StackNode, mut regions: Regions) -> Self { + fn new(stack: &'a StackNode, mut regions: Regions, styles: StyleChain<'a>) -> Self { let dir = stack.dir; let axis = dir.axis(); let expand = regions.expand; @@ -121,6 +124,7 @@ impl<'a> StackLayouter<'a> { axis, spacing: stack.spacing, regions, + styles, expand, full, used: Gen::zero(), @@ -190,7 +194,7 @@ impl<'a> StackLayouter<'a> { .and_then(|node| node.aligns.get(self.axis)) .unwrap_or(self.dir.start().into()); - let frames = node.layout(ctx, &self.regions); + let frames = node.layout(ctx, &self.regions, self.styles); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. diff --git a/src/library/text.rs b/src/library/text.rs index 26e949fc9..5fa19cb99 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -23,7 +23,7 @@ pub struct TextNode { /// The run's text. pub text: EcoString, /// The run's styles. - pub styles: Styles, + pub styles: StyleMap, } #[properties] @@ -108,7 +108,7 @@ impl Construct for TextNode { } impl Set for TextNode { - fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> { + fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { let list = args.named("family")?.or_else(|| { let families: Vec<_> = args.all().collect(); (!families.is_empty()).then(|| families) @@ -412,7 +412,7 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult { let body: Node = args.expect("body")?; let deco = LineDecoration { kind, stroke, thickness, offset, extent }; Ok(Value::Node( - body.styled(Styles::one(TextNode::LINES, vec![deco])), + body.styled(StyleMap::with(TextNode::LINES, vec![deco])), )) } @@ -449,7 +449,7 @@ pub enum LineKind { pub fn shape<'a>( fonts: &mut FontStore, text: &'a str, - styles: Styles, + styles: StyleChain<'a>, dir: Dir, ) -> ShapedText<'a> { let mut glyphs = vec![]; @@ -459,16 +459,16 @@ pub fn shape<'a>( &mut glyphs, 0, text, - variant(&styles), - families(&styles), + variant(styles), + families(styles), None, dir, - &tags(&styles), + &tags(styles), ); } track(&mut glyphs, styles.get(TextNode::TRACKING)); - let (size, baseline) = measure(fonts, &glyphs, &styles); + let (size, baseline) = measure(fonts, &glyphs, styles); ShapedText { text, @@ -627,7 +627,7 @@ fn track(glyphs: &mut [ShapedGlyph], tracking: Em) { fn measure( fonts: &mut FontStore, glyphs: &[ShapedGlyph], - styles: &Styles, + styles: StyleChain, ) -> (Size, Length) { let mut width = Length::zero(); let mut top = Length::zero(); @@ -667,7 +667,7 @@ fn measure( } /// Resolve the font variant with `STRONG` and `EMPH` factored in. -fn variant(styles: &Styles) -> FontVariant { +fn variant(styles: StyleChain) -> FontVariant { let mut variant = FontVariant::new( styles.get(TextNode::STYLE), styles.get(TextNode::WEIGHT), @@ -690,7 +690,7 @@ fn variant(styles: &Styles) -> FontVariant { } /// Resolve a prioritized iterator over the font families. -fn families(styles: &Styles) -> impl Iterator + Clone { +fn families(styles: StyleChain) -> impl Iterator + Clone { let head = if styles.get(TextNode::MONOSPACE) { styles.get_ref(TextNode::MONOSPACE_LIST).as_slice() } else { @@ -719,7 +719,7 @@ fn families(styles: &Styles) -> impl Iterator + Clone { } /// Collect the tags of the OpenType features to apply. -fn tags(styles: &Styles) -> Vec { +fn tags(styles: StyleChain) -> Vec { let mut tags = vec![]; let mut feat = |tag, value| { tags.push(Feature::new(Tag::from_bytes(tag), value, ..)); @@ -803,8 +803,7 @@ pub struct ShapedText<'a> { /// The text direction. pub dir: Dir, /// The text's style properties. - // TODO(style): Go back to reference. - pub styles: Styles, + pub styles: StyleChain<'a>, /// The font size. pub size: Size, /// The baseline from the top of the frame. @@ -858,7 +857,7 @@ impl<'a> ShapedText<'a> { frame.push(pos, Element::Text(text)); // Apply line decorations. - for line in self.styles.get_ref(TextNode::LINES) { + for line in self.styles.get_cloned(TextNode::LINES) { let face = fonts.get(face_id); let metrics = match line.kind { LineKind::Underline => face.underline, @@ -905,7 +904,7 @@ impl<'a> ShapedText<'a> { text_range: Range, ) -> ShapedText<'a> { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { - let (size, baseline) = measure(fonts, glyphs, &self.styles); + let (size, baseline) = measure(fonts, glyphs, self.styles); Self { text: &self.text[text_range], dir: self.dir, diff --git a/src/library/transform.rs b/src/library/transform.rs index 03f0a84c9..7392b89f9 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -53,8 +53,9 @@ impl Layout for TransformNode { &self, ctx: &mut LayoutContext, regions: &Regions, + styles: StyleChain, ) -> Vec>> { - let mut frames = self.child.layout(ctx, regions); + let mut frames = self.child.layout(ctx, regions, styles); for Constrained { item: frame, .. } in &mut frames { let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s)); diff --git a/tests/typeset.rs b/tests/typeset.rs index 452d6584d..164ccc913 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -11,7 +11,7 @@ use usvg::FitTo; use walkdir::WalkDir; use typst::diag::Error; -use typst::eval::{Smart, Styles, Value}; +use typst::eval::{Smart, StyleMap, Value}; use typst::font::Face; use typst::frame::{Element, Frame, Geometry, Shape, Stroke, Text}; use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Size, Transform}; @@ -70,7 +70,7 @@ fn main() { // Set page width to 120pt with 10pt margins, so that the inner page is // exactly 100pt wide. Page height is unbounded and font size is 10pt so // that it multiplies to nice round numbers. - let mut styles = Styles::new(); + let mut styles = StyleMap::new(); styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0))); styles.set(PageNode::HEIGHT, Smart::Auto); styles.set(PageNode::LEFT, Smart::Custom(Length::pt(10.0).into()));