1# $Id$
2# Copyright (C) 2005  Greg Landrum and Rational Discovery LLC
3#
4# This library is free software; you can redistribute it and/or
5# modify it under the terms of the GNU Lesser General Public
6# License as published by the Free Software Foundation; either
7# version 2 of the License, or (at your option) any later version.
8#
9# This library is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12# Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public
15# License along with this library; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17"""pidReportLab
18
19Bits have been shamelessly cobbled from piddlePDF.py and/or
20piddlePS.py
21
22Greg Landrum (greg.landrum@gmail.com) 3/28/2005
23"""
24"""
25  Functionality implemented:
26  -drawLine
27  -drawPolygon
28  -drawEllipse
29  -drawArc
30  -drawCurve
31  -drawString (rotated text is, mostly, fine... see below)
32  -drawImage
33
34"""
35
36from rdkit.sping.pid import *
37from rdkit.sping.PDF import pidPDF, pdfmetrics
38from reportlab.lib import colors
39from reportlab.graphics import shapes
40import os, types
41
42from math import *
43
44
45def colorToRL(color):
46  if color != transparent:
47    return colors.Color(color.red, color.green, color.blue)
48  else:
49    return None
50
51
52class RLCanvas(Canvas):
53
54  def __init__(self, size=(300, 300), name='RLCanvas'):
55    self.size = size
56    self._initOutput()
57    Canvas.__init__(self, size, name)
58    self.drawing = shapes.Drawing(size[0], size[1])
59
60  def _initOutput(self):
61    pass
62
63  # public functions
64  def clear(self):
65    self._initOutput()
66
67  def flush(self):
68    # self.save('svg')
69    pass  # to fit new definition of flush() -cwl
70
71  def save(self, file=None, format=None):
72    """Hand this either a file= <filename> or
73    file = <an open file object>.
74    """
75    if not file:
76      file = self.name
77    from reportlab.graphics import renderPDF
78    renderPDF.drawToFile(self.drawing, file, self.name)
79
80  def fixY(self, y):
81    return self.size[1] - y
82
83  # taken from pidPDF.py
84  def _findPostScriptFontName(self, font):
85    """Attempts to return proper font name."""
86    #maps a piddle font to a postscript one.
87    #step 1 - no face ends up serif, others are lowercased
88    if not font.face:
89      face = 'serif'
90    else:
91      face = font.face.lower()
92    while face in pidPDF.font_face_map:
93      face = pidPDF.font_face_map[face]
94    #step 2, - resolve bold/italic to get the right PS font name
95    psname = pidPDF.ps_font_map[(face, font.bold, font.italic)]
96    return psname
97
98  #------------- drawing methods --------------
99  def drawLine(self, x1, y1, x2, y2, color=None, width=None, dash=None, **kwargs):
100    "Draw a straight line between x1,y1 and x2,y2."
101    # set color...
102    if color:
103      if color == transparent:
104        return
105    elif self.defaultLineColor == transparent:
106      return
107    else:
108      color = self.defaultLineColor
109    color = colorToRL(color)
110    if width:
111      w = width
112    else:
113      w = self.defaultLineWidth
114
115    self.drawing.add(
116      shapes.Line(x1, self.fixY(y1), x2, self.fixY(y2), strokeColor=color, strokeWidth=w,
117                  strokeDashArray=dash))
118    return
119
120  def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0, **kwargs):
121    "Draw a Bezier curve with control points x1,y1 to x4,y4."
122    pts = self.curvePoints(x1, y1, x2, y2, x3, y3, x4, y4)
123    if not closed:
124      pointlist = [(pts[x][0], pts[x][1], pts[x + 1][0], pts[x + 1][1])
125                   for x in range(len(pts) - 1)]
126      self.drawLines(pointlist, **kwargs)
127    else:
128      self.drawPolygon(pointlist, closed=1, **kwargs)
129
130  def drawArc(self, x1, y1, x2, y2, startAng=0, extent=360, edgeColor=None, edgeWidth=None,
131              fillColor=None, dash=None, **kwargs):
132    """Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
133    starting at startAng degrees and covering extent degrees.   Angles
134    start with 0 to the right (+x) and increase counter-clockwise.
135    These should have x1<x2 and y1<y2."""
136
137    center = (x1 + x2) / 2, (y1 + y2) / 2
138    pointlist = self.arcPoints(x1, y1, x2, y2, startAng, extent)
139
140    # Fill...
141    self.drawPolygon(pointlist + [center], edgeColor=transparent, edgeWidth=0, fillColor=fillColor)
142
143    # Outline...
144    pts = pointlist
145    pointlist = [(pts[x][0], pts[x][1], pts[x + 1][0], pts[x + 1][1]) for x in range(len(pts) - 1)]
146    self.drawLines(pointlist, edgeColor, edgeWidth, dash=dash, **kwargs)
147
148  def drawPolygon(self, pointlist, edgeColor=None, edgeWidth=None, fillColor=transparent, closed=0,
149                  dash=None, **kwargs):
150    """drawPolygon(pointlist) -- draws a polygon
151    pointlist: a list of (x,y) tuples defining vertices
152    """
153    if not edgeColor:
154      edgeColor = self.defaultLineColor
155
156    edgeColor = colorToRL(edgeColor)
157    if not fillColor or fillColor == transparent:
158      fillColor = None
159    else:
160      fillColor = colorToRL(fillColor)
161
162    if edgeWidth:
163      w = edgeWidth
164    else:
165      w = self.defaultLineWidth
166
167    points = []
168    for x, y in pointlist:
169      points.append(x)
170      points.append(self.fixY(y))
171    self.drawing.add(
172      shapes.Polygon(points, strokeColor=edgeColor, strokeWidth=w, strokeDashArray=dash,
173                     fillColor=fillColor))
174
175  def drawString(self, s, x, y, font=None, color=None, angle=0, **kwargs):
176    # set color...
177    if color:
178      if color == transparent:
179        return
180    elif self.defaultLineColor == transparent:
181      return
182    else:
183      color = self.defaultLineColor
184    color = colorToRL(color)
185    if font is None:
186      font = self.defaultFont
187
188    txt = shapes.String(0, 0, s, fillColor=color)
189    txt.fontName = self._findPostScriptFontName(font)
190    txt.fontSize = font.size
191
192    g = shapes.Group(txt)
193    g.translate(x, self.fixY(y))
194    g.rotate(angle)
195    self.drawing.add(g)
196    return
197
198  def drawImage(self, image, x1, y1, x2=None, y2=None, **kwargs):
199    """
200      to the best of my knowledge, the only real way to get an image
201    """
202    return
203
204  def stringWidth(self, s, font=None):
205    "Return the logical width of the string if it were drawn \
206    in the current font (defaults to self.font)."
207
208    if not font:
209      font = self.defaultFont
210    fontName = self._findPostScriptFontName(font)
211    return pdfmetrics.stringwidth(s, fontName) * font.size * 0.001
212
213  def fontAscent(self, font=None):
214    if not font:
215      font = self.defaultFont
216    #return -font.size
217    fontName = self._findPostScriptFontName(font)
218    return pdfmetrics.ascent_descent[fontName][0] * 0.001 * font.size
219
220  def fontDescent(self, font=None):
221    if not font:
222      font = self.defaultFont
223    fontName = self._findPostScriptFontName(font)
224    return -pdfmetrics.ascent_descent[fontName][1] * 0.001 * font.size
225
226
227def test():
228  #... for testing...
229  canvas = RLCanvas(name="test")
230
231  canvas.defaultLineColor = Color(0.7, 0.7, 1.0)  # light blue
232  canvas.drawLines(map(lambda i: (i * 10, 0, i * 10, 300), range(30)))
233  canvas.drawLines(map(lambda i: (0, i * 10, 300, i * 10), range(30)))
234  canvas.defaultLineColor = black
235
236  canvas.drawLine(10, 200, 20, 190, color=red)
237
238  canvas.drawEllipse(130, 30, 200, 100, fillColor=yellow, edgeWidth=4)
239
240  canvas.drawArc(130, 30, 200, 100, 45, 50, fillColor=blue, edgeColor=navy, edgeWidth=4)
241
242  canvas.defaultLineWidth = 4
243  canvas.drawRoundRect(30, 30, 100, 100, fillColor=blue, edgeColor=maroon, dash=(3, 3))
244  canvas.drawCurve(20, 20, 100, 50, 50, 100, 160, 160)
245
246  canvas.drawString("This is a test!", 30, 130, Font(face="times", size=16, bold=1), color=green,
247                    angle=-45)
248
249  canvas.drawString("This is a test!", 30, 130, color=red)
250
251  polypoints = [(160, 120), (130, 190), (210, 145), (110, 145), (190, 190)]
252  canvas.drawPolygon(polypoints, fillColor=lime, edgeColor=red, edgeWidth=3, closed=1)
253
254  canvas.drawRect(200, 200, 260, 260, edgeColor=yellow, edgeWidth=5)
255  canvas.drawLine(200, 260, 260, 260, color=green, width=5)
256  canvas.drawLine(260, 200, 260, 260, color=red, width=5)
257
258  canvas.save('test.pdf')
259
260
261def dashtest():
262  #... for testing...
263  canvas = RLCanvas(name="test.pdf")
264
265  canvas.defaultLineColor = Color(0.7, 0.7, 1.0)  # light blue
266  canvas.drawLines(map(lambda i: (i * 10, 0, i * 10, 300), range(30)), dash=(3, 3))
267  canvas.drawLines(map(lambda i: (0, i * 10, 300, i * 10), range(30)), dash=(3, 3))
268  canvas.defaultLineColor = black
269
270  canvas.drawLine(10, 200, 20, 190, color=red, dash=(3, 3))
271
272  canvas.drawEllipse(130, 30, 200, 100, fillColor=yellow, edgeWidth=4, dash=(3, 3))
273
274  canvas.drawArc(130, 30, 200, 100, 45, 50, fillColor=blue, edgeColor=navy, edgeWidth=4, dash=(3,
275                                                                                               3))
276
277  canvas.defaultLineWidth = 4
278  canvas.drawRoundRect(30, 30, 100, 100, fillColor=blue, edgeColor=maroon, dash=(3, 3))
279  canvas.drawCurve(20, 20, 100, 50, 50, 100, 160, 160, dash=(3, 3))
280
281  canvas.drawString("This is a test!", 30, 130, Font(face="times", size=16, bold=1), color=green,
282                    angle=-45)
283
284  canvas.drawString("This is a test!", 30, 130, color=red, angle=-45)
285
286  polypoints = [(160, 120), (130, 190), (210, 145), (110, 145), (190, 190)]
287  canvas.drawPolygon(polypoints, fillColor=lime, edgeColor=red, edgeWidth=3, closed=1, dash=(3, 3))
288
289  canvas.drawRect(200, 200, 260, 260, edgeColor=yellow, edgeWidth=5, dash=(3, 3))
290  canvas.drawLine(200, 260, 260, 260, color=green, width=5, dash=(3, 3))
291  canvas.drawLine(260, 200, 260, 260, color=red, width=5, dash=(3, 3))
292
293  canvas.save()
294
295
296def testit(canvas, s, x, y, font=None):
297  canvas.defaultLineColor = black
298  canvas.drawString(s, x, y, font=font)
299  canvas.defaultLineColor = blue
300  w = canvas.stringWidth(s, font=font)
301  canvas.drawLine(x, y, x + w, y)
302  canvas.drawLine(x, y - canvas.fontAscent(font=font), x + w, y - canvas.fontAscent(font=font))
303  canvas.drawLine(x, y + canvas.fontDescent(font=font), x + w, y + canvas.fontDescent(font=font))
304
305
306def test2():
307
308  canvas = RLCanvas(name="Foogar.pdf")
309  testit(canvas, "Foogar", 20, 30)
310
311  testit(canvas, "Foogar", 20, 90, font=Font(size=24))
312
313  testit(canvas, "Foogar", 20, 150, font=Font(face='courier', size=24))
314
315  testit(canvas, "Foogar", 20, 240, font=Font(face='courier'))
316  canvas.flush()
317  canvas.save()
318
319
320if __name__ == '__main__':
321  test()
322  #dashtest()
323  #test2()
324