Skip to content

Commit

Permalink
feat(outlines): set outlines
Browse files Browse the repository at this point in the history
  • Loading branch information
TD-Sky committed Jul 7, 2024
1 parent 0d6658e commit bae3dd5
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 9 deletions.
34 changes: 27 additions & 7 deletions src/destination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::Error;
#[derive(Debug, Clone)]
pub struct Destination {
/// Indirect reference to page object.
pub page: PdfObject,
pub kind: DestinationKind,
page: PdfObject,
kind: DestinationKind,
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum DestinationKind {
/// Display the page at a scale which just fits the whole page
/// in the window both horizontally and vertically.
Expand All @@ -21,7 +21,11 @@ pub enum DestinationKind {
FitV { left: f32 },
/// Display the page with (`left`, `top`) at the upper-left corner
/// of the window and the page magnified by factor `zoom`.
XYZ { left: f32, top: f32, zoom: f32 },
XYZ {
left: Option<f32>,
top: Option<f32>,
zoom: Option<f32>,
},
/// Display the page zoomed to show the rectangle specified by `left`, `bottom`, `right`, and `top`.
FitR {
left: f32,
Expand All @@ -41,6 +45,10 @@ pub enum DestinationKind {
}

impl Destination {
pub(crate) fn new(page: PdfObject, kind: DestinationKind) -> Self {
Self { page, kind }
}

/// Encode destination into a PDF array.
pub(crate) fn encode_into(self, array: &mut PdfObject) -> Result<(), Error> {
debug_assert_eq!(array.len()?, 0);
Expand All @@ -58,9 +66,21 @@ impl Destination {
}
DestinationKind::XYZ { left, top, zoom } => {
array.array_push(PdfObject::new_name("XYZ")?)?;
array.array_push(PdfObject::new_real(left)?)?;
array.array_push(PdfObject::new_real(top)?)?;
array.array_push(PdfObject::new_real(zoom)?)?;
array.array_push(
left.map(PdfObject::new_real)
.transpose()?
.unwrap_or(PdfObject::new_null()),
)?;
array.array_push(
top.map(PdfObject::new_real)
.transpose()?
.unwrap_or(PdfObject::new_null()),
)?;
array.array_push(
zoom.map(PdfObject::new_real)
.transpose()?
.unwrap_or(PdfObject::new_null()),
)?;
}
DestinationKind::FitR {
left,
Expand Down
88 changes: 86 additions & 2 deletions src/pdf/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use num_enum::TryFromPrimitive;

use crate::pdf::{PdfGraftMap, PdfObject, PdfPage};
use crate::{
context, Buffer, CjkFontOrdering, Document, Error, Font, Image, SimpleFontEncoding, Size,
WriteMode,
context, Buffer, CjkFontOrdering, Destination, DestinationKind, Document, Error, Font, Image,
Outline, SimpleFontEncoding, Size, WriteMode,
};

bitflags! {
Expand Down Expand Up @@ -573,6 +573,90 @@ impl PdfDocument {
Ok(())
}

pub fn set_outlines(&mut self, toc: &[Outline]) -> Result<(), Error> {
self.delete_outlines()?;

if !toc.is_empty() {
let mut outlines = self.new_dict()?;
outlines.dict_put("Type", PdfObject::new_name("Outlines")?)?;
// Now we access outlines indirectly
let mut outlines = self.add_object(&outlines)?;
self.walk_outlines_insert(toc, &mut outlines)?;
self.catalog()?.dict_put("Outlines", outlines)?;
}

Ok(())
}

fn walk_outlines_insert(
&mut self,
down: &[Outline],
parent: &mut PdfObject,
) -> Result<(), Error> {
debug_assert!(!down.is_empty() && parent.is_indirect()?);

// All the indirect references in the current level.
let mut refs = Vec::new();

for outline in down {
let mut item = self.new_dict()?;
item.dict_put("Title", PdfObject::new_string(&outline.title)?)?;
item.dict_put("Parent", parent.clone())?;
if let Some(dest) = outline
.page
.map(|page| {
let page = self.find_page(page as i32)?;

let has_x = !outline.x.is_nan();
let has_y = !outline.y.is_nan();
let dest_kind = match (has_x, has_y) {
(true, true) => DestinationKind::XYZ {
left: Some(outline.x),
top: Some(outline.y),
zoom: None,
},
(true, false) => DestinationKind::FitV { left: outline.x },
(false, true) => DestinationKind::FitH { top: outline.y },
(false, false) => DestinationKind::Fit,
};
let dest = Destination::new(page, dest_kind);

let mut array = self.new_array()?;
dest.encode_into(&mut array)?;

Ok(array)
})
.or_else(|| outline.uri.as_deref().map(PdfObject::new_string))
.transpose()?
{
item.dict_put("Dest", dest)?;
}

refs.push(self.add_object(&item)?);
if !outline.down.is_empty() {
self.walk_outlines_insert(&outline.down, refs.last_mut().unwrap())?;
}
}

// NOTE: doing the same thing as mutation version of `slice::array_windows`
for i in 0..down.len().saturating_sub(1) {
let [prev, next, ..] = &mut refs[i..] else {
unreachable!();
};
prev.dict_put("Next", next.clone())?;
next.dict_put("Prev", prev.clone())?;
}

let mut refs = refs.into_iter();
let first = refs.next().unwrap();
let last = refs.last().unwrap_or_else(|| first.clone());

parent.dict_put("First", first)?;
parent.dict_put("Last", last)?;

Ok(())
}

/// Delete `/Outlines` in document catalog and all the **outline items** it points to.
///
/// Do nothing if document has no outlines.
Expand Down

0 comments on commit bae3dd5

Please sign in to comment.