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.3'

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
# File 'lib/fillable-pdf.rb', line 13

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)


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

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



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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


104
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
# File 'lib/fillable-pdf.rb', line 104

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


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

def set_image_base64(key, base64_image_data)
  tmp_file = SecureRandom.uuid
  File.open(tmp_file, 'wb') { |f| f.write(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


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

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