Skip to content

Commit

Permalink
Form Field types (PDF 1.7 Section 12.7.4) (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
tingerrr authored Oct 22, 2023
1 parent 7a03bb8 commit bb19bfd
Showing 1 changed file with 338 additions and 0 deletions.
338 changes: 338 additions & 0 deletions src/forms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub struct Field<'a> {

writer!(Field: |obj| Self { dict: obj.dict() });

/// Permissible on all fields.
impl<'a> Field<'a> {
/// Write the `/FT` attribute to set the type of this field.
pub fn field_type(&mut self, typ: FieldType) -> &mut Self {
Expand Down Expand Up @@ -104,6 +105,267 @@ impl FieldType {
}
}

/// Only permissible on button fields.
impl<'a> Field<'a> {
/// Start writing the `/Opt` array to set the export values of children of
/// this field. Only permissible on checkbox fields, or radio button fields.
/// PDF 1.4+.
pub fn button_options(&mut self) -> TypedArray<'_, TextStr> {
self.dict.insert(Name(b"Opt")).array().typed()
}
}

/// Only permissible on check box fields.
impl<'a> Field<'a> {
/// Write the `/V` attribute to set the state of this check box field.
/// The state corresponds to an appearance stream in the
/// [appearance dictionary](Appearance) of this field's widget
/// [annotation](Annotation). Only permissible on check box fields.
pub fn checkbox_value(&mut self, state: CheckBoxState) -> &mut Self {
self.dict.pair(Name(b"V"), state.to_name());
self
}

/// Write the `/DV` attribute to set the default state of this check box
/// field. The state corresponds to an appearance stream in the
/// [appearance dictionary](Appearance) of this field's widget
/// [annotation](Annotation). Only permissible on check box fields.
pub fn checkbox_default_value(&mut self, state: CheckBoxState) -> &mut Self {
self.dict.pair(Name(b"DV"), state.to_name());
self
}
}

/// The state of a check box [`Field`].
pub enum CheckBoxState {
/// The check box selected state `/Yes`.
Yes,
/// The check box unselected state `/Off`.
Off,
}

impl CheckBoxState {
pub(crate) fn to_name(self) -> Name<'static> {
match self {
Self::Yes => Name(b"Yes"),
Self::Off => Name(b"Off"),
}
}
}

/// Only permissible on radio button fields.
impl<'a> Field<'a> {
/// Write the `/V` attribute to set the state of this check box field.
/// The state corresponds to an appearance stream in the
/// [appearance dictionary](Appearance) of this field's widget
/// [annotation](Annotation). Only permissible on radio button fields.
pub fn radio_value(&mut self, state: RadioState) -> &mut Self {
self.dict.pair(Name(b"V"), state.to_name());
self
}

/// Write the `/DV` attribute to set the default state of this check box
/// field. The state corresponds to an appearance stream in the
/// [appearance dictionary](Appearance) of this field's widget
/// [annotation](Annotation). Only permissible on radio button fields.
pub fn radio_default_value(&mut self, state: RadioState) -> &mut Self {
self.dict.pair(Name(b"DV"), state.to_name());
self
}
}

/// The state of a radio button [`Field`].
pub enum RadioState<'a> {
/// The radio button with the given name is selected.
Selected(Name<'a>),
/// No radio button is selected `/Off`.
Off,
}

impl<'a> RadioState<'a> {
pub(crate) fn to_name(self) -> Name<'a> {
match self {
Self::Selected(name) => name,
Self::Off => Name(b"Off"),
}
}
}

/// Only permissible on text fields.
impl<'a> Field<'a> {
// TODO: the spec likely means the equivalent of unicode graphemes here
// for characters

/// Write the `/MaxLen` attribute to set the maximum length of the field's
/// text in characters. Only permissible on text fields.
pub fn text_max_len(&mut self, len: i32) -> &mut Self {
self.dict.pair(Name(b"MaxLen"), len);
self
}

/// Write the `/V` attribute to set the value of this text field.
/// Only permissible on text fields.
pub fn text_value(&mut self, value: TextStr) -> &mut Self {
self.dict.pair(Name(b"V"), value);
self
}

/// Start writing the `/DV` attribute to set the default value of this text
/// field. Only permissible on text fields.
pub fn text_default_value(&mut self, value: TextStr) -> &mut Self {
self.dict.pair(Name(b"DV"), value);
self
}
}

/// Only permissible on fields containing variable text.
impl<'a> Field<'a> {
/// Write the `/DA` attribute containing a sequence of valid page-content
/// graphics or text state operators that define such properties as the
/// field's text size and colour. Only permissible on fields containing
/// variable text.
pub fn vartext_default_appearance(&mut self, appearance: Str) -> &mut Self {
self.dict.pair(Name(b"DA"), appearance);
self
}

/// Write the `/Q` attribute to set the quadding (justification) that shall
/// be used in dispalying the text. Only permissible on fields containing
/// variable text.
pub fn vartext_quadding(&mut self, quadding: Quadding) -> &mut Self {
self.dict.pair(Name(b"Q"), quadding as i32);
self
}

/// Write the `/DS` attribute to set the default style string. Only
/// permissible on fields containing variable text. PDF 1.5+.
pub fn vartext_default_style(&mut self, style: TextStr) -> &mut Self {
self.dict.pair(Name(b"DS"), style);
self
}

/// Write the `/RV` attribute to set the value of this variable text field.
/// Only permissible on fields containing variable text. PDF 1.5+.
pub fn vartext_rich_value(&mut self, value: TextStr) -> &mut Self {
self.dict.pair(Name(b"RV"), value);
self
}
}

/// The quadding (justification) of a field containing variable text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Quadding {
/// Left justify the text.
Left = 0,
/// Center justify the text.
Center = 1,
/// Right justify the text.
Right = 2,
}

/// Only permissible on choice fields.
impl<'a> Field<'a> {
/// Start writing the `/Opt` array to set the options that shall be
/// presented to the user.
pub fn choice_options(&mut self) -> ChoiceOptions<'_> {
self.dict.insert(Name(b"Opt")).start()
}

/// Write the `/TI` attribute to set the index in the
/// [`Field::choice_options`] array of the first visible option for
/// scrollable lists.
pub fn choice_top_index(&mut self, index: i32) -> &mut Self {
self.dict.pair(Name(b"TI"), index);
self
}

/// Start writing the `/I` array to set the indices of the currently
/// selected options. The integers in this array must be sorted in ascending
/// order and correspond to 0-based indices in the [`Field::choice_options`]
/// array.
///
/// This entry shall be used for choice fields which allow multiple
/// selections ([`FieldFlags::MULTI_SELECT`]). This means when two or more
/// elements in the [`Field::choice_options`] array have different names
/// but export the same value or when the value fo the choice field is an
/// array. This entry should not be used for choice fields that do not allow
/// multiple selections. PDF 1.4+.
pub fn choice_indices(&mut self) -> TypedArray<'_, i32> {
self.dict.insert(Name(b"I")).array().typed()
}

/// Write the `/V` attribute to set the currently selected values
/// of this choice field. Should be one of the values given in
/// [`Self::choice_options`] or `None` if no choice is selected. Only
/// permissible on choice fields.
pub fn choice_value<'b>(&mut self, option: Option<TextStr>) -> &mut Self {
match option {
Some(value) => self.dict.pair(Name(b"V"), value),
None => self.dict.pair(Name(b"V"), Null),
};
self
}

/// Write the `/V` attribute to set the currently selected values of this
/// choice field. See also [`Self::choice_value`], for a single or no value.
/// Only permissible on choice fields.
pub fn choice_values<'b>(
&mut self,
options: impl IntoIterator<Item = TextStr<'b>>,
) -> &mut Self {
self.dict.insert(Name(b"V")).array().items(options);
self
}

/// Write the `/DV` attribute to set the default selected value
/// of this choice field. Should be one of the values given in
/// [`Self::choice_options`] or `None` if no choice is selected. Only
/// permissible on choice fields.
pub fn choice_default_value(&mut self, option: Option<TextStr>) -> &mut Self {
match option {
Some(value) => self.dict.pair(Name(b"DV"), value),
None => self.dict.pair(Name(b"DV"), Null),
};
self
}

/// Write the `/DV` attribute to set the default selected values of this
/// choice field. See also [`Self::choice_default_value`], for a single or
/// no value. Only permissible on choice fields.
pub fn choice_default_values<'b>(
&mut self,
options: impl IntoIterator<Item = TextStr<'b>>,
) -> &mut Self {
self.dict.insert(Name(b"DV")).array().items(options);
self
}
}

/// Writer for a _choice options array_.
///
/// This struct is created by [`Field::choice_options`].
pub struct ChoiceOptions<'a> {
array: Array<'a>,
}

writer!(ChoiceOptions: |obj| Self { array: obj.array() });

impl<'a> ChoiceOptions<'a> {
/// Add an option with the given value.
pub fn option(&mut self, value: TextStr) -> &mut Self {
self.array.item(value);
self
}

/// Add an option with the given value and export value.
pub fn export(&mut self, value: TextStr, export_value: TextStr) -> &mut Self {
self.array.push().array().items([export_value, value]);
self
}
}

deref!('a, ChoiceOptions<'a> => Array<'a>, array);

bitflags::bitflags! {
/// Bitflags describing various characteristics of a form field.
pub struct FieldFlags: u32 {
Expand All @@ -119,5 +381,81 @@ bitflags::bitflags! {
/// The field shall not be exported by a
/// [submit-form](crate::types::ActionType::SubmitForm)[`Action`].
const NO_EXPORT = 1 << 3;
/// The entered text shall not be spell-checked, can be used for text
/// and choice fields.
const DO_NOT_SPELL_CHECK = 1 << 23;

// Button specific flags

/// Exactly one radio button shall be selected at all times; selecting
/// the currently selected button has no effect. If unset, clicking
/// the selected button deselects it, leaving no button selected. Only
/// permissible for radio buttons.
const NO_TOGGLE_TO_OFF = 1 << 15;
/// The field is a set of radio buttons; if clear, the field is a check
/// box. This flag may be set only if the `PUSHBUTTON` flag is unset.
const RADIO = 1 << 16;
/// The field is a push button that does not retain a permanent
/// value.
const PUSHBUTTON = 1 << 17;
/// A group of radio buttons within a radio button field that use the
/// same value for the on state will turn on and off in unison; that
/// is if one is checked, they are all checked. If unset, the buttons
/// are mutually exclusive (the same behavior as HTML radio buttons).
/// PDF 1.5+.
const RADIOS_IN_UNISON = 1 << 26;

// Text field specific flags

/// The text may contain multiple lines of text, otherwise the text is
/// restricted to one line.
const MULTILINE = 1 << 13;
/// The text contains a password and should not be echoed visibly to
/// the screen.
const PASSWORD = 1 << 14;
/// The entered text represents a path to a file who's contents shall be
/// submitted as the value of the field. PDF 1.4+.
const FILE_SELECT = 1 << 21;
/// The field shall not scroll horizontally (for single-line) or
/// vertically (for multi-line) to accomodate more text. Once the field
/// is full, no further text shall be accepted for interactive form
/// filling; for non-interactive form filling, the filler should take
/// care not to add more character than will visibly fit in the defined
/// area. PDF 1.4+.
const DO_NOT_SCROLL = 1 << 24;
/// The field shall eb automatically divided into as many equally
/// spaced postions or _combs_ as the value of [`Field::max_len`]
/// and the text is layed out into these combs. May only be set if
/// the [`Field::max_len`] property is set and if the [`MULTILINE`],
/// [`PASSWORD`] and [`FILE_SELECT`] flags are clear. PDF 1.5+.
const COMB = 1 << 25;
/// The value of this field shall be a rich text string. If the field
/// has a value, the [`TextField::rich_text_value`] shall specify the
/// rich text string. PDF 1.5+.
const RICH_TEXT = 1 << 26;

// Choice field specific flags

/// The field is a combo box if set, else it's a list box.
const COMBO = 1 << 18;
/// The combo box shall include an editable text box as well as a
/// drop-down list. Shall only be used if [`COMBO`] is set.
const EDIT = 1 << 19;
/// The field’s option items shall be sorted alphabetically. This
/// flag is intended for use by writers, not by readers.
const SORT = 1 << 20;
/// More than one option of the choice field may be selected
/// simultaneously. PDF 1.4+.
const MULTI_SELECT = 1 << 22;
/// The new value shall be committed as soon as a selection is made
/// (commonly with the mouse). In this case, supplying a value for
/// a field involves three actions: selecting the field for fill-in,
/// selecting a choice for the fill-in value, and leaving that field,
/// which finalizes or "commits" the data choice and triggers any
/// actions associated with the entry or changing of this data.
///
/// If set, processing does not wait for leaving the field action to
/// occur, but immediately proceeds to the third step. PDF 1.5+.
const COMMIT_ON_SEL_CHANGE = 1 << 27;
}
}

0 comments on commit bb19bfd

Please sign in to comment.