From 1fa9e9e49560f1f99272142e5920831223e28a33 Mon Sep 17 00:00:00 2001 From: Hiren Date: Wed, 25 Mar 2026 19:26:58 -0400 Subject: [PATCH 1/3] fix: skip widgets with empty or infinite rects in remove_rotation remove_rotation() calls widget.update() on every widget, but widgets with empty or infinite rects (like invisible signature fields) cause ValueError('bad rect') during validation. Skip these widgets and also guard widget.update() with a try/except for any remaining edge cases. Fixes #4950 --- src/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/__init__.py b/src/__init__.py index e3845575b..550967e5d 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -11913,8 +11913,13 @@ def remove_rotation(self): pass for widget in self.widgets(): # modify field rectangles r = widget.rect * rot + if r.is_empty or r.is_infinite: + continue widget.rect = r - widget.update() + try: + widget.update() + except ValueError: + pass return rot # the inverse of the generated derotation matrix def cluster_drawings( From f31589b9ae32464fd76e9cb3f567b99f5f9a87e0 Mon Sep 17 00:00:00 2001 From: Hiren Date: Fri, 5 Jun 2026 10:04:36 -0400 Subject: [PATCH 2/3] fix: remove redundant widget update guard --- src/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 550967e5d..6484e495c 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -11916,10 +11916,7 @@ def remove_rotation(self): if r.is_empty or r.is_infinite: continue widget.rect = r - try: - widget.update() - except ValueError: - pass + widget.update() return rot # the inverse of the generated derotation matrix def cluster_drawings( From b6aedf883b0e7c8f8c0d529625ffa44e18df4734 Mon Sep 17 00:00:00 2001 From: Hiren Date: Fri, 5 Jun 2026 12:49:42 -0400 Subject: [PATCH 3/3] test: cover remove_rotation with empty signature widget --- tests/test_widgets.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 9eafd0246..00a5b4e36 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -430,3 +430,24 @@ def test_4055(): # Round 5: final check: setting to "Yes" also does work for w in page.widgets(types=[2]): assert w.field_value == w.on_state() + + +def test_4950(): + with pymupdf.open() as document: + page = document.new_page() + page.set_rotation(90) + + # Simulate an existing invisible signature field with a zero-dimension + # rectangle. Creating such a widget through add_widget() is rejected by + # validation, so create a valid widget first and then modify the PDF + # object directly. + widget = pymupdf.Widget() + widget.field_name = "Signature" + widget.field_type = pymupdf.PDF_WIDGET_TYPE_SIGNATURE + widget.rect = pymupdf.Rect(0, 0, 10, 10) + page.add_widget(widget) + document.xref_set_key(page.first_widget.xref, "Rect", "[0 0 0 0]") + page = document.reload_page(page) + + page.remove_rotation() + assert page.rotation == 0