[comp.windows.x] drawing rotated text

bill@mica.stat.washington.edu (Bill Dunlap) (11/08/89)

Several people have asked about drawing rotated text strings in X.
Here is my attempt at doing it.  It seems to work.  It also seems
clumsy.  That may be my fault; it may be X's.  This just draws
test rotated 90 degrees clockwise (to label the y axis of a plot).
It is straighforward to modify it to do arbitrary transformations
of the rendered text string.

If anyone has a simpler, but general, way to do this please send
me a note.

	Bill

------cut here----
/* Notes :
 *	Text90() draws a text string rotated by 90 degrees on the given
 *	drawable and given gc.  The gc is not modified.  Text90() respects
 *	the Foreground, ClipMask, and ClipOrigin components of the
 *	gc.  You must pass the Fontstruct separately because Text90()
 *	needs to determine text extents from the gc alone.
 *
 *	The general scheme is as follows.  First make a 1 bit deep pixmap
 *	and render the text using the usual XDrawString() function.
 *	Then use XGetImage() to pull that back to the client, XCreateImage()
 *      another XImage and put the rotated points from the gotten image
 *	into it.  XPutImage() the rotated image back to another 1 bit
 *	deep pixmap on the server.  Now, if there were not a ClipMask
 *	set in the gc, we could use this pixmap with the rotated image
 *	('rot_pm') as a ClipMask in gc and use FillRectangle on the
 *	given drawable.  But this would ignore and destroy the ClipMask
 *	in the given gc.  So we make another pixmap ('deep_pm'), as deep
 *	as the given drawable, XCopyArea() the relevant stuff in the
 *	drawable into deep_pm, then using 'rot_pm' as a ClipMask
 *	call XFillRectangle to bleed the rotated text onto 'deep_pm',
 *	then XCopyArea 'deep_pm' back onto the given drawable, using
 *	the given 'gc' so its ClipMask is respected.  Whew.
 *
 *	The XImage structure needs a Visual when you create it with
 *	XCreateImage.  I am making an XImage which should be identical
 *	except in shape to the that I got from XGetImage but the gotten
 *	one contains no information about the Visual.  I punt and use
 *	the default Visual of the default Screen  -- I am only making
 *	a 1 bit deep pixmap so what difference can it make?.
 *
 *	I need to pass a drawable_depth to Text90 because there is no
 *	way to ask a drawable how deep it is.
 *
 *	If we knew that there were no ClipMask in the given gc we could
 *	skip the 2 CopyArea()s, but there is no way to determine this
 *	without passing yet another argument to Text90 (an argument whose
 *	value is probably not known by the caller).
 *	
 *	Text90 allocates and frees quite a few X objects.  If your server
 *	has memory leaks you will find out about them using this.  Perhaps
 *	one could cache these things as statics to Text90.
 *
 *	I have included a test main() program.  To use it :
 *		cc -o xrot xrot.c -lX11 -lm
 *		xrot xpos ypos
 *	where xpos and ypos give the position of the string.
 *
 *	If any one has a better way of doing this, please tell me about it.
 *
 *	Bill Dunlap
 *	Dept. of Statistics, GN-22
 *	University of Washington
 *	Seattle, WA 98195
 *	bill@stat.washington.edu
 */
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

void Text90() ;
static void BailOut() ;
static Display *dpy ;
static Window win ;
static XFontStruct *fontstruct ;
static GC win_gc ;
static XGCValues xgcv ;
extern char *malloc() ;

/* ARGSUSED */
main(argc, argv)
int argc ;
char **argv ;
{
	unsigned long fg, bg ;
	int x, y ;
	if (!(--argc>0 && sscanf(*++argv, "%d", &x)==1))
		x = 20 ;
	if (!(--argc>0 && sscanf(*++argv, "%d", &y)==1))
		y = 20 ;
	dpy = XOpenDisplay((char *)NULL) ;
	if (!dpy) BailOut("XOpenDisplay") ;
	fg = BlackPixel(dpy, DefaultScreen(dpy)) ;
	bg = WhitePixel(dpy, DefaultScreen(dpy)) ;
#define WIN_WIDTH 500
#define WIN_HEIGHT 100
	win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
		200, 200, /* position */
		WIN_WIDTH, WIN_HEIGHT, /* size */
		2, /* border width */
		fg, /* border color */
		bg) ; /* background color */
	if (!win) BailOut("XCreateSimpleWindow") ;
#define STRING "This is a test"
	fontstruct = XLoadQueryFont(dpy, "fixed") ;
	if (!fontstruct) BailOut("XLoadQueryFont") ;
	xgcv.foreground = fg ;
	xgcv.background = bg ;
	xgcv.line_width = 4 ;
	win_gc = XCreateGC(dpy, win,
		GCForeground|GCBackground|GCLineWidth,
		&xgcv) ;
	if (!win_gc) BailOut("XCreateGC(win_gc)") ;
	XSelectInput(dpy, win, ExposureMask) ;
	XMapWindow(dpy, win) ;
	while (1) {
		XRectangle rect ;
		XEvent event ;
		XWindowAttributes xwa ;
		XNextEvent(dpy, &event) ;
		if (!(event.type == Expose && ((XExposeEvent *)&event)->count == 0))
			continue ;
		if (!XGetWindowAttributes(dpy, win, &xwa))
			break ;
		XClearWindow(dpy, win) ;
#define MARGIN 5
		rect.x = MARGIN ; rect.y = MARGIN ;
		rect.width = xwa.width - 2 * MARGIN ;
		rect.height = xwa.height -  2 * MARGIN ;
		XSetClipRectangles(dpy, win_gc, 0, 0, &rect, 1, Unsorted) ;
		XDrawLine(dpy, win, win_gc, 0, 0, xwa.width, xwa.height) ;
		XDrawLine(dpy, win, win_gc, xwa.width, 0, 0, xwa.height) ;
		Text90(dpy, win, xwa.depth, win_gc, x, y, STRING, strlen(STRING), fontstruct) ;
	}
	exit(0) ;
}

static void
BailOut(msg)
char *msg ;
{
	fprintf(stderr, "Error in %s -- bailing out\n", msg) ;
	exit(1) ;
}

void
Text90(dpy, drawable, drawable_depth, gc, x, y, string, length, fontstruct)
Display *dpy ;
Drawable drawable ;
int drawable_depth ;
GC gc ;
int x, y ;
char *string ;
int length ;
XFontStruct *fontstruct ;
{
	Pixmap pm, rot_pm, deep_pm ;
	GC pm_gc, deep_gc ;
	XImage *image, *rot_image ;
	int row, col ;
	char *d ;
	int rot_bytes_per_line ;
	XCharStruct xcs ;
	int width, height ;
	int direction, font_ascent, font_descent ;
	XTextExtents(fontstruct, string, length,
		&direction, &font_ascent, &font_descent,
		&xcs) ;
	width = xcs.width ;
	height = font_ascent + font_descent ;
	pm = XCreatePixmap(dpy, win, width, height, 1) ;
	if (!pm) BailOut("XCreatePixmap") ;
	rot_pm = XCreatePixmap(dpy, drawable, height, width, 1) ;
	if (!rot_pm) BailOut("XCreatePixmap") ;
	xgcv.foreground = 1 ; /* yes, 1, this will be a mask */
	xgcv.background = 0 ;
	xgcv.font = fontstruct->fid ;
	pm_gc = XCreateGC(dpy, pm,
		GCForeground|GCBackground|GCFont,
		&xgcv) ;
	if (!pm_gc) BailOut("XCreateGC(pm_gc)") ;
	XDrawString(dpy, pm, pm_gc, 0, font_ascent, string, length) ;
	image = XGetImage(dpy, pm, 0, 0, width, height, AllPlanes, XYPixmap) ;
	if (!image) BailOut("XGetImage") ;
#define roundup(n, mod) ( (n)%(mod) ? (1+(n)/(mod))*mod : n )
	rot_bytes_per_line = roundup(image->height, image->bitmap_pad)/(image->bitmap_pad/8) ;
	d = (char *)malloc(rot_bytes_per_line * image->width) ;
	if (!d) BailOut("malloc") ;
	rot_image = XCreateImage(dpy,
		DefaultVisual(dpy, 0), /* I want the same as is in image */
		image->depth,
		image->format,
		image->xoffset,
		d,
		image->height, image->width, /* yes, reverse these */
		image->bitmap_pad, 
		rot_bytes_per_line) ;
	if (!rot_image) BailOut("XCreateImage") ;
	/* rotate image 90 degrees clockwise */
	for(row=0 ; row<image->height ; row++) {
		for(col=0 ; col<image->width ; col++) {
			(void)XPutPixel(rot_image, row, rot_image->height-1-col,
				XGetPixel(image, col, row)) ;
		}
	}
	XPutImage(dpy, rot_pm, pm_gc, rot_image,
		0, 0, /* source origin */
		0, 0, /* dest origin */
		rot_image->width, rot_image->height) ;
	/* Now the pixmap rot_pm contains a mask of the rotated text */

	deep_pm = XCreatePixmap(dpy, drawable,
		rot_image->width, rot_image->height, drawable_depth) ;
	if (!deep_pm) BailOut("XCreatePixmap(deep_pm)") ;
	deep_gc = XCreateGC(dpy, deep_pm, 0L, &xgcv) ;
	if (!deep_gc) BailOut("XCreateGC(deep_gc)") ;
	XCopyArea(dpy, drawable, deep_pm, deep_gc,
		x, y, /* source origin */
		rot_image->width, rot_image->height, /* size */
		0, 0) ; /* destination origin */
	XCopyGC(dpy, gc, GCForeground, deep_gc) ;
	XSetClipMask(dpy, deep_gc, rot_pm) ;
	XFillRectangle(dpy, deep_pm, deep_gc, 0, 0,
		rot_image->width, rot_image->height) ;
	XCopyArea(dpy, deep_pm, drawable, gc,
		0, 0, /* source origin */
		rot_image->width, rot_image->height, /* size */
		x, y) ; /* destination origin */

	/* I set these things to NULL after freeing to prepare for
	 * caching them */
	free(d) ; d = (char *)NULL ;
	XFreeGC(dpy, pm_gc) ; pm_gc = (GC)NULL ;
	XFreeGC(dpy, deep_gc) ; deep_gc = (GC)NULL ;
	XFreePixmap(dpy, pm) ; pm = (Pixmap)NULL ;
	XFreePixmap(dpy, deep_pm) ; deep_pm = (Pixmap)NULL ;
	XFreePixmap(dpy, rot_pm) ; rot_pm = (Pixmap)NULL ;
	XDestroyImage(image) ; image = (XImage *)NULL ;
	XDestroyImage(rot_image) ; rot_image = (XImage *)NULL ;
	/* did I free everything? */
	return ;
}