]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/fancytext.py
0767c986e76a7f1dabf1bc2ea2fb0350e2a2bc99
[wxWidgets.git] / wxPython / wxPython / lib / fancytext.py
1 """wxFancyText -- methods for rendering XML specified text
2
3 This module has four main methods:
4
5 def getExtent(str, dc=None, enclose=1):
6 def renderToBitmap(str, background=None, enclose=1)
7 def renderToDC(str, dc, x, y, enclose=1)
8
9 In all cases, 'str' is an XML string. The tags in the string can
10 currently specify the font, subscripts, superscripts, and the angle
11 sign. The allowable properties of font are size, family, style, weght,
12 encoding, and color. See the example on the bottom for a better idea
13 of how this works.
14
15 Note that start and end tags for the string are provided if enclose is
16 True, so for instance, renderToBitmap("X<sub>1</sub>") will work.
17
18 """
19 # Copyright 2001 Timothy Hochberg
20 # Use as you see fit. No warantees, I cannot be held responsible, etc.
21
22
23
24 # TODO: Make a wxFancyTextCtrl class that derives from wxControl.
25 # Add support for line breaks
26 # etc.
27 # - Robin
28
29
30
31 from wxPython.wx import *
32 import xml.parsers.expat, copy
33
34 _families = {"default" : wxDEFAULT, "decorative" : wxDECORATIVE, "roman" : wxROMAN,
35 "swiss" : wxSWISS, "modern" : wxMODERN}
36 _styles = {"normal" : wxNORMAL, "slant" : wxSLANT, "italic" : wxITALIC}
37 _weights = {"normal" : wxNORMAL, "light" : wxLIGHT, "bold" : wxBOLD}
38
39 # The next three classes: Renderer, SizeRenderer and DCRenderer are
40 # what you will need to override to extend the XML language. All of
41 # the font stuff as well as the subscript and superscript stuff are in
42 # Renderer.
43
44 class Renderer:
45
46 defaultSize = wxNORMAL_FONT.GetPointSize()
47 defaultFamily = wxDEFAULT
48 defaultStyle = wxNORMAL
49 defaultWeight = wxNORMAL
50 defaultEncoding = wxFont_GetDefaultEncoding()
51 defaultColor = "black"
52
53 def __init__(self, dc=None):
54 if dc == None:
55 dc = wxMemoryDC()
56 self.dc = dc
57 self.offsets = [0]
58 self.fonts = [{}]
59
60 def startElement(self, name, attrs):
61 method = "start_" + name
62 if not hasattr(self, method):
63 raise ValueError("XML tag '%s' not supported" % name)
64 getattr(self, method)(attrs)
65
66 def endElement(self, name):
67 method = "end_" + name
68 if not hasattr(self, method):
69 raise ValueError("XML tag '%s' not supported" % name)
70 getattr(self, method)()
71
72 def start_wxFancyString(self, attrs):
73 pass
74
75 def end_wxFancyString(self):
76 pass
77
78 def start_font(self, attrs):
79 for key, value in attrs.items():
80 if key == "size":
81 value = int(value)
82 elif key == "family":
83 value = _families[value]
84 elif key == "style":
85 value = _styles[value]
86 elif key == "weight":
87 value = _weights[value]
88 elif key == "encoding":
89 pass
90 elif key == "color":
91 pass
92 else:
93 raise ValueError("unknown font attribute '%s'" % key)
94 attrs[key] = value
95 font = copy.copy(self.fonts[-1])
96 font.update(attrs)
97 self.fonts.append(font)
98
99 def end_font(self):
100 self.fonts.pop()
101
102 def start_sub(self, attrs):
103 if attrs.keys():
104 raise ValueError("<sub> does not take attributes")
105 font = self.getCurrentFont()
106 self.offsets.append(self.offsets[-1] + self.dc.GetFullTextExtent("M", font)[1]*0.5)
107 self.start_font({"size" : font.GetPointSize() * 0.8})
108
109 def end_sub(self):
110 self.fonts.pop()
111 self.offsets.pop()
112
113 def start_sup(self, attrs):
114 if attrs.keys():
115 raise ValueError("<sup> does not take attributes")
116 font = self.getCurrentFont()
117 self.offsets.append(self.offsets[-1] - self.dc.GetFullTextExtent("M", font)[1]*0.3)
118 self.start_font({"size" : font.GetPointSize() * 0.8})
119
120 def end_sup(self):
121 self.fonts.pop()
122 self.offsets.pop()
123
124 def getCurrentFont(self):
125 font = self.fonts[-1]
126 return wxFont(font.get("size", self.defaultSize),
127 font.get("family", self.defaultFamily),
128 font.get("style", self.defaultStyle),
129 font.get("weight", self.defaultWeight),
130 encoding = font.get("encoding", self.defaultEncoding))
131
132 def getCurrentColor(self):
133 font = self.fonts[-1]
134 return wxNamedColour(font.get("color", self.defaultColor))
135
136
137 class SizeRenderer(Renderer):
138
139 def __init__(self, dc=None):
140 Renderer.__init__(self, dc)
141 self.width = self.height = 0
142 self.minY = self.maxY = 0
143
144 def characterData(self, data):
145 self.dc.SetFont(self.getCurrentFont())
146 width, height = self.dc.GetTextExtent(data)
147 self.width = self.width + width
148 self.minY = min(self.minY, self.offsets[-1])
149 self.maxY = max(self.maxY, self.offsets[-1] + height)
150 self.height = self.maxY - self.minY
151
152 def start_angle(self, attrs):
153 self.characterData("M")
154
155 def end_angle(self):
156 pass
157
158 class DCRenderer(Renderer):
159
160 def __init__(self, dc=None, x=0, y=0):
161 Renderer.__init__(self, dc)
162 self.x = x
163 self.y = y
164
165 def characterData(self, data):
166 self.dc.SetFont(self.getCurrentFont())
167 self.dc.SetTextForeground(self.getCurrentColor())
168 width, height = self.dc.GetTextExtent(data)
169 self.dc.DrawText(data, self.x, self.y + self.offsets[-1])
170 self.x = self.x + width
171
172 def start_angle(self, attrs):
173 self.dc.SetFont(self.getCurrentFont())
174 self.dc.SetPen(wxPen(self.getCurrentColor(), 1))
175 width, height, descent, leading = self.dc.GetFullTextExtent("M")
176 y = self.y + self.offsets[-1] + height - descent
177 self.dc.DrawLine(self.x, y, self.x+width, y)
178 self.dc.DrawLine(self.x, y, self.x+width, y-width)
179 self.x = self.x + width
180
181 def end_angle(self):
182 pass
183
184 # This is a rendering function that is primarily used internally,
185 # although it could be used externally if one had overridden the
186 # Renderer classes.
187
188 def renderToRenderer(str, renderer, enclose=1):
189 if enclose:
190 str = '<?xml version="1.0"?><wxFancyString>%s</wxFancyString>' % str
191 p = xml.parsers.expat.ParserCreate()
192 p.returns_unicode = 0
193 p.StartElementHandler = renderer.startElement
194 p.EndElementHandler = renderer.endElement
195 p.CharacterDataHandler = renderer.characterData
196 p.Parse(str, 1)
197
198
199 def getExtent(str, dc=None, enclose=1):
200 "Return the extent of str"
201 renderer = SizeRenderer(dc)
202 renderToRenderer(str, renderer, enclose)
203 return wxSize(renderer.width, renderer.height)
204
205 # This should probably only be used internally....
206
207 def getFullExtent(str, dc=None, enclose=1):
208 renderer = SizeRenderer(dc)
209 renderToRenderer(str, renderer, enclose)
210 return renderer.width, renderer.height, -renderer.minY
211
212 def renderToBitmap(str, background=None, enclose=1):
213 "Return str rendered on a minumum size bitmap"
214 dc = wxMemoryDC()
215 width, height, dy = getFullExtent(str, dc)
216 bmp = wxEmptyBitmap(width, height)
217 dc.SelectObject(bmp)
218 if background is not None:
219 dc.SetBackground(background)
220 dc.Clear()
221 renderer = DCRenderer(dc, y=dy)
222 dc.BeginDrawing()
223 renderToRenderer(str, renderer, enclose)
224 dc.EndDrawing()
225 dc.SelectObject(wxNullBitmap)
226 return bmp
227
228 def renderToDC(str, dc, x, y, enclose=1):
229 "Render str onto a wxDC at (x,y)"
230 width, height, dy = getFullExtent(str, dc)
231 renderer = DCRenderer(dc, x, y+dy)
232 renderToRenderer(str, renderer, enclose)
233
234
235 if __name__ == "__main__":
236 str = ('<font style="italic" family="swiss" color="red" weight="bold" >some |<sup>23</sup> <angle/>text<sub>with <angle/> subscript</sub> </font> some other text'
237 '<font family="swiss" color="green" size="40">big green text</font>')
238 ID_EXIT = 102
239 class myApp(wxApp):
240 def OnInit(self):
241 return 1
242 app = myApp()
243 frame = wxFrame(NULL, -1, "wxFancyText demo", wxDefaultPosition)
244 frame.SetClientSize(getExtent(str))
245 bmp = renderToBitmap(str, wxCYAN_BRUSH)
246 sb = wxStaticBitmap(frame, -1, bmp)
247 EVT_MENU(frame, ID_EXIT, frame.Destroy)
248 frame.Show(1)
249 app.MainLoop()