renderPDF.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. #Copyright ReportLab Europe Ltd. 2000-2017
  2. #see license.txt for license details
  3. #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/renderPDF.py
  4. # renderPDF - draws Drawings onto a canvas
  5. __version__='3.3.0'
  6. __doc__="""Render Drawing objects within others PDFs or standalone
  7. Usage::
  8. import renderpdf
  9. renderpdf.draw(drawing, canvas, x, y)
  10. Execute the script to see some test drawings.
  11. changed
  12. """
  13. from reportlab.graphics.shapes import *
  14. from reportlab.pdfgen.canvas import Canvas
  15. from reportlab.pdfbase.pdfmetrics import stringWidth
  16. from reportlab.lib.utils import getBytesIO
  17. from reportlab import rl_config
  18. from reportlab.graphics.renderbase import Renderer, StateTracker, getStateDelta, renderScaledDrawing, STATE_DEFAULTS
  19. # the main entry point for users...
  20. def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_):
  21. """As it says"""
  22. R = _PDFRenderer()
  23. R.draw(renderScaledDrawing(drawing), canvas, x, y, showBoundary=showBoundary)
  24. class _PDFRenderer(Renderer):
  25. """This draws onto a PDF document. It needs to be a class
  26. rather than a function, as some PDF-specific state tracking is
  27. needed outside of the state info in the SVG model."""
  28. def __init__(self):
  29. self._stroke = 0
  30. self._fill = 0
  31. def drawNode(self, node):
  32. """This is the recursive method called for each node
  33. in the tree"""
  34. #print "pdf:drawNode", self
  35. #if node.__class__ is Wedge: stop
  36. if not (isinstance(node, Path) and node.isClipPath):
  37. self._canvas.saveState()
  38. #apply state changes
  39. deltas = getStateDelta(node)
  40. self._tracker.push(deltas)
  41. self.applyStateChanges(deltas, {})
  42. #draw the object, or recurse
  43. self.drawNodeDispatcher(node)
  44. self._tracker.pop()
  45. if not (isinstance(node, Path) and node.isClipPath):
  46. self._canvas.restoreState()
  47. def drawRect(self, rect):
  48. if rect.rx == rect.ry == 0:
  49. #plain old rectangle
  50. self._canvas.rect(
  51. rect.x, rect.y,
  52. rect.width, rect.height,
  53. stroke=self._stroke,
  54. fill=self._fill
  55. )
  56. else:
  57. #cheat and assume ry = rx; better to generalize
  58. #pdfgen roundRect function. TODO
  59. self._canvas.roundRect(
  60. rect.x, rect.y,
  61. rect.width, rect.height, rect.rx,
  62. fill=self._fill,
  63. stroke=self._stroke
  64. )
  65. def drawImage(self, image):
  66. path = image.path
  67. # currently not implemented in other renderers
  68. if path and (hasattr(path,'mode') or os.path.exists(image.path)):
  69. self._canvas.drawInlineImage(
  70. path,
  71. image.x, image.y,
  72. image.width, image.height
  73. )
  74. def drawLine(self, line):
  75. if self._stroke:
  76. self._canvas.line(line.x1, line.y1, line.x2, line.y2)
  77. def drawCircle(self, circle):
  78. self._canvas.circle(
  79. circle.cx, circle.cy, circle.r,
  80. fill=self._fill,
  81. stroke=self._stroke,
  82. )
  83. def drawPolyLine(self, polyline):
  84. if self._stroke:
  85. assert len(polyline.points) >= 2, 'Polyline must have 2 or more points'
  86. head, tail = polyline.points[0:2], polyline.points[2:],
  87. path = self._canvas.beginPath()
  88. path.moveTo(head[0], head[1])
  89. for i in range(0, len(tail), 2):
  90. path.lineTo(tail[i], tail[i+1])
  91. self._canvas.drawPath(path)
  92. def drawWedge(self, wedge):
  93. if wedge.annular:
  94. self.drawPath(wedge.asPolygon())
  95. else:
  96. centerx, centery, radius, startangledegrees, endangledegrees = \
  97. wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees
  98. yradius, radius1, yradius1 = wedge._xtraRadii()
  99. if yradius is None: yradius = radius
  100. angle = endangledegrees-startangledegrees
  101. path = self._canvas.beginPath()
  102. if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None):
  103. path.moveTo(centerx, centery)
  104. path.arcTo(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
  105. startangledegrees, angle)
  106. else:
  107. path.arc(centerx-radius, centery-yradius, centerx+radius, centery+yradius,
  108. startangledegrees, angle)
  109. path.arcTo(centerx-radius1, centery-yradius1, centerx+radius1, centery+yradius1,
  110. endangledegrees, -angle)
  111. path.close()
  112. self._canvas.drawPath(path,
  113. fill=self._fill,
  114. stroke=self._stroke,
  115. )
  116. def drawEllipse(self, ellipse):
  117. #need to convert to pdfgen's bounding box representation
  118. x1 = ellipse.cx - ellipse.rx
  119. x2 = ellipse.cx + ellipse.rx
  120. y1 = ellipse.cy - ellipse.ry
  121. y2 = ellipse.cy + ellipse.ry
  122. self._canvas.ellipse(x1,y1,x2,y2,fill=self._fill,stroke=self._stroke)
  123. def drawPolygon(self, polygon):
  124. assert len(polygon.points) >= 2, 'Polyline must have 2 or more points'
  125. head, tail = polygon.points[0:2], polygon.points[2:],
  126. path = self._canvas.beginPath()
  127. path.moveTo(head[0], head[1])
  128. for i in range(0, len(tail), 2):
  129. path.lineTo(tail[i], tail[i+1])
  130. path.close()
  131. self._canvas.drawPath(
  132. path,
  133. stroke=self._stroke,
  134. fill=self._fill,
  135. )
  136. def drawString(self, stringObj):
  137. textRenderMode = getattr(stringObj,'textRenderMode',0)
  138. needFill = textRenderMode in (0,2,4,6)
  139. needStroke = textRenderMode in (1,2,5,6)
  140. if (self._fill and needFill) or (self._stroke and needStroke):
  141. S = self._tracker.getState()
  142. text_anchor, x, y, text, enc = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text, stringObj.encoding
  143. if not text_anchor in ['start','inherited']:
  144. font, font_size = S['fontName'], S['fontSize']
  145. textLen = stringWidth(text, font, font_size, enc)
  146. if text_anchor=='end':
  147. x -= textLen
  148. elif text_anchor=='middle':
  149. x -= textLen*0.5
  150. elif text_anchor=='numeric':
  151. x -= numericXShift(text_anchor,text,textLen,font,font_size,enc)
  152. else:
  153. raise ValueError('bad value for textAnchor '+str(text_anchor))
  154. self._canvas.drawString(x, y, text, mode=textRenderMode or None)
  155. def drawPath(self, path):
  156. from reportlab.graphics.shapes import _renderPath
  157. pdfPath = self._canvas.beginPath()
  158. drawFuncs = (pdfPath.moveTo, pdfPath.lineTo, pdfPath.curveTo, pdfPath.close)
  159. autoclose = getattr(path,'autoclose','')
  160. fill = self._fill
  161. stroke = self._stroke
  162. isClosed = _renderPath(path, drawFuncs, forceClose=fill and autoclose=='pdf')
  163. dP = self._canvas.drawPath
  164. cP = self._canvas.clipPath if path.isClipPath else dP
  165. fillMode = getattr(path,'fillMode',None)
  166. if autoclose=='svg':
  167. if fill and stroke and not isClosed:
  168. cP(pdfPath, fill=fill, stroke=0)
  169. dP(pdfPath, stroke=stroke, fill=0, fillMode=fillMode)
  170. else:
  171. cP(pdfPath, fill=fill, stroke=stroke, fillMode=fillMode)
  172. elif autoclose=='pdf':
  173. cP(pdfPath, fill=fill, stroke=stroke, fillMode=fillMode)
  174. else:
  175. #our old broken default
  176. if not isClosed:
  177. fill = 0
  178. cP(pdfPath, fill=fill, stroke=stroke, fillMode=fillMode)
  179. def setStrokeColor(self,c):
  180. self._canvas.setStrokeColor(c)
  181. def setFillColor(self,c):
  182. self._canvas.setFillColor(c)
  183. def applyStateChanges(self, delta, newState):
  184. """This takes a set of states, and outputs the PDF operators
  185. needed to set those properties"""
  186. for key, value in (sorted(delta.items()) if rl_config.invariant else delta.items()):
  187. if key == 'transform':
  188. self._canvas.transform(value[0], value[1], value[2],
  189. value[3], value[4], value[5])
  190. elif key == 'strokeColor':
  191. #this has different semantics in PDF to SVG;
  192. #we always have a color, and either do or do
  193. #not apply it; in SVG one can have a 'None' color
  194. if value is None:
  195. self._stroke = 0
  196. else:
  197. self._stroke = 1
  198. self.setStrokeColor(value)
  199. elif key == 'strokeWidth':
  200. self._canvas.setLineWidth(value)
  201. elif key == 'strokeLineCap': #0,1,2
  202. self._canvas.setLineCap(value)
  203. elif key == 'strokeLineJoin':
  204. self._canvas.setLineJoin(value)
  205. # elif key == 'stroke_dasharray':
  206. # self._canvas.setDash(array=value)
  207. elif key == 'strokeDashArray':
  208. if value:
  209. if isinstance(value,(list,tuple)) and len(value)==2 and isinstance(value[1],(tuple,list)):
  210. phase = value[0]
  211. value = value[1]
  212. else:
  213. phase = 0
  214. self._canvas.setDash(value,phase)
  215. else:
  216. self._canvas.setDash()
  217. elif key == 'fillColor':
  218. #this has different semantics in PDF to SVG;
  219. #we always have a color, and either do or do
  220. #not apply it; in SVG one can have a 'None' color
  221. if value is None:
  222. self._fill = 0
  223. else:
  224. self._fill = 1
  225. self.setFillColor(value)
  226. elif key in ['fontSize', 'fontName']:
  227. # both need setting together in PDF
  228. # one or both might be in the deltas,
  229. # so need to get whichever is missing
  230. fontname = delta.get('fontName', self._canvas._fontname)
  231. fontsize = delta.get('fontSize', self._canvas._fontsize)
  232. self._canvas.setFont(fontname, fontsize)
  233. elif key=='fillOpacity':
  234. if value is not None:
  235. self._canvas.setFillAlpha(value)
  236. elif key=='strokeOpacity':
  237. if value is not None:
  238. self._canvas.setStrokeAlpha(value)
  239. elif key=='fillOverprint':
  240. self._canvas.setFillOverprint(value)
  241. elif key=='strokeOverprint':
  242. self._canvas.setStrokeOverprint(value)
  243. elif key=='overprintMask':
  244. self._canvas.setOverprintMask(value)
  245. elif key=='fillMode':
  246. self._canvas._fillMode = value
  247. from reportlab.platypus import Flowable
  248. class GraphicsFlowable(Flowable):
  249. """Flowable wrapper around a Pingo drawing"""
  250. def __init__(self, drawing):
  251. self.drawing = drawing
  252. self.width = self.drawing.width
  253. self.height = self.drawing.height
  254. def draw(self):
  255. draw(self.drawing, self.canv, 0, 0)
  256. def drawToFile(d, fn, msg="", showBoundary=rl_config._unset_, autoSize=1, canvasKwds={}):
  257. """Makes a one-page PDF with just the drawing.
  258. If autoSize=1, the PDF will be the same size as
  259. the drawing; if 0, it will place the drawing on
  260. an A4 page with a title above it - possibly overflowing
  261. if too big."""
  262. d = renderScaledDrawing(d)
  263. for x in ('Name','Size'):
  264. a = 'initialFont'+x
  265. canvasKwds[a] = getattr(d,a,canvasKwds.pop(a,STATE_DEFAULTS['font'+x]))
  266. c = Canvas(fn,**canvasKwds)
  267. if msg:
  268. c.setFont(rl_config.defaultGraphicsFontName, 36)
  269. c.drawString(80, 750, msg)
  270. c.setTitle(msg)
  271. if autoSize:
  272. c.setPageSize((d.width, d.height))
  273. draw(d, c, 0, 0, showBoundary=showBoundary)
  274. else:
  275. #show with a title
  276. c.setFont(rl_config.defaultGraphicsFontName, 12)
  277. y = 740
  278. i = 1
  279. y = y - d.height
  280. draw(d, c, 80, y, showBoundary=showBoundary)
  281. c.showPage()
  282. c.save()
  283. if sys.platform=='mac' and not hasattr(fn, "write"):
  284. try:
  285. import macfs, macostools
  286. macfs.FSSpec(fn).SetCreatorType("CARO", "PDF ")
  287. macostools.touched(fn)
  288. except:
  289. pass
  290. def drawToString(d, msg="", showBoundary=rl_config._unset_,autoSize=1,canvasKwds={}):
  291. "Returns a PDF as a string in memory, without touching the disk"
  292. s = getBytesIO()
  293. drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize, canvasKwds=canvasKwds)
  294. return s.getvalue()
  295. #########################################################
  296. #
  297. # test code. First, define a bunch of drawings.
  298. # Routine to draw them comes at the end.
  299. #
  300. #########################################################
  301. def test(outDir='pdfout',shout=False):
  302. from reportlab.graphics.shapes import _baseGFontName, _baseGFontNameBI
  303. from reportlab.rl_config import verbose
  304. import os
  305. if not os.path.isdir(outDir):
  306. os.mkdir(outDir)
  307. fn = os.path.join(outDir,'renderPDF.pdf')
  308. c = Canvas(fn)
  309. c.setFont(_baseGFontName, 36)
  310. c.drawString(80, 750, 'Graphics Test')
  311. # print all drawings and their doc strings from the test
  312. # file
  313. #grab all drawings from the test module
  314. from reportlab.graphics import testshapes
  315. drawings = []
  316. for funcname in dir(testshapes):
  317. if funcname[0:10] == 'getDrawing':
  318. func = getattr(testshapes,funcname)
  319. drawing = func() #execute it
  320. docstring = getattr(func,'__doc__','')
  321. drawings.append((drawing, docstring))
  322. #print in a loop, with their doc strings
  323. c.setFont(_baseGFontName, 12)
  324. y = 740
  325. i = 1
  326. for (drawing, docstring) in drawings:
  327. assert (docstring is not None), "Drawing %d has no docstring!" % i
  328. if y < 300: #allows 5-6 lines of text
  329. c.showPage()
  330. y = 740
  331. # draw a title
  332. y = y - 30
  333. c.setFont(_baseGFontNameBI,12)
  334. c.drawString(80, y, 'Drawing %d' % i)
  335. c.setFont(_baseGFontName,12)
  336. y = y - 14
  337. textObj = c.beginText(80, y)
  338. textObj.textLines(docstring)
  339. c.drawText(textObj)
  340. y = textObj.getY()
  341. y = y - drawing.height
  342. draw(drawing, c, 80, y)
  343. i = i + 1
  344. if y!=740: c.showPage()
  345. c.save()
  346. if shout or verbose>2:
  347. print('saved %s' % ascii(fn))
  348. if __name__=='__main__':
  349. test(shout=True)
  350. import sys
  351. if len(sys.argv)>1:
  352. outdir = sys.argv[1]
  353. else:
  354. outdir = 'pdfout'
  355. test(outdir,shout=True)
  356. #testFlowable()