use zpdf_core::{ObjectId, PdfObject, Rect};
use zpdf_parser::PdfFile;
use crate::page::PdfPage;
pub const ANNOT_FLAG_HIDDEN: i64 = 1 << 1;
pub const ANNOT_FLAG_NOVIEW: i64 = 1 << 5;
#[derive(Debug, Clone)]
pub struct Annotation {
pub subtype: String,
pub rect: Rect,
pub flags: i64,
pub appearance: Option<ObjectId>,
pub oc: Option<PdfObject>,
}
impl Annotation {
pub fn is_viewable(&self) -> bool {
self.flags & (ANNOT_FLAG_HIDDEN | ANNOT_FLAG_NOVIEW) == 0
&& self.subtype != "Popup"
&& self.appearance.is_some()
&& self.rect.width() > 0.0
&& self.rect.height() > 0.0
}
}
pub fn parse_annotations(file: &PdfFile, page: &PdfPage) -> Vec<Annotation> {
page.annots
.iter()
.filter_map(|&id| parse_annotation(file, id))
.collect()
}
fn parse_annotation(file: &PdfFile, id: ObjectId) -> Option<Annotation> {
let obj = file.resolve(id).ok()?;
let dict = obj.as_dict().ok()?;
let subtype = dict.get_name("Subtype").unwrap_or("").to_string();
let rect = crate::page::resolve_rect(file, dict, "Rect")?;
let flags = match dict.get("F") {
Some(PdfObject::Integer(n)) => *n,
Some(PdfObject::Ref(r)) => file
.resolve(*r)
.ok()
.and_then(|o| o.as_i64().ok())
.unwrap_or(0),
_ => 0,
};
let appearance = select_appearance(file, dict);
let oc = dict.get("OC").cloned();
Some(Annotation {
subtype,
rect,
flags,
appearance,
oc,
})
}
fn select_appearance(file: &PdfFile, annot: &zpdf_core::PdfDict) -> Option<ObjectId> {
let ap = match annot.get("AP")? {
PdfObject::Dict(d) => d.clone(),
PdfObject::Ref(r) => file.resolve(*r).ok()?.as_dict().ok()?.clone(),
_ => return None,
};
let n = ap.get("N")?;
if let PdfObject::Ref(r) = n {
match file.resolve(*r).ok()? {
PdfObject::Stream(_) => return Some(*r),
PdfObject::Dict(states) => return select_state(file, &states, annot),
_ => return None,
}
}
if let PdfObject::Dict(states) = n {
return select_state(file, states, annot);
}
None
}
fn select_state(
file: &PdfFile,
states: &zpdf_core::PdfDict,
annot: &zpdf_core::PdfDict,
) -> Option<ObjectId> {
let as_name = annot.get_name("AS").ok();
if let Some(state) = as_name {
if let Some(PdfObject::Ref(r)) = states.get(state) {
return Some(*r);
}
}
if states.0.len() == 1 {
if let Some(PdfObject::Ref(r)) = states.0.values().next() {
return Some(*r);
}
}
let _ = file;
None
}