1 /* texfonts, Copyright (c) 2005-2014 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * Renders X11 fonts into textures for use with OpenGL.
12 * A higher level API is in glxfonts.c.
28 # include <OpenGL/glu.h>
30 #elif defined(HAVE_ANDROID)
40 #endif /* HAVE_JWZGLES */
42 #ifdef HAVE_XSHM_EXTENSION
44 #endif /* HAVE_XSHM_EXTENSION */
47 #include "resources.h"
49 #include "fps.h" /* for current_device_rotation() */
51 #undef HAVE_XSHM_EXTENSION /* doesn't actually do any good here */
54 /* These are in xlock-gl.c */
55 extern void clear_gl_error (void);
56 extern void check_gl_error (const char *type);
59 extern char *progname;
61 /* LRU cache of textures, to optimize the case where we're drawing the
62 same strings repeatedly.
64 typedef struct texfont_cache texfont_cache;
65 struct texfont_cache {
73 struct texture_font_data {
82 #define countof(x) (sizeof((x))/sizeof((*x)))
85 /* return the next larger power of 2. */
89 static const unsigned int pow2[] = {
90 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
91 2048, 4096, 8192, 16384, 32768, 65536 };
93 for (j = 0; j < sizeof(pow2)/sizeof(*pow2); j++)
94 if (pow2[j] >= i) return pow2[j];
95 abort(); /* too big! */
99 /* Given a Pixmap (of screen depth), converts it to an OpenGL luminance mipmap.
100 RGB are averaged to grayscale, and the resulting value is treated as alpha.
101 Pass in the size of the pixmap; the size of the texture is returned
102 (it may be larger, since GL like powers of 2.)
104 We use a screen-depth pixmap instead of a 1bpp bitmap so that if the fonts
105 were drawn with antialiasing, that is preserved.
108 bitmap_to_texture (Display *dpy, Pixmap p, Visual *visual, int depth,
111 Bool mipmap_p = True;
114 int w2 = to_pow2 (ow);
115 int h2 = to_pow2 (oh);
116 int x, y, max, scale;
118 unsigned char *data = (unsigned char *) calloc (w2 * 2, (h2 + 1));
119 unsigned char *out = data;
121 /* If either dimension is larger than the supported size, reduce.
122 We still return the old size to keep the caller's math working,
123 but the texture itself will have fewer pixels in it.
125 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max);
127 while (w2 > max || h2 > max)
134 /* OpenGLES doesn't support GL_INTENSITY, so instead of using a
135 texture with 1 byte per pixel, the intensity value, we have
136 to use 2 bytes per pixel: solid white, and an alpha value.
143 GLuint iformat = GL_INTENSITY;
144 GLuint format = GL_LUMINANCE;
146 GLuint iformat = GL_LUMINANCE_ALPHA;
147 GLuint format = GL_LUMINANCE_ALPHA;
149 GLuint type = GL_UNSIGNED_BYTE;
151 # ifdef HAVE_XSHM_EXTENSION
152 Bool use_shm = get_boolean_resource (dpy, "useSHM", "Boolean");
153 XShmSegmentInfo shm_info;
154 # endif /* HAVE_XSHM_EXTENSION */
156 # ifdef HAVE_XSHM_EXTENSION
160 image = create_xshm_image (dpy, visual, depth, ZPixmap, 0, &shm_info,
163 XShmGetImage (dpy, p, image, 0, 0, ~0L);
167 # endif /* HAVE_XSHM_EXTENSION */
170 image = XGetImage (dpy, p, 0, 0, ow, oh, ~0L, ZPixmap);
173 /* This would work, but it's wasteful for no benefit. */
177 for (y = 0; y < h2; y++)
178 for (x = 0; x < w2; x++) {
179 /* Might be better to average a scale x scale square of source pixels,
180 but at the resolutions we're dealing with, this is probably good
184 unsigned long pixel = (sx >= ow || sy >= oh ? 0 :
185 XGetPixel (image, sx, sy));
186 /* instead of averaging all three channels, let's just use red,
187 and assume it was already grayscale. */
188 unsigned long r = pixel & visual->red_mask;
189 /* This goofy trick is to make any of RGBA/ABGR/ARGB work. */
190 pixel = ((r >> 24) | (r >> 16) | (r >> 8) | r) & 0xFF;
191 # ifndef GL_INTENSITY
192 *out++ = 0xFF; /* 2 bytes per pixel (luminance, alpha) */
197 # ifdef HAVE_XSHM_EXTENSION
199 destroy_xshm_image (dpy, image, &shm_info);
201 # endif /* HAVE_XSHM_EXTENSION */
202 XDestroyImage (image);
207 gluBuild2DMipmaps (GL_TEXTURE_2D, iformat, w2, h2, format, type, data);
209 glTexImage2D (GL_TEXTURE_2D, 0, iformat, w2, h2, 0, format, type, data);
213 sprintf (msg, "texture font %s (%d x %d)",
214 mipmap_p ? "gluBuild2DMipmaps" : "glTexImage2D",
216 check_gl_error (msg);
226 /* Loads the font named by the X resource "res" and returns
227 a texture-font object.
230 load_texture_font (Display *dpy, char *res)
232 int screen = DefaultScreen (dpy);
233 char *font = get_string_resource (dpy, res, "Font");
234 const char *def1 = "-*-helvetica-medium-r-normal-*-*-180-*-*-*-*-*-*";
235 const char *def2 = "-*-helvetica-medium-r-normal-*-*-140-*-*-*-*-*-*";
236 const char *def3 = "fixed";
238 texture_font_data *data;
239 int cache_size = get_integer_resource (dpy, "texFontCacheSize", "Integer");
241 /* Hacks that draw a lot of different strings on the screen simultaneously,
242 like Star Wars, should set this to a larger value for performance. */
246 if (!res || !*res) abort();
248 if (!strcmp (res, "fpsFont")) { /* Kludge. */
249 def1 = "-*-courier-bold-r-normal-*-*-140-*-*-*-*-*-*";
250 cache_size = 0; /* No need for a cache on FPS: already throttled. */
253 if (!font) font = strdup(def1);
255 f = XftFontOpenXlfd (dpy, screen, font);
256 if (!f && !!strcmp (font, def1))
258 fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
259 progname, font, def1);
261 font = strdup (def1);
262 f = XftFontOpenXlfd (dpy, screen, font);
265 if (!f && !!strcmp (font, def2))
267 fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
268 progname, font, def2);
270 font = strdup (def2);
271 f = XftFontOpenXlfd (dpy, screen, font);
274 if (!f && !!strcmp (font, def3))
276 fprintf (stderr, "%s: unable to load font \"%s\", using \"%s\"\n",
277 progname, font, def3);
279 font = strdup (def3);
280 f = XftFontOpenXlfd (dpy, screen, font);
285 fprintf (stderr, "%s: unable to load fallback font \"%s\" either!\n",
293 data = (texture_font_data *) calloc (1, sizeof(*data));
296 data->cache_size = cache_size;
302 /* Measure the string, or render it, depending on whether the XftDraw
306 iterate_texture_string (texture_font_data *data,
308 XftDraw *xftdraw, XftColor *xftcolor,
312 int line_height = data->xftfont->ascent + data->xftfont->descent;
316 Bool sub_p = False, osub_p = False;
317 int cw = 0, tabs = 0;
330 (*s == '[' && isdigit(s[1])) ||
331 (*s == ']' && sub_p))
336 XftTextExtentsUtf8 (data->dpy, data->xftfont,
337 (FcChar8 *) os, (int) (s - os),
353 /* Measure "m" to determine tab width. */
354 XftTextExtentsUtf8 (data->dpy, data->xftfont,
355 (FcChar8 *) "m", 1, &extents);
360 x = ((x + tabs) / tabs) * tabs;
362 else if (*s == '[' && isdigit(s[1]))
364 else if (*s == ']' && sub_p)
367 if (xftdraw && s != os)
368 XftDrawStringUtf8 (xftdraw, xftcolor, data->xftfont,
370 oy + (int) (osub_p ? line_height * 0.3 : 0),
371 (FcChar8 *) os, (int) (s - os));
387 /* Bounding box of the multi-line string, in pixels.
390 texture_string_width (texture_font_data *data, const char *s, int *height_ret)
392 return iterate_texture_string (data, s, 0, 0, 0, 0, height_ret);
396 static struct texfont_cache *
397 get_cache (texture_font_data *data, const char *string)
400 texfont_cache *prev = 0, *prev2 = 0, *curr = 0, *next = 0;
403 for (prev2 = 0, prev = 0, curr = data->cache, next = curr->next;
405 prev2 = prev, prev = curr, curr = next,
406 next = (curr ? curr->next : 0), count++)
408 if (!strcmp (string, curr->string))
411 prev->next = next; /* Unlink from list */
412 if (curr != data->cache)
414 curr->next = data->cache; /* Move to front */
421 /* Made it to the end of the list without a hit.
422 If the cache is full, empty out the last one on the list,
423 and move it to the front. Keep the texid.
425 if (count > data->cache_size)
435 if (prev != data->cache)
436 prev->next = data->cache;
441 /* Not cached, and cache not full. Add a new entry at the front,
442 and allocate a new texture for it.
444 curr = (struct texfont_cache *) calloc (1, sizeof(*prev));
445 glGenTextures (1, &curr->texid);
447 curr->next = data->cache;
454 /* Draws the string in the scene at the origin.
455 Newlines and tab stops are honored.
456 Any numbers inside [] will be rendered as a subscript.
457 Assumes the font has been loaded as with load_texture_font().
460 print_texture_string (texture_font_data *data, const char *string)
462 int line_height = data->xftfont->ascent + data->xftfont->descent;
463 int margin = line_height * 0.35;
466 XWindowAttributes xgwa;
468 texfont_cache *cache;
470 if (!*string) return;
472 cache = get_cache (data, string);
474 /* Measure the string and make a pixmap that will fit it,
479 width = data->cache->width;
480 height = data->cache->height;
484 Window window = RootWindow (data->dpy, 0);
488 XGetWindowAttributes (data->dpy, window, &xgwa);
489 width = iterate_texture_string (data, string, 0, 0, 0, 0, &height);
490 p = XCreatePixmap (data->dpy, window,
494 gcv.foreground = BlackPixelOfScreen (xgwa.screen);
495 gc = XCreateGC (data->dpy, p, GCForeground, &gcv);
496 XFillRectangle (data->dpy, p, gc, 0, 0,
499 XFreeGC (data->dpy, gc);
502 /* Draw the string into the pixmap, unless it's cached.
509 rcolor.red = rcolor.green = rcolor.blue = rcolor.alpha = 0xFFFF;
510 XftColorAllocValue (data->dpy, xgwa.visual, xgwa.colormap,
512 xftdraw = XftDrawCreate (data->dpy, p, xgwa.visual, xgwa.colormap);
513 iterate_texture_string (data, string, xftdraw, &xftcolor,
515 XftDrawDestroy (xftdraw);
516 XftColorFree (data->dpy, xgwa.visual, xgwa.colormap, &xftcolor);
522 Bool alpha_p, blend_p;
524 GLfloat qx0, qy0, qx1, qy1;
525 GLfloat tx0, ty0, tx1, ty1;
527 /* Save the prevailing texture environment, and set up ours.
529 glGetIntegerv (GL_TEXTURE_BINDING_2D, &old_texture);
530 glGetIntegerv (GL_FRONT_FACE, &ofront);
531 glGetIntegerv (GL_BLEND_DST, &oblend);
532 glGetFloatv (GL_TEXTURE_MATRIX, omatrix);
533 blend_p = glIsEnabled (GL_BLEND);
534 alpha_p = glIsEnabled (GL_ALPHA_TEST);
540 glNormal3f (0, 0, 1);
543 glMatrixMode (GL_TEXTURE);
545 glMatrixMode (GL_MODELVIEW);
547 glBindTexture (GL_TEXTURE_2D, cache->texid);
548 check_gl_error ("texture font binding");
550 glEnable(GL_TEXTURE_2D);
552 /* Copy the bits from the Pixmap into a texture, unless it's cached.
556 width2 = data->cache->width2;
557 height2 = data->cache->height2;
562 width2 = width + margin*2;
563 height2 = height + margin*2;
564 bitmap_to_texture (data->dpy, p, xgwa.visual, xgwa.depth,
566 XFreePixmap (data->dpy, p);
570 /* Texture-rendering parameters to make font pixmaps tolerable to look at.
573 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
574 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
575 GL_LINEAR_MIPMAP_LINEAR);
577 /* LOD bias is part of OpenGL 1.4.
578 GL_EXT_texture_lod_bias has been present since the original iPhone.
580 # if !defined(GL_TEXTURE_LOD_BIAS) && defined(GL_TEXTURE_LOD_BIAS_EXT)
581 # define GL_TEXTURE_LOD_BIAS GL_TEXTURE_LOD_BIAS_EXT
583 # ifdef GL_TEXTURE_LOD_BIAS
584 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.25);
586 clear_gl_error(); /* invalid enum on iPad 3 */
588 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
589 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
591 /* Don't write the transparent parts of the quad into the depth buffer. */
592 glAlphaFunc (GL_GREATER, 0.01);
593 glEnable (GL_ALPHA_TEST);
595 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
597 /* Draw a quad with that texture on it, possibly using a cached texture.
600 qy0 = line_height + margin;
601 qx1 = width + margin;
602 qy1 = line_height - height - margin;
606 tx1 = (width + margin*2) / (GLfloat) width2;
607 ty1 = (height + margin*2) / (GLfloat) height2;
610 glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
611 glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
612 glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
613 glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
618 /* Reset to the caller's texture environment.
620 glBindTexture (GL_TEXTURE_2D, old_texture);
621 glFrontFace (ofront);
622 if (!alpha_p) glDisable (GL_ALPHA_TEST);
623 if (!blend_p) glDisable (GL_BLEND);
624 glBlendFunc (GL_SRC_ALPHA, oblend);
626 glMatrixMode (GL_TEXTURE);
627 glMultMatrixf (omatrix);
628 glMatrixMode (GL_MODELVIEW);
630 check_gl_error ("texture font print");
632 /* Store this string into the cache, unless that's where it came from.
636 cache->string = strdup (string);
637 cache->width = width;
638 cache->height = height;
639 cache->width2 = width2;
640 cache->height2 = height2;
646 /* Draws the string on the window at the given pixel position.
647 Newlines and tab stops are honored.
648 Any numbers inside [] will be rendered as a subscript.
649 Assumes the font has been loaded as with load_texture_font().
651 Position is 0 for center, 1 for top left, 2 for bottom left.
654 print_texture_label (Display *dpy,
655 texture_font_data *data,
656 int window_width, int window_height,
662 Bool tex_p = glIsEnabled (GL_TEXTURE_2D);
663 Bool texs_p = glIsEnabled (GL_TEXTURE_GEN_S);
664 Bool text_p = glIsEnabled (GL_TEXTURE_GEN_T);
665 Bool light_p = glIsEnabled (GL_LIGHTING);
666 Bool depth_p = glIsEnabled (GL_DEPTH_TEST);
667 Bool cull_p = glIsEnabled (GL_CULL_FACE);
668 Bool fog_p = glIsEnabled (GL_FOG);
671 # ifndef HAVE_JWZGLES
673 glGetIntegerv (GL_POLYGON_MODE, opoly);
676 glGetIntegerv (GL_VIEWPORT, ovp);
678 glGetFloatv (GL_CURRENT_COLOR, color);
680 glEnable (GL_TEXTURE_2D);
681 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
682 glPolygonMode (GL_FRONT, GL_FILL);
684 glDisable (GL_TEXTURE_GEN_S);
685 glDisable (GL_TEXTURE_GEN_T);
686 glDisable (GL_LIGHTING);
687 glDisable (GL_CULL_FACE);
690 glDisable (GL_DEPTH_TEST);
692 /* Each matrix mode has its own stack, so we need to push/pop
695 glMatrixMode(GL_PROJECTION);
700 glMatrixMode(GL_MODELVIEW);
703 int x, y, w, h, lh, swap;
704 int rot = (int) current_device_rotation();
707 glViewport (0, 0, window_width, window_height);
708 glOrtho (0, window_width, 0, window_height, -1, 1);
710 while (rot <= -180) rot += 360;
711 while (rot > 180) rot -= 360;
713 lh = texture_string_width (data, "M", 0);
714 w = texture_string_width (data, string, &h);
716 if (rot > 135 || rot < -135) /* 180 */
718 glTranslatef (window_width, window_height, 0);
719 glRotatef (180, 0, 0, 1);
721 else if (rot > 45) /* 90 */
723 glTranslatef (window_width, 0, 0);
724 glRotatef (90, 0, 0, 1);
726 window_width = window_height;
727 window_height = swap;
729 else if (rot < -45) /* 270 */
731 glTranslatef(0, window_height, 0);
732 glRotatef (-90, 0, 0, 1);
734 window_width = window_height;
735 window_height = swap;
740 x = (window_width - w) / 2;
741 y = (window_height - h) / 2;
745 y = window_height - lh * 2;
755 glTranslatef (x, y, 0);
757 /* draw the text five times, to give it a border. */
759 const XPoint offsets[] = {{ -1, -1 },
767 for (i = 0; i < countof(offsets); i++)
769 if (offsets[i].x == 0)
772 glTranslatef (offsets[i].x, offsets[i].y, 0);
773 print_texture_string (data, string);
780 glMatrixMode(GL_PROJECTION);
783 if (tex_p) glEnable (GL_TEXTURE_2D); else glDisable (GL_TEXTURE_2D);
784 if (texs_p) glEnable (GL_TEXTURE_GEN_S);/*else glDisable(GL_TEXTURE_GEN_S);*/
785 if (text_p) glEnable (GL_TEXTURE_GEN_T);/*else glDisable(GL_TEXTURE_GEN_T);*/
786 if (light_p) glEnable (GL_LIGHTING); /*else glDisable (GL_LIGHTING);*/
787 if (depth_p) glEnable (GL_DEPTH_TEST); else glDisable (GL_DEPTH_TEST);
788 if (cull_p) glEnable (GL_CULL_FACE); /*else glDisable (GL_CULL_FACE);*/
789 if (fog_p) glEnable (GL_FOG); /*else glDisable (GL_FOG);*/
791 glViewport (ovp[0], ovp[1], ovp[2], ovp[3]);
793 # ifndef HAVE_JWZGLES
794 glPolygonMode (GL_FRONT, opoly[0]);
797 glMatrixMode(GL_MODELVIEW);
801 /* Releases the font and texture.
804 free_texture_font (texture_font_data *data)
808 texfont_cache *next = data->cache->next;
809 glDeleteTextures (1, &data->cache->texid);
814 XftFontClose (data->dpy, data->xftfont);