pdfluent_lopdf/
bookmarks.rs1use super::{Dictionary, Document, Object, ObjectId};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5pub struct Bookmark {
6 pub children: Vec<u32>,
8 pub title: String,
9 pub format: u32,
11 pub color: [f32; 3],
13 pub page: ObjectId,
14 pub id: u32,
15}
16
17impl Bookmark {
18 pub fn new(title: String, color: [f32; 3], format: u32, page: ObjectId) -> Bookmark {
19 Bookmark {
20 children: Vec::new(),
21 title,
22 format,
23 color,
24 page,
25 id: 0,
26 }
27 }
28}
29
30impl Document {
31 pub fn add_bookmark(&mut self, mut bookmark: Bookmark, parent: Option<u32>) -> u32 {
32 self.max_bookmark_id += 1;
33 let id = self.max_bookmark_id;
34
35 bookmark.id = id;
36
37 if let Some(p) = parent {
38 if let Some(b) = self.bookmark_table.get_mut(&p) {
39 b.children.push(id);
40 }
41 } else {
42 self.bookmarks.push(id);
43 }
44
45 self.bookmark_table.insert(id, bookmark);
46 id
47 }
48
49 fn outline_child(
50 &self,
51 maxid: &mut u32,
52 parent: (ObjectId, &[u32]),
53 processed: &mut HashMap<ObjectId, Dictionary>,
54 ) -> (Option<ObjectId>, Option<ObjectId>, i64) {
55 let mut first: Option<ObjectId> = None;
56 let mut last: Option<ObjectId> = None;
57 let count = parent.1.len();
58 for i in parent.1 {
59 let mut child = Dictionary::new();
60 *maxid += 1;
61 let id: ObjectId = (*maxid, 0);
62 *maxid += 1;
63 let info_id: ObjectId = (*maxid, 0);
64 let bookmark = self.bookmark_table.get(i).unwrap();
65
66 let info = dictionary! {
67 "D" => vec![bookmark.page.into(), Object::Name("Fit".into())],
68 "S" => "GoTo",
69 };
70
71 let title_bytes = if bookmark.title.is_ascii() {
72 bookmark.title.as_bytes().to_vec()
73 } else {
74 let mut bom = vec![0xFE, 0xFF];
77 let utf16_title = bookmark.title.encode_utf16();
78 bom.extend(utf16_title.flat_map(u16::to_be_bytes));
80 bom
81 };
82
83 child.set("Parent", parent.0);
84 child.set("Title", Object::string_literal(title_bytes));
85 child.set("A", info_id);
86 child.set("F", Object::Integer(bookmark.format.into()));
87 child.set(
88 "C",
89 vec![
90 bookmark.color[0].into(),
91 bookmark.color[1].into(),
92 bookmark.color[2].into(),
93 ],
94 );
95
96 if first.is_none() {
97 first = Some(id);
98 } else if let Some(x) = last {
99 let inner_object = processed.get_mut(&x).unwrap();
100 inner_object.set("Next", id);
101 child.set("Prev", x);
102 }
103
104 last = Some(id);
105
106 if !bookmark.children.is_empty() {
107 let (c_first, c_last, c_count) =
108 self.outline_child(maxid, (id, &bookmark.children[..]), processed);
109
110 if let Some(n) = c_first {
111 child.set("First", n);
112 }
113
114 if let Some(n) = c_last {
115 child.set("Last", n);
116 }
117
118 child.set("Count", c_count);
119 }
120
121 processed.insert(id, child);
122 processed.insert(info_id, info);
123 }
124
125 (first, last, count as i64)
126 }
127
128 pub fn build_outline(&mut self) -> Option<ObjectId> {
129 let mut processed: HashMap<ObjectId, Dictionary> = HashMap::new();
130
131 if !self.bookmarks.is_empty() {
132 let mut outline = Dictionary::new();
133 let mut maxid = self.max_id;
134 maxid += 1;
135 let id: ObjectId = (maxid, 0);
136
137 let (first, last, count) =
138 self.outline_child(&mut maxid, (id, &self.bookmarks[..]), &mut processed);
139
140 if let Some(n) = first {
141 outline.set("First", n);
142 }
143
144 if let Some(n) = last {
145 outline.set("Last", n);
146 }
147
148 outline.set("Count", Object::Integer(count));
149
150 for (obj_id, obj) in processed.drain() {
151 self.objects.insert(obj_id, obj.into());
152 }
153
154 self.objects.insert(id, outline.into());
155 self.max_id = maxid;
156 return Some(id);
157 }
158
159 None
160 }
161}