use crate::WarningSinkFn;
use crate::function::{Clamper, TupleVec, Values, interpolate};
use crate::util::decode_or_warn;
use log::{error, warn};
use pdf_syntax::bit_reader::BitReader;
use pdf_syntax::object::Array;
use pdf_syntax::object::Stream;
use pdf_syntax::object::dict::keys::{BITS_PER_SAMPLE, DECODE, ENCODE, SIZE};
use smallvec::{SmallVec, ToSmallVec, smallvec};
use std::collections::HashMap;
#[derive(Debug)]
pub(crate) struct Type0 {
sizes: IntVec,
table: HashMap<Key, IntVec>,
clamper: Clamper,
range: TupleVec,
bits_per_sample: u8,
encode: TupleVec,
decode: TupleVec,
}
impl Type0 {
pub(crate) fn new(stream: &Stream<'_>, warning_sink: &WarningSinkFn) -> Option<Self> {
let dict = stream.dict();
let bits_per_sample = dict.get::<u8>(BITS_PER_SAMPLE)?;
if !matches!(bits_per_sample, 1 | 2 | 4 | 8 | 16 | 24 | 32) {
error!("invalid bits per sample: {bits_per_sample}");
return None;
}
let clamper = Clamper::new(dict)?;
let range = clamper.range.clone()?;
if range.is_empty() {
warn!("encountered Type0 function with invalid range length 0.");
return None;
}
let sizes = dict
.get::<Array<'_>>(SIZE)?
.iter::<u32>()
.collect::<IntVec>();
let encode = dict.get::<TupleVec>(ENCODE).unwrap_or(
sizes
.iter()
.map(|s| (0.0, s.saturating_sub(1) as f32))
.collect(),
);
let decode = dict.get::<TupleVec>(DECODE).unwrap_or(range.clone());
let mut data = {
let decoded = decode_or_warn(stream, warning_sink)?;
let mut buf = vec![];
let mut reader = BitReader::new(&decoded);
while let Some(data) = reader.read(bits_per_sample) {
buf.push(data);
}
buf
};
let num_expected_entries = sizes
.iter()
.try_fold(1usize, |acc, s| acc.checked_mul(*s as usize))
.and_then(|n| n.checked_mul(range.len()));
let Some(num_expected_entries) = num_expected_entries else {
warn!("Type0 function /Size product overflows; rejecting function.");
return None;
};
if data.len() != num_expected_entries {
warn!("Type0 function didn't have the expected number of sample entries.");
data.truncate(num_expected_entries);
}
let table = build_table(&data, &sizes, range.len())?;
Some(Self {
sizes,
clamper,
range,
bits_per_sample,
table,
encode,
decode,
})
}
pub(crate) fn eval(&self, mut input: Values) -> Option<Values> {
if input.len() != self.sizes.len() {
warn!("wrong number of arguments for sampled function");
return None;
}
self.clamper.clamp_input(&mut input);
let mut key = input;
for (((x, domain), encode), size) in key
.iter_mut()
.zip(self.clamper.domain.iter())
.zip(self.encode.iter())
.zip(self.sizes.iter())
{
*x = interpolate(*x, domain.0, domain.1, encode.0, encode.1);
*x = x.max(0.0).min(*size as f32 - 1.0);
}
let in_prev = key.iter().map(|v| v.floor() as u32).collect::<IntVec>();
let in_next = key.iter().map(|v| v.ceil() as u32).collect::<IntVec>();
let interpolator = Interpolator::new(
key.clone().to_smallvec(),
in_prev,
in_next,
self.sizes.clone(),
self.range.len(),
);
let interpolated = interpolator.interpolate(&self.table)?;
let mut out = interpolated
.iter()
.zip(self.decode.iter())
.map(|(x, decode)| {
interpolate(
*x,
0.0,
((1u64 << self.bits_per_sample) - 1) as f32,
decode.0,
decode.1,
)
})
.collect::<SmallVec<_>>();
self.clamper.clamp_output(&mut out);
Some(out)
}
}
type FloatVec = SmallVec<[f32; 4]>;
type IntVec = SmallVec<[u32; 4]>;
struct Interpolator {
input: FloatVec,
sizes: IntVec,
in_prev: IntVec,
in_next: IntVec,
out_len: usize,
}
impl Interpolator {
fn new(
input: FloatVec,
in_prev: IntVec,
in_next: IntVec,
sizes: IntVec,
out_len: usize,
) -> Self {
Self {
input,
in_prev,
in_next,
sizes,
out_len,
}
}
fn interpolate(&self, table: &HashMap<Key, IntVec>) -> Option<FloatVec> {
self.interpolate_inner(smallvec![0; self.input.len()], 0, table)
}
fn interpolate_inner(
&self,
mut coord: IntVec,
step: usize,
table: &HashMap<Key, IntVec>,
) -> Option<FloatVec> {
if step == self.input.len() - 1 {
if self.in_prev[step] == self.in_next[step] {
coord[step] = self.in_prev[step];
Some(
table
.get(&Key::from_raw(&self.sizes, &coord))?
.clone()
.iter()
.map(|n| *n as f32)
.collect(),
)
} else {
coord[step] = self.in_prev[step];
let val1 = table.get(&Key::from_raw(&self.sizes, &coord))?;
coord[step] = self.in_next[step];
let val2 = table.get(&Key::from_raw(&self.sizes, &coord))?;
let mut out = smallvec![0.0; self.out_len];
for i in 0..self.out_len {
out[i] = interpolate(
self.input[step],
self.in_prev[step] as f32,
self.in_next[step] as f32,
val1[i] as f32,
val2[i] as f32,
);
}
Some(out)
}
} else if self.in_prev[step] == self.in_next[step] {
coord[step] = self.in_prev[step];
self.interpolate_inner(coord, step + 1, table)
} else {
coord[step] = self.in_prev[step];
let val1 = self.interpolate_inner(coord.clone(), step + 1, table)?;
coord[step] = self.in_next[step];
let val2 = self.interpolate_inner(coord, step + 1, table)?;
let mut out = smallvec![0.0; self.out_len];
for i in 0..self.out_len {
out[i] = interpolate(
self.input[step],
self.in_prev[step] as f32,
self.in_next[step] as f32,
val1[i],
val2[i],
);
}
Some(out)
}
}
}
fn build_table(data: &[u32], sizes: &[u32], n: usize) -> Option<HashMap<Key, IntVec>> {
let mut key = Key::new(sizes);
let mut table = HashMap::new();
let mut first = true;
for b in data.chunks_exact(n) {
if !first {
key.increment();
}
table.insert(key.clone(), b.to_smallvec());
first = false;
}
Some(table)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Key {
sizes: SmallVec<[u32; 4]>,
parts: SmallVec<[u32; 4]>,
}
impl Key {
fn new(sizes: &[u32]) -> Self {
let parts = smallvec![0; sizes.len()];
Self {
sizes: sizes.to_smallvec(),
parts,
}
}
fn from_raw(sizes: &[u32], parts: &[u32]) -> Self {
Self {
sizes: sizes.to_smallvec(),
parts: parts.to_smallvec(),
}
}
fn increment(&mut self) -> Option<()> {
self.increment_index(0)
}
fn increment_index(&mut self, index: usize) -> Option<()> {
let size = *self.sizes.get(index).or_else(|| {
error!("overflowed key in sampled function");
None
})?;
let val = self.parts.get_mut(index)?;
if *val >= (size - 1) {
*val = 0;
self.increment_index(index + 1)?;
} else {
*val += 1;
}
Some(())
}
}
#[cfg(test)]
mod tests {
use crate::function::Function;
use pdf_syntax::Pdf;
use pdf_syntax::object::{Object, ObjectIdentifier};
use smallvec::smallvec;
fn pdf_with_function(function_body: &[u8]) -> Pdf {
let objects: Vec<Vec<u8>> = vec![
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>".to_vec(),
function_body.to_vec(),
];
let mut out = b"%PDF-1.7\n".to_vec();
let mut offsets = Vec::new();
for (i, body) in objects.iter().enumerate() {
offsets.push(out.len());
out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
out.extend_from_slice(body);
out.extend_from_slice(b"\nendobj\n");
}
let xref_pos = out.len();
out.extend_from_slice(format!("xref\n0 {}\n", objects.len() + 1).as_bytes());
out.extend_from_slice(b"0000000000 65535 f \n");
for offset in offsets {
out.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
}
out.extend_from_slice(
format!(
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_pos}\n%%EOF\n",
objects.len() + 1
)
.as_bytes(),
);
Pdf::new(out).expect("test PDF parses")
}
#[test]
fn type0_bits_per_sample_32_does_not_overflow() {
let mut body =
b"<< /FunctionType 0 /Domain [0 1] /Range [0 1] /Size [2] /BitsPerSample 32 /Length 8 >>\nstream\n"
.to_vec();
body.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF]);
body.extend_from_slice(b"\nendstream");
let pdf = pdf_with_function(&body);
let obj: Object<'_> = pdf
.xref()
.get(ObjectIdentifier::new(4, 0))
.expect("function object resolves");
let function = Function::new(&obj).expect("Type0 with 32 bps parses");
for x in [0.0f32, 0.25, 0.5, 0.75, 1.0] {
let out = function.eval(smallvec![x]).expect("eval succeeds");
assert!(
(0.0..=1.0).contains(&out[0]),
"output in range for x={x}: {out:?}"
);
}
}
#[test]
fn type0_pathological_sizes_rejected_without_panic() {
let zero_size =
b"<< /FunctionType 0 /Domain [0 1] /Range [0 1] /Size [0] /BitsPerSample 8 /Length 0 >>\nstream\n\nendstream"
.to_vec();
let pdf = pdf_with_function(&zero_size);
let obj: Object<'_> = pdf.xref().get(ObjectIdentifier::new(4, 0)).unwrap();
let _ = Function::new(&obj);
let huge =
b"<< /FunctionType 0 /Domain [0 1 0 1 0 1] /Range [0 1] /Size [4000000000 4000000000 4000000000] /BitsPerSample 8 /Length 0 >>\nstream\n\nendstream"
.to_vec();
let pdf = pdf_with_function(&huge);
let obj: Object<'_> = pdf.xref().get(ObjectIdentifier::new(4, 0)).unwrap();
assert!(
Function::new(&obj).is_none(),
"overflowing /Size product is rejected"
);
}
}