use pdfrum_object::{Dict, Name, Object, Resolve, names};
use crate::{doc::EditDoc, error::Error};
type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ViewerPreferences {
hide_toolbar: Option<bool>,
hide_menubar: Option<bool>,
hide_window_ui: Option<bool>,
fit_window: Option<bool>,
center_window: Option<bool>,
display_doc_title: Option<bool>,
direction_r2l: Option<bool>,
print_scaling: Option<bool>,
num_copies: Option<i64>,
duplex: Option<Duplex>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Duplex {
Simplex,
DuplexFlipShortEdge,
DuplexFlipLongEdge,
}
impl Duplex {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Simplex => "Simplex",
Self::DuplexFlipShortEdge => "DuplexFlipShortEdge",
Self::DuplexFlipLongEdge => "DuplexFlipLongEdge",
}
}
}
impl ViewerPreferences {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn hide_toolbar(mut self, yes: bool) -> Self {
self.hide_toolbar = Some(yes);
self
}
#[must_use]
pub fn hide_menubar(mut self, yes: bool) -> Self {
self.hide_menubar = Some(yes);
self
}
#[must_use]
pub fn hide_window_ui(mut self, yes: bool) -> Self {
self.hide_window_ui = Some(yes);
self
}
#[must_use]
pub fn fit_window(mut self, yes: bool) -> Self {
self.fit_window = Some(yes);
self
}
#[must_use]
pub fn center_window(mut self, yes: bool) -> Self {
self.center_window = Some(yes);
self
}
#[must_use]
pub fn display_doc_title(mut self, yes: bool) -> Self {
self.display_doc_title = Some(yes);
self
}
#[must_use]
pub fn direction_r2l(mut self, yes: bool) -> Self {
self.direction_r2l = Some(yes);
self
}
#[must_use]
pub fn print_scaling(mut self, yes: bool) -> Self {
self.print_scaling = Some(yes);
self
}
#[must_use]
pub fn num_copies(mut self, copies: i64) -> Self {
self.num_copies = Some(copies);
self
}
#[must_use]
pub fn duplex(mut self, duplex: Duplex) -> Self {
self.duplex = Some(duplex);
self
}
fn merge_into(&self, mut dict: Dict) -> Dict {
let booleans = [
("HideToolbar", self.hide_toolbar),
("HideMenubar", self.hide_menubar),
("HideWindowUI", self.hide_window_ui),
("FitWindow", self.fit_window),
("CenterWindow", self.center_window),
("DisplayDocTitle", self.display_doc_title),
];
for (key, value) in booleans {
if let Some(value) = value {
dict.insert(Name::from(key), Object::Bool(value));
}
}
if let Some(r2l) = self.direction_r2l {
let value = if r2l { "R2L" } else { "L2R" };
dict.insert(
Name::from("Direction"),
Object::Name(Name::from(value.as_bytes())),
);
}
if let Some(scaling) = self.print_scaling {
let value = if scaling { "AppDefault" } else { "None" };
dict.insert(
Name::from("PrintScaling"),
Object::Name(Name::from(value.as_bytes())),
);
}
if let Some(copies) = self.num_copies {
dict.insert(Name::from("NumCopies"), Object::Int(copies));
}
if let Some(duplex) = self.duplex {
dict.insert(
Name::from("Duplex"),
Object::Name(Name::from(duplex.as_str().as_bytes())),
);
}
dict
}
}
pub fn set_viewer_preferences(
dest: &mut EditDoc<'_>,
preferences: &ViewerPreferences,
) -> Result<()> {
let Some(root) = dest.base().trailer().reference(names::ROOT) else {
return Err(Error::NoDestinationCatalog);
};
let Some(mut catalog) = dest
.fetch(root)
.ok()
.and_then(|object| object.as_dict().cloned())
else {
return Err(Error::NoDestinationCatalog);
};
match catalog.raw(names::VIEWER_PREFERENCES).cloned() {
Some(Object::Ref(prefs_ref)) => {
let existing = dest
.fetch(prefs_ref)
.ok()
.and_then(|object| object.as_dict().cloned())
.unwrap_or_default();
dest.replace(prefs_ref, Object::Dict(preferences.merge_into(existing)));
}
existing => {
let dict = match existing {
Some(Object::Dict(dict)) => dict,
_ => Dict::new(),
};
catalog.insert(
names::VIEWER_PREFERENCES.clone(),
Object::Dict(preferences.merge_into(dict)),
);
dest.replace(root, Object::Dict(catalog));
}
}
Ok(())
}
pub fn set_open_action(
dest: &mut EditDoc<'_>,
page: impl Into<pdfrum_common::PageIndex>,
view: crate::AnnotGoToView,
) -> Result<()> {
let page = page.into();
let Some((page_ref, _, _)) = dest.page_state(page)? else {
return Err(Error::InlinePage(page));
};
let Some(root) = dest.base().trailer().reference(names::ROOT) else {
return Err(Error::NoDestinationCatalog);
};
let Some(mut catalog) = dest
.fetch(root)
.ok()
.and_then(|object| object.as_dict().cloned())
else {
return Err(Error::NoDestinationCatalog);
};
catalog.insert(
crate::names::OPEN_ACTION.clone(),
Object::Array(crate::annot::goto_dest_array(page_ref, view)),
);
dest.replace(root, Object::Dict(catalog));
Ok(())
}
pub fn clear_open_action(dest: &mut EditDoc<'_>) -> Result<bool> {
let Some(root) = dest.base().trailer().reference(names::ROOT) else {
return Err(Error::NoDestinationCatalog);
};
let Some(mut catalog) = dest
.fetch(root)
.ok()
.and_then(|object| object.as_dict().cloned())
else {
return Err(Error::NoDestinationCatalog);
};
if !catalog.contains_key(crate::names::OPEN_ACTION) {
return Ok(false);
}
catalog.remove(crate::names::OPEN_ACTION);
dest.replace(root, Object::Dict(catalog));
Ok(true)
}