Minor realization improvements (#3408)

This commit is contained in:
Laurenz 2024-02-13 19:35:38 +01:00 committed by GitHub
parent 40099e32e1
commit 1f68e15725
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 475 additions and 454 deletions

View File

@ -319,9 +319,9 @@ fn create_struct(element: &Elem) -> TokenStream {
/// Create a field declaration for the struct. /// Create a field declaration for the struct.
fn create_field(field: &Field) -> TokenStream { fn create_field(field: &Field) -> TokenStream {
let Field { ident, ty, .. } = field; let Field { vis, ident, ty, .. } = field;
if field.required { if field.required {
quote! { #ident: #ty } quote! { #vis #ident: #ty }
} else { } else {
quote! { #ident: ::std::option::Option<#ty> } quote! { #ident: ::std::option::Option<#ty> }
} }

View File

@ -14,13 +14,13 @@ use smallvec::smallvec;
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
elem, func, scope, ty, Behave, Behaviour, Dict, Element, Fields, IntoValue, Label, elem, func, scope, ty, Dict, Element, Fields, IntoValue, Label, NativeElement,
NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Value,
Value,
}; };
use crate::introspection::{Location, Meta, MetaElem}; use crate::introspection::{Location, Meta, MetaElem};
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, StrongElem}; use crate::model::{Destination, EmphElem, StrongElem};
use crate::realize::{Behave, Behaviour};
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::UnderlineElem; use crate::text::UnderlineElem;
use crate::util::{fat, BitSet}; use crate::util::{fat, BitSet};
@ -330,34 +330,17 @@ impl Content {
sequence.children.is_empty() 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 /// Also auto expands sequence of sequences into flat sequence
pub fn sequence_recursive_for_each<'a>(&'a self, f: &mut impl FnMut(&'a Self)) { pub fn sequence_recursive_for_each<'a>(&'a self, f: &mut impl FnMut(&'a Self)) {
if let Some(children) = self.to_sequence() { if let Some(sequence) = self.to_packed::<SequenceElem>() {
children.for_each(|c| c.sequence_recursive_for_each(f)); for child in &sequence.children {
child.sequence_recursive_for_each(f);
}
} else { } else {
f(self); 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. /// Style this content with a recipe, eagerly applying it if possible.
pub fn styled_with_recipe( pub fn styled_with_recipe(
self, 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)] #[elem(Debug, Repr, PartialEq)]
struct SequenceElem { pub struct SequenceElem {
/// The elements.
#[required] #[required]
children: Vec<Prehashed<Content>>, pub children: Vec<Prehashed<Content>>,
} }
impl Debug for SequenceElem { 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)] #[elem(Debug, Repr, PartialEq)]
struct StyledElem { pub struct StyledElem {
/// The content.
#[required] #[required]
child: Prehashed<Content>, pub child: Prehashed<Content>,
/// The styles.
#[required] #[required]
styles: Styles, pub styles: Styles,
} }
impl Debug for StyledElem { impl Debug for StyledElem {

View File

@ -299,42 +299,3 @@ pub trait ShowSet {
/// that should work even in the face of a user-defined show rule. /// that should work even in the face of a user-defined show rule.
fn show_set(&self, styles: StyleChain) -> Styles; 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(_))
}
}

View File

@ -1,9 +1,8 @@
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{elem, Content, Packed, Show, StyleChain, Value};
elem, Behave, Behaviour, Content, Packed, Show, StyleChain, Value,
};
use crate::introspection::Locatable; use crate::introspection::Locatable;
use crate::realize::{Behave, Behaviour};
/// Exposes a value to the query system without producing visible content. /// Exposes a value to the query system without producing visible content.
/// ///

View File

@ -27,9 +27,10 @@ use smallvec::SmallVec;
use crate::foundations::Packed; use crate::foundations::Packed;
use crate::foundations::{ 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::model::Destination;
use crate::realize::{Behave, Behaviour};
/// Interactions between document parts. /// Interactions between document parts.
/// ///

View File

@ -2,11 +2,12 @@ use std::num::NonZeroUsize;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{elem, Behave, Behaviour, Content, Packed, StyleChain}; use crate::foundations::{elem, Content, Packed, StyleChain};
use crate::layout::{ use crate::layout::{
Abs, Axes, Dir, Fragment, Frame, LayoutMultiple, Length, Point, Ratio, Regions, Rel, Abs, Axes, Dir, Fragment, Frame, LayoutMultiple, Length, Point, Ratio, Regions, Rel,
Size, Size,
}; };
use crate::realize::{Behave, Behaviour};
use crate::text::TextElem; use crate::text::TextElem;
use crate::util::Numeric; use crate::util::Numeric;

View File

@ -5,7 +5,7 @@ use comemo::Prehashed;
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ 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::introspection::{Meta, MetaElem};
use crate::layout::{ use crate::layout::{
@ -46,9 +46,9 @@ impl LayoutMultiple for Packed<FlowElem> {
for mut child in self.children().iter().map(|c| &**c) { for mut child in self.children().iter().map(|c| &**c) {
let outer = styles; let outer = styles;
let mut styles = styles; let mut styles = styles;
if let Some((elem, map)) = child.to_styled() { if let Some(styled) = child.to_packed::<StyledElem>() {
child = elem; child = &styled.child;
styles = outer.chain(map); styles = outer.chain(&styled.styles);
} }
if child.is::<MetaElem>() { if child.is::<MetaElem>() {
@ -344,8 +344,8 @@ impl<'a> FlowLayouter<'a> {
// How to align the block. // How to align the block.
let align = if let Some(align) = child.to_packed::<AlignElem>() { let align = if let Some(align) = child.to_packed::<AlignElem>() {
align.alignment(styles) align.alignment(styles)
} else if let Some((_, local)) = child.to_styled() { } else if let Some(styled) = child.to_packed::<StyledElem>() {
AlignElem::alignment_in(styles.chain(local)) AlignElem::alignment_in(styles.chain(&styled.styles))
} else { } else {
AlignElem::alignment_in(styles) AlignElem::alignment_in(styles)
} }

View File

@ -13,7 +13,7 @@ use self::shaping::{
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route};
use crate::eval::Tracer; 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::introspection::{Introspector, Locator, MetaElem};
use crate::layout::{ use crate::layout::{
Abs, AlignElem, Axes, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, HElem, 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() { while let Some(mut child) = iter.next() {
let outer = styles; let outer = styles;
let mut styles = *styles; let mut styles = *styles;
if let Some((elem, local)) = child.to_styled() { if let Some(styled) = child.to_packed::<StyledElem>() {
child = elem; child = &styled.child;
styles = outer.chain(local); styles = outer.chain(&styled.styles);
} }
let segment = if child.is::<SpaceElem>() { let segment = if child.is::<SpaceElem>() {
@ -474,9 +474,9 @@ fn collect<'a>(
region, region,
SmartQuoteElem::alternative_in(styles), SmartQuoteElem::alternative_in(styles),
); );
let peeked = iter.peek().and_then(|child| { let peeked = iter.peek().and_then(|&child| {
let child = if let Some((child, _)) = child.to_styled() { let child = if let Some(styled) = child.to_packed::<StyledElem>() {
child &styled.child
} else { } else {
child child
}; };
@ -761,8 +761,8 @@ fn shared_get<T: PartialEq>(
let value = getter(styles); let value = getter(styles);
children children
.iter() .iter()
.filter_map(|child| child.to_styled()) .filter_map(|child| child.to_packed::<StyledElem>())
.all(|(_, local)| getter(styles.chain(local)) == value) .all(|styled| getter(styles.chain(&styled.styles)) == value)
.then_some(value) .then_some(value)
} }

View File

@ -1,9 +1,10 @@
use crate::diag::{bail, At, Hint, SourceResult}; use crate::diag::{bail, At, Hint, SourceResult};
use crate::engine::Engine; 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::{ use crate::layout::{
Alignment, Axes, Em, Fragment, LayoutMultiple, Length, Regions, Rel, VAlignment, Alignment, Axes, Em, Fragment, LayoutMultiple, Length, Regions, Rel, VAlignment,
}; };
use crate::realize::{Behave, Behaviour};
/// Places content at an absolute position. /// Places content at an absolute position.
/// ///

View File

@ -1,7 +1,6 @@
use crate::foundations::{ use crate::foundations::{cast, elem, Content, Packed, Resolve, StyleChain};
cast, elem, Behave, Behaviour, Content, Packed, Resolve, StyleChain,
};
use crate::layout::{Abs, Em, Fr, Length, Ratio, Rel}; use crate::layout::{Abs, Em, Fr, Length, Ratio, Rel};
use crate::realize::{Behave, Behaviour};
use crate::util::Numeric; use crate::util::Numeric;
/// Inserts horizontal spacing into a paragraph. /// Inserts horizontal spacing into a paragraph.

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::engine::Engine; 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::{ use crate::layout::{
Abs, AlignElem, Axes, Axis, Dir, FixedAlignment, Fr, Fragment, Frame, LayoutMultiple, Abs, AlignElem, Axes, Axis, Dir, FixedAlignment, Fr, Fragment, Frame, LayoutMultiple,
Point, Regions, Size, Spacing, Point, Regions, Size, Spacing,
@ -209,8 +209,8 @@ impl<'a> StackLayouter<'a> {
// Block-axis alignment of the `AlignElement` is respected by stacks. // Block-axis alignment of the `AlignElement` is respected by stacks.
let align = if let Some(align) = block.to_packed::<AlignElem>() { let align = if let Some(align) = block.to_packed::<AlignElem>() {
align.alignment(styles) align.alignment(styles)
} else if let Some((_, local)) = block.to_styled() { } else if let Some(styled) = block.to_packed::<StyledElem>() {
AlignElem::alignment_in(styles.chain(local)) AlignElem::alignment_in(styles.chain(&styled.styles))
} else { } else {
AlignElem::alignment_in(styles) AlignElem::alignment_in(styles)
} }

View File

@ -41,11 +41,13 @@ use self::row::*;
use self::spacing::*; use self::spacing::*;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::foundations::SequenceElem;
use crate::foundations::StyledElem;
use crate::foundations::{ use crate::foundations::{
category, Category, Content, Module, Resolve, Scope, StyleChain, category, Category, Content, Module, Resolve, Scope, StyleChain,
}; };
use crate::layout::{BoxElem, HElem, Spacing}; use crate::layout::{BoxElem, HElem, Spacing};
use crate::realize::{realize, BehavedBuilder}; use crate::realize::{process, BehavedBuilder};
use crate::text::{LinebreakElem, SpaceElem, TextElem}; use crate::text::{LinebreakElem, SpaceElem, TextElem};
/// Typst has special [syntax]($syntax/#math) and library functions to typeset /// 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); 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); return realized.layout_math(ctx, styles);
} }
if self.is_sequence() { if self.is::<SequenceElem>() {
let mut bb = BehavedBuilder::new(); let mut bb = BehavedBuilder::new();
self.sequence_recursive_for_each(&mut |child: &Content| { self.sequence_recursive_for_each(&mut |child: &Content| {
bb.push(child, StyleChain::default()); bb.push(child, StyleChain::default());
@ -245,17 +247,17 @@ impl LayoutMath for Content {
return Ok(()); return Ok(());
} }
if let Some((elem, local)) = self.to_styled() { if let Some(styled) = self.to_packed::<StyledElem>() {
let outer = styles; let outer = styles;
let styles = outer.chain(local); let styles = outer.chain(&styled.styles);
if TextElem::font_in(styles) != TextElem::font_in(outer) { 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)); ctx.push(FrameFragment::new(ctx, styles, frame).with_spaced(true));
return Ok(()); return Ok(());
} }
elem.layout_math(ctx, styles)?; styled.child.layout_math(ctx, styles)?;
return Ok(()); return Ok(());
} }

View File

@ -4,7 +4,7 @@ use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain, cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain,
Value, StyledElem, Value,
}; };
use crate::introspection::{Introspector, ManualPageCounter}; use crate::introspection::{Introspector, ManualPageCounter};
use crate::layout::{LayoutRoot, Page, PageElem}; use crate::layout::{LayoutRoot, Page, PageElem};
@ -82,16 +82,16 @@ impl LayoutRoot for Packed<DocumentElem> {
while let Some(mut child) = iter.next() { while let Some(mut child) = iter.next() {
let outer = styles; let outer = styles;
let mut styles = styles; let mut styles = styles;
if let Some((elem, local)) = child.to_styled() { if let Some(styled) = child.to_packed::<StyledElem>() {
styles = outer.chain(local); child = &styled.child;
child = elem; styles = outer.chain(&styled.styles);
} }
if let Some(page) = child.to_packed::<PageElem>() { if let Some(page) = child.to_packed::<PageElem>() {
let extend_to = iter.peek().and_then(|&next| { let extend_to = iter.peek().and_then(|&next| {
*next *next
.to_styled() .to_packed::<StyledElem>()
.map_or(next, |(elem, _)| elem) .map_or(next, |styled| &styled.child)
.to_packed::<PageElem>()? .to_packed::<PageElem>()?
.clear_to()? .clear_to()?
}); });

View File

@ -1,8 +1,47 @@
//! Element interaction. //! Element interaction.
use crate::foundations::{Behave, Behaviour, Content, StyleChain, Styles}; use crate::foundations::{Content, StyleChain, Styles};
use crate::syntax::Span; 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 /// Processes a sequence of content and resolves behaviour interactions between
/// them and separates local styles for each element from the shared trunk of /// them and separates local styles for each element from the shared trunk of
/// styles. /// styles.

View File

@ -1,24 +1,23 @@
//! Realization of content. //! Realization of content.
mod arenas; mod arenas;
mod behave; mod behaviour;
mod process;
pub use self::arenas::Arenas; 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::borrow::Cow;
use std::cell::OnceCell;
use std::mem;
use smallvec::smallvec; use std::mem;
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route};
use crate::foundations::{ use crate::foundations::{
Content, NativeElement, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet, Content, NativeElement, Packed, SequenceElem, StyleChain, StyledElem, Styles,
Style, StyleChain, Styles, Synthesize, Transformation,
}; };
use crate::introspection::{Locatable, Meta, MetaElem}; use crate::introspection::MetaElem;
use crate::layout::{ use crate::layout::{
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, HElem, LayoutMultiple, AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, HElem, LayoutMultiple,
LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem, LayoutSingle, PageElem, PagebreakElem, Parity, PlaceElem, VElem,
@ -30,7 +29,6 @@ use crate::model::{
}; };
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem}; use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
use crate::util::{hash128, BitSet};
/// Realize into an element that is capable of root-level layout. /// Realize into an element that is capable of root-level layout.
#[typst_macros::time(name = "realize root")] #[typst_macros::time(name = "realize root")]
@ -43,8 +41,8 @@ pub fn realize_root<'a>(
let mut builder = Builder::new(engine, arenas, true); let mut builder = Builder::new(engine, arenas, true);
builder.accept(content, styles)?; builder.accept(content, styles)?;
builder.interrupt_page(Some(styles), true)?; builder.interrupt_page(Some(styles), true)?;
let (pages, shared, span) = builder.doc.unwrap().pages.finish(); let (doc, trunk) = builder.doc.unwrap().finish();
Ok((Packed::new(DocumentElem::new(pages.to_vec())).spanned(span), shared)) Ok((doc, trunk))
} }
/// Realize into an element that is capable of block-level layout. /// 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>)> { ) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> {
// These elements implement `Layout` but still require a flow for // These elements implement `Layout` but still require a flow for
// proper layout. // 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)); return Ok((Cow::Borrowed(content), styles));
} }
@ -65,298 +63,8 @@ pub fn realize_block<'a>(
builder.accept(content, styles)?; builder.accept(content, styles)?;
builder.interrupt_par()?; builder.interrupt_par()?;
let (children, shared, span) = builder.flow.0.finish(); let (flow, trunk) = builder.flow.finish();
Ok((Cow::Owned(FlowElem::new(children).pack().spanned(span)), shared)) Ok((Cow::Owned(flow.pack()), trunk))
}
/// 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)))
} }
/// Builds a document or a flow element from content. /// 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())); .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(); self.engine.route.increase();
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) { if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
bail!( bail!(
@ -414,12 +122,12 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
return result; return result;
} }
if let Some((elem, local)) = content.to_styled() { if let Some(styled) = content.to_packed::<StyledElem>() {
return self.styled(elem, local, styles); return self.styled(styled, styles);
} }
if let Some(children) = content.to_sequence() { if let Some(sequence) = content.to_packed::<SequenceElem>() {
for elem in children { for elem in &sequence.children {
self.accept(elem, styles)?; self.accept(elem, styles)?;
} }
return Ok(()); return Ok(());
@ -472,15 +180,14 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
fn styled( fn styled(
&mut self, &mut self,
elem: &'a Content, styled: &'a StyledElem,
map: &'a Styles,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
let stored = self.arenas.store(styles); let stored = self.arenas.store(styles);
let styles = stored.chain(map); let styles = stored.chain(&styled.styles);
self.interrupt_style(map, None)?; self.interrupt_style(&styled.styles, None)?;
self.accept(elem, styles)?; self.accept(&styled.child, styles)?;
self.interrupt_style(map, Some(styles))?; self.interrupt_style(&styled.styles, Some(styles))?;
Ok(()) Ok(())
} }
@ -490,13 +197,15 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
outer: Option<StyleChain<'a>>, outer: Option<StyleChain<'a>>,
) -> SourceResult<()> { ) -> SourceResult<()> {
if let Some(Some(span)) = local.interruption::<DocumentElem>() { 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"); bail!(span, "document set rules are not allowed inside of containers");
} };
if outer.is_none() if outer.is_none()
&& (!self.flow.0.is_empty() && (!doc.pages.is_empty()
|| !self.flow.0.is_empty()
|| !self.par.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"); 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() { if !self.cites.items.is_empty() {
let staged = mem::take(&mut self.cites.staged); let staged = mem::take(&mut self.cites.staged);
let (group, styles) = mem::take(&mut self.cites).finish(); 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 { for (content, styles) in staged {
self.accept(content, styles)?; self.accept(content, styles)?;
} }
@ -547,7 +256,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.interrupt_list()?; self.interrupt_list()?;
if !self.par.0.is_empty() { if !self.par.0.is_empty() {
let (par, styles) = mem::take(&mut self.par).finish(); 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(()) Ok(())
@ -561,15 +270,15 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.interrupt_par()?; self.interrupt_par()?;
let Some(doc) = &mut self.doc else { return Ok(()) }; let Some(doc) = &mut self.doc else { return Ok(()) };
if (doc.keep_next && styles.is_some()) || self.flow.0.has_strong_elements(last) { 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 (flow, trunk) = mem::take(&mut self.flow).finish();
let styles = if shared == StyleChain::default() { let span = flow.span();
let styles = if trunk == StyleChain::default() {
styles.unwrap_or_default() styles.unwrap_or_default()
} else { } else {
shared trunk
}; };
let flow = FlowElem::new(children); let page = PageElem::new(flow.pack()).pack().spanned(span);
let page = PageElem::new(flow.pack().spanned(span)); self.accept(self.arenas.store(page), styles)?;
self.accept(self.arenas.store(page.pack().spanned(span)), styles)?;
} }
Ok(()) Ok(())
} }
@ -614,6 +323,11 @@ impl<'a> DocBuilder<'a> {
false 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<'_> { impl Default for DocBuilder<'_> {
@ -688,6 +402,11 @@ impl<'a> FlowBuilder<'a> {
false 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. /// Accepts paragraph content.
@ -718,9 +437,9 @@ impl<'a> ParBuilder<'a> {
false false
} }
fn finish(self) -> (Content, StyleChain<'a>) { fn finish(self) -> (Packed<ParElem>, StyleChain<'a>) {
let (children, shared, span) = self.0.finish(); let (children, trunk, span) = self.0.finish();
(ParElem::new(children).pack().spanned(span), shared) (Packed::new(ParElem::new(children)).spanned(span), trunk)
} }
} }
@ -761,7 +480,7 @@ impl<'a> ListBuilder<'a> {
} }
fn finish(self) -> (Content, StyleChain<'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 mut items = items.peekable();
let (first, _) = items.peek().unwrap(); let (first, _) = items.peek().unwrap();
let output = if first.is::<ListItem>() { let output = if first.is::<ListItem>() {
@ -812,7 +531,7 @@ impl<'a> ListBuilder<'a> {
} else { } else {
unreachable!() unreachable!()
}; };
(output, shared) (output, trunk)
} }
} }
@ -858,8 +577,8 @@ impl<'a> CiteGroupBuilder<'a> {
false 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()); 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)
} }
} }

View File

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

View File

@ -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. /// Inserts a line break.
/// ///

View File

@ -2,7 +2,7 @@ use ecow::EcoString;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::engine::Engine; 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::layout::{Em, Length};
use crate::text::{variant, SpaceElem, TextElem, TextSize}; use crate::text::{variant, SpaceElem, TextElem, TextSize};
use crate::World; use crate::World;
@ -134,9 +134,9 @@ fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
Some(' '.into()) Some(' '.into())
} else if let Some(elem) = content.to_packed::<TextElem>() { } else if let Some(elem) = content.to_packed::<TextElem>() {
convert_script(elem.text(), sub) 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(); let mut full = EcoString::new();
for item in children { for item in &sequence.children {
match search_text(item, sub) { match search_text(item, sub) {
Some(text) => full.push_str(&text), Some(text) => full.push_str(&text),
None => return None, None => return None,

View File

@ -1,8 +1,8 @@
use crate::foundations::{
elem, Behave, Behaviour, Packed, PlainText, Repr, Unlabellable,
};
use ecow::EcoString; use ecow::EcoString;
use crate::foundations::{elem, Packed, PlainText, Repr, Unlabellable};
use crate::realize::{Behave, Behaviour};
/// A text space. /// A text space.
#[elem(Behave, Unlabellable, PlainText, Repr)] #[elem(Behave, Unlabellable, PlainText, Repr)]
pub struct SpaceElem {} pub struct SpaceElem {}