diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index bcc5bf6dd..daa7b8a1d 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -32,7 +32,7 @@ use typst::diag::SourceResult; use typst::frame::Frame; use typst::geom::*; use typst::model::{ - capability, Content, Node, SequenceNode, Show, Style, StyleChain, StyleVecBuilder, + capability, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder, StyledNode, }; use typst::World; @@ -87,7 +87,7 @@ impl LayoutBlock for Content { regions: &Regions, styles: StyleChain, ) -> SourceResult> { - if !self.has::() || !styles.applicable(self) { + if !styles.applicable(self) { if let Some(node) = self.to::() { let barrier = Style::Barrier(self.id()); let styles = barrier.chain(&styles); @@ -126,7 +126,7 @@ impl LayoutInline for Content { assert!(regions.backlog.is_empty()); assert!(regions.last.is_none()); - if !self.has::() || !styles.applicable(self) { + if !styles.applicable(self) { if let Some(node) = self.to::() { let barrier = Style::Barrier(self.id()); let styles = barrier.chain(&styles); @@ -312,16 +312,17 @@ impl<'a> Builder<'a> { content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { - if content.is::() { - if let Some(realized) = styles.apply(self.world, content)? { - let stored = self.scratch.content.alloc(realized); - return self.accept(stored, styles); - } - } else if let Some(styled) = content.downcast::() { + if let Some(styled) = content.downcast::() { return self.styled(styled, styles); - } else if let Some(seq) = content.downcast::() { + } + + if let Some(seq) = content.downcast::() { return self.sequence(seq, styles); - } else if content.has::() && self.show(content, styles)? { + } + + if let Some(realized) = styles.show(self.world, content)? { + let stored = self.scratch.content.alloc(realized); + self.accept(stored, styles)?; return Ok(()); } @@ -361,17 +362,6 @@ impl<'a> Builder<'a> { Ok(()) } - fn show(&mut self, content: &Content, styles: StyleChain<'a>) -> SourceResult { - let Some(realized) = styles.apply(self.world, content)? else { - return Ok(false); - }; - - let stored = self.scratch.content.alloc(realized); - self.accept(stored, styles)?; - - Ok(true) - } - fn styled( &mut self, styled: &'a StyledNode, diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 7772c0af8..fb53b52de 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -28,15 +28,16 @@ impl MathNode { } impl Show for MathNode { - fn unguard_parts(&self, _: RecipeId) -> Content { - self.clone().pack() - } - fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { let mut map = StyleMap::new(); map.set_family(FontFamily::new("NewComputerModernMath"), styles); - let mut realized = self.clone().pack().styled_with_map(map); + let mut realized = self + .clone() + .pack() + .guard(RecipeId::Base(NodeId::of::())) + .styled_with_map(map); + if self.display { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index b7095aeef..c6bbe676b 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -16,8 +16,8 @@ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, - Content, Dict, Finalize, Fold, Func, Node, RecipeId, Resolve, Show, Smart, Str, - StyleChain, StyleMap, StyleVec, Value, Vm, + Content, Dict, Finalize, Fold, Func, Node, NodeId, RecipeId, Resolve, Show, Smart, + Str, StyleChain, StyleMap, StyleVec, Value, Vm, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs index e069e3a9e..d99e2db8e 100644 --- a/library/src/structure/heading.rs +++ b/library/src/structure/heading.rs @@ -34,10 +34,6 @@ impl HeadingNode { } impl Show for HeadingNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self { body: self.body.unguard(id), ..*self }.pack() - } - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(BlockNode(self.body.clone()).pack()) } diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index 8de22f64f..aa8442324 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -6,7 +6,7 @@ use crate::prelude::*; use crate::text::{ParNode, SpaceNode, TextNode}; /// An unordered (bulleted) or ordered (numbered) list. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Hash)] pub struct ListNode { /// If true, the items are separated by leading instead of list spacing. pub tight: bool, @@ -20,7 +20,7 @@ pub type EnumNode = ListNode; /// A description list. pub type DescNode = ListNode; -#[node(Show, LayoutBlock)] +#[node(LayoutBlock)] impl ListNode { /// How the list is labelled. #[property(referenced)] @@ -77,20 +77,6 @@ impl ListNode { } } -impl Show for ListNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self { - items: self.items.map(|item| item.unguard(id)), - ..*self - } - .pack() - } - - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.clone().pack()) - } -} - impl LayoutBlock for ListNode { fn layout_block( &self, @@ -178,17 +164,6 @@ impl ListItem { } } - fn unguard(&self, sel: RecipeId) -> Self { - match self { - Self::List(body) => Self::List(Box::new(body.unguard(sel))), - Self::Enum(number, body) => Self::Enum(*number, Box::new(body.unguard(sel))), - Self::Desc(item) => Self::Desc(Box::new(DescItem { - term: item.term.unguard(sel), - body: item.body.unguard(sel), - })), - } - } - /// Encode the item into a value. fn encode(&self) -> Value { match self { diff --git a/library/src/structure/reference.rs b/library/src/structure/reference.rs index 7004f49ea..4f6727078 100644 --- a/library/src/structure/reference.rs +++ b/library/src/structure/reference.rs @@ -20,10 +20,6 @@ impl RefNode { } impl Show for RefNode { - fn unguard_parts(&self, _: RecipeId) -> Content { - Self(self.0.clone()).pack() - } - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(TextNode::packed(format_eco!("@{}", self.0))) } diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs index 72ea3da55..eaadc3a1f 100644 --- a/library/src/structure/table.rs +++ b/library/src/structure/table.rs @@ -2,7 +2,7 @@ use crate::layout::{GridNode, TrackSizing, TrackSizings}; use crate::prelude::*; /// A table of items. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Hash)] pub struct TableNode { /// Defines sizing for content rows and columns. pub tracks: Axes>, @@ -12,7 +12,7 @@ pub struct TableNode { pub cells: Vec, } -#[node(Show, LayoutBlock)] +#[node(LayoutBlock)] impl TableNode { /// How to fill the cells. #[property(referenced)] @@ -50,21 +50,6 @@ impl TableNode { } } -impl Show for TableNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self { - tracks: self.tracks.clone(), - gutter: self.gutter.clone(), - cells: self.cells.iter().map(|cell| cell.unguard(id)).collect(), - } - .pack() - } - - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.clone().pack()) - } -} - impl LayoutBlock for TableNode { fn layout_block( &self, diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index bc7a312d9..7db7fa1bc 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -47,10 +47,6 @@ impl DecoNode { } impl Show for DecoNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self(self.0.unguard(id)).pack() - } - fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { Ok(self.0.clone().styled( TextNode::DECO, diff --git a/library/src/text/link.rs b/library/src/text/link.rs index acd37df67..45e7c7ec5 100644 --- a/library/src/text/link.rs +++ b/library/src/text/link.rs @@ -50,14 +50,6 @@ impl LinkNode { } impl Show for LinkNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self { - dest: self.dest.clone(), - body: self.body.unguard(id), - } - .pack() - } - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(self.body.clone()) } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 6cf1e1c53..d36f8db5c 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -528,10 +528,6 @@ impl StrongNode { } impl Show for StrongNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self(self.0.unguard(id)).pack() - } - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) } @@ -556,10 +552,6 @@ impl EmphNode { } impl Show for EmphNode { - fn unguard_parts(&self, id: RecipeId) -> Content { - Self(self.0.unguard(id)).pack() - } - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) } diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index d7dae244c..f13fd3947 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -43,10 +43,6 @@ impl RawNode { } impl Show for RawNode { - fn unguard_parts(&self, _: RecipeId) -> Content { - Self { text: self.text.clone(), ..*self }.pack() - } - fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index a91285bf4..32f110c66 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -43,10 +43,6 @@ impl ShiftNode { } impl Show for ShiftNode { - fn unguard_parts(&self, _: RecipeId) -> Content { - Self(self.0.clone()).pack() - } - fn show( &self, world: Tracked, diff --git a/src/model/content.rs b/src/model/content.rs index 3cae99b2d..15ea33cbf 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -20,7 +20,7 @@ use crate::World; /// - anything written between square brackets in Typst /// - any constructor function #[derive(Clone, Hash)] -pub struct Content(Arc); +pub struct Content(Arc, Vec); impl Content { /// Create empty content. @@ -112,16 +112,16 @@ impl Content { } /// Style this content with a style entry. - pub fn styled_with_entry(mut self, entry: Style) -> Self { + pub fn styled_with_entry(mut self, style: Style) -> Self { if let Some(styled) = self.try_downcast_mut::() { - styled.map.apply(entry); + styled.map.apply(style); self } else if let Some(styled) = self.downcast::() { let mut map = styled.map.clone(); - map.apply(entry); + map.apply(style); StyledNode { sub: styled.sub.clone(), map }.pack() } else { - StyledNode { sub: self, map: entry.into() }.pack() + StyledNode { sub: self, map: style.into() }.pack() } } @@ -139,9 +139,20 @@ impl Content { StyledNode { sub: self, map: styles }.pack() } - /// Reenable a specific show rule recipe. - pub fn unguard(&self, id: RecipeId) -> Self { - self.clone().styled_with_entry(Style::Unguard(id)) + /// Disable a show rule recipe. + pub fn guard(mut self, id: RecipeId) -> Self { + self.1.push(id); + self + } + + /// Whether no show rule was executed for this node so far. + pub fn pristine(&self) -> bool { + self.1.is_empty() + } + + /// Check whether a show rule recipe is disabled. + pub fn guarded(&self, id: RecipeId) -> bool { + self.1.contains(&id) } } @@ -241,7 +252,7 @@ pub trait Node: 'static { where Self: Debug + Hash + Sync + Send + Sized + 'static, { - Content(Arc::new(self)) + Content(Arc::new(self), vec![]) } /// Construct a node from the arguments. diff --git a/src/model/styles.rs b/src/model/styles.rs index ff9905f65..ced11e6ac 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -31,13 +31,6 @@ impl StyleMap { self.0.is_empty() } - /// Create a style map from a single property-value pair. - pub fn with<'a, K: Key<'a>>(key: K, value: K::Value) -> Self { - let mut styles = Self::new(); - styles.set(key, value); - styles - } - /// Set an inner value for a style property. /// /// If the property needs folding and the value is already contained in the @@ -75,12 +68,12 @@ impl StyleMap { } } - /// Set an outer style property. + /// Set an outer style. /// /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with /// only a entry. - pub fn apply(&mut self, entry: Style) { - self.0.insert(0, entry); + pub fn apply(&mut self, style: Style) { + self.0.insert(0, style); } /// Apply styles from `tail` in-place. The resulting style map is equivalent @@ -135,10 +128,6 @@ pub enum Style { Recipe(Recipe), /// A barrier for scoped styles. Barrier(NodeId), - /// Guards against recursive show rules. - Guard(RecipeId), - /// Allows recursive show rules again. - Unguard(RecipeId), } impl Style { @@ -190,8 +179,6 @@ impl Debug for Style { Self::Property(property) => property.fmt(f)?, Self::Recipe(recipe) => recipe.fmt(f)?, Self::Barrier(id) => write!(f, "Barrier for {id:?}")?, - Self::Guard(sel) => write!(f, "Guard against {sel:?}")?, - Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?, } f.write_str("]") } @@ -338,14 +325,10 @@ impl Debug for KeyId { } } -/// A node that can be realized given some styles. +/// A built-in show rule for a node. #[capability] pub trait Show: 'static + Sync + Send { - /// Unguard nested content against a specific recipe. - fn unguard_parts(&self, id: RecipeId) -> Content; - - /// The base recipe for this node that is executed if there is no - /// user-defined show rule. + /// Execute the base recipe for this node. fn show( &self, world: Tracked, @@ -356,9 +339,9 @@ pub trait Show: 'static + Sync + Send { /// Post-process a node after it was realized. #[capability] pub trait Finalize: 'static + Sync + Send { - /// Finalize this node given the realization of a base or user recipe. Use - /// this for effects that should work even in the face of a user-defined - /// show rule, for example the linking behaviour of a link node. + /// Finalize the fully realized form of the node. Use this for effects that + /// should work even in the face of a user-defined show rule, for example + /// the linking behaviour of a link node. fn finalize( &self, world: Tracked, @@ -400,7 +383,7 @@ impl Recipe { } self.transform.apply(world, self.span, || { - Value::Content(target.to::().unwrap().unguard_parts(sel)) + Value::Content(target.clone().guard(sel)) })? } @@ -420,7 +403,7 @@ impl Recipe { } let transformed = self.transform.apply(world, self.span, || { - Value::Content(make(mat.as_str().into())) + Value::Content(make(mat.as_str().into()).guard(sel)) })?; result.push(transformed); @@ -441,7 +424,7 @@ impl Recipe { None => return Ok(None), }; - Ok(Some(content.styled_with_entry(Style::Guard(sel)))) + Ok(Some(content)) } /// Whether this recipe is for the given node. @@ -566,87 +549,41 @@ impl<'a> StyleChain<'a> { K::get(self, self.values(key)) } - /// Whether the style chain has a matching recipe for the content. - pub fn applicable(self, target: &Content) -> bool { - // Find out how many recipes there any and whether any of them match. - let mut n = 0; - let mut any = true; - for recipe in self.entries().filter_map(Style::recipe) { - n += 1; - any |= recipe.applicable(target); - } - - // Find an applicable recipe. - if any { - for recipe in self.entries().filter_map(Style::recipe) { - if recipe.applicable(target) { - let sel = RecipeId::Nth(n); - if !self.guarded(sel) { - return true; - } - } - n -= 1; - } - } - - false - } - /// Apply show recipes in this style chain to a target. - pub fn apply( + pub fn show( self, world: Tracked, target: &Content, ) -> SourceResult> { - // Find out how many recipes there any and whether any of them match. - let mut n = 0; - let mut any = true; - for recipe in self.entries().filter_map(Style::recipe) { - n += 1; - any |= recipe.applicable(target); - } + // Find out how many recipes there are. + let mut n = self.entries().filter_map(Style::recipe).count(); // Find an applicable recipe. let mut realized = None; - let mut guarded = false; - if any { - for recipe in self.entries().filter_map(Style::recipe) { - if recipe.applicable(target) { - let sel = RecipeId::Nth(n); - if self.guarded(sel) { - guarded = true; - } else if let Some(content) = recipe.apply(world, sel, target)? { - realized = Some(content); - break; - } + for recipe in self.entries().filter_map(Style::recipe) { + let sel = RecipeId::Nth(n); + if recipe.applicable(target) && !target.guarded(sel) { + if let Some(content) = recipe.apply(world, sel, target)? { + realized = Some(content); + break; } - n -= 1; + } + n -= 1; + } + + // Realize if there was no matching recipe. + let base = RecipeId::Base(target.id()); + if realized.is_none() && !target.guarded(base) { + if let Some(showable) = target.to::() { + realized = Some(showable.show(world, self)?); } } - if let Some(showable) = target.to::() { - // Realize if there was no matching recipe. - if realized.is_none() { - let sel = RecipeId::Base(target.id()); - if self.guarded(sel) { - guarded = true; - } else { - let content = showable - .unguard_parts(sel) - .to::() - .unwrap() - .show(world, self)?; - - realized = Some(content.styled_with_entry(Style::Guard(sel))); - } - } - - // Finalize only if guarding didn't stop any recipe. - if !guarded { - if let Some(node) = target.to::() { - if let Some(content) = realized { - realized = Some(node.finalize(world, self, content)?); - } + // Finalize only if this is the first application for this node. + if let Some(node) = target.to::() { + if target.pristine() { + if let Some(content) = realized { + realized = Some(node.finalize(world, self, content)?); } } } @@ -654,14 +591,17 @@ impl<'a> StyleChain<'a> { Ok(realized) } - /// Whether the recipe identified by the selector is guarded. - fn guarded(self, sel: RecipeId) -> bool { - for entry in self.entries() { - match *entry { - Style::Guard(s) if s == sel => return true, - Style::Unguard(s) if s == sel => return false, - _ => {} + /// Whether the style chain has a matching recipe for the content. + pub fn applicable(self, target: &Content) -> bool { + // Find out how many recipes there are. + let mut n = self.entries().filter_map(Style::recipe).count(); + + // Find out whether any recipe matches and is unguarded. + for recipe in self.entries().filter_map(Style::recipe) { + if recipe.applicable(target) && !target.guarded(RecipeId::Nth(n)) { + return true; } + n -= 1; } false @@ -689,7 +629,6 @@ impl<'a> StyleChain<'a> { entries: self.entries(), key: PhantomData, barriers: 0, - guarded: false, } } @@ -727,7 +666,6 @@ struct Values<'a, K> { entries: Entries<'a>, key: PhantomData, barriers: usize, - guarded: bool, } impl<'a, K: Key<'a>> Iterator for Values<'a, K> { @@ -738,13 +676,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { match entry { Style::Property(property) => { if let Some(value) = property.downcast::() { - if !property.scoped() - || if self.guarded { - self.barriers == 1 - } else { - self.barriers <= 1 - } - { + if !property.scoped() || self.barriers <= 1 { return Some(value); } } @@ -752,9 +684,6 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> { Style::Barrier(id) => { self.barriers += (*id == K::node()) as usize; } - Style::Guard(RecipeId::Base(id)) => { - self.guarded |= *id == K::node(); - } _ => {} } } diff --git a/tests/ref/style/show-node.png b/tests/ref/style/show-node.png index 63be751ae..1ea33e4fb 100644 Binary files a/tests/ref/style/show-node.png and b/tests/ref/style/show-node.png differ diff --git a/tests/ref/style/show-text.png b/tests/ref/style/show-text.png index 1d343345d..d0ed2f927 100644 Binary files a/tests/ref/style/show-text.png and b/tests/ref/style/show-text.png differ diff --git a/tests/ref/text/shifts.png b/tests/ref/text/shifts.png index c1e9dec6b..7e8d99a9d 100644 Binary files a/tests/ref/text/shifts.png and b/tests/ref/text/shifts.png differ diff --git a/tests/typ/style/show-text.typ b/tests/typ/style/show-text.typ index 138b9b02e..124d2ed2d 100644 --- a/tests/typ/style/show-text.typ +++ b/tests/typ/style/show-text.typ @@ -13,17 +13,6 @@ Die Zeitung Der Spiegel existiert. TeX, LaTeX, LuaTeX and LuaLaTeX! ---- -// Test out-of-order guarding. -#show "Good": [Typst!] -#show "Typst": [Fun!] -#show "Fun": [Good!] -#show enum: [] - -Good \ -Fun \ -Typst \ - --- // Test that replacements happen exactly once. #show "A": [BB] diff --git a/tests/typ/text/shifts.typ b/tests/typ/text/shifts.typ index b70425c4e..01a83f6e3 100644 --- a/tests/typ/text/shifts.typ +++ b/tests/typ/text/shifts.typ @@ -1,19 +1,19 @@ // Test sub- and superscipt shifts. --- -#table(columns: 3, - [Typo.], [Fallb.], [Synth], - [x#super[1]], [x#super[5n]], [x#super[2 #square(width: 6pt)]], - [x#sub[1]], [x#sub[5n]], [x#sub[2 #square(width: 6pt)]], +#table( + columns: 3, + [Typo.], [Fallb.], [Synth], + [x#super[1]], [x#super[5n]], [x#super[2 #square(width: 6pt)]], + [x#sub[1]], [x#sub[5n]], [x#sub[2 #square(width: 6pt)]], ) --- #set super(typographic: false, baseline: -0.25em, size: 0.7em) -n#super[1], n#sub[2], ... n#super[N] +n#super[1], n#sub[2], ... n#super[N] --- #set underline(stroke: 0.5pt, offset: 0.15em) - #underline[The claim#super[\[4\]]] has been disputed. \ The claim#super[#underline[\[4\]]] has been disputed. \ It really has been#super(box(text(baseline: 0pt, underline[\[4\]]))) \