summaryrefslogtreecommitdiff
path: root/library/src/basics
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-02 15:48:42 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-02 15:53:01 +0100
commitf57ce8643178c533a43c42baff3553f8613fb5ac (patch)
tree4aca4f5636f1330963264fa3f1e1d5dca60df0ad /library/src/basics
parent56923ee472f1eaa67d3543e19372823139205885 (diff)
Heading numbering and outline
Diffstat (limited to 'library/src/basics')
-rw-r--r--library/src/basics/heading.rs75
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
+ }
+}