/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

//	UTextBox.cp

// Based on "The TextBox You've Always Wanted" by Bryan K. Ressler (Beaker) in develop #9

#ifdef PowerPlant_PCH
	#include PowerPlant_PCH
#endif

#include "UTextBox.h"

#include <Gestalt.h>
#include <Fonts.h>
#include <FixMath.h>

// ---------------------------------------------------------------------------
//		¥ UTextBox
// ---------------------------------------------------------------------------
//	Constructor

/*
void
UTextBox::UTextBox( void )
{
	long gestResult;

	::Gestalt( gestaltFontMgrAttr, &gestResult );
	hasTrueType = ( gestResult & gestaltOutlineFonts );
}
*/

// ---------------------------------------------------------------------------
//		¥ TBLineHeight
// ---------------------------------------------------------------------------
//	Figures line height
//
// Input:	theText		the entire text that was given to the DrawTextBox call
//			textLen		the length in bytes of the text
//			lhCode		the line height code that was passed to DrawTextBox
//			startY		VAR - we return the starting vertical pen location here
//
// Output:	returns the line height to use
//

unsigned short
UTextBox::TBLineHeight( Ptr theText, unsigned long textLen,
						Rect *wrapBox, short lhCode, short *startY )
{
	short			asc,desc;	/* Used in the OutlineMetrics calls */
	FontInfo		fInfo;		/* The old-style font information record */
	Point			frac;		/* The fraction for the TrueType calls */
	unsigned short	lineHeight;	/* The return value */
	Boolean			hasTrueType;
	long			gestResult;

	::Gestalt( gestaltFontMgrAttr, &gestResult );
	hasTrueType = ( gestResult & gestaltOutlineFonts );

	::GetFontInfo( &fInfo );
	if (lhCode < 0) {
		frac.h = frac.v = 1;
		if ( hasTrueType && ::IsOutline(frac, frac) ) {
			::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 {
			lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
			*startY = wrapBox->top + fInfo.ascent + fInfo.leading;
		}
	} else if (lhCode == 0) {
		lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
		*startY = wrapBox->top + fInfo.ascent + fInfo.leading;
	} else {
		lineHeight = lhCode;
		*startY = wrapBox->top + lhCode + fInfo.leading;
	}

	return(lineHeight);
}

// ---------------------------------------------------------------------------
//		¥ TBDraw
// ---------------------------------------------------------------------------
//	Draws a line with appropriate justification
//
// Input:	breakCode	the break code that was returned from StyledLineBreak
//			lineStart	pointer to the beginning of the text for the current line
//			lineBytes	the length in bytes of the the text for this line
//			wrapBox		the box within which we're wrapping
//			align		the text alignment as specified by the user
//			curY		our current vertical pen coordinate
//			boxWidth	the width of wrapBox (since DrawTextBox already calculated it)
//
// Output:	none (draws on the screen)
//

void
UTextBox::TBDraw( StyledLineBreakCode breakCode, Ptr lineStart,
				long lineBytes, Rect *wrapBox, short align, short curY, short boxWidth )
{
	unsigned long	blackLen;	/* Length of non-white characters */
	short			slop;		/* Number of pixels of slop for full just */

	blackLen = ::VisibleLength( (Ptr)lineStart, lineBytes);
	
	if (align == ntbJustFull) {
		slop = boxWidth - ::TextWidth(lineStart, 0, blackLen);
		::MoveTo(wrapBox->left, curY);
		if (breakCode == smBreakOverflow ||
			*(lineStart + (lineBytes - 1)) == kReturnChar)
			align = ::GetSysDirection();
		else
			::DrawJust( (Ptr)lineStart, blackLen, slop );
	}

	switch(align) {
		case teForceLeft:
		case teJustLeft:
			::MoveTo(wrapBox->left, curY);
			break;
		case teJustRight:
			::MoveTo(wrapBox->right - TextWidth(lineStart, 0, blackLen), curY);
			break;
		case teJustCenter:
			::MoveTo(wrapBox->left + (boxWidth - ::TextWidth(lineStart, 0, blackLen)) / 2,
				curY);
			break;
	}
	if (align != ntbJustFull)
		::DrawText(lineStart, 0, lineBytes);
}

// ---------------------------------------------------------------------------
//		¥ DrawTextBox
// ---------------------------------------------------------------------------
//	Word-wraps text inside a given box
//
// Input:	theText		the text we need to wrap
//			textLen		the length in bytes of the text
//			wrapBox		the box within which we're wrapping
//			align		the text alignment
//							teForceLeft, teFlushLeft	left justified
//							teJustCenter, teCenter		center justified
//							teJustRight, teFlushRight	right justified
//							ntbJustFull					full justified
//							teJustLeft, teFlushDefault	system justified
//			lhCode		the line height code that was passed to DrawTextBox
//							< 0		variable - based on tallest character
//							0		default - based on GetFontInfo
//							> 0		fixed - use lhCode as the line height
//			endY		VAR - if non-nil, the vertical coord of the last line
//			lhUsed		VAR - if non-nil, the line height used to draw the text
//
// Output:	returns the number of line drawn total (even if they drew outside of
//			the boundries of wrapBox)
//			

short
UTextBox::DrawTextBox( Ptr theText, unsigned long textLen, Rect *wrapBox,
					short align, short lhCode, short *endY, short *lhUsed)
{
	StyledLineBreakCode	breakCode;		/* Returned code from StyledLineBreak */
	Fixed				fixedMax;		/* boxWidth converted to fixed point */
	Fixed				wrapWid;		/* Width to wrap to */
	short				boxWidth;		/* Width of the wrapBox */
	long				lineBytes;		/* Number of bytes in one line */
	unsigned short		lineHeight;		/* Calculated line height */
	short				curY;			/* Current vertical pen location */
	unsigned short		lineCount;		/* Number of lines we've drawn */
	long				textLeft;		/* Pointer to remaining bytes of text */
	Ptr					lineStart;		/* Pointer to beginning of a line */
	Ptr					textEnd;		/* Pointer to the end of input text */

	boxWidth = wrapBox->right - wrapBox->left;
	fixedMax = ::Long2Fix((long)boxWidth);
	if (align == teFlushDefault)
		align = ::GetSysDirection();

	lineHeight = UTextBox::TBLineHeight(theText, textLen, wrapBox, lhCode, &curY);
	lineCount = 0;
	lineStart = theText;
	textEnd = theText + textLen;
	textLeft = textLen;
	
	do {
		lineBytes = 1;
		wrapWid = fixedMax;

		breakCode = ::StyledLineBreak( (Ptr)lineStart, textLeft, 0, textLeft, 0,
									&wrapWid, &lineBytes );
		
		UTextBox::TBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
		curY += lineHeight;
		lineStart += lineBytes;
		textLeft -= lineBytes;
		lineCount++;
		
	} while (lineStart < textEnd);
	
	if (endY)
		*endY = curY - lineHeight;
	if (lhUsed)
		*lhUsed = lineHeight;

	return(lineCount);
}

// ---------------------------------------------------------------------------
//		¥ DrawTextBox
// ---------------------------------------------------------------------------
//	Word-wraps text inside a given box
//
// Input:	theText		the text we need to wrap
//			textLen		the length in bytes of the text
//			wrapBox		the box within which we're wrapping
//			align		the text alignment
//							teForceLeft, teFlushLeft	left justified
//							teJustCenter, teCenter		center justified
//							teJustRight, teFlushRight	right justified
//							ntbJustFull					full justified
//							teJustLeft, teFlushDefault	system justified
//			lhCode		the line height code that was passed to DrawTextBox
//							< 0		variable - based on tallest character
//							0		default - based on GetFontInfo
//							> 0		fixed - use lhCode as the line height
//
//			

void
UTextBox::DrawTextBox( Ptr theText, unsigned long textLen, Rect *wrapBox,
					short align, short lhCode )
{
	RgnHandle			oldClip;		/* Saved clipping region */
	StyledLineBreakCode	breakCode;		/* Returned code from StyledLineBreak */
	Fixed				fixedMax;		/* boxWidth converted to fixed point */
	Fixed				wrapWid;		/* Width to wrap to */
	short				boxWidth;		/* Width of the wrapBox */
	long				lineBytes;		/* Number of bytes in one line */
	unsigned short		lineHeight;		/* Calculated line height */
	short				curY;			/* Current vertical pen location */
	unsigned short		lineCount;		/* Number of lines we've drawn */
	long				textLeft;		/* Pointer to remaining bytes of text */
	Ptr					lineStart;		/* Pointer to beginning of a line */
	Ptr					textEnd;		/* Pointer to the end of input text */

	::GetClip((oldClip = ::NewRgn()));
	::ClipRect(wrapBox);
	boxWidth = wrapBox->right - wrapBox->left;
	fixedMax = ::Long2Fix((long)boxWidth);
	if (align == teFlushDefault)
		align = ::GetSysDirection();

	lineHeight = UTextBox::TBLineHeight(theText, textLen, wrapBox, lhCode, &curY);
	lineCount = 0;
	lineStart = theText;
	textEnd = theText + textLen;
	textLeft = textLen;
	
	do {
		lineBytes = 1;
		wrapWid = fixedMax;

		breakCode = ::StyledLineBreak( (Ptr)lineStart, textLeft, 0, textLeft, 0,
									&wrapWid, &lineBytes );
		
		UTextBox::TBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
		
		curY += lineHeight;
		lineStart += lineBytes;
		textLeft -= lineBytes;
		lineCount++;
		
	} while ((lineStart < textEnd) & ( curY <= (wrapBox->bottom + lineHeight) ));
	
	::SetClip(oldClip);
	::DisposeRgn(oldClip);

}

// Hacked up, copied version of DrawTextBox. Returns the height needed for the lines
// DESTROYS THE ORIGINAL STRING
// pkc (6/6/96) added scan for '\n' to determine line breaks since StyledLineBreak doesn't
// consider '\n' to be a line break.
short UTextBox::TextBoxDialogHeight( Ptr theText, unsigned long textLen, Rect *wrapBox,
					short align, short lhCode)
{
	StyledLineBreakCode	breakCode;		/* Returned code from StyledLineBreak */
	Fixed				fixedMax;		/* boxWidth converted to fixed point */
	Fixed				wrapWid;		/* Width to wrap to */
	short				boxWidth;		/* Width of the wrapBox */
	long				lineBytes;		/* Number of bytes in one line */
	unsigned short		lineHeight;		/* Calculated line height */
	short				curY;			/* Current vertical pen location */
	unsigned short		lineCount;		/* Number of lines we've drawn */
	long				textLeft;		/* Pointer to remaining bytes of text */
	Ptr					lineStart;		/* Pointer to beginning of a line */
	Ptr					textEnd;		/* Pointer to the end of input text */
	Ptr					scanner;		/* Pointer to scan for '\n' */

	boxWidth = wrapBox->right - wrapBox->left;
	fixedMax = ::Long2Fix((long)boxWidth);
	if (align == teFlushDefault)
		align = ::GetSysDirection();

	lineHeight = UTextBox::TBLineHeight(theText, textLen, wrapBox, lhCode, &curY);
	lineCount = 0;
	lineStart = theText;
	textEnd = theText + textLen;
	textLeft = textLen;
	
	// ¥¥¥ HACK
	
	for (int i=0; i< textLen; i++)
	{
		if (theText[i] == '/')
			theText[i] = 'w';
	}
	do {
		lineBytes = 1;
		wrapWid = fixedMax;

		breakCode = ::StyledLineBreak( (Ptr)lineStart, textLeft, 0, textLeft, 0,
									&wrapWid, &lineBytes );
		// pkc (6/6/96) now scan for '\n' because StyledLineBreak doesn't take '\n' into account
		scanner = lineStart;
		while( *scanner != '\n' && scanner < textEnd )
			++scanner;
		// pkc (6/6/96) move past '\n'
		++scanner;
		// pkc (6/6/96) if there is a '\n' before where StyledLineBreak says that there should be
		// a break, we should break at the '\n'
		if( scanner < textEnd && scanner - lineStart < lineBytes )
			lineBytes = scanner - lineStart;
		curY += lineHeight;
		lineStart += lineBytes;
		textLeft -= lineBytes;
		lineCount++;
		
	} while (lineStart < textEnd);
	

	return(lineCount * lineHeight);
}
