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