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