From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / texfont.c
index f58755d1ab3833d7a0cd9b35d9fbb1cc45c4a396..5388efd4f75f94b3d35c28a5cba52fb832433dbd 100644 (file)
@@ -1,5 +1,4 @@
-/* texfonts, Copyright (c) 2005-2012 Jamie Zawinski <jwz@jwz.org>
- * Loads X11 fonts into textures for use with OpenGL.
+/* texfonts, Copyright (c) 2005-2017 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -8,9 +7,11 @@
  * documentation.  No representations are made about the suitability of this
  * software for any purpose.  It is provided "as is" without express or 
  * implied warranty.
+ *
+ * Renders X11 fonts into textures for use with OpenGL.
+ * A higher level API is in glxfonts.c.
  */
 
-
 #ifdef HAVE_CONFIG_H
 # include "config.h"
 #endif
@@ -26,6 +27,9 @@
 # else
 #  include <OpenGL/glu.h>
 # endif
+#elif defined(HAVE_ANDROID)
+# include <GLES/gl.h>
+# include "jwzgles.h"
 #else
 # include <GL/glx.h>
 # include <GL/glu.h>
 # include "jwzgles.h"
 #endif /* HAVE_JWZGLES */
 
+#ifdef HAVE_XSHM_EXTENSION
+# include "xshm.h"
+#endif /* HAVE_XSHM_EXTENSION */
+
+#include "xft.h"
+#include "pow2.h"
 #include "resources.h"
 #include "texfont.h"
+#include "fps.h"       /* for current_device_rotation() */
+
+#undef HAVE_XSHM_EXTENSION  /* doesn't actually do any good here */
+
 
 /* These are in xlock-gl.c */
 extern void clear_gl_error (void);
@@ -45,31 +59,28 @@ extern void check_gl_error (const char *type);
 /* screenhack.h */
 extern char *progname;
 
+/* LRU cache of textures, to optimize the case where we're drawing the
+   same strings repeatedly.
+ */
+typedef struct texfont_cache texfont_cache;
+struct texfont_cache {
+  char *string;
+  GLuint texid;
+  XCharStruct extents;
+  int tex_width, tex_height;
+  texfont_cache *next;
+};
+
 struct texture_font_data {
   Display *dpy;
-  XFontStruct *font;
-  int cell_width, cell_height;  /* maximal charcell */
-  int tex_width, tex_height;    /* size of each texture */
-
-  int grid_mag;                        /* 1,  2,  4, or 8 */
-  int ntextures;               /* 1,  4, 16, or 64 (grid_mag ^ 2) */
-
-  GLuint texid[64];            /* must hold ntextures */
+  XftFont *xftfont;
+  int cache_size;
+  texfont_cache *cache;
 };
 
 
-/* return the next larger power of 2. */
-static int
-to_pow2 (int i)
-{
-  static const unsigned int pow2[] = { 
-    1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 
-    2048, 4096, 8192, 16384, 32768, 65536 };
-  int j;
-  for (j = 0; j < sizeof(pow2)/sizeof(*pow2); j++)
-    if (pow2[j] >= i) return pow2[j];
-  abort();  /* too big! */
-}
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
 
 
 /* Given a Pixmap (of screen depth), converts it to an OpenGL luminance mipmap.
@@ -81,15 +92,16 @@ to_pow2 (int i)
    were drawn with antialiasing, that is preserved.
  */
 static void
-bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
+bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int depth,
+                   int *wP, int *hP)
 {
   Bool mipmap_p = True;
   int ow = *wP;
   int oh = *hP;
-  int w2 = to_pow2 (ow);
-  int h2 = to_pow2 (oh);
-  int x, y;
-  XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap);
+  GLsizei w2 = (GLsizei) to_pow2 (ow);
+  GLsizei h2 = (GLsizei) to_pow2 (oh);
+  int x, y, max, scale;
+  XImage *image = 0;
   unsigned char *data = (unsigned char *) calloc (w2 * 2, (h2 + 1));
   unsigned char *out = data;
 
@@ -101,40 +113,128 @@ bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
 #  undef GL_INTENSITY
 # endif
 
-# ifdef GL_INTENSITY
-  GLuint iformat = GL_INTENSITY;
-  GLuint format  = GL_LUMINANCE;
-# else
-  GLuint iformat = GL_LUMINANCE_ALPHA;
-  GLuint format  = GL_LUMINANCE_ALPHA;
-# endif
-  GLuint type    = GL_UNSIGNED_BYTE;
+# ifdef HAVE_XSHM_EXTENSION
+  Bool use_shm = get_boolean_resource (dpy, "useSHM", "Boolean");
+  XShmSegmentInfo shm_info;
+# endif /* HAVE_XSHM_EXTENSION */
+
+  /* If either dimension is larger than the supported size, reduce.
+     We still return the old size to keep the caller's math working,
+     but the texture itself will have fewer pixels in it.
+   */
+  glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max);
+  scale = 1;
+  while (w2 > max || h2 > max)
+    {
+      w2 /= 2;
+      h2 /= 2;
+      scale *= 2;
+    }
+
+# ifdef HAVE_XSHM_EXTENSION
+  if (use_shm)
+    {
+      image = create_xshm_image (dpy, visual, depth, ZPixmap, 0, &shm_info,
+                                 ow, oh);
+      if (image)
+        XShmGetImage (dpy, p, image, 0, 0, ~0L);
+      else
+        use_shm = False;
+    }
+# endif /* HAVE_XSHM_EXTENSION */
+
+  if (!image) {
+    /* XCreateImage fills in (red|green_blue)_mask. XGetImage only does that
+       when reading from a Window, not when it's a Pixmap.
+     */
+    image = XCreateImage (dpy, visual, depth, ZPixmap, 0, NULL, ow, oh,
+                          BitmapPad (dpy), 0);
+    image->data = malloc (image->height * image->bytes_per_line);
+    XGetSubImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap, image, 0, 0);
+  }
 
 # ifdef HAVE_JWZGLES
   /* This would work, but it's wasteful for no benefit. */
   mipmap_p = False;
 # endif
 
-  for (y = 0; y < h2; y++)
+# ifdef DUMP_BITMAPS
+  fprintf (stderr, "\n");
+# endif
+  for (y = 0; y < h2; y++) {
     for (x = 0; x < w2; x++) {
-      unsigned long pixel = (x >= ow || y >= oh ? 0 : XGetPixel (image, x, y));
+      /* Might be better to average a scale x scale square of source pixels,
+         but at the resolutions we're dealing with, this is probably good
+         enough. */
+      int sx = x * scale;
+      int sy = y * scale;
+      unsigned long pixel = (sx >= ow || sy >= oh ? 0 :
+                             XGetPixel (image, sx, sy));
       /* instead of averaging all three channels, let's just use red,
          and assume it was already grayscale. */
-      unsigned long r = pixel & visual->red_mask;
+      unsigned long r = pixel & image->red_mask;
       /* This goofy trick is to make any of RGBA/ABGR/ARGB work. */
       pixel = ((r >> 24) | (r >> 16) | (r >> 8) | r) & 0xFF;
+
+# ifdef DUMP_BITMAPS
+      if (sx < ow && sy < oh)
+#  ifdef HAVE_JWXYZ
+        fprintf (stderr, "%c", 
+                 r >= 0xFF000000 ? '#' : 
+                 r >= 0x88000000 ? '%' : 
+                 r ? '.' : ' ');
+#  else
+        fprintf (stderr, "%c", 
+                 r >= 0xFF0000 ? '#' : 
+                 r >= 0x880000 ? '%' : 
+                 r ? '.' : ' ');
+#  endif
+# endif
+
+# if 0  /* Debugging checkerboard */
+      if (sx < ow && sy < oh && (((sx / 4) & 1) ^ ((sy / 4) & 1)))
+        pixel = 0x3F;
+# endif
+
 # ifndef GL_INTENSITY
-      *out++ = 0xFF;  /* 2 bytes per pixel */
+      *out++ = 0xFF;  /* 2 bytes per pixel (luminance, alpha) */
 # endif
       *out++ = pixel;
     }
-  XDestroyImage (image);
-  image = 0;
+# ifdef DUMP_BITMAPS
+    fprintf (stderr, "\n");
+# endif
+  }
 
-  if (mipmap_p)
-    gluBuild2DMipmaps (GL_TEXTURE_2D, iformat, w2, h2, format, type, data);
+# ifdef HAVE_XSHM_EXTENSION
+  if (use_shm)
+    destroy_xshm_image (dpy, image, &shm_info);
   else
-    glTexImage2D (GL_TEXTURE_2D, 0, iformat, w2, h2, 0, format, type, data);
+# endif /* HAVE_XSHM_EXTENSION */
+  {
+    /* We malloc'd image->data, so we free it. */
+    free (image->data);
+    image->data = NULL;
+    XDestroyImage (image);
+  }
+
+  image = 0;
+
+  {
+# ifdef GL_INTENSITY
+    GLuint iformat = GL_INTENSITY;
+    GLuint format  = GL_LUMINANCE;
+# else
+    GLuint iformat = GL_LUMINANCE_ALPHA;
+    GLuint format  = GL_LUMINANCE_ALPHA;
+# endif
+    GLuint type    = GL_UNSIGNED_BYTE;
+
+    if (mipmap_p)
+      gluBuild2DMipmaps (GL_TEXTURE_2D, iformat, w2, h2, format, type, data);
+    else
+      glTexImage2D (GL_TEXTURE_2D, 0, iformat, w2, h2, 0, format, type, data);
+  }
 
   {
     char msg[100];
@@ -144,18 +244,10 @@ bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
     check_gl_error (msg);
   }
 
-
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
-                   mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
-
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-
   free (data);
 
-  *wP = w2;
-  *hP = h2;
+  *wP = w2 * scale;
+  *hP = h2 * scale;
 }
 
 
@@ -165,37 +257,37 @@ bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
 texture_font_data *
 load_texture_font (Display *dpy, char *res)
 {
-  Screen *screen = DefaultScreenOfDisplay (dpy);
-  Window root = RootWindowOfScreen (screen);
-  XWindowAttributes xgwa;
-
-  texture_font_data *data = 0;
+  int screen = DefaultScreen (dpy);
   char *font = get_string_resource (dpy, res, "Font");
-  const char *def1 = "-*-helvetica-medium-r-normal-*-240-*";
-  const char *def2 = "-*-helvetica-medium-r-normal-*-180-*";
+  const char *def1 = "-*-helvetica-medium-r-normal-*-*-180-*-*-*-*-*-*";
+  const char *def2 = "-*-helvetica-medium-r-normal-*-*-140-*-*-*-*-*-*";
   const char *def3 = "fixed";
-  XFontStruct *f;
-  int which;
-  GLint old_texture = 0;
+  XftFont *f = 0;
+  texture_font_data *data;
+  int cache_size = get_integer_resource (dpy, "texFontCacheSize", "Integer");
 
-  glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
+  /* Hacks that draw a lot of different strings on the screen simultaneously,
+     like Star Wars, should set this to a larger value for performance. */
+  if (cache_size <= 0)
+    cache_size = 30;
 
-  if (!strcmp (res, "fpsFont"))
-    def1 = "-*-courier-bold-r-normal-*-180-*";  /* Kludge. Sue me. */
+  if (!res || !*res) abort();
 
-  XGetWindowAttributes (dpy, root, &xgwa);
+  if (!strcmp (res, "fpsFont")) {  /* Kludge. */
+    def1 = "-*-courier-bold-r-normal-*-*-140-*-*-*-*-*-*";
+    cache_size = 0;  /* No need for a cache on FPS: already throttled. */
+  }
 
-  if (!res || !*res) abort();
   if (!font) font = strdup(def1);
 
-  f = XLoadQueryFont(dpy, font);
+  f = XftFontOpenXlfd (dpy, screen, font);
   if (!f && !!strcmp (font, def1))
     {
       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
                progname, font, def1);
       free (font);
       font = strdup (def1);
-      f = XLoadQueryFont(dpy, font);
+      f = XftFontOpenXlfd (dpy, screen, font);
     }
 
   if (!f && !!strcmp (font, def2))
@@ -204,7 +296,7 @@ load_texture_font (Display *dpy, char *res)
                progname, font, def2);
       free (font);
       font = strdup (def2);
-      f = XLoadQueryFont(dpy, font);
+      f = XftFontOpenXlfd (dpy, screen, font);
     }
 
   if (!f && !!strcmp (font, def3))
@@ -213,14 +305,14 @@ load_texture_font (Display *dpy, char *res)
                progname, font, def3);
       free (font);
       font = strdup (def3);
-      f = XLoadQueryFont(dpy, font);
+      f = XftFontOpenXlfd (dpy, screen, font);
     }
 
   if (!f)
     {
       fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
                progname, font);
-      exit (1);
+      abort();
     }
 
   free (font);
@@ -228,358 +320,643 @@ load_texture_font (Display *dpy, char *res)
 
   data = (texture_font_data *) calloc (1, sizeof(*data));
   data->dpy = dpy;
-  data->font = f;
+  data->xftfont = f;
+  data->cache_size = cache_size;
 
-  /* Figure out how many textures to use.
-     E.g., if we need 1024x1024 bits, use four 512x512 textures,
-     to be gentle to machines with low texture size limits.
-   */
-  {
-    int w = to_pow2 (16 * (f->max_bounds.rbearing - f->min_bounds.lbearing));
-    int h = to_pow2 (16 * (f->max_bounds.ascent   + f->max_bounds.descent));
-    int i = (w > h ? w : h);
-
-    if      (i <= 512)  data->grid_mag = 1;  /*  1 tex of 16x16 chars */
-    else if (i <= 1024) data->grid_mag = 2;  /*  4 tex of 8x8 chars */
-    else if (i <= 2048) data->grid_mag = 4;  /* 16 tex of 4x4 chars */
-    else                data->grid_mag = 8;  /* 32 tex of 2x2 chars */
-
-    data->ntextures = data->grid_mag * data->grid_mag;
-
-# if 0
-    fprintf (stderr,
-             "%s: %dx%d grid of %d textures of %dx%d chars (%dx%d bits)\n",
-             progname,
-             data->grid_mag, data->grid_mag,
-             data->ntextures,
-             16 / data->grid_mag, 16 / data->grid_mag,
-             i, i);
-# endif
-  }
+  return data;
+}
 
-  for (which = 0; which < data->ntextures; which++)
+
+/* Measure the string, returning the overall metrics.
+   Newlines and tab stops are honored.
+   Any numbers inside [] will be rendered as a subscript.
+
+   The origin is at the origin of the first character, so subsequent
+   lines of a multi-line string look like descenders (below baseline).
+
+   If an XftDraw is supplied, render the string as well, at X,Y.
+   Positive Y is down (X11 style, not OpenGL style).
+ */
+static void
+iterate_texture_string (texture_font_data *data,
+                        const char *s,
+                        int draw_x, int draw_y,
+                        XftDraw *xftdraw, XftColor *xftcolor,
+                        XCharStruct *metrics_ret)
+{
+  int line_height = data->xftfont->ascent + data->xftfont->descent;
+  int subscript_offset = line_height * 0.3;
+  const char *os = s;
+  Bool sub_p = False, osub_p = False;
+  int cw = 0, tabs = 0;
+  XCharStruct overall = { 0, };
+  int x  = 0, y  = 0;
+  int ox = x, oy = y;
+
+  while (1)
     {
-      /* Create a pixmap big enough to fit every character in the font.
-         (modulo the "ntextures" scaling.)
-         Make it square-ish, since GL likes dimensions to be powers of 2.
-       */
-      XGCValues gcv;
-      GC gc;
-      Pixmap p;
-      int cw = f->max_bounds.rbearing - f->min_bounds.lbearing;
-      int ch = f->max_bounds.ascent   + f->max_bounds.descent;
-      int grid_size = (16 / data->grid_mag);
-      int w = cw * grid_size;
-      int h = ch * grid_size;
-      int i;
-
-      data->cell_width  = cw;
-      data->cell_height = ch;
-
-      p = XCreatePixmap (dpy, root, w, h, xgwa.depth);
-      gcv.font = f->fid;
-      gcv.foreground = BlackPixelOfScreen (xgwa.screen);
-      gcv.background = BlackPixelOfScreen (xgwa.screen);
-      gc = XCreateGC (dpy, p, (GCFont|GCForeground|GCBackground), &gcv);
-      XFillRectangle (dpy, p, gc, 0, 0, w, h);
-      XSetForeground (dpy, gc, WhitePixelOfScreen (xgwa.screen));
-      for (i = 0; i < 256 / data->ntextures; i++)
+      if (*s == 0 ||
+          *s == '\n' ||
+          *s == '\t' ||
+          (*s == '[' && isdigit(s[1])) ||
+          (*s == ']' && sub_p))
         {
-          int ii = (i + (which * 256 / data->ntextures));
-          char c = (char) ii;
-          int x = (i % grid_size) * cw;
-          int y = (i / grid_size) * ch;
-
-          /* See comment in print_texture_string for bit layout explanation.
-           */
-          int lbearing = (f->per_char && ii >= f->min_char_or_byte2
-                          ? f->per_char[ii - f->min_char_or_byte2].lbearing
-                          : f->min_bounds.lbearing);
-          int ascent   = (f->per_char && ii >= f->min_char_or_byte2
-                          ? f->per_char[ii - f->min_char_or_byte2].ascent
-                          : f->max_bounds.ascent);
-          int width    = (f->per_char && ii >= f->min_char_or_byte2
-                          ? f->per_char[ii - f->min_char_or_byte2].width
-                          : f->max_bounds.width);
-
-          if (width == 0) continue;
-          XDrawString (dpy, p, gc, x - lbearing, y + ascent, &c, 1);
+          if (s != os)
+            {
+              XGlyphInfo e;
+              XCharStruct c;
+              int y2 = y;
+              if (sub_p) y2 += subscript_offset;
+
+              XftTextExtentsUtf8 (data->dpy, data->xftfont,
+                                  (FcChar8 *) os, (int) (s - os),
+                                  &e);
+              c.lbearing = -e.x;               /* XGlyphInfo to XCharStruct */
+              c.rbearing =  e.width  - e.x;
+              c.ascent   =  e.y;
+              c.descent  =  e.height - e.y;
+              c.width    =  e.xOff;
+
+# undef MAX
+# undef MIN
+# define MAX(A,B) ((A)>(B)?(A):(B))
+# define MIN(A,B) ((A)<(B)?(A):(B))
+              overall.ascent   = MAX (overall.ascent,   -y2 + c.ascent);
+              overall.descent  = MAX (overall.descent,   y2 + c.descent);
+              overall.lbearing = MIN (overall.lbearing, (x  + c.lbearing));
+              overall.rbearing = MAX (overall.rbearing,  x  + c.rbearing);
+              overall.width    = MAX (overall.width,     x  + c.width);
+              x += c.width;
+# undef MAX
+# undef MIN
+            }
+
+          if (*s == '\n')
+            {
+              x = 0;
+              y += line_height;
+              sub_p = False;
+            }
+          else if (*s == '\t')
+            {
+              if (! cw)
+                {
+                  /* Measure "m" to determine tab width. */
+                  XGlyphInfo e;
+                  XftTextExtentsUtf8 (data->dpy, data->xftfont,
+                                      (FcChar8 *) "m", 1, &e);
+                  cw = e.xOff;
+                  if (cw <= 0) cw = 1;
+                  tabs = cw * 7;
+                }
+              x = ((x + tabs) / tabs) * tabs;
+            }
+          else if (*s == '[' && isdigit(s[1]))
+            sub_p = True;
+          else if (*s == ']' && sub_p)
+            sub_p = False;
+
+          if (xftdraw && s != os)
+            XftDrawStringUtf8 (xftdraw, xftcolor, data->xftfont,
+                               draw_x + ox,
+                               draw_y +
+                               oy + (osub_p ? subscript_offset : 0),
+                               (FcChar8 *) os, (int) (s - os));
+          if (!*s) break;
+          os = s+1;
+          ox = x;
+          oy = y;
+          osub_p = sub_p;
         }
-      XFreeGC (dpy, gc);
+      s++;
+    }
 
-      glGenTextures (1, &data->texid[which]);
-      glBindTexture (GL_TEXTURE_2D, data->texid[which]);
-      check_gl_error ("texture font load");
-      data->tex_width  = w;
-      data->tex_height = h;
+  if (metrics_ret)
+    *metrics_ret = overall;
+}
 
-#if 0  /* debugging: splat the bitmap onto the desktop root window */
-      {
-        Window win = RootWindow (dpy, 0);
-        GC gc2 = XCreateGC (dpy, win, 0, &gcv);
-        XSetForeground (dpy, gc2, BlackPixel (dpy, 0));
-        XSetBackground (dpy, gc2, WhitePixel (dpy, 0));
-        XCopyArea (dpy, p, win, gc2, 0, 0, w, h, 0, 0);
-        XFreeGC (dpy, gc2);
-        XSync(dpy, False);
-        usleep (100000);
-      }
-#endif
 
-#if 0  /* debugging: write the bitmap to a pgm file */
+/* Bounding box of the multi-line string, in pixels,
+   and overall ascent/descent of the font.
+ */
+void
+texture_string_metrics (texture_font_data *data, const char *s,
+                        XCharStruct *metrics_ret,
+                        int *ascent_ret, int *descent_ret)
+{
+  if (metrics_ret)
+    iterate_texture_string (data, s, 0, 0, 0, 0, metrics_ret);
+  if (ascent_ret)  *ascent_ret  = data->xftfont->ascent;
+  if (descent_ret) *descent_ret = data->xftfont->descent;
+}
+
+
+/* Returns a cache entry for this string, with a valid texid.
+   If the returned entry has a string in it, the texture is valid.
+   Otherwise it is an empty entry waiting to be rendered.
+ */
+static struct texfont_cache *
+get_cache (texture_font_data *data, const char *string)
+{
+  int count = 0;
+  texfont_cache *prev = 0, *prev2 = 0, *curr = 0, *next = 0;
+
+  if (data->cache)
+    for (prev2 = 0, prev = 0, curr = data->cache, next = curr->next;
+         curr;
+         prev2 = prev, prev = curr, curr = next,
+           next = (curr ? curr->next : 0), count++)
       {
-        char file[255];
-        XImage *image;
-        int x, y;
-        FILE *f;
-        sprintf (file, "/tmp/%02d.pgm", which);
-        image = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
-        f = fopen (file, "w");
-        fprintf (f, "P5\n%d %d\n255\n", w, h);
-        for (y = 0; y < h; y++)
-          for (x = 0; x < w; x++) {
-            unsigned long pix = XGetPixel (image, x, y);
-            unsigned long r = (pix & xgwa.visual->red_mask);
-            r = ((r >> 24) | (r >> 16) | (r >> 8) | r);
-            fprintf (f, "%c", (char) r);
+        if (!strcmp (string, curr->string))
+          {
+            if (prev)
+              prev->next = next;       /* Unlink from list */
+            if (curr != data->cache)
+              {
+                curr->next = data->cache;  /* Move to front */
+                data->cache = curr;
+              }
+            return curr;
           }
-        fclose (f);
-        XDestroyImage (image);
-        fprintf (stderr, "%s: wrote %s\n", progname, file);
       }
-#endif
 
-      bitmap_to_texture (dpy, p, xgwa.visual, 
-                         &data->tex_width, &data->tex_height);
-      XFreePixmap (dpy, p);
+  /* Made it to the end of the list without a hit.
+     If the cache is full, empty out the last one on the list,
+     and move it to the front.  Keep the texid.
+   */
+  if (count > data->cache_size)
+    {
+      if (!prev) abort();
+      free (prev->string);
+      prev->string     = 0;
+      prev->tex_width  = 0;
+      prev->tex_height = 0;
+      memset (&prev->extents, 0, sizeof(prev->extents));
+      if (prev2)
+        prev2->next = 0;
+      if (prev != data->cache)
+        prev->next = data->cache;
+      data->cache = prev;
+      return prev;
     }
 
-  /* Reset to the caller's default */
-  glBindTexture (GL_TEXTURE_2D, old_texture);
+  /* Not cached, and cache not full.  Add a new entry at the front,
+     and allocate a new texture for it.
+   */
+  curr = (struct texfont_cache *) calloc (1, sizeof(*prev));
+  glGenTextures (1, &curr->texid);
+  curr->string = 0;
+  curr->next = data->cache;
+  data->cache = curr;
 
-  return data;
+  return curr;
 }
 
 
-/* Width of the string in pixels.
+/* Renders the given string into the prevailing texture.
+   Returns the metrics of the text, and size of the texture.
  */
-int
-texture_string_width (texture_font_data *data, const char *c,
-                      int *height_ret)
+void
+string_to_texture (texture_font_data *data, const char *string,
+                   XCharStruct *extents_ret,
+                   int *tex_width_ret, int *tex_height_ret)
 {
-  int x = 0;
-  int max_w = 0;
-  XFontStruct *f = data->font;
-  int h = f->ascent + f->descent;
-  while (*c)
-    {
-      int cc = *((unsigned char *) c);
-      if (*c == '\n')
-        {
-          if (x > max_w) max_w = x;
-          x = 0;
-          h += f->ascent + f->descent;
-        }
-      else
-        x += (f->per_char && cc >= f->min_char_or_byte2
-              ? f->per_char[cc-f->min_char_or_byte2].width
-              : f->min_bounds.rbearing);
-      c++;
-    }
-  if (x > max_w) max_w = x;
-  if (height_ret) *height_ret = h;
+  Window window = RootWindow (data->dpy, 0);
+  Pixmap p;
+  XGCValues gcv;
+  GC gc;
+  XWindowAttributes xgwa;
+  XRenderColor rcolor;
+  XftColor xftcolor;
+  XftDraw *xftdraw;
+  int width, height;
+  XCharStruct overall;
+
+  /* Measure the string and create a Pixmap of the proper size.
+   */
+  XGetWindowAttributes (data->dpy, window, &xgwa);
+  iterate_texture_string (data, string, 0, 0, 0, 0, &overall);
+  width  = overall.rbearing - overall.lbearing;
+  height = overall.ascent   + overall.descent;
+  if (width  <= 0) width  = 1;
+  if (height <= 0) height = 1;
+  p = XCreatePixmap (data->dpy, window, width, height, xgwa.depth);
+
+  gcv.foreground = BlackPixelOfScreen (xgwa.screen);
+  gc = XCreateGC (data->dpy, p, GCForeground, &gcv);
+  XFillRectangle (data->dpy, p, gc, 0, 0, width, height);
+  XFreeGC (data->dpy, gc);
+
+  /* Render the string into the pixmap.
+   */
+  rcolor.red = rcolor.green = rcolor.blue = rcolor.alpha = 0xFFFF;
+  XftColorAllocValue (data->dpy, xgwa.visual, xgwa.colormap,
+                      &rcolor, &xftcolor);
+  xftdraw = XftDrawCreate (data->dpy, p, xgwa.visual, xgwa.colormap);
+  iterate_texture_string (data, string,
+                          -overall.lbearing, overall.ascent,
+                          xftdraw, &xftcolor, 0);
+  XftDrawDestroy (xftdraw);
+  XftColorFree (data->dpy, xgwa.visual, xgwa.colormap, &xftcolor);
+
+  /* Copy the bits from the Pixmap into a texture, unless it's cached.
+   */
+  bitmap_to_texture (data->dpy, p, xgwa.visual, xgwa.depth, 
+                     &width, &height);
+  XFreePixmap (data->dpy, p);
+
+  if (extents_ret)    *extents_ret    = overall;
+  if (tex_width_ret)  *tex_width_ret  = width;
+  if (tex_height_ret) *tex_height_ret = height;
+}
+
+
+/* Set the various OpenGL parameters for properly rendering things
+   with a texture generated by string_to_texture().
+ */
+void
+enable_texture_string_parameters (void)
+{
+  glEnable (GL_TEXTURE_2D);
+
+  /* Texture-rendering parameters to make font pixmaps tolerable to look at.
+   */
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+                   GL_LINEAR_MIPMAP_LINEAR);
 
-  return max_w;
+  /* LOD bias is part of OpenGL 1.4.
+     GL_EXT_texture_lod_bias has been present since the original iPhone.
+  */
+# if !defined(GL_TEXTURE_LOD_BIAS) && defined(GL_TEXTURE_LOD_BIAS_EXT)
+#   define GL_TEXTURE_LOD_BIAS GL_TEXTURE_LOD_BIAS_EXT
+# endif
+# ifdef GL_TEXTURE_LOD_BIAS
+  glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.25);
+# endif
+  clear_gl_error();  /* invalid enum on iPad 3 */
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+  /* Don't write the transparent parts of the quad into the depth buffer. */
+  glAlphaFunc (GL_GREATER, 0.01);
+  glEnable (GL_ALPHA_TEST);
+  glEnable (GL_BLEND);
+  glDisable (GL_LIGHTING);
+  glDisable (GL_TEXTURE_GEN_S);
+  glDisable (GL_TEXTURE_GEN_T);
+  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 }
 
 
-/* Draws the string in the scene at the current point.
-   Newlines, tab stops and subscripts are honored.
+/* Draws the string in the scene at the origin.
+   Newlines and tab stops are honored.
+   Any numbers inside [] will be rendered as a subscript.
+   Assumes the font has been loaded as with load_texture_font().
+
+   The origin is at the origin of the first character, so subsequent
+   lines of a multi-line string are below that.
  */
 void
 print_texture_string (texture_font_data *data, const char *string)
 {
-  XFontStruct *f = data->font;
-  GLfloat line_height = f->ascent + f->descent;
-# ifdef DO_SUBSCRIPTS
-  GLfloat sub_shift = (line_height * 0.3);
-  Bool sub_p = False;
-# endif /* DO_SUBSCRIPTS */
-  int cw = texture_string_width (data, "m", 0);
-  int tabs = cw * 7;
-  int x, y;
-  unsigned int i;
-  GLint old_texture = 0;
-  GLfloat omatrix[16];
-  int ofront;
+  XCharStruct overall;
+  int tex_width, tex_height;
+  texfont_cache *cache;
+  GLint old_texture;
 
-  glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
-  glGetIntegerv (GL_FRONT_FACE, &ofront);
-  glGetFloatv (GL_TEXTURE_MATRIX, omatrix);
+  if (!*string) return;
 
   clear_gl_error ();
 
-  glPushMatrix();
+  /* Save the prevailing texture ID, and bind ours.  Restored at the end. */
+  glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
 
-  glNormal3f (0, 0, 1);
-  glFrontFace (GL_CW);
+  cache = get_cache (data, string);
 
-  glMatrixMode (GL_TEXTURE);
-  glLoadIdentity ();
-  glMatrixMode (GL_MODELVIEW);
+  glBindTexture (GL_TEXTURE_2D, cache->texid);
+  check_gl_error ("texture font binding");
 
-  x = 0;
-  y = 0;
-  for (i = 0; i < strlen(string); i++)
+  /* Measure the string and make a pixmap that will fit it,
+     unless it's cached.
+   */
+  if (cache->string)
     {
-      unsigned char c = string[i];
-      if (c == '\n')
-        {
-          y -= line_height;
-          x = 0;
-        }
-      else if (c == '\t')
+      overall    = data->cache->extents;
+      tex_width  = data->cache->tex_width;
+      tex_height = data->cache->tex_height;
+    }
+  else
+    {
+# if defined HAVE_JWXYZ && defined JWXYZ_GL
+      /* TODO, JWXYZ_GL: Mixing Xlib and GL has issues. */
+      memset (&overall, 0, sizeof(overall));
+      tex_width = 8;
+      tex_height = 8;
+# else
+      string_to_texture (data, string, &overall, &tex_width, &tex_height);
+# endif
+    }
+
+  {
+    int ofront, oblend;
+    Bool alpha_p, blend_p, light_p, gen_s_p, gen_t_p;
+    GLfloat omatrix[16];
+    GLfloat qx0, qy0, qx1, qy1;
+    GLfloat tx0, ty0, tx1, ty1;
+
+    /* If face culling is not enabled, draw front and back. */
+    Bool draw_back_face_p = !glIsEnabled (GL_CULL_FACE);
+
+    /* Save the prevailing texture environment, and set up ours.
+     */
+    glGetIntegerv (GL_FRONT_FACE, &ofront);
+    glGetIntegerv (GL_BLEND_DST, &oblend);
+    glGetFloatv (GL_TEXTURE_MATRIX, omatrix);
+    blend_p = glIsEnabled (GL_BLEND);
+    alpha_p = glIsEnabled (GL_ALPHA_TEST);
+    light_p = glIsEnabled (GL_LIGHTING);
+    gen_s_p = glIsEnabled (GL_TEXTURE_GEN_S);
+    gen_t_p = glIsEnabled (GL_TEXTURE_GEN_T);
+
+    glPushMatrix();
+
+    glNormal3f (0, 0, 1);
+    glFrontFace (GL_CW);
+
+    glMatrixMode (GL_TEXTURE);
+    glLoadIdentity ();
+    glMatrixMode (GL_MODELVIEW);
+
+    enable_texture_string_parameters();
+
+    /* Draw a quad with that texture on it, possibly using a cached texture.
+       Position the XCharStruct origin at 0,0 in the scene.
+     */
+    qx0 =  overall.lbearing;
+    qy0 = -overall.descent;
+    qx1 =  overall.rbearing;
+    qy1 =  overall.ascent;
+
+    tx0 = 0;
+    ty1 = 0;
+    tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
+    ty0 = (overall.ascent + overall.descent)    / (GLfloat) tex_height;
+
+    glEnable (GL_CULL_FACE);
+    glFrontFace (GL_CCW);
+    glBegin (GL_QUADS);
+    glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
+    glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
+    glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
+    glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
+    glEnd();
+
+    if (draw_back_face_p)
+      {
+        glFrontFace (GL_CW);
+        glBegin (GL_QUADS);
+        glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
+        glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
+        glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
+        glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
+        glEnd();
+        glDisable (GL_CULL_FACE);
+      }
+
+    glPopMatrix();
+
+    /* Reset to the caller's texture environment.
+     */
+    glBindTexture (GL_TEXTURE_2D, old_texture);
+    glFrontFace (ofront);
+    if (!alpha_p) glDisable (GL_ALPHA_TEST);
+    if (!blend_p) glDisable (GL_BLEND);
+    if (light_p) glEnable (GL_LIGHTING);
+    if (gen_s_p) glEnable (GL_TEXTURE_GEN_S);
+    if (gen_t_p) glEnable (GL_TEXTURE_GEN_T);
+
+    glBlendFunc (GL_SRC_ALPHA, oblend);
+  
+    glMatrixMode (GL_TEXTURE);
+    glMultMatrixf (omatrix);
+    glMatrixMode (GL_MODELVIEW);
+
+    check_gl_error ("texture font print");
+
+    /* Store this string into the cache, unless that's where it came from.
+     */
+    if (!cache->string)
+      {
+        cache->string     = strdup (string);
+        cache->extents    = overall;
+        cache->tex_width  = tex_width;
+        cache->tex_height = tex_height;
+      }
+  }
+}
+
+
+/* Draws the string on the window at the given pixel position.
+   Newlines and tab stops are honored.
+   Any numbers inside [] will be rendered as a subscript.
+   Assumes the font has been loaded as with load_texture_font().
+
+   Position is 0 for center, 1 for top left, 2 for bottom left.
+ */
+void
+print_texture_label (Display *dpy,
+                     texture_font_data *data,
+                     int window_width, int window_height,
+                     int position,
+                     const char *string)
+{
+  GLfloat color[4];
+
+  Bool tex_p   = glIsEnabled (GL_TEXTURE_2D);
+  Bool texs_p  = glIsEnabled (GL_TEXTURE_GEN_S);
+  Bool text_p  = glIsEnabled (GL_TEXTURE_GEN_T);
+  Bool depth_p = glIsEnabled (GL_DEPTH_TEST);
+  Bool cull_p  = glIsEnabled (GL_CULL_FACE);
+  Bool fog_p   = glIsEnabled (GL_FOG);
+  GLint ovp[4];
+
+#  ifndef HAVE_JWZGLES
+  GLint opoly[2];
+  glGetIntegerv (GL_POLYGON_MODE, opoly);
+#  endif
+
+  glGetIntegerv (GL_VIEWPORT, ovp);
+
+  glGetFloatv (GL_CURRENT_COLOR, color);
+
+  glEnable (GL_TEXTURE_2D);
+  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  glPolygonMode (GL_FRONT, GL_FILL);
+
+  glDisable (GL_TEXTURE_GEN_S);
+  glDisable (GL_TEXTURE_GEN_T);
+  glDisable (GL_CULL_FACE);
+  glDisable (GL_FOG);
+
+  glDisable (GL_DEPTH_TEST);
+
+  /* Each matrix mode has its own stack, so we need to push/pop
+     them separately.
+   */
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  {
+    glLoadIdentity();
+
+    glMatrixMode(GL_MODELVIEW);
+    glPushMatrix();
+    {
+      XCharStruct cs;
+      int ascent, descent;
+      int x, y, w, h, swap;
+      /* int rot = (int) current_device_rotation(); */
+      int rot = 0;  /* Since GL hacks rotate now */
+
+      glLoadIdentity();
+      glViewport (0, 0, window_width, window_height);
+      glOrtho (0, window_width, 0, window_height, -1, 1);
+
+      while (rot <= -180) rot += 360;
+      while (rot >   180) rot -= 360;
+
+      texture_string_metrics (data, string, &cs, &ascent, &descent);
+      h = cs.ascent + cs.descent;
+      w  = cs.width;
+
+# ifdef USE_IPHONE
+      {
+        /* Size of the font is in points, so scale iOS pixels to points. */
+        GLfloat scale = ((window_width > window_height
+                          ? window_width : window_height)
+                         / 768.0);
+        if (scale < 1) scale = 1;
+
+        /* jwxyz-XLoadFont has already doubled the font size, to compensate
+           for physically smaller screens.  Undo that, since OpenGL hacks
+           use full-resolution framebuffers, unlike X11 hacks. */
+        scale /= 2;
+
+        window_width  /= scale;
+        window_height /= scale;
+        glScalef (scale, scale, scale);
+      }
+# endif /* USE_IPHONE */
+
+      if (rot > 135 || rot < -135)             /* 180 */
         {
-          x = ((x + tabs) / tabs) * tabs;  /* tab to tab stop */
+          glTranslatef (window_width, window_height, 0);
+          glRotatef (180, 0, 0, 1);
         }
-# ifdef DO_SUBSCRIPTS
-      else if (c == '[' && (isdigit (string[i+1])))
+      else if (rot > 45)                       /* 90 */
         {
-          sub_p = True;
-          y -= sub_shift;
+          glTranslatef (window_width, 0, 0);
+          glRotatef (90, 0, 0, 1);
+          swap = window_width;
+          window_width = window_height;
+          window_height = swap;
         }
-      else if (c == ']' && sub_p)
+      else if (rot < -45)                      /* 270 */
         {
-          sub_p = False;
-          y += sub_shift;
+          glTranslatef(0, window_height, 0);
+          glRotatef (-90, 0, 0, 1);
+          swap = window_width;
+          window_width = window_height;
+          window_height = swap;
         }
-# endif /* DO_SUBSCRIPTS */
-      else
-        {
-          /* For a small font, the texture is divided into 16x16 rectangles
-             whose size are the max_bounds charcell of the font.  Within each
-             rectangle, the individual characters' charcells sit in the upper
-             left.
-
-             For a larger font, the texture will itself be subdivided, to
-             keep the texture sizes small (in that case we deal with, e.g.,
-             4 grids of 8x8 characters instead of 1 grid of 16x16.)
-
-             Within each texture:
-
-               [A]----------------------------
-                |     |           |   |      |
-                |   l |         w |   | r    |
-                |   b |         i |   | b    |
-                |   e |         d |   | e    |
-                |   a |         t |   | a    |
-                |   r |         h |   | r    |
-                |   i |           |   | i    |
-                |   n |           |   | n    |
-                |   g |           |   | g    |
-                |     |           |   |      |
-                |----[B]----------|---|      |
-                |     |   ascent  |   |      |
-                |     |           |   |      |
-                |     |           |   |      |
-                |--------------------[C]     |
-                |         descent            |
-                |                            | cell_width,
-                ------------------------------ cell_height
-
-             We want to make a quad from point A to point C.
-             We want to position that quad so that point B lies at x,y.
-           */
-          int lbearing = (f->per_char && c >= f->min_char_or_byte2
-                          ? f->per_char[c - f->min_char_or_byte2].lbearing
-                          : f->min_bounds.lbearing);
-          int rbearing = (f->per_char && c >= f->min_char_or_byte2
-                          ? f->per_char[c - f->min_char_or_byte2].rbearing
-                          : f->max_bounds.rbearing);
-          int ascent   = (f->per_char && c >= f->min_char_or_byte2
-                          ? f->per_char[c - f->min_char_or_byte2].ascent
-                          : f->max_bounds.ascent);
-          int descent  = (f->per_char && c >= f->min_char_or_byte2
-                          ? f->per_char[c - f->min_char_or_byte2].descent
-                          : f->max_bounds.descent);
-          int cwidth   = (f->per_char && c >= f->min_char_or_byte2
-                          ? f->per_char[c - f->min_char_or_byte2].width
-                          : f->max_bounds.width);
-
-          unsigned char cc = c % (256 / data->ntextures);
-
-          int gs = (16 / data->grid_mag);                /* grid size */
-
-          int ax = ((int) cc % gs) * data->cell_width;    /* point A */
-          int ay = ((int) cc / gs) * data->cell_height;
-
-          int bx = ax - lbearing;                         /* point B */
-          int by = ay + ascent;
-
-          int cx = bx + rbearing;                         /* point C */
-          int cy = by + descent;
-
-          GLfloat tax = (GLfloat) ax / data->tex_width;  /* tex coords of A */
-          GLfloat tay = (GLfloat) ay / data->tex_height;
-
-          GLfloat tcx = (GLfloat) cx / data->tex_width;  /* tex coords of C */
-          GLfloat tcy = (GLfloat) cy / data->tex_height;
-
-          GLfloat qx0 = x + lbearing;                   /* quad top left */
-          GLfloat qy0 = y + ascent;
-          GLfloat qx1 = qx0 + rbearing - lbearing;       /* quad bot right */
-          GLfloat qy1 = qy0 - (ascent + descent);
-
-          if (cwidth > 0 && c != ' ')
-            {
-              int which = c / (256 / data->ntextures);
-              if (which >= data->ntextures) abort();
-              glBindTexture (GL_TEXTURE_2D, data->texid[which]);
-
-              glBegin (GL_QUADS);
-              glTexCoord2f (tax, tay); glVertex3f (qx0, qy0, 0);
-              glTexCoord2f (tcx, tay); glVertex3f (qx1, qy0, 0);
-              glTexCoord2f (tcx, tcy); glVertex3f (qx1, qy1, 0);
-              glTexCoord2f (tax, tcy); glVertex3f (qx0, qy1, 0);
-              glEnd();
-#if 0
-              glDisable(GL_TEXTURE_2D);
-              glBegin (GL_LINE_LOOP);
-              glTexCoord2f (tax, tay); glVertex3f (qx0, qy0, 0);
-              glTexCoord2f (tcx, tay); glVertex3f (qx1, qy0, 0);
-              glTexCoord2f (tcx, tcy); glVertex3f (qx1, qy1, 0);
-              glTexCoord2f (tax, tcy); glVertex3f (qx0, qy1, 0);
-              glEnd();
-              glEnable(GL_TEXTURE_2D);
-#endif
-            }
 
-          x += cwidth;
-        }
+      switch (position) {
+      case 0:                                  /* center */
+        x = (window_width  - w) / 2;
+        y = (window_height + h) / 2 - ascent;
+        break;
+      case 1:                                  /* top */
+        x = ascent;
+        y = window_height - ascent*2;
+        break;
+      case 2:                                  /* bottom */
+        x = ascent;
+        y = h;
+        break;
+      default:
+        abort();
       }
+
+      glTranslatef (x, y, 0);
+
+      /* draw the text five times, to give it a border. */
+      {
+        const XPoint offsets[] = {{ -1, -1 },
+                                  { -1,  1 },
+                                  {  1,  1 },
+                                  {  1, -1 },
+                                  {  0,  0 }};
+        int i;
+
+        glColor3f (0, 0, 0);
+        for (i = 0; i < countof(offsets); i++)
+          {
+            if (offsets[i].x == 0)
+              glColor4fv (color);
+            glPushMatrix();
+            glTranslatef (offsets[i].x, offsets[i].y, 0);
+            print_texture_string (data, string);
+            glPopMatrix();
+          }
+      }
+    }
+    glPopMatrix();
+  }
+  glMatrixMode(GL_PROJECTION);
   glPopMatrix();
 
-  /* Reset to the caller's default */
-  glBindTexture (GL_TEXTURE_2D, old_texture);
-  glFrontFace (ofront);
-  
-  glMatrixMode (GL_TEXTURE);
-  glMultMatrixf (omatrix);
-  glMatrixMode (GL_MODELVIEW);
+  if (tex_p)   glEnable (GL_TEXTURE_2D); else glDisable (GL_TEXTURE_2D);
+  if (texs_p)  glEnable (GL_TEXTURE_GEN_S);/*else glDisable(GL_TEXTURE_GEN_S);*/
+  if (text_p)  glEnable (GL_TEXTURE_GEN_T);/*else glDisable(GL_TEXTURE_GEN_T);*/
+  if (depth_p) glEnable (GL_DEPTH_TEST); else glDisable (GL_DEPTH_TEST);
+  if (cull_p)  glEnable (GL_CULL_FACE); /*else glDisable (GL_CULL_FACE);*/
+  if (fog_p)   glEnable (GL_FOG); /*else glDisable (GL_FOG);*/
+
+  glViewport (ovp[0], ovp[1], ovp[2], ovp[3]);
+
+# ifndef HAVE_JWZGLES
+  glPolygonMode (GL_FRONT, opoly[0]);
+# endif
+
+  glMatrixMode(GL_MODELVIEW);
+}
 
-  check_gl_error ("texture font print");
+
+#ifdef HAVE_JWXYZ
+char *
+texfont_unicode_character_name (texture_font_data *data, unsigned long uc)
+{
+  Font fid = data->xftfont->xfont->fid;
+  return jwxyz_unicode_character_name (data->dpy, fid, uc);
 }
+#endif /* HAVE_JWXYZ */
+
+
 
 /* Releases the font and texture.
  */
 void
 free_texture_font (texture_font_data *data)
 {
-  int i;
-  if (data->font)
-    XFreeFont (data->dpy, data->font);
-  for (i = 0; i < data->ntextures; i++)
-    if (data->texid[i])
-      glDeleteTextures (1, &data->texid[i]);
+  while (data->cache)
+    {
+      texfont_cache *next = data->cache->next;
+      glDeleteTextures (1, &data->cache->texid);
+      free (data->cache);
+      data->cache = next;
+    }
+  if (data->xftfont)
+    XftFontClose (data->dpy, data->xftfont);
   free (data);
 }