fix(render): index line style groups, stop futile glyph-cache eviction, read PDF page sizes once (#2217)

This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-03 22:23:11 -05:00
1 parent c4ed9b2210
commit 64c956f09e
4 files changed
+38 -13

No files matched your search

+3 -2
View File
@@ -340,8 +340,9 @@ pub fn specs() -> Vec<CommandSpec> {
let _fonts = super::file::load_document_fonts(&mut d, path).0;
let cache = designcraft_compose::Cache::new();
let r = designcraft_pdf::export_pdf_with_report(&super::interchange::with_pdf_boxes(&d), &cache, &designcraft_pdf::PdfOptions::default()).map_err(|e| EngineError::Other(e.to_string()))?;
let n = designcraft_render::pdf_page_count(&r.bytes).unwrap_or(0);
let sizes: Vec<(f32, f32)> = (0..n).filter_map(|i| designcraft_render::pdf_page_size(&r.bytes, i)).map(|(w, h)| (w as f32, h as f32)).collect();
// Every page's size from one opening of the chapter's PDF (not one per page).
let sizes: Vec<(f32, f32)> =
designcraft_render::pdf_page_sizes(&r.bytes).unwrap_or_default().into_iter().map(|(w, h)| (w as f32, h as f32)).collect();
parts.push((r.bytes, sizes));
}
let pages: usize = parts.iter().map(|p| p.1.len()).sum();
+1 -1
View File
@@ -50,7 +50,7 @@ pub use decode::{decode_pixmap, decode_pixmap_page, image_mime, image_size, natu
use pdf_raster::PDF_RENDERS;
pub use pdf_raster::{
PdfRasterError, check_pdf_input_size, crop_pixmap, is_pdf, pdf_crop_box, pdf_page_box, pdf_page_count, pdf_page_png, pdf_page_png_checked,
pdf_page_png_options_checked, pdf_page_size, render_pdf_page, shown_box,
pdf_page_png_options_checked, pdf_page_size, pdf_page_sizes, render_pdf_page, shown_box,
};
pub(crate) use types::Frame;
pub use types::{DisplayQuality, FrameStats, MAX_PAGE_PIXELS, MAX_SIDE, Placed, RenderOptions, Rendered, default_threads, effect_outset};
+14
View File
@@ -21,6 +21,20 @@ pub fn pdf_page_size(bytes: &[u8], page: usize) -> Option<(f64, f64)> {
Some((w as f64, h as f64))
}
/// Sizes of every page of a PDF, as [`pdf_page_size`] gives each: the file is opened once.
pub fn pdf_page_sizes(bytes: &[u8]) -> Option<Vec<(f64, f64)>> {
let pdf = craft_pdf::open_slice(bytes, None).ok()?;
Some(
pdf.pages()
.iter()
.map(|p| {
let (w, h) = p.render_dimensions();
(w as f64, h as f64)
})
.collect(),
)
}
/// A placed page could not be rasterized completely for export.
#[derive(Debug, thiserror::Error)]
pub enum PdfRasterError {
+20 -10
View File
@@ -91,6 +91,9 @@ pub(crate) struct GlyphCache {
map: HashMap<(usize, ItemId), Entry>,
elements: usize,
clock: u64,
/// The clock at which eviction last found nothing it may drop (everything is in use by the
/// render then going on): until the next render, looking again would find the same.
spent: Option<u64>,
}
/// Roughly 56 bytes per element: ~110 MB of cached outlines.
@@ -111,7 +114,7 @@ impl GlyphCache {
let g = Arc::new(build(cs, ft));
self.elements += g.elements;
self.map.insert(key, Entry { _story: Arc::downgrade(cs), glyphs: g.clone(), stamp });
if self.elements > MAX_ELEMENTS {
if self.elements > MAX_ELEMENTS && self.spent != Some(self.clock) {
self.evict();
}
g
@@ -123,12 +126,20 @@ impl GlyphCache {
let mut stamps: Vec<(u64, usize, (usize, ItemId))> = self.map.iter().map(|(k, e)| (e.stamp, e.glyphs.elements, *k)).collect();
stamps.sort_unstable_by_key(|s| s.0);
for (stamp, n, k) in stamps {
if self.elements <= MAX_ELEMENTS / 2 || stamp >= self.clock {
break;
if self.elements <= MAX_ELEMENTS / 2 {
return;
}
if stamp >= self.clock {
// The rest belong to this render: nothing more to drop until the next one.
self.spent = Some(self.clock);
return;
}
self.map.remove(&k);
self.elements -= n;
}
if self.elements > MAX_ELEMENTS / 2 {
self.spent = Some(self.clock);
}
}
pub fn clear(&mut self) {
@@ -174,6 +185,8 @@ fn push_bar(bars: &mut Vec<(String, f32, Rect)>, color: &str, tint: f32, r: Rect
fn build_line(db: &FontDb, cs: &ComposedStory, l: &Line, vertical: bool) -> LineGlyphs {
let mut runs: Vec<(u32, BezPath)> = Vec::new();
// Where each style's run is in `runs` (a line can hold many styles).
let mut run_of: HashMap<u32, usize> = HashMap::new();
let mut underlines: Vec<(String, f32, Rect)> = Vec::new();
let mut strikes: Vec<(String, f32, Rect)> = Vec::new();
let mut glyphs = 0;
@@ -207,13 +220,10 @@ fn build_line(db: &FontDb, cs: &ComposedStory, l: &Line, vertical: bool) -> Line
// Upright in vertical text.
a = turn * a;
}
let k = match runs.iter().position(|r| r.0 == g.style) {
Some(k) => k,
None => {
runs.push((g.style, BezPath::new()));
runs.len() - 1
}
};
let k = *run_of.entry(g.style).or_insert_with(|| {
runs.push((g.style, BezPath::new()));
runs.len() - 1
});
let bp = &mut runs[k].1;
for el in outline.elements() {
bp.push(transform_el(a, *el));