/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Support for printing.
 */
#include "stdafx.h"
#include "Print.h"
#include "Main.h"
#include "Preferences.h"


/*
 * ==========================================================================
 *		PrintStuff
 * ==========================================================================
 */

/*static*/ const char* PrintStuff::kCourierNew = _T("Courier New");
/*static*/ const char* PrintStuff::kTimesNewRoman = _T("Times New Roman");

/*
 * Set up various values.
 */
void
PrintStuff::InitBasics(CDC* pDC)
{
	ASSERT(pDC != nil);
	ASSERT(fpDC == nil);

	fpDC = pDC;

	/* make sure we're in MM_TEXT mode */
	pDC->SetMapMode(MM_TEXT);

	/* get device capabilities; logPixels is e.g. 300 */
	fVertRes = pDC->GetDeviceCaps(VERTRES);
	fHorzRes = pDC->GetDeviceCaps(HORZRES);
	fLogPixelsX = pDC->GetDeviceCaps(LOGPIXELSX);
	fLogPixelsY = pDC->GetDeviceCaps(LOGPIXELSY);
	WMSG4("+++ logPixelsX=%d logPixelsY=%d fHorzRes=%d fVertRes=%d\n",
		fLogPixelsX, fLogPixelsY, fHorzRes, fVertRes);
}

/*
 * Create a new font, based on the number of lines per page we need.
 */
void
PrintStuff::CreateFontByNumLines(CFont* pFont, int numLines)
{
	ASSERT(pFont != nil);
	ASSERT(numLines > 0);
	ASSERT(fpDC != nil);

	/* required height */
	int reqCharHeight;
	reqCharHeight = (fVertRes + numLines/2) / numLines;

	/* magic fudge factor */
	int fudge = reqCharHeight / 24;
	WMSG2("  Reducing reqCharHeight from %d to %d\n",
		reqCharHeight, reqCharHeight - fudge);
	reqCharHeight -= fudge;

	pFont->CreateFont(reqCharHeight, 0, 0, 0, FW_NORMAL, 0, 0, 0,
		DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
		DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, kTimesNewRoman);
	/*fpOldFont =*/ fpDC->SelectObject(pFont);
}


/*
 * Returns the width of the string.
 */
int
PrintStuff::StringWidth(const CString& str)
{
	CSize size;
	size = fpDC->GetTextExtent(str);
	return size.cx;
}

/*
 * Trim a string to the specified number of pixels.  If it's too large,
 * ellipsis will be added on the left or right.
 */
int
PrintStuff::TrimString(CString* pStr, int width, bool addOnLeft)
{
	static const char* kEllipsis = "...";
	CString newStr;
	int strWidth;
	CSize size;

	size = fpDC->GetTextExtent(kEllipsis);

	if (width < size.cx) {
		ASSERT(false);
		return width;
	}

	newStr = *pStr;

	/*
	 * Do a linear search.  This would probably be better served with a
	 * binary search or at least a good textmetric-based guess.
	 */
	strWidth = StringWidth(newStr);
	while (strWidth > width) {
		if (pStr->IsEmpty()) {
			ASSERT(false);
			return width;
		}
		if (addOnLeft) {
			*pStr = pStr->Right(pStr->GetLength() -1);
			newStr = kEllipsis + *pStr;
		} else {
			*pStr = pStr->Left(pStr->GetLength() -1);
			newStr = *pStr + kEllipsis;
		}

		if (!addOnLeft) {
			WMSG1("Now trying '%s'\n", (LPCTSTR) newStr);
		}
		strWidth = StringWidth(newStr);
	}

	*pStr = newStr;
	return strWidth;
}


/*
 * ==========================================================================
 *		PrintContentList
 * ==========================================================================
 */

/*
 * Calculate some constant values.
 */
void
PrintContentList::Setup(CDC* pDC, CWnd* pParent)
{
	/* init base class */
	InitBasics(pDC);

	/* init our stuff */
	CreateFontByNumLines(&fPrintFont, kTargetLinesPerPage);

	fpParentWnd = pParent;

	/* compute text metrics */
	TEXTMETRIC metrics;
	pDC->GetTextMetrics(&metrics);
	fCharHeight = metrics.tmHeight + metrics.tmExternalLeading;
	fLinesPerPage = fVertRes / fCharHeight;

	WMSG2("fVertRes=%d, fCharHeight=%d\n", fVertRes, fCharHeight);

	/* set up our slightly reduced lines per page */
	ASSERT(fLinesPerPage > kHeaderLines+1);
	fCLLinesPerPage = fLinesPerPage - kHeaderLines;
}

/*
 * Compute the number of pages in fpContentList.
 */
void
PrintContentList::CalcNumPages(void)
{
	/* set up our local goodies */
	ASSERT(fpContentList != nil);
	int numLines = fpContentList->GetItemCount();
	ASSERT(numLines > 0);

	fNumPages = (numLines + fCLLinesPerPage -1) / fCLLinesPerPage;
	ASSERT(fNumPages > 0);

	WMSG3("Using numLines=%d, fNumPages=%d, fCLLinesPerPage=%d\n",
		numLines, fNumPages, fCLLinesPerPage);
}

/*
 * Initiate printing of the specified list to the specified DC.
 *
 * Returns 0 if all went well, nonzero on cancellation or failure.
 */
int
PrintContentList::Print(const ContentList* pContentList)
{
	fpContentList = pContentList;
	CalcNumPages();

	fFromPage = 1;
	fToPage = fNumPages;
	return StartPrint();
}
int
PrintContentList::Print(const ContentList* pContentList, int fromPage, int toPage)
{
	fpContentList = pContentList;
	CalcNumPages();

	fFromPage = fromPage;
	fToPage = toPage;
	return StartPrint();
}

/*
 * Kick off the print job.
 */
int
PrintContentList::StartPrint(void)
{
	MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
	BOOL bres;
	int jobID;
	int result = -1;

	ASSERT(fFromPage >= 1);
	ASSERT(fToPage <= fNumPages);

	// clear the abort flag
	pMain->SetAbortPrinting(false);

	// obstruct input to the main window
	fpParentWnd->EnableWindow(FALSE);

	// create a print-cancel dialog
//	PrintCancelDialog* pPCD = new PrintCancelDialog;
	CancelDialog* pPCD = new CancelDialog;
	bres = pPCD->Create(&pMain->fAbortPrinting, IDD_PRINT_CANCEL, fpParentWnd);
	if (bres == FALSE) {
		WMSG0("WARNING: PrintCancelDialog init failed\n");
	} else {
		fpDC->SetAbortProc(pMain->PrintAbortProc);
	}

	fDocTitle = pMain->GetPrintTitle();

	// set up the print job
	CString printTitle;
	printTitle.LoadString(IDS_PRINT_CL_JOB_TITLE);
	DOCINFO di;
	::ZeroMemory(&di, sizeof(DOCINFO));
	di.cbSize = sizeof(DOCINFO);
	di.lpszDocName = printTitle;

	jobID = fpDC->StartDoc(&di);
	if (jobID <= 0) {
		WMSG0("Got invalid jobID from StartDoc\n");
		goto bail;
	}
	WMSG1("Got jobID=%d\n", jobID);

	// do the printing
	if (DoPrint() != 0) {
		WMSG0("Printing was aborted\n");
		fpDC->AbortDoc();
	} else {
		WMSG0("Printing was successful\n");
		fpDC->EndDoc();
		result = 0;
	}

bail:
	// destroy print-cancel dialog and restore main window
	fpParentWnd->EnableWindow(TRUE);
	//fpParentWnd->SetActiveWindow();
	if (pPCD != nil)
		pPCD->DestroyWindow();

	return result;
}


/*
 * Print all pages.
 *
 * Returns 0 on success, nonzero on failure.
 */
int
PrintContentList::DoPrint(void)
{
	WMSG2("Printing from page=%d to page=%d\n", fFromPage, fToPage);

	for (int page = fFromPage; page <= fToPage; page++) {
		if (fpDC->StartPage() <= 0) {
			WMSG0("StartPage returned <= 0, returning -1\n");
			return -1;
		}

		DoPrintPage(page);

		// delay so we can test "cancel" button
//		{
//			MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
//			pMain->EventPause(1000);
//		}


		if (fpDC->EndPage() <= 0) {
			WMSG0("EndPage returned <= 0, returning -1\n");
			return -1;
		}
	}

	return 0;
}

/*
 * Print page N of the content list, where N is a 1-based count.
 */
void
PrintContentList::DoPrintPage(int page)
{
	/*
	 * Column widths, on an arbitrary scale.  These will be
	 * scaled appropriately for the page resolution.
	 */
	static const struct {
		const char*		name;
		int				width;
		bool			rightJust;
	} kColumnWidths[kNumVisibleColumns] = {
		{ "Pathname",	250, false },	// 200
		{ "Type",		40, false },	// 44
		{ "Auxtype",	47, false },	// 42
		{ "Mod Date",	96, false },	// 99
		{ "Format",		52, false },	// 54
		{ "Size",		55, true },		// 60
		{ "Ratio",		40, true },		// 41
		{ "Packed",		55, true },		// 60
		{ "Access",		39, false },	// 53 upper, 45 lower
	};
	const int kBorderWidth = 3;

	/* normalize */
	float widthMult;
	int totalWidth;

	totalWidth = 0;
	for (int i = 0; i < NELEM(kColumnWidths); i++)
		totalWidth += kColumnWidths[i].width;

	widthMult = (float) fHorzRes / totalWidth;
	WMSG3("totalWidth=%d, fHorzRes=%d, mult=%.3f\n",
		totalWidth, fHorzRes, widthMult);

	/*
	 * Calculate some goodies.
	 */
	int start, end;
	start = (page-1) * fCLLinesPerPage;
	end = start + fCLLinesPerPage;
	if (end >= fpContentList->GetItemCount())
		end = fpContentList->GetItemCount()-1;

	int offset, nextOffset, cellWidth, border;
	border = (int) (kBorderWidth * widthMult);

	/*
	 * Print page header.
	 */
	fpDC->TextOut(0, 1 * fCharHeight, fDocTitle);
	CString pageNum;
	pageNum.Format("Page %d/%d", page, fNumPages);
	int pageNumWidth = StringWidth(pageNum);
	fpDC->TextOut(fHorzRes - pageNumWidth, 1 * fCharHeight, pageNum);

	/*
	 * Print data.
	 */
	for (int row = -1; row < fCLLinesPerPage && start + row <= end; row++) {
		CString text;
		offset = 0;

		for (int col = 0; col < kNumVisibleColumns; col++) {
			cellWidth = (int) ((float)kColumnWidths[col].width * widthMult);
			nextOffset = offset + cellWidth;
			if (col != kNumVisibleColumns-1)
				cellWidth -= border;
			if (col == 0)
				cellWidth -= (border*2);	// extra border on pathname

			int yOffset;
			if (row == -1) {
				text = kColumnWidths[col].name;
				yOffset = (row+kHeaderLines-1) * fCharHeight;
			} else {
				text = fpContentList->GetItemText(start + row, col);
				yOffset = (row+kHeaderLines) * fCharHeight;
			}

			int strWidth;
			strWidth = TrimString(&text, cellWidth, col == 0);

			if (kColumnWidths[col].rightJust)
				fpDC->TextOut((offset + cellWidth) - strWidth, yOffset, text);
			else
				fpDC->TextOut(offset, yOffset, text);

			offset = nextOffset;
		}
	}

	/*
	 * Add some fancy lines.
	 */
	CPen penBlack(PS_SOLID, 1, RGB(0, 0, 0));
	CPen* pOldPen = fpDC->SelectObject(&penBlack);
	fpDC->MoveTo(0, (int) (fCharHeight * (kHeaderLines - 0.5)));
	fpDC->LineTo(fHorzRes, (int) (fCharHeight * (kHeaderLines - 0.5)));

	//fpDC->MoveTo(0, 0);
	//fpDC->LineTo(fHorzRes, fVertRes);
	//fpDC->MoveTo(fHorzRes-1, 0);
	//fpDC->LineTo(0, fVertRes);

	fpDC->SelectObject(pOldPen);
}


/*
 * ==========================================================================
 *		PrintRichEdit
 * ==========================================================================
 */

/*
 * Calculate some constant values.
 */
void
PrintRichEdit::Setup(CDC* pDC, CWnd* pParent)
{
	/* preflighting can cause this to be initialized twice */
	fpDC = nil;

	/* init base class */
	InitBasics(pDC);

	if (!fInitialized) {
		/*
		 * Find a nice font for the title area.
		 */
		const int kPointSize = 10;
		int fontHeight;
		BOOL result;

		fontHeight = -MulDiv(kPointSize, fLogPixelsY, 72);

		result = fTitleFont.CreateFont(fontHeight, 0, 0, 0, FW_NORMAL, 0, 0, 0,
			DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
			DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, kTimesNewRoman);
		ASSERT(result);		// everybody has Times New Roman
	}

	fpParentWnd = pParent;
	fInitialized = true;
}

/*
 * Pre-flight the print process to get the number of pages.
 */
int
PrintRichEdit::PrintPreflight(CRichEditCtrl* pREC, int* pNumPages)
{
	fStartChar = 0;
	fEndChar = -1;
	fStartPage = 0;
	fEndPage = -1;
	return StartPrint(pREC, "(test)", pNumPages, false);
}

/*
 * Print all pages.
 */
int
PrintRichEdit::PrintAll(CRichEditCtrl* pREC, const char* title)
{
	fStartChar = 0;
	fEndChar = -1;
	fStartPage = 0;
	fEndPage = -1;
	return StartPrint(pREC, title, nil, true);
}

/*
 * Print a range of pages.
 */
int
PrintRichEdit::PrintPages(CRichEditCtrl* pREC, const char* title,
	int startPage, int endPage)
{
	fStartChar = 0;
	fEndChar = -1;
	fStartPage = startPage;
	fEndPage = endPage;
	return StartPrint(pREC, title, nil, true);
}

/*
 * Print the selected area.
 */
int
PrintRichEdit::PrintSelection(CRichEditCtrl* pREC, const char* title,
	long startChar, long endChar)
{
	fStartChar = startChar;
	fEndChar = endChar;
	fStartPage = 0;
	fEndPage = -1;
	return StartPrint(pREC, title, nil, true);
}

/*
 * Start the printing process by posting a print-cancel dialog.
 */
int
PrintRichEdit::StartPrint(CRichEditCtrl* pREC, const char* title,
	int* pNumPages, bool doPrint)
{
	CancelDialog* pPCD = nil;
	MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
	int result;

	/* set up the print cancel dialog */
	if (doPrint) {
		BOOL bres;

		/* disable main UI */
		fpParentWnd->EnableWindow(FALSE);

		pPCD = new CancelDialog;
		bres = pPCD->Create(&pMain->fAbortPrinting, IDD_PRINT_CANCEL,
					fpParentWnd);

		/* set up the DC's print abort callback */
		if (bres != FALSE)
			fpDC->SetAbortProc(pMain->PrintAbortProc);
	}

	result = DoPrint(pREC, title, pNumPages, doPrint);

	if (doPrint) {
		fpParentWnd->EnableWindow(TRUE);
		if (pPCD != nil)
			pPCD->DestroyWindow();
	}

	return result;
}

/*
 * Do some prep work before printing.
 */
void
PrintRichEdit::PrintPrep(FORMATRANGE* pFR)
{
	CFont* pOldFont;

	/* make sure we're in MM_TEXT mode */
	fpDC->SetMapMode(MM_TEXT);

	TEXTMETRIC metrics;
	pOldFont = fpDC->SelectObject(&fTitleFont);
	fpDC->GetTextMetrics(&metrics);
	fCharHeight = metrics.tmHeight + metrics.tmExternalLeading;
	fpDC->SelectObject(pOldFont);
	//WMSG1("CHAR HEIGHT is %d\n", fCharHeight);

	/* compute fLeftMargin and fRightMargin */
	ComputeMargins();

	/*
	 * Set up the FORMATRANGE values.  The Rich Edit stuff likes to have
	 * measurements in TWIPS, whereas the printer is using DC pixel
	 * values.  fLogPixels_ tells us how many pixels per inch.
	 */
	memset(pFR, 0, sizeof(FORMATRANGE));
	pFR->hdc = pFR->hdcTarget = fpDC->m_hDC;

	/*
	 * Set frame for printable area, in TWIPS.  The printer DC will set
	 * its own "reasonable" margins, so the area here is not the entire
	 * sheet of paper.
	 */
	pFR->rcPage.left = pFR->rcPage.top = 0;
	pFR->rcPage.right = (fHorzRes * kTwipsPerInch) / fLogPixelsX;
	pFR->rcPage.bottom = (fVertRes * kTwipsPerInch) / fLogPixelsY;

	long topOffset = (long) ((fCharHeight * 1.5 * kTwipsPerInch) / fLogPixelsY);
	pFR->rc.top = pFR->rcPage.top + topOffset;
	pFR->rc.bottom = pFR->rcPage.bottom;
	pFR->rc.left = pFR->rcPage.left + (fLeftMargin * kTwipsPerInch) / fLogPixelsX;
	pFR->rc.right = pFR->rcPage.right - (fRightMargin * kTwipsPerInch) / fLogPixelsX;

	WMSG2("PRINTABLE AREA is %d wide x %d high (twips)\n",
		pFR->rc.right - pFR->rc.left, pFR->rc.bottom - pFR->rc.top);
	WMSG2("FRAME is %d wide x %d high (twips)\n",
		pFR->rcPage.right - pFR->rcPage.left, pFR->rcPage.bottom - pFR->rcPage.top);

	pFR->chrg.cpMin = fStartChar;
	pFR->chrg.cpMax = fEndChar;
}

/*
 * Compute the size of the left and right margins, based on the width of 80
 * characters of 10-point Courier New on the current printer.
 *
 * Sets fLeftMargin and fRightMargin, in printer DC pixels.
 */
void
PrintRichEdit::ComputeMargins(void)
{
	CFont tmpFont;
	CFont* pOldFont;
	int char80width, fontHeight, totalMargin;
	BOOL result;

	fontHeight = -MulDiv(10, fLogPixelsY, 72);

	result = tmpFont.CreateFont(fontHeight, 0, 0, 0, FW_NORMAL, 0, 0, 0,
		DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
		DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, kCourierNew);
	ASSERT(result);

	pOldFont = fpDC->SelectObject(&tmpFont);
	// in theory we could compute one 'X' * 80; this seems more reliable
	char str[81];
	for (int i = 0; i < 80; i++)
		str[i] = 'X';
	str[i] = '\0';
	char80width = StringWidth(str);
	fpDC->SelectObject(pOldFont);

	//WMSG1("char80 string width=%d\n", char80width);

	/*
	 * If there's not enough room on the page, set the margins to zero.
	 * If the margins required exceed two inches, just set the margin
	 * to one inch on either side.
	 */
	totalMargin = fHorzRes - char80width;
	if (totalMargin < 0) {
		WMSG0(" Page not wide enough, setting margins to zero\n");
		fLeftMargin = fRightMargin = 0;
	} else if (totalMargin > fLogPixelsX * 2) {
		WMSG0("  Page too wide, setting margins to 1 inch\n");
		fLeftMargin = fRightMargin = fLogPixelsX;
	} else {
		// try to get leftMargin equal to 1/2"
		fLeftMargin = totalMargin / 2;
		if (fLeftMargin > fLogPixelsX / 2)
			fLeftMargin = fLogPixelsX / 2;
		fRightMargin = totalMargin - fLeftMargin -1;
		WMSG3("  +++ Margins (in %d pixels/inch) are left=%ld right=%ld\n",
			fLogPixelsX, fLeftMargin, fRightMargin);
	}
}

/*
 * Send the contents of the rich edit control to the printer DC.
 *
 * This was derived from Microsft KB article 129860.
 */
int
PrintRichEdit::DoPrint(CRichEditCtrl* pREC, const char* title,
	int* pNumPages, bool doPrint)
{
	FORMATRANGE fr;
	DOCINFO di;
	long textLength, textPrinted, lastTextPrinted;
	int pageNum;

	WMSG2("DoPrint: title='%s' doPrint=%d\n", title, doPrint);
	WMSG4("         startChar=%d endChar=%d startPage=%d endPage=%d\n",
		fStartChar, fEndChar, fStartPage, fEndPage);

	/*
	 * Get the title font and fill out the FORMATRANGE structure.
	 */
	PrintPrep(&fr);

	/* fill out a DOCINFO */
	memset(&di, 0, sizeof(di));
	di.cbSize = sizeof(DOCINFO);
	di.lpszDocName = title;

	if (doPrint)
		fpDC->StartDoc(&di);

	/*
	 * Here's the really strange part of the affair.  The GetTextLength call
	 * shown in the MSFT KB article doesn't return the correct number of
	 * characters.  The CRichEditView code in MFC uses a GetTextLengthEx
	 * call, which is documented as being part of the CRichEditCtrl but
	 * doesn't appear in the header files.  Call it the hard way.
	 *
	 * If you print a "raw" file with carriage returns, and you use version
	 * 5.30.23.1200 of "riched20.dll", you get "9609" from GetTextLength and
	 * "9528" from GetTextLengthEx when the document's length is 9528 and
	 * there are 81 carriage returns.  The value you want to use is 9528,
	 * because the print code doesn't double-up the count on CRs.
	 *
	 * If instead you use version 5.30.23.1215, you get the same answer
	 * from both calls, and printing works fine.
	 *
	 * GetTextLengthEx is part of "riched20.dll".  Win9x uses "riched32.dll",
	 * which doesn't support the call.
	 */
#ifdef _UNICODE
# error "should be code page 1200, not CP_ACP"
#endif
	GETTEXTLENGTHEX exLenReq;
	long basicTextLength, extdTextLength;
	basicTextLength = pREC->GetTextLength();
	exLenReq.flags = GTL_PRECISE | GTL_NUMCHARS;
	exLenReq.codepage = CP_ACP;
	extdTextLength = (long)::SendMessage(pREC->m_hWnd, EM_GETTEXTLENGTHEX,
		(WPARAM) &exLenReq, (LPARAM) NULL);
	WMSG2("RichEdit text length: std=%ld extd=%ld\n",
		basicTextLength, extdTextLength);

	if (fEndChar == -1) {
		if (extdTextLength > 0)
			textLength = extdTextLength;
		else
			textLength = basicTextLength;
	} else
		textLength = fEndChar - fStartChar;

	WMSG1("  +++ starting while loop, textLength=%ld\n", textLength);
	pageNum = 0;
	lastTextPrinted = -1;
	do {
		bool skipPage = false;
		pageNum++;
		WMSG1("  +++  while loop: pageNum is %d\n", pageNum);

		if (fEndPage > 0) {
			if (pageNum < fStartPage)
				skipPage = true;
			if (pageNum > fEndPage)
				break;	// out of while, ending print
		}

		if (doPrint && !skipPage) {
			fpDC->StartPage();

			CFont* pOldFont = fpDC->SelectObject(&fTitleFont);
			fpDC->TextOut(0, 0 * fCharHeight, title);
			CString pageNumStr;
			pageNumStr.Format("Page %d", pageNum);
			int pageNumWidth = StringWidth(pageNumStr);
			fpDC->TextOut(fHorzRes - pageNumWidth, 0 * fCharHeight, pageNumStr);
			fpDC->SelectObject(pOldFont);

			CPen penBlack(PS_SOLID, 1, RGB(0, 0, 0));
			CPen* pOldPen = fpDC->SelectObject(&penBlack);
			int ycoord = (int) (fCharHeight * 1.25);
			fpDC->MoveTo(0, ycoord-1);
			fpDC->LineTo(fHorzRes, ycoord-1);
			fpDC->MoveTo(0, ycoord);
			fpDC->LineTo(fHorzRes, ycoord);

			fpDC->SelectObject(pOldPen);
		}

		//WMSG1("  +++   calling FormatRange(%d)\n", doPrint && !skipPage);
		//LogHexDump(&fr, sizeof(fr));

		/* print a page full of RichEdit stuff */
		textPrinted = pREC->FormatRange(&fr, doPrint && !skipPage);
		WMSG1("  +++    returned from FormatRange (textPrinted=%d)\n",
			textPrinted);
		if (textPrinted <= lastTextPrinted) {
			/* the earlier StartPage can't be undone, so we'll get an
			   extra blank page at the very end */
			WMSG3("GLITCH: no new text printed (printed=%ld, last=%ld, len=%ld)\n",
				textPrinted, lastTextPrinted, textLength);
			pageNum--;	// fix page count estimator
			break;
		}
		lastTextPrinted = textPrinted;

		// delay so we can test "cancel" button
		//((MainWindow*)::AfxGetMainWnd())->EventPause(1000);

		if (doPrint && !skipPage) {
			if (fpDC->EndPage() <= 0) {
				/* the "cancel" button was hit */
				WMSG0("EndPage returned <= 0 (cancelled)\n");
				fpDC->AbortDoc();
				return -1;
			}
		}

		if (textPrinted < textLength) {
			fr.chrg.cpMin = textPrinted;
			fr.chrg.cpMax = fEndChar;	// -1 if nothing selected
		}
	} while (textPrinted < textLength);

	//WMSG0("  +++ calling FormatRange(nil, FALSE)\n");
	pREC->FormatRange(nil, FALSE);
	//WMSG0("  +++  returned from final FormatRange\n");

	if (doPrint)
		fpDC->EndDoc();

	if (pNumPages != nil)
		*pNumPages = pageNum;

	WMSG1("Printing completed (textPrinted=%ld)\n", textPrinted);

	return 0;
}
