From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / glx / starwars.c
1 /* starwars, Copyright (c) 1998-2015 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
572   glDeleteLists (sc->star_list, 1);
573   sc->star_list = glGenLists (1);
574   glNewList (sc->star_list, GL_COMPILE);
575
576   glEnable(GL_POINT_SMOOTH);
577
578   for (j = 1; j <= steps; j++)
579     {
580       glPointSize(inc * j);
581       glBegin (GL_POINTS);
582       for (i = 0; i < nstars / steps; i++)
583         {
584           glColor3f (0.6 + frand(0.3),
585                      0.6 + frand(0.3),
586                      0.6 + frand(0.3));
587           glVertex2f (2 * size * (0.5 - frand(1.0)),
588                       2 * size * (0.5 - frand(1.0)));
589         }
590       glEnd ();
591     }
592   glEndList ();
593 }
594
595
596 /* Window management, etc
597  */
598 ENTRYPOINT void
599 reshape_sws (ModeInfo *mi, int width, int height)
600 {
601   sws_configuration *sc = &scs[MI_SCREEN(mi)];
602
603   /* Set up matrices for perspective text display
604    */
605   {
606     GLfloat desired_aspect = (GLfloat) 3/4;
607     int w = mi->xgwa.width;
608     int h = mi->xgwa.height;
609     int yoff = 0;
610     GLfloat rot = current_device_rotation();
611
612 #ifdef KEEP_ASPECT
613     {
614       int h2 = w * desired_aspect;
615       yoff = (h - h2) / 2;      /* Wide window: letterbox at top and bottom. */
616       if (yoff < 0) yoff = 0;   /* Tall window: clip off the top. */
617       h = h2;
618     }
619 #endif
620
621     glViewport (0, yoff, w, h);
622
623     glMatrixMode (GL_PROJECTION);
624     glLoadIdentity();
625
626     glMatrixMode (GL_MODELVIEW);
627     glLoadIdentity ();
628     gluPerspective (80.0, 1/desired_aspect, 1000, 55000);
629     gluLookAt (0.0, 0.0, 4600.0,
630                0.0, 0.0, 0.0,
631                0.0, 1.0, 0.0);
632
633     /* Horrible kludge to prevent the text from materializing already
634        on screen on iPhone in landscape mode.
635      */
636     if ((rot >  45 && rot <  135) ||
637         (rot < -45 && rot > -135))
638       {
639         GLfloat s = 1.1;
640         glScalef (s, s, s);
641       }
642
643     glRotatef (-60.0, 1.0, 0.0, 0.0);
644
645 #if 0
646     glRotatef (60.0, 1.0, 0.0, 0.0);
647     glTranslatef (260, 3200, 0);
648     glScalef (1.85, 1.85, 1);
649 #endif
650
651     /* The above gives us an arena where the bottom edge of the screen is
652        represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
653
654     /* Now let's move the origin to the front of the screen. */
655     glTranslatef (0.0, -3140, 0.0);
656
657     /* And then let's scale so that the bottom of the screen is 1.0 wide. */
658     glScalef (4200, 4200, 4200);
659   }
660
661
662   /* Compute the height in pixels of the line at the bottom of the screen. */
663   {
664     GLdouble mm[17], pm[17];
665     GLint vp[5];
666     GLdouble x = 0.5, y1 = 0, z = 0;
667     GLdouble y2 = sc->line_height;
668     GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
669
670     glGetDoublev (GL_MODELVIEW_MATRIX, mm);
671     glGetDoublev (GL_PROJECTION_MATRIX, pm);
672     glGetIntegerv (GL_VIEWPORT, vp);
673     gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
674     gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
675     sc->line_pixel_height = (wy2 - wy1);
676     glLineWidth (1);
677   }
678
679   /* Compute the best looking line thickness for the bottom line.
680    */
681   if (!thick_p)
682     sc->line_thickness = 1.0;
683   else
684     sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
685
686   if (sc->line_thickness < 1.2)
687     sc->line_thickness = 1.0;
688 }
689
690
691 static void
692 gl_init (ModeInfo *mi)
693 {
694   sws_configuration *sc = &scs[MI_SCREEN(mi)];
695
696   glDisable (GL_LIGHTING);
697   glDisable (GL_DEPTH_TEST);
698
699   if (smooth_p)
700     {
701       glEnable (GL_LINE_SMOOTH);
702       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
703       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
704       glEnable (GL_BLEND);
705     }
706
707   sc->text_list = glGenLists (1);
708   glNewList (sc->text_list, GL_COMPILE);
709   glEndList ();
710
711   sc->star_list = glGenLists (1);
712   glNewList (sc->star_list, GL_COMPILE);
713   glEndList ();
714
715   sc->line_thickness = 1.0;
716 }
717
718
719 ENTRYPOINT void 
720 init_sws (ModeInfo *mi)
721 {
722   double font_height;
723
724   sws_configuration *sc = 0;
725
726   MI_INIT (mi, scs);
727
728   sc = &scs[MI_SCREEN(mi)];
729
730   sc->dpy = MI_DISPLAY(mi);
731   sc = &scs[MI_SCREEN(mi)];
732   /* Unchecked malloc. :( */
733   sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
734   sc->line_widths = (int *) calloc (max_lines+1, sizeof(int));
735
736   if ((sc->glx_context = init_GL(mi)) != NULL) {
737     gl_init(mi);
738     reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
739     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
740
741     init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
742   }
743
744   if (textures_p)
745     {
746       XCharStruct e;
747       int cw, ascent, descent;
748       sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
749       texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
750       cw = e.width;
751       sc->char_width = cw;
752       font_height = ascent + descent;
753       sc->descent = descent;
754       glEnable(GL_ALPHA_TEST);
755       glEnable (GL_TEXTURE_2D);
756
757       check_gl_error ("loading font");
758
759       /* "Anistropic filtering helps for quadrilateral-angled textures.
760          A sharper image is accomplished by interpolating and filtering
761          multiple samples from one or more mipmaps to better approximate
762          very distorted textures.  This is the next level of filtering
763          after trilinear filtering." */
764       if (smooth_p && 
765           strstr ((char *) glGetString(GL_EXTENSIONS),
766                   "GL_EXT_texture_filter_anisotropic"))
767       {
768         GLfloat anisotropic = 0.0;
769         glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
770         if (anisotropic >= 1.0)
771           glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
772                            anisotropic);
773       }
774     }
775   else
776     {
777       font_height = GLUT_FONT->top - GLUT_FONT->bottom;
778       sc->char_width = glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems wide */
779       sc->descent = 0;
780     }
781   
782   sc->font_scale = 1.0 / sc->char_width;
783
784
785   /* We consider a font that consumes 80 columns to be "18 points".
786
787      If neither -size nor -columns was specified, default to 60 columns
788      (which is 24 points.)
789
790      If both were specified, -columns has priority.
791    */
792   {
793     int base_col  = 80;
794     int base_size = 18;
795
796     if (target_columns <= 0 && font_size <= 0)
797       target_columns = 60;
798
799     if (target_columns > 0)
800       font_size = base_size * (base_col / (double) target_columns);
801     else if (font_size > 0)
802       target_columns = base_col * (base_size / (double) font_size);
803   }
804
805   sc->line_pixel_width = target_columns * sc->char_width;
806
807   sc->font_scale /= target_columns;
808   sc->line_height = font_height * sc->font_scale;
809
810   /* Buffer only a few lines of text.
811      If the buffer is too big, there's a significant delay between
812      when the program launches and when the text appears, which can be
813      irritating for time-sensitive output (clock, current music, etc.)
814
815      I'd like to buffer only 2 lines, but we need to assume that we
816      could get a whole line of N-byte Unicrud, and if we fill the buffer
817      before hitting the end of the line, we stall.
818    */
819   sc->buf_size = target_columns * 2 * 4;
820   if (sc->buf_size < 80) sc->buf_size = 80;
821   sc->buf = (char *) calloc (1, sc->buf_size);
822
823   sc->total_lines = max_lines-1;
824
825   if (random() & 1)
826     star_spin = -star_spin;
827
828   if (!alignment_str || !*alignment_str ||
829       !strcasecmp(alignment_str, "left"))
830     alignment = -1;
831   else if (!strcasecmp(alignment_str, "center") ||
832            !strcasecmp(alignment_str, "middle"))
833     alignment = 0;
834   else if (!strcasecmp(alignment_str, "right"))
835     alignment = 1;
836   else
837     {
838       fprintf (stderr,
839                "%s: alignment must be left, center, or right, not \"%s\"\n",
840                progname, alignment_str);
841       exit (1);
842     }
843
844   sc->tc = textclient_open (sc->dpy);
845
846   /* one more reshape, after line_height has been computed */
847   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
848 }
849
850
851 static void
852 draw_stars (ModeInfo *mi)
853 {
854   sws_configuration *sc = &scs[MI_SCREEN(mi)];
855
856   glMatrixMode (GL_PROJECTION);
857   glPushMatrix ();
858   {
859     glLoadIdentity ();
860
861     glMatrixMode (GL_MODELVIEW);
862     glPushMatrix ();
863     {
864       glLoadIdentity ();
865       glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
866                -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
867                -100.0, 100.0);
868       glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
869       if (textures_p) glDisable (GL_TEXTURE_2D);
870
871       glCallList (sc->star_list);
872       if (textures_p) glEnable (GL_TEXTURE_2D);
873     }
874     glPopMatrix ();
875   }
876   glMatrixMode (GL_PROJECTION);
877   glPopMatrix ();
878 }
879
880 ENTRYPOINT void
881 draw_sws (ModeInfo *mi)
882 {
883   sws_configuration *sc = &scs[MI_SCREEN(mi)];
884 /*  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
885   Display *dpy = MI_DISPLAY(mi);
886   Window window = MI_WINDOW(mi);
887   int i;
888
889   if (!sc->glx_context)
890     return;
891
892   glDrawBuffer (GL_BACK);
893   glXMakeCurrent (dpy, window, *(sc->glx_context));
894
895   glClear (GL_COLOR_BUFFER_BIT);
896
897   draw_stars (mi);
898
899   glMatrixMode (GL_MODELVIEW);
900   glPushMatrix ();
901
902 # ifdef HAVE_MOBILE
903   /* Need to do this every time to get device rotation right */
904   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
905 # endif
906
907   if (debug_p)
908     {
909       int i;
910       glPushMatrix ();
911       if (textures_p) glDisable (GL_TEXTURE_2D);
912       glLineWidth (1);
913       glTranslatef (0,-1, 0);
914
915       glColor3f(1, 0, 0);       /* Red line is where text appears */
916       glPushMatrix();
917       glTranslatef(0, -0.028, 0);
918       glLineWidth (4);
919       glBegin(GL_LINES);
920       glVertex3f(-0.5,  1, 0);
921       glVertex3f( 0.5,  1, 0);
922       glVertex3f(-0.5, -1, 0);
923       glVertex3f( 0.5, -1, 0);
924       glEnd();
925       glLineWidth (1);
926       glPopMatrix();
927
928       glColor3f (0.2, 0.2, 0.2);
929       for (i = 0; i < 16; i++)
930         {
931           box (1, 1, 1);
932           grid (1, 1, sc->char_width * sc->font_scale, sc->line_height, 0);
933           glTranslatef(0, 1, 0);
934         }
935       if (textures_p) glEnable (GL_TEXTURE_2D);
936       glPopMatrix ();
937       check_gl_error ("debug render");
938     }
939
940   /* Scroll to current position */
941   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
942
943   glColor3f (1.0, 1.0, 0.4);
944
945   mi->polygon_count = 0;
946
947   glPushMatrix ();
948   glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
949   for (i = 0; i < sc->total_lines; i++)
950     {
951       double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
952       int offscreen_lines = 2;
953
954       double x = -0.5;
955       double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
956                    * sc->line_height);
957       double xoff = 0;
958       char *line = sc->lines[i];
959
960       if (debug_p)
961         {
962           double xx = x * 1.4;  /* a little more to the left */
963           char n[20];
964           sprintf(n, "%d:", i);
965           glColor3f (1.0, 1.0, 1.0);
966           draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
967         }
968
969       if (!line || !*line)
970         continue;
971
972       if (sc->line_thickness != 1 && !textures_p)
973         {
974           int max_thick_lines = MAX_THICK_LINES;
975           GLfloat thinnest_line = 1.0;
976           GLfloat thickest_line = sc->line_thickness;
977           GLfloat range = thickest_line - thinnest_line;
978           GLfloat thickness;
979
980           int j = sc->total_lines - i - 1;
981
982           if (j > max_thick_lines)
983             thickness = thinnest_line;
984           else
985             thickness = (thinnest_line +
986                          (range * ((max_thick_lines - j) /
987                                    (GLfloat) max_thick_lines)));
988
989           glLineWidth (thickness);
990         }
991
992       if (alignment >= 0)
993         {
994           int n = sc->line_widths[i];
995           xoff = 1.0 - (n * sc->font_scale);
996         }
997
998       if (alignment == 0)
999         xoff /= 2;
1000
1001       glColor3f (fade, fade, 0.5 * fade);
1002       draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
1003                    line);
1004       if (textures_p)
1005         mi->polygon_count += strlen (line);
1006     }
1007   glPopMatrix ();
1008
1009
1010
1011   sc->intra_line_scroll += sc->line_height / scroll_steps;
1012
1013   if (sc->intra_line_scroll >= sc->line_height)
1014     {
1015       sc->intra_line_scroll = 0;
1016
1017       /* Drop the oldest line off the end. */
1018       if (sc->lines[0])
1019         free (sc->lines[0]);
1020
1021       /* Scroll the contents of the lines array toward 0. */
1022       if (sc->total_lines > 0)
1023         {
1024           for (i = 1; i < sc->total_lines; i++) {
1025             sc->lines[i-1] = sc->lines[i];
1026             sc->line_widths[i-1] = sc->line_widths[i];
1027           }
1028           sc->lines[--sc->total_lines] = 0;
1029         }
1030
1031       /* Bring in new lines at the end. */
1032       get_more_lines (sc);
1033
1034       if (sc->total_lines < max_lines)
1035         /* Oops, we ran out of text... well, insert some blank lines
1036            here so that new text still pulls in from the bottom of
1037            the screen, isntead of just appearing. */
1038         sc->total_lines = max_lines;
1039     }
1040
1041   glPopMatrix ();
1042
1043   if (mi->fps_p) do_fps (mi);
1044   glFinish();
1045   glXSwapBuffers(dpy, window);
1046
1047   sc->star_theta += star_spin;
1048 }
1049
1050 ENTRYPOINT void
1051 free_sws (ModeInfo *mi)
1052 {
1053   sws_configuration *sc = &scs[MI_SCREEN(mi)];
1054   if (sc->tc)
1055     textclient_close (sc->tc);
1056
1057   /* #### there's more to free here */
1058 }
1059
1060
1061 #ifdef __GNUC__
1062  __extension__ /* don't warn about "string length is greater than the length
1063                   ISO C89 compilers are required to support" when including
1064                   "starwars.txt" in the defaults... */
1065 #endif
1066
1067 XSCREENSAVER_MODULE_2 ("StarWars", starwars, sws)
1068
1069 #endif /* USE_GL */