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