/*===============================
|	BlugsAppearanceExtras.c
|
|	Blugs 1.1 auxiliary routines.
|	www.blugs.com
|
|	Portions copyright © 1998-2001 Brian Hall
|	local_BLDrawTextBox based on NeoTextBox from an early
|		issue of d e v e l o p Magazine.
|	Theme draw state code adapted from "More is Better" sample code
|		(specifically MoreAppearance.cp) copyright © 1998 Apple Computer, Inc.
|
|===============================*/
#include "Blugs.h"
#include "BlugsAppearanceExtras.h"




#define MAXOF(a,b)			(((a) > (b)) ? (a) : (b))

static const RGBColor	_FFCol = {0xFFFF,0xFFFF,0xFFFF},
						_ADCol = {0xADAD,0xADAD,0xADAD},
						_8CCol = {0x8C8C,0x8C8C,0x8C8C},
						_73Col = {0x7373,0x7373,0x7373},
						_52Col = {0x5252,0x5252,0x5252},
						_00Col = {0x0000,0x0000,0x0000};
#if !TARGET_RT_MAC_MACHO
static void		local_BLDrawTextBox( void* inText, Size inLength, Boolean inDraw,
										const Rect* inRect, SInt16 inAlign, SInt16 inHtCode,
										SInt16* outEndY );
static UInt16	local_BLLineHeight( UInt8* theText, UInt32 textLen, const Rect* wrapBox,
									SInt16 lhCode, SInt16* startY );
static void		local_BLDraw( Ptr lineStart, SInt32 lineBytes, const Rect *wrapBox,
								SInt16 align, SInt16 curY, SInt16 boxWidth);
#endif

#if __BL_USE_CARBON_API__
static OSErr local_BLCallCarbonGetThemeTextDimensions( void* inText, Size inLength, ThemeFontID inFont,
														ThemeDrawState inState, Boolean inWrapToWidth,
														Point* ioBounds, SInt16* outBaseline );
static OSErr local_BLCallCarbonDrawThemeTextBox( void* inText, Size inLength, ThemeFontID inFont,
												ThemeDrawState inState, Boolean inWrapToWidth,
												const Rect* inRect );

#endif


//===================================
// A checkbox is a 12x12 square. Aha! in Aqua it's 16 x 16!
// Only need to use top left corner of rect when drawing it ourselves (since we
//	use the Platinum metrics).
void BLDrawCheckBox( Rect inRect, ThemeDrawState inState, ThemeButtonValue inValue )
{
	if (BLAppearanceVersion() >= 0x0110)
	{
		ThemeButtonDrawInfo	newInfo;
		
		newInfo.state = inState;
		newInfo.value = inValue;
		newInfo.adornment = 0;
		DrawThemeButton( &inRect, kThemeCheckBox, &newInfo, nil, nil, nil, 0 );
	}
	else
	{
		inRect.right = inRect.left + 12;
		inRect.bottom = inRect.top + 12;
		BLDrawPlacard( &inRect, inState );
		// Draw the check
		RGBForeColor( (inState == kThemeStateInactive) ? &_8CCol : &_00Col );
		if (inValue == kThemeButtonOn)
		{
			PenSize( 2, 1 );
			MoveTo( inRect.left + 2, inRect.top + 5);
			Line( 2, 2 );
			Line( 1, 0 );
			Line( 6, -6 );
			Move( -6, 7 );
			PenSize( 1, 1 );
			Line( 0, 0 );
			// Draw shadow
			if (inState == kThemeStateActive)
			{
				Move( -2, -1 );
				RGBForeColor( &_ADCol );
				Line( 1, 1 );
				Move( 2, 1 );
				Line( 3, -3 );
				Move( -4, 3 );
				RGBForeColor( &_73Col );
				Line( 4, -4 );
				Line( 1, 0 );
				Move ( 2, -3 );
				Line( 0, 0 );
				Move( -2, 2 );
				RGBForeColor( &_52Col );
				Line( 0, 0 );
				RGBForeColor( &_00Col );
			}
		}
		else if (inValue == kThemeButtonMixed)
		{
			MoveTo( inRect.left + 2, inRect.top + 5);
			PenSize( 2, 2 );
			Line( 4, 0 );
			PenNormal();
		}
	}
}

//===================================
void BLDrawPopupArrow( Rect inRect, ThemeArrowOrientation inOrientation,
						ThemePopupArrowSize inSize, ThemeDrawState inState )
{
	if (BLAppearanceVersion() >= 0x0110)
		DrawThemePopupArrow( &inRect, inOrientation, inSize, inState, nil, 0 );
	else
	{
		PolyHandle	triangle;
		Point		point1;
		SInt16		h2, v2, h3, v3, shorterDimension;
		Boolean		isUpDown;
		
		isUpDown = (inOrientation == kThemeArrowDown || inOrientation == kThemeArrowUp);
		shorterDimension = inSize + 1;
		// Shorter dimension is inSize + 1. (kThemeArrow9pt => 3 + 1 = 4)
		// First point is leftmost if up/down, top if left/right.
		if (isUpDown)
		{
			// If up, point1.v is short dimension away.
			// If down, it's at top of rect.
			point1.h = inRect.left;
			point1.v = inRect.top + ((inOrientation == kThemeArrowUp) ? shorterDimension : 0);
		}
		else
		{
			point1.v = inRect.top;
			point1.h = inRect.left + ((inOrientation == kThemeArrowLeft) ? shorterDimension : 0);
		}
		// Second point moves from point 1 along shorter side (not hypotenuse).
		h2 = shorterDimension;
		v2 = shorterDimension;
		if (inOrientation == kThemeArrowUp) v2 = -shorterDimension;
		else if (inOrientation == kThemeArrowLeft) h2 = -shorterDimension;
		// Third point moves from point 2 along other shorter side.
		h3 = shorterDimension;
		v3 = shorterDimension;
		if (inOrientation == kThemeArrowDown) v3 = -shorterDimension;
		else if (inOrientation == kThemeArrowRight) h3 = -shorterDimension;
		// Draw the polygon
		triangle = OpenPoly();
		if (triangle)
		{
			MoveTo( point1.h, point1.v );
			Line( h2, v2 );
			Line( h3, v3 );
			LineTo( point1.h, point1.v );
			ClosePoly();
			RGBForeColor( (inState == kThemeStateActive) ? &_00Col :
							((inState == kThemeStatePressed) ? &_FFCol : &_8CCol) );
			PaintPoly( triangle );
			FramePoly( triangle );
			KillPoly( triangle );
		}
	}
}



//===============================
// All this pseudo-GetThemeDrawinfState stuff is stolen and modified
//	from MoreAppearance.cp in the Apple's MoreIsBetter sample code.
#if !TARGET_API_MAC_CARBON

struct BLThemeDrawingStateRec
{
	PixPatHandle	pnPixPat;
	PixPatHandle	bkPixPat;
	Pattern			bkPat;
	UInt32			fgColor;
	UInt32			bkColor;
	RGBColor		foreColor;
	RGBColor		backColor;
	PenState		pen;
	SInt16			textMode;
	Boolean			colorPort;
	Boolean			bkPatternIsValid;
};

typedef struct BLThemeDrawingStateRec	BLThemeDrawingStateRec;


//===============================
OSStatus BLGetThemeDrawingState( BLThemeDrawingState* outState )
{
	OSStatus			err = noErr;
	BLThemeDrawingState	state = nil;
	
	if (BLAppearanceVersion() >= 0x0110)
		err = GetThemeDrawingState( (ThemeDrawingState*)outState );
	else
	{
		state = (BLThemeDrawingState)NewPtrClear( sizeof(BLThemeDrawingStateRec) );
		if (!state) err = MemError();
		else
		{
			GrafPtr curPort;
			
			GetPort( &curPort );
			state->pnPixPat = nil;
			state->bkPixPat = nil;
			state->colorPort = (((CGrafPtr)curPort)->portVersion < 0);
			// Save the black and white information always
			state->bkPatternIsValid = true;
			state->bkPat = curPort->bkPat;
			state->bkColor = curPort->bkColor;
			state->fgColor = curPort->fgColor;

			if (state->colorPort)
			{
				PixPatHandle	penPixPat, backPixPat;
				
				penPixPat = ((CGrafPtr)curPort)->pnPixPat;
				backPixPat = ((CGrafPtr)curPort)->bkPixPat;
				GetForeColor( &state->foreColor );
				GetBackColor( &state->backColor );
				
				// If the pen pattern is not an old style pattern,
				// copy the handle. If it is an old style pattern,
				// GetPenState below will save the right thing.
				if (penPixPat) if ((**penPixPat).patType != 0) state->pnPixPat = penPixPat;

				// If the back pattern is not an old style pattern,
				// copy the handle, else get the old pattern into
				// bkPat for restoring that way.
				if (backPixPat)
				{
					if ((**backPixPat).patType != 0) state->bkPixPat = backPixPat;
					else state->bkPat = *(PatPtr)(*(**backPixPat).patData);
					state->bkPatternIsValid = true;
				}
				else state->bkPatternIsValid = false;
			}
			GetPenState( &state->pen );
			state->textMode = curPort->txMode;
		}
		*outState = state;		// Moved up here from below - AP 011118
	}
	//*outState = state;
	return err;
}

//===============================
OSStatus BLNormalizeThemeDrawingState( void )
{
	OSStatus	err = noErr;
	
	if (BLAppearanceVersion() >= 0x0110) err = NormalizeThemeDrawingState();
	else
	{
		static Pattern	whitePat = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
		GrafPtr			curPort;
		
		GetPort( &curPort );
		ForeColor( blackColor );
		BackColor( whiteColor );
		PenNormal( );
		BackPat( &whitePat );
		TextMode( srcOr );
	}
	return err;
}

//===============================
OSStatus BLSetThemeDrawingState( BLThemeDrawingState inState, Boolean inDisposeNow )
{
	OSStatus	err = noErr;

	if (!inState) err = paramErr;
	else if (BLAppearanceVersion() >= 0x0110)
		err = SetThemeDrawingState( (ThemeDrawingState)inState, inDisposeNow );
	else
	{
		GrafPtr curPort;
		
		GetPort( &curPort );
		SetPenState( &(inState->pen) );
		// If we saved color information, and this port is a
		// color port, use the color stuff, else just use the
		// black and white information.
		if (((CGrafPtr)curPort)->portVersion < 0 && inState->colorPort)
		{
			RGBForeColor( &inState->foreColor );
			RGBBackColor( &inState->backColor );
			if (inState->pnPixPat) PenPixPat( inState->pnPixPat );
			if (inState->bkPatternIsValid)
			{
				if (inState->bkPixPat) BackPixPat( inState->bkPixPat );
				else BackPat( &inState->bkPat );
			}
		}
		else
		{
			// back pattern is always valid for monochrome ports
			BackPat( &inState->bkPat );
			ForeColor( inState->fgColor );
			BackColor( inState->bkColor );
		}

		TextMode( inState->textMode );
		if (inDisposeNow)
		{
			DisposePtr( (Ptr)inState );
			err = MemError();
		}
	}
	return err;
}

//===============================
OSStatus BLDisposeThemeDrawingState( BLThemeDrawingState inState )
{
	OSStatus	err = noErr;

	if (!inState) err = paramErr;
	else if (BLAppearanceVersion() >= 0x0110)
		err = DisposeThemeDrawingState( (ThemeDrawingState)inState );
	else
	{
		DisposePtr( (Ptr)inState );
		err = MemError();
	}
	return err;
}

#endif !TARGET_API_MAC_CARBON

#pragma mark -
//===============================
// These are pseudo-Theme drawing routines stolen (& renamed) from my FerrousFace library
//	of Appearance-Mgr replacement routines.
//===============================
OSErr BLGetFont( ThemeFontID inFont, Str255 outName, SInt16* outFont,
					SInt16* outSize, Style* outStyle )
{
	SInt16	font = 0;
	SInt16	size = 12;
	Style	style = 0;
	OSErr	err = noErr;
	
	switch (inFont)
	{
		case kThemeSystemFont:
		case kThemeMenuTitleFont:
		case kThemeMenuItemFont:
		case kThemeMenuItemMarkFont:
		case kThemeMenuItemCmdKeyFont:
		case kThemeWindowTitleFont:
		case kThemePushButtonFont:
		case kThemeAlertHeaderFont:
		break;
		
		case kThemeSmallSystemFont:
		size = 10;
		break;
		
		case kThemeSmallEmphasizedSystemFont:
		case kThemeUtilityWindowTitleFont:
		size = 10;
		style = bold;
		break;
		
		case kThemeViewsFont:
		err = BLGetViewsFont( &font, &size );
		if (err)
		{
			font = 1;
			size = 10;
			err = noErr;
		}
		break;
		
		case kThemeApplicationFont:
		case kThemeLabelFont:
		font = 1;
		size = 10;
		break;
		
		case kThemeEmphasizedSystemFont:
		style = bold;
		break;
		
		case kThemeCurrentPortFont:
		{
			CGrafPtr	curPort;
			
			GetPort( &(GrafPtr)curPort );
			font = BLGetPortTextFont( curPort );
			size = BLGetPortTextSize( curPort );
			style = BLGetPortTextFace( curPort );
		}
		break;
		
		default:
		err = paramErr;
		break;
	}
	if (outName) GetFontName( font, outName );
	*outFont = font;
	*outSize = size;
	*outStyle = style;
	return err;
}

//===============================
OSErr BLUseFont( ThemeFontID inFont )
{
	SInt16	font, size;
	Style	style;
	OSErr	err;
	
	err = BLGetFont( inFont, nil, &font, &size, &style );
	if (!err)
	{
		TextFont( font );
		TextSize( size );
		TextFace( style );
	}
	return err;
}

//===============================
OSErr BLGetFontInfo( ThemeFontID inFont, FontInfo* outInfo )
{
	OSErr				err;
	ThemeDrawingState	state;
	
	err = BLGetThemeDrawingState( &state );
	if (!err)
	{
		err = BLUseFont( inFont );
		GetFontInfo( outInfo );
		(void)BLSetThemeDrawingState( state, true );
	}
	return err;
}


//===============================
OSErr BLGetStringLength( Str255 inString, ThemeFontID inFont, SInt16* outLength )
{
	OSErr				err;
	ThemeDrawingState	state;
	
	err = BLGetThemeDrawingState( &state );
	if (!err)
	{
		err = BLUseFont( inFont );
		*outLength = TextWidth( &inString[1], 0, inString[0] );
		(void)BLSetThemeDrawingState( state, true );
	}
	return err;
}


//===============================
OSErr BLTruncateString( Str255 inString, ThemeFontID inFont, SInt16 inWidthLimit,
						TruncCode inWhere, Boolean* outTruncated )
{
	OSErr				err;
	ThemeDrawingState	state;
	
	err = BLGetThemeDrawingState( &state );
	if (!err)
	{
		SInt16	result;
		
		err = BLUseFont( inFont );
		result = TruncString( inWidthLimit, inString, inWhere );
		if (outTruncated) *outTruncated = (result == truncated);
		(void)BLSetThemeDrawingState( state, true );
	}
	return err;
}


#if TARGET_RT_MAC_MACHO
//===============================
OSErr BLGetTextDimensions( void* inText, Size inLength, ThemeFontID inFont, ThemeDrawState inState,
							Boolean inWrapToWidth, Point* ioBounds, SInt16* outBaseline )
{
	return local_BLCallCarbonGetThemeTextDimensions( inText, inLength, inFont, inState, inWrapToWidth,
													ioBounds, outBaseline );
}
#else
OSErr BLGetTextDimensions( void* inText, Size inLength, ThemeFontID inFont, ThemeDrawState inState,
							Boolean inWrapToWidth, Point* ioBounds, SInt16* outBaseline )
{
	OSErr		err = noErr;
	
#if !TARGET_API_MAC_CARBON
	#pragma unused (inState)
#endif
#if TARGET_API_MAC_CARBON
	err = local_BLCallCarbonGetThemeTextDimensions( inText, inLength, inFont, inState, inWrapToWidth,
													ioBounds, outBaseline );
	if (err)
#endif
	{
		ThemeDrawingState		state = nil;
		FontInfo				fi;
		
		(void)BLGetThemeDrawingState( &state );
		BLUseFont( inFont );
		GetFontInfo( &fi );
		if (inWrapToWidth)
		{
			Rect		r;
			
			SetRect( &r, 0, 0, ioBounds->h, ioBounds->v );
			local_BLDrawTextBox( inText, inLength, false, &r, teFlushDefault, 0, &ioBounds->v );
			ioBounds->v = *outBaseline;
		}
		else
		{
			ioBounds->h = TextWidth( inText, 0, inLength );
			ioBounds->v = fi.ascent + fi.descent;
		}
		*outBaseline = -fi.ascent;
		if (state) (void)BLSetThemeDrawingState( state, true );
	}
	return err;
}
#endif

#if TARGET_RT_MAC_MACHO
//===============================
OSErr BLDrawTextBox( void* inText, Size inLength, ThemeFontID inFont, ThemeDrawState inState,
					Boolean inWrapToWidth, const Rect* inRect, SInt16 inJust )
{
	#pragma unused (inJust)
	return local_BLCallCarbonDrawThemeTextBox( inText, inLength, inFont, inState, inWrapToWidth, inRect );
}
#else
OSErr BLDrawTextBox( void* inText, Size inLength, ThemeFontID inFont, ThemeDrawState inState,
					Boolean inWrapToWidth, const Rect* inRect, SInt16 inJust )
{
	OSErr		err = noErr;

#if !__BL_USE_CARBON_API__
	#pragma unused (inState)
#else
	if ((BLEnvironment() & blHasAqua) == blHasAqua)
		err = local_BLCallCarbonDrawThemeTextBox( inText, inLength, inFont, inState, inWrapToWidth, inRect );
	else err = unimpErr;
	if (err)
#endif
	{
		ThemeDrawingState	state = nil;
		
		err = BLGetThemeDrawingState( &state );
		BLUseFont( inFont );
		if (inWrapToWidth) local_BLDrawTextBox( inText, inLength, true, inRect, inJust, 0, nil );
		else
		{
			FontInfo		fi;
			BLAlignment		alignment = blAlignVCenter;
			Rect			textRect;
			
			switch (inJust)
			{
				case teFlushDefault:
				alignment |= blAlignSysDirection;
				break;
				
				case teFlushLeft:
				alignment |= blAlignLeft;
				break;
				
				case teFlushRight:
				alignment |= blAlignRight;
				break;
				
				default:
				alignment |= blAlignVCenter;
				break;
	  		}
			GetFontInfo( &fi );
			textRect.left = 0;
			textRect.right = TextWidth( inText, 0, inLength );
			textRect.top = 0;
			textRect.bottom = fi.ascent + fi.descent;
			BLAlignRectInRect( alignment, false, &textRect, inRect );
			MoveTo( textRect.left, textRect.top + fi.ascent );
			DrawText( inText, 0, inLength );
		}
		if (state) (void)BLSetThemeDrawingState( state, true );
	}
	return err;
}
#endif

#if !TARGET_RT_MAC_MACHO
//===============================
// Closely based on NeoTextBox code in an early issue of d e v e l o p Magazine.
static void local_BLDrawTextBox( void* inText, Size inLength, Boolean inDraw,
									const Rect* inRect, SInt16 inAlign, SInt16 inHtCode,
									SInt16* outEndY )
{
	RgnHandle			oldClip, clip;
	StyledLineBreakCode	breakCode;		// Returned code from StyledLineBreak
	Fixed				fixedMax;		// boxWidth converted to fixed point
	Fixed				wrapWid;		// Width to wrap to
	SInt16				boxWidth;		// Width of the wrapBox
	SInt32				lineBytes;		// Number of bytes in one line
	UInt16				lineHeight;		// Calculated line height
	SInt16				curY;			// Current vertical pen location
	SInt32				textLeft;		// Pointer to remaining bytes of text
	Ptr					lineStart;		// Pointer to beginning of a line
	Ptr					textEnd;		// Pointer to the end of input text
	
	/*	First, we save the old clipping region and clip to wrapBox.  Then, figure
		the width of wrapBox, and make a fixed point version of it.  Also, resolve
		the text alignment teFlushDefault to be the default system text alignment.
		
		**Moses added this**
		NeoTextBox assumes the rect in which the text is being drawn is entirely within
		the desired clipping region. This is not necessarily the case if the content is
		scrolled. So we clip to the intersection of the initial clip, and the rect/rgn
		passed as the draw target.
	*/
	oldClip = NewRgn();
	clip = NewRgn();
	GetClip( oldClip );
	RectRgn( clip, inRect );
	SectRgn( clip, oldClip, clip );
	SetClip( clip );
	DisposeRgn( clip );
	boxWidth = inRect->right - inRect->left;
	fixedMax = Long2Fix( (SInt32)boxWidth );
	if (inAlign == teFlushDefault)
	{
		if (GetSysDirection() == teFlushRight) inAlign = teFlushRight;
		else inAlign = teFlushLeft;
	}
	
	/*	Now we call local_FFaceLineHeight to calculate the appropriate line height.  It
		also figures our starting vertical pen location in curY based on the
		line height and other metric parameters.  Clear lineCount, set
		lineStart to point to the beginning of the text, calculate textEnd for
		comparison to know when we're done, and preset textLeft to be all the
		text. */

	lineHeight = local_BLLineHeight( inText, inLength, inRect, inHtCode, &curY );
	lineStart = inText;
	textEnd = ((Ptr) inText) + inLength;
	textLeft = inLength;
	
	/* This is the main wrap-and-draw loop. I bet you never thought wrapping
		text could be so easy... */

	do {
		/* Every line, we have to preset lineBytes to something non-zero.
			This tells StyledLineBreak that we're drawing the first format
			run on the line (of course, for us, there's only ONE format run
			total).  Also preset wrapWid.  StyledLineBreak will always modify
			lineBytes (to tell you how many bytes are on this line), and will
			modify wrapWid, so we have to reset them each line. */
		
		lineBytes = 1;
		wrapWid = fixedMax;
		breakCode = StyledLineBreak(lineStart, textLeft, 0, textLeft, 0, &wrapWid, &lineBytes);
		
		/* Now that the Script Manager has done all the really hard work for
			us, we draw the line.  We already knew lineStart, StyledLineBreak
			gave us lineBytes, which we pass to local_FFaceDraw.  It'll handle the
			different text alignments itself. */
		
		if (inDraw) local_BLDraw( lineStart, lineBytes, inRect, inAlign, curY, boxWidth );
		
		/* Now we advance our vertical position down by the height of one
			line, advance lineStart by the number of bytes we just drew,
			calculate a new textLeft, and increment our line count. */
		
		curY += lineHeight;
		lineStart += lineBytes;
		textLeft -= lineBytes;
	} while (lineStart < textEnd);
	
	if (outEndY) *outEndY = curY - lineHeight;
	
	SetClip( oldClip );
	DisposeRgn( oldClip );
}

//===============================
static UInt16 local_BLLineHeight( UInt8* theText, UInt32 textLen, const Rect* wrapBox,
									SInt16 lhCode, SInt16* startY )
{
	SInt16		asc,desc;	// Used in the OutlineMetrics calls
	FontInfo	fInfo;		// The old-style font information record
	Point		frac;		// The fraction for the TrueType calls
	UInt16		lineHeight;	// The return value

	GetFontInfo(&fInfo);
	if (lhCode < 0)
	{
		/*
			If the user has specified variable-height lines, we need to try
			to determine the tallest ascent in the given text.  We can only
			really do this if the font is a TrueType font.  Otherwise, we
			punt and use old-fashioned GetFontInfo numbers.
		*/

		frac.h = frac.v = 1;
		if (IsOutline(frac, frac))
		{
			/*	At this point we know the current font is a TrueType font, so
				we do an OutlineMetrics call with our full text.  It will put
				the tallest character ascent into asc, and the deepest descent
				into desc.  Then we choose between whichever's most between
				the old-style ascent/descent and the numbers we get from
				the OutlineMetrics call. */
			OutlineMetrics( (short)textLen, theText, frac, frac, &asc, &desc, nil, nil, nil );
			lineHeight = MAXOF(fInfo.ascent, asc) + MAXOF(fInfo.descent,-desc) + fInfo.leading;
			*startY = wrapBox->top + MAXOF(fInfo.ascent, asc);
			*startY += fInfo.leading;
		}
		else
		{
			/* At this point we know the current font isn't TrueType, so we
				just use the old way of calculating line height. */
			lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
			*startY = wrapBox->top + fInfo.ascent + fInfo.leading;
		}
	}
	else if (lhCode == 0)
	{
		/* If the user has specified "default" line height, he just wants us
			to get the line height from the FontInfo record. */
		lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
		*startY = wrapBox->top + fInfo.ascent + fInfo.leading;
	}
	else
	{
		/* If the user has provided a specific line height, we just trust
			them.  We can't really generate too good of a starting vertical
			coordinate, but we munge one together anyway. */

		lineHeight = lhCode;
		*startY = wrapBox->top + lhCode + fInfo.leading;
	}
	return lineHeight;
}

//===============================
static void local_BLDraw( Ptr lineStart, SInt32 lineBytes, const Rect* wrapBox,
						SInt16 inAlign, SInt16 curY, SInt16 boxWidth)
{
	UInt32		blackLen;	// Length of non-white characters

	blackLen = VisibleLength(lineStart, lineBytes);
	switch (inAlign)
	{
		case teFlushLeft:
		MoveTo(wrapBox->left, curY);
		break;
		
		case teFlushRight:
		MoveTo(wrapBox->right - TextWidth(lineStart, 0, blackLen), curY);
		break;
		
		case teCenter:
		MoveTo(wrapBox->left + (boxWidth - TextWidth(lineStart, 0, blackLen)) >> 1, curY);
		break;
	}
	DrawText(lineStart, 0, lineBytes);
}
#endif //!TARGET_RT_MAC_MACHO


#if TARGET_API_MAC_CARBON
//===============================
static OSErr local_BLCallCarbonGetThemeTextDimensions( void* inText, Size inLength, ThemeFontID inFont,
														ThemeDrawState inState, Boolean inWrapToWidth,
														Point* ioBounds, SInt16* outBaseline )
{
	OSErr			err;
#if TARGET_RT_MAC_MACHO
	err = noErr;
#else
	err = cfragNoSymbolErr;
	if (GetThemeTextDimensions != (void*)kUnresolvedCFragSymbolAddress)
#endif
	{
		CFStringRef		string;
		Ptr				cString;
		
		cString = NewPtrClear( inLength + 1 );
		if (!cString) err = memFullErr;
		else
		{
			BlockMoveData( inText, cString, inLength );
			string = CFStringCreateWithCString( CFAllocatorGetDefault(), cString,
												kCFStringEncodingMacRoman );
			err = GetThemeTextDimensions( string, inFont, inState, inWrapToWidth, ioBounds, outBaseline );
			CFRelease( string );
			DisposePtr( cString );
		}
	}
	return err;
}

//===============================
static OSErr local_BLCallCarbonDrawThemeTextBox( void* inText, Size inLength, ThemeFontID inFont,
												ThemeDrawState inState, Boolean inWrapToWidth,
												const Rect* inRect )
{
	OSErr			err;
#if TARGET_RT_MAC_MACHO
	err = noErr;
#else
	err = cfragNoSymbolErr;
	if (DrawThemeTextBox != (void*)kUnresolvedCFragSymbolAddress)
#endif
	{
		Ptr		cString;
		
		cString = NewPtrClear( inLength + 1 );
		if (!cString) err = memFullErr;
		else
		{
			CFStringRef		string;
			
			BlockMoveData( inText, cString, inLength );
			string = CFStringCreateWithCString( CFAllocatorGetDefault(), cString,
												kCFStringEncodingMacRoman );
			err = DrawThemeTextBox( string, inFont, inState, inWrapToWidth, inRect, teFlushDefault, nil );
			CFRelease( string );
			DisposePtr( cString );
		}
	}
	return err;
}
#endif

//===============================
void BLDrawStringVerticallyCentered( Str255 inString, const Rect* inRect, UInt16 inIndent )
{
	Str255		truncatedString;
	OSErr		err;
	Boolean		truncated;
	Rect		textRect;
	Point		bounds;
	SInt16		baseline;
	
	BlockMoveData( inString, truncatedString, inString[0] + 1 );
	textRect = *inRect;
	textRect.left += inIndent;
	err = BLTruncateString( truncatedString, kThemeCurrentPortFont,
							textRect.right - textRect.left,
							((BLEnvironment() & blHasAqua) == blHasAqua) ? truncMiddle : truncEnd,
							&truncated );
	err = BLGetTextDimensions( &truncatedString[1], truncatedString[0], kThemeCurrentPortFont,
								kThemeStateActive, false, &bounds, &baseline );
	textRect.bottom = textRect.top + bounds.v;
	textRect.right = textRect.left + bounds.h;
	BLAlignRectInRect( blAlignVCenter, false, &textRect, inRect );
	err = BLDrawTextBox( &truncatedString[1], truncatedString[0], kThemeCurrentPortFont,
						kThemeStateActive, false, &textRect, teFlushDefault );
}

#pragma mark -
//===============================
SInt16 BLGetPortTextFont( CGrafPtr inPort )
{
#if __BL_USE_CARBON_API__
	return GetPortTextFont( inPort );
#else
	return inPort->txFont;
#endif
}

//===============================
SInt16 BLGetPortTextSize( CGrafPtr inPort )
{
#if __BL_USE_CARBON_API__
	return GetPortTextSize( inPort );
#else
	return inPort->txSize;
#endif
}

//===============================
SInt16 BLGetPortTextFace( CGrafPtr inPort )
{
#if __BL_USE_CARBON_API__
	return GetPortTextFace( inPort );
#else
	return inPort->txFace;
#endif
}

//===============================
void BLAlignRectInRect( BLAlignment inAlignment, Boolean inShrinkToFit,
						Rect* inAlignRect, const Rect* inThisRect )
{
	SInt16	width, height, thisWidth, thisHeight, offsetH = 0, offsetV = 0;
	UInt16	alignH, alignV;
	
	alignH = inAlignment & blAlignHMask;
	alignV = inAlignment & blAlignVMask;
	width = inAlignRect->right - inAlignRect->left;
	height = inAlignRect->bottom - inAlignRect->top;
	thisWidth = inThisRect->right - inThisRect->left;
	thisHeight = inThisRect->bottom - inThisRect->top;
	if (inShrinkToFit)
	{
		if (width > thisWidth)
		{
			inAlignRect->right = inAlignRect->left + thisWidth;
			width = thisWidth;
		}
		if (height > thisHeight)
		{
			inAlignRect->bottom = inAlignRect->top + thisHeight;
			height = thisHeight;
		}
	}
	if (alignH == blAlignSysDirection)
	{
		if (GetSysDirection()) alignH = blAlignRight;
		else alignH = blAlignLeft;
	}
	switch (alignH)
	{
		case blAlignLeft:
		offsetH = inThisRect->left - inAlignRect->left;
		break;
		
		case blAlignRight:
		offsetH = inThisRect->right - inAlignRect->right;
		break;
		
		case blAlignHCenter:
		offsetH = inThisRect->left - inAlignRect->left + (thisWidth - width) / 2;
		break;
	}
	switch (alignV)
	{
		case blAlignTop:
		offsetV = inThisRect->top - inAlignRect->top;
		break;
		
		case blAlignBottom:
		offsetV = inThisRect->bottom - inAlignRect->bottom;
		break;
		
		case blAlignVCenter:
		offsetV = inThisRect->top - inAlignRect->top + (thisHeight - height) / 2;
		break;
	}
	if (offsetH || offsetV) OffsetRect( inAlignRect, offsetH, offsetV );
}

//===============================
// OK, anybody know how to do this pre-Appearance 1.1?
OSErr BLGetViewsFont( SInt16* outFont, SInt16* outSize )
{
	OSErr		err = noErr;
	
	// Under X/Aqua, Lucide Grande 12 is appropriate.
	if ((BLEnvironment() & blHasAqua) == blHasAqua)
	{
		*outFont = 0;
		*outSize = 12;
	}
	else if (BLAppearanceVersion() < 0x0110) err = unimpErr;
	else
	{
		Collection		theme;
		
		theme = NewCollection();
		if (theme)
		{
			err = GetTheme( theme );
			if (!err)
			{
				Size	size = sizeof(Str255);
				Str255	fontName;
				
				err = GetTaggedCollectionItem( theme, kThemeViewsFontTag, 1, &size, &fontName[0] );
				if (!err)
				{
					GetFNum( fontName, outFont );
					size = sizeof(SInt16);
					err = GetTaggedCollectionItem( theme, kThemeViewsFontSizeTag, 1, &size, outSize );
				}
			}
			DisposeCollection( theme );
		}
	}
	return err;
}