summaryrefslogtreecommitdiff
path: root/library/src
diff options
context:
space:
mode:
authorMALO <57839069+MDLC01@users.noreply.github.com>2023-06-26 13:40:52 +0200
committerGitHub <noreply@github.com>2023-06-26 13:40:52 +0200
commit33803b16148ec064ccabc9bffd4a9383b969e23e (patch)
tree37727816b2ff4b0bd152086f761847509cf5cb7d /library/src
parent9ef4643ba104acc08cd3a4fce8cfea72253654b1 (diff)
Make footnotes referenceable (#1546)
Diffstat (limited to 'library/src')
-rw-r--r--library/src/layout/flow.rs5
-rw-r--r--library/src/meta/bibliography.rs2
-rw-r--r--library/src/meta/footnote.rs107
-rw-r--r--library/src/meta/reference.rs16
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(|| {