mirror of
https://github.com/typst/typst
synced 2025-08-21 10:18:35 +08:00
Compare commits
2 Commits
d99ebe42ac
...
5ada3bb3cd
Author | SHA1 | Date | |
---|---|---|---|
|
5ada3bb3cd | ||
|
8a97c1ee57 |
@ -185,6 +185,8 @@ fn layout_page_run_impl(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Layouts a single marginal.
|
// 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 mut layout_marginal = |content: &Option<Content>, area, align| {
|
||||||
let Some(content) = content else { return Ok(None) };
|
let Some(content) = content else { return Ok(None) };
|
||||||
let aligned = content.clone().styled(AlignElem::set_alignment(align));
|
let aligned = content.clone().styled(AlignElem::set_alignment(align));
|
||||||
|
@ -10,11 +10,12 @@ use krilla::error::KrillaError;
|
|||||||
use krilla::geom::PathBuilder;
|
use krilla::geom::PathBuilder;
|
||||||
use krilla::page::{PageLabel, PageSettings};
|
use krilla::page::{PageLabel, PageSettings};
|
||||||
use krilla::surface::Surface;
|
use krilla::surface::Surface;
|
||||||
|
use krilla::tagging::{Node, SpanTag, Tag, TagGroup, TagTree};
|
||||||
use krilla::{Document, SerializeSettings};
|
use krilla::{Document, SerializeSettings};
|
||||||
use krilla_svg::render_svg_glyph;
|
use krilla_svg::render_svg_glyph;
|
||||||
use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
|
use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
|
||||||
use typst_library::foundations::NativeElement;
|
use typst_library::foundations::{NativeElement, StyleChain};
|
||||||
use typst_library::introspection::Location;
|
use typst_library::introspection::{self, Location};
|
||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
Abs, Frame, FrameItem, GroupItem, PagedDocument, Size, Transform,
|
Abs, Frame, FrameItem, GroupItem, PagedDocument, Size, Transform,
|
||||||
};
|
};
|
||||||
@ -39,14 +40,18 @@ pub fn convert(
|
|||||||
typst_document: &PagedDocument,
|
typst_document: &PagedDocument,
|
||||||
options: &PdfOptions,
|
options: &PdfOptions,
|
||||||
) -> SourceResult<Vec<u8>> {
|
) -> SourceResult<Vec<u8>> {
|
||||||
|
// HACK
|
||||||
|
let config = Configuration::new_with_validator(Validator::UA1);
|
||||||
let settings = SerializeSettings {
|
let settings = SerializeSettings {
|
||||||
compress_content_streams: true,
|
compress_content_streams: true,
|
||||||
no_device_cs: true,
|
no_device_cs: true,
|
||||||
ascii_compatible: false,
|
ascii_compatible: false,
|
||||||
xmp_metadata: true,
|
xmp_metadata: true,
|
||||||
cmyk_profile: None,
|
cmyk_profile: None,
|
||||||
configuration: options.standards.config,
|
configuration: config,
|
||||||
enable_tagging: false,
|
// TODO: Should we just set this to false? If set to `false` this will
|
||||||
|
// automatically be enabled if the `UA1` validator is used.
|
||||||
|
enable_tagging: true,
|
||||||
render_svg_glyph_fn: render_svg_glyph,
|
render_svg_glyph_fn: render_svg_glyph,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,6 +59,7 @@ pub fn convert(
|
|||||||
let page_index_converter = PageIndexConverter::new(typst_document, options);
|
let page_index_converter = PageIndexConverter::new(typst_document, options);
|
||||||
let named_destinations =
|
let named_destinations =
|
||||||
collect_named_destinations(typst_document, &page_index_converter);
|
collect_named_destinations(typst_document, &page_index_converter);
|
||||||
|
|
||||||
let mut gc = GlobalContext::new(
|
let mut gc = GlobalContext::new(
|
||||||
typst_document,
|
typst_document,
|
||||||
options,
|
options,
|
||||||
@ -67,6 +73,12 @@ pub fn convert(
|
|||||||
document.set_outline(build_outline(&gc));
|
document.set_outline(build_outline(&gc));
|
||||||
document.set_metadata(build_metadata(&gc));
|
document.set_metadata(build_metadata(&gc));
|
||||||
|
|
||||||
|
let mut tag_tree = TagTree::new();
|
||||||
|
for tag in gc.tags.drain(..) {
|
||||||
|
tag_tree.push(tag);
|
||||||
|
}
|
||||||
|
document.set_tag_tree(tag_tree);
|
||||||
|
|
||||||
finish(document, gc, options.standards.config)
|
finish(document, gc, options.standards.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +117,8 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
|
|||||||
let mut surface = page.surface();
|
let mut surface = page.surface();
|
||||||
let mut fc = FrameContext::new(typst_page.frame.size());
|
let mut fc = FrameContext::new(typst_page.frame.size());
|
||||||
|
|
||||||
|
// TODO: PDF/UA tags may not cross page boundaries: close tags left
|
||||||
|
// in the stack and reopen them on a new page
|
||||||
handle_frame(
|
handle_frame(
|
||||||
&mut fc,
|
&mut fc,
|
||||||
&typst_page.frame,
|
&typst_page.frame,
|
||||||
@ -225,6 +239,8 @@ pub(crate) struct GlobalContext<'a> {
|
|||||||
/// The languages used throughout the document.
|
/// The languages used throughout the document.
|
||||||
pub(crate) languages: BTreeMap<Lang, usize>,
|
pub(crate) languages: BTreeMap<Lang, usize>,
|
||||||
pub(crate) page_index_converter: PageIndexConverter,
|
pub(crate) page_index_converter: PageIndexConverter,
|
||||||
|
pub(crate) tag_stack: Vec<Location>,
|
||||||
|
pub(crate) tags: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GlobalContext<'a> {
|
impl<'a> GlobalContext<'a> {
|
||||||
@ -244,6 +260,8 @@ impl<'a> GlobalContext<'a> {
|
|||||||
image_spans: HashSet::new(),
|
image_spans: HashSet::new(),
|
||||||
languages: BTreeMap::new(),
|
languages: BTreeMap::new(),
|
||||||
page_index_converter,
|
page_index_converter,
|
||||||
|
tag_stack: Vec::new(),
|
||||||
|
tags: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,7 +297,40 @@ pub(crate) fn handle_frame(
|
|||||||
handle_image(gc, fc, image, *size, surface, *span)?
|
handle_image(gc, fc, image, *size, surface, *span)?
|
||||||
}
|
}
|
||||||
FrameItem::Link(d, s) => handle_link(fc, gc, d, *s),
|
FrameItem::Link(d, s) => handle_link(fc, gc, d, *s),
|
||||||
FrameItem::Tag(_) => {}
|
FrameItem::Tag(introspection::Tag::Start(elem)) => {
|
||||||
|
let Some(heading) = elem.to_packed::<HeadingElem>() else { continue };
|
||||||
|
let loc = heading.location().expect("heading element to have a location");
|
||||||
|
|
||||||
|
// TODO: PDF/UA "Logical Structure" should not include artifacts:
|
||||||
|
// mabye close all open tags before an artifact and reopen them after?
|
||||||
|
|
||||||
|
let level = heading.resolve_level(StyleChain::default());
|
||||||
|
let name = heading.body.plain_text().to_string();
|
||||||
|
let heading_id = surface
|
||||||
|
.start_tagged(krilla::tagging::ContentTag::Span(SpanTag::empty()));
|
||||||
|
let tag = match level.get() {
|
||||||
|
1 => Tag::H1(Some(name)),
|
||||||
|
2 => Tag::H2(Some(name)),
|
||||||
|
3 => Tag::H3(Some(name)),
|
||||||
|
4 => Tag::H4(Some(name)),
|
||||||
|
5 => Tag::H5(Some(name)),
|
||||||
|
_ => Tag::H6(Some(name)),
|
||||||
|
};
|
||||||
|
let mut tag_group = TagGroup::new(tag);
|
||||||
|
tag_group.push(Node::Leaf(heading_id));
|
||||||
|
// TODO: Keep track of the logical document hierarchy and build
|
||||||
|
// a proper tag tree.
|
||||||
|
gc.tags.push(Node::Group(tag_group));
|
||||||
|
|
||||||
|
gc.tag_stack.push(loc);
|
||||||
|
}
|
||||||
|
FrameItem::Tag(introspection::Tag::End(loc, _)) => {
|
||||||
|
// FIXME: support or split up content tags that span multiple pages
|
||||||
|
if gc.tag_stack.last() == Some(loc) {
|
||||||
|
surface.end_tagged();
|
||||||
|
gc.tag_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fc.pop();
|
fc.pop();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user