From http://www.jwz.org/xscreensaver/xscreensaver-5.18.tar.gz
[xscreensaver] / hacks / glx / glxfonts.c
1 /* glxfonts, Copyright (c) 2001-2012 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  * Draws 2D text over the GL scene, e.g., the FPS displays.
13  *
14  * There are two implementations here: one using glBitmap (the OpenGL 1.1 way)
15  * and one using textures via texfont.c (since OpenGLES doesn't have glBitmap).
16  */
17
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26
27 #ifdef HAVE_COCOA
28 # include "jwxyz.h"
29 /*# include <AGL/agl.h>*/
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 "glxfonts.h"
41 #include "fps.h"
42
43 #ifndef HAVE_GLBITMAP
44 # include "texfont.h"
45 #endif /* HAVE_GLBITMAP */
46
47
48 /* These are in xlock-gl.c */
49 extern void clear_gl_error (void);
50 extern void check_gl_error (const char *type);
51
52 /* screenhack.h */
53 extern char *progname;
54
55
56 #undef DEBUG  /* Defining this causes check_gl_error() to be called inside
57                  time-critical sections, which could slow things down (since
58                  it might result in a round-trip, and stall of the pipeline.)
59                */
60
61
62 #undef countof
63 #define countof(x) (sizeof((x))/sizeof((*x)))
64
65
66 /* Width (and optionally height) of the string in pixels.
67  */
68 int
69 string_width (XFontStruct *f, const char *c, int *height_ret)
70 {
71   int x = 0;
72   int max_w = 0;
73   int h = f->ascent + f->descent;
74   while (*c)
75     {
76       int cc = *((unsigned char *) c);
77       if (*c == '\n')
78         {
79           if (x > max_w) max_w = x;
80           x = 0;
81           h += f->ascent + f->descent;
82         }
83       else
84         x += (f->per_char
85               ? f->per_char[cc-f->min_char_or_byte2].width
86               : f->min_bounds.rbearing);
87       c++;
88     }
89   if (x > max_w) max_w = x;
90   if (height_ret) *height_ret = h;
91
92   return max_w;
93 }
94
95
96 #ifdef HAVE_GLBITMAP
97
98 /* Mostly lifted from the Mesa implementation of glXUseXFont(), since
99    Mac OS 10.6 no longer supports aglUseFont() which was their analog
100    of that.  This code could be in libjwxyz instead, but we might as
101    well use the same text-drawing code on both X11 and Cocoa.
102  */
103 static void
104 fill_bitmap (Display *dpy, Window win, GC gc,
105              unsigned int width, unsigned int height,
106              int x0, int y0, char c, GLubyte *bitmap)
107 {
108   XImage *image;
109   int x, y;
110   Pixmap pixmap;
111
112   pixmap = XCreatePixmap (dpy, win, 8*width, height, 1);
113   XSetForeground(dpy, gc, 0);
114   XFillRectangle (dpy, pixmap, gc, 0, 0, 8*width, height);
115   XSetForeground(dpy, gc, 1);
116   XDrawString (dpy, pixmap, gc, x0, y0, &c, 1);
117
118   image = XGetImage (dpy, pixmap, 0, 0, 8*width, height, 1, XYPixmap);
119
120   /* Fill the bitmap (X11 and OpenGL are upside down wrt each other).  */
121   for (y = 0; y < height; y++)
122     for (x = 0; x < 8*width; x++)
123       if (XGetPixel (image, x, y))
124         bitmap[width*(height - y - 1) + x/8] |= (1 << (7 - (x % 8)));
125   
126   XFreePixmap (dpy, pixmap);
127   XDestroyImage (image);
128 }
129
130
131 #if 0
132 static void
133 dump_bitmap (unsigned int width, unsigned int height, GLubyte *bitmap)
134 {
135   int x, y;
136
137   printf ("    ");
138   for (x = 0; x < 8*width; x++)
139     printf ("%o", 7 - (x % 8));
140   putchar ('\n');
141   for (y = 0; y < height; y++)
142     {
143       printf ("%3o:", y);
144       for (x = 0; x < 8*width; x++)
145         putchar ((bitmap[width*(height - y - 1) + x/8] & (1 << (7 - (x % 8))))
146                  ? '#' : '.');
147       printf ("   ");
148       for (x = 0; x < width; x++)
149         printf ("0x%02x, ", bitmap[width*(height - y - 1) + x]);
150       putchar ('\n');
151     }
152 }
153 #endif
154
155
156 void
157 xscreensaver_glXUseXFont (Display *dpy, Font font, 
158                           int first, int count, int listbase)
159 {
160   Window win = RootWindowOfScreen (DefaultScreenOfDisplay (dpy));
161   Pixmap pixmap;
162   GC gc;
163   XGCValues values;
164   unsigned long valuemask;
165
166   XFontStruct *fs;
167
168   GLint swapbytes, lsbfirst, rowlength;
169   GLint skiprows, skippixels, alignment;
170
171   unsigned int max_width, max_height, max_bm_width, max_bm_height;
172   GLubyte *bm;
173
174   int i;
175
176   clear_gl_error ();
177
178   fs = XQueryFont (dpy, font);  
179   if (!fs)
180     {
181       /*gl_error (CC->gl_ctx, GL_INVALID_VALUE,
182                 "Couldn't get font structure information");*/
183       abort();
184       return;
185     }
186
187   /* Allocate a bitmap that can fit all characters.  */
188   max_width = fs->max_bounds.rbearing - fs->min_bounds.lbearing;
189   max_height = fs->max_bounds.ascent + fs->max_bounds.descent;
190   max_bm_width = (max_width + 7) / 8;
191   max_bm_height = max_height;
192
193   bm = (GLubyte *) malloc ((max_bm_width * max_bm_height) * sizeof (GLubyte));
194   if (!bm)
195     {
196       /*gl_error (CC->gl_ctx, GL_OUT_OF_MEMORY,
197                 "Couldn't allocate bitmap in glXUseXFont()");*/
198       abort();
199       return;
200     }
201
202   /* Save the current packing mode for bitmaps.  */
203   glGetIntegerv (GL_UNPACK_SWAP_BYTES, &swapbytes);
204   glGetIntegerv (GL_UNPACK_LSB_FIRST, &lsbfirst);
205   glGetIntegerv (GL_UNPACK_ROW_LENGTH, &rowlength);
206   glGetIntegerv (GL_UNPACK_SKIP_ROWS, &skiprows);
207   glGetIntegerv (GL_UNPACK_SKIP_PIXELS, &skippixels);
208   glGetIntegerv (GL_UNPACK_ALIGNMENT, &alignment);
209
210   /* Enforce a standard packing mode which is compatible with
211      fill_bitmap() from above.  This is actually the default mode,
212      except for the (non)alignment.  */
213   glPixelStorei (GL_UNPACK_SWAP_BYTES, GL_FALSE);
214   glPixelStorei (GL_UNPACK_LSB_FIRST, GL_FALSE);
215   glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
216   glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
217   glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
218   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
219
220   clear_gl_error(); /* WTF? sometimes "invalid op" from glPixelStorei! */
221
222
223   pixmap = XCreatePixmap (dpy, win, 10, 10, 1);
224   values.foreground = 0;
225   values.background = 1;
226   values.font = fs->fid;
227   valuemask = GCForeground | GCBackground | GCFont;
228   gc = XCreateGC (dpy, pixmap, valuemask, &values);
229   XFreePixmap (dpy, pixmap);
230
231 # ifdef HAVE_COCOA
232   /* Anti-aliasing of fonts looks like crap with 1-bit bitmaps.
233      It would be nice if we were using full-depth bitmaps, so
234      that the text showed up anti-aliased on screen, but
235      glBitmap() doesn't work that way. */
236   jwxyz_XSetAntiAliasing (dpy, gc, False);
237 # endif
238
239   for (i = 0; i < count; i++)
240     {
241       unsigned int width, height, bm_width, bm_height;
242       GLfloat x0, y0, dx, dy;
243       XCharStruct *ch;
244       int x, y;
245       int c = first + i;
246       int list = listbase + i;
247
248       if (fs->per_char
249           && (c >= fs->min_char_or_byte2) && (c <= fs->max_char_or_byte2))
250         ch = &fs->per_char[c-fs->min_char_or_byte2];
251       else
252         ch = &fs->max_bounds;
253
254       /* I'm not entirely clear on why this is necessary on OSX, but
255          without it, the characters are clipped.  And it does not hurt
256          under real X11.  -- jwz. */
257       ch->lbearing--;
258       ch->ascent++;
259
260       /* glBitmap()'s parameters:
261          "Bitmap parameters xorig, yorig, width, and height are
262          computed from font metrics as descent-1, -lbearing,
263          rbearing-lbearing, and ascent+descent, respectively. 
264          xmove is taken from the glyph's width metric, and 
265          ymove is set to zero. Finally, the glyph's image is 
266          converted to the appropriate format for glBitmap."
267       */
268       width = ch->rbearing - ch->lbearing;
269       height = ch->ascent + ch->descent;
270       x0 = - ch->lbearing;
271       y0 = ch->descent - 1;
272       dx = ch->width;
273       dy = 0;
274
275       /* X11's starting point.  */
276       x = - ch->lbearing;
277       y = ch->ascent;
278       
279       /* Round the width to a multiple of eight.  We will use this also
280          for the pixmap for capturing the X11 font.  This is slightly
281          inefficient, but it makes the OpenGL part real easy.  */
282       bm_width = (width + 7) / 8;
283       bm_height = height;
284
285       glNewList (list, GL_COMPILE);
286         if ((c >= fs->min_char_or_byte2) && (c <= fs->max_char_or_byte2)
287             && (bm_width > 0) && (bm_height > 0))
288           {
289             memset (bm, '\0', bm_width * bm_height);
290             fill_bitmap (dpy, win, gc, bm_width, bm_height, x, y, c, bm);
291             glBitmap (width, height, x0, y0, dx, dy, bm);
292 #if 0
293             printf ("width/height = %d/%d\n", width, height);
294             printf ("bm_width/bm_height = %d/%d\n", bm_width, bm_height);
295             dump_bitmap (bm_width, bm_height, bm);
296 #endif
297           }
298         else
299           glBitmap (0, 0, 0.0, 0.0, dx, dy, NULL);
300       glEndList ();
301     }
302
303   free (bm);
304   XFreeFontInfo( NULL, fs, 0 );
305   XFreeGC (dpy, gc);
306
307   /* Restore saved packing modes.  */    
308   glPixelStorei(GL_UNPACK_SWAP_BYTES, swapbytes);
309   glPixelStorei(GL_UNPACK_LSB_FIRST, lsbfirst);
310   glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlength);
311   glPixelStorei(GL_UNPACK_SKIP_ROWS, skiprows);
312   glPixelStorei(GL_UNPACK_SKIP_PIXELS, skippixels);
313   glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
314
315   check_gl_error ("xscreensaver_glXUseXFont");
316 }
317
318
319
320 /* Loads the font named by the X resource "res".
321    Returns an XFontStruct.
322    Also converts the font to a set of GL lists and returns the first list.
323 */
324 void
325 load_font (Display *dpy, char *res, XFontStruct **font_ret, GLuint *dlist_ret)
326 {
327   XFontStruct *f;
328
329   const char *font = get_string_resource (dpy, res, "Font");
330   const char *def1 = "-*-helvetica-medium-r-normal-*-180-*";
331   const char *def2 = "fixed";
332   Font id;
333   int first, last;
334
335   if (!res || !*res) abort();
336   if (!font_ret && !dlist_ret) abort();
337
338   if (!font) font = def1;
339
340   f = XLoadQueryFont(dpy, font);
341   if (!f && !!strcmp (font, def1))
342     {
343       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
344                progname, font, def1);
345       font = def1;
346       f = XLoadQueryFont(dpy, font);
347     }
348
349   if (!f && !!strcmp (font, def2))
350     {
351       fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
352                progname, font, def2);
353       font = def2;
354       f = XLoadQueryFont(dpy, font);
355     }
356
357   if (!f)
358     {
359       fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
360                progname, font);
361       exit (1);
362     }
363
364   id = f->fid;
365   first = f->min_char_or_byte2;
366   last = f->max_char_or_byte2;
367   
368
369   if (dlist_ret)
370     {
371       clear_gl_error ();
372       *dlist_ret = glGenLists ((GLuint) last+1);
373       check_gl_error ("glGenLists");
374       xscreensaver_glXUseXFont (dpy, id, first, last-first+1,
375                                 *dlist_ret + first);
376     }
377
378   if (font_ret)
379     *font_ret = f;
380 }
381
382 #endif /* HAVE_GLBITMAP */
383
384
385 /* Draws the string on the window at the given pixel position.
386    Newlines and tab stops are honored.
387    Any text inside [] will be rendered as a subscript.
388    Assumes the font has been loaded as with load_font().
389  */
390 void
391 print_gl_string (Display *dpy,
392 # ifdef HAVE_GLBITMAP
393                  XFontStruct *font, GLuint font_dlist,
394 # else
395                  texture_font_data *font_data,
396 # endif
397                  int window_width, int window_height,
398                  GLfloat x, GLfloat y,
399                  const char *string,
400                  Bool clear_background_p)
401 {
402
403   /* If window_width was specified, we're drawing ortho in pixel coordinates.
404      Otherwise, we're just dropping the text at the current position in the
405      scene, billboarded. */
406   Bool in_scene_p = (window_width == 0);
407
408 # ifdef HAVE_GLBITMAP
409   GLfloat line_height = font->ascent + font->descent;
410   int cw = string_width (font, "m", 0);
411 # else /* !HAVE_GLBITMAP */
412   int line_height = 0;
413   int cw = texture_string_width (font_data, "m", &line_height);
414 # endif /* !HAVE_GLBITMAP */
415
416   GLfloat sub_shift = (line_height * 0.3);
417   int tabs = cw * 7;
418   int lines = 0;
419   const char *c;
420   GLfloat color[4];
421
422 # ifdef HAVE_GLBITMAP
423   /* Sadly, this causes a stall of the graphics pipeline (as would the
424      equivalent calls to glGet*.)  But there's no way around this, short
425      of having each caller set up the specific display matrix we need
426      here, which would kind of defeat the purpose of centralizing this
427      code in one file.
428    */
429   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
430                 GL_ENABLE_BIT |     /* for various glDisable calls */
431                 GL_CURRENT_BIT |    /* for glColor3f() */
432                 GL_LIST_BIT);       /* for glListBase() */
433 #  ifdef DEBUG
434     check_gl_error ("glPushAttrib");
435 #  endif
436 # else /* !HAVE_GLBITMAP */
437     Bool tex_p   = glIsEnabled (GL_TEXTURE_2D);
438     Bool texs_p  = glIsEnabled (GL_TEXTURE_GEN_S);
439     Bool text_p  = glIsEnabled (GL_TEXTURE_GEN_T);
440     Bool light_p = glIsEnabled (GL_LIGHTING);
441     Bool blend_p = glIsEnabled (GL_BLEND);
442     Bool depth_p = glIsEnabled (GL_DEPTH_TEST);
443     Bool cull_p  = glIsEnabled (GL_CULL_FACE);
444     Bool fog_p   = glIsEnabled (GL_FOG);
445     GLint oblend;
446 #  ifndef HAVE_JWZGLES
447     GLint opoly[2];
448     glGetIntegerv (GL_POLYGON_MODE, opoly);
449 #  endif
450     glGetIntegerv (GL_BLEND_DST, &oblend);
451 # endif /* !HAVE_GLBITMAP */
452
453     glGetFloatv (GL_CURRENT_COLOR, color);
454
455   for (c = string; *c; c++)
456     if (*c == '\n') lines++;
457
458   y -= line_height;
459
460   {
461
462     /* disable lighting and texturing when drawing bitmaps!
463        (glPopAttrib() restores these.)
464      */
465 # ifdef HAVE_GLBITMAP
466     glDisable (GL_TEXTURE_2D);
467 # else /* !HAVE_GLBITMAP */
468     glEnable (GL_TEXTURE_2D);
469     glDisable (GL_TEXTURE_GEN_S);
470     glDisable (GL_TEXTURE_GEN_T);
471     glPolygonMode (GL_FRONT, GL_FILL);
472     glEnable (GL_BLEND);
473     glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
474 # endif /* !HAVE_GLBITMAP */
475
476     glDisable (GL_LIGHTING);
477
478
479       if (!in_scene_p)
480         glDisable (GL_DEPTH_TEST);
481     glDisable (GL_CULL_FACE);
482     glDisable (GL_FOG);
483
484     /* glPopAttrib() does not restore matrix changes, so we must
485        push/pop the matrix stacks to be non-intrusive there.
486      */
487     glMatrixMode(GL_PROJECTION);
488     glPushMatrix();
489     {
490 # ifdef DEBUG
491       check_gl_error ("glPushMatrix");
492 # endif
493       if (!in_scene_p)
494         glLoadIdentity();
495
496       /* Each matrix mode has its own stack, so we need to push/pop
497          them separately. */
498       glMatrixMode(GL_MODELVIEW);
499       glPushMatrix();
500       {
501         unsigned int i;
502         int x2 = x;
503         Bool sub_p = False;
504
505 # ifdef DEBUG
506         check_gl_error ("glPushMatrix");
507 # endif
508
509         if (!in_scene_p)
510           {
511             double rot = current_device_rotation();
512
513             glLoadIdentity();
514             glOrtho (0, window_width, 0, window_height, -1, 1);
515
516             if (rot > 135 || rot < -135)
517               {
518                 glTranslatef (window_width, window_height, 0);
519                 glRotatef (180, 0, 0, 1);
520               }
521             else if (rot > 45)
522               {
523                 glTranslatef (window_width, 0, 0);
524                 glRotatef (90, 0, 0, 1);
525                 y -= (window_height - window_width);
526                 if (y < line_height * lines + 10)
527                   y = line_height * lines + 10;
528               }
529             else if (rot < -45)
530               {
531                 glTranslatef(0, window_height, 0);
532                 glRotatef (-90, 0, 0, 1);
533                 y -= (window_height - window_width);
534                 if (y < line_height * lines + 10)
535                   y = line_height * lines + 10;
536               }
537           }
538 # ifdef DEBUG
539         check_gl_error ("glOrtho");
540 # endif
541
542         /* Let's always dropshadow the FPS and Title text. */
543         if (! in_scene_p)
544           clear_background_p = True;
545
546 #if 0
547         if (clear_background_p)
548           {
549             int w, h;
550             glColor3f (0, 0, 0);
551 # ifdef HAVE_GLBITMAP
552             w = string_width (font, string, &h);
553             glRecti (x - font->descent,
554                      y + line_height, 
555                      x + w + 2*font->descent,
556                      y + line_height - h - font->descent);
557 # else /* !HAVE_GLBITMAP */
558             {
559               int descent = line_height * 0.2;
560               if (descent < 2) descent = 2;
561               w = texture_string_width (font_data, string, &h);
562               glRecti (x - descent,
563                        y + line_height, 
564                        x + w + 2*descent,
565                        y + line_height - h - descent);
566             }
567 # endif /* !HAVE_GLBITMAP */
568             glColor3f (1, 1, 1);
569           }
570 #endif /* 0 */
571
572
573         /* draw the text */
574
575         for (i = 0; i < strlen(string); i++)
576           {
577             unsigned char c = (unsigned char) string[i];
578             if (c == '\n')
579               {
580                 y -= line_height;
581                 x2 = x;
582               }
583             else if (c == '\t')
584               {
585                 x2 -= x;
586                 x2 = ((x2 + tabs) / tabs) * tabs;  /* tab to tab stop */
587                 x2 += x;
588               }
589             else if (c == '[' && (isdigit (string[i+1])))
590               {
591                 sub_p = True;
592                 y -= sub_shift;
593               }
594             else if (c == ']' && sub_p)
595               {
596                 sub_p = False;
597                 y += sub_shift;
598               }
599             else
600               {
601                 /* outline font in black */
602                 const XPoint offsets[] = {{ -1, -1 },
603                                           { -1,  1 },
604                                           {  1,  1 },
605                                           {  1, -1 },
606                                           {  0,  0 }};
607                 int j;
608 # ifndef HAVE_GLBITMAP
609                 char s[2];
610                 s[0] = c;
611                 s[1] = 0;
612 # endif /* !HAVE_GLBITMAP */
613
614                 glColor3f (0, 0, 0);
615                 for (j = 0; j < countof(offsets); j++)
616                   {
617                     if (! clear_background_p)
618                       j = countof(offsets)-1;
619                     if (offsets[j].x == 0)
620                       glColor4fv (color);
621
622 # ifdef HAVE_GLBITMAP
623                     glRasterPos2f (x2 + offsets[j].x, y + offsets[j].y);
624                     glCallList (font_dlist + (int)(c));
625 # else /* !HAVE_GLBITMAP */
626                     glPushMatrix();
627                     glTranslatef (x2 + offsets[j].x, y + offsets[j].y, 0);
628                     print_texture_string (font_data, s);
629                     glPopMatrix();
630 # endif /* !HAVE_GLBITMAP */
631                   }
632 # ifdef HAVE_GLBITMAP
633                 x2 += (font->per_char
634                        ? font->per_char[c - font->min_char_or_byte2].width
635                        : font->min_bounds.width);
636 # else /* !HAVE_GLBITMAP */
637                 x2 += texture_string_width (font_data, s, 0);
638 # endif /* !HAVE_GLBITMAP */
639               }
640           }
641 # ifdef DEBUG
642         check_gl_error ("print_gl_string");
643 # endif
644       }
645       glPopMatrix();
646     }
647     glMatrixMode(GL_PROJECTION);
648     glPopMatrix();
649   }
650 # ifdef HAVE_GLBITMAP
651   glPopAttrib();
652 #  ifdef DEBUG
653   check_gl_error ("glPopAttrib");
654 #  endif
655 # else  /* !HAVE_GLBITMAP */
656   if (tex_p)   glEnable (GL_TEXTURE_2D); else glDisable (GL_TEXTURE_2D);
657   if (texs_p)  glEnable (GL_TEXTURE_GEN_S);/*else glDisable (GL_TEXTURE_GEN_S);*/
658   if (text_p)  glEnable (GL_TEXTURE_GEN_T);/*else glDisable (GL_TEXTURE_GEN_T);*/
659   if (blend_p) glEnable (GL_BLEND); else glDisable (GL_BLEND);
660   if (light_p) glEnable (GL_LIGHTING); /*else glDisable (GL_LIGHTING);*/
661   if (depth_p) glEnable (GL_DEPTH_TEST); else glDisable (GL_DEPTH_TEST);
662   if (cull_p)  glEnable (GL_CULL_FACE); /*else glDisable (GL_CULL_FACE);*/
663   if (fog_p)   glEnable (GL_FOG); /*else glDisable (GL_FOG);*/
664 #  ifndef HAVE_JWZGLES
665   glPolygonMode (GL_FRONT, opoly[0]);
666 #  endif
667   glBlendFunc (GL_SRC_ALPHA, oblend);
668 # endif /* !HAVE_GLBITMAP */
669
670   glMatrixMode(GL_MODELVIEW);
671 }