summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/model/enum.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/model/enum.rs')
-rw-r--r--crates/typst-library/src/model/enum.rs271
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
+ }
+}