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