Milán Major

netscape-revival

1 branch
Code

lib/mac/TX/Line.cp

// ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
//	
// ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ

#include "Line.h"
#include "RunObject.h"
#include "RunsRanges.h"
#include "Paragraph.h"
#include "QDTextRun.h" //for kFontAttr


//***************************************************************************************************


const short kMaxLineRuns = 100;

CLine::CLine()
{
	fOrderedRuns = NULL;
	fUnorderedRuns = NULL;
}

#ifdef txtnRulers
void CLine::ILine(CStyledText* lineText, char direction, CRulersRanges* rulersRanges, TSize sizeInfo)
#else
void CLine::ILine(CStyledText* lineText, char direction, TSize sizeInfo)
#endif
{
	fStart = -1;
	fDirection = direction;
	
	fStyledText = lineText;
	fRunsRanges = lineText->GetRunsRanges();

	#ifdef txtnRulers
	fRulersRanges = rulersRanges;
	#endif

	// Failure at this point (fully constructed object) will be handled by the caller because the
	// destructor will be called.
	fOrderedRuns = new CArray;
	fOrderedRuns->IArray(sizeof(LineRunInfo), (sizeInfo == kLargeSize) ? 3 : 1);

	fUnorderedRuns = new CArray;
	fUnorderedRuns->IArray(sizeof(LineRunInfo), 3);
}
//***************************************************************************************************

void CLine::Free()
{
	fOrderedRuns->Free();
	delete fOrderedRuns;
	
	fUnorderedRuns->Free();
	delete fUnorderedRuns;
}
//***************************************************************************************************



void CLine::GetRunDisplayInfo(const LineRunInfo* runInfo, LineRunDisplayInfo* runDisplayInfo)
{
	runDisplayInfo->runChars = fStyledText->GetCharPtr(fStart + runInfo->runOffset);
	runDisplayInfo->runLen = runInfo->runLen;
	
	runDisplayInfo->runWidth = runInfo->runWidth;
	runDisplayInfo->runExtraWidth = runInfo->runExtraWidth;

	runDisplayInfo->lineDirection = fDirection;
	
//	runDisplayInfo->zoomNumer = fNumer;
//	runDisplayInfo->zoomDenom = fDenom;

Point foo = { 1, 1 };
	runDisplayInfo->zoomNumer = foo;
	runDisplayInfo->zoomDenom = foo;
}
//***************************************************************************************************

Fixed CLine::RunCharToPixel(const LineRunInfo* runInfo, short offset)
{
	if (!offset || (offset == runInfo->runLen)) {
		/*¥Note that it is important to calc run bounds like this and to not add the "short" result of CharToPixel
		to FixRound(runLeftEdge) since it may differ by 1 pix from the drawn runs (a run is drawn by "MoveTo" to FixRound of all preceding runs)*/
		
		Boolean startBound = (offset == 0);
		if (this->GetRunDirection(*runInfo) == kRL)
			startBound = !startBound;
	
		return (startBound) ? 0 : runInfo->runWidth + runInfo->runExtraWidth;
	}
	
	LineRunDisplayInfo runDisplayInfo;
	this->GetRunDisplayInfo(runInfo, &runDisplayInfo);

	CRunObject* runObj = runInfo->runObj;
	
	if (runInfo->runCtrlChar || (runObj->GetObjFlags() & kIndivisibleRun))
		return IndivisibleCharToPixel(runDisplayInfo, offset);
	else
		return runObj->CharToPixel(runDisplayInfo, offset);
}
//***************************************************************************************************

Fixed CLine::RunMeasure(const LineRunInfo* runInfo)
{
	LineRunDisplayInfo runDisplayInfo;
	this->GetRunDisplayInfo(runInfo, &runDisplayInfo);
	
	return runInfo->runObj->MeasureWidth(runDisplayInfo);
}
//***************************************************************************************************

void CLine::DoLineLayout(long lineStart, short lineLen, short maxLineWidth)
//lineLen == 0 ==> empty line (useful for empty texts, to calc the caret position)

{
	if (fStart == lineStart)
		return;
	
	fMaxWidth = LongToFixed(maxLineWidth);

	//SaveTextEnv, LockChars many called routines (DefineRunWidths, ..) rely on this
	TTextEnv savedTextEnv;
	fStyledText->SetTextEnv(&savedTextEnv);
	
	fStyledText->LockChars();

	fStart = lineStart;
	
	#ifdef txtnRulers
	fLineRuler = (CRulerObject*) fRulersRanges->OffsetToObject(lineStart);
	#endif

	this->DefineRuns(lineStart, lineLen, fUnorderedRuns); //fill gUnorderedRuns

	fLastRun = short(fUnorderedRuns->CountElements()) - 1; //-ive if no Runs
	
	fUnorderedRuns->LockArray(); //assumed in "CalcVisibleLength", "DefineRunWidths" and "OrderRuns"

	short blankRuns = 0;
	
	short visLength = (lineLen) ? this->CalcVisibleLength(fUnorderedRuns, lineLen, &blankRuns) : 0;
	
	fNoDraw = visLength == 0;
	
	#ifdef txtnRulers
	char lineJust;
	fLineRuler->GetAttributeValue(kJustAttr, &lineJust);

	if (lineJust == kForceFullJust) {
		if (visLength)
			lineJust = kFullJust;
		else
			lineJust = (fDirection == kLR) ? kLeftJust : kRightJust;
	}
	else {
		if (lineJust == kFullJust) {
			long lastChar = lineStart+lineLen-1;
			
			if (!visLength || (*fStyledText->GetCharPtr(lastChar) == kCRCharCode) || (lastChar == fStyledText->CountCharsBytes()-1))
				lineJust = (fDirection == kLR) ? kLeftJust : kRightJust;
		}
	}

	if (lineJust == kFullJust) {
		fVisLen = visLength;
		
		if (blankRuns) {
			fLastRun -= blankRuns;
			gUnorderedRuns->SetElementsCount(fLastRun+1);
		}
		
		//fLastRun >= 0 (since visLength > 0)
		LineRunInfo* lastRunInfo = (LineRunInfo*) gUnorderedRuns->GetElementPtr(fLastRun);
		lastRunInfo->runLen = visLength-lastRunInfo->runOffset;
	}
	else
	#endif
	fVisLen = lineLen;
	
	Boolean paragStart;
	
	#ifdef txtnRulers
	paragStart = (lineStart == 0) || (*fStyledText->GetCharPtr(lineStart-1) == kCRCharCode);

	fLeftEdge = fLineRuler->GetLineLeftBlanks(fDirection, paragStart, fNumer.h, fDenom.h);
	
	Fixed effectiveWidth = fMaxWidth
								- fLineRuler->GetLineLeftBlanks(fDirection, paragStart, fNumer.h, fDenom.h)
								- fLineRuler->GetLineRightBlanks(fDirection, paragStart, fNumer.h, fDenom.h);
	//"effectiveWidth" takes into account marges and indent
		
	#else
	paragStart = false; //DefineRunWidths will not use it in this case
	
	Fixed effectiveWidth = fMaxWidth;
	
	fLeftEdge = 0;
	#endif //txtnRulers
	
	Fixed blankWidth = effectiveWidth - this->DefineRunWidths(fUnorderedRuns, effectiveWidth, paragStart);
	/*blankWidth can be -ive if lineJust != kFullJust since in this case the whites are included, but
	this has no vis effect since if just==right or left the extra whites will be drawn outside the line
	pix space
	*/
	
	this->OrderRuns(fUnorderedRuns); //define fOrderedRuns

	
	#ifdef txtnRulers
	if (lineJust == kFullJust)
		this->DefineRunsExtraWidths(blankWidth);
	else {
		if (lineJust == kRightJust)
			fLeftEdge += blankWidth;
		else {
			if (lineJust == kCenterJust)
				fLeftEdge += FixDiv(blankWidth, LongToFixed(2));
		}
	}
	#else
	if (fDirection == kRL)
		fLeftEdge += blankWidth;
	#endif

	fUnorderedRuns->UnlockArray();
	
	fStyledText->UnlockChars();
	fStyledText->RestoreTextEnv(savedTextEnv);
}
//***************************************************************************************************

short CLine::CalcVisibleLength(CArray* theRuns, short lineLen, short* blankRuns)
//assume lineLen != 0, "theRuns" is locked and curr port font is saved
{
	Assert_(lineLen != 0);
	
	register LineRunInfo* firstRunInfo = (LineRunInfo*) theRuns->GetElementPtr(0);
	register LineRunInfo* runInfo = (LineRunInfo*) theRuns->GetLastElementPtr();
	
	::SetSysDirection(fDirection);
	//became necessary with CUBE-E where "VisibleLength" is dependent on sysJust
	
	register short visLen = lineLen;
	
	*blankRuns = 0;
	
	register short visRunLen;
	do  {
		short runFont;
		if (!runInfo->runObj->GetAttributeValue(kFontAttr, &runFont))
			break; //run doesn't have a font (pict, sound..)
		
		register short runLen = runInfo->runLen;
		
		TextFont(runFont);
		visRunLen = short(VisibleLength(Ptr(fStyledText->GetCharPtr(fStart+runInfo->runOffset)), runLen));
		
		visLen -= (runLen-visRunLen);
		
		if (!visRunLen)
			++(*blankRuns);
		
	} while (!visRunLen && (--runInfo >= firstRunInfo));
	
	return visLen;
}
//***************************************************************************************************

void CLine::InsertRun(long runStart, short runLen, CRunObject* runObj, CArray* unorderedRuns)
//runStart is an offset from the whole text
{
	short remainingLen = runLen;
	
	while (remainingLen) {
		long ctrlOffset = gParagCtrlChars.GetCurrCtrlOffset()-runStart; //offset from runStart, -ive if no curr

		LineRunInfo* runInfo = (LineRunInfo*) unorderedRuns->InsertElements(1, nil); //¥¥ neglect error (drawing, clicking...!)
		
		runInfo->runObj = runObj;
		long forceLongEval = runStart-fStart;
		runInfo->runOffset = short(forceLongEval);
		runInfo->runExtraWidth = 0;

		if (ctrlOffset == 0) {
			runInfo->runLen = 1;
			runInfo->runCtrlChar = gParagCtrlChars.GetCurrCtrlChar();
			gParagCtrlChars.Next();
		}
		else {
			if ((ctrlOffset > 0) && (remainingLen > ctrlOffset))
				runInfo->runLen = short(ctrlOffset);
			else
				runInfo->runLen = remainingLen;
				
			runInfo->runCtrlChar = 0;
		}
		
		runStart += runInfo->runLen;
		remainingLen -= runInfo->runLen;
	}
}
//***************************************************************************************************

void CLine::DefineRuns(long lineStart, short lineLen, CArray* unorderedRuns)
{
	unorderedRuns->SetElementsCount(0);
	
	gParagCtrlChars.Define(fStyledText->GetCharPtr(0), lineStart, lineStart+lineLen);
	
	while (lineLen > 0) {
		long runLen;
		CRunObject* runObj;
		fRunsRanges->GetNextRun(lineStart, &runObj, &runLen);
		if (runLen > lineLen)
			runLen = lineLen;
		
		this->InsertRun(lineStart, short(runLen), runObj, unorderedRuns);
		
		lineStart += runLen;
		lineLen -= short(runLen);
	}
}
//***************************************************************************************************

short CLine::GetRunDirection(const LineRunInfo& runInfo)
{
	if (runInfo.runCtrlChar)
		return  fDirection;
	else {
		short theDir = runInfo.runObj->GetRunDir();
		
		if (theDir == kLineDirection)
			theDir = fDirection;
			
		return theDir;
	}
}
//***************************************************************************************************


// #ifdef powerc
enum {
	uppRunDirProcInfo = kPascalStackBased
		 | RESULT_SIZE(SIZE_CODE(sizeof(Boolean)))
		 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(short)))
		 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(long)))
};

// #endif

struct RunDirProcData {
	LineRunInfo* runsInfo;
	CLine* theLine;
};
typedef RunDirProcData* RunDirProcDataPtr;

pascal Boolean RunDirProc(short runIndex, RunDirProcDataPtr dataLink);
pascal Boolean RunDirProc(short runIndex, RunDirProcDataPtr dataLink)
{
	short theDir = dataLink->theLine->GetRunDirection(*(dataLink->runsInfo + runIndex));
	return theDir == kRL;
}
//***************************************************************************************************

#ifndef powerc
void Push_D3()	= 0x2F03;		// MOVE.L	D3, -(A7)
void Pop_D3() 	= 0x261F; 	// MOVE.L	(A7)+, D3
#endif

void CLine::OrderRuns(CArray* unorderedRuns)
{
	short countRuns = fLastRun+1;
	
	if (countRuns <= 0)
		return; //fLastRun is -ive if no runs
	
	#ifdef txtnDebug
	if (countRuns > kMaxLineRuns)
		SignalPStr_("\p line having too much runs, will not be ordered");
	#endif
	
	fOrderedRuns->SetElementsCount(countRuns); //¥¥ neglect error (drawing, clicking...!)
	
	LineRunInfo* unOrderedRunPtr = (LineRunInfo*) unorderedRuns->GetElementPtr(0);
	
	// a line having more than 100 runs will not be ordered!
	if (!g2Directions || (countRuns == 1) || (countRuns > kMaxLineRuns)) {
		fOrderedRuns->SetElementsVal(0, Ptr(unOrderedRunPtr), countRuns);
		return;
	}
	//(countRuns == 1) is also made since GetFormatOrder does nothing if no of runs <= 1
	
	short orderedRunsIndices[kMaxLineRuns];
	
	RunDirProcData dirProcData;
	dirProcData.runsInfo = unOrderedRunPtr;
	dirProcData.theLine = this;

//	#ifdef powerc
	StyleRunDirectionUPP runDirRD = NewStyleRunDirectionProc(RunDirProc);
//	(uppRunDirProcInfo, &RunDirProc);
	
#ifndef	powerc
	Push_D3();
#endif
	GetFormatOrder(FormatOrderPtr(orderedRunsIndices), 0, fLastRun
								 , fDirection == kRL, runDirRD, Ptr(&dirProcData));
#ifndef	powerc
	Pop_D3();
#endif

/*	#else
	Push_D3(); //GetFormatOrder bug in 7.0: Destroy D3!
	GetFormatOrder(FormatOrderPtr(orderedRunsIndices), 0, fLastRun
								 , fDirection == kRL, StyleRunDirectionUPP(&RunDirProc), Ptr(&dirProcData));
	Pop_D3();
	#endif
*/	
	//Copy the runs in their visible order to fOrderedRuns
	LineRunInfo* orderedRunPtr = (LineRunInfo*) fOrderedRuns->GetElementPtr(0);
	short* orderedIndex = orderedRunsIndices;
	
	while (--countRuns >= 0)
		*orderedRunPtr++ = *(unOrderedRunPtr + *orderedIndex++);
}
//***************************************************************************************************

#ifdef txtnRulers
Fixed CLine::CalcAlignTabWidth(TPendingTab* pendingTab, Fixed tabTrailWidth, const LineRunInfo& runAfter)
{
	short alignOffset = SearchChar(pendingTab->theTab.alignChar
																, fStyledText->GetCharPtr(fStart+runAfter.runOffset)
																, runAfter.runLen);
																
	if (alignOffset < 0)
		return 0; //alignChar not found, still pending
	
	pendingTab->valid = false;
	Fixed tabWidth = pendingTab->tabWidth-tabTrailWidth;
	
	if (alignOffset) {
		LineRunInfo runToMeasure = runAfter;
		runToMeasure.runLen = alignOffset;
		tabWidth -= this->RunMeasure(&runToMeasure);
	}
	
	return (tabWidth >= 0) ? tabWidth : 0;
}
//***************************************************************************************************
#endif

Fixed CLine::DefineRunWidths(CArray* unorderedRuns, Fixed maxWidth, Boolean paragStart) // return sum of run widths
//works on the unordered runs (to calculate the tab widths as the formatter did)
{
	if (fLastRun  < 0) return 0;
	
	LineRunInfo* runInfo = (LineRunInfo*) unorderedRuns->GetElementPtr(0);
	
	Fixed sumWidth = 0;
	
	#ifdef txtnRulers
	TPendingTab pendingTab;

	pendingTab.valid = false;
	LineRunInfo* pendingTabRun = nil;
	Fixed tabTrailWidth = 0;
	
	Fixed blanks;
	if (fDirection == kLR)
		blanks = fLineRuler->GetLineLeftBlanks(fDirection, paragStart, fNumer.h, fDenom.h);
	else
		blanks = fLineRuler->GetLineRightBlanks(fDirection, paragStart, fNumer.h, fDenom.h);
	#endif
	
	short lastRun = fLastRun;
	
	for (short ctr = 0; ctr <= lastRun; ++ctr, ++runInfo) {
		if (runInfo->runCtrlChar) {
			if (runInfo->runCtrlChar != kTabCharCode) {
				runInfo->runWidth = 0;
				continue;
			}
			
			#ifdef txtnRulers
			if (pendingTab.valid) {
				pendingTabRun->runWidth = fLineRuler->CalcPendingTabWidth(pendingTab, tabTrailWidth, maxWidth-sumWidth);
				sumWidth += pendingTabRun->runWidth;
			}
			
			runInfo->runWidth = fLineRuler->GetTabWidth(sumWidth+blanks, fDirection, &pendingTab, fNumer.h, fDenom.h);
			
			if (pendingTab.valid) {
				pendingTabRun = runInfo;
				tabTrailWidth = 0;
				runInfo->runWidth = 0;
			}
			
			#else
//			runInfo->runWidth = GetDefaultTabWidth(sumWidth, fNumer.h, fDenom.h);
			runInfo->runWidth = GetDefaultTabWidth(sumWidth, 1, 1);
			#endif
		}
		else {
			runInfo->runObj->SetRunEnv();
			
			runInfo->runWidth = this->RunMeasure(runInfo);
			
			
			#ifdef txtnRulers
			if (pendingTab.valid) {
				if (pendingTab.theTab.kind == kAlignTab) {
					pendingTabRun->runWidth = this->CalcAlignTabWidth(&pendingTab, tabTrailWidth, *runInfo);
					sumWidth += pendingTabRun->runWidth;
				}
					
				tabTrailWidth += runInfo->runWidth;
			}
			#endif
		}
		sumWidth += runInfo->runWidth;
	}

	#ifdef txtnRulers
	if (pendingTab.valid) {//last tab is pending
		pendingTabRun->runWidth = fLineRuler->CalcPendingTabWidth(pendingTab, tabTrailWidth, maxWidth-sumWidth);
		sumWidth += pendingTabRun->runWidth;
	}
	#endif
	
	return sumWidth;
}
//***************************************************************************************************

#ifdef txtnRulers
Fixed CLine::CalcFullJustifPortions(Fixed* portionRunArr, short* cFullJustifRuns)
{
	fOrderedRuns->LockArray();
	
	short lastRun = fLastRun;
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(lastRun);
	
	Fixed sumPortions = 0;
	*cFullJustifRuns = 0;
	
	for (short ctr = 0; ctr <= lastRun; ++ctr, --runInfo) {
		if (runInfo->runCtrlChar) {
			if (runInfo->runCtrlChar == kTabCharCode)
				break;
				
			*portionRunArr++ = 0;
		}
		else {
			CRunObject* run = runInfo->runObj;
			
			++(*cFullJustifRuns);
			
			run->SetRunEnv();

			LineRunDisplayInfo runDisplayInfo;
			this->GetRunDisplayInfo(runInfo, &runDisplayInfo);
			
			*portionRunArr = run->FullJustifPortion(runDisplayInfo);

			sumPortions += *portionRunArr++;
		}
	}
	
	fOrderedRuns->UnlockArray();
	
	return sumPortions;
}
//***************************************************************************************************
#endif //txtnRulers

#ifdef txtnRulers
void CLine::DefineRunsExtraWidths(Fixed theSlope)
{
	if ((theSlope == 0) || (fLastRun >= kMaxLineRuns) || (fLastRun < 0))
		return;
	// a line having more than 100 runs will not be full justified!
	
	if (fLastRun == 0) {
		((LineRunInfo*) fOrderedRuns->GetElementPtr(0))->runExtraWidth = theSlope;
		return;
	}
	
	Fixed portionRun[kMaxLineRuns];
	
	Fixed sumPortions;
	short cFullJustifRuns;
	sumPortions = this->CalcFullJustifPortions(portionRun, &cFullJustifRuns);
	if (sumPortions) { //can be 0 : a line with only numerics with no spaces or the tab constraints doesn't allow for any portion
		Fixed* portionRunPtr = portionRun;
		LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(fLastRun);
		
		for (short ctr = 0; ctr < cFullJustifRuns; ++ctr, --runInfo)
			runInfo->runExtraWidth = FixMul(FixDiv(*portionRunPtr++, sumPortions), theSlope);
	}
}
//***************************************************************************************************
#endif //txtnRulers

void CLine::Draw(const Rect& lineRect, short lineAscent)
{
	if (fNoDraw)
		return;

	Fixed runLeftEdge = LongToFixed(lineRect.left) + fLeftEdge;
	
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->LockArray();
	
	short vBase = lineRect.top + lineAscent;
	short lastRun = fLastRun;
	for (short runIndex=0; runIndex <= lastRun; ++runIndex, ++runInfo) {
		if (!runInfo->runCtrlChar) {
			CRunObject* run = runInfo->runObj;
			
			run->SetRunEnv();
		
			MoveTo(FixRound(runLeftEdge), vBase);


			LineRunDisplayInfo runDisplayInfo;
			this->GetRunDisplayInfo(runInfo, &runDisplayInfo);
			
			run->Draw(runDisplayInfo, runLeftEdge, lineRect, lineAscent);
		}
		runLeftEdge += runInfo->runWidth + runInfo->runExtraWidth;
	}
	
	fOrderedRuns->UnlockArray();
}
//***************************************************************************************************


void CLine::SetDirection(char newDirection)
{
	fDirection = newDirection;
	this->Invalid();
}
//***************************************************************************************************

short CLine::CharToRun(TOffset charOffset, Fixed* runLeftEdge/*= nil*/)
/*
¥charOffset.offset is wrt the whole text
¥charOffset.offset may be > Sum (run->runLen) for full justif lines since in this case run->runLen is
	the vis len (without spaces) and the caret may be in the invis spaces (back space at the end of a
	full justif line, for instance)
¥return the runIndex in the fOrderedRuns, -1 if no runs (empty line)
*/
{
	short lastRun = fLastRun;
	
	if (lastRun < 0)
		return -1;
	
	charOffset.offset -=  fStart;
	
	Fixed leftEdge = fLeftEdge;
	
	if (charOffset.trailEdge && (charOffset.offset))
		--charOffset.offset;
	charOffset.offset = MinLong(charOffset.offset, fVisLen-1);
	
	short runNo;
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(0);
	for (runNo = 0; runNo < lastRun; ++runNo, ++runInfo) {
		if ((charOffset.offset >= runInfo->runOffset)
			&& (charOffset.offset < runInfo->runOffset+runInfo->runLen))
			break;
			
		leftEdge += runInfo->runWidth + runInfo->runExtraWidth;
	}
	
	if (runLeftEdge)
		*runLeftEdge = leftEdge;
		
	return runNo;
}
//***************************************************************************************************

short CLine::CharacterToPixel(TOffset charOffset, char caretDir /*= kRunDir*/)
/*
¥charOffset.offset is wrt the whole text
¥ charOffset.offset may be > Sum (run->runLen) (see comment at CharToRun)
*/
{
	Fixed runLeftEdge;
	short charRun = this->CharToRun(charOffset, &runLeftEdge);
	if (charRun < 0)
		return FixRound(fLeftEdge);
	
	fOrderedRuns->LockArray();
	
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(charRun);

	charOffset.offset -=  (fStart+runInfo->runOffset);
	
	if (caretDir != kRunDir) {
		if ((charOffset.offset == runInfo->runLen)
				&& (this->GetRunDirection(*runInfo) != caretDir)
				&& (caretDir == fDirection))
			charOffset.offset = 0;
	}
	
	fStyledText->LockChars();

	TTextEnv savedTextEnv;
	fStyledText->SetTextEnv(&savedTextEnv);

	runInfo->runObj->SetRunEnv();
	
	short result = FixRound(runLeftEdge + this->RunCharToPixel(runInfo, short(charOffset.offset)));
	
	fStyledText->RestoreTextEnv(savedTextEnv);
	fStyledText->UnlockChars();
	
	fOrderedRuns->UnlockArray();
	
	return result;
}
//***************************************************************************************************

short CLine::CalcRunHilite(short startOff, short endOff, short runVisIndex
												, Fixed* startPix, Fixed* pixWidth, Fixed* runLeft, Boolean lineBounds)
/* runVisIndex is index in fOrderedRuns
startOff, endOff are offsets from the start of the line.
endOff > startOff.
startOff-endOff need not be completely in the run char range
runLeft is input-output
Assume fOrderedRuns, fTextChars are Locked, the Style Env is saved
startPix is returned wrt to the start of the line
returns count selected chars */
{
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(runVisIndex);
	Fixed runWidth = runInfo->runWidth + runInfo->runExtraWidth;
	
	Fixed prevRunRight = *runLeft;
	*runLeft += runWidth;
	
	startOff -= runInfo->runOffset;
	endOff -= runInfo->runOffset;
	if ((endOff < 0) || (startOff > runInfo->runLen))
		return 0;
	
	if (startOff < 0)
		startOff = 0;
	if (endOff > runInfo->runLen)
		endOff = runInfo->runLen;
	if (endOff == startOff)
		return 0;
	
	runInfo->runObj->SetRunEnv();
	
	*startPix = prevRunRight;
	
	short runDir = this->GetRunDirection(*runInfo);
	
	if (startOff == 0) {
		if (runDir == kLR) {
			if ((runVisIndex == 0) && lineBounds)
				*startPix = 0;  //else it is already = prevRunRight
		}
		else {
			if ((runVisIndex == fLastRun) && lineBounds)
				*startPix = fMaxWidth;
			else
				*startPix += runWidth;
		}
	}
	else
		*startPix += this->RunCharToPixel(runInfo, startOff);
	
	Fixed endPix = prevRunRight;
	if (endOff == runInfo->runLen) {
		if (runDir == kLR) {
			if ((runVisIndex == fLastRun) && lineBounds)
				endPix = fMaxWidth;
			else
				endPix += runWidth;
		}
		else
			if ((runVisIndex == 0) && lineBounds)
				endPix = 0; //else endPix is already = prevRunRight
	}
	else
		endPix += this->RunCharToPixel(runInfo, endOff);
		
	if (endPix < *startPix)
		SwapLong(startPix, &endPix);
	
	*pixWidth = endPix - *startPix;
	
	return endOff-startOff;
}
//***************************************************************************************************


short CLine::PixelToRun(Fixed* hPix)
/*
¥return the runIndex in the fOrderedRuns
¥hPix is input/output, on exit it contains the remaining pix
*/
{
	*hPix -= fLeftEdge;
	if (*hPix < 0)
		*hPix = 0;
	
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(0);
	
	Fixed runTotalWidth = 0;
	short lastRun = fLastRun;
	for (short runIndex=0; runIndex <= lastRun; ++runIndex, ++runInfo) {
		runTotalWidth = runInfo->runWidth + runInfo->runExtraWidth;
		if (*hPix > runTotalWidth)
			*hPix -= runTotalWidth;
		else
			return runIndex;
	}
	
	//The pix offset is beyond the last vis run	
	*hPix = runTotalWidth;
	return fLastRun;
}
//***************************************************************************************************

CRunObject* CLine::PixelToCharacter(Fixed hPixel, TOffsetRange* charRange)
/* offsets in charRange are wrt to the start of the Text (not start of the line)
-the fn result is the range CRunObject*, nil if empty line
*/
{
	if (fLastRun < 0) { //emptyLine
		charRange->Set(fStart, fStart, false, false);
		return nil;
	}

	short charRun = this->PixelToRun(&hPixel);
	
	fStyledText->LockChars();
	fOrderedRuns->LockArray();
	
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(charRun);
	
	TTextEnv savedTextEnv;
	fStyledText->SetTextEnv(&savedTextEnv);
	
	CRunObject* runObj = runInfo->runObj;
	
	runObj->SetRunEnv();
	
	LineRunDisplayInfo runDisplayInfo;
	this->GetRunDisplayInfo(runInfo, &runDisplayInfo);
	
	if (runInfo->runCtrlChar || (runObj->GetObjFlags() & kIndivisibleRun)) {
		if (runInfo->runCtrlChar == kCRCharCode)
			charRange->Set(0, 0, false, false);
		//trailEdge was set to true (for clicking beyond a cr to follow preceding style, but should be false for double clicking on the cr to work
		else
			IndivisiblePixelToChar(runDisplayInfo, hPixel, charRange);
	}
	else
		runObj->PixelToChar(runDisplayInfo, hPixel, charRange);
	
	charRange->Offset(fStart + runInfo->runOffset);
	
	fStyledText->RestoreTextEnv(savedTextEnv);
	
	fStyledText->UnlockChars();
	fOrderedRuns->UnlockArray();
	
	return runInfo->runObj;
}
//***************************************************************************************************

Boolean CLine::VisibleToBackOrder(TOffset* charOffset, char reqDir)
/*
	define the back storage order for a run with a direction "reqDir" to be inserted at "charOffset"
	(inverse of "GetFormatOrder"?)
¥charOffset is offset from the start of the whole text (input/output)
*/
{
	short charRun = this->CharToRun(*charOffset);
	if (charRun < 0)
		return false;
	//charRun is the visible order of the run containing charOffset
	
	LineRunInfo* runInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(charRun);
	
	short runDir = this->GetRunDirection(*runInfo);
	
	if (runDir == reqDir)
		return false;
	
	long startOffset = charOffset->offset - fStart - runInfo->runOffset;
	Boolean runStart = (startOffset == 0);
	Boolean runEnd = (startOffset == runInfo->runLen);
	if (!runStart && !runEnd)
		return false;
	
	Boolean boundIsNextVis = (runEnd && (runDir == kLR)) || (runStart && (runDir == kRL));
	//!boundIsNextVis means ((runEnd && (runDir == kRL)) || (runStart && (runDir == kLR))) since either runStart or runEnd is true
	
	short nearRun = (boundIsNextVis) ? ++charRun : --charRun;
	if ((nearRun >= 0) && (nearRun <= fLastRun)) {
		LineRunInfo* nearRunInfo = (LineRunInfo*) fOrderedRuns->GetElementPtr(nearRun);
		if (this->GetRunDirection(*nearRunInfo) == runDir)
			return false;
		
		charOffset->offset = fStart+nearRunInfo->runOffset;
		if (boundIsNextVis && (runDir == kLR) || !boundIsNextVis && (runDir == kRL)) {
			charOffset->offset += nearRunInfo->runLen;
			charOffset->trailEdge = true;
		}
		else
			charOffset->trailEdge = false;
	}
	else {
		if (reqDir != fDirection)
			return false;
		
		charOffset->offset = fStart+runInfo->runOffset;
		if (runStart) {
			charOffset->offset += runInfo->runLen;
			charOffset->trailEdge = true;
		}
		else
			charOffset->trailEdge = false;
	}
	
	return true;
}
//***************************************************************************************************


void CLine::Invalid() {fStart = -1;}

Boolean	 CLine::IsValid(void) const
{
	return (fStart != -1);
}