1use crate::error::PdfError;
8use crate::objects::PdfDict;
9
10pub const MAX_DECODED_STREAM_BYTES: usize = 512 * 1024 * 1024;
41
42#[derive(Debug, Clone, Copy)]
51pub struct DecodeBudget {
52 limit: usize,
53}
54
55impl Default for DecodeBudget {
56 fn default() -> Self {
57 Self {
58 limit: MAX_DECODED_STREAM_BYTES,
59 }
60 }
61}
62
63impl DecodeBudget {
64 pub const fn new(limit: usize) -> Self {
66 Self { limit }
67 }
68
69 pub const fn limit(&self) -> usize {
71 self.limit
72 }
73
74 pub fn for_stream(dict: &PdfDict) -> Self {
95 let declared = declared_image_bytes(dict)
96 .or_else(|| declared_embedded_file_bytes(dict))
97 .unwrap_or(0);
98 Self {
99 limit: declared.max(MAX_DECODED_STREAM_BYTES),
100 }
101 }
102
103 fn check(&self, produced: usize) -> Result<(), PdfError> {
109 if produced > self.limit {
110 return Err(PdfError::DecompressionError(format!(
111 "decompressed stream exceeds the {} byte limit",
112 self.limit
113 )));
114 }
115 Ok(())
116 }
117
118 fn reserve_hint(&self, want: usize) -> usize {
124 want.min(self.limit)
125 }
126}
127
128fn declared_image_bytes(dict: &PdfDict) -> Option<usize> {
135 use stet_graphics::image_limits::{
136 validate_bits_per_component, validate_image_dimension, validate_image_size,
137 };
138
139 let width = validate_image_dimension(dict.get_int(b"Width"))?;
140 let height = validate_image_dimension(dict.get_int(b"Height"))?;
141 let pixels = validate_image_size(width, height)?;
142 let bpc = validate_bits_per_component(dict.get_int(b"BitsPerComponent"))? as usize;
143
144 let components = match dict.get_name(b"ColorSpace") {
145 Some(b"DeviceGray" | b"G" | b"CalGray") => 1,
146 Some(b"DeviceRGB" | b"RGB" | b"CalRGB" | b"Lab") => 3,
147 _ => 4,
148 };
149
150 let row_bits = (width as usize)
157 .saturating_mul(components)
158 .saturating_mul(bpc);
159 let row_bytes = row_bits.div_ceil(8);
160 Some(row_bytes.saturating_mul(pixels / width as usize))
161}
162
163fn declared_embedded_file_bytes(dict: &PdfDict) -> Option<usize> {
165 let size = dict.get_dict(b"Params")?.get_int(b"Size")?;
166 usize::try_from(size).ok()
167}
168
169#[derive(Debug, Clone, PartialEq)]
171pub enum Filter {
172 FlateDecode,
173 LZWDecode,
174 ASCIIHexDecode,
175 ASCII85Decode,
176 RunLengthDecode,
177 DCTDecode,
178 CCITTFaxDecode,
179 JPXDecode,
180 JBIG2Decode,
181}
182
183pub fn parse_filters(
188 dict: &PdfDict,
189 resolver: Option<&crate::resolver::Resolver>,
190) -> Result<(Vec<Filter>, Vec<Option<PdfDict>>), PdfError> {
191 let filter_obj = match dict.get(b"Filter") {
192 Some(obj) => obj,
193 None => return Ok((Vec::new(), Vec::new())),
194 };
195
196 let resolved_filter;
198 let filter_obj = if let crate::objects::PdfObj::Ref(_, _) = filter_obj {
199 if let Some(r) = resolver {
200 resolved_filter = r.deref(filter_obj).unwrap_or_else(|_| filter_obj.clone());
201 &resolved_filter
202 } else {
203 filter_obj
204 }
205 } else {
206 filter_obj
207 };
208
209 let filter_names: Vec<&[u8]> = match filter_obj {
210 crate::objects::PdfObj::Name(n) => vec![n.as_slice()],
211 crate::objects::PdfObj::Array(arr) => {
212 arr.iter()
214 .filter_map(|o| {
215 if let Some(n) = o.as_name() {
216 return Some(n);
217 }
218 None
219 })
220 .collect()
221 }
222 _ => return Ok((Vec::new(), Vec::new())),
223 };
224
225 let mut filters = Vec::new();
226 for name in &filter_names {
227 filters.push(filter_from_name(name)?);
228 }
229
230 let dp_obj = dict.get(b"DecodeParms");
232 let resolved_dp;
233 let dp_obj = match dp_obj {
234 Some(crate::objects::PdfObj::Ref(_, _)) if resolver.is_some() => {
235 resolved_dp = resolver.unwrap().deref(dp_obj.unwrap()).ok();
236 resolved_dp.as_ref()
237 }
238 other => other,
239 };
240
241 let parms = match dp_obj {
242 Some(crate::objects::PdfObj::Dict(d)) => vec![Some(d.clone())],
243 Some(crate::objects::PdfObj::Array(arr)) => arr
244 .iter()
245 .map(|o| match o {
246 crate::objects::PdfObj::Dict(d) => Some(d.clone()),
247 crate::objects::PdfObj::Ref(_, _) if resolver.is_some() => resolver
248 .unwrap()
249 .deref(o)
250 .ok()
251 .and_then(|r| r.as_dict().cloned()),
252 _ => None,
253 })
254 .collect(),
255 _ => vec![None; filters.len()],
256 };
257
258 let mut parms = parms;
260 while parms.len() < filters.len() {
261 parms.push(None);
262 }
263
264 for (i, filter) in filters.iter().enumerate() {
271 if *filter != Filter::CCITTFaxDecode {
272 continue;
273 }
274 let dp = parms[i].get_or_insert_with(PdfDict::new);
275 if dp.get_int(b"Columns").is_none()
276 && let Some(w) = dict.get_int(b"Width")
277 {
278 dp.insert(b"Columns".to_vec(), crate::objects::PdfObj::Int(w));
279 }
280 if dp.get_int(b"Rows").is_none()
281 && let Some(h) = dict.get_int(b"Height")
282 {
283 dp.insert(b"Rows".to_vec(), crate::objects::PdfObj::Int(h));
284 }
285 }
286
287 Ok((filters, parms))
288}
289
290fn filter_from_name(name: &[u8]) -> Result<Filter, PdfError> {
291 match name {
292 b"FlateDecode" | b"Fl" => Ok(Filter::FlateDecode),
293 b"LZWDecode" | b"LZW" => Ok(Filter::LZWDecode),
294 b"ASCIIHexDecode" | b"AHx" => Ok(Filter::ASCIIHexDecode),
295 b"ASCII85Decode" | b"A85" => Ok(Filter::ASCII85Decode),
296 b"RunLengthDecode" | b"RL" => Ok(Filter::RunLengthDecode),
297 b"DCTDecode" | b"DCT" => Ok(Filter::DCTDecode),
298 b"CCITTFaxDecode" | b"CCF" => Ok(Filter::CCITTFaxDecode),
299 b"JPXDecode" | b"JPX" => Ok(Filter::JPXDecode),
300 b"JBIG2Decode" | b"JBIG2" => Ok(Filter::JBIG2Decode),
301 _ if name.starts_with(b"Flate") => Ok(Filter::FlateDecode),
303 _ if name.starts_with(b"LZW") => Ok(Filter::LZWDecode),
304 _ if name.starts_with(b"ASCIIHex") => Ok(Filter::ASCIIHexDecode),
305 _ if name.starts_with(b"ASCII85") => Ok(Filter::ASCII85Decode),
306 _ if name.starts_with(b"RunLength") => Ok(Filter::RunLengthDecode),
307 _ if name.starts_with(b"CCITT") => Ok(Filter::CCITTFaxDecode),
308 _ if name.starts_with(b"JPX") => Ok(Filter::JPXDecode),
309 _ if name.starts_with(b"JBIG2") => Ok(Filter::JBIG2Decode),
310 _ => Err(PdfError::UnsupportedFilter(
311 String::from_utf8_lossy(name).into(),
312 )),
313 }
314}
315
316pub fn decode_stream(
323 raw_data: &[u8],
324 filters: &[Filter],
325 decode_parms: &[Option<PdfDict>],
326 jbig2_globals: Option<&[u8]>,
327) -> Result<Vec<u8>, PdfError> {
328 decode_stream_bounded(
329 raw_data,
330 filters,
331 decode_parms,
332 jbig2_globals,
333 DecodeBudget::default(),
334 )
335}
336
337pub fn decode_stream_bounded(
344 raw_data: &[u8],
345 filters: &[Filter],
346 decode_parms: &[Option<PdfDict>],
347 jbig2_globals: Option<&[u8]>,
348 budget: DecodeBudget,
349) -> Result<Vec<u8>, PdfError> {
350 let mut data = raw_data.to_vec();
351
352 for (i, filter) in filters.iter().enumerate() {
353 let parms = decode_parms.get(i).and_then(|p| p.as_ref());
354 data = match filter {
355 Filter::FlateDecode => decode_flate(&data, parms, budget)?,
356 Filter::LZWDecode => decode_lzw(&data, parms, budget)?,
357 Filter::ASCIIHexDecode => decode_ascii_hex(&data)?,
358 Filter::ASCII85Decode => decode_ascii85(&data)?,
359 Filter::RunLengthDecode => decode_run_length(&data, budget)?,
360 Filter::DCTDecode => decode_dct(&data)?,
361 Filter::CCITTFaxDecode => decode_ccittfax(&data, parms)?,
362 #[cfg(feature = "jpx")]
363 Filter::JPXDecode => decode_jpx(&data)?,
364 #[cfg(not(feature = "jpx"))]
365 Filter::JPXDecode => {
366 return Err(PdfError::UnsupportedFilter("JPXDecode (disabled)".into()));
367 }
368 Filter::JBIG2Decode => decode_jbig2(&data, jbig2_globals)?,
369 };
370 budget.check(data.len())?;
374 }
375
376 Ok(data)
377}
378
379fn decode_flate(
381 data: &[u8],
382 parms: Option<&PdfDict>,
383 budget: DecodeBudget,
384) -> Result<Vec<u8>, PdfError> {
385 let (zlib_output, zlib_clean, _) = decode_flate_inner(data, true, budget);
388 let output = if zlib_clean {
389 zlib_output?
390 } else {
391 let zlib_data = zlib_output.unwrap_or_default();
397 if data.len() > 2 {
398 let (raw_output, _, _) = decode_flate_inner(&data[2..], false, budget);
399 let raw_data = raw_output.unwrap_or_default();
400 if raw_data.len() > zlib_data.len()
401 && raw_data[..zlib_data.len()] == zlib_data[..]
402 && looks_like_valid_continuation(&raw_data, zlib_data.len())
403 {
404 raw_data
408 } else if !zlib_data.is_empty() {
409 zlib_data
410 } else if !raw_data.is_empty() {
411 raw_data
412 } else {
413 return Err(PdfError::DecompressionError(
414 "flate: decompression failed".into(),
415 ));
416 }
417 } else if !zlib_data.is_empty() {
418 zlib_data
419 } else {
420 return Err(PdfError::DecompressionError(
421 "flate: decompression failed".into(),
422 ));
423 }
424 };
425
426 if let Some(parms) = parms {
428 let predictor = parms.get_int(b"Predictor").unwrap_or(1);
429 if predictor > 1 {
430 return apply_predictor(&output, parms, predictor);
431 }
432 }
433
434 Ok(output)
435}
436
437fn looks_like_valid_continuation(data: &[u8], start: usize) -> bool {
442 if start >= data.len() {
443 return false;
444 }
445 let sample = &data[start..data.len().min(start + 64)];
447 let printable = sample
448 .iter()
449 .filter(|&&b| b.is_ascii_graphic() || b.is_ascii_whitespace())
450 .count();
451 printable * 5 >= sample.len() * 4
453}
454
455fn decode_flate_inner(
466 data: &[u8],
467 zlib: bool,
468 budget: DecodeBudget,
469) -> (Result<Vec<u8>, PdfError>, bool, usize) {
470 use flate2::Decompress;
471
472 let mut decompressor = Decompress::new(zlib);
473 let mut output = Vec::with_capacity(budget.reserve_hint(data.len().saturating_mul(3)));
474 let mut buf = [0u8; 8192];
475 let mut input_offset = 0;
476
477 loop {
478 let before_in = decompressor.total_in() as usize;
479 let before_out = decompressor.total_out() as usize;
480 let result = decompressor.decompress(
481 &data[input_offset..],
482 &mut buf,
483 flate2::FlushDecompress::None,
484 );
485
486 let consumed = decompressor.total_in() as usize - before_in;
487 let produced = decompressor.total_out() as usize - before_out;
488 input_offset += consumed;
489 output.extend_from_slice(&buf[..produced]);
490
491 if let Err(e) = budget.check(output.len()) {
492 return (Err(e), true, input_offset);
493 }
494
495 match result {
496 Ok(status) => match status {
497 flate2::Status::StreamEnd => return (Ok(output), true, input_offset),
498 flate2::Status::Ok | flate2::Status::BufError => {
499 if consumed == 0 && produced == 0 {
500 return (Ok(output), true, input_offset);
501 }
502 }
503 },
504 Err(_) if !output.is_empty() => {
505 return (Ok(output), false, input_offset);
507 }
508 Err(e) => {
509 return (
510 Err(PdfError::DecompressionError(format!("flate: {e}"))),
511 false,
512 input_offset,
513 );
514 }
515 }
516 }
517}
518
519fn decode_lzw(
524 data: &[u8],
525 parms: Option<&PdfDict>,
526 budget: DecodeBudget,
527) -> Result<Vec<u8>, PdfError> {
528 let early_change = parms.and_then(|p| p.get_int(b"EarlyChange")).unwrap_or(1) != 0;
529
530 let output = lzw_decode(data, early_change, budget)?;
531
532 if let Some(parms) = parms {
534 let predictor = parms.get_int(b"Predictor").unwrap_or(1);
535 if predictor > 1 {
536 return apply_predictor(&output, parms, predictor);
537 }
538 }
539
540 Ok(output)
541}
542
543const LZW_CLEAR_TABLE: usize = 256;
546const LZW_EOD: usize = 257;
547const LZW_MAX_ENTRIES: usize = 4096;
548const LZW_INITIAL_SIZE: usize = 258;
549
550fn lzw_decode(data: &[u8], early_change: bool, budget: DecodeBudget) -> Result<Vec<u8>, PdfError> {
557 let failed = || PdfError::DecompressionError("lzw: decode failed".into());
558
559 let mut table = LzwTable::new(early_change);
560 let mut bit_size = table.code_length();
561 let mut reader = LzwBitReader::new(data);
562 let mut decoded = Vec::new();
563 let mut prev: Option<usize> = None;
564
565 loop {
566 let next = match reader.read(bit_size) {
567 Some(code) => code as usize,
568 None => {
569 return Ok(decoded);
571 }
572 };
573
574 match next {
575 LZW_CLEAR_TABLE => {
576 table.clear();
577 prev = None;
578 bit_size = table.code_length();
579 }
580 LZW_EOD => return Ok(decoded),
581 new => {
582 if new > table.size() {
583 if decoded.is_empty() {
585 return Err(failed());
586 }
587 return Ok(decoded);
588 }
589
590 if new < table.size() {
591 let entry = table.get(new).ok_or_else(failed)?;
592 let first_byte = entry[0];
593 decoded.extend_from_slice(entry);
594
595 if let Some(prev_code) = prev {
596 table.register(prev_code, first_byte);
597 }
598 } else if new == table.size() && prev.is_some() {
599 let prev_code = prev.unwrap();
601 let prev_entry = table.get(prev_code).ok_or_else(failed)?;
602 let first_byte = prev_entry[0];
603
604 let new_entry = table.register(prev_code, first_byte).ok_or_else(failed)?;
605 decoded.extend_from_slice(new_entry);
606 } else {
607 if decoded.is_empty() {
608 return Err(failed());
609 }
610 return Ok(decoded);
611 }
612
613 budget.check(decoded.len())?;
614
615 bit_size = table.code_length();
616 prev = Some(new);
617 }
618 }
619 }
620}
621
622struct LzwTable {
624 early_change: bool,
625 entries: Vec<Option<Vec<u8>>>,
626}
627
628impl LzwTable {
629 fn new(early_change: bool) -> Self {
630 let mut entries: Vec<_> = (0..=255u8).map(|b| Some(vec![b])).collect();
631 entries.push(None); entries.push(None); Self {
634 early_change,
635 entries,
636 }
637 }
638
639 fn push(&mut self, entry: Vec<u8>) -> Option<&[u8]> {
640 if self.entries.len() >= LZW_MAX_ENTRIES {
641 None
642 } else {
643 self.entries.push(Some(entry));
644 self.entries.last()?.as_deref()
645 }
646 }
647
648 fn register(&mut self, prev: usize, new_byte: u8) -> Option<&[u8]> {
649 let prev_entry = self.get(prev)?;
650 let mut new_entry = Vec::with_capacity(prev_entry.len() + 1);
651 new_entry.extend(prev_entry);
652 new_entry.push(new_byte);
653 self.push(new_entry)
654 }
655
656 fn get(&self, index: usize) -> Option<&[u8]> {
657 self.entries.get(index)?.as_deref()
658 }
659
660 fn clear(&mut self) {
661 self.entries.truncate(LZW_INITIAL_SIZE);
662 }
663
664 fn size(&self) -> usize {
665 self.entries.len()
666 }
667
668 fn code_length(&self) -> u8 {
669 let adjusted = self.entries.len() + if self.early_change { 1 } else { 0 };
670 if adjusted >= 2048 {
671 12
672 } else if adjusted >= 1024 {
673 11
674 } else if adjusted >= 512 {
675 10
676 } else {
677 9
678 }
679 }
680}
681
682struct LzwBitReader<'a> {
684 data: &'a [u8],
685 bit_pos: usize,
686}
687
688impl<'a> LzwBitReader<'a> {
689 fn new(data: &'a [u8]) -> Self {
690 Self { data, bit_pos: 0 }
691 }
692
693 fn read(&mut self, bit_size: u8) -> Option<u32> {
694 let byte_pos = self.bit_pos / 8;
695 if byte_pos >= self.data.len() {
696 return None;
697 }
698 let bit_offset = self.bit_pos % 8;
699 let end_byte = (self.bit_pos + bit_size as usize - 1) / 8;
700
701 let mut buf = [0u8; 8];
703 for (i, b) in buf.iter_mut().enumerate().take(end_byte - byte_pos + 1) {
704 *b = *self.data.get(byte_pos + i)?;
705 }
706 let bits = u64::from_be_bytes(buf);
707 let shift = 64 - bit_offset - bit_size as usize;
708 let mask = (1u64 << bit_size) - 1;
709 let value = ((bits >> shift) & mask) as u32;
710
711 self.bit_pos += bit_size as usize;
712 Some(value)
713 }
714}
715
716fn decode_ascii_hex(data: &[u8]) -> Result<Vec<u8>, PdfError> {
718 let mut result = Vec::with_capacity(data.len() / 2);
719 let mut high: Option<u8> = None;
720
721 for &b in data {
722 if b == b'>' {
723 break;
724 }
725 if b.is_ascii_whitespace() {
726 continue;
727 }
728 let nibble = hex_digit(b)
729 .ok_or_else(|| PdfError::DecompressionError(format!("invalid hex digit: 0x{b:02x}")))?;
730 match high {
731 None => high = Some(nibble),
732 Some(h) => {
733 result.push(h << 4 | nibble);
734 high = None;
735 }
736 }
737 }
738 if let Some(h) = high {
739 result.push(h << 4);
740 }
741
742 Ok(result)
743}
744
745fn decode_ascii85(data: &[u8]) -> Result<Vec<u8>, PdfError> {
747 let mut result = Vec::with_capacity(data.len() * 4 / 5);
748 let mut tuple: u64 = 0;
749 let mut count = 0u8;
750
751 for &b in data {
752 if b == b'~' {
753 break; }
755 if b.is_ascii_whitespace() {
756 continue;
757 }
758 if b == b'z' && count == 0 {
759 result.extend_from_slice(&[0, 0, 0, 0]);
760 continue;
761 }
762 if !(b'!'..=b'u').contains(&b) {
763 continue; }
765 tuple = tuple * 85 + (b - b'!') as u64;
766 count += 1;
767 if count == 5 {
768 result.push((tuple >> 24) as u8);
769 result.push((tuple >> 16) as u8);
770 result.push((tuple >> 8) as u8);
771 result.push(tuple as u8);
772 tuple = 0;
773 count = 0;
774 }
775 }
776
777 if count > 0 {
779 for _ in count..5 {
780 tuple = tuple * 85 + 84; }
782 for i in 0..(count - 1) {
783 result.push((tuple >> (24 - i * 8)) as u8);
784 }
785 }
786
787 Ok(result)
788}
789
790fn decode_run_length(data: &[u8], budget: DecodeBudget) -> Result<Vec<u8>, PdfError> {
795 let mut result = Vec::new();
796 let mut i = 0;
797
798 while i < data.len() {
799 budget.check(result.len())?;
800 let length_byte = data[i];
801 i += 1;
802 if length_byte < 128 {
803 let count = length_byte as usize + 1;
805 if i + count > data.len() {
806 break;
807 }
808 result.extend_from_slice(&data[i..i + count]);
809 i += count;
810 } else if length_byte > 128 {
811 if i >= data.len() {
813 break;
814 }
815 let count = 257 - length_byte as usize;
816 let val = data[i];
817 i += 1;
818 for _ in 0..count {
819 result.push(val);
820 }
821 } else {
822 break;
824 }
825 }
826
827 Ok(result)
828}
829
830fn decode_dct(data: &[u8]) -> Result<Vec<u8>, PdfError> {
837 use jpeg_decoder::Decoder;
838
839 #[cfg(target_arch = "wasm32")]
850 if let Some(pixels) = decode_dct_via_zune(data) {
851 return Ok(pixels);
852 }
853
854 let mut decoder = Decoder::new(data);
855
856 if has_adobe_rgb_marker(data) || is_raw_rgb_jpeg(data) {
862 decoder.set_color_transform(jpeg_decoder::ColorTransform::RGB);
863 } else if needs_ycck_override(data) {
864 decoder.set_color_transform(jpeg_decoder::ColorTransform::YCCK);
865 }
866
867 let pixels = match decoder.decode() {
868 Ok(p) => p,
869 Err(e) => {
870 if let Some(pixels) = decode_dct_zune(data) {
874 return Ok(pixels);
875 }
876 if let Some(patched) = patch_jpeg_dnl_height(data) {
880 return decode_dct(&patched);
881 }
882 if let Some(pixels) = decode_dct_tolerant(data) {
884 return Ok(pixels);
885 }
886 {
889 let mut padded = data.to_vec();
890 if padded.last() == Some(&0xFF) {
892 padded.pop();
893 }
894 padded.extend_from_slice(&[0xFF, 0xD9]);
895 let mut retry_dec = Decoder::new(&padded[..]);
896 if has_adobe_rgb_marker(&padded) || is_raw_rgb_jpeg(&padded) {
897 retry_dec.set_color_transform(jpeg_decoder::ColorTransform::RGB);
898 } else if needs_ycck_override(&padded) {
899 retry_dec.set_color_transform(jpeg_decoder::ColorTransform::YCCK);
900 }
901 if let Ok(pixels) = retry_dec.decode() {
902 if let Some(info) = retry_dec.info()
904 && info.pixel_format == jpeg_decoder::PixelFormat::CMYK32
905 {
906 let mut result = pixels;
907 for b in result.iter_mut() {
908 *b = 255 - *b;
909 }
910 return Ok(result);
911 }
912 return Ok(pixels);
913 }
914 }
915 return Err(PdfError::DecompressionError(format!("DCTDecode: {e}")));
916 }
917 };
918
919 if let Some(info) = decoder.info()
925 && info.pixel_format == jpeg_decoder::PixelFormat::CMYK32
926 {
927 let mut result = pixels;
928 for b in result.iter_mut() {
929 *b = 255 - *b;
930 }
931 return Ok(result);
932 }
933
934 Ok(pixels)
935}
936
937fn catch_silent<F, T>(f: F) -> Option<T>
941where
942 F: FnOnce() -> Option<T> + std::panic::UnwindSafe,
943{
944 let prev = std::panic::take_hook();
945 std::panic::set_hook(Box::new(|_| {}));
946 let result = std::panic::catch_unwind(f).ok().flatten();
947 std::panic::set_hook(prev);
948 result
949}
950
951#[cfg(target_arch = "wasm32")]
958fn decode_dct_via_zune(data: &[u8]) -> Option<Vec<u8>> {
959 use zune_jpeg::JpegDecoder;
960 let n_comps = jpeg_dimensions_and_components(data)
961 .map(|(_, _, n)| n)
962 .unwrap_or(3);
963 let out_cs = match n_comps {
964 1 => zune_core::colorspace::ColorSpace::Luma,
965 4 => zune_core::colorspace::ColorSpace::CMYK,
966 _ => zune_core::colorspace::ColorSpace::RGB,
967 };
968 let options = zune_core::options::DecoderOptions::default().jpeg_set_out_colorspace(out_cs);
969 let mut decoder = JpegDecoder::new_with_options(std::io::Cursor::new(data), options);
970 decoder.decode().ok()
971}
972
973fn decode_dct_zune(data: &[u8]) -> Option<Vec<u8>> {
976 use zune_jpeg::JpegDecoder;
977 let data = data.to_vec();
981 catch_silent(move || {
982 let options = zune_core::options::DecoderOptions::default()
983 .jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::LumaA);
984 let mut decoder = JpegDecoder::new_with_options(std::io::Cursor::new(&data), options);
985 decoder.decode().ok()
986 })
987}
988
989fn decode_dct_tolerant(data: &[u8]) -> Option<Vec<u8>> {
993 let n_comps = jpeg_dimensions_and_components(data)
996 .map(|(_, _, n)| n)
997 .unwrap_or(3);
998 let data = data.to_vec();
999 catch_silent(move || {
1000 use zune_jpeg::JpegDecoder;
1001 let out_cs = match n_comps {
1002 1 => zune_core::colorspace::ColorSpace::Luma,
1003 4 => zune_core::colorspace::ColorSpace::CMYK,
1004 _ => zune_core::colorspace::ColorSpace::RGB,
1005 };
1006 let options = zune_core::options::DecoderOptions::default()
1007 .set_strict_mode(false)
1008 .jpeg_set_out_colorspace(out_cs);
1009 let mut decoder = JpegDecoder::new_with_options(std::io::Cursor::new(&data), options);
1010 decoder.decode().ok()
1011 })
1012}
1013
1014fn patch_jpeg_dnl_height(data: &[u8]) -> Option<Vec<u8>> {
1019 let dnl_height = {
1021 let mut pos = 0;
1022 let mut found = None;
1023 while pos + 4 < data.len() {
1024 if data[pos] == 0xFF && data[pos + 1] == 0xDC {
1025 if pos + 5 < data.len() {
1027 let h = ((data[pos + 4] as u16) << 8) | data[pos + 5] as u16;
1028 found = Some((pos, h));
1029 }
1030 break;
1031 }
1032 pos += 1;
1033 }
1034 found
1035 };
1036 let (dnl_pos, height) = dnl_height?;
1037 if height == 0 {
1038 return None;
1039 }
1040
1041 let mut patched = data.to_vec();
1043 let mut pos = 2; while pos + 8 < patched.len() {
1045 if patched[pos] != 0xFF {
1046 pos += 1;
1047 continue;
1048 }
1049 let marker = patched[pos + 1];
1050 if (0xC0..=0xC3).contains(&marker) {
1051 patched[pos + 5] = (height >> 8) as u8;
1054 patched[pos + 6] = (height & 0xFF) as u8;
1055 break;
1056 }
1057 if marker == 0xDA {
1058 break; }
1060 if pos + 3 < patched.len() {
1062 let seg_len = ((patched[pos + 2] as usize) << 8) | patched[pos + 3] as usize;
1063 pos += 2 + seg_len;
1064 } else {
1065 break;
1066 }
1067 }
1068
1069 if dnl_pos + 6 <= patched.len() {
1071 patched.drain(dnl_pos..dnl_pos + 6);
1072 }
1073
1074 Some(patched)
1075}
1076
1077pub fn patch_jpeg_sof_height(data: &mut [u8], new_height: u16) {
1084 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
1085 return;
1086 }
1087 let mut pos = 2;
1088 while pos + 4 < data.len() {
1089 if data[pos] != 0xFF {
1090 pos += 1;
1091 continue;
1092 }
1093 let marker = data[pos + 1];
1094 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
1095 if pos + 6 < data.len() {
1096 data[pos + 5] = (new_height >> 8) as u8;
1097 data[pos + 6] = (new_height & 0xFF) as u8;
1098 }
1099 return;
1100 }
1101 if marker == 0xDA {
1102 return; }
1104 let seg_len = if pos + 3 < data.len() {
1105 ((data[pos + 2] as usize) << 8) | data[pos + 3] as usize
1106 } else {
1107 return;
1108 };
1109 pos += 2 + seg_len;
1110 }
1111}
1112
1113pub(crate) fn jpeg_dimensions_and_components(data: &[u8]) -> Option<(u32, u32, u8)> {
1115 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
1116 return None;
1117 }
1118 let mut pos = 2;
1119 while pos + 4 < data.len() {
1120 if data[pos] != 0xFF {
1121 pos += 1;
1122 continue;
1123 }
1124 let marker = data[pos + 1];
1125 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
1126 if pos + 9 < data.len() {
1127 let h = ((data[pos + 5] as u32) << 8) | data[pos + 6] as u32;
1128 let w = ((data[pos + 7] as u32) << 8) | data[pos + 8] as u32;
1129 let n = data[pos + 9];
1130 return Some((w, h, n));
1131 }
1132 }
1133 if marker == 0xDA {
1134 break;
1135 }
1136 let seg_len = ((data[pos + 2] as usize) << 8) | data[pos + 3] as usize;
1137 pos += 2 + seg_len;
1138 }
1139 None
1140}
1141
1142pub fn jpeg_dimensions(data: &[u8]) -> Option<(u32, u32)> {
1146 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
1147 return None;
1148 }
1149 let mut pos = 2;
1150 while pos + 4 < data.len() {
1151 if data[pos] != 0xFF {
1152 pos += 1;
1153 continue;
1154 }
1155 let marker = data[pos + 1];
1156 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
1158 if pos + 9 < data.len() {
1159 let mut h = ((data[pos + 5] as u32) << 8) | data[pos + 6] as u32;
1160 let w = ((data[pos + 7] as u32) << 8) | data[pos + 8] as u32;
1161 if h == 0 || h == 0xFFFF {
1163 if let Some(dnl_h) = find_dnl_height(data) {
1164 h = dnl_h as u32;
1165 }
1166 }
1167 return Some((w, h));
1168 }
1169 }
1170 if marker == 0xDA {
1171 break; }
1173 let seg_len = ((data[pos + 2] as usize) << 8) | data[pos + 3] as usize;
1174 pos += 2 + seg_len;
1175 }
1176 None
1177}
1178
1179fn find_dnl_height(data: &[u8]) -> Option<u16> {
1181 let mut pos = 0;
1182 while pos + 5 < data.len() {
1183 if data[pos] == 0xFF && data[pos + 1] == 0xDC && pos + 5 < data.len() {
1184 return Some(((data[pos + 4] as u16) << 8) | data[pos + 5] as u16);
1185 }
1186 pos += 1;
1187 }
1188 None
1189}
1190
1191fn has_adobe_rgb_marker(data: &[u8]) -> bool {
1196 let mut has_ct0 = false;
1197 let mut uniform_sampling = false;
1198 let mut i = 2; while i + 4 < data.len() {
1200 if data[i] != 0xFF {
1201 break;
1202 }
1203 let marker = data[i + 1];
1204 if marker == 0xDA {
1205 break; }
1207 let len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
1208 if i + 2 + len > data.len() {
1209 break;
1210 }
1211 if marker == 0xEE && len >= 14 {
1214 let color_transform = data[i + 2 + 13];
1215 has_ct0 = color_transform == 0;
1216 }
1217 if (marker == 0xC0 || marker == 0xC2) && i + 9 < data.len() {
1219 let ncomp = data[i + 9] as usize;
1220 if ncomp == 3 && i + 10 + ncomp * 3 <= data.len() {
1221 let s0 = data[i + 11]; let s1 = data[i + 14]; let s2 = data[i + 17]; uniform_sampling = s0 == s1 && s1 == s2;
1225 }
1226 }
1227 i += 2 + len;
1228 }
1229 has_ct0 && uniform_sampling
1230}
1231
1232fn is_raw_rgb_jpeg(data: &[u8]) -> bool {
1237 let mut has_jfif = false;
1238 let mut has_adobe = false;
1239 let mut non_standard_ids = false;
1240 let mut uniform_sampling = false;
1241 let mut n_components = 0u8;
1242 let mut i = 2; while i + 4 < data.len() {
1244 if data[i] != 0xFF {
1245 break;
1246 }
1247 let marker = data[i + 1];
1248 if marker == 0xDA {
1249 break;
1250 }
1251 let len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
1252 if i + 2 + len > data.len() {
1253 break;
1254 }
1255 if marker == 0xE0 && len >= 7 && &data[i + 4..i + 9] == b"JFIF\x00" {
1256 has_jfif = true;
1257 }
1258 if marker == 0xEE && len >= 7 && &data[i + 4..i + 9] == b"Adobe" {
1259 has_adobe = true;
1260 }
1261 if (marker == 0xC0 || marker == 0xC2) && i + 9 < data.len() {
1262 n_components = data[i + 9];
1263 if n_components == 3 && i + 10 + 9 <= data.len() {
1264 let id0 = data[i + 10];
1265 let id1 = data[i + 13];
1266 let id2 = data[i + 16];
1267 non_standard_ids = !(id0 == 1 && id1 == 2 && id2 == 3);
1269 let s0 = data[i + 11];
1270 let s1 = data[i + 14];
1271 let s2 = data[i + 17];
1272 uniform_sampling = s0 == s1 && s1 == s2;
1273 }
1274 }
1275 i += 2 + len;
1276 }
1277 n_components == 3 && non_standard_ids && uniform_sampling && !has_jfif && !has_adobe
1279}
1280
1281fn needs_ycck_override(data: &[u8]) -> bool {
1288 let mut last_ct = None;
1289 let mut decoder_would_miss = false;
1290 let mut n_components = 0u8;
1291 let mut i = 2; while i + 4 < data.len() {
1293 if data[i] != 0xFF {
1294 break;
1295 }
1296 let marker = data[i + 1];
1297 if marker == 0xDA {
1298 break; }
1300 let len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
1301 if i + 2 + len > data.len() {
1302 break;
1303 }
1304 if marker == 0xEE && len >= 14 && &data[i + 4..i + 9] == b"Adobe" {
1306 let ct = data[i + 2 + 13];
1307 last_ct = Some(ct);
1308 decoder_would_miss = data[i + 9] != 0;
1311 }
1312 if (marker == 0xC0 || marker == 0xC2) && i + 9 < data.len() {
1314 n_components = data[i + 9];
1315 }
1316 i += 2 + len;
1317 }
1318 last_ct == Some(2) && decoder_would_miss && n_components == 4
1321}
1322
1323fn decode_ccittfax(data: &[u8], parms: Option<&PdfDict>) -> Result<Vec<u8>, PdfError> {
1325 use crate::objects::PdfObj;
1326
1327 let k = parms.and_then(|p| p.get_int(b"K")).unwrap_or(0) as i32;
1328 let columns = parms.and_then(|p| p.get_int(b"Columns")).unwrap_or(1728) as u16;
1329 let rows_limit = parms.and_then(|p| p.get_int(b"Rows")).unwrap_or(0) as u32;
1330 let end_of_block = parms
1331 .and_then(|p| match p.get(b"EndOfBlock") {
1332 Some(PdfObj::Bool(b)) => Some(*b),
1333 _ => None,
1334 })
1335 .unwrap_or(true);
1336 let black_is1 = parms
1337 .and_then(|p| match p.get(b"BlackIs1") {
1338 Some(PdfObj::Bool(b)) => Some(*b),
1339 _ => None,
1340 })
1341 .unwrap_or(false);
1342
1343 let encoded_byte_align = parms
1344 .and_then(|p| match p.get(b"EncodedByteAlign") {
1345 Some(PdfObj::Bool(b)) => Some(*b),
1346 _ => None,
1347 })
1348 .unwrap_or(false);
1349
1350 let encoding = if k < 0 {
1351 hayro_ccitt::EncodingMode::Group4
1352 } else if k == 0 {
1353 hayro_ccitt::EncodingMode::Group3_1D
1354 } else {
1355 hayro_ccitt::EncodingMode::Group3_2D { k: k as u32 }
1356 };
1357
1358 let settings = hayro_ccitt::DecodeSettings {
1359 columns: columns as u32,
1360 rows: if rows_limit > 0 { rows_limit } else { u32::MAX },
1361 end_of_block,
1362 end_of_line: false,
1363 rows_are_byte_aligned: encoded_byte_align,
1364 encoding,
1365 invert_black: false,
1366 };
1367
1368 decode_ccitt_hayro(data, &settings, black_is1)
1369}
1370
1371struct CcittByteDecoder {
1374 output: Vec<u8>,
1375 current_byte: u8,
1376 bit_pos: u8,
1377 black_is1: bool,
1378}
1379
1380impl CcittByteDecoder {
1381 fn new(black_is1: bool) -> Self {
1382 Self {
1383 output: Vec::new(),
1384 current_byte: 0,
1385 bit_pos: 0,
1386 black_is1,
1387 }
1388 }
1389
1390 fn flush_byte(&mut self) {
1391 if self.bit_pos > 0 {
1392 let remaining = 8 - self.bit_pos;
1394 self.current_byte <<= remaining;
1395 if !self.black_is1 {
1396 self.current_byte |= (1u8 << remaining) - 1;
1398 }
1399 self.output.push(self.current_byte);
1400 self.current_byte = 0;
1401 self.bit_pos = 0;
1402 }
1403 }
1404}
1405
1406impl hayro_ccitt::Decoder for CcittByteDecoder {
1407 fn push_pixel(&mut self, white: bool) {
1408 let bit = if self.black_is1 { !white } else { white };
1411 self.current_byte = (self.current_byte << 1) | (bit as u8);
1412 self.bit_pos += 1;
1413 if self.bit_pos == 8 {
1414 self.output.push(self.current_byte);
1415 self.current_byte = 0;
1416 self.bit_pos = 0;
1417 }
1418 }
1419
1420 fn push_pixel_chunk(&mut self, white: bool, chunk_count: u32) {
1421 if self.bit_pos != 0 {
1424 for _ in 0..chunk_count * 8 {
1425 self.push_pixel(white);
1426 }
1427 return;
1428 }
1429 let byte = if (self.black_is1 && !white) || (!self.black_is1 && white) {
1430 0xFF
1431 } else {
1432 0x00
1433 };
1434 for _ in 0..chunk_count {
1435 self.output.push(byte);
1436 }
1437 }
1438
1439 fn next_line(&mut self) {
1440 self.flush_byte();
1441 }
1442}
1443
1444fn decode_ccitt_hayro(
1451 data: &[u8],
1452 settings: &hayro_ccitt::DecodeSettings,
1453 black_is1: bool,
1454) -> Result<Vec<u8>, PdfError> {
1455 let mut decoder = CcittByteDecoder::new(black_is1);
1456 let hayro_err = hayro_ccitt::decode(data, &mut decoder, settings).err();
1457
1458 if let Some(e) = hayro_err
1461 && e != hayro_ccitt::DecodeError::UnexpectedEof
1462 {
1463 let fallback = decode_ccitt_fax(data, settings, black_is1);
1464 use std::sync::atomic::{AtomicBool, Ordering};
1465 static WARNED: AtomicBool = AtomicBool::new(false);
1466 if fallback.len() > decoder.output.len() {
1467 if !WARNED.swap(true, Ordering::Relaxed) {
1468 eprintln!(
1469 "[CCITT] hayro-ccitt error: {} — fell back to `fax` crate",
1470 e
1471 );
1472 }
1473 return Ok(fallback);
1474 }
1475 if !WARNED.swap(true, Ordering::Relaxed) {
1476 eprintln!("[CCITT] decode warning: {} (using partial data)", e);
1477 }
1478 }
1479 Ok(decoder.output)
1480}
1481
1482fn decode_ccitt_fax(
1485 data: &[u8],
1486 settings: &hayro_ccitt::DecodeSettings,
1487 black_is1: bool,
1488) -> Vec<u8> {
1489 let width = settings.columns as u16;
1490 let row_bytes = settings.columns.div_ceil(8) as usize;
1491 let mut out: Vec<u8> = Vec::new();
1492 let white_byte: u8 = if black_is1 { 0x00 } else { 0xFF };
1496 let black_byte: u8 = !white_byte;
1497
1498 let rows_limit = if settings.rows == u32::MAX || settings.rows == 0 {
1499 None
1500 } else {
1501 Some(settings.rows.min(u16::MAX as u32) as u16)
1502 };
1503
1504 let mut emit_row = |transitions: &[u16]| {
1505 let mut row = vec![white_byte; row_bytes];
1507 let mut color_white = true;
1509 let mut cursor: u16 = 0;
1510 let iter = transitions.iter().copied().chain(std::iter::once(width));
1512 for next in iter {
1513 let end = next.min(width);
1514 if !color_white && end > cursor {
1515 fill_bits(&mut row, cursor as usize, end as usize, black_byte != 0);
1516 }
1517 color_white = !color_white;
1518 cursor = end;
1519 if cursor >= width {
1520 break;
1521 }
1522 }
1523 out.extend_from_slice(&row);
1524 };
1525
1526 match settings.encoding {
1527 hayro_ccitt::EncodingMode::Group4 => {
1528 let _ = fax::decoder::decode_g4(data.iter().copied(), width, rows_limit, &mut emit_row);
1529 }
1530 hayro_ccitt::EncodingMode::Group3_1D | hayro_ccitt::EncodingMode::Group3_2D { .. } => {
1531 let _ = fax::decoder::decode_g3(data.iter().copied(), &mut emit_row);
1532 }
1533 }
1534
1535 if let Some(target_rows) = rows_limit {
1541 let expected = row_bytes * target_rows as usize;
1542 if out.len() < expected {
1543 out.resize(expected, white_byte);
1544 }
1545 }
1546
1547 out
1548}
1549
1550fn fill_bits(row: &mut [u8], start: usize, end: usize, black_is_one: bool) {
1554 if end <= start {
1555 return;
1556 }
1557 for x in start..end {
1558 let byte = x / 8;
1559 let bit = 0x80u8 >> (x % 8);
1560 if black_is_one {
1561 row[byte] |= bit;
1562 } else {
1563 row[byte] &= !bit;
1564 }
1565 }
1566}
1567
1568fn decode_jbig2(data: &[u8], globals: Option<&[u8]>) -> Result<Vec<u8>, PdfError> {
1570 #[cfg(not(target_arch = "wasm32"))]
1578 let image = {
1579 let data_owned = data.to_vec();
1580 let globals_owned = globals.map(|g| g.to_vec());
1581 let (tx, rx) = std::sync::mpsc::channel();
1582 std::thread::spawn(move || {
1583 let result = hayro_jbig2::decode_embedded(&data_owned, globals_owned.as_deref());
1584 let _ = tx.send(result);
1585 });
1586 let timeout_secs = 5 + (data.len() as u64 / (1024 * 1024)) * 5;
1591 rx.recv_timeout(std::time::Duration::from_secs(timeout_secs))
1592 .map_err(|_| PdfError::DecompressionError("JBIG2: decode timed out".into()))?
1593 .map_err(|e| PdfError::DecompressionError(format!("JBIG2: {e}")))?
1594 };
1595
1596 #[cfg(target_arch = "wasm32")]
1597 let image = hayro_jbig2::decode_embedded(data, globals)
1598 .map_err(|e| PdfError::DecompressionError(format!("JBIG2: {e}")))?;
1599
1600 let row_bytes = (image.width as usize).div_ceil(8);
1605 let mut packed = vec![0xFFu8; row_bytes * image.height as usize];
1606 for y in 0..image.height as usize {
1607 for x in 0..image.width as usize {
1608 if image.data[y * image.width as usize + x] {
1609 packed[y * row_bytes + x / 8] &= !(0x80 >> (x % 8));
1610 }
1611 }
1612 }
1613 Ok(packed)
1614}
1615
1616#[cfg(feature = "jpx")]
1620fn decode_jpx(data: &[u8]) -> Result<Vec<u8>, PdfError> {
1621 if data.is_empty() {
1622 return Ok(Vec::new());
1623 }
1624
1625 let image = hayro_jpeg2000::Image::new(data, &hayro_jpeg2000::DecodeSettings::default())
1626 .map_err(|e| PdfError::DecompressionError(format!("JPXDecode: {e}")))?;
1627
1628 image
1629 .decode()
1630 .map_err(|e| PdfError::DecompressionError(format!("JPXDecode: {e}")))
1631}
1632
1633#[cfg(feature = "jpx")]
1645pub fn decode_jpx_no_palette(data: &[u8]) -> Result<(Vec<u8>, u8), PdfError> {
1646 if data.is_empty() {
1647 return Ok((Vec::new(), 8));
1648 }
1649
1650 let settings = hayro_jpeg2000::DecodeSettings {
1651 resolve_palette_indices: false,
1652 ..Default::default()
1653 };
1654 let image = hayro_jpeg2000::Image::new(data, &settings)
1655 .map_err(|e| PdfError::DecompressionError(format!("JPXDecode: {e}")))?;
1656 let bit_depth = image.original_bit_depth();
1657
1658 let pixels = image
1659 .decode()
1660 .map_err(|e| PdfError::DecompressionError(format!("JPXDecode: {e}")))?;
1661 Ok((pixels, bit_depth))
1662}
1663
1664#[cfg(feature = "jpx")]
1668pub fn jpx_color_info(data: &[u8]) -> Option<(u8, bool)> {
1669 let image =
1670 hayro_jpeg2000::Image::new(data, &hayro_jpeg2000::DecodeSettings::default()).ok()?;
1671 Some((image.color_space().num_channels(), image.has_alpha()))
1672}
1673
1674#[cfg(feature = "jpx")]
1677pub fn jpx_dimensions(data: &[u8]) -> Option<(u32, u32)> {
1678 let image =
1679 hayro_jpeg2000::Image::new(data, &hayro_jpeg2000::DecodeSettings::default()).ok()?;
1680 Some((image.width(), image.height()))
1681}
1682
1683pub fn decode_pre_jpx(raw: &[u8], dict: &crate::objects::PdfDict) -> Vec<u8> {
1686 let (filters, parms) = parse_filters(dict, None).unwrap_or_default();
1687 let pre_count = filters
1689 .iter()
1690 .take_while(|f| !matches!(f, Filter::JPXDecode))
1691 .count();
1692 if pre_count == 0 {
1693 return raw.to_vec();
1694 }
1695 let pre_parms: Vec<_> = parms.into_iter().take(pre_count).collect();
1696 decode_stream(raw, &filters[..pre_count], &pre_parms, None).unwrap_or_else(|_| raw.to_vec())
1697}
1698
1699const MAX_PREDICTOR_COLUMNS: i64 = 100_000;
1704
1705const MAX_PREDICTOR_COLORS: i64 = 32;
1711
1712fn validate_decode_parm(value: Option<i64>, default: i64, max: i64) -> Option<usize> {
1719 let v = value.unwrap_or(default);
1720 if v >= 1 && v <= max {
1721 usize::try_from(v).ok()
1722 } else {
1723 None
1724 }
1725}
1726
1727fn apply_predictor(data: &[u8], parms: &PdfDict, predictor: i64) -> Result<Vec<u8>, PdfError> {
1735 let (Some(columns), Some(colors), Some(bpc)) = (
1736 validate_decode_parm(parms.get_int(b"Columns"), 1, MAX_PREDICTOR_COLUMNS),
1737 validate_decode_parm(parms.get_int(b"Colors"), 1, MAX_PREDICTOR_COLORS),
1738 validate_decode_parm(
1739 parms.get_int(b"BitsPerComponent"),
1740 8,
1741 stet_graphics::image_limits::MAX_BITS_PER_COMPONENT,
1742 ),
1743 ) else {
1744 return Ok(data.to_vec());
1745 };
1746
1747 let Some(bytes_per_pixel) = colors.checked_mul(bpc).map(|b| b.div_ceil(8)) else {
1751 return Ok(data.to_vec());
1752 };
1753 let Some(row_bytes) = columns
1754 .checked_mul(colors)
1755 .and_then(|c| c.checked_mul(bpc))
1756 .map(|b| b.div_ceil(8))
1757 else {
1758 return Ok(data.to_vec());
1759 };
1760 if row_bytes == 0 || bytes_per_pixel == 0 {
1763 return Ok(data.to_vec());
1764 }
1765
1766 if predictor == 2 {
1767 if bpc < 8 {
1769 apply_tiff_predictor_subbyte(data, columns, colors, bpc, row_bytes)
1771 } else if bpc == 16 {
1772 apply_tiff_predictor_16bit(data, columns, colors, row_bytes)
1774 } else {
1775 apply_tiff_predictor(data, row_bytes, bytes_per_pixel)
1776 }
1777 } else if predictor >= 10 {
1778 apply_png_predictor(data, row_bytes, bytes_per_pixel)
1780 } else {
1781 Ok(data.to_vec())
1782 }
1783}
1784
1785fn apply_tiff_predictor_subbyte(
1788 data: &[u8],
1789 columns: usize,
1790 colors: usize,
1791 bpc: usize,
1792 row_bytes: usize,
1793) -> Result<Vec<u8>, PdfError> {
1794 let samples_per_row = columns * colors;
1795 let mask = (1u8 << bpc) - 1; let mut result = Vec::with_capacity(data.len());
1797
1798 for row in data.chunks(row_bytes) {
1799 let mut out_row = vec![0u8; row.len()];
1800 out_row[..row.len()].copy_from_slice(row);
1802
1803 let mut prev = vec![0u8; colors];
1805 for col in 0..columns {
1806 for c in 0..colors {
1807 let sample_idx = col * colors + c;
1808 if sample_idx >= samples_per_row {
1809 break;
1810 }
1811 let bit_offset = sample_idx * bpc;
1812 let byte_idx = bit_offset / 8;
1813 let bit_pos = 8 - bpc - (bit_offset % 8); if byte_idx >= row.len() {
1815 break;
1816 }
1817 let encoded = (row[byte_idx] >> bit_pos) & mask;
1818 let decoded = (encoded.wrapping_add(prev[c])) & mask;
1819 prev[c] = decoded;
1820 out_row[byte_idx] = (out_row[byte_idx] & !(mask << bit_pos)) | (decoded << bit_pos);
1822 }
1823 }
1824 result.extend_from_slice(&out_row);
1825 }
1826
1827 Ok(result)
1828}
1829
1830fn apply_tiff_predictor_16bit(
1835 data: &[u8],
1836 columns: usize,
1837 colors: usize,
1838 row_bytes: usize,
1839) -> Result<Vec<u8>, PdfError> {
1840 let mut result = Vec::with_capacity(data.len());
1841
1842 for row in data.chunks(row_bytes) {
1843 let mut out_row = vec![0u8; row.len()];
1844 let mut prev = vec![0u16; colors];
1845
1846 for col in 0..columns {
1847 for c in 0..colors {
1848 let byte_idx = (col * colors + c) * 2;
1849 if byte_idx + 1 >= row.len() {
1850 break;
1851 }
1852 let encoded = u16::from_be_bytes([row[byte_idx], row[byte_idx + 1]]);
1853 let decoded = encoded.wrapping_add(prev[c]);
1854 prev[c] = decoded;
1855 let [hi, lo] = decoded.to_be_bytes();
1856 out_row[byte_idx] = hi;
1857 out_row[byte_idx + 1] = lo;
1858 }
1859 }
1860 result.extend_from_slice(&out_row);
1861 }
1862
1863 Ok(result)
1864}
1865
1866fn apply_tiff_predictor(
1868 data: &[u8],
1869 row_bytes: usize,
1870 bytes_per_pixel: usize,
1871) -> Result<Vec<u8>, PdfError> {
1872 let mut result = Vec::with_capacity(data.len());
1873
1874 for row in data.chunks(row_bytes) {
1875 let mut out_row = vec![0u8; row.len()];
1876 for i in 0..row.len() {
1877 let left = if i >= bytes_per_pixel {
1878 out_row[i - bytes_per_pixel]
1879 } else {
1880 0
1881 };
1882 out_row[i] = row[i].wrapping_add(left);
1883 }
1884 result.extend_from_slice(&out_row);
1885 }
1886
1887 Ok(result)
1888}
1889
1890fn apply_png_predictor(
1892 data: &[u8],
1893 row_bytes: usize,
1894 bytes_per_pixel: usize,
1895) -> Result<Vec<u8>, PdfError> {
1896 let stride = row_bytes + 1;
1898
1899 if row_bytes > 0
1903 && !data.is_empty()
1904 && data.len().is_multiple_of(row_bytes)
1905 && !data.len().is_multiple_of(stride)
1906 {
1907 return Ok(data.to_vec());
1908 }
1909
1910 let num_rows = data.len() / stride;
1911 let mut result = Vec::with_capacity(num_rows * row_bytes);
1912 let mut prev_row = vec![0u8; row_bytes];
1913
1914 for row_idx in 0..num_rows {
1915 let row_start = row_idx * stride;
1916 if row_start >= data.len() {
1917 break;
1918 }
1919 let filter_type = data[row_start];
1920 let row_data = &data[row_start + 1..std::cmp::min(row_start + stride, data.len())];
1921 let mut out_row = vec![0u8; row_data.len()];
1922
1923 match filter_type {
1924 0 => {
1925 out_row.copy_from_slice(row_data);
1927 }
1928 1 => {
1929 for i in 0..row_data.len() {
1931 let left = if i >= bytes_per_pixel {
1932 out_row[i - bytes_per_pixel]
1933 } else {
1934 0
1935 };
1936 out_row[i] = row_data[i].wrapping_add(left);
1937 }
1938 }
1939 2 => {
1940 for i in 0..row_data.len() {
1942 let up = if i < prev_row.len() { prev_row[i] } else { 0 };
1943 out_row[i] = row_data[i].wrapping_add(up);
1944 }
1945 }
1946 3 => {
1947 for i in 0..row_data.len() {
1949 let left = if i >= bytes_per_pixel {
1950 out_row[i - bytes_per_pixel] as u16
1951 } else {
1952 0
1953 };
1954 let up = if i < prev_row.len() {
1955 prev_row[i] as u16
1956 } else {
1957 0
1958 };
1959 out_row[i] = row_data[i].wrapping_add(((left + up) / 2) as u8);
1960 }
1961 }
1962 4 => {
1963 for i in 0..row_data.len() {
1965 let left = if i >= bytes_per_pixel {
1966 out_row[i - bytes_per_pixel]
1967 } else {
1968 0
1969 };
1970 let up = if i < prev_row.len() { prev_row[i] } else { 0 };
1971 let up_left = if i >= bytes_per_pixel && i - bytes_per_pixel < prev_row.len() {
1972 prev_row[i - bytes_per_pixel]
1973 } else {
1974 0
1975 };
1976 out_row[i] = row_data[i].wrapping_add(paeth(left, up, up_left));
1977 }
1978 }
1979 _ => {
1980 out_row.copy_from_slice(row_data);
1982 }
1983 }
1984
1985 prev_row[..out_row.len()].copy_from_slice(&out_row);
1986 result.extend_from_slice(&out_row);
1987 }
1988
1989 Ok(result)
1990}
1991
1992fn paeth(a: u8, b: u8, c: u8) -> u8 {
1994 let a = a as i16;
1995 let b = b as i16;
1996 let c = c as i16;
1997 let p = a + b - c;
1998 let pa = (p - a).abs();
1999 let pb = (p - b).abs();
2000 let pc = (p - c).abs();
2001 if pa <= pb && pa <= pc {
2002 a as u8
2003 } else if pb <= pc {
2004 b as u8
2005 } else {
2006 c as u8
2007 }
2008}
2009
2010fn hex_digit(b: u8) -> Option<u8> {
2011 match b {
2012 b'0'..=b'9' => Some(b - b'0'),
2013 b'a'..=b'f' => Some(b - b'a' + 10),
2014 b'A'..=b'F' => Some(b - b'A' + 10),
2015 _ => None,
2016 }
2017}
2018
2019#[cfg(test)]
2020mod tests {
2021 use super::*;
2022
2023 #[test]
2024 fn flate_round_trip() {
2025 use flate2::Compression;
2026 use flate2::write::ZlibEncoder;
2027 use std::io::Write;
2028
2029 let original = b"Hello, PDF world! This is a test of FlateDecode.";
2030 let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
2031 enc.write_all(original).unwrap();
2032 let compressed = enc.finish().unwrap();
2033
2034 let decoded = decode_flate(&compressed, None, DecodeBudget::default()).unwrap();
2035 assert_eq!(&decoded, original);
2036 }
2037
2038 #[test]
2039 fn ascii_hex_decode() {
2040 let decoded = decode_ascii_hex(b"48656C6C6F>").unwrap();
2041 assert_eq!(&decoded, b"Hello");
2042 }
2043
2044 #[test]
2045 fn ascii_hex_odd_digits() {
2046 let decoded = decode_ascii_hex(b"ABC>").unwrap();
2047 assert_eq!(decoded, vec![0xAB, 0xC0]);
2048 }
2049
2050 #[test]
2051 fn ascii85_decode() {
2052 let decoded = decode_ascii85(b"FCfN8~>").unwrap();
2056 assert_eq!(&decoded, b"test");
2057 }
2058
2059 #[test]
2060 fn ascii85_z_shortcut() {
2061 let decoded = decode_ascii85(b"z~>").unwrap();
2062 assert_eq!(decoded, vec![0, 0, 0, 0]);
2063 }
2064
2065 #[test]
2066 fn run_length_decode() {
2067 let data = vec![2, b'A', b'B', b'C', 253, b'X', 128];
2069 let decoded = decode_run_length(&data, DecodeBudget::default()).unwrap();
2070 assert_eq!(&decoded, b"ABCXXXX");
2071 }
2072
2073 #[test]
2074 fn png_predictor_none() {
2075 let data = vec![0, 10, 20, 30];
2077 let result = apply_png_predictor(&data, 3, 1).unwrap();
2078 assert_eq!(result, vec![10, 20, 30]);
2079 }
2080
2081 #[test]
2082 fn png_predictor_sub() {
2083 let data = vec![1, 5, 3, 4];
2086 let result = apply_png_predictor(&data, 3, 1).unwrap();
2087 assert_eq!(result, vec![5, 8, 12]);
2088 }
2089
2090 #[test]
2091 fn png_predictor_up() {
2092 let data = vec![0, 10, 20, 30, 2, 5, 5, 5];
2096 let result = apply_png_predictor(&data, 3, 1).unwrap();
2097 assert_eq!(result, vec![10, 20, 30, 15, 25, 35]);
2098 }
2099
2100 #[test]
2101 fn filter_chain() {
2102 use flate2::Compression;
2103 use flate2::write::ZlibEncoder;
2104 use std::io::Write;
2105
2106 let original = b"filter chain test data";
2107 let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
2108 enc.write_all(original).unwrap();
2109 let compressed = enc.finish().unwrap();
2110
2111 let mut hex = String::new();
2113 for b in &compressed {
2114 hex.push_str(&format!("{b:02X}"));
2115 }
2116 hex.push('>');
2117
2118 let filters = vec![Filter::ASCIIHexDecode, Filter::FlateDecode];
2119 let parms = vec![None, None];
2120 let decoded = decode_stream(hex.as_bytes(), &filters, &parms, None).unwrap();
2121 assert_eq!(&decoded, original);
2122 }
2123
2124 fn zlib(data: &[u8]) -> Vec<u8> {
2127 use flate2::Compression;
2128 use flate2::write::ZlibEncoder;
2129 use std::io::Write;
2130 let mut enc = ZlibEncoder::new(Vec::new(), Compression::best());
2131 enc.write_all(data).unwrap();
2132 enc.finish().unwrap()
2133 }
2134
2135 #[test]
2144 fn nested_flate_chain_is_refused_rather_than_expanded() {
2145 let mut data = zlib(&vec![0u8; 4 << 20]);
2146 let mut filters = vec![Filter::FlateDecode];
2147 for _ in 0..3 {
2148 data = zlib(&data);
2149 filters.push(Filter::FlateDecode);
2150 }
2151 assert!(
2152 data.len() < 1024,
2153 "the bomb must stay small: {}",
2154 data.len()
2155 );
2156
2157 let parms = vec![None; filters.len()];
2158 let err =
2159 decode_stream_bounded(&data, &filters, &parms, None, DecodeBudget::new(64 * 1024))
2160 .unwrap_err();
2161 assert!(
2162 matches!(err, PdfError::DecompressionError(ref m) if m.contains("exceeds")),
2163 "expected a budget refusal, got {err:?}"
2164 );
2165 }
2166
2167 #[test]
2171 fn run_length_on_flate_is_refused() {
2172 let rle = b"\x81\x00".repeat(64 << 10); let data = zlib(&rle);
2174 let filters = [Filter::FlateDecode, Filter::RunLengthDecode];
2175 let parms = vec![None; filters.len()];
2176 let err =
2177 decode_stream_bounded(&data, &filters, &parms, None, DecodeBudget::new(64 * 1024))
2178 .unwrap_err();
2179 assert!(matches!(err, PdfError::DecompressionError(_)), "{err:?}");
2180 }
2181
2182 #[test]
2186 fn flate_budget_overrun_is_an_error_not_a_truncation() {
2187 let data = zlib(&vec![0u8; 4 << 20]);
2188 let err = decode_flate(&data, None, DecodeBudget::new(4096)).unwrap_err();
2189 assert!(matches!(err, PdfError::DecompressionError(ref m) if m.contains("exceeds")));
2190 }
2191
2192 #[test]
2193 fn lzw_output_is_bounded() {
2194 let mut bits = Vec::new();
2197 let mut acc: u32 = 0;
2198 let mut nbits = 0;
2199 for code in std::iter::once(LZW_CLEAR_TABLE).chain(std::iter::repeat_n(0usize, 512)) {
2200 acc = (acc << 9) | code as u32;
2201 nbits += 9;
2202 while nbits >= 8 {
2203 bits.push((acc >> (nbits - 8)) as u8);
2204 nbits -= 8;
2205 }
2206 }
2207 let err = decode_lzw(&bits, None, DecodeBudget::new(32)).unwrap_err();
2208 assert!(matches!(err, PdfError::DecompressionError(ref m) if m.contains("exceeds")));
2209 }
2210
2211 #[test]
2214 fn ordinary_streams_are_unaffected() {
2215 let original = b"q 1 0 0 1 10 10 cm BT /F1 12 Tf (hello) Tj ET Q".repeat(1000);
2216 let data = zlib(&original);
2217 let decoded = decode_stream_bounded(
2218 &data,
2219 &[Filter::FlateDecode],
2220 &[None],
2221 None,
2222 DecodeBudget::default(),
2223 )
2224 .unwrap();
2225 assert_eq!(decoded, original);
2226 }
2227
2228 fn dict_from(src: &[u8]) -> PdfDict {
2231 let mut lexer = crate::lexer::Lexer::new(src);
2232 match crate::lexer::parse_object(&mut lexer).unwrap() {
2233 crate::objects::PdfObj::Dict(d) => d,
2234 other => panic!("expected a dict, got {other:?}"),
2235 }
2236 }
2237
2238 #[test]
2239 fn a_dict_declaring_nothing_gets_the_general_ceiling() {
2240 let budget = DecodeBudget::for_stream(&dict_from(b"<</Type/ObjStm/N 4/First 20>>"));
2241 assert_eq!(budget.limit(), MAX_DECODED_STREAM_BYTES);
2242 }
2243
2244 #[test]
2248 fn a_declared_image_raster_raises_the_ceiling() {
2249 let budget = DecodeBudget::for_stream(&dict_from(
2251 b"<</Subtype/Image/Width 72000/Height 48000/BitsPerComponent 8/ColorSpace/DeviceCMYK>>",
2252 ));
2253 assert_eq!(budget.limit(), 72_000usize * 48_000 * 4);
2254 }
2255
2256 #[test]
2259 fn a_small_declared_image_does_not_lower_the_ceiling() {
2260 let budget = DecodeBudget::for_stream(&dict_from(
2261 b"<</Subtype/Image/Width 8/Height 8/BitsPerComponent 8/ColorSpace/DeviceGray>>",
2262 ));
2263 assert_eq!(budget.limit(), MAX_DECODED_STREAM_BYTES);
2264 }
2265
2266 #[test]
2269 fn sub_byte_rows_are_padded_to_a_byte_boundary() {
2270 let bytes = declared_image_bytes(&dict_from(
2271 b"<</Width 9/Height 4/BitsPerComponent 1/ColorSpace/DeviceGray>>",
2272 ))
2273 .unwrap();
2274 assert_eq!(bytes, 2 * 4);
2275 }
2276
2277 #[test]
2280 fn out_of_range_dimensions_do_not_raise_the_ceiling() {
2281 for src in [
2282 &b"<</Width 999999999/Height 999999999/BitsPerComponent 8>>"[..],
2283 &b"<</Width -1/Height 10/BitsPerComponent 8>>"[..],
2284 &b"<</Width 10/Height 10/BitsPerComponent 999>>"[..],
2285 ] {
2286 assert_eq!(
2287 DecodeBudget::for_stream(&dict_from(src)).limit(),
2288 MAX_DECODED_STREAM_BYTES,
2289 "{}",
2290 String::from_utf8_lossy(src)
2291 );
2292 }
2293 }
2294
2295 #[test]
2298 fn an_embedded_file_size_raises_the_ceiling() {
2299 let budget = DecodeBudget::for_stream(&dict_from(
2300 b"<</Type/EmbeddedFile/Params<</Size 2000000000>>>>",
2301 ));
2302 assert_eq!(budget.limit(), 2_000_000_000);
2303 }
2304}