diff --git a/crates/fonts/src/pdf.rs b/crates/fonts/src/pdf.rs index 240b1c4d55..a6c63acf93 100644 --- a/crates/fonts/src/pdf.rs +++ b/crates/fonts/src/pdf.rs @@ -564,6 +564,7 @@ fn number(t: &Tok) -> Option { #[allow(clippy::type_complexity)] fn parse_cmap(data: &[u8]) -> (Vec<(usize, Vec, Vec)>, Vec<(u32, u32, u32)>) { let (mut spaces, mut cids) = (Vec::new(), Vec::new()); + let mut seen = HashSet::new(); let mut toks = Lexer::new(data); while let Some(t) = toks.next() { match t { @@ -571,7 +572,10 @@ fn parse_cmap(data: &[u8]) -> (Vec<(usize, Vec, Vec)>, Vec<(u32, u32, u3 while let Some([lo, hi]) = entry(&mut toks, b"endcodespacerange") { if let (Tok::Hex(Some(lo)), Tok::Hex(Some(hi))) = (lo, hi) && lo.len() == hi.len() + // A code is at most four bytes; a repeated range can never match first, so it only costs every glyph a comparison. + && (1..=4).contains(&lo.len()) && spaces.len() < MAX_CMAP_RANGES + && seen.insert((lo.clone(), hi.clone())) { spaces.push((lo.len(), lo, hi)); } @@ -654,7 +658,7 @@ impl Metrics { m.code_len = match &m.codes { Codes::One => 1, Codes::Two => 2, - Codes::Ranges(r) => r.last().map_or(2, |x| x.0), + Codes::Ranges(r) => r.last().map_or(2, |x| x.0.clamp(1, 4)), }; m.unicode = unicode_map(doc, font, m.composite); if m.std14.is_some() { diff --git a/crates/fonts/src/pdf/tests.rs b/crates/fonts/src/pdf/tests.rs index c2acef191c..19b4781eae 100644 --- a/crates/fonts/src/pdf/tests.rs +++ b/crates/fonts/src/pdf/tests.rs @@ -795,3 +795,13 @@ fn low_codes_resolve_to_the_covering_range() { assert_eq!(covered, [5, 6, 7, 100, 101]); assert_eq!((u.covering(0xFFFF).is_some(), u.covering(0x1_0001).is_some(), u.covering(0x1_0002).is_none()), (true, true, true)); } + +#[test] +fn codespaces_are_deduplicated_and_at_most_four_bytes() { + let cmap = "3 begincodespacerange <00> <7F> <00> <7F> <0000000000> endcodespacerange"; + let (spaces, _) = parse_cmap(cmap.as_bytes()); + assert_eq!(spaces, vec![(1, vec![0x00], vec![0x7F])]); + let many: String = (0..2000).map(|_| "<0000> \n").collect(); + let (spaces, _) = parse_cmap(format!("2000 begincodespacerange\n{many}endcodespacerange").as_bytes()); + assert_eq!(spaces.len(), 1); +}