use crate::color::Color;
use crate::object::{ObjRef, PdfDict, PdfObject};
use crate::writer::PdfWriter;
#[derive(Debug, Clone, Copy, Default)]
pub struct OutlineFlags {
pub italic: bool,
pub bold: bool,
}
impl OutlineFlags {
fn to_pdf_flags(&self) -> i64 {
let mut f = 0i64;
if self.italic { f |= 1; }
if self.bold { f |= 2; }
f
}
}
#[derive(Debug, Clone)]
pub enum Destination {
Page(usize),
PageXY { page: usize, x: f64, y: f64, zoom: Option<f64> },
PageFit(usize),
}
#[derive(Debug, Clone)]
pub struct OutlineItem {
pub title: String,
pub dest: Destination,
pub color: Option<Color>,
pub flags: OutlineFlags,
pub children: Vec<OutlineItem>,
pub collapsed: bool,
}
impl OutlineItem {
pub fn new(title: impl Into<String>, dest: Destination) -> Self {
Self {
title: title.into(),
dest,
color: None,
flags: OutlineFlags::default(),
children: Vec::new(),
collapsed: false,
}
}
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
pub fn bold(mut self) -> Self {
self.flags.bold = true;
self
}
pub fn italic(mut self) -> Self {
self.flags.italic = true;
self
}
pub fn collapsed(mut self) -> Self {
self.collapsed = true;
self
}
pub fn child(mut self, item: OutlineItem) -> Self {
self.children.push(item);
self
}
}
#[derive(Debug, Default)]
pub struct Outline {
pub items: Vec<OutlineItem>,
}
impl Outline {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn add(mut self, item: OutlineItem) -> Self {
self.items.push(item);
self
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn write(
&self,
writer: &mut PdfWriter,
page_refs: &[ObjRef],
) -> ObjRef {
let root_ref = writer.reserve();
let (first, last, count) = self.write_items(writer, &self.items, page_refs, root_ref);
let mut root = PdfDict::new();
root.set("Type", PdfObject::name("Outlines"));
if let (Some(f), Some(l)) = (first, last) {
root.set("First", PdfObject::Reference(f));
root.set("Last", PdfObject::Reference(l));
}
root.set("Count", PdfObject::Integer(count as i64));
writer.write_object(root_ref, &PdfObject::Dictionary(root));
root_ref
}
fn write_items(
&self,
writer: &mut PdfWriter,
items: &[OutlineItem],
page_refs: &[ObjRef],
parent_ref: ObjRef,
) -> (Option<ObjRef>, Option<ObjRef>, usize) {
if items.is_empty() {
return (None, None, 0);
}
let refs: Vec<ObjRef> = items.iter().map(|_| writer.reserve()).collect();
let mut open_count = 0usize;
for (i, (item, &item_ref)) in items.iter().zip(refs.iter()).enumerate() {
let prev = if i > 0 { Some(refs[i - 1]) } else { None };
let next = refs.get(i + 1).copied();
let (child_first, child_last, child_count) =
self.write_items(writer, &item.children, page_refs, item_ref);
let this_count = if item.collapsed {
-(child_count as i64)
} else {
child_count as i64
};
if !item.collapsed {
open_count += 1 + child_count;
} else {
open_count += 1;
}
let mut dict = PdfDict::new();
dict.set("Title", PdfObject::string(item.title.as_str()));
dict.set("Parent", PdfObject::Reference(parent_ref));
if let Some(p) = prev { dict.set("Prev", PdfObject::Reference(p)); }
if let Some(n) = next { dict.set("Next", PdfObject::Reference(n)); }
if let (Some(f), Some(l)) = (child_first, child_last) {
dict.set("First", PdfObject::Reference(f));
dict.set("Last", PdfObject::Reference(l));
dict.set("Count", PdfObject::Integer(this_count));
}
let dest = build_dest(&item.dest, page_refs);
if !dest.is_empty() {
dict.set("Dest", PdfObject::Array(dest));
}
if let Some(col) = item.color {
if let Color::Rgb(r, g, b) = col {
dict.set("C", PdfObject::Array(vec![
PdfObject::Real(r),
PdfObject::Real(g),
PdfObject::Real(b),
]));
}
}
let flags = item.flags.to_pdf_flags();
if flags != 0 {
dict.set("F", PdfObject::Integer(flags));
}
writer.write_object(item_ref, &PdfObject::Dictionary(dict));
}
(refs.first().copied(), refs.last().copied(), open_count)
}
}
fn build_dest(dest: &Destination, page_refs: &[ObjRef]) -> Vec<PdfObject> {
match dest {
Destination::Page(n) => {
if let Some(&r) = page_refs.get(*n) {
vec![
PdfObject::Reference(r),
PdfObject::name("XYZ"),
PdfObject::Null,
PdfObject::Null,
PdfObject::Null,
]
} else { vec![] }
}
Destination::PageXY { page, x, y, zoom } => {
if let Some(&r) = page_refs.get(*page) {
vec![
PdfObject::Reference(r),
PdfObject::name("XYZ"),
PdfObject::Real(*x),
PdfObject::Real(*y),
zoom.map(PdfObject::Real).unwrap_or(PdfObject::Null),
]
} else { vec![] }
}
Destination::PageFit(n) => {
if let Some(&r) = page_refs.get(*n) {
vec![PdfObject::Reference(r), PdfObject::name("Fit")]
} else { vec![] }
}
}
}