/* xscreensaver, Copyright (c) 2003-2017 Jamie Zawinski * * 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 #include #ifndef HAVE_JWXYZ # include #endif #ifdef HAVE_UNISTD_H # include #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) { char *suffix = XGetAtomName (dpy, font->properties[i].card32); int L = strlen(suffix); strcpy(dest, suffix); free (suffix); return dest + L; } } 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); free (w); } 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; free (t2); 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); w = 0; /* 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 == '(')) { free (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 */ { 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 (ss) free (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; int i; textclient_close (s->tc); // if (s->b && s->b != s->window) XFreePixmap (dpy, s->b); // if (s->ba && s->ba != s->b) XFreePixmap (dpy, s->ba); XFreeGC (dpy, s->bg_gc); if (s->charset) free (s->charset); if (s->font_override) free (s->font_override); for (i = 0;i < s->nsentences; i++) if (s->sentences[i]) free_sentence (s, s->sentences[i]); free (s->sentences); #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 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)