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}