From f87c24a1cda25c64ac49b575548e40fca9585065 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Thu, 12 Nov 2026 06:27:18 -0500 Subject: [PATCH] PPTX export: bound the retained glyph-to-character maps * PPTX export: keep only the last four faces' glyph-to-character maps --- crates/pptx/src/fonts.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/pptx/src/fonts.rs b/crates/pptx/src/fonts.rs index 27c976aaf..ed02ff297 100644 --- a/crates/pptx/src/fonts.rs +++ b/crates/pptx/src/fonts.rs @@ -15,6 +15,9 @@ pub struct RunFont { pub italic: bool, } +/// Faces whose glyph-to-character map is kept at once. +const MAX_REVERSE_MAPS: usize = 4; + #[derive(Default)] pub struct Fonts { names: HashMap, @@ -116,6 +119,11 @@ impl Fonts { /// The character a glyph shows (the lowest code point mapping to it). pub fn char_of(&mut self, face: &FontFace, gid: u32) -> Option { + // A face's reverse map can hold over a million entries; keeping one per face a bundle uses + // is unbounded, so only the last few stay (a rebuilt map answers the same). + if !self.reverse.contains_key(&face.id()) && self.reverse.len() >= MAX_REVERSE_MAPS { + self.reverse.clear(); + } let map = self.reverse.entry(face.id()).or_insert_with(|| { let mut m = HashMap::new(); for (c, g) in face.chars() { @@ -216,3 +224,26 @@ pub fn eot(face: &FontFace) -> Result, NoEmbed> { h.extend_from_slice(data); Ok(h) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn only_a_few_reverse_maps_are_kept_and_answers_do_not_change() { + let db = designcraft_fonts::FontDb::global(); + let mut fonts = Fonts::default(); + let mut last = None; + for style in ["Regular", "Bold", "Italic", "Bold Italic", "Semibold", "Light", "Black", "ExtraLight", "Medium", "SemiBold Italic"] { + let face = db.face("Source Serif 4", style); + let gid = face.glyph_for('A'); + assert_eq!(fonts.char_of(&face, gid), Some('A'), "{style}"); + assert!(fonts.reverse.len() <= MAX_REVERSE_MAPS); + last = Some((face, gid)); + } + // The map evicted earlier is rebuilt on demand with the same answer. + if let Some((face, gid)) = last { + assert_eq!(fonts.char_of(&face, gid), Some('A')); + } + } +}