361 lines
13 KiB
Python
361 lines
13 KiB
Python
#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/renderPDF.py
|
|
# renderPDF - draws Drawings onto a canvas
|
|
"""Usage:
|
|
import renderpdf
|
|
renderpdf.draw(drawing, canvas, x, y)
|
|
Execute the script to see some test drawings.
|
|
changed
|
|
"""
|
|
__version__=''' $Id: renderPDF.py 2830 2006-04-05 15:18:32Z rgbecker $ '''
|
|
|
|
from reportlab.graphics.shapes import *
|
|
from reportlab.pdfgen.canvas import Canvas
|
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
from reportlab.lib.utils import getStringIO
|
|
from reportlab import rl_config
|
|
from renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing
|
|
|
|
# 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
|
|
self._tracker = StateTracker()
|
|
|
|
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):
|
|
# currently not implemented in other renderers
|
|
if image.path and os.path.exists(image.path):
|
|
self._canvas.drawInlineImage(
|
|
image.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):
|
|
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):
|
|
if self._fill:
|
|
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 = x-textLen
|
|
elif text_anchor=='middle':
|
|
x = x - textLen/2
|
|
else:
|
|
raise ValueError, 'bad value for textAnchor '+str(text_anchor)
|
|
t = self._canvas.beginText(x,y)
|
|
t.textLine(text)
|
|
self._canvas.drawText(t)
|
|
|
|
def drawPath(self, path):
|
|
from reportlab.graphics.shapes import _renderPath
|
|
pdfPath = self._canvas.beginPath()
|
|
drawFuncs = (pdfPath.moveTo, pdfPath.lineTo, pdfPath.curveTo, pdfPath.close)
|
|
isClosed = _renderPath(path, drawFuncs)
|
|
if isClosed:
|
|
fill = self._fill
|
|
else:
|
|
fill = 0
|
|
if path.isClipPath:
|
|
self._canvas.clipPath(pdfPath, fill=fill, stroke=self._stroke)
|
|
else:
|
|
self._canvas.drawPath(pdfPath,
|
|
fill=fill,
|
|
stroke=self._stroke)
|
|
|
|
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 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._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 == 'stroke_dasharray':
|
|
# self._canvas.setDash(array=value)
|
|
elif key == 'strokeDashArray':
|
|
if value:
|
|
self._canvas.setDash(value)
|
|
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._canvas.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)
|
|
|
|
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):
|
|
"""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)
|
|
c = Canvas(fn)
|
|
c.setFont('Times-Roman', 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('Times-Roman', 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):
|
|
"Returns a PDF as a string in memory, without touching the disk"
|
|
s = getStringIO()
|
|
drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize)
|
|
return s.getvalue()
|
|
|
|
|
|
#########################################################
|
|
#
|
|
# test code. First, defin a bunch of drawings.
|
|
# Routine to draw them comes at the end.
|
|
#
|
|
#########################################################
|
|
|
|
|
|
def test():
|
|
c = Canvas('renderPDF.pdf')
|
|
c.setFont('Times-Roman', 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':
|
|
drawing = eval('testshapes.' + funcname + '()') #execute it
|
|
docstring = eval('testshapes.' + funcname + '.__doc__')
|
|
drawings.append((drawing, docstring))
|
|
|
|
#print in a loop, with their doc strings
|
|
c.setFont('Times-Roman', 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('Times-BoldItalic',12)
|
|
c.drawString(80, y, 'Drawing %d' % i)
|
|
c.setFont('Times-Roman',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()
|
|
print 'saved renderPDF.pdf'
|
|
|
|
##def testFlowable():
|
|
## """Makes a platypus document"""
|
|
## from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
|
## from reportlab.lib.styles import getSampleStyleSheet
|
|
## styles = getSampleStyleSheet()
|
|
## styNormal = styles['Normal']
|
|
##
|
|
## doc = SimpleDocTemplate('test_flowable.pdf')
|
|
## story = []
|
|
## story.append(Paragraph("This sees is a drawing can work as a flowable", styNormal))
|
|
##
|
|
## import testdrawings
|
|
## drawings = []
|
|
##
|
|
## for funcname in dir(testdrawings):
|
|
## if funcname[0:10] == 'getDrawing':
|
|
## drawing = eval('testdrawings.' + funcname + '()') #execute it
|
|
## docstring = eval('testdrawings.' + funcname + '.__doc__')
|
|
## story.append(Paragraph(docstring, styNormal))
|
|
## story.append(Spacer(18,18))
|
|
## story.append(drawing)
|
|
## story.append(Spacer(36,36))
|
|
##
|
|
## doc.build(story)
|
|
## print 'saves test_flowable.pdf'
|
|
|
|
if __name__=='__main__':
|
|
test()
|
|
#testFlowable()
|