use super::content_mask::device_colour_space_components;
use lopdf::{DecompressError, Document, Error, Object, ObjectId};
pub(super) fn colour_space_components(
doc: &Document,
resources: &[&lopdf::Dictionary],
name: &[u8],
) -> Option<u32> {
resources
.iter()
.find_map(|scope| colour_space_bound(doc, scope, name))
.and_then(|space| components_of_colour_space(doc, space, 0))
}
pub(super) fn colour_space_bound<'a>(
doc: &'a Document,
scope: &'a lopdf::Dictionary,
name: &[u8],
) -> Option<&'a Object> {
let spaces = match scope.get(b"ColorSpace").ok()? {
Object::Dictionary(dict) => dict,
Object::Reference(id) => doc.get_dictionary(*id).ok()?,
_ => return None,
};
spaces.get(name).ok()
}
pub(super) fn components_of_colour_space(doc: &Document, space: &Object, depth: u8) -> Option<u32> {
if depth > 4 {
return None;
}
match space {
Object::Reference(id) => {
components_of_colour_space(doc, doc.get_object(*id).ok()?, depth + 1)
}
Object::Name(name) => device_colour_space_components(name),
Object::Array(items) => {
let family = match items.first()? {
Object::Reference(id) => doc.get_object(*id).ok()?.as_name().ok()?,
other => other.as_name().ok()?,
};
match family {
b"ICCBased" => {
let stream = match items.get(1)? {
Object::Reference(id) => doc.get_object(*id).ok()?,
other => other,
};
let n = stream.as_stream().ok()?.dict.get(b"N").ok()?;
let n = match n {
Object::Reference(id) => doc.get_object(*id).ok()?.as_i64().ok()?,
other => other.as_i64().ok()?,
};
u32::try_from(n).ok().filter(|n| matches!(n, 1 | 3 | 4))
}
b"Indexed" | b"I" | b"Separation" => Some(1),
b"DeviceN" => {
let names = match items.get(1)? {
Object::Reference(id) => doc.get_object(*id).ok()?.as_array().ok()?,
other => other.as_array().ok()?,
};
u32::try_from(names.len()).ok()
}
b"Pattern" => None,
other => device_colour_space_components(other),
}
}
_ => None,
}
}
pub(super) fn decoded_within(stream: &lopdf::Stream, limit: usize) -> Option<Vec<u8>> {
let raw_within = || (stream.content.len() <= limit).then(|| stream.content.clone());
if stream.dict.get(b"Filter").is_err() {
return raw_within();
}
match stream.decompressed_content_with_limit(limit) {
Ok(content) => Some(content),
Err(Error::Decompress(DecompressError::MemoryLimitExceeded { .. })) => None,
Err(_) => raw_within(),
}
}
pub(super) enum XObjectDrawn<'a> {
Image,
Form(ObjectId, &'a lopdf::Stream),
}
pub(super) fn resolve_xobject<'a>(
doc: &'a Document,
resources: &[&'a lopdf::Dictionary],
name: &[u8],
) -> Option<XObjectDrawn<'a>> {
for scope in resources {
let xobjects = match scope.get(b"XObject").ok() {
Some(Object::Dictionary(dict)) => dict,
Some(Object::Reference(id)) => match doc.get_dictionary(*id) {
Ok(dict) => dict,
Err(_) => continue,
},
_ => continue,
};
let Ok(entry) = xobjects.get(name) else {
continue;
};
let id = entry.as_reference().ok()?;
let Ok(Object::Stream(stream)) = doc.get_object(id) else {
return None;
};
let subtype = match stream.dict.get(b"Subtype").ok()? {
Object::Name(name) => Some(name.as_slice()),
Object::Reference(id) => doc.get_object(*id).ok().and_then(|o| o.as_name().ok()),
_ => None,
};
return match subtype {
Some(b"Image") => Some(XObjectDrawn::Image),
Some(b"Form") => Some(XObjectDrawn::Form(id, stream)),
_ => None,
};
}
None
}
pub(super) fn stream_resources<'a>(
doc: &'a Document,
stream: &'a lopdf::Stream,
) -> Option<&'a lopdf::Dictionary> {
match stream.dict.get(b"Resources").ok()? {
Object::Dictionary(dict) => Some(dict),
Object::Reference(id) => doc.get_dictionary(*id).ok(),
_ => None,
}
}
pub(super) fn resolve_pattern<'a>(
doc: &'a Document,
resources: &[&'a lopdf::Dictionary],
name: &[u8],
) -> Option<(ObjectId, &'a Object)> {
for scope in resources {
let patterns = match scope.get(b"Pattern").ok() {
Some(Object::Dictionary(dict)) => dict,
Some(Object::Reference(id)) => match doc.get_dictionary(*id) {
Ok(dict) => dict,
Err(_) => continue,
},
_ => continue,
};
let Ok(entry) = patterns.get(name) else {
continue;
};
let id = entry.as_reference().ok()?;
return doc.get_object(id).ok().map(|pattern| (id, pattern));
}
None
}
pub(super) fn pattern_type(doc: &Document, dict: &lopdf::Dictionary) -> Option<i64> {
match dict.get(b"PatternType").ok()? {
Object::Reference(id) => doc.get_object(*id).ok()?.as_i64().ok(),
other => other.as_i64().ok(),
}
}
pub(super) fn numbers_of<const N: usize>(
doc: &Document,
dict: &lopdf::Dictionary,
key: &[u8],
) -> Option<[f64; N]> {
let array = match dict.get(key).ok()? {
Object::Array(array) => array,
Object::Reference(id) => doc.get_object(*id).ok()?.as_array().ok()?,
_ => return None,
};
if array.len() < N {
return None;
}
let mut numbers = [0.0f64; N];
for (slot, value) in numbers.iter_mut().zip(array) {
*slot = match value {
Object::Reference(id) => doc.get_object(*id).ok().and_then(coordinate),
other => coordinate(other),
}?;
}
Some(numbers)
}
pub(super) fn coordinate(value: &Object) -> Option<f64> {
match value {
Object::Integer(n) => Some(*n as f64),
Object::Real(r) => Some(f64::from(*r)),
_ => None,
}
}