diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 225 | ||||
| -rw-r--r-- | src/utils.rs | 49 | 
2 files changed, 158 insertions, 116 deletions
| @@ -3,6 +3,8 @@ extern crate bitflags;  #[macro_use]  extern crate derive_error; +mod utils; +  use std::collections::VecDeque;  use std::io;  use std::io::Write; @@ -10,28 +12,10 @@ 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; -        const RADIO             = 0x10000; -        const PUSHBUTTON        = 0x20000; -        const RADIO_IN_UNISON   = 0x4000000; -    } -} +use lopdf::{Document, Object, ObjectId, StringFormat}; -bitflags! { -    struct ChoiceFlags: u32 { -        const COBMO             = 0x20000; -        const EDIT              = 0x40000; -        const SORT              = 0x80000; -        const MULTISELECT       = 0x200000; -        const DO_NOT_SPELLCHECK = 0x800000; -        const COMMIT_ON_CHANGE  = 0x8000000; -    } -} +use crate::utils::*;  /// A PDF Form that contains fillable fields  /// @@ -54,6 +38,30 @@ pub enum FieldType {      Text,  } +#[derive(Debug, Error)] +/// Errors that may occur while loading a PDF +pub enum LoadError { +    /// 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, +} + +/// Errors That may occur while setting values in a form +#[derive(Debug, Error)] +pub enum ValueError { +    /// The method used to set the state is incompatible with the type of the field +    TypeMismatch, +    /// One or more selected values are not valid choices +    InvalidSelection, +    /// Multiple values were selected when only one was allowed +    TooManySelected, +    /// Readonly field cannot be edited +    Readonly, +}  /// The current state of a form field  #[derive(Debug)]  pub enum FieldState { @@ -63,46 +71,37 @@ pub enum FieldState {      Radio {          selected: String,          options: Vec<String>, +        readonly: bool, +        required: bool,      },      /// The toggle state of the checkbox -    CheckBox { is_checked: bool }, +    CheckBox { +        is_checked: bool, +        readonly: bool, +        required: bool, +    },      /// `selected` is the list of selected options from `options`      ListBox {          selected: Vec<String>,          options: Vec<String>,          multiselect: bool, +        readonly: bool, +        required: bool,      },      /// `selected` is the list of selected options from `options`      ComboBox {          selected: Vec<String>,          options: Vec<String>,          editable: bool, +        readonly: bool, +        required: bool,      },      /// User Text Input -    Text { text: String }, -} - -#[derive(Debug, Error)] -/// Errors that may occur while loading a PDF -pub enum LoadError { -    /// 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, -} - -/// Errors That may occur while setting values in a form -#[derive(Debug, Error)] -pub enum ValueError { -    /// The method used to set the state is incompatible with the type of the field -    TypeMismatch, -    /// One or more selected values are not valid choices -    InvalidSelection, -    /// Multiple values were selected when only one was allowed -    TooManySelected, +    Text { +        text: String, +        readonly: bool, +        required: bool, +    },  }  trait PdfObjectDeref { @@ -189,12 +188,10 @@ impl Form {              .unwrap()              .as_dict()              .unwrap(); -        let obj_zero = Object::Integer(0); +          let type_str = field.get(b"FT").unwrap().as_name_str().unwrap();          if type_str == "Btn" { -            let flags = ButtonFlags::from_bits_truncate( -                field.get(b"Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32, -            ); +            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) { @@ -203,9 +200,7 @@ impl Form {                  FieldType::CheckBox              }          } else if type_str == "Ch" { -            let flags = ChoiceFlags::from_bits_truncate( -                field.get(b"Ff").unwrap_or(&obj_zero).as_i64().unwrap() as u32, -            ); +            let flags = ChoiceFlags::from_bits_truncate(get_field_flags(field));              if flags.intersects(ChoiceFlags::COBMO) {                  FieldType::ComboBox              } else { @@ -278,6 +273,8 @@ impl Form {                      },                  },                  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") { @@ -287,6 +284,8 @@ impl Form {                          _ => false,                      },                  }, +                readonly: is_read_only(field), +                required: is_required(field),              },              FieldType::ListBox => FieldState::ListBox {                  // V field in a list box can be either text for one option, an array for many @@ -332,15 +331,11 @@ impl Form {                      _ => Vec::new(),                  },                  multiselect: { -                    let flags = ChoiceFlags::from_bits_truncate( -                        field -                            .get(b"Ff") -                            .unwrap_or(&Object::Integer(0)) -                            .as_i64() -                            .unwrap() as u32, -                    ); +                    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 {                  // V field in a list box can be either text for one option, an array for many @@ -386,15 +381,12 @@ impl Form {                      _ => Vec::new(),                  },                  editable: { -                    let flags = ChoiceFlags::from_bits_truncate( -                        field -                            .get(b"Ff") -                            .unwrap_or(&Object::Integer(0)) -                            .as_i64() -                            .unwrap() as u32, -                    ); +                    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") { @@ -403,6 +395,8 @@ impl Form {                      }                      _ => "".to_owned(),                  }, +                readonly: is_read_only(field), +                required: is_required(field),              },          }      } @@ -413,8 +407,8 @@ impl Form {      /// # Panics      /// Will panic if n is larger than the number of fields      pub fn set_text(&mut self, n: usize, s: String) -> Result<(), ValueError> { -        match self.get_type(n) { -            FieldType::Text => { +        match self.get_state(n) { +            FieldState::Text { .. } => {                  let field = self                      .doc                      .objects @@ -422,52 +416,16 @@ impl Form {                      .unwrap()                      .as_dict_mut()                      .unwrap(); +                  field.set("V", Object::String(s.into_bytes(), StringFormat::Literal));                  field.remove(b"AP"); +                  Ok(())              }              _ => 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(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 -    } -      /// If the field at index `n` is a checkbox field, toggles the check box based on the value      /// `is_checked`.      /// If it is not a checkbox field, returns ValueError @@ -475,8 +433,8 @@ 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> { -        match self.get_type(n) { -            FieldType::CheckBox => { +        match self.get_state(n) { +            FieldState::CheckBox { .. } => {                  let state = Object::Name(                      {                          if is_checked { @@ -495,8 +453,10 @@ impl Form {                      .unwrap()                      .as_dict_mut()                      .unwrap(); +                  field.set("V", state.clone());                  field.set("AS", state); +                  Ok(())              }              _ => Err(ValueError::TypeMismatch), @@ -511,10 +471,7 @@ impl Form {      /// Will panic if n is larger than the number of fields      pub fn set_radio(&mut self, n: usize, choice: String) -> Result<(), ValueError> {          match self.get_state(n) { -            FieldState::Radio { -                selected: _, -                options, -            } => { +            FieldState::Radio { options, .. } => {                  if options.contains(&choice) {                      let field = self                          .doc @@ -541,9 +498,9 @@ impl Form {      pub fn set_list_box(&mut self, n: usize, choices: Vec<String>) -> Result<(), ValueError> {          match self.get_state(n) {              FieldState::ListBox { -                selected: _,                  options,                  multiselect, +                ..              } => {                  if choices.iter().fold(true, |a, h| options.contains(h) && a) {                      if !multiselect && choices.len() > 1 { @@ -598,9 +555,7 @@ impl Form {      pub fn set_combo_box(&mut self, n: usize, choice: String) -> Result<(), ValueError> {          match self.get_state(n) {              FieldState::ComboBox { -                selected: _, -                options, -                editable, +                options, editable, ..              } => {                  if options.contains(&choice) || editable {                      let field = self @@ -632,4 +587,42 @@ impl Form {      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 +    }  } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..7ff4e41 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,49 @@ +use lopdf::{Dictionary, Object}; + +bitflags! { +    pub struct FieldFlags: u32 { +        const READONLY          = 0x1; +        const REQUIRED          = 0x2; +    } +} + +bitflags! { +    pub struct ButtonFlags: u32 { +        const NO_TOGGLE_TO_OFF  = 0x8000; +        const RADIO             = 0x10000; +        const PUSHBUTTON        = 0x20000; +        const RADIO_IN_UNISON   = 0x4000000; + +    } +} + +bitflags! { +    pub struct ChoiceFlags: u32 { +        const COBMO             = 0x20000; +        const EDIT              = 0x40000; +        const SORT              = 0x80000; +        const MULTISELECT       = 0x200000; +        const DO_NOT_SPELLCHECK = 0x800000; +        const COMMIT_ON_CHANGE  = 0x8000000; +    } +} + +pub fn is_read_only(field: &Dictionary) -> bool { +    let flags = FieldFlags::from_bits_truncate(get_field_flags(field)); + +    flags.intersects(FieldFlags::READONLY) +} + +pub fn is_required(field: &Dictionary) -> bool { +    let flags = FieldFlags::from_bits_truncate(get_field_flags(field)); + +    flags.intersects(FieldFlags::REQUIRED) +} + +pub fn get_field_flags(field: &Dictionary) -> u32 { +    field +        .get(b"Ff") +        .unwrap_or(&Object::Integer(0)) +        .as_i64() +        .unwrap() as u32 +} | 
