Compare commits

..

No commits in common. "1780a28c7009842d9841838f377f6097a118e5bf" and "d35640bf8e8b973f132470b0a4bc0bbf23521ad8" have entirely different histories.

8 changed files with 32 additions and 76 deletions

View File

@ -246,13 +246,6 @@ pub struct CompileArgs {
#[arg(long = "pdf-standard", value_delimiter = ',')] #[arg(long = "pdf-standard", value_delimiter = ',')]
pub pdf_standard: Vec<PdfStandard>, pub pdf_standard: Vec<PdfStandard>,
/// By default, even when not producing a `PDF/UA-1` document, a tagged PDF
/// document is written to provide a baseline of accessibility. In some
/// circumstances (for example when trying to reduce the size of a document)
/// it can be desirable to disable tagged PDF.
#[arg(long = "disable-pdf-tags")]
pub disable_pdf_tags: bool,
/// The PPI (pixels per inch) to use for PNG export. /// The PPI (pixels per inch) to use for PNG export.
#[arg(long = "ppi", default_value_t = 144.0)] #[arg(long = "ppi", default_value_t = 144.0)]
pub ppi: f32, pub ppi: f32,
@ -513,9 +506,6 @@ pub enum PdfStandard {
/// PDF/A-4e. /// PDF/A-4e.
#[value(name = "a-4e")] #[value(name = "a-4e")]
A_4e, A_4e,
/// PDF/UA-1.
#[value(name = "ua-1")]
Ua_1,
} }
display_possible_values!(PdfStandard); display_possible_values!(PdfStandard);

View File

@ -65,8 +65,6 @@ pub struct CompileConfig {
pub open: Option<Option<String>>, pub open: Option<Option<String>>,
/// A list of standards the PDF should conform to. /// A list of standards the PDF should conform to.
pub pdf_standards: PdfStandards, pub pdf_standards: PdfStandards,
/// Whether to write PDF (accessibility) tags.
pub disable_pdf_tags: bool,
/// A path to write a Makefile rule describing the current compilation. /// A path to write a Makefile rule describing the current compilation.
pub make_deps: Option<PathBuf>, pub make_deps: Option<PathBuf>,
/// The PPI (pixels per inch) to use for PNG export. /// The PPI (pixels per inch) to use for PNG export.
@ -152,7 +150,6 @@ impl CompileConfig {
output_format, output_format,
pages, pages,
pdf_standards, pdf_standards,
disable_pdf_tags: args.disable_pdf_tags,
creation_timestamp: args.world.creation_timestamp, creation_timestamp: args.world.creation_timestamp,
make_deps: args.make_deps.clone(), make_deps: args.make_deps.clone(),
ppi: args.ppi, ppi: args.ppi,
@ -294,7 +291,6 @@ fn export_pdf(document: &PagedDocument, config: &CompileConfig) -> SourceResult<
timestamp, timestamp,
page_ranges: config.pages.clone(), page_ranges: config.pages.clone(),
standards: config.pdf_standards.clone(), standards: config.pdf_standards.clone(),
disable_tags: config.disable_pdf_tags,
}; };
let buffer = typst_pdf::pdf(document, &options)?; let buffer = typst_pdf::pdf(document, &options)?;
config config
@ -777,7 +773,6 @@ impl From<PdfStandard> for typst_pdf::PdfStandard {
PdfStandard::A_4 => typst_pdf::PdfStandard::A_4, PdfStandard::A_4 => typst_pdf::PdfStandard::A_4,
PdfStandard::A_4f => typst_pdf::PdfStandard::A_4f, PdfStandard::A_4f => typst_pdf::PdfStandard::A_4f,
PdfStandard::A_4e => typst_pdf::PdfStandard::A_4e, PdfStandard::A_4e => typst_pdf::PdfStandard::A_4e,
PdfStandard::Ua_1 => typst_pdf::PdfStandard::Ua_1,
} }
} }
} }

View File

@ -25,6 +25,7 @@ use crate::layout::{BlockElem, Length};
/// Berlin, the 22nd of December, 2022 /// Berlin, the 22nd of December, 2022
/// ] /// ]
/// ``` /// ```
// TODO: should this be a PDF artifact by deafult?
#[elem(Locatable, Show)] #[elem(Locatable, Show)]
pub struct RepeatElem { pub struct RepeatElem {
/// The content to repeat. /// The content to repeat.

View File

@ -192,11 +192,10 @@ pub enum TableHeaderScope {
/// TODO: maybe generalize this and use it to mark html elements with `aria-hidden="true"`? /// TODO: maybe generalize this and use it to mark html elements with `aria-hidden="true"`?
#[elem(Locatable, Show)] #[elem(Locatable, Show)]
pub struct ArtifactElem { pub struct ArtifactElem {
/// The artifact kind.
#[default(ArtifactKind::Other)] #[default(ArtifactKind::Other)]
pub kind: ArtifactKind, pub kind: ArtifactKind,
/// The content that is an artifact. /// The content to underline.
#[required] #[required]
pub body: Content, pub body: Content,
} }

View File

@ -39,14 +39,17 @@ 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: false, // true,
no_device_cs: true, no_device_cs: true,
ascii_compatible: false, ascii_compatible: true, // false,
xmp_metadata: true, xmp_metadata: true,
cmyk_profile: None, cmyk_profile: None,
configuration: options.standards.config, configuration: config, // options.standards.config,
enable_tagging: options.disable_tags, // TODO: allow opting out of tagging PDFs
enable_tagging: true,
render_svg_glyph_fn: render_svg_glyph, render_svg_glyph_fn: render_svg_glyph,
}; };

View File

@ -4,7 +4,6 @@ use std::sync::{Arc, OnceLock};
use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba}; use image::{DynamicImage, EncodableLayout, GenericImageView, Rgba};
use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace}; use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace};
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla::tagging::SpanTag;
use krilla_svg::{SurfaceExt, SvgSettings}; use krilla_svg::{SurfaceExt, SvgSettings};
use typst_library::diag::{bail, SourceResult}; use typst_library::diag::{bail, SourceResult};
use typst_library::foundations::Smart; use typst_library::foundations::Smart;
@ -34,8 +33,7 @@ pub(crate) fn handle_image(
gc.image_spans.insert(span); gc.image_spans.insert(span);
let mut handle = let mut handle = tags::start_marked(gc, surface);
tags::start_span(gc, surface, SpanTag::empty().with_alt_text(image.alt()));
let surface = handle.surface(); let surface = handle.surface();
match image.kind() { match image.kind() {
ImageKind::Raster(raster) => { ImageKind::Raster(raster) => {

View File

@ -54,11 +54,6 @@ pub struct PdfOptions<'a> {
pub page_ranges: Option<PageRanges>, pub page_ranges: Option<PageRanges>,
/// A list of PDF standards that Typst will enforce conformance with. /// A list of PDF standards that Typst will enforce conformance with.
pub standards: PdfStandards, pub standards: PdfStandards,
/// By default, even when not producing a `PDF/UA-1` document, a tagged PDF
/// document is written to provide a baseline of accessibility. In some
/// circumstances, for example when trying to reduce the size of a document,
/// it can be desirable to disable tagged PDF.
pub disable_tags: bool,
} }
/// Encapsulates a list of compatible PDF standards. /// Encapsulates a list of compatible PDF standards.
@ -110,7 +105,6 @@ impl PdfStandards {
PdfStandard::A_4 => set_validator(Validator::A4)?, PdfStandard::A_4 => set_validator(Validator::A4)?,
PdfStandard::A_4f => set_validator(Validator::A4F)?, PdfStandard::A_4f => set_validator(Validator::A4F)?,
PdfStandard::A_4e => set_validator(Validator::A4E)?, PdfStandard::A_4e => set_validator(Validator::A4E)?,
PdfStandard::Ua_1 => set_validator(Validator::UA1)?,
} }
} }
@ -194,7 +188,4 @@ pub enum PdfStandard {
/// PDF/A-4e. /// PDF/A-4e.
#[serde(rename = "a-4e")] #[serde(rename = "a-4e")]
A_4e, A_4e,
/// PDF/UA-1.
#[serde(rename = "ua-1")]
Ua_1,
} }

View File

@ -5,15 +5,14 @@ use ecow::EcoString;
use krilla::page::Page; use krilla::page::Page;
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla::tagging::{ use krilla::tagging::{
ArtifactType, ContentTag, Identifier, Node, SpanTag, TableCellSpan, TableDataCell, ArtifactType, ContentTag, Identifier, Node, TableCellSpan, TableDataCell,
TableHeaderCell, TableHeaderScope, Tag, TagBuilder, TagGroup, TagKind, TagTree, TableHeaderCell, TableHeaderScope, Tag, TagBuilder, TagGroup, TagKind, TagTree,
}; };
use typst_library::foundations::{Content, LinkMarker, Packed, StyleChain}; use typst_library::foundations::{Content, LinkMarker, Packed, StyleChain};
use typst_library::introspection::Location; use typst_library::introspection::Location;
use typst_library::layout::RepeatElem;
use typst_library::model::{ use typst_library::model::{
Destination, FigureCaption, FigureElem, HeadingElem, Outlinable, OutlineElem, Destination, FigureCaption, FigureElem, HeadingElem, Outlinable, OutlineElem,
OutlineEntry, TableCell, TableElem, OutlineEntry, TableCell, TableElem, TableHLine, TableVLine,
}; };
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind}; use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind};
use typst_library::visualize::ImageElem; use typst_library::visualize::ImageElem;
@ -157,16 +156,16 @@ impl Tags {
/// Returns the current parent's list of children and the structure type ([Tag]). /// Returns the current parent's list of children and the structure type ([Tag]).
/// In case of the document root the structure type will be `None`. /// In case of the document root the structure type will be `None`.
pub(crate) fn parent(&mut self) -> Option<&mut StackEntryKind> { pub(crate) fn parent(&mut self) -> (Option<&mut StackEntryKind>, &mut Vec<TagNode>) {
self.stack.last_mut().map(|e| &mut e.kind) if let Some(entry) = self.stack.last_mut() {
(Some(&mut entry.kind), &mut entry.nodes)
} else {
(None, &mut self.tree)
}
} }
pub(crate) fn push(&mut self, node: TagNode) { pub(crate) fn push(&mut self, node: TagNode) {
if let Some(entry) = self.stack.last_mut() { self.parent().1.push(node);
entry.nodes.push(node);
} else {
self.tree.push(node);
}
} }
pub(crate) fn build_tree(&mut self) -> TagTree { pub(crate) fn build_tree(&mut self) -> TagTree {
@ -225,34 +224,12 @@ impl<'a> TagHandle<'a, '_> {
pub(crate) fn start_marked<'a, 'b>( pub(crate) fn start_marked<'a, 'b>(
gc: &mut GlobalContext, gc: &mut GlobalContext,
surface: &'b mut Surface<'a>, surface: &'b mut Surface<'a>,
) -> TagHandle<'a, 'b> {
start_content(gc, surface, ContentTag::Other)
}
/// Returns a [`TagHandle`] that automatically calls [`Surface::end_tagged`]
/// when dropped.
pub(crate) fn start_span<'a, 'b>(
gc: &mut GlobalContext,
surface: &'b mut Surface<'a>,
span: SpanTag,
) -> TagHandle<'a, 'b> {
start_content(gc, surface, ContentTag::Span(span))
}
fn start_content<'a, 'b>(
gc: &mut GlobalContext,
surface: &'b mut Surface<'a>,
content: ContentTag,
) -> TagHandle<'a, 'b> { ) -> TagHandle<'a, 'b> {
let content = if let Some((_, kind)) = gc.tags.in_artifact { let content = if let Some((_, kind)) = gc.tags.in_artifact {
let ty = artifact_type(kind); let ty = artifact_type(kind);
ContentTag::Artifact(ty) ContentTag::Artifact(ty)
} else if let Some(StackEntryKind::Table(_)) = gc.tags.stack.last().map(|e| &e.kind) {
// Mark any direct child of a table as an aritfact. Any real content
// will be wrapped inside a `TableCell`.
ContentTag::Artifact(ArtifactType::Other)
} else { } else {
content ContentTag::Other
}; };
let id = surface.start_tagged(content); let id = surface.start_tagged(content);
gc.tags.push(TagNode::Leaf(id)); gc.tags.push(TagNode::Leaf(id));
@ -288,9 +265,6 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
let kind = artifact.kind(StyleChain::default()); let kind = artifact.kind(StyleChain::default());
start_artifact(gc, loc, kind); start_artifact(gc, loc, kind);
return; return;
} else if let Some(_) = elem.to_packed::<RepeatElem>() {
start_artifact(gc, loc, ArtifactKind::Other);
return;
} }
let tag: Tag = if let Some(pdf_tag) = elem.to_packed::<PdfTagElem>() { let tag: Tag = if let Some(pdf_tag) = elem.to_packed::<PdfTagElem>() {
@ -321,14 +295,13 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
} else if let Some(image) = elem.to_packed::<ImageElem>() { } else if let Some(image) = elem.to_packed::<ImageElem>() {
let alt = image.alt(StyleChain::default()).map(|s| s.to_string()); let alt = image.alt(StyleChain::default()).map(|s| s.to_string());
let figure_tag = (gc.tags.parent()) let figure_tag = (gc.tags.parent().0)
.and_then(StackEntryKind::as_standard_mut) .and_then(|parent| parent.as_standard_mut())
.filter(|tag| tag.kind == TagKind::Figure); .filter(|tag| tag.kind == TagKind::Figure && tag.alt_text.is_none());
if let Some(figure_tag) = figure_tag { if let Some(figure_tag) = figure_tag {
// Set alt text of outer figure tag, if not present. // HACK: set alt text of outer figure tag, if the contained image
if figure_tag.alt_text.is_none() { // has alt text specified
figure_tag.alt_text = alt; figure_tag.alt_text = alt;
}
return; return;
} else { } else {
TagKind::Figure.with_alt_text(alt) TagKind::Figure.with_alt_text(alt)
@ -346,6 +319,12 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
} else if let Some(cell) = elem.to_packed::<TableCell>() { } else if let Some(cell) = elem.to_packed::<TableCell>() {
push_stack(gc, loc, StackEntryKind::TableCell(cell.clone())); push_stack(gc, loc, StackEntryKind::TableCell(cell.clone()));
return; return;
} else if let Some(_) = elem.to_packed::<TableHLine>() {
start_artifact(gc, loc, ArtifactKind::Other);
return;
} else if let Some(_) = elem.to_packed::<TableVLine>() {
start_artifact(gc, loc, ArtifactKind::Other);
return;
} else { } else {
return; return;
}; };