textlabels.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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/charts/textlabels.py
  4. __version__='3.3.0'
  5. import string
  6. from reportlab.lib import colors
  7. from reportlab.lib.utils import simpleSplit, _simpleSplit
  8. from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \
  9. isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString, isNoneOrCallable, \
  10. isSubclassOf
  11. from reportlab.lib.attrmap import *
  12. from reportlab.pdfbase.pdfmetrics import stringWidth, getAscentDescent, getFont
  13. from reportlab.graphics.shapes import Drawing, Group, Circle, Rect, String, STATE_DEFAULTS
  14. from reportlab.graphics.widgetbase import Widget, PropHolder
  15. from reportlab.graphics.shapes import _baseGFontName, DirectDraw
  16. from reportlab.platypus import XPreformatted, Paragraph, Flowable
  17. from reportlab.lib.styles import ParagraphStyle, PropertySet
  18. from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
  19. _ta2al = dict(start=TA_LEFT,end=TA_RIGHT,middle=TA_CENTER)
  20. from ..utils import (text2Path as _text2Path, #here for continuity
  21. pathNumTrunc as _pathNumTrunc,
  22. processGlyph as _processGlyph,
  23. text2PathDescription as _text2PathDescription)
  24. _A2BA= {
  25. 'x': {0:'n', 45:'ne', 90:'e', 135:'se', 180:'s', 225:'sw', 270:'w', 315: 'nw', -45: 'nw'},
  26. 'y': {0:'e', 45:'se', 90:'s', 135:'sw', 180:'w', 225:'nw', 270:'n', 315: 'ne', -45: 'ne'},
  27. }
  28. try:
  29. from rlextra.graphics.canvasadapter import DirectDrawFlowable
  30. except ImportError:
  31. DirectDrawFlowable = None
  32. _BA2TA={'w':'start','nw':'start','sw':'start','e':'end', 'ne': 'end', 'se':'end', 'n':'middle','s':'middle','c':'middle'}
  33. class Label(Widget):
  34. """A text label to attach to something else, such as a chart axis.
  35. This allows you to specify an offset, angle and many anchor
  36. properties relative to the label's origin. It allows, for example,
  37. angled multiline axis labels.
  38. """
  39. # fairly straight port of Robin Becker's textbox.py to new widgets
  40. # framework.
  41. _attrMap = AttrMap(
  42. x = AttrMapValue(isNumber,desc=''),
  43. y = AttrMapValue(isNumber,desc=''),
  44. dx = AttrMapValue(isNumber,desc='delta x - offset'),
  45. dy = AttrMapValue(isNumber,desc='delta y - offset'),
  46. angle = AttrMapValue(isNumber,desc='angle of label: default (0), 90 is vertical, 180 is upside down, etc'),
  47. boxAnchor = AttrMapValue(isBoxAnchor,desc='anchoring point of the label'),
  48. boxStrokeColor = AttrMapValue(isColorOrNone,desc='border color of the box'),
  49. boxStrokeWidth = AttrMapValue(isNumber,desc='border width'),
  50. boxFillColor = AttrMapValue(isColorOrNone,desc='the filling color of the box'),
  51. boxTarget = AttrMapValue(OneOf('normal','anti','lo','hi'),desc="one of ('normal','anti','lo','hi')"),
  52. fillColor = AttrMapValue(isColorOrNone,desc='label text color'),
  53. strokeColor = AttrMapValue(isColorOrNone,desc='label text border color'),
  54. strokeWidth = AttrMapValue(isNumber,desc='label text border width'),
  55. text = AttrMapValue(isString,desc='the actual text to display'),
  56. fontName = AttrMapValue(isString,desc='the name of the font used'),
  57. fontSize = AttrMapValue(isNumber,desc='the size of the font'),
  58. leading = AttrMapValue(isNumberOrNone,desc=''),
  59. width = AttrMapValue(isNumberOrNone,desc='the width of the label'),
  60. maxWidth = AttrMapValue(isNumberOrNone,desc='maximum width the label can grow to'),
  61. height = AttrMapValue(isNumberOrNone,desc='the height of the text'),
  62. textAnchor = AttrMapValue(isTextAnchor,desc='the anchoring point of the text inside the label'),
  63. visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
  64. topPadding = AttrMapValue(isNumber,desc='padding at top of box'),
  65. leftPadding = AttrMapValue(isNumber,desc='padding at left of box'),
  66. rightPadding = AttrMapValue(isNumber,desc='padding at right of box'),
  67. bottomPadding = AttrMapValue(isNumber,desc='padding at bottom of box'),
  68. useAscentDescent = AttrMapValue(isBoolean,desc="If True then the font's Ascent & Descent will be used to compute default heights and baseline."),
  69. customDrawChanger = AttrMapValue(isNoneOrCallable,desc="An instance of CustomDrawChanger to modify the behavior at draw time", _advancedUsage=1),
  70. ddf = AttrMapValue(NoneOr(isSubclassOf(DirectDraw),'NoneOrDirectDraw'),desc="A DirectDrawFlowable instance", _advancedUsage=1),
  71. ddfKlass = AttrMapValue(NoneOr(isSubclassOf(Flowable),'NoneOrDirectDraw'),desc="A DirectDrawFlowable instance", _advancedUsage=1),
  72. ddfStyle = AttrMapValue(NoneOr(isSubclassOf(PropertySet)),desc="A style for a ddfKlass or None", _advancedUsage=1),
  73. )
  74. def __init__(self,**kw):
  75. self._setKeywords(**kw)
  76. self._setKeywords(
  77. _text = 'Multi-Line\nString',
  78. boxAnchor = 'c',
  79. angle = 0,
  80. x = 0,
  81. y = 0,
  82. dx = 0,
  83. dy = 0,
  84. topPadding = 0,
  85. leftPadding = 0,
  86. rightPadding = 0,
  87. bottomPadding = 0,
  88. boxStrokeWidth = 0.5,
  89. boxStrokeColor = None,
  90. boxTarget = 'normal',
  91. strokeColor = None,
  92. boxFillColor = None,
  93. leading = None,
  94. width = None,
  95. maxWidth = None,
  96. height = None,
  97. fillColor = STATE_DEFAULTS['fillColor'],
  98. fontName = STATE_DEFAULTS['fontName'],
  99. fontSize = STATE_DEFAULTS['fontSize'],
  100. strokeWidth = 0.1,
  101. textAnchor = 'start',
  102. visible = 1,
  103. useAscentDescent = False,
  104. ddf = DirectDrawFlowable,
  105. ddfKlass = None,
  106. ddfStyle = None,
  107. )
  108. def setText(self, text):
  109. """Set the text property. May contain embedded newline characters.
  110. Called by the containing chart or axis."""
  111. self._text = text
  112. def setOrigin(self, x, y):
  113. """Set the origin. This would be the tick mark or bar top relative to
  114. which it is defined. Called by the containing chart or axis."""
  115. self.x = x
  116. self.y = y
  117. def demo(self):
  118. """This shows a label positioned with its top right corner
  119. at the top centre of the drawing, and rotated 45 degrees."""
  120. d = Drawing(200, 100)
  121. # mark the origin of the label
  122. d.add(Circle(100,90, 5, fillColor=colors.green))
  123. lab = Label()
  124. lab.setOrigin(100,90)
  125. lab.boxAnchor = 'ne'
  126. lab.angle = 45
  127. lab.dx = 0
  128. lab.dy = -20
  129. lab.boxStrokeColor = colors.green
  130. lab.setText('Another\nMulti-Line\nString')
  131. d.add(lab)
  132. return d
  133. def _getBoxAnchor(self):
  134. '''hook for allowing special box anchor effects'''
  135. ba = self.boxAnchor
  136. if ba in ('autox', 'autoy'):
  137. angle = self.angle
  138. na = (int((angle%360)/45.)*45)%360
  139. if not (na % 90): # we have a right angle case
  140. da = (angle - na) % 360
  141. if abs(da)>5:
  142. na = na + (da>0 and 45 or -45)
  143. ba = _A2BA[ba[-1]][na]
  144. return ba
  145. def _getBaseLineRatio(self):
  146. if self.useAscentDescent:
  147. self._ascent, self._descent = getAscentDescent(self.fontName,self.fontSize)
  148. self._baselineRatio = self._ascent/(self._ascent-self._descent)
  149. else:
  150. self._baselineRatio = 1/1.2
  151. def _computeSizeEnd(self,objH):
  152. self._height = self.height or (objH + self.topPadding + self.bottomPadding)
  153. self._ewidth = (self._width-self.leftPadding-self.rightPadding)
  154. self._eheight = (self._height-self.topPadding-self.bottomPadding)
  155. boxAnchor = self._getBoxAnchor()
  156. if boxAnchor in ['n','ne','nw']:
  157. self._top = -self.topPadding
  158. elif boxAnchor in ['s','sw','se']:
  159. self._top = self._height-self.topPadding
  160. else:
  161. self._top = 0.5*self._eheight
  162. self._bottom = self._top - self._eheight
  163. if boxAnchor in ['ne','e','se']:
  164. self._left = self.leftPadding - self._width
  165. elif boxAnchor in ['nw','w','sw']:
  166. self._left = self.leftPadding
  167. else:
  168. self._left = -self._ewidth*0.5
  169. self._right = self._left+self._ewidth
  170. def computeSize(self):
  171. # the thing will draw in its own coordinate system
  172. ddfKlass = getattr(self,'ddfKlass',None)
  173. if not ddfKlass:
  174. self._lineWidths = []
  175. self._lines = simpleSplit(self._text,self.fontName,self.fontSize,self.maxWidth)
  176. if not self.width:
  177. self._width = self.leftPadding+self.rightPadding
  178. if self._lines:
  179. self._lineWidths = [stringWidth(line,self.fontName,self.fontSize) for line in self._lines]
  180. self._width += max(self._lineWidths)
  181. else:
  182. self._width = self.width
  183. self._getBaseLineRatio()
  184. if self.leading:
  185. self._leading = self.leading
  186. elif self.useAscentDescent:
  187. self._leading = self._ascent - self._descent
  188. else:
  189. self._leading = self.fontSize*1.2
  190. objH = self._leading*len(self._lines)
  191. else:
  192. if self.ddf is None:
  193. raise RuntimeError('DirectDrawFlowable class is not available you need the rlextra package as well as reportlab')
  194. sty = dict(
  195. name='xlabel-generated',
  196. fontName=self.fontName,
  197. fontSize=self.fontSize,
  198. fillColor=self.fillColor,
  199. strokeColor=self.strokeColor,
  200. )
  201. sty = self._style = (ddfStyle.clone if self.ddfStyle else ParagraphStyle)(**sty)
  202. self._getBaseLineRatio()
  203. if self.useAscentDescent:
  204. sty.autoLeading = True
  205. sty.leading = self._ascent - self._descent
  206. else:
  207. sty.leading = self.leading if self.leading else self.fontSize*1.2
  208. self._leading = sty.leading
  209. ta = self._getTextAnchor()
  210. aW = self.maxWidth or 0x7fffffff
  211. if ta!='start':
  212. sty.alignment = TA_LEFT
  213. obj = ddfKlass(self._text,style=sty)
  214. _, objH = obj.wrap(aW,0x7fffffff)
  215. aW = self.maxWidth or obj._width_max
  216. sty.alignment = _ta2al[ta]
  217. self._ddfObj = obj = ddfKlass(self._text,style=sty)
  218. _, objH = obj.wrap(aW,0x7fffffff)
  219. if not self.width:
  220. self._width = self.leftPadding+self.rightPadding
  221. self._width += obj._width_max
  222. else:
  223. self._width = self.width
  224. self._computeSizeEnd(objH)
  225. def _getTextAnchor(self):
  226. '''This can be overridden to allow special effects'''
  227. ta = self.textAnchor
  228. if ta=='boxauto': ta = _BA2TA[self._getBoxAnchor()]
  229. return ta
  230. def _rawDraw(self):
  231. _text = self._text
  232. self._text = _text or ''
  233. self.computeSize()
  234. self._text = _text
  235. g = Group()
  236. g.translate(self.x + self.dx, self.y + self.dy)
  237. g.rotate(self.angle)
  238. ddfKlass = getattr(self,'ddfKlass',None)
  239. if ddfKlass:
  240. x = self._left
  241. else:
  242. y = self._top - self._leading*self._baselineRatio
  243. textAnchor = self._getTextAnchor()
  244. if textAnchor == 'start':
  245. x = self._left
  246. elif textAnchor == 'middle':
  247. x = self._left + self._ewidth*0.5
  248. else:
  249. x = self._right
  250. # paint box behind text just in case they
  251. # fill it
  252. if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth):
  253. g.add(Rect( self._left-self.leftPadding,
  254. self._bottom-self.bottomPadding,
  255. self._width,
  256. self._height,
  257. strokeColor=self.boxStrokeColor,
  258. strokeWidth=self.boxStrokeWidth,
  259. fillColor=self.boxFillColor)
  260. )
  261. if ddfKlass:
  262. g1 = Group()
  263. g1.translate(x,self._top-self._eheight)
  264. g1.add(self.ddf(self._ddfObj))
  265. g.add(g1)
  266. else:
  267. fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize
  268. strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, self._leading
  269. svgAttrs=getattr(self,'_svgAttrs',{})
  270. if strokeColor:
  271. for line in self._lines:
  272. s = _text2Path(line, x, y, fontName, fontSize, textAnchor)
  273. s.fillColor = fillColor
  274. s.strokeColor = strokeColor
  275. s.strokeWidth = strokeWidth
  276. g.add(s)
  277. y -= leading
  278. else:
  279. for line in self._lines:
  280. s = String(x, y, line, _svgAttrs=svgAttrs)
  281. s.textAnchor = textAnchor
  282. s.fontName = fontName
  283. s.fontSize = fontSize
  284. s.fillColor = fillColor
  285. g.add(s)
  286. y -= leading
  287. return g
  288. def draw(self):
  289. customDrawChanger = getattr(self,'customDrawChanger',None)
  290. if customDrawChanger:
  291. customDrawChanger(True,self)
  292. try:
  293. return self._rawDraw()
  294. finally:
  295. customDrawChanger(False,self)
  296. else:
  297. return self._rawDraw()
  298. class LabelDecorator:
  299. _attrMap = AttrMap(
  300. x = AttrMapValue(isNumberOrNone,desc=''),
  301. y = AttrMapValue(isNumberOrNone,desc=''),
  302. dx = AttrMapValue(isNumberOrNone,desc=''),
  303. dy = AttrMapValue(isNumberOrNone,desc=''),
  304. angle = AttrMapValue(isNumberOrNone,desc=''),
  305. boxAnchor = AttrMapValue(isBoxAnchor,desc=''),
  306. boxStrokeColor = AttrMapValue(isColorOrNone,desc=''),
  307. boxStrokeWidth = AttrMapValue(isNumberOrNone,desc=''),
  308. boxFillColor = AttrMapValue(isColorOrNone,desc=''),
  309. fillColor = AttrMapValue(isColorOrNone,desc=''),
  310. strokeColor = AttrMapValue(isColorOrNone,desc=''),
  311. strokeWidth = AttrMapValue(isNumberOrNone),desc='',
  312. fontName = AttrMapValue(isNoneOrString,desc=''),
  313. fontSize = AttrMapValue(isNumberOrNone,desc=''),
  314. leading = AttrMapValue(isNumberOrNone,desc=''),
  315. width = AttrMapValue(isNumberOrNone,desc=''),
  316. maxWidth = AttrMapValue(isNumberOrNone,desc=''),
  317. height = AttrMapValue(isNumberOrNone,desc=''),
  318. textAnchor = AttrMapValue(isTextAnchor,desc=''),
  319. visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
  320. )
  321. def __init__(self):
  322. self.textAnchor = 'start'
  323. self.boxAnchor = 'w'
  324. for a in self._attrMap.keys():
  325. if not hasattr(self,a): setattr(self,a,None)
  326. def decorate(self,l,L):
  327. chart,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0 = l._callOutInfo
  328. L.setText(chart.categoryAxis.categoryNames[colNo])
  329. g.add(L)
  330. def __call__(self,l):
  331. from copy import deepcopy
  332. L = Label()
  333. for a,v in self.__dict__.items():
  334. if v is None: v = getattr(l,a,None)
  335. setattr(L,a,v)
  336. self.decorate(l,L)
  337. isOffsetMode=OneOf('high','low','bar','axis')
  338. class LabelOffset(PropHolder):
  339. _attrMap = AttrMap(
  340. posMode = AttrMapValue(isOffsetMode,desc="Where to base +ve offset"),
  341. pos = AttrMapValue(isNumber,desc='Value for positive elements'),
  342. negMode = AttrMapValue(isOffsetMode,desc="Where to base -ve offset"),
  343. neg = AttrMapValue(isNumber,desc='Value for negative elements'),
  344. )
  345. def __init__(self):
  346. self.posMode=self.negMode='axis'
  347. self.pos = self.neg = 0
  348. def _getValue(self, chart, val):
  349. flipXY = chart._flipXY
  350. A = chart.categoryAxis
  351. jA = A.joinAxis
  352. if val>=0:
  353. mode = self.posMode
  354. delta = self.pos
  355. else:
  356. mode = self.negMode
  357. delta = self.neg
  358. if flipXY:
  359. v = A._x
  360. else:
  361. v = A._y
  362. if jA:
  363. if flipXY:
  364. _v = jA._x
  365. else:
  366. _v = jA._y
  367. if mode=='high':
  368. v = _v + jA._length
  369. elif mode=='low':
  370. v = _v
  371. elif mode=='bar':
  372. v = _v+val
  373. return v+delta
  374. NoneOrInstanceOfLabelOffset=NoneOr(isInstanceOf(LabelOffset))
  375. class PMVLabel(Label):
  376. _attrMap = AttrMap(
  377. BASE=Label,
  378. )
  379. def __init__(self, **kwds):
  380. Label.__init__(self, **kwds)
  381. self._pmv = 0
  382. def _getBoxAnchor(self):
  383. a = Label._getBoxAnchor(self)
  384. if self._pmv<0: a = {'nw':'se','n':'s','ne':'sw','w':'e','c':'c','e':'w','sw':'ne','s':'n','se':'nw'}[a]
  385. return a
  386. def _getTextAnchor(self):
  387. a = Label._getTextAnchor(self)
  388. if self._pmv<0: a = {'start':'end', 'middle':'middle', 'end':'start'}[a]
  389. return a
  390. class BarChartLabel(PMVLabel):
  391. """
  392. An extended Label allowing for nudging, lines visibility etc
  393. """
  394. _attrMap = AttrMap(
  395. BASE=PMVLabel,
  396. lineStrokeWidth = AttrMapValue(isNumberOrNone, desc="Non-zero for a drawn line"),
  397. lineStrokeColor = AttrMapValue(isColorOrNone, desc="Color for a drawn line"),
  398. fixedEnd = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw ends +/-"),
  399. fixedStart = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw starts +/-"),
  400. nudge = AttrMapValue(isNumber, desc="Non-zero sign dependent nudge"),
  401. boxTarget = AttrMapValue(OneOf('normal','anti','lo','hi','mid'),desc="one of ('normal','anti','lo','hi','mid')"),
  402. )
  403. def __init__(self, **kwds):
  404. PMVLabel.__init__(self, **kwds)
  405. self.lineStrokeWidth = 0
  406. self.lineStrokeColor = None
  407. self.fixedStart = self.fixedEnd = None
  408. self.nudge = 0
  409. class NA_Label(BarChartLabel):
  410. """
  411. An extended Label allowing for nudging, lines visibility etc
  412. """
  413. _attrMap = AttrMap(
  414. BASE=BarChartLabel,
  415. text = AttrMapValue(isNoneOrString, desc="Text to be used for N/A values"),
  416. )
  417. def __init__(self):
  418. BarChartLabel.__init__(self)
  419. self.text = 'n/a'
  420. NoneOrInstanceOfNA_Label=NoneOr(isInstanceOf(NA_Label))
  421. from reportlab.graphics.charts.utils import CustomDrawChanger
  422. class RedNegativeChanger(CustomDrawChanger):
  423. def __init__(self,fillColor=colors.red):
  424. CustomDrawChanger.__init__(self)
  425. self.fillColor = fillColor
  426. def _changer(self,obj):
  427. R = {}
  428. if obj._text.startswith('-'):
  429. R['fillColor'] = obj.fillColor
  430. obj.fillColor = self.fillColor
  431. return R
  432. class XLabel(Label):
  433. '''like label but uses XPreFormatted/Paragraph to draw the _text'''
  434. _attrMap = AttrMap(BASE=Label,
  435. )
  436. def __init__(self,*args,**kwds):
  437. Label.__init__(self,*args,**kwds)
  438. self.ddfKlass = kwds.pop('flowableClass',XPreformatted)
  439. self.ddf = kwds.pop('directDrawClass',self.ddf)
  440. if False:
  441. def __init__(self,*args,**kwds):
  442. self._flowableClass = kwds.pop('flowableClass',XPreformatted)
  443. ddf = kwds.pop('directDrawClass',DirectDrawFlowable)
  444. if ddf is None:
  445. raise RuntimeError('DirectDrawFlowable class is not available you need the rlextra package as well as reportlab')
  446. self._ddf = ddf
  447. Label.__init__(self,*args,**kwds)
  448. def computeSize(self):
  449. # the thing will draw in its own coordinate system
  450. self._lineWidths = []
  451. sty = self._style = ParagraphStyle('xlabel-generated',
  452. fontName=self.fontName,
  453. fontSize=self.fontSize,
  454. fillColor=self.fillColor,
  455. strokeColor=self.strokeColor,
  456. )
  457. self._getBaseLineRatio()
  458. if self.useAscentDescent:
  459. sty.autoLeading = True
  460. sty.leading = self._ascent - self._descent
  461. else:
  462. sty.leading = self.leading if self.leading else self.fontSize*1.2
  463. self._leading = sty.leading
  464. ta = self._getTextAnchor()
  465. aW = self.maxWidth or 0x7fffffff
  466. if ta!='start':
  467. sty.alignment = TA_LEFT
  468. obj = self._flowableClass(self._text,style=sty)
  469. _, objH = obj.wrap(aW,0x7fffffff)
  470. aW = self.maxWidth or obj._width_max
  471. sty.alignment = _ta2al[ta]
  472. self._obj = obj = self._flowableClass(self._text,style=sty)
  473. _, objH = obj.wrap(aW,0x7fffffff)
  474. if not self.width:
  475. self._width = self.leftPadding+self.rightPadding
  476. self._width += self._obj._width_max
  477. else:
  478. self._width = self.width
  479. self._computeSizeEnd(objH)
  480. def _rawDraw(self):
  481. _text = self._text
  482. self._text = _text or ''
  483. self.computeSize()
  484. self._text = _text
  485. g = Group()
  486. g.translate(self.x + self.dx, self.y + self.dy)
  487. g.rotate(self.angle)
  488. x = self._left
  489. # paint box behind text just in case they
  490. # fill it
  491. if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth):
  492. g.add(Rect( self._left-self.leftPadding,
  493. self._bottom-self.bottomPadding,
  494. self._width,
  495. self._height,
  496. strokeColor=self.boxStrokeColor,
  497. strokeWidth=self.boxStrokeWidth,
  498. fillColor=self.boxFillColor)
  499. )
  500. g1 = Group()
  501. g1.translate(x,self._top-self._eheight)
  502. g1.add(self._ddf(self._obj))
  503. g.add(g1)
  504. return g