diff options
author | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
commit | d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch) | |
tree | cbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /hacks/fontglide.c | |
download | xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip |
Original 5.40
Diffstat (limited to 'hacks/fontglide.c')
-rw-r--r-- | hacks/fontglide.c | 2489 |
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) |