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()