1 /* xscreensaver, Copyright (c) 2003-2017 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * fontglide -- reads text from a subprocess and puts it on the screen using
12 * large characters that glide in from the edges, assemble, then disperse.
13 * Requires a system with scalable fonts. (X's font handing sucks. A lot.)
18 #endif /* HAVE_CONFIG_H */
21 /* If you turn on DEBUG, this program also masquerades as a tool for
22 debugging font metrics issues, which is probably only if interest
23 if you are doing active development on libjwxyz.a itself.
31 # include <X11/Intrinsic.h>
38 #include "screenhack.h"
39 #include "textclient.h"
43 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
45 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
50 int x, y; /* Position of origin of first character in word */
52 /* These have the same meanings as in XCharStruct: */
53 int lbearing; /* origin to leftmost pixel */
54 int rbearing; /* origin to rightmost pixel */
55 int ascent; /* origin to topmost pixel */
56 int descent; /* origin to bottommost pixel */
57 int width; /* origin to next word's origin */
61 int target_x, target_y;
75 XftColor xftcolor_fg, xftcolor_bg;
80 enum { IN, PAUSE, OUT } anim_state;
81 enum { LEFT, CENTER, RIGHT } alignment;
90 XWindowAttributes xgwa;
92 Pixmap b, ba; /* double-buffer to reduce flicker */
95 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
98 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
100 Bool dbuf; /* Whether we're using double buffering. */
102 int border_width; /* size of the font outline */
103 char *charset; /* registry and encoding for font lookups */
104 double speed; /* frame rate multiplier */
105 double linger; /* multiplier for how long to leave words on screen */
107 enum { PAGE, SCROLL, CHARS } mode;
109 char *font_override; /* if -font was specified on the cmd line */
111 char buf [40]; /* this only needs to be as big as one "word". */
117 sentence **sentences;
118 Bool spawn_p; /* whether it is time to create a new sentence */
120 unsigned long frame_delay;
126 unsigned long debug_metrics_p;
127 int debug_metrics_antialiasing_p;
129 unsigned entering_unicode_p; /* 0 = No, 1 = Just started, 2 = in progress */
130 XFontStruct *metrics_font1;
131 XFontStruct *metrics_font2;
132 XftFont *metrics_xftfont;
134 char *prev_font_name;
135 char *next_font_name;
141 static void drain_input (state *s);
145 pick_font_size (state *s)
147 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
148 int min, max, r, pixel;
153 if (min < 10) min = 10;
154 if (max < 30) max = 30;
158 pixel = min + ((random() % r) + (random() % r) + (random() % r));
160 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
170 append_font_name(Display *dpy, char *dest, const XFontStruct *font)
173 for (i = 0; i != font->n_properties; ++i) {
174 if (font->properties[i].name == XA_FONT) {
175 const char *suffix = XGetAtomName (dpy, font->properties[i].card32);
176 strcpy(dest, suffix);
177 return dest + strlen(suffix);
187 const char *n = jwxyz_nativeFontName (font->fid, &s);
188 return dest + sprintf (dest, "%s %.1f", n, s);
195 /* Finds the set of scalable fonts on the system; picks one;
196 and loads that font in a random pixel size.
197 Returns False if something went wrong.
200 pick_font_1 (state *s, sentence *se)
206 #ifndef HAVE_JWXYZ /* real Xlib */
209 XFontStruct *info = 0;
210 int count = 0, count2 = 0;
215 XftFontClose (s->dpy, se->xftfont);
216 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
218 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
221 free (se->font_name);
226 if (s->font_override)
227 sprintf (pattern, "%.200s", s->font_override);
229 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
236 "0", /* pixel size */
237 "0", /* point size */
238 "0", /* resolution x */
239 "0", /* resolution y */
242 s->charset); /* registry + encoding */
244 names = XListFonts (s->dpy, pattern, 1000, &count);
248 if (s->font_override)
249 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
251 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
256 i = random() % count;
258 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
263 fprintf (stderr, "%s: pattern %s\n"
264 " gave unusable %s\n\n",
265 progname, pattern, names[i]);
271 XFontStruct *font = &info[0];
272 unsigned long value = 0;
273 char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
274 unsigned long pixel=0, point=0, res_x=0, res_y=0;
276 unsigned long avg_width=0;
277 char *registry=0, *encoding=0;
279 char *bogus = "\"?\"";
281 # define STR(ATOM,VAR) \
283 a = XInternAtom (s->dpy, (ATOM), False); \
284 if (XGetFontProperty (font, a, &value)) \
285 VAR = XGetAtomName (s->dpy, value); \
289 # define INT(ATOM,VAR) \
291 a = XInternAtom (s->dpy, (ATOM), False); \
292 if (!XGetFontProperty (font, a, &VAR) || \
296 STR ("FOUNDRY", foundry);
297 STR ("FAMILY_NAME", family);
298 STR ("WEIGHT_NAME", weight);
299 STR ("SLANT", slant);
300 STR ("SETWIDTH_NAME", setwidth);
301 STR ("ADD_STYLE_NAME", add_style);
302 INT ("PIXEL_SIZE", pixel);
303 INT ("POINT_SIZE", point);
304 INT ("RESOLUTION_X", res_x);
305 INT ("RESOLUTION_Y", res_y);
306 STR ("SPACING", spacing);
307 INT ("AVERAGE_WIDTH", avg_width);
308 STR ("CHARSET_REGISTRY", registry);
309 STR ("CHARSET_ENCODING", encoding);
314 pixel = pick_font_size (s);
317 /* Occasionally change the aspect ratio of the font, by increasing
318 either the X or Y resolution (while leaving the other alone.)
320 #### Looks like this trick doesn't really work that well: the
321 metrics of the individual characters are ok, but the
322 overall font ascent comes out wrong (unscaled.)
324 if (! (random() % 8))
327 double scale = 1 + (frand(n) + frand(n) + frand(n));
336 "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
337 foundry, family, weight, slant, setwidth, add_style,
338 pixel, "*", /* point, */
339 res_x, res_y, spacing,
346 fprintf (stderr, "%s: font has bogus %s property: %s\n",
347 progname, bogus, names[i]);
349 if (foundry) XFree (foundry);
350 if (family) XFree (family);
351 if (weight) XFree (weight);
352 if (slant) XFree (slant);
353 if (setwidth) XFree (setwidth);
354 if (add_style) XFree (add_style);
355 if (spacing) XFree (spacing);
356 if (registry) XFree (registry);
357 if (encoding) XFree (encoding);
362 XFreeFontInfo (names2, info, count2);
363 XFreeFontNames (names);
365 # else /* HAVE_JWXYZ */
367 if (s->font_override)
368 sprintf (pattern, "%.200s", s->font_override);
371 const char *family = "random";
372 const char *weight = ((random() % 2) ? "regular" : "bold");
373 const char *slant = ((random() % 2) ? "o" : "r");
374 int size = 10 * pick_font_size (s);
375 sprintf (pattern, "*-%s-%s-%s-*-*-*-%d-*", family, weight, slant, size);
378 # endif /* HAVE_JWXYZ */
380 if (! ok) return False;
382 se->xftfont = XftFontOpenXlfd (s->dpy, screen_number (s->xgwa.screen),
389 fprintf (stderr, "%s: unable to load font %s\n",
395 strcpy (pattern2, pattern);
398 char *out = pattern2 + strlen(pattern2);
401 out = append_font_name (s->dpy, out + 2, se->xftfont->xfont);
408 if (s->prev_font_name) free (s->prev_font_name);
409 s->prev_font_name = s->next_font_name;
410 s->next_font_name = strdup (pattern2);
413 /* Sometimes we get fonts with screwed up metrics. For example:
414 -b&h-lucida-medium-r-normal-sans-40-289-100-100-p-0-iso8859-1
416 When using XDrawString, XTextExtents and XTextExtents16, it is rendered
417 as a scaled-up bitmap font. The character M has rbearing 70, ascent 68
418 and width 78, which is correct for the glyph as rendered.
420 But when using XftDrawStringUtf8 and XftTextExtentsUtf8, it is rendered
421 at the original, smaller, un-scaled size, with rbearing 26, ascent 25
424 So it's taking the *size* from the unscaled font, the *advancement* from
425 the scaled-up version, and then *not* actually scaling it up. Awesome.
427 So, after loading the font, measure the M, and if its advancement is more
428 than 20% larger than its rbearing, reject the font.
430 ------------------------------------------------------------------------
432 Some observations on this nonsense from Dave Odell:
434 1. -*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1 normally
435 resolves to /usr/share/fonts/X11/100dpi/lutBS24-ISO8859-1.pcf.gz.
437 -*-lucidatypewriter-* is from the 'xfonts-100dpi' package in
438 Debian/Ubuntu. It's usually (54.46% of systems), but not always,
439 installed whenever an X.org server (57.96% of systems) is. It might
440 be a good idea for this and xfonts-75dpi to be recommended
441 dependencies of XScreenSaver in Debian, but that's neither here nor
442 there. https://qa.debian.org/popcon.php?package=xorg
443 https://qa.debian.org/popcon.php?package=xfonts-100dpi
445 2. It normally resolves to the PCF font... but not always.
447 Fontconfig has /etc/fonts/conf.d/ (it's /opt/local/etc/fonts/conf.d/
448 with MacPorts) containing symlinks to configuration files. And both
449 Debian and Ubuntu normally has a 70-no-bitmaps.conf, installed as part
450 of the 'fontconfig-config' package. And the 70-no-bitmaps.conf
451 symlink... disables bitmap fonts.
453 Without bitmap fonts, I get DejaVu Sans.
455 3. There's another symlink of interest here:
456 /etc/fonts/conf.d/10-scale-bitmap-fonts.conf. This adds space to the
457 right of glyphs of bitmap fonts when the requested size of the font is
458 larger than the actual bitmap font. Ubuntu and MacPorts has this one.
460 This specifically is causing text to have excessive character spacing.
462 (jwz asks: WHY WOULD ANYONE EVER WANT THIS BEHAVIOR?)
464 4. Notice that I'm only talking about Debian and Ubuntu. Other distros
465 will probably have different symlinks in /etc/fonts/conf.d/. So yes,
466 this can be an issue on Linux as well as MacOS.
474 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) "M", 1, &extents);
475 rbearing = extents.width - extents.x;
476 width = extents.xOff;
477 ratio = rbearing / (float) width;
481 fprintf (stderr, "%s: M ratio %.2f (%d %d): %s\n", progname,
482 ratio, rbearing, width, pattern2);
485 if (ratio < min && !s->font_override)
489 fprintf (stderr, "%s: skipping font with broken metrics: %s\n",
499 fprintf(stderr, "%s: %s\n", progname, pattern2);
502 se->font_name = strdup (pattern);
507 /* Finds the set of scalable fonts on the system; picks one;
508 and loads that font in a random pixel size.
511 pick_font (state *s, sentence *se)
514 for (i = 0; i < 50; i++)
515 if (pick_font_1 (s, se))
517 fprintf (stderr, "%s: too many font-loading failures: giving up!\n",
523 static char *unread_word_text = 0;
525 /* Returns a newly-allocated string with one word in it, or NULL if there
526 is no complete word available.
529 get_word_text (state *s)
531 const char *start = s->buf;
538 /* If we just launched, and haven't had any text yet, and it has been
539 more than 2 seconds since we launched, then push out "Loading..."
540 as our first text. So if the text source is speedy, just use that.
541 But if we'd display a blank screen for a while, give 'em something
547 s->start_time < ((time ((time_t *) 0) - 2)))
549 unread_word_text = "Loading...";
553 if (unread_word_text)
555 result = unread_word_text;
556 unread_word_text = 0;
557 return strdup (result);
560 /* Skip over whitespace at the beginning of the buffer,
561 and count up how many linebreaks we see while doing so.
569 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
576 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
577 to trigger a sentence break here.) */
581 /* Skip forward to the end of this word (find next whitespace.) */
589 /* If we have a word, allocate a string for it */
592 result = malloc ((end - start) + 1);
593 strncpy (result, start, (end-start));
594 result [end-start] = 0;
599 /* Make room in the buffer by compressing out any bytes we've processed.
603 int n = end - s->buf;
604 memmove (s->buf, end, sizeof(s->buf) - n);
612 /* Returns a 1-bit pixmap of the same size as the drawable,
613 with a 0 wherever the drawable is black.
616 make_mask (Screen *screen, Visual *visual, Drawable drawable)
618 Display *dpy = DisplayOfScreen (screen);
619 unsigned long black = BlackPixelOfScreen (screen);
622 unsigned int w, h, bw, d;
627 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bw, &d);
628 in = XGetImage (dpy, drawable, 0, 0, w, h, ~0L, ZPixmap);
629 out = XCreateImage (dpy, visual, 1, XYPixmap, 0, 0, w, h, 8, 0);
630 out->data = (char *) malloc (h * out->bytes_per_line);
631 for (y = 0; y < h; y++)
632 for (x = 0; x < w; x++)
633 XPutPixel (out, x, y, (black != XGetPixel (in, x, y)));
634 mask = XCreatePixmap (dpy, drawable, w, h, 1L);
635 gc = XCreateGC (dpy, mask, 0, 0);
636 XPutImage (dpy, mask, gc, out, 0, 0, 0, 0, w, h);
640 in->data = out->data = 0;
647 /* Gets some random text, and creates a "word" object from it.
650 new_word (state *s, sentence *se, const char *txt, Bool alloc_p)
654 int bw = s->border_width;
659 w = (word *) calloc (1, sizeof(*w));
660 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) txt, strlen(txt),
663 w->lbearing = -extents.x;
664 w->rbearing = extents.width - extents.x;
665 w->ascent = extents.y;
666 w->descent = extents.height - extents.y;
667 w->width = extents.xOff;
674 if (s->mode == SCROLL && !alloc_p) abort();
680 GC gc_fg, gc_bg, gc_black;
682 int width = w->rbearing - w->lbearing;
683 int height = w->ascent + w->descent;
685 if (width <= 0) width = 1;
686 if (height <= 0) height = 1;
688 w->pixmap = XCreatePixmap (s->dpy, s->b, width, height, s->xgwa.depth);
689 xftdraw = XftDrawCreate (s->dpy, w->pixmap, s->xgwa.visual,
692 gcv.foreground = se->xftcolor_fg.pixel;
693 gc_fg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
695 gcv.foreground = se->xftcolor_bg.pixel;
696 gc_bg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
698 gcv.foreground = BlackPixelOfScreen (s->xgwa.screen);
699 gc_black = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
701 XFillRectangle (s->dpy, w->pixmap, gc_black, 0, 0, width, height);
706 /* bounding box (behind the characters) */
707 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
708 0, 0, width-1, height-1);
712 /* Draw background text for border */
713 for (i = -bw; i <= bw; i++)
714 for (j = -bw; j <= bw; j++)
715 XftDrawStringUtf8 (xftdraw, &se->xftcolor_bg, se->xftfont,
716 -w->lbearing + i, w->ascent + j,
717 (FcChar8 *) txt, strlen(txt));
719 /* Draw foreground text */
720 XftDrawStringUtf8 (xftdraw, &se->xftcolor_fg, se->xftfont,
721 -w->lbearing, w->ascent,
722 (FcChar8 *) txt, strlen(txt));
727 if (w->ascent != height)
729 /* baseline (on top of the characters) */
730 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
731 0, w->ascent, width-1, w->ascent);
736 /* left edge of charcell */
737 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
739 -w->lbearing, height-1);
742 if (w->rbearing != w->width)
744 /* right edge of charcell */
745 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
746 w->width - w->lbearing, 0,
747 w->width - w->lbearing, height-1);
752 w->mask = make_mask (s->xgwa.screen, s->xgwa.visual, w->pixmap);
754 XftDrawDestroy (xftdraw);
755 XFreeGC (s->dpy, gc_fg);
756 XFreeGC (s->dpy, gc_bg);
757 XFreeGC (s->dpy, gc_black);
760 w->text = strdup (txt);
766 free_word (state *s, word *w)
768 if (w->text) free (w->text);
769 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
770 if (w->mask) XFreePixmap (s->dpy, w->mask);
775 new_sentence (state *st, state *s)
778 sentence *se = (sentence *) calloc (1, sizeof (*se));
779 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
781 se->id = ++st->id_tick;
787 free_sentence (state *s, sentence *se)
790 for (i = 0; i < se->nwords; i++)
791 free_word (s, se->words[i]);
795 free (se->font_name);
797 XFreeGC (s->dpy, se->fg_gc);
801 XftFontClose (s->dpy, se->xftfont);
802 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
804 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
812 /* free the word, and put its text back at the front of the input queue,
813 to be read next time. */
815 unread_word (state *s, word *w)
817 if (unread_word_text)
819 unread_word_text = w->text;
825 /* Divide each of the words in the sentence into one character words,
826 without changing the positions of those characters.
829 split_words (state *s, sentence *se)
835 char ***word_chars = (char ***) malloc (se->nwords * sizeof(*word_chars));
836 for (i = 0; i < se->nwords; i++)
839 word *ow = se->words[i];
840 word_chars[i] = utf8_split (ow->text, &L);
844 words2 = (word **) calloc (nwords2, sizeof(*words2));
846 for (i = 0, j = 0; i < se->nwords; i++)
848 char **chars = word_chars[i];
849 word *parent = se->words[i];
852 int sx = parent->start_x;
853 int sy = parent->start_y;
854 int tx = parent->target_x;
855 int ty = parent->target_y;
858 for (k = 0; chars[k]; k++)
861 word *w2 = new_word (s, se, t2, True);
876 /* This is not invariant when kerning is involved! */
877 /* if (x != parent->x + parent->width) abort(); */
879 free (chars); /* but we retain its contents */
880 free_word (s, parent);
882 if (j != nwords2) abort();
887 se->nwords = nwords2;
891 /* Set the source or destination position of the words to be somewhere
895 scatter_sentence (state *s, sentence *se)
898 int off = s->border_width * 4 + 2;
900 int flock_p = ((random() % 4) == 0);
901 int mode = (flock_p ? (random() % 12) : 0);
903 for (i = 0; i < se->nwords; i++)
905 word *w = se->words[i];
907 int r = (flock_p ? mode : (random() % 4));
908 int left = -(off + w->rbearing);
909 int top = -(off + w->descent);
910 int right = off - w->lbearing + s->xgwa.width;
911 int bottom = off + w->ascent + s->xgwa.height;
914 /* random positions on the edges */
915 case 0: x = left; y = random() % s->xgwa.height; break;
916 case 1: x = right; y = random() % s->xgwa.height; break;
917 case 2: x = random() % s->xgwa.width; y = top; break;
918 case 3: x = random() % s->xgwa.width; y = bottom; break;
920 /* straight towards the edges */
921 case 4: x = left; y = w->target_y; break;
922 case 5: x = right; y = w->target_y; break;
923 case 6: x = w->target_x; y = top; break;
924 case 7: x = w->target_x; y = bottom; break;
927 case 8: x = left; y = top; break;
928 case 9: x = left; y = bottom; break;
929 case 10: x = right; y = top; break;
930 case 11: x = right; y = bottom; break;
932 default: abort(); break;
935 if (se->anim_state == IN)
948 w->nticks = ((100 + ((random() % 140) +
959 /* Set the source position of the words to be off the right side,
960 and the destination to be off the left side.
963 aim_sentence (state *s, sentence *se)
969 if (se->nwords <= 0) abort();
971 /* Have the sentence shift up or down a little bit; not too far, and
972 never let it fall off the top or bottom of the screen before its
973 last character has reached the left edge.
975 for (i = 0; i < 10; i++)
977 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
978 yoff = ty - se->words[0]->target_y;
979 if (yoff < s->xgwa.height/3) /* this one is ok */
983 for (i = 0; i < se->nwords; i++)
985 word *w = se->words[i];
986 w->start_x = w->target_x + s->xgwa.width;
987 w->target_x -= se->width;
988 w->start_y = w->target_y;
992 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
994 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
999 for (i = 0; i < se->nwords; i++)
1001 word *w = se->words[i];
1008 /* Randomize the order of the words in the list (since that changes
1009 which ones are "on top".)
1012 shuffle_words (state *s, sentence *se)
1015 for (i = 0; i < se->nwords-1; i++)
1017 int j = i + (random() % (se->nwords - i));
1018 word *swap = se->words[i];
1019 se->words[i] = se->words[j];
1020 se->words[j] = swap;
1025 /* qsort comparitor */
1027 cmp_sentences (const void *aa, const void *bb)
1029 const sentence *a = *(sentence **) aa;
1030 const sentence *b = *(sentence **) bb;
1031 return ((a ? a->id : 999999) - (b ? b->id : 999999));
1035 /* Sort the sentences by id, so that sentences added later are on top.
1038 sort_sentences (state *s)
1040 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
1044 /* Re-pick the colors of the text and border
1047 recolor (state *s, sentence *se)
1049 XRenderColor fg, bg;
1051 fg.red = (random() % 0x5555) + 0xAAAA;
1052 fg.green = (random() % 0x5555) + 0xAAAA;
1053 fg.blue = (random() % 0x5555) + 0xAAAA;
1055 bg.red = (random() % 0x5555);
1056 bg.green = (random() % 0x5555);
1057 bg.blue = (random() % 0x5555);
1063 XRenderColor swap = fg; fg = bg; bg = swap;
1069 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1071 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1075 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &fg,
1077 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &bg,
1083 align_line (state *s, sentence *se, int line_start, int x, int right)
1086 switch (se->alignment)
1088 case LEFT: off = 0; break;
1089 case CENTER: off = (right - x) / 2; break;
1090 case RIGHT: off = (right - x); break;
1091 default: abort(); break;
1095 for (j = line_start; j < se->nwords; j++)
1096 se->words[j]->target_x += off;
1100 /* Fill the sentence with new words: in "page" mode, fills the page
1101 with text; in "scroll" mode, just makes one long horizontal sentence.
1102 The sentence might have *no* words in it, if no text is currently
1106 populate_sentence (state *s, sentence *se)
1109 int left, right, top, x, y;
1114 int array_size = 100;
1116 se->move_chars_p = (s->mode == CHARS ? True :
1117 s->mode == SCROLL ? False :
1118 (random() % 3) ? False : True);
1119 se->alignment = (random() % 3);
1125 for (i = 0; i < se->nwords; i++)
1126 free_word (s, se->words[i]);
1130 se->words = (word **) calloc (array_size, sizeof(*se->words));
1137 left = random() % (s->xgwa.width / 3);
1138 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1139 top = random() % (s->xgwa.height * 2 / 3);
1143 right = s->xgwa.width;
1144 top = random() % s->xgwa.height;
1156 char *txt = get_word_text (s);
1160 if (se->nwords == 0)
1161 return; /* If the stream is empty, bail. */
1163 break; /* If EOF after some words, end of sentence. */
1166 if (! se->xftfont) /* Got a word: need a font now */
1170 if (y < se->xftfont->ascent)
1171 y += se->xftfont->ascent;
1173 /* Measure the space character to figure out how much room to
1174 leave between words (since we don't actually render that.) */
1175 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) " ", 1,
1177 space = extents.xOff;
1180 w = new_word (s, se, txt, !se->move_chars_p);
1184 /* If we have a few words, let punctuation terminate the sentence:
1185 stop gathering more words if the last word ends in a period, etc. */
1186 if (se->nwords >= 4)
1188 char c = w->text[strlen(w->text)-1];
1189 if (c == '.' || c == '?' || c == '!')
1193 /* If the sentence is kind of long already, terminate at commas, etc. */
1194 if (se->nwords >= 12)
1196 char c = w->text[strlen(w->text)-1];
1197 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1198 c == ')' || c == ']' || c == '}')
1202 if (se->nwords >= 25) /* ok that's just about enough out of you */
1205 if ((s->mode == PAGE || s->mode == CHARS) &&
1206 x + w->rbearing > right) /* wrap line */
1208 align_line (s, se, line_start, x, right);
1209 line_start = se->nwords;
1212 y += se->xftfont->ascent + se->xftfont->descent;
1214 /* If we're close to the bottom of the screen, stop, and
1215 unread the current word. (But not if this is the first
1216 word, otherwise we might just get stuck on it.)
1218 if (se->nwords > 0 &&
1219 y + se->xftfont->ascent + se->xftfont->descent > s->xgwa.height)
1231 x += w->width + space;
1234 if (se->nwords >= (array_size - 1))
1237 se->words = (word **)
1238 realloc (se->words, array_size * sizeof(*se->words));
1241 fprintf (stderr, "%s: out of memory (%d words)\n",
1242 progname, array_size);
1247 se->words[se->nwords++] = w;
1256 align_line (s, se, line_start, x, right);
1257 if (se->move_chars_p)
1258 split_words (s, se);
1259 scatter_sentence (s, se);
1260 shuffle_words (s, se);
1263 aim_sentence (s, se);
1273 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1274 for (i = 0; i < se->nwords; i++)
1275 fprintf (stderr, " %s", se->words[i]->text);
1276 fprintf (stderr, "\n");
1282 /* Render a single word object to the screen.
1285 draw_word (state *s, sentence *se, word *word)
1288 if (! word->pixmap) return;
1290 x = word->x + word->lbearing;
1291 y = word->y - word->ascent;
1292 w = word->rbearing - word->lbearing;
1293 h = word->ascent + word->descent;
1297 x > s->xgwa.width ||
1301 XSetClipMask (s->dpy, se->fg_gc, word->mask);
1302 XSetClipOrigin (s->dpy, se->fg_gc, x, y);
1303 XCopyArea (s->dpy, word->pixmap, s->b, se->fg_gc,
1308 /* If there is room for more sentences, add one.
1311 more_sentences (state *s)
1315 for (i = 0; i < s->nsentences; i++)
1317 sentence *se = s->sentences[i];
1320 se = new_sentence (s, s);
1321 populate_sentence (s, se);
1323 s->spawn_p = False, any = True;
1326 free_sentence (s, se);
1329 s->sentences[i] = se;
1331 s->latest_sentence = se->id;
1336 if (any) sort_sentences (s);
1340 /* Render all the words to the screen, and run the animation one step.
1343 draw_sentence (state *s, sentence *se)
1350 for (i = 0; i < se->nwords; i++)
1352 word *w = se->words[i];
1358 if (se->anim_state != PAUSE &&
1359 w->tick <= w->nticks)
1361 int dx = w->target_x - w->start_x;
1362 int dy = w->target_y - w->start_y;
1363 double r = sin (w->tick * M_PI / (2 * w->nticks));
1364 w->x = w->start_x + (dx * r);
1365 w->y = w->start_y + (dy * r);
1368 if (se->anim_state == OUT &&
1369 (s->mode == PAGE || s->mode == CHARS))
1370 w->tick++; /* go out faster */
1376 int dx = w->target_x - w->start_x;
1377 int dy = w->target_y - w->start_y;
1378 double r = (double) w->tick / w->nticks;
1379 w->x = w->start_x + (dx * r);
1380 w->y = w->start_y + (dy * r);
1382 moved = (w->tick <= w->nticks);
1384 /* Launch a new sentence when:
1385 - the front of this sentence is almost off the left edge;
1386 - the end of this sentence is almost on screen.
1389 if (se->anim_state != OUT &&
1391 se->id == s->latest_sentence)
1393 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1394 w->x + se->width < (s->xgwa.width * 2.1));
1395 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1397 if (new_p || rand_p)
1399 se->anim_state = OUT;
1403 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1405 se->words[0]->x + se->width,
1406 rand_p ? " randomly" : "");
1417 draw_word (s, se, w);
1420 if (moved && se->anim_state == PAUSE)
1425 switch (se->anim_state)
1428 se->anim_state = PAUSE;
1429 se->pause_tick = (se->nwords * 7 * s->linger);
1430 if (se->move_chars_p)
1431 se->pause_tick /= 5;
1432 scatter_sentence (s, se);
1433 shuffle_words (s, se);
1436 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1440 if (--se->pause_tick <= 0)
1442 se->anim_state = OUT;
1446 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1453 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1457 for (j = 0; j < s->nsentences; j++)
1458 if (s->sentences[j] == se)
1459 s->sentences[j] = 0;
1460 free_sentence (s, se);
1471 #ifdef DEBUG /* All of this stuff is for -debug-metrics mode. */
1475 scale_ximage (Screen *screen, Window window, XImage *img, int scale,
1478 Display *dpy = DisplayOfScreen (screen);
1480 unsigned width = img->width, height = img->height;
1484 p2 = XCreatePixmap (dpy, window, width*scale, height*scale, img->depth);
1485 gc = XCreateGC (dpy, p2, 0, 0);
1487 XSetForeground (dpy, gc, BlackPixelOfScreen (screen));
1488 XFillRectangle (dpy, p2, gc, 0, 0, width*scale, height*scale);
1489 for (y = 0; y < height; y++)
1490 for (x = 0; x < width; x++)
1492 XSetForeground (dpy, gc, XGetPixel (img, x, y));
1493 XFillRectangle (dpy, p2, gc, x*scale, y*scale, scale, scale);
1498 XWindowAttributes xgwa;
1500 c.red = c.green = c.blue = 0x4444;
1501 c.flags = DoRed|DoGreen|DoBlue;
1502 XGetWindowAttributes (dpy, window, &xgwa);
1503 if (! XAllocColor (dpy, xgwa.colormap, &c)) abort();
1504 XSetForeground (dpy, gc, c.pixel);
1505 XDrawRectangle (dpy, p2, gc, 0, 0, width*scale-1, height*scale-1);
1506 XDrawRectangle (dpy, p2, gc, margin*scale, margin*scale,
1507 width*scale-1, height*scale-1);
1508 for (y = 0; y <= height - 2*margin; y++)
1509 XDrawLine (dpy, p2, gc,
1510 margin*scale, (y+margin)*scale-1,
1511 (width-margin)*scale, (y+margin)*scale-1);
1512 for (x = 0; x <= width - 2*margin; x++)
1513 XDrawLine (dpy, p2, gc,
1514 (x+margin)*scale-1, margin*scale,
1515 (x+margin)*scale-1, (height-margin)*scale);
1516 XFreeColors (dpy, xgwa.colormap, &c.pixel, 1, 0);
1524 static int check_edge (Display *dpy, Drawable p, GC gc,
1525 unsigned msg_x, unsigned msg_y, const char *msg,
1527 unsigned x, unsigned y, unsigned dim, unsigned end)
1538 XDrawString (dpy, p, gc, msg_x, msg_y, msg, strlen (msg));
1542 if (XGetPixel(img, pt[0], pt[1]) & 0xffffff)
1552 static unsigned long
1553 fontglide_draw_metrics (state *s)
1555 unsigned int margin = (s->debug_metrics_antialiasing_p ? 2 : 0);
1557 char txt[2], utxt[3], txt2[80];
1559 const char *fn = (s->font_override ? s->font_override : "fixed");
1561 XCharStruct c, overall, fake_c;
1562 int dir, ascent, descent;
1567 int sc = s->debug_scale;
1569 unsigned long red = 0xFFFF0000; /* so shoot me */
1570 unsigned long green = 0xFF00FF00;
1571 unsigned long blue = 0xFF6666FF;
1572 unsigned long yellow = 0xFFFFFF00;
1573 unsigned long cyan = 0xFF004040;
1575 Drawable dest = s->b ? s->b : s->window;
1579 /* Self-test these macros to make sure they're symmetrical. */
1580 for (i = 0; i < 1000; i++)
1584 c.lbearing = (random()%50)-100;
1585 c.rbearing = (random()%50)-100;
1586 c.ascent = (random()%50)-100;
1587 c.descent = (random()%50)-100;
1588 c.width = (random()%50)-100;
1589 XCharStruct_to_XGlyphInfo (c, g);
1590 XGlyphInfo_to_XCharStruct (g, overall);
1591 if (c.lbearing != overall.lbearing) abort();
1592 if (c.rbearing != overall.rbearing) abort();
1593 if (c.ascent != overall.ascent) abort();
1594 if (c.descent != overall.descent) abort();
1595 if (c.width != overall.width) abort();
1596 XCharStruct_to_XGlyphInfo (overall, g2);
1597 if (g.x != g2.x) abort();
1598 if (g.y != g2.y) abort();
1599 if (g.xOff != g2.xOff) abort();
1600 if (g.yOff != g2.yOff) abort();
1601 if (g.width != g2.width) abort();
1602 if (g.height != g2.height) abort();
1603 XCharStruct_to_XmbRectangle (overall, r);
1604 XmbRectangle_to_XCharStruct (r, c, c.width);
1605 if (c.lbearing != overall.lbearing) abort();
1606 if (c.rbearing != overall.rbearing) abort();
1607 if (c.ascent != overall.ascent) abort();
1608 if (c.descent != overall.descent) abort();
1609 if (c.width != overall.width) abort();
1612 txt[0] = s->debug_metrics_p;
1615 /* Convert Unicode code point to UTF-8. */
1616 utxt[utf8_encode(s->debug_metrics_p, utxt, 4)] = 0;
1618 txt3 = utf8_to_XChar2b (utxt, 0);
1620 if (! s->metrics_font1)
1621 s->metrics_font1 = XLoadQueryFont (s->dpy, fn);
1622 if (! s->metrics_font2)
1623 s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
1624 if (! s->metrics_font1)
1625 s->metrics_font1 = s->metrics_font2;
1627 gc = XCreateGC (s->dpy, dest, 0, 0);
1628 XSetFont (s->dpy, gc, s->metrics_font1->fid);
1630 # if defined(HAVE_JWXYZ)
1631 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1634 if (! s->metrics_xftfont)
1636 s->metrics_xftfont =
1637 XftFontOpenXlfd (s->dpy, screen_number(s->xgwa.screen), fn);
1638 if (! s->metrics_xftfont)
1640 const char *fn2 = "fixed";
1641 s->metrics_xftfont =
1642 XftFontOpenName (s->dpy, screen_number(s->xgwa.screen), fn2);
1643 if (s->metrics_xftfont)
1647 fprintf (stderr, "%s: XftFontOpen failed on \"%s\" and \"%s\"\n",
1656 append_font_name (s->dpy, fn2, s->metrics_xftfont->xfont);
1659 xftdraw = XftDrawCreate (s->dpy, dest, s->xgwa.visual,
1661 XftColorAllocName (s->dpy, s->xgwa.visual, s->xgwa.colormap, "white",
1663 XftTextExtentsUtf8 (s->dpy, s->metrics_xftfont,
1664 (FcChar8 *) utxt, strlen(utxt),
1668 XTextExtents (s->metrics_font1, txt, strlen(txt),
1669 &dir, &ascent, &descent, &overall);
1670 c = ((s->debug_metrics_p >= s->metrics_font1->min_char_or_byte2 &&
1671 s->debug_metrics_p <= s->metrics_font1->max_char_or_byte2)
1672 ? s->metrics_font1->per_char[s->debug_metrics_p -
1673 s->metrics_font1->min_char_or_byte2]
1676 XSetForeground (s->dpy, gc, BlackPixelOfScreen (s->xgwa.screen));
1677 XFillRectangle (s->dpy, dest, gc, 0, 0, s->xgwa.width, s->xgwa.height);
1679 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1680 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1681 XDrawString (s->dpy, dest, gc,
1688 char *name = jwxyz_unicode_character_name (
1689 s->dpy, s->metrics_font1->fid, s->debug_metrics_p);
1690 if (!name || !*name) name = strdup("unknown character name");
1691 XDrawString (s->dpy, dest, gc,
1693 10 + 2 * (s->metrics_font2->ascent +
1694 s->metrics_font2->descent),
1695 name, strlen(name));
1700 /* i 0, j 0: top left, XDrawString, char metrics
1701 i 1, j 0: bot left, XDrawString, overall metrics, ink escape
1702 i 0, j 1: top right, XftDrawStringUtf8, utf8 metrics
1703 i 1, j 1: bot right, XDrawString16, 16 metrics, ink escape
1705 for (j = 0; j < 2; j++) {
1706 Bool xft_p = (j != 0);
1707 int ww = s->xgwa.width / 2 - 20;
1708 int xoff = (j == 0 ? 0 : ww + 20);
1710 /* XDrawString only does 8-bit characters, so skip it outside Latin-1. */
1711 if (s->debug_metrics_p >= 256)
1719 x = (ww - overall.width) / 2;
1721 for (i = 0; i < 2; i++)
1724 int x1 = xoff + ww * 0.18;
1725 int x2 = xoff + ww * 0.82;
1732 int h = sc * (ascent + descent);
1733 int min = (ascent + descent) * 4;
1734 if (h < min) h = min;
1739 memset (&fake_c, 0, sizeof(fake_c));
1741 if (!xft_p && i == 0)
1743 else if (!xft_p && i == 1)
1745 else if (xft_p && i == 0)
1747 /* Measure the glyph in the Xft way */
1749 XftTextExtentsUtf8 (s->dpy,
1751 (FcChar8 *) utxt, strlen(utxt),
1753 XGlyphInfo_to_XCharStruct (extents, fake_c);
1758 /* Measure the glyph in the 16-bit way */
1759 int dir, ascent, descent;
1760 XTextExtents16 (s->metrics_font1, txt3, 1, &dir, &ascent, &descent,
1765 pixw = margin * 2 + cc.rbearing - cc.lbearing;
1766 pixh = margin * 2 + cc.ascent + cc.descent;
1767 p = (pixw > 0 && pixh > 0
1768 ? XCreatePixmap (s->dpy, dest, pixw, pixh, s->xgwa.depth)
1774 GC gc2 = XCreateGC (s->dpy, p, 0, 0);
1776 jwxyz_XSetAntiAliasing (s->dpy, gc2, False);
1778 XSetFont (s->dpy, gc2, s->metrics_font1->fid);
1779 XSetForeground (s->dpy, gc2, BlackPixelOfScreen (s->xgwa.screen));
1780 XFillRectangle (s->dpy, p, gc2, 0, 0, pixw, pixh);
1781 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1782 XSetForeground (s->dpy, gc2, WhitePixelOfScreen (s->xgwa.screen));
1784 jwxyz_XSetAntiAliasing (s->dpy, gc2,
1785 s->debug_metrics_antialiasing_p);
1788 if (xft_p && i == 0)
1790 XftDraw *xftdraw2 = XftDrawCreate (s->dpy, p, s->xgwa.visual,
1792 XftDrawStringUtf8 (xftdraw2, &xftcolor,
1794 -cc.lbearing + margin,
1796 (FcChar8 *) utxt, strlen(utxt));
1797 XftDrawDestroy (xftdraw2);
1800 XDrawString16 (s->dpy, p, gc2,
1801 -cc.lbearing + margin,
1805 XDrawString (s->dpy, p, gc2,
1806 -cc.lbearing + margin,
1812 XImage *img = XGetImage (s->dpy, p, 0, 0, pixw, pixh,
1818 unsigned w = pixw - margin * 2, h = pixh - margin * 2;
1822 /* Check for ink escape. */
1823 unsigned long ink = 0;
1824 for (y2 = 0; y2 != pixh; ++y2)
1825 for (x2 = 0; x2 != pixw; ++x2)
1828 if (! (x2 >= margin &&
1829 x2 < pixw - margin &&
1831 y2 < pixh - margin))
1832 ink |= XGetPixel (img, x2, y2);
1837 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1838 XDrawString (s->dpy, dest, gc,
1844 /* ...And wasted space. */
1847 if (check_edge (s->dpy, dest, gc, 120, 60, "left",
1848 img, margin, margin, 1, h) |
1849 check_edge (s->dpy, dest, gc, 160, 60, "right",
1850 img, margin + w - 1, margin, 1, h) |
1851 check_edge (s->dpy, dest, gc, 200, 60, "top",
1852 img, margin, margin, 0, w) |
1853 check_edge (s->dpy, dest, gc, 240, 60, "bottom",
1854 img, margin, margin + h - 1, 0, w))
1856 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1857 XDrawString (s->dpy, dest, gc,
1859 "Wasted space: ", 14);
1864 if (s->debug_metrics_antialiasing_p)
1866 /* Draw a dark cyan boundary around antialiased glyphs */
1867 img2 = XCreateImage (s->dpy, s->xgwa.visual, img->depth,
1869 img->width, img->height,
1870 img->bitmap_pad, 0);
1871 img2->data = malloc (img->bytes_per_line * img->height);
1873 for (y2 = 0; y2 != pixh; ++y2)
1874 for (x2 = 0; x2 != pixw; ++x2)
1876 unsigned long px = XGetPixel (img, x2, y2);
1877 if ((px & 0xffffff) == 0)
1879 unsigned long neighbors = 0;
1881 neighbors |= XGetPixel (img, x2 - 1, y2);
1883 neighbors |= XGetPixel (img, x2 + 1, y2);
1885 neighbors |= XGetPixel (img, x2, y2 - 1);
1887 neighbors |= XGetPixel (img, x2, y2 + 1);
1888 XPutPixel (img2, x2, y2,
1889 (neighbors & 0xffffff
1891 : BlackPixelOfScreen (s->xgwa.screen)));
1895 XPutPixel (img2, x2, y2, px);
1905 p2 = scale_ximage (s->xgwa.screen, s->window, img2, sc, margin);
1907 XDestroyImage (img);
1908 XDestroyImage (img2);
1911 XCopyArea (s->dpy, p2, dest, gc,
1912 0, 0, sc*pixw, sc*pixh,
1913 xoff + x + sc * (cc.lbearing - margin),
1914 y - sc * (cc.ascent + margin));
1915 XFreePixmap (s->dpy, p);
1916 XFreePixmap (s->dpy, p2);
1917 XFreeGC (s->dpy, gc2);
1922 XSetFont (s->dpy, gc, s->metrics_font1->fid);
1923 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1925 jwxyz_XSetAntiAliasing (s->dpy, gc, s->debug_metrics_antialiasing_p);
1927 sprintf (txt2, "%s [XX%sXX] [%s%s%s%s]",
1928 (xft_p ? utxt : txt),
1929 (xft_p ? utxt : txt),
1930 (xft_p ? utxt : txt),
1931 (xft_p ? utxt : txt),
1932 (xft_p ? utxt : txt),
1933 (xft_p ? utxt : txt));
1936 XftDrawStringUtf8 (xftdraw, &xftcolor,
1938 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2
1941 (FcChar8 *) txt2, strlen(txt2));
1943 XDrawString (s->dpy, dest, gc,
1944 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2,
1946 txt2, strlen(txt2));
1948 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1950 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1954 char *tptr = txt2 + sprintf(txt2, "U+%04lX", s->debug_metrics_p);
1955 *tptr++ = " ?_"[s->entering_unicode_p];
1961 tptr += sprintf (tptr, "0%03o ", (unsigned char) *uptr);
1968 tptr += sprintf (tptr, "%02x ", (unsigned char) *uptr);
1973 sprintf (txt2, "%c %3ld 0%03lo 0x%02lx%s",
1974 (char)s->debug_metrics_p, s->debug_metrics_p,
1975 s->debug_metrics_p, s->debug_metrics_p,
1976 (txt[0] < s->metrics_font1->min_char_or_byte2
1978 XDrawString (s->dpy, dest, gc,
1980 txt2, strlen(txt2));
1984 jwxyz_XSetAntiAliasing (s->dpy, gc, True);
1988 const char *ss = (j == 0
1989 ? (i == 0 ? "char" : "overall")
1990 : (i == 0 ? "utf8" : "16 bit"));
1991 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1993 XSetForeground (s->dpy, gc, red);
1995 sprintf (txt2, "%s ascent %d", ss, ascent);
1996 XDrawString (s->dpy, dest, gc,
1999 txt2, strlen(txt2));
2000 XDrawLine (s->dpy, dest, gc,
2001 xoff, y - sc*ascent,
2004 sprintf (txt2, "%s descent %d", ss, descent);
2005 XDrawString (s->dpy, dest, gc,
2008 txt2, strlen(txt2));
2009 XDrawLine (s->dpy, dest, gc,
2010 xoff, y + sc*descent,
2011 x3, y + sc*descent);
2015 /* ascent, descent, baseline */
2017 XSetForeground (s->dpy, gc, green);
2019 sprintf (txt2, "ascent %d", cc.ascent);
2021 XDrawString (s->dpy, dest, gc,
2022 x1, y - sc*cc.ascent - 2,
2023 txt2, strlen(txt2));
2024 XDrawLine (s->dpy, dest, gc,
2025 x1, y - sc*cc.ascent,
2026 x2, y - sc*cc.ascent);
2028 sprintf (txt2, "descent %d", cc.descent);
2029 if (cc.descent != 0)
2030 XDrawString (s->dpy, dest, gc,
2031 x1, y + sc*cc.descent - 2,
2032 txt2, strlen(txt2));
2033 XDrawLine (s->dpy, dest, gc,
2034 x1, y + sc*cc.descent,
2035 x2, y + sc*cc.descent);
2037 XSetForeground (s->dpy, gc, yellow);
2038 strcpy (txt2, "baseline");
2039 XDrawString (s->dpy, dest, gc,
2041 txt2, strlen(txt2));
2042 XDrawLine (s->dpy, dest, gc, x1, y, x2, y);
2047 XSetForeground (s->dpy, gc, blue);
2049 strcpy (txt2, "origin");
2050 XDrawString (s->dpy, dest, gc,
2052 y + sc*descent + 50,
2053 txt2, strlen(txt2));
2054 XDrawLine (s->dpy, dest, gc,
2055 xoff + x, y - sc*(ascent - 10),
2056 xoff + x, y + sc*(descent + 10));
2058 sprintf (txt2, "width %d", cc.width);
2059 XDrawString (s->dpy, dest, gc,
2060 xoff + x + sc*cc.width + 2,
2061 y + sc*descent + 60,
2062 txt2, strlen(txt2));
2063 XDrawLine (s->dpy, dest, gc,
2064 xoff + x + sc*cc.width, y - sc*(ascent - 10),
2065 xoff + x + sc*cc.width, y + sc*(descent + 10));
2068 /* lbearing, rbearing */
2070 XSetForeground (s->dpy, gc, green);
2072 sprintf (txt2, "lbearing %d", cc.lbearing);
2073 XDrawString (s->dpy, dest, gc,
2074 xoff + x + sc*cc.lbearing + 2,
2075 y + sc * descent + 30,
2076 txt2, strlen(txt2));
2077 XDrawLine (s->dpy, dest, gc,
2078 xoff + x + sc*cc.lbearing, y - sc*ascent,
2079 xoff + x + sc*cc.lbearing, y + sc*descent + 20);
2081 sprintf (txt2, "rbearing %d", cc.rbearing);
2082 XDrawString (s->dpy, dest, gc,
2083 xoff + x + sc*cc.rbearing + 2,
2084 y + sc * descent + 40,
2085 txt2, strlen(txt2));
2086 XDrawLine (s->dpy, dest, gc,
2087 xoff + x + sc*cc.rbearing, y - sc*ascent,
2088 xoff + x + sc*cc.rbearing, y + sc*descent + 40);
2090 /* y += sc * (ascent + descent) * 2; */
2094 if (dest != s->window)
2095 XCopyArea (s->dpy, dest, s->window, s->bg_gc,
2096 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
2098 XFreeGC (s->dpy, gc);
2099 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap, &xftcolor);
2100 XftDrawDestroy (xftdraw);
2103 return s->frame_delay;
2109 /* Render all the words to the screen, and run the animation one step.
2110 Clear screen first, swap buffers after.
2112 static unsigned long
2113 fontglide_draw (Display *dpy, Window window, void *closure)
2115 state *s = (state *) closure;
2119 if (s->debug_metrics_p)
2120 return fontglide_draw_metrics (closure);
2127 XFillRectangle (s->dpy, s->b, s->bg_gc,
2128 0, 0, s->xgwa.width, s->xgwa.height);
2130 for (i = 0; i < s->nsentences; i++)
2131 draw_sentence (s, s->sentences[i]);
2134 if (s->debug_p && (s->prev_font_name || s->next_font_name))
2138 if (! s->metrics_font2)
2139 s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
2140 s->label_gc = XCreateGC (dpy, s->b, 0, 0);
2141 XSetFont (s->dpy, s->label_gc, s->metrics_font2->fid);
2143 if (s->prev_font_name)
2144 XDrawString (s->dpy, s->b, s->label_gc,
2145 10, 10 + s->metrics_font2->ascent,
2146 s->prev_font_name, strlen(s->prev_font_name));
2147 if (s->next_font_name)
2148 XDrawString (s->dpy, s->b, s->label_gc,
2149 10, 10 + s->metrics_font2->ascent * 2,
2150 s->next_font_name, strlen(s->next_font_name));
2154 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2157 XdbeSwapInfo info[1];
2158 info[0].swap_window = s->window;
2159 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
2160 XdbeSwapBuffers (s->dpy, info, 1);
2163 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2166 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
2167 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
2170 return s->frame_delay;
2175 /* When the subprocess has generated some output, this reads as much as it
2176 can into s->buf at s->buf_tail.
2179 drain_input (state *s)
2181 while (s->buf_tail < sizeof(s->buf) - 2)
2183 int c = textclient_getc (s->tc);
2185 s->buf[s->buf_tail++] = (char) c;
2192 /* Window setup and resource loading */
2195 fontglide_init (Display *dpy, Window window)
2198 state *s = (state *) calloc (1, sizeof(*s));
2201 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
2203 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2205 s->font_override = get_string_resource (dpy, "font", "Font");
2206 if (s->font_override && (!*s->font_override || *s->font_override == '('))
2207 s->font_override = 0;
2209 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
2210 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
2211 if (s->border_width < 0 || s->border_width > 20)
2212 s->border_width = 1;
2214 s->speed = get_float_resource (dpy, "speed", "Float");
2215 if (s->speed <= 0 || s->speed > 200)
2218 s->linger = get_float_resource (dpy, "linger", "Float");
2219 if (s->linger <= 0 || s->linger > 200)
2222 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
2225 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
2226 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
2231 if (s->debug_metrics_p && !s->font_override)
2232 s->font_override = "Helvetica Bold 16";
2237 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
2239 # ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */
2244 if (s->debug_metrics_p) s->trails_p = False;
2247 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
2250 const char *ss = get_string_resource (dpy, "mode", "Mode");
2251 if (!ss || !*ss || !strcasecmp (ss, "random"))
2252 s->mode = ((random() % 2) ? SCROLL : PAGE);
2253 else if (!strcasecmp (ss, "scroll"))
2255 else if (!strcasecmp (ss, "page"))
2257 else if (!strcasecmp (ss, "chars") || !strcasecmp (ss, "char"))
2262 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
2269 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2270 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
2272 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
2274 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
2276 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2280 s->ba = XCreatePixmap (s->dpy, s->window,
2281 s->xgwa.width, s->xgwa.height,
2291 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
2292 "background", "Background");
2293 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
2295 s->nsentences = 5; /* #### */
2296 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
2300 s->start_time = time ((time_t *) 0);
2301 s->tc = textclient_open (dpy);
2308 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
2311 state *s = (state *) closure;
2313 if (! s->debug_metrics_p)
2315 if (event->xany.type == KeyPress)
2317 static const unsigned long max = 0x110000;
2320 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2322 if (s->entering_unicode_p > 0)
2325 unsigned long new_char = 0;
2327 if (c >= 'a' && c <= 'f')
2328 digit = c + 0xa - 'a';
2329 else if (c >= 'A' && c <= 'F')
2330 digit = c + 0xa - 'A';
2331 else if (c >= '0' && c <= '9')
2332 digit = c + 0 - '0';
2335 s->entering_unicode_p = 0;
2339 if (s->entering_unicode_p == 1)
2341 else if (s->entering_unicode_p == 2)
2342 new_char = s->debug_metrics_p;
2344 new_char = (new_char << 4) | digit;
2345 if (new_char > 0 && new_char < max)
2347 s->debug_metrics_p = new_char;
2348 s->entering_unicode_p = 2;
2351 s->entering_unicode_p = 0;
2356 s->debug_metrics_antialiasing_p ^= True;
2357 else if (c == 3 || c == 27)
2360 s->debug_metrics_p = (unsigned char) c;
2361 else if (keysym == XK_Left || keysym == XK_Right)
2363 s->debug_metrics_p += (keysym == XK_Left ? -1 : 1);
2364 if (s->debug_metrics_p >= max)
2365 s->debug_metrics_p = 1;
2366 else if (s->debug_metrics_p <= 0)
2367 s->debug_metrics_p = max - 1;
2370 else if (keysym == XK_Prior)
2371 s->debug_metrics_p = (s->debug_metrics_p + max - 0x80) % max;
2372 else if (keysym == XK_Next)
2373 s->debug_metrics_p = (s->debug_metrics_p + 0x80) % max;
2374 else if (keysym == XK_Up)
2376 else if (keysym == XK_Down)
2377 s->debug_scale = (s->debug_scale > 1 ? s->debug_scale-1 : 1);
2378 else if (keysym == XK_F1)
2379 s->entering_unicode_p = 1;
2391 fontglide_reshape (Display *dpy, Window window, void *closure,
2392 unsigned int w, unsigned int h)
2394 state *s = (state *) closure;
2395 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2397 if (s->dbuf && s->ba)
2399 XFreePixmap (s->dpy, s->ba);
2400 s->ba = XCreatePixmap (s->dpy, s->window,
2401 s->xgwa.width, s->xgwa.height,
2403 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
2404 s->xgwa.width, s->xgwa.height);
2410 fontglide_free (Display *dpy, Window window, void *closure)
2412 state *s = (state *) closure;
2413 textclient_close (s->tc);
2416 if (s->metrics_xftfont)
2417 XftFontClose (s->dpy, s->metrics_xftfont);
2418 if (s->metrics_font1)
2419 XFreeFont (s->dpy, s->metrics_font1);
2420 if (s->metrics_font2 && s->metrics_font1 != s->metrics_font2)
2421 XFreeFont (s->dpy, s->metrics_font2);
2422 if (s->prev_font_name) free (s->prev_font_name);
2423 if (s->next_font_name) free (s->next_font_name);
2424 if (s->label_gc) XFreeGC (dpy, s->label_gc);
2427 /* #### there's more to free here */
2433 static const char *fontglide_defaults [] = {
2434 ".background: #000000",
2435 ".foreground: #DDDDDD",
2436 ".borderColor: #555555",
2438 "*program: xscreensaver-text",
2443 /* I'm not entirely clear on whether the charset of an XLFD has any
2444 meaning when Xft is being used. */
2445 "*fontCharset: iso8859-1",
2446 /*"*fontCharset: iso10646-1", */
2447 /*"*fontCharset: *-*",*/
2449 "*fontBorderWidth: 2",
2455 "*debugMetrics: False",
2457 "*doubleBuffer: True",
2458 # ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2460 "*useDBEClear: True",
2461 # endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2465 static XrmOptionDescRec fontglide_options [] = {
2466 { "-mode", ".mode", XrmoptionSepArg, 0 },
2467 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
2468 { "-page", ".mode", XrmoptionNoArg, "page" },
2469 { "-random", ".mode", XrmoptionNoArg, "random" },
2470 { "-delay", ".delay", XrmoptionSepArg, 0 },
2471 { "-speed", ".speed", XrmoptionSepArg, 0 },
2472 { "-linger", ".linger", XrmoptionSepArg, 0 },
2473 { "-program", ".program", XrmoptionSepArg, 0 },
2474 { "-font", ".font", XrmoptionSepArg, 0 },
2475 { "-fn", ".font", XrmoptionSepArg, 0 },
2476 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
2477 { "-trails", ".trails", XrmoptionNoArg, "True" },
2478 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
2479 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
2480 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
2482 { "-debug", ".debug", XrmoptionNoArg, "True" },
2483 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
2489 XSCREENSAVER_MODULE ("FontGlide", fontglide)