use crate::annotation_types::AnnotationFlags;
use crate::geometry::Rect;
use crate::object::{Object, ObjectRef};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum StampType {
Approved,
Experimental,
NotApproved,
AsIs,
Expired,
NotForPublicRelease,
Confidential,
Final,
Sold,
Departmental,
ForComment,
TopSecret,
Draft,
ForPublicRelease,
Custom(String),
}
impl StampType {
pub fn pdf_name(&self) -> String {
match self {
StampType::Approved => "Approved".to_string(),
StampType::Experimental => "Experimental".to_string(),
StampType::NotApproved => "NotApproved".to_string(),
StampType::AsIs => "AsIs".to_string(),
StampType::Expired => "Expired".to_string(),
StampType::NotForPublicRelease => "NotForPublicRelease".to_string(),
StampType::Confidential => "Confidential".to_string(),
StampType::Final => "Final".to_string(),
StampType::Sold => "Sold".to_string(),
StampType::Departmental => "Departmental".to_string(),
StampType::ForComment => "ForComment".to_string(),
StampType::TopSecret => "TopSecret".to_string(),
StampType::Draft => "Draft".to_string(),
StampType::ForPublicRelease => "ForPublicRelease".to_string(),
StampType::Custom(name) => name.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct StampAnnotation {
pub rect: Rect,
pub stamp_type: StampType,
pub contents: Option<String>,
pub author: Option<String>,
pub subject: Option<String>,
pub flags: AnnotationFlags,
pub creation_date: Option<String>,
pub modification_date: Option<String>,
pub opacity: Option<f32>,
}
impl StampAnnotation {
pub fn new(rect: Rect, stamp_type: StampType) -> Self {
Self {
rect,
stamp_type,
contents: None,
author: None,
subject: None,
flags: AnnotationFlags::printable(),
creation_date: None,
modification_date: None,
opacity: None,
}
}
pub fn approved(rect: Rect) -> Self {
Self::new(rect, StampType::Approved)
}
pub fn draft(rect: Rect) -> Self {
Self::new(rect, StampType::Draft)
}
pub fn confidential(rect: Rect) -> Self {
Self::new(rect, StampType::Confidential)
}
pub fn final_stamp(rect: Rect) -> Self {
Self::new(rect, StampType::Final)
}
pub fn experimental(rect: Rect) -> Self {
Self::new(rect, StampType::Experimental)
}
pub fn not_approved(rect: Rect) -> Self {
Self::new(rect, StampType::NotApproved)
}
pub fn as_is(rect: Rect) -> Self {
Self::new(rect, StampType::AsIs)
}
pub fn expired(rect: Rect) -> Self {
Self::new(rect, StampType::Expired)
}
pub fn not_for_public_release(rect: Rect) -> Self {
Self::new(rect, StampType::NotForPublicRelease)
}
pub fn for_public_release(rect: Rect) -> Self {
Self::new(rect, StampType::ForPublicRelease)
}
pub fn departmental(rect: Rect) -> Self {
Self::new(rect, StampType::Departmental)
}
pub fn for_comment(rect: Rect) -> Self {
Self::new(rect, StampType::ForComment)
}
pub fn top_secret(rect: Rect) -> Self {
Self::new(rect, StampType::TopSecret)
}
pub fn sold(rect: Rect) -> Self {
Self::new(rect, StampType::Sold)
}
pub fn custom(rect: Rect, name: impl Into<String>) -> Self {
Self::new(rect, StampType::Custom(name.into()))
}
pub fn with_stamp_type(mut self, stamp_type: StampType) -> Self {
self.stamp_type = stamp_type;
self
}
pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
self.contents = Some(contents.into());
self
}
pub fn with_author(mut self, author: impl Into<String>) -> Self {
self.author = Some(author.into());
self
}
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
self.flags = flags;
self
}
pub fn with_opacity(mut self, opacity: f32) -> Self {
self.opacity = Some(opacity.clamp(0.0, 1.0));
self
}
pub fn build(&self, _page_refs: &[ObjectRef]) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("Annot".to_string()));
dict.insert("Subtype".to_string(), Object::Name("Stamp".to_string()));
dict.insert(
"Rect".to_string(),
Object::Array(vec![
Object::Real(self.rect.x as f64),
Object::Real(self.rect.y as f64),
Object::Real((self.rect.x + self.rect.width) as f64),
Object::Real((self.rect.y + self.rect.height) as f64),
]),
);
dict.insert("Name".to_string(), Object::Name(self.stamp_type.pdf_name()));
if let Some(ref contents) = self.contents {
dict.insert("Contents".to_string(), Object::String(contents.as_bytes().to_vec()));
}
if self.flags.bits() != 0 {
dict.insert("F".to_string(), Object::Integer(self.flags.bits() as i64));
}
if let Some(opacity) = self.opacity {
dict.insert("CA".to_string(), Object::Real(opacity as f64));
}
if let Some(ref author) = self.author {
dict.insert("T".to_string(), Object::String(author.as_bytes().to_vec()));
}
if let Some(ref subject) = self.subject {
dict.insert("Subj".to_string(), Object::String(subject.as_bytes().to_vec()));
}
if let Some(ref date) = self.creation_date {
dict.insert("CreationDate".to_string(), Object::String(date.as_bytes().to_vec()));
}
if let Some(ref date) = self.modification_date {
dict.insert("M".to_string(), Object::String(date.as_bytes().to_vec()));
}
dict
}
pub fn rect(&self) -> Rect {
self.rect
}
}
impl Default for StampAnnotation {
fn default() -> Self {
Self::new(Rect::new(0.0, 0.0, 100.0, 50.0), StampType::Draft)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stamp_new() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::new(rect, StampType::Approved);
assert_eq!(stamp.rect, rect);
assert_eq!(stamp.stamp_type, StampType::Approved);
}
#[test]
fn test_stamp_approved() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::approved(rect);
assert_eq!(stamp.stamp_type, StampType::Approved);
}
#[test]
fn test_stamp_draft() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::draft(rect);
assert_eq!(stamp.stamp_type, StampType::Draft);
}
#[test]
fn test_stamp_confidential() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::confidential(rect);
assert_eq!(stamp.stamp_type, StampType::Confidential);
}
#[test]
fn test_stamp_custom() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::custom(rect, "ReviewPending");
assert_eq!(stamp.stamp_type, StampType::Custom("ReviewPending".to_string()));
}
#[test]
fn test_stamp_type_pdf_names() {
assert_eq!(StampType::Approved.pdf_name(), "Approved");
assert_eq!(StampType::Draft.pdf_name(), "Draft");
assert_eq!(StampType::Confidential.pdf_name(), "Confidential");
assert_eq!(StampType::NotApproved.pdf_name(), "NotApproved");
assert_eq!(StampType::TopSecret.pdf_name(), "TopSecret");
assert_eq!(StampType::Custom("MyStamp".to_string()).pdf_name(), "MyStamp");
}
#[test]
fn test_stamp_fluent_builder() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::approved(rect)
.with_contents("Approved by manager")
.with_author("John Doe")
.with_subject("Approval Stamp")
.with_opacity(0.9);
assert_eq!(stamp.contents, Some("Approved by manager".to_string()));
assert_eq!(stamp.author, Some("John Doe".to_string()));
assert_eq!(stamp.subject, Some("Approval Stamp".to_string()));
assert_eq!(stamp.opacity, Some(0.9));
}
#[test]
fn test_stamp_build() {
let rect = Rect::new(100.0, 700.0, 150.0, 50.0);
let stamp = StampAnnotation::approved(rect).with_contents("Approved");
let dict = stamp.build(&[]);
assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
assert_eq!(dict.get("Subtype"), Some(&Object::Name("Stamp".to_string())));
assert_eq!(dict.get("Name"), Some(&Object::Name("Approved".to_string())));
assert!(dict.contains_key("Rect"));
assert!(dict.contains_key("Contents"));
}
#[test]
fn test_all_standard_stamps() {
let rect = Rect::new(0.0, 0.0, 100.0, 50.0);
let stamps = vec![
StampAnnotation::approved(rect),
StampAnnotation::draft(rect),
StampAnnotation::confidential(rect),
StampAnnotation::final_stamp(rect),
StampAnnotation::experimental(rect),
StampAnnotation::not_approved(rect),
StampAnnotation::as_is(rect),
StampAnnotation::expired(rect),
StampAnnotation::not_for_public_release(rect),
StampAnnotation::for_public_release(rect),
StampAnnotation::departmental(rect),
StampAnnotation::for_comment(rect),
StampAnnotation::top_secret(rect),
StampAnnotation::sold(rect),
];
assert_eq!(stamps.len(), 14);
for stamp in stamps {
let dict = stamp.build(&[]);
assert!(dict.contains_key("Name"));
}
}
}