pathobject.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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/pdfgen/pathobject.py
  4. __version__='3.3.0'
  5. __doc__="""
  6. PDFPathObject is an efficient way to draw paths on a Canvas. Do not
  7. instantiate directly, obtain one from the Canvas instead.
  8. Progress Reports:
  9. 8.83, 2000-01-13, gmcm: created from pdfgen.py
  10. """
  11. from reportlab.pdfgen import pdfgeom
  12. from reportlab.lib.rl_accel import fp_str
  13. class PDFPathObject:
  14. """Represents a graphic path. There are certain 'modes' to PDF
  15. drawing, and making a separate object to expose Path operations
  16. ensures they are completed with no run-time overhead. Ask
  17. the Canvas for a PDFPath with getNewPathObject(); moveto/lineto/
  18. curveto wherever you want; add whole shapes; and then add it back
  19. into the canvas with one of the relevant operators.
  20. Path objects are probably not long, so we pack onto one line
  21. the code argument allows a canvas to get the operations appended directly so
  22. avoiding the final getCode
  23. """
  24. def __init__(self,code=None):
  25. self._code = (code,[])[code is None]
  26. self._code_append = self._init_code_append
  27. def _init_code_append(self,c):
  28. assert c.endswith(' m') or c.endswith(' re'), 'path must start with a moveto or rect'
  29. code_append = self._code.append
  30. code_append('n')
  31. code_append(c)
  32. self._code_append = code_append
  33. def getCode(self):
  34. "pack onto one line; used internally"
  35. return ' '.join(self._code)
  36. def moveTo(self, x, y):
  37. self._code_append('%s m' % fp_str(x,y))
  38. def lineTo(self, x, y):
  39. self._code_append('%s l' % fp_str(x,y))
  40. def curveTo(self, x1, y1, x2, y2, x3, y3):
  41. self._code_append('%s c' % fp_str(x1, y1, x2, y2, x3, y3))
  42. def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
  43. """Contributed to piddlePDF by Robert Kern, 28/7/99.
  44. Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
  45. starting at startAng degrees and covering extent degrees. Angles
  46. start with 0 to the right (+x) and increase counter-clockwise.
  47. These should have x1<x2 and y1<y2.
  48. The algorithm is an elliptical generalization of the formulae in
  49. Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
  50. self._curves(pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent))
  51. def arcTo(self, x1,y1, x2,y2, startAng=0, extent=90):
  52. """Like arc, but draws a line from the current point to
  53. the start if the start is not the current point."""
  54. self._curves(pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent),'lineTo')
  55. def rect(self, x, y, width, height):
  56. """Adds a rectangle to the path"""
  57. self._code_append('%s re' % fp_str((x, y, width, height)))
  58. def ellipse(self, x, y, width, height):
  59. """adds an ellipse to the path"""
  60. self._curves(pdfgeom.bezierArc(x, y, x + width,y + height, 0, 360))
  61. def _curves(self,curves,initial='moveTo'):
  62. getattr(self,initial)(*curves[0][:2])
  63. for curve in curves:
  64. self.curveTo(*curve[2:])
  65. def circle(self, x_cen, y_cen, r):
  66. """adds a circle to the path"""
  67. x1 = x_cen - r
  68. y1 = y_cen - r
  69. width = height = 2*r
  70. self.ellipse(x1, y1, width, height)
  71. def roundRect(self, x, y, width, height, radius):
  72. """Draws a rectangle with rounded corners. The corners are
  73. approximately quadrants of a circle, with the given radius."""
  74. #use a precomputed set of factors for the bezier approximation
  75. #to a circle. There are six relevant points on the x axis and y axis.
  76. #sketch them and it should all make sense!
  77. m = 0.4472 #radius multiplier
  78. xhi = x,x+width
  79. xlo, xhi = min(xhi), max(xhi)
  80. yhi = y,y+height
  81. ylo, yhi = min(yhi), max(yhi)
  82. if isinstance(radius,(list,tuple)):
  83. r = [max(0,r) for r in radius]
  84. if len(r)<4: r += (4-len(r))*[0]
  85. self.moveTo(xlo + r[2], ylo) #start at bottom left
  86. self.lineTo(xhi - r[3], ylo) #bottom row
  87. if r[3]>0:
  88. t = m*r[3]
  89. self.curveTo(xhi - t, ylo, xhi, ylo + t, xhi, ylo + r[3]) #bottom right
  90. self.lineTo(xhi, yhi - r[1]) #right edge
  91. if r[1]>0:
  92. t = m*r[1]
  93. self.curveTo(xhi, yhi - t, xhi - t, yhi, xhi - r[1], yhi) #top right
  94. self.lineTo(xlo + r[0], yhi) #top row
  95. if r[0]>0:
  96. t = m*r[0]
  97. self.curveTo(xlo + t, yhi, xlo, yhi - t, xlo, yhi - r[0]) #top left
  98. self.lineTo(xlo, ylo + r[2]) #left edge
  99. if r[2]>0:
  100. t = m*r[2]
  101. self.curveTo(xlo, ylo + t, xlo + t, ylo, xlo + r[2], ylo) #bottom left
  102. # 4 radii top left top right bittom left bottom right
  103. else:
  104. t = m * radius
  105. self.moveTo(xlo + radius, ylo)
  106. self.lineTo(xhi - radius, ylo) #bottom row
  107. self.curveTo(xhi - t, ylo, xhi, ylo + t, xhi, ylo + radius) #bottom right
  108. self.lineTo(xhi, yhi - radius) #right edge
  109. self.curveTo(xhi, yhi - t, xhi - t, yhi, xhi - radius, yhi) #top right
  110. self.lineTo(xlo + radius, yhi) #top row
  111. self.curveTo(xlo + t, yhi, xlo, yhi - t, xlo, yhi - radius) #top left
  112. self.lineTo(xlo, ylo + radius) #left edge
  113. self.curveTo(xlo, ylo + t, xlo + t, ylo, xlo + radius, ylo) #bottom left
  114. self.close()
  115. def close(self):
  116. "draws a line back to where it started"
  117. self._code_append('h')