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