1 /* xscreensaver, Copyright (c) 2003-2014 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 } 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 int debug_metrics_p, debug_metrics_antialiasing_p;
132 static void drain_input (state *s);
136 pick_font_size (state *s)
138 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
139 int min, max, r, pixel;
144 if (min < 10) min = 10;
145 if (max < 30) max = 30;
149 pixel = min + ((random() % r) + (random() % r) + (random() % r));
151 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
158 /* Finds the set of scalable fonts on the system; picks one;
159 and loads that font in a random pixel size.
160 Returns False if something went wrong.
163 pick_font_1 (state *s, sentence *se)
168 # ifndef HAVE_COCOA /* real Xlib */
171 XFontStruct *info = 0;
172 int count = 0, count2 = 0;
177 XftFontClose (s->dpy, se->xftfont);
178 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
180 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
183 free (se->font_name);
188 if (s->font_override)
189 sprintf (pattern, "%.200s", s->font_override);
191 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
198 "0", /* pixel size */
199 "0", /* point size */
200 "0", /* resolution x */
201 "0", /* resolution y */
204 s->charset); /* registry + encoding */
206 names = XListFonts (s->dpy, pattern, 1000, &count);
210 if (s->font_override)
211 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
213 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
218 i = random() % count;
220 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
225 fprintf (stderr, "%s: pattern %s\n"
226 " gave unusable %s\n\n",
227 progname, pattern, names[i]);
233 XFontStruct *font = &info[0];
234 unsigned long value = 0;
235 char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
236 unsigned long pixel=0, point=0, res_x=0, res_y=0;
238 unsigned long avg_width=0;
239 char *registry=0, *encoding=0;
241 char *bogus = "\"?\"";
243 # define STR(ATOM,VAR) \
245 a = XInternAtom (s->dpy, (ATOM), False); \
246 if (XGetFontProperty (font, a, &value)) \
247 VAR = XGetAtomName (s->dpy, value); \
251 # define INT(ATOM,VAR) \
253 a = XInternAtom (s->dpy, (ATOM), False); \
254 if (!XGetFontProperty (font, a, &VAR) || \
258 STR ("FOUNDRY", foundry);
259 STR ("FAMILY_NAME", family);
260 STR ("WEIGHT_NAME", weight);
261 STR ("SLANT", slant);
262 STR ("SETWIDTH_NAME", setwidth);
263 STR ("ADD_STYLE_NAME", add_style);
264 INT ("PIXEL_SIZE", pixel);
265 INT ("POINT_SIZE", point);
266 INT ("RESOLUTION_X", res_x);
267 INT ("RESOLUTION_Y", res_y);
268 STR ("SPACING", spacing);
269 INT ("AVERAGE_WIDTH", avg_width);
270 STR ("CHARSET_REGISTRY", registry);
271 STR ("CHARSET_ENCODING", encoding);
276 pixel = pick_font_size (s);
279 /* Occasionally change the aspect ratio of the font, by increasing
280 either the X or Y resolution (while leaving the other alone.)
282 #### Looks like this trick doesn't really work that well: the
283 metrics of the individual characters are ok, but the
284 overall font ascent comes out wrong (unscaled.)
286 if (! (random() % 8))
289 double scale = 1 + (frand(n) + frand(n) + frand(n));
298 "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
299 foundry, family, weight, slant, setwidth, add_style,
300 pixel, "*", /* point, */
301 res_x, res_y, spacing,
308 fprintf (stderr, "%s: font has bogus %s property: %s\n",
309 progname, bogus, names[i]);
311 if (foundry) XFree (foundry);
312 if (family) XFree (family);
313 if (weight) XFree (weight);
314 if (slant) XFree (slant);
315 if (setwidth) XFree (setwidth);
316 if (add_style) XFree (add_style);
317 if (spacing) XFree (spacing);
318 if (registry) XFree (registry);
319 if (encoding) XFree (encoding);
324 XFreeFontInfo (names2, info, count2);
325 XFreeFontNames (names);
327 # else /* HAVE_COCOA */
329 if (s->font_override)
330 sprintf (pattern, "%.200s", s->font_override);
333 const char *family = "random";
334 const char *weight = ((random() % 2) ? "normal" : "bold");
335 const char *slant = ((random() % 2) ? "o" : "r");
336 int size = 10 * pick_font_size (s);
337 sprintf (pattern, "*-%s-%s-%s-*-%d-*", family, weight, slant, size);
340 # endif /* HAVE_COCOA */
342 if (! ok) return False;
344 se->xftfont = XftFontOpenXlfd (s->dpy, screen_number (s->xgwa.screen),
347 /* Sometimes we get fonts with screwed up metrics. For example:
348 -b&h-lucida-medium-r-normal-sans-40-289-100-100-p-0-iso8859-1
350 When using XDrawString, XTextExtents and XTextExtents16, it is rendered
351 as a scaled-up bitmap font. The character M has rbearing 70, ascent 68
352 and width 78, which is correct for the glyph as rendered.
354 But when using XftDrawStringUtf8 and XftTextExtentsUtf8, it is rendered
355 at the original, smaller, un-scaled size, with rbearing 26, ascent 25
358 So it's taking the *size* from the unscaled font, the *advancement* from
359 the scaled-up version, and then *not* actually scaling it up. Awesome.
361 So, after loading the font, measure the M, and if its advancement is more
362 than 20% larger than its rbearing, reject the font.
364 ------------------------------------------------------------------------
366 Some observations on this nonsense from Dave Odell:
368 1. -*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1 normally
369 resolves to /usr/share/fonts/X11/100dpi/lutBS24-ISO8859-1.pcf.gz.
371 -*-lucidatypewriter-* is from the 'xfonts-100dpi' package in
372 Debian/Ubuntu. It's usually (54.46% of systems), but not always,
373 installed whenever an X.org server (57.96% of systems) is. It might
374 be a good idea for this and xfonts-75dpi to be recommended
375 dependencies of XScreenSaver in Debian, but that's neither here nor
376 there. https://qa.debian.org/popcon.php?package=xorg
377 https://qa.debian.org/popcon.php?package=xfonts-100dpi
379 2. It normally resolves to the PCF font... but not always.
381 Fontconfig has /etc/fonts/conf.d/ (it's /opt/local/etc/fonts/conf.d/
382 with MacPorts) containing symlinks to configuration files. And both
383 Debian and Ubuntu normally has a 70-no-bitmaps.conf, installed as part
384 of the 'fontconfig-config' package. And the 70-no-bitmaps.conf
385 symlink... disables bitmap fonts.
387 Without bitmap fonts, I get DejaVu Sans.
389 3. There's another symlink of interest here:
390 /etc/fonts/conf.d/10-scale-bitmap-fonts.conf. This adds space to the
391 right of glyphs of bitmap fonts when the requested size of the font is
392 larger than the actual bitmap font. Ubuntu and MacPorts has this one.
394 This specifically is causing text to have excessive character spacing.
396 (jwz asks: WHY WOULD ANYONE EVER WANT THIS BEHAVIOR?)
398 4. Notice that I'm only talking about Debian and Ubuntu. Other distros
399 will probably have different symlinks in /etc/fonts/conf.d/. So yes,
400 this can be an issue on Linux as well as MacOS.
408 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) "M", 1, &extents);
409 rbearing = extents.width - extents.x;
410 width = extents.xOff;
411 ratio = rbearing / (float) width;
415 fprintf (stderr, "%s: M ratio %.2f (%d %d): %s\n", progname,
416 ratio, rbearing, width, pattern);
419 if (ratio < min && !s->font_override)
423 fprintf (stderr, "%s: skipping font with broken metrics: %s\n",
435 fprintf (stderr, "%s: unable to load font %s\n",
441 fprintf(stderr, "%s: %s\n", progname, pattern);
444 se->font_name = strdup (pattern);
449 /* Finds the set of scalable fonts on the system; picks one;
450 and loads that font in a random pixel size.
453 pick_font (state *s, sentence *se)
456 for (i = 0; i < 50; i++)
457 if (pick_font_1 (s, se))
459 fprintf (stderr, "%s: too many font-loading failures: giving up!\n",
465 static char *unread_word_text = 0;
467 /* Returns a newly-allocated string with one word in it, or NULL if there
468 is no complete word available.
471 get_word_text (state *s)
473 const char *start = s->buf;
480 /* If we just launched, and haven't had any text yet, and it has been
481 more than 2 seconds since we launched, then push out "Loading..."
482 as our first text. So if the text source is speedy, just use that.
483 But if we'd display a blank screen for a while, give 'em something
489 s->start_time < ((time ((time_t *) 0) - 2)))
491 unread_word_text = "Loading...";
495 if (unread_word_text)
497 start = unread_word_text;
498 unread_word_text = 0;
502 /* Skip over whitespace at the beginning of the buffer,
503 and count up how many linebreaks we see while doing so.
511 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
518 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
519 to trigger a sentence break here.) */
523 /* Skip forward to the end of this word (find next whitespace.) */
531 /* If we have a word, allocate a string for it */
534 result = malloc ((end - start) + 1);
535 strncpy (result, start, (end-start));
536 result [end-start] = 0;
541 /* Make room in the buffer by compressing out any bytes we've processed.
545 int n = end - s->buf;
546 memmove (s->buf, end, sizeof(s->buf) - n);
554 /* Returns a 1-bit pixmap of the same size as the drawable,
555 with a 0 wherever the drawable is black.
558 make_mask (Screen *screen, Visual *visual, Drawable drawable)
560 Display *dpy = DisplayOfScreen (screen);
561 unsigned long black = BlackPixelOfScreen (screen);
564 unsigned int w, h, bw, d;
569 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bw, &d);
570 in = XGetImage (dpy, drawable, 0, 0, w, h, ~0L, ZPixmap);
571 out = XCreateImage (dpy, visual, 1, XYPixmap, 0, 0, w, h, 8, 0);
572 out->data = (char *) malloc (h * out->bytes_per_line);
573 for (y = 0; y < h; y++)
574 for (x = 0; x < w; x++)
575 XPutPixel (out, x, y, (black != XGetPixel (in, x, y)));
576 mask = XCreatePixmap (dpy, drawable, w, h, 1L);
577 gc = XCreateGC (dpy, mask, 0, 0);
578 XPutImage (dpy, mask, gc, out, 0, 0, 0, 0, w, h);
582 in->data = out->data = 0;
589 /* Gets some random text, and creates a "word" object from it.
592 new_word (state *s, sentence *se, const char *txt, Bool alloc_p)
596 int bw = s->border_width;
601 w = (word *) calloc (1, sizeof(*w));
602 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) txt, strlen(txt),
605 w->lbearing = -extents.x;
606 w->rbearing = extents.width - extents.x;
607 w->ascent = extents.y;
608 w->descent = extents.height - extents.y;
609 w->width = extents.xOff;
616 if (s->mode == SCROLL && !alloc_p) abort();
622 GC gc_fg, gc_bg, gc_black;
624 int width = w->rbearing - w->lbearing;
625 int height = w->ascent + w->descent;
627 if (width <= 0) width = 1;
628 if (height <= 0) height = 1;
630 w->pixmap = XCreatePixmap (s->dpy, s->b, width, height, s->xgwa.depth);
631 xftdraw = XftDrawCreate (s->dpy, w->pixmap, s->xgwa.visual,
634 gcv.foreground = se->xftcolor_fg.pixel;
635 gc_fg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
637 gcv.foreground = se->xftcolor_bg.pixel;
638 gc_bg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
640 gcv.foreground = BlackPixelOfScreen (s->xgwa.screen);
641 gc_black = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
643 XFillRectangle (s->dpy, w->pixmap, gc_black, 0, 0, width, height);
648 /* bounding box (behind the characters) */
649 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
650 0, 0, width-1, height-1);
654 /* Draw background text for border */
655 for (i = -bw; i <= bw; i++)
656 for (j = -bw; j <= bw; j++)
657 XftDrawStringUtf8 (xftdraw, &se->xftcolor_bg, se->xftfont,
658 -w->lbearing + i, w->ascent + j,
659 (FcChar8 *) txt, strlen(txt));
661 /* Draw foreground text */
662 XftDrawStringUtf8 (xftdraw, &se->xftcolor_fg, se->xftfont,
663 -w->lbearing, w->ascent,
664 (FcChar8 *) txt, strlen(txt));
669 if (w->ascent != height)
671 /* baseline (on top of the characters) */
672 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
673 0, w->ascent, width-1, w->ascent);
678 /* left edge of charcell */
679 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
681 -w->lbearing, height-1);
684 if (w->rbearing != w->width)
686 /* right edge of charcell */
687 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
688 w->width - w->lbearing, 0,
689 w->width - w->lbearing, height-1);
694 w->mask = make_mask (s->xgwa.screen, s->xgwa.visual, w->pixmap);
696 XftDrawDestroy (xftdraw);
697 XFreeGC (s->dpy, gc_fg);
698 XFreeGC (s->dpy, gc_bg);
699 XFreeGC (s->dpy, gc_black);
702 w->text = strdup (txt);
708 free_word (state *s, word *w)
710 if (w->text) free (w->text);
711 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
712 if (w->mask) XFreePixmap (s->dpy, w->mask);
717 new_sentence (state *st, state *s)
720 sentence *se = (sentence *) calloc (1, sizeof (*se));
721 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
723 se->id = ++st->id_tick;
729 free_sentence (state *s, sentence *se)
732 for (i = 0; i < se->nwords; i++)
733 free_word (s, se->words[i]);
737 free (se->font_name);
739 XFreeGC (s->dpy, se->fg_gc);
743 XftFontClose (s->dpy, se->xftfont);
744 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
746 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
754 /* free the word, and put its text back at the front of the input queue,
755 to be read next time. */
757 unread_word (state *s, word *w)
759 if (unread_word_text)
761 unread_word_text = w->text;
767 /* Divide each of the words in the sentence into one character words,
768 without changing the positions of those characters.
771 split_words (state *s, sentence *se)
777 char ***word_chars = (char ***) malloc (se->nwords * sizeof(*word_chars));
778 for (i = 0; i < se->nwords; i++)
781 word *ow = se->words[i];
782 word_chars[i] = utf8_split (ow->text, &L);
786 words2 = (word **) calloc (nwords2, sizeof(*words2));
788 for (i = 0, j = 0; i < se->nwords; i++)
790 char **chars = word_chars[i];
791 word *parent = se->words[i];
794 int sx = parent->start_x;
795 int sy = parent->start_y;
796 int tx = parent->target_x;
797 int ty = parent->target_y;
800 for (k = 0; chars[k]; k++)
803 word *w2 = new_word (s, se, t2, True);
818 /* This is not invariant when kerning is involved! */
819 /* if (x != parent->x + parent->width) abort(); */
821 free (chars); /* but we retain its contents */
822 free_word (s, parent);
824 if (j != nwords2) abort();
829 se->nwords = nwords2;
833 /* Set the source or destination position of the words to be somewhere
837 scatter_sentence (state *s, sentence *se)
840 int off = s->border_width * 4 + 2;
842 int flock_p = ((random() % 4) == 0);
843 int mode = (flock_p ? (random() % 12) : 0);
845 for (i = 0; i < se->nwords; i++)
847 word *w = se->words[i];
849 int r = (flock_p ? mode : (random() % 4));
850 int left = -(off + w->rbearing);
851 int top = -(off + w->descent);
852 int right = off - w->lbearing + s->xgwa.width;
853 int bottom = off + w->ascent + s->xgwa.height;
856 /* random positions on the edges */
857 case 0: x = left; y = random() % s->xgwa.height; break;
858 case 1: x = right; y = random() % s->xgwa.height; break;
859 case 2: x = random() % s->xgwa.width; y = top; break;
860 case 3: x = random() % s->xgwa.width; y = bottom; break;
862 /* straight towards the edges */
863 case 4: x = left; y = w->target_y; break;
864 case 5: x = right; y = w->target_y; break;
865 case 6: x = w->target_x; y = top; break;
866 case 7: x = w->target_x; y = bottom; break;
869 case 8: x = left; y = top; break;
870 case 9: x = left; y = bottom; break;
871 case 10: x = right; y = top; break;
872 case 11: x = right; y = bottom; break;
874 default: abort(); break;
877 if (se->anim_state == IN)
890 w->nticks = ((100 + ((random() % 140) +
901 /* Set the source position of the words to be off the right side,
902 and the destination to be off the left side.
905 aim_sentence (state *s, sentence *se)
911 if (se->nwords <= 0) abort();
913 /* Have the sentence shift up or down a little bit; not too far, and
914 never let it fall off the top or bottom of the screen before its
915 last character has reached the left edge.
917 for (i = 0; i < 10; i++)
919 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
920 yoff = ty - se->words[0]->target_y;
921 if (yoff < s->xgwa.height/3) /* this one is ok */
925 for (i = 0; i < se->nwords; i++)
927 word *w = se->words[i];
928 w->start_x = w->target_x + s->xgwa.width;
929 w->target_x -= se->width;
930 w->start_y = w->target_y;
934 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
936 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
941 for (i = 0; i < se->nwords; i++)
943 word *w = se->words[i];
950 /* Randomize the order of the words in the list (since that changes
951 which ones are "on top".)
954 shuffle_words (state *s, sentence *se)
957 for (i = 0; i < se->nwords-1; i++)
959 int j = i + (random() % (se->nwords - i));
960 word *swap = se->words[i];
961 se->words[i] = se->words[j];
967 /* qsort comparitor */
969 cmp_sentences (const void *aa, const void *bb)
971 const sentence *a = *(sentence **) aa;
972 const sentence *b = *(sentence **) bb;
973 return ((a ? a->id : 999999) - (b ? b->id : 999999));
977 /* Sort the sentences by id, so that sentences added later are on top.
980 sort_sentences (state *s)
982 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
986 /* Re-pick the colors of the text and border
989 recolor (state *s, sentence *se)
993 fg.red = (random() % 0x5555) + 0xAAAA;
994 fg.green = (random() % 0x5555) + 0xAAAA;
995 fg.blue = (random() % 0x5555) + 0xAAAA;
997 bg.red = (random() % 0x5555);
998 bg.green = (random() % 0x5555);
999 bg.blue = (random() % 0x5555);
1005 XRenderColor swap = fg; fg = bg; bg = swap;
1011 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1013 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1017 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &fg,
1019 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &bg,
1025 align_line (state *s, sentence *se, int line_start, int x, int right)
1028 switch (se->alignment)
1030 case LEFT: off = 0; break;
1031 case CENTER: off = (right - x) / 2; break;
1032 case RIGHT: off = (right - x); break;
1033 default: abort(); break;
1037 for (j = line_start; j < se->nwords; j++)
1038 se->words[j]->target_x += off;
1042 /* Fill the sentence with new words: in "page" mode, fills the page
1043 with text; in "scroll" mode, just makes one long horizontal sentence.
1044 The sentence might have *no* words in it, if no text is currently
1048 populate_sentence (state *s, sentence *se)
1051 int left, right, top, x, y;
1056 int array_size = 100;
1058 se->move_chars_p = (s->mode == SCROLL ? False :
1059 (random() % 3) ? False : True);
1060 se->alignment = (random() % 3);
1066 for (i = 0; i < se->nwords; i++)
1067 free_word (s, se->words[i]);
1071 se->words = (word **) calloc (array_size, sizeof(*se->words));
1077 left = random() % (s->xgwa.width / 3);
1078 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1079 top = random() % (s->xgwa.height * 2 / 3);
1083 right = s->xgwa.width;
1084 top = random() % s->xgwa.height;
1096 const char *txt = get_word_text (s);
1100 if (se->nwords == 0)
1101 return; /* If the stream is empty, bail. */
1103 break; /* If EOF after some words, end of sentence. */
1106 if (! se->xftfont) /* Got a word: need a font now */
1110 if (y < se->xftfont->ascent)
1111 y += se->xftfont->ascent;
1113 /* Measure the space character to figure out how much room to
1114 leave between words (since we don't actually render that.) */
1115 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) " ", 1,
1117 space = extents.xOff;
1120 w = new_word (s, se, txt, !se->move_chars_p);
1122 /* If we have a few words, let punctuation terminate the sentence:
1123 stop gathering more words if the last word ends in a period, etc. */
1124 if (se->nwords >= 4)
1126 char c = w->text[strlen(w->text)-1];
1127 if (c == '.' || c == '?' || c == '!')
1131 /* If the sentence is kind of long already, terminate at commas, etc. */
1132 if (se->nwords >= 12)
1134 char c = w->text[strlen(w->text)-1];
1135 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1136 c == ')' || c == ']' || c == '}')
1140 if (se->nwords >= 25) /* ok that's just about enough out of you */
1143 if (s->mode == PAGE &&
1144 x + w->rbearing > right) /* wrap line */
1146 align_line (s, se, line_start, x, right);
1147 line_start = se->nwords;
1150 y += se->xftfont->ascent + se->xftfont->descent;
1152 /* If we're close to the bottom of the screen, stop, and
1153 unread the current word. (But not if this is the first
1154 word, otherwise we might just get stuck on it.)
1156 if (se->nwords > 0 &&
1157 y + se->xftfont->ascent + se->xftfont->descent > s->xgwa.height)
1168 x += w->width + space;
1171 if (se->nwords >= (array_size - 1))
1174 se->words = (word **)
1175 realloc (se->words, array_size * sizeof(*se->words));
1178 fprintf (stderr, "%s: out of memory (%d words)\n",
1179 progname, array_size);
1184 se->words[se->nwords++] = w;
1192 align_line (s, se, line_start, x, right);
1193 if (se->move_chars_p)
1194 split_words (s, se);
1195 scatter_sentence (s, se);
1196 shuffle_words (s, se);
1199 aim_sentence (s, se);
1209 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1210 for (i = 0; i < se->nwords; i++)
1211 fprintf (stderr, " %s", se->words[i]->text);
1212 fprintf (stderr, "\n");
1218 /* Render a single word object to the screen.
1221 draw_word (state *s, sentence *se, word *word)
1224 if (! word->pixmap) return;
1226 x = word->x + word->lbearing;
1227 y = word->y - word->ascent;
1228 w = word->rbearing - word->lbearing;
1229 h = word->ascent + word->descent;
1233 x > s->xgwa.width ||
1237 XSetClipMask (s->dpy, se->fg_gc, word->mask);
1238 XSetClipOrigin (s->dpy, se->fg_gc, x, y);
1239 XCopyArea (s->dpy, word->pixmap, s->b, se->fg_gc,
1244 /* If there is room for more sentences, add one.
1247 more_sentences (state *s)
1251 for (i = 0; i < s->nsentences; i++)
1253 sentence *se = s->sentences[i];
1256 se = new_sentence (s, s);
1257 populate_sentence (s, se);
1259 s->spawn_p = False, any = True;
1262 free_sentence (s, se);
1265 s->sentences[i] = se;
1267 s->latest_sentence = se->id;
1272 if (any) sort_sentences (s);
1276 /* Render all the words to the screen, and run the animation one step.
1279 draw_sentence (state *s, sentence *se)
1286 for (i = 0; i < se->nwords; i++)
1288 word *w = se->words[i];
1293 if (se->anim_state != PAUSE &&
1294 w->tick <= w->nticks)
1296 int dx = w->target_x - w->start_x;
1297 int dy = w->target_y - w->start_y;
1298 double r = sin (w->tick * M_PI / (2 * w->nticks));
1299 w->x = w->start_x + (dx * r);
1300 w->y = w->start_y + (dy * r);
1303 if (se->anim_state == OUT && s->mode == PAGE)
1304 w->tick++; /* go out faster */
1310 int dx = w->target_x - w->start_x;
1311 int dy = w->target_y - w->start_y;
1312 double r = (double) w->tick / w->nticks;
1313 w->x = w->start_x + (dx * r);
1314 w->y = w->start_y + (dy * r);
1316 moved = (w->tick <= w->nticks);
1318 /* Launch a new sentence when:
1319 - the front of this sentence is almost off the left edge;
1320 - the end of this sentence is almost on screen.
1323 if (se->anim_state != OUT &&
1325 se->id == s->latest_sentence)
1327 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1328 w->x + se->width < (s->xgwa.width * 2.1));
1329 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1331 if (new_p || rand_p)
1333 se->anim_state = OUT;
1337 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1339 se->words[0]->x + se->width,
1340 rand_p ? " randomly" : "");
1351 draw_word (s, se, w);
1354 if (moved && se->anim_state == PAUSE)
1359 switch (se->anim_state)
1362 se->anim_state = PAUSE;
1363 se->pause_tick = (se->nwords * 7 * s->linger);
1364 if (se->move_chars_p)
1365 se->pause_tick /= 5;
1366 scatter_sentence (s, se);
1367 shuffle_words (s, se);
1370 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1374 if (--se->pause_tick <= 0)
1376 se->anim_state = OUT;
1380 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1387 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1391 for (j = 0; j < s->nsentences; j++)
1392 if (s->sentences[j] == se)
1393 s->sentences[j] = 0;
1394 free_sentence (s, se);
1405 #ifdef DEBUG /* All of this stuff is for -debug-metrics mode. */
1409 scale_ximage (Screen *screen, Window window, XImage *img, int scale,
1412 Display *dpy = DisplayOfScreen (screen);
1414 unsigned width = img->width, height = img->height;
1418 p2 = XCreatePixmap (dpy, window, width*scale, height*scale, img->depth);
1419 gc = XCreateGC (dpy, p2, 0, 0);
1421 XSetForeground (dpy, gc, BlackPixelOfScreen (screen));
1422 XFillRectangle (dpy, p2, gc, 0, 0, width*scale, height*scale);
1423 for (y = 0; y < height; y++)
1424 for (x = 0; x < width; x++)
1426 XSetForeground (dpy, gc, XGetPixel (img, x, y));
1427 XFillRectangle (dpy, p2, gc, x*scale, y*scale, scale, scale);
1432 XWindowAttributes xgwa;
1434 c.red = c.green = c.blue = 0x4444;
1435 c.flags = DoRed|DoGreen|DoBlue;
1436 XGetWindowAttributes (dpy, window, &xgwa);
1437 if (! XAllocColor (dpy, xgwa.colormap, &c)) abort();
1438 XSetForeground (dpy, gc, c.pixel);
1439 XDrawRectangle (dpy, p2, gc, 0, 0, width*scale-1, height*scale-1);
1440 XDrawRectangle (dpy, p2, gc, margin*scale, margin*scale,
1441 width*scale-1, height*scale-1);
1442 for (y = 0; y <= height - 2*margin; y++)
1443 XDrawLine (dpy, p2, gc,
1444 margin*scale, (y+margin)*scale-1,
1445 (width-margin)*scale, (y+margin)*scale-1);
1446 for (x = 0; x <= width - 2*margin; x++)
1447 XDrawLine (dpy, p2, gc,
1448 (x+margin)*scale-1, margin*scale,
1449 (x+margin)*scale-1, (height-margin)*scale);
1450 XFreeColors (dpy, xgwa.colormap, &c.pixel, 1, 0);
1458 static int check_edge (Display *dpy, Drawable p, GC gc,
1459 unsigned msg_x, unsigned msg_y, const char *msg,
1461 unsigned x, unsigned y, unsigned dim, unsigned end)
1472 XDrawString (dpy, p, gc, msg_x, msg_y, msg, strlen (msg));
1476 if (XGetPixel(img, pt[0], pt[1]) & 0xffffff)
1486 static unsigned long
1487 fontglide_draw_metrics (state *s)
1489 unsigned int margin = (s->debug_metrics_antialiasing_p ? 2 : 0);
1491 char txt[2], utxt[3], txt2[80];
1493 const char *fn = (s->font_override ? s->font_override : "fixed");
1494 XFontStruct *font = XLoadQueryFont (s->dpy, fn);
1495 XFontStruct *font2 = XLoadQueryFont (s->dpy, "fixed");
1496 XCharStruct c, overall, fake_c;
1497 int dir, ascent, descent;
1503 int sc = s->debug_scale;
1505 unsigned long red = 0xFFFF0000; /* so shoot me */
1506 unsigned long green = 0xFF00FF00;
1507 unsigned long blue = 0xFF6666FF;
1508 unsigned long yellow = 0xFFFFFF00;
1509 unsigned long cyan = 0xFF004040;
1511 Drawable dest = s->b ? s->b : s->window;
1515 txt[0] = s->debug_metrics_p;
1518 /* Convert Latin1 to UTF-8. */
1519 if ((unsigned char) txt[0] >= 0xC0)
1522 utxt[1] = ((unsigned char) txt[0]) & 0xBF;
1525 else if ((unsigned char) txt[0] >= 0xA0)
1537 txt3 = utf8_to_XChar2b (utxt, 0);
1539 if (! font) font = font2;
1541 gc = XCreateGC (s->dpy, dest, 0, 0);
1542 XSetFont (s->dpy, gc, font->fid);
1545 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1548 xftfont = XftFontOpenXlfd (s->dpy, screen_number(s->xgwa.screen), fn);
1550 xftfont = XftFontOpenXlfd (s->dpy, screen_number(s->xgwa.screen), "fixed");
1555 xftdraw = XftDrawCreate (s->dpy, dest, s->xgwa.visual,
1557 XftColorAllocName (s->dpy, s->xgwa.visual, s->xgwa.colormap, "white",
1559 XftTextExtentsUtf8 (s->dpy, xftfont, (FcChar8 *) utxt, strlen(utxt),
1563 XTextExtents (font, txt, strlen(txt),
1564 &dir, &ascent, &descent, &overall);
1565 c = (s->debug_metrics_p >= font->min_char_or_byte2
1566 ? font->per_char[s->debug_metrics_p - font->min_char_or_byte2]
1569 XSetForeground (s->dpy, gc, BlackPixelOfScreen (s->xgwa.screen));
1570 XFillRectangle (s->dpy, dest, gc, 0, 0, s->xgwa.width, s->xgwa.height);
1572 for (j = 0; j < 2; j++) {
1573 Bool xft_p = (j != 0);
1574 int ww = s->xgwa.width / 2 - 20;
1575 x = (ww - overall.width) / 2;
1576 y = (s->xgwa.height - (2 * sc * (ascent + descent))) / 2;
1578 for (i = 0; i < 2; i++)
1580 int xoff = (j == 0 ? 0 : ww + 20);
1582 int x1 = xoff + ww * 0.18;
1583 int x2 = xoff + ww * 0.82;
1588 memset (&fake_c, 0, sizeof(fake_c));
1590 if (!xft_p && i == 0)
1592 else if (!xft_p && i == 1)
1594 else if (xft_p && i == 0)
1596 /* Measure the glyph in the Xft way */
1598 XftTextExtentsUtf8 (s->dpy, xftfont, (FcChar8 *) utxt, strlen(utxt),
1600 fake_c.lbearing = -extents.x;
1601 fake_c.rbearing = extents.width - extents.x;
1602 fake_c.ascent = extents.y;
1603 fake_c.descent = extents.height - extents.y;
1604 fake_c.width = extents.xOff;
1609 /* Measure the glyph in the 16-bit way */
1610 int dir, ascent, descent;
1611 XTextExtents16 (font, txt3, 1, &dir, &ascent, &descent, &fake_c);
1615 pixw = margin * 2 + cc.rbearing - cc.lbearing;
1616 pixh = margin * 2 + cc.ascent + cc.descent;
1617 p = (pixw > 0 && pixh > 0
1618 ? XCreatePixmap (s->dpy, dest, pixw, pixh, s->xgwa.depth)
1624 GC gc2 = XCreateGC (s->dpy, p, 0, 0);
1626 jwxyz_XSetAntiAliasing (s->dpy, gc2, False);
1628 XSetFont (s->dpy, gc2, font->fid);
1629 XSetForeground (s->dpy, gc2, BlackPixelOfScreen (s->xgwa.screen));
1630 XFillRectangle (s->dpy, p, gc2, 0, 0, pixw, pixh);
1631 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1632 XSetForeground (s->dpy, gc2, WhitePixelOfScreen (s->xgwa.screen));
1634 jwxyz_XSetAntiAliasing (s->dpy, gc2, s->debug_metrics_antialiasing_p);
1637 if (xft_p && i == 0)
1639 XftDraw *xftdraw2 = XftDrawCreate (s->dpy, p, s->xgwa.visual,
1641 XftDrawStringUtf8 (xftdraw2, &xftcolor, xftfont,
1642 -cc.lbearing + margin,
1644 (FcChar8 *) utxt, strlen(utxt));
1645 XftDrawDestroy (xftdraw2);
1648 XDrawString16 (s->dpy, p, gc2,
1649 -cc.lbearing + margin,
1653 XDrawString (s->dpy, p, gc2,
1654 -cc.lbearing + margin,
1660 XImage *img = XGetImage (s->dpy, p, 0, 0, pixw, pixh,
1666 unsigned w = pixw - margin * 2, h = pixh - margin * 2;
1670 /* Check for ink escape. */
1671 unsigned long ink = 0;
1672 for (y2 = 0; y2 != pixh; ++y2)
1673 for (x2 = 0; x2 != pixw; ++x2)
1676 if (! (x2 >= margin &&
1677 x2 < pixw - margin &&
1679 y2 < pixh - margin))
1680 ink |= XGetPixel (img, x2, y2);
1685 XSetFont (s->dpy, gc, font2->fid);
1686 XDrawString (s->dpy, dest, gc,
1692 /* ...And wasted space. */
1695 if (check_edge (s->dpy, dest, gc, 120, 60, "left",
1696 img, margin, margin, 1, h) |
1697 check_edge (s->dpy, dest, gc, 160, 60, "right",
1698 img, margin + w - 1, margin, 1, h) |
1699 check_edge (s->dpy, dest, gc, 200, 60, "top",
1700 img, margin, margin, 0, w) |
1701 check_edge (s->dpy, dest, gc, 240, 60, "bottom",
1702 img, margin, margin + h - 1, 0, w))
1704 XSetFont (s->dpy, gc, font2->fid);
1705 XDrawString (s->dpy, dest, gc,
1707 "Wasted space: ", 14);
1712 if (s->debug_metrics_antialiasing_p)
1714 /* Draw a dark cyan boundary around antialiased glyphs */
1715 img2 = XCreateImage (s->dpy, s->xgwa.visual, img->depth,
1717 img->width, img->height,
1718 img->bitmap_pad, 0);
1719 img2->data = malloc (img->bytes_per_line * img->height);
1721 for (y2 = 0; y2 != pixh; ++y2)
1722 for (x2 = 0; x2 != pixw; ++x2)
1724 unsigned long px = XGetPixel (img, x2, y2);
1725 if ((px & 0xffffff) == 0)
1727 unsigned long neighbors = 0;
1729 neighbors |= XGetPixel (img, x2 - 1, y2);
1731 neighbors |= XGetPixel (img, x2 + 1, y2);
1733 neighbors |= XGetPixel (img, x2, y2 - 1);
1735 neighbors |= XGetPixel (img, x2, y2 + 1);
1736 XPutPixel (img2, x2, y2,
1737 (neighbors & 0xffffff
1739 : BlackPixelOfScreen (s->xgwa.screen)));
1743 XPutPixel (img2, x2, y2, px);
1753 p2 = scale_ximage (s->xgwa.screen, s->window, img2, sc, margin);
1755 XDestroyImage (img);
1756 XDestroyImage (img2);
1759 XCopyArea (s->dpy, p2, dest, gc,
1760 0, 0, sc*pixw, sc*pixh,
1761 xoff + x + sc * (cc.lbearing - margin),
1762 y - sc * (cc.ascent + margin));
1763 XFreePixmap (s->dpy, p);
1764 XFreePixmap (s->dpy, p2);
1765 XFreeGC (s->dpy, gc2);
1770 XSetFont (s->dpy, gc, font->fid);
1771 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1773 jwxyz_XSetAntiAliasing (s->dpy, gc, s->debug_metrics_antialiasing_p);
1775 sprintf (txt2, "%s [XX%sXX] [%s%s%s%s]",
1776 (xft_p ? utxt : txt),
1777 (xft_p ? utxt : txt),
1778 (xft_p ? utxt : txt),
1779 (xft_p ? utxt : txt),
1780 (xft_p ? utxt : txt),
1781 (xft_p ? utxt : txt));
1784 XftDrawStringUtf8 (xftdraw, &xftcolor, xftfont,
1785 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2,
1787 (FcChar8 *) txt2, strlen(txt2));
1789 XDrawString (s->dpy, dest, gc,
1790 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2,
1792 txt2, strlen(txt2));
1794 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1796 XSetFont (s->dpy, gc, font2->fid);
1797 if (xft_p && utxt[1])
1798 sprintf (txt2, "0%03o 0%03o %02x %02x",
1799 (unsigned char) utxt[0], (unsigned char) utxt[1],
1800 (unsigned char) utxt[0], (unsigned char) utxt[1]);
1804 sprintf (txt2, "%c %3d 0%03o 0x%02x%s",
1805 s->debug_metrics_p, s->debug_metrics_p,
1806 s->debug_metrics_p, s->debug_metrics_p,
1807 (txt[0] < font->min_char_or_byte2 ? " *" : ""));
1808 XDrawString (s->dpy, dest, gc,
1810 txt2, strlen(txt2));
1814 jwxyz_XSetAntiAliasing (s->dpy, gc, True);
1818 const char *ss = (j == 0
1819 ? (i == 0 ? "char" : "overall")
1820 : (i == 0 ? "utf8" : "16 bit"));
1821 XSetFont (s->dpy, gc, font2->fid);
1823 XSetForeground (s->dpy, gc, red);
1825 sprintf (txt2, "%s ascent %d", ss, ascent);
1826 XDrawString (s->dpy, dest, gc,
1829 txt2, strlen(txt2));
1830 XDrawLine (s->dpy, dest, gc,
1831 xoff, y - sc*ascent,
1834 sprintf (txt2, "%s descent %d", ss, descent);
1835 XDrawString (s->dpy, dest, gc,
1838 txt2, strlen(txt2));
1839 XDrawLine (s->dpy, dest, gc,
1840 xoff, y + sc*descent,
1841 x3, y + sc*descent);
1845 /* ascent, descent, baseline */
1847 XSetForeground (s->dpy, gc, green);
1849 sprintf (txt2, "ascent %d", cc.ascent);
1851 XDrawString (s->dpy, dest, gc,
1852 x1, y - sc*cc.ascent - 2,
1853 txt2, strlen(txt2));
1854 XDrawLine (s->dpy, dest, gc,
1855 x1, y - sc*cc.ascent,
1856 x2, y - sc*cc.ascent);
1858 sprintf (txt2, "descent %d", cc.descent);
1859 if (cc.descent != 0)
1860 XDrawString (s->dpy, dest, gc,
1861 x1, y + sc*cc.descent - 2,
1862 txt2, strlen(txt2));
1863 XDrawLine (s->dpy, dest, gc,
1864 x1, y + sc*cc.descent,
1865 x2, y + sc*cc.descent);
1867 XSetForeground (s->dpy, gc, yellow);
1868 strcpy (txt2, "baseline");
1869 XDrawString (s->dpy, dest, gc,
1871 txt2, strlen(txt2));
1872 XDrawLine (s->dpy, dest, gc, x1, y, x2, y);
1877 XSetForeground (s->dpy, gc, blue);
1879 strcpy (txt2, "origin");
1880 XDrawString (s->dpy, dest, gc,
1882 y + sc*(descent + 10),
1883 txt2, strlen(txt2));
1884 XDrawLine (s->dpy, dest, gc,
1885 xoff + x, y - sc*(ascent - 10),
1886 xoff + x, y + sc*(descent + 10));
1888 sprintf (txt2, "width %d", cc.width);
1889 XDrawString (s->dpy, dest, gc,
1890 xoff + x + sc*cc.width + 2,
1891 y + sc*(descent + 10) + 10,
1892 txt2, strlen(txt2));
1893 XDrawLine (s->dpy, dest, gc,
1894 xoff + x + sc*cc.width, y - sc*(ascent - 10),
1895 xoff + x + sc*cc.width, y + sc*(descent + 10));
1898 /* lbearing, rbearing */
1900 XSetForeground (s->dpy, gc, green);
1902 sprintf (txt2, "lbearing %d", cc.lbearing);
1903 XDrawString (s->dpy, dest, gc,
1904 xoff + x + sc*cc.lbearing + 2,
1905 y + sc * descent + 30,
1906 txt2, strlen(txt2));
1907 XDrawLine (s->dpy, dest, gc,
1908 xoff + x + sc*cc.lbearing, y - sc*ascent,
1909 xoff + x + sc*cc.lbearing, y + sc*descent + 20);
1911 sprintf (txt2, "rbearing %d", cc.rbearing);
1912 XDrawString (s->dpy, dest, gc,
1913 xoff + x + sc*cc.rbearing + 2,
1914 y + sc * descent + 40,
1915 txt2, strlen(txt2));
1916 XDrawLine (s->dpy, dest, gc,
1917 xoff + x + sc*cc.rbearing, y - sc*ascent,
1918 xoff + x + sc*cc.rbearing, y + sc*descent + 40);
1920 y += sc * (ascent + descent) * 2;
1924 if (dest != s->window)
1925 XCopyArea (s->dpy, dest, s->window, s->bg_gc,
1926 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1928 XFreeGC (s->dpy, gc);
1929 XFreeFont (s->dpy, font);
1931 XFreeFont (s->dpy, font2);
1933 XftFontClose (s->dpy, xftfont);
1934 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap, &xftcolor);
1935 XftDrawDestroy (xftdraw);
1938 return s->frame_delay;
1944 /* Render all the words to the screen, and run the animation one step.
1945 Clear screen first, swap buffers after.
1947 static unsigned long
1948 fontglide_draw (Display *dpy, Window window, void *closure)
1950 state *s = (state *) closure;
1954 if (s->debug_metrics_p)
1955 return fontglide_draw_metrics (closure);
1962 XFillRectangle (s->dpy, s->b, s->bg_gc,
1963 0, 0, s->xgwa.width, s->xgwa.height);
1965 for (i = 0; i < s->nsentences; i++)
1966 draw_sentence (s, s->sentences[i]);
1968 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1971 XdbeSwapInfo info[1];
1972 info[0].swap_window = s->window;
1973 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1974 XdbeSwapBuffers (s->dpy, info, 1);
1977 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1980 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1981 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1984 return s->frame_delay;
1989 /* When the subprocess has generated some output, this reads as much as it
1990 can into s->buf at s->buf_tail.
1993 drain_input (state *s)
1995 while (s->buf_tail < sizeof(s->buf) - 2)
1997 int c = textclient_getc (s->tc);
1999 s->buf[s->buf_tail++] = (char) c;
2006 /* Window setup and resource loading */
2009 fontglide_init (Display *dpy, Window window)
2012 state *s = (state *) calloc (1, sizeof(*s));
2015 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
2017 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2019 s->font_override = get_string_resource (dpy, "font", "Font");
2020 if (s->font_override && (!*s->font_override || *s->font_override == '('))
2021 s->font_override = 0;
2023 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
2024 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
2025 if (s->border_width < 0 || s->border_width > 20)
2026 s->border_width = 1;
2028 s->speed = get_float_resource (dpy, "speed", "Float");
2029 if (s->speed <= 0 || s->speed > 200)
2032 s->linger = get_float_resource (dpy, "linger", "Float");
2033 if (s->linger <= 0 || s->linger > 200)
2036 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
2039 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
2040 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
2045 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
2047 # ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
2052 if (s->debug_metrics_p) s->trails_p = False;
2055 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
2058 const char *ss = get_string_resource (dpy, "mode", "Mode");
2059 if (!ss || !*ss || !strcasecmp (ss, "random"))
2060 s->mode = ((random() % 2) ? SCROLL : PAGE);
2061 else if (!strcasecmp (ss, "scroll"))
2063 else if (!strcasecmp (ss, "page"))
2068 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
2075 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2076 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
2078 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
2080 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
2082 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2086 s->ba = XCreatePixmap (s->dpy, s->window,
2087 s->xgwa.width, s->xgwa.height,
2097 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
2098 "background", "Background");
2099 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
2101 s->nsentences = 5; /* #### */
2102 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
2106 s->start_time = time ((time_t *) 0);
2107 s->tc = textclient_open (dpy);
2114 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
2117 state *s = (state *) closure;
2119 if (! s->debug_metrics_p)
2121 if (event->xany.type == KeyPress)
2125 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2127 s->debug_metrics_antialiasing_p ^= True;
2128 else if (c == 3 || c == 27)
2131 s->debug_metrics_p = (unsigned char) c;
2132 else if (keysym == XK_Left || keysym == XK_Right)
2134 s->debug_metrics_p += (keysym == XK_Left ? -1 : 1);
2135 if (s->debug_metrics_p > 255)
2136 s->debug_metrics_p = 1;
2137 else if (s->debug_metrics_p <= 0)
2138 s->debug_metrics_p = 255;
2141 else if (keysym == XK_Up)
2143 else if (keysym == XK_Down)
2144 s->debug_scale = (s->debug_scale > 1 ? s->debug_scale-1 : 1);
2156 fontglide_reshape (Display *dpy, Window window, void *closure,
2157 unsigned int w, unsigned int h)
2159 state *s = (state *) closure;
2160 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2162 if (s->dbuf && s->ba)
2164 XFreePixmap (s->dpy, s->ba);
2165 s->ba = XCreatePixmap (s->dpy, s->window,
2166 s->xgwa.width, s->xgwa.height,
2168 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
2169 s->xgwa.width, s->xgwa.height);
2175 fontglide_free (Display *dpy, Window window, void *closure)
2177 state *s = (state *) closure;
2178 textclient_close (s->tc);
2180 /* #### there's more to free here */
2186 static const char *fontglide_defaults [] = {
2187 ".background: #000000",
2188 ".foreground: #DDDDDD",
2189 ".borderColor: #555555",
2191 "*program: xscreensaver-text",
2196 /* I'm not entirely clear on whether the charset of an XLFD has any
2197 meaning when Xft is being used. */
2198 "*fontCharset: iso8859-1",
2199 /*"*fontCharset: iso10646-1", */
2200 /*"*fontCharset: *-*",*/
2202 "*fontBorderWidth: 2",
2208 "*debugMetrics: False",
2210 "*doubleBuffer: True",
2211 # ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2213 "*useDBEClear: True",
2214 # endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2218 static XrmOptionDescRec fontglide_options [] = {
2219 { "-mode", ".mode", XrmoptionSepArg, 0 },
2220 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
2221 { "-page", ".mode", XrmoptionNoArg, "page" },
2222 { "-random", ".mode", XrmoptionNoArg, "random" },
2223 { "-delay", ".delay", XrmoptionSepArg, 0 },
2224 { "-speed", ".speed", XrmoptionSepArg, 0 },
2225 { "-linger", ".linger", XrmoptionSepArg, 0 },
2226 { "-program", ".program", XrmoptionSepArg, 0 },
2227 { "-font", ".font", XrmoptionSepArg, 0 },
2228 { "-fn", ".font", XrmoptionSepArg, 0 },
2229 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
2230 { "-trails", ".trails", XrmoptionNoArg, "True" },
2231 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
2232 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
2233 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
2235 { "-debug", ".debug", XrmoptionNoArg, "True" },
2236 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
2242 XSCREENSAVER_MODULE ("FontGlide", fontglide)