From 02c7ae22b4b1e7cc4fb70e18b208115f438f8f7b Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Thu, 16 Jun 2022 09:49:17 +0200 Subject: [PATCH 01/26] Refactor URL handling --- lib/matplotlib/backends/backend_pdf.py | 56 +++++++++++--------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 26cf26559113..db2f08cf18f5 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -250,6 +250,23 @@ def _datetime_to_pdf(d): return r +def _get_link_annotation(gc, x, y, width, height): + """ + Create a link annotation object for embedding URLs. + """ + link_annotation = { + 'Type': Name('Annot'), + 'Subtype': Name('Link'), + 'Rect': (x, y, x + width, y + height), + 'Border': [0, 0, 0], + 'A': { + 'S': Name('URI'), + 'URI': gc.get_url(), + }, + } + return link_annotation + + def pdfRepr(obj): """Map Python objects to PDF syntax.""" @@ -2154,17 +2171,8 @@ def draw_mathtext(self, gc, x, y, s, prop, angle): self._text2path.mathtext_parser.parse(s, 72, prop) if gc.get_url() is not None: - link_annotation = { - 'Type': Name('Annot'), - 'Subtype': Name('Link'), - 'Rect': (x, y, x + width, y + height), - 'Border': [0, 0, 0], - 'A': { - 'S': Name('URI'), - 'URI': gc.get_url(), - }, - } - self.file._annotations[-1][1].append(link_annotation) + self.file._annotations[-1][1].append(_get_link_annotation( + gc, x, y, width, height)) fonttype = mpl.rcParams['pdf.fonttype'] @@ -2220,17 +2228,8 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None): page, = dvi if gc.get_url() is not None: - link_annotation = { - 'Type': Name('Annot'), - 'Subtype': Name('Link'), - 'Rect': (x, y, x + page.width, y + page.height), - 'Border': [0, 0, 0], - 'A': { - 'S': Name('URI'), - 'URI': gc.get_url(), - }, - } - self.file._annotations[-1][1].append(link_annotation) + self.file._annotations[-1][1].append(_get_link_annotation( + gc, x, y, page.width, page.height)) # Gather font information and do some setup for combining # characters into strings. The variable seq will contain a @@ -2330,17 +2329,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): if gc.get_url() is not None: font.set_text(s) width, height = font.get_width_height() - link_annotation = { - 'Type': Name('Annot'), - 'Subtype': Name('Link'), - 'Rect': (x, y, x + width / 64, y + height / 64), - 'Border': [0, 0, 0], - 'A': { - 'S': Name('URI'), - 'URI': gc.get_url(), - }, - } - self.file._annotations[-1][1].append(link_annotation) + self.file._annotations[-1][1].append(_get_link_annotation( + gc, x, y, width / 64, height / 64)) # If fonttype is neither 3 nor 42, emit the whole string at once # without manual kerning. From afdda57db3587df235ec263d4c2a088d3d514219 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 10:25:20 +0100 Subject: [PATCH 02/26] Init Solving #23205 --- lib/matplotlib/backends/backend_pdf.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index db2f08cf18f5..a3ee25670b35 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -249,15 +249,21 @@ def _datetime_to_pdf(d): r += "-%02d'%02d'" % (z // 3600, z % 3600) return r +def _get_coordinated_from_angle(x, y, width, height, phi = 0): + """ + Calculate the coordinates of the URL-active area + and take an angle of rotation into account. + """ + return (x, y, x + math.cos(phi + math.atan(height / width)), math.sin(phi + math.atan(height / width))) -def _get_link_annotation(gc, x, y, width, height): +def _get_link_annotation(gc, x, y, width, height, angle = 0): """ Create a link annotation object for embedding URLs. """ link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), - 'Rect': (x, y, x + width, y + height), + 'Rect': _get_coordinated_from_angle(x, y, width, height, angle), 'Border': [0, 0, 0], 'A': { 'S': Name('URI'), @@ -2172,7 +2178,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle): if gc.get_url() is not None: self.file._annotations[-1][1].append(_get_link_annotation( - gc, x, y, width, height)) + gc, x, y, width, height, angle)) fonttype = mpl.rcParams['pdf.fonttype'] @@ -2229,7 +2235,7 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None): if gc.get_url() is not None: self.file._annotations[-1][1].append(_get_link_annotation( - gc, x, y, page.width, page.height)) + gc, x, y, page.width, page.height, angle)) # Gather font information and do some setup for combining # characters into strings. The variable seq will contain a @@ -2330,7 +2336,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): font.set_text(s) width, height = font.get_width_height() self.file._annotations[-1][1].append(_get_link_annotation( - gc, x, y, width / 64, height / 64)) + gc, x, y, width / 64, height / 64, angle)) # If fonttype is neither 3 nor 42, emit the whole string at once # without manual kerning. From d5dda495103f9116f6a4753b27f7ead3f6ad2e4d Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 10:43:04 +0100 Subject: [PATCH 03/26] Fixed lint issues Fixed errors thrown by flake8 --- lib/matplotlib/backends/backend_pdf.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index a3ee25670b35..6de65b2853ea 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -249,14 +249,17 @@ def _datetime_to_pdf(d): r += "-%02d'%02d'" % (z // 3600, z % 3600) return r -def _get_coordinated_from_angle(x, y, width, height, phi = 0): + +def _get_coordinated_from_angle(x, y, width, height, angle=0): """ Calculate the coordinates of the URL-active area and take an angle of rotation into account. """ - return (x, y, x + math.cos(phi + math.atan(height / width)), math.sin(phi + math.atan(height / width))) + return (x, y, x + math.cos(angle + math.atan(height / width)), + math.sin(angle + math.atan(height / width))) + -def _get_link_annotation(gc, x, y, width, height, angle = 0): +def _get_link_annotation(gc, x, y, width, height, angle=0): """ Create a link annotation object for embedding URLs. """ From 7620ed8fb72734b29dc99299229e27b212aaa3ed Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 13:54:59 +0100 Subject: [PATCH 04/26] Calculates vertices Created a new function that calculates the vertices of the rectangle rotated around x,y by angle. Changed spelling of _get_coordinated_from_angle --- lib/matplotlib/backends/backend_pdf.py | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 6de65b2853ea..166b3eaaa7c5 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -250,13 +250,33 @@ def _datetime_to_pdf(d): return r -def _get_coordinated_from_angle(x, y, width, height, angle=0): +def _calculate_quad_point_coordinates(x, y, width, height, angle=0): + """ + Uses matrix maths to calculate the coordinates of a + rectangle when rotated by angle around x, y + """ + + angle = math.radians(angle) + a = x - x * math.cos(angle) + (height - y) * math.sin(angle) + b = y + x * math.sin(angle) + (height - y) * math.cos(angle) + c = x + (width - x) * math.cos(angle) + (height - y) * math.sin(angle) + d = y - (width - x) * math.sin(angle) + (height - y) * math.cos(angle) + e = x + (width - x) * math.cos(angle) - y * math.sin(angle) + f = y - (width - x) * math.sin(angle) - y * math.cos(angle) + return ((a, b), (c, d), (e, f)) + + +def _get_coordinates_from_angle(x, y, width, height, angle=0): """ Calculate the coordinates of the URL-active area and take an angle of rotation into account. """ - return (x, y, x + math.cos(angle + math.atan(height / width)), - math.sin(angle + math.atan(height / width))) + + hypot = math.hypot(width, height) + angle = math.radians(angle) + + return (x, y, x + hypot * math.cos(angle + math.atan(height / width)), + hypot * math.sin(angle + math.atan(height / width))) def _get_link_annotation(gc, x, y, width, height, angle=0): @@ -266,7 +286,9 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), - 'Rect': _get_coordinated_from_angle(x, y, width, height, angle), + 'Rect': _get_coordinates_from_angle(x, y, width, height, angle), + 'QuadPoint': _calculate_quad_point_coordinates(x, y, width, + height, angle), 'Border': [0, 0, 0], 'A': { 'S': Name('URI'), From 13b48f7acf200e44df82e5bfcb953d13691f0568 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:02:42 +0100 Subject: [PATCH 05/26] Vertices of outlining rectangle A rectangle that surrounds the URL text is calculated --- lib/matplotlib/backends/backend_pdf.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 166b3eaaa7c5..6aaee8e31dff 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -266,17 +266,20 @@ def _calculate_quad_point_coordinates(x, y, width, height, angle=0): return ((a, b), (c, d), (e, f)) -def _get_coordinates_from_angle(x, y, width, height, angle=0): +def _get_coordinates_of_block(x, y, width, height, angle=0): """ - Calculate the coordinates of the URL-active area - and take an angle of rotation into account. + Get the coordinates of a rectange that contains the URL text. + This is for use in PDF < v1.6 """ - hypot = math.hypot(width, height) - angle = math.radians(angle) + vertices = _calculate_quad_point_coordinates(x, y, width, + height, angle) - return (x, y, x + hypot * math.cos(angle + math.atan(height / width)), - hypot * math.sin(angle + math.atan(height / width))) + min_x = min(vertices[0][0], vertices[1][0], vertices[2][0], vertices[3][0]) + min_y = min(vertices[0][1], vertices[1][1], vertices[2][1], vertices[3][1]) + max_x = max(vertices[0][0], vertices[1][0], vertices[2][0], vertices[3][0]) + max_y = max(vertices[0][1], vertices[1][1], vertices[2][1], vertices[3][1]) + return (min_x, min_y, max_x, max_y) def _get_link_annotation(gc, x, y, width, height, angle=0): @@ -286,7 +289,7 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), - 'Rect': _get_coordinates_from_angle(x, y, width, height, angle), + 'Rect': _get_coordinates_of_block(x, y, width, height, angle), 'QuadPoint': _calculate_quad_point_coordinates(x, y, width, height, angle), 'Border': [0, 0, 0], From 8ef374055f121ff0e128579d8a957042db1efc53 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 15:47:33 +0100 Subject: [PATCH 06/26] Correct trigonometry Realised a mistake in my equations --- lib/matplotlib/backends/backend_pdf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 6aaee8e31dff..c3e0397e05e4 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -257,12 +257,12 @@ def _calculate_quad_point_coordinates(x, y, width, height, angle=0): """ angle = math.radians(angle) - a = x - x * math.cos(angle) + (height - y) * math.sin(angle) - b = y + x * math.sin(angle) + (height - y) * math.cos(angle) - c = x + (width - x) * math.cos(angle) + (height - y) * math.sin(angle) - d = y - (width - x) * math.sin(angle) + (height - y) * math.cos(angle) - e = x + (width - x) * math.cos(angle) - y * math.sin(angle) - f = y - (width - x) * math.sin(angle) - y * math.cos(angle) + a = x + height * math.sin(angle) + b = y + height * math.cos(angle) + c = x + width * math.cos(angle) + height * math.sin(angle) + d = y - width * math.sin(angle) + height * math.cos(angle) + e = x + width * math.cos(angle) + f = y - width * math.sin(angle) return ((a, b), (c, d), (e, f)) From ed8b1ca2e954ff230008f7b0f27757d19f200b69 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:02:51 +0100 Subject: [PATCH 07/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index c3e0397e05e4..17345ffe90c0 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -263,7 +263,7 @@ def _calculate_quad_point_coordinates(x, y, width, height, angle=0): d = y - width * math.sin(angle) + height * math.cos(angle) e = x + width * math.cos(angle) f = y - width * math.sin(angle) - return ((a, b), (c, d), (e, f)) + return ((x, y), (a, b), (c, d), (e, f)) def _get_coordinates_of_block(x, y, width, height, angle=0): From c47e089fede75fe53125c0fdafcf2b0c532cb23c Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:03:05 +0100 Subject: [PATCH 08/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 17345ffe90c0..143cdc3b985b 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -279,7 +279,7 @@ def _get_coordinates_of_block(x, y, width, height, angle=0): min_y = min(vertices[0][1], vertices[1][1], vertices[2][1], vertices[3][1]) max_x = max(vertices[0][0], vertices[1][0], vertices[2][0], vertices[3][0]) max_y = max(vertices[0][1], vertices[1][1], vertices[2][1], vertices[3][1]) - return (min_x, min_y, max_x, max_y) + return vertices, (min_x, min_y, max_x, max_y) def _get_link_annotation(gc, x, y, width, height, angle=0): From 31f9b3b5eead1ea64b83ff58e63391b47e9bb15d Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:03:33 +0100 Subject: [PATCH 09/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 143cdc3b985b..ad47693ae9bd 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -286,6 +286,7 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): """ Create a link annotation object for embedding URLs. """ + quadpoints, rect = _get_coordinates_of_block(x, y, width, height, angle) link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), From 6da81cd4822eb973547a52bc8cb699fe7ab5f5f7 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:03:42 +0100 Subject: [PATCH 10/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index ad47693ae9bd..082570dd0205 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -290,9 +290,8 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), - 'Rect': _get_coordinates_of_block(x, y, width, height, angle), - 'QuadPoint': _calculate_quad_point_coordinates(x, y, width, - height, angle), + 'Rect': rect, + 'QuadPoint': quadpoint, 'Border': [0, 0, 0], 'A': { 'S': Name('URI'), From 9eae98615f5fda768ab39f6b230f3792c51595e0 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:07:05 +0100 Subject: [PATCH 11/26] Changed variable name Corrected variable name --- lib/matplotlib/backends/backend_pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 082570dd0205..df1d4555b249 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -291,7 +291,7 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): 'Type': Name('Annot'), 'Subtype': Name('Link'), 'Rect': rect, - 'QuadPoint': quadpoint, + 'QuadPoint': quadpoints, 'Border': [0, 0, 0], 'A': { 'S': Name('URI'), From 0ce7e879b265b85b7ef96b7510a6f04f82a1ba51 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 17:01:06 +0100 Subject: [PATCH 12/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index df1d4555b249..c4099bddc823 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -275,10 +275,10 @@ def _get_coordinates_of_block(x, y, width, height, angle=0): vertices = _calculate_quad_point_coordinates(x, y, width, height, angle) - min_x = min(vertices[0][0], vertices[1][0], vertices[2][0], vertices[3][0]) - min_y = min(vertices[0][1], vertices[1][1], vertices[2][1], vertices[3][1]) - max_x = max(vertices[0][0], vertices[1][0], vertices[2][0], vertices[3][0]) - max_y = max(vertices[0][1], vertices[1][1], vertices[2][1], vertices[3][1]) + min_x = min(v[0] for v in vertices) + min_y = min(v[1] for v in vertices) + max_x = max(v[0] for v in vertices) + max_y = max(v[1] for v in vertices) return vertices, (min_x, min_y, max_x, max_y) From 760267b36c6aaf6c0cdea65848bda8aee7eb3467 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Thu, 16 Jun 2022 19:29:27 +0100 Subject: [PATCH 13/26] Spellcheck error Changed spelling in comment --- lib/matplotlib/backends/backend_pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index c4099bddc823..e790bb943e72 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -268,7 +268,7 @@ def _calculate_quad_point_coordinates(x, y, width, height, angle=0): def _get_coordinates_of_block(x, y, width, height, angle=0): """ - Get the coordinates of a rectange that contains the URL text. + Get the coordinates of a rectangle that contains the URL text. This is for use in PDF < v1.6 """ From a99c3ec5c8f18e2df7ad4ad18e1a3dca6dc9ae9e Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Fri, 17 Jun 2022 11:18:48 +0100 Subject: [PATCH 14/26] Added negative sign and compute sin and cos once --- lib/matplotlib/backends/backend_pdf.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index e790bb943e72..f4997ac8ea3f 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -256,13 +256,15 @@ def _calculate_quad_point_coordinates(x, y, width, height, angle=0): rectangle when rotated by angle around x, y """ - angle = math.radians(angle) - a = x + height * math.sin(angle) - b = y + height * math.cos(angle) - c = x + width * math.cos(angle) + height * math.sin(angle) - d = y - width * math.sin(angle) + height * math.cos(angle) - e = x + width * math.cos(angle) - f = y - width * math.sin(angle) + angle = math.radians(-angle) + sin_angle = math.sin(angle) + cos_angle = math.cos(angle) + a = x + height * sin_angle + b = y + height * cos_angle + c = x + width * cos_angle + height * sin_angle + d = y - width * sin_angle + height * cos_angle + e = x + width * cos_angle + f = y - width * sin_angle return ((x, y), (a, b), (c, d), (e, f)) From 336c070b6f31934ff26bc776277f810fc6216728 Mon Sep 17 00:00:00 2001 From: eindH Date: Fri, 17 Jun 2022 18:23:16 +0100 Subject: [PATCH 15/26] Added tests --- lib/matplotlib/tests/test_backend_pdf.py | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 5c77ffa2a740..8ad6c24d543a 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -247,6 +247,32 @@ def test_text_urls(): assert annot.Rect[1] == decimal.Decimal(y) * 72 +def test_text_rotated_urls(): + pikepdf = pytest.importorskip('pikepdf') + + test_url = 'https://test_text_urls.matplotlib.org/' + + fig = plt.figure(figsize=(2, 1)) + text = fig.text(0.1, 0.1, 'test plain 123', rotation=45, url=f'{test_url}') + + with io.BytesIO() as fd: + fig.savefig(fd, format='pdf') + + with pikepdf.Pdf.open(fd) as pdf: + annots = pdf.pages[0].Annots + + # Iteration over Annots must occur within the context manager, + # otherwise it may fail depending on the pdf structure. + annot = next( + (a for a in annots if a.A.URI == f'{test_url}'), + None) + assert annot is not None + # Positions in points (72 per inch.) Figure is 2 inches + assert annot.QuadPoint[0][0] / 72 / 2 == pytest.approx(decimal.Decimal('0.1'), abs=1e-1) + assert annot.QuadPoint[1][0] / 72 / 2 == pytest.approx(decimal.Decimal('0.08'), abs=1e-1) + assert annot.Rect[0] == annot.QuadPoint[1][0] + + @needs_usetex def test_text_urls_tex(): pikepdf = pytest.importorskip('pikepdf') From 4c193bc87e765efd87151d98624bd3d73d5771f0 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Fri, 17 Jun 2022 18:28:37 +0100 Subject: [PATCH 16/26] Lint errors --- lib/matplotlib/tests/test_backend_pdf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 8ad6c24d543a..678eaf7aa519 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -268,8 +268,10 @@ def test_text_rotated_urls(): None) assert annot is not None # Positions in points (72 per inch.) Figure is 2 inches - assert annot.QuadPoint[0][0] / 72 / 2 == pytest.approx(decimal.Decimal('0.1'), abs=1e-1) - assert annot.QuadPoint[1][0] / 72 / 2 == pytest.approx(decimal.Decimal('0.08'), abs=1e-1) + assert annot.QuadPoint[0][0] / 72 / 2 == \ + pytest.approx(decimal.Decimal('0.1'), abs=1e-1) + assert annot.QuadPoint[1][0] / 72 / 2 == \ + pytest.approx(decimal.Decimal('0.08'), abs=1e-1) assert annot.Rect[0] == annot.QuadPoint[1][0] From 076fc32a1b93cf51b8588d480600b9904b5e73fe Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Fri, 17 Jun 2022 18:31:33 +0100 Subject: [PATCH 17/26] Lint --- lib/matplotlib/tests/test_backend_pdf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 678eaf7aa519..3bd8eaafec03 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -253,8 +253,8 @@ def test_text_rotated_urls(): test_url = 'https://test_text_urls.matplotlib.org/' fig = plt.figure(figsize=(2, 1)) - text = fig.text(0.1, 0.1, 'test plain 123', rotation=45, url=f'{test_url}') - + fig.text(0.1, 0.1, 'test plain 123', rotation=45, url=f'{test_url}') + with io.BytesIO() as fd: fig.savefig(fd, format='pdf') From 4b3fa6b2f79e6c84960d6594e6f86f3c12ae02b1 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 11:22:33 +0100 Subject: [PATCH 18/26] New feature documentation --- doc/users/next_whats_new/url_active_areas_rotate.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/users/next_whats_new/url_active_areas_rotate.rst diff --git a/doc/users/next_whats_new/url_active_areas_rotate.rst b/doc/users/next_whats_new/url_active_areas_rotate.rst new file mode 100644 index 000000000000..26b10c960c7c --- /dev/null +++ b/doc/users/next_whats_new/url_active_areas_rotate.rst @@ -0,0 +1,5 @@ +The active URL area rotates when link text is rotated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When link text is rotated in a matplotlib figure, the active URL +area will now include the link area. Previously, the active area +remained in the original, non-rotated, position. \ No newline at end of file From 467a876dcf6cc6b07f651995d479ba6a706cc492 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 11:33:22 +0100 Subject: [PATCH 19/26] Lint --- doc/users/next_whats_new/url_active_areas_rotate.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/users/next_whats_new/url_active_areas_rotate.rst b/doc/users/next_whats_new/url_active_areas_rotate.rst index 26b10c960c7c..6c6d29be86fa 100644 --- a/doc/users/next_whats_new/url_active_areas_rotate.rst +++ b/doc/users/next_whats_new/url_active_areas_rotate.rst @@ -1,5 +1,5 @@ The active URL area rotates when link text is rotated ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When link text is rotated in a matplotlib figure, the active URL -area will now include the link area. Previously, the active area -remained in the original, non-rotated, position. \ No newline at end of file +When link text is rotated in a matplotlib figure, the active URL +area will now include the link area. Previously, the active area +remained in the original, non-rotated, position. From 649779b97e1b7012edfbc5929ede905307d81b1f Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Sat, 18 Jun 2022 12:47:20 +0200 Subject: [PATCH 20/26] Fixed tests --- lib/matplotlib/backends/backend_pdf.py | 37 +++++++++++++++--------- lib/matplotlib/tests/test_backend_pdf.py | 14 ++++----- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index f4997ac8ea3f..2a57f853072d 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -252,8 +252,7 @@ def _datetime_to_pdf(d): def _calculate_quad_point_coordinates(x, y, width, height, angle=0): """ - Uses matrix maths to calculate the coordinates of a - rectangle when rotated by angle around x, y + Calculate the coordinates of rectangle when rotated by angle around x, y """ angle = math.radians(-angle) @@ -265,41 +264,51 @@ def _calculate_quad_point_coordinates(x, y, width, height, angle=0): d = y - width * sin_angle + height * cos_angle e = x + width * cos_angle f = y - width * sin_angle - return ((x, y), (a, b), (c, d), (e, f)) + return ((x, y), (e, f), (c, d), (a, b)) def _get_coordinates_of_block(x, y, width, height, angle=0): """ - Get the coordinates of a rectangle that contains the URL text. - This is for use in PDF < v1.6 + Get the coordinates of rotated rectangle and rectangle that covers the + rotated rectangle. """ vertices = _calculate_quad_point_coordinates(x, y, width, - height, angle) + height, angle) - min_x = min(v[0] for v in vertices) - min_y = min(v[1] for v in vertices) - max_x = max(v[0] for v in vertices) - max_y = max(v[1] for v in vertices) - return vertices, (min_x, min_y, max_x, max_y) + # Find min and max values for rectangle + # adjust so that QuadPoints is inside Rect + # PDF docs says that QuadPoints should be ignored if any point lies + # outside Rect, but for Acrobat it is enough that QuadPoints is on the + # border of Rect. + + min_x = min(v[0] for v in vertices) - 0.00001 + min_y = min(v[1] for v in vertices) - 0.00001 + max_x = max(v[0] for v in vertices) + 0.00001 + max_y = max(v[1] for v in vertices) + 0.00001 + return (tuple(itertools.chain.from_iterable(vertices)), + (min_x, min_y, max_x, max_y)) def _get_link_annotation(gc, x, y, width, height, angle=0): """ Create a link annotation object for embedding URLs. """ - quadpoints, rect = _get_coordinates_of_block(x, y, width, height, angle) link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), - 'Rect': rect, - 'QuadPoint': quadpoints, + 'Rect': [x, y, x + width, y + height], 'Border': [0, 0, 0], 'A': { 'S': Name('URI'), 'URI': gc.get_url(), }, } + if angle % 90: + # Get QuadPoints and new rect + quadpoints, rect = _get_coordinates_of_block(x, y, width, + height, angle) + link_annotation.update({'Rect': rect, 'QuadPoints': quadpoints}) return link_annotation diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 3bd8eaafec03..dd62d2d92f8c 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -243,6 +243,7 @@ def test_text_urls(): (a for a in annots if a.A.URI == f'{test_url}{fragment}'), None) assert annot is not None + assert getattr(annot, 'QuadPoints', None) is None # Positions in points (72 per inch.) assert annot.Rect[1] == decimal.Decimal(y) * 72 @@ -252,8 +253,8 @@ def test_text_rotated_urls(): test_url = 'https://test_text_urls.matplotlib.org/' - fig = plt.figure(figsize=(2, 1)) - fig.text(0.1, 0.1, 'test plain 123', rotation=45, url=f'{test_url}') + fig = plt.figure(figsize=(1, 1)) + fig.text(0.1, 0.1, 'N', rotation=45, url=f'{test_url}') with io.BytesIO() as fd: fig.savefig(fd, format='pdf') @@ -267,12 +268,9 @@ def test_text_rotated_urls(): (a for a in annots if a.A.URI == f'{test_url}'), None) assert annot is not None - # Positions in points (72 per inch.) Figure is 2 inches - assert annot.QuadPoint[0][0] / 72 / 2 == \ - pytest.approx(decimal.Decimal('0.1'), abs=1e-1) - assert annot.QuadPoint[1][0] / 72 / 2 == \ - pytest.approx(decimal.Decimal('0.08'), abs=1e-1) - assert annot.Rect[0] == annot.QuadPoint[1][0] + assert getattr(annot, 'QuadPoints', None) is not None + # Positions in points (72 per inch) + assert annot.Rect[0] == annot.QuadPoints[6] - decimal.Decimal('0.00001') @needs_usetex From 8c97b615341eaa215f7193e35c88d2355bb9a161 Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:09:43 +0100 Subject: [PATCH 21/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 2a57f853072d..eef5d6561e44 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -282,10 +282,11 @@ def _get_coordinates_of_block(x, y, width, height, angle=0): # outside Rect, but for Acrobat it is enough that QuadPoints is on the # border of Rect. - min_x = min(v[0] for v in vertices) - 0.00001 - min_y = min(v[1] for v in vertices) - 0.00001 - max_x = max(v[0] for v in vertices) + 0.00001 - max_y = max(v[1] for v in vertices) + 0.00001 + pad = 0.00001 if angle % 90 else 0 + min_x = min(v[0] for v in vertices) - pad + min_y = min(v[1] for v in vertices) - pad + max_x = max(v[0] for v in vertices) + pad + max_y = max(v[1] for v in vertices) + pad return (tuple(itertools.chain.from_iterable(vertices)), (min_x, min_y, max_x, max_y)) From ff0315295b2414e7b550f0bddad5f997a9204add Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:10:35 +0100 Subject: [PATCH 22/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index eef5d6561e44..78ae86cbb19f 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -295,6 +295,7 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): """ Create a link annotation object for embedding URLs. """ + quadpoints, rect = _get_coordinates_of_block(x, y, width, height, angle) link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), From d9cbfbc7e108b3155355a26be47db3e7fd7e757f Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:13:30 +0100 Subject: [PATCH 23/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 78ae86cbb19f..d2a354df1c4b 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -299,7 +299,7 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): link_annotation = { 'Type': Name('Annot'), 'Subtype': Name('Link'), - 'Rect': [x, y, x + width, y + height], + 'Rect': rect, 'Border': [0, 0, 0], 'A': { 'S': Name('URI'), From e2b8ca06c295c75aaac6cb03c1818d5a7f3db7eb Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:13:51 +0100 Subject: [PATCH 24/26] Update lib/matplotlib/backends/backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/backends/backend_pdf.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index d2a354df1c4b..c3cdc6ebfbee 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -307,10 +307,8 @@ def _get_link_annotation(gc, x, y, width, height, angle=0): }, } if angle % 90: - # Get QuadPoints and new rect - quadpoints, rect = _get_coordinates_of_block(x, y, width, - height, angle) - link_annotation.update({'Rect': rect, 'QuadPoints': quadpoints}) + # Add QuadPoints + link_annotation['QuadPoints'] = quadpoints return link_annotation From c9b4f262b5a70b550a8acc3ccb88b2b72638790f Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:14:28 +0100 Subject: [PATCH 25/26] Update lib/matplotlib/tests/test_backend_pdf.py Co-authored-by: Oscar Gustafsson --- lib/matplotlib/tests/test_backend_pdf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index dd62d2d92f8c..80f63133ce7d 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -270,7 +270,8 @@ def test_text_rotated_urls(): assert annot is not None assert getattr(annot, 'QuadPoints', None) is not None # Positions in points (72 per inch) - assert annot.Rect[0] == annot.QuadPoints[6] - decimal.Decimal('0.00001') + assert annot.Rect[0] == \ + annot.QuadPoints[6] - decimal.Decimal('0.00001') @needs_usetex From 76005216747d4b734d803b7411dc9a1dde2d805c Mon Sep 17 00:00:00 2001 From: eindH <32923596+eindH@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:18:31 +0100 Subject: [PATCH 26/26] Lint --- lib/matplotlib/backends/backend_pdf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index c3cdc6ebfbee..916cfb2280f4 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -282,11 +282,11 @@ def _get_coordinates_of_block(x, y, width, height, angle=0): # outside Rect, but for Acrobat it is enough that QuadPoints is on the # border of Rect. - pad = 0.00001 if angle % 90 else 0 - min_x = min(v[0] for v in vertices) - pad - min_y = min(v[1] for v in vertices) - pad - max_x = max(v[0] for v in vertices) + pad - max_y = max(v[1] for v in vertices) + pad + pad = 0.00001 if angle % 90 else 0 + min_x = min(v[0] for v in vertices) - pad + min_y = min(v[1] for v in vertices) - pad + max_x = max(v[0] for v in vertices) + pad + max_y = max(v[1] for v in vertices) + pad return (tuple(itertools.chain.from_iterable(vertices)), (min_x, min_y, max_x, max_y))