9197af975b2f69ecb4ab8e4e8fb7f0395ff615d1
[xscreensaver] / hacks / glx / glxfonts.c
1 /* glxfonts, Copyright (c) 2001-2009 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   fs = XQueryFont (dpy, font);  
128   if (!fs)
129     {
130       /*gl_error (CC->gl_ctx, GL_INVALID_VALUE,
131                 "Couldn't get font structure information");*/
132       abort();
133       return;
134     }
135
136   /* Allocate a bitmap that can fit all characters.  */
137   max_width = fs->max_bounds.rbearing - fs->min_bounds.lbearing;
138   max_height = fs->max_bounds.ascent + fs->max_bounds.descent;
139   max_bm_width = (max_width + 7) / 8;
140   max_bm_height = max_height;
141
142   bm = (GLubyte *) malloc ((max_bm_width * max_bm_height) * sizeof (GLubyte));
143   if (!bm)
144     {
145       /*gl_error (CC->gl_ctx, GL_OUT_OF_MEMORY,
146                 "Couldn't allocate bitmap in glXUseXFont()");*/
147       abort();
148       return;
149     }
150
151   /* Save the current packing mode for bitmaps.  */
152   glGetIntegerv (GL_UNPACK_SWAP_BYTES, &swapbytes);
153   glGetIntegerv (GL_UNPACK_LSB_FIRST, &lsbfirst);
154   glGetIntegerv (GL_UNPACK_ROW_LENGTH, &rowlength);
155   glGetIntegerv (GL_UNPACK_SKIP_ROWS, &skiprows);
156   glGetIntegerv (GL_UNPACK_SKIP_PIXELS, &skippixels);
157   glGetIntegerv (GL_UNPACK_ALIGNMENT, &alignment);
158
159   /* Enforce a standard packing mode which is compatible with
160      fill_bitmap() from above.  This is actually the default mode,
161      except for the (non)alignment.  */
162   glPixelStorei (GL_UNPACK_SWAP_BYTES, GL_FALSE);
163   glPixelStorei (GL_UNPACK_LSB_FIRST, GL_FALSE);
164   glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
165   glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
166   glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
167   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
168
169   pixmap = XCreatePixmap (dpy, win, 10, 10, 1);
170   values.foreground = 0;
171   values.background = 1;
172   values.font = fs->fid;
173   valuemask = GCForeground | GCBackground | GCFont;
174   gc = XCreateGC (dpy, pixmap, valuemask, &values);
175   XFreePixmap (dpy, pixmap);
176
177 # ifdef HAVE_COCOA
178   /* Anti-aliasing of fonts looks like crap with 1-bit bitmaps.
179      It would be nice if we were using full-depth bitmaps, so
180      that the text showed up anti-aliased on screen, but
181      glBitmap() doesn't work that way. */
182   jwxyz_XSetAntiAliasing (dpy, gc, False);
183 # endif
184
185   for (i = 0; i < count; i++)
186     {
187       unsigned int width, height, bm_width, bm_height;
188       GLfloat x0, y0, dx, dy;
189       XCharStruct *ch;
190       int x, y;
191       int c = first + i;
192       int list = listbase + i;
193
194       if (fs->per_char
195           && (c >= fs->min_char_or_byte2) && (c <= fs->max_char_or_byte2))
196         ch = &fs->per_char[c-fs->min_char_or_byte2];
197       else
198         ch = &fs->max_bounds;
199
200       /* I'm not entirely clear on why this is necessary on OSX, but
201          without it, the characters are clipped.  And it does not hurt
202          under real X11.  -- jwz. */
203       ch->lbearing--;
204       ch->ascent++;
205
206       /* glBitmap()'s parameters:
207          "Bitmap parameters xorig, yorig, width, and height are
208          computed from font metrics as descent-1, -lbearing,
209          rbearing-lbearing, and ascent+descent, respectively. 
210          xmove is taken from the glyph's width metric, and 
211          ymove is set to zero. Finally, the glyph's image is 
212          converted to the appropriate format for glBitmap."
213       */
214       width = ch->rbearing - ch->lbearing;
215       height = ch->ascent + ch->descent;
216       x0 = - ch->lbearing;
217       y0 = ch->descent - 1;
218       dx = ch->width;
219       dy = 0;
220
221       /* X11's starting point.  */
222       x = - ch->lbearing;
223       y = ch->ascent;
224       
225       /* Round the width to a multiple of eight.  We will use this also
226          for the pixmap for capturing the X11 font.  This is slightly
227          inefficient, but it makes the OpenGL part real easy.  */
228       bm_width = (width + 7) / 8;
229       bm_height = height;
230
231       glNewList (list, GL_COMPILE);
232         if ((c >= fs->min_char_or_byte2) && (c <= fs->max_char_or_byte2)
233             && (bm_width > 0) && (bm_height > 0))
234           {
235             memset (bm, '\0', bm_width * bm_height);
236             fill_bitmap (dpy, win, gc, bm_width, bm_height, x, y, c, bm);
237             glBitmap (width, height, x0, y0, dx, dy, bm);
238 #if 0
239             printf ("width/height = %d/%d\n", width, height);
240             printf ("bm_width/bm_height = %d/%d\n", bm_width, bm_height);
241             dump_bitmap (bm_width, bm_height, bm);
242 #endif
243           }
244         else
245           glBitmap (0, 0, 0.0, 0.0, dx, dy, NULL);
246       glEndList ();
247     }
248
249   free (bm);
250   XFreeFontInfo( NULL, fs, 0 );
251   XFreeGC (dpy, gc);
252
253   /* Restore saved packing modes.  */    
254   glPixelStorei(GL_UNPACK_SWAP_BYTES, swapbytes);
255   glPixelStorei(GL_UNPACK_LSB_FIRST, lsbfirst);
256   glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlength);
257   glPixelStorei(GL_UNPACK_SKIP_ROWS, skiprows);
258   glPixelStorei(GL_UNPACK_SKIP_PIXELS, skippixels);
259   glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
260 }
261
262
263
264 /* Loads the font named by the X resource "res".
265    Returns an XFontStruct.
266    Also converts the font to a set of GL lists and returns the first list.
267 */
268 void
269 load_font (Display *dpy, char *res, XFontStruct **font_ret, GLuint *dlist_ret)
270 {
271   XFontStruct *f;
272
273   const char *font = get_string_resource (dpy, res, "Font");
274   const char *def1 = "-*-helvetica-medium-r-normal-*-180-*";
275   const char *def2 = "fixed";
276   Font id;
277   int first, last;
278
279   if (!res || !*res) abort();
280   if (!font_ret && !dlist_ret) abort();
281
282   if (!font) font = def1;
283
284   f = XLoadQueryFont(dpy, font);
285   if (!f && !!strcmp (font, def1))
286     {
287       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
288                progname, font, def1);
289       font = def1;
290       f = XLoadQueryFont(dpy, font);
291     }
292
293   if (!f && !!strcmp (font, def2))
294     {
295       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
296                progname, font, def2);
297       font = def2;
298       f = XLoadQueryFont(dpy, font);
299     }
300
301   if (!f)
302     {
303       fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
304                progname, font);
305       exit (1);
306     }
307
308   id = f->fid;
309   first = f->min_char_or_byte2;
310   last = f->max_char_or_byte2;
311   
312
313   if (dlist_ret)
314     {
315       clear_gl_error ();
316       *dlist_ret = glGenLists ((GLuint) last+1);
317       check_gl_error ("glGenLists");
318       xscreensaver_glXUseXFont(dpy, id, first, last-first+1,
319                                *dlist_ret + first);
320       check_gl_error ("xscreensaver_glXUseXFont");
321     }
322
323   if (font_ret)
324     *font_ret = f;
325 }
326
327
328 /* Width (and optionally height) of the string in pixels.
329  */
330 int
331 string_width (XFontStruct *f, const char *c, int *height_ret)
332 {
333   int x = 0;
334   int max_w = 0;
335   int h = f->ascent + f->descent;
336   while (*c)
337     {
338       int cc = *((unsigned char *) c);
339       if (*c == '\n')
340         {
341           if (x > max_w) max_w = x;
342           x = 0;
343           h += f->ascent + f->descent;
344         }
345       else
346         x += (f->per_char
347               ? f->per_char[cc-f->min_char_or_byte2].width
348               : f->min_bounds.rbearing);
349       c++;
350     }
351   if (x > max_w) max_w = x;
352   if (height_ret) *height_ret = h;
353
354   return max_w;
355 }
356
357
358 /* Draws the string on the window at the given pixel position.
359    Newlines and tab stops are honored.
360    Any text inside [] will be rendered as a subscript.
361    Assumes the font has been loaded as with load_font().
362  */
363 void
364 print_gl_string (Display *dpy,
365                  XFontStruct *font,
366                  GLuint font_dlist,
367                  int window_width, int window_height,
368                  GLfloat x, GLfloat y,
369                  const char *string,
370                  Bool clear_background_p)
371 {
372   GLfloat line_height = font->ascent + font->descent;
373   GLfloat sub_shift = (line_height * 0.3);
374   int cw = string_width (font, "m", 0);
375   int tabs = cw * 7;
376
377   y -= line_height;
378
379   /* Sadly, this causes a stall of the graphics pipeline (as would the
380      equivalent calls to glGet*.)  But there's no way around this, short
381      of having each caller set up the specific display matrix we need
382      here, which would kind of defeat the purpose of centralizing this
383      code in one file.
384    */
385   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
386                 GL_ENABLE_BIT |     /* for various glDisable calls */
387                 GL_CURRENT_BIT |    /* for glColor3f() */
388                 GL_LIST_BIT);       /* for glListBase() */
389   {
390 # ifdef DEBUG
391     check_gl_error ("glPushAttrib");
392 # endif
393
394     /* disable lighting and texturing when drawing bitmaps!
395        (glPopAttrib() restores these.)
396      */
397     glDisable (GL_TEXTURE_2D);
398     glDisable (GL_LIGHTING);
399     glDisable (GL_BLEND);
400     glDisable (GL_DEPTH_TEST);
401     glDisable (GL_CULL_FACE);
402
403     /* glPopAttrib() does not restore matrix changes, so we must
404        push/pop the matrix stacks to be non-intrusive there.
405      */
406     glMatrixMode(GL_PROJECTION);
407     glPushMatrix();
408     {
409 # ifdef DEBUG
410       check_gl_error ("glPushMatrix");
411 # endif
412       glLoadIdentity();
413
414       /* Each matrix mode has its own stack, so we need to push/pop
415          them separately. */
416       glMatrixMode(GL_MODELVIEW);
417       glPushMatrix();
418       {
419         unsigned int i;
420         int x2 = x;
421         Bool sub_p = False;
422
423 # ifdef DEBUG
424         check_gl_error ("glPushMatrix");
425 # endif
426
427         glLoadIdentity();
428         gluOrtho2D (0, window_width, 0, window_height);
429 # ifdef DEBUG
430         check_gl_error ("gluOrtho2D");
431 # endif
432
433         if (clear_background_p)
434           {
435             int w, h;
436             int lh = font->ascent + font->descent;
437             w = string_width (font, string, &h);
438             glColor3f (0, 0, 0);
439             glRecti (x - font->descent,
440                      y + lh, 
441                      x + w + 2*font->descent,
442                      y + lh - h - font->descent);
443             glColor3f (1, 1, 1);
444           }
445
446         /* draw the text */
447         glRasterPos2f (x, y);
448 /*        glListBase (font_dlist);*/
449         for (i = 0; i < strlen(string); i++)
450           {
451             unsigned char c = (unsigned char) string[i];
452             if (c == '\n')
453               {
454                 glRasterPos2f (x, (y -= line_height));
455                 x2 = x;
456               }
457             else if (c == '\t')
458               {
459                 x2 -= x;
460                 x2 = ((x2 + tabs) / tabs) * tabs;  /* tab to tab stop */
461                 x2 += x;
462                 glRasterPos2f (x2, y);
463               }
464             else if (c == '[' && (isdigit (string[i+1])))
465               {
466                 sub_p = True;
467                 glRasterPos2f (x2, (y -= sub_shift));
468               }
469             else if (c == ']' && sub_p)
470               {
471                 sub_p = False;
472                 glRasterPos2f (x2, (y += sub_shift));
473               }
474             else
475               {
476 /*            glCallLists (s - string, GL_UNSIGNED_BYTE, string);*/
477                 glCallList (font_dlist + (int)(c));
478                 x2 += (font->per_char
479                        ? font->per_char[c - font->min_char_or_byte2].width
480                        : font->min_bounds.width);
481               }
482           }
483 # ifdef DEBUG
484         check_gl_error ("print_gl_string");
485 # endif
486       }
487       glPopMatrix();
488     }
489     glMatrixMode(GL_PROJECTION);
490     glPopMatrix();
491   }
492   glPopAttrib();
493 # ifdef DEBUG
494   check_gl_error ("glPopAttrib");
495 # endif
496
497   glMatrixMode(GL_MODELVIEW);
498 }