Skip to content

Commit b47fa78

Browse files
committed
pdf: Support setting URLs on Text objects.
1 parent 777e89c commit b47fa78

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PDF supports URLs on ``Text`` artists
2+
-------------------------------------
3+
4+
URLs on `.text.Text` artists (i.e., from `.Artist.set_url`) will now be saved
5+
in PDF files.

lib/matplotlib/backends/backend_pdf.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,19 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
21062106
width, height, descent, glyphs, rects = \
21072107
self._text2path.mathtext_parser.parse(s, 72, prop)
21082108

2109+
if gc.get_url() is not None:
2110+
link_annotation = {
2111+
'Type': Name('Annot'),
2112+
'Subtype': Name('Link'),
2113+
'Rect': (x, y, x + width, y + height),
2114+
'Border': [0, 0, 0],
2115+
'A': {
2116+
'S': Name('URI'),
2117+
'URI': gc.get_url(),
2118+
},
2119+
}
2120+
self.file._annotations[-1][1].append(link_annotation)
2121+
21092122
global_fonttype = mpl.rcParams['pdf.fonttype']
21102123

21112124
# Set up a global transformation matrix for the whole math expression
@@ -2162,6 +2175,19 @@ def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
21622175
with dviread.Dvi(dvifile, 72) as dvi:
21632176
page, = dvi
21642177

2178+
if gc.get_url() is not None:
2179+
link_annotation = {
2180+
'Type': Name('Annot'),
2181+
'Subtype': Name('Link'),
2182+
'Rect': (x, y, x + page.width, y + page.height),
2183+
'Border': [0, 0, 0],
2184+
'A': {
2185+
'S': Name('URI'),
2186+
'URI': gc.get_url(),
2187+
},
2188+
}
2189+
self.file._annotations[-1][1].append(link_annotation)
2190+
21652191
# Gather font information and do some setup for combining
21662192
# characters into strings. The variable seq will contain a
21672193
# sequence of font and text entries. A font entry is a list
@@ -2261,6 +2287,21 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
22612287
if is_opentype_cff_font(font.fname):
22622288
fonttype = 42
22632289

2290+
if gc.get_url() is not None:
2291+
font.set_text(s)
2292+
width, height = font.get_width_height()
2293+
link_annotation = {
2294+
'Type': Name('Annot'),
2295+
'Subtype': Name('Link'),
2296+
'Rect': (x, y, x + width / 64, y + height / 64),
2297+
'Border': [0, 0, 0],
2298+
'A': {
2299+
'S': Name('URI'),
2300+
'URI': gc.get_url(),
2301+
},
2302+
}
2303+
self.file._annotations[-1][1].append(link_annotation)
2304+
22642305
# If fonttype != 3 or there are no multibyte characters, emit the whole
22652306
# string at once.
22662307
if fonttype != 3 or all(ord(char) <= 255 for char in s):

lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import decimal
23
import io
34
import os
45
from pathlib import Path
@@ -212,6 +213,53 @@ def test_multipage_metadata(monkeypatch):
212213
}
213214

214215

216+
def test_text_urls():
217+
pikepdf = pytest.importorskip('pikepdf')
218+
219+
test_url = 'https://test_text_urls.matplotlib.org/'
220+
221+
fig = plt.figure(figsize=(2, 1))
222+
fig.text(0.1, 0.1, 'test plain 123', url=f'{test_url}plain')
223+
fig.text(0.1, 0.4, 'test mathtext $123$', url=f'{test_url}mathtext')
224+
225+
with io.BytesIO() as fd:
226+
fig.savefig(fd, format='pdf')
227+
228+
with pikepdf.Pdf.open(fd) as pdf:
229+
annots = pdf.pages[0].Annots
230+
231+
for y, fragment in [('0.1', 'plain'), ('0.4', 'mathtext')]:
232+
annot = next(
233+
(a for a in annots if a.A.URI == f'{test_url}{fragment}'),
234+
None)
235+
assert annot is not None
236+
# Positions in points (72 per inch.)
237+
assert annot.Rect[1] == decimal.Decimal(y) * 72
238+
239+
240+
@needs_usetex
241+
def test_text_urls_tex():
242+
pikepdf = pytest.importorskip('pikepdf')
243+
244+
test_url = 'https://test_text_urls.matplotlib.org/'
245+
246+
fig = plt.figure(figsize=(2, 1))
247+
fig.text(0.1, 0.7, 'test tex $123$', usetex=True, url=f'{test_url}tex')
248+
249+
with io.BytesIO() as fd:
250+
fig.savefig(fd, format='pdf')
251+
252+
with pikepdf.Pdf.open(fd) as pdf:
253+
annots = pdf.pages[0].Annots
254+
255+
annot = next(
256+
(a for a in annots if a.A.URI == f'{test_url}tex'),
257+
None)
258+
assert annot is not None
259+
# Positions in points (72 per inch.)
260+
assert annot.Rect[1] == decimal.Decimal('0.7') * 72
261+
262+
215263
def test_pdfpages_fspath():
216264
with PdfPages(Path(os.devnull)) as pdf:
217265
pdf.savefig(plt.figure())

0 commit comments

Comments
 (0)