mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::library::{self, ORDERED, UNORDERED};
|
use crate::library;
|
||||||
use crate::syntax::ast::*;
|
use crate::syntax::ast::*;
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -180,9 +180,9 @@ impl Eval for ListNode {
|
|||||||
type Output = Template;
|
type Output = Template;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Template::show(library::ListNode::<UNORDERED> {
|
Ok(Template::List(library::ListItem {
|
||||||
number: None,
|
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;
|
type Output = Template;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> TypResult<Self::Output> {
|
||||||
Ok(Template::show(library::ListNode::<ORDERED> {
|
Ok(Template::Enum(library::ListItem {
|
||||||
number: self.number(),
|
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::layout::{Layout, LayoutNode};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind,
|
DecoNode, FlowChild, FlowNode, Labelling, ListItem, ListNode, PageNode, ParChild,
|
||||||
TextNode, UNDERLINE,
|
ParNode, PlaceNode, SpacingKind, TextNode, ORDERED, UNDERLINE, UNORDERED,
|
||||||
};
|
};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -63,6 +63,10 @@ pub enum Template {
|
|||||||
Vertical(SpacingKind),
|
Vertical(SpacingKind),
|
||||||
/// A block-level node.
|
/// A block-level node.
|
||||||
Block(LayoutNode),
|
Block(LayoutNode),
|
||||||
|
/// An item in an unordered list.
|
||||||
|
List(ListItem),
|
||||||
|
/// An item in an ordered list.
|
||||||
|
Enum(ListItem),
|
||||||
/// A page break.
|
/// A page break.
|
||||||
Pagebreak,
|
Pagebreak,
|
||||||
/// A page node.
|
/// A page node.
|
||||||
@ -166,13 +170,13 @@ impl Template {
|
|||||||
|
|
||||||
/// Layout this template into a collection of pages.
|
/// Layout this template into a collection of pages.
|
||||||
pub fn layout(&self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> {
|
pub fn layout(&self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let style_arena = Arena::new();
|
let sya = Arena::new();
|
||||||
let template_arena = Arena::new();
|
let tpa = Arena::new();
|
||||||
|
|
||||||
let mut builder = Builder::new(&style_arena, &template_arena, true);
|
let mut builder = Builder::new(&sya, &tpa, true);
|
||||||
let chain = StyleChain::new(vm.styles);
|
let styles = StyleChain::new(vm.styles);
|
||||||
builder.process(self, vm, chain)?;
|
builder.process(vm, self, styles)?;
|
||||||
builder.finish_page(true, false, chain);
|
builder.finish(vm, styles)?;
|
||||||
|
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
let (pages, shared) = builder.pages.unwrap().finish();
|
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 {
|
impl Add for Template {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
@ -272,12 +239,12 @@ impl Layout for Template {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||||
let style_arena = Arena::new();
|
let sya = Arena::new();
|
||||||
let template_arena = Arena::new();
|
let tpa = Arena::new();
|
||||||
|
|
||||||
let mut builder = Builder::new(&style_arena, &template_arena, false);
|
let mut builder = Builder::new(&sya, &tpa, false);
|
||||||
builder.process(self, vm, styles)?;
|
builder.process(vm, self, styles)?;
|
||||||
builder.finish_par(styles);
|
builder.finish(vm, styles)?;
|
||||||
|
|
||||||
let (flow, shared) = builder.flow.finish();
|
let (flow, shared) = builder.flow.finish();
|
||||||
FlowNode(flow).layout(vm, regions, shared)
|
FlowNode(flow).layout(vm, regions, shared)
|
||||||
@ -294,11 +261,13 @@ impl Layout for Template {
|
|||||||
/// Builds a flow or page nodes from a template.
|
/// Builds a flow or page nodes from a template.
|
||||||
struct Builder<'a> {
|
struct Builder<'a> {
|
||||||
/// An arena where intermediate style chains are stored.
|
/// 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.
|
/// An arena where intermediate templates are stored.
|
||||||
template_arena: &'a Arena<Template>,
|
tpa: &'a Arena<Template>,
|
||||||
/// The already built page runs.
|
/// The already built page runs.
|
||||||
pages: Option<StyleVecBuilder<'a, PageNode>>,
|
pages: Option<StyleVecBuilder<'a, PageNode>>,
|
||||||
|
/// The currently built list.
|
||||||
|
list: Option<ListBuilder<'a>>,
|
||||||
/// The currently built flow.
|
/// The currently built flow.
|
||||||
flow: CollapsingBuilder<'a, FlowChild>,
|
flow: CollapsingBuilder<'a, FlowChild>,
|
||||||
/// The currently built paragraph.
|
/// The currently built paragraph.
|
||||||
@ -309,16 +278,13 @@ struct Builder<'a> {
|
|||||||
|
|
||||||
impl<'a> Builder<'a> {
|
impl<'a> Builder<'a> {
|
||||||
/// Prepare the builder.
|
/// Prepare the builder.
|
||||||
fn new(
|
fn new(sya: &'a Arena<StyleChain<'a>>, tpa: &'a Arena<Template>, top: bool) -> Self {
|
||||||
style_arena: &'a Arena<StyleChain<'a>>,
|
|
||||||
template_arena: &'a Arena<Template>,
|
|
||||||
top: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
style_arena,
|
sya,
|
||||||
template_arena,
|
tpa,
|
||||||
pages: top.then(|| StyleVecBuilder::new()),
|
pages: top.then(|| StyleVecBuilder::new()),
|
||||||
flow: CollapsingBuilder::new(),
|
flow: CollapsingBuilder::new(),
|
||||||
|
list: None,
|
||||||
par: CollapsingBuilder::new(),
|
par: CollapsingBuilder::new(),
|
||||||
keep_next: true,
|
keep_next: true,
|
||||||
}
|
}
|
||||||
@ -327,10 +293,38 @@ impl<'a> Builder<'a> {
|
|||||||
/// Process a template.
|
/// Process a template.
|
||||||
fn process(
|
fn process(
|
||||||
&mut self,
|
&mut self,
|
||||||
template: &'a Template,
|
|
||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
|
template: &'a Template,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> TypResult<()> {
|
) -> 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 {
|
match template {
|
||||||
Template::Space => {
|
Template::Space => {
|
||||||
self.par.weak(ParChild::Text(' '.into()), 0, styles);
|
self.par.weak(ParChild::Text(' '.into()), 0, styles);
|
||||||
@ -379,6 +373,24 @@ impl<'a> Builder<'a> {
|
|||||||
}
|
}
|
||||||
self.finish_par(styles);
|
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 => {
|
Template::Pagebreak => {
|
||||||
self.finish_page(true, true, styles);
|
self.finish_page(true, true, styles);
|
||||||
}
|
}
|
||||||
@ -390,12 +402,12 @@ impl<'a> Builder<'a> {
|
|||||||
}
|
}
|
||||||
Template::Show(node) => {
|
Template::Show(node) => {
|
||||||
let template = node.show(vm, styles)?;
|
let template = node.show(vm, styles)?;
|
||||||
let stored = self.template_arena.alloc(template);
|
let stored = self.tpa.alloc(template);
|
||||||
self.process(stored, vm, styles.unscoped(node.id()))?;
|
self.process(vm, stored, styles.unscoped(node.id()))?;
|
||||||
}
|
}
|
||||||
Template::Styled(styled) => {
|
Template::Styled(styled) => {
|
||||||
let (sub, map) = styled.as_ref();
|
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 styles = map.chain(stored);
|
||||||
|
|
||||||
let interruption = map.interruption();
|
let interruption = map.interruption();
|
||||||
@ -405,7 +417,7 @@ impl<'a> Builder<'a> {
|
|||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.process(sub, vm, styles)?;
|
self.process(vm, sub, styles)?;
|
||||||
|
|
||||||
match interruption {
|
match interruption {
|
||||||
Some(Interruption::Page) => self.finish_page(true, false, styles),
|
Some(Interruption::Page) => self.finish_page(true, false, styles),
|
||||||
@ -415,7 +427,7 @@ impl<'a> Builder<'a> {
|
|||||||
}
|
}
|
||||||
Template::Sequence(seq) => {
|
Template::Sequence(seq) => {
|
||||||
for sub in seq.iter() {
|
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);
|
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.
|
/// Finish the currently built page run.
|
||||||
fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) {
|
fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) {
|
||||||
self.finish_par(styles);
|
self.finish_par(styles);
|
||||||
@ -446,4 +480,68 @@ impl<'a> Builder<'a> {
|
|||||||
}
|
}
|
||||||
self.keep_next = keep_next;
|
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.
|
//! Unordered (bulleted) and ordered (numbered) lists.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{GridNode, TextNode, TrackSizing};
|
use super::{GridNode, ParNode, TextNode, TrackSizing};
|
||||||
|
|
||||||
/// An unordered or ordered list.
|
/// An unordered or ordered list.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ListNode<const L: Labelling> {
|
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.
|
/// The number of the item.
|
||||||
pub number: Option<usize>,
|
pub number: Option<usize>,
|
||||||
/// The node that produces the item's body.
|
/// The node that produces the item's body.
|
||||||
pub child: LayoutNode,
|
pub body: LayoutNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[class]
|
#[class]
|
||||||
@ -18,28 +30,54 @@ impl<const L: Labelling> ListNode<L> {
|
|||||||
pub const LABEL_INDENT: Linear = Relative::new(0.0).into();
|
pub const LABEL_INDENT: Linear = Relative::new(0.0).into();
|
||||||
/// The space between the label and the body of each item.
|
/// The space between the label and the body of each item.
|
||||||
pub const BODY_INDENT: Linear = Relative::new(0.5).into();
|
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> {
|
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
|
||||||
Ok(args
|
Ok(Template::show(Self {
|
||||||
|
items: args
|
||||||
.all()?
|
.all()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.map(|body| ListItem { number: None, body })
|
||||||
.map(|(i, child)| Template::show(Self { number: Some(1 + i), child }))
|
.collect(),
|
||||||
.sum())
|
wide: args.named("wide")?.unwrap_or(false),
|
||||||
|
start: args.named("start")?.unwrap_or(0),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: Labelling> Show for ListNode<L> {
|
impl<const L: Labelling> Show for ListNode<L> {
|
||||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||||
let em = styles.get(TextNode::SIZE).abs;
|
let mut children = vec![];
|
||||||
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
|
let mut number = self.start;
|
||||||
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
|
|
||||||
|
for item in &self.items {
|
||||||
|
number = item.number.unwrap_or(number);
|
||||||
|
|
||||||
let label = match L {
|
let label = match L {
|
||||||
UNORDERED => '•'.into(),
|
UNORDERED => '•'.into(),
|
||||||
ORDERED | _ => format_eco!("{}.", self.number.unwrap_or(1)),
|
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 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 {
|
Ok(Template::block(GridNode {
|
||||||
tracks: Spec::with_x(vec![
|
tracks: Spec::with_x(vec![
|
||||||
TrackSizing::Linear(label_indent.into()),
|
TrackSizing::Linear(label_indent.into()),
|
||||||
@ -47,17 +85,18 @@ impl<const L: Labelling> Show for ListNode<L> {
|
|||||||
TrackSizing::Linear(body_indent.into()),
|
TrackSizing::Linear(body_indent.into()),
|
||||||
TrackSizing::Auto,
|
TrackSizing::Auto,
|
||||||
]),
|
]),
|
||||||
gutter: Spec::default(),
|
gutter: Spec::with_y(vec![TrackSizing::Linear(gutter.into())]),
|
||||||
children: vec![
|
children,
|
||||||
LayoutNode::default(),
|
|
||||||
Template::Text(label).pack(),
|
|
||||||
LayoutNode::default(),
|
|
||||||
self.child.clone(),
|
|
||||||
],
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// How to label a list.
|
||||||
pub type Labelling = usize;
|
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.
|
// Test enums.
|
||||||
|
|
||||||
---
|
---
|
||||||
1. Embrace
|
. Embrace
|
||||||
2. Extend
|
. Extend
|
||||||
3. Extinguish
|
. Extinguish
|
||||||
|
|
||||||
---
|
---
|
||||||
1. First.
|
1. First.
|
||||||
@ -13,5 +13,11 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
2. Second
|
2. Second
|
||||||
. First
|
1. First
|
||||||
. Indented
|
. Indented
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test automatic numbering in summed templates.
|
||||||
|
#for i in range(5) {
|
||||||
|
[. #roman(1 + i)]
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user