1
2
3
4
5
6
7
8
9
10
11
12
13 import sys
14
15 from rdkit import six
16 if not six.PY3:
17 bytes = buffer
18
19
20 if six.PY3:
21 try:
22 import cairocffi as cairo
23 except ImportError:
24 import cairo
25 else:
26 try:
27 import cairo
28 except ImportError:
29 import cairocffi as cairo
30
31 if not hasattr(cairo.ImageSurface,'get_data') and \
32 not hasattr(cairo.ImageSurface,'get_data_as_rgba'):
33 raise ImportError('cairo version too old')
34
35 import math
36 import rdkit.RDConfig
37 import os,re
38 import array
39 if not 'RDK_NOPANGO' in os.environ:
40 try:
41 import pangocairo
42 except ImportError:
43 pangocairo=None
44 try:
45 import pango
46 except ImportError:
47 pango=None
48 else:
49 pango=None
50 pangocairo=None
51
52 from rdkit.Chem.Draw.canvasbase import CanvasBase
53 from PIL import Image
54
55 scriptPattern=re.compile(r'\<.+?\>')
56
58 - def __init__(self,
59 image=None,
60 size=None,
61 ctx=None,
62 imageType=None,
63 fileName=None,
64 ):
65 """
66 Canvas can be used in four modes:
67 1) using the supplied PIL image
68 2) using the supplied cairo context ctx
69 3) writing to a file fileName with image type imageType
70 4) creating a cairo surface and context within the constructor
71 """
72 self.image=None
73 self.imageType=imageType
74 if image is not None:
75 try:
76 imgd = getattr(image,'tobytes',image.tostring)("raw","BGRA")
77 except SystemError:
78 r,g,b,a = image.split()
79 mrg = Image.merge("RGBA",(b,g,r,a))
80 imgd = getattr(mrg,'tobytes',mrg.tostring)("raw","RGBA")
81
82 a = array.array('B',imgd)
83 stride=image.size[0]*4
84 surface = cairo.ImageSurface.create_for_data (
85 a, cairo.FORMAT_ARGB32,
86 image.size[0], image.size[1], stride)
87 ctx = cairo.Context(surface)
88 size=image.size[0], image.size[1]
89 self.image=image
90 elif ctx is None and size is not None:
91 if hasattr(cairo, "PDFSurface") and imageType == "pdf":
92 surface = cairo.PDFSurface(fileName, size[0], size[1])
93 elif hasattr(cairo, "SVGSurface") and imageType == "svg":
94 surface = cairo.SVGSurface(fileName, size[0], size[1])
95 elif hasattr(cairo, "PSSurface") and imageType == "ps":
96 surface = cairo.PSSurface(fileName, size[0], size[1])
97 elif imageType == "png":
98 surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, size[0], size[1])
99 else:
100 raise ValueError("Unrecognized file type. Valid choices are pdf, svg, ps, and png")
101 ctx = cairo.Context(surface)
102 ctx.set_source_rgb(1,1,1)
103 ctx.paint()
104 else:
105 surface = ctx.get_target()
106 if size is None:
107 try:
108 size = surface.get_width(),surface.get_height()
109 except AttributeError:
110 size = None
111 self.ctx = ctx
112 self.size = size
113 self.surface = surface
114 self.fileName = fileName
115
117 """temporary interface, must be splitted to different methods,
118 """
119 if self.fileName and self.imageType=='png':
120 self.surface.write_to_png(self.fileName)
121 elif self.image is not None:
122
123 if hasattr(self.surface,'get_data'):
124 getattr(self.image,'frombytes',self.image.fromstring)(bytes(self.surface.get_data()),
125 "raw","BGRA",0,1)
126 else:
127 getattr(self.image,'frombytes',self.image.fromstring)(bytes(self.surface.get_data_as_rgba()),
128 "raw","RGBA",0,1)
129 self.surface.finish()
130 elif self.imageType == "png":
131 if hasattr(self.surface,'get_data'):
132 buffer=self.surface.get_data()
133 else:
134 buffer=self.surface.get_data_as_rgba()
135 return buffer
136
137 - def _doLine(self, p1, p2, **kwargs):
138 if kwargs.get('dash',(0,0)) == (0,0):
139 self.ctx.move_to(p1[0],p1[1])
140 self.ctx.line_to(p2[0],p2[1])
141 else:
142 dash = kwargs['dash']
143 pts = self._getLinePoints(p1,p2,dash)
144
145 currDash = 0
146 dashOn = True
147 while currDash<(len(pts)-1):
148 if dashOn:
149 p1 = pts[currDash]
150 p2 = pts[currDash+1]
151 self.ctx.move_to(p1[0],p1[1])
152 self.ctx.line_to(p2[0],p2[1])
153 currDash+=1
154 dashOn = not dashOn
155
156 - def addCanvasLine(self,p1,p2,color=(0,0,0),color2=None,**kwargs):
157 self.ctx.set_line_width(kwargs.get('linewidth',1))
158 if color2 and color2!=color:
159 mp = (p1[0]+p2[0])/2.,(p1[1]+p2[1])/2.
160 self.ctx.set_source_rgb(*color)
161 self._doLine(p1,mp,**kwargs)
162 self.ctx.stroke()
163 self.ctx.set_source_rgb(*color2)
164 self._doLine(mp,p2,**kwargs)
165 self.ctx.stroke()
166 else:
167 self.ctx.set_source_rgb(*color)
168 self._doLine(p1,p2,**kwargs)
169 self.ctx.stroke()
170
171 - def _addCanvasText1(self,text,pos,font,color=(0,0,0),**kwargs):
172 if font.weight=='bold':
173 weight=cairo.FONT_WEIGHT_BOLD
174 else:
175 weight=cairo.FONT_WEIGHT_NORMAL
176 self.ctx.select_font_face(font.face,
177 cairo.FONT_SLANT_NORMAL,
178 weight)
179 text = scriptPattern.sub('',text)
180 self.ctx.set_font_size(font.size)
181 w,h=self.ctx.text_extents(text)[2:4]
182 bw,bh=w+h*0.4,h*1.4
183 offset = w*pos[2]
184 dPos = pos[0]-w/2.+offset,pos[1]+h/2.
185 self.ctx.set_source_rgb(*color)
186 self.ctx.move_to(*dPos)
187 self.ctx.show_text(text)
188
189 if 0:
190 self.ctx.move_to(dPos[0],dPos[1])
191 self.ctx.line_to(dPos[0]+bw,dPos[1])
192 self.ctx.line_to(dPos[0]+bw,dPos[1]-bh)
193 self.ctx.line_to(dPos[0],dPos[1]-bh)
194 self.ctx.line_to(dPos[0],dPos[1])
195 self.ctx.close_path()
196 self.ctx.stroke()
197
198 return (bw,bh,offset)
199 - def _addCanvasText2(self,text,pos,font,color=(0,0,0),**kwargs):
200 if font.weight=='bold':
201 weight=cairo.FONT_WEIGHT_BOLD
202 else:
203 weight=cairo.FONT_WEIGHT_NORMAL
204 self.ctx.select_font_face(font.face,
205 cairo.FONT_SLANT_NORMAL,
206 weight)
207 orientation=kwargs.get('orientation','E')
208 cctx=pangocairo.CairoContext(self.ctx)
209
210 plainText = scriptPattern.sub('',text)
211 measureLout = cctx.create_layout()
212 measureLout.set_alignment(pango.ALIGN_LEFT)
213 measureLout.set_markup(plainText)
214
215 lout = cctx.create_layout()
216 lout.set_alignment(pango.ALIGN_LEFT)
217 lout.set_markup(text)
218
219
220
221 fnt = pango.FontDescription('%s %d'%(font.face,font.size*.8))
222 lout.set_font_description(fnt)
223 measureLout.set_font_description(fnt)
224
225
226
227
228 iext,lext=measureLout.get_pixel_extents()
229 iext2,lext2=lout.get_pixel_extents()
230 w=lext2[2]-lext2[0]
231 h=lext[3]-lext[1]
232 pad = [h*.2,h*.3]
233
234
235 if orientation=='S':
236 pad[1] *= 0.5
237 bw,bh=w+pad[0],h+pad[1]
238 offset = w*pos[2]
239 if 0:
240 if orientation=='W':
241 dPos = pos[0]-w+offset,pos[1]-h/2.
242 elif orientation=='E':
243 dPos = pos[0]-w/2+offset,pos[1]-h/2.
244 else:
245 dPos = pos[0]-w/2+offset,pos[1]-h/2.
246 self.ctx.move_to(dPos[0],dPos[1])
247 else:
248 dPos = pos[0]-w/2.+offset,pos[1]-h/2.
249 self.ctx.move_to(dPos[0],dPos[1])
250
251 self.ctx.set_source_rgb(*color)
252 cctx.update_layout(lout)
253 cctx.show_layout(lout)
254
255 if 0:
256 self.ctx.move_to(dPos[0],dPos[1])
257 self.ctx.line_to(dPos[0]+bw,dPos[1])
258 self.ctx.line_to(dPos[0]+bw,dPos[1]+bh)
259 self.ctx.line_to(dPos[0],dPos[1]+bh)
260 self.ctx.line_to(dPos[0],dPos[1])
261 self.ctx.close_path()
262 self.ctx.stroke()
263
264
265 return (bw,bh,offset)
266
267 - def addCanvasText(self,text,pos,font,color=(0,0,0),**kwargs):
268 if pango is not None and pangocairo is not None:
269 textSize = self._addCanvasText2(text,pos,font,color,**kwargs)
270 else:
271 textSize = self._addCanvasText1(text,pos,font,color,**kwargs)
272 return textSize
273
275 if not fill and not stroke: return
276 dps = []
277 self.ctx.set_source_rgb(*color)
278 self.ctx.move_to(ps[0][0],ps[0][1])
279 for p in ps[1:]:
280 self.ctx.line_to(p[0],p[1])
281 self.ctx.close_path()
282 if stroke:
283 if fill:
284 self.ctx.stroke_preserve()
285 else:
286 self.ctx.stroke()
287 if fill:
288 self.ctx.fill()
289
290 - def addCanvasDashedWedge(self,p1,p2,p3,dash=(2,2),color=(0,0,0),
291 color2=None,**kwargs):
292 self.ctx.set_line_width(kwargs.get('linewidth',1))
293 self.ctx.set_source_rgb(*color)
294 dash = (3,3)
295 pts1 = self._getLinePoints(p1,p2,dash)
296 pts2 = self._getLinePoints(p1,p3,dash)
297
298 if len(pts2)<len(pts1): pts2,pts1=pts1,pts2
299
300 for i in range(len(pts1)):
301 self.ctx.move_to(pts1[i][0],pts1[i][1])
302 self.ctx.line_to(pts2[i][0],pts2[i][1])
303 self.ctx.stroke()
304
305 - def addCircle(self,center,radius,color=(0,0,0),fill=True,stroke=False,alpha=1.0,
306 **kwargs):
307 if not fill and not stroke: return
308 dps = []
309
310 self.ctx.set_source_rgba(color[0],color[1],color[2],alpha)
311 self.ctx.arc(center[0],center[1],radius,0,2.*math.pi)
312 self.ctx.close_path()
313 if stroke:
314 if fill:
315 self.ctx.stroke_preserve()
316 else:
317 self.ctx.stroke()
318 if fill:
319 self.ctx.fill()
320