diff --git a/crates/typst-pdf/src/convert.rs b/crates/typst-pdf/src/convert.rs index 54444ba89..df45b7784 100644 --- a/crates/typst-pdf/src/convert.rs +++ b/crates/typst-pdf/src/convert.rs @@ -213,10 +213,7 @@ impl FrameContext { &mut self, link_id: tags::LinkId, ) -> Option<&mut LinkAnnotation> { - self.link_annotations - .iter_mut() - .rev() - .find(|annot| annot.id == link_id) + self.link_annotations.iter_mut().rfind(|a| a.id == link_id) } pub(crate) fn push_link_annotation(&mut self, annotation: LinkAnnotation) { diff --git a/crates/typst-pdf/src/link.rs b/crates/typst-pdf/src/link.rs index 2d9595c01..44dd975ec 100644 --- a/crates/typst-pdf/src/link.rs +++ b/crates/typst-pdf/src/link.rs @@ -9,11 +9,11 @@ use typst_library::model::Destination; use typst_syntax::Span; use crate::convert::{FrameContext, GlobalContext}; -use crate::tags::{self, Placeholder, TagNode}; +use crate::tags::{LinkId, Placeholder, TagNode}; use crate::util::{AbsExt, PointExt}; pub(crate) struct LinkAnnotation { - pub(crate) id: tags::LinkId, + pub(crate) id: LinkId, pub(crate) placeholder: Placeholder, pub(crate) alt: Option, pub(crate) quad_points: Vec, @@ -60,12 +60,19 @@ pub(crate) fn handle_link( }; let quad = to_quadrilateral(fc, size); - // Unfortunately quadpoints still aren't well supported by most PDF readers, - // even by acrobat. Which is understandable since they were only introduced - // in PDF 1.6 (2005) /s - let should_use_quadpoints = gc.options.standards.config.validator() == Validator::UA1; + // Unfortunately quadpoints still aren't well supported by most PDF readers. + // So only add multiple quadpoint entries to one annotation when targeting + // PDF/UA. Otherwise generate multiple annotations, to avoid pdf readers + // falling back to the bounding box rectangle, which can span parts unrelated + // to the link. For example if there is a linebreak: + // ``` + // Imagine this is a paragraph containing a link. It starts here https://github.com/ + // typst/typst and then ends on another line. + // ``` + // The bounding box would span the entire paragraph, which is undesirable. + let join_annotations = gc.options.standards.config.validator() == Validator::UA1; match fc.get_link_annotation(link_id) { - Some(annotation) if should_use_quadpoints => annotation.quad_points.push(quad), + Some(annotation) if join_annotations => annotation.quad_points.push(quad), _ => { let placeholder = gc.tags.placeholders.reserve(); let (alt, span) = if let Some((link, nodes)) = tagging_ctx { diff --git a/crates/typst-pdf/src/tags/mod.rs b/crates/typst-pdf/src/tags/mod.rs index 5f35eaf0e..d9bc06694 100644 --- a/crates/typst-pdf/src/tags/mod.rs +++ b/crates/typst-pdf/src/tags/mod.rs @@ -434,20 +434,21 @@ pub(crate) fn add_link_annotations( page: &mut Page, annotations: Vec, ) { - for annotation in annotations.into_iter() { - let LinkAnnotation { id: _, placeholder, alt, quad_points, target, span } = - annotation; - let annot = krilla::annotation::Annotation::new_link( - krilla::annotation::LinkAnnotation::new_with_quad_points(quad_points, target), - alt, + for a in annotations.into_iter() { + let annotation = krilla::annotation::Annotation::new_link( + krilla::annotation::LinkAnnotation::new_with_quad_points( + a.quad_points, + a.target, + ), + a.alt, ) - .with_location(Some(span.into_raw().get())); + .with_location(Some(a.span.into_raw())); if gc.options.disable_tags { - page.add_annotation(annot); + page.add_annotation(annotation); } else { - let annot_id = page.add_tagged_annotation(annot); - gc.tags.placeholders.init(placeholder, Node::Leaf(annot_id)); + let annot_id = page.add_tagged_annotation(annotation); + gc.tags.placeholders.init(a.placeholder, Node::Leaf(annot_id)); } } }