Class: FillablePDF
- Inherits:
-
Object
- Object
- FillablePDF
- Includes:
- SuppressWarnings
- Defined in:
- lib/fillable-pdf.rb,
lib/fillable-pdf/field.rb,
lib/fillable-pdf/itext.rb,
lib/fillable-pdf/errors.rb,
lib/fillable-pdf/version.rb,
lib/fillable-pdf/suppress_warnings.rb
Overview
rubocop:disable Metrics/ClassLength
Defined Under Namespace
Modules: ITEXT, SuppressWarnings Classes: Error, Field, FieldNotFoundError, FileOperationError, InvalidArgumentError
Constant Summary collapse
- VERSION =
'1.0.0'
Instance Method Summary collapse
-
#any_fields? ⇒ Boolean
Determines whether the form has any fields.
-
#close ⇒ Boolean
Closes the PDF document discarding all unsaved changes.
-
#closed? ⇒ Boolean
Checks if the PDF document is closed.
-
#field(key) ⇒ Object
Retrieves the value of a field given its unique field name.
-
#field_count ⇒ Object
Returns the total number of fillable form fields.
-
#field_type(key) ⇒ Object
Retrieves the string type of a field given its unique field name.
-
#fields ⇒ Object
Retrieves a hash of all fields and their values.
-
#initialize(file_path) ⇒ FillablePDF
constructor
Opens a given fillable-pdf PDF file and prepares it for modification.
-
#names ⇒ Object
Returns a list of all field keys used in the document.
-
#num_fields ⇒ Object
deprecated
Deprecated.
Use #field_count instead
-
#remove_field(key) ⇒ Object
Removes a field from the document given its unique field name.
-
#rename_field(old_key, new_key) ⇒ Object
Renames a field given its unique field name and the new field name.
-
#save(flatten: false) ⇒ Object
Overwrites the previously opened PDF document and flattens it if requested.
-
#save_as(file_path, flatten: false) ⇒ Object
Saves the filled out PDF document in a given path and flattens it if requested.
-
#save_as!(file_path, flatten: false) ⇒ Object
Saves the filled out PDF document in a given path and flattens it if requested.
-
#set_field(key, value, generate_appearance: nil) ⇒ Object
Sets the value of a field given its unique field name and value.
-
#set_fields(fields, generate_appearance: nil) ⇒ Object
Sets the values of multiple fields given a set of unique field names and values.
-
#set_image(key, file_path) ⇒ Object
Sets an image within the bounds of the given form field.
-
#set_image_base64(key, base64_image_data) ⇒ Object
Sets an image within the bounds of the given form field.
-
#values ⇒ Object
Returns a list of all field values used in the document.
Methods included from SuppressWarnings
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
@raise [FileOperationError] if the file is not found or cannot be opened
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/fillable-pdf.rb', line 18 def initialize(file_path) # rubocop:disable Metrics/MethodLength raise FileOperationError, "File <#{file_path}> is not found" unless File.exist?(file_path) @file_path = file_path @closed = false 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.getAllFormFields rescue StandardError => e handle_pdf_open_error(e) end end |
Instance Method Details
#any_fields? ⇒ Boolean
Determines whether the form has any fields.
@return [Boolean] true if form has fields, false otherwise
39 40 41 |
# File 'lib/fillable-pdf.rb', line 39 def any_fields? field_count.positive? end |
#close ⇒ Boolean
Closes the PDF document discarding all unsaved changes.
357 358 359 360 361 362 363 |
# File 'lib/fillable-pdf.rb', line 357 def close # rubocop:disable Naming/PredicateMethod return true if closed? @pdf_doc.close @closed = true true end |
#closed? ⇒ Boolean
Checks if the PDF document is closed.
370 371 372 |
# File 'lib/fillable-pdf.rb', line 370 def closed? @closed ||= false end |
#field(key) ⇒ Object
Retrieves the value of a field given its unique field name.
@param [String|Symbol] key the field name
@return [String] the value of the field
@raise [FieldNotFoundError] if the field does not exist
66 67 68 69 70 |
# File 'lib/fillable-pdf.rb', line 66 def field(key) pdf_field(key).getValueAsString rescue NoMethodError raise FieldNotFoundError, "Unknown key name `#{key}'" end |
#field_count ⇒ Object
Returns the total number of fillable form fields.
@return [Integer] the number of fields
48 49 50 |
# File 'lib/fillable-pdf.rb', line 48 def field_count @form_fields.size 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 [String, nil] the type of the field (e.g., '/Btn', '/Tx', '/Ch', '/Sig')
@raise [FieldNotFoundError] if the field does not exist
79 80 81 |
# File 'lib/fillable-pdf.rb', line 79 def field_type(key) pdf_field(key).getFormType&.toString end |
#fields ⇒ Object
Retrieves a hash of all fields and their values.
@return [Hash{Symbol => String}] hash of field keys (as symbols) and values
88 89 90 91 92 93 94 95 96 |
# File 'lib/fillable-pdf.rb', line 88 def fields iterator = @form_fields.keySet.iterator map = {} while iterator.hasNext key = iterator.next.toString map[key.to_sym] = field(key) end map end |
#names ⇒ Object
Returns a list of all field keys used in the document.
@return [Array<Symbol>] array of field names as symbols
274 275 276 277 278 279 |
# File 'lib/fillable-pdf.rb', line 274 def names iterator = @form_fields.keySet.iterator set = [] set << iterator.next.toString.to_sym while iterator.hasNext set end |
#num_fields ⇒ Object
Use #field_count instead
54 55 56 57 |
# File 'lib/fillable-pdf.rb', line 54 def num_fields warn '[DEPRECATION] `num_fields` is deprecated. Use `field_count` instead.' field_count end |
#remove_field(key) ⇒ Object
Removes a field from the document given its unique field name.
@param [String|Symbol] key the field name
@return [self] returns self for method chaining
@raise [FieldNotFoundError] if the field does not exist
258 259 260 261 262 263 264 265 266 267 |
# File 'lib/fillable-pdf.rb', line 258 def remove_field(key) ensure_document_open validate_field_name(key) raise FieldNotFoundError, "Unknown key name `#{key}'" unless @form_fields.containsKey(key.to_s) @pdf_form.removeField(key.to_s) @form_fields.remove(key.to_s) self 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 current field name
@param [String|Symbol] new_key the new field name
@return [self] returns self for method chaining
@raise [FieldNotFoundError] if the field does not exist
@raise [InvalidArgumentError] if the new field name already exists
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/fillable-pdf.rb', line 227 def rename_field(old_key, new_key) # rubocop:disable Metrics/MethodLength ensure_document_open validate_field_name(old_key) validate_field_name(new_key) old_key = old_key.to_s new_key = new_key.to_s raise FieldNotFoundError, "Field `#{old_key}` not found" unless @form_fields.containsKey(old_key) raise InvalidArgumentError, "Field name `#{new_key}` already exists" if @form_fields.containsKey(new_key) field = pdf_field(old_key) field.setFieldName(new_key) @form_fields.remove(old_key) @form_fields.put(new_key, field) self rescue FieldNotFoundError, InvalidArgumentError raise rescue StandardError => e raise FileOperationError, "Unable to rename field `#{old_key}` to `#{new_key}`: #{e.}" end |
#save(flatten: false) ⇒ Object
Overwrites the previously opened PDF document and flattens it if requested.
@param [Boolean] flatten true if PDF should be flattened, false otherwise
@return [self] returns self for method chaining
@raise [FileOperationError] if the save operation fails
300 301 302 303 304 305 306 |
# File 'lib/fillable-pdf.rb', line 300 def save(flatten: false) ensure_document_open tmp_file = "#{Dir.tmpdir}/#{SecureRandom.uuid}" save_as(tmp_file, flatten: flatten) FileUtils.mv tmp_file, @file_path self end |
#save_as(file_path, flatten: false) ⇒ Object
Saves the filled out PDF document in a given path and flattens it if requested. If the path matches the current file path, it will call save() instead.
@param [String] file_path the name of the PDF file or file path
@param [Boolean] flatten true if PDF should be flattened, false otherwise
@return [self] returns self for method chaining
@raise [FileOperationError] if the save operation fails
317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/fillable-pdf.rb', line 317 def save_as(file_path, flatten: false) ensure_document_open if @file_path == file_path save(flatten: flatten) return self end File.open(file_path, 'wb') { |f| f.write(finalize(flatten: flatten)) && f.close } self rescue StandardError => e raise FileOperationError, "Failed to save file `#{file_path}`: #{e.}" end |
#save_as!(file_path, flatten: false) ⇒ Object
Saves the filled out PDF document in a given path and flattens it if requested. Raises an error if the path matches the current file path (use save() instead).
@param [String] file_path the name of the PDF file or file path
@param [Boolean] flatten true if PDF should be flattened, false otherwise
@return [self] returns self for method chaining
@raise [InvalidArgumentError] if file_path matches the current file path
@raise [FileOperationError] if the save operation fails
340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/fillable-pdf.rb', line 340 def save_as!(file_path, flatten: false) ensure_document_open raise InvalidArgumentError, 'Cannot save_as! to the same file path. Use save() instead.' if @file_path == file_path File.open(file_path, 'wb') { |f| f.write(finalize(flatten: flatten)) && f.close } self rescue InvalidArgumentError raise rescue StandardError => e raise FileOperationError, "Failed to save file `#{file_path}`: #{e.}" 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 [Boolean, nil] 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
@return [self] returns self for method chaining
@raise [InvalidArgumentError] if key or value are invalid
@raise [FieldNotFoundError] if the field does not exist
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/fillable-pdf.rb', line 108 def set_field(key, value, generate_appearance: nil) ensure_document_open validate_input(key, value) field = pdf_field(key) if generate_appearance.nil? field.setValue(value.to_s) else field.setValue(value.to_s, generate_appearance) end self 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{String, Symbol => String}] fields the set of field names and values
@param [Boolean, nil] 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
@return [self] returns self for method chaining
@raise [InvalidArgumentError] if any key or value is invalid
@raise [FieldNotFoundError] if any field does not exist
212 213 214 215 216 |
# File 'lib/fillable-pdf.rb', line 212 def set_fields(fields, generate_appearance: nil) ensure_document_open fields.each { |key, value| set_field(key, value, generate_appearance: generate_appearance) } self 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
@return [self] returns self for method chaining
@raise [FileOperationError] if the image file is not found
@raise [FieldNotFoundError] if the field does not exist
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/fillable-pdf.rb', line 134 def set_image(key, file_path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength ensure_document_open raise FileOperationError, "File <#{file_path}> is not found" unless File.exist?(file_path) begin field = pdf_field(key) = field.getWidgets = suppress_warnings { .isEmpty ? field.getPdfObject : .get(0).getPdfObject } orig_rect = .getAsRectangle(ITEXT::PdfName.Rect) border_style = field.getWidgets.get(0).getBorderStyle border_width = border_style.nil? ? 0 : border_style.getWidth 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 .put(ITEXT::PdfName.AP, pdf_dict) pdf_dict.put(ITEXT::PdfName.N, pdf_form_x_object.getPdfObject) .setModified rescue StandardError => e raise FileOperationError, "Failed to set image for field '#{key}': #{e.}" end self 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] base64_image_data base64 encoded image data
@return [self] returns self for method chaining
@raise [InvalidArgumentError] if the base64 data is invalid
@raise [FieldNotFoundError] if the field does not exist
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/fillable-pdf.rb', line 187 def set_image_base64(key, base64_image_data) ensure_document_open tmp_file = "#{Dir.tmpdir}/#{SecureRandom.uuid}" begin decoded_data = Base64.strict_decode64(base64_image_data) File.binwrite(tmp_file, decoded_data) set_image(key, tmp_file) rescue ArgumentError => e raise InvalidArgumentError, "Invalid base64 data: #{e.}" ensure FileUtils.rm_f(tmp_file) end self end |
#values ⇒ Object
Returns a list of all field values used in the document.
@return [Array<String>] array of field values
286 287 288 289 290 291 |
# File 'lib/fillable-pdf.rb', line 286 def values iterator = @form_fields.keySet.iterator set = [] set << field(iterator.next.toString) while iterator.hasNext set end |