/*
 *	@doc INTERNAL
 *
 *	@module	dispprt.cpp	-- Special logic for printer object |
 *  
 *  Authors:
 *      Original RichEdit code: David R. Fulmer
 *      Christian Fortini
 *      Jon Matousek
 */
#include "_common.h"
#include "_dispprt.h"
#include "_edit.h"
#include "_font.h"
#include "_measure.h"
#include "_render.h"
#include "_select.h"

#define PARA_NUMBER_NOT_SET ((WORD) -1)

ASSERTDATA

/*
 *	CDisplayPrinter::CDisplayPrinter(ped, hdc, x, y, prtcon)
 *
 *	@mfunc
 *		Contructs a object that can be used to print a text control
 */
CDisplayPrinter::CDisplayPrinter(
	CTxtEdit* ped,		//@parm CTxtEdit
	HDC hdc, 			//@parm HDC for drawing
	RECT *prc, 			//@parm dimensions of current area to print to.
	SPrintControl prtcon//@parm Special controls for this print object
)	
	: CDisplayML(ped), _prtcon(prtcon)
{
	TRACEBEGIN(TRCSUBSYSPRT, TRCSCOPEINTERN, "CDisplayPrinter::CDisplayPrinter");
	Assert (hdc);

	_fNoUpdateView = TRUE;

	SetTflow(ped->_pdp->GetTflow());

	GetDupDvpFromRect(*prc, GetTflow(), _dulTarget, _dvlTarget);
	_wNumber = PARA_NUMBER_NOT_SET;
}

/*
 *	CDisplayPrinter::SetPrintDimensions(prc)
 *
 *	@mfunc
 *		Set area to print.
 */
void CDisplayPrinter::SetPrintDimensions(
	RECT *prc)			//@parm dimensions of current area to print to.
{
	GetDupDvpFromRect(*prc, GetTflow(), _dulTarget, _dvlTarget);
}

/*
 *	CDisplayPrinter::FormatRange(cpFirst, cpMost)
 *
 *	@mfunc
 *		Format a range of text into this display and used only for printing.
 *
 *	@rdesc
 *		actual end of range position (updated)	
 */
LONG CDisplayPrinter::FormatRange(
	LONG cpFirst, 		//@parm Start of text range
	LONG cpMost,		//@parm End of text range
	BOOL fWidowOrphanControl)	//@parm If TRUE, suppress widow/orphan
{
	TRACEBEGIN(TRCSUBSYSPRT, TRCSCOPEINTERN, "CDisplayPrinter::FormatRange");

	LONG		cch;
	WCHAR		ch;
	BOOL		fFirstInPara = TRUE;
	CLine		liTarget;
	CLine	*	pliNew = NULL;
	LONG		dvp;
	LONG		dvt;
	const CDevDesc *pddTarget = GetDdTarget() ? GetDdTarget() : this;

	// Set client height for zooming
	_dvpClient = LYtoDY(_dvlTarget);

	// Set maximum in terms of target DC.
	LONG	dvtMax = pddTarget->LYtoDY(_dvlTarget);

	if(cpMost < 0)
		cpMost = _ped->GetTextLength();

	CMeasurer me(this);
	
	cpFirst = me.SetCp(cpFirst);		// Validate cpFirst while setting me
	ch = me.GetChar();

	// TODO: COMPATIBILITY ISSUE:  Richedit 1.0 adjusted to before a
	// CRLF/CRCRLF boundary.  if_ped->fUseCRLF(), adjust accordingly

	_cpMin = cpFirst;
	_cpFirstVisible = cpFirst;
	
	dvt = 0;
	dvp = 0;
	if(me.GetCp())
		fFirstInPara = me._rpTX.IsAfterEOP();

	// Clear line CArray
	DeleteSubLayouts(0, -1);
	Clear(AF_DELETEMEM);

	// Assume that we will break on words
	UINT uiBreakAtWord = MEASURE_BREAKATWORD;

	if(_prtcon._fPrintFromDraw)
	{
		// This is from Draw so we want to take inset into account
		LONG dut = LYtoDY(_dulTarget);

		GetViewDim(dut, dvtMax);
		_dupView = dut;

		// Restore client height
		_dvpClient = this->LYtoDY(_dvlTarget);
	}
	else			// Message-based printing always does word wrap
		SetWordWrap(TRUE);

	// Set paragraph numbering. This is a fairly difficult problem
	// because printing can start anywhere and end anywhere. However,
	// most printing will involve a contiguous range of pages. Therefore,
	// we cache the paragraph number and the cp for that number and
	// only resort to looking in the line array if the cached information
	// has become invalid.
	if ((PARA_NUMBER_NOT_SET == _wNumber) || (cpFirst != _cpForNumber))
	{
		CLinePtr rp(_ped->_pdp);
		rp.SetCp(cpFirst, FALSE);
		_wNumber = rp.GetNumber();
		_cpForNumber = cpFirst;
	}
	
	me.SetNumber(_wNumber);
	
	while(me.GetCp() < cpMost)
	{
		// Add one new line
		pliNew = Add(1, NULL);
		if(!pliNew)
		{
			_ped->GetCallMgr()->SetOutOfMemory();
			goto err;
		}

		// Store the current number of the paragraph. We do it
		// here because we have to measure and that potentially
		// updates the number of the paragraph in the measurer
		// for a line that might not be on the page.
		_wNumber = me.GetNumber();

		// Stuff some text into this new line
		if(!Measure(me, pliNew, Count() - 1, uiBreakAtWord | (fFirstInPara ? MEASURE_FIRSTINPARA : 0), &liTarget))
		{
			Assert(FALSE);
			goto err;
		}

		//FUTURE (keithcu) We break tables across pages on row boundaries which
		//isn't very granular--especially in the HTML world.

		// Note, we always put at least one line on a page. Otherwise, if the 
		// first line is too big, we would cause our client to infinite loop
		// because we would never advance the print cp.
		if(_cel > 1 && (dvt + liTarget.GetHeight() > dvtMax))
		{
			cch = -pliNew->_cch;		// Bump last line to next page
			_cel--;						// One less line

#if 0
			CLine *pli = pliNew - 1;	// Point at previous line

			// If this line and the previous one are in the same para and
			// either this one ends in an EOP or the previous one starts
			// a para, bump both to following page (widow/orphan)
			if(fWidowOrphanControl)
			{
				if(_cel > 1 && !fFirstInPara &&
				   (pli->_bFlags & fliFirstInPara || (pliNew->_bFlags & fliHasEOP)))
				{
					cch -= pli->_cch;
					_cel--;					// One less line
					pli--;					// Point to previous line
				}
				if(_cel > 1 && pli->_nHeading)
				{							// Don't end page with a heading
					cch -= pli->_cch;
					_cel--;					// One less line
				}
			}
#endif
			me.Move(cch);			// Move back over lines discarded
			delete pliNew->GetPlo(); //Delete the CLayout which didn't get added to the CLineArray
			break;
		}

		fFirstInPara = (pliNew->_fHasEOP);

		dvt += liTarget.GetHeight();
		dvp += pliNew->GetHeight();
		if (me.GetPrevChar() == FF)
			break;
	}

	// If there was no text, then add a single blank line
	if(!pliNew)
	{
		pliNew = Add(1, NULL);
		if(!pliNew)
		{
			_ped->GetCallMgr()->SetOutOfMemory();
			goto err;
		}
		me.NewLine(fFirstInPara);
		*pliNew = me._li;
	}

	// Update display height
	_dvp = dvp;

	// Update display width
	_dupLineMax = CalcDisplayDup();

	cpMost = me.GetCp();
	_cpCalcMax = cpMost;
	_vpCalcMax = _dvp;

	// Update paragraph caching information.
	_cpForNumber = cpMost;

	return cpMost;

err:
	DeleteSubLayouts(0, -1);
	Clear(AF_DELETEMEM);
	_dupLineMax = 0;
	_dvp = 0;
	return -1;
}

/*
 *	CDisplayPrinter::GetNaturalSize(hdcDraw, hicTarget, dwMode, pwidth, pheight)
 *
 *	@mfunc
 *		Recalculate display to input width & height for TXTNS_FITTOCONTENT.
 *
 *	@rdesc
 *		S_OK - Call completed successfully <nl>
 *
 *	@devnote
 *		This assumes that FormatRange was called just prior to this.
 */
HRESULT	CDisplayPrinter::GetNaturalSize(
	HDC hdcDraw,		//@parm DC for drawing
	HDC hicTarget,		//@parm DC for information
	DWORD dwMode,		//@parm Type of natural size required
	LONG *pwidth,		//@parm Width in device units to use for fitting 
	LONG *pheight)		//@parm Height in device units to use for fitting
{
	TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CDisplayPrinter::GetNaturalSize");

	*pwidth = _dupLineMax;
	*pheight = _dvp;
	return S_OK;
}

/*
 *	CDisplayPrinter::IsPrinter()
 *
 *	@mfunc
 *		Returns whether this is a printer
 *
 *	@rdesc
 *		TRUE - is a display to a printer
 *		FALSE - is not a display to a printer
 */
BOOL CDisplayPrinter::IsPrinter() const
{
	AssertSz(_hdc, "CDisplayPrinter::IsPrinter no hdc set");
	
	return GetDeviceCaps(_hdc, TECHNOLOGY) == DT_RASPRINTER;
}