mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
219 lines
6.3 KiB
Rust
219 lines
6.3 KiB
Rust
use std::str::FromStr;
|
|
|
|
use crate::compute::{Numbering, NumberingPattern};
|
|
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
|
|
use crate::prelude::*;
|
|
|
|
/// # Numbered List
|
|
/// 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 ...
|
|
///
|
|
/// 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
|
|
/// ```
|
|
///
|
|
/// ## 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 plus sign or dot
|
|
/// becomes part of that item.
|
|
///
|
|
/// ## Parameters
|
|
/// - items: `Content` (positional, variadic)
|
|
/// The enumeration's children.
|
|
///
|
|
/// 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]
|
|
/// ```
|
|
///
|
|
/// - start: `NonZeroUsize` (named)
|
|
/// Which number to start the enumeration with.
|
|
///
|
|
/// ```example
|
|
/// #enum(
|
|
/// start: 3,
|
|
/// [Skipping],
|
|
/// [Ahead],
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// - tight: `bool` (named)
|
|
/// If this is `{false}`, the items are spaced apart with
|
|
/// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal
|
|
/// [leading]($func/par.leading) instead. This makes the enumeration more
|
|
/// compact, which can look better if the items are short.
|
|
///
|
|
/// ```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.
|
|
/// ```
|
|
///
|
|
/// ## Category
|
|
/// layout
|
|
#[func]
|
|
#[capable(Layout)]
|
|
#[derive(Debug, Hash)]
|
|
pub struct EnumNode {
|
|
/// If true, the items are separated by leading instead of list spacing.
|
|
pub tight: bool,
|
|
/// The individual numbered items.
|
|
pub items: StyleVec<(Option<NonZeroUsize>, Content)>,
|
|
}
|
|
|
|
#[node]
|
|
impl EnumNode {
|
|
/// How to number the enumeration. Accepts a
|
|
/// [numbering pattern or function]($func/numbering).
|
|
///
|
|
/// ```example
|
|
/// #set enum(numbering: "(a)")
|
|
/// + Different
|
|
/// + Numbering
|
|
/// + Style
|
|
///
|
|
/// #set enum(numbering: n => super[#n])
|
|
/// + Superscript
|
|
/// + Numbering!
|
|
/// ```
|
|
#[property(referenced)]
|
|
pub const NUMBERING: Numbering =
|
|
Numbering::Pattern(NumberingPattern::from_str("1.").unwrap());
|
|
|
|
/// The indentation of each item's label.
|
|
#[property(resolve)]
|
|
pub const INDENT: Length = Length::zero();
|
|
|
|
/// The space between the numbering and the body of each item.
|
|
#[property(resolve)]
|
|
pub const BODY_INDENT: Length = Em::new(0.5).into();
|
|
|
|
/// The spacing between the items of a wide (non-tight) enumeration.
|
|
///
|
|
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
|
|
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
|
|
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
let mut number: NonZeroUsize =
|
|
args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
|
|
|
|
Ok(Self {
|
|
tight: args.named("tight")?.unwrap_or(true),
|
|
items: args
|
|
.all()?
|
|
.into_iter()
|
|
.map(|body| {
|
|
let item = (Some(number), body);
|
|
number = number.saturating_add(1);
|
|
item
|
|
})
|
|
.collect(),
|
|
}
|
|
.pack())
|
|
}
|
|
|
|
fn field(&self, name: &str) -> Option<Value> {
|
|
match name {
|
|
"tight" => Some(Value::Bool(self.tight)),
|
|
"items" => Some(Value::Array(
|
|
self.items
|
|
.items()
|
|
.map(|(number, body)| {
|
|
Value::Dict(dict! {
|
|
"number" => match *number {
|
|
Some(n) => Value::Int(n.get() as i64),
|
|
None => Value::None,
|
|
},
|
|
"body" => Value::Content(body.clone()),
|
|
})
|
|
})
|
|
.collect(),
|
|
)),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Layout for EnumNode {
|
|
fn layout(
|
|
&self,
|
|
vt: &mut Vt,
|
|
styles: StyleChain,
|
|
regions: Regions,
|
|
) -> SourceResult<Fragment> {
|
|
let numbering = styles.get(Self::NUMBERING);
|
|
let indent = styles.get(Self::INDENT);
|
|
let body_indent = styles.get(Self::BODY_INDENT);
|
|
let gutter = if self.tight {
|
|
styles.get(ParNode::LEADING).into()
|
|
} else {
|
|
styles
|
|
.get(Self::SPACING)
|
|
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
|
|
};
|
|
|
|
let mut cells = vec![];
|
|
let mut number = NonZeroUsize::new(1).unwrap();
|
|
for ((n, item), map) in self.items.iter() {
|
|
number = n.unwrap_or(number);
|
|
let resolved = numbering.apply(vt.world(), &[number])?.display();
|
|
cells.push(Content::empty());
|
|
cells.push(resolved.styled_with_map(map.clone()));
|
|
cells.push(Content::empty());
|
|
cells.push(item.clone().styled_with_map(map.clone()));
|
|
number = number.saturating_add(1);
|
|
}
|
|
|
|
GridNode {
|
|
tracks: Axes::with_x(vec![
|
|
Sizing::Rel(indent.into()),
|
|
Sizing::Auto,
|
|
Sizing::Rel(body_indent.into()),
|
|
Sizing::Auto,
|
|
]),
|
|
gutter: Axes::with_y(vec![gutter.into()]),
|
|
cells,
|
|
}
|
|
.layout(vt, styles, regions)
|
|
}
|
|
}
|