Package rdkit :: Package Chem :: Package Draw :: Module cairoCanvas
[hide private]
[frames] | no frames]

Source Code for Module rdkit.Chem.Draw.cairoCanvas

  1  # $Id: cairoCanvas.py 11930 2014-01-24 07:00:08Z landrgr1 $ 
  2  # 
  3  #  Copyright (C) 2008 Greg Landrum 
  4  #  Copyright (C) 2009 Uwe Hoffmann 
  5  # 
  6  #   @@ All Rights Reserved @@ 
  7  #  This file is part of the RDKit. 
  8  #  The contents are covered by the terms of the BSD license 
  9  #  which is included in the file license.txt, found at the root 
 10  #  of the RDKit source tree. 
 11  # 
 12  #pylint: disable=F0401,C0324,C0322,W0142 
 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     
48 -class Canvas(CanvasBase):
49 - def __init__(self, 50 image=None, # PIL image 51 size=None, 52 ctx=None, 53 imageType=None, # determines file type 54 fileName=None, # if set determines output file name 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
106 - def flush(self):
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 # on linux at least it seems like the PIL images are BGRA, not RGBA: 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 # for whatever reason, the font size using pango is larger 210 # than that w/ default cairo (at least for me) 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 # this is a bit kludgy, but empirically we end up with too much 216 # vertical padding if we use the text box with super and subscripts 217 # for the measurement. 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 # another empirical correction: labels draw at the bottom 224 # of bonds have too much vertical padding 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
264 - def addCanvasPolygon(self,ps,color=(0,0,0),fill=True,stroke=False,**kwargs):
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 #import pdb; pdb.set_trace(); 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