123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- #Copyright ReportLab Europe Ltd. 2000-2017
- #see license.txt for license details
- #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/renderPDF.py
- # renderPDF - draws Drawings onto a canvas
- __version__='3.3.0'
- __doc__="""Render Drawing objects within others PDFs or standalone
- Usage::
-
- import renderpdf
- renderpdf.draw(drawing, canvas, x, y)
- Execute the script to see some test drawings.
- changed
- """
- from reportlab.graphics.shapes import *
- from reportlab.pdfgen.canvas import Canvas
- from reportlab.pdfbase.pdfmetrics import stringWidth
- from reportlab.lib.utils import getBytesIO
- from reportlab import rl_config
- from reportlab.graphics.renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing, STATE_DEFAULTS
- # the main entry point for users...
- def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
- """As it says"""
- R = _PDFRenderer()
- R.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
- class _PDFRenderer(Renderer):
- """This draws onto a PDF document. It needs to be a class
- rather than a function, as some PDF-specific state tracking is
- needed outside of the state info in the SVG model."""
- def __init__(self):
- self._stroke = 0
- self._fill = 0
- def drawNode(self, node):
- """This is the recursive method called for each node
- in the tree"""
- #print "pdf:drawNode", self
- #if node.__class__ is Wedge: stop
- 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)
- self._tracker.pop()
- if not (isinstance(node, Path) and node.isClipPath):
- self._canvas.restoreState()
- def drawRect(self, rect):
- if rect.rx == rect.ry == 0:
- #plain old rectangle
- self._canvas.rect(
- rect.x, rect.y,
- rect.width, rect.height,
- stroke=self._stroke,
- fill=self._fill
- )
- else:
- #cheat and assume ry = rx; better to generalize
- #pdfgen roundRect function. TODO
- self._canvas.roundRect(
- rect.x, rect.y,
- rect.width, rect.height, rect.rx,
- fill=self._fill,
- stroke=self._stroke
- )
- def drawImage(self, image):
- path = image.path
- # currently not implemented in other renderers
- if path and (hasattr(path,'mode') or os.path.exists(image.path)):
- self._canvas.drawInlineImage(
- path,
- image.x, image.y,
- image.width, image.height
- )
- def drawLine(self, line):
- if self._stroke:
- self._canvas.line(line.x1, line.y1, line.x2, line.y2)
- def drawCircle(self, circle):
- self._canvas.circle(
- circle.cx, circle.cy, circle.r,
- fill=self._fill,
- stroke=self._stroke,
- )
- def drawPolyLine(self, polyline):
- if self._stroke:
- assert len(polyline.points) >= 2, 'Polyline must have 2 or more points'
- head, tail = polyline.points[0:2], polyline.points[2:],
- path = self._canvas.beginPath()
- path.moveTo(head[0], head[1])
- for i in range(0, len(tail), 2):
- path.lineTo(tail[i], tail[i+1])
- self._canvas.drawPath(path)
- def drawWedge(self, wedge):
- if wedge.annular:
- self.drawPath(wedge.asPolygon())
- else:
- centerx, centery, radius, startangledegrees, endangledegrees = \
- wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees
- yradius, radius1, yradius1 = wedge._xtraRadii()
- if yradius is None: yradius = radius
- angle = endangledegrees-startangledegrees
- path = self._canvas.beginPath()
- if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
- path.moveTo(centerx, centery)
- path.arcTo(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
- startangledegrees, angle)
- else:
- path.arc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
- startangledegrees, angle)
- path.arcTo(centerx-radius1, centery-yradius1, centerx+radius1, centery+yradius1,
- endangledegrees, -angle)
- path.close()
- self._canvas.drawPath(path,
- fill=self._fill,
- stroke=self._stroke,
- )
- 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,fill=self._fill,stroke=self._stroke)
- def drawPolygon(self, polygon):
- assert len(polygon.points) >= 2, 'Polyline must have 2 or more points'
- head, tail = polygon.points[0:2], polygon.points[2:],
- path = self._canvas.beginPath()
- path.moveTo(head[0], head[1])
- for i in range(0, len(tail), 2):
- path.lineTo(tail[i], tail[i+1])
- path.close()
- self._canvas.drawPath(
- path,
- stroke=self._stroke,
- fill=self._fill,
- )
- def drawString(self, stringObj):
- textRenderMode = getattr(stringObj,'textRenderMode',0)
- needFill = textRenderMode in (0,2,4,6)
- needStroke = textRenderMode in (1,2,5,6)
- if (self._fill and needFill) or (self._stroke and needStroke):
- S = self._tracker.getState()
- text_anchor, x, y, text, enc = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text, stringObj.encoding
- if not text_anchor in ['start','inherited']:
- font, font_size = S['fontName'], S['fontSize']
- textLen = stringWidth(text, font, font_size, enc)
- if text_anchor=='end':
- x -= textLen
- elif text_anchor=='middle':
- x -= textLen*0.5
- elif text_anchor=='numeric':
- x -= numericXShift(text_anchor,text,textLen,font,font_size,enc)
- else:
- raise ValueError('bad value for textAnchor '+str(text_anchor))
- self._canvas.drawString(x, y, text, mode=textRenderMode or None)
- def drawPath(self, path):
- from reportlab.graphics.shapes import _renderPath
- pdfPath = self._canvas.beginPath()
- drawFuncs = (pdfPath.moveTo, pdfPath.lineTo, pdfPath.curveTo, pdfPath.close)
- autoclose = getattr(path,'autoclose','')
- fill = self._fill
- stroke = self._stroke
- isClosed = _renderPath(path, drawFuncs, forceClose=fill and autoclose=='pdf')
- dP = self._canvas.drawPath
- cP = self._canvas.clipPath if path.isClipPath else dP
- fillMode = getattr(path,'fillMode',None)
- if autoclose=='svg':
- if fill and stroke and not isClosed:
- cP(pdfPath, fill=fill, stroke=0)
- dP(pdfPath, stroke=stroke, fill=0, fillMode=fillMode)
- else:
- cP(pdfPath, fill=fill, stroke=stroke, fillMode=fillMode)
- elif autoclose=='pdf':
- cP(pdfPath, fill=fill, stroke=stroke, fillMode=fillMode)
- else:
- #our old broken default
- if not isClosed:
- fill = 0
- cP(pdfPath, fill=fill, stroke=stroke, fillMode=fillMode)
- def setStrokeColor(self,c):
- self._canvas.setStrokeColor(c)
- def setFillColor(self,c):
- self._canvas.setFillColor(c)
- def applyStateChanges(self, delta, newState):
- """This takes a set of states, and outputs the PDF operators
- needed to set those properties"""
- for key, value in (sorted(delta.items()) if rl_config.invariant else 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
- if value is None:
- self._stroke = 0
- else:
- self._stroke = 1
- self.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 == 'stroke_dasharray':
- # self._canvas.setDash(array=value)
- elif key == 'strokeDashArray':
- if value:
- if isinstance(value,(list,tuple)) and len(value)==2 and isinstance(value[1],(tuple,list)):
- phase = value[0]
- value = value[1]
- else:
- phase = 0
- self._canvas.setDash(value,phase)
- else:
- self._canvas.setDash()
- 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
- if value is None:
- self._fill = 0
- else:
- self._fill = 1
- self.setFillColor(value)
- 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._fontname)
- fontsize = delta.get('fontSize', self._canvas._fontsize)
- self._canvas.setFont(fontname, fontsize)
- elif key=='fillOpacity':
- if value is not None:
- self._canvas.setFillAlpha(value)
- elif key=='strokeOpacity':
- if value is not None:
- self._canvas.setStrokeAlpha(value)
- elif key=='fillOverprint':
- self._canvas.setFillOverprint(value)
- elif key=='strokeOverprint':
- self._canvas.setStrokeOverprint(value)
- elif key=='overprintMask':
- self._canvas.setOverprintMask(value)
- elif key=='fillMode':
- self._canvas._fillMode = value
- from reportlab.platypus import Flowable
- class GraphicsFlowable(Flowable):
- """Flowable wrapper around a Pingo drawing"""
- def __init__(self, drawing):
- self.drawing = drawing
- self.width = self.drawing.width
- self.height = self.drawing.height
- def draw(self):
- draw(self.drawing, self.canv, 0, 0)
- def drawToFile(d, fn, msg="", showBoundary=rl_config._unset_, autoSize=1, canvasKwds={}):
- """Makes a one-page PDF with just the drawing.
- If autoSize=1, the PDF will be the same size as
- the drawing; if 0, it will place the drawing on
- an A4 page with a title above it - possibly overflowing
- if too big."""
- d = renderScaledDrawing(d)
- for x in ('Name','Size'):
- a = 'initialFont'+x
- canvasKwds[a] = getattr(d,a,canvasKwds.pop(a,STATE_DEFAULTS['font'+x]))
- c = Canvas(fn,**canvasKwds)
- if msg:
- c.setFont(rl_config.defaultGraphicsFontName, 36)
- c.drawString(80, 750, msg)
- c.setTitle(msg)
- if autoSize:
- c.setPageSize((d.width, d.height))
- draw(d, c, 0, 0, showBoundary=showBoundary)
- else:
- #show with a title
- c.setFont(rl_config.defaultGraphicsFontName, 12)
- y = 740
- i = 1
- y = y - d.height
- draw(d, c, 80, y, showBoundary=showBoundary)
- c.showPage()
- c.save()
- if sys.platform=='mac' and not hasattr(fn, "write"):
- try:
- import macfs, macostools
- macfs.FSSpec(fn).SetCreatorType("CARO", "PDF ")
- macostools.touched(fn)
- except:
- pass
- def drawToString(d, msg="", showBoundary=rl_config._unset_,autoSize=1,canvasKwds={}):
- "Returns a PDF as a string in memory, without touching the disk"
- s = getBytesIO()
- drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize, canvasKwds=canvasKwds)
- return s.getvalue()
- #########################################################
- #
- # test code. First, define a bunch of drawings.
- # Routine to draw them comes at the end.
- #
- #########################################################
- def test(outDir='pdfout',shout=False):
- from reportlab.graphics.shapes import _baseGFontName, _baseGFontNameBI
- from reportlab.rl_config import verbose
- import os
- if not os.path.isdir(outDir):
- os.mkdir(outDir)
- fn = os.path.join(outDir,'renderPDF.pdf')
- c = Canvas(fn)
- c.setFont(_baseGFontName, 36)
- c.drawString(80, 750, 'Graphics Test')
- # print all drawings and their doc strings from the test
- # file
- #grab all drawings from the test module
- from reportlab.graphics import testshapes
- drawings = []
- for funcname in dir(testshapes):
- if funcname[0:10] == 'getDrawing':
- func = getattr(testshapes,funcname)
- drawing = func() #execute it
- docstring = getattr(func,'__doc__','')
- drawings.append((drawing, docstring))
- #print in a loop, with their doc strings
- c.setFont(_baseGFontName, 12)
- y = 740
- i = 1
- for (drawing, docstring) in drawings:
- assert (docstring is not None), "Drawing %d has no docstring!" % i
- if y < 300: #allows 5-6 lines of text
- c.showPage()
- y = 740
- # draw a title
- y = y - 30
- c.setFont(_baseGFontNameBI,12)
- c.drawString(80, y, 'Drawing %d' % i)
- c.setFont(_baseGFontName,12)
- y = y - 14
- textObj = c.beginText(80, y)
- textObj.textLines(docstring)
- c.drawText(textObj)
- y = textObj.getY()
- y = y - drawing.height
- draw(drawing, c, 80, y)
- i = i + 1
- if y!=740: c.showPage()
- c.save()
- if shout or verbose>2:
- print('saved %s' % ascii(fn))
- if __name__=='__main__':
- test(shout=True)
- import sys
- if len(sys.argv)>1:
- outdir = sys.argv[1]
- else:
- outdir = 'pdfout'
- test(outdir,shout=True)
- #testFlowable()
|