From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / glx / texfont.c
1 /* texfonts, Copyright (c) 2005-2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Renders X11 fonts into textures for use with OpenGL.
12  * A higher level API is in glxfonts.c.
13  */
14
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <ctype.h>
23
24 #ifdef HAVE_COCOA
25 # ifdef USE_IPHONE
26 #  include "jwzgles.h"
27 # else
28 #  include <OpenGL/glu.h>
29 # endif
30 #else
31 # include <GL/glx.h>
32 # include <GL/glu.h>
33 #endif
34
35 #ifdef HAVE_JWZGLES
36 # include "jwzgles.h"
37 #endif /* HAVE_JWZGLES */
38
39 #include "resources.h"
40 #include "texfont.h"
41
42 #define DO_SUBSCRIPTS
43
44
45 /* These are in xlock-gl.c */
46 extern void clear_gl_error (void);
47 extern void check_gl_error (const char *type);
48
49 /* screenhack.h */
50 extern char *progname;
51
52 struct texture_font_data {
53   Display *dpy;
54   XFontStruct *font;
55   int cell_width, cell_height;  /* maximal charcell */
56   int tex_width, tex_height;    /* size of each texture */
57
58   int grid_mag;                 /* 1,  2,  4, or 8 */
59   int ntextures;                /* 1,  4, 16, or 64 (grid_mag ^ 2) */
60
61   GLuint texid[64];             /* must hold ntextures */
62 };
63
64
65 /* return the next larger power of 2. */
66 static int
67 to_pow2 (int i)
68 {
69   static const unsigned int pow2[] = { 
70     1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 
71     2048, 4096, 8192, 16384, 32768, 65536 };
72   int j;
73   for (j = 0; j < sizeof(pow2)/sizeof(*pow2); j++)
74     if (pow2[j] >= i) return pow2[j];
75   abort();  /* too big! */
76 }
77
78
79 /* Given a Pixmap (of screen depth), converts it to an OpenGL luminance mipmap.
80    RGB are averaged to grayscale, and the resulting value is treated as alpha.
81    Pass in the size of the pixmap; the size of the texture is returned
82    (it may be larger, since GL like powers of 2.)
83
84    We use a screen-depth pixmap instead of a 1bpp bitmap so that if the fonts
85    were drawn with antialiasing, that is preserved.
86  */
87 static void
88 bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int *wP, int *hP)
89 {
90   Bool mipmap_p = True;
91   int ow = *wP;
92   int oh = *hP;
93   int w2 = to_pow2 (ow);
94   int h2 = to_pow2 (oh);
95   int x, y;
96   XImage *image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap);
97   unsigned char *data = (unsigned char *) calloc (w2 * 2, (h2 + 1));
98   unsigned char *out = data;
99
100   /* OpenGLES doesn't support GL_INTENSITY, so instead of using a
101      texture with 1 byte per pixel, the intensity value, we have
102      to use 2 bytes per pixel: solid white, and an alpha value.
103    */
104 # ifdef HAVE_JWZGLES
105 #  undef GL_INTENSITY
106 # endif
107
108 # ifdef GL_INTENSITY
109   GLuint iformat = GL_INTENSITY;
110   GLuint format  = GL_LUMINANCE;
111 # else
112   GLuint iformat = GL_LUMINANCE_ALPHA;
113   GLuint format  = GL_LUMINANCE_ALPHA;
114 # endif
115   GLuint type    = GL_UNSIGNED_BYTE;
116
117 # ifdef HAVE_JWZGLES
118   /* This would work, but it's wasteful for no benefit. */
119   mipmap_p = False;
120 # endif
121
122   for (y = 0; y < h2; y++)
123     for (x = 0; x < w2; x++) {
124       unsigned long pixel = (x >= ow || y >= oh ? 0 : XGetPixel (image, x, y));
125       /* instead of averaging all three channels, let's just use red,
126          and assume it was already grayscale. */
127       unsigned long r = pixel & visual->red_mask;
128       /* This goofy trick is to make any of RGBA/ABGR/ARGB work. */
129       pixel = ((r >> 24) | (r >> 16) | (r >> 8) | r) & 0xFF;
130 # ifndef GL_INTENSITY
131       *out++ = 0xFF;  /* 2 bytes per pixel */
132 # endif
133       *out++ = pixel;
134     }
135   XDestroyImage (image);
136   image = 0;
137
138   if (mipmap_p)
139     gluBuild2DMipmaps (GL_TEXTURE_2D, iformat, w2, h2, format, type, data);
140   else
141     glTexImage2D (GL_TEXTURE_2D, 0, iformat, w2, h2, 0, format, type, data);
142
143   {
144     char msg[100];
145     sprintf (msg, "texture font %s (%d x %d)",
146              mipmap_p ? "gluBuild2DMipmaps" : "glTexImage2D",
147              w2, h2);
148     check_gl_error (msg);
149   }
150
151
152   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
153   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
154                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
155
156
157   /* This makes scaled font pixmaps tolerable to look at.
158      LOD bias is part of OpenGL 1.4.
159      GL_EXT_texture_lod_bias has been present since the original iPhone.
160    */
161 # if !defined(GL_TEXTURE_LOD_BIAS) && defined(GL_TEXTURE_LOD_BIAS_EXT)
162 #   define GL_TEXTURE_LOD_BIAS GL_TEXTURE_LOD_BIAS_EXT
163 # endif
164 # ifdef GL_TEXTURE_LOD_BIAS
165   if (mipmap_p)
166     glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.25);
167 # endif
168   clear_gl_error();  /* invalid enum on iPad 3 */
169
170   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
171   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
172
173   free (data);
174
175   *wP = w2;
176   *hP = h2;
177 }
178
179
180 static texture_font_data *
181 load_texture_xfont (Display *dpy, XFontStruct *f)
182 {
183   Screen *screen = DefaultScreenOfDisplay (dpy);
184   Window root = RootWindowOfScreen (screen);
185   XWindowAttributes xgwa;
186   int which;
187   GLint old_texture = 0;
188   texture_font_data *data = 0;
189
190   glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
191
192   XGetWindowAttributes (dpy, root, &xgwa);
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 && ii >= f->min_char_or_byte2
261                           ? f->per_char[ii - f->min_char_or_byte2].lbearing
262                           : f->min_bounds.lbearing);
263           int ascent   = (f->per_char && ii >= f->min_char_or_byte2
264                           ? f->per_char[ii - f->min_char_or_byte2].ascent
265                           : f->max_bounds.ascent);
266           int width    = (f->per_char && ii >= f->min_char_or_byte2
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: write the bitmap to a pgm file */
282       {
283         char file[255];
284         XImage *image;
285         int x, y;
286         FILE *ff;
287         sprintf (file, "/tmp/%02d.pgm", which);
288         image = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
289         ff = fopen (file, "w");
290         fprintf (ff, "P5\n%d %d\n255\n", w, h);
291         for (y = 0; y < h; y++)
292           for (x = 0; x < w; x++) {
293             unsigned long pix = XGetPixel (image, x, y);
294             unsigned long r = (pix & xgwa.visual->red_mask);
295             r = ((r >> 24) | (r >> 16) | (r >> 8) | r);
296             fprintf (ff, "%c", (char) r);
297           }
298         fclose (ff);
299         XDestroyImage (image);
300         fprintf (stderr, "%s: wrote %s (%d x %d)\n", progname, file,
301                  f->max_bounds.rbearing - f->min_bounds.lbearing,
302                  f->max_bounds.ascent   + f->max_bounds.descent);
303       }
304 #endif /* 0 */
305
306       bitmap_to_texture (dpy, p, xgwa.visual, 
307                          &data->tex_width, &data->tex_height);
308       XFreePixmap (dpy, p);
309     }
310
311   /* Reset to the caller's default */
312   glBindTexture (GL_TEXTURE_2D, old_texture);
313
314   return data;
315 }
316
317
318 /* Loads the font named by the X resource "res" and returns
319    a texture-font object.
320 */
321 texture_font_data *
322 load_texture_font (Display *dpy, char *res)
323 {
324   char *font = get_string_resource (dpy, res, "Font");
325   const char *def1 = "-*-helvetica-medium-r-normal-*-240-*";
326   const char *def2 = "-*-helvetica-medium-r-normal-*-180-*";
327   const char *def3 = "fixed";
328   XFontStruct *f;
329
330   if (!strcmp (res, "fpsFont"))
331     def1 = "-*-courier-bold-r-normal-*-180-*";  /* Kludge. Sue me. */
332
333   if (!res || !*res) abort();
334   if (!font) font = strdup(def1);
335
336   f = XLoadQueryFont(dpy, font);
337   if (!f && !!strcmp (font, def1))
338     {
339       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
340                progname, font, def1);
341       free (font);
342       font = strdup (def1);
343       f = XLoadQueryFont(dpy, font);
344     }
345
346   if (!f && !!strcmp (font, def2))
347     {
348       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
349                progname, font, def2);
350       free (font);
351       font = strdup (def2);
352       f = XLoadQueryFont(dpy, font);
353     }
354
355   if (!f && !!strcmp (font, def3))
356     {
357       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
358                progname, font, def3);
359       free (font);
360       font = strdup (def3);
361       f = XLoadQueryFont(dpy, font);
362     }
363
364   if (!f)
365     {
366       fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
367                progname, font);
368       exit (1);
369     }
370
371   free (font);
372   font = 0;
373
374   return load_texture_xfont (dpy, f);
375 }
376
377
378 /* Bounding box of the multi-line string, in pixels.
379  */
380 int
381 texture_string_width (texture_font_data *data, const char *c, int *height_ret)
382 {
383   XFontStruct *f = data->font;
384   int x = 0;
385   int max_w = 0;
386   int lh = f->ascent + f->descent;
387   int h = lh;
388   while (*c)
389     {
390       int cc = *((unsigned char *) c);
391       if (*c == '\n')
392         {
393           if (x > max_w) max_w = x;
394           x = 0;
395           h += lh;
396         }
397       else
398         x += (f->per_char
399               ? f->per_char[cc-f->min_char_or_byte2].width
400               : f->min_bounds.rbearing);
401       c++;
402     }
403   if (x > max_w) max_w = x;
404   if (height_ret) *height_ret = h;
405   return max_w;
406 }
407
408
409 /* Draws the string in the scene at the origin.
410    Newlines and tab stops are honored.
411    Any numbers inside [] will be rendered as a subscript.
412    Assumes the font has been loaded as with load_texture_font().
413  */
414 void
415 print_texture_string (texture_font_data *data, const char *string)
416 {
417   XFontStruct *f = data->font;
418   GLfloat line_height = f->ascent + f->descent;
419 # ifdef DO_SUBSCRIPTS
420   GLfloat sub_shift = (line_height * 0.3);
421   Bool sub_p = False;
422 # endif /* DO_SUBSCRIPTS */
423   int cw = texture_string_width (data, "m", 0);
424   int tabs = cw * 7;
425   int x, y;
426   unsigned int i;
427   GLint old_texture = 0;
428   GLfloat omatrix[16];
429   int ofront;
430
431   glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
432   glGetIntegerv (GL_FRONT_FACE, &ofront);
433   glGetFloatv (GL_TEXTURE_MATRIX, omatrix);
434
435   clear_gl_error ();
436
437   glPushMatrix();
438
439   glNormal3f (0, 0, 1);
440   glFrontFace (GL_CW);
441
442   glMatrixMode (GL_TEXTURE);
443   glLoadIdentity ();
444   glMatrixMode (GL_MODELVIEW);
445
446   x = 0;
447   y = 0;
448   for (i = 0; i < strlen(string); i++)
449     {
450       unsigned char c = string[i];
451       if (c == '\n')
452         {
453           y -= line_height;
454           x = 0;
455         }
456       else if (c == '\t')
457         {
458           if (tabs)
459             x = ((x + tabs) / tabs) * tabs;  /* tab to tab stop */
460         }
461 # ifdef DO_SUBSCRIPTS
462       else if (c == '[' && (isdigit (string[i+1])))
463         {
464           sub_p = True;
465           y -= sub_shift;
466         }
467       else if (c == ']' && sub_p)
468         {
469           sub_p = False;
470           y += sub_shift;
471         }
472 # endif /* DO_SUBSCRIPTS */
473       else
474         {
475           /* For a small font, the texture is divided into 16x16 rectangles
476              whose size are the max_bounds charcell of the font.  Within each
477              rectangle, the individual characters' charcells sit in the upper
478              left.
479
480              For a larger font, the texture will itself be subdivided, to
481              keep the texture sizes small (in that case we deal with, e.g.,
482              4 grids of 8x8 characters instead of 1 grid of 16x16.)
483
484              Within each texture:
485
486                [A]----------------------------
487                 |     |           |   |      |
488                 |   l |         w |   | r    |
489                 |   b |         i |   | b    |
490                 |   e |         d |   | e    |
491                 |   a |         t |   | a    |
492                 |   r |         h |   | r    |
493                 |   i |           |   | i    |
494                 |   n |           |   | n    |
495                 |   g |           |   | g    |
496                 |     |           |   |      |
497                 |----[B]----------|---|      |
498                 |     |   ascent  |   |      |
499                 |     |           |   |      |
500                 |     |           |   |      |
501                 |--------------------[C]     |
502                 |         descent            |
503                 |                            | cell_width,
504                 ------------------------------ cell_height
505
506              We want to make a quad from point A to point C.
507              We want to position that quad so that point B lies at x,y.
508            */
509           int lbearing = (f->per_char && c >= f->min_char_or_byte2
510                           ? f->per_char[c - f->min_char_or_byte2].lbearing
511                           : f->min_bounds.lbearing);
512           int rbearing = (f->per_char && c >= f->min_char_or_byte2
513                           ? f->per_char[c - f->min_char_or_byte2].rbearing
514                           : f->max_bounds.rbearing);
515           int ascent   = (f->per_char && c >= f->min_char_or_byte2
516                           ? f->per_char[c - f->min_char_or_byte2].ascent
517                           : f->max_bounds.ascent);
518           int descent  = (f->per_char && c >= f->min_char_or_byte2
519                           ? f->per_char[c - f->min_char_or_byte2].descent
520                           : f->max_bounds.descent);
521           int cwidth   = (f->per_char && c >= f->min_char_or_byte2
522                           ? f->per_char[c - f->min_char_or_byte2].width
523                           : f->max_bounds.width);
524
525           unsigned char cc = c % (256 / data->ntextures);
526
527           int gs = (16 / data->grid_mag);                 /* grid size */
528
529           int ax = ((int) cc % gs) * data->cell_width;    /* point A */
530           int ay = ((int) cc / gs) * data->cell_height;
531
532           int bx = ax - lbearing;                         /* point B */
533           int by = ay + ascent;
534
535           int cx = bx + rbearing + 1;                     /* point C */
536           int cy = by + descent  + 1;
537
538           GLfloat tax = (GLfloat) ax / data->tex_width;  /* tex coords of A */
539           GLfloat tay = (GLfloat) ay / data->tex_height;
540
541           GLfloat tcx = (GLfloat) cx / data->tex_width;  /* tex coords of C */
542           GLfloat tcy = (GLfloat) cy / data->tex_height;
543
544           GLfloat qx0 = x + lbearing;                    /* quad top left */
545           GLfloat qy0 = y + ascent;
546           GLfloat qx1 = qx0 + rbearing - lbearing;       /* quad bot right */
547           GLfloat qy1 = qy0 - (ascent + descent);
548
549           if (cwidth > 0 && c != ' ')
550             {
551               int which = c / (256 / data->ntextures);
552               if (which >= data->ntextures) abort();
553               glBindTexture (GL_TEXTURE_2D, data->texid[which]);
554
555               glBegin (GL_QUADS);
556               glTexCoord2f (tax, tay); glVertex3f (qx0, qy0, 0);
557               glTexCoord2f (tcx, tay); glVertex3f (qx1, qy0, 0);
558               glTexCoord2f (tcx, tcy); glVertex3f (qx1, qy1, 0);
559               glTexCoord2f (tax, tcy); glVertex3f (qx0, qy1, 0);
560               glEnd();
561 #if 0
562               glDisable(GL_TEXTURE_2D);
563               glBegin (GL_LINE_LOOP);
564               glTexCoord2f (tax, tay); glVertex3f (qx0, qy0, 0);
565               glTexCoord2f (tcx, tay); glVertex3f (qx1, qy0, 0);
566               glTexCoord2f (tcx, tcy); glVertex3f (qx1, qy1, 0);
567               glTexCoord2f (tax, tcy); glVertex3f (qx0, qy1, 0);
568               glEnd();
569               glEnable(GL_TEXTURE_2D);
570 #endif
571             }
572
573           x += cwidth;
574         }
575       }
576   glPopMatrix();
577
578   /* Reset to the caller's default */
579   glBindTexture (GL_TEXTURE_2D, old_texture);
580   glFrontFace (ofront);
581   
582   glMatrixMode (GL_TEXTURE);
583   glMultMatrixf (omatrix);
584   glMatrixMode (GL_MODELVIEW);
585
586   check_gl_error ("texture font print");
587 }
588
589 /* Releases the font and texture.
590  */
591 void
592 free_texture_font (texture_font_data *data)
593 {
594   int i;
595
596   for (i = 0; i < data->ntextures; i++)
597     if (data->texid[i])
598       glDeleteTextures (1, &data->texid[i]);
599
600   if (data->font)
601     XFreeFont (data->dpy, data->font);
602
603   free (data);
604 }