From http://www.jwz.org/xscreensaver/xscreensaver-5.16.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 #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
450   if (!txt)
451     return 0;
452
453   w = (word *) calloc (1, sizeof(*w));
454   XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
455
456   /* Leave a little more slack */
457   overall.lbearing -= (bw * 2);
458   overall.rbearing += (bw * 2);
459   overall.ascent   += (bw * 2);
460   overall.descent  += (bw * 2);
461
462   w->width    = overall.rbearing - overall.lbearing + bw + bw;
463   w->height   = overall.ascent   + overall.descent  + bw + bw;
464   w->ascent   = overall.ascent   + bw;
465   w->lbearing = overall.lbearing - bw;
466   w->rbearing = overall.width    + bw;
467
468 # if 0
469   /* The metrics on some fonts are strange -- e.g.,
470      "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
471      "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1" both have
472      an rbearing so wide that it looks like there are two spaces after
473      each letter.  If this character says it has an rbearing that is to
474      the right of its ink, ignore that.
475
476      #### Of course, this hack only helps when we're in `move_chars_p' mode
477           and drawing a char at a time -- when we draw the whole word at once,
478           XDrawString believes the bogus metrics and spaces the font out
479           crazily anyway.
480
481      Sigh, this causes some text to mis-render in, e.g.,
482      "-adobe-utopia-medium-i-normal--114-*-100-100-p-*-iso8859-1"
483      (in "ux", we need the rbearing on "r" or we get too much overlap.)
484    */
485   if (w->rbearing > w->width)
486     w->rbearing = w->width;
487 # endif /* 0 */
488
489   if (s->mode == SCROLL && !alloc_p) abort();
490
491   if (alloc_p)
492     {
493       int i, j;
494       XGCValues gcv;
495       GC gc0, gc1;
496
497       w->pixmap = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
498       w->mask   = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
499
500       gcv.font = se->font->fid;
501       gcv.foreground = 0L;
502       gcv.background = 1L;
503       gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
504                        &gcv);
505       gcv.foreground = 1L;
506       gcv.background = 0L;
507       gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
508                        &gcv);
509
510       XFillRectangle (s->dpy, w->mask,   gc0, 0, 0, w->width, w->height);
511       XFillRectangle (s->dpy, w->pixmap, gc0, 0, 0, w->width, w->height);
512
513       if (s->debug_p)
514         {
515           /* bounding box (behind the characters) */
516           XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
517                           0, 0, w->width-1, w->height-1);
518           XDrawRectangle (s->dpy, w->mask,   gc1,
519                           0, 0, w->width-1, w->height-1);
520         }
521
522       if (s->debug_p > 1)
523         {
524           /* bounding box (behind *each* character) */
525           char *ss;
526           int x = 0;
527           for (ss = txt; *ss; ss++)
528             {
529               XTextExtents (se->font, ss, 1, &dir, &ascent, &descent, &overall);
530               XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
531                               x, w->ascent - overall.ascent, 
532                               overall.width, 
533                               overall.ascent + overall.descent);
534               XDrawRectangle (s->dpy, w->mask,   gc1,
535                               x, w->ascent - overall.ascent, 
536                               overall.width,
537                               overall.ascent + overall.descent);
538
539               XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
540                               x - overall.lbearing, w->ascent - overall.ascent, 
541                               overall.rbearing, 
542                               overall.ascent + overall.descent);
543               XDrawRectangle (s->dpy, w->mask,   gc1,
544                               x - overall.lbearing, w->ascent - overall.ascent, 
545                               overall.rbearing,
546                               overall.ascent + overall.descent);
547
548
549               x += overall.width;
550             }
551         }
552
553       /* Draw foreground text */
554       XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
555                    txt, strlen(txt));
556
557       /* Cheesy hack to draw a border */
558       /* (I should be able to do this in i*2 time instead of i*i time,
559          but I can't get it right, so fuck it.) */
560       XSetFunction (s->dpy, gc1, GXor);
561       for (i = -bw; i <= bw; i++)
562         for (j = -bw; j <= bw; j++)
563           XCopyArea (s->dpy, w->pixmap, w->mask, gc1,
564                      0, 0, w->width, w->height,
565                      i, j);
566
567       if (s->debug_p)
568         {
569           XSetFunction (s->dpy, gc1, GXcopy);
570           if (w->ascent != w->height)
571             {
572               /* baseline (on top of the characters) */
573               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
574                          0, w->ascent, w->width-1, w->ascent);
575               XDrawLine (s->dpy, w->mask,   gc1,
576                          0, w->ascent, w->width-1, w->ascent);
577             }
578
579           if (w->lbearing != 0)
580             {
581               /* left edge of charcell */
582               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
583                          w->lbearing, 0, w->lbearing, w->height-1);
584               XDrawLine (s->dpy, w->mask,   gc1,
585                          w->lbearing, 0, w->lbearing, w->height-1);
586             }
587
588           if (w->rbearing != w->width)
589             {
590               /* right edge of charcell */
591               XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
592                          w->rbearing, 0, w->rbearing, w->height-1);
593               XDrawLine (s->dpy, w->mask,   gc1,
594                          w->rbearing, 0, w->rbearing, w->height-1);
595             }
596         }
597
598       XFreeGC (s->dpy, gc0);
599       XFreeGC (s->dpy, gc1);
600     }
601
602   w->text = txt;
603   return w;
604 }
605
606
607 static void
608 free_word (state *s, word *w)
609 {
610   if (w->text)   free (w->text);
611   if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
612   if (w->mask)   XFreePixmap (s->dpy, w->mask);
613 }
614
615
616 static sentence *
617 new_sentence (state *st, state *s)
618 {
619   XGCValues gcv;
620   sentence *se = (sentence *) calloc (1, sizeof (*se));
621   se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
622   se->anim_state = IN;
623   se->id = ++st->id_tick;
624   return se;
625 }
626
627
628 static void
629 free_sentence (state *s, sentence *se)
630 {
631   int i;
632   for (i = 0; i < se->nwords; i++)
633     free_word (s, se->words[i]);
634   if (se->words) free (se->words);
635
636   if (se->fg.flags)
637     XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
638   if (se->bg.flags)
639     XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
640
641   if (se->font_name) free (se->font_name);
642   if (se->font) XFreeFont (s->dpy, se->font);
643   if (se->fg_gc) XFreeGC (s->dpy, se->fg_gc);
644
645   free (se);
646 }
647
648
649 /* free the word, and put its text back at the front of the input queue,
650    to be read next time. */
651 static void
652 unread_word (state *s, word *w)
653 {
654   if (unread_word_text)
655     abort();
656   unread_word_text = w->text;
657   w->text = 0;
658   free_word (s, w);
659 }
660
661
662 /* Divide each of the words in the sentence into one character words,
663    without changing the positions of those characters.
664  */
665 static void
666 split_words (state *s, sentence *se)
667 {
668   word **words2;
669   int nwords2 = 0;
670   int i, j;
671   for (i = 0; i < se->nwords; i++)
672     nwords2 += strlen (se->words[i]->text);
673
674   words2 = (word **) calloc (nwords2, sizeof(*words2));
675
676   for (i = 0, j = 0; i < se->nwords; i++)
677     {
678       word *ow = se->words[i];
679       int L = strlen (ow->text);
680       int k;
681
682       int x  = ow->x;
683       int y  = ow->y;
684       int sx = ow->start_x;
685       int sy = ow->start_y;
686       int tx = ow->target_x;
687       int ty = ow->target_y;
688
689       for (k = 0; k < L; k++)
690         {
691           char *t2 = malloc (2);
692           word *w2;
693           int xoff, yoff;
694
695           t2[0] = ow->text[k];
696           t2[1] = 0;
697           w2 = new_word (s, se, t2, True);
698           words2[j++] = w2;
699
700           xoff = (w2->lbearing - ow->lbearing);
701           yoff = (ow->ascent - w2->ascent);
702
703           w2->x        = x  + xoff;
704           w2->y        = y  + yoff;
705           w2->start_x  = sx + xoff;
706           w2->start_y  = sy + yoff;
707           w2->target_x = tx + xoff;
708           w2->target_y = ty + yoff;
709
710           x  += w2->rbearing;
711           sx += w2->rbearing;
712           tx += w2->rbearing;
713         }
714
715       free_word (s, ow);
716       se->words[i] = 0;
717     }
718   free (se->words);
719
720   se->words = words2;
721   se->nwords = nwords2;
722 }
723
724
725 /* Set the source or destination position of the words to be somewhere
726    off screen.
727  */
728 static void
729 scatter_sentence (state *s, sentence *se)
730 {
731   int i = 0;
732   int off = 100;
733
734   int flock_p = ((random() % 4) == 0);
735   int mode = (flock_p ? (random() % 12) : 0);
736
737   for (i = 0; i < se->nwords; i++)
738     {
739       word *w = se->words[i];
740       int x, y;
741       int r = (flock_p ? mode : (random() % 4));
742       switch (r)
743         {
744           /* random positions on the edges */
745
746         case 0:
747           x = -off - w->width;
748           y = random() % s->xgwa.height;
749           break;
750         case 1:
751           x = off + s->xgwa.width;
752           y = random() % s->xgwa.height;
753           break;
754         case 2:
755           x = random() % s->xgwa.width;
756           y = -off - w->height;
757           break;
758         case 3:
759           x = random() % s->xgwa.width;
760           y = off + s->xgwa.height;
761           break;
762
763           /* straight towards the edges */
764
765         case 4:
766           x = -off - w->width;
767           y = w->target_y;
768           break;
769         case 5:
770           x = off + s->xgwa.width;
771           y = w->target_y;
772           break;
773         case 6:
774           x = w->target_x;
775           y = -off - w->height;
776           break;
777         case 7:
778           x = w->target_x;
779           y = off + s->xgwa.height;
780           break;
781
782           /* corners */
783
784         case 8:
785           x = -off - w->width;
786           y = -off - w->height;
787           break;
788         case 9:
789           x = -off - w->width;
790           y =  off + s->xgwa.height;
791           break;
792         case 10:
793           x =  off + s->xgwa.width;
794           y =  off + s->xgwa.height;
795           break;
796         case 11:
797           x =  off + s->xgwa.width;
798           y = -off - w->height;
799           break;
800
801         default:
802           abort();
803           break;
804         }
805
806       if (se->anim_state == IN)
807         {
808           w->start_x = x;
809           w->start_y = y;
810         }
811       else
812         {
813           w->start_x = w->x;
814           w->start_y = w->y;
815           w->target_x = x;
816           w->target_y = y;
817         }
818
819       w->nticks = ((100 + ((random() % 140) +
820                            (random() % 140) +
821                            (random() % 140)))
822                    / s->speed);
823       if (w->nticks < 2)
824         w->nticks = 2;
825       w->tick = 0;
826     }
827 }
828
829
830 /* Set the source position of the words to be off the right side,
831    and the destination to be off the left side.
832  */
833 static void
834 aim_sentence (state *s, sentence *se)
835 {
836   int i = 0;
837   int nticks;
838   int yoff = 0;
839
840   if (se->nwords <= 0) abort();
841
842   /* Have the sentence shift up or down a little bit; not too far, and
843      never let it fall off the top or bottom of the screen before its
844      last character has reached the left edge.
845    */
846   for (i = 0; i < 10; i++)
847     {
848       int ty = random() % (s->xgwa.height - se->words[0]->ascent);
849       yoff = ty - se->words[0]->target_y;
850       if (yoff < s->xgwa.height/3)  /* this one is ok */
851         break;
852     }
853
854   for (i = 0; i < se->nwords; i++)
855     {
856       word *w = se->words[i];
857       w->start_x   = w->target_x + s->xgwa.width;
858       w->target_x -= se->width;
859       w->start_y   = w->target_y;
860       w->target_y += yoff;
861     }
862
863   nticks = ((se->words[0]->start_x - se->words[0]->target_x)
864             / (s->speed * 10));
865   nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
866
867   if (nticks < 2)
868     nticks = 2;
869
870   for (i = 0; i < se->nwords; i++)
871     {
872       word *w = se->words[i];
873       w->nticks = nticks;
874       w->tick = 0;
875     }
876 }
877
878
879 /* Randomize the order of the words in the list (since that changes
880    which ones are "on top".)
881  */
882 static void
883 shuffle_words (state *s, sentence *se)
884 {
885   int i;
886   for (i = 0; i < se->nwords-1; i++)
887     {
888       int j = i + (random() % (se->nwords - i));
889       word *swap = se->words[i];
890       se->words[i] = se->words[j];
891       se->words[j] = swap;
892     }
893 }
894
895
896 /* qsort comparitor */
897 static int
898 cmp_sentences (const void *aa, const void *bb)
899 {
900   const sentence *a = *(sentence **) aa;
901   const sentence *b = *(sentence **) bb;
902   return ((a ? a->id : 999999) - (b ? b->id : 999999));
903 }
904
905
906 /* Sort the sentences by id, so that sentences added later are on top.
907  */
908 static void
909 sort_sentences (state *s)
910 {
911   qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
912 }
913
914
915 /* Re-pick the colors of the text and border
916  */
917 static void
918 recolor (state *s, sentence *se)
919 {
920   if (se->fg.flags)
921     XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
922   if (se->bg.flags)
923     XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
924
925   se->fg.flags  = DoRed|DoGreen|DoBlue;
926   se->bg.flags  = DoRed|DoGreen|DoBlue;
927
928   switch (random() % 2)
929     {
930     case 0:   /* bright fg, dim bg */
931       se->fg.red    = (random() % 0x8888) + 0x8888;
932       se->fg.green  = (random() % 0x8888) + 0x8888;
933       se->fg.blue   = (random() % 0x8888) + 0x8888;
934       se->bg.red    = (random() % 0x5555);
935       se->bg.green  = (random() % 0x5555);
936       se->bg.blue   = (random() % 0x5555);
937       break;
938
939     case 1:   /* bright bg, dim fg */
940       se->fg.red    = (random() % 0x4444);
941       se->fg.green  = (random() % 0x4444);
942       se->fg.blue   = (random() % 0x4444);
943       se->bg.red    = (random() % 0x4444) + 0xCCCC;
944       se->bg.green  = (random() % 0x4444) + 0xCCCC;
945       se->bg.blue   = (random() % 0x4444) + 0xCCCC;
946       break;
947
948     default:
949       abort();
950       break;
951     }
952
953   if (s->debug_p)
954     se->dark_p = (se->fg.red*2 + se->fg.green*3 + se->fg.blue <
955                   se->bg.red*2 + se->bg.green*3 + se->bg.blue);
956
957   if (XAllocColor (s->dpy, s->xgwa.colormap, &se->fg))
958     XSetForeground (s->dpy, se->fg_gc, se->fg.pixel);
959   if (XAllocColor (s->dpy, s->xgwa.colormap, &se->bg))
960     XSetBackground (s->dpy, se->fg_gc, se->bg.pixel);
961 }
962
963
964 static void
965 align_line (state *s, sentence *se, int line_start, int x, int right)
966 {
967   int off, j;
968   switch (se->alignment)
969     {
970     case LEFT:   off = 0;               break;
971     case CENTER: off = (right - x) / 2; break;
972     case RIGHT:  off = (right - x);     break;
973     default:     abort();               break;
974     }
975
976   if (off != 0)
977     for (j = line_start; j < se->nwords; j++)
978       se->words[j]->target_x += off;
979 }
980
981
982 /* Fill the sentence with new words: in "page" mode, fills the page
983    with text; in "scroll" mode, just makes one long horizontal sentence.
984    The sentence might have *no* words in it, if no text is currently
985    available.
986  */
987 static void
988 populate_sentence (state *s, sentence *se)
989 {
990   int i = 0;
991   int left, right, top, x, y;
992   int space = 0;
993   int line_start = 0;
994   Bool done = False;
995
996   int array_size = 100;
997
998   se->move_chars_p = (s->mode == SCROLL ? False :
999                       (random() % 3) ? False : True);
1000   se->alignment = (random() % 3);
1001
1002   recolor (s, se);
1003
1004   if (se->words)
1005     {
1006       for (i = 0; i < se->nwords; i++)
1007         free_word (s, se->words[i]);
1008       free (se->words);
1009     }
1010
1011   se->words = (word **) calloc (array_size, sizeof(*se->words));
1012   se->nwords = 0;
1013
1014   switch (s->mode)
1015     {
1016     case PAGE:
1017       left  = random() % (s->xgwa.width / 3);
1018       right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1019       top = random() % (s->xgwa.height * 2 / 3);
1020       break;
1021     case SCROLL:
1022       left = 0;
1023       right = s->xgwa.width;
1024       top = random() % s->xgwa.height;
1025       break;
1026     default:
1027       abort();
1028       break;
1029     }
1030
1031   x = left;
1032   y = top;
1033
1034   while (!done)
1035     {
1036       char *txt = get_word_text (s);
1037       word *w;
1038       if (!txt)
1039         {
1040           if (se->nwords == 0)
1041             return;             /* If the stream is empty, bail. */
1042           else
1043             break;              /* If EOF after some words, end of sentence. */
1044         }
1045
1046       if (! se->font)           /* Got a word: need a font now */
1047         {
1048           pick_font (s, se);
1049           if (y < se->font->ascent)
1050             y += se->font->ascent;
1051           space = XTextWidth (se->font, " ", 1);
1052         }
1053
1054       w = new_word (s, se, txt, !se->move_chars_p);
1055
1056       /* If we have a few words, let punctuation terminate the sentence:
1057          stop gathering more words if the last word ends in a period, etc. */
1058       if (se->nwords >= 4)
1059         {
1060           char c = w->text[strlen(w->text)-1];
1061           if (c == '.' || c == '?' || c == '!')
1062             done = True;
1063         }
1064
1065       /* If the sentence is kind of long already, terminate at commas, etc. */
1066       if (se->nwords >= 12)
1067         {
1068           char c = w->text[strlen(w->text)-1];
1069           if (c == ',' || c == ';' || c == ':' || c == '-' ||
1070               c == ')' || c == ']' || c == '}')
1071             done = True;
1072         }
1073
1074       if (se->nwords >= 25)  /* ok that's just about enough out of you */
1075         done = True;
1076
1077       if (s->mode == PAGE &&
1078           x + w->rbearing > right)                      /* wrap line */
1079         {
1080           align_line (s, se, line_start, x, right);
1081           line_start = se->nwords;
1082
1083           x = left;
1084           y += se->font->ascent;
1085
1086           /* If we're close to the bottom of the screen, stop, and 
1087              unread the current word.  (But not if this is the first
1088              word, otherwise we might just get stuck on it.)
1089            */
1090           if (se->nwords > 0 &&
1091               y + se->font->ascent > s->xgwa.height)
1092             {
1093               unread_word (s, w);
1094               /* done = True; */
1095               break;
1096             }
1097         }
1098
1099       w->target_x = x + w->lbearing;
1100       w->target_y = y - w->ascent;
1101
1102       x += w->rbearing + space;
1103       se->width = x;
1104
1105       if (se->nwords >= (array_size - 1))
1106         {
1107           array_size += 100;
1108           se->words = (word **) realloc (se->words,
1109                                          array_size * sizeof(*se->words));
1110           if (!se->words)
1111             {
1112               fprintf (stderr, "%s: out of memory (%d words)\n",
1113                        progname, array_size);
1114               exit (1);
1115             }
1116         }
1117
1118       se->words[se->nwords++] = w;
1119     }
1120
1121   se->width -= space;
1122
1123   switch (s->mode)
1124     {
1125     case PAGE:
1126       align_line (s, se, line_start, x, right);
1127       if (se->move_chars_p)
1128         split_words (s, se);
1129       scatter_sentence (s, se);
1130       shuffle_words (s, se);
1131       break;
1132     case SCROLL:
1133       aim_sentence (s, se);
1134       break;
1135     default:
1136       abort();
1137       break;
1138     }
1139
1140 # ifdef DEBUG
1141   if (s->debug_p)
1142     {
1143       fprintf (stderr, "%s: sentence %d:", progname, se->id);
1144       for (i = 0; i < se->nwords; i++)
1145         fprintf (stderr, " %s", se->words[i]->text);
1146       fprintf (stderr, "\n");
1147     }
1148 # endif
1149 }
1150
1151
1152 /* Render a single word object to the screen.
1153  */
1154 static void
1155 draw_word (state *s, sentence *se, word *w)
1156 {
1157   if (! w->pixmap) return;
1158
1159   if (w->x + w->width < 0 ||
1160       w->y + w->height < 0 ||
1161       w->x > s->xgwa.width ||
1162       w->y > s->xgwa.height)
1163     return;
1164
1165   XSetClipMask (s->dpy, se->fg_gc, w->mask);
1166   XSetClipOrigin (s->dpy, se->fg_gc, w->x, w->y);
1167   XCopyPlane (s->dpy, w->pixmap, s->b, se->fg_gc,
1168               0, 0, w->width, w->height,
1169               w->x, w->y,
1170               1L);
1171 }
1172
1173
1174 /* If there is room for more sentences, add one.
1175  */
1176 static void
1177 more_sentences (state *s)
1178 {
1179   int i;
1180   Bool any = False;
1181   for (i = 0; i < s->nsentences; i++)
1182     {
1183       sentence *se = s->sentences[i];
1184       if (! se)
1185         {
1186           se = new_sentence (s, s);
1187           populate_sentence (s, se);
1188           if (se->nwords > 0)
1189             s->spawn_p = False, any = True;
1190           else
1191             {
1192               free_sentence (s, se);
1193               se = 0;
1194             }
1195           s->sentences[i] = se;
1196           if (se)
1197             s->latest_sentence = se->id;
1198           break;
1199         }
1200     }
1201
1202   if (any) sort_sentences (s);
1203 }
1204
1205
1206 /* Render all the words to the screen, and run the animation one step.
1207  */
1208 static void
1209 draw_sentence (state *s, sentence *se)
1210 {
1211   int i;
1212   Bool moved = False;
1213
1214   if (! se) return;
1215
1216   for (i = 0; i < se->nwords; i++)
1217     {
1218       word *w = se->words[i];
1219
1220       switch (s->mode)
1221         {
1222         case PAGE:
1223           if (se->anim_state != PAUSE &&
1224               w->tick <= w->nticks)
1225             {
1226               int dx = w->target_x - w->start_x;
1227               int dy = w->target_y - w->start_y;
1228               double r = sin (w->tick * M_PI / (2 * w->nticks));
1229               w->x = w->start_x + (dx * r);
1230               w->y = w->start_y + (dy * r);
1231
1232               w->tick++;
1233               if (se->anim_state == OUT && s->mode == PAGE)
1234                 w->tick++;  /* go out faster */
1235               moved = True;
1236             }
1237           break;
1238         case SCROLL:
1239           {
1240             int dx = w->target_x - w->start_x;
1241             int dy = w->target_y - w->start_y;
1242             double r = (double) w->tick / w->nticks;
1243             w->x = w->start_x + (dx * r);
1244             w->y = w->start_y + (dy * r);
1245             w->tick++;
1246             moved = (w->tick <= w->nticks);
1247
1248             /* Launch a new sentence when:
1249                - the front of this sentence is almost off the left edge;
1250                - the end of this sentence is almost on screen.
1251                - or, randomly
1252              */
1253             if (se->anim_state != OUT &&
1254                 i == 0 &&
1255                 se->id == s->latest_sentence)
1256               {
1257                 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1258                               w->x + se->width < (s->xgwa.width * 2.1));
1259                 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1260
1261                 if (new_p || rand_p)
1262                   {
1263                     se->anim_state = OUT;
1264                     s->spawn_p = True;
1265 # ifdef DEBUG
1266                     if (s->debug_p)
1267                       fprintf (stderr, "%s: OUT   %d (x2 = %d%s)\n",
1268                                progname, se->id,
1269                                se->words[0]->x + se->width,
1270                                rand_p ? " randomly" : "");
1271 # endif
1272                   }
1273               }
1274           }
1275           break;
1276         default:
1277           abort();
1278           break;
1279         }
1280
1281       draw_word (s, se, w);
1282     }
1283
1284   if (moved && se->anim_state == PAUSE)
1285     abort();
1286
1287   if (! moved)
1288     {
1289       switch (se->anim_state)
1290         {
1291         case IN:
1292           se->anim_state = PAUSE;
1293           se->pause_tick = (se->nwords * 7 * s->linger);
1294           if (se->move_chars_p)
1295             se->pause_tick /= 5;
1296           scatter_sentence (s, se);
1297           shuffle_words (s, se);
1298 # ifdef DEBUG
1299           if (s->debug_p)
1300             fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1301 # endif
1302           break;
1303         case PAUSE:
1304           if (--se->pause_tick <= 0)
1305             {
1306               se->anim_state = OUT;
1307               s->spawn_p = True;
1308 # ifdef DEBUG
1309               if (s->debug_p)
1310                 fprintf (stderr, "%s: OUT   %d\n", progname, se->id);
1311 # endif
1312             }
1313           break;
1314         case OUT:
1315 # ifdef DEBUG
1316           if (s->debug_p)
1317             fprintf (stderr, "%s: DEAD  %d\n", progname, se->id);
1318 # endif
1319           {
1320             int j;
1321             for (j = 0; j < s->nsentences; j++)
1322               if (s->sentences[j] == se)
1323                 s->sentences[j] = 0;
1324             free_sentence (s, se);
1325           }
1326           break;
1327         default:
1328           abort();
1329           break;
1330         }
1331     }
1332 }
1333
1334
1335 static unsigned long
1336 fontglide_draw_metrics (state *s)
1337 {
1338   char txt[2];
1339   char *fn = (s->font_override ? s->font_override : "fixed");
1340   XFontStruct *font = XLoadQueryFont (s->dpy, fn);
1341   XCharStruct c, overall;
1342   int dir, ascent, descent;
1343   int x, y;
1344   GC gc;
1345   unsigned long red   = 0xFFFF0000;  /* so shoot me */
1346   unsigned long green = 0xFF00FF00;
1347   unsigned long blue  = 0xFF6666FF;
1348   int i;
1349
1350   txt[0] = s->debug_metrics_p;
1351   txt[1] = 0;
1352
1353   gc = XCreateGC (s->dpy, s->window, 0, 0);
1354   XSetFont (s->dpy, gc, font->fid);
1355
1356 #ifdef HAVE_COCOA
1357   jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1358 #endif
1359
1360   XTextExtents (font, txt, strlen(txt), 
1361                 &dir, &ascent, &descent, &overall);
1362   c = font->per_char[((unsigned char *) txt)[0] - font->min_char_or_byte2];
1363
1364   XClearWindow (s->dpy, s->window);
1365
1366   x = (s->xgwa.width  - overall.width) / 2;
1367   y = (s->xgwa.height - (2 * (ascent + descent))) / 2;
1368
1369   for (i = 0; i < 2; i++)
1370     {
1371       XCharStruct cc = (i == 0 ? c : overall);
1372       int x1 = 20;
1373       int x2 = s->xgwa.width - 40;
1374       int x3 = s->xgwa.width;
1375
1376       XSetForeground (s->dpy, gc, red);
1377       XDrawLine (s->dpy, s->window, gc, 0, y - ascent,  x3, y - ascent);
1378       XDrawLine (s->dpy, s->window, gc, 0, y + descent, x3, y + descent);
1379
1380       XSetForeground (s->dpy, gc, green);
1381       /* ascent, baseline, descent */
1382       XDrawLine (s->dpy, s->window, gc, x1, y - cc.ascent,  x2, y - cc.ascent);
1383       XDrawLine (s->dpy, s->window, gc, x1, y, x2, y);
1384       XDrawLine (s->dpy, s->window, gc, x1, y + cc.descent, x2, y + cc.descent);
1385
1386       /* origin, width */
1387       XSetForeground (s->dpy, gc, blue);
1388       XDrawLine (s->dpy, s->window, gc,
1389                  x, y - ascent  - 10,
1390                  x, y + descent + 10);
1391       XDrawLine (s->dpy, s->window, gc,
1392                  x + cc.width, y - ascent  - 10,
1393                  x + cc.width, y + descent + 10);
1394
1395       /* lbearing, rbearing */
1396       XSetForeground (s->dpy, gc, green);
1397       XDrawLine (s->dpy, s->window, gc,
1398                  x + cc.lbearing, y - ascent,
1399                  x + cc.lbearing, y + descent);
1400       XDrawLine (s->dpy, s->window, gc,
1401                  x + cc.rbearing, y - ascent,
1402                  x + cc.rbearing, y + descent);
1403
1404       XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1405       XDrawString (s->dpy, s->window, gc, x, y, txt, strlen(txt));
1406
1407       y += (ascent + descent) * 2;
1408     }
1409
1410   XFreeGC (s->dpy, gc);
1411   XFreeFont (s->dpy, font);
1412   return s->frame_delay;
1413 }
1414
1415
1416 /* Render all the words to the screen, and run the animation one step.
1417    Clear screen first, swap buffers after.
1418  */
1419 static unsigned long
1420 fontglide_draw (Display *dpy, Window window, void *closure)
1421 {
1422   state *s = (state *) closure;
1423   int i;
1424
1425   if (s->debug_metrics_p)
1426     return fontglide_draw_metrics (closure);
1427
1428   if (s->spawn_p)
1429     more_sentences (s);
1430
1431   if (!s->trails_p)
1432     XFillRectangle (s->dpy, s->b, s->bg_gc,
1433                     0, 0, s->xgwa.width, s->xgwa.height);
1434
1435   for (i = 0; i < s->nsentences; i++)
1436     draw_sentence (s, s->sentences[i]);
1437
1438 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1439   if (s->backb)
1440     {
1441       XdbeSwapInfo info[1];
1442       info[0].swap_window = s->window;
1443       info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1444       XdbeSwapBuffers (s->dpy, info, 1);
1445     }
1446   else
1447 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1448   if (s->dbuf)
1449     {
1450       XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1451                  0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1452     }
1453
1454   return s->frame_delay;
1455 }
1456
1457
1458
1459 /* When the subprocess has generated some output, this reads as much as it
1460    can into s->buf at s->buf_tail.
1461  */
1462 static void
1463 drain_input (state *s)
1464 {
1465   while (s->buf_tail < sizeof(s->buf) - 2)
1466     {
1467       char c = textclient_getc (s->tc);
1468       if (c > 0)
1469         s->buf[s->buf_tail++] = c;
1470       else
1471         break;
1472     }
1473 }
1474
1475 \f
1476 /* Window setup and resource loading */
1477
1478 static void *
1479 fontglide_init (Display *dpy, Window window)
1480 {
1481   XGCValues gcv;
1482   state *s = (state *) calloc (1, sizeof(*s));
1483   s->dpy = dpy;
1484   s->window = window;
1485   s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
1486
1487   XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1488
1489   s->font_override = get_string_resource (dpy, "font", "Font");
1490   if (s->font_override && (!*s->font_override || *s->font_override == '('))
1491     s->font_override = 0;
1492
1493   s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
1494   s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
1495   if (s->border_width < 0 || s->border_width > 20)
1496     s->border_width = 1;
1497
1498   s->speed = get_float_resource (dpy, "speed", "Float");
1499   if (s->speed <= 0 || s->speed > 200)
1500     s->speed = 1;
1501
1502   s->linger = get_float_resource (dpy, "linger", "Float");
1503   if (s->linger <= 0 || s->linger > 200)
1504     s->linger = 1;
1505
1506   s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
1507   s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
1508   s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
1509                         ? 'y' : 0);
1510
1511   s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
1512
1513 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
1514   s->dbuf = False;
1515 # endif
1516
1517   if (s->trails_p) s->dbuf = False;  /* don't need it in this case */
1518
1519   {
1520     char *ss = get_string_resource (dpy, "mode", "Mode");
1521     if (!ss || !*ss || !strcasecmp (ss, "random"))
1522       s->mode = ((random() % 2) ? SCROLL : PAGE);
1523     else if (!strcasecmp (ss, "scroll"))
1524       s->mode = SCROLL;
1525     else if (!strcasecmp (ss, "page"))
1526       s->mode = PAGE;
1527     else
1528       {
1529         fprintf (stderr,
1530                 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1531                  progname, ss);
1532       }
1533   }
1534
1535   if (s->dbuf)
1536     {
1537 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1538       s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
1539       if (s->dbeclear_p)
1540         s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1541       else
1542         s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1543       s->backb = s->b;
1544 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1545
1546       if (!s->b)
1547         {
1548           s->ba = XCreatePixmap (s->dpy, s->window, 
1549                                  s->xgwa.width, s->xgwa.height,
1550                                  s->xgwa.depth);
1551           s->b = s->ba;
1552         }
1553     }
1554   else
1555     {
1556       s->b = s->window;
1557     }
1558
1559   gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
1560                                        "background", "Background");
1561   s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
1562
1563   s->nsentences = 5; /* #### */
1564   s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1565   s->spawn_p = True;
1566
1567   s->tc = textclient_open (dpy);
1568
1569   return s;
1570 }
1571
1572
1573 static Bool
1574 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
1575 {
1576   state *s = (state *) closure;
1577
1578   if (! s->debug_metrics_p)
1579     return False;
1580   else if (event->xany.type == ButtonPress)
1581     {
1582       s->debug_metrics_p++;
1583       if (s->debug_metrics_p > 255)
1584         s->debug_metrics_p = ' ';
1585       else if (s->debug_metrics_p > 127 &&
1586                s->debug_metrics_p < 159)
1587         s->debug_metrics_p = 160;
1588       return True;
1589     }
1590   else if (event->xany.type == KeyPress)
1591     {
1592       KeySym keysym;
1593       char c = 0;
1594       XLookupString (&event->xkey, &c, 1, &keysym, 0);
1595       if (c)
1596         s->debug_metrics_p = (unsigned char) c;
1597       return !!c;
1598     }
1599   else
1600     return False;
1601 }
1602
1603
1604 static void
1605 fontglide_reshape (Display *dpy, Window window, void *closure, 
1606                  unsigned int w, unsigned int h)
1607 {
1608   state *s = (state *) closure;
1609   XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1610
1611   if (s->dbuf && (s->ba))
1612     {
1613       XFreePixmap (s->dpy, s->ba);
1614       s->ba = XCreatePixmap (s->dpy, s->window, 
1615                              s->xgwa.width, s->xgwa.height,
1616                              s->xgwa.depth);
1617       XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0, 
1618                       s->xgwa.width, s->xgwa.height);
1619       s->b = s->ba;
1620     }
1621 }
1622
1623 static void
1624 fontglide_free (Display *dpy, Window window, void *closure)
1625 {
1626   state *s = (state *) closure;
1627   textclient_close (s->tc);
1628
1629   /* #### there's more to free here */
1630
1631   free (s);
1632 }
1633
1634
1635 static const char *fontglide_defaults [] = {
1636   ".background:         #000000",
1637   ".foreground:         #DDDDDD",
1638   ".borderColor:        #555555",
1639   "*delay:              10000",
1640   "*program:            xscreensaver-text",
1641   "*usePty:             false",
1642   "*mode:               random",
1643   ".font:               (default)",
1644   "*fontCharset:        iso8859-1",
1645   "*fontBorderWidth:    2",
1646   "*speed:              1.0",
1647   "*linger:             1.0",
1648   "*trails:             False",
1649   "*debug:              False",
1650   "*debugMetrics:       False",
1651   "*doubleBuffer:       True",
1652 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1653   "*useDBE:             True",
1654   "*useDBEClear:        True",
1655 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1656   0
1657 };
1658
1659 static XrmOptionDescRec fontglide_options [] = {
1660   { "-mode",            ".mode",            XrmoptionSepArg, 0 },
1661   { "-scroll",          ".mode",            XrmoptionNoArg, "scroll" },
1662   { "-page",            ".mode",            XrmoptionNoArg, "page"   },
1663   { "-random",          ".mode",            XrmoptionNoArg, "random" },
1664   { "-delay",           ".delay",           XrmoptionSepArg, 0 },
1665   { "-speed",           ".speed",           XrmoptionSepArg, 0 },
1666   { "-linger",          ".linger",          XrmoptionSepArg, 0 },
1667   { "-program",         ".program",         XrmoptionSepArg, 0 },
1668   { "-font",            ".font",            XrmoptionSepArg, 0 },
1669   { "-fn",              ".font",            XrmoptionSepArg, 0 },
1670   { "-bw",              ".fontBorderWidth", XrmoptionSepArg, 0 },
1671   { "-trails",          ".trails",          XrmoptionNoArg,  "True"  },
1672   { "-no-trails",       ".trails",          XrmoptionNoArg,  "False" },
1673   { "-db",              ".doubleBuffer",    XrmoptionNoArg,  "True"  },
1674   { "-no-db",           ".doubleBuffer",    XrmoptionNoArg,  "False" },
1675   { "-debug",           ".debug",           XrmoptionNoArg,  "True"  },
1676   { "-debug-metrics",   ".debugMetrics",    XrmoptionNoArg,  "True"  },
1677   { 0, 0, 0, 0 }
1678 };
1679
1680
1681 XSCREENSAVER_MODULE ("FontGlide", fontglide)