diff options
| -rw-r--r-- | src/eval/mod.rs | 10 | ||||
| -rw-r--r-- | src/eval/template.rs | 228 | ||||
| -rw-r--r-- | src/library/list.rs | 77 | ||||
| -rw-r--r-- | tests/ref/markup/enums.png | bin | 6075 -> 7427 bytes | |||
| -rw-r--r-- | tests/ref/markup/lists.png | bin | 21378 -> 21396 bytes | |||
| -rw-r--r-- | tests/typ/markup/enums.typ | 14 |
6 files changed, 236 insertions, 93 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 98f84082..32ffb0c9 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -37,7 +37,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; use crate::layout::Layout; -use crate::library::{self, ORDERED, UNORDERED}; +use crate::library; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -180,9 +180,9 @@ impl Eval for ListNode { type Output = Template; fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> { - Ok(Template::show(library::ListNode::<UNORDERED> { + Ok(Template::List(library::ListItem { number: None, - child: self.body().eval(vm)?.pack(), + body: self.body().eval(vm)?.pack(), })) } } @@ -191,9 +191,9 @@ impl Eval for EnumNode { type Output = Template; fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> { - Ok(Template::show(library::ListNode::<ORDERED> { + Ok(Template::Enum(library::ListItem { number: self.number(), - child: self.body().eval(vm)?.pack(), + body: self.body().eval(vm)?.pack(), })) } } diff --git a/src/eval/template.rs b/src/eval/template.rs index b1fe07fe..0db63535 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -13,8 +13,8 @@ use crate::diag::StrResult; use crate::layout::{Layout, LayoutNode}; use crate::library::prelude::*; use crate::library::{ - DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, - TextNode, UNDERLINE, + DecoNode, FlowChild, FlowNode, Labelling, ListItem, ListNode, PageNode, ParChild, + ParNode, PlaceNode, SpacingKind, TextNode, ORDERED, UNDERLINE, UNORDERED, }; use crate::util::EcoString; @@ -63,6 +63,10 @@ pub enum Template { Vertical(SpacingKind), /// A block-level node. Block(LayoutNode), + /// An item in an unordered list. + List(ListItem), + /// An item in an ordered list. + Enum(ListItem), /// A page break. Pagebreak, /// A page node. @@ -166,13 +170,13 @@ impl Template { /// Layout this template into a collection of pages. pub fn layout(&self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> { - let style_arena = Arena::new(); - let template_arena = Arena::new(); + let sya = Arena::new(); + let tpa = Arena::new(); - let mut builder = Builder::new(&style_arena, &template_arena, true); - let chain = StyleChain::new(vm.styles); - builder.process(self, vm, chain)?; - builder.finish_page(true, false, chain); + let mut builder = Builder::new(&sya, &tpa, true); + let styles = StyleChain::new(vm.styles); + builder.process(vm, self, styles)?; + builder.finish(vm, styles)?; let mut frames = vec![]; let (pages, shared) = builder.pages.unwrap().finish(); @@ -190,43 +194,6 @@ impl Default for Template { } } -impl Debug for Template { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Space => f.pad("Space"), - Self::Linebreak => f.pad("Linebreak"), - Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"), - Self::Text(text) => write!(f, "Text({text:?})"), - Self::Inline(node) => { - f.write_str("Inline(")?; - node.fmt(f)?; - f.write_str(")") - } - Self::Parbreak => f.pad("Parbreak"), - Self::Colbreak => f.pad("Colbreak"), - Self::Vertical(kind) => write!(f, "Vertical({kind:?})"), - Self::Block(node) => { - f.write_str("Block(")?; - node.fmt(f)?; - f.write_str(")") - } - Self::Pagebreak => f.pad("Pagebreak"), - Self::Page(page) => page.fmt(f), - Self::Show(node) => { - f.write_str("Show(")?; - node.fmt(f)?; - f.write_str(")") - } - Self::Styled(styled) => { - let (sub, map) = styled.as_ref(); - map.fmt(f)?; - sub.fmt(f) - } - Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(), - } - } -} - impl Add for Template { type Output = Self; @@ -272,12 +239,12 @@ impl Layout for Template { regions: &Regions, styles: StyleChain, ) -> TypResult<Vec<Constrained<Arc<Frame>>>> { - let style_arena = Arena::new(); - let template_arena = Arena::new(); + let sya = Arena::new(); + let tpa = Arena::new(); - let mut builder = Builder::new(&style_arena, &template_arena, false); - builder.process(self, vm, styles)?; - builder.finish_par(styles); + let mut builder = Builder::new(&sya, &tpa, false); + builder.process(vm, self, styles)?; + builder.finish(vm, styles)?; let (flow, shared) = builder.flow.finish(); FlowNode(flow).layout(vm, regions, shared) @@ -294,11 +261,13 @@ impl Layout for Template { /// Builds a flow or page nodes from a template. struct Builder<'a> { /// An arena where intermediate style chains are stored. - style_arena: &'a Arena<StyleChain<'a>>, + sya: &'a Arena<StyleChain<'a>>, /// An arena where intermediate templates are stored. - template_arena: &'a Arena<Template>, + tpa: &'a Arena<Template>, /// 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. @@ -309,16 +278,13 @@ struct Builder<'a> { impl<'a> Builder<'a> { /// Prepare the builder. - fn new( - style_arena: &'a Arena<StyleChain<'a>>, - template_arena: &'a Arena<Template>, - top: bool, - ) -> Self { + fn new(sya: &'a Arena<StyleChain<'a>>, tpa: &'a Arena<Template>, top: bool) -> Self { Self { - style_arena, - template_arena, + sya, + tpa, pages: top.then(|| StyleVecBuilder::new()), flow: CollapsingBuilder::new(), + list: None, par: CollapsingBuilder::new(), keep_next: true, } @@ -327,10 +293,38 @@ impl<'a> Builder<'a> { /// Process a template. fn process( &mut self, - template: &'a Template, vm: &mut Vm, + template: &'a Template, styles: StyleChain<'a>, ) -> TypResult<()> { + if let Some(builder) = &mut self.list { + match template { + Template::Space => { + builder.staged.push((template, styles)); + return Ok(()); + } + Template::Parbreak => { + builder.staged.push((template, styles)); + return Ok(()); + } + Template::List(item) if builder.labelling == UNORDERED => { + builder.wide |= + builder.staged.iter().any(|&(t, _)| *t == Template::Parbreak); + builder.staged.clear(); + builder.items.push(item.clone()); + return Ok(()); + } + Template::Enum(item) if builder.labelling == ORDERED => { + builder.wide |= + builder.staged.iter().any(|&(t, _)| *t == Template::Parbreak); + builder.staged.clear(); + builder.items.push(item.clone()); + return Ok(()); + } + _ => self.finish_list(vm)?, + } + } + match template { Template::Space => { self.par.weak(ParChild::Text(' '.into()), 0, styles); @@ -379,6 +373,24 @@ impl<'a> Builder<'a> { } self.finish_par(styles); } + Template::List(item) => { + self.list = Some(ListBuilder { + styles, + labelling: UNORDERED, + items: vec![item.clone()], + wide: false, + staged: vec![], + }); + } + Template::Enum(item) => { + self.list = Some(ListBuilder { + styles, + labelling: ORDERED, + items: vec![item.clone()], + wide: false, + staged: vec![], + }); + } Template::Pagebreak => { self.finish_page(true, true, styles); } @@ -390,12 +402,12 @@ impl<'a> Builder<'a> { } Template::Show(node) => { let template = node.show(vm, styles)?; - let stored = self.template_arena.alloc(template); - self.process(stored, vm, styles.unscoped(node.id()))?; + let stored = self.tpa.alloc(template); + self.process(vm, stored, styles.unscoped(node.id()))?; } Template::Styled(styled) => { let (sub, map) = styled.as_ref(); - let stored = self.style_arena.alloc(styles); + let stored = self.sya.alloc(styles); let styles = map.chain(stored); let interruption = map.interruption(); @@ -405,7 +417,7 @@ impl<'a> Builder<'a> { None => {} } - self.process(sub, vm, styles)?; + self.process(vm, sub, styles)?; match interruption { Some(Interruption::Page) => self.finish_page(true, false, styles), @@ -415,7 +427,7 @@ impl<'a> Builder<'a> { } Template::Sequence(seq) => { for sub in seq.iter() { - self.process(sub, vm, styles)?; + self.process(vm, sub, styles)?; } } } @@ -433,6 +445,28 @@ impl<'a> Builder<'a> { self.flow.weak(FlowChild::Leading, 0, styles); } + /// Finish the currently built list. + fn finish_list(&mut self, vm: &mut Vm) -> TypResult<()> { + let ListBuilder { styles, labelling, items, wide, staged } = + match self.list.take() { + Some(list) => list, + None => return Ok(()), + }; + + let template = match labelling { + UNORDERED => Template::show(ListNode::<UNORDERED> { items, wide, start: 1 }), + ORDERED | _ => Template::show(ListNode::<ORDERED> { items, wide, start: 1 }), + }; + + let stored = self.tpa.alloc(template); + self.process(vm, stored, styles)?; + for (template, styles) in staged { + self.process(vm, template, styles)?; + } + + Ok(()) + } + /// Finish the currently built page run. fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) { self.finish_par(styles); @@ -446,4 +480,68 @@ impl<'a> Builder<'a> { } self.keep_next = keep_next; } + + /// Finish everything. + fn finish(&mut self, vm: &mut Vm, styles: StyleChain<'a>) -> TypResult<()> { + self.finish_list(vm)?; + self.finish_page(true, false, styles); + Ok(()) + } +} + +/// Builds an unordered or ordered list from items. +struct ListBuilder<'a> { + styles: StyleChain<'a>, + labelling: Labelling, + items: Vec<ListItem>, + wide: bool, + staged: Vec<(&'a Template, StyleChain<'a>)>, +} + +impl Debug for Template { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Space => f.pad("Space"), + Self::Linebreak => f.pad("Linebreak"), + Self::Horizontal(kind) => write!(f, "Horizontal({kind:?})"), + Self::Text(text) => write!(f, "Text({text:?})"), + Self::Inline(node) => { + f.write_str("Inline(")?; + node.fmt(f)?; + f.write_str(")") + } + Self::Parbreak => f.pad("Parbreak"), + Self::Colbreak => f.pad("Colbreak"), + Self::Vertical(kind) => write!(f, "Vertical({kind:?})"), + Self::Block(node) => { + f.write_str("Block(")?; + node.fmt(f)?; + f.write_str(")") + } + 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::Pagebreak => f.pad("Pagebreak"), + Self::Page(page) => page.fmt(f), + Self::Show(node) => { + f.write_str("Show(")?; + node.fmt(f)?; + f.write_str(")") + } + Self::Styled(styled) => { + let (sub, map) = styled.as_ref(); + map.fmt(f)?; + sub.fmt(f) + } + Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(), + } + } } diff --git a/src/library/list.rs b/src/library/list.rs index 08ccfc18..1ec89da2 100644 --- a/src/library/list.rs +++ b/src/library/list.rs @@ -1,15 +1,27 @@ //! Unordered (bulleted) and ordered (numbered) lists. use super::prelude::*; -use super::{GridNode, TextNode, TrackSizing}; +use super::{GridNode, ParNode, TextNode, TrackSizing}; /// An unordered or ordered list. #[derive(Debug, Hash)] pub struct ListNode<const L: Labelling> { + /// The individual bulleted or numbered items. + pub items: Vec<ListItem>, + /// If true, there is paragraph spacing between the items, if false + /// there is list spacing between the items. + pub wide: bool, + /// Where the list starts. + pub start: usize, +} + +/// An item in a list. +#[derive(Debug, Clone, PartialEq, Hash)] +pub struct ListItem { /// The number of the item. pub number: Option<usize>, /// The node that produces the item's body. - pub child: LayoutNode, + pub body: LayoutNode, } #[class] @@ -18,28 +30,54 @@ impl<const L: Labelling> ListNode<L> { pub const LABEL_INDENT: Linear = Relative::new(0.0).into(); /// The space between the label and the body of each item. pub const BODY_INDENT: Linear = Relative::new(0.5).into(); + /// The spacing between the list items of a non-wide list. + pub const SPACING: Linear = Linear::zero(); fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { - Ok(args - .all()? - .into_iter() - .enumerate() - .map(|(i, child)| Template::show(Self { number: Some(1 + i), child })) - .sum()) + Ok(Template::show(Self { + items: args + .all()? + .into_iter() + .map(|body| ListItem { number: None, body }) + .collect(), + wide: args.named("wide")?.unwrap_or(false), + start: args.named("start")?.unwrap_or(0), + })) } } impl<const L: Labelling> Show for ListNode<L> { fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> { + let mut children = vec![]; + let mut number = self.start; + + for item in &self.items { + number = item.number.unwrap_or(number); + + let label = match L { + UNORDERED => '•'.into(), + ORDERED | _ => format_eco!("{}.", number), + }; + + children.push(LayoutNode::default()); + children.push(Template::Text(label).pack()); + children.push(LayoutNode::default()); + children.push(item.body.clone()); + + number += 1; + } + let em = styles.get(TextNode::SIZE).abs; let label_indent = styles.get(Self::LABEL_INDENT).resolve(em); let body_indent = styles.get(Self::BODY_INDENT).resolve(em); - - let label = match L { - UNORDERED => '•'.into(), - ORDERED | _ => format_eco!("{}.", self.number.unwrap_or(1)), + let leading = styles.get(ParNode::LEADING); + let spacing = if self.wide { + styles.get(ParNode::SPACING) + } else { + styles.get(Self::SPACING) }; + let gutter = (leading + spacing).resolve(em); Ok(Template::block(GridNode { tracks: Spec::with_x(vec![ TrackSizing::Linear(label_indent.into()), @@ -47,17 +85,18 @@ impl<const L: Labelling> Show for ListNode<L> { TrackSizing::Linear(body_indent.into()), TrackSizing::Auto, ]), - gutter: Spec::default(), - children: vec![ - LayoutNode::default(), - Template::Text(label).pack(), - LayoutNode::default(), - self.child.clone(), - ], + gutter: Spec::with_y(vec![TrackSizing::Linear(gutter.into())]), + children, })) } } +impl<const L: Labelling> From<ListItem> for ListNode<L> { + fn from(item: ListItem) -> Self { + Self { items: vec![item], wide: false, start: 1 } + } +} + /// How to label a list. pub type Labelling = usize; diff --git a/tests/ref/markup/enums.png b/tests/ref/markup/enums.png Binary files differindex 3878f32c..04d6ee4d 100644 --- a/tests/ref/markup/enums.png +++ b/tests/ref/markup/enums.png diff --git a/tests/ref/markup/lists.png b/tests/ref/markup/lists.png Binary files differindex 618e1191..503bfc9a 100644 --- a/tests/ref/markup/lists.png +++ b/tests/ref/markup/lists.png diff --git a/tests/typ/markup/enums.typ b/tests/typ/markup/enums.typ index 93a175d4..aeff9f2c 100644 --- a/tests/typ/markup/enums.typ +++ b/tests/typ/markup/enums.typ @@ -1,9 +1,9 @@ // Test enums. --- -1. Embrace -2. Extend -3. Extinguish +. Embrace +. Extend +. Extinguish --- 1. First. @@ -13,5 +13,11 @@ --- 2. Second - . First +1. First . Indented + +--- +// Test automatic numbering in summed templates. +#for i in range(5) { + [. #roman(1 + i)] +} |
