Add depth and offset field to heading (#3038)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
tingerrr 2024-02-29 09:51:56 +01:00 committed by GitHub
parent edf957399c
commit 5a03c818c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 95 additions and 15 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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()),
);

View File

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

View File

@ -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()),
);

View File

@ -248,7 +248,7 @@ can be either:
#let it = [= Heading]
#it.body \
#it.level
#it.depth
```
## Methods

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -45,6 +45,26 @@ multiline.
===== Heading 🌍
#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.")