http://ftp.nluug.nl/pub/os/Linux/distr/pardusrepo/sources/xscreensaver-5.02.tar.gz
[xscreensaver] / hacks / glx / texfont.c
1 /* texfonts, Copyright (c) 2005-2007 Jamie Zawinski <jwz@jwz.org>
2  * Loads X11 fonts into textures for use with OpenGL.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22
23 #ifdef HAVE_COCOA
24 # include <OpenGL/glu.h>
25 #else
26 # include <GL/glx.h>
27 # include <GL/glu.h>
28 #endif
29
30 #include "resources.h"
31 #include "texfont.h"
32
33 /* These are in xlock-gl.c */
34 extern void clear_gl_error (void);
35 extern void check_gl_error (const char *type);
36
37 /* screenhack.h */
38 extern char *progname;
39
40 struct texture_font_data {
41   Display *dpy;
42   XFontStruct *font;
43   int cell_width, cell_height;  /* maximal charcell */
44   int tex_width, tex_height;    /* size of each texture */
45
46   int grid_mag;                 /* 1,  2,  4, or 8 */
47   int ntextures;                /* 1,  4, 16, or 64 (grid_mag ^ 2) */
48
49   GLuint texid[64];             /* must hold ntextures */
50 };
51
52
53 /* return the next larger power of 2. */
54 static int
55 to_pow2 (int i)
56 {
57   static const unsigned int pow2[] = { 
58     1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 
59     2048, 4096, 8192, 16384, 32768, 65536 };
60   int j;
61   for (j = 0; j < sizeof(pow2)/sizeof(*pow2); j++)
62     if (pow2[j] >= i) return pow2[j];
63   abort();  /* too big! */
64 }
65
66
67 /* Given a Pixmap (of screen depth), converts it to an OpenGL luminance mipmap.
68    RGB are averaged to grayscale, and the resulting value is treated as alpha.
69    Pass in the size of the pixmap; the size of the texture is returned
70    (it may be larger, since GL like powers of 2.)
71
72    We use a screen-depth pixmap instead of a 1bpp bitmap so that if the fonts
73    were drawn with antialiasing, that is preserved.
74  */
75 static void
76 bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
77 {
78   Bool mipmap_p = True;
79   int ow = *wP;
80   int oh = *hP;
81   int w2 = to_pow2 (ow);
82   int h2 = to_pow2 (oh);
83   int x, y;
84   XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap);
85   unsigned char *data = (unsigned char *) calloc (w2, (h2 + 1));
86   unsigned char *out = data;
87   GLuint iformat = GL_INTENSITY;
88   GLuint format = GL_LUMINANCE;
89   GLuint type = GL_UNSIGNED_BYTE;
90
91   for (y = 0; y < h2; y++)
92     for (x = 0; x < w2; x++) {
93       unsigned long pixel = (x >= ow || y >= oh ? 0 : XGetPixel (image, x, y));
94       /* instead of averaging all three channels, let's just use red,
95          and assume it was already grayscale. */
96       unsigned long r = pixel & visual->red_mask;
97       pixel = ((r >> 24) | (r >> 16) | (r >> 8) | r) & 0xFF;
98       *out++ = pixel;
99     }
100   XDestroyImage (image);
101   image = 0;
102
103   if (mipmap_p)
104     gluBuild2DMipmaps (GL_TEXTURE_2D, iformat, w2, h2, format, type, data);
105   else
106     glTexImage2D (GL_TEXTURE_2D, 0, iformat, w2, h2, 0, format, type, data);
107
108   {
109     char msg[100];
110     sprintf (msg, "texture font %s (%d x %d)",
111              mipmap_p ? "gluBuild2DMipmaps" : "glTexImage2D",
112              w2, h2);
113     check_gl_error (msg);
114   }
115
116
117   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
118   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
119                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
120
121   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
122   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
123
124   free (data);
125
126   *wP = w2;
127   *hP = h2;
128 }
129
130
131 /* Loads the font named by the X resource "res" and returns
132    a texture-font object.
133 */
134 texture_font_data *
135 load_texture_font (Display *dpy, char *res)
136 {
137   Screen *screen = DefaultScreenOfDisplay (dpy);
138   Window root = RootWindowOfScreen (screen);
139   XWindowAttributes xgwa;
140
141   texture_font_data *data = 0;
142   char *font = get_string_resource (dpy, res, "Font");
143   const char *def1 = "-*-times-bold-r-normal-*-240-*";
144   const char *def2 = "-*-times-bold-r-normal-*-180-*";
145   const char *def3 = "fixed";
146   XFontStruct *f;
147   int which;
148
149   check_gl_error ("stale texture font");
150
151   XGetWindowAttributes (dpy, root, &xgwa);
152
153   if (!res || !*res) abort();
154   if (!font) font = strdup(def1);
155
156   f = XLoadQueryFont(dpy, font);
157   if (!f && !!strcmp (font, def1))
158     {
159       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
160                progname, font, def1);
161       free (font);
162       font = strdup (def1);
163       f = XLoadQueryFont(dpy, font);
164     }
165
166   if (!f && !!strcmp (font, def2))
167     {
168       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
169                progname, font, def2);
170       free (font);
171       font = strdup (def2);
172       f = XLoadQueryFont(dpy, font);
173     }
174
175   if (!f && !!strcmp (font, def3))
176     {
177       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
178                progname, font, def3);
179       free (font);
180       font = strdup (def3);
181       f = XLoadQueryFont(dpy, font);
182     }
183
184   if (!f)
185     {
186       fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
187                progname, font);
188       exit (1);
189     }
190
191   free (font);
192   font = 0;
193
194   data = (texture_font_data *) calloc (1, sizeof(*data));
195   data->dpy = dpy;
196   data->font = f;
197
198   /* Figure out how many textures to use.
199      E.g., if we need 1024x1024 bits, use four 512x512 textures,
200      to be gentle to machines with low texture size limits.
201    */
202   {
203     int w = to_pow2 (16 * (f->max_bounds.rbearing - f->min_bounds.lbearing));
204     int h = to_pow2 (16 * (f->max_bounds.ascent   + f->max_bounds.descent));
205     int i = (w > h ? w : h);
206
207     if      (i <= 512)  data->grid_mag = 1;  /*  1 tex of 16x16 chars */
208     else if (i <= 1024) data->grid_mag = 2;  /*  4 tex of 8x8 chars */
209     else if (i <= 2048) data->grid_mag = 4;  /* 16 tex of 4x4 chars */
210     else                data->grid_mag = 8;  /* 32 tex of 2x2 chars */
211
212     data->ntextures = data->grid_mag * data->grid_mag;
213
214 # if 0
215     fprintf (stderr,
216              "%s: %dx%d grid of %d textures of %dx%d chars (%dx%d bits)\n",
217              progname,
218              data->grid_mag, data->grid_mag,
219              data->ntextures,
220              16 / data->grid_mag, 16 / data->grid_mag,
221              i, i);
222 # endif
223   }
224
225   for (which = 0; which < data->ntextures; which++)
226     {
227       /* Create a pixmap big enough to fit every character in the font.
228          (modulo the "ntextures" scaling.)
229          Make it square-ish, since GL likes dimensions to be powers of 2.
230        */
231       XGCValues gcv;
232       GC gc;
233       Pixmap p;
234       int cw = f->max_bounds.rbearing - f->min_bounds.lbearing;
235       int ch = f->max_bounds.ascent   + f->max_bounds.descent;
236       int grid_size = (16 / data->grid_mag);
237       int w = cw * grid_size;
238       int h = ch * grid_size;
239       int i;
240
241       data->cell_width  = cw;
242       data->cell_height = ch;
243
244       p = XCreatePixmap (dpy, root, w, h, xgwa.depth);
245       gcv.font = f->fid;
246       gcv.foreground = BlackPixelOfScreen (xgwa.screen);
247       gcv.background = BlackPixelOfScreen (xgwa.screen);
248       gc = XCreateGC (dpy, p, (GCFont|GCForeground|GCBackground), &gcv);
249       XFillRectangle (dpy, p, gc, 0, 0, w, h);
250       XSetForeground (dpy, gc, WhitePixelOfScreen (xgwa.screen));
251       for (i = 0; i < 256 / data->ntextures; i++)
252         {
253           int ii = (i + (which * 256 / data->ntextures));
254           char c = (char) ii;
255           int x = (i % grid_size) * cw;
256           int y = (i / grid_size) * ch;
257
258           /* See comment in print_texture_string for bit layout explanation.
259            */
260           int lbearing = (f->per_char
261                           ? f->per_char[ii - f->min_char_or_byte2].lbearing
262                           : f->min_bounds.lbearing);
263           int ascent   = (f->per_char
264                           ? f->per_char[ii - f->min_char_or_byte2].ascent
265                           : f->max_bounds.ascent);
266           int width    = (f->per_char
267                           ? f->per_char[ii - f->min_char_or_byte2].width
268                           : f->max_bounds.width);
269
270           if (width == 0) continue;
271           XDrawString (dpy, p, gc, x - lbearing, y + ascent, &c, 1);
272         }
273       XFreeGC (dpy, gc);
274
275       glGenTextures (1, &data->texid[which]);
276       glBindTexture (GL_TEXTURE_2D, data->texid[which]);
277       check_gl_error ("texture font load");
278       data->tex_width  = w;
279       data->tex_height = h;
280
281 #if 0  /* debugging: splat the bitmap onto the desktop root window */
282       {
283         Window win = RootWindow (dpy, 0);
284         GC gc2 = XCreateGC (dpy, win, 0, &gcv);
285         XSetForeground (dpy, gc2, BlackPixel (dpy, 0));
286         XSetBackground (dpy, gc2, WhitePixel (dpy, 0));
287         XCopyArea (dpy, p, win, gc2, 0, 0, w, h, 0, 0);
288         XFreeGC (dpy, gc2);
289         XSync(dpy, False);
290         usleep (100000);
291       }
292 #endif
293
294 #if 0  /* debugging: write the bitmap to a pgm file */
295       {
296         char file[255];
297         XImage *image;
298         int x, y;
299         FILE *f;
300         sprintf (file, "/tmp/%02d.pgm", which);
301         image = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
302         f = fopen (file, "w");
303         fprintf (f, "P5\n%d %d\n255\n", w, h);
304         for (y = 0; y < h; y++)
305           for (x = 0; x < w; x++) {
306             unsigned long pix = XGetPixel (image, x, y);
307             unsigned long r = (pix & xgwa.visual->red_mask);
308             r = ((r >> 24) | (r >> 16) | (r >> 8) | r);
309             fprintf (f, "%c", (char) r);
310           }
311         fclose (f);
312         XDestroyImage (image);
313         fprintf (stderr, "%s: wrote %s\n", progname, file);
314       }
315 #endif
316
317       bitmap_to_texture (dpy, p, xgwa.visual, 
318                          &data->tex_width, &data->tex_height);
319       XFreePixmap (dpy, p);
320     }
321
322   return data;
323 }
324
325
326 /* Width of the string in pixels.
327  */
328 int
329 texture_string_width (texture_font_data *data, const char *c,
330                       int *line_height_ret)
331 {
332   int w = 0;
333   XFontStruct *f = data->font;
334   while (*c)
335     {
336       int cc = *((unsigned char *) c);
337       w += (f->per_char
338             ? f->per_char[cc-f->min_char_or_byte2].width
339             : f->max_bounds.width);
340       c++;
341     }
342   if (line_height_ret)
343     *line_height_ret = f->ascent + f->descent;
344   return w;
345 }
346
347
348 /* Draws the string in the scene at the origin.
349    Newlines and tab stops are honored.
350  */
351 void
352 print_texture_string (texture_font_data *data, const char *string)
353 {
354   XFontStruct *f = data->font;
355   GLfloat line_height = f->ascent + f->descent;
356 # ifdef DO_SUBSCRIPTS
357   GLfloat sub_shift = (line_height * 0.3);
358   Bool sub_p = False;
359 # endif /* DO_SUBSCRIPTS */
360   int cw = texture_string_width (data, "m", 0);
361   int tabs = cw * 7;
362   int x, y;
363   unsigned int i;
364
365   clear_gl_error ();
366
367   glPushMatrix();
368
369   glNormal3f (0, 0, 1);
370
371   x = 0;
372   y = 0;
373   for (i = 0; i < strlen(string); i++)
374     {
375       unsigned char c = string[i];
376       if (c == '\n')
377         {
378           y -= line_height;
379           x = 0;
380         }
381       else if (c == '\t')
382         {
383           x = ((x + tabs) / tabs) * tabs;  /* tab to tab stop */
384         }
385 # ifdef DO_SUBSCRIPTS
386       else if (c == '[' && (isdigit (string[i+1])))
387         {
388           sub_p = True;
389           y -= sub_shift;
390         }
391       else if (c == ']' && sub_p)
392         {
393           sub_p = False;
394           y += sub_shift;
395         }
396 # endif /* DO_SUBSCRIPTS */
397       else
398         {
399           /* For a small font, the texture is divided into 16x16 rectangles
400              whose size are the max_bounds charcell of the font.  Within each
401              rectangle, the individual characters' charcells sit in the upper
402              left.
403
404              For a larger font, the texture will itself be subdivided, to
405              keep the texture sizes small (in that case we deal with, e.g.,
406              4 grids of 8x8 characters instead of 1 grid of 16x16.)
407
408              Within each texture:
409
410                [A]----------------------------
411                 |     |           |   |      |
412                 |   l |         w |   | r    |
413                 |   b |         i |   | b    |
414                 |   e |         d |   | e    |
415                 |   a |         t |   | a    |
416                 |   r |         h |   | r    |
417                 |   i |           |   | i    |
418                 |   n |           |   | n    |
419                 |   g |           |   | g    |
420                 |     |           |   |      |
421                 |----[B]----------|---|      |
422                 |     |   ascent  |   |      |
423                 |     |           |   |      |
424                 |     |           |   |      |
425                 |--------------------[C]     |
426                 |         descent            |
427                 |                            | cell_width,
428                 ------------------------------ cell_height
429
430              We want to make a quad from point A to point C.
431              We want to position that quad so that point B lies at x,y.
432            */
433           int lbearing = (f->per_char
434                           ? f->per_char[c - f->min_char_or_byte2].lbearing
435                           : f->min_bounds.lbearing);
436           int rbearing = (f->per_char
437                           ? f->per_char[c - f->min_char_or_byte2].rbearing
438                           : f->max_bounds.rbearing);
439           int ascent   = (f->per_char
440                           ? f->per_char[c - f->min_char_or_byte2].ascent
441                           : f->max_bounds.ascent);
442           int descent  = (f->per_char
443                           ? f->per_char[c - f->min_char_or_byte2].descent
444                           : f->max_bounds.descent);
445           int cwidth   = (f->per_char
446                           ? f->per_char[c - f->min_char_or_byte2].width
447                           : f->max_bounds.width);
448
449           char cc = c % (256 / data->ntextures);
450
451           int gs = (16 / data->grid_mag);                 /* grid size */
452
453           int ax = ((int) cc % gs) * data->cell_width;    /* point A */
454           int ay = ((int) cc / gs) * data->cell_height;
455
456           int bx = ax - lbearing;                         /* point B */
457           int by = ay + ascent;
458
459           int cx = bx + rbearing;                         /* point C */
460           int cy = by + descent;
461
462           GLfloat tax = (GLfloat) ax / data->tex_width;  /* tex coords of A */
463           GLfloat tay = (GLfloat) ay / data->tex_height;
464
465           GLfloat tcx = (GLfloat) cx / data->tex_width;  /* tex coords of C */
466           GLfloat tcy = (GLfloat) cy / data->tex_height;
467
468           GLfloat qx0 = x + lbearing;                    /* quad top left */
469           GLfloat qy0 = y + ascent;
470           GLfloat qx1 = qx0 + rbearing - lbearing;       /* quad bot right */
471           GLfloat qy1 = qy0 - (ascent + descent);
472
473           if (cwidth > 0 && c != ' ')
474             {
475               int which = c / (256 / data->ntextures);
476               if (which >= data->ntextures) abort();
477               glBindTexture (GL_TEXTURE_2D, data->texid[which]);
478
479               glBegin (GL_QUADS);
480               glTexCoord2f (tax, tay); glVertex3f (qx0, qy0, 0);
481               glTexCoord2f (tcx, tay); glVertex3f (qx1, qy0, 0);
482               glTexCoord2f (tcx, tcy); glVertex3f (qx1, qy1, 0);
483               glTexCoord2f (tax, tcy); glVertex3f (qx0, qy1, 0);
484               glEnd();
485 #if 0
486               glDisable(GL_TEXTURE_2D);
487               glBegin (GL_LINE_LOOP);
488               glTexCoord2f (tax, tay); glVertex3f (qx0, qy0, 0);
489               glTexCoord2f (tcx, tay); glVertex3f (qx1, qy0, 0);
490               glTexCoord2f (tcx, tcy); glVertex3f (qx1, qy1, 0);
491               glTexCoord2f (tax, tcy); glVertex3f (qx0, qy1, 0);
492               glEnd();
493               glEnable(GL_TEXTURE_2D);
494 #endif
495             }
496
497           x += cwidth;
498         }
499       }
500   glPopMatrix();
501
502   check_gl_error ("texture font print");
503 }
504
505 /* Releases the font and texture.
506  */
507 void
508 free_texture_font (texture_font_data *data)
509 {
510   int i;
511   if (data->font)
512     XFreeFont (data->dpy, data->font);
513   for (i = 0; i < data->ntextures; i++)
514     if (data->texid[i])
515       glDeleteTextures (1, &data->texid[i]);
516   free (data);
517 }