summaryrefslogtreecommitdiffstats
path: root/hacks/fontglide.c
diff options
context:
space:
mode:
authorSimon Rettberg2018-10-16 10:08:48 +0200
committerSimon Rettberg2018-10-16 10:08:48 +0200
commitd3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch)
treecbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /hacks/fontglide.c
downloadxscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip
Original 5.40
Diffstat (limited to 'hacks/fontglide.c')
-rw-r--r--hacks/fontglide.c2489
1 files changed, 2489 insertions, 0 deletions
diff --git a/hacks/fontglide.c b/hacks/fontglide.c
new file mode 100644
index 0000000..263393a
--- /dev/null
+++ b/hacks/fontglide.c
@@ -0,0 +1,2489 @@
+/* xscreensaver, Copyright (c) 2003-2017 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * fontglide -- reads text from a subprocess and puts it on the screen using
+ * large characters that glide in from the edges, assemble, then disperse.
+ * Requires a system with scalable fonts. (X's font handing sucks. A lot.)
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+
+/* If you turn on DEBUG, this program also masquerades as a tool for
+ debugging font metrics issues, which is probably only if interest
+ if you are doing active development on libjwxyz.a itself.
+ */
+/* #define DEBUG */
+
+#include <math.h>
+#include <time.h>
+
+#ifndef HAVE_JWXYZ
+# include <X11/Intrinsic.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "screenhack.h"
+#include "textclient.h"
+#include "xft.h"
+#include "utf8wc.h"
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+#include "xdbe.h"
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
+typedef struct {
+ char *text;
+
+ int x, y; /* Position of origin of first character in word */
+
+ /* These have the same meanings as in XCharStruct: */
+ int lbearing; /* origin to leftmost pixel */
+ int rbearing; /* origin to rightmost pixel */
+ int ascent; /* origin to topmost pixel */
+ int descent; /* origin to bottommost pixel */
+ int width; /* origin to next word's origin */
+
+ int nticks, tick;
+ int start_x, start_y;
+ int target_x, target_y;
+ Pixmap pixmap, mask;
+} word;
+
+
+typedef struct {
+ int id;
+ Bool dark_p;
+ Bool move_chars_p;
+ int width;
+
+ char *font_name;
+ GC fg_gc;
+ XftFont *xftfont;
+ XftColor xftcolor_fg, xftcolor_bg;
+
+ int nwords;
+ word **words;
+
+ enum { IN, PAUSE, OUT } anim_state;
+ enum { LEFT, CENTER, RIGHT } alignment;
+ int pause_tick;
+
+} sentence;
+
+
+typedef struct {
+ Display *dpy;
+ Window window;
+ XWindowAttributes xgwa;
+
+ Pixmap b, ba; /* double-buffer to reduce flicker */
+ GC bg_gc;
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+ XdbeBackBuffer backb;
+ Bool dbeclear_p;
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
+ Bool dbuf; /* Whether we're using double buffering. */
+
+ int border_width; /* size of the font outline */
+ char *charset; /* registry and encoding for font lookups */
+ double speed; /* frame rate multiplier */
+ double linger; /* multiplier for how long to leave words on screen */
+ Bool trails_p;
+ enum { PAGE, SCROLL, CHARS } mode;
+
+ char *font_override; /* if -font was specified on the cmd line */
+
+ char buf [40]; /* this only needs to be as big as one "word". */
+ int buf_tail;
+ Bool early_p;
+ time_t start_time;
+
+ int nsentences;
+ sentence **sentences;
+ Bool spawn_p; /* whether it is time to create a new sentence */
+ int latest_sentence;
+ unsigned long frame_delay;
+ int id_tick;
+ text_data *tc;
+
+# ifdef DEBUG
+ Bool debug_p;
+ unsigned long debug_metrics_p;
+ int debug_metrics_antialiasing_p;
+ int debug_scale;
+ unsigned entering_unicode_p; /* 0 = No, 1 = Just started, 2 = in progress */
+ XFontStruct *metrics_font1;
+ XFontStruct *metrics_font2;
+ XftFont *metrics_xftfont;
+ GC label_gc;
+ char *prev_font_name;
+ char *next_font_name;
+# endif /* DEBUG */
+
+} state;
+
+
+static void drain_input (state *s);
+
+
+static int
+pick_font_size (state *s)
+{
+ double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
+ int min, max, r, pixel;
+
+ min = scale * 24;
+ max = scale * 260;
+
+ if (min < 10) min = 10;
+ if (max < 30) max = 30;
+
+ r = ((max-min)/3)+1;
+
+ pixel = min + ((random() % r) + (random() % r) + (random() % r));
+
+ if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
+ pixel *= 1.5;
+
+ return pixel;
+}
+
+
+#ifdef HAVE_JWXYZ
+
+static char *
+append_font_name(Display *dpy, char *dest, const XFontStruct *font)
+{
+ int i;
+ for (i = 0; i != font->n_properties; ++i) {
+ if (font->properties[i].name == XA_FONT) {
+ const char *suffix = XGetAtomName (dpy, font->properties[i].card32);
+ strcpy(dest, suffix);
+ return dest + strlen(suffix);
+ }
+ }
+
+ dest[0] = '?';
+ dest[1] = 0;
+ return dest + 1;
+
+/*
+ float s;
+ const char *n = jwxyz_nativeFontName (font->fid, &s);
+ return dest + sprintf (dest, "%s %.1f", n, s);
+ */
+}
+
+#endif
+
+
+/* Finds the set of scalable fonts on the system; picks one;
+ and loads that font in a random pixel size.
+ Returns False if something went wrong.
+ */
+static Bool
+pick_font_1 (state *s, sentence *se)
+{
+ Bool ok = False;
+ char pattern[1024];
+ char pattern2[1024];
+
+#ifndef HAVE_JWXYZ /* real Xlib */
+ char **names = 0;
+ char **names2 = 0;
+ XFontStruct *info = 0;
+ int count = 0, count2 = 0;
+ int i;
+
+ if (se->xftfont)
+ {
+ XftFontClose (s->dpy, se->xftfont);
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
+ &se->xftcolor_fg);
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
+ &se->xftcolor_bg);
+
+ free (se->font_name);
+ se->xftfont = 0;
+ se->font_name = 0;
+ }
+
+ if (s->font_override)
+ sprintf (pattern, "%.200s", s->font_override);
+ else
+ sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
+ "*", /* foundry */
+ "*", /* family */
+ "*", /* weight */
+ "*", /* slant */
+ "*", /* swidth */
+ "*", /* adstyle */
+ "0", /* pixel size */
+ "0", /* point size */
+ "0", /* resolution x */
+ "0", /* resolution y */
+ "p", /* spacing */
+ "0", /* avg width */
+ s->charset); /* registry + encoding */
+
+ names = XListFonts (s->dpy, pattern, 1000, &count);
+
+ if (count <= 0)
+ {
+ if (s->font_override)
+ fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
+ else
+ fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
+ progname, pattern);
+ exit (1);
+ }
+
+ i = random() % count;
+
+ names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
+ if (count2 <= 0)
+ {
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: pattern %s\n"
+ " gave unusable %s\n\n",
+ progname, pattern, names[i]);
+# endif /* DEBUG */
+ goto FAIL;
+ }
+
+ {
+ XFontStruct *font = &info[0];
+ unsigned long value = 0;
+ char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
+ unsigned long pixel=0, point=0, res_x=0, res_y=0;
+ char *spacing=0;
+ unsigned long avg_width=0;
+ char *registry=0, *encoding=0;
+ Atom a;
+ char *bogus = "\"?\"";
+
+# define STR(ATOM,VAR) \
+ bogus = (ATOM); \
+ a = XInternAtom (s->dpy, (ATOM), False); \
+ if (XGetFontProperty (font, a, &value)) \
+ VAR = XGetAtomName (s->dpy, value); \
+ else \
+ goto FAIL2
+
+# define INT(ATOM,VAR) \
+ bogus = (ATOM); \
+ a = XInternAtom (s->dpy, (ATOM), False); \
+ if (!XGetFontProperty (font, a, &VAR) || \
+ VAR > 9999) \
+ goto FAIL2
+
+ STR ("FOUNDRY", foundry);
+ STR ("FAMILY_NAME", family);
+ STR ("WEIGHT_NAME", weight);
+ STR ("SLANT", slant);
+ STR ("SETWIDTH_NAME", setwidth);
+ STR ("ADD_STYLE_NAME", add_style);
+ INT ("PIXEL_SIZE", pixel);
+ INT ("POINT_SIZE", point);
+ INT ("RESOLUTION_X", res_x);
+ INT ("RESOLUTION_Y", res_y);
+ STR ("SPACING", spacing);
+ INT ("AVERAGE_WIDTH", avg_width);
+ STR ("CHARSET_REGISTRY", registry);
+ STR ("CHARSET_ENCODING", encoding);
+
+#undef INT
+#undef STR
+
+ pixel = pick_font_size (s);
+
+#if 0
+ /* Occasionally change the aspect ratio of the font, by increasing
+ either the X or Y resolution (while leaving the other alone.)
+
+ #### Looks like this trick doesn't really work that well: the
+ metrics of the individual characters are ok, but the
+ overall font ascent comes out wrong (unscaled.)
+ */
+ if (! (random() % 8))
+ {
+ double n = 2.5 / 3;
+ double scale = 1 + (frand(n) + frand(n) + frand(n));
+ if (random() % 2)
+ res_x *= scale;
+ else
+ res_y *= scale;
+ }
+# endif
+
+ sprintf (pattern,
+ "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
+ foundry, family, weight, slant, setwidth, add_style,
+ pixel, "*", /* point, */
+ res_x, res_y, spacing,
+ "*", /* avg_width */
+ registry, encoding);
+ ok = True;
+
+ FAIL2:
+ if (!ok)
+ fprintf (stderr, "%s: font has bogus %s property: %s\n",
+ progname, bogus, names[i]);
+
+ if (foundry) XFree (foundry);
+ if (family) XFree (family);
+ if (weight) XFree (weight);
+ if (slant) XFree (slant);
+ if (setwidth) XFree (setwidth);
+ if (add_style) XFree (add_style);
+ if (spacing) XFree (spacing);
+ if (registry) XFree (registry);
+ if (encoding) XFree (encoding);
+ }
+
+ FAIL:
+
+ XFreeFontInfo (names2, info, count2);
+ XFreeFontNames (names);
+
+# else /* HAVE_JWXYZ */
+
+ if (s->font_override)
+ sprintf (pattern, "%.200s", s->font_override);
+ else
+ {
+ const char *family = "random";
+ const char *weight = ((random() % 2) ? "regular" : "bold");
+ const char *slant = ((random() % 2) ? "o" : "r");
+ int size = 10 * pick_font_size (s);
+ sprintf (pattern, "*-%s-%s-%s-*-*-*-%d-*", family, weight, slant, size);
+ }
+ ok = True;
+# endif /* HAVE_JWXYZ */
+
+ if (! ok) return False;
+
+ se->xftfont = XftFontOpenXlfd (s->dpy, screen_number (s->xgwa.screen),
+ pattern);
+
+ if (! se->xftfont)
+ {
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: unable to load font %s\n",
+ progname, pattern);
+#endif
+ return False;
+ }
+
+ strcpy (pattern2, pattern);
+# ifdef HAVE_JWXYZ
+ {
+ char *out = pattern2 + strlen(pattern2);
+ out[0] = ' ';
+ out[1] = '(';
+ out = append_font_name (s->dpy, out + 2, se->xftfont->xfont);
+ out[0] = ')';
+ out[1] = 0;
+ }
+# endif
+
+# ifdef DEBUG
+ if (s->prev_font_name) free (s->prev_font_name);
+ s->prev_font_name = s->next_font_name;
+ s->next_font_name = strdup (pattern2);
+# endif
+
+ /* Sometimes we get fonts with screwed up metrics. For example:
+ -b&h-lucida-medium-r-normal-sans-40-289-100-100-p-0-iso8859-1
+
+ When using XDrawString, XTextExtents and XTextExtents16, it is rendered
+ as a scaled-up bitmap font. The character M has rbearing 70, ascent 68
+ and width 78, which is correct for the glyph as rendered.
+
+ But when using XftDrawStringUtf8 and XftTextExtentsUtf8, it is rendered
+ at the original, smaller, un-scaled size, with rbearing 26, ascent 25
+ and... width 77!
+
+ So it's taking the *size* from the unscaled font, the *advancement* from
+ the scaled-up version, and then *not* actually scaling it up. Awesome.
+
+ So, after loading the font, measure the M, and if its advancement is more
+ than 20% larger than its rbearing, reject the font.
+
+ ------------------------------------------------------------------------
+
+ Some observations on this nonsense from Dave Odell:
+
+ 1. -*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1 normally
+ resolves to /usr/share/fonts/X11/100dpi/lutBS24-ISO8859-1.pcf.gz.
+
+ -*-lucidatypewriter-* is from the 'xfonts-100dpi' package in
+ Debian/Ubuntu. It's usually (54.46% of systems), but not always,
+ installed whenever an X.org server (57.96% of systems) is. It might
+ be a good idea for this and xfonts-75dpi to be recommended
+ dependencies of XScreenSaver in Debian, but that's neither here nor
+ there. https://qa.debian.org/popcon.php?package=xorg
+ https://qa.debian.org/popcon.php?package=xfonts-100dpi
+
+ 2. It normally resolves to the PCF font... but not always.
+
+ Fontconfig has /etc/fonts/conf.d/ (it's /opt/local/etc/fonts/conf.d/
+ with MacPorts) containing symlinks to configuration files. And both
+ Debian and Ubuntu normally has a 70-no-bitmaps.conf, installed as part
+ of the 'fontconfig-config' package. And the 70-no-bitmaps.conf
+ symlink... disables bitmap fonts.
+
+ Without bitmap fonts, I get DejaVu Sans.
+
+ 3. There's another symlink of interest here:
+ /etc/fonts/conf.d/10-scale-bitmap-fonts.conf. This adds space to the
+ right of glyphs of bitmap fonts when the requested size of the font is
+ larger than the actual bitmap font. Ubuntu and MacPorts has this one.
+
+ This specifically is causing text to have excessive character spacing.
+
+ (jwz asks: WHY WOULD ANYONE EVER WANT THIS BEHAVIOR?)
+
+ 4. Notice that I'm only talking about Debian and Ubuntu. Other distros
+ will probably have different symlinks in /etc/fonts/conf.d/. So yes,
+ this can be an issue on Linux as well as MacOS.
+ */
+ {
+ XGlyphInfo extents;
+ int rbearing, width;
+ float ratio;
+ float min = 0.8;
+
+ XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) "M", 1, &extents);
+ rbearing = extents.width - extents.x;
+ width = extents.xOff;
+ ratio = rbearing / (float) width;
+
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: M ratio %.2f (%d %d): %s\n", progname,
+ ratio, rbearing, width, pattern2);
+# endif
+
+ if (ratio < min && !s->font_override)
+ {
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: skipping font with broken metrics: %s\n",
+ progname, pattern2);
+# endif
+ return False;
+ }
+ }
+
+
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf(stderr, "%s: %s\n", progname, pattern2);
+# endif /* DEBUG */
+
+ se->font_name = strdup (pattern);
+ return True;
+}
+
+
+/* Finds the set of scalable fonts on the system; picks one;
+ and loads that font in a random pixel size.
+ */
+static void
+pick_font (state *s, sentence *se)
+{
+ int i;
+ for (i = 0; i < 50; i++)
+ if (pick_font_1 (s, se))
+ return;
+ fprintf (stderr, "%s: too many font-loading failures: giving up!\n",
+ progname);
+ exit (1);
+}
+
+
+static char *unread_word_text = 0;
+
+/* Returns a newly-allocated string with one word in it, or NULL if there
+ is no complete word available.
+ */
+static char *
+get_word_text (state *s)
+{
+ const char *start = s->buf;
+ const char *end;
+ char *result = 0;
+ int lfs = 0;
+
+ drain_input (s);
+
+ /* If we just launched, and haven't had any text yet, and it has been
+ more than 2 seconds since we launched, then push out "Loading..."
+ as our first text. So if the text source is speedy, just use that.
+ But if we'd display a blank screen for a while, give 'em something
+ to see.
+ */
+ if (s->early_p &&
+ !*s->buf &&
+ !unread_word_text &&
+ s->start_time < ((time ((time_t *) 0) - 2)))
+ {
+ unread_word_text = "Loading...";
+ s->early_p = False;
+ }
+
+ if (unread_word_text)
+ {
+ result = unread_word_text;
+ unread_word_text = 0;
+ return strdup (result);
+ }
+
+ /* Skip over whitespace at the beginning of the buffer,
+ and count up how many linebreaks we see while doing so.
+ */
+ while (*start &&
+ (*start == ' ' ||
+ *start == '\t' ||
+ *start == '\r' ||
+ *start == '\n'))
+ {
+ if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
+ lfs++;
+ start++;
+ }
+
+ end = start;
+
+ /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
+ to trigger a sentence break here.) */
+ if (lfs >= 2)
+ goto DONE;
+
+ /* Skip forward to the end of this word (find next whitespace.) */
+ while (*end &&
+ (! (*end == ' ' ||
+ *end == '\t' ||
+ *end == '\r' ||
+ *end == '\n')))
+ end++;
+
+ /* If we have a word, allocate a string for it */
+ if (end > start)
+ {
+ result = malloc ((end - start) + 1);
+ strncpy (result, start, (end-start));
+ result [end-start] = 0;
+ }
+
+ DONE:
+
+ /* Make room in the buffer by compressing out any bytes we've processed.
+ */
+ if (end > s->buf)
+ {
+ int n = end - s->buf;
+ memmove (s->buf, end, sizeof(s->buf) - n);
+ s->buf_tail -= n;
+ }
+
+ return result;
+}
+
+
+/* Returns a 1-bit pixmap of the same size as the drawable,
+ with a 0 wherever the drawable is black.
+ */
+static Pixmap
+make_mask (Screen *screen, Visual *visual, Drawable drawable)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ unsigned long black = BlackPixelOfScreen (screen);
+ Window r;
+ int x, y;
+ unsigned int w, h, bw, d;
+ XImage *out, *in;
+ Pixmap mask;
+ GC gc;
+
+ XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bw, &d);
+ in = XGetImage (dpy, drawable, 0, 0, w, h, ~0L, ZPixmap);
+ out = XCreateImage (dpy, visual, 1, XYPixmap, 0, 0, w, h, 8, 0);
+ out->data = (char *) malloc (h * out->bytes_per_line);
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++)
+ XPutPixel (out, x, y, (black != XGetPixel (in, x, y)));
+ mask = XCreatePixmap (dpy, drawable, w, h, 1L);
+ gc = XCreateGC (dpy, mask, 0, 0);
+ XPutImage (dpy, mask, gc, out, 0, 0, 0, 0, w, h);
+ XFreeGC (dpy, gc);
+ free (in->data);
+ free (out->data);
+ in->data = out->data = 0;
+ XDestroyImage (in);
+ XDestroyImage (out);
+ return mask;
+}
+
+
+/* Gets some random text, and creates a "word" object from it.
+ */
+static word *
+new_word (state *s, sentence *se, const char *txt, Bool alloc_p)
+{
+ word *w;
+ XGlyphInfo extents;
+ int bw = s->border_width;
+
+ if (!txt)
+ return 0;
+
+ w = (word *) calloc (1, sizeof(*w));
+ XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) txt, strlen(txt),
+ &extents);
+
+ w->lbearing = -extents.x;
+ w->rbearing = extents.width - extents.x;
+ w->ascent = extents.y;
+ w->descent = extents.height - extents.y;
+ w->width = extents.xOff;
+
+ w->lbearing -= bw;
+ w->rbearing += bw;
+ w->descent += bw;
+ w->ascent += bw;
+
+ if (s->mode == SCROLL && !alloc_p) abort();
+
+ if (alloc_p)
+ {
+ int i, j;
+ XGCValues gcv;
+ GC gc_fg, gc_bg, gc_black;
+ XftDraw *xftdraw;
+ int width = w->rbearing - w->lbearing;
+ int height = w->ascent + w->descent;
+
+ if (width <= 0) width = 1;
+ if (height <= 0) height = 1;
+
+ w->pixmap = XCreatePixmap (s->dpy, s->b, width, height, s->xgwa.depth);
+ xftdraw = XftDrawCreate (s->dpy, w->pixmap, s->xgwa.visual,
+ s->xgwa.colormap);
+
+ gcv.foreground = se->xftcolor_fg.pixel;
+ gc_fg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
+
+ gcv.foreground = se->xftcolor_bg.pixel;
+ gc_bg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
+
+ gcv.foreground = BlackPixelOfScreen (s->xgwa.screen);
+ gc_black = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
+
+ XFillRectangle (s->dpy, w->pixmap, gc_black, 0, 0, width, height);
+
+# ifdef DEBUG
+ if (s->debug_p)
+ {
+ /* bounding box (behind the characters) */
+ XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
+ 0, 0, width-1, height-1);
+ }
+# endif /* DEBUG */
+
+ /* Draw background text for border */
+ for (i = -bw; i <= bw; i++)
+ for (j = -bw; j <= bw; j++)
+ XftDrawStringUtf8 (xftdraw, &se->xftcolor_bg, se->xftfont,
+ -w->lbearing + i, w->ascent + j,
+ (FcChar8 *) txt, strlen(txt));
+
+ /* Draw foreground text */
+ XftDrawStringUtf8 (xftdraw, &se->xftcolor_fg, se->xftfont,
+ -w->lbearing, w->ascent,
+ (FcChar8 *) txt, strlen(txt));
+
+# ifdef DEBUG
+ if (s->debug_p)
+ {
+ if (w->ascent != height)
+ {
+ /* baseline (on top of the characters) */
+ XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
+ 0, w->ascent, width-1, w->ascent);
+ }
+
+ if (w->lbearing < 0)
+ {
+ /* left edge of charcell */
+ XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
+ -w->lbearing, 0,
+ -w->lbearing, height-1);
+ }
+
+ if (w->rbearing != w->width)
+ {
+ /* right edge of charcell */
+ XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
+ w->width - w->lbearing, 0,
+ w->width - w->lbearing, height-1);
+ }
+ }
+# endif /* DEBUG */
+
+ w->mask = make_mask (s->xgwa.screen, s->xgwa.visual, w->pixmap);
+
+ XftDrawDestroy (xftdraw);
+ XFreeGC (s->dpy, gc_fg);
+ XFreeGC (s->dpy, gc_bg);
+ XFreeGC (s->dpy, gc_black);
+ }
+
+ w->text = strdup (txt);
+ return w;
+}
+
+
+static void
+free_word (state *s, word *w)
+{
+ if (w->text) free (w->text);
+ if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
+ if (w->mask) XFreePixmap (s->dpy, w->mask);
+}
+
+
+static sentence *
+new_sentence (state *st, state *s)
+{
+ XGCValues gcv;
+ sentence *se = (sentence *) calloc (1, sizeof (*se));
+ se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
+ se->anim_state = IN;
+ se->id = ++st->id_tick;
+ return se;
+}
+
+
+static void
+free_sentence (state *s, sentence *se)
+{
+ int i;
+ for (i = 0; i < se->nwords; i++)
+ free_word (s, se->words[i]);
+ if (se->words)
+ free (se->words);
+ if (se->font_name)
+ free (se->font_name);
+ if (se->fg_gc)
+ XFreeGC (s->dpy, se->fg_gc);
+
+ if (se->xftfont)
+ {
+ XftFontClose (s->dpy, se->xftfont);
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
+ &se->xftcolor_fg);
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
+ &se->xftcolor_bg);
+ }
+
+ free (se);
+}
+
+
+/* free the word, and put its text back at the front of the input queue,
+ to be read next time. */
+static void
+unread_word (state *s, word *w)
+{
+ if (unread_word_text)
+ abort();
+ unread_word_text = w->text;
+ w->text = 0;
+ free_word (s, w);
+}
+
+
+/* Divide each of the words in the sentence into one character words,
+ without changing the positions of those characters.
+ */
+static void
+split_words (state *s, sentence *se)
+{
+ word **words2;
+ int nwords2 = 0;
+ int i, j;
+
+ char ***word_chars = (char ***) malloc (se->nwords * sizeof(*word_chars));
+ for (i = 0; i < se->nwords; i++)
+ {
+ int L;
+ word *ow = se->words[i];
+ word_chars[i] = utf8_split (ow->text, &L);
+ nwords2 += L;
+ }
+
+ words2 = (word **) calloc (nwords2, sizeof(*words2));
+
+ for (i = 0, j = 0; i < se->nwords; i++)
+ {
+ char **chars = word_chars[i];
+ word *parent = se->words[i];
+ int x = parent->x;
+ int y = parent->y;
+ int sx = parent->start_x;
+ int sy = parent->start_y;
+ int tx = parent->target_x;
+ int ty = parent->target_y;
+ int k;
+
+ for (k = 0; chars[k]; k++)
+ {
+ char *t2 = chars[k];
+ word *w2 = new_word (s, se, t2, True);
+ words2[j++] = w2;
+
+ w2->x = x;
+ w2->y = y;
+ w2->start_x = sx;
+ w2->start_y = sy;
+ w2->target_x = tx;
+ w2->target_y = ty;
+
+ x += w2->width;
+ sx += w2->width;
+ tx += w2->width;
+ }
+
+ /* This is not invariant when kerning is involved! */
+ /* if (x != parent->x + parent->width) abort(); */
+
+ free (chars); /* but we retain its contents */
+ free_word (s, parent);
+ }
+ if (j != nwords2) abort();
+ free (word_chars);
+ free (se->words);
+
+ se->words = words2;
+ se->nwords = nwords2;
+}
+
+
+/* Set the source or destination position of the words to be somewhere
+ off screen.
+ */
+static void
+scatter_sentence (state *s, sentence *se)
+{
+ int i = 0;
+ int off = s->border_width * 4 + 2;
+
+ int flock_p = ((random() % 4) == 0);
+ int mode = (flock_p ? (random() % 12) : 0);
+
+ for (i = 0; i < se->nwords; i++)
+ {
+ word *w = se->words[i];
+ int x, y;
+ int r = (flock_p ? mode : (random() % 4));
+ int left = -(off + w->rbearing);
+ int top = -(off + w->descent);
+ int right = off - w->lbearing + s->xgwa.width;
+ int bottom = off + w->ascent + s->xgwa.height;
+
+ switch (r) {
+ /* random positions on the edges */
+ case 0: x = left; y = random() % s->xgwa.height; break;
+ case 1: x = right; y = random() % s->xgwa.height; break;
+ case 2: x = random() % s->xgwa.width; y = top; break;
+ case 3: x = random() % s->xgwa.width; y = bottom; break;
+
+ /* straight towards the edges */
+ case 4: x = left; y = w->target_y; break;
+ case 5: x = right; y = w->target_y; break;
+ case 6: x = w->target_x; y = top; break;
+ case 7: x = w->target_x; y = bottom; break;
+
+ /* corners */
+ case 8: x = left; y = top; break;
+ case 9: x = left; y = bottom; break;
+ case 10: x = right; y = top; break;
+ case 11: x = right; y = bottom; break;
+
+ default: abort(); break;
+ }
+
+ if (se->anim_state == IN)
+ {
+ w->start_x = x;
+ w->start_y = y;
+ }
+ else
+ {
+ w->start_x = w->x;
+ w->start_y = w->y;
+ w->target_x = x;
+ w->target_y = y;
+ }
+
+ w->nticks = ((100 + ((random() % 140) +
+ (random() % 140) +
+ (random() % 140)))
+ / s->speed);
+ if (w->nticks < 2)
+ w->nticks = 2;
+ w->tick = 0;
+ }
+}
+
+
+/* Set the source position of the words to be off the right side,
+ and the destination to be off the left side.
+ */
+static void
+aim_sentence (state *s, sentence *se)
+{
+ int i = 0;
+ int nticks;
+ int yoff = 0;
+
+ if (se->nwords <= 0) abort();
+
+ /* Have the sentence shift up or down a little bit; not too far, and
+ never let it fall off the top or bottom of the screen before its
+ last character has reached the left edge.
+ */
+ for (i = 0; i < 10; i++)
+ {
+ int ty = random() % (s->xgwa.height - se->words[0]->ascent);
+ yoff = ty - se->words[0]->target_y;
+ if (yoff < s->xgwa.height/3) /* this one is ok */
+ break;
+ }
+
+ for (i = 0; i < se->nwords; i++)
+ {
+ word *w = se->words[i];
+ w->start_x = w->target_x + s->xgwa.width;
+ w->target_x -= se->width;
+ w->start_y = w->target_y;
+ w->target_y += yoff;
+ }
+
+ nticks = ((se->words[0]->start_x - se->words[0]->target_x)
+ / (s->speed * 7));
+ nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
+
+ if (nticks < 2)
+ nticks = 2;
+
+ for (i = 0; i < se->nwords; i++)
+ {
+ word *w = se->words[i];
+ w->nticks = nticks;
+ w->tick = 0;
+ }
+}
+
+
+/* Randomize the order of the words in the list (since that changes
+ which ones are "on top".)
+ */
+static void
+shuffle_words (state *s, sentence *se)
+{
+ int i;
+ for (i = 0; i < se->nwords-1; i++)
+ {
+ int j = i + (random() % (se->nwords - i));
+ word *swap = se->words[i];
+ se->words[i] = se->words[j];
+ se->words[j] = swap;
+ }
+}
+
+
+/* qsort comparitor */
+static int
+cmp_sentences (const void *aa, const void *bb)
+{
+ const sentence *a = *(sentence **) aa;
+ const sentence *b = *(sentence **) bb;
+ return ((a ? a->id : 999999) - (b ? b->id : 999999));
+}
+
+
+/* Sort the sentences by id, so that sentences added later are on top.
+ */
+static void
+sort_sentences (state *s)
+{
+ qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
+}
+
+
+/* Re-pick the colors of the text and border
+ */
+static void
+recolor (state *s, sentence *se)
+{
+ XRenderColor fg, bg;
+
+ fg.red = (random() % 0x5555) + 0xAAAA;
+ fg.green = (random() % 0x5555) + 0xAAAA;
+ fg.blue = (random() % 0x5555) + 0xAAAA;
+ fg.alpha = 0xFFFF;
+ bg.red = (random() % 0x5555);
+ bg.green = (random() % 0x5555);
+ bg.blue = (random() % 0x5555);
+ bg.alpha = 0xFFFF;
+ se->dark_p = False;
+
+ if (random() & 1)
+ {
+ XRenderColor swap = fg; fg = bg; bg = swap;
+ se->dark_p = True;
+ }
+
+ if (se->xftfont)
+ {
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
+ &se->xftcolor_fg);
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
+ &se->xftcolor_bg);
+ }
+
+ XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &fg,
+ &se->xftcolor_fg);
+ XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &bg,
+ &se->xftcolor_bg);
+}
+
+
+static void
+align_line (state *s, sentence *se, int line_start, int x, int right)
+{
+ int off, j;
+ switch (se->alignment)
+ {
+ case LEFT: off = 0; break;
+ case CENTER: off = (right - x) / 2; break;
+ case RIGHT: off = (right - x); break;
+ default: abort(); break;
+ }
+
+ if (off != 0)
+ for (j = line_start; j < se->nwords; j++)
+ se->words[j]->target_x += off;
+}
+
+
+/* Fill the sentence with new words: in "page" mode, fills the page
+ with text; in "scroll" mode, just makes one long horizontal sentence.
+ The sentence might have *no* words in it, if no text is currently
+ available.
+ */
+static void
+populate_sentence (state *s, sentence *se)
+{
+ int i = 0;
+ int left, right, top, x, y;
+ int space = 0;
+ int line_start = 0;
+ Bool done = False;
+
+ int array_size = 100;
+
+ se->move_chars_p = (s->mode == CHARS ? True :
+ s->mode == SCROLL ? False :
+ (random() % 3) ? False : True);
+ se->alignment = (random() % 3);
+
+ recolor (s, se);
+
+ if (se->words)
+ {
+ for (i = 0; i < se->nwords; i++)
+ free_word (s, se->words[i]);
+ free (se->words);
+ }
+
+ se->words = (word **) calloc (array_size, sizeof(*se->words));
+ se->nwords = 0;
+
+ switch (s->mode)
+ {
+ case PAGE:
+ case CHARS:
+ left = random() % (s->xgwa.width / 3);
+ right = s->xgwa.width - (random() % (s->xgwa.width / 3));
+ top = random() % (s->xgwa.height * 2 / 3);
+ break;
+ case SCROLL:
+ left = 0;
+ right = s->xgwa.width;
+ top = random() % s->xgwa.height;
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ x = left;
+ y = top;
+
+ while (!done)
+ {
+ char *txt = get_word_text (s);
+ word *w;
+ if (!txt)
+ {
+ if (se->nwords == 0)
+ return; /* If the stream is empty, bail. */
+ else
+ break; /* If EOF after some words, end of sentence. */
+ }
+
+ if (! se->xftfont) /* Got a word: need a font now */
+ {
+ XGlyphInfo extents;
+ pick_font (s, se);
+ if (y < se->xftfont->ascent)
+ y += se->xftfont->ascent;
+
+ /* Measure the space character to figure out how much room to
+ leave between words (since we don't actually render that.) */
+ XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) " ", 1,
+ &extents);
+ space = extents.xOff;
+ }
+
+ w = new_word (s, se, txt, !se->move_chars_p);
+ free (txt);
+ txt = 0;
+
+ /* If we have a few words, let punctuation terminate the sentence:
+ stop gathering more words if the last word ends in a period, etc. */
+ if (se->nwords >= 4)
+ {
+ char c = w->text[strlen(w->text)-1];
+ if (c == '.' || c == '?' || c == '!')
+ done = True;
+ }
+
+ /* If the sentence is kind of long already, terminate at commas, etc. */
+ if (se->nwords >= 12)
+ {
+ char c = w->text[strlen(w->text)-1];
+ if (c == ',' || c == ';' || c == ':' || c == '-' ||
+ c == ')' || c == ']' || c == '}')
+ done = True;
+ }
+
+ if (se->nwords >= 25) /* ok that's just about enough out of you */
+ done = True;
+
+ if ((s->mode == PAGE || s->mode == CHARS) &&
+ x + w->rbearing > right) /* wrap line */
+ {
+ align_line (s, se, line_start, x, right);
+ line_start = se->nwords;
+
+ x = left;
+ y += se->xftfont->ascent + se->xftfont->descent;
+
+ /* If we're close to the bottom of the screen, stop, and
+ unread the current word. (But not if this is the first
+ word, otherwise we might just get stuck on it.)
+ */
+ if (se->nwords > 0 &&
+ y + se->xftfont->ascent + se->xftfont->descent > s->xgwa.height)
+ {
+ unread_word (s, w);
+ free (w);
+ /* done = True; */
+ break;
+ }
+ }
+
+ w->target_x = x;
+ w->target_y = y;
+
+ x += w->width + space;
+ se->width = x;
+
+ if (se->nwords >= (array_size - 1))
+ {
+ array_size += 100;
+ se->words = (word **)
+ realloc (se->words, array_size * sizeof(*se->words));
+ if (!se->words)
+ {
+ fprintf (stderr, "%s: out of memory (%d words)\n",
+ progname, array_size);
+ exit (1);
+ }
+ }
+
+ se->words[se->nwords++] = w;
+ }
+
+ se->width -= space;
+
+ switch (s->mode)
+ {
+ case PAGE:
+ case CHARS:
+ align_line (s, se, line_start, x, right);
+ if (se->move_chars_p)
+ split_words (s, se);
+ scatter_sentence (s, se);
+ shuffle_words (s, se);
+ break;
+ case SCROLL:
+ aim_sentence (s, se);
+ break;
+ default:
+ abort();
+ break;
+ }
+
+# ifdef DEBUG
+ if (s->debug_p)
+ {
+ fprintf (stderr, "%s: sentence %d:", progname, se->id);
+ for (i = 0; i < se->nwords; i++)
+ fprintf (stderr, " %s", se->words[i]->text);
+ fprintf (stderr, "\n");
+ }
+# endif /* DEBUG */
+}
+
+
+/* Render a single word object to the screen.
+ */
+static void
+draw_word (state *s, sentence *se, word *word)
+{
+ int x, y, w, h;
+ if (! word->pixmap) return;
+
+ x = word->x + word->lbearing;
+ y = word->y - word->ascent;
+ w = word->rbearing - word->lbearing;
+ h = word->ascent + word->descent;
+
+ if (x + w < 0 ||
+ y + h < 0 ||
+ x > s->xgwa.width ||
+ y > s->xgwa.height)
+ return;
+
+ XSetClipMask (s->dpy, se->fg_gc, word->mask);
+ XSetClipOrigin (s->dpy, se->fg_gc, x, y);
+ XCopyArea (s->dpy, word->pixmap, s->b, se->fg_gc,
+ 0, 0, w, h, x, y);
+}
+
+
+/* If there is room for more sentences, add one.
+ */
+static void
+more_sentences (state *s)
+{
+ int i;
+ Bool any = False;
+ for (i = 0; i < s->nsentences; i++)
+ {
+ sentence *se = s->sentences[i];
+ if (! se)
+ {
+ se = new_sentence (s, s);
+ populate_sentence (s, se);
+ if (se->nwords > 0)
+ s->spawn_p = False, any = True;
+ else
+ {
+ free_sentence (s, se);
+ se = 0;
+ }
+ s->sentences[i] = se;
+ if (se)
+ s->latest_sentence = se->id;
+ break;
+ }
+ }
+
+ if (any) sort_sentences (s);
+}
+
+
+/* Render all the words to the screen, and run the animation one step.
+ */
+static void
+draw_sentence (state *s, sentence *se)
+{
+ int i;
+ Bool moved = False;
+
+ if (! se) return;
+
+ for (i = 0; i < se->nwords; i++)
+ {
+ word *w = se->words[i];
+
+ switch (s->mode)
+ {
+ case PAGE:
+ case CHARS:
+ if (se->anim_state != PAUSE &&
+ w->tick <= w->nticks)
+ {
+ int dx = w->target_x - w->start_x;
+ int dy = w->target_y - w->start_y;
+ double r = sin (w->tick * M_PI / (2 * w->nticks));
+ w->x = w->start_x + (dx * r);
+ w->y = w->start_y + (dy * r);
+
+ w->tick++;
+ if (se->anim_state == OUT &&
+ (s->mode == PAGE || s->mode == CHARS))
+ w->tick++; /* go out faster */
+ moved = True;
+ }
+ break;
+ case SCROLL:
+ {
+ int dx = w->target_x - w->start_x;
+ int dy = w->target_y - w->start_y;
+ double r = (double) w->tick / w->nticks;
+ w->x = w->start_x + (dx * r);
+ w->y = w->start_y + (dy * r);
+ w->tick++;
+ moved = (w->tick <= w->nticks);
+
+ /* Launch a new sentence when:
+ - the front of this sentence is almost off the left edge;
+ - the end of this sentence is almost on screen.
+ - or, randomly
+ */
+ if (se->anim_state != OUT &&
+ i == 0 &&
+ se->id == s->latest_sentence)
+ {
+ Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
+ w->x + se->width < (s->xgwa.width * 2.1));
+ Bool rand_p = (new_p ? 0 : !(random() % 2000));
+
+ if (new_p || rand_p)
+ {
+ se->anim_state = OUT;
+ s->spawn_p = True;
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
+ progname, se->id,
+ se->words[0]->x + se->width,
+ rand_p ? " randomly" : "");
+# endif /* DEBUG */
+ }
+ }
+ }
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ draw_word (s, se, w);
+ }
+
+ if (moved && se->anim_state == PAUSE)
+ abort();
+
+ if (! moved)
+ {
+ switch (se->anim_state)
+ {
+ case IN:
+ se->anim_state = PAUSE;
+ se->pause_tick = (se->nwords * 7 * s->linger);
+ if (se->move_chars_p)
+ se->pause_tick /= 5;
+ scatter_sentence (s, se);
+ shuffle_words (s, se);
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
+# endif /* DEBUG */
+ break;
+ case PAUSE:
+ if (--se->pause_tick <= 0)
+ {
+ se->anim_state = OUT;
+ s->spawn_p = True;
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: OUT %d\n", progname, se->id);
+# endif /* DEBUG */
+ }
+ break;
+ case OUT:
+# ifdef DEBUG
+ if (s->debug_p)
+ fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
+# endif /* DEBUG */
+ {
+ int j;
+ for (j = 0; j < s->nsentences; j++)
+ if (s->sentences[j] == se)
+ s->sentences[j] = 0;
+ free_sentence (s, se);
+ }
+ break;
+ default:
+ abort();
+ break;
+ }
+ }
+}
+
+
+#ifdef DEBUG /* All of this stuff is for -debug-metrics mode. */
+
+
+static Pixmap
+scale_ximage (Screen *screen, Window window, XImage *img, int scale,
+ int margin)
+{
+ Display *dpy = DisplayOfScreen (screen);
+ int x, y;
+ unsigned width = img->width, height = img->height;
+ Pixmap p2;
+ GC gc;
+
+ p2 = XCreatePixmap (dpy, window, width*scale, height*scale, img->depth);
+ gc = XCreateGC (dpy, p2, 0, 0);
+
+ XSetForeground (dpy, gc, BlackPixelOfScreen (screen));
+ XFillRectangle (dpy, p2, gc, 0, 0, width*scale, height*scale);
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ XSetForeground (dpy, gc, XGetPixel (img, x, y));
+ XFillRectangle (dpy, p2, gc, x*scale, y*scale, scale, scale);
+ }
+
+ if (scale > 2)
+ {
+ XWindowAttributes xgwa;
+ XColor c;
+ c.red = c.green = c.blue = 0x4444;
+ c.flags = DoRed|DoGreen|DoBlue;
+ XGetWindowAttributes (dpy, window, &xgwa);
+ if (! XAllocColor (dpy, xgwa.colormap, &c)) abort();
+ XSetForeground (dpy, gc, c.pixel);
+ XDrawRectangle (dpy, p2, gc, 0, 0, width*scale-1, height*scale-1);
+ XDrawRectangle (dpy, p2, gc, margin*scale, margin*scale,
+ width*scale-1, height*scale-1);
+ for (y = 0; y <= height - 2*margin; y++)
+ XDrawLine (dpy, p2, gc,
+ margin*scale, (y+margin)*scale-1,
+ (width-margin)*scale, (y+margin)*scale-1);
+ for (x = 0; x <= width - 2*margin; x++)
+ XDrawLine (dpy, p2, gc,
+ (x+margin)*scale-1, margin*scale,
+ (x+margin)*scale-1, (height-margin)*scale);
+ XFreeColors (dpy, xgwa.colormap, &c.pixel, 1, 0);
+ }
+
+ XFreeGC (dpy, gc);
+ return p2;
+}
+
+
+static int check_edge (Display *dpy, Drawable p, GC gc,
+ unsigned msg_x, unsigned msg_y, const char *msg,
+ XImage *img,
+ unsigned x, unsigned y, unsigned dim, unsigned end)
+{
+ unsigned pt[2];
+ pt[0] = x;
+ pt[1] = y;
+ end += pt[dim];
+
+ for (;;)
+ {
+ if (pt[dim] == end)
+ {
+ XDrawString (dpy, p, gc, msg_x, msg_y, msg, strlen (msg));
+ return 1;
+ }
+
+ if (XGetPixel(img, pt[0], pt[1]) & 0xffffff)
+ break;
+
+ ++pt[dim];
+ }
+
+ return 0;
+}
+
+
+static unsigned long
+fontglide_draw_metrics (state *s)
+{
+ unsigned int margin = (s->debug_metrics_antialiasing_p ? 2 : 0);
+
+ char txt[2], utxt[3], txt2[80];
+ XChar2b *txt3 = 0;
+ const char *fn = (s->font_override ? s->font_override : "fixed");
+ char fn2[1024];
+ XCharStruct c, overall, fake_c;
+ int dir, ascent, descent;
+ int x, y;
+ XGlyphInfo extents;
+ XftColor xftcolor;
+ XftDraw *xftdraw;
+ int sc = s->debug_scale;
+ GC gc;
+ unsigned long red = 0xFFFF0000; /* so shoot me */
+ unsigned long green = 0xFF00FF00;
+ unsigned long blue = 0xFF6666FF;
+ unsigned long yellow = 0xFFFFFF00;
+ unsigned long cyan = 0xFF004040;
+ int i, j;
+ Drawable dest = s->b ? s->b : s->window;
+
+ if (sc < 1) sc = 1;
+
+ /* Self-test these macros to make sure they're symmetrical. */
+ for (i = 0; i < 1000; i++)
+ {
+ XGlyphInfo g, g2;
+ XRectangle r;
+ c.lbearing = (random()%50)-100;
+ c.rbearing = (random()%50)-100;
+ c.ascent = (random()%50)-100;
+ c.descent = (random()%50)-100;
+ c.width = (random()%50)-100;
+ XCharStruct_to_XGlyphInfo (c, g);
+ XGlyphInfo_to_XCharStruct (g, overall);
+ if (c.lbearing != overall.lbearing) abort();
+ if (c.rbearing != overall.rbearing) abort();
+ if (c.ascent != overall.ascent) abort();
+ if (c.descent != overall.descent) abort();
+ if (c.width != overall.width) abort();
+ XCharStruct_to_XGlyphInfo (overall, g2);
+ if (g.x != g2.x) abort();
+ if (g.y != g2.y) abort();
+ if (g.xOff != g2.xOff) abort();
+ if (g.yOff != g2.yOff) abort();
+ if (g.width != g2.width) abort();
+ if (g.height != g2.height) abort();
+ XCharStruct_to_XmbRectangle (overall, r);
+ XmbRectangle_to_XCharStruct (r, c, c.width);
+ if (c.lbearing != overall.lbearing) abort();
+ if (c.rbearing != overall.rbearing) abort();
+ if (c.ascent != overall.ascent) abort();
+ if (c.descent != overall.descent) abort();
+ if (c.width != overall.width) abort();
+ }
+
+ txt[0] = s->debug_metrics_p;
+ txt[1] = 0;
+
+ /* Convert Unicode code point to UTF-8. */
+ utxt[utf8_encode(s->debug_metrics_p, utxt, 4)] = 0;
+
+ txt3 = utf8_to_XChar2b (utxt, 0);
+
+ if (! s->metrics_font1)
+ s->metrics_font1 = XLoadQueryFont (s->dpy, fn);
+ if (! s->metrics_font2)
+ s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
+ if (! s->metrics_font1)
+ s->metrics_font1 = s->metrics_font2;
+
+ gc = XCreateGC (s->dpy, dest, 0, 0);
+ XSetFont (s->dpy, gc, s->metrics_font1->fid);
+
+# if defined(HAVE_JWXYZ)
+ jwxyz_XSetAntiAliasing (s->dpy, gc, False);
+# endif
+
+ if (! s->metrics_xftfont)
+ {
+ s->metrics_xftfont =
+ XftFontOpenXlfd (s->dpy, screen_number(s->xgwa.screen), fn);
+ if (! s->metrics_xftfont)
+ {
+ const char *fn2 = "fixed";
+ s->metrics_xftfont =
+ XftFontOpenName (s->dpy, screen_number(s->xgwa.screen), fn2);
+ if (s->metrics_xftfont)
+ fn = fn2;
+ else
+ {
+ fprintf (stderr, "%s: XftFontOpen failed on \"%s\" and \"%s\"\n",
+ progname, fn, fn2);
+ exit (1);
+ }
+ }
+ }
+
+ strcpy (fn2, fn);
+# ifdef HAVE_JWXYZ
+ append_font_name (s->dpy, fn2, s->metrics_xftfont->xfont);
+# endif
+
+ xftdraw = XftDrawCreate (s->dpy, dest, s->xgwa.visual,
+ s->xgwa.colormap);
+ XftColorAllocName (s->dpy, s->xgwa.visual, s->xgwa.colormap, "white",
+ &xftcolor);
+ XftTextExtentsUtf8 (s->dpy, s->metrics_xftfont,
+ (FcChar8 *) utxt, strlen(utxt),
+ &extents);
+
+
+ XTextExtents (s->metrics_font1, txt, strlen(txt),
+ &dir, &ascent, &descent, &overall);
+ c = ((s->debug_metrics_p >= s->metrics_font1->min_char_or_byte2 &&
+ s->debug_metrics_p <= s->metrics_font1->max_char_or_byte2)
+ ? s->metrics_font1->per_char[s->debug_metrics_p -
+ s->metrics_font1->min_char_or_byte2]
+ : overall);
+
+ XSetForeground (s->dpy, gc, BlackPixelOfScreen (s->xgwa.screen));
+ XFillRectangle (s->dpy, dest, gc, 0, 0, s->xgwa.width, s->xgwa.height);
+
+ XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
+ XSetFont (s->dpy, gc, s->metrics_font2->fid);
+ XDrawString (s->dpy, dest, gc,
+ s->xgwa.width / 2,
+ s->xgwa.height - 5,
+ fn2, strlen(fn2));
+
+# ifdef HAVE_JWXYZ
+ {
+ char *name = jwxyz_unicode_character_name (
+ s->dpy, s->metrics_font1->fid, s->debug_metrics_p);
+ if (!name || !*name) name = strdup("unknown character name");
+ XDrawString (s->dpy, dest, gc,
+ 10,
+ 10 + 2 * (s->metrics_font2->ascent +
+ s->metrics_font2->descent),
+ name, strlen(name));
+ free (name);
+ }
+# endif
+
+ /* i 0, j 0: top left, XDrawString, char metrics
+ i 1, j 0: bot left, XDrawString, overall metrics, ink escape
+ i 0, j 1: top right, XftDrawStringUtf8, utf8 metrics
+ i 1, j 1: bot right, XDrawString16, 16 metrics, ink escape
+ */
+ for (j = 0; j < 2; j++) {
+ Bool xft_p = (j != 0);
+ int ww = s->xgwa.width / 2 - 20;
+ int xoff = (j == 0 ? 0 : ww + 20);
+
+ /* XDrawString only does 8-bit characters, so skip it outside Latin-1. */
+ if (s->debug_metrics_p >= 256)
+ {
+ if (!xft_p)
+ continue;
+ xoff = 0;
+ ww = s->xgwa.width;
+ }
+
+ x = (ww - overall.width) / 2;
+
+ for (i = 0; i < 2; i++)
+ {
+ XCharStruct cc;
+ int x1 = xoff + ww * 0.18;
+ int x2 = xoff + ww * 0.82;
+ int x3 = xoff + ww;
+ int pixw, pixh;
+ Pixmap p;
+
+ y = 80;
+ {
+ int h = sc * (ascent + descent);
+ int min = (ascent + descent) * 4;
+ if (h < min) h = min;
+ if (i == 1) h *= 3;
+ y += h;
+ }
+
+ memset (&fake_c, 0, sizeof(fake_c));
+
+ if (!xft_p && i == 0)
+ cc = c;
+ else if (!xft_p && i == 1)
+ cc = overall;
+ else if (xft_p && i == 0)
+ {
+ /* Measure the glyph in the Xft way */
+ XGlyphInfo extents;
+ XftTextExtentsUtf8 (s->dpy,
+ s->metrics_xftfont,
+ (FcChar8 *) utxt, strlen(utxt),
+ &extents);
+ XGlyphInfo_to_XCharStruct (extents, fake_c);
+ cc = fake_c;
+ }
+ else if (xft_p)
+ {
+ /* Measure the glyph in the 16-bit way */
+ int dir, ascent, descent;
+ XTextExtents16 (s->metrics_font1, txt3, 1, &dir, &ascent, &descent,
+ &fake_c);
+ cc = fake_c;
+ }
+
+ pixw = margin * 2 + cc.rbearing - cc.lbearing;
+ pixh = margin * 2 + cc.ascent + cc.descent;
+ p = (pixw > 0 && pixh > 0
+ ? XCreatePixmap (s->dpy, dest, pixw, pixh, s->xgwa.depth)
+ : 0);
+
+ if (p)
+ {
+ Pixmap p2;
+ GC gc2 = XCreateGC (s->dpy, p, 0, 0);
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (s->dpy, gc2, False);
+# endif
+ XSetFont (s->dpy, gc2, s->metrics_font1->fid);
+ XSetForeground (s->dpy, gc2, BlackPixelOfScreen (s->xgwa.screen));
+ XFillRectangle (s->dpy, p, gc2, 0, 0, pixw, pixh);
+ XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
+ XSetForeground (s->dpy, gc2, WhitePixelOfScreen (s->xgwa.screen));
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (s->dpy, gc2,
+ s->debug_metrics_antialiasing_p);
+# endif
+
+ if (xft_p && i == 0)
+ {
+ XftDraw *xftdraw2 = XftDrawCreate (s->dpy, p, s->xgwa.visual,
+ s->xgwa.colormap);
+ XftDrawStringUtf8 (xftdraw2, &xftcolor,
+ s->metrics_xftfont,
+ -cc.lbearing + margin,
+ cc.ascent + margin,
+ (FcChar8 *) utxt, strlen(utxt));
+ XftDrawDestroy (xftdraw2);
+ }
+ else if (xft_p)
+ XDrawString16 (s->dpy, p, gc2,
+ -cc.lbearing + margin,
+ cc.ascent + margin,
+ txt3, 1);
+ else
+ XDrawString (s->dpy, p, gc2,
+ -cc.lbearing + margin,
+ cc.ascent + margin,
+ txt, strlen(txt));
+
+ {
+ unsigned x2, y2;
+ XImage *img = XGetImage (s->dpy, p, 0, 0, pixw, pixh,
+ ~0L, ZPixmap);
+ XImage *img2;
+
+ if (i == 1)
+ {
+ unsigned w = pixw - margin * 2, h = pixh - margin * 2;
+
+ if (margin > 0)
+ {
+ /* Check for ink escape. */
+ unsigned long ink = 0;
+ for (y2 = 0; y2 != pixh; ++y2)
+ for (x2 = 0; x2 != pixw; ++x2)
+ {
+ /* Sloppy... */
+ if (! (x2 >= margin &&
+ x2 < pixw - margin &&
+ y2 >= margin &&
+ y2 < pixh - margin))
+ ink |= XGetPixel (img, x2, y2);
+ }
+
+ if (ink & 0xFFFFFF)
+ {
+ XSetFont (s->dpy, gc, s->metrics_font2->fid);
+ XDrawString (s->dpy, dest, gc,
+ xoff + 10, 40,
+ "Ink escape!", 11);
+ }
+ }
+
+ /* ...And wasted space. */
+ if (w && h)
+ {
+ if (check_edge (s->dpy, dest, gc, 120, 60, "left",
+ img, margin, margin, 1, h) |
+ check_edge (s->dpy, dest, gc, 160, 60, "right",
+ img, margin + w - 1, margin, 1, h) |
+ check_edge (s->dpy, dest, gc, 200, 60, "top",
+ img, margin, margin, 0, w) |
+ check_edge (s->dpy, dest, gc, 240, 60, "bottom",
+ img, margin, margin + h - 1, 0, w))
+ {
+ XSetFont (s->dpy, gc, s->metrics_font2->fid);
+ XDrawString (s->dpy, dest, gc,
+ xoff + 10, 60,
+ "Wasted space: ", 14);
+ }
+ }
+ }
+
+ if (s->debug_metrics_antialiasing_p)
+ {
+ /* Draw a dark cyan boundary around antialiased glyphs */
+ img2 = XCreateImage (s->dpy, s->xgwa.visual, img->depth,
+ ZPixmap, 0, NULL,
+ img->width, img->height,
+ img->bitmap_pad, 0);
+ img2->data = malloc (img->bytes_per_line * img->height);
+
+ for (y2 = 0; y2 != pixh; ++y2)
+ for (x2 = 0; x2 != pixw; ++x2)
+ {
+ unsigned long px = XGetPixel (img, x2, y2);
+ if ((px & 0xffffff) == 0)
+ {
+ unsigned long neighbors = 0;
+ if (x2)
+ neighbors |= XGetPixel (img, x2 - 1, y2);
+ if (x2 != pixw - 1)
+ neighbors |= XGetPixel (img, x2 + 1, y2);
+ if (y2)
+ neighbors |= XGetPixel (img, x2, y2 - 1);
+ if (y2 != pixh - 1)
+ neighbors |= XGetPixel (img, x2, y2 + 1);
+ XPutPixel (img2, x2, y2,
+ (neighbors & 0xffffff
+ ? cyan
+ : BlackPixelOfScreen (s->xgwa.screen)));
+ }
+ else
+ {
+ XPutPixel (img2, x2, y2, px);
+ }
+ }
+ }
+ else
+ {
+ img2 = img;
+ img = NULL;
+ }
+
+ p2 = scale_ximage (s->xgwa.screen, s->window, img2, sc, margin);
+ if (img)
+ XDestroyImage (img);
+ XDestroyImage (img2);
+ }
+
+ XCopyArea (s->dpy, p2, dest, gc,
+ 0, 0, sc*pixw, sc*pixh,
+ xoff + x + sc * (cc.lbearing - margin),
+ y - sc * (cc.ascent + margin));
+ XFreePixmap (s->dpy, p);
+ XFreePixmap (s->dpy, p2);
+ XFreeGC (s->dpy, gc2);
+ }
+
+ if (i == 0)
+ {
+ XSetFont (s->dpy, gc, s->metrics_font1->fid);
+ XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (s->dpy, gc, s->debug_metrics_antialiasing_p);
+# endif
+ sprintf (txt2, "%s [XX%sXX] [%s%s%s%s]",
+ (xft_p ? utxt : txt),
+ (xft_p ? utxt : txt),
+ (xft_p ? utxt : txt),
+ (xft_p ? utxt : txt),
+ (xft_p ? utxt : txt),
+ (xft_p ? utxt : txt));
+
+ if (xft_p)
+ XftDrawStringUtf8 (xftdraw, &xftcolor,
+ s->metrics_xftfont,
+ xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2
+ + 40,
+ ascent + 10,
+ (FcChar8 *) txt2, strlen(txt2));
+ else
+ XDrawString (s->dpy, dest, gc,
+ xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2,
+ ascent + 10,
+ txt2, strlen(txt2));
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (s->dpy, gc, False);
+# endif
+ XSetFont (s->dpy, gc, s->metrics_font2->fid);
+ if (xft_p)
+ {
+ char *uptr;
+ char *tptr = txt2 + sprintf(txt2, "U+%04lX", s->debug_metrics_p);
+ *tptr++ = " ?_"[s->entering_unicode_p];
+ *tptr++ = ' ';
+
+ uptr = utxt;
+ while (*uptr)
+ {
+ tptr += sprintf (tptr, "0%03o ", (unsigned char) *uptr);
+ ++uptr;
+ }
+ *tptr++ = ' ';
+ uptr = utxt;
+ while (*uptr)
+ {
+ tptr += sprintf (tptr, "%02x ", (unsigned char) *uptr);
+ ++uptr;
+ }
+ }
+ else
+ sprintf (txt2, "%c %3ld 0%03lo 0x%02lx%s",
+ (char)s->debug_metrics_p, s->debug_metrics_p,
+ s->debug_metrics_p, s->debug_metrics_p,
+ (txt[0] < s->metrics_font1->min_char_or_byte2
+ ? " *" : ""));
+ XDrawString (s->dpy, dest, gc,
+ xoff + 10, 20,
+ txt2, strlen(txt2));
+ }
+
+# ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (s->dpy, gc, True);
+# endif
+
+ {
+ const char *ss = (j == 0
+ ? (i == 0 ? "char" : "overall")
+ : (i == 0 ? "utf8" : "16 bit"));
+ XSetFont (s->dpy, gc, s->metrics_font2->fid);
+
+ XSetForeground (s->dpy, gc, red);
+
+ sprintf (txt2, "%s ascent %d", ss, ascent);
+ XDrawString (s->dpy, dest, gc,
+ xoff + 10,
+ y - sc*ascent - 2,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ xoff, y - sc*ascent,
+ x3, y - sc*ascent);
+
+ sprintf (txt2, "%s descent %d", ss, descent);
+ XDrawString (s->dpy, dest, gc,
+ xoff + 10,
+ y + sc*descent - 2,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ xoff, y + sc*descent,
+ x3, y + sc*descent);
+ }
+
+
+ /* ascent, descent, baseline */
+
+ XSetForeground (s->dpy, gc, green);
+
+ sprintf (txt2, "ascent %d", cc.ascent);
+ if (cc.ascent != 0)
+ XDrawString (s->dpy, dest, gc,
+ x1, y - sc*cc.ascent - 2,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ x1, y - sc*cc.ascent,
+ x2, y - sc*cc.ascent);
+
+ sprintf (txt2, "descent %d", cc.descent);
+ if (cc.descent != 0)
+ XDrawString (s->dpy, dest, gc,
+ x1, y + sc*cc.descent - 2,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ x1, y + sc*cc.descent,
+ x2, y + sc*cc.descent);
+
+ XSetForeground (s->dpy, gc, yellow);
+ strcpy (txt2, "baseline");
+ XDrawString (s->dpy, dest, gc,
+ x1, y - 2,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc, x1, y, x2, y);
+
+
+ /* origin, width */
+
+ XSetForeground (s->dpy, gc, blue);
+
+ strcpy (txt2, "origin");
+ XDrawString (s->dpy, dest, gc,
+ xoff + x + 2,
+ y + sc*descent + 50,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ xoff + x, y - sc*(ascent - 10),
+ xoff + x, y + sc*(descent + 10));
+
+ sprintf (txt2, "width %d", cc.width);
+ XDrawString (s->dpy, dest, gc,
+ xoff + x + sc*cc.width + 2,
+ y + sc*descent + 60,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ xoff + x + sc*cc.width, y - sc*(ascent - 10),
+ xoff + x + sc*cc.width, y + sc*(descent + 10));
+
+
+ /* lbearing, rbearing */
+
+ XSetForeground (s->dpy, gc, green);
+
+ sprintf (txt2, "lbearing %d", cc.lbearing);
+ XDrawString (s->dpy, dest, gc,
+ xoff + x + sc*cc.lbearing + 2,
+ y + sc * descent + 30,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ xoff + x + sc*cc.lbearing, y - sc*ascent,
+ xoff + x + sc*cc.lbearing, y + sc*descent + 20);
+
+ sprintf (txt2, "rbearing %d", cc.rbearing);
+ XDrawString (s->dpy, dest, gc,
+ xoff + x + sc*cc.rbearing + 2,
+ y + sc * descent + 40,
+ txt2, strlen(txt2));
+ XDrawLine (s->dpy, dest, gc,
+ xoff + x + sc*cc.rbearing, y - sc*ascent,
+ xoff + x + sc*cc.rbearing, y + sc*descent + 40);
+
+ /* y += sc * (ascent + descent) * 2; */
+ }
+ }
+
+ if (dest != s->window)
+ XCopyArea (s->dpy, dest, s->window, s->bg_gc,
+ 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
+
+ XFreeGC (s->dpy, gc);
+ XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap, &xftcolor);
+ XftDrawDestroy (xftdraw);
+ free (txt3);
+
+ return s->frame_delay;
+}
+
+# endif /* DEBUG */
+
+
+/* Render all the words to the screen, and run the animation one step.
+ Clear screen first, swap buffers after.
+ */
+static unsigned long
+fontglide_draw (Display *dpy, Window window, void *closure)
+{
+ state *s = (state *) closure;
+ int i;
+
+# ifdef DEBUG
+ if (s->debug_metrics_p)
+ return fontglide_draw_metrics (closure);
+# endif /* DEBUG */
+
+ if (s->spawn_p)
+ more_sentences (s);
+
+ if (!s->trails_p)
+ XFillRectangle (s->dpy, s->b, s->bg_gc,
+ 0, 0, s->xgwa.width, s->xgwa.height);
+
+ for (i = 0; i < s->nsentences; i++)
+ draw_sentence (s, s->sentences[i]);
+
+# ifdef DEBUG
+ if (s->debug_p && (s->prev_font_name || s->next_font_name))
+ {
+ if (! s->label_gc)
+ {
+ if (! s->metrics_font2)
+ s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
+ s->label_gc = XCreateGC (dpy, s->b, 0, 0);
+ XSetFont (s->dpy, s->label_gc, s->metrics_font2->fid);
+ }
+ if (s->prev_font_name)
+ XDrawString (s->dpy, s->b, s->label_gc,
+ 10, 10 + s->metrics_font2->ascent,
+ s->prev_font_name, strlen(s->prev_font_name));
+ if (s->next_font_name)
+ XDrawString (s->dpy, s->b, s->label_gc,
+ 10, 10 + s->metrics_font2->ascent * 2,
+ s->next_font_name, strlen(s->next_font_name));
+ }
+# endif /* DEBUG */
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+ if (s->backb)
+ {
+ XdbeSwapInfo info[1];
+ info[0].swap_window = s->window;
+ info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
+ XdbeSwapBuffers (s->dpy, info, 1);
+ }
+ else
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+ if (s->dbuf)
+ {
+ XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
+ 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
+ }
+
+ return s->frame_delay;
+}
+
+
+
+/* When the subprocess has generated some output, this reads as much as it
+ can into s->buf at s->buf_tail.
+ */
+static void
+drain_input (state *s)
+{
+ while (s->buf_tail < sizeof(s->buf) - 2)
+ {
+ int c = textclient_getc (s->tc);
+ if (c > 0)
+ s->buf[s->buf_tail++] = (char) c;
+ else
+ break;
+ }
+}
+
+
+/* Window setup and resource loading */
+
+static void *
+fontglide_init (Display *dpy, Window window)
+{
+ XGCValues gcv;
+ state *s = (state *) calloc (1, sizeof(*s));
+ s->dpy = dpy;
+ s->window = window;
+ s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
+
+ XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
+
+ s->font_override = get_string_resource (dpy, "font", "Font");
+ if (s->font_override && (!*s->font_override || *s->font_override == '('))
+ s->font_override = 0;
+
+ s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
+ s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
+ if (s->border_width < 0 || s->border_width > 20)
+ s->border_width = 1;
+
+ s->speed = get_float_resource (dpy, "speed", "Float");
+ if (s->speed <= 0 || s->speed > 200)
+ s->speed = 1;
+
+ s->linger = get_float_resource (dpy, "linger", "Float");
+ if (s->linger <= 0 || s->linger > 200)
+ s->linger = 1;
+
+ s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
+
+# ifdef DEBUG
+ s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
+ s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
+ ? 199 : 0);
+ s->debug_scale = 6;
+
+# ifdef HAVE_JWXYZ
+ if (s->debug_metrics_p && !s->font_override)
+ s->font_override = "Helvetica Bold 16";
+# endif
+
+# endif /* DEBUG */
+
+ s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
+
+# ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */
+ s->dbuf = False;
+# endif
+
+# ifdef DEBUG
+ if (s->debug_metrics_p) s->trails_p = False;
+# endif /* DEBUG */
+
+ if (s->trails_p) s->dbuf = False; /* don't need it in this case */
+
+ {
+ const char *ss = get_string_resource (dpy, "mode", "Mode");
+ if (!ss || !*ss || !strcasecmp (ss, "random"))
+ s->mode = ((random() % 2) ? SCROLL : PAGE);
+ else if (!strcasecmp (ss, "scroll"))
+ s->mode = SCROLL;
+ else if (!strcasecmp (ss, "page"))
+ s->mode = PAGE;
+ else if (!strcasecmp (ss, "chars") || !strcasecmp (ss, "char"))
+ s->mode = CHARS;
+ else
+ {
+ fprintf (stderr,
+ "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
+ progname, ss);
+ }
+ }
+
+ if (s->dbuf)
+ {
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+ s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
+ if (s->dbeclear_p)
+ s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
+ else
+ s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
+ s->backb = s->b;
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
+ if (!s->b)
+ {
+ s->ba = XCreatePixmap (s->dpy, s->window,
+ s->xgwa.width, s->xgwa.height,
+ s->xgwa.depth);
+ s->b = s->ba;
+ }
+ }
+ else
+ {
+ s->b = s->window;
+ }
+
+ gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
+ "background", "Background");
+ s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
+
+ s->nsentences = 5; /* #### */
+ s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
+ s->spawn_p = True;
+
+ s->early_p = True;
+ s->start_time = time ((time_t *) 0);
+ s->tc = textclient_open (dpy);
+
+ return s;
+}
+
+
+static Bool
+fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+# ifdef DEBUG
+ state *s = (state *) closure;
+
+ if (! s->debug_metrics_p)
+ return False;
+ if (event->xany.type == KeyPress)
+ {
+ static const unsigned long max = 0x110000;
+ KeySym keysym;
+ char c = 0;
+ XLookupString (&event->xkey, &c, 1, &keysym, 0);
+
+ if (s->entering_unicode_p > 0)
+ {
+ unsigned digit;
+ unsigned long new_char = 0;
+
+ if (c >= 'a' && c <= 'f')
+ digit = c + 0xa - 'a';
+ else if (c >= 'A' && c <= 'F')
+ digit = c + 0xa - 'A';
+ else if (c >= '0' && c <= '9')
+ digit = c + 0 - '0';
+ else
+ {
+ s->entering_unicode_p = 0;
+ return True;
+ }
+
+ if (s->entering_unicode_p == 1)
+ new_char = 0;
+ else if (s->entering_unicode_p == 2)
+ new_char = s->debug_metrics_p;
+
+ new_char = (new_char << 4) | digit;
+ if (new_char > 0 && new_char < max)
+ {
+ s->debug_metrics_p = new_char;
+ s->entering_unicode_p = 2;
+ }
+ else
+ s->entering_unicode_p = 0;
+ return True;
+ }
+
+ if (c == '\t')
+ s->debug_metrics_antialiasing_p ^= True;
+ else if (c == 3 || c == 27)
+ exit (0);
+ else if (c >= ' ')
+ s->debug_metrics_p = (unsigned char) c;
+ else if (keysym == XK_Left || keysym == XK_Right)
+ {
+ s->debug_metrics_p += (keysym == XK_Left ? -1 : 1);
+ if (s->debug_metrics_p >= max)
+ s->debug_metrics_p = 1;
+ else if (s->debug_metrics_p <= 0)
+ s->debug_metrics_p = max - 1;
+ return True;
+ }
+ else if (keysym == XK_Prior)
+ s->debug_metrics_p = (s->debug_metrics_p + max - 0x80) % max;
+ else if (keysym == XK_Next)
+ s->debug_metrics_p = (s->debug_metrics_p + 0x80) % max;
+ else if (keysym == XK_Up)
+ s->debug_scale++;
+ else if (keysym == XK_Down)
+ s->debug_scale = (s->debug_scale > 1 ? s->debug_scale-1 : 1);
+ else if (keysym == XK_F1)
+ s->entering_unicode_p = 1;
+ else
+ return False;
+ return True;
+ }
+# endif /* DEBUG */
+
+ return False;
+}
+
+
+static void
+fontglide_reshape (Display *dpy, Window window, void *closure,
+ unsigned int w, unsigned int h)
+{
+ state *s = (state *) closure;
+ XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
+
+ if (s->dbuf && s->ba)
+ {
+ XFreePixmap (s->dpy, s->ba);
+ s->ba = XCreatePixmap (s->dpy, s->window,
+ s->xgwa.width, s->xgwa.height,
+ s->xgwa.depth);
+ XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
+ s->xgwa.width, s->xgwa.height);
+ s->b = s->ba;
+ }
+}
+
+static void
+fontglide_free (Display *dpy, Window window, void *closure)
+{
+ state *s = (state *) closure;
+ textclient_close (s->tc);
+
+#ifdef DEBUG
+ if (s->metrics_xftfont)
+ XftFontClose (s->dpy, s->metrics_xftfont);
+ if (s->metrics_font1)
+ XFreeFont (s->dpy, s->metrics_font1);
+ if (s->metrics_font2 && s->metrics_font1 != s->metrics_font2)
+ XFreeFont (s->dpy, s->metrics_font2);
+ if (s->prev_font_name) free (s->prev_font_name);
+ if (s->next_font_name) free (s->next_font_name);
+ if (s->label_gc) XFreeGC (dpy, s->label_gc);
+#endif
+
+ /* #### there's more to free here */
+
+ free (s);
+}
+
+
+static const char *fontglide_defaults [] = {
+ ".background: #000000",
+ ".foreground: #DDDDDD",
+ ".borderColor: #555555",
+ "*delay: 10000",
+ "*program: xscreensaver-text",
+ "*usePty: false",
+ "*mode: random",
+ ".font: (default)",
+
+ /* I'm not entirely clear on whether the charset of an XLFD has any
+ meaning when Xft is being used. */
+ "*fontCharset: iso8859-1",
+/*"*fontCharset: iso10646-1", */
+/*"*fontCharset: *-*",*/
+
+ "*fontBorderWidth: 2",
+ "*speed: 1.0",
+ "*linger: 1.0",
+ "*trails: False",
+# ifdef DEBUG
+ "*debug: False",
+ "*debugMetrics: False",
+# endif /* DEBUG */
+ "*doubleBuffer: True",
+# ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+ "*useDBE: True",
+ "*useDBEClear: True",
+# endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+ 0
+};
+
+static XrmOptionDescRec fontglide_options [] = {
+ { "-mode", ".mode", XrmoptionSepArg, 0 },
+ { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
+ { "-page", ".mode", XrmoptionNoArg, "page" },
+ { "-random", ".mode", XrmoptionNoArg, "random" },
+ { "-delay", ".delay", XrmoptionSepArg, 0 },
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+ { "-linger", ".linger", XrmoptionSepArg, 0 },
+ { "-program", ".program", XrmoptionSepArg, 0 },
+ { "-font", ".font", XrmoptionSepArg, 0 },
+ { "-fn", ".font", XrmoptionSepArg, 0 },
+ { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
+ { "-trails", ".trails", XrmoptionNoArg, "True" },
+ { "-no-trails", ".trails", XrmoptionNoArg, "False" },
+ { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
+ { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
+# ifdef DEBUG
+ { "-debug", ".debug", XrmoptionNoArg, "True" },
+ { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
+# endif /* DEBUG */
+ { 0, 0, 0, 0 }
+};
+
+
+XSCREENSAVER_MODULE ("FontGlide", fontglide)