zpdf_document/
optional_content.rs1use std::collections::HashSet;
8
9use zpdf_core::{ObjectId, PdfObject};
10use zpdf_parser::PdfFile;
11
12#[derive(Debug, Clone, Default)]
14pub struct OcConfig {
15 off: HashSet<ObjectId>,
17 on: HashSet<ObjectId>,
19 base_state_off: bool,
21}
22
23impl OcConfig {
24 pub fn group_visible(&self, id: ObjectId) -> bool {
28 if self.off.contains(&id) {
29 return false;
30 }
31 if self.on.contains(&id) {
32 return true;
33 }
34 !self.base_state_off
35 }
36
37 pub fn all_visible(&self) -> bool {
39 self.off.is_empty() && !self.base_state_off
40 }
41}
42
43pub fn parse_oc_config(file: &PdfFile) -> Option<OcConfig> {
46 let root_ref = file.trailer.get_ref("Root").ok()?;
47 let root = file.resolve(root_ref).ok()?;
48 let root_dict = root.as_dict().ok()?;
49
50 let ocp = resolve_dict(file, root_dict.get("OCProperties")?)?;
51 let d = resolve_dict(file, ocp.get("D")?).unwrap_or_default();
52
53 let mut config = OcConfig {
54 base_state_off: matches!(d.get_name("BaseState"), Ok("OFF")),
55 ..Default::default()
56 };
57 for id in ref_array(file, d.get("OFF")) {
58 config.off.insert(id);
59 }
60 for id in ref_array(file, d.get("ON")) {
61 config.on.insert(id);
62 }
63 Some(config)
64}
65
66fn resolve_dict(file: &PdfFile, obj: &PdfObject) -> Option<zpdf_core::PdfDict> {
67 match obj {
68 PdfObject::Dict(d) => Some(d.clone()),
69 PdfObject::Ref(r) => match file.resolve(*r).ok()? {
70 PdfObject::Dict(d) => Some(d),
71 _ => None,
72 },
73 _ => None,
74 }
75}
76
77fn ref_array(file: &PdfFile, obj: Option<&PdfObject>) -> Vec<ObjectId> {
78 let arr = match obj {
79 Some(PdfObject::Array(a)) => a.clone(),
80 Some(PdfObject::Ref(r)) => match file.resolve(*r) {
81 Ok(PdfObject::Array(a)) => a,
82 _ => return Vec::new(),
83 },
84 _ => return Vec::new(),
85 };
86 arr.iter()
87 .filter_map(|o| match o {
88 PdfObject::Ref(r) => Some(*r),
89 _ => None,
90 })
91 .collect()
92}