http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / glx / glxfonts.c
1 /* glxfonts, Copyright (c) 2001-2011 Jamie Zawinski <jwz@jwz.org>
2  * Loads X11 fonts 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 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #include <ctype.h>
21
22 #ifdef HAVE_COCOA
23 # include "jwxyz.h"
24 # include <OpenGL/gl.h>
25 # include <OpenGL/glu.h>
26 # include <AGL/agl.h>
27 #else
28 # include <GL/glx.h>
29 # include <GL/glu.h>
30 #endif
31
32 #include "resources.h"
33 #include "glxfonts.h"
34
35 /* These are in xlock-gl.c */
36 extern void clear_gl_error (void);
37 extern void check_gl_error (const char *type);
38
39 /* screenhack.h */
40 extern char *progname;
41
42
43 #undef DEBUG  /* Defining this causes check_gl_error() to be called inside
44                  time-critical sections, which could slow things down (since
45                  it might result in a round-trip, and stall of the pipeline.)
46                */
47
48
49 /* Mostly lifted from the Mesa implementation of glXUseXFont(), since
50    Mac OS 10.6 no longer supports aglUseFont() which was their analog
51    of that.  This code could be in libjwxyz instead, but we might as
52    well use the same text-drawing code on both X11 and Cocoa.
53  */
54 static void
55 fill_bitmap (Display *dpy, Window win, GC gc,
56              unsigned int width, unsigned int height,
57              int x0, int y0, char c, GLubyte *bitmap)
58 {
59   XImage *image;
60   int x, y;
61   Pixmap pixmap;
62
63   pixmap = XCreatePixmap (dpy, win, 8*width, height, 1);
64   XSetForeground(dpy, gc, 0);
65   XFillRectangle (dpy, pixmap, gc, 0, 0, 8*width, height);
66   XSetForeground(dpy, gc, 1);
67   XDrawString (dpy, pixmap, gc, x0, y0, &c, 1);
68
69   image = XGetImage (dpy, pixmap, 0, 0, 8*width, height, 1, XYPixmap);
70
71   /* Fill the bitmap (X11 and OpenGL are upside down wrt each other).  */
72   for (y = 0; y < height; y++)
73     for (x = 0; x < 8*width; x++)
74       if (XGetPixel (image, x, y))
75         bitmap[width*(height - y - 1) + x/8] |= (1 << (7 - (x % 8)));
76   
77   XFreePixmap (dpy, pixmap);
78   XDestroyImage (image);
79 }
80
81
82 #if 0
83 static void
84 dump_bitmap (unsigned int width, unsigned int height, GLubyte *bitmap)
85 {
86   int x, y;
87
88   printf ("    ");
89   for (x = 0; x < 8*width; x++)
90     printf ("%o", 7 - (x % 8));
91   putchar ('\n');
92   for (y = 0; y < height; y++)
93     {
94       printf ("%3o:", y);
95       for (x = 0; x < 8*width; x++)
96         putchar ((bitmap[width*(height - y - 1) + x/8] & (1 << (7 - (x % 8))))
97                  ? '#' : '.');
98       printf ("   ");
99       for (x = 0; x < width; x++)
100         printf ("0x%02x, ", bitmap[width*(height - y - 1) + x]);
101       putchar ('\n');
102     }
103 }
104 #endif
105
106
107 void
108 xscreensaver_glXUseXFont (Display *dpy, Font font, 
109                           int first, int count, int listbase)
110 {
111   Window win = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
112   Pixmap pixmap;
113   GC gc;
114   XGCValues values;
115   unsigned long valuemask;
116
117   XFontStruct *fs;
118
119   GLint swapbytes, lsbfirst, rowlength;
120   GLint skiprows, skippixels, alignment;
121
122   unsigned int max_width, max_height, max_bm_width, max_bm_height;
123   GLubyte *bm;
124
125   int i;
126
127   clear_gl_error ();
128
129   fs = XQueryFont (dpy, font);  
130   if (!fs)
131     {
132       /*gl_error (CC->gl_ctx, GL_INVALID_VALUE,
133                 "Couldn't get font structure information");*/
134       abort();
135       return;
136     }
137
138   /* Allocate a bitmap that can fit all characters.  */
139   max_width = fs->max_bounds.rbearing - fs->min_bounds.lbearing;
140   max_height = fs->max_bounds.ascent + fs->max_bounds.descent;
141   max_bm_width = (max_width + 7) / 8;
142   max_bm_height = max_height;
143
144   bm = (GLubyte *) malloc ((max_bm_width * max_bm_height) * sizeof (GLubyte));
145   if (!bm)
146     {
147       /*gl_error (CC->gl_ctx, GL_OUT_OF_MEMORY,
148                 "Couldn't allocate bitmap in glXUseXFont()");*/
149       abort();
150       return;
151     }
152
153   /* Save the current packing mode for bitmaps.  */
154   glGetIntegerv (GL_UNPACK_SWAP_BYTES, &swapbytes);
155   glGetIntegerv (GL_UNPACK_LSB_FIRST, &lsbfirst);
156   glGetIntegerv (GL_UNPACK_ROW_LENGTH, &rowlength);
157   glGetIntegerv (GL_UNPACK_SKIP_ROWS, &skiprows);
158   glGetIntegerv (GL_UNPACK_SKIP_PIXELS, &skippixels);
159   glGetIntegerv (GL_UNPACK_ALIGNMENT, &alignment);
160
161   /* Enforce a standard packing mode which is compatible with
162      fill_bitmap() from above.  This is actually the default mode,
163      except for the (non)alignment.  */
164   glPixelStorei (GL_UNPACK_SWAP_BYTES, GL_FALSE);
165   glPixelStorei (GL_UNPACK_LSB_FIRST, GL_FALSE);
166   glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
167   glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
168   glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
169   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
170
171   clear_gl_error(); /* WTF? sometimes "invalid op" from glPixelStorei! */
172
173
174   pixmap = XCreatePixmap (dpy, win, 10, 10, 1);
175   values.foreground = 0;
176   values.background = 1;
177   values.font = fs->fid;
178   valuemask = GCForeground | GCBackground | GCFont;
179   gc = XCreateGC (dpy, pixmap, valuemask, &values);
180   XFreePixmap (dpy, pixmap);
181
182 # ifdef HAVE_COCOA
183   /* Anti-aliasing of fonts looks like crap with 1-bit bitmaps.
184      It would be nice if we were using full-depth bitmaps, so
185      that the text showed up anti-aliased on screen, but
186      glBitmap() doesn't work that way. */
187   jwxyz_XSetAntiAliasing (dpy, gc, False);
188 # endif
189
190   for (i = 0; i < count; i++)
191     {
192       unsigned int width, height, bm_width, bm_height;
193       GLfloat x0, y0, dx, dy;
194       XCharStruct *ch;
195       int x, y;
196       int c = first + i;
197       int list = listbase + i;
198
199       if (fs->per_char
200           && (c >= fs->min_char_or_byte2) && (c <= fs->max_char_or_byte2))
201         ch = &fs->per_char[c-fs->min_char_or_byte2];
202       else
203         ch = &fs->max_bounds;
204
205       /* I'm not entirely clear on why this is necessary on OSX, but
206          without it, the characters are clipped.  And it does not hurt
207          under real X11.  -- jwz. */
208       ch->lbearing--;
209       ch->ascent++;
210
211       /* glBitmap()'s parameters:
212          "Bitmap parameters xorig, yorig, width, and height are
213          computed from font metrics as descent-1, -lbearing,
214          rbearing-lbearing, and ascent+descent, respectively. 
215          xmove is taken from the glyph's width metric, and 
216          ymove is set to zero. Finally, the glyph's image is 
217          converted to the appropriate format for glBitmap."
218       */
219       width = ch->rbearing - ch->lbearing;
220       height = ch->ascent + ch->descent;
221       x0 = - ch->lbearing;
222       y0 = ch->descent - 1;
223       dx = ch->width;
224       dy = 0;
225
226       /* X11's starting point.  */
227       x = - ch->lbearing;
228       y = ch->ascent;
229       
230       /* Round the width to a multiple of eight.  We will use this also
231          for the pixmap for capturing the X11 font.  This is slightly
232          inefficient, but it makes the OpenGL part real easy.  */
233       bm_width = (width + 7) / 8;
234       bm_height = height;
235
236       glNewList (list, GL_COMPILE);
237         if ((c >= fs->min_char_or_byte2) && (c <= fs->max_char_or_byte2)
238             && (bm_width > 0) && (bm_height > 0))
239           {
240             memset (bm, '\0', bm_width * bm_height);
241             fill_bitmap (dpy, win, gc, bm_width, bm_height, x, y, c, bm);
242             glBitmap (width, height, x0, y0, dx, dy, bm);
243 #if 0
244             printf ("width/height = %d/%d\n", width, height);
245             printf ("bm_width/bm_height = %d/%d\n", bm_width, bm_height);
246             dump_bitmap (bm_width, bm_height, bm);
247 #endif
248           }
249         else
250           glBitmap (0, 0, 0.0, 0.0, dx, dy, NULL);
251       glEndList ();
252     }
253
254   free (bm);
255   XFreeFontInfo( NULL, fs, 0 );
256   XFreeGC (dpy, gc);
257
258   /* Restore saved packing modes.  */    
259   glPixelStorei(GL_UNPACK_SWAP_BYTES, swapbytes);
260   glPixelStorei(GL_UNPACK_LSB_FIRST, lsbfirst);
261   glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlength);
262   glPixelStorei(GL_UNPACK_SKIP_ROWS, skiprows);
263   glPixelStorei(GL_UNPACK_SKIP_PIXELS, skippixels);
264   glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
265
266   check_gl_error ("xscreensaver_glXUseXFont");
267 }
268
269
270
271 /* Loads the font named by the X resource "res".
272    Returns an XFontStruct.
273    Also converts the font to a set of GL lists and returns the first list.
274 */
275 void
276 load_font (Display *dpy, char *res, XFontStruct **font_ret, GLuint *dlist_ret)
277 {
278   XFontStruct *f;
279
280   const char *font = get_string_resource (dpy, res, "Font");
281   const char *def1 = "-*-helvetica-medium-r-normal-*-180-*";
282   const char *def2 = "fixed";
283   Font id;
284   int first, last;
285
286   if (!res || !*res) abort();
287   if (!font_ret && !dlist_ret) abort();
288
289   if (!font) font = def1;
290
291   f = XLoadQueryFont(dpy, font);
292   if (!f && !!strcmp (font, def1))
293     {
294       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
295                progname, font, def1);
296       font = def1;
297       f = XLoadQueryFont(dpy, font);
298     }
299
300   if (!f && !!strcmp (font, def2))
301     {
302       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
303                progname, font, def2);
304       font = def2;
305       f = XLoadQueryFont(dpy, font);
306     }
307
308   if (!f)
309     {
310       fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
311                progname, font);
312       exit (1);
313     }
314
315   id = f->fid;
316   first = f->min_char_or_byte2;
317   last = f->max_char_or_byte2;
318   
319
320   if (dlist_ret)
321     {
322       clear_gl_error ();
323       *dlist_ret = glGenLists ((GLuint) last+1);
324       check_gl_error ("glGenLists");
325       xscreensaver_glXUseXFont (dpy, id, first, last-first+1,
326                                 *dlist_ret + first);
327     }
328
329   if (font_ret)
330     *font_ret = f;
331 }
332
333
334 /* Width (and optionally height) of the string in pixels.
335  */
336 int
337 string_width (XFontStruct *f, const char *c, int *height_ret)
338 {
339   int x = 0;
340   int max_w = 0;
341   int h = f->ascent + f->descent;
342   while (*c)
343     {
344       int cc = *((unsigned char *) c);
345       if (*c == '\n')
346         {
347           if (x > max_w) max_w = x;
348           x = 0;
349           h += f->ascent + f->descent;
350         }
351       else
352         x += (f->per_char
353               ? f->per_char[cc-f->min_char_or_byte2].width
354               : f->min_bounds.rbearing);
355       c++;
356     }
357   if (x > max_w) max_w = x;
358   if (height_ret) *height_ret = h;
359
360   return max_w;
361 }
362
363
364 /* Draws the string on the window at the given pixel position.
365    Newlines and tab stops are honored.
366    Any text inside [] will be rendered as a subscript.
367    Assumes the font has been loaded as with load_font().
368  */
369 void
370 print_gl_string (Display *dpy,
371                  XFontStruct *font,
372                  GLuint font_dlist,
373                  int window_width, int window_height,
374                  GLfloat x, GLfloat y,
375                  const char *string,
376                  Bool clear_background_p)
377 {
378   GLfloat line_height = font->ascent + font->descent;
379   GLfloat sub_shift = (line_height * 0.3);
380   int cw = string_width (font, "m", 0);
381   int tabs = cw * 7;
382
383   y -= line_height;
384
385   /* Sadly, this causes a stall of the graphics pipeline (as would the
386      equivalent calls to glGet*.)  But there's no way around this, short
387      of having each caller set up the specific display matrix we need
388      here, which would kind of defeat the purpose of centralizing this
389      code in one file.
390    */
391   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
392                 GL_ENABLE_BIT |     /* for various glDisable calls */
393                 GL_CURRENT_BIT |    /* for glColor3f() */
394                 GL_LIST_BIT);       /* for glListBase() */
395   {
396 # ifdef DEBUG
397     check_gl_error ("glPushAttrib");
398 # endif
399
400     /* disable lighting and texturing when drawing bitmaps!
401        (glPopAttrib() restores these.)
402      */
403     glDisable (GL_TEXTURE_2D);
404     glDisable (GL_LIGHTING);
405     glDisable (GL_BLEND);
406     glDisable (GL_DEPTH_TEST);
407     glDisable (GL_CULL_FACE);
408
409     /* glPopAttrib() does not restore matrix changes, so we must
410        push/pop the matrix stacks to be non-intrusive there.
411      */
412     glMatrixMode(GL_PROJECTION);
413     glPushMatrix();
414     {
415 # ifdef DEBUG
416       check_gl_error ("glPushMatrix");
417 # endif
418       glLoadIdentity();
419
420       /* Each matrix mode has its own stack, so we need to push/pop
421          them separately. */
422       glMatrixMode(GL_MODELVIEW);
423       glPushMatrix();
424       {
425         unsigned int i;
426         int x2 = x;
427         Bool sub_p = False;
428
429 # ifdef DEBUG
430         check_gl_error ("glPushMatrix");
431 # endif
432
433         glLoadIdentity();
434         gluOrtho2D (0, window_width, 0, window_height);
435 # ifdef DEBUG
436         check_gl_error ("gluOrtho2D");
437 # endif
438
439         if (clear_background_p)
440           {
441             int w, h;
442             int lh = font->ascent + font->descent;
443             w = string_width (font, string, &h);
444             glColor3f (0, 0, 0);
445             glRecti (x - font->descent,
446                      y + lh, 
447                      x + w + 2*font->descent,
448                      y + lh - h - font->descent);
449             glColor3f (1, 1, 1);
450           }
451
452         /* draw the text */
453         glRasterPos2f (x, y);
454 /*        glListBase (font_dlist);*/
455         for (i = 0; i < strlen(string); i++)
456           {
457             unsigned char c = (unsigned char) string[i];
458             if (c == '\n')
459               {
460                 glRasterPos2f (x, (y -= line_height));
461                 x2 = x;
462               }
463             else if (c == '\t')
464               {
465                 x2 -= x;
466                 x2 = ((x2 + tabs) / tabs) * tabs;  /* tab to tab stop */
467                 x2 += x;
468                 glRasterPos2f (x2, y);
469               }
470             else if (c == '[' && (isdigit (string[i+1])))
471               {
472                 sub_p = True;
473                 glRasterPos2f (x2, (y -= sub_shift));
474               }
475             else if (c == ']' && sub_p)
476               {
477                 sub_p = False;
478                 glRasterPos2f (x2, (y += sub_shift));
479               }
480             else
481               {
482 /*            glCallLists (s - string, GL_UNSIGNED_BYTE, string);*/
483                 glCallList (font_dlist + (int)(c));
484                 x2 += (font->per_char
485                        ? font->per_char[c - font->min_char_or_byte2].width
486                        : font->min_bounds.width);
487               }
488           }
489 # ifdef DEBUG
490         check_gl_error ("print_gl_string");
491 # endif
492       }
493       glPopMatrix();
494     }
495     glMatrixMode(GL_PROJECTION);
496     glPopMatrix();
497   }
498   glPopAttrib();
499 # ifdef DEBUG
500   check_gl_error ("glPopAttrib");
501 # endif
502
503   glMatrixMode(GL_MODELVIEW);
504 }