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