[dasher: 11/43] CDasherScreen renders/sizes Labels created by MakeLabel not arbitrary strings



commit 7f92a884312c35522f4ae8a64fd0f52e7a6157b8
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Wed May 11 11:30:57 2011 +0100

    CDasherScreen renders/sizes Labels created by MakeLabel not arbitrary strings
    
    Delete label to release layout etc. - better fit w/ modern typesetting libraries
    
    Add various MakeLabels+ChangeScreen functions; CDasherScreen resizable via
      ScreenResized without full call to ChangeScreen. Initialization: ChangeScreen
      before or after Realize. AlphabetManager, ControlManager cache+reuse Labels
      internally => preload cache + flush on alphabet change.
    AlphabetManager creates own structure of groups+chars with Labels at setup time,
      eliding single-child groups once only rather than in every IterateChildGroups
    
    Each platform's Screen's own caching of text size/layout info removed
    
    Mac/iPhone: combine most common implementation into Shared/OpenGLScreen.{h,mm}
      in C++; rm old objective-C protocols and Mac DasherViewAqua + ZippyString*.
     Both return font size as CGSize, Mac by calling NSSizeToCGSize, which should be
      "toll-free bridged" i.e. zero-cost.
    MacOSX: make cachedFontName a property
    
    Gtk2: Rm PangoCache; store map<size,PangoLayout> in each PangoLabel; keep a
      PangoFontDescription for each font size.
    Rm CanvasExperimental, unused so not worth maintaining (?). With Cairo,
     with or without modern h/w-accelerated graphics, is there really any point?
    
    Win32: inc adding version of UTF8string_to_wstring that returns result

 Src/Common/OpenGLScreen.h                    |   70 ++
 Src/Common/OpenGLScreen.mm                   |  292 ++++++++
 Src/DasherCore/Alphabet/AlphInfo.h           |    2 +-
 Src/DasherCore/AlphabetManager.cpp           |  157 +++--
 Src/DasherCore/AlphabetManager.h             |   55 +-
 Src/DasherCore/ControlManager.cpp            |   26 +-
 Src/DasherCore/ControlManager.h              |   13 +-
 Src/DasherCore/ConversionHelper.cpp          |   10 +-
 Src/DasherCore/ConversionHelper.h            |    9 +-
 Src/DasherCore/ConversionManager.cpp         |   25 +-
 Src/DasherCore/ConversionManager.h           |   15 +-
 Src/DasherCore/ConvertingAlphMgr.cpp         |   10 +-
 Src/DasherCore/ConvertingAlphMgr.h           |    4 +-
 Src/DasherCore/DasherInterfaceBase.cpp       |   43 +-
 Src/DasherCore/DasherInterfaceBase.h         |   19 +-
 Src/DasherCore/DasherNode.cpp                |    4 +-
 Src/DasherCore/DasherNode.h                  |   18 +-
 Src/DasherCore/DasherScreen.h                |  101 +++-
 Src/DasherCore/DasherView.h                  |   12 +-
 Src/DasherCore/DasherViewSquare.cpp          |   41 +-
 Src/DasherCore/DasherViewSquare.h            |   17 +-
 Src/DasherCore/MandarinAlphMgr.cpp           |   64 ++-
 Src/DasherCore/MandarinAlphMgr.h             |   21 +-
 Src/DasherCore/NodeCreationManager.cpp       |   10 +-
 Src/DasherCore/NodeCreationManager.h         |    7 +
 Src/DasherCore/Parameters.h                  |    3 +-
 Src/Gtk2/Canvas.cpp                          |  137 +++--
 Src/Gtk2/Canvas.h                            |   67 +--
 Src/Gtk2/CanvasExperimental.cpp              |  914 --------------------------
 Src/Gtk2/CanvasExperimental.h                |  265 --------
 Src/Gtk2/DasherControl.cpp                   |   81 +--
 Src/Gtk2/DasherControl.h                     |    7 -
 Src/Gtk2/Makefile.am                         |    2 -
 Src/Gtk2/PangoCache.cpp                      |   63 --
 Src/Gtk2/PangoCache.h                        |   26 -
 Src/MacOSX/AlphabetLetter.h                  |   22 -
 Src/MacOSX/AlphabetLetter.mm                 |  112 ----
 Src/MacOSX/COSXDasherScreen.h                |  157 -----
 Src/MacOSX/COSXDasherScreen.mm               |   82 ---
 Src/MacOSX/Dasher.xcodeproj/project.pbxproj  |   75 +--
 Src/MacOSX/DasherApp.h                       |   12 +-
 Src/MacOSX/DasherApp.mm                      |   40 +-
 Src/MacOSX/DasherViewAqua.h                  |   85 ---
 Src/MacOSX/DasherViewAqua.mm                 |  461 -------------
 Src/MacOSX/DasherViewCocoa.h                 |   35 -
 Src/MacOSX/DasherViewOpenGL.h                |   57 +--
 Src/MacOSX/DasherViewOpenGL.mm               |  407 +++---------
 Src/MacOSX/ZippyCache.h                      |   31 -
 Src/MacOSX/ZippyCache.m                      |   89 ---
 Src/MacOSX/ZippyString.h                     |   35 -
 Src/MacOSX/ZippyString.m                     |   70 --
 Src/MacOSX/ZippyStringGlyph.h                |   30 -
 Src/MacOSX/ZippyStringGlyph.m                |  110 ---
 Src/MacOSX/ZippyStringImage.h                |   21 -
 Src/MacOSX/ZippyStringImage.m                |   60 --
 Src/Win32/Common/WinUTF8.cpp                 |    6 +
 Src/Win32/Common/WinUTF8.h                   |    1 +
 Src/Win32/Widgets/Canvas.cpp                 |   10 +-
 Src/Win32/Widgets/Screen.cpp                 |  115 ++--
 Src/Win32/Widgets/Screen.h                   |   42 +-
 Src/Win32/Widgets/Screen.inl                 |   15 +-
 Src/iPhone/Classes/AlphabetLetter.h          |   20 -
 Src/iPhone/Classes/AlphabetLetter.mm         |  118 ----
 Src/iPhone/Classes/CDasherInterfaceBridge.h  |    2 +-
 Src/iPhone/Classes/CDasherInterfaceBridge.mm |    8 +-
 Src/iPhone/Classes/CDasherScreenBridge.h     |  113 ----
 Src/iPhone/Classes/CDasherScreenBridge.mm    |   85 ---
 Src/iPhone/Classes/DasherAppDelegate.h       |    1 -
 Src/iPhone/Classes/DasherAppDelegate.mm      |   24 +-
 Src/iPhone/Classes/DasherScreenCallbacks.h   |   29 -
 Src/iPhone/Classes/EAGLView.h                |   42 +-
 Src/iPhone/Classes/EAGLView.mm               |  324 +++-------
 Src/iPhone/Classes/IPhoneFilters.mm          |    2 +-
 Src/iPhone/Classes/IPhoneInputs.mm           |    1 +
 Src/iPhone/Dasher.xcodeproj/project.pbxproj  |   27 +-
 75 files changed, 1372 insertions(+), 4214 deletions(-)
---
diff --git a/Src/Common/OpenGLScreen.h b/Src/Common/OpenGLScreen.h
new file mode 100755
index 0000000..895da5d
--- /dev/null
+++ b/Src/Common/OpenGLScreen.h
@@ -0,0 +1,70 @@
+//
+//  AlphabetLetter.h
+//  Dasher
+//
+//  Created by Alan Lawrence on 20/03/2009.
+//  Copyright 2009 Cavendish Laboratory. All rights reserved.
+//
+#ifdef TARGET_OS_IPHONE
+#import <OpenGLES/ES1/gl.h>
+#else
+#import <AppKit/AppKit.h>
+#import <OpenGL/gl.h>
+#endif
+
+#import "DasherScreen.h"
+#include <string>
+
+namespace Dasher {
+  class OpenGLScreen : public CLabelListScreen {
+  protected:
+    class AlphabetLetter : public Label {
+    public: //to OpenGLScreen and subclasses - all read by DrawString
+      NSString *str;
+      GLuint texture;
+      GLfloat texcoords[8];
+      AlphabetLetter(OpenGLScreen *pScreen, const std::string &strText);
+      ~AlphabetLetter();
+      void PrepareTexture();
+    };    
+  public:
+    OpenGLScreen(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y, GLuint *textures);
+    ~OpenGLScreen();
+    
+    ///Note, subclasses should override to additionally have buffers ready, etc.
+    void Display();
+    void SetColourScheme(const CColourIO::ColourInfo *pColourScheme);
+    void Polyline(point *Points, int iNum, int iWidth, int iColour);
+    void Polygon(point *points, int iNum, int iFillColour, int iOutlineColour, int iWidth);  
+    void DrawRectangle(int x1, int y1, int x2, int y2, int iFillColorIndex, int iOutlineColour, int iThickness);
+    void DrawCircle(screenint iCX, screenint iCY, screenint iR, int iFillColour, int iLineColour, int iLineWidth);
+    
+    bool MultiSizeFonts() {return true;}
+    AlphabetLetter *MakeLabel(const std::string &strText);
+    std::pair<screenint,screenint> TextSize(CDasherScreen::Label *label, unsigned int iFontSize);
+    void DrawString(CDasherScreen::Label *label, screenint x, screenint y, unsigned int iFontSize, int iColour);
+    //leave virtual: SendMarker, Display
+  protected:
+    void resize(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y);
+    void RegenerateLabels();
+    virtual void RenderStringOntoCGContext(NSString *string, CGContextRef context)=0;
+    virtual CGSize TextSize(NSString *str, unsigned int iFontSize)=0;
+  private:
+    typedef struct {
+      float r, g, b;
+    } colour_t;
+    
+    //colours in use, we cache these as floats for feeding to OpenGL.
+    colour_t *colourTable;
+    
+    ///Caches for circleCallbackWithCentrePoint:... (see therein)
+    float circ_rad;
+    GLshort *circ_coords;
+    int circPoints;
+
+    GLshort rectcoords[8];
+    GLfloat texcoords[8];
+    GLuint *textures;
+    
+  };
+}
\ No newline at end of file
diff --git a/Src/Common/OpenGLScreen.mm b/Src/Common/OpenGLScreen.mm
new file mode 100644
index 0000000..ad6935a
--- /dev/null
+++ b/Src/Common/OpenGLScreen.mm
@@ -0,0 +1,292 @@
+//
+//  OpenGLScreen.cpp
+//  Dasher
+//
+//  Created by Alan Lawrence on 20/03/2009.
+//  Copyright 2009 Cavendish Laboratory. All rights reserved.
+//
+
+#import "OpenGLScreen.h"
+#import <iostream>
+#import "DasherUtil.h"
+
+using namespace Dasher;
+using namespace std;
+
+OpenGLScreen::OpenGLScreen(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y, GLuint *_textures)
+: CLabelListScreen(iWidth,iHeight), colourTable(NULL), circ_rad(-1.0f), circ_coords(NULL), circPoints(0), textures(_textures) {
+  resize(iWidth,iHeight,backingWidth,backingHeight,tc_x,tc_y);
+}
+
+void OpenGLScreen::resize(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y) {
+  CLabelListScreen::resize(iWidth, iHeight);
+  rectcoords[0] = rectcoords[1] = 0;
+  rectcoords[2] = backingWidth; rectcoords[3] = 0;
+  rectcoords[4] = 0; rectcoords[5] = backingHeight;
+  rectcoords[6] = backingWidth; rectcoords[7] = backingHeight;
+  
+  texcoords[0] = 0.0; texcoords[1] = tc_y;
+  texcoords[2] = tc_x; texcoords[3] = tc_y;
+  texcoords[4] = 0.0; texcoords[5] = 0.0;
+  texcoords[6] = tc_x; texcoords[7] = 0.0;  
+}
+
+OpenGLScreen::~OpenGLScreen() {
+  delete colourTable;
+  delete circ_coords;
+}
+
+void OpenGLScreen::Display() {
+  SendMarker(-1);
+  
+	glEnable(GL_TEXTURE_2D);
+	glColor4f(1.0, 1.0, 1.0, 1.0);
+	
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	glVertexPointer(2, GL_SHORT, 0, rectcoords);
+	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
+	for (int i=0; i<2; i++)
+	{
+		glBindTexture(GL_TEXTURE_2D, textures[i]);
+		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	}
+  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+  glDisableClientState(GL_VERTEX_ARRAY);
+	glFlush();
+}
+
+void OpenGLScreen::SetColourScheme(const CColourIO::ColourInfo *pColourScheme) {
+ 	delete colourTable;
+  
+  int iNumColours = pColourScheme->Reds.size();
+    
+  colourTable = new colour_t[iNumColours];
+    
+  for(int i = 0; i < iNumColours; i++) {
+    colour_t &ct(colourTable[i]);
+    ct.r = pColourScheme->Reds[i] / 255.0;
+    ct.g = pColourScheme->Greens[i] / 255.0;
+    ct.b = pColourScheme->Blues[i] / 255.0;
+  }
+}
+  
+void OpenGLScreen::Polyline(point *Points, int iNum, int iWidth, int iColour) {
+	if (iNum < 2) return;
+	GLshort *coords = new GLshort[iNum*2];
+	for (int i = 0; i<iNum; i++)
+	{
+		coords[2*i] = Points[i].x;
+		coords[2*i+1] = Points[i].y;
+	}
+	glDisable(GL_TEXTURE_2D);
+	glColor4f(colourTable[iColour].r, colourTable[iColour].g, colourTable[iColour].b, 1.0);
+	glLineWidth(iWidth);
+  glEnableClientState(GL_VERTEX_ARRAY);
+	glVertexPointer(2, GL_SHORT, 0, coords);
+	glDrawArrays(GL_LINE_STRIP, 0, iNum);
+  glDisableClientState(GL_VERTEX_ARRAY);
+	delete coords;
+}
+
+void OpenGLScreen::Polygon(point *points, int iNum, int iFillColour, int iOutlineColour, int iWidth) {
+  if (iNum < 2) return;
+  GLshort *coords = new GLshort[iNum*2];
+  for (int i = 0; i<iNum; i++)
+  {
+    coords[2*i] = points[i].x;
+    coords[2*i+1] = points[i].y;
+  }
+  glDisable(GL_TEXTURE_2D);
+  if (iFillColour != -1) {
+    glColor4f(colourTable[iFillColour].r, colourTable[iFillColour].g, colourTable[iFillColour].b, 1.0);
+    glVertexPointer(2, GL_SHORT, 0, coords);
+    glDrawArrays(GL_TRIANGLE_FAN, 0, iNum);
+  }
+  if (iWidth>0) {
+    glColor4f(colourTable[iOutlineColour].r, colourTable[iOutlineColour].g, colourTable[iOutlineColour].b, 1.0);
+    glLineWidth(iWidth);
+    glVertexPointer(2, GL_SHORT, 0, coords);
+    glDrawArrays(GL_LINE_LOOP, 0, iNum);
+  }
+  delete coords;  
+}
+
+void OpenGLScreen::DrawRectangle(int x1, int y1, int x2, int y2, int iFillColorIndex, int iOutlineColour, int iThickness) {
+  glDisable(GL_TEXTURE_2D);
+  glEnableClientState(GL_VERTEX_ARRAY);
+  if (iFillColorIndex != -1) {
+    glColor4f(colourTable[iFillColorIndex].r, colourTable[iFillColorIndex].g, colourTable[iFillColorIndex].b, 1.0);
+    GLshort coords[8] = {x1,y1, x2,y1, x1,y2, x2,y2};
+    glVertexPointer(2, GL_SHORT, 0, coords);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+  }
+  if (iThickness>0) {
+    int oci = iOutlineColour == -1 ? 3 : iOutlineColour;
+    glColor4f(colourTable[oci].r, colourTable[oci].g, colourTable[oci].b, 1.0);
+    glLineWidth(iThickness);
+    GLshort coords[] = {x1,y1, x2,y1, x2,y2,  x1,y2};
+    glVertexPointer(2, GL_SHORT, 0, coords);
+    glDrawArrays(GL_LINE_LOOP, 0, 4);
+  }
+}
+
+void OpenGLScreen::DrawCircle(screenint iCX, screenint iCY, screenint iR, int iFillColour, int iOutlineColour, int iLineWidth) {
+  //it's a bit of a hack, but we cache the last-computed set of points round the circle,
+  // as these are the same for all calls with the same radius - and (the hack!) it happens
+  // that the radius tends to be the same every time (as the only call to CDasherScreen::DrawCircle
+  // is from CircleStartHandler!)...
+  if (circ_rad != iR) {
+    delete circ_coords;
+    double costh=1.0f - 1.0f/(2.0f*iR);
+    double th = acos(costh);
+    int numPoints = circPoints = ceil(M_PI/th/2.0f); //for a quarter-circle
+    double sinth = sin(th),x(iR),y(0.0);
+    circ_coords = new GLshort[numPoints*8]; circ_rad = iR;
+    circ_coords[0] = x; circ_coords[1] = y;
+    for (int i=1; i<numPoints; i++) {
+      double nx = x*costh - y*sinth;
+      double ny = x*sinth + y*costh;
+      circ_coords[2*i] = nx;
+      circ_coords[2*i+1] = ny;
+      x=nx; y=ny;
+    }
+    for (int i=0; i<numPoints; i++) {
+      circ_coords[2*(i+numPoints)] = -circ_coords[2*i+1];
+      circ_coords[2*(i+numPoints)+1] = circ_coords[2*i];
+      
+      circ_coords[2*(i+numPoints*2)] = -circ_coords[2*i];
+      circ_coords[2*(i+numPoints*2)+1] = -circ_coords[2*i+1];
+      
+      circ_coords[2*(i+numPoints*3)] = circ_coords[2*i+1];
+      circ_coords[2*(i+numPoints*3)+1] = -circ_coords[2*i];
+    }
+  }
+
+  glDisable(GL_TEXTURE_2D);
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glTranslatef(iCX, iCY, 0.0);
+  if (iFillColour!=-1) {
+    glColor4f(colourTable[iFillColour].r, colourTable[iFillColour].g, colourTable[iFillColour].b, 1.0);
+    glVertexPointer(2, GL_SHORT, 0, circ_coords);
+    glDrawArrays(GL_TRIANGLE_FAN, 0, circPoints*4);
+  }
+  if (iLineWidth>0) {
+    int oci = iOutlineColour == -1 ? 3 : iOutlineColour;
+    glColor4f(colourTable[oci].r, colourTable[oci].g, colourTable[oci].b, 1.0);
+    glLineWidth(iLineWidth);
+    glVertexPointer(2, GL_SHORT, 0, circ_coords);
+    glDrawArrays(GL_LINE_LOOP, 0, circPoints*4);
+  }
+  glTranslatef(-iCX, -iCY, 0.0);
+}
+
+
+#pragma mark text/label classes+routines
+
+void dump(char *data, int width, int height)
+{
+	static char buf[10240]; buf[0] = 0;
+	for (int h = 0; h < height; h++)
+	{
+		for (int w = 0; w < width; w++)
+			sprintf(buf+strlen(buf), "%i ", *((int*)&data[4* (w + h * width)]));
+		sprintf(buf + strlen(buf), "\n");
+	}
+	std::cout << buf;
+}
+
+OpenGLScreen::AlphabetLetter::AlphabetLetter(OpenGLScreen *pScreen, const string &strText) : Label(pScreen,strText), str(NSStringFromStdString(strText)) {
+  [str retain];
+
+  glGenTextures(1, &texture);
+
+  PrepareTexture();
+}
+
+void OpenGLScreen::AlphabetLetter::PrepareTexture() {
+  int width=1, height=1;
+  GLfloat texw,texh;
+  {
+    CGSize sz = static_cast<OpenGLScreen *>(m_pScreen)->TextSize(str,36);
+    while (width<sz.width) width<<=1;
+    while (height<sz.height) height<<=1;
+    texw = sz.width/(float)width;
+    texh = sz.height/(float)height;
+  }
+
+  char *data = new char[width*height*4];
+  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+  CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width*4, colorSpace, kCGImageAlphaPremultipliedLast);
+  CGContextClearRect(context, CGRectMake(0.0, 0.0, width, height));
+
+  static_cast<OpenGLScreen *>(m_pScreen)->RenderStringOntoCGContext(str,context);
+
+  glBindTexture(GL_TEXTURE_2D, texture);
+  //...but tell the GL _not_ to interpolate between texels, as that results in a _big_
+  // grey border round each letter (?!)
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+//	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, CGBitmapContextGetData(context));
+
+  CGColorSpaceRelease(colorSpace);
+  CGContextRelease(context);
+  free(data);
+  
+  //set texture coords for the corners of the part of the texture we actually
+  // drew into (as the texture had to be sized to a power of 2; note we rendered
+  // into the *bottom* left portion)
+  texcoords[0] = 0.0; texcoords[1] = 1.0;
+  texcoords[2] = texw; texcoords[3] = 1.0;
+  texcoords[4] = 0.0; texcoords[5] = 1.0-texh;
+  texcoords[6] = texw; texcoords[7] = 1.0-texh;
+}
+
+OpenGLScreen::AlphabetLetter::~AlphabetLetter() {
+  [str release];
+  glDeleteTextures(1, &texture);
+}
+
+OpenGLScreen::AlphabetLetter *OpenGLScreen::MakeLabel(const std::string &strText) {
+  return new AlphabetLetter(this,strText);
+}
+
+void OpenGLScreen::RegenerateLabels() {
+  for (set<Label *>::iterator it=LabelsBegin(); it!=LabelsEnd(); it++)
+    static_cast<AlphabetLetter *>(*it)->PrepareTexture();
+}
+
+void OpenGLScreen::DrawString(CDasherScreen::Label *label, screenint x, screenint y, unsigned int iFontSize, int iColour) {
+  //(void)drawWithSize:(int)iSize x:(int)x y:(int)y r:(float)r g:(float)g b:(float)b {
+	const AlphabetLetter *l(static_cast<AlphabetLetter *> (label));
+	// bind and draw
+	glEnable(GL_TEXTURE_2D);
+	glBindTexture(GL_TEXTURE_2D, l->texture);
+  //"modulate" means to multiply the texture (i.e. 100%=white text, 0%=transparent background)
+  // by the currently selected GL foreground colour
+	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+	glColor4f(colourTable[iColour].r, colourTable[iColour].g, colourTable[iColour].b, 1.0); //so we select the colour we want the text to appear in
+	CGSize sz = TextSize(l->str, iFontSize);
+	GLshort coords[8];
+	coords[0] = x; coords[1]=y;
+	coords[2] = x+sz.width; coords[3] = y;
+	coords[4] = x; coords[5] = y+sz.height;
+	coords[6] = x+sz.width; coords[7]=y+sz.height;
+  glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	glVertexPointer(2, GL_SHORT, 0, coords);
+	glTexCoordPointer(2, GL_FLOAT, 0, l->texcoords);
+	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+  glDisableClientState(GL_VERTEX_ARRAY);
+	glDisable(GL_TEXTURE_2D);
+}
+
+pair<screenint,screenint> OpenGLScreen::TextSize(CDasherScreen::Label *label, unsigned int iFontSize) {
+  const AlphabetLetter *l(static_cast<AlphabetLetter *> (label));
+  CGSize sz = TextSize(l->str, iFontSize);
+  //apply "ceil" to floating-point width/height ?
+  return pair<screenint,screenint>(sz.width, sz.height);
+}
+
diff --git a/Src/DasherCore/Alphabet/AlphInfo.h b/Src/DasherCore/Alphabet/AlphInfo.h
index 03abd44..7e137af 100644
--- a/Src/DasherCore/Alphabet/AlphInfo.h
+++ b/Src/DasherCore/Alphabet/AlphInfo.h
@@ -69,7 +69,7 @@ public:
   /// Return number of text symbols - inc space and para, but no control/conversion start/end
   /// Note symbol numbers are 1-indexed; 0 is reserved to indicate an "unknown symbol" (-1 = End-Of-Stream),
   /// and for element 0 of the probability array to contain a 0.
-  int GetNumberTextSymbols() const {return m_vCharacters.size();}
+  unsigned int GetNumberTextSymbols() const {return m_vCharacters.size();}
 
   Opts::ScreenOrientations GetOrientation() const {return Orientation;}
 
diff --git a/Src/DasherCore/AlphabetManager.cpp b/Src/DasherCore/AlphabetManager.cpp
index 7654ec4..4992cc5 100644
--- a/Src/DasherCore/AlphabetManager.cpp
+++ b/Src/DasherCore/AlphabetManager.cpp
@@ -51,9 +51,9 @@ static char THIS_FILE[] = __FILE__;
 #endif
 
 CAlphabetManager::CAlphabetManager(CDasherInterfaceBase *pInterface, CNodeCreationManager *pNCManager, const CAlphInfo *pAlphabet, const CAlphabetMap *pAlphabetMap)
-  : m_pNCManager(pNCManager), m_pAlphabet(pAlphabet), m_pAlphabetMap(pAlphabetMap), m_pInterface(pInterface) {
+  : m_pFirstGroup(NULL), m_pNCManager(pNCManager), m_pAlphabet(pAlphabet), m_pAlphabetMap(pAlphabetMap), m_pInterface(pInterface) {
   //Look for a (single-octet) character not in the alphabet...
-  for (char c=33; c<0x80; c++) {
+  for (char c=33; (c&0x80)==0; c++) {
     string s(&c,1);
     if (pAlphabetMap->Get(s)==0) {
       m_sDelim = s;
@@ -62,6 +62,8 @@ CAlphabetManager::CAlphabetManager(CDasherInterfaceBase *pInterface, CNodeCreati
   }
   //else, if all single-octet chars are in alphabet - leave m_sDelim==""
   // (and we'll find a delimiter for each context)
+
+  m_vLabels.resize(m_pAlphabet->GetNumberTextSymbols()+1);
 }
 
 void CAlphabetManager::CreateLanguageModel(CEventHandler *pEventHandler, CSettingsStore *pSettingsStore) {
@@ -89,6 +91,64 @@ CTrainer *CAlphabetManager::GetTrainer() {
   return new CTrainer(m_pLanguageModel, m_pAlphabet, m_pAlphabetMap);
 }
 
+void CAlphabetManager::MakeLabels(CDasherScreen *pScreen) {
+  delete m_pFirstGroup;
+  for (vector<CDasherScreen::Label *>::iterator it=m_vLabels.begin(); it!=m_vLabels.end(); it++) {
+    delete (*it); *it = NULL;
+  }
+  m_pFirstGroup = copyGroups(pScreen, 1, m_pAlphabet->GetNumberTextSymbols()+1,m_pAlphabet->m_pBaseGroup);
+}
+
+CAlphabetManager::SGroupInfo::SGroupInfo(CDasherScreen *pScreen, const std::string &strEnc, int iBkgCol, const ::SGroupInfo *pCopy)
+: pChild(NULL), pNext(NULL), strLabel(strEnc + pCopy->strLabel), iStart(pCopy->iStart), iEnd(pCopy->iEnd),
+  iColour(pCopy->bVisible ? pCopy->iColour : iBkgCol), bVisible(pCopy->bVisible || (iBkgCol!=-1)),
+  iNumChildNodes(pCopy->iNumChildNodes), pLabel(strLabel.empty() ? NULL : pScreen->MakeLabel(strLabel)) {
+}
+
+CAlphabetManager::SGroupInfo::~SGroupInfo() {
+  delete pChild;
+  delete pNext;
+  delete pLabel;
+}
+
+CAlphabetManager::SGroupInfo *CAlphabetManager::copyGroups(CDasherScreen *pScreen, int iStart, int iEnd, ::SGroupInfo *pFirstChild) {  
+  for (int i = iStart; i< iEnd; i++) {
+    string strGroupPrefix;
+    if (pFirstChild && i>=pFirstChild->iStart) {
+      //reached group. elide any group with only a single child (see below).
+      // Variables store necessary properties of any elided groups:
+      int iBkgCol(-1);
+      for (const ::SGroupInfo *pInner=pFirstChild;;) {
+        if (pInner->iNumChildNodes>1) { //in/reached nontrivial subgroup - do make node for entire group:
+          SGroupInfo *pRes = new SGroupInfo(pScreen, strGroupPrefix, iBkgCol, pInner);
+          pRes->pChild = copyGroups(pScreen, pInner->iStart, pInner->iEnd, pInner->pChild);
+          pRes->pNext = copyGroups(pScreen, pInner->iEnd, iEnd, pFirstChild->pNext);
+          return pRes;
+        }
+        //were about to create a group node, which would have only one child
+        // (eventually, if the group node were PopulateChildren'd).
+        // Such a child would entirely fill it's parent (the group), and thus,
+        // creation/destruction of the child would cause the node's colour to flash
+        // between that for parent group and child.
+        // Hence, instead we elide the group node and create the child _here_...
+        
+        //1. however we also have to take account of the appearance of the elided group. Hence:
+        strGroupPrefix += pInner->strLabel;
+        if (pInner->bVisible) iBkgCol=pInner->iColour;
+        //2. inner group might contain a single subgroup, or a single symbol...
+        if (!pInner->pChild) break;
+        //...a subgroup, so go into it
+        pInner = pInner->pChild;
+        DASHER_ASSERT(!pInner->pNext);
+        //3. loop round inner loop...
+      }
+      pFirstChild = pFirstChild->pNext; //making a symbol, so we've still moved past the outer (elided) group
+    }
+    m_vLabels[i]=pScreen->MakeLabel(strGroupPrefix+m_pAlphabet->GetDisplayText(i));
+  }
+  return NULL;
+}
+
 const CAlphInfo *CAlphabetManager::GetAlphabet() const {
   return m_pAlphabet;
 }
@@ -144,8 +204,8 @@ int CAlphabetManager::GetColour(symbol sym, int iOffset) const {
 }
 
 
-CAlphabetManager::CAlphBase::CAlphBase(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText, CAlphabetManager *pMgr)
-: CDasherNode(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText), m_pMgr(pMgr) {
+CAlphabetManager::CAlphBase::CAlphBase(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr)
+: CDasherNode(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel), m_pMgr(pMgr) {
 }
 
 void CAlphabetManager::CAlphBase::Output(Dasher::VECTOR_SYMBOL_PROB* pAdded, int iNormalization) {
@@ -159,23 +219,23 @@ void CAlphabetManager::CAlphBase::Output(Dasher::VECTOR_SYMBOL_PROB* pAdded, int
 void CAlphabetManager::CAlphBase::Undo(int *pNumDeleted) {
   if (m_pMgr->m_pLastOutput==this) m_pMgr->m_pLastOutput = Parent();
 }
-CAlphabetManager::CAlphNode::CAlphNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText, CAlphabetManager *pMgr)
-: CAlphBase(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText, pMgr), m_pProbInfo(NULL) {
+CAlphabetManager::CAlphNode::CAlphNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr)
+: CAlphBase(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel, pMgr), m_pProbInfo(NULL) {
 }
 
-CAlphabetManager::CSymbolNode::CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, CAlphabetManager *pMgr, symbol _iSymbol)
-: CAlphNode(pParent, iOffset, iLbnd, iHbnd, pMgr->GetColour(_iSymbol, iOffset), strGroup+pMgr->m_pAlphabet->GetDisplayText(_iSymbol), pMgr), iSymbol(_iSymbol) {
+CAlphabetManager::CSymbolNode::CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr, symbol _iSymbol)
+: CAlphNode(pParent, iOffset, iLbnd, iHbnd, pMgr->GetColour(_iSymbol, iOffset), pLabel, pMgr), iSymbol(_iSymbol) {
 }
 
-CAlphabetManager::CSymbolNode::CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText, CAlphabetManager *pMgr, symbol _iSymbol)
-: CAlphNode(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText, pMgr), iSymbol(_iSymbol) {
+CAlphabetManager::CSymbolNode::CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr, symbol _iSymbol)
+: CAlphNode(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel, pMgr), iSymbol(_iSymbol) {
 }
 
-CAlphabetManager::CGroupNode::CGroupNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, CAlphabetManager *pMgr, const SGroupInfo *pGroup)
+CAlphabetManager::CGroupNode::CGroupNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CDasherScreen::Label *pLabel, int iBkgCol, CAlphabetManager *pMgr, const SGroupInfo *pGroup)
 : CAlphNode(pParent, iOffset, iLbnd, iHbnd,
             pGroup ? (pGroup->bVisible ? pGroup->iColour : iBkgCol)
             : (iOffset&1) ? 7 : 137, //special case for root nodes
-            pGroup ? strEnc+pGroup->strLabel : strEnc, pMgr), m_pGroup(pGroup) {
+            pLabel, pMgr), m_pGroup(pGroup) {
 }
 
 CAlphabetManager::CAlphNode *CAlphabetManager::GetRoot(CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, bool bEnteredLast, int iOffset) {
@@ -187,12 +247,12 @@ CAlphabetManager::CAlphNode *CAlphabetManager::GetRoot(CDasherNode *pParent, uns
   CAlphNode *pNewNode;
   if(p.first==0 || !bEnteredLast) {
     //couldn't extract last symbol (so probably using default context), or shouldn't
-    pNewNode = new CGroupNode(pParent, iNewOffset, iLower, iUpper, "", 0, this, NULL); //default background colour
+    pNewNode = new CGroupNode(pParent, iNewOffset, iLower, iUpper, NULL, 0, this, NULL); //default background colour
   } else {
     //new node represents a symbol that's already happened - i.e. user has already steered through it;
     // so either we're rebuilding, or else creating a new root from existing text (in edit box)
     DASHER_ASSERT(!pParent);
-    pNewNode = new CSymbolNode(pParent, iNewOffset, iLower, iUpper, "", this, p.first);
+    pNewNode = new CSymbolNode(pParent, iNewOffset, iLower, iUpper, m_vLabels[p.first], this, p.first);
   }
 
   pNewNode->iContext = p.second;
@@ -337,12 +397,12 @@ int CAlphabetManager::CGroupNode::ExpectedNumChildren() {
   return (m_pGroup) ? m_pGroup->iNumChildNodes : CAlphNode::ExpectedNumChildren();
 }
 
-CAlphabetManager::CGroupNode *CAlphabetManager::CreateGroupNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, const SGroupInfo *pInfo) {
+CAlphabetManager::CGroupNode *CAlphabetManager::CreateGroupNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, int iBkgCol, const SGroupInfo *pInfo) {
 
   // When creating a group node...
   // ...the offset is the same as the parent...
 
-  CGroupNode *pNewNode = new CGroupNode(pParent, pParent->offset(), iLbnd, iHbnd, strEnc, iBkgCol, this, pInfo);
+  CGroupNode *pNewNode = new CGroupNode(pParent, pParent->offset(), iLbnd, iHbnd, pInfo->pLabel, iBkgCol, this, pInfo);
 
   //...as is the context!
   pNewNode->iContext = m_pLanguageModel->CloneContext(pParent->iContext);
@@ -350,8 +410,8 @@ CAlphabetManager::CGroupNode *CAlphabetManager::CreateGroupNode(CAlphNode *pPare
   return pNewNode;
 }
 
-CDasherNode *CAlphabetManager::CAlphBase::RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, const SGroupInfo *pInfo) {
-  CGroupNode *pRet=m_pMgr->CreateGroupNode(pParent, iLbnd, iHbnd, strEnc, iBkgCol, pInfo);
+CDasherNode *CAlphabetManager::CAlphBase::RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, int iBkgCol, const SGroupInfo *pInfo) {
+  CGroupNode *pRet=m_pMgr->CreateGroupNode(pParent, iLbnd, iHbnd, iBkgCol, pInfo);
   if (isInGroup(pInfo)) {
     //created group node should contain this one
     m_pMgr->IterateChildGroups(pRet,pInfo,this);
@@ -359,7 +419,7 @@ CDasherNode *CAlphabetManager::CAlphBase::RebuildGroup(CAlphNode *pParent, unsig
   return pRet;
 }
 
-CDasherNode *CAlphabetManager::CGroupNode::RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, const SGroupInfo *pInfo) {
+CDasherNode *CAlphabetManager::CGroupNode::RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, int iBkgCol, const SGroupInfo *pInfo) {
   if (pInfo == m_pGroup) {
     SetRange(iLbnd, iHbnd);
     SetParent(pParent);
@@ -367,7 +427,7 @@ CDasherNode *CAlphabetManager::CGroupNode::RebuildGroup(CAlphNode *pParent, unsi
     DASHER_ASSERT (offset() == pParent->offset());
     return this;
   }
-  return CAlphBase::RebuildGroup(pParent, iLbnd, iHbnd, strEnc, iBkgCol, pInfo);
+  return CAlphBase::RebuildGroup(pParent, iLbnd, iHbnd, iBkgCol, pInfo);
 }
 
 bool CAlphabetManager::CGroupNode::isInGroup(const SGroupInfo *pInfo) {
@@ -378,7 +438,7 @@ bool CAlphabetManager::CSymbolNode::isInGroup(const SGroupInfo *pInfo) {
   return (pInfo->iStart <= iSymbol && pInfo->iEnd > iSymbol);
 }
 
-CDasherNode *CAlphabetManager::CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol) {
+CDasherNode *CAlphabetManager::CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol) {
 
     // TODO: Exceptions / error handling in general
 
@@ -386,7 +446,7 @@ CDasherNode *CAlphabetManager::CreateSymbolNode(CAlphNode *pParent, unsigned int
     // (and we can't call numChars() on the symbol before we've constructed it!)
     int iNewOffset = pParent->offset()+1;
     if (m_pAlphabet->GetText(iSymbol)=="\r\n") iNewOffset++;
-    CSymbolNode *pAlphNode = new CSymbolNode(pParent, iNewOffset, iLbnd, iHbnd, "", this, iSymbol);
+    CSymbolNode *pAlphNode = new CSymbolNode(pParent, iNewOffset, iLbnd, iHbnd, m_vLabels[iSymbol], this, iSymbol);
 
     //     std::stringstream ssLabel;
 
@@ -400,18 +460,18 @@ CDasherNode *CAlphabetManager::CreateSymbolNode(CAlphNode *pParent, unsigned int
   return pAlphNode;
 }
 
-CDasherNode *CAlphabetManager::CAlphBase::RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol) {
-  return m_pMgr->CreateSymbolNode(pParent, iLbnd, iHbnd, strGroup, iBkgCol, iSymbol);
+CDasherNode *CAlphabetManager::CAlphBase::RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol) {
+  return m_pMgr->CreateSymbolNode(pParent, iLbnd, iHbnd, iSymbol);
 }
 
-CDasherNode *CAlphabetManager::CSymbolNode::RebuildSymbol(CAlphNode *pParent,  unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol) {
+CDasherNode *CAlphabetManager::CSymbolNode::RebuildSymbol(CAlphNode *pParent,  unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol) {
   if(iSymbol == this->iSymbol) {
     SetRange(iLbnd, iHbnd);
     SetParent(pParent);
     DASHER_ASSERT(offset() == pParent->offset() + numChars());
     return this;
   }
-  return CAlphBase::RebuildSymbol(pParent, iLbnd, iHbnd, strGroup, iBkgCol, iSymbol);
+  return CAlphBase::RebuildSymbol(pParent, iLbnd, iHbnd, iSymbol);
 }
 
 void CAlphabetManager::IterateChildGroups(CAlphNode *pParent, const SGroupInfo *pParentGroup, CAlphBase *buildAround) {
@@ -427,7 +487,7 @@ void CAlphabetManager::IterateChildGroups(CAlphNode *pParent, const SGroupInfo *
   // Create child nodes and add them
 
   int i(iMin); //lowest index of child which we haven't yet added
-  const SGroupInfo *pCurrentNode(pParentGroup ? pParentGroup->pChild : m_pAlphabet->m_pBaseGroup);
+  const SGroupInfo *pCurrentNode(pParentGroup ? pParentGroup->pChild : m_pFirstGroup);
   // The SGroupInfo structure has something like linked list behaviour
   // Each SGroupInfo contains a pNext, a pointer to a sibling group info
   while (i < iMax) {
@@ -442,38 +502,15 @@ void CAlphabetManager::IterateChildGroups(CAlphNode *pParent, const SGroupInfo *
     unsigned int iHbnd = (((*pCProb)[iEnd-1] - (*pCProb)[iMin-1]) *
                           (uint64)(m_pNCManager->GetLongParameter(LP_NORMALIZATION))) /
                          iRange;
-    //loop for eliding groups with single children (see below).
-    // Variables store necessary properties of any elided groups:
-    std::string groupPrefix=""; int iBackgroundColour=pParent->getColour();
-    const SGroupInfo *pInner=pCurrentNode;
-    while (true) {
-      if (bSymbol) {
-        pNewChild = (buildAround) ? buildAround->RebuildSymbol(pParent, iLbnd, iHbnd, groupPrefix, iBackgroundColour, i) : CreateSymbolNode(pParent, iLbnd, iHbnd, groupPrefix, iBackgroundColour, i);
-        i++; //make one symbol at a time - move onto next symbol in next iteration of (outer) loop
-        break; //exit inner (group elision) loop
-      } else if (pInner->iNumChildNodes>1) { //in/reached nontrivial subgroup - do make node for entire group:
-        pNewChild= (buildAround) ? buildAround->RebuildGroup(pParent, iLbnd, iHbnd, groupPrefix, iBackgroundColour, pInner) : CreateGroupNode(pParent, iLbnd, iHbnd, groupPrefix, iBackgroundColour, pInner);
-        i = pInner->iEnd; //make one group at a time - so move past entire group...
-        pCurrentNode = pCurrentNode->pNext; //next sibling of _original_ pCurrentNode (above)
-                                     // (maybe not of pCurrentNode now, which might be a subgroup filling the original)
-        break; //exit inner (group elision) loop
-      }
-      //were about to create a group node, which would have only one child
-      // (eventually, if the group node were PopulateChildren'd).
-      // Such a child would entirely fill it's parent (the group), and thus,
-      // creation/destruction of the child would cause the node's colour to flash
-      // between that for parent group and child.
-      // Hence, instead we elide the group node and create the child _here_...
-
-      //1. however we also have to take account of the appearance of the elided group. Hence:
-      groupPrefix += pInner->strLabel;
-      if (pInner->bVisible) iBackgroundColour=pInner->iColour;
-      //2. now go into the group...
-      pInner = pInner->pChild;
-      bSymbol = (pInner==NULL); //which might contain a single subgroup, or a single symbol
-      if (bSymbol) pCurrentNode = pCurrentNode->pNext; //if a symbol, we've still moved past the outer (elided) group
-      DASHER_ASSERT(iEnd == (bSymbol ? i+1 : pInner->iEnd)); //probability calcs still ok
-      //3. loop round inner loop...
+    if (bSymbol) {
+      pNewChild = (buildAround) ? buildAround->RebuildSymbol(pParent, iLbnd, iHbnd, i) : CreateSymbolNode(pParent, iLbnd, iHbnd, i);
+      i++; //make one symbol at a time - move onto next symbol in next iteration of (outer) loop
+    } else {
+      DASHER_ASSERT(pCurrentNode->iNumChildNodes > 1);
+      pNewChild= (buildAround) ? buildAround->RebuildGroup(pParent, iLbnd, iHbnd, pParent->getColour(), pCurrentNode) : CreateGroupNode(pParent, iLbnd, iHbnd, pParent->getColour(), pCurrentNode);
+      i = pCurrentNode->iEnd; //make one group at a time - so move past entire group...
+      pCurrentNode = pCurrentNode->pNext; //next sibling of _original_ pCurrentNode (above)
+      // (maybe not of pCurrentNode now, which might be a subgroup filling the original)
     }
     //created a new node - symbol or (group which will have >1 child).
     DASHER_ASSERT(pParent->GetChildren().back()==pNewChild);
@@ -564,7 +601,7 @@ CDasherNode *CAlphabetManager::CGroupNode::RebuildParent() {
   if (Parent()) return Parent();
 
   // CGroupNodes with an m_pGroup have a container i.e. the parent group, unless
-  // m_pGroup==NULL => "root" node where Alphabet->m_pBaseGroup is the *first*child*...
+  // m_pGroup==NULL => "root" node where m_pMgr->m_pFirstGroup is the *first*child*...
   if (m_pGroup == NULL) return NULL;
 
   return CAlphBase::RebuildParent();
diff --git a/Src/DasherCore/AlphabetManager.h b/Src/DasherCore/AlphabetManager.h
index a587d57..50d5f9a 100644
--- a/Src/DasherCore/AlphabetManager.h
+++ b/Src/DasherCore/AlphabetManager.h
@@ -48,10 +48,12 @@ namespace Dasher {
   public:
     ///Create a new AlphabetManager. Note, not usable until CreateLanguageModel() called.
     CAlphabetManager(CDasherInterfaceBase *pInterface, CNodeCreationManager *pNCManager, const CAlphInfo *pAlphabet, const CAlphabetMap *pAlphabetMap);
+    
     ///Creates the LM, and stores in m_pLanguageModel. Must be called after construction,
     /// before the AlphMgr is used. Default implementation switches on LP_LANGUAGE_MODEL_ID.
     virtual void CreateLanguageModel(CEventHandler *pEventHandler, CSettingsStore *pSets);
 
+    virtual void MakeLabels(CDasherScreen *pScreen);
     ///Gets a new trainer to train this LM. Caller is responsible for deallocating the
     /// trainer later.
     virtual CTrainer *GetTrainer();
@@ -61,6 +63,25 @@ namespace Dasher {
     /// \param pInterface to use for I/O by calling WriteTrainFile(fname,txt)
     void WriteTrainFileFull(CDasherInterfaceBase *pInterface);
   protected:
+    ///The SGroupInfo tree from the alphabet, but with single-child groups collapsed,
+    /// and with labels from the Screen.
+    struct SGroupInfo {
+      SGroupInfo(CDasherScreen *pScreen, const std::string &strEnc, int iBkgCol, const ::SGroupInfo *pCopy);
+      ~SGroupInfo();
+      SGroupInfo *pChild;
+      SGroupInfo *pNext;
+      std::string strLabel;
+      int iStart;
+      int iEnd;
+      int iColour;
+      bool bVisible;
+      int iNumChildNodes;
+      CDasherScreen::Label *pLabel;
+    } *m_pFirstGroup;
+    //A label for each symbol, indexed by symbol id (element 0 = null)
+    std::vector<CDasherScreen::Label *> m_vLabels;
+    SGroupInfo *copyGroups(CDasherScreen *pScreen, int iStart, int iEnd, ::SGroupInfo *pFirstChild);
+    
     class CAlphNode;
     /// Abstract superclass for alphabet manager nodes, provides common implementation
     /// code for rebuilding parent nodes = reversing.
@@ -74,18 +95,15 @@ namespace Dasher {
       /// Default implementation just calls the manager's CreateSymbolNode method to create a new node,
       /// but subclasses can override to graft themselves into the appropriate point beneath the previous node.
       /// \param pParent parent of the symbol node to create; could be the previous root, or an intervening node (e.g. group)
-      /// \param strGroup caption of any elided group node. If a new node is created, this should be appended to that node's caption;
-      /// however, if this existing node is grafted in instead, the caption will already have been prepended (as the group structure
-      /// is always the same and just repeats endlessly), so should be ignored.
       /// \param iBkgCol background colour to show through any new transparent node created;
       /// if the existing node is grafted in, again this will already have been taken into account.
-      virtual CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
+      virtual CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
       ///Called to build a group node which is a descendant of the symbol or root node preceding this.
       /// Default implementation calls the manager's CreateGroupNode method to create a new node,
       /// but then populates that group (i.e. further descends the hierarchy) _if_ that group
       /// would contain this node (see IsInGroup). Subclasses can override to graft themselves into the hierarchy, if appropriate.
       /// \param pParent parent of the symbol node to create; could be the previous root, or an intervening node (e.g. group)
-      virtual CDasherNode *RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, const SGroupInfo *pInfo);
+      virtual CDasherNode *RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, int iBkgCol, const SGroupInfo *pInfo);
       ///Just keep track of the last node output (for training file purposes)
       void Undo(int *pNumDeleted);
       ///Just keep track of the last node output (for training file purposes)
@@ -97,7 +115,7 @@ namespace Dasher {
       /// at which point RebuildSymbol/Group should graft it in.
       /// \param pNewNode newly-created root node beneath which this node should fit
       virtual void RebuildForwardsFromAncestor(CAlphNode *pNewNode);
-      CAlphBase(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText, CAlphabetManager *pMgr);
+      CAlphBase(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr);
       CAlphabetManager *m_pMgr;
       ///Number of unicode characters entered by this node; i.e., the number
       /// to take off this node's offset, to get the offset of the most-recent
@@ -107,11 +125,10 @@ namespace Dasher {
       /// (as a symbol or subgroup), any number of levels beneath it
       virtual bool isInGroup(const SGroupInfo *pGroup)=0;
     };
-
     ///Additionally stores LM contexts and probabilities calculated therefrom
     class CAlphNode : public CAlphBase {
     public:
-      CAlphNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText, CAlphabetManager *pMgr);
+      CAlphNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr);
       CLanguageModel::Context iContext;
       ///
       /// Delete any storage alocated for this node
@@ -127,10 +144,8 @@ namespace Dasher {
     class CSymbolNode : public CAlphNode {
     public:
       ///Standard constructor, gets colour from GetColour(symbol,offset) and label from current alphabet
-      /// \param strGroup caption of any enclosing group(s) of which this symbol is a singleton child
-      /// - this is prepended onto the symbol caption. Note, we don't need the "background colour" of
-      /// any such group, as GetColour() always returns an opaque color.
-      CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, CAlphabetManager *pMgr, symbol iSymbol);
+      /// Note we treat GetColour() as always returning an opaque color.
+      CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr, symbol iSymbol);
 
       ///Create the children of this node, by starting traversal of the alphabet from the top
       virtual void PopulateChildren();
@@ -143,7 +158,7 @@ namespace Dasher {
       virtual void GetContext(CDasherInterfaceBase *pInterface, const CAlphabetMap *pAlphabetMap, std::vector<symbol> &vContextSymbols, int iOffset, int iLength);
       virtual symbol GetAlphSymbol();
       ///Override: if the symbol to create is the same as this node's symbol, return this node instead of creating a new one
-      virtual CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
+      virtual CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
     protected:
       virtual const std::string &outputText();
       ///Text to write to user training file/buffer when this symbol output.
@@ -155,7 +170,7 @@ namespace Dasher {
       /// unicode char, even if that might take >1 octet.
       int numChars();
       ///Compatibility constructor, so that subclasses can specify their own colour & label
-      CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText, CAlphabetManager *pMgr, symbol _iSymbol);
+      CSymbolNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CAlphabetManager *pMgr, symbol _iSymbol);
       ///Override: true iff pGroup encloses this symbol (according to its start/end symbol#)
       bool isInGroup(const SGroupInfo *pGroup);
       const symbol iSymbol;
@@ -163,7 +178,7 @@ namespace Dasher {
 
     class CGroupNode : public CAlphNode {
     public:
-      CGroupNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, CAlphabetManager *pMgr, const SGroupInfo *pGroup);
+      CGroupNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CDasherScreen::Label *pLabel, int iBkgCol, CAlphabetManager *pMgr, const SGroupInfo *pGroup);
 
       ///Override: if m_pGroup==NULL, i.e. whole/root-of alphabet, cannot rebuild.
       virtual CDasherNode *RebuildParent();
@@ -175,7 +190,7 @@ namespace Dasher {
       virtual bool GameSearchNode(std::string strTargetUtf8Char);
       std::vector<unsigned int> *GetProbInfo();
       ///Override: if the group to create is the same as this node's group, return this node instead of creating a new one
-      virtual CDasherNode *RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, const SGroupInfo *pInfo);
+      virtual CDasherNode *RebuildGroup(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, int iBkgCol, const SGroupInfo *pInfo);
     protected:
       ///Override: true if pGroup encloses this one (by start/end symbol#)
       bool isInGroup(const SGroupInfo *pGroup);
@@ -207,11 +222,9 @@ namespace Dasher {
     std::pair<symbol, CLanguageModel::Context> GetContextSymbols(CDasherNode *pParent, int iRootOffset, const CAlphabetMap *pAlphMap);
 
     ///Called to create a node for a given symbol (leaf), as a child of a specified parent node
-    /// \param strGroup caption of any group containing this node, that will not be created:
-    /// thus, should be prepended onto the caption of the node created.
-    /// \param iBkgCol colour behind the new node, i.e. that should show through if the node is transparent
-    virtual CDasherNode *CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
-    virtual CGroupNode *CreateGroupNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strEnc, int iBkgCol, const SGroupInfo *pInfo);
+    /// \param iBkgCol colour behind the new node, i.e. that should show through if the (group) node is transparent
+    virtual CDasherNode *CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
+    virtual CGroupNode *CreateGroupNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, int iBkgCol, const SGroupInfo *pInfo);
 
     ///Called to compute colour for a symbol at a specified offset.
     /// Wraps CAlphabet::GetColour(sym), but (a) implements a default
diff --git a/Src/DasherCore/ControlManager.cpp b/Src/DasherCore/ControlManager.cpp
index 0df295e..74b8b25 100644
--- a/Src/DasherCore/ControlManager.cpp
+++ b/Src/DasherCore/ControlManager.cpp
@@ -61,12 +61,34 @@ CDasherNode *CControlBase::GetRoot(CDasherNode *pParent, unsigned int iLower, un
   return pNewNode;
 }
 
+void CControlBase::MakeLabels(CDasherScreen *pScreen) {
+  deque<NodeTemplate *> templateQueue(1,m_pRoot);
+  set<NodeTemplate *> allTemplates(templateQueue.begin(),templateQueue.end());
+  while (!templateQueue.empty()) {
+    NodeTemplate *head = templateQueue.front();
+    templateQueue.pop_front();
+    delete head->m_pLabel;
+    head->m_pLabel = pScreen->MakeLabel(head->m_strLabel);
+    for (vector<NodeTemplate *>::iterator it = head->successors.begin(); it!=head->successors.end(); it++) {
+      if (!(*it)) continue; //an escape back to the alphabet, no label/successors here
+      if (allTemplates.find(*it)==allTemplates.end()) {
+        allTemplates.insert(*it);
+        templateQueue.push_back(*it);
+      }
+    }
+  }
+}
+
 CControlBase::NodeTemplate::NodeTemplate(const string &strLabel,int iColour)
-: m_strLabel(strLabel), m_iColour(iColour) {
+: m_strLabel(strLabel), m_iColour(iColour), m_pLabel(NULL) {
+}
+
+CControlBase::NodeTemplate::~NodeTemplate() {
+  delete m_pLabel;
 }
 
 CControlBase::CContNode::CContNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, NodeTemplate *pTemplate, CControlBase *pMgr)
-: CDasherNode(pParent, iOffset, iLbnd, iHbnd, (pTemplate->colour() != -1) ? pTemplate->colour() : (pParent->ChildCount()%99)+11, pTemplate->label()), m_pTemplate(pTemplate), m_pMgr(pMgr) {
+: CDasherNode(pParent, iOffset, iLbnd, iHbnd, (pTemplate->m_iColour != -1) ? pTemplate->m_iColour : (pParent->ChildCount()%99)+11, pTemplate->m_pLabel), m_pTemplate(pTemplate), m_pMgr(pMgr) {
 }
 
 void CControlBase::CContNode::PopulateChildren() {
diff --git a/Src/DasherCore/ControlManager.h b/Src/DasherCore/ControlManager.h
index 828f279..42ffa27 100644
--- a/Src/DasherCore/ControlManager.h
+++ b/Src/DasherCore/ControlManager.h
@@ -87,13 +87,13 @@ namespace Dasher {
     class NodeTemplate : public Action {
     public:
       NodeTemplate(const std::string &strLabel, int iColour);
-      virtual ~NodeTemplate() {}
-      int colour() {return m_iColour;};
-      const std::string &label() {return m_strLabel;};
+      virtual ~NodeTemplate();
+      const std::string m_strLabel;
+      const int m_iColour;
       std::vector<NodeTemplate *> successors;
     private:
-      std::string m_strLabel;
-      int m_iColour;
+      friend class CControlBase;
+      CDasherScreen::Label *m_pLabel;
     };
 
     template <typename T> class MethodTemplate : public NodeTemplate {
@@ -115,6 +115,9 @@ namespace Dasher {
 
     CControlBase(CNodeCreationManager *pNCManager);
 
+    ///Make this manager ready to make nodes renderable on the screen by preallocating labels
+    virtual void MakeLabels(CDasherScreen *pScreen);
+    
     ///
     /// Get a new root node owned by this manager
     ///
diff --git a/Src/DasherCore/ConversionHelper.cpp b/Src/DasherCore/ConversionHelper.cpp
index 305ba3e..e453063 100644
--- a/Src/DasherCore/ConversionHelper.cpp
+++ b/Src/DasherCore/ConversionHelper.cpp
@@ -121,7 +121,7 @@ void CConversionHelper::CConvHNode::PopulateChildren() {
 
       //  std::cout << "#" << pCurrentSCEChild->pszConversion << "#" << std::endl;
 
-      CConvNode *pNewNode = mgr()->makeNode(this, offset()+1, iLbnd, iHbnd, mgr()->AssignColour(parentClr, pCurrentSCEChild, iIdx), string(pCurrentSCEChild->pszConversion));
+      CConvNode *pNewNode = mgr()->makeNode(this, offset()+1, iLbnd, iHbnd, mgr()->AssignColour(parentClr, pCurrentSCEChild, iIdx), mgr()->GetLabel(pCurrentSCEChild->pszConversion));
 
       // TODO: Reimplement ----
 
@@ -190,13 +190,13 @@ void CConversionHelper::BuildTree(CConvHNode *pRoot) {
   pRoot->pSCENode = pStartTemp;
 }
 
-CConversionHelper::CConvHNode::CConvHNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText, CConversionHelper *pMgr)
-: CConvNode(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText, pMgr) {
+CConversionHelper::CConvHNode::CConvHNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CConversionHelper *pMgr)
+: CConvNode(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel, pMgr) {
 }
 
 
-CConversionHelper::CConvHNode *CConversionHelper::makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText) {
-  return new CConvHNode(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText, this);
+CConversionHelper::CConvHNode *CConversionHelper::makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel) {
+  return new CConvHNode(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel, this);
 }
 
 void CConversionHelper::CConvHNode::SetFlag(int iFlag, bool bValue) {
diff --git a/Src/DasherCore/ConversionHelper.h b/Src/DasherCore/ConversionHelper.h
index ca5aa16..b3c39cd 100644
--- a/Src/DasherCore/ConversionHelper.h
+++ b/Src/DasherCore/ConversionHelper.h
@@ -26,6 +26,7 @@
 #include <vector>
 #include "SCENode.h"
 #include "LanguageModelling/LanguageModel.h"
+#include "DasherScreen.h"
 
 namespace Dasher{
 	class CDasherNode;  //forward declaration
@@ -42,7 +43,7 @@ namespace Dasher{
   class CConversionHelper : public CConversionManager {
   public:
 	CConversionHelper(CNodeCreationManager *pNCManager, const CAlphInfo *pAlphabet, CLanguageModel *pLanguageModel);
-
+    
 	/// Convert a given string to a lattice of candidates. Sizes for
 	/// candidates aren't assigned at this point. The input string
 	/// should be UTF-8 encoded.
@@ -111,7 +112,7 @@ namespace Dasher{
 	protected:
     class CConvHNode : public CConvNode {
     public:
-      CConvHNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText, CConversionHelper *pMgr);
+      CConvHNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CConversionHelper *pMgr);
       ///
       /// Provide children for the supplied node
       ///
@@ -121,7 +122,7 @@ namespace Dasher{
     protected:
       inline CConversionHelper *mgr() {return static_cast<CConversionHelper *>(m_pMgr);}
     };
-	  virtual CConvHNode *makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText);
+	  virtual CConvHNode *makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel);
     ///
     /// Build the conversion tree (lattice) for the given string -
     /// evaluated late to prevent unnecessary conversions when the
@@ -133,7 +134,7 @@ namespace Dasher{
     virtual Dasher::CLanguageModel *GetLanguageModel() {
       return m_pLanguageModel;
     }
-
+    
   private:
     CLanguageModel *m_pLanguageModel;
 
diff --git a/Src/DasherCore/ConversionManager.cpp b/Src/DasherCore/ConversionManager.cpp
index 65b0755..8703807 100644
--- a/Src/DasherCore/ConversionManager.cpp
+++ b/Src/DasherCore/ConversionManager.cpp
@@ -56,8 +56,23 @@ CConversionManager::CConversionManager(CNodeCreationManager *pNCManager, const C
   */
 }
 
-CConversionManager::CConvNode *CConversionManager::makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText) {
-  return new CConvNode(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText, this);
+CConversionManager::CConvNode *CConversionManager::makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel) {
+  return new CConvNode(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel, this);
+}
+
+void CConversionManager::ChangeScreen(CDasherScreen *pScreen) {
+  if (m_pScreen==pScreen) return;
+  for (map<string, CDasherScreen::Label *>::iterator it=m_vLabels.begin(); it!=m_vLabels.end(); it++)
+    delete it->second;
+  m_vLabels.clear();
+  m_pScreen=pScreen;
+}
+
+CDasherScreen::Label *CConversionManager::GetLabel(const char *pszConversion) {
+  string strConv(pszConversion);
+  if (m_vLabels[strConv])
+    return m_vLabels[strConv];
+  return m_vLabels[strConv] = m_pScreen->MakeLabel(strConv);
 }
 
 CConversionManager::CConvNode *CConversionManager::GetRoot(CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, int iOffset) {
@@ -65,7 +80,7 @@ CConversionManager::CConvNode *CConversionManager::GetRoot(CDasherNode *pParent,
   // TODO: Parameters here are placeholders - need to figure out what's right
 
   //TODO: hard-coded colour, and hard-coded displaytext... (ACL: read from Alphabet -> startConversionSymbol ?)
-  CConvNode *pNewNode = makeNode(pParent, iOffset, iLower, iUpper, 9, "");
+  CConvNode *pNewNode = makeNode(pParent, iOffset, iLower, iUpper, 9, NULL);
 
   // FIXME - handle context properly
   // TODO: Reimplemnt -----
@@ -82,8 +97,8 @@ CConversionManager::CConvNode *CConversionManager::GetRoot(CDasherNode *pParent,
   return pNewNode;
 }
 
-CConversionManager::CConvNode::CConvNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText, CConversionManager *pMgr)
- : CDasherNode(pParent, iOffset, iLbnd, iHbnd, iColour, strDisplayText), m_pMgr(pMgr) {
+CConversionManager::CConvNode::CConvNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CConversionManager *pMgr)
+ : CDasherNode(pParent, iOffset, iLbnd, iHbnd, iColour, pLabel), m_pMgr(pMgr) {
   pMgr->m_iRefCount++;
 }
 
diff --git a/Src/DasherCore/ConversionManager.h b/Src/DasherCore/ConversionManager.h
index 4bbd531..ab89a8f 100644
--- a/Src/DasherCore/ConversionManager.h
+++ b/Src/DasherCore/ConversionManager.h
@@ -63,6 +63,10 @@ namespace Dasher {
     // TODO: We shouldn't need to know about this stuff, but the code is somewhat in knots at the moment
     CConversionManager(CNodeCreationManager *pNCManager, const CAlphInfo *pAlphabet);
 
+    ///Tells us to use the specified screen to create node labels.
+    /// (note we cache the screen and create labels lazily)
+    void ChangeScreen(CDasherScreen *pScreen);
+    
     ///
     /// Decrement reference count
     ///
@@ -83,7 +87,7 @@ namespace Dasher {
     class CConvNode : public CDasherNode {
     public:
       CConversionManager *mgr() {return m_pMgr;}
-      CConvNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText, CConversionManager *pMgr);
+      CConvNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel, CConversionManager *pMgr);
     ///
     /// Provide children for the supplied node
     ///
@@ -131,14 +135,21 @@ namespace Dasher {
     virtual CConvNode *GetRoot(CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, int iOffset);
   protected:
 
-    virtual CConvNode *makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText);
+    virtual CConvNode *makeNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel);
 
 
 	CNodeCreationManager *m_pNCManager;
 	const CAlphInfo *m_pAlphabet;
 	
+    ///Utility method for subclasses, lazily makes+caches node labels
+    /// \return a node label displaying the specified string
+    CDasherScreen::Label *GetLabel(const char *pszConversion);
+    
   private:
 
+    std::map<std::string, CDasherScreen::Label *> m_vLabels;
+    CDasherScreen *m_pScreen;
+
     ///
     /// Dump tree to stdout (debug)
     ///
diff --git a/Src/DasherCore/ConvertingAlphMgr.cpp b/Src/DasherCore/ConvertingAlphMgr.cpp
index e2f28e3..487fada 100644
--- a/Src/DasherCore/ConvertingAlphMgr.cpp
+++ b/Src/DasherCore/ConvertingAlphMgr.cpp
@@ -16,11 +16,17 @@ CConvertingAlphMgr::CConvertingAlphMgr(CDasherInterfaceBase *pInterface, CNodeCr
  : CAlphabetManager(pInterface, pNCManager, pAlphabet, pAlphabetMap), m_pConvMgr(pConvMgr) {
  }
 
+void CConvertingAlphMgr::MakeLabels(CDasherScreen *pScreen) {
+  CAlphabetManager::MakeLabels(pScreen);
+  m_pConvMgr->ChangeScreen(pScreen);
+}
+
+
 CConvertingAlphMgr::~CConvertingAlphMgr() {
   m_pConvMgr->Unref();
 }
 
-CDasherNode *CConvertingAlphMgr::CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const string &strGroup, int iBkgCol, symbol iSymbol) {
+CDasherNode *CConvertingAlphMgr::CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol) {
   int i=m_pAlphabet->GetNumberTextSymbols()+1;
   if (iSymbol == i) {
     vector<unsigned int> *pCProb(pParent->GetProbInfo());
@@ -28,6 +34,6 @@ CDasherNode *CConvertingAlphMgr::CreateSymbolNode(CAlphNode *pParent, unsigned i
     //ACL setting m_iOffset+1 for consistency with "proper" symbol nodes...
     return m_pConvMgr->GetRoot(pParent, (*pCProb)[i-1], (*pCProb)[i], pParent->offset()+1);
   } else {
-    return CAlphabetManager::CreateSymbolNode(pParent, iLbnd, iHbnd, strGroup, iBkgCol, iSymbol);
+    return CAlphabetManager::CreateSymbolNode(pParent, iLbnd, iHbnd, iSymbol);
   }
 }
diff --git a/Src/DasherCore/ConvertingAlphMgr.h b/Src/DasherCore/ConvertingAlphMgr.h
index d6e4501..59dafc5 100644
--- a/Src/DasherCore/ConvertingAlphMgr.h
+++ b/Src/DasherCore/ConvertingAlphMgr.h
@@ -23,10 +23,12 @@ namespace Dasher {
   class CConvertingAlphMgr : public CAlphabetManager {
   public:
     CConvertingAlphMgr(CDasherInterfaceBase *pInterface, CNodeCreationManager *pNCManager, CConversionManager *pConvMgr, const CAlphInfo *pAlphabet, const CAlphabetMap *pAlphabetMap);
+    ///Override to also tell the ConversionManager that the screen has changed.
+    void MakeLabels(CDasherScreen *pScreen);
     virtual ~CConvertingAlphMgr();
   protected:
     ///Override to return a conversion root for iSymbol==(one beyond last alphabet symbol)
-    virtual CDasherNode *CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
+    virtual CDasherNode *CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
   private:
     CConversionManager *m_pConvMgr;
     
diff --git a/Src/DasherCore/DasherInterfaceBase.cpp b/Src/DasherCore/DasherInterfaceBase.cpp
index 89bf202..517f7c7 100644
--- a/Src/DasherCore/DasherInterfaceBase.cpp
+++ b/Src/DasherCore/DasherInterfaceBase.cpp
@@ -122,8 +122,13 @@ CDasherInterfaceBase::CDasherInterfaceBase() {
 }
 
 void CDasherInterfaceBase::Realize() {
+
   // TODO: What exactly needs to have happened by the time we call Realize()?
   CreateSettingsStore();
+  
+  //create a view, if we have a screen...
+  //if(GetLongParameter(LP_VIEW_ID) != -1)
+  ChangeView();
 
   //create the model... (no nodes just yet)
   m_pDasherModel = new CDasherModel(m_pEventHandler, m_pSettingsStore, this);
@@ -370,10 +375,13 @@ void CDasherInterfaceBase::CreateNCManager() {
   //now create the new manager...
   m_pNCManager = new CNodeCreationManager(this, m_pEventHandler, m_pSettingsStore, m_AlphIO);
 
-  //and start a new tree of nodes from it (retaining old offset -
-  // this will be a sensible default of 0 if no nodes previously existed).
-  // This deletes the old tree of nodes...
-  m_pDasherModel->SetOffset(m_pDasherModel->GetOffset(), m_pNCManager->GetAlphabetManager(), m_pDasherView, true);
+  if (m_DasherScreen) {
+    m_pNCManager->ChangeScreen(m_DasherScreen);
+    //and start a new tree of nodes from it (retaining old offset -
+    // this will be a sensible default of 0 if no nodes previously existed).
+    // This deletes the old tree of nodes...
+    m_pDasherModel->SetOffset(m_pDasherModel->GetOffset(), m_pNCManager->GetAlphabetManager(), m_pDasherView, true);
+  } //else, if there is no screen, the model should not contain any nodes from the old NCManager. (Assert, somehow?)
 
   //...so now we can delete the old manager
   delete pOldMgr;
@@ -610,15 +618,30 @@ void CDasherInterfaceBase::ChangeColours() {
 }
 
 void CDasherInterfaceBase::ChangeScreen(CDasherScreen *NewScreen) {
-  // What does ChangeScreen do?
+  
   m_DasherScreen = NewScreen;
   ChangeColours();
-
+  
   if(m_pDasherView != 0) {
-    m_pDasherView->ChangeScreen(m_DasherScreen);
-  } else if(GetLongParameter(LP_VIEW_ID) != -1) {
+    m_pDasherView->ChangeScreen(NewScreen);
+    ScreenResized(NewScreen);
+  } else if (m_pEventHandler && m_pSettingsStore) {
+    //no screen, but other essential components (created in Realize) present.
+    // IOW, (assume) we were delaying creating a View, until we had a screen...
     ChangeView();
   }
+  
+  if (m_pNCManager) {
+    m_pNCManager->ChangeScreen(m_DasherScreen);
+    if (m_pDasherModel)
+      m_pDasherModel->SetOffset(m_pDasherModel->GetOffset(), m_pNCManager->GetAlphabetManager(), m_pDasherView, true);
+  }
+}
+
+void CDasherInterfaceBase::ScreenResized(CDasherScreen *pScreen) {
+  DASHER_ASSERT(pScreen == m_DasherScreen);
+  if (!m_pDasherView) return;
+  m_pDasherView->ScreenResized(m_DasherScreen);
 
   PositionActionButtons();
   BudgettingPolicy pol(GetLongParameter(LP_NODE_BUDGET)); //maintain budget, but allow arbitrary amount of work.
@@ -633,14 +656,14 @@ void CDasherInterfaceBase::ChangeView() {
   // TODO: Actually respond to LP_VIEW_ID parameter (although there is only one view at the moment)
 
   // removed condition that m_pDasherModel != 0. Surely the view can exist without the model?-pconlon
-    if(m_DasherScreen != 0 /*&& m_pDasherModel != 0*/) {
+  if(m_DasherScreen != 0 /*&& m_pDasherModel != 0*/) {
     delete m_pDasherView;
 
     m_pDasherView = new CDasherViewSquare(m_pEventHandler, m_pSettingsStore, m_DasherScreen);
 
     // Tell the Teacher which view we are using
     if(GameMode::CDasherGameMode* pTeacher = GameMode::CDasherGameMode::GetTeacher())
-	pTeacher->SetDasherView(m_pDasherView);
+      pTeacher->SetDasherView(m_pDasherView);
   }
 }
 
diff --git a/Src/DasherCore/DasherInterfaceBase.h b/Src/DasherCore/DasherInterfaceBase.h
index 893ef2d..a2485ba 100644
--- a/Src/DasherCore/DasherInterfaceBase.h
+++ b/Src/DasherCore/DasherInterfaceBase.h
@@ -287,10 +287,23 @@ public:
   // std::vector<std::string>& GetLangModels();
   // std::vector<std::string>& GetViews();
 
-  /// Supply a new CDasherScreen object to do the rendering.
+  /// Supply a new CDasherScreen object onto which to render. Note this should
+  /// only be called (a) at startup, and (b) when the new screen is _significantly_
+  /// different from the old, rather than just a window resize: specifically, this means
+  /// the tree of nodes will be rebuilt with new Labels for the new screen; and in the future,
+  /// maybe also if things like colour depth, alpha transparency support, etc., change.
+  /// If the existing rendering setup should just scale to the new screen dimensions,
+  /// call ScreenResized() instead (we expect this to be the case most/all of the time,
+  /// and this method subsumes a call to ScreenResized.) Note, at startup, ChangeScreen
+  /// and Realize may occur in either order; if ChangeScreen comes after, Resize will create a 
+  /// tree with null Labels, which will have to be rebuilt in the call to ChangeScreen.
   /// \param NewScreen Pointer to the new CDasherScreen.
-
-  void ChangeScreen(CDasherScreen * NewScreen); // We may change the widgets Dasher uses
+  void ChangeScreen(CDasherScreen * NewScreen);
+  
+  ///Call when the screen dimensions have been changed, to recalculate scaling factors etc.
+  /// \param pScreen the screen whose dimensions have changed. TODO we expect this to be
+  /// the same one-and-only screen that we are using anyway, so remove parameter?
+  void ScreenResized(CDasherScreen *pScreen);
 
   /// Train Dasher from a file
   /// All traing data must be in UTF-8
diff --git a/Src/DasherCore/DasherNode.cpp b/Src/DasherCore/DasherNode.cpp
index ef247fe..fdae932 100644
--- a/Src/DasherCore/DasherNode.cpp
+++ b/Src/DasherCore/DasherNode.cpp
@@ -42,8 +42,8 @@ static int iNumNodes = 0;
 int Dasher::currentNumNodeObjects() {return iNumNodes;}
 
 //TODO this used to be inline - should we make it so again?
-CDasherNode::CDasherNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const string &strDisplayText)
-: m_pParent(pParent), m_iOffset(iOffset), m_iLbnd(iLbnd), m_iHbnd(iHbnd), m_iColour(iColour), m_strDisplayText(strDisplayText) {
+CDasherNode::CDasherNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel)
+: m_pParent(pParent), m_iLbnd(iLbnd), m_iHbnd(iHbnd), m_iOffset(iOffset), m_iColour(iColour), m_pLabel(pLabel) {
   DASHER_ASSERT(iHbnd >= iLbnd);
 
   if (pParent) {
diff --git a/Src/DasherCore/DasherNode.h b/Src/DasherCore/DasherNode.h
index f503208..b8bf2eb 100644
--- a/Src/DasherCore/DasherNode.h
+++ b/Src/DasherCore/DasherNode.h
@@ -27,6 +27,7 @@
 #include "DasherTypes.h"
 #include "NodeManager.h"
 #include "Alphabet/AlphabetMap.h"
+#include "DasherScreen.h"
 
 namespace Dasher {
   class CDasherNode;
@@ -66,7 +67,7 @@ class Dasher::CDasherNode:private NoClones {
   /// Colour; note invisible nodes just have the same colour as their parent.
   /// (so we know what colour to use when their parents are deleted)
   inline int getColour() {return m_iColour;}
-  inline const std::string &getDisplayText() {return m_strDisplayText;}
+  inline CDasherScreen::Label *getLabel() {return m_pLabel;}
   ///Whether labels on child nodes should be displaced to the right of this node's label.
   /// (Default implementation returns true, subclasses should override if appropriate)
   virtual bool bShove() {return true;}
@@ -80,12 +81,13 @@ class Dasher::CDasherNode:private NoClones {
 
   /// @brief Constructor
   ///
-  /// @param pParent Parent of the new node
-  /// @param iLbnd Lower bound of node within parent
-  /// @param iHbnd Upper bound of node within parent
-  /// @param pDisplayInfo Struct containing information on how to display the node
-  ///
-  CDasherNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, const std::string &strDisplayText);
+  /// \param pParent Parent of the new node; automatically adds to end of parent's child list
+  /// \param iOffset Index into text buffer of character to LHS of cursor _after_ this node is Output().
+  /// \param iLbnd Lower bound of node within parent
+  /// \param iHbnd Upper bound of node within parent
+  /// \param iColour background colour of node (for transparent nodes, same colour as parent)
+  /// \param pLabel label to render onto node, NULL if no label required.
+  CDasherNode(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, int iColour, CDasherScreen::Label *pLabel);
 
   /// @brief Destructor
   ///
@@ -281,7 +283,7 @@ class Dasher::CDasherNode:private NoClones {
 
  protected:
   const int m_iColour;
-  const std::string m_strDisplayText;
+  CDasherScreen::Label * m_pLabel;
 };
 /// @}
 
diff --git a/Src/DasherCore/DasherScreen.h b/Src/DasherCore/DasherScreen.h
index 95f4140..ee83a95 100644
--- a/Src/DasherCore/DasherScreen.h
+++ b/Src/DasherCore/DasherScreen.h
@@ -7,6 +7,7 @@
 
 #include "DasherTypes.h"
 #include "../DasherCore/ColourIO.h"
+#include <set>
 
 // DJW20050505 - renamed DrawText to DrawString - windows defines DrawText as a macro and it's 
 // really hard to work around
@@ -14,11 +15,21 @@
 
 namespace Dasher {
   class CDasherScreen;
+  class CLabelListScreen;
   class CDasherInterfaceBase;
 }
 
 /// \ingroup View
 /// @{
+/// Abstract interface for drawing operations, implemented by platform-specific canvases.
+/// Instances have _mutable_ dimensions; changes should be reported to the
+/// interface's ScreenResized method.
+/// Note the DrawString and TextSize methods: these now take platform-specific
+/// Label objects returned from MakeLabel. Thus, it is up to external clients to
+/// cache and reuse Labels. (This replaces the previous scheme where these methods
+/// took arbitrary std::strings, which were cached in a hashmap internal to each
+/// platform's screen. The new scheme allows clients to control cache preloading
+/// and flushing.)
 class Dasher::CDasherScreen
 {
 public:
@@ -49,14 +60,40 @@ public:
     screenint x;
     screenint y;
   } point;
+  
+  /// (Default implementation returns false)
+  ///\return true if this Screen can efficiently support fonts of many sizes (by continuous scaling);
+  /// false if clients should try to minimise the number of distinct font sizes required.
+  virtual bool MultiSizeFonts() {return false;}
+  
+  ///Abstract class for objects representing strings that can be drawn on the screen.
+  /// Platform-specific instances are created by the MakeLabel(String) method, which
+  /// may then be passed to GetSize() and DrawText().
+  class Label {
+    friend class CDasherScreen;
+  protected:
+    Label(const std::string &strText) : m_strText(strText) {};
+  public:
+    const std::string m_strText;
+    ///Delete the label. This should free up any resources associated with
+    /// drawing the string onto the screen, e.g. layouts or textures.
+    virtual ~Label() {}
+  };
+
+  ///Make a label for use with this screen.
+  /// \param strText UTF8-encoded text.
+  virtual Label *MakeLabel(const std::string &strText) {return new Label(strText);}
 
-  // DasherView asks for the width and height of the given UTF8 string at a requested height,
-  // then it is able to sensibly specify the upper left corner in DrawString.
-  //! Set Width and Height to those of the string at size Size
-  virtual void TextSize(const std::string & String, screenint * Width, screenint * Height, int Size) = 0;
+  ///Get Width and Height of a Label previously created by MakeLabel. Note behaviour
+  /// undefined if the Label is not one returned from a call to MakeLabel _on_this_Screen_.
+  virtual std::pair<screenint,screenint> TextSize(Label *label, unsigned int iFontSize) = 0;
 
-  //! Draw UTF8-encoded string String of size Size positioned at x1 and y1
-  virtual void DrawString(const std::string & String, screenint x1, screenint y1, int Size, int iColour) = 0;
+  /// Draw a label at position (x1,y1)
+  /// \param label a Label previously created by MakeLabel. Note behaviour
+  /// undefined if the Label is not one returned from a call to MakeLabel _on_this_Screen_.
+  /// \param x Coordinate of top left corner (i.e., left hand side)
+  /// \param y Coordinate of top left corner (i.e., top)
+  virtual void DrawString(Label *label, screenint x, screenint y, unsigned int iFontSize, int iColour) = 0;
 
   // Send a marker to indicate 'phases' of drawing. 
 
@@ -112,10 +149,6 @@ public:
   /// \param lineWidth thickness of outline; 0 or less => don't draw outline.
   virtual void Polygon(point * Points, int Number, int fillColour, int outlineColour, int lineWidth) = 0;
 
-  // Signal the screen when a frame is started and finished
-  //! Signal that a frame is being started
-  virtual void Blank() = 0;
-
   //! Signal that a frame is finished - the screen should be updated
   virtual void Display() = 0;
 
@@ -124,12 +157,52 @@ public:
   /// \param pColourScheme A colour scheme that should be used
   virtual void SetColourScheme(const Dasher::CColourIO::ColourInfo *pColourScheme) = 0;
   
-protected:
+private:
   //! Width and height of the screen
-  const screenint m_iWidth, m_iHeight;
+  screenint m_iWidth, m_iHeight;
 
-  //! Pointer to a widget interface for communication with the core
-  //  CDasherInterfaceBase *m_pDasherInterface;
+protected:
+  ///Subclasses should call this if the canvas dimensions have changed.
+  /// It is up to subclasses to make sure they also call
+  /// ScreenResized on the intf.
+  void resize(screenint width, screenint height) {
+    m_iWidth = width; m_iHeight = height;
+  }
+};
+
+/// Subclass that preserves a list of all labels returned from MakeLabel
+/// (and not yet deleted) so that they can be mutated en mass (by further
+/// subclasses) if necessary. Note we have to return a new Labels each time,
+/// and cannot hash/flyweight together similar Labels, because _clients_ are
+/// in control of deletion.
+class Dasher::CLabelListScreen : public Dasher::CDasherScreen {
+protected:
+  CLabelListScreen(screenint width, screenint height) : CDasherScreen(width,height) {
+  }
+  class Label : public CDasherScreen::Label {
+  public: //to instances of CLabelListScreen and subclasses
+    Label(CLabelListScreen *pScreen, const std::string &strText)
+    : CDasherScreen::Label(strText), m_pScreen(pScreen) {
+      m_pScreen->m_sLabels.insert(this);
+    }
+    ~Label() {
+      std::set<Label *>::iterator it = m_pScreen->m_sLabels.find(this);
+      DASHER_ASSERT(it != m_pScreen->m_sLabels.end());
+      m_pScreen->m_sLabels.erase(it);
+    }
+    CLabelListScreen * const m_pScreen;
+  };
+  ///An iterator pointing to the first extant (non-deleted) label created
+  /// from this screen. This allows iteration through modifiable labels,
+  /// but without being able to access or hence modify the set.
+  std::set<Label *>::iterator LabelsBegin() {return m_sLabels.begin();}
+
+  ///An iterator pointing just beyond the last extant (non-deleted) label
+  /// created from this screen. This allows iteration through modifiable labels,
+  /// but without being able to access or hence modify the set.
+  std::set<Label *>::iterator LabelsEnd() {return m_sLabels.end();}
+private:
+  std::set<Label *> m_sLabels;
 };
 /// @}
 
diff --git a/Src/DasherCore/DasherView.h b/Src/DasherCore/DasherView.h
index f2c01e4..ebc6b10 100644
--- a/Src/DasherCore/DasherView.h
+++ b/Src/DasherCore/DasherView.h
@@ -84,10 +84,18 @@ public:
 
   /// @}
 
-  /// Change the screen - must be called if the Screen is replaced or resized
+  /// Change the screen - must be called if the Screen is replaced (not resized).
+  /// Default implementation just stores pointer. Note that a call to ChangeScreen
+  /// is usually followed by a call to ScreenResized as well, so stuff that only
+  /// depends on screen size/resolution can be done there instead.
   /// \param NewScreen Pointer to the new CDasherScreen.
-
   virtual void ChangeScreen(CDasherScreen * NewScreen);
+  
+  ///Call when the screen dimensions have been changed, to recalculate scaling factors etc.
+  /// The default implementation does nothing.
+  /// \param pScreen the screen whose dimensions have changed. TODO we expect this to be
+  /// the same one-and-only screen that we are using anyway, so remove parameter?
+  virtual void ScreenResized(CDasherScreen *pScreen) {}
 
   /// @name High level drawing
   /// Drawing more complex structures, generally implemented by derived class
diff --git a/Src/DasherCore/DasherViewSquare.cpp b/Src/DasherCore/DasherViewSquare.cpp
index 8330ef7..1088e1b 100644
--- a/Src/DasherCore/DasherViewSquare.cpp
+++ b/Src/DasherCore/DasherViewSquare.cpp
@@ -62,7 +62,7 @@ CDasherViewSquare::CDasherViewSquare(CEventHandler *pEventHandler, CSettingsStor
 : CDasherView(pEventHandler, pSettingsStore, DasherScreen),   m_Y1(4), m_Y2(0.95 * GetLongParameter(LP_MAX_Y)), m_Y3(0.05 * GetLongParameter((LP_MAX_Y))), m_bVisibleRegionValid(false) {
 
   //Note, nonlinearity parameters set in SetScaleFactor
-  ChangeScreen(DasherScreen);
+  ScreenResized(DasherScreen);
 }
 
 CDasherViewSquare::~CDasherViewSquare() {}
@@ -148,7 +148,7 @@ CDasherNode *CDasherViewSquare::Render(CDasherNode *pRoot, myint iRootMin, myint
 /// specified as two co-ordinates, intended to the be the corners of
 /// the leading edge of the containing box.
 
-CDasherViewSquare::CTextString *CDasherViewSquare::DasherDrawText(myint iDasherMaxX, myint iDasherMidY, const std::string &sDisplayText, CTextString *pParent, int iColor) {
+CDasherViewSquare::CTextString *CDasherViewSquare::DasherDrawText(myint iDasherMaxX, myint iDasherMidY, CDasherScreen::Label *pLabel, CTextString *pParent, int iColor) {
 
   screenint x,y;
   Dasher2Screen(iDasherMaxX, iDasherMidY, x, y);
@@ -157,7 +157,7 @@ CDasherViewSquare::CTextString *CDasherViewSquare::DasherDrawText(myint iDasherM
   int iSize = GetLongParameter(LP_DASHER_FONTSIZE);
   {
     const myint iMaxY(GetLongParameter(LP_MAX_Y));
-    if (GetBoolParameter(BP_MULTISIZE_FONTS)) {
+    if (Screen()->MultiSizeFonts() && iSize>4) {
       //font size maxes out at ((iMaxY*3)/2)+iMaxY)/iMaxY = 3/2*smallest
       // which is reached when iDasherMaxX == iMaxY/2, i.e. the crosshair
       iSize = ((min(iDasherMaxX*3,(iMaxY*3)/2) + iMaxY) * iSize) / iMaxY;
@@ -173,7 +173,7 @@ CDasherViewSquare::CTextString *CDasherViewSquare::DasherDrawText(myint iDasherM
     }
   }
 
-  CTextString *pRet = new CTextString(sDisplayText, x, y, iSize, iColor);
+  CTextString *pRet = new CTextString(pLabel, x, y, iSize, iColor);
   vector<CTextString *> &dest(pParent ? pParent->m_children : m_DelayedTexts);
   dest.push_back(pRet);
   return pRet;
@@ -187,13 +187,13 @@ void CDasherViewSquare::DoDelayedText(CTextString *pText) {
   // across according to the aiMax array, but this needs Dasher co-ordinates, which were
   // more easily available at CTextString creation time. If it really doesn't look as good,
   // can put in extra calls to Screen2Dasher....
-  screenint x(pText->m_ix), y(pText->m_iy), textWidth, textHeight;
-  Screen()->TextSize(pText->m_String, &textWidth, &textHeight, pText->m_iSize);
+  screenint x(pText->m_ix), y(pText->m_iy);
+  pair<screenint,screenint> textDims=Screen()->TextSize(pText->m_pLabel, pText->m_iSize);
   switch (orient) {
     case Dasher::Opts::LeftToRight: {
-      screenint iRight = x + textWidth;
+      screenint iRight = x + textDims.first;
       if (iRight < Screen()->GetWidth()) {
-        Screen()->DrawString(pText->m_String, x, y-textHeight/2, pText->m_iSize, pText->m_iColor);
+        Screen()->DrawString(pText->m_pLabel, x, y-textDims.second/2, pText->m_iSize, pText->m_iColor);
         for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
           CTextString *pChild=*it;
           pChild->m_ix = max(pChild->m_ix, iRight);
@@ -204,9 +204,9 @@ void CDasherViewSquare::DoDelayedText(CTextString *pText) {
       break;
     }
     case Dasher::Opts::RightToLeft: {
-      screenint iLeft = x-textWidth;
+      screenint iLeft = x-textDims.first;
       if (iLeft>=0) {
-        Screen()->DrawString(pText->m_String, x - textWidth, y-textHeight/2, pText->m_iSize, pText->m_iColor);
+        Screen()->DrawString(pText->m_pLabel, iLeft, y-textDims.second/2, pText->m_iSize, pText->m_iColor);
         for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
           CTextString *pChild=*it;
           pChild->m_ix = min(pChild->m_ix, iLeft);
@@ -217,9 +217,9 @@ void CDasherViewSquare::DoDelayedText(CTextString *pText) {
       break;
     }
     case Dasher::Opts::TopToBottom: {
-      screenint iBottom = y + textHeight;
+      screenint iBottom = y + textDims.second;
       if (iBottom < Screen()->GetHeight()) {
-        Screen()->DrawString(pText->m_String, x-textWidth/2, y, pText->m_iSize, pText->m_iColor);
+        Screen()->DrawString(pText->m_pLabel, x-textDims.first/2, y, pText->m_iSize, pText->m_iColor);
         for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
           CTextString *pChild=*it;
           pChild->m_iy = max(pChild->m_iy, iBottom);
@@ -230,9 +230,9 @@ void CDasherViewSquare::DoDelayedText(CTextString *pText) {
       break;
     }
     case Dasher::Opts::BottomToTop: {
-      screenint iTop = y - textHeight;
+      screenint iTop = y - textDims.second;
       if (y>=0) {
-        Screen()->DrawString(pText->m_String, x-textWidth/2, y-textHeight, pText->m_iSize, pText->m_iColor);
+        Screen()->DrawString(pText->m_pLabel, x-textDims.first/2, iTop, pText->m_iSize, pText->m_iColor);
         for (vector<CTextString *>::iterator it = pText->m_children.begin(); it!=pText->m_children.end(); it++) {
           CTextString *pChild=*it;
           pChild->m_iy = min(pChild->m_iy, iTop);
@@ -483,13 +483,12 @@ void CDasherViewSquare::DisjointRender(CDasherNode *pRender, myint y1, myint y2,
 
   const int myColor = pRender->getColour();
 
-  const std::string &sDisplayText(pRender->getDisplayText());
-  if( sDisplayText.size() > 0 )
+  if( pRender->getLabel() )
   {
     const int textColor = GetLongParameter(LP_OUTLINE_WIDTH)<0 ? myColor : 4;
     myint ny1 = std::min(iDasherMaxY, std::max(iDasherMinY, y1)),
           ny2 = std::min(iDasherMaxY, std::max(iDasherMinY, y2));
-    CTextString *pText = DasherDrawText(y2-y1, (ny1+ny2)/2, sDisplayText, pPrevText, textColor);
+    CTextString *pText = DasherDrawText(y2-y1, (ny1+ny2)/2, pRender->getLabel(), pPrevText, textColor);
     if (pRender->bShove()) pPrevText = pText;
   }
 
@@ -657,13 +656,12 @@ beginning:
 
   const int myColor = pRender->getColour();
 
-  const std::string &sDisplayText(pRender->getDisplayText());
-  if( sDisplayText.size() > 0 )
+  if( pRender->getLabel() )
   {
     const int textColor = GetLongParameter(LP_OUTLINE_WIDTH)<0 ? myColor : 4;
     myint ny1 = std::min(iDasherMaxY, std::max(iDasherMinY, y1)),
     ny2 = std::min(iDasherMaxY, std::max(iDasherMinY, y2));
-    CTextString *pText = DasherDrawText(y2-y1, (ny1+ny2)/2, sDisplayText, pPrevText, textColor);
+    CTextString *pText = DasherDrawText(y2-y1, (ny1+ny2)/2, pRender->getLabel(), pPrevText, textColor);
     if (pRender->bShove()) pPrevText = pText;
   }
 
@@ -1129,8 +1127,7 @@ void CDasherViewSquare::VisibleRegion( myint &iDasherMinX, myint &iDasherMinY, m
 //   Screen()->Polyline(p, 4, iWidth, iColour);
 // }
 
-void CDasherViewSquare::ChangeScreen(CDasherScreen *NewScreen) {
-  CDasherView::ChangeScreen(NewScreen);
+void CDasherViewSquare::ScreenResized(CDasherScreen *NewScreen) {
   m_bVisibleRegionValid = false;
   m_iScalingFactor = 100000000;
   SetScaleFactor();
diff --git a/Src/DasherCore/DasherViewSquare.h b/Src/DasherCore/DasherViewSquare.h
index 30216a8..db8f693 100644
--- a/Src/DasherCore/DasherViewSquare.h
+++ b/Src/DasherCore/DasherViewSquare.h
@@ -56,11 +56,8 @@ public:
 
   virtual void HandleEvent(Dasher::CEvent * pEvent);
 
-  ///
-  /// Supply a new screen to draw to
-  ///
-
-  void ChangeScreen(CDasherScreen * NewScreen);
+  /// Resets scale factors etc. that depend on the screen size, to be recomputed when next needed.
+  void ScreenResized(CDasherScreen * NewScreen);
 
   ///
   /// @name Coordinate system conversion
@@ -116,14 +113,14 @@ private:
 
   class CTextString {
   public: //to CDasherViewSquare...
-    ///Creates a request that string str will be drawn.
+    ///Creates a request that label will be drawn.
     /// x,y are screen coords of midpoint of leading edge;
     /// iSize is desired size (already computed from requested position)
-    CTextString(const std::string & str, screenint x, screenint y, int iSize, int iColor)
-    : m_String(str), m_ix(x), m_iy(y), m_iSize(iSize), m_iColor(iColor) {
+    CTextString(CDasherScreen::Label *pLabel, screenint x, screenint y, int iSize, int iColor)
+    : m_pLabel(pLabel), m_ix(x), m_iy(y), m_iSize(iSize), m_iColor(iColor) {
     }
     ~CTextString();
-    std::string m_String;
+    CDasherScreen::Label *m_pLabel;
     screenint m_ix,m_iy;
     vector<CTextString *> m_children;
     int m_iSize;
@@ -137,7 +134,7 @@ private:
   /// Draw text specified in Dasher co-ordinates
   ///
 
-  CTextString *DasherDrawText(myint iMaxX, myint iMidY, const std::string & sDisplayText, CTextString *pParent, int iColor);
+  CTextString *DasherDrawText(myint iMaxX, myint iMidY, CDasherScreen::Label *pLabel, CTextString *pParent, int iColor);
 
   ///
   /// (Recursively) render a node and all contained subnodes, in disjoint rects.
diff --git a/Src/DasherCore/MandarinAlphMgr.cpp b/Src/DasherCore/MandarinAlphMgr.cpp
index 0052da7..0539d8b 100644
--- a/Src/DasherCore/MandarinAlphMgr.cpp
+++ b/Src/DasherCore/MandarinAlphMgr.cpp
@@ -63,10 +63,10 @@ CMandarinAlphMgr::CMandarinAlphMgr(CDasherInterfaceBase *pInterface, CNodeCreati
   if (symbol para = pCHAlphabet->GetParagraphSymbol())
     conversions[pCHAlphabet->GetDisplayText(para)]=pair<symbol,symbol>(para,para+1);
   //Non-recursive traversal of all the groups in the CHAlphabet (we don't care where they are, just to find them)
-  vector<const SGroupInfo *> groups;
+  vector<const ::SGroupInfo *> groups;
   groups.push_back(pCHAlphabet->m_pBaseGroup);
   while (!groups.empty()) {
-    const SGroupInfo *pGroup(groups.back()); groups.pop_back();
+    const ::SGroupInfo *pGroup(groups.back()); groups.pop_back();
     if (pGroup->pNext) groups.push_back(pGroup->pNext);
     if (pGroup->pChild) groups.push_back(pGroup->pChild);
     //process this group. The SPY syll+tone is stored as the label, using a tone mark over the vowel, e.g. &#257; = a1
@@ -112,6 +112,16 @@ CMandarinAlphMgr::CMandarinAlphMgr(CDasherInterfaceBase *pInterface, CNodeCreati
   //that leaves m_pConversionsBySymbol as desired.
 }
 
+void CMandarinAlphMgr::MakeLabels(CDasherScreen *pScreen) {
+  CAlphabetManager::MakeLabels(pScreen);
+  //a bit of a waste, that fills m_vLabels with labels for all the pinyin symbols - which we don't use.
+  for (vector<CDasherScreen::Label *>::iterator it=m_vLabels.begin(); it!=m_vLabels.end(); it++)
+    delete *it;
+  m_vLabels.clear();
+  //instead, keep the screen to create labels lazily...
+  m_pScreen = pScreen;
+}
+
 CMandarinAlphMgr::~CMandarinAlphMgr() {
   delete[] m_pConversionsBySymbol;
 }
@@ -137,10 +147,10 @@ CAlphabetManager::CAlphNode *CMandarinAlphMgr::GetRoot(CDasherNode *pParent, uns
 
   CAlphNode *pNewNode;
   if (p.first==0 || !bEnteredLast) {
-    pNewNode = new CGroupNode(pParent, iNewOffset, iLower, iUpper, "", 0, this, NULL);
+    pNewNode = new CGroupNode(pParent, iNewOffset, iLower, iUpper, NULL, 0, this, NULL);
   } else {
     DASHER_ASSERT(p.first>0 && p.first<m_CHtext.size());
-    pNewNode = new CMandSym(pParent, iNewOffset, iLower, iUpper,  "", this, p.first, 0);
+    pNewNode = new CMandSym(pParent, iNewOffset, iLower, iUpper, this, p.first, 0);
   }
   pNewNode->iContext = p.second;
   
@@ -165,7 +175,7 @@ int CMandarinAlphMgr::GetCHColour(symbol CHsym, int iOffset) const {
   return iColour;
 }
 
-CDasherNode *CMandarinAlphMgr::CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol) {
+CDasherNode *CMandarinAlphMgr::CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol) {
   
   //For every PY symbol (=syllable+tone, or "punctuation"),
   // m_pConversionsBySymbol identifies the possible chinese-alphabet symbols
@@ -173,24 +183,24 @@ CDasherNode *CMandarinAlphMgr::CreateSymbolNode(CAlphNode *pParent, unsigned int
   // punctuation character in the chinese alphabet). A CConvRoot thus offers a choice between them...
   
   if (m_pConversionsBySymbol[iSymbol].size()>1)
-    return CreateConvRoot(pParent, iLbnd, iHbnd, strGroup, iSymbol);
+    return CreateConvRoot(pParent, iLbnd, iHbnd, iSymbol);
   
-  return CreateCHSymbol(pParent,pParent->iContext, iLbnd, iHbnd, strGroup, *(m_pConversionsBySymbol[iSymbol].begin()), iSymbol);
+  return CreateCHSymbol(pParent,pParent->iContext, iLbnd, iHbnd, *(m_pConversionsBySymbol[iSymbol].begin()), iSymbol);
 }
 
-CMandarinAlphMgr::CConvRoot *CMandarinAlphMgr::CreateConvRoot(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, symbol iPYsym) {
+CMandarinAlphMgr::CConvRoot *CMandarinAlphMgr::CreateConvRoot(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iPYsym) {
   
   // the same offset as we've still not entered/selected a symbol (leaf);
   // Colour is always 9 so ignore iBkgCol
-  CConvRoot *pConv = new CConvRoot(pParent, pParent->offset(), iLbnd, iHbnd, strGroup, this, iPYsym);
+  CConvRoot *pConv = new CConvRoot(pParent, pParent->offset(), iLbnd, iHbnd, this, iPYsym);
     
   // and use the same context too (pinyin syll+tone is _not_ used as part of the LM context)
   pConv->iContext = m_pLanguageModel->CloneContext(pParent->iContext);
   return pConv;
 }
 
-CMandarinAlphMgr::CConvRoot::CConvRoot(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, CMandarinAlphMgr *pMgr, symbol pySym)
-: CAlphBase(pParent, iOffset, iLbnd, iHbnd, 9, strGroup, pMgr), m_pySym(pySym) {
+CMandarinAlphMgr::CConvRoot::CConvRoot(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CMandarinAlphMgr *pMgr, symbol pySym)
+: CAlphBase(pParent, iOffset, iLbnd, iHbnd, 9, NULL, pMgr), m_pySym(pySym) {
   DASHER_ASSERT(pMgr->m_pConversionsBySymbol[pySym].size()>1);
   //colour + label from ConversionManager.
 }
@@ -218,31 +228,31 @@ void CMandarinAlphMgr::CConvRoot::PopulateChildrenWithExisting(CMandSym *existin
     iCum = iHbnd;
     CMandSym *pNewNode = (existing)
       ? existing->RebuildCHSymbol(this, iLbnd, iHbnd, it->first)
-      : mgr()->CreateCHSymbol(this, this->iContext, iLbnd, iHbnd, "", it->first, m_pySym);
+      : mgr()->CreateCHSymbol(this, this->iContext, iLbnd, iHbnd, it->first, m_pySym);
     
     DASHER_ASSERT(GetChildren().back()==pNewNode);
   }
 }
 
-CMandarinAlphMgr::CMandSym *CMandarinAlphMgr::CreateCHSymbol(CDasherNode *pParent, CLanguageModel::Context iContext, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, symbol iCHsym, symbol iPYparent) {
+CMandarinAlphMgr::CMandSym *CMandarinAlphMgr::CreateCHSymbol(CDasherNode *pParent, CLanguageModel::Context iContext, unsigned int iLbnd, unsigned int iHbnd, symbol iCHsym, symbol iPYparent) {
   // TODO: Parameters here are placeholders - need to figure out
   // what's right 
 
   int iNewOffset = pParent->offset()+1;
   if (m_CHtext[iCHsym] == "\r\n") iNewOffset++;
-  CMandSym *pNewNode = new CMandSym(pParent, iNewOffset, iLbnd, iHbnd, strGroup, this, iCHsym, iPYparent);
+  CMandSym *pNewNode = new CMandSym(pParent, iNewOffset, iLbnd, iHbnd, this, iCHsym, iPYparent);
   pNewNode->iContext = m_pLanguageModel->CloneContext(iContext);
   m_pLanguageModel->EnterSymbol(pNewNode->iContext, iCHsym);
   return pNewNode;
 }
 
-CDasherNode *CMandarinAlphMgr::CConvRoot::RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSym) {
+CDasherNode *CMandarinAlphMgr::CConvRoot::RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSym) {
   if (iSym == m_pySym) {
     SetParent(pParent);
     SetRange(iLbnd,iHbnd);
     return this;
   }
-  return CAlphBase::RebuildSymbol(pParent, iLbnd, iHbnd, strGroup, iBkgCol, iSym);
+  return CAlphBase::RebuildSymbol(pParent, iLbnd, iHbnd, iSym);
 }
 
 bool CMandarinAlphMgr::CConvRoot::isInGroup(const SGroupInfo *pGroup) {
@@ -337,11 +347,21 @@ void CMandarinAlphMgr::GetConversions(std::vector<pair<symbol,unsigned int> > &v
   
 }
 
-CMandarinAlphMgr::CMandSym::CMandSym(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, CMandarinAlphMgr *pMgr, symbol iSymbol, symbol pyParent)
-: CSymbolNode(pParent, iOffset, iLbnd, iHbnd, pMgr->GetCHColour(iSymbol,iOffset), strGroup+pMgr->m_CHdisplayText[iSymbol], pMgr, iSymbol), m_pyParent(pyParent) {
+CDasherScreen::Label *CMandarinAlphMgr::GetLabel(int iCHsym) {
+  //TODO: LRU cache, keep down to some sensible #labels allocated?
+  if (iCHsym>=m_vLabels.size()) {
+    m_vLabels.resize(iCHsym+1);
+  } else if (m_vLabels[iCHsym]) {
+    return m_vLabels[iCHsym];
+  }
+  return m_vLabels[iCHsym] = m_pScreen->MakeLabel(m_CHdisplayText[iCHsym]);
+}
+
+CMandarinAlphMgr::CMandSym::CMandSym(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CMandarinAlphMgr *pMgr, symbol iSymbol, symbol pyParent)
+: CSymbolNode(pParent, iOffset, iLbnd, iHbnd, pMgr->GetCHColour(iSymbol,iOffset), pMgr->GetLabel(iSymbol), pMgr, iSymbol), m_pyParent(pyParent) {
 }
 
-CDasherNode *CMandarinAlphMgr::CMandSym::RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol) {
+CDasherNode *CMandarinAlphMgr::CMandSym::RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol) {
   DASHER_ASSERT(m_pyParent!=0); //should have been computed in RebuildForwardsFromAncestor()
   if (iSymbol==m_pyParent) {
     //create the PY node that lead to this chinese
@@ -352,11 +372,11 @@ CDasherNode *CMandarinAlphMgr::CMandSym::RebuildSymbol(CAlphNode *pParent, unsig
       return this;
     }
     //ok, will be a PY-to-Chinese conversion choice
-    CConvRoot *pConv = mgr()->CreateConvRoot(pParent, iLbnd, iHbnd, strGroup, iSymbol);
+    CConvRoot *pConv = mgr()->CreateConvRoot(pParent, iLbnd, iHbnd, iSymbol);
     pConv->PopulateChildrenWithExisting(this);
     return pConv;
   }
-  return CAlphBase::RebuildSymbol(pParent, iLbnd, iHbnd, strGroup, iBkgCol, iSymbol);
+  return CAlphBase::RebuildSymbol(pParent, iLbnd, iHbnd, iSymbol);
 }
 
 bool CMandarinAlphMgr::CMandSym::isInGroup(const SGroupInfo *pGroup) {
@@ -372,7 +392,7 @@ CMandarinAlphMgr::CMandSym *CMandarinAlphMgr::CMandSym::RebuildCHSymbol(CConvRoo
     SetRange(iLbnd, iHbnd);
     return this;
   }
-  return mgr()->CreateCHSymbol(pParent, pParent->iContext, iLbnd, iHbnd, "", iNewSym, pParent->m_pySym);
+  return mgr()->CreateCHSymbol(pParent, pParent->iContext, iLbnd, iHbnd, iNewSym, pParent->m_pySym);
 }
 
 void CMandarinAlphMgr::CMandSym::RebuildForwardsFromAncestor(CAlphNode *pNewNode) {
diff --git a/Src/DasherCore/MandarinAlphMgr.h b/Src/DasherCore/MandarinAlphMgr.h
index b2264d3..5602743 100644
--- a/Src/DasherCore/MandarinAlphMgr.h
+++ b/Src/DasherCore/MandarinAlphMgr.h
@@ -79,6 +79,7 @@ namespace Dasher {
     CAlphNode *GetRoot(CDasherNode *pParent, unsigned int iLower, unsigned int iUpper, bool bEnteredLast, int iOffset);    
     
   protected:
+    void MakeLabels(CDasherScreen *pScreen);
     class CConvRoot;
     ///Subclass of CSymbolNode for (converted) chinese-alphabet symbols:
     /// these use the chinese alphabet in place of the pinyin one for text to display/enter,
@@ -87,9 +88,8 @@ namespace Dasher {
     public:
       CMandarinAlphMgr *mgr() {return static_cast<CMandarinAlphMgr *>(CSymbolNode::mgr());}
       ///Symbol constructor: display text from CHAlphabet, colour from GetCHColour
-      /// \param strGroup caption of any group(s) containing this symbol for which no nodes created; prepended to display text.
-      CMandSym(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, CMandarinAlphMgr *pMgr, symbol iSymbol, symbol pyParent);
-      CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
+      CMandSym(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CMandarinAlphMgr *pMgr, symbol iSymbol, symbol pyParent);
+      CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
       CMandSym *RebuildCHSymbol(CConvRoot *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iNewSym);
     protected:
       ///Override to compute which pinyin symbol to make our parent...
@@ -109,7 +109,7 @@ namespace Dasher {
       class CConvRoot : public CAlphBase {
     public:
       /// \param pySym symbol in pinyin alphabet; must have >1 possible chinese conversion.
-      CConvRoot(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, CMandarinAlphMgr *pMgr, symbol pySym);
+      CConvRoot(CDasherNode *pParent, int iOffset, unsigned int iLbnd, unsigned int iHbnd, CMandarinAlphMgr *pMgr, symbol pySym);
       CMandarinAlphMgr *mgr() {return static_cast<CMandarinAlphMgr *>(CAlphBase::mgr());}
       void PopulateChildren();
       void PopulateChildrenWithExisting(CMandSym *existing);
@@ -117,7 +117,7 @@ namespace Dasher {
       CLanguageModel::Context iContext;
       void SetFlag(int iFlag, bool bValue);
       const symbol m_pySym;
-      CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
+      CDasherNode *RebuildSymbol(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
     protected:
       bool isInGroup(const SGroupInfo *pGroup);
     private:        
@@ -126,23 +126,22 @@ namespace Dasher {
     ///Called to create the node for a pinyin leaf symbol;
     /// Overridden to call either CreateConvRoot or CreateCHSymbol, according to #chinese symbols under specified pinyin
     /// \param iSymbol Symbol number in pinyin alphabet
-    virtual CDasherNode *CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, int iBkgCol, symbol iSymbol);
+    virtual CDasherNode *CreateSymbolNode(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iSymbol);
 
     ///Creates a CConvRoot, for a Pinyin symbol with multiple possible chinese symbols.
     /// Colour is always 9 (opaque), so no need for background colour.
     /// \param pParent parent node, context will be taken from here
     /// \param iPYsym Symbol (leaf) in pinyin alphabet
-    CConvRoot *CreateConvRoot(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, symbol iPYsym);
+    CConvRoot *CreateConvRoot(CAlphNode *pParent, unsigned int iLbnd, unsigned int iHbnd, symbol iPYsym);
       
     ///Creates a node for (i.e. that will actually enter) a chinese symbol
     /// \param pParent parent node: could be a CGroupNode (directly), if some pinyin symbol in that group had only
     /// one corresponding chinese symbol (=> this), or a CConvRoot (if this chinese symbol is one of many possible
     /// chinese symbols for a particular pinyin).
     /// \param iContext parent node's context, from which to generate context for this node
-    /// \param strGroup caption of any elided groups (prepended to this node's caption)
     /// \param iCHsym symbol number in chinese alphabet
     /// \param pyParent pinyin-alphabet symbol which was used to enter this chinese symbol (if known, else 0)
-    CMandSym *CreateCHSymbol(CDasherNode *pParent, CLanguageModel::Context iContext, unsigned int iLbnd, unsigned int iHbnd, const std::string &strGroup, symbol iCHsym, symbol pyParent);
+    CMandSym *CreateCHSymbol(CDasherNode *pParent, CLanguageModel::Context iContext, unsigned int iLbnd, unsigned int iHbnd, symbol iCHsym, symbol pyParent);
 
     ///Gets the possible chinese symbols for a pinyin one, along with their probabilities in the specified context.
     ///Probabilities are computed by CPPMPYLanguageModel::GetPartProbs, then renormalized here. (TODO unnecessary?)
@@ -175,6 +174,10 @@ namespace Dasher {
     /// returned by CAlphabetMap::GetSymbols() for that text)
     /// the set of pinyin syllable+tones which could yield that symbol.
     std::map<symbol,std::set<symbol> > m_PinyinByChinese;
+    
+    //Used to create labels lazily
+    CDasherScreen *m_pScreen;
+    CDasherScreen::Label *GetLabel(int iCHsym);
   };
   /// @}
 
diff --git a/Src/DasherCore/NodeCreationManager.cpp b/Src/DasherCore/NodeCreationManager.cpp
index 00187af..9efeb9f 100644
--- a/Src/DasherCore/NodeCreationManager.cpp
+++ b/Src/DasherCore/NodeCreationManager.cpp
@@ -12,7 +12,7 @@ CNodeCreationManager::CNodeCreationManager(Dasher::CDasherInterfaceBase *pInterf
                                            Dasher::CEventHandler *pEventHandler, 
                                            CSettingsStore *pSettingsStore,
                                            Dasher::CAlphIO *pAlphIO) : CDasherComponent(pEventHandler, pSettingsStore),
-  m_pInterface(pInterface), m_pControlManager(NULL) {
+  m_pInterface(pInterface), m_pControlManager(NULL), m_pScreen(NULL) {
 
   const Dasher::CAlphInfo *pAlphInfo(pAlphIO->GetInfo(pSettingsStore->GetStringParameter(SP_ALPHABET_ID)));
   const CAlphabetMap *pAlphMap = pAlphInfo->MakeMap();
@@ -98,6 +98,13 @@ CNodeCreationManager::~CNodeCreationManager() {
   delete m_pControlManager;
 }
 
+void CNodeCreationManager::ChangeScreen(CDasherScreen *pScreen) {
+  if (m_pScreen == pScreen) return;
+  m_pScreen = pScreen;
+  m_pAlphabetManager->MakeLabels(pScreen);
+  if (m_pControlManager) m_pControlManager->MakeLabels(pScreen);
+}
+
 void CNodeCreationManager::HandleEvent(CEvent *pEvent) {
   if (pEvent->m_iEventType == EV_PARAM_NOTIFY) {
     switch (static_cast<CParameterNotificationEvent *>(pEvent)->m_iParameter) {
@@ -107,6 +114,7 @@ void CNodeCreationManager::HandleEvent(CEvent *pEvent) {
         unsigned long iControlSpace;
         if (GetBoolParameter(BP_CONTROL_MODE)) {
           m_pControlManager = new CControlManager(m_pEventHandler, m_pSettingsStore, this, m_pInterface);
+          if (m_pScreen) m_pControlManager->MakeLabels(m_pScreen);
           iControlSpace = iNorm / 20;
         } else {
           m_pControlManager = NULL;
diff --git a/Src/DasherCore/NodeCreationManager.h b/Src/DasherCore/NodeCreationManager.h
index 68213c6..b2ed99e 100644
--- a/Src/DasherCore/NodeCreationManager.h
+++ b/Src/DasherCore/NodeCreationManager.h
@@ -18,6 +18,7 @@ namespace Dasher {
   class CDasherNode;
   class CDasherInterfaceBase;
   class CControlManager;
+  class CDasherScreen;
 }
 //TODO why is CNodeCreationManager _not_ in namespace Dasher?!?!
 /// \ingroup Model
@@ -30,6 +31,9 @@ class CNodeCreationManager : public Dasher::CDasherComponent {
                        Dasher::CAlphIO *pAlphIO);
   ~CNodeCreationManager();
   
+  ///Tells us the screen on which all created node labels must be rendered
+  void ChangeScreen(Dasher::CDasherScreen *pScreen);
+  
   //we watch for changes to BP_CONTROL_MODE and create the Control Manager lazily
   void HandleEvent(Dasher::CEvent *pEvent);
   ///
@@ -66,6 +70,9 @@ class CNodeCreationManager : public Dasher::CDasherComponent {
   ///Amount of probability space to assign to letters (language model + smoothing),
   /// i.e. remaining after taking away whatever we need for control mode (perhaps 0)
   unsigned long m_iAlphNorm;
+  
+  ///Screen to use to create node labels
+  Dasher::CDasherScreen *m_pScreen;
 };
 /// @}
 
diff --git a/Src/DasherCore/Parameters.h b/Src/DasherCore/Parameters.h
index 5484b36..303636d 100644
--- a/Src/DasherCore/Parameters.h
+++ b/Src/DasherCore/Parameters.h
@@ -30,7 +30,7 @@
 // All parameters go into the enums here
 // They are unique across the different types
 enum { 
-  BP_MULTISIZE_FONTS, BP_DRAW_MOUSE_LINE, BP_DRAW_MOUSE, BP_CURVE_MOUSE_LINE,
+  BP_DRAW_MOUSE_LINE, BP_DRAW_MOUSE, BP_CURVE_MOUSE_LINE,
   BP_SHOW_SLIDER, BP_START_MOUSE,
   BP_START_SPACE, BP_STOP_IDLE, BP_CONTROL_MODE, 
   BP_COLOUR_MODE, BP_MOUSEPOS_MODE,
@@ -124,7 +124,6 @@ struct sp_table {
 // The only important thing here is that these are in the same order
 // as the enum declarations (could add check in class that enforces this instead)
 static bp_table boolparamtable[] = {
-  {BP_MULTISIZE_FONTS, "MultiSizeFonts", PERS, false, "Use multiple font sizes"},
   {BP_DRAW_MOUSE_LINE, "DrawMouseLine", PERS, true, "Draw Mouse Line"},
   {BP_DRAW_MOUSE, "DrawMouse", PERS, false, "Draw Mouse Position"},
   {BP_CURVE_MOUSE_LINE, "CurveMouseLine", PERS, false, "Curve mouse line according to screen nonlinearity"},
diff --git a/Src/Gtk2/Canvas.cpp b/Src/Gtk2/Canvas.cpp
index 64fbeec..5fc66f9 100644
--- a/Src/Gtk2/Canvas.cpp
+++ b/Src/Gtk2/Canvas.cpp
@@ -7,9 +7,8 @@
 
 using namespace Dasher;
 
-CCanvas::CCanvas(GtkWidget *pCanvas, CPangoCache *pPangoCache,
-                 screenint iWidth, screenint iHeight)
-  : CDasherScreen(iWidth, iHeight) {
+CCanvas::CCanvas(GtkWidget *pCanvas)
+  : CLabelListScreen(0,0) {
 
 #if WITH_CAIRO
   cairo_colours = 0;
@@ -18,24 +17,31 @@ CCanvas::CCanvas(GtkWidget *pCanvas, CPangoCache *pPangoCache,
 #endif
   
   m_pCanvas = pCanvas;
-  m_pPangoCache = pPangoCache;
-  
+
+  gtk_widget_add_events(m_pCanvas, GDK_ALL_EVENTS_MASK);
+
+  InitSurfaces(); //will create 0*0 surfaces. Is that a good idea? It seems to
+  // let us create PangoLayouts ok, which is what we need - but if we can
+  // create them without a backing surface at all, that might be better...
+}
+
+void CCanvas::InitSurfaces() {  
   // Construct the buffer pixmaps
   // FIXME - only allocate without cairo
 
 #if WITH_CAIRO
 
-  m_pDisplaySurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, m_iWidth, m_iHeight);
-  m_pDecorationSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, m_iWidth, m_iHeight);
-  //m_pOnscreenSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, m_iWidth, m_iHeight);
+  m_pDisplaySurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, GetWidth(), GetHeight());
+  m_pDecorationSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, GetWidth(), GetHeight());
+  //m_pOnscreenSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, GetWidth(), GetHeight());
 
 #else
 
-  //m_pDummyBuffer = gdk_pixmap_new(pCanvas->window, m_iWidth, m_iHeight, -1);
+  //m_pDummyBuffer = gdk_pixmap_new(pCanvas->window, GetWidth(), GetHeight(), -1);
 
-  m_pDisplayBuffer = gdk_pixmap_new(pCanvas->window, m_iWidth, m_iHeight, -1);
-  m_pDecorationBuffer = gdk_pixmap_new(pCanvas->window, m_iWidth, m_iHeight, -1);
-  //m_pOnscreenBuffer = gdk_pixmap_new(pCanvas->window, m_iWidth, m_iHeight, -1);
+  m_pDisplayBuffer = gdk_pixmap_new(pCanvas->window, GetWidth(), GetHeight(), -1);
+  m_pDecorationBuffer = gdk_pixmap_new(pCanvas->window, GetWidth(), GetHeight(), -1);
+  //m_pOnscreenBuffer = gdk_pixmap_new(pCanvas->window, GetWidth(), GetHeight(), -1);
 
   // Set the display buffer to be current
 
@@ -60,11 +66,9 @@ CCanvas::CCanvas(GtkWidget *pCanvas, CPangoCache *pPangoCache,
   //onscreen_cr = cairo_create(m_pOnscreenSurface);
 
 #endif
-
-  gtk_widget_add_events(m_pCanvas, GDK_ALL_EVENTS_MASK);
 }
 
-CCanvas::~CCanvas() {
+void CCanvas::DestroySurfaces() {
   // Free the buffer pixmaps
 
 #if WITH_CAIRO
@@ -81,12 +85,21 @@ CCanvas::~CCanvas() {
   g_object_unref(m_pDecorationBuffer);
   //g_object_unref(m_pOnscreenBuffer);
 #endif
+}
 
+CCanvas::~CCanvas() {
+  DestroySurfaces();
 #if WITH_CAIRO
   delete[] cairo_colours;
 #endif
 }
 
+void CCanvas::resize(screenint w,screenint h) {
+  DestroySurfaces();
+  CDasherScreen::resize(w,h);
+  InitSurfaces();
+} 
+
 void CCanvas::Blank() {
   // FIXME - this is replicated throughout this file - do something
   // about that
@@ -105,7 +118,7 @@ void CCanvas::Blank() {
 #if WITH_CAIRO
   cairo_paint(cr);
 #else
-  gdk_draw_rectangle(m_pOffscreenBuffer, graphics_context, TRUE, 0, 0, m_iWidth, m_iHeight);
+  gdk_draw_rectangle(m_pOffscreenBuffer, graphics_context, TRUE, 0, 0, GetWidth(), GetHeight());
 #endif
 
   END_DRAWING;
@@ -131,12 +144,12 @@ void CCanvas::Display() {
 
   // TODO: Reimplement (kind of important!)
 
-  //  gdk_draw_drawable(m_pOnscreenBuffer, m_pCanvas->style->fg_gc[GTK_WIDGET_STATE(m_pCanvas)], m_pOffscreenBuffer, 0, 0, 0, 0, m_iWidth, m_iHeight);
+  //  gdk_draw_drawable(m_pOnscreenBuffer, m_pCanvas->style->fg_gc[GTK_WIDGET_STATE(m_pCanvas)], m_pOffscreenBuffer, 0, 0, 0, 0, GetWidth(), GetHeight());
   
   //  BEGIN_DRAWING;
 
 //   cairo_set_source_surface(onscreen_cr, m_pDecorationSurface, 0, 0);
-//   cairo_rectangle(onscreen_cr, 0, 0, m_iWidth, m_iHeight);
+//   cairo_rectangle(onscreen_cr, 0, 0, GetWidth(), GetHeight());
 //   cairo_fill(onscreen_cr);
 
 
@@ -145,20 +158,20 @@ void CCanvas::Display() {
 
   // Blank the offscreen buffer (?)
 
-  //  gdk_draw_rectangle(m_pOffscreenBuffer, graphics_context, TRUE, 0, 0, m_iWidth, m_iHeight);
+  //  gdk_draw_rectangle(m_pOffscreenBuffer, graphics_context, TRUE, 0, 0, GetWidth(), GetHeight());
 
   // Invalidate the full canvas to force it to be redrawn on-screen
 
  //  update_rect.x = 0;
 //   update_rect.y = 0;
-//   update_rect.width = m_iWidth;
-//   update_rect.height = m_iHeight;
+//   update_rect.width = GetWidth();
+//   update_rect.height = GetHeight();
 
 //   gdk_window_invalidate_rect(m_pCanvas->window, &update_rect, FALSE);
 
   //  BEGIN_DRAWING;
 
-  //  GdkRectangle sRect = {0, 0, m_iWidth, m_iHeight};
+  //  GdkRectangle sRect = {0, 0, GetWidth(), GetHeight()};
   //  gdk_window_begin_paint_rect(m_pCanvas->window, &sRect);
 
 #if WITH_CAIRO  
@@ -169,11 +182,11 @@ void CCanvas::Display() {
   widget_cr = gdk_cairo_create(m_pCanvas->window);
 #endif
   cairo_set_source_surface(widget_cr, m_pDecorationSurface, 0, 0);
-  cairo_rectangle(widget_cr, 0, 0, m_iWidth, m_iHeight);
+  cairo_rectangle(widget_cr, 0, 0, GetWidth(), GetHeight());
   cairo_fill(widget_cr);
   cairo_destroy(widget_cr);
 #else
-  gdk_draw_drawable(m_pCanvas->window, m_pCanvas->style->fg_gc[GTK_WIDGET_STATE(m_pCanvas)], m_pDecorationBuffer, 0, 0, 0, 0, m_iWidth, m_iHeight);
+  gdk_draw_drawable(m_pCanvas->window, m_pCanvas->style->fg_gc[GTK_WIDGET_STATE(m_pCanvas)], m_pDecorationBuffer, 0, 0, 0, 0, GetWidth(), GetHeight());
 #endif
 
   //   gdk_window_end_paint(m_pCanvas->window);
@@ -399,7 +412,56 @@ void CCanvas::Polyline(Dasher::CDasherScreen::point *Points, int Number, int iWi
   END_DRAWING;
 }
 
-void CCanvas::DrawString(const std::string &String, screenint x1, screenint y1, int size, int iColor) {
+CDasherScreen::Label *CCanvas::MakeLabel(const string &strText) {
+  return new CPangoLabel(this, strText);
+}
+
+void CCanvas::SetFont(const std::string &strName) {
+  m_strFontName=strName;
+  for (map<unsigned int,PangoFontDescription *>::iterator it=m_mFonts.begin(); it!=m_mFonts.end(); it++) {
+    pango_font_description_free(it->second);
+    it->second = pango_font_description_from_string(m_strFontName.c_str());
+    pango_font_description_set_size(it->second,it->first);
+  }
+  for (set<CLabelListScreen::Label *>::iterator it=LabelsBegin(); it!=LabelsEnd(); it++) {
+    map<unsigned int,PangoLayout *> &layouts(static_cast<CPangoLabel *>(*it)->m_mLayouts);
+    for (map<unsigned int,PangoLayout *>::iterator it2=layouts.begin(); it2!=layouts.end(); it2++) {
+      DASHER_ASSERT(m_mFonts.find(it2->first) != m_mFonts.end()); //central font repository knows about this size
+      pango_layout_set_font_description(it2->second,m_mFonts[it2->first]);
+    }
+  }
+}
+
+PangoLayout *CCanvas::GetLayout(CPangoLabel *label, unsigned int iFontSize) {
+  {
+    map<unsigned int,PangoLayout *>::iterator it = label->m_mLayouts.find(iFontSize);
+    if (it != label->m_mLayouts.end()) return it->second;
+  }
+#if WITH_CAIRO
+    PangoLayout *pNewPangoLayout(pango_cairo_create_layout(cr));
+#else
+    PangoLayout *pNewPangoLayout(gtk_widget_create_pango_layout(pCanvas, ""));
+#endif
+  label->m_mLayouts.insert(pair<unsigned int,PangoLayout *>(iFontSize, pNewPangoLayout));
+
+  pango_layout_set_text(pNewPangoLayout, label->m_strText.c_str(), -1);
+  
+  PangoFontDescription *pF;
+  {
+    map<unsigned int,PangoFontDescription *>::iterator it = m_mFonts.find(iFontSize);
+    if (it != m_mFonts.end())
+      pF=it->second;
+    else {
+      pF = pango_font_description_from_string(m_strFontName.c_str());
+      pango_font_description_set_size(pF, iFontSize * PANGO_SCALE);
+      m_mFonts[iFontSize] = pF;
+    }
+    pango_layout_set_font_description(pNewPangoLayout, pF);
+  }
+    return pNewPangoLayout;
+}
+
+void CCanvas::DrawString(CDasherScreen::Label *label, screenint x1, screenint y1, unsigned int size, int iColor) {
   
 #if WITH_CAIRO
 #else
@@ -413,11 +475,7 @@ void CCanvas::DrawString(const std::string &String, screenint x1, screenint y1,
   BEGIN_DRAWING;
   SET_COLOR(iColor);
 
-#if WITH_CAIRO
-  PangoLayout *pLayout(m_pPangoCache->GetLayout(cr, String, size));
-#else
-  PangoLayout *pLayout(m_pPangoCache->GetLayout(GTK_WIDGET(m_pCanvas), String, size));
-#endif
+  PangoLayout *pLayout(GetLayout(static_cast<CPangoLabel*>(label),size));
 
   PangoRectangle sPangoInk;
 
@@ -434,18 +492,13 @@ void CCanvas::DrawString(const std::string &String, screenint x1, screenint y1,
   END_DRAWING;
 }
 
-void CCanvas::TextSize(const std::string &String, screenint *Width, screenint *Height, int size) {
-#if WITH_CAIRO
-  PangoLayout *pLayout(m_pPangoCache->GetLayout(cr, String, size));
-#else
-  PangoLayout *pLayout(m_pPangoCache->GetLayout(GTK_WIDGET(m_pCanvas), String, size));
-#endif
+pair<screenint,screenint> CCanvas::TextSize(CDasherScreen::Label *label, unsigned int size) {
+  PangoLayout *pLayout(GetLayout(static_cast<CPangoLabel*>(label),size));
 
   PangoRectangle sPangoInk;
   pango_layout_get_pixel_extents(pLayout, &sPangoInk, NULL);
 
-  *Width = sPangoInk.width;
-  *Height = sPangoInk.height;
+  return pair<screenint,screenint>(sPangoInk.width,sPangoInk.height);
 }
 
 void CCanvas::SendMarker(int iMarker) {
@@ -462,11 +515,11 @@ void CCanvas::SendMarker(int iMarker) {
 
 #if WITH_CAIRO
     cairo_set_source_surface(decoration_cr, m_pDisplaySurface, 0, 0);
-    cairo_rectangle(decoration_cr, 0, 0, m_iWidth, m_iHeight);
+    cairo_rectangle(decoration_cr, 0, 0, GetWidth(), GetHeight());
     cairo_fill(decoration_cr);
     cr = decoration_cr;
 #else
-    gdk_draw_drawable(m_pDecorationBuffer, m_pCanvas->style->fg_gc[GTK_WIDGET_STATE(m_pCanvas)], m_pDisplayBuffer, 0, 0, 0, 0, m_iWidth, m_iHeight);
+    gdk_draw_drawable(m_pDecorationBuffer, m_pCanvas->style->fg_gc[GTK_WIDGET_STATE(m_pCanvas)], m_pDisplayBuffer, 0, 0, 0, 0, GetWidth(), GetHeight());
     m_pOffscreenBuffer = m_pDecorationBuffer;
 #endif
     break;
@@ -521,8 +574,8 @@ bool CCanvas::GetCanvasSize(GdkRectangle *pRectangle)
 
   pRectangle->x       = iX;
   pRectangle->y       = iY;
-  pRectangle->width   = m_iWidth;
-  pRectangle->height  = m_iHeight;
+  pRectangle->width   = GetWidth();
+  pRectangle->height  = GetHeight();
 
   return true;
 }
diff --git a/Src/Gtk2/Canvas.h b/Src/Gtk2/Canvas.h
index 0d9f78d..44987a4 100644
--- a/Src/Gtk2/Canvas.h
+++ b/Src/Gtk2/Canvas.h
@@ -8,7 +8,8 @@
 
 #include <gtk/gtk.h>
 #include <gdk/gdk.h>
-#include "PangoCache.h"
+#include <pango/pango.h>
+#include <map>
 
 #include <iostream>
 
@@ -52,8 +53,6 @@
 #define SET_COLOR(c)					\
   SET_COLOR_BACKEND(c)
 
-using namespace Dasher;
-
 /// CCanvas
 ///
 /// Method definitions for CCanvas, implementing the CDasherScreen
@@ -62,17 +61,16 @@ using namespace Dasher;
 /// primitive' should really not be here - higher level drawing
 /// functions belong in CDasherView.
 
-class CCanvas:public Dasher::CDasherScreen {
+class CCanvas:public Dasher::CLabelListScreen {
 
 public:
-
-  /// 
+  typedef Dasher::screenint screenint;
+  /// Creates a new canvas - initially of zero size, so drawing
+  /// operations won't do anything until a call to resize() is made.
   /// \param pCanvas The GTK drawing area used by the canvas
-  /// \param pPangoCache A cache for precomputed Pango layouts
   ///
 
-  CCanvas(GtkWidget *pCanvas, CPangoCache *pPangoCache,
-          screenint iWidth, screenint iHeight);
+  CCanvas(GtkWidget *pCanvas);
   ~CCanvas();
 
   ///
@@ -91,28 +89,10 @@ public:
   /// \deprecated In Linux - now handled by the pango cache, but need to think how this fits in with Windows
   ///  
 
-  void SetFont(std::string Name) {
-  };
-
-  ///
-  /// Set the font size for rendering
-  /// \param fontsize The font size to use
-  /// \deprecated Obsolete
-  ///
+  void SetFont(const std::string &strName);
 
-  void SetFontSize(Dasher::Opts::FontSize fontsize) {
-
-  };
-
-  ///
-  /// Get the current font size
-  /// \deprecated To be removed before 4.0 release
-  /// \todo We should not be relying on locally cached variables - check to see whether this is still used or not
-  ///
-
-  Dasher::Opts::FontSize GetFontSize() {
-    return Dasher::Opts::FontSize(1);
-  };
+  ///Make a label for use with this screen; caches Pango layout information inside it.
+  CDasherScreen::Label *MakeLabel(const std::string &strText);
 
   ///
   /// Return the physical extent of a given string being rendered at a given size.
@@ -122,7 +102,7 @@ public:
   /// \param Size Size at which the string will be rendered (units?)
   ///
 
-  void TextSize(const std::string &String, screenint *Width, screenint *Height, int Size);
+  std::pair<screenint,screenint> TextSize(CDasherScreen::Label *label, unsigned int Size);
 
   ///
   /// Draw a text string
@@ -132,7 +112,7 @@ public:
   /// \param Size The size at which to render the rectangle (units?)
   ///
 
-  void DrawString(const std::string &String, screenint x1, screenint y1, int Size, int iColor);
+  void DrawString(CDasherScreen::Label *label, screenint x1, screenint y1, unsigned int Size, int iColor);
 
   ///
   /// Draw a rectangle
@@ -196,13 +176,16 @@ public:
   /// \param Colours New colours to use
   ///
 
-  void SetColourScheme(const CColourIO::ColourInfo *pColourScheme);
+  void SetColourScheme(const Dasher::CColourIO::ColourInfo *pColourScheme);
 
   /// 
   /// Gets the location and size of our canvas.
   /// Returns true on success, false otherwise.
   bool GetCanvasSize(GdkRectangle *pRectangle);
 
+  // Redeclare to make public and adjust cairo/gdk surface sizes
+  void resize(screenint w,screenint h);
+
 private:
 
   ///
@@ -211,6 +194,8 @@ private:
 
   GtkWidget *m_pCanvas;
 
+  void InitSurfaces();
+  void DestroySurfaces();
 #if WITH_CAIRO
 
   cairo_surface_t *m_pDisplaySurface;
@@ -250,12 +235,18 @@ private:
 
 #endif
 
-  /// 
-  /// The Pango cache - used to store pre-computed pango layouts as
-  /// they are costly to regenerate every time they are needed.
-  ///
+  std::string m_strFontName;
+  std::map<unsigned int,PangoFontDescription *> m_mFonts;
+
+  class CPangoLabel : public CLabelListScreen::Label {
+  public:
+    CPangoLabel(CCanvas *pCanvas, const std::string &strText)
+    : CLabelListScreen::Label(pCanvas, strText) {
+    }
+    std::map<unsigned int,PangoLayout *> m_mLayouts;
+  };
 
-  CPangoCache *m_pPangoCache;
+  PangoLayout *GetLayout(CPangoLabel *label, unsigned int iFontSize);
 
 #if WITH_CAIRO
   cairo_t *display_cr;
diff --git a/Src/Gtk2/DasherControl.cpp b/Src/Gtk2/DasherControl.cpp
index f1fba80..3a51857 100644
--- a/Src/Gtk2/DasherControl.cpp
+++ b/Src/Gtk2/DasherControl.cpp
@@ -37,40 +37,11 @@ static bool g_iTimeoutID = 0;
 
 // CDasherControl class definitions
 CDasherControl::CDasherControl(GtkVBox *pVBox, GtkDasherControl *pDasherControl) {
-  m_pPangoCache = NULL;
   m_pScreen = NULL;
 
   m_pDasherControl = pDasherControl;
   m_pVBox = GTK_WIDGET(pVBox);
   pClipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
-  Realize();
- 
-  //  m_pKeyboardHelper = new CKeyboardHelper(this);
-  //  m_pKeyboardHelper->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD));
-}
-
-void CDasherControl::CreateModules() {
-  CDasherInterfaceBase::CreateModules(); //create default set first
-  // Create locally cached copies of the mouse input objects, as we
-  // need to pass coordinates to them from the timer callback
-  m_pMouseInput =
-    (CDasherMouseInput *)  RegisterModule(new CDasherMouseInput(m_pEventHandler, m_pSettingsStore));
-  SetDefaultInputDevice(m_pMouseInput);
-  m_p1DMouseInput =
-    (CDasher1DMouseInput *)RegisterModule(new CDasher1DMouseInput(m_pEventHandler, m_pSettingsStore));
-  RegisterModule(new CSocketInput(m_pEventHandler, m_pSettingsStore));
-
-#ifdef JOYSTICK
-  RegisterModule(new CDasherJoystickInput(m_pEventHandler, m_pSettingsStore, this));
-  RegisterModule(new CDasherJoystickInputDiscrete(m_pEventHandler, m_pSettingsStore, this));
-#endif
-  
-#ifdef TILT
-  RegisterModule(new CDasherTiltInput(m_pEventHandler, m_pSettingsStore, this));
-#endif
-}
-
-void CDasherControl::SetupUI() {
   m_pCanvas = gtk_drawing_area_new();
 #if GTK_CHECK_VERSION (2,18,0)
   gtk_widget_set_can_focus(m_pCanvas, TRUE);
@@ -106,14 +77,42 @@ void CDasherControl::SetupUI() {
   g_signal_connect(m_pCanvas, "expose_event", G_CALLBACK(canvas_expose_event), this);
 #endif
 
-  // Create the Pango cache
+  m_pScreen = new CCanvas(m_pCanvas);
+  ChangeScreen(m_pScreen);
+
+  Realize();
+ 
+  //  m_pKeyboardHelper = new CKeyboardHelper(this);
+  //  m_pKeyboardHelper->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD));
+}
+
+void CDasherControl::CreateModules() {
+  CDasherInterfaceBase::CreateModules(); //create default set first
+  // Create locally cached copies of the mouse input objects, as we
+  // need to pass coordinates to them from the timer callback
+  m_pMouseInput =
+    (CDasherMouseInput *)  RegisterModule(new CDasherMouseInput(m_pEventHandler, m_pSettingsStore));
+  SetDefaultInputDevice(m_pMouseInput);
+  m_p1DMouseInput =
+    (CDasher1DMouseInput *)RegisterModule(new CDasher1DMouseInput(m_pEventHandler, m_pSettingsStore));
+  RegisterModule(new CSocketInput(m_pEventHandler, m_pSettingsStore));
 
+#ifdef JOYSTICK
+  RegisterModule(new CDasherJoystickInput(m_pEventHandler, m_pSettingsStore, this));
+  RegisterModule(new CDasherJoystickInputDiscrete(m_pEventHandler, m_pSettingsStore, this));
+#endif
+  
+#ifdef TILT
+  RegisterModule(new CDasherTiltInput(m_pEventHandler, m_pSettingsStore, this));
+#endif
+}
+
+void CDasherControl::SetupUI() {
   // TODO: Use system defaults?
   if(GetStringParameter(SP_DASHER_FONT) == "")
     SetStringParameter(SP_DASHER_FONT, "Sans 10");
- 
-  m_pPangoCache = new CPangoCache(GetStringParameter(SP_DASHER_FONT));
-
+  else
+    m_pScreen->SetFont(GetStringParameter(SP_DASHER_FONT));
 }
 
 
@@ -243,11 +242,6 @@ CDasherControl::~CDasherControl() {
     m_p1DMouseInput = NULL;
   }
 
-  if(m_pPangoCache) {
-    delete m_pPangoCache;
-    m_pPangoCache = NULL;
-  }
-
 //   if(m_pKeyboardHelper) {
 //     delete m_pKeyboardHelper;
 //     m_pKeyboardHelper = 0;
@@ -333,11 +327,8 @@ int CDasherControl::CanvasConfigureEvent() {
   a.height = m_pCanvas->allocation.height;
 #endif
 
-  if(m_pScreen != NULL)
-    delete m_pScreen;
-
-  m_pScreen = new CCanvas(m_pCanvas, m_pPangoCache, a.width, a.height);
-  ChangeScreen(m_pScreen);
+  m_pScreen->resize(a.width,a.height);
+  ScreenResized(m_pScreen);
  
   return 0;
 }
@@ -435,10 +426,8 @@ void CDasherControl::ExternalKeyUp(int iKeyVal) {
 void CDasherControl::HandleParameterNotification(int iParameter) {
   switch(iParameter) {
   case SP_DASHER_FONT:
-    if(m_pPangoCache) {
-      m_pPangoCache->ChangeFont(GetStringParameter(SP_DASHER_FONT));
+      m_pScreen->SetFont(GetStringParameter(SP_DASHER_FONT));
       ScheduleRedraw();
-    }
     break;
   case BP_GLOBAL_KEYBOARD:
     // TODO: reimplement
diff --git a/Src/Gtk2/DasherControl.h b/Src/Gtk2/DasherControl.h
index c2a8ac2..85a2a34 100644
--- a/Src/Gtk2/DasherControl.h
+++ b/Src/Gtk2/DasherControl.h
@@ -5,7 +5,6 @@
 #include <config.h>
 #endif
 
-#include "PangoCache.h"
 #include "Canvas.h"
 #include "../DasherCore/SocketInput.h"
 
@@ -193,12 +192,6 @@ private:
   CDasher1DMouseInput *m_p1DMouseInput;
 
   ///
-  /// Cache of Pango layouts
-  ///
-
-  CPangoCache *m_pPangoCache;
-
-  ///
   /// The CCanvas object
   ///
 
diff --git a/Src/Gtk2/Makefile.am b/Src/Gtk2/Makefile.am
index c0d05ad..985180e 100644
--- a/Src/Gtk2/Makefile.am
+++ b/Src/Gtk2/Makefile.am
@@ -35,8 +35,6 @@ libdashercontrol_la_SOURCES = \
 		GtkDasherControl.h \
 		KeyboardHelper.cpp \
 		KeyboardHelper.h \
-		PangoCache.cpp \
-		PangoCache.h \
 		Timer.cpp \
 		Timer.h \
 		Speech.h \
diff --git a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
index d6efe89..9e34cca 100755
--- a/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
+++ b/Src/MacOSX/Dasher.xcodeproj/project.pbxproj
@@ -136,22 +136,17 @@
 		1948BF3A0C226CFD001DFA32 /* UserLogTrial.h in Headers */ = {isa = PBXBuildFile; fileRef = 1948BE950C226CFD001DFA32 /* UserLogTrial.h */; };
 		1948BF3E0C226CFD001DFA32 /* XMLUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1948BE9A0C226CFD001DFA32 /* XMLUtil.cpp */; };
 		1948BF3F0C226CFD001DFA32 /* XMLUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 1948BE9B0C226CFD001DFA32 /* XMLUtil.h */; };
-		19558AD60C3182730054A193 /* DasherViewCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 19558AD50C3182730054A193 /* DasherViewCocoa.h */; };
-		196874060C2BDC2E00D63879 /* AlphabetLetter.h in Headers */ = {isa = PBXBuildFile; fileRef = 196874000C2BDC2E00D63879 /* AlphabetLetter.h */; };
-		196874070C2BDC2E00D63879 /* AlphabetLetter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 196874010C2BDC2E00D63879 /* AlphabetLetter.mm */; };
+		196874060C2BDC2E00D63879 /* OpenGLScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 196874000C2BDC2E00D63879 /* OpenGLScreen.h */; };
+		196874070C2BDC2E00D63879 /* OpenGLScreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = 196874010C2BDC2E00D63879 /* OpenGLScreen.mm */; };
 		1968740A0C2BDC2E00D63879 /* GLUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 196874040C2BDC2E00D63879 /* GLUtils.h */; };
 		1968740B0C2BDC2E00D63879 /* GLUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 196874050C2BDC2E00D63879 /* GLUtils.mm */; };
 		196874290C2BE12E00D63879 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 196874280C2BE12E00D63879 /* OpenGL.framework */; };
 		1974FE340714861B00B95DA0 /* DasherApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 19C49619045029A40000000A /* DasherApp.h */; };
-		1974FE370714861B00B95DA0 /* ZippyCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 1946CAB90481AD440000000A /* ZippyCache.h */; };
-		1974FE380714861B00B95DA0 /* ZippyString.h in Headers */ = {isa = PBXBuildFile; fileRef = 1946CABB0481AD440000000A /* ZippyString.h */; };
 		1974FE420714861B00B95DA0 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
 		1974FE430714861B00B95DA0 /* Preferences.nib in Resources */ = {isa = PBXBuildFile; fileRef = 19D442390454667F0000000A /* Preferences.nib */; };
 		1974FE440714861B00B95DA0 /* DasherApp.icns in Resources */ = {isa = PBXBuildFile; fileRef = 191CE19C04595BBA0000000A /* DasherApp.icns */; };
 		1974FE5A0714861B00B95DA0 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = 1974FD9C07145C6500B95DA0 /* Credits.html */; };
 		1974FE6B0714861B00B95DA0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; };
-		1974FE6F0714861B00B95DA0 /* ZippyCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 1946CABA0481AD440000000A /* ZippyCache.m */; };
-		1974FE700714861B00B95DA0 /* ZippyString.m in Sources */ = {isa = PBXBuildFile; fileRef = 1946CABC0481AD440000000A /* ZippyString.m */; };
 		1974FE780714861B00B95DA0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
 		1988ABBD0C9FF97000D97977 /* GameMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 1988ABB80C9FF97000D97977 /* GameMessages.h */; };
 		1988ABBE0C9FF97000D97977 /* GameStatistics.h in Headers */ = {isa = PBXBuildFile; fileRef = 1988ABB90C9FF97000D97977 /* GameStatistics.h */; };
@@ -317,17 +312,9 @@
 		19BEF4130C2291CE00275D06 /* colour.vowels2.xml in Resources */ = {isa = PBXBuildFile; fileRef = 19BEF4060C2291CE00275D06 /* colour.vowels2.xml */; };
 		19BEF4140C2291CE00275D06 /* colour.xml in Resources */ = {isa = PBXBuildFile; fileRef = 19BEF4070C2291CE00275D06 /* colour.xml */; };
 		19BEF4150C2291CE00275D06 /* colour.xsl in Resources */ = {isa = PBXBuildFile; fileRef = 19BEF4080C2291CE00275D06 /* colour.xsl */; };
-		19C190AB0C3267D700979F34 /* DasherViewAqua.h in Headers */ = {isa = PBXBuildFile; fileRef = 19C190A90C3267D700979F34 /* DasherViewAqua.h */; };
-		19C190AC0C3267D700979F34 /* DasherViewAqua.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19C190AA0C3267D700979F34 /* DasherViewAqua.mm */; };
 		19C1AE820B130F18005C68D3 /* COSXMouseInput.h in Headers */ = {isa = PBXBuildFile; fileRef = 19C1AE810B130F18005C68D3 /* COSXMouseInput.h */; };
-		19E1AE4F0B0DB73300F3466C /* COSXDasherScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 19E1AE4D0B0DB73300F3466C /* COSXDasherScreen.h */; };
-		19E1AE500B0DB73300F3466C /* COSXDasherScreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19E1AE4E0B0DB73300F3466C /* COSXDasherScreen.mm */; };
 		19E49DB50B10556100BA5CE8 /* DasherUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 196D8785048AA2750000000A /* DasherUtil.mm */; };
 		19E49DB60B10556200BA5CE8 /* DasherUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 196D8784048AA2750000000A /* DasherUtil.h */; };
-		19F36D8E0B18B60E002F41F1 /* ZippyStringImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 19F36D8A0B18B60E002F41F1 /* ZippyStringImage.h */; };
-		19F36D8F0B18B60E002F41F1 /* ZippyStringImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F36D8B0B18B60E002F41F1 /* ZippyStringImage.m */; };
-		19F36D900B18B60E002F41F1 /* ZippyStringGlyph.h in Headers */ = {isa = PBXBuildFile; fileRef = 19F36D8C0B18B60E002F41F1 /* ZippyStringGlyph.h */; };
-		19F36D910B18B60E002F41F1 /* ZippyStringGlyph.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F36D8D0B18B60E002F41F1 /* ZippyStringGlyph.m */; };
 		19F8C7E60C858A2800276B4F /* I18n.h in Headers */ = {isa = PBXBuildFile; fileRef = 19F8C7E50C858A2800276B4F /* I18n.h */; };
 		3300115210A2EA7700D31B1D /* ExpansionPolicy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3300115010A2EA7700D31B1D /* ExpansionPolicy.cpp */; };
 		3300115310A2EA7700D31B1D /* ExpansionPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 3300115110A2EA7700D31B1D /* ExpansionPolicy.h */; };
@@ -414,10 +401,6 @@
 		1921DB6D0C7ECB9900E6DAA5 /* GameScorer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameScorer.h; sourceTree = "<group>"; };
 		19351BF304575C6C0000000A /* mktar.sh */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; path = mktar.sh; sourceTree = "<group>"; };
 		193731A70C8586F20022CBC7 /* config.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
-		1946CAB90481AD440000000A /* ZippyCache.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ZippyCache.h; sourceTree = "<group>"; };
-		1946CABA0481AD440000000A /* ZippyCache.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ZippyCache.m; sourceTree = "<group>"; };
-		1946CABB0481AD440000000A /* ZippyString.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ZippyString.h; sourceTree = "<group>"; };
-		1946CABC0481AD440000000A /* ZippyString.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ZippyString.m; sourceTree = "<group>"; };
 		1948BDF60C226CFC001DFA32 /* ActionButton.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ActionButton.cpp; sourceTree = "<group>"; };
 		1948BDF70C226CFC001DFA32 /* ActionButton.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ActionButton.h; sourceTree = "<group>"; };
 		1948BDFC0C226CFC001DFA32 /* AlphabetMap.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AlphabetMap.cpp; sourceTree = "<group>"; };
@@ -543,9 +526,8 @@
 		1948BE950C226CFD001DFA32 /* UserLogTrial.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = UserLogTrial.h; sourceTree = "<group>"; };
 		1948BE9A0C226CFD001DFA32 /* XMLUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = XMLUtil.cpp; sourceTree = "<group>"; };
 		1948BE9B0C226CFD001DFA32 /* XMLUtil.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = XMLUtil.h; sourceTree = "<group>"; };
-		19558AD50C3182730054A193 /* DasherViewCocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DasherViewCocoa.h; sourceTree = "<group>"; };
-		196874000C2BDC2E00D63879 /* AlphabetLetter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AlphabetLetter.h; sourceTree = "<group>"; };
-		196874010C2BDC2E00D63879 /* AlphabetLetter.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = AlphabetLetter.mm; sourceTree = "<group>"; };
+		196874000C2BDC2E00D63879 /* OpenGLScreen.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = OpenGLScreen.h; path = ../Common/OpenGLScreen.h; sourceTree = SOURCE_ROOT; };
+		196874010C2BDC2E00D63879 /* OpenGLScreen.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; name = OpenGLScreen.mm; path = ../Common/OpenGLScreen.mm; sourceTree = SOURCE_ROOT; };
 		196874040C2BDC2E00D63879 /* GLUtils.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GLUtils.h; sourceTree = "<group>"; };
 		196874050C2BDC2E00D63879 /* GLUtils.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = GLUtils.mm; sourceTree = "<group>"; };
 		196874280C2BE12E00D63879 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = /System/Library/Frameworks/OpenGL.framework; sourceTree = "<absolute>"; };
@@ -719,21 +701,13 @@
 		19BEF4060C2291CE00275D06 /* colour.vowels2.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = colour.vowels2.xml; sourceTree = "<group>"; };
 		19BEF4070C2291CE00275D06 /* colour.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = colour.xml; sourceTree = "<group>"; };
 		19BEF4080C2291CE00275D06 /* colour.xsl */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = colour.xsl; sourceTree = "<group>"; };
-		19C190A90C3267D700979F34 /* DasherViewAqua.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DasherViewAqua.h; sourceTree = "<group>"; };
-		19C190AA0C3267D700979F34 /* DasherViewAqua.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = DasherViewAqua.mm; sourceTree = "<group>"; };
 		19C1AE810B130F18005C68D3 /* COSXMouseInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = COSXMouseInput.h; sourceTree = "<group>"; };
 		19C49619045029A40000000A /* DasherApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DasherApp.h; sourceTree = "<group>"; };
 		19C4961D045029D70000000A /* DasherViewOpenGL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DasherViewOpenGL.h; sourceTree = "<group>"; };
 		19D4423A0454667F0000000A /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Preferences.nib; sourceTree = "<group>"; };
 		19D4423C04546C410000000A /* PreferencesController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PreferencesController.h; sourceTree = "<group>"; };
 		19D4423D04546C410000000A /* PreferencesController.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = PreferencesController.mm; sourceTree = "<group>"; };
-		19E1AE4D0B0DB73300F3466C /* COSXDasherScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = COSXDasherScreen.h; sourceTree = "<group>"; };
-		19E1AE4E0B0DB73300F3466C /* COSXDasherScreen.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = COSXDasherScreen.mm; sourceTree = "<group>"; };
 		19EEDB310450E75F0000000A /* DasherApp.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = DasherApp.mm; sourceTree = "<group>"; };
-		19F36D8A0B18B60E002F41F1 /* ZippyStringImage.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ZippyStringImage.h; sourceTree = "<group>"; };
-		19F36D8B0B18B60E002F41F1 /* ZippyStringImage.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ZippyStringImage.m; sourceTree = "<group>"; };
-		19F36D8C0B18B60E002F41F1 /* ZippyStringGlyph.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ZippyStringGlyph.h; sourceTree = "<group>"; };
-		19F36D8D0B18B60E002F41F1 /* ZippyStringGlyph.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ZippyStringGlyph.m; sourceTree = "<group>"; };
 		19F8C7E50C858A2800276B4F /* I18n.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = I18n.h; path = ../Common/I18n.h; sourceTree = SOURCE_ROOT; };
 		29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
 		29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
@@ -830,21 +804,14 @@
 				191180EA0B0FC91A001CB987 /* COSXSettingsStore.mm */,
 				191428E40B0E6CC6004F1C28 /* COSXDasherControl.h */,
 				191428E50B0E6CC6004F1C28 /* COSXDasherControl.mm */,
-				19E1AE4D0B0DB73300F3466C /* COSXDasherScreen.h */,
-				19E1AE4E0B0DB73300F3466C /* COSXDasherScreen.mm */,
 				19B57A74080D4E4900BCE3C6 /* AppWatcher.h */,
 				19B57A72080D4E4000BCE3C6 /* AppWatcher.m */,
 				19C49619045029A40000000A /* DasherApp.h */,
 				19EEDB310450E75F0000000A /* DasherApp.mm */,
 				1904CDA5048813400000000A /* DasherEdit.h */,
 				1904CDA6048813400000000A /* DasherEdit.mm */,
-				19558AD50C3182730054A193 /* DasherViewCocoa.h */,
-				19C190A90C3267D700979F34 /* DasherViewAqua.h */,
-				19C190AA0C3267D700979F34 /* DasherViewAqua.mm */,
 				19C4961D045029D70000000A /* DasherViewOpenGL.h */,
 				199DCCD70450B94C0000000A /* DasherViewOpenGL.mm */,
-				196874000C2BDC2E00D63879 /* AlphabetLetter.h */,
-				196874010C2BDC2E00D63879 /* AlphabetLetter.mm */,
 				330B91631292FE700022831C /* ModuleSettingsController.h */,
 				330B91611292FE510022831C /* ModuleSettingsController.mm */,
 				196874040C2BDC2E00D63879 /* GLUtils.h */,
@@ -861,14 +828,6 @@
 				198EC7B307153D6E00474B38 /* UnicharGenerator.m */,
 				196D8784048AA2750000000A /* DasherUtil.h */,
 				196D8785048AA2750000000A /* DasherUtil.mm */,
-				1946CAB90481AD440000000A /* ZippyCache.h */,
-				1946CABA0481AD440000000A /* ZippyCache.m */,
-				1946CABB0481AD440000000A /* ZippyString.h */,
-				1946CABC0481AD440000000A /* ZippyString.m */,
-				19F36D8C0B18B60E002F41F1 /* ZippyStringGlyph.h */,
-				19F36D8D0B18B60E002F41F1 /* ZippyStringGlyph.m */,
-				19F36D8A0B18B60E002F41F1 /* ZippyStringImage.h */,
-				19F36D8B0B18B60E002F41F1 /* ZippyStringImage.m */,
 				339055E61195FBD0001BE240 /* Queue.h */,
 				339055E71195FBD0001BE240 /* Queue.m */,
 			);
@@ -1265,6 +1224,8 @@
 		19F8C7E30C858A1500276B4F /* Common */ = {
 			isa = PBXGroup;
 			children = (
+				196874000C2BDC2E00D63879 /* OpenGLScreen.h */,
+				196874010C2BDC2E00D63879 /* OpenGLScreen.mm */,
 				19F8C7E50C858A2800276B4F /* I18n.h */,
 			);
 			name = Common;
@@ -1376,8 +1337,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				1974FE340714861B00B95DA0 /* DasherApp.h in Headers */,
-				1974FE370714861B00B95DA0 /* ZippyCache.h in Headers */,
-				1974FE380714861B00B95DA0 /* ZippyString.h in Headers */,
 				198EC7B407153D6E00474B38 /* KeyboardEvent.h in Headers */,
 				198EC7B707153D6E00474B38 /* LowLevelKeyboardHandling.h in Headers */,
 				198EC7B807153D6E00474B38 /* UnicharGenerator.h in Headers */,
@@ -1385,13 +1344,10 @@
 				190257FD0B0C981300178CCD /* DasherEdit.h in Headers */,
 				190258010B0C981900178CCD /* DasherViewOpenGL.h in Headers */,
 				190258030B0C981900178CCD /* PreferencesController.h in Headers */,
-				19E1AE4F0B0DB73300F3466C /* COSXDasherScreen.h in Headers */,
 				191428E60B0E6CC6004F1C28 /* COSXDasherControl.h in Headers */,
 				191180EB0B0FC91A001CB987 /* COSXSettingsStore.h in Headers */,
 				19E49DB60B10556200BA5CE8 /* DasherUtil.h in Headers */,
 				19C1AE820B130F18005C68D3 /* COSXMouseInput.h in Headers */,
-				19F36D8E0B18B60E002F41F1 /* ZippyStringImage.h in Headers */,
-				19F36D900B18B60E002F41F1 /* ZippyStringGlyph.h in Headers */,
 				1948BE9E0C226CFD001DFA32 /* ActionButton.h in Headers */,
 				1948BEA30C226CFD001DFA32 /* AlphabetMap.h in Headers */,
 				1948BEA50C226CFD001DFA32 /* AlphIO.h in Headers */,
@@ -1454,10 +1410,8 @@
 				1948BF380C226CFD001DFA32 /* UserLogParam.h in Headers */,
 				1948BF3A0C226CFD001DFA32 /* UserLogTrial.h in Headers */,
 				1948BF3F0C226CFD001DFA32 /* XMLUtil.h in Headers */,
-				196874060C2BDC2E00D63879 /* AlphabetLetter.h in Headers */,
+				196874060C2BDC2E00D63879 /* OpenGLScreen.h in Headers */,
 				1968740A0C2BDC2E00D63879 /* GLUtils.h in Headers */,
-				19558AD60C3182730054A193 /* DasherViewCocoa.h in Headers */,
-				19C190AB0C3267D700979F34 /* DasherViewAqua.h in Headers */,
 				1921DB3A0C7ECAA400E6DAA5 /* OneButtonDynamicFilter.h in Headers */,
 				1921DB3F0C7ECB4A00E6DAA5 /* DasherGameMode.h in Headers */,
 				1921DB6F0C7ECB9900E6DAA5 /* GameScorer.h in Headers */,
@@ -1513,7 +1467,14 @@
 			isa = PBXProject;
 			buildConfigurationList = 198D19FD08965C4800CE3CC9 /* Build configuration list for PBXProject "Dasher" */;
 			compatibilityVersion = "Xcode 2.4";
+			developmentRegion = English;
 			hasScannedForEncodings = 1;
+			knownRegions = (
+				English,
+				Japanese,
+				French,
+				German,
+			);
 			mainGroup = 29B97314FDCFA39411CA2CEA /* MacOSX */;
 			projectDirPath = "";
 			projectRoot = "";
@@ -1738,8 +1699,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				1974FE6B0714861B00B95DA0 /* main.m in Sources */,
-				1974FE6F0714861B00B95DA0 /* ZippyCache.m in Sources */,
-				1974FE700714861B00B95DA0 /* ZippyString.m in Sources */,
 				198EC7B507153D6E00474B38 /* KeyboardEvent.m in Sources */,
 				198EC7B607153D6E00474B38 /* LowLevelKeyboardHandling.c in Sources */,
 				198EC7B907153D6E00474B38 /* UnicharGenerator.m in Sources */,
@@ -1747,13 +1706,10 @@
 				190257FC0B0C980800178CCD /* DasherApp.mm in Sources */,
 				190257FE0B0C981400178CCD /* DasherEdit.mm in Sources */,
 				190258040B0C981A00178CCD /* PreferencesController.mm in Sources */,
-				19E1AE500B0DB73300F3466C /* COSXDasherScreen.mm in Sources */,
 				191427A80B0E0C68004F1C28 /* DasherViewOpenGL.mm in Sources */,
 				191428E70B0E6CC6004F1C28 /* COSXDasherControl.mm in Sources */,
 				191180EC0B0FC91A001CB987 /* COSXSettingsStore.mm in Sources */,
 				19E49DB50B10556100BA5CE8 /* DasherUtil.mm in Sources */,
-				19F36D8F0B18B60E002F41F1 /* ZippyStringImage.m in Sources */,
-				19F36D910B18B60E002F41F1 /* ZippyStringGlyph.m in Sources */,
 				1948BE9D0C226CFD001DFA32 /* ActionButton.cpp in Sources */,
 				1948BEA20C226CFD001DFA32 /* AlphabetMap.cpp in Sources */,
 				1948BEA40C226CFD001DFA32 /* AlphIO.cpp in Sources */,
@@ -1802,9 +1758,8 @@
 				1948BF370C226CFD001DFA32 /* UserLogParam.cpp in Sources */,
 				1948BF390C226CFD001DFA32 /* UserLogTrial.cpp in Sources */,
 				1948BF3E0C226CFD001DFA32 /* XMLUtil.cpp in Sources */,
-				196874070C2BDC2E00D63879 /* AlphabetLetter.mm in Sources */,
+				196874070C2BDC2E00D63879 /* OpenGLScreen.mm in Sources */,
 				1968740B0C2BDC2E00D63879 /* GLUtils.mm in Sources */,
-				19C190AC0C3267D700979F34 /* DasherViewAqua.mm in Sources */,
 				1921DB390C7ECAA400E6DAA5 /* OneButtonDynamicFilter.cpp in Sources */,
 				1921DB400C7ECB4B00E6DAA5 /* DasherGameMode.cpp in Sources */,
 				1921DB6E0C7ECB9900E6DAA5 /* GameScorer.cpp in Sources */,
diff --git a/Src/MacOSX/DasherApp.h b/Src/MacOSX/DasherApp.h
index c120c68..8ebc205 100644
--- a/Src/MacOSX/DasherApp.h
+++ b/Src/MacOSX/DasherApp.h
@@ -11,9 +11,7 @@
 #import <AppKit/AppKit.h>
 
 #import "COSXDasherControl.h"
-//#import "DasherAppInterface.h"
-#import "COSXDasherScreen.h"
-#import "DasherViewCocoa.h"
+#import "DasherViewOpenGL.h"
 #import "Queue.h"
 
 @class AppWatcher;
@@ -24,7 +22,7 @@
   IBOutlet NSPanel *dasherPanelUI;
   
   COSXDasherControl *aquaDasherControl;
-  id <DasherViewCocoa>dasherView;
+  DasherViewOpenGL *dasherView;
   
   IBOutlet AppWatcher *appWatcher;
   NSTimer *_timer;
@@ -34,7 +32,7 @@
 
 - (void)start;
 - (void)redraw;
-- (void)changeScreen:(COSXDasherScreen *)aScreen;
+- (void)changeScreen:(CDasherScreen *)aScreen;
 - (void)pause;
 - (void)unpause:(unsigned long int)time;
 - (NSDictionary *)parameterDictionary;
@@ -50,8 +48,6 @@
 - (void)windowWillClose:(NSNotification *)aNotification;
 - (COSXDasherControl *)aquaDasherControl;
 - (void)setAquaDasherControl:(COSXDasherControl *)value;
-- (id <DasherViewCocoa>)dasherView;
-- (void)setDasherView:(id <DasherViewCocoa>)value;
 - (void)startTimer;
 - (void)shutdownTimer;
 - (NSTimer *)timer;
@@ -61,4 +57,6 @@
 - (bool)supportsSpeech;
 - (void)speak:(NSString *)sText interrupt:(bool)bInt;
 - (void)copyToClipboard:(NSString *)sText;
+
+ property (assign) DasherViewOpenGL *dasherView;
 @end
diff --git a/Src/MacOSX/DasherApp.mm b/Src/MacOSX/DasherApp.mm
index 2aed2cf..925c7da 100644
--- a/Src/MacOSX/DasherApp.mm
+++ b/Src/MacOSX/DasherApp.mm
@@ -10,9 +10,7 @@
 #import "PreferencesController.h"
 #import "DasherEdit.h"
 #import "DasherUtil.h"
-#import "DasherViewCocoa.h"
 #import "AppWatcher.h"
-#import "COSXDasherScreen.h"
 
 /*
  * Created by Doug Dickinson (dasher AT DressTheMonkey DOT plus DOT com), 18 April 2003
@@ -23,6 +21,8 @@
 
 @implementation DasherApp
 
+ synthesize dasherView;
+
 - (void)start {
 //  aquaDasherControl->Start();
 }
@@ -31,7 +31,7 @@
   aquaDasherControl->ScheduleRedraw();
 }
 
-- (void)changeScreen:(COSXDasherScreen *)aScreen {
+- (void)changeScreen:(CDasherScreen *)aScreen {
   aquaDasherControl->ChangeScreen( aScreen );
 }
 
@@ -87,44 +87,36 @@
   return self;
 }
 
-- (IBAction)importTrainingText:(id)sender
-{
+- (IBAction)importTrainingText:(id)sender {
   NSOpenPanel *op = [NSOpenPanel openPanel];
 
   int returnCode = [op runModalForDirectory:nil file:nil types:nil];
 
-  if (returnCode == NSOKButton)
-    {
+  if (returnCode == NSOKButton) {
     aquaDasherControl->Train([op filename]);
     NSBeep();
-    }
+  }
 }
 
-- (IBAction)showPreferences:(id)sender
-{
+- (IBAction)showPreferences:(id)sender {
   [[PreferencesController preferencesController] makeKeyAndOrderFront:sender];
 }
 
-- (void)setPanelAlphaValue:(float)anAlphaValue
-{
+- (void)setPanelAlphaValue:(float)anAlphaValue {
   [dasherPanelUI setAlphaValue:anAlphaValue];
 }
 
 - (void)finishRealization {
-  
   aquaDasherControl->Realize2();
-  [dasherView finishRealization];
-  
 }
 
-- (void)awakeFromNib
-{
+- (void)awakeFromNib {
   [dasherPanelUI setBecomesKeyOnlyIfNeeded:YES];
   [dasherPanelUI setFloatingPanel:YES];
-  
+
   // TODO leave out until defaults works properly
 //  [self setPanelAlphaValue:[[NSUserDefaults standardUserDefaults] floatForKey:DASHER_PANEL_OPACITY]];
-  
+
   // not sure if this is the right place for this.  if we ever have a local/remote switch (to control typing into self or others, it should probably be turned off/on there)
   // not sure if this is the right call; is there an equivalent in the AXUI API?  I can't find it...
   // default value for seconds is 0.25, which makes the app miss eg mouse presses sometimes
@@ -144,17 +136,9 @@
 }
 
 - (void)setAquaDasherControl:(COSXDasherControl *)value {
-    aquaDasherControl = value;
+  aquaDasherControl = value;
 }
 
-- (id <DasherViewCocoa>)dasherView {
-  return dasherView;
-}
-
-- (void)setDasherView:(id <DasherViewCocoa>)value {
-    dasherView = value;
-  }
-
 - (void)startTimer {
 #define FPS 40.0f
   
diff --git a/Src/MacOSX/DasherViewOpenGL.h b/Src/MacOSX/DasherViewOpenGL.h
index e038c16..6258ebd 100755
--- a/Src/MacOSX/DasherViewOpenGL.h
+++ b/Src/MacOSX/DasherViewOpenGL.h
@@ -7,25 +7,17 @@
 //
 
 
-
+#import <OpenGL/gl.h>
 #import <AppKit/NSOpenGLView.h>
 #import <AppKit/NSNibDeclarations.h>
-#import "COSXDasherScreen.h"
-#import "AlphabetLetter.h"
 #import "ColourIO.h"
-#import "DasherViewCocoa.h"
 #import "KeyboardHelper.h"
 
- class NSColor, NSTimer, NSTextField, NSString, NSMutableDictionary;
- class NSBezierPath;
+ class NSString;
 @class DasherApp;
+class COSXDasherScreen;
 
-typedef struct {
-  float r, g, b;
-} colour_t;
-
-
- interface DasherViewOpenGL : NSOpenGLView <DasherViewCocoa> {
+ interface DasherViewOpenGL : NSOpenGLView {
   
   COSXDasherScreen *aquaDasherScreen;
   
@@ -33,32 +25,18 @@ typedef struct {
   
   NSTrackingRectTag trackingRectTag;
 
-  NSString *_cachedFontName;
-
-  NSMutableDictionary *_textAttributeCache;
-
-  NSArray *_colourScheme;
+  NSString *cachedFontName;
 
   GLuint frameBuffers[2];
   GLuint textures[2];
-  GLfloat texcoords[8];
+  GLfloat tc_x,tc_y;
   
   ///current (last successful) framebuffer width & height
 	int fw,fh;
   
-  colour_t *colourTable;
-  
-  NSMutableDictionary *_letterDict;
   CKeyboardHelper *_keyboardHelper;
-  
-  ///Caches for circleCallbackWithCentrePoint:... (see therein)
-  float circ_rad;
-  GLshort *circ_coords;
-  int circPoints;  
 }
 
-- (void)sendMarker:(int)iMarker;
-- (void)displayCallback;
 - (void)drawRect:(NSRect)rect;
 - (void)mouseEntered:(NSEvent *)theEvent;
 - (void)mouseExited:(NSEvent *)theEvent;
@@ -66,38 +44,19 @@ typedef struct {
 - (void)mouseUp:(NSEvent *)e;
 - (void)keyDown:(NSEvent *)e;
 - (void)keyUp:(NSEvent *)e;
-- (void)circleCallbackCentrePoint:(NSPoint)aCentrePoint radius:(float)aRadius fillColourIndex:(int)aFillColourIndex outlineColorIndex:(int)anOutlineColorIndex lineWidth:(int)aLineWidth;
-- (void)rectangleCallbackX1:(int)x1 y1:(int)y1 x2:(int)x2 y2:(int)y2 fillColorIndex:(int)aFillColorIndex outlineColorIndex:(int)anOutlineColorIndex lineWidth:(int)aLineWidth;
-- (AlphabetLetter *)letterForString:(NSString *)aString;
-- (NSSize)textSizeCallbackWithString:(NSString *)aString size:(int)aSize;
-- (void)drawTextCallbackWithString:(NSString *)aString x1:(int)x1 y1:(int)y1 size:(int)aSize colorIndex:(int)aColorIndex;
-- (void)colourSchemeCallbackWithColourTable:(colour_t *)aColourTable;
-- (void)polygonCallbackPoints:(NSArray *)points fillColorIndex:(int)fColorIndex outlineColorIndex:(int)iColorIndex lineWidth:(int)aWidth;
-- (void)polylineCallbackPoints:(NSArray *)points width:(int)aWidth colorIndex:(int)aColorIndex;
 - (id)initWithFrame:(NSRect)frame;
 - (void)userDefaultsDidChange:(NSNotification *)aNote;
 - (void)adjustTrackingRect;
 - (BOOL)isFlipped;
 - (BOOL)isOpaque;
 - (void)awakeFromNib;
-- (void)finishRealization;
-- (COSXDasherScreen *)aquaDasherScreen;
 - (BOOL)acceptsFirstResponder;
-- (NSArray *)colourScheme;
-- (void)setColourScheme:(NSArray *)newColourScheme;
-- (void)setColourSchemeFromColourInfo:(const CColourIO::ColourInfo *)pColourScheme;
-- (NSString *)cachedFontName;
-- (void)setCachedFontName:(NSString *)newCachedFontName;
 - (void)dealloc;
 - (void)gl_init;
 - (void)reshape;
 - (void) gl_reshape:(int)w :(int)h;
-- (void)flushCaches;
-
+- (void)redisplay;
 - (NSPoint)mouseLocation;
-- (float)boundsWidth;
-- (float)boundsHeight;
-
 
+ property (retain) NSString *cachedFontName;
 @end
-
diff --git a/Src/MacOSX/DasherViewOpenGL.mm b/Src/MacOSX/DasherViewOpenGL.mm
index df17a93..9a74852 100755
--- a/Src/MacOSX/DasherViewOpenGL.mm
+++ b/Src/MacOSX/DasherViewOpenGL.mm
@@ -18,11 +18,65 @@
 #import "DasherApp.h"
 #import "GLUtils.h"
 
-#import "COSXDasherScreen.h"
+#import "OpenGLScreen.h"
 
-#define MAX_CACHE_COUNT 1000
+ interface DasherViewOpenGL (private_methods)
+-(void)sendMarker:(int)iMarker;
+ end
+
+class COSXDasherScreen : public OpenGLScreen {
+  DasherViewOpenGL *dasherView;
+  NSDictionary *fontAttrs;
+public:
+  COSXDasherScreen(DasherViewOpenGL *_dasherView,screenint iWidth, screenint iHeight, GLfloat tc_x, GLfloat tc_y, GLuint *textures)
+  : OpenGLScreen(iWidth, iHeight, iWidth, iHeight,tc_x,tc_y,textures), dasherView(_dasherView), fontAttrs(nil) {
+    RegenerateLabels(); //no actual labels, so just initialize fontAttrs
+  }
+  
+  ~COSXDasherScreen() {
+    [fontAttrs release];
+  }
+  
+  void SendMarker(int iMarker) {
+    [dasherView sendMarker:iMarker];    
+  }
+  
+  void Display() {
+    OpenGLScreen::Display();
+    [[dasherView openGLContext] flushBuffer];
+  }
+  
+  void resize(screenint w, screenint h,GLfloat tc_x,GLfloat tc_y) {
+    OpenGLScreen::resize(w,h, w,h, tc_x,tc_y);
+  }
+  
+  void RegenerateLabels() {
+    [fontAttrs release];
+    //white text on (default) transparent background means that when we texture
+    //a surface using a colour, the text appears in that colour...
+    fontAttrs = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:dasherView.cachedFontName size:36.0],NSFontAttributeName,[NSColor whiteColor],NSForegroundColorAttributeName,nil];
+    //dictionaryWith...: does an autorelease - only "alloc" methods do not.
+    // But we want to keep the fontAttrs indefinitely...
+    [fontAttrs retain];
+    OpenGLScreen::RegenerateLabels();
+  }
+  
+protected:
+  void RenderStringOntoCGContext(NSString *string, CGContextRef context) {
+    NSGraphicsContext *old = [NSGraphicsContext currentContext];
+    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:YES]];
+    
+    [string drawAtPoint:NSMakePoint(0.0, 0.0) withAttributes:fontAttrs];
+    [NSGraphicsContext setCurrentContext:old];  
+  }
+  
+  CGSize TextSize(NSString *str, unsigned int iFontSize) {
+    return NSSizeToCGSize([str sizeWithAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:dasherView.cachedFontName size:iFontSize] forKey:NSFontAttributeName]]);    
+  }
+};
 
 @implementation DasherViewOpenGL
+ synthesize cachedFontName;
 
 /*
  BEWARE!
@@ -31,12 +85,10 @@
 
 - (void)sendMarker:(int)iMarker {
   [[self openGLContext] makeCurrentContext];
-  if (iMarker == -1)
-  {
+  glFlush();
+  if (iMarker == -1) {
 	  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
-  }
-  else
-  {
+  } else {
 	  //NSLog(@"SendMarker %i\n",iMarker);
 	  glDisable(GL_TEXTURE_2D);
 	  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBuffers[iMarker]);
@@ -49,26 +101,6 @@
   glClear(GL_COLOR_BUFFER_BIT);
 }
 
-- (void)displayCallback
-{
-  [self sendMarker:-1];
-  glEnable(GL_TEXTURE_2D);
-  glColor4f(1.0, 1.0, 1.0, 1.0);
-  NSSize r = [self bounds].size;
-  glEnableClientState(GL_VERTEX_ARRAY);
-  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-  GLshort coords[] = {0,0, r.width,0, 0,r.height, r.width,r.height};
-  glVertexPointer(2, GL_SHORT, 0, coords);
-  glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
-  
-  for (int i=0; i<2; i++)
-  {
-    glBindTexture(GL_TEXTURE_2D, textures[i]);
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-  }
-  glFlush();
-}
-
 - (void)drawRect:(NSRect)rect {
   //if (![dasherApp aquaDasherControl]->GetBoolParameter(BP_DASHER_PAUSED)) {
     // (ACL) want to redraw even if paused, to get mouse line etc. So invoke
@@ -81,8 +113,6 @@
   
 }
 
-
-
 - (void)mouseEntered:(NSEvent *)theEvent
 {
 }
@@ -91,23 +121,20 @@
 {
 }
 
-- (void)mouseDown:(NSEvent *)e
-{
+- (void)mouseDown:(NSEvent *)e {
   NSPoint q = [self convertPoint:[e locationInWindow] fromView:nil];
     
   [dasherApp aquaDasherControl]->HandleClickDown(get_time(), q.x, q.y);
 }
 
-- (void)mouseUp:(NSEvent *)e
-{
+- (void)mouseUp:(NSEvent *)e {
   NSPoint q = [self convertPoint:[e locationInWindow] fromView:nil];
   
   [dasherApp aquaDasherControl]->HandleClickUp(get_time(), q.x, q.y);
 }
 
 
-- (void)keyDown:(NSEvent *)e
-{
+- (void)keyDown:(NSEvent *)e {
   /*TODO, note that this isn't really "key down", rather it's "character entered"
 	or similar - if the key is held down long enough to repeat, we get multiple keyDowns
    before a keyUp, and we just send them all along to the DasherCore code...*/
@@ -119,8 +146,7 @@
     [dasherApp aquaDasherControl]->KeyDown(get_time(), keyCode);
 }
 
-- (void)keyUp:(NSEvent *)e
-{
+- (void)keyUp:(NSEvent *)e {
   NSString *chars = [e characters];
   if ([chars length] > 1)
     NSLog(@"KeyUp event for %i chars %@ - what to do? Ignoring all but first...\n", [chars length], chars);
@@ -129,180 +155,6 @@
     [dasherApp aquaDasherControl]->KeyUp(get_time(), keyCode);
 }
 
-- (void)circleCallbackCentrePoint:(NSPoint)aCentrePoint radius:(float)aRadius fillColourIndex:(int)aFillColourIndex outlineColorIndex:(int)anOutlineColorIndex lineWidth:(int)aLineWidth {
-  
-  //it's a bit of a hack, but we cache the last-computed set of points round the
-  // as these are the same for all calls with the same radius - and (the hack!) 
-  // that the radius tends to be the same every time (as the only call to CDashe
-  // is from CircleStartHandler!)...
-  if (circ_rad != aRadius) {
-    delete circ_coords;
-    double costh=1.0f - 1.0f/(2.0f*aRadius);
-    double th = acos(costh);
-    int numPoints = circPoints = ceil(M_PI/th/2.0f); //for a quarter-circle
-    double sinth = sin(th),x(aRadius),y(0.0);
-    circ_coords = new GLshort[numPoints*8]; circ_rad = aRadius;
-    circ_coords[0] = x; circ_coords[1] = y;
-    for (int i=1; i<numPoints; i++) {
-      double nx = x*costh - y*sinth;
-      double ny = x*sinth + y*costh;
-      circ_coords[2*i] = nx;
-      circ_coords[2*i+1] = ny;
-      x=nx; y=ny;
-    }
-    for (int i=0; i<numPoints; i++) {
-      circ_coords[2*(i+numPoints)] = -circ_coords[2*i+1];
-      circ_coords[2*(i+numPoints)+1] = circ_coords[2*i];
-      
-      circ_coords[2*(i+numPoints*2)] = -circ_coords[2*i];
-      circ_coords[2*(i+numPoints*2)+1] = -circ_coords[2*i+1];
-      
-      circ_coords[2*(i+numPoints*3)] = circ_coords[2*i+1];
-      circ_coords[2*(i+numPoints*3)+1] = -circ_coords[2*i];
-    }
-  }
-  
-  glDisable(GL_TEXTURE_2D);
-  glEnableClientState(GL_VERTEX_ARRAY);
-  glTranslatef(aCentrePoint.x, aCentrePoint.y, 0.0);
-  if (aFillColourIndex!=-1) {
-    glColor4f(colourTable[aFillColourIndex].r, colourTable[aFillColourIndex].g, colourTable[aFillColourIndex].b, 1.0f);
-    glVertexPointer(2, GL_SHORT, 0, circ_coords);
-    glDrawArrays(GL_TRIANGLE_FAN, 0, circPoints*4);
-  }
-  if (aLineWidth>0) {
-    int oci = anOutlineColorIndex == -1 ? 3 : anOutlineColorIndex;
-    glColor4f(colourTable[oci].r, colourTable[oci].g, colourTable[oci].b, 1.0f);
-    glLineWidth(aLineWidth);
-    glVertexPointer(2, GL_SHORT, 0, circ_coords);
-    glDrawArrays(GL_LINE_LOOP, 0, circPoints*4);
-  }
-  glTranslatef(-aCentrePoint.x, -aCentrePoint.y, 0.0);
-}
-
-
-- (void)rectangleCallbackX1:(int)x1 y1:(int)y1 x2:(int)x2 y2:(int)y2 fillColorIndex:(int)aFillColorIndex outlineColorIndex:(int)oci lineWidth:(int)aLineWidth {
-  
-  // don't know if this is needed with opengl...does it cope with wonky coords?
-  //  float x, y, width, height;
-  //  
-  //  if( x2 > x1 ) {
-  //    x = x1;
-  //    width = x2 - x1;
-  //  }
-  //  else {
-  //    x = x2;
-  //    width = x1 - x2;
-  //  }
-  //  
-  //  if( y2 > y1 ) {
-  //    y = y1;
-  //    height = y2 - y1;
-  //  }
-  //  else {
-  //    y = y2;
-  //    height = y1 - y2;
-  //  }
-  glDisable(GL_TEXTURE_2D);
-  glEnableClientState(GL_VERTEX_ARRAY);
-  if (aFillColorIndex!=-1) {
-    glColor4f(colourTable[aFillColorIndex].r, colourTable[aFillColorIndex].g, colourTable[aFillColorIndex].b, 1.0);
-    GLshort coords[8] = {x1,y1, x2,y1, x1,y2, x2,y2};
-    glVertexPointer(2, GL_SHORT, 0, coords);
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-  }
-  
-  if (aLineWidth>0) {
-    glColor4f(colourTable[oci].r, colourTable[oci].g, colourTable[oci].b, 1.0);
-    glLineWidth(aLineWidth);
-    GLshort coords[] = {x1,y1, x2,y1, x2,y2, x1,y2};
-    glVertexPointer(2, GL_SHORT, 0, coords);
-    glDrawArrays(GL_LINE_LOOP, 0, 4);
-  }
-  
-  
-}
-
-
-- (AlphabetLetter *)letterForString:(NSString *)aString {
-  AlphabetLetter *result = [_letterDict objectForKey:aString];
-  if (result == nil) {
-    result = [[AlphabetLetter alloc] initWithString:aString];
-    [_letterDict setObject:result forKey:aString];
-  }
-  return result;
-}
-
-- (NSSize)textSizeCallbackWithString:(NSString *)aString size:(int)aSize
-{
-  return [[self letterForString:aString] sizeWithSize:aSize];
-}
-
-
-- (void)drawTextCallbackWithString:(NSString *)aString x1:(int)x1 y1:(int)y1 size:(int)aSize colorIndex:(int)aColorIndex
-{
-  AlphabetLetter *letter = [self letterForString:aString];
-  glEnable(GL_TEXTURE_2D);
-  // TODO could pass the whole colour_t in and let it deal with splitting out the items
-  [letter drawWithSize:/*1.0*/ aSize x:x1 y:y1 r:colourTable[aColorIndex].r g:colourTable[aColorIndex].g b:colourTable[aColorIndex].b];
-  glDisable(GL_TEXTURE_2D);
-}
-
-- (void)colourSchemeCallbackWithColourTable:(colour_t *)aColourTable {
-  
-  if (colourTable != NULL) {
-    free(colourTable);
-  }
-  
-  colourTable = aColourTable;
-}
-
-- (void)polygonCallbackPoints:(NSArray *)points fillColorIndex:(int)fColorIndex outlineColorIndex:(int)iColorIndex lineWidth:(int)iWidth {
-  int len = [points count];
-	if (len < 2) return;
-  //1. fill...
-	glDisable(GL_TEXTURE_2D);
-  if (fColorIndex != -1) {
-    glColor3f(colourTable[fColorIndex].r, colourTable[fColorIndex].g, colourTable[fColorIndex].b);
-	  glBegin(GL_TRIANGLE_FAN);
-    for (int i = 0; i < len; i++)
-    {
-      NSPoint nsp = [[points objectAtIndex:i] pointValue];
-      glVertex2i(nsp.x,nsp.y);
-    }
-    glEnd();
-  }
-  if (iWidth>0) {
-    glColor3f(colourTable[iColorIndex].r,colourTable[iColorIndex].g,colourTable[iColorIndex].b);
-    glLineWidth(iWidth);
-    glBegin(GL_LINE_LOOP);
-    for (int i = 0; i < len; i++)
-    {
-      NSPoint nsp = [[points objectAtIndex:i] pointValue];
-      glVertex2i(nsp.x,nsp.y);
-    }
-    glEnd();
-  }
-}
-
-- (void)polylineCallbackPoints:(NSArray *)points width:(int)aWidth colorIndex:(int)aColorIndex
-{
-	int len = [points count];
-	if (len < 2) return;
-	glDisable(GL_TEXTURE_2D);
-	glColor3f(colourTable[aColorIndex].r, colourTable[aColorIndex].g, colourTable[aColorIndex].b);
-	glLineWidth(aWidth);
-	glBegin(GL_LINE_STRIP);
-	for (int i = 0; i < len; i++)
-	{
-		NSPoint nsp = [[points objectAtIndex:i] pointValue];
-		glVertex2i(nsp.x,nsp.y);
-	}
-	glEnd();
-}
-
-
-
 - (id)initWithFrame:(NSRect)aFrame {
   NSOpenGLPixelFormatAttribute attrs[] =
   {
@@ -321,7 +173,6 @@
   if (self = [super initWithFrame:aFrame pixelFormat:pixFmt]) {
         // init open gl
     [self gl_init];
-    [self flushCaches];
     [self setFrameSize:aFrame.size];
     //note these give us framebuffer _references_...
     glGenFramebuffersEXT(2, frameBuffers);
@@ -333,126 +184,61 @@
     glClear(GL_COLOR_BUFFER_BIT);
 	  
     _keyboardHelper = new CKeyboardHelper();
-    circ_rad=-1.0f;
   }
   return self;
 }
 
 - (void)userDefaultsDidChange:(NSNotification *)aNote {
-  if (![[[NSUserDefaults standardUserDefaults] objectForKey:@"DasherFont"] isEqualToString:[self cachedFontName]]) {
-    [self flushCaches];
-    [dasherApp aquaDasherControl]->ScheduleRedraw();
+  //TODO also flush caches (i.e. glyphs) if alphabet changed?
+  NSString *newFont=[[NSUserDefaults standardUserDefaults] objectForKey:@"DasherFont"];
+  if (![newFont isEqualToString:self.cachedFontName]) {
+    self.cachedFontName=newFont;
+    aquaDasherScreen->RegenerateLabels();
+    [dasherApp redraw];
   }
 }
 
-- (void)flushCaches {
-  [_letterDict release];
-  _letterDict = [[NSMutableDictionary alloc] init];
-  [self setCachedFontName:[[NSUserDefaults standardUserDefaults] objectForKey:@"DasherFont"]];
-}  
-
-- (void)adjustTrackingRect
-{
+- (void)adjustTrackingRect {
 // need this in order to get mouseEntered and Exited
 //  [self removeTrackingRect:trackingRectTag];
 //  trackingRectTag = [self addTrackingRect:[self frame] owner:self userData:nil assumeInside:[self isDashing]];
 }
 
-- (BOOL)isFlipped
-{
+- (BOOL)isFlipped {
   return YES;
 //  return NO;
 }
 
-- (BOOL)isOpaque
-{
+- (BOOL)isOpaque {
   return YES;
 }
 
-- (void)awakeFromNib
-{
+- (void)awakeFromNib {
 //  aquaDasherScreen = new COSXDasherScreen(self);
   [dasherApp setDasherView:self];
-
-  [self adjustTrackingRect];
-  
-  [self flushCaches];
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:nil];
-}
+  self.cachedFontName=[[NSUserDefaults standardUserDefaults] objectForKey:@"DasherFont"];
 
-- (void)finishRealization {
+  NSSize sz = [self bounds].size;
+  aquaDasherScreen = new COSXDasherScreen(self, sz.width, sz.height, tc_x, tc_y, textures);
   [dasherApp changeScreen:aquaDasherScreen];
-}
-
-- (COSXDasherScreen *)aquaDasherScreen {
-  return aquaDasherScreen;
-}
-
-- (BOOL)acceptsFirstResponder
-{
-  return YES;
-}
-
-
-- (NSArray *)colourScheme {
-  return [[_colourScheme retain] autorelease];
-}
-
-- (void)setColourScheme:(NSArray *)newColourScheme {
-  if (_colourScheme != newColourScheme) {
-    [_colourScheme release];
-    _colourScheme = [newColourScheme retain];
-  }
-}
-
-- (void)setColourSchemeFromColourInfo:(const CColourIO::ColourInfo *)pColourScheme {
-  
-  
-  int iNumColours = pColourScheme->Reds.size();
   
-  colour_t *ct = (colour_t *)malloc(iNumColours * sizeof(colour_t));
-  
-  for(int i = 0; i < iNumColours; i++) {
-    ct[i].r = pColourScheme->Reds[i] / 255.0;
-    ct[i].g = pColourScheme->Greens[i] / 255.0;
-    ct[i].b = pColourScheme->Blues[i] / 255.0;
-  }
-  
-  // colour table must be freed elsewhere...
-  [self colourSchemeCallbackWithColourTable:ct];
-}
-
-- (NSString *)cachedFontName {
-  return _cachedFontName;
+  [self adjustTrackingRect];
 }
 
-- (void)setCachedFontName:(NSString *)newCachedFontName {
-  if (_cachedFontName != newCachedFontName) {
-    NSString *oldValue = _cachedFontName;
-    _cachedFontName = [newCachedFontName retain];
-    [oldValue release];
-  }
+- (BOOL)acceptsFirstResponder {
+  return YES;
 }
 
-- (void)dealloc
-{
-  [_cachedFontName release];
-  [_textAttributeCache release];
+- (void)dealloc {
+  self.cachedFontName=nil;
   
   [[self openGLContext] makeCurrentContext];
-  
-  if (colourTable != NULL) {
-    free(colourTable);
-  }
-  
-  [_letterDict release];
-  
+    
   [super dealloc];
 }
 
-
-- (void)gl_init
-{
+- (void)gl_init {
   [[self openGLContext] makeCurrentContext];
   //glClearColor(1.0, 1.0, 1.0, 1.0);
   glShadeModel(GL_FLAT);
@@ -463,12 +249,12 @@
 }
 
 - (void)reshape {
-  [self gl_reshape:[self bounds].size.width :[self bounds].size.height];
-  
-  delete( aquaDasherScreen );
-  aquaDasherScreen = new COSXDasherScreen(self);
-  [dasherApp changeScreen:aquaDasherScreen];
-  
+  NSSize sz=[self bounds].size;
+  screenint w=sz.width,h=sz.height;
+  [self gl_reshape:w :h];
+  aquaDasherScreen->resize(w, h, tc_x, tc_y);
+  [dasherApp aquaDasherControl]->ScreenResized(aquaDasherScreen);
+    
   [self adjustTrackingRect];  
 }
 
@@ -494,11 +280,8 @@
     }
     fw=tw; fh=th;
   }
-  GLfloat tc_x = w/(double)tw, tc_y = h/(double)th;
-  texcoords[0] = 0.0; texcoords[1] = tc_y;
-  texcoords[2] = tc_x; texcoords[3] = tc_y;
-  texcoords[4] = 0.0; texcoords[5] = 0.0;
-  texcoords[6] = tc_x; texcoords[7] = 0.0;
+  tc_x = w/(double)tw;
+  tc_y = h/(double)th;
   
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
@@ -514,14 +297,6 @@
   return [self convertPoint:p fromView:nil];
 }
 
-- (float)boundsWidth {
-  return [self bounds].size.width;
-}
-
-- (float)boundsHeight {
-  return [self bounds].size.height;
-}
-
 - (void)redisplay {
   [self setNeedsDisplay:YES];
 }
diff --git a/Src/Win32/Common/WinUTF8.cpp b/Src/Win32/Common/WinUTF8.cpp
index 597ac78..085889b 100644
--- a/Src/Win32/Common/WinUTF8.cpp
+++ b/Src/Win32/Common/WinUTF8.cpp
@@ -85,6 +85,12 @@ void WinUTF8::UTF8string_to_wstring(const std::string &UTF8string, std::wstring
 
 }
 
+std::wstring WinUTF8::UTF8string_to_wstring(const std::string &utf8string) {
+  std::wstring res;
+  UTF8string_to_wstring(utf8string,res);
+  return res;
+}
+
 /////////////////////////////////////////////////////////////////////////////
 
 void WinUTF8::wstring_to_UTF8string(const wstring &Input, string &Output) {
diff --git a/Src/Win32/Common/WinUTF8.h b/Src/Win32/Common/WinUTF8.h
index b038215..1c89a9b 100644
--- a/Src/Win32/Common/WinUTF8.h
+++ b/Src/Win32/Common/WinUTF8.h
@@ -23,6 +23,7 @@
 
 namespace WinUTF8 {
   void UTF8string_to_wstring(const std::string & UTF8string, std::wstring & Output);
+  std::wstring UTF8string_to_wstring(const std::string &UTF8string);
   void wstring_to_UTF8string(const std::wstring & Input, std::string & Output);
 }
 #endif                          /* #ifndef __WinUTF8_h__ */
diff --git a/Src/Win32/Widgets/Canvas.cpp b/Src/Win32/Widgets/Canvas.cpp
index d668075..7ce700d 100644
--- a/Src/Win32/Widgets/Canvas.cpp
+++ b/Src/Win32/Widgets/Canvas.cpp
@@ -471,15 +471,9 @@ LRESULT CCanvas::OnMouseMove(UINT message, WPARAM wParam, LPARAM lParam, BOOL& b
 }
 
 LRESULT CCanvas::OnSize(UINT message, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
-	if(m_pScreen != 0) {
-		delete m_pScreen;
-		m_pScreen = 0;
-	}
-
 	if (LOWORD(lParam)>0 && HIWORD(lParam) >0) {
-		m_pScreen = new CScreen(m_hdc, m_hWnd, LOWORD(lParam), HIWORD(lParam));
-    m_pScreen->SetFont(m_pDasherInterface->GetStringParameter(SP_DASHER_FONT));
-		m_pDasherInterface->ChangeScreen(m_pScreen);
+		m_pScreen->resize(LOWORD(lParam), HIWORD(lParam));
+		m_pDasherInterface->ScreenResized(m_pScreen);
 		InvalidateRect( NULL, FALSE);
 	}
 
diff --git a/Src/Win32/Widgets/Screen.cpp b/Src/Win32/Widgets/Screen.cpp
index f3cd6fe..445f0b9 100644
--- a/Src/Win32/Widgets/Screen.cpp
+++ b/Src/Win32/Widgets/Screen.cpp
@@ -30,20 +30,31 @@ static char THIS_FILE[] = __FILE__;
 /////////////////////////////////////////////////////////////////////////////
 
 CScreen::CScreen(HDC hdc, HWND hWnd, Dasher::screenint iWidth, Dasher::screenint iHeight)
-:CDasherScreen(iWidth, iHeight), m_hdc(hdc) {
+:CLabelListScreen(iWidth, iHeight), m_hdc(hdc) {
   // set up the off-screen buffers
   // HDC hdc = GetDC(mainwindow);
 
   m_hWnd = hWnd;
 
+  CreateBuffers();
+
+  CodePage = GetUserCodePage();
+
+//      m_hDCScreen = ::GetDC(m_hwnd);
+//      TCHAR debug[256];
+//      _stprintf(debug, TEXT("GetDC: hwnd %x hdc %x\n"), m_hwnd, m_hDCScreen);
+//      OutputDebugString(debug); 
+}
+
+void CScreen::CreateBuffers() {
   // Create a memory device context compatible with the screen
-  m_hDCBufferBackground = CreateCompatibleDC(hdc);      // one for rectangles
-  m_hbmBitBackground = CreateCompatibleBitmap(hdc, m_iWidth, m_iHeight);
+  m_hDCBufferBackground = CreateCompatibleDC(m_hdc);      // one for rectangles
+  m_hbmBitBackground = CreateCompatibleBitmap(m_hdc, GetWidth(), GetHeight());
   SetBkMode(m_hDCBufferBackground, TRANSPARENT);
   m_prevhbmBitBackground = SelectObject(m_hDCBufferBackground, m_hbmBitBackground);
 
-  m_hDCBufferDecorations = CreateCompatibleDC(hdc);
-  m_hbmBitDecorations = CreateCompatibleBitmap(hdc, m_iWidth, m_iHeight);
+  m_hDCBufferDecorations = CreateCompatibleDC(m_hdc);
+  m_hbmBitDecorations = CreateCompatibleBitmap(m_hdc, GetWidth(), GetHeight());
   SetBkMode(m_hDCBufferDecorations, TRANSPARENT);
   m_prevhbmBitDecorations = SelectObject(m_hDCBufferDecorations, m_hbmBitDecorations);
 
@@ -52,10 +63,13 @@ CScreen::CScreen(HDC hdc, HWND hWnd, Dasher::screenint iWidth, Dasher::screenint
   m_hDCBuffer = m_hDCBufferBackground;
 
 }
-
 /////////////////////////////////////////////////////////////////////////////
 
 CScreen::~CScreen() {
+  DeleteBuffers();
+}
+
+void CScreen::DeleteBuffers() {
   // tidy up
 
   // Select the old bitmaps back into the device contexts (is this really
@@ -89,6 +103,15 @@ void CScreen::SetColourScheme(const CColourIO::ColourInfo *pColours) {
   m_pColours = pColours;
 }
 
+void CScreen::SetFont(const string &strFont) {
+  if(FontName == strFont) return;
+  FontName = strFont;
+  for(stdext::hash_map<int, HFONT>::const_iterator it(m_cFonts.begin()); it != m_cFonts.end(); ++it)
+    DeleteObject(it->second);
+  m_cFonts.clear();
+  for (set<CLabelListScreen::Label*>::iterator it=LabelsBegin(); it!=LabelsEnd(); it++)
+	  static_cast<CScreen::Label*>(*it)->m_sizeCache.clear();
+}
 
 // Handler for redraw markers;
 void CScreen::SendMarker(int iMarker) {
@@ -97,44 +120,27 @@ void CScreen::SendMarker(int iMarker) {
     m_hDCBuffer = m_hDCBufferBackground;
     break;
   case 1:
-    BitBlt(m_hDCBufferDecorations, 0, 0, m_iWidth, m_iHeight, m_hDCBufferBackground, 0, 0, SRCCOPY);
+    BitBlt(m_hDCBufferDecorations, 0, 0, GetWidth(), GetHeight(), m_hDCBufferBackground, 0, 0, SRCCOPY);
     m_hDCBuffer = m_hDCBufferDecorations;
     break;
   }
 }
 
-void CScreen::DrawMousePosBox(int which, int iMousePosDist,int layer) {
-  RECT Rect;
-  HBRUSH brush;
-  switch (which) {
-  case 0:
-    Rect.left = 0;
-    Rect.top = m_iHeight / 2 - iMousePosDist - 50;
-    Rect.right = m_iWidth;
-    Rect.bottom = Rect.top + 100;
-    brush = CreateSolidBrush(RGB(255, 0, 0));
-    break;
-  case 1:
-    Rect.left = 0;
-    Rect.bottom = m_iHeight / 2 + iMousePosDist + 50;
-    Rect.right = m_iWidth;
-    Rect.top = Rect.bottom - 100;
-    brush = CreateSolidBrush(RGB(255, 255, 0));
-    break;
-  default:
-    DASHER_ASSERT(0);
-  }
-  FillRect(m_hDCBuffer, &Rect, brush);
-  DeleteObject(brush);
-  Display();
+void CScreen::resize(screenint width, screenint height) {
+  DeleteBuffers();
+  CLabelListScreen::resize(width,height);
+  CreateBuffers();
 }
 
-void CScreen::DrawString(const std::string &OutputString, Dasher::screenint x1, Dasher::screenint y1, int iSize, int Colour) {
-
-  Tstring OutputText;
+CScreen::Label::Label(CScreen *pScreen, const string &strText) : CLabelListScreen::Label(pScreen, strText), m_OutputText(WinUTF8::UTF8string_to_wstring(m_strText)) {
+}
 
-  WinUTF8::UTF8string_to_wstring(OutputString, OutputText);
+CDasherScreen::Label *CScreen::MakeLabel(const string &strText) {
+  return new Label(this,strText);
+}
 
+void CScreen::DrawString(CDasherScreen::Label *lab, screenint x1, screenint y1, unsigned int iSize, int Colour) {
+  Label *label(static_cast<Label *>(lab));
   RECT Rect;
   Rect.left = x1;
   Rect.top = y1;
@@ -152,54 +158,41 @@ void CScreen::DrawString(const std::string &OutputString, Dasher::screenint x1,
   iCRefOld = SetTextColor(m_hDCBuffer, iCRefNew);
 
   // The Windows API dumps all its function names in the global namespace, ::
-  ::DrawText(m_hDCBuffer, OutputText.c_str(), OutputText.size(), &Rect, DT_LEFT | DT_TOP | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE);
+  ::DrawText(m_hDCBuffer, label->m_OutputText.c_str(), label->m_OutputText.size(), &Rect, DT_LEFT | DT_TOP | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE);
   
   SetTextColor(m_hDCBuffer, iCRefOld);
   SelectObject(m_hDCBuffer, old);
 }
 
-void CScreen::TextSize(const std::string &String, screenint *Width, screenint *Height, int iSize) {
-  CTextSizeInput in;
-  in.m_String = String;
-  in.m_iSize = iSize;
+pair<screenint,screenint> CScreen::TextSize(CDasherScreen::Label *lab, unsigned int iSize) {
+  Label *label(static_cast<Label *>(lab));
 
 //      stdext::hash_map< CTextSizeInput, CTextSizeOutput, hash_textsize>::const_iterator it;
-  std::map < CTextSizeInput, CTextSizeOutput >::const_iterator it;
-  it = m_mapTextSize.find(in);
-
-  if(it == m_mapTextSize.end()) {
-    CTextSizeOutput out;
-    TextSize_Impl(String, &out.m_iWidth, &out.m_iHeight, iSize);
-//              m_mapTextSize.insert( stdext::hash_map< CTextSizeInput, CTextSizeOutput, hash_textsize>::value_type( in,out) );
-    m_mapTextSize.insert(std::map < CTextSizeInput, CTextSizeOutput >::value_type(in, out));
-    *Width = out.m_iWidth;
-    *Height = out.m_iHeight;
-    return;
-  }
+  map<unsigned int,pair<screenint,screenint> >::const_iterator it = label->m_sizeCache.find(iSize);
+  if (it!=label->m_sizeCache.end()) return it->second;
 
-  const CTextSizeOutput & out = it->second;
-  *Width = out.m_iWidth;
-  *Height = out.m_iHeight;
+  pair<screenint,screenint> res = TextSize_Impl(label, iSize);
+  label->m_sizeCache.insert(map<unsigned int,pair<screenint,screenint> >::value_type(iSize,res));
+  return res;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 
-void CScreen::TextSize_Impl(const std::string &String, screenint *Width, screenint *Height, int iSize) {
+pair<screenint,screenint> CScreen::TextSize_Impl(CScreen::Label *label, unsigned int iSize) {
   // TODO This function could be improved. The height of an "o" is returned as the
   // same as the height of an "O". Perhaps GetGlyphOutline could help.
   // Remember if it gets complicted, the height of each symbol could be pre-calculated
 
-  wstring OutputText;
-  WinUTF8::UTF8string_to_wstring(String, OutputText);
+  //wstring OutputText; //ACL assuming wstring is same as Tstring?
+  //WinUTF8::UTF8string_to_wstring(String, OutputText);
 
   HFONT old = (HFONT) SelectObject(m_hDCBuffer, CScreen::GetFont(iSize));
 
   // Get the dimensions of the text in pixels
   SIZE OutSize;
-  GetTextExtentPoint32(m_hDCBuffer, OutputText.c_str(), OutputText.size(), &OutSize);
+  GetTextExtentPoint32(m_hDCBuffer, label->m_OutputText.c_str(), label->m_OutputText.size(), &OutSize);
   SelectObject(m_hDCBuffer, old);
-  *Width = OutSize.cx;
-  *Height = OutSize.cy;
+  return pair<screenint,screenint>(OutSize.cx,OutSize.cy);
 }
 
 /////////////////////////////////////////////////////////////////////////////
diff --git a/Src/Win32/Widgets/Screen.h b/Src/Win32/Widgets/Screen.h
index 14334bf..dd1cf61 100644
--- a/Src/Win32/Widgets/Screen.h
+++ b/Src/Win32/Widgets/Screen.h
@@ -27,7 +27,7 @@
 
 /////////////////////////////////////////////////////////////////////////////
 
-class CScreen:public Dasher::CDasherScreen, private NoClones {
+class CScreen:public Dasher::CLabelListScreen, private NoClones {
 public:
   //Saves a lot of typing; typedefs are equal to their declaration & do not create distinct types.
   typedef Dasher::screenint screenint;
@@ -40,12 +40,13 @@ public:
 
   void DrawMousePosBox(int which, int iMousePosDist,int layer=0);
 
-  void TextSize(const std::string & String, Dasher::screenint * Width, Dasher::screenint * Height, int Size);
+  //! Make label from UTF8-encoded string
+  CDasherScreen::Label *MakeLabel(const std::string &strText);
 
-  //! Draw UTF8-encoded string String of size Size positioned at x1 and y1
-  
-  //void DrawString(const std::string & String, Dasher::screenint x1, Dasher::screenint y1, int Size,int layer=0);
-  void DrawString(const std::string & String, Dasher::screenint x1, Dasher::screenint y1, int Size, int Colour);
+  std::pair<screenint,screenint> TextSize(CDasherScreen::Label *label, unsigned int Size);
+
+  //! Draw label at size Size positioned at x1 and y1
+  void DrawString(CDasherScreen::Label *label, screenint x1, screenint y1, unsigned int Size, int Colour);
 
   void DrawRectangle(Dasher::screenint x1, Dasher::screenint y1, Dasher::screenint x2, Dasher::screenint y2, int Colour, int iOutlineColour, int iThickness);
 
@@ -86,13 +87,14 @@ public:
 
   void SendMarker(int iMarker);
 
+  void resize(screenint w,screenint h);
 private:
   const void point2POINT(const point * In, POINT * Out, int Number);
 
-  void TextSize_Impl(const std::string & String, Dasher::screenint * Width, Dasher::screenint * Height, int Size);
-
   HWND m_hWnd;
 
+  void DeleteBuffers();
+  void CreateBuffers();
   HDC m_hdc;
   HDC m_hDCBuffer;
   HDC m_hDCBufferBackground;
@@ -114,26 +116,14 @@ private:
   stdext::hash_map <int, HFONT> m_cFonts;  // Holds cached font sizes for current font
   std::string FontName; // Shouldn't need to cache, should work on events to reset font cache
 
-  struct CTextSizeInput {
-    std::string m_String;
-    int m_iSize;
-
-    bool operator<(const CTextSizeInput & rhs) const {
-      if(m_iSize < rhs.m_iSize)
-        return true;
-      if(m_iSize > rhs.m_iSize)
-        return false;
-      return m_String < rhs.m_String;
-    } bool operator!=(const CTextSizeInput & rhs)const {
-      return ((m_iSize != rhs.m_iSize) || (m_String != rhs.m_String));
-  }};
-  struct CTextSizeOutput {
-    screenint m_iWidth;
-    screenint m_iHeight;
+  class Label : public CLabelListScreen::Label {
+  public:
+    const Tstring m_OutputText;
+    Label(CScreen *pScreen, const std::string &strText);
+    map<unsigned int,pair<screenint,screenint> > m_sizeCache;
   };
 
-
-  mutable std::map < CTextSizeInput, CTextSizeOutput > m_mapTextSize;
+  std::pair<screenint,screenint> TextSize_Impl(CScreen::Label *label, unsigned int Size);
 };
 
 #include "Screen.inl"
diff --git a/Src/Win32/Widgets/Screen.inl b/Src/Win32/Widgets/Screen.inl
index 85dfffb..927850f 100644
--- a/Src/Win32/Widgets/Screen.inl
+++ b/Src/Win32/Widgets/Screen.inl
@@ -20,7 +20,7 @@ inline void CScreen::DrawRectangle(screenint x1, screenint y1, screenint x2, scr
 
 #ifndef DASHER_WINCE
 
-  if(m_iWidth != -1) {
+  if(GetWidth() != -1) {
 
     point aPoints[5];
 
@@ -77,8 +77,8 @@ inline void CScreen::Polyline(point *Points, int Number, int iWidth, int iColour
 inline void CScreen::Blank() {
   RECT rect;
   rect.top = 0;
-  rect.right = long (m_iWidth);
-  rect.bottom = long (m_iHeight);
+  rect.right = long (GetWidth());
+  rect.bottom = long (GetHeight());
   rect.left = 0;
   FillRect(m_hDCBuffer, &rect, (HBRUSH) GetStockObject(WHITE_BRUSH));
 }
@@ -135,15 +135,6 @@ inline HBRUSH& CScreen::GetBrush(int iColor) {
   return m_cBrushes[key];
 }
 
-inline void CScreen::SetFont(const std::string &strFont) {
-  if(FontName != strFont) {
-    FontName = strFont;
-     for(stdext::hash_map<int, HFONT>::const_iterator it(m_cFonts.begin()); it != m_cFonts.end(); ++it)
-  	   DeleteObject(it->second);
-    m_cFonts.clear();
-  }
-}
-
 inline HFONT& CScreen::GetFont(int iSize) {
   // TODO: Reimplement
   //if(FontName != m_pDasherInterface->GetStringParameter(SP_DASHER_FONT)) {
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.h b/Src/iPhone/Classes/CDasherInterfaceBridge.h
index 42cfd20..a274464 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.h
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.h
@@ -39,7 +39,7 @@ public:
   ~CDasherInterfaceBridge();
   
   //redefinitions to make public....
-  void OnUIRealised();
+  void Realize();//also calls OnUIRealised
   void NewFrame(unsigned long iTime, bool bForceRedraw);
   
   void SetTiltAxes(Vec3 main, float off, Vec3 slow, float off2);
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.mm b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
index fa1a09b..f197cfb 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.mm
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
@@ -27,8 +27,6 @@
 using namespace std;
 
 CDasherInterfaceBridge::CDasherInterfaceBridge(DasherAppDelegate *aDasherApp) : dasherApp(aDasherApp) {
-  Realize();
-  ExternalEventHandler(&CParameterNotificationEvent(SP_ALPHABET_ID)); //calls dasherApp::SetAlphabet
 }
 
 void CDasherInterfaceBridge::CreateModules() {
@@ -69,7 +67,11 @@ void CDasherInterfaceBridge::SetupUI() {
   NSLog(@"CDasherInterfaceBridge::SetupUI");
 }
 
-void CDasherInterfaceBridge::OnUIRealised() {CDasherInterfaceBase::OnUIRealised();}
+void CDasherInterfaceBridge::Realize() {  
+  CDasherInterfaceBase::Realize();
+  ExternalEventHandler(&CParameterNotificationEvent(SP_ALPHABET_ID)); //calls dasherApp::SetAlphabet
+  CDasherInterfaceBase::OnUIRealised();
+}
 
 void CDasherInterfaceBridge::SetupPaths() {
   NSString *systemDir = [NSString stringWithFormat:@"%@/", [[NSBundle mainBundle] bundlePath]];
diff --git a/Src/iPhone/Classes/DasherAppDelegate.h b/Src/iPhone/Classes/DasherAppDelegate.h
index c40f898..0cbe972 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.h
+++ b/Src/iPhone/Classes/DasherAppDelegate.h
@@ -8,7 +8,6 @@
 
 #import <UIKit/UIKit.h>
 #import "CDasherInterfaceBridge.h"
-#import "CDasherScreenBridge.h"
 #import "TextView.h"
 #import "Actions.h"
 
diff --git a/Src/iPhone/Classes/DasherAppDelegate.mm b/Src/iPhone/Classes/DasherAppDelegate.mm
index c4686d1..66860c9 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.mm
+++ b/Src/iPhone/Classes/DasherAppDelegate.mm
@@ -26,6 +26,8 @@
 - (void)doSpeedBtnImage:(NSString *)msg;
 - (void)speedSlid:(id)slider;
 - (CGRect)doLayout:(UIInterfaceOrientation)orient;
+//calls through to [EAGLView makeContextCurrent]
+- (void)selectEAGLContext;
 @property (retain) UILabel *screenLockLabel;
 @property (nonatomic,retain) NSString *m_wordBoundary;
 @property (nonatomic,retain) NSString *m_sentenceBoundary;
@@ -56,6 +58,7 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
 @synthesize m_wordBoundary;
 @synthesize m_sentenceBoundary;
 @synthesize m_lineBoundary;
+
 -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
   if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
     return NO;
@@ -161,13 +164,18 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
 
 	[window addSubview:self.view];
   
+  //make object (this doesn't do anything much, initialization/Realize later
+  // - but we have to set a screen before we Realize)
+  _dasherInterface = new CDasherInterfaceBridge(self);
+  
   //create GUI components...
 	text = [[[TextView alloc] init] autorelease];
   messageLabel = [[[UILabel alloc] init] autorelease];
   tools = [[UIToolbar alloc] init]; //retain a reference (until dealloc) because of rotation
 	glView = [[[EAGLView alloc] initWithFrame:[self doLayout:UIInterfaceOrientationPortrait] Delegate:self] autorelease];
+  //that last, calls ChangeScreen on the interface, so now we can:
 		
-  //start training in a separate thread. (Has to be after first
+  //start Realization i.e. training in a separate thread. (Has to be after first
   // call to doLayout, or get a black band across top of screen)
   [self doAsyncLocked:@"Initializing..." target:self selector:@selector(initDasherInterface) param:nil];
 
@@ -223,7 +231,7 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
 - (void)initDasherInterface {
   //training takes too long to perform in applicationDidFinishLaunching;
   // so we do it here instead (having let the main thread display a "training" message);
-  _dasherInterface = new CDasherInterfaceBridge(self);
+  _dasherInterface->Realize();
   //the rest has to be done on the main thread to avoid problems with OpenGL contexts
   // (which are local to one thread); however, we'll have the background thread wait...
   [self performSelectorOnMainThread:@selector(finishStartup) withObject:nil waitUntilDone:YES];
@@ -232,11 +240,8 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
 }
 
 - (void)finishStartup {
-  self.dasherInterface->ChangeScreen(new CDasherScreenBridge(glView));
-  
 	[CalibrationController doSetup]; //restore tilt settings
 	[self notifySpeedChange];
-  self.dasherInterface->OnUIRealised(); //that does startAnimation...
   doneSetup = YES;
   //The following will cause the text cursor to be displayed whenever
   // any change is made to the textbox...
@@ -521,6 +526,10 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
   return text.text;
 }
 
+-(void)selectEAGLContext {
+  [glView makeContextCurrent];
+}
+
 #pragma mark TextViewDelegate methods
 
 -(void)textViewDidChangeSelection:(UITextView *)textView {
@@ -602,11 +611,14 @@ static SModuleSettings _miscSettings[] = { //note iStep and string description a
 
 - (void)aSyncMain:(NSInvocation *)action {
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+  //This method is being executed on (some, unknown) background thread, i.e. not the main thread.
+  // We don't _know_ it's going to do anything OpenGL-related, but to mirror the main thread:
+  [[DasherAppDelegate theApp] selectEAGLContext];
   [action invoke];
+  [DasherAppDelegate theApp].screenLockLabel = nil;
   //passing 'nil' here, where a BOOL is expected, is a horrendous trick - nil = 0x0 is effectively reinterpret_casted... 
   // however, the 'correct' method of passing [NSNumber numberWithBool:] is erratic, resulting in either inversion, 
   // always true, or always false, on different versions of the iPhone OS/SDK...
-  [DasherAppDelegate theApp].screenLockLabel = nil;
   [self performSelectorOnMainThread:@selector(dismissModalViewControllerAnimated:) withObject:nil waitUntilDone:NO];
   [pool release];
 }
diff --git a/Src/iPhone/Classes/EAGLView.h b/Src/iPhone/Classes/EAGLView.h
index 51d1aea..7e58bf5 100644
--- a/Src/iPhone/Classes/EAGLView.h
+++ b/Src/iPhone/Classes/EAGLView.h
@@ -12,43 +12,57 @@
 #import <OpenGLES/ES1/gl.h>
 #import <OpenGLES/ES1/glext.h>
 
-#import "DasherScreenCallbacks.h"
+#import "../Common/OpenGLScreen.h"
 #import "DasherAppDelegate.h"
 
+ class EAGLView;
+
+class CDasherScreenBridge : public OpenGLScreen {
+  EAGLView *view;
+public:
+  CDasherScreenBridge(EAGLView *_view, screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y, GLuint *textures);
+  ///Only for EAGLView to call...
+  void resize(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y);
+  bool GetTouchCoords(screenint &iX, screenint &iY);
+  void Display();
+  void SendMarker(int iMarker);
+  
+protected:
+  void RenderStringOntoCGContext(NSString *str, CGContextRef context);
+  CGSize TextSize(NSString *str, unsigned int iFontSize);
+};
+
 /*
 This class wraps the CAEAGLLayer from CoreAnimation into a convenient UIView subclass.
 The view content is basically an EAGL surface you render your OpenGL scene into.
 Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel.
 */
- interface EAGLView : UIView<DasherScreenCallbacks> {
+ interface EAGLView : UIView {
     
 @private    
     EAGLContext *context;
     
+  CDasherScreenBridge *dasherScreen;
+  
     /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
     GLuint viewRenderbuffer, viewFramebuffer;
     
 	BOOL animating, doneLayout, anyDown;
     NSTimeInterval animationInterval;
 	DasherAppDelegate *dasherApp;
-	colour_t *colourTable;
 	
-	GLuint mouseBuffer, boxesBuffer;
-	GLuint mouseTex, boxesTex;
-	GLshort rectcoords[8];
-	GLfloat texcoords[8];
+	GLuint buffers[2];
+	GLuint textures[2];
+	GLint backingWidth, backingHeight;
+	GLfloat texw,texh;
   
   CGPoint lastTouchCoords;
-  
-  ///Caches for circleCallbackWithCentrePoint:... (see therein)
-  float circ_rad;
-  GLshort *circ_coords;
-  int circPoints;
-  
 }
 
 @property (readonly,assign) CGPoint lastTouchCoords;
-
+//OpenGL context (needed to do any OGL operation) is only current per-thread, so must call this
+// if doing anything on any thread other than the main thread.
+-(void)makeContextCurrent;
 - (void)startAnimation;
 - (void)stopAnimation;
 - (void)drawView;
diff --git a/Src/iPhone/Classes/EAGLView.mm b/Src/iPhone/Classes/EAGLView.mm
index c65d774..6a334f2 100644
--- a/Src/iPhone/Classes/EAGLView.mm
+++ b/Src/iPhone/Classes/EAGLView.mm
@@ -11,19 +11,59 @@
 
 #import "EAGLView.h"
 #import "DasherUtil.h"
-#import "AlphabetLetter.h"
+#import "../Common/OpenGLScreen.h"
 
 // A class extension to declare private methods
 @interface EAGLView ()
-
 - (BOOL) createFramebuffer;
 - (void) destroyFramebuffer;
-
+- (void)displayCallback;
+-(void)sendMarker:(int)iMarker;
+ property (readonly) bool readyToDisplay;
 @end
 
+CDasherScreenBridge::CDasherScreenBridge(EAGLView *_view, screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y, GLuint *textures)
+: OpenGLScreen(iWidth, iHeight, backingWidth, backingHeight, tc_x, tc_y, textures), view(_view) {
+}
 
- implementation EAGLView
+void CDasherScreenBridge::resize(screenint iWidth, screenint iHeight, GLshort backingWidth, GLshort backingHeight, GLfloat tc_x, GLfloat tc_y) {
+  OpenGLScreen::resize(iWidth, iHeight, backingWidth, backingHeight, tc_x, tc_y);
+}
+  
+bool CDasherScreenBridge::GetTouchCoords(screenint &iX, screenint &iY) {
+  CGPoint p = view.lastTouchCoords;
+  if (p.x==-1) return false;
+  iX=p.x; iY=p.y;
+  return true;
+}
+  
+void CDasherScreenBridge::Display() {
+  if (!view.readyToDisplay) return; //can't display anything yet!
+  OpenGLScreen::Display();
+  [view displayCallback];
+};
+  
+void CDasherScreenBridge::SendMarker(int iMarker) {
+  [view sendMarker:iMarker];
+}
+
+void CDasherScreenBridge::RenderStringOntoCGContext(NSString *str, CGContextRef context) {
+  UIGraphicsPushContext(context);
+  //white text on transparent background means that when we texture
+  //a surface using a colour, the text appears in that colour...
+  const CGFloat whiteComps[] = {1.0, 1.0, 1.0, 1.0};
+  CGColorRef white = CGColorCreate(CGBitmapContextGetColorSpace(context), whiteComps);
+  CGContextSetFillColorWithColor(context, white);
+  [str drawAtPoint:CGPointMake(0.0, 0.0) withFont:[UIFont systemFontOfSize:36]];
+  CGColorRelease(white);
+  UIGraphicsPopContext();  
+}
+  
+CGSize CDasherScreenBridge::TextSize(NSString *str, unsigned int iFontSize) {
+  return [str sizeWithFont:[UIFont systemFontOfSize:iFontSize]];
+}
 
+ implementation EAGLView
 @synthesize lastTouchCoords;
 
 // You must implement this method
@@ -31,7 +71,6 @@
     return [CAEAGLLayer class];
 }
 
-
 - (id)initWithFrame:(CGRect)frame {
   NSLog(@"initWithFrame needs delegate\n");
   return nil;
@@ -41,7 +80,6 @@
     
     if ((self = [super initWithFrame:frame])) {
 		dasherApp = _dasherApp;
-    circ_rad=-1.0f;
       
         // Get the layer
         CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
@@ -59,9 +97,14 @@
 		glShadeModel(GL_FLAT);
 		glEnable(GL_BLEND);
 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        
+    
+      glGenTextures(2, textures);
+      glGenFramebuffersOES(2, buffers);
+
         animationInterval = 1.0 / 60.0;
-		colourTable = nil;
+      //textures is a pointer to 2 ints, so even tho the ints haven't been init'd yet, we can still pass the ptr
+      dasherScreen = new CDasherScreenBridge(self, frame.size.width, frame.size.height, 0, 0, 0, 0, textures);
+      dasherApp.dasherInterface->ChangeScreen(dasherScreen);
     }
     return self;
 }
@@ -100,19 +143,16 @@
   }
 }
 
-
 - (void)layoutSubviews {
-	  [EAGLContext setCurrentContext:context];
-    [self destroyFramebuffer];
-    [self createFramebuffer];
-  if (doneLayout)
-    dasherApp.dasherInterface->ChangeScreen(new CDasherScreenBridge(self));
-  else //first time, DasherAppDelegate will create screen as part of startup
-    doneLayout = YES;
+  [EAGLContext setCurrentContext:context];
+  [self destroyFramebuffer];
+  [self createFramebuffer];
+  CGSize sz = [self bounds].size;
+  dasherScreen->resize(sz.width, sz.height, backingWidth, backingHeight, texw, texh);
+  doneLayout = YES;
   [self drawView];
 }
 
-
 - (BOOL)createFramebuffer {
     //dasherApp.dasherInterface->ChangeView();//???
     glGenFramebuffersOES(1, &viewFramebuffer);
@@ -124,7 +164,7 @@
     glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
 	//CGRect r = [self bounds];
     //glViewport(r.origin.x, r.origin.y, r.size.width, r.size.height);
-	int backingWidth, backingHeight;
+
     glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
     glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
 	
@@ -137,35 +177,19 @@
 	int w=1, h=1;
 	while (w < backingWidth) w <<= 1;
 	while (h < backingHeight) h <<= 1;
-	//boxes buffer
-	glGenTextures(1, &boxesTex);
-	glGenFramebuffersOES(1, &boxesBuffer);
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, boxesBuffer);
-	glBindTexture(GL_TEXTURE_2D, boxesTex);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
-	glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, boxesTex, 0);
-	if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
-	{
-        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
-        return NO;
-	}
-
-	//mouse buffer
-	glGenTextures(1, &mouseTex);
-	glGenFramebuffersOES(1, &mouseBuffer);
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, mouseBuffer);
-	glBindTexture(GL_TEXTURE_2D, mouseTex);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
-	glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, mouseTex, 0);
-	if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
-	{
-        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
-        return NO;
-	}
+
+	for (int i=0; i<(sizeof(textures)/sizeof(textures[0])); i++) {
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, buffers[i]);
+    glBindTexture(GL_TEXTURE_2D, textures[i]);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
+    glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, textures[i], 0);
+    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+      NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+      return NO;
+    }
+  }
 
 	glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
@@ -176,32 +200,27 @@
     glOrthox(0, IntToFixed(backingWidth), IntToFixed(backingHeight), 0, IntToFixed(-1), IntToFixed(1));
     glMatrixMode(GL_MODELVIEW);
 	glLoadIdentity();
-
-	rectcoords[0] = rectcoords[1] = 0;
-	rectcoords[2] = backingWidth; rectcoords[3] = 0;
-	rectcoords[4] = 0; rectcoords[5] = backingHeight;
-	rectcoords[6] = backingWidth; rectcoords[7] = backingHeight;
 	
-	GLfloat texw = backingWidth/(float)w, texh = backingHeight/(float)h;
-	texcoords[0] = 0.0; texcoords [1] = texh;//0.0;
-	texcoords[2] = texw; texcoords[3] = texh;//0.0;
-	texcoords[4] = 0.0; texcoords[5] = 0.0;//texh;
-	texcoords[6] = texw; texcoords[7] = 0.0;//texh;
-
+	texw = backingWidth/(float)w, texh = backingHeight/(float)h;
+	
 	return YES;
 }
 
+-(bool)readyToDisplay {
+  return viewFramebuffer!=0;
+}
+
+-(void)makeContextCurrent {
+  [EAGLContext setCurrentContext:context];
+}
 
 - (void)destroyFramebuffer {
     
     glDeleteFramebuffersOES(1, &viewFramebuffer); viewFramebuffer = 0;
-	glDeleteFramebuffersOES(1, &mouseBuffer); mouseBuffer = 0;
-	glDeleteFramebuffersOES(1, &boxesBuffer); boxesBuffer = 0;
-    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+	  glDeleteRenderbuffersOES(1, &viewRenderbuffer);
     viewRenderbuffer = 0;
 }
 
-
 - (void)startAnimation {
   if (animating) return;
   animating = YES;
@@ -211,7 +230,6 @@
     [self setNeedsDisplay];
 }
 
-
 - (void)stopAnimation {
   //stop for now at least - shutdown, settings dialog, etc.
   // (causes drawView not to enqueue another repaint)
@@ -221,7 +239,13 @@
 - (void)dealloc {
     
     [self stopAnimation];
-    
+
+  glDeleteFramebuffersOES(2, buffers);
+  glDeleteTextures(2,textures);
+  for (int i=0; i<2; i++)
+    buffers[i]=textures[i]=0;
+  
+  [self destroyFramebuffer];
     if ([EAGLContext currentContext] == context) {
         [EAGLContext setCurrentContext:nil];
     }
@@ -230,173 +254,21 @@
     [super dealloc];
 }
 
-//DasherScreenCallbacks...
--(void)blankCallback {};
-
 -(void)displayCallback {
-	if (!viewFramebuffer) return; //can't display anything yet!
-	[self sendMarker:-1];
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-    	
-	glEnable(GL_TEXTURE_2D);
-	glColor4f(1.0, 1.0, 1.0, 1.0);
-	
-	glEnableClientState(GL_VERTEX_ARRAY);
-	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-	glVertexPointer(2, GL_SHORT, 0, rectcoords);
-	glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
-	for (int i=0; i<2; i++)
-	{
-		glBindTexture(GL_TEXTURE_2D, i==0 ? boxesTex : mouseTex);
-		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-	}
-	glFlush();
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
-    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
-};
-
--(void)rectangleCallbackX1:(int)x1 y1:(int)y1 x2:(int)x2 y2:(int)y2 fillColorIndex:(int)aFillColorIndex outlineColorIndex:(int)anOutlineColorIndex lineWidth:(int)aLineWidth {
-	glDisable(GL_TEXTURE_2D);
-	glEnableClientState(GL_VERTEX_ARRAY);
-	if (aFillColorIndex != -1) {
-		glColor4f(colourTable[aFillColorIndex].r, colourTable[aFillColorIndex].g, colourTable[aFillColorIndex].b, 1.0);
-		GLshort coords[8] = {x1,y1, x2,y1, x1,y2, x2,y2};
-		glVertexPointer(2, GL_SHORT, 0, coords);
-		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-	}
-	if (aLineWidth>0) {
-		int oci = anOutlineColorIndex == -1 ? 3 : anOutlineColorIndex;
-		glColor4f(colourTable[oci].r, colourTable[oci].g, colourTable[oci].b, 1.0);
-		glLineWidth(aLineWidth);
-		GLshort coords[] = {x1,y1, x2,y1, x2,y2,  x1,y2};
-		glVertexPointer(2, GL_SHORT, 0, coords);
-		glDrawArrays(GL_LINE_LOOP, 0, 4);
-	}
-}
-
--(void)circleCallbackCentrePoint:(CGPoint)aCentrePoint radius:(float)aRadius fillColourIndex:(int)aFillColourIndex outlineColorIndex:(int)anOutlineColorIndex lineWidth:(int)aLineWidth {
-  //it's a bit of a hack, but we cache the last-computed set of points round the circle,
-  // as these are the same for all calls with the same radius - and (the hack!) it happens
-  // that the radius tends to be the same every time (as the only call to CDasherScreen::DrawCircle
-  // is from CircleStartHandler!)...
-  if (circ_rad != aRadius) {
-    delete circ_coords;
-    double costh=1.0f - 1.0f/(2.0f*aRadius);
-    double th = acos(costh);
-    int numPoints = circPoints = ceil(M_PI/th/2.0f); //for a quarter-circle
-    double sinth = sin(th),x(aRadius),y(0.0);
-    circ_coords = new GLshort[numPoints*8]; circ_rad = aRadius;
-    circ_coords[0] = x; circ_coords[1] = y;
-    for (int i=1; i<numPoints; i++) {
-      double nx = x*costh - y*sinth;
-      double ny = x*sinth + y*costh;
-      circ_coords[2*i] = nx;
-      circ_coords[2*i+1] = ny;
-      x=nx; y=ny;
-    }
-    for (int i=0; i<numPoints; i++) {
-      circ_coords[2*(i+numPoints)] = -circ_coords[2*i+1];
-      circ_coords[2*(i+numPoints)+1] = circ_coords[2*i];
-      
-      circ_coords[2*(i+numPoints*2)] = -circ_coords[2*i];
-      circ_coords[2*(i+numPoints*2)+1] = -circ_coords[2*i+1];
-      
-      circ_coords[2*(i+numPoints*3)] = circ_coords[2*i+1];
-      circ_coords[2*(i+numPoints*3)+1] = -circ_coords[2*i];
-    }
-  }
-  
-  glDisable(GL_TEXTURE_2D);
-  glEnableClientState(GL_VERTEX_ARRAY);
-  glTranslatef(aCentrePoint.x, aCentrePoint.y, 0.0);
-  if (aFillColourIndex!=-1) {
-    glColor4f(colourTable[aFillColourIndex].r, colourTable[aFillColourIndex].g, colourTable[aFillColourIndex].b, 1.0);
-    glVertexPointer(2, GL_SHORT, 0, circ_coords);
-    glDrawArrays(GL_TRIANGLE_FAN, 0, circPoints*4);
-  }
-  if (aLineWidth>0) {
-    int oci = anOutlineColorIndex == -1 ? 3 : anOutlineColorIndex;
-		glColor4f(colourTable[oci].r, colourTable[oci].g, colourTable[oci].b, 1.0);
-		glLineWidth(aLineWidth);
-		glVertexPointer(2, GL_SHORT, 0, circ_coords);
-		glDrawArrays(GL_LINE_LOOP, 0, circPoints*4);
-  }
-  glTranslatef(-aCentrePoint.x, -aCentrePoint.y, 0.0);
-};
-
--(CGSize)textSizeCallbackWithString:(NSString *)aString size:(int)aSize {
-	return [aString sizeWithFont:[UIFont systemFontOfSize:aSize]];
-}
-
-- (void)drawTextCallbackWithString:(NSString *)aString x1:(int)x1 y1:(int)y1 size:(int)aSize colorIndex:(int)aColorIndex
-{
-	AlphabetLetter *letter = [AlphabetLetter forString:aString];
-	// TODO could pass the whole colour_t in and let it deal with splitting out the items
-	[letter drawWithSize:aSize x:x1 y:y1 r:colourTable[aColorIndex].r g:colourTable[aColorIndex].g b:colourTable[aColorIndex].b];
+  glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+  [context presentRenderbuffer:GL_RENDERBUFFER_OES];
 }
 
 -(void)sendMarker:(int)iMarker {
-	if (iMarker != -1)
-	{
-		glDisable(GL_TEXTURE_2D);
-		glBindFramebufferOES(GL_FRAMEBUFFER_OES, iMarker == 0 ? boxesBuffer : mouseBuffer);
-		glClearColor(1.0, 1.0, 1.0, iMarker == 0 ? 1.0 : 0.0);
-		glClear(GL_COLOR_BUFFER_BIT);
-	}
-}
-
--(void)polylineCallback:(int)iNum points:(Dasher::CDasherScreen::point *)points width:(int)iWidth colourIndex:(int)iColour {
-	if (iNum < 2) return;
-	GLshort *coords = new GLshort[iNum*2];
-	for (int i = 0; i<iNum; i++)
-	{
-		coords[2*i] = points[i].x;
-		coords[2*i+1] = points[i].y;
-	}
-	glDisable(GL_TEXTURE_2D);
-	glColor4f(colourTable[iColour].r, colourTable[iColour].g, colourTable[iColour].b, 1.0);
-	glLineWidth(iWidth);
-	glVertexPointer(2, GL_SHORT, 0, coords);
-	glDrawArrays(GL_LINE_STRIP, 0, iNum);
-	delete coords;
-}
-
--(void)polygonCallback:(int)iNum points:(Dasher::CDasherScreen::point *)points fillColourIndex:(int)iFillColour outlineColourIndex:(int)iOutlineColour width:(int)iWidth {
-  if (iNum < 2) return;
-  GLshort *coords = new GLshort[iNum*2];
-  for (int i = 0; i<iNum; i++)
-  {
-    coords[2*i] = points[i].x;
-    coords[2*i+1] = points[i].y;
-  }
-  glDisable(GL_TEXTURE_2D);
-  if (iFillColour != -1) {
-    glColor4f(colourTable[iFillColour].r, colourTable[iFillColour].g, colourTable[iFillColour].b, 1.0);
-    glVertexPointer(2, GL_SHORT, 0, coords);
-    glDrawArrays(GL_TRIANGLE_FAN, 0, iNum);
-  }
-  if (iWidth>0) {
-    glColor4f(colourTable[iOutlineColour].r, colourTable[iOutlineColour].g, colourTable[iOutlineColour].b, 1.0);
-    glLineWidth(iWidth);
-    glVertexPointer(2, GL_SHORT, 0, coords);
-    glDrawArrays(GL_LINE_LOOP, 0, iNum);
-  }
-  delete coords;
-}
-
--(void)setColourSchemeWithColourTable:(colour_t *)aColourTable {
-	if (colourTable != NULL) {
-		free(colourTable);
-	}
-	colourTable = aColourTable;
-}
-
--(int)boundsWidth {
-	return [self bounds].size.width;
+  if (iMarker == -1) {
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+  } else {
+    glDisable(GL_TEXTURE_2D);
+    glBindFramebufferOES(GL_FRAMEBUFFER_OES, buffers[iMarker]);
+    glClearColor(1.0, 1.0, 1.0, iMarker == 0 ? 1.0 : 0.0);
+    glClear(GL_COLOR_BUFFER_BIT);
+  }  
 }
 
--(int)boundsHeight {
-	return [self bounds].size.height;
-}
 
 @end
diff --git a/Src/iPhone/Classes/IPhoneFilters.mm b/Src/iPhone/Classes/IPhoneFilters.mm
index acdd512..3713bc6 100644
--- a/Src/iPhone/Classes/IPhoneFilters.mm
+++ b/Src/iPhone/Classes/IPhoneFilters.mm
@@ -12,7 +12,7 @@
 #include "../Common/Common.h"
 #include "DasherInterfaceBase.h"
 #include "Event.h"
-#include "CDasherScreenBridge.h"
+#include "EAGLView.h"
 #include <iostream>
 
 NSString *HOLD_TO_GO=@"HoldToGo";
diff --git a/Src/iPhone/Classes/IPhoneInputs.mm b/Src/iPhone/Classes/IPhoneInputs.mm
index 51991fb..a0aa967 100644
--- a/Src/iPhone/Classes/IPhoneInputs.mm
+++ b/Src/iPhone/Classes/IPhoneInputs.mm
@@ -11,6 +11,7 @@
 #import "IPhoneInputs.h"
 #import "DasherUtil.h"
 #import "DasherAppDelegate.h"
+#import "EAGLView.h"
 
 using namespace std;
 using namespace Dasher;
diff --git a/Src/iPhone/Dasher.xcodeproj/project.pbxproj b/Src/iPhone/Dasher.xcodeproj/project.pbxproj
index fa7d40e..064f4b2 100755
--- a/Src/iPhone/Dasher.xcodeproj/project.pbxproj
+++ b/Src/iPhone/Dasher.xcodeproj/project.pbxproj
@@ -272,8 +272,7 @@
 		33CBB4F3101FA33E00510BF9 /* alphabet.swedish.xml in Resources */ = {isa = PBXBuildFile; fileRef = 33CBB4EB101FA33E00510BF9 /* alphabet.swedish.xml */; };
 		33CBB4F5101FA35900510BF9 /* training_basque_ES.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33CBB4F4101FA35900510BF9 /* training_basque_ES.txt */; };
 		33DA5E2D11B70FA100011CD2 /* paste.png in Resources */ = {isa = PBXBuildFile; fileRef = 33DA5E2C11B70FA100011CD2 /* paste.png */; };
-		33EB483D0F7287DC0048E7C2 /* CDasherScreenBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33EB483C0F7287DC0048E7C2 /* CDasherScreenBridge.mm */; };
-		33EB49240F73E8B30048E7C2 /* AlphabetLetter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33EB49230F73E8B30048E7C2 /* AlphabetLetter.mm */; };
+		33EB49240F73E8B30048E7C2 /* OpenGLScreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33EB49230F73E8B30048E7C2 /* OpenGLScreen.mm */; };
 		33EC5DF70FF3E30D00275986 /* ButtonMultiPress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33EC5DF20FF3E30D00275986 /* ButtonMultiPress.cpp */; };
 		33EC5DF80FF3E30D00275986 /* FrameRate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33EC5DF40FF3E30D00275986 /* FrameRate.cpp */; };
 		33EC5DF90FF3E30D00275986 /* TwoPushDynamicFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33EC5DF50FF3E30D00275986 /* TwoPushDynamicFilter.cpp */; };
@@ -720,11 +719,8 @@
 		33CBB4EB101FA33E00510BF9 /* alphabet.swedish.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = alphabet.swedish.xml; sourceTree = "<group>"; };
 		33CBB4F4101FA35900510BF9 /* training_basque_ES.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_basque_ES.txt; sourceTree = "<group>"; };
 		33DA5E2C11B70FA100011CD2 /* paste.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = paste.png; sourceTree = "<group>"; };
-		33EB483B0F7287DC0048E7C2 /* CDasherScreenBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDasherScreenBridge.h; sourceTree = "<group>"; };
-		33EB483C0F7287DC0048E7C2 /* CDasherScreenBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CDasherScreenBridge.mm; sourceTree = "<group>"; };
-		33EB48400F72A5680048E7C2 /* DasherScreenCallbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DasherScreenCallbacks.h; sourceTree = "<group>"; };
-		33EB49220F73E8B30048E7C2 /* AlphabetLetter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AlphabetLetter.h; sourceTree = "<group>"; };
-		33EB49230F73E8B30048E7C2 /* AlphabetLetter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AlphabetLetter.mm; sourceTree = "<group>"; };
+		33EB49220F73E8B30048E7C2 /* OpenGLScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenGLScreen.h; sourceTree = "<group>"; };
+		33EB49230F73E8B30048E7C2 /* OpenGLScreen.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OpenGLScreen.mm; sourceTree = "<group>"; };
 		33EC5DF20FF3E30D00275986 /* ButtonMultiPress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ButtonMultiPress.cpp; sourceTree = "<group>"; };
 		33EC5DF30FF3E30D00275986 /* ButtonMultiPress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ButtonMultiPress.h; sourceTree = "<group>"; };
 		33EC5DF40FF3E30D00275986 /* FrameRate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameRate.cpp; sourceTree = "<group>"; };
@@ -776,10 +772,6 @@
 				337691850F9CEFC70083FEB2 /* InputMethodSelector.mm */,
 				3376916F0F9CE8630083FEB2 /* StringParamController.h */,
 				337691700F9CE8630083FEB2 /* StringParamController.mm */,
-				33EB49220F73E8B30048E7C2 /* AlphabetLetter.h */,
-				33EB49230F73E8B30048E7C2 /* AlphabetLetter.mm */,
-				33EB483B0F7287DC0048E7C2 /* CDasherScreenBridge.h */,
-				33EB483C0F7287DC0048E7C2 /* CDasherScreenBridge.mm */,
 				331C73F30F717594004492FF /* DasherUtil.h */,
 				331C73F40F717594004492FF /* DasherUtil.mm */,
 				331C73C10F71750D004492FF /* COSXSettingsStore.h */,
@@ -794,7 +786,6 @@
 				3354AF4711ADBAFD006CF570 /* Actions.mm */,
 				3324F7BD129D744D00EE6A22 /* ActionConfigurator.h */,
 				3324F7BE129D744D00EE6A22 /* ActionConfigurator.mm */,
-				33EB48400F72A5680048E7C2 /* DasherScreenCallbacks.h */,
 				339F8A310FF5088000282847 /* CalibrationController.h */,
 				339F8A320FF5088000282847 /* CalibrationController.mm */,
 			);
@@ -1334,6 +1325,8 @@
 		3344FE670F71718B00506EAA /* Common */ = {
 			isa = PBXGroup;
 			children = (
+				33EB49220F73E8B30048E7C2 /* OpenGLScreen.h */,
+				33EB49230F73E8B30048E7C2 /* OpenGLScreen.mm */,
 				334B1C2011233B8B007A6DFF /* ModuleSettings.h */,
 				332387350F7838AC00DD75C5 /* Platform.h */,
 				332387300F78388200DD75C5 /* Common.h */,
@@ -1414,7 +1407,14 @@
 			isa = PBXProject;
 			buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Dasher" */;
 			compatibilityVersion = "Xcode 3.1";
+			developmentRegion = English;
 			hasScannedForEncodings = 1;
+			knownRegions = (
+				English,
+				Japanese,
+				French,
+				German,
+			);
 			mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
 			projectDirPath = "";
 			projectRoot = "";
@@ -1609,8 +1609,7 @@
 				3344FE660F71717C00506EAA /* XMLUtil.cpp in Sources */,
 				331C73C30F71750D004492FF /* COSXSettingsStore.mm in Sources */,
 				331C73F50F717594004492FF /* DasherUtil.mm in Sources */,
-				33EB483D0F7287DC0048E7C2 /* CDasherScreenBridge.mm in Sources */,
-				33EB49240F73E8B30048E7C2 /* AlphabetLetter.mm in Sources */,
+				33EB49240F73E8B30048E7C2 /* OpenGLScreen.mm in Sources */,
 				337690140F989C870083FEB2 /* SBTree.cpp in Sources */,
 				337691710F9CE8630083FEB2 /* StringParamController.mm in Sources */,
 				337691860F9CEFC70083FEB2 /* InputMethodSelector.mm in Sources */,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]