Skip to main content

pdf_oxide/writer/
layers.rs

1//! PDF Layers (Optional Content Groups) support.
2//!
3//! This module provides functionality to create and manage PDF layers,
4//! also known as Optional Content Groups (OCGs) per PDF specification.
5//!
6//! ## Overview
7//!
8//! PDF layers allow content to be selectively shown or hidden in viewers.
9//! Common uses include:
10//! - CAD drawings with multiple views
11//! - Multi-language documents
12//! - Print vs. screen versions
13//! - Watermarks that can be toggled
14//!
15//! ## PDF Structure
16//!
17//! Layers are implemented using:
18//! - `/OCProperties` dictionary in the catalog
19//! - `/OCGs` array listing all Optional Content Groups
20//! - `/D` (default) dictionary with visibility settings
21//! - `/OC` key in content streams/XObjects to associate with layers
22//!
23//! ## Example
24//!
25//! ```ignore
26//! use pdf_oxide::writer::layers::{LayerBuilder, LayerVisibility};
27//!
28//! // Create layers
29//! let mut layer_builder = LayerBuilder::new();
30//! let watermark_layer = layer_builder.add_layer("Watermark")
31//!     .visible_on_screen(true)
32//!     .printable(false);
33//! let annotations_layer = layer_builder.add_layer("Annotations")
34//!     .visible(true);
35//!
36//! // Build layer configuration
37//! let layers = layer_builder.build();
38//! ```
39//!
40//! ## Standards Reference
41//!
42//! - PDF Reference 1.7: Section 4.10 "Optional Content"
43//! - ISO 32000-1:2008: Section 8.11 "Optional Content"
44
45use crate::object::{Object, ObjectRef};
46use std::collections::HashMap;
47
48/// Represents a PDF layer (Optional Content Group).
49#[derive(Debug, Clone)]
50pub struct Layer {
51    /// Unique name for the layer.
52    pub name: String,
53    /// Display name shown in PDF viewer UI.
54    pub display_name: Option<String>,
55    /// Whether the layer is initially visible.
56    pub visible: bool,
57    /// Whether the layer is visible on screen.
58    pub visible_on_screen: bool,
59    /// Whether the layer is visible when printing.
60    pub visible_on_print: bool,
61    /// Whether the layer is exportable.
62    pub exportable: bool,
63    /// Intent of the layer (View, Design, or both).
64    pub intent: LayerIntent,
65    /// Layer order within groups (lower = shown first).
66    pub order: Option<u32>,
67}
68
69impl Default for Layer {
70    fn default() -> Self {
71        Self {
72            name: String::new(),
73            display_name: None,
74            visible: true,
75            visible_on_screen: true,
76            visible_on_print: true,
77            exportable: true,
78            intent: LayerIntent::View,
79            order: None,
80        }
81    }
82}
83
84impl Layer {
85    /// Create a new layer with the given name.
86    pub fn new(name: impl Into<String>) -> Self {
87        let name = name.into();
88        Self {
89            name: name.clone(),
90            display_name: Some(name),
91            ..Default::default()
92        }
93    }
94
95    /// Set the display name.
96    pub fn display_name(&mut self, name: impl Into<String>) -> &mut Self {
97        self.display_name = Some(name.into());
98        self
99    }
100
101    /// Set initial visibility.
102    pub fn visible(&mut self, visible: bool) -> &mut Self {
103        self.visible = visible;
104        self
105    }
106
107    /// Set screen visibility.
108    pub fn visible_on_screen(&mut self, visible: bool) -> &mut Self {
109        self.visible_on_screen = visible;
110        self
111    }
112
113    /// Set print visibility.
114    pub fn visible_on_print(&mut self, visible: bool) -> &mut Self {
115        self.visible_on_print = visible;
116        self
117    }
118
119    /// Set whether exportable.
120    pub fn exportable(&mut self, exportable: bool) -> &mut Self {
121        self.exportable = exportable;
122        self
123    }
124
125    /// Set the layer intent.
126    pub fn intent(&mut self, intent: LayerIntent) -> &mut Self {
127        self.intent = intent;
128        self
129    }
130
131    /// Set the layer order.
132    pub fn order(&mut self, order: u32) -> &mut Self {
133        self.order = Some(order);
134        self
135    }
136
137    /// Build the OCG dictionary.
138    pub fn build_ocg_dict(&self) -> HashMap<String, Object> {
139        let mut dict = HashMap::new();
140        dict.insert("Type".to_string(), Object::Name("OCG".to_string()));
141        dict.insert(
142            "Name".to_string(),
143            Object::String(
144                self.display_name
145                    .as_ref()
146                    .unwrap_or(&self.name)
147                    .as_bytes()
148                    .to_vec(),
149            ),
150        );
151
152        // Intent
153        match self.intent {
154            LayerIntent::View => {
155                dict.insert("Intent".to_string(), Object::Name("View".to_string()));
156            },
157            LayerIntent::Design => {
158                dict.insert("Intent".to_string(), Object::Name("Design".to_string()));
159            },
160            LayerIntent::Both => {
161                dict.insert(
162                    "Intent".to_string(),
163                    Object::Array(vec![
164                        Object::Name("View".to_string()),
165                        Object::Name("Design".to_string()),
166                    ]),
167                );
168            },
169        }
170
171        dict
172    }
173
174    /// Build the usage dictionary for this layer.
175    pub fn build_usage_dict(&self) -> Option<HashMap<String, Object>> {
176        // Only create usage dict if we have non-default settings
177        if self.visible_on_screen && self.visible_on_print && self.exportable {
178            return None;
179        }
180
181        let mut usage = HashMap::new();
182
183        // Print usage
184        if !self.visible_on_print {
185            let mut print_dict = HashMap::new();
186            print_dict.insert("PrintState".to_string(), Object::Name("OFF".to_string()));
187            usage.insert("Print".to_string(), Object::Dictionary(print_dict));
188        }
189
190        // View usage
191        if !self.visible_on_screen {
192            let mut view_dict = HashMap::new();
193            view_dict.insert("ViewState".to_string(), Object::Name("OFF".to_string()));
194            usage.insert("View".to_string(), Object::Dictionary(view_dict));
195        }
196
197        // Export usage
198        if !self.exportable {
199            let mut export_dict = HashMap::new();
200            export_dict.insert("ExportState".to_string(), Object::Name("OFF".to_string()));
201            usage.insert("Export".to_string(), Object::Dictionary(export_dict));
202        }
203
204        if usage.is_empty() {
205            None
206        } else {
207            Some(usage)
208        }
209    }
210}
211
212/// Layer intent.
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
214pub enum LayerIntent {
215    /// Layer is for viewing purposes.
216    #[default]
217    View,
218    /// Layer is for design purposes.
219    Design,
220    /// Layer is for both viewing and design.
221    Both,
222}
223
224/// Layer visibility state.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
226pub enum LayerVisibility {
227    /// Layer is visible by default.
228    #[default]
229    On,
230    /// Layer is hidden by default.
231    Off,
232}
233
234/// Builder for creating PDF layer configurations.
235#[derive(Debug, Default)]
236pub struct LayerBuilder {
237    layers: Vec<Layer>,
238    base_state: LayerVisibility,
239    creator: Option<String>,
240    application_name: Option<String>,
241}
242
243impl LayerBuilder {
244    /// Create a new layer builder.
245    pub fn new() -> Self {
246        Self::default()
247    }
248
249    /// Add a new layer.
250    pub fn add_layer(&mut self, name: impl Into<String>) -> &mut Layer {
251        let layer = Layer::new(name);
252        self.layers.push(layer);
253        self.layers.last_mut().unwrap()
254    }
255
256    /// Set the base visibility state for layers.
257    pub fn base_state(mut self, state: LayerVisibility) -> Self {
258        self.base_state = state;
259        self
260    }
261
262    /// Set the creator application name.
263    pub fn creator(mut self, creator: impl Into<String>) -> Self {
264        self.creator = Some(creator.into());
265        self
266    }
267
268    /// Set the application name.
269    pub fn application_name(mut self, name: impl Into<String>) -> Self {
270        self.application_name = Some(name.into());
271        self
272    }
273
274    /// Get the layers.
275    pub fn layers(&self) -> &[Layer] {
276        &self.layers
277    }
278
279    /// Get mutable access to layers.
280    pub fn layers_mut(&mut self) -> &mut Vec<Layer> {
281        &mut self.layers
282    }
283
284    /// Check if there are any layers.
285    pub fn is_empty(&self) -> bool {
286        self.layers.is_empty()
287    }
288
289    /// Get the number of layers.
290    pub fn len(&self) -> usize {
291        self.layers.len()
292    }
293
294    /// Build the OCProperties dictionary.
295    ///
296    /// This returns the dictionary that should be added to the catalog.
297    pub fn build_oc_properties(&self, ocg_refs: &[ObjectRef]) -> HashMap<String, Object> {
298        let mut props = HashMap::new();
299
300        // OCGs array - list of all Optional Content Groups
301        let ocgs_array: Vec<Object> = ocg_refs.iter().map(|r| Object::Reference(*r)).collect();
302        props.insert("OCGs".to_string(), Object::Array(ocgs_array.clone()));
303
304        // D (default) configuration dictionary
305        let mut d_dict = HashMap::new();
306
307        // Name of the configuration
308        d_dict.insert("Name".to_string(), Object::text_string("Default"));
309
310        // Creator (application that created the layers)
311        if let Some(ref creator) = self.creator {
312            d_dict.insert("Creator".to_string(), Object::text_string(creator));
313        }
314
315        // Base state
316        d_dict.insert(
317            "BaseState".to_string(),
318            Object::Name(
319                match self.base_state {
320                    LayerVisibility::On => "ON",
321                    LayerVisibility::Off => "OFF",
322                }
323                .to_string(),
324            ),
325        );
326
327        // ON array - layers that are visible by default (when BaseState is OFF)
328        // OFF array - layers that are hidden by default (when BaseState is ON)
329        let mut on_refs: Vec<Object> = Vec::new();
330        let mut off_refs: Vec<Object> = Vec::new();
331
332        for (i, layer) in self.layers.iter().enumerate() {
333            if i < ocg_refs.len() {
334                if layer.visible {
335                    on_refs.push(Object::Reference(ocg_refs[i]));
336                } else {
337                    off_refs.push(Object::Reference(ocg_refs[i]));
338                }
339            }
340        }
341
342        if !on_refs.is_empty() {
343            d_dict.insert("ON".to_string(), Object::Array(on_refs));
344        }
345        if !off_refs.is_empty() {
346            d_dict.insert("OFF".to_string(), Object::Array(off_refs));
347        }
348
349        // Order array - defines display order in viewer UI
350        d_dict.insert("Order".to_string(), Object::Array(ocgs_array));
351
352        props.insert("D".to_string(), Object::Dictionary(d_dict));
353
354        props
355    }
356}
357
358/// Configuration for layer membership.
359#[derive(Debug, Clone)]
360pub struct LayerMembership {
361    /// The OCGs this content belongs to.
362    pub ocgs: Vec<ObjectRef>,
363    /// Visibility policy.
364    pub policy: VisibilityPolicy,
365}
366
367/// Visibility policy for layer membership.
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
369pub enum VisibilityPolicy {
370    /// Content is visible if ALL OCGs are visible.
371    #[default]
372    AllOn,
373    /// Content is visible if ANY OCG is visible.
374    AnyOn,
375    /// Content is visible if ALL OCGs are hidden.
376    AllOff,
377    /// Content is visible if ANY OCG is hidden.
378    AnyOff,
379}
380
381impl LayerMembership {
382    /// Create a new layer membership with a single OCG.
383    pub fn new(ocg_ref: ObjectRef) -> Self {
384        Self {
385            ocgs: vec![ocg_ref],
386            policy: VisibilityPolicy::AllOn,
387        }
388    }
389
390    /// Add an OCG to the membership.
391    pub fn add_ocg(&mut self, ocg_ref: ObjectRef) -> &mut Self {
392        self.ocgs.push(ocg_ref);
393        self
394    }
395
396    /// Set the visibility policy.
397    pub fn policy(mut self, policy: VisibilityPolicy) -> Self {
398        self.policy = policy;
399        self
400    }
401
402    /// Build the OCMD dictionary.
403    pub fn build_ocmd_dict(&self) -> HashMap<String, Object> {
404        let mut dict = HashMap::new();
405        dict.insert("Type".to_string(), Object::Name("OCMD".to_string()));
406
407        if self.ocgs.len() == 1 {
408            dict.insert("OCGs".to_string(), Object::Reference(self.ocgs[0]));
409        } else {
410            dict.insert(
411                "OCGs".to_string(),
412                Object::Array(self.ocgs.iter().map(|r| Object::Reference(*r)).collect()),
413            );
414        }
415
416        // Policy (P key)
417        let policy_name = match self.policy {
418            VisibilityPolicy::AllOn => "AllOn",
419            VisibilityPolicy::AnyOn => "AnyOn",
420            VisibilityPolicy::AllOff => "AllOff",
421            VisibilityPolicy::AnyOff => "AnyOff",
422        };
423        dict.insert("P".to_string(), Object::Name(policy_name.to_string()));
424
425        dict
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[test]
434    fn test_layer_creation() {
435        let mut layer = Layer::new("Background");
436        layer
437            .display_name("Background Layer")
438            .visible(true)
439            .visible_on_print(false);
440
441        assert_eq!(layer.name, "Background");
442        assert_eq!(layer.display_name, Some("Background Layer".to_string()));
443        assert!(layer.visible);
444        assert!(!layer.visible_on_print);
445    }
446
447    #[test]
448    fn test_layer_builder() {
449        let mut builder = LayerBuilder::new();
450        builder.add_layer("Layer1").visible(true);
451        builder.add_layer("Layer2").visible(false);
452
453        assert_eq!(builder.len(), 2);
454        assert!(!builder.is_empty());
455        assert!(builder.layers()[0].visible);
456        assert!(!builder.layers()[1].visible);
457    }
458
459    #[test]
460    fn test_layer_builder_base_state() {
461        let builder = LayerBuilder::new()
462            .base_state(LayerVisibility::Off)
463            .creator("pdf_oxide");
464
465        assert_eq!(builder.base_state, LayerVisibility::Off);
466        assert_eq!(builder.creator, Some("pdf_oxide".to_string()));
467    }
468
469    #[test]
470    fn test_layer_ocg_dict() {
471        let mut layer = Layer::new("Test");
472        layer.intent(LayerIntent::Both);
473
474        let dict = layer.build_ocg_dict();
475        assert!(dict.contains_key("Type"));
476        assert!(dict.contains_key("Name"));
477        assert!(dict.contains_key("Intent"));
478
479        if let Some(Object::Name(type_name)) = dict.get("Type") {
480            assert_eq!(type_name, "OCG");
481        } else {
482            panic!("Type should be a Name");
483        }
484    }
485
486    #[test]
487    fn test_layer_usage_dict() {
488        // Default settings - no usage dict needed
489        let layer_default = Layer::new("Default");
490        assert!(layer_default.build_usage_dict().is_none());
491
492        // Non-printable layer - should have usage dict
493        let mut layer_no_print = Layer::new("NoPrint");
494        layer_no_print.visible_on_print(false);
495        let usage = layer_no_print.build_usage_dict();
496        assert!(usage.is_some());
497        assert!(usage.unwrap().contains_key("Print"));
498    }
499
500    #[test]
501    fn test_layer_membership() {
502        let ocg_ref = ObjectRef::new(1, 0);
503        let membership = LayerMembership::new(ocg_ref).policy(VisibilityPolicy::AnyOn);
504
505        assert_eq!(membership.ocgs.len(), 1);
506        assert_eq!(membership.policy, VisibilityPolicy::AnyOn);
507    }
508
509    #[test]
510    fn test_layer_membership_dict() {
511        let ocg_ref = ObjectRef::new(1, 0);
512        let membership = LayerMembership::new(ocg_ref);
513        let dict = membership.build_ocmd_dict();
514
515        assert!(dict.contains_key("Type"));
516        assert!(dict.contains_key("OCGs"));
517        assert!(dict.contains_key("P"));
518
519        if let Some(Object::Name(type_name)) = dict.get("Type") {
520            assert_eq!(type_name, "OCMD");
521        } else {
522            panic!("Type should be a Name");
523        }
524    }
525
526    #[test]
527    fn test_layer_intent() {
528        assert_eq!(LayerIntent::default(), LayerIntent::View);
529
530        let mut layer = Layer::new("Design");
531        layer.intent(LayerIntent::Design);
532        assert_eq!(layer.intent, LayerIntent::Design);
533    }
534
535    #[test]
536    fn test_visibility_policy() {
537        assert_eq!(VisibilityPolicy::default(), VisibilityPolicy::AllOn);
538    }
539
540    // ---- Tests for Layer defaults ----
541
542    #[test]
543    fn test_layer_default() {
544        let layer = Layer::default();
545        assert_eq!(layer.name, "");
546        assert!(layer.display_name.is_none());
547        assert!(layer.visible);
548        assert!(layer.visible_on_screen);
549        assert!(layer.visible_on_print);
550        assert!(layer.exportable);
551        assert_eq!(layer.intent, LayerIntent::View);
552        assert!(layer.order.is_none());
553    }
554
555    #[test]
556    fn test_layer_new_sets_name_and_display_name() {
557        let layer = Layer::new("MyLayer");
558        assert_eq!(layer.name, "MyLayer");
559        assert_eq!(layer.display_name, Some("MyLayer".to_string()));
560    }
561
562    // ---- Tests for Layer setter methods ----
563
564    #[test]
565    fn test_layer_visible_on_screen() {
566        let mut layer = Layer::new("Test");
567        layer.visible_on_screen(false);
568        assert!(!layer.visible_on_screen);
569    }
570
571    #[test]
572    fn test_layer_exportable() {
573        let mut layer = Layer::new("Test");
574        layer.exportable(false);
575        assert!(!layer.exportable);
576    }
577
578    #[test]
579    fn test_layer_order() {
580        let mut layer = Layer::new("Test");
581        layer.order(5);
582        assert_eq!(layer.order, Some(5));
583    }
584
585    // ---- Tests for build_ocg_dict with different intents ----
586
587    #[test]
588    fn test_layer_ocg_dict_view_intent() {
589        let layer = Layer::new("ViewLayer");
590        let dict = layer.build_ocg_dict();
591        if let Some(Object::Name(intent)) = dict.get("Intent") {
592            assert_eq!(intent, "View");
593        } else {
594            panic!("Expected View intent");
595        }
596    }
597
598    #[test]
599    fn test_layer_ocg_dict_design_intent() {
600        let mut layer = Layer::new("DesignLayer");
601        layer.intent(LayerIntent::Design);
602        let dict = layer.build_ocg_dict();
603        if let Some(Object::Name(intent)) = dict.get("Intent") {
604            assert_eq!(intent, "Design");
605        } else {
606            panic!("Expected Design intent");
607        }
608    }
609
610    #[test]
611    fn test_layer_ocg_dict_both_intent() {
612        let mut layer = Layer::new("BothLayer");
613        layer.intent(LayerIntent::Both);
614        let dict = layer.build_ocg_dict();
615        if let Some(Object::Array(arr)) = dict.get("Intent") {
616            assert_eq!(arr.len(), 2);
617            assert_eq!(arr[0], Object::Name("View".to_string()));
618            assert_eq!(arr[1], Object::Name("Design".to_string()));
619        } else {
620            panic!("Expected Array intent for Both");
621        }
622    }
623
624    #[test]
625    fn test_layer_ocg_dict_uses_display_name() {
626        let mut layer = Layer::new("internal_name");
627        layer.display_name("Displayed Name");
628        let dict = layer.build_ocg_dict();
629        if let Some(Object::String(name_bytes)) = dict.get("Name") {
630            assert_eq!(name_bytes, b"Displayed Name");
631        } else {
632            panic!("Expected String for Name");
633        }
634    }
635
636    #[test]
637    fn test_layer_ocg_dict_falls_back_to_name() {
638        let layer = Layer {
639            name: "fallback_name".to_string(),
640            ..Default::default()
641        };
642        // display_name is None, so build_ocg_dict should use name
643        let dict = layer.build_ocg_dict();
644        if let Some(Object::String(name_bytes)) = dict.get("Name") {
645            assert_eq!(name_bytes, b"fallback_name");
646        } else {
647            panic!("Expected String for Name");
648        }
649    }
650
651    // ---- Tests for build_usage_dict ----
652
653    #[test]
654    fn test_usage_dict_not_visible_on_screen() {
655        let mut layer = Layer::new("Test");
656        layer.visible_on_screen(false);
657        let usage = layer.build_usage_dict().unwrap();
658        assert!(usage.contains_key("View"));
659        if let Some(Object::Dictionary(view_dict)) = usage.get("View") {
660            assert_eq!(view_dict.get("ViewState"), Some(&Object::Name("OFF".to_string())));
661        }
662    }
663
664    #[test]
665    fn test_usage_dict_not_exportable() {
666        let mut layer = Layer::new("Test");
667        layer.exportable(false);
668        let usage = layer.build_usage_dict().unwrap();
669        assert!(usage.contains_key("Export"));
670        if let Some(Object::Dictionary(export_dict)) = usage.get("Export") {
671            assert_eq!(export_dict.get("ExportState"), Some(&Object::Name("OFF".to_string())));
672        }
673    }
674
675    #[test]
676    fn test_usage_dict_multiple_non_defaults() {
677        let mut layer = Layer::new("Test");
678        layer
679            .visible_on_screen(false)
680            .visible_on_print(false)
681            .exportable(false);
682        let usage = layer.build_usage_dict().unwrap();
683        assert!(usage.contains_key("View"));
684        assert!(usage.contains_key("Print"));
685        assert!(usage.contains_key("Export"));
686    }
687
688    // ---- Tests for LayerBuilder ----
689
690    #[test]
691    fn test_layer_builder_empty() {
692        let builder = LayerBuilder::new();
693        assert!(builder.is_empty());
694        assert_eq!(builder.len(), 0);
695        assert!(builder.layers().is_empty());
696    }
697
698    #[test]
699    fn test_layer_builder_application_name() {
700        let builder = LayerBuilder::new().application_name("MyApp");
701        assert_eq!(builder.application_name, Some("MyApp".to_string()));
702    }
703
704    #[test]
705    fn test_layer_builder_layers_mut() {
706        let mut builder = LayerBuilder::new();
707        builder.add_layer("Layer1");
708        builder.layers_mut().push(Layer::new("Layer2"));
709        assert_eq!(builder.len(), 2);
710    }
711
712    // ---- Tests for build_oc_properties ----
713
714    #[test]
715    fn test_build_oc_properties_basic() {
716        let mut builder = LayerBuilder::new();
717        builder.add_layer("Visible").visible(true);
718        builder.add_layer("Hidden").visible(false);
719
720        let refs = vec![ObjectRef::new(10, 0), ObjectRef::new(11, 0)];
721        let props = builder.build_oc_properties(&refs);
722
723        assert!(props.contains_key("OCGs"));
724        assert!(props.contains_key("D"));
725
726        if let Some(Object::Array(ocgs)) = props.get("OCGs") {
727            assert_eq!(ocgs.len(), 2);
728        }
729
730        if let Some(Object::Dictionary(d_dict)) = props.get("D") {
731            assert!(d_dict.contains_key("Name"));
732            assert!(d_dict.contains_key("BaseState"));
733            assert!(d_dict.contains_key("Order"));
734
735            // Visible layer should be in ON array
736            if let Some(Object::Array(on_arr)) = d_dict.get("ON") {
737                assert_eq!(on_arr.len(), 1);
738            }
739            // Hidden layer should be in OFF array
740            if let Some(Object::Array(off_arr)) = d_dict.get("OFF") {
741                assert_eq!(off_arr.len(), 1);
742            }
743        }
744    }
745
746    #[test]
747    fn test_build_oc_properties_with_creator() {
748        let mut builder = LayerBuilder::new().creator("pdf_oxide_test");
749        builder.add_layer("Layer1");
750
751        let refs = vec![ObjectRef::new(1, 0)];
752        let props = builder.build_oc_properties(&refs);
753
754        if let Some(Object::Dictionary(d_dict)) = props.get("D") {
755            if let Some(Object::String(creator_bytes)) = d_dict.get("Creator") {
756                assert_eq!(creator_bytes, b"pdf_oxide_test");
757            } else {
758                panic!("Expected Creator string");
759            }
760        }
761    }
762
763    #[test]
764    fn test_build_oc_properties_off_base_state() {
765        let mut builder = LayerBuilder::new().base_state(LayerVisibility::Off);
766        builder.add_layer("Layer1");
767
768        let refs = vec![ObjectRef::new(1, 0)];
769        let props = builder.build_oc_properties(&refs);
770
771        if let Some(Object::Dictionary(d_dict)) = props.get("D") {
772            assert_eq!(d_dict.get("BaseState"), Some(&Object::Name("OFF".to_string())));
773        }
774    }
775
776    #[test]
777    fn test_build_oc_properties_all_visible() {
778        let mut builder = LayerBuilder::new();
779        builder.add_layer("Layer1").visible(true);
780        builder.add_layer("Layer2").visible(true);
781
782        let refs = vec![ObjectRef::new(1, 0), ObjectRef::new(2, 0)];
783        let props = builder.build_oc_properties(&refs);
784
785        if let Some(Object::Dictionary(d_dict)) = props.get("D") {
786            // All visible = all in ON, no OFF
787            if let Some(Object::Array(on_arr)) = d_dict.get("ON") {
788                assert_eq!(on_arr.len(), 2);
789            }
790            assert!(!d_dict.contains_key("OFF"));
791        }
792    }
793
794    #[test]
795    fn test_build_oc_properties_all_hidden() {
796        let mut builder = LayerBuilder::new();
797        builder.add_layer("Layer1").visible(false);
798        builder.add_layer("Layer2").visible(false);
799
800        let refs = vec![ObjectRef::new(1, 0), ObjectRef::new(2, 0)];
801        let props = builder.build_oc_properties(&refs);
802
803        if let Some(Object::Dictionary(d_dict)) = props.get("D") {
804            assert!(!d_dict.contains_key("ON"));
805            if let Some(Object::Array(off_arr)) = d_dict.get("OFF") {
806                assert_eq!(off_arr.len(), 2);
807            }
808        }
809    }
810
811    #[test]
812    fn test_build_oc_properties_more_layers_than_refs() {
813        // When there are more layers than refs, extra layers are skipped
814        let mut builder = LayerBuilder::new();
815        builder.add_layer("Layer1").visible(true);
816        builder.add_layer("Layer2").visible(true);
817        builder.add_layer("Layer3").visible(true);
818
819        let refs = vec![ObjectRef::new(1, 0)]; // Only 1 ref for 3 layers
820        let props = builder.build_oc_properties(&refs);
821
822        if let Some(Object::Dictionary(d_dict)) = props.get("D") {
823            if let Some(Object::Array(on_arr)) = d_dict.get("ON") {
824                assert_eq!(on_arr.len(), 1); // Only first layer included
825            }
826        }
827    }
828
829    // ---- Tests for LayerMembership ----
830
831    #[test]
832    fn test_layer_membership_add_ocg() {
833        let mut membership = LayerMembership::new(ObjectRef::new(1, 0));
834        membership.add_ocg(ObjectRef::new(2, 0));
835        membership.add_ocg(ObjectRef::new(3, 0));
836        assert_eq!(membership.ocgs.len(), 3);
837    }
838
839    #[test]
840    fn test_layer_membership_policy_method() {
841        let membership =
842            LayerMembership::new(ObjectRef::new(1, 0)).policy(VisibilityPolicy::AllOff);
843        assert_eq!(membership.policy, VisibilityPolicy::AllOff);
844    }
845
846    #[test]
847    fn test_layer_membership_ocmd_single_ocg() {
848        let membership = LayerMembership::new(ObjectRef::new(5, 0));
849        let dict = membership.build_ocmd_dict();
850
851        // Single OCG should be a Reference, not an Array
852        if let Some(Object::Reference(r)) = dict.get("OCGs") {
853            assert_eq!(r.id, 5);
854            assert_eq!(r.gen, 0);
855        } else {
856            panic!("Expected single Reference for single OCG");
857        }
858    }
859
860    #[test]
861    fn test_layer_membership_ocmd_multiple_ocgs() {
862        let mut membership = LayerMembership::new(ObjectRef::new(5, 0));
863        membership.add_ocg(ObjectRef::new(6, 0));
864        let dict = membership.build_ocmd_dict();
865
866        // Multiple OCGs should be an Array
867        if let Some(Object::Array(arr)) = dict.get("OCGs") {
868            assert_eq!(arr.len(), 2);
869        } else {
870            panic!("Expected Array for multiple OCGs");
871        }
872    }
873
874    #[test]
875    fn test_layer_membership_ocmd_all_policies() {
876        let policies = vec![
877            (VisibilityPolicy::AllOn, "AllOn"),
878            (VisibilityPolicy::AnyOn, "AnyOn"),
879            (VisibilityPolicy::AllOff, "AllOff"),
880            (VisibilityPolicy::AnyOff, "AnyOff"),
881        ];
882
883        for (policy, expected_name) in policies {
884            let membership = LayerMembership::new(ObjectRef::new(1, 0)).policy(policy);
885            let dict = membership.build_ocmd_dict();
886            if let Some(Object::Name(name)) = dict.get("P") {
887                assert_eq!(name, expected_name);
888            } else {
889                panic!("Expected Name for P key");
890            }
891        }
892    }
893
894    // ---- Tests for LayerVisibility ----
895
896    #[test]
897    fn test_layer_visibility_default() {
898        assert_eq!(LayerVisibility::default(), LayerVisibility::On);
899    }
900
901    // ---- Tests for LayerIntent ----
902
903    #[test]
904    fn test_layer_intent_default() {
905        assert_eq!(LayerIntent::default(), LayerIntent::View);
906    }
907
908    #[test]
909    fn test_layer_intent_equality() {
910        assert_eq!(LayerIntent::View, LayerIntent::View);
911        assert_eq!(LayerIntent::Design, LayerIntent::Design);
912        assert_eq!(LayerIntent::Both, LayerIntent::Both);
913        assert_ne!(LayerIntent::View, LayerIntent::Design);
914        assert_ne!(LayerIntent::View, LayerIntent::Both);
915    }
916}