Refactor behaved building (#3403)

This commit is contained in:
Laurenz 2024-02-12 13:29:52 +01:00 committed by GitHub
parent 36d588ae5d
commit 63b73ee98c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 283 additions and 397 deletions

View File

@ -14,8 +14,9 @@ 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, Dict, Element, Fields, IntoValue, Label, NativeElement, elem, func, scope, ty, Behave, Behaviour, Dict, Element, Fields, IntoValue, Label,
Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Value, NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
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};
@ -167,6 +168,12 @@ impl Content {
self.make_mut().lifecycle.insert(0); self.make_mut().lifecycle.insert(0);
} }
/// How this element interacts with other elements in a stream.
pub fn behaviour(&self) -> Behaviour {
self.with::<dyn Behave>()
.map_or(Behaviour::Supportive, Behave::behaviour)
}
/// Get a field by ID. /// Get a field by ID.
/// ///
/// This is the preferred way to access fields. However, you can only use it /// This is the preferred way to access fields. However, you can only use it
@ -335,7 +342,7 @@ impl Content {
} }
/// Also auto expands sequence of sequences into flat sequence /// Also auto expands sequence of sequences into flat sequence
pub fn sequence_recursive_for_each(&self, f: &mut impl FnMut(&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(children) = self.to_sequence() {
children.for_each(|c| c.sequence_recursive_for_each(f)); children.for_each(|c| c.sequence_recursive_for_each(f));
} else { } else {

View File

@ -1,5 +1,4 @@
use std::any::TypeId; use std::any::TypeId;
use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::hash::Hash; use std::hash::Hash;
@ -309,11 +308,7 @@ pub trait Behave {
/// Whether this weak element is larger than a previous one and thus picked /// Whether this weak element is larger than a previous one and thus picked
/// as the maximum when the levels are the same. /// as the maximum when the levels are the same.
#[allow(unused_variables)] #[allow(unused_variables)]
fn larger( fn larger(&self, prev: &(&Content, StyleChain), styles: StyleChain) -> bool {
&self,
prev: &(Cow<Content>, Behaviour, StyleChain),
styles: StyleChain,
) -> bool {
false false
} }
} }
@ -336,3 +331,10 @@ pub enum Behaviour {
/// An element that does not have a visual representation. /// An element that does not have a visual representation.
Invisible, Invisible,
} }
impl Behaviour {
/// Whether this of `Weak(_)` variant.
pub fn is_weak(self) -> bool {
matches!(self, Self::Weak(_))
}
}

View File

@ -1,8 +1,7 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::{iter, mem, ptr}; use std::{mem, ptr};
use comemo::Prehashed; use comemo::Prehashed;
use ecow::{eco_vec, EcoString, EcoVec}; use ecow::{eco_vec, EcoString, EcoVec};
@ -570,13 +569,13 @@ impl<'a> StyleChain<'a> {
} }
/// Iterate over the links of the chain. /// Iterate over the links of the chain.
fn links(self) -> Links<'a> { pub fn links(self) -> Links<'a> {
Links(Some(self)) Links(Some(self))
} }
/// Build owned styles from the suffix (all links beyond the `len`) of the /// Build owned styles from the suffix (all links beyond the `len`) of the
/// chain. /// chain.
fn suffix(self, len: usize) -> Styles { pub fn suffix(self, len: usize) -> Styles {
let mut suffix = Styles::new(); let mut suffix = Styles::new();
let take = self.links().count().saturating_sub(len); let take = self.links().count().saturating_sub(len);
for link in self.links().take(take) { for link in self.links().take(take) {
@ -586,7 +585,7 @@ impl<'a> StyleChain<'a> {
} }
/// Remove the last link from the chain. /// Remove the last link from the chain.
fn pop(&mut self) { pub fn pop(&mut self) {
*self = self.tail.copied().unwrap_or_default(); *self = self.tail.copied().unwrap_or_default();
} }
} }
@ -672,7 +671,7 @@ impl<'a> Iterator for Entries<'a> {
} }
/// An iterator over the links of a style chain. /// An iterator over the links of a style chain.
struct Links<'a>(Option<StyleChain<'a>>); pub struct Links<'a>(Option<StyleChain<'a>>);
impl<'a> Iterator for Links<'a> { impl<'a> Iterator for Links<'a> {
type Item = &'a [Prehashed<Style>]; type Item = &'a [Prehashed<Style>];
@ -684,201 +683,6 @@ impl<'a> Iterator for Links<'a> {
} }
} }
/// A sequence of items with associated styles.
#[derive(Clone, Hash)]
pub struct StyleVec<T> {
items: Vec<T>,
styles: Vec<(Styles, usize)>,
}
impl<T> StyleVec<T> {
/// Whether there are any items in the sequence.
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
/// Number of items in the sequence.
pub fn len(&self) -> usize {
self.items.len()
}
/// Insert an item in the front. The item will share the style of the
/// current first item.
///
/// This method has no effect if the vector is empty.
pub fn push_front(&mut self, item: T) {
if !self.styles.is_empty() {
self.items.insert(0, item);
self.styles[0].1 += 1;
}
}
/// Map the contained items.
pub fn map<F, U>(&self, f: F) -> StyleVec<U>
where
F: FnMut(&T) -> U,
{
StyleVec {
items: self.items.iter().map(f).collect(),
styles: self.styles.clone(),
}
}
/// Iterate over references to the contained items and associated styles.
pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ {
self.items().zip(
self.styles
.iter()
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
)
}
/// Iterate over the contained items.
pub fn items(&self) -> std::slice::Iter<'_, T> {
self.items.iter()
}
/// Extract the contained items.
pub fn into_items(self) -> Vec<T> {
self.items
}
/// Iterate over the contained style lists. Note that zipping this with
/// `items()` does not yield the same result as calling `iter()` because
/// this method only returns lists once that are shared by consecutive
/// items. This method is designed for use cases where you want to check,
/// for example, whether any of the lists fulfills a specific property.
pub fn styles(&self) -> impl Iterator<Item = &Styles> {
self.styles.iter().map(|(map, _)| map)
}
}
impl<'a> StyleVec<Cow<'a, Content>> {
pub fn to_vec<F: From<Content>>(self) -> Vec<F> {
self.items
.into_iter()
.zip(
self.styles
.iter()
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
)
.map(|(content, styles)| content.into_owned().styled_with_map(styles.clone()))
.map(F::from)
.collect()
}
}
impl<T> Default for StyleVec<T> {
fn default() -> Self {
Self { items: vec![], styles: vec![] }
}
}
impl<T> FromIterator<T> for StyleVec<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let items: Vec<_> = iter.into_iter().collect();
let styles = vec![(Styles::new(), items.len())];
Self { items, styles }
}
}
impl<T: Debug> Debug for StyleVec<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.iter().map(|(item, styles)| {
crate::util::debug(|f| {
styles.fmt(f)?;
item.fmt(f)
})
}))
.finish()
}
}
/// Assists in the construction of a [`StyleVec`].
#[derive(Debug)]
pub struct StyleVecBuilder<'a, T> {
items: Vec<T>,
chains: Vec<(StyleChain<'a>, usize)>,
}
impl<'a, T> StyleVecBuilder<'a, T> {
/// Create a new style-vec builder.
pub fn new() -> Self {
Self { items: vec![], chains: vec![] }
}
/// Whether the builder is empty.
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
/// Push a new item into the style vector.
pub fn push(&mut self, item: T, styles: StyleChain<'a>) {
self.items.push(item);
if let Some((prev, count)) = self.chains.last_mut() {
if *prev == styles {
*count += 1;
return;
}
}
self.chains.push((styles, 1));
}
/// Iterate over the contained items.
pub fn elems(&self) -> std::slice::Iter<'_, T> {
self.items.iter()
}
/// Finish building, returning a pair of two things:
/// - a style vector of items with the non-shared styles
/// - a shared prefix chain of styles that apply to all items
pub fn finish(self) -> (StyleVec<T>, StyleChain<'a>) {
let mut iter = self.chains.iter();
let mut trunk = match iter.next() {
Some(&(chain, _)) => chain,
None => return Default::default(),
};
let mut shared = trunk.links().count();
for &(mut chain, _) in iter {
let len = chain.links().count();
if len < shared {
for _ in 0..shared - len {
trunk.pop();
}
shared = len;
} else if len > shared {
for _ in 0..len - shared {
chain.pop();
}
}
while shared > 0 && chain != trunk {
trunk.pop();
chain.pop();
shared -= 1;
}
}
let styles = self
.chains
.into_iter()
.map(|(chain, count)| (chain.suffix(shared), count))
.collect();
(StyleVec { items: self.items, styles }, trunk)
}
}
impl<'a, T> Default for StyleVecBuilder<'a, T> {
fn default() -> Self {
Self::new()
}
}
/// A property that is resolved with other properties from the style chain. /// A property that is resolved with other properties from the style chain.
pub trait Resolve { pub trait Resolve {
/// The type of the resolved output. /// The type of the resolved output.

View File

@ -77,7 +77,7 @@ use crate::eval::Tracer;
use crate::foundations::{category, Category, Content, Scope, StyleChain}; use crate::foundations::{category, Category, Content, Scope, StyleChain};
use crate::introspection::{Introspector, Locator}; use crate::introspection::{Introspector, Locator};
use crate::model::Document; use crate::model::Document;
use crate::realize::{realize_block, realize_root, Scratch}; use crate::realize::{realize_block, realize_root, Arenas};
use crate::World; use crate::World;
/// Arranging elements on the page in different ways. /// Arranging elements on the page in different ways.
@ -195,9 +195,8 @@ impl LayoutRoot for Content {
locator: &mut locator, locator: &mut locator,
tracer, tracer,
}; };
let scratch = Scratch::default(); let arenas = Arenas::default();
let (document, styles) = let (document, styles) = realize_root(&mut engine, &arenas, content, styles)?;
realize_root(&mut engine, &scratch, content, styles)?;
document.layout_root(&mut engine, styles) document.layout_root(&mut engine, styles)
} }
@ -248,9 +247,9 @@ impl LayoutMultiple for Content {
); );
} }
let scratch = Scratch::default(); let arenas = Arenas::default();
let (realized, styles) = let (realized, styles) =
realize_block(&mut engine, &scratch, content, styles)?; realize_block(&mut engine, &arenas, content, styles)?;
realized.with::<dyn LayoutMultiple>().unwrap().layout( realized.with::<dyn LayoutMultiple>().unwrap().layout(
&mut engine, &mut engine,
styles, styles,

View File

@ -1,5 +1,3 @@
use std::borrow::Cow;
use crate::foundations::{ use crate::foundations::{
cast, elem, Behave, Behaviour, Content, Packed, Resolve, StyleChain, cast, elem, Behave, Behaviour, Content, Packed, Resolve, StyleChain,
}; };
@ -75,16 +73,12 @@ impl Behave for Packed<HElem> {
} }
} }
fn larger( fn larger(&self, prev: &(&Content, StyleChain), styles: StyleChain) -> bool {
&self,
prev: &(Cow<Content>, Behaviour, StyleChain),
styles: StyleChain,
) -> bool {
let Some(other) = prev.0.to_packed::<HElem>() else { return false }; let Some(other) = prev.0.to_packed::<HElem>() else { return false };
match (self.amount(), other.amount()) { match (self.amount(), other.amount()) {
(Spacing::Fr(this), Spacing::Fr(other)) => this > other, (Spacing::Fr(this), Spacing::Fr(other)) => this > other,
(Spacing::Rel(this), Spacing::Rel(other)) => { (Spacing::Rel(this), Spacing::Rel(other)) => {
this.resolve(styles) > other.resolve(prev.2) this.resolve(styles) > other.resolve(prev.1)
} }
_ => false, _ => false,
} }
@ -177,16 +171,12 @@ impl Behave for Packed<VElem> {
} }
} }
fn larger( fn larger(&self, prev: &(&Content, StyleChain), styles: StyleChain) -> bool {
&self,
prev: &(Cow<Content>, Behaviour, StyleChain),
styles: StyleChain,
) -> bool {
let Some(other) = prev.0.to_packed::<VElem>() else { return false }; let Some(other) = prev.0.to_packed::<VElem>() else { return false };
match (self.amount(), other.amount()) { match (self.amount(), other.amount()) {
(Spacing::Fr(this), Spacing::Fr(other)) => this > other, (Spacing::Fr(this), Spacing::Fr(other)) => this > other,
(Spacing::Rel(this), Spacing::Rel(other)) => { (Spacing::Rel(this), Spacing::Rel(other)) => {
this.resolve(styles) > other.resolve(prev.2) this.resolve(styles) > other.resolve(prev.1)
} }
_ => false, _ => false,
} }

View File

@ -40,8 +40,6 @@ use self::fragment::*;
use self::row::*; use self::row::*;
use self::spacing::*; use self::spacing::*;
use std::borrow::Cow;
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::foundations::{ use crate::foundations::{
category, Category, Content, Module, Resolve, Scope, StyleChain, category, Category, Content, Module, Resolve, Scope, StyleChain,
@ -239,10 +237,9 @@ impl LayoutMath for Content {
if self.is_sequence() { if self.is_sequence() {
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(Cow::Owned(child.clone()), StyleChain::default()) bb.push(child, StyleChain::default());
}); });
for child in bb.finish::<Content>().0 {
for (child, _) in bb.finish().0.iter() {
child.layout_math(ctx, styles)?; child.layout_math(ctx, styles)?;
} }
return Ok(()); return Ok(());

View File

@ -0,0 +1,34 @@
use typed_arena::Arena;
use crate::foundations::{Content, StyleChain};
/// Temporary storage arenas for building.
#[derive(Default)]
pub struct Arenas<'a> {
chains: Arena<StyleChain<'a>>,
content: Arena<Content>,
}
impl<'a> Arenas<'a> {
/// Store a value in the matching arena.
pub fn store<T: Store<'a>>(&'a self, val: T) -> &'a T {
val.store(self)
}
}
/// Implemented by storable types.
pub trait Store<'a> {
fn store(self, arenas: &'a Arenas<'a>) -> &'a Self;
}
impl<'a> Store<'a> for Content {
fn store(self, arenas: &'a Arenas<'a>) -> &'a Self {
arenas.content.alloc(self)
}
}
impl<'a> Store<'a> for StyleChain<'a> {
fn store(self, arenas: &'a Arenas<'a>) -> &'a Self {
arenas.chains.alloc(self)
}
}

View File

@ -1,106 +1,184 @@
//! Element interaction. //! Element interaction.
use std::borrow::Cow; use crate::foundations::{Behave, Behaviour, Content, StyleChain, Styles};
use crate::syntax::Span;
use crate::foundations::{ /// Processes a sequence of content and resolves behaviour interactions between
Behave, Behaviour, Content, StyleChain, StyleVec, StyleVecBuilder, /// them and separates local styles for each element from the shared trunk of
}; /// styles.
/// A wrapper around a [`StyleVecBuilder`] that allows elements to interact.
#[derive(Debug)] #[derive(Debug)]
pub struct BehavedBuilder<'a> { pub struct BehavedBuilder<'a> {
/// The internal builder. /// The collected content with its styles.
builder: StyleVecBuilder<'a, Cow<'a, Content>>, buf: Vec<(&'a Content, StyleChain<'a>)>,
/// Staged weak and ignorant elements that we can't yet commit to the /// What the last non-ignorant, visible item was.
/// builder. The option is `Some(_)` for weak elements and `None` for
/// ignorant elements.
staged: Vec<(Cow<'a, Content>, Behaviour, StyleChain<'a>)>,
/// What the last non-ignorant item was.
last: Behaviour, last: Behaviour,
} }
impl<'a> BehavedBuilder<'a> { impl<'a> BehavedBuilder<'a> {
/// Create a new style-vec builder. /// Create a new style-vec builder.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { buf: vec![], last: Behaviour::Destructive }
builder: StyleVecBuilder::new(),
staged: vec![],
last: Behaviour::Destructive,
}
} }
/// Whether the builder is totally empty. /// Whether the builder is totally empty.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.builder.is_empty() && self.staged.is_empty() self.buf.is_empty()
} }
/// Whether the builder is empty except for some weak elements that will /// Whether the builder has any proper (non-weak & visible) elements.
/// probably collapse.
pub fn has_strong_elements(&self, last: bool) -> bool { pub fn has_strong_elements(&self, last: bool) -> bool {
!self.builder.is_empty() self.buf.iter().any(|(content, _)| {
|| self.staged.iter().any(|(_, behaviour, _)| { let behaviour = content.behaviour();
!matches!(behaviour, Behaviour::Weak(_) | Behaviour::Invisible) !matches!(behaviour, Behaviour::Weak(_) | Behaviour::Invisible)
|| (last && *behaviour == Behaviour::Invisible) || (last && behaviour == Behaviour::Invisible)
}) })
} }
/// Push an item into the sequence. /// Push an item into the builder.
pub fn push(&mut self, elem: Cow<'a, Content>, styles: StyleChain<'a>) { pub fn push(&mut self, content: &'a Content, styles: StyleChain<'a>) {
let interaction = elem let mut behaviour = content.behaviour();
match behaviour {
Behaviour::Supportive => {}
Behaviour::Weak(level) => match self.last {
// Remove either this or the preceding weak item.
Behaviour::Weak(prev_level) => {
if level > prev_level {
return;
}
let i = self.find_last_weak().unwrap();
if level == prev_level
&& !content
.with::<dyn Behave>() .with::<dyn Behave>()
.map_or(Behaviour::Supportive, Behave::behaviour); .unwrap()
.larger(&self.buf[i], styles)
match interaction { {
Behaviour::Weak(level) => { return;
if matches!(self.last, Behaviour::Weak(_)) {
let item = elem.with::<dyn Behave>().unwrap();
let i = self.staged.iter().position(|prev| {
let Behaviour::Weak(prev_level) = prev.1 else { return false };
level < prev_level
|| (level == prev_level && item.larger(prev, styles))
});
let Some(i) = i else { return };
self.staged.remove(i);
} }
if self.last != Behaviour::Destructive { self.buf.remove(i);
self.staged.push((elem, interaction, styles));
self.last = interaction;
}
}
Behaviour::Supportive => {
self.flush(true);
self.builder.push(elem, styles);
self.last = interaction;
} }
Behaviour::Destructive => return,
_ => {}
},
Behaviour::Destructive => { Behaviour::Destructive => {
self.flush(false); // Remove preceding weak item.
self.builder.push(elem, styles); if self.last.is_weak() {
self.last = interaction; let i = self.find_last_weak().unwrap();
self.buf.remove(i);
}
} }
Behaviour::Ignorant | Behaviour::Invisible => { Behaviour::Ignorant | Behaviour::Invisible => {
self.staged.push((elem, interaction, styles)); behaviour = self.last;
}
} }
} }
/// Return the finish style vec and the common prefix chain. self.last = behaviour;
pub fn finish(mut self) -> (StyleVec<Cow<'a, Content>>, StyleChain<'a>) { self.buf.push((content, styles));
self.flush(false);
self.builder.finish()
} }
/// Push the staged elements, filtering out weak elements if `supportive` is /// Iterate over the content that has been pushed so far.
/// false. pub fn items(&self) -> impl Iterator<Item = &'a Content> + '_ {
fn flush(&mut self, supportive: bool) { self.buf.iter().map(|&(c, _)| c)
for (item, interaction, styles) in self.staged.drain(..) { }
if supportive
|| interaction == Behaviour::Ignorant /// Return the built content (possibly styled with local styles) plus a
|| interaction == Behaviour::Invisible /// trunk style chain and a span for the collection.
{ pub fn finish<F: From<Content>>(self) -> (Vec<F>, StyleChain<'a>, Span) {
self.builder.push(item, styles); let (output, trunk, span) = self.finish_iter();
let output = output.map(|(c, s)| c.clone().styled_with_map(s).into()).collect();
(output, trunk, span)
}
/// Return an iterator over the built content and its local styles plus a
/// trunk style chain and a span for the collection.
pub fn finish_iter(
mut self,
) -> (impl Iterator<Item = (&'a Content, Styles)>, StyleChain<'a>, Span) {
self.trim_weak();
let span = self.determine_span();
let (trunk, depth) = self.determine_style_trunk();
let mut iter = self.buf.into_iter().peekable();
let mut reuse = None;
// Map the content + style chains to content + suffix maps, reusing
// equivalent adjacent suffix maps, if possible.
let output = std::iter::from_fn(move || {
let (c, s) = iter.next()?;
// Try to reuse a suffix map that the previous element has
// stored for us.
let suffix = reuse.take().unwrap_or_else(|| s.suffix(depth));
// Store a suffix map for the next element if it has the same style
// chain.
if iter.peek().map_or(false, |&(_, s2)| s == s2) {
reuse = Some(suffix.clone());
}
Some((c, suffix))
});
(output, trunk, span)
}
/// Trim a possibly remaining weak item.
fn trim_weak(&mut self) {
if self.last.is_weak() {
let i = self.find_last_weak().unwrap();
self.buf.remove(i);
} }
} }
/// Get the position of the right most weak item.
fn find_last_weak(&self) -> Option<usize> {
self.buf.iter().rposition(|(c, _)| c.behaviour().is_weak())
}
/// Determine a span for the built collection.
fn determine_span(&self) -> Span {
let mut span = Span::detached();
for &(content, _) in &self.buf {
span = content.span();
if !span.is_detached() {
break;
}
}
span
}
/// Determine the shared trunk style chain.
fn determine_style_trunk(&self) -> (StyleChain<'a>, usize) {
// Determine shared style depth and first span.
let mut trunk = match self.buf.first() {
Some(&(_, chain)) => chain,
None => Default::default(),
};
let mut depth = trunk.links().count();
for (_, mut chain) in &self.buf {
let len = chain.links().count();
if len < depth {
for _ in 0..depth - len {
trunk.pop();
}
depth = len;
} else if len > depth {
for _ in 0..len - depth {
chain.pop();
}
}
while depth > 0 && chain != trunk {
trunk.pop();
chain.pop();
depth -= 1;
}
}
(trunk, depth)
} }
} }

View File

@ -1,7 +1,9 @@
//! Realization of content. //! Realization of content.
mod arenas;
mod behave; mod behave;
pub use self::arenas::Arenas;
pub use self::behave::BehavedBuilder; pub use self::behave::BehavedBuilder;
use std::borrow::Cow; use std::borrow::Cow;
@ -9,14 +11,12 @@ use std::cell::OnceCell;
use std::mem; use std::mem;
use smallvec::smallvec; use smallvec::smallvec;
use typed_arena::Arena;
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::{
Behave, Behaviour, Content, NativeElement, Packed, Recipe, RecipeIndex, Regex, Content, NativeElement, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet,
Selector, Show, ShowSet, Style, StyleChain, StyleVec, StyleVecBuilder, Styles, Style, StyleChain, Styles, Synthesize, Transformation,
Synthesize, Transformation,
}; };
use crate::introspection::{Locatable, Meta, MetaElem}; use crate::introspection::{Locatable, Meta, MetaElem};
use crate::layout::{ use crate::layout::{
@ -36,15 +36,14 @@ use crate::util::{hash128, BitSet};
#[typst_macros::time(name = "realize root")] #[typst_macros::time(name = "realize root")]
pub fn realize_root<'a>( pub fn realize_root<'a>(
engine: &mut Engine, engine: &mut Engine,
scratch: &'a Scratch<'a>, arenas: &'a Arenas<'a>,
content: &'a Content, content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<(Packed<DocumentElem>, StyleChain<'a>)> { ) -> SourceResult<(Packed<DocumentElem>, StyleChain<'a>)> {
let mut builder = Builder::new(engine, scratch, 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) = builder.doc.unwrap().pages.finish(); let (pages, shared, span) = builder.doc.unwrap().pages.finish();
let span = first_span(&pages);
Ok((Packed::new(DocumentElem::new(pages.to_vec())).spanned(span), shared)) Ok((Packed::new(DocumentElem::new(pages.to_vec())).spanned(span), shared))
} }
@ -52,7 +51,7 @@ pub fn realize_root<'a>(
#[typst_macros::time(name = "realize block")] #[typst_macros::time(name = "realize block")]
pub fn realize_block<'a>( pub fn realize_block<'a>(
engine: &mut Engine, engine: &mut Engine,
scratch: &'a Scratch<'a>, arenas: &'a Arenas<'a>,
content: &'a Content, content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> { ) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> {
@ -62,13 +61,12 @@ pub fn realize_block<'a>(
return Ok((Cow::Borrowed(content), styles)); return Ok((Cow::Borrowed(content), styles));
} }
let mut builder = Builder::new(engine, scratch, false); let mut builder = Builder::new(engine, arenas, false);
builder.accept(content, styles)?; builder.accept(content, styles)?;
builder.interrupt_par()?; builder.interrupt_par()?;
let (children, shared) = builder.flow.0.finish(); let (children, shared, span) = builder.flow.0.finish();
let span = first_span(&children); Ok((Cow::Owned(FlowElem::new(children).pack().spanned(span)), shared))
Ok((Cow::Owned(FlowElem::new(children.to_vec()).pack().spanned(span)), shared))
} }
/// Apply the show rules in the given style chain to a target element. /// Apply the show rules in the given style chain to a target element.
@ -366,7 +364,7 @@ struct Builder<'a, 'v, 't> {
/// The engine. /// The engine.
engine: &'v mut Engine<'t>, engine: &'v mut Engine<'t>,
/// Scratch arenas for building. /// Scratch arenas for building.
scratch: &'a Scratch<'a>, arenas: &'a Arenas<'a>,
/// The current document building state. /// The current document building state.
doc: Option<DocBuilder<'a>>, doc: Option<DocBuilder<'a>>,
/// The current flow building state. /// The current flow building state.
@ -379,20 +377,11 @@ struct Builder<'a, 'v, 't> {
cites: CiteGroupBuilder<'a>, cites: CiteGroupBuilder<'a>,
} }
/// Temporary storage arenas for building.
#[derive(Default)]
pub struct Scratch<'a> {
/// An arena where intermediate style chains are stored.
styles: Arena<StyleChain<'a>>,
/// An arena where intermediate content resulting from show rules is stored.
content: Arena<Content>,
}
impl<'a, 'v, 't> Builder<'a, 'v, 't> { impl<'a, 'v, 't> Builder<'a, 'v, 't> {
fn new(engine: &'v mut Engine<'t>, scratch: &'a Scratch<'a>, top: bool) -> Self { fn new(engine: &'v mut Engine<'t>, arenas: &'a Arenas<'a>, top: bool) -> Self {
Self { Self {
engine, engine,
scratch, arenas,
doc: top.then(DocBuilder::default), doc: top.then(DocBuilder::default),
flow: FlowBuilder::default(), flow: FlowBuilder::default(),
par: ParBuilder::default(), par: ParBuilder::default(),
@ -408,9 +397,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
) -> SourceResult<()> { ) -> SourceResult<()> {
if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() { if content.can::<dyn LayoutMath>() && !content.is::<EquationElem>() {
content = self content = self
.scratch .arenas
.content .store(EquationElem::new(content.clone()).pack().spanned(content.span()));
.alloc(EquationElem::new(content.clone()).pack().spanned(content.span()));
} }
if let Some(realized) = realize(self.engine, content, styles)? { if let Some(realized) = realize(self.engine, content, styles)? {
@ -421,10 +409,9 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
hint: "check whether the show rule matches its own output" hint: "check whether the show rule matches its own output"
); );
} }
let stored = self.scratch.content.alloc(realized); let result = self.accept(self.arenas.store(realized), styles);
let v = self.accept(stored, styles);
self.engine.route.decrease(); self.engine.route.decrease();
return v; return result;
} }
if let Some((elem, local)) = content.to_styled() { if let Some((elem, local)) = content.to_styled() {
@ -460,7 +447,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.interrupt_par()?; self.interrupt_par()?;
if self.flow.accept(content, styles) { if self.flow.accept(self.arenas, content, styles) {
return Ok(()); return Ok(());
} }
@ -471,7 +458,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
self.interrupt_page(keep.then_some(styles), false)?; self.interrupt_page(keep.then_some(styles), false)?;
if let Some(doc) = &mut self.doc { if let Some(doc) = &mut self.doc {
if doc.accept(content, styles) { if doc.accept(self.arenas, content, styles) {
return Ok(()); return Ok(());
} }
} }
@ -489,7 +476,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
map: &'a Styles, map: &'a Styles,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
let stored = self.scratch.styles.alloc(styles); let stored = self.arenas.store(styles);
let styles = stored.chain(map); let styles = stored.chain(map);
self.interrupt_style(map, None)?; self.interrupt_style(map, None)?;
self.accept(elem, styles)?; self.accept(elem, styles)?;
@ -535,8 +522,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();
let stored = self.scratch.content.alloc(group); self.accept(self.arenas.store(group), styles)?;
self.accept(stored, styles)?;
for (content, styles) in staged { for (content, styles) in staged {
self.accept(content, styles)?; self.accept(content, styles)?;
} }
@ -549,8 +535,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
if !self.list.items.is_empty() { if !self.list.items.is_empty() {
let staged = mem::take(&mut self.list.staged); let staged = mem::take(&mut self.list.staged);
let (list, styles) = mem::take(&mut self.list).finish(); let (list, styles) = mem::take(&mut self.list).finish();
let stored = self.scratch.content.alloc(list); self.accept(self.arenas.store(list), styles)?;
self.accept(stored, styles)?;
for (content, styles) in staged { for (content, styles) in staged {
self.accept(content, styles)?; self.accept(content, styles)?;
} }
@ -562,8 +547,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();
let stored = self.scratch.content.alloc(par); self.accept(self.arenas.store(par), styles)?;
self.accept(stored, styles)?;
} }
Ok(()) Ok(())
@ -577,17 +561,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) = mem::take(&mut self.flow).0.finish(); let (children, shared, span) = mem::take(&mut self.flow).0.finish();
let styles = if shared == StyleChain::default() { let styles = if shared == StyleChain::default() {
styles.unwrap_or_default() styles.unwrap_or_default()
} else { } else {
shared shared
}; };
let span = first_span(&children); let flow = FlowElem::new(children);
let flow = FlowElem::new(children.to_vec());
let page = PageElem::new(flow.pack().spanned(span)); let page = PageElem::new(flow.pack().spanned(span));
let stored = self.scratch.content.alloc(page.pack().spanned(span)); self.accept(self.arenas.store(page.pack().spanned(span)), styles)?;
self.accept(stored, styles)?;
} }
Ok(()) Ok(())
} }
@ -596,7 +578,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
/// Accepts pagebreaks and pages. /// Accepts pagebreaks and pages.
struct DocBuilder<'a> { struct DocBuilder<'a> {
/// The page runs built so far. /// The page runs built so far.
pages: StyleVecBuilder<'a, Cow<'a, Content>>, pages: BehavedBuilder<'a>,
/// Whether to keep a following page even if it is empty. /// Whether to keep a following page even if it is empty.
keep_next: bool, keep_next: bool,
/// Whether the next page should be cleared to an even or odd number. /// Whether the next page should be cleared to an even or odd number.
@ -604,7 +586,12 @@ struct DocBuilder<'a> {
} }
impl<'a> DocBuilder<'a> { impl<'a> DocBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { fn accept(
&mut self,
arenas: &'a Arenas<'a>,
content: &'a Content,
styles: StyleChain<'a>,
) -> bool {
if let Some(pagebreak) = content.to_packed::<PagebreakElem>() { if let Some(pagebreak) = content.to_packed::<PagebreakElem>() {
self.keep_next = !pagebreak.weak(styles); self.keep_next = !pagebreak.weak(styles);
self.clear_next = pagebreak.to(styles); self.clear_next = pagebreak.to(styles);
@ -615,9 +602,9 @@ impl<'a> DocBuilder<'a> {
let elem = if let Some(clear_to) = self.clear_next.take() { let elem = if let Some(clear_to) = self.clear_next.take() {
let mut page = page.clone(); let mut page = page.clone();
page.push_clear_to(Some(clear_to)); page.push_clear_to(Some(clear_to));
Cow::Owned(page.pack()) arenas.store(page.pack())
} else { } else {
Cow::Borrowed(content) content
}; };
self.pages.push(elem, styles); self.pages.push(elem, styles);
@ -632,7 +619,7 @@ impl<'a> DocBuilder<'a> {
impl Default for DocBuilder<'_> { impl Default for DocBuilder<'_> {
fn default() -> Self { fn default() -> Self {
Self { Self {
pages: StyleVecBuilder::new(), pages: BehavedBuilder::new(),
keep_next: true, keep_next: true,
clear_next: None, clear_next: None,
} }
@ -644,7 +631,12 @@ impl Default for DocBuilder<'_> {
struct FlowBuilder<'a>(BehavedBuilder<'a>, bool); struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
impl<'a> FlowBuilder<'a> { impl<'a> FlowBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { fn accept(
&mut self,
arenas: &'a Arenas<'a>,
content: &'a Content,
styles: StyleChain<'a>,
) -> bool {
if content.is::<ParbreakElem>() { if content.is::<ParbreakElem>() {
self.1 = true; self.1 = true;
return true; return true;
@ -658,7 +650,7 @@ impl<'a> FlowBuilder<'a> {
|| content.is::<MetaElem>() || content.is::<MetaElem>()
|| content.is::<PlaceElem>() || content.is::<PlaceElem>()
{ {
self.0.push(Cow::Borrowed(content), styles); self.0.push(content, styles);
return true; return true;
} }
@ -679,7 +671,7 @@ impl<'a> FlowBuilder<'a> {
if !last_was_parbreak && is_tight_list { if !last_was_parbreak && is_tight_list {
let leading = ParElem::leading_in(styles); let leading = ParElem::leading_in(styles);
let spacing = VElem::list_attach(leading.into()); let spacing = VElem::list_attach(leading.into());
self.0.push(Cow::Owned(spacing.pack()), styles); self.0.push(arenas.store(spacing.pack()), styles);
} }
let (above, below) = if let Some(block) = content.to_packed::<BlockElem>() { let (above, below) = if let Some(block) = content.to_packed::<BlockElem>() {
@ -688,9 +680,9 @@ impl<'a> FlowBuilder<'a> {
(BlockElem::above_in(styles), BlockElem::below_in(styles)) (BlockElem::above_in(styles), BlockElem::below_in(styles))
}; };
self.0.push(Cow::Owned(above.pack()), styles); self.0.push(arenas.store(above.pack()), styles);
self.0.push(Cow::Borrowed(content), styles); self.0.push(content, styles);
self.0.push(Cow::Owned(below.pack()), styles); self.0.push(arenas.store(below.pack()), styles);
return true; return true;
} }
@ -706,7 +698,7 @@ impl<'a> ParBuilder<'a> {
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
if content.is::<MetaElem>() { if content.is::<MetaElem>() {
if self.0.has_strong_elements(false) { if self.0.has_strong_elements(false) {
self.0.push(Cow::Borrowed(content), styles); self.0.push(content, styles);
return true; return true;
} }
} else if content.is::<SpaceElem>() } else if content.is::<SpaceElem>()
@ -719,7 +711,7 @@ impl<'a> ParBuilder<'a> {
.map_or(false, |elem| !elem.block(styles)) .map_or(false, |elem| !elem.block(styles))
|| content.is::<BoxElem>() || content.is::<BoxElem>()
{ {
self.0.push(Cow::Borrowed(content), styles); self.0.push(content, styles);
return true; return true;
} }
@ -727,16 +719,15 @@ impl<'a> ParBuilder<'a> {
} }
fn finish(self) -> (Content, StyleChain<'a>) { fn finish(self) -> (Content, StyleChain<'a>) {
let (children, shared) = self.0.finish(); let (children, shared, span) = self.0.finish();
let span = first_span(&children); (ParElem::new(children).pack().spanned(span), shared)
(ParElem::new(children.to_vec()).pack().spanned(span), shared)
} }
} }
/// Accepts list / enum items, spaces, paragraph breaks. /// Accepts list / enum items, spaces, paragraph breaks.
struct ListBuilder<'a> { struct ListBuilder<'a> {
/// The list items collected so far. /// The list items collected so far.
items: StyleVecBuilder<'a, Cow<'a, Content>>, items: BehavedBuilder<'a>,
/// Whether the list contains no paragraph breaks. /// Whether the list contains no paragraph breaks.
tight: bool, tight: bool,
/// Trailing content for which it is unclear whether it is part of the list. /// Trailing content for which it is unclear whether it is part of the list.
@ -757,11 +748,11 @@ impl<'a> ListBuilder<'a> {
|| content.is::<TermItem>()) || content.is::<TermItem>())
&& self && self
.items .items
.elems() .items()
.next() .next()
.map_or(true, |first| first.func() == content.func()) .map_or(true, |first| first.func() == content.func())
{ {
self.items.push(Cow::Borrowed(content), styles); self.items.push(content, styles);
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakElem>()); self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakElem>());
return true; return true;
} }
@ -770,53 +761,50 @@ impl<'a> ListBuilder<'a> {
} }
fn finish(self) -> (Content, StyleChain<'a>) { fn finish(self) -> (Content, StyleChain<'a>) {
let (items, shared) = self.items.finish(); let (items, shared, span) = self.items.finish_iter();
let span = first_span(&items); let mut items = items.peekable();
let item = items.items().next().unwrap(); let (first, _) = items.peek().unwrap();
let output = if item.is::<ListItem>() { let output = if first.is::<ListItem>() {
ListElem::new( ListElem::new(
items items
.iter()
.map(|(item, local)| { .map(|(item, local)| {
let mut item = item.to_packed::<ListItem>().unwrap().clone(); let mut item = item.to_packed::<ListItem>().unwrap().clone();
let body = item.body().clone().styled_with_map(local.clone()); let body = item.body().clone().styled_with_map(local);
item.push_body(body); item.push_body(body);
item item
}) })
.collect::<Vec<_>>(), .collect(),
) )
.with_tight(self.tight) .with_tight(self.tight)
.pack() .pack()
.spanned(span) .spanned(span)
} else if item.is::<EnumItem>() { } else if first.is::<EnumItem>() {
EnumElem::new( EnumElem::new(
items items
.iter()
.map(|(item, local)| { .map(|(item, local)| {
let mut item = item.to_packed::<EnumItem>().unwrap().clone(); let mut item = item.to_packed::<EnumItem>().unwrap().clone();
let body = item.body().clone().styled_with_map(local.clone()); let body = item.body().clone().styled_with_map(local);
item.push_body(body); item.push_body(body);
item item
}) })
.collect::<Vec<_>>(), .collect(),
) )
.with_tight(self.tight) .with_tight(self.tight)
.pack() .pack()
.spanned(span) .spanned(span)
} else if item.is::<TermItem>() { } else if first.is::<TermItem>() {
TermsElem::new( TermsElem::new(
items items
.iter()
.map(|(item, local)| { .map(|(item, local)| {
let mut item = item.to_packed::<TermItem>().unwrap().clone(); let mut item = item.to_packed::<TermItem>().unwrap().clone();
let term = item.term().clone().styled_with_map(local.clone()); let term = item.term().clone().styled_with_map(local.clone());
let description = let description =
item.description().clone().styled_with_map(local.clone()); item.description().clone().styled_with_map(local);
item.push_term(term); item.push_term(term);
item.push_description(description); item.push_description(description);
item item
}) })
.collect::<Vec<_>>(), .collect(),
) )
.with_tight(self.tight) .with_tight(self.tight)
.pack() .pack()
@ -831,7 +819,7 @@ impl<'a> ListBuilder<'a> {
impl Default for ListBuilder<'_> { impl Default for ListBuilder<'_> {
fn default() -> Self { fn default() -> Self {
Self { Self {
items: StyleVecBuilder::default(), items: BehavedBuilder::default(),
tight: true, tight: true,
staged: vec![], staged: vec![],
} }
@ -875,16 +863,3 @@ impl<'a> CiteGroupBuilder<'a> {
(CiteGroup::new(self.items).pack().spanned(span), self.styles) (CiteGroup::new(self.items).pack().spanned(span), self.styles)
} }
} }
/// Find the first span that isn't detached.
fn first_span(children: &StyleVec<Cow<Content>>) -> Span {
children
.iter()
.filter(|(elem, _)| {
elem.with::<dyn Behave>()
.map_or(true, |b| b.behaviour() != Behaviour::Invisible)
})
.map(|(elem, _)| elem.span())
.find(|span| !span.is_detached())
.unwrap_or_else(Span::detached)
}