Skip to content

Commit bcae0b1

Browse files
authored
Merge pull request #244
pdf-filter: parse decode parameters in one pass
2 parents 40af042 + 3a75dd1 commit bcae0b1

1 file changed

Lines changed: 126 additions & 149 deletions

File tree

crates/pdf-filter/src/filter.rs

Lines changed: 126 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -112,27 +112,6 @@ impl fmt::Display for Filter {
112112
}
113113
}
114114

115-
/// Per-filter decoded parameters extracted from `/DecodeParms`.
116-
///
117-
/// Each variant holds the strongly-typed parameters for one filter in the
118-
/// chain. Variants are added as new filters gain parameter support.
119-
#[derive(Debug, Clone)]
120-
pub(crate) enum DecodeParms {
121-
/// No parameters needed or provided for this filter.
122-
None,
123-
/// Parameters for the `CCITTFaxDecode` filter.
124-
CcittFax(CCITTFaxParams),
125-
/// Parameters for `LZWDecode`: EarlyChange flag + optional predictor.
126-
Lzw {
127-
early_change: bool,
128-
predictor: PredictorParams,
129-
},
130-
/// Predictor parameters for `FlateDecode`.
131-
Flate { predictor: PredictorParams },
132-
/// JBIG2 globals stream data, if present.
133-
Jbig2 { globals: Option<Vec<u8>> },
134-
}
135-
136115
/// Methods for parsing the `/Filter` entry from a PDF dictionary.
137116
impl Filter {
138117
const KEY: &'static str = "Filter";
@@ -285,32 +264,58 @@ pub fn decode_data_with_resolver(
285264
return Ok(data);
286265
};
287266

288-
let decode_params = parse_decode_params(dictionary, filters, objects)?;
267+
let decode_parms = dictionary
268+
.get("DecodeParms")
269+
.map(|entry| objects.resolve_object(entry))
270+
.transpose()?;
271+
272+
for (index, filter) in filters.iter().enumerate() {
273+
let param_dict = match decode_parms {
274+
None => None,
275+
Some(ObjectVariant::Dictionary(dictionary)) => Some(dictionary.as_ref()),
276+
Some(ObjectVariant::Array(array)) => {
277+
array.as_slice().optional_dictionary(index, objects)?
278+
}
279+
Some(other) => {
280+
return Err(FilterError::from(
281+
pdf_object::error::ObjectError::TypeMismatch(
282+
"Dictionary or Array",
283+
other.name(),
284+
),
285+
));
286+
}
287+
};
289288

290-
for (filter, params) in filters.iter().zip(decode_params.iter()) {
291289
match filter {
292290
Filter::FlateDecode => {
293291
let decoded = Filter::decode_flate(data.as_slice())?;
294-
let decoded = match params {
295-
DecodeParms::Flate { predictor } if !predictor.is_none() => {
296-
crate::predictor::apply_predictor(&decoded, predictor)?
297-
}
298-
_ => decoded,
292+
let predictor = match param_dict {
293+
Some(dictionary) => PredictorParams::from_dictionary(dictionary, objects)?,
294+
None => PredictorParams::default(),
295+
};
296+
let decoded = if predictor.is_none() {
297+
decoded
298+
} else {
299+
crate::predictor::apply_predictor(&decoded, &predictor)?
299300
};
300301
data = Arc::new(decoded);
301302
}
302303
Filter::LZWDecode => {
303-
let (early_change, predictor) = match params {
304-
DecodeParms::Lzw {
305-
early_change,
306-
predictor,
307-
} => (*early_change, Some(predictor)),
308-
_ => (true, None),
304+
let (early_change, predictor) = match param_dict {
305+
Some(dictionary) => (
306+
dictionary
307+
.optional_number("EarlyChange", objects)?
308+
.unwrap_or(1)
309+
!= 0,
310+
PredictorParams::from_dictionary(dictionary, objects)?,
311+
),
312+
None => (true, PredictorParams::default()),
309313
};
310314
let decoded = crate::lzw::decode(data.as_slice(), early_change)?;
311-
let decoded = match predictor {
312-
Some(p) if !p.is_none() => crate::predictor::apply_predictor(&decoded, p)?,
313-
_ => decoded,
315+
let decoded = if predictor.is_none() {
316+
decoded
317+
} else {
318+
crate::predictor::apply_predictor(&decoded, &predictor)?
314319
};
315320
data = Arc::new(decoded);
316321
}
@@ -336,19 +341,20 @@ pub fn decode_data_with_resolver(
336341
}
337342
Filter::JBIG2Decode => {
338343
let (width, height) = resolve_jbig2_dimensions(dictionary, objects)?;
339-
let globals = match params {
340-
DecodeParms::Jbig2 { globals } => globals.as_deref(),
341-
_ => None,
344+
let globals = match param_dict {
345+
Some(dictionary) => resolve_jbig2_globals(dictionary, objects)?,
346+
None => None,
342347
};
343-
let decoded = pdf_jbig2::decode(data.as_slice(), width, height, globals)?;
348+
let decoded =
349+
pdf_jbig2::decode(data.as_slice(), width, height, globals.as_deref())?;
344350
data = Arc::new(decoded);
345351
}
346352
Filter::CCITTFaxDecode => {
347-
let ccitt_params = match params {
348-
DecodeParms::CcittFax(p) => p,
349-
_ => &CCITTFaxParams::DEFAULT,
353+
let ccitt_params = match param_dict {
354+
Some(dictionary) => CCITTFaxParams::from_dictionary(dictionary, objects)?,
355+
None => CCITTFaxParams::default(),
350356
};
351-
let decoded = pdf_ccitt::decode(data.as_slice(), ccitt_params)?;
357+
let decoded = pdf_ccitt::decode(data.as_slice(), &ccitt_params)?;
352358
data = Arc::new(decoded);
353359
}
354360
Filter::Unsupported(name) => {
@@ -383,110 +389,6 @@ pub fn decode(stream: &StreamObject) -> Result<Arc<Vec<u8>>, FilterError> {
383389
decode_with_resolver(stream, &objects)
384390
}
385391

386-
/// Parses the `/DecodeParms` entry from a stream dictionary into a
387-
/// [`Vec<DecodeParms>`] aligned 1-to-1 with `filters`.
388-
///
389-
/// Per PDF spec §7.3.8.2, `/DecodeParms` is either a single dictionary (when
390-
/// there is one filter) or an array of dictionaries (one per filter).
391-
fn parse_decode_params(
392-
dict: &Dictionary,
393-
filters: &[Filter],
394-
objects: &dyn ObjectResolver,
395-
) -> Result<Vec<DecodeParms>, FilterError> {
396-
let param_dicts = resolve_decode_parms_dicts(dict, filters, objects)?;
397-
398-
filters
399-
.iter()
400-
.zip(param_dicts.iter().copied())
401-
.map(|(filter, param_dict)| decode_parms_for_filter(filter, param_dict, objects))
402-
.collect()
403-
}
404-
405-
fn resolve_decode_parms_dicts<'a>(
406-
dict: &'a Dictionary,
407-
filters: &[Filter],
408-
objects: &'a dyn ObjectResolver,
409-
) -> Result<Vec<Option<&'a Dictionary>>, FilterError> {
410-
let Some(entry) = dict.get("DecodeParms") else {
411-
return Ok(vec![None; filters.len()]);
412-
};
413-
414-
let resolved = objects.resolve_object(entry)?;
415-
416-
match resolved {
417-
ObjectVariant::Dictionary(d) => Ok(vec![Some(d.as_ref()); filters.len()]),
418-
ObjectVariant::Array(arr) => {
419-
resolve_decode_parms_array(arr.as_slice(), filters.len(), objects)
420-
}
421-
other => Err(FilterError::from(
422-
pdf_object::error::ObjectError::TypeMismatch("Dictionary or Array", other.name()),
423-
)),
424-
}
425-
}
426-
427-
fn resolve_decode_parms_array<'a>(
428-
arr: &'a [ObjectVariant],
429-
filter_count: usize,
430-
objects: &'a dyn ObjectResolver,
431-
) -> Result<Vec<Option<&'a Dictionary>>, FilterError> {
432-
(0..filter_count)
433-
.map(|index| {
434-
let Some(item) = arr.get(index) else {
435-
return Ok(None);
436-
};
437-
438-
let resolved = objects.resolve_object(item)?;
439-
match resolved {
440-
ObjectVariant::Dictionary(d) => Ok(Some(d.as_ref())),
441-
ObjectVariant::Null => Ok(None),
442-
other => Err(FilterError::from(
443-
pdf_object::error::ObjectError::TypeMismatch("Dictionary", other.name()),
444-
)),
445-
}
446-
})
447-
.collect()
448-
}
449-
450-
fn decode_parms_for_filter(
451-
filter: &Filter,
452-
param_dict: Option<&Dictionary>,
453-
objects: &dyn ObjectResolver,
454-
) -> Result<DecodeParms, FilterError> {
455-
let params = match (filter, param_dict) {
456-
(Filter::CCITTFaxDecode, Some(d)) => {
457-
let p = CCITTFaxParams::from_dictionary(d, objects)?;
458-
DecodeParms::CcittFax(p)
459-
}
460-
(Filter::CCITTFaxDecode, None) => DecodeParms::CcittFax(CCITTFaxParams::default()),
461-
(Filter::LZWDecode, Some(d)) => {
462-
let early_change = d.optional_number("EarlyChange", objects)?.unwrap_or(1) != 0;
463-
let predictor = PredictorParams::from_dictionary(d, objects)?;
464-
DecodeParms::Lzw {
465-
early_change,
466-
predictor,
467-
}
468-
}
469-
(Filter::LZWDecode, None) => DecodeParms::Lzw {
470-
early_change: true,
471-
predictor: PredictorParams::default(),
472-
},
473-
(Filter::FlateDecode, Some(d)) => {
474-
let predictor = PredictorParams::from_dictionary(d, objects)?;
475-
DecodeParms::Flate { predictor }
476-
}
477-
(Filter::FlateDecode, None) => DecodeParms::Flate {
478-
predictor: PredictorParams::default(),
479-
},
480-
(Filter::JBIG2Decode, Some(d)) => DecodeParms::Jbig2 {
481-
globals: resolve_jbig2_globals(d, objects)?,
482-
},
483-
(Filter::JBIG2Decode, None) => DecodeParms::Jbig2 { globals: None },
484-
_ => DecodeParms::None,
485-
};
486-
487-
Ok(params)
488-
}
489-
490392
fn resolve_jbig2_dimensions(
491393
dict: &Dictionary,
492394
objects: &dyn ObjectResolver,
@@ -762,6 +664,81 @@ mod tests {
762664
assert_eq!(decoded.as_ref(), b"hello");
763665
}
764666

667+
#[test]
668+
fn test_decode_with_sparse_decode_parms_array() {
669+
use flate2::{Compression, write::ZlibEncoder};
670+
use std::fmt::Write as _;
671+
use std::io::Write as _;
672+
673+
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
674+
encoder.write_all(b"hello").expect("zlib write failed");
675+
let compressed = encoder.finish().expect("zlib finish failed");
676+
677+
let mut encoded = String::new();
678+
for byte in compressed {
679+
write!(&mut encoded, "{byte:02x}").expect("hex write failed");
680+
}
681+
encoded.push('>');
682+
683+
let dictionary = Dictionary::new(BTreeMap::from([
684+
(
685+
"Filter".to_string(),
686+
ObjectVariant::Array(vec![
687+
ObjectVariant::Name(b"ASCIIHexDecode".to_vec()),
688+
ObjectVariant::Name(b"FlateDecode".to_vec()),
689+
]),
690+
),
691+
(
692+
"DecodeParms".to_string(),
693+
ObjectVariant::Array(vec![ObjectVariant::Null]),
694+
),
695+
]));
696+
697+
let decoded = decode_data_with_resolver(
698+
&dictionary,
699+
Arc::new(encoded.into_bytes()),
700+
&PassthroughResolver,
701+
)
702+
.expect("decode failed");
703+
704+
assert_eq!(decoded.as_ref(), b"hello");
705+
}
706+
707+
#[test]
708+
fn test_decode_parms_array_accepts_indirect_dictionary_object() {
709+
use flate2::{Compression, write::ZlibEncoder};
710+
use pdf_object::indirect_object::IndirectObject;
711+
use std::io::Write;
712+
713+
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
714+
encoder.write_all(b"hello").expect("zlib write failed");
715+
let compressed = encoder.finish().expect("zlib finish failed");
716+
717+
let decode_parms = ObjectVariant::IndirectObject(Box::new(IndirectObject::new(
718+
2,
719+
0,
720+
Some(ObjectVariant::Dictionary(Box::new(Dictionary::new(
721+
BTreeMap::new(),
722+
)))),
723+
)));
724+
let dictionary = Dictionary::new(BTreeMap::from([
725+
(
726+
"Filter".to_string(),
727+
ObjectVariant::Name(b"FlateDecode".to_vec()),
728+
),
729+
(
730+
"DecodeParms".to_string(),
731+
ObjectVariant::Array(vec![decode_parms]),
732+
),
733+
]));
734+
735+
let decoded =
736+
decode_data_with_resolver(&dictionary, Arc::new(compressed), &PassthroughResolver)
737+
.expect("decode failed");
738+
739+
assert_eq!(decoded.as_ref(), b"hello");
740+
}
741+
765742
#[test]
766743
fn test_decode_run_length_stream() {
767744
let mut dict = BTreeMap::new();

0 commit comments

Comments
 (0)