diff --git a/crates/pdf-canvas/benches/text_recording.rs b/crates/pdf-canvas/benches/text_recording.rs index f1c8ff50..0e76fa22 100644 --- a/crates/pdf-canvas/benches/text_recording.rs +++ b/crates/pdf-canvas/benches/text_recording.rs @@ -18,7 +18,7 @@ fn content_stream(text: &[u8]) -> ContentStream { let mut data = b"BT /F1 10 Tf (".to_vec(); data.extend_from_slice(text); data.extend_from_slice(b") Tj ET"); - let stream = StreamObject::new(1, 0, Dictionary::new(Default::default()), data); + let stream = StreamObject::new(1, 0, Dictionary::default(), data); pdf_object_reader::ObjectReader::new(EmptySource) .read::(&ObjectVariant::Stream(stream)) .expect("benchmark stream should parse") diff --git a/crates/pdf-canvas/tests/common/mod.rs b/crates/pdf-canvas/tests/common/mod.rs index fa8897e3..a19a87a7 100644 --- a/crates/pdf-canvas/tests/common/mod.rs +++ b/crates/pdf-canvas/tests/common/mod.rs @@ -157,7 +157,7 @@ pub fn content_stream(object_number: usize, data: &[u8]) -> ContentStream { let stream = StreamObject::new( object_number, 0, - Dictionary::new(std::collections::BTreeMap::< + Dictionary::from_iter(std::collections::BTreeMap::< Vec, pdf_object_reader::object_variant::ObjectVariant, >::new()), diff --git a/crates/pdf-canvas/tests/pdf_canvas.rs b/crates/pdf-canvas/tests/pdf_canvas.rs index 09159d24..2f33c3aa 100644 --- a/crates/pdf-canvas/tests/pdf_canvas.rs +++ b/crates/pdf-canvas/tests/pdf_canvas.rs @@ -53,7 +53,7 @@ fn form_resource(name: &str, stream: ContentStream) -> Resources { } fn image_dictionary() -> Dictionary { - Dictionary::new(std::collections::BTreeMap::from([ + Dictionary::from_iter(std::collections::BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(1)), ( Vec::from(b"ColorSpace"), @@ -75,7 +75,7 @@ fn image_dictionary() -> Dictionary { fn inline_image() -> PdfImage { PdfImage::inline( - Dictionary::new(std::collections::BTreeMap::from([ + Dictionary::from_iter(std::collections::BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(1)), ( Vec::from(b"CS"), diff --git a/crates/pdf-ccitt/src/ccitt_fax_params.rs b/crates/pdf-ccitt/src/ccitt_fax_params.rs index 38f37dca..e2259583 100644 --- a/crates/pdf-ccitt/src/ccitt_fax_params.rs +++ b/crates/pdf-ccitt/src/ccitt_fax_params.rs @@ -79,12 +79,10 @@ mod tests { use pdf_object_reader::EmptySource; use pdf_object_reader::object_variant::ObjectVariant; use pdf_object_reader::{Dictionary, ObjectReader}; - use std::collections::BTreeMap; #[test] fn params_from_empty_dict_uses_defaults() -> ReadResult<()> { - use std::collections::BTreeMap; - let dict = Dictionary::new(BTreeMap::, ObjectVariant>::new()); + let dict = Dictionary::default(); let p = ObjectReader::new(EmptySource) .read::(&ObjectVariant::Dictionary(dict))?; assert_eq!(p.k, 0); @@ -102,25 +100,16 @@ mod tests { fn params_from_dict_reads_all_keys() -> ReadResult<()> { use pdf_object_reader::object_variant::ObjectVariant; - let mut dict = Dictionary::new(BTreeMap::, ObjectVariant>::new()); - dict.dictionary - .insert(b"K".to_vec(), ObjectVariant::Integer(-1)); - dict.dictionary - .insert(b"Columns".to_vec(), ObjectVariant::Integer(800)); - dict.dictionary - .insert(b"Rows".to_vec(), ObjectVariant::Integer(600)); - dict.dictionary - .insert(b"EndOfLine".to_vec(), ObjectVariant::Boolean(true)); - dict.dictionary - .insert(b"EncodedByteAlign".to_vec(), ObjectVariant::Boolean(true)); - dict.dictionary - .insert(b"EndOfBlock".to_vec(), ObjectVariant::Boolean(false)); - dict.dictionary - .insert(b"BlackIs1".to_vec(), ObjectVariant::Boolean(true)); - dict.dictionary.insert( - b"DamagedRowsBeforeError".to_vec(), - ObjectVariant::Integer(2), - ); + let dict = Dictionary::from_entries([ + (b"K", ObjectVariant::Integer(-1)), + (b"Columns", ObjectVariant::Integer(800)), + (b"Rows", ObjectVariant::Integer(600)), + (b"EndOfLine", ObjectVariant::Boolean(true)), + (b"EncodedByteAlign", ObjectVariant::Boolean(true)), + (b"EndOfBlock", ObjectVariant::Boolean(false)), + (b"BlackIs1", ObjectVariant::Boolean(true)), + (b"DamagedRowsBeforeError", ObjectVariant::Integer(2)), + ]); let p = ObjectReader::new(EmptySource) .read::(&ObjectVariant::Dictionary(dict))?; diff --git a/crates/pdf-color-space/src/device_n_color_space.rs b/crates/pdf-color-space/src/device_n_color_space.rs index 205ed673..60a7a5ee 100644 --- a/crates/pdf-color-space/src/device_n_color_space.rs +++ b/crates/pdf-color-space/src/device_n_color_space.rs @@ -141,7 +141,7 @@ mod tests { ObjectVariant::Stream(StreamObject::new( 1, 0, - Dictionary::new(dict), + Dictionary::from_iter(dict), code.as_bytes().to_vec(), )) } @@ -173,8 +173,7 @@ mod tests { #[test] fn parses_five_element_device_n_array_with_attributes() { - let attrs = - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::, ObjectVariant>::new())); + let attrs = ObjectVariant::Dictionary(Dictionary::default()); let arr = device_n_array(vec![ ObjectVariant::Array(vec![name("Spot")].into()), name("DeviceCMYK"), diff --git a/crates/pdf-color-space/src/indexed_color_space.rs b/crates/pdf-color-space/src/indexed_color_space.rs index b1ff2010..3b5bfe40 100644 --- a/crates/pdf-color-space/src/indexed_color_space.rs +++ b/crates/pdf-color-space/src/indexed_color_space.rs @@ -144,7 +144,6 @@ impl IndexedColorSpace { #[cfg(test)] #[allow(clippy::unwrap_used, clippy::expect_used)] mod tests { - use std::collections::BTreeMap; use pdf_object_reader::{ EmptySource, ObjectReader, dictionary::Dictionary, object_variant::ObjectVariant, @@ -169,12 +168,7 @@ mod tests { #[test] fn stream_lookup_shares_decoded_stream_bytes() { - let stream = StreamObject::new( - 1, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - vec![1, 2, 3, 4], - ); + let stream = StreamObject::new(1, 0, Dictionary::default(), vec![1, 2, 3, 4]); let stream_data = stream.shared_data(); let lookup = ObjectVariant::Stream(stream); diff --git a/crates/pdf-content-stream/benches/operator_parsing.rs b/crates/pdf-content-stream/benches/operator_parsing.rs index 84766c2b..31e23df1 100644 --- a/crates/pdf-content-stream/benches/operator_parsing.rs +++ b/crates/pdf-content-stream/benches/operator_parsing.rs @@ -16,12 +16,7 @@ fn repeated_stream(fragment: &[u8]) -> ObjectVariant { data.extend_from_slice(fragment); } - ObjectVariant::Stream(StreamObject::new( - 1, - 0, - Dictionary::new(Default::default()), - data, - )) + ObjectVariant::Stream(StreamObject::new(1, 0, Dictionary::default(), data)) } fn benchmark_stream( diff --git a/crates/pdf-content-stream/src/content_stream.rs b/crates/pdf-content-stream/src/content_stream.rs index 154956cc..1b69e858 100644 --- a/crates/pdf-content-stream/src/content_stream.rs +++ b/crates/pdf-content-stream/src/content_stream.rs @@ -96,12 +96,7 @@ mod tests { } fn stream_object(object_number: usize, data: &[u8]) -> StreamObject { - StreamObject::new( - object_number, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - data.to_vec(), - ) + StreamObject::new(object_number, 0, Dictionary::default(), data.to_vec()) } struct MapResolver { @@ -291,7 +286,7 @@ mod tests { #[test] fn optional_contents_preserves_allocator_for_missing_contents() { - let page = Dictionary::new(BTreeMap::, ObjectVariant>::new()); + let page = Dictionary::default(); let reader = pdf_object_reader::ObjectReader::new(EmptySource); let contents = reader @@ -318,7 +313,7 @@ mod tests { ] .into(), ); - let page = Dictionary::new(BTreeMap::from([(Vec::from(b"Contents"), contents)])); + let page = Dictionary::from_iter(BTreeMap::from([(Vec::from(b"Contents"), contents)])); let resolver = MapResolver { objects: BTreeMap::from([ (1, ObjectVariant::Stream(stream_object(1, b"q"))), diff --git a/crates/pdf-content-stream/tests/parser_api.rs b/crates/pdf-content-stream/tests/parser_api.rs index 71f9819e..d44c6432 100644 --- a/crates/pdf-content-stream/tests/parser_api.rs +++ b/crates/pdf-content-stream/tests/parser_api.rs @@ -24,12 +24,7 @@ impl pdf_object_reader::FromPdfObject for PageContents { } fn stream_object(object_number: usize, data: &[u8]) -> StreamObject { - StreamObject::new( - object_number, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - data.to_vec(), - ) + StreamObject::new(object_number, 0, Dictionary::default(), data.to_vec()) } struct MapResolver { @@ -151,7 +146,7 @@ fn content_stream_read_skips_unknown_operator_and_recovers() { #[test] fn optional_contents_preserves_allocator_for_missing_contents() { - let page = Dictionary::new(BTreeMap::, ObjectVariant>::new()); + let page = Dictionary::default(); let reader = pdf_object_reader::ObjectReader::new(EmptySource); let contents = reader @@ -178,7 +173,7 @@ fn optional_contents_parses_stream_arrays_and_allocates_monotonically() { ] .into(), ); - let page = Dictionary::new(BTreeMap::from([(Vec::from(b"Contents"), contents)])); + let page = Dictionary::from_iter(BTreeMap::from([(Vec::from(b"Contents"), contents)])); let resolver = MapResolver { objects: BTreeMap::from([ (1, ObjectVariant::Stream(stream_object(1, b"1 2"))), diff --git a/crates/pdf-document/src/decryption.rs b/crates/pdf-document/src/decryption.rs index 4f376ac7..484e6b6c 100644 --- a/crates/pdf-document/src/decryption.rs +++ b/crates/pdf-document/src/decryption.rs @@ -292,27 +292,19 @@ impl DocumentDecryptor { /// Decrypts dictionary values while preserving signature contents required by PDF rules. fn decrypt_dictionary( &self, - dictionary: Dictionary, + mut dictionary: Dictionary, object_number: usize, generation_number: usize, ) -> Result { let is_signature = is_signature_dictionary(&dictionary); - let entries = dictionary - .dictionary - .into_iter() - .map(|(key, value)| { - if is_signature && key == b"Contents" { - Ok((key, value)) - } else { - self.decrypt_object_value(value, object_number, generation_number) - .map(|value| (key, value)) - } - }) - .collect::>()?; - Ok(Dictionary { - dictionary: entries, - object_number: dictionary.object_number, - }) + for (key, value) in dictionary.iter_mut() { + if is_signature && key == b"Contents" { + continue; + } + let encrypted = std::mem::replace(value, ObjectVariant::Null); + *value = self.decrypt_object_value(encrypted, object_number, generation_number)?; + } + Ok(dictionary) } /// Decrypts a stream's data and recursively transforms its dictionary values. @@ -385,7 +377,7 @@ mod tests { ); } - StreamObject::new(7, 0, Dictionary::new(entries), data) + StreamObject::new(7, 0, Dictionary::from_iter(entries), data) } fn encrypt_for_object( @@ -473,7 +465,7 @@ mod tests { number: object_number, generation: generation_number, }; - let object = ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + let object = ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Contents"), pdf_object_reader::pdf_string::PdfString::from( @@ -556,7 +548,7 @@ mod tests { let stream = StreamObject::new( 99, 1, - Dictionary::new(BTreeMap::from([( + Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Label"), pdf_object_reader::pdf_string::PdfString::from( encrypt_for_object( @@ -602,7 +594,7 @@ mod tests { number: 9, generation: 0, }; - let object = ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + let object = ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Type"), pdf_object_reader::pdf_string::PdfString::from( diff --git a/crates/pdf-document/src/encryption.rs b/crates/pdf-document/src/encryption.rs index 74ab398f..1672950f 100644 --- a/crates/pdf-document/src/encryption.rs +++ b/crates/pdf-document/src/encryption.rs @@ -316,7 +316,7 @@ mod tests { for (key, value) in entries { map.insert(Vec::from(key.as_bytes()), value); } - Dictionary::new(map) + Dictionary::from_iter(map) } #[test] diff --git a/crates/pdf-document/tests/pages.rs b/crates/pdf-document/tests/pages.rs index 976f7a23..efa7a32e 100644 --- a/crates/pdf-document/tests/pages.rs +++ b/crates/pdf-document/tests/pages.rs @@ -48,14 +48,14 @@ fn color_space(value: &str) -> ObjectVariant { } fn resources_dictionary(entries: &[(&str, &str)]) -> Dictionary { - let color_spaces = Dictionary::new( + let color_spaces = Dictionary::from_iter( entries .iter() .map(|(name, value)| (Vec::from(name.as_bytes()), color_space(value))) .collect::>(), ); - Dictionary::new(BTreeMap::from([( + Dictionary::from_iter(BTreeMap::from([( Vec::from(b"ColorSpace"), ObjectVariant::Dictionary(color_spaces), )])) @@ -92,7 +92,7 @@ fn page_dictionary( ); } - let mut dictionary = Dictionary::new(entries); + let mut dictionary = Dictionary::from_iter(entries); dictionary.object_number = Some(object_number); dictionary } @@ -134,7 +134,7 @@ fn pages_dictionary( ); } - let mut dictionary = Dictionary::new(entries); + let mut dictionary = Dictionary::from_iter(entries); dictionary.object_number = Some(object_number); dictionary } diff --git a/crates/pdf-filter/src/filter.rs b/crates/pdf-filter/src/filter.rs index e22fdd58..8574bef1 100644 --- a/crates/pdf-filter/src/filter.rs +++ b/crates/pdf-filter/src/filter.rs @@ -299,7 +299,7 @@ mod tests { #[test] fn decode_data_without_filters_preserves_shared_data() { - let dictionary = Dictionary::new(BTreeMap::, ObjectVariant>::new()); + let dictionary = Dictionary::default(); let data = Bytes::from_static(b"stream data"); let decoded = decode_with( @@ -314,7 +314,7 @@ mod tests { #[test] fn decode_data_with_filter_returns_shared_decoded_data() { - let dictionary = Dictionary::new(BTreeMap::from([( + let dictionary = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Filter"), pdf_object_reader::pdf_string::PdfString::from( b"ASCIIHexDecode", @@ -336,12 +336,7 @@ mod tests { #[test] fn decode_unfiltered_stream_shares_data() { - let stream = StreamObject::new( - 1, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - b"shared stream data".to_vec(), - ); + let stream = StreamObject::new(1, 0, Dictionary::default(), b"shared stream data".to_vec()); let data = stream.shared_data(); let decoded = decode(stream).expect("unfiltered data should decode"); @@ -361,7 +356,7 @@ mod tests { ), ); - let stream = StreamObject::new(1, 0, Dictionary::new(dict), Vec::new()); + let stream = StreamObject::new(1, 0, Dictionary::from_iter(dict), Vec::new()); let err = decode(stream).expect_err("expected decode failure"); assert!(matches!(err, FilterError::Decompression(_))); } @@ -401,7 +396,8 @@ mod tests { pdf_object_reader::string_kind::StringKind::Name, ), ); - let globals_stream = StreamObject::new(3, 0, Dictionary::new(globals_dict), Vec::new()); + let globals_stream = + StreamObject::new(3, 0, Dictionary::from_iter(globals_dict), Vec::new()); let mut decode_parms = BTreeMap::new(); decode_parms.insert( @@ -424,10 +420,13 @@ mod tests { ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(2, 0)), ); - let stream = StreamObject::new_encoded(1, 0, Dictionary::new(dict), Vec::new()); + let stream = StreamObject::new_encoded(1, 0, Dictionary::from_iter(dict), Vec::new()); let mut objects = BTreeMap::new(); - objects.insert(2, ObjectVariant::Dictionary(Dictionary::new(decode_parms))); + objects.insert( + 2, + ObjectVariant::Dictionary(Dictionary::from_iter(decode_parms)), + ); objects.insert(3, ObjectVariant::Stream(globals_stream)); let resolver = TrackingResolver { @@ -453,7 +452,7 @@ mod tests { dict.insert(Vec::from(b"Width"), ObjectVariant::Integer(8)); dict.insert(Vec::from(b"Height"), ObjectVariant::Integer(1)); - let stream = StreamObject::new(1, 0, Dictionary::new(dict), vec![0x00]); + let stream = StreamObject::new(1, 0, Dictionary::from_iter(dict), vec![0x00]); let err = decode(stream).expect_err("expected decode failure"); assert!(matches!(err, FilterError::Decompression(_))); @@ -473,7 +472,7 @@ mod tests { let stream = StreamObject::new( 1, 0, - Dictionary::new(dict), + Dictionary::from_iter(dict), b"48 65 6c 6c 6f>ignored".to_vec(), ); @@ -519,7 +518,7 @@ mod tests { ObjectVariant::Integer(compressed.len() as i64), ); - let stream = StreamObject::new_encoded(1, 0, Dictionary::new(dict), compressed); + let stream = StreamObject::new_encoded(1, 0, Dictionary::from_iter(dict), compressed); let mut objects = BTreeMap::new(); objects.insert( @@ -554,7 +553,7 @@ mod tests { } encoded.push('>'); - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Filter"), ObjectVariant::Array( @@ -597,7 +596,7 @@ mod tests { let decode_parms = ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(2, 0)); - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Filter"), pdf_object_reader::pdf_string::PdfString::from( @@ -612,12 +611,7 @@ mod tests { ])); let resolver = TestResolver { - objects: BTreeMap::from([( - 2, - ObjectVariant::Dictionary(Dictionary::new( - BTreeMap::, ObjectVariant>::new(), - )), - )]), + objects: BTreeMap::from([(2, ObjectVariant::Dictionary(Dictionary::default()))]), }; let decoded = decode_with( StreamObject::new_encoded(1, 0, dictionary, compressed), @@ -642,7 +636,7 @@ mod tests { let stream = StreamObject::new( 1, 0, - Dictionary::new(dict), + Dictionary::from_iter(dict), vec![2, b'A', b'B', b'C', 255, b'!', 128], ); @@ -661,7 +655,7 @@ mod tests { ), ); - let stream = StreamObject::new(1, 0, Dictionary::new(dict), vec![0, b'X', 128]); + let stream = StreamObject::new(1, 0, Dictionary::from_iter(dict), vec![0, b'X', 128]); let decoded = decode(stream).expect("decode failed"); assert_eq!(decoded.as_ref(), b"X"); diff --git a/crates/pdf-font/src/pdf_font_metrics.rs b/crates/pdf-font/src/pdf_font_metrics.rs index bd48c00d..429d31b8 100644 --- a/crates/pdf-font/src/pdf_font_metrics.rs +++ b/crates/pdf-font/src/pdf_font_metrics.rs @@ -102,7 +102,7 @@ mod tests { ), )]) .collect(); - Dictionary::new(map) + Dictionary::from_iter(map) } fn int(n: i64) -> ObjectVariant { diff --git a/crates/pdf-image/src/decoded_samples.rs b/crates/pdf-image/src/decoded_samples.rs index 463eee6f..099e46dc 100644 --- a/crates/pdf-image/src/decoded_samples.rs +++ b/crates/pdf-image/src/decoded_samples.rs @@ -474,7 +474,7 @@ mod tests { #[test] fn decode_direct_preserves_shared_identity_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), diff --git a/crates/pdf-image/src/image_decoder.rs b/crates/pdf-image/src/image_decoder.rs index 5d55f032..edb7bd57 100644 --- a/crates/pdf-image/src/image_decoder.rs +++ b/crates/pdf-image/src/image_decoder.rs @@ -243,7 +243,7 @@ mod tests { ObjectVariant::Array( vec![ name("CalRGB"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"WhitePoint"), ObjectVariant::Array( @@ -282,7 +282,7 @@ mod tests { let function = StreamObject::new( 1, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"FunctionType"), ObjectVariant::Integer(4)), ( Vec::from(b"Domain"), @@ -342,7 +342,7 @@ mod tests { #[test] fn decode_normalized_1bpc_gray_inverts_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(1)), ( Vec::from(b"ColorSpace"), @@ -373,7 +373,7 @@ mod tests { #[test] fn decode_normalized_8bpc_gray_remaps_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -404,7 +404,7 @@ mod tests { #[test] fn decode_normalized_indexed_image_applies_decode_before_lookup() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(1)), ( Vec::from(b"ColorSpace"), @@ -449,7 +449,7 @@ mod tests { #[test] fn indexed_device_n_palette_is_converted_through_cal_rgb() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -479,7 +479,7 @@ mod tests { #[test] fn direct_four_component_device_n_is_not_interpreted_as_cmyk() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), (Vec::from(b"ColorSpace"), fixture_device_n()), (Vec::from(b"Height"), ObjectVariant::Integer(1)), @@ -497,7 +497,7 @@ mod tests { #[test] fn indexed_device_cmyk_keeps_device_fast_path() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -529,7 +529,7 @@ mod tests { let tint_function = StreamObject::new( 2, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"FunctionType"), ObjectVariant::Integer(4)), ( Vec::from(b"Domain"), @@ -548,7 +548,7 @@ mod tests { ])), b"{ pop 1 1 1 }".to_vec(), ); - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -584,7 +584,7 @@ mod tests { #[test] fn decode_normalized_image_rejects_invalid_decode_length() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -615,7 +615,7 @@ mod tests { #[test] fn decode_normalized_image_without_decode_preserves_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -642,7 +642,7 @@ mod tests { #[test] fn decode_normalized_image_trims_trailing_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -668,7 +668,7 @@ mod tests { #[test] fn decode_normalized_image_mask_defaults_bits_per_component_to_one() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"ImageMask"), ObjectVariant::Boolean(true)), (Vec::from(b"Height"), ObjectVariant::Integer(1)), (Vec::from(b"Width"), ObjectVariant::Integer(4)), @@ -686,7 +686,7 @@ mod tests { #[test] fn decode_normalized_non_mask_image_still_requires_bits_per_component() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"ColorSpace"), pdf_object_reader::pdf_string::PdfString::from( @@ -709,7 +709,7 @@ mod tests { #[test] fn decode_normalized_dct_cmyk_accepts_preconverted_rgb_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -741,7 +741,7 @@ mod tests { #[test] fn decode_normalized_non_dct_cmyk_still_rejects_rgb_sized_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -771,7 +771,7 @@ mod tests { #[test] fn decode_normalized_dct_single_pixel_expands_to_declared_size() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -806,7 +806,7 @@ mod tests { #[test] fn decode_normalized_image_applies_resolved_soft_mask() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -842,7 +842,7 @@ mod tests { #[test] fn decode_inline_image_applies_filter_chain_before_samples() { let image = PdfImage::inline( - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(8)), ( Vec::from(b"CS"), @@ -875,7 +875,7 @@ mod tests { #[test] fn decode_inline_image_accepts_abbreviated_gray_color_space() { let image = PdfImage::inline( - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(1)), ( Vec::from(b"CS"), @@ -903,7 +903,7 @@ mod tests { #[test] fn decode_inline_image_preserves_unfiltered_shared_samples() { let image = PdfImage::inline( - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(8)), ( Vec::from(b"CS"), @@ -932,7 +932,7 @@ mod tests { #[test] fn decode_inline_image_accepts_abbreviated_indexed_color_space() { let image = PdfImage::inline( - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(1)), ( Vec::from(b"CS"), @@ -978,7 +978,7 @@ mod tests { #[test] fn decode_normalized_1bpc_image_expands_samples() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(1)), ( Vec::from(b"ColorSpace"), @@ -1077,7 +1077,7 @@ mod tests { } fn indexed_dictionary(bits_per_component: i64) -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(bits_per_component), @@ -1109,7 +1109,7 @@ mod tests { } fn filtered_gray_dictionary() -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), diff --git a/crates/pdf-image/src/image_metadata.rs b/crates/pdf-image/src/image_metadata.rs index 3d7bdb1a..fe8e157f 100644 --- a/crates/pdf-image/src/image_metadata.rs +++ b/crates/pdf-image/src/image_metadata.rs @@ -181,7 +181,7 @@ mod tests { #[test] fn image_mask_defaults_bits_per_component_to_one() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"Height"), ObjectVariant::Integer(1)), (Vec::from(b"ImageMask"), ObjectVariant::Boolean(true)), (Vec::from(b"Width"), ObjectVariant::Integer(1)), @@ -197,7 +197,7 @@ mod tests { #[test] fn image_mask_rejects_non_one_bit_components() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), (Vec::from(b"Height"), ObjectVariant::Integer(1)), (Vec::from(b"ImageMask"), ObjectVariant::Boolean(true)), @@ -216,7 +216,7 @@ mod tests { #[test] fn image_mask_stores_filter_chain() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Filter"), pdf_object_reader::pdf_string::PdfString::from( @@ -276,7 +276,7 @@ mod tests { #[test] fn jpx_images_default_bits_per_component_to_eight() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Filter"), pdf_object_reader::pdf_string::PdfString::from( @@ -301,7 +301,7 @@ mod tests { #[test] fn stores_ordered_filter_chain() { let mut dictionary = direct_dictionary(1, 1, 8); - dictionary.dictionary.insert( + dictionary.insert( b"Filter".to_vec(), ObjectVariant::Array( vec![ @@ -331,7 +331,7 @@ mod tests { #[test] fn non_jpx_images_require_bits_per_component() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"Height"), ObjectVariant::Integer(1)), (Vec::from(b"Width"), ObjectVariant::Integer(1)), ])); @@ -347,7 +347,7 @@ mod tests { } fn direct_dictionary(width: i64, height: i64, bits_per_component: i64) -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(bits_per_component), @@ -365,7 +365,7 @@ mod tests { } fn indexed_dictionary(bits_per_component: i64) -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(bits_per_component), diff --git a/crates/pdf-image/src/pdf_image.rs b/crates/pdf-image/src/pdf_image.rs index e02e9196..9ff43f18 100644 --- a/crates/pdf-image/src/pdf_image.rs +++ b/crates/pdf-image/src/pdf_image.rs @@ -1,10 +1,10 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; use bytes::Bytes; use pdf_filter::{filter::Filters, image_payload::ImagePayload}; use pdf_object_reader::{ FromPdfObject, ObjectAccess, ObjectContext, ObjectReadError, ReadResult, - dictionary::Dictionary, object_variant::ObjectVariant, stream::StreamObject, + dictionary::Dictionary, object_variant::ObjectVariant, pdf_name::PdfName, stream::StreamObject, }; use crate::{error::PdfImageError, image_metadata::ImageMetadata}; @@ -94,7 +94,7 @@ impl InlineImageHeader { let dictionary = match self.0 { ObjectVariant::Dictionary(dictionary) => dictionary, // Unreachable: a header is only built from a dictionary. - _ => Dictionary::new(BTreeMap::new()), + _ => Dictionary::default(), }; ObjectVariant::Stream(StreamObject::new_encoded(0, 0, dictionary, data)) } @@ -140,34 +140,35 @@ impl FromPdfObject for InlineDataLength { /// The parser preserves the abbreviated inline-image keys from the content stream. Where /// both spellings of a key are present, the abbreviation wins. fn normalize_inline_image_dictionary(dictionary: Dictionary) -> Dictionary { - let mut normalized = BTreeMap::new(); - - for (key, value) in dictionary.dictionary { - let canonical_key = match key.as_slice() { - b"W" => b"Width".to_vec(), - b"H" => b"Height".to_vec(), - b"BPC" => b"BitsPerComponent".to_vec(), - b"CS" => b"ColorSpace".to_vec(), - b"IM" => b"ImageMask".to_vec(), - b"I" => b"Interpolate".to_vec(), - b"F" => b"Filter".to_vec(), - b"D" => b"Decode".to_vec(), - b"DP" => b"DecodeParms".to_vec(), + let mut normalized = Dictionary::default(); + normalized.object_number = dictionary.object_number; + + // Entries arrive in key order, where each abbreviation sorts before its full name. + for (key, value) in dictionary { + let canonical_key = match key.as_bytes() { + b"W" => PdfName::from(b"Width"), + b"H" => PdfName::from(b"Height"), + b"BPC" => PdfName::from(b"BitsPerComponent"), + b"CS" => PdfName::from(b"ColorSpace"), + b"IM" => PdfName::from(b"ImageMask"), + b"I" => PdfName::from(b"Interpolate"), + b"F" => PdfName::from(b"Filter"), + b"D" => PdfName::from(b"Decode"), + b"DP" => PdfName::from(b"DecodeParms"), _ => key, }; + if normalized.get(canonical_key.as_bytes()).is_some() { + continue; + } - let canonical_value = match canonical_key.as_slice() { + let canonical_value = match canonical_key.as_bytes() { b"ColorSpace" => normalize_inline_color_space(value), _ => value, }; - - normalized.entry(canonical_key).or_insert(canonical_value); + normalized.insert(canonical_key, canonical_value); } - Dictionary { - dictionary: normalized, - object_number: dictionary.object_number, - } + normalized } /// Expands abbreviated device colour-space names, including inside an array. @@ -206,7 +207,7 @@ mod tests { #[test] fn normalize_inline_image_dictionary_expands_abbreviations() { - let dictionary = pdf_object_reader::dictionary::Dictionary::new(BTreeMap::from([ + let dictionary = pdf_object_reader::dictionary::Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(8)), ( Vec::from(b"CS"), @@ -233,8 +234,8 @@ mod tests { let normalized = normalize_inline_image_dictionary(dictionary); assert_eq!( - normalized.dictionary, - BTreeMap::from([ + normalized, + Dictionary::from_iter(BTreeMap::from([ (b"BitsPerComponent".to_vec(), ObjectVariant::Integer(8)), ( b"ColorSpace".to_vec(), @@ -256,7 +257,7 @@ mod tests { (b"ImageMask".to_vec(), ObjectVariant::Boolean(true)), (b"Interpolate".to_vec(), ObjectVariant::Boolean(false)), (b"Width".to_vec(), ObjectVariant::Integer(1)), - ]) + ])) ); } @@ -270,18 +271,19 @@ mod tests { ]; for (abbreviated, canonical) in cases { - let dictionary = pdf_object_reader::dictionary::Dictionary::new(BTreeMap::from([( - Vec::from(b"CS"), - pdf_object_reader::pdf_string::PdfString::from( - abbreviated.as_bytes(), - pdf_object_reader::string_kind::StringKind::Name, - ), - )])); + let dictionary = + pdf_object_reader::dictionary::Dictionary::from_iter(BTreeMap::from([( + Vec::from(b"CS"), + pdf_object_reader::pdf_string::PdfString::from( + abbreviated.as_bytes(), + pdf_object_reader::string_kind::StringKind::Name, + ), + )])); let normalized = normalize_inline_image_dictionary(dictionary); assert_eq!( - normalized.dictionary.get(b"ColorSpace".as_slice()), + normalized.get(b"ColorSpace".as_slice()), Some(&pdf_object_reader::pdf_string::PdfString::from( canonical.as_bytes(), pdf_object_reader::string_kind::StringKind::Name @@ -292,7 +294,7 @@ mod tests { #[test] fn normalize_inline_image_dictionary_expands_indexed_color_space_arrays() { - let dictionary = pdf_object_reader::dictionary::Dictionary::new(BTreeMap::from([( + let dictionary = pdf_object_reader::dictionary::Dictionary::from_iter(BTreeMap::from([( Vec::from(b"CS"), ObjectVariant::Array( vec![ @@ -317,7 +319,7 @@ mod tests { let normalized = normalize_inline_image_dictionary(dictionary); assert_eq!( - normalized.dictionary.get(b"ColorSpace".as_slice()), + normalized.get(b"ColorSpace".as_slice()), Some(&ObjectVariant::Array( vec![ pdf_object_reader::pdf_string::PdfString::from( @@ -355,7 +357,7 @@ mod tests { #[test] fn inline_image_applies_filters_during_construction() { let mut dictionary = gray_dictionary(); - dictionary.dictionary.insert( + dictionary.insert( b"F".to_vec(), pdf_object_reader::pdf_string::PdfString::from( b"ASCIIHexDecode", @@ -376,7 +378,7 @@ mod tests { #[test] fn invalid_metadata_is_rejected_during_construction() { let error = PdfImage::inline( - Dictionary::new(BTreeMap::, ObjectVariant>::new()), + Dictionary::default(), vec![1], &mut pdf_object_reader::ObjectReader::new(EmptySource).session(), ) @@ -389,7 +391,7 @@ mod tests { } fn gray_dictionary() -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BPC"), ObjectVariant::Integer(8)), ( Vec::from(b"CS"), diff --git a/crates/pdf-object-collection/src/object_collection.rs b/crates/pdf-object-collection/src/object_collection.rs index 1fc1dfb5..5dd8b492 100644 --- a/crates/pdf-object-collection/src/object_collection.rs +++ b/crates/pdf-object-collection/src/object_collection.rs @@ -217,7 +217,7 @@ impl ObjectCollection { #[cfg(feature = "json")] fn dictionary_to_json(dict: &pdf_object_reader::dictionary::Dictionary) -> JsonValue { let mut map = serde_json::Map::new(); - for (key, value) in &dict.dictionary { + for (key, value) in dict.iter() { map.insert( String::from_utf8_lossy(key).into_owned(), Self::object_variant_to_json(value), @@ -280,12 +280,7 @@ mod tests { fn inserting_unfiltered_stream_preserves_the_byte_allocation() { let data = vec![1, 2, 3, 4]; let original = data.as_ptr(); - let stream = StreamObject::new( - 99, - 4, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - data, - ); + let stream = StreamObject::new(99, 4, Dictionary::default(), data); let mut collection = ObjectCollection::default(); collection @@ -319,9 +314,7 @@ mod tests { number: 7, generation: 2, }, - ObjectVariant::Dictionary(Dictionary::new( - BTreeMap::, ObjectVariant>::new(), - )), + ObjectVariant::Dictionary(Dictionary::default()), ) .expect("dictionary insert failed"); @@ -353,7 +346,7 @@ mod tests { decode_parms.insert(Vec::from(b"Colors"), ObjectVariant::Integer(1)); decode_parms.insert(Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)); - let decode_parms_object = ObjectVariant::Dictionary(Dictionary::new(decode_parms)); + let decode_parms_object = ObjectVariant::Dictionary(Dictionary::from_iter(decode_parms)); let mut collection = ObjectCollection::default(); collection @@ -386,7 +379,7 @@ mod tests { let stream = ObjectVariant::Stream(StreamObject::new_encoded( 1, 0, - Dictionary::new(stream_dict), + Dictionary::from_iter(stream_dict), compressed, )); @@ -410,7 +403,7 @@ mod tests { #[test] fn insert_preserves_encoded_state_when_filter_dependencies_are_unresolved() { - let stream_dictionary = Dictionary::new(BTreeMap::from([ + let stream_dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"DecodeParms"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(2, 0)), @@ -451,9 +444,7 @@ mod tests { number: 2, generation: 0, }, - ObjectVariant::Dictionary(Dictionary::new( - BTreeMap::, ObjectVariant>::new(), - )), + ObjectVariant::Dictionary(Dictionary::default()), ) .expect("decode parameters should be inserted"); diff --git a/crates/pdf-object-reader/src/context.rs b/crates/pdf-object-reader/src/context.rs index d8e4c355..b925051a 100644 --- a/crates/pdf-object-reader/src/context.rs +++ b/crates/pdf-object-reader/src/context.rs @@ -124,7 +124,7 @@ impl<'read, A: ObjectAccess + ?Sized> DictionaryContext<'read, A> { /// The names borrow the dictionary rather than the context, so entries can be read /// while iterating. pub fn keys(&self) -> impl Iterator + use<'read, A> { - self.dictionary.dictionary.keys().map(Vec::as_slice) + self.dictionary.keys() } /// Returns the number of the indirect object holding this dictionary, if it is one. @@ -241,16 +241,16 @@ impl<'read, A: ObjectAccess + ?Sized> DictionaryContext<'read, A> { key: &[u8], mut decode: impl FnMut(&mut Self, &ObjectVariant) -> ReadResult, ) -> ReadResult, T>> { - let Some(dictionary) = self.optional::(key)? else { - return Ok(HashMap::new()); - }; - let mut entries = HashMap::with_capacity(dictionary.dictionary.len()); - for (name, value) in &dictionary.dictionary { - if !self.is_absent(value)? { - entries.insert(name.clone(), decode(self, value)?); + let entries = self.with_optional_dictionary(key, |context, dictionary| { + let mut entries = HashMap::with_capacity(dictionary.len()); + for (name, value) in dictionary.iter() { + if !context.is_absent(value)? { + entries.insert(name.to_vec(), decode(context, value)?); + } } - } - Ok(entries) + Ok(entries) + })?; + Ok(entries.unwrap_or_default()) } /// Decodes each entry of an optional sub-dictionary as `T`, keyed by entry name, skipping @@ -261,14 +261,36 @@ impl<'read, A: ObjectAccess + ?Sized> DictionaryContext<'read, A> { &mut self, key: &[u8], ) -> ReadResult, T>> { - let Some(dictionary) = self.optional::(key)? else { - return Ok(HashMap::new()); + let entries = self.with_optional_dictionary(key, |context, dictionary| { + Ok(dictionary + .iter() + .filter_map(|(name, value)| Some((name.to_vec(), context.access.read(value).ok()?))) + .collect()) + })?; + Ok(entries.unwrap_or_default()) + } + + /// Runs `visit` on an optional sub-dictionary, borrowed from its resolved object rather + /// than copied. A missing or null entry yields `None`. + fn with_optional_dictionary( + &mut self, + key: &[u8], + visit: impl FnOnce(&mut Self, &Dictionary) -> ReadResult, + ) -> ReadResult> { + let Some(object) = self.resolve_entry(key)? else { + return Ok(None); }; - Ok(dictionary - .dictionary - .iter() - .filter_map(|(name, value)| Some((name.clone(), self.access.read(value).ok()?))) - .collect()) + match object.value() { + ObjectVariant::Null => Ok(None), + ObjectVariant::Dictionary(dictionary) => visit(self, dictionary).map(Some), + other => Err(ObjectReadError::At { + location: ReadLocation::DictionaryKey(PdfName::from_bytes(key)), + source: Box::new(ObjectReadError::TypeMismatch { + expected: ObjectKind::Dictionary, + actual: other.kind(), + }), + }), + } } /// Reads an entry holding one value or an array of values, as a list. diff --git a/crates/pdf-object-reader/src/decode.rs b/crates/pdf-object-reader/src/decode.rs index cd60e833..fbb6a284 100644 --- a/crates/pdf-object-reader/src/decode.rs +++ b/crates/pdf-object-reader/src/decode.rs @@ -326,11 +326,11 @@ impl FromPdfObject for MaskMode { } /// Reads a name, accepting a string in its place as producers substitute one for the -/// other. The bytes are shared with the object, not copied. +/// other. Short names are copied inline; longer ones share the object's bytes. impl FromPdfObject for PdfName { fn from_pdf_object(context: ObjectContext<'_, impl ObjectAccess + ?Sized>) -> ReadResult { match context.object() { - ObjectVariant::String(value) => Ok(Self(std::sync::Arc::clone(&value.bytes))), + ObjectVariant::String(value) => Ok(Self::from_shared(&value.bytes)), other => Err(ObjectError::TypeMismatch("Name", other.name()).into()), } } diff --git a/crates/pdf-object-reader/src/dictionary.rs b/crates/pdf-object-reader/src/dictionary.rs index 45baf884..c090c55e 100644 --- a/crates/pdf-object-reader/src/dictionary.rs +++ b/crates/pdf-object-reader/src/dictionary.rs @@ -1,32 +1,18 @@ -use std::collections::BTreeMap; +use crate::{object_id::ObjectId, object_variant::ObjectVariant, pdf_name::PdfName}; -use crate::{object_id::ObjectId, object_variant::ObjectVariant}; - -#[derive(Debug, PartialEq, Clone)] +/// Stores an immutable PDF dictionary. +/// +/// Entries are kept sorted by key with each key present once. +#[derive(Debug, PartialEq, Clone, Default)] pub struct Dictionary { - pub dictionary: BTreeMap, ObjectVariant>, + entries: Vec<(PdfName, ObjectVariant)>, pub object_number: Option, } impl Dictionary { - /// Creates a dictionary from already-owned PDF Name keys. - pub fn new(dictionary: BTreeMap, ObjectVariant>) -> Self { - Dictionary { - dictionary, - object_number: None, - } - } - /// Creates a hand-built dictionary from borrowed PDF Name keys. - /// - /// This keeps the unavoidable ownership copy at the dictionary boundary. pub fn from_entries(entries: [(&[u8], ObjectVariant); N]) -> Self { - Self::new( - entries - .into_iter() - .map(|(key, value)| (Vec::from(key), value)) - .collect(), - ) + entries.into_iter().collect() } /// Returns a reference to the value associated with the given key, if present. @@ -39,24 +25,37 @@ impl Dictionary { /// /// Returns an optional reference to [`ObjectVariant`] when the key exists, or `None` if it does not. pub fn get(&self, key: &[u8]) -> Option<&ObjectVariant> { - self.dictionary.get(key) + let index = self.position(key).ok()?; + self.entries.get(index).map(|(_, value)| value) } /// Iterates the entries in key order, yielding each PDF Name key as a byte slice. pub fn iter(&self) -> impl Iterator { - self.dictionary + self.entries .iter() - .map(|(key, value)| (key.as_slice(), value)) + .map(|(key, value)| (key.as_bytes(), value)) + } + + /// Iterates the entries in key order with mutable access to each value. + pub fn iter_mut(&mut self) -> impl Iterator { + self.entries + .iter_mut() + .map(|(key, value)| (key.as_bytes(), value)) + } + + /// Iterates the entry names in key order. + pub fn keys(&self) -> impl Iterator { + self.entries.iter().map(|(key, _)| key.as_bytes()) } /// Returns the number of entries. pub fn len(&self) -> usize { - self.dictionary.len() + self.entries.len() } /// Returns `true` when the dictionary has no entries. pub fn is_empty(&self) -> bool { - self.dictionary.is_empty() + self.entries.is_empty() } /// Removes and returns the value associated with the given key, if present. @@ -69,7 +68,27 @@ impl Dictionary { /// /// Returns an `Option` containing the [`ObjectVariant`] if the key exists, or `None` if it does not. pub fn take(&mut self, key: &[u8]) -> Option { - self.dictionary.remove(key) + let index = self.position(key).ok()?; + (index < self.entries.len()).then(|| self.entries.remove(index).1) + } + + /// Sets the value of `key`, returning the value it replaces, if any. + pub fn insert( + &mut self, + key: impl Into, + value: ObjectVariant, + ) -> Option { + let key = key.into(); + match self.position(key.as_bytes()) { + Ok(index) => self + .entries + .get_mut(index) + .map(|(_, existing)| std::mem::replace(existing, value)), + Err(index) => { + self.entries.insert(index, (key, value)); + None + } + } } /// Returns the `/Parent` entry and its object id when it is an indirect reference. @@ -79,4 +98,33 @@ impl Dictionary { _ => None, } } + + /// Finds the index of `key`, or where it would be inserted. + #[inline] + fn position(&self, key: &[u8]) -> Result { + self.entries + .binary_search_by(|(name, _)| name.as_bytes().cmp(key)) + } +} + +/// Collects entries in source order; when a key repeats, the later definition replaces +/// the earlier one. +impl> FromIterator<(K, ObjectVariant)> for Dictionary { + fn from_iter>(entries: T) -> Self { + let mut dictionary = Self::default(); + for (key, value) in entries { + dictionary.insert(key, value); + } + dictionary + } +} + +impl IntoIterator for Dictionary { + type Item = (PdfName, ObjectVariant); + type IntoIter = std::vec::IntoIter<(PdfName, ObjectVariant)>; + + /// Moves the entries out in key order. + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } } diff --git a/crates/pdf-object-reader/src/pdf_name.rs b/crates/pdf-object-reader/src/pdf_name.rs index 4b128d13..46ccf3d3 100644 --- a/crates/pdf-object-reader/src/pdf_name.rs +++ b/crates/pdf-object-reader/src/pdf_name.rs @@ -1,21 +1,81 @@ //! PDF names. use std::borrow::Borrow; +use std::cmp::Ordering; +use std::fmt; +use std::hash::{Hash, Hasher}; use std::sync::Arc; -/// Stores a PDF name as immutable shared bytes. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PdfName(pub(crate) Arc<[u8]>); +/// The longest name stored without a heap allocation. +/// +/// With the length byte and the enum tag this keeps a name the size of a `Vec`. +const INLINE_CAPACITY: usize = 22; + +/// The storage of a [`PdfName`]: short names live inline, longer ones are shared. +#[derive(Clone)] +enum NameStorage { + Inline { + len: u8, + bytes: [u8; INLINE_CAPACITY], + }, + Shared(Arc<[u8]>), +} + +/// Stores a PDF name as immutable bytes. +/// +/// Names of up to 22 bytes, which covers nearly every dictionary key and name +/// value in practice, are stored inline without a heap allocation. Longer +/// names share one immutable allocation between clones. +#[derive(Clone)] +pub struct PdfName(NameStorage); impl PdfName { /// Copies a byte sequence into a new PDF name. pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { - Self(Arc::from(bytes.as_ref())) + let bytes = bytes.as_ref(); + let mut inline = [0; INLINE_CAPACITY]; + match (u8::try_from(bytes.len()), inline.get_mut(..bytes.len())) { + (Ok(len), Some(prefix)) => { + prefix.copy_from_slice(bytes); + Self(NameStorage::Inline { len, bytes: inline }) + } + _ => Self(NameStorage::Shared(Arc::from(bytes))), + } } /// Returns the decoded bytes of the name without a leading slash. pub fn as_bytes(&self) -> &[u8] { - &self.0 + match &self.0 { + NameStorage::Inline { len, bytes } => bytes.get(..usize::from(*len)).unwrap_or(&[]), + NameStorage::Shared(bytes) => bytes, + } + } + + /// Shares the bytes of an existing allocation, copying them inline when they are short. + pub(crate) fn from_shared(bytes: &Arc<[u8]>) -> Self { + if bytes.len() <= INLINE_CAPACITY { + Self::from_bytes(bytes.as_ref()) + } else { + Self(NameStorage::Shared(Arc::clone(bytes))) + } + } +} + +impl From<&[u8]> for PdfName { + fn from(bytes: &[u8]) -> Self { + Self::from_bytes(bytes) + } +} + +impl From<&[u8; N]> for PdfName { + fn from(bytes: &[u8; N]) -> Self { + Self::from_bytes(bytes) + } +} + +impl From> for PdfName { + fn from(bytes: Vec) -> Self { + Self::from_bytes(bytes) } } @@ -24,3 +84,41 @@ impl Borrow<[u8]> for PdfName { self.as_bytes() } } + +impl AsRef<[u8]> for PdfName { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl PartialEq for PdfName { + fn eq(&self, other: &Self) -> bool { + self.as_bytes() == other.as_bytes() + } +} + +impl Eq for PdfName {} + +impl PartialOrd for PdfName { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PdfName { + fn cmp(&self, other: &Self) -> Ordering { + self.as_bytes().cmp(other.as_bytes()) + } +} + +impl Hash for PdfName { + fn hash(&self, state: &mut H) { + self.as_bytes().hash(state); + } +} + +impl fmt::Debug for PdfName { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "/{}", self.as_bytes().escape_ascii()) + } +} diff --git a/crates/pdf-object-reader/src/stream.rs b/crates/pdf-object-reader/src/stream.rs index 51a58e95..6cd5d421 100644 --- a/crates/pdf-object-reader/src/stream.rs +++ b/crates/pdf-object-reader/src/stream.rs @@ -89,26 +89,13 @@ impl StreamObject { #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use super::StreamObject; use crate::dictionary::Dictionary; - use crate::object_variant::ObjectVariant; #[test] fn constructors_record_filter_state() { - let decoded = StreamObject::new( - 1, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - vec![1], - ); - let encoded = StreamObject::new_encoded( - 2, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - vec![2], - ); + let decoded = StreamObject::new(1, 0, Dictionary::default(), vec![1]); + let encoded = StreamObject::new_encoded(2, 0, Dictionary::default(), vec![2]); assert!(decoded.filters_applied()); assert!(!encoded.filters_applied()); @@ -116,12 +103,7 @@ mod tests { #[test] fn setting_filtered_data_updates_bytes_and_state() { - let mut stream = StreamObject::new_encoded( - 1, - 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), - vec![1], - ); + let mut stream = StreamObject::new_encoded(1, 0, Dictionary::default(), vec![1]); stream.set_filtered_data(vec![2, 3]); diff --git a/crates/pdf-parser/src/cross_reference_stream.rs b/crates/pdf-parser/src/cross_reference_stream.rs index e42c3af6..0f0ef409 100644 --- a/crates/pdf-parser/src/cross_reference_stream.rs +++ b/crates/pdf-parser/src/cross_reference_stream.rs @@ -217,7 +217,7 @@ mod tests { ObjectVariant::Integer(raw_data.len() as i64), ); - StreamObject::new_encoded(0, 0, Dictionary::new(dict_map), raw_data) + StreamObject::new_encoded(0, 0, Dictionary::from_iter(dict_map), raw_data) } #[test] @@ -384,24 +384,20 @@ mod tests { ); dict_map.insert( Vec::from(b"DecodeParms"), - ObjectVariant::Dictionary(Dictionary::new( - vec![ - ( - Vec::from(b"Columns"), - ObjectVariant::Integer(columns as i64), - ), - (Vec::from(b"Predictor"), ObjectVariant::Integer(12)), - ] - .into_iter() - .collect(), - )), + ObjectVariant::Dictionary(Dictionary::from_iter(vec![ + ( + Vec::from(b"Columns"), + ObjectVariant::Integer(columns as i64), + ), + (Vec::from(b"Predictor"), ObjectVariant::Integer(12)), + ])), ); dict_map.insert( Vec::from(b"Length"), ObjectVariant::Integer(compressed.len() as i64), ); - StreamObject::new_encoded(0, 0, Dictionary::new(dict_map), compressed) + StreamObject::new_encoded(0, 0, Dictionary::from_iter(dict_map), compressed) } #[test] diff --git a/crates/pdf-parser/src/dictionary.rs b/crates/pdf-parser/src/dictionary.rs index 4156456f..fb7e0989 100644 --- a/crates/pdf-parser/src/dictionary.rs +++ b/crates/pdf-parser/src/dictionary.rs @@ -1,8 +1,6 @@ -use std::collections::BTreeMap; - use pdf_object_reader::{ - dictionary::Dictionary, object_variant::ObjectVariant, pdf_string::PdfString, - string_kind::StringKind, + dictionary::Dictionary, object_variant::ObjectVariant, pdf_name::PdfName, + pdf_string::PdfString, string_kind::StringKind, }; use pdf_tokenizer::PdfToken; @@ -10,8 +8,8 @@ use crate::{error::ParserError, parser::PdfParser}; enum DictionaryEntryState { ExpectKeyOrTerminator, - ExpectValue { key: Vec }, - ContinueNameValue { key: Vec, value: Vec }, + ExpectValue { key: PdfName }, + ContinueNameValue { key: PdfName, value: PdfName }, } impl PdfParser<'_> { @@ -37,7 +35,7 @@ impl PdfParser<'_> { /// Parses the key-value pairs inside a regular dictionary until `>>`. fn parse_dictionary_entries(&mut self) -> Result { - let mut dictionary = BTreeMap::new(); + let mut dictionary = Dictionary::default(); let mut state = DictionaryEntryState::ExpectKeyOrTerminator; loop { @@ -52,7 +50,7 @@ impl PdfParser<'_> { if matches!(state, DictionaryEntryState::ExpectKeyOrTerminator) && self.is_at_dictionary_end() { - return Ok(Dictionary::new(dictionary)); + return Ok(dictionary); } state = self.next_dictionary_state(&mut dictionary, state)?; @@ -64,14 +62,14 @@ impl PdfParser<'_> { /// This stops as soon as the next non-whitespace token is no longer a name /// key, leaving `ID` in the input stream for the caller to consume. pub(crate) fn parse_inline_image_entries(&mut self) -> Result { - let mut dictionary = BTreeMap::new(); + let mut dictionary = Dictionary::default(); loop { self.skip_whitespace_and_comments(); match self.tokenizer.peek() { Some(PdfToken::Solidus) => {} - Some(_) => return Ok(Dictionary::new(dictionary)), + Some(_) => return Ok(dictionary), None => return Err(ParserError::UnexpectedEndOfFile), } @@ -96,7 +94,7 @@ impl PdfParser<'_> { /// Advances the regular dictionary state machine by one step. fn next_dictionary_state( &mut self, - dictionary: &mut BTreeMap, ObjectVariant>, + dictionary: &mut Dictionary, state: DictionaryEntryState, ) -> Result { match state { @@ -122,8 +120,8 @@ impl PdfParser<'_> { /// characters so spaced name values remain intact. fn parse_dictionary_value_state( &mut self, - dictionary: &mut BTreeMap, ObjectVariant>, - key: Vec, + dictionary: &mut Dictionary, + key: PdfName, ) -> Result { match self.tokenizer.peek() { Some(PdfToken::Solidus) => { @@ -141,9 +139,9 @@ impl PdfParser<'_> { /// Extends a spaced name value until it reaches a real delimiter or key boundary. fn continue_dictionary_name_value( &mut self, - dictionary: &mut BTreeMap, ObjectVariant>, - key: Vec, - mut value: Vec, + dictionary: &mut Dictionary, + key: PdfName, + value: PdfName, ) -> Result { if self.is_at_dictionary_end() || matches!(self.tokenizer.peek(), Some(PdfToken::Solidus)) { dictionary.insert(key, PdfString::from(value, StringKind::Name)); @@ -160,10 +158,11 @@ impl PdfParser<'_> { } let suffix = self.tokenizer.read_while_u8(Self::is_pdf_regular_character); - if !suffix.is_empty() { - value.push(b' '); - value.extend_from_slice(suffix); - } + let value = if suffix.is_empty() { + value + } else { + PdfName::from([value.as_bytes(), suffix].join(&b' ')) + }; Ok(DictionaryEntryState::ContinueNameValue { key, value }) } @@ -202,12 +201,12 @@ mod tests { let result = parser.parse_dictionary().unwrap(); assert_eq!( - result.dictionary.len(), + result.len(), expected_count, "Expected {} elements for input '{}', got {}", expected_count, String::from_utf8_lossy(input), - result.dictionary.len() + result.len() ); } } @@ -324,7 +323,7 @@ mod tests { let mut parser = PdfParser::from(b"/IM true /W 1 /H 1 ID \x00\x01".as_slice()); let result = parser.parse_inline_image_entries().unwrap(); - assert_eq!(result.dictionary.len(), 3); + assert_eq!(result.len(), 3); assert!(parser.tokenizer.data().starts_with(b"ID \x00\x01")); } diff --git a/crates/pdf-parser/src/inline_image.rs b/crates/pdf-parser/src/inline_image.rs index 2af82c54..bf3fa2c4 100644 --- a/crates/pdf-parser/src/inline_image.rs +++ b/crates/pdf-parser/src/inline_image.rs @@ -279,7 +279,7 @@ mod tests { let mut parser = PdfParser::from(b"ID x\nEI".as_slice()); let dictionary = parser.parse_inline_image_dictionary().unwrap(); - assert!(dictionary.dictionary.is_empty()); + assert!(dictionary.is_empty()); assert_eq!(parser.parse_inline_image_data(None).unwrap(), b"x\n"); assert_eq!(parser.tokenizer.data(), b""); } diff --git a/crates/pdf-parser/src/name.rs b/crates/pdf-parser/src/name.rs index b1f1a551..a11cc4a9 100644 --- a/crates/pdf-parser/src/name.rs +++ b/crates/pdf-parser/src/name.rs @@ -1,3 +1,4 @@ +use pdf_object_reader::pdf_name::PdfName; use pdf_tokenizer::{PdfToken, error::TokenizerError}; use thiserror::Error; @@ -21,13 +22,17 @@ impl PdfParser<'_> { /// /// # Returns /// - /// A `Name` object containing the decoded name string (with hex escapes resolved), - /// or a `NameObjectError` on failure. - pub fn parse_name(&mut self) -> Result, NameObjectError> { + /// The decoded name (with hex escapes resolved), or a `NameObjectError` on failure. + /// A name without escapes is copied straight from the input. + pub fn parse_name(&mut self) -> Result { self.tokenizer.expect(PdfToken::Solidus)?; let name = self.tokenizer.read_while_u8(|b| !Self::is_pdf_delimiter(b)); - escape(name) + if name.contains(&b'#') { + escape(name).map(PdfName::from) + } else { + Ok(PdfName::from_bytes(name)) + } } } @@ -112,7 +117,7 @@ mod tests { let mut parser = PdfParser::from(input); let value = parser.parse_name().unwrap(); assert_eq!( - value, + value.as_bytes(), expected, "input: `{}`", String::from_utf8_lossy(input) diff --git a/crates/pdf-resources/src/external_graphics_state.rs b/crates/pdf-resources/src/external_graphics_state.rs index b64eb065..b863777d 100644 --- a/crates/pdf-resources/src/external_graphics_state.rs +++ b/crates/pdf-resources/src/external_graphics_state.rs @@ -177,7 +177,7 @@ mod tests { use super::{ExternalGraphicsState, ExternalGraphicsStateKey}; fn dash_dict(dash_array: Vec, dash_phase: f32) -> Dictionary { - Dictionary::new(BTreeMap::from([( + Dictionary::from_iter(BTreeMap::from([( Vec::from(b"D"), ObjectVariant::Array( vec![ @@ -268,7 +268,7 @@ mod tests { #[test] fn soft_mask_none_is_preserved() { - let dictionary = Dictionary::new(BTreeMap::from([( + let dictionary = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"SMask"), pdf_object_reader::pdf_string::PdfString::from( b"None", @@ -288,7 +288,7 @@ mod tests { #[test] fn invalid_soft_mask_name_is_rejected() { - let dictionary = Dictionary::new(BTreeMap::from([( + let dictionary = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"SMask"), pdf_object_reader::pdf_string::PdfString::from( b"Invalid", diff --git a/crates/pdf-resources/src/form.rs b/crates/pdf-resources/src/form.rs index 9d22488f..4e93b707 100644 --- a/crates/pdf-resources/src/form.rs +++ b/crates/pdf-resources/src/form.rs @@ -59,7 +59,7 @@ mod tests { #[test] fn read_xobject_normalizes_inverted_bbox() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"BBox"), ObjectVariant::Array( @@ -97,7 +97,7 @@ mod tests { #[test] fn empty_from_dictionary_creates_empty_content_stream() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"BBox"), ObjectVariant::Array( diff --git a/crates/pdf-resources/src/soft_mask.rs b/crates/pdf-resources/src/soft_mask.rs index 76fcac0c..10cf7952 100644 --- a/crates/pdf-resources/src/soft_mask.rs +++ b/crates/pdf-resources/src/soft_mask.rs @@ -112,7 +112,7 @@ mod tests { use super::{MaskTransfer, SoftMask}; fn soft_mask_dictionary(stream_object_number: usize, subtype: &str) -> Dictionary { - let form_dictionary = Dictionary::new(BTreeMap::from([ + let form_dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"BBox"), ObjectVariant::Array( @@ -135,7 +135,7 @@ mod tests { ])); let stream = StreamObject::new(stream_object_number, 0, form_dictionary, Vec::new()); - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"G"), ObjectVariant::Stream(stream)), ( Vec::from(b"S"), @@ -170,9 +170,7 @@ mod tests { ]), b"{1 exch sub}".to_vec(), ); - dictionary - .dictionary - .insert(b"TR".to_vec(), ObjectVariant::Stream(function)); + dictionary.insert(b"TR".to_vec(), ObjectVariant::Stream(function)); let reader = pdf_object_reader::ObjectReader::new(EmptySource); let mask = reader .read::(&ObjectVariant::Dictionary(dictionary)) @@ -186,9 +184,7 @@ mod tests { #[test] fn identity_mask_transfer_needs_no_table() { let mut dictionary = soft_mask_dictionary(7, "Form"); - dictionary - .dictionary - .insert(b"TR".to_vec(), ObjectVariant::name_from_bytes(b"Identity")); + dictionary.insert(b"TR".to_vec(), ObjectVariant::name_from_bytes(b"Identity")); let reader = pdf_object_reader::ObjectReader::new(EmptySource); let mask = reader .read::(&ObjectVariant::Dictionary(dictionary)) @@ -229,7 +225,7 @@ mod tests { let Some(ObjectVariant::Stream(mut stream)) = dictionary.take(b"G") else { panic!("group stream"); }; - stream.dictionary.dictionary.insert( + stream.dictionary.insert( b"Resources".to_vec(), ObjectVariant::Dictionary(Dictionary::from_entries([( b"ExtGState".as_slice(), @@ -242,9 +238,7 @@ mod tests { )])), )])), ); - dictionary - .dictionary - .insert(b"G".to_vec(), ObjectVariant::Reference(ObjectId::new(7, 0))); + dictionary.insert(b"G".to_vec(), ObjectVariant::Reference(ObjectId::new(7, 0))); let mut objects = ObjectCollection::default(); objects .insert(ObjectId::new(7, 0), ObjectVariant::Stream(stream)) diff --git a/crates/pdf-resources/src/xobject.rs b/crates/pdf-resources/src/xobject.rs index 69944df4..7299876a 100644 --- a/crates/pdf-resources/src/xobject.rs +++ b/crates/pdf-resources/src/xobject.rs @@ -178,7 +178,7 @@ mod tests { } fn image_dictionary(object_number: usize) -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -234,7 +234,7 @@ mod tests { let image_stream = StreamObject::new( 1, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -262,7 +262,7 @@ mod tests { let mask_stream = StreamObject::new( 2, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -316,7 +316,7 @@ mod tests { let mask_stream = StreamObject::new( 2, 0, - Dictionary::new(BTreeMap::from([( + Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( b"Unsupported", @@ -344,7 +344,7 @@ mod tests { #[test] fn encoded_image_retries_filters_after_dependencies_are_resolved() { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"BitsPerComponent"), ObjectVariant::Integer(8)), ( Vec::from(b"ColorSpace"), @@ -383,9 +383,7 @@ mod tests { objects .insert( object_id(2), - ObjectVariant::Dictionary(Dictionary::new( - BTreeMap::, ObjectVariant>::new(), - )), + ObjectVariant::Dictionary(Dictionary::default()), ) .expect("decode parameters should be inserted"); @@ -412,7 +410,7 @@ mod tests { let stream = StreamObject::new( 9, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( diff --git a/crates/pdf-resources/tests/resources.rs b/crates/pdf-resources/tests/resources.rs index 45f2d513..f4b66754 100644 --- a/crates/pdf-resources/tests/resources.rs +++ b/crates/pdf-resources/tests/resources.rs @@ -37,7 +37,7 @@ fn array(values: Vec) -> ObjectVariant { } fn type2_function(c0: Vec, c1: Vec) -> ObjectVariant { - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"FunctionType"), integer(2)), (Vec::from(b"Domain"), array(vec![real(0.0), real(1.0)])), (Vec::from(b"C0"), array(c0)), @@ -47,7 +47,7 @@ fn type2_function(c0: Vec, c1: Vec) -> ObjectVaria } fn inline_axial_shading() -> ObjectVariant { - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"ShadingType"), integer(2)), (Vec::from(b"ColorSpace"), name("DeviceRGB")), ( @@ -65,7 +65,7 @@ fn inline_axial_shading() -> ObjectVariant { } fn form_xobject_stream(object_number: usize, data: &[u8]) -> ObjectVariant { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -95,7 +95,7 @@ fn recursive_form_xobject_stream( nested_object_number: usize, data: &[u8], ) -> ObjectVariant { - let dictionary = Dictionary::new(BTreeMap::from([ + let dictionary = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -111,9 +111,9 @@ fn recursive_form_xobject_stream( ), ( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"XObject"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(nested_name.as_bytes()), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new( nested_object_number, @@ -138,11 +138,11 @@ fn xobject_resources(entries: Vec<(&str, ObjectVariant)>) -> Dictionary { .map(|(name, value)| (Vec::from(name.as_bytes()), value)) .collect::>(); - Dictionary::new(BTreeMap::from([( + Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"XObject"), - ObjectVariant::Dictionary(Dictionary::new(xobjects)), + ObjectVariant::Dictionary(Dictionary::from_iter(xobjects)), )]))), )])) } @@ -153,11 +153,11 @@ fn page_resources(entries: Vec<(&str, ObjectVariant)>) -> Dictionary { .map(|(name, value)| (Vec::from(name.as_bytes()), value)) .collect::>(); - Dictionary::new(BTreeMap::from([( + Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Shading"), - ObjectVariant::Dictionary(Dictionary::new(shading_entries)), + ObjectVariant::Dictionary(Dictionary::from_iter(shading_entries)), )]))), )])) } @@ -166,13 +166,13 @@ fn type3_char_proc(data: &[u8]) -> ObjectVariant { ObjectVariant::Stream(StreamObject::new( 0, 0, - Dictionary::new(BTreeMap::, ObjectVariant>::new()), + Dictionary::default(), data.to_vec(), )) } fn self_referential_type3_font(object_number: usize) -> Dictionary { - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"Type"), name("Font")), (Vec::from(b"Subtype"), name("Type3")), (Vec::from(b"Name"), name("Self")), @@ -198,16 +198,16 @@ fn self_referential_type3_font(object_number: usize) -> Dictionary { ), ( Vec::from(b"CharProcs"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"A"), type3_char_proc(b"0 0 d0"), )]))), ), ( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Font"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Self"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new( object_number, @@ -223,7 +223,7 @@ fn self_referential_tiling_pattern(object_number: usize) -> ObjectVariant { ObjectVariant::Stream(StreamObject::new( object_number, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"PatternType"), integer(1)), (Vec::from(b"PaintType"), integer(1)), (Vec::from(b"TilingType"), integer(1)), @@ -237,9 +237,9 @@ fn self_referential_tiling_pattern(object_number: usize) -> ObjectVariant { (Vec::from(b"YStep"), real(10.0)), ( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Pattern"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Self"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new( object_number, @@ -327,7 +327,7 @@ fn form_resources_accept_an_indirect_stream_dictionary() { let nested_resources = ObjectVariant::Stream(StreamObject::new( 15, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"Subtype"), name("Form")), ( Vec::from(b"BBox"), @@ -339,7 +339,7 @@ fn form_resources_accept_an_indirect_stream_dictionary() { let form = ObjectVariant::Stream(StreamObject::new( 14, 0, - Dictionary::new(BTreeMap::from([ + Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"Subtype"), name("Form")), ( Vec::from(b"BBox"), @@ -386,20 +386,20 @@ fn form_resources_accept_an_indirect_stream_dictionary() { #[test] fn dictionary_only_form_xobjects_are_loaded_as_empty_forms() { - let page_dict = Dictionary::new(BTreeMap::from([( + let page_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(100, 0)), )])); - let resources_dict = Dictionary::new(BTreeMap::from([( + let resources_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"XObject"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Meta6"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(101, 0)), )]))), )])); - let form_dict = Dictionary::new(BTreeMap::from([ + let form_dict = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -493,12 +493,12 @@ fn cyclic_form_resources_resolve_lazily_without_recursing_forever() { Vec::from(b"Self"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(11, 0)), )]); - let resource_dict = Dictionary::new(BTreeMap::from([( + let resource_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"XObject"), - ObjectVariant::Dictionary(Dictionary::new(xobject_entries)), + ObjectVariant::Dictionary(Dictionary::from_iter(xobject_entries)), )])); - let form_dict = Dictionary::new(BTreeMap::from([ + let form_dict = Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -518,7 +518,7 @@ fn cyclic_form_resources_resolve_lazily_without_recursing_forever() { ), ])); - let page_dict = Dictionary::new(BTreeMap::from([( + let page_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(10, 0)), )])); @@ -587,11 +587,11 @@ fn cyclic_form_resources_resolve_lazily_without_recursing_forever() { #[test] fn mutually_recursive_form_xobjects_resolve_lazily() { - let page_dict = Dictionary::new(BTreeMap::from([( + let page_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"XObject"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"First"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(6, 0)), )]))), @@ -648,11 +648,11 @@ fn mutually_recursive_form_xobjects_resolve_lazily() { #[test] fn self_referential_font_resources_resolve_lazily() { - let page_dict = Dictionary::new(BTreeMap::from([( + let page_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Font"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Self"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(21, 0)), )]))), @@ -699,7 +699,7 @@ fn self_referential_font_resources_resolve_lazily() { #[test] fn fallback_fonts_do_not_read_nested_type3_resources() { - let malformed_type3 = ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + let malformed_type3 = ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ ( Vec::from(b"Subtype"), pdf_object_reader::pdf_string::PdfString::from( @@ -709,11 +709,11 @@ fn fallback_fonts_do_not_read_nested_type3_resources() { ), (Vec::from(b"Resources"), ObjectVariant::Integer(1)), ]))); - let page_dict = Dictionary::new(BTreeMap::from([( + let page_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Font"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"F1"), malformed_type3, )]))), @@ -734,11 +734,11 @@ fn fallback_fonts_do_not_read_nested_type3_resources() { #[test] fn self_referential_pattern_resources_resolve_lazily() { - let page_dict = Dictionary::new(BTreeMap::from([( + let page_dict = Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Resources"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Pattern"), - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([( + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([( Vec::from(b"Self"), ObjectVariant::Reference(pdf_object_reader::object_id::ObjectId::new(31, 0)), )]))), diff --git a/crates/pdf-shading/tests/parse.rs b/crates/pdf-shading/tests/parse.rs index a50b7519..017c4dee 100644 --- a/crates/pdf-shading/tests/parse.rs +++ b/crates/pdf-shading/tests/parse.rs @@ -26,7 +26,7 @@ fn number_array(values: &[f32]) -> ObjectVariant { } fn type_2_rgb_function() -> ObjectVariant { - ObjectVariant::Dictionary(Dictionary::new(BTreeMap::from([ + ObjectVariant::Dictionary(Dictionary::from_iter(BTreeMap::from([ (Vec::from(b"FunctionType"), integer(2)), (Vec::from(b"Domain"), number_array(&[0.0, 1.0])), (Vec::from(b"C0"), number_array(&[1.0, 0.0, 0.0])), @@ -95,7 +95,12 @@ fn mesh_entries( } fn shading_stream(entries: BTreeMap, ObjectVariant>, data: Vec) -> ObjectVariant { - ObjectVariant::Stream(StreamObject::new(1, 0, Dictionary::new(entries), data)) + ObjectVariant::Stream(StreamObject::new( + 1, + 0, + Dictionary::from_iter(entries), + data, + )) } fn push_field(bits: &mut Vec, value: u32, width: usize) { diff --git a/tools/conformance/src/inventory.rs b/tools/conformance/src/inventory.rs index c4914d8b..fa333dda 100644 --- a/tools/conformance/src/inventory.rs +++ b/tools/conformance/src/inventory.rs @@ -251,12 +251,12 @@ fn walk( } } ObjectVariant::Dictionary(dictionary) => { - for value in dictionary.dictionary.values() { + for (_, value) in dictionary.iter() { walk(objects, value, next, features); } } ObjectVariant::Stream(stream) => { - for value in stream.dictionary.dictionary.values() { + for (_, value) in stream.dictionary.iter() { walk(objects, value, next, features); } }