diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs index 37909a1f5..f1e598967 100644 --- a/crates/pdf/src/lib.rs +++ b/crates/pdf/src/lib.rs @@ -215,6 +215,8 @@ pub fn parse_page_range(s: &str, count: usize) -> Result> { let bad = || PdfError::BadRange(s.to_string()); let num = |v: &str| v.trim().parse::().map_err(|_| bad()); let mut out = Vec::new(); + // The pages selected so far, for ranges that overlap (`1-9, 2-8, …`): a set, not a search. + let mut selected = vec![false; count]; for part in t.split(',').map(str::trim).filter(|p| !p.is_empty()) { let (a, b) = match part.split_once('-') { Some((a, b)) => { @@ -234,7 +236,8 @@ pub fn parse_page_range(s: &str, count: usize) -> Result> { if p > count { return Err(PdfError::BadPage(p)); } - if !out.contains(&(p - 1)) { + if let Some(new) = selected.get_mut(p - 1).filter(|s| !**s) { + *new = true; out.push(p - 1); } } diff --git a/crates/pdf/src/optional_content.rs b/crates/pdf/src/optional_content.rs index 374c0d1f1..08dbbe0bd 100644 --- a/crates/pdf/src/optional_content.rs +++ b/crates/pdf/src/optional_content.rs @@ -1,6 +1,7 @@ use hayro_syntax::Pdf; use hayro_syntax::object::{Array, Dict, MaybeRef, Name, Object, ObjectIdentifier, Stream}; use hayro_syntax::reader::{Reader, ReaderExt}; +use std::collections::HashSet; use std::sync::Arc; use crate::{PdfError, Result}; @@ -218,10 +219,12 @@ pub(crate) fn prepare(bytes: &Arc>, choice: Option<&[String]>) -> Result } Err(error) => return Err(error), }; - if on.iter().any(|id| off.contains(id)) { + let off_set: HashSet<_> = off.iter().copied().collect(); + if on.iter().any(|id| off_set.contains(id)) { return Err(invalid()); } - let incomplete = on.iter().chain(&off).any(|id| !groups.contains(id)); + let group_set: HashSet<_> = groups.iter().copied().collect(); + let incomplete = on.iter().chain(&off).any(|id| !group_set.contains(id)); groups.extend(on.iter().chain(&off).copied()); groups.sort(); groups.dedup(); diff --git a/crates/pdf/src/pdfx.rs b/crates/pdf/src/pdfx.rs index 6d7a3714f..731a5989d 100644 --- a/crates/pdf/src/pdfx.rs +++ b/crates/pdf/src/pdfx.rs @@ -3,7 +3,7 @@ //! krilla's PDF 1.6 output, and a check of the PDF/X-4 requirements this exporter is responsible //! for. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use hayro_syntax::content::UntypedIter; use hayro_syntax::object::{Array, Dict, Name, Object, ObjectIdentifier, Stream}; @@ -217,6 +217,7 @@ fn device_rgb(file: Option<&hayro_syntax::Pdf>) -> Vec { return Vec::new(); } let mut found: Vec<(String, Vec)> = Vec::new(); + let mut at: HashMap = HashMap::new(); for (i, page) in file.pages().iter().enumerate() { let mut scan = RgbScan::default(); let res = page.resources(); @@ -227,9 +228,12 @@ fn device_rgb(file: Option<&hayro_syntax::Pdf>) -> Vec { scan.content(content, res, 0); } for what in scan.found { - match found.iter_mut().find(|(w, _)| *w == what) { + match at.get(&what).and_then(|k| found.get_mut(*k)) { Some((_, pages)) => pages.push(i + 1), - None => found.push((what, vec![i + 1])), + None => { + at.insert(what.clone(), found.len()); + found.push((what, vec![i + 1])); + } } } } @@ -274,14 +278,16 @@ pub(crate) const MAX_DEPTH: usize = 16; /// What one page uses in device RGB, in the order found. #[derive(Default)] struct RgbScan { + /// What was found, in the order found; `known` is the same as a set. found: Vec, + known: HashSet, /// Forms, images and patterns already looked at on this page. seen: HashSet, } impl RgbScan { fn add(&mut self, what: String) { - if !self.found.contains(&what) { + if self.known.insert(what.clone()) { self.found.push(what); } } diff --git a/crates/pdf/src/pdfx1a.rs b/crates/pdf/src/pdfx1a.rs index 495a389d8..aa8873083 100644 --- a/crates/pdf/src/pdfx1a.rs +++ b/crates/pdf/src/pdfx1a.rs @@ -520,7 +520,9 @@ fn disallowed(cs: &Object<'_>, res: &Resources<'_>, depth: usize) -> Option, + known: HashSet, font_issues: Vec, seen: HashSet, fonts: HashSet, @@ -528,7 +530,7 @@ struct Scan { impl Scan { fn add(&mut self, what: String) { - if !self.found.contains(&what) { + if self.known.insert(what.clone()) { self.found.push(what); } } diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs index 262e19c8e..e582b4cde 100644 --- a/crates/pdf/src/tests.rs +++ b/crates/pdf/src/tests.rs @@ -178,6 +178,12 @@ fn spreads_and_ranges() { assert_eq!(parse_page_range("1-2, 4", 4).unwrap(), vec![0, 1, 3]); assert_eq!(parse_page_range("3-", 4).unwrap(), vec![2, 3]); assert_eq!(parse_page_range("", 2).unwrap(), vec![0, 1]); + // Ranges that overlap many times over: each page is selected once, in the order first named. + let overlapping = std::iter::repeat_n("1-20000, 5-19000", 500).collect::>().join(","); + let t = std::time::Instant::now(); + assert_eq!(parse_page_range(&overlapping, 20000).unwrap().len(), 20000); + assert!(t.elapsed() < std::time::Duration::from_secs(5), "{:?}", t.elapsed()); + assert_eq!(parse_page_range("3, 1-3", 4).unwrap(), vec![2, 0, 1]); assert!(parse_page_range("5", 4).is_err()); assert!(parse_page_range("x", 4).is_err()); let bytes = export_pdf(&d, &Cache::new(), &PdfOptions { pages: Some(vec![1, 2]), ..Default::default() }).unwrap();