Game Development Community

dev|Pro Game Development Curriculum

Using Xft Fonts on linux

by Gregory "Centove" McLean · 10/24/2005 (8:03 am) · 3 comments

Download Code File

Recently I upgraded my X Window install and somehow managed to hose up my font configuration and broke torque in the process! I went digging and found the font code on linux was fragile at best. Here is my attempt to improve things a bit. This will require that you are using Xfree 4.0 or Xorg 6.0 or higher.

To install (on a fresh torque 1.3 install):
Copy the existing x86UNIXFont.cc somewhere (x86UNIXFont.cc.orig)

Replace it with the attached code file.

Edit the mk/conf.UNIX.mk file
CFLAGS.GENERAL    = -DUSE_FILE_REDIRECT -I/usr/X11R6/include/  -MD -march=i586 \
becomes
CFLAGS.GENERAL    = -DUSE_FILE_REDIRECT -I/usr/X11R6/include/ 'freetype-config --cflags' -MD -march=i586 \

and
LINK.LIBS.RELEASE = 
LINK.LIBS.DEBUG   =
becomes
LINK.LIBS.RELEASE = -lXft
LINK.LIBS.DEBUG   = -lXft

save that file and build the engine.

then from the example directory
torque-1.3/example $ rm common/ui/cache/*
torque-1.3/example $ ./torqueDemo.bin -nohomedir

Now enjoy your newly rendered fonts.

Here is the file:
//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------



#include "platformX86UNIX/platformX86UNIX.h"
#include "dgl/gFont.h"
#include "dgl/gBitmap.h"
#include "math/mRect.h"
#include "console/console.h"

// Needed by createFont
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/Xft/Xft.h>
#include <X11/extensions/Xrender.h>      // For XRenderColor

// Needed for getenv in createFont
#include <stdlib.h>

XftFont *loadFont(const char *name, S32 size, Display *display)
{
  XftFont *fontInfo = NULL;
  char* fontname = const_cast<char*>(name);
  if (dStrlen(fontname)==0)
    fontname = "arial";
  else if (stristr(const_cast<char*>(name), "arial") != NULL)
    fontname = "arial";
  else if (stristr(const_cast<char*>(name), "lucida console") != NULL)
    fontname = "lucida console";
  
  char* weight = "medium";
  char* slant = "roman"; // no slant
  
  if (stristr(const_cast<char*>(name), "bold") != NULL)
    weight = "bold";
  if (stristr(const_cast<char*>(name), "italic") != NULL)
    slant = "italic";
  
  char xfontName[512];
  // We specify a lower DPI to get 'correct' looking fonts, if we go with the
  // native DPI the fonts are to big and don't fit the widgets.
  dSprintf(xfontName, 512, "%s-%d:%s:slant=%s:dpi=65", fontname, size, weight, slant);
  
  // Lets see if Xft can get a font for us.
  char xftname[1024];
  fontInfo = XftFontOpenName(display, DefaultScreen(display), xfontName);
  if (fontInfo)
    {
      XftNameUnparse(fontInfo->pattern, xftname, 1024);
      Con::printf("Font '%s %d' mapped to '%s'\n", name, size, xftname);
    }
  return fontInfo;
}
GFont *createFont(const char *name, dsize_t size)
{
  // Our connection to the X Server, can't do anything without this
  Display *display = XOpenDisplay(getenv("DISPLAY"));
  if (!display)
    AssertFatal(false, "createFont: cannot connect to X server");
  XftFont *font = loadFont(name, size, display);
  if (!font) // This should almost never trigger anymore.
    AssertFatal(false, "createFont: cannot load font");
  int screen = DefaultScreen(display);
  int width, height;
  width = font->max_advance_width;
  height = font->height;
  // Create the pixmap to draw on.
  Pixmap pixmap = XCreatePixmap(display, 
				DefaultRootWindow(display),
				width, 
				height,
				DefaultDepth(display, screen));
  // And the Xft wrapper around it.
  XftDraw *draw = XftDrawCreate(display, 
				pixmap,
				DefaultVisual(display, screen),
				DefaultColormap(display, screen));
  // Allocate some colors, we don't use XftColorAllocValue here as that
  // Don't appear to function correctly (or I'm using it wrong) As we only do
  // this twice per new un cached font it isn't that big of a penalty. (Each
  // call to XftColorAllocName involves a round trip to the X Server)
  XftColor black, white;
  XftColorAllocName(display,
		    DefaultVisual(display, screen),
		    DefaultColormap(display, screen),
		    "black",
		    &black);
  // White
  XftColorAllocName(display,
		    DefaultVisual(display, screen),
		    DefaultColormap(display, screen),
		    "white",
		    &white);
  
  // The font.
  GFont *retFont = new GFont;
  static U8 scratchPad[65536];
  int x = 0;
  int y = 0;
  // Build the bitmaps
  for (U16 i = 32; i < 256; i++)
    {
      XGlyphInfo extent;
      FT_UInt glyph;
      if (!XftCharExists(display, font, i))
	{
	  retFont->insertBitmap(i, scratchPad, 0, 0, 0, 0, 0, width);
	  continue;
	}
      // Get the glyph and its extents.
      glyph = XftCharIndex(display, font, i);
      XftGlyphExtents (display, font, &glyph, 1, &extent);
      // Clear the bounding box and draw the glyph
      XftDrawRect (draw, &black, 0, 0, width, height);
      XftDrawGlyphs (draw, &white, font, 0, font->ascent, &glyph, 1);
      // Grab the rendered image ...
      XImage *ximage = XGetImage(display, pixmap, 0, 0, 
				 extent.xOff, height, 
				 AllPlanes, XYPixmap);
      if (ximage == NULL)
	AssertFatal(false, "cannot get x image");
      // And store each pixel in the scratchPad for insertion into the bitmap.
      // We grab the full height of the pixmap.
      for(y = 0; y < height; y++)
	{
	  // and the width of the glyph and its padding.
	  for(x = 0; x < extent.xOff; x++)
	    {
	      U8 val = static_cast<U8>(XGetPixel(ximage, x, y));
	      scratchPad[y * extent.xOff + x] = val;
	    }
	}
      // Done with the image.
      XDestroyImage(ximage);
      // Add it to the bitmap.
      retFont->insertBitmap(i,                   // index
			    scratchPad,          // src
			    extent.xOff,         // stride
			    extent.xOff,         // width
			    height,              // height 
			    0,                   // xOrigin
			    font->ascent,        // yOrigin
			    extent.xOff);        // xIncrement

    }
  retFont->pack(height, font->ascent);
  XftColorFree(display, DefaultVisual(display, screen), 
	       DefaultColormap(display, screen), &black);
  XftColorFree(display, DefaultVisual(display, screen),
	       DefaultColormap(display, screen), &white);
  XftDrawDestroy(draw);
  XFreePixmap(display, pixmap);
  // XftFontClose(display, font); //-- Why do we crash on this?
  XCloseDisplay(display);
  return retFont;
}

Better screenshots:
Old XFLD rendering (Xlib) slyvester.gxsnmp.org/torque/screenshots/oldfont-screenshot.jpg
XFT Rendering slyvester.gxsnmp.org/torque/screenshots/font-screenshot.jpg

#1
10/31/2005 (8:24 am)
great resource!

notice issues loading fonts, one solution would be to use the orig loadFont() to find a standard xLib font and save its name then use the xFont call to load a XLFD (uses old style naming etc..) and then use all the xFt calls to do the font rendering. This has fixed the font rending issue on my ubuntoo box, not sure why xFt can not find the stock fonts. Kind of figured if I have the issue then someone else will as well..

-Ron
#2
11/24/2005 (4:21 pm)
The only issue I bumped into was getting fonts that were waaay to big, I haven't figured out the correct way to scale things from what windows uses as a DPI and what X uses as a DPI. I however always got _a_ font which is one issue I was trying to solve with this.
#3
01/01/2008 (12:54 am)
do I really need that line if im making dedicated?

I managed to link without it.