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