From http://www.jwz.org/xscreensaver/xscreensaver-5.33.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 refresh_sws 0
50 # define sws_handle_event 0
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 USE_IPHONE
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   int col_pix = 0;
287
288   char *s = sc->buf;
289
290   int target = sc->buf_size - sc->buf_tail - 2;
291
292   /* Fill as much as we can into sc->buf.
293    */
294   while (target > 0)
295     {
296       int c = textclient_getc (sc->tc);
297       if (c <= 0)
298         break;
299       sc->buf[sc->buf_tail++] = (char) c;
300       sc->buf[sc->buf_tail] = 0;
301       target--;
302     }
303
304   while (sc->total_lines < max_lines)
305     {
306       char *next_s = s;
307       unsigned counter = 0;
308
309       /* OS X is really slow to calcuate the bounds for a line of text,
310          so skip ahead a bit.
311
312          Really though, the right thing to do is probably to wrap
313          CTLineCreateTruncatedLine, one way or another. */
314       for (;;)
315         {
316           if (next_s >= sc->buf + sc->buf_tail)
317             break;
318
319           if (!counter)
320             {
321               if (s > sc->buf &&
322                   sw_string_width2 (sc, sc->buf, next_s - sc->buf) >= wrap_pix)
323                 break;
324
325               counter = 12; /* <-- Adjust to taste. */
326               s = next_s;
327             }
328
329           if (*next_s == '\r' || *next_s == '\n')
330             break;
331
332           --counter;
333           ++next_s;
334         }
335
336       for (;;)
337         {
338           if (s >= sc->buf + sc->buf_tail)
339             /* Reached end of buffer before end of line.  Bail. */
340             return;
341
342           /* When checking pixel width, always measure the line from the
343              beginning, or else multi-byte UTF-8 characters, particularly
344              combining diacriticals, aren't measured right. */
345
346           if (*s == '\r' || *s == '\n' ||
347               (s > sc->buf && sw_string_width2 (sc, sc->buf, s - sc->buf) >= wrap_pix))
348             {
349               int L = s - sc->buf;
350
351               if (*s == '\r' || *s == '\n')
352                 {
353                   if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
354                     *s++ = 0;
355
356                   *s++ = 0;
357                 }
358               else
359                 {
360                   /* We wrapped -- try to back up to the previous word boundary. */
361                   char *s2 = s;
362                   int n = 0;
363                   while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
364                     s2--, n++;
365                   if (s2 > sc->buf)
366                     {
367                       s = s2;
368                       *s++ = 0;
369                       L = s - sc->buf;
370                     }
371                 }
372
373               sc->lines[sc->total_lines] = (char *) malloc (L+1);
374               memcpy (sc->lines[sc->total_lines], sc->buf, L);
375               sc->lines[sc->total_lines][L] = 0;
376
377               if (!textures_p)
378                 {
379                   /* The GLUT font only has ASCII characters. */
380                   char *s1 = utf8_to_latin1 (sc->lines[sc->total_lines], True);
381                   free (sc->lines[sc->total_lines]);
382                   sc->lines[sc->total_lines] = s1;
383                 }
384
385               {
386                 char *t = sc->lines[sc->total_lines];
387                 char *ut = untabify (t);
388                 strip (ut, (alignment == 0), 1); /* if centering, strip
389                                                     leading whitespace too */
390                 sc->lines[sc->total_lines] = ut;
391                 free (t);
392               }
393
394               sc->line_widths[sc->total_lines] =
395                 sw_string_width(sc, sc->lines[sc->total_lines]);
396
397               sc->total_lines++;
398
399               if (sc->buf_tail > (s - sc->buf))
400                 {
401                   int i = sc->buf_tail - (s - sc->buf);
402                   memmove (sc->buf, s, i);
403                   sc->buf_tail = i;
404                   sc->buf[sc->buf_tail] = 0;
405                 }
406               else
407                 {
408                   sc->buf_tail = 0;
409                 }
410
411               sc->buf[sc->buf_tail] = 0;
412               s = sc->buf;
413               col_pix = 0;
414
415               break;
416             }
417           else
418             {
419               s++;
420             }
421         }
422     }
423 }
424
425
426 static void
427 draw_string (sws_configuration *sc, GLfloat x, GLfloat y, const char *s)
428 {
429   const char *os = s;
430   if (!s || !*s) return;
431   glPushMatrix ();
432   glTranslatef (x, y, 0);
433
434   if (textures_p)
435     print_texture_string (sc->texfont, s);
436   else
437     while (*s)
438       glutStrokeCharacter (GLUT_FONT, *s++);
439   glPopMatrix ();
440
441   if (debug_p)
442     {
443       GLfloat h = sc->line_height / sc->font_scale;
444       char **chars = utf8_split (os, 0);
445       int i, w = 0;
446       char *s2 = (char *) malloc (strlen(s) + 1);
447       *s2 = 0;
448
449       if (textures_p) glDisable (GL_TEXTURE_2D);
450       glLineWidth (1);
451
452       glColor3f (0.2, 0.2, 0.5);
453
454       glBegin (GL_LINES);
455
456       for (i = 0; chars[i]; i++)
457         {
458           glVertex3f (x + w, y - sc->descent, 0);       /* char left */
459           glVertex3f (x + w, y - sc->descent + h, 0);
460           strcat (s2, chars[i]);
461           w = sw_string_width (sc, s2);
462           free (chars[i]);
463         }
464
465       glVertex3f (x + w, y - sc->descent, 0);           /* char right */
466       glVertex3f (x + w, y - sc->descent + h, 0);
467
468       glVertex3f (x,     y - sc->descent + h, 0);       /* ascent */
469       glVertex3f (x + w, y - sc->descent + h, 0);
470
471       glVertex3f (x - sc->char_width,     y, 0);        /* baseline */
472       glVertex3f (x + sc->char_width + w, y, 0);
473
474       glVertex3f (x,     y - sc->descent, 0);           /* descent */
475       glVertex3f (x + w, y - sc->descent, 0);
476
477       glEnd();
478
479       free (s2);
480       free (chars);
481
482       if (textures_p) glEnable (GL_TEXTURE_2D);
483     }
484 }
485
486
487 static void
488 grid (double width, double height, double xspacing, double yspacing, double z)
489 {
490   double x, y;
491   for (y = 0; y <= height/2; y += yspacing)
492     {
493       glBegin(GL_LINES);
494       glVertex3f(-width/2,  y, z);
495       glVertex3f( width/2,  y, z);
496       glVertex3f(-width/2, -y, z);
497       glVertex3f( width/2, -y, z);
498       glEnd();
499     }
500   for (x = 0; x <= width/2; x += xspacing)
501     {
502       glBegin(GL_LINES);
503       glVertex3f( x, -height/2, z);
504       glVertex3f( x,  height/2, z);
505       glVertex3f(-x, -height/2, z);
506       glVertex3f(-x,  height/2, z);
507       glEnd();
508     }
509
510   glBegin(GL_LINES);
511   glVertex3f(-width, 0, z);
512   glVertex3f( width, 0, z);
513   glVertex3f(0, -height, z);
514   glVertex3f(0,  height, z);
515   glEnd();
516 }
517
518 static void
519 box (double width, double height, double depth)
520 {
521   glBegin(GL_LINE_LOOP);
522   glVertex3f(-width/2,  -height/2, -depth/2);
523   glVertex3f(-width/2,   height/2, -depth/2);
524   glVertex3f( width/2,   height/2, -depth/2);
525   glVertex3f( width/2,  -height/2, -depth/2);
526   glEnd();
527   glBegin(GL_LINE_LOOP);
528   glVertex3f(-width/2,  -height/2,  depth/2);
529   glVertex3f(-width/2,   height/2,  depth/2);
530   glVertex3f( width/2,   height/2,  depth/2);
531   glVertex3f( width/2,  -height/2,  depth/2);
532   glEnd();
533   glBegin(GL_LINE_LOOP);
534   glVertex3f(-width/2,  -height/2, -depth/2);
535   glVertex3f(-width/2,  -height/2,  depth/2);
536   glVertex3f(-width/2,   height/2,  depth/2);
537   glVertex3f(-width/2,   height/2, -depth/2);
538   glEnd();
539   glBegin(GL_LINE_LOOP);
540   glVertex3f( width/2,  -height/2, -depth/2);
541   glVertex3f( width/2,  -height/2,  depth/2);
542   glVertex3f( width/2,   height/2,  depth/2);
543   glVertex3f( width/2,   height/2, -depth/2);
544   glEnd();
545
546   glBegin(GL_LINES);
547   glVertex3f(-width/2,   height/2,  depth/2);
548   glVertex3f(-width/2,  -height/2, -depth/2);
549
550   glVertex3f( width/2,   height/2,  depth/2);
551   glVertex3f( width/2,  -height/2, -depth/2);
552
553   glVertex3f(-width/2,  -height/2,  depth/2);
554   glVertex3f(-width/2,   height/2, -depth/2);
555
556   glVertex3f( width/2,  -height/2,  depth/2);
557   glVertex3f( width/2,   height/2, -depth/2);
558   glEnd();
559 }
560
561
562 /* Construct stars (number of stars is dependent on size of screen) */
563 static void
564 init_stars (ModeInfo *mi, int width, int height)
565 {
566   sws_configuration *sc = &scs[MI_SCREEN(mi)];
567   int i, j;
568   int size = (width > height ? width : height);
569   int nstars = size * size / 320;
570   int max_size = 3;
571   GLfloat inc = 0.5;
572   int steps = max_size / inc;
573
574   glDeleteLists (sc->star_list, 1);
575   sc->star_list = glGenLists (1);
576   glNewList (sc->star_list, GL_COMPILE);
577
578   glEnable(GL_POINT_SMOOTH);
579
580   for (j = 1; j <= steps; j++)
581     {
582       glPointSize(inc * j);
583       glBegin (GL_POINTS);
584       for (i = 0; i < nstars / steps; i++)
585         {
586           glColor3f (0.6 + frand(0.3),
587                      0.6 + frand(0.3),
588                      0.6 + frand(0.3));
589           glVertex2f (2 * size * (0.5 - frand(1.0)),
590                       2 * size * (0.5 - frand(1.0)));
591         }
592       glEnd ();
593     }
594   glEndList ();
595 }
596
597
598 /* Window management, etc
599  */
600 ENTRYPOINT void
601 reshape_sws (ModeInfo *mi, int width, int height)
602 {
603   sws_configuration *sc = &scs[MI_SCREEN(mi)];
604
605   /* Set up matrices for perspective text display
606    */
607   {
608     GLfloat desired_aspect = (GLfloat) 3/4;
609     int w = mi->xgwa.width;
610     int h = mi->xgwa.height;
611     int yoff = 0;
612     GLfloat rot = current_device_rotation();
613
614 #ifdef KEEP_ASPECT
615     {
616       int h2 = w * desired_aspect;
617       yoff = (h - h2) / 2;      /* Wide window: letterbox at top and bottom. */
618       if (yoff < 0) yoff = 0;   /* Tall window: clip off the top. */
619       h = h2;
620     }
621 #endif
622
623     glMatrixMode (GL_PROJECTION);
624     glViewport (0, yoff, w, h);
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     glRotatef(rot, 0, 0, 1);
634
635     /* Horrible kludge to prevent the text from materializing already
636        on screen on iPhone in landscape mode.
637      */
638     if ((rot >  45 && rot <  135) ||
639         (rot < -45 && rot > -135))
640       {
641         GLfloat s = 1.1;
642         glScalef (s, s, s);
643       }
644
645     glRotatef (-60.0, 1.0, 0.0, 0.0);
646
647 #if 0
648     glRotatef (60.0, 1.0, 0.0, 0.0);
649     glTranslatef (260, 3200, 0);
650     glScalef (1.85, 1.85, 1);
651 #endif
652
653     /* The above gives us an arena where the bottom edge of the screen is
654        represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
655
656     /* Now let's move the origin to the front of the screen. */
657     glTranslatef (0.0, -3140, 0.0);
658
659     /* And then let's scale so that the bottom of the screen is 1.0 wide. */
660     glScalef (4200, 4200, 4200);
661   }
662
663
664   /* Compute the height in pixels of the line at the bottom of the screen. */
665   {
666     GLdouble mm[17], pm[17];
667     GLint vp[5];
668     GLdouble x = 0.5, y1 = 0, z = 0;
669     GLdouble y2 = sc->line_height;
670     GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
671
672     glGetDoublev (GL_MODELVIEW_MATRIX, mm);
673     glGetDoublev (GL_PROJECTION_MATRIX, pm);
674     glGetIntegerv (GL_VIEWPORT, vp);
675     gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
676     gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
677     sc->line_pixel_height = (wy2 - wy1);
678     glLineWidth (1);
679   }
680
681   /* Compute the best looking line thickness for the bottom line.
682    */
683   if (!thick_p)
684     sc->line_thickness = 1.0;
685   else
686     sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
687
688   if (sc->line_thickness < 1.2)
689     sc->line_thickness = 1.0;
690 }
691
692
693 static void
694 gl_init (ModeInfo *mi)
695 {
696   sws_configuration *sc = &scs[MI_SCREEN(mi)];
697
698   glDisable (GL_LIGHTING);
699   glDisable (GL_DEPTH_TEST);
700
701   if (smooth_p)
702     {
703       glEnable (GL_LINE_SMOOTH);
704       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
705       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
706       glEnable (GL_BLEND);
707     }
708
709   sc->text_list = glGenLists (1);
710   glNewList (sc->text_list, GL_COMPILE);
711   glEndList ();
712
713   sc->star_list = glGenLists (1);
714   glNewList (sc->star_list, GL_COMPILE);
715   glEndList ();
716
717   sc->line_thickness = 1.0;
718 }
719
720
721 ENTRYPOINT void 
722 init_sws (ModeInfo *mi)
723 {
724   double font_height;
725
726   sws_configuration *sc = 0;
727
728   if (!scs) {
729     scs = (sws_configuration *)
730       calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
731     if (!scs) {
732       fprintf(stderr, "%s: out of memory\n", progname);
733       exit(1);
734     }
735   }
736
737   sc = &scs[MI_SCREEN(mi)];
738
739   sc->dpy = MI_DISPLAY(mi);
740   sc = &scs[MI_SCREEN(mi)];
741   /* Unchecked malloc. :( */
742   sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
743   sc->line_widths = (int *) calloc (max_lines+1, sizeof(int));
744
745   if ((sc->glx_context = init_GL(mi)) != NULL) {
746     gl_init(mi);
747     reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
748     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
749
750     init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
751   }
752
753   if (textures_p)
754     {
755       XCharStruct e;
756       int cw, ascent, descent;
757       sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
758       texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
759       cw = e.width;
760       sc->char_width = cw;
761       font_height = ascent + descent;
762       sc->descent = descent;
763       glEnable(GL_ALPHA_TEST);
764       glEnable (GL_TEXTURE_2D);
765
766       check_gl_error ("loading font");
767
768       /* "Anistropic filtering helps for quadrilateral-angled textures.
769          A sharper image is accomplished by interpolating and filtering
770          multiple samples from one or more mipmaps to better approximate
771          very distorted textures.  This is the next level of filtering
772          after trilinear filtering." */
773       if (smooth_p && 
774           strstr ((char *) glGetString(GL_EXTENSIONS),
775                   "GL_EXT_texture_filter_anisotropic"))
776       {
777         GLfloat anisotropic = 0.0;
778         glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
779         if (anisotropic >= 1.0)
780           glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
781                            anisotropic);
782       }
783     }
784   else
785     {
786       font_height = GLUT_FONT->top - GLUT_FONT->bottom;
787       sc->char_width = glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems wide */
788       sc->descent = 0;
789     }
790   
791   sc->font_scale = 1.0 / sc->char_width;
792
793
794   /* We consider a font that consumes 80 columns to be "18 points".
795
796      If neither -size nor -columns was specified, default to 60 columns
797      (which is 24 points.)
798
799      If both were specified, -columns has priority.
800    */
801   {
802     int base_col  = 80;
803     int base_size = 18;
804
805     if (target_columns <= 0 && font_size <= 0)
806       target_columns = 60;
807
808     if (target_columns > 0)
809       font_size = base_size * (base_col / (double) target_columns);
810     else if (font_size > 0)
811       target_columns = base_col * (base_size / (double) font_size);
812   }
813
814   sc->line_pixel_width = target_columns * sc->char_width;
815
816   sc->font_scale /= target_columns;
817   sc->line_height = font_height * sc->font_scale;
818
819   /* Buffer only a few lines of text.
820      If the buffer is too big, there's a significant delay between
821      when the program launches and when the text appears, which can be
822      irritating for time-sensitive output (clock, current music, etc.)
823
824      I'd like to buffer only 2 lines, but we need to assume that we
825      could get a whole line of N-byte Unicrud, and if we fill the buffer
826      before hitting the end of the line, we stall.
827    */
828   sc->buf_size = target_columns * 2 * 4;
829   if (sc->buf_size < 80) sc->buf_size = 80;
830   sc->buf = (char *) calloc (1, sc->buf_size);
831
832   sc->total_lines = max_lines-1;
833
834   if (random() & 1)
835     star_spin = -star_spin;
836
837   if (!alignment_str || !*alignment_str ||
838       !strcasecmp(alignment_str, "left"))
839     alignment = -1;
840   else if (!strcasecmp(alignment_str, "center") ||
841            !strcasecmp(alignment_str, "middle"))
842     alignment = 0;
843   else if (!strcasecmp(alignment_str, "right"))
844     alignment = 1;
845   else
846     {
847       fprintf (stderr,
848                "%s: alignment must be left, center, or right, not \"%s\"\n",
849                progname, alignment_str);
850       exit (1);
851     }
852
853   sc->tc = textclient_open (sc->dpy);
854
855   /* one more reshape, after line_height has been computed */
856   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
857 }
858
859
860 static void
861 draw_stars (ModeInfo *mi)
862 {
863   sws_configuration *sc = &scs[MI_SCREEN(mi)];
864
865   glMatrixMode (GL_PROJECTION);
866   glPushMatrix ();
867   {
868     glLoadIdentity ();
869
870     glMatrixMode (GL_MODELVIEW);
871     glPushMatrix ();
872     {
873       glLoadIdentity ();
874       glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
875                -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
876                -100.0, 100.0);
877       glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
878       if (textures_p) glDisable (GL_TEXTURE_2D);
879
880       /* Keep the stars pointing in the same direction after rotation */
881       glRotatef(current_device_rotation(), 0, 0, 1);
882
883       glCallList (sc->star_list);
884       if (textures_p) glEnable (GL_TEXTURE_2D);
885     }
886     glPopMatrix ();
887   }
888   glMatrixMode (GL_PROJECTION);
889   glPopMatrix ();
890 }
891
892 ENTRYPOINT void
893 draw_sws (ModeInfo *mi)
894 {
895   sws_configuration *sc = &scs[MI_SCREEN(mi)];
896 /*  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
897   Display *dpy = MI_DISPLAY(mi);
898   Window window = MI_WINDOW(mi);
899   int i;
900
901   if (!sc->glx_context)
902     return;
903
904   glDrawBuffer (GL_BACK);
905   glXMakeCurrent (dpy, window, *(sc->glx_context));
906
907   glClear (GL_COLOR_BUFFER_BIT);
908
909   draw_stars (mi);
910
911   glMatrixMode (GL_MODELVIEW);
912   glPushMatrix ();
913
914 # ifdef USE_IPHONE
915   /* Need to do this every time to get device rotation right */
916   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
917 # endif
918
919   if (debug_p)
920     {
921       int i;
922       glPushMatrix ();
923       if (textures_p) glDisable (GL_TEXTURE_2D);
924       glLineWidth (1);
925       glTranslatef (0,-1, 0);
926
927       glColor3f(1, 0, 0);       /* Red line is where text appears */
928       glPushMatrix();
929       glTranslatef(0, -0.028, 0);
930       glLineWidth (4);
931       glBegin(GL_LINES);
932       glVertex3f(-0.5,  1, 0);
933       glVertex3f( 0.5,  1, 0);
934       glVertex3f(-0.5, -1, 0);
935       glVertex3f( 0.5, -1, 0);
936       glEnd();
937       glLineWidth (1);
938       glPopMatrix();
939
940       glColor3f (0.2, 0.2, 0.2);
941       for (i = 0; i < 16; i++)
942         {
943           box (1, 1, 1);
944           grid (1, 1, sc->char_width * sc->font_scale, sc->line_height, 0);
945           glTranslatef(0, 1, 0);
946         }
947       if (textures_p) glEnable (GL_TEXTURE_2D);
948       glPopMatrix ();
949       check_gl_error ("debug render");
950     }
951
952   /* Scroll to current position */
953   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
954
955   glColor3f (1.0, 1.0, 0.4);
956
957   mi->polygon_count = 0;
958
959   glPushMatrix ();
960   glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
961   for (i = 0; i < sc->total_lines; i++)
962     {
963       double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
964       int offscreen_lines = 2;
965
966       double x = -0.5;
967       double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
968                    * sc->line_height);
969       double xoff = 0;
970       char *line = sc->lines[i];
971
972       if (debug_p)
973         {
974           double xx = x * 1.4;  /* a little more to the left */
975           char n[20];
976           sprintf(n, "%d:", i);
977           glColor3f (1.0, 1.0, 1.0);
978           draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
979         }
980
981       if (!line || !*line)
982         continue;
983
984       if (sc->line_thickness != 1 && !textures_p)
985         {
986           int max_thick_lines = MAX_THICK_LINES;
987           GLfloat thinnest_line = 1.0;
988           GLfloat thickest_line = sc->line_thickness;
989           GLfloat range = thickest_line - thinnest_line;
990           GLfloat thickness;
991
992           int j = sc->total_lines - i - 1;
993
994           if (j > max_thick_lines)
995             thickness = thinnest_line;
996           else
997             thickness = (thinnest_line +
998                          (range * ((max_thick_lines - j) /
999                                    (GLfloat) max_thick_lines)));
1000
1001           glLineWidth (thickness);
1002         }
1003
1004       if (alignment >= 0)
1005         {
1006           int n = sc->line_widths[i];
1007           xoff = 1.0 - (n * sc->font_scale);
1008         }
1009
1010       if (alignment == 0)
1011         xoff /= 2;
1012
1013       glColor3f (fade, fade, 0.5 * fade);
1014       draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
1015                    line);
1016       if (textures_p)
1017         mi->polygon_count += strlen (line);
1018     }
1019   glPopMatrix ();
1020
1021
1022
1023   sc->intra_line_scroll += sc->line_height / scroll_steps;
1024
1025   if (sc->intra_line_scroll >= sc->line_height)
1026     {
1027       sc->intra_line_scroll = 0;
1028
1029       /* Drop the oldest line off the end. */
1030       if (sc->lines[0])
1031         free (sc->lines[0]);
1032
1033       /* Scroll the contents of the lines array toward 0. */
1034       if (sc->total_lines > 0)
1035         {
1036           for (i = 1; i < sc->total_lines; i++) {
1037             sc->lines[i-1] = sc->lines[i];
1038             sc->line_widths[i-1] = sc->line_widths[i];
1039           }
1040           sc->lines[--sc->total_lines] = 0;
1041         }
1042
1043       /* Bring in new lines at the end. */
1044       get_more_lines (sc);
1045
1046       if (sc->total_lines < max_lines)
1047         /* Oops, we ran out of text... well, insert some blank lines
1048            here so that new text still pulls in from the bottom of
1049            the screen, isntead of just appearing. */
1050         sc->total_lines = max_lines;
1051     }
1052
1053   glPopMatrix ();
1054
1055   if (mi->fps_p) do_fps (mi);
1056   glFinish();
1057   glXSwapBuffers(dpy, window);
1058
1059   sc->star_theta += star_spin;
1060 }
1061
1062 ENTRYPOINT void
1063 release_sws (ModeInfo *mi)
1064 {
1065   if (scs) {
1066     int screen;
1067     for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
1068       sws_configuration *sc = &scs[screen];
1069       if (sc->tc)
1070         textclient_close (sc->tc);
1071
1072       /* #### there's more to free here */
1073     }
1074     free (scs);
1075     scs = 0;
1076   }
1077   FreeAllGL(mi);
1078 }
1079
1080
1081 #ifdef __GNUC__
1082  __extension__ /* don't warn about "string length is greater than the length
1083                   ISO C89 compilers are required to support" when including
1084                   "starwars.txt" in the defaults... */
1085 #endif
1086
1087 XSCREENSAVER_MODULE_2 ("StarWars", starwars, sws)
1088
1089 #endif /* USE_GL */