-/* texfonts, Copyright (c) 2005 Jamie Zawinski <jwz@jwz.org>
+/* texfonts, Copyright (c) 2005-2007 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
+# include <OpenGL/glu.h>
+#else
+# include <GL/glx.h>
+# include <GL/glu.h>
+#endif
+
#include "resources.h"
#include "texfont.h"
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 */
};
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];
}
-/* 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;
int w2 = to_pow2 (ow);
int h2 = to_pow2 (oh);
int x, y;
- XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, XYPixmap);
+ XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap);
unsigned char *data = (unsigned char *) calloc (w2, (h2 + 1));
unsigned char *out = data;
GLuint iformat = GL_INTENSITY;
GLuint type = GL_UNSIGNED_BYTE;
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;
+ pixel = ((r >> 24) | (r >> 16) | (r >> 8) | r) & 0xFF;
+ *out++ = pixel;
+ }
XDestroyImage (image);
image = 0;
{
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);
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");
+ char *font = get_string_resource (dpy, res, "Font");
const char *def1 = "-*-times-bold-r-normal-*-240-*";
const char *def2 = "-*-times-bold-r-normal-*-180-*";
const char *def3 = "fixed";
XFontStruct *f;
+ int which;
+
+ check_gl_error ("stale texture font");
+
+ 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);
}
{
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);
}
{
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);
}
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++)
- {
- 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);
- }
- XFreeGC (dpy, gc);
+ 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
+ }
- glGenTextures (1, &data->texid);
- glBindTexture (GL_TEXTURE_2D, data->texid);
- data->tex_width = w;
- data->tex_height = h;
+ 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
+ ? f->per_char[ii - f->min_char_or_byte2].lbearing
+ : f->min_bounds.lbearing);
+ int ascent = (f->per_char
+ ? f->per_char[ii - f->min_char_or_byte2].ascent
+ : f->max_bounds.ascent);
+ int width = (f->per_char
+ ? 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 */
- {
- 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);
- }
+ {
+ 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
- bitmap_to_texture (dpy, p, &data->tex_width, &data->tex_height);
- XFreePixmap (dpy, p);
- }
+#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
+
+ bitmap_to_texture (dpy, p, xgwa.visual,
+ &data->tex_width, &data->tex_height);
+ XFreePixmap (dpy, p);
+ }
return data;
}
int x, y;
unsigned int i;
+ clear_gl_error ();
+
glPushMatrix();
- glBindTexture (GL_TEXTURE_2D, data->texid);
glNormal3f (0, 0, 1);
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;
# 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]----------------------------
| | | | |
? 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;
+ 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;
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);
}
}
glPopMatrix();
+
+ check_gl_error ("texture font print");
}
/* Releases the font and texture.
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);
}