extern crate alloc;
use crate::appearance::AppearanceDict;
use crate::types::*;
use pdf_syntax::object::dict::keys::*;
use pdf_syntax::object::{Array, Dict, Name, Rect, Stream};
use pdf_syntax::page::Page;
#[derive(Debug, Clone)]
pub struct Annotation<'a> {
dict: Dict<'a>,
}
impl<'a> Annotation<'a> {
pub fn from_page(page: &Page<'a>) -> Vec<Self> {
page.annots()
.into_iter()
.map(|dict| Self { dict })
.collect()
}
pub fn from_dict(dict: Dict<'a>) -> Self {
Self { dict }
}
pub fn dict(&self) -> &Dict<'a> {
&self.dict
}
pub fn annotation_type(&self) -> AnnotationType {
self.dict
.get::<Name>(SUBTYPE)
.map(|n| AnnotationType::from_name(n.as_ref()))
.unwrap_or(AnnotationType::Unknown)
}
pub fn rect(&self) -> Option<Rect> {
self.dict.get::<Rect>(RECT)
}
pub fn contents(&self) -> Option<alloc::string::String> {
self.dict
.get::<pdf_syntax::object::String>(CONTENTS)
.map(|s| pdf_string_to_string(&s))
}
pub fn flags(&self) -> AnnotationFlags {
AnnotationFlags(self.dict.get::<u32>(F).unwrap_or(0))
}
pub fn is_hidden(&self) -> bool {
self.flags().hidden()
}
pub fn is_printable(&self) -> bool {
self.flags().print()
}
pub fn name(&self) -> Option<alloc::string::String> {
self.dict
.get::<pdf_syntax::object::String>(NM)
.map(|s| pdf_string_to_string(&s))
}
pub fn modified(&self) -> Option<alloc::string::String> {
self.dict
.get::<pdf_syntax::object::String>(M)
.map(|s| pdf_string_to_string(&s))
}
pub fn author(&self) -> Option<alloc::string::String> {
self.dict
.get::<pdf_syntax::object::String>(T)
.map(|s| pdf_string_to_string(&s))
}
pub fn subject(&self) -> Option<alloc::string::String> {
self.dict
.get::<pdf_syntax::object::String>(SUBJ)
.map(|s| pdf_string_to_string(&s))
}
pub fn color(&self) -> Option<Color> {
self.dict
.get::<Array<'_>>(C)
.map(|arr| Color::from_array(&arr))
}
pub fn border_style(&self) -> Option<BorderStyle> {
self.dict
.get::<Dict<'_>>(BS)
.map(|d| BorderStyle::from_dict(&d))
}
pub fn border_array(&self) -> Option<[f32; 3]> {
let arr = self.dict.get::<Array<'_>>(BORDER)?;
let mut iter = arr.iter::<f32>();
let h_radius = iter.next()?;
let v_radius = iter.next()?;
let width = iter.next()?;
Some([h_radius, v_radius, width])
}
pub fn border_effect(&self) -> Option<BorderEffect> {
self.dict
.get::<Dict<'_>>(BE)
.map(|d| BorderEffect::from_dict(&d))
}
pub fn interior_color(&self) -> Option<Color> {
self.dict
.get::<Array<'_>>(IC)
.map(|arr| Color::from_array(&arr))
}
pub fn appearance(&self) -> Option<AppearanceDict<'a>> {
AppearanceDict::from_annot(&self.dict)
}
pub fn normal_appearance(&self) -> Option<Stream<'a>> {
self.appearance()?.normal(&self.dict)
}
pub fn creation_date(&self) -> Option<alloc::string::String> {
self.dict
.get::<pdf_syntax::object::String>(CREATION_DATE)
.map(|s| pdf_string_to_string(&s))
}
pub fn opacity(&self) -> Option<f32> {
self.dict.get::<f32>(CA)
}
pub fn irt(&self) -> Option<Annotation<'a>> {
self.dict
.get::<Dict<'_>>(IRT)
.map(|d| Annotation { dict: d })
}
pub fn reply_type(&self) -> Option<alloc::string::String> {
self.dict
.get::<Name>(RT)
.map(|n| alloc::string::String::from(n.as_str()))
}
pub fn state(&self) -> Option<alloc::string::String> {
self.dict
.get::<Name>(STATE)
.map(|n| alloc::string::String::from(n.as_str()))
}
pub fn state_model(&self) -> Option<alloc::string::String> {
self.dict
.get::<Name>(STATE_MODEL)
.map(|n| alloc::string::String::from(n.as_str()))
}
pub fn popup(&self) -> Option<Annotation<'a>> {
self.dict
.get::<Dict<'_>>(POPUP)
.map(|d| Annotation { dict: d })
}
pub fn quad_points(&self) -> Option<QuadPoints> {
self.dict
.get::<Array<'_>>(QUADPOINTS)
.map(|arr| QuadPoints::from_array(&arr))
}
}
pub fn pdf_string_to_string(s: &pdf_syntax::object::String) -> alloc::string::String {
let bytes = s.as_bytes();
if bytes.len() >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF {
let utf16: Vec<u16> = bytes[2..]
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
.collect();
alloc::string::String::from_utf16_lossy(&utf16)
} else if bytes.len() >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF {
alloc::string::String::from_utf8_lossy(&bytes[3..]).into_owned()
} else {
let mut s = alloc::string::String::with_capacity(bytes.len());
for &b in bytes {
s.push(pdfdoc_byte_to_char(b));
}
s
}
}
fn pdfdoc_byte_to_char(b: u8) -> char {
#[rustfmt::skip]
static HIGH: [char; 46] = [
'\u{2022}', '\u{2020}', '\u{2021}', '\u{2026}', '\u{2014}', '\u{2013}', '\u{0192}', '\u{2044}', '\u{2039}', '\u{203A}', '\u{2212}', '\u{2030}', '\u{201E}', '\u{201C}', '\u{201D}', '\u{2018}', '\u{2019}', '\u{201A}', '\u{2122}', '\u{FB01}', '\u{FB02}', '\u{0141}', '\u{0152}', '\u{0160}', '\u{0178}', '\u{017D}', '\u{0131}', '\u{0142}', '\u{0153}', '\u{0161}', '\u{017E}', '\u{FFFD}', '\u{20AC}', '\u{00A1}', '\u{00A2}', '\u{00A3}', '\u{00A4}', '\u{00A5}', '\u{00A6}', '\u{00A7}', '\u{00A8}', '\u{00A9}', '\u{00AA}', '\u{00AB}', '\u{00AC}', '\u{00AD}', ];
match b {
0x00..=0x7F => b as char,
0x80..=0xAD => HIGH[(b - 0x80) as usize],
0xAE..=0xFF => char::from(b), }
}