1 """<font weight="bold" size="16">FancyText</font> -- <font style="italic" size="16">methods for rendering XML specified text</font>
2 <font family="swiss" size="12">
3 This module exports four main methods::
4 <font family="fixed" style="slant">
5 def GetExtent(str, dc=None, enclose=True)
6 def GetFullExtent(str, dc=None, enclose=True)
7 def RenderToBitmap(str, background=None, enclose=True)
8 def RenderToDC(str, dc, x, y, enclose=True)
10 In all cases, 'str' is an XML string. Note that start and end tags
11 are only required if *enclose* is set to False. In this case the
12 text should be wrapped in FancyText tags.
14 In addition, the module exports one class::
15 <font family="fixed" style="slant">
16 class StaticFancyText(self, window, id, text, background, ...)
18 This class works similar to StaticText except it interprets its text
21 The text can support<sup>superscripts</sup> and <sub>subscripts</sub>, text
22 in different <font size="20">sizes</font>, <font color="blue">colors</font>, <font style="italic">styles</font>, <font weight="bold">weights</font> and
23 <font family="script">families</font>. It also supports a limited set of symbols,
24 currently <times/>, <infinity/>, <angle/> as well as greek letters in both
25 upper case (<Alpha/><Beta/>...<Omega/>) and lower case (<alpha/><beta/>...<omega/>).
27 We can use doctest/guitest to display this string in all its marked up glory.
29 >>> frame = wx.Frame(wx.NULL, -1, "FancyText demo", wx.DefaultPosition)
30 >>> sft = StaticFancyText(frame, -1, __doc__, wx.Brush("light grey", wx.SOLID))
31 >>> frame.SetClientSize(sft.GetSize())
32 >>> didit = frame.Show()
33 >>> from guitest import PauseTests; PauseTests()
37 # Copyright 2001-2003 Timothy Hochberg
38 # Use as you see fit. No warantees, I cannot be help responsible, etc.
43 import xml
.parsers
.expat
45 __all__
= "GetExtent", "GetFullExtent", "RenderToBitmap", "RenderToDC", "StaticFancyText"
47 if sys
.platform
== "win32":
48 _greekEncoding
= str(wx
.FONTENCODING_CP1253
)
50 _greekEncoding
= str(wx
.FONTENCODING_ISO8859_7
)
52 _families
= {"fixed" : wx
.FIXED
, "default" : wx
.DEFAULT
, "decorative" : wx
.DECORATIVE
, "roman" : wx
.ROMAN
,
53 "script" : wx
.SCRIPT
, "swiss" : wx
.SWISS
, "modern" : wx
.MODERN
}
54 _styles
= {"normal" : wx.NORMAL, "slant" : wx.SLANT, "italic" : wx.ITALIC}
55 _weights
= {"normal" : wx.NORMAL, "light" : wx.LIGHT, "bold" : wx.BOLD}
57 # The next three classes: Renderer, SizeRenderer and DCRenderer are
58 # what you will need to override to extend the XML language. All of
59 # the font stuff as well as the subscript and superscript stuff are in
62 _greek_letters
= ("alpha", "beta", "gamma", "delta", "epsilon", "zeta",
63 "eta", "theta", "iota", "kappa", "lambda", "mu", "nu",
64 "xi", "omnikron", "pi", "rho", "altsigma", "sigma", "tau", "upsilon",
65 "phi", "chi", "psi", "omega")
68 return int(round(number
))
71 return int(math
.ceil(number
))
74 """Class for rendering XML marked up text.
76 See the module docstring for a description of the markup.
78 This class must be subclassed and the methods the methods that do
79 the drawing overridden for a particular output device.
83 defaultFamily
= wx
.DEFAULT
84 defaultStyle
= wx
.NORMAL
85 defaultWeight
= wx
.NORMAL
86 defaultEncoding
= None
87 defaultColor
= "black"
89 def __init__(self
, dc
=None, x
=0, y
=None):
95 self
.width
= self
.height
= 0
97 self
.minY
= self
.maxY
= self
._y
= y
98 if Renderer
.defaultSize
is None:
99 Renderer
.defaultSize
= wx
.NORMAL_FONT
.GetPointSize()
100 if Renderer
.defaultEncoding
is None:
101 Renderer
.defaultEncoding
= wx
.Font_GetDefaultEncoding()
105 self
.minY
= self
.maxY
= self
._y
= self
.dc
.GetTextExtent("M")[1]
107 def setY(self
, value
):
109 y
= property(getY
, setY
)
111 def startElement(self
, name
, attrs
):
112 method
= "start_" + name
113 if not hasattr(self
, method
):
114 raise ValueError("XML tag '%s' not supported" % name
)
115 getattr(self
, method
)(attrs
)
117 def endElement(self
, name
):
118 methname
= "end_" + name
119 if hasattr(self
, methname
):
120 getattr(self
, methname
)()
121 elif hasattr(self
, "start_" + name
):
124 raise ValueError("XML tag '%s' not supported" % methname
)
126 def characterData(self
, data
):
127 self
.dc
.SetFont(self
.getCurrentFont())
128 for i
, chunk
in enumerate(data
.split('\n')):
131 self
.y
= self
.mayY
= self
.maxY
+ self
.dc
.GetTextExtent("M")[1]
133 width
, height
, descent
, extl
= self
.dc
.GetFullTextExtent(chunk
)
134 self
.renderCharacterData(data
, iround(self
.x
), iround(self
.y
+ self
.offsets
[-1] - height
+ descent
))
136 width
= height
= descent
= extl
= 0
137 self
.updateDims(width
, height
, descent
, extl
)
139 def updateDims(self
, width
, height
, descent
, externalLeading
):
141 self
.width
= max(self
.x
, self
.width
)
142 self
.minY
= min(self
.minY
, self
.y
+self
.offsets
[-1]-height
+descent
)
143 self
.maxY
= max(self
.maxY
, self
.y
+self
.offsets
[-1]+descent
)
144 self
.height
= self
.maxY
- self
.minY
146 def start_FancyText(self
, attrs
):
148 start_wxFancyText
= start_FancyText
# For backward compatibility
150 def start_font(self
, attrs
):
151 for key
, value
in attrs
.items():
154 elif key
== "family":
155 value
= _families
[value
]
157 value
= _styles
[value
]
158 elif key
== "weight":
159 value
= _weights
[value
]
160 elif key
== "encoding":
165 raise ValueError("unknown font attribute '%s'" % key
)
167 font
= copy
.copy(self
.fonts
[-1])
169 self
.fonts
.append(font
)
174 def start_sub(self
, attrs
):
176 raise ValueError("<sub> does not take attributes")
177 font
= self
.getCurrentFont()
178 self
.offsets
.append(self
.offsets
[-1] + self
.dc
.GetFullTextExtent("M", font
)[1]*0.5)
179 self
.start_font({"size" : font.GetPointSize() * 0.8}
)
185 def start_sup(self
, attrs
):
187 raise ValueError("<sup> does not take attributes")
188 font
= self
.getCurrentFont()
189 self
.offsets
.append(self
.offsets
[-1] - self
.dc
.GetFullTextExtent("M", font
)[1]*0.3)
190 self
.start_font({"size" : font.GetPointSize() * 0.8}
)
196 def getCurrentFont(self
):
197 font
= self
.fonts
[-1]
198 return wx
.TheFontList
.FindOrCreateFont(font
.get("size", self
.defaultSize
),
199 font
.get("family", self
.defaultFamily
),
200 font
.get("style", self
.defaultStyle
),
201 font
.get("weight", self
.defaultWeight
),
202 encoding
= font
.get("encoding", self
.defaultEncoding
))
204 def getCurrentColor(self
):
205 font
= self
.fonts
[-1]
206 return wx
.TheColourDatabase
.FindColour(font
.get("color", self
.defaultColor
))
208 def getCurrentPen(self
):
209 return wx
.ThePenList
.FindOrCreatePen(self
.getCurrentColor(), 1, wx
.SOLID
)
211 def renderCharacterData(self
, data
, x
, y
):
212 raise NotImplementedError()
220 for i
, name
in enumerate(_greek_letters
):
221 def start(self
, attrs
, code
=chr(alpha
+i
)):
222 self
.start_font({"encoding" : _greekEncoding}
)
223 self
.characterData(code
)
225 setattr(Renderer
, "start_%s" % name
, start
)
226 setattr(Renderer
, "end_%s" % name
, end
)
227 if name
== "altsigma":
228 continue # There is no capital for altsigma
229 def start(self
, attrs
, code
=chr(Alpha
+i
)):
230 self
.start_font({"encoding" : _greekEncoding}
)
231 self
.characterData(code
)
233 setattr(Renderer
, "start_%s" % name
.capitalize(), start
)
234 setattr(Renderer
, "end_%s" % name
.capitalize(), end
)
239 class SizeRenderer(Renderer
):
240 """Processes text as if rendering it, but just computes the size."""
242 def __init__(self
, dc
=None):
243 Renderer
.__init
__(self
, dc
, 0, 0)
245 def renderCharacterData(self
, data
, x
, y
):
248 def start_angle(self
, attrs
):
249 self
.characterData("M")
251 def start_infinity(self
, attrs
):
252 width
, height
= self
.dc
.GetTextExtent("M")
253 width
= max(width
, 10)
254 height
= max(height
, width
/ 2)
255 self
.updateDims(width
, height
, 0, 0)
257 def start_times(self
, attrs
):
258 self
.characterData("M")
260 def start_in(self
, attrs
):
261 self
.characterData("M")
263 def start_times(self
, attrs
):
264 self
.characterData("M")
267 class DCRenderer(Renderer
):
268 """Renders text to a wxPython device context DC."""
270 def renderCharacterData(self
, data
, x
, y
):
271 self
.dc
.SetTextForeground(self
.getCurrentColor())
272 self
.dc
.DrawText(data
, (x
, y
))
274 def start_angle(self
, attrs
):
275 self
.dc
.SetFont(self
.getCurrentFont())
276 self
.dc
.SetPen(self
.getCurrentPen())
277 width
, height
, descent
, leading
= self
.dc
.GetFullTextExtent("M")
278 y
= self
.y
+ self
.offsets
[-1]
279 self
.dc
.DrawLine((iround(self
.x
), iround(y
)), (iround( self
.x
+width
), iround(y
)))
280 self
.dc
.DrawLine((iround(self
.x
), iround(y
)), (iround(self
.x
+width
), iround(y
-width
)))
281 self
.updateDims(width
, height
, descent
, leading
)
284 def start_infinity(self
, attrs
):
285 self
.dc
.SetFont(self
.getCurrentFont())
286 self
.dc
.SetPen(self
.getCurrentPen())
287 width
, height
, descent
, leading
= self
.dc
.GetFullTextExtent("M")
288 width
= max(width
, 10)
289 height
= max(height
, width
/ 2)
290 self
.dc
.SetPen(wx
.Pen(self
.getCurrentColor(), max(1, width
/10)))
291 self
.dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
292 y
= self
.y
+ self
.offsets
[-1]
293 r
= iround( 0.95 * width
/ 4)
294 xc
= (2*self
.x
+ width
) / 2
296 self
.dc
.DrawCircle((xc
- r
, yc
), r
)
297 self
.dc
.DrawCircle((xc
+ r
, yc
), r
)
298 self
.updateDims(width
, height
, 0, 0)
300 def start_times(self
, attrs
):
301 self
.dc
.SetFont(self
.getCurrentFont())
302 self
.dc
.SetPen(self
.getCurrentPen())
303 width
, height
, descent
, leading
= self
.dc
.GetFullTextExtent("M")
304 y
= self
.y
+ self
.offsets
[-1]
306 width
= iround(width
+.5)
307 self
.dc
.SetPen(wx
.Pen(self
.getCurrentColor(), 1))
308 self
.dc
.DrawLine((iround(self
.x
), iround(y
-width
)), (iround(self
.x
+width
-1), iround(y
-1)))
309 self
.dc
.DrawLine((iround(self
.x
), iround(y
-2)), (iround(self
.x
+width
-1), iround(y
-width
-1)))
310 self
.updateDims(width
, height
, 0, 0)
313 def RenderToRenderer(str, renderer
, enclose
=True):
316 str = '<?xml version="1.0"?><FancyText>%s</FancyText>' % str
317 p
= xml
.parsers
.expat
.ParserCreate()
318 p
.returns_unicode
= 0
319 p
.StartElementHandler
= renderer
.startElement
320 p
.EndElementHandler
= renderer
.endElement
321 p
.CharacterDataHandler
= renderer
.characterData
323 except xml
.parsers
.expat
.error
, err
:
324 raise ValueError('error parsing text text "%s": %s' % (str, err
))
330 def GetExtent(str, dc
=None, enclose
=True):
331 "Return the extent of str"
332 renderer
= SizeRenderer(dc
)
333 RenderToRenderer(str, renderer
, enclose
)
334 return iceil(renderer
.width
), iceil(renderer
.height
) # XXX round up
337 def GetFullExtent(str, dc
=None, enclose
=True):
338 renderer
= SizeRenderer(dc
)
339 RenderToRenderer(str, renderer
, enclose
)
340 return iceil(renderer
.width
), iceil(renderer
.height
), -iceil(renderer
.minY
) # XXX round up
343 def RenderToBitmap(str, background
=None, enclose
=1):
344 "Return str rendered on a minumum size bitmap"
346 width
, height
, dy
= GetFullExtent(str, dc
, enclose
)
347 bmp
= wx
.EmptyBitmap(width
, height
)
349 if background
is None:
350 dc
.SetBackground(wx
.WHITE_BRUSH
)
352 dc
.SetBackground(background
)
354 renderer
= DCRenderer(dc
, y
=dy
)
356 RenderToRenderer(str, renderer
, enclose
)
358 dc
.SelectObject(wx
.NullBitmap
)
359 if background
is None:
360 img
= wx
.ImageFromBitmap(bmp
)
361 bg
= dc
.GetBackground().GetColour()
362 img
.SetMaskColour(bg
.Red(), bg
.Green(), bg
.Blue())
363 bmp
= img
.ConvertToBitmap()
367 def RenderToDC(str, dc
, x
, y
, enclose
=1):
368 "Render str onto a wxDC at (x,y)"
369 width
, height
, dy
= GetFullExtent(str, dc
)
370 renderer
= DCRenderer(dc
, x
, y
+dy
)
371 RenderToRenderer(str, renderer
, enclose
)
374 class StaticFancyText(wx
.StaticBitmap
):
375 def __init__(self
, window
, id, text
, *args
, **kargs
):
377 kargs
.setdefault('name', 'staticFancyText')
378 if 'background' in kargs
:
379 background
= kargs
.pop('background')
381 background
= args
.pop(0)
383 background
= wx
.Brush(window
.GetBackgroundColour(), wx
.SOLID
)
385 bmp
= RenderToBitmap(text
, background
)
386 wx
.StaticBitmap
.__init
__(self
, window
, id, bmp
, *args
, **kargs
)
389 # Old names for backward compatibiliry
390 getExtent
= GetExtent
391 renderToBitmap
= RenderToBitmap
392 renderToDC
= RenderToDC
399 box
= wx
.BoxSizer(wx
.VERTICAL
)
400 frame
= wx
.Frame(wx
.NULL
, -1, "FancyText demo", wx
.DefaultPosition
)
401 frame
.SetBackgroundColour("light grey")
402 sft
= StaticFancyText(frame
, -1, __doc__
)
403 box
.Add(sft
, 1, wx
.EXPAND
)
405 frame
.SetAutoLayout(True)
407 box
.SetSizeHints(frame
)
411 if __name__
== "__main__":