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