Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf6e34a6

Browse files
committed
Add URL support for images in PDF backend
1 parent51ce677 commitf6e34a6

File tree

3 files changed

+162
-28
lines changed

3 files changed

+162
-28
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
URL Support for Images in PDF Backend
2+
-------------------------------------
3+
4+
The PDF backend can now generate clickable images if an URL is provided to the
5+
image. There are a few limitations worth noting though:
6+
7+
* If parts of the image are clipped, the non-visible parts are still clickable.
8+
* If there are transforms applied to the image, the whole enclosing rectangle
9+
is clickable. However, if you use ``interpolation='none'`` for the image,
10+
only the transformed image area is clickable (depending on viewer support).

‎lib/matplotlib/backends/backend_pdf.py

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -230,32 +230,54 @@ def _datetime_to_pdf(d):
230230
returnr
231231

232232

233-
def_calculate_quad_point_coordinates(x,y,width,height,angle=0):
233+
def_calculate_rotated_quad_point_coordinates(x,y,width,height,angle):
234234
"""
235235
Calculate the coordinates of rectangle when rotated by angle around x, y
236236
"""
237237

238-
angle=math.radians(-angle)
238+
angle=math.radians(angle)
239239
sin_angle=math.sin(angle)
240240
cos_angle=math.cos(angle)
241-
a=x+height*sin_angle
241+
width_cos=width*cos_angle
242+
width_sin=width*sin_angle
243+
a=x-height*sin_angle
242244
b=y+height*cos_angle
243-
c=x+width*cos_angle+height*sin_angle
244-
d=y-width*sin_angle+height*cos_angle
245-
e=x+width*cos_angle
246-
f=y-width*sin_angle
245+
c=a+width_cos
246+
d=b+width_sin
247+
e=x+width_cos
248+
f=y+width_sin
247249
return ((x,y), (e,f), (c,d), (a,b))
248250

249251

250-
def_get_coordinates_of_block(x,y,width,height,angle=0):
252+
def_calculate_transformed_quad_point_coordinates(x,y,trans):
251253
"""
252-
Get the coordinates of rotated rectangle and rectangle that covers the
253-
rotated rectangle.
254+
Calculate the coordinates of rectangle when rotated by angle around x, y
255+
"""
256+
tr1,tr2,tr3,tr4,tr5,tr6=trans
257+
width=1
258+
height=1
259+
a=x+0*tr1+height*tr3+tr5
260+
b=y+0*tr2+height*tr4+tr6
261+
c=x+width*tr1+height*tr3+tr5
262+
d=y+width*tr2+height*tr4+tr6
263+
e=x+width*tr1+0*tr3+tr5
264+
f=y+width*tr2+0*tr4+tr6
265+
return (((x+tr5,y+tr6), (e,f), (c,d), (a,b)),
266+
(0ifmath.isclose(tr2,0)else45))
267+
268+
269+
def_get_coordinates_of_block(x,y,width,height,angle,trans):
270+
"""
271+
Get the coordinates of rotated or transformed rectangle and rectangle
272+
that covers the rotated or transformed rectangle.
254273
"""
255274

256-
vertices=_calculate_quad_point_coordinates(x,y,width,
257-
height,angle)
258-
275+
iftransisNone:
276+
vertices=_calculate_rotated_quad_point_coordinates(x,y,width,
277+
height,angle)
278+
else:
279+
vertices,angle=_calculate_transformed_quad_point_coordinates(x,y,
280+
trans)
259281
# Find min and max values for rectangle
260282
# adjust so that QuadPoints is inside Rect
261283
# PDF docs says that QuadPoints should be ignored if any point lies
@@ -268,27 +290,29 @@ def _get_coordinates_of_block(x, y, width, height, angle=0):
268290
max_x=max(v[0]forvinvertices)+pad
269291
max_y=max(v[1]forvinvertices)+pad
270292
return (tuple(itertools.chain.from_iterable(vertices)),
271-
(min_x,min_y,max_x,max_y))
293+
(min_x,min_y,max_x,max_y),angle)
272294

273295

274-
def_get_link_annotation(gc,x,y,width,height,angle=0):
296+
def_get_link_annotation(gc,x,y,width,height,angle=0,trans=None):
275297
"""
276298
Create a link annotation object for embedding URLs.
277299
"""
278-
quadpoints,rect=_get_coordinates_of_block(x,y,width,height,angle)
279300
link_annotation= {
280301
'Type':Name('Annot'),
281302
'Subtype':Name('Link'),
282-
'Rect':rect,
283303
'Border': [0,0,0],
284304
'A': {
285305
'S':Name('URI'),
286306
'URI':gc.get_url(),
287307
},
288308
}
309+
quadpoints,rect,angle=_get_coordinates_of_block(x,y,width,height,
310+
angle,trans)
311+
link_annotation['Rect']=rect
289312
ifangle%90:
290313
# Add QuadPoints
291314
link_annotation['QuadPoints']=quadpoints
315+
292316
returnlink_annotation
293317

294318

@@ -2012,21 +2036,24 @@ def draw_image(self, gc, x, y, im, transform=None):
20122036

20132037
self.check_gc(gc)
20142038

2015-
w=72.0*w/self.image_dpi
2016-
h=72.0*h/self.image_dpi
2017-
20182039
imob=self.file.imageObject(im)
20192040

20202041
iftransformisNone:
2042+
w=72.0*w/self.image_dpi
2043+
h=72.0*h/self.image_dpi
2044+
ifgc.get_url()isnotNone:
2045+
self._add_link_annotation(gc,x,y,w,h)
20212046
self.file.output(Op.gsave,
20222047
w,0,0,h,x,y,Op.concat_matrix,
20232048
imob,Op.use_xobject,Op.grestore)
20242049
else:
2025-
tr1,tr2,tr3,tr4,tr5,tr6=transform.frozen().to_values()
2050+
trans=transform.frozen().to_values()
2051+
ifgc.get_url()isnotNone:
2052+
self._add_link_annotation(gc,x,y,w,h,trans=trans)
20262053

20272054
self.file.output(Op.gsave,
20282055
1,0,0,1,x,y,Op.concat_matrix,
2029-
tr1,tr2,tr3,tr4,tr5,tr6,Op.concat_matrix,
2056+
*trans,Op.concat_matrix,
20302057
imob,Op.use_xobject,Op.grestore)
20312058

20322059
defdraw_path(self,gc,path,transform,rgbFace=None):
@@ -2038,6 +2065,11 @@ def draw_path(self, gc, path, transform, rgbFace=None):
20382065
gc.get_sketch_params())
20392066
self.file.output(self.gc.paint())
20402067

2068+
def_add_link_annotation(self,gc,x,y,width,height,angle=0,
2069+
trans=None):
2070+
self.file._annotations[-1][1].append(
2071+
_get_link_annotation(gc,x,y,width,height,angle,trans))
2072+
20412073
defdraw_path_collection(self,gc,master_transform,paths,all_transforms,
20422074
offsets,offsetTrans,facecolors,edgecolors,
20432075
linewidths,linestyles,antialiaseds,urls,
@@ -2206,8 +2238,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
22062238
self._text2path.mathtext_parser.parse(s,72,prop)
22072239

22082240
ifgc.get_url()isnotNone:
2209-
self.file._annotations[-1][1].append(_get_link_annotation(
2210-
gc,x,y,width,height,angle))
2241+
self._add_link_annotation(gc,x,y,width,height,angle)
22112242

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

@@ -2263,8 +2294,7 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
22632294
page,=dvi
22642295

22652296
ifgc.get_url()isnotNone:
2266-
self.file._annotations[-1][1].append(_get_link_annotation(
2267-
gc,x,y,page.width,page.height,angle))
2297+
self._add_link_annotation(gc,x,y,page.width,page.height,angle)
22682298

22692299
# Gather font information and do some setup for combining
22702300
# characters into strings. The variable seq will contain a
@@ -2364,8 +2394,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23642394
ifgc.get_url()isnotNone:
23652395
font.set_text(s)
23662396
width,height=font.get_width_height()
2367-
self.file._annotations[-1][1].append(_get_link_annotation(
2368-
gc,x,y,width/64,height/64,angle))
2397+
self._add_link_annotation(gc,x,y,width/64,height/64,angle)
23692398

23702399
# If fonttype is neither 3 nor 42, emit the whole string at once
23712400
# without manual kerning.

‎lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,101 @@ def test_kerning():
360360
fig.text(0,.75,s,size=20)
361361

362362

363+
deftest_image_url():
364+
pikepdf=pytest.importorskip('pikepdf')
365+
366+
image_url='https://test_image_urls.matplotlib.org/'
367+
image_url2='https://test_image_urls2.matplotlib.org/'
368+
369+
X,Y=np.meshgrid(np.arange(-5,5,1),np.arange(-5,5,1))
370+
Z=np.sin(Y**2)
371+
fig,ax=plt.subplots()
372+
ax.imshow(Z,extent=[0,1,0,1],url=image_url)
373+
withio.BytesIO()asfd:
374+
fig.savefig(fd,format="pdf")
375+
withpikepdf.Pdf.open(fd)aspdf:
376+
annots=pdf.pages[0].Annots
377+
378+
# Iteration over Annots must occur within the context manager,
379+
# otherwise it may fail depending on the pdf structure.
380+
annot=next(
381+
(aforainannotsifa.A.URI==image_url),
382+
None)
383+
assertannotisnotNone
384+
# Positions in points (72 per inch.)
385+
assertannot.Rect== [decimal.Decimal('122.4'),
386+
decimal.Decimal('43.2'),
387+
468,
388+
decimal.Decimal('388.8')]
389+
ax.set_xlim(0,3)
390+
ax.imshow(Z[::-1],extent=[2,3,0,1],url=image_url2)
391+
# Must save as separate images
392+
plt.rcParams['image.composite_image']=False
393+
withio.BytesIO()asfd:
394+
fig.savefig(fd,format="pdf")
395+
withpikepdf.Pdf.open(fd)aspdf:
396+
annots=pdf.pages[0].Annots
397+
398+
# Iteration over Annots must occur within the context manager,
399+
# otherwise it may fail depending on the pdf structure.
400+
annot=next(
401+
(aforainannotsifa.A.URI==image_url2),
402+
None)
403+
assertannotisnotNone
404+
# Positions in points (72 per inch.)
405+
assertannot.Rect== [decimal.Decimal('369.6'),
406+
decimal.Decimal('141.6'),
407+
decimal.Decimal('518.64'),
408+
decimal.Decimal('290.64')]
409+
410+
411+
deftest_transformed_image_url():
412+
pikepdf=pytest.importorskip('pikepdf')
413+
414+
image_url='https://test_image_urls.matplotlib.org/'
415+
416+
X,Y=np.meshgrid(np.arange(-5,5,1),np.arange(-5,5,1))
417+
Z=np.sin(Y**2)
418+
fig,ax=plt.subplots()
419+
im=ax.imshow(Z,interpolation='none',url=image_url)
420+
withio.BytesIO()asfd:
421+
fig.savefig(fd,format="pdf")
422+
withpikepdf.Pdf.open(fd)aspdf:
423+
annots=pdf.pages[0].Annots
424+
425+
# Iteration over Annots must occur within the context manager,
426+
# otherwise it may fail depending on the pdf structure.
427+
annot=next(
428+
(aforainannotsifa.A.URI==image_url),
429+
None)
430+
assertannotisnotNone
431+
# Positions in points (72 per inch.)
432+
assertannot.Rect== [decimal.Decimal('122.4'),
433+
decimal.Decimal('43.2'),
434+
decimal.Decimal('468.4'),
435+
decimal.Decimal('389.2')]
436+
assertgetattr(annot,'QuadPoints',None)isNone
437+
# Transform
438+
im.set_transform(mpl.transforms.Affine2D().skew_deg(30,15)+ax.transData)
439+
withio.BytesIO()asfd:
440+
fig.savefig(fd,format="pdf")
441+
withpikepdf.Pdf.open(fd)aspdf:
442+
annots=pdf.pages[0].Annots
443+
444+
# Iteration over Annots must occur within the context manager,
445+
# otherwise it may fail depending on the pdf structure.
446+
annot=next(
447+
(aforainannotsifa.A.URI==image_url),
448+
None)
449+
assertannotisnotNone
450+
# Positions in points (72 per inch.)
451+
assertannot.Rect[0]==decimal.Decimal('112.411830343')
452+
assertgetattr(annot,'QuadPoints',None)isnotNone
453+
# Positions in points (72 per inch)
454+
assertannot.Rect[0]== \
455+
annot.QuadPoints[0]-decimal.Decimal('0.00001')
456+
457+
363458
deftest_glyphs_subset():
364459
fpath=str(_get_data_path("fonts/ttf/DejaVuSerif.ttf"))
365460
chars="these should be subsetted! 1234567890"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp