mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Automatic list numbering
This commit is contained in:
parent
261f387535
commit
980f898d55
@ -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(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@ -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)]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user