mirror of
https://github.com/typst/typst
synced 2025-08-23 11:14:13 +08:00
Compare commits
6 Commits
1780a28c70
...
e307a44477
Author | SHA1 | Date | |
---|---|---|---|
|
e307a44477 | ||
|
4d8bb38384 | ||
|
b0d7a2de8e | ||
|
940343fc81 | ||
|
a8b64ddd83 | ||
|
2731b0a015 |
@ -246,6 +246,13 @@ 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,
|
||||||
@ -506,6 +513,9 @@ 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);
|
||||||
|
@ -65,6 +65,8 @@ 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.
|
||||||
@ -150,6 +152,7 @@ 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,
|
||||||
@ -291,6 +294,7 @@ 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
|
||||||
@ -773,6 +777,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ 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.
|
||||||
|
@ -192,10 +192,11 @@ 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 to underline.
|
/// The content that is an artifact.
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
@ -39,17 +39,14 @@ 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: false, // true,
|
compress_content_streams: true,
|
||||||
no_device_cs: true,
|
no_device_cs: true,
|
||||||
ascii_compatible: true, // false,
|
ascii_compatible: false,
|
||||||
xmp_metadata: true,
|
xmp_metadata: true,
|
||||||
cmyk_profile: None,
|
cmyk_profile: None,
|
||||||
configuration: config, // options.standards.config,
|
configuration: options.standards.config,
|
||||||
// TODO: allow opting out of tagging PDFs
|
enable_tagging: !options.disable_tags,
|
||||||
enable_tagging: true,
|
|
||||||
render_svg_glyph_fn: render_svg_glyph,
|
render_svg_glyph_fn: render_svg_glyph,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ 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;
|
||||||
@ -33,7 +34,8 @@ pub(crate) fn handle_image(
|
|||||||
|
|
||||||
gc.image_spans.insert(span);
|
gc.image_spans.insert(span);
|
||||||
|
|
||||||
let mut handle = tags::start_marked(gc, surface);
|
let mut handle =
|
||||||
|
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) => {
|
||||||
|
@ -54,6 +54,11 @@ 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.
|
||||||
@ -105,6 +110,7 @@ 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)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,4 +194,7 @@ 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,
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,15 @@ 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, TableCellSpan, TableDataCell,
|
ArtifactType, ContentTag, Identifier, Node, SpanTag, 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, TableHLine, TableVLine,
|
OutlineEntry, TableCell, TableElem,
|
||||||
};
|
};
|
||||||
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;
|
||||||
@ -41,6 +42,8 @@ pub(crate) struct StackEntry {
|
|||||||
pub(crate) enum StackEntryKind {
|
pub(crate) enum StackEntryKind {
|
||||||
Standard(Tag),
|
Standard(Tag),
|
||||||
Link(LinkId, Packed<LinkMarker>),
|
Link(LinkId, Packed<LinkMarker>),
|
||||||
|
Outline(OutlineCtx),
|
||||||
|
OutlineEntry(Packed<OutlineEntry>),
|
||||||
Table(TableCtx),
|
Table(TableCtx),
|
||||||
TableCell(Packed<TableCell>),
|
TableCell(Packed<TableCell>),
|
||||||
}
|
}
|
||||||
@ -55,12 +58,82 @@ impl StackEntryKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct OutlineCtx {
|
||||||
|
stack: Vec<OutlineSection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct OutlineSection {
|
||||||
|
entries: Vec<TagNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutlineSection {
|
||||||
|
const fn new() -> Self {
|
||||||
|
OutlineSection { entries: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, entry: TagNode) {
|
||||||
|
self.entries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_tag(self) -> TagNode {
|
||||||
|
TagNode::Group(TagKind::TOC.into(), self.entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutlineCtx {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { stack: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
&mut self,
|
||||||
|
outline_nodes: &mut Vec<TagNode>,
|
||||||
|
entry: Packed<OutlineEntry>,
|
||||||
|
nodes: Vec<TagNode>,
|
||||||
|
) {
|
||||||
|
let expected_len = entry.level.get() - 1;
|
||||||
|
if self.stack.len() < expected_len {
|
||||||
|
self.stack.resize_with(expected_len, || OutlineSection::new());
|
||||||
|
} else {
|
||||||
|
while self.stack.len() > expected_len {
|
||||||
|
self.finish_section(outline_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let section_entry = TagNode::Group(TagKind::TOCI.into(), nodes);
|
||||||
|
self.push(outline_nodes, section_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_section(&mut self, outline_nodes: &mut Vec<TagNode>) {
|
||||||
|
let sub_section = self.stack.pop().unwrap().into_tag();
|
||||||
|
self.push(outline_nodes, sub_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, outline_nodes: &mut Vec<TagNode>, entry: TagNode) {
|
||||||
|
match self.stack.last_mut() {
|
||||||
|
Some(section) => section.push(entry),
|
||||||
|
None => outline_nodes.push(entry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_outline(mut self, mut outline_nodes: Vec<TagNode>) -> Vec<TagNode> {
|
||||||
|
while self.stack.len() > 0 {
|
||||||
|
self.finish_section(&mut outline_nodes);
|
||||||
|
}
|
||||||
|
outline_nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct TableCtx {
|
pub(crate) struct TableCtx {
|
||||||
table: Packed<TableElem>,
|
table: Packed<TableElem>,
|
||||||
rows: Vec<Vec<Option<(Packed<TableCell>, Tag, Vec<TagNode>)>>>,
|
rows: Vec<Vec<Option<(Packed<TableCell>, Tag, Vec<TagNode>)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableCtx {
|
impl TableCtx {
|
||||||
|
fn new(table: Packed<TableElem>) -> Self {
|
||||||
|
Self { table: table.clone(), rows: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&mut self, cell: Packed<TableCell>, nodes: Vec<TagNode>) {
|
fn insert(&mut self, cell: Packed<TableCell>, nodes: Vec<TagNode>) {
|
||||||
let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
||||||
let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
||||||
@ -156,16 +229,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>, &mut Vec<TagNode>) {
|
pub(crate) fn parent(&mut self) -> Option<&mut StackEntryKind> {
|
||||||
if let Some(entry) = self.stack.last_mut() {
|
self.stack.last_mut().map(|e| &mut e.kind)
|
||||||
(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) {
|
||||||
self.parent().1.push(node);
|
if let Some(entry) = self.stack.last_mut() {
|
||||||
|
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 {
|
||||||
@ -224,12 +297,34 @@ 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 {
|
||||||
ContentTag::Other
|
content
|
||||||
};
|
};
|
||||||
let id = surface.start_tagged(content);
|
let id = surface.start_tagged(content);
|
||||||
gc.tags.push(TagNode::Leaf(id));
|
gc.tags.push(TagNode::Leaf(id));
|
||||||
@ -265,6 +360,9 @@ 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>() {
|
||||||
@ -286,22 +384,25 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
|||||||
_ => TagKind::H6(Some(name)).into(),
|
_ => TagKind::H6(Some(name)).into(),
|
||||||
}
|
}
|
||||||
} else if let Some(_) = elem.to_packed::<OutlineElem>() {
|
} else if let Some(_) = elem.to_packed::<OutlineElem>() {
|
||||||
TagKind::TOC.into()
|
push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()));
|
||||||
} else if let Some(_) = elem.to_packed::<OutlineEntry>() {
|
return;
|
||||||
TagKind::TOCI.into()
|
} else if let Some(entry) = elem.to_packed::<OutlineEntry>() {
|
||||||
|
push_stack(gc, loc, StackEntryKind::OutlineEntry(entry.clone()));
|
||||||
|
return;
|
||||||
} else if let Some(_) = elem.to_packed::<FigureElem>() {
|
} else if let Some(_) = elem.to_packed::<FigureElem>() {
|
||||||
let alt = None; // TODO
|
let alt = None; // TODO
|
||||||
TagKind::Figure.with_alt_text(alt)
|
TagKind::Figure.with_alt_text(alt)
|
||||||
} 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().0)
|
let figure_tag = (gc.tags.parent())
|
||||||
.and_then(|parent| parent.as_standard_mut())
|
.and_then(StackEntryKind::as_standard_mut)
|
||||||
.filter(|tag| tag.kind == TagKind::Figure && tag.alt_text.is_none());
|
.filter(|tag| tag.kind == TagKind::Figure);
|
||||||
if let Some(figure_tag) = figure_tag {
|
if let Some(figure_tag) = figure_tag {
|
||||||
// HACK: set alt text of outer figure tag, if the contained image
|
// Set alt text of outer figure tag, if not present.
|
||||||
// has alt text specified
|
if figure_tag.alt_text.is_none() {
|
||||||
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)
|
||||||
@ -313,18 +414,11 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
|||||||
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()));
|
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()));
|
||||||
return;
|
return;
|
||||||
} else if let Some(table) = elem.to_packed::<TableElem>() {
|
} else if let Some(table) = elem.to_packed::<TableElem>() {
|
||||||
let ctx = TableCtx { table: table.clone(), rows: Vec::new() };
|
push_stack(gc, loc, StackEntryKind::Table(TableCtx::new(table.clone())));
|
||||||
push_stack(gc, loc, StackEntryKind::Table(ctx));
|
|
||||||
return;
|
return;
|
||||||
} 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;
|
||||||
};
|
};
|
||||||
@ -364,6 +458,20 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
|
|||||||
}
|
}
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
StackEntryKind::Outline(ctx) => {
|
||||||
|
let nodes = ctx.build_outline(entry.nodes);
|
||||||
|
TagNode::Group(TagKind::TOC.into(), nodes)
|
||||||
|
}
|
||||||
|
StackEntryKind::OutlineEntry(outline_entry) => {
|
||||||
|
let parent = gc.tags.stack.last_mut().expect("outline");
|
||||||
|
let StackEntryKind::Outline(outline_ctx) = &mut parent.kind else {
|
||||||
|
unreachable!("expected outline")
|
||||||
|
};
|
||||||
|
|
||||||
|
outline_ctx.insert(&mut parent.nodes, outline_entry, entry.nodes);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
StackEntryKind::Table(ctx) => {
|
StackEntryKind::Table(ctx) => {
|
||||||
let summary = ctx.table.summary(StyleChain::default()).map(EcoString::into);
|
let summary = ctx.table.summary(StyleChain::default()).map(EcoString::into);
|
||||||
let nodes = ctx.build_table(entry.nodes);
|
let nodes = ctx.build_table(entry.nodes);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user