mirror of
https://github.com/typst/typst
synced 2025-05-15 01:25:28 +08:00
Fix backlinks (#4409)
This commit is contained in:
parent
0d93ccd4bf
commit
a2c9807159
@ -168,6 +168,15 @@ fn is_in_rect(pos: Point, size: Size, click: Point) -> bool {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
//! This can be used in a normal test to determine positions:
|
||||||
|
//! ```
|
||||||
|
//! #set page(background: place(
|
||||||
|
//! dx: 10pt,
|
||||||
|
//! dy: 10pt,
|
||||||
|
//! square(size: 2pt, fill: red),
|
||||||
|
//! ))
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use typst::layout::{Abs, Point, Position};
|
use typst::layout::{Abs, Point, Position};
|
||||||
@ -200,7 +209,16 @@ mod tests {
|
|||||||
fn test_click(text: &str, click: Point, expected: Option<Jump>) {
|
fn test_click(text: &str, click: Point, expected: Option<Jump>) {
|
||||||
let world = TestWorld::new(text);
|
let world = TestWorld::new(text);
|
||||||
let doc = typst::compile(&world).output.unwrap();
|
let doc = typst::compile(&world).output.unwrap();
|
||||||
assert_eq!(jump_from_click(&world, &doc, &doc.pages[0].frame, click), expected);
|
let jump = jump_from_click(&world, &doc, &doc.pages[0].frame, click);
|
||||||
|
if let (Some(Jump::Position(pos)), Some(Jump::Position(expected))) =
|
||||||
|
(&jump, &expected)
|
||||||
|
{
|
||||||
|
assert_eq!(pos.page, expected.page);
|
||||||
|
assert_approx_eq!(pos.point.x, expected.point.x);
|
||||||
|
assert_approx_eq!(pos.point.y, expected.point.y);
|
||||||
|
} else {
|
||||||
|
assert_eq!(jump, expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
@ -240,4 +258,10 @@ mod tests {
|
|||||||
test_cursor(s, 12, None);
|
test_cursor(s, 12, None);
|
||||||
test_cursor(s, 14, pos(1, 37.55, 16.58));
|
test_cursor(s, 14, pos(1, 37.55, 16.58));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_backlink() {
|
||||||
|
let s = "#footnote[Hi]";
|
||||||
|
test_click(s, point(10.0, 10.0), pos(1, 18.5, 37.1).map(Jump::Position));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,20 +137,26 @@ impl Content {
|
|||||||
self.inner.label
|
self.inner.label
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the label of the content.
|
/// Attach a label to the content.
|
||||||
pub fn labelled(mut self, label: Label) -> Self {
|
pub fn labelled(mut self, label: Label) -> Self {
|
||||||
self.make_mut().label = Some(label);
|
self.set_label(label);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether a show rule recipe is disabled.
|
/// Set the label of the content.
|
||||||
pub fn is_guarded(&self, index: RecipeIndex) -> bool {
|
pub fn set_label(&mut self, label: Label) {
|
||||||
self.inner.lifecycle.contains(index.0)
|
self.make_mut().label = Some(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this content has already been prepared.
|
/// Assigns a location to the content.
|
||||||
pub fn is_prepared(&self) -> bool {
|
///
|
||||||
self.inner.lifecycle.contains(0)
|
/// This identifies the content and e.g. makes it linkable by
|
||||||
|
/// `.linked(Destination::Location(loc))`.
|
||||||
|
///
|
||||||
|
/// Useful in combination with [`Location::variant`].
|
||||||
|
pub fn located(mut self, loc: Location) -> Self {
|
||||||
|
self.set_location(loc);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the location of the content.
|
/// Set the location of the content.
|
||||||
@ -158,12 +164,22 @@ impl Content {
|
|||||||
self.make_mut().location = Some(location);
|
self.make_mut().location = Some(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether a show rule recipe is disabled.
|
||||||
|
pub fn is_guarded(&self, index: RecipeIndex) -> bool {
|
||||||
|
self.inner.lifecycle.contains(index.0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Disable a show rule recipe.
|
/// Disable a show rule recipe.
|
||||||
pub fn guarded(mut self, index: RecipeIndex) -> Self {
|
pub fn guarded(mut self, index: RecipeIndex) -> Self {
|
||||||
self.make_mut().lifecycle.insert(index.0);
|
self.make_mut().lifecycle.insert(index.0);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this content has already been prepared.
|
||||||
|
pub fn is_prepared(&self) -> bool {
|
||||||
|
self.inner.lifecycle.contains(0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Mark this content as prepared.
|
/// Mark this content as prepared.
|
||||||
pub fn mark_prepared(&mut self) {
|
pub fn mark_prepared(&mut self) {
|
||||||
self.make_mut().lifecycle.insert(0);
|
self.make_mut().lifecycle.insert(0);
|
||||||
@ -488,15 +504,6 @@ impl Content {
|
|||||||
self.styled(LinkElem::set_dests(smallvec![dest]))
|
self.styled(LinkElem::set_dests(smallvec![dest]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make the content linkable by `.linked(Destination::Location(loc))`.
|
|
||||||
///
|
|
||||||
/// Should be used in combination with [`Location::variant`].
|
|
||||||
pub fn backlinked(self, loc: Location) -> Self {
|
|
||||||
let mut backlink = Content::empty().spanned(self.span());
|
|
||||||
backlink.set_location(loc);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set alignments for this content.
|
/// Set alignments for this content.
|
||||||
pub fn aligned(self, align: Alignment) -> Self {
|
pub fn aligned(self, align: Alignment) -> Self {
|
||||||
self.styled(AlignElem::set_alignment(align))
|
self.styled(AlignElem::set_alignment(align))
|
||||||
|
@ -833,13 +833,16 @@ impl<'a> Generator<'a> {
|
|||||||
let dest = Destination::Location(*location);
|
let dest = Destination::Location(*location);
|
||||||
content = content.linked(dest);
|
content = content.linked(dest);
|
||||||
}
|
}
|
||||||
content.backlinked(backlink)
|
content
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render the main reference content.
|
// Render the main reference content.
|
||||||
let reference = renderer
|
let mut reference =
|
||||||
.display_elem_children(&item.content, &mut prefix)
|
renderer.display_elem_children(&item.content, &mut prefix);
|
||||||
.backlinked(backlink);
|
|
||||||
|
// Attach a backlink to either the prefix or the reference so that
|
||||||
|
// we can link to the bibliography entry.
|
||||||
|
prefix.as_mut().unwrap_or(&mut reference).set_location(backlink);
|
||||||
|
|
||||||
output.push((prefix, reference));
|
output.push((prefix, reference));
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,8 @@ impl Show for Packed<FootnoteEntry> {
|
|||||||
.pack()
|
.pack()
|
||||||
.spanned(span)
|
.spanned(span)
|
||||||
.linked(Destination::Location(loc))
|
.linked(Destination::Location(loc))
|
||||||
.backlinked(loc.variant(1));
|
.located(loc.variant(1));
|
||||||
|
|
||||||
Ok(Content::sequence([
|
Ok(Content::sequence([
|
||||||
HElem::new(self.indent(styles).into()).pack(),
|
HElem::new(self.indent(styles).into()).pack(),
|
||||||
sup,
|
sup,
|
||||||
|
@ -119,6 +119,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
|||||||
.store(EquationElem::new(content.clone()).pack().spanned(content.span()));
|
.store(EquationElem::new(content.clone()).pack().spanned(content.span()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Styled elements and sequences can (at least currently) also have
|
||||||
|
// labels, so this needs to happen before they are handled.
|
||||||
if let Some(realized) = process(self.engine, &mut self.locator, content, styles)?
|
if let Some(realized) = process(self.engine, &mut self.locator, content, styles)?
|
||||||
{
|
{
|
||||||
self.engine.route.increase();
|
self.engine.route.increase();
|
||||||
|
@ -168,6 +168,7 @@ fn verdict<'a>(
|
|||||||
&& map.is_empty()
|
&& map.is_empty()
|
||||||
&& (prepared || {
|
&& (prepared || {
|
||||||
target.label().is_none()
|
target.label().is_none()
|
||||||
|
&& target.location().is_none()
|
||||||
&& !target.can::<dyn ShowSet>()
|
&& !target.can::<dyn ShowSet>()
|
||||||
&& !target.can::<dyn Locatable>()
|
&& !target.can::<dyn Locatable>()
|
||||||
&& !target.can::<dyn Synthesize>()
|
&& !target.can::<dyn Synthesize>()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user