[Python] Extract XDF Form Fields from pdf

Discussão em 'Python' iniciado por Stack, Agosto 5, 2025.

  1. Stack

    Stack Membro Participativo

    Some of the XDF form fields are missing when the /PageItemUIDToLocationDataMap is extracted from some pdf files as shown on the image below only showing fields identified identified with black dots for pages 1 and 2 (Click image to open pdf). How can the missing XDF form fields be extracted without using commercial software?

    [​IMG]

    The following code was used to extract input.pdf datamaps, save in a csv file and add the points to output.pdf. Disable sort_and_filter to see original data in the .csv file:

    import pikepdf
    import fitz # PyMuPDF
    import csv

    INPUT_PDF = "input.pdf"
    OUTPUT_CSV = "points.csv"
    OUTPUT_PDF = "output.pdf"
    TARGET_KEY = "/PageItemUIDToLocationDataMap"

    def extract_datamap_points(pdf_path, target_key=TARGET_KEY):
    out_rows = []
    with pikepdf.open(pdf_path) as pdf:
    for i, page in enumerate(pdf.pages):
    piece_info = page.get('/PieceInfo', None)
    if piece_info and '/InDesign' in piece_info:
    indesign = piece_info['/InDesign']
    if target_key in indesign:
    for k, v in indesign[target_key].items():
    try:
    id_ = int(str(k).lstrip('/'))
    type_val = float(v[2])
    coords = [float(val) for val in list(v)[3:7]]
    out_rows.append([i+1, id_, type_val] + coords)
    except Exception as e:
    print(f"Error parsing {k}:{v} ({e})")
    return out_rows

    def get_pdf_page_count(pdf_path):
    with pikepdf.open(pdf_path) as pdf:
    return len(pdf.pages)

    def process_rows(rows, max_pdf_pages):
    Y_TRANSFORM_BASE = 420.945 # Local constant hack for y-coordinate transform

    # Datamaps are read sequentially so hack to pages
    total_pages = get_pdf_page_count(INPUT_PDF)
    hack_page = lambda page: 2 if (page >= max_pdf_pages) else (page + 1 if page > 1 else page)
    processed_rows = []
    for row in rows:
    page, id_, type_val, x1, y1, x2, y2 = row
    hacked_page = hack_page(page)
    new_y1 = round(Y_TRANSFORM_BASE - y1, 3)
    new_y2 = round(Y_TRANSFORM_BASE - y2, 3)
    new_x1 = round(x1, 3)
    new_x2 = round(x2, 3)
    h = round(abs(new_y2 - new_y1), 1)
    processed_rows.append([hacked_page, id_, type_val, new_x1, new_y1, new_x2, new_y2, h])
    return processed_rows

    def sort_and_filter(rows):
    # Sort by page ascending, -y2 descending, x1 ascending, id ascending
    rows_sorted = sorted(rows, key=lambda r: (r[0], -r[6], r[3], r[1]))
    # Filter rows
    filtered = []
    for row in rows_sorted:
    if (row[2] == 4 # type
    and row[7] == 17): # height
    filtered.append(row)

    return filtered

    def write_csv(csv_filename, rows):
    with open(csv_filename, 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerow(['page', 'id', 'type', 'x1', 'y1', 'x2', 'y2', 'h'])
    writer.writerows(rows)

    def mark_points_on_pdf(input_pdf, output_pdf, rows):
    doc = fitz.open(input_pdf)
    for row in rows:
    page_num = int(row[0])
    cx = row[3]
    cy = row[6]
    page = doc[page_num - 1]
    pymupdf_y = page.rect.height - cy
    page.draw_circle((cx, pymupdf_y), radius=2, color=(0, 0, 0), fill=(0, 0, 0))
    doc.save(output_pdf)

    if __name__ == "__main__":
    points = extract_datamap_points(INPUT_PDF)
    processed_points = process_rows(points, total_pages)
    filtered_points = sort_and_filter(processed_points)
    write_csv(OUTPUT_CSV, filtered_points)
    mark_points_on_pdf(INPUT_PDF, OUTPUT_PDF, filtered_points)
    print(f"Done. Points: {len(filtered_points)}; Wrote {OUTPUT_CSV} and {OUTPUT_PDF}")

    Continue reading...

Compartilhe esta Página