mirror of
https://github.com/typst/typst
synced 2025-07-27 14:27:56 +08:00
fix: avoid empty marked-content sequences
This commit is contained in:
parent
8231439b11
commit
e6341c0fe4
@ -13,7 +13,7 @@ use krilla::{Document, SerializeSettings};
|
||||
use krilla_svg::render_svg_glyph;
|
||||
use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
|
||||
use typst_library::foundations::{NativeElement, Repr};
|
||||
use typst_library::introspection::{self, Location};
|
||||
use typst_library::introspection::{Location, Tag};
|
||||
use typst_library::layout::{
|
||||
Abs, Frame, FrameItem, GroupItem, PagedDocument, Size, Transform,
|
||||
};
|
||||
@ -110,8 +110,6 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
|
||||
let mut surface = page.surface();
|
||||
let mut fc = FrameContext::new(typst_page.frame.size());
|
||||
|
||||
tags::restart_open(gc, &mut surface);
|
||||
|
||||
handle_frame(
|
||||
&mut fc,
|
||||
&typst_page.frame,
|
||||
@ -120,8 +118,6 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
|
||||
gc,
|
||||
)?;
|
||||
|
||||
tags::end_open(gc, &mut surface);
|
||||
|
||||
surface.finish();
|
||||
|
||||
tags::add_annotations(gc, &mut page, fc.link_annotations);
|
||||
@ -286,12 +282,8 @@ pub(crate) fn handle_frame(
|
||||
handle_image(gc, fc, image, *size, surface, *span)?
|
||||
}
|
||||
FrameItem::Link(link, size) => handle_link(fc, gc, link, *size),
|
||||
FrameItem::Tag(introspection::Tag::Start(elem)) => {
|
||||
tags::handle_start(gc, surface, elem)
|
||||
}
|
||||
FrameItem::Tag(introspection::Tag::End(loc, _)) => {
|
||||
tags::handle_end(gc, surface, *loc);
|
||||
}
|
||||
FrameItem::Tag(Tag::Start(elem)) => tags::handle_start(gc, elem),
|
||||
FrameItem::Tag(Tag::End(loc, _)) => tags::handle_end(gc, *loc),
|
||||
}
|
||||
|
||||
fc.pop();
|
||||
@ -306,7 +298,7 @@ pub(crate) fn handle_group(
|
||||
fc: &mut FrameContext,
|
||||
group: &GroupItem,
|
||||
surface: &mut Surface,
|
||||
context: &mut GlobalContext,
|
||||
gc: &mut GlobalContext,
|
||||
) -> SourceResult<()> {
|
||||
fc.push();
|
||||
fc.state_mut().pre_concat(group.transform);
|
||||
@ -322,10 +314,12 @@ pub(crate) fn handle_group(
|
||||
.and_then(|p| p.transform(fc.state().transform.to_krilla()));
|
||||
|
||||
if let Some(clip_path) = &clip_path {
|
||||
let mut handle = tags::start_marked(gc, surface);
|
||||
let surface = handle.surface();
|
||||
surface.push_clip_path(clip_path, &krilla::paint::FillRule::NonZero);
|
||||
}
|
||||
|
||||
handle_frame(fc, &group.frame, None, surface, context)?;
|
||||
handle_frame(fc, &group.frame, None, surface, gc)?;
|
||||
|
||||
if clip_path.is_some() {
|
||||
surface.pop();
|
||||
|
@ -14,6 +14,7 @@ use typst_library::visualize::{
|
||||
use typst_syntax::Span;
|
||||
|
||||
use crate::convert::{FrameContext, GlobalContext};
|
||||
use crate::tags;
|
||||
use crate::util::{SizeExt, TransformExt};
|
||||
|
||||
#[typst_macros::time(name = "handle image")]
|
||||
@ -32,6 +33,8 @@ pub(crate) fn handle_image(
|
||||
|
||||
gc.image_spans.insert(span);
|
||||
|
||||
let mut handle = tags::start_marked(gc, surface);
|
||||
let surface = handle.surface();
|
||||
match image.kind() {
|
||||
ImageKind::Raster(raster) => {
|
||||
let (exif_transform, new_size) = exif_transform(raster, size);
|
||||
|
@ -5,8 +5,8 @@ use typst_library::visualize::{Geometry, Shape};
|
||||
use typst_syntax::Span;
|
||||
|
||||
use crate::convert::{FrameContext, GlobalContext};
|
||||
use crate::paint;
|
||||
use crate::util::{convert_path, AbsExt, TransformExt};
|
||||
use crate::{paint, tags};
|
||||
|
||||
#[typst_macros::time(name = "handle shape")]
|
||||
pub(crate) fn handle_shape(
|
||||
@ -16,6 +16,9 @@ pub(crate) fn handle_shape(
|
||||
gc: &mut GlobalContext,
|
||||
span: Span,
|
||||
) -> SourceResult<()> {
|
||||
let mut handle = tags::start_marked(gc, surface);
|
||||
let surface = handle.surface();
|
||||
|
||||
surface.set_location(span.into_raw().get());
|
||||
surface.push_transform(&fc.state().transform().to_krilla());
|
||||
|
||||
|
@ -45,6 +45,16 @@ pub(crate) enum StackEntryKind {
|
||||
TableCell(Packed<TableCell>),
|
||||
}
|
||||
|
||||
impl StackEntryKind {
|
||||
pub(crate) fn as_standard_mut(&mut self) -> Option<&mut Tag> {
|
||||
if let Self::Standard(v) = self {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TableCtx {
|
||||
table: Packed<TableElem>,
|
||||
rows: Vec<Vec<Option<(Packed<TableCell>, Tag, Vec<TagNode>)>>>,
|
||||
@ -144,10 +154,6 @@ impl Tags {
|
||||
.expect("initialized placeholder node")
|
||||
}
|
||||
|
||||
pub(crate) fn is_root(&self) -> bool {
|
||||
self.stack.is_empty()
|
||||
}
|
||||
|
||||
/// 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>, &mut Vec<TagNode>) {
|
||||
@ -196,25 +202,40 @@ impl Tags {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marked-content may not cross page boundaries: restart tag that was still open
|
||||
/// at the end of the last page.
|
||||
pub(crate) fn restart_open(gc: &mut GlobalContext, surface: &mut Surface) {
|
||||
// TODO: somehow avoid empty marked-content sequences
|
||||
if let Some((loc, kind)) = gc.tags.in_artifact {
|
||||
start_artifact(gc, surface, loc, kind);
|
||||
} else if let Some(entry) = gc.tags.stack.last_mut() {
|
||||
let id = surface.start_tagged(ContentTag::Other);
|
||||
entry.nodes.push(TagNode::Leaf(id));
|
||||
/// Automatically calls [`Surface::end_tagged`] when dropped.
|
||||
pub(crate) struct TagHandle<'a, 'b> {
|
||||
surface: &'b mut Surface<'a>,
|
||||
}
|
||||
|
||||
impl Drop for TagHandle<'_, '_> {
|
||||
fn drop(&mut self) {
|
||||
self.surface.end_tagged();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
impl<'a> TagHandle<'a, '_> {
|
||||
pub(crate) fn surface<'c>(&'c mut self) -> &'c mut Surface<'a> {
|
||||
&mut self.surface
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`TagHandle`] that automatically calls [`Surface::end_tagged`]
|
||||
/// when dropped.
|
||||
pub(crate) fn start_marked<'a, 'b>(
|
||||
gc: &mut GlobalContext,
|
||||
surface: &'b mut Surface<'a>,
|
||||
) -> TagHandle<'a, 'b> {
|
||||
let content = if let Some((_, kind)) = gc.tags.in_artifact {
|
||||
let ty = artifact_type(kind);
|
||||
ContentTag::Artifact(ty)
|
||||
} else {
|
||||
ContentTag::Other
|
||||
};
|
||||
let id = surface.start_tagged(content);
|
||||
gc.tags.push(TagNode::Leaf(id));
|
||||
TagHandle { surface }
|
||||
}
|
||||
|
||||
/// Add all annotations that were found in the page frame.
|
||||
pub(crate) fn add_annotations(
|
||||
gc: &mut GlobalContext,
|
||||
@ -232,11 +253,7 @@ pub(crate) fn add_annotations(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_start(
|
||||
gc: &mut GlobalContext,
|
||||
surface: &mut Surface,
|
||||
elem: &Content,
|
||||
) {
|
||||
pub(crate) fn handle_start(gc: &mut GlobalContext, elem: &Content) {
|
||||
if gc.tags.in_artifact.is_some() {
|
||||
// Don't nest artifacts
|
||||
return;
|
||||
@ -245,9 +262,8 @@ pub(crate) fn handle_start(
|
||||
let loc = elem.location().unwrap();
|
||||
|
||||
if let Some(artifact) = elem.to_packed::<ArtifactElem>() {
|
||||
end_open(gc, surface);
|
||||
let kind = artifact.kind(StyleChain::default());
|
||||
start_artifact(gc, surface, loc, kind);
|
||||
start_artifact(gc, loc, kind);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -279,79 +295,55 @@ pub(crate) fn handle_start(
|
||||
} else if let Some(image) = elem.to_packed::<ImageElem>() {
|
||||
let alt = image.alt(StyleChain::default()).map(|s| s.to_string());
|
||||
|
||||
end_open(gc, surface);
|
||||
let id = surface.start_tagged(ContentTag::Other);
|
||||
let mut node = TagNode::Leaf(id);
|
||||
|
||||
if let Some(StackEntryKind::Standard(parent)) = gc.tags.parent().0 {
|
||||
if parent.kind == TagKind::Figure && parent.alt_text.is_none() {
|
||||
// HACK: set alt text of outer figure tag, if the contained image
|
||||
// has alt text specified
|
||||
parent.alt_text = alt;
|
||||
} else {
|
||||
node = TagNode::Group(TagKind::Figure.with_alt_text(alt), vec![node]);
|
||||
}
|
||||
let figure_tag = (gc.tags.parent().0)
|
||||
.and_then(|parent| parent.as_standard_mut())
|
||||
.filter(|tag| tag.kind == TagKind::Figure && tag.alt_text.is_none());
|
||||
if let Some(figure_tag) = figure_tag {
|
||||
// HACK: set alt text of outer figure tag, if the contained image
|
||||
// has alt text specified
|
||||
figure_tag.alt_text = alt;
|
||||
return;
|
||||
} else {
|
||||
node = TagNode::Group(TagKind::Figure.with_alt_text(alt), vec![node]);
|
||||
TagKind::Figure.with_alt_text(alt)
|
||||
}
|
||||
gc.tags.push(node);
|
||||
|
||||
return;
|
||||
} else if let Some(_) = elem.to_packed::<FigureCaption>() {
|
||||
TagKind::Caption.into()
|
||||
} else if let Some(link) = elem.to_packed::<LinkMarker>() {
|
||||
let link_id = gc.tags.next_link_id();
|
||||
push_stack(gc, surface, loc, StackEntryKind::Link(link_id, link.clone()));
|
||||
push_stack(gc, loc, StackEntryKind::Link(link_id, link.clone()));
|
||||
return;
|
||||
} else if let Some(table) = elem.to_packed::<TableElem>() {
|
||||
let ctx = TableCtx { table: table.clone(), rows: Vec::new() };
|
||||
push_stack(gc, surface, loc, StackEntryKind::Table(ctx));
|
||||
push_stack(gc, loc, StackEntryKind::Table(ctx));
|
||||
return;
|
||||
} else if let Some(cell) = elem.to_packed::<TableCell>() {
|
||||
push_stack(gc, surface, loc, StackEntryKind::TableCell(cell.clone()));
|
||||
push_stack(gc, loc, StackEntryKind::TableCell(cell.clone()));
|
||||
return;
|
||||
} else if let Some(_) = elem.to_packed::<TableHLine>() {
|
||||
end_open(gc, surface);
|
||||
start_artifact(gc, surface, loc, ArtifactKind::Other);
|
||||
start_artifact(gc, loc, ArtifactKind::Other);
|
||||
return;
|
||||
} else if let Some(_) = elem.to_packed::<TableVLine>() {
|
||||
end_open(gc, surface);
|
||||
start_artifact(gc, surface, loc, ArtifactKind::Other);
|
||||
start_artifact(gc, loc, ArtifactKind::Other);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
push_stack(gc, surface, loc, StackEntryKind::Standard(tag));
|
||||
push_stack(gc, loc, StackEntryKind::Standard(tag));
|
||||
}
|
||||
|
||||
fn push_stack(
|
||||
gc: &mut GlobalContext,
|
||||
surface: &mut Surface,
|
||||
loc: Location,
|
||||
kind: StackEntryKind,
|
||||
) {
|
||||
fn push_stack(gc: &mut GlobalContext, loc: Location, kind: StackEntryKind) {
|
||||
if !gc.tags.context_supports(&kind) {
|
||||
// TODO: error or warning?
|
||||
}
|
||||
|
||||
// close previous marked-content and open a nested tag.
|
||||
end_open(gc, surface);
|
||||
let id = surface.start_tagged(krilla::tagging::ContentTag::Other);
|
||||
gc.tags
|
||||
.stack
|
||||
.push(StackEntry { loc, kind, nodes: vec![TagNode::Leaf(id)] });
|
||||
gc.tags.stack.push(StackEntry { loc, kind, nodes: Vec::new() });
|
||||
}
|
||||
|
||||
pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Location) {
|
||||
pub(crate) fn handle_end(gc: &mut GlobalContext, loc: Location) {
|
||||
if let Some((l, _)) = gc.tags.in_artifact {
|
||||
if l == loc {
|
||||
gc.tags.in_artifact = None;
|
||||
surface.end_tagged();
|
||||
if let Some(entry) = gc.tags.stack.last_mut() {
|
||||
let id = surface.start_tagged(ContentTag::Other);
|
||||
entry.nodes.push(TagNode::Leaf(id));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -360,8 +352,6 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc
|
||||
return;
|
||||
};
|
||||
|
||||
surface.end_tagged();
|
||||
|
||||
let node = match entry.kind {
|
||||
StackEntryKind::Standard(tag) => TagNode::Group(tag, entry.nodes),
|
||||
StackEntryKind::Link(_, link) => {
|
||||
@ -387,30 +377,14 @@ pub(crate) fn handle_end(gc: &mut GlobalContext, surface: &mut Surface, loc: Loc
|
||||
|
||||
table_ctx.insert(cell, entry.nodes);
|
||||
|
||||
// TODO: somehow avoid empty marked-content sequences
|
||||
let id = surface.start_tagged(ContentTag::Other);
|
||||
gc.tags.push(TagNode::Leaf(id));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
gc.tags.push(node);
|
||||
if !gc.tags.is_root() {
|
||||
// TODO: somehow avoid empty marked-content sequences
|
||||
let id = surface.start_tagged(ContentTag::Other);
|
||||
gc.tags.push(TagNode::Leaf(id));
|
||||
}
|
||||
}
|
||||
|
||||
fn start_artifact(
|
||||
gc: &mut GlobalContext,
|
||||
surface: &mut Surface,
|
||||
loc: Location,
|
||||
kind: ArtifactKind,
|
||||
) {
|
||||
let ty = artifact_type(kind);
|
||||
let id = surface.start_tagged(ContentTag::Artifact(ty));
|
||||
gc.tags.push(TagNode::Leaf(id));
|
||||
fn start_artifact(gc: &mut GlobalContext, loc: Location, kind: ArtifactKind) {
|
||||
gc.tags.in_artifact = Some((loc, kind));
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,8 @@ use typst_library::visualize::FillRule;
|
||||
use typst_syntax::Span;
|
||||
|
||||
use crate::convert::{FrameContext, GlobalContext};
|
||||
use crate::paint;
|
||||
use crate::util::{display_font, AbsExt, TransformExt};
|
||||
use crate::{paint, tags};
|
||||
|
||||
#[typst_macros::time(name = "handle text")]
|
||||
pub(crate) fn handle_text(
|
||||
@ -23,6 +23,9 @@ pub(crate) fn handle_text(
|
||||
) -> SourceResult<()> {
|
||||
*gc.languages.entry(t.lang).or_insert(0) += t.glyphs.len();
|
||||
|
||||
let mut handle = tags::start_marked(gc, surface);
|
||||
let surface = handle.surface();
|
||||
|
||||
let font = convert_font(gc, t.font.clone())?;
|
||||
let fill = paint::convert_fill(
|
||||
gc,
|
||||
|
Loading…
x
Reference in New Issue
Block a user