use pdfrum_object::{Array, Dict, Name, ObjRef, Object, Resolve, names};
use peniko::Color;
use crate::{doc::EditDoc, error::Error};
type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct WidgetAppearance {
background: Option<Color>,
border: Option<Color>,
rotation: Option<i64>,
caption: Option<String>,
}
impl WidgetAppearance {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn background(mut self, color: Color) -> Self {
self.background = Some(color);
self
}
#[must_use]
pub fn border(mut self, color: Color) -> Self {
self.border = Some(color);
self
}
#[must_use]
pub fn rotation(mut self, degrees: i64) -> Self {
self.rotation = Some(degrees);
self
}
#[must_use]
pub fn caption(mut self, text: impl Into<String>) -> Self {
self.caption = Some(text.into());
self
}
pub(crate) fn to_dict(&self) -> Dict {
self.merge_into(Dict::new())
}
fn merge_into(&self, mut mk: Dict) -> Dict {
if let Some(color) = self.background {
mk.insert(names::BG.clone(), color_object(color));
}
if let Some(color) = self.border {
mk.insert(names::BC.clone(), color_object(color));
}
if let Some(degrees) = self.rotation {
mk.insert(Name::from("R"), Object::Int(degrees));
}
if let Some(caption) = &self.caption {
mk.insert(
names::CA.clone(),
Object::Str(pdfrum_object::PdfString::literal(caption.as_bytes())),
);
}
mk
}
}
pub(crate) fn mk_key() -> Name {
Name::from("MK")
}
fn color_object(color: Color) -> Object {
let [r, g, b, _] = color.components;
Object::Array(Array::of([
Object::Real(r.clamp(0.0, 1.0)),
Object::Real(g.clamp(0.0, 1.0)),
Object::Real(b.clamp(0.0, 1.0)),
]))
}
pub fn set_widget_appearance(
dest: &mut EditDoc<'_>,
annot: ObjRef,
appearance: &WidgetAppearance,
) -> Result<()> {
let Some(dict) = dest
.fetch(annot)
.ok()
.and_then(|object| object.as_dict().cloned())
else {
return Err(Error::Object(pdfrum_object::Error::UnresolvedRef(annot)));
};
match dict.raw(&mk_key()).cloned() {
Some(Object::Ref(mk_ref)) => {
let mk = dest
.fetch(mk_ref)
.ok()
.and_then(|object| object.as_dict().cloned())
.unwrap_or_default();
dest.replace(mk_ref, Object::Dict(appearance.merge_into(mk)));
}
existing => {
let mk = match existing {
Some(Object::Dict(mk)) => mk,
_ => Dict::new(),
};
let mut dict = dict;
dict.insert(mk_key(), Object::Dict(appearance.merge_into(mk)));
dest.replace(annot, Object::Dict(dict));
}
}
Ok(())
}
pub fn set_need_appearances(dest: &mut EditDoc<'_>, needed: bool) -> Result<()> {
let key = Name::from("NeedAppearances");
let Some(root) = dest.base().trailer().reference(names::ROOT) else {
return Err(Error::NoDestinationCatalog);
};
let Ok(fetched) = dest.fetch(root) else {
return Err(Error::NoDestinationCatalog);
};
let Some(mut catalog) = fetched.as_dict().cloned() else {
return Err(Error::NoDestinationCatalog);
};
match catalog.raw(names::ACRO_FORM).cloned() {
Some(Object::Ref(form_ref)) => {
let mut form = dest
.fetch(form_ref)
.ok()
.and_then(|object| object.as_dict().cloned())
.unwrap_or_default();
set_flag(&mut form, &key, needed);
dest.replace(form_ref, Object::Dict(form));
}
Some(Object::Dict(mut form)) => {
set_flag(&mut form, &key, needed);
catalog.insert(names::ACRO_FORM.clone(), Object::Dict(form));
dest.replace(root, Object::Dict(catalog));
}
_ => {
let mut form = Dict::new();
set_flag(&mut form, &key, needed);
catalog.insert(names::ACRO_FORM.clone(), Object::Dict(form));
dest.replace(root, Object::Dict(catalog));
}
}
Ok(())
}
fn set_flag(form: &mut Dict, key: &Name, needed: bool) {
if needed {
form.insert(key.clone(), Object::Bool(true));
} else {
form.remove(key);
}
}