Configurable numbering for nested enums

This commit is contained in:
Laurenz 2023-02-13 16:51:30 +01:00
parent 8f68bc7a8e
commit 05c8c6045c
4 changed files with 134 additions and 3 deletions

View File

@ -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 {

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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: "(())")