aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEmulator0002020-09-23 23:01:05 +0200
committerEmulator0002020-09-23 23:01:05 +0200
commitec35be1f2ded5e90bf4dd8bdb8aaa8d76bf84942 (patch)
treea12a9831e152946a15024b53bbd22b2039729cd7 /src
parent0990ddfbdb63a5683d6f94513842dae37166c838 (diff)
downloadpdf_form-ec35be1f2ded5e90bf4dd8bdb8aaa8d76bf84942.tar.bz2
Updated all libraries and a lot of fixes
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs538
1 files changed, 371 insertions, 167 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 3f1e1c4..670f3ac 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,16 +1,17 @@
-//! This crate is for filling out PDFs with forms programatically.
-extern crate lopdf;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate derive_error;
-use lopdf::{Document, ObjectId, Object, StringFormat};
-use std::path::Path;
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::{Document, Object, ObjectId, StringFormat};
+
bitflags! {
struct ButtonFlags: u32 {
const NO_TOGGLE_TO_OFF = 0x8000;
@@ -23,10 +24,10 @@ bitflags! {
bitflags! {
struct ChoiceFlags: u32 {
- const COBMO = 0x40000;
- const EDIT = 0x80000;
- const SORT = 0x100000;
- const MULTISELECT = 0x400000;
+ const COBMO = 0x20000;
+ const EDIT = 0x40000;
+ const SORT = 0x80000;
+ const MULTISELECT = 0x200000;
const DO_NOT_SPELLCHECK = 0x800000;
const COMMIT_ON_CHANGE = 0x8000000;
}
@@ -38,8 +39,8 @@ bitflags! {
/// analyze the PDF and identify the fields. Then you can get and set the content of the fields by
/// index.
pub struct Form {
- doc: Document,
- form_ids: Vec<ObjectId>
+ doc: Document,
+ form_ids: Vec<ObjectId>,
}
/// The possible types of fillable form fields in a PDF
@@ -50,7 +51,7 @@ pub enum FieldType {
CheckBox,
ListBox,
ComboBox,
- Text
+ Text,
}
/// The current state of a form field
@@ -58,32 +59,39 @@ pub enum FieldType {
pub enum FieldState {
/// Push buttons have no state
Button,
- /// `selected` is the sigular option from `options` that is selected
- Radio { selected: String, options: Vec<String> },
+ /// `selected` is the singular option from `options` that is selected
+ Radio {
+ selected: String,
+ options: Vec<String>,
+ },
/// The toggle state of the checkbox
CheckBox { is_checked: bool },
/// `selected` is the list of selected options from `options`
- ListBox { selected: Vec<String>, options: Vec<String>, multiselect: bool },
+ ListBox {
+ selected: Vec<String>,
+ options: Vec<String>,
+ multiselect: bool,
+ },
/// `selected` is the list of selected options from `options`
- ComboBox { selected: Vec<String>, options: Vec<String>, multiselect: bool },
+ ComboBox {
+ selected: Vec<String>,
+ options: Vec<String>,
+ editable: bool,
+ },
/// User Text Input
- Text { text: String }
+ Text { text: String },
}
#[derive(Debug, Error)]
/// Errors that may occur while loading a PDF
pub enum LoadError {
- /// An IO Error
- IoError(io::Error),
- /// A dictionary key that must be present in order to find forms was not present
- DictionaryKeyNotFound,
+ /// An Lopdf Error
+ LopdfError(lopdf::Error),
/// The reference `ObjectId` did not point to any values
#[error(non_std, no_from)]
NoSuchReference(ObjectId),
/// An element that was expected to be a reference was not a reference
NotAReference,
- /// A value that must be a certain type was not that type
- UnexpectedType
}
/// Errors That may occur while setting values in a form
@@ -94,7 +102,7 @@ pub enum ValueError {
/// One or more selected values are not valid choices
InvalidSelection,
/// Multiple values were selected when only one was allowed
- TooManySelected
+ TooManySelected,
}
trait PdfObjectDeref {
@@ -105,13 +113,12 @@ 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)
+ _ => Err(LoadError::NotAReference),
}
}
}
impl Form {
-
/// Takes a reader containing a PDF with a fillable form, analyzes the content, and attempts to
/// identify all of the fields the form has.
pub fn load_from<R: io::Read>(reader: R) -> Result<Self, LoadError> {
@@ -126,24 +133,18 @@ impl Form {
Self::load_doc(doc)
}
- fn load_doc(doc: Document) -> Result<Self, LoadError>{
+ fn load_doc(doc: Document) -> Result<Self, LoadError> {
let mut form_ids = Vec::new();
let mut queue = VecDeque::new();
// Block so borrow of doc ends before doc is moved into the result
{
// Get the form's top level fields
- let catalog = doc.trailer.get("Root")
- .ok_or(LoadError::DictionaryKeyNotFound)?
- .deref(&doc)?
- .as_dict().ok_or(LoadError::UnexpectedType)?;
- let acroform = catalog.get("AcroForm")
- .ok_or(LoadError::DictionaryKeyNotFound)?
- .deref(&doc)?
- .as_dict().ok_or(LoadError::UnexpectedType)?;
- let fields_list = acroform.get("Fields")
- .ok_or(LoadError::DictionaryKeyNotFound)?
- // .deref(&doc)?
- .as_array().ok_or(LoadError::UnexpectedType)?;
+ let catalog = doc.trailer.get(b"Root")?.deref(&doc)?.as_dict()?;
+ let acroform = catalog.get(b"AcroForm")?.deref(&doc)?.as_dict()?;
+ let fields_list = acroform
+ .get(b"Fields")?
+ // .deref(&doc)?
+ .as_array()?;
queue.append(&mut VecDeque::from(fields_list.clone()));
// Iterate over the fields
@@ -151,11 +152,12 @@ impl Form {
let obj = objref.deref(&doc)?;
if let &Object::Dictionary(ref dict) = obj {
// If the field has FT, it actually takes input. Save this
- if let Some(_) = dict.get("FT") {
+ if let Ok(_) = dict.get(b"FT") {
form_ids.push(objref.as_reference().unwrap());
}
+
// If this field has kids, they might have FT, so add them to the queue
- if let Some(&Object::Array(ref kids)) = dict.get("Kids") {
+ if let Ok(&Object::Array(ref kids)) = dict.get(b"Kids") {
queue.append(&mut VecDeque::from(kids.clone()));
}
}
@@ -166,7 +168,7 @@ impl Form {
/// Returns the number of fields the form has
pub fn len(&self) -> usize {
- self.form_ids.len()
+ self.form_ids.len()
}
/// Gets the type of field of the given index
@@ -175,12 +177,20 @@ impl Form {
/// This function will panic if the index is greater than the number of fields
pub fn get_type(&self, n: usize) -> FieldType {
// unwraps should be fine because load should have verified everything exists
- let field = self.doc.objects.get(&self.form_ids[n]).unwrap().as_dict().unwrap();
+ let field = self
+ .doc
+ .objects
+ .get(&self.form_ids[n])
+ .unwrap()
+ .as_dict()
+ .unwrap();
let obj_zero = Object::Integer(0);
- let type_str = field.get("FT").unwrap().as_name_str().unwrap();
+ let type_str = field.get(b"FT").unwrap().as_name_str().unwrap();
if type_str == "Btn" {
- let flags = ButtonFlags::from_bits_truncate(field.get("Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32);
- if flags.intersects(ButtonFlags::RADIO) {
+ let flags = ButtonFlags::from_bits_truncate(
+ field.get(b"Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32,
+ );
+ if flags.intersects(ButtonFlags::RADIO | ButtonFlags::NO_TOGGLE_TO_OFF) {
FieldType::Radio
} else if flags.intersects(ButtonFlags::PUSHBUTTON) {
FieldType::Button
@@ -188,7 +198,9 @@ impl Form {
FieldType::CheckBox
}
} else if type_str == "Ch" {
- let flags = ChoiceFlags::from_bits_truncate(field.get("Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32);
+ let flags = ChoiceFlags::from_bits_truncate(
+ field.get(b"Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32,
+ );
if flags.intersects(ChoiceFlags::COBMO) {
FieldType::ComboBox
} else {
@@ -199,12 +211,42 @@ impl Form {
}
}
+ /// Gets the name of field of the given index
+ ///
+ /// # Panics
+ /// This function will panic if the index is greater than the number of fields
+ pub fn get_name(&self, n: usize) -> Option<String> {
+ // unwraps should be fine because load should have verified everything exists
+ let field = self
+ .doc
+ .objects
+ .get(&self.form_ids[n])
+ .unwrap()
+ .as_dict()
+ .unwrap();
+
+ // The "T" key refers to the name of the field
+ match field.get(b"T") {
+ Ok(Object::String(data, _)) => String::from_utf8(data.clone()).ok(),
+ _ => None,
+ }
+ }
+
/// Gets the types of all of the fields in the form
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
+ }
+
+ /// Gets the names of all of the fields in the form
+ 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
}
@@ -213,150 +255,223 @@ impl Form {
/// # Panics
/// This function will panic if the index is greater than the number of fields
pub fn get_state(&self, n: usize) -> FieldState {
- let field = self.doc.objects.get(&self.form_ids[n]).unwrap().as_dict().unwrap();
+ 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("V") {
- Some(name) => name.as_name_str().unwrap().to_owned(),
- None => match field.get("AS") {
- Some(name) => name.as_name_str().unwrap().to_owned(),
- None => "".to_owned()
- }
+ selected: match field.get(b"V") {
+ Ok(name) => name.as_name_str().unwrap().to_owned(),
+ Err(_) => match field.get(b"AS") {
+ Ok(name) => name.as_name_str().unwrap().to_owned(),
+ Err(_) => "".to_owned(),
+ },
},
- options: self.get_possibilities(self.form_ids[n])
+ options: self.get_possibilities(self.form_ids[n]),
},
- FieldType::CheckBox=> FieldState::CheckBox { is_checked:
- match field.get("V") {
- Some(name) => if name.as_name_str().unwrap() == "Yes" { true } else { false },
- None => match field.get("AS") {
- Some(name) => if name.as_name_str().unwrap() == "Yes" { true } else { false },
- None => false
+ FieldType::CheckBox => FieldState::CheckBox {
+ is_checked: match field.get(b"V") {
+ Ok(name) => {
+ if name.as_name_str().unwrap() == "Yes" {
+ true
+ } else {
+ false
+ }
}
- }
+ Err(_) => match field.get(b"AS") {
+ Ok(name) => {
+ if name.as_name_str().unwrap() == "Yes" {
+ true
+ } else {
+ false
+ }
+ }
+ Err(_) => false,
+ },
+ },
},
FieldType::ListBox => FieldState::ListBox {
// V field in a list box can be either text for one option, an array for many
// options, or null
- selected: match field.get("V") {
- Some(selection) => match selection {
- &Object::String(ref s,StringFormat::Literal) => vec![str::from_utf8(&s).unwrap().to_owned()],
+ 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 {
+ if let &Object::String(ref s, StringFormat::Literal) = obj {
res.push(str::from_utf8(&s).unwrap().to_owned());
}
}
res
}
- _ => Vec::new()
+ _ => Vec::new(),
},
- None => Vec::new()
+ Err(_) => Vec::new(),
},
// The options is an array of either text elements or arrays where the second
// element is what we want
- options: match field.get("Opt") {
- Some(&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] {
+ 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()
- } else {
- String::new()
- },
- _ => String::new()
- }
- }).filter(|x| x.len() > 0).collect(),
- _ => Vec::new()
+ }
+ &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.len() > 0)
+ .collect(),
+ _ => Vec::new(),
},
multiselect: {
-
- let flags = ChoiceFlags::from_bits_truncate(field.get("Ff").unwrap().as_i64().unwrap() as u32);
+ let flags = ChoiceFlags::from_bits_truncate(
+ field
+ .get(b"Ff")
+ .unwrap_or(&Object::Integer(0))
+ .as_i64()
+ .unwrap() as u32,
+ );
flags.intersects(ChoiceFlags::MULTISELECT)
- }
+ },
},
FieldType::ComboBox => FieldState::ComboBox {
// V field in a list box can be either text for one option, an array for many
// options, or null
- selected: match field.get("V") {
- Some(selection) => match selection {
- &Object::String(ref s,StringFormat::Literal) => vec![str::from_utf8(&s).unwrap().to_owned()],
+ 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 {
+ if let &Object::String(ref s, StringFormat::Literal) = obj {
res.push(str::from_utf8(&s).unwrap().to_owned());
}
}
res
}
- _ => Vec::new()
+ _ => Vec::new(),
},
- None => Vec::new()
+ Err(_) => Vec::new(),
},
// The options is an array of either text elements or arrays where the second
// element is what we want
- options: match field.get("Opt") {
- Some(&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] {
+ 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()
- } else {
- String::new()
- },
- _ => String::new()
- }
- }).filter(|x| x.len() > 0).collect(),
- _ => Vec::new()
+ }
+ &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.len() > 0)
+ .collect(),
+ _ => Vec::new(),
+ },
+ editable: {
+ let flags = ChoiceFlags::from_bits_truncate(
+ field
+ .get(b"Ff")
+ .unwrap_or(&Object::Integer(0))
+ .as_i64()
+ .unwrap() as u32,
+ );
+ flags.intersects(ChoiceFlags::EDIT)
+ },
+ },
+ 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(),
},
- multiselect: {
-
- let flags = ChoiceFlags::from_bits_truncate(field.get("Ff").unwrap().as_i64().unwrap() as u32);
- flags.intersects(ChoiceFlags::MULTISELECT)
- }
},
- FieldType::Text => FieldState::Text{ text:
- match field.get("V") {
- Some(&Object::String(ref s,StringFormat::Literal)) =>
- str::from_utf8(&s.clone()).unwrap().to_owned(),
- _ => "".to_owned()
- }
-
- }
}
}
-
/// If the field at index `n` is a text field, fills in that field with the text `s`.
/// If it is not a text field, returns ValueError
///
/// # Panics
/// Will panic if n is larger than the number of fields
- pub fn set_text(&mut self, n: usize, s: String) -> Result<(),ValueError> {
+ pub fn set_text(&mut self, n: usize, s: String) -> Result<(), ValueError> {
match self.get_type(n) {
FieldType::Text => {
- let field = self.doc.objects.get_mut(&self.form_ids[n]).unwrap().as_dict_mut().unwrap();
- field.set("V",Object::String(s.into_bytes(),StringFormat::Literal));
- field.remove("AP");
+ let field = self
+ .doc
+ .objects
+ .get_mut(&self.form_ids[n])
+ .unwrap()
+ .as_dict_mut()
+ .unwrap();
+ field.set("V", Object::String(s.into_bytes(), StringFormat::Literal));
+ field.remove(b"AP");
Ok(())
- },
- _ => Err(ValueError::TypeMismatch)
-
+ }
+ _ => Err(ValueError::TypeMismatch),
}
}
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("Kids");
- if let Some(&Object::Array(ref kids)) = kids_obj {
- for kid in kids {
- if let Some(&Object::Name(ref s)) = kid.deref(&self.doc).unwrap().as_dict().unwrap().get("AS") {
- res.push(str::from_utf8(&s).unwrap().to_owned());
+ 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
}
@@ -366,17 +481,32 @@ impl Form {
///
/// # Panics
/// Will panic if n is larger than the number of fields
- pub fn set_check_box(&mut self, n: usize, is_checked: bool) -> Result<(),ValueError> {
+ pub fn set_check_box(&mut self, n: usize, is_checked: bool) -> Result<(), ValueError> {
match self.get_type(n) {
FieldType::CheckBox => {
- let state = Object::Name({if is_checked {"Yes"} else {"Off"}}.to_owned().into_bytes());
- let field = self.doc.objects.get_mut(&self.form_ids[n]).unwrap().as_dict_mut().unwrap();
- field.set("V",state.clone());
- field.set("AS",state);
+ let state = Object::Name(
+ {
+ if is_checked {
+ "Yes"
+ } else {
+ "Off"
+ }
+ }
+ .to_owned()
+ .into_bytes(),
+ );
+ let field = self
+ .doc
+ .objects
+ .get_mut(&self.form_ids[n])
+ .unwrap()
+ .as_dict_mut()
+ .unwrap();
+ field.set("V", state.clone());
+ field.set("AS", state);
Ok(())
- },
- _ => Err(ValueError::TypeMismatch)
-
+ }
+ _ => Err(ValueError::TypeMismatch),
}
}
@@ -386,53 +516,127 @@ impl Form {
///
/// # Panics
/// Will panic if n is larger than the number of fields
- pub fn set_radio(&mut self, n: usize, choice: String) -> Result<(),ValueError> {
+ pub fn set_radio(&mut self, n: usize, choice: String) -> Result<(), ValueError> {
match self.get_state(n) {
- FieldState::Radio { selected: _, 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)
-
+ FieldState::Radio {
+ selected: _,
+ 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),
}
}
- /// If the field at index `n` is a listbox or comboox field, selects the options in `choice`
- /// If it is not a listbox or combobox field or one of the choices is not a valid option, or if too many choices are selected, returns ValueError
+ /// If the field at index `n` is a listbox field, selects the options in `choice`
+ /// If it is not a listbox field or one of the choices is not a valid option, or if too many choices are selected, returns ValueError
///
/// # Panics
/// Will panic if n is larger than the number of fields
- pub fn set_choice(&mut self, n: usize, choices: Vec<String>) -> Result<(),ValueError> {
+ pub fn set_list_box(&mut self, n: usize, choices: Vec<String>) -> Result<(), ValueError> {
match self.get_state(n) {
- FieldState::ListBox { selected: _, options, multiselect } | FieldState::ComboBox { selected: _, options, multiselect } => if choices.iter().fold(true, |a,h| options.contains(h) && a) {
- if !multiselect && choices.len() > 1 {
- Err(ValueError::TooManySelected)
+ FieldState::ListBox {
+ selected: _,
+ 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 {
- 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(())
+ Err(ValueError::InvalidSelection)
}
-
- } else {
- Err(ValueError::InvalidSelection)
- },
- _ => Err(ValueError::TypeMismatch)
-
+ }
+ _ => Err(ValueError::TypeMismatch),
}
}
+ /// If the field at index `n` is a combobox field, selects the options in `choice`
+ /// If it is not a combobox field or one of the choices is not a valid option, or if too many choices are selected, returns ValueError
+ ///
+ /// # Panics
+ /// Will panic if n is larger than the number of fields
+ pub fn set_combo_box(&mut self, n: usize, choice: String) -> Result<(), ValueError> {
+ match self.get_state(n) {
+ FieldState::ComboBox {
+ selected: _,
+ 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.clone().into_bytes(), StringFormat::Literal),
+ );
+ Ok(())
+ } else {
+ Err(ValueError::InvalidSelection)
+ }
+ }
+ _ => Err(ValueError::TypeMismatch),
+ }
+ }
/// Saves the form to the specified path
- pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(),io::Error> {
+ pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<(), io::Error> {
self.doc.save(path).map(|_| ())
}
+
+ /// Saves the form to the specified path
+ pub fn save_to<W: Write>(&mut self, target: &mut W) -> Result<(), io::Error> {
+ self.doc.save_to(target)
+ }
}