comparison tests/test_annots.py @ 3:2c135c81b16c

MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:44:09 +0200
parents 1d09e1dec1d9
children a6bc019ac0b2
comparison
equal deleted inserted replaced
0:6015a75abc2d 3:2c135c81b16c
1 # -*- coding: utf-8 -*-
2 """
3 Test PDF annotation insertions.
4 """
5
6 import os
7 import platform
8
9 import pymupdf
10 import gentle_compare
11
12
13 red = (1, 0, 0)
14 blue = (0, 0, 1)
15 gold = (1, 1, 0)
16 green = (0, 1, 0)
17 scriptdir = os.path.dirname(__file__)
18
19 displ = pymupdf.Rect(0, 50, 0, 50)
20 r = pymupdf.Rect(72, 72, 220, 100)
21 t1 = "têxt üsès Lätiñ charß,\nEUR: €, mu: µ, super scripts: ²³!"
22 rect = pymupdf.Rect(100, 100, 200, 200)
23
24
25 def test_caret():
26 doc = pymupdf.open()
27 page = doc.new_page()
28 annot = page.add_caret_annot(rect.tl)
29 assert annot.type == (14, "Caret")
30 annot.update(rotate=20)
31 page.annot_names()
32 page.annot_xrefs()
33
34
35 def test_freetext():
36 doc = pymupdf.open()
37 page = doc.new_page()
38 annot = page.add_freetext_annot(
39 rect,
40 t1,
41 fontsize=10,
42 rotate=90,
43 text_color=blue,
44 fill_color=gold,
45 align=pymupdf.TEXT_ALIGN_CENTER,
46 )
47 annot.set_border(width=0.3, dashes=[2])
48 annot.update(text_color=blue, fill_color=gold)
49 assert annot.type == (2, "FreeText")
50
51
52 def test_text():
53 doc = pymupdf.open()
54 page = doc.new_page()
55 annot = page.add_text_annot(r.tl, t1)
56 assert annot.type == (0, "Text")
57
58
59 def test_highlight():
60 doc = pymupdf.open()
61 page = doc.new_page()
62 annot = page.add_highlight_annot(rect)
63 assert annot.type == (8, "Highlight")
64
65
66 def test_underline():
67 doc = pymupdf.open()
68 page = doc.new_page()
69 annot = page.add_underline_annot(rect)
70 assert annot.type == (9, "Underline")
71
72
73 def test_squiggly():
74 doc = pymupdf.open()
75 page = doc.new_page()
76 annot = page.add_squiggly_annot(rect)
77 assert annot.type == (10, "Squiggly")
78
79
80 def test_strikeout():
81 doc = pymupdf.open()
82 page = doc.new_page()
83 annot = page.add_strikeout_annot(rect)
84 assert annot.type == (11, "StrikeOut")
85 page.delete_annot(annot)
86
87
88 def test_polyline():
89 doc = pymupdf.open()
90 page = doc.new_page()
91 rect = page.rect + (100, 36, -100, -36)
92 cell = pymupdf.make_table(rect, rows=10)
93 for i in range(10):
94 annot = page.add_polyline_annot((cell[i][0].bl, cell[i][0].br))
95 annot.set_line_ends(i, i)
96 annot.update()
97 for i, annot in enumerate(page.annots()):
98 assert annot.line_ends == (i, i)
99 assert annot.type == (7, "PolyLine")
100
101
102 def test_polygon():
103 doc = pymupdf.open()
104 page = doc.new_page()
105 annot = page.add_polygon_annot([rect.bl, rect.tr, rect.br, rect.tl])
106 assert annot.type == (6, "Polygon")
107
108
109 def test_line():
110 doc = pymupdf.open()
111 page = doc.new_page()
112 rect = page.rect + (100, 36, -100, -36)
113 cell = pymupdf.make_table(rect, rows=10)
114 for i in range(10):
115 annot = page.add_line_annot(cell[i][0].bl, cell[i][0].br)
116 annot.set_line_ends(i, i)
117 annot.update()
118 for i, annot in enumerate(page.annots()):
119 assert annot.line_ends == (i, i)
120 assert annot.type == (3, "Line")
121
122
123 def test_square():
124 doc = pymupdf.open()
125 page = doc.new_page()
126 annot = page.add_rect_annot(rect)
127 assert annot.type == (4, "Square")
128
129
130 def test_circle():
131 doc = pymupdf.open()
132 page = doc.new_page()
133 annot = page.add_circle_annot(rect)
134 assert annot.type == (5, "Circle")
135
136
137 def test_fileattachment():
138 doc = pymupdf.open()
139 page = doc.new_page()
140 annot = page.add_file_annot(rect.tl, b"just anything for testing", "testdata.txt")
141 assert annot.type == (17, "FileAttachment")
142
143
144 def test_stamp():
145 doc = pymupdf.open()
146 page = doc.new_page()
147 annot = page.add_stamp_annot(r, stamp=0)
148 assert annot.type == (13, "Stamp")
149 assert annot.info["content"] == "Approved"
150 annot_id = annot.info["id"]
151 annot_xref = annot.xref
152 page.load_annot(annot_id)
153 page.load_annot(annot_xref)
154 page = doc.reload_page(page)
155
156
157 def test_image_stamp():
158 doc = pymupdf.open()
159 page = doc.new_page()
160 filename = os.path.join(scriptdir, "resources", "nur-ruhig.jpg")
161 annot = page.add_stamp_annot(r, stamp=filename)
162 assert annot.info["content"] == "Image Stamp"
163
164
165 def test_redact1():
166 doc = pymupdf.open()
167 page = doc.new_page()
168 annot = page.add_redact_annot(r, text="Hello")
169 annot.update(
170 cross_out=True,
171 rotate=-1,
172 )
173 assert annot.type == (12, "Redact")
174 annot.get_pixmap()
175 info = annot.info
176 annot.set_info(info)
177 assert not annot.has_popup
178 annot.set_popup(r)
179 s = annot.popup_rect
180 assert s == r
181 page.apply_redactions()
182
183
184 def test_redact2():
185 """Test for keeping text and removing graphics."""
186 if not hasattr(pymupdf, "mupdf"):
187 print("Not executing 'test_redact2' in classic")
188 return
189 filename = os.path.join(scriptdir, "resources", "symbol-list.pdf")
190 doc = pymupdf.open(filename)
191 page = doc[0]
192 all_text0 = page.get_text("words")
193 page.add_redact_annot(page.rect)
194 page.apply_redactions(text=1)
195 t = page.get_text("words")
196 assert t == all_text0
197 assert not page.get_drawings()
198
199
200 def test_redact3():
201 """Test for removing text and graphics."""
202 if not hasattr(pymupdf, "mupdf"):
203 print("Not executing 'test_redact3' in classic")
204 return
205 filename = os.path.join(scriptdir, "resources", "symbol-list.pdf")
206 doc = pymupdf.open(filename)
207 page = doc[0]
208 page.add_redact_annot(page.rect)
209 page.apply_redactions()
210 assert not page.get_text("words")
211 assert not page.get_drawings()
212
213
214 def test_redact4():
215 """Test for removing text and keeping graphics."""
216 if not hasattr(pymupdf, "mupdf"):
217 print("Not executing 'test_redact4' in classic")
218 return
219 filename = os.path.join(scriptdir, "resources", "symbol-list.pdf")
220 doc = pymupdf.open(filename)
221 page = doc[0]
222 line_art = page.get_drawings()
223 page.add_redact_annot(page.rect)
224 page.apply_redactions(graphics=0)
225 assert not page.get_text("words")
226 assert line_art == page.get_drawings()
227
228
229 def test_1645():
230 '''
231 Test fix for #1645.
232 '''
233 # The expected output files assume annot_stem is 'jorj'. We need to always
234 # restore this before returning (this is checked by conftest.py).
235 annot_stem = pymupdf.JM_annot_id_stem
236 pymupdf.TOOLS.set_annot_stem('jorj')
237 try:
238 path_in = os.path.abspath( f'{__file__}/../resources/symbol-list.pdf')
239 path_expected = os.path.abspath( f'{__file__}/../../tests/resources/test_1645_expected.pdf')
240 path_out = os.path.abspath( f'{__file__}/../test_1645_out.pdf')
241 doc = pymupdf.open(path_in)
242 page = doc[0]
243 page_bounds = page.bound()
244 annot_loc = pymupdf.Rect(page_bounds.x0, page_bounds.y0, page_bounds.x0 + 75, page_bounds.y0 + 15)
245 # Check type of page.derotation_matrix - this is #2911.
246 assert isinstance(page.derotation_matrix, pymupdf.Matrix), \
247 f'Bad type for page.derotation_matrix: {type(page.derotation_matrix)=} {page.derotation_matrix=}.'
248 page.add_freetext_annot(
249 annot_loc * page.derotation_matrix,
250 "TEST",
251 fontsize=18,
252 fill_color=pymupdf.utils.getColor("FIREBRICK1"),
253 rotate=page.rotation,
254 )
255 doc.save(path_out, garbage=1, deflate=True, no_new_id=True)
256 print(f'Have created {path_out}. comparing with {path_expected}.')
257 with open( path_out, 'rb') as f:
258 out = f.read()
259 with open( path_expected, 'rb') as f:
260 expected = f.read()
261 assert out == expected, f'Files differ: {path_out} {path_expected}'
262 finally:
263 # Restore annot_stem.
264 pymupdf.TOOLS.set_annot_stem(annot_stem)
265
266 def test_1824():
267 '''
268 Test for fix for #1824: SegFault when applying redactions overlapping a
269 transparent image.
270 '''
271 path = os.path.abspath( f'{__file__}/../resources/test_1824.pdf')
272 doc=pymupdf.open(path)
273 page=doc[0]
274 page.apply_redactions()
275
276 def test_2270():
277 '''
278 https://github.com/pymupdf/PyMuPDF/issues/2270
279 '''
280 path = os.path.abspath( f'{__file__}/../../tests/resources/test_2270.pdf')
281 with pymupdf.open(path) as document:
282 for page_number, page in enumerate(document):
283 for textBox in page.annots(types=(pymupdf.PDF_ANNOT_FREE_TEXT,pymupdf.PDF_ANNOT_TEXT)):
284 print("textBox.type :", textBox.type)
285 print(f"{textBox.rect=}")
286 print("textBox.get_text('words') : ", textBox.get_text('words'))
287 print("textBox.get_text('text') : ", textBox.get_text('text'))
288 print("textBox.get_textbox(textBox.rect) : ", textBox.get_textbox(textBox.rect))
289 print("textBox.info['content'] : ", textBox.info['content'])
290 assert textBox.type == (2, 'FreeText')
291 assert textBox.get_text('words')[0][4] == 'abc123'
292 assert textBox.get_text('text') == 'abc123\n'
293 assert textBox.get_textbox(textBox.rect) == 'abc123'
294 assert textBox.info['content'] == 'abc123'
295
296 # Additional check that Annot.get_textpage() returns a
297 # TextPage that works with page.get_text() - prior to
298 # 2024-01-30 the TextPage had no `.parent` member.
299 textpage = textBox.get_textpage()
300 text = page.get_text()
301 print(f'{text=}')
302 text = page.get_text(textpage=textpage)
303 print(f'{text=}')
304 print(f'{getattr(textpage, "parent")=}')
305
306 if pymupdf.mupdf_version_tuple >= (1, 26):
307 # Check Annotation.get_textpage()'s <clip> arg.
308 clip = textBox.rect
309 clip.x1 = clip.x0 + (clip.x1 - clip.x0) / 3
310 textpage2 = textBox.get_textpage(clip=clip)
311 text = textpage2.extractText()
312 print(f'With {clip=}: {text=}')
313 assert text == 'ab\n'
314 else:
315 assert not hasattr(pymupdf.mupdf, 'FZ_STEXT_CLIP_RECT')
316
317
318 def test_2934_add_redact_annot():
319 '''
320 Test fix for bug mentioned in #2934.
321 '''
322 path = os.path.abspath(f'{__file__}/../../tests/resources/mupdf_explored.pdf')
323 with open(path, 'rb') as f:
324 data = f.read()
325 doc = pymupdf.Document(stream=data)
326 print(f'Is PDF: {doc.is_pdf}')
327 print(f'Number of pages: {doc.page_count}')
328
329 import json
330 page=doc[0]
331 page_json_str =doc[0].get_text("json")
332 page_json_data = json.loads(page_json_str)
333 span=page_json_data.get("blocks")[0].get("lines")[0].get("spans")[0]
334 page.add_redact_annot(span["bbox"], text="")
335 page.apply_redactions()
336
337 def test_2969():
338 '''
339 https://github.com/pymupdf/PyMuPDF/issues/2969
340 '''
341 path = os.path.abspath(f'{__file__}/../../tests/resources/test_2969.pdf')
342 doc = pymupdf.open(path)
343 page = doc[0]
344 first_annot = list(page.annots())[0]
345 first_annot.next
346
347 def test_file_info():
348 path = os.path.abspath(f'{__file__}/../../tests/resources/test_annot_file_info.pdf')
349 document = pymupdf.open(path)
350 results = list()
351 for i, page in enumerate(document):
352 print(f'{i=}')
353 annotations = page.annots()
354 for j, annotation in enumerate(annotations):
355 print(f'{j=} {annotation=}')
356 t = annotation.type
357 print(f'{t=}')
358 if t[0] == pymupdf.PDF_ANNOT_FILE_ATTACHMENT:
359 file_info = annotation.file_info
360 print(f'{file_info=}')
361 results.append(file_info)
362 assert results == [
363 {'filename': 'example.pdf', 'description': '', 'length': 8416, 'size': 8992},
364 {'filename': 'photo1.jpeg', 'description': '', 'length': 10154, 'size': 8012},
365 ]
366
367 def test_3131():
368 doc = pymupdf.open()
369 page = doc.new_page()
370
371 page.add_line_annot((0, 0), (1, 1))
372 page.add_line_annot((1, 0), (0, 1))
373
374 first_annot, _ = page.annots()
375 first_annot.next.type
376
377 def test_3209():
378 pdf = pymupdf.Document(filetype="pdf")
379 page = pdf.new_page()
380 page.add_ink_annot([[(300,300), (400, 380), (350, 350)]])
381 n = 0
382 for annot in page.annots():
383 n += 1
384 assert annot.vertices == [[(300.0, 300.0), (400.0, 380.0), (350.0, 350.0)]]
385 assert n == 1
386 path = os.path.abspath(f'{__file__}/../../tests/test_3209_out.pdf')
387 pdf.save(path) # Check the output PDF that the annotation is correctly drawn
388
389 def test_3863():
390 path_in = os.path.normpath(f'{__file__}/../../tests/resources/test_3863.pdf')
391 path_out = os.path.normpath(f'{__file__}/../../tests/test_3863.pdf.pdf')
392
393 # Create redacted PDF.
394 print(f'Loading {path_in=}.')
395 with pymupdf.open(path_in) as document:
396
397 for num, page in enumerate(document):
398 print(f"Page {num + 1} - {page.rect}:")
399
400 for image in page.get_images(full=True):
401 print(f" - Image: {image}")
402
403 redact_rect = page.rect
404
405 if page.rotation in (90, 270):
406 redact_rect = pymupdf.Rect(0, 0, page.rect.height, page.rect.width)
407
408 page.add_redact_annot(redact_rect)
409 page.apply_redactions(images=pymupdf.PDF_REDACT_IMAGE_NONE)
410
411 print(f'Writing to {path_out=}.')
412 document.save(path_out)
413
414 with pymupdf.open(path_out) as document:
415 assert len(document) == 8
416
417 # Create PNG for each page of redacted PDF.
418 for num, page in enumerate(document):
419 path_png = f'{path_out}.{num}.png'
420 pixmap = page.get_pixmap()
421 print(f'Writing to {path_png=}.')
422 pixmap.save(path_png)
423 # Compare with expected png.
424
425 print(f'Comparing page PNGs with expected PNGs.')
426 for num, _ in enumerate(document):
427 path_png = f'{path_out}.{num}.png'
428 path_png_expected = f'{path_in}.pdf.{num}.png'
429 print(f'{path_png=}.')
430 print(f'{path_png_expected=}.')
431 rms = gentle_compare.pixmaps_rms(path_png, path_png_expected, ' ')
432 # We get small differences in sysinstall tests, where some
433 # thirdparty libraries can differ.
434 assert rms < 1
435
436 def test_3758():
437 # This test requires input file that is not public, so is usually not
438 # available.
439 path = os.path.normpath(f'{__file__}/../../../test_3758.pdf')
440 if not os.path.exists(path):
441 print(f'test_3758(): not running because does not exist: {path=}.')
442 return
443 import json
444 with pymupdf.open(path) as document:
445 for page in document:
446 info = json.loads(page.get_text('json', flags=pymupdf.TEXTFLAGS_TEXT))
447 for block_ind, block in enumerate(info['blocks']):
448 for line_ind, line in enumerate(block['lines']):
449 for span_ind, span in enumerate(line['spans']):
450 # print(span)
451 page.add_redact_annot(pymupdf.Rect(*span['bbox']))
452 page.apply_redactions()
453 wt = pymupdf.TOOLS.mupdf_warnings()
454 assert wt
455
456
457 def test_parent():
458 """Test invalidating parent on page re-assignment."""
459 doc = pymupdf.open()
460 page = doc.new_page()
461 a = page.add_highlight_annot(page.rect) # insert annotation on page 0
462 page = doc.new_page() # make a new page, should orphanate annotation
463 try:
464 print(a) # should raise
465 except Exception as e:
466 if platform.system() == 'OpenBSD':
467 assert isinstance(e, pymupdf.mupdf.FzErrorBase), f'Incorrect {type(e)=}.'
468 else:
469 assert isinstance(e, pymupdf.mupdf.FzErrorArgument), f'Incorrect {type(e)=}.'
470 assert str(e) == 'code=4: annotation not bound to any page', f'Incorrect error text {str(e)=}.'
471 else:
472 assert 0, f'Failed to get expected exception.'
473
474 def test_4047():
475 path = os.path.normpath(f'{__file__}/../../tests/resources/test_4047.pdf')
476 with pymupdf.open(path) as document:
477 page = document[0]
478 fontname = page.get_fonts()[0][3]
479 if fontname not in pymupdf.Base14_fontnames:
480 fontname = "Courier"
481 hits = page.search_for("|")
482 for rect in hits:
483 page.add_redact_annot(
484 rect, " ", fontname=fontname, align=pymupdf.TEXT_ALIGN_CENTER, fontsize=10
485 ) # Segmentation Fault...
486 page.apply_redactions()
487
488 def test_4079():
489 path = os.path.normpath(f'{__file__}/../../tests/resources/test_4079.pdf')
490 if pymupdf.mupdf_version_tuple >= (1, 25, 5):
491 path_after = os.path.normpath(f'{__file__}/../../tests/resources/test_4079_after.pdf')
492 else:
493 # 2024-11-27 Expect incorrect behaviour.
494 path_after = os.path.normpath(f'{__file__}/../../tests/resources/test_4079_after_1.25.pdf')
495
496 path_out = os.path.normpath(f'{__file__}/../../tests/test_4079_out')
497 with pymupdf.open(path_after) as document_after:
498 page = document_after[0]
499 pixmap_after_expected = page.get_pixmap()
500 with pymupdf.open(path) as document:
501 page = document[0]
502 rects = [
503 [164,213,282,227],
504 [282,213,397,233],
505 [434,209,525,243],
506 [169,228,231,243],
507 [377,592,440,607],
508 [373,611,444,626],
509 ]
510 for rect in rects:
511 page.add_redact_annot(rect, fill=(1,0,0))
512 page.draw_rect(rect, color=(0, 1, 0))
513 document.save(f'{path_out}_before.pdf')
514 page.apply_redactions(images=0)
515 pixmap_after = page.get_pixmap()
516 document.save(f'{path_out}_after.pdf')
517 rms = gentle_compare.pixmaps_rms(pixmap_after_expected, pixmap_after)
518 diff = gentle_compare.pixmaps_diff(pixmap_after_expected, pixmap_after)
519 path = os.path.normpath(f'{__file__}/../../tests/test_4079_diff.png')
520 diff.save(path)
521 print(f'{rms=}')
522 assert rms == 0
523
524 def test_4254():
525 """Ensure that both annotations are fully created
526
527 We do this by asserting equal top-used colors in respective pixmaps.
528 """
529 doc = pymupdf.open()
530 page = doc.new_page()
531
532 rect = pymupdf.Rect(100, 100, 200, 150)
533 annot = page.add_freetext_annot(rect, "Test Annotation from minimal example")
534 annot.set_border(width=1, dashes=(3, 3))
535 annot.set_opacity(0.5)
536 try:
537 annot.set_colors(stroke=(1, 0, 0))
538 except ValueError as e:
539 assert 'cannot be used for FreeText annotations' in str(e), f'{e}'
540 else:
541 assert 0
542 annot.update()
543
544 rect = pymupdf.Rect(200, 200, 400, 400)
545 annot2 = page.add_freetext_annot(rect, "Test Annotation from minimal example pt 2")
546 annot2.set_border(width=1, dashes=(3, 3))
547 annot2.set_opacity(0.5)
548 try:
549 annot2.set_colors(stroke=(1, 0, 0))
550 except ValueError as e:
551 assert 'cannot be used for FreeText annotations' in str(e), f'{e}'
552 else:
553 assert 0
554 annot.update()
555 annot2.update()
556
557 # stores top color for each pixmap
558 top_colors = set()
559 for annot in page.annots():
560 pix = annot.get_pixmap()
561 top_colors.add(pix.color_topusage()[1])
562
563 # only one color must exist
564 assert len(top_colors) == 1
565
566 def test_richtext():
567 """Test creation of rich text FreeText annotations.
568
569 We create the same annotation on different pages in different ways,
570 with and without using Annotation.update(), and then assert equality
571 of the respective images.
572 """
573 ds = """font-size: 11pt; font-family: sans-serif;"""
574 bullet = chr(0x2610) + chr(0x2611) + chr(0x2612)
575 text = f"""<p style="text-align:justify;margin-top:-25px;">
576 PyMuPDF <span style="color: red;">འདི་ ཡིག་ཆ་བཀྲམ་སྤེལ་གྱི་དོན་ལུ་ པའི་ཐོན་ཐུམ་སྒྲིལ་དྲག་ཤོས་དང་མགྱོགས་ཤོས་ཅིག་ཨིན།</span>
577 <span style="color:blue;">Here is some <b>bold</b> and <i>italic</i> text, followed by <b><i>bold-italic</i></b>. Text-based check boxes: {bullet}.</span>
578 </p>"""
579 gold = (1, 1, 0)
580 doc = pymupdf.open()
581
582 # First page.
583 page = doc.new_page()
584 rect = pymupdf.Rect(100, 100, 350, 200)
585 p2 = rect.tr + (50, 30)
586 p3 = p2 + (0, 30)
587 annot = page.add_freetext_annot(
588 rect,
589 text,
590 fill_color=gold,
591 opacity=0.5,
592 rotate=90,
593 border_width=1,
594 dashes=None,
595 richtext=True,
596 callout=(p3, p2, rect.tr),
597 )
598
599 pix1 = page.get_pixmap()
600
601 # Second page.
602 # the annotation is created with minimal parameters, which are supplied
603 # in a separate call to the .update() method.
604 page = doc.new_page()
605 annot = page.add_freetext_annot(
606 rect,
607 text,
608 border_width=1,
609 dashes=None,
610 richtext=True,
611 callout=(p3, p2, rect.tr),
612 )
613 annot.update(fill_color=gold, opacity=0.5, rotate=90)
614 pix2 = page.get_pixmap()
615 assert pix1.samples == pix2.samples
616
617
618 def test_4447():
619 document = pymupdf.open()
620
621 page = document.new_page()
622
623 text_color = (1, 0, 0)
624 fill_color = (0, 1, 0)
625 border_color = (0, 0, 1)
626
627 annot_rect = pymupdf.Rect(90.1, 486.73, 139.26, 499.46)
628
629 try:
630 annot = page.add_freetext_annot(
631 annot_rect,
632 "AETERM",
633 fontname="Arial",
634 fontsize=10,
635 text_color=text_color,
636 fill_color=fill_color,
637 border_color=border_color,
638 border_width=1,
639 )
640 except ValueError as e:
641 assert 'cannot set border_color if rich_text is False' in str(e), str(e)
642 else:
643 assert 0
644
645 try:
646 annot = page.add_freetext_annot(
647 (30, 400, 100, 450),
648 "Two",
649 fontname="Arial",
650 fontsize=10,
651 text_color=text_color,
652 fill_color=fill_color,
653 border_color=border_color,
654 border_width=1,
655 )
656 except ValueError as e:
657 assert 'cannot set border_color if rich_text is False' in str(e), str(e)
658 else:
659 assert 0
660
661 annot = page.add_freetext_annot(
662 (30, 500, 100, 550),
663 "Three",
664 fontname="Arial",
665 fontsize=10,
666 text_color=text_color,
667 border_width=1,
668 )
669 annot.update(text_color=text_color, fill_color=fill_color)
670 try:
671 annot.update(border_color=border_color)
672 except ValueError as e:
673 assert 'cannot set border_color if rich_text is False' in str(e), str(e)
674 else:
675 assert 0
676
677 path_out = os.path.normpath(f'{__file__}/../../tests/test_4447.pdf')
678 document.save(path_out)