Implemented high-quality preview for wxMSW (this approach doesn't work

on other platforms). The preview is now accurate representation of printed
page and wxHtmlEasyPrinting preview shows identical layout to what will
be printed.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@54264 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Václav Slavík 2008-06-16 08:41:51 +00:00
parent 9f7e7edb78
commit 25a3fca2e5
5 changed files with 362 additions and 32 deletions

View File

@ -406,6 +406,10 @@ wxMSW:
- Implement support for display enumeration under WinCE (Vince Harron)
- Use different Win32 class names in different wx instances (Thomas Hauk)
- Support multiline labels for wxCheckBox.
- Print preview is now rendered in the resolution used by printer and
accurately represents what will be printed. This fixes wxHtmlEasyPrinting
preview inaccuracies on Windows; on other platforms, native preview
should be used.
wxX11:

View File

@ -44,6 +44,8 @@ private:
// wxPrintout.
// ---------------------------------------------------------------------------
#define wxUSE_HIGH_QUALITY_PREVIEW (wxUSE_IMAGE && wxUSE_WXDIB)
class WXDLLIMPEXP_CORE wxWindowsPrintPreview : public wxPrintPreviewBase
{
public:
@ -58,7 +60,22 @@ public:
virtual bool Print(bool interactive);
virtual void DetermineScaling();
#if wxUSE_HIGH_QUALITY_PREVIEW
protected:
bool RenderPageIntoBitmapHQ(wxBitmap& bmp, int pageNum);
virtual bool RenderPageIntoBitmap(wxBitmap& bmp, int pageNum);
private:
bool RenderPageFragment(float scaleX, float scaleY,
int *nextFinalLine,
wxPrinterDC& printer,
wxMemoryDC& finalDC,
const wxRect& rect,
int pageNum);
bool m_hqPreviewFailed;
#endif // wxUSE_HIGH_QUALITY_PREVIEW
DECLARE_DYNAMIC_CLASS_NO_COPY(wxWindowsPrintPreview)
};

View File

@ -562,6 +562,11 @@ public:
virtual void DetermineScaling() = 0;
protected:
// helpers for RenderPage():
virtual bool RenderPageIntoDC(wxDC& dc, int pageNum);
// renders preview into m_previewBitmap
virtual bool RenderPageIntoBitmap(wxBitmap& bmp, int pageNum);
void InvalidatePreviewBitmap();
protected:

View File

@ -1535,6 +1535,47 @@ void wxPrintPreviewBase::AdjustScrollbars(wxPreviewCanvas *canvas)
canvas->SetScrollbars(10, 10, scrollUnitsX, scrollUnitsY, 0, 0, true);
}
bool wxPrintPreviewBase::RenderPageIntoDC(wxDC& dc, int pageNum)
{
m_previewPrintout->SetDC(&dc);
m_previewPrintout->SetPageSizePixels(m_pageWidth, m_pageHeight);
// Need to delay OnPreparePrinting() until here, so we have enough
// information.
if (!m_printingPrepared)
{
m_previewPrintout->OnPreparePrinting();
int selFrom, selTo;
m_previewPrintout->GetPageInfo(&m_minPage, &m_maxPage, &selFrom, &selTo);
m_printingPrepared = true;
}
m_previewPrintout->OnBeginPrinting();
if (!m_previewPrintout->OnBeginDocument(m_printDialogData.GetFromPage(), m_printDialogData.GetToPage()))
{
wxMessageBox(_("Could not start document preview."), _("Print Preview Failure"), wxOK);
return false;
}
m_previewPrintout->OnPrintPage(pageNum);
m_previewPrintout->OnEndDocument();
m_previewPrintout->OnEndPrinting();
m_previewPrintout->SetDC(NULL);
return true;
}
bool wxPrintPreviewBase::RenderPageIntoBitmap(wxBitmap& bmp, int pageNum)
{
wxMemoryDC memoryDC;
memoryDC.SelectObject(bmp);
memoryDC.Clear();
return RenderPageIntoDC(memoryDC, pageNum);
}
bool wxPrintPreviewBase::RenderPage(int pageNum)
{
wxBusyCursor busy;
@ -1560,43 +1601,13 @@ bool wxPrintPreviewBase::RenderPage(int pageNum)
}
}
wxMemoryDC memoryDC;
memoryDC.SelectObject(*m_previewBitmap);
memoryDC.Clear();
m_previewPrintout->SetDC(&memoryDC);
m_previewPrintout->SetPageSizePixels(m_pageWidth, m_pageHeight);
// Need to delay OnPreparePrinting until here, so we have enough information.
if (!m_printingPrepared)
if ( !RenderPageIntoBitmap(*m_previewBitmap, pageNum) )
{
m_previewPrintout->OnPreparePrinting();
int selFrom, selTo;
m_previewPrintout->GetPageInfo(&m_minPage, &m_maxPage, &selFrom, &selTo);
m_printingPrepared = true;
}
m_previewPrintout->OnBeginPrinting();
if (!m_previewPrintout->OnBeginDocument(m_printDialogData.GetFromPage(), m_printDialogData.GetToPage()))
{
wxMessageBox(_("Could not start document preview."), _("Print Preview Failure"), wxOK);
memoryDC.SelectObject(wxNullBitmap);
InvalidatePreviewBitmap();
wxMessageBox(_("Sorry, not enough memory to create a preview."), _("Print Preview Failure"), wxOK);
return false;
}
m_previewPrintout->OnPrintPage(pageNum);
m_previewPrintout->OnEndDocument();
m_previewPrintout->OnEndPrinting();
m_previewPrintout->SetDC(NULL);
memoryDC.SelectObject(wxNullBitmap);
#if wxUSE_STATUSBAR
wxString status;
if (m_maxPage != 0)

View File

@ -39,8 +39,12 @@
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/dcprint.h"
#include "wx/dcmemory.h"
#include "wx/image.h"
#endif
#include "wx/msw/dib.h"
#include "wx/msw/dcmemory.h"
#include "wx/msw/printwin.h"
#include "wx/msw/printdlg.h"
#include "wx/msw/private.h"
@ -331,6 +335,9 @@ wxWindowsPrintPreview::wxWindowsPrintPreview(wxPrintout *printout,
wxPrintDialogData *data)
: wxPrintPreviewBase(printout, printoutForPrinting, data)
{
#if wxUSE_HIGH_QUALITY_PREVIEW
m_hqPreviewFailed = false;
#endif
DetermineScaling();
}
@ -339,6 +346,9 @@ wxWindowsPrintPreview::wxWindowsPrintPreview(wxPrintout *printout,
wxPrintData *data)
: wxPrintPreviewBase(printout, printoutForPrinting, data)
{
#if wxUSE_HIGH_QUALITY_PREVIEW
m_hqPreviewFailed = false;
#endif
DetermineScaling();
}
@ -419,6 +429,289 @@ void wxWindowsPrintPreview::DetermineScaling()
m_previewScaleY = float(logPPIScreenY) / logPPIPrinterY;
}
#if wxUSE_HIGH_QUALITY_PREVIEW
// The preview, as implemented in wxPrintPreviewBase (and as used prior to wx3)
// is inexact: it uses screen DC, which has much lower resolution and has
// other properties different from printer DC, so the preview is not quite
// right.
//
// To make matters worse, if the application depends heavily on GetTextExtent()
// or does text layout itself, the output in preview and on paper can be very
// different. In particular, wxHtmlEasyPrinting is affected and the preview
// can be easily off by several pages.
//
// To fix this, we attempt to render the preview into high-resolution bitmap
// using DC with same resolution etc. as the printer DC. This takes lot of
// memory, so the code is more complicated than it could be, but the results
// are much better.
//
// Finally, this code is specific to wxMSW, because it doesn't make sense to
// bother with it on other platforms. Both OSX and modern GNOME/GTK+
// environments have builtin accurate preview (that applications should use
// instead) and the differences between screen and printer DC in wxGTK are so
// large than this trick doesn't help at all.
namespace
{
// If there's not enough memory, we need to render the preview in parts.
// Unfortunately we cannot simply use wxMemoryDC, because it reports its size
// as bitmap's size, and we need to use smaller bitmap while still reporting
// original ("correct") DC size, because printing code frequently uses
// GetSize() to determine scaling factor. This DC class handles this.
class PageFragmentDCImpl : public wxMemoryDCImpl
{
public:
PageFragmentDCImpl(wxMemoryDC *owner, wxDC *printer, const wxRect& rect)
: wxMemoryDCImpl(owner, printer),
m_rect(rect)
{
SetDeviceOrigin(0, 0);
}
virtual void SetDeviceOrigin(wxCoord x, wxCoord y)
{
wxMemoryDCImpl::SetDeviceOrigin(x - m_rect.x, y - m_rect.y);
}
virtual void DoGetDeviceOrigin(wxCoord *x, wxCoord *y) const
{
wxMemoryDCImpl::DoGetDeviceOrigin(x, y);
if ( x ) *x += m_rect.x;
if ( x ) *y += m_rect.y;
}
virtual void DoGetSize(int *width, int *height) const
{
if ( width )
*width = m_rect.width;
if ( height )
*height = m_rect.height;
}
private:
wxRect m_rect;
};
class PageFragmentDC : public wxDC
{
public:
PageFragmentDC(wxDC* printer, wxBitmap& bmp, const wxRect& rect)
: wxDC(new PageFragmentDCImpl((wxMemoryDC*)this, printer, rect))
{
wx_static_cast(PageFragmentDCImpl*, m_pimpl)->DoSelect(bmp);
}
};
// estimate how big chunks we can render, given available RAM
long ComputeFragmentSize(long printerDepth,
long width,
long height)
{
// Compute the amount of memory needed to generate the preview.
// Memory requirements of RenderPageFragment() are as follows:
//
// (memory DC - always)
// width * height * printerDepth/8
// (wxImage + wxDIB instance)
// width * height * (3 + 4)
// (this could be reduced to *3 if using wxGraphicsContext)
//
// So, given amount of memory M, we can render at most
//
// height = M / (width * (printerDepth/8 + F))
//
// where F is 3 or 7 depending on whether wxGraphicsContext is used or not
wxMemorySize memAvail = wxGetFreeMemory();
if ( memAvail == -1 )
{
// we don't know; 10meg shouldn't be a problem hopefully
memAvail = 10000000;
}
else
{
// limit ourselves to half of available RAM to have a margin for other
// apps, for our rendering code, and for miscalculations
memAvail /= 2;
}
const float perPixel = float(printerDepth)/8 + (3 + 4);
const long perLine = long(width * perPixel);
const long maxstep = (memAvail / perLine).GetValue();
const long step = wxMin(height, maxstep);
wxLogTrace("printing",
"using %liMB of RAM (%li lines) for preview, %li %lipx fragments",
long((memAvail >> 20).GetValue()),
maxstep,
(height+step-1) / step,
step);
return step;
}
} // anonymous namespace
bool wxWindowsPrintPreview::RenderPageFragment(float scaleX, float scaleY,
int *nextFinalLine,
wxPrinterDC& printer,
wxMemoryDC& finalDC,
const wxRect& rect,
int pageNum)
{
// compute 'rect' equivalent in the small final bitmap:
const wxRect smallRect(wxPoint(0, *nextFinalLine),
wxPoint(int(rect.GetRight() * scaleX),
int(rect.GetBottom() * scaleY)));
wxLogTrace("printing",
"rendering fragment of page %i: [%i,%i,%i,%i] scaled down to [%i,%i,%i,%i]",
pageNum,
rect.x, rect.y, rect.GetRight(), rect.GetBottom(),
smallRect.x, smallRect.y, smallRect.GetRight(), smallRect.GetBottom()
);
// create DC and bitmap compatible with printer DC:
wxBitmap large(rect.width, rect.height, printer);
if ( !large.IsOk() )
return false;
// render part of the page into it:
{
PageFragmentDC memoryDC(&printer, large, rect);
if ( !memoryDC.IsOk() )
return false;
memoryDC.Clear();
if ( !RenderPageIntoDC(memoryDC, pageNum) )
return false;
} // release bitmap from memoryDC
// now scale the rendered part down and blit it into final output:
wxImage img;
{
wxDIB dib(large);
if ( !dib.IsOk() )
return false;
large = wxNullBitmap; // free memory a.s.a.p.
img = dib.ConvertToImage();
} // free the DIB now that it's no longer needed, too
if ( !img.IsOk() )
return false;
img.Rescale(smallRect.width, smallRect.height, wxIMAGE_QUALITY_HIGH);
if ( !img.IsOk() )
return false;
wxBitmap bmp(img);
if ( !bmp.IsOk() )
return false;
img = wxNullImage;
finalDC.DrawBitmap(bmp, smallRect.x, smallRect.y);
if ( bmp.IsOk() )
{
*nextFinalLine += smallRect.height;
return true;
}
return false;
}
bool wxWindowsPrintPreview::RenderPageIntoBitmapHQ(wxBitmap& bmp, int pageNum)
{
wxLogTrace("printing", "rendering HQ preview of page %i", pageNum);
wxPrinterDC printerDC(m_printDialogData.GetPrintData());
if ( !printerDC.IsOk() )
return false;
// compute scale factor
const float scaleX = float(bmp.GetWidth()) / float(m_pageWidth);
const float scaleY = float(bmp.GetHeight()) / float(m_pageHeight);
wxMemoryDC bmpDC;
bmpDC.SelectObject(bmp);
bmpDC.Clear();
const int initialStep = ComputeFragmentSize(printerDC.GetDepth(),
m_pageWidth, m_pageHeight);
wxRect todo(0, 0, m_pageWidth, initialStep); // rect to render
int nextFinalLine = 0; // first not-yet-rendered output line
while ( todo.y < m_pageHeight )
{
todo.SetBottom(wxMin(todo.GetBottom(), m_pageHeight - 1));
if ( !RenderPageFragment(scaleX, scaleY,
&nextFinalLine,
printerDC,
bmpDC,
todo,
pageNum) )
{
if ( todo.height < 20 )
{
// something is very wrong if we can't render even at this
// slow space, let's bail out and fall back to low quality
// preview
wxLogTrace("printing",
"it seems that HQ preview doesn't work at all");
return false;
}
// it's possible our memory calculation was off, or conditions
// changed, or there's not enough _bitmap_ resources; try if using
// smaller bitmap would help:
todo.height /= 2;
wxLogTrace("printing",
"preview of fragment failed, reducing height to %ipx",
todo.height);
continue; // retry at the same position again
}
// move to the next segment
todo.Offset(0, todo.height);
}
return true;
}
bool wxWindowsPrintPreview::RenderPageIntoBitmap(wxBitmap& bmp, int pageNum)
{
// try high quality rendering first:
if ( !m_hqPreviewFailed )
{
if ( RenderPageIntoBitmapHQ(bmp, pageNum) )
{
return true;
}
else
{
wxLogTrace("printing",
"high-quality preview failed, falling back to normal");
m_hqPreviewFailed = true; // don't bother re-trying
}
}
// if it fails, use the generic method that is less resource intensive,
// but inexact
return wxPrintPreviewBase::RenderPageIntoBitmap(bmp, pageNum);
}
#endif // wxUSE_HIGH_QUALITY_PREVIEW
/****************************************************************************
FUNCTION: wxAbortProc()