]> git.saurik.com Git - wxWidgets.git/blame_incremental - wxPython/wx/lib/fancytext.py
removed second parameter from GetFirstChild calls
[wxWidgets.git] / wxPython / wx / lib / fancytext.py
... / ...
CommitLineData
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">
3This 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)
9</font>
10In all cases, 'str' is an XML string. Note that start and end tags
11are only required if *enclose* is set to False. In this case the
12text should be wrapped in FancyText tags.
13
14In addition, the module exports one class::
15<font family="fixed" style="slant">
16 class StaticFancyText(self, window, id, text, background, ...)
17</font>
18This class works similar to StaticText except it interprets its text
19as FancyText.
20
21The text can support<sup>superscripts</sup> and <sub>subscripts</sub>, text
22in 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,
24currently <times/>, <infinity/>, <angle/> as well as greek letters in both
25upper case (<Alpha/><Beta/>...<Omega/>) and lower case (<alpha/><beta/>...<omega/>).
26
27We can use doctest/guitest to display this string in all its marked up glory.
28<font family="fixed">
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()
34
35</font></font>
36The End"""
37# Copyright 2001-2003 Timothy Hochberg
38# Use as you see fit. No warantees, I cannot be help responsible, etc.
39import copy
40import math
41import sys
42import wx
43import xml.parsers.expat
44
45__all__ = "GetExtent", "GetFullExtent", "RenderToBitmap", "RenderToDC", "StaticFancyText"
46
47if sys.platform == "win32":
48 _greekEncoding = str(wx.FONTENCODING_CP1253)
49else:
50 _greekEncoding = str(wx.FONTENCODING_ISO8859_7)
51
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}
56
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
60# Renderer.
61
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")
66
67def iround(number):
68 return int(round(number))
69
70def iceil(number):
71 return int(math.ceil(number))
72
73class Renderer:
74 """Class for rendering XML marked up text.
75
76 See the module docstring for a description of the markup.
77
78 This class must be subclassed and the methods the methods that do
79 the drawing overridden for a particular output device.
80
81 """
82 defaultSize = None
83 defaultFamily = wx.DEFAULT
84 defaultStyle = wx.NORMAL
85 defaultWeight = wx.NORMAL
86 defaultEncoding = None
87 defaultColor = "black"
88
89 def __init__(self, dc=None, x=0, y=None):
90 if dc == None:
91 dc = wx.MemoryDC()
92 self.dc = dc
93 self.offsets = [0]
94 self.fonts = [{}]
95 self.width = self.height = 0
96 self.x = x
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()
102
103 def getY(self):
104 if self._y is None:
105 self.minY = self.maxY = self._y = self.dc.GetTextExtent("M")[1]
106 return self._y
107 def setY(self, value):
108 self._y = y
109 y = property(getY, setY)
110
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)
116
117 def endElement(self, name):
118 methname = "end_" + name
119 if hasattr(self, methname):
120 getattr(self, methname)()
121 elif hasattr(self, "start_" + name):
122 pass
123 else:
124 raise ValueError("XML tag '%s' not supported" % methname)
125
126 def characterData(self, data):
127 self.dc.SetFont(self.getCurrentFont())
128 for i, chunk in enumerate(data.split('\n')):
129 if i:
130 self.x = 0
131 self.y = self.mayY = self.maxY + self.dc.GetTextExtent("M")[1]
132 if chunk:
133 width, height, descent, extl = self.dc.GetFullTextExtent(chunk)
134 self.renderCharacterData(data, iround(self.x), iround(self.y + self.offsets[-1] - height + descent))
135 else:
136 width = height = descent = extl = 0
137 self.updateDims(width, height, descent, extl)
138
139 def updateDims(self, width, height, descent, externalLeading):
140 self.x += width
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
145
146 def start_FancyText(self, attrs):
147 pass
148 start_wxFancyText = start_FancyText # For backward compatibility
149
150 def start_font(self, attrs):
151 for key, value in attrs.items():
152 if key == "size":
153 value = int(value)
154 elif key == "family":
155 value = _families[value]
156 elif key == "style":
157 value = _styles[value]
158 elif key == "weight":
159 value = _weights[value]
160 elif key == "encoding":
161 value = int(value)
162 elif key == "color":
163 pass
164 else:
165 raise ValueError("unknown font attribute '%s'" % key)
166 attrs[key] = value
167 font = copy.copy(self.fonts[-1])
168 font.update(attrs)
169 self.fonts.append(font)
170
171 def end_font(self):
172 self.fonts.pop()
173
174 def start_sub(self, attrs):
175 if attrs.keys():
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})
180
181 def end_sub(self):
182 self.fonts.pop()
183 self.offsets.pop()
184
185 def start_sup(self, attrs):
186 if attrs.keys():
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})
191
192 def end_sup(self):
193 self.fonts.pop()
194 self.offsets.pop()
195
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))
203
204 def getCurrentColor(self):
205 font = self.fonts[-1]
206 return wx.TheColourDatabase.FindColour(font.get("color", self.defaultColor))
207
208 def getCurrentPen(self):
209 return wx.ThePenList.FindOrCreatePen(self.getCurrentColor(), 1, wx.SOLID)
210
211 def renderCharacterData(self, data, x, y):
212 raise NotImplementedError()
213
214
215def _addGreek():
216 alpha = 0xE1
217 Alpha = 0xC1
218 def end(self):
219 pass
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)
224 self.end_font()
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)
232 self.end_font()
233 setattr(Renderer, "start_%s" % name.capitalize(), start)
234 setattr(Renderer, "end_%s" % name.capitalize(), end)
235_addGreek()
236
237
238
239class SizeRenderer(Renderer):
240 """Processes text as if rendering it, but just computes the size."""
241
242 def __init__(self, dc=None):
243 Renderer.__init__(self, dc, 0, 0)
244
245 def renderCharacterData(self, data, x, y):
246 pass
247
248 def start_angle(self, attrs):
249 self.characterData("M")
250
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)
256
257 def start_times(self, attrs):
258 self.characterData("M")
259
260 def start_in(self, attrs):
261 self.characterData("M")
262
263 def start_times(self, attrs):
264 self.characterData("M")
265
266
267class DCRenderer(Renderer):
268 """Renders text to a wxPython device context DC."""
269
270 def renderCharacterData(self, data, x, y):
271 self.dc.SetTextForeground(self.getCurrentColor())
272 self.dc.DrawText(data, (x, y))
273
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)
282
283
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
295 yc = iround(y-1.5*r)
296 self.dc.DrawCircle((xc - r, yc), r)
297 self.dc.DrawCircle((xc + r, yc), r)
298 self.updateDims(width, height, 0, 0)
299
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]
305 width *= 0.8
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)
311
312
313def RenderToRenderer(str, renderer, enclose=True):
314 try:
315 if enclose:
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
322 p.Parse(str, 1)
323 except xml.parsers.expat.error, err:
324 raise ValueError('error parsing text text "%s": %s' % (str, err))
325
326
327# Public interface
328
329
330def 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
335
336
337def 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
341
342
343def RenderToBitmap(str, background=None, enclose=1):
344 "Return str rendered on a minumum size bitmap"
345 dc = wx.MemoryDC()
346 width, height, dy = GetFullExtent(str, dc, enclose)
347 bmp = wx.EmptyBitmap(width, height)
348 dc.SelectObject(bmp)
349 if background is None:
350 dc.SetBackground(wx.WHITE_BRUSH)
351 else:
352 dc.SetBackground(background)
353 dc.Clear()
354 renderer = DCRenderer(dc, y=dy)
355 dc.BeginDrawing()
356 RenderToRenderer(str, renderer, enclose)
357 dc.EndDrawing()
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()
364 return bmp
365
366
367def 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)
372
373
374class StaticFancyText(wx.StaticBitmap):
375 def __init__(self, window, id, text, *args, **kargs):
376 args = list(args)
377 kargs.setdefault('name', 'staticFancyText')
378 if 'background' in kargs:
379 background = kargs.pop('background')
380 elif args:
381 background = args.pop(0)
382 else:
383 background = wx.Brush(window.GetBackgroundColour(), wx.SOLID)
384
385 bmp = RenderToBitmap(text, background)
386 wx.StaticBitmap.__init__(self, window, id, bmp, *args, **kargs)
387
388
389# Old names for backward compatibiliry
390getExtent = GetExtent
391renderToBitmap = RenderToBitmap
392renderToDC = RenderToDC
393
394
395# Test Driver
396
397def test():
398 app = wx.PyApp()
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)
404 frame.SetSizer(box)
405 frame.SetAutoLayout(True)
406 box.Fit(frame)
407 box.SetSizeHints(frame)
408 frame.Show()
409 app.MainLoop()
410
411if __name__ == "__main__":
412 test()
413
414