PPTX export: bound the retained glyph-to-character maps

* PPTX export: keep only the last four faces' glyph-to-character maps
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-12 06:27:18 -05:00
1 parent 6fff5328eb
commit f87c24a1cd
1 file changed
+31
+31
View File
@@ -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<u32, RunFont>,
@@ -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<char> {
// 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<Vec<u8>, 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'));
}
}
}