diff tests/test_widgets.py @ 1:1d09e1dec1d9 upstream

ADD: PyMuPDF v1.26.4: the original sdist. It does not yet contain MuPDF. This normally will be downloaded when building PyMuPDF.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:37:51 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_widgets.py	Mon Sep 15 11:37:51 2025 +0200
@@ -0,0 +1,432 @@
+# -*- 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()