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