summaryrefslogtreecommitdiff
path: root/library/src/meta
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-02-07 12:10:52 +0100
committerLaurenz <laurmaedje@gmail.com>2023-02-07 12:10:52 +0100
commit1d5bf56e585c659ed7975cdf6cdfc9c67c29998d (patch)
tree7ebe6e2d99869c5d91b00dcf85c8b54b8865cb84 /library/src/meta
parenta0674990d3c318bb2e42e4d5fad66ea5477a54bf (diff)
Remove basics section
Diffstat (limited to 'library/src/meta')
-rw-r--r--library/src/meta/heading.rs200
-rw-r--r--library/src/meta/mod.rs6
-rw-r--r--library/src/meta/outline.rs2
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};