From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / glx / gltext.c
1 /* gltext, Copyright (c) 2001-2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        20000        \n" \
13                         "*showFPS:      False        \n" \
14                         "*wireframe:    False        \n" \
15                         "*usePty:       False        \n" \
16
17 # define refresh_text 0
18 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
19
20 #ifdef SMOOTH_TUBE
21 # define TUBE_FACES  12   /* how densely to render tubes */
22 #else
23 # define TUBE_FACES  8
24 #endif
25
26
27 #undef countof
28 #define countof(x) (sizeof((x))/sizeof((*x)))
29
30 #include "xlockmore.h"
31 #include "colors.h"
32 #include "tube.h"
33 #include "sphere.h"
34 #include "rotator.h"
35 #include "gltrackball.h"
36 #include "textclient.h"
37
38 #include <ctype.h>
39
40 #ifdef USE_GL /* whole file */
41
42 #define DEF_TEXT        "(default)"
43 #define DEF_PROGRAM     "(default)"
44 #define DEF_SPIN        "XYZ"
45 #define DEF_WANDER      "True"
46 #define DEF_FACE_FRONT  "True"
47
48 #ifdef HAVE_UNAME
49 # include <sys/utsname.h>
50 #endif /* HAVE_UNAME */
51
52 #include "glutstroke.h"
53 #include "glut_roman.h"
54 #define GLUT_FONT (&glutStrokeRoman)
55
56
57 typedef struct {
58   GLXContext *glx_context;
59   rotator *rot, *rot2;
60   trackball_state *trackball;
61   Bool button_down_p;
62   Bool spinx, spiny, spinz;
63
64   GLuint text_list;
65
66   int ncolors;
67   XColor *colors;
68   int ccolor;
69
70   char *text;
71   int reload;
72
73   time_t last_update;
74   text_data *tc;
75
76 } text_configuration;
77
78 static text_configuration *tps = NULL;
79
80 static char *text_fmt;
81 static char *program_str;
82 static char *do_spin;
83 static Bool do_wander;
84 static Bool face_front_p;
85
86 static XrmOptionDescRec opts[] = {
87   { "-text",    ".text",      XrmoptionSepArg, 0 },
88   { "-program", ".program",   XrmoptionSepArg, 0 },
89   { "-spin",    ".spin",      XrmoptionSepArg, 0 },
90   { "+spin",    ".spin",      XrmoptionNoArg, "" },
91   { "-wander",  ".wander",    XrmoptionNoArg, "True" },
92   { "+wander",  ".wander",    XrmoptionNoArg, "False" },
93   { "-front",   ".faceFront", XrmoptionNoArg, "True" },
94   { "+front",   ".faceFront", XrmoptionNoArg, "False" }
95 };
96
97 static argtype vars[] = {
98   {&text_fmt,     "text",      "Text",      DEF_TEXT,       t_String},
99   {&program_str,  "program",   "Program",   DEF_PROGRAM,    t_String},
100   {&do_spin,      "spin",      "Spin",      DEF_SPIN,       t_String},
101   {&do_wander,    "wander",    "Wander",    DEF_WANDER,     t_Bool},
102   {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool},
103 };
104
105 ENTRYPOINT ModeSpecOpt text_opts = {countof(opts), opts, countof(vars), vars, NULL};
106
107
108 /* Window management, etc
109  */
110 ENTRYPOINT void
111 reshape_text (ModeInfo *mi, int width, int height)
112 {
113   GLfloat h = (GLfloat) height / (GLfloat) width;
114
115   glViewport (0, 0, (GLint) width, (GLint) height);
116
117   glMatrixMode(GL_PROJECTION);
118   glLoadIdentity();
119   gluPerspective (30.0, 1/h, 1.0, 100.0);
120
121   glMatrixMode(GL_MODELVIEW);
122   glLoadIdentity();
123   gluLookAt( 0.0, 0.0, 30.0,
124              0.0, 0.0, 0.0,
125              0.0, 1.0, 0.0);
126
127   glClear(GL_COLOR_BUFFER_BIT);
128 }
129
130
131 static void
132 gl_init (ModeInfo *mi)
133 {
134   text_configuration *tp = &tps[MI_SCREEN(mi)];
135   int wire = MI_IS_WIREFRAME(mi);
136
137   static const GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
138
139   if (!wire)
140     {
141       glLightfv(GL_LIGHT0, GL_POSITION, pos);
142       glEnable(GL_CULL_FACE);
143       glEnable(GL_LIGHTING);
144       glEnable(GL_LIGHT0);
145       glEnable(GL_DEPTH_TEST);
146     }
147
148   tp->text_list = glGenLists (1);
149   glNewList (tp->text_list, GL_COMPILE);
150   glEndList ();
151 }
152
153
154 /* The GLUT font only has ASCII characters in them, so do what we can to
155    convert Latin1 characters to the nearest ASCII equivalent... 
156  */
157 static void
158 latin1_to_ascii (char *s)
159 {
160   unsigned char *us = (unsigned char *) s;
161   const unsigned char ascii[95] = {
162     '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
163     '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
164     '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
165     'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
166     'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
167     'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
168     'u', 'u', 'y', 'p', 'y' };
169   while (*us)
170     {
171       if (*us >= 161)
172         *us = ascii[*us - 161];
173       else if (*us > 127)
174         *us = '?';
175       us++;
176     }
177 }
178
179
180 static void
181 parse_text (ModeInfo *mi)
182 {
183   text_configuration *tp = &tps[MI_SCREEN(mi)];
184
185   if (tp->text) free (tp->text);
186   tp->text = 0;
187
188   if (program_str && *program_str && !!strcmp(program_str, "(default)"))
189     {
190       int max_lines = 20;
191       char buf[1024];
192       char *p = buf;
193       int lines = 0;
194
195       if (! tp->tc)
196         tp->tc = textclient_open (mi->dpy);
197
198       while (p < buf + sizeof(buf) - 1 &&
199              lines < max_lines)
200         {
201           int c = textclient_getc (tp->tc);
202           if (c == '\n')
203             lines++;
204           if (c > 0)
205             *p++ = (char) c;
206           else
207             break;
208         }
209       *p = 0;
210       if (lines == 0 && buf[0])
211         lines++;
212
213       tp->text = strdup (buf);
214       tp->reload = 1;
215     }
216   else if (!text_fmt || !*text_fmt || !strcmp(text_fmt, "(default)"))
217     {
218 # ifdef HAVE_UNAME
219       struct utsname uts;
220
221       if (uname (&uts) < 0)
222         {
223           tp->text = strdup("uname() failed");
224         }
225       else
226         {
227           char *s;
228           if ((s = strchr(uts.nodename, '.')))
229             *s = 0;
230           tp->text = (char *) malloc(strlen(uts.nodename) +
231                                      strlen(uts.sysname) +
232                                      strlen(uts.version) +
233                                      strlen(uts.release) + 10);
234 #  if defined(_AIX)
235           sprintf(tp->text, "%s\n%s %s.%s",
236                   uts.nodename, uts.sysname, uts.version, uts.release);
237 #  elif defined(USE_IPHONE)
238           /* "My iPhone\n iPhone4,1\n Darwin 11.0.0" */
239           sprintf(tp->text, "%s\n%s\n%s %s",
240                   uts.nodename, uts.machine, uts.sysname, uts.release);
241 #  elif defined(__APPLE__)  /* MacOS X + XDarwin */
242           {
243             const char *file = 
244               "/System/Library/CoreServices/SystemVersion.plist";
245             FILE *f = fopen (file, "r");
246             char *pbv = 0, *pn = 0, *puvv = 0;
247             if (f) {
248               char *s, buf[255];
249
250               while (fgets (buf, sizeof(buf)-1, f)) {
251 #               define GRAB(S,V)                                        \
252                 if (strstr(buf, S)) {                                   \
253                   fgets (buf, sizeof(buf)-1, f);                        \
254                   if ((s = strchr (buf, '>'))) V = strdup(s+1);         \
255                   if ((s = strchr (V, '<'))) *s = 0;                    \
256                 }
257                 GRAB ("ProductName", pn)
258                 GRAB ("ProductBuildVersion", pbv)
259                 GRAB ("ProductUserVisibleVersion", puvv)
260 #               undef GRAB
261               }
262             }
263             if (pbv)
264               sprintf (tp->text, "%s\n%s\n%s\n%s", 
265                        uts.nodename, pn, puvv, uts.machine);
266             else
267               sprintf(tp->text, "%s\n%s %s\n%s",
268                       uts.nodename, uts.sysname, uts.release, uts.machine);
269           }
270 #  else
271           sprintf(tp->text, "%s\n%s %s",
272                   uts.nodename, uts.sysname, uts.release);
273 #  endif /* special system types */
274         }
275 # else  /* !HAVE_UNAME */
276 #  ifdef VMS
277       tp->text = strdup(getenv("SYS$NODE"));
278 #  else
279       tp->text = strdup("*  *\n*  *  *\nxscreensaver\n*  *  *\n*  *");
280 #  endif
281 # endif /* !HAVE_UNAME */
282     }
283   else if (!strchr (text_fmt, '%'))
284     {
285       tp->text = strdup (text_fmt);
286     }
287   else
288     {
289       time_t now = time ((time_t *) 0);
290       struct tm *tm = localtime (&now);
291       int L = strlen(text_fmt) + 100;
292       tp->text = (char *) malloc (L);
293       *tp->text = 0;
294       strftime (tp->text, L-1, text_fmt, tm);
295       if (!*tp->text)
296         sprintf (tp->text, "strftime error:\n%s", text_fmt);
297       tp->reload = 1;
298     }
299
300   latin1_to_ascii (tp->text);
301 }
302
303
304 ENTRYPOINT Bool
305 text_handle_event (ModeInfo *mi, XEvent *event)
306 {
307   text_configuration *tp = &tps[MI_SCREEN(mi)];
308
309   if (gltrackball_event_handler (event, tp->trackball,
310                                  MI_WIDTH (mi), MI_HEIGHT (mi),
311                                  &tp->button_down_p))
312     return True;
313
314   return False;
315 }
316
317
318 ENTRYPOINT void 
319 init_text (ModeInfo *mi)
320 {
321   text_configuration *tp;
322   int i;
323
324   if (!tps) {
325     tps = (text_configuration *)
326       calloc (MI_NUM_SCREENS(mi), sizeof (text_configuration));
327     if (!tps) {
328       fprintf(stderr, "%s: out of memory\n", progname);
329       exit(1);
330     }
331   }
332
333   tp = &tps[MI_SCREEN(mi)];
334
335   if ((tp->glx_context = init_GL(mi)) != NULL) {
336     gl_init(mi);
337     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
338   }
339
340   {
341     double spin_speed   = 0.5;
342     double wander_speed = 0.02;
343     double tilt_speed   = 0.03;
344     double spin_accel   = 0.5;
345
346     char *s = do_spin;
347     while (*s)
348       {
349         if      (*s == 'x' || *s == 'X') tp->spinx = True;
350         else if (*s == 'y' || *s == 'Y') tp->spiny = True;
351         else if (*s == 'z' || *s == 'Z') tp->spinz = True;
352         else if (*s == '0') ;
353         else
354           {
355             fprintf (stderr,
356          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
357                      progname, do_spin);
358             exit (1);
359           }
360         s++;
361       }
362
363     tp->rot = make_rotator (tp->spinx ? spin_speed : 0,
364                             tp->spiny ? spin_speed : 0,
365                             tp->spinz ? spin_speed : 0,
366                             spin_accel,
367                             do_wander ? wander_speed : 0,
368                             False);
369     tp->rot2 = (face_front_p
370                 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
371                 : 0);
372     tp->trackball = gltrackball_init (False);
373   }
374
375   tp->ncolors = 255;
376   tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
377   make_smooth_colormap (0, 0, 0,
378                         tp->colors, &tp->ncolors,
379                         False, 0, False);
380
381   /* brighter colors, please... */
382   for (i = 0; i < tp->ncolors; i++)
383     {
384       tp->colors[i].red   = (tp->colors[i].red   / 2) + 32767;
385       tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
386       tp->colors[i].blue  = (tp->colors[i].blue  / 2) + 32767;
387     }
388
389   parse_text (mi);
390
391 }
392
393
394 static int
395 fill_character (GLUTstrokeFont font, int c, Bool wire, int *polysP)
396 {
397   GLfloat tube_width = 10;
398
399   const StrokeCharRec *ch;
400   const StrokeRec *stroke;
401   const CoordRec *coord;
402   StrokeFontPtr fontinfo;
403   int i, j;
404
405   fontinfo = (StrokeFontPtr) font;
406
407   if (c < 0 || c >= fontinfo->num_chars)
408     return 0;
409   ch = &(fontinfo->ch[c]);
410   if (ch)
411     {
412       GLfloat lx=0, ly=0;
413       for (i = ch->num_strokes, stroke = ch->stroke;
414            i > 0; i--, stroke++) {
415         for (j = stroke->num_coords, coord = stroke->coord;
416              j > 0; j--, coord++)
417           {
418 # ifdef SMOOTH_TUBE
419             int smooth = True;
420 # else
421             int smooth = False;
422 # endif
423
424             if (j != stroke->num_coords)
425               *polysP += tube (lx,       ly,       0,
426                                coord->x, coord->y, 0,
427                                tube_width,
428                                tube_width * 0.15,
429                                TUBE_FACES, smooth, False, wire);
430             lx = coord->x;
431             ly = coord->y;
432
433             /* Put a sphere at the endpoint of every line segment.  Wasteful
434                on curves like "0" but necessary on corners like "4". */
435             if (! wire)
436               {
437                 glPushMatrix();
438                 glTranslatef (lx, ly, 0);
439                 glScalef (tube_width, tube_width, tube_width);
440                 *polysP += unit_sphere (TUBE_FACES, TUBE_FACES, wire);
441                 glPopMatrix();
442               }
443           }
444       }
445       return (int) (ch->right + tube_width);
446     }
447   return 0;
448 }
449
450
451 static int
452 text_extents (const char *string, int *wP, int *hP)
453 {
454   const char *s, *start;
455   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
456   int lines = 0;
457   *wP = 0;
458   *hP = 0;
459   start = string;
460   s = start;
461   while (1)
462     if (*s == '\n' || *s == 0)
463       {
464         int w = 0;
465         while (start < s)
466           {
467             w += glutStrokeWidth(GLUT_FONT, *start);
468             start++;
469           }
470         start = s+1;
471
472         if (w > *wP) *wP = w;
473         *hP += line_height;
474         lines++;
475         if (*s == 0) break;
476         s++;
477       }
478     else
479       s++;
480
481   return lines;
482 }
483
484
485 static unsigned long
486 fill_string (const char *string, Bool wire)
487 {
488   int polys = 0;
489   const char *s, *start;
490   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
491   int off;
492   GLfloat x = 0, y = 0;
493
494   int ow, oh;
495   text_extents (string, &ow, &oh);
496
497   y = oh / 2 - line_height;
498
499   start = string;
500   s = start;
501   while (1)
502     if (*s == '\n' || *s == 0)
503       {
504         int line_w = 0;
505         const char *s2;
506         const char *lstart = start;
507         const char *lend = s;
508
509         /* strip off whitespace at beginning and end of line
510            (since we're centering.) */
511         while (lend > lstart && isspace(lend[-1]))
512           lend--;
513         while (lstart < lend && isspace(*lstart))
514           lstart++;
515
516         for (s2 = lstart; s2 < lend; s2++)
517           line_w += glutStrokeWidth (GLUT_FONT, *s2);
518
519         x = (-ow/2) + ((ow-line_w)/2);
520         for (s2 = lstart; s2 < lend; s2++)
521           {
522             glPushMatrix();
523             glTranslatef(x, y, 0);
524             off = fill_character (GLUT_FONT, *s2, wire, &polys);
525             x += off;
526             glPopMatrix();
527           }
528
529         start = s+1;
530
531         y -= line_height;
532         if (*s == 0) break;
533         s++;
534       }
535     else
536       s++;
537   return polys;
538 }
539
540
541 ENTRYPOINT void
542 draw_text (ModeInfo *mi)
543 {
544   text_configuration *tp = &tps[MI_SCREEN(mi)];
545   Display *dpy = MI_DISPLAY(mi);
546   Window window = MI_WINDOW(mi);
547   int wire = MI_IS_WIREFRAME(mi);
548
549   GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
550   GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
551
552   if (!tp->glx_context)
553     return;
554
555   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tp->glx_context));
556
557   if (tp->reload)
558     {
559       if (time ((time_t *) 0) >= tp->last_update + tp->reload)
560         {
561           parse_text (mi);
562           tp->last_update = time ((time_t *) 0);
563         }
564     }
565
566   glShadeModel(GL_SMOOTH);
567
568   glEnable(GL_DEPTH_TEST);
569   glEnable(GL_NORMALIZE);
570   glEnable(GL_CULL_FACE);
571
572
573   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
574
575   glPushMatrix ();
576   glScalef(1.1, 1.1, 1.1);
577   glRotatef(current_device_rotation(), 0, 0, 1);
578
579   {
580     double x, y, z;
581     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
582     glTranslatef((x - 0.5) * 8,
583                  (y - 0.5) * 8,
584                  (z - 0.5) * 8);
585
586     gltrackball_rotate (tp->trackball);
587
588     if (face_front_p)
589       {
590         double max = 90;
591         get_position (tp->rot2, &x, &y, &z, !tp->button_down_p);
592         if (tp->spinx) glRotatef (max/2 - x*max, 1, 0, 0);
593         if (tp->spiny) glRotatef (max/2 - y*max, 0, 1, 0);
594         if (tp->spinz) glRotatef (max/2 - z*max, 0, 0, 1);
595       }
596     else
597       {
598         get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
599         glRotatef (x * 360, 1, 0, 0);
600         glRotatef (y * 360, 0, 1, 0);
601         glRotatef (z * 360, 0, 0, 1);
602       }
603   }
604
605
606   glColor4fv (white);
607
608   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
609   color[1] = tp->colors[tp->ccolor].green / 65536.0;
610   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
611   tp->ccolor++;
612   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
613
614   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
615
616   glScalef(0.01, 0.01, 0.01);
617
618   mi->polygon_count = fill_string(tp->text, wire);
619
620   glPopMatrix ();
621
622   if (mi->fps_p) do_fps (mi);
623   glFinish();
624
625   glXSwapBuffers(dpy, window);
626 }
627
628 ENTRYPOINT void
629 release_text(ModeInfo * mi)
630 {
631   if (tps)
632     {
633     int screen;
634     for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
635       {
636         text_configuration *tp = &tps[MI_SCREEN(mi)];
637         if (tp->tc)
638           textclient_close (tp->tc);
639       }
640     }
641   (void) free(tps);
642   tps = 0;
643   FreeAllGL(mi);
644 }
645
646
647 XSCREENSAVER_MODULE_2 ("GLText", gltext, text)
648
649 #endif /* USE_GL */