summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/compute/utility.rs18
-rw-r--r--library/src/layout/enum.rs64
-rw-r--r--tests/ref/layout/enum-numbering.pngbin0 -> 20863 bytes
-rw-r--r--tests/typ/layout/enum-numbering.typ55
4 files changed, 134 insertions, 3 deletions
diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs
index cb765577..2220c890 100644
--- a/library/src/compute/utility.rs
+++ b/library/src/compute/utility.rs
@@ -176,6 +176,24 @@ impl NumberingPattern {
fmt.push_str(&self.suffix);
fmt
}
+
+ /// Apply only the k-th segment of the pattern to a number.
+ pub fn apply_kth(&self, k: usize, number: NonZeroUsize) -> EcoString {
+ let mut fmt = EcoString::new();
+ if let Some((prefix, _, _)) = self.pieces.first() {
+ fmt.push_str(prefix);
+ }
+ if let Some((_, kind, case)) = self
+ .pieces
+ .iter()
+ .chain(self.pieces.last().into_iter().cycle())
+ .nth(k)
+ {
+ fmt.push_str(&kind.apply(number, *case));
+ }
+ fmt.push_str(&self.suffix);
+ fmt
+ }
}
impl FromStr for NumberingPattern {
diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs
index 585b20e5..9b7ea199 100644
--- a/library/src/layout/enum.rs
+++ b/library/src/layout/enum.rs
@@ -3,6 +3,7 @@ use std::str::FromStr;
use crate::compute::{Numbering, NumberingPattern};
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
use crate::prelude::*;
+use crate::text::TextNode;
/// # Numbered List
/// A numbered list.
@@ -105,10 +106,16 @@ impl EnumNode {
/// How to number the enumeration. Accepts a
/// [numbering pattern or function]($func/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: "(a)")
+ /// #set enum(numbering: "1.a)")
/// + Different
/// + Numbering
+ /// + Nested
+ /// + Items
/// + Style
///
/// #set enum(numbering: n => super[#n])
@@ -119,6 +126,20 @@ impl EnumNode {
pub const NUMBERING: Numbering =
Numbering::Pattern(NumberingPattern::from_str("1.").unwrap());
+ /// Whether to display the full numbering, including the numbers of
+ /// all parent enumerations.
+ ///
+ /// Defaults to `{false}`.
+ ///
+ /// ```example
+ /// #set enum(numbering: "1.a)", full: true)
+ /// + Cook
+ /// + Heat water
+ /// + Add integredients
+ /// + Eat
+ /// ```
+ pub const FULL: bool = false;
+
/// The indentation of each item's label.
#[property(resolve)]
pub const INDENT: Length = Length::zero();
@@ -132,6 +153,10 @@ impl EnumNode {
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
pub const SPACING: Smart<Spacing> = Smart::Auto;
+ /// The numbers of parent items.
+ #[property(skip, fold)]
+ const PARENTS: Parent = vec![];
+
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let mut number: NonZeroUsize =
args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
@@ -193,13 +218,34 @@ impl Layout for EnumNode {
let mut cells = vec![];
let mut number = NonZeroUsize::new(1).unwrap();
+ let mut parents = styles.get(Self::PARENTS);
+ let full = styles.get(Self::FULL);
+
for ((n, item), map) in self.items.iter() {
number = n.unwrap_or(number);
- let resolved = numbering.apply(vt.world(), &[number])?.display();
+
+ let resolved = if full {
+ parents.push(number);
+ let content = numbering.apply(vt.world(), &parents)?.display();
+ parents.pop();
+ content
+ } else {
+ match numbering {
+ Numbering::Pattern(pattern) => {
+ TextNode::packed(pattern.apply_kth(parents.len(), number))
+ }
+ other => other.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()));
+ cells.push(
+ item.clone()
+ .styled_with_map(map.clone())
+ .styled(Self::PARENTS, Parent(number)),
+ );
number = number.saturating_add(1);
}
@@ -216,3 +262,15 @@ impl Layout for EnumNode {
.layout(vt, styles, regions)
}
}
+
+#[derive(Debug, Clone, Hash)]
+struct Parent(NonZeroUsize);
+
+impl Fold for Parent {
+ type Output = Vec<NonZeroUsize>;
+
+ fn fold(self, mut outer: Self::Output) -> Self::Output {
+ outer.push(self.0);
+ outer
+ }
+}
diff --git a/tests/ref/layout/enum-numbering.png b/tests/ref/layout/enum-numbering.png
new file mode 100644
index 00000000..6745a2f5
--- /dev/null
+++ b/tests/ref/layout/enum-numbering.png
Binary files differ
diff --git a/tests/typ/layout/enum-numbering.typ b/tests/typ/layout/enum-numbering.typ
new file mode 100644
index 00000000..1d905f69
--- /dev/null
+++ b/tests/typ/layout/enum-numbering.typ
@@ -0,0 +1,55 @@
+// Test enum numbering styles.
+
+---
+// Test numbering pattern.
+#set enum(numbering: "(1.a.*)")
++ First
++ Second
+ 2. Nested
+ + Deep
++ Normal
+
+---
+// Test full numbering.
+#set enum(numbering: "1.a.", full: true)
++ First
+ + Nested
+
+---
+// Test numbering with closure.
+#enum(
+ start: 3,
+ spacing: 0.65em - 3pt,
+ tight: false,
+ numbering: n => text(
+ fill: (red, green, blue).at(calc.mod(n, 3)),
+ numbering("A", n),
+ ),
+ [Red], [Green], [Blue], [Red],
+)
+
+---
+// Test numbering with closure and nested lists.
+#set enum(numbering: n => super[#n])
++ A
+ + B
++ C
+
+---
+// Test numbering with closure and nested lists.
+#set text("Latin Modern Roman")
+#set enum(numbering: (..args) => math.mat(args.pos()), full: true)
++ A
+ + B
+ + C
+ + D
++ E
++ F
+
+---
+// Error: 22-24 invalid numbering pattern
+#set enum(numbering: "")
+
+---
+// Error: 22-28 invalid numbering pattern
+#set enum(numbering: "(())")