diff options
Diffstat (limited to 'crates/typst-library/src/model/enum.rs')
| -rw-r--r-- | crates/typst-library/src/model/enum.rs | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs new file mode 100644 index 00000000..bac792d3 --- /dev/null +++ b/crates/typst-library/src/model/enum.rs @@ -0,0 +1,271 @@ +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 + } +} |
