use enhanced metafiles for wxMSW print preview: this is as accurate as previous code, but much faster (fixes #9970)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@60844 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
parent
cc3445715d
commit
a5c1223d2f
@ -44,8 +44,6 @@ private:
|
||||
// wxPrintout.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define wxUSE_HIGH_QUALITY_PREVIEW (wxUSE_IMAGE && wxUSE_WXDIB)
|
||||
|
||||
class WXDLLIMPEXP_CORE wxWindowsPrintPreview : public wxPrintPreviewBase
|
||||
{
|
||||
public:
|
||||
@ -60,22 +58,9 @@ 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)
|
||||
};
|
||||
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "wx/msw/printdlg.h"
|
||||
#include "wx/msw/private.h"
|
||||
#include "wx/msw/dcprint.h"
|
||||
#include "wx/msw/enhmeta.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -335,9 +336,6 @@ wxWindowsPrintPreview::wxWindowsPrintPreview(wxPrintout *printout,
|
||||
wxPrintDialogData *data)
|
||||
: wxPrintPreviewBase(printout, printoutForPrinting, data)
|
||||
{
|
||||
#if wxUSE_HIGH_QUALITY_PREVIEW
|
||||
m_hqPreviewFailed = false;
|
||||
#endif
|
||||
DetermineScaling();
|
||||
}
|
||||
|
||||
@ -346,9 +344,6 @@ wxWindowsPrintPreview::wxWindowsPrintPreview(wxPrintout *printout,
|
||||
wxPrintData *data)
|
||||
: wxPrintPreviewBase(printout, printoutForPrinting, data)
|
||||
{
|
||||
#if wxUSE_HIGH_QUALITY_PREVIEW
|
||||
m_hqPreviewFailed = false;
|
||||
#endif
|
||||
DetermineScaling();
|
||||
}
|
||||
|
||||
@ -429,296 +424,52 @@ 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
|
||||
bool wxWindowsPrintPreview::RenderPageIntoBitmap(wxBitmap& bmp, int pageNum)
|
||||
{
|
||||
|
||||
// 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 wxPoint& offset,
|
||||
const wxSize& fullSize)
|
||||
: wxMemoryDCImpl(owner, printer),
|
||||
m_offset(offset),
|
||||
m_fullSize(fullSize)
|
||||
{
|
||||
SetDeviceOrigin(0, 0);
|
||||
}
|
||||
|
||||
virtual void SetDeviceOrigin(wxCoord x, wxCoord y)
|
||||
{
|
||||
wxMemoryDCImpl::SetDeviceOrigin(x - m_offset.x, y - m_offset.y);
|
||||
}
|
||||
|
||||
virtual void DoGetDeviceOrigin(wxCoord *x, wxCoord *y) const
|
||||
{
|
||||
wxMemoryDCImpl::DoGetDeviceOrigin(x, y);
|
||||
if ( x ) *x += m_offset.x;
|
||||
if ( x ) *y += m_offset.y;
|
||||
}
|
||||
|
||||
virtual void DoGetSize(int *width, int *height) const
|
||||
{
|
||||
if ( width )
|
||||
*width = m_fullSize.x;
|
||||
if ( height )
|
||||
*height = m_fullSize.y;
|
||||
}
|
||||
|
||||
private:
|
||||
wxPoint m_offset;
|
||||
wxSize m_fullSize;
|
||||
};
|
||||
|
||||
class PageFragmentDC : public wxDC
|
||||
{
|
||||
public:
|
||||
PageFragmentDC(wxDC* printer, wxBitmap& bmp,
|
||||
const wxPoint& offset,
|
||||
const wxSize& fullSize)
|
||||
: wxDC(new PageFragmentDCImpl((wxMemoryDC*)this, printer, offset, fullSize))
|
||||
{
|
||||
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:
|
||||
// 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.
|
||||
//
|
||||
// (memory DC - always)
|
||||
// width * height * printerDepth/8
|
||||
// (wxImage + wxDIB instance)
|
||||
// width * height * (3 + 4)
|
||||
// (this could be reduced to *3 if using wxGraphicsContext)
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// To fix this, we render the preview into high-resolution enhanced
|
||||
// metafile with properties identical to the printer DC. This guarantees
|
||||
// metrics correctness while still being fast.
|
||||
|
||||
|
||||
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.GetPosition(),
|
||||
wxSize(m_pageWidth, m_pageHeight));
|
||||
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);
|
||||
|
||||
// print the preview into a metafile:
|
||||
wxPrinterDC printerDC(m_printDialogData.GetPrintData());
|
||||
if ( !printerDC.IsOk() )
|
||||
wxEnhMetaFileDC metaDC(printerDC,
|
||||
wxEmptyString,
|
||||
printerDC.GetSize().x, printerDC.GetSize().y);
|
||||
|
||||
if ( !RenderPageIntoDC(metaDC, pageNum) )
|
||||
return false;
|
||||
|
||||
// compute scale factor
|
||||
const float scaleX = float(bmp.GetWidth()) / float(m_pageWidth);
|
||||
const float scaleY = float(bmp.GetHeight()) / float(m_pageHeight);
|
||||
wxEnhMetaFile *metafile = metaDC.Close();
|
||||
if ( !metafile )
|
||||
return false;
|
||||
|
||||
// now render the metafile:
|
||||
wxMemoryDC bmpDC;
|
||||
bmpDC.SelectObject(bmp);
|
||||
bmpDC.Clear();
|
||||
|
||||
const int initialStep = ComputeFragmentSize(printerDC.GetDepth(),
|
||||
m_pageWidth, m_pageHeight);
|
||||
wxRect outRect(0, 0, bmp.GetWidth(), bmp.GetHeight());
|
||||
metafile->Play(&bmpDC, &outRect);
|
||||
|
||||
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));
|
||||
delete metafile;
|
||||
|
||||
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);
|
||||
}
|
||||
// TODO: we should keep the metafile and reuse it when changing zoom level
|
||||
|
||||
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
|
||||
|
||||
/****************************************************************************
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user