typst_library/model/
terms.rs

1use crate::diag::bail;
2use crate::foundations::{
3    Array, Content, NativeElement, Packed, Smart, Styles, cast, elem, scope,
4};
5use crate::introspection::{Locatable, Tagged};
6use crate::layout::{Em, HElem, Length};
7use crate::model::{ListItemLike, ListLike};
8
9/// A list of terms and their descriptions.
10///
11/// Displays a sequence of terms and their descriptions vertically. When the
12/// descriptions span over multiple lines, they use hanging indent to
13/// communicate the visual hierarchy.
14///
15/// # Example
16/// ```example
17/// / Ligature: A merged glyph.
18/// / Kerning: A spacing adjustment
19///   between two adjacent letters.
20/// ```
21///
22/// # Syntax
23/// This function also has dedicated syntax: Starting a line with a slash,
24/// followed by a term, a colon and a description creates a term list item.
25#[elem(scope, title = "Term List", Locatable, Tagged)]
26pub struct TermsElem {
27    /// Defines the default [spacing]($terms.spacing) of the term list. If it is
28    /// `{false}`, the items are spaced apart with
29    /// [paragraph spacing]($par.spacing). If it is `{true}`, they use
30    /// [paragraph leading]($par.leading) instead. This makes the list more
31    /// compact, which can look better if the items are short.
32    ///
33    /// In markup mode, the value of this parameter is determined based on
34    /// whether items are separated with a blank line. If items directly follow
35    /// each other, this is set to `{true}`; if items are separated by a blank
36    /// line, this is set to `{false}`. The markup-defined tightness cannot be
37    /// overridden with set rules.
38    ///
39    /// ```example
40    /// / Fact: If a term list has a lot
41    ///   of text, and maybe other inline
42    ///   content, it should not be tight
43    ///   anymore.
44    ///
45    /// / Tip: To make it wide, simply
46    ///   insert a blank line between the
47    ///   items.
48    /// ```
49    #[default(true)]
50    pub tight: bool,
51
52    /// The separator between the item and the description.
53    ///
54    /// If you want to just separate them with a certain amount of space, use
55    /// `{h(2cm, weak: true)}` as the separator and replace `{2cm}` with your
56    /// desired amount of space.
57    ///
58    /// ```example
59    /// #set terms(separator: [: ])
60    ///
61    /// / Colon: A nice separator symbol.
62    /// ```
63    #[default(HElem::new(Em::new(0.6).into()).with_weak(true).pack())]
64    pub separator: Content,
65
66    /// The indentation of each item.
67    pub indent: Length,
68
69    /// The hanging indent of the description.
70    ///
71    /// This is in addition to the whole item's `indent`.
72    ///
73    /// ```example
74    /// #set terms(hanging-indent: 0pt)
75    /// / Term: This term list does not
76    ///   make use of hanging indents.
77    /// ```
78    #[default(Em::new(2.0).into())]
79    pub hanging_indent: Length,
80
81    /// The spacing between the items of the term list.
82    ///
83    /// If set to `{auto}`, uses paragraph [`leading`]($par.leading) for tight
84    /// term lists and paragraph [`spacing`]($par.spacing) for wide
85    /// (non-tight) term lists.
86    pub spacing: Smart<Length>,
87
88    /// The term list's children.
89    ///
90    /// When using the term list syntax, adjacent items are automatically
91    /// collected into term lists, even through constructs like for loops.
92    ///
93    /// ```example
94    /// #for (year, product) in (
95    ///   "1978": "TeX",
96    ///   "1984": "LaTeX",
97    ///   "2019": "Typst",
98    /// ) [/ #product: Born in #year.]
99    /// ```
100    #[variadic]
101    pub children: Vec<Packed<TermItem>>,
102
103    /// Whether we are currently within a term list.
104    #[internal]
105    #[ghost]
106    pub within: bool,
107}
108
109#[scope]
110impl TermsElem {
111    #[elem]
112    type TermItem;
113}
114
115/// A term list item.
116#[elem(name = "item", title = "Term List Item", Tagged)]
117pub struct TermItem {
118    /// The term described by the list item.
119    #[required]
120    pub term: Content,
121
122    /// The description of the term.
123    #[required]
124    pub description: Content,
125}
126
127cast! {
128    TermItem,
129    array: Array => {
130        let mut iter = array.into_iter();
131        let (term, description) = match (iter.next(), iter.next(), iter.next()) {
132            (Some(a), Some(b), None) => (a.cast()?, b.cast()?),
133            _ => bail!("array must contain exactly two entries"),
134        };
135        Self::new(term, description)
136    },
137    v: Content => v.unpack::<Self>().map_err(|_| "expected term item or array")?,
138}
139
140impl ListLike for TermsElem {
141    type Item = TermItem;
142
143    fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self {
144        Self::new(children).with_tight(tight)
145    }
146}
147
148impl ListItemLike for TermItem {
149    fn styled(mut item: Packed<Self>, styles: Styles) -> Packed<Self> {
150        item.term.style_in_place(styles.clone());
151        item.description.style_in_place(styles);
152        item
153    }
154}