diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 4a820a840..bb9eb2fb1 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -177,7 +177,8 @@ impl Eval for ListNode { type Output = Content; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { - Ok(Content::List(library::structure::ListItem { + Ok(Content::Item(library::structure::ListItem { + kind: library::structure::UNORDERED, number: None, body: Box::new(self.body().eval(ctx, scp)?), })) @@ -188,7 +189,8 @@ impl Eval for EnumNode { type Output = Content; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { - Ok(Content::Enum(library::structure::ListItem { + Ok(Content::Item(library::structure::ListItem { + kind: library::structure::ORDERED, number: self.number(), body: Box::new(self.body().eval(ctx, scp)?), })) diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index 38ba53e9b..8e5801ea5 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -4,7 +4,7 @@ use super::ColumnsNode; use crate::library::prelude::*; /// Layouts its child onto one or multiple pages. -#[derive(Clone, PartialEq, Hash)] +#[derive(PartialEq, Clone, Hash)] pub struct PageNode(pub LayoutNode); #[node] diff --git a/src/library/structure/doc.rs b/src/library/structure/doc.rs new file mode 100644 index 000000000..d3fc0b391 --- /dev/null +++ b/src/library/structure/doc.rs @@ -0,0 +1,29 @@ +use crate::library::layout::PageNode; +use crate::library::prelude::*; + +/// A sequence of page runs. +#[derive(Hash)] +pub struct DocNode(pub StyleVec); + +impl DocNode { + /// Layout the document into a sequence of frames, one per page. + pub fn layout( + &self, + ctx: &mut Context, + styles: StyleChain, + ) -> TypResult>> { + let mut frames = vec![]; + for (page, map) in self.0.iter() { + let number = 1 + frames.len(); + frames.extend(page.layout(ctx, number, map.chain(&styles))?); + } + Ok(frames) + } +} + +impl Debug for DocNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("Doc ")?; + self.0.fmt(f) + } +} diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 6655175fc..ac705156e 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -1,3 +1,5 @@ +use std::fmt::Write; + use unscanny::Scanner; use crate::library::layout::{GridNode, TrackSizing}; @@ -14,12 +16,14 @@ pub struct ListNode { /// there is list spacing between the items. pub tight: bool, /// The individual bulleted or numbered items. - pub items: Vec, + pub items: StyleVec, } /// An item in a list. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct ListItem { + /// The kind of item. + pub kind: ListKind, /// The number of the item. pub number: Option, /// The node that produces the item's body. @@ -59,7 +63,11 @@ impl ListNode { items: args .all()? .into_iter() - .map(|body| ListItem { number: None, body: Box::new(body) }) + .map(|body| ListItem { + kind: L, + number: None, + body: Box::new(body), + }) .collect(), })) } @@ -72,7 +80,7 @@ impl Show for ListNode { "tight" => Value::Bool(self.tight), "items" => Value::Array( self.items - .iter() + .items() .map(|item| Value::Content((*item.body).clone())) .collect() ), @@ -85,12 +93,13 @@ impl Show for ListNode { let label = styles.get(Self::LABEL); - for item in &self.items { + for (item, map) in self.items.iter() { number = item.number.unwrap_or(number); cells.push(LayoutNode::default()); - cells.push(label.resolve(ctx, L, number)?.pack()); + cells + .push(label.resolve(ctx, L, number)?.styled_with_map(map.clone()).pack()); cells.push(LayoutNode::default()); - cells.push((*item.body).clone().pack()); + cells.push((*item.body).clone().styled_with_map(map.clone()).pack()); number += 1; } @@ -127,9 +136,18 @@ impl Show for ListNode { } } -impl From for ListNode { - fn from(item: ListItem) -> Self { - Self { items: vec![item], tight: true, start: 1 } +impl Debug for ListItem { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if self.kind == UNORDERED { + f.write_char('-')?; + } else { + if let Some(number) = self.number { + write!(f, "{}", number)?; + } + f.write_char('.')?; + } + f.write_char(' ')?; + self.body.fmt(f) } } diff --git a/src/library/structure/mod.rs b/src/library/structure/mod.rs index 073d52e90..a597211e7 100644 --- a/src/library/structure/mod.rs +++ b/src/library/structure/mod.rs @@ -1,9 +1,11 @@ //! Document structuring. +mod doc; mod heading; mod list; mod table; +pub use doc::*; pub use heading::*; pub use list::*; pub use table::*; diff --git a/src/model/collapse.rs b/src/model/collapse.rs index 18cfae8b9..17933fe8f 100644 --- a/src/model/collapse.rs +++ b/src/model/collapse.rs @@ -29,6 +29,11 @@ impl<'a, T> CollapsingBuilder<'a, T> { } } + /// Whether the builder is empty. + pub fn is_empty(&self) -> bool { + self.builder.is_empty() && self.staged.is_empty() + } + /// Can only exist when there is at least one supportive item to its left /// and to its right, with no destructive items or weak items in between to /// its left and no destructive items in between to its right. There may be diff --git a/src/model/content.rs b/src/model/content.rs index a7eb906a1..6e1e2f1c5 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::Sum; +use std::mem; use std::ops::{Add, AddAssign}; use typed_arena::Arena; @@ -12,7 +13,7 @@ use super::{ use crate::diag::StrResult; use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; use crate::library::prelude::*; -use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED}; +use crate::library::structure::{DocNode, ListItem, ListNode, ORDERED, UNORDERED}; use crate::library::text::{DecoNode, ParChild, ParNode, UNDERLINE}; use crate::util::EcoString; @@ -58,10 +59,8 @@ pub enum Content { Vertical(Spacing), /// A block-level node. Block(LayoutNode), - /// An item in an unordered list. - List(ListItem), - /// An item in an ordered list. - Enum(ListItem), + /// A list / enum item. + Item(ListItem), /// A page break. Pagebreak(bool), /// A page node. @@ -176,25 +175,15 @@ impl Content { /// Layout this content into a collection of pages. pub fn layout(&self, ctx: &mut Context) -> TypResult>> { - let sya = Arena::new(); - let tpa = Arena::new(); + let copy = ctx.styles.clone(); + let styles = StyleChain::with_root(©); + let scratch = Scratch::default(); - let styles = ctx.styles.clone(); - let styles = StyleChain::with_root(&styles); + let mut builder = Builder::new(ctx, &scratch, true); + builder.accept(self, styles)?; - let mut builder = Builder::new(&sya, &tpa, true); - builder.process(ctx, self, styles)?; - builder.finish(ctx, styles)?; - - let mut frames = vec![]; - let (pages, shared) = builder.pages.unwrap().finish(); - - for (page, map) in pages.iter() { - let number = 1 + frames.len(); - frames.extend(page.layout(ctx, number, map.chain(&shared))?); - } - - Ok(frames) + let (doc, shared) = builder.into_doc(styles)?; + doc.layout(ctx, shared) } } @@ -205,15 +194,11 @@ impl Layout for Content { regions: &Regions, styles: StyleChain, ) -> TypResult>> { - let sya = Arena::new(); - let tpa = Arena::new(); - - let mut builder = Builder::new(&sya, &tpa, false); - builder.process(ctx, self, styles)?; - builder.finish(ctx, styles)?; - - let (flow, shared) = builder.flow.finish(); - FlowNode(flow).layout(ctx, regions, shared) + let scratch = Scratch::default(); + let mut builder = Builder::new(ctx, &scratch, false); + builder.accept(self, styles)?; + let (flow, shared) = builder.into_flow(styles)?; + flow.layout(ctx, regions, shared) } fn pack(self) -> LayoutNode { @@ -243,17 +228,7 @@ impl Debug for Content { Self::Colbreak => f.pad("Colbreak"), Self::Vertical(kind) => write!(f, "Vertical({kind:?})"), Self::Block(node) => node.fmt(f), - Self::List(item) => { - f.write_str("- ")?; - item.body.fmt(f) - } - Self::Enum(item) => { - if let Some(number) = item.number { - write!(f, "{}", number)?; - } - f.write_str(". ")?; - item.body.fmt(f) - } + Self::Item(item) => item.fmt(f), Self::Pagebreak(soft) => write!(f, "Pagebreak({soft})"), Self::Page(page) => page.fmt(f), Self::Show(node) => node.fmt(f), @@ -305,288 +280,400 @@ impl Sum for Content { } } -/// Builds a flow or page nodes from content. -struct Builder<'a> { +/// Builds a document or a flow node from content. +struct Builder<'a, 'ctx> { + /// The core context. + ctx: &'ctx mut Context, + /// Scratch arenas for building. + scratch: &'a Scratch<'a>, + /// The current document building state. + doc: Option>, + /// The current flow building state. + flow: FlowBuilder<'a>, + /// The current paragraph building state. + par: ParBuilder<'a>, + /// The current list building state. + list: ListBuilder<'a>, +} + +/// Temporary storage arenas for building. +#[derive(Default)] +struct Scratch<'a> { /// An arena where intermediate style chains are stored. - sya: &'a Arena>, + styles: Arena>, /// An arena where intermediate content resulting from show rules is stored. - tpa: &'a Arena, - /// The already built page runs. - pages: Option>, - /// The currently built list. - list: Option>, - /// The currently built flow. - flow: CollapsingBuilder<'a, FlowChild>, - /// The currently built paragraph. - par: CollapsingBuilder<'a, ParChild>, - /// Whether to keep the next page even if it is empty. - keep_next: bool, + templates: Arena, } -/// Builds an unordered or ordered list from items. -struct ListBuilder<'a> { - styles: StyleChain<'a>, - kind: ListKind, - items: Vec, - tight: bool, - staged: Vec<(&'a Content, StyleChain<'a>)>, -} - -impl<'a> Builder<'a> { - /// Prepare the builder. - fn new(sya: &'a Arena>, tpa: &'a Arena, top: bool) -> Self { +impl<'a, 'ctx> Builder<'a, 'ctx> { + fn new(ctx: &'ctx mut Context, scratch: &'a Scratch<'a>, top: bool) -> Self { Self { - sya, - tpa, - pages: top.then(|| StyleVecBuilder::new()), - flow: CollapsingBuilder::new(), - list: None, - par: CollapsingBuilder::new(), - keep_next: true, + ctx, + scratch, + doc: top.then(|| DocBuilder::default()), + flow: FlowBuilder::default(), + par: ParBuilder::default(), + list: ListBuilder::default(), } } - /// Process content. - fn process( - &mut self, - ctx: &mut Context, - content: &'a Content, + fn into_doc( + mut self, styles: StyleChain<'a>, - ) -> TypResult<()> { - if let Some(builder) = &mut self.list { - match content { - Content::Space => { - builder.staged.push((content, styles)); - return Ok(()); - } - Content::Parbreak => { - builder.staged.push((content, styles)); - return Ok(()); - } - Content::List(item) if builder.kind == UNORDERED => { - builder.tight &= - builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); - builder.staged.clear(); - builder.items.push(item.clone()); - return Ok(()); - } - Content::Enum(item) if builder.kind == ORDERED => { - builder.tight &= - builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak); - builder.staged.clear(); - builder.items.push(item.clone()); - return Ok(()); - } - _ => self.finish_list(ctx)?, - } + ) -> TypResult<(DocNode, StyleChain<'a>)> { + self.interrupt(Interruption::Page, styles, true)?; + let (pages, shared) = self.doc.unwrap().pages.finish(); + Ok((DocNode(pages), shared)) + } + + fn into_flow( + mut self, + styles: StyleChain<'a>, + ) -> TypResult<(FlowNode, StyleChain<'a>)> { + self.interrupt(Interruption::Par, styles, false)?; + let (children, shared) = self.flow.0.finish(); + Ok((FlowNode(children), shared)) + } + + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> TypResult<()> { + // Handle special content kinds. + match content { + Content::Show(node) => return self.show(node, styles), + Content::Styled(styled) => return self.styled(styled, styles), + Content::Sequence(seq) => return self.sequence(seq, styles), + _ => {} } - match content { - Content::Space => { - self.par.weak(ParChild::Text(' '.into()), 0, styles); - } - Content::Linebreak(justified) => { - let c = if *justified { '\u{2028}' } else { '\n' }; - self.par.destructive(ParChild::Text(c.into()), styles); - } - Content::Horizontal(kind) => { - let child = ParChild::Spacing(*kind); - if kind.is_fractional() { - self.par.destructive(child, styles); - } else { - self.par.ignorant(child, styles); - } - } - Content::Quote(double) => { - self.par.supportive(ParChild::Quote(*double), styles); - } - Content::Text(text) => { - self.par.supportive(ParChild::Text(text.clone()), styles); - } - Content::Inline(node) => { - self.par.supportive(ParChild::Node(node.clone()), styles); - } - Content::Parbreak => { - self.finish_par(styles); - self.flow.weak(FlowChild::Parbreak, 1, styles); - } - Content::Colbreak => { - self.finish_par(styles); - self.flow.destructive(FlowChild::Colbreak, styles); - } - Content::Vertical(kind) => { - self.finish_par(styles); - let child = FlowChild::Spacing(*kind); - if kind.is_fractional() { - self.flow.destructive(child, styles); - } else { - self.flow.ignorant(child, styles); - } - } - Content::Block(node) => { - self.finish_par(styles); - let child = FlowChild::Node(node.clone()); - if node.is::() { - self.flow.ignorant(child, styles); - } else { - self.flow.supportive(child, styles); - } - self.finish_par(styles); - } - Content::List(item) => { - self.list = Some(ListBuilder { - styles, - kind: UNORDERED, - items: vec![item.clone()], - tight: true, - staged: vec![], - }); - } - Content::Enum(item) => { - self.list = Some(ListBuilder { - styles, - kind: ORDERED, - items: vec![item.clone()], - tight: true, - staged: vec![], - }); - } - Content::Pagebreak(soft) => { - self.finish_page(ctx, !soft, !soft, styles)?; - } - Content::Page(page) => { - self.finish_page(ctx, false, false, styles)?; - if let Some(pages) = &mut self.pages { - pages.push(page.clone(), styles); - } - } - Content::Show(node) => { - let id = node.id(); - let realized = match styles.realize(ctx, node)? { - Some(content) => content, - None => node.realize(ctx, styles)?, - }; - let content = node.finalize(ctx, styles, realized)?; - let stored = self.tpa.alloc(content); - self.process(ctx, stored, styles.unscoped(id))?; - } - Content::Styled(styled) => { - let (sub, map) = styled.as_ref(); - let stored = self.sya.alloc(styles); - let styles = map.chain(stored); + if self.list.accept(content, styles) { + return Ok(()); + } - let interruption = map.interruption(); - match interruption { - Some(Interruption::Page) => { - self.finish_page(ctx, false, true, styles)? - } - Some(Interruption::Par) => self.finish_par(styles), - None => {} - } + self.interrupt(Interruption::List, styles, false)?; - self.process(ctx, sub, styles)?; + if self.par.accept(content, styles) { + return Ok(()); + } - match interruption { - Some(Interruption::Page) => { - self.finish_page(ctx, true, false, styles)? - } - Some(Interruption::Par) => self.finish_par(styles), - None => {} - } + self.interrupt(Interruption::Par, styles, false)?; + + if self.flow.accept(content, styles) { + return Ok(()); + } + + let keep = matches!(content, Content::Pagebreak(false)); + self.interrupt(Interruption::Page, styles, keep)?; + + if let Some(doc) = &mut self.doc { + doc.accept(content, styles); + } + + // We might want to issue a warning or error for content that wasn't + // handled (e.g. a pagebreak in a flow building process). However, we + // don't have the spans here at the moment. + Ok(()) + } + + fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> { + let id = node.id(); + let realized = match styles.realize(self.ctx, node)? { + Some(content) => content, + None => node.realize(self.ctx, styles)?, + }; + + let content = node.finalize(self.ctx, styles, realized)?; + let stored = self.scratch.templates.alloc(content); + self.accept(stored, styles.unscoped(id)) + } + + fn styled( + &mut self, + (content, map): &'a (Content, StyleMap), + styles: StyleChain<'a>, + ) -> TypResult<()> { + let stored = self.scratch.styles.alloc(styles); + let styles = map.chain(stored); + let intr = map.interruption(); + + if let Some(intr) = intr { + self.interrupt(intr, styles, false)?; + } + + self.accept(content, styles)?; + + if let Some(intr) = intr { + self.interrupt(intr, styles, true)?; + } + + Ok(()) + } + + fn interrupt( + &mut self, + intr: Interruption, + styles: StyleChain<'a>, + keep: bool, + ) -> TypResult<()> { + if intr >= Interruption::List && !self.list.is_empty() { + mem::take(&mut self.list).finish(self)?; + } + + if intr >= Interruption::Par { + if !self.par.is_empty() { + self.flow.0.weak(FlowChild::Leading, 0, styles); + mem::take(&mut self.par).finish(self); } - Content::Sequence(seq) => { - for sub in seq.iter() { - self.process(ctx, sub, styles)?; + self.flow.0.weak(FlowChild::Leading, 0, styles); + } + + if intr >= Interruption::Page { + if let Some(doc) = &mut self.doc { + if !self.flow.is_empty() || (doc.keep_next && keep) { + mem::take(&mut self.flow).finish(doc, styles); } + doc.keep_next = !keep; } } Ok(()) } - /// Finish the currently built paragraph. - fn finish_par(&mut self, styles: StyleChain<'a>) { - let (mut par, shared) = std::mem::take(&mut self.par).finish(); - if !par.is_empty() { - // Paragraph indent should only apply if the paragraph starts with - // text and follows directly after another paragraph. - let indent = shared.get(ParNode::INDENT); - if !indent.is_zero() - && par - .items() - .find_map(|child| match child { - ParChild::Spacing(_) => None, - ParChild::Text(_) | ParChild::Quote(_) => Some(true), - ParChild::Node(_) => Some(false), - }) - .unwrap_or_default() - && self - .flow - .items() - .rev() - .find_map(|child| match child { - FlowChild::Leading => None, - FlowChild::Parbreak => None, - FlowChild::Node(node) => Some(node.is::()), - FlowChild::Spacing(_) => Some(false), - FlowChild::Colbreak => Some(false), - }) - .unwrap_or_default() - { - par.push_front(ParChild::Spacing(indent.into())); - } - - let node = ParNode(par).pack(); - self.flow.supportive(FlowChild::Node(node), shared); + fn sequence(&mut self, seq: &'a [Content], styles: StyleChain<'a>) -> TypResult<()> { + for content in seq { + self.accept(content, styles)?; } - self.flow.weak(FlowChild::Leading, 0, styles); + Ok(()) + } +} + +/// Accepts pagebreaks and pages. +struct DocBuilder<'a> { + /// The page runs built so far. + pages: StyleVecBuilder<'a, PageNode>, + /// Whether to keep a following page even if it is empty. + keep_next: bool, +} + +impl<'a> DocBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) { + match content { + Content::Pagebreak(soft) => { + self.keep_next = !soft; + } + Content::Page(page) => { + self.pages.push(page.clone(), styles); + self.keep_next = false; + } + _ => {} + } + } +} + +impl Default for DocBuilder<'_> { + fn default() -> Self { + Self { + pages: StyleVecBuilder::new(), + keep_next: true, + } + } +} + +/// Accepts flow content. +#[derive(Default)] +struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>); + +impl<'a> FlowBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + match content { + Content::Parbreak => { + self.0.weak(FlowChild::Parbreak, 1, styles); + } + Content::Colbreak => { + self.0.destructive(FlowChild::Colbreak, styles); + } + Content::Vertical(kind) => { + let child = FlowChild::Spacing(*kind); + if kind.is_fractional() { + self.0.destructive(child, styles); + } else { + self.0.ignorant(child, styles); + } + } + Content::Block(node) => { + let child = FlowChild::Node(node.clone()); + if node.is::() { + self.0.ignorant(child, styles); + } else { + self.0.supportive(child, styles); + } + } + _ => return false, + } + + true } - /// Finish the currently built list. - fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> { - let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() { - Some(list) => list, + fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) { + let (flow, shared) = self.0.finish(); + let styles = if flow.is_empty() { styles } else { shared }; + let node = PageNode(FlowNode(flow).pack()); + doc.pages.push(node, styles); + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +/// Accepts paragraph content. +#[derive(Default)] +struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>); + +impl<'a> ParBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + match content { + Content::Space => { + self.0.weak(ParChild::Text(' '.into()), 0, styles); + } + Content::Linebreak(justified) => { + let c = if *justified { '\u{2028}' } else { '\n' }; + self.0.destructive(ParChild::Text(c.into()), styles); + } + Content::Horizontal(kind) => { + let child = ParChild::Spacing(*kind); + if kind.is_fractional() { + self.0.destructive(child, styles); + } else { + self.0.ignorant(child, styles); + } + } + Content::Quote(double) => { + self.0.supportive(ParChild::Quote(*double), styles); + } + Content::Text(text) => { + self.0.supportive(ParChild::Text(text.clone()), styles); + } + Content::Inline(node) => { + self.0.supportive(ParChild::Node(node.clone()), styles); + } + _ => return false, + } + + true + } + + fn finish(self, parent: &mut Builder<'a, '_>) { + let (mut children, shared) = self.0.finish(); + if children.is_empty() { + return; + } + + // Paragraph indent should only apply if the paragraph starts with + // text and follows directly after another paragraph. + let indent = shared.get(ParNode::INDENT); + if !indent.is_zero() + && children + .items() + .find_map(|child| match child { + ParChild::Spacing(_) => None, + ParChild::Text(_) | ParChild::Quote(_) => Some(true), + ParChild::Node(_) => Some(false), + }) + .unwrap_or_default() + && parent + .flow + .0 + .items() + .rev() + .find_map(|child| match child { + FlowChild::Leading => None, + FlowChild::Parbreak => None, + FlowChild::Node(node) => Some(node.is::()), + FlowChild::Spacing(_) => Some(false), + FlowChild::Colbreak => Some(false), + }) + .unwrap_or_default() + { + children.push_front(ParChild::Spacing(indent.into())); + } + + let node = ParNode(children).pack(); + parent.flow.0.supportive(FlowChild::Node(node), shared); + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +/// Accepts list / enum items, spaces, paragraph breaks. +struct ListBuilder<'a> { + /// The list items collected so far. + items: StyleVecBuilder<'a, ListItem>, + /// Whether the list contains no paragraph breaks. + tight: bool, + /// Trailing content for which it is unclear whether it is part of the list. + staged: Vec<(&'a Content, StyleChain<'a>)>, +} + +impl<'a> ListBuilder<'a> { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { + match content { + Content::Space if !self.items.is_empty() => { + self.staged.push((content, styles)); + } + Content::Parbreak if !self.items.is_empty() => { + self.staged.push((content, styles)); + } + Content::Item(item) + if self + .items + .items() + .next() + .map_or(true, |first| item.kind == first.kind) => + { + self.items.push(item.clone(), styles); + self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak); + } + _ => return false, + } + + true + } + + fn finish(self, parent: &mut Builder<'a, '_>) -> TypResult<()> { + let (items, shared) = self.items.finish(); + let kind = match items.items().next() { + Some(item) => item.kind, None => return Ok(()), }; + let tight = self.tight; let content = match kind { UNORDERED => Content::show(ListNode:: { start: 1, tight, items }), ORDERED | _ => Content::show(ListNode:: { start: 1, tight, items }), }; - let stored = self.tpa.alloc(content); - self.process(ctx, stored, styles)?; - for (content, styles) in staged { - self.process(ctx, content, styles)?; + let stored = parent.scratch.templates.alloc(content); + parent.accept(stored, shared)?; + + for (content, styles) in self.staged { + parent.accept(content, styles)?; } Ok(()) } - /// Finish the currently built page run. - fn finish_page( - &mut self, - ctx: &mut Context, - keep_last: bool, - keep_next: bool, - styles: StyleChain<'a>, - ) -> TypResult<()> { - self.finish_list(ctx)?; - self.finish_par(styles); - if let Some(pages) = &mut self.pages { - let (flow, shared) = std::mem::take(&mut self.flow).finish(); - if !flow.is_empty() || (keep_last && self.keep_next) { - let styles = if flow.is_empty() { styles } else { shared }; - let node = PageNode(FlowNode(flow).pack()); - pages.push(node, styles); - } - } - self.keep_next = keep_next; - Ok(()) - } - - /// Finish everything. - fn finish(&mut self, ctx: &mut Context, styles: StyleChain<'a>) -> TypResult<()> { - self.finish_page(ctx, true, false, styles) + fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +impl Default for ListBuilder<'_> { + fn default() -> Self { + Self { + items: StyleVecBuilder::default(), + tight: true, + staged: vec![], + } } } diff --git a/src/model/styles.rs b/src/model/styles.rs index 7e5bfe944..eb7a70537 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,6 +1,7 @@ use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; +use std::iter; use std::marker::PhantomData; use std::sync::Arc; @@ -9,6 +10,7 @@ use crate::diag::{At, TypResult}; use crate::eval::{Args, Func, Node, Smart, Value}; use crate::geom::{Numeric, Relative, Sides, Spec}; use crate::library::layout::PageNode; +use crate::library::structure::{EnumNode, ListNode}; use crate::library::text::{FontFamily, ParNode, TextNode}; use crate::syntax::Span; use crate::util::{Prehashed, ReadableTypeId}; @@ -62,7 +64,7 @@ impl StyleMap { pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { self.set( TextNode::FAMILY, - std::iter::once(preferred) + iter::once(preferred) .chain(existing.get(TextNode::FAMILY).iter().cloned()) .collect(), ); @@ -281,6 +283,8 @@ impl Property { Some(Interruption::Page) } else if self.is_of::() { Some(Interruption::Par) + } else if self.is_of::() || self.is_of::() { + Some(Interruption::List) } else { None } @@ -503,6 +507,8 @@ impl Debug for Recipe { /// Determines whether a style could interrupt some composable structure. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Interruption { + /// The style forces a list break. + List, /// The style forces a paragraph break. Par, /// The style forces a page break. @@ -721,6 +727,17 @@ impl StyleVec { self.items.len() } + /// Insert an element in the front. The element will share the style of the + /// current first element. + /// + /// This method has no effect if the vector is empty. + pub fn push_front(&mut self, item: T) { + if !self.maps.is_empty() { + self.items.insert(0, item); + self.maps[0].1 += 1; + } + } + /// Iterate over the contained maps. Note that zipping this with `items()` /// does not yield the same result as calling `iter()` because this method /// only returns maps once that are shared by consecutive items. This method @@ -735,24 +752,13 @@ impl StyleVec { self.items.iter() } - /// Iterate over the contained items and associated style maps. + /// Iterate over references to the contained items and associated style maps. pub fn iter(&self) -> impl Iterator + '_ { - let styles = self - .maps - .iter() - .flat_map(|(map, count)| std::iter::repeat(map).take(*count)); - self.items().zip(styles) - } - - /// Insert an element in the front. The element will share the style of the - /// current first element. - /// - /// This method has no effect if the vector is empty. - pub fn push_front(&mut self, item: T) { - if !self.maps.is_empty() { - self.items.insert(0, item); - self.maps[0].1 += 1; - } + self.items().zip( + self.maps + .iter() + .flat_map(|(map, count)| iter::repeat(map).take(*count)), + ) } } @@ -762,6 +768,14 @@ impl Default for StyleVec { } } +impl FromIterator for StyleVec { + fn from_iter>(iter: I) -> Self { + let items: Vec<_> = iter.into_iter().collect(); + let maps = vec![(StyleMap::new(), items.len())]; + Self { items, maps } + } +} + impl Debug for StyleVec { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_list() @@ -787,6 +801,11 @@ impl<'a, T> StyleVecBuilder<'a, T> { 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); @@ -801,13 +820,6 @@ impl<'a, T> StyleVecBuilder<'a, T> { self.chains.push((styles, 1)); } - /// Access the last item mutably and its chain by value. - pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> { - let item = self.items.last_mut()?; - let chain = self.chains.last()?.0; - Some((item, chain)) - } - /// Iterate over the contained items. pub fn items(&self) -> std::slice::Iter<'_, T> { self.items.iter()