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