1 /* xscreensaver, Copyright (c) 2003, 2005, 2006 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 */
23 # include <X11/Intrinsic.h>
30 #include "screenhack.h"
31 #include "textclient.h"
33 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
35 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
39 int x, y, width, height;
40 int ascent, lbearing, rbearing;
44 int target_x, target_y;
65 enum { IN, PAUSE, OUT } anim_state;
66 enum { LEFT, CENTER, RIGHT } alignment;
75 XWindowAttributes xgwa;
77 Pixmap b, ba; /* double-buffer to reduce flicker */
80 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
83 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
85 Bool dbuf; /* Whether we're using double buffering. */
87 int border_width; /* size of the font outline */
88 char *charset; /* registry and encoding for font lookups */
89 double speed; /* frame rate multiplier */
90 double linger; /* multiplier for how long to leave words on screen */
94 enum { PAGE, SCROLL } mode;
96 char *font_override; /* if -font was specified on the cmd line */
98 char buf [40]; /* this only needs to be as big as one "word". */
102 sentence **sentences;
103 Bool spawn_p; /* whether it is time to create a new sentence */
105 unsigned long frame_delay;
112 static void drain_input (state *s);
116 pick_font_size (state *s)
118 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
119 int min, max, r, pixel;
124 if (min < 10) min = 10;
125 if (max < 30) max = 30;
129 pixel = min + ((random() % r) + (random() % r) + (random() % r));
131 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
138 /* Finds the set of scalable fonts on the system; picks one;
139 and loads that font in a random pixel size.
140 Returns False if something went wrong.
143 pick_font_1 (state *s, sentence *se)
148 # ifndef HAVE_COCOA /* real Xlib */
151 XFontStruct *info = 0;
152 int count = 0, count2 = 0;
157 XFreeFont (s->dpy, se->font);
158 free (se->font_name);
163 if (s->font_override)
164 sprintf (pattern, "%.200s", s->font_override);
166 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
173 "0", /* pixel size */
174 "0", /* point size */
175 "0", /* resolution x */
176 "0", /* resolution y */
179 s->charset); /* registry + encoding */
181 names = XListFonts (s->dpy, pattern, 1000, &count);
185 if (s->font_override)
186 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
188 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
193 i = random() % count;
195 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
198 fprintf (stderr, "%s: pattern %s\n"
199 " gave unusable %s\n\n",
200 progname, pattern, names[i]);
205 XFontStruct *font = &info[0];
206 unsigned long value = 0;
207 char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
208 unsigned long pixel=0, point=0, res_x=0, res_y=0;
210 unsigned long avg_width=0;
211 char *registry=0, *encoding=0;
213 char *bogus = "\"?\"";
215 # define STR(ATOM,VAR) \
217 a = XInternAtom (s->dpy, (ATOM), False); \
218 if (XGetFontProperty (font, a, &value)) \
219 VAR = XGetAtomName (s->dpy, value); \
223 # define INT(ATOM,VAR) \
225 a = XInternAtom (s->dpy, (ATOM), False); \
226 if (!XGetFontProperty (font, a, &VAR) || \
230 STR ("FOUNDRY", foundry);
231 STR ("FAMILY_NAME", family);
232 STR ("WEIGHT_NAME", weight);
233 STR ("SLANT", slant);
234 STR ("SETWIDTH_NAME", setwidth);
235 STR ("ADD_STYLE_NAME", add_style);
236 INT ("PIXEL_SIZE", pixel);
237 INT ("POINT_SIZE", point);
238 INT ("RESOLUTION_X", res_x);
239 INT ("RESOLUTION_Y", res_y);
240 STR ("SPACING", spacing);
241 INT ("AVERAGE_WIDTH", avg_width);
242 STR ("CHARSET_REGISTRY", registry);
243 STR ("CHARSET_ENCODING", encoding);
248 pixel = pick_font_size (s);
251 /* Occasionally change the aspect ratio of the font, by increasing
252 either the X or Y resolution (while leaving the other alone.)
254 #### Looks like this trick doesn't really work that well: the
255 metrics of the individual characters are ok, but the
256 overall font ascent comes out wrong (unscaled.)
258 if (! (random() % 8))
261 double scale = 1 + (frand(n) + frand(n) + frand(n));
270 "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
271 foundry, family, weight, slant, setwidth, add_style,
272 pixel, "*", /* point, */
273 res_x, res_y, spacing,
280 fprintf (stderr, "%s: font has bogus %s property: %s\n",
281 progname, bogus, names[i]);
283 if (foundry) XFree (foundry);
284 if (family) XFree (family);
285 if (weight) XFree (weight);
286 if (slant) XFree (slant);
287 if (setwidth) XFree (setwidth);
288 if (add_style) XFree (add_style);
289 if (spacing) XFree (spacing);
290 if (registry) XFree (registry);
291 if (encoding) XFree (encoding);
296 XFreeFontInfo (names2, info, count2);
297 XFreeFontNames (names);
299 # else /* HAVE_COCOA */
301 if (s->font_override)
302 sprintf (pattern, "%.200s", s->font_override);
305 const char *family = "random";
306 const char *weight = ((random() % 2) ? "normal" : "bold");
307 const char *slant = ((random() % 2) ? "o" : "r");
308 int size = 10 * pick_font_size (s);
309 sprintf (pattern, "*-%s-%s-%s-*-%d-*", family, weight, slant, size);
312 # endif /* HAVE_COCOA */
314 if (! ok) return False;
316 se->font = XLoadQueryFont (s->dpy, pattern);
320 fprintf (stderr, "%s: unable to load font %s\n",
325 if (se->font->min_bounds.width == se->font->max_bounds.width &&
328 /* This is to weed out
329 "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
330 "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1".
331 We asked for only proportional fonts, but this fixed-width font
332 shows up anyway -- but it has goofy metrics (see below) so it
333 looks terrible anyway.
337 "%s: skipping bogus monospace non-charcell font: %s\n",
343 fprintf(stderr, "%s: %s\n", progname, pattern);
345 se->font_name = strdup (pattern);
346 XSetFont (s->dpy, se->fg_gc, se->font->fid);
351 /* Finds the set of scalable fonts on the system; picks one;
352 and loads that font in a random pixel size.
355 pick_font (state *s, sentence *se)
358 for (i = 0; i < 20; i++)
359 if (pick_font_1 (s, se))
361 fprintf (stderr, "%s: too many font-loading failures: giving up!\n", progname);
366 static char *unread_word_text = 0;
368 /* Returns a newly-allocated string with one word in it, or NULL if there
369 is no complete word available.
372 get_word_text (state *s)
374 char *start = s->buf;
381 if (unread_word_text)
383 start = unread_word_text;
384 unread_word_text = 0;
388 /* Skip over whitespace at the beginning of the buffer,
389 and count up how many linebreaks we see while doing so.
397 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
404 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
405 to trigger a sentence break here.) */
409 /* Skip forward to the end of this word (find next whitespace.) */
417 /* If we have a word, allocate a string for it */
420 result = malloc ((end - start) + 1);
421 strncpy (result, start, (end-start));
422 result [end-start] = 0;
427 /* Make room in the buffer by compressing out any bytes we've processed.
431 int n = end - s->buf;
432 memmove (s->buf, end, sizeof(s->buf) - n);
440 /* Gets some random text, and creates a "word" object from it.
443 new_word (state *s, sentence *se, char *txt, Bool alloc_p)
447 int dir, ascent, descent;
448 int bw = s->border_width;
453 w = (word *) calloc (1, sizeof(*w));
454 XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
456 /* Leave a little more slack */
457 overall.lbearing -= (bw * 2);
458 overall.rbearing += (bw * 2);
459 overall.ascent += (bw * 2);
460 overall.descent += (bw * 2);
462 w->width = overall.rbearing - overall.lbearing + bw + bw;
463 w->height = overall.ascent + overall.descent + bw + bw;
464 w->ascent = overall.ascent + bw;
465 w->lbearing = overall.lbearing - bw;
466 w->rbearing = overall.width + bw;
469 /* The metrics on some fonts are strange -- e.g.,
470 "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
471 "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1" both have
472 an rbearing so wide that it looks like there are two spaces after
473 each letter. If this character says it has an rbearing that is to
474 the right of its ink, ignore that.
476 #### Of course, this hack only helps when we're in `move_chars_p' mode
477 and drawing a char at a time -- when we draw the whole word at once,
478 XDrawString believes the bogus metrics and spaces the font out
481 Sigh, this causes some text to mis-render in, e.g.,
482 "-adobe-utopia-medium-i-normal--114-*-100-100-p-*-iso8859-1"
483 (in "ux", we need the rbearing on "r" or we get too much overlap.)
485 if (w->rbearing > w->width)
486 w->rbearing = w->width;
489 if (s->mode == SCROLL && !alloc_p) abort();
497 w->pixmap = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
498 w->mask = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
500 gcv.font = se->font->fid;
503 gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
507 gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
510 XFillRectangle (s->dpy, w->mask, gc0, 0, 0, w->width, w->height);
511 XFillRectangle (s->dpy, w->pixmap, gc0, 0, 0, w->width, w->height);
515 /* bounding box (behind the characters) */
516 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
517 0, 0, w->width-1, w->height-1);
518 XDrawRectangle (s->dpy, w->mask, gc1,
519 0, 0, w->width-1, w->height-1);
524 /* bounding box (behind *each* character) */
527 for (ss = txt; *ss; ss++)
529 XTextExtents (se->font, ss, 1, &dir, &ascent, &descent, &overall);
530 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
531 x, w->ascent - overall.ascent,
533 overall.ascent + overall.descent);
534 XDrawRectangle (s->dpy, w->mask, gc1,
535 x, w->ascent - overall.ascent,
537 overall.ascent + overall.descent);
539 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
540 x - overall.lbearing, w->ascent - overall.ascent,
542 overall.ascent + overall.descent);
543 XDrawRectangle (s->dpy, w->mask, gc1,
544 x - overall.lbearing, w->ascent - overall.ascent,
546 overall.ascent + overall.descent);
553 /* Draw foreground text */
554 XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
557 /* Cheesy hack to draw a border */
558 /* (I should be able to do this in i*2 time instead of i*i time,
559 but I can't get it right, so fuck it.) */
560 XSetFunction (s->dpy, gc1, GXor);
561 for (i = -bw; i <= bw; i++)
562 for (j = -bw; j <= bw; j++)
563 XCopyArea (s->dpy, w->pixmap, w->mask, gc1,
564 0, 0, w->width, w->height,
569 XSetFunction (s->dpy, gc1, GXcopy);
570 if (w->ascent != w->height)
572 /* baseline (on top of the characters) */
573 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
574 0, w->ascent, w->width-1, w->ascent);
575 XDrawLine (s->dpy, w->mask, gc1,
576 0, w->ascent, w->width-1, w->ascent);
579 if (w->lbearing != 0)
581 /* left edge of charcell */
582 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
583 w->lbearing, 0, w->lbearing, w->height-1);
584 XDrawLine (s->dpy, w->mask, gc1,
585 w->lbearing, 0, w->lbearing, w->height-1);
588 if (w->rbearing != w->width)
590 /* right edge of charcell */
591 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
592 w->rbearing, 0, w->rbearing, w->height-1);
593 XDrawLine (s->dpy, w->mask, gc1,
594 w->rbearing, 0, w->rbearing, w->height-1);
598 XFreeGC (s->dpy, gc0);
599 XFreeGC (s->dpy, gc1);
608 free_word (state *s, word *w)
610 if (w->text) free (w->text);
611 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
612 if (w->mask) XFreePixmap (s->dpy, w->mask);
617 new_sentence (state *st, state *s)
620 sentence *se = (sentence *) calloc (1, sizeof (*se));
621 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
623 se->id = ++st->id_tick;
629 free_sentence (state *s, sentence *se)
632 for (i = 0; i < se->nwords; i++)
633 free_word (s, se->words[i]);
634 if (se->words) free (se->words);
637 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
639 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
641 if (se->font_name) free (se->font_name);
642 if (se->font) XFreeFont (s->dpy, se->font);
643 if (se->fg_gc) XFreeGC (s->dpy, se->fg_gc);
649 /* free the word, and put its text back at the front of the input queue,
650 to be read next time. */
652 unread_word (state *s, word *w)
654 if (unread_word_text)
656 unread_word_text = w->text;
662 /* Divide each of the words in the sentence into one character words,
663 without changing the positions of those characters.
666 split_words (state *s, sentence *se)
671 for (i = 0; i < se->nwords; i++)
672 nwords2 += strlen (se->words[i]->text);
674 words2 = (word **) calloc (nwords2, sizeof(*words2));
676 for (i = 0, j = 0; i < se->nwords; i++)
678 word *ow = se->words[i];
679 int L = strlen (ow->text);
684 int sx = ow->start_x;
685 int sy = ow->start_y;
686 int tx = ow->target_x;
687 int ty = ow->target_y;
689 for (k = 0; k < L; k++)
691 char *t2 = malloc (2);
697 w2 = new_word (s, se, t2, True);
700 xoff = (w2->lbearing - ow->lbearing);
701 yoff = (ow->ascent - w2->ascent);
705 w2->start_x = sx + xoff;
706 w2->start_y = sy + yoff;
707 w2->target_x = tx + xoff;
708 w2->target_y = ty + yoff;
721 se->nwords = nwords2;
725 /* Set the source or destination position of the words to be somewhere
729 scatter_sentence (state *s, sentence *se)
734 int flock_p = ((random() % 4) == 0);
735 int mode = (flock_p ? (random() % 12) : 0);
737 for (i = 0; i < se->nwords; i++)
739 word *w = se->words[i];
741 int r = (flock_p ? mode : (random() % 4));
744 /* random positions on the edges */
748 y = random() % s->xgwa.height;
751 x = off + s->xgwa.width;
752 y = random() % s->xgwa.height;
755 x = random() % s->xgwa.width;
756 y = -off - w->height;
759 x = random() % s->xgwa.width;
760 y = off + s->xgwa.height;
763 /* straight towards the edges */
770 x = off + s->xgwa.width;
775 y = -off - w->height;
779 y = off + s->xgwa.height;
786 y = -off - w->height;
790 y = off + s->xgwa.height;
793 x = off + s->xgwa.width;
794 y = off + s->xgwa.height;
797 x = off + s->xgwa.width;
798 y = -off - w->height;
806 if (se->anim_state == IN)
819 w->nticks = ((100 + ((random() % 140) +
830 /* Set the source position of the words to be off the right side,
831 and the destination to be off the left side.
834 aim_sentence (state *s, sentence *se)
840 if (se->nwords <= 0) abort();
842 /* Have the sentence shift up or down a little bit; not too far, and
843 never let it fall off the top or bottom of the screen before its
844 last character has reached the left edge.
846 for (i = 0; i < 10; i++)
848 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
849 yoff = ty - se->words[0]->target_y;
850 if (yoff < s->xgwa.height/3) /* this one is ok */
854 for (i = 0; i < se->nwords; i++)
856 word *w = se->words[i];
857 w->start_x = w->target_x + s->xgwa.width;
858 w->target_x -= se->width;
859 w->start_y = w->target_y;
863 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
865 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
870 for (i = 0; i < se->nwords; i++)
872 word *w = se->words[i];
879 /* Randomize the order of the words in the list (since that changes
880 which ones are "on top".)
883 shuffle_words (state *s, sentence *se)
886 for (i = 0; i < se->nwords-1; i++)
888 int j = i + (random() % (se->nwords - i));
889 word *swap = se->words[i];
890 se->words[i] = se->words[j];
896 /* qsort comparitor */
898 cmp_sentences (const void *aa, const void *bb)
900 const sentence *a = *(sentence **) aa;
901 const sentence *b = *(sentence **) bb;
902 return ((a ? a->id : 999999) - (b ? b->id : 999999));
906 /* Sort the sentences by id, so that sentences added later are on top.
909 sort_sentences (state *s)
911 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
915 /* Re-pick the colors of the text and border
918 recolor (state *s, sentence *se)
921 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
923 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
925 se->fg.flags = DoRed|DoGreen|DoBlue;
926 se->bg.flags = DoRed|DoGreen|DoBlue;
928 switch (random() % 2)
930 case 0: /* bright fg, dim bg */
931 se->fg.red = (random() % 0x8888) + 0x8888;
932 se->fg.green = (random() % 0x8888) + 0x8888;
933 se->fg.blue = (random() % 0x8888) + 0x8888;
934 se->bg.red = (random() % 0x5555);
935 se->bg.green = (random() % 0x5555);
936 se->bg.blue = (random() % 0x5555);
939 case 1: /* bright bg, dim fg */
940 se->fg.red = (random() % 0x4444);
941 se->fg.green = (random() % 0x4444);
942 se->fg.blue = (random() % 0x4444);
943 se->bg.red = (random() % 0x4444) + 0xCCCC;
944 se->bg.green = (random() % 0x4444) + 0xCCCC;
945 se->bg.blue = (random() % 0x4444) + 0xCCCC;
954 se->dark_p = (se->fg.red*2 + se->fg.green*3 + se->fg.blue <
955 se->bg.red*2 + se->bg.green*3 + se->bg.blue);
957 if (XAllocColor (s->dpy, s->xgwa.colormap, &se->fg))
958 XSetForeground (s->dpy, se->fg_gc, se->fg.pixel);
959 if (XAllocColor (s->dpy, s->xgwa.colormap, &se->bg))
960 XSetBackground (s->dpy, se->fg_gc, se->bg.pixel);
965 align_line (state *s, sentence *se, int line_start, int x, int right)
968 switch (se->alignment)
970 case LEFT: off = 0; break;
971 case CENTER: off = (right - x) / 2; break;
972 case RIGHT: off = (right - x); break;
973 default: abort(); break;
977 for (j = line_start; j < se->nwords; j++)
978 se->words[j]->target_x += off;
982 /* Fill the sentence with new words: in "page" mode, fills the page
983 with text; in "scroll" mode, just makes one long horizontal sentence.
984 The sentence might have *no* words in it, if no text is currently
988 populate_sentence (state *s, sentence *se)
991 int left, right, top, x, y;
996 int array_size = 100;
998 se->move_chars_p = (s->mode == SCROLL ? False :
999 (random() % 3) ? False : True);
1000 se->alignment = (random() % 3);
1006 for (i = 0; i < se->nwords; i++)
1007 free_word (s, se->words[i]);
1011 se->words = (word **) calloc (array_size, sizeof(*se->words));
1017 left = random() % (s->xgwa.width / 3);
1018 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1019 top = random() % (s->xgwa.height * 2 / 3);
1023 right = s->xgwa.width;
1024 top = random() % s->xgwa.height;
1036 char *txt = get_word_text (s);
1040 if (se->nwords == 0)
1041 return; /* If the stream is empty, bail. */
1043 break; /* If EOF after some words, end of sentence. */
1046 if (! se->font) /* Got a word: need a font now */
1049 if (y < se->font->ascent)
1050 y += se->font->ascent;
1051 space = XTextWidth (se->font, " ", 1);
1054 w = new_word (s, se, txt, !se->move_chars_p);
1056 /* If we have a few words, let punctuation terminate the sentence:
1057 stop gathering more words if the last word ends in a period, etc. */
1058 if (se->nwords >= 4)
1060 char c = w->text[strlen(w->text)-1];
1061 if (c == '.' || c == '?' || c == '!')
1065 /* If the sentence is kind of long already, terminate at commas, etc. */
1066 if (se->nwords >= 12)
1068 char c = w->text[strlen(w->text)-1];
1069 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1070 c == ')' || c == ']' || c == '}')
1074 if (se->nwords >= 25) /* ok that's just about enough out of you */
1077 if (s->mode == PAGE &&
1078 x + w->rbearing > right) /* wrap line */
1080 align_line (s, se, line_start, x, right);
1081 line_start = se->nwords;
1084 y += se->font->ascent;
1086 /* If we're close to the bottom of the screen, stop, and
1087 unread the current word. (But not if this is the first
1088 word, otherwise we might just get stuck on it.)
1090 if (se->nwords > 0 &&
1091 y + se->font->ascent > s->xgwa.height)
1099 w->target_x = x + w->lbearing;
1100 w->target_y = y - w->ascent;
1102 x += w->rbearing + space;
1105 if (se->nwords >= (array_size - 1))
1108 se->words = (word **) realloc (se->words,
1109 array_size * sizeof(*se->words));
1112 fprintf (stderr, "%s: out of memory (%d words)\n",
1113 progname, array_size);
1118 se->words[se->nwords++] = w;
1126 align_line (s, se, line_start, x, right);
1127 if (se->move_chars_p)
1128 split_words (s, se);
1129 scatter_sentence (s, se);
1130 shuffle_words (s, se);
1133 aim_sentence (s, se);
1143 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1144 for (i = 0; i < se->nwords; i++)
1145 fprintf (stderr, " %s", se->words[i]->text);
1146 fprintf (stderr, "\n");
1152 /* Render a single word object to the screen.
1155 draw_word (state *s, sentence *se, word *w)
1157 if (! w->pixmap) return;
1159 if (w->x + w->width < 0 ||
1160 w->y + w->height < 0 ||
1161 w->x > s->xgwa.width ||
1162 w->y > s->xgwa.height)
1165 XSetClipMask (s->dpy, se->fg_gc, w->mask);
1166 XSetClipOrigin (s->dpy, se->fg_gc, w->x, w->y);
1167 XCopyPlane (s->dpy, w->pixmap, s->b, se->fg_gc,
1168 0, 0, w->width, w->height,
1174 /* If there is room for more sentences, add one.
1177 more_sentences (state *s)
1181 for (i = 0; i < s->nsentences; i++)
1183 sentence *se = s->sentences[i];
1186 se = new_sentence (s, s);
1187 populate_sentence (s, se);
1189 s->spawn_p = False, any = True;
1192 free_sentence (s, se);
1195 s->sentences[i] = se;
1197 s->latest_sentence = se->id;
1202 if (any) sort_sentences (s);
1206 /* Render all the words to the screen, and run the animation one step.
1209 draw_sentence (state *s, sentence *se)
1216 for (i = 0; i < se->nwords; i++)
1218 word *w = se->words[i];
1223 if (se->anim_state != PAUSE &&
1224 w->tick <= w->nticks)
1226 int dx = w->target_x - w->start_x;
1227 int dy = w->target_y - w->start_y;
1228 double r = sin (w->tick * M_PI / (2 * w->nticks));
1229 w->x = w->start_x + (dx * r);
1230 w->y = w->start_y + (dy * r);
1233 if (se->anim_state == OUT && s->mode == PAGE)
1234 w->tick++; /* go out faster */
1240 int dx = w->target_x - w->start_x;
1241 int dy = w->target_y - w->start_y;
1242 double r = (double) w->tick / w->nticks;
1243 w->x = w->start_x + (dx * r);
1244 w->y = w->start_y + (dy * r);
1246 moved = (w->tick <= w->nticks);
1248 /* Launch a new sentence when:
1249 - the front of this sentence is almost off the left edge;
1250 - the end of this sentence is almost on screen.
1253 if (se->anim_state != OUT &&
1255 se->id == s->latest_sentence)
1257 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1258 w->x + se->width < (s->xgwa.width * 2.1));
1259 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1261 if (new_p || rand_p)
1263 se->anim_state = OUT;
1267 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1269 se->words[0]->x + se->width,
1270 rand_p ? " randomly" : "");
1281 draw_word (s, se, w);
1284 if (moved && se->anim_state == PAUSE)
1289 switch (se->anim_state)
1292 se->anim_state = PAUSE;
1293 se->pause_tick = (se->nwords * 7 * s->linger);
1294 if (se->move_chars_p)
1295 se->pause_tick /= 5;
1296 scatter_sentence (s, se);
1297 shuffle_words (s, se);
1300 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1304 if (--se->pause_tick <= 0)
1306 se->anim_state = OUT;
1310 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1317 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1321 for (j = 0; j < s->nsentences; j++)
1322 if (s->sentences[j] == se)
1323 s->sentences[j] = 0;
1324 free_sentence (s, se);
1335 static unsigned long
1336 fontglide_draw_metrics (state *s)
1339 char *fn = (s->font_override ? s->font_override : "fixed");
1340 XFontStruct *font = XLoadQueryFont (s->dpy, fn);
1341 XCharStruct c, overall;
1342 int dir, ascent, descent;
1345 unsigned long red = 0xFFFF0000; /* so shoot me */
1346 unsigned long green = 0xFF00FF00;
1347 unsigned long blue = 0xFF6666FF;
1350 txt[0] = s->debug_metrics_p;
1353 gc = XCreateGC (s->dpy, s->window, 0, 0);
1354 XSetFont (s->dpy, gc, font->fid);
1357 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1360 XTextExtents (font, txt, strlen(txt),
1361 &dir, &ascent, &descent, &overall);
1362 c = font->per_char[((unsigned char *) txt)[0] - font->min_char_or_byte2];
1364 XClearWindow (s->dpy, s->window);
1366 x = (s->xgwa.width - overall.width) / 2;
1367 y = (s->xgwa.height - (2 * (ascent + descent))) / 2;
1369 for (i = 0; i < 2; i++)
1371 XCharStruct cc = (i == 0 ? c : overall);
1373 int x2 = s->xgwa.width - 40;
1374 int x3 = s->xgwa.width;
1376 XSetForeground (s->dpy, gc, red);
1377 XDrawLine (s->dpy, s->window, gc, 0, y - ascent, x3, y - ascent);
1378 XDrawLine (s->dpy, s->window, gc, 0, y + descent, x3, y + descent);
1380 XSetForeground (s->dpy, gc, green);
1381 /* ascent, baseline, descent */
1382 XDrawLine (s->dpy, s->window, gc, x1, y - cc.ascent, x2, y - cc.ascent);
1383 XDrawLine (s->dpy, s->window, gc, x1, y, x2, y);
1384 XDrawLine (s->dpy, s->window, gc, x1, y + cc.descent, x2, y + cc.descent);
1387 XSetForeground (s->dpy, gc, blue);
1388 XDrawLine (s->dpy, s->window, gc,
1390 x, y + descent + 10);
1391 XDrawLine (s->dpy, s->window, gc,
1392 x + cc.width, y - ascent - 10,
1393 x + cc.width, y + descent + 10);
1395 /* lbearing, rbearing */
1396 XSetForeground (s->dpy, gc, green);
1397 XDrawLine (s->dpy, s->window, gc,
1398 x + cc.lbearing, y - ascent,
1399 x + cc.lbearing, y + descent);
1400 XDrawLine (s->dpy, s->window, gc,
1401 x + cc.rbearing, y - ascent,
1402 x + cc.rbearing, y + descent);
1404 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1405 XDrawString (s->dpy, s->window, gc, x, y, txt, strlen(txt));
1407 y += (ascent + descent) * 2;
1410 XFreeGC (s->dpy, gc);
1411 XFreeFont (s->dpy, font);
1412 return s->frame_delay;
1416 /* Render all the words to the screen, and run the animation one step.
1417 Clear screen first, swap buffers after.
1419 static unsigned long
1420 fontglide_draw (Display *dpy, Window window, void *closure)
1422 state *s = (state *) closure;
1425 if (s->debug_metrics_p)
1426 return fontglide_draw_metrics (closure);
1432 XFillRectangle (s->dpy, s->b, s->bg_gc,
1433 0, 0, s->xgwa.width, s->xgwa.height);
1435 for (i = 0; i < s->nsentences; i++)
1436 draw_sentence (s, s->sentences[i]);
1438 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1441 XdbeSwapInfo info[1];
1442 info[0].swap_window = s->window;
1443 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1444 XdbeSwapBuffers (s->dpy, info, 1);
1447 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1450 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1451 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1454 return s->frame_delay;
1459 /* When the subprocess has generated some output, this reads as much as it
1460 can into s->buf at s->buf_tail.
1463 drain_input (state *s)
1465 while (s->buf_tail < sizeof(s->buf) - 2)
1467 char c = textclient_getc (s->tc);
1469 s->buf[s->buf_tail++] = c;
1476 /* Window setup and resource loading */
1479 fontglide_init (Display *dpy, Window window)
1482 state *s = (state *) calloc (1, sizeof(*s));
1485 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
1487 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1489 s->font_override = get_string_resource (dpy, "font", "Font");
1490 if (s->font_override && (!*s->font_override || *s->font_override == '('))
1491 s->font_override = 0;
1493 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
1494 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
1495 if (s->border_width < 0 || s->border_width > 20)
1496 s->border_width = 1;
1498 s->speed = get_float_resource (dpy, "speed", "Float");
1499 if (s->speed <= 0 || s->speed > 200)
1502 s->linger = get_float_resource (dpy, "linger", "Float");
1503 if (s->linger <= 0 || s->linger > 200)
1506 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
1507 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
1508 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
1511 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
1513 # ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
1517 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
1520 char *ss = get_string_resource (dpy, "mode", "Mode");
1521 if (!ss || !*ss || !strcasecmp (ss, "random"))
1522 s->mode = ((random() % 2) ? SCROLL : PAGE);
1523 else if (!strcasecmp (ss, "scroll"))
1525 else if (!strcasecmp (ss, "page"))
1530 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1537 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1538 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
1540 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1542 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1544 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1548 s->ba = XCreatePixmap (s->dpy, s->window,
1549 s->xgwa.width, s->xgwa.height,
1559 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
1560 "background", "Background");
1561 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
1563 s->nsentences = 5; /* #### */
1564 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1567 s->tc = textclient_open (dpy);
1574 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
1576 state *s = (state *) closure;
1578 if (! s->debug_metrics_p)
1580 else if (event->xany.type == ButtonPress)
1582 s->debug_metrics_p++;
1583 if (s->debug_metrics_p > 255)
1584 s->debug_metrics_p = ' ';
1585 else if (s->debug_metrics_p > 127 &&
1586 s->debug_metrics_p < 159)
1587 s->debug_metrics_p = 160;
1590 else if (event->xany.type == KeyPress)
1594 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1596 s->debug_metrics_p = (unsigned char) c;
1605 fontglide_reshape (Display *dpy, Window window, void *closure,
1606 unsigned int w, unsigned int h)
1608 state *s = (state *) closure;
1609 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1611 if (s->dbuf && (s->ba))
1613 XFreePixmap (s->dpy, s->ba);
1614 s->ba = XCreatePixmap (s->dpy, s->window,
1615 s->xgwa.width, s->xgwa.height,
1617 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
1618 s->xgwa.width, s->xgwa.height);
1624 fontglide_free (Display *dpy, Window window, void *closure)
1626 state *s = (state *) closure;
1627 textclient_close (s->tc);
1629 /* #### there's more to free here */
1635 static const char *fontglide_defaults [] = {
1636 ".background: #000000",
1637 ".foreground: #DDDDDD",
1638 ".borderColor: #555555",
1640 "*program: xscreensaver-text",
1644 "*fontCharset: iso8859-1",
1645 "*fontBorderWidth: 2",
1650 "*debugMetrics: False",
1651 "*doubleBuffer: True",
1652 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1654 "*useDBEClear: True",
1655 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1659 static XrmOptionDescRec fontglide_options [] = {
1660 { "-mode", ".mode", XrmoptionSepArg, 0 },
1661 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
1662 { "-page", ".mode", XrmoptionNoArg, "page" },
1663 { "-random", ".mode", XrmoptionNoArg, "random" },
1664 { "-delay", ".delay", XrmoptionSepArg, 0 },
1665 { "-speed", ".speed", XrmoptionSepArg, 0 },
1666 { "-linger", ".linger", XrmoptionSepArg, 0 },
1667 { "-program", ".program", XrmoptionSepArg, 0 },
1668 { "-font", ".font", XrmoptionSepArg, 0 },
1669 { "-fn", ".font", XrmoptionSepArg, 0 },
1670 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
1671 { "-trails", ".trails", XrmoptionNoArg, "True" },
1672 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
1673 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
1674 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
1675 { "-debug", ".debug", XrmoptionNoArg, "True" },
1676 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
1681 XSCREENSAVER_MODULE ("FontGlide", fontglide)