1 /* xscreensaver, Copyright (c) 2003-2013 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;
454 w = (word *) calloc (1, sizeof(*w));
455 XTextExtents (se->font, txt, strlen(txt), &dir, &ascent, &descent, &overall);
457 /* Leave a little more slack. Not entirely clear on what's going on here,
458 but maybe it's fonts with goofy metrics. */
459 slack = (overall.ascent + overall.descent) * 0.25;
460 if (slack < bw*2) slack = bw*2;
461 overall.lbearing -= slack;
462 overall.rbearing += slack;
463 overall.ascent += slack;
464 overall.descent += slack;
466 w->width = overall.rbearing - overall.lbearing;
467 w->height = overall.ascent + overall.descent;
468 w->ascent = overall.ascent + bw;
469 w->lbearing = overall.lbearing - bw;
470 w->rbearing = overall.width + bw;
473 /* The metrics on some fonts are strange -- e.g.,
474 "-urw-nimbus mono l-medium-o-normal--*-*-*-*-p-*-iso8859-1" and
475 "-urw-courier-medium-r-normal--*-*-*-*-p-*-iso8859-1" both have
476 an rbearing so wide that it looks like there are two spaces after
477 each letter. If this character says it has an rbearing that is to
478 the right of its ink, ignore that.
480 #### Of course, this hack only helps when we're in `move_chars_p' mode
481 and drawing a char at a time -- when we draw the whole word at once,
482 XDrawString believes the bogus metrics and spaces the font out
485 Sigh, this causes some text to mis-render in, e.g.,
486 "-adobe-utopia-medium-i-normal--114-*-100-100-p-*-iso8859-1"
487 (in "ux", we need the rbearing on "r" or we get too much overlap.)
489 if (w->rbearing > w->width)
490 w->rbearing = w->width;
493 if (s->mode == SCROLL && !alloc_p) abort();
501 if (w->width <= 0) w->width = 1;
502 if (w->height <= 0) w->height = 1;
504 w->pixmap = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
505 w->mask = XCreatePixmap (s->dpy, s->b, w->width, w->height, 1L);
507 gcv.font = se->font->fid;
510 gc0 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
514 gc1 = XCreateGC (s->dpy, w->pixmap, GCFont|GCForeground|GCBackground,
517 XFillRectangle (s->dpy, w->mask, gc0, 0, 0, w->width, w->height);
518 XFillRectangle (s->dpy, w->pixmap, gc0, 0, 0, w->width, w->height);
522 /* bounding box (behind the characters) */
523 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
524 0, 0, w->width-1, w->height-1);
525 XDrawRectangle (s->dpy, w->mask, gc1,
526 0, 0, w->width-1, w->height-1);
531 /* bounding box (behind *each* character) */
534 for (ss = txt; *ss; ss++)
536 XTextExtents (se->font, ss, 1, &dir, &ascent, &descent, &overall);
537 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
538 x, w->ascent - overall.ascent,
540 overall.ascent + overall.descent);
541 XDrawRectangle (s->dpy, w->mask, gc1,
542 x, w->ascent - overall.ascent,
544 overall.ascent + overall.descent);
546 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
547 x - overall.lbearing, w->ascent - overall.ascent,
549 overall.ascent + overall.descent);
550 XDrawRectangle (s->dpy, w->mask, gc1,
551 x - overall.lbearing, w->ascent - overall.ascent,
553 overall.ascent + overall.descent);
560 /* Draw foreground text */
561 XDrawString (s->dpy, w->pixmap, gc1, -w->lbearing, w->ascent,
564 /* Cheesy hack to draw a border */
565 /* (I should be able to do this in i*2 time instead of i*i time,
566 but I can't get it right, so fuck it.) */
567 XSetFunction (s->dpy, gc1, GXor);
568 for (i = -bw; i <= bw; i++)
569 for (j = -bw; j <= bw; j++)
570 XCopyArea (s->dpy, w->pixmap, w->mask, gc1,
571 0, 0, w->width, w->height,
576 XSetFunction (s->dpy, gc1, GXcopy);
577 if (w->ascent != w->height)
579 /* baseline (on top of the characters) */
580 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
581 0, w->ascent, w->width-1, w->ascent);
582 XDrawLine (s->dpy, w->mask, gc1,
583 0, w->ascent, w->width-1, w->ascent);
586 if (w->lbearing != 0)
588 /* left edge of charcell */
589 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
590 w->lbearing, 0, w->lbearing, w->height-1);
591 XDrawLine (s->dpy, w->mask, gc1,
592 w->lbearing, 0, w->lbearing, w->height-1);
595 if (w->rbearing != w->width)
597 /* right edge of charcell */
598 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc0 : gc1),
599 w->rbearing, 0, w->rbearing, w->height-1);
600 XDrawLine (s->dpy, w->mask, gc1,
601 w->rbearing, 0, w->rbearing, w->height-1);
605 XFreeGC (s->dpy, gc0);
606 XFreeGC (s->dpy, gc1);
615 free_word (state *s, word *w)
617 if (w->text) free (w->text);
618 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
619 if (w->mask) XFreePixmap (s->dpy, w->mask);
624 new_sentence (state *st, state *s)
627 sentence *se = (sentence *) calloc (1, sizeof (*se));
628 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
630 se->id = ++st->id_tick;
636 free_sentence (state *s, sentence *se)
639 for (i = 0; i < se->nwords; i++)
640 free_word (s, se->words[i]);
641 if (se->words) free (se->words);
644 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
646 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
648 if (se->font_name) free (se->font_name);
649 if (se->font) XFreeFont (s->dpy, se->font);
650 if (se->fg_gc) XFreeGC (s->dpy, se->fg_gc);
656 /* free the word, and put its text back at the front of the input queue,
657 to be read next time. */
659 unread_word (state *s, word *w)
661 if (unread_word_text)
663 unread_word_text = w->text;
669 /* Divide each of the words in the sentence into one character words,
670 without changing the positions of those characters.
673 split_words (state *s, sentence *se)
678 for (i = 0; i < se->nwords; i++)
679 nwords2 += strlen (se->words[i]->text);
681 words2 = (word **) calloc (nwords2, sizeof(*words2));
683 for (i = 0, j = 0; i < se->nwords; i++)
685 word *ow = se->words[i];
686 int L = strlen (ow->text);
691 int sx = ow->start_x;
692 int sy = ow->start_y;
693 int tx = ow->target_x;
694 int ty = ow->target_y;
696 for (k = 0; k < L; k++)
698 char *t2 = malloc (2);
704 w2 = new_word (s, se, t2, True);
707 xoff = (w2->lbearing - ow->lbearing);
708 yoff = (ow->ascent - w2->ascent);
712 w2->start_x = sx + xoff;
713 w2->start_y = sy + yoff;
714 w2->target_x = tx + xoff;
715 w2->target_y = ty + yoff;
728 se->nwords = nwords2;
732 /* Set the source or destination position of the words to be somewhere
736 scatter_sentence (state *s, sentence *se)
741 int flock_p = ((random() % 4) == 0);
742 int mode = (flock_p ? (random() % 12) : 0);
744 for (i = 0; i < se->nwords; i++)
746 word *w = se->words[i];
748 int r = (flock_p ? mode : (random() % 4));
751 /* random positions on the edges */
755 y = random() % s->xgwa.height;
758 x = off + s->xgwa.width;
759 y = random() % s->xgwa.height;
762 x = random() % s->xgwa.width;
763 y = -off - w->height;
766 x = random() % s->xgwa.width;
767 y = off + s->xgwa.height;
770 /* straight towards the edges */
777 x = off + s->xgwa.width;
782 y = -off - w->height;
786 y = off + s->xgwa.height;
793 y = -off - w->height;
797 y = off + s->xgwa.height;
800 x = off + s->xgwa.width;
801 y = off + s->xgwa.height;
804 x = off + s->xgwa.width;
805 y = -off - w->height;
813 if (se->anim_state == IN)
826 w->nticks = ((100 + ((random() % 140) +
837 /* Set the source position of the words to be off the right side,
838 and the destination to be off the left side.
841 aim_sentence (state *s, sentence *se)
847 if (se->nwords <= 0) abort();
849 /* Have the sentence shift up or down a little bit; not too far, and
850 never let it fall off the top or bottom of the screen before its
851 last character has reached the left edge.
853 for (i = 0; i < 10; i++)
855 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
856 yoff = ty - se->words[0]->target_y;
857 if (yoff < s->xgwa.height/3) /* this one is ok */
861 for (i = 0; i < se->nwords; i++)
863 word *w = se->words[i];
864 w->start_x = w->target_x + s->xgwa.width;
865 w->target_x -= se->width;
866 w->start_y = w->target_y;
870 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
872 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
877 for (i = 0; i < se->nwords; i++)
879 word *w = se->words[i];
886 /* Randomize the order of the words in the list (since that changes
887 which ones are "on top".)
890 shuffle_words (state *s, sentence *se)
893 for (i = 0; i < se->nwords-1; i++)
895 int j = i + (random() % (se->nwords - i));
896 word *swap = se->words[i];
897 se->words[i] = se->words[j];
903 /* qsort comparitor */
905 cmp_sentences (const void *aa, const void *bb)
907 const sentence *a = *(sentence **) aa;
908 const sentence *b = *(sentence **) bb;
909 return ((a ? a->id : 999999) - (b ? b->id : 999999));
913 /* Sort the sentences by id, so that sentences added later are on top.
916 sort_sentences (state *s)
918 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
922 /* Re-pick the colors of the text and border
925 recolor (state *s, sentence *se)
928 XFreeColors (s->dpy, s->xgwa.colormap, &se->fg.pixel, 1, 0);
930 XFreeColors (s->dpy, s->xgwa.colormap, &se->bg.pixel, 1, 0);
932 se->fg.flags = DoRed|DoGreen|DoBlue;
933 se->bg.flags = DoRed|DoGreen|DoBlue;
935 switch (random() % 2)
937 case 0: /* bright fg, dim bg */
938 se->fg.red = (random() % 0x8888) + 0x8888;
939 se->fg.green = (random() % 0x8888) + 0x8888;
940 se->fg.blue = (random() % 0x8888) + 0x8888;
941 se->bg.red = (random() % 0x5555);
942 se->bg.green = (random() % 0x5555);
943 se->bg.blue = (random() % 0x5555);
946 case 1: /* bright bg, dim fg */
947 se->fg.red = (random() % 0x4444);
948 se->fg.green = (random() % 0x4444);
949 se->fg.blue = (random() % 0x4444);
950 se->bg.red = (random() % 0x4444) + 0xCCCC;
951 se->bg.green = (random() % 0x4444) + 0xCCCC;
952 se->bg.blue = (random() % 0x4444) + 0xCCCC;
961 se->dark_p = (se->fg.red*2 + se->fg.green*3 + se->fg.blue <
962 se->bg.red*2 + se->bg.green*3 + se->bg.blue);
964 if (XAllocColor (s->dpy, s->xgwa.colormap, &se->fg))
965 XSetForeground (s->dpy, se->fg_gc, se->fg.pixel);
966 if (XAllocColor (s->dpy, s->xgwa.colormap, &se->bg))
967 XSetBackground (s->dpy, se->fg_gc, se->bg.pixel);
972 align_line (state *s, sentence *se, int line_start, int x, int right)
975 switch (se->alignment)
977 case LEFT: off = 0; break;
978 case CENTER: off = (right - x) / 2; break;
979 case RIGHT: off = (right - x); break;
980 default: abort(); break;
984 for (j = line_start; j < se->nwords; j++)
985 se->words[j]->target_x += off;
989 /* Fill the sentence with new words: in "page" mode, fills the page
990 with text; in "scroll" mode, just makes one long horizontal sentence.
991 The sentence might have *no* words in it, if no text is currently
995 populate_sentence (state *s, sentence *se)
998 int left, right, top, x, y;
1003 int array_size = 100;
1005 se->move_chars_p = (s->mode == SCROLL ? False :
1006 (random() % 3) ? False : True);
1007 se->alignment = (random() % 3);
1013 for (i = 0; i < se->nwords; i++)
1014 free_word (s, se->words[i]);
1018 se->words = (word **) calloc (array_size, sizeof(*se->words));
1024 left = random() % (s->xgwa.width / 3);
1025 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1026 top = random() % (s->xgwa.height * 2 / 3);
1030 right = s->xgwa.width;
1031 top = random() % s->xgwa.height;
1043 char *txt = get_word_text (s);
1047 if (se->nwords == 0)
1048 return; /* If the stream is empty, bail. */
1050 break; /* If EOF after some words, end of sentence. */
1053 if (! se->font) /* Got a word: need a font now */
1056 if (y < se->font->ascent)
1057 y += se->font->ascent;
1058 space = XTextWidth (se->font, " ", 1);
1061 w = new_word (s, se, txt, !se->move_chars_p);
1063 /* If we have a few words, let punctuation terminate the sentence:
1064 stop gathering more words if the last word ends in a period, etc. */
1065 if (se->nwords >= 4)
1067 char c = w->text[strlen(w->text)-1];
1068 if (c == '.' || c == '?' || c == '!')
1072 /* If the sentence is kind of long already, terminate at commas, etc. */
1073 if (se->nwords >= 12)
1075 char c = w->text[strlen(w->text)-1];
1076 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1077 c == ')' || c == ']' || c == '}')
1081 if (se->nwords >= 25) /* ok that's just about enough out of you */
1084 if (s->mode == PAGE &&
1085 x + w->rbearing > right) /* wrap line */
1087 align_line (s, se, line_start, x, right);
1088 line_start = se->nwords;
1091 y += se->font->ascent;
1093 /* If we're close to the bottom of the screen, stop, and
1094 unread the current word. (But not if this is the first
1095 word, otherwise we might just get stuck on it.)
1097 if (se->nwords > 0 &&
1098 y + se->font->ascent > s->xgwa.height)
1106 w->target_x = x + w->lbearing;
1107 w->target_y = y - w->ascent;
1109 x += w->rbearing + space;
1112 if (se->nwords >= (array_size - 1))
1115 se->words = (word **) realloc (se->words,
1116 array_size * sizeof(*se->words));
1119 fprintf (stderr, "%s: out of memory (%d words)\n",
1120 progname, array_size);
1125 se->words[se->nwords++] = w;
1133 align_line (s, se, line_start, x, right);
1134 if (se->move_chars_p)
1135 split_words (s, se);
1136 scatter_sentence (s, se);
1137 shuffle_words (s, se);
1140 aim_sentence (s, se);
1150 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1151 for (i = 0; i < se->nwords; i++)
1152 fprintf (stderr, " %s", se->words[i]->text);
1153 fprintf (stderr, "\n");
1159 /* Render a single word object to the screen.
1162 draw_word (state *s, sentence *se, word *w)
1164 if (! w->pixmap) return;
1166 if (w->x + w->width < 0 ||
1167 w->y + w->height < 0 ||
1168 w->x > s->xgwa.width ||
1169 w->y > s->xgwa.height)
1172 XSetClipMask (s->dpy, se->fg_gc, w->mask);
1173 XSetClipOrigin (s->dpy, se->fg_gc, w->x, w->y);
1174 XCopyPlane (s->dpy, w->pixmap, s->b, se->fg_gc,
1175 0, 0, w->width, w->height,
1181 /* If there is room for more sentences, add one.
1184 more_sentences (state *s)
1188 for (i = 0; i < s->nsentences; i++)
1190 sentence *se = s->sentences[i];
1193 se = new_sentence (s, s);
1194 populate_sentence (s, se);
1196 s->spawn_p = False, any = True;
1199 free_sentence (s, se);
1202 s->sentences[i] = se;
1204 s->latest_sentence = se->id;
1209 if (any) sort_sentences (s);
1213 /* Render all the words to the screen, and run the animation one step.
1216 draw_sentence (state *s, sentence *se)
1223 for (i = 0; i < se->nwords; i++)
1225 word *w = se->words[i];
1230 if (se->anim_state != PAUSE &&
1231 w->tick <= w->nticks)
1233 int dx = w->target_x - w->start_x;
1234 int dy = w->target_y - w->start_y;
1235 double r = sin (w->tick * M_PI / (2 * w->nticks));
1236 w->x = w->start_x + (dx * r);
1237 w->y = w->start_y + (dy * r);
1240 if (se->anim_state == OUT && s->mode == PAGE)
1241 w->tick++; /* go out faster */
1247 int dx = w->target_x - w->start_x;
1248 int dy = w->target_y - w->start_y;
1249 double r = (double) w->tick / w->nticks;
1250 w->x = w->start_x + (dx * r);
1251 w->y = w->start_y + (dy * r);
1253 moved = (w->tick <= w->nticks);
1255 /* Launch a new sentence when:
1256 - the front of this sentence is almost off the left edge;
1257 - the end of this sentence is almost on screen.
1260 if (se->anim_state != OUT &&
1262 se->id == s->latest_sentence)
1264 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1265 w->x + se->width < (s->xgwa.width * 2.1));
1266 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1268 if (new_p || rand_p)
1270 se->anim_state = OUT;
1274 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1276 se->words[0]->x + se->width,
1277 rand_p ? " randomly" : "");
1288 draw_word (s, se, w);
1291 if (moved && se->anim_state == PAUSE)
1296 switch (se->anim_state)
1299 se->anim_state = PAUSE;
1300 se->pause_tick = (se->nwords * 7 * s->linger);
1301 if (se->move_chars_p)
1302 se->pause_tick /= 5;
1303 scatter_sentence (s, se);
1304 shuffle_words (s, se);
1307 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1311 if (--se->pause_tick <= 0)
1313 se->anim_state = OUT;
1317 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1324 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1328 for (j = 0; j < s->nsentences; j++)
1329 if (s->sentences[j] == se)
1330 s->sentences[j] = 0;
1331 free_sentence (s, se);
1342 static unsigned long
1343 fontglide_draw_metrics (state *s)
1346 char *fn = (s->font_override ? s->font_override : "fixed");
1347 XFontStruct *font = XLoadQueryFont (s->dpy, fn);
1348 XCharStruct c, overall;
1349 int dir, ascent, descent;
1352 unsigned long red = 0xFFFF0000; /* so shoot me */
1353 unsigned long green = 0xFF00FF00;
1354 unsigned long blue = 0xFF6666FF;
1357 txt[0] = s->debug_metrics_p;
1360 gc = XCreateGC (s->dpy, s->window, 0, 0);
1361 XSetFont (s->dpy, gc, font->fid);
1364 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1367 XTextExtents (font, txt, strlen(txt),
1368 &dir, &ascent, &descent, &overall);
1369 c = font->per_char[((unsigned char *) txt)[0] - font->min_char_or_byte2];
1371 XClearWindow (s->dpy, s->window);
1373 x = (s->xgwa.width - overall.width) / 2;
1374 y = (s->xgwa.height - (2 * (ascent + descent))) / 2;
1376 for (i = 0; i < 2; i++)
1378 XCharStruct cc = (i == 0 ? c : overall);
1380 int x2 = s->xgwa.width - 40;
1381 int x3 = s->xgwa.width;
1383 XSetForeground (s->dpy, gc, red);
1384 XDrawLine (s->dpy, s->window, gc, 0, y - ascent, x3, y - ascent);
1385 XDrawLine (s->dpy, s->window, gc, 0, y + descent, x3, y + descent);
1387 XSetForeground (s->dpy, gc, green);
1388 /* ascent, baseline, descent */
1389 XDrawLine (s->dpy, s->window, gc, x1, y - cc.ascent, x2, y - cc.ascent);
1390 XDrawLine (s->dpy, s->window, gc, x1, y, x2, y);
1391 XDrawLine (s->dpy, s->window, gc, x1, y + cc.descent, x2, y + cc.descent);
1394 XSetForeground (s->dpy, gc, blue);
1395 XDrawLine (s->dpy, s->window, gc,
1397 x, y + descent + 10);
1398 XDrawLine (s->dpy, s->window, gc,
1399 x + cc.width, y - ascent - 10,
1400 x + cc.width, y + descent + 10);
1402 /* lbearing, rbearing */
1403 XSetForeground (s->dpy, gc, green);
1404 XDrawLine (s->dpy, s->window, gc,
1405 x + cc.lbearing, y - ascent,
1406 x + cc.lbearing, y + descent);
1407 XDrawLine (s->dpy, s->window, gc,
1408 x + cc.rbearing, y - ascent,
1409 x + cc.rbearing, y + descent);
1411 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1412 XDrawString (s->dpy, s->window, gc, x, y, txt, strlen(txt));
1414 y += (ascent + descent) * 2;
1417 XFreeGC (s->dpy, gc);
1418 XFreeFont (s->dpy, font);
1419 return s->frame_delay;
1423 /* Render all the words to the screen, and run the animation one step.
1424 Clear screen first, swap buffers after.
1426 static unsigned long
1427 fontglide_draw (Display *dpy, Window window, void *closure)
1429 state *s = (state *) closure;
1432 if (s->debug_metrics_p)
1433 return fontglide_draw_metrics (closure);
1439 XFillRectangle (s->dpy, s->b, s->bg_gc,
1440 0, 0, s->xgwa.width, s->xgwa.height);
1442 for (i = 0; i < s->nsentences; i++)
1443 draw_sentence (s, s->sentences[i]);
1445 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1448 XdbeSwapInfo info[1];
1449 info[0].swap_window = s->window;
1450 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
1451 XdbeSwapBuffers (s->dpy, info, 1);
1454 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1457 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
1458 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
1461 return s->frame_delay;
1466 /* When the subprocess has generated some output, this reads as much as it
1467 can into s->buf at s->buf_tail.
1470 drain_input (state *s)
1472 while (s->buf_tail < sizeof(s->buf) - 2)
1474 int c = textclient_getc (s->tc);
1476 s->buf[s->buf_tail++] = (char) c;
1483 /* Window setup and resource loading */
1486 fontglide_init (Display *dpy, Window window)
1489 state *s = (state *) calloc (1, sizeof(*s));
1492 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
1494 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1496 s->font_override = get_string_resource (dpy, "font", "Font");
1497 if (s->font_override && (!*s->font_override || *s->font_override == '('))
1498 s->font_override = 0;
1500 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
1501 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
1502 if (s->border_width < 0 || s->border_width > 20)
1503 s->border_width = 1;
1505 s->speed = get_float_resource (dpy, "speed", "Float");
1506 if (s->speed <= 0 || s->speed > 200)
1509 s->linger = get_float_resource (dpy, "linger", "Float");
1510 if (s->linger <= 0 || s->linger > 200)
1513 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
1514 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
1515 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
1518 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
1520 # ifdef HAVE_COCOA /* Don't second-guess Quartz's double-buffering */
1524 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
1527 char *ss = get_string_resource (dpy, "mode", "Mode");
1528 if (!ss || !*ss || !strcasecmp (ss, "random"))
1529 s->mode = ((random() % 2) ? SCROLL : PAGE);
1530 else if (!strcasecmp (ss, "scroll"))
1532 else if (!strcasecmp (ss, "page"))
1537 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
1544 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1545 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
1547 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
1549 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
1551 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1555 s->ba = XCreatePixmap (s->dpy, s->window,
1556 s->xgwa.width, s->xgwa.height,
1566 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
1567 "background", "Background");
1568 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
1570 s->nsentences = 5; /* #### */
1571 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
1574 s->tc = textclient_open (dpy);
1581 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
1583 state *s = (state *) closure;
1585 if (! s->debug_metrics_p)
1587 else if (event->xany.type == ButtonPress)
1589 s->debug_metrics_p++;
1590 if (s->debug_metrics_p > 255)
1591 s->debug_metrics_p = ' ';
1592 else if (s->debug_metrics_p > 127 &&
1593 s->debug_metrics_p < 159)
1594 s->debug_metrics_p = 160;
1597 else if (event->xany.type == KeyPress)
1601 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1603 s->debug_metrics_p = (unsigned char) c;
1612 fontglide_reshape (Display *dpy, Window window, void *closure,
1613 unsigned int w, unsigned int h)
1615 state *s = (state *) closure;
1616 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
1618 if (s->dbuf && (s->ba))
1620 XFreePixmap (s->dpy, s->ba);
1621 s->ba = XCreatePixmap (s->dpy, s->window,
1622 s->xgwa.width, s->xgwa.height,
1624 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
1625 s->xgwa.width, s->xgwa.height);
1631 fontglide_free (Display *dpy, Window window, void *closure)
1633 state *s = (state *) closure;
1634 textclient_close (s->tc);
1636 /* #### there's more to free here */
1642 static const char *fontglide_defaults [] = {
1643 ".background: #000000",
1644 ".foreground: #DDDDDD",
1645 ".borderColor: #555555",
1647 "*program: xscreensaver-text",
1651 "*fontCharset: iso8859-1",
1652 "*fontBorderWidth: 2",
1657 "*debugMetrics: False",
1658 "*doubleBuffer: True",
1659 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
1661 "*useDBEClear: True",
1662 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
1666 static XrmOptionDescRec fontglide_options [] = {
1667 { "-mode", ".mode", XrmoptionSepArg, 0 },
1668 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
1669 { "-page", ".mode", XrmoptionNoArg, "page" },
1670 { "-random", ".mode", XrmoptionNoArg, "random" },
1671 { "-delay", ".delay", XrmoptionSepArg, 0 },
1672 { "-speed", ".speed", XrmoptionSepArg, 0 },
1673 { "-linger", ".linger", XrmoptionSepArg, 0 },
1674 { "-program", ".program", XrmoptionSepArg, 0 },
1675 { "-font", ".font", XrmoptionSepArg, 0 },
1676 { "-fn", ".font", XrmoptionSepArg, 0 },
1677 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
1678 { "-trails", ".trails", XrmoptionNoArg, "True" },
1679 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
1680 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
1681 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
1682 { "-debug", ".debug", XrmoptionNoArg, "True" },
1683 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
1688 XSCREENSAVER_MODULE ("FontGlide", fontglide)