mirror of
https://github.com/typst/typst
synced 2025-08-03 01:37: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| {
|
||||
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 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 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".
|
||||
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| {
|
||||
|
@ -8,7 +8,7 @@ use crate::diag::SourceResult;
|
||||
use crate::diag::bail;
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{Args, Construct, Content, NativeElement, Smart};
|
||||
use crate::introspection::Locatable;
|
||||
use crate::introspection::{Locatable, Location};
|
||||
use crate::model::TableCell;
|
||||
|
||||
/// Mark content as a PDF artifact.
|
||||
@ -149,6 +149,8 @@ pdf_marker_tag! {
|
||||
OutlineBody,
|
||||
/// `Figure`
|
||||
FigureBody(alt: Option<EcoString>),
|
||||
/// `Note` footnote reference
|
||||
FootnoteRef(decl_loc: Location),
|
||||
/// `L` bibliography list
|
||||
Bibliography(numbered: bool),
|
||||
/// `LBody` wrapping `BibEntry`
|
||||
|
@ -21,9 +21,9 @@ use typst_library::introspection::Location;
|
||||
use typst_library::layout::{Abs, Point, Rect, RepeatElem};
|
||||
use typst_library::math::EquationElem;
|
||||
use typst_library::model::{
|
||||
Destination, EnumElem, FigureCaption, FigureElem, FootnoteElem, FootnoteEntry,
|
||||
HeadingElem, ListElem, Outlinable, OutlineEntry, ParElem, QuoteElem, TableCell,
|
||||
TableElem, TermsElem,
|
||||
Destination, EnumElem, FigureCaption, FigureElem, FootnoteEntry, HeadingElem,
|
||||
ListElem, Outlinable, OutlineEntry, ParElem, QuoteElem, TableCell, TableElem,
|
||||
TermsElem,
|
||||
};
|
||||
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfMarkerTag, PdfMarkerTagKind};
|
||||
use typst_library::visualize::ImageElem;
|
||||
@ -74,6 +74,10 @@ pub(crate) fn handle_start(
|
||||
push_stack(gc, elem, StackEntryKind::Figure(FigureCtx::new(alt)))?;
|
||||
return Ok(());
|
||||
}
|
||||
PdfMarkerTagKind::FootnoteRef(decl_loc) => {
|
||||
push_stack(gc, elem, StackEntryKind::FootnoteRef(*decl_loc))?;
|
||||
return Ok(());
|
||||
}
|
||||
PdfMarkerTagKind::Bibliography(numbered) => {
|
||||
let numbering =
|
||||
if *numbered { ListNumbering::Decimal } else { ListNumbering::None };
|
||||
@ -167,12 +171,9 @@ pub(crate) fn handle_start(
|
||||
let link_id = gc.tags.next_link_id();
|
||||
push_stack(gc, elem, StackEntryKind::Link(link_id, link.clone()))?;
|
||||
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>() {
|
||||
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(());
|
||||
} else if let Some(quote) = elem.to_packed::<QuoteElem>() {
|
||||
// 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) {
|
||||
// The tag nesting was properly closed.
|
||||
pop_stack(gc, loc, entry);
|
||||
pop_stack(gc, entry);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -304,12 +305,12 @@ pub(crate) fn handle_end(
|
||||
kind,
|
||||
nodes: Vec::new(),
|
||||
});
|
||||
pop_stack(gc, loc, entry);
|
||||
pop_stack(gc, entry);
|
||||
}
|
||||
|
||||
// Pop the closed entry off the stack.
|
||||
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.
|
||||
gc.tags.stack.extend(broken_entries);
|
||||
@ -317,7 +318,7 @@ pub(crate) fn handle_end(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pop_stack(gc: &mut GlobalContext, loc: Location, entry: StackEntry) {
|
||||
fn pop_stack(gc: &mut GlobalContext, entry: StackEntry) {
|
||||
let node = match entry.kind {
|
||||
StackEntryKind::Standard(tag) => TagNode::Group(tag, 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
|
||||
}
|
||||
StackEntryKind::FootNoteRef => {
|
||||
// transparently inset all children.
|
||||
StackEntryKind::FootnoteRef(decl_loc) => {
|
||||
// transparently insert all children.
|
||||
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;
|
||||
}
|
||||
StackEntryKind::FootNoteEntry(footnote_loc) => {
|
||||
StackEntryKind::FootnoteEntry(footnote_loc) => {
|
||||
// Store footnotes separately so they can be inserted directly after
|
||||
// the footnote reference in the reading order.
|
||||
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;
|
||||
}
|
||||
};
|
||||
@ -473,7 +482,7 @@ pub(crate) struct Tags {
|
||||
/// reading order. Because of some layouting bugs, the entry might appear
|
||||
/// before the reference in the text, so we only resolve them once tags
|
||||
/// 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)>,
|
||||
/// Used to group multiple link annotations using quad points.
|
||||
link_id: LinkId,
|
||||
@ -537,7 +546,9 @@ impl Tags {
|
||||
TagNode::Leaf(identifier) => Node::Leaf(identifier),
|
||||
TagNode::Placeholder(placeholder) => self.placeholders.take(placeholder),
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -738,11 +749,11 @@ pub(crate) enum StackEntryKind {
|
||||
Figure(FigureCtx),
|
||||
Formula(FigureCtx),
|
||||
Link(LinkId, Packed<LinkMarker>),
|
||||
/// The footnote reference in the text.
|
||||
FootNoteRef,
|
||||
/// The footnote reference in the text, contains the declaration location.
|
||||
FootnoteRef(Location),
|
||||
/// The footnote entry at the end of the page. Contains the [`Location`] of
|
||||
/// the [`FootnoteElem`](typst_library::model::FootnoteElem).
|
||||
FootNoteEntry(Location),
|
||||
FootnoteEntry(Location),
|
||||
}
|
||||
|
||||
impl StackEntryKind {
|
||||
@ -836,12 +847,28 @@ impl StackEntryKind {
|
||||
StackEntryKind::Figure(_) => false,
|
||||
StackEntryKind::Formula(_) => false,
|
||||
StackEntryKind::Link(..) => !is_pdf_ua,
|
||||
StackEntryKind::FootNoteRef => false,
|
||||
StackEntryKind::FootNoteEntry(_) => false,
|
||||
StackEntryKind::FootnoteRef(_) => 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
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct FigureCtx {
|
||||
|
Loading…
x
Reference in New Issue
Block a user