diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-02-07 12:10:52 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-02-07 12:10:52 +0100 |
| commit | 1d5bf56e585c659ed7975cdf6cdfc9c67c29998d (patch) | |
| tree | 7ebe6e2d99869c5d91b00dcf85c8b54b8865cb84 /library/src/meta | |
| parent | a0674990d3c318bb2e42e4d5fad66ea5477a54bf (diff) | |
Remove basics section
Diffstat (limited to 'library/src/meta')
| -rw-r--r-- | library/src/meta/heading.rs | 200 | ||||
| -rw-r--r-- | library/src/meta/mod.rs | 6 | ||||
| -rw-r--r-- | library/src/meta/outline.rs | 2 |
3 files changed, 205 insertions, 3 deletions
diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs new file mode 100644 index 00000000..8da5cd6e --- /dev/null +++ b/library/src/meta/heading.rs @@ -0,0 +1,200 @@ +use typst::font::FontWeight; + +use crate::compute::Numbering; +use crate::layout::{BlockNode, VNode}; +use crate::prelude::*; +use crate::text::{SpaceNode, TextNode, TextSize}; + +/// # Heading +/// A section heading. +/// +/// 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 +/// (not the document's title). +/// +/// Typst can automatically number your headings for you. To enable numbering, +/// specify how you want your headings to be numbered with a +/// [numbering pattern or function]($func/numbering). +/// +/// Independently from the numbering, Typst can also automatically generate an +/// [outline]($func/outline) of all headings for you. To exclude one or more +/// headings from this outline, you can set the `outlined` parameter to +/// `{false}`. +/// +/// ## Example +/// ```example +/// #set heading(numbering: "1.a)") +/// +/// = Introduction +/// In recent years, ... +/// +/// == Preliminaries +/// To start, ... +/// ``` +/// +/// ## 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. +/// +/// ## Parameters +/// - title: `Content` (positional, required) +/// The heading's title. +/// +/// - level: `NonZeroUsize` (named) +/// The logical nesting depth of the heading, starting from one. +/// +/// ## Category +/// meta +#[func] +#[capable(Prepare, Show, Finalize)] +#[derive(Debug, Hash)] +pub struct HeadingNode { + /// The logical nesting depth of the section, starting from one. In the + /// default style, this controls the text size of the heading. + pub level: NonZeroUsize, + /// The heading's contents. + pub title: Content, +} + +#[node] +impl HeadingNode { + /// How to number the heading. Accepts a + /// [numbering pattern or function]($func/numbering). + /// + /// ```example + /// #set heading(numbering: "1.a.") + /// + /// = A section + /// == A subsection + /// === A sub-subsection + /// ``` + #[property(referenced)] + pub const NUMBERING: Option<Numbering> = None; + + /// Whether the heading should appear in the outline. + /// + /// ```example + /// #outline() + /// + /// #heading[Normal] + /// This is a normal heading. + /// + /// #heading(outlined: false)[Hidden] + /// This heading does not appear + /// in the outline. + /// ``` + pub const OUTLINED: bool = true; + + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self { + title: args.expect("title")?, + level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), + } + .pack()) + } + + fn field(&self, name: &str) -> Option<Value> { + match name { + "level" => Some(Value::Int(self.level.get() as i64)), + "title" => Some(Value::Content(self.title.clone())), + _ => None, + } + } +} + +impl Prepare for HeadingNode { + fn prepare( + &self, + vt: &mut Vt, + mut this: Content, + styles: StyleChain, + ) -> SourceResult<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; + } + + let numbers = node.field("numbers").unwrap(); + if numbers != Value::None { + let heading = node.to::<Self>().unwrap(); + counter.advance(heading); + } + } + + let mut numbers = Value::None; + if let Some(numbering) = styles.get(Self::NUMBERING) { + numbers = numbering.apply(vt.world(), counter.advance(self))?; + } + + this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); + this.push_field("numbers", numbers); + + let meta = Meta::Node(my_id, this.clone()); + Ok(this.styled(Meta::DATA, vec![meta])) + } +} + +impl Show for HeadingNode { + fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> { + let mut realized = self.title.clone(); + let numbers = this.field("numbers").unwrap(); + if numbers != Value::None { + realized = numbers.display() + SpaceNode.pack() + realized; + } + Ok(BlockNode(realized).pack()) + } +} + +impl Finalize for HeadingNode { + fn finalize(&self, realized: Content) -> Content { + let scale = match self.level.get() { + 1 => 1.4, + 2 => 1.2, + _ => 1.0, + }; + + let size = Em::new(scale); + let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 }) / scale; + let below = Em::new(0.66) / scale; + + let mut map = StyleMap::new(); + map.set(TextNode::SIZE, TextSize(size.into())); + map.set(TextNode::WEIGHT, FontWeight::BOLD); + map.set(BlockNode::ABOVE, VNode::block_around(above.into())); + map.set(BlockNode::BELOW, VNode::block_around(below.into())); + map.set(BlockNode::STICKY, true); + 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 + } +} diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs index 4612274c..fd72a8cb 100644 --- a/library/src/meta/mod.rs +++ b/library/src/meta/mod.rs @@ -1,11 +1,13 @@ //! Interaction between document parts. mod document; +mod heading; mod link; -mod reference; mod outline; +mod reference; pub use self::document::*; -pub use self::outline::*; +pub use self::heading::*; pub use self::link::*; +pub use self::outline::*; pub use self::reference::*; diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 8a8bc57c..01aa7ee9 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -1,4 +1,4 @@ -use crate::basics::HeadingNode; +use super::HeadingNode; use crate::layout::{BlockNode, HNode, HideNode, RepeatNode, Spacing}; use crate::prelude::*; use crate::text::{LinebreakNode, SpaceNode, TextNode}; |
