Skip to main content

pdfluent_lopdf/
bookmarks.rs

1use super::{Dictionary, Document, Object, ObjectId};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5pub struct Bookmark {
6    /// Children, Must be a Collection that allows for insertion of the same page ID.
7    pub children: Vec<u32>,
8    pub title: String,
9    /// 0, 1 for italic, 2 for bold, 3 for italic bold
10    pub format: u32,
11    /// R,G,B
12    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                // If the title contains non-ASCII characters:
75                // Create a new vector with the UTF-16 Byte Order Mark (BOM) for UTF-16BE.
76                let mut bom = vec![0xFE, 0xFF];
77                let utf16_title = bookmark.title.encode_utf16();
78                // Append the UTF-16BE encoded bytes of the title to the BOM.
79                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}