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