http://ftp.nluug.nl/pub/os/Linux/distr/pardusrepo/sources/xscreensaver-5.02.tar.gz
[xscreensaver] / hacks / glx / fliptext.c
1 /*
2  * fliptext, Copyright (c) 2005-2007 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_ALIGN      "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_ALIGN,     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   }
918
919   program = get_string_resource (mi->dpy, "program", "Program");
920
921   {
922     int cw, lh;
923     sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
924     check_gl_error ("loading font");
925     cw = texture_string_width (sc->texfont, "n", &lh);
926     sc->char_width = cw;
927     sc->line_height = lh;
928   }
929
930   if (!wire)
931     {
932       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
933       glEnable (GL_BLEND);
934       glEnable (GL_ALPHA_TEST);
935       glEnable (GL_TEXTURE_2D);
936
937       /* "Anistropic filtering helps for quadrilateral-angled textures.
938          A sharper image is accomplished by interpolating and filtering
939          multiple samples from one or more mipmaps to better approximate
940          very distorted textures.  This is the next level of filtering
941          after trilinear filtering." */
942       if (strstr ((char *) glGetString(GL_EXTENSIONS),
943                   "GL_EXT_texture_filter_anisotropic"))
944       {
945         GLfloat anisotropic = 0.0;
946         glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
947         if (anisotropic >= 1.0)
948           glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
949                            anisotropic);
950       }
951     }
952   
953   /* The default font is (by fiat) "18 points".
954      Interpret the user's font size request relative to that.
955    */
956   sc->font_scale = 3 * (font_size / 18.0);
957
958   if (target_columns <= 2) target_columns = 2;
959
960   /* Figure out what the wrap column should be, in font-coordinate pixels.
961      Compute it from the given -columns value, but don't let it be wider
962      than the screen.
963    */
964   {
965     GLfloat maxw = 110 * sc->line_height / sc->font_scale;  /* magic... */
966     sc->font_wrap_pixels = target_columns * sc->char_width;
967     if (sc->font_wrap_pixels > maxw ||
968         sc->font_wrap_pixels <= 0)
969       sc->font_wrap_pixels = maxw;
970   }
971
972   sc->buf_size = target_columns * max_lines;
973   sc->buf = (char *) calloc (1, sc->buf_size);
974
975   sc->subproc_relaunch_delay = 2 * 1000;   /* 2 seconds */
976
977   alignment_random_p = False;
978   if (!alignment_str || !*alignment_str ||
979       !strcasecmp(alignment_str, "left"))
980     alignment = -1;
981   else if (!strcasecmp(alignment_str, "center") ||
982            !strcasecmp(alignment_str, "middle"))
983     alignment = 0;
984   else if (!strcasecmp(alignment_str, "right"))
985     alignment = 1;
986   else if (!strcasecmp(alignment_str, "random"))
987     alignment = -1, alignment_random_p = True;
988
989   else
990     {
991       fprintf (stderr,
992                "%s: alignment must be left/center/right/random, not \"%s\"\n",
993                progname, alignment_str);
994       exit (1);
995     }
996
997   launch_text_generator (sc);
998
999   if (max_lines < 1) max_lines = 1;
1000   min_lines = max_lines * 0.66;
1001   if (min_lines > max_lines - 3) min_lines = max_lines - 4;
1002   if (min_lines < 1) min_lines = 1;
1003
1004   parse_color (mi, "foreground",
1005                get_string_resource(mi->dpy, "foreground", "Foreground"),
1006                sc->color);
1007
1008   sc->top_margin = (sc->char_width * 100);
1009   sc->bottom_margin = -sc->top_margin;
1010   reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));  /* compute left/right */
1011 }
1012
1013
1014 ENTRYPOINT void
1015 draw_fliptext (ModeInfo *mi)
1016 {
1017   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
1018 /*  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
1019   Display *dpy = MI_DISPLAY(mi);
1020   Window window = MI_WINDOW(mi);
1021   int i;
1022
1023   if (!sc->glx_context)
1024     return;
1025
1026   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
1027
1028 #if 0
1029   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1030     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1031 #endif
1032
1033   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1034
1035   mi->polygon_count = 0;
1036
1037   glPushMatrix();
1038   {
1039     GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
1040     glScalef(s, s, s);
1041   }
1042
1043   glRotatef (sc->rotation.x, 1, 0, 0);
1044   glRotatef (sc->rotation.y, 0, 1, 0);
1045   glRotatef (sc->rotation.z, 0, 0, 1);
1046
1047 #if 0
1048   glDisable (GL_TEXTURE_2D);
1049   glColor3f (1,1,1);
1050   glBegin (GL_LINE_LOOP);
1051   glVertex3f (sc->left_margin,  sc->top_margin, 0);
1052   glVertex3f (sc->right_margin, sc->top_margin, 0);
1053   glVertex3f (sc->right_margin, sc->bottom_margin, 0);
1054   glVertex3f (sc->left_margin,  sc->bottom_margin, 0);
1055   glEnd();
1056   glBegin (GL_LINES);
1057   glVertex3f (sc->in.x,  sc->top_margin,    sc->in.z);
1058   glVertex3f (sc->in.x,  sc->bottom_margin, sc->in.z);
1059   glVertex3f (sc->mid.x, sc->top_margin,    sc->mid.z);
1060   glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
1061   glVertex3f (sc->out.x, sc->top_margin,    sc->out.z);
1062   glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
1063   glEnd();
1064   glEnable (GL_TEXTURE_2D);
1065 #endif
1066
1067   for (i = 0; i < sc->nlines; i++)
1068     {
1069       line *line = sc->lines[i];
1070       draw_line (mi, line);
1071       tick_line (sc, line);
1072     }
1073
1074   for (i = sc->nlines-1; i >= 0; i--)
1075     {
1076       line *line = sc->lines[i];
1077       if (line->state == DEAD)
1078         free_line (sc, line);
1079     }
1080
1081   if (sc->nlines == 0)
1082     reset_lines (mi);
1083
1084   glPopMatrix();
1085
1086   if (mi->fps_p) do_fps (mi);
1087   glFinish();
1088   glXSwapBuffers(dpy, window);
1089 }
1090
1091 ENTRYPOINT void
1092 release_fliptext (ModeInfo *mi)
1093 {
1094   if (scs) {
1095     int screen;
1096     for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
1097       fliptext_configuration *sc = &scs[screen];
1098       if (sc->pipe_id)
1099         XtRemoveInput (sc->pipe_id);
1100       if (sc->pipe)
1101         pclose (sc->pipe);
1102       if (sc->pipe_timer)
1103         XtRemoveTimeOut (sc->pipe_timer);
1104
1105       /* #### there's more to free here */
1106     }
1107     free (scs);
1108     scs = 0;
1109   }
1110   FreeAllGL(mi);
1111 }
1112
1113 XSCREENSAVER_MODULE ("FlipText", fliptext)
1114
1115 #endif /* USE_GL */