view tests/test_widgets.py @ 44:0a8b06e38e19

Need "packaging" at wheel build time too. Parsing of version_p into a tuple now is done at build time.
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 11 Oct 2025 17:16:23 +0200
parents 1d09e1dec1d9
children
line wrap: on
line source

# -*- coding: utf-8 -*-
"""
Test PDF field (widget) insertion.
"""
import pymupdf
import os

scriptdir = os.path.abspath(os.path.dirname(__file__))
filename = os.path.join(scriptdir, "resources", "widgettest.pdf")
file_2333 = os.path.join(scriptdir, "resources", "test-2333.pdf")
file_4055 = os.path.join(scriptdir, "resources", "test-4055.pdf")


doc = pymupdf.open()
page = doc.new_page()
gold = (1, 1, 0)  # define some colors
blue = (0, 0, 1)
gray = (0.9, 0.9, 0.9)
fontsize = 11.0  # define a fontsize
lineheight = fontsize + 4.0
rect = pymupdf.Rect(50, 72, 400, 200)


def test_text():
    doc = pymupdf.open()
    page = doc.new_page()
    widget = pymupdf.Widget()  # create a widget object
    widget.border_color = blue  # border color
    widget.border_width = 0.3  # border width
    widget.border_style = "d"
    widget.border_dashes = (2, 3)
    widget.field_name = "Textfield-1"  # field name
    widget.field_label = "arbitrary text - e.g. to help filling the field"
    widget.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT  # field type
    widget.fill_color = gold  # field background
    widget.rect = rect  # set field rectangle
    widget.text_color = blue  # rext color
    widget.text_font = "TiRo"  # use font Times-Roman
    widget.text_fontsize = fontsize  # set fontsize
    widget.text_maxlen = 50  # restrict number of characters
    widget.field_value = "Times-Roman"
    page.add_widget(widget)  # create the field
    field = page.first_widget
    assert field.field_type_string == "Text"


def test_checkbox():
    doc = pymupdf.open()
    page = doc.new_page()
    widget = pymupdf.Widget()
    widget.border_style = "b"
    widget.field_name = "Button-1"
    widget.field_label = "a simple check box button"
    widget.field_type = pymupdf.PDF_WIDGET_TYPE_CHECKBOX
    widget.fill_color = gold
    widget.rect = rect
    widget.text_color = blue
    widget.text_font = "ZaDb"
    widget.field_value = True
    page.add_widget(widget)  # create the field
    field = page.first_widget
    assert field.field_type_string == "CheckBox"

    # Check #2350 - setting checkbox to readonly.
    #
    widget.field_flags |= pymupdf.PDF_FIELD_IS_READ_ONLY
    widget.update()
    path = f"{scriptdir}/test_checkbox.pdf"
    doc.save(path)

    doc = pymupdf.open(path)
    page = doc[0]
    widget = page.first_widget
    assert widget
    assert widget.field_flags == pymupdf.PDF_FIELD_IS_READ_ONLY


def test_listbox():
    doc = pymupdf.open()
    page = doc.new_page()
    widget = pymupdf.Widget()
    widget.field_name = "ListBox-1"
    widget.field_label = "is not a drop down: scroll with cursor in field"
    widget.field_type = pymupdf.PDF_WIDGET_TYPE_LISTBOX
    widget.field_flags = pymupdf.PDF_CH_FIELD_IS_COMMIT_ON_SEL_CHANGE
    widget.fill_color = gold
    widget.choice_values = (
        "Frankfurt",
        "Hamburg",
        "Stuttgart",
        "Hannover",
        "Berlin",
        "München",
        "Köln",
        "Potsdam",
    )
    widget.rect = rect
    widget.text_color = blue
    widget.text_fontsize = fontsize
    widget.field_value = widget.choice_values[-1]
    print("About to add '%s'" % widget.field_name)
    page.add_widget(widget)  # create the field
    field = page.first_widget
    assert field.field_type_string == "ListBox"


def test_combobox():
    doc = pymupdf.open()
    page = doc.new_page()
    widget = pymupdf.Widget()
    widget.field_name = "ComboBox-1"
    widget.field_label = "an editable combo box ..."
    widget.field_type = pymupdf.PDF_WIDGET_TYPE_COMBOBOX
    widget.field_flags = (
        pymupdf.PDF_CH_FIELD_IS_COMMIT_ON_SEL_CHANGE | pymupdf.PDF_CH_FIELD_IS_EDIT
    )
    widget.fill_color = gold
    widget.choice_values = (
        "Spanien",
        "Frankreich",
        "Holland",
        "Dänemark",
        "Schweden",
        "Norwegen",
        "England",
        "Polen",
        "Russland",
        "Italien",
        "Portugal",
        "Griechenland",
    )
    widget.rect = rect
    widget.text_color = blue
    widget.text_fontsize = fontsize
    widget.field_value = widget.choice_values[-1]
    page.add_widget(widget)  # create the field
    field = page.first_widget
    assert field.field_type_string == "ComboBox"


def test_text2():
    doc = pymupdf.open()
    doc.new_page()
    page = [p for p in doc.pages()][0]
    widget = pymupdf.Widget()
    widget.field_name = "textfield-2"
    widget.field_label = "multi-line text with tabs is also possible!"
    widget.field_flags = pymupdf.PDF_TX_FIELD_IS_MULTILINE
    widget.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT
    widget.fill_color = gray
    widget.rect = rect
    widget.text_color = blue
    widget.text_font = "TiRo"
    widget.text_fontsize = fontsize
    widget.field_value = "This\n\tis\n\t\ta\n\t\t\tmulti-\n\t\tline\n\ttext."
    page.add_widget(widget)  # create the field
    widgets = [w for w in page.widgets()]
    field = widgets[0]
    assert field.field_type_string == "Text"


def test_2333():
    doc = pymupdf.open(file_2333)
    page = doc[0]

    def values():
        return set(
            (
                doc.xref_get_key(635, "AS")[1],
                doc.xref_get_key(636, "AS")[1],
                doc.xref_get_key(637, "AS")[1],
                doc.xref_get_key(638, "AS")[1],
                doc.xref_get_key(127, "V")[1],
            )
        )

    for i, xref in enumerate((635, 636, 637, 638)):
        w = page.load_widget(xref)
        w.field_value = True
        w.update()
        assert values() == set(("/Off", f"{i}", f"/{i}"))
    w.field_value = False
    w.update()
    assert values() == set(("Off", "/Off"))


def test_2411():
    """Add combobox values in different formats."""
    doc = pymupdf.open()
    page = doc.new_page()
    rect = pymupdf.Rect(100, 100, 300, 200)

    widget = pymupdf.Widget()
    widget.field_flags = (
        pymupdf.PDF_CH_FIELD_IS_COMBO
        | pymupdf.PDF_CH_FIELD_IS_EDIT
        | pymupdf.PDF_CH_FIELD_IS_COMMIT_ON_SEL_CHANGE
    )
    widget.field_name = "ComboBox-1"
    widget.field_label = "an editable combo box ..."
    widget.field_type = pymupdf.PDF_WIDGET_TYPE_COMBOBOX
    widget.fill_color = pymupdf.pdfcolor["gold"]
    widget.rect = rect
    widget.choice_values = [
        ["Spain", "ES"],  # double value as list
        ("Italy", "I"),  # double value as tuple
        "Portugal",  # single value
    ]
    page.add_widget(widget)


def test_2391():
    """Confirm that multiple times setting a checkbox to ON/True/Yes will work."""
    doc = pymupdf.open(f"{scriptdir}/resources/widgettest.pdf")
    page = doc[0]
    # its work when we update first-time
    for field in page.widgets(types=[pymupdf.PDF_WIDGET_TYPE_CHECKBOX]):
        field.field_value = True
        field.update()

    for i in range(5):
        pdfdata = doc.tobytes()
        doc.close()
        doc = pymupdf.open("pdf", pdfdata)
        page = doc[0]
        for field in page.widgets(types=[pymupdf.PDF_WIDGET_TYPE_CHECKBOX]):
            assert field.field_value == field.on_state()
            field_field_value = field.on_state()
            field.update()


def test_3216():
    document = pymupdf.open(filename)
    for page in document:
        while 1:
            w = page.first_widget
            print(f"{w=}")
            if not w:
                break
            page.delete_widget(w)


def test_add_widget():
    doc = pymupdf.open()
    page = doc.new_page()
    w = pymupdf.Widget()
    w.field_type = pymupdf.PDF_WIDGET_TYPE_BUTTON
    w.rect = pymupdf.Rect(5, 5, 20, 20)
    w.field_flags = pymupdf.PDF_BTN_FIELD_IS_PUSHBUTTON
    w.field_name = "button"
    w.fill_color = (0, 0, 1)
    w.script = "app.alert('Hello, PDF!');"
    page.add_widget(w)


def test_interfield_calculation():
    """Confirm correct working of interfield calculations.

    We are going to create three pages with a computed result field each.

    Tests the fix for https://github.com/pymupdf/PyMuPDF/issues/3402.
    """
    # Field bboxes (same on each page)
    r1 = pymupdf.Rect(100, 100, 300, 120)
    r2 = pymupdf.Rect(100, 130, 300, 150)
    r3 = pymupdf.Rect(100, 180, 300, 200)

    doc = pymupdf.open()
    pdf = pymupdf._as_pdf_document(doc)  # we need underlying PDF document

    # Make PDF name object for "CO" because it is not defined in MuPDF.
    CO_name = pymupdf.mupdf.pdf_new_name("CO")  # = PDF_NAME(CO)
    for i in range(3):
        page = doc.new_page()
        w = pymupdf.Widget()
        w.field_name = f"NUM1{page.number}"
        w.rect = r1
        w.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT
        w.field_value = f"{i*100+1}"
        w.field_flags = 2
        page.add_widget(w)

        w = pymupdf.Widget()
        w.field_name = f"NUM2{page.number}"
        w.rect = r2
        w.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT
        w.field_value = "200"
        w.field_flags = 2
        page.add_widget(w)

        w = pymupdf.Widget()
        w.field_name = f"RESULT{page.number}"
        w.rect = r3
        w.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT
        w.field_value = "Result?"
        # Script that adds previous two fields.
        w.script_calc = f"""AFSimple_Calculate("SUM",
        new Array("NUM1{page.number}", "NUM2{page.number}"));"""
        page.add_widget(w)

        # Access the inter-field calculation array. It contains a reference to
        # all fields which have a JavaScript stored in their "script_calc"
        # property, i.e. an "AA/C" entry.
        # Every iteration adds another such field, so this array's length must
        # always equal the loop index.
        if i == 0:  # only need to execute this on first time through
            CO = pymupdf.mupdf.pdf_dict_getl(
                pymupdf.mupdf.pdf_trailer(pdf),
                pymupdf.PDF_NAME("Root"),
                pymupdf.PDF_NAME("AcroForm"),
                CO_name,
            )
        # we confirm CO is an array of foreseeable length
        assert pymupdf.mupdf.pdf_array_len(CO) == i + 1

        # the xref of the i-th item must equal that of the last widget
        assert (
            pymupdf.mupdf.pdf_to_num(pymupdf.mupdf.pdf_array_get(CO, i))
            == list(page.widgets())[-1].xref
        )


def test_3950():
    path = os.path.normpath(f'{__file__}/../../tests/resources/test_3950.pdf')
    items = list()
    with pymupdf.open(path) as document:
        for page in document:
            for widget in page.widgets():
                items.append(widget.field_label)
                print(f'test_3950(): {widget.field_label=}.')
    assert items == [
            '{{ named_insured }}',
            '{{ policy_period_start_date }}',
            '{{ policy_period_end_date }}',
            '{{ insurance_line }}',
            ]


def test_4004():
    import collections
    
    def get_widgets_by_name(doc):
        """
        Extracts and returns a dictionary of widgets indexed by their names.
        """
        widgets_by_name = collections.defaultdict(list)
        for page_num in range(len(doc)):
            page = doc.load_page(page_num)
            for field in page.widgets():
                widgets_by_name[field.field_name].append({
                    "page_num": page_num,
                    "widget": field
                })
        return widgets_by_name

    # Open document and get widgets
    path = os.path.normpath(f'{__file__}/../../tests/resources/test_4004.pdf')
    doc = pymupdf.open(path)
    widgets_by_name = get_widgets_by_name(doc)

    # Print widget information
    for name, widgets in widgets_by_name.items():
        print(f"Widget Name: {name}")
        for entry in widgets:
            widget = entry["widget"]
            page_num = entry["page_num"]
            print(f"  Page: {page_num + 1}, Type: {widget.field_type}, Value: {widget.field_value}, Rect: {widget.rect}")

    # Attempt to update field value
    w = widgets_by_name["Text1"][0]
    field = w['widget']
    field.value = "1234567890"
    try:
        field.update()
    except Exception as e:
        assert str(e) == 'Annot is not bound to a page'

    doc.close()


def test_4055():
    """Check correct setting of CheckBox "Yes" values.

    Test scope:
    * setting on with any of 'True' / 'Yes' / built-in values works
    * setting off with any of 'False' or 'Off' works
    """

    # this PDF has digits as "Yes" values.
    doc = pymupdf.open(file_4055)
    page = doc[0]

    # Round 1: confirm all check boxes are off
    for w in page.widgets(types=[2]):
        # check that this file doesn't use the "Yes" standard
        assert w.on_state() != "Yes"
        assert w.field_value == "Off"  # all check boxes are off
        w.field_value = w.on_state()
        w.update()

    page = doc.reload_page(page)  # reload page to make sure we start fresh

    # Round 2: confirm that fields contain the PDF's own on values
    for w in page.widgets(types=[2]):
        # confirm each value coincides with the "Yes" value
        assert w.field_value == w.on_state()
        w.field_value = False  # switch to "Off" using False
        w.update()

    page = doc.reload_page(page)

    # Round 3: confirm that 'False' achieved "Off" values
    for w in page.widgets(types=[2]):
        assert w.field_value == "Off"
        w.field_value = True  # use True for the next round
        w.update()

    page = doc.reload_page(page)

    # Round 4: confirm that setting to True also worked
    for w in page.widgets(types=[2]):
        assert w.field_value == w.on_state()
        w.field_value = "Off"  # set off again
        w.update()
        w.field_value = "Yes"
        w.update()

    page = doc.reload_page(page)

    # Round 5: final check: setting to "Yes" also does work
    for w in page.widgets(types=[2]):
        assert w.field_value == w.on_state()