use crate::error::PdfError;
pub fn flate_decompress(input: &[u8]) -> Result<Vec<u8>, PdfError> {
crate::zlib::flate_decompress(input)
}
pub fn ascii85_decode(input: &[u8]) -> Result<Vec<u8>, PdfError> {
let mut out = Vec::with_capacity(input.len() * 4 / 5);
let mut group: [u8; 5] = [0; 5];
let mut filled: usize = 0;
let mut i = 0;
while i < input.len() {
let b = input[i];
i += 1;
if b == b'~' {
if i < input.len() && input[i] == b'>' {
break;
}
return Err(PdfError::other(
"PDF filter: ASCII85 stray '~' (expected '~>')",
));
}
if matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0C | 0x00) {
continue;
}
if b == b'z' {
if filled != 0 {
return Err(PdfError::other(
"PDF filter: ASCII85 'z' shorthand mid-group",
));
}
out.extend_from_slice(&[0, 0, 0, 0]);
continue;
}
if !(b'!'..=b'u').contains(&b) {
return Err(PdfError::other(format!(
"PDF filter: ASCII85 illegal byte {b:#x}"
)));
}
group[filled] = b - b'!';
filled += 1;
if filled == 5 {
decode_ascii85_group_full(&group, &mut out);
filled = 0;
}
}
if filled == 1 {
return Err(PdfError::other(
"PDF filter: ASCII85 trailing 1-character group is illegal",
));
}
if filled > 1 {
for slot in group.iter_mut().skip(filled) {
*slot = 84;
}
let mut tmp = Vec::with_capacity(4);
decode_ascii85_group_full(&group, &mut tmp);
out.extend_from_slice(&tmp[..filled - 1]);
}
Ok(out)
}
fn decode_ascii85_group_full(group: &[u8; 5], out: &mut Vec<u8>) {
let value: u64 = group.iter().fold(0u64, |acc, &d| acc * 85 + d as u64);
out.push(((value >> 24) & 0xFF) as u8);
out.push(((value >> 16) & 0xFF) as u8);
out.push(((value >> 8) & 0xFF) as u8);
out.push((value & 0xFF) as u8);
}
pub fn ascii_hex_decode(input: &[u8]) -> Result<Vec<u8>, PdfError> {
let mut out = Vec::with_capacity(input.len() / 2);
let mut high: Option<u8> = None;
for &b in input {
if b == b'>' {
break;
}
if matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0C | 0x00) {
continue;
}
let nibble = match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
_ => {
return Err(PdfError::other(format!(
"PDF filter: ASCIIHex illegal byte {b:#x}"
)))
}
};
match high.take() {
None => high = Some(nibble),
Some(h) => out.push((h << 4) | nibble),
}
}
if let Some(h) = high {
out.push(h << 4);
}
Ok(out)
}
pub fn run_length_decode(input: &[u8]) -> Result<Vec<u8>, PdfError> {
let mut out = Vec::new();
let mut i = 0;
while i < input.len() {
let tag = input[i];
i += 1;
if tag == 128 {
break;
}
if tag < 128 {
let copy = tag as usize + 1;
if i + copy > input.len() {
return Err(PdfError::other(
"PDF filter: RunLengthDecode literal run exceeds input",
));
}
out.extend_from_slice(&input[i..i + copy]);
i += copy;
} else {
let count = 257 - tag as usize;
if i >= input.len() {
return Err(PdfError::other(
"PDF filter: RunLengthDecode repeat-run missing byte",
));
}
let byte = input[i];
i += 1;
out.extend(std::iter::repeat(byte).take(count));
}
}
Ok(out)
}
pub fn lzw_decode_with_early_change(input: &[u8], early_change: bool) -> Result<Vec<u8>, PdfError> {
const CLEAR: u32 = 256;
const EOD: u32 = 257;
const FIRST_FREE: u32 = 258;
const MAX_WIDTH: u32 = 12;
let mut table: Vec<Vec<u8>> = Vec::new();
let early = if early_change { 1 } else { 0 };
let mut out: Vec<u8> = Vec::new();
let mut bit_buf: u32 = 0;
let mut bit_cnt: u32 = 0;
let mut byte_pos: usize = 0;
let mut code_width: u32 = 9;
let mut previous: Option<Vec<u8>> = None;
let expand = |code: u32, table: &[Vec<u8>]| -> Option<Vec<u8>> {
if code < 256 {
Some(vec![code as u8])
} else if code >= FIRST_FREE {
table.get((code - FIRST_FREE) as usize).cloned()
} else {
None }
};
loop {
while bit_cnt < code_width {
if byte_pos >= input.len() {
return Ok(out);
}
bit_buf = (bit_buf << 8) | input[byte_pos] as u32;
byte_pos += 1;
bit_cnt += 8;
}
bit_cnt -= code_width;
let code = (bit_buf >> bit_cnt) & ((1 << code_width) - 1);
if code == EOD {
break;
}
if code == CLEAR {
table.clear();
code_width = 9;
previous = None;
continue;
}
let next_code = FIRST_FREE + table.len() as u32;
let entry = match expand(code, &table) {
Some(seq) => seq,
None if code == next_code => {
let prev = previous.as_ref().ok_or_else(|| {
PdfError::other("PDF filter: LZW first code references empty table")
})?;
let mut seq = prev.clone();
seq.push(prev[0]);
seq
}
None => {
return Err(PdfError::other(format!(
"PDF filter: LZW code {code} out of range (next free {next_code})"
)));
}
};
out.extend_from_slice(&entry);
if let Some(prev) = previous.as_ref() {
if next_code <= 4095 {
let mut new_entry = prev.clone();
new_entry.push(entry[0]);
table.push(new_entry);
}
}
previous = Some(entry);
let assigned = FIRST_FREE + table.len() as u32;
if code_width < MAX_WIDTH && assigned + early >= (1 << code_width) {
code_width += 1;
}
}
Ok(out)
}
pub fn lzw_decode(input: &[u8]) -> Result<Vec<u8>, PdfError> {
lzw_decode_with_early_change(input, true)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PredictorParams {
pub predictor: i64,
pub colors: usize,
pub bits_per_component: usize,
pub columns: usize,
}
impl Default for PredictorParams {
fn default() -> Self {
PredictorParams {
predictor: 1,
colors: 1,
bits_per_component: 8,
columns: 1,
}
}
}
pub fn apply_predictor(data: &[u8], params: &PredictorParams) -> Result<Vec<u8>, PdfError> {
if params.predictor <= 1 {
return Ok(data.to_vec());
}
if params.colors == 0 || params.bits_per_component == 0 || params.columns == 0 {
return Err(PdfError::other(
"PDF filter: predictor /Colors, /BitsPerComponent, /Columns must be positive",
));
}
if !matches!(params.bits_per_component, 1 | 2 | 4 | 8 | 16) {
return Err(PdfError::other(format!(
"PDF filter: predictor /BitsPerComponent {} invalid (1, 2, 4, 8, 16)",
params.bits_per_component
)));
}
let bits_per_pixel = params.colors * params.bits_per_component;
let row_bytes = bits_per_pixel
.checked_mul(params.columns)
.map(|b| b.div_ceil(8))
.ok_or_else(|| PdfError::other("PDF filter: predictor row width overflow"))?;
if row_bytes == 0 {
return Ok(Vec::new());
}
match params.predictor {
2 => tiff_predictor_2(data, params, row_bytes),
10..=15 => png_predictor(data, bits_per_pixel.div_ceil(8).max(1), row_bytes),
other => Err(PdfError::other(format!(
"PDF filter: /Predictor {other} not supported (1, 2, 10..=15)"
))),
}
}
fn tiff_predictor_2(
data: &[u8],
params: &PredictorParams,
row_bytes: usize,
) -> Result<Vec<u8>, PdfError> {
if data.len() % row_bytes != 0 {
return Err(PdfError::other(format!(
"PDF filter: TIFF predictor row width {row_bytes} does not divide data length {}",
data.len()
)));
}
let mut out = data.to_vec();
let bpc = params.bits_per_component;
let comps_per_row = params.colors * params.columns;
for row in out.chunks_mut(row_bytes) {
if bpc == 8 {
for i in params.colors..row.len() {
row[i] = row[i].wrapping_add(row[i - params.colors]);
}
} else if bpc == 16 {
let total = row.len() / 2;
for i in params.colors..total {
let prev = u16::from_be_bytes([
row[2 * (i - params.colors)],
row[2 * (i - params.colors) + 1],
]);
let cur = u16::from_be_bytes([row[2 * i], row[2 * i + 1]]);
let sum = cur.wrapping_add(prev).to_be_bytes();
row[2 * i] = sum[0];
row[2 * i + 1] = sum[1];
}
} else {
let mask = (1u16 << bpc) - 1;
let mut comps: Vec<u16> = Vec::with_capacity(comps_per_row);
let mut bit = 0usize;
for _ in 0..comps_per_row {
let byte = row[bit / 8];
let shift = 8 - bpc - (bit % 8);
comps.push(((byte as u16) >> shift) & mask);
bit += bpc;
}
for i in params.colors..comps.len() {
comps[i] = (comps[i] + comps[i - params.colors]) & mask;
}
for b in row.iter_mut() {
*b = 0;
}
let mut bit = 0usize;
for c in comps {
let shift = 8 - bpc - (bit % 8);
row[bit / 8] |= ((c & mask) << shift) as u8;
bit += bpc;
}
}
}
Ok(out)
}
fn png_predictor(data: &[u8], bpp: usize, row_bytes: usize) -> Result<Vec<u8>, PdfError> {
let stride = row_bytes + 1;
if data.len() % stride != 0 {
return Err(PdfError::other(format!(
"PDF filter: PNG predictor row stride {stride} does not divide data length {}",
data.len()
)));
}
let rows = data.len() / stride;
let mut out = vec![0u8; rows * row_bytes];
let mut prev = vec![0u8; row_bytes];
for r in 0..rows {
let tag = data[r * stride];
let src = &data[r * stride + 1..r * stride + stride];
let dst_start = r * row_bytes;
for i in 0..row_bytes {
let raw = src[i];
let left = if i >= bpp {
out[dst_start + i - bpp]
} else {
0
};
let up = prev[i];
let up_left = if i >= bpp { prev[i - bpp] } else { 0 };
let recon = match tag {
0 => raw, 1 => raw.wrapping_add(left), 2 => raw.wrapping_add(up), 3 => {
let avg = ((left as u16 + up as u16) / 2) as u8;
raw.wrapping_add(avg)
}
4 => raw.wrapping_add(paeth(left, up, up_left)), other => {
return Err(PdfError::other(format!(
"PDF filter: PNG predictor row tag {other} unknown (0..=4)"
)))
}
};
out[dst_start + i] = recon;
}
prev.copy_from_slice(&out[dst_start..dst_start + row_bytes]);
}
Ok(out)
}
fn paeth(a: u8, b: u8, c: u8) -> u8 {
let (a, b, c) = (a as i16, b as i16, c as i16);
let p = a + b - c;
let pa = (p - a).abs();
let pb = (p - b).abs();
let pc = (p - c).abs();
if pa <= pb && pa <= pc {
a as u8
} else if pb <= pc {
b as u8
} else {
c as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii85_decodes_canonical_man_example() {
let encoded = b"9jqo^~>";
let out = ascii85_decode(encoded).unwrap();
assert_eq!(out, b"Man ");
}
#[test]
fn ascii85_z_shorthand_decodes_four_zero_bytes() {
let out = ascii85_decode(b"z~>").unwrap();
assert_eq!(out, [0u8, 0, 0, 0]);
}
#[test]
fn ascii_hex_basic() {
let out = ascii_hex_decode(b"48656C6C6F>").unwrap();
assert_eq!(out, b"Hello");
}
#[test]
fn ascii_hex_odd_nibble_pads_zero() {
let out = ascii_hex_decode(b"414>").unwrap();
assert_eq!(out, [0x41u8, 0x40]);
}
#[test]
fn run_length_literal_run() {
let input = [4u8, b'h', b'e', b'l', b'l', b'o', 128];
let out = run_length_decode(&input).unwrap();
assert_eq!(out, b"hello");
}
#[test]
fn run_length_repeat_run() {
let input = [253u8, b'x', 128];
let out = run_length_decode(&input).unwrap();
assert_eq!(out, b"xxxx");
}
#[test]
fn run_length_mixed_runs() {
let input = [0u8, b'A', 252, b'B', 128];
let out = run_length_decode(&input).unwrap();
assert_eq!(out, b"ABBBBB");
}
#[test]
fn run_length_no_eod_accepts_eof() {
let input = [4u8, b'h', b'e', b'l', b'l', b'o'];
let out = run_length_decode(&input).unwrap();
assert_eq!(out, b"hello");
}
#[test]
fn lzw_decodes_spec_example_2() {
let encoded = [0x80u8, 0x0B, 0x60, 0x50, 0x22, 0x0C, 0x0C, 0x85, 0x01];
let out = lzw_decode(&encoded).unwrap();
assert_eq!(out, [45u8, 45, 45, 45, 45, 65, 45, 45, 45, 66]);
}
#[test]
fn lzw_handles_clear_then_eod_only() {
let bits: u32 = (256 << 9) | 257; let packed = [
((bits >> 10) & 0xFF) as u8,
((bits >> 2) & 0xFF) as u8,
((bits << 6) & 0xFF) as u8,
];
let out = lzw_decode(&packed).unwrap();
assert!(out.is_empty());
}
#[test]
fn lzw_truncated_stream_returns_partial_not_error() {
let out = lzw_decode(&[0x80u8, 0x0B, 0x60]).unwrap();
assert_eq!(out, [45u8]);
}
#[test]
fn lzw_round_trips_a_longer_payload() {
let codes = [256u32, 97, 258, 259, 258, 257];
let mut bit_buf: u64 = 0;
let mut bit_cnt = 0u32;
let mut packed = Vec::new();
for c in codes {
bit_buf = (bit_buf << 9) | c as u64;
bit_cnt += 9;
while bit_cnt >= 8 {
bit_cnt -= 8;
packed.push(((bit_buf >> bit_cnt) & 0xFF) as u8);
}
}
if bit_cnt > 0 {
packed.push(((bit_buf << (8 - bit_cnt)) & 0xFF) as u8);
}
let out = lzw_decode(&packed).unwrap();
assert_eq!(out, b"aaaaaaaa");
}
#[test]
fn predictor_1_passes_through() {
let data = [1u8, 2, 3, 4, 5];
let p = PredictorParams {
predictor: 1,
..Default::default()
};
assert_eq!(apply_predictor(&data, &p).unwrap(), data);
}
#[test]
fn png_up_predictor_round_trip() {
let encoded = [
0u8, 10, 20, 30, 2, 1, 2, 3, ];
let p = PredictorParams {
predictor: 12, colors: 1,
bits_per_component: 8,
columns: 3,
};
let out = apply_predictor(&encoded, &p).unwrap();
assert_eq!(out, [10u8, 20, 30, 11, 22, 33]);
}
#[test]
fn png_sub_predictor_respects_bpp() {
let encoded = [1u8, 50, 60, 70, 5, 6, 7];
let p = PredictorParams {
predictor: 11,
colors: 3,
bits_per_component: 8,
columns: 2,
};
let out = apply_predictor(&encoded, &p).unwrap();
assert_eq!(out, [50u8, 60, 70, 55, 66, 77]);
}
#[test]
fn png_average_and_paeth_match_definitions() {
let encoded = [0u8, 100, 3, 30, 4, 5];
let p = PredictorParams {
predictor: 15,
colors: 1,
bits_per_component: 8,
columns: 1,
};
let out = apply_predictor(&encoded, &p).unwrap();
assert_eq!(out, [100u8, 80, 85]);
}
#[test]
fn tiff_predictor_2_eight_bit() {
let encoded = [5u8, 5, 5, 5];
let p = PredictorParams {
predictor: 2,
colors: 1,
bits_per_component: 8,
columns: 4,
};
let out = apply_predictor(&encoded, &p).unwrap();
assert_eq!(out, [5u8, 10, 15, 20]);
}
#[test]
fn tiff_predictor_2_rgb_interleaved() {
let encoded = [10u8, 20, 30, 30, 40, 50];
let p = PredictorParams {
predictor: 2,
colors: 3,
bits_per_component: 8,
columns: 2,
};
let out = apply_predictor(&encoded, &p).unwrap();
assert_eq!(out, [10u8, 20, 30, 40, 60, 80]);
}
#[test]
fn tiff_predictor_2_four_bit_components() {
let encoded = [0x32u8, 0x34];
let p = PredictorParams {
predictor: 2,
colors: 1,
bits_per_component: 4,
columns: 4,
};
let out = apply_predictor(&encoded, &p).unwrap();
assert_eq!(out, [0x35u8, 0x8C]);
}
#[test]
fn png_predictor_rejects_misaligned_data() {
let p = PredictorParams {
predictor: 12,
colors: 1,
bits_per_component: 8,
columns: 3,
};
assert!(apply_predictor(&[2u8, 1, 2], &p).is_err());
}
}