Class: FillablePDF

Inherits:
Object
  • Object
show all
Defined in:
lib/fillable-pdf.rb,
lib/fillable-pdf/version.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

VERSION =
'0.9.4'

Instance Method Summary collapse

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)


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

def initialize(file_path) # rubocop:disable Metrics/MethodLength
  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
    @pdf_form.setGenerateAppearance false
  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)


34
35
36
# File 'lib/fillable-pdf.rb', line 34

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



236
237
238
239
# File 'lib/fillable-pdf.rb', line 236

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


54
55
56
57
58
# File 'lib/fillable-pdf.rb', line 54

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

#field_type(key) ⇒ Object

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

@param [String|Symbol] key the field name

@return the type of the field


67
68
69
# File 'lib/fillable-pdf.rb', line 67

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


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

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


187
188
189
190
191
192
# File 'lib/fillable-pdf.rb', line 187

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


43
44
45
# File 'lib/fillable-pdf.rb', line 43

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


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

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


169
170
171
# File 'lib/fillable-pdf.rb', line 169

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


211
212
213
214
215
# File 'lib/fillable-pdf.rb', line 211

def save(flatten: false)
  tmp_file = 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 [Hash] flatten: true if PDF should be flattened, false otherwise


223
224
225
226
227
228
229
# File 'lib/fillable-pdf.rb', line 223

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) ⇒ 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


92
93
94
# File 'lib/fillable-pdf.rb', line 92

def set_field(key, value)
  pdf_field(key).setValue(value.to_s)
end

#set_fields(fields) ⇒ 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


159
160
161
# File 'lib/fillable-pdf.rb', line 159

def set_fields(fields)
  fields.each { |key, value| set_field key, value }
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


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/fillable-pdf.rb', line 105

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


146
147
148
149
150
151
152
# File 'lib/fillable-pdf.rb', line 146

def set_image_base64(key, base64_image_data)
  tmp_file = 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


199
200
201
202
203
204
# File 'lib/fillable-pdf.rb', line 199

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