summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-02-13 19:35:38 +0100
committerGitHub <noreply@github.com>2024-02-13 18:35:38 +0000
commit1f68e157259f1eb55b0eb22f06bb1b589d398971 (patch)
treeb58a6478b0a349cf6f8ef44cb5d953231ec1535e
parent40099e32e1e840a02dacf8f5ddfdfd95fcb7d6c5 (diff)
Minor realization improvements (#3408)
-rw-r--r--crates/typst-macros/src/elem.rs4
-rw-r--r--crates/typst/src/foundations/content.rs48
-rw-r--r--crates/typst/src/foundations/element.rs39
-rw-r--r--crates/typst/src/introspection/metadata.rs5
-rw-r--r--crates/typst/src/introspection/mod.rs3
-rw-r--r--crates/typst/src/layout/columns.rs3
-rw-r--r--crates/typst/src/layout/flow.rs12
-rw-r--r--crates/typst/src/layout/inline/mod.rs18
-rw-r--r--crates/typst/src/layout/place.rs3
-rw-r--r--crates/typst/src/layout/spacing.rs5
-rw-r--r--crates/typst/src/layout/stack.rs6
-rw-r--r--crates/typst/src/math/mod.rs16
-rw-r--r--crates/typst/src/model/document.rs12
-rw-r--r--crates/typst/src/realize/behaviour.rs (renamed from crates/typst/src/realize/behave.rs)41
-rw-r--r--crates/typst/src/realize/mod.rs387
-rw-r--r--crates/typst/src/realize/process.rs312
-rw-r--r--crates/typst/src/text/linebreak.rs3
-rw-r--r--crates/typst/src/text/shift.rs6
-rw-r--r--crates/typst/src/text/space.rs6
19 files changed, 475 insertions, 454 deletions
diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs
index eab359d6..81548dd7 100644
--- a/crates/typst-macros/src/elem.rs
+++ b/crates/typst-macros/src/elem.rs
@@ -319,9 +319,9 @@ fn create_struct(element: &Elem) -> TokenStream {
/// Create a field declaration for the struct.
fn create_field(field: &Field) -> TokenStream {
- let Field { ident, ty, .. } = field;
+ let Field { vis, ident, ty, .. } = field;
if field.required {
- quote! { #ident: #ty }
+ quote! { #vis #ident: #ty }
} else {
quote! { #ident: ::std::option::Option<#ty> }
}
diff --git a/crates/typst/src/foundations/content.rs b/crates/typst/src/foundations/content.rs
index 8c093764..86e75eb5 100644
--- a/crates/typst/src/foundations/content.rs
+++ b/crates/typst/src/foundations/content.rs
@@ -14,13 +14,13 @@ use smallvec::smallvec;
use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, func, scope, ty, Behave, Behaviour, Dict, Element, Fields, IntoValue, Label,
- NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
- Value,
+ elem, func, scope, ty, Dict, Element, Fields, IntoValue, Label, NativeElement,
+ Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Value,
};
use crate::introspection::{Location, Meta, MetaElem};
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, StrongElem};
+use crate::realize::{Behave, Behaviour};
use crate::syntax::Span;
use crate::text::UnderlineElem;
use crate::util::{fat, BitSet};
@@ -330,34 +330,17 @@ impl Content {
sequence.children.is_empty()
}
- /// Whether the content is a sequence.
- pub fn is_sequence(&self) -> bool {
- self.is::<SequenceElem>()
- }
-
- /// Access the children if this is a sequence.
- pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Prehashed<Content>>> {
- let sequence = self.to_packed::<SequenceElem>()?;
- Some(sequence.children.iter())
- }
-
/// Also auto expands sequence of sequences into flat sequence
pub fn sequence_recursive_for_each<'a>(&'a self, f: &mut impl FnMut(&'a Self)) {
- if let Some(children) = self.to_sequence() {
- children.for_each(|c| c.sequence_recursive_for_each(f));
+ if let Some(sequence) = self.to_packed::<SequenceElem>() {
+ for child in &sequence.children {
+ child.sequence_recursive_for_each(f);
+ }
} else {
f(self);
}
}
- /// Access the child and styles.
- pub fn to_styled(&self) -> Option<(&Content, &Styles)> {
- let styled = self.to_packed::<StyledElem>()?;
- let child = styled.child();
- let styles = styled.styles();
- Some((child, styles))
- }
-
/// Style this content with a recipe, eagerly applying it if possible.
pub fn styled_with_recipe(
self,
@@ -886,11 +869,12 @@ impl<T: NativeElement + Debug> Debug for Packed<T> {
}
}
-/// Defines the element for sequences.
+/// A sequence of content.
#[elem(Debug, Repr, PartialEq)]
-struct SequenceElem {
+pub struct SequenceElem {
+ /// The elements.
#[required]
- children: Vec<Prehashed<Content>>,
+ pub children: Vec<Prehashed<Content>>,
}
impl Debug for SequenceElem {
@@ -937,13 +921,15 @@ impl Repr for SequenceElem {
}
}
-/// Defines the `ElemFunc` for styled elements.
+/// Content alongside styles.
#[elem(Debug, Repr, PartialEq)]
-struct StyledElem {
+pub struct StyledElem {
+ /// The content.
#[required]
- child: Prehashed<Content>,
+ pub child: Prehashed<Content>,
+ /// The styles.
#[required]
- styles: Styles,
+ pub styles: Styles,
}
impl Debug for StyledElem {
diff --git a/crates/typst/src/foundations/element.rs b/crates/typst/src/foundations/element.rs
index b59a16cb..324eceef 100644
--- a/crates/typst/src/foundations/element.rs
+++ b/crates/typst/src/foundations/element.rs
@@ -299,42 +299,3 @@ pub trait ShowSet {
/// that should work even in the face of a user-defined show rule.
fn show_set(&self, styles: StyleChain) -> Styles;
}
-
-/// How the element interacts with other elements.
-pub trait Behave {
- /// The element's interaction behaviour.
- fn behaviour(&self) -> Behaviour;
-
- /// Whether this weak element is larger than a previous one and thus picked
- /// as the maximum when the levels are the same.
- #[allow(unused_variables)]
- fn larger(&self, prev: &(&Content, StyleChain), styles: StyleChain) -> bool {
- false
- }
-}
-
-/// How an element interacts with other elements in a stream.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Behaviour {
- /// A weak element which only survives when a supportive element is before
- /// and after it. Furthermore, per consecutive run of weak elements, only
- /// one survives: The one with the lowest weakness level (or the larger one
- /// if there is a tie).
- Weak(usize),
- /// An element that enables adjacent weak elements to exist. The default.
- Supportive,
- /// An element that destroys adjacent weak elements.
- Destructive,
- /// An element that does not interact at all with other elements, having the
- /// same effect as if it didn't exist, but has a visual representation.
- Ignorant,
- /// An element that does not have a visual representation.
- Invisible,
-}
-
-impl Behaviour {
- /// Whether this of `Weak(_)` variant.
- pub fn is_weak(self) -> bool {
- matches!(self, Self::Weak(_))
- }
-}
diff --git a/crates/typst/src/introspection/metadata.rs b/crates/typst/src/introspection/metadata.rs
index d66441a2..d8fb1574 100644
--- a/crates/typst/src/introspection/metadata.rs
+++ b/crates/typst/src/introspection/metadata.rs
@@ -1,9 +1,8 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{
- elem, Behave, Behaviour, Content, Packed, Show, StyleChain, Value,
-};
+use crate::foundations::{elem, Content, Packed, Show, StyleChain, Value};
use crate::introspection::Locatable;
+use crate::realize::{Behave, Behaviour};
/// Exposes a value to the query system without producing visible content.
///
diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst/src/introspection/mod.rs
index 62512510..af5faf26 100644
--- a/crates/typst/src/introspection/mod.rs
+++ b/crates/typst/src/introspection/mod.rs
@@ -27,9 +27,10 @@ use smallvec::SmallVec;
use crate::foundations::Packed;
use crate::foundations::{
- category, elem, ty, Behave, Behaviour, Category, Content, Repr, Scope, Unlabellable,
+ category, elem, ty, Category, Content, Repr, Scope, Unlabellable,
};
use crate::model::Destination;
+use crate::realize::{Behave, Behaviour};
/// Interactions between document parts.
///
diff --git a/crates/typst/src/layout/columns.rs b/crates/typst/src/layout/columns.rs
index 6458acac..04dc9275 100644
--- a/crates/typst/src/layout/columns.rs
+++ b/crates/typst/src/layout/columns.rs
@@ -2,11 +2,12 @@ use std::num::NonZeroUsize;
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Behave, Behaviour, Content, Packed, StyleChain};
+use crate::foundations::{elem, Content, Packed, StyleChain};
use crate::layout::{
Abs, Axes, Dir, Fragment, Frame, LayoutMultiple, Length, Point, Ratio, Regions, Rel,
Size,
};
+use crate::realize::{Behave, Behaviour};
use crate::text::TextElem;
use crate::util::Numeric;
diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs
index 747fb97c..4308182d 100644
--- a/crates/typst/src/layout/flow.rs
+++ b/crates/typst/src/layout/flow.rs
@@ -5,7 +5,7 @@ use comemo::Prehashed;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
+ elem, Content, NativeElement, Packed, Resolve, Smart, StyleChain, StyledElem,
};
use crate::introspection::{Meta, MetaElem};
use crate::layout::{
@@ -46,9 +46,9 @@ impl LayoutMultiple for Packed<FlowElem> {
for mut child in self.children().iter().map(|c| &**c) {
let outer = styles;
let mut styles = styles;
- if let Some((elem, map)) = child.to_styled() {
- child = elem;
- styles = outer.chain(map);
+ if let Some(styled) = child.to_packed::<StyledElem>() {
+ child = &styled.child;
+ styles = outer.chain(&styled.styles);
}
if child.is::<MetaElem>() {
@@ -344,8 +344,8 @@ impl<'a> FlowLayouter<'a> {
// How to align the block.
let align = if let Some(align) = child.to_packed::<AlignElem>() {
align.alignment(styles)
- } else if let Some((_, local)) = child.to_styled() {
- AlignElem::alignment_in(styles.chain(local))
+ } else if let Some(styled) = child.to_packed::<StyledElem>() {
+ AlignElem::alignment_in(styles.chain(&styled.styles))
} else {
AlignElem::alignment_in(styles)
}
diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs
index 43c2c509..3e3b7a76 100644
--- a/crates/typst/src/layout/inline/mod.rs
+++ b/crates/typst/src/layout/inline/mod.rs
@@ -13,7 +13,7 @@ use self::shaping::{
use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::eval::Tracer;
-use crate::foundations::{Content, Packed, Resolve, Smart, StyleChain};
+use crate::foundations::{Content, Packed, Resolve, Smart, StyleChain, StyledElem};
use crate::introspection::{Introspector, Locator, MetaElem};
use crate::layout::{
Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, HElem,
@@ -435,9 +435,9 @@ fn collect<'a>(
while let Some(mut child) = iter.next() {
let outer = styles;
let mut styles = *styles;
- if let Some((elem, local)) = child.to_styled() {
- child = elem;
- styles = outer.chain(local);
+ if let Some(styled) = child.to_packed::<StyledElem>() {
+ child = &styled.child;
+ styles = outer.chain(&styled.styles);
}
let segment = if child.is::<SpaceElem>() {
@@ -474,9 +474,9 @@ fn collect<'a>(
region,
SmartQuoteElem::alternative_in(styles),
);
- let peeked = iter.peek().and_then(|child| {
- let child = if let Some((child, _)) = child.to_styled() {
- child
+ let peeked = iter.peek().and_then(|&child| {
+ let child = if let Some(styled) = child.to_packed::<StyledElem>() {
+ &styled.child
} else {
child
};
@@ -761,8 +761,8 @@ fn shared_get<T: PartialEq>(
let value = getter(styles);
children
.iter()
- .filter_map(|child| child.to_styled())
- .all(|(_, local)| getter(styles.chain(local)) == value)
+ .filter_map(|child| child.to_packed::<StyledElem>())
+ .all(|styled| getter(styles.chain(&styled.styles)) == value)
.then_some(value)
}
diff --git a/crates/typst/src/layout/place.rs b/crates/typst/src/layout/place.rs
index 31876e43..b2906024 100644
--- a/crates/typst/src/layout/place.rs
+++ b/crates/typst/src/layout/place.rs
@@ -1,9 +1,10 @@
use crate::diag::{bail, At, Hint, SourceResult};
use crate::engine::Engine;
-use crate::foundations::{elem, Behave, Behaviour, Content, Packed, Smart, StyleChain};
+use crate::foundations::{elem, Content, Packed, Smart, StyleChain};
use crate::layout::{
Alignment, Axes, Em, Fragment, LayoutMultiple, Length, Regions, Rel, VAlignment,
};
+use crate::realize::{Behave, Behaviour};
/// Places content at an absolute position.
///
diff --git a/crates/typst/src/layout/spacing.rs b/crates/typst/src/layout/spacing.rs
index d6a1d592..7367c1aa 100644
--- a/crates/typst/src/layout/spacing.rs
+++ b/crates/typst/src/layout/spacing.rs
@@ -1,7 +1,6 @@
-use crate::foundations::{
- cast, elem, Behave, Behaviour, Content, Packed, Resolve, StyleChain,
-};
+use crate::foundations::{cast, elem, Content, Packed, Resolve, StyleChain};
use crate::layout::{Abs, Em, Fr, Length, Ratio, Rel};
+use crate::realize::{Behave, Behaviour};
use crate::util::Numeric;
/// Inserts horizontal spacing into a paragraph.
diff --git a/crates/typst/src/layout/stack.rs b/crates/typst/src/layout/stack.rs
index 863e6eab..e1c26625 100644
--- a/crates/typst/src/layout/stack.rs
+++ b/crates/typst/src/layout/stack.rs
@@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{cast, elem, Content, Packed, Resolve, StyleChain};
+use crate::foundations::{cast, elem, Content, Packed, Resolve, StyleChain, StyledElem};
use crate::layout::{
Abs, AlignElem, Axes, Axis, Dir, FixedAlignment, Fr, Fragment, Frame, LayoutMultiple,
Point, Regions, Size, Spacing,
@@ -209,8 +209,8 @@ impl<'a> StackLayouter<'a> {
// Block-axis alignment of the `AlignElement` is respected by stacks.
let align = if let Some(align) = block.to_packed::<AlignElem>() {
align.alignment(styles)
- } else if let Some((_, local)) = block.to_styled() {
- AlignElem::alignment_in(styles.chain(local))
+ } else if let Some(styled) = block.to_packed::<StyledElem>() {
+ AlignElem::alignment_in(styles.chain(&styled.styles))
} else {
AlignElem::alignment_in(styles)
}
diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs
index 63333d0f..c98cac7b 100644
--- a/crates/typst/src/math/mod.rs
+++ b/crates/typst/src/math/mod.rs
@@ -41,11 +41,13 @@ use self::row::*;
use self::spacing::*;
use crate::diag::SourceResult;
+use crate::foundations::SequenceElem;
+use crate::foundations::StyledElem;
use crate::foundations::{
category, Category, Content, Module, Resolve, Scope, StyleChain,
};
use crate::layout::{BoxElem, HElem, Spacing};
-use crate::realize::{realize, BehavedBuilder};
+use crate::realize::{process, BehavedBuilder};
use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// Typst has special [syntax]($syntax/#math) and library functions to typeset
@@ -230,11 +232,11 @@ impl LayoutMath for Content {
return elem.layout_math(ctx, styles);
}
- if let Some(realized) = realize(ctx.engine, self, styles)? {
+ if let Some(realized) = process(ctx.engine, self, styles)? {
return realized.layout_math(ctx, styles);
}
- if self.is_sequence() {
+ if self.is::<SequenceElem>() {
let mut bb = BehavedBuilder::new();
self.sequence_recursive_for_each(&mut |child: &Content| {
bb.push(child, StyleChain::default());
@@ -245,17 +247,17 @@ impl LayoutMath for Content {
return Ok(());
}
- if let Some((elem, local)) = self.to_styled() {
+ if let Some(styled) = self.to_packed::<StyledElem>() {
let outer = styles;
- let styles = outer.chain(local);
+ let styles = outer.chain(&styled.styles);
if TextElem::font_in(styles) != TextElem::font_in(outer) {
- let frame = ctx.layout_content(elem, styles)?;
+ let frame = ctx.layout_content(&styled.child, styles)?;
ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
return Ok(());
}
- elem.layout_math(ctx, styles)?;
+ styled.child.layout_math(ctx, styles)?;
return Ok(());
}
diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs
index 81086ded..edd5e6c9 100644
--- a/crates/typst/src/model/document.rs
+++ b/crates/typst/src/model/document.rs
@@ -4,7 +4,7 @@ use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain,
- Value,
+ StyledElem, Value,
};
use crate::introspection::{Introspector, ManualPageCounter};
use crate::layout::{LayoutRoot, Page, PageElem};
@@ -82,16 +82,16 @@ impl LayoutRoot for Packed<DocumentElem> {
while let Some(mut child) = iter.next() {
let outer = styles;
let mut styles = styles;
- if let Some((elem, local)) = child.to_styled() {
- styles = outer.chain(local);
- child = elem;
+ if let Some(styled) = child.to_packed::<StyledElem>() {
+ child = &styled.child;
+ styles = outer.chain(&styled.styles);
}
if let Some(page) = child.to_packed::<PageElem>() {
let extend_to = iter.peek().and_then(|&next| {
*next
- .to_styled()
- .map_or(next, |(elem, _)| elem)
+ .to_packed::<StyledElem>()
+ .map_or(next, |styled| &styled.child)
.to_packed::<PageElem>()?
.clear_to()?
});
diff --git a/crates/typst/src/realize/behave.rs b/crates/typst/src/realize/behaviour.rs
index 035f6644..747ce30c 100644
--- a/crates/typst/src/realize/behave.rs
+++ b/crates/typst/src/realize/behaviour.rs
@@ -1,8 +1,47 @@
//! Element interaction.
-use crate::foundations::{Behave, Behaviour, Content, StyleChain, Styles};
+use crate::foundations::{Content, StyleChain, Styles};
use crate::syntax::Span;
+/// How an element interacts with other elements in a stream.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Behaviour {
+ /// A weak element which only survives when a supportive element is before
+ /// and after it. Furthermore, per consecutive run of weak elements, only
+ /// one survives: The one with the lowest weakness level (or the larger one
+ /// if there is a tie).
+ Weak(usize),
+ /// An element that enables adjacent weak elements to exist. The default.
+ Supportive,
+ /// An element that destroys adjacent weak elements.
+ Destructive,
+ /// An element that does not interact at all with other elements, having the
+ /// same effect as if it didn't exist, but has a visual representation.
+ Ignorant,
+ /// An element that does not have a visual representation.
+ Invisible,
+}
+
+impl Behaviour {
+ /// Whether this of `Weak(_)` variant.
+ pub fn is_weak(self) -> bool {
+ matches!(self, Self::Weak(_))
+ }
+}
+
+/// How the element interacts with other elements.
+pub trait Behave {
+ /// The element's interaction behaviour.
+ fn behaviour(&self) -> Behaviour;
+
+ /// Whether this weak element is larger than a previous one and thus picked
+ /// as the maximum when the levels are the same.
+ #[allow(unused_variables)]
+ fn larger(&self, prev: &(&Content, StyleChain), styles: StyleChain) -> bool {
+ false
+ }
+}
+
/// Processes a sequence of content and resolves behaviour interactions between
/// them and separates local styles for each element from the shared trunk of
/// styles.
diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs
index a03a209e..e9eaae60 100644
--- a/crates/typst/src/realize/mod.rs
+++ b/crates/typst/src/realize/mod.rs
@@ -1,24 +1,23 @@
//! Realization of content.
mod arenas;
-mod behave;
+mod behaviour;
+mod process;
pub use self::arenas::Arenas;
-pub use self::behave::BehavedBuilder;
+pub use self::behaviour::{Behave, BehavedBuilder, Behaviour};
+pub use self::process::{process, processable};
use std::borrow::Cow;
-use std::cell::OnceCell;
-use std::mem;
-use smallvec::smallvec;
+use std::mem;
use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route};
use crate::foundations::{
- Content, NativeElement, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet,
- Style, StyleChain, Styles, Synthesize, Transformation,
+ Content, NativeElement, Packed, SequenceElem, StyleChain, StyledElem, Styles,
};
-use crate::introspection::{Locatable, Meta, MetaElem};
+use crate::introspection::MetaElem;
use crate::layout::{
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, HElem, LayoutMultiple,
LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem,
@@ -30,7 +29,6 @@ use crate::model::{
};
use crate::syntax::Span;
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
-use crate::util::{hash128, BitSet};
/// Realize into an element that is capable of root-level layout.
#[typst_macros::time(name = "realize root")]
@@ -43,8 +41,8 @@ pub fn realize_root<'a>(
let mut builder = Builder::new(engine, arenas, true);
builder.accept(content, styles)?;
builder.interrupt_page(Some(styles), true)?;
- let (pages, shared, span) = builder.doc.unwrap().pages.finish();
- Ok((Packed::new(DocumentElem::new(pages.to_vec())).spanned(span), shared))
+ let (doc, trunk) = builder.doc.unwrap().finish();
+ Ok((doc, trunk))
}
/// Realize into an element that is capable of block-level layout.
@@ -57,7 +55,7 @@ pub fn realize_block<'a>(
) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> {
// These elements implement `Layout` but still require a flow for
// proper layout.
- if content.can::<dyn LayoutMultiple>() && verdict(engine, content, styles).is_none() {
+ if content.can::<dyn LayoutMultiple>() && !processable(engine, content, styles) {
return Ok((Cow::Borrowed(content), styles));
}
@@ -65,298 +63,8 @@ pub fn realize_block<'a>(
builder.accept(content, styles)?;
builder.interrupt_par()?;
- let (children, shared, span) = builder.flow.0.finish();
- Ok((Cow::Owned(FlowElem::new(children).pack().spanned(span)), shared))
-}
-
-/// Apply the show rules in the given style chain to a target element.
-pub fn realize(
- engine: &mut Engine,
- target: &Content,
- styles: StyleChain,
-) -> SourceResult<Option<Content>> {
- let Some(Verdict { prepared, mut map, step }) = verdict(engine, target, styles)
- else {
- return Ok(None);
- };
-
- // Create a fresh copy that we can mutate.
- let mut target = target.clone();
-
- // If the element isn't yet prepared (we're seeing it for the first time),
- // prepare it.
- let mut meta = None;
- if !prepared {
- meta = prepare(engine, &mut target, &mut map, styles)?;
- }
-
- // Apply a step, if there is one.
- let mut output = match step {
- Some(step) => {
- // Errors in show rules don't terminate compilation immediately. We
- // just continue with empty content for them and show all errors
- // together, if they remain by the end of the introspection loop.
- //
- // This way, we can ignore errors that only occur in earlier
- // iterations and also show more useful errors at once.
- engine.delayed(|engine| show(engine, target, step, styles.chain(&map)))
- }
- None => target,
- };
-
- // If necessary, apply metadata generated in the preparation.
- if let Some(meta) = meta {
- output += meta.pack();
- }
-
- Ok(Some(output.styled_with_map(map)))
-}
-
-/// What to do with an element when encountering it during realization.
-struct Verdict<'a> {
- /// Whether the element is already prepated (i.e. things that should only
- /// happen once have happened).
- prepared: bool,
- /// A map of styles to apply to the element.
- map: Styles,
- /// An optional show rule transformation to apply to the element.
- step: Option<ShowStep<'a>>,
-}
-
-/// An optional show rule transformation to apply to the element.
-enum ShowStep<'a> {
- /// A user-defined transformational show rule.
- Recipe(&'a Recipe, RecipeIndex),
- /// The built-in show rule.
- Builtin,
-}
-
-/// Inspects a target element and the current styles and determines how to
-/// proceed with the styling.
-fn verdict<'a>(
- engine: &mut Engine,
- target: &'a Content,
- styles: StyleChain<'a>,
-) -> Option<Verdict<'a>> {
- let mut target = target;
- let mut map = Styles::new();
- let mut revoked = BitSet::new();
- let mut step = None;
- let mut slot;
-
- let depth = OnceCell::new();
- let prepared = target.is_prepared();
-
- // Do pre-synthesis on a cloned element to be able to match on synthesized
- // fields before real synthesis runs (during preparation). It's really
- // unfortunate that we have to do this, but otherwise
- // `show figure.where(kind: table)` won't work :(
- if !prepared && target.can::<dyn Synthesize>() {
- slot = target.clone();
- slot.with_mut::<dyn Synthesize>()
- .unwrap()
- .synthesize(engine, styles)
- .ok();
- target = &slot;
- }
-
- let mut r = 0;
- for entry in styles.entries() {
- let recipe = match entry {
- Style::Recipe(recipe) => recipe,
- Style::Property(_) => continue,
- Style::Revocation(index) => {
- revoked.insert(index.0);
- continue;
- }
- };
-
- // We're not interested in recipes that don't match.
- if !recipe.applicable(target, styles) {
- r += 1;
- continue;
- }
-
- if let Transformation::Style(transform) = &recipe.transform {
- // If this is a show-set for an unprepared element, we need to apply
- // it.
- if !prepared {
- map.apply(transform.clone());
- }
- } else if step.is_none() {
- // Lazily compute the total number of recipes in the style chain. We
- // need it to determine whether a particular show rule was already
- // applied to the `target` previously. For this purpose, show rules
- // are indexed from the top of the chain as the chain might grow to
- // the bottom.
- let depth =
- *depth.get_or_init(|| styles.entries().filter_map(Style::recipe).count());
- let index = RecipeIndex(depth - r);
-
- if !target.is_guarded(index) && !revoked.contains(index.0) {
- // If we find a matching, unguarded replacement show rule,
- // remember it, but still continue searching for potential
- // show-set styles that might change the verdict.
- step = Some(ShowStep::Recipe(recipe, index));
-
- // If we found a show rule and are already prepared, there is
- // nothing else to do, so we can just break.
- if prepared {
- break;
- }
- }
- }
-
- r += 1;
- }
-
- // If we found no user-defined rule, also consider the built-in show rule.
- if step.is_none() && target.can::<dyn Show>() {
- step = Some(ShowStep::Builtin);
- }
-
- // If there's no nothing to do, there is also no verdict.
- if step.is_none()
- && map.is_empty()
- && (prepared || {
- target.label().is_none()
- && !target.can::<dyn ShowSet>()
- && !target.can::<dyn Locatable>()
- && !target.can::<dyn Synthesize>()
- })
- {
- return None;
- }
-
- Some(Verdict { prepared, map, step })
-}
-
-/// This is only executed the first time an element is visited.
-fn prepare(
- engine: &mut Engine,
- target: &mut Content,
- map: &mut Styles,
- styles: StyleChain,
-) -> SourceResult<Option<Packed<MetaElem>>> {
- // Generate a location for the element, which uniquely identifies it in
- // the document. This has some overhead, so we only do it for elements
- // that are explicitly marked as locatable and labelled elements.
- if target.can::<dyn Locatable>() || target.label().is_some() {
- let location = engine.locator.locate(hash128(&target));
- target.set_location(location);
- }
-
- // Apply built-in show-set rules. User-defined show-set rules are already
- // considered in the map built while determining the verdict.
- if let Some(show_settable) = target.with::<dyn ShowSet>() {
- map.apply(show_settable.show_set(styles));
- }
-
- // If necessary, generated "synthesized" fields (which are derived from
- // other fields or queries). Do this after show-set so that show-set styles
- // are respected.
- if let Some(synthesizable) = target.with_mut::<dyn Synthesize>() {
- synthesizable.synthesize(engine, styles.chain(map))?;
- }
-
- // Copy style chain fields into the element itself, so that they are
- // available in rules.
- target.materialize(styles.chain(map));
-
- // Ensure that this preparation only runs once by marking the element as
- // prepared.
- target.mark_prepared();
-
- // Apply metadata be able to find the element in the frames.
- // Do this after synthesis, so that it includes the synthesized fields.
- if target.location().is_some() {
- // Add a style to the whole element's subtree identifying it as
- // belonging to the element.
- map.set(MetaElem::set_data(smallvec![Meta::Elem(target.clone())]));
-
- // Return an extra meta elem that will be attached so that the metadata
- // styles are not lost in case the element's show rule results in
- // nothing.
- return Ok(Some(Packed::new(MetaElem::new()).spanned(target.span())));
- }
-
- Ok(None)
-}
-
-/// Apply a step.
-fn show(
- engine: &mut Engine,
- target: Content,
- step: ShowStep,
- styles: StyleChain,
-) -> SourceResult<Content> {
- match step {
- // Apply a user-defined show rule.
- ShowStep::Recipe(recipe, guard) => match &recipe.selector {
- // If the selector is a regex, the `target` is guaranteed to be a
- // text element. This invokes special regex handling.
- Some(Selector::Regex(regex)) => {
- let text = target.into_packed::<TextElem>().unwrap();
- show_regex(engine, &text, regex, recipe, guard)
- }
-
- // Just apply the recipe.
- _ => recipe.apply(engine, target.guarded(guard)),
- },
-
- // If the verdict picks this step, the `target` is guaranteed to have a
- // built-in show rule.
- ShowStep::Builtin => target.with::<dyn Show>().unwrap().show(engine, styles),
- }
-}
-
-/// Apply a regex show rule recipe to a target.
-fn show_regex(
- engine: &mut Engine,
- elem: &Packed<TextElem>,
- regex: &Regex,
- recipe: &Recipe,
- index: RecipeIndex,
-) -> SourceResult<Content> {
- let make = |s: &str| {
- let mut fresh = elem.clone();
- fresh.push_text(s.into());
- fresh.pack()
- };
-
- let mut result = vec![];
- let mut cursor = 0;
-
- let text = elem.text();
-
- for m in regex.find_iter(elem.text()) {
- let start = m.start();
- if cursor < start {
- result.push(make(&text[cursor..start]));
- }
-
- let piece = make(m.as_str());
- let transformed = recipe.apply(engine, piece)?;
- result.push(transformed);
- cursor = m.end();
- }
-
- if cursor < text.len() {
- result.push(make(&text[cursor..]));
- }
-
- // In contrast to normal elements, which are guarded individually, for text
- // show rules, we fully revoke the rule. This means that we can replace text
- // with other text that rematches without running into infinite recursion
- // problems.
- //
- // We do _not_ do this for all content because revoking e.g. a list show
- // rule for all content resulting from that rule would be wrong: The list
- // might contain nested lists. Moreover, replacing a normal element with one
- // that rematches is bad practice: It can for instance also lead to
- // surprising query results, so it's better to let the user deal with it.
- // All these problems don't exist for text, so it's fine here.
- Ok(Content::sequence(result).styled(Style::Revocation(index)))
+ let (flow, trunk) = builder.flow.finish();
+ Ok((Cow::Owned(flow.pack()), trunk))
}
/// Builds a document or a flow element from content.
@@ -401,7 +109,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
.store(EquationElem::new(content.clone()).pack().spanned(content.span()));
}
- if let Some(realized) = realize(self.engine, content, styles)? {
+ if let Some(realized) = process(self.engine, content, styles)? {
self.engine.route.increase();
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
bail!(
@@ -414,12 +122,12 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
return result;
}
- if let Some((elem, local)) = content.to_styled() {
- return self.styled(elem, local, styles);
+ if let Some(styled) = content.to_packed::<StyledElem>() {
+ return self.styled(styled, styles);
}
- if let Some(children) = content.to_sequence() {
- for elem in children {
+ if let Some(sequence) = content.to_packed::<SequenceElem>() {
+ for elem in &sequence.children {
self.accept(elem, styles)?;
}
return Ok(());
@@ -472,15 +180,14 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
fn styled(
&mut self,
- elem: &'a Content,
- map: &'a Styles,
+ styled: &'a StyledElem,
styles: StyleChain<'a>,
) -> SourceResult<()> {
let stored = self.arenas.store(styles);
- let styles = stored.chain(map);
- self.interrupt_style(map, None)?;
- self.accept(elem, styles)?;
- self.interrupt_style(map, Some(styles))?;
+ let styles = stored.chain(&styled.styles);
+ self.interrupt_style(&styled.styles, None)?;
+ self.accept(&styled.child, styles)?;
+ self.interrupt_style(&styled.styles, Some(styles))?;
Ok(())
}
@@ -490,13 +197,15 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
outer: Option<StyleChain<'a>>,
) -> SourceResult<()> {
if let Some(Some(span)) = local.interruption::<DocumentElem>() {
- if self.doc.is_none() {
+ let Some(doc) = &self.doc else {
bail!(span, "document set rules are not allowed inside of containers");
- }
+ };
if outer.is_none()
- && (!self.flow.0.is_empty()
+ && (!doc.pages.is_empty()
+ || !self.flow.0.is_empty()
|| !self.par.0.is_empty()
- || !self.list.items.is_empty())
+ || !self.list.items.is_empty()
+ || !self.cites.items.is_empty())
{
bail!(span, "document set rules must appear before any content");
}
@@ -522,7 +231,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
if !self.cites.items.is_empty() {
let staged = mem::take(&mut self.cites.staged);
let (group, styles) = mem::take(&mut self.cites).finish();
- self.accept(self.arenas.store(group), styles)?;
+ self.accept(self.arenas.store(group.pack()), styles)?;
for (content, styles) in staged {
self.accept(content, styles)?;
}
@@ -547,7 +256,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.interrupt_list()?;
if !self.par.0.is_empty() {
let (par, styles) = mem::take(&mut self.par).finish();
- self.accept(self.arenas.store(par), styles)?;
+ self.accept(self.arenas.store(par.pack()), styles)?;
}
Ok(())
@@ -561,15 +270,15 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.interrupt_par()?;
let Some(doc) = &mut self.doc else { return Ok(()) };
if (doc.keep_next && styles.is_some()) || self.flow.0.has_strong_elements(last) {
- let (children, shared, span) = mem::take(&mut self.flow).0.finish();
- let styles = if shared == StyleChain::default() {
+ let (flow, trunk) = mem::take(&mut self.flow).finish();
+ let span = flow.span();
+ let styles = if trunk == StyleChain::default() {
styles.unwrap_or_default()
} else {
- shared
+ trunk
};
- let flow = FlowElem::new(children);
- let page = PageElem::new(flow.pack().spanned(span));
- self.accept(self.arenas.store(page.pack().spanned(span)), styles)?;
+ let page = PageElem::new(flow.pack()).pack().spanned(span);
+ self.accept(self.arenas.store(page), styles)?;
}
Ok(())
}
@@ -614,6 +323,11 @@ impl<'a> DocBuilder<'a> {
false
}
+
+ fn finish(self) -> (Packed<DocumentElem>, StyleChain<'a>) {
+ let (children, trunk, span) = self.pages.finish();
+ (Packed::new(DocumentElem::new(children)).spanned(span), trunk)
+ }
}
impl Default for DocBuilder<'_> {
@@ -688,6 +402,11 @@ impl<'a> FlowBuilder<'a> {
false
}
+
+ fn finish(self) -> (Packed<FlowElem>, StyleChain<'a>) {
+ let (children, trunk, span) = self.0.finish();
+ (Packed::new(FlowElem::new(children)).spanned(span), trunk)
+ }
}
/// Accepts paragraph content.
@@ -718,9 +437,9 @@ impl<'a> ParBuilder<'a> {
false
}
- fn finish(self) -> (Content, StyleChain<'a>) {
- let (children, shared, span) = self.0.finish();
- (ParElem::new(children).pack().spanned(span), shared)
+ fn finish(self) -> (Packed<ParElem>, StyleChain<'a>) {
+ let (children, trunk, span) = self.0.finish();
+ (Packed::new(ParElem::new(children)).spanned(span), trunk)
}
}
@@ -761,7 +480,7 @@ impl<'a> ListBuilder<'a> {
}
fn finish(self) -> (Content, StyleChain<'a>) {
- let (items, shared, span) = self.items.finish_iter();
+ let (items, trunk, span) = self.items.finish_iter();
let mut items = items.peekable();
let (first, _) = items.peek().unwrap();
let output = if first.is::<ListItem>() {
@@ -812,7 +531,7 @@ impl<'a> ListBuilder<'a> {
} else {
unreachable!()
};
- (output, shared)
+ (output, trunk)
}
}
@@ -858,8 +577,8 @@ impl<'a> CiteGroupBuilder<'a> {
false
}
- fn finish(self) -> (Content, StyleChain<'a>) {
+ fn finish(self) -> (Packed<CiteGroup>, StyleChain<'a>) {
let span = self.items.first().map(|cite| cite.span()).unwrap_or(Span::detached());
- (CiteGroup::new(self.items).pack().spanned(span), self.styles)
+ (Packed::new(CiteGroup::new(self.items)).spanned(span), self.styles)
}
}
diff --git a/crates/typst/src/realize/process.rs b/crates/typst/src/realize/process.rs
new file mode 100644
index 00000000..73d5cc4f
--- /dev/null
+++ b/crates/typst/src/realize/process.rs
@@ -0,0 +1,312 @@
+use std::cell::OnceCell;
+
+use smallvec::smallvec;
+
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{
+ Content, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet, Style,
+ StyleChain, Styles, Synthesize, Transformation,
+};
+use crate::introspection::{Locatable, Meta, MetaElem};
+use crate::text::TextElem;
+use crate::util::{hash128, BitSet};
+
+/// What to do with an element when encountering it during realization.
+struct Verdict<'a> {
+ /// Whether the element is already prepated (i.e. things that should only
+ /// happen once have happened).
+ prepared: bool,
+ /// A map of styles to apply to the element.
+ map: Styles,
+ /// An optional show rule transformation to apply to the element.
+ step: Option<ShowStep<'a>>,
+}
+
+/// An optional show rule transformation to apply to the element.
+enum ShowStep<'a> {
+ /// A user-defined transformational show rule.
+ Recipe(&'a Recipe, RecipeIndex),
+ /// The built-in show rule.
+ Builtin,
+}
+
+/// Whether the `target` element needs processing.
+pub fn processable<'a>(
+ engine: &mut Engine,
+ target: &'a Content,
+ styles: StyleChain<'a>,
+) -> bool {
+ verdict(engine, target, styles).is_some()
+}
+
+/// Processes the given `target` element when encountering it during realization.
+pub fn process(
+ engine: &mut Engine,
+ target: &Content,
+ styles: StyleChain,
+) -> SourceResult<Option<Content>> {
+ let Some(Verdict { prepared, mut map, step }) = verdict(engine, target, styles)
+ else {
+ return Ok(None);
+ };
+
+ // Create a fresh copy that we can mutate.
+ let mut target = target.clone();
+
+ // If the element isn't yet prepared (we're seeing it for the first time),
+ // prepare it.
+ let mut meta = None;
+ if !prepared {
+ meta = prepare(engine, &mut target, &mut map, styles)?;
+ }
+
+ // Apply a step, if there is one.
+ let mut output = match step {
+ Some(step) => {
+ // Errors in show rules don't terminate compilation immediately. We
+ // just continue with empty content for them and show all errors
+ // together, if they remain by the end of the introspection loop.
+ //
+ // This way, we can ignore errors that only occur in earlier
+ // iterations and also show more useful errors at once.
+ engine.delayed(|engine| show(engine, target, step, styles.chain(&map)))
+ }
+ None => target,
+ };
+
+ // If necessary, apply metadata generated in the preparation.
+ if let Some(meta) = meta {
+ output += meta.pack();
+ }
+
+ Ok(Some(output.styled_with_map(map)))
+}
+
+/// Inspects a target element and the current styles and determines how to
+/// proceed with the styling.
+fn verdict<'a>(
+ engine: &mut Engine,
+ target: &'a Content,
+ styles: StyleChain<'a>,
+) -> Option<Verdict<'a>> {
+ let mut target = target;
+ let mut map = Styles::new();
+ let mut revoked = BitSet::new();
+ let mut step = None;
+ let mut slot;
+
+ let depth = OnceCell::new();
+ let prepared = target.is_prepared();
+
+ // Do pre-synthesis on a cloned element to be able to match on synthesized
+ // fields before real synthesis runs (during preparation). It's really
+ // unfortunate that we have to do this, but otherwise
+ // `show figure.where(kind: table)` won't work :(
+ if !prepared && target.can::<dyn Synthesize>() {
+ slot = target.clone();
+ slot.with_mut::<dyn Synthesize>()
+ .unwrap()
+ .synthesize(engine, styles)
+ .ok();
+ target = &slot;
+ }
+
+ let mut r = 0;
+ for entry in styles.entries() {
+ let recipe = match entry {
+ Style::Recipe(recipe) => recipe,
+ Style::Property(_) => continue,
+ Style::Revocation(index) => {
+ revoked.insert(index.0);
+ continue;
+ }
+ };
+
+ // We're not interested in recipes that don't match.
+ if !recipe.applicable(target, styles) {
+ r += 1;
+ continue;
+ }
+
+ if let Transformation::Style(transform) = &recipe.transform {
+ // If this is a show-set for an unprepared element, we need to apply
+ // it.
+ if !prepared {
+ map.apply(transform.clone());
+ }
+ } else if step.is_none() {
+ // Lazily compute the total number of recipes in the style chain. We
+ // need it to determine whether a particular show rule was already
+ // applied to the `target` previously. For this purpose, show rules
+ // are indexed from the top of the chain as the chain might grow to
+ // the bottom.
+ let depth =
+ *depth.get_or_init(|| styles.entries().filter_map(Style::recipe).count());
+ let index = RecipeIndex(depth - r);
+
+ if !target.is_guarded(index) && !revoked.contains(index.0) {
+ // If we find a matching, unguarded replacement show rule,
+ // remember it, but still continue searching for potential
+ // show-set styles that might change the verdict.
+ step = Some(ShowStep::Recipe(recipe, index));
+
+ // If we found a show rule and are already prepared, there is
+ // nothing else to do, so we can just break.
+ if prepared {
+ break;
+ }
+ }
+ }
+
+ r += 1;
+ }
+
+ // If we found no user-defined rule, also consider the built-in show rule.
+ if step.is_none() && target.can::<dyn Show>() {
+ step = Some(ShowStep::Builtin);
+ }
+
+ // If there's no nothing to do, there is also no verdict.
+ if step.is_none()
+ && map.is_empty()
+ && (prepared || {
+ target.label().is_none()
+ && !target.can::<dyn ShowSet>()
+ && !target.can::<dyn Locatable>()
+ && !target.can::<dyn Synthesize>()
+ })
+ {
+ return None;
+ }
+
+ Some(Verdict { prepared, map, step })
+}
+
+/// This is only executed the first time an element is visited.
+fn prepare(
+ engine: &mut Engine,
+ target: &mut Content,
+ map: &mut Styles,
+ styles: StyleChain,
+) -> SourceResult<Option<Packed<MetaElem>>> {
+ // Generate a location for the element, which uniquely identifies it in
+ // the document. This has some overhead, so we only do it for elements
+ // that are explicitly marked as locatable and labelled elements.
+ if target.can::<dyn Locatable>() || target.label().is_some() {
+ let location = engine.locator.locate(hash128(&target));
+ target.set_location(location);
+ }
+
+ // Apply built-in show-set rules. User-defined show-set rules are already
+ // considered in the map built while determining the verdict.
+ if let Some(show_settable) = target.with::<dyn ShowSet>() {
+ map.apply(show_settable.show_set(styles));
+ }
+
+ // If necessary, generated "synthesized" fields (which are derived from
+ // other fields or queries). Do this after show-set so that show-set styles
+ // are respected.
+ if let Some(synthesizable) = target.with_mut::<dyn Synthesize>() {
+ synthesizable.synthesize(engine, styles.chain(map))?;
+ }
+
+ // Copy style chain fields into the element itself, so that they are
+ // available in rules.
+ target.materialize(styles.chain(map));
+
+ // Ensure that this preparation only runs once by marking the element as
+ // prepared.
+ target.mark_prepared();
+
+ // Apply metadata be able to find the element in the frames.
+ // Do this after synthesis, so that it includes the synthesized fields.
+ if target.location().is_some() {
+ // Add a style to the whole element's subtree identifying it as
+ // belonging to the element.
+ map.set(MetaElem::set_data(smallvec![Meta::Elem(target.clone())]));
+
+ // Return an extra meta elem that will be attached so that the metadata
+ // styles are not lost in case the element's show rule results in
+ // nothing.
+ return Ok(Some(Packed::new(MetaElem::new()).spanned(target.span())));
+ }
+
+ Ok(None)
+}
+
+/// Apply a step.
+fn show(
+ engine: &mut Engine,
+ target: Content,
+ step: ShowStep,
+ styles: StyleChain,
+) -> SourceResult<Content> {
+ match step {
+ // Apply a user-defined show rule.
+ ShowStep::Recipe(recipe, guard) => match &recipe.selector {
+ // If the selector is a regex, the `target` is guaranteed to be a
+ // text element. This invokes special regex handling.
+ Some(Selector::Regex(regex)) => {
+ let text = target.into_packed::<TextElem>().unwrap();
+ show_regex(engine, &text, regex, recipe, guard)
+ }
+
+ // Just apply the recipe.
+ _ => recipe.apply(engine, target.guarded(guard)),
+ },
+
+ // If the verdict picks this step, the `target` is guaranteed to have a
+ // built-in show rule.
+ ShowStep::Builtin => target.with::<dyn Show>().unwrap().show(engine, styles),
+ }
+}
+
+/// Apply a regex show rule recipe to a target.
+fn show_regex(
+ engine: &mut Engine,
+ elem: &Packed<TextElem>,
+ regex: &Regex,
+ recipe: &Recipe,
+ index: RecipeIndex,
+) -> SourceResult<Content> {
+ let make = |s: &str| {
+ let mut fresh = elem.clone();
+ fresh.push_text(s.into());
+ fresh.pack()
+ };
+
+ let mut result = vec![];
+ let mut cursor = 0;
+
+ let text = elem.text();
+
+ for m in regex.find_iter(elem.text()) {
+ let start = m.start();
+ if cursor < start {
+ result.push(make(&text[cursor..start]));
+ }
+
+ let piece = make(m.as_str());
+ let transformed = recipe.apply(engine, piece)?;
+ result.push(transformed);
+ cursor = m.end();
+ }
+
+ if cursor < text.len() {
+ result.push(make(&text[cursor..]));
+ }
+
+ // In contrast to normal elements, which are guarded individually, for text
+ // show rules, we fully revoke the rule. This means that we can replace text
+ // with other text that rematches without running into infinite recursion
+ // problems.
+ //
+ // We do _not_ do this for all content because revoking e.g. a list show
+ // rule for all content resulting from that rule would be wrong: The list
+ // might contain nested lists. Moreover, replacing a normal element with one
+ // that rematches is bad practice: It can for instance also lead to
+ // surprising query results, so it's better to let the user deal with it.
+ // All these problems don't exist for text, so it's fine here.
+ Ok(Content::sequence(result).styled(Style::Revocation(index)))
+}
diff --git a/crates/typst/src/text/linebreak.rs b/crates/typst/src/text/linebreak.rs
index 2bc315a3..23349e25 100644
--- a/crates/typst/src/text/linebreak.rs
+++ b/crates/typst/src/text/linebreak.rs
@@ -1,4 +1,5 @@
-use crate::foundations::{elem, Behave, Behaviour, Packed};
+use crate::foundations::{elem, Packed};
+use crate::realize::{Behave, Behaviour};
/// Inserts a line break.
///
diff --git a/crates/typst/src/text/shift.rs b/crates/typst/src/text/shift.rs
index dcff1c48..2dbfd2b6 100644
--- a/crates/typst/src/text/shift.rs
+++ b/crates/typst/src/text/shift.rs
@@ -2,7 +2,7 @@ use ecow::EcoString;
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Show, StyleChain};
+use crate::foundations::{elem, Content, Packed, SequenceElem, Show, StyleChain};
use crate::layout::{Em, Length};
use crate::text::{variant, SpaceElem, TextElem, TextSize};
use crate::World;
@@ -134,9 +134,9 @@ fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
Some(' '.into())
} else if let Some(elem) = content.to_packed::<TextElem>() {
convert_script(elem.text(), sub)
- } else if let Some(children) = content.to_sequence() {
+ } else if let Some(sequence) = content.to_packed::<SequenceElem>() {
let mut full = EcoString::new();
- for item in children {
+ for item in &sequence.children {
match search_text(item, sub) {
Some(text) => full.push_str(&text),
None => return None,
diff --git a/crates/typst/src/text/space.rs b/crates/typst/src/text/space.rs
index e584d948..114a2e80 100644
--- a/crates/typst/src/text/space.rs
+++ b/crates/typst/src/text/space.rs
@@ -1,8 +1,8 @@
-use crate::foundations::{
- elem, Behave, Behaviour, Packed, PlainText, Repr, Unlabellable,
-};
use ecow::EcoString;
+use crate::foundations::{elem, Packed, PlainText, Repr, Unlabellable};
+use crate::realize::{Behave, Behaviour};
+
/// A text space.
#[elem(Behave, Unlabellable, PlainText, Repr)]
pub struct SpaceElem {}