Class: FillablePDF

Inherits:
Object
  • Object
show all
Includes:
SuppressWarnings
Defined in:
lib/fillable-pdf.rb,
lib/fillable-pdf/field.rb,
lib/fillable-pdf/itext.rb,
lib/fillable-pdf/version.rb,
lib/fillable-pdf/suppress_warnings.rb

Overview

rubocop:disable Metrics/ClassLength

Defined Under Namespace

Modules: ITEXT, SuppressWarnings Classes: Field

Constant Summary collapse

VERSION =
'0.9.6'

Instance Method Summary collapse

Methods included from SuppressWarnings

#suppress_warnings

Constructor Details

#initialize(file_path) ⇒ FillablePDF

Opens a given fillable-pdf PDF file and prepares it for modification.

@param [String|Symbol] file_path the name of the PDF file or file path

Raises:

  • (IOError)


16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/fillable-pdf.rb', line 16

def initialize(file_path)
  raise IOError, "File <#{file_path}> is not found" unless File.exist?(file_path)
  @file_path = file_path
  begin
    @byte_stream = ITEXT::ByteArrayOutputStream.new
    @pdf_reader = ITEXT::PdfReader.new @file_path.to_s
    @pdf_writer = ITEXT::PdfWriter.new @byte_stream
    @pdf_doc = ITEXT::PdfDocument.new @pdf_reader, @pdf_writer
    @pdf_form = ITEXT::PdfAcroForm.getAcroForm(@pdf_doc, true)
    @form_fields = @pdf_form.getFormFields
  rescue StandardError => e
    raise "#{e.message} (Input file may be corrupt, incompatible, read-only, write-protected, encrypted, or may not have any form fields)" # rubocop:disable Layout/LineLength
  end
end

Instance Method Details

#any_fields?Boolean

Determines whether the form has any fields.

@return true if form has fields, false otherwise

Returns:

  • (Boolean)


36
37
38
# File 'lib/fillable-pdf.rb', line 36

def any_fields?
  num_fields.positive?
end

#closeBoolean

Closes the PDF document discarding all unsaved changes.

Returns:

  • (Boolean)

    true if document is closed, false otherwise



244
245
246
247
# File 'lib/fillable-pdf.rb', line 244

def close
  @pdf_doc.close
  @pdf_doc.isClosed
end

#field(key) ⇒ Object

Retrieves the value of a field given its unique field name.

@param [String|Symbol] key the field name

@return the value of the field


56
57
58
59
60
# File 'lib/fillable-pdf.rb', line 56

def field(key)
  pdf_field(key).getValueAsString
rescue NoMethodError
  raise "unknown key name `#{key}'"
end

#field_type(key) ⇒ Object

Retrieves the string type of a field given its unique field name.

@param [String|Symbol] key the field name

@return the type of the field


69
70
71
# File 'lib/fillable-pdf.rb', line 69

def field_type(key)
  pdf_field(key).getFormType.toString
end

#fieldsObject

Retrieves a hash of all fields and their values.

@return the hash of field keys and values


78
79
80
81
82
83
84
85
86
# File 'lib/fillable-pdf.rb', line 78

def fields
  iterator = @form_fields.keySet.iterator
  map = {}
  while iterator.hasNext
    key = iterator.next.toString
    map[key.to_sym] = field(key)
  end
  map
end

#namesObject

Returns a list of all field keys used in the document.

@return array of field names


195
196
197
198
199
200
# File 'lib/fillable-pdf.rb', line 195

def names
  iterator = @form_fields.keySet.iterator
  set = []
  set << iterator.next.toString.to_sym while iterator.hasNext
  set
end

#num_fieldsObject

Returns the total number of fillable form fields.

@return the number of fields


45
46
47
# File 'lib/fillable-pdf.rb', line 45

def num_fields
  @form_fields.size
end

#remove_field(key) ⇒ Object

Removes a field from the document given its unique field name.

@param [String|Symbol] key the field name


186
187
188
# File 'lib/fillable-pdf.rb', line 186

def remove_field(key)
  @pdf_form.removeField(key.to_s)
end

#rename_field(old_key, new_key) ⇒ Object

Renames a field given its unique field name and the new field name.

@param [String|Symbol] old_key the field name
@param [String|Symbol] new_key the field name


177
178
179
# File 'lib/fillable-pdf.rb', line 177

def rename_field(old_key, new_key)
  pdf_field(old_key).setFieldName(new_key.to_s)
end

#save(flatten: false) ⇒ Object

Overwrites the previously opened PDF document and flattens it if requested.

@param [bool] flatten true if PDF should be flattened, false otherwise


219
220
221
222
223
# File 'lib/fillable-pdf.rb', line 219

def save(flatten: false)
  tmp_file = "#{Dir.tmpdir}/#{SecureRandom.uuid}"
  save_as(tmp_file, flatten: flatten)
  FileUtils.mv tmp_file, @file_path
end

#save_as(file_path, flatten: false) ⇒ Object

Saves the filled out PDF document in a given path and flattens it if requested.

@param [String] file_path the name of the PDF file or file path
@param [TrueClass|FalseClass] flatten true if PDF should be flattened, false otherwise


231
232
233
234
235
236
237
# File 'lib/fillable-pdf.rb', line 231

def save_as(file_path, flatten: false)
  if @file_path == file_path
    save(flatten: flatten)
  else
    File.open(file_path, 'wb') { |f| f.write(finalize(flatten: flatten)) && f.close }
  end
end

#set_field(key, value, generate_appearance: nil) ⇒ Object

Sets the value of a field given its unique field name and value.

@param [String|Symbol] key the field name
@param [String|Symbol] value the field value
@param [NilClass|TrueClass|FalseClass] generate_appearance true to generate appearance, false to let the PDF viewer application generate form field appearance, nil (default) to let iText decide what's appropriate


95
96
97
98
99
100
101
# File 'lib/fillable-pdf.rb', line 95

def set_field(key, value, generate_appearance: nil)
  if generate_appearance.nil?
    pdf_field(key).setValue(value.to_s)
  else
    pdf_field(key).setValue(value.to_s, generate_appearance)
  end
end

#set_fields(fields, generate_appearance: nil) ⇒ Object

Sets the values of multiple fields given a set of unique field names and values.

@param [Hash] fields the set of field names and values
@param [NilClass|TrueClass|FalseClass] generate_appearance true to generate appearance, false to let the PDF viewer application generate form field appearance,  nil (default) to let iText decide what's appropriate


167
168
169
# File 'lib/fillable-pdf.rb', line 167

def set_fields(fields, generate_appearance: nil)
  fields.each { |key, value| set_field key, value, generate_appearance: generate_appearance }
end

#set_image(key, file_path) ⇒ Object

Sets an image within the bounds of the given form field. It doesn’t matter what type of form field it is (signature, image, etc). The image will be scaled to fill the available space while preserving its aspect ratio. All previous content will be removed, which means you cannot have both text and image.

@param [String|Symbol] key the field name
@param [String|Symbol] file_path the name of the image file or image path


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/fillable-pdf.rb', line 112

def set_image(key, file_path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  raise IOError, "File <#{file_path}> is not found" unless File.exist?(file_path)
  field = pdf_field(key)
  widgets = field.getWidgets
  widget_dict = suppress_warnings { widgets.isEmpty ? field.getPdfObject : widgets.get(0).getPdfObject }
  orig_rect = widget_dict.getAsRectangle(ITEXT::PdfName.Rect)
  border_width = field.getBorderWidth
  bounding_rectangle = ITEXT::Rectangle.new(
    orig_rect.getWidth - (border_width * 2),
    orig_rect.getHeight - (border_width * 2)
  )

  pdf_form_x_object = ITEXT::PdfFormXObject.new(bounding_rectangle)
  canvas = ITEXT::Canvas.new(pdf_form_x_object, @pdf_doc)
  image = ITEXT::Image.new(ITEXT::ImageDataFactory.create(file_path.to_s))
                      .setAutoScale(true)
                      .setHorizontalAlignment(ITEXT::HorizontalAlignment.CENTER)
  container = ITEXT::Div.new
                        .setMargin(border_width).add(image)
                        .setVerticalAlignment(ITEXT::VerticalAlignment.MIDDLE)
                        .setFillAvailableArea(true)
  canvas.add(container)
  canvas.close

  pdf_dict = ITEXT::PdfDictionary.new
  widget_dict.put(ITEXT::PdfName.AP, pdf_dict)
  pdf_dict.put(ITEXT::PdfName.N, pdf_form_x_object.getPdfObject)
  widget_dict.setModified
rescue StandardError => e
  raise "#{e.message} (there may be something wrong with your image)"
end

#set_image_base64(key, base64_image_data) ⇒ Object

Sets an image within the bounds of the given form field. It doesn’t matter what type of form field it is (signature, image, etc). The image will be scaled to fill the available space while preserving its aspect ratio. All previous content will be removed, which means you cannot have both text and image.

@param [String|Symbol] key the field name
@param [String|Symbol] base64_image_data base64 encoded data image


153
154
155
156
157
158
159
# File 'lib/fillable-pdf.rb', line 153

def set_image_base64(key, base64_image_data)
  tmp_file = "#{Dir.tmpdir}/#{SecureRandom.uuid}"
  File.binwrite(tmp_file, Base64.decode64(base64_image_data))
  set_image(key, tmp_file)
ensure
  FileUtils.rm tmp_file
end

#valuesObject

Returns a list of all field values used in the document.

@return array of field values


207
208
209
210
211
212
# File 'lib/fillable-pdf.rb', line 207

def values
  iterator = @form_fields.keySet.iterator
  set = []
  set << field(iterator.next.toString) while iterator.hasNext
  set
end