Skip to main content

falcon_pdf/
tree.rs

1//! The layers list's tree, from a configuration's `/Order` array. The
2//! reading of the file itself is platform code; this turns what it read
3//! into the tree, following the PDF spec and what pdf.js shows.
4//!
5//! In `/Order` an optional content group is listed by reference. An array
6//! whose first element is a string is a labelled group of what follows the
7//! label; the label is text, not a switch. Any other array that follows a
8//! group nests under that group. Groups the order never names are listed
9//! last, under "additional layers", as pdf.js does. Turning a group on or
10//! off never changes the groups under it: the nesting is only for reading.
11
12/// What a walk of `/Order` found.
13#[derive(Clone, Debug, PartialEq)]
14pub enum OrderItem {
15    /// A group of `/OCGs`, by its index there.
16    Group(usize),
17    /// A string.
18    Text(String),
19    /// A nested array.
20    Array(Vec<OrderItem>),
21    /// Anything else, such as a reference to something that isn't one of
22    /// the document's groups. Kept so that what follows isn't mistaken for
23    /// its children.
24    Other,
25}
26
27/// One row of the layers tree.
28#[derive(Clone, Debug, PartialEq)]
29pub enum LayerNode {
30    /// A switch: the group's index in [`crate::Document::layers`], and the
31    /// rows nested under it.
32    Layer { index: usize, children: Vec<LayerNode> },
33    /// A heading with no switch.
34    Label { text: String, children: Vec<LayerNode> },
35    /// The heading for the groups `/Order` leaves out; its wording is the
36    /// reader's.
37    Additional(Vec<LayerNode>),
38}
39
40/// pdf.js stops reading nested arrays past this depth.
41const MAX_LEVELS: usize = 10;
42
43/// The tree for `order`, in a document of `groups` groups. Indices past
44/// `groups` are skipped. Nothing when the document has no groups.
45pub fn layer_tree(order: &[OrderItem], groups: usize) -> Vec<LayerNode> {
46    let mut seen = vec![false; groups];
47    let mut tree = level(order, 0, &mut seen);
48    let rest: Vec<LayerNode> = (0..groups).filter(|&i| !seen[i]).map(|index| LayerNode::Layer { index, children: Vec::new() }).collect();
49    if !rest.is_empty() {
50        tree.push(LayerNode::Additional(rest));
51    }
52    tree
53}
54
55fn level(items: &[OrderItem], depth: usize, seen: &mut [bool]) -> Vec<LayerNode> {
56    let mut out: Vec<LayerNode> = Vec::new();
57    if depth > MAX_LEVELS {
58        return out;
59    }
60    // Whether the last item was a group, whose children an array may be.
61    let mut after_group = false;
62    for item in items {
63        let mut group = false;
64        match item {
65            OrderItem::Group(i) if *i < seen.len() => {
66                seen[*i] = true;
67                out.push(LayerNode::Layer { index: *i, children: Vec::new() });
68                group = true;
69            }
70            OrderItem::Array(inner) => match inner.first() {
71                Some(OrderItem::Text(text)) => {
72                    let children = level(&inner[1..], depth + 1, seen);
73                    if !children.is_empty() {
74                        out.push(LayerNode::Label { text: text.clone(), children });
75                    }
76                }
77                _ => {
78                    let nested = level(inner, depth + 1, seen);
79                    match out.last_mut() {
80                        Some(LayerNode::Layer { children, .. }) if after_group => children.extend(nested),
81                        _ => out.extend(nested),
82                    }
83                }
84            },
85            _ => {}
86        }
87        after_group = group;
88    }
89    out
90}
91
92#[cfg(test)]
93mod tests {
94    use super::OrderItem::*;
95    use super::*;
96
97    fn layer(index: usize, children: Vec<LayerNode>) -> LayerNode {
98        LayerNode::Layer { index, children }
99    }
100
101    fn label(text: &str, children: Vec<LayerNode>) -> LayerNode {
102        LayerNode::Label { text: text.into(), children }
103    }
104
105    #[test]
106    fn groups_labels_and_children_nest() {
107        let order = [Group(0), Array(vec![Group(1), Group(2)]), Array(vec![Text("Notes".into()), Group(3)])];
108        assert_eq!(layer_tree(&order, 4), vec![layer(0, vec![layer(1, vec![]), layer(2, vec![])]), label("Notes", vec![layer(3, vec![])])]);
109    }
110
111    #[test]
112    fn groups_left_out_go_last_and_unknown_ones_are_skipped() {
113        let order = [Group(2), Group(9), Other, Array(vec![Text("Empty".into())])];
114        assert_eq!(layer_tree(&order, 3), vec![layer(2, vec![]), LayerNode::Additional(vec![layer(0, vec![]), layer(1, vec![])])]);
115        assert!(layer_tree(&[], 0).is_empty());
116    }
117
118    #[test]
119    fn an_array_after_something_skipped_is_not_its_children() {
120        let order = [Group(0), Other, Array(vec![Group(1)])];
121        assert_eq!(layer_tree(&order, 2), vec![layer(0, vec![]), layer(1, vec![])]);
122        // With no group before it at all.
123        assert_eq!(layer_tree(&[Array(vec![Group(1)])], 2), vec![layer(1, vec![]), LayerNode::Additional(vec![layer(0, vec![])])]);
124    }
125
126    #[test]
127    fn a_group_listed_twice_is_shown_twice() {
128        assert_eq!(layer_tree(&[Group(0), Array(vec![Text("Again".into()), Group(0)])], 1), vec![layer(0, vec![]), label("Again", vec![layer(0, vec![])])]);
129    }
130
131    #[test]
132    fn nesting_stops_at_pdf_js_limit() {
133        let mut deep = vec![Group(1)];
134        for _ in 0..30 {
135            deep = vec![Array(vec![Text("L".into())].into_iter().chain(deep).collect())];
136        }
137        let tree = layer_tree(&deep, 2);
138        let mut depth = 0;
139        let mut at = &tree;
140        while let Some(LayerNode::Label { children, .. }) = at.first() {
141            depth += 1;
142            at = children;
143        }
144        assert!(depth == 0, "past the limit nothing is kept, got {depth} levels");
145        // Group 1 was dropped with it, so it lands in the additional layers.
146        assert!(tree.iter().any(|n| matches!(n, LayerNode::Additional(c) if c.contains(&layer(1, vec![])))));
147        let shallow = vec![Array(vec![Text("L".into()), Group(0)])];
148        assert_eq!(layer_tree(&shallow, 1), vec![label("L", vec![layer(0, vec![])])]);
149    }
150}