2 * fliptext, Copyright (c) 2005-2015 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
15 #endif /* HAVE_CONFIG_H */
17 #define DEF_FONT "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-*-*"
18 #define DEF_COLOR "#00CCFF"
20 #define DEFAULTS "*delay: 10000 \n" \
21 "*showFPS: False \n" \
22 "*wireframe: False \n" \
24 "*texFontCacheSize: 60 \n" \
25 "*font: " DEF_FONT "\n" \
26 ".foreground: " DEF_COLOR "\n" \
27 "*program: xscreensaver-text --cols 0" /* don't wrap */
29 # define release_fliptext 0
30 # define fliptext_handle_event xlockmore_no_events
32 #define countof(x) (sizeof((x))/sizeof((*x)))
35 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
37 #include "xlockmore.h"
39 #include "textclient.h"
41 #ifdef USE_GL /* whole file */
43 /* Should be in <GL/glext.h> */
44 # ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
45 # define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
47 # ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
48 # define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
53 #define DEF_FONT_SIZE "20"
54 #define DEF_COLUMNS "80"
55 #define DEF_ALIGNMENT "random"
56 #define DEF_SPEED "1.0"
59 #define FONT_WEIGHT 14
62 typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state;
63 typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type;
65 typedef struct { GLfloat x, y, z; } XYZ;
69 GLfloat width, height; /* size */
70 XYZ from, to, current; /* start, end, and current position */
71 GLfloat fth, tth, cth; /* rotation around Z */
73 int cluster_size; /* how many lines in this cluster */
74 int cluster_pos; /* position of this line in the cluster */
76 line_state state; /* current motion model */
77 int step, steps; /* progress along this path */
85 GLXContext *glx_context;
87 texture_font_data *texfont;
96 int char_width; /* in font units */
97 int line_height; /* in font units */
98 double font_scale; /* convert font units to display units */
100 int font_wrap_pixels; /* in font units (for wrapping text) */
102 int top_margin, bottom_margin;
103 int left_margin, right_margin;
109 line_anim_type anim_type;
114 } fliptext_configuration;
117 static fliptext_configuration *scs = NULL;
119 static int max_lines, min_lines;
120 static float font_size;
121 static int target_columns;
122 static char *alignment_str;
123 static int alignment_random_p;
124 static GLfloat speed;
126 static XrmOptionDescRec opts[] = {
127 {"-lines", ".lines", XrmoptionSepArg, 0 },
128 {"-size", ".fontSize", XrmoptionSepArg, 0 },
129 {"-columns", ".columns", XrmoptionSepArg, 0 },
130 {"-speed", ".speed", XrmoptionSepArg, 0 },
131 /*{"-font", ".font", XrmoptionSepArg, 0 },*/
132 {"-alignment", ".alignment", XrmoptionSepArg, 0 },
133 {"-left", ".alignment", XrmoptionNoArg, "Left" },
134 {"-right", ".alignment", XrmoptionNoArg, "Right" },
135 {"-center", ".alignment", XrmoptionNoArg, "Center" },
138 static argtype vars[] = {
139 {&max_lines, "lines", "Integer", DEF_LINES, t_Int},
140 {&font_size, "fontSize", "Float", DEF_FONT_SIZE, t_Float},
141 {&target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
142 {&alignment_str, "alignment", "Alignment", DEF_ALIGNMENT, t_String},
143 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
146 ENTRYPOINT ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL};
150 /* Tabs are bad, mmmkay? */
153 untabify (const char *string)
155 const char *ostring = string;
156 char *result = (char *) malloc ((strlen(string) * 8) + 1);
166 } while (col % TAB_WIDTH);
169 else if (*string == '\r' || *string == '\n')
174 else if (*string == '\010') /* backspace */
176 if (string > ostring)
191 strip (char *s, Bool leading, Bool trailing)
195 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
200 while (*s2 == ' ' || *s2 == '\t')
212 char_width (fliptext_configuration *sc, char c)
218 texture_string_metrics (sc->texfont, s, &e, 0, 0);
223 /* Returns a single line of text from the output buffer of the subprocess,
224 taking into account wrapping, centering, etc. Returns 0 if no complete
225 line is currently available.
228 get_one_line (fliptext_configuration *sc)
231 int wrap_pix = sc->font_wrap_pixels;
235 int target = sc->buf_size - sc->buf_tail - 2;
237 /* Fill as much as we can into sc->buf, but stop at newline.
241 int c = textclient_getc (sc->tc);
244 sc->buf[sc->buf_tail++] = (char) c;
245 sc->buf[sc->buf_tail] = 0;
247 if (c == '\r' || c == '\n')
255 if (s >= sc->buf + sc->buf_tail)
256 /* Reached end of buffer before end of line. Bail. */
259 cw = char_width (sc, *s);
261 if (*s == '\r' || *s == '\n' ||
262 col_pix + cw >= wrap_pix)
266 if (*s == '\r' || *s == '\n')
268 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
275 /* We wrapped -- try to back up to the previous word boundary. */
278 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
289 result = (char *) malloc (L+1);
290 memcpy (result, sc->buf, L);
295 char *ut = untabify (t);
296 strip (ut, (sc->alignment == 0), 1); /* if centering, strip
297 leading whitespace too */
302 if (sc->buf_tail > (s - sc->buf))
304 int i = sc->buf_tail - (s - sc->buf);
305 memmove (sc->buf, s, i);
307 sc->buf[sc->buf_tail] = 0;
314 sc->buf[sc->buf_tail] = 0;
325 int tab_pix = TAB_WIDTH * sc->char_width;
326 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
327 col_pix = tab_pix * ((col / tab_pix) + 1);
338 blank_p (const char *s)
341 if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
346 /* Reads some text from the subprocess, and creates and returns a `line'
347 object. Adds that object to the lines list. Returns 0 if no text
350 If skip_blanks_p, then keep trying for new lines of text until we
351 get one that is not empty.
354 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
361 s = get_one_line (sc);
362 if (s && skip_blanks_p && blank_p (s))
370 ln = (line *) calloc (1, sizeof(*ln));
373 texture_string_metrics (sc->texfont, s, &e, 0, 0);
374 ln->width = sc->font_scale * e.width;
375 ln->height = sc->font_scale * sc->line_height;
377 memcpy (ln->color, sc->color, sizeof(ln->color));
380 if (sc->lines_size <= sc->nlines)
382 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
383 sc->lines = (line **)
384 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
387 fprintf (stderr, "%s: out of memory (%d lines)\n",
388 progname, sc->lines_size);
393 sc->lines[sc->nlines-1] = ln;
398 /* frees the object and removes it from the list.
401 free_line (fliptext_configuration *sc, line *line)
404 for (i = 0; i < sc->nlines; i++)
405 if (sc->lines[i] == line)
407 if (i == sc->nlines) abort();
408 for (; i < sc->nlines-1; i++)
409 sc->lines[i] = sc->lines[i+1];
419 draw_line (ModeInfo *mi, line *line)
421 int wire = MI_IS_WIREFRAME(mi);
422 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
424 if (! line->text || !*line->text ||
425 line->state == NEW || line->state == HESITATE || line->state == DEAD)
429 glTranslatef (line->current.x, line->current.y, line->current.z);
431 glRotatef (line->cth, 0, 1, 0);
433 if (sc->alignment == 1)
434 glTranslatef (-line->width, 0, 0);
435 else if (sc->alignment == 0)
436 glTranslatef (-line->width/2, 0, 0);
438 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
440 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
443 print_texture_string (sc->texfont, line->text);
447 char *s = line->text;
450 glDisable (GL_TEXTURE_2D);
451 glColor3f (0.4, 0.4, 0.4);
456 texture_string_metrics (sc->texfont, c, &e, 0, 0);
458 h = e.ascent + e.descent;
459 glBegin (GL_LINE_LOOP);
460 glVertex3f (0, 0, 0);
461 glVertex3f (w, 0, 0);
462 glVertex3f (w, h, 0);
463 glVertex3f (0, h, 0);
465 glTranslatef (w, 0, 0);
470 glDisable (GL_TEXTURE_2D);
471 glColor3f (0.4, 0.4, 0.4);
472 glBegin (GL_LINE_LOOP);
473 glVertex3f (0, 0, 0);
474 glVertex3f (line->width/sc->font_scale, 0, 0);
475 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
476 glVertex3f (0, line->height/sc->font_scale, 0);
478 if (!wire) glEnable (GL_TEXTURE_2D);
483 mi->polygon_count += strlen (line->text);
487 tick_line (fliptext_configuration *sc, line *line)
489 int stagger = 30; /* frames of delay between line spin-outs */
490 int slide = 600; /* frames in a slide in/out */
491 int linger = 0; /* frames to pause with no motion */
494 if (line->state >= DEAD) abort();
495 if (++line->step >= line->steps)
500 if (linger == 0 && line->state == LINGER)
503 if (sc->anim_type != SPIN)
508 case HESITATE: /* entering state HESITATE */
509 switch (sc->anim_type)
512 line->steps = (line->cluster_pos * stagger);
515 line->steps = stagger * (line->cluster_size - line->cluster_pos);
518 line->steps = stagger * line->cluster_pos;
527 switch (sc->anim_type)
529 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
532 line->from.y = (sc->bottom_margin -
534 (line->cluster_pos + 1)));
535 line->to.y += (line->height *
536 ((line->cluster_size/2.0) - line->cluster_pos));
540 case SCROLL_TOP: /* entering state TOP IN */
543 line->from.y = (sc->top_margin +
545 (line->cluster_size - line->cluster_pos)));
546 line->to.y += (line->height *
547 ((line->cluster_size/2.0) - line->cluster_pos));
551 case SPIN: /* entering state SPIN IN */
554 line->to.y += (line->height *
555 ((line->cluster_size/2.0) - line->cluster_pos));
556 line->from.y += (line->height *
557 ((line->cluster_size/2.0) - line->cluster_pos));
570 switch (sc->anim_type)
572 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
573 line->from = line->to;
575 line->to.y = (sc->top_margin +
577 (line->cluster_size - line->cluster_pos)));
581 case SCROLL_TOP: /* entering state TOP OUT */
582 line->from = line->to;
584 line->to.y = (sc->bottom_margin -
586 (line->cluster_pos + 1)));
590 case SPIN: /* entering state SPIN OUT */
591 line->from = line->to;
593 line->to.y += (line->height *
594 ((line->cluster_size/2.0) - line->cluster_pos));
596 line->fth = line->tth;
607 line->from = line->to;
608 line->steps = linger;
615 line->steps /= speed;
622 i = (double) line->step / line->steps;
624 /* Move along the path exponentially, slow side towards the middle. */
625 if (line->state == OUT)
628 ii = 1 - ((1-i) * (1-i));
630 line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
631 line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
632 line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
633 line->cth = line->fth + (ii * (line->tth - line->fth));
635 if (line->state == OUT) ii = 1-ii;
636 line->color[3] = sc->color[3] * ii;
649 /* Start a new cluster of lines going.
650 Pick their anim type, and in, mid, and out positions.
653 reset_lines (ModeInfo *mi)
655 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
658 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
660 sc->rotation.x = 5 - BELLRAND(10);
661 sc->rotation.y = 5 - BELLRAND(10);
662 sc->rotation.z = 5 - BELLRAND(10);
664 switch (random() % 8)
666 case 0: sc->anim_type = SCROLL_TOP; break;
667 case 1: sc->anim_type = SCROLL_BOTTOM; break;
668 default: sc->anim_type = SPIN; break;
671 minx = sc->left_margin * 0.9;
672 maxx = sc->right_margin * 0.9;
674 miny = sc->bottom_margin * 0.9;
675 maxy = sc->top_margin * 0.9;
677 minz = sc->left_margin * 5;
678 maxz = sc->right_margin * 2;
680 maxw = sc->font_wrap_pixels * sc->font_scale;
681 maxh = max_lines * sc->line_height * sc->font_scale;
683 if (maxw > maxx - minx)
685 if (maxh > maxy - miny)
688 if (alignment_random_p)
689 sc->alignment = (random() % 3) - 1;
691 if (sc->alignment == -1) maxx -= maxw;
692 else if (sc->alignment == 1) minx += maxw;
693 else minx += maxw/2, maxx -= maxw/2;
698 sc->mid.x = minx + frand (maxx - minx);
699 if (sc->anim_type == SPIN)
700 sc->mid.y = miny + BELLRAND (maxy - miny);
702 sc->mid.y = miny + frand (maxy - miny);
704 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
705 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
707 sc->in.y = miny + frand(maxy - miny);
708 sc->out.y = miny + frand(maxy - miny);
710 sc->in.z = minz + frand(maxz - minz);
711 sc->out.z = minz + frand(maxz - minz);
715 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
716 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
718 for (i = 0; i < max_lines; i++)
720 line *line = make_line (sc, (i == 0));
721 if (!line) break; /* no text available */
722 if (i >= min_lines &&
723 (!line->text || !*line->text)) /* blank after min */
727 for (i = 0; i < sc->nlines; i++)
729 line *line = sc->lines[i];
732 line->from.y = sc->bottom_margin;
737 line->from.y = prev->from.y - prev->height;
738 line->to.y = prev->to.y - prev->height;
740 line->cluster_pos = i;
741 line->cluster_size = sc->nlines;
748 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
751 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
753 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
756 a[0] = c.red / 65536.0;
757 a[1] = c.green / 65536.0;
758 a[2] = c.blue / 65536.0;
763 /* Window management, etc
766 reshape_fliptext (ModeInfo *mi, int width, int height)
768 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
769 GLfloat h = (GLfloat) height / (GLfloat) width;
772 if (width > height * 5) { /* tiny window: show middle */
773 height = width * 9/16;
775 h = height / (GLfloat) width;
778 glViewport (0, y, (GLint) width, (GLint) height);
780 glMatrixMode(GL_PROJECTION);
782 gluPerspective (60.0, 1/h, 0.01, 100.0);
784 glMatrixMode(GL_MODELVIEW);
786 gluLookAt( 0.0, 0.0, 2.6,
790 glClear(GL_COLOR_BUFFER_BIT);
792 sc->right_margin = sc->top_margin / h;
793 sc->left_margin = -sc->right_margin;
798 init_fliptext (ModeInfo *mi)
800 int wire = MI_IS_WIREFRAME(mi);
802 fliptext_configuration *sc;
806 sc = &scs[MI_SCREEN(mi)];
807 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
809 sc->dpy = MI_DISPLAY(mi);
811 if ((sc->glx_context = init_GL(mi)) != NULL) {
812 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
813 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
818 int cw, lh, ascent, descent;
819 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
820 check_gl_error ("loading font");
821 texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
823 lh = ascent + descent;
825 sc->line_height = lh;
830 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
832 glEnable (GL_ALPHA_TEST);
833 glEnable (GL_TEXTURE_2D);
835 /* "Anistropic filtering helps for quadrilateral-angled textures.
836 A sharper image is accomplished by interpolating and filtering
837 multiple samples from one or more mipmaps to better approximate
838 very distorted textures. This is the next level of filtering
839 after trilinear filtering." */
840 if (strstr ((char *) glGetString(GL_EXTENSIONS),
841 "GL_EXT_texture_filter_anisotropic"))
843 GLfloat anisotropic = 0.0;
844 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
845 if (anisotropic >= 1.0)
846 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
851 /* The default font is (by fiat) "18 points".
852 Interpret the user's font size request relative to that.
854 sc->font_scale = 3 * (font_size / 18.0);
856 if (target_columns <= 2) target_columns = 2;
858 /* Figure out what the wrap column should be, in font-coordinate pixels.
859 Compute it from the given -columns value, but don't let it be wider
863 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
864 sc->font_wrap_pixels = target_columns * sc->char_width;
865 if (sc->font_wrap_pixels > maxw ||
866 sc->font_wrap_pixels <= 0)
867 sc->font_wrap_pixels = maxw;
870 sc->buf_size = target_columns * max_lines;
871 sc->buf = (char *) calloc (1, sc->buf_size);
873 alignment_random_p = False;
874 if (!alignment_str || !*alignment_str ||
875 !strcasecmp(alignment_str, "left"))
877 else if (!strcasecmp(alignment_str, "center") ||
878 !strcasecmp(alignment_str, "middle"))
880 else if (!strcasecmp(alignment_str, "right"))
882 else if (!strcasecmp(alignment_str, "random"))
883 sc->alignment = -1, alignment_random_p = True;
888 "%s: alignment must be left/center/right/random, not \"%s\"\n",
889 progname, alignment_str);
893 sc->tc = textclient_open (sc->dpy);
895 if (max_lines < 1) max_lines = 1;
896 min_lines = max_lines * 0.66;
897 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
898 if (min_lines < 1) min_lines = 1;
900 parse_color (mi, "foreground",
901 get_string_resource(mi->dpy, "foreground", "Foreground"),
904 sc->top_margin = (sc->char_width * 100);
905 sc->bottom_margin = -sc->top_margin;
906 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
911 draw_fliptext (ModeInfo *mi)
913 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
914 /* XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
915 Display *dpy = MI_DISPLAY(mi);
916 Window window = MI_WINDOW(mi);
919 if (!sc->glx_context)
922 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
925 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
926 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
929 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
931 mi->polygon_count = 0;
934 glRotatef(current_device_rotation(), 0, 0, 1);
936 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
940 glRotatef (sc->rotation.x, 1, 0, 0);
941 glRotatef (sc->rotation.y, 0, 1, 0);
942 glRotatef (sc->rotation.z, 0, 0, 1);
945 glDisable (GL_TEXTURE_2D);
947 glBegin (GL_LINE_LOOP);
948 glVertex3f (sc->left_margin, sc->top_margin, 0);
949 glVertex3f (sc->right_margin, sc->top_margin, 0);
950 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
951 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
954 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
955 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
956 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
957 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
958 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
959 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
961 glEnable (GL_TEXTURE_2D);
964 for (i = 0; i < sc->nlines; i++)
966 line *line = sc->lines[i];
967 draw_line (mi, line);
968 tick_line (sc, line);
971 for (i = sc->nlines-1; i >= 0; i--)
973 line *line = sc->lines[i];
974 if (line->state == DEAD)
975 free_line (sc, line);
983 if (mi->fps_p) do_fps (mi);
985 glXSwapBuffers(dpy, window);
989 free_fliptext (ModeInfo *mi)
991 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
993 textclient_close (sc->tc);
996 /* #### there's more to free here */
999 XSCREENSAVER_MODULE ("FlipText", fliptext)