Mercurial > hgrepos > Python2 > PyMuPDF
comparison 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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 1:1d09e1dec1d9 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 """ | |
| 3 Test PDF field (widget) insertion. | |
| 4 """ | |
| 5 import pymupdf | |
| 6 import os | |
| 7 | |
| 8 scriptdir = os.path.abspath(os.path.dirname(__file__)) | |
| 9 filename = os.path.join(scriptdir, "resources", "widgettest.pdf") | |
| 10 file_2333 = os.path.join(scriptdir, "resources", "test-2333.pdf") | |
| 11 file_4055 = os.path.join(scriptdir, "resources", "test-4055.pdf") | |
| 12 | |
| 13 | |
| 14 doc = pymupdf.open() | |
| 15 page = doc.new_page() | |
| 16 gold = (1, 1, 0) # define some colors | |
| 17 blue = (0, 0, 1) | |
| 18 gray = (0.9, 0.9, 0.9) | |
| 19 fontsize = 11.0 # define a fontsize | |
| 20 lineheight = fontsize + 4.0 | |
| 21 rect = pymupdf.Rect(50, 72, 400, 200) | |
| 22 | |
| 23 | |
| 24 def test_text(): | |
| 25 doc = pymupdf.open() | |
| 26 page = doc.new_page() | |
| 27 widget = pymupdf.Widget() # create a widget object | |
| 28 widget.border_color = blue # border color | |
| 29 widget.border_width = 0.3 # border width | |
| 30 widget.border_style = "d" | |
| 31 widget.border_dashes = (2, 3) | |
| 32 widget.field_name = "Textfield-1" # field name | |
| 33 widget.field_label = "arbitrary text - e.g. to help filling the field" | |
| 34 widget.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT # field type | |
| 35 widget.fill_color = gold # field background | |
| 36 widget.rect = rect # set field rectangle | |
| 37 widget.text_color = blue # rext color | |
| 38 widget.text_font = "TiRo" # use font Times-Roman | |
| 39 widget.text_fontsize = fontsize # set fontsize | |
| 40 widget.text_maxlen = 50 # restrict number of characters | |
| 41 widget.field_value = "Times-Roman" | |
| 42 page.add_widget(widget) # create the field | |
| 43 field = page.first_widget | |
| 44 assert field.field_type_string == "Text" | |
| 45 | |
| 46 | |
| 47 def test_checkbox(): | |
| 48 doc = pymupdf.open() | |
| 49 page = doc.new_page() | |
| 50 widget = pymupdf.Widget() | |
| 51 widget.border_style = "b" | |
| 52 widget.field_name = "Button-1" | |
| 53 widget.field_label = "a simple check box button" | |
| 54 widget.field_type = pymupdf.PDF_WIDGET_TYPE_CHECKBOX | |
| 55 widget.fill_color = gold | |
| 56 widget.rect = rect | |
| 57 widget.text_color = blue | |
| 58 widget.text_font = "ZaDb" | |
| 59 widget.field_value = True | |
| 60 page.add_widget(widget) # create the field | |
| 61 field = page.first_widget | |
| 62 assert field.field_type_string == "CheckBox" | |
| 63 | |
| 64 # Check #2350 - setting checkbox to readonly. | |
| 65 # | |
| 66 widget.field_flags |= pymupdf.PDF_FIELD_IS_READ_ONLY | |
| 67 widget.update() | |
| 68 path = f"{scriptdir}/test_checkbox.pdf" | |
| 69 doc.save(path) | |
| 70 | |
| 71 doc = pymupdf.open(path) | |
| 72 page = doc[0] | |
| 73 widget = page.first_widget | |
| 74 assert widget | |
| 75 assert widget.field_flags == pymupdf.PDF_FIELD_IS_READ_ONLY | |
| 76 | |
| 77 | |
| 78 def test_listbox(): | |
| 79 doc = pymupdf.open() | |
| 80 page = doc.new_page() | |
| 81 widget = pymupdf.Widget() | |
| 82 widget.field_name = "ListBox-1" | |
| 83 widget.field_label = "is not a drop down: scroll with cursor in field" | |
| 84 widget.field_type = pymupdf.PDF_WIDGET_TYPE_LISTBOX | |
| 85 widget.field_flags = pymupdf.PDF_CH_FIELD_IS_COMMIT_ON_SEL_CHANGE | |
| 86 widget.fill_color = gold | |
| 87 widget.choice_values = ( | |
| 88 "Frankfurt", | |
| 89 "Hamburg", | |
| 90 "Stuttgart", | |
| 91 "Hannover", | |
| 92 "Berlin", | |
| 93 "München", | |
| 94 "Köln", | |
| 95 "Potsdam", | |
| 96 ) | |
| 97 widget.rect = rect | |
| 98 widget.text_color = blue | |
| 99 widget.text_fontsize = fontsize | |
| 100 widget.field_value = widget.choice_values[-1] | |
| 101 print("About to add '%s'" % widget.field_name) | |
| 102 page.add_widget(widget) # create the field | |
| 103 field = page.first_widget | |
| 104 assert field.field_type_string == "ListBox" | |
| 105 | |
| 106 | |
| 107 def test_combobox(): | |
| 108 doc = pymupdf.open() | |
| 109 page = doc.new_page() | |
| 110 widget = pymupdf.Widget() | |
| 111 widget.field_name = "ComboBox-1" | |
| 112 widget.field_label = "an editable combo box ..." | |
| 113 widget.field_type = pymupdf.PDF_WIDGET_TYPE_COMBOBOX | |
| 114 widget.field_flags = ( | |
| 115 pymupdf.PDF_CH_FIELD_IS_COMMIT_ON_SEL_CHANGE | pymupdf.PDF_CH_FIELD_IS_EDIT | |
| 116 ) | |
| 117 widget.fill_color = gold | |
| 118 widget.choice_values = ( | |
| 119 "Spanien", | |
| 120 "Frankreich", | |
| 121 "Holland", | |
| 122 "Dänemark", | |
| 123 "Schweden", | |
| 124 "Norwegen", | |
| 125 "England", | |
| 126 "Polen", | |
| 127 "Russland", | |
| 128 "Italien", | |
| 129 "Portugal", | |
| 130 "Griechenland", | |
| 131 ) | |
| 132 widget.rect = rect | |
| 133 widget.text_color = blue | |
| 134 widget.text_fontsize = fontsize | |
| 135 widget.field_value = widget.choice_values[-1] | |
| 136 page.add_widget(widget) # create the field | |
| 137 field = page.first_widget | |
| 138 assert field.field_type_string == "ComboBox" | |
| 139 | |
| 140 | |
| 141 def test_text2(): | |
| 142 doc = pymupdf.open() | |
| 143 doc.new_page() | |
| 144 page = [p for p in doc.pages()][0] | |
| 145 widget = pymupdf.Widget() | |
| 146 widget.field_name = "textfield-2" | |
| 147 widget.field_label = "multi-line text with tabs is also possible!" | |
| 148 widget.field_flags = pymupdf.PDF_TX_FIELD_IS_MULTILINE | |
| 149 widget.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT | |
| 150 widget.fill_color = gray | |
| 151 widget.rect = rect | |
| 152 widget.text_color = blue | |
| 153 widget.text_font = "TiRo" | |
| 154 widget.text_fontsize = fontsize | |
| 155 widget.field_value = "This\n\tis\n\t\ta\n\t\t\tmulti-\n\t\tline\n\ttext." | |
| 156 page.add_widget(widget) # create the field | |
| 157 widgets = [w for w in page.widgets()] | |
| 158 field = widgets[0] | |
| 159 assert field.field_type_string == "Text" | |
| 160 | |
| 161 | |
| 162 def test_2333(): | |
| 163 doc = pymupdf.open(file_2333) | |
| 164 page = doc[0] | |
| 165 | |
| 166 def values(): | |
| 167 return set( | |
| 168 ( | |
| 169 doc.xref_get_key(635, "AS")[1], | |
| 170 doc.xref_get_key(636, "AS")[1], | |
| 171 doc.xref_get_key(637, "AS")[1], | |
| 172 doc.xref_get_key(638, "AS")[1], | |
| 173 doc.xref_get_key(127, "V")[1], | |
| 174 ) | |
| 175 ) | |
| 176 | |
| 177 for i, xref in enumerate((635, 636, 637, 638)): | |
| 178 w = page.load_widget(xref) | |
| 179 w.field_value = True | |
| 180 w.update() | |
| 181 assert values() == set(("/Off", f"{i}", f"/{i}")) | |
| 182 w.field_value = False | |
| 183 w.update() | |
| 184 assert values() == set(("Off", "/Off")) | |
| 185 | |
| 186 | |
| 187 def test_2411(): | |
| 188 """Add combobox values in different formats.""" | |
| 189 doc = pymupdf.open() | |
| 190 page = doc.new_page() | |
| 191 rect = pymupdf.Rect(100, 100, 300, 200) | |
| 192 | |
| 193 widget = pymupdf.Widget() | |
| 194 widget.field_flags = ( | |
| 195 pymupdf.PDF_CH_FIELD_IS_COMBO | |
| 196 | pymupdf.PDF_CH_FIELD_IS_EDIT | |
| 197 | pymupdf.PDF_CH_FIELD_IS_COMMIT_ON_SEL_CHANGE | |
| 198 ) | |
| 199 widget.field_name = "ComboBox-1" | |
| 200 widget.field_label = "an editable combo box ..." | |
| 201 widget.field_type = pymupdf.PDF_WIDGET_TYPE_COMBOBOX | |
| 202 widget.fill_color = pymupdf.pdfcolor["gold"] | |
| 203 widget.rect = rect | |
| 204 widget.choice_values = [ | |
| 205 ["Spain", "ES"], # double value as list | |
| 206 ("Italy", "I"), # double value as tuple | |
| 207 "Portugal", # single value | |
| 208 ] | |
| 209 page.add_widget(widget) | |
| 210 | |
| 211 | |
| 212 def test_2391(): | |
| 213 """Confirm that multiple times setting a checkbox to ON/True/Yes will work.""" | |
| 214 doc = pymupdf.open(f"{scriptdir}/resources/widgettest.pdf") | |
| 215 page = doc[0] | |
| 216 # its work when we update first-time | |
| 217 for field in page.widgets(types=[pymupdf.PDF_WIDGET_TYPE_CHECKBOX]): | |
| 218 field.field_value = True | |
| 219 field.update() | |
| 220 | |
| 221 for i in range(5): | |
| 222 pdfdata = doc.tobytes() | |
| 223 doc.close() | |
| 224 doc = pymupdf.open("pdf", pdfdata) | |
| 225 page = doc[0] | |
| 226 for field in page.widgets(types=[pymupdf.PDF_WIDGET_TYPE_CHECKBOX]): | |
| 227 assert field.field_value == field.on_state() | |
| 228 field_field_value = field.on_state() | |
| 229 field.update() | |
| 230 | |
| 231 | |
| 232 def test_3216(): | |
| 233 document = pymupdf.open(filename) | |
| 234 for page in document: | |
| 235 while 1: | |
| 236 w = page.first_widget | |
| 237 print(f"{w=}") | |
| 238 if not w: | |
| 239 break | |
| 240 page.delete_widget(w) | |
| 241 | |
| 242 | |
| 243 def test_add_widget(): | |
| 244 doc = pymupdf.open() | |
| 245 page = doc.new_page() | |
| 246 w = pymupdf.Widget() | |
| 247 w.field_type = pymupdf.PDF_WIDGET_TYPE_BUTTON | |
| 248 w.rect = pymupdf.Rect(5, 5, 20, 20) | |
| 249 w.field_flags = pymupdf.PDF_BTN_FIELD_IS_PUSHBUTTON | |
| 250 w.field_name = "button" | |
| 251 w.fill_color = (0, 0, 1) | |
| 252 w.script = "app.alert('Hello, PDF!');" | |
| 253 page.add_widget(w) | |
| 254 | |
| 255 | |
| 256 def test_interfield_calculation(): | |
| 257 """Confirm correct working of interfield calculations. | |
| 258 | |
| 259 We are going to create three pages with a computed result field each. | |
| 260 | |
| 261 Tests the fix for https://github.com/pymupdf/PyMuPDF/issues/3402. | |
| 262 """ | |
| 263 # Field bboxes (same on each page) | |
| 264 r1 = pymupdf.Rect(100, 100, 300, 120) | |
| 265 r2 = pymupdf.Rect(100, 130, 300, 150) | |
| 266 r3 = pymupdf.Rect(100, 180, 300, 200) | |
| 267 | |
| 268 doc = pymupdf.open() | |
| 269 pdf = pymupdf._as_pdf_document(doc) # we need underlying PDF document | |
| 270 | |
| 271 # Make PDF name object for "CO" because it is not defined in MuPDF. | |
| 272 CO_name = pymupdf.mupdf.pdf_new_name("CO") # = PDF_NAME(CO) | |
| 273 for i in range(3): | |
| 274 page = doc.new_page() | |
| 275 w = pymupdf.Widget() | |
| 276 w.field_name = f"NUM1{page.number}" | |
| 277 w.rect = r1 | |
| 278 w.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT | |
| 279 w.field_value = f"{i*100+1}" | |
| 280 w.field_flags = 2 | |
| 281 page.add_widget(w) | |
| 282 | |
| 283 w = pymupdf.Widget() | |
| 284 w.field_name = f"NUM2{page.number}" | |
| 285 w.rect = r2 | |
| 286 w.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT | |
| 287 w.field_value = "200" | |
| 288 w.field_flags = 2 | |
| 289 page.add_widget(w) | |
| 290 | |
| 291 w = pymupdf.Widget() | |
| 292 w.field_name = f"RESULT{page.number}" | |
| 293 w.rect = r3 | |
| 294 w.field_type = pymupdf.PDF_WIDGET_TYPE_TEXT | |
| 295 w.field_value = "Result?" | |
| 296 # Script that adds previous two fields. | |
| 297 w.script_calc = f"""AFSimple_Calculate("SUM", | |
| 298 new Array("NUM1{page.number}", "NUM2{page.number}"));""" | |
| 299 page.add_widget(w) | |
| 300 | |
| 301 # Access the inter-field calculation array. It contains a reference to | |
| 302 # all fields which have a JavaScript stored in their "script_calc" | |
| 303 # property, i.e. an "AA/C" entry. | |
| 304 # Every iteration adds another such field, so this array's length must | |
| 305 # always equal the loop index. | |
| 306 if i == 0: # only need to execute this on first time through | |
| 307 CO = pymupdf.mupdf.pdf_dict_getl( | |
| 308 pymupdf.mupdf.pdf_trailer(pdf), | |
| 309 pymupdf.PDF_NAME("Root"), | |
| 310 pymupdf.PDF_NAME("AcroForm"), | |
| 311 CO_name, | |
| 312 ) | |
| 313 # we confirm CO is an array of foreseeable length | |
| 314 assert pymupdf.mupdf.pdf_array_len(CO) == i + 1 | |
| 315 | |
| 316 # the xref of the i-th item must equal that of the last widget | |
| 317 assert ( | |
| 318 pymupdf.mupdf.pdf_to_num(pymupdf.mupdf.pdf_array_get(CO, i)) | |
| 319 == list(page.widgets())[-1].xref | |
| 320 ) | |
| 321 | |
| 322 | |
| 323 def test_3950(): | |
| 324 path = os.path.normpath(f'{__file__}/../../tests/resources/test_3950.pdf') | |
| 325 items = list() | |
| 326 with pymupdf.open(path) as document: | |
| 327 for page in document: | |
| 328 for widget in page.widgets(): | |
| 329 items.append(widget.field_label) | |
| 330 print(f'test_3950(): {widget.field_label=}.') | |
| 331 assert items == [ | |
| 332 '{{ named_insured }}', | |
| 333 '{{ policy_period_start_date }}', | |
| 334 '{{ policy_period_end_date }}', | |
| 335 '{{ insurance_line }}', | |
| 336 ] | |
| 337 | |
| 338 | |
| 339 def test_4004(): | |
| 340 import collections | |
| 341 | |
| 342 def get_widgets_by_name(doc): | |
| 343 """ | |
| 344 Extracts and returns a dictionary of widgets indexed by their names. | |
| 345 """ | |
| 346 widgets_by_name = collections.defaultdict(list) | |
| 347 for page_num in range(len(doc)): | |
| 348 page = doc.load_page(page_num) | |
| 349 for field in page.widgets(): | |
| 350 widgets_by_name[field.field_name].append({ | |
| 351 "page_num": page_num, | |
| 352 "widget": field | |
| 353 }) | |
| 354 return widgets_by_name | |
| 355 | |
| 356 # Open document and get widgets | |
| 357 path = os.path.normpath(f'{__file__}/../../tests/resources/test_4004.pdf') | |
| 358 doc = pymupdf.open(path) | |
| 359 widgets_by_name = get_widgets_by_name(doc) | |
| 360 | |
| 361 # Print widget information | |
| 362 for name, widgets in widgets_by_name.items(): | |
| 363 print(f"Widget Name: {name}") | |
| 364 for entry in widgets: | |
| 365 widget = entry["widget"] | |
| 366 page_num = entry["page_num"] | |
| 367 print(f" Page: {page_num + 1}, Type: {widget.field_type}, Value: {widget.field_value}, Rect: {widget.rect}") | |
| 368 | |
| 369 # Attempt to update field value | |
| 370 w = widgets_by_name["Text1"][0] | |
| 371 field = w['widget'] | |
| 372 field.value = "1234567890" | |
| 373 try: | |
| 374 field.update() | |
| 375 except Exception as e: | |
| 376 assert str(e) == 'Annot is not bound to a page' | |
| 377 | |
| 378 doc.close() | |
| 379 | |
| 380 | |
| 381 def test_4055(): | |
| 382 """Check correct setting of CheckBox "Yes" values. | |
| 383 | |
| 384 Test scope: | |
| 385 * setting on with any of 'True' / 'Yes' / built-in values works | |
| 386 * setting off with any of 'False' or 'Off' works | |
| 387 """ | |
| 388 | |
| 389 # this PDF has digits as "Yes" values. | |
| 390 doc = pymupdf.open(file_4055) | |
| 391 page = doc[0] | |
| 392 | |
| 393 # Round 1: confirm all check boxes are off | |
| 394 for w in page.widgets(types=[2]): | |
| 395 # check that this file doesn't use the "Yes" standard | |
| 396 assert w.on_state() != "Yes" | |
| 397 assert w.field_value == "Off" # all check boxes are off | |
| 398 w.field_value = w.on_state() | |
| 399 w.update() | |
| 400 | |
| 401 page = doc.reload_page(page) # reload page to make sure we start fresh | |
| 402 | |
| 403 # Round 2: confirm that fields contain the PDF's own on values | |
| 404 for w in page.widgets(types=[2]): | |
| 405 # confirm each value coincides with the "Yes" value | |
| 406 assert w.field_value == w.on_state() | |
| 407 w.field_value = False # switch to "Off" using False | |
| 408 w.update() | |
| 409 | |
| 410 page = doc.reload_page(page) | |
| 411 | |
| 412 # Round 3: confirm that 'False' achieved "Off" values | |
| 413 for w in page.widgets(types=[2]): | |
| 414 assert w.field_value == "Off" | |
| 415 w.field_value = True # use True for the next round | |
| 416 w.update() | |
| 417 | |
| 418 page = doc.reload_page(page) | |
| 419 | |
| 420 # Round 4: confirm that setting to True also worked | |
| 421 for w in page.widgets(types=[2]): | |
| 422 assert w.field_value == w.on_state() | |
| 423 w.field_value = "Off" # set off again | |
| 424 w.update() | |
| 425 w.field_value = "Yes" | |
| 426 w.update() | |
| 427 | |
| 428 page = doc.reload_page(page) | |
| 429 | |
| 430 # Round 5: final check: setting to "Yes" also does work | |
| 431 for w in page.widgets(types=[2]): | |
| 432 assert w.field_value == w.on_state() |
