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