Skip to content

Commit f5f0c19

Browse files
tests/: added test_3478(), checks complete removal of widgets.
Looks like this was fixed in pymupdf-1.25.3, where `Page.delete_widget()` also removes fields from Root/AcroForm/Fields[].
1 parent c55d70e commit f5f0c19

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

‎tests/resources/test_3478.pdf‎

249 KB
Binary file not shown.

‎tests/test_widgets.py‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""
33
Test PDF field (widget) insertion.
44
"""
5+
import gc
56
import pymupdf
67
import os
78
from pymupdf import mupdf
@@ -570,3 +571,90 @@ def test_5101():
570571
else:
571572
assert 0, 'test_5101(): Expected exception from document2.insert_pdf.'
572573

574+
def test_3478():
575+
print()
576+
print(f'test_3478(): {pymupdf.version=}')
577+
path = os.path.normpath(f'{__file__}/../../tests/resources/test_3478.pdf')
578+
path_out = os.path.normpath(f'{__file__}/../../tests/test_3478_out.pdf')
579+
path_out2 = os.path.normpath(f'{__file__}/../../tests/test_3478_out2.pdf')
580+
581+
def get_acro_names(document):
582+
ret = list()
583+
obj = mupdf.pdf_dict_getl(
584+
mupdf.pdf_trailer(pymupdf._as_pdf_document(document)),
585+
pymupdf.PDF_NAME('Root'),
586+
pymupdf.PDF_NAME('AcroForm'),
587+
pymupdf.PDF_NAME('Fields'),
588+
)
589+
assert pymupdf.mupdf.pdf_is_array(obj)
590+
l = pymupdf.mupdf.pdf_array_len(obj)
591+
for i in range(l):
592+
field = pymupdf.mupdf.pdf_array_get(obj, i)
593+
assert pymupdf.mupdf.pdf_is_dict(field)
594+
T = mupdf.pdf_dict_getp(field, 'T').pdf_to_text_string()
595+
ret.append(T)
596+
return ret
597+
598+
with pymupdf.open(path) as document:
599+
600+
print(f'test_3478(): Root/AcroForm/Fields/[]/T:')
601+
acro_names = get_acro_names(document)
602+
for acro_name in acro_names:
603+
if acro_name.startswith('sig') or acro_name.startswith('init'):
604+
print(f'test_3478(): {acro_name}')
605+
606+
print(f'test_3478(): Page widgets:')
607+
for page in document:
608+
# iterate the fields on this page.
609+
widget = page.first_widget
610+
while widget:
611+
name = widget.field_name
612+
# If it's a signature, remove it.
613+
if name.startswith('sig') or name.startswith('init'):
614+
xref = widget.xref
615+
print(f'test_3478(): Calling page.delete_widget() for {name=}.')
616+
widget = page.delete_widget(widget)
617+
else:
618+
widget = widget.next
619+
620+
document.save(path_out2)
621+
document.ez_save(path_out)
622+
623+
# 2026-09-10: page.delete_widget() introduces a cyclic dependency which
624+
# results in us leaving a fd open and a warning from conftest.py, so do a
625+
# collection here to keep output clean.
626+
gc.collect()
627+
628+
629+
# In the ez_save()'d document:
630+
#
631+
# * Check that deleted widget fields are not in page widgets.
632+
# (This has always been ok.)
633+
# * Check that deleted widget fields are not in Root/AcroForm/Fields[]/T.
634+
# (This appears to have been fixed in pymupdf-1.25.3.)
635+
#
636+
print(f'test_3478(): looking at ez_save document.')
637+
num_still_present = 0
638+
num_still_present_acro = 0
639+
640+
with pymupdf.open(path_out) as document:
641+
642+
print(f'test_3478(): Page widgets:')
643+
for page_i, page in enumerate(document):
644+
for (widget_i, widget) in enumerate(page.widgets()):
645+
name = widget.field_name
646+
if name.startswith('sig') or name.startswith('init'):
647+
print(f'test_3478(): {page_i=} {widget_i=}: {name}')
648+
num_still_present += 1
649+
print(f'test_3478(): {num_still_present=}')
650+
651+
acro_names = get_acro_names(document)
652+
for acro_name in acro_names:
653+
if acro_name.startswith('sig') or acro_name.startswith('init'):
654+
print(f'test_3478(): {i}: {acro_name}')
655+
num_still_present_acro += 1
656+
print(f'test_3478(): {num_still_present_acro=}')
657+
658+
assert num_still_present == 0, f'{num_still_present=}'
659+
assert num_still_present_acro == 0, f'{num_still_present_acro=}'
660+

0 commit comments

Comments
 (0)