Mercurial > hgrepos > Python2 > PyMuPDF
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) |
