mirror of
https://github.com/typst/typst
synced 2025-06-15 00:26:26 +08:00
feat: pdf.artifact element
This commit is contained in:
parent
c9bb9e8981
commit
ff8f9768c6
@ -1,10 +1,7 @@
|
|||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Content, NativeElement};
|
use typst_library::introspection::{ManualPageCounter, Tag};
|
||||||
use typst_library::introspection::{ManualPageCounter, SplitLocator, Tag};
|
use typst_library::layout::{Frame, FrameItem, Page, Point};
|
||||||
use typst_library::layout::{
|
|
||||||
ArtifactKind, ArtifactMarker, Frame, FrameItem, Page, Point,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::LayoutedPage;
|
use super::LayoutedPage;
|
||||||
|
|
||||||
@ -13,7 +10,6 @@ use super::LayoutedPage;
|
|||||||
/// physical page number, which is unknown during parallel layout.
|
/// physical page number, which is unknown during parallel layout.
|
||||||
pub fn finalize(
|
pub fn finalize(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
locator: &mut SplitLocator,
|
|
||||||
counter: &mut ManualPageCounter,
|
counter: &mut ManualPageCounter,
|
||||||
tags: &mut Vec<Tag>,
|
tags: &mut Vec<Tag>,
|
||||||
LayoutedPage {
|
LayoutedPage {
|
||||||
@ -49,12 +45,10 @@ pub fn finalize(
|
|||||||
// important as it affects the relative ordering of introspectable elements
|
// important as it affects the relative ordering of introspectable elements
|
||||||
// and thus how counters resolve.
|
// and thus how counters resolve.
|
||||||
if let Some(background) = background {
|
if let Some(background) = background {
|
||||||
let tag = ArtifactMarker::new(ArtifactKind::Page).pack();
|
frame.push_frame(Point::zero(), background);
|
||||||
push_tagged(engine, locator, &mut frame, Point::zero(), background, tag);
|
|
||||||
}
|
}
|
||||||
if let Some(header) = header {
|
if let Some(header) = header {
|
||||||
let tag = ArtifactMarker::new(ArtifactKind::Header).pack();
|
frame.push_frame(Point::with_x(margin.left), header);
|
||||||
push_tagged(engine, locator, &mut frame, Point::with_x(margin.left), header, tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the inner contents.
|
// Add the inner contents.
|
||||||
@ -63,8 +57,7 @@ pub fn finalize(
|
|||||||
// Add the "after" marginals.
|
// Add the "after" marginals.
|
||||||
if let Some(footer) = footer {
|
if let Some(footer) = footer {
|
||||||
let y = frame.height() - footer.height();
|
let y = frame.height() - footer.height();
|
||||||
let tag = ArtifactMarker::new(ArtifactKind::Footer).pack();
|
frame.push_frame(Point::new(margin.left, y), footer);
|
||||||
push_tagged(engine, locator, &mut frame, Point::new(margin.left, y), footer, tag);
|
|
||||||
}
|
}
|
||||||
if let Some(foreground) = foreground {
|
if let Some(foreground) = foreground {
|
||||||
frame.push_frame(Point::zero(), foreground);
|
frame.push_frame(Point::zero(), foreground);
|
||||||
@ -79,25 +72,3 @@ pub fn finalize(
|
|||||||
|
|
||||||
Ok(Page { frame, fill, numbering, supplement, number })
|
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)));
|
|
||||||
}
|
|
||||||
|
@ -123,19 +123,17 @@ fn layout_pages<'a>(
|
|||||||
Item::Run(..) => {
|
Item::Run(..) => {
|
||||||
let layouted = runs.next().unwrap()?;
|
let layouted = runs.next().unwrap()?;
|
||||||
for layouted in layouted {
|
for layouted in layouted {
|
||||||
let page =
|
let page = finalize(engine, &mut counter, &mut tags, layouted)?;
|
||||||
finalize(engine, locator, &mut counter, &mut tags, layouted)?;
|
|
||||||
pages.push(page);
|
pages.push(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item::Parity(parity, initial, page_locator) => {
|
Item::Parity(parity, initial, locator) => {
|
||||||
if !parity.matches(pages.len()) {
|
if !parity.matches(pages.len()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layouted =
|
let layouted = layout_blank_page(engine, locator.relayout(), *initial)?;
|
||||||
layout_blank_page(engine, page_locator.relayout(), *initial)?;
|
let page = finalize(engine, &mut counter, &mut tags, layouted)?;
|
||||||
let page = finalize(engine, locator, &mut counter, &mut tags, layouted)?;
|
|
||||||
pages.push(page);
|
pages.push(page);
|
||||||
}
|
}
|
||||||
Item::Tags(items) => {
|
Item::Tags(items) => {
|
||||||
|
@ -13,6 +13,7 @@ use typst_library::layout::{
|
|||||||
VAlignment,
|
VAlignment,
|
||||||
};
|
};
|
||||||
use typst_library::model::Numbering;
|
use typst_library::model::Numbering;
|
||||||
|
use typst_library::pdf::ArtifactKind;
|
||||||
use typst_library::routines::{Pair, Routines};
|
use typst_library::routines::{Pair, Routines};
|
||||||
use typst_library::text::{LocalName, TextElem};
|
use typst_library::text::{LocalName, TextElem};
|
||||||
use typst_library::visualize::Paint;
|
use typst_library::visualize::Paint;
|
||||||
@ -200,6 +201,11 @@ fn layout_page_run_impl(
|
|||||||
|
|
||||||
// Layout marginals.
|
// Layout marginals.
|
||||||
let mut layouted = Vec::with_capacity(fragment.len());
|
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 {
|
for inner in fragment {
|
||||||
let header_size = Size::new(inner.width(), margin.top - header_ascent);
|
let header_size = Size::new(inner.width(), margin.top - header_ascent);
|
||||||
let footer_size = Size::new(inner.width(), margin.bottom - footer_descent);
|
let footer_size = Size::new(inner.width(), margin.bottom - footer_descent);
|
||||||
@ -210,9 +216,9 @@ fn layout_page_run_impl(
|
|||||||
fill: fill.clone(),
|
fill: fill.clone(),
|
||||||
numbering: numbering.clone(),
|
numbering: numbering.clone(),
|
||||||
supplement: supplement.clone(),
|
supplement: supplement.clone(),
|
||||||
header: layout_marginal(header, header_size, Alignment::BOTTOM)?,
|
header: layout_marginal(&header, header_size, Alignment::BOTTOM)?,
|
||||||
footer: layout_marginal(footer, footer_size, Alignment::TOP)?,
|
footer: layout_marginal(&footer, footer_size, Alignment::TOP)?,
|
||||||
background: layout_marginal(background, full_size, mid)?,
|
background: layout_marginal(&background, full_size, mid)?,
|
||||||
foreground: layout_marginal(foreground, full_size, mid)?,
|
foreground: layout_marginal(foreground, full_size, mid)?,
|
||||||
margin,
|
margin,
|
||||||
binding,
|
binding,
|
||||||
|
@ -22,6 +22,7 @@ use crate::foundations::{
|
|||||||
use crate::introspection::Location;
|
use crate::introspection::Location;
|
||||||
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
|
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
|
||||||
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
|
use crate::model::{Destination, EmphElem, LinkElem, StrongElem};
|
||||||
|
use crate::pdf::{ArtifactElem, ArtifactKind};
|
||||||
use crate::text::UnderlineElem;
|
use crate::text::UnderlineElem;
|
||||||
|
|
||||||
/// A piece of document content.
|
/// A piece of document content.
|
||||||
@ -534,6 +535,12 @@ impl Content {
|
|||||||
.pack()
|
.pack()
|
||||||
.spanned(span)
|
.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]
|
#[scope]
|
||||||
|
@ -10,7 +10,7 @@ use crate::foundations::{
|
|||||||
cast, elem, Args, AutoValue, Cast, Construct, Content, Dict, Fold, NativeElement,
|
cast, elem, Args, AutoValue, Cast, Construct, Content, Dict, Fold, NativeElement,
|
||||||
Set, Smart, Value,
|
Set, Smart, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locatable};
|
use crate::introspection::Introspector;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Alignment, FlushElem, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel,
|
Abs, Alignment, FlushElem, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel,
|
||||||
Sides, SpecificAlignment,
|
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.
|
/// A finished document with metadata and page frames.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct PagedDocument {
|
pub struct PagedDocument {
|
||||||
|
54
crates/typst-library/src/pdf/accessibility.rs
Normal file
54
crates/typst-library/src/pdf/accessibility.rs
Normal file
@ -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())
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
//! PDF-specific functionality.
|
//! PDF-specific functionality.
|
||||||
|
|
||||||
|
mod accessibility;
|
||||||
mod embed;
|
mod embed;
|
||||||
|
|
||||||
|
pub use self::accessibility::*;
|
||||||
pub use self::embed::*;
|
pub use self::embed::*;
|
||||||
|
|
||||||
use crate::foundations::{Module, Scope};
|
use crate::foundations::{Module, Scope};
|
||||||
@ -11,5 +13,6 @@ pub fn module() -> Module {
|
|||||||
let mut pdf = Scope::deduplicating();
|
let mut pdf = Scope::deduplicating();
|
||||||
pdf.start_category(crate::Category::Pdf);
|
pdf.start_category(crate::Category::Pdf);
|
||||||
pdf.define_elem::<EmbedElem>();
|
pdf.define_elem::<EmbedElem>();
|
||||||
|
pdf.define_elem::<ArtifactElem>();
|
||||||
Module::new("pdf", pdf)
|
Module::new("pdf", pdf)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use krilla::annotation::Annotation;
|
use krilla::annotation::Annotation;
|
||||||
use krilla::page::Page;
|
use krilla::page::Page;
|
||||||
@ -9,8 +8,8 @@ use krilla::tagging::{
|
|||||||
};
|
};
|
||||||
use typst_library::foundations::{Content, StyleChain};
|
use typst_library::foundations::{Content, StyleChain};
|
||||||
use typst_library::introspection::Location;
|
use typst_library::introspection::Location;
|
||||||
use typst_library::layout::{ArtifactKind, ArtifactMarker};
|
|
||||||
use typst_library::model::{HeadingElem, OutlineElem, OutlineEntry};
|
use typst_library::model::{HeadingElem, OutlineElem, OutlineEntry};
|
||||||
|
use typst_library::pdf::{ArtifactElem, ArtifactKind};
|
||||||
|
|
||||||
use crate::convert::GlobalContext;
|
use crate::convert::GlobalContext;
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ pub(crate) struct Tags {
|
|||||||
/// The intermediary stack of nested tag groups.
|
/// The intermediary stack of nested tag groups.
|
||||||
pub(crate) stack: Vec<(Location, Tag, Vec<TagNode>)>,
|
pub(crate) stack: Vec<(Location, Tag, Vec<TagNode>)>,
|
||||||
pub(crate) placeholders: Vec<OnceCell<Node>>,
|
pub(crate) placeholders: Vec<OnceCell<Node>>,
|
||||||
pub(crate) in_artifact: Option<(Location, ArtifactMarker)>,
|
pub(crate) in_artifact: Option<(Location, ArtifactKind)>,
|
||||||
|
|
||||||
/// The output.
|
/// The output.
|
||||||
pub(crate) tree: Vec<TagNode>,
|
pub(crate) tree: Vec<TagNode>,
|
||||||
@ -161,8 +160,8 @@ impl Tags {
|
|||||||
/// at the end of the last page.
|
/// at the end of the last page.
|
||||||
pub(crate) fn restart(gc: &mut GlobalContext, surface: &mut Surface) {
|
pub(crate) fn restart(gc: &mut GlobalContext, surface: &mut Surface) {
|
||||||
// TODO: somehow avoid empty marked-content sequences
|
// TODO: somehow avoid empty marked-content sequences
|
||||||
if let Some((_, marker)) = gc.tags.in_artifact {
|
if let Some((_, kind)) = gc.tags.in_artifact {
|
||||||
start_artifact(gc, surface, marker.kind);
|
start_artifact(gc, surface, kind);
|
||||||
} else if let Some((_, _, nodes)) = gc.tags.stack.last_mut() {
|
} else if let Some((_, _, nodes)) = gc.tags.stack.last_mut() {
|
||||||
let id = surface.start_tagged(ContentTag::Other);
|
let id = surface.start_tagged(ContentTag::Other);
|
||||||
nodes.push(TagNode::Leaf(id));
|
nodes.push(TagNode::Leaf(id));
|
||||||
@ -200,12 +199,13 @@ pub(crate) fn handle_start(
|
|||||||
|
|
||||||
let loc = elem.location().unwrap();
|
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() {
|
if !gc.tags.stack.is_empty() {
|
||||||
surface.end_tagged();
|
surface.end_tagged();
|
||||||
}
|
}
|
||||||
start_artifact(gc, surface, marker.kind);
|
let kind = artifact.kind(StyleChain::default());
|
||||||
gc.tags.in_artifact = Some((loc, *marker.deref()));
|
start_artifact(gc, surface, kind);
|
||||||
|
gc.tags.in_artifact = Some((loc, kind));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,5 +282,6 @@ fn artifact_type(kind: ArtifactKind) -> ArtifactType {
|
|||||||
ArtifactKind::Header => ArtifactType::Header,
|
ArtifactKind::Header => ArtifactType::Header,
|
||||||
ArtifactKind::Footer => ArtifactType::Footer,
|
ArtifactKind::Footer => ArtifactType::Footer,
|
||||||
ArtifactKind::Page => ArtifactType::Page,
|
ArtifactKind::Page => ArtifactType::Page,
|
||||||
|
ArtifactKind::Other => ArtifactType::Other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user