~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/reportlab/graphics/renderPS.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#Copyright ReportLab Europe Ltd. 2000-2004
 
2
#see license.txt for license details
 
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderPS.py
 
4
__version__=''' $Id$ '''
 
5
import string, types
 
6
from reportlab.pdfbase.pdfmetrics import getFont, stringWidth # for font info
 
7
from reportlab.lib.utils import fp_str, getStringIO
 
8
from reportlab.lib.colors import black
 
9
from reportlab.graphics.renderbase import StateTracker, getStateDelta
 
10
from reportlab.graphics.shapes import STATE_DEFAULTS
 
11
import math
 
12
from types import StringType
 
13
from operator import getitem
 
14
from reportlab import rl_config
 
15
 
 
16
 
 
17
# we need to create encoding vectors for each font we use, or they will
 
18
# come out in Adobe's old StandardEncoding, which NOBODY uses.
 
19
PS_WinAnsiEncoding="""
 
20
/RE { %def
 
21
  findfont begin
 
22
  currentdict dup length dict begin
 
23
    { %forall
 
24
      1 index /FID ne { def } { pop pop } ifelse
 
25
    } forall
 
26
    /FontName exch def dup length 0 ne { %if
 
27
      /Encoding Encoding 256 array copy def
 
28
      0 exch { %forall
 
29
        dup type /nametype eq { %ifelse
 
30
          Encoding 2 index 2 index put
 
31
          pop 1 add
 
32
        }{ %else
 
33
          exch pop
 
34
        } ifelse
 
35
      } forall
 
36
    } if pop
 
37
  currentdict dup end end
 
38
  /FontName get exch definefont pop
 
39
} bind def
 
40
 
 
41
/WinAnsiEncoding [
 
42
  39/quotesingle 96/grave 128/euro 130/quotesinglbase/florin/quotedblbase
 
43
  /ellipsis/dagger/daggerdbl/circumflex/perthousand
 
44
  /Scaron/guilsinglleft/OE 145/quoteleft/quoteright
 
45
  /quotedblleft/quotedblright/bullet/endash/emdash
 
46
  /tilde/trademark/scaron/guilsinglright/oe/dotlessi
 
47
  159/Ydieresis 164/currency 166/brokenbar 168/dieresis/copyright
 
48
  /ordfeminine 172/logicalnot 174/registered/macron/ring
 
49
  177/plusminus/twosuperior/threesuperior/acute/mu
 
50
  183/periodcentered/cedilla/onesuperior/ordmasculine
 
51
  188/onequarter/onehalf/threequarters 192/Agrave/Aacute
 
52
  /Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla
 
53
  /Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute
 
54
  /Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute
 
55
  /Ocircumflex/Otilde/Odieresis/multiply/Oslash
 
56
  /Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn
 
57
  /germandbls/agrave/aacute/acircumflex/atilde/adieresis
 
58
  /aring/ae/ccedilla/egrave/eacute/ecircumflex
 
59
  /edieresis/igrave/iacute/icircumflex/idieresis
 
60
  /eth/ntilde/ograve/oacute/ocircumflex/otilde
 
61
  /odieresis/divide/oslash/ugrave/uacute/ucircumflex
 
62
  /udieresis/yacute/thorn/ydieresis
 
63
] def
 
64
 
 
65
"""
 
66
 
 
67
class PSCanvas:
 
68
    def __init__(self,size=(300,300), PostScriptLevel=2):
 
69
        self.width, self.height = size
 
70
        self.code = []
 
71
        self._sep = '\n'
 
72
        self._strokeColor = self._fillColor = self._lineWidth = \
 
73
            self._font = self._fontSize = self._lineCap = \
 
74
            self._lineJoin = self._color = None
 
75
 
 
76
 
 
77
        self._fontsUsed =   [] # track them as we go
 
78
 
 
79
        self.setFont(STATE_DEFAULTS['fontName'],STATE_DEFAULTS['fontSize'])
 
80
        self.setStrokeColor(STATE_DEFAULTS['strokeColor'])
 
81
        self.setLineCap(2)
 
82
        self.setLineJoin(0)
 
83
        self.setLineWidth(1)
 
84
 
 
85
        self.PostScriptLevel=PostScriptLevel
 
86
 
 
87
    def comment(self,msg):
 
88
        self.code.append('%'+msg)
 
89
 
 
90
    def drawImage(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
 
91
        # select between postscript level 1 or level 2
 
92
        if PostScriptLevel==1:
 
93
            self._drawImageLevel1(image, x1,y1, x2=None,y2=None)
 
94
        elif PostScriptLevel == 2 :
 
95
            self._drawImageLevel2(image, x1,y1, x2=None,y2=None)
 
96
        else :
 
97
            raise 'PostScriptLevelException'
 
98
 
 
99
    def clear(self):
 
100
        self.code.append('showpage') # ugh, this makes no sense oh well.
 
101
 
 
102
    def save(self,f=None):
 
103
        if not hasattr(f,'write'):
 
104
            file = open(f,'wb')
 
105
        else:
 
106
            file = f
 
107
        if self.code[-1]!='showpage': self.clear()
 
108
        self.code.insert(0,'''\
 
109
%%!PS-Adobe-3.0 EPSF-3.0
 
110
%%%%BoundingBox: 0 0 %d %d
 
111
%%%% Initialization:
 
112
/m {moveto} bind def
 
113
/l {lineto} bind def
 
114
/c {curveto} bind def
 
115
 
 
116
%s
 
117
''' % (self.width,self.height, PS_WinAnsiEncoding))
 
118
 
 
119
        # for each font used, reencode the vectors
 
120
        fontReencode = []
 
121
        for fontName in self._fontsUsed:
 
122
            fontReencode.append('WinAnsiEncoding /%s /%s RE' % (fontName, fontName))
 
123
        self.code.insert(1, string.join(fontReencode, self._sep))
 
124
 
 
125
        file.write(string.join(self.code,self._sep))
 
126
        if file is not f:
 
127
            file.close()
 
128
            from reportlab.lib.utils import markfilename
 
129
            markfilename(f,creatorcode='XPR3',filetype='EPSF')
 
130
 
 
131
    def saveState(self):
 
132
        self.code.append('gsave')
 
133
 
 
134
    def restoreState(self):
 
135
        self.code.append('grestore')
 
136
 
 
137
    def stringWidth(self, s, font=None, fontSize=None):
 
138
        """Return the logical width of the string if it were drawn
 
139
        in the current font (defaults to self.font)."""
 
140
        font = font or self._font
 
141
        fontSize = fontSize or self._fontSize
 
142
        return stringWidth(s, font, fontSize)
 
143
 
 
144
    def setLineCap(self,v):
 
145
        if self._lineCap!=v:
 
146
            self._lineCap = v
 
147
            self.code.append('%d setlinecap'%v)
 
148
 
 
149
    def setLineJoin(self,v):
 
150
        if self._lineJoin!=v:
 
151
            self._lineJoin = v
 
152
            self.code.append('%d setlinejoin'%v)
 
153
 
 
154
    def setDash(self, array=[], phase=0):
 
155
        """Two notations.  pass two numbers, or an array and phase"""
 
156
        # copied and modified from reportlab.canvas
 
157
        psoperation = "setdash"
 
158
        if type(array) == types.IntType or type(array) == types.FloatType:
 
159
            self._code.append('[%s %s] 0 %s' % (array, phase, psoperation))
 
160
        elif type(array) == types.ListType or type(array) == types.TupleType:
 
161
            assert phase >= 0, "phase is a length in user space"
 
162
            textarray = string.join(map(str, array))
 
163
            self.code.append('[%s] %s %s' % (textarray, phase, psoperation))
 
164
 
 
165
    def setStrokeColor(self, color):
 
166
        self._strokeColor = color
 
167
        self.setColor(color)
 
168
 
 
169
    def setColor(self, color):
 
170
        if self._color!=color:
 
171
            self._color = color
 
172
            if color:
 
173
                if hasattr(color, "cyan"):
 
174
                    self.code.append('%s setcmykcolor' % fp_str(color.cyan, color.magenta, color.yellow, color.black))
 
175
                else:
 
176
                    self.code.append('%s setrgbcolor' % fp_str(color.red, color.green, color.blue))
 
177
 
 
178
    def setFillColor(self, color):
 
179
        self._fillColor = color
 
180
        self.setColor(color)
 
181
 
 
182
    def setLineWidth(self, width):
 
183
        if width != self._lineWidth:
 
184
            self._lineWidth = width
 
185
            self.code.append('%s setlinewidth' % width)
 
186
 
 
187
    def setFont(self,font,fontSize,leading=None):
 
188
        if self._font!=font or self._fontSize!=fontSize:
 
189
            self._fontCodeLoc = len(self.code)
 
190
            self._font = font
 
191
            self._fontSize = fontSize
 
192
            self.code.append('')
 
193
 
 
194
    def line(self, x1, y1, x2, y2):
 
195
        if self._strokeColor != None:
 
196
            self.code.append('%s m %s l stroke' % (fp_str(x1, y1), fp_str(x2, y2)))
 
197
 
 
198
    def _escape(self, s):
 
199
        '''
 
200
        return a copy of string s with special characters in postscript strings
 
201
        escaped with backslashes.
 
202
        Have not handled characters that are converted normally in python strings
 
203
        i.e. \n -> newline
 
204
        '''
 
205
        str = string.replace(s, chr(0x5C), r'\\' )
 
206
        str = string.replace(str, '(', '\(' )
 
207
        str = string.replace(str, ')', '\)')
 
208
        return str
 
209
 
 
210
    def drawString(self, x, y, s, angle=0):
 
211
        if self._fillColor != None:
 
212
            if not self.code[self._fontCodeLoc]:
 
213
                psName = getFont(self._font).face.name
 
214
                self.code[self._fontCodeLoc]='(%s) findfont %s scalefont setfont' % (psName,fp_str(self._fontSize))
 
215
                if psName not in self._fontsUsed:
 
216
                    self._fontsUsed.append(psName)
 
217
            self.setColor(self._fillColor)
 
218
            s = self._escape(s)
 
219
## before inverting...
 
220
##            if angle == 0 :   # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
 
221
##                self.code.append('%s m 1 -1 scale (%s) show 1 -1 scale' % (fp_str(x,y),s))
 
222
##            else : # general case, rotated text
 
223
##                self.code.append('gsave %s %s translate %s rotate' % (x,y,angle))
 
224
##                self.code.append('0 0 m 1 -1 scale (%s) show' % s)
 
225
##                self.code.append('grestore')
 
226
            if angle == 0 :   # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops
 
227
                self.code.append('%s m (%s) show ' % (fp_str(x,y),s))
 
228
            else : # general case, rotated text
 
229
                self.code.append('gsave %s %s translate %s rotate' % (x,y,angle))
 
230
                self.code.append('0 0 m (%s) show' % s)
 
231
                self.code.append('grestore')
 
232
 
 
233
    def drawCentredString(self, x, y, text, text_anchor='middle'):
 
234
        if self.fillColor is not None:
 
235
            textLen = stringWidth(text, self._font,self._fontSize)
 
236
            if text_anchor=='end':
 
237
                x = x-textLen
 
238
            elif text_anchor=='middle':
 
239
                x = x - textLen/2
 
240
            self.drawString(x,y,text)
 
241
 
 
242
    def drawRightString(self, text, x, y):
 
243
        self.drawCentredString(text,x,y,text_anchor='end')
 
244
 
 
245
    def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0):
 
246
        codeline = '%s m %s curveto'
 
247
        data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
 
248
        if self._fillColor != None:
 
249
            self.setColor(self._fillColor)
 
250
            self.code.append((codeline % data) + ' eofill')
 
251
        if self._strokeColor != None:
 
252
            self.setColor(self._strokeColor)
 
253
            self.code.append((codeline % data)
 
254
                            + ((closed and ' closepath') or '')
 
255
                            + ' stroke')
 
256
 
 
257
    ########################################################################################
 
258
 
 
259
    def rect(self, x1,y1, x2,y2, stroke=1, fill=1):
 
260
        "Draw a rectangle between x1,y1, and x2,y2"
 
261
        # Path is drawn in counter-clockwise direction"
 
262
 
 
263
        x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
 
264
        y1, y2 = min(y1,y2), max(y1, y2)
 
265
        self.polygon(((x1,y1),(x2,y1),(x2,y2),(x1,y2)), closed=1, stroke=stroke, fill = fill)
 
266
 
 
267
    def roundRect(self, x1,y1, x2,y2, rx=8, ry=8):
 
268
        """Draw a rounded rectangle between x1,y1, and x2,y2,
 
269
        with corners inset as ellipses with x radius rx and y radius ry.
 
270
        These should have x1<x2, y1<y2, rx>0, and ry>0."""
 
271
        # Path is drawn in counter-clockwise direction
 
272
 
 
273
        x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py
 
274
        y1, y2 = min(y1,y2), max(y1, y2)
 
275
 
 
276
        # Note: arcto command draws a line from current point to beginning of arc
 
277
        # save current matrix, translate to center of ellipse, scale by rx ry, and draw
 
278
        # a circle of unit radius in counterclockwise dir, return to original matrix
 
279
        # arguments are (cx, cy, rx, ry, startAngle, endAngle)
 
280
        ellipsePath = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s arc setmatrix'
 
281
 
 
282
        # choice between newpath and moveTo beginning of arc
 
283
        # go with newpath for precision, does this violate any assumptions in code???
 
284
        rrCode = ['newpath'] # Round Rect code path
 
285
        # upper left corner ellipse is first
 
286
        rrCode.append(ellipsePath % (x1+rx, y1+ry, rx, -ry, 90, 180))
 
287
        rrCode.append(ellipsePath % (x1+rx, y2-ry, rx, -ry, 180, 270))
 
288
        rrCode.append(ellipsePath % (x2-rx, y2-ry, rx, -ry, 270, 360))
 
289
        rrCode.append(ellipsePath % (x2-rx, y1+ry, rx, -ry, 0,  90) )
 
290
        rrCode.append('closepath')
 
291
 
 
292
        self._fillAndStroke(rrCode)
 
293
 
 
294
    def ellipse(self, x1,y1, x2,y2):
 
295
        """Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2.
 
296
        These should have x1<x2 and y1<y2."""
 
297
        #Just invoke drawArc to actually draw the ellipse
 
298
        self.drawArc(x1,y1, x2,y2)
 
299
 
 
300
    def circle(self, xc, yc, r):
 
301
        self.ellipse(xc-r,yc-r, xc+r,yc+r)
 
302
 
 
303
    def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, fromcenter=0):
 
304
        """Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
 
305
        starting at startAng degrees and covering extent degrees.   Angles
 
306
        start with 0 to the right (+x) and increase counter-clockwise.
 
307
        These should have x1<x2 and y1<y2."""
 
308
        #calculate centre of ellipse
 
309
        #print "x1,y1,x2,y2,startAng,extent,fromcenter", x1,y1,x2,y2,startAng,extent,fromcenter
 
310
        cx, cy = (x1+x2)/2.0, (y1+y2)/2.0
 
311
        rx, ry = (x2-x1)/2.0, (y2-y1)/2.0
 
312
 
 
313
        codeline = self._genArcCode(x1, y1, x2, y2, startAng, extent)
 
314
 
 
315
        startAngleRadians = math.pi*startAng/180.0
 
316
        extentRadians = math.pi*extent/180.0
 
317
        endAngleRadians = startAngleRadians + extentRadians
 
318
 
 
319
        codelineAppended = 0
 
320
 
 
321
        # fill portion
 
322
 
 
323
        if self._fillColor != None:
 
324
            self.setColor(self._fillColor)
 
325
            self.code.append(codeline)
 
326
            codelineAppended = 1
 
327
            if self._strokeColor!=None: self.code.append('gsave')
 
328
            self.lineTo(cx,cy)
 
329
            self.code.append('eofill')
 
330
            if self._strokeColor!=None: self.code.append('grestore')
 
331
 
 
332
        # stroke portion
 
333
        if self._strokeColor != None:
 
334
            # this is a bit hacked up.  There is certainly a better way...
 
335
            self.setColor(self._strokeColor)
 
336
            (startx, starty) = (cx+rx*math.cos(startAngleRadians), cy+ry*math.sin(startAngleRadians))
 
337
            if not codelineAppended:
 
338
                self.code.append(codeline)
 
339
            if fromcenter:
 
340
                # move to center
 
341
                self.lineTo(cx,cy)
 
342
                self.lineTo(startx, starty)
 
343
                self.code.append('closepath')
 
344
            self.code.append('stroke')
 
345
 
 
346
    def _genArcCode(self, x1, y1, x2, y2, startAng, extent):
 
347
        "Calculate the path for an arc inscribed in rectangle defined by (x1,y1),(x2,y2)"
 
348
        #calculate semi-minor and semi-major axes of ellipse
 
349
        xScale = abs((x2-x1)/2.0)
 
350
        yScale = abs((y2-y1)/2.0)
 
351
        #calculate centre of ellipse
 
352
        x, y = (x1+x2)/2.0, (y1+y2)/2.0
 
353
 
 
354
        codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix'
 
355
 
 
356
        if extent >= 0:
 
357
            arc='arc'
 
358
        else:
 
359
            arc='arcn'
 
360
        data = (x,y, xScale, yScale, startAng, startAng+extent, arc)
 
361
 
 
362
        return codeline % data
 
363
 
 
364
    def polygon(self, p, closed=0, stroke=1, fill=1):
 
365
        assert len(p) >= 2, 'Polygon must have 2 or more points'
 
366
 
 
367
        start = p[0]
 
368
        p = p[1:]
 
369
 
 
370
        polyCode = []
 
371
        polyCode.append("%s m" % fp_str(start))
 
372
        for point in p:
 
373
            polyCode.append("%s l" % fp_str(point))
 
374
        if closed:
 
375
            polyCode.append("closepath")
 
376
 
 
377
        self._fillAndStroke(polyCode,stroke=stroke,fill=fill)
 
378
 
 
379
    def lines(self, lineList, color=None, width=None):
 
380
        if self._strokeColor != None:
 
381
            self._setColor(self._strokeColor)
 
382
            codeline = '%s m %s l stroke'
 
383
            for line in lineList:
 
384
                self.code.append(codeline % (fp_str(line[0]),fp_str(line[1])))
 
385
 
 
386
    def moveTo(self,x,y):
 
387
        self.code.append('%s m' % fp_str(x, y))
 
388
 
 
389
    def lineTo(self,x,y):
 
390
        self.code.append('%s l' % fp_str(x, y))
 
391
 
 
392
    def curveTo(self,x1,y1,x2,y2,x3,y3):
 
393
        self.code.append('%s c' % fp_str(x1,y1,x2,y2,x3,y3))
 
394
 
 
395
    def closePath(self):
 
396
        self.code.append('closepath')
 
397
 
 
398
    def polyLine(self, p):
 
399
        assert len(p) >= 1, 'Polyline must have 1 or more points'
 
400
        if self._strokeColor != None:
 
401
            self.setColor(self._strokeColor)
 
402
            self.moveTo(p[0][0], p[0][1])
 
403
            for t in p[1:]:
 
404
                self.lineTo(t[0], t[1])
 
405
            self.code.append('stroke')
 
406
 
 
407
 
 
408
    def drawFigure(self, partList, closed=0):
 
409
        figureCode = []
 
410
        first = 1
 
411
 
 
412
        for part in partList:
 
413
            op = part[0]
 
414
            args = list(part[1:])
 
415
 
 
416
            if op == figureLine:
 
417
                if first:
 
418
                    first = 0
 
419
                    figureCode.append("%s m" % fp_str(args[:2]))
 
420
                else:
 
421
                    figureCode.append("%s l" % fp_str(args[:2]))
 
422
                figureCode.append("%s l" % fp_str(args[2:]))
 
423
 
 
424
            elif op == figureArc:
 
425
                first = 0
 
426
                x1,y1,x2,y2,startAngle,extent = args[:6]
 
427
                figureCode.append(self._genArcCode(x1,y1,x2,y2,startAngle,extent))
 
428
 
 
429
            elif op == figureCurve:
 
430
                if first:
 
431
                    first = 0
 
432
                    figureCode.append("%s m" % fp_str(args[:2]))
 
433
                else:
 
434
                    figureCode.append("%s l" % fp_str(args[:2]))
 
435
                figureCode.append("%s curveto" % fp_str(args[2:]))
 
436
            else:
 
437
                raise TypeError, "unknown figure operator: "+op
 
438
 
 
439
        if closed:
 
440
            figureCode.append("closepath")
 
441
        self._fillAndStroke(figureCode)
 
442
 
 
443
    def _fillAndStroke(self,code,clip=0,fill=1,stroke=1):
 
444
        fill = self._fillColor and fill
 
445
        stroke = self._strokeColor and stroke
 
446
        if fill or stroke or clip:
 
447
            self.code.extend(code)
 
448
            if fill:
 
449
                if stroke or clip: self.code.append("gsave")
 
450
                self.setColor(self._fillColor)
 
451
                self.code.append("eofill")
 
452
                if stroke or clip: self.code.append("grestore")
 
453
            if stroke:
 
454
                if clip: self.code.append("gsave")
 
455
                self.setColor(self._strokeColor)
 
456
                self.code.append("stroke")
 
457
                if clip: self.code.append("grestore")
 
458
            if clip:
 
459
                self.code.append("clip")
 
460
                self.code.append("newpath")
 
461
 
 
462
 
 
463
    def translate(self,x,y):
 
464
        self.code.append('%s translate' % fp_str(x,y))
 
465
 
 
466
    def scale(self,x,y):
 
467
        self.code.append('%s scale' % fp_str(x,y))
 
468
 
 
469
    def transform(self,a,b,c,d,e,f):
 
470
        self.code.append('[%s] concat' % fp_str(a,b,c,d,e,f))
 
471
 
 
472
    def _drawTimeResize(self,w,h):
 
473
        '''if this is used we're probably in the wrong world'''
 
474
        self.width, self.height = w, h
 
475
 
 
476
    ############################################################################################
 
477
    # drawImage(self. image, x1, y1, x2=None, y2=None) is now defined by either _drawImageLevel1
 
478
    #    ._drawImageLevel2, the choice is made in .__init__ depending on option
 
479
    def _drawImageLevel1(self, image, x1, y1, x2=None,y2=None):
 
480
        # Postscript Level1 version available for fallback mode when Level2 doesn't work
 
481
        """drawImage(self,image,x1,y1,x2=None,y2=None) : If x2 and y2 are ommitted, they are
 
482
        calculated from image size. (x1,y1) is upper left of image, (x2,y2) is lower right of
 
483
        image in piddle coordinates."""
 
484
        # For now let's start with 24 bit RGB images (following piddlePDF again)
 
485
        print "Trying to drawImage in piddlePS"
 
486
        component_depth = 8
 
487
        myimage = image.convert('RGB')
 
488
        imgwidth, imgheight = myimage.size
 
489
        if not x2:
 
490
            x2 = imgwidth + x1
 
491
        if not y2:
 
492
            y2 = y1 + imgheight
 
493
        drawwidth = x2 - x1
 
494
        drawheight = y2 - y1
 
495
        print 'Image size (%d, %d); Draw size (%d, %d)' % (imgwidth, imgheight, drawwidth, drawheight)
 
496
        # now I need to tell postscript how big image is
 
497
 
 
498
        # "image operators assume that they receive sample data from
 
499
        # their data source in x-axis major index order.  The coordinate
 
500
        # of the lower-left corner of the first sample is (0,0), of the
 
501
        # second (1,0) and so on" -PS2 ref manual p. 215
 
502
        #
 
503
        # The ImageMatrix maps unit squre of user space to boundary of the source image
 
504
        #
 
505
 
 
506
        # The CurrentTransformationMatrix (CTM) maps the unit square of
 
507
        # user space to the rect...on the page that is to receive the
 
508
        # image. A common ImageMatrix is [width 0 0 -height 0 height]
 
509
        # (for a left to right, top to bottom image )
 
510
 
 
511
        # first let's map the user coordinates start at offset x1,y1 on page
 
512
 
 
513
        self.code.extend([
 
514
            'gsave',
 
515
            '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
 
516
            '%s %s scale' % (drawwidth,drawheight),
 
517
            '/scanline %d 3 mul string def' % imgwidth  # scanline by multiples of image width
 
518
            ])
 
519
 
 
520
        # now push the dimensions and depth info onto the stack
 
521
        # and push the ImageMatrix to map the source to the target rectangle (see above)
 
522
        # finally specify source (PS2 pp. 225 ) and by exmample
 
523
        self.code.extend([
 
524
            '%s %s %s' % (imgwidth, imgheight, component_depth),
 
525
            '[%s %s %s %s %s %s]' % (imgwidth, 0, 0, -imgheight, 0, imgheight),
 
526
            '{ currentfile scanline readhexstring pop } false 3',
 
527
            'colorimage '
 
528
            ])
 
529
 
 
530
        # data source output--now we just need to deliver a hex encode
 
531
        # series of lines of the right overall size can follow
 
532
        # piddlePDF again
 
533
 
 
534
        rawimage = myimage.tostring()
 
535
        assert(len(rawimage) == imgwidth*imgheight, 'Wrong amount of data for image')
 
536
        #compressed = zlib.compress(rawimage) # no zlib at moment
 
537
        hex_encoded = self._AsciiHexEncode(rawimage)
 
538
 
 
539
        # write in blocks of 78 chars per line
 
540
        outstream = getStringIO(hex_encoded)
 
541
 
 
542
        dataline = outstream.read(78)
 
543
        while dataline <> "":
 
544
            self.code.append(dataline)
 
545
            dataline= outstream.read(78)
 
546
        self.code.append('% end of image data') # for clarity
 
547
        self.code.append('grestore') # return coordinates to normal
 
548
 
 
549
    # end of drawImage
 
550
    def _AsciiHexEncode(self, input):  # also based on piddlePDF
 
551
        "Helper function used by images"
 
552
        output = getStringIO()
 
553
        for char in input:
 
554
            output.write('%02x' % ord(char))
 
555
        return output.getvalue()
 
556
 
 
557
    def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version
 
558
        Image = import_Image()
 
559
        if not Image: return
 
560
        ### what sort of image are we to draw
 
561
        if image.mode=='L' :
 
562
            print 'found image.mode= L'
 
563
            imBitsPerComponent = 8
 
564
            imNumComponents = 1
 
565
            myimage = image
 
566
        elif image.mode == '1':
 
567
            print 'found image.mode= 1'
 
568
            myimage = image.convert('L')
 
569
            imNumComponents = 1
 
570
            myimage = image
 
571
        else :
 
572
            myimage = image.convert('RGB')
 
573
            imNumComponents = 3
 
574
            imBitsPerComponent = 8
 
575
 
 
576
        imwidth, imheight = myimage.size
 
577
        # print 'imwidth = %s, imheight = %s' % myimage.size
 
578
        if not x2:
 
579
            x2 = imwidth + x1
 
580
        if not y2:
 
581
            y2 = y1 + imheight
 
582
        drawwidth = x2 - x1
 
583
        drawheight = y2 - y1
 
584
        self.code.extend([
 
585
            'gsave',
 
586
            '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image
 
587
            '%s %s scale' % (drawwidth,drawheight)])
 
588
 
 
589
        if imNumComponents == 3 :
 
590
            self.code.append('/DeviceRGB setcolorspace')
 
591
        elif imNumComponents == 1 :
 
592
            self.code.append('/DeviceGray setcolorspace')
 
593
            print 'setting colorspace gray'
 
594
        # create the image dictionary
 
595
        self.code.append("""
 
596
<<
 
597
/ImageType 1
 
598
/Width %d /Height %d  %% dimensions of source image
 
599
/BitsPerComponent %d""" % (imwidth, imheight, imBitsPerComponent) )
 
600
 
 
601
        if imNumComponents == 1:
 
602
            self.code.append('/Decode [0 1]')
 
603
        if imNumComponents == 3:
 
604
            self.code.append('/Decode [0 1 0 1 0 1]  %% decode color values normally')
 
605
 
 
606
        self.code.extend([  '/ImageMatrix [%s 0 0 %s 0 %s]' % (imwidth, -imheight, imheight),
 
607
                            '/DataSource currentfile /ASCIIHexDecode filter',
 
608
                            '>> % End image dictionary',
 
609
                            'image'])
 
610
        # after image operator just need to dump image dat to file as hexstring
 
611
        rawimage = myimage.tostring()
 
612
        assert(len(rawimage) == imwidth*imheight, 'Wrong amount of data for image')
 
613
        #compressed = zlib.compress(rawimage) # no zlib at moment
 
614
        hex_encoded = self._AsciiHexEncode(rawimage)
 
615
 
 
616
        # write in blocks of 78 chars per line
 
617
        outstream = getStringIO(hex_encoded)
 
618
 
 
619
        dataline = outstream.read(78)
 
620
        while dataline <> "":
 
621
            self.code.append(dataline)
 
622
            dataline= outstream.read(78)
 
623
        self.code.append('> % end of image data') # > is EOD for hex encoded filterfor clarity
 
624
        self.code.append('grestore') # return coordinates to normal
 
625
 
 
626
# renderpdf - draws them onto a canvas
 
627
"""Usage:
 
628
    from reportlab.graphics import renderPS
 
629
    renderPS.draw(drawing, canvas, x, y)
 
630
Execute the script to see some test drawings."""
 
631
from shapes import *
 
632
from renderbase import Renderer
 
633
 
 
634
# hack so we only get warnings once each
 
635
#warnOnce = WarnOnce()
 
636
 
 
637
# the main entry point for users...
 
638
def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary):
 
639
    """As it says"""
 
640
    R = _PSRenderer()
 
641
    R.draw(drawing, canvas, x, y, showBoundary=showBoundary)
 
642
 
 
643
def _pointsFromList(L):
 
644
    '''
 
645
    given a list of coordinates [x0, y0, x1, y1....]
 
646
    produce a list of points [(x0,y0), (y1,y0),....]
 
647
    '''
 
648
    P=[]
 
649
    for i in range(0,len(L),2):
 
650
        P.append((L[i],L[i+1]))
 
651
    return P
 
652
 
 
653
class _PSRenderer(Renderer):
 
654
    """This draws onto a EPS document.  It needs to be a class
 
655
    rather than a function, as some EPS-specific state tracking is
 
656
    needed outside of the state info in the SVG model."""
 
657
 
 
658
    def __init__(self):
 
659
        self._tracker = StateTracker()
 
660
 
 
661
    def drawNode(self, node):
 
662
        """This is the recursive method called for each node
 
663
        in the tree"""
 
664
        self._canvas.comment('begin node %s'%`node`)
 
665
        color = self._canvas._color
 
666
        if not (isinstance(node, Path) and node.isClipPath):
 
667
            self._canvas.saveState()
 
668
 
 
669
        #apply state changes
 
670
        deltas = getStateDelta(node)
 
671
        self._tracker.push(deltas)
 
672
        self.applyStateChanges(deltas, {})
 
673
 
 
674
        #draw the object, or recurse
 
675
        self.drawNodeDispatcher(node)
 
676
 
 
677
        rDeltas = self._tracker.pop()
 
678
        if not (isinstance(node, Path) and node.isClipPath):
 
679
            self._canvas.restoreState()
 
680
        self._canvas.comment('end node %s'%`node`)
 
681
        self._canvas._color = color
 
682
 
 
683
        #restore things we might have lost (without actually doing anything).
 
684
        for k, v in rDeltas.items():
 
685
            if self._restores.has_key(k):
 
686
                setattr(self._canvas,self._restores[k],v)
 
687
 
 
688
##  _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap',
 
689
##              'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font',
 
690
##              'font_size':'_fontSize'}
 
691
    _restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap',
 
692
                'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font',
 
693
                'fontSize':'_fontSize'}
 
694
 
 
695
    def drawRect(self, rect):
 
696
        if rect.rx == rect.ry == 0:
 
697
            #plain old rectangle
 
698
            self._canvas.rect(
 
699
                    rect.x, rect.y,
 
700
                    rect.x+rect.width, rect.y+rect.height)
 
701
        else:
 
702
            #cheat and assume ry = rx; better to generalize
 
703
            #pdfgen roundRect function.  TODO
 
704
            self._canvas.roundRect(
 
705
                    rect.x, rect.y,
 
706
                    rect.x+rect.width, rect.y+rect.height, rect.rx, rect.ry
 
707
                    )
 
708
 
 
709
    def drawLine(self, line):
 
710
        if self._canvas._strokeColor:
 
711
            self._canvas.line(line.x1, line.y1, line.x2, line.y2)
 
712
 
 
713
    def drawCircle(self, circle):
 
714
        self._canvas.circle( circle.cx, circle.cy, circle.r)
 
715
 
 
716
    def drawWedge(self, wedge):
 
717
        yradius, radius1, yradius1 = wedge._xtraRadii()
 
718
        if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
 
719
            startangledegrees = wedge.startangledegrees
 
720
            endangledegrees = wedge.endangledegrees
 
721
            centerx= wedge.centerx
 
722
            centery = wedge.centery
 
723
            radius = wedge.radius
 
724
            extent = endangledegrees - startangledegrees
 
725
            self._canvas.drawArc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
 
726
                startangledegrees, extent, fromcenter=1)
 
727
        else:
 
728
            self.drawPolygon(wedge.asPolygon())
 
729
 
 
730
    def drawPolyLine(self, p):
 
731
        if self._canvas._strokeColor:
 
732
            self._canvas.polyLine(_pointsFromList(p.points))
 
733
 
 
734
    def drawEllipse(self, ellipse):
 
735
        #need to convert to pdfgen's bounding box representation
 
736
        x1 = ellipse.cx - ellipse.rx
 
737
        x2 = ellipse.cx + ellipse.rx
 
738
        y1 = ellipse.cy - ellipse.ry
 
739
        y2 = ellipse.cy + ellipse.ry
 
740
        self._canvas.ellipse(x1,y1,x2,y2)
 
741
 
 
742
    def drawPolygon(self, p):
 
743
        self._canvas.polygon(_pointsFromList(p.points), closed=1)
 
744
 
 
745
    def drawString(self, stringObj):
 
746
        if self._canvas._fillColor:
 
747
            S = self._tracker.getState()
 
748
            text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
 
749
            if not text_anchor in ['start','inherited']:
 
750
                font, fontSize = S['fontName'], S['fontSize']
 
751
                textLen = stringWidth(text, font,fontSize)
 
752
                if text_anchor=='end':
 
753
                    x = x-textLen
 
754
                elif text_anchor=='middle':
 
755
                    x = x - textLen/2
 
756
                else:
 
757
                    raise ValueError, 'bad value for text_anchor '+str(text_anchor)
 
758
            self._canvas.drawString(x,y,text)
 
759
 
 
760
    def drawPath(self, path):
 
761
        from reportlab.graphics.shapes import _renderPath
 
762
        c = self._canvas
 
763
        drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath)
 
764
        isClosed = _renderPath(path, drawFuncs)
 
765
        if not isClosed:
 
766
            c._fillColor = None
 
767
        c._fillAndStroke([], clip=path.isClipPath)
 
768
 
 
769
    def applyStateChanges(self, delta, newState):
 
770
        """This takes a set of states, and outputs the operators
 
771
        needed to set those properties"""
 
772
        for key, value in delta.items():
 
773
            if key == 'transform':
 
774
                self._canvas.transform(value[0], value[1], value[2],
 
775
                                 value[3], value[4], value[5])
 
776
            elif key == 'strokeColor':
 
777
                #this has different semantics in PDF to SVG;
 
778
                #we always have a color, and either do or do
 
779
                #not apply it; in SVG one can have a 'None' color
 
780
                self._canvas.setStrokeColor(value)
 
781
            elif key == 'strokeWidth':
 
782
                self._canvas.setLineWidth(value)
 
783
            elif key == 'strokeLineCap':  #0,1,2
 
784
                self._canvas.setLineCap(value)
 
785
            elif key == 'strokeLineJoin':
 
786
                self._canvas.setLineJoin(value)
 
787
            elif key == 'strokeDashArray':
 
788
                if value:
 
789
                    self._canvas.setDash(value)
 
790
                else:
 
791
                    self._canvas.setDash()
 
792
##          elif key == 'stroke_opacity':
 
793
##              warnOnce('Stroke Opacity not supported yet')
 
794
            elif key == 'fillColor':
 
795
                #this has different semantics in PDF to SVG;
 
796
                #we always have a color, and either do or do
 
797
                #not apply it; in SVG one can have a 'None' color
 
798
                self._canvas.setFillColor(value)
 
799
##          elif key == 'fill_rule':
 
800
##              warnOnce('Fill rules not done yet')
 
801
##          elif key == 'fill_opacity':
 
802
##              warnOnce('Fill opacity not done yet')
 
803
            elif key in ['fontSize', 'fontName']:
 
804
                # both need setting together in PDF
 
805
                # one or both might be in the deltas,
 
806
                # so need to get whichever is missing
 
807
                fontname = delta.get('fontName', self._canvas._font)
 
808
                fontsize = delta.get('fontSize', self._canvas._fontSize)
 
809
                self._canvas.setFont(fontname, fontsize)
 
810
 
 
811
def drawToFile(d,fn, showBoundary=rl_config.showBoundary):
 
812
    c = PSCanvas((d.width,d.height))
 
813
    draw(d, c, 0, 0, showBoundary=showBoundary)
 
814
    c.save(fn)
 
815
 
 
816
def drawToString(d, showBoundary=rl_config.showBoundary):
 
817
    "Returns a PS as a string in memory, without touching the disk"
 
818
    s = getStringIO()
 
819
    drawToFile(d, s, showBoundary=showBoundary)
 
820
    return s.getvalue()
 
821
 
 
822
#########################################################
 
823
#
 
824
#   test code.  First, defin a bunch of drawings.
 
825
#   Routine to draw them comes at the end.
 
826
#
 
827
#########################################################
 
828
def test(outdir='epsout'):
 
829
    import os
 
830
    # print all drawings and their doc strings from the test
 
831
    # file
 
832
    if not os.path.isdir(outdir):
 
833
        os.mkdir(outdir)
 
834
    #grab all drawings from the test module
 
835
    import testshapes
 
836
    drawings = []
 
837
 
 
838
    for funcname in dir(testshapes):
 
839
        #if funcname[0:11] == 'getDrawing2':
 
840
        #    print 'hacked to only show drawing 2'
 
841
        if funcname[0:10] == 'getDrawing':
 
842
            drawing = eval('testshapes.' + funcname + '()')  #execute it
 
843
            docstring = eval('testshapes.' + funcname + '.__doc__')
 
844
            drawings.append((drawing, docstring))
 
845
 
 
846
    i = 0
 
847
    for (d, docstring) in drawings:
 
848
        filename = outdir + os.sep + 'renderPS_%d.eps'%i
 
849
        drawToFile(d,filename)
 
850
        print 'saved', filename
 
851
        i = i + 1
 
852
 
 
853
if __name__=='__main__':
 
854
    import sys
 
855
    if len(sys.argv)>1:
 
856
        outdir = sys.argv[1]
 
857
    else:
 
858
        outdir = 'epsout'
 
859
    test(outdir)