mirror of
https://github.com/typst/typst
synced 2025-06-28 00:03:17 +08:00
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.
|
/// 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> }
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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(_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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.
|
||||||
///
|
///
|
||||||
|
@ -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.
|
||||||
///
|
///
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
///
|
///
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()?
|
||||||
});
|
});
|
||||||
|
@ -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.
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.
|
/// Inserts a line break.
|
||||||
///
|
///
|
||||||
|
@ -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,
|
||||||
|
@ -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 {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user