From 72fb155403801216e244ab1df784ccd2a29c54cb Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 29 Mar 2023 20:08:53 +0200 Subject: [PATCH] Link to label --- docs/src/general/changelog.md | 5 +- library/src/meta/bibliography.rs | 3 +- library/src/meta/link.rs | 89 +++++++++++++++++++++++-------- library/src/meta/reference.rs | 16 +++--- src/model/introspect.rs | 14 +++++ src/model/realize.rs | 5 +- tests/ref/meta/link.png | Bin 49582 -> 52099 bytes tests/typ/meta/link.typ | 15 ++++++ 8 files changed, 109 insertions(+), 38 deletions(-) diff --git a/docs/src/general/changelog.md b/docs/src/general/changelog.md index 958c3e4a8..cbd6b0391 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 2a0a96b1e..c95329155 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 d8cb779a1..135ce0afd 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 /// #link("mailto:hello@typst.app") \ + /// #link()[Go to intro] \ /// #link((page: 1, x: 0pt, y: 0pt))[ /// Go to top /// ] /// ``` #[required] #[parse( - let dest = args.expect::("destination")?; + let dest = args.expect::("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 { - Ok(self.body()) - } -} + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { + let body = self.body(); + let dest = match self.dest() { + LinkTarget::Dest(dest) => dest, + LinkTarget::Label(label) => { + if !vt.introspector.init() { + return Ok(body); + } -impl Finalize for LinkElem { - fn finalize(&self, realized: Content, _: StyleChain) -> Content { - realized - .linked(self.dest()) - .styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))) + let elem = vt.introspector.query_label(&label).at(self.span())?; + Destination::Location(elem.location().unwrap()) + } + }; + + 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 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 14246436e..0b317db06 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::() { bail!(self.span(), "cannot reference {}", elem.func().name()); } diff --git a/src/model/introspect.rs b/src/model/introspect.rs index f0ff11690..31786d5bf 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 { + 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 10e1b0e2e..e96e0dc16 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 d80acc6f67d69b55426618e593cb537805b4e654..4e182e9bb522f16045d0075c5ac109b78712db6d 100644 GIT binary patch delta 11337 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 TstLSd7)efAS*l#})7Sq4#L{8{ delta 8871 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 diff --git a/tests/typ/meta/link.typ b/tests/typ/meta/link.typ index 3ceb261d9..de4c91c95 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 +#link()[Go to text.] + +--- +// Error: 2-20 label does not exist in the document +#link()[Nope.] + +--- +Text +Text +// Error: 2-20 label occurs multiple times in the document +#link()[Nope.]