http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / glx / fliptext.c
1 /*
2  * fliptext, Copyright (c) 2005-2011 Jamie Zawinski <jwz@jwz.org>
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
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif /* HAVE_CONFIG_H */
16
17 #include <ctype.h>
18 #include <sys/stat.h>
19
20 #ifndef HAVE_COCOA
21 # include <X11/Intrinsic.h>
22 #endif
23
24 #ifdef HAVE_UNISTD_H
25 # include <unistd.h>
26 #endif
27
28 #ifdef HAVE_UNAME
29 # include <sys/utsname.h>
30 #endif /* HAVE_UNAME */
31
32
33 /* Utopia 800 needs 64 512x512 textures (4096x4096 bitmap).
34    Utopia 720 needs 16 512x512 textures (2048x2048 bitmap).
35    Utopia 480 needs 16 512x512 textures (2048x2048 bitmap).
36    Utopia 400 needs  4 512x512 textures (1024x1024 bitmap).
37    Utopia 180 needs  1 512x512 texture.
38    Times  240 needs  1 512x512 texture.
39  */
40 #define DEF_FONT       "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-iso8859-1"
41 #define DEF_COLOR      "#00CCFF"
42
43 #define DEFAULTS "*delay:        10000      \n" \
44                  "*showFPS:      False      \n" \
45                  "*wireframe:    False      \n" \
46                  "*font:       " DEF_FONT  "\n" \
47                  ".foreground: " DEF_COLOR "\n" \
48
49 # define refresh_fliptext 0
50 # define fliptext_handle_event 0
51 #undef countof
52 #define countof(x) (sizeof((x))/sizeof((*x)))
53
54 #undef BELLRAND
55 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
56
57 #include "xlockmore.h"
58 #include "texfont.h"
59
60 #ifdef USE_GL /* whole file */
61
62 /* Should be in <GL/glext.h> */
63 # ifndef  GL_TEXTURE_MAX_ANISOTROPY_EXT
64 #  define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
65 # endif
66 # ifndef  GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
67 #  define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
68 # endif
69
70
71 #define DEF_PROGRAM    "xscreensaver-text --cols 0"  /* don't wrap */
72 #define DEF_LINES      "8"
73 #define DEF_FONT_SIZE  "20"
74 #define DEF_COLUMNS    "80"
75 #define DEF_ALIGNMENT  "random"
76 #define DEF_SPEED       "1.0"
77 #define TAB_WIDTH        8
78
79 #define FONT_WEIGHT       14
80 #define KEEP_ASPECT
81
82 typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state;
83 typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type;
84
85 typedef struct { GLfloat x, y, z; } XYZ;
86
87 typedef struct {
88   char *text;
89   GLfloat width, height;        /* size */
90   XYZ from, to, current;        /* start, end, and current position */
91   GLfloat fth, tth, cth;        /* rotation around Z */
92
93   int cluster_size;             /* how many lines in this cluster */
94   int cluster_pos;              /* position of this line in the cluster */
95
96   line_state state;             /* current motion model */
97   int step, steps;              /* progress along this path */
98   GLfloat color[4];
99
100 } line;
101
102
103 typedef struct {
104   Display *dpy;
105   GLXContext *glx_context;
106
107   texture_font_data *texfont;
108
109   FILE *pipe;
110   XtInputId pipe_id;
111   XtIntervalId pipe_timer;
112   Time subproc_relaunch_delay;
113
114   char *buf;
115   int buf_size;
116   int buf_tail;
117
118   int char_width;         /* in font units */
119   int line_height;        /* in font units */
120   double font_scale;      /* convert font units to display units */
121
122   int font_wrap_pixels;   /* in font units (for wrapping text) */
123
124   int top_margin, bottom_margin;
125   int left_margin, right_margin;
126
127   int nlines;
128   int lines_size;
129   line **lines;
130
131   line_anim_type anim_type;
132   XYZ in, mid, out;
133   XYZ rotation;
134   GLfloat color[4];
135
136 } fliptext_configuration;
137
138
139 static fliptext_configuration *scs = NULL;
140
141 static char *program;
142 static int max_lines, min_lines;
143 static float font_size;
144 static int target_columns;
145 static char *alignment_str;
146 static int alignment, alignment_random_p;
147 static GLfloat speed;
148
149 static XrmOptionDescRec opts[] = {
150   {"-program",     ".program",   XrmoptionSepArg, 0 },
151   {"-lines",       ".lines",     XrmoptionSepArg, 0 },
152   {"-size",        ".fontSize",  XrmoptionSepArg, 0 },
153   {"-columns",     ".columns",   XrmoptionSepArg, 0 },
154   {"-speed",       ".speed",     XrmoptionSepArg, 0 },
155 /*{"-font",        ".font",      XrmoptionSepArg, 0 },*/
156   {"-alignment",   ".alignment", XrmoptionSepArg, 0 },
157   {"-left",        ".alignment", XrmoptionNoArg,  "Left"   },
158   {"-right",       ".alignment", XrmoptionNoArg,  "Right"  },
159   {"-center",      ".alignment", XrmoptionNoArg,  "Center" },
160 };
161
162 static argtype vars[] = {
163   {&program,        "program",   "Program",    DEF_PROGRAM,   t_String},
164   {&max_lines,      "lines",     "Integer",    DEF_LINES,     t_Int},
165   {&font_size,      "fontSize",  "Float",      DEF_FONT_SIZE, t_Float},
166   {&target_columns, "columns",   "Integer",    DEF_COLUMNS,   t_Int},
167   {&alignment_str,  "alignment", "Alignment",  DEF_ALIGNMENT, t_String},
168   {&speed,          "speed",     "Speed",      DEF_SPEED,     t_Float},
169 };
170
171 ENTRYPOINT ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL};
172
173
174
175 /* Tabs are bad, mmmkay? */
176
177 static char *
178 untabify (const char *string)
179 {
180   const char *ostring = string;
181   char *result = (char *) malloc ((strlen(string) * 8) + 1);
182   char *out = result;
183   int col = 0;
184   while (*string)
185     {
186       if (*string == '\t')
187         {
188           do {
189             col++;
190             *out++ = ' ';
191           } while (col % TAB_WIDTH);
192           string++;
193         }
194       else if (*string == '\r' || *string == '\n')
195         {
196           *out++ = *string++;
197           col = 0;
198         }
199       else if (*string == '\010')    /* backspace */
200         {
201           if (string > ostring)
202             out--, string++;
203         }
204       else
205         {
206           *out++ = *string++;
207           col++;
208         }
209     }
210   *out = 0;
211
212   return result;
213 }
214
215 static void
216 strip (char *s, Bool leading, Bool trailing)
217 {
218   int L = strlen(s);
219   if (trailing)
220     while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
221       s[L--] = 0;
222   if (leading)
223     {
224       char *s2 = s;
225       while (*s2 == ' ' || *s2 == '\t')
226         s2++;
227       if (s == s2)
228         return;
229       while (*s2)
230         *s++ = *s2++;
231       *s = 0;
232     }
233 }
234
235
236 \f
237 /* Subprocess.
238    (This bit mostly cribbed from phosphor.c)
239  */
240
241 static void drain_input (fliptext_configuration *sc);
242
243 static void
244 subproc_cb (XtPointer closure, int *source, XtInputId *id)
245 {
246   fliptext_configuration *sc = (fliptext_configuration *) closure;
247   drain_input (sc);
248 }
249
250
251 static void
252 launch_text_generator (fliptext_configuration *sc)
253 {
254   XtAppContext app = XtDisplayToApplicationContext (sc->dpy);
255   char *oprogram = get_string_resource (sc->dpy, "program", "Program");
256   char *program = (char *) malloc (strlen (oprogram) + 10);
257   strcpy (program, "( ");
258   strcat (program, oprogram);
259   strcat (program, " ) 2>&1");
260
261   if ((sc->pipe = popen (program, "r")))
262     {
263       sc->pipe_id =
264         XtAppAddInput (app, fileno (sc->pipe),
265                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
266                        subproc_cb, (XtPointer) sc);
267     }
268   else
269     {
270       perror (program);
271     }
272 }
273
274
275 static void
276 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
277 {
278   fliptext_configuration *sc = (fliptext_configuration *) closure;
279   if (!sc->pipe_timer) abort();
280   sc->pipe_timer = 0;
281   launch_text_generator (sc);
282 }
283
284
285 /* When the subprocess has generated some output, this reads as much as it
286    can into sc->buf at sc->buf_tail.
287  */
288 static void
289 drain_input (fliptext_configuration *sc)
290 {
291   XtAppContext app = XtDisplayToApplicationContext (sc->dpy);
292   if (sc->buf_tail < sc->buf_size - 2)
293     {
294       int target = sc->buf_size - sc->buf_tail - 2;
295       int n = (sc->pipe
296                ? read (fileno (sc->pipe),
297                        (void *) (sc->buf + sc->buf_tail),
298                        target)
299                : 0);
300       if (n > 0)
301         {
302           sc->buf_tail += n;
303           sc->buf[sc->buf_tail] = 0;
304         }
305       else
306         {
307           if (sc->pipe)
308             {
309               XtRemoveInput (sc->pipe_id);
310               sc->pipe_id = 0;
311               pclose (sc->pipe);
312               sc->pipe = 0;
313             }
314
315           /* If the process didn't print a terminating newline, add one. */
316           if (sc->buf_tail > 1 &&
317               sc->buf[sc->buf_tail-1] != '\n')
318             {
319               sc->buf[sc->buf_tail++] = '\n';
320               sc->buf[sc->buf_tail] = 0;
321             }
322
323           /* Then add one more, just for giggles. */
324           sc->buf[sc->buf_tail++] = '\n';
325           sc->buf[sc->buf_tail] = 0;
326
327           /* Set up a timer to re-launch the subproc in a bit. */
328           sc->pipe_timer = XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
329                                             relaunch_generator_timer,
330                                             (XtPointer) sc);
331         }
332     }
333 }
334
335
336 static int
337 char_width (fliptext_configuration *sc, char c)
338 {
339   char s[2];
340   s[0] = c;
341   s[1] = 0;
342   return texture_string_width (sc->texfont, s, 0);
343 }
344
345
346 /* Returns a single line of text from the output buffer of the subprocess,
347    taking into account wrapping, centering, etc.  Returns 0 if no complete
348    line is currently available.
349  */
350 static char *
351 get_one_line (fliptext_configuration *sc)
352 {
353   char *result = 0;
354   int wrap_pix = sc->font_wrap_pixels;
355   int col = 0;
356   int col_pix = 0;
357
358   char *s = sc->buf;
359   while (!result)
360     {
361       int cw;
362
363       if (s >= sc->buf + sc->buf_tail)
364         /* Reached end of buffer before end of line.  Bail. */
365         return 0;
366
367       cw = char_width (sc, *s);
368
369       if (*s == '\r' || *s == '\n' ||
370           col_pix + cw >= wrap_pix)
371         {
372           int L = s - sc->buf;
373
374           if (*s == '\r' || *s == '\n')
375             {
376               if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
377                 *s++ = 0;
378
379               *s++ = 0;
380             }
381           else
382             {
383               /* We wrapped -- try to back up to the previous word boundary. */
384               char *s2 = s;
385               int n = 0;
386               while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
387                 s2--, n++;
388               if (s2 > sc->buf)
389                 {
390                   s = s2;
391                   *s++ = 0;
392                   L = s - sc->buf;
393                 }
394             }
395
396           if (result) abort();
397           result = (char *) malloc (L+1);
398           memcpy (result, sc->buf, L);
399           result[L] = 0;
400
401           {
402             char *t = result;
403             char *ut = untabify (t);
404             strip (ut, (alignment == 0), 1); /* if centering, strip
405                                                 leading whitespace too */
406             result = ut;
407             free (t);
408           }
409
410           if (sc->buf_tail > (s - sc->buf))
411             {
412               int i = sc->buf_tail - (s - sc->buf);
413               memmove (sc->buf, s, i);
414               sc->buf_tail = i;
415               sc->buf[sc->buf_tail] = 0;
416             }
417           else
418             {
419               sc->buf_tail = 0;
420             }
421
422           sc->buf[sc->buf_tail] = 0;
423           s = sc->buf;
424           col = 0;
425           col_pix = 0;
426         }
427       else
428         {
429           col++;
430           col_pix += cw;
431           if (*s == '\t')
432             {
433               int tab_pix = TAB_WIDTH * sc->char_width;
434               col     = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
435               col_pix = tab_pix   * ((col / tab_pix)   + 1);
436             }
437           s++;
438         }
439     }
440
441   return result;
442 }
443
444
445 static Bool
446 blank_p (const char *s)
447 {
448   for (; *s; s++)
449     if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
450       return False;
451   return True;
452 }
453
454 /* Reads some text from the subprocess, and creates and returns a `line'
455    object.  Adds that object to the lines list.   Returns 0 if no text
456    available yet.
457
458    If skip_blanks_p, then keep trying for new lines of text until we
459    get one that is not empty.
460  */
461 static line *
462 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
463 {
464   line *ln;
465   char *s;
466
467  AGAIN:
468   s = get_one_line (sc);
469   if (s && skip_blanks_p && blank_p (s))
470     {
471       free (s);
472       goto AGAIN;
473     }
474
475   if (!s) return 0;
476
477   ln = (line *) calloc (1, sizeof(*ln));
478   ln->text = s;
479   ln->state = NEW;
480   ln->width = sc->font_scale * texture_string_width (sc->texfont, s, 0);
481   ln->height = sc->font_scale * sc->line_height;
482
483   memcpy (ln->color, sc->color, sizeof(ln->color));
484
485   sc->nlines++;
486   if (sc->lines_size <= sc->nlines)
487     {
488       sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
489       sc->lines = (line **)
490         realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
491       if (! sc->lines)
492         {
493           fprintf (stderr, "%s: out of memory (%d lines)\n",
494                    progname, sc->lines_size);
495           exit (1);
496         }
497     }
498
499   sc->lines[sc->nlines-1] = ln;
500   return ln;
501 }
502
503
504 /* frees the object and removes it from the list.
505  */
506 static void
507 free_line (fliptext_configuration *sc, line *line)
508 {
509   int i;
510   for (i = 0; i < sc->nlines; i++)
511     if (sc->lines[i] == line)
512       break;
513   if (i == sc->nlines) abort();
514   for (; i < sc->nlines-1; i++)
515     sc->lines[i] = sc->lines[i+1];
516   sc->lines[i] = 0;
517   sc->nlines--;
518
519   free (line->text);
520   free (line);
521 }
522
523
524 static void
525 draw_line (ModeInfo *mi, line *line)
526 {
527   int wire = MI_IS_WIREFRAME(mi);
528   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
529
530   if (! line->text || !*line->text ||
531       line->state == NEW || line->state == HESITATE || line->state == DEAD)
532     return;
533
534   glPushMatrix();
535   glTranslatef (line->current.x, line->current.y, line->current.z);
536
537   glRotatef (line->cth, 0, 1, 0);
538
539   if (alignment == 1)
540     glTranslatef (-line->width, 0, 0);
541   else if (alignment == 0)
542     glTranslatef (-line->width/2, 0, 0);
543
544   glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
545
546   glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
547
548   if (!wire)
549     print_texture_string (sc->texfont, line->text);
550   else
551     {
552       int w, h;
553       char *s = line->text;
554       char c[2];
555       c[1]=0;
556       glDisable (GL_TEXTURE_2D);
557       glColor3f (0.4, 0.4, 0.4);
558       while (*s)
559         {
560           *c = *s++;
561           w = texture_string_width (sc->texfont, c, &h);
562           glBegin (GL_LINE_LOOP);
563           glVertex3f (0, 0, 0);
564           glVertex3f (w, 0, 0);
565           glVertex3f (w, h, 0);
566           glVertex3f (0, h, 0);
567           glEnd();
568           glTranslatef (w, 0, 0);
569         }
570     }
571
572 #if 0
573   glDisable (GL_TEXTURE_2D);
574   glColor3f (0.4, 0.4, 0.4);
575   glBegin (GL_LINE_LOOP);
576   glVertex3f (0, 0, 0);
577   glVertex3f (line->width/sc->font_scale, 0, 0);
578   glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
579   glVertex3f (0, line->height/sc->font_scale, 0);
580   glEnd();
581   if (!wire) glEnable (GL_TEXTURE_2D);
582 #endif
583
584   glPopMatrix();
585
586   mi->polygon_count += strlen (line->text);
587 }
588
589 static void
590 tick_line (fliptext_configuration *sc, line *line)
591 {
592   int stagger = 30;            /* frames of delay between line spin-outs */
593   int slide   = 600;           /* frames in a slide in/out */
594   int linger  = 0;             /* frames to pause with no motion */
595   double i, ii;
596
597   if (line->state >= DEAD) abort();
598   if (++line->step >= line->steps)
599     {
600       line->state++;
601       line->step = 0;
602
603       if (linger == 0 && line->state == LINGER)
604         line->state++;
605
606       if (sc->anim_type != SPIN)
607         stagger *= 2;
608
609       switch (line->state)
610         {
611         case HESITATE:                  /* entering state HESITATE */
612           switch (sc->anim_type)
613             {
614             case SPIN:
615               line->steps = (line->cluster_pos * stagger);
616               break;
617             case SCROLL_TOP:
618               line->steps = stagger * (line->cluster_size - line->cluster_pos);
619               break;
620             case SCROLL_BOTTOM:
621               line->steps = stagger * line->cluster_pos;
622               break;
623             default:
624               abort();
625             }
626           break;
627
628         case IN:
629           line->color[3] = 0;
630           switch (sc->anim_type)
631             {
632             case SCROLL_BOTTOM:         /* entering state BOTTOM IN */
633               line->from = sc->in;
634               line->to   = sc->mid;
635               line->from.y = (sc->bottom_margin -
636                               (line->height *
637                                (line->cluster_pos + 1)));
638               line->to.y += (line->height *
639                              ((line->cluster_size/2.0) - line->cluster_pos));
640               line->steps = slide;
641               break;
642
643             case SCROLL_TOP:            /* entering state TOP IN */
644               line->from = sc->in;
645               line->to   = sc->mid;
646               line->from.y = (sc->top_margin +
647                               (line->height *
648                                (line->cluster_size - line->cluster_pos)));
649               line->to.y += (line->height *
650                              ((line->cluster_size/2.0) - line->cluster_pos));
651               line->steps = slide;
652               break;
653
654             case SPIN:                  /* entering state SPIN IN */
655               line->from = sc->in;
656               line->to   = sc->mid;
657               line->to.y += (line->height *
658                              ((line->cluster_size/2.0) - line->cluster_pos));
659               line->from.y += (line->height *
660                                ((line->cluster_size/2.0) - line->cluster_pos));
661
662               line->fth = 270;
663               line->tth = 0;
664               line->steps = slide;
665               break;
666
667             default:
668               abort();
669             }
670           break;
671
672         case OUT:
673           switch (sc->anim_type)
674             {
675             case SCROLL_BOTTOM:         /* entering state BOTTOM OUT */
676               line->from = line->to;
677               line->to   = sc->out;
678               line->to.y = (sc->top_margin +
679                             (line->height *
680                              (line->cluster_size - line->cluster_pos)));
681               line->steps = slide;
682               break;
683
684             case SCROLL_TOP:            /* entering state TOP OUT */
685               line->from = line->to;
686               line->to   = sc->out;
687               line->to.y = (sc->bottom_margin -
688                             (line->height *
689                              (line->cluster_pos + 1)));
690               line->steps = slide;
691               break;
692
693             case SPIN:                  /* entering state SPIN OUT */
694               line->from = line->to;
695               line->to   = sc->out;
696               line->to.y += (line->height *
697                              ((line->cluster_size/2.0) - line->cluster_pos));
698
699               line->fth = line->tth;
700               line->tth = -270;
701               line->steps = slide;
702               break;
703
704             default:
705               abort();
706             }
707           break;
708
709         case LINGER:
710           line->from = line->to;
711           line->steps = linger;
712           break;
713
714         default:
715           break;
716         }
717
718       line->steps /= speed;
719     }
720
721   switch (line->state)
722     {
723     case IN:
724     case OUT:
725       i = (double) line->step / line->steps;
726
727       /* Move along the path exponentially, slow side towards the middle. */
728       if (line->state == OUT)
729         ii = i * i;
730       else
731         ii = 1 - ((1-i) * (1-i));
732
733       line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
734       line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
735       line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
736       line->cth = line->fth + (ii * (line->tth - line->fth));
737
738       if (line->state == OUT) ii = 1-ii;
739       line->color[3] = sc->color[3] * ii;
740       break;
741
742     case HESITATE:
743     case LINGER:
744     case DEAD:
745       break;
746     default:
747       abort();
748     }
749 }
750
751
752 /* Start a new cluster of lines going.
753    Pick their anim type, and in, mid, and out positions.
754  */
755 static void
756 reset_lines (ModeInfo *mi)
757 {
758   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
759   int i;
760   line *prev = 0;
761   GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
762
763   sc->rotation.x = 5 - BELLRAND(10);
764   sc->rotation.y = 5 - BELLRAND(10);
765   sc->rotation.z = 5 - BELLRAND(10);
766
767   switch (random() % 8)
768     {
769     case 0:  sc->anim_type = SCROLL_TOP;    break;
770     case 1:  sc->anim_type = SCROLL_BOTTOM; break;
771     default: sc->anim_type = SPIN;          break;
772     }
773
774   minx = sc->left_margin  * 0.9;
775   maxx = sc->right_margin * 0.9;
776
777   miny = sc->bottom_margin * 0.9;
778   maxy = sc->top_margin    * 0.9;
779
780   minz = sc->left_margin  * 5;
781   maxz = sc->right_margin * 2;
782
783   maxw = sc->font_wrap_pixels * sc->font_scale;
784   maxh = max_lines * sc->line_height * sc->font_scale;
785  
786   if (maxw > maxx - minx)
787     maxw = maxx - minx;
788   if (maxh > maxy - miny)
789     maxh = maxy - miny;
790       
791   if (alignment_random_p)
792     alignment = (random() % 3) - 1;
793
794   if      (alignment == -1) maxx -= maxw;
795   else if (alignment ==  1) minx += maxw;
796   else                      minx += maxw/2, maxx -= maxw/2;
797
798   miny += maxh/2;
799   maxy -= maxh/2;
800
801   sc->mid.x = minx + frand (maxx - minx);
802   if (sc->anim_type == SPIN)
803     sc->mid.y = miny + BELLRAND (maxy - miny);
804   else
805     sc->mid.y = miny + frand (maxy - miny);
806
807   sc->in.x  = BELLRAND(sc->right_margin * 2) - sc->right_margin;
808   sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
809
810   sc->in.y  = miny + frand(maxy - miny);
811   sc->out.y = miny + frand(maxy - miny);
812
813   sc->in.z  = minz + frand(maxz - minz);
814   sc->out.z = minz + frand(maxz - minz);
815
816   sc->mid.z = 0;
817
818   if (sc->anim_type == SPIN && sc->in.z  > 0) sc->in.z  /= 4;
819   if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
820
821   for (i = 0; i < max_lines; i++)
822     {
823       line *line = make_line (sc, (i == 0));
824       if (!line) break;                 /* no text available */
825       if (i >= min_lines &&
826           (!line->text || !*line->text))        /* blank after min */
827         break;
828     }
829
830   for (i = 0; i < sc->nlines; i++)
831     {
832       line *line = sc->lines[i];
833       if (!prev)
834         {
835           line->from.y = sc->bottom_margin;
836           line->to.y   = 0;
837         }
838       else
839         {
840           line->from.y = prev->from.y - prev->height;
841           line->to.y   = prev->to.y   - prev->height;
842         }
843       line->cluster_pos = i;
844       line->cluster_size = sc->nlines;
845       prev = line;
846     }
847 }
848
849
850 static void
851 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
852 {
853   XColor c;
854   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
855     {
856       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
857       exit (1);
858     }
859   a[0] = c.red   / 65536.0;
860   a[1] = c.green / 65536.0;
861   a[2] = c.blue  / 65536.0;
862   a[3] = 1.0;
863 }
864
865
866 /* Window management, etc
867  */
868 ENTRYPOINT void
869 reshape_fliptext (ModeInfo *mi, int width, int height)
870 {
871   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
872   GLfloat h = (GLfloat) height / (GLfloat) width;
873
874   glViewport (0, 0, (GLint) width, (GLint) height);
875
876   glMatrixMode(GL_PROJECTION);
877   glLoadIdentity();
878   gluPerspective (60.0, 1/h, 0.01, 100.0);
879
880   glMatrixMode(GL_MODELVIEW);
881   glLoadIdentity();
882   gluLookAt( 0.0, 0.0, 2.6,
883              0.0, 0.0, 0.0,
884              0.0, 1.0, 0.0);
885
886   glClear(GL_COLOR_BUFFER_BIT);
887
888   sc->right_margin = sc->top_margin / h;
889   sc->left_margin = -sc->right_margin;
890 }
891
892
893 ENTRYPOINT void 
894 init_fliptext (ModeInfo *mi)
895 {
896   int wire = MI_IS_WIREFRAME(mi);
897
898   fliptext_configuration *sc;
899
900   if (!scs) {
901     scs = (fliptext_configuration *)
902       calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
903     if (!scs) {
904       fprintf(stderr, "%s: out of memory\n", progname);
905       exit(1);
906     }
907
908     sc = &scs[MI_SCREEN(mi)];
909     sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
910   }
911
912   sc = &scs[MI_SCREEN(mi)];
913   sc->dpy = MI_DISPLAY(mi);
914
915   if ((sc->glx_context = init_GL(mi)) != NULL) {
916     reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
917     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
918   }
919
920   program = get_string_resource (mi->dpy, "program", "Program");
921
922   {
923     int cw, lh;
924     sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
925     check_gl_error ("loading font");
926     cw = texture_string_width (sc->texfont, "n", &lh);
927     sc->char_width = cw;
928     sc->line_height = lh;
929   }
930
931   if (!wire)
932     {
933       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
934       glEnable (GL_BLEND);
935       glEnable (GL_ALPHA_TEST);
936       glEnable (GL_TEXTURE_2D);
937
938       /* "Anistropic filtering helps for quadrilateral-angled textures.
939          A sharper image is accomplished by interpolating and filtering
940          multiple samples from one or more mipmaps to better approximate
941          very distorted textures.  This is the next level of filtering
942          after trilinear filtering." */
943       if (strstr ((char *) glGetString(GL_EXTENSIONS),
944                   "GL_EXT_texture_filter_anisotropic"))
945       {
946         GLfloat anisotropic = 0.0;
947         glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
948         if (anisotropic >= 1.0)
949           glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
950                            anisotropic);
951       }
952     }
953   
954   /* The default font is (by fiat) "18 points".
955      Interpret the user's font size request relative to that.
956    */
957   sc->font_scale = 3 * (font_size / 18.0);
958
959   if (target_columns <= 2) target_columns = 2;
960
961   /* Figure out what the wrap column should be, in font-coordinate pixels.
962      Compute it from the given -columns value, but don't let it be wider
963      than the screen.
964    */
965   {
966     GLfloat maxw = 110 * sc->line_height / sc->font_scale;  /* magic... */
967     sc->font_wrap_pixels = target_columns * sc->char_width;
968     if (sc->font_wrap_pixels > maxw ||
969         sc->font_wrap_pixels <= 0)
970       sc->font_wrap_pixels = maxw;
971   }
972
973   sc->buf_size = target_columns * max_lines;
974   sc->buf = (char *) calloc (1, sc->buf_size);
975
976   sc->subproc_relaunch_delay = 2 * 1000;   /* 2 seconds */
977
978   alignment_random_p = False;
979   if (!alignment_str || !*alignment_str ||
980       !strcasecmp(alignment_str, "left"))
981     alignment = -1;
982   else if (!strcasecmp(alignment_str, "center") ||
983            !strcasecmp(alignment_str, "middle"))
984     alignment = 0;
985   else if (!strcasecmp(alignment_str, "right"))
986     alignment = 1;
987   else if (!strcasecmp(alignment_str, "random"))
988     alignment = -1, alignment_random_p = True;
989
990   else
991     {
992       fprintf (stderr,
993                "%s: alignment must be left/center/right/random, not \"%s\"\n",
994                progname, alignment_str);
995       exit (1);
996     }
997
998   launch_text_generator (sc);
999
1000   if (max_lines < 1) max_lines = 1;
1001   min_lines = max_lines * 0.66;
1002   if (min_lines > max_lines - 3) min_lines = max_lines - 4;
1003   if (min_lines < 1) min_lines = 1;
1004
1005   parse_color (mi, "foreground",
1006                get_string_resource(mi->dpy, "foreground", "Foreground"),
1007                sc->color);
1008
1009   sc->top_margin = (sc->char_width * 100);
1010   sc->bottom_margin = -sc->top_margin;
1011   reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));  /* compute left/right */
1012 }
1013
1014
1015 ENTRYPOINT void
1016 draw_fliptext (ModeInfo *mi)
1017 {
1018   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
1019 /*  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
1020   Display *dpy = MI_DISPLAY(mi);
1021   Window window = MI_WINDOW(mi);
1022   int i;
1023
1024   if (!sc->glx_context)
1025     return;
1026
1027   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
1028
1029 #if 0
1030   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1031     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1032 #endif
1033
1034   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1035
1036   mi->polygon_count = 0;
1037
1038   glPushMatrix();
1039   {
1040     GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
1041     glScalef(s, s, s);
1042   }
1043
1044   glRotatef (sc->rotation.x, 1, 0, 0);
1045   glRotatef (sc->rotation.y, 0, 1, 0);
1046   glRotatef (sc->rotation.z, 0, 0, 1);
1047
1048 #if 0
1049   glDisable (GL_TEXTURE_2D);
1050   glColor3f (1,1,1);
1051   glBegin (GL_LINE_LOOP);
1052   glVertex3f (sc->left_margin,  sc->top_margin, 0);
1053   glVertex3f (sc->right_margin, sc->top_margin, 0);
1054   glVertex3f (sc->right_margin, sc->bottom_margin, 0);
1055   glVertex3f (sc->left_margin,  sc->bottom_margin, 0);
1056   glEnd();
1057   glBegin (GL_LINES);
1058   glVertex3f (sc->in.x,  sc->top_margin,    sc->in.z);
1059   glVertex3f (sc->in.x,  sc->bottom_margin, sc->in.z);
1060   glVertex3f (sc->mid.x, sc->top_margin,    sc->mid.z);
1061   glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
1062   glVertex3f (sc->out.x, sc->top_margin,    sc->out.z);
1063   glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
1064   glEnd();
1065   glEnable (GL_TEXTURE_2D);
1066 #endif
1067
1068   for (i = 0; i < sc->nlines; i++)
1069     {
1070       line *line = sc->lines[i];
1071       draw_line (mi, line);
1072       tick_line (sc, line);
1073     }
1074
1075   for (i = sc->nlines-1; i >= 0; i--)
1076     {
1077       line *line = sc->lines[i];
1078       if (line->state == DEAD)
1079         free_line (sc, line);
1080     }
1081
1082   if (sc->nlines == 0)
1083     reset_lines (mi);
1084
1085   glPopMatrix();
1086
1087   if (mi->fps_p) do_fps (mi);
1088   glFinish();
1089   glXSwapBuffers(dpy, window);
1090 }
1091
1092 ENTRYPOINT void
1093 release_fliptext (ModeInfo *mi)
1094 {
1095   if (scs) {
1096     int screen;
1097     for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
1098       fliptext_configuration *sc = &scs[screen];
1099       if (sc->pipe_id)
1100         XtRemoveInput (sc->pipe_id);
1101       if (sc->pipe)
1102         pclose (sc->pipe);
1103       if (sc->pipe_timer)
1104         XtRemoveTimeOut (sc->pipe_timer);
1105
1106       /* #### there's more to free here */
1107     }
1108     free (scs);
1109     scs = 0;
1110   }
1111   FreeAllGL(mi);
1112 }
1113
1114 XSCREENSAVER_MODULE ("FlipText", fliptext)
1115
1116 #endif /* USE_GL */