summaryrefslogtreecommitdiff
path: root/src/model/content.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-26 13:25:14 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-26 13:26:31 +0200
commitf7c67cde72e6a67f45180856b332bae9863243bd (patch)
tree09b1a40e17b2f8aa0c3661ac989a40840cea1f12 /src/model/content.rs
parent09aabc3a21e403e0b09a6d6ba517e34a303b217c (diff)
New document & flow building
Diffstat (limited to 'src/model/content.rs')
-rw-r--r--src/model/content.rs605
1 files changed, 346 insertions, 259 deletions
diff --git a/src/model/content.rs b/src/model/content.rs
index a7eb906a..6e1e2f1c 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<Vec<Arc<Frame>>> {
- let sya = Arena::new();
- let tpa = Arena::new();
+ let copy = ctx.styles.clone();
+ let styles = StyleChain::with_root(&copy);
+ 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<Vec<Arc<Frame>>> {
- 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> {
- /// An arena where intermediate style chains are stored.
- sya: &'a Arena<StyleChain<'a>>,
- /// An arena where intermediate content resulting from show rules is stored.
- tpa: &'a Arena<Content>,
- /// The already built page runs.
- pages: Option<StyleVecBuilder<'a, PageNode>>,
- /// The currently built list.
- list: Option<ListBuilder<'a>>,
- /// 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,
+/// 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<DocBuilder<'a>>,
+ /// The current flow building state.
+ flow: FlowBuilder<'a>,
+ /// The current paragraph building state.
+ par: ParBuilder<'a>,
+ /// The current list building state.
+ list: ListBuilder<'a>,
}
-/// Builds an unordered or ordered list from items.
-struct ListBuilder<'a> {
- styles: StyleChain<'a>,
- kind: ListKind,
- items: Vec<ListItem>,
- tight: bool,
- staged: Vec<(&'a Content, StyleChain<'a>)>,
+/// Temporary storage arenas for building.
+#[derive(Default)]
+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.
+ templates: Arena<Content>,
}
-impl<'a> Builder<'a> {
- /// Prepare the builder.
- fn new(sya: &'a Arena<StyleChain<'a>>, tpa: &'a Arena<Content>, 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(
+ fn into_doc(
+ mut self,
+ styles: StyleChain<'a>,
+ ) -> 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),
+ _ => {}
+ }
+
+ if self.list.accept(content, styles) {
+ return Ok(());
+ }
+
+ self.interrupt(Interruption::List, styles, false)?;
+
+ if self.par.accept(content, styles) {
+ return Ok(());
+ }
+
+ 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,
- ctx: &mut Context,
- content: &'a Content,
+ (content, map): &'a (Content, StyleMap),
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)?,
- }
+ 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)?;
}
- 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);
+ 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::Horizontal(kind) => {
- let child = ParChild::Spacing(*kind);
- if kind.is_fractional() {
- self.par.destructive(child, styles);
- } else {
- self.par.ignorant(child, 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;
}
- Content::Quote(double) => {
- self.par.supportive(ParChild::Quote(*double), styles);
- }
- Content::Text(text) => {
- self.par.supportive(ParChild::Text(text.clone()), styles);
+ }
+
+ Ok(())
+ }
+
+ fn sequence(&mut self, seq: &'a [Content], styles: StyleChain<'a>) -> TypResult<()> {
+ for content in seq {
+ self.accept(content, 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::Inline(node) => {
- self.par.supportive(ParChild::Node(node.clone()), styles);
+ 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.finish_par(styles);
- self.flow.weak(FlowChild::Parbreak, 1, styles);
+ self.0.weak(FlowChild::Parbreak, 1, styles);
}
Content::Colbreak => {
- self.finish_par(styles);
- self.flow.destructive(FlowChild::Colbreak, styles);
+ self.0.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);
+ self.0.destructive(child, styles);
} else {
- self.flow.ignorant(child, styles);
+ self.0.ignorant(child, styles);
}
}
Content::Block(node) => {
- self.finish_par(styles);
let child = FlowChild::Node(node.clone());
if node.is::<PlaceNode>() {
- self.flow.ignorant(child, styles);
+ self.0.ignorant(child, styles);
} else {
- self.flow.supportive(child, styles);
+ self.0.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![],
- });
+ _ => return false,
+ }
+
+ true
+ }
+
+ 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::Pagebreak(soft) => {
- self.finish_page(ctx, !soft, !soft, styles)?;
+ Content::Linebreak(justified) => {
+ let c = if *justified { '\u{2028}' } else { '\n' };
+ self.0.destructive(ParChild::Text(c.into()), styles);
}
- Content::Page(page) => {
- self.finish_page(ctx, false, false, styles)?;
- if let Some(pages) = &mut self.pages {
- pages.push(page.clone(), 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::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::Quote(double) => {
+ self.0.supportive(ParChild::Quote(*double), styles);
}
- Content::Styled(styled) => {
- let (sub, map) = styled.as_ref();
- let stored = self.sya.alloc(styles);
- let styles = map.chain(stored);
-
- 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.process(ctx, sub, styles)?;
-
- match interruption {
- Some(Interruption::Page) => {
- self.finish_page(ctx, true, false, styles)?
- }
- Some(Interruption::Par) => self.finish_par(styles),
- None => {}
- }
+ Content::Text(text) => {
+ self.0.supportive(ParChild::Text(text.clone()), styles);
}
- Content::Sequence(seq) => {
- for sub in seq.iter() {
- self.process(ctx, sub, styles)?;
- }
+ Content::Inline(node) => {
+ self.0.supportive(ParChild::Node(node.clone()), styles);
}
+ _ => return false,
}
- Ok(())
+ true
}
- /// 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
+ 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::<ParNode>()),
+ 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()
- .rev()
- .find_map(|child| match child {
- FlowChild::Leading => None,
- FlowChild::Parbreak => None,
- FlowChild::Node(node) => Some(node.is::<ParNode>()),
- FlowChild::Spacing(_) => Some(false),
- FlowChild::Colbreak => Some(false),
- })
- .unwrap_or_default()
+ .next()
+ .map_or(true, |first| item.kind == first.kind) =>
{
- par.push_front(ParChild::Spacing(indent.into()));
+ self.items.push(item.clone(), styles);
+ self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak);
}
-
- let node = ParNode(par).pack();
- self.flow.supportive(FlowChild::Node(node), shared);
+ _ => return false,
}
- self.flow.weak(FlowChild::Leading, 0, styles);
+
+ 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, 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::<UNORDERED> { start: 1, tight, items }),
ORDERED | _ => Content::show(ListNode::<ORDERED> { 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(())
+ fn is_empty(&self) -> bool {
+ self.items.is_empty()
}
+}
- /// Finish everything.
- fn finish(&mut self, ctx: &mut Context, styles: StyleChain<'a>) -> TypResult<()> {
- self.finish_page(ctx, true, false, styles)
+impl Default for ListBuilder<'_> {
+ fn default() -> Self {
+ Self {
+ items: StyleVecBuilder::default(),
+ tight: true,
+ staged: vec![],
+ }
}
}