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