8000 Correct URL area with rotated texts in PDFs by eindH · Pull Request #23288 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content
Dismiss alert

Correct URL area with rotated texts in PDFs #23288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
02c7ae2
Refactor URL handling
oscargus Jun 16, 2022
afdda57
Init
eindH Jun 16, 2022
d5dda49
Fixed lint issues
eindH Jun 16, 2022
7620ed8
Calculates vertices
eindH Jun 16, 2022
13b48f7
Vertices of outlining rectangle
eindH Jun 16, 2022
8ef3740
Correct trigonometry
eindH Jun 16, 2022
ed8b1ca
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 16, 2022
c47e089
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 16, 2022
31f9b3b
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 16, 2022
6da81cd
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 16, 2022
9eae986
Changed variable name
eindH Jun 16, 2022
0ce7e87
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 16, 2022
760267b
Spellcheck error
eindH Jun 16, 2022
a99c3ec
Added negative sign and compute sin and cos once
eindH Jun 17, 2022
336c070
Added tests
Jun 17, 2022
4c193bc
Lint errors
eindH Jun 17, 2022
076fc32
Lint
eindH Jun 17, 2022
4b3fa6b
New feature documentation
eindH Jun 18, 2022
467a876
Lint
eindH Jun 18, 2022
649779b
Fixed tests
oscargus Jun 18, 2022
0e72d7a
Merge pull request #1 from oscargus/urlrotationfixes
eindH Jun 18, 2022
8c97b61
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 18, 2022
ff03152
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 18, 2022
d9cbfbc
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 18, 2022
e2b8ca0
Update lib/matplotlib/backends/backend_pdf.py
eindH Jun 18, 2022
c9b4f26
Update lib/matplotlib/tests/test_backend_pdf.py
eindH Jun 18, 2022
7600521
Lint
eindH Jun 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/users/next_whats_new/url_active_areas_rotate.rst
Original file line number Diff line number Diff line change
@@ -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.
101 changes: 68 additions & 33 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,68 @@ def _datetime_to_pdf(d):
return r


def _calculate_quad_point_coordinates(x, y, width, height, angle=0):
"""
Calculate the coordinates of rectangle when rotated by angle around x, y
"""

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), (e, f), (c, d), (a, b))


def _get_coordinates_of_block(x, y, width, height, angle=0):
"""
Get the coordinates of rotated rectangle and rectangle that covers the
rotated rectangle.
"""

vertices = _calculate_quad_point_coordinates(x, y, width,
height, angle)

# 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.

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))


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,
'Border': [0, 0, 0],
'A': {
'S': Name('URI'),
'URI': gc.get_url(),
},
}
if angle % 90:
# Add QuadPoints
link_annotation['QuadPoints'] = quadpoints
return link_annotation


def pdfRepr(obj):
"""Map Python objects to PDF syntax."""

Expand Down Expand Up @@ -2154,17 +2216,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, angle))

fonttype = mpl.rcParams['pdf.fonttype']

Expand Down Expand Up @@ -2220,17 +2273,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, angle))

# Gather font information and do some setup for combining
# characters into strings. The variable seq will contain a
Expand Down Expand Up @@ -2330,17 +2374,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, angle))

# If fonttype is neither 3 nor 42, emit the whole string at once
# without manual kerning.
Expand Down
27 changes: 27 additions & 0 deletions lib/matplotlib/tests/test_backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,37 @@ 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


def test_text_rotated_urls():
pikepdf = pytest.importorskip('pikepdf')

test_url = 'https://test_text_urls.matplotlib.org/'

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')

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
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
def test_text_urls_tex():
pikepdf = pytest.importorskip('pikepdf')
Expand Down
0