3D Text for Tk in Unix

GPS Tue Jun 18, 2002: The following code makes all text in Tk 3D. It only works in Unix, but the concept could be ported to Windows. It passes all ANSI-C tests with gcc 2.95.3. I tested it with OpenBSD 3.0 x86.

You can compile it with: cc -ansi -Wall -W -pedantic -pedantic-errors -I/usr/local/include -I/usr/X11R6/include -L/usr/local/lib -L/usr/X11R6/lib -lX11 -lm -ltcl83 -ltk83 3D_Tk_Text.c

It comes with an example built in that looks like this:

http://www.xmission.com/~georgeps/images/wiki/3D_Tk_Text.png

(This is a HACK. I wrote this years ago, and some systems may need the _ dropped in the dlsym usage. You may also find that -ansi disturbs the compilation. See the news:comp.windows.x archives for an example of someone doing something similar based on this code in Linux.)


  #include <stdio.h>
  #include <stdlib.h>
  #include <dlfcn.h>
  #include <tcl.h>
  #include <tk.h>
  #include <X11/X.h>
  #include <X11/Xlib.h>
  
  GC lightGC;
  GC darkGC;
  
  /*Make these typedefs because ANSI forbids assignment between function pointer and `void *' */
  typedef int (*oldXDrawText) (Display *dis, Drawable drawable, GC gc, int x, int y, XTextItem *item, int nItems);
  typedef int (*oldXDrawText16) (Display *dis, Drawable drawable, GC gc, int x, int y, XTextItem16 *item, int nItems);
  typedef int (*oldXDrawString) (Display *dis, Drawable drawable, GC gc, int x, int y, _Xconst char *string, int length);
  typedef int (*oldXDrawString16) (Display *dis, Drawable drawable, GC gc, int x, int y, _Xconst XChar2b *string, int length);
  
  oldXDrawText oldXDrawTextPtr;
  oldXDrawText16 oldXDrawText16Ptr;
  oldXDrawString oldXDrawStringPtr;
  oldXDrawString16 oldXDrawString16Ptr;
  
  int XDrawText (Display *dis, Drawable drawable, GC gc, int x, int y, XTextItem *item, int nItems) {
    XCopyGC (dis, gc, GCFont, lightGC);
    XCopyGC (dis, gc, GCFont, darkGC);
  
    (*oldXDrawTextPtr) (dis, drawable, lightGC, x - 1, y - 1, item, nItems);
    (*oldXDrawTextPtr) (dis, drawable, darkGC, x + 1, y + 1, item, nItems);
    return (*oldXDrawTextPtr) (dis, drawable, lightGC, x, y, item, nItems);
  }
  
  int XDrawText16 (Display *dis, Drawable drawable, GC gc, int x, int y, XTextItem16 *item, int nItems) {
    XCopyGC (dis, gc, GCFont, lightGC);
    XCopyGC (dis, gc, GCFont, darkGC);
  
    (*oldXDrawText16Ptr) (dis, drawable, lightGC, x - 1, y - 1, item, nItems);
    (*oldXDrawText16Ptr) (dis, drawable, darkGC, x + 1, y + 1, item, nItems);
    return (*oldXDrawText16Ptr) (dis, drawable, gc, x, y, item, nItems);
  }
  
  int XDrawString (Display *dis, Drawable drawable, GC gc, int x, int y, _Xconst char *string, int length) {
    XCopyGC (dis, gc, GCFont, lightGC);
    XCopyGC (dis, gc, GCFont, darkGC);
  
    (*oldXDrawStringPtr) (dis, drawable, lightGC, x - 1, y - 1, string, length);
    (*oldXDrawStringPtr) (dis, drawable, darkGC, x + 1, y + 1, string, length);
    return (*oldXDrawStringPtr) (dis, drawable, gc, x, y, string, length);
  }
  
  int XDrawString16 (Display *dis, Drawable drawable, GC gc, int x, int y, _Xconst XChar2b *string, int length) {
    XCopyGC (dis, gc, GCFont, lightGC);
    XCopyGC (dis, gc, GCFont, darkGC);
  
    (*oldXDrawString16Ptr) (dis, drawable, lightGC, x - 1, y - 1, string, length);
    (*oldXDrawString16Ptr) (dis, drawable, darkGC, x + 1, y + 1, string, length);
    return (*oldXDrawString16Ptr) (dis, drawable, gc, x, y, string, length);
  }
  
  int main (int argc, char *argv[]) {
    Tcl_Interp *interp;
    char evalStr[] = {
      "option add *font -*-lucida-medium-*-*-*-14-*-*-*-*-*-*-*;"
      "option add *Text.background white;"
      "option add *Text.foreground black;"
      "pack [frame .f] -side top -fill x;" 
      "pack [button .f.b -text {Hello World}] -side left;" 
      "pack [entry .f.e] -side left;"
      "pack [label .f.l -text {Sample Text}] -side left;"
      "pack [text .t] -side top -fill both;"
    };
    void *libHandle;
    
    Tk_Window tkwin;
    Window xWin;
    Display *dis;
    Colormap colormap;
    XColor xLightColor;
    char lightColor[] = "gray90";
    XColor xDarkColor;
    char darkColor[] = "gray30";
    int nScreen;
  
  
    Tcl_FindExecutable (argv[0]);
    interp = Tcl_CreateInterp ();
  
    if (Tcl_Init (interp) != TCL_OK) {
      fprintf (stderr, "Tcl_Init error %s", Tcl_GetStringResult (interp));
      exit (1);
    }
          
    if (Tk_Init (interp) != TCL_OK) {
      fprintf (stderr, "Tk_Init error %s", Tcl_GetStringResult (interp));
      exit (1);
    }
  
    tkwin = Tk_MainWindow (interp);
    dis = Tk_Display (tkwin);
    
    nScreen = DefaultScreen (dis);
    colormap = DefaultColormap (dis, nScreen);
  
    Tk_MakeWindowExist (tkwin);
    xWin = Tk_WindowId (tkwin);
  
    lightGC = XCreateGC (dis, xWin, 0, 0);
    darkGC = XCreateGC (dis, xWin, 0, 0);
  
  
    XParseColor (dis, colormap, lightColor, &xLightColor);
    XAllocColor (dis, colormap, &xLightColor);
    XSetForeground (dis, lightGC, xLightColor.pixel);
  
    XParseColor (dis, colormap, darkColor, &xDarkColor);
    XAllocColor (dis, colormap, &xDarkColor);
    XSetForeground (dis, darkGC, xDarkColor.pixel);
  
    libHandle = dlopen ("/usr/X11R6/lib/libX11.so.6.2", RTLD_LAZY);
    fprintf (stderr, "dlopen %s\n", dlerror ());
  
    oldXDrawTextPtr = (oldXDrawText) dlsym (libHandle, "_XDrawText");
    fprintf (stderr, "dlsym %s\n", dlerror ());
  
    oldXDrawText16Ptr = (oldXDrawText16) dlsym (libHandle, "_XDrawText16");
    fprintf (stderr, "dlsym %s\n", dlerror ());
  
    oldXDrawStringPtr = (oldXDrawString) dlsym (libHandle, "_XDrawString");
    fprintf (stderr, "dlsym %s\n", dlerror ());
  
    oldXDrawString16Ptr = (oldXDrawString16) dlsym (libHandle, "_XDrawString16");
    fprintf (stderr, "dlsym %s\n", dlerror ());
  
    if (Tcl_Eval (interp, evalStr) != TCL_OK) {
      fprintf (stderr, "error %s\n", Tcl_GetStringResult (interp));
    }
    Tk_MainLoop ();
  
    return 0;
  }

Cool. Except I'd want to shoot anybody who actually deployes 3D text in an application - GUI design 101.

DKF: It can work, but only if done subtly. Many of the fancy graphical effects (including virtually all alpha-blending effects as well as 3D text) need to be used with a very large helping of good taste and discretion or they fail to make the GUI more usable. They often work better with large items (windows, "headlines", etc.) than small.


DKF: You can do a similar thing on a canvas by just drawing the text twice (e.g. Unavailable appearance). That also lets you do extra cool effects, like text that jumps out at you when the mouse pointer goes over it...

See also Shaded text on canvas for a pure-Tcl solution.