http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[xscreensaver] / hacks / fontglide.c
1 /* xscreensaver, Copyright (c) 2003, 2005, 2006 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * fontglide -- reads text from a subprocess and puts it on the screen using
12  * large characters that glide in from the edges, assemble, then disperse.
13  * Requires a system with scalable fonts.  (X's font handing sucks.  A lot.)
14  */
15
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif /* HAVE_CONFIG_H */
19
20 #include <math.h>
21
22 #ifndef HAVE_COCOA
23 # include <X11/Intrinsic.h>
24 #endif
25
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
29
30 #include "screenhack.h"
31
32 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
33 #include "xdbe.h"
34 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
35
36 typedef struct {
37   char *text;
38   int x, y, width, height;
39   int ascent, lbearing, rbearing;
40
41   int nticks, tick;
42   int start_x,  start_y;
43   int target_x, target_y;
44   Pixmap pixmap, mask;
45 } word;
46
47
48 typedef struct {
49   int id;
50   XColor fg;
51   XColor bg;
52   Bool dark_p;
53   Bool move_chars_p;
54   int width;
55
56   char *font_name;
57   XFontStruct *font;
58
59   GC fg_gc;
60
61   int nwords;
62   word **words;
63
64   enum { IN, PAUSE, OUT } anim_state;
65   enum { LEFT, CENTER, RIGHT } alignment;
66   int pause_tick;
67
68 } sentence;
69
70
71 typedef struct {
72   Display *dpy;
73   Window window;
74   XWindowAttributes xgwa;
75
76   Pixmap b, ba; /* double-buffer to reduce flicker */
77   GC bg_gc;
78
79 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
80   XdbeBackBuffer backb;
81   Bool dbeclear_p;
82 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
83
84   Bool dbuf;            /* Whether we're using double buffering. */
85
86   int border_width;     /* size of the font outline */
87   char *charset;        /* registry and encoding for font lookups */
88   double speed;         /* frame rate multiplier */
89   double linger;        /* multiplier for how long to leave words on screen */
90   Bool trails_p;
91   Bool debug_p;
92   int debug_metrics_p;
93   enum { PAGE, SCROLL } mode;
94
95   char *font_override;  /* if -font was specified on the cmd line */
96
97   FILE *pipe;
98   XtIntervalId timer_id;
99   XtInputId pipe_id;
100   Time subproc_relaunch_delay;
101   /* Bool input_available_p; */
102
103   char buf [40];        /* this only needs to be as big as one "word". */
104   int buf_tail;
105
106   int nsentences;
107   sentence **sentences;
108   Bool spawn_p;         /* whether it is time to create a new sentence */
109   int latest_sentence;
110
111   unsigned long frame_delay;
112
113   int id_tick;
114
115 } state;
116
117
118 static void launch_text_generator (state *);
119 static void drain_input (state *s);
120
121
122 static int
123 pick_font_size (state *s)
124 {
125   double scale = s->xgwa.height / 1024.0;  /* shrink for small windows */
126   int min, max, r, pixel;
127
128   min = scale * 24;
129   max = scale * 260;
130
131   if (min < 10) min = 10;
132   if (max < 30) max = 30;
133
134   r = ((max-min)/3)+1;
135
136   pixel = min + ((random() % r) + (random() % r) + (random() % r));
137
138   if (s->mode == SCROLL)  /* scroll mode likes bigger fonts */
139     pixel *= 1.5;
140
141   return pixel;
142 }
143
144
145 /* Finds the set of scalable fonts on the system; picks one;
146    and loads that font in a random pixel size.
147    Returns False if something went wrong.
148  */
149 static Bool
150 pick_font_1 (state *s, sentence *se)
151 {
152   Bool ok = False;
153   char pattern[1024];
154
155 # ifndef HAVE_COCOA /* real Xlib */
156   char **names = 0;
157   char **names2 = 0;
158   XFontStruct *info = 0;
159   int count = 0, count2 = 0;
160   int i;
161
162   if (se->font)
163     {
164       XFreeFont (s->dpy, se->font);
165       free (se->font_name);
166       se->font = 0;
167       se->font_name = 0;
168     }
169
170   if (s->font_override)
171     sprintf (pattern, "%.200s", s->font_override);
172   else
173     sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
174              "*",         /* foundry */
175              "*",         /* family */
176              "*",         /* weight */
177              "*",         /* slant */
178              "*",         /* swidth */
179              "*",         /* adstyle */
180              "0",         /* pixel size */
181              "0",         /* point size */
182              "0",         /* resolution x */
183              "0",         /* resolution y */
184              "p",         /* spacing */
185              "0",         /* avg width */
186              s->charset); /* registry + encoding */
187
188   names = XListFonts (s->dpy, pattern, 1000, &count);
189
190   if (count <= 0)
191     {
192       if (s->font_override)
193         fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
194       else
195         fprintf (stderr, "%s: no scalable fonts found!  (pattern: %s)\n",
196                  progname, pattern);
197       exit (1);
198     }
199
200   i = random() % count;
201
202   names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
203   if (count2 <= 0)
204     {
205       fprintf (stderr, "%s: pattern %s\n"
206                 "     gave unusable %s\n\n",
207                progname, pattern, names[i]);
208       goto FAIL;
209     }
210
211   {
212     XFontStruct *font = &info[0];
213     unsigned long value = 0;
214     char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
215     unsigned long pixel=0, point=0, res_x=0, res_y=0;
216     char *spacing=0;
217     unsigned long avg_width=0;
218     char *registry=0, *encoding=0;
219     Atom a;
220     char *bogus = "\"?\"";
221
222 # define STR(ATOM,VAR)                                  \
223   bogus = (ATOM);                                       \
224   a = XInternAtom (s->dpy, (ATOM), False);              \
225   if (XGetFontProperty (font, a, &value))               \
226     VAR = XGetAtomName (s->dpy, value);                 \
227   else                                                  \
228     goto FAIL2
229
230 # define INT(ATOM,VAR)                                  \
231   bogus = (ATOM);                                       \
232   a = XInternAtom (s->dpy, (ATOM), False);              \
233   if (!XGetFontProperty (font, a, &VAR) ||              \
234       VAR > 9999)                                       \
235     goto FAIL2
236
237     STR ("FOUNDRY",          foundry);
238     STR ("FAMILY_NAME",      family);
239     STR ("WEIGHT_NAME",      weight);
240     STR ("SLANT",            slant);
241     STR ("SETWIDTH_NAME",    setwidth);
242     STR ("ADD_STYLE_NAME",   add_style);
243     INT ("PIXEL_SIZE",       pixel);
244     INT ("POINT_SIZE",       point);
245     INT ("RESOLUTION_X",     res_x);
246     INT ("RESOLUTION_Y",     res_y);
247     STR ("SPACING",          spacing);
248     INT ("AVERAGE_WIDTH",    avg_width);
249     STR ("CHARSET_REGISTRY", registry);
250     STR ("CHARSET_ENCODING", encoding);
251
252 #undef INT
253 #undef STR
254
255     pixel = pick_font_size (s);
256
257 #if 0
258     /* Occasionally change the aspect ratio of the font, by increasing
259        either the X or Y resolution (while leaving the other alone.)
260
261        #### Looks like this trick doesn't really work that well: the
262             metrics of the individual characters are ok, but the
263             overall font ascent comes out wrong (unscaled.)
264      */
265     if (! (random() % 8))
266       {
267         double n = 2.5 / 3;
268         double scale = 1 + (frand(n) + frand(n) + frand(n));
269         if (random() % 2)
270           res_x *= scale;
271         else
272           res_y *= scale;
273       }
274 # endif
275
276     sprintf (pattern,
277              "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
278              foundry, family, weight, slant, setwidth, add_style,
279              pixel, "*", /* point, */
280              res_x, res_y, spacing,
281              "*", /* avg_width */
282              registry, encoding);
283     ok = True;
284
285   FAIL2:
286     if (!ok)
287       fprintf (stderr, "%s: font has bogus %s property: %s\n",
288                progname, bogus, names[i]);
289
290     if (foundry)   XFree (foundry);
291     if (family)    XFree (family);
292     if (weight)    XFree (weight);
293     if (slant)     XFree (slant);
294     if (setwidth)  XFree (setwidth);
295     if (add_style) XFree (add_style);
296     if (spacing)   XFree (spacing);
297     if (registry)  XFree (registry);
298     if (encoding)  XFree (encoding);
299   }
300
301  FAIL: 
302
303   XFreeFontInfo (names2, info, count2);
304   XFreeFontNames (names);
305
306 # else  /* HAVE_COCOA */
307
308   if (s->font_override)
309     sprintf (pattern, "%.200s", s->font_override);
310   else
311     {
312       const char *family = "random";
313       const char *weight = ((random() % 2)  ? "normal" : "bold");
314       const char *slant  = ((random() % 2)  ? "o" : "r");
315       int size = 10 * pick_font_size (s);
316       sprintf (pattern, "*-%s-%s-%s-*-%d-*", family, weight, slant, size);
317     }
318   ok = True;
319 # endif /* HAVE_COCOA */
320
321   if (! ok) return False;
322
323   se->font = XLoadQueryFont (s->dpy, pattern);
324   if (! se->font)
325     {
326       if (s->debug_p)
327         fprintf (stderr, "%s: unable to load font %s\n",
328                  progname, pattern);
329       return False;
330     }
331
332   if (se->font->min_bounds.width == se->font->max_bounds.width &&
333       !s->font_override)
334     {
335       /* This is to weed out
336          "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
337          "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1".
338          We asked for only proportional fonts, but this fixed-width font
339          shows up anyway -- but it has goofy metrics (see below) so it
340          looks terrible anyway.
341        */
342       if (s->debug_p)
343         fprintf (stderr,
344                  "%s: skipping bogus monospace non-charcell font: %s\n",
345                  progname, pattern);
346       return False;
347     }
348
349   if (s->debug_p) 
350     fprintf(stderr, "%s: %s\n", progname, pattern);
351
352   se->font_name = strdup (pattern);
353   XSetFont (s->dpy, se->fg_gc, se->font->fid);
354   return True;
355 }
356
357
358 /* Finds the set of scalable fonts on the system; picks one;
359    and loads that font in a random pixel size.
360  */
361 static void
362 pick_font (state *s, sentence *se)
363 {
364   int i;
365   for (i = 0; i < 20; i++)
366     if (pick_font_1 (s, se))
367       return;
368   fprintf (stderr, "%s: too many font-loading failures: giving up!\n", progname);
369   exit (1);
370 }
371
372
373 static char *unread_word_text = 0;
374
375 /* Returns a newly-allocated string with one word in it, or NULL if there
376    is no complete word available.
377  */
378 static char *
379 get_word_text (state *s)
380 {
381   char *start = s->buf;
382   char *end;
383   char *result = 0;
384   int lfs = 0;
385
386   drain_input (s);
387
388   if (unread_word_text)
389     {
390       start = unread_word_text;
391       unread_word_text = 0;
392       return start;
393     }
394
395   /* Skip over whitespace at the beginning of the buffer,
396      and count up how many linebreaks we see while doing so.
397    */
398   while (*start &&
399          (*start == ' ' ||
400           *start == '\t' ||
401           *start == '\r' ||
402           *start == '\n'))
403     {
404       if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
405         lfs++;
406       start++;
407     }
408
409   end = start;
410
411   /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
412      to trigger a sentence break here.) */
413   if (lfs >= 2)
414     goto DONE;
415
416   /* Skip forward to the end of this word (find next whitespace.) */
417   while (*end &&
418          (! (*end == ' ' ||
419              *end == '\t' ||
420              *end == '\r' ||
421              *end == '\n')))
422     end++;
423
424   /* If we have a word, allocate a string for it */
425   if (end > start)
426     {
427       result = malloc ((end - start) + 1);
428       strncpy (result, start, (end-start));
429       result [end-start] = 0;
430     }
431
432  DONE:
433
434   /* Make room in the buffer by compressing out any bytes we've processed.
435    */
436   if (end > s->buf)
437     {
438       int n = end - s->buf;
439       memmove (s->buf, end, sizeof(s->buf) - n);
440       s->buf_tail -= n;
441     }
442
443   return result;
444 }
445
446
447 /* Gets some random text, and creates a "word" object from it.
448  */
449 static word *
450 new_word (state *s, sentence *se, char *txt, Bool alloc_p)
451 {
452   word *w;
453   XCharStruct overall;
454   int dir, ascent, descent;
455   int bw = s->border_width;
456
457   if (!txt)
458     return 0;
459
460   w = (word *) calloc (1, sizeof(*w));
461   XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
462
463   /* Leave a little more slack */
464   overall.lbearing -= (bw * 2);
465   overall.rbearing += (bw * 2);
466   overall.ascent   += (bw * 2);
467   overall.descent  += (bw * 2);
468
469   w->width    = overall.rbearing - overall.lbearing + bw + bw;
470   w->height   = overall.ascent   + overall.descent  + bw + bw;
471   w->ascent   = overall.ascent   + bw;
472   w->lbearing = overall.lbearing - bw;
473   w->rbearing = overall.width    + bw;
474
475 # if 0
476   /* The metrics on some fonts are strange -- e.g.,
477      "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
478      "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1" both have
479      an rbearing so wide that it looks like there are two spaces after
480      each letter.  If this character says it has an rbearing that is to
481      the right of its ink, ignore that.
482
483      #### Of course, this hack only helps when we're in `move_chars_p' mode
484           and drawing a char at a time -- when we draw the whole word at once,
485           XDrawString believes the bogus metrics and spaces the font out
486           crazily anyway.
487
488      Sigh, this causes some text to mis-render in, e.g.,
489      "-adobe-utopia-medium-i-normal--114-*-100-100-p-*-iso8859-1"
490      (in "ux", we need the rbearing on "r" or we get too much overlap.)
491    */
492   if (w->rbearing > w->width)
493     w->rbearing = w->width;
494 # endif /* 0 */
495
496   if (s->mode == SCROLL && !alloc_p) abort();
497
498   if (alloc_p)
499     {
500       int i, j;
501       XGCValues gcv;
502       GC gc0, gc1;
503
504       w->pixmap = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
505       w->mask   = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
506
507       gcv.font = se->font->fid;
508       gcv.foreground = 0L;
509       gcv.background = 1L;
510       gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
511                        &gcv);
512       gcv.foreground = 1L;
513       gcv.background = 0L;
514       gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
515                        &gcv);
516
517       XFillRectangle (s->dpy, w->mask,   gc0, 0, 0, w->width, w->height);
518       XFillRectangle (s->dpy, w->pixmap, gc0, 0, 0, w->width, w->height);
519
520       if (s->debug_p)
521         {
522           /* bounding box (behind the characters) */
523           XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
524                           0, 0, w->width-1, w->height-1);
525           XDrawRectangle (s->dpy, w->mask,   gc1,
526                           0, 0, w->width-1, w->height-1);
527         }
528
529 #if 0
530       if (s->debug_p)
531         {
532           /* bounding box (behind *each* character) */
533           char *ss;
534           int x = 0;
535           for (ss = txt; *ss; ss++)
536             {
537               XTextExtents (se->font, ss, 1, &dir, &ascent, &descent, &overall);
538               XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
539                               x, w->ascent - overall.ascent, 
540                               overall.width, 
541                               overall.ascent + overall.descent);
542               XDrawRectangle (s->dpy, w->mask,   gc1,
543                               x, w->ascent - overall.ascent, 
544                               overall.width,
545                               overall.ascent + overall.descent);
546
547               XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
548                               x - overall.lbearing, w->ascent - overall.ascent, 
549                               overall.rbearing, 
550                               overall.ascent + overall.descent);
551               XDrawRectangle (s->dpy, w->mask,   gc1,
552                               x - overall.lbearing, w->ascent - overall.ascent, 
553                               overall.rbearing,
554                               overall.ascent + overall.descent);
555
556
557               x += overall.width;
558             }
559         }
560 #endif
561
562       /* Draw foreground text */
563       XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
564                    txt, strlen(txt));
565
566       /* Cheesy hack to draw a border */
567       /* (I should be able to do this in i*2 time instead of i*i time,
568          but I can't get it right, so fuck it.) */
569       XSetFunction (s->dpy, gc1, GXor);
570       for (i = -bw; i <= bw; i++)
571         for (j = -bw; j <= bw; j++)
572           XCopyArea (s->dpy, w->pixmap, w->mask, gc1,
573                      0, 0, w->width, w->height,
574                      i, j);
575
576       if (s->debug_p)
577         {
578           XSetFunction (s->dpy, gc1, GXcopy);
579           if (w->ascent != w->height)
580             {
581               /* baseline (on top of the characters) */
582               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
583                          0, w->ascent, w->width-1, w->ascent);
584               XDrawLine (s->dpy, w->mask,   gc1,
585                          0, w->ascent, w->width-1, w->ascent);
586             }
587
588           if (w->lbearing != 0)
589             {
590               /* left edge of charcell */
591               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
592                          w->lbearing, 0, w->lbearing, w->height-1);
593               XDrawLine (s->dpy, w->mask,   gc1,
594                          w->lbearing, 0, w->lbearing, w->height-1);
595             }
596
597           if (w->rbearing != w->width)
598             {
599               /* right edge of charcell */
600               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
601                          w->rbearing, 0, w->rbearing, w->height-1);
602               XDrawLine (s->dpy, w->mask,   gc1,
603                          w->rbearing, 0, w->rbearing, w->height-1);
604             }
605         }
606
607       XFreeGC (s->dpy, gc0);
608       XFreeGC (s->dpy, gc1);
609     }
610
611   w->text = txt;
612   return w;
613 }
614
615
616 static void
617 free_word (state *s, word *w)
618 {
619   if (w->text)   free (w->text);
620   if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
621   if (w->mask)   XFreePixmap (s->dpy, w->mask);
622 }
623
624
625 static sentence *
626 new_sentence (state *st, state *s)
627 {
628   XGCValues gcv;
629   sentence *se = (sentence *) calloc (1, sizeof (*se));
630   se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
631   se->anim_state = IN;
632   se->id = ++st->id_tick;
633   return se;
634 }
635
636
637 static void
638 free_sentence (state *s, sentence *se)
639 {
640   int i;
641   for (i = 0; i < se->nwords; i++)
642     free_word (s, se->words[i]);
643   if (se->words) free (se->words);
644
645   if (se->fg.flags)
646     XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
647   if (se->bg.flags)
648     XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
649
650   if (se->font_name) free (se->font_name);
651   if (se->font) XFreeFont (s->dpy, se->font);
652   if (se->fg_gc) XFreeGC (s->dpy, se->fg_gc);
653
654   free (se);
655 }
656
657
658 /* free the word, and put its text back at the front of the input queue,
659    to be read next time. */
660 static void
661 unread_word (state *s, word *w)
662 {
663   if (unread_word_text)
664     abort();
665   unread_word_text = w->text;
666   w->text = 0;
667   free_word (s, w);
668 }
669
670
671 /* Divide each of the words in the sentence into one character words,
672    without changing the positions of those characters.
673  */
674 static void
675 split_words (state *s, sentence *se)
676 {
677   word **words2;
678   int nwords2 = 0;
679   int i, j;
680   for (i = 0; i < se->nwords; i++)
681     nwords2 += strlen (se->words[i]->text);
682
683   words2 = (word **) calloc (nwords2, sizeof(*words2));
684
685   for (i = 0, j = 0; i < se->nwords; i++)
686     {
687       word *ow = se->words[i];
688       int L = strlen (ow->text);
689       int k;
690
691       int x  = ow->x;
692       int y  = ow->y;
693       int sx = ow->start_x;
694       int sy = ow->start_y;
695       int tx = ow->target_x;
696       int ty = ow->target_y;
697
698       for (k = 0; k < L; k++)
699         {
700           char *t2 = malloc (2);
701           word *w2;
702           int xoff, yoff;
703
704           t2[0] = ow->text[k];
705           t2[1] = 0;
706           w2 = new_word (s, se, t2, True);
707           words2[j++] = w2;
708
709           xoff = (w2->lbearing - ow->lbearing);
710           yoff = (ow->ascent - w2->ascent);
711
712           w2->x        = x  + xoff;
713           w2->y        = y  + yoff;
714           w2->start_x  = sx + xoff;
715           w2->start_y  = sy + yoff;
716           w2->target_x = tx + xoff;
717           w2->target_y = ty + yoff;
718
719           x  += w2->rbearing;
720           sx += w2->rbearing;
721           tx += w2->rbearing;
722         }
723
724       free_word (s, ow);
725       se->words[i] = 0;
726     }
727   free (se->words);
728
729   se->words = words2;
730   se->nwords = nwords2;
731 }
732
733
734 /* Set the source or destination position of the words to be somewhere
735    off screen.
736  */
737 static void
738 scatter_sentence (state *s, sentence *se)
739 {
740   int i = 0;
741   int off = 100;
742
743   int flock_p = ((random() % 4) == 0);
744   int mode = (flock_p ? (random() % 12) : 0);
745
746   for (i = 0; i < se->nwords; i++)
747     {
748       word *w = se->words[i];
749       int x, y;
750       int r = (flock_p ? mode : (random() % 4));
751       switch (r)
752         {
753           /* random positions on the edges */
754
755         case 0:
756           x = -off - w->width;
757           y = random() % s->xgwa.height;
758           break;
759         case 1:
760           x = off + s->xgwa.width;
761           y = random() % s->xgwa.height;
762           break;
763         case 2:
764           x = random() % s->xgwa.width;
765           y = -off - w->height;
766           break;
767         case 3:
768           x = random() % s->xgwa.width;
769           y = off + s->xgwa.height;
770           break;
771
772           /* straight towards the edges */
773
774         case 4:
775           x = -off - w->width;
776           y = w->target_y;
777           break;
778         case 5:
779           x = off + s->xgwa.width;
780           y = w->target_y;
781           break;
782         case 6:
783           x = w->target_x;
784           y = -off - w->height;
785           break;
786         case 7:
787           x = w->target_x;
788           y = off + s->xgwa.height;
789           break;
790
791           /* corners */
792
793         case 8:
794           x = -off - w->width;
795           y = -off - w->height;
796           break;
797         case 9:
798           x = -off - w->width;
799           y =  off + s->xgwa.height;
800           break;
801         case 10:
802           x =  off + s->xgwa.width;
803           y =  off + s->xgwa.height;
804           break;
805         case 11:
806           x =  off + s->xgwa.width;
807           y = -off - w->height;
808           break;
809
810         default:
811           abort();
812           break;
813         }
814
815       if (se->anim_state == IN)
816         {
817           w->start_x = x;
818           w->start_y = y;
819         }
820       else
821         {
822           w->start_x = w->x;
823           w->start_y = w->y;
824           w->target_x = x;
825           w->target_y = y;
826         }
827
828       w->nticks = ((100 + ((random() % 140) +
829                            (random() % 140) +
830                            (random() % 140)))
831                    / s->speed);
832       if (w->nticks < 2)
833         w->nticks = 2;
834       w->tick = 0;
835     }
836 }
837
838
839 /* Set the source position of the words to be off the right side,
840    and the destination to be off the left side.
841  */
842 static void
843 aim_sentence (state *s, sentence *se)
844 {
845   int i = 0;
846   int nticks;
847   int yoff = 0;
848
849   if (se->nwords <= 0) abort();
850
851   /* Have the sentence shift up or down a little bit; not too far, and
852      never let it fall off the top or bottom of the screen before its
853      last character has reached the left edge.
854    */
855   for (i = 0; i < 10; i++)
856     {
857       int ty = random() % (s->xgwa.height - se->words[0]->ascent);
858       yoff = ty - se->words[0]->target_y;
859       if (yoff < s->xgwa.height/3)  /* this one is ok */
860         break;
861     }
862
863   for (i = 0; i < se->nwords; i++)
864     {
865       word *w = se->words[i];
866       w->start_x   = w->target_x + s->xgwa.width;
867       w->target_x -= se->width;
868       w->start_y   = w->target_y;
869       w->target_y += yoff;
870     }
871
872   nticks = ((se->words[0]->start_x - se->words[0]->target_x)
873             / (s->speed * 10));
874   nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
875
876   if (nticks < 2)
877     nticks = 2;
878
879   for (i = 0; i < se->nwords; i++)
880     {
881       word *w = se->words[i];
882       w->nticks = nticks;
883       w->tick = 0;
884     }
885 }
886
887
888 /* Randomize the order of the words in the list (since that changes
889    which ones are "on top".)
890  */
891 static void
892 shuffle_words (state *s, sentence *se)
893 {
894   int i;
895   for (i = 0; i < se->nwords-1; i++)
896     {
897       int j = i + (random() % (se->nwords - i));
898       word *swap = se->words[i];
899       se->words[i] = se->words[j];
900       se->words[j] = swap;
901     }
902 }
903
904
905 /* qsort comparitor */
906 static int
907 cmp_sentences (const void *aa, const void *bb)
908 {
909   const sentence *a = *(sentence **) aa;
910   const sentence *b = *(sentence **) bb;
911   return ((a ? a->id : 999999) - (b ? b->id : 999999));
912 }
913
914
915 /* Sort the sentences by id, so that sentences added later are on top.
916  */
917 static void
918 sort_sentences (state *s)
919 {
920   qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
921 }
922
923
924 /* Re-pick the colors of the text and border
925  */
926 static void
927 recolor (state *s, sentence *se)
928 {
929   if (se->fg.flags)
930     XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
931   if (se->bg.flags)
932     XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
933
934   se->fg.flags  = DoRed|DoGreen|DoBlue;
935   se->bg.flags  = DoRed|DoGreen|DoBlue;
936
937   switch (random() % 2)
938     {
939     case 0:   /* bright fg, dim bg */
940       se->fg.red    = (random() % 0x8888) + 0x8888;
941       se->fg.green  = (random() % 0x8888) + 0x8888;
942       se->fg.blue   = (random() % 0x8888) + 0x8888;
943       se->bg.red    = (random() % 0x5555);
944       se->bg.green  = (random() % 0x5555);
945       se->bg.blue   = (random() % 0x5555);
946       break;
947
948     case 1:   /* bright bg, dim fg */
949       se->fg.red    = (random() % 0x4444);
950       se->fg.green  = (random() % 0x4444);
951       se->fg.blue   = (random() % 0x4444);
952       se->bg.red    = (random() % 0x4444) + 0xCCCC;
953       se->bg.green  = (random() % 0x4444) + 0xCCCC;
954       se->bg.blue   = (random() % 0x4444) + 0xCCCC;
955       break;
956
957     default:
958       abort();
959       break;
960     }
961
962   if (s->debug_p)
963     se->dark_p = (se->fg.red*2 + se->fg.green*3 + se->fg.blue <
964                   se->bg.red*2 + se->bg.green*3 + se->bg.blue);
965
966   if (XAllocColor (s->dpy, s->xgwa.colormap, &se->fg))
967     XSetForeground (s->dpy, se->fg_gc, se->fg.pixel);
968   if (XAllocColor (s->dpy, s->xgwa.colormap, &se->bg))
969     XSetBackground (s->dpy, se->fg_gc, se->bg.pixel);
970 }
971
972
973 static void
974 align_line (state *s, sentence *se, int line_start, int x, int right)
975 {
976   int off, j;
977   switch (se->alignment)
978     {
979     case LEFT:   off = 0;               break;
980     case CENTER: off = (right - x) / 2; break;
981     case RIGHT:  off = (right - x);     break;
982     default:     abort();               break;
983     }
984
985   if (off != 0)
986     for (j = line_start; j < se->nwords; j++)
987       se->words[j]->target_x += off;
988 }
989
990
991 /* Fill the sentence with new words: in "page" mode, fills the page
992    with text; in "scroll" mode, just makes one long horizontal sentence.
993    The sentence might have *no* words in it, if no text is currently
994    available.
995  */
996 static void
997 populate_sentence (state *s, sentence *se)
998 {
999   int i = 0;
1000   int left, right, top, x, y;
1001   int space = 0;
1002   int line_start = 0;
1003   Bool done = False;
1004
1005   int array_size = 100;
1006
1007   se->move_chars_p = (s->mode == SCROLL ? False :
1008                       (random() % 3) ? False : True);
1009   se->alignment = (random() % 3);
1010
1011   recolor (s, se);
1012
1013   if (se->words)
1014     {
1015       for (i = 0; i < se->nwords; i++)
1016         free_word (s, se->words[i]);
1017       free (se->words);
1018     }
1019
1020   se->words = (word **) calloc (array_size, sizeof(*se->words));
1021   se->nwords = 0;
1022
1023   switch (s->mode)
1024     {
1025     case PAGE:
1026       left  = random() % (s->xgwa.width / 3);
1027       right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1028       top = random() % (s->xgwa.height * 2 / 3);
1029       break;
1030     case SCROLL:
1031       left = 0;
1032       right = s->xgwa.width;
1033       top = random() % s->xgwa.height;
1034       break;
1035     default:
1036       abort();
1037       break;
1038     }
1039
1040   x = left;
1041   y = top;
1042
1043   while (!done)
1044     {
1045       char *txt = get_word_text (s);
1046       word *w;
1047       if (!txt)
1048         {
1049           if (se->nwords == 0)
1050             return;             /* If the stream is empty, bail. */
1051           else
1052             break;              /* If EOF after some words, end of sentence. */
1053         }
1054
1055       if (! se->font)           /* Got a word: need a font now */
1056         {
1057           pick_font (s, se);
1058           if (y < se->font->ascent)
1059             y += se->font->ascent;
1060           space = XTextWidth (se->font, " ", 1);
1061         }
1062
1063       w = new_word (s, se, txt, !se->move_chars_p);
1064
1065       /* If we have a few words, let punctuation terminate the sentence:
1066          stop gathering more words if the last word ends in a period, etc. */
1067       if (se->nwords >= 4)
1068         {
1069           char c = w->text[strlen(w->text)-1];
1070           if (c == '.' || c == '?' || c == '!')
1071             done = True;
1072         }
1073
1074       /* If the sentence is kind of long already, terminate at commas, etc. */
1075       if (se->nwords >= 12)
1076         {
1077           char c = w->text[strlen(w->text)-1];
1078           if (c == ',' || c == ';' || c == ':' || c == '-' ||
1079               c == ')' || c == ']' || c == '}')
1080             done = True;
1081         }
1082
1083       if (se->nwords >= 25)  /* ok that's just about enough out of you */
1084         done = True;
1085
1086       if (s->mode == PAGE &&
1087           x + w->rbearing > right)                      /* wrap line */
1088         {
1089           align_line (s, se, line_start, x, right);
1090           line_start = se->nwords;
1091
1092           x = left;
1093           y += se->font->ascent;
1094
1095           /* If we're close to the bottom of the screen, stop, and 
1096              unread the current word.  (But not if this is the first
1097              word, otherwise we might just get stuck on it.)
1098            */
1099           if (se->nwords > 0 &&
1100               y + se->font->ascent > s->xgwa.height)
1101             {
1102               unread_word (s, w);
1103               /* done = True; */
1104               break;
1105             }
1106         }
1107
1108       w->target_x = x + w->lbearing;
1109       w->target_y = y - w->ascent;
1110
1111       x += w->rbearing + space;
1112       se->width = x;
1113
1114       if (se->nwords >= (array_size - 1))
1115         {
1116           array_size += 100;
1117           se->words = (word **) realloc (se->words,
1118                                          array_size * sizeof(*se->words));
1119           if (!se->words)
1120             {
1121               fprintf (stderr, "%s: out of memory (%d words)\n",
1122                        progname, array_size);
1123               exit (1);
1124             }
1125         }
1126
1127       se->words[se->nwords++] = w;
1128     }
1129
1130   se->width -= space;
1131
1132   switch (s->mode)
1133     {
1134     case PAGE:
1135       align_line (s, se, line_start, x, right);
1136       if (se->move_chars_p)
1137         split_words (s, se);
1138       scatter_sentence (s, se);
1139       shuffle_words (s, se);
1140       break;
1141     case SCROLL:
1142       aim_sentence (s, se);
1143       break;
1144     default:
1145       abort();
1146       break;
1147     }
1148
1149 # ifdef DEBUG
1150   if (s->debug_p)
1151     {
1152       fprintf (stderr, "%s: sentence %d:", progname, se->id);
1153       for (i = 0; i < se->nwords; i++)
1154         fprintf (stderr, " %s", se->words[i]->text);
1155       fprintf (stderr, "\n");
1156     }
1157 # endif
1158 }
1159
1160
1161 /* Render a single word object to the screen.
1162  */
1163 static void
1164 draw_word (state *s, sentence *se, word *w)
1165 {
1166   if (! w->pixmap) return;
1167
1168   if (w->x + w->width < 0 ||
1169       w->y + w->height < 0 ||
1170       w->x > s->xgwa.width ||
1171       w->y > s->xgwa.height)
1172     return;
1173
1174   XSetClipMask (s->dpy, se->fg_gc, w->mask);
1175   XSetClipOrigin (s->dpy, se->fg_gc, w->x, w->y);
1176   XCopyPlane (s->dpy, w->pixmap, s->b, se->fg_gc,
1177               0, 0, w->width, w->height,
1178               w->x, w->y,
1179               1L);
1180 }
1181
1182
1183 /* If there is room for more sentences, add one.
1184  */
1185 static void
1186 more_sentences (state *s)
1187 {
1188   int i;
1189   Bool any = False;
1190   for (i = 0; i < s->nsentences; i++)
1191     {
1192       sentence *se = s->sentences[i];
1193       if (! se)
1194         {
1195           se = new_sentence (s, s);
1196           populate_sentence (s, se);
1197           if (se->nwords > 0)
1198             s->spawn_p = False, any = True;
1199           else
1200             {
1201               free_sentence (s, se);
1202               se = 0;
1203             }
1204           s->sentences[i] = se;
1205           if (se)
1206             s->latest_sentence = se->id;
1207           break;
1208         }
1209     }
1210
1211   if (any) sort_sentences (s);
1212 }
1213
1214
1215 /* Render all the words to the screen, and run the animation one step.
1216  */
1217 static void
1218 draw_sentence (state *s, sentence *se)
1219 {
1220   int i;
1221   Bool moved = False;
1222
1223   if (! se) return;
1224
1225   for (i = 0; i < se->nwords; i++)
1226     {
1227       word *w = se->words[i];
1228
1229       switch (s->mode)
1230         {
1231         case PAGE:
1232           if (se->anim_state != PAUSE &&
1233               w->tick <= w->nticks)
1234             {
1235               int dx = w->target_x - w->start_x;
1236               int dy = w->target_y - w->start_y;
1237               double r = sin (w->tick * M_PI / (2 * w->nticks));
1238               w->x = w->start_x + (dx * r);
1239               w->y = w->start_y + (dy * r);
1240
1241               w->tick++;
1242               if (se->anim_state == OUT && s->mode == PAGE)
1243                 w->tick++;  /* go out faster */
1244               moved = True;
1245             }
1246           break;
1247         case SCROLL:
1248           {
1249             int dx = w->target_x - w->start_x;
1250             int dy = w->target_y - w->start_y;
1251             double r = (double) w->tick / w->nticks;
1252             w->x = w->start_x + (dx * r);
1253             w->y = w->start_y + (dy * r);
1254             w->tick++;
1255             moved = (w->tick <= w->nticks);
1256
1257             /* Launch a new sentence when:
1258                - the front of this sentence is almost off the left edge;
1259                - the end of this sentence is almost on screen.
1260                - or, randomly
1261              */
1262             if (se->anim_state != OUT &&
1263                 i == 0 &&
1264                 se->id == s->latest_sentence)
1265               {
1266                 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1267                               w->x + se->width < (s->xgwa.width * 2.1));
1268                 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1269
1270                 if (new_p || rand_p)
1271                   {
1272                     se->anim_state = OUT;
1273                     s->spawn_p = True;
1274 # ifdef DEBUG
1275                     if (s->debug_p)
1276                       fprintf (stderr, "%s: OUT   %d (x2 = %d%s)\n",
1277                                progname, se->id,
1278                                se->words[0]->x + se->width,
1279                                rand_p ? " randomly" : "");
1280 # endif
1281                   }
1282               }
1283           }
1284           break;
1285         default:
1286           abort();
1287           break;
1288         }
1289
1290       draw_word (s, se, w);
1291     }
1292
1293   if (moved && se->anim_state == PAUSE)
1294     abort();
1295
1296   if (! moved)
1297     {
1298       switch (se->anim_state)
1299         {
1300         case IN:
1301           se->anim_state = PAUSE;
1302           se->pause_tick = (se->nwords * 7 * s->linger);
1303           if (se->move_chars_p)
1304             se->pause_tick /= 5;
1305           scatter_sentence (s, se);
1306           shuffle_words (s, se);
1307 # ifdef DEBUG
1308           if (s->debug_p)
1309             fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1310 # endif
1311           break;
1312         case PAUSE:
1313           if (--se->pause_tick <= 0)
1314             {
1315               se->anim_state = OUT;
1316               s->spawn_p = True;
1317 # ifdef DEBUG
1318               if (s->debug_p)
1319                 fprintf (stderr, "%s: OUT   %d\n", progname, se->id);
1320 # endif
1321             }
1322           break;
1323         case OUT:
1324 # ifdef DEBUG
1325           if (s->debug_p)
1326             fprintf (stderr, "%s: DEAD  %d\n", progname, se->id);
1327 # endif
1328           {
1329             int j;
1330             for (j = 0; j < s->nsentences; j++)
1331               if (s->sentences[j] == se)
1332                 s->sentences[j] = 0;
1333             free_sentence (s, se);
1334           }
1335           break;
1336         default:
1337           abort();
1338           break;
1339         }
1340     }
1341 }
1342
1343
1344 static unsigned long
1345 fontglide_draw_metrics (state *s)
1346 {
1347   char txt[2];
1348   char *fn = (s->font_override ? s->font_override : "fixed");
1349   XFontStruct *font = XLoadQueryFont (s->dpy, fn);
1350   XCharStruct c, overall;
1351   int dir, ascent, descent;
1352   int x, y;
1353   GC gc;
1354   unsigned long red   = 0xFFFF0000;  /* so shoot me */
1355   unsigned long green = 0xFF00FF00;
1356   unsigned long blue  = 0xFF6666FF;
1357   int i;
1358
1359   txt[0] = s->debug_metrics_p;
1360   txt[1] = 0;
1361
1362   gc = XCreateGC (s->dpy, s->window, 0, 0);
1363   XSetFont (s->dpy, gc, font->fid);
1364
1365 #ifdef HAVE_COCOA
1366   jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1367 #endif
1368
1369   XTextExtents (font, txt, strlen(txt), 
1370                 &dir, &ascent, &descent, &overall);
1371   c = font->per_char[((unsigned char *) txt)[0] - font->min_char_or_byte2];
1372
1373   XClearWindow (s->dpy, s->window);
1374
1375   x = (s->xgwa.width  - overall.width) / 2;
1376   y = (s->xgwa.height - (2 * (ascent + descent))) / 2;
1377
1378   for (i = 0; i < 2; i++)
1379     {
1380       XCharStruct cc = (i == 0 ? c : overall);
1381       int x1 = 20;
1382       int x2 = s->xgwa.width - 40;
1383       int x3 = s->xgwa.width;
1384
1385       XSetForeground (s->dpy, gc, red);
1386       XDrawLine (s->dpy, s->window, gc, 0, y - ascent,  x3, y - ascent);
1387       XDrawLine (s->dpy, s->window, gc, 0, y + descent, x3, y + descent);
1388
1389       XSetForeground (s->dpy, gc, green);
1390       /* ascent, baseline, descent */
1391       XDrawLine (s->dpy, s->window, gc, x1, y - cc.ascent,  x2, y - cc.ascent);
1392       XDrawLine (s->dpy, s->window, gc, x1, y, x2, y);
1393       XDrawLine (s->dpy, s->window, gc, x1, y + cc.descent, x2, y + cc.descent);
1394
1395       /* origin, width */
1396       XSetForeground (s->dpy, gc, blue);
1397       XDrawLine (s->dpy, s->window, gc,
1398                  x, y - ascent  - 10,
1399                  x, y + descent + 10);
1400       XDrawLine (s->dpy, s->window, gc,
1401                  x + cc.width, y - ascent  - 10,
1402                  x + cc.width, y + descent + 10);
1403
1404       /* lbearing, rbearing */
1405       XSetForeground (s->dpy, gc, green);
1406       XDrawLine (s->dpy, s->window, gc,
1407                  x + cc.lbearing, y - ascent,
1408                  x + cc.lbearing, y + descent);
1409       XDrawLine (s->dpy, s->window, gc,
1410                  x + cc.rbearing, y - ascent,
1411                  x + cc.rbearing, y + descent);
1412
1413       XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1414       XDrawString (s->dpy, s->window, gc, x, y, txt, strlen(txt));
1415
1416       y += (ascent + descent) * 2;
1417     }
1418
1419   XFreeGC (s->dpy, gc);
1420   XFreeFont (s->dpy, font);
1421   return s->frame_delay;
1422 }
1423
1424
1425 /* Render all the words to the screen, and run the animation one step.
1426    Clear screen first, swap buffers after.
1427  */
1428 static unsigned long
1429 fontglide_draw (Display *dpy, Window window, void *closure)
1430 {
1431   state *s = (state *) closure;
1432   int i;
1433
1434   if (s->debug_metrics_p)
1435     return fontglide_draw_metrics (closure);
1436
1437   if (s->spawn_p)
1438     more_sentences (s);
1439
1440   if (!s->trails_p)
1441     XFillRectangle (s->dpy, s->b, s->bg_gc,
1442                     0, 0, s->xgwa.width, s->xgwa.height);
1443
1444   for (i = 0; i < s->nsentences; i++)
1445     draw_sentence (s, s->sentences[i]);
1446
1447 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1448   if (s->backb)
1449     {
1450       XdbeSwapInfo info[1];
1451       info[0].swap_window = s->window;
1452       info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1453       XdbeSwapBuffers (s->dpy, info, 1);
1454     }
1455   else
1456 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1457   if (s->dbuf)
1458     {
1459       XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1460                  0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1461     }
1462
1463   return s->frame_delay;
1464 }
1465
1466
1467
1468 \f
1469 /* Subprocess.
1470    (This bit mostly cribbed from phosphor.c)
1471  */
1472
1473 static void
1474 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1475 {
1476   /* state *s = (state *) closure; */
1477   /* s->input_available_p = True; */
1478 }
1479
1480
1481 static void
1482 launch_text_generator (state *s)
1483 {
1484   char *oprogram = get_string_resource (s->dpy, "program", "Program");
1485   char *program = (char *) malloc (strlen (oprogram) + 10);
1486   strcpy (program, "( ");
1487   strcat (program, oprogram);
1488   strcat (program, " ) 2>&1");
1489
1490   if (s->debug_p)
1491     fprintf (stderr, "%s: forking: %s\n", progname, program);
1492
1493   if ((s->pipe = popen (program, "r")))
1494     {
1495       s->pipe_id =
1496         XtAppAddInput (XtDisplayToApplicationContext (s->dpy), 
1497                        fileno (s->pipe),
1498                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
1499                        subproc_cb, (XtPointer) s);
1500     }
1501   else
1502     {
1503       char buf[255];
1504       sprintf (buf, "%.100s: %.100s", progname, program);
1505       perror (buf);
1506     }
1507
1508   free(oprogram);
1509   free(program);
1510 }
1511
1512
1513 static void
1514 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1515 {
1516   state *s = (state *) closure;
1517   if (!s->timer_id) abort();
1518   s->timer_id = 0;
1519   launch_text_generator (s);
1520 }
1521
1522
1523 /* whether there is data available to be read on the file descriptor
1524  */
1525 static int
1526 input_available_p (int fd)
1527 {
1528   struct timeval tv = { 0, };
1529   fd_set fds;
1530 # if 0
1531   /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1532   FD_ZERO (&fds);
1533 # else
1534   memset (&fds, 0, sizeof(fds));
1535 # endif
1536   FD_SET (fd, &fds);
1537   return select (fd+1, &fds, NULL, NULL, &tv);
1538 }
1539
1540
1541 /* When the subprocess has generated some output, this reads as much as it
1542    can into s->buf at s->buf_tail.
1543  */
1544 static void
1545 drain_input (state *s)
1546 {
1547   XtAppContext app = XtDisplayToApplicationContext (s->dpy);
1548
1549   if (! s->pipe) return;
1550
1551   /* if (! s->input_available_p) return; */
1552   /* s->input_available_p = False; */
1553
1554   if (! input_available_p (fileno (s->pipe)))
1555     return;
1556
1557
1558   if (s->buf_tail < sizeof(s->buf) - 2)
1559     {
1560       int target = sizeof(s->buf) - s->buf_tail - 2;
1561       int n;
1562       n = read (fileno (s->pipe),
1563                 (void *) (s->buf + s->buf_tail),
1564                 target);
1565       if (n > 0)
1566         {
1567           s->buf_tail += n;
1568           s->buf[s->buf_tail] = 0;
1569         }
1570       else
1571         {
1572           XtRemoveInput (s->pipe_id);
1573           s->pipe_id = 0;
1574           pclose (s->pipe);
1575           s->pipe = 0;
1576
1577           /* If the process didn't print a terminating newline, add one. */
1578           if (s->buf_tail > 1 &&
1579               s->buf[s->buf_tail-1] != '\n')
1580             {
1581               s->buf[s->buf_tail++] = '\n';
1582               s->buf[s->buf_tail] = 0;
1583             }
1584
1585           /* Then add one more, to make sure there's a sentence break at EOF.
1586            */
1587           s->buf[s->buf_tail++] = '\n';
1588           s->buf[s->buf_tail] = 0;
1589
1590           /* Set up a timer to re-launch the subproc in a bit. */
1591           if (s->timer_id) abort();
1592           s->timer_id = XtAppAddTimeOut (app, s->subproc_relaunch_delay,
1593                                          relaunch_generator_timer,
1594                                          (XtPointer) s);
1595         }
1596     }
1597 }
1598
1599 \f
1600 /* Window setup and resource loading */
1601
1602 static void *
1603 fontglide_init (Display *dpy, Window window)
1604 {
1605   XGCValues gcv;
1606   state *s = (state *) calloc (1, sizeof(*s));
1607   s->dpy = dpy;
1608   s->window = window;
1609   s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
1610
1611   XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1612
1613   s->font_override = get_string_resource (dpy, "font", "Font");
1614   if (s->font_override && (!*s->font_override || *s->font_override == '('))
1615     s->font_override = 0;
1616
1617   s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
1618   s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
1619   if (s->border_width < 0 || s->border_width > 20)
1620     s->border_width = 1;
1621
1622   s->speed = get_float_resource (dpy, "speed", "Float");
1623   if (s->speed <= 0 || s->speed > 200)
1624     s->speed = 1;
1625
1626   s->linger = get_float_resource (dpy, "linger", "Float");
1627   if (s->linger <= 0 || s->linger > 200)
1628     s->linger = 1;
1629
1630   s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
1631   s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
1632   s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
1633                         ? 'y' : 0);
1634
1635   s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
1636
1637 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
1638   s->dbuf = False;
1639 # endif
1640
1641   if (s->trails_p) s->dbuf = False;  /* don't need it in this case */
1642
1643   {
1644     char *ss = get_string_resource (dpy, "mode", "Mode");
1645     if (!ss || !*ss || !strcasecmp (ss, "random"))
1646       s->mode = ((random() % 2) ? SCROLL : PAGE);
1647     else if (!strcasecmp (ss, "scroll"))
1648       s->mode = SCROLL;
1649     else if (!strcasecmp (ss, "page"))
1650       s->mode = PAGE;
1651     else
1652       {
1653         fprintf (stderr,
1654                 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1655                  progname, ss);
1656       }
1657   }
1658
1659   if (s->dbuf)
1660     {
1661 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1662       s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
1663       if (s->dbeclear_p)
1664         s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1665       else
1666         s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1667       s->backb = s->b;
1668 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1669
1670       if (!s->b)
1671         {
1672           s->ba = XCreatePixmap (s->dpy, s->window, 
1673                                  s->xgwa.width, s->xgwa.height,
1674                                  s->xgwa.depth);
1675           s->b = s->ba;
1676         }
1677     }
1678   else
1679     {
1680       s->b = s->window;
1681     }
1682
1683   gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
1684                                        "background", "Background");
1685   s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
1686
1687   s->subproc_relaunch_delay = 2 * 1000;
1688
1689   if (! s->debug_metrics_p)
1690     launch_text_generator (s);
1691
1692   s->nsentences = 5; /* #### */
1693   s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1694   s->spawn_p = True;
1695
1696   return s;
1697 }
1698
1699
1700 static Bool
1701 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
1702 {
1703   state *s = (state *) closure;
1704
1705   if (! s->debug_metrics_p)
1706     return False;
1707   else if (event->xany.type == ButtonPress)
1708     {
1709       s->debug_metrics_p++;
1710       if (s->debug_metrics_p > 255)
1711         s->debug_metrics_p = ' ';
1712       else if (s->debug_metrics_p > 127 &&
1713                s->debug_metrics_p < 159)
1714         s->debug_metrics_p = 160;
1715       return True;
1716     }
1717   else if (event->xany.type == KeyPress)
1718     {
1719       KeySym keysym;
1720       char c = 0;
1721       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1722       if (c)
1723         s->debug_metrics_p = (unsigned char) c;
1724       return !!c;
1725     }
1726   else
1727     return False;
1728 }
1729
1730
1731 static void
1732 fontglide_reshape (Display *dpy, Window window, void *closure, 
1733                  unsigned int w, unsigned int h)
1734 {
1735   state *s = (state *) closure;
1736   XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1737
1738   if (s->dbuf && (s->ba))
1739     {
1740       XFreePixmap (s->dpy, s->ba);
1741       s->ba = XCreatePixmap (s->dpy, s->window, 
1742                              s->xgwa.width, s->xgwa.height,
1743                              s->xgwa.depth);
1744       XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0, 
1745                       s->xgwa.width, s->xgwa.height);
1746       s->b = s->ba;
1747     }
1748 }
1749
1750 static void
1751 fontglide_free (Display *dpy, Window window, void *closure)
1752 {
1753   state *s = (state *) closure;
1754
1755   if (s->pipe_id)
1756     XtRemoveInput (s->pipe_id);
1757   if (s->pipe)
1758     pclose (s->pipe);
1759   if (s->timer_id)
1760     XtRemoveTimeOut (s->timer_id);
1761
1762   /* #### there's more to free here */
1763
1764   free (s);
1765 }
1766
1767
1768 static const char *fontglide_defaults [] = {
1769   ".background:         #000000",
1770   ".foreground:         #DDDDDD",
1771   ".borderColor:        #555555",
1772   "*delay:              10000",
1773   "*program:            xscreensaver-text",
1774   "*mode:               random",
1775   ".font:               (default)",
1776   "*fontCharset:        iso8859-1",
1777   "*fontBorderWidth:    2",
1778   "*speed:              1.0",
1779   "*linger:             1.0",
1780   "*trails:             False",
1781   "*debug:              False",
1782   "*debugMetrics:       False",
1783   "*doubleBuffer:       True",
1784 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1785   "*useDBE:             True",
1786   "*useDBEClear:        True",
1787 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1788   0
1789 };
1790
1791 static XrmOptionDescRec fontglide_options [] = {
1792   { "-mode",            ".mode",            XrmoptionSepArg, 0 },
1793   { "-scroll",          ".mode",            XrmoptionNoArg, "scroll" },
1794   { "-page",            ".mode",            XrmoptionNoArg, "page"   },
1795   { "-random",          ".mode",            XrmoptionNoArg, "random" },
1796   { "-delay",           ".delay",           XrmoptionSepArg, 0 },
1797   { "-speed",           ".speed",           XrmoptionSepArg, 0 },
1798   { "-linger",          ".linger",          XrmoptionSepArg, 0 },
1799   { "-program",         ".program",         XrmoptionSepArg, 0 },
1800   { "-font",            ".font",            XrmoptionSepArg, 0 },
1801   { "-fn",              ".font",            XrmoptionSepArg, 0 },
1802   { "-bw",              ".fontBorderWidth", XrmoptionSepArg, 0 },
1803   { "-trails",          ".trails",          XrmoptionNoArg,  "True"  },
1804   { "-no-trails",       ".trails",          XrmoptionNoArg,  "False" },
1805   { "-db",              ".doubleBuffer",    XrmoptionNoArg,  "True"  },
1806   { "-no-db",           ".doubleBuffer",    XrmoptionNoArg,  "False" },
1807   { "-debug",           ".debug",           XrmoptionNoArg,  "True"  },
1808   { "-debug-metrics",   ".debugMetrics",    XrmoptionNoArg,  "True"  },
1809   { 0, 0, 0, 0 }
1810 };
1811
1812
1813 XSCREENSAVER_MODULE ("FontGlide", fontglide)