summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-17 18:56:50 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-17 23:00:30 +0100
commit980f898d553bec35bd94171d47fd86cb13e39b23 (patch)
tree50f5b983b3cf88b0e19a8766b9c3735612d2db36 /src
parent261f387535ebe3b784d69893027d8edac01e1ba9 (diff)
Automatic list numbering
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs10
-rw-r--r--src/eval/template.rs228
-rw-r--r--src/library/list.rs77
3 files changed, 226 insertions, 89 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;