use crate::bindgen::{FPDF_BOOKMARK, FPDF_DOCUMENT};
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::document::bookmark::PdfBookmark;
use crate::pdfium::PdfiumLibraryBindingsAccessor;
use std::collections::HashSet;
use std::marker::PhantomData;
pub struct PdfBookmarks<'a> {
document_handle: FPDF_DOCUMENT,
lifetime: PhantomData<&'a FPDF_DOCUMENT>,
}
impl<'a> PdfBookmarks<'a> {
#[inline]
pub(crate) fn from_pdfium(document_handle: FPDF_DOCUMENT) -> Self {
Self {
document_handle,
lifetime: PhantomData,
}
}
#[inline]
pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
self.document_handle
}
pub fn root(&self) -> Option<PdfBookmark<'_>> {
let bookmark_handle = unsafe {
self.bindings()
.FPDFBookmark_GetFirstChild(self.document_handle, std::ptr::null_mut())
};
if bookmark_handle.is_null() {
None
} else {
Some(PdfBookmark::from_pdfium(
bookmark_handle,
None,
self.document_handle,
))
}
}
pub fn find_first_by_title(&self, title: &str) -> Result<PdfBookmark<'_>, PdfiumError> {
let handle = unsafe {
self.bindings()
.FPDFBookmark_Find_str(self.document_handle, title)
};
if handle.is_null() {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
Ok(PdfBookmark::from_pdfium(handle, None, self.document_handle))
}
}
pub fn find_all_by_title(&self, title: &str) -> Vec<PdfBookmark<'_>> {
self.iter()
.filter(|bookmark| match bookmark.title() {
Some(bookmark_title) => bookmark_title == title,
None => false,
})
.collect()
}
#[inline]
pub fn iter(&self) -> PdfBookmarksIterator<'_> {
PdfBookmarksIterator::new(self.root(), true, None, self.document_handle())
}
}
impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfBookmarks<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Send for PdfBookmarks<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Sync for PdfBookmarks<'a> {}
pub struct PdfBookmarksIterator<'a> {
document_handle: FPDF_DOCUMENT,
include_descendants: bool,
pending_stack: Vec<(FPDF_BOOKMARK, FPDF_BOOKMARK)>,
visited: HashSet<FPDF_BOOKMARK>,
skip_sibling: FPDF_BOOKMARK,
lifetime: PhantomData<&'a FPDF_BOOKMARK>,
}
impl<'a> PdfBookmarksIterator<'a> {
pub(crate) fn new(
start_node: Option<PdfBookmark<'a>>,
include_descendants: bool,
skip_sibling: Option<PdfBookmark<'a>>,
document_handle: FPDF_DOCUMENT,
) -> Self {
let mut result = PdfBookmarksIterator {
document_handle,
include_descendants,
pending_stack: Vec::with_capacity(20),
visited: HashSet::new(),
skip_sibling: std::ptr::null_mut(),
lifetime: PhantomData,
};
if let Some(skip_sibling) = skip_sibling {
result.skip_sibling = skip_sibling.bookmark_handle();
}
if let Some(start_node) = start_node {
result.pending_stack.push((
start_node.bookmark_handle(),
start_node
.parent()
.map(|parent| parent.bookmark_handle())
.unwrap_or(std::ptr::null_mut()),
));
}
result
}
}
impl<'a> Iterator for PdfBookmarksIterator<'a> {
type Item = PdfBookmark<'a>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((node, parent)) = self.pending_stack.pop() {
if node.is_null() || self.visited.contains(&node) {
continue;
}
self.visited.insert(node);
self.pending_stack.push((
unsafe {
self.bindings()
.FPDFBookmark_GetNextSibling(self.document_handle, node)
},
parent,
));
if self.include_descendants {
self.pending_stack.push((
unsafe {
self.bindings()
.FPDFBookmark_GetFirstChild(self.document_handle, node)
},
node,
));
}
if node != self.skip_sibling {
let parent = if parent.is_null() { None } else { Some(parent) };
return Some(PdfBookmark::from_pdfium(node, parent, self.document_handle));
}
}
None
}
}
impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfBookmarksIterator<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Send for PdfBookmarksIterator<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Sync for PdfBookmarksIterator<'a> {}