mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Configurable numbering for nested enums
This commit is contained in:
parent
8f68bc7a8e
commit
05c8c6045c
@ -176,6 +176,24 @@ impl NumberingPattern {
|
|||||||
fmt.push_str(&self.suffix);
|
fmt.push_str(&self.suffix);
|
||||||
fmt
|
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 {
|
impl FromStr for NumberingPattern {
|
||||||
|
@ -3,6 +3,7 @@ use std::str::FromStr;
|
|||||||
use crate::compute::{Numbering, NumberingPattern};
|
use crate::compute::{Numbering, NumberingPattern};
|
||||||
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
|
use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// # Numbered List
|
/// # Numbered List
|
||||||
/// A numbered list.
|
/// A numbered list.
|
||||||
@ -105,10 +106,16 @@ impl EnumNode {
|
|||||||
/// How to number the enumeration. Accepts a
|
/// How to number the enumeration. Accepts a
|
||||||
/// [numbering pattern or function]($func/numbering).
|
/// [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
|
/// ```example
|
||||||
/// #set enum(numbering: "(a)")
|
/// #set enum(numbering: "1.a)")
|
||||||
/// + Different
|
/// + Different
|
||||||
/// + Numbering
|
/// + Numbering
|
||||||
|
/// + Nested
|
||||||
|
/// + Items
|
||||||
/// + Style
|
/// + Style
|
||||||
///
|
///
|
||||||
/// #set enum(numbering: n => super[#n])
|
/// #set enum(numbering: n => super[#n])
|
||||||
@ -119,6 +126,20 @@ impl EnumNode {
|
|||||||
pub const NUMBERING: Numbering =
|
pub const NUMBERING: Numbering =
|
||||||
Numbering::Pattern(NumberingPattern::from_str("1.").unwrap());
|
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.
|
/// The indentation of each item's label.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const INDENT: Length = Length::zero();
|
pub const INDENT: Length = Length::zero();
|
||||||
@ -132,6 +153,10 @@ impl EnumNode {
|
|||||||
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
|
/// If set to `{auto}` uses the spacing [below blocks]($func/block.below).
|
||||||
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
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> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let mut number: NonZeroUsize =
|
let mut number: NonZeroUsize =
|
||||||
args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
|
args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap());
|
||||||
@ -193,13 +218,34 @@ impl Layout for EnumNode {
|
|||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
let mut number = NonZeroUsize::new(1).unwrap();
|
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() {
|
for ((n, item), map) in self.items.iter() {
|
||||||
number = n.unwrap_or(number);
|
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(Content::empty());
|
||||||
cells.push(resolved.styled_with_map(map.clone()));
|
cells.push(resolved.styled_with_map(map.clone()));
|
||||||
cells.push(Content::empty());
|
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);
|
number = number.saturating_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,3 +262,15 @@ impl Layout for EnumNode {
|
|||||||
.layout(vt, styles, regions)
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
tests/ref/layout/enum-numbering.png
Normal file
BIN
tests/ref/layout/enum-numbering.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
55
tests/typ/layout/enum-numbering.typ
Normal file
55
tests/typ/layout/enum-numbering.typ
Normal 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: "(())")
|
Loading…
x
Reference in New Issue
Block a user