From 385bdbf4ed9fb2533826c9b793b4460743ee84e8 Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Fri, 9 Oct 2026 18:09:54 +0100 Subject: [PATCH 1/3] tests/test_annots.py:test_5152(): new, passes. --- tests/test_annots.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_annots.py b/tests/test_annots.py index d50f0ddcc..294a11513 100644 --- a/tests/test_annots.py +++ b/tests/test_annots.py @@ -820,3 +820,61 @@ def test_annots_removal(): a = page.add_circle_annot((100, 100, 200, 200)) _ = page.delete_annot(a) assert "Annots" not in doc.xref_get_keys(page.xref) + + +def test_5152(): + def make_page(ops): + doc = pymupdf.open() + page = doc.new_page(width=300, height=200) + page.insert_text((0, 0), "x", fontname="helv") # adds a Helvetica resource + font = page.get_fonts()[0][4] + doc.update_stream(page.get_contents()[0], ops.replace("/F1", "/" + font).encode()) + return doc, page + + def words(page): + return [(w[4], round(w[1], 1)) for w in page.get_text("words")] + + OPS = "BT /F1 12 Tf 20 150 Td 20 TL (Line1) Tj 0 -40 Td (SECRET) ' ET" + print("PyMuPDF", pymupdf.VersionBind, "MuPDF", pymupdf.mupdf_version) + + results = list() + + doc, page = make_page(OPS) + print("original: ", words(page)) + assert words(page) == [('Line1', 37.1), ('SECRET', 97.1)] + results.append(words(page)) + + page.add_redact_annot(page.search_for("SECRET")[0]) + page.apply_redactions() + print("after redaction: ", words(page), " <- SECRET should be gone") + results.append(words(page)) + #assert words(page) == [('Line1', 37.1)] + + doc, page = make_page(OPS) + page.clean_contents() + print("after clean: ", words(page), " <- positions should be unchanged") + results.append(words(page)) + #assert words(page) == [('Line1', 37.1), ('SECRET', 117.1)] + + doc, page = make_page(OPS.replace("(SECRET) '", "T* (SECRET) Tj")) + page.add_redact_annot(page.search_for("SECRET")[0]) + page.apply_redactions() + print("T* Tj equivalent: ", words(page), " <- works") + results.append(words(page)) + + expected = [ + [('Line1', 37.1), ('SECRET', 97.1)], + [('Line1', 37.1)], + [('Line1', 37.1), ('SECRET', 97.1)], + [('Line1', 37.1)], + ] + + print(f'expected') + for i in expected: + print(i) + + print(f'results') + for i in results: + print(i) + + assert results == expected From 5b1c8ee704d789b8a2e16c6d36c9ea381c7e8584 Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Fri, 9 Oct 2026 18:10:25 +0100 Subject: [PATCH 2/3] changes.txt: updated list of fixed issues after recent changes. --- changes.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index 76e79047f..5b3b46e07 100644 --- a/changes.txt +++ b/changes.txt @@ -8,6 +8,7 @@ Fixed issues: * **Fixed** `4994 `_: Dehyphenate does no longer work since 1.27.1 * **Fixed** `5054 `_: clean_contents(sanitize=True) drops glyph positioning, duplicating an overprinted leading glyph (regression in 1.28.0 from 1.27.2.3) +* **Fixed** `5055 `_: insert_file(...) breaks radio button groups * **Fixed** `5059 `_: Annotations disappear or move when using Page.remove_rotation() for 90 or 270 rotated pages * **Fixed** `5067 `_: Memory leak in extractBLOCKS (_as_blocks): text object reference never released * **Fixed** `5077 `_: Page.remove_rotation() takes minutes on pages with thousands of links @@ -16,7 +17,13 @@ Fixed issues: * **Fixed** `5101 `_: Cyclic Widget /Parent makes page operations fail with FzErrorFormat: code=7: cycle in resources * **Fixed** `5108 `_: Page.clip_to_rect() drops 'sh' (shading) operators * **Fixed** `5112 `_: Stack-based buffer overflow in JM_set_annot_callout_line +* **Fixed** `5122 `_: get_drawings() reports a wrong start point for a subpath following a re with negative width/height * **Fixed** `5125 `_: get_pixmap() never returns on a tagged PDF page with many MCIDs - pdf_lookup_mcid_in_mcids is a linear scan, making rendering O(n**2) per page +* **Fixed** `5140 `_: Page.get_label() raises IndexError when the page precedes the first label definition +* **Fixed** `5152 `_: apply_redactions() does not remove text shown with the ' operator after TL + Td (regression in 1.28) +* **Fixed** `5154 `_: insert_pdf raises ValueError: is not in list when a widget's /Parent does not list it in /Kids +* **Fixed** `5165 `_: Page.add_widget fails for radio buttons + Other: @@ -24,9 +31,10 @@ Other: * Added support for form field hierarchies. * Added support for radio button groups. * Added support for widget aliases. -* Fixed CVE-2026-82035. * Fixed builds with swig-4.5. +* Fixed CVE-2026-82035. * Fixed ignoring aspect ratio for square images. +* Fixed potential SEGV in pymupdf.Page.get_cdrawings() if callback raises exception. * Fixed use with python-3.15. * Remove the debug timing loop from `Annot.update()`. * Retrospectively marked #3478 fixed in 1.25.3. From b85efa273bba298638476367ca4850b36ee6801d Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Sat, 10 Oct 2026 08:40:09 +0100 Subject: [PATCH 3/3] tests/test_rewrite_images.py:test_5164(): avoid spurious test failures on platforms without pillow. --- tests/test_rewrite_images.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_rewrite_images.py b/tests/test_rewrite_images.py index 5622c2bb2..9169aa40e 100644 --- a/tests/test_rewrite_images.py +++ b/tests/test_rewrite_images.py @@ -1,11 +1,10 @@ import io -import pymupdf import os +import platform +import pymupdf import re import util -import PIL.Image - scriptdir = os.path.dirname(__file__) @@ -44,6 +43,14 @@ def test_4918(): def test_5164(): + if platform.system() == 'Windows' and int.bit_length(sys.maxsize+1) == 32: + # module `PIL` not available - see + # conftest.py:install_required_packages for details. + return + if os.environ.get('PYODIDE_ROOT'): + print('test_5164(): not running on Pyodide - No module named \'PIL\'.') + return + import PIL.Image def colorspace_components(doc, xref): kind, value = doc.xref_get_key(xref, "ColorSpace")