-"""wxFancyText -- methods for rendering XML specified text
-
-This module has four main methods:
-
-def getExtent(str, dc=None, enclose=1):
-def renderToBitmap(str, background=None, enclose=1)
-def renderToDC(str, dc, x, y, enclose=1)
-
-In all cases, 'str' is an XML string. The tags in the string can
-currently specify the font, subscripts, superscripts, and the angle
-sign. The allowable properties of font are size, family, style, weght,
-encoding, and color. See the example on the bottom for a better idea
-of how this works.
-
-Note that start and end tags for the string are provided if enclose is
-true, so for instance, renderToBitmap("X<sub>1</sub>") will work.
-
-"""
-# Copyright 2001 Timothy Hochberg
-# Use as you see fit. No warantees, I cannot be held responsible, etc.
-
-
-
-# TODO: Make a wxFancyTextCtrl class that derives from wxControl.
-# Add support for line breaks
-# etc.
-# - Robin
-
-
-
-from wxPython.wx import *
-import xml.parsers.expat, copy
-
-_families = {"default" : wxDEFAULT, "decorative" : wxDECORATIVE, "roman" : wxROMAN,
- "swiss" : wxSWISS, "modern" : wxMODERN}
-_styles = {"normal" : wxNORMAL, "slant" : wxSLANT, "italic" : wxITALIC}
-_weights = {"normal" : wxNORMAL, "light" : wxLIGHT, "bold" : wxBOLD}
-
-# The next three classes: Renderer, SizeRenderer and DCRenderer are
-# what you will need to override to extend the XML language. All of
-# the font stuff as well as the subscript and superscript stuff are in
-# Renderer.
-
-class Renderer:
-
- defaultSize = wxNORMAL_FONT.GetPointSize()
- defaultFamily = wxDEFAULT
- defaultStyle = wxNORMAL
- defaultWeight = wxNORMAL
- defaultEncoding = wxFont_GetDefaultEncoding()
- defaultColor = "black"
-
- def __init__(self, dc=None):
- if dc == None:
- dc = wxMemoryDC()
- self.dc = dc
- self.offsets = [0]
- self.fonts = [{}]
-
- def startElement(self, name, attrs):
- method = "start_" + name
- if not hasattr(self, method):
- raise ValueError("XML tag '%s' not supported" % name)
- getattr(self, method)(attrs)
-
- def endElement(self, name):
- method = "end_" + name
- if not hasattr(self, method):
- raise ValueError("XML tag '%s' not supported" % name)
- getattr(self, method)()
-
- def start_wxFancyString(self, attrs):
- pass
-
- def end_wxFancyString(self):
- pass
-
- def start_font(self, attrs):
- for key, value in attrs.items():
- if key == "size":
- value = int(value)
- elif key == "family":
- value = _families[value]
- elif key == "style":
- value = _styles[value]
- elif key == "weight":
- value = _weights[value]
- elif key == "encoding":
- pass
- elif key == "color":
- pass
- else:
- raise ValueError("unknown font attribute '%s'" % key)
- attrs[key] = value
- font = copy.copy(self.fonts[-1])
- font.update(attrs)
- self.fonts.append(font)
-
- def end_font(self):
- self.fonts.pop()
-
- def start_sub(self, attrs):
- if attrs.keys():
- raise ValueError("<sub> does not take attributes")
- font = self.getCurrentFont()
- self.offsets.append(self.offsets[-1] + self.dc.GetFullTextExtent("M", font)[1]*0.5)
- self.start_font({"size" : font.GetPointSize() * 0.8})
-
- def end_sub(self):
- self.fonts.pop()
- self.offsets.pop()
-
- def start_sup(self, attrs):
- if attrs.keys():
- raise ValueError("<sup> does not take attributes")
- font = self.getCurrentFont()
- self.offsets.append(self.offsets[-1] - self.dc.GetFullTextExtent("M", font)[1]*0.3)
- self.start_font({"size" : font.GetPointSize() * 0.8})
-
- def end_sup(self):
- self.fonts.pop()
- self.offsets.pop()
-
- def getCurrentFont(self):
- font = self.fonts[-1]
- return wxFont(font.get("size", self.defaultSize),
- font.get("family", self.defaultFamily),
- font.get("style", self.defaultStyle),
- font.get("weight", self.defaultWeight),
- encoding = font.get("encoding", self.defaultEncoding))
-
- def getCurrentColor(self):
- font = self.fonts[-1]
- return wxNamedColour(font.get("color", self.defaultColor))
-
-
-class SizeRenderer(Renderer):
-
- def __init__(self, dc=None):
- Renderer.__init__(self, dc)
- self.width = self.height = 0
- self.minY = self.maxY = 0
-
- def characterData(self, data):
- self.dc.SetFont(self.getCurrentFont())
- width, height = self.dc.GetTextExtent(data)
- self.width = self.width + width
- self.minY = min(self.minY, self.offsets[-1])
- self.maxY = max(self.maxY, self.offsets[-1] + height)
- self.height = self.maxY - self.minY
-
- def start_angle(self, attrs):
- self.characterData("M")
-
- def end_angle(self):
- pass
-
-class DCRenderer(Renderer):
-
- def __init__(self, dc=None, x=0, y=0):
- Renderer.__init__(self, dc)
- self.x = x
- self.y = y
-
- def characterData(self, data):
- self.dc.SetFont(self.getCurrentFont())
- self.dc.SetTextForeground(self.getCurrentColor())
- width, height = self.dc.GetTextExtent(data)
- self.dc.DrawText(data, self.x, self.y + self.offsets[-1])
- self.x = self.x + width
-
- def start_angle(self, attrs):
- self.dc.SetFont(self.getCurrentFont())
- self.dc.SetPen(wxPen(self.getCurrentColor(), 1))
- width, height, descent, leading = self.dc.GetFullTextExtent("M")
- y = self.y + self.offsets[-1] + height - descent
- self.dc.DrawLine(self.x, y, self.x+width, y)
- self.dc.DrawLine(self.x, y, self.x+width, y-width)
- self.x = self.x + width
-
- def end_angle(self):
- pass
-
-# This is a rendering function that is primarily used internally,
-# although it could be used externally if one had overridden the
-# Renderer classes.
-
-def renderToRenderer(str, renderer, enclose=1):
- if enclose:
- str = '<?xml version="1.0"?><wxFancyString>%s</wxFancyString>' % str
- p = xml.parsers.expat.ParserCreate()
- p.returns_unicode = 0
- p.StartElementHandler = renderer.startElement
- p.EndElementHandler = renderer.endElement
- p.CharacterDataHandler = renderer.characterData
- p.Parse(str, 1)
-
-
-def getExtent(str, dc=None, enclose=1):
- "Return the extent of str"
- renderer = SizeRenderer(dc)
- renderToRenderer(str, renderer, enclose)
- return wxSize(renderer.width, renderer.height)
-
-# This should probably only be used internally....
-
-def getFullExtent(str, dc=None, enclose=1):
- renderer = SizeRenderer(dc)
- renderToRenderer(str, renderer, enclose)
- return renderer.width, renderer.height, -renderer.minY
-
-def renderToBitmap(str, background=None, enclose=1):
- "Return str rendered on a minumum size bitmap"
- dc = wxMemoryDC()
- width, height, dy = getFullExtent(str, dc)
- bmp = wxEmptyBitmap(width, height)
- dc.SelectObject(bmp)
- if background is not None:
- dc.SetBackground(background)
- dc.Clear()
- renderer = DCRenderer(dc, y=dy)
- dc.BeginDrawing()
- renderToRenderer(str, renderer, enclose)
- dc.EndDrawing()
- dc.SelectObject(wxNullBitmap)
- return bmp
-
-def renderToDC(str, dc, x, y, enclose=1):
- "Render str onto a wxDC at (x,y)"
- width, height, dy = getFullExtent(str, dc)
- renderer = DCRenderer(dc, x, y+dy)
- renderToRenderer(str, renderer, enclose)
-
-
-if __name__ == "__main__":
- 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'
- '<font family="swiss" color="green" size="40">big green text</font>')
- ID_EXIT = 102
- class myApp(wxApp):
- def OnInit(self):
- return 1
- app = myApp()
- frame = wxFrame(NULL, -1, "wxFancyText demo", wxDefaultPosition)
- frame.SetClientSize(getExtent(str))
- bmp = renderToBitmap(str, wxCYAN_BRUSH)
- sb = wxStaticBitmap(frame, -1, bmp)
- EVT_MENU(frame, ID_EXIT, frame.Destroy)
- frame.Show(1)
- app.MainLoop()