mirror of
https://github.com/typst/typst
synced 2025-07-27 14:27:56 +08:00
fix: handle some edge cases instead of panicking
This commit is contained in:
parent
377dc87325
commit
0bc39338a1
@ -293,7 +293,7 @@ pub(crate) fn handle_frame(
|
|||||||
handle_image(gc, fc, image, *size, surface, *span)?
|
handle_image(gc, fc, image, *size, surface, *span)?
|
||||||
}
|
}
|
||||||
FrameItem::Link(dest, size) => handle_link(fc, gc, dest, *size),
|
FrameItem::Link(dest, size) => handle_link(fc, gc, dest, *size),
|
||||||
FrameItem::Tag(Tag::Start(elem)) => tags::handle_start(gc, elem),
|
FrameItem::Tag(Tag::Start(elem)) => tags::handle_start(gc, elem)?,
|
||||||
FrameItem::Tag(Tag::End(loc, _)) => tags::handle_end(gc, *loc),
|
FrameItem::Tag(Tag::End(loc, _)) => tags::handle_end(gc, *loc),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use typst_library::layout::{Abs, Point, Position, Size};
|
|||||||
use typst_library::model::Destination;
|
use typst_library::model::Destination;
|
||||||
|
|
||||||
use crate::convert::{FrameContext, GlobalContext};
|
use crate::convert::{FrameContext, GlobalContext};
|
||||||
use crate::tags::{self, Placeholder, StackEntryKind, TagNode};
|
use crate::tags::{self, Placeholder, TagNode};
|
||||||
use crate::util::{AbsExt, PointExt};
|
use crate::util::{AbsExt, PointExt};
|
||||||
|
|
||||||
pub(crate) struct LinkAnnotation {
|
pub(crate) struct LinkAnnotation {
|
||||||
@ -49,8 +49,7 @@ pub(crate) fn handle_link(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let entry = gc.tags.stack.last_mut().expect("a link parent");
|
let Some((link_id, link)) = gc.tags.find_parent_link() else {
|
||||||
let StackEntryKind::Link(link_id, ref link) = entry.kind else {
|
|
||||||
unreachable!("expected a link parent")
|
unreachable!("expected a link parent")
|
||||||
};
|
};
|
||||||
let alt = link.alt.as_ref().map(EcoString::to_string);
|
let alt = link.alt.as_ref().map(EcoString::to_string);
|
||||||
|
@ -2,12 +2,14 @@ use std::cell::OnceCell;
|
|||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
use krilla::configure::Validator;
|
||||||
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, Tag, TagBuilder, TagGroup,
|
ArtifactType, ContentTag, Identifier, Node, SpanTag, TableDataCell, Tag, TagBuilder,
|
||||||
TagKind, TagTree,
|
TagGroup, TagKind, TagTree,
|
||||||
};
|
};
|
||||||
|
use typst_library::diag::SourceResult;
|
||||||
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::layout::RepeatElem;
|
||||||
@ -26,21 +28,21 @@ use crate::tags::table::TableCtx;
|
|||||||
mod outline;
|
mod outline;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) -> SourceResult<()> {
|
||||||
if gc.tags.in_artifact.is_some() {
|
if gc.tags.in_artifact.is_some() {
|
||||||
// Don't nest artifacts
|
// Don't nest artifacts
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let loc = elem.location().unwrap();
|
let loc = elem.location().expect("elem to be locatable");
|
||||||
|
|
||||||
if let Some(artifact) = elem.to_packed::<ArtifactElem>() {
|
if let Some(artifact) = elem.to_packed::<ArtifactElem>() {
|
||||||
let kind = artifact.kind(StyleChain::default());
|
let kind = artifact.kind(StyleChain::default());
|
||||||
start_artifact(gc, loc, kind);
|
start_artifact(gc, loc, kind);
|
||||||
return;
|
return Ok(());
|
||||||
} else if let Some(_) = elem.to_packed::<RepeatElem>() {
|
} else if let Some(_) = elem.to_packed::<RepeatElem>() {
|
||||||
start_artifact(gc, loc, ArtifactKind::Other);
|
start_artifact(gc, loc, ArtifactKind::Other);
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let tag: Tag = if let Some(pdf_tag) = elem.to_packed::<PdfTagElem>() {
|
let tag: Tag = if let Some(pdf_tag) = elem.to_packed::<PdfTagElem>() {
|
||||||
@ -54,11 +56,11 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
|||||||
let name = heading.body.plain_text().to_string();
|
let name = heading.body.plain_text().to_string();
|
||||||
TagKind::Hn(level, Some(name)).into()
|
TagKind::Hn(level, Some(name)).into()
|
||||||
} else if let Some(_) = elem.to_packed::<OutlineBody>() {
|
} else if let Some(_) = elem.to_packed::<OutlineBody>() {
|
||||||
push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()));
|
push_stack(gc, loc, StackEntryKind::Outline(OutlineCtx::new()))?;
|
||||||
return;
|
return Ok(());
|
||||||
} else if let Some(entry) = elem.to_packed::<OutlineEntry>() {
|
} else if let Some(entry) = elem.to_packed::<OutlineEntry>() {
|
||||||
push_stack(gc, loc, StackEntryKind::OutlineEntry(entry.clone()));
|
push_stack(gc, loc, StackEntryKind::OutlineEntry(entry.clone()))?;
|
||||||
return;
|
return Ok(());
|
||||||
} 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)
|
||||||
@ -73,7 +75,7 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
|||||||
if figure_tag.alt_text.is_none() {
|
if figure_tag.alt_text.is_none() {
|
||||||
figure_tag.alt_text = alt;
|
figure_tag.alt_text = alt;
|
||||||
}
|
}
|
||||||
return;
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
TagKind::Figure.with_alt_text(alt)
|
TagKind::Figure.with_alt_text(alt)
|
||||||
}
|
}
|
||||||
@ -82,19 +84,16 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
|||||||
} else if let Some(table) = elem.to_packed::<TableElem>() {
|
} else if let Some(table) = elem.to_packed::<TableElem>() {
|
||||||
let table_id = gc.tags.next_table_id();
|
let table_id = gc.tags.next_table_id();
|
||||||
let ctx = TableCtx::new(table_id, table.clone());
|
let ctx = TableCtx::new(table_id, table.clone());
|
||||||
push_stack(gc, loc, StackEntryKind::Table(ctx));
|
push_stack(gc, loc, StackEntryKind::Table(ctx))?;
|
||||||
return;
|
return Ok(());
|
||||||
} else if let Some(cell) = elem.to_packed::<TableCell>() {
|
} else if let Some(cell) = elem.to_packed::<TableCell>() {
|
||||||
let parent = gc.tags.stack.last_mut().expect("table");
|
let table_ctx = gc.tags.parent_table();
|
||||||
let StackEntryKind::Table(table_ctx) = &mut parent.kind else {
|
|
||||||
unreachable!("expected table")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only repeated table headers and footer cells are layed out multiple
|
// Only repeated table headers and footer cells are layed out multiple
|
||||||
// times. Mark duplicate headers as artifacts, since they have no
|
// times. Mark duplicate headers as artifacts, since they have no
|
||||||
// semantic meaning in the tag tree, which doesn't use page breaks for
|
// semantic meaning in the tag tree, which doesn't use page breaks for
|
||||||
// it's semantic structure.
|
// it's semantic structure.
|
||||||
if table_ctx.contains(cell) {
|
if table_ctx.is_some_and(|ctx| ctx.contains(cell)) {
|
||||||
// TODO: currently the first layouted cell is picked to be part of
|
// TODO: currently the first layouted cell is picked to be part of
|
||||||
// the tag tree, for repeating footers this will be the cell on the
|
// the tag tree, for repeating footers this will be the cell on the
|
||||||
// first page. Maybe it should be the cell on the last page, but that
|
// first page. Maybe it should be the cell on the last page, but that
|
||||||
@ -102,26 +101,38 @@ pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
|||||||
// on the frames to figure out if there are other footers following.
|
// on the frames to figure out if there are other footers following.
|
||||||
start_artifact(gc, loc, ArtifactKind::Other);
|
start_artifact(gc, loc, ArtifactKind::Other);
|
||||||
} else {
|
} else {
|
||||||
push_stack(gc, loc, StackEntryKind::TableCell(cell.clone()));
|
push_stack(gc, loc, StackEntryKind::TableCell(cell.clone()))?;
|
||||||
}
|
}
|
||||||
return;
|
return Ok(());
|
||||||
} else if let Some(link) = elem.to_packed::<LinkMarker>() {
|
} else if let Some(link) = elem.to_packed::<LinkMarker>() {
|
||||||
let link_id = gc.tags.next_link_id();
|
let link_id = gc.tags.next_link_id();
|
||||||
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()));
|
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()))?;
|
||||||
return;
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
push_stack(gc, loc, StackEntryKind::Standard(tag));
|
push_stack(gc, loc, StackEntryKind::Standard(tag))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_stack(gc: &mut GlobalContext, loc: Location, kind: StackEntryKind) {
|
fn push_stack(
|
||||||
|
gc: &mut GlobalContext,
|
||||||
|
loc: Location,
|
||||||
|
kind: StackEntryKind,
|
||||||
|
) -> SourceResult<()> {
|
||||||
if !gc.tags.context_supports(&kind) {
|
if !gc.tags.context_supports(&kind) {
|
||||||
// TODO: error or warning?
|
if gc.options.standards.config.validator() == Validator::UA1 {
|
||||||
|
// TODO: error
|
||||||
|
} else {
|
||||||
|
// TODO: warning
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.tags.stack.push(StackEntry { loc, kind, nodes: Vec::new() });
|
gc.tags.stack.push(StackEntry { loc, kind, nodes: Vec::new() });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
|
pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
|
||||||
@ -143,13 +154,20 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
|
|||||||
TagNode::Group(TagKind::TOC.into(), nodes)
|
TagNode::Group(TagKind::TOC.into(), nodes)
|
||||||
}
|
}
|
||||||
StackEntryKind::OutlineEntry(outline_entry) => {
|
StackEntryKind::OutlineEntry(outline_entry) => {
|
||||||
let parent = gc.tags.stack.last_mut().expect("outline");
|
let parent = gc.tags.stack.last_mut().and_then(|parent| {
|
||||||
let StackEntryKind::Outline(outline_ctx) = &mut parent.kind else {
|
let ctx = parent.kind.as_outline_mut()?;
|
||||||
unreachable!("expected outline")
|
Some((&mut parent.nodes, ctx))
|
||||||
|
});
|
||||||
|
let Some((parent_nodes, outline_ctx)) = parent else {
|
||||||
|
// PDF/UA compliance of the structure hierarchy is checked
|
||||||
|
// elsewhere. While this doesn't make a lot of sense, just
|
||||||
|
// avoid crashing here.
|
||||||
|
let tag = TagKind::TOCI.into();
|
||||||
|
gc.tags.push(TagNode::Group(tag, entry.nodes));
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
outline_ctx.insert(&mut parent.nodes, outline_entry, entry.nodes);
|
outline_ctx.insert(parent_nodes, outline_entry, entry.nodes);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StackEntryKind::Table(ctx) => {
|
StackEntryKind::Table(ctx) => {
|
||||||
@ -158,13 +176,16 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
|
|||||||
TagNode::Group(TagKind::Table(summary).into(), nodes)
|
TagNode::Group(TagKind::Table(summary).into(), nodes)
|
||||||
}
|
}
|
||||||
StackEntryKind::TableCell(cell) => {
|
StackEntryKind::TableCell(cell) => {
|
||||||
let parent = gc.tags.stack.last_mut().expect("table");
|
let Some(table_ctx) = gc.tags.parent_table() else {
|
||||||
let StackEntryKind::Table(table_ctx) = &mut parent.kind else {
|
// PDF/UA compliance of the structure hierarchy is checked
|
||||||
unreachable!("expected table")
|
// elsewhere. While this doesn't make a lot of sense, just
|
||||||
|
// avoid crashing here.
|
||||||
|
let tag = TagKind::TD(TableDataCell::new()).into();
|
||||||
|
gc.tags.push(TagNode::Group(tag, entry.nodes));
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
table_ctx.insert(cell, entry.nodes);
|
table_ctx.insert(cell, entry.nodes);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StackEntryKind::Link(_, link) => {
|
StackEntryKind::Link(_, link) => {
|
||||||
@ -248,12 +269,18 @@ impl Tags {
|
|||||||
.expect("initialized placeholder node")
|
.expect("initialized placeholder node")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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`.
|
|
||||||
pub(crate) fn parent(&mut self) -> Option<&mut StackEntryKind> {
|
pub(crate) fn parent(&mut self) -> Option<&mut StackEntryKind> {
|
||||||
self.stack.last_mut().map(|e| &mut e.kind)
|
self.stack.last_mut().map(|e| &mut e.kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parent_table(&mut self) -> Option<&mut TableCtx> {
|
||||||
|
self.parent()?.as_table_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_parent_link(&self) -> Option<(LinkId, &Packed<LinkMarker>)> {
|
||||||
|
self.stack.iter().rev().find_map(|entry| entry.kind.as_link())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn push(&mut self, node: TagNode) {
|
pub(crate) fn push(&mut self, node: TagNode) {
|
||||||
if let Some(entry) = self.stack.last_mut() {
|
if let Some(entry) = self.stack.last_mut() {
|
||||||
entry.nodes.push(node);
|
entry.nodes.push(node);
|
||||||
@ -330,6 +357,30 @@ impl StackEntryKind {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_outline_mut(&mut self) -> Option<&mut OutlineCtx> {
|
||||||
|
if let Self::Outline(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_table_mut(&mut self) -> Option<&mut TableCtx> {
|
||||||
|
if let Self::Table(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_link(&self) -> Option<(LinkId, &Packed<LinkMarker>)> {
|
||||||
|
if let Self::Link(id, link) = self {
|
||||||
|
Some((*id, link))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -159,8 +159,8 @@ impl TableCtx {
|
|||||||
.filter_map(|cell| {
|
.filter_map(|cell| {
|
||||||
let cell = cell.into_cell()?;
|
let cell = cell.into_cell()?;
|
||||||
let span = TableCellSpan {
|
let span = TableCellSpan {
|
||||||
rows: cell.rowspan.try_into().unwrap(),
|
rows: cell.rowspan.try_into().unwrap_or(NonZeroU32::MAX),
|
||||||
cols: cell.colspan.try_into().unwrap(),
|
cols: cell.colspan.try_into().unwrap_or(NonZeroU32::MAX),
|
||||||
};
|
};
|
||||||
let tag = match cell.unwrap_kind() {
|
let tag = match cell.unwrap_kind() {
|
||||||
TableCellKind::Header(_, scope) => {
|
TableCellKind::Header(_, scope) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user