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