summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortingerrr <me@tinger.dev>2024-02-29 09:51:56 +0100
committerGitHub <noreply@github.com>2024-02-29 08:51:56 +0000
commit5a03c818c8b595c9bb62613c8da6d7464ccff05c (patch)
tree21cd4d3664dac88f43350b14ceb227b716f295ec
parentedf957399c59332f1472321e72106d31fc9f610b (diff)
Add `depth` and `offset` field to `heading` (#3038)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
-rw-r--r--crates/typst-pdf/src/outline.rs2
-rw-r--r--crates/typst-syntax/src/ast.rs2
-rw-r--r--crates/typst/src/eval/markup.rs4
-rw-r--r--crates/typst/src/foundations/func.rs7
-rw-r--r--crates/typst/src/model/bibliography.rs2
-rw-r--r--crates/typst/src/model/heading.rs69
-rw-r--r--crates/typst/src/model/outline.rs2
-rw-r--r--docs/reference/scripting.md2
-rw-r--r--tests/ref/meta/heading.pngbin25212 -> 41024 bytes
-rw-r--r--tests/typ/meta/heading.typ20
10 files changed, 95 insertions, 15 deletions
diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs
index a060c175..e247c322 100644
--- a/crates/typst-pdf/src/outline.rs
+++ b/crates/typst-pdf/src/outline.rs
@@ -117,7 +117,7 @@ struct HeadingNode<'a> {
impl<'a> HeadingNode<'a> {
fn leaf(element: &'a Packed<HeadingElem>) -> Self {
HeadingNode {
- level: element.level(StyleChain::default()),
+ level: element.resolve_level(StyleChain::default()),
// 'bookmarked' set to 'auto' falls back to the value of 'outlined'.
bookmarked: element
.bookmarked(StyleChain::default())
diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs
index 26a26160..8f8eaac4 100644
--- a/crates/typst-syntax/src/ast.rs
+++ b/crates/typst-syntax/src/ast.rs
@@ -695,7 +695,7 @@ impl<'a> Heading<'a> {
}
/// The section depth (number of equals signs).
- pub fn level(self) -> NonZeroUsize {
+ pub fn depth(self) -> NonZeroUsize {
self.0
.children()
.find(|node| node.kind() == SyntaxKind::HeadingMarker)
diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs
index d7d400e7..1bb12d49 100644
--- a/crates/typst/src/eval/markup.rs
+++ b/crates/typst/src/eval/markup.rs
@@ -208,9 +208,9 @@ impl Eval for ast::Heading<'_> {
type Output = Content;
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let level = self.level();
+ let depth = self.depth();
let body = self.body().eval(vm)?;
- Ok(HeadingElem::new(body).with_level(level).pack())
+ Ok(HeadingElem::new(body).with_depth(depth).pack())
}
}
diff --git a/crates/typst/src/foundations/func.rs b/crates/typst/src/foundations/func.rs
index bb839994..7871e297 100644
--- a/crates/typst/src/foundations/func.rs
+++ b/crates/typst/src/foundations/func.rs
@@ -341,6 +341,13 @@ impl Func {
/// Returns a selector that filters for elements belonging to this function
/// whose fields have the values of the given arguments.
+ ///
+ /// ```example
+ /// #show heading.where(level: 2): set text(blue)
+ /// = Section
+ /// == Subsection
+ /// === Sub-subection
+ /// ```
#[func]
pub fn where_(
self,
diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs
index 19fc44ea..e3464026 100644
--- a/crates/typst/src/model/bibliography.rs
+++ b/crates/typst/src/model/bibliography.rs
@@ -220,7 +220,7 @@ impl Show for Packed<BibliographyElem> {
seq.push(
HeadingElem::new(title)
- .with_level(NonZeroUsize::ONE)
+ .with_level(Smart::Custom(NonZeroUsize::ONE))
.pack()
.spanned(self.span()),
);
diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs
index b958438b..cd342bec 100644
--- a/crates/typst/src/model/heading.rs
+++ b/crates/typst/src/model/heading.rs
@@ -17,7 +17,7 @@ use crate::util::{option_eq, NonZeroExt};
/// With headings, you can structure your document into sections. Each heading
/// has a _level,_ which starts at one and is unbounded upwards. This level
/// indicates the logical role of the following content (section, subsection,
-/// etc.) A top-level heading indicates a top-level section of the document
+/// etc.) A top-level heading indicates a top-level section of the document
/// (not the document's title).
///
/// Typst can automatically number your headings for you. To enable numbering,
@@ -42,12 +42,54 @@ use crate::util::{option_eq, NonZeroExt};
/// # Syntax
/// Headings have dedicated syntax: They can be created by starting a line with
/// one or multiple equals signs, followed by a space. The number of equals
-/// signs determines the heading's logical nesting depth.
+/// signs determines the heading's logical nesting depth. The `{offset}` field
+/// can be set to configure the starting depth.
#[elem(Locatable, Synthesize, Count, Show, ShowSet, LocalName, Refable, Outlinable)]
pub struct HeadingElem {
- /// The logical nesting depth of the heading, starting from one.
+ /// The absolute nesting depth of the heading, starting from one. If set
+ /// to `{auto}`, it is computed from `{offset + depth}`.
+ ///
+ /// This is primarily useful for usage in [show rules]($styling/#show-rules)
+ /// (either with [`where`]($function.where) selectors or by accessing the
+ /// level directly on a shown heading).
+ ///
+ /// ```example
+ /// #show heading.where(level: 2): set text(red)
+ ///
+ /// = Level 1
+ /// == Level 2
+ ///
+ /// #set heading(offset: 1)
+ /// = Also level 2
+ /// == Level 3
+ /// ```
+ pub level: Smart<NonZeroUsize>,
+
+ /// The relative nesting depth of the heading, starting from one. This is
+ /// combined with `{offset}` to compute the actual `{level}`.
+ ///
+ /// This is set by the heading syntax, such that `[== Heading]` creates a
+ /// heading with logical depth 2, but actual level `{offset + 2}`. If you
+ /// construct a heading manually, you should typically prefer this over
+ /// setting the absolute `level`.
#[default(NonZeroUsize::ONE)]
- pub level: NonZeroUsize,
+ pub depth: NonZeroUsize,
+
+ /// The starting offset of each heading's level, used to turn its relative
+ /// `{depth}` into its absolute `{level}`.
+ ///
+ /// ```example
+ /// = Level 1
+ ///
+ /// #set heading(offset: 1, numbering: "1.1")
+ /// = Level 2
+ ///
+ /// #heading(offset: 2, depth: 2)[
+ /// I'm level 4
+ /// ]
+ /// ```
+ #[default(0)]
+ pub offset: usize,
/// How to number the heading. Accepts a
/// [numbering pattern or function]($numbering).
@@ -126,6 +168,15 @@ pub struct HeadingElem {
pub body: Content,
}
+impl HeadingElem {
+ pub fn resolve_level(&self, styles: StyleChain) -> NonZeroUsize {
+ self.level(styles).unwrap_or_else(|| {
+ NonZeroUsize::new(self.offset(styles) + self.depth(styles).get())
+ .expect("overflow to 0 on NoneZeroUsize + usize")
+ })
+ }
+}
+
impl Synthesize for Packed<HeadingElem> {
fn synthesize(
&mut self,
@@ -140,7 +191,9 @@ impl Synthesize for Packed<HeadingElem> {
}
};
- self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
+ let elem = self.as_mut();
+ elem.push_level(Smart::Custom(elem.resolve_level(styles)));
+ elem.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
Ok(())
}
}
@@ -163,7 +216,7 @@ impl Show for Packed<HeadingElem> {
impl ShowSet for Packed<HeadingElem> {
fn show_set(&self, styles: StyleChain) -> Styles {
- let level = (**self).level(styles).get();
+ let level = (**self).resolve_level(styles).get();
let scale = match level {
1 => 1.4,
2 => 1.2,
@@ -189,7 +242,7 @@ impl Count for Packed<HeadingElem> {
(**self)
.numbering(StyleChain::default())
.is_some()
- .then(|| CounterUpdate::Step((**self).level(StyleChain::default())))
+ .then(|| CounterUpdate::Step((**self).resolve_level(StyleChain::default())))
}
}
@@ -236,7 +289,7 @@ impl Outlinable for Packed<HeadingElem> {
}
fn level(&self) -> NonZeroUsize {
- (**self).level(StyleChain::default())
+ (**self).resolve_level(StyleChain::default())
}
}
diff --git a/crates/typst/src/model/outline.rs b/crates/typst/src/model/outline.rs
index cb8d5563..bec98b7d 100644
--- a/crates/typst/src/model/outline.rs
+++ b/crates/typst/src/model/outline.rs
@@ -197,7 +197,7 @@ impl Show for Packed<OutlineElem> {
seq.push(
HeadingElem::new(title)
- .with_level(NonZeroUsize::ONE)
+ .with_depth(NonZeroUsize::ONE)
.pack()
.spanned(self.span()),
);
diff --git a/docs/reference/scripting.md b/docs/reference/scripting.md
index 9a04bfb9..b66b9896 100644
--- a/docs/reference/scripting.md
+++ b/docs/reference/scripting.md
@@ -248,7 +248,7 @@ can be either:
#let it = [= Heading]
#it.body \
-#it.level
+#it.depth
```
## Methods
diff --git a/tests/ref/meta/heading.png b/tests/ref/meta/heading.png
index 28b41907..8467ea53 100644
--- a/tests/ref/meta/heading.png
+++ b/tests/ref/meta/heading.png
Binary files differ
diff --git a/tests/typ/meta/heading.typ b/tests/typ/meta/heading.typ
index 7db2a5cf..a253913e 100644
--- a/tests/typ/meta/heading.typ
+++ b/tests/typ/meta/heading.typ
@@ -46,6 +46,26 @@ multiline.
#heading(level: 5)[Heading]
---
+// Test setting the starting offset.
+#set heading(numbering: "1.1")
+#show heading.where(level: 2): set text(blue)
+= Level 1
+
+#heading(depth: 1)[We're twins]
+#heading(level: 1)[We're twins]
+
+== Real level 2
+
+#set heading(offset: 1)
+= Fake level 2
+== Fake level 3
+
+---
+// Passing level directly still overrides all other set values
+#set heading(numbering: "1.1", offset: 1)
+#heading(level: 1)[Still level 1]
+
+---
// Edge cases.
#set heading(numbering: "1.")
=