3d134b473d0343779bc84fd1d97fc07d659dc70a
[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   sc->nlines++;
465   if (sc->lines_size <= sc->nlines)
466     {
467       sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
468       sc->lines = (line **)
469         realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
470       if (! sc->lines)
471         {
472           fprintf (stderr, "%s: out of memory (%d lines)\n",
473                    progname, sc->lines_size);
474           exit (1);
475         }
476     }
477
478   sc->lines[sc->nlines-1] = ln;
479   return ln;
480 }
481
482
483 /* frees the object and removes it from the list.
484  */
485 static void
486 free_line (fliptext_configuration *sc, line *line)
487 {
488   int i;
489   for (i = 0; i < sc->nlines; i++)
490     if (sc->lines[i] == line)
491       break;
492   if (i == sc->nlines) abort();
493   for (; i < sc->nlines-1; i++)
494     sc->lines[i] = sc->lines[i+1];
495   sc->lines[i] = 0;
496   sc->nlines--;
497
498   free (line->text);
499   free (line);
500 }
501
502
503 static void
504 draw_line (ModeInfo *mi, line *line)
505 {
506   int wire = MI_IS_WIREFRAME(mi);
507   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
508
509   if (! line->text || !*line->text ||
510       line->state == NEW || line->state == HESITATE || line->state == DEAD)
511     return;
512
513   glPushMatrix();
514   glTranslatef (line->current.x, line->current.y, line->current.z);
515
516   glRotatef (line->cth, 0, 1, 0);
517
518   if (alignment == 1)
519     glTranslatef (-line->width, 0, 0);
520   else if (alignment == 0)
521     glTranslatef (-line->width/2, 0, 0);
522
523   glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
524
525   glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
526
527   if (!wire)
528     print_texture_string (sc->texfont, line->text);
529   else
530     {
531       int w, h;
532       char *s = line->text;
533       char c[2];
534       c[1]=0;
535       glDisable (GL_TEXTURE_2D);
536       glColor3f (0.4, 0.4, 0.4);
537       while (*s)
538         {
539           *c = *s++;
540           w = texture_string_width (sc->texfont, c, &h);
541           glBegin (GL_LINE_LOOP);
542           glVertex3f (0, 0, 0);
543           glVertex3f (w, 0, 0);
544           glVertex3f (w, h, 0);
545           glVertex3f (0, h, 0);
546           glEnd();
547           glTranslatef (w, 0, 0);
548         }
549     }
550
551 #if 0
552   glDisable (GL_TEXTURE_2D);
553   glColor3f (0.4, 0.4, 0.4);
554   glBegin (GL_LINE_LOOP);
555   glVertex3f (0, 0, 0);
556   glVertex3f (line->width/sc->font_scale, 0, 0);
557   glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
558   glVertex3f (0, line->height/sc->font_scale, 0);
559   glEnd();
560   if (!wire) glEnable (GL_TEXTURE_2D);
561 #endif
562
563   glPopMatrix();
564
565   mi->polygon_count += strlen (line->text);
566 }
567
568 static void
569 tick_line (fliptext_configuration *sc, line *line)
570 {
571   int stagger = 60;            /* frames of delay between line spin-outs */
572   int slide   = 600;           /* frames in a slide in/out */
573   int linger  = 0;             /* frames to pause with no motion */
574   double i;
575
576   if (line->state >= DEAD) abort();
577   if (++line->step >= line->steps)
578     {
579       line->state++;
580       line->step = 0;
581
582       if (linger == 0 && line->state == LINGER)
583         line->state++;
584
585       switch (line->state)
586         {
587         case HESITATE:                  /* entering state HESITATE */
588           switch (sc->anim_type)
589             {
590             case SPIN:
591               line->steps = (line->cluster_pos * stagger);
592               break;
593             case SCROLL_TOP:
594               line->steps = stagger * (line->cluster_size - line->cluster_pos);
595               break;
596             case SCROLL_BOTTOM:
597               line->steps = stagger * line->cluster_pos;
598               break;
599             default:
600               abort();
601             }
602           break;
603
604         case IN:
605           memset (line->color, 0, sizeof (line->color));
606           switch (sc->anim_type)
607             {
608             case SCROLL_BOTTOM:         /* entering state BOTTOM IN */
609               line->from = sc->in;
610               line->to   = sc->mid;
611               line->from.y = (sc->bottom_margin -
612                               (line->height *
613                                (line->cluster_pos + 1)));
614               line->to.y += (line->height *
615                              ((line->cluster_size/2.0) - line->cluster_pos));
616               line->steps = slide;
617               break;
618
619             case SCROLL_TOP:            /* entering state TOP IN */
620               line->from = sc->in;
621               line->to   = sc->mid;
622               line->from.y = (sc->top_margin +
623                               (line->height *
624                                (line->cluster_size - line->cluster_pos)));
625               line->to.y += (line->height *
626                              ((line->cluster_size/2.0) - line->cluster_pos));
627               line->steps = slide;
628               break;
629
630             case SPIN:                  /* entering state SPIN IN */
631               line->from = sc->in;
632               line->to   = sc->mid;
633               line->to.y += (line->height *
634                              ((line->cluster_size/2.0) - line->cluster_pos));
635               line->from.y += (line->height *
636                                ((line->cluster_size/2.0) - line->cluster_pos));
637
638               line->fth = 270;
639               line->tth = 0;
640               line->steps = slide;
641               break;
642
643             default:
644               abort();
645             }
646           break;
647
648         case OUT:
649           memcpy (sc->color, line->color, sizeof(line->color));
650           switch (sc->anim_type)
651             {
652             case SCROLL_BOTTOM:         /* entering state BOTTOM OUT */
653               line->from = line->to;
654               line->to   = sc->out;
655               line->to.y = (sc->top_margin +
656                             (line->height *
657                              (line->cluster_size - line->cluster_pos)));
658               line->steps = slide;
659               break;
660
661             case SCROLL_TOP:            /* entering state TOP OUT */
662               line->from = line->to;
663               line->to   = sc->out;
664               line->to.y = (sc->bottom_margin -
665                             (line->height *
666                              (line->cluster_pos + 1)));
667               line->steps = slide;
668               break;
669
670             case SPIN:                  /* entering state SPIN OUT */
671               line->from = line->to;
672               line->to   = sc->out;
673               line->to.y += (line->height *
674                              ((line->cluster_size/2.0) - line->cluster_pos));
675
676               line->fth = line->tth;
677               line->tth = -270;
678               line->steps = slide;
679               break;
680
681             default:
682               abort();
683             }
684           break;
685
686         case LINGER:
687           memcpy (sc->color, line->color, sizeof(line->color));
688           line->from = line->to;
689           line->steps = linger;
690           break;
691
692         default:
693           break;
694         }
695
696       line->steps /= speed;
697     }
698
699   switch (line->state)
700     {
701     case IN:
702     case OUT:
703       i = (double) line->step / line->steps;
704       if (line->state == IN) i = 1-i;
705       i = i * i;
706       if (line->state == IN) i = 1-i;
707
708       line->color[0] = sc->color[0] * (line->state == IN ? i : 1-i);
709       line->color[1] = sc->color[1] * (line->state == IN ? i : 1-i);
710       line->color[2] = sc->color[2] * (line->state == IN ? i : 1-i);
711       line->color[3] = sc->color[3] * (line->state == IN ? i : 1-i);
712
713       line->current.x = line->from.x + (i * (line->to.x - line->from.x));
714       line->current.y = line->from.y + (i * (line->to.y - line->from.y));
715       line->current.z = line->from.z + (i * (line->to.z - line->from.z));
716       line->cth = line->fth + (i * (line->tth - line->fth));
717       break;
718
719     case HESITATE:
720     case LINGER:
721     case DEAD:
722       break;
723     default:
724       abort();
725     }
726 }
727
728
729 /* Start a new cluster of lines going.
730    Pick their anim type, and in, mid, and out positions.
731  */
732 static void
733 reset_lines (ModeInfo *mi)
734 {
735   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
736   int i;
737   line *prev = 0;
738   GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
739
740   sc->rotation.x = 5 - BELLRAND(10);
741   sc->rotation.y = 5 - BELLRAND(10);
742   sc->rotation.z = 5 - BELLRAND(10);
743
744   switch (random() % 7)
745     {
746     case 0:  sc->anim_type = SCROLL_TOP;    break;
747     case 1:  sc->anim_type = SCROLL_BOTTOM; break;
748     default: sc->anim_type = SPIN;          break;
749     }
750
751   minx = sc->left_margin  * 0.9;
752   maxx = sc->right_margin * 0.9;
753
754   miny = sc->bottom_margin * 0.9;
755   maxy = sc->top_margin    * 0.9;
756
757   minz = sc->left_margin  * 5;
758   maxz = sc->right_margin * 2;
759
760   maxw = sc->font_wrap_pixels * sc->font_scale;
761   maxh = max_lines * sc->line_height * sc->font_scale;
762  
763   if (maxw > maxx - minx)
764     maxw = maxx - minx;
765   if (maxh > maxy - miny)
766     maxh = maxy - miny;
767       
768   if (alignment_random_p)
769     alignment = (random() % 3) - 1;
770
771   if      (alignment == -1) maxx -= maxw;
772   else if (alignment ==  1) minx += maxw;
773   else                      minx += maxw/2, maxx -= maxw/2;
774
775   miny += maxh/2;
776   maxy -= maxh/2;
777
778   sc->mid.x = minx + frand (maxx - minx);
779   sc->mid.y = miny + frand (maxy - miny);
780
781   sc->in.x  = BELLRAND(sc->right_margin * 2) - sc->right_margin;
782   sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
783
784   sc->in.y  = miny + frand(maxy - miny);
785   sc->out.y = miny + frand(maxy - miny);
786
787   sc->in.z  = minz + frand(maxz - minz);
788   sc->out.z = minz + frand(maxz - minz);
789
790   sc->mid.z = 0;
791
792   if (sc->anim_type == SPIN && sc->in.z  > 0) sc->in.z  /= 4;
793   if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
794
795   for (i = 0; i < max_lines; i++)
796     {
797       line *line = make_line (sc, (i == 0));
798       if (!line) break;                 /* no text available */
799       if (i >= min_lines &&
800           (!line->text || !*line->text))        /* blank after min */
801         break;
802     }
803
804   for (i = 0; i < sc->nlines; i++)
805     {
806       line *line = sc->lines[i];
807       if (!prev)
808         {
809           line->from.y = sc->bottom_margin;
810           line->to.y   = 0;
811         }
812       else
813         {
814           line->from.y = prev->from.y - prev->height;
815           line->to.y   = prev->to.y   - prev->height;
816         }
817       line->cluster_pos = i;
818       line->cluster_size = sc->nlines;
819       prev = line;
820     }
821 }
822
823
824 static void
825 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
826 {
827   XColor c;
828   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
829     {
830       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
831       exit (1);
832     }
833   a[0] = c.red   / 65536.0;
834   a[1] = c.green / 65536.0;
835   a[2] = c.blue  / 65536.0;
836   a[3] = 1.0;
837 }
838
839
840 /* Window management, etc
841  */
842 void
843 reshape_fliptext (ModeInfo *mi, int width, int height)
844 {
845   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
846   GLfloat h = (GLfloat) height / (GLfloat) width;
847
848   glViewport (0, 0, (GLint) width, (GLint) height);
849
850   glMatrixMode(GL_PROJECTION);
851   glLoadIdentity();
852   gluPerspective (60.0, 1/h, 0.01, 100.0);
853
854   glMatrixMode(GL_MODELVIEW);
855   glLoadIdentity();
856   gluLookAt( 0.0, 0.0, 2.6,
857              0.0, 0.0, 0.0,
858              0.0, 1.0, 0.0);
859
860   glClear(GL_COLOR_BUFFER_BIT);
861
862   sc->right_margin = sc->top_margin / h;
863   sc->left_margin = -sc->right_margin;
864 }
865
866
867 void 
868 init_fliptext (ModeInfo *mi)
869 {
870   int wire = MI_IS_WIREFRAME(mi);
871
872   fliptext_configuration *sc;
873
874   if (!scs) {
875     scs = (fliptext_configuration *)
876       calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
877     if (!scs) {
878       fprintf(stderr, "%s: out of memory\n", progname);
879       exit(1);
880     }
881
882     sc = &scs[MI_SCREEN(mi)];
883     sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
884   }
885
886   sc = &scs[MI_SCREEN(mi)];
887
888   if ((sc->glx_context = init_GL(mi)) != NULL) {
889     reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
890   }
891
892   program = get_string_resource ("program", "Program");
893
894   {
895     int cw, lh;
896     sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
897     check_gl_error ("loading font");
898     cw = texture_string_width (sc->texfont, "n", &lh);
899     sc->char_width = cw;
900     sc->line_height = lh;
901   }
902
903   if (!wire)
904     {
905       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
906       glEnable (GL_BLEND);
907       glEnable (GL_ALPHA_TEST);
908       glEnable (GL_TEXTURE_2D);
909
910 # ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT
911       /* "Anistropic filtering helps for quadrilateral-angled textures.
912          A sharper image is accomplished by interpolating and filtering
913          multiple samples from one or more mipmaps to better approximate
914          very distorted textures.  This is the next level of filtering
915          after trilinear filtering." */
916       glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
917       clear_gl_error();
918 # endif
919     }
920   
921   /* The default font is (by fiat) "18 points".
922      Interpret the user's font size request relative to that.
923    */
924   sc->font_scale = 3 * (font_size / 18.0);
925
926   if (target_columns <= 2) target_columns = 2;
927
928   /* Figure out what the wrap column should be, in font-coordinate pixels.
929      Compute it from the given -columns value, but don't let it be wider
930      than the screen.
931    */
932   {
933     GLfloat maxw = 110 * sc->line_height / sc->font_scale;  /* magic... */
934     sc->font_wrap_pixels = target_columns * sc->char_width;
935     if (sc->font_wrap_pixels > maxw ||
936         sc->font_wrap_pixels <= 0)
937       sc->font_wrap_pixels = maxw;
938   }
939
940   sc->buf_size = target_columns * max_lines;
941   sc->buf = (char *) calloc (1, sc->buf_size);
942
943   sc->subproc_relaunch_delay = 2 * 1000;   /* 2 seconds */
944
945   alignment_random_p = False;
946   if (!alignment_str || !*alignment_str ||
947       !strcasecmp(alignment_str, "left"))
948     alignment = -1;
949   else if (!strcasecmp(alignment_str, "center") ||
950            !strcasecmp(alignment_str, "middle"))
951     alignment = 0;
952   else if (!strcasecmp(alignment_str, "right"))
953     alignment = 1;
954   else if (!strcasecmp(alignment_str, "random"))
955     alignment = -1, alignment_random_p = True;
956
957   else
958     {
959       fprintf (stderr,
960                "%s: alignment must be left/center/right/random, not \"%s\"\n",
961                progname, alignment_str);
962       exit (1);
963     }
964
965   launch_text_generator (sc);
966
967   if (max_lines < 1) max_lines = 1;
968   min_lines = max_lines * 0.66;
969   if (min_lines > max_lines - 3) min_lines = max_lines - 4;
970   if (min_lines < 1) min_lines = 1;
971
972   parse_color (mi, "foreground",
973                get_string_resource("foreground", "Foreground"),
974                sc->color);
975
976   sc->top_margin = (sc->char_width * 100);
977   sc->bottom_margin = -sc->top_margin;
978   reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));  /* compute left/right */
979 }
980
981
982 void
983 draw_fliptext (ModeInfo *mi)
984 {
985   fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
986   Display *dpy = MI_DISPLAY(mi);
987   Window window = MI_WINDOW(mi);
988   int i;
989
990   if (!sc->glx_context)
991     return;
992
993   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
994     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
995
996   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
997
998   mi->polygon_count = 0;
999
1000   glPushMatrix();
1001   {
1002     GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
1003     glScalef(s, s, s);
1004   }
1005
1006   glRotatef (sc->rotation.x, 1, 0, 0);
1007   glRotatef (sc->rotation.y, 0, 1, 0);
1008   glRotatef (sc->rotation.z, 0, 0, 1);
1009
1010 #if 0
1011   glDisable (GL_TEXTURE_2D);
1012   glColor3f (1,1,1);
1013   glBegin (GL_LINE_LOOP);
1014   glVertex3f (sc->left_margin,  sc->top_margin, 0);
1015   glVertex3f (sc->right_margin, sc->top_margin, 0);
1016   glVertex3f (sc->right_margin, sc->bottom_margin, 0);
1017   glVertex3f (sc->left_margin,  sc->bottom_margin, 0);
1018   glEnd();
1019   glBegin (GL_LINES);
1020   glVertex3f (sc->in.x,  sc->top_margin,    sc->in.z);
1021   glVertex3f (sc->in.x,  sc->bottom_margin, sc->in.z);
1022   glVertex3f (sc->mid.x, sc->top_margin,    sc->mid.z);
1023   glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
1024   glVertex3f (sc->out.x, sc->top_margin,    sc->out.z);
1025   glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
1026   glEnd();
1027   glEnable (GL_TEXTURE_2D);
1028 #endif
1029
1030   for (i = 0; i < sc->nlines; i++)
1031     {
1032       line *line = sc->lines[i];
1033       draw_line (mi, line);
1034       tick_line (sc, line);
1035     }
1036
1037   for (i = sc->nlines-1; i >= 0; i--)
1038     {
1039       line *line = sc->lines[i];
1040       if (line->state == DEAD)
1041         free_line (sc, line);
1042     }
1043
1044   if (sc->nlines == 0)
1045     reset_lines (mi);
1046
1047   glPopMatrix();
1048
1049   if (mi->fps_p) do_fps (mi);
1050   glFinish();
1051   glXSwapBuffers(dpy, window);
1052 }
1053
1054 #endif /* USE_GL */