#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate derive_error;
mod utils;
use std::collections::VecDeque;
use std::io;
use std::io::Write;
use std::path::Path;
use std::str;
use bitflags::_core::str::from_utf8;
use lopdf::content::{Content, Operation};
use lopdf::{Document, Object, ObjectId, StringFormat};
use crate::utils::*;
pub struct Form {
doc: Document,
form_ids: Vec<ObjectId>,
}
#[derive(Debug)]
pub enum FieldType {
Button,
Radio,
CheckBox,
ListBox,
ComboBox,
Text,
Unknown,
}
#[derive(Debug, Error)]
pub enum LoadError {
LopdfError(lopdf::Error),
#[error(non_std, no_from)]
NoSuchReference(ObjectId),
NotAReference,
}
#[derive(Debug, Error)]
pub enum ValueError {
TypeMismatch,
InvalidSelection,
TooManySelected,
Readonly,
}
#[derive(Debug)]
pub enum FieldState {
Button,
Radio {
selected: String,
options: Vec<String>,
readonly: bool,
required: bool,
},
CheckBox {
is_checked: bool,
readonly: bool,
required: bool,
},
ListBox {
selected: Vec<String>,
options: Vec<String>,
multiselect: bool,
readonly: bool,
required: bool,
},
ComboBox {
selected: Vec<String>,
options: Vec<String>,
editable: bool,
readonly: bool,
required: bool,
},
Text {
text: String,
readonly: bool,
required: bool,
},
Unknown,
}
trait PdfObjectDeref {
fn deref<'a>(&self, doc: &'a Document) -> Result<&'a Object, LoadError>;
}
impl PdfObjectDeref for Object {
fn deref<'a>(&self, doc: &'a Document) -> Result<&'a Object, LoadError> {
match *self {
Object::Reference(oid) => doc.objects.get(&oid).ok_or(LoadError::NoSuchReference(oid)),
_ => Err(LoadError::NotAReference),
}
}
}
impl Form {
pub fn load_from<R: io::Read>(reader: R) -> Result<Self, LoadError> {
let doc = Document::load_from(reader)?;
Self::load_doc(doc)
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, LoadError> {
let doc = Document::load(path)?;
Self::load_doc(doc)
}
fn load_doc(mut doc: Document) -> Result<Self, LoadError> {
let mut form_ids = Vec::new();
let mut queue = VecDeque::new();
{
doc.decompress();
let acroform = doc
.objects
.get_mut(
&doc.trailer
.get(b"Root")?
.deref(&doc)?
.as_dict()?
.get(b"AcroForm")?
.as_reference()?,
)
.ok_or(LoadError::NotAReference)?
.as_dict_mut()?;
let fields_list = acroform.get(b"Fields")?.as_array()?;
queue.append(&mut VecDeque::from(fields_list.clone()));
while let Some(objref) = queue.pop_front() {
let obj = objref.deref(&doc)?;
if let Object::Dictionary(ref dict) = *obj {
if dict.get(b"FT").is_ok() {
form_ids.push(objref.as_reference().unwrap());
}
if let Ok(&Object::Array(ref kids)) = dict.get(b"Kids") {
queue.append(&mut VecDeque::from(kids.clone()));
}
}
}
}
Ok(Form { doc, form_ids })
}
pub fn len(&self) -> usize {
self.form_ids.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get_type(&self, n: usize) -> FieldType {
let field = self
.doc
.objects
.get(&self.form_ids[n])
.unwrap()
.as_dict()
.unwrap();
let type_str = field.get(b"FT").unwrap().as_name_str().unwrap();
if type_str == "Btn" {
let flags = ButtonFlags::from_bits_truncate(get_field_flags(field));
if flags.intersects(ButtonFlags::RADIO | ButtonFlags::NO_TOGGLE_TO_OFF) {
FieldType::Radio
} else if flags.intersects(ButtonFlags::PUSHBUTTON) {
FieldType::Button
} else {
FieldType::CheckBox
}
} else if type_str == "Ch" {
let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field));
if flags.intersects(ChoiceFlags::COBMO) {
FieldType::ComboBox
} else {
FieldType::ListBox
}
} else if type_str == "Tx" {
FieldType::Text
} else {
FieldType::Unknown
}
}
pub fn get_name(&self, n: usize) -> Option<String> {
let field = self
.doc
.objects
.get(&self.form_ids[n])
.unwrap()
.as_dict()
.unwrap();
match field.get(b"T") {
Ok(Object::String(data, _)) => String::from_utf8(data.clone()).ok(),
_ => None,
}
}
pub fn get_all_types(&self) -> Vec<FieldType> {
let mut res = Vec::with_capacity(self.len());
for i in 0..self.len() {
res.push(self.get_type(i))
}
res
}
pub fn get_all_names(&self) -> Vec<Option<String>> {
let mut res = Vec::with_capacity(self.len());
for i in 0..self.len() {
res.push(self.get_name(i))
}
res
}
pub fn get_state(&self, n: usize) -> FieldState {
let field = self
.doc
.objects
.get(&self.form_ids[n])
.unwrap()
.as_dict()
.unwrap();
match self.get_type(n) {
FieldType::Button => FieldState::Button,
FieldType::Radio => FieldState::Radio {
selected: match field.get(b"V") {
Ok(name) => name.as_name_str().unwrap().to_owned(),
_ => match field.get(b"AS") {
Ok(name) => name.as_name_str().unwrap().to_owned(),
_ => "".to_owned(),
},
},
options: self.get_possibilities(self.form_ids[n]),
readonly: is_read_only(field),
required: is_required(field),
},
FieldType::CheckBox => FieldState::CheckBox {
is_checked: match field.get(b"V") {
Ok(name) => name.as_name_str().unwrap() == "Yes",
_ => match field.get(b"AS") {
Ok(name) => name.as_name_str().unwrap() == "Yes",
_ => false,
},
},
readonly: is_read_only(field),
required: is_required(field),
},
FieldType::ListBox => FieldState::ListBox {
selected: match field.get(b"V") {
Ok(selection) => match *selection {
Object::String(ref s, StringFormat::Literal) => {
vec![str::from_utf8(&s).unwrap().to_owned()]
}
Object::Array(ref chosen) => {
let mut res = Vec::new();
for obj in chosen {
if let Object::String(ref s, StringFormat::Literal) = *obj {
res.push(str::from_utf8(&s).unwrap().to_owned());
}
}
res
}
_ => Vec::new(),
},
_ => Vec::new(),
},
options: match field.get(b"Opt") {
Ok(&Object::Array(ref options)) => options
.iter()
.map(|x| match *x {
Object::String(ref s, StringFormat::Literal) => {
str::from_utf8(&s).unwrap().to_owned()
}
Object::Array(ref arr) => {
if let Object::String(ref s, StringFormat::Literal) = &arr[1] {
str::from_utf8(&s).unwrap().to_owned()
} else {
String::new()
}
}
_ => String::new(),
})
.filter(|x| !x.is_empty())
.collect(),
_ => Vec::new(),
},
multiselect: {
let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field));
flags.intersects(ChoiceFlags::MULTISELECT)
},
readonly: is_read_only(field),
required: is_required(field),
},
FieldType::ComboBox => FieldState::ComboBox {
selected: match field.get(b"V") {
Ok(selection) => match *selection {
Object::String(ref s, StringFormat::Literal) => {
vec![str::from_utf8(&s).unwrap().to_owned()]
}
Object::Array(ref chosen) => {
let mut res = Vec::new();
for obj in chosen {
if let Object::String(ref s, StringFormat::Literal) = *obj {
res.push(str::from_utf8(&s).unwrap().to_owned());
}
}
res
}
_ => Vec::new(),
},
_ => Vec::new(),
},
options: match field.get(b"Opt") {
Ok(&Object::Array(ref options)) => options
.iter()
.map(|x| match *x {
Object::String(ref s, StringFormat::Literal) => {
str::from_utf8(&s).unwrap().to_owned()
}
Object::Array(ref arr) => {
if let Object::String(ref s, StringFormat::Literal) = &arr[1] {
str::from_utf8(&s).unwrap().to_owned()
} else {
String::new()
}
}
_ => String::new(),
})
.filter(|x| !x.is_empty())
.collect(),
_ => Vec::new(),
},
editable: {
let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field));
flags.intersects(ChoiceFlags::EDIT)
},
readonly: is_read_only(field),
required: is_required(field),
},
FieldType::Text => FieldState::Text {
text: match field.get(b"V") {
Ok(&Object::String(ref s, StringFormat::Literal)) => {
str::from_utf8(&s.clone()).unwrap().to_owned()
}
_ => "".to_owned(),
},
readonly: is_read_only(field),
required: is_required(field),
},
FieldType::Unknown => FieldState::Unknown,
}
}
pub fn set_text(&mut self, n: usize, s: String) -> Result<(), ValueError> {
match self.get_state(n) {
FieldState::Text { .. } => {
let field = self
.doc
.objects
.get_mut(&self.form_ids[n])
.unwrap()
.as_dict_mut()
.unwrap();
field.set("V", Object::string_literal(s.into_bytes()));
let _ = self.regenerate_text_appearance(n);
Ok(())
}
_ => Err(ValueError::TypeMismatch),
}
}
fn regenerate_text_appearance(&mut self, n: usize) -> Result<(), lopdf::Error> {
let field = {
self.doc
.objects
.get(&self.form_ids[n])
.unwrap()
.as_dict()
.unwrap()
};
let value = field.get(b"V")?.to_owned();
let da = field.get(b"DA")?.to_owned();
let rect = field
.get(b"Rect")?
.as_array()?
.iter()
.map(|object| {
object
.as_f64()
.unwrap_or(object.as_i64().unwrap_or(0) as f64) as f32
})
.collect::<Vec<_>>();
let object_id = field.get(b"AP")?.as_dict()?.get(b"N")?.as_reference()?;
let stream = self.doc.get_object_mut(object_id)?.as_stream_mut()?;
let mut content = {
if let Ok(content) = stream.decompressed_content() {
Content::decode(&content)?
} else {
Content::decode(&stream.content)?
}
};
let ignored_operators = vec![
"bt", "tc", "tw", "tz", "g", "tm", "tr", "tf", "tj", "et", "q", "bmc", "emc",
];
content.operations.retain(|operation| {
!ignored_operators.contains(&operation.operator.to_lowercase().as_str())
});
content.operations.append(&mut vec![
Operation::new("BMC", vec!["Tx".into()]),
Operation::new("q", vec![]),
Operation::new("BT", vec![]),
]);
let font = parse_font(match da {
Object::String(ref bytes, _) => Some(from_utf8(bytes)?),
_ => None,
});
let font_name = (font.0).0;
let font_size = (font.0).1;
let font_color = font.1;
content.operations.append(&mut vec![
Operation::new("Tf", vec![font_name.into(), font_size.into()]),
Operation::new(
font_color.0,
match font_color.0 {
"k" => vec![
font_color.1.into(),
font_color.2.into(),
font_color.3.into(),
font_color.4.into(),
],
"rg" => vec![
font_color.1.into(),
font_color.2.into(),
font_color.3.into(),
],
_ => vec![font_color.1.into()],
},
),
]);
let x = 2.0;
let dy = rect[1] - rect[3];
let y = if dy > 0.0 {
0.5 * dy - 0.4 * font_size as f32
} else {
0.5 * font_size as f32
};
content.operations.append(&mut vec![Operation::new(
"Tm",
vec![1.into(), 0.into(), 0.into(), 1.into(), x.into(), y.into()],
)]);
content.operations.append(&mut vec![
Operation::new("Tj", vec![value]),
Operation::new("ET", vec![]),
Operation::new("Q", vec![]),
Operation::new("EMC", vec![]),
]);
if let Ok(encoded_content) = content.encode() {
stream.set_plain_content(encoded_content);
let _ = stream.compress();
}
Ok(())
}
pub fn set_check_box(&mut self, n: usize, is_checked: bool) -> Result<(), ValueError> {
match self.get_state(n) {
FieldState::CheckBox { .. } => {
let field = self
.doc
.objects
.get_mut(&self.form_ids[n])
.unwrap()
.as_dict_mut()
.unwrap();
let on = get_on_value(field);
let state = Object::Name(
if is_checked { on.as_str() } else { "Off" }
.to_owned()
.into_bytes(),
);
field.set("V", state.clone());
field.set("AS", state);
Ok(())
}
_ => Err(ValueError::TypeMismatch),
}
}
pub fn set_radio(&mut self, n: usize, choice: String) -> Result<(), ValueError> {
match self.get_state(n) {
FieldState::Radio { options, .. } => {
if options.contains(&choice) {
let field = self
.doc
.objects
.get_mut(&self.form_ids[n])
.unwrap()
.as_dict_mut()
.unwrap();
field.set("V", Object::Name(choice.into_bytes()));
Ok(())
} else {
Err(ValueError::InvalidSelection)
}
}
_ => Err(ValueError::TypeMismatch),
}
}
pub fn set_list_box(&mut self, n: usize, choices: Vec<String>) -> Result<(), ValueError> {
match self.get_state(n) {
FieldState::ListBox {
options,
multiselect,
..
} => {
if choices.iter().fold(true, |a, h| options.contains(h) && a) {
if !multiselect && choices.len() > 1 {
Err(ValueError::TooManySelected)
} else {
let field = self
.doc
.objects
.get_mut(&self.form_ids[n])
.unwrap()
.as_dict_mut()
.unwrap();
match choices.len() {
0 => field.set("V", Object::Null),
1 => field.set(
"V",
Object::String(
choices[0].clone().into_bytes(),
StringFormat::Literal,
),
),
_ => field.set(
"V",
Object::Array(
choices
.iter()
.map(|x| {
Object::String(
x.clone().into_bytes(),
StringFormat::Literal,
)
})
.collect(),
),
),
};
Ok(())
}
} else {
Err(ValueError::InvalidSelection)
}
}
_ => Err(ValueError::TypeMismatch),
}
}
pub fn set_combo_box(&mut self, n: usize, choice: String) -> Result<(), ValueError> {
match self.get_state(n) {
FieldState::ComboBox {
options, editable, ..
} => {
if options.contains(&choice) || editable {
let field = self
.doc
.objects
.get_mut(&self.form_ids[n])
.unwrap()
.as_dict_mut()
.unwrap();
field.set(
"V",
Object::String(choice.into_bytes(), StringFormat::Literal),
);
Ok(())
} else {
Err(ValueError::InvalidSelection)
}
}
_ => Err(ValueError::TypeMismatch),
}
}
pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(), io::Error> {
self.doc.save(path).map(|_| ())
}
pub fn save_to<W: Write>(&mut self, target: &mut W) -> Result<(), io::Error> {
self.doc.save_to(target)
}
fn get_possibilities(&self, oid: ObjectId) -> Vec<String> {
let mut res = Vec::new();
let kids_obj = self
.doc
.objects
.get(&oid)
.unwrap()
.as_dict()
.unwrap()
.get(b"Kids");
if let Ok(&Object::Array(ref kids)) = kids_obj {
for (i, kid) in kids.iter().enumerate() {
let mut found = false;
if let Ok(&Object::Dictionary(ref appearance_states)) =
kid.deref(&self.doc).unwrap().as_dict().unwrap().get(b"AP")
{
if let Ok(&Object::Dictionary(ref normal_appearance)) =
appearance_states.get(b"N")
{
for (key, _) in normal_appearance {
if key != b"Off" {
res.push(from_utf8(key).unwrap_or("").to_owned());
found = true;
break;
}
}
}
}
if !found {
res.push(i.to_string());
}
}
}
res
}
}