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