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