mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
272 lines
7.7 KiB
Rust
272 lines
7.7 KiB
Rust
use std::str::FromStr;
|
|
|
|
use smallvec::SmallVec;
|
|
|
|
use crate::diag::{bail, SourceResult};
|
|
use crate::engine::Engine;
|
|
use crate::foundations::{
|
|
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
|
Styles,
|
|
};
|
|
use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
|
|
use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem};
|
|
|
|
/// A numbered list.
|
|
///
|
|
/// Displays a sequence of items vertically and numbers them consecutively.
|
|
///
|
|
/// # Example
|
|
/// ```example
|
|
/// Automatically numbered:
|
|
/// + Preparations
|
|
/// + Analysis
|
|
/// + Conclusions
|
|
///
|
|
/// Manually numbered:
|
|
/// 2. What is the first step?
|
|
/// 5. I am confused.
|
|
/// + Moving on ...
|
|
///
|
|
/// Multiple lines:
|
|
/// + This enum item has multiple
|
|
/// lines because the next line
|
|
/// is indented.
|
|
///
|
|
/// Function call.
|
|
/// #enum[First][Second]
|
|
/// ```
|
|
///
|
|
/// You can easily switch all your enumerations to a different numbering style
|
|
/// with a set rule.
|
|
/// ```example
|
|
/// #set enum(numbering: "a)")
|
|
///
|
|
/// + Starting off ...
|
|
/// + Don't forget step two
|
|
/// ```
|
|
///
|
|
/// You can also use [`enum.item`]($enum.item) to programmatically customize the
|
|
/// number of each item in the enumeration:
|
|
///
|
|
/// ```example
|
|
/// #enum(
|
|
/// enum.item(1)[First step],
|
|
/// enum.item(5)[Fifth step],
|
|
/// enum.item(10)[Tenth step]
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// # Syntax
|
|
/// This functions also has dedicated syntax:
|
|
///
|
|
/// - Starting a line with a plus sign creates an automatically numbered
|
|
/// enumeration item.
|
|
/// - Starting a line with a number followed by a dot creates an explicitly
|
|
/// numbered enumeration item.
|
|
///
|
|
/// Enumeration items can contain multiple paragraphs and other block-level
|
|
/// content. All content that is indented more than an item's marker becomes
|
|
/// part of that item.
|
|
#[elem(scope, title = "Numbered List", Show)]
|
|
pub struct EnumElem {
|
|
/// Defines the default [spacing]($enum.spacing) of the enumeration. If it
|
|
/// is `{false}`, the items are spaced apart with
|
|
/// [paragraph spacing]($par.spacing). If it is `{true}`, they use
|
|
/// [paragraph leading]($par.leading) instead. This makes the list more
|
|
/// compact, which can look better if the items are short.
|
|
///
|
|
/// In markup mode, the value of this parameter is determined based on
|
|
/// whether items are separated with a blank line. If items directly follow
|
|
/// each other, this is set to `{true}`; if items are separated by a blank
|
|
/// line, this is set to `{false}`. The markup-defined tightness cannot be
|
|
/// overridden with set rules.
|
|
///
|
|
/// ```example
|
|
/// + If an enum has a lot of text, and
|
|
/// maybe other inline content, it
|
|
/// should not be tight anymore.
|
|
///
|
|
/// + To make an enum wide, simply
|
|
/// insert a blank line between the
|
|
/// items.
|
|
/// ```
|
|
#[default(true)]
|
|
pub tight: bool,
|
|
|
|
/// How to number the enumeration. Accepts a
|
|
/// [numbering pattern or function]($numbering).
|
|
///
|
|
/// If the numbering pattern contains multiple counting symbols, they apply
|
|
/// to nested enums. If given a function, the function receives one argument
|
|
/// if `full` is `{false}` and multiple arguments if `full` is `{true}`.
|
|
///
|
|
/// ```example
|
|
/// #set enum(numbering: "1.a)")
|
|
/// + Different
|
|
/// + Numbering
|
|
/// + Nested
|
|
/// + Items
|
|
/// + Style
|
|
///
|
|
/// #set enum(numbering: n => super[#n])
|
|
/// + Superscript
|
|
/// + Numbering!
|
|
/// ```
|
|
#[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
|
|
#[borrowed]
|
|
pub numbering: Numbering,
|
|
|
|
/// Which number to start the enumeration with.
|
|
///
|
|
/// ```example
|
|
/// #enum(
|
|
/// start: 3,
|
|
/// [Skipping],
|
|
/// [Ahead],
|
|
/// )
|
|
/// ```
|
|
#[default(1)]
|
|
pub start: usize,
|
|
|
|
/// Whether to display the full numbering, including the numbers of
|
|
/// all parent enumerations.
|
|
///
|
|
///
|
|
/// ```example
|
|
/// #set enum(numbering: "1.a)", full: true)
|
|
/// + Cook
|
|
/// + Heat water
|
|
/// + Add ingredients
|
|
/// + Eat
|
|
/// ```
|
|
#[default(false)]
|
|
pub full: bool,
|
|
|
|
/// The indentation of each item.
|
|
#[resolve]
|
|
pub indent: Length,
|
|
|
|
/// The space between the numbering and the body of each item.
|
|
#[resolve]
|
|
#[default(Em::new(0.5).into())]
|
|
pub body_indent: Length,
|
|
|
|
/// The spacing between the items of the enumeration.
|
|
///
|
|
/// If set to `{auto}`, uses paragraph [`leading`]($par.leading) for tight
|
|
/// enumerations and paragraph [`spacing`]($par.spacing) for wide
|
|
/// (non-tight) enumerations.
|
|
pub spacing: Smart<Length>,
|
|
|
|
/// The alignment that enum numbers should have.
|
|
///
|
|
/// By default, this is set to `{end + top}`, which aligns enum numbers
|
|
/// towards end of the current text direction (in left-to-right script,
|
|
/// for example, this is the same as `{right}`) and at the top of the line.
|
|
/// The choice of `{end}` for horizontal alignment of enum numbers is
|
|
/// usually preferred over `{start}`, as numbers then grow away from the
|
|
/// text instead of towards it, avoiding certain visual issues. This option
|
|
/// lets you override this behaviour, however. (Also to note is that the
|
|
/// [unordered list]($list) uses a different method for this, by giving the
|
|
/// `marker` content an alignment directly.).
|
|
///
|
|
/// ````example
|
|
/// #set enum(number-align: start + bottom)
|
|
///
|
|
/// Here are some powers of two:
|
|
/// 1. One
|
|
/// 2. Two
|
|
/// 4. Four
|
|
/// 8. Eight
|
|
/// 16. Sixteen
|
|
/// 32. Thirty two
|
|
/// ````
|
|
#[default(HAlignment::End + VAlignment::Top)]
|
|
pub number_align: Alignment,
|
|
|
|
/// The numbered list's items.
|
|
///
|
|
/// When using the enum syntax, adjacent items are automatically collected
|
|
/// into enumerations, even through constructs like for loops.
|
|
///
|
|
/// ```example
|
|
/// #for phase in (
|
|
/// "Launch",
|
|
/// "Orbit",
|
|
/// "Descent",
|
|
/// ) [+ #phase]
|
|
/// ```
|
|
#[variadic]
|
|
pub children: Vec<Packed<EnumItem>>,
|
|
|
|
/// The numbers of parent items.
|
|
#[internal]
|
|
#[fold]
|
|
#[ghost]
|
|
pub parents: SmallVec<[usize; 4]>,
|
|
}
|
|
|
|
#[scope]
|
|
impl EnumElem {
|
|
#[elem]
|
|
type EnumItem;
|
|
}
|
|
|
|
impl Show for Packed<EnumElem> {
|
|
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
let mut realized =
|
|
BlockElem::multi_layouter(self.clone(), engine.routines.layout_enum)
|
|
.pack()
|
|
.spanned(self.span());
|
|
|
|
if self.tight(styles) {
|
|
let leading = ParElem::leading_in(styles);
|
|
let spacing =
|
|
VElem::new(leading.into()).with_weak(true).with_attach(true).pack();
|
|
realized = spacing + realized;
|
|
}
|
|
|
|
Ok(realized)
|
|
}
|
|
}
|
|
|
|
/// An enumeration item.
|
|
#[elem(name = "item", title = "Numbered List Item")]
|
|
pub struct EnumItem {
|
|
/// The item's number.
|
|
#[positional]
|
|
pub number: Option<usize>,
|
|
|
|
/// The item's body.
|
|
#[required]
|
|
pub body: Content,
|
|
}
|
|
|
|
cast! {
|
|
EnumItem,
|
|
array: Array => {
|
|
let mut iter = array.into_iter();
|
|
let (number, body) = match (iter.next(), iter.next(), iter.next()) {
|
|
(Some(a), Some(b), None) => (a.cast()?, b.cast()?),
|
|
_ => bail!("array must contain exactly two entries"),
|
|
};
|
|
Self::new(body).with_number(number)
|
|
},
|
|
v: Content => v.unpack::<Self>().unwrap_or_else(Self::new),
|
|
}
|
|
|
|
impl ListLike for EnumElem {
|
|
type Item = EnumItem;
|
|
|
|
fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self {
|
|
Self::new(children).with_tight(tight)
|
|
}
|
|
}
|
|
|
|
impl ListItemLike for EnumItem {
|
|
fn styled(mut item: Packed<Self>, styles: Styles) -> Packed<Self> {
|
|
item.body.style_in_place(styles);
|
|
item
|
|
}
|
|
}
|