perf: optimize bbox computation

This commit is contained in:
Tobias Schmitz 2025-07-26 14:49:41 +02:00
parent 35f44f79de
commit 6d0c4c620d
No known key found for this signature in database
2 changed files with 56 additions and 24 deletions

View File

@ -42,6 +42,7 @@ impl TextItem {
} }
/// The bounding box of the text run. /// The bounding box of the text run.
#[comemo::memoize]
pub fn bbox(&self) -> Rect { pub fn bbox(&self) -> Rect {
let mut min = Point::splat(Abs::inf()); let mut min = Point::splat(Abs::inf());
let mut max = Point::splat(-Abs::inf()); let mut max = Point::splat(-Abs::inf());

View File

@ -295,6 +295,9 @@ pub(crate) fn handle_end(
// Assign a new link id, so a new link annotation will be created. // Assign a new link id, so a new link annotation will be created.
*id = gc.tags.next_link_id(); *id = gc.tags.next_link_id();
} }
if let Some(bbox) = kind.bbox_mut() {
bbox.reset();
}
broken_entries.push(StackEntry { broken_entries.push(StackEntry {
loc: entry.loc, loc: entry.loc,
@ -454,8 +457,8 @@ pub(crate) fn update_bbox(
fc: &FrameContext, fc: &FrameContext,
compute_bbox: impl FnOnce() -> Rect, compute_bbox: impl FnOnce() -> Rect,
) { ) {
if gc.options.standards.config.validator() == Validator::UA1 if let Some(bbox) = gc.tags.stack.find_parent_bbox()
&& let Some(bbox) = gc.tags.stack.find_parent_bbox() && gc.options.standards.config.validator() == Validator::UA1
{ {
bbox.expand_frame(fc, compute_bbox()); bbox.expand_frame(fc, compute_bbox());
} }
@ -485,7 +488,7 @@ pub(crate) struct Tags {
impl Tags { impl Tags {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
stack: TagStack(Vec::new()), stack: TagStack::new(),
placeholders: Placeholders(Vec::new()), placeholders: Placeholders(Vec::new()),
footnotes: HashMap::new(), footnotes: HashMap::new(),
in_artifact: None, in_artifact: None,
@ -557,47 +560,65 @@ impl Tags {
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct TagStack(Vec<StackEntry>); pub(crate) struct TagStack {
items: Vec<StackEntry>,
/// The index of the topmost stack entry that has a bbox.
bbox_idx: Option<usize>,
}
impl<I: SliceIndex<[StackEntry]>> std::ops::Index<I> for TagStack { impl<I: SliceIndex<[StackEntry]>> std::ops::Index<I> for TagStack {
type Output = I::Output; type Output = I::Output;
#[inline] #[inline]
fn index(&self, index: I) -> &Self::Output { fn index(&self, index: I) -> &Self::Output {
std::ops::Index::index(&self.0, index) std::ops::Index::index(&self.items, index)
} }
} }
impl<I: SliceIndex<[StackEntry]>> std::ops::IndexMut<I> for TagStack { impl<I: SliceIndex<[StackEntry]>> std::ops::IndexMut<I> for TagStack {
#[inline] #[inline]
fn index_mut(&mut self, index: I) -> &mut Self::Output { fn index_mut(&mut self, index: I) -> &mut Self::Output {
std::ops::IndexMut::index_mut(&mut self.0, index) std::ops::IndexMut::index_mut(&mut self.items, index)
} }
} }
impl TagStack { impl TagStack {
pub(crate) fn new() -> Self {
Self { items: Vec::new(), bbox_idx: None }
}
pub(crate) fn len(&self) -> usize { pub(crate) fn len(&self) -> usize {
self.0.len() self.items.len()
} }
pub(crate) fn last(&self) -> Option<&StackEntry> { pub(crate) fn last(&self) -> Option<&StackEntry> {
self.0.last() self.items.last()
} }
pub(crate) fn last_mut(&mut self) -> Option<&mut StackEntry> { pub(crate) fn last_mut(&mut self) -> Option<&mut StackEntry> {
self.0.last_mut() self.items.last_mut()
} }
pub(crate) fn iter(&self) -> std::slice::Iter<StackEntry> { pub(crate) fn iter(&self) -> std::slice::Iter<StackEntry> {
self.0.iter() self.items.iter()
} }
pub(crate) fn push(&mut self, entry: StackEntry) { pub(crate) fn push(&mut self, entry: StackEntry) {
self.0.push(entry); if entry.kind.bbox().is_some() {
self.bbox_idx = Some(self.len());
}
self.items.push(entry);
} }
pub(crate) fn extend(&mut self, iter: impl IntoIterator<Item = StackEntry>) { pub(crate) fn extend(&mut self, iter: impl IntoIterator<Item = StackEntry>) {
self.0.extend(iter); let start = self.len();
self.items.extend(iter);
let last_bbox_offset = self.items[start..]
.iter()
.rposition(|entry| entry.kind.bbox().is_some());
if let Some(offset) = last_bbox_offset {
self.bbox_idx = Some(start + offset);
}
} }
/// Remove the last stack entry if the predicate returns true. /// Remove the last stack entry if the predicate returns true.
@ -606,24 +627,30 @@ impl TagStack {
&mut self, &mut self,
mut predicate: impl FnMut(&mut StackEntry) -> bool, mut predicate: impl FnMut(&mut StackEntry) -> bool,
) -> Option<StackEntry> { ) -> Option<StackEntry> {
let last = self.0.last_mut()?; let last = self.items.last_mut()?;
if predicate(last) { self.pop() } else { None } if predicate(last) { self.pop() } else { None }
} }
/// Remove the last stack entry. /// Remove the last stack entry.
/// This takes care of updating the parent bboxes. /// This takes care of updating the parent bboxes.
pub(crate) fn pop(&mut self) -> Option<StackEntry> { pub(crate) fn pop(&mut self) -> Option<StackEntry> {
let entry = self.0.pop()?; let removed = self.items.pop()?;
if let Some((page_idx, rect)) = entry.kind.bbox().and_then(|b| b.rect)
&& let Some(bbox) = self.find_parent_bbox() let Some(inner_bbox) = removed.kind.bbox() else { return Some(removed) };
{
bbox.expand_page(page_idx, rect); self.bbox_idx = self.items.iter_mut().enumerate().rev().find_map(|(i, entry)| {
let outer_bbox = entry.kind.bbox_mut()?;
if let Some((page_idx, rect)) = inner_bbox.rect {
outer_bbox.expand_page(page_idx, rect);
} }
Some(entry) Some(i)
});
Some(removed)
} }
pub(crate) fn parent(&mut self) -> Option<&mut StackEntryKind> { pub(crate) fn parent(&mut self) -> Option<&mut StackEntryKind> {
self.0.last_mut().map(|e| &mut e.kind) self.items.last_mut().map(|e| &mut e.kind)
} }
pub(crate) fn parent_table(&mut self) -> Option<&mut TableCtx> { pub(crate) fn parent_table(&mut self) -> Option<&mut TableCtx> {
@ -641,7 +668,7 @@ impl TagStack {
pub(crate) fn parent_outline( pub(crate) fn parent_outline(
&mut self, &mut self,
) -> Option<(&mut OutlineCtx, &mut Vec<TagNode>)> { ) -> Option<(&mut OutlineCtx, &mut Vec<TagNode>)> {
self.0.last_mut().and_then(|e| { self.items.last_mut().and_then(|e| {
let ctx = e.kind.as_outline_mut()?; let ctx = e.kind.as_outline_mut()?;
Some((ctx, &mut e.nodes)) Some((ctx, &mut e.nodes))
}) })
@ -650,7 +677,7 @@ impl TagStack {
pub(crate) fn find_parent_link( pub(crate) fn find_parent_link(
&mut self, &mut self,
) -> Option<(LinkId, &Packed<LinkMarker>, &mut Vec<TagNode>)> { ) -> Option<(LinkId, &Packed<LinkMarker>, &mut Vec<TagNode>)> {
self.0.iter_mut().rev().find_map(|e| { self.items.iter_mut().rev().find_map(|e| {
let (link_id, link) = e.kind.as_link()?; let (link_id, link) = e.kind.as_link()?;
Some((link_id, link, &mut e.nodes)) Some((link_id, link, &mut e.nodes))
}) })
@ -658,7 +685,7 @@ impl TagStack {
/// Finds the first parent that has a bounding box. /// Finds the first parent that has a bounding box.
pub(crate) fn find_parent_bbox(&mut self) -> Option<&mut BBoxCtx> { pub(crate) fn find_parent_bbox(&mut self) -> Option<&mut BBoxCtx> {
self.0.iter_mut().rev().find_map(|e| e.kind.bbox_mut()) self.items[self.bbox_idx?].kind.bbox_mut()
} }
} }
@ -836,6 +863,10 @@ impl BBoxCtx {
Self { rect: None, multi_page: false } Self { rect: None, multi_page: false }
} }
pub(crate) fn reset(&mut self) {
*self = Self::new();
}
/// Expand the bounding box with a `rect` relative to the current frame /// Expand the bounding box with a `rect` relative to the current frame
/// context transform. /// context transform.
pub(crate) fn expand_frame(&mut self, fc: &FrameContext, rect: Rect) { pub(crate) fn expand_frame(&mut self, fc: &FrameContext, rect: Rect) {