use crate::bindgen::{FPDF_BOOKMARK, FPDF_DOCUMENT};
use crate::bindings::PdfiumLibraryBindings;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::document::bookmark::PdfBookmark;
use std::collections::HashSet;
use std::ptr::null_mut;
pub struct PdfBookmarks<'a> {
document_handle: FPDF_DOCUMENT,
bindings: &'a dyn PdfiumLibraryBindings,
}
impl<'a> PdfBookmarks<'a> {
#[inline]
pub(crate) fn from_pdfium(
document_handle: FPDF_DOCUMENT,
bindings: &'a dyn PdfiumLibraryBindings,
) -> Self {
Self {
document_handle,
bindings,
}
}
#[inline]
pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
self.document_handle
}
#[inline]
pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
self.bindings
}
pub fn root(&self) -> Option<PdfBookmark<'_>> {
let bookmark_handle = self
.bindings
.FPDFBookmark_GetFirstChild(self.document_handle, null_mut());
if bookmark_handle.is_null() {
None
} else {
Some(PdfBookmark::from_pdfium(
bookmark_handle,
None,
self.document_handle,
self.bindings,
))
}
}
pub fn find_first_by_title(&self, title: &str) -> Result<PdfBookmark<'_>, PdfiumError> {
let handle = 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,
self.bindings,
))
}
}
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(),
self.bindings(),
)
}
}
pub struct PdfBookmarksIterator<'a> {
include_descendants: bool,
pending_stack: Vec<(FPDF_BOOKMARK, FPDF_BOOKMARK)>,
visited: HashSet<FPDF_BOOKMARK>,
skip_sibling: FPDF_BOOKMARK,
document_handle: FPDF_DOCUMENT,
bindings: &'a dyn PdfiumLibraryBindings,
}
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,
bindings: &'a dyn PdfiumLibraryBindings,
) -> Self {
let mut result = PdfBookmarksIterator {
document_handle,
include_descendants,
pending_stack: Vec::with_capacity(20),
visited: HashSet::new(),
skip_sibling: null_mut(),
bindings,
};
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(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((
self.bindings
.FPDFBookmark_GetNextSibling(self.document_handle, node),
parent,
));
if self.include_descendants {
self.pending_stack.push((
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,
self.bindings,
));
}
}
None
}
}