From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / glx / texfont.c
index 29cf5336c950015c3de72b834114fe274eb06255..2305a937769d1a9e637147ee156af168e5b2bc76 100644 (file)
@@ -1,4 +1,4 @@
-/* texfonts, Copyright (c) 2005 Jamie Zawinski <jwz@jwz.org>
+/* texfonts, Copyright (c) 2005-2013 Jamie Zawinski <jwz@jwz.org>
  * Loads X11 fonts into textures for use with OpenGL.
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  */
 
 
-#include "config.h"
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <ctype.h>
-#include <GL/glx.h>
-#include <GL/glu.h>
+
+#ifdef HAVE_COCOA
+# ifdef USE_IPHONE
+#  include "jwzgles.h"
+# else
+#  include <OpenGL/glu.h>
+# endif
+#else
+# include <GL/glx.h>
+# include <GL/glu.h>
+#endif
+
+#ifdef HAVE_JWZGLES
+# include "jwzgles.h"
+#endif /* HAVE_JWZGLES */
+
 #include "resources.h"
 #include "texfont.h"
 
@@ -31,9 +48,13 @@ extern char *progname;
 struct texture_font_data {
   Display *dpy;
   XFontStruct *font;
-  GLuint texid;
-  int cell_width, cell_height;
-  int tex_width, tex_height;
+  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 */
 };
 
 
@@ -41,8 +62,9 @@ struct texture_font_data {
 static int
 to_pow2 (int i)
 {
-  static unsigned int pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
-                                 2048, 4096, 8192, 16384, 32768, 65536 };
+  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];
@@ -50,13 +72,16 @@ to_pow2 (int i)
 }
 
 
-/* Given a Pixmap of depth 1, converts it to an OpenGL luminance mipmap.
-   The 1 bits are drawn, the 0 bits are alpha.
+/* Given a Pixmap (of screen depth), converts it to an OpenGL luminance mipmap.
+   RGB are averaged to grayscale, and the resulting value is treated as alpha.
    Pass in the size of the pixmap; the size of the texture is returned
    (it may be larger, since GL like powers of 2.)
+
+   We use a screen-depth pixmap instead of a 1bpp bitmap so that if the fonts
+   were drawn with antialiasing, that is preserved.
  */
 static void
-bitmap_to_texture (Display *dpy, Pixmap p, int *wP, int *hP)
+bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
 {
   Bool mipmap_p = True;
   int ow = *wP;
@@ -64,17 +89,45 @@ bitmap_to_texture (Display *dpy, Pixmap p, int *wP, int *hP)
   int w2 = to_pow2 (ow);
   int h2 = to_pow2 (oh);
   int x, y;
-  XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, XYPixmap);
-  unsigned char *data = (unsigned char *) calloc (w2, (h2 + 1));
+  XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap);
+  unsigned char *data = (unsigned char *) calloc (w2 * 2, (h2 + 1));
   unsigned char *out = data;
+
+  /* OpenGLES doesn't support GL_INTENSITY, so instead of using a
+     texture with 1 byte per pixel, the intensity value, we have
+     to use 2 bytes per pixel: solid white, and an alpha value.
+   */
+# ifdef HAVE_JWZGLES
+#  undef GL_INTENSITY
+# endif
+
+# ifdef GL_INTENSITY
   GLuint iformat = GL_INTENSITY;
-  GLuint format = GL_LUMINANCE;
-  GLuint type = GL_UNSIGNED_BYTE;
+  GLuint format  = GL_LUMINANCE;
+# else
+  GLuint iformat = GL_LUMINANCE_ALPHA;
+  GLuint format  = GL_LUMINANCE_ALPHA;
+# endif
+  GLuint type    = GL_UNSIGNED_BYTE;
+
+# ifdef HAVE_JWZGLES
+  /* This would work, but it's wasteful for no benefit. */
+  mipmap_p = False;
+# endif
 
   for (y = 0; y < h2; y++)
-    for (x = 0; x < w2; x++)
-      *out++ = (x >= ow || y >= oh ? 0 :
-                XGetPixel (image, x, y) ? 255 : 0);
+    for (x = 0; x < w2; x++) {
+      unsigned long pixel = (x >= ow || y >= oh ? 0 : XGetPixel (image, x, y));
+      /* instead of averaging all three channels, let's just use red,
+         and assume it was already grayscale. */
+      unsigned long r = pixel & visual->red_mask;
+      /* This goofy trick is to make any of RGBA/ABGR/ARGB work. */
+      pixel = ((r >> 24) | (r >> 16) | (r >> 8) | r) & 0xFF;
+# ifndef GL_INTENSITY
+      *out++ = 0xFF;  /* 2 bytes per pixel */
+# endif
+      *out++ = pixel;
+    }
   XDestroyImage (image);
   image = 0;
 
@@ -85,7 +138,7 @@ bitmap_to_texture (Display *dpy, Pixmap p, int *wP, int *hP)
 
   {
     char msg[100];
-    sprintf (msg, "%s (%d x %d)",
+    sprintf (msg, "texture font %s (%d x %d)",
              mipmap_p ? "gluBuild2DMipmaps" : "glTexImage2D",
              w2, h2);
     check_gl_error (msg);
@@ -112,22 +165,36 @@ bitmap_to_texture (Display *dpy, Pixmap p, 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;
-  const char *font = get_string_resource (res, "Font");
-  const char *def1 = "-*-times-bold-r-normal-*-240-*";
-  const char *def2 = "-*-times-bold-r-normal-*-180-*";
+  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 *def3 = "fixed";
   XFontStruct *f;
+  int which;
+  GLint old_texture = 0;
+
+  glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
+
+  if (!strcmp (res, "fpsFont"))
+    def1 = "-*-courier-bold-r-normal-*-180-*";  /* Kludge. Sue me. */
+
+  XGetWindowAttributes (dpy, root, &xgwa);
 
   if (!res || !*res) abort();
-  if (!font) font = def1;
+  if (!font) font = strdup(def1);
 
   f = XLoadQueryFont(dpy, font);
   if (!f && !!strcmp (font, def1))
     {
       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
                progname, font, def1);
-      font = def1;
+      free (font);
+      font = strdup (def1);
       f = XLoadQueryFont(dpy, font);
     }
 
@@ -135,7 +202,8 @@ load_texture_font (Display *dpy, char *res)
     {
       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
                progname, font, def2);
-      font = def2;
+      free (font);
+      font = strdup (def2);
       f = XLoadQueryFont(dpy, font);
     }
 
@@ -143,7 +211,8 @@ load_texture_font (Display *dpy, char *res)
     {
       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
                progname, font, def3);
-      font = def3;
+      free (font);
+      font = strdup (def3);
       f = XLoadQueryFont(dpy, font);
     }
 
@@ -154,78 +223,139 @@ load_texture_font (Display *dpy, char *res)
       exit (1);
     }
 
+  free (font);
+  font = 0;
+
   data = (texture_font_data *) calloc (1, sizeof(*data));
   data->dpy = dpy;
   data->font = f;
 
-  /* Create a pixmap big enough to fit every character in the font.
-     Make it square-ish, since GL likes dimensions to be powers of 2.
+  /* 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.
    */
   {
-    Screen *screen = DefaultScreenOfDisplay (dpy);
-    Window root = RootWindowOfScreen (screen);
-    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 w = cw * 16;
-    int h = ch * 16;
-    int i;
-
-    data->cell_width  = cw;
-    data->cell_height = ch;
-
-    p = XCreatePixmap (dpy, root, w, h, 1);
-    gcv.font = f->fid;
-    gcv.foreground = 0;
-    gcv.background = 0;
-    gc = XCreateGC (dpy, p, (GCFont|GCForeground|GCBackground), &gcv);
-    XFillRectangle (dpy, p, gc, 0, 0, w, h);
-    XSetForeground (dpy, gc, 1);
-    for (i = 0; i < 256; i++)
+    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
+  }
+
+  for (which = 0; which < data->ntextures; which++)
+    {
+      /* 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++)
+        {
+          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);
+        }
+      XFreeGC (dpy, gc);
+
+      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 0  /* debugging: splat the bitmap onto the desktop root window */
       {
-        char c = (char) i;
-        int x = (i % 16) * cw;
-        int y = (i / 16) * ch;
-
-        /* See comment in print_texture_string for bit layout explanation. */
-
-        int lbearing = (f->per_char
-                        ? f->per_char[i - f->min_char_or_byte2].lbearing
-                        : f->min_bounds.lbearing);
-        int ascent   = (f->per_char
-                        ? f->per_char[i - f->min_char_or_byte2].ascent
-                        : f->max_bounds.ascent);
-        int width    = (f->per_char
-                        ? f->per_char[i - f->min_char_or_byte2].width
-                        : f->max_bounds.width);
-
-        if (width == 0) continue;
-        XDrawString (dpy, p, gc, x - lbearing, y + ascent, &c, 1);
+        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);
       }
-    XFreeGC (dpy, gc);
+#endif
 
-    glGenTextures (1, &data->texid);
-    glBindTexture (GL_TEXTURE_2D, data->texid);
-    data->tex_width  = w;
-    data->tex_height = h;
+#if 0  /* debugging: write the bitmap to a pgm file */
+      {
+        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);
+          }
+        fclose (f);
+        XDestroyImage (image);
+        fprintf (stderr, "%s: wrote %s\n", progname, file);
+      }
+#endif
 
-#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));
-      XCopyPlane (dpy, p, win, gc2, 0, 0, w, h, 0, 0, 1);
-      XFreeGC (dpy, gc2);
-      XSync(dpy, False);
+      bitmap_to_texture (dpy, p, xgwa.visual, 
+                         &data->tex_width, &data->tex_height);
+      XFreePixmap (dpy, p);
     }
-#endif
 
-    bitmap_to_texture (dpy, p, &data->tex_width, &data->tex_height);
-    XFreePixmap (dpy, p);
-  }
+  /* Reset to the caller's default */
+  glBindTexture (GL_TEXTURE_2D, old_texture);
 
   return data;
 }
@@ -235,26 +365,36 @@ load_texture_font (Display *dpy, char *res)
  */
 int
 texture_string_width (texture_font_data *data, const char *c,
-                      int *line_height_ret)
+                      int *height_ret)
 {
-  int w = 0;
+  int x = 0;
+  int max_w = 0;
   XFontStruct *f = data->font;
+  int h = f->ascent + f->descent;
   while (*c)
     {
       int cc = *((unsigned char *) c);
-      w += (f->per_char
-            ? f->per_char[cc-f->min_char_or_byte2].width
-            : f->max_bounds.width);
+      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 (line_height_ret)
-    *line_height_ret = f->ascent + f->descent;
-  return w;
+  if (x > max_w) max_w = x;
+  if (height_ret) *height_ret = h;
+
+  return max_w;
 }
 
 
-/* Draws the string in the scene at the origin.
-   Newlines and tab stops are honored.
+/* Draws the string in the scene at the current point.
+   Newlines, tab stops and subscripts are honored.
  */
 void
 print_texture_string (texture_font_data *data, const char *string)
@@ -269,17 +409,30 @@ print_texture_string (texture_font_data *data, const char *string)
   int tabs = cw * 7;
   int x, y;
   unsigned int i;
+  GLint old_texture = 0;
+  GLfloat omatrix[16];
+  int ofront;
+
+  glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
+  glGetIntegerv (GL_FRONT_FACE, &ofront);
+  glGetFloatv (GL_TEXTURE_MATRIX, omatrix);
+
+  clear_gl_error ();
 
   glPushMatrix();
 
-  glBindTexture (GL_TEXTURE_2D, data->texid);
   glNormal3f (0, 0, 1);
+  glFrontFace (GL_CW);
+
+  glMatrixMode (GL_TEXTURE);
+  glLoadIdentity ();
+  glMatrixMode (GL_MODELVIEW);
 
   x = 0;
   y = 0;
   for (i = 0; i < strlen(string); i++)
     {
-      char c = string[i];
+      unsigned char c = string[i];
       if (c == '\n')
         {
           y -= line_height;
@@ -287,7 +440,8 @@ print_texture_string (texture_font_data *data, const char *string)
         }
       else if (c == '\t')
         {
-          x = ((x + tabs) / tabs) * tabs;  /* tab to tab stop */
+          if (tabs)
+            x = ((x + tabs) / tabs) * tabs;  /* tab to tab stop */
         }
 # ifdef DO_SUBSCRIPTS
       else if (c == '[' && (isdigit (string[i+1])))
@@ -303,9 +457,16 @@ print_texture_string (texture_font_data *data, const char *string)
 # endif /* DO_SUBSCRIPTS */
       else
         {
-          /* 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 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]----------------------------
                 |     |           |   |      |
@@ -330,24 +491,28 @@ print_texture_string (texture_font_data *data, const char *string)
              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
+          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
+          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
+          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
+          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
+          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);
 
-          int ax = ((int) c % 16) * data->cell_width;     /* point A */
-          int ay = ((int) c / 16) * data->cell_height;
+          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;
@@ -368,6 +533,10 @@ print_texture_string (texture_font_data *data, const char *string)
 
           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);
@@ -390,6 +559,16 @@ print_texture_string (texture_font_data *data, const char *string)
         }
       }
   glPopMatrix();
+
+  /* Reset to the caller's default */
+  glBindTexture (GL_TEXTURE_2D, old_texture);
+  glFrontFace (ofront);
+  
+  glMatrixMode (GL_TEXTURE);
+  glMultMatrixf (omatrix);
+  glMatrixMode (GL_MODELVIEW);
+
+  check_gl_error ("texture font print");
 }
 
 /* Releases the font and texture.
@@ -397,9 +576,11 @@ print_texture_string (texture_font_data *data, const char *string)
 void
 free_texture_font (texture_font_data *data)
 {
+  int i;
   if (data->font)
     XFreeFont (data->dpy, data->font);
-  if (data->texid)
-    glDeleteTextures (1, &data->texid);
+  for (i = 0; i < data->ntextures; i++)
+    if (data->texid[i])
+      glDeleteTextures (1, &data->texid[i]);
   free (data);
 }