diff options
Diffstat (limited to 'bin/reportlab/graphics/renderPS.py')
| -rw-r--r-- | bin/reportlab/graphics/renderPS.py | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/bin/reportlab/graphics/renderPS.py b/bin/reportlab/graphics/renderPS.py new file mode 100644 index 00000000000..e117bb7fc89 --- /dev/null +++ b/bin/reportlab/graphics/renderPS.py @@ -0,0 +1,859 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/renderPS.py +__version__=''' $Id$ ''' +import string, types +from reportlab.pdfbase.pdfmetrics import getFont, stringWidth # for font info +from reportlab.lib.utils import fp_str, getStringIO +from reportlab.lib.colors import black +from reportlab.graphics.renderbase import StateTracker, getStateDelta +from reportlab.graphics.shapes import STATE_DEFAULTS +import math +from types import StringType +from operator import getitem +from reportlab import rl_config + + +# we need to create encoding vectors for each font we use, or they will +# come out in Adobe's old StandardEncoding, which NOBODY uses. +PS_WinAnsiEncoding=""" +/RE { %def + findfont begin + currentdict dup length dict begin + { %forall + 1 index /FID ne { def } { pop pop } ifelse + } forall + /FontName exch def dup length 0 ne { %if + /Encoding Encoding 256 array copy def + 0 exch { %forall + dup type /nametype eq { %ifelse + Encoding 2 index 2 index put + pop 1 add + }{ %else + exch pop + } ifelse + } forall + } if pop + currentdict dup end end + /FontName get exch definefont pop +} bind def + +/WinAnsiEncoding [ + 39/quotesingle 96/grave 128/euro 130/quotesinglbase/florin/quotedblbase + /ellipsis/dagger/daggerdbl/circumflex/perthousand + /Scaron/guilsinglleft/OE 145/quoteleft/quoteright + /quotedblleft/quotedblright/bullet/endash/emdash + /tilde/trademark/scaron/guilsinglright/oe/dotlessi + 159/Ydieresis 164/currency 166/brokenbar 168/dieresis/copyright + /ordfeminine 172/logicalnot 174/registered/macron/ring + 177/plusminus/twosuperior/threesuperior/acute/mu + 183/periodcentered/cedilla/onesuperior/ordmasculine + 188/onequarter/onehalf/threequarters 192/Agrave/Aacute + /Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla + /Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute + /Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute + /Ocircumflex/Otilde/Odieresis/multiply/Oslash + /Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn + /germandbls/agrave/aacute/acircumflex/atilde/adieresis + /aring/ae/ccedilla/egrave/eacute/ecircumflex + /edieresis/igrave/iacute/icircumflex/idieresis + /eth/ntilde/ograve/oacute/ocircumflex/otilde + /odieresis/divide/oslash/ugrave/uacute/ucircumflex + /udieresis/yacute/thorn/ydieresis +] def + +""" + +class PSCanvas: + def __init__(self,size=(300,300), PostScriptLevel=2): + self.width, self.height = size + self.code = [] + self._sep = '\n' + self._strokeColor = self._fillColor = self._lineWidth = \ + self._font = self._fontSize = self._lineCap = \ + self._lineJoin = self._color = None + + + self._fontsUsed = [] # track them as we go + + self.setFont(STATE_DEFAULTS['fontName'],STATE_DEFAULTS['fontSize']) + self.setStrokeColor(STATE_DEFAULTS['strokeColor']) + self.setLineCap(2) + self.setLineJoin(0) + self.setLineWidth(1) + + self.PostScriptLevel=PostScriptLevel + + def comment(self,msg): + self.code.append('%'+msg) + + def drawImage(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version + # select between postscript level 1 or level 2 + if PostScriptLevel==1: + self._drawImageLevel1(image, x1,y1, x2=None,y2=None) + elif PostScriptLevel == 2 : + self._drawImageLevel2(image, x1,y1, x2=None,y2=None) + else : + raise 'PostScriptLevelException' + + def clear(self): + self.code.append('showpage') # ugh, this makes no sense oh well. + + def save(self,f=None): + if not hasattr(f,'write'): + file = open(f,'wb') + else: + file = f + if self.code[-1]!='showpage': self.clear() + self.code.insert(0,'''\ +%%!PS-Adobe-3.0 EPSF-3.0 +%%%%BoundingBox: 0 0 %d %d +%%%% Initialization: +/m {moveto} bind def +/l {lineto} bind def +/c {curveto} bind def + +%s +''' % (self.width,self.height, PS_WinAnsiEncoding)) + + # for each font used, reencode the vectors + fontReencode = [] + for fontName in self._fontsUsed: + fontReencode.append('WinAnsiEncoding /%s /%s RE' % (fontName, fontName)) + self.code.insert(1, string.join(fontReencode, self._sep)) + + file.write(string.join(self.code,self._sep)) + if file is not f: + file.close() + from reportlab.lib.utils import markfilename + markfilename(f,creatorcode='XPR3',filetype='EPSF') + + def saveState(self): + self.code.append('gsave') + + def restoreState(self): + self.code.append('grestore') + + def stringWidth(self, s, font=None, fontSize=None): + """Return the logical width of the string if it were drawn + in the current font (defaults to self.font).""" + font = font or self._font + fontSize = fontSize or self._fontSize + return stringWidth(s, font, fontSize) + + def setLineCap(self,v): + if self._lineCap!=v: + self._lineCap = v + self.code.append('%d setlinecap'%v) + + def setLineJoin(self,v): + if self._lineJoin!=v: + self._lineJoin = v + self.code.append('%d setlinejoin'%v) + + def setDash(self, array=[], phase=0): + """Two notations. pass two numbers, or an array and phase""" + # copied and modified from reportlab.canvas + psoperation = "setdash" + if type(array) == types.IntType or type(array) == types.FloatType: + self._code.append('[%s %s] 0 %s' % (array, phase, psoperation)) + elif type(array) == types.ListType or type(array) == types.TupleType: + assert phase >= 0, "phase is a length in user space" + textarray = string.join(map(str, array)) + self.code.append('[%s] %s %s' % (textarray, phase, psoperation)) + + def setStrokeColor(self, color): + self._strokeColor = color + self.setColor(color) + + def setColor(self, color): + if self._color!=color: + self._color = color + if color: + if hasattr(color, "cyan"): + self.code.append('%s setcmykcolor' % fp_str(color.cyan, color.magenta, color.yellow, color.black)) + else: + self.code.append('%s setrgbcolor' % fp_str(color.red, color.green, color.blue)) + + def setFillColor(self, color): + self._fillColor = color + self.setColor(color) + + def setLineWidth(self, width): + if width != self._lineWidth: + self._lineWidth = width + self.code.append('%s setlinewidth' % width) + + def setFont(self,font,fontSize,leading=None): + if self._font!=font or self._fontSize!=fontSize: + self._fontCodeLoc = len(self.code) + self._font = font + self._fontSize = fontSize + self.code.append('') + + def line(self, x1, y1, x2, y2): + if self._strokeColor != None: + self.code.append('%s m %s l stroke' % (fp_str(x1, y1), fp_str(x2, y2))) + + def _escape(self, s): + ''' + return a copy of string s with special characters in postscript strings + escaped with backslashes. + Have not handled characters that are converted normally in python strings + i.e. \n -> newline + ''' + str = string.replace(s, chr(0x5C), r'\\' ) + str = string.replace(str, '(', '\(' ) + str = string.replace(str, ')', '\)') + return str + + def drawString(self, x, y, s, angle=0): + if self._fillColor != None: + if not self.code[self._fontCodeLoc]: + psName = getFont(self._font).face.name + self.code[self._fontCodeLoc]='(%s) findfont %s scalefont setfont' % (psName,fp_str(self._fontSize)) + if psName not in self._fontsUsed: + self._fontsUsed.append(psName) + self.setColor(self._fillColor) + s = self._escape(s) +## before inverting... +## if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops +## self.code.append('%s m 1 -1 scale (%s) show 1 -1 scale' % (fp_str(x,y),s)) +## else : # general case, rotated text +## self.code.append('gsave %s %s translate %s rotate' % (x,y,angle)) +## self.code.append('0 0 m 1 -1 scale (%s) show' % s) +## self.code.append('grestore') + if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops + self.code.append('%s m (%s) show ' % (fp_str(x,y),s)) + else : # general case, rotated text + self.code.append('gsave %s %s translate %s rotate' % (x,y,angle)) + self.code.append('0 0 m (%s) show' % s) + self.code.append('grestore') + + def drawCentredString(self, x, y, text, text_anchor='middle'): + if self.fillColor is not None: + textLen = stringWidth(text, self._font,self._fontSize) + if text_anchor=='end': + x = x-textLen + elif text_anchor=='middle': + x = x - textLen/2 + self.drawString(x,y,text) + + def drawRightString(self, text, x, y): + self.drawCentredString(text,x,y,text_anchor='end') + + def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0): + codeline = '%s m %s curveto' + data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4)) + if self._fillColor != None: + self.setColor(self._fillColor) + self.code.append((codeline % data) + ' eofill') + if self._strokeColor != None: + self.setColor(self._strokeColor) + self.code.append((codeline % data) + + ((closed and ' closepath') or '') + + ' stroke') + + ######################################################################################## + + def rect(self, x1,y1, x2,y2, stroke=1, fill=1): + "Draw a rectangle between x1,y1, and x2,y2" + # Path is drawn in counter-clockwise direction" + + x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py + y1, y2 = min(y1,y2), max(y1, y2) + self.polygon(((x1,y1),(x2,y1),(x2,y2),(x1,y2)), closed=1, stroke=stroke, fill = fill) + + def roundRect(self, x1,y1, x2,y2, rx=8, ry=8): + """Draw a rounded rectangle between x1,y1, and x2,y2, + with corners inset as ellipses with x radius rx and y radius ry. + These should have x1<x2, y1<y2, rx>0, and ry>0.""" + # Path is drawn in counter-clockwise direction + + x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py + y1, y2 = min(y1,y2), max(y1, y2) + + # Note: arcto command draws a line from current point to beginning of arc + # save current matrix, translate to center of ellipse, scale by rx ry, and draw + # a circle of unit radius in counterclockwise dir, return to original matrix + # arguments are (cx, cy, rx, ry, startAngle, endAngle) + ellipsePath = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s arc setmatrix' + + # choice between newpath and moveTo beginning of arc + # go with newpath for precision, does this violate any assumptions in code??? + rrCode = ['newpath'] # Round Rect code path + # upper left corner ellipse is first + rrCode.append(ellipsePath % (x1+rx, y1+ry, rx, -ry, 90, 180)) + rrCode.append(ellipsePath % (x1+rx, y2-ry, rx, -ry, 180, 270)) + rrCode.append(ellipsePath % (x2-rx, y2-ry, rx, -ry, 270, 360)) + rrCode.append(ellipsePath % (x2-rx, y1+ry, rx, -ry, 0, 90) ) + rrCode.append('closepath') + + self._fillAndStroke(rrCode) + + def ellipse(self, x1,y1, x2,y2): + """Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. + These should have x1<x2 and y1<y2.""" + #Just invoke drawArc to actually draw the ellipse + self.drawArc(x1,y1, x2,y2) + + def circle(self, xc, yc, r): + self.ellipse(xc-r,yc-r, xc+r,yc+r) + + def drawArc(self, x1,y1, x2,y2, startAng=0, extent=360, fromcenter=0): + """Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2, + starting at startAng degrees and covering extent degrees. Angles + start with 0 to the right (+x) and increase counter-clockwise. + These should have x1<x2 and y1<y2.""" + #calculate centre of ellipse + #print "x1,y1,x2,y2,startAng,extent,fromcenter", x1,y1,x2,y2,startAng,extent,fromcenter + cx, cy = (x1+x2)/2.0, (y1+y2)/2.0 + rx, ry = (x2-x1)/2.0, (y2-y1)/2.0 + + codeline = self._genArcCode(x1, y1, x2, y2, startAng, extent) + + startAngleRadians = math.pi*startAng/180.0 + extentRadians = math.pi*extent/180.0 + endAngleRadians = startAngleRadians + extentRadians + + codelineAppended = 0 + + # fill portion + + if self._fillColor != None: + self.setColor(self._fillColor) + self.code.append(codeline) + codelineAppended = 1 + if self._strokeColor!=None: self.code.append('gsave') + self.lineTo(cx,cy) + self.code.append('eofill') + if self._strokeColor!=None: self.code.append('grestore') + + # stroke portion + if self._strokeColor != None: + # this is a bit hacked up. There is certainly a better way... + self.setColor(self._strokeColor) + (startx, starty) = (cx+rx*math.cos(startAngleRadians), cy+ry*math.sin(startAngleRadians)) + if not codelineAppended: + self.code.append(codeline) + if fromcenter: + # move to center + self.lineTo(cx,cy) + self.lineTo(startx, starty) + self.code.append('closepath') + self.code.append('stroke') + + def _genArcCode(self, x1, y1, x2, y2, startAng, extent): + "Calculate the path for an arc inscribed in rectangle defined by (x1,y1),(x2,y2)" + #calculate semi-minor and semi-major axes of ellipse + xScale = abs((x2-x1)/2.0) + yScale = abs((y2-y1)/2.0) + #calculate centre of ellipse + x, y = (x1+x2)/2.0, (y1+y2)/2.0 + + codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix' + + if extent >= 0: + arc='arc' + else: + arc='arcn' + data = (x,y, xScale, yScale, startAng, startAng+extent, arc) + + return codeline % data + + def polygon(self, p, closed=0, stroke=1, fill=1): + assert len(p) >= 2, 'Polygon must have 2 or more points' + + start = p[0] + p = p[1:] + + polyCode = [] + polyCode.append("%s m" % fp_str(start)) + for point in p: + polyCode.append("%s l" % fp_str(point)) + if closed: + polyCode.append("closepath") + + self._fillAndStroke(polyCode,stroke=stroke,fill=fill) + + def lines(self, lineList, color=None, width=None): + if self._strokeColor != None: + self._setColor(self._strokeColor) + codeline = '%s m %s l stroke' + for line in lineList: + self.code.append(codeline % (fp_str(line[0]),fp_str(line[1]))) + + def moveTo(self,x,y): + self.code.append('%s m' % fp_str(x, y)) + + def lineTo(self,x,y): + self.code.append('%s l' % fp_str(x, y)) + + def curveTo(self,x1,y1,x2,y2,x3,y3): + self.code.append('%s c' % fp_str(x1,y1,x2,y2,x3,y3)) + + def closePath(self): + self.code.append('closepath') + + def polyLine(self, p): + assert len(p) >= 1, 'Polyline must have 1 or more points' + if self._strokeColor != None: + self.setColor(self._strokeColor) + self.moveTo(p[0][0], p[0][1]) + for t in p[1:]: + self.lineTo(t[0], t[1]) + self.code.append('stroke') + + + def drawFigure(self, partList, closed=0): + figureCode = [] + first = 1 + + for part in partList: + op = part[0] + args = list(part[1:]) + + if op == figureLine: + if first: + first = 0 + figureCode.append("%s m" % fp_str(args[:2])) + else: + figureCode.append("%s l" % fp_str(args[:2])) + figureCode.append("%s l" % fp_str(args[2:])) + + elif op == figureArc: + first = 0 + x1,y1,x2,y2,startAngle,extent = args[:6] + figureCode.append(self._genArcCode(x1,y1,x2,y2,startAngle,extent)) + + elif op == figureCurve: + if first: + first = 0 + figureCode.append("%s m" % fp_str(args[:2])) + else: + figureCode.append("%s l" % fp_str(args[:2])) + figureCode.append("%s curveto" % fp_str(args[2:])) + else: + raise TypeError, "unknown figure operator: "+op + + if closed: + figureCode.append("closepath") + self._fillAndStroke(figureCode) + + def _fillAndStroke(self,code,clip=0,fill=1,stroke=1): + fill = self._fillColor and fill + stroke = self._strokeColor and stroke + if fill or stroke or clip: + self.code.extend(code) + if fill: + if stroke or clip: self.code.append("gsave") + self.setColor(self._fillColor) + self.code.append("eofill") + if stroke or clip: self.code.append("grestore") + if stroke: + if clip: self.code.append("gsave") + self.setColor(self._strokeColor) + self.code.append("stroke") + if clip: self.code.append("grestore") + if clip: + self.code.append("clip") + self.code.append("newpath") + + + def translate(self,x,y): + self.code.append('%s translate' % fp_str(x,y)) + + def scale(self,x,y): + self.code.append('%s scale' % fp_str(x,y)) + + def transform(self,a,b,c,d,e,f): + self.code.append('[%s] concat' % fp_str(a,b,c,d,e,f)) + + def _drawTimeResize(self,w,h): + '''if this is used we're probably in the wrong world''' + self.width, self.height = w, h + + ############################################################################################ + # drawImage(self. image, x1, y1, x2=None, y2=None) is now defined by either _drawImageLevel1 + # ._drawImageLevel2, the choice is made in .__init__ depending on option + def _drawImageLevel1(self, image, x1, y1, x2=None,y2=None): + # Postscript Level1 version available for fallback mode when Level2 doesn't work + """drawImage(self,image,x1,y1,x2=None,y2=None) : If x2 and y2 are ommitted, they are + calculated from image size. (x1,y1) is upper left of image, (x2,y2) is lower right of + image in piddle coordinates.""" + # For now let's start with 24 bit RGB images (following piddlePDF again) + print "Trying to drawImage in piddlePS" + component_depth = 8 + myimage = image.convert('RGB') + imgwidth, imgheight = myimage.size + if not x2: + x2 = imgwidth + x1 + if not y2: + y2 = y1 + imgheight + drawwidth = x2 - x1 + drawheight = y2 - y1 + print 'Image size (%d, %d); Draw size (%d, %d)' % (imgwidth, imgheight, drawwidth, drawheight) + # now I need to tell postscript how big image is + + # "image operators assume that they receive sample data from + # their data source in x-axis major index order. The coordinate + # of the lower-left corner of the first sample is (0,0), of the + # second (1,0) and so on" -PS2 ref manual p. 215 + # + # The ImageMatrix maps unit squre of user space to boundary of the source image + # + + # The CurrentTransformationMatrix (CTM) maps the unit square of + # user space to the rect...on the page that is to receive the + # image. A common ImageMatrix is [width 0 0 -height 0 height] + # (for a left to right, top to bottom image ) + + # first let's map the user coordinates start at offset x1,y1 on page + + self.code.extend([ + 'gsave', + '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image + '%s %s scale' % (drawwidth,drawheight), + '/scanline %d 3 mul string def' % imgwidth # scanline by multiples of image width + ]) + + # now push the dimensions and depth info onto the stack + # and push the ImageMatrix to map the source to the target rectangle (see above) + # finally specify source (PS2 pp. 225 ) and by exmample + self.code.extend([ + '%s %s %s' % (imgwidth, imgheight, component_depth), + '[%s %s %s %s %s %s]' % (imgwidth, 0, 0, -imgheight, 0, imgheight), + '{ currentfile scanline readhexstring pop } false 3', + 'colorimage ' + ]) + + # data source output--now we just need to deliver a hex encode + # series of lines of the right overall size can follow + # piddlePDF again + + rawimage = myimage.tostring() + assert(len(rawimage) == imgwidth*imgheight, 'Wrong amount of data for image') + #compressed = zlib.compress(rawimage) # no zlib at moment + hex_encoded = self._AsciiHexEncode(rawimage) + + # write in blocks of 78 chars per line + outstream = getStringIO(hex_encoded) + + dataline = outstream.read(78) + while dataline <> "": + self.code.append(dataline) + dataline= outstream.read(78) + self.code.append('% end of image data') # for clarity + self.code.append('grestore') # return coordinates to normal + + # end of drawImage + def _AsciiHexEncode(self, input): # also based on piddlePDF + "Helper function used by images" + output = getStringIO() + for char in input: + output.write('%02x' % ord(char)) + return output.getvalue() + + def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version + Image = import_Image() + if not Image: return + ### what sort of image are we to draw + if image.mode=='L' : + print 'found image.mode= L' + imBitsPerComponent = 8 + imNumComponents = 1 + myimage = image + elif image.mode == '1': + print 'found image.mode= 1' + myimage = image.convert('L') + imNumComponents = 1 + myimage = image + else : + myimage = image.convert('RGB') + imNumComponents = 3 + imBitsPerComponent = 8 + + imwidth, imheight = myimage.size + # print 'imwidth = %s, imheight = %s' % myimage.size + if not x2: + x2 = imwidth + x1 + if not y2: + y2 = y1 + imheight + drawwidth = x2 - x1 + drawheight = y2 - y1 + self.code.extend([ + 'gsave', + '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image + '%s %s scale' % (drawwidth,drawheight)]) + + if imNumComponents == 3 : + self.code.append('/DeviceRGB setcolorspace') + elif imNumComponents == 1 : + self.code.append('/DeviceGray setcolorspace') + print 'setting colorspace gray' + # create the image dictionary + self.code.append(""" +<< +/ImageType 1 +/Width %d /Height %d %% dimensions of source image +/BitsPerComponent %d""" % (imwidth, imheight, imBitsPerComponent) ) + + if imNumComponents == 1: + self.code.append('/Decode [0 1]') + if imNumComponents == 3: + self.code.append('/Decode [0 1 0 1 0 1] %% decode color values normally') + + self.code.extend([ '/ImageMatrix [%s 0 0 %s 0 %s]' % (imwidth, -imheight, imheight), + '/DataSource currentfile /ASCIIHexDecode filter', + '>> % End image dictionary', + 'image']) + # after image operator just need to dump image dat to file as hexstring + rawimage = myimage.tostring() + assert(len(rawimage) == imwidth*imheight, 'Wrong amount of data for image') + #compressed = zlib.compress(rawimage) # no zlib at moment + hex_encoded = self._AsciiHexEncode(rawimage) + + # write in blocks of 78 chars per line + outstream = getStringIO(hex_encoded) + + dataline = outstream.read(78) + while dataline <> "": + self.code.append(dataline) + dataline= outstream.read(78) + self.code.append('> % end of image data') # > is EOD for hex encoded filterfor clarity + self.code.append('grestore') # return coordinates to normal + +# renderpdf - draws them onto a canvas +"""Usage: + from reportlab.graphics import renderPS + renderPS.draw(drawing, canvas, x, y) +Execute the script to see some test drawings.""" +from shapes import * +from renderbase import Renderer + +# hack so we only get warnings once each +#warnOnce = WarnOnce() + +# the main entry point for users... +def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary): + """As it says""" + R = _PSRenderer() + R.draw(drawing, canvas, x, y, showBoundary=showBoundary) + +def _pointsFromList(L): + ''' + given a list of coordinates [x0, y0, x1, y1....] + produce a list of points [(x0,y0), (y1,y0),....] + ''' + P=[] + for i in range(0,len(L),2): + P.append((L[i],L[i+1])) + return P + +class _PSRenderer(Renderer): + """This draws onto a EPS document. It needs to be a class + rather than a function, as some EPS-specific state tracking is + needed outside of the state info in the SVG model.""" + + def __init__(self): + self._tracker = StateTracker() + + def drawNode(self, node): + """This is the recursive method called for each node + in the tree""" + self._canvas.comment('begin node %s'%`node`) + color = self._canvas._color + if not (isinstance(node, Path) and node.isClipPath): + self._canvas.saveState() + + #apply state changes + deltas = getStateDelta(node) + self._tracker.push(deltas) + self.applyStateChanges(deltas, {}) + + #draw the object, or recurse + self.drawNodeDispatcher(node) + + rDeltas = self._tracker.pop() + if not (isinstance(node, Path) and node.isClipPath): + self._canvas.restoreState() + self._canvas.comment('end node %s'%`node`) + self._canvas._color = color + + #restore things we might have lost (without actually doing anything). + for k, v in rDeltas.items(): + if self._restores.has_key(k): + setattr(self._canvas,self._restores[k],v) + +## _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap', +## 'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font', +## 'font_size':'_fontSize'} + _restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap', + 'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font', + 'fontSize':'_fontSize'} + + def drawRect(self, rect): + if rect.rx == rect.ry == 0: + #plain old rectangle + self._canvas.rect( + rect.x, rect.y, + rect.x+rect.width, rect.y+rect.height) + else: + #cheat and assume ry = rx; better to generalize + #pdfgen roundRect function. TODO + self._canvas.roundRect( + rect.x, rect.y, + rect.x+rect.width, rect.y+rect.height, rect.rx, rect.ry + ) + + def drawLine(self, line): + if self._canvas._strokeColor: + self._canvas.line(line.x1, line.y1, line.x2, line.y2) + + def drawCircle(self, circle): + self._canvas.circle( circle.cx, circle.cy, circle.r) + + def drawWedge(self, wedge): + yradius, radius1, yradius1 = wedge._xtraRadii() + if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None): + startangledegrees = wedge.startangledegrees + endangledegrees = wedge.endangledegrees + centerx= wedge.centerx + centery = wedge.centery + radius = wedge.radius + extent = endangledegrees - startangledegrees + self._canvas.drawArc(centerx-radius, centery-yradius, centerx+radius, centery+yradius, + startangledegrees, extent, fromcenter=1) + else: + self.drawPolygon(wedge.asPolygon()) + + def drawPolyLine(self, p): + if self._canvas._strokeColor: + self._canvas.polyLine(_pointsFromList(p.points)) + + def drawEllipse(self, ellipse): + #need to convert to pdfgen's bounding box representation + x1 = ellipse.cx - ellipse.rx + x2 = ellipse.cx + ellipse.rx + y1 = ellipse.cy - ellipse.ry + y2 = ellipse.cy + ellipse.ry + self._canvas.ellipse(x1,y1,x2,y2) + + def drawPolygon(self, p): + self._canvas.polygon(_pointsFromList(p.points), closed=1) + + def drawString(self, stringObj): + if self._canvas._fillColor: + S = self._tracker.getState() + text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text + if not text_anchor in ['start','inherited']: + font, fontSize = S['fontName'], S['fontSize'] + textLen = stringWidth(text, font,fontSize) + if text_anchor=='end': + x = x-textLen + elif text_anchor=='middle': + x = x - textLen/2 + else: + raise ValueError, 'bad value for text_anchor '+str(text_anchor) + self._canvas.drawString(x,y,text) + + def drawPath(self, path): + from reportlab.graphics.shapes import _renderPath + c = self._canvas + drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath) + isClosed = _renderPath(path, drawFuncs) + if not isClosed: + c._fillColor = None + c._fillAndStroke([], clip=path.isClipPath) + + def applyStateChanges(self, delta, newState): + """This takes a set of states, and outputs the operators + needed to set those properties""" + for key, value in delta.items(): + if key == 'transform': + self._canvas.transform(value[0], value[1], value[2], + value[3], value[4], value[5]) + elif key == 'strokeColor': + #this has different semantics in PDF to SVG; + #we always have a color, and either do or do + #not apply it; in SVG one can have a 'None' color + self._canvas.setStrokeColor(value) + elif key == 'strokeWidth': + self._canvas.setLineWidth(value) + elif key == 'strokeLineCap': #0,1,2 + self._canvas.setLineCap(value) + elif key == 'strokeLineJoin': + self._canvas.setLineJoin(value) + elif key == 'strokeDashArray': + if value: + self._canvas.setDash(value) + else: + self._canvas.setDash() +## elif key == 'stroke_opacity': +## warnOnce('Stroke Opacity not supported yet') + elif key == 'fillColor': + #this has different semantics in PDF to SVG; + #we always have a color, and either do or do + #not apply it; in SVG one can have a 'None' color + self._canvas.setFillColor(value) +## elif key == 'fill_rule': +## warnOnce('Fill rules not done yet') +## elif key == 'fill_opacity': +## warnOnce('Fill opacity not done yet') + elif key in ['fontSize', 'fontName']: + # both need setting together in PDF + # one or both might be in the deltas, + # so need to get whichever is missing + fontname = delta.get('fontName', self._canvas._font) + fontsize = delta.get('fontSize', self._canvas._fontSize) + self._canvas.setFont(fontname, fontsize) + +def drawToFile(d,fn, showBoundary=rl_config.showBoundary): + c = PSCanvas((d.width,d.height)) + draw(d, c, 0, 0, showBoundary=showBoundary) + c.save(fn) + +def drawToString(d, showBoundary=rl_config.showBoundary): + "Returns a PS as a string in memory, without touching the disk" + s = getStringIO() + drawToFile(d, s, showBoundary=showBoundary) + return s.getvalue() + +######################################################### +# +# test code. First, defin a bunch of drawings. +# Routine to draw them comes at the end. +# +######################################################### +def test(outdir='epsout'): + import os + # print all drawings and their doc strings from the test + # file + if not os.path.isdir(outdir): + os.mkdir(outdir) + #grab all drawings from the test module + import testshapes + drawings = [] + + for funcname in dir(testshapes): + #if funcname[0:11] == 'getDrawing2': + # print 'hacked to only show drawing 2' + if funcname[0:10] == 'getDrawing': + drawing = eval('testshapes.' + funcname + '()') #execute it + docstring = eval('testshapes.' + funcname + '.__doc__') + drawings.append((drawing, docstring)) + + i = 0 + for (d, docstring) in drawings: + filename = outdir + os.sep + 'renderPS_%d.eps'%i + drawToFile(d,filename) + print 'saved', filename + i = i + 1 + +if __name__=='__main__': + import sys + if len(sys.argv)>1: + outdir = sys.argv[1] + else: + outdir = 'epsout' + test(outdir) |
