|
2 | 2 | """ |
3 | 3 | Test PDF field (widget) insertion. |
4 | 4 | """ |
| 5 | +import gc |
5 | 6 | import pymupdf |
6 | 7 | import os |
7 | 8 | from pymupdf import mupdf |
@@ -570,3 +571,90 @@ def test_5101(): |
570 | 571 | else: |
571 | 572 | assert 0, 'test_5101(): Expected exception from document2.insert_pdf.' |
572 | 573 |
|
| 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