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