Minor realization improvements (#3408)
This commit is contained in:
parent
40099e32e1
commit
1f68e15725
@ -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> }
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(_))
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(());
|
||||
}
|
||||
|
||||
|
@ -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()?
|
||||
});
|
||||
|
@ -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.
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
312
crates/typst/src/realize/process.rs
Normal file
312
crates/typst/src/realize/process.rs
Normal 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)))
|
||||
}
|
@ -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.
|
||||
///
|
||||
|
@ -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,
|
||||
|
@ -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 {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user