use pdfrum_common::PageIndex;
use pdfrum_object::{Array, Dict, Object, PdfString, Resolve, encode_text, names};
use crate::{doc::EditDoc, error::Error};
type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PageLabelStyle {
#[default]
None,
Decimal,
UpperRoman,
LowerRoman,
UpperLetters,
LowerLetters,
}
impl PageLabelStyle {
fn key(self) -> Option<&'static str> {
match self {
Self::None => None,
Self::Decimal => Some("D"),
Self::UpperRoman => Some("R"),
Self::LowerRoman => Some("r"),
Self::UpperLetters => Some("A"),
Self::LowerLetters => Some("a"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageLabelRange {
start: PageIndex,
style: PageLabelStyle,
prefix: Option<String>,
first: Option<i64>,
}
impl PageLabelRange {
#[must_use]
pub fn new(start: impl Into<PageIndex>, style: PageLabelStyle) -> Self {
Self {
start: start.into(),
style,
prefix: None,
first: None,
}
}
#[must_use]
pub fn start(&self) -> PageIndex {
self.start
}
#[must_use]
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
#[must_use]
pub fn first(mut self, first: i64) -> Self {
self.first = Some(first);
self
}
fn to_dict(&self) -> Dict {
let mut dict = Dict::new();
if let Some(style) = self.style.key() {
dict.insert(
names::S.clone(),
Object::Name(pdfrum_object::Name::from(style.as_bytes())),
);
}
if let Some(prefix) = &self.prefix {
dict.insert(
crate::names::P.clone(),
Object::Str(PdfString::literal(encode_text(prefix))),
);
}
if let Some(first) = self.first {
dict.insert(crate::names::ST.clone(), Object::Int(first));
}
dict
}
}
pub fn set_page_labels(dest: &mut EditDoc<'_>, ranges: &[PageLabelRange]) -> 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);
};
if ranges.is_empty() {
catalog.remove(names::PAGE_LABELS);
dest.replace(root, Object::Dict(catalog));
return Ok(());
}
let mut ranges: Vec<&PageLabelRange> = ranges.iter().collect();
ranges.sort_by_key(|range| range.start.get());
let mut nums = Array::default();
for (index, range) in ranges.iter().enumerate() {
let superseded = ranges
.get(index + 1..)
.is_some_and(|rest| rest.iter().any(|other| other.start == range.start));
if superseded {
continue;
}
nums.push(Object::Int(i64::from(range.start.get())));
nums.push(Object::Dict(range.to_dict()));
}
let tree = Dict::from_pairs([(crate::names::NUMS.clone(), Object::Array(nums))]);
catalog.insert(names::PAGE_LABELS.clone(), Object::Dict(tree));
dest.replace(root, Object::Dict(catalog));
Ok(())
}