lopdf/
outlines.rs

1use indexmap::IndexMap;
2
3use super::{Destination, Dictionary, Document, Error, Object, Result};
4
5pub enum Outline {
6    Destination(Destination),
7    SubOutlines(Vec<Outline>),
8}
9
10impl Document {
11    pub fn get_outline(
12        &self, node: &Dictionary, named_destinations: &mut IndexMap<Vec<u8>, Destination>,
13    ) -> Result<Option<Outline>> {
14        let action = match self.get_dict_in_dict(node, b"A") {
15            Ok(a) => a,
16            Err(_) => {
17                return self.build_outline_result(node.get(b"Dest")?, node.get(b"Title")?, named_destinations);
18            }
19        };
20        let command = action.get(b"S")?.as_name()?;
21        if command != b"GoTo" && command != b"GoToR" {
22            return Err(Error::InvalidOutline("Expected GoTo or GoToR".to_string()));
23        }
24        let title_obj = node.get(b"Title")?;
25        let title_ref = match title_obj.as_reference() {
26            Ok(o) => o,
27            Err(_) => match title_obj.as_str() {
28                Ok(_) => return self.build_outline_result(action.get(b"D")?, title_obj, named_destinations),
29                Err(err) => return Err(err),
30            },
31        };
32        self.build_outline_result(action.get(b"D")?, self.get_object(title_ref)?, named_destinations)
33    }
34
35    pub fn get_outlines(
36        &self, mut node: Option<Object>, mut outlines: Option<Vec<Outline>>,
37        named_destinations: &mut IndexMap<Vec<u8>, Destination>,
38    ) -> Result<Option<Vec<Outline>>> {
39        if outlines.is_none() {
40            outlines = Some(Vec::new());
41            let catalog = self.catalog()?;
42            let mut dict_node = self.get_dict_in_dict(catalog, b"Outlines")?;
43            let first = self.get_dict_in_dict(dict_node, b"First");
44            if let Ok(first) = first {
45                dict_node = first;
46            }
47            let mut tree = self.get_dict_in_dict(catalog, b"Dests");
48            if tree.is_err() {
49                let names = self.get_dict_in_dict(catalog, b"Names");
50                if let Ok(names) = names {
51                    let dests = self.get_dict_in_dict(names, b"Dests");
52                    if dests.is_ok() {
53                        tree = dests;
54                    }
55                }
56            }
57            if let Ok(tree) = tree {
58                self.get_named_destinations(tree, named_destinations)?;
59            }
60            node = Some(Object::Dictionary(dict_node.clone()));
61        }
62        if node.is_none() {
63            return Ok(outlines);
64        }
65        let node = node.unwrap();
66        let mut node = match node.as_dict() {
67            Ok(n) => n,
68            Err(_) => self.get_object(node.as_reference()?)?.as_dict()?,
69        };
70        loop {
71            if let Ok(Some(outline)) = self.get_outline(node, named_destinations) {
72                if let Some(ref mut outlines) = outlines {
73                    outlines.push(outline);
74                }
75            }
76            if let Ok(first) = node.get(b"First") {
77                let sub_outlines = Vec::new();
78                let sub_outlines = self.get_outlines(Some(first.clone()), Some(sub_outlines), named_destinations)?;
79                if let Some(sub_outlines) = sub_outlines {
80                    if !sub_outlines.is_empty() {
81                        if let Some(ref mut outlines) = outlines {
82                            outlines.push(Outline::SubOutlines(sub_outlines));
83                        }
84                    }
85                }
86            }
87            node = match self.get_dict_in_dict(node, b"Next") {
88                Ok(n) => n,
89                Err(_) => break,
90            };
91        }
92        Ok(outlines)
93    }
94
95    fn build_outline_result(
96        &self, dest: &Object, title: &Object, named_destinations: &mut IndexMap<Vec<u8>, Destination>,
97    ) -> Result<Option<Outline>> {
98        let outline = match dest {
99            Object::Array(obj_array) => Outline::Destination(Destination::new(
100                title.to_owned(),
101                obj_array[0].clone(),
102                obj_array[1].clone(),
103            )),
104            Object::String(key, _fmt) => {
105                if let Some(destination) = named_destinations.get_mut(key) {
106                    destination.set(b"Title", title.to_owned());
107                    Outline::Destination(destination.clone())
108                } else {
109                    return Ok(None);
110                }
111            }
112            Object::Reference(object_id) => {
113                return self.build_outline_result(self.get_object(*object_id)?, title, named_destinations);
114            }
115            _ => return Err(Error::InvalidOutline(format!("Unexpected destination {dest:?}"))),
116        };
117        Ok(Some(outline))
118    }
119}