mirror of
https://github.com/typst/typst
synced 2025-08-20 17:58:32 +08:00
feat: [no ci] mark artifacts
This commit is contained in:
parent
38dd4a36ea
commit
ee9e0fd7bb
@ -1,7 +1,10 @@
|
||||
use typst_library::diag::SourceResult;
|
||||
use typst_library::engine::Engine;
|
||||
use typst_library::foundations::{Content, NativeElement};
|
||||
use typst_library::introspection::{ManualPageCounter, Tag};
|
||||
use typst_library::layout::{Frame, FrameItem, Page, Point};
|
||||
use typst_library::layout::{
|
||||
ArtifactKind, ArtifactMarker, Frame, FrameItem, Page, Point,
|
||||
};
|
||||
|
||||
use super::LayoutedPage;
|
||||
|
||||
@ -45,10 +48,12 @@ pub fn finalize(
|
||||
// important as it affects the relative ordering of introspectable elements
|
||||
// and thus how counters resolve.
|
||||
if let Some(background) = background {
|
||||
frame.push_frame(Point::zero(), background);
|
||||
let tag = ArtifactMarker::new(ArtifactKind::Page).pack();
|
||||
push_tagged(&mut frame, Point::zero(), background, tag);
|
||||
}
|
||||
if let Some(header) = header {
|
||||
frame.push_frame(Point::with_x(margin.left), header);
|
||||
let tag = ArtifactMarker::new(ArtifactKind::Page).pack();
|
||||
push_tagged(&mut frame, Point::with_x(margin.left), header, tag);
|
||||
}
|
||||
|
||||
// Add the inner contents.
|
||||
@ -57,7 +62,8 @@ pub fn finalize(
|
||||
// Add the "after" marginals.
|
||||
if let Some(footer) = footer {
|
||||
let y = frame.height() - footer.height();
|
||||
frame.push_frame(Point::new(margin.left, y), footer);
|
||||
let tag = ArtifactMarker::new(ArtifactKind::Footer).pack();
|
||||
push_tagged(&mut frame, Point::new(margin.left, y), footer, tag);
|
||||
}
|
||||
if let Some(foreground) = foreground {
|
||||
frame.push_frame(Point::zero(), foreground);
|
||||
@ -72,3 +78,13 @@ pub fn finalize(
|
||||
|
||||
Ok(Page { frame, fill, numbering, supplement, number })
|
||||
}
|
||||
|
||||
fn push_tagged(frame: &mut Frame, mut pos: Point, inner: Frame, tag: Content) {
|
||||
frame.push(pos, FrameItem::Tag(Tag::Start(tag)));
|
||||
|
||||
frame.push_frame(pos, inner);
|
||||
|
||||
pos.y += inner.height();
|
||||
let loc = tag.location().unwrap();
|
||||
frame.push(pos, FrameItem::Tag(Tag::End(loc, key)))
|
||||
}
|
||||
|
@ -185,8 +185,6 @@ fn layout_page_run_impl(
|
||||
)?;
|
||||
|
||||
// Layouts a single marginal.
|
||||
// TODO: add some sort of tag that indicates the marginals and use it to
|
||||
// mark them as artifacts for PDF/UA.
|
||||
let mut layout_marginal = |content: &Option<Content>, area, align| {
|
||||
let Some(content) = content else { return Ok(None) };
|
||||
let aligned = content.clone().styled(AlignElem::set_alignment(align));
|
||||
|
@ -10,7 +10,7 @@ use crate::foundations::{
|
||||
cast, elem, Args, AutoValue, Cast, Construct, Content, Dict, Fold, NativeElement,
|
||||
Set, Smart, Value,
|
||||
};
|
||||
use crate::introspection::Introspector;
|
||||
use crate::introspection::{Introspector, Locatable};
|
||||
use crate::layout::{
|
||||
Abs, Alignment, FlushElem, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel,
|
||||
Sides, SpecificAlignment,
|
||||
@ -451,6 +451,28 @@ 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 {
|
||||
|
@ -31,7 +31,7 @@ use crate::metadata::build_metadata;
|
||||
use crate::outline::build_outline;
|
||||
use crate::page::PageLabelExt;
|
||||
use crate::shape::handle_shape;
|
||||
use crate::tags::{handle_close_tag, handle_open_tag, Placeholder, TagNode, Tags};
|
||||
use crate::tags::{self, Placeholder, TagNode, Tags};
|
||||
use crate::text::handle_text;
|
||||
use crate::util::{convert_path, display_font, AbsExt, TransformExt};
|
||||
use crate::PdfOptions;
|
||||
@ -114,18 +114,7 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
|
||||
let mut surface = page.surface();
|
||||
let mut fc = FrameContext::new(typst_page.frame.size());
|
||||
|
||||
// Marked-content may not cross page boundaries: reopen tag
|
||||
// that was closed at the end of the last page.
|
||||
if let Some((_, _, nodes)) = gc.tags.stack.last_mut() {
|
||||
let tag = if gc.tags.in_artifact {
|
||||
ContentTag::Artifact(ArtifactType::Other)
|
||||
} else {
|
||||
ContentTag::Other
|
||||
};
|
||||
// TODO: somehow avoid empty marked-content sequences
|
||||
let id = surface.start_tagged(tag);
|
||||
nodes.push(TagNode::Leaf(id));
|
||||
}
|
||||
tags::restart(gc, &mut surface);
|
||||
|
||||
handle_frame(
|
||||
&mut fc,
|
||||
@ -135,17 +124,11 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
|
||||
gc,
|
||||
)?;
|
||||
|
||||
// Marked-content may not cross page boundaries: close open tag.
|
||||
if !gc.tags.stack.is_empty() {
|
||||
surface.end_tagged();
|
||||
}
|
||||
tags::end_open(gc, &mut surface);
|
||||
|
||||
surface.finish();
|
||||
|
||||
for (placeholder, annotation) in fc.annotations {
|
||||
let annotation_id = page.add_tagged_annotation(annotation);
|
||||
gc.tags.init_placeholder(placeholder, Node::Leaf(annotation_id));
|
||||
}
|
||||
tags::add_annotations(gc, &mut page, fc.annotations);
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,10 +301,10 @@ pub(crate) fn handle_frame(
|
||||
handle_link(fc, gc, alt.as_ref().map(EcoString::to_string), dest, *size)
|
||||
}
|
||||
FrameItem::Tag(introspection::Tag::Start(elem)) => {
|
||||
handle_open_tag(gc, surface, elem)
|
||||
tags::handle_start(gc, surface, elem)
|
||||
}
|
||||
FrameItem::Tag(introspection::Tag::End(loc, _)) => {
|
||||
handle_close_tag(gc, surface, loc);
|
||||
tags::handle_end(gc, surface, loc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,15 @@
|
||||
use std::cell::OnceCell;
|
||||
use std::ops::Deref;
|
||||
|
||||
use krilla::annotation::Annotation;
|
||||
use krilla::page::Page;
|
||||
use krilla::surface::Surface;
|
||||
use krilla::tagging::{ContentTag, Identifier, Node, Tag, TagGroup, TagTree};
|
||||
use krilla::tagging::{
|
||||
ArtifactType, ContentTag, Identifier, Node, Tag, TagGroup, TagTree,
|
||||
};
|
||||
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 crate::convert::GlobalContext;
|
||||
@ -12,7 +18,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: bool,
|
||||
pub(crate) in_artifact: Option<(Location, ArtifactMarker)>,
|
||||
|
||||
/// The output.
|
||||
pub(crate) tree: Vec<TagNode>,
|
||||
@ -34,7 +40,7 @@ impl Tags {
|
||||
Self {
|
||||
stack: Vec::new(),
|
||||
placeholders: Vec::new(),
|
||||
in_artifact: false,
|
||||
in_artifact: None,
|
||||
|
||||
tree: Vec::new(),
|
||||
}
|
||||
@ -142,16 +148,61 @@ impl Tags {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_open_tag(
|
||||
/// Marked-content may not cross page boundaries: restart tag that was still open
|
||||
/// 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 {
|
||||
let ty = artifact_type(marker.kind);
|
||||
surface.start_tagged(ContentTag::Artifact(ty));
|
||||
} else if let Some((_, _, nodes)) = gc.tags.stack.last_mut() {
|
||||
let id = surface.start_tagged(ContentTag::Other);
|
||||
nodes.push(TagNode::Leaf(id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Marked-content may not cross page boundaries: end any open tag.
|
||||
pub(crate) fn end_open(gc: &mut GlobalContext, surface: &mut Surface) {
|
||||
if !gc.tags.stack.is_empty() || gc.tags.in_artifact.is_some() {
|
||||
surface.end_tagged();
|
||||
}
|
||||
}
|
||||
|
||||
/// Add all annotations that were found in the page frame.
|
||||
pub(crate) fn add_annotations(
|
||||
gc: &mut GlobalContext,
|
||||
page: &mut Page,
|
||||
annotations: Vec<(Placeholder, Annotation)>,
|
||||
) {
|
||||
for (placeholder, annotation) in annotations {
|
||||
let annotation_id = page.add_tagged_annotation(annotation);
|
||||
gc.tags.init_placeholder(placeholder, Node::Leaf(annotation_id));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_start(
|
||||
gc: &mut GlobalContext,
|
||||
surface: &mut Surface,
|
||||
elem: &Content,
|
||||
) {
|
||||
if gc.tags.in_artifact {
|
||||
if gc.tags.in_artifact.is_some() {
|
||||
// Don't nest artifacts
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(loc) = elem.location() else { return };
|
||||
let loc = elem.location().unwrap();
|
||||
|
||||
if let Some(marker) = elem.to_packed::<ArtifactMarker>() {
|
||||
if !gc.tags.stack.is_empty() {
|
||||
surface.end_tagged();
|
||||
}
|
||||
|
||||
let marker = *marker.deref();
|
||||
let ty = artifact_type(marker.kind);
|
||||
surface.start_tagged(ContentTag::Artifact(ty));
|
||||
gc.tags.in_artifact = Some((loc, marker));
|
||||
return;
|
||||
}
|
||||
|
||||
let tag = if let Some(heading) = elem.to_packed::<HeadingElem>() {
|
||||
let level = heading.resolve_level(StyleChain::default());
|
||||
@ -186,11 +237,14 @@ pub(crate) fn handle_open_tag(
|
||||
gc.tags.stack.push((loc, tag, vec![TagNode::Leaf(content_id)]));
|
||||
}
|
||||
|
||||
pub(crate) fn handle_close_tag(
|
||||
gc: &mut GlobalContext,
|
||||
surface: &mut Surface,
|
||||
loc: &Location,
|
||||
) {
|
||||
pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: &Location) {
|
||||
if gc.tags.in_artifact.is_some_and(|(l, _)| l == *loc) {
|
||||
gc.tags.in_artifact = None;
|
||||
surface.end_tagged();
|
||||
restart(gc, surface);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((_, tag, nodes)) = gc.tags.stack.pop_if(|(l, ..)| l == loc) else {
|
||||
return;
|
||||
};
|
||||
@ -207,3 +261,11 @@ pub(crate) fn handle_close_tag(
|
||||
gc.tags.tree.push(TagNode::Group(tag, nodes));
|
||||
}
|
||||
}
|
||||
|
||||
fn artifact_type(kind: ArtifactKind) -> ArtifactType {
|
||||
match kind {
|
||||
ArtifactKind::Header => ArtifactType::Header,
|
||||
ArtifactKind::Footer => ArtifactType::Footer,
|
||||
ArtifactKind::Page => ArtifactType::Page,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user