Skip to main content

typst_library/model/
enum.rs

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