1 /* xscreensaver, Copyright (c) 2003-2015 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.
30 # include <X11/Intrinsic.h>
37 #include "screenhack.h"
38 #include "textclient.h"
42 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
44 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
49 int x, y; /* Position of origin of first character in word */
51 /* These have the same meanings as in XCharStruct: */
52 int lbearing; /* origin to leftmost pixel */
53 int rbearing; /* origin to rightmost pixel */
54 int ascent; /* origin to topmost pixel */
55 int descent; /* origin to bottommost pixel */
56 int width; /* origin to next word's origin */
60 int target_x, target_y;
74 XftColor xftcolor_fg, xftcolor_bg;
79 enum { IN, PAUSE, OUT } anim_state;
80 enum { LEFT, CENTER, RIGHT } alignment;
89 XWindowAttributes xgwa;
91 Pixmap b, ba; /* double-buffer to reduce flicker */
94 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
97 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
99 Bool dbuf; /* Whether we're using double buffering. */
101 int border_width; /* size of the font outline */
102 char *charset; /* registry and encoding for font lookups */
103 double speed; /* frame rate multiplier */
104 double linger; /* multiplier for how long to leave words on screen */
106 enum { PAGE, SCROLL, CHARS } mode;
108 char *font_override; /* if -font was specified on the cmd line */
110 char buf [40]; /* this only needs to be as big as one "word". */
116 sentence **sentences;
117 Bool spawn_p; /* whether it is time to create a new sentence */
119 unsigned long frame_delay;
125 unsigned long debug_metrics_p;
126 int debug_metrics_antialiasing_p;
128 unsigned entering_unicode_p; /* 0 = No, 1 = Just started, 2 = in progress */
129 XFontStruct *metrics_font1;
130 XFontStruct *metrics_font2;
131 XftFont *metrics_xftfont;
133 char *prev_font_name;
134 char *next_font_name;
140 static void drain_input (state *s);
144 pick_font_size (state *s)
146 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
147 int min, max, r, pixel;
152 if (min < 10) min = 10;
153 if (max < 30) max = 30;
157 pixel = min + ((random() % r) + (random() % r) + (random() % r));
159 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
166 /* Finds the set of scalable fonts on the system; picks one;
167 and loads that font in a random pixel size.
168 Returns False if something went wrong.
171 pick_font_1 (state *s, sentence *se)
177 # ifndef HAVE_COCOA /* real Xlib */
180 XFontStruct *info = 0;
181 int count = 0, count2 = 0;
186 XftFontClose (s->dpy, se->xftfont);
187 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
189 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
192 free (se->font_name);
197 if (s->font_override)
198 sprintf (pattern, "%.200s", s->font_override);
200 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
207 "0", /* pixel size */
208 "0", /* point size */
209 "0", /* resolution x */
210 "0", /* resolution y */
213 s->charset); /* registry + encoding */
215 names = XListFonts (s->dpy, pattern, 1000, &count);
219 if (s->font_override)
220 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
222 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
227 i = random() % count;
229 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
234 fprintf (stderr, "%s: pattern %s\n"
235 " gave unusable %s\n\n",
236 progname, pattern, names[i]);
242 XFontStruct *font = &info[0];
243 unsigned long value = 0;
244 char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
245 unsigned long pixel=0, point=0, res_x=0, res_y=0;
247 unsigned long avg_width=0;
248 char *registry=0, *encoding=0;
250 char *bogus = "\"?\"";
252 # define STR(ATOM,VAR) \
254 a = XInternAtom (s->dpy, (ATOM), False); \
255 if (XGetFontProperty (font, a, &value)) \
256 VAR = XGetAtomName (s->dpy, value); \
260 # define INT(ATOM,VAR) \
262 a = XInternAtom (s->dpy, (ATOM), False); \
263 if (!XGetFontProperty (font, a, &VAR) || \
267 STR ("FOUNDRY", foundry);
268 STR ("FAMILY_NAME", family);
269 STR ("WEIGHT_NAME", weight);
270 STR ("SLANT", slant);
271 STR ("SETWIDTH_NAME", setwidth);
272 STR ("ADD_STYLE_NAME", add_style);
273 INT ("PIXEL_SIZE", pixel);
274 INT ("POINT_SIZE", point);
275 INT ("RESOLUTION_X", res_x);
276 INT ("RESOLUTION_Y", res_y);
277 STR ("SPACING", spacing);
278 INT ("AVERAGE_WIDTH", avg_width);
279 STR ("CHARSET_REGISTRY", registry);
280 STR ("CHARSET_ENCODING", encoding);
285 pixel = pick_font_size (s);
288 /* Occasionally change the aspect ratio of the font, by increasing
289 either the X or Y resolution (while leaving the other alone.)
291 #### Looks like this trick doesn't really work that well: the
292 metrics of the individual characters are ok, but the
293 overall font ascent comes out wrong (unscaled.)
295 if (! (random() % 8))
298 double scale = 1 + (frand(n) + frand(n) + frand(n));
307 "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
308 foundry, family, weight, slant, setwidth, add_style,
309 pixel, "*", /* point, */
310 res_x, res_y, spacing,
317 fprintf (stderr, "%s: font has bogus %s property: %s\n",
318 progname, bogus, names[i]);
320 if (foundry) XFree (foundry);
321 if (family) XFree (family);
322 if (weight) XFree (weight);
323 if (slant) XFree (slant);
324 if (setwidth) XFree (setwidth);
325 if (add_style) XFree (add_style);
326 if (spacing) XFree (spacing);
327 if (registry) XFree (registry);
328 if (encoding) XFree (encoding);
333 XFreeFontInfo (names2, info, count2);
334 XFreeFontNames (names);
336 # else /* HAVE_COCOA */
338 if (s->font_override)
339 sprintf (pattern, "%.200s", s->font_override);
342 const char *family = "random";
343 const char *weight = ((random() % 2) ? "regular" : "bold");
344 const char *slant = ((random() % 2) ? "o" : "r");
345 int size = 10 * pick_font_size (s);
346 sprintf (pattern, "*-%s-%s-%s-*-*-*-%d-*", family, weight, slant, size);
349 # endif /* HAVE_COCOA */
351 if (! ok) return False;
353 se->xftfont = XftFontOpenXlfd (s->dpy, screen_number (s->xgwa.screen),
360 fprintf (stderr, "%s: unable to load font %s\n",
366 strcpy (pattern2, pattern);
370 const char *n = jwxyz_nativeFontName (se->xftfont->xfont->fid, &s);
371 sprintf (pattern2 + strlen(pattern2), " (%s %.1f)", n, s);
376 if (s->prev_font_name) free (s->prev_font_name);
377 s->prev_font_name = s->next_font_name;
378 s->next_font_name = strdup (pattern2);
381 /* Sometimes we get fonts with screwed up metrics. For example:
382 -b&h-lucida-medium-r-normal-sans-40-289-100-100-p-0-iso8859-1
384 When using XDrawString, XTextExtents and XTextExtents16, it is rendered
385 as a scaled-up bitmap font. The character M has rbearing 70, ascent 68
386 and width 78, which is correct for the glyph as rendered.
388 But when using XftDrawStringUtf8 and XftTextExtentsUtf8, it is rendered
389 at the original, smaller, un-scaled size, with rbearing 26, ascent 25
392 So it's taking the *size* from the unscaled font, the *advancement* from
393 the scaled-up version, and then *not* actually scaling it up. Awesome.
395 So, after loading the font, measure the M, and if its advancement is more
396 than 20% larger than its rbearing, reject the font.
398 ------------------------------------------------------------------------
400 Some observations on this nonsense from Dave Odell:
402 1. -*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1 normally
403 resolves to /usr/share/fonts/X11/100dpi/lutBS24-ISO8859-1.pcf.gz.
405 -*-lucidatypewriter-* is from the 'xfonts-100dpi' package in
406 Debian/Ubuntu. It's usually (54.46% of systems), but not always,
407 installed whenever an X.org server (57.96% of systems) is. It might
408 be a good idea for this and xfonts-75dpi to be recommended
409 dependencies of XScreenSaver in Debian, but that's neither here nor
410 there. https://qa.debian.org/popcon.php?package=xorg
411 https://qa.debian.org/popcon.php?package=xfonts-100dpi
413 2. It normally resolves to the PCF font... but not always.
415 Fontconfig has /etc/fonts/conf.d/ (it's /opt/local/etc/fonts/conf.d/
416 with MacPorts) containing symlinks to configuration files. And both
417 Debian and Ubuntu normally has a 70-no-bitmaps.conf, installed as part
418 of the 'fontconfig-config' package. And the 70-no-bitmaps.conf
419 symlink... disables bitmap fonts.
421 Without bitmap fonts, I get DejaVu Sans.
423 3. There's another symlink of interest here:
424 /etc/fonts/conf.d/10-scale-bitmap-fonts.conf. This adds space to the
425 right of glyphs of bitmap fonts when the requested size of the font is
426 larger than the actual bitmap font. Ubuntu and MacPorts has this one.
428 This specifically is causing text to have excessive character spacing.
430 (jwz asks: WHY WOULD ANYONE EVER WANT THIS BEHAVIOR?)
432 4. Notice that I'm only talking about Debian and Ubuntu. Other distros
433 will probably have different symlinks in /etc/fonts/conf.d/. So yes,
434 this can be an issue on Linux as well as MacOS.
442 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) "M", 1, &extents);
443 rbearing = extents.width - extents.x;
444 width = extents.xOff;
445 ratio = rbearing / (float) width;
449 fprintf (stderr, "%s: M ratio %.2f (%d %d): %s\n", progname,
450 ratio, rbearing, width, pattern2);
453 if (ratio < min && !s->font_override)
457 fprintf (stderr, "%s: skipping font with broken metrics: %s\n",
467 fprintf(stderr, "%s: %s\n", progname, pattern2);
470 se->font_name = strdup (pattern);
475 /* Finds the set of scalable fonts on the system; picks one;
476 and loads that font in a random pixel size.
479 pick_font (state *s, sentence *se)
482 for (i = 0; i < 50; i++)
483 if (pick_font_1 (s, se))
485 fprintf (stderr, "%s: too many font-loading failures: giving up!\n",
491 static char *unread_word_text = 0;
493 /* Returns a newly-allocated string with one word in it, or NULL if there
494 is no complete word available.
497 get_word_text (state *s)
499 const char *start = s->buf;
506 /* If we just launched, and haven't had any text yet, and it has been
507 more than 2 seconds since we launched, then push out "Loading..."
508 as our first text. So if the text source is speedy, just use that.
509 But if we'd display a blank screen for a while, give 'em something
515 s->start_time < ((time ((time_t *) 0) - 2)))
517 unread_word_text = "Loading...";
521 if (unread_word_text)
523 start = unread_word_text;
524 unread_word_text = 0;
528 /* Skip over whitespace at the beginning of the buffer,
529 and count up how many linebreaks we see while doing so.
537 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
544 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
545 to trigger a sentence break here.) */
549 /* Skip forward to the end of this word (find next whitespace.) */
557 /* If we have a word, allocate a string for it */
560 result = malloc ((end - start) + 1);
561 strncpy (result, start, (end-start));
562 result [end-start] = 0;
567 /* Make room in the buffer by compressing out any bytes we've processed.
571 int n = end - s->buf;
572 memmove (s->buf, end, sizeof(s->buf) - n);
580 /* Returns a 1-bit pixmap of the same size as the drawable,
581 with a 0 wherever the drawable is black.
584 make_mask (Screen *screen, Visual *visual, Drawable drawable)
586 Display *dpy = DisplayOfScreen (screen);
587 unsigned long black = BlackPixelOfScreen (screen);
590 unsigned int w, h, bw, d;
595 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bw, &d);
596 in = XGetImage (dpy, drawable, 0, 0, w, h, ~0L, ZPixmap);
597 out = XCreateImage (dpy, visual, 1, XYPixmap, 0, 0, w, h, 8, 0);
598 out->data = (char *) malloc (h * out->bytes_per_line);
599 for (y = 0; y < h; y++)
600 for (x = 0; x < w; x++)
601 XPutPixel (out, x, y, (black != XGetPixel (in, x, y)));
602 mask = XCreatePixmap (dpy, drawable, w, h, 1L);
603 gc = XCreateGC (dpy, mask, 0, 0);
604 XPutImage (dpy, mask, gc, out, 0, 0, 0, 0, w, h);
608 in->data = out->data = 0;
615 /* Gets some random text, and creates a "word" object from it.
618 new_word (state *s, sentence *se, const char *txt, Bool alloc_p)
622 int bw = s->border_width;
627 w = (word *) calloc (1, sizeof(*w));
628 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) txt, strlen(txt),
631 w->lbearing = -extents.x;
632 w->rbearing = extents.width - extents.x;
633 w->ascent = extents.y;
634 w->descent = extents.height - extents.y;
635 w->width = extents.xOff;
642 if (s->mode == SCROLL && !alloc_p) abort();
648 GC gc_fg, gc_bg, gc_black;
650 int width = w->rbearing - w->lbearing;
651 int height = w->ascent + w->descent;
653 if (width <= 0) width = 1;
654 if (height <= 0) height = 1;
656 w->pixmap = XCreatePixmap (s->dpy, s->b, width, height, s->xgwa.depth);
657 xftdraw = XftDrawCreate (s->dpy, w->pixmap, s->xgwa.visual,
660 gcv.foreground = se->xftcolor_fg.pixel;
661 gc_fg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
663 gcv.foreground = se->xftcolor_bg.pixel;
664 gc_bg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
666 gcv.foreground = BlackPixelOfScreen (s->xgwa.screen);
667 gc_black = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
669 XFillRectangle (s->dpy, w->pixmap, gc_black, 0, 0, width, height);
674 /* bounding box (behind the characters) */
675 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
676 0, 0, width-1, height-1);
680 /* Draw background text for border */
681 for (i = -bw; i <= bw; i++)
682 for (j = -bw; j <= bw; j++)
683 XftDrawStringUtf8 (xftdraw, &se->xftcolor_bg, se->xftfont,
684 -w->lbearing + i, w->ascent + j,
685 (FcChar8 *) txt, strlen(txt));
687 /* Draw foreground text */
688 XftDrawStringUtf8 (xftdraw, &se->xftcolor_fg, se->xftfont,
689 -w->lbearing, w->ascent,
690 (FcChar8 *) txt, strlen(txt));
695 if (w->ascent != height)
697 /* baseline (on top of the characters) */
698 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
699 0, w->ascent, width-1, w->ascent);
704 /* left edge of charcell */
705 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
707 -w->lbearing, height-1);
710 if (w->rbearing != w->width)
712 /* right edge of charcell */
713 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
714 w->width - w->lbearing, 0,
715 w->width - w->lbearing, height-1);
720 w->mask = make_mask (s->xgwa.screen, s->xgwa.visual, w->pixmap);
722 XftDrawDestroy (xftdraw);
723 XFreeGC (s->dpy, gc_fg);
724 XFreeGC (s->dpy, gc_bg);
725 XFreeGC (s->dpy, gc_black);
728 w->text = strdup (txt);
734 free_word (state *s, word *w)
736 if (w->text) free (w->text);
737 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
738 if (w->mask) XFreePixmap (s->dpy, w->mask);
743 new_sentence (state *st, state *s)
746 sentence *se = (sentence *) calloc (1, sizeof (*se));
747 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
749 se->id = ++st->id_tick;
755 free_sentence (state *s, sentence *se)
758 for (i = 0; i < se->nwords; i++)
759 free_word (s, se->words[i]);
763 free (se->font_name);
765 XFreeGC (s->dpy, se->fg_gc);
769 XftFontClose (s->dpy, se->xftfont);
770 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
772 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
780 /* free the word, and put its text back at the front of the input queue,
781 to be read next time. */
783 unread_word (state *s, word *w)
785 if (unread_word_text)
787 unread_word_text = w->text;
793 /* Divide each of the words in the sentence into one character words,
794 without changing the positions of those characters.
797 split_words (state *s, sentence *se)
803 char ***word_chars = (char ***) malloc (se->nwords * sizeof(*word_chars));
804 for (i = 0; i < se->nwords; i++)
807 word *ow = se->words[i];
808 word_chars[i] = utf8_split (ow->text, &L);
812 words2 = (word **) calloc (nwords2, sizeof(*words2));
814 for (i = 0, j = 0; i < se->nwords; i++)
816 char **chars = word_chars[i];
817 word *parent = se->words[i];
820 int sx = parent->start_x;
821 int sy = parent->start_y;
822 int tx = parent->target_x;
823 int ty = parent->target_y;
826 for (k = 0; chars[k]; k++)
829 word *w2 = new_word (s, se, t2, True);
844 /* This is not invariant when kerning is involved! */
845 /* if (x != parent->x + parent->width) abort(); */
847 free (chars); /* but we retain its contents */
848 free_word (s, parent);
850 if (j != nwords2) abort();
855 se->nwords = nwords2;
859 /* Set the source or destination position of the words to be somewhere
863 scatter_sentence (state *s, sentence *se)
866 int off = s->border_width * 4 + 2;
868 int flock_p = ((random() % 4) == 0);
869 int mode = (flock_p ? (random() % 12) : 0);
871 for (i = 0; i < se->nwords; i++)
873 word *w = se->words[i];
875 int r = (flock_p ? mode : (random() % 4));
876 int left = -(off + w->rbearing);
877 int top = -(off + w->descent);
878 int right = off - w->lbearing + s->xgwa.width;
879 int bottom = off + w->ascent + s->xgwa.height;
882 /* random positions on the edges */
883 case 0: x = left; y = random() % s->xgwa.height; break;
884 case 1: x = right; y = random() % s->xgwa.height; break;
885 case 2: x = random() % s->xgwa.width; y = top; break;
886 case 3: x = random() % s->xgwa.width; y = bottom; break;
888 /* straight towards the edges */
889 case 4: x = left; y = w->target_y; break;
890 case 5: x = right; y = w->target_y; break;
891 case 6: x = w->target_x; y = top; break;
892 case 7: x = w->target_x; y = bottom; break;
895 case 8: x = left; y = top; break;
896 case 9: x = left; y = bottom; break;
897 case 10: x = right; y = top; break;
898 case 11: x = right; y = bottom; break;
900 default: abort(); break;
903 if (se->anim_state == IN)
916 w->nticks = ((100 + ((random() % 140) +
927 /* Set the source position of the words to be off the right side,
928 and the destination to be off the left side.
931 aim_sentence (state *s, sentence *se)
937 if (se->nwords <= 0) abort();
939 /* Have the sentence shift up or down a little bit; not too far, and
940 never let it fall off the top or bottom of the screen before its
941 last character has reached the left edge.
943 for (i = 0; i < 10; i++)
945 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
946 yoff = ty - se->words[0]->target_y;
947 if (yoff < s->xgwa.height/3) /* this one is ok */
951 for (i = 0; i < se->nwords; i++)
953 word *w = se->words[i];
954 w->start_x = w->target_x + s->xgwa.width;
955 w->target_x -= se->width;
956 w->start_y = w->target_y;
960 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
962 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
967 for (i = 0; i < se->nwords; i++)
969 word *w = se->words[i];
976 /* Randomize the order of the words in the list (since that changes
977 which ones are "on top".)
980 shuffle_words (state *s, sentence *se)
983 for (i = 0; i < se->nwords-1; i++)
985 int j = i + (random() % (se->nwords - i));
986 word *swap = se->words[i];
987 se->words[i] = se->words[j];
993 /* qsort comparitor */
995 cmp_sentences (const void *aa, const void *bb)
997 const sentence *a = *(sentence **) aa;
998 const sentence *b = *(sentence **) bb;
999 return ((a ? a->id : 999999) - (b ? b->id : 999999));
1003 /* Sort the sentences by id, so that sentences added later are on top.
1006 sort_sentences (state *s)
1008 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
1012 /* Re-pick the colors of the text and border
1015 recolor (state *s, sentence *se)
1017 XRenderColor fg, bg;
1019 fg.red = (random() % 0x5555) + 0xAAAA;
1020 fg.green = (random() % 0x5555) + 0xAAAA;
1021 fg.blue = (random() % 0x5555) + 0xAAAA;
1023 bg.red = (random() % 0x5555);
1024 bg.green = (random() % 0x5555);
1025 bg.blue = (random() % 0x5555);
1031 XRenderColor swap = fg; fg = bg; bg = swap;
1037 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1039 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1043 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &fg,
1045 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &bg,
1051 align_line (state *s, sentence *se, int line_start, int x, int right)
1054 switch (se->alignment)
1056 case LEFT: off = 0; break;
1057 case CENTER: off = (right - x) / 2; break;
1058 case RIGHT: off = (right - x); break;
1059 default: abort(); break;
1063 for (j = line_start; j < se->nwords; j++)
1064 se->words[j]->target_x += off;
1068 /* Fill the sentence with new words: in "page" mode, fills the page
1069 with text; in "scroll" mode, just makes one long horizontal sentence.
1070 The sentence might have *no* words in it, if no text is currently
1074 populate_sentence (state *s, sentence *se)
1077 int left, right, top, x, y;
1082 int array_size = 100;
1084 se->move_chars_p = (s->mode == CHARS ? True :
1085 s->mode == SCROLL ? False :
1086 (random() % 3) ? False : True);
1087 se->alignment = (random() % 3);
1093 for (i = 0; i < se->nwords; i++)
1094 free_word (s, se->words[i]);
1098 se->words = (word **) calloc (array_size, sizeof(*se->words));
1105 left = random() % (s->xgwa.width / 3);
1106 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1107 top = random() % (s->xgwa.height * 2 / 3);
1111 right = s->xgwa.width;
1112 top = random() % s->xgwa.height;
1124 const char *txt = get_word_text (s);
1128 if (se->nwords == 0)
1129 return; /* If the stream is empty, bail. */
1131 break; /* If EOF after some words, end of sentence. */
1134 if (! se->xftfont) /* Got a word: need a font now */
1138 if (y < se->xftfont->ascent)
1139 y += se->xftfont->ascent;
1141 /* Measure the space character to figure out how much room to
1142 leave between words (since we don't actually render that.) */
1143 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) " ", 1,
1145 space = extents.xOff;
1148 w = new_word (s, se, txt, !se->move_chars_p);
1150 /* If we have a few words, let punctuation terminate the sentence:
1151 stop gathering more words if the last word ends in a period, etc. */
1152 if (se->nwords >= 4)
1154 char c = w->text[strlen(w->text)-1];
1155 if (c == '.' || c == '?' || c == '!')
1159 /* If the sentence is kind of long already, terminate at commas, etc. */
1160 if (se->nwords >= 12)
1162 char c = w->text[strlen(w->text)-1];
1163 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1164 c == ')' || c == ']' || c == '}')
1168 if (se->nwords >= 25) /* ok that's just about enough out of you */
1171 if ((s->mode == PAGE || s->mode == CHARS) &&
1172 x + w->rbearing > right) /* wrap line */
1174 align_line (s, se, line_start, x, right);
1175 line_start = se->nwords;
1178 y += se->xftfont->ascent + se->xftfont->descent;
1180 /* If we're close to the bottom of the screen, stop, and
1181 unread the current word. (But not if this is the first
1182 word, otherwise we might just get stuck on it.)
1184 if (se->nwords > 0 &&
1185 y + se->xftfont->ascent + se->xftfont->descent > s->xgwa.height)
1196 x += w->width + space;
1199 if (se->nwords >= (array_size - 1))
1202 se->words = (word **)
1203 realloc (se->words, array_size * sizeof(*se->words));
1206 fprintf (stderr, "%s: out of memory (%d words)\n",
1207 progname, array_size);
1212 se->words[se->nwords++] = w;
1221 align_line (s, se, line_start, x, right);
1222 if (se->move_chars_p)
1223 split_words (s, se);
1224 scatter_sentence (s, se);
1225 shuffle_words (s, se);
1228 aim_sentence (s, se);
1238 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1239 for (i = 0; i < se->nwords; i++)
1240 fprintf (stderr, " %s", se->words[i]->text);
1241 fprintf (stderr, "\n");
1247 /* Render a single word object to the screen.
1250 draw_word (state *s, sentence *se, word *word)
1253 if (! word->pixmap) return;
1255 x = word->x + word->lbearing;
1256 y = word->y - word->ascent;
1257 w = word->rbearing - word->lbearing;
1258 h = word->ascent + word->descent;
1262 x > s->xgwa.width ||
1266 XSetClipMask (s->dpy, se->fg_gc, word->mask);
1267 XSetClipOrigin (s->dpy, se->fg_gc, x, y);
1268 XCopyArea (s->dpy, word->pixmap, s->b, se->fg_gc,
1273 /* If there is room for more sentences, add one.
1276 more_sentences (state *s)
1280 for (i = 0; i < s->nsentences; i++)
1282 sentence *se = s->sentences[i];
1285 se = new_sentence (s, s);
1286 populate_sentence (s, se);
1288 s->spawn_p = False, any = True;
1291 free_sentence (s, se);
1294 s->sentences[i] = se;
1296 s->latest_sentence = se->id;
1301 if (any) sort_sentences (s);
1305 /* Render all the words to the screen, and run the animation one step.
1308 draw_sentence (state *s, sentence *se)
1315 for (i = 0; i < se->nwords; i++)
1317 word *w = se->words[i];
1323 if (se->anim_state != PAUSE &&
1324 w->tick <= w->nticks)
1326 int dx = w->target_x - w->start_x;
1327 int dy = w->target_y - w->start_y;
1328 double r = sin (w->tick * M_PI / (2 * w->nticks));
1329 w->x = w->start_x + (dx * r);
1330 w->y = w->start_y + (dy * r);
1333 if (se->anim_state == OUT &&
1334 (s->mode == PAGE || s->mode == CHARS))
1335 w->tick++; /* go out faster */
1341 int dx = w->target_x - w->start_x;
1342 int dy = w->target_y - w->start_y;
1343 double r = (double) w->tick / w->nticks;
1344 w->x = w->start_x + (dx * r);
1345 w->y = w->start_y + (dy * r);
1347 moved = (w->tick <= w->nticks);
1349 /* Launch a new sentence when:
1350 - the front of this sentence is almost off the left edge;
1351 - the end of this sentence is almost on screen.
1354 if (se->anim_state != OUT &&
1356 se->id == s->latest_sentence)
1358 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1359 w->x + se->width < (s->xgwa.width * 2.1));
1360 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1362 if (new_p || rand_p)
1364 se->anim_state = OUT;
1368 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1370 se->words[0]->x + se->width,
1371 rand_p ? " randomly" : "");
1382 draw_word (s, se, w);
1385 if (moved && se->anim_state == PAUSE)
1390 switch (se->anim_state)
1393 se->anim_state = PAUSE;
1394 se->pause_tick = (se->nwords * 7 * s->linger);
1395 if (se->move_chars_p)
1396 se->pause_tick /= 5;
1397 scatter_sentence (s, se);
1398 shuffle_words (s, se);
1401 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1405 if (--se->pause_tick <= 0)
1407 se->anim_state = OUT;
1411 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1418 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1422 for (j = 0; j < s->nsentences; j++)
1423 if (s->sentences[j] == se)
1424 s->sentences[j] = 0;
1425 free_sentence (s, se);
1436 #ifdef DEBUG /* All of this stuff is for -debug-metrics mode. */
1440 scale_ximage (Screen *screen, Window window, XImage *img, int scale,
1443 Display *dpy = DisplayOfScreen (screen);
1445 unsigned width = img->width, height = img->height;
1449 p2 = XCreatePixmap (dpy, window, width*scale, height*scale, img->depth);
1450 gc = XCreateGC (dpy, p2, 0, 0);
1452 XSetForeground (dpy, gc, BlackPixelOfScreen (screen));
1453 XFillRectangle (dpy, p2, gc, 0, 0, width*scale, height*scale);
1454 for (y = 0; y < height; y++)
1455 for (x = 0; x < width; x++)
1457 XSetForeground (dpy, gc, XGetPixel (img, x, y));
1458 XFillRectangle (dpy, p2, gc, x*scale, y*scale, scale, scale);
1463 XWindowAttributes xgwa;
1465 c.red = c.green = c.blue = 0x4444;
1466 c.flags = DoRed|DoGreen|DoBlue;
1467 XGetWindowAttributes (dpy, window, &xgwa);
1468 if (! XAllocColor (dpy, xgwa.colormap, &c)) abort();
1469 XSetForeground (dpy, gc, c.pixel);
1470 XDrawRectangle (dpy, p2, gc, 0, 0, width*scale-1, height*scale-1);
1471 XDrawRectangle (dpy, p2, gc, margin*scale, margin*scale,
1472 width*scale-1, height*scale-1);
1473 for (y = 0; y <= height - 2*margin; y++)
1474 XDrawLine (dpy, p2, gc,
1475 margin*scale, (y+margin)*scale-1,
1476 (width-margin)*scale, (y+margin)*scale-1);
1477 for (x = 0; x <= width - 2*margin; x++)
1478 XDrawLine (dpy, p2, gc,
1479 (x+margin)*scale-1, margin*scale,
1480 (x+margin)*scale-1, (height-margin)*scale);
1481 XFreeColors (dpy, xgwa.colormap, &c.pixel, 1, 0);
1489 static int check_edge (Display *dpy, Drawable p, GC gc,
1490 unsigned msg_x, unsigned msg_y, const char *msg,
1492 unsigned x, unsigned y, unsigned dim, unsigned end)
1503 XDrawString (dpy, p, gc, msg_x, msg_y, msg, strlen (msg));
1507 if (XGetPixel(img, pt[0], pt[1]) & 0xffffff)
1517 static unsigned long
1518 fontglide_draw_metrics (state *s)
1520 unsigned int margin = (s->debug_metrics_antialiasing_p ? 2 : 0);
1522 char txt[2], utxt[3], txt2[80];
1524 const char *fn = (s->font_override ? s->font_override : "fixed");
1526 XCharStruct c, overall, fake_c;
1527 int dir, ascent, descent;
1532 int sc = s->debug_scale;
1534 unsigned long red = 0xFFFF0000; /* so shoot me */
1535 unsigned long green = 0xFF00FF00;
1536 unsigned long blue = 0xFF6666FF;
1537 unsigned long yellow = 0xFFFFFF00;
1538 unsigned long cyan = 0xFF004040;
1540 Drawable dest = s->b ? s->b : s->window;
1544 /* Self-test these macros to make sure they're symmetrical. */
1545 for (i = 0; i < 1000; i++)
1549 c.lbearing = (random()%50)-100;
1550 c.rbearing = (random()%50)-100;
1551 c.ascent = (random()%50)-100;
1552 c.descent = (random()%50)-100;
1553 c.width = (random()%50)-100;
1554 XCharStruct_to_XGlyphInfo (c, g);
1555 XGlyphInfo_to_XCharStruct (g, overall);
1556 if (c.lbearing != overall.lbearing) abort();
1557 if (c.rbearing != overall.rbearing) abort();
1558 if (c.ascent != overall.ascent) abort();
1559 if (c.descent != overall.descent) abort();
1560 if (c.width != overall.width) abort();
1561 XCharStruct_to_XGlyphInfo (overall, g2);
1562 if (g.x != g2.x) abort();
1563 if (g.y != g2.y) abort();
1564 if (g.xOff != g2.xOff) abort();
1565 if (g.yOff != g2.yOff) abort();
1566 if (g.width != g2.width) abort();
1567 if (g.height != g2.height) abort();
1568 XCharStruct_to_XmbRectangle (overall, r);
1569 XmbRectangle_to_XCharStruct (r, c, c.width);
1570 if (c.lbearing != overall.lbearing) abort();
1571 if (c.rbearing != overall.rbearing) abort();
1572 if (c.ascent != overall.ascent) abort();
1573 if (c.descent != overall.descent) abort();
1574 if (c.width != overall.width) abort();
1577 txt[0] = s->debug_metrics_p;
1580 /* Convert Unicode code point to UTF-8. */
1581 utxt[utf8_encode(s->debug_metrics_p, utxt, 4)] = 0;
1583 txt3 = utf8_to_XChar2b (utxt, 0);
1585 if (! s->metrics_font1)
1586 s->metrics_font1 = XLoadQueryFont (s->dpy, fn);
1587 if (! s->metrics_font2)
1588 s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
1589 if (! s->metrics_font1)
1590 s->metrics_font1 = s->metrics_font2;
1592 gc = XCreateGC (s->dpy, dest, 0, 0);
1593 XSetFont (s->dpy, gc, s->metrics_font1->fid);
1596 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1599 if (! s->metrics_xftfont)
1601 s->metrics_xftfont =
1602 XftFontOpenXlfd (s->dpy, screen_number(s->xgwa.screen), fn);
1603 if (! s->metrics_xftfont)
1605 const char *fn2 = "fixed";
1606 s->metrics_xftfont =
1607 XftFontOpenName (s->dpy, screen_number(s->xgwa.screen), fn2);
1608 if (s->metrics_xftfont)
1612 fprintf (stderr, "%s: XftFontOpen failed on \"%s\" and \"%s\"\n",
1623 const char *n = jwxyz_nativeFontName (s->metrics_xftfont->xfont->fid, &ss);
1624 sprintf (fn2, "%s %.1f", n, ss);
1628 xftdraw = XftDrawCreate (s->dpy, dest, s->xgwa.visual,
1630 XftColorAllocName (s->dpy, s->xgwa.visual, s->xgwa.colormap, "white",
1632 XftTextExtentsUtf8 (s->dpy, s->metrics_xftfont,
1633 (FcChar8 *) utxt, strlen(utxt),
1637 XTextExtents (s->metrics_font1, txt, strlen(txt),
1638 &dir, &ascent, &descent, &overall);
1639 c = ((s->debug_metrics_p >= s->metrics_font1->min_char_or_byte2 &&
1640 s->debug_metrics_p <= s->metrics_font1->max_char_or_byte2)
1641 ? s->metrics_font1->per_char[s->debug_metrics_p -
1642 s->metrics_font1->min_char_or_byte2]
1645 XSetForeground (s->dpy, gc, BlackPixelOfScreen (s->xgwa.screen));
1646 XFillRectangle (s->dpy, dest, gc, 0, 0, s->xgwa.width, s->xgwa.height);
1648 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1649 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1650 XDrawString (s->dpy, dest, gc,
1658 jwxyz_unicode_character_name (s->metrics_font1->fid, s->debug_metrics_p);
1659 if (!name || !*name) name = strdup("unknown character name");
1660 XDrawString (s->dpy, dest, gc,
1662 10 + 2 * (s->metrics_font2->ascent +
1663 s->metrics_font2->descent),
1664 name, strlen(name));
1669 /* i 0, j 0: top left, XDrawString, char metrics
1670 i 1, j 0: bot left, XDrawString, overall metrics, ink escape
1671 i 0, j 1: top right, XftDrawStringUtf8, utf8 metrics
1672 i 1, j 1: bot right, XDrawString16, 16 metrics, ink escape
1674 for (j = 0; j < 2; j++) {
1675 Bool xft_p = (j != 0);
1676 int ww = s->xgwa.width / 2 - 20;
1677 int xoff = (j == 0 ? 0 : ww + 20);
1679 /* XDrawString only does 8-bit characters, so skip it outside Latin-1. */
1680 if (s->debug_metrics_p >= 256)
1688 x = (ww - overall.width) / 2;
1690 for (i = 0; i < 2; i++)
1693 int x1 = xoff + ww * 0.18;
1694 int x2 = xoff + ww * 0.82;
1701 int h = sc * (ascent + descent);
1702 int min = (ascent + descent) * 4;
1703 if (h < min) h = min;
1708 memset (&fake_c, 0, sizeof(fake_c));
1710 if (!xft_p && i == 0)
1712 else if (!xft_p && i == 1)
1714 else if (xft_p && i == 0)
1716 /* Measure the glyph in the Xft way */
1718 XftTextExtentsUtf8 (s->dpy,
1720 (FcChar8 *) utxt, strlen(utxt),
1722 XGlyphInfo_to_XCharStruct (extents, fake_c);
1727 /* Measure the glyph in the 16-bit way */
1728 int dir, ascent, descent;
1729 XTextExtents16 (s->metrics_font1, txt3, 1, &dir, &ascent, &descent,
1734 pixw = margin * 2 + cc.rbearing - cc.lbearing;
1735 pixh = margin * 2 + cc.ascent + cc.descent;
1736 p = (pixw > 0 && pixh > 0
1737 ? XCreatePixmap (s->dpy, dest, pixw, pixh, s->xgwa.depth)
1743 GC gc2 = XCreateGC (s->dpy, p, 0, 0);
1745 jwxyz_XSetAntiAliasing (s->dpy, gc2, False);
1747 XSetFont (s->dpy, gc2, s->metrics_font1->fid);
1748 XSetForeground (s->dpy, gc2, BlackPixelOfScreen (s->xgwa.screen));
1749 XFillRectangle (s->dpy, p, gc2, 0, 0, pixw, pixh);
1750 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1751 XSetForeground (s->dpy, gc2, WhitePixelOfScreen (s->xgwa.screen));
1753 jwxyz_XSetAntiAliasing (s->dpy, gc2,
1754 s->debug_metrics_antialiasing_p);
1757 if (xft_p && i == 0)
1759 XftDraw *xftdraw2 = XftDrawCreate (s->dpy, p, s->xgwa.visual,
1761 XftDrawStringUtf8 (xftdraw2, &xftcolor,
1763 -cc.lbearing + margin,
1765 (FcChar8 *) utxt, strlen(utxt));
1766 XftDrawDestroy (xftdraw2);
1769 XDrawString16 (s->dpy, p, gc2,
1770 -cc.lbearing + margin,
1774 XDrawString (s->dpy, p, gc2,
1775 -cc.lbearing + margin,
1781 XImage *img = XGetImage (s->dpy, p, 0, 0, pixw, pixh,
1787 unsigned w = pixw - margin * 2, h = pixh - margin * 2;
1791 /* Check for ink escape. */
1792 unsigned long ink = 0;
1793 for (y2 = 0; y2 != pixh; ++y2)
1794 for (x2 = 0; x2 != pixw; ++x2)
1797 if (! (x2 >= margin &&
1798 x2 < pixw - margin &&
1800 y2 < pixh - margin))
1801 ink |= XGetPixel (img, x2, y2);
1806 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1807 XDrawString (s->dpy, dest, gc,
1813 /* ...And wasted space. */
1816 if (check_edge (s->dpy, dest, gc, 120, 60, "left",
1817 img, margin, margin, 1, h) |
1818 check_edge (s->dpy, dest, gc, 160, 60, "right",
1819 img, margin + w - 1, margin, 1, h) |
1820 check_edge (s->dpy, dest, gc, 200, 60, "top",
1821 img, margin, margin, 0, w) |
1822 check_edge (s->dpy, dest, gc, 240, 60, "bottom",
1823 img, margin, margin + h - 1, 0, w))
1825 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1826 XDrawString (s->dpy, dest, gc,
1828 "Wasted space: ", 14);
1833 if (s->debug_metrics_antialiasing_p)
1835 /* Draw a dark cyan boundary around antialiased glyphs */
1836 img2 = XCreateImage (s->dpy, s->xgwa.visual, img->depth,
1838 img->width, img->height,
1839 img->bitmap_pad, 0);
1840 img2->data = malloc (img->bytes_per_line * img->height);
1842 for (y2 = 0; y2 != pixh; ++y2)
1843 for (x2 = 0; x2 != pixw; ++x2)
1845 unsigned long px = XGetPixel (img, x2, y2);
1846 if ((px & 0xffffff) == 0)
1848 unsigned long neighbors = 0;
1850 neighbors |= XGetPixel (img, x2 - 1, y2);
1852 neighbors |= XGetPixel (img, x2 + 1, y2);
1854 neighbors |= XGetPixel (img, x2, y2 - 1);
1856 neighbors |= XGetPixel (img, x2, y2 + 1);
1857 XPutPixel (img2, x2, y2,
1858 (neighbors & 0xffffff
1860 : BlackPixelOfScreen (s->xgwa.screen)));
1864 XPutPixel (img2, x2, y2, px);
1874 p2 = scale_ximage (s->xgwa.screen, s->window, img2, sc, margin);
1876 XDestroyImage (img);
1877 XDestroyImage (img2);
1880 XCopyArea (s->dpy, p2, dest, gc,
1881 0, 0, sc*pixw, sc*pixh,
1882 xoff + x + sc * (cc.lbearing - margin),
1883 y - sc * (cc.ascent + margin));
1884 XFreePixmap (s->dpy, p);
1885 XFreePixmap (s->dpy, p2);
1886 XFreeGC (s->dpy, gc2);
1891 XSetFont (s->dpy, gc, s->metrics_font1->fid);
1892 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1894 jwxyz_XSetAntiAliasing (s->dpy, gc, s->debug_metrics_antialiasing_p);
1896 sprintf (txt2, "%s [XX%sXX] [%s%s%s%s]",
1897 (xft_p ? utxt : txt),
1898 (xft_p ? utxt : txt),
1899 (xft_p ? utxt : txt),
1900 (xft_p ? utxt : txt),
1901 (xft_p ? utxt : txt),
1902 (xft_p ? utxt : txt));
1905 XftDrawStringUtf8 (xftdraw, &xftcolor,
1907 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2
1910 (FcChar8 *) txt2, strlen(txt2));
1912 XDrawString (s->dpy, dest, gc,
1913 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2,
1915 txt2, strlen(txt2));
1917 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1919 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1923 char *tptr = txt2 + sprintf(txt2, "U+%04lX", s->debug_metrics_p);
1924 *tptr++ = " ?_"[s->entering_unicode_p];
1930 tptr += sprintf (tptr, "0%03o ", (unsigned char) *uptr);
1937 tptr += sprintf (tptr, "%02x ", (unsigned char) *uptr);
1942 sprintf (txt2, "%c %3ld 0%03lo 0x%02lx%s",
1943 (char)s->debug_metrics_p, s->debug_metrics_p,
1944 s->debug_metrics_p, s->debug_metrics_p,
1945 (txt[0] < s->metrics_font1->min_char_or_byte2
1947 XDrawString (s->dpy, dest, gc,
1949 txt2, strlen(txt2));
1953 jwxyz_XSetAntiAliasing (s->dpy, gc, True);
1957 const char *ss = (j == 0
1958 ? (i == 0 ? "char" : "overall")
1959 : (i == 0 ? "utf8" : "16 bit"));
1960 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1962 XSetForeground (s->dpy, gc, red);
1964 sprintf (txt2, "%s ascent %d", ss, ascent);
1965 XDrawString (s->dpy, dest, gc,
1968 txt2, strlen(txt2));
1969 XDrawLine (s->dpy, dest, gc,
1970 xoff, y - sc*ascent,
1973 sprintf (txt2, "%s descent %d", ss, descent);
1974 XDrawString (s->dpy, dest, gc,
1977 txt2, strlen(txt2));
1978 XDrawLine (s->dpy, dest, gc,
1979 xoff, y + sc*descent,
1980 x3, y + sc*descent);
1984 /* ascent, descent, baseline */
1986 XSetForeground (s->dpy, gc, green);
1988 sprintf (txt2, "ascent %d", cc.ascent);
1990 XDrawString (s->dpy, dest, gc,
1991 x1, y - sc*cc.ascent - 2,
1992 txt2, strlen(txt2));
1993 XDrawLine (s->dpy, dest, gc,
1994 x1, y - sc*cc.ascent,
1995 x2, y - sc*cc.ascent);
1997 sprintf (txt2, "descent %d", cc.descent);
1998 if (cc.descent != 0)
1999 XDrawString (s->dpy, dest, gc,
2000 x1, y + sc*cc.descent - 2,
2001 txt2, strlen(txt2));
2002 XDrawLine (s->dpy, dest, gc,
2003 x1, y + sc*cc.descent,
2004 x2, y + sc*cc.descent);
2006 XSetForeground (s->dpy, gc, yellow);
2007 strcpy (txt2, "baseline");
2008 XDrawString (s->dpy, dest, gc,
2010 txt2, strlen(txt2));
2011 XDrawLine (s->dpy, dest, gc, x1, y, x2, y);
2016 XSetForeground (s->dpy, gc, blue);
2018 strcpy (txt2, "origin");
2019 XDrawString (s->dpy, dest, gc,
2021 y + sc*descent + 50,
2022 txt2, strlen(txt2));
2023 XDrawLine (s->dpy, dest, gc,
2024 xoff + x, y - sc*(ascent - 10),
2025 xoff + x, y + sc*(descent + 10));
2027 sprintf (txt2, "width %d", cc.width);
2028 XDrawString (s->dpy, dest, gc,
2029 xoff + x + sc*cc.width + 2,
2030 y + sc*descent + 60,
2031 txt2, strlen(txt2));
2032 XDrawLine (s->dpy, dest, gc,
2033 xoff + x + sc*cc.width, y - sc*(ascent - 10),
2034 xoff + x + sc*cc.width, y + sc*(descent + 10));
2037 /* lbearing, rbearing */
2039 XSetForeground (s->dpy, gc, green);
2041 sprintf (txt2, "lbearing %d", cc.lbearing);
2042 XDrawString (s->dpy, dest, gc,
2043 xoff + x + sc*cc.lbearing + 2,
2044 y + sc * descent + 30,
2045 txt2, strlen(txt2));
2046 XDrawLine (s->dpy, dest, gc,
2047 xoff + x + sc*cc.lbearing, y - sc*ascent,
2048 xoff + x + sc*cc.lbearing, y + sc*descent + 20);
2050 sprintf (txt2, "rbearing %d", cc.rbearing);
2051 XDrawString (s->dpy, dest, gc,
2052 xoff + x + sc*cc.rbearing + 2,
2053 y + sc * descent + 40,
2054 txt2, strlen(txt2));
2055 XDrawLine (s->dpy, dest, gc,
2056 xoff + x + sc*cc.rbearing, y - sc*ascent,
2057 xoff + x + sc*cc.rbearing, y + sc*descent + 40);
2059 y += sc * (ascent + descent) * 2;
2063 if (dest != s->window)
2064 XCopyArea (s->dpy, dest, s->window, s->bg_gc,
2065 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
2067 XFreeGC (s->dpy, gc);
2068 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap, &xftcolor);
2069 XftDrawDestroy (xftdraw);
2072 return s->frame_delay;
2078 /* Render all the words to the screen, and run the animation one step.
2079 Clear screen first, swap buffers after.
2081 static unsigned long
2082 fontglide_draw (Display *dpy, Window window, void *closure)
2084 state *s = (state *) closure;
2088 if (s->debug_metrics_p)
2089 return fontglide_draw_metrics (closure);
2096 XFillRectangle (s->dpy, s->b, s->bg_gc,
2097 0, 0, s->xgwa.width, s->xgwa.height);
2099 for (i = 0; i < s->nsentences; i++)
2100 draw_sentence (s, s->sentences[i]);
2103 if (s->debug_p && (s->prev_font_name || s->next_font_name))
2107 if (! s->metrics_font2)
2108 s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
2109 s->label_gc = XCreateGC (dpy, s->b, 0, 0);
2110 XSetFont (s->dpy, s->label_gc, s->metrics_font2->fid);
2112 if (s->prev_font_name)
2113 XDrawString (s->dpy, s->b, s->label_gc,
2114 10, 10 + s->metrics_font2->ascent,
2115 s->prev_font_name, strlen(s->prev_font_name));
2116 if (s->next_font_name)
2117 XDrawString (s->dpy, s->b, s->label_gc,
2118 10, 10 + s->metrics_font2->ascent * 2,
2119 s->next_font_name, strlen(s->next_font_name));
2123 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2126 XdbeSwapInfo info[1];
2127 info[0].swap_window = s->window;
2128 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
2129 XdbeSwapBuffers (s->dpy, info, 1);
2132 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2135 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
2136 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
2139 return s->frame_delay;
2144 /* When the subprocess has generated some output, this reads as much as it
2145 can into s->buf at s->buf_tail.
2148 drain_input (state *s)
2150 while (s->buf_tail < sizeof(s->buf) - 2)
2152 int c = textclient_getc (s->tc);
2154 s->buf[s->buf_tail++] = (char) c;
2161 /* Window setup and resource loading */
2164 fontglide_init (Display *dpy, Window window)
2167 state *s = (state *) calloc (1, sizeof(*s));
2170 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
2172 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2174 s->font_override = get_string_resource (dpy, "font", "Font");
2175 if (s->font_override && (!*s->font_override || *s->font_override == '('))
2176 s->font_override = 0;
2178 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
2179 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
2180 if (s->border_width < 0 || s->border_width > 20)
2181 s->border_width = 1;
2183 s->speed = get_float_resource (dpy, "speed", "Float");
2184 if (s->speed <= 0 || s->speed > 200)
2187 s->linger = get_float_resource (dpy, "linger", "Float");
2188 if (s->linger <= 0 || s->linger > 200)
2191 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
2194 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
2195 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
2200 if (s->debug_metrics_p && !s->font_override)
2201 s->font_override = "Helvetica Bold 16";
2206 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
2208 # ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
2213 if (s->debug_metrics_p) s->trails_p = False;
2216 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
2219 const char *ss = get_string_resource (dpy, "mode", "Mode");
2220 if (!ss || !*ss || !strcasecmp (ss, "random"))
2221 s->mode = ((random() % 2) ? SCROLL : PAGE);
2222 else if (!strcasecmp (ss, "scroll"))
2224 else if (!strcasecmp (ss, "page"))
2226 else if (!strcasecmp (ss, "chars") || !strcasecmp (ss, "char"))
2231 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
2238 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2239 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
2241 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
2243 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
2245 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2249 s->ba = XCreatePixmap (s->dpy, s->window,
2250 s->xgwa.width, s->xgwa.height,
2260 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
2261 "background", "Background");
2262 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
2264 s->nsentences = 5; /* #### */
2265 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
2269 s->start_time = time ((time_t *) 0);
2270 s->tc = textclient_open (dpy);
2277 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
2280 state *s = (state *) closure;
2282 if (! s->debug_metrics_p)
2284 if (event->xany.type == KeyPress)
2286 static const unsigned long max = 0x110000;
2289 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2291 if (s->entering_unicode_p > 0)
2294 unsigned long new_char = 0;
2296 if (c >= 'a' && c <= 'f')
2297 digit = c + 0xa - 'a';
2298 else if (c >= 'A' && c <= 'F')
2299 digit = c + 0xa - 'A';
2300 else if (c >= '0' && c <= '9')
2301 digit = c + 0 - '0';
2304 s->entering_unicode_p = 0;
2308 if (s->entering_unicode_p == 1)
2310 else if (s->entering_unicode_p == 2)
2311 new_char = s->debug_metrics_p;
2313 new_char = (new_char << 4) | digit;
2314 if (new_char > 0 && new_char < max)
2316 s->debug_metrics_p = new_char;
2317 s->entering_unicode_p = 2;
2320 s->entering_unicode_p = 0;
2325 s->debug_metrics_antialiasing_p ^= True;
2326 else if (c == 3 || c == 27)
2329 s->debug_metrics_p = (unsigned char) c;
2330 else if (keysym == XK_Left || keysym == XK_Right)
2332 s->debug_metrics_p += (keysym == XK_Left ? -1 : 1);
2333 if (s->debug_metrics_p >= max)
2334 s->debug_metrics_p = 1;
2335 else if (s->debug_metrics_p <= 0)
2336 s->debug_metrics_p = max - 1;
2339 else if (keysym == XK_Prior)
2340 s->debug_metrics_p = (s->debug_metrics_p + max - 0x80) % max;
2341 else if (keysym == XK_Next)
2342 s->debug_metrics_p = (s->debug_metrics_p + 0x80) % max;
2343 else if (keysym == XK_Up)
2345 else if (keysym == XK_Down)
2346 s->debug_scale = (s->debug_scale > 1 ? s->debug_scale-1 : 1);
2347 else if (keysym == XK_F1)
2348 s->entering_unicode_p = 1;
2360 fontglide_reshape (Display *dpy, Window window, void *closure,
2361 unsigned int w, unsigned int h)
2363 state *s = (state *) closure;
2364 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2366 if (s->dbuf && s->ba)
2368 XFreePixmap (s->dpy, s->ba);
2369 s->ba = XCreatePixmap (s->dpy, s->window,
2370 s->xgwa.width, s->xgwa.height,
2372 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
2373 s->xgwa.width, s->xgwa.height);
2379 fontglide_free (Display *dpy, Window window, void *closure)
2381 state *s = (state *) closure;
2382 textclient_close (s->tc);
2385 if (s->metrics_xftfont)
2386 XftFontClose (s->dpy, s->metrics_xftfont);
2387 if (s->metrics_font1)
2388 XFreeFont (s->dpy, s->metrics_font1);
2389 if (s->metrics_font2 && s->metrics_font1 != s->metrics_font2)
2390 XFreeFont (s->dpy, s->metrics_font2);
2391 if (s->prev_font_name) free (s->prev_font_name);
2392 if (s->next_font_name) free (s->next_font_name);
2393 if (s->label_gc) XFreeGC (dpy, s->label_gc);
2396 /* #### there's more to free here */
2402 static const char *fontglide_defaults [] = {
2403 ".background: #000000",
2404 ".foreground: #DDDDDD",
2405 ".borderColor: #555555",
2407 "*program: xscreensaver-text",
2412 /* I'm not entirely clear on whether the charset of an XLFD has any
2413 meaning when Xft is being used. */
2414 "*fontCharset: iso8859-1",
2415 /*"*fontCharset: iso10646-1", */
2416 /*"*fontCharset: *-*",*/
2418 "*fontBorderWidth: 2",
2424 "*debugMetrics: False",
2426 "*doubleBuffer: True",
2427 # ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2429 "*useDBEClear: True",
2430 # endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2434 static XrmOptionDescRec fontglide_options [] = {
2435 { "-mode", ".mode", XrmoptionSepArg, 0 },
2436 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
2437 { "-page", ".mode", XrmoptionNoArg, "page" },
2438 { "-random", ".mode", XrmoptionNoArg, "random" },
2439 { "-delay", ".delay", XrmoptionSepArg, 0 },
2440 { "-speed", ".speed", XrmoptionSepArg, 0 },
2441 { "-linger", ".linger", XrmoptionSepArg, 0 },
2442 { "-program", ".program", XrmoptionSepArg, 0 },
2443 { "-font", ".font", XrmoptionSepArg, 0 },
2444 { "-fn", ".font", XrmoptionSepArg, 0 },
2445 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
2446 { "-trails", ".trails", XrmoptionNoArg, "True" },
2447 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
2448 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
2449 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
2451 { "-debug", ".debug", XrmoptionNoArg, "True" },
2452 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
2458 XSCREENSAVER_MODULE ("FontGlide", fontglide)