summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-29 20:08:53 +0200
committerLaurenz <laurmaedje@gmail.com>2023-03-29 20:10:23 +0200
commit72fb155403801216e244ab1df784ccd2a29c54cb (patch)
tree820001268f86d7f9e43d60120db54124241fd62e
parent621922bb35657d002b123913dd507e35a178f4c2 (diff)
Link to label
-rw-r--r--docs/src/general/changelog.md5
-rw-r--r--library/src/meta/bibliography.rs3
-rw-r--r--library/src/meta/link.rs89
-rw-r--r--library/src/meta/reference.rs16
-rw-r--r--src/model/introspect.rs14
-rw-r--r--src/model/realize.rs5
-rw-r--r--tests/ref/meta/link.pngbin49582 -> 52099 bytes
-rw-r--r--tests/typ/meta/link.typ15
8 files changed, 109 insertions, 38 deletions
diff --git a/docs/src/general/changelog.md b/docs/src/general/changelog.md
index 958c3e4a..cbd6b039 100644
--- a/docs/src/general/changelog.md
+++ b/docs/src/general/changelog.md
@@ -7,9 +7,12 @@ description: |
# Changelog
## Unreleased
- Added [`polygon`]($func/polygon) function
-- Reduced maximum function call depth from 256 to 64
+- The [`link`]($func/link) function now accepts [labels]($func/label)
+- Fixed styling of text operators in math
+- Fixed invalid parsing of language tag in raw block with a single backtick
- CLI now returns with non-zero status code if there is an error
- CLI now watches the root directory instead of the current one
+- Reduced maximum function call depth from 256 to 64
## March 28, 2023
- **Breaking:** Enumerations now require a space after their marker, that is,
diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs
index 2a0a96b1..c9532915 100644
--- a/library/src/meta/bibliography.rs
+++ b/library/src/meta/bibliography.rs
@@ -614,7 +614,8 @@ fn format_display_string(
Formatting::Bold => content.strong(),
Formatting::Italic => content.emph(),
Formatting::Link(link) => {
- LinkElem::new(Destination::Url(link.as_str().into()), content).pack()
+ LinkElem::new(Destination::Url(link.as_str().into()).into(), content)
+ .pack()
}
};
}
diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs
index d8cb779a..135ce0af 100644
--- a/library/src/meta/link.rs
+++ b/library/src/meta/link.rs
@@ -13,6 +13,7 @@ use crate::text::{Hyphenate, TextElem};
/// #show link: underline
///
/// https://example.com \
+///
/// #link("https://example.com") \
/// #link("https://example.com")[
/// See example.com
@@ -25,7 +26,7 @@ use crate::text::{Hyphenate, TextElem};
///
/// Display: Link
/// Category: meta
-#[element(Show, Finalize)]
+#[element(Show)]
pub struct LinkElem {
/// The destination the link points to.
///
@@ -34,33 +35,42 @@ pub struct LinkElem {
/// omitted, the email address or phone number will be the link's body,
/// without the scheme.
///
- /// - To link to another part of the document, `dest` can take one of two
- /// forms: A [`location`]($func/locate) or a dictionary with a `page` key
- /// of type `integer` and `x` and `y` coordinates of type `length`. Pages
- /// are counted from one, and the coordinates are relative to the page's
- /// top left corner.
+ /// - To link to another part of the document, `dest` can take one of three
+ /// forms:
+ /// - A [label]($func/label) attached to an element. If you also want
+ /// automatic text for the link based on the element, consider using
+ /// a [reference]($func/ref) instead.
+ ///
+ /// - A [location]($func/locate) resulting from a [`locate`]($func/locate)
+ /// call or [`query`]($func/query).
+ ///
+ /// - A dictionary with a `page` key of type [integer]($type/integer) and
+ /// `x` and `y` coordinates of type [length]($type/length). Pages are
+ /// counted from one, and the coordinates are relative to the page's top
+ /// left corner.
///
/// ```example
+ /// = Introduction <intro>
/// #link("mailto:hello@typst.app") \
+ /// #link(<intro>)[Go to intro] \
/// #link((page: 1, x: 0pt, y: 0pt))[
/// Go to top
/// ]
/// ```
#[required]
#[parse(
- let dest = args.expect::<Destination>("destination")?;
+ let dest = args.expect::<LinkTarget>("destination")?;
dest.clone()
)]
- pub dest: Destination,
+ pub dest: LinkTarget,
- /// How the link is represented.
+ /// The content that should become a link.
///
- /// The content that should become a link. If `dest` is an URL string, the
- /// parameter can be omitted. In this case, the URL will be shown as the
- /// link.
+ /// If `dest` is an URL string, the parameter can be omitted. In this case,
+ /// the URL will be shown as the link.
#[required]
#[parse(match &dest {
- Destination::Url(url) => match args.eat()? {
+ LinkTarget::Dest(Destination::Url(url)) => match args.eat()? {
Some(body) => body,
None => body_from_url(url),
},
@@ -73,21 +83,28 @@ impl LinkElem {
/// Create a link element from a URL with its bare text.
pub fn from_url(url: EcoString) -> Self {
let body = body_from_url(&url);
- Self::new(Destination::Url(url), body)
+ Self::new(LinkTarget::Dest(Destination::Url(url)), body)
}
}
impl Show for LinkElem {
- fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(self.body())
- }
-}
+ fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
+ let body = self.body();
+ let dest = match self.dest() {
+ LinkTarget::Dest(dest) => dest,
+ LinkTarget::Label(label) => {
+ if !vt.introspector.init() {
+ return Ok(body);
+ }
+
+ let elem = vt.introspector.query_label(&label).at(self.span())?;
+ Destination::Location(elem.location().unwrap())
+ }
+ };
-impl Finalize for LinkElem {
- fn finalize(&self, realized: Content, _: StyleChain) -> Content {
- realized
- .linked(self.dest())
- .styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))))
+ Ok(body
+ .linked(dest)
+ .styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))))
}
}
@@ -99,3 +116,29 @@ fn body_from_url(url: &EcoString) -> Content {
let shorter = text.len() < url.len();
TextElem::packed(if shorter { text.into() } else { url.clone() })
}
+
+/// A target where a link can go.
+#[derive(Debug, Clone)]
+pub enum LinkTarget {
+ Dest(Destination),
+ Label(Label),
+}
+
+cast_from_value! {
+ LinkTarget,
+ v: Destination => Self::Dest(v),
+ v: Label => Self::Label(v),
+}
+
+cast_to_value! {
+ v: LinkTarget => match v {
+ LinkTarget::Dest(v) => v.into(),
+ LinkTarget::Label(v) => v.into(),
+ }
+}
+
+impl From<Destination> for LinkTarget {
+ fn from(dest: Destination) -> Self {
+ Self::Dest(dest)
+ }
+}
diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs
index 14246436..0b317db0 100644
--- a/library/src/meta/reference.rs
+++ b/library/src/meta/reference.rs
@@ -11,6 +11,9 @@ use crate::text::TextElem;
///
/// Reference syntax can also be used to [cite]($func/cite) from a bibliography.
///
+/// 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.
+///
/// # Example
/// ```example
/// #set heading(numbering: "1.")
@@ -93,24 +96,17 @@ impl Show for RefElem {
}
let target = self.target();
- let matches = vt.introspector.query(Selector::Label(self.target()));
+ let elem = vt.introspector.query_label(&self.target());
if BibliographyElem::has(vt, &target.0) {
- if !matches.is_empty() {
+ if elem.is_ok() {
bail!(self.span(), "label occurs in the document and its bibliography");
}
return Ok(self.to_citation(styles).pack());
}
- let [elem] = matches.as_slice() else {
- bail!(self.span(), if matches.is_empty() {
- "label does not exist in the document"
- } else {
- "label occurs multiple times in the document"
- });
- };
-
+ let elem = elem.at(self.span())?;
if !elem.can::<dyn Locatable>() {
bail!(self.span(), "cannot reference {}", elem.func().name());
}
diff --git a/src/model/introspect.rs b/src/model/introspect.rs
index f0ff1169..31786d5b 100644
--- a/src/model/introspect.rs
+++ b/src/model/introspect.rs
@@ -3,9 +3,11 @@ use std::hash::Hash;
use std::num::NonZeroUsize;
use super::{Content, Selector};
+use crate::diag::StrResult;
use crate::doc::{Frame, FrameItem, Meta, Position};
use crate::eval::cast_from_value;
use crate::geom::{Point, Transform};
+use crate::model::Label;
use crate::util::NonZeroExt;
/// Stably identifies an element in the document across multiple layout passes.
@@ -160,6 +162,18 @@ impl Introspector {
.collect()
}
+ /// Query for a unique element with the label.
+ pub fn query_label(&self, label: &Label) -> StrResult<Content> {
+ let mut found = None;
+ for elem in self.all().filter(|elem| elem.label() == Some(label)) {
+ if found.is_some() {
+ return Err("label occurs multiple times in the document".into());
+ }
+ found = Some(elem.clone());
+ }
+ found.ok_or_else(|| "label does not exist in the document".into())
+ }
+
/// The total number pages.
pub fn pages(&self) -> NonZeroUsize {
NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)
diff --git a/src/model/realize.rs b/src/model/realize.rs
index 10e1b0e2..e96e0dc1 100644
--- a/src/model/realize.rs
+++ b/src/model/realize.rs
@@ -176,9 +176,8 @@ pub trait Show {
/// Post-process an element after it was realized.
pub trait Finalize {
- /// Finalize the fully realized form of the element. Use this for effects that
- /// should work even in the face of a user-defined show rule, for example
- /// the linking behaviour of a link element.
+ /// Finalize the fully realized form of the element. Use this for effects
+ /// that should work even in the face of a user-defined show rule.
fn finalize(&self, realized: Content, styles: StyleChain) -> Content;
}
diff --git a/tests/ref/meta/link.png b/tests/ref/meta/link.png
index d80acc6f..4e182e9b 100644
--- a/tests/ref/meta/link.png
+++ b/tests/ref/meta/link.png
Binary files differ
diff --git a/tests/typ/meta/link.typ b/tests/typ/meta/link.typ
index 3ceb261d..de4c91c9 100644
--- a/tests/typ/meta/link.typ
+++ b/tests/typ/meta/link.typ
@@ -44,3 +44,18 @@ My cool #box(move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink))))
---
// Link to page one.
#link((page: 1, x: 10pt, y: 20pt))[Back to the start]
+
+---
+// Test link to label.
+Text <hey>
+#link(<hey>)[Go to text.]
+
+---
+// Error: 2-20 label does not exist in the document
+#link(<hey>)[Nope.]
+
+---
+Text <hey>
+Text <hey>
+// Error: 2-20 label occurs multiple times in the document
+#link(<hey>)[Nope.]