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

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



241
242
243
244
# File 'lib/fillable-pdf.rb', line 241

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 string 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


192
193
194
195
196
197
# File 'lib/fillable-pdf.rb', line 192

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


183
184
185
# File 'lib/fillable-pdf.rb', line 183

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


174
175
176
# File 'lib/fillable-pdf.rb', line 174

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


216
217
218
219
220
# File 'lib/fillable-pdf.rb', line 216

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 [TrueClass|FalseClass] flatten true if PDF should be flattened, false otherwise


228
229
230
231
232
233
234
# File 'lib/fillable-pdf.rb', line 228

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


92
93
94
95
96
97
98
# File 'lib/fillable-pdf.rb', line 92

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


164
165
166
# File 'lib/fillable-pdf.rb', line 164

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


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
136
137
138
139
# File 'lib/fillable-pdf.rb', line 109

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


150
151
152
153
154
155
156
# File 'lib/fillable-pdf.rb', line 150

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


204
205
206
207
208
209
# File 'lib/fillable-pdf.rb', line 204

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