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