1use zpdf_core::{Error, PdfDict, PdfName, PdfObject, Result};
2
3const MAX_DECODED_OUTPUT: usize = 1 << 30; pub fn decode_stream(data: &[u8], dict: &PdfDict) -> Result<Vec<u8>> {
9 let filters = match dict.get("Filter") {
10 Some(PdfObject::Name(n)) => vec![n.clone()],
11 Some(PdfObject::Array(arr)) => arr
12 .iter()
13 .map(|obj| match obj {
14 PdfObject::Name(n) => Ok(n.clone()),
15 _ => Err(Error::TypeMismatch {
16 expected: "Name",
17 actual: obj.type_name(),
18 }),
19 })
20 .collect::<Result<Vec<_>>>()?,
21 Some(_) => {
22 return Err(Error::TypeMismatch {
23 expected: "Name or Array",
24 actual: "other",
25 })
26 }
27 None => return Ok(data.to_vec()),
28 };
29
30 let decode_parms = extract_decode_parms(dict, filters.len());
31
32 let mut result = data.to_vec();
33 for (i, filter) in filters.iter().enumerate() {
34 let params = decode_parms[i].as_ref();
35 result = apply_filter(filter, &result, params)?;
36 if let Some(p) = params {
37 result = apply_predictor(&result, p)?;
38 }
39 }
40 Ok(result)
41}
42
43fn extract_decode_parms(dict: &PdfDict, filter_count: usize) -> Vec<Option<PdfDict>> {
44 match dict.get("DecodeParms").or_else(|| dict.get("DP")) {
45 Some(PdfObject::Dict(d)) => {
46 let mut v = vec![None; filter_count];
47 if !v.is_empty() {
48 v[0] = Some(d.clone());
49 }
50 v
51 }
52 Some(PdfObject::Array(arr)) => arr
53 .iter()
54 .map(|obj| match obj {
55 PdfObject::Dict(d) => Some(d.clone()),
56 _ => None,
57 })
58 .chain(std::iter::repeat(None))
59 .take(filter_count)
60 .collect(),
61 _ => vec![None; filter_count],
62 }
63}
64
65fn apply_predictor(data: &[u8], params: &PdfDict) -> Result<Vec<u8>> {
66 let predictor = params.get_i64("Predictor").unwrap_or(1) as u32;
67 if predictor == 1 {
68 return Ok(data.to_vec());
69 }
70
71 let colors = params.get_i64("Colors").unwrap_or(1).max(1) as usize;
72 let bpc = params.get_i64("BitsPerComponent").unwrap_or(8).max(1) as usize;
73 let columns = params.get_i64("Columns").unwrap_or(1).max(1) as usize;
74
75 if predictor == 2 {
76 decode_tiff_predictor(data, colors, bpc, columns)
77 } else if predictor >= 10 {
78 decode_png_predictor(data, colors, bpc, columns)
79 } else {
80 Ok(data.to_vec())
81 }
82}
83
84fn decode_tiff_predictor(
85 data: &[u8],
86 colors: usize,
87 bpc: usize,
88 columns: usize,
89) -> Result<Vec<u8>> {
90 if bpc != 8 {
91 return Ok(data.to_vec());
92 }
93 let row_bytes = columns * colors;
94 let mut output = data.to_vec();
95 for row_start in (0..output.len()).step_by(row_bytes) {
96 let row_end = (row_start + row_bytes).min(output.len());
97 for i in (row_start + colors)..row_end {
98 output[i] = output[i].wrapping_add(output[i - colors]);
99 }
100 }
101 Ok(output)
102}
103
104fn decode_png_predictor(data: &[u8], colors: usize, bpc: usize, columns: usize) -> Result<Vec<u8>> {
105 let row_bytes = (colors * bpc * columns).div_ceil(8);
106 let bpp = (colors * bpc).div_ceil(8); let stride = 1 + row_bytes; if !data.len().is_multiple_of(stride) && !data.is_empty() {
110 tracing::debug!(
112 "PNG predictor: data length {} not multiple of stride {stride}",
113 data.len()
114 );
115 }
116
117 let num_rows = data.len().div_ceil(stride);
118 let mut output = Vec::with_capacity(num_rows * row_bytes);
119 let mut prev_row = vec![0u8; row_bytes];
120
121 let mut pos = 0;
122 while pos < data.len() {
123 if pos >= data.len() {
124 break;
125 }
126 let filter_type = data[pos];
127 pos += 1;
128
129 let available = (data.len() - pos).min(row_bytes);
130 let cur = &data[pos..pos + available];
131
132 let mut row = vec![0u8; row_bytes];
133 row[..available].copy_from_slice(cur);
134
135 match filter_type {
136 0 => {} 1 => {
138 for i in bpp..row_bytes {
140 row[i] = row[i].wrapping_add(row[i - bpp]);
141 }
142 }
143 2 => {
144 for i in 0..row_bytes {
146 row[i] = row[i].wrapping_add(prev_row[i]);
147 }
148 }
149 3 => {
150 for i in 0..row_bytes {
152 let left = if i >= bpp { row[i - bpp] as u16 } else { 0 };
153 let above = prev_row[i] as u16;
154 row[i] = row[i].wrapping_add(((left + above) / 2) as u8);
155 }
156 }
157 4 => {
158 for i in 0..row_bytes {
160 let left = if i >= bpp { row[i - bpp] as i32 } else { 0 };
161 let above = prev_row[i] as i32;
162 let upper_left = if i >= bpp {
163 prev_row[i - bpp] as i32
164 } else {
165 0
166 };
167 row[i] = row[i].wrapping_add(paeth(left, above, upper_left));
168 }
169 }
170 _ => {
171 tracing::debug!("PNG predictor: unknown filter type {filter_type}");
172 }
173 }
174
175 output.extend_from_slice(&row);
176 prev_row.copy_from_slice(&row);
177 pos += available;
178 }
179
180 Ok(output)
181}
182
183fn paeth(a: i32, b: i32, c: i32) -> u8 {
184 let p = a + b - c;
185 let pa = (p - a).abs();
186 let pb = (p - b).abs();
187 let pc = (p - c).abs();
188 if pa <= pb && pa <= pc {
189 a as u8
190 } else if pb <= pc {
191 b as u8
192 } else {
193 c as u8
194 }
195}
196
197fn apply_filter(filter: &PdfName, data: &[u8], params: Option<&PdfDict>) -> Result<Vec<u8>> {
198 match filter.as_str() {
199 "FlateDecode" | "Fl" => decode_flate(data),
200 "LZWDecode" | "LZW" => {
201 let early_change = params
203 .and_then(|p| p.get_i64("EarlyChange").ok())
204 .unwrap_or(1);
205 lzw_decode(data, early_change)
206 }
207 "ASCIIHexDecode" | "AHx" => decode_ascii_hex(data),
208 "ASCII85Decode" | "A85" => decode_ascii85(data),
209 "RunLengthDecode" | "RL" => decode_run_length(data),
210 "DCTDecode" | "DCT" => decode_dct(data),
211 "CCITTFaxDecode" | "CCF" => {
212 let ccitt_params = crate::ccitt::CcittParams::from_dict(params);
213 crate::ccitt::decode(data, &ccitt_params)
214 }
215 "JBIG2Decode" => {
216 let jbig2_params = crate::jbig2::Jbig2Params::from_dict(params);
217 crate::jbig2::decode(data, &jbig2_params)
218 }
219 "JPXDecode" => Ok(data.to_vec()),
224 other => Err(Error::UnsupportedFilter(other.to_string())),
225 }
226}
227
228fn lzw_decode(data: &[u8], early_change: i64) -> Result<Vec<u8>> {
236 const CLEAR: u32 = 256;
237 const EOD: u32 = 257;
238
239 let early: u32 = if early_change == 0 { 0 } else { 1 };
241
242 let mut table: Vec<Vec<u8>> = Vec::with_capacity(4096);
245 let reset = |t: &mut Vec<Vec<u8>>| {
246 t.clear();
247 for i in 0..256u32 {
248 t.push(vec![i as u8]);
249 }
250 t.push(Vec::new()); t.push(Vec::new()); };
253 reset(&mut table);
254
255 let mut width: u32 = 9;
256 let mut bit_pos: usize = 0;
257 let total_bits = data.len() * 8;
258
259 let read_code = |bit_pos: &mut usize, width: u32| -> Option<u32> {
261 if *bit_pos + width as usize > total_bits {
262 return None;
263 }
264 let mut code: u32 = 0;
265 for _ in 0..width {
266 let byte = data[*bit_pos / 8];
267 let bit = (byte >> (7 - (*bit_pos % 8))) & 1;
268 code = (code << 1) | bit as u32;
269 *bit_pos += 1;
270 }
271 Some(code)
272 };
273
274 let mut out: Vec<u8> = Vec::new();
275 let mut prev: Option<u32> = None;
276
277 while let Some(code) = read_code(&mut bit_pos, width) {
279 if code == EOD {
280 break;
281 }
282 if code == CLEAR {
283 reset(&mut table);
284 width = 9;
285 prev = None;
286 continue;
287 }
288
289 let entry: Vec<u8> = if (code as usize) < table.len() {
291 table[code as usize].clone()
292 } else if code as usize == table.len() {
293 match prev {
295 Some(p) => {
296 let mut e = table[p as usize].clone();
297 e.push(table[p as usize][0]);
298 e
299 }
300 None => {
301 return Err(Error::StreamDecode(format!(
302 "LZWDecode: code {code} before any literal"
303 )))
304 }
305 }
306 } else {
307 return Err(Error::StreamDecode(format!(
308 "LZWDecode: invalid code {code} (table size {})",
309 table.len()
310 )));
311 };
312
313 out.extend_from_slice(&entry);
314 if out.len() > MAX_DECODED_OUTPUT {
315 return Err(Error::StreamDecode(
316 "LZWDecode: output exceeds decompression limit".into(),
317 ));
318 }
319
320 if let Some(p) = prev {
323 let mut new_entry = table[p as usize].clone();
324 new_entry.push(entry[0]);
325 table.push(new_entry);
326 }
327 prev = Some(code);
328
329 let next_code = table.len() as u32;
336 if width < 12 && next_code + early >= (1u32 << width) {
337 width += 1;
338 }
339 }
340
341 Ok(out)
342}
343
344enum InflateOutcome {
347 Complete(Vec<u8>),
348 Failed(Vec<u8>, String),
349}
350
351fn inflate_chunked(mut reader: impl std::io::Read) -> Result<InflateOutcome> {
356 let mut out = Vec::new();
357 let mut buf = [0u8; 16 * 1024];
358 loop {
359 match reader.read(&mut buf) {
360 Ok(0) => return Ok(InflateOutcome::Complete(out)),
361 Ok(n) => {
362 if out.len() + n > MAX_DECODED_OUTPUT {
363 return Err(Error::StreamDecode(
364 "FlateDecode: output exceeds decompression limit".into(),
365 ));
366 }
367 out.extend_from_slice(&buf[..n]);
368 }
369 Err(e) => return Ok(InflateOutcome::Failed(out, e.to_string())),
370 }
371 }
372}
373
374fn decode_flate(data: &[u8]) -> Result<Vec<u8>> {
378 use flate2::read::{DeflateDecoder, ZlibDecoder};
379
380 if data.is_empty() {
382 return Ok(Vec::new());
383 }
384
385 let plausible_zlib = |i: usize| {
388 data.len() >= i + 2
389 && data[i] & 0x0f == 8
390 && ((data[i] as u32) << 8 | data[i + 1] as u32).is_multiple_of(31)
391 };
392
393 let mut zlib_err: Option<String> = None;
394 if plausible_zlib(0) {
395 match inflate_chunked(ZlibDecoder::new(data))? {
396 InflateOutcome::Complete(out) => return Ok(out),
397 InflateOutcome::Failed(partial, err) if !partial.is_empty() => {
398 tracing::warn!(
399 "FlateDecode: zlib stream failed after {} bytes ({err}); keeping partial output",
400 partial.len()
401 );
402 return Ok(partial);
403 }
404 InflateOutcome::Failed(_, err) => zlib_err = Some(err),
405 }
406 }
407
408 const MAX_HEADER_SCAN: usize = 64;
411 if let Some(k) = (1..data.len().min(MAX_HEADER_SCAN)).find(|&k| plausible_zlib(k)) {
412 match inflate_chunked(ZlibDecoder::new(&data[k..]))? {
413 InflateOutcome::Complete(out) => {
414 tracing::warn!("FlateDecode: skipped {k} bytes of leading garbage");
415 return Ok(out);
416 }
417 InflateOutcome::Failed(partial, err) if !partial.is_empty() => {
418 tracing::warn!(
419 "FlateDecode: zlib stream at offset {k} failed ({err}); keeping {} partial bytes",
420 partial.len()
421 );
422 return Ok(partial);
423 }
424 InflateOutcome::Failed(..) => {}
425 }
426 }
427
428 match inflate_chunked(DeflateDecoder::new(data))? {
430 InflateOutcome::Complete(out) => {
431 tracing::warn!("FlateDecode: decoded as raw deflate (missing zlib header)");
432 Ok(out)
433 }
434 InflateOutcome::Failed(partial, err) if !partial.is_empty() => {
435 tracing::warn!(
436 "FlateDecode: raw deflate failed ({err}); keeping {} partial bytes",
437 partial.len()
438 );
439 Ok(partial)
440 }
441 InflateOutcome::Failed(_, err) => Err(Error::StreamDecode(format!(
442 "FlateDecode: {}",
443 zlib_err.unwrap_or(err)
444 ))),
445 }
446}
447
448fn decode_ascii_hex(data: &[u8]) -> Result<Vec<u8>> {
452 let mut output = Vec::with_capacity(data.len() / 2);
453 let mut high: Option<u8> = None;
454 let mut stray = 0usize;
455
456 for &b in data {
457 if b == b'>' {
458 break; }
460 if b.is_ascii_whitespace() || b == 0 {
461 continue;
462 }
463 let nibble = match b {
464 b'0'..=b'9' => b - b'0',
465 b'a'..=b'f' => b - b'a' + 10,
466 b'A'..=b'F' => b - b'A' + 10,
467 _ => {
468 stray += 1;
469 continue;
470 }
471 };
472
473 match high {
474 None => high = Some(nibble),
475 Some(h) => {
476 output.push((h << 4) | nibble);
477 high = None;
478 }
479 }
480 }
481
482 if let Some(h) = high {
483 output.push(h << 4);
484 }
485 if stray > 0 {
486 tracing::warn!("ASCIIHexDecode: ignored {stray} invalid byte(s)");
487 }
488
489 Ok(output)
490}
491
492fn decode_ascii85(data: &[u8]) -> Result<Vec<u8>> {
496 let mut output = Vec::new();
497 let mut tuple: u64 = 0;
500 let mut count = 0usize;
501 let mut stray = 0usize;
502
503 for &b in data {
504 if b == b'~' {
505 break; }
507 if b.is_ascii_whitespace() || b == 0 {
508 continue;
509 }
510
511 if b == b'z' && count == 0 {
512 output.extend_from_slice(&[0, 0, 0, 0]);
513 continue;
514 }
515
516 if !(b'!'..=b'u').contains(&b) {
517 stray += 1;
518 continue;
519 }
520
521 tuple = tuple * 85 + (b - b'!') as u64;
522 count += 1;
523
524 if count == 5 {
525 let t = (tuple & 0xFFFF_FFFF) as u32;
526 output.extend_from_slice(&t.to_be_bytes());
527 tuple = 0;
528 count = 0;
529 }
530 }
531
532 if count > 1 {
534 for _ in count..5 {
535 tuple = tuple * 85 + 84; }
537 let t = (tuple & 0xFFFF_FFFF) as u32;
538 for i in 0..(count - 1) {
539 output.push((t >> (24 - i * 8)) as u8);
540 }
541 }
542 if stray > 0 {
543 tracing::warn!("ASCII85Decode: ignored {stray} invalid byte(s)");
544 }
545
546 Ok(output)
547}
548
549fn decode_dct(data: &[u8]) -> Result<Vec<u8>> {
550 use zune_jpeg::JpegDecoder;
551
552 if jpeg_is_adobe_ycck(data) {
559 use zune_jpeg::zune_core::colorspace::ColorSpace;
560 use zune_jpeg::zune_core::options::DecoderOptions;
561 let opts = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::YCCK);
562 let mut decoder = JpegDecoder::new_with_options(std::io::Cursor::new(data), opts);
563 match decoder.decode() {
564 Ok(ycck) if decoder.output_colorspace() == Some(ColorSpace::YCCK) => {
565 return Ok(ycck_to_rgb(&ycck));
566 }
567 _ => {}
570 }
571 }
572
573 let mut decoder = JpegDecoder::new(std::io::Cursor::new(data));
574 decoder
575 .decode()
576 .map_err(|e| Error::StreamDecode(format!("DCTDecode: {e}")))
577}
578
579fn ycck_to_rgb(ycck: &[u8]) -> Vec<u8> {
590 let mut out = Vec::with_capacity(ycck.len() / 4 * 3);
591 for px in ycck.chunks_exact(4) {
592 let (y, cb, cr) = (px[0] as f64, px[1] as f64, px[2] as f64);
593 let rp = (y + 1.402 * (cr - 128.0)).clamp(0.0, 255.0);
595 let gp = (y - 0.344_136 * (cb - 128.0) - 0.714_136 * (cr - 128.0)).clamp(0.0, 255.0);
596 let bp = (y + 1.772 * (cb - 128.0)).clamp(0.0, 255.0);
597 let (r, g, b) = zpdf_color::cmyk_to_rgb(
598 1.0 - rp / 255.0,
599 1.0 - gp / 255.0,
600 1.0 - bp / 255.0,
601 px[3] as f64 / 255.0,
602 );
603 out.push((r * 255.0).round() as u8);
604 out.push((g * 255.0).round() as u8);
605 out.push((b * 255.0).round() as u8);
606 }
607 out
608}
609
610fn jpeg_is_adobe_ycck(data: &[u8]) -> bool {
613 let mut adobe_ycck = false;
614 let mut four_components = false;
615 let mut i = 2; while i + 3 < data.len() {
617 if data[i] != 0xFF {
618 i += 1;
619 continue;
620 }
621 let marker = data[i + 1];
622 if marker == 0xFF || marker == 0x01 || (0xD0..=0xD9).contains(&marker) {
624 i += 2;
625 continue;
626 }
627 let seg_len = ((data[i + 2] as usize) << 8) | data[i + 3] as usize;
628 if seg_len < 2 {
629 break;
630 }
631 let payload_start = i + 4;
632 let payload_end = i + 2 + seg_len;
633 if payload_end > data.len() {
634 break;
635 }
636 let payload = &data[payload_start..payload_end];
637 match marker {
638 0xEE => {
640 if payload.len() >= 12 && &payload[0..5] == b"Adobe" {
641 adobe_ycck = payload[11] == 2;
642 }
643 }
644 0xC0..=0xCF if marker != 0xC4 && marker != 0xC8 && marker != 0xCC => {
646 if payload.len() >= 6 {
648 four_components = payload[5] == 4;
649 }
650 }
651 0xDA => break,
653 _ => {}
654 }
655 i = payload_end;
656 }
657 adobe_ycck && four_components
658}
659
660fn decode_run_length(data: &[u8]) -> Result<Vec<u8>> {
661 let mut output = Vec::new();
662 let mut i = 0;
663
664 while i < data.len() {
665 let length_byte = data[i];
666 i += 1;
667
668 if length_byte == 128 {
669 break; } else if length_byte < 128 {
671 let count = length_byte as usize + 1;
673 if i + count > data.len() {
674 return Err(Error::StreamDecode("RunLengthDecode: truncated".into()));
675 }
676 if output.len() + count > MAX_DECODED_OUTPUT {
677 return Err(Error::StreamDecode(
678 "RunLengthDecode: output exceeds decompression limit".into(),
679 ));
680 }
681 output.extend_from_slice(&data[i..i + count]);
682 i += count;
683 } else {
684 let count = 257 - length_byte as usize;
686 if i >= data.len() {
687 return Err(Error::StreamDecode("RunLengthDecode: truncated".into()));
688 }
689 if output.len() + count > MAX_DECODED_OUTPUT {
690 return Err(Error::StreamDecode(
691 "RunLengthDecode: output exceeds decompression limit".into(),
692 ));
693 }
694 let byte = data[i];
695 i += 1;
696 output.resize(output.len() + count, byte);
697 }
698 }
699
700 Ok(output)
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn ycck_white_decodes_white_not_black() {
709 let rgb = ycck_to_rgb(&[255, 128, 128, 0]);
711 assert_eq!(rgb, vec![255, 255, 255], "Adobe YCCK white must stay white");
712 }
713
714 #[test]
715 fn ycck_full_black_ink_decodes_near_black() {
716 let rgb = ycck_to_rgb(&[255, 128, 128, 255]);
719 assert_eq!(rgb, vec![44, 46, 53]);
720 }
721
722 #[test]
723 fn ycck_neutral_gray_via_polynomial() {
724 let rgb = ycck_to_rgb(&[255, 128, 128, 128]);
727 assert_eq!(rgb, vec![154, 156, 159]);
728 }
729
730 #[test]
731 fn ycck_colored_pixel_via_polynomial() {
732 let rgb = ycck_to_rgb(&[200, 100, 150, 50]);
734 assert_eq!(rgb, vec![198, 165, 131]);
735 }
736
737 #[test]
738 fn adobe_ycck_detection() {
739 let mut j = vec![0xFF, 0xD8];
741 j.extend_from_slice(&[0xFF, 0xEE, 0x00, 0x0E]);
743 j.extend_from_slice(b"Adobe");
744 j.extend_from_slice(&[0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x02]); j.extend_from_slice(&[0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x04]);
747 j.extend_from_slice(&[1, 0x11, 0, 2, 0x11, 0, 3, 0x11, 0, 4, 0x11, 0]);
748 j.extend_from_slice(&[0xFF, 0xDA, 0x00, 0x02]); assert!(jpeg_is_adobe_ycck(&j));
750
751 let mut j0 = j.clone();
753 j0[17] = 0;
755 assert!(!jpeg_is_adobe_ycck(&j0));
756 }
757
758 #[test]
759 fn flate_roundtrip() {
760 use flate2::write::ZlibEncoder;
761 use flate2::Compression;
762 use std::io::Write;
763
764 let original = b"Hello, zpdf! This is a test of FlateDecode.";
765 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
766 encoder.write_all(original).unwrap();
767 let compressed = encoder.finish().unwrap();
768
769 let decoded = decode_flate(&compressed).unwrap();
770 assert_eq!(decoded, original);
771 }
772
773 #[test]
774 fn flate_partial_salvage_on_truncation() {
775 use flate2::write::ZlibEncoder;
776 use flate2::Compression;
777 use std::io::Write;
778
779 let mut state = 0x2545F491u64;
782 let original: Vec<u8> = (0..64 * 1024)
783 .map(|_| {
784 state = state
785 .wrapping_mul(6364136223846793005)
786 .wrapping_add(1442695040888963407);
787 (state >> 33) as u8
788 })
789 .collect();
790 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
791 encoder.write_all(&original).unwrap();
792 let compressed = encoder.finish().unwrap();
793
794 let truncated = &compressed[..compressed.len() / 2];
795 let decoded = decode_flate(truncated).unwrap();
796 assert!(!decoded.is_empty(), "partial output must be salvaged");
797 assert!(decoded.len() < original.len());
798 assert_eq!(
799 &original[..decoded.len()],
800 &decoded[..],
801 "salvaged bytes are a prefix"
802 );
803 }
804
805 #[test]
806 fn flate_raw_deflate_fallback() {
807 use flate2::write::DeflateEncoder;
808 use flate2::Compression;
809 use std::io::Write;
810
811 let original = b"raw deflate stream without a zlib wrapper".to_vec();
812 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
813 encoder.write_all(&original).unwrap();
814 let compressed = encoder.finish().unwrap();
815
816 let decoded = decode_flate(&compressed).unwrap();
817 assert_eq!(decoded, original);
818 }
819
820 #[test]
821 fn flate_skips_leading_garbage() {
822 use flate2::write::ZlibEncoder;
823 use flate2::Compression;
824 use std::io::Write;
825
826 let original = b"zlib data behind a garbage prefix".to_vec();
827 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
828 encoder.write_all(&original).unwrap();
829 let compressed = encoder.finish().unwrap();
830
831 let mut data = b"\r\n\xff".to_vec();
833 data.extend_from_slice(&compressed);
834 let decoded = decode_flate(&data).unwrap();
835 assert_eq!(decoded, original);
836 }
837
838 #[test]
839 fn flate_empty_input_is_empty_output() {
840 assert_eq!(decode_flate(&[]).unwrap(), Vec::<u8>::new());
841 }
842
843 #[test]
844 fn flate_garbage_still_errors() {
845 assert!(decode_flate(b"this is not compressed data at all....").is_err());
847 }
848
849 #[test]
850 fn ascii_hex() {
851 let decoded = decode_ascii_hex(b"48 65 6C 6C 6F>").unwrap();
852 assert_eq!(decoded, b"Hello");
853 }
854
855 #[test]
856 fn ascii_hex_tolerates_stray_bytes_and_data_after_eod() {
857 let decoded = decode_ascii_hex(b"48 65 x6C!6C 6F> trailing garbage \xff").unwrap();
859 assert_eq!(decoded, b"Hello");
860 }
861
862 #[test]
863 fn ascii85_basic() {
864 let decoded = decode_ascii85(b"9jqo^~>").unwrap();
866 assert_eq!(decoded, b"Man ");
867 }
868
869 #[test]
870 fn ascii85_ignores_bytes_after_eod() {
871 let decoded = decode_ascii85(b"9jqo^~> stray bytes \xff\xfe after EOD").unwrap();
872 assert_eq!(decoded, b"Man ");
873 }
874
875 #[test]
876 fn ascii85_skips_stray_bytes_and_whitespace() {
877 let decoded = decode_ascii85(b"9j\x00qo\xff ^~>").unwrap();
880 assert_eq!(decoded, b"Man ");
881 }
882
883 #[test]
884 fn ascii85_overflowing_group_does_not_panic() {
885 assert!(decode_ascii85(b"uuuuu~>").is_ok());
888 }
889
890 #[test]
891 fn run_length_literal_and_repeat() {
892 let data = [1, 0x41, 0x42, 254, 0x43, 128];
894 let decoded = decode_run_length(&data).unwrap();
895 assert_eq!(decoded, vec![0x41, 0x42, 0x43, 0x43, 0x43]);
896 }
897
898 #[test]
899 fn png_predictor_none() {
900 let data = [0, 0x41, 0x42];
903 let result = decode_png_predictor(&data, 1, 8, 2).unwrap();
904 assert_eq!(result, vec![0x41, 0x42]);
905 }
906
907 #[test]
908 fn png_predictor_sub() {
909 let data = [1, 10, 5, 3];
912 let result = decode_png_predictor(&data, 1, 8, 3).unwrap();
913 assert_eq!(result, vec![10, 15, 18]);
914 }
915
916 #[test]
917 fn png_predictor_up() {
918 let data = [0, 10, 20, 2, 5, 3];
922 let result = decode_png_predictor(&data, 1, 8, 2).unwrap();
923 assert_eq!(result, vec![10, 20, 15, 23]);
924 }
925
926 #[test]
927 fn png_predictor_paeth() {
928 let data = [0, 10, 20, 30, 4, 5, 7, 3];
935 let result = decode_png_predictor(&data, 1, 8, 3).unwrap();
936 assert_eq!(result, vec![10, 20, 30, 15, 27, 33]);
937 }
938
939 #[test]
940 fn tiff_predictor_basic() {
941 let data = [100, 150, 200, 10, 20, 30];
944 let result = decode_tiff_predictor(&data, 3, 8, 2).unwrap();
945 assert_eq!(result, vec![100, 150, 200, 110, 170, 230]);
946 }
947
948 #[test]
951 fn lzw_canonical_vector() {
952 let data = [0x80, 0x0B, 0x60, 0x50, 0x22, 0x0C, 0x0C, 0x85, 0x01];
956 let decoded = lzw_decode(&data, 1).unwrap();
957 assert_eq!(decoded, b"-----A---B");
958 }
959
960 #[test]
961 fn lzw_via_apply_filter_default_early_change() {
962 let data = [0x80, 0x0B, 0x60, 0x50, 0x22, 0x0C, 0x0C, 0x85, 0x01];
963 let name = PdfName::new("LZWDecode");
964 let out = apply_filter(&name, &data, None).unwrap();
965 assert_eq!(out, b"-----A---B");
966 }
967
968 #[test]
969 fn lzw_stops_at_end_without_eod() {
970 let data = [0x80, 0x0B, 0x60, 0x50];
972 let out = lzw_decode(&data, 1).unwrap();
973 assert!(out.starts_with(b"-"));
974 }
975
976 #[test]
977 fn lzw_empty_input() {
978 assert_eq!(lzw_decode(&[], 1).unwrap(), Vec::<u8>::new());
979 }
980
981 fn weezl_encode(data: &[u8], early_change: i64) -> Vec<u8> {
987 use weezl::{encode::Encoder, BitOrder};
988 let mut enc = if early_change == 0 {
989 Encoder::new(BitOrder::Msb, 8)
990 } else {
991 Encoder::with_tiff_size_switch(BitOrder::Msb, 8)
992 };
993 enc.encode(data).expect("weezl encode")
994 }
995
996 #[test]
997 fn lzw_roundtrip_against_weezl() {
998 for ec in [1i64, 0] {
1001 for &len in &[0usize, 1, 300, 600, 1200, 3000, 5000, 9000] {
1002 let input: Vec<u8> = (0..len).map(|i| ((i * 7 + i / 11) % 251) as u8).collect();
1004 let encoded = weezl_encode(&input, ec);
1005 let decoded = lzw_decode(&encoded, ec).unwrap();
1006 assert_eq!(decoded, input, "ec={ec} len={len}");
1007 }
1008 }
1009 }
1010
1011 #[test]
1012 fn lzw_single_byte_run_roundtrip_against_weezl() {
1013 let input = vec![b'A'; 5000];
1015 for ec in [1i64, 0] {
1016 let encoded = weezl_encode(&input, ec);
1017 assert_eq!(lzw_decode(&encoded, ec).unwrap(), input, "ec={ec}");
1018 }
1019 }
1020}