Skip to content

Commit 40af042

Browse files
authored
Merge pull request #243
pdf-filter: share decoded data with Arc
2 parents ce43507 + 55c4361 commit 40af042

5 files changed

Lines changed: 96 additions & 57 deletions

File tree

crates/pdf-filter/src/filter.rs

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::borrow::Cow;
21
use std::fmt;
2+
use std::sync::Arc;
33

44
use crate::{error::FilterError, predictor::PredictorParams};
55

@@ -260,24 +260,25 @@ impl Filter {
260260
}
261261
}
262262

263-
/// Decodes borrowed stream data by applying the filter chain from its dictionary.
263+
/// Decodes stream data by applying the filter chain from its dictionary.
264264
///
265265
/// Reads the `/Filter` entry from `dictionary` and applies each filter in order.
266-
/// Returns the fully decoded bytes, or `Cow::Borrowed` if no filters are present.
266+
/// Returns shared ownership of the fully decoded bytes.
267+
/// Unfiltered input retains the original shared allocation.
267268
///
268-
/// This entry point accepts a dictionary and data slice separately so callers
269+
/// This entry point accepts a dictionary and shared data separately so callers
269270
/// such as inline-image decoders do not need to construct a temporary
270271
/// [`StreamObject`].
271272
///
272273
/// # Errors
273274
///
274275
/// Returns [`FilterError`] if any filter in the chain fails or is unsupported.
275-
pub fn decode_data_with_resolver<'a>(
276+
pub fn decode_data_with_resolver(
276277
dictionary: &Dictionary,
277-
stream_data: &'a [u8],
278+
stream_data: Arc<Vec<u8>>,
278279
objects: &dyn ObjectResolver,
279-
) -> Result<Cow<'a, [u8]>, FilterError> {
280-
let mut data: Cow<'a, [u8]> = Cow::Borrowed(stream_data);
280+
) -> Result<Arc<Vec<u8>>, FilterError> {
281+
let mut data = stream_data;
281282
let filters = Filter::from_dictionary(dictionary, objects)?;
282283

283284
let Some(filters) = &filters else {
@@ -289,14 +290,14 @@ pub fn decode_data_with_resolver<'a>(
289290
for (filter, params) in filters.iter().zip(decode_params.iter()) {
290291
match filter {
291292
Filter::FlateDecode => {
292-
let decoded = Filter::decode_flate(&data)?;
293+
let decoded = Filter::decode_flate(data.as_slice())?;
293294
let decoded = match params {
294295
DecodeParms::Flate { predictor } if !predictor.is_none() => {
295296
crate::predictor::apply_predictor(&decoded, predictor)?
296297
}
297298
_ => decoded,
298299
};
299-
data = Cow::Owned(decoded);
300+
data = Arc::new(decoded);
300301
}
301302
Filter::LZWDecode => {
302303
let (early_change, predictor) = match params {
@@ -306,49 +307,49 @@ pub fn decode_data_with_resolver<'a>(
306307
} => (*early_change, Some(predictor)),
307308
_ => (true, None),
308309
};
309-
let decoded = crate::lzw::decode(&data, early_change)?;
310+
let decoded = crate::lzw::decode(data.as_slice(), early_change)?;
310311
let decoded = match predictor {
311312
Some(p) if !p.is_none() => crate::predictor::apply_predictor(&decoded, p)?,
312313
_ => decoded,
313314
};
314-
data = Cow::Owned(decoded);
315+
data = Arc::new(decoded);
315316
}
316317
Filter::JPXDecode => {
317-
let decoded = Filter::decode_jpeg2000(&data)?;
318-
data = Cow::Owned(decoded);
318+
let decoded = Filter::decode_jpeg2000(data.as_slice())?;
319+
data = Arc::new(decoded);
319320
}
320321
Filter::DCTDecode => {
321-
let decoded = Filter::decode_jpeg_baseline(&data)?;
322-
data = Cow::Owned(decoded);
322+
let decoded = Filter::decode_jpeg_baseline(data.as_slice())?;
323+
data = Arc::new(decoded);
323324
}
324325
Filter::ASCII85Decode => {
325-
let decoded = crate::ascii85::decode_ascii85(&data)?;
326-
data = Cow::Owned(decoded);
326+
let decoded = crate::ascii85::decode_ascii85(data.as_slice())?;
327+
data = Arc::new(decoded);
327328
}
328329
Filter::ASCIIHexDecode => {
329-
let decoded = crate::asciihex::decode_ascii_hex(&data)?;
330-
data = Cow::Owned(decoded);
330+
let decoded = crate::asciihex::decode_ascii_hex(data.as_slice())?;
331+
data = Arc::new(decoded);
331332
}
332333
Filter::RunLengthDecode => {
333-
let decoded = crate::runlength::decode_run_length(&data)?;
334-
data = Cow::Owned(decoded);
334+
let decoded = crate::runlength::decode_run_length(data.as_slice())?;
335+
data = Arc::new(decoded);
335336
}
336337
Filter::JBIG2Decode => {
337338
let (width, height) = resolve_jbig2_dimensions(dictionary, objects)?;
338339
let globals = match params {
339340
DecodeParms::Jbig2 { globals } => globals.as_deref(),
340341
_ => None,
341342
};
342-
let decoded = pdf_jbig2::decode(&data, width, height, globals)?;
343-
data = Cow::Owned(decoded);
343+
let decoded = pdf_jbig2::decode(data.as_slice(), width, height, globals)?;
344+
data = Arc::new(decoded);
344345
}
345346
Filter::CCITTFaxDecode => {
346347
let ccitt_params = match params {
347348
DecodeParms::CcittFax(p) => p,
348349
_ => &CCITTFaxParams::DEFAULT,
349350
};
350-
let decoded = pdf_ccitt::decode(&data, ccitt_params)?;
351-
data = Cow::Owned(decoded);
351+
let decoded = pdf_ccitt::decode(data.as_slice(), ccitt_params)?;
352+
data = Arc::new(decoded);
352353
}
353354
Filter::Unsupported(name) => {
354355
return Err(FilterError::UnsupportedFilter(name.clone()));
@@ -360,24 +361,24 @@ pub fn decode_data_with_resolver<'a>(
360361

361362
/// Decodes a [`StreamObject`] by applying its full filter chain.
362363
///
363-
/// This compatibility entry point forwards the stream's dictionary and raw
364-
/// data to [`decode_data_with_resolver`].
364+
/// Unfiltered data shares the stream's existing allocation. Filtered data is
365+
/// returned in a newly allocated shared buffer.
365366
///
366367
/// # Errors
367368
///
368369
/// Returns [`FilterError`] if any filter in the chain fails or is unsupported.
369-
pub fn decode_with_resolver<'a>(
370-
stream: &'a StreamObject,
370+
pub fn decode_with_resolver(
371+
stream: &StreamObject,
371372
objects: &dyn ObjectResolver,
372-
) -> Result<Cow<'a, [u8]>, FilterError> {
373-
decode_data_with_resolver(&stream.dictionary, stream.raw_data(), objects)
373+
) -> Result<Arc<Vec<u8>>, FilterError> {
374+
decode_data_with_resolver(&stream.dictionary, stream.shared_data(), objects)
374375
}
375376

376377
/// Decodes a [`StreamObject`] by applying its full filter chain.
377378
///
378379
/// This convenience wrapper uses a passthrough resolver, so it only supports
379380
/// direct `/Filter` and `/DecodeParms` values.
380-
pub fn decode(stream: &StreamObject) -> Result<Cow<'_, [u8]>, FilterError> {
381+
pub fn decode(stream: &StreamObject) -> Result<Arc<Vec<u8>>, FilterError> {
381382
let objects = PassthroughResolver;
382383
decode_with_resolver(stream, &objects)
383384
}
@@ -550,31 +551,49 @@ mod tests {
550551
}
551552

552553
#[test]
553-
fn decode_data_without_filters_borrows_input() {
554+
fn decode_data_without_filters_preserves_shared_data() {
554555
let dictionary = Dictionary::new(BTreeMap::new());
555-
let data = b"borrowed stream data";
556+
let data = Arc::new(b"stream data".to_vec());
556557

557-
let decoded = decode_data_with_resolver(&dictionary, data, &PassthroughResolver)
558-
.expect("unfiltered data should decode");
558+
let decoded =
559+
decode_data_with_resolver(&dictionary, Arc::clone(&data), &PassthroughResolver)
560+
.expect("unfiltered data should decode");
559561

560-
assert!(matches!(&decoded, Cow::Borrowed(_)));
561-
assert_eq!(decoded.as_ref().as_ptr(), data.as_ptr());
562-
assert_eq!(decoded.as_ref(), data);
562+
assert!(Arc::ptr_eq(&decoded, &data));
563+
assert_eq!(decoded.as_slice(), b"stream data");
563564
}
564565

565566
#[test]
566-
fn decode_data_with_filter_returns_decoded_owned_data() {
567+
fn decode_data_with_filter_returns_shared_decoded_data() {
567568
let dictionary = Dictionary::new(BTreeMap::from([(
568569
"Filter".to_string(),
569570
ObjectVariant::Name(b"ASCIIHexDecode".to_vec()),
570571
)]));
571572

573+
let encoded = Arc::new(b"48 65 6c 6c 6f>".to_vec());
572574
let decoded =
573-
decode_data_with_resolver(&dictionary, b"48 65 6c 6c 6f>", &PassthroughResolver)
575+
decode_data_with_resolver(&dictionary, Arc::clone(&encoded), &PassthroughResolver)
574576
.expect("filtered data should decode");
575577

576-
assert!(matches!(&decoded, Cow::Owned(_)));
577-
assert_eq!(decoded.as_ref(), b"Hello");
578+
assert_eq!(Arc::strong_count(&decoded), 1);
579+
assert!(!Arc::ptr_eq(&decoded, &encoded));
580+
assert_eq!(decoded.as_slice(), b"Hello");
581+
}
582+
583+
#[test]
584+
fn decode_unfiltered_stream_shares_data() {
585+
let stream = StreamObject::new(
586+
1,
587+
0,
588+
Box::new(Dictionary::new(BTreeMap::new())),
589+
b"shared stream data".to_vec(),
590+
);
591+
592+
let decoded = decode_with_resolver(&stream, &PassthroughResolver)
593+
.expect("unfiltered data should decode");
594+
595+
assert!(Arc::ptr_eq(&decoded, &stream.data));
596+
assert_eq!(decoded.as_slice(), b"shared stream data");
578597
}
579598

580599
#[test]

crates/pdf-filter/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//! The main entry point is [`filter::decode`], which accepts a
1616
//! [`StreamObject`](pdf_object::stream::StreamObject) and applies the full
1717
//! filter chain declared in its `/Filter` dictionary entry. Callers that hold
18-
//! a dictionary and borrowed data separately can use
18+
//! a dictionary and shared data separately can use
1919
//! [`filter::decode_data_with_resolver`] without constructing a stream object.
2020
2121
pub(crate) mod ascii85;

crates/pdf-image/src/image_xobject.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl ImageXObject {
8080
soft_mask: Option<ImageXObject>,
8181
) -> Result<Self, PdfImageError> {
8282
let dictionary = image.normalized_dictionary();
83-
let decoded = decode_data_with_resolver(&dictionary, image.data(), objects)?;
83+
let decoded = decode_data_with_resolver(&dictionary, image.shared_data(), objects)?;
8484

8585
Self::decode_normalized_image(&dictionary, decoded.as_ref(), objects, soft_mask)
8686
}

crates/pdf-image/src/inline_image.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
use std::collections::BTreeMap;
2+
use std::sync::Arc;
23

34
use pdf_object::{dictionary::Dictionary, object_variant::ObjectVariant};
45

56
/// Canonical parsed representation of a PDF inline image.
67
#[derive(Debug, Clone, PartialEq)]
78
pub struct InlineImage {
89
dictionary: Dictionary,
9-
data: Vec<u8>,
10+
data: Arc<Vec<u8>>,
1011
}
1112

1213
impl InlineImage {
1314
/// Creates a new inline image from its parsed dictionary and raw payload bytes.
14-
pub fn new(dictionary: Dictionary, data: Vec<u8>) -> Self {
15-
Self { dictionary, data }
15+
pub fn new(dictionary: Dictionary, data: impl Into<Arc<Vec<u8>>>) -> Self {
16+
Self {
17+
dictionary,
18+
data: data.into(),
19+
}
1620
}
1721

1822
/// Returns the parsed inline-image dictionary.
@@ -22,11 +26,16 @@ impl InlineImage {
2226

2327
/// Returns the raw inline-image payload bytes.
2428
pub fn data(&self) -> &[u8] {
25-
&self.data
29+
self.data.as_slice()
30+
}
31+
32+
/// Returns shared ownership of the raw inline-image payload bytes.
33+
pub fn shared_data(&self) -> Arc<Vec<u8>> {
34+
Arc::clone(&self.data)
2635
}
2736

28-
/// Splits the inline image into its parsed dictionary and raw payload.
29-
pub fn into_parts(self) -> (Dictionary, Vec<u8>) {
37+
/// Splits the inline image into its parsed dictionary and shared raw payload.
38+
pub fn into_parts(self) -> (Dictionary, Arc<Vec<u8>>) {
3039
(self.dictionary, self.data)
3140
}
3241

@@ -100,11 +109,27 @@ fn normalize_inline_image_value(key: &str, value: &ObjectVariant) -> ObjectVaria
100109
#[cfg(test)]
101110
mod tests {
102111
use std::collections::BTreeMap;
112+
use std::sync::Arc;
103113

104114
use pdf_object::object_variant::ObjectVariant;
105115

106116
use super::{InlineImage, normalize_inline_image_dictionary};
107117

118+
#[test]
119+
fn inline_image_shares_payload_data() {
120+
let data = Arc::new(vec![1, 2, 3, 4]);
121+
let image = InlineImage::new(
122+
pdf_object::dictionary::Dictionary::new(BTreeMap::new()),
123+
Arc::clone(&data),
124+
);
125+
126+
assert!(Arc::ptr_eq(&image.shared_data(), &data));
127+
assert_eq!(image.data(), data.as_slice());
128+
129+
let (_, payload) = image.into_parts();
130+
assert!(Arc::ptr_eq(&payload, &data));
131+
}
132+
108133
#[test]
109134
fn normalize_inline_image_dictionary_expands_abbreviations() {
110135
let dictionary = pdf_object::dictionary::Dictionary::new(BTreeMap::from([

crates/pdf-object-collection/src/object_collection.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use pdf_object::indirect_object::IndirectObject;
22
use pdf_object::object_resolver::ObjectResolver;
33
use pdf_object::stream::StreamObject;
44
use pdf_object::{error::ObjectError, object_variant::ObjectVariant};
5-
use std::borrow::Cow;
65
use std::collections::{HashMap, HashSet};
76

87
#[cfg(feature = "json")]
@@ -86,10 +85,6 @@ impl ObjectCollection {
8685
}
8786
ObjectVariant::Stream(stream) => {
8887
let data = pdf_filter::filter::decode_with_resolver(&stream, self)
89-
.map(|data| match data {
90-
Cow::Borrowed(_) => stream.shared_data(),
91-
Cow::Owned(data) => data.into(),
92-
})
9388
.unwrap_or_else(|_| stream.shared_data());
9489
let StreamObject {
9590
object_number,

0 commit comments

Comments
 (0)