diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-11 17:42:40 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-11 18:29:48 +0100 |
| commit | 529d3e10c6b4d973e88b6c295eb22a45ea426e42 (patch) | |
| tree | 1815aa9336e71565e24d94bacccb98f09e91b693 /library | |
| parent | 8e5f446544fd147277ed2e4208c7ea793cc846a7 (diff) | |
Section references
Diffstat (limited to 'library')
| -rw-r--r-- | library/src/layout/mod.rs | 14 | ||||
| -rw-r--r-- | library/src/meta/heading.rs | 73 | ||||
| -rw-r--r-- | library/src/meta/numbering.rs | 17 | ||||
| -rw-r--r-- | library/src/meta/outline.rs | 62 | ||||
| -rw-r--r-- | library/src/meta/reference.rs | 141 | ||||
| -rw-r--r-- | library/src/prelude.rs | 4 | ||||
| -rw-r--r-- | library/src/text/raw.rs | 10 |
7 files changed, 231 insertions, 90 deletions
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index dc373ff5..eb440b7f 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -232,20 +232,6 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { self.scratch.content.alloc(FormulaNode::new(content.clone()).pack()); } - // Prepare only if this is the first application for this node. - if content.can::<dyn Prepare>() { - if !content.is_prepared() { - let prepared = content - .clone() - .prepared() - .with::<dyn Prepare>() - .unwrap() - .prepare(self.vt, styles)?; - let stored = self.scratch.content.alloc(prepared); - return self.accept(stored, styles); - } - } - if let Some(styled) = content.to::<StyledNode>() { return self.styled(styled, styles); } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index 8677aa55..1bff3af4 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -40,7 +40,7 @@ use crate::text::{TextNode, TextSize}; /// /// Display: Heading /// Category: meta -#[node(Prepare, Show, Finalize)] +#[node(Synthesize, Show, Finalize)] pub struct HeadingNode { /// The logical nesting depth of the heading, starting from one. #[default(NonZeroUsize::new(1).unwrap())] @@ -76,44 +76,61 @@ pub struct HeadingNode { /// The heading's title. #[required] pub body: Content, + + /// The heading's numbering numbers. + /// + /// ```example + /// #show heading: it => it.numbers + /// + /// = First + /// == Second + /// = Third + /// ``` + #[synthesized] + pub numbers: Option<Vec<NonZeroUsize>>, } -impl Prepare for HeadingNode { - fn prepare(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { +impl Synthesize for HeadingNode { + fn synthesize(&self, vt: &mut Vt, styles: StyleChain) -> Content { let my_id = vt.identify(self); + let numbered = self.numbering(styles).is_some(); let mut counter = HeadingCounter::new(); - for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) { - if node_id == my_id { - break; + if numbered { + // Advance passed existing headings. + for (_, node) in vt + .locate(Selector::node::<HeadingNode>()) + .into_iter() + .take_while(|&(id, _)| id != my_id) + { + let heading = node.to::<HeadingNode>().unwrap(); + if heading.numbering(StyleChain::default()).is_some() { + counter.advance(heading); + } } - let numbers = node.field("numbers").unwrap(); - if *numbers != Value::None { - let heading = node.to::<Self>().unwrap(); - counter.advance(heading); - } + // Advance passed self. + counter.advance(self); } - let mut numbers = Value::None; - if let Some(numbering) = self.numbering(styles) { - numbers = numbering.apply(vt.world(), counter.advance(self))?; - } + let node = self + .clone() + .with_outlined(self.outlined(styles)) + .with_numbering(self.numbering(styles)) + .with_numbers(numbered.then(|| counter.take())) + .pack(); - let mut node = self.clone().pack(); - node.push_field("outlined", Value::Bool(self.outlined(styles))); - node.push_field("numbers", numbers); let meta = Meta::Node(my_id, node.clone()); - Ok(node.styled(MetaNode::set_data(vec![meta]))) + node.styled(MetaNode::set_data(vec![meta])) } } impl Show for HeadingNode { - fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { + fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { let mut realized = self.body(); - let numbers = self.0.field("numbers").unwrap(); - if *numbers != Value::None { - realized = numbers.clone().display() + if let Some(numbering) = self.numbering(styles) { + let numbers = self.numbers().unwrap(); + realized = numbering.apply(vt.world(), &numbers)?.display() + HNode::new(Em::new(0.3).into()).with_weak(true).pack() + realized; } @@ -168,4 +185,14 @@ impl HeadingCounter { &self.0 } + + /// Take out the current counts. + pub fn take(self) -> Vec<NonZeroUsize> { + self.0 + } +} + +cast_from_value! { + HeadingNode, + v: Content => v.to::<Self>().ok_or("expected heading")?.clone(), } diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs index 4e6e1aed..d71fb233 100644 --- a/library/src/meta/numbering.rs +++ b/library/src/meta/numbering.rs @@ -82,7 +82,7 @@ impl Numbering { numbers: &[NonZeroUsize], ) -> SourceResult<Value> { Ok(match self { - Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()), + Self::Pattern(pattern) => Value::Str(pattern.apply(numbers, false).into()), Self::Func(func) => { let args = Args::new( func.span(), @@ -124,12 +124,16 @@ pub struct NumberingPattern { impl NumberingPattern { /// Apply the pattern to the given number. - pub fn apply(&self, numbers: &[NonZeroUsize]) -> EcoString { + pub fn apply(&self, numbers: &[NonZeroUsize], trimmed: bool) -> EcoString { let mut fmt = EcoString::new(); let mut numbers = numbers.into_iter(); - for ((prefix, kind, case), &n) in self.pieces.iter().zip(&mut numbers) { - fmt.push_str(prefix); + for (i, ((prefix, kind, case), &n)) in + self.pieces.iter().zip(&mut numbers).enumerate() + { + if i > 0 || !trimmed { + fmt.push_str(prefix); + } fmt.push_str(&kind.apply(n, *case)); } @@ -144,7 +148,10 @@ impl NumberingPattern { fmt.push_str(&kind.apply(n, *case)); } - fmt.push_str(&self.suffix); + if !trimmed { + fmt.push_str(&self.suffix); + } + fmt } diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index d66a573d..a2b12511 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -22,7 +22,7 @@ use crate::text::{LinebreakNode, SpaceNode, TextNode}; /// /// Display: Outline /// Category: meta -#[node(Prepare, Show)] +#[node(Synthesize, Show)] pub struct OutlineNode { /// The title of the outline. /// @@ -67,21 +67,22 @@ pub struct OutlineNode { /// ``` #[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))] pub fill: Option<Content>, + + /// All outlined headings in the document. + #[synthesized] + pub headings: Vec<HeadingNode>, } -impl Prepare for OutlineNode { - fn prepare(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> { +impl Synthesize for OutlineNode { + fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content { let headings = vt .locate(Selector::node::<HeadingNode>()) .into_iter() - .map(|(_, node)| node) - .filter(|node| *node.field("outlined").unwrap() == Value::Bool(true)) - .map(|node| Value::Content(node.clone())) + .map(|(_, node)| node.to::<HeadingNode>().unwrap().clone()) + .filter(|node| node.outlined(StyleChain::default())) .collect(); - let mut node = self.clone().pack(); - node.push_field("headings", Value::Array(Array::from_vec(headings))); - Ok(node) + self.clone().with_headings(headings).pack() } } @@ -108,13 +109,12 @@ impl Show for OutlineNode { let indent = self.indent(styles); let depth = self.depth(styles); - let mut ancestors: Vec<&Content> = vec![]; - for (_, node) in vt.locate(Selector::node::<HeadingNode>()) { - if *node.field("outlined").unwrap() != Value::Bool(true) { + let mut ancestors: Vec<&HeadingNode> = vec![]; + for heading in self.headings().iter() { + if !heading.outlined(StyleChain::default()) { continue; } - let heading = node.to::<HeadingNode>().unwrap(); if let Some(depth) = depth { if depth < heading.level(StyleChain::default()) { continue; @@ -122,37 +122,40 @@ impl Show for OutlineNode { } while ancestors.last().map_or(false, |last| { - last.to::<HeadingNode>().unwrap().level(StyleChain::default()) - >= heading.level(StyleChain::default()) + last.level(StyleChain::default()) >= heading.level(StyleChain::default()) }) { ancestors.pop(); } // Adjust the link destination a bit to the topleft so that the // heading is fully visible. - let mut loc = node.field("loc").unwrap().clone().cast::<Location>().unwrap(); + let mut loc = heading.0.expect_field::<Location>("location"); loc.pos -= Point::splat(Abs::pt(10.0)); // Add hidden ancestors numberings to realize the indent. if indent { - let hidden: Vec<_> = ancestors - .iter() - .map(|node| node.field("numbers").unwrap()) - .filter(|&numbers| *numbers != Value::None) - .map(|numbers| numbers.clone().display() + SpaceNode::new().pack()) - .collect(); - - if !hidden.is_empty() { - seq.push(HideNode::new(Content::sequence(hidden)).pack()); + let mut hidden = Content::empty(); + for ancestor in &ancestors { + if let Some(numbering) = ancestor.numbering(StyleChain::default()) { + let numbers = ancestor.numbers().unwrap(); + hidden += numbering.apply(vt.world(), &numbers)?.display() + + SpaceNode::new().pack(); + }; + } + + if !ancestors.is_empty() { + seq.push(HideNode::new(hidden).pack()); seq.push(SpaceNode::new().pack()); } } // Format the numbering. let mut start = heading.body(); - let numbers = node.field("numbers").unwrap(); - if *numbers != Value::None { - start = numbers.clone().display() + SpaceNode::new().pack() + start; + if let Some(numbering) = heading.numbering(StyleChain::default()) { + let numbers = heading.numbers().unwrap(); + start = numbering.apply(vt.world(), &numbers)?.display() + + SpaceNode::new().pack() + + start; }; // Add the numbering and section name. @@ -176,8 +179,7 @@ impl Show for OutlineNode { let end = TextNode::packed(eco_format!("{}", loc.page)); seq.push(end.linked(Destination::Internal(loc))); seq.push(LinebreakNode::new().pack()); - - ancestors.push(node); + ancestors.push(heading); } seq.push(ParbreakNode::new().pack()); diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index bfc31785..a46198bd 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -1,31 +1,152 @@ +use super::{HeadingNode, Numbering}; use crate::prelude::*; use crate::text::TextNode; /// A reference to a label. /// -/// *Note: This function is currently unimplemented.* -/// /// The reference function produces a textual reference to a label. For example, /// a reference to a heading will yield an appropriate string such as "Section -/// 1" for a reference to the first heading's label. The references are also -/// links to the respective labels. +/// 1" for a reference to the first heading. The references are also links to +/// the respective element. +/// +/// # Example +/// ```example +/// #set heading(numbering: "1.") +/// +/// = Introduction <intro> +/// Recent developments in typesetting +/// software have rekindled hope in +/// previously frustrated researchers. +/// As shown in @results, we ... +/// +/// = Results <results> +/// We evaluate our method in a +/// series of tests. @perf discusses +/// the performance aspects of ... +/// +/// == Performance <perf> +/// As described in @intro, we ... +/// ``` /// /// ## Syntax /// This function also has dedicated syntax: A reference to a label can be -/// created by typing an `@` followed by the name of the label (e.g. `[= -/// Introduction <intro>]` can be referenced by typing `[@intro]`). +/// created by typing an `@` followed by the name of the label (e.g. +/// `[= Introduction <intro>]` can be referenced by typing `[@intro]`). /// /// Display: Reference /// Category: meta -#[node(Show)] +#[node(Synthesize, Show)] pub struct RefNode { /// The label that should be referenced. #[required] - pub target: EcoString, + pub label: Label, + + /// The prefix before the referenced number. + /// + /// ```example + /// #set heading(numbering: "1.") + /// #set ref(prefix: it => { + /// if it.func() == heading { + /// "Chapter" + /// } else { + /// "Thing" + /// } + /// }) + /// + /// = Introduction <intro> + /// In @intro, we see how to turn + /// Sections into Chapters. + /// ``` + pub prefix: Smart<Option<Func>>, + + /// All elements with the `target` label in the document. + #[synthesized] + pub matches: Vec<Content>, +} + +impl Synthesize for RefNode { + fn synthesize(&self, vt: &mut Vt, _: StyleChain) -> Content { + let matches = vt + .locate(Selector::Label(self.label())) + .into_iter() + .map(|(_, node)| node.clone()) + .collect(); + + self.clone().with_matches(matches).pack() + } } impl Show for RefNode { - fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> { - Ok(TextNode::packed(eco_format!("@{}", self.target()))) + fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { + let matches = self.matches(); + let [target] = matches.as_slice() else { + if vt.locatable() { + bail!(self.span(), if matches.is_empty() { + "label does not exist in the document" + } else { + "label occurs multiple times in the document" + }); + } else { + return Ok(Content::empty()); + } + }; + + let mut prefix = match self.prefix(styles) { + Smart::Auto => prefix(target, TextNode::lang_in(styles)) + .map(TextNode::packed) + .unwrap_or_default(), + Smart::Custom(None) => Content::empty(), + Smart::Custom(Some(func)) => { + let args = Args::new(func.span(), [target.clone().into()]); + func.call_detached(vt.world(), args)?.display() + } + }; + + if !prefix.is_empty() { + prefix += TextNode::packed('\u{a0}'); + } + + let formatted = if let Some(heading) = target.to::<HeadingNode>() { + if let Some(numbering) = heading.numbering(StyleChain::default()) { + let numbers = heading.numbers().unwrap(); + numbered(vt, prefix, &numbering, &numbers)? + } else { + bail!(self.span(), "cannot reference unnumbered heading"); + } + } else { + bail!(self.span(), "cannot reference {}", target.id().name); + }; + + let loc = target.expect_field::<Location>("location"); + Ok(formatted.linked(Destination::Internal(loc))) + } +} + +/// Generate a numbered reference like "Section 1.1". +fn numbered( + vt: &Vt, + prefix: Content, + numbering: &Numbering, + numbers: &[NonZeroUsize], +) -> SourceResult<Content> { + Ok(prefix + + match numbering { + Numbering::Pattern(pattern) => { + TextNode::packed(pattern.apply(&numbers, true)) + } + Numbering::Func(_) => numbering.apply(vt.world(), &numbers)?.display(), + }) +} + +/// The default prefix. +fn prefix(node: &Content, lang: Lang) -> Option<&str> { + if node.is::<HeadingNode>() { + match lang { + Lang::ENGLISH => Some("Section"), + Lang::GERMAN => Some("Abschnitt"), + _ => None, + } + } else { + None } } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 49afc9ca..a9b19f58 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -22,8 +22,8 @@ pub use typst::eval::{ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ - node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Prepare, - Resolve, Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, + node, Construct, Content, Finalize, Fold, Introspector, Label, Node, NodeId, Resolve, + Selector, Set, Show, StabilityProvider, StyleChain, StyleMap, StyleVec, Synthesize, Unlabellable, Vt, }; #[doc(no_inline)] diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 99fb89d2..3f03ba8e 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -35,7 +35,7 @@ use crate::prelude::*; /// /// Display: Raw Text / Code /// Category: text -#[node(Prepare, Show, Finalize)] +#[node(Synthesize, Show, Finalize)] pub struct RawNode { /// The raw text. /// @@ -120,11 +120,9 @@ impl RawNode { } } -impl Prepare for RawNode { - fn prepare(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> { - let mut node = self.clone().pack(); - node.push_field("lang", self.lang(styles).clone()); - Ok(node) +impl Synthesize for RawNode { + fn synthesize(&self, _: &mut Vt, styles: StyleChain) -> Content { + self.clone().with_lang(self.lang(styles)).pack() } } |
