From 5d05c3f68a32c4214661a6807a5358865f54f0af Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 5 Feb 2022 12:41:42 +0100 Subject: [PATCH] Refactor and fix style folding --- macros/src/lib.rs | 4 +- src/eval/styles.rs | 207 ++++++++++++++------------------- tests/ref/layout/pagebreak.png | Bin 5140 -> 4607 bytes tests/ref/style/construct.png | Bin 935 -> 2029 bytes tests/typ/layout/pagebreak.typ | 4 +- tests/typ/style/construct.typ | 7 ++ 6 files changed, 96 insertions(+), 126 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index b2dee7c91..efaf8be8a 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -148,7 +148,7 @@ fn process_const( if attr.path.is_ident("fold") { let func: syn::Expr = attr.parse_args()?; folder = Some(quote! { - const FOLDABLE: bool = true; + const FOLDING: bool = true; fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { let f: fn(Self::Value, Self::Value) -> Self::Value = #func; @@ -179,7 +179,7 @@ fn process_const( const NAME: &'static str = #name; - fn class_id() -> TypeId { + fn node_id() -> TypeId { TypeId::of::<#self_ty>() } diff --git a/src/eval/styles.rs b/src/eval/styles.rs index e3dc51bbc..14826aa83 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -3,11 +3,6 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -// 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 - /// An item with associated styles. #[derive(PartialEq, Clone, Hash)] pub struct Styled { @@ -68,15 +63,6 @@ impl StyleMap { /// Set the value for a style property. pub fn set(&mut self, key: P, value: P::Value) { - 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.0.push(Entry::new(key, value)); } @@ -121,14 +107,7 @@ impl StyleMap { /// 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) { - for outer in &outer.0 { - if let Some(inner) = self.0.iter_mut().find(|inner| inner.is_same(outer)) { - *inner = inner.fold(outer); - continue; - } - - self.0.push(outer.clone()); - } + self.0.splice(0 .. 0, outer.0.clone()); } /// Subtract `other` from `self` in-place, keeping only styles that are in @@ -144,7 +123,7 @@ impl StyleMap { } /// Whether two style maps are equal when filtered down to properties of the - /// class `T`. + /// node `T`. pub fn compatible(&self, other: &Self) -> bool { let f = |entry: &&Entry| entry.is_of::(); self.0.iter().filter(f).count() == other.0.iter().filter(f).count() @@ -154,7 +133,7 @@ impl StyleMap { impl Debug for StyleMap { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for entry in &self.0 { + for entry in self.0.iter().rev() { writeln!(f, "{:#?}", entry)?; } Ok(()) @@ -176,23 +155,26 @@ impl PartialEq for StyleMap { /// matches further up the chain. #[derive(Clone, Copy, Hash)] pub struct StyleChain<'a> { - /// The first map in the chain. + /// The first link in the chain. first: Link<'a>, - /// The remaining maps in the chain. + /// The remaining links in the chain. outer: Option<&'a Self>, } /// The two kinds of links in the chain. #[derive(Clone, Copy, Hash)] enum Link<'a> { + /// Just a map with styles. Map(&'a StyleMap), + /// A barrier that, in combination with one more such barrier, stops scoped + /// styles for the node with this type id. Barrier(TypeId), } impl<'a> StyleChain<'a> { /// Start a new style chain with a root map. - pub fn new(map: &'a StyleMap) -> Self { - Self { first: Link::Map(map), outer: None } + pub fn new(first: &'a StyleMap) -> Self { + Self { first: Link::Map(first), outer: None } } /// Get the (folded) value of a copyable style property. @@ -207,7 +189,7 @@ impl<'a> StyleChain<'a> { where P::Value: Copy, { - self.get_impl(key, 0) + self.get_cloned(key) } /// Get a reference to a style property's value. @@ -222,7 +204,7 @@ impl<'a> StyleChain<'a> { where P: Nonfolding, { - self.get_ref_impl(key, 0) + self.values(key).next().unwrap_or_else(|| P::default_ref()) } /// Get the (folded) value of any style property. @@ -234,7 +216,15 @@ impl<'a> StyleChain<'a> { /// Returns the property's default value if no map in the chain contains an /// entry for it. pub fn get_cloned(self, key: P) -> P::Value { - self.get_impl(key, 0) + if P::FOLDING { + self.values(key) + .cloned() + .chain(std::iter::once(P::default())) + .reduce(P::fold) + .unwrap() + } else { + self.values(key).next().cloned().unwrap_or_else(P::default) + } } /// Insert a barrier into the style chain. @@ -242,81 +232,60 @@ impl<'a> StyleChain<'a> { /// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style /// can still be read through a single barrier (the one of the node it /// _should_ apply to), but a second barrier will make it invisible. - pub fn barred<'b>(&'b self, class: TypeId) -> StyleChain<'b> { - if self.needs_barrier(class) { + pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> { + if self + .maps() + .any(|map| map.0.iter().any(|entry| entry.scoped && entry.is_of_same(node))) + { StyleChain { - first: Link::Barrier(class), + first: Link::Barrier(node), outer: Some(self), } } else { *self } } -} -impl<'a> StyleChain<'a> { - fn get_impl(self, key: P, depth: usize) -> P::Value { - let (value, depth) = self.process(key, depth); - if let Some(value) = value.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 + /// Iterate over all values for the given property in the chain. + fn values(self, _: P) -> impl Iterator { + let mut depth = 0; + self.links().flat_map(move |link| { + let mut entries: &[Entry] = &[]; + match link { + Link::Map(map) => entries = &map.0, + Link::Barrier(id) => depth += (id == P::node_id()) as usize, } - } else if let Some(outer) = self.outer { - outer.get_impl(key, depth) - } else { - P::default() - } + entries + .iter() + .rev() + .filter(move |entry| entry.is::

() && (!entry.scoped || depth <= 1)) + .filter_map(|entry| entry.downcast::

()) + }) } - fn get_ref_impl(self, key: P, depth: usize) -> &'a P::Value - where - P: Nonfolding, - { - let (value, depth) = self.process(key, depth); - if let Some(value) = value { - value - } else if let Some(outer) = self.outer { - outer.get_ref_impl(key, depth) - } else { - P::default_ref() - } + /// Iterate over the links of the chain. + fn links(self) -> impl Iterator> { + let mut cursor = Some(self); + std::iter::from_fn(move || { + let Self { first, outer } = cursor?; + cursor = outer.copied(); + Some(first) + }) } - fn process(self, _: P, depth: usize) -> (Option<&'a P::Value>, usize) { - match self.first { - Link::Map(map) => ( - map.0 - .iter() - .find(|entry| entry.is::

() && (!entry.scoped || depth <= 1)) - .and_then(|entry| entry.downcast::

()), - depth, - ), - Link::Barrier(class) => (None, depth + (P::class_id() == class) as usize), - } - } - - fn needs_barrier(self, class: TypeId) -> bool { - if let Link::Map(map) = self.first { - if map.0.iter().any(|entry| entry.is_of_same(class)) { - return true; - } - } - - self.outer.map_or(false, |outer| outer.needs_barrier(class)) + /// Iterate over the map links of the chain. + fn maps(self) -> impl Iterator { + self.links().filter_map(|link| match link { + Link::Map(map) => Some(map), + Link::Barrier(_) => None, + }) } } impl Debug for StyleChain<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.first.fmt(f)?; - if let Some(outer) = self.outer { - outer.fmt(f)?; + for link in self.links() { + link.fmt(f)?; } Ok(()) } @@ -334,55 +303,56 @@ impl Debug for Link<'_> { /// An entry for a single style property. #[derive(Clone)] struct Entry { - p: Arc, + pair: Arc, scoped: bool, } impl Entry { fn new(key: P, value: P::Value) -> Self { - Self { p: Arc::new((key, value)), scoped: false } + Self { + pair: Arc::new((key, value)), + scoped: false, + } } fn is(&self) -> bool { - self.p.style_id() == TypeId::of::

() - } - - fn is_same(&self, other: &Self) -> bool { - self.p.style_id() == other.p.style_id() + self.pair.style_id() == TypeId::of::

() } fn is_of(&self) -> bool { - self.p.class_id() == TypeId::of::() + self.pair.node_id() == TypeId::of::() } - fn is_of_same(&self, class: TypeId) -> bool { - self.p.class_id() == class + fn is_of_same(&self, node: TypeId) -> bool { + self.pair.node_id() == node } fn downcast(&self) -> Option<&P::Value> { - self.p.as_any().downcast_ref() - } - - fn fold(&self, outer: &Self) -> Self { - self.p.fold(outer) + self.pair.as_any().downcast_ref() } } impl Debug for Entry { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.p.dyn_fmt(f) + f.write_str("#[")?; + self.pair.dyn_fmt(f)?; + if self.scoped { + f.write_str(" (scoped)")?; + } + f.write_str("]") } } impl PartialEq for Entry { fn eq(&self, other: &Self) -> bool { - self.p.dyn_eq(other) + self.pair.dyn_eq(other) && self.scoped == other.scoped } } impl Hash for Entry { fn hash(&self, state: &mut H) { - state.write_u64(self.p.hash64()); + state.write_u64(self.pair.hash64()); + state.write_u8(self.scoped as u8); } } @@ -390,7 +360,7 @@ impl Hash for Entry { /// /// This trait is not intended to be implemented manually, but rather through /// the `#[properties]` proc-macro. -pub trait Property: Copy + Sync + Send + 'static { +pub trait Property: Sync + Send + '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. @@ -400,10 +370,10 @@ pub trait Property: Copy + Sync + Send + 'static { const NAME: &'static str; /// Whether the property needs folding. - const FOLDABLE: bool = false; + const FOLDING: bool = false; - /// The type id of the class this property belongs to. - fn class_id() -> TypeId; + /// The type id of the node this property belongs to. + fn node_id() -> TypeId; /// The default value of the property. fn default() -> Self::Value; @@ -437,9 +407,8 @@ trait Bounds: Sync + Send + 'static { fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; fn dyn_eq(&self, other: &Entry) -> bool; fn hash64(&self) -> u64; - fn class_id(&self) -> TypeId; + fn node_id(&self) -> TypeId; fn style_id(&self) -> TypeId; - fn fold(&self, outer: &Entry) -> Entry; } impl Bounds for (P, P::Value) { @@ -448,11 +417,11 @@ impl Bounds for (P, P::Value) { } fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "#[{} = {:?}]", P::NAME, self.1) + write!(f, "{} = {:?}", P::NAME, self.1) } fn dyn_eq(&self, other: &Entry) -> bool { - self.style_id() == other.p.style_id() + self.style_id() == other.pair.style_id() && if let Some(other) = other.downcast::

() { &self.1 == other } else { @@ -467,17 +436,11 @@ impl Bounds for (P, P::Value) { state.finish() } - fn class_id(&self) -> TypeId { - P::class_id() + fn node_id(&self) -> TypeId { + P::node_id() } fn style_id(&self) -> TypeId { TypeId::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) - } } diff --git a/tests/ref/layout/pagebreak.png b/tests/ref/layout/pagebreak.png index 364d9294651281832f62bdd74a1b4eb8b2cc6964..454d106db7de6fcc8256ebb170136f37f3261aeb 100644 GIT binary patch literal 4607 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 O**R@sreCgS7xjMuHus$X literal 5140 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 diff --git a/tests/ref/style/construct.png b/tests/ref/style/construct.png index b5b3a152fe2ba7385f4d683314fd3e518cc542ab..3e37eb5a8bb2f8f5136913c3d69ac6eef2d8d67d 100644 GIT binary patch literal 2029 zcmY*Zc{J3I7N1pLgqbuVOJ;ud@f(B2NI#l1cEf}j+i&nV5g8)M*q5d8ImnPATaC@ zzXG6#ac(0b7X*U5q?%gk9UL6&@9*#J?fv@oYj=0|=g*%zJ3Bvq{Mg>!{{H>@*4EbM z=H}P0UpF>3*4EZmS65e7R+g8SzkKgw$5?C9v=a=Gp8?X9h?xyu$UEiKK>%{dFyrlzLsd1_;0W9Ez*hr_u)WmaEb zUsqRGTU%RGQ&U}CU0GRKT3T9CQu6%y^P-}n!otFWf`TVcp5*7}KYsi;FE1}QH}}z_ zM>#n;+1c4ySy`ExnfLGC&&bF~Pfur6>!hWnrKYCds?bhJN%1e&PEJlvN=iyhOuT#d z?wvb#Zr{EgA0HnV7Z)2F%gon|iHV7hj%KsjQBhHmk&zJ*5m&P{!o$PE!ou7#38A5( zuJ_M|goFeK2L}ZO1qKGPSge460H-8XKR-WTU*GH3uX}rYdwF@;vT;l%)6>(_-QArQ zs_f?G=IZKd8HBxZ<;rE2(&Yd}7Z(@mEk!3MCyK8EgTXMpE^p#3Z|o&!Z*OnJl(Vz5 zv$3(Uwzj6z>H2OmR#sM)mX=g1)!f{iLZO(Nni?A$8yOiH8X6iH7@VU?>g(&1$>fU{ zFA~heNhFevj*hmrwyL3+mX_AJbLVhm(cdqfA`*!j8X5!wLFs~sy1KfWni?LDS5Z;H z;c&7aBqJk(!C=G{1*N5>#bgDfq@*MzB_$*z z#KpzM#Kc5JMMXqJP$(2a6hNE;goTAqoH!vQBqS&(C?FufCj>u!{5U^9KOY|-zz^f) z*-!1otDYobF29tv4h8zL}X2Se*YG%xm$5=`Jrvo&7SkEpIc+jYFij1 zA^@^91Q|zyVGw9#n5YQmXyyp{x(onW)KQ2h(O4ViSyyiUqcX!z(G`H3nvN}b1D=)8 zW>mT!I2n*Klz${)*9d(7Ag{5@E`3I|hUd_PSO(B%xRDS%@m7N(z)hCA$sNl(P0kws zmadt6m-WGQO+;lfpqQ4a&{Mgz#%@gfrdoOsq5{c}m89 zk|FU#zJ=?}E{eZ>5=?5DzeVqo-z1Yf?L3ZS^cH(QT*wK;Y^t@B3}4W0vQv3tI{=5v z1o_rM8}M|$e)+I!15yn-IlUwnu`_WDR2_D&s!>^r6v&iDs5c-JQJQbfq&(6VobnJW zqL@u0B>XY%*%A=P&vhO|CS$*NPz;*I2~sKUnz}8Iv-KsER3*^nGA^?Y_Q57AzqKsE zyX3YCH*LGVB{{^8(o08m6UzWnU^ar%G4lcLtM#_M)QIDp z=8)$DZD>nkT@qx&H>9KtXs|=SJ6?~gu%LAcW{vhQZtw2p#+%&KYmQ^+dY%#SVn`R8 z#r=*BlPQejB+*74hEU+^iRNoG$1`d^ri*V@of8Ojc;YY$Jpc;w;Jn>&t}6z(h-ij< zt3B~SLU{h3FsIkyr{bg)>}3o<76sTNWK>XX)V_YREZwKlyW2vDqoPQs?= z-t^^00a;(AED?!j2(SV#6IF=*i)Lr{bJL(|z=3Bk0l#`4yIwBgtQ7h1(xJ_dnED7r zbAZCbq5gLZ5x@*)^Z$2q5cmN@Qp!dY@JxrhkW+`DR@Lovb3%r(p2E6ocn|Wknb}*oa@qd Q=06Y6NY9L1rt1{>Z^-|982|tP delta 926 zcmaFMznp!7czqrR0}wo^Hi~0lV9xV&aSW-r_4a12M{uCb@sEcC3!hyPU&JBW(h}yz z7Bo>n!Xs0KwY$qnV3CRj*Gt#d)J(6$n4_C{L`W$!u|L;flc0Yc~WA{ex`9#C6MH*d;>NUDP8F+{(3NYb7O<{rz zTh#YX5Lp;GbFKVWo$GgHYqve^Ye<_G^&pGq$D@*R_p&EN?heM<7F#4GxZi$eQ_N~!5#gBd{78XlcJMocm$|+ST}S-o zs+uQE&|4KOu)~#S`>(f|MXy8|%PpSoDO=dz+{7TlzIlPH>g8W9KXy+0{A1%$B?gJA z8q0~Y*VUQSqHizT7}0WbQng`z zAIvh*p8mc^bTd1H>1xT(=OtJ#2ql~N>F2lnY})+%af{o$=A}*-l3E^QvoHvkwZGwz zFy16{Q!wD{;^Y+f1NSPpw|n&&h*Z>C7gsD-&@w=JxSU z{oEBxTsG>Mybxu~;Cx`SNb& zym!CIGZbjl1g+=Tp)K&rX0G@NVPn%(Z9+~yYLhdK>sN)d@Z>+KxxHigXQtYVbKVF2 zop|Hi;_V$weLy=3YF>TIik<)JO(=iNL;0BJBK42;_bTa_DVWtgi#eE`;@Z&r{CrH0 z#HAEhhrE>QZ>~O^x_FaPhN+W6%Tk9KG9sDTmLE)+=IvB_9(Z&AQnwQuB6OamEZZu} zdS5A|YqIjW1)cRfbu^j!HpSf(Y`E*VsYa;b*qkOmQzre*x8F8@|680IDg0rQvDi1~ zzn%r61)jPSMIpaw^W^%J=M`+-;K_!qU~xTo1-EH_N`CPnSWWbH9E}1v m(Zl;n0V1w}Kr}^s_CJP-pk2ocCLOK=WkgR`KbLh*2~7YKwv;3Q diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ index 9a74d2bfb..81af710c8 100644 --- a/tests/typ/layout/pagebreak.typ +++ b/tests/typ/layout/pagebreak.typ @@ -25,8 +25,8 @@ D #set page(width: 80pt, height: 30pt) -Fi[#set page(width: 80pt);rst] -[#set page(width: 70pt); Second] +[#set page(width: 80pt); First] +#pagebreak() #pagebreak() #pagebreak() Fourth diff --git a/tests/typ/style/construct.typ b/tests/typ/style/construct.typ index 02f1a6aac..ab53d40fd 100644 --- a/tests/typ/style/construct.typ +++ b/tests/typ/style/construct.typ @@ -8,3 +8,10 @@ [First], list([A], [B]) ) + +--- +// Ensure that constructor styles win, but not over outer styles. +#set par(align: center) +#par(align: right)[ + A #rect(width: 2cm, fill: conifer, padding: 4pt)[B] +]