use lopdf::{content::Operation, dictionary, Dictionary, Document, Object, ObjectId};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Layer {
pub id: ObjectId,
pub name: String,
pub default_visible: bool,
pub tag: Option<String>,
}
impl Layer {
pub fn new(name: impl Into<String>, default_visible: bool) -> Self {
Layer {
id: (0, 0),
name: name.into(),
default_visible,
tag: None,
}
}
pub fn with_visibility(mut self, visible: bool) -> Self {
self.default_visible = visible;
self
}
}
#[derive(Debug, Clone)]
pub struct OCGConfig {
pub base_state: String,
pub create_panel_ui: bool,
pub intent: Vec<String>,
}
impl Default for OCGConfig {
fn default() -> Self {
OCGConfig {
base_state: "ON".to_string(),
create_panel_ui: true,
intent: vec!["View".to_string()],
}
}
}
pub struct OCGManager {
pub(crate) layers: Vec<Layer>,
pub config: OCGConfig,
pub(crate) oc_properties_id: Option<ObjectId>,
layer_index: HashMap<String, usize>,
}
impl Default for OCGManager {
fn default() -> Self {
Self::new()
}
}
impl OCGManager {
pub fn new() -> Self {
OCGManager {
layers: Vec::new(),
config: OCGConfig::default(),
oc_properties_id: None,
layer_index: HashMap::new(),
}
}
pub fn with_config(config: OCGConfig) -> Self {
OCGManager {
layers: Vec::new(),
config,
oc_properties_id: None,
layer_index: HashMap::new(),
}
}
pub fn add_layer(&mut self, layer: Layer) -> usize {
let index = self.layers.len();
self.layer_index.insert(layer.name.clone(), index);
self.layers.push(layer);
index
}
pub fn get_layer(&self, name: &str) -> Option<&Layer> {
self.layer_index
.get(name)
.and_then(|&idx| self.layers.get(idx))
}
pub fn get_layer_mut(&mut self, name: &str) -> Option<&mut Layer> {
if let Some(&idx) = self.layer_index.get(name) {
self.layers.get_mut(idx)
} else {
None
}
}
pub fn len(&self) -> usize {
self.layers.len()
}
pub fn is_empty(&self) -> bool {
self.layers.is_empty()
}
pub fn has_oc_properties(&self) -> bool {
self.oc_properties_id.is_some()
}
pub fn initialize(&mut self, doc: &mut Document) {
for layer in &mut self.layers {
let ocg_dict = dictionary! {
"Type" => "OCG",
"Name" => Object::string_literal(layer.name.as_bytes().to_vec()),
};
layer.id = doc.add_object(ocg_dict);
}
self.create_oc_properties(doc);
}
pub fn setup_page_resources(&mut self, resources: &mut Dictionary) -> HashMap<String, String> {
let mut properties = Dictionary::new();
let mut layer_map = HashMap::new();
for (i, layer) in self.layers.iter_mut().enumerate() {
let tag = format!("L{}", i);
properties.set(tag.clone(), Object::Reference(layer.id));
layer.tag = Some(tag.clone());
layer_map.insert(layer.name.clone(), tag);
}
resources.set("Properties", properties);
layer_map
}
pub fn update_catalog(&self, doc: &mut Document) {
if let Some(oc_props_id) = self.oc_properties_id {
if let Ok(Object::Reference(catalog_id)) = doc.trailer.get(b"Root") {
if let Ok(Object::Dictionary(ref mut catalog)) = doc.get_object_mut(*catalog_id) {
catalog.set("OCProperties", Object::Reference(oc_props_id));
}
}
}
}
fn create_oc_properties(&mut self, doc: &mut Document) {
let ocg_refs: Vec<Object> = self
.layers
.iter()
.map(|layer| Object::Reference(layer.id))
.collect();
let on_refs: Vec<Object> = self
.layers
.iter()
.filter(|layer| layer.default_visible)
.map(|layer| Object::Reference(layer.id))
.collect();
let off_refs: Vec<Object> = self
.layers
.iter()
.filter(|layer| !layer.default_visible)
.map(|layer| Object::Reference(layer.id))
.collect();
let mut default_dict = dictionary! {
"Order" => ocg_refs.clone(),
};
if !self.config.base_state.is_empty() {
default_dict.set(
"BaseState",
Object::Name(self.config.base_state.as_bytes().to_vec()),
);
}
if !on_refs.is_empty() {
default_dict.set("ON", on_refs);
}
if !off_refs.is_empty() {
default_dict.set("OFF", off_refs);
}
if self.config.create_panel_ui {
default_dict.set("ListMode", "AllPages");
}
let mut oc_properties = dictionary! {
"OCGs" => ocg_refs,
"D" => default_dict,
};
if !self.config.intent.is_empty() {
let intents: Vec<Object> = self
.config
.intent
.iter()
.map(|s| Object::Name(s.as_bytes().to_vec()))
.collect();
oc_properties.set("Intent", intents);
}
self.oc_properties_id = Some(doc.add_object(oc_properties));
}
}
pub struct LayerContentBuilder {
operations: Vec<Operation>,
current_layer: Option<String>,
}
impl Default for LayerContentBuilder {
fn default() -> Self {
Self::new()
}
}
impl LayerContentBuilder {
pub fn new() -> Self {
LayerContentBuilder {
operations: Vec::new(),
current_layer: None,
}
}
pub fn begin_layer(&mut self, layer_tag: &str) -> &mut Self {
if self.current_layer.is_some() {
self.end_layer();
}
self.operations.push(Operation::new(
"BDC",
vec![
Object::Name(b"OC".to_vec()),
Object::Name(layer_tag.as_bytes().to_vec()),
],
));
self.current_layer = Some(layer_tag.to_string());
self
}
pub fn end_layer(&mut self) -> &mut Self {
if self.current_layer.is_some() {
self.operations.push(Operation::new("EMC", vec![]));
self.current_layer = None;
}
self
}
pub fn add_operation(&mut self, op: Operation) -> &mut Self {
self.operations.push(op);
self
}
pub fn add_operations(&mut self, ops: Vec<Operation>) -> &mut Self {
self.operations.extend(ops);
self
}
pub fn build(mut self) -> Vec<Operation> {
if self.current_layer.is_some() {
self.end_layer();
}
self.operations
}
}
pub struct LayerOperations;
impl LayerOperations {
pub fn rectangle(x: f32, y: f32, width: f32, height: f32) -> Operation {
Operation::new("re", vec![x.into(), y.into(), width.into(), height.into()])
}
pub fn fill() -> Operation {
Operation::new("f", vec![])
}
pub fn stroke() -> Operation {
Operation::new("S", vec![])
}
pub fn set_fill_color_rgb(r: f32, g: f32, b: f32) -> Operation {
Operation::new("rg", vec![r.into(), g.into(), b.into()])
}
pub fn set_stroke_color_rgb(r: f32, g: f32, b: f32) -> Operation {
Operation::new("RG", vec![r.into(), g.into(), b.into()])
}
pub fn set_fill_color_gray(gray: f32) -> Operation {
Operation::new("g", vec![gray.into()])
}
pub fn begin_text() -> Operation {
Operation::new("BT", vec![])
}
pub fn end_text() -> Operation {
Operation::new("ET", vec![])
}
pub fn set_font(font_name: &str, size: f32) -> Operation {
Operation::new(
"Tf",
vec![Object::Name(font_name.as_bytes().to_vec()), size.into()],
)
}
pub fn text_position(x: f32, y: f32) -> Operation {
Operation::new("Td", vec![x.into(), y.into()])
}
pub fn show_text(text: &str) -> Operation {
Operation::new("Tj", vec![Object::string_literal(text)])
}
}
#[macro_export]
macro_rules! layer {
($name:expr) => {
Layer::new($name, true)
};
($name:expr, visible: $vis:expr) => {
Layer::new($name, $vis)
};
}
#[macro_export]
macro_rules! layer_content {
($builder:expr, in layer $tag:expr => { $($ops:expr),* }) => {
$builder.begin_layer($tag)
$(.add_operation($ops))*
.end_layer()
};
}