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