]> git.hungrycats.org Git - xscreensaver/blob - hacks/glx/starwars.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / hacks / glx / starwars.c
1 /* starwars, Copyright (c) 1998-2018 Jamie Zawinski <jwz@jwz.org> and
2  * Claudio Matsuoka <claudio@helllabs.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  * Star Wars -- Phosphor meets a well-known scroller from a galaxy far,
13  *           far away.
14  *
15  * Feb 2000 Claudio Matsuoka    First version.
16  * Jan 2001 Jamie Zawinski      Rewrote large sections to add the ability to
17  *                              run a subprocess, customization of the font
18  *                              size and other parameters, etc.
19  * Feb 2001 jepler@inetnebr.com Added anti-aliased lines, and fade-to-black.
20  * Feb 2005 Jamie Zawinski      Added texture fonts.
21  *
22  *
23  * For the fanboys:
24  *
25  *     starwars -program 'cat starwars.txt' -columns 25 -no-wrap
26  */
27
28 #define DEFAULTS "*delay:    40000     \n" \
29                  "*showFPS:  False     \n" \
30                  "*fpsTop:   True      \n" \
31                  "*usePty:   False     \n" \
32                  "*texFontCacheSize: 300\n" \
33                  "*font:   " DEF_FONT "\n" \
34                  "*textLiteral: " DEF_TEXT "\n" \
35                  "*program: xscreensaver-text --cols 0"  /* don't wrap */
36
37 # define release_sws 0
38 # define sws_handle_event xlockmore_no_events
39
40 #include "xlockmore.h"
41 #include "textclient.h"
42 #include "utf8wc.h"
43 #include "starwars.h"
44
45 #include <ctype.h>
46 #include <sys/stat.h>
47
48 #ifdef HAVE_UNISTD_H
49 # include <unistd.h>
50 #endif
51
52 #ifdef USE_GL /* whole file */
53
54 /* Should be in <GL/glext.h> */
55 # ifndef  GL_TEXTURE_MAX_ANISOTROPY_EXT
56 #  define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
57 # endif
58 # ifndef  GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
59 #  define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
60 # endif
61
62
63 #define DEF_LINES      "125"
64 #define DEF_STEPS      "35"
65 #define DEF_SPIN       "0.03"
66 #define DEF_SATURATION "0.3"
67 #define DEF_SIZE       "-1"
68 #define DEF_COLUMNS    "-1"
69 #define DEF_LINE_WRAP  "True"
70 #define DEF_ALIGNMENT  "Center"
71 #define DEF_SMOOTH     "True"
72 #define DEF_THICK      "True"
73 #define DEF_FADE       "True"
74 #define DEF_TEXTURES   "True"
75 #define DEF_DEBUG      "False"
76 #define DEF_FONT       "sans-serif 36"
77
78 #define TAB_WIDTH        8
79
80 #define MAX_THICK_LINES   25
81 #define FONT_WEIGHT       14
82
83 #ifndef HAVE_MOBILE
84 # define KEEP_ASPECT    /* Letterboxing looks dumb on iPhone. */
85 #endif
86
87 #include "texfont.h"
88 #include "glutstroke.h"
89 #include "glut_roman.h"
90 #define GLUT_FONT (&glutStrokeRoman)
91
92 typedef struct {
93   Display *dpy;
94   GLXContext *glx_context;
95
96   GLuint text_list, star_list;
97   texture_font_data *texfont;
98   text_data *tc;
99
100   char *buf;
101   int buf_size;
102   int buf_tail;
103
104   char **lines;
105   int *line_widths;
106   int total_lines;
107
108   double star_theta;
109   double char_width;
110   double line_height;
111   double descent;
112   double font_scale;
113   double intra_line_scroll;
114
115   int line_pixel_width;   /* in font units (for wrapping text) */
116   int line_pixel_height;  /* in screen units (for computing line thickness) */
117   GLfloat line_thickness;
118
119 } sws_configuration;
120
121
122 static sws_configuration *scs = NULL;
123
124 static int max_lines;
125 static int scroll_steps;
126 static float star_spin;
127 static float star_saturation;
128 static float font_size;
129 static int target_columns;
130 static int wrap_p;
131 static int smooth_p;
132 static int thick_p;
133 static int fade_p;
134 static int textures_p;
135 static int debug_p;
136 static char *alignment_str;
137 static int alignment;
138
139 static XrmOptionDescRec opts[] = {
140   {"-lines",       ".lines",     XrmoptionSepArg, 0 },
141   {"-steps",       ".steps",     XrmoptionSepArg, 0 },
142   {"-spin",        ".spin",      XrmoptionSepArg, 0 },
143   {"-saturation",  ".saturation",XrmoptionSepArg, 0 },
144   {"-size",        ".size",      XrmoptionSepArg, 0 },
145   {"-columns",     ".columns",   XrmoptionSepArg, 0 },
146 /*{"-font",        ".font",      XrmoptionSepArg, 0 },*/
147   {"-program",     ".program",   XrmoptionSepArg, 0 },
148   {"-fade",        ".fade",      XrmoptionNoArg,  "True"   },
149   {"-no-fade",     ".fade",      XrmoptionNoArg,  "False"  },
150   {"-textures",    ".textures",  XrmoptionNoArg,  "True"   },
151   {"-smooth",      ".smooth",    XrmoptionNoArg,  "True"   },
152   {"-no-smooth",   ".smooth",    XrmoptionNoArg,  "False"  },
153   {"-thick",       ".thick",     XrmoptionNoArg,  "True"   },
154   {"-no-thick",    ".thick",     XrmoptionNoArg,  "False"  },
155   {"-no-textures", ".textures",  XrmoptionNoArg,  "False"  },
156   {"-wrap",        ".lineWrap",  XrmoptionNoArg,  "True"   },
157   {"-no-wrap",     ".lineWrap",  XrmoptionNoArg,  "False"  },
158   {"-nowrap",      ".lineWrap",  XrmoptionNoArg,  "False"  },
159   {"-alignment",   ".alignment", XrmoptionSepArg, 0        },
160   {"-left",        ".alignment", XrmoptionNoArg,  "Left"   },
161   {"-right",       ".alignment", XrmoptionNoArg,  "Right"  },
162   {"-center",      ".alignment", XrmoptionNoArg,  "Center" },
163   {"-debug",       ".debug",     XrmoptionNoArg,  "True"   },
164 };
165
166 static argtype vars[] = {
167   {&max_lines,      "lines",     "Integer",    DEF_LINES,     t_Int},
168   {&scroll_steps,   "steps",     "Integer",    DEF_STEPS,     t_Int},
169   {&star_spin,      "spin",      "Float",      DEF_SPIN,      t_Float},
170   {&star_saturation,"saturation","Float",      DEF_SATURATION,t_Float},
171   {&font_size,      "size",      "Float",      DEF_SIZE,      t_Float},
172   {&target_columns, "columns",   "Integer",    DEF_COLUMNS,   t_Int},
173   {&wrap_p,         "lineWrap",  "Boolean",    DEF_LINE_WRAP, t_Bool},
174   {&alignment_str,  "alignment", "Alignment",  DEF_ALIGNMENT, t_String},
175   {&smooth_p,       "smooth",    "Boolean",    DEF_SMOOTH,    t_Bool},
176   {&thick_p,        "thick",     "Boolean",    DEF_THICK,     t_Bool},
177   {&fade_p,         "fade",      "Boolean",    DEF_FADE,      t_Bool},
178   {&textures_p,     "textures",  "Boolean",    DEF_TEXTURES,  t_Bool},
179   {&debug_p,        "debug",     "Boolean",    DEF_DEBUG,     t_Bool},
180 };
181
182 ENTRYPOINT ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
183
184
185
186 /* Tabs are bad, mmmkay? */
187
188 static char *
189 untabify (const char *string)
190 {
191   const char *ostring = string;
192   char *result = (char *) malloc ((strlen(string) * 8) + 1);
193   char *out = result;
194   int col = 0;
195   while (*string)
196     {
197       if (*string == '\t')
198         {
199           do {
200             col++;
201             *out++ = ' ';
202           } while (col % TAB_WIDTH);
203           string++;
204         }
205       else if (*string == '\r' || *string == '\n')
206         {
207           *out++ = *string++;
208           col = 0;
209         }
210       else if (*string == '\010')    /* backspace */
211         {
212           if (string > ostring)
213             out--, string++;
214         }
215       else
216         {
217           *out++ = *string++;
218           col++;
219         }
220     }
221   *out = 0;
222
223   return result;
224 }
225
226 static void
227 strip (char *s, Bool leading, Bool trailing)
228 {
229   int L = strlen(s);
230   if (trailing)
231     while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
232       s[L--] = 0;
233   if (leading)
234     {
235       char *s2 = s;
236       while (*s2 == ' ' || *s2 == '\t')
237         s2++;
238       if (s == s2)
239         return;
240       while (*s2)
241         *s++ = *s2++;
242       *s = 0;
243     }
244 }
245
246
247 static int
248 sw_string_width (sws_configuration *sc, const char *s)
249 {
250   if (textures_p)
251     {
252       XCharStruct e;
253       texture_string_metrics (sc->texfont, s, &e, 0, 0);
254       return e.width;
255     }
256   else
257     return glutStrokeLength (GLUT_FONT, (unsigned char *) s);
258 }
259
260
261 static int
262 sw_string_width2 (sws_configuration *sc, const char *s, size_t size)
263 {
264   char *s2 = (char *) malloc (size + 1);
265   int result;
266
267   strncpy (s2, s, size);
268   s2[size] = 0;
269
270   result = sw_string_width (sc, s2);
271
272   free (s2);
273   return result;
274 }
275
276
277 /* Populates the sc->lines list with as many lines as possible.
278  */
279 static void
280 get_more_lines (sws_configuration *sc)
281 {
282   /* wrap anyway, if it's absurdly long. */
283   int wrap_pix = (wrap_p ? sc->line_pixel_width : 10000);
284
285   char *s = sc->buf;
286
287   int target = sc->buf_size - sc->buf_tail - 2;
288
289   /* Fill as much as we can into sc->buf.
290    */
291   while (target > 0)
292     {
293       int c = textclient_getc (sc->tc);
294       if (c <= 0)
295         break;
296       sc->buf[sc->buf_tail++] = (char) c;
297       sc->buf[sc->buf_tail] = 0;
298       target--;
299     }
300
301   while (sc->total_lines < max_lines)
302     {
303       char *next_s = s;
304       unsigned counter = 0;
305
306       /* OS X is really slow to calcuate the bounds for a line of text,
307          so skip ahead a bit.
308
309          Really though, the right thing to do is probably to wrap
310          CTLineCreateTruncatedLine, one way or another. */
311       for (;;)
312         {
313           if (next_s >= sc->buf + sc->buf_tail)
314             break;
315
316           if (!counter)
317             {
318               if (s > sc->buf &&
319                   sw_string_width2 (sc, sc->buf, next_s - sc->buf) >= wrap_pix)
320                 break;
321
322               counter = 12; /* <-- Adjust to taste. */
323               s = next_s;
324             }
325
326           if (*next_s == '\r' || *next_s == '\n')
327             break;
328
329           --counter;
330           ++next_s;
331         }
332
333       for (;;)
334         {
335           if (s >= sc->buf + sc->buf_tail)
336             /* Reached end of buffer before end of line.  Bail. */
337             return;
338
339           /* When checking pixel width, always measure the line from the
340              beginning, or else multi-byte UTF-8 characters, particularly
341              combining diacriticals, aren't measured right. */
342
343           if (*s == '\r' || *s == '\n' ||
344               (s > sc->buf && sw_string_width2 (sc, sc->buf, s - sc->buf) >= wrap_pix))
345             {
346               int L = s - sc->buf;
347
348               if (*s == '\r' || *s == '\n')
349                 {
350                   if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
351                     *s++ = 0;
352
353                   *s++ = 0;
354                 }
355               else
356                 {
357                   /* We wrapped -- try to back up to the previous word boundary. */
358                   char *s2 = s;
359                   int n = 0;
360                   while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
361                     s2--, n++;
362                   if (s2 > sc->buf)
363                     {
364                       s = s2;
365                       *s++ = 0;
366                       L = s - sc->buf;
367                     }
368                 }
369
370               sc->lines[sc->total_lines] = (char *) malloc (L+1);
371               memcpy (sc->lines[sc->total_lines], sc->buf, L);
372               sc->lines[sc->total_lines][L] = 0;
373
374               if (!textures_p)
375                 {
376                   /* The GLUT font only has ASCII characters. */
377                   char *s1 = utf8_to_latin1 (sc->lines[sc->total_lines], True);
378                   free (sc->lines[sc->total_lines]);
379                   sc->lines[sc->total_lines] = s1;
380                 }
381
382               {
383                 char *t = sc->lines[sc->total_lines];
384                 char *ut = untabify (t);
385                 strip (ut, (alignment == 0), 1); /* if centering, strip
386                                                     leading whitespace too */
387                 sc->lines[sc->total_lines] = ut;
388                 free (t);
389               }
390
391               sc->line_widths[sc->total_lines] =
392                 sw_string_width(sc, sc->lines[sc->total_lines]);
393
394               sc->total_lines++;
395
396               if (sc->buf_tail > (s - sc->buf))
397                 {
398                   int i = sc->buf_tail - (s - sc->buf);
399                   memmove (sc->buf, s, i);
400                   sc->buf_tail = i;
401                   sc->buf[sc->buf_tail] = 0;
402                 }
403               else
404                 {
405                   sc->buf_tail = 0;
406                 }
407
408               sc->buf[sc->buf_tail] = 0;
409               s = sc->buf;
410
411               break;
412             }
413           else
414             {
415               s++;
416             }
417         }
418     }
419 }
420
421
422 static void
423 draw_string (sws_configuration *sc, GLfloat x, GLfloat y, const char *s)
424 {
425   const char *os = s;
426   if (!s || !*s) return;
427   glPushMatrix ();
428   glTranslatef (x, y, 0);
429
430   if (textures_p)
431     print_texture_string (sc->texfont, s);
432   else
433     while (*s)
434       glutStrokeCharacter (GLUT_FONT, *s++);
435   glPopMatrix ();
436
437   if (debug_p)
438     {
439       GLfloat h = sc->line_height / sc->font_scale;
440       char **chars = utf8_split (os, 0);
441       int i, w = 0;
442       char *s2 = (char *) malloc (strlen(s) + 1);
443       *s2 = 0;
444
445       if (textures_p) glDisable (GL_TEXTURE_2D);
446       glLineWidth (1);
447
448       glColor3f (0.2, 0.2, 0.5);
449
450       glBegin (GL_LINES);
451
452       for (i = 0; chars[i]; i++)
453         {
454           glVertex3f (x + w, y - sc->descent, 0);       /* char left */
455           glVertex3f (x + w, y - sc->descent + h, 0);
456           strcat (s2, chars[i]);
457           w = sw_string_width (sc, s2);
458           free (chars[i]);
459         }
460
461       glVertex3f (x + w, y - sc->descent, 0);           /* char right */
462       glVertex3f (x + w, y - sc->descent + h, 0);
463
464       glVertex3f (x,     y - sc->descent + h, 0);       /* ascent */
465       glVertex3f (x + w, y - sc->descent + h, 0);
466
467       glVertex3f (x - sc->char_width,     y, 0);        /* baseline */
468       glVertex3f (x + sc->char_width + w, y, 0);
469
470       glVertex3f (x,     y - sc->descent, 0);           /* descent */
471       glVertex3f (x + w, y - sc->descent, 0);
472
473       glEnd();
474
475       free (s2);
476       free (chars);
477
478       if (textures_p) glEnable (GL_TEXTURE_2D);
479     }
480 }
481
482
483 static void
484 grid (double width, double height, double xspacing, double yspacing, double z)
485 {
486   double x, y;
487   for (y = 0; y <= height/2; y += yspacing)
488     {
489       glBegin(GL_LINES);
490       glVertex3f(-width/2,  y, z);
491       glVertex3f( width/2,  y, z);
492       glVertex3f(-width/2, -y, z);
493       glVertex3f( width/2, -y, z);
494       glEnd();
495     }
496   for (x = 0; x <= width/2; x += xspacing)
497     {
498       glBegin(GL_LINES);
499       glVertex3f( x, -height/2, z);
500       glVertex3f( x,  height/2, z);
501       glVertex3f(-x, -height/2, z);
502       glVertex3f(-x,  height/2, z);
503       glEnd();
504     }
505
506   glBegin(GL_LINES);
507   glVertex3f(-width, 0, z);
508   glVertex3f( width, 0, z);
509   glVertex3f(0, -height, z);
510   glVertex3f(0,  height, z);
511   glEnd();
512 }
513
514 static void
515 box (double width, double height, double depth)
516 {
517   glBegin(GL_LINE_LOOP);
518   glVertex3f(-width/2,  -height/2, -depth/2);
519   glVertex3f(-width/2,   height/2, -depth/2);
520   glVertex3f( width/2,   height/2, -depth/2);
521   glVertex3f( width/2,  -height/2, -depth/2);
522   glEnd();
523   glBegin(GL_LINE_LOOP);
524   glVertex3f(-width/2,  -height/2,  depth/2);
525   glVertex3f(-width/2,   height/2,  depth/2);
526   glVertex3f( width/2,   height/2,  depth/2);
527   glVertex3f( width/2,  -height/2,  depth/2);
528   glEnd();
529   glBegin(GL_LINE_LOOP);
530   glVertex3f(-width/2,  -height/2, -depth/2);
531   glVertex3f(-width/2,  -height/2,  depth/2);
532   glVertex3f(-width/2,   height/2,  depth/2);
533   glVertex3f(-width/2,   height/2, -depth/2);
534   glEnd();
535   glBegin(GL_LINE_LOOP);
536   glVertex3f( width/2,  -height/2, -depth/2);
537   glVertex3f( width/2,  -height/2,  depth/2);
538   glVertex3f( width/2,   height/2,  depth/2);
539   glVertex3f( width/2,   height/2, -depth/2);
540   glEnd();
541
542   glBegin(GL_LINES);
543   glVertex3f(-width/2,   height/2,  depth/2);
544   glVertex3f(-width/2,  -height/2, -depth/2);
545
546   glVertex3f( width/2,   height/2,  depth/2);
547   glVertex3f( width/2,  -height/2, -depth/2);
548
549   glVertex3f(-width/2,  -height/2,  depth/2);
550   glVertex3f(-width/2,   height/2, -depth/2);
551
552   glVertex3f( width/2,  -height/2,  depth/2);
553   glVertex3f( width/2,   height/2, -depth/2);
554   glEnd();
555 }
556
557
558 /* Construct stars (number of stars is dependent on size of screen) */
559 static void
560 init_stars (ModeInfo *mi, int width, int height)
561 {
562   sws_configuration *sc = &scs[MI_SCREEN(mi)];
563   int i, j;
564   int size = (width > height ? width : height);
565   int nstars = size * size / 320;
566   int max_size = 3;
567   GLfloat inc = 0.5;
568   int steps = max_size / inc;
569   GLfloat scale = 1;
570
571   if (MI_WIDTH(mi) > 2560) {  /* Retina displays */
572     scale *= 2;
573     nstars = (size/scale) * (size/scale) / 320;
574   }
575
576   glDeleteLists (sc->star_list, 1);
577   sc->star_list = glGenLists (1);
578   glNewList (sc->star_list, GL_COMPILE);
579
580   glEnable(GL_POINT_SMOOTH);
581
582   for (j = 1; j <= steps; j++)
583     {
584       glPointSize(inc * j * scale);
585       glBegin (GL_POINTS);
586       for (i = 0; i < nstars / steps; i++)
587         {
588           GLfloat brightness = 0.9 - star_saturation;
589           glColor3f (brightness + frand(star_saturation),
590                      brightness + frand(star_saturation),
591                      brightness + frand(star_saturation));
592           glVertex2f (2 * size * (0.5 - frand(1.0)),
593                       2 * size * (0.5 - frand(1.0)));
594         }
595       glEnd ();
596     }
597   glEndList ();
598 }
599
600
601 /* Window management, etc
602  */
603 ENTRYPOINT void
604 reshape_sws (ModeInfo *mi, int width, int height)
605 {
606   sws_configuration *sc = &scs[MI_SCREEN(mi)];
607
608   /* Set up matrices for perspective text display
609    */
610   {
611     GLfloat desired_aspect = (GLfloat) 3/4;
612     int w = mi->xgwa.width;
613     int h = mi->xgwa.height;
614     int yoff = 0;
615     GLfloat rot = current_device_rotation();
616
617 #ifdef KEEP_ASPECT
618     {
619       int h2 = w * desired_aspect;
620       yoff = (h - h2) / 2;      /* Wide window: letterbox at top and bottom. */
621       if (yoff < 0) yoff = 0;   /* Tall window: clip off the top. */
622       h = h2;
623     }
624 #endif
625
626     glViewport (0, yoff, w, h);
627
628     glMatrixMode (GL_PROJECTION);
629     glLoadIdentity();
630
631     glMatrixMode (GL_MODELVIEW);
632     glLoadIdentity ();
633     gluPerspective (80.0, 1/desired_aspect, 1000, 55000);
634     gluLookAt (0.0, 0.0, 4600.0,
635                0.0, 0.0, 0.0,
636                0.0, 1.0, 0.0);
637
638     /* Horrible kludge to prevent the text from materializing already
639        on screen on iPhone in landscape mode.
640      */
641     if ((rot >  45 && rot <  135) ||
642         (rot < -45 && rot > -135))
643       {
644         GLfloat s = 1.1;
645         glScalef (s, s, s);
646       }
647
648     glRotatef (-60.0, 1.0, 0.0, 0.0);
649
650 #if 0
651     glRotatef (60.0, 1.0, 0.0, 0.0);
652     glTranslatef (260, 3200, 0);
653     glScalef (1.85, 1.85, 1);
654 #endif
655
656     /* The above gives us an arena where the bottom edge of the screen is
657        represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
658
659     /* Now let's move the origin to the front of the screen. */
660     glTranslatef (0.0, -3140, 0.0);
661
662     /* And then let's scale so that the bottom of the screen is 1.0 wide. */
663     glScalef (4200, 4200, 4200);
664   }
665
666
667   /* Compute the height in pixels of the line at the bottom of the screen. */
668   {
669     GLdouble mm[17], pm[17];
670     GLint vp[5];
671     GLdouble x = 0.5, y1 = 0, z = 0;
672     GLdouble y2 = sc->line_height;
673     GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
674
675     glGetDoublev (GL_MODELVIEW_MATRIX, mm);
676     glGetDoublev (GL_PROJECTION_MATRIX, pm);
677     glGetIntegerv (GL_VIEWPORT, vp);
678     gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
679     gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
680     sc->line_pixel_height = (wy2 - wy1);
681     glLineWidth (1);
682   }
683
684   /* Compute the best looking line thickness for the bottom line.
685    */
686   if (!thick_p)
687     sc->line_thickness = 1.0;
688   else
689     sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
690
691   if (sc->line_thickness < 1.2)
692     sc->line_thickness = 1.0;
693 }
694
695
696 static void
697 gl_init (ModeInfo *mi)
698 {
699   sws_configuration *sc = &scs[MI_SCREEN(mi)];
700
701   glDisable (GL_LIGHTING);
702   glDisable (GL_DEPTH_TEST);
703
704   if (smooth_p)
705     {
706       glEnable (GL_LINE_SMOOTH);
707       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
708       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
709       glEnable (GL_BLEND);
710     }
711
712   sc->text_list = glGenLists (1);
713   glNewList (sc->text_list, GL_COMPILE);
714   glEndList ();
715
716   sc->star_list = glGenLists (1);
717   glNewList (sc->star_list, GL_COMPILE);
718   glEndList ();
719
720   sc->line_thickness = 1.0;
721 }
722
723
724 ENTRYPOINT void 
725 init_sws (ModeInfo *mi)
726 {
727   double font_height;
728
729   sws_configuration *sc = 0;
730
731   MI_INIT (mi, scs);
732
733   sc = &scs[MI_SCREEN(mi)];
734
735   sc->dpy = MI_DISPLAY(mi);
736   sc = &scs[MI_SCREEN(mi)];
737   /* Unchecked malloc. :( */
738   sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
739   sc->line_widths = (int *) calloc (max_lines+1, sizeof(int));
740
741   if ((sc->glx_context = init_GL(mi)) != NULL) {
742     gl_init(mi);
743     reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
744     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
745
746     init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
747   }
748
749   if (textures_p)
750     {
751       XCharStruct e;
752       int cw, ascent, descent;
753       sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
754       texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
755       cw = e.width;
756       sc->char_width = cw;
757       font_height = ascent + descent;
758       sc->descent = descent;
759       glEnable(GL_ALPHA_TEST);
760       glEnable (GL_TEXTURE_2D);
761
762       check_gl_error ("loading font");
763
764       /* "Anistropic filtering helps for quadrilateral-angled textures.
765          A sharper image is accomplished by interpolating and filtering
766          multiple samples from one or more mipmaps to better approximate
767          very distorted textures.  This is the next level of filtering
768          after trilinear filtering." */
769       if (smooth_p && 
770           strstr ((char *) glGetString(GL_EXTENSIONS),
771                   "GL_EXT_texture_filter_anisotropic"))
772       {
773         GLfloat anisotropic = 0.0;
774         glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
775         if (anisotropic >= 1.0)
776           glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
777                            anisotropic);
778       }
779     }
780   else
781     {
782       font_height = GLUT_FONT->top - GLUT_FONT->bottom;
783       sc->char_width = glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems wide */
784       sc->descent = 0;
785     }
786   
787   sc->font_scale = 1.0 / sc->char_width;
788
789
790   /* We consider a font that consumes 80 columns to be "18 points".
791
792      If neither -size nor -columns was specified, default to 60 columns
793      (which is 24 points.)
794
795      If both were specified, -columns has priority.
796    */
797   {
798     int base_col  = 80;
799     int base_size = 18;
800
801     if (target_columns <= 0 && font_size <= 0)
802       target_columns = 60;
803
804     if (target_columns > 0)
805       font_size = base_size * (base_col / (double) target_columns);
806     else if (font_size > 0)
807       target_columns = base_col * (base_size / (double) font_size);
808   }
809
810   sc->line_pixel_width = target_columns * sc->char_width;
811
812   sc->font_scale /= target_columns;
813   sc->line_height = font_height * sc->font_scale;
814
815   /* Buffer only a few lines of text.
816      If the buffer is too big, there's a significant delay between
817      when the program launches and when the text appears, which can be
818      irritating for time-sensitive output (clock, current music, etc.)
819
820      I'd like to buffer only 2 lines, but we need to assume that we
821      could get a whole line of N-byte Unicrud, and if we fill the buffer
822      before hitting the end of the line, we stall.
823    */
824   sc->buf_size = target_columns * 2 * 4;
825   if (sc->buf_size < 80) sc->buf_size = 80;
826   sc->buf = (char *) calloc (1, sc->buf_size);
827
828   sc->total_lines = max_lines-1;
829
830   if (random() & 1)
831     star_spin = -star_spin;
832
833   if (!alignment_str || !*alignment_str ||
834       !strcasecmp(alignment_str, "left"))
835     alignment = -1;
836   else if (!strcasecmp(alignment_str, "center") ||
837            !strcasecmp(alignment_str, "middle"))
838     alignment = 0;
839   else if (!strcasecmp(alignment_str, "right"))
840     alignment = 1;
841   else
842     {
843       fprintf (stderr,
844                "%s: alignment must be left, center, or right, not \"%s\"\n",
845                progname, alignment_str);
846       exit (1);
847     }
848
849   sc->tc = textclient_open (sc->dpy);
850
851   /* one more reshape, after line_height has been computed */
852   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
853 }
854
855
856 static void
857 draw_stars (ModeInfo *mi)
858 {
859   sws_configuration *sc = &scs[MI_SCREEN(mi)];
860
861   glMatrixMode (GL_PROJECTION);
862   glPushMatrix ();
863   {
864     glLoadIdentity ();
865
866     glMatrixMode (GL_MODELVIEW);
867     glPushMatrix ();
868     {
869       glLoadIdentity ();
870       glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
871                -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
872                -100.0, 100.0);
873       glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
874       if (textures_p) glDisable (GL_TEXTURE_2D);
875
876       glCallList (sc->star_list);
877       if (textures_p) glEnable (GL_TEXTURE_2D);
878     }
879     glPopMatrix ();
880   }
881   glMatrixMode (GL_PROJECTION);
882   glPopMatrix ();
883 }
884
885 ENTRYPOINT void
886 draw_sws (ModeInfo *mi)
887 {
888   sws_configuration *sc = &scs[MI_SCREEN(mi)];
889 /*  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
890   Display *dpy = MI_DISPLAY(mi);
891   Window window = MI_WINDOW(mi);
892   int i;
893
894   if (!sc->glx_context)
895     return;
896
897   glDrawBuffer (GL_BACK);
898   glXMakeCurrent (dpy, window, *sc->glx_context);
899
900   glClear (GL_COLOR_BUFFER_BIT);
901
902   draw_stars (mi);
903
904   glMatrixMode (GL_MODELVIEW);
905   glPushMatrix ();
906
907 # ifdef HAVE_MOBILE
908   /* Need to do this every time to get device rotation right */
909   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
910 # endif
911
912   if (debug_p)
913     {
914       int i;
915       glPushMatrix ();
916       if (textures_p) glDisable (GL_TEXTURE_2D);
917       glLineWidth (1);
918       glTranslatef (0,-1, 0);
919
920       glColor3f(1, 0, 0);       /* Red line is where text appears */
921       glPushMatrix();
922       glTranslatef(0, -0.028, 0);
923       glLineWidth (4);
924       glBegin(GL_LINES);
925       glVertex3f(-0.5,  1, 0);
926       glVertex3f( 0.5,  1, 0);
927       glVertex3f(-0.5, -1, 0);
928       glVertex3f( 0.5, -1, 0);
929       glEnd();
930       glLineWidth (1);
931       glPopMatrix();
932
933       glColor3f (0.2, 0.2, 0.2);
934       for (i = 0; i < 16; i++)
935         {
936           box (1, 1, 1);
937           grid (1, 1, sc->char_width * sc->font_scale, sc->line_height, 0);
938           glTranslatef(0, 1, 0);
939         }
940       if (textures_p) glEnable (GL_TEXTURE_2D);
941       glPopMatrix ();
942       check_gl_error ("debug render");
943     }
944
945   /* Scroll to current position */
946   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
947
948   glColor3f (1.0, 1.0, 0.4);
949
950   mi->polygon_count = 0;
951
952   glPushMatrix ();
953   glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
954   for (i = 0; i < sc->total_lines; i++)
955     {
956       double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
957       int offscreen_lines = 2;
958
959       double x = -0.5;
960       double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
961                    * sc->line_height);
962       double xoff = 0;
963       char *line = sc->lines[i];
964
965       if (debug_p)
966         {
967           double xx = x * 1.4;  /* a little more to the left */
968           char n[20];
969           sprintf(n, "%d:", i);
970           glColor3f (1.0, 1.0, 1.0);
971           draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
972         }
973
974       if (!line || !*line)
975         continue;
976
977       if (sc->line_thickness != 1 && !textures_p)
978         {
979           int max_thick_lines = MAX_THICK_LINES;
980           GLfloat thinnest_line = 1.0;
981           GLfloat thickest_line = sc->line_thickness;
982           GLfloat range = thickest_line - thinnest_line;
983           GLfloat thickness;
984
985           int j = sc->total_lines - i - 1;
986
987           if (j > max_thick_lines)
988             thickness = thinnest_line;
989           else
990             thickness = (thinnest_line +
991                          (range * ((max_thick_lines - j) /
992                                    (GLfloat) max_thick_lines)));
993
994           glLineWidth (thickness);
995         }
996
997       if (alignment >= 0)
998         {
999           int n = sc->line_widths[i];
1000           xoff = 1.0 - (n * sc->font_scale);
1001         }
1002
1003       if (alignment == 0)
1004         xoff /= 2;
1005
1006       glColor3f (fade, fade, 0.5 * fade);
1007       draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
1008                    line);
1009       if (textures_p)
1010         mi->polygon_count += strlen (line);
1011     }
1012   glPopMatrix ();
1013
1014
1015
1016   sc->intra_line_scroll += sc->line_height / scroll_steps;
1017
1018   if (sc->intra_line_scroll >= sc->line_height)
1019     {
1020       sc->intra_line_scroll = 0;
1021
1022       /* Drop the oldest line off the end. */
1023       if (sc->lines[0])
1024         free (sc->lines[0]);
1025
1026       /* Scroll the contents of the lines array toward 0. */
1027       if (sc->total_lines > 0)
1028         {
1029           for (i = 1; i < sc->total_lines; i++) {
1030             sc->lines[i-1] = sc->lines[i];
1031             sc->line_widths[i-1] = sc->line_widths[i];
1032           }
1033           sc->lines[--sc->total_lines] = 0;
1034         }
1035
1036       /* Bring in new lines at the end. */
1037       get_more_lines (sc);
1038
1039       if (sc->total_lines < max_lines)
1040         /* Oops, we ran out of text... well, insert some blank lines
1041            here so that new text still pulls in from the bottom of
1042            the screen, isntead of just appearing. */
1043         sc->total_lines = max_lines;
1044     }
1045
1046   glPopMatrix ();
1047
1048   if (mi->fps_p) do_fps (mi);
1049   glFinish();
1050   glXSwapBuffers(dpy, window);
1051
1052   sc->star_theta += star_spin;
1053 }
1054
1055 ENTRYPOINT void
1056 free_sws (ModeInfo *mi)
1057 {
1058   sws_configuration *sc = &scs[MI_SCREEN(mi)];
1059   int i;
1060   if (!sc->glx_context) return;
1061   glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *sc->glx_context);
1062   if (sc->tc) textclient_close (sc->tc);
1063   if (sc->texfont) free_texture_font (sc->texfont);
1064   if (sc->buf) free (sc->buf);
1065   if (sc->line_widths) free (sc->line_widths);
1066   for (i = 0; i < sc->total_lines; i++)
1067     if (sc->lines[i]) free (sc->lines[i]);
1068   if (sc->lines) free (sc->lines);
1069   if (glIsList(sc->star_list)) glDeleteLists(sc->star_list, 1);
1070   if (glIsList(sc->text_list)) glDeleteLists(sc->text_list, 1);
1071 }
1072
1073
1074 #ifdef __GNUC__
1075  __extension__ /* don't warn about "string length is greater than the length
1076                   ISO C89 compilers are required to support" when including
1077                   "starwars.txt" in the defaults... */
1078 #endif
1079
1080 XSCREENSAVER_MODULE_2 ("StarWars", starwars, sws)
1081
1082 #endif /* USE_GL */