typst_library/model/
enum.rs

1use std::str::FromStr;
2
3use smallvec::SmallVec;
4
5use crate::diag::bail;
6use crate::foundations::{Array, Content, Packed, Smart, Styles, cast, elem, scope};
7use crate::introspection::{Locatable, Tagged};
8use crate::layout::{Alignment, Em, HAlignment, Length, VAlignment};
9use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern};
10
11/// A numbered list.
12///
13/// Displays a sequence of items vertically and numbers them consecutively.
14///
15/// # Example
16/// ```example
17/// Automatically numbered:
18/// + Preparations
19/// + Analysis
20/// + Conclusions
21///
22/// Manually numbered:
23/// 2. What is the first step?
24/// 5. I am confused.
25/// +  Moving on ...
26///
27/// Multiple lines:
28/// + This enum item has multiple
29///   lines because the next line
30///   is indented.
31///
32/// Function call.
33/// #enum[First][Second]
34/// ```
35///
36/// You can easily switch all your enumerations to a different numbering style
37/// with a set rule.
38/// ```example
39/// #set enum(numbering: "a)")
40///
41/// + Starting off ...
42/// + Don't forget step two
43/// ```
44///
45/// You can also use [`enum.item`] to programmatically customize the number of
46/// each item in the enumeration:
47///
48/// ```example
49/// #enum(
50///   enum.item(1)[First step],
51///   enum.item(5)[Fifth step],
52///   enum.item(10)[Tenth step]
53/// )
54/// ```
55///
56/// # Syntax
57/// This functions also has dedicated syntax:
58///
59/// - Starting a line with a plus sign creates an automatically numbered
60///   enumeration item.
61/// - Starting a line with a number followed by a dot creates an explicitly
62///   numbered enumeration item.
63///
64/// Enumeration items can contain multiple paragraphs and other block-level
65/// content. All content that is indented more than an item's marker becomes
66/// part of that item.
67#[elem(scope, title = "Numbered List", Locatable, Tagged)]
68pub struct EnumElem {
69    /// Defines the default [spacing]($enum.spacing) of the enumeration. If it
70    /// is `{false}`, the items are spaced apart with
71    /// [paragraph spacing]($par.spacing). If it is `{true}`, they use
72    /// [paragraph leading]($par.leading) instead. This makes the list more
73    /// compact, which can look better if the items are short.
74    ///
75    /// In markup mode, the value of this parameter is determined based on
76    /// whether items are separated with a blank line. If items directly follow
77    /// each other, this is set to `{true}`; if items are separated by a blank
78    /// line, this is set to `{false}`. The markup-defined tightness cannot be
79    /// overridden with set rules.
80    ///
81    /// ```example
82    /// + If an enum has a lot of text, and
83    ///   maybe other inline content, it
84    ///   should not be tight anymore.
85    ///
86    /// + To make an enum wide, simply
87    ///   insert a blank line between the
88    ///   items.
89    /// ```
90    #[default(true)]
91    pub tight: bool,
92
93    /// How to number the enumeration. Accepts a
94    /// [numbering pattern or function]($numbering).
95    ///
96    /// If the numbering pattern contains multiple counting symbols, they apply
97    /// to nested enums. If given a function, the function receives one argument
98    /// if `full` is `{false}` and multiple arguments if `full` is `{true}`.
99    ///
100    /// ```example
101    /// #set enum(numbering: "1.a)")
102    /// + Different
103    /// + Numbering
104    ///   + Nested
105    ///   + Items
106    /// + Style
107    ///
108    /// #set enum(numbering: n => super[#n])
109    /// + Superscript
110    /// + Numbering!
111    /// ```
112    #[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
113    pub numbering: Numbering,
114
115    /// Which number to start the enumeration with.
116    ///
117    /// ```example
118    /// #enum(
119    ///   start: 3,
120    ///   [Skipping],
121    ///   [Ahead],
122    /// )
123    /// ```
124    pub start: Smart<u64>,
125
126    /// Whether to display the full numbering, including the numbers of
127    /// all parent enumerations.
128    ///
129    ///
130    /// ```example
131    /// #set enum(numbering: "1.a)", full: true)
132    /// + Cook
133    ///   + Heat water
134    ///   + Add ingredients
135    /// + Eat
136    /// ```
137    #[default(false)]
138    pub full: bool,
139
140    /// Whether to reverse the numbering for this enumeration.
141    ///
142    /// ```example
143    /// #set enum(reversed: true)
144    /// + Coffee
145    /// + Tea
146    /// + Milk
147    /// ```
148    #[default(false)]
149    pub reversed: bool,
150
151    /// The indentation of each item.
152    pub indent: Length,
153
154    /// The space between the numbering and the body of each item.
155    #[default(Em::new(0.5).into())]
156    pub body_indent: Length,
157
158    /// The spacing between the items of the enumeration.
159    ///
160    /// If set to `{auto}`, uses paragraph [`leading`]($par.leading) for tight
161    /// enumerations and paragraph [`spacing`]($par.spacing) for wide
162    /// (non-tight) enumerations.
163    pub spacing: Smart<Length>,
164
165    /// The alignment that enum numbers should have.
166    ///
167    /// By default, this is set to `{end + top}`, which aligns enum numbers
168    /// towards end of the current text direction (in left-to-right script,
169    /// for example, this is the same as `{right}`) and at the top of the line.
170    /// The choice of `{end}` for horizontal alignment of enum numbers is
171    /// usually preferred over `{start}`, as numbers then grow away from the
172    /// text instead of towards it, avoiding certain visual issues. This option
173    /// lets you override this behaviour, however. (Also to note is that the
174    /// [unordered list]($list) uses a different method for this, by giving the
175    /// `marker` content an alignment directly.).
176    ///
177    /// ````example
178    /// #set enum(number-align: start + bottom)
179    ///
180    /// Here are some powers of two:
181    /// 1. One
182    /// 2. Two
183    /// 4. Four
184    /// 8. Eight
185    /// 16. Sixteen
186    /// 32. Thirty two
187    /// ````
188    #[default(HAlignment::End + VAlignment::Top)]
189    pub number_align: Alignment,
190
191    /// The numbered list's items.
192    ///
193    /// When using the enum syntax, adjacent items are automatically collected
194    /// into enumerations, even through constructs like for loops.
195    ///
196    /// ```example
197    /// #for phase in (
198    ///    "Launch",
199    ///    "Orbit",
200    ///    "Descent",
201    /// ) [+ #phase]
202    /// ```
203    #[variadic]
204    pub children: Vec<Packed<EnumItem>>,
205
206    /// The numbers of parent items.
207    #[internal]
208    #[fold]
209    #[ghost]
210    pub parents: SmallVec<[u64; 4]>,
211}
212
213#[scope]
214impl EnumElem {
215    #[elem]
216    type EnumItem;
217}
218
219/// An enumeration item.
220#[elem(name = "item", title = "Numbered List Item", Tagged)]
221pub struct EnumItem {
222    /// The item's number.
223    #[positional]
224    pub number: Smart<u64>,
225
226    /// The item's body.
227    #[required]
228    pub body: Content,
229}
230
231cast! {
232    EnumItem,
233    array: Array => {
234        let mut iter = array.into_iter();
235        let (number, body) = match (iter.next(), iter.next(), iter.next()) {
236            (Some(a), Some(b), None) => (a.cast()?, b.cast()?),
237            _ => bail!("array must contain exactly two entries"),
238        };
239        Self::new(body).with_number(number)
240    },
241    v: Content => v.unpack::<Self>().unwrap_or_else(Self::new),
242}
243
244impl ListLike for EnumElem {
245    type Item = EnumItem;
246
247    fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self {
248        Self::new(children).with_tight(tight)
249    }
250}
251
252impl ListItemLike for EnumItem {
253    fn styled(mut item: Packed<Self>, styles: Styles) -> Packed<Self> {
254        item.body.style_in_place(styles);
255        item
256    }
257}