extract_form_structure.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. """
  2. Extract form structure from a non-fillable PDF.
  3. This script analyzes the PDF to find:
  4. - Text labels with their exact coordinates
  5. - Horizontal lines (row boundaries)
  6. - Checkboxes (small rectangles)
  7. Output: A JSON file with the form structure that can be used to generate
  8. accurate field coordinates for filling.
  9. Usage: python extract_form_structure.py <input.pdf> <output.json>
  10. """
  11. import json
  12. import sys
  13. import pdfplumber
  14. def extract_form_structure(pdf_path):
  15. structure = {
  16. "pages": [],
  17. "labels": [],
  18. "lines": [],
  19. "checkboxes": [],
  20. "row_boundaries": []
  21. }
  22. with pdfplumber.open(pdf_path) as pdf:
  23. for page_num, page in enumerate(pdf.pages, 1):
  24. structure["pages"].append({
  25. "page_number": page_num,
  26. "width": float(page.width),
  27. "height": float(page.height)
  28. })
  29. words = page.extract_words()
  30. for word in words:
  31. structure["labels"].append({
  32. "page": page_num,
  33. "text": word["text"],
  34. "x0": round(float(word["x0"]), 1),
  35. "top": round(float(word["top"]), 1),
  36. "x1": round(float(word["x1"]), 1),
  37. "bottom": round(float(word["bottom"]), 1)
  38. })
  39. for line in page.lines:
  40. if abs(float(line["x1"]) - float(line["x0"])) > page.width * 0.5:
  41. structure["lines"].append({
  42. "page": page_num,
  43. "y": round(float(line["top"]), 1),
  44. "x0": round(float(line["x0"]), 1),
  45. "x1": round(float(line["x1"]), 1)
  46. })
  47. for rect in page.rects:
  48. width = float(rect["x1"]) - float(rect["x0"])
  49. height = float(rect["bottom"]) - float(rect["top"])
  50. if 5 <= width <= 15 and 5 <= height <= 15 and abs(width - height) < 2:
  51. structure["checkboxes"].append({
  52. "page": page_num,
  53. "x0": round(float(rect["x0"]), 1),
  54. "top": round(float(rect["top"]), 1),
  55. "x1": round(float(rect["x1"]), 1),
  56. "bottom": round(float(rect["bottom"]), 1),
  57. "center_x": round((float(rect["x0"]) + float(rect["x1"])) / 2, 1),
  58. "center_y": round((float(rect["top"]) + float(rect["bottom"])) / 2, 1)
  59. })
  60. lines_by_page = {}
  61. for line in structure["lines"]:
  62. page = line["page"]
  63. if page not in lines_by_page:
  64. lines_by_page[page] = []
  65. lines_by_page[page].append(line["y"])
  66. for page, y_coords in lines_by_page.items():
  67. y_coords = sorted(set(y_coords))
  68. for i in range(len(y_coords) - 1):
  69. structure["row_boundaries"].append({
  70. "page": page,
  71. "row_top": y_coords[i],
  72. "row_bottom": y_coords[i + 1],
  73. "row_height": round(y_coords[i + 1] - y_coords[i], 1)
  74. })
  75. return structure
  76. def main():
  77. if len(sys.argv) != 3:
  78. print("Usage: extract_form_structure.py <input.pdf> <output.json>")
  79. sys.exit(1)
  80. pdf_path = sys.argv[1]
  81. output_path = sys.argv[2]
  82. print(f"Extracting structure from {pdf_path}...")
  83. structure = extract_form_structure(pdf_path)
  84. with open(output_path, "w") as f:
  85. json.dump(structure, f, indent=2)
  86. print(f"Found:")
  87. print(f" - {len(structure['pages'])} pages")
  88. print(f" - {len(structure['labels'])} text labels")
  89. print(f" - {len(structure['lines'])} horizontal lines")
  90. print(f" - {len(structure['checkboxes'])} checkboxes")
  91. print(f" - {len(structure['row_boundaries'])} row boundaries")
  92. print(f"Saved to {output_path}")
  93. if __name__ == "__main__":
  94. main()