diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-12-02 15:48:42 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-12-02 15:53:01 +0100 |
| commit | f57ce8643178c533a43c42baff3553f8613fb5ac (patch) | |
| tree | 4aca4f5636f1330963264fa3f1e1d5dca60df0ad /library/src/basics | |
| parent | 56923ee472f1eaa67d3543e19372823139205885 (diff) | |
Heading numbering and outline
Diffstat (limited to 'library/src/basics')
| -rw-r--r-- | library/src/basics/heading.rs | 75 |
1 files changed, 71 insertions, 4 deletions
diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index d1ea9da6..58d0d3bf 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -1,8 +1,9 @@ use typst::font::FontWeight; +use crate::compute::NumberingPattern; use crate::layout::{BlockNode, VNode}; use crate::prelude::*; -use crate::text::{TextNode, TextSize}; +use crate::text::{SpaceNode, TextNode, TextSize}; /// A section heading. #[derive(Debug, Hash)] @@ -14,8 +15,15 @@ pub struct HeadingNode { pub body: Content, } -#[node(Show, Finalize)] +#[node(Prepare, Show, Finalize)] impl HeadingNode { + /// How to number the heading. + #[property(referenced)] + pub const NUMBERING: Option<NumberingPattern> = None; + + /// Whether the heading should appear in the outline. + pub const OUTLINED: bool = true; + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self { body: args.expect("body")?, @@ -33,9 +41,42 @@ impl HeadingNode { } } +impl Prepare for HeadingNode { + fn prepare(&self, vt: &mut Vt, mut this: Content, styles: StyleChain) -> Content { + let my_id = vt.identify(&this); + + let mut counter = HeadingCounter::new(); + for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) { + if node_id == my_id { + break; + } + + if matches!(node.field("numbers"), Some(Value::Str(_))) { + let heading = node.to::<Self>().unwrap(); + counter.advance(heading); + } + } + + let mut numbers = Value::None; + if let Some(pattern) = styles.get(Self::NUMBERING) { + numbers = Value::Str(pattern.apply(counter.advance(self)).into()); + } + + this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); + this.push_field("numbers", numbers); + + let meta = Meta::Node(my_id, this.clone()); + this.styled(Meta::DATA, vec![meta]) + } +} + impl Show for HeadingNode { - fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content { - BlockNode(self.body.clone()).pack() + fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> Content { + let mut realized = self.body.clone(); + if let Some(Value::Str(numbering)) = this.field("numbers") { + realized = TextNode::packed(numbering) + SpaceNode.pack() + realized; + } + BlockNode(realized).pack() } } @@ -60,3 +101,29 @@ impl Finalize for HeadingNode { realized.styled_with_map(map) } } + +/// Counters through headings with different levels. +pub struct HeadingCounter(Vec<NonZeroUsize>); + +impl HeadingCounter { + /// Create a new heading counter. + pub fn new() -> Self { + Self(vec![]) + } + + /// Advance the counter and return the numbers for the given heading. + pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] { + let level = heading.level.get(); + + if self.0.len() >= level { + self.0[level - 1] = self.0[level - 1].saturating_add(1); + self.0.truncate(level); + } + + while self.0.len() < level { + self.0.push(NonZeroUsize::new(1).unwrap()); + } + + &self.0 + } +} |
