Filling radio buttons in form

I’m trying the following with the attached PDF:

import pymupdf 

# Load the PDF
doc = pymupdf.open("form.pdf")

# Read data from CSV into a Python dictionary
data = {"Name":"An Alien", "Address":"The Moon", "RadioGroup":"Choice1"}

# Iterate over all pages and process fields
for page in doc:  
    for field in page.widgets():
        print("found field")  
        if field.field_name in data:
            value = data[field.field_name]
            #print(f"found data:{value}")  
  
            # Handling radio buttons
            if field.field_type == pymupdf.PDF_WIDGET_TYPE_RADIOBUTTON:
                print(f"field on state:{field.on_state()}")
                print(f"data value:{value}")  
                if value == field.on_state():
                    print("---> check radio")
                    field.field_value = value  
            
            # Handling other fields types
            else:
                field.field_value = value
            
            field.update()

# Save the modified PDF
doc.save("filled-form.pdf")


However it seems to check both radio buttons? They are Choice1 and Choice2 in the PDF and the code seems to show that just the first one is checked. Can anyone help here?

form.pdf (10.0 KB)

You should assume the habit to only use True or False when setting values of radio buttons.
Definitely only False for the Off-state.
The possible values for buttons (radio or checkbox) are enumerated by function Widget.button_states(). Here are the responses for those 2 buttons:

btn1.button_states()
{'normal': ['Off', 'Choice1'], 'down': None}
btn2.button_states()
{'normal': ['Off', 'Choice2'], 'down': None}

The name of the “On” state of a button can be found out by

btn2.on_state()
'Choice2'
btn1.on_state()
'Choice1'

Setting a button to “On”, the statement btn.field_value = btn.on_state() should also work (instead of using True).
So much for the theory.

Here is the state of the file after open:

doc=pymupdf.open(doc.name)
page=doc[0]
btn1=page.load_widget(111)
btn2=page.load_widget(112)
btn1.field_value
'Off'
btn2.field_value
'Choice2'
# print the PDF objects, including their parent field (xref=63)
print(doc.xref_object(63))
<<
  /DA (/ZaDb 0 Tf 0 g)
  /FT /Btn
  /Ff 49152
  /Kids [ 111 0 R 112 0 R ]
  /T (RadioGroup)
  /V /Choice2
>>
print(doc.xref_object(111))
<<
  /AP <<
    /D <<
      /Choice1 75 0 R
      /Off 76 0 R
    >>
    /N <<
      /Choice1 73 0 R
      /Off 74 0 R
    >>
  >>
  /AS /Off
  /BS <<
    /S /I
    /W 1
  >>
  /F 4
  /MK <<
    /BC [ 0 ]
    /BG [ 1 ]
  >>
  /P 69 0 R
  /Parent 63 0 R
  /Rect [ 42.5456 693.665 60.5456 711.665 ]
  /Subtype /Widget
  /Type /Annot
>>
print(doc.xref_object(112))
<<
  /AP <<
    /D <<
      /Choice2 79 0 R
      /Off 80 0 R
    >>
    /N <<
      /Choice2 77 0 R
      /Off 78 0 R
    >>
  >>
  /AS /Choice2
  /BS <<
    /S /I
    /W 1
  >>
  /F 4
  /MK <<
    /BC [ 0 ]
    /BG [ 1 ]
  >>
  /P 69 0 R
  /Parent 63 0 R
  /Rect [ 42.5456 668.792 60.5456 686.792 ]
  /Subtype /Widget
  /Type /Annot
>>

Everything looks as it should!

  • parent shows /V = /Choice2
  • btn2 shows /AS = /Choice2
  • btn1 shows /AS = /Off.

Now set btn1 to selected and update it!

btn1.field_value=btn1.on_state()
btn1.field_value
'Choice1'
btn1.update()  #<=== this is a MUST DO
print(doc.xref_object(111))
<<
  /AP <<
    /N <<
      /Off 152 0 R
      /Choice1 153 0 R
    >>
  >>
  /AS /Choice1  #<=== this has changed as expected
  /BS <<
    /S /I
    /W 1
  >>
  /F 4
  /MK <<
    /BC [ 0 ]
    /BG [ 1 ]
  >>
  /P 69 0 R
  /Parent 63 0 R
  /Rect [ 42.5456 693.665 60.5456 711.665 ]
  /Subtype /Widget
  /Type /Annot
  /DA (0 g /ZaDb 0 Tf)
  /Ff 49152
>>
print(doc.xref_object(63))  #<=== look at the parent
<<
  /DA (/ZaDb 0 Tf 0 g)
  /FT /Btn
  /Ff 49152
  /Kids [ 111 0 R 112 0 R ]
  /T (RadioGroup)
  /V (Choice1)  #<=== correct / expected
>>
print(doc.xref_object(112))  #<=== look at the other button
<<
  /Type /Annot
  /Subtype /Widget
  /AP <<
    /N <<
      /Off 154 0 R
      /Choice2 155 0 R
    >>
  >>
  /AS /Off  #<===  correct / as expected
  /BS <<
    /S /I
    /W 1
  >>
  /F 4
  /MK <<
    /BC [ 0 ]
    /BG [ 1 ]
  >>
  /P 69 0 R
  /Parent 63 0 R
  /Rect [ 42.5456 668.792 60.5456 686.792 ]
>>

So, what is going wrong in the following: … ???

btn2.field_value
'Choice2'

The problem is that all widgets are Python objects in memory. Changes in the underlying PDF are not (and cannot be) updated automatically.
Button btn2 must be reloaded from the PDF before we can see the changes:

btn2 = page.load_widget(btn2.xref)  #<=== refresh the widget from the PDF
btn2.field_value
'Off'

Maybe what is missing in PyMuPDF is a more mnemonic way to do this: btn2.refresh_widget() or something like that.

Got it - so update() for the widget is a critical step - many thanks! :folded_hands: