[comp.sys.mac.programmer] Rotated Text Solutions

ba0k+@andrew.cmu.edu (Brian Patrick Arnold) (05/17/91)

Howdy,

   due to popular demand, here be solutions given to me for drawing
rotated text.  The most general solution was in:

"Mactutor V4-N2-p62 and V4-N11-p86 were the article and its followup."

The Pascal code in that article by Kelly King includes nicely optimized
and therefore from my perspective unmaintainable assembler code as well.
 It also has some big phrase using the capitalized word EXPRESS followed
by "permission of the author" which made me queasy about using it.  It
does however know how to rotate a bitmap up, down, backwards and flip
vertical and horizontal.

I also received some C code that can similarly rotate, flip etc. for
text strings only, with a bonus that it also knows how to generate the
appropriate PostScript PicComments for the LaserWriter.  If Michael
doesn't mind, I'll drop his name for anyone interested in his C code for
doing this: that's Michael Hecht, hecht@sas.com.

Since I am a lazy Pascal programmer, I chose to use "a hack" sent by Jim
Amundson, which was suprisingly the most useable "out of the box".  All
it does is draw text rotated 90 degrees up from the current pen
position.  This is entirely in Pascal and I spent some time making it
readable, although it isn't optimized.  As Jim says, "it is simple at
the price of efficiency" which is exactly what I was looking for.  For
the population of lazy programmers "just like me", here is "tha" code
for drawing rotated text.

----
(* RotateBits and DrawStringUp:
   Copyright 1991 Jim Amundson and Brian Arnold
   All Rights Reserved, except:

   Feel free to distribute and use it WITHOUT permission of
   the authors, as long as you don't pocket any money in the
   process (duhh, no we don't want royalties from commercial
   apps that use this algo, but don't go charging people to
   use this either).  Retain the copyright notice in your
   source code and contact us if you have questions!

   amun@midway.uchicago.edu
   ba0k@andrew.cmu.edu
*)

TYPE
	MaskArray	= ARRAY[1..16] OF LongInt;
	BitArray	= ARRAY[0..16000] OF INTEGER;
	BitPtr		= ^BitArray;
	
PROCEDURE InitMask( VAR masks: MaskArray );
(* Set "on" bits for mask LongInts *)
VAR
	i: INTEGER;
BEGIN
	masks[1] := 1;
	FOR i := 2 TO 16 DO
		masks[i] := masks[i - 1] * 2;
END;

PROCEDURE RotateBits( srcMap: BitMap; VAR dstMap: BitMap );
(* Turn srcMap bitmap counter-clockwise by 90 degrees *)
VAR
	srcBits,			(* Typecast baseAddrs to peek      *)
	dstBits:	BitPtr;	(* and poke indexed Integer values *)
	dstIndex,
	srcIndex,
	dstWidth,
	srcWidth,
	dstHeight,
	srcHeight,
	whichMask,
	i, j, k:	INTEGER;
	curMask,
	rawBits:	LongInt;

	masks:		MaskArray;
BEGIN
	InitMask( masks );
	srcBits := BitPtr( srcMap.baseAddr );
	srcHeight := srcMap.bounds.bottom - srcMap.bounds.top;
	
(* Number of integers in a row *)
	srcWidth := srcMap.rowBytes DIV 2;
	dstBits := BitPtr( dstMap.baseAddr );
	dstHeight := srcWidth * 16;
	dstMap.rowBytes := ( srcHeight - 1 ) DIV 8 + 1;
	dstWidth := dstMap.rowBytes DIV 2;

(* Set dstMapination rect *)
	WITH srcMap.bounds DO
		SetRect( dstMap.bounds, left, top, 
				 left + srcHeight, top + dstHeight );

	dstIndex := 0;
(* Walk across rows of dst *)
	FOR i := 0 TO dstHeight - 1 DO BEGIN
		srcIndex := srcWidth - 1 - i DIV 16;
		whichMask := i MOD 16 + 1;
		curMask := masks[whichMask];
	(* walk across cols of dst *)
		FOR j := 1 TO dstWidth DO BEGIN
			rawBits := 0;
		(* set bits for this row/col *)
			FOR k := 16 DOWNTO 1 DO BEGIN
				rawBits := rawBits + BitShift( BitAnd( curMask, 
							srcBits^[srcIndex] ), k - whichMask );
				srcIndex := srcIndex + srcWidth;
			END; { FOR k }
			
		(* Poke in the bits *)
			dstBits^[dstIndex] := LoWord( rawBits );
			dstIndex := dstIndex + 1;
		END; { FOR j }
	END; { FOR i }
END;

PROCEDURE DrawStringUp( theStr: Str255 );
(* Draw a string from current pen loc rotated
   counterclockwise 90 degrees
*)
LABEL 999;	(* Exit label in case of error *)
VAR
	offScreenAcross:	GrafPort;
	rotatedBits:		BitMap;
	actualPort:			GrafPtr;
	theInfo:			FontInfo;
	srcRect,
	dstRect:			Rect;
	pnLoc:				Point;
	
	PROCEDURE HdlFailure;
	(* In case of memory failure, set port back
	   and jump to end *)
	BEGIN
		SetPort( actualPort );
		GOTO 999;
	END;
	
BEGIN
	GetPort( actualPort );

(* Open port and set font characteristics to actualPort *)
	OpenPort( @offScreenAcross );
	TextFont( actualPort^.txFont );
	TextSize( actualPort^.txSize );
	TextFace( actualPort^.txFace );
	SetPort( actualPort );
 
	WITH offScreenAcross, portBits, bounds DO BEGIN
	(* make bitmap exactly the size of the port *)
		portRect.left := 0;
		portRect.right := StringWidth( theStr );
		portRect.top := 0;
		GetFontInfo( theInfo );
		portRect.bottom := theInfo.ascent + theInfo.descent 
							+ theInfo.leading;
		
		bounds := portRect;

	(* rowBytes is size of row rounded up to an even number of bytes *)
		rowBytes := ( ( ( right - left ) + 15 ) DIV 16 ) * 2;
		
	(* number of bytes in BitMap is rowBytes * number of rows
	   this calculation must be done with longs *)
		baseAddr := NewPtr( rowBytes * LongInt( bottom - top ) );
		
	(* check MemError to see if we had enough room for the bitmap *)
		IF MemError <> noErr THEN
			HdlFailure;
	END;
 
	SetPort( @offScreenAcross );
 
(* since it's just an area of memory, clear it before we start *)
	EraseRect( thePort^.portRect );
	Moveto( 0, theInfo.ascent );
	DrawString( theStr );
	
	WITH offscreenAcross, portBits, bounds DO
		rotatedBits.baseAddr := NewPtr( rowBytes * 
									LongInt( bottom - top ) * 2 );
		
	IF MemError <> noErr THEN
		HdlFailure;
		
	RotateBits( offScreenAcross.portBits, rotatedBits );
	
	SetPort( actualPort );
	getpen( pnLoc );
	WITH rotatedBits.bounds DO BEGIN
		SetRect( srcRect, top, bottom - StringWidth( theStr ), 
						  right, bottom );
		SetRect( dstRect, pnLoc.h - ( right - left ) + 
							theInfo.descent, 
						  pnLoc.v - StringWidth( theStr ),
						  pnLoc.h + theInfo.descent, pnLoc.v );
	END;
		
	CopyBits( rotatedBits, thePort^.portBits, srcRect, 
				dstRect, srcCopy, NIL );
	DisposPtr( rotatedBits.baseAddr );
999:
	ClosePort( @offScreenAcross );
END;

-----

- Brian