From 529ae785615e930f2d890ec52002a16b1dc49f13 Mon Sep 17 00:00:00 2001 From: Tobias Schmitz Date: Mon, 16 Jun 2025 18:28:42 +0200 Subject: [PATCH] WIP [no ci] --- crates/typst-library/src/pdf/accessibility.rs | 14 +-- crates/typst-pdf/src/link.rs | 11 ++- crates/typst-pdf/src/tags.rs | 87 +++++++------------ 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/crates/typst-library/src/pdf/accessibility.rs b/crates/typst-library/src/pdf/accessibility.rs index 5ca7444aa..f5210476d 100644 --- a/crates/typst-library/src/pdf/accessibility.rs +++ b/crates/typst-library/src/pdf/accessibility.rs @@ -9,11 +9,11 @@ use crate::introspection::Locatable; // TODO: docs #[elem(Locatable, Show)] pub struct PdfTagElem { - #[default(PdfStructElem::NonStruct)] - pub kind: PdfStructElem, + #[default(PdfTagKind::NonStruct)] + pub kind: PdfTagKind, - /// An alternate description - pub alt_desc: Option, + /// An alternate description. + pub alt: Option, /// Exact replacement for this structure element and its children. pub actual_text: Option, /// The expanded form of an abbreviation/acronym. @@ -34,7 +34,7 @@ impl Show for Packed { // TODO: docs /// PDF structure elements #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum PdfStructElem { +pub enum PdfTagKind { // grouping elements /// (Part) Part, @@ -146,9 +146,9 @@ pub enum PdfStructElem { } cast! { - PdfStructElem, + PdfTagKind, self => match self { - PdfStructElem::Part => "part".into_value(), + PdfTagKind::Part => "part".into_value(), _ => todo!(), }, "part" => Self::Part, diff --git a/crates/typst-pdf/src/link.rs b/crates/typst-pdf/src/link.rs index 6dfefbc11..c78ca389f 100644 --- a/crates/typst-pdf/src/link.rs +++ b/crates/typst-pdf/src/link.rs @@ -2,6 +2,7 @@ use krilla::action::{Action, LinkAction}; use krilla::annotation::{Annotation, LinkAnnotation, Target}; use krilla::destination::XyzDestination; use krilla::geom::Rect; +use krilla::tagging::Tag; use typst_library::layout::{Abs, Point, Position, Size}; use typst_library::model::Destination; @@ -45,6 +46,8 @@ pub(crate) fn handle_link( let rect = Rect::from_ltrb(x1, y1, x2, y2).unwrap(); // TODO: Support quad points. + // > Beginning with PDF 1.7, use of the Link structure element to enclose + // > multiple link annotations is deprecated. let target = match dest { Destination::Url(u) => { @@ -70,7 +73,13 @@ pub(crate) fn handle_link( }; let placeholder = gc.tags.reserve_placeholder(); - gc.tags.push(TagNode::Placeholder(placeholder)); + let mut node = TagNode::Placeholder(placeholder); + let (parent_tag, _) = gc.tags.parent_nodes(); + if !matches!(parent_tag, Some(Tag::Link | Tag::Reference)) { + node = TagNode::Group(Link, ) + } + // Prepend so the link annotation is the first child. + gc.tags.prepend(); fc.push_annotation( placeholder, diff --git a/crates/typst-pdf/src/tags.rs b/crates/typst-pdf/src/tags.rs index 0402d7fe5..1c5b0bef0 100644 --- a/crates/typst-pdf/src/tags.rs +++ b/crates/typst-pdf/src/tags.rs @@ -8,8 +8,11 @@ use krilla::tagging::{ }; use typst_library::foundations::{Content, StyleChain}; use typst_library::introspection::Location; -use typst_library::model::{HeadingElem, OutlineElem, OutlineEntry}; -use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfStructElem, PdfTagElem}; +use typst_library::model::{ + CiteElem, FigureCaption, FigureElem, HeadingElem, LinkElem, OutlineElem, + OutlineEntry, RefElem, +}; +use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind}; use crate::convert::GlobalContext; @@ -72,6 +75,14 @@ impl Tags { } } + pub(crate) fn prepend(&mut self, node: TagNode) { + if let Some((_, _, nodes)) = self.stack.last_mut() { + nodes.insert(0, node); + } else { + self.tree.insert(0, node); + } + } + pub(crate) fn build_tree(&mut self) -> TagTree { let mut tree = TagTree::new(); let nodes = std::mem::take(&mut self.tree); @@ -99,60 +110,17 @@ impl Tags { } /// Returns the current parent's list of children and whether it is the tree root. - fn parent_nodes(&mut self) -> (bool, &mut Vec) { - if let Some((_, _, parent_nodes)) = self.stack.last_mut() { - (false, parent_nodes) + pub(crate) fn parent_nodes(&mut self) -> (Option<&mut Tag>, &mut Vec) { + if let Some((_, tag, parent_nodes)) = self.stack.last_mut() { + (Some(tag), parent_nodes) } else { - (true, &mut self.tree) + (None, &mut self.tree) } } - fn context_supports(&self, tag: &Tag) -> bool { - let Some((_, parent, _)) = self.stack.last() else { return true }; - - use Tag::*; - - match parent { - Part => true, - Article => !matches!(tag, Article), - Section => true, - BlockQuote => todo!(), - Caption => todo!(), - TOC => matches!(tag, TOC | TOCI), - // TODO: NonStruct is allowed to but (currently?) not supported by krilla - TOCI => matches!(tag, TOC | Lbl | Reference | P), - Index => todo!(), - P => todo!(), - H1(_) => todo!(), - H2(_) => todo!(), - H3(_) => todo!(), - H4(_) => todo!(), - H5(_) => todo!(), - H6(_) => todo!(), - L(_list_numbering) => todo!(), - LI => todo!(), - Lbl => todo!(), - LBody => todo!(), - Table => todo!(), - TR => todo!(), - TH(_table_header_scope) => todo!(), - TD => todo!(), - THead => todo!(), - TBody => todo!(), - TFoot => todo!(), - InlineQuote => todo!(), - Note => todo!(), - Reference => todo!(), - BibEntry => todo!(), - Code => todo!(), - Link => todo!(), - Annot => todo!(), - Figure(_) => todo!(), - Formula(_) => todo!(), - Datetime => todo!(), - Terms => todo!(), - Title => todo!(), - } + fn context_supports(&self, _tag: &Tag) -> bool { + // TODO: generate using: https://pdfa.org/resource/iso-ts-32005-hierarchical-inclusion-rules/ + true } } @@ -212,7 +180,7 @@ pub(crate) fn handle_start( let tag = if let Some(pdf_tag) = elem.to_packed::() { let kind = pdf_tag.kind(StyleChain::default()); match kind { - PdfStructElem::Part => Tag::Part, + PdfTagKind::Part => Tag::Part, _ => todo!(), } } else if let Some(heading) = elem.to_packed::() { @@ -229,8 +197,19 @@ pub(crate) fn handle_start( } } else if let Some(_) = elem.to_packed::() { Tag::TOC - } else if let Some(_outline_entry) = elem.to_packed::() { + } else if let Some(_) = elem.to_packed::() { Tag::TOCI + } else if let Some(_) = elem.to_packed::() { + let alt = None; // TODO + Tag::Figure(alt) + } else if let Some(_) = elem.to_packed::() { + Tag::Caption + } else if let Some(_) = elem.to_packed::() { + Tag::Link + } else if let Some(_) = elem.to_packed::() { + Tag::Reference + } else if let Some(_) = elem.to_packed::() { + Tag::Reference } else { return; };