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