mirror of
https://github.com/typst/typst
synced 2025-08-03 09:47:54 +08:00
fix: footnotes referencing other footnotes
This commit is contained in:
parent
be04cdb029
commit
e4825f7957
@ -384,15 +384,16 @@ const QUOTE_RULE: ShowFn<QuoteElem> = |elem, _, styles| {
|
|||||||
|
|
||||||
const FOOTNOTE_RULE: ShowFn<FootnoteElem> = |elem, engine, styles| {
|
const FOOTNOTE_RULE: ShowFn<FootnoteElem> = |elem, engine, styles| {
|
||||||
let span = elem.span();
|
let span = elem.span();
|
||||||
let loc = elem.declaration_location(engine).at(span)?;
|
let decl_loc = elem.declaration_location(engine).at(span)?;
|
||||||
let numbering = elem.numbering.get_ref(styles);
|
let numbering = elem.numbering.get_ref(styles);
|
||||||
let counter = Counter::of(FootnoteElem::ELEM);
|
let counter = Counter::of(FootnoteElem::ELEM);
|
||||||
let num = counter.display_at_loc(engine, loc, styles, numbering)?;
|
let num = counter.display_at_loc(engine, decl_loc, styles, numbering)?;
|
||||||
let alt = FootnoteElem::alt_text(styles, &num.plain_text());
|
let alt = FootnoteElem::alt_text(styles, &num.plain_text());
|
||||||
let sup = PdfMarkerTag::Label(SuperElem::new(num).pack().spanned(span));
|
let sup = PdfMarkerTag::Label(SuperElem::new(num).pack().spanned(span));
|
||||||
let loc = loc.variant(1);
|
let loc = decl_loc.variant(1);
|
||||||
// Add zero-width weak spacing to make the footnote "sticky".
|
// Add zero-width weak spacing to make the footnote "sticky".
|
||||||
Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc), Some(alt)))
|
let link = sup.linked(Destination::Location(loc), Some(alt));
|
||||||
|
Ok(PdfMarkerTag::FootnoteRef(decl_loc, HElem::hole().pack() + link))
|
||||||
};
|
};
|
||||||
|
|
||||||
const FOOTNOTE_ENTRY_RULE: ShowFn<FootnoteEntry> = |elem, engine, styles| {
|
const FOOTNOTE_ENTRY_RULE: ShowFn<FootnoteEntry> = |elem, engine, styles| {
|
||||||
|
@ -8,7 +8,7 @@ use crate::diag::SourceResult;
|
|||||||
use crate::diag::bail;
|
use crate::diag::bail;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{Args, Construct, Content, NativeElement, Smart};
|
use crate::foundations::{Args, Construct, Content, NativeElement, Smart};
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::{Locatable, Location};
|
||||||
use crate::model::TableCell;
|
use crate::model::TableCell;
|
||||||
|
|
||||||
/// Mark content as a PDF artifact.
|
/// Mark content as a PDF artifact.
|
||||||
@ -149,6 +149,8 @@ pdf_marker_tag! {
|
|||||||
OutlineBody,
|
OutlineBody,
|
||||||
/// `Figure`
|
/// `Figure`
|
||||||
FigureBody(alt: Option<EcoString>),
|
FigureBody(alt: Option<EcoString>),
|
||||||
|
/// `Note` footnote reference
|
||||||
|
FootnoteRef(decl_loc: Location),
|
||||||
/// `L` bibliography list
|
/// `L` bibliography list
|
||||||
Bibliography(numbered: bool),
|
Bibliography(numbered: bool),
|
||||||
/// `LBody` wrapping `BibEntry`
|
/// `LBody` wrapping `BibEntry`
|
||||||
|
@ -21,9 +21,9 @@ use typst_library::introspection::Location;
|
|||||||
use typst_library::layout::{Abs, Point, Rect, RepeatElem};
|
use typst_library::layout::{Abs, Point, Rect, RepeatElem};
|
||||||
use typst_library::math::EquationElem;
|
use typst_library::math::EquationElem;
|
||||||
use typst_library::model::{
|
use typst_library::model::{
|
||||||
Destination, EnumElem, FigureCaption, FigureElem, FootnoteElem, FootnoteEntry,
|
Destination, EnumElem, FigureCaption, FigureElem, FootnoteEntry, HeadingElem,
|
||||||
HeadingElem, ListElem, Outlinable, OutlineEntry, ParElem, QuoteElem, TableCell,
|
ListElem, Outlinable, OutlineEntry, ParElem, QuoteElem, TableCell, TableElem,
|
||||||
TableElem, TermsElem,
|
TermsElem,
|
||||||
};
|
};
|
||||||
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfMarkerTag, PdfMarkerTagKind};
|
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfMarkerTag, PdfMarkerTagKind};
|
||||||
use typst_library::visualize::ImageElem;
|
use typst_library::visualize::ImageElem;
|
||||||
@ -74,6 +74,10 @@ pub(crate) fn handle_start(
|
|||||||
push_stack(gc, elem, StackEntryKind::Figure(FigureCtx::new(alt)))?;
|
push_stack(gc, elem, StackEntryKind::Figure(FigureCtx::new(alt)))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
PdfMarkerTagKind::FootnoteRef(decl_loc) => {
|
||||||
|
push_stack(gc, elem, StackEntryKind::FootnoteRef(*decl_loc))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
PdfMarkerTagKind::Bibliography(numbered) => {
|
PdfMarkerTagKind::Bibliography(numbered) => {
|
||||||
let numbering =
|
let numbering =
|
||||||
if *numbered { ListNumbering::Decimal } else { ListNumbering::None };
|
if *numbered { ListNumbering::Decimal } else { ListNumbering::None };
|
||||||
@ -167,12 +171,9 @@ pub(crate) fn handle_start(
|
|||||||
let link_id = gc.tags.next_link_id();
|
let link_id = gc.tags.next_link_id();
|
||||||
push_stack(gc, elem, StackEntryKind::Link(link_id, link.clone()))?;
|
push_stack(gc, elem, StackEntryKind::Link(link_id, link.clone()))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if let Some(_) = elem.to_packed::<FootnoteElem>() {
|
|
||||||
push_stack(gc, elem, StackEntryKind::FootNoteRef)?;
|
|
||||||
return Ok(());
|
|
||||||
} else if let Some(entry) = elem.to_packed::<FootnoteEntry>() {
|
} else if let Some(entry) = elem.to_packed::<FootnoteEntry>() {
|
||||||
let footnote_loc = entry.note.location().unwrap();
|
let footnote_loc = entry.note.location().unwrap();
|
||||||
push_stack(gc, elem, StackEntryKind::FootNoteEntry(footnote_loc))?;
|
push_stack(gc, elem, StackEntryKind::FootnoteEntry(footnote_loc))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if let Some(quote) = elem.to_packed::<QuoteElem>() {
|
} else if let Some(quote) = elem.to_packed::<QuoteElem>() {
|
||||||
// TODO: should the attribution be handled somehow?
|
// TODO: should the attribution be handled somehow?
|
||||||
@ -244,7 +245,7 @@ pub(crate) fn handle_end(
|
|||||||
|
|
||||||
if let Some(entry) = gc.tags.stack.pop_if(|e| e.loc == loc) {
|
if let Some(entry) = gc.tags.stack.pop_if(|e| e.loc == loc) {
|
||||||
// The tag nesting was properly closed.
|
// The tag nesting was properly closed.
|
||||||
pop_stack(gc, loc, entry);
|
pop_stack(gc, entry);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,12 +305,12 @@ pub(crate) fn handle_end(
|
|||||||
kind,
|
kind,
|
||||||
nodes: Vec::new(),
|
nodes: Vec::new(),
|
||||||
});
|
});
|
||||||
pop_stack(gc, loc, entry);
|
pop_stack(gc, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop the closed entry off the stack.
|
// Pop the closed entry off the stack.
|
||||||
let closed = gc.tags.stack.pop().unwrap();
|
let closed = gc.tags.stack.pop().unwrap();
|
||||||
pop_stack(gc, loc, closed);
|
pop_stack(gc, closed);
|
||||||
|
|
||||||
// Push all broken and afterwards duplicated entries back on.
|
// Push all broken and afterwards duplicated entries back on.
|
||||||
gc.tags.stack.extend(broken_entries);
|
gc.tags.stack.extend(broken_entries);
|
||||||
@ -317,7 +318,7 @@ pub(crate) fn handle_end(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_stack(gc: &mut GlobalContext, loc: Location, entry: StackEntry) {
|
fn pop_stack(gc: &mut GlobalContext, entry: StackEntry) {
|
||||||
let node = match entry.kind {
|
let node = match entry.kind {
|
||||||
StackEntryKind::Standard(tag) => TagNode::Group(tag, entry.nodes),
|
StackEntryKind::Standard(tag) => TagNode::Group(tag, entry.nodes),
|
||||||
StackEntryKind::Outline(ctx) => ctx.build_outline(entry.nodes),
|
StackEntryKind::Outline(ctx) => ctx.build_outline(entry.nodes),
|
||||||
@ -382,17 +383,25 @@ fn pop_stack(gc: &mut GlobalContext, loc: Location, entry: StackEntry) {
|
|||||||
}
|
}
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
StackEntryKind::FootNoteRef => {
|
StackEntryKind::FootnoteRef(decl_loc) => {
|
||||||
// transparently inset all children.
|
// transparently insert all children.
|
||||||
gc.tags.extend(entry.nodes);
|
gc.tags.extend(entry.nodes);
|
||||||
gc.tags.push(TagNode::FootnoteEntry(loc));
|
|
||||||
|
let ctx = gc.tags.footnotes.entry(decl_loc).or_insert(FootnoteCtx::new());
|
||||||
|
|
||||||
|
// Only insert the footnote entry once after the first reference.
|
||||||
|
if !ctx.is_referenced {
|
||||||
|
ctx.is_referenced = true;
|
||||||
|
gc.tags.push(TagNode::FootnoteEntry(decl_loc));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StackEntryKind::FootNoteEntry(footnote_loc) => {
|
StackEntryKind::FootnoteEntry(footnote_loc) => {
|
||||||
// Store footnotes separately so they can be inserted directly after
|
// Store footnotes separately so they can be inserted directly after
|
||||||
// the footnote reference in the reading order.
|
// the footnote reference in the reading order.
|
||||||
let tag = TagNode::Group(Tag::Note.into(), entry.nodes);
|
let tag = TagNode::Group(Tag::Note.into(), entry.nodes);
|
||||||
gc.tags.footnotes.insert(footnote_loc, tag);
|
let ctx = gc.tags.footnotes.entry(footnote_loc).or_insert(FootnoteCtx::new());
|
||||||
|
ctx.entry = Some(tag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -473,7 +482,7 @@ pub(crate) struct Tags {
|
|||||||
/// reading order. Because of some layouting bugs, the entry might appear
|
/// reading order. Because of some layouting bugs, the entry might appear
|
||||||
/// before the reference in the text, so we only resolve them once tags
|
/// before the reference in the text, so we only resolve them once tags
|
||||||
/// for the whole document are generated.
|
/// for the whole document are generated.
|
||||||
pub(crate) footnotes: HashMap<Location, TagNode>,
|
pub(crate) footnotes: HashMap<Location, FootnoteCtx>,
|
||||||
pub(crate) in_artifact: Option<(Location, ArtifactKind)>,
|
pub(crate) in_artifact: Option<(Location, ArtifactKind)>,
|
||||||
/// Used to group multiple link annotations using quad points.
|
/// Used to group multiple link annotations using quad points.
|
||||||
link_id: LinkId,
|
link_id: LinkId,
|
||||||
@ -537,7 +546,9 @@ impl Tags {
|
|||||||
TagNode::Leaf(identifier) => Node::Leaf(identifier),
|
TagNode::Leaf(identifier) => Node::Leaf(identifier),
|
||||||
TagNode::Placeholder(placeholder) => self.placeholders.take(placeholder),
|
TagNode::Placeholder(placeholder) => self.placeholders.take(placeholder),
|
||||||
TagNode::FootnoteEntry(loc) => {
|
TagNode::FootnoteEntry(loc) => {
|
||||||
let node = self.footnotes.remove(&loc).expect("footnote");
|
let node = (self.footnotes.remove(&loc))
|
||||||
|
.and_then(|ctx| ctx.entry)
|
||||||
|
.expect("footnote");
|
||||||
self.resolve_node(node)
|
self.resolve_node(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -738,11 +749,11 @@ pub(crate) enum StackEntryKind {
|
|||||||
Figure(FigureCtx),
|
Figure(FigureCtx),
|
||||||
Formula(FigureCtx),
|
Formula(FigureCtx),
|
||||||
Link(LinkId, Packed<LinkMarker>),
|
Link(LinkId, Packed<LinkMarker>),
|
||||||
/// The footnote reference in the text.
|
/// The footnote reference in the text, contains the declaration location.
|
||||||
FootNoteRef,
|
FootnoteRef(Location),
|
||||||
/// The footnote entry at the end of the page. Contains the [`Location`] of
|
/// The footnote entry at the end of the page. Contains the [`Location`] of
|
||||||
/// the [`FootnoteElem`](typst_library::model::FootnoteElem).
|
/// the [`FootnoteElem`](typst_library::model::FootnoteElem).
|
||||||
FootNoteEntry(Location),
|
FootnoteEntry(Location),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackEntryKind {
|
impl StackEntryKind {
|
||||||
@ -836,12 +847,28 @@ impl StackEntryKind {
|
|||||||
StackEntryKind::Figure(_) => false,
|
StackEntryKind::Figure(_) => false,
|
||||||
StackEntryKind::Formula(_) => false,
|
StackEntryKind::Formula(_) => false,
|
||||||
StackEntryKind::Link(..) => !is_pdf_ua,
|
StackEntryKind::Link(..) => !is_pdf_ua,
|
||||||
StackEntryKind::FootNoteRef => false,
|
StackEntryKind::FootnoteRef(_) => false,
|
||||||
StackEntryKind::FootNoteEntry(_) => false,
|
StackEntryKind::FootnoteEntry(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub(crate) struct FootnoteCtx {
|
||||||
|
/// Whether this footenote has been referenced inside the document. The
|
||||||
|
/// entry will be inserted inside the reading order after the first
|
||||||
|
/// reference. All other references will still have links to the footnote.
|
||||||
|
is_referenced: bool,
|
||||||
|
/// The nodes that make up the footnote entry.
|
||||||
|
entry: Option<TagNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FootnoteCtx {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { is_referenced: false, entry: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Figure/Formula context
|
/// Figure/Formula context
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct FigureCtx {
|
pub(crate) struct FigureCtx {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user