use crate::object::{Object, ObjectRef};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Layer {
pub name: String,
pub display_name: Option<String>,
pub visible: bool,
pub visible_on_screen: bool,
pub visible_on_print: bool,
pub exportable: bool,
pub intent: LayerIntent,
pub order: Option<u32>,
}
impl Default for Layer {
fn default() -> Self {
Self {
name: String::new(),
display_name: None,
visible: true,
visible_on_screen: true,
visible_on_print: true,
exportable: true,
intent: LayerIntent::View,
order: None,
}
}
}
impl Layer {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
Self {
name: name.clone(),
display_name: Some(name),
..Default::default()
}
}
pub fn display_name(&mut self, name: impl Into<String>) -> &mut Self {
self.display_name = Some(name.into());
self
}
pub fn visible(&mut self, visible: bool) -> &mut Self {
self.visible = visible;
self
}
pub fn visible_on_screen(&mut self, visible: bool) -> &mut Self {
self.visible_on_screen = visible;
self
}
pub fn visible_on_print(&mut self, visible: bool) -> &mut Self {
self.visible_on_print = visible;
self
}
pub fn exportable(&mut self, exportable: bool) -> &mut Self {
self.exportable = exportable;
self
}
pub fn intent(&mut self, intent: LayerIntent) -> &mut Self {
self.intent = intent;
self
}
pub fn order(&mut self, order: u32) -> &mut Self {
self.order = Some(order);
self
}
pub fn build_ocg_dict(&self) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("OCG".to_string()));
dict.insert(
"Name".to_string(),
Object::String(
self.display_name
.as_ref()
.unwrap_or(&self.name)
.as_bytes()
.to_vec(),
),
);
match self.intent {
LayerIntent::View => {
dict.insert("Intent".to_string(), Object::Name("View".to_string()));
},
LayerIntent::Design => {
dict.insert("Intent".to_string(), Object::Name("Design".to_string()));
},
LayerIntent::Both => {
dict.insert(
"Intent".to_string(),
Object::Array(vec![
Object::Name("View".to_string()),
Object::Name("Design".to_string()),
]),
);
},
}
dict
}
pub fn build_usage_dict(&self) -> Option<HashMap<String, Object>> {
if self.visible_on_screen && self.visible_on_print && self.exportable {
return None;
}
let mut usage = HashMap::new();
if !self.visible_on_print {
let mut print_dict = HashMap::new();
print_dict.insert("PrintState".to_string(), Object::Name("OFF".to_string()));
usage.insert("Print".to_string(), Object::Dictionary(print_dict));
}
if !self.visible_on_screen {
let mut view_dict = HashMap::new();
view_dict.insert("ViewState".to_string(), Object::Name("OFF".to_string()));
usage.insert("View".to_string(), Object::Dictionary(view_dict));
}
if !self.exportable {
let mut export_dict = HashMap::new();
export_dict.insert("ExportState".to_string(), Object::Name("OFF".to_string()));
usage.insert("Export".to_string(), Object::Dictionary(export_dict));
}
if usage.is_empty() {
None
} else {
Some(usage)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LayerIntent {
#[default]
View,
Design,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LayerVisibility {
#[default]
On,
Off,
}
#[derive(Debug, Default)]
pub struct LayerBuilder {
layers: Vec<Layer>,
base_state: LayerVisibility,
creator: Option<String>,
application_name: Option<String>,
}
impl LayerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_layer(&mut self, name: impl Into<String>) -> &mut Layer {
let layer = Layer::new(name);
self.layers.push(layer);
self.layers.last_mut().unwrap()
}
pub fn base_state(mut self, state: LayerVisibility) -> Self {
self.base_state = state;
self
}
pub fn creator(mut self, creator: impl Into<String>) -> Self {
self.creator = Some(creator.into());
self
}
pub fn application_name(mut self, name: impl Into<String>) -> Self {
self.application_name = Some(name.into());
self
}
pub fn layers(&self) -> &[Layer] {
&self.layers
}
pub fn layers_mut(&mut self) -> &mut Vec<Layer> {
&mut self.layers
}
pub fn is_empty(&self) -> bool {
self.layers.is_empty()
}
pub fn len(&self) -> usize {
self.layers.len()
}
pub fn build_oc_properties(&self, ocg_refs: &[ObjectRef]) -> HashMap<String, Object> {
let mut props = HashMap::new();
let ocgs_array: Vec<Object> = ocg_refs.iter().map(|r| Object::Reference(*r)).collect();
props.insert("OCGs".to_string(), Object::Array(ocgs_array.clone()));
let mut d_dict = HashMap::new();
d_dict.insert("Name".to_string(), Object::String("Default".as_bytes().to_vec()));
if let Some(ref creator) = self.creator {
d_dict.insert("Creator".to_string(), Object::String(creator.as_bytes().to_vec()));
}
d_dict.insert(
"BaseState".to_string(),
Object::Name(
match self.base_state {
LayerVisibility::On => "ON",
LayerVisibility::Off => "OFF",
}
.to_string(),
),
);
let mut on_refs: Vec<Object> = Vec::new();
let mut off_refs: Vec<Object> = Vec::new();
for (i, layer) in self.layers.iter().enumerate() {
if i < ocg_refs.len() {
if layer.visible {
on_refs.push(Object::Reference(ocg_refs[i]));
} else {
off_refs.push(Object::Reference(ocg_refs[i]));
}
}
}
if !on_refs.is_empty() {
d_dict.insert("ON".to_string(), Object::Array(on_refs));
}
if !off_refs.is_empty() {
d_dict.insert("OFF".to_string(), Object::Array(off_refs));
}
d_dict.insert("Order".to_string(), Object::Array(ocgs_array));
props.insert("D".to_string(), Object::Dictionary(d_dict));
props
}
}
#[derive(Debug, Clone)]
pub struct LayerMembership {
pub ocgs: Vec<ObjectRef>,
pub policy: VisibilityPolicy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VisibilityPolicy {
#[default]
AllOn,
AnyOn,
AllOff,
AnyOff,
}
impl LayerMembership {
pub fn new(ocg_ref: ObjectRef) -> Self {
Self {
ocgs: vec![ocg_ref],
policy: VisibilityPolicy::AllOn,
}
}
pub fn add_ocg(&mut self, ocg_ref: ObjectRef) -> &mut Self {
self.ocgs.push(ocg_ref);
self
}
pub fn policy(mut self, policy: VisibilityPolicy) -> Self {
self.policy = policy;
self
}
pub fn build_ocmd_dict(&self) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("OCMD".to_string()));
if self.ocgs.len() == 1 {
dict.insert("OCGs".to_string(), Object::Reference(self.ocgs[0]));
} else {
dict.insert(
"OCGs".to_string(),
Object::Array(self.ocgs.iter().map(|r| Object::Reference(*r)).collect()),
);
}
let policy_name = match self.policy {
VisibilityPolicy::AllOn => "AllOn",
VisibilityPolicy::AnyOn => "AnyOn",
VisibilityPolicy::AllOff => "AllOff",
VisibilityPolicy::AnyOff => "AnyOff",
};
dict.insert("P".to_string(), Object::Name(policy_name.to_string()));
dict
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layer_creation() {
let mut layer = Layer::new("Background");
layer
.display_name("Background Layer")
.visible(true)
.visible_on_print(false);
assert_eq!(layer.name, "Background");
assert_eq!(layer.display_name, Some("Background Layer".to_string()));
assert!(layer.visible);
assert!(!layer.visible_on_print);
}
#[test]
fn test_layer_builder() {
let mut builder = LayerBuilder::new();
builder.add_layer("Layer1").visible(true);
builder.add_layer("Layer2").visible(false);
assert_eq!(builder.len(), 2);
assert!(!builder.is_empty());
assert!(builder.layers()[0].visible);
assert!(!builder.layers()[1].visible);
}
#[test]
fn test_layer_builder_base_state() {
let builder = LayerBuilder::new()
.base_state(LayerVisibility::Off)
.creator("pdf_oxide");
assert_eq!(builder.base_state, LayerVisibility::Off);
assert_eq!(builder.creator, Some("pdf_oxide".to_string()));
}
#[test]
fn test_layer_ocg_dict() {
let mut layer = Layer::new("Test");
layer.intent(LayerIntent::Both);
let dict = layer.build_ocg_dict();
assert!(dict.contains_key("Type"));
assert!(dict.contains_key("Name"));
assert!(dict.contains_key("Intent"));
if let Some(Object::Name(type_name)) = dict.get("Type") {
assert_eq!(type_name, "OCG");
} else {
panic!("Type should be a Name");
}
}
#[test]
fn test_layer_usage_dict() {
let layer_default = Layer::new("Default");
assert!(layer_default.build_usage_dict().is_none());
let mut layer_no_print = Layer::new("NoPrint");
layer_no_print.visible_on_print(false);
let usage = layer_no_print.build_usage_dict();
assert!(usage.is_some());
assert!(usage.unwrap().contains_key("Print"));
}
#[test]
fn test_layer_membership() {
let ocg_ref = ObjectRef::new(1, 0);
let membership = LayerMembership::new(ocg_ref).policy(VisibilityPolicy::AnyOn);
assert_eq!(membership.ocgs.len(), 1);
assert_eq!(membership.policy, VisibilityPolicy::AnyOn);
}
#[test]
fn test_layer_membership_dict() {
let ocg_ref = ObjectRef::new(1, 0);
let membership = LayerMembership::new(ocg_ref);
let dict = membership.build_ocmd_dict();
assert!(dict.contains_key("Type"));
assert!(dict.contains_key("OCGs"));
assert!(dict.contains_key("P"));
if let Some(Object::Name(type_name)) = dict.get("Type") {
assert_eq!(type_name, "OCMD");
} else {
panic!("Type should be a Name");
}
}
#[test]
fn test_layer_intent() {
assert_eq!(LayerIntent::default(), LayerIntent::View);
let mut layer = Layer::new("Design");
layer.intent(LayerIntent::Design);
assert_eq!(layer.intent, LayerIntent::Design);
}
#[test]
fn test_visibility_policy() {
assert_eq!(VisibilityPolicy::default(), VisibilityPolicy::AllOn);
}
#[test]
fn test_layer_default() {
let layer = Layer::default();
assert_eq!(layer.name, "");
assert!(layer.display_name.is_none());
assert!(layer.visible);
assert!(layer.visible_on_screen);
assert!(layer.visible_on_print);
assert!(layer.exportable);
assert_eq!(layer.intent, LayerIntent::View);
assert!(layer.order.is_none());
}
#[test]
fn test_layer_new_sets_name_and_display_name() {
let layer = Layer::new("MyLayer");
assert_eq!(layer.name, "MyLayer");
assert_eq!(layer.display_name, Some("MyLayer".to_string()));
}
#[test]
fn test_layer_visible_on_screen() {
let mut layer = Layer::new("Test");
layer.visible_on_screen(false);
assert!(!layer.visible_on_screen);
}
#[test]
fn test_layer_exportable() {
let mut layer = Layer::new("Test");
layer.exportable(false);
assert!(!layer.exportable);
}
#[test]
fn test_layer_order() {
let mut layer = Layer::new("Test");
layer.order(5);
assert_eq!(layer.order, Some(5));
}
#[test]
fn test_layer_ocg_dict_view_intent() {
let layer = Layer::new("ViewLayer");
let dict = layer.build_ocg_dict();
if let Some(Object::Name(intent)) = dict.get("Intent") {
assert_eq!(intent, "View");
} else {
panic!("Expected View intent");
}
}
#[test]
fn test_layer_ocg_dict_design_intent() {
let mut layer = Layer::new("DesignLayer");
layer.intent(LayerIntent::Design);
let dict = layer.build_ocg_dict();
if let Some(Object::Name(intent)) = dict.get("Intent") {
assert_eq!(intent, "Design");
} else {
panic!("Expected Design intent");
}
}
#[test]
fn test_layer_ocg_dict_both_intent() {
let mut layer = Layer::new("BothLayer");
layer.intent(LayerIntent::Both);
let dict = layer.build_ocg_dict();
if let Some(Object::Array(arr)) = dict.get("Intent") {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], Object::Name("View".to_string()));
assert_eq!(arr[1], Object::Name("Design".to_string()));
} else {
panic!("Expected Array intent for Both");
}
}
#[test]
fn test_layer_ocg_dict_uses_display_name() {
let mut layer = Layer::new("internal_name");
layer.display_name("Displayed Name");
let dict = layer.build_ocg_dict();
if let Some(Object::String(name_bytes)) = dict.get("Name") {
assert_eq!(name_bytes, b"Displayed Name");
} else {
panic!("Expected String for Name");
}
}
#[test]
fn test_layer_ocg_dict_falls_back_to_name() {
let layer = Layer {
name: "fallback_name".to_string(),
..Default::default()
};
let dict = layer.build_ocg_dict();
if let Some(Object::String(name_bytes)) = dict.get("Name") {
assert_eq!(name_bytes, b"fallback_name");
} else {
panic!("Expected String for Name");
}
}
#[test]
fn test_usage_dict_not_visible_on_screen() {
let mut layer = Layer::new("Test");
layer.visible_on_screen(false);
let usage = layer.build_usage_dict().unwrap();
assert!(usage.contains_key("View"));
if let Some(Object::Dictionary(view_dict)) = usage.get("View") {
assert_eq!(view_dict.get("ViewState"), Some(&Object::Name("OFF".to_string())));
}
}
#[test]
fn test_usage_dict_not_exportable() {
let mut layer = Layer::new("Test");
layer.exportable(false);
let usage = layer.build_usage_dict().unwrap();
assert!(usage.contains_key("Export"));
if let Some(Object::Dictionary(export_dict)) = usage.get("Export") {
assert_eq!(export_dict.get("ExportState"), Some(&Object::Name("OFF".to_string())));
}
}
#[test]
fn test_usage_dict_multiple_non_defaults() {
let mut layer = Layer::new("Test");
layer
.visible_on_screen(false)
.visible_on_print(false)
.exportable(false);
let usage = layer.build_usage_dict().unwrap();
assert!(usage.contains_key("View"));
assert!(usage.contains_key("Print"));
assert!(usage.contains_key("Export"));
}
#[test]
fn test_layer_builder_empty() {
let builder = LayerBuilder::new();
assert!(builder.is_empty());
assert_eq!(builder.len(), 0);
assert!(builder.layers().is_empty());
}
#[test]
fn test_layer_builder_application_name() {
let builder = LayerBuilder::new().application_name("MyApp");
assert_eq!(builder.application_name, Some("MyApp".to_string()));
}
#[test]
fn test_layer_builder_layers_mut() {
let mut builder = LayerBuilder::new();
builder.add_layer("Layer1");
builder.layers_mut().push(Layer::new("Layer2"));
assert_eq!(builder.len(), 2);
}
#[test]
fn test_build_oc_properties_basic() {
let mut builder = LayerBuilder::new();
builder.add_layer("Visible").visible(true);
builder.add_layer("Hidden").visible(false);
let refs = vec![ObjectRef::new(10, 0), ObjectRef::new(11, 0)];
let props = builder.build_oc_properties(&refs);
assert!(props.contains_key("OCGs"));
assert!(props.contains_key("D"));
if let Some(Object::Array(ocgs)) = props.get("OCGs") {
assert_eq!(ocgs.len(), 2);
}
if let Some(Object::Dictionary(d_dict)) = props.get("D") {
assert!(d_dict.contains_key("Name"));
assert!(d_dict.contains_key("BaseState"));
assert!(d_dict.contains_key("Order"));
if let Some(Object::Array(on_arr)) = d_dict.get("ON") {
assert_eq!(on_arr.len(), 1);
}
if let Some(Object::Array(off_arr)) = d_dict.get("OFF") {
assert_eq!(off_arr.len(), 1);
}
}
}
#[test]
fn test_build_oc_properties_with_creator() {
let mut builder = LayerBuilder::new().creator("pdf_oxide_test");
builder.add_layer("Layer1");
let refs = vec![ObjectRef::new(1, 0)];
let props = builder.build_oc_properties(&refs);
if let Some(Object::Dictionary(d_dict)) = props.get("D") {
if let Some(Object::String(creator_bytes)) = d_dict.get("Creator") {
assert_eq!(creator_bytes, b"pdf_oxide_test");
} else {
panic!("Expected Creator string");
}
}
}
#[test]
fn test_build_oc_properties_off_base_state() {
let mut builder = LayerBuilder::new().base_state(LayerVisibility::Off);
builder.add_layer("Layer1");
let refs = vec![ObjectRef::new(1, 0)];
let props = builder.build_oc_properties(&refs);
if let Some(Object::Dictionary(d_dict)) = props.get("D") {
assert_eq!(d_dict.get("BaseState"), Some(&Object::Name("OFF".to_string())));
}
}
#[test]
fn test_build_oc_properties_all_visible() {
let mut builder = LayerBuilder::new();
builder.add_layer("Layer1").visible(true);
builder.add_layer("Layer2").visible(true);
let refs = vec![ObjectRef::new(1, 0), ObjectRef::new(2, 0)];
let props = builder.build_oc_properties(&refs);
if let Some(Object::Dictionary(d_dict)) = props.get("D") {
if let Some(Object::Array(on_arr)) = d_dict.get("ON") {
assert_eq!(on_arr.len(), 2);
}
assert!(!d_dict.contains_key("OFF"));
}
}
#[test]
fn test_build_oc_properties_all_hidden() {
let mut builder = LayerBuilder::new();
builder.add_layer("Layer1").visible(false);
builder.add_layer("Layer2").visible(false);
let refs = vec![ObjectRef::new(1, 0), ObjectRef::new(2, 0)];
let props = builder.build_oc_properties(&refs);
if let Some(Object::Dictionary(d_dict)) = props.get("D") {
assert!(!d_dict.contains_key("ON"));
if let Some(Object::Array(off_arr)) = d_dict.get("OFF") {
assert_eq!(off_arr.len(), 2);
}
}
}
#[test]
fn test_build_oc_properties_more_layers_than_refs() {
let mut builder = LayerBuilder::new();
builder.add_layer("Layer1").visible(true);
builder.add_layer("Layer2").visible(true);
builder.add_layer("Layer3").visible(true);
let refs = vec![ObjectRef::new(1, 0)]; let props = builder.build_oc_properties(&refs);
if let Some(Object::Dictionary(d_dict)) = props.get("D") {
if let Some(Object::Array(on_arr)) = d_dict.get("ON") {
assert_eq!(on_arr.len(), 1); }
}
}
#[test]
fn test_layer_membership_add_ocg() {
let mut membership = LayerMembership::new(ObjectRef::new(1, 0));
membership.add_ocg(ObjectRef::new(2, 0));
membership.add_ocg(ObjectRef::new(3, 0));
assert_eq!(membership.ocgs.len(), 3);
}
#[test]
fn test_layer_membership_policy_method() {
let membership =
LayerMembership::new(ObjectRef::new(1, 0)).policy(VisibilityPolicy::AllOff);
assert_eq!(membership.policy, VisibilityPolicy::AllOff);
}
#[test]
fn test_layer_membership_ocmd_single_ocg() {
let membership = LayerMembership::new(ObjectRef::new(5, 0));
let dict = membership.build_ocmd_dict();
if let Some(Object::Reference(r)) = dict.get("OCGs") {
assert_eq!(r.id, 5);
assert_eq!(r.gen, 0);
} else {
panic!("Expected single Reference for single OCG");
}
}
#[test]
fn test_layer_membership_ocmd_multiple_ocgs() {
let mut membership = LayerMembership::new(ObjectRef::new(5, 0));
membership.add_ocg(ObjectRef::new(6, 0));
let dict = membership.build_ocmd_dict();
if let Some(Object::Array(arr)) = dict.get("OCGs") {
assert_eq!(arr.len(), 2);
} else {
panic!("Expected Array for multiple OCGs");
}
}
#[test]
fn test_layer_membership_ocmd_all_policies() {
let policies = vec![
(VisibilityPolicy::AllOn, "AllOn"),
(VisibilityPolicy::AnyOn, "AnyOn"),
(VisibilityPolicy::AllOff, "AllOff"),
(VisibilityPolicy::AnyOff, "AnyOff"),
];
for (policy, expected_name) in policies {
let membership = LayerMembership::new(ObjectRef::new(1, 0)).policy(policy);
let dict = membership.build_ocmd_dict();
if let Some(Object::Name(name)) = dict.get("P") {
assert_eq!(name, expected_name);
} else {
panic!("Expected Name for P key");
}
}
}
#[test]
fn test_layer_visibility_default() {
assert_eq!(LayerVisibility::default(), LayerVisibility::On);
}
#[test]
fn test_layer_intent_default() {
assert_eq!(LayerIntent::default(), LayerIntent::View);
}
#[test]
fn test_layer_intent_equality() {
assert_eq!(LayerIntent::View, LayerIntent::View);
assert_eq!(LayerIntent::Design, LayerIntent::Design);
assert_eq!(LayerIntent::Both, LayerIntent::Both);
assert_ne!(LayerIntent::View, LayerIntent::Design);
assert_ne!(LayerIntent::View, LayerIntent::Both);
}
}