1use crate::bindgen::{FPDF_BOOKMARK, FPDF_DOCUMENT};
5use crate::pdf::action::PdfAction;
6use crate::pdf::destination::PdfDestination;
7use crate::pdf::document::bookmarks::PdfBookmarksIterator;
8use crate::pdfium::PdfiumLibraryBindingsAccessor;
9use crate::utils::mem::create_byte_buffer;
10use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
11use std::hash::{Hash, Hasher};
12use std::marker::PhantomData;
13use std::os::raw::c_void;
14
15#[cfg(doc)]
16use {
17 crate::pdf::action::PdfActionType, crate::pdf::document::bookmarks::PdfBookmarks,
18 crate::pdf::document::PdfDocument,
19};
20
21#[derive(Clone)]
23pub struct PdfBookmark<'a> {
24 bookmark_handle: FPDF_BOOKMARK,
25 parent: Option<FPDF_BOOKMARK>,
26 document_handle: FPDF_DOCUMENT,
27 lifetime: PhantomData<&'a FPDF_BOOKMARK>,
28}
29
30impl<'a> PartialEq for PdfBookmark<'a> {
31 fn eq(&self, other: &Self) -> bool {
32 self.bookmark_handle == other.bookmark_handle
49 }
50}
51
52impl<'a> Eq for PdfBookmark<'a> {}
53
54impl<'a> Hash for PdfBookmark<'a> {
55 fn hash<H>(&self, state: &mut H)
56 where
57 H: Hasher,
58 {
59 self.bookmark_handle.hash(state);
60 }
61}
62
63impl<'a> PdfBookmark<'a> {
64 pub(crate) fn from_pdfium(
65 bookmark_handle: FPDF_BOOKMARK,
66 parent: Option<FPDF_BOOKMARK>,
67 document_handle: FPDF_DOCUMENT,
68 ) -> Self {
69 PdfBookmark {
70 bookmark_handle,
71 parent,
72 document_handle,
73 lifetime: PhantomData,
74 }
75 }
76
77 #[inline]
79 pub(crate) fn bookmark_handle(&self) -> FPDF_BOOKMARK {
80 self.bookmark_handle
81 }
82
83 #[inline]
85 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
86 self.document_handle
87 }
88
89 pub fn title(&self) -> Option<String> {
91 let buffer_length = unsafe {
100 self.bindings()
101 .FPDFBookmark_GetTitle(self.bookmark_handle, std::ptr::null_mut(), 0)
102 };
103
104 if buffer_length == 0 {
105 return None;
108 }
109
110 let mut buffer = create_byte_buffer(buffer_length as usize);
111
112 let result = unsafe {
113 self.bindings().FPDFBookmark_GetTitle(
114 self.bookmark_handle,
115 buffer.as_mut_ptr() as *mut c_void,
116 buffer_length,
117 )
118 };
119
120 assert_eq!(result, buffer_length);
121
122 get_string_from_pdfium_utf16le_bytes(buffer)
123 }
124
125 pub fn action(&self) -> Option<PdfAction<'a>> {
132 let handle = unsafe { self.bindings().FPDFBookmark_GetAction(self.bookmark_handle) };
133
134 if handle.is_null() {
135 None
136 } else {
137 Some(PdfAction::from_pdfium(
138 handle,
139 self.document_handle,
140 self.bindings(),
141 ))
142 }
143 }
144
145 pub fn destination(&self) -> Option<PdfDestination<'a>> {
150 let handle = unsafe {
151 self.bindings()
152 .FPDFBookmark_GetDest(self.document_handle, self.bookmark_handle)
153 };
154
155 if handle.is_null() {
156 None
157 } else {
158 Some(PdfDestination::from_pdfium(self.document_handle, handle))
159 }
160 }
161
162 #[inline]
164 pub fn parent(&self) -> Option<PdfBookmark<'a>> {
165 self.parent.map(|parent_handle| {
166 PdfBookmark::from_pdfium(parent_handle, None, self.document_handle)
167 })
168 }
169
170 #[inline]
172 pub fn children_len(&self) -> usize {
173 (unsafe {
177 self.bindings()
178 .FPDFBookmark_GetCount(self.bookmark_handle)
179 .unsigned_abs()
180 }) as usize
181 }
182
183 pub fn first_child(&self) -> Option<PdfBookmark<'a>> {
185 let handle = unsafe {
186 self.bindings()
187 .FPDFBookmark_GetFirstChild(self.document_handle, self.bookmark_handle)
188 };
189
190 if handle.is_null() {
191 None
192 } else {
193 Some(PdfBookmark::from_pdfium(
194 handle,
195 Some(self.bookmark_handle),
196 self.document_handle,
197 ))
198 }
199 }
200
201 pub fn next_sibling(&self) -> Option<PdfBookmark<'a>> {
203 let handle = unsafe {
204 self.bindings()
205 .FPDFBookmark_GetNextSibling(self.document_handle, self.bookmark_handle)
206 };
207
208 if handle.is_null() {
209 None
210 } else {
211 Some(PdfBookmark::from_pdfium(
212 handle,
213 self.parent,
214 self.document_handle,
215 ))
216 }
217 }
218
219 #[inline]
221 pub fn iter_siblings(&self) -> PdfBookmarksIterator<'a> {
222 match self.parent {
223 Some(parent_handle) => {
224 PdfBookmarksIterator::new(
229 PdfBookmark::from_pdfium(parent_handle, None, self.document_handle)
230 .first_child(),
231 false,
232 Some(self.clone()),
235 self.document_handle(),
236 )
237 }
238 None => {
239 PdfBookmarksIterator::new(
244 Some(self.clone()),
245 false,
246 Some(self.clone()),
249 self.document_handle(),
250 )
251 }
252 }
253 }
254
255 #[inline]
260 pub fn iter_direct_children(&self) -> PdfBookmarksIterator<'a> {
261 PdfBookmarksIterator::new(self.first_child(), false, None, self.document_handle())
262 }
263
264 #[inline]
268 pub fn iter_all_descendants(&self) -> PdfBookmarksIterator<'a> {
269 PdfBookmarksIterator::new(self.first_child(), true, None, self.document_handle())
270 }
271}
272
273impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfBookmark<'a> {}
274
275#[cfg(feature = "thread_safe")]
276unsafe impl<'a> Send for PdfBookmark<'a> {}
277
278#[cfg(feature = "thread_safe")]
279unsafe impl<'a> Sync for PdfBookmark<'a> {}
280
281#[cfg(test)]
282mod tests {
283 use crate::prelude::*;
284 use crate::utils::test::test_bind_to_pdfium;
285 use std::hash::{DefaultHasher, Hash, Hasher};
286
287 #[test]
288 fn test_bookmarks() -> Result<(), PdfiumError> {
289 fn title(bookmark: PdfBookmark) -> String {
290 bookmark.title().expect("Bookmark Title")
291 }
292
293 fn hash(b: &PdfBookmark) -> u64 {
294 let mut s = DefaultHasher::new();
295 b.hash(&mut s);
296 s.finish()
297 }
298
299 let pdfium = test_bind_to_pdfium();
300 let document = pdfium.load_pdf_from_file("./test/test-toc.pdf", None)?;
301
302 let section3 = document.bookmarks().find_first_by_title("Section 3")?;
304 let section4 = document.bookmarks().find_first_by_title("Section 4")?;
305
306 let direct_children: Vec<String> = section3.iter_direct_children().map(title).collect();
309 let expected: Vec<String> = (1..6).map(|i| format!("Section 3.{i}")).collect();
310 assert_eq!(direct_children, expected);
311 assert_eq!(section3.children_len(), 5);
312
313 assert_eq!(section4.iter_direct_children().count(), 0);
315 assert_eq!(section4.children_len(), 0);
316
317 let all_children: Vec<String> = section3.iter_all_descendants().map(title).collect();
319 let expected = [
320 "Section 3.1",
321 "Section 3.2",
322 "Section 3.2.1",
323 "Section 3.2.2",
324 "Section 3.2.3",
325 "Section 3.2.4",
326 "Section 3.2.5",
327 "Section 3.2.6",
328 "Section 3.2.7",
329 "Section 3.2.8",
330 "Section 3.2.9",
331 "Section 3.2.10",
332 "Section 3.2.11",
333 "Section 3.2.12",
334 "Section 3.3",
335 "Section 3.3.1",
336 "Section 3.3.2",
337 "Section 3.3.2.1",
338 "Section 3.3.2.2",
339 "Section 3.3.2.3",
340 "Section 3.3.3",
341 "Section 3.3.4",
342 "Section 3.3.5",
343 "Section 3.3.6",
344 "Section 3.4",
345 "Section 3.4.1",
346 "Section 3.4.2",
347 "Section 3.5",
348 "Section 3.5.1",
349 "Section 3.5.2",
350 ];
351 assert_eq!(all_children, expected);
352
353 assert_eq!(section4.iter_all_descendants().count(), 0);
355
356 let siblings: Vec<String> = section3.iter_siblings().map(title).collect();
359 assert_eq!(siblings, ["Section 4", "Section 5"]);
360
361 let section3_2_6 = section3
363 .iter_all_descendants()
364 .find(|bookmark| bookmark.title() == Some("Section 3.2.6".into()))
365 .expect("§3.2.6");
366 assert!(section3_2_6.parent().is_some());
367 let siblings: Vec<String> = section3_2_6.iter_siblings().map(title).collect();
370 let expected = [
371 "Section 3.2.1",
372 "Section 3.2.2",
373 "Section 3.2.3",
374 "Section 3.2.4",
375 "Section 3.2.5",
376 "Section 3.2.7",
377 "Section 3.2.8",
378 "Section 3.2.9",
379 "Section 3.2.10",
380 "Section 3.2.11",
381 "Section 3.2.12",
382 ];
383 assert_eq!(siblings, expected);
384
385 let section5_3_3_1 = document
387 .bookmarks()
388 .find_first_by_title("Section 5.3.3.1")?;
389 assert!(section5_3_3_1.parent().is_none());
390 assert_eq!(section5_3_3_1.iter_siblings().count(), 0);
391 let section5_3_3_1 = document
393 .bookmarks()
394 .iter()
395 .find(|bookmark| bookmark.title() == Some("Section 5.3.3.1".into()))
396 .expect("§5.3.3.1");
397 assert!(section5_3_3_1.parent().is_some());
398 assert_eq!(section5_3_3_1.iter_siblings().count(), 0);
399
400 for bookmark in document.bookmarks().iter() {
402 match bookmark.parent() {
403 None => assert!(!title(bookmark).contains('.')),
406 Some(parent) => {
407 let this_title = title(bookmark);
411 let last_dot = this_title.rfind('.').unwrap();
412 assert_eq!(title(parent), this_title[..last_dot]);
413 }
414 }
415 }
416
417 let all_bookmarks: Vec<_> = document.bookmarks().iter().map(title).collect();
418 let expected = [
419 "Section 1",
420 "Section 2",
421 "Section 3",
422 "Section 3.1",
423 "Section 3.2",
424 "Section 3.2.1",
425 "Section 3.2.2",
426 "Section 3.2.3",
427 "Section 3.2.4",
428 "Section 3.2.5",
429 "Section 3.2.6",
430 "Section 3.2.7",
431 "Section 3.2.8",
432 "Section 3.2.9",
433 "Section 3.2.10",
434 "Section 3.2.11",
435 "Section 3.2.12",
436 "Section 3.3",
437 "Section 3.3.1",
438 "Section 3.3.2",
439 "Section 3.3.2.1",
440 "Section 3.3.2.2",
441 "Section 3.3.2.3",
442 "Section 3.3.3",
443 "Section 3.3.4",
444 "Section 3.3.5",
445 "Section 3.3.6",
446 "Section 3.4",
447 "Section 3.4.1",
448 "Section 3.4.2",
449 "Section 3.5",
450 "Section 3.5.1",
451 "Section 3.5.2",
452 "Section 4",
453 "Section 5",
454 "Section 5.1",
455 "Section 5.2",
456 "Section 5.3",
457 "Section 5.3.1",
458 "Section 5.3.2",
459 "Section 5.3.3",
460 "Section 5.3.3.1",
461 "Section 5.3.4",
462 "Section 5.4",
463 "Section 5.4.1",
464 "Section 5.4.1.1",
465 "Section 5.4.1.2",
466 "Section 5.4.2",
467 "Section 5.4.2.1",
468 "Section 5.4.3",
469 "Section 5.4.4",
470 "Section 5.4.5",
471 "Section 5.4.6",
472 "Section 5.4.7",
473 "Section 5.4.8",
474 "Section 5.4.8.1",
475 "Section 5.5",
476 "Section 5.5.1",
477 "Section 5.5.2",
478 "Section 5.5.3",
479 "Section 5.5.4",
480 "Section 5.5.5",
481 "Section 5.5.5.1",
482 "Section 5.5.5.2",
483 "Section 5.5.5.3",
484 "Section 5.5.6",
485 "Section 5.5.6.1",
486 "Section 5.5.6.2",
487 "Section 5.5.6.3",
488 "Section 5.5.7",
489 "Section 5.5.7.1",
490 "Section 5.5.7.2",
491 "Section 5.5.7.3",
492 "Section 5.5.7.4",
493 "Section 5.5.7.5",
494 "Section 5.5.7.6",
495 "Section 5.5.8",
496 "Section 5.5.8.1",
497 "Section 5.5.8.2",
498 "Section 5.5.8.3",
499 "Section 5.5.8.4",
500 "Section 5.5.8.5",
501 "Section 5.5.8.6",
502 "Section 5.5.8.7",
503 "Section 5.5.8.8",
504 "Section 5.5.8.9",
505 "Section 5.5.9",
506 "Section 5.5.10",
507 "Section 5.6",
508 "Section 5.7",
509 "Section 5.7.1",
510 "Section 5.7.2",
511 "Section 5.7.3",
512 "Section 5.7.3.1",
513 "Section 5.7.3.2",
514 "Section 5.7.3.3",
515 "Section 5.7.3.4",
516 "Section 5.7.4",
517 "Section 5.7.4.1",
518 "Section 5.7.4.2",
519 "Section 5.7.4.3",
520 ];
521 assert_eq!(all_bookmarks, expected);
522
523 let all_bookmarks: Vec<_> = document.bookmarks().iter().collect();
525 for i in 0..all_bookmarks.len() {
526 assert!(all_bookmarks[i] == all_bookmarks[i]);
527 assert_eq!(hash(&all_bookmarks[i]), hash(&all_bookmarks[i]));
528 for j in (i + 1)..all_bookmarks.len() {
529 assert!(all_bookmarks[i] != all_bookmarks[j]);
530 }
531 }
532
533 let all_bookmarks2: Vec<_> = document.bookmarks().iter().collect();
536 assert!(all_bookmarks == all_bookmarks2);
537
538 let the_clone = all_bookmarks[0].clone();
540 assert!(all_bookmarks[0] == the_clone);
541 assert_eq!(hash(&all_bookmarks[0]), hash(&the_clone));
542
543 let document2 = pdfium.load_pdf_from_file("./test/test-toc.pdf", None)?;
545 let all_bookmarks2: Vec<_> = document2.bookmarks().iter().collect();
546 assert_eq!(all_bookmarks.len(), all_bookmarks2.len());
547 for i in 0..all_bookmarks.len() {
548 for j in 0..all_bookmarks.len() {
549 assert!(all_bookmarks[i] != all_bookmarks2[j]);
550 }
551 }
552
553 Ok(())
554 }
555}