fill_fillable_fields.py 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import json
  2. import sys
  3. from pypdf import PdfReader, PdfWriter
  4. from extract_form_field_info import get_field_info
  5. def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_pdf_path: str):
  6. with open(fields_json_path) as f:
  7. fields = json.load(f)
  8. fields_by_page = {}
  9. for field in fields:
  10. if "value" in field:
  11. field_id = field["field_id"]
  12. page = field["page"]
  13. if page not in fields_by_page:
  14. fields_by_page[page] = {}
  15. fields_by_page[page][field_id] = field["value"]
  16. reader = PdfReader(input_pdf_path)
  17. has_error = False
  18. field_info = get_field_info(reader)
  19. fields_by_ids = {f["field_id"]: f for f in field_info}
  20. for field in fields:
  21. existing_field = fields_by_ids.get(field["field_id"])
  22. if not existing_field:
  23. has_error = True
  24. print(f"ERROR: `{field['field_id']}` is not a valid field ID")
  25. elif field["page"] != existing_field["page"]:
  26. has_error = True
  27. print(f"ERROR: Incorrect page number for `{field['field_id']}` (got {field['page']}, expected {existing_field['page']})")
  28. else:
  29. if "value" in field:
  30. err = validation_error_for_field_value(existing_field, field["value"])
  31. if err:
  32. print(err)
  33. has_error = True
  34. if has_error:
  35. sys.exit(1)
  36. writer = PdfWriter(clone_from=reader)
  37. for page, field_values in fields_by_page.items():
  38. writer.update_page_form_field_values(writer.pages[page - 1], field_values, auto_regenerate=False)
  39. writer.set_need_appearances_writer(True)
  40. with open(output_pdf_path, "wb") as f:
  41. writer.write(f)
  42. def validation_error_for_field_value(field_info, field_value):
  43. field_type = field_info["type"]
  44. field_id = field_info["field_id"]
  45. if field_type == "checkbox":
  46. checked_val = field_info["checked_value"]
  47. unchecked_val = field_info["unchecked_value"]
  48. if field_value != checked_val and field_value != unchecked_val:
  49. return f'ERROR: Invalid value "{field_value}" for checkbox field "{field_id}". The checked value is "{checked_val}" and the unchecked value is "{unchecked_val}"'
  50. elif field_type == "radio_group":
  51. option_values = [opt["value"] for opt in field_info["radio_options"]]
  52. if field_value not in option_values:
  53. return f'ERROR: Invalid value "{field_value}" for radio group field "{field_id}". Valid values are: {option_values}'
  54. elif field_type == "choice":
  55. choice_values = [opt["value"] for opt in field_info["choice_options"]]
  56. if field_value not in choice_values:
  57. return f'ERROR: Invalid value "{field_value}" for choice field "{field_id}". Valid values are: {choice_values}'
  58. return None
  59. def monkeypatch_pydpf_method():
  60. from pypdf.generic import DictionaryObject
  61. from pypdf.constants import FieldDictionaryAttributes
  62. original_get_inherited = DictionaryObject.get_inherited
  63. def patched_get_inherited(self, key: str, default = None):
  64. result = original_get_inherited(self, key, default)
  65. if key == FieldDictionaryAttributes.Opt:
  66. if isinstance(result, list) and all(isinstance(v, list) and len(v) == 2 for v in result):
  67. result = [r[0] for r in result]
  68. return result
  69. DictionaryObject.get_inherited = patched_get_inherited
  70. if __name__ == "__main__":
  71. if len(sys.argv) != 4:
  72. print("Usage: fill_fillable_fields.py [input pdf] [field_values.json] [output pdf]")
  73. sys.exit(1)
  74. monkeypatch_pydpf_method()
  75. input_pdf = sys.argv[1]
  76. fields_json = sys.argv[2]
  77. output_pdf = sys.argv[3]
  78. fill_pdf_fields(input_pdf, fields_json, output_pdf)