From 7692f674bc4d813cac2b999f5380205e363a4e66 Mon Sep 17 00:00:00 2001 From: Vadim Kononov Date: Sat, 19 Nov 2022 15:40:39 -0600 Subject: [PATCH] Fix an issue with incorrect or blank checkbox appearances. (#29) In a previous commit, iText's `generateAppearance` field was set to true for all fields except checkboxes in order to enable custom checkboxes to show correctly. However, this had unintended consequences for some developers where checkboxes with the default appearance were completely blank. In order to work correctly with both custom checkboxes (such as square, circle, diamond, or other) and default checkboxes, developers can now set `generate_appearance` themselves to whatever suits their type of PDF file. --- .rubocop.yml | 1 + README.md | 86 ++++++++++++++++++++++++++++++++++---------- example/run.rb | 7 ++-- images/blank.png | Bin 0 -> 5087 bytes images/checked.png | Bin 0 -> 6965 bytes images/distinct.png | Bin 0 -> 6233 bytes lib/fillable-pdf.rb | 21 ++++++----- test/pdf_test.rb | 14 +++++++- 8 files changed, 98 insertions(+), 31 deletions(-) create mode 100755 images/blank.png create mode 100755 images/checked.png create mode 100755 images/distinct.png 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 0000000000000000000000000000000000000000..d510176d0924aa33707ab063691aaa75a2943ed3 GIT binary patch literal 5087 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