mirror of
https://github.com/typst/typst
synced 2025-05-17 10:35:28 +08:00
Make footnotes referenceable (#1546)
This commit is contained in:
parent
9ef4643ba1
commit
33803b1614
@ -488,6 +488,11 @@ impl FlowLayouter<'_> {
|
|||||||
// Process footnotes one at a time.
|
// Process footnotes one at a time.
|
||||||
let mut k = 0;
|
let mut k = 0;
|
||||||
while k < notes.len() {
|
while k < notes.len() {
|
||||||
|
if notes[k].is_ref() {
|
||||||
|
k += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if !self.has_footnotes {
|
if !self.has_footnotes {
|
||||||
self.layout_footnote_separator(vt)?;
|
self.layout_footnote_separator(vt)?;
|
||||||
}
|
}
|
||||||
|
@ -545,7 +545,7 @@ fn create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if style == CitationStyle::ChicagoNotes {
|
if style == CitationStyle::ChicagoNotes {
|
||||||
content = FootnoteElem::new(content).pack();
|
content = FootnoteElem::with_content(content).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
(location, Some(content))
|
(location, Some(content))
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
|
use comemo::Prehashed;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::{Counter, Numbering, NumberingPattern};
|
use super::{Counter, Numbering, NumberingPattern};
|
||||||
use crate::layout::{HElem, ParElem};
|
use crate::layout::{HElem, ParElem};
|
||||||
|
use crate::meta::{Count, CounterUpdate};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{SuperElem, TextElem, TextSize};
|
use crate::text::{SuperElem, TextElem, TextSize};
|
||||||
use crate::visualize::LineElem;
|
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.
|
/// A footnote.
|
||||||
///
|
///
|
||||||
/// Include additional remarks and references on the same page with footnotes. A
|
/// Includes additional remarks and references on the same page with footnotes.
|
||||||
/// footnote will insert a superscript number that links to the note at the
|
/// 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
|
/// bottom of the page. Notes are numbered sequentially throughout your document
|
||||||
/// and can break across multiple pages.
|
/// 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
|
/// there is a space before it in the markup. To force space, you can use the
|
||||||
/// string `[#" "]` or explicit [horizontal spacing]($func/h).
|
/// 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
|
/// _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.
|
/// apply to the footnote's content. See [here][issue] more information.
|
||||||
///
|
///
|
||||||
@ -35,7 +64,7 @@ use crate::visualize::LineElem;
|
|||||||
///
|
///
|
||||||
/// Display: Footnote
|
/// Display: Footnote
|
||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[element(Locatable, Synthesize, Show)]
|
#[element(Locatable, Synthesize, Show, Count)]
|
||||||
#[scope(
|
#[scope(
|
||||||
scope.define("entry", FootnoteEntry::func());
|
scope.define("entry", FootnoteEntry::func());
|
||||||
scope
|
scope
|
||||||
@ -58,9 +87,49 @@ pub struct FootnoteElem {
|
|||||||
#[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))]
|
#[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))]
|
||||||
pub numbering: Numbering,
|
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]
|
#[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 {
|
impl Synthesize for FootnoteElem {
|
||||||
@ -73,14 +142,22 @@ impl Synthesize for FootnoteElem {
|
|||||||
impl Show for FootnoteElem {
|
impl Show for FootnoteElem {
|
||||||
#[tracing::instrument(name = "FootnoteElem::show", skip_all)]
|
#[tracing::instrument(name = "FootnoteElem::show", skip_all)]
|
||||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let loc = self.0.location().unwrap();
|
Ok(vt.delayed(|vt| {
|
||||||
|
let loc = self.declaration_location(vt).at(self.span())?;
|
||||||
let numbering = self.numbering(styles);
|
let numbering = self.numbering(styles);
|
||||||
let counter = Counter::of(Self::func());
|
let counter = Counter::of(Self::func());
|
||||||
let num = counter.at(vt, loc)?.display(vt, &numbering)?;
|
let num = counter.at(vt, loc)?.display(vt, &numbering)?;
|
||||||
let sup = SuperElem::new(num).pack();
|
let sup = SuperElem::new(num).pack();
|
||||||
let hole = HElem::new(Abs::zero().into()).with_weak(true).pack();
|
let hole = HElem::new(Abs::zero().into()).with_weak(true).pack();
|
||||||
let loc = self.0.location().unwrap().variant(1);
|
let loc = loc.variant(1);
|
||||||
Ok(hole + sup.linked(Destination::Location(loc)))
|
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(),
|
HElem::new(self.indent(styles).into()).pack(),
|
||||||
sup,
|
sup,
|
||||||
HElem::new(number_gap.into()).with_weak(true).pack(),
|
HElem::new(number_gap.into()).with_weak(true).pack(),
|
||||||
note.body(),
|
note.body_content().unwrap(),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,5 +295,5 @@ impl Finalize for FootnoteEntry {
|
|||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
FootnoteElem,
|
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())),
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::{BibliographyElem, CiteElem, Counter, Figurable, Numbering};
|
use super::{BibliographyElem, CiteElem, Counter, Figurable, Numbering};
|
||||||
|
use crate::meta::FootnoteElem;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
|
|
||||||
@ -11,11 +12,11 @@ use crate::text::TextElem;
|
|||||||
/// bibliography.
|
/// bibliography.
|
||||||
///
|
///
|
||||||
/// Referenceable elements include [headings]($func/heading),
|
/// Referenceable elements include [headings]($func/heading),
|
||||||
/// [figures]($func/figure), and [equations]($func/math.equation). To create a
|
/// [figures]($func/figure), [equations]($func/math.equation), and
|
||||||
/// custom referenceable element like a theorem, you can create a figure of a
|
/// [footnotes]($func/footnote). To create a custom referenceable element like a
|
||||||
/// custom [`kind`]($func/figure.kind) and write a show rule for it. In the
|
/// theorem, you can create a figure of a custom [`kind`]($func/figure.kind) and
|
||||||
/// future, there might be a more direct way to define a custom referenceable
|
/// write a show rule for it. In the future, there might be a more direct way to
|
||||||
/// element.
|
/// define a custom referenceable element.
|
||||||
///
|
///
|
||||||
/// If you just want to link to a labelled element and not get an automatic
|
/// 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.
|
/// textual reference, consider using the [`link`]($func/link) function instead.
|
||||||
@ -160,6 +161,11 @@ impl Show for RefElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let elem = elem.at(span)?;
|
let elem = elem.at(span)?;
|
||||||
|
|
||||||
|
if elem.func() == FootnoteElem::func() {
|
||||||
|
return Ok(FootnoteElem::with_label(target).pack().spanned(span));
|
||||||
|
}
|
||||||
|
|
||||||
let refable = elem
|
let refable = elem
|
||||||
.with::<dyn Refable>()
|
.with::<dyn Refable>()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
|
BIN
tests/ref/meta/footnote-refs.png
Normal file
BIN
tests/ref/meta/footnote-refs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
40
tests/typ/meta/footnote-refs.typ
Normal file
40
tests/typ/meta/footnote-refs.typ
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Test references to footnotes.
|
||||||
|
|
||||||
|
---
|
||||||
|
A footnote #footnote[Hi]<fn> \
|
||||||
|
A reference to it @fn
|
||||||
|
|
||||||
|
---
|
||||||
|
// Multiple footnotes are refs
|
||||||
|
First #footnote[A]<fn1> \
|
||||||
|
Second #footnote[B]<fn2> \
|
||||||
|
First ref @fn1 \
|
||||||
|
Third #footnote[C] \
|
||||||
|
Fourth #footnote[D]<fn4> \
|
||||||
|
Fourth ref @fn4 \
|
||||||
|
Second ref @fn2 \
|
||||||
|
Second ref again @fn2
|
||||||
|
|
||||||
|
---
|
||||||
|
// Forward reference
|
||||||
|
Usage @fn \
|
||||||
|
Definition #footnote[Hi]<fn>
|
||||||
|
|
||||||
|
---
|
||||||
|
// Footnote ref in footnote
|
||||||
|
#footnote[Reference to next @fn]
|
||||||
|
#footnote[Reference to myself @fn]<fn>
|
||||||
|
#footnote[Reference to previous @fn]
|
||||||
|
|
||||||
|
---
|
||||||
|
// Styling
|
||||||
|
#show footnote: text.with(fill: red)
|
||||||
|
Real #footnote[...]<fn> \
|
||||||
|
Ref @fn
|
||||||
|
|
||||||
|
---
|
||||||
|
// Footnote call with label
|
||||||
|
#footnote(<fn>)
|
||||||
|
#footnote[Hi]<fn>
|
||||||
|
#ref(<fn>)
|
||||||
|
#footnote(<fn>)
|
Loading…
x
Reference in New Issue
Block a user