summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Schmitz <tobiasschmitz2001@gmail.com>2025-06-11 16:10:34 +0200
committerTobias Schmitz <tobiasschmitz2001@gmail.com>2025-07-03 18:42:10 +0200
commit9e2235dbd87e8f1f9bb76d79833139da8616f295 (patch)
tree20510f325ae1916eb186466a5a3d13071cd43e3f
parent19804305783ee47b0ccc2874c53781d1dff48711 (diff)
feat: pdf.artifact element
-rw-r--r--crates/typst-layout/src/pages/finalize.rs39
-rw-r--r--crates/typst-layout/src/pages/mod.rs10
-rw-r--r--crates/typst-layout/src/pages/run.rs12
-rw-r--r--crates/typst-library/src/foundations/content.rs7
-rw-r--r--crates/typst-library/src/layout/page.rs24
-rw-r--r--crates/typst-library/src/pdf/accessibility.rs54
-rw-r--r--crates/typst-library/src/pdf/mod.rs3
-rw-r--r--crates/typst-pdf/src/tags.rs17
8 files changed, 92 insertions, 74 deletions
diff --git a/crates/typst-layout/src/pages/finalize.rs b/crates/typst-layout/src/pages/finalize.rs
index 543dbb0c..b16d9569 100644
--- a/crates/typst-layout/src/pages/finalize.rs
+++ b/crates/typst-layout/src/pages/finalize.rs
@@ -1,10 +1,7 @@
use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
-use typst_library::foundations::{Content, NativeElement};
-use typst_library::introspection::{ManualPageCounter, SplitLocator, Tag};
-use typst_library::layout::{
- ArtifactKind, ArtifactMarker, Frame, FrameItem, Page, Point,
-};
+use typst_library::introspection::{ManualPageCounter, Tag};
+use typst_library::layout::{Frame, FrameItem, Page, Point};
use super::LayoutedPage;
@@ -13,7 +10,6 @@ use super::LayoutedPage;
/// physical page number, which is unknown during parallel layout.
pub fn finalize(
engine: &mut Engine,
- locator: &mut SplitLocator,
counter: &mut ManualPageCounter,
tags: &mut Vec<Tag>,
LayoutedPage {
@@ -49,12 +45,10 @@ pub fn finalize(
// important as it affects the relative ordering of introspectable elements
// and thus how counters resolve.
if let Some(background) = background {
- let tag = ArtifactMarker::new(ArtifactKind::Page).pack();
- push_tagged(engine, locator, &mut frame, Point::zero(), background, tag);
+ frame.push_frame(Point::zero(), background);
}
if let Some(header) = header {
- let tag = ArtifactMarker::new(ArtifactKind::Header).pack();
- push_tagged(engine, locator, &mut frame, Point::with_x(margin.left), header, tag);
+ frame.push_frame(Point::with_x(margin.left), header);
}
// Add the inner contents.
@@ -63,8 +57,7 @@ pub fn finalize(
// Add the "after" marginals.
if let Some(footer) = footer {
let y = frame.height() - footer.height();
- let tag = ArtifactMarker::new(ArtifactKind::Footer).pack();
- push_tagged(engine, locator, &mut frame, Point::new(margin.left, y), footer, tag);
+ frame.push_frame(Point::new(margin.left, y), footer);
}
if let Some(foreground) = foreground {
frame.push_frame(Point::zero(), foreground);
@@ -79,25 +72,3 @@ pub fn finalize(
Ok(Page { frame, fill, numbering, supplement, number })
}
-
-fn push_tagged(
- engine: &mut Engine,
- locator: &mut SplitLocator,
- frame: &mut Frame,
- mut pos: Point,
- inner: Frame,
- mut tag: Content,
-) {
- // TODO: use general PDF Tagged/Artifact element that wraps some content and
- // is also available to the user.
- let key = typst_utils::hash128(&tag);
- let loc = locator.next_location(engine.introspector, key);
- tag.set_location(loc);
- frame.push(pos, FrameItem::Tag(Tag::Start(tag)));
-
- let height = inner.height();
- frame.push_frame(pos, inner);
-
- pos.y += height;
- frame.push(pos, FrameItem::Tag(Tag::End(loc, key)));
-}
diff --git a/crates/typst-layout/src/pages/mod.rs b/crates/typst-layout/src/pages/mod.rs
index a64fee4b..14dc0f3f 100644
--- a/crates/typst-layout/src/pages/mod.rs
+++ b/crates/typst-layout/src/pages/mod.rs
@@ -123,19 +123,17 @@ fn layout_pages<'a>(
Item::Run(..) => {
let layouted = runs.next().unwrap()?;
for layouted in layouted {
- let page =
- finalize(engine, locator, &mut counter, &mut tags, layouted)?;
+ let page = finalize(engine, &mut counter, &mut tags, layouted)?;
pages.push(page);
}
}
- Item::Parity(parity, initial, page_locator) => {
+ Item::Parity(parity, initial, locator) => {
if !parity.matches(pages.len()) {
continue;
}
- let layouted =
- layout_blank_page(engine, page_locator.relayout(), *initial)?;
- let page = finalize(engine, locator, &mut counter, &mut tags, layouted)?;
+ let layouted = layout_blank_page(engine, locator.relayout(), *initial)?;
+ let page = finalize(engine, &mut counter, &mut tags, layouted)?;
pages.push(page);
}
Item::Tags(items) => {
diff --git a/crates/typst-layout/src/pages/run.rs b/crates/typst-layout/src/pages/run.rs
index 6d2d29da..23360838 100644
--- a/crates/typst-layout/src/pages/run.rs
+++ b/crates/typst-layout/src/pages/run.rs
@@ -13,6 +13,7 @@ use typst_library::layout::{
VAlignment,
};
use typst_library::model::Numbering;
+use typst_library::pdf::ArtifactKind;
use typst_library::routines::{Pair, Routines};
use typst_library::text::{LocalName, TextElem};
use typst_library::visualize::Paint;
@@ -200,6 +201,11 @@ fn layout_page_run_impl(
// Layout marginals.
let mut layouted = Vec::with_capacity(fragment.len());
+
+ let header = header.as_ref().map(|h| h.clone().artifact(ArtifactKind::Header));
+ let footer = footer.as_ref().map(|f| f.clone().artifact(ArtifactKind::Footer));
+ let background = background.as_ref().map(|b| b.clone().artifact(ArtifactKind::Page));
+
for inner in fragment {
let header_size = Size::new(inner.width(), margin.top - header_ascent);
let footer_size = Size::new(inner.width(), margin.bottom - footer_descent);
@@ -210,9 +216,9 @@ fn layout_page_run_impl(
fill: fill.clone(),
numbering: numbering.clone(),
supplement: supplement.clone(),
- header: layout_marginal(header, header_size, Alignment::BOTTOM)?,
- footer: layout_marginal(footer, footer_size, Alignment::TOP)?,
- background: layout_marginal(background, full_size, mid)?,
+ header: layout_marginal(&header, header_size, Alignment::BOTTOM)?,
+ footer: layout_marginal(&footer, footer_size, Alignment::TOP)?,
+ background: layout_marginal(&background, full_size, mid)?,
foreground: layout_marginal(foreground, full_size, mid)?,
margin,
binding,
diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs
index 278d4940..8cd46f0d 100644
--- a/crates/typst-library/src/foundations/content.rs
+++ b/crates/typst-library/src/foundations/content.rs
@@ -22,6 +22,7 @@ use crate::foundations::{
use crate::introspection::Location;
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
+use crate::pdf::{ArtifactElem, ArtifactKind};
use crate::text::UnderlineElem;
/// A piece of document content.
@@ -534,6 +535,12 @@ impl Content {
.pack()
.spanned(span)
}
+
+ /// Link the content somewhere.
+ pub fn artifact(self, kind: ArtifactKind) -> Self {
+ let span = self.span();
+ ArtifactElem::new(self).with_kind(kind).pack().spanned(span)
+ }
}
#[scope]
diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs
index b6fa5d0b..98afbd06 100644
--- a/crates/typst-library/src/layout/page.rs
+++ b/crates/typst-library/src/layout/page.rs
@@ -10,7 +10,7 @@ use crate::foundations::{
cast, elem, Args, AutoValue, Cast, Construct, Content, Dict, Fold, NativeElement,
Set, Smart, Value,
};
-use crate::introspection::{Introspector, Locatable};
+use crate::introspection::Introspector;
use crate::layout::{
Abs, Alignment, FlushElem, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel,
Sides, SpecificAlignment,
@@ -451,28 +451,6 @@ impl PagebreakElem {
}
}
-// HACK: this should probably not be an element
-#[derive(Copy)]
-#[elem(Construct, Locatable)]
-pub struct ArtifactMarker {
- #[internal]
- #[required]
- pub kind: ArtifactKind,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum ArtifactKind {
- Header,
- Footer,
- Page,
-}
-
-impl Construct for ArtifactMarker {
- fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
- bail!(args.span, "cannot be constructed manually");
- }
-}
-
/// A finished document with metadata and page frames.
#[derive(Debug, Default, Clone)]
pub struct PagedDocument {
diff --git a/crates/typst-library/src/pdf/accessibility.rs b/crates/typst-library/src/pdf/accessibility.rs
new file mode 100644
index 00000000..586e2cbb
--- /dev/null
+++ b/crates/typst-library/src/pdf/accessibility.rs
@@ -0,0 +1,54 @@
+use typst_macros::{cast, elem};
+
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{Content, Packed, Show, StyleChain};
+use crate::introspection::Locatable;
+
+// TODO: docs
+
+/// Mark content as a PDF artifact.
+/// TODO: also use to mark html elements with `aria-hidden="true"`?
+#[elem(Locatable, Show)]
+pub struct ArtifactElem {
+ #[default(ArtifactKind::Other)]
+ pub kind: ArtifactKind,
+
+ /// The content to underline.
+ #[required]
+ pub body: Content,
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
+pub enum ArtifactKind {
+ /// Page header artifacts.
+ Header,
+ /// Page footer artifacts.
+ Footer,
+ /// Other page artifacts.
+ Page,
+ /// Other artifacts.
+ #[default]
+ Other,
+}
+
+cast! {
+ ArtifactKind,
+ self => match self {
+ ArtifactKind::Header => "header".into_value(),
+ ArtifactKind::Footer => "footer".into_value(),
+ ArtifactKind::Page => "page".into_value(),
+ ArtifactKind::Other => "other".into_value(),
+ },
+ "header" => Self::Header,
+ "footer" => Self::Footer,
+ "page" => Self::Page,
+ "other" => Self::Other,
+}
+
+impl Show for Packed<ArtifactElem> {
+ #[typst_macros::time(name = "underline", span = self.span())]
+ fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(self.body.clone())
+ }
+}
diff --git a/crates/typst-library/src/pdf/mod.rs b/crates/typst-library/src/pdf/mod.rs
index 786a3637..835cc69f 100644
--- a/crates/typst-library/src/pdf/mod.rs
+++ b/crates/typst-library/src/pdf/mod.rs
@@ -1,7 +1,9 @@
//! PDF-specific functionality.
+mod accessibility;
mod embed;
+pub use self::accessibility::*;
pub use self::embed::*;
use crate::foundations::{Module, Scope};
@@ -11,5 +13,6 @@ pub fn module() -> Module {
let mut pdf = Scope::deduplicating();
pdf.start_category(crate::Category::Pdf);
pdf.define_elem::<EmbedElem>();
+ pdf.define_elem::<ArtifactElem>();
Module::new("pdf", pdf)
}
diff --git a/crates/typst-pdf/src/tags.rs b/crates/typst-pdf/src/tags.rs
index ae15674f..d6415ade 100644
--- a/crates/typst-pdf/src/tags.rs
+++ b/crates/typst-pdf/src/tags.rs
@@ -1,5 +1,4 @@
use std::cell::OnceCell;
-use std::ops::Deref;
use krilla::annotation::Annotation;
use krilla::page::Page;
@@ -9,8 +8,8 @@ use krilla::tagging::{
};
use typst_library::foundations::{Content, StyleChain};
use typst_library::introspection::Location;
-use typst_library::layout::{ArtifactKind, ArtifactMarker};
use typst_library::model::{HeadingElem, OutlineElem, OutlineEntry};
+use typst_library::pdf::{ArtifactElem, ArtifactKind};
use crate::convert::GlobalContext;
@@ -18,7 +17,7 @@ pub(crate) struct Tags {
/// The intermediary stack of nested tag groups.
pub(crate) stack: Vec<(Location, Tag, Vec<TagNode>)>,
pub(crate) placeholders: Vec<OnceCell<Node>>,
- pub(crate) in_artifact: Option<(Location, ArtifactMarker)>,
+ pub(crate) in_artifact: Option<(Location, ArtifactKind)>,
/// The output.
pub(crate) tree: Vec<TagNode>,
@@ -161,8 +160,8 @@ impl Tags {
/// at the end of the last page.
pub(crate) fn restart(gc: &mut GlobalContext, surface: &mut Surface) {
// TODO: somehow avoid empty marked-content sequences
- if let Some((_, marker)) = gc.tags.in_artifact {
- start_artifact(gc, surface, marker.kind);
+ if let Some((_, kind)) = gc.tags.in_artifact {
+ start_artifact(gc, surface, kind);
} else if let Some((_, _, nodes)) = gc.tags.stack.last_mut() {
let id = surface.start_tagged(ContentTag::Other);
nodes.push(TagNode::Leaf(id));
@@ -200,12 +199,13 @@ pub(crate) fn handle_start(
let loc = elem.location().unwrap();
- if let Some(marker) = elem.to_packed::<ArtifactMarker>() {
+ if let Some(artifact) = elem.to_packed::<ArtifactElem>() {
if !gc.tags.stack.is_empty() {
surface.end_tagged();
}
- start_artifact(gc, surface, marker.kind);
- gc.tags.in_artifact = Some((loc, *marker.deref()));
+ let kind = artifact.kind(StyleChain::default());
+ start_artifact(gc, surface, kind);
+ gc.tags.in_artifact = Some((loc, kind));
return;
}
@@ -282,5 +282,6 @@ fn artifact_type(kind: ArtifactKind) -> ArtifactType {
ArtifactKind::Header => ArtifactType::Header,
ArtifactKind::Footer => ArtifactType::Footer,
ArtifactKind::Page => ArtifactType::Page,
+ ArtifactKind::Other => ArtifactType::Other,
}
}