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