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