diff --git a/.rubocop.yml b/.rubocop.yml index 2993c27..6be802b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -29,6 +29,7 @@ Layout/LineLength: - README.md - fillable-pdf.gemspec Max: 120 + AllowedPatterns: ['^(\s*#)'] Layout/SpaceInsideHashLiteralBraces: Enabled: false diff --git a/README.md b/README.md index f114f82..d65f6ce 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,35 @@ FillablePDF is an extremely simple and lightweight utility that bridges iText an 4. Read-only, write-protected or encrypted PDF files are currently not supported. -5. Adobe generated field arrays (i.e. fields with names such as `array.0` or `array.1.0`) are not supported. +5. Adobe generated field arrays (i.e. fields with names such as `array.0` or `array.1.0`) are not supported. +## Troubleshooting Issues + +### Blank Fields + +* **Actual Result:** + + ![Blank](images/blank.png) + +* **Expected Result:** + + ![Blank](images/checked.png) + +If only of the fields are blank, try setting the `generate_appearance` flag to `true` when calling `set_field` or `set_fields`. + +### Invalid Checkbox Appearances + +* **Actual Result:** + + ![Blank](images/checked.png) + +* **Expected Result:** + + ![Blank](images/distinct.png) + +If your checkboxes are showing incorrectly, it's likely because iText is overwriting your checkbox appearances. Try setting the `generate_appearance` flag to `false` when calling `set_field` or `set_fields`. + ## Installation **Prerequisites:** Java SE Development Kit v8, v11 @@ -131,19 +157,21 @@ An instance of `FillablePDF` has the following methods at its disposal: pdf.num_fields ``` -* `field` +* `field(key)` *Retrieves the value of a field given its unique field name.* ```ruby pdf.field(:full_name) + pdf.field('full_name') # output example: 'Richard' ``` -* `field_type` +* `field_type(key)` *Retrieves the string type of a field given its unique field name.* ```ruby pdf.field_type(:football) + pdf.field_type('football') # output example: '/Btn' # list of all field types @@ -157,6 +185,7 @@ An instance of `FillablePDF` has the following methods at its disposal: ```ruby pdf.field_type(:football) == Field::BUTTON + pdf.field_type('football') == Field::BUTTON ``` * `fields` @@ -167,52 +196,71 @@ An instance of `FillablePDF` has the following methods at its disposal: # output example: {first_name: "Richard", last_name: "Rahl"} ``` -* `set_field` - *Sets the value of a field given its unique field name and value.* +* `set_field(key, value, generate_appearance: nil)` + *Sets the value of a field given its unique field name and value, with an optional `generate_appearance` directive.* ```ruby pdf.set_field(:first_name, 'Richard') + pdf.set_field('first_name', 'Richard') # result: changes the value of 'first_name' to 'Richard' ``` -* `set_fields` - *Sets the values of multiple fields given a set of unique field names and values.* + Optionally, you can choose to override iText's `generateAppearance` flag to take better control of your field's appearance, using `generate_appearance`. Passing `true` will force the field to generate its own appearance, while setting it to `false` would leave the appearance generation up to the PDF viewer application. Omitting the parameter would allow iText to decide what should happen. + + ```ruby + pdf.set_field(:first_name, 'Richard', generate_appearance: true) + pdf.set_field('first_name', 'Richard', generate_appearance: false) + ``` + +* `def set_fields(fields, generate_appearance: nil)` + *Sets the values of multiple fields given a set of unique field names and values, with an optional `generate_appearance` directive.* ```ruby - pdf.set_fields(first_name: 'Richard', last_name: 'Rahl') + pdf.set_fields({first_name: 'Richard', last_name: 'Rahl'}) # result: changes the values of 'first_name' and 'last_name' ``` -* `set_image` + Optionally, you can choose to override iText's `generateAppearance` flag to take better control of your fields' appearance, using `generate_appearance`. Passing `true` will force the field to generate its own appearance, while setting it to `false` would leave the appearance generation up to the PDF viewer application. Omitting the parameter would allow iText to decide what should happen. + + ```ruby + pdf.set_fields({first_name: 'Richard', last_name: 'Rahl'}, generate_appearance: true) + pdf.set_fields({first_name: 'Richard', last_name: 'Rahl'}, generate_appearance: false) + ``` + +* `set_image(key, file_path)` *Places an image file within the rectangular bounding box of the given form field.* ```ruby pdf.set_image(:signature, 'signature.png') + pdf.set_image('signature', 'signature.png') # result: the image 'signature.png' is shown in the foreground of the form field ``` -* `set_image_base64` +* `set_image_base64(key, base64_image_data)` *Places a base64 encoded image within the rectangular bounding box of the given form field.* ```ruby + pdf.set_image_base64('signature', 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==') pdf.set_image_base64(:signature, 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==') # result: the base64 encoded image is shown in the foreground of the form field ``` -* `rename_field` +* `rename_field(old_key, new_key)` *Renames a field given its unique field name and the new field name.* ```ruby pdf.rename_field(:last_name, :surname) + pdf.rename_field('last_name', 'surname') # result: renames field name 'last_name' to 'surname' # NOTE: this action does not take effect until the document is saved ``` -* `remove_field` +* `remove_field(key)` *Removes a field from the document given its unique field name.* ```ruby pdf.remove_field(:last_name) + pdf.remove_field('last_name') # result: physically removes field 'last_name' from document ``` @@ -232,7 +280,7 @@ An instance of `FillablePDF` has the following methods at its disposal: # output example: ["Rahl", "Richard"] ``` -* `save` +* `save(flatten: false)` *Overwrites the previously opened PDF document and flattens it if requested.* ```ruby @@ -242,7 +290,7 @@ An instance of `FillablePDF` has the following methods at its disposal: # result: document is saved with flattening ``` -* `save_as` +* `save_as(file_path, flatten: false)` *Saves the filled out PDF document in a given path and flattens it if requested.* ```ruby @@ -344,8 +392,8 @@ end puts # setting form fields -pdf.set_fields(first_name: 'Richard', last_name: 'Rahl') -pdf.set_fields(football: 'Yes', baseball: 'Yes', basketball: 'Yes', nascar: 'Yes', hockey: 'Yes') +pdf.set_fields({first_name: 'Richard', last_name: 'Rahl'}) +pdf.set_fields({football: 'Yes', baseball: 'Yes', basketball: 'Yes', nascar: 'Yes', hockey: 'Yes', rugby: 'Yes'}, generate_appearance: false) pdf.set_field(:date, Time.now.strftime('%B %e, %Y')) pdf.set_field(:newsletter, 'Off') # uncheck the checkbox pdf.set_field(:language, 'dart') # select a radio button option @@ -368,7 +416,7 @@ puts "Values: #{pdf.values}" puts # Checking field type -if pdf.field_type(:football) == Field::BUTTON +if pdf.field_type(:rugby) == Field::BUTTON puts "Field 'football' is of type BUTTON" else puts "Field 'football' is not of type BUTTON" @@ -383,8 +431,8 @@ puts "Renamed field 'last_name' to 'surname'" puts # Removing field -pdf.remove_field :nascar -puts "Removed field 'nascar'" +pdf.remove_field :marketing +puts "Removed field 'marketing'" # saving the filled out PDF in another file pdf.save_as('output.pdf') diff --git a/example/run.rb b/example/run.rb index ae4c116..5a445fd 100644 --- a/example/run.rb +++ b/example/run.rb @@ -15,8 +15,11 @@ puts # setting form fields -pdf.set_fields(first_name: 'Richard', last_name: 'Rahl') -pdf.set_fields(football: 'Yes', baseball: 'Yes', basketball: 'Yes', nascar: 'Yes', hockey: 'Yes', rugby: 'Yes') +pdf.set_fields({first_name: 'Richard', last_name: 'Rahl'}) +pdf.set_fields( + {football: 'Yes', baseball: 'Yes', basketball: 'Yes', nascar: 'Yes', hockey: 'Yes', rugby: 'Yes'}, + generate_appearance: false +) pdf.set_field(:date, Time.now.strftime('%B %e, %Y')) pdf.set_field(:newsletter, 'Off') # uncheck the checkbox pdf.set_field(:language, 'dart') # select a radio button option diff --git a/images/blank.png b/images/blank.png new file mode 100755 index 0000000..d510176 Binary files /dev/null and b/images/blank.png differ diff --git a/images/checked.png b/images/checked.png new file mode 100755 index 0000000..72c5da3 Binary files /dev/null and b/images/checked.png differ diff --git a/images/distinct.png b/images/distinct.png new file mode 100755 index 0000000..648903c Binary files /dev/null and b/images/distinct.png differ diff --git a/lib/fillable-pdf.rb b/lib/fillable-pdf.rb index b26cd25..e61bf5b 100644 --- a/lib/fillable-pdf.rb +++ b/lib/fillable-pdf.rb @@ -87,12 +87,14 @@ def fields # # @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 # - def set_field(key, value) - # we set generate_appearance to false for buttons to ensure that the chosen - # appearance for checkboxes (i.e. check, circle, diamond) is not changed - generate_appearance = field_type(key) != Field::BUTTON - pdf_field(key).setValue(value.to_s, generate_appearance) + 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 ## @@ -157,9 +159,10 @@ def set_image_base64(key, base64_image_data) # 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 # - def set_fields(fields) - fields.each { |key, value| set_field key, value } + def set_fields(fields, generate_appearance: nil) + fields.each { |key, value| set_field key, value, generate_appearance: generate_appearance } end ## @@ -220,7 +223,7 @@ def save(flatten: false) # 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 + # @param [TrueClass|FalseClass] flatten true if PDF should be flattened, false otherwise # def save_as(file_path, flatten: false) if @file_path == file_path @@ -245,7 +248,7 @@ def close ## # Writes the contents of the modified fields to the previously opened PDF file. # - # @param [Hash] flatten: true if PDF should be flattened, false otherwise + # @param [TrueClass|FalseClass] flatten: true if PDF should be flattened, false otherwise # def finalize(flatten: false) @pdf_form.flattenFields if flatten diff --git a/test/pdf_test.rb b/test/pdf_test.rb index 8fb0f72..7762468 100644 --- a/test/pdf_test.rb +++ b/test/pdf_test.rb @@ -19,6 +19,7 @@ def test_that_an_error_is_thrown_for_non_existing_file err = assert_raises IOError do @pdf = FillablePDF.new 'test.pdf' end + assert_match 'is not found', err.message end @@ -46,6 +47,7 @@ def test_that_a_field_type_can_be_accessed_by_name def test_that_a_field_value_can_be_modified @pdf.set_field(:first_name, 'Richard') + assert_equal 'Richard', @pdf.field(:first_name) end @@ -59,26 +61,32 @@ def test_that_a_base64_can_be_placed_in_photo_field def test_that_an_asian_font_works @pdf.set_field(:first_name, '理查德') + assert_equal '理查德', @pdf.field(:first_name) end def test_that_multiple_field_values_can_be_modified - @pdf.set_fields(first_name: 'Richard', last_name: 'Rahl') + @pdf.set_fields({first_name: 'Richard', last_name: 'Rahl'}) + assert_equal 'Richard', @pdf.field(:first_name) assert_equal 'Rahl', @pdf.field(:last_name) end def test_that_a_checkbox_can_be_checked_and_unchecked @pdf.set_field(:nascar, 'Yes') + assert_equal 'Yes', @pdf.field(:nascar) @pdf.set_field(:newsletter, 'Off') + assert_equal 'Off', @pdf.field(:newsletter) end def test_that_a_radio_button_can_be_checked_and_unchecked @pdf.set_field(:language, 'ruby') + 3.times { |i| assert_equal 'ruby', @pdf.field("language.#{i}".gsub('.0', '')) } @pdf.set_field(:language, 'Off') + 3.times { |i| assert_equal 'Off', @pdf.field("language.#{i}".gsub('.0', '')) } end @@ -89,6 +97,7 @@ def test_that_a_field_can_be_renamed err = assert_raises RuntimeError do @pdf.field(:last_name) end + assert_match 'unknown key name', err.message assert_equal 'Test', @pdf.field(:surname) end @@ -98,6 +107,7 @@ def test_that_a_field_can_be_removed err = assert_raises RuntimeError do @pdf.field(:first_name) end + assert_match 'unknown key name', err.message end @@ -111,9 +121,11 @@ def test_that_field_values_can_be_accessed def test_that_a_file_can_be_saved @pdf.save_as(@tmp) + refute_nil FillablePDF.new(@tmp) @pdf = FillablePDF.new(@tmp) @pdf.save + refute_nil FillablePDF.new(@tmp) end