1 """wxFancyText -- methods for rendering XML specified text 
   3 This module has four main methods: 
   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) 
   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 
  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. 
  19 # Copyright 2001 Timothy Hochberg 
  20 # Use as you see fit. No warantees, I cannot be held responsible, etc. 
  24 # TODO:  Make a wxFancyTextCtrl class that derives from wxControl. 
  25 #        Add support for line breaks 
  31 from wxPython
.wx 
import * 
  32 import xml
.parsers
.expat
, copy
 
  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}
 
  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 
  46     defaultSize 
= wxNORMAL_FONT
.GetPointSize() 
  47     defaultFamily 
= wxDEFAULT
 
  48     defaultStyle 
= wxNORMAL
 
  49     defaultWeight 
= wxNORMAL
 
  50     defaultEncoding 
= wxFont_GetDefaultEncoding() 
  51     defaultColor 
= "black" 
  53     def __init__(self
, dc
=None): 
  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
) 
  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
)() 
  72     def start_wxFancyString(self
, attrs
): 
  75     def end_wxFancyString(self
): 
  78     def start_font(self
, attrs
): 
  79         for key
, value 
in attrs
.items(): 
  83                 value 
= _families
[value
] 
  85                 value 
= _styles
[value
] 
  87                 value 
= _weights
[value
] 
  88             elif key 
== "encoding": 
  93                 raise ValueError("unknown font attribute '%s'" % key
) 
  95         font 
= copy
.copy(self
.fonts
[-1]) 
  97         self
.fonts
.append(font
) 
 102     def start_sub(self
, attrs
): 
 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}
) 
 113     def start_sup(self
, attrs
): 
 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}
) 
 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
)) 
 132     def getCurrentColor(self
): 
 133         font 
= self
.fonts
[-1] 
 134         return wxNamedColour(font
.get("color", self
.defaultColor
)) 
 137 class SizeRenderer(Renderer
): 
 139     def __init__(self
, dc
=None): 
 140         Renderer
.__init
__(self
, dc
) 
 141         self
.width 
= self
.height 
= 0 
 142         self
.minY 
= self
.maxY 
= 0 
 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
 
 152     def start_angle(self
, attrs
): 
 153         self
.characterData("M") 
 158 class DCRenderer(Renderer
): 
 160     def __init__(self
, dc
=None, x
=0, y
=0): 
 161         Renderer
.__init
__(self
, dc
) 
 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
 
 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
 
 184 # This is a rendering function that is primarily used internally, 
 185 # although it could be used externally if one had overridden the 
 188 def renderToRenderer(str, renderer
, enclose
=1): 
 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
 
 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
) 
 205 # This should probably only be used internally.... 
 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
 
 212 def renderToBitmap(str, background
=None, enclose
=1): 
 213     "Return str rendered on a minumum size bitmap" 
 215     width
, height
, dy 
= getFullExtent(str, dc
) 
 216     bmp 
= wxEmptyBitmap(width
, height
) 
 218     if background 
is not None: 
 219         dc
.SetBackground(background
) 
 221     renderer 
= DCRenderer(dc
, y
=dy
) 
 223     renderToRenderer(str, renderer
, enclose
) 
 225     dc
.SelectObject(wxNullBitmap
) 
 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
) 
 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>') 
 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
)