use pdfrum_object::{Dict, Name, Resolve};
#[derive(Debug, Clone)]
pub struct Signature {
field: Dict,
}
#[must_use]
pub fn signatures(catalog: &Dict, r: &impl Resolve) -> Vec<Signature> {
let Some(acro) = catalog.dict(&Name::from("AcroForm"), r) else {
return Vec::new();
};
let Some(fields) = acro.array(&Name::from("Fields"), r) else {
return Vec::new();
};
let ft = Name::from("FT");
fields
.iter()
.filter_map(|object| object.resolve(r).ok()?.get().as_dict().cloned())
.filter(|dict| {
dict.get(&ft, r).is_some_and(|value| {
value
.get()
.as_name()
.is_some_and(|n| n.as_bytes() == b"Sig")
})
})
.map(|field| Signature { field })
.collect()
}
impl Signature {
fn value(&self, r: &impl Resolve) -> Option<Dict> {
self.field.dict(&Name::from("V"), r)
}
#[must_use]
pub fn contents(&self, r: &impl Resolve) -> Vec<u8> {
self.value(r)
.and_then(|v| v.byte_string(&Name::from("Contents"), r))
.unwrap_or_default()
}
#[must_use]
pub fn byte_range(&self, r: &impl Resolve) -> Vec<i64> {
let Some(array) = self
.value(r)
.and_then(|v| v.array(&Name::from("ByteRange"), r))
else {
return Vec::new();
};
array
.iter()
.map(|object| {
object
.resolve(r)
.ok()
.and_then(|o| o.get().as_int())
.unwrap_or(0)
})
.collect()
}
#[must_use]
pub fn sub_filter(&self, r: &impl Resolve) -> Option<String> {
let value = self.value(r)?;
let key = Name::from("SubFilter");
if !value.contains_key(&key) {
return None;
}
let name = value.get(&key, r).and_then(|o| {
o.get()
.as_name()
.map(|n| String::from_utf8_lossy(n.as_bytes()).into_owned())
});
Some(name.unwrap_or_default())
}
#[must_use]
pub fn reason(&self, r: &impl Resolve) -> Option<String> {
let value = self.value(r)?;
let object = value.get(&Name::from("Reason"), r)?;
object.get().as_string().map(|s| s.as_text().into_owned())
}
#[must_use]
pub fn time(&self, r: &impl Resolve) -> Option<String> {
let value = self.value(r)?;
let object = value.get(&Name::from("M"), r)?;
object
.get()
.as_string()
.map(|s| String::from_utf8_lossy(&s.bytes).into_owned())
}
#[must_use]
pub fn doc_mdp_permission(&self, r: &impl Resolve) -> u32 {
let Some(references) = self
.value(r)
.and_then(|v| v.array(&Name::from("Reference"), r))
else {
return 0;
};
for object in references.iter() {
let Some(reference) = object
.resolve(r)
.ok()
.and_then(|o| o.get().as_dict().cloned())
else {
continue;
};
let is_doc_mdp = reference
.get(&Name::from("TransformMethod"), r)
.is_some_and(|o| o.get().as_name().is_some_and(|n| n.as_bytes() == b"DocMDP"));
if !is_doc_mdp {
continue;
}
let Some(params) = reference.dict(&Name::from("TransformParams"), r) else {
continue;
};
let permission = params.int(&Name::from("P"), r).unwrap_or(2);
return if (1..=3).contains(&permission) {
u32::try_from(permission).unwrap_or(0)
} else {
0
};
}
0
}
}