#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AnnotationSubtype {
Text,
Link,
FreeText,
Line,
Square,
Circle,
Polygon,
PolyLine,
Highlight,
Underline,
Squiggly,
StrikeOut,
Stamp,
Caret,
Ink,
Popup,
FileAttachment,
Sound,
Movie,
Widget,
Screen,
PrinterMark,
TrapNet,
Watermark,
ThreeD,
Redact,
RichMedia,
Unknown,
}
impl AnnotationSubtype {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::Text => "Text",
Self::Link => "Link",
Self::FreeText => "FreeText",
Self::Line => "Line",
Self::Square => "Square",
Self::Circle => "Circle",
Self::Polygon => "Polygon",
Self::PolyLine => "PolyLine",
Self::Highlight => "Highlight",
Self::Underline => "Underline",
Self::Squiggly => "Squiggly",
Self::StrikeOut => "StrikeOut",
Self::Stamp => "Stamp",
Self::Caret => "Caret",
Self::Ink => "Ink",
Self::Popup => "Popup",
Self::FileAttachment => "FileAttachment",
Self::Sound => "Sound",
Self::Movie => "Movie",
Self::Widget => "Widget",
Self::Screen => "Screen",
Self::PrinterMark => "PrinterMark",
Self::TrapNet => "TrapNet",
Self::Watermark => "Watermark",
Self::ThreeD => "3D",
Self::Redact => "Redact",
Self::RichMedia => "RichMedia",
Self::Unknown => "Unknown",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"Text" => Self::Text,
"Link" => Self::Link,
"FreeText" => Self::FreeText,
"Line" => Self::Line,
"Square" => Self::Square,
"Circle" => Self::Circle,
"Polygon" => Self::Polygon,
"PolyLine" => Self::PolyLine,
"Highlight" => Self::Highlight,
"Underline" => Self::Underline,
"Squiggly" => Self::Squiggly,
"StrikeOut" => Self::StrikeOut,
"Stamp" => Self::Stamp,
"Caret" => Self::Caret,
"Ink" => Self::Ink,
"Popup" => Self::Popup,
"FileAttachment" => Self::FileAttachment,
"Sound" => Self::Sound,
"Movie" => Self::Movie,
"Widget" => Self::Widget,
"Screen" => Self::Screen,
"PrinterMark" => Self::PrinterMark,
"TrapNet" => Self::TrapNet,
"Watermark" => Self::Watermark,
"3D" => Self::ThreeD,
"Redact" => Self::Redact,
"RichMedia" => Self::RichMedia,
_ => Self::Unknown,
}
}
pub fn is_markup(&self) -> bool {
matches!(
self,
Self::Text
| Self::FreeText
| Self::Line
| Self::Square
| Self::Circle
| Self::Polygon
| Self::PolyLine
| Self::Highlight
| Self::Underline
| Self::Squiggly
| Self::StrikeOut
| Self::Stamp
| Self::Caret
| Self::Ink
| Self::FileAttachment
| Self::Sound
| Self::Redact
)
}
pub fn is_text_markup(&self) -> bool {
matches!(self, Self::Highlight | Self::Underline | Self::Squiggly | Self::StrikeOut)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AnnotationFlags(u32);
impl AnnotationFlags {
pub const INVISIBLE: u32 = 1 << 0;
pub const HIDDEN: u32 = 1 << 1;
pub const PRINT: u32 = 1 << 2;
pub const NO_ZOOM: u32 = 1 << 3;
pub const NO_ROTATE: u32 = 1 << 4;
pub const NO_VIEW: u32 = 1 << 5;
pub const READ_ONLY: u32 = 1 << 6;
pub const LOCKED: u32 = 1 << 7;
pub const TOGGLE_NO_VIEW: u32 = 1 << 8;
pub const LOCKED_CONTENTS: u32 = 1 << 9;
pub fn new(value: u32) -> Self {
Self(value)
}
pub fn empty() -> Self {
Self(0)
}
pub fn printable() -> Self {
Self(Self::PRINT)
}
pub fn bits(&self) -> u32 {
self.0
}
pub fn contains(&self, flag: u32) -> bool {
(self.0 & flag) != 0
}
pub fn set(&mut self, flag: u32) {
self.0 |= flag;
}
pub fn clear(&mut self, flag: u32) {
self.0 &= !flag;
}
pub fn is_invisible(&self) -> bool {
self.contains(Self::INVISIBLE)
}
pub fn is_hidden(&self) -> bool {
self.contains(Self::HIDDEN)
}
pub fn is_printable(&self) -> bool {
self.contains(Self::PRINT)
}
pub fn is_no_zoom(&self) -> bool {
self.contains(Self::NO_ZOOM)
}
pub fn is_no_rotate(&self) -> bool {
self.contains(Self::NO_ROTATE)
}
pub fn is_read_only(&self) -> bool {
self.contains(Self::READ_ONLY)
}
pub fn is_locked(&self) -> bool {
self.contains(Self::LOCKED)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BorderStyleType {
#[default]
Solid,
Dashed,
Beveled,
Inset,
Underline,
}
impl BorderStyleType {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::Solid => "S",
Self::Dashed => "D",
Self::Beveled => "B",
Self::Inset => "I",
Self::Underline => "U",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"S" => Self::Solid,
"D" => Self::Dashed,
"B" => Self::Beveled,
"I" => Self::Inset,
"U" => Self::Underline,
_ => Self::Solid,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AnnotationBorderStyle {
pub width: f32,
pub style: BorderStyleType,
pub dash_pattern: Option<Vec<f32>>,
}
impl AnnotationBorderStyle {
pub fn solid(width: f32) -> Self {
Self {
width,
style: BorderStyleType::Solid,
dash_pattern: None,
}
}
pub fn dashed(width: f32, dash: f32, gap: f32) -> Self {
Self {
width,
style: BorderStyleType::Dashed,
dash_pattern: Some(vec![dash, gap]),
}
}
pub fn none() -> Self {
Self {
width: 0.0,
style: BorderStyleType::Solid,
dash_pattern: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BorderEffectStyle {
#[default]
None,
Cloudy,
}
impl BorderEffectStyle {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::None => "S",
Self::Cloudy => "C",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"C" => Self::Cloudy,
_ => Self::None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BorderEffect {
pub style: BorderEffectStyle,
pub intensity: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineEndingStyle {
#[default]
None,
Square,
Circle,
Diamond,
OpenArrow,
ClosedArrow,
Butt,
ROpenArrow,
RClosedArrow,
Slash,
}
impl LineEndingStyle {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::None => "None",
Self::Square => "Square",
Self::Circle => "Circle",
Self::Diamond => "Diamond",
Self::OpenArrow => "OpenArrow",
Self::ClosedArrow => "ClosedArrow",
Self::Butt => "Butt",
Self::ROpenArrow => "ROpenArrow",
Self::RClosedArrow => "RClosedArrow",
Self::Slash => "Slash",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"None" => Self::None,
"Square" => Self::Square,
"Circle" => Self::Circle,
"Diamond" => Self::Diamond,
"OpenArrow" => Self::OpenArrow,
"ClosedArrow" => Self::ClosedArrow,
"Butt" => Self::Butt,
"ROpenArrow" => Self::ROpenArrow,
"RClosedArrow" => Self::RClosedArrow,
"Slash" => Self::Slash,
_ => Self::None,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum AnnotationColor {
#[default]
None,
Gray(f32),
Rgb(f32, f32, f32),
Cmyk(f32, f32, f32, f32),
}
impl AnnotationColor {
pub fn yellow() -> Self {
Self::Rgb(1.0, 1.0, 0.0)
}
pub fn red() -> Self {
Self::Rgb(1.0, 0.0, 0.0)
}
pub fn green() -> Self {
Self::Rgb(0.0, 1.0, 0.0)
}
pub fn blue() -> Self {
Self::Rgb(0.0, 0.0, 1.0)
}
pub fn black() -> Self {
Self::Gray(0.0)
}
pub fn white() -> Self {
Self::Gray(1.0)
}
pub fn to_array(&self) -> Option<Vec<f32>> {
match self {
Self::None => None,
Self::Gray(g) => Some(vec![*g]),
Self::Rgb(r, g, b) => Some(vec![*r, *g, *b]),
Self::Cmyk(c, m, y, k) => Some(vec![*c, *m, *y, *k]),
}
}
pub fn from_array(arr: &[f32]) -> Self {
match arr.len() {
0 => Self::None,
1 => Self::Gray(arr[0]),
3 => Self::Rgb(arr[0], arr[1], arr[2]),
4 => Self::Cmyk(arr[0], arr[1], arr[2], arr[3]),
_ => Self::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextAnnotationIcon {
Comment,
Key,
#[default]
Note,
Help,
NewParagraph,
Paragraph,
Insert,
}
impl TextAnnotationIcon {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::Comment => "Comment",
Self::Key => "Key",
Self::Note => "Note",
Self::Help => "Help",
Self::NewParagraph => "NewParagraph",
Self::Paragraph => "Paragraph",
Self::Insert => "Insert",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"Comment" => Self::Comment,
"Key" => Self::Key,
"Note" => Self::Note,
"Help" => Self::Help,
"NewParagraph" => Self::NewParagraph,
"Paragraph" => Self::Paragraph,
"Insert" => Self::Insert,
_ => Self::Note,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextMarkupType {
Highlight,
Underline,
Squiggly,
StrikeOut,
}
impl TextMarkupType {
pub fn subtype(&self) -> AnnotationSubtype {
match self {
Self::Highlight => AnnotationSubtype::Highlight,
Self::Underline => AnnotationSubtype::Underline,
Self::Squiggly => AnnotationSubtype::Squiggly,
Self::StrikeOut => AnnotationSubtype::StrikeOut,
}
}
pub fn default_color(&self) -> AnnotationColor {
match self {
Self::Highlight => AnnotationColor::yellow(),
Self::Underline => AnnotationColor::green(),
Self::Squiggly => AnnotationColor::Rgb(1.0, 0.5, 0.0), Self::StrikeOut => AnnotationColor::red(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum StampType {
Approved,
Experimental,
NotApproved,
AsIs,
Expired,
NotForPublicRelease,
Confidential,
Final,
Sold,
Departmental,
ForComment,
TopSecret,
#[default]
Draft,
ForPublicRelease,
Custom(String),
}
impl StampType {
pub fn pdf_name(&self) -> String {
match self {
Self::Approved => "Approved".to_string(),
Self::Experimental => "Experimental".to_string(),
Self::NotApproved => "NotApproved".to_string(),
Self::AsIs => "AsIs".to_string(),
Self::Expired => "Expired".to_string(),
Self::NotForPublicRelease => "NotForPublicRelease".to_string(),
Self::Confidential => "Confidential".to_string(),
Self::Final => "Final".to_string(),
Self::Sold => "Sold".to_string(),
Self::Departmental => "Departmental".to_string(),
Self::ForComment => "ForComment".to_string(),
Self::TopSecret => "TopSecret".to_string(),
Self::Draft => "Draft".to_string(),
Self::ForPublicRelease => "ForPublicRelease".to_string(),
Self::Custom(name) => name.clone(),
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"Approved" => Self::Approved,
"Experimental" => Self::Experimental,
"NotApproved" => Self::NotApproved,
"AsIs" => Self::AsIs,
"Expired" => Self::Expired,
"NotForPublicRelease" => Self::NotForPublicRelease,
"Confidential" => Self::Confidential,
"Final" => Self::Final,
"Sold" => Self::Sold,
"Departmental" => Self::Departmental,
"ForComment" => Self::ForComment,
"TopSecret" => Self::TopSecret,
"Draft" => Self::Draft,
"ForPublicRelease" => Self::ForPublicRelease,
other => Self::Custom(other.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FreeTextIntent {
#[default]
FreeText,
FreeTextCallout,
FreeTextTypeWriter,
}
impl FreeTextIntent {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::FreeText => "FreeText",
Self::FreeTextCallout => "FreeTextCallout",
Self::FreeTextTypeWriter => "FreeTextTypeWriter",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"FreeTextCallout" => Self::FreeTextCallout,
"FreeTextTypeWriter" => Self::FreeTextTypeWriter,
_ => Self::FreeText,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextAlignment {
#[default]
Left,
Center,
Right,
}
impl TextAlignment {
pub fn to_pdf_int(&self) -> i32 {
match self {
Self::Left => 0,
Self::Center => 1,
Self::Right => 2,
}
}
pub fn from_pdf_int(value: i32) -> Self {
match value {
1 => Self::Center,
2 => Self::Right,
_ => Self::Left,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CaretSymbol {
#[default]
None,
Paragraph,
}
impl CaretSymbol {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::None => "None",
Self::Paragraph => "P",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"P" => Self::Paragraph,
_ => Self::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FileAttachmentIcon {
GraphPushPin,
#[default]
PaperclipTag,
PushPin,
}
impl FileAttachmentIcon {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::GraphPushPin => "GraphPushPin",
Self::PaperclipTag => "PaperclipTag",
Self::PushPin => "PushPin",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"GraphPushPin" => Self::GraphPushPin,
"PushPin" => Self::PushPin,
_ => Self::PaperclipTag,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ReplyType {
#[default]
Reply,
Group,
}
impl ReplyType {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::Reply => "R",
Self::Group => "Group",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"Group" => Self::Group,
_ => Self::Reply,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HighlightMode {
None,
#[default]
Invert,
Outline,
Push,
}
impl HighlightMode {
pub fn pdf_name(&self) -> &'static str {
match self {
Self::None => "N",
Self::Invert => "I",
Self::Outline => "O",
Self::Push => "P",
}
}
pub fn from_pdf_name(name: &str) -> Self {
match name {
"N" => Self::None,
"O" => Self::Outline,
"P" => Self::Push,
_ => Self::Invert,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum WidgetFieldType {
#[default]
Text,
Checkbox {
checked: bool,
},
Radio {
selected: Option<String>,
},
Button,
Choice {
options: Vec<String>,
selected: Option<String>,
},
Signature,
Unknown,
}
pub type QuadPoint = [f64; 8];
pub mod quad_points {
use super::QuadPoint;
use crate::geometry::Rect;
pub fn from_rect(rect: &Rect) -> QuadPoint {
let x1 = rect.x as f64;
let y1 = rect.y as f64;
let x2 = (rect.x + rect.width) as f64;
let y2 = rect.y as f64;
let x3 = (rect.x + rect.width) as f64;
let y3 = (rect.y + rect.height) as f64;
let x4 = rect.x as f64;
let y4 = (rect.y + rect.height) as f64;
[x1, y1, x2, y2, x3, y3, x4, y4]
}
pub fn bounding_rect(quad: &QuadPoint) -> Rect {
let min_x = quad[0].min(quad[2]).min(quad[4]).min(quad[6]) as f32;
let max_x = quad[0].max(quad[2]).max(quad[4]).max(quad[6]) as f32;
let min_y = quad[1].min(quad[3]).min(quad[5]).min(quad[7]) as f32;
let max_y = quad[1].max(quad[3]).max(quad[5]).max(quad[7]) as f32;
Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
}
pub fn parse(arr: &[f64]) -> Vec<QuadPoint> {
arr.chunks_exact(8)
.map(|chunk| {
[
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
]
})
.collect()
}
pub fn flatten(quads: &[QuadPoint]) -> Vec<f64> {
quads.iter().flat_map(|q| q.iter().copied()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Rect;
#[test]
fn test_annotation_subtype_roundtrip() {
let subtypes = [
AnnotationSubtype::Text,
AnnotationSubtype::Link,
AnnotationSubtype::Highlight,
AnnotationSubtype::StrikeOut,
AnnotationSubtype::Stamp,
AnnotationSubtype::Ink,
AnnotationSubtype::Widget,
AnnotationSubtype::Redact,
];
for subtype in subtypes {
let name = subtype.pdf_name();
let parsed = AnnotationSubtype::from_pdf_name(name);
assert_eq!(subtype, parsed);
}
}
#[test]
fn test_annotation_flags() {
let mut flags = AnnotationFlags::empty();
assert!(!flags.is_printable());
flags.set(AnnotationFlags::PRINT);
assert!(flags.is_printable());
flags.set(AnnotationFlags::READ_ONLY);
assert!(flags.is_read_only());
flags.clear(AnnotationFlags::PRINT);
assert!(!flags.is_printable());
assert!(flags.is_read_only());
}
#[test]
fn test_annotation_color() {
let yellow = AnnotationColor::yellow();
let arr = yellow.to_array().unwrap();
assert_eq!(arr, vec![1.0, 1.0, 0.0]);
let parsed = AnnotationColor::from_array(&arr);
assert_eq!(parsed, yellow);
}
#[test]
fn test_quad_points() {
let rect = Rect::new(100.0, 200.0, 50.0, 20.0);
let quad = quad_points::from_rect(&rect);
assert_eq!(quad[0], 100.0); assert_eq!(quad[1], 200.0); assert_eq!(quad[2], 150.0); assert_eq!(quad[3], 200.0);
let bounding = quad_points::bounding_rect(&quad);
assert_eq!(bounding.x, rect.x);
assert_eq!(bounding.y, rect.y);
assert_eq!(bounding.width, rect.width);
assert_eq!(bounding.height, rect.height);
}
#[test]
fn test_stamp_types() {
assert_eq!(StampType::Approved.pdf_name(), "Approved");
assert_eq!(StampType::Draft.pdf_name(), "Draft");
assert_eq!(StampType::Custom("MyStamp".to_string()).pdf_name(), "MyStamp");
assert_eq!(StampType::from_pdf_name("Approved"), StampType::Approved);
assert_eq!(
StampType::from_pdf_name("CustomName"),
StampType::Custom("CustomName".to_string())
);
}
#[test]
fn test_line_ending_styles() {
let styles = [
LineEndingStyle::None,
LineEndingStyle::OpenArrow,
LineEndingStyle::ClosedArrow,
LineEndingStyle::Circle,
LineEndingStyle::Square,
];
for style in styles {
let name = style.pdf_name();
let parsed = LineEndingStyle::from_pdf_name(name);
assert_eq!(style, parsed);
}
}
#[test]
fn test_is_markup() {
assert!(AnnotationSubtype::Text.is_markup());
assert!(AnnotationSubtype::Highlight.is_markup());
assert!(AnnotationSubtype::Ink.is_markup());
assert!(!AnnotationSubtype::Link.is_markup());
assert!(!AnnotationSubtype::Widget.is_markup());
assert!(!AnnotationSubtype::Popup.is_markup());
}
#[test]
fn test_is_text_markup() {
assert!(AnnotationSubtype::Highlight.is_text_markup());
assert!(AnnotationSubtype::Underline.is_text_markup());
assert!(AnnotationSubtype::Squiggly.is_text_markup());
assert!(AnnotationSubtype::StrikeOut.is_text_markup());
assert!(!AnnotationSubtype::Text.is_text_markup());
assert!(!AnnotationSubtype::Ink.is_text_markup());
}
#[test]
fn test_annotation_subtype_all_variants_roundtrip() {
let all_subtypes = [
(AnnotationSubtype::Text, "Text"),
(AnnotationSubtype::Link, "Link"),
(AnnotationSubtype::FreeText, "FreeText"),
(AnnotationSubtype::Line, "Line"),
(AnnotationSubtype::Square, "Square"),
(AnnotationSubtype::Circle, "Circle"),
(AnnotationSubtype::Polygon, "Polygon"),
(AnnotationSubtype::PolyLine, "PolyLine"),
(AnnotationSubtype::Highlight, "Highlight"),
(AnnotationSubtype::Underline, "Underline"),
(AnnotationSubtype::Squiggly, "Squiggly"),
(AnnotationSubtype::StrikeOut, "StrikeOut"),
(AnnotationSubtype::Stamp, "Stamp"),
(AnnotationSubtype::Caret, "Caret"),
(AnnotationSubtype::Ink, "Ink"),
(AnnotationSubtype::Popup, "Popup"),
(AnnotationSubtype::FileAttachment, "FileAttachment"),
(AnnotationSubtype::Sound, "Sound"),
(AnnotationSubtype::Movie, "Movie"),
(AnnotationSubtype::Widget, "Widget"),
(AnnotationSubtype::Screen, "Screen"),
(AnnotationSubtype::PrinterMark, "PrinterMark"),
(AnnotationSubtype::TrapNet, "TrapNet"),
(AnnotationSubtype::Watermark, "Watermark"),
(AnnotationSubtype::ThreeD, "3D"),
(AnnotationSubtype::Redact, "Redact"),
(AnnotationSubtype::RichMedia, "RichMedia"),
(AnnotationSubtype::Unknown, "Unknown"),
];
for (subtype, expected_name) in &all_subtypes {
assert_eq!(subtype.pdf_name(), *expected_name, "pdf_name mismatch for {:?}", subtype);
let parsed = AnnotationSubtype::from_pdf_name(expected_name);
assert_eq!(*subtype, parsed, "roundtrip mismatch for {}", expected_name);
}
}
#[test]
fn test_annotation_subtype_unknown_name() {
let parsed = AnnotationSubtype::from_pdf_name("NonExistentType");
assert_eq!(parsed, AnnotationSubtype::Unknown);
}
#[test]
fn test_annotation_subtype_is_markup_complete() {
let markup = [
AnnotationSubtype::Text,
AnnotationSubtype::FreeText,
AnnotationSubtype::Line,
AnnotationSubtype::Square,
AnnotationSubtype::Circle,
AnnotationSubtype::Polygon,
AnnotationSubtype::PolyLine,
AnnotationSubtype::Highlight,
AnnotationSubtype::Underline,
AnnotationSubtype::Squiggly,
AnnotationSubtype::StrikeOut,
AnnotationSubtype::Stamp,
AnnotationSubtype::Caret,
AnnotationSubtype::Ink,
AnnotationSubtype::FileAttachment,
AnnotationSubtype::Sound,
AnnotationSubtype::Redact,
];
for subtype in &markup {
assert!(subtype.is_markup(), "{:?} should be markup", subtype);
}
let non_markup = [
AnnotationSubtype::Link,
AnnotationSubtype::Popup,
AnnotationSubtype::Movie,
AnnotationSubtype::Widget,
AnnotationSubtype::Screen,
AnnotationSubtype::PrinterMark,
AnnotationSubtype::TrapNet,
AnnotationSubtype::Watermark,
AnnotationSubtype::ThreeD,
AnnotationSubtype::RichMedia,
AnnotationSubtype::Unknown,
];
for subtype in &non_markup {
assert!(!subtype.is_markup(), "{:?} should NOT be markup", subtype);
}
}
#[test]
fn test_annotation_flags_new() {
let flags = AnnotationFlags::new(0b101); assert!(flags.is_invisible());
assert!(!flags.is_hidden());
assert!(flags.is_printable());
}
#[test]
fn test_annotation_flags_printable_constructor() {
let flags = AnnotationFlags::printable();
assert!(flags.is_printable());
assert!(!flags.is_invisible());
assert!(!flags.is_hidden());
assert_eq!(flags.bits(), AnnotationFlags::PRINT);
}
#[test]
fn test_annotation_flags_all_named_flags() {
let mut flags = AnnotationFlags::empty();
flags.set(AnnotationFlags::INVISIBLE);
assert!(flags.is_invisible());
flags.set(AnnotationFlags::HIDDEN);
assert!(flags.is_hidden());
flags.set(AnnotationFlags::PRINT);
assert!(flags.is_printable());
flags.set(AnnotationFlags::NO_ZOOM);
assert!(flags.is_no_zoom());
flags.set(AnnotationFlags::NO_ROTATE);
assert!(flags.is_no_rotate());
flags.set(AnnotationFlags::READ_ONLY);
assert!(flags.is_read_only());
flags.set(AnnotationFlags::LOCKED);
assert!(flags.is_locked());
}
#[test]
fn test_annotation_flags_no_view_and_toggle() {
let mut flags = AnnotationFlags::empty();
flags.set(AnnotationFlags::NO_VIEW);
assert!(flags.contains(AnnotationFlags::NO_VIEW));
flags.set(AnnotationFlags::TOGGLE_NO_VIEW);
assert!(flags.contains(AnnotationFlags::TOGGLE_NO_VIEW));
flags.set(AnnotationFlags::LOCKED_CONTENTS);
assert!(flags.contains(AnnotationFlags::LOCKED_CONTENTS));
}
#[test]
fn test_annotation_flags_clear_all() {
let mut flags = AnnotationFlags::new(0xFFFF);
flags.clear(AnnotationFlags::PRINT);
assert!(!flags.is_printable());
assert!(flags.is_invisible()); }
#[test]
fn test_annotation_flags_default() {
let flags = AnnotationFlags::default();
assert_eq!(flags.bits(), 0);
assert!(!flags.is_printable());
assert!(!flags.is_invisible());
}
#[test]
fn test_annotation_flags_bits_roundtrip() {
let flags = AnnotationFlags::new(0b1010_0101);
assert_eq!(flags.bits(), 0b1010_0101);
}
#[test]
fn test_border_style_type_all_variants() {
let styles = [
(BorderStyleType::Solid, "S"),
(BorderStyleType::Dashed, "D"),
(BorderStyleType::Beveled, "B"),
(BorderStyleType::Inset, "I"),
(BorderStyleType::Underline, "U"),
];
for (style, name) in &styles {
assert_eq!(style.pdf_name(), *name);
assert_eq!(BorderStyleType::from_pdf_name(name), *style);
}
}
#[test]
fn test_border_style_type_unknown_defaults_to_solid() {
assert_eq!(BorderStyleType::from_pdf_name("X"), BorderStyleType::Solid);
assert_eq!(BorderStyleType::from_pdf_name(""), BorderStyleType::Solid);
}
#[test]
fn test_border_style_type_default() {
let default: BorderStyleType = Default::default();
assert_eq!(default, BorderStyleType::Solid);
}
#[test]
fn test_annotation_border_style_solid() {
let bs = AnnotationBorderStyle::solid(2.0);
assert_eq!(bs.width, 2.0);
assert_eq!(bs.style, BorderStyleType::Solid);
assert!(bs.dash_pattern.is_none());
}
#[test]
fn test_annotation_border_style_dashed() {
let bs = AnnotationBorderStyle::dashed(1.5, 3.0, 2.0);
assert_eq!(bs.width, 1.5);
assert_eq!(bs.style, BorderStyleType::Dashed);
assert_eq!(bs.dash_pattern, Some(vec![3.0, 2.0]));
}
#[test]
fn test_annotation_border_style_none() {
let bs = AnnotationBorderStyle::none();
assert_eq!(bs.width, 0.0);
assert_eq!(bs.style, BorderStyleType::Solid);
assert!(bs.dash_pattern.is_none());
}
#[test]
fn test_annotation_border_style_default() {
let bs: AnnotationBorderStyle = Default::default();
assert_eq!(bs.width, 0.0);
assert_eq!(bs.style, BorderStyleType::Solid);
assert!(bs.dash_pattern.is_none());
}
#[test]
fn test_border_effect_style_roundtrip() {
assert_eq!(BorderEffectStyle::None.pdf_name(), "S");
assert_eq!(BorderEffectStyle::Cloudy.pdf_name(), "C");
assert_eq!(BorderEffectStyle::from_pdf_name("S"), BorderEffectStyle::None);
assert_eq!(BorderEffectStyle::from_pdf_name("C"), BorderEffectStyle::Cloudy);
assert_eq!(BorderEffectStyle::from_pdf_name("X"), BorderEffectStyle::None);
}
#[test]
fn test_border_effect_style_default() {
let default: BorderEffectStyle = Default::default();
assert_eq!(default, BorderEffectStyle::None);
}
#[test]
fn test_border_effect_default() {
let effect: BorderEffect = Default::default();
assert_eq!(effect.style, BorderEffectStyle::None);
assert_eq!(effect.intensity, 0.0);
}
#[test]
fn test_line_ending_style_all_variants() {
let styles = [
(LineEndingStyle::None, "None"),
(LineEndingStyle::Square, "Square"),
(LineEndingStyle::Circle, "Circle"),
(LineEndingStyle::Diamond, "Diamond"),
(LineEndingStyle::OpenArrow, "OpenArrow"),
(LineEndingStyle::ClosedArrow, "ClosedArrow"),
(LineEndingStyle::Butt, "Butt"),
(LineEndingStyle::ROpenArrow, "ROpenArrow"),
(LineEndingStyle::RClosedArrow, "RClosedArrow"),
(LineEndingStyle::Slash, "Slash"),
];
for (style, name) in &styles {
assert_eq!(style.pdf_name(), *name, "pdf_name mismatch for {:?}", style);
assert_eq!(
LineEndingStyle::from_pdf_name(name),
*style,
"from_pdf_name mismatch for {}",
name
);
}
}
#[test]
fn test_line_ending_style_unknown_defaults_to_none() {
assert_eq!(LineEndingStyle::from_pdf_name("Unknown"), LineEndingStyle::None);
assert_eq!(LineEndingStyle::from_pdf_name(""), LineEndingStyle::None);
}
#[test]
fn test_line_ending_style_default() {
let default: LineEndingStyle = Default::default();
assert_eq!(default, LineEndingStyle::None);
}
#[test]
fn test_annotation_color_factory_methods() {
let red = AnnotationColor::red();
assert_eq!(red, AnnotationColor::Rgb(1.0, 0.0, 0.0));
let green = AnnotationColor::green();
assert_eq!(green, AnnotationColor::Rgb(0.0, 1.0, 0.0));
let blue = AnnotationColor::blue();
assert_eq!(blue, AnnotationColor::Rgb(0.0, 0.0, 1.0));
let black = AnnotationColor::black();
assert_eq!(black, AnnotationColor::Gray(0.0));
let white = AnnotationColor::white();
assert_eq!(white, AnnotationColor::Gray(1.0));
}
#[test]
fn test_annotation_color_to_array_none() {
let none = AnnotationColor::None;
assert!(none.to_array().is_none());
}
#[test]
fn test_annotation_color_to_array_gray() {
let gray = AnnotationColor::Gray(0.5);
assert_eq!(gray.to_array(), Some(vec![0.5]));
}
#[test]
fn test_annotation_color_to_array_cmyk() {
let cmyk = AnnotationColor::Cmyk(0.1, 0.2, 0.3, 0.4);
assert_eq!(cmyk.to_array(), Some(vec![0.1, 0.2, 0.3, 0.4]));
}
#[test]
fn test_annotation_color_from_array_all_sizes() {
assert_eq!(AnnotationColor::from_array(&[]), AnnotationColor::None);
assert_eq!(AnnotationColor::from_array(&[0.5]), AnnotationColor::Gray(0.5));
assert_eq!(
AnnotationColor::from_array(&[1.0, 0.0, 0.0]),
AnnotationColor::Rgb(1.0, 0.0, 0.0)
);
assert_eq!(
AnnotationColor::from_array(&[0.1, 0.2, 0.3, 0.4]),
AnnotationColor::Cmyk(0.1, 0.2, 0.3, 0.4)
);
assert_eq!(AnnotationColor::from_array(&[1.0, 2.0]), AnnotationColor::None);
assert_eq!(AnnotationColor::from_array(&[1.0, 2.0, 3.0, 4.0, 5.0]), AnnotationColor::None);
}
#[test]
fn test_annotation_color_default() {
let default: AnnotationColor = Default::default();
assert_eq!(default, AnnotationColor::None);
}
#[test]
fn test_text_annotation_icon_all_variants() {
let icons = [
(TextAnnotationIcon::Comment, "Comment"),
(TextAnnotationIcon::Key, "Key"),
(TextAnnotationIcon::Note, "Note"),
(TextAnnotationIcon::Help, "Help"),
(TextAnnotationIcon::NewParagraph, "NewParagraph"),
(TextAnnotationIcon::Paragraph, "Paragraph"),
(TextAnnotationIcon::Insert, "Insert"),
];
for (icon, name) in &icons {
assert_eq!(icon.pdf_name(), *name);
assert_eq!(TextAnnotationIcon::from_pdf_name(name), *icon);
}
}
#[test]
fn test_text_annotation_icon_unknown_defaults_to_note() {
assert_eq!(TextAnnotationIcon::from_pdf_name("Unknown"), TextAnnotationIcon::Note);
}
#[test]
fn test_text_annotation_icon_default() {
let default: TextAnnotationIcon = Default::default();
assert_eq!(default, TextAnnotationIcon::Note);
}
#[test]
fn test_text_markup_type_subtype() {
assert_eq!(TextMarkupType::Highlight.subtype(), AnnotationSubtype::Highlight);
assert_eq!(TextMarkupType::Underline.subtype(), AnnotationSubtype::Underline);
assert_eq!(TextMarkupType::Squiggly.subtype(), AnnotationSubtype::Squiggly);
assert_eq!(TextMarkupType::StrikeOut.subtype(), AnnotationSubtype::StrikeOut);
}
#[test]
fn test_text_markup_type_default_color() {
let h = TextMarkupType::Highlight.default_color();
assert_eq!(h, AnnotationColor::yellow());
let u = TextMarkupType::Underline.default_color();
assert_eq!(u, AnnotationColor::green());
let sq = TextMarkupType::Squiggly.default_color();
assert_eq!(sq, AnnotationColor::Rgb(1.0, 0.5, 0.0));
let so = TextMarkupType::StrikeOut.default_color();
assert_eq!(so, AnnotationColor::red());
}
#[test]
fn test_stamp_type_all_standard_variants() {
let stamps = [
(StampType::Approved, "Approved"),
(StampType::Experimental, "Experimental"),
(StampType::NotApproved, "NotApproved"),
(StampType::AsIs, "AsIs"),
(StampType::Expired, "Expired"),
(StampType::NotForPublicRelease, "NotForPublicRelease"),
(StampType::Confidential, "Confidential"),
(StampType::Final, "Final"),
(StampType::Sold, "Sold"),
(StampType::Departmental, "Departmental"),
(StampType::ForComment, "ForComment"),
(StampType::TopSecret, "TopSecret"),
(StampType::Draft, "Draft"),
(StampType::ForPublicRelease, "ForPublicRelease"),
];
for (stamp, name) in &stamps {
assert_eq!(stamp.pdf_name(), *name, "pdf_name mismatch for {:?}", stamp);
assert_eq!(
StampType::from_pdf_name(name),
*stamp,
"from_pdf_name mismatch for {}",
name
);
}
}
#[test]
fn test_stamp_type_custom_roundtrip() {
let custom = StampType::Custom("CompanyLogo".to_string());
assert_eq!(custom.pdf_name(), "CompanyLogo");
assert_eq!(
StampType::from_pdf_name("CompanyLogo"),
StampType::Custom("CompanyLogo".to_string())
);
}
#[test]
fn test_stamp_type_default() {
let default: StampType = Default::default();
assert_eq!(default, StampType::Draft);
}
#[test]
fn test_free_text_intent_all_variants() {
let intents = [
(FreeTextIntent::FreeText, "FreeText"),
(FreeTextIntent::FreeTextCallout, "FreeTextCallout"),
(FreeTextIntent::FreeTextTypeWriter, "FreeTextTypeWriter"),
];
for (intent, name) in &intents {
assert_eq!(intent.pdf_name(), *name);
assert_eq!(FreeTextIntent::from_pdf_name(name), *intent);
}
}
#[test]
fn test_free_text_intent_unknown_defaults_to_freetext() {
assert_eq!(FreeTextIntent::from_pdf_name("Something"), FreeTextIntent::FreeText);
}
#[test]
fn test_free_text_intent_default() {
let default: FreeTextIntent = Default::default();
assert_eq!(default, FreeTextIntent::FreeText);
}
#[test]
fn test_text_alignment_all_variants() {
assert_eq!(TextAlignment::Left.to_pdf_int(), 0);
assert_eq!(TextAlignment::Center.to_pdf_int(), 1);
assert_eq!(TextAlignment::Right.to_pdf_int(), 2);
assert_eq!(TextAlignment::from_pdf_int(0), TextAlignment::Left);
assert_eq!(TextAlignment::from_pdf_int(1), TextAlignment::Center);
assert_eq!(TextAlignment::from_pdf_int(2), TextAlignment::Right);
}
#[test]
fn test_text_alignment_unknown_defaults_to_left() {
assert_eq!(TextAlignment::from_pdf_int(-1), TextAlignment::Left);
assert_eq!(TextAlignment::from_pdf_int(3), TextAlignment::Left);
assert_eq!(TextAlignment::from_pdf_int(999), TextAlignment::Left);
}
#[test]
fn test_text_alignment_default() {
let default: TextAlignment = Default::default();
assert_eq!(default, TextAlignment::Left);
}
#[test]
fn test_caret_symbol_all_variants() {
assert_eq!(CaretSymbol::None.pdf_name(), "None");
assert_eq!(CaretSymbol::Paragraph.pdf_name(), "P");
assert_eq!(CaretSymbol::from_pdf_name("None"), CaretSymbol::None);
assert_eq!(CaretSymbol::from_pdf_name("P"), CaretSymbol::Paragraph);
}
#[test]
fn test_caret_symbol_unknown_defaults_to_none() {
assert_eq!(CaretSymbol::from_pdf_name("Q"), CaretSymbol::None);
assert_eq!(CaretSymbol::from_pdf_name(""), CaretSymbol::None);
}
#[test]
fn test_caret_symbol_default() {
let default: CaretSymbol = Default::default();
assert_eq!(default, CaretSymbol::None);
}
#[test]
fn test_file_attachment_icon_all_variants() {
let icons = [
(FileAttachmentIcon::GraphPushPin, "GraphPushPin"),
(FileAttachmentIcon::PaperclipTag, "PaperclipTag"),
(FileAttachmentIcon::PushPin, "PushPin"),
];
for (icon, name) in &icons {
assert_eq!(icon.pdf_name(), *name);
assert_eq!(FileAttachmentIcon::from_pdf_name(name), *icon);
}
}
#[test]
fn test_file_attachment_icon_unknown_defaults_to_paperclip() {
assert_eq!(FileAttachmentIcon::from_pdf_name("X"), FileAttachmentIcon::PaperclipTag);
}
#[test]
fn test_file_attachment_icon_default() {
let default: FileAttachmentIcon = Default::default();
assert_eq!(default, FileAttachmentIcon::PaperclipTag);
}
#[test]
fn test_reply_type_all_variants() {
assert_eq!(ReplyType::Reply.pdf_name(), "R");
assert_eq!(ReplyType::Group.pdf_name(), "Group");
assert_eq!(ReplyType::from_pdf_name("R"), ReplyType::Reply);
assert_eq!(ReplyType::from_pdf_name("Group"), ReplyType::Group);
}
#[test]
fn test_reply_type_unknown_defaults_to_reply() {
assert_eq!(ReplyType::from_pdf_name("X"), ReplyType::Reply);
assert_eq!(ReplyType::from_pdf_name(""), ReplyType::Reply);
}
#[test]
fn test_reply_type_default() {
let default: ReplyType = Default::default();
assert_eq!(default, ReplyType::Reply);
}
#[test]
fn test_highlight_mode_all_variants() {
let modes = [
(HighlightMode::None, "N"),
(HighlightMode::Invert, "I"),
(HighlightMode::Outline, "O"),
(HighlightMode::Push, "P"),
];
for (mode, name) in &modes {
assert_eq!(mode.pdf_name(), *name);
assert_eq!(HighlightMode::from_pdf_name(name), *mode);
}
}
#[test]
fn test_highlight_mode_unknown_defaults_to_invert() {
assert_eq!(HighlightMode::from_pdf_name("X"), HighlightMode::Invert);
assert_eq!(HighlightMode::from_pdf_name("I"), HighlightMode::Invert);
}
#[test]
fn test_highlight_mode_default() {
let default: HighlightMode = Default::default();
assert_eq!(default, HighlightMode::Invert);
}
#[test]
fn test_widget_field_type_default() {
let default: WidgetFieldType = Default::default();
assert_eq!(default, WidgetFieldType::Text);
}
#[test]
fn test_widget_field_type_checkbox() {
let checked = WidgetFieldType::Checkbox { checked: true };
let unchecked = WidgetFieldType::Checkbox { checked: false };
assert_ne!(checked, unchecked);
match checked {
WidgetFieldType::Checkbox { checked } => assert!(checked),
_ => panic!("Expected Checkbox"),
}
}
#[test]
fn test_widget_field_type_radio() {
let radio = WidgetFieldType::Radio {
selected: Some("Option1".to_string()),
};
match radio {
WidgetFieldType::Radio { selected } => {
assert_eq!(selected, Some("Option1".to_string()));
},
_ => panic!("Expected Radio"),
}
}
#[test]
fn test_widget_field_type_choice() {
let choice = WidgetFieldType::Choice {
options: vec!["A".to_string(), "B".to_string(), "C".to_string()],
selected: Some("B".to_string()),
};
match choice {
WidgetFieldType::Choice { options, selected } => {
assert_eq!(options.len(), 3);
assert_eq!(selected, Some("B".to_string()));
},
_ => panic!("Expected Choice"),
}
}
#[test]
fn test_widget_field_type_variants() {
let _ = WidgetFieldType::Text;
let _ = WidgetFieldType::Button;
let _ = WidgetFieldType::Signature;
let _ = WidgetFieldType::Unknown;
}
#[test]
fn test_quad_points_parse() {
let flat: Vec<f64> = vec![
0.0, 0.0, 100.0, 0.0, 100.0, 50.0, 0.0, 50.0, 200.0, 200.0, 300.0, 200.0, 300.0, 250.0,
200.0, 250.0,
];
let quads = quad_points::parse(&flat);
assert_eq!(quads.len(), 2);
assert_eq!(quads[0][0], 0.0);
assert_eq!(quads[1][0], 200.0);
}
#[test]
fn test_quad_points_parse_partial() {
let flat: Vec<f64> = vec![1.0, 2.0, 3.0];
let quads = quad_points::parse(&flat);
assert!(quads.is_empty());
}
#[test]
fn test_quad_points_parse_empty() {
let quads = quad_points::parse(&[]);
assert!(quads.is_empty());
}
#[test]
fn test_quad_points_flatten() {
let quads: Vec<[f64; 8]> = vec![
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
[9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0],
];
let flat = quad_points::flatten(&quads);
assert_eq!(flat.len(), 16);
assert_eq!(flat[0], 1.0);
assert_eq!(flat[8], 9.0);
assert_eq!(flat[15], 16.0);
}
#[test]
fn test_quad_points_flatten_empty() {
let quads: Vec<[f64; 8]> = vec![];
let flat = quad_points::flatten(&quads);
assert!(flat.is_empty());
}
#[test]
fn test_quad_points_roundtrip() {
let original: Vec<[f64; 8]> = vec![[10.0, 20.0, 110.0, 20.0, 110.0, 40.0, 10.0, 40.0]];
let flat = quad_points::flatten(&original);
let recovered = quad_points::parse(&flat);
assert_eq!(recovered, original);
}
#[test]
fn test_quad_points_bounding_rect_rotated() {
let quad: [f64; 8] = [50.0, 0.0, 100.0, 50.0, 50.0, 100.0, 0.0, 50.0];
let r = quad_points::bounding_rect(&quad);
assert_eq!(r.x, 0.0);
assert_eq!(r.y, 0.0);
assert_eq!(r.width, 100.0);
assert_eq!(r.height, 100.0);
}
#[test]
fn test_annotation_subtype_clone_copy() {
let subtype = AnnotationSubtype::Highlight;
let cloned = subtype;
assert_eq!(subtype, cloned); }
#[test]
fn test_annotation_subtype_debug() {
let debug = format!("{:?}", AnnotationSubtype::ThreeD);
assert!(debug.contains("ThreeD"));
}
#[test]
fn test_annotation_subtype_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(AnnotationSubtype::Text);
set.insert(AnnotationSubtype::Link);
set.insert(AnnotationSubtype::Text); assert_eq!(set.len(), 2);
}
}