1 /* xscreensaver, Copyright (c) 2003-2016 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * fontglide -- reads text from a subprocess and puts it on the screen using
12 * large characters that glide in from the edges, assemble, then disperse.
13 * Requires a system with scalable fonts. (X's font handing sucks. A lot.)
18 #endif /* HAVE_CONFIG_H */
21 /* If you turn on DEBUG, this program also masquerades as a tool for
22 debugging font metrics issues, which is probably only if interest
23 if you are doing active development on libjwxyz.a itself.
31 # include <X11/Intrinsic.h>
38 #include "screenhack.h"
39 #include "textclient.h"
43 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
45 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
50 int x, y; /* Position of origin of first character in word */
52 /* These have the same meanings as in XCharStruct: */
53 int lbearing; /* origin to leftmost pixel */
54 int rbearing; /* origin to rightmost pixel */
55 int ascent; /* origin to topmost pixel */
56 int descent; /* origin to bottommost pixel */
57 int width; /* origin to next word's origin */
61 int target_x, target_y;
75 XftColor xftcolor_fg, xftcolor_bg;
80 enum { IN, PAUSE, OUT } anim_state;
81 enum { LEFT, CENTER, RIGHT } alignment;
90 XWindowAttributes xgwa;
92 Pixmap b, ba; /* double-buffer to reduce flicker */
95 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
98 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
100 Bool dbuf; /* Whether we're using double buffering. */
102 int border_width; /* size of the font outline */
103 char *charset; /* registry and encoding for font lookups */
104 double speed; /* frame rate multiplier */
105 double linger; /* multiplier for how long to leave words on screen */
107 enum { PAGE, SCROLL, CHARS } mode;
109 char *font_override; /* if -font was specified on the cmd line */
111 char buf [40]; /* this only needs to be as big as one "word". */
117 sentence **sentences;
118 Bool spawn_p; /* whether it is time to create a new sentence */
120 unsigned long frame_delay;
126 unsigned long debug_metrics_p;
127 int debug_metrics_antialiasing_p;
129 unsigned entering_unicode_p; /* 0 = No, 1 = Just started, 2 = in progress */
130 XFontStruct *metrics_font1;
131 XFontStruct *metrics_font2;
132 XftFont *metrics_xftfont;
134 char *prev_font_name;
135 char *next_font_name;
141 static void drain_input (state *s);
145 pick_font_size (state *s)
147 double scale = s->xgwa.height / 1024.0; /* shrink for small windows */
148 int min, max, r, pixel;
153 if (min < 10) min = 10;
154 if (max < 30) max = 30;
158 pixel = min + ((random() % r) + (random() % r) + (random() % r));
160 if (s->mode == SCROLL) /* scroll mode likes bigger fonts */
167 /* Finds the set of scalable fonts on the system; picks one;
168 and loads that font in a random pixel size.
169 Returns False if something went wrong.
172 pick_font_1 (state *s, sentence *se)
178 #ifndef HAVE_JWXYZ /* real Xlib */
181 XFontStruct *info = 0;
182 int count = 0, count2 = 0;
187 XftFontClose (s->dpy, se->xftfont);
188 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
190 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
193 free (se->font_name);
198 if (s->font_override)
199 sprintf (pattern, "%.200s", s->font_override);
201 sprintf (pattern, "-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s",
208 "0", /* pixel size */
209 "0", /* point size */
210 "0", /* resolution x */
211 "0", /* resolution y */
214 s->charset); /* registry + encoding */
216 names = XListFonts (s->dpy, pattern, 1000, &count);
220 if (s->font_override)
221 fprintf (stderr, "%s: -font option bogus: %s\n", progname, pattern);
223 fprintf (stderr, "%s: no scalable fonts found! (pattern: %s)\n",
228 i = random() % count;
230 names2 = XListFontsWithInfo (s->dpy, names[i], 1000, &count2, &info);
235 fprintf (stderr, "%s: pattern %s\n"
236 " gave unusable %s\n\n",
237 progname, pattern, names[i]);
243 XFontStruct *font = &info[0];
244 unsigned long value = 0;
245 char *foundry=0, *family=0, *weight=0, *slant=0, *setwidth=0, *add_style=0;
246 unsigned long pixel=0, point=0, res_x=0, res_y=0;
248 unsigned long avg_width=0;
249 char *registry=0, *encoding=0;
251 char *bogus = "\"?\"";
253 # define STR(ATOM,VAR) \
255 a = XInternAtom (s->dpy, (ATOM), False); \
256 if (XGetFontProperty (font, a, &value)) \
257 VAR = XGetAtomName (s->dpy, value); \
261 # define INT(ATOM,VAR) \
263 a = XInternAtom (s->dpy, (ATOM), False); \
264 if (!XGetFontProperty (font, a, &VAR) || \
268 STR ("FOUNDRY", foundry);
269 STR ("FAMILY_NAME", family);
270 STR ("WEIGHT_NAME", weight);
271 STR ("SLANT", slant);
272 STR ("SETWIDTH_NAME", setwidth);
273 STR ("ADD_STYLE_NAME", add_style);
274 INT ("PIXEL_SIZE", pixel);
275 INT ("POINT_SIZE", point);
276 INT ("RESOLUTION_X", res_x);
277 INT ("RESOLUTION_Y", res_y);
278 STR ("SPACING", spacing);
279 INT ("AVERAGE_WIDTH", avg_width);
280 STR ("CHARSET_REGISTRY", registry);
281 STR ("CHARSET_ENCODING", encoding);
286 pixel = pick_font_size (s);
289 /* Occasionally change the aspect ratio of the font, by increasing
290 either the X or Y resolution (while leaving the other alone.)
292 #### Looks like this trick doesn't really work that well: the
293 metrics of the individual characters are ok, but the
294 overall font ascent comes out wrong (unscaled.)
296 if (! (random() % 8))
299 double scale = 1 + (frand(n) + frand(n) + frand(n));
308 "-%s-%s-%s-%s-%s-%s-%ld-%s-%ld-%ld-%s-%s-%s-%s",
309 foundry, family, weight, slant, setwidth, add_style,
310 pixel, "*", /* point, */
311 res_x, res_y, spacing,
318 fprintf (stderr, "%s: font has bogus %s property: %s\n",
319 progname, bogus, names[i]);
321 if (foundry) XFree (foundry);
322 if (family) XFree (family);
323 if (weight) XFree (weight);
324 if (slant) XFree (slant);
325 if (setwidth) XFree (setwidth);
326 if (add_style) XFree (add_style);
327 if (spacing) XFree (spacing);
328 if (registry) XFree (registry);
329 if (encoding) XFree (encoding);
334 XFreeFontInfo (names2, info, count2);
335 XFreeFontNames (names);
337 # else /* HAVE_JWXYZ */
339 if (s->font_override)
340 sprintf (pattern, "%.200s", s->font_override);
343 const char *family = "random";
344 const char *weight = ((random() % 2) ? "regular" : "bold");
345 const char *slant = ((random() % 2) ? "o" : "r");
346 int size = 10 * pick_font_size (s);
347 sprintf (pattern, "*-%s-%s-%s-*-*-*-%d-*", family, weight, slant, size);
350 # endif /* HAVE_JWXYZ */
352 if (! ok) return False;
354 se->xftfont = XftFontOpenXlfd (s->dpy, screen_number (s->xgwa.screen),
361 fprintf (stderr, "%s: unable to load font %s\n",
367 strcpy (pattern2, pattern);
371 const char *n = jwxyz_nativeFontName (se->xftfont->xfont->fid, &s);
372 sprintf (pattern2 + strlen(pattern2), " (%s %.1f)", n, s);
377 if (s->prev_font_name) free (s->prev_font_name);
378 s->prev_font_name = s->next_font_name;
379 s->next_font_name = strdup (pattern2);
382 /* Sometimes we get fonts with screwed up metrics. For example:
383 -b&h-lucida-medium-r-normal-sans-40-289-100-100-p-0-iso8859-1
385 When using XDrawString, XTextExtents and XTextExtents16, it is rendered
386 as a scaled-up bitmap font. The character M has rbearing 70, ascent 68
387 and width 78, which is correct for the glyph as rendered.
389 But when using XftDrawStringUtf8 and XftTextExtentsUtf8, it is rendered
390 at the original, smaller, un-scaled size, with rbearing 26, ascent 25
393 So it's taking the *size* from the unscaled font, the *advancement* from
394 the scaled-up version, and then *not* actually scaling it up. Awesome.
396 So, after loading the font, measure the M, and if its advancement is more
397 than 20% larger than its rbearing, reject the font.
399 ------------------------------------------------------------------------
401 Some observations on this nonsense from Dave Odell:
403 1. -*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1 normally
404 resolves to /usr/share/fonts/X11/100dpi/lutBS24-ISO8859-1.pcf.gz.
406 -*-lucidatypewriter-* is from the 'xfonts-100dpi' package in
407 Debian/Ubuntu. It's usually (54.46% of systems), but not always,
408 installed whenever an X.org server (57.96% of systems) is. It might
409 be a good idea for this and xfonts-75dpi to be recommended
410 dependencies of XScreenSaver in Debian, but that's neither here nor
411 there. https://qa.debian.org/popcon.php?package=xorg
412 https://qa.debian.org/popcon.php?package=xfonts-100dpi
414 2. It normally resolves to the PCF font... but not always.
416 Fontconfig has /etc/fonts/conf.d/ (it's /opt/local/etc/fonts/conf.d/
417 with MacPorts) containing symlinks to configuration files. And both
418 Debian and Ubuntu normally has a 70-no-bitmaps.conf, installed as part
419 of the 'fontconfig-config' package. And the 70-no-bitmaps.conf
420 symlink... disables bitmap fonts.
422 Without bitmap fonts, I get DejaVu Sans.
424 3. There's another symlink of interest here:
425 /etc/fonts/conf.d/10-scale-bitmap-fonts.conf. This adds space to the
426 right of glyphs of bitmap fonts when the requested size of the font is
427 larger than the actual bitmap font. Ubuntu and MacPorts has this one.
429 This specifically is causing text to have excessive character spacing.
431 (jwz asks: WHY WOULD ANYONE EVER WANT THIS BEHAVIOR?)
433 4. Notice that I'm only talking about Debian and Ubuntu. Other distros
434 will probably have different symlinks in /etc/fonts/conf.d/. So yes,
435 this can be an issue on Linux as well as MacOS.
443 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) "M", 1, &extents);
444 rbearing = extents.width - extents.x;
445 width = extents.xOff;
446 ratio = rbearing / (float) width;
450 fprintf (stderr, "%s: M ratio %.2f (%d %d): %s\n", progname,
451 ratio, rbearing, width, pattern2);
454 if (ratio < min && !s->font_override)
458 fprintf (stderr, "%s: skipping font with broken metrics: %s\n",
468 fprintf(stderr, "%s: %s\n", progname, pattern2);
471 se->font_name = strdup (pattern);
476 /* Finds the set of scalable fonts on the system; picks one;
477 and loads that font in a random pixel size.
480 pick_font (state *s, sentence *se)
483 for (i = 0; i < 50; i++)
484 if (pick_font_1 (s, se))
486 fprintf (stderr, "%s: too many font-loading failures: giving up!\n",
492 static char *unread_word_text = 0;
494 /* Returns a newly-allocated string with one word in it, or NULL if there
495 is no complete word available.
498 get_word_text (state *s)
500 const char *start = s->buf;
507 /* If we just launched, and haven't had any text yet, and it has been
508 more than 2 seconds since we launched, then push out "Loading..."
509 as our first text. So if the text source is speedy, just use that.
510 But if we'd display a blank screen for a while, give 'em something
516 s->start_time < ((time ((time_t *) 0) - 2)))
518 unread_word_text = "Loading...";
522 if (unread_word_text)
524 result = unread_word_text;
525 unread_word_text = 0;
526 return strdup (result);
529 /* Skip over whitespace at the beginning of the buffer,
530 and count up how many linebreaks we see while doing so.
538 if (*start == '\n' || (*start == '\r' && start[1] != '\n'))
545 /* If we saw a blank line, then return NULL (treat it as a temporary "eof",
546 to trigger a sentence break here.) */
550 /* Skip forward to the end of this word (find next whitespace.) */
558 /* If we have a word, allocate a string for it */
561 result = malloc ((end - start) + 1);
562 strncpy (result, start, (end-start));
563 result [end-start] = 0;
568 /* Make room in the buffer by compressing out any bytes we've processed.
572 int n = end - s->buf;
573 memmove (s->buf, end, sizeof(s->buf) - n);
581 /* Returns a 1-bit pixmap of the same size as the drawable,
582 with a 0 wherever the drawable is black.
585 make_mask (Screen *screen, Visual *visual, Drawable drawable)
587 Display *dpy = DisplayOfScreen (screen);
588 unsigned long black = BlackPixelOfScreen (screen);
591 unsigned int w, h, bw, d;
596 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bw, &d);
597 in = XGetImage (dpy, drawable, 0, 0, w, h, ~0L, ZPixmap);
598 out = XCreateImage (dpy, visual, 1, XYPixmap, 0, 0, w, h, 8, 0);
599 out->data = (char *) malloc (h * out->bytes_per_line);
600 for (y = 0; y < h; y++)
601 for (x = 0; x < w; x++)
602 XPutPixel (out, x, y, (black != XGetPixel (in, x, y)));
603 mask = XCreatePixmap (dpy, drawable, w, h, 1L);
604 gc = XCreateGC (dpy, mask, 0, 0);
605 XPutImage (dpy, mask, gc, out, 0, 0, 0, 0, w, h);
609 in->data = out->data = 0;
616 /* Gets some random text, and creates a "word" object from it.
619 new_word (state *s, sentence *se, const char *txt, Bool alloc_p)
623 int bw = s->border_width;
628 w = (word *) calloc (1, sizeof(*w));
629 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) txt, strlen(txt),
632 w->lbearing = -extents.x;
633 w->rbearing = extents.width - extents.x;
634 w->ascent = extents.y;
635 w->descent = extents.height - extents.y;
636 w->width = extents.xOff;
643 if (s->mode == SCROLL && !alloc_p) abort();
649 GC gc_fg, gc_bg, gc_black;
651 int width = w->rbearing - w->lbearing;
652 int height = w->ascent + w->descent;
654 if (width <= 0) width = 1;
655 if (height <= 0) height = 1;
657 w->pixmap = XCreatePixmap (s->dpy, s->b, width, height, s->xgwa.depth);
658 xftdraw = XftDrawCreate (s->dpy, w->pixmap, s->xgwa.visual,
661 gcv.foreground = se->xftcolor_fg.pixel;
662 gc_fg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
664 gcv.foreground = se->xftcolor_bg.pixel;
665 gc_bg = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
667 gcv.foreground = BlackPixelOfScreen (s->xgwa.screen);
668 gc_black = XCreateGC (s->dpy, w->pixmap, GCForeground, &gcv);
670 XFillRectangle (s->dpy, w->pixmap, gc_black, 0, 0, width, height);
675 /* bounding box (behind the characters) */
676 XDrawRectangle (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
677 0, 0, width-1, height-1);
681 /* Draw background text for border */
682 for (i = -bw; i <= bw; i++)
683 for (j = -bw; j <= bw; j++)
684 XftDrawStringUtf8 (xftdraw, &se->xftcolor_bg, se->xftfont,
685 -w->lbearing + i, w->ascent + j,
686 (FcChar8 *) txt, strlen(txt));
688 /* Draw foreground text */
689 XftDrawStringUtf8 (xftdraw, &se->xftcolor_fg, se->xftfont,
690 -w->lbearing, w->ascent,
691 (FcChar8 *) txt, strlen(txt));
696 if (w->ascent != height)
698 /* baseline (on top of the characters) */
699 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
700 0, w->ascent, width-1, w->ascent);
705 /* left edge of charcell */
706 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
708 -w->lbearing, height-1);
711 if (w->rbearing != w->width)
713 /* right edge of charcell */
714 XDrawLine (s->dpy, w->pixmap, (se->dark_p ? gc_bg : gc_fg),
715 w->width - w->lbearing, 0,
716 w->width - w->lbearing, height-1);
721 w->mask = make_mask (s->xgwa.screen, s->xgwa.visual, w->pixmap);
723 XftDrawDestroy (xftdraw);
724 XFreeGC (s->dpy, gc_fg);
725 XFreeGC (s->dpy, gc_bg);
726 XFreeGC (s->dpy, gc_black);
729 w->text = strdup (txt);
735 free_word (state *s, word *w)
737 if (w->text) free (w->text);
738 if (w->pixmap) XFreePixmap (s->dpy, w->pixmap);
739 if (w->mask) XFreePixmap (s->dpy, w->mask);
744 new_sentence (state *st, state *s)
747 sentence *se = (sentence *) calloc (1, sizeof (*se));
748 se->fg_gc = XCreateGC (s->dpy, s->b, 0, &gcv);
750 se->id = ++st->id_tick;
756 free_sentence (state *s, sentence *se)
759 for (i = 0; i < se->nwords; i++)
760 free_word (s, se->words[i]);
764 free (se->font_name);
766 XFreeGC (s->dpy, se->fg_gc);
770 XftFontClose (s->dpy, se->xftfont);
771 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
773 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
781 /* free the word, and put its text back at the front of the input queue,
782 to be read next time. */
784 unread_word (state *s, word *w)
786 if (unread_word_text)
788 unread_word_text = w->text;
794 /* Divide each of the words in the sentence into one character words,
795 without changing the positions of those characters.
798 split_words (state *s, sentence *se)
804 char ***word_chars = (char ***) malloc (se->nwords * sizeof(*word_chars));
805 for (i = 0; i < se->nwords; i++)
808 word *ow = se->words[i];
809 word_chars[i] = utf8_split (ow->text, &L);
813 words2 = (word **) calloc (nwords2, sizeof(*words2));
815 for (i = 0, j = 0; i < se->nwords; i++)
817 char **chars = word_chars[i];
818 word *parent = se->words[i];
821 int sx = parent->start_x;
822 int sy = parent->start_y;
823 int tx = parent->target_x;
824 int ty = parent->target_y;
827 for (k = 0; chars[k]; k++)
830 word *w2 = new_word (s, se, t2, True);
845 /* This is not invariant when kerning is involved! */
846 /* if (x != parent->x + parent->width) abort(); */
848 free (chars); /* but we retain its contents */
849 free_word (s, parent);
851 if (j != nwords2) abort();
856 se->nwords = nwords2;
860 /* Set the source or destination position of the words to be somewhere
864 scatter_sentence (state *s, sentence *se)
867 int off = s->border_width * 4 + 2;
869 int flock_p = ((random() % 4) == 0);
870 int mode = (flock_p ? (random() % 12) : 0);
872 for (i = 0; i < se->nwords; i++)
874 word *w = se->words[i];
876 int r = (flock_p ? mode : (random() % 4));
877 int left = -(off + w->rbearing);
878 int top = -(off + w->descent);
879 int right = off - w->lbearing + s->xgwa.width;
880 int bottom = off + w->ascent + s->xgwa.height;
883 /* random positions on the edges */
884 case 0: x = left; y = random() % s->xgwa.height; break;
885 case 1: x = right; y = random() % s->xgwa.height; break;
886 case 2: x = random() % s->xgwa.width; y = top; break;
887 case 3: x = random() % s->xgwa.width; y = bottom; break;
889 /* straight towards the edges */
890 case 4: x = left; y = w->target_y; break;
891 case 5: x = right; y = w->target_y; break;
892 case 6: x = w->target_x; y = top; break;
893 case 7: x = w->target_x; y = bottom; break;
896 case 8: x = left; y = top; break;
897 case 9: x = left; y = bottom; break;
898 case 10: x = right; y = top; break;
899 case 11: x = right; y = bottom; break;
901 default: abort(); break;
904 if (se->anim_state == IN)
917 w->nticks = ((100 + ((random() % 140) +
928 /* Set the source position of the words to be off the right side,
929 and the destination to be off the left side.
932 aim_sentence (state *s, sentence *se)
938 if (se->nwords <= 0) abort();
940 /* Have the sentence shift up or down a little bit; not too far, and
941 never let it fall off the top or bottom of the screen before its
942 last character has reached the left edge.
944 for (i = 0; i < 10; i++)
946 int ty = random() % (s->xgwa.height - se->words[0]->ascent);
947 yoff = ty - se->words[0]->target_y;
948 if (yoff < s->xgwa.height/3) /* this one is ok */
952 for (i = 0; i < se->nwords; i++)
954 word *w = se->words[i];
955 w->start_x = w->target_x + s->xgwa.width;
956 w->target_x -= se->width;
957 w->start_y = w->target_y;
961 nticks = ((se->words[0]->start_x - se->words[0]->target_x)
963 nticks *= (frand(0.9) + frand(0.9) + frand(0.9));
968 for (i = 0; i < se->nwords; i++)
970 word *w = se->words[i];
977 /* Randomize the order of the words in the list (since that changes
978 which ones are "on top".)
981 shuffle_words (state *s, sentence *se)
984 for (i = 0; i < se->nwords-1; i++)
986 int j = i + (random() % (se->nwords - i));
987 word *swap = se->words[i];
988 se->words[i] = se->words[j];
994 /* qsort comparitor */
996 cmp_sentences (const void *aa, const void *bb)
998 const sentence *a = *(sentence **) aa;
999 const sentence *b = *(sentence **) bb;
1000 return ((a ? a->id : 999999) - (b ? b->id : 999999));
1004 /* Sort the sentences by id, so that sentences added later are on top.
1007 sort_sentences (state *s)
1009 qsort (s->sentences, s->nsentences, sizeof(*s->sentences), cmp_sentences);
1013 /* Re-pick the colors of the text and border
1016 recolor (state *s, sentence *se)
1018 XRenderColor fg, bg;
1020 fg.red = (random() % 0x5555) + 0xAAAA;
1021 fg.green = (random() % 0x5555) + 0xAAAA;
1022 fg.blue = (random() % 0x5555) + 0xAAAA;
1024 bg.red = (random() % 0x5555);
1025 bg.green = (random() % 0x5555);
1026 bg.blue = (random() % 0x5555);
1032 XRenderColor swap = fg; fg = bg; bg = swap;
1038 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1040 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap,
1044 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &fg,
1046 XftColorAllocValue (s->dpy, s->xgwa.visual, s->xgwa.colormap, &bg,
1052 align_line (state *s, sentence *se, int line_start, int x, int right)
1055 switch (se->alignment)
1057 case LEFT: off = 0; break;
1058 case CENTER: off = (right - x) / 2; break;
1059 case RIGHT: off = (right - x); break;
1060 default: abort(); break;
1064 for (j = line_start; j < se->nwords; j++)
1065 se->words[j]->target_x += off;
1069 /* Fill the sentence with new words: in "page" mode, fills the page
1070 with text; in "scroll" mode, just makes one long horizontal sentence.
1071 The sentence might have *no* words in it, if no text is currently
1075 populate_sentence (state *s, sentence *se)
1078 int left, right, top, x, y;
1083 int array_size = 100;
1085 se->move_chars_p = (s->mode == CHARS ? True :
1086 s->mode == SCROLL ? False :
1087 (random() % 3) ? False : True);
1088 se->alignment = (random() % 3);
1094 for (i = 0; i < se->nwords; i++)
1095 free_word (s, se->words[i]);
1099 se->words = (word **) calloc (array_size, sizeof(*se->words));
1106 left = random() % (s->xgwa.width / 3);
1107 right = s->xgwa.width - (random() % (s->xgwa.width / 3));
1108 top = random() % (s->xgwa.height * 2 / 3);
1112 right = s->xgwa.width;
1113 top = random() % s->xgwa.height;
1125 char *txt = get_word_text (s);
1129 if (se->nwords == 0)
1130 return; /* If the stream is empty, bail. */
1132 break; /* If EOF after some words, end of sentence. */
1135 if (! se->xftfont) /* Got a word: need a font now */
1139 if (y < se->xftfont->ascent)
1140 y += se->xftfont->ascent;
1142 /* Measure the space character to figure out how much room to
1143 leave between words (since we don't actually render that.) */
1144 XftTextExtentsUtf8 (s->dpy, se->xftfont, (FcChar8 *) " ", 1,
1146 space = extents.xOff;
1149 w = new_word (s, se, txt, !se->move_chars_p);
1153 /* If we have a few words, let punctuation terminate the sentence:
1154 stop gathering more words if the last word ends in a period, etc. */
1155 if (se->nwords >= 4)
1157 char c = w->text[strlen(w->text)-1];
1158 if (c == '.' || c == '?' || c == '!')
1162 /* If the sentence is kind of long already, terminate at commas, etc. */
1163 if (se->nwords >= 12)
1165 char c = w->text[strlen(w->text)-1];
1166 if (c == ',' || c == ';' || c == ':' || c == '-' ||
1167 c == ')' || c == ']' || c == '}')
1171 if (se->nwords >= 25) /* ok that's just about enough out of you */
1174 if ((s->mode == PAGE || s->mode == CHARS) &&
1175 x + w->rbearing > right) /* wrap line */
1177 align_line (s, se, line_start, x, right);
1178 line_start = se->nwords;
1181 y += se->xftfont->ascent + se->xftfont->descent;
1183 /* If we're close to the bottom of the screen, stop, and
1184 unread the current word. (But not if this is the first
1185 word, otherwise we might just get stuck on it.)
1187 if (se->nwords > 0 &&
1188 y + se->xftfont->ascent + se->xftfont->descent > s->xgwa.height)
1200 x += w->width + space;
1203 if (se->nwords >= (array_size - 1))
1206 se->words = (word **)
1207 realloc (se->words, array_size * sizeof(*se->words));
1210 fprintf (stderr, "%s: out of memory (%d words)\n",
1211 progname, array_size);
1216 se->words[se->nwords++] = w;
1225 align_line (s, se, line_start, x, right);
1226 if (se->move_chars_p)
1227 split_words (s, se);
1228 scatter_sentence (s, se);
1229 shuffle_words (s, se);
1232 aim_sentence (s, se);
1242 fprintf (stderr, "%s: sentence %d:", progname, se->id);
1243 for (i = 0; i < se->nwords; i++)
1244 fprintf (stderr, " %s", se->words[i]->text);
1245 fprintf (stderr, "\n");
1251 /* Render a single word object to the screen.
1254 draw_word (state *s, sentence *se, word *word)
1257 if (! word->pixmap) return;
1259 x = word->x + word->lbearing;
1260 y = word->y - word->ascent;
1261 w = word->rbearing - word->lbearing;
1262 h = word->ascent + word->descent;
1266 x > s->xgwa.width ||
1270 XSetClipMask (s->dpy, se->fg_gc, word->mask);
1271 XSetClipOrigin (s->dpy, se->fg_gc, x, y);
1272 XCopyArea (s->dpy, word->pixmap, s->b, se->fg_gc,
1277 /* If there is room for more sentences, add one.
1280 more_sentences (state *s)
1284 for (i = 0; i < s->nsentences; i++)
1286 sentence *se = s->sentences[i];
1289 se = new_sentence (s, s);
1290 populate_sentence (s, se);
1292 s->spawn_p = False, any = True;
1295 free_sentence (s, se);
1298 s->sentences[i] = se;
1300 s->latest_sentence = se->id;
1305 if (any) sort_sentences (s);
1309 /* Render all the words to the screen, and run the animation one step.
1312 draw_sentence (state *s, sentence *se)
1319 for (i = 0; i < se->nwords; i++)
1321 word *w = se->words[i];
1327 if (se->anim_state != PAUSE &&
1328 w->tick <= w->nticks)
1330 int dx = w->target_x - w->start_x;
1331 int dy = w->target_y - w->start_y;
1332 double r = sin (w->tick * M_PI / (2 * w->nticks));
1333 w->x = w->start_x + (dx * r);
1334 w->y = w->start_y + (dy * r);
1337 if (se->anim_state == OUT &&
1338 (s->mode == PAGE || s->mode == CHARS))
1339 w->tick++; /* go out faster */
1345 int dx = w->target_x - w->start_x;
1346 int dy = w->target_y - w->start_y;
1347 double r = (double) w->tick / w->nticks;
1348 w->x = w->start_x + (dx * r);
1349 w->y = w->start_y + (dy * r);
1351 moved = (w->tick <= w->nticks);
1353 /* Launch a new sentence when:
1354 - the front of this sentence is almost off the left edge;
1355 - the end of this sentence is almost on screen.
1358 if (se->anim_state != OUT &&
1360 se->id == s->latest_sentence)
1362 Bool new_p = (w->x < (s->xgwa.width * 0.4) &&
1363 w->x + se->width < (s->xgwa.width * 2.1));
1364 Bool rand_p = (new_p ? 0 : !(random() % 2000));
1366 if (new_p || rand_p)
1368 se->anim_state = OUT;
1372 fprintf (stderr, "%s: OUT %d (x2 = %d%s)\n",
1374 se->words[0]->x + se->width,
1375 rand_p ? " randomly" : "");
1386 draw_word (s, se, w);
1389 if (moved && se->anim_state == PAUSE)
1394 switch (se->anim_state)
1397 se->anim_state = PAUSE;
1398 se->pause_tick = (se->nwords * 7 * s->linger);
1399 if (se->move_chars_p)
1400 se->pause_tick /= 5;
1401 scatter_sentence (s, se);
1402 shuffle_words (s, se);
1405 fprintf (stderr, "%s: PAUSE %d\n", progname, se->id);
1409 if (--se->pause_tick <= 0)
1411 se->anim_state = OUT;
1415 fprintf (stderr, "%s: OUT %d\n", progname, se->id);
1422 fprintf (stderr, "%s: DEAD %d\n", progname, se->id);
1426 for (j = 0; j < s->nsentences; j++)
1427 if (s->sentences[j] == se)
1428 s->sentences[j] = 0;
1429 free_sentence (s, se);
1440 #ifdef DEBUG /* All of this stuff is for -debug-metrics mode. */
1444 scale_ximage (Screen *screen, Window window, XImage *img, int scale,
1447 Display *dpy = DisplayOfScreen (screen);
1449 unsigned width = img->width, height = img->height;
1453 p2 = XCreatePixmap (dpy, window, width*scale, height*scale, img->depth);
1454 gc = XCreateGC (dpy, p2, 0, 0);
1456 XSetForeground (dpy, gc, BlackPixelOfScreen (screen));
1457 XFillRectangle (dpy, p2, gc, 0, 0, width*scale, height*scale);
1458 for (y = 0; y < height; y++)
1459 for (x = 0; x < width; x++)
1461 XSetForeground (dpy, gc, XGetPixel (img, x, y));
1462 XFillRectangle (dpy, p2, gc, x*scale, y*scale, scale, scale);
1467 XWindowAttributes xgwa;
1469 c.red = c.green = c.blue = 0x4444;
1470 c.flags = DoRed|DoGreen|DoBlue;
1471 XGetWindowAttributes (dpy, window, &xgwa);
1472 if (! XAllocColor (dpy, xgwa.colormap, &c)) abort();
1473 XSetForeground (dpy, gc, c.pixel);
1474 XDrawRectangle (dpy, p2, gc, 0, 0, width*scale-1, height*scale-1);
1475 XDrawRectangle (dpy, p2, gc, margin*scale, margin*scale,
1476 width*scale-1, height*scale-1);
1477 for (y = 0; y <= height - 2*margin; y++)
1478 XDrawLine (dpy, p2, gc,
1479 margin*scale, (y+margin)*scale-1,
1480 (width-margin)*scale, (y+margin)*scale-1);
1481 for (x = 0; x <= width - 2*margin; x++)
1482 XDrawLine (dpy, p2, gc,
1483 (x+margin)*scale-1, margin*scale,
1484 (x+margin)*scale-1, (height-margin)*scale);
1485 XFreeColors (dpy, xgwa.colormap, &c.pixel, 1, 0);
1493 static int check_edge (Display *dpy, Drawable p, GC gc,
1494 unsigned msg_x, unsigned msg_y, const char *msg,
1496 unsigned x, unsigned y, unsigned dim, unsigned end)
1507 XDrawString (dpy, p, gc, msg_x, msg_y, msg, strlen (msg));
1511 if (XGetPixel(img, pt[0], pt[1]) & 0xffffff)
1521 static unsigned long
1522 fontglide_draw_metrics (state *s)
1524 unsigned int margin = (s->debug_metrics_antialiasing_p ? 2 : 0);
1526 char txt[2], utxt[3], txt2[80];
1528 const char *fn = (s->font_override ? s->font_override : "fixed");
1530 XCharStruct c, overall, fake_c;
1531 int dir, ascent, descent;
1536 int sc = s->debug_scale;
1538 unsigned long red = 0xFFFF0000; /* so shoot me */
1539 unsigned long green = 0xFF00FF00;
1540 unsigned long blue = 0xFF6666FF;
1541 unsigned long yellow = 0xFFFFFF00;
1542 unsigned long cyan = 0xFF004040;
1544 Drawable dest = s->b ? s->b : s->window;
1548 /* Self-test these macros to make sure they're symmetrical. */
1549 for (i = 0; i < 1000; i++)
1553 c.lbearing = (random()%50)-100;
1554 c.rbearing = (random()%50)-100;
1555 c.ascent = (random()%50)-100;
1556 c.descent = (random()%50)-100;
1557 c.width = (random()%50)-100;
1558 XCharStruct_to_XGlyphInfo (c, g);
1559 XGlyphInfo_to_XCharStruct (g, overall);
1560 if (c.lbearing != overall.lbearing) abort();
1561 if (c.rbearing != overall.rbearing) abort();
1562 if (c.ascent != overall.ascent) abort();
1563 if (c.descent != overall.descent) abort();
1564 if (c.width != overall.width) abort();
1565 XCharStruct_to_XGlyphInfo (overall, g2);
1566 if (g.x != g2.x) abort();
1567 if (g.y != g2.y) abort();
1568 if (g.xOff != g2.xOff) abort();
1569 if (g.yOff != g2.yOff) abort();
1570 if (g.width != g2.width) abort();
1571 if (g.height != g2.height) abort();
1572 XCharStruct_to_XmbRectangle (overall, r);
1573 XmbRectangle_to_XCharStruct (r, c, c.width);
1574 if (c.lbearing != overall.lbearing) abort();
1575 if (c.rbearing != overall.rbearing) abort();
1576 if (c.ascent != overall.ascent) abort();
1577 if (c.descent != overall.descent) abort();
1578 if (c.width != overall.width) abort();
1581 txt[0] = s->debug_metrics_p;
1584 /* Convert Unicode code point to UTF-8. */
1585 utxt[utf8_encode(s->debug_metrics_p, utxt, 4)] = 0;
1587 txt3 = utf8_to_XChar2b (utxt, 0);
1589 if (! s->metrics_font1)
1590 s->metrics_font1 = XLoadQueryFont (s->dpy, fn);
1591 if (! s->metrics_font2)
1592 s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
1593 if (! s->metrics_font1)
1594 s->metrics_font1 = s->metrics_font2;
1596 gc = XCreateGC (s->dpy, dest, 0, 0);
1597 XSetFont (s->dpy, gc, s->metrics_font1->fid);
1599 # if defined(HAVE_JWXYZ)
1600 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1603 if (! s->metrics_xftfont)
1605 s->metrics_xftfont =
1606 XftFontOpenXlfd (s->dpy, screen_number(s->xgwa.screen), fn);
1607 if (! s->metrics_xftfont)
1609 const char *fn2 = "fixed";
1610 s->metrics_xftfont =
1611 XftFontOpenName (s->dpy, screen_number(s->xgwa.screen), fn2);
1612 if (s->metrics_xftfont)
1616 fprintf (stderr, "%s: XftFontOpen failed on \"%s\" and \"%s\"\n",
1627 const char *n = jwxyz_nativeFontName (s->metrics_xftfont->xfont->fid, &ss);
1628 sprintf (fn2, "%s %.1f", n, ss);
1632 xftdraw = XftDrawCreate (s->dpy, dest, s->xgwa.visual,
1634 XftColorAllocName (s->dpy, s->xgwa.visual, s->xgwa.colormap, "white",
1636 XftTextExtentsUtf8 (s->dpy, s->metrics_xftfont,
1637 (FcChar8 *) utxt, strlen(utxt),
1641 XTextExtents (s->metrics_font1, txt, strlen(txt),
1642 &dir, &ascent, &descent, &overall);
1643 c = ((s->debug_metrics_p >= s->metrics_font1->min_char_or_byte2 &&
1644 s->debug_metrics_p <= s->metrics_font1->max_char_or_byte2)
1645 ? s->metrics_font1->per_char[s->debug_metrics_p -
1646 s->metrics_font1->min_char_or_byte2]
1649 XSetForeground (s->dpy, gc, BlackPixelOfScreen (s->xgwa.screen));
1650 XFillRectangle (s->dpy, dest, gc, 0, 0, s->xgwa.width, s->xgwa.height);
1652 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1653 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1654 XDrawString (s->dpy, dest, gc,
1661 char *name = jwxyz_unicode_character_name (
1662 s->dpy, s->metrics_font1->fid, s->debug_metrics_p);
1663 if (!name || !*name) name = strdup("unknown character name");
1664 XDrawString (s->dpy, dest, gc,
1666 10 + 2 * (s->metrics_font2->ascent +
1667 s->metrics_font2->descent),
1668 name, strlen(name));
1673 /* i 0, j 0: top left, XDrawString, char metrics
1674 i 1, j 0: bot left, XDrawString, overall metrics, ink escape
1675 i 0, j 1: top right, XftDrawStringUtf8, utf8 metrics
1676 i 1, j 1: bot right, XDrawString16, 16 metrics, ink escape
1678 for (j = 0; j < 2; j++) {
1679 Bool xft_p = (j != 0);
1680 int ww = s->xgwa.width / 2 - 20;
1681 int xoff = (j == 0 ? 0 : ww + 20);
1683 /* XDrawString only does 8-bit characters, so skip it outside Latin-1. */
1684 if (s->debug_metrics_p >= 256)
1692 x = (ww - overall.width) / 2;
1694 for (i = 0; i < 2; i++)
1697 int x1 = xoff + ww * 0.18;
1698 int x2 = xoff + ww * 0.82;
1705 int h = sc * (ascent + descent);
1706 int min = (ascent + descent) * 4;
1707 if (h < min) h = min;
1712 memset (&fake_c, 0, sizeof(fake_c));
1714 if (!xft_p && i == 0)
1716 else if (!xft_p && i == 1)
1718 else if (xft_p && i == 0)
1720 /* Measure the glyph in the Xft way */
1722 XftTextExtentsUtf8 (s->dpy,
1724 (FcChar8 *) utxt, strlen(utxt),
1726 XGlyphInfo_to_XCharStruct (extents, fake_c);
1731 /* Measure the glyph in the 16-bit way */
1732 int dir, ascent, descent;
1733 XTextExtents16 (s->metrics_font1, txt3, 1, &dir, &ascent, &descent,
1738 pixw = margin * 2 + cc.rbearing - cc.lbearing;
1739 pixh = margin * 2 + cc.ascent + cc.descent;
1740 p = (pixw > 0 && pixh > 0
1741 ? XCreatePixmap (s->dpy, dest, pixw, pixh, s->xgwa.depth)
1747 GC gc2 = XCreateGC (s->dpy, p, 0, 0);
1749 jwxyz_XSetAntiAliasing (s->dpy, gc2, False);
1751 XSetFont (s->dpy, gc2, s->metrics_font1->fid);
1752 XSetForeground (s->dpy, gc2, BlackPixelOfScreen (s->xgwa.screen));
1753 XFillRectangle (s->dpy, p, gc2, 0, 0, pixw, pixh);
1754 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1755 XSetForeground (s->dpy, gc2, WhitePixelOfScreen (s->xgwa.screen));
1757 jwxyz_XSetAntiAliasing (s->dpy, gc2,
1758 s->debug_metrics_antialiasing_p);
1761 if (xft_p && i == 0)
1763 XftDraw *xftdraw2 = XftDrawCreate (s->dpy, p, s->xgwa.visual,
1765 XftDrawStringUtf8 (xftdraw2, &xftcolor,
1767 -cc.lbearing + margin,
1769 (FcChar8 *) utxt, strlen(utxt));
1770 XftDrawDestroy (xftdraw2);
1773 XDrawString16 (s->dpy, p, gc2,
1774 -cc.lbearing + margin,
1778 XDrawString (s->dpy, p, gc2,
1779 -cc.lbearing + margin,
1785 XImage *img = XGetImage (s->dpy, p, 0, 0, pixw, pixh,
1791 unsigned w = pixw - margin * 2, h = pixh - margin * 2;
1795 /* Check for ink escape. */
1796 unsigned long ink = 0;
1797 for (y2 = 0; y2 != pixh; ++y2)
1798 for (x2 = 0; x2 != pixw; ++x2)
1801 if (! (x2 >= margin &&
1802 x2 < pixw - margin &&
1804 y2 < pixh - margin))
1805 ink |= XGetPixel (img, x2, y2);
1810 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1811 XDrawString (s->dpy, dest, gc,
1817 /* ...And wasted space. */
1820 if (check_edge (s->dpy, dest, gc, 120, 60, "left",
1821 img, margin, margin, 1, h) |
1822 check_edge (s->dpy, dest, gc, 160, 60, "right",
1823 img, margin + w - 1, margin, 1, h) |
1824 check_edge (s->dpy, dest, gc, 200, 60, "top",
1825 img, margin, margin, 0, w) |
1826 check_edge (s->dpy, dest, gc, 240, 60, "bottom",
1827 img, margin, margin + h - 1, 0, w))
1829 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1830 XDrawString (s->dpy, dest, gc,
1832 "Wasted space: ", 14);
1837 if (s->debug_metrics_antialiasing_p)
1839 /* Draw a dark cyan boundary around antialiased glyphs */
1840 img2 = XCreateImage (s->dpy, s->xgwa.visual, img->depth,
1842 img->width, img->height,
1843 img->bitmap_pad, 0);
1844 img2->data = malloc (img->bytes_per_line * img->height);
1846 for (y2 = 0; y2 != pixh; ++y2)
1847 for (x2 = 0; x2 != pixw; ++x2)
1849 unsigned long px = XGetPixel (img, x2, y2);
1850 if ((px & 0xffffff) == 0)
1852 unsigned long neighbors = 0;
1854 neighbors |= XGetPixel (img, x2 - 1, y2);
1856 neighbors |= XGetPixel (img, x2 + 1, y2);
1858 neighbors |= XGetPixel (img, x2, y2 - 1);
1860 neighbors |= XGetPixel (img, x2, y2 + 1);
1861 XPutPixel (img2, x2, y2,
1862 (neighbors & 0xffffff
1864 : BlackPixelOfScreen (s->xgwa.screen)));
1868 XPutPixel (img2, x2, y2, px);
1878 p2 = scale_ximage (s->xgwa.screen, s->window, img2, sc, margin);
1880 XDestroyImage (img);
1881 XDestroyImage (img2);
1884 XCopyArea (s->dpy, p2, dest, gc,
1885 0, 0, sc*pixw, sc*pixh,
1886 xoff + x + sc * (cc.lbearing - margin),
1887 y - sc * (cc.ascent + margin));
1888 XFreePixmap (s->dpy, p);
1889 XFreePixmap (s->dpy, p2);
1890 XFreeGC (s->dpy, gc2);
1895 XSetFont (s->dpy, gc, s->metrics_font1->fid);
1896 XSetForeground (s->dpy, gc, WhitePixelOfScreen (s->xgwa.screen));
1898 jwxyz_XSetAntiAliasing (s->dpy, gc, s->debug_metrics_antialiasing_p);
1900 sprintf (txt2, "%s [XX%sXX] [%s%s%s%s]",
1901 (xft_p ? utxt : txt),
1902 (xft_p ? utxt : txt),
1903 (xft_p ? utxt : txt),
1904 (xft_p ? utxt : txt),
1905 (xft_p ? utxt : txt),
1906 (xft_p ? utxt : txt));
1909 XftDrawStringUtf8 (xftdraw, &xftcolor,
1911 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2
1914 (FcChar8 *) txt2, strlen(txt2));
1916 XDrawString (s->dpy, dest, gc,
1917 xoff + x/2 + (sc*cc.rbearing/2) - cc.rbearing/2,
1919 txt2, strlen(txt2));
1921 jwxyz_XSetAntiAliasing (s->dpy, gc, False);
1923 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1927 char *tptr = txt2 + sprintf(txt2, "U+%04lX", s->debug_metrics_p);
1928 *tptr++ = " ?_"[s->entering_unicode_p];
1934 tptr += sprintf (tptr, "0%03o ", (unsigned char) *uptr);
1941 tptr += sprintf (tptr, "%02x ", (unsigned char) *uptr);
1946 sprintf (txt2, "%c %3ld 0%03lo 0x%02lx%s",
1947 (char)s->debug_metrics_p, s->debug_metrics_p,
1948 s->debug_metrics_p, s->debug_metrics_p,
1949 (txt[0] < s->metrics_font1->min_char_or_byte2
1951 XDrawString (s->dpy, dest, gc,
1953 txt2, strlen(txt2));
1957 jwxyz_XSetAntiAliasing (s->dpy, gc, True);
1961 const char *ss = (j == 0
1962 ? (i == 0 ? "char" : "overall")
1963 : (i == 0 ? "utf8" : "16 bit"));
1964 XSetFont (s->dpy, gc, s->metrics_font2->fid);
1966 XSetForeground (s->dpy, gc, red);
1968 sprintf (txt2, "%s ascent %d", ss, ascent);
1969 XDrawString (s->dpy, dest, gc,
1972 txt2, strlen(txt2));
1973 XDrawLine (s->dpy, dest, gc,
1974 xoff, y - sc*ascent,
1977 sprintf (txt2, "%s descent %d", ss, descent);
1978 XDrawString (s->dpy, dest, gc,
1981 txt2, strlen(txt2));
1982 XDrawLine (s->dpy, dest, gc,
1983 xoff, y + sc*descent,
1984 x3, y + sc*descent);
1988 /* ascent, descent, baseline */
1990 XSetForeground (s->dpy, gc, green);
1992 sprintf (txt2, "ascent %d", cc.ascent);
1994 XDrawString (s->dpy, dest, gc,
1995 x1, y - sc*cc.ascent - 2,
1996 txt2, strlen(txt2));
1997 XDrawLine (s->dpy, dest, gc,
1998 x1, y - sc*cc.ascent,
1999 x2, y - sc*cc.ascent);
2001 sprintf (txt2, "descent %d", cc.descent);
2002 if (cc.descent != 0)
2003 XDrawString (s->dpy, dest, gc,
2004 x1, y + sc*cc.descent - 2,
2005 txt2, strlen(txt2));
2006 XDrawLine (s->dpy, dest, gc,
2007 x1, y + sc*cc.descent,
2008 x2, y + sc*cc.descent);
2010 XSetForeground (s->dpy, gc, yellow);
2011 strcpy (txt2, "baseline");
2012 XDrawString (s->dpy, dest, gc,
2014 txt2, strlen(txt2));
2015 XDrawLine (s->dpy, dest, gc, x1, y, x2, y);
2020 XSetForeground (s->dpy, gc, blue);
2022 strcpy (txt2, "origin");
2023 XDrawString (s->dpy, dest, gc,
2025 y + sc*descent + 50,
2026 txt2, strlen(txt2));
2027 XDrawLine (s->dpy, dest, gc,
2028 xoff + x, y - sc*(ascent - 10),
2029 xoff + x, y + sc*(descent + 10));
2031 sprintf (txt2, "width %d", cc.width);
2032 XDrawString (s->dpy, dest, gc,
2033 xoff + x + sc*cc.width + 2,
2034 y + sc*descent + 60,
2035 txt2, strlen(txt2));
2036 XDrawLine (s->dpy, dest, gc,
2037 xoff + x + sc*cc.width, y - sc*(ascent - 10),
2038 xoff + x + sc*cc.width, y + sc*(descent + 10));
2041 /* lbearing, rbearing */
2043 XSetForeground (s->dpy, gc, green);
2045 sprintf (txt2, "lbearing %d", cc.lbearing);
2046 XDrawString (s->dpy, dest, gc,
2047 xoff + x + sc*cc.lbearing + 2,
2048 y + sc * descent + 30,
2049 txt2, strlen(txt2));
2050 XDrawLine (s->dpy, dest, gc,
2051 xoff + x + sc*cc.lbearing, y - sc*ascent,
2052 xoff + x + sc*cc.lbearing, y + sc*descent + 20);
2054 sprintf (txt2, "rbearing %d", cc.rbearing);
2055 XDrawString (s->dpy, dest, gc,
2056 xoff + x + sc*cc.rbearing + 2,
2057 y + sc * descent + 40,
2058 txt2, strlen(txt2));
2059 XDrawLine (s->dpy, dest, gc,
2060 xoff + x + sc*cc.rbearing, y - sc*ascent,
2061 xoff + x + sc*cc.rbearing, y + sc*descent + 40);
2063 /* y += sc * (ascent + descent) * 2; */
2067 if (dest != s->window)
2068 XCopyArea (s->dpy, dest, s->window, s->bg_gc,
2069 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
2071 XFreeGC (s->dpy, gc);
2072 XftColorFree (s->dpy, s->xgwa.visual, s->xgwa.colormap, &xftcolor);
2073 XftDrawDestroy (xftdraw);
2076 return s->frame_delay;
2082 /* Render all the words to the screen, and run the animation one step.
2083 Clear screen first, swap buffers after.
2085 static unsigned long
2086 fontglide_draw (Display *dpy, Window window, void *closure)
2088 state *s = (state *) closure;
2092 if (s->debug_metrics_p)
2093 return fontglide_draw_metrics (closure);
2100 XFillRectangle (s->dpy, s->b, s->bg_gc,
2101 0, 0, s->xgwa.width, s->xgwa.height);
2103 for (i = 0; i < s->nsentences; i++)
2104 draw_sentence (s, s->sentences[i]);
2107 if (s->debug_p && (s->prev_font_name || s->next_font_name))
2111 if (! s->metrics_font2)
2112 s->metrics_font2 = XLoadQueryFont (s->dpy, "fixed");
2113 s->label_gc = XCreateGC (dpy, s->b, 0, 0);
2114 XSetFont (s->dpy, s->label_gc, s->metrics_font2->fid);
2116 if (s->prev_font_name)
2117 XDrawString (s->dpy, s->b, s->label_gc,
2118 10, 10 + s->metrics_font2->ascent,
2119 s->prev_font_name, strlen(s->prev_font_name));
2120 if (s->next_font_name)
2121 XDrawString (s->dpy, s->b, s->label_gc,
2122 10, 10 + s->metrics_font2->ascent * 2,
2123 s->next_font_name, strlen(s->next_font_name));
2127 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2130 XdbeSwapInfo info[1];
2131 info[0].swap_window = s->window;
2132 info[0].swap_action = (s->dbeclear_p ? XdbeBackground : XdbeUndefined);
2133 XdbeSwapBuffers (s->dpy, info, 1);
2136 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2139 XCopyArea (s->dpy, s->b, s->window, s->bg_gc,
2140 0, 0, s->xgwa.width, s->xgwa.height, 0, 0);
2143 return s->frame_delay;
2148 /* When the subprocess has generated some output, this reads as much as it
2149 can into s->buf at s->buf_tail.
2152 drain_input (state *s)
2154 while (s->buf_tail < sizeof(s->buf) - 2)
2156 int c = textclient_getc (s->tc);
2158 s->buf[s->buf_tail++] = (char) c;
2165 /* Window setup and resource loading */
2168 fontglide_init (Display *dpy, Window window)
2171 state *s = (state *) calloc (1, sizeof(*s));
2174 s->frame_delay = get_integer_resource (dpy, "delay", "Integer");
2176 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2178 s->font_override = get_string_resource (dpy, "font", "Font");
2179 if (s->font_override && (!*s->font_override || *s->font_override == '('))
2180 s->font_override = 0;
2182 s->charset = get_string_resource (dpy, "fontCharset", "FontCharset");
2183 s->border_width = get_integer_resource (dpy, "fontBorderWidth", "Integer");
2184 if (s->border_width < 0 || s->border_width > 20)
2185 s->border_width = 1;
2187 s->speed = get_float_resource (dpy, "speed", "Float");
2188 if (s->speed <= 0 || s->speed > 200)
2191 s->linger = get_float_resource (dpy, "linger", "Float");
2192 if (s->linger <= 0 || s->linger > 200)
2195 s->trails_p = get_boolean_resource (dpy, "trails", "Trails");
2198 s->debug_p = get_boolean_resource (dpy, "debug", "Debug");
2199 s->debug_metrics_p = (get_boolean_resource (dpy, "debugMetrics", "Debug")
2204 if (s->debug_metrics_p && !s->font_override)
2205 s->font_override = "Helvetica Bold 16";
2210 s->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
2212 # ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */
2217 if (s->debug_metrics_p) s->trails_p = False;
2220 if (s->trails_p) s->dbuf = False; /* don't need it in this case */
2223 const char *ss = get_string_resource (dpy, "mode", "Mode");
2224 if (!ss || !*ss || !strcasecmp (ss, "random"))
2225 s->mode = ((random() % 2) ? SCROLL : PAGE);
2226 else if (!strcasecmp (ss, "scroll"))
2228 else if (!strcasecmp (ss, "page"))
2230 else if (!strcasecmp (ss, "chars") || !strcasecmp (ss, "char"))
2235 "%s: `mode' must be `scroll', `page', or `random', not `%s'\n",
2242 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2243 s->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
2245 s->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
2247 s->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
2249 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2253 s->ba = XCreatePixmap (s->dpy, s->window,
2254 s->xgwa.width, s->xgwa.height,
2264 gcv.foreground = get_pixel_resource (s->dpy, s->xgwa.colormap,
2265 "background", "Background");
2266 s->bg_gc = XCreateGC (s->dpy, s->b, GCForeground, &gcv);
2268 s->nsentences = 5; /* #### */
2269 s->sentences = (sentence **) calloc (s->nsentences, sizeof (sentence *));
2273 s->start_time = time ((time_t *) 0);
2274 s->tc = textclient_open (dpy);
2281 fontglide_event (Display *dpy, Window window, void *closure, XEvent *event)
2284 state *s = (state *) closure;
2286 if (! s->debug_metrics_p)
2288 if (event->xany.type == KeyPress)
2290 static const unsigned long max = 0x110000;
2293 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2295 if (s->entering_unicode_p > 0)
2298 unsigned long new_char = 0;
2300 if (c >= 'a' && c <= 'f')
2301 digit = c + 0xa - 'a';
2302 else if (c >= 'A' && c <= 'F')
2303 digit = c + 0xa - 'A';
2304 else if (c >= '0' && c <= '9')
2305 digit = c + 0 - '0';
2308 s->entering_unicode_p = 0;
2312 if (s->entering_unicode_p == 1)
2314 else if (s->entering_unicode_p == 2)
2315 new_char = s->debug_metrics_p;
2317 new_char = (new_char << 4) | digit;
2318 if (new_char > 0 && new_char < max)
2320 s->debug_metrics_p = new_char;
2321 s->entering_unicode_p = 2;
2324 s->entering_unicode_p = 0;
2329 s->debug_metrics_antialiasing_p ^= True;
2330 else if (c == 3 || c == 27)
2333 s->debug_metrics_p = (unsigned char) c;
2334 else if (keysym == XK_Left || keysym == XK_Right)
2336 s->debug_metrics_p += (keysym == XK_Left ? -1 : 1);
2337 if (s->debug_metrics_p >= max)
2338 s->debug_metrics_p = 1;
2339 else if (s->debug_metrics_p <= 0)
2340 s->debug_metrics_p = max - 1;
2343 else if (keysym == XK_Prior)
2344 s->debug_metrics_p = (s->debug_metrics_p + max - 0x80) % max;
2345 else if (keysym == XK_Next)
2346 s->debug_metrics_p = (s->debug_metrics_p + 0x80) % max;
2347 else if (keysym == XK_Up)
2349 else if (keysym == XK_Down)
2350 s->debug_scale = (s->debug_scale > 1 ? s->debug_scale-1 : 1);
2351 else if (keysym == XK_F1)
2352 s->entering_unicode_p = 1;
2364 fontglide_reshape (Display *dpy, Window window, void *closure,
2365 unsigned int w, unsigned int h)
2367 state *s = (state *) closure;
2368 XGetWindowAttributes (s->dpy, s->window, &s->xgwa);
2370 if (s->dbuf && s->ba)
2372 XFreePixmap (s->dpy, s->ba);
2373 s->ba = XCreatePixmap (s->dpy, s->window,
2374 s->xgwa.width, s->xgwa.height,
2376 XFillRectangle (s->dpy, s->ba, s->bg_gc, 0, 0,
2377 s->xgwa.width, s->xgwa.height);
2383 fontglide_free (Display *dpy, Window window, void *closure)
2385 state *s = (state *) closure;
2386 textclient_close (s->tc);
2389 if (s->metrics_xftfont)
2390 XftFontClose (s->dpy, s->metrics_xftfont);
2391 if (s->metrics_font1)
2392 XFreeFont (s->dpy, s->metrics_font1);
2393 if (s->metrics_font2 && s->metrics_font1 != s->metrics_font2)
2394 XFreeFont (s->dpy, s->metrics_font2);
2395 if (s->prev_font_name) free (s->prev_font_name);
2396 if (s->next_font_name) free (s->next_font_name);
2397 if (s->label_gc) XFreeGC (dpy, s->label_gc);
2400 /* #### there's more to free here */
2406 static const char *fontglide_defaults [] = {
2407 ".background: #000000",
2408 ".foreground: #DDDDDD",
2409 ".borderColor: #555555",
2411 "*program: xscreensaver-text",
2416 /* I'm not entirely clear on whether the charset of an XLFD has any
2417 meaning when Xft is being used. */
2418 "*fontCharset: iso8859-1",
2419 /*"*fontCharset: iso10646-1", */
2420 /*"*fontCharset: *-*",*/
2422 "*fontBorderWidth: 2",
2428 "*debugMetrics: False",
2430 "*doubleBuffer: True",
2431 # ifdef HAVE_DOUBLE_BUFFER_EXTENSION
2433 "*useDBEClear: True",
2434 # endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
2438 static XrmOptionDescRec fontglide_options [] = {
2439 { "-mode", ".mode", XrmoptionSepArg, 0 },
2440 { "-scroll", ".mode", XrmoptionNoArg, "scroll" },
2441 { "-page", ".mode", XrmoptionNoArg, "page" },
2442 { "-random", ".mode", XrmoptionNoArg, "random" },
2443 { "-delay", ".delay", XrmoptionSepArg, 0 },
2444 { "-speed", ".speed", XrmoptionSepArg, 0 },
2445 { "-linger", ".linger", XrmoptionSepArg, 0 },
2446 { "-program", ".program", XrmoptionSepArg, 0 },
2447 { "-font", ".font", XrmoptionSepArg, 0 },
2448 { "-fn", ".font", XrmoptionSepArg, 0 },
2449 { "-bw", ".fontBorderWidth", XrmoptionSepArg, 0 },
2450 { "-trails", ".trails", XrmoptionNoArg, "True" },
2451 { "-no-trails", ".trails", XrmoptionNoArg, "False" },
2452 { "-db", ".doubleBuffer", XrmoptionNoArg, "True" },
2453 { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" },
2455 { "-debug", ".debug", XrmoptionNoArg, "True" },
2456 { "-debug-metrics", ".debugMetrics", XrmoptionNoArg, "True" },
2462 XSCREENSAVER_MODULE ("FontGlide", fontglide)