diff options
| author | MALO <57839069+MDLC01@users.noreply.github.com> | 2023-06-26 13:40:52 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-26 13:40:52 +0200 |
| commit | 33803b16148ec064ccabc9bffd4a9383b969e23e (patch) | |
| tree | 37727816b2ff4b0bd152086f761847509cf5cb7d /library/src | |
| parent | 9ef4643ba104acc08cd3a4fce8cfea72253654b1 (diff) | |
Make footnotes referenceable (#1546)
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/layout/flow.rs | 5 | ||||
| -rw-r--r-- | library/src/meta/bibliography.rs | 2 | ||||
| -rw-r--r-- | library/src/meta/footnote.rs | 107 | ||||
| -rw-r--r-- | library/src/meta/reference.rs | 16 |
4 files changed, 109 insertions, 21 deletions
diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index c173ef1a..accd092a 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -488,6 +488,11 @@ impl FlowLayouter<'_> { // Process footnotes one at a time. let mut k = 0; while k < notes.len() { + if notes[k].is_ref() { + k += 1; + continue; + } + if !self.has_footnotes { self.layout_footnote_separator(vt)?; } diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index 9bc88f28..0abcf5bb 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -545,7 +545,7 @@ fn create( } if style == CitationStyle::ChicagoNotes { - content = FootnoteElem::new(content).pack(); + content = FootnoteElem::with_content(content).pack(); } (location, Some(content)) diff --git a/library/src/meta/footnote.rs b/library/src/meta/footnote.rs index 950057ba..31ec9fe9 100644 --- a/library/src/meta/footnote.rs +++ b/library/src/meta/footnote.rs @@ -1,15 +1,35 @@ +use comemo::Prehashed; use std::str::FromStr; use super::{Counter, Numbering, NumberingPattern}; use crate::layout::{HElem, ParElem}; +use crate::meta::{Count, CounterUpdate}; use crate::prelude::*; use crate::text::{SuperElem, TextElem, TextSize}; use crate::visualize::LineElem; +/// The body of a footnote can be either some content or a label referencing +/// another footnote. +#[derive(Debug)] +pub enum FootnoteBody { + Content(Content), + Reference(Label), +} + +cast! { + FootnoteBody, + self => match self { + Self::Content(v) => v.into_value(), + Self::Reference(v) => v.into_value(), + }, + v: Content => Self::Content(v), + v: Label => Self::Reference(v), +} + /// A footnote. /// -/// Include additional remarks and references on the same page with footnotes. A -/// footnote will insert a superscript number that links to the note at the +/// Includes additional remarks and references on the same page with footnotes. +/// A footnote will insert a superscript number that links to the note at the /// bottom of the page. Notes are numbered sequentially throughout your document /// and can break across multiple pages. /// @@ -28,6 +48,15 @@ use crate::visualize::LineElem; /// there is a space before it in the markup. To force space, you can use the /// string `[#" "]` or explicit [horizontal spacing]($func/h). /// +/// By giving a label to a footnote, you can have multiple references to it. +/// +/// ```example +/// You can edit Typst documents online. +/// #footnote[https://typst.app/app] <fn> +/// Checkout Typst's website. @fn +/// And the online app. #footnote(<fn>) +/// ``` +/// /// _Note:_ Set and show rules in the scope where `footnote` is called may not /// apply to the footnote's content. See [here][issue] more information. /// @@ -35,7 +64,7 @@ use crate::visualize::LineElem; /// /// Display: Footnote /// Category: meta -#[element(Locatable, Synthesize, Show)] +#[element(Locatable, Synthesize, Show, Count)] #[scope( scope.define("entry", FootnoteEntry::func()); scope @@ -58,9 +87,49 @@ pub struct FootnoteElem { #[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))] pub numbering: Numbering, - /// The content to put into the footnote. + /// The content to put into the footnote. Can also be the label of another + /// footnote this one should point to. #[required] - pub body: Content, + pub body: FootnoteBody, +} + +impl FootnoteElem { + /// Creates a new footnote that the passed content as its body. + pub fn with_content(content: Content) -> Self { + Self::new(FootnoteBody::Content(content)) + } + + /// Creates a new footnote referencing the footnote with the specified label. + pub fn with_label(label: Label) -> Self { + Self::new(FootnoteBody::Reference(label)) + } + + /// Tests if this footnote is a reference to another footnote. + pub fn is_ref(&self) -> bool { + matches!(self.body(), FootnoteBody::Reference(_)) + } + + /// Returns the content of the body of this footnote if it is not a ref. + pub fn body_content(&self) -> Option<Content> { + match self.body() { + FootnoteBody::Content(content) => Some(content), + _ => None, + } + } + + /// Returns the location of the definition of this footnote. + pub fn declaration_location(&self, vt: &Vt) -> StrResult<Location> { + match self.body() { + FootnoteBody::Reference(label) => { + let element: Prehashed<Content> = vt.introspector.query_label(&label)?; + let footnote = element + .to::<FootnoteElem>() + .ok_or("referenced element should be a footnote")?; + footnote.declaration_location(vt) + } + _ => Ok(self.0.location().unwrap()), + } + } } impl Synthesize for FootnoteElem { @@ -73,14 +142,22 @@ impl Synthesize for FootnoteElem { impl Show for FootnoteElem { #[tracing::instrument(name = "FootnoteElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> { - let loc = self.0.location().unwrap(); - let numbering = self.numbering(styles); - let counter = Counter::of(Self::func()); - let num = counter.at(vt, loc)?.display(vt, &numbering)?; - let sup = SuperElem::new(num).pack(); - let hole = HElem::new(Abs::zero().into()).with_weak(true).pack(); - let loc = self.0.location().unwrap().variant(1); - Ok(hole + sup.linked(Destination::Location(loc))) + Ok(vt.delayed(|vt| { + let loc = self.declaration_location(vt).at(self.span())?; + let numbering = self.numbering(styles); + let counter = Counter::of(Self::func()); + let num = counter.at(vt, loc)?.display(vt, &numbering)?; + let sup = SuperElem::new(num).pack(); + let hole = HElem::new(Abs::zero().into()).with_weak(true).pack(); + let loc = loc.variant(1); + Ok(hole + sup.linked(Destination::Location(loc))) + })) + } +} + +impl Count for FootnoteElem { + fn update(&self) -> Option<CounterUpdate> { + (!self.is_ref()).then(|| CounterUpdate::Step(NonZeroUsize::ONE)) } } @@ -201,7 +278,7 @@ impl Show for FootnoteEntry { HElem::new(self.indent(styles).into()).pack(), sup, HElem::new(number_gap.into()).with_weak(true).pack(), - note.body(), + note.body_content().unwrap(), ])) } } @@ -218,5 +295,5 @@ impl Finalize for FootnoteEntry { cast! { FootnoteElem, - v: Content => v.to::<Self>().cloned().unwrap_or_else(|| Self::new(v.clone())), + v: Content => v.to::<Self>().cloned().unwrap_or_else(|| Self::with_content(v.clone())), } diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 26961ec5..90823871 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -1,4 +1,5 @@ use super::{BibliographyElem, CiteElem, Counter, Figurable, Numbering}; +use crate::meta::FootnoteElem; use crate::prelude::*; use crate::text::TextElem; @@ -11,11 +12,11 @@ use crate::text::TextElem; /// bibliography. /// /// Referenceable elements include [headings]($func/heading), -/// [figures]($func/figure), and [equations]($func/math.equation). To create a -/// custom referenceable element like a theorem, you can create a figure of a -/// custom [`kind`]($func/figure.kind) and write a show rule for it. In the -/// future, there might be a more direct way to define a custom referenceable -/// element. +/// [figures]($func/figure), [equations]($func/math.equation), and +/// [footnotes]($func/footnote). To create a custom referenceable element like a +/// theorem, you can create a figure of a custom [`kind`]($func/figure.kind) and +/// write a show rule for it. In the future, there might be a more direct way to +/// define a custom referenceable element. /// /// If you just want to link to a labelled element and not get an automatic /// textual reference, consider using the [`link`]($func/link) function instead. @@ -160,6 +161,11 @@ impl Show for RefElem { } let elem = elem.at(span)?; + + if elem.func() == FootnoteElem::func() { + return Ok(FootnoteElem::with_label(target).pack().spanned(span)); + } + let refable = elem .with::<dyn Refable>() .ok_or_else(|| { |
