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