From 898a08b0ce1ec019a392c5336dfe00847a76d085 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 23:00:28 -0500 Subject: [PATCH] perf(pdf): collect each text style's outlines and decorations in one pass (#1965) * perf(pdf): collect each text style's outlines and decorations in one pass * address review: build each style's paths inside the style loop --- crates/pdf/src/text.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/pdf/src/text.rs b/crates/pdf/src/text.rs index abbb65b34..feb1f1a07 100644 --- a/crates/pdf/src/text.rs +++ b/crates/pdf/src/text.rs @@ -1,7 +1,7 @@ //! Text: real, selectable text in embedded (subset) fonts, positioned glyph by glyph from the //! `figcraft_text` layout the renderer uses, with outline fallbacks. -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::ops::Range; use std::sync::Arc; @@ -56,14 +56,24 @@ pub(crate) fn draw_text(ex: &mut Exporter, s: &mut Surface, n: &Node, xf: Affine let styles: BTreeSet = layout.glyphs.iter().map(|g| g.style).chain(layout.decorations.iter().map(|d| d.style)).collect(); // Every run's fills, then the inner shadows over them, then the strokes on top. let mut stroked = Vec::new(); + // Glyphs and decorations are grouped by style in one pass; each style's paths are built (and + // dropped) inside its own iteration, so only one style's paths are held at a time. + let mut decoration_of: BTreeMap> = BTreeMap::new(); + for (i, d) in layout.decorations.iter().enumerate() { + decoration_of.entry(d.style).or_default().push(i); + } + let mut glyph_of: BTreeMap> = BTreeMap::new(); + for (i, g) in layout.glyphs.iter().enumerate() { + glyph_of.entry(g.style).or_default().push(i); + } for k in styles { let fills = layout.styles.get(k).and_then(|st| st.fills.as_ref()).unwrap_or(&n.fills); let mut decorations = BezPath::new(); - for d in layout.decorations.iter().filter(|d| d.style == k) { + for d in decoration_of.remove(&k).unwrap_or_default().into_iter().filter_map(|i| layout.decorations.get(i)) { decorations.extend(d.path()); } let mut outlines = BezPath::new(); - for g in layout.glyphs.iter().filter(|g| g.style == k) { + for g in glyph_of.remove(&k).unwrap_or_default().into_iter().filter_map(|i| layout.glyphs.get(i)) { outlines.extend(g.path()); } outlines.extend(decorations.iter());