diff --git a/docs/changes.txt b/docs/changes.txt index 169e2c5f8d..8c9dad7df3 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -121,6 +121,10 @@ wxMSW: - Fixed generation of wxEVT_CHAR_HOOK events. - Implement wxFileName::SetTimes() for directories (Steve Lamerton). +- Backported speed-up of Vaclav Slavik's high quality print preview speed-up. + You now need to set wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW in + src/common/prntbase.cpp and wxUSE_ENH_METAFILE_FROM_DC in + include/wx/msw/enhmeta.h. wxMac: diff --git a/include/wx/msw/enhmeta.h b/include/wx/msw/enhmeta.h index fa5389b1d0..1bf0d782b5 100644 --- a/include/wx/msw/enhmeta.h +++ b/include/wx/msw/enhmeta.h @@ -18,6 +18,9 @@ #include "wx/dataobj.h" #endif +// Change this to 1 if you set wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW to 1 in prntbase.cpp +#define wxUSE_ENH_METAFILE_FROM_DC 0 + // ---------------------------------------------------------------------------- // wxEnhMetaFile: encapsulation of Win32 HENHMETAFILE // ---------------------------------------------------------------------------- @@ -82,6 +85,15 @@ public: int width = 0, int height = 0, const wxString& description = wxEmptyString); +#if wxUSE_ENH_METAFILE_FROM_DC + // as above, but takes reference DC as first argument to take resolution, + // size, font metrics etc. from + wxEnhMetaFileDC(const wxDC& referenceDC, + const wxString& filename = wxEmptyString, + int width = 0, int height = 0, + const wxString& description = wxEmptyString); +#endif + virtual ~wxEnhMetaFileDC(); // obtain a pointer to the new metafile (caller should delete it) diff --git a/src/common/prntbase.cpp b/src/common/prntbase.cpp index bb6cdc4030..f6e43e39d5 100644 --- a/src/common/prntbase.cpp +++ b/src/common/prntbase.cpp @@ -29,6 +29,14 @@ #endif #endif +#if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW +#include "wx/msw/enhmeta.h" +#endif + +#if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW && !wxUSE_ENH_METAFILE_FROM_DC +#error Please set wxUSE_ENH_METAFILE_FROM_DC to 1 in include/wx/msw/enhmeta.h. +#endif + #include "wx/dcprint.h" #ifndef WX_PRECOMP @@ -84,9 +92,7 @@ typedef bool (wxPrintPreviewBase::*RenderPageIntoDCFunc)(wxDC&, int); static bool RenderPageIntoBitmapHQ(wxPrintPreviewBase *preview, RenderPageIntoDCFunc RenderPageIntoDC, - wxPrinterDC& printerDC, - wxBitmap& bmp, int pageNum, - int pageWidth, int pageHeight); + wxBitmap& bmp, int pageNum); #endif // wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW //---------------------------------------------------------------------------- @@ -1529,25 +1535,13 @@ bool wxPrintPreviewBase::RenderPageIntoDC(wxDC& dc, int pageNum) bool wxPrintPreviewBase::RenderPageIntoBitmap(wxBitmap& bmp, int pageNum) { #if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW - // try high quality rendering first: - static bool s_hqPreviewFailed = false; - if ( !s_hqPreviewFailed ) { - wxPrinterDC printerDC(m_printDialogData.GetPrintData()); if ( RenderPageIntoBitmapHQ(this, &wxPrintPreviewBase::RenderPageIntoDC, - printerDC, - bmp, pageNum, - m_pageWidth, m_pageHeight) ) + bmp, pageNum) ) { return true; } - else - { - wxLogTrace(_T("printing"), - _T("high-quality preview failed, falling back to normal")); - s_hqPreviewFailed = true; // don't bother re-trying - } } #endif // wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW @@ -1823,268 +1817,53 @@ void wxPrintPreview::DetermineScaling() //---------------------------------------------------------------------------- #if wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW - -// 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 PageFragmentDC : public wxMemoryDC -{ -public: - PageFragmentDC(wxDC *printer, wxBitmap& bmp, - const wxPoint& offset, - const wxSize& fullSize) - : wxMemoryDC(printer), - m_offset(offset), - m_fullSize(fullSize) - { - SetDeviceOrigin(0, 0); - SelectObject(bmp); - } - - virtual void SetDeviceOrigin(wxCoord x, wxCoord y) - { - wxMemoryDC::SetDeviceOrigin(x - m_offset.x, y - m_offset.y); - } - - virtual void DoGetDeviceOrigin(wxCoord *x, wxCoord *y) const - { - wxMemoryDC::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; -}; - -// 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(_T("printing"), - _T("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 - - -static bool RenderPageFragment(wxPrintPreviewBase *preview, - RenderPageIntoDCFunc RenderPageIntoDC, - float scaleX, float scaleY, - int *nextFinalLine, - wxPrinterDC& printer, - wxMemoryDC& finalDC, - const wxRect& rect, - int pageWidth, int pageHeight, - 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(_T("printing"), - _T("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(pageWidth, pageHeight)); - if ( !memoryDC.IsOk() ) - return false; - - memoryDC.Clear(); - - if ( !(preview->*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; -} - static bool RenderPageIntoBitmapHQ(wxPrintPreviewBase *preview, RenderPageIntoDCFunc RenderPageIntoDC, - wxPrinterDC& printerDC, - wxBitmap& bmp, int pageNum, - int pageWidth, int pageHeight) + wxBitmap& bmp, int pageNum) { - wxLogTrace(_T("printing"), _T("rendering HQ preview of page %i"), pageNum); + // 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 render the preview into high-resolution enhanced + // metafile with properties identical to the printer DC. This guarantees + // metrics correctness while still being fast. - if ( !printerDC.IsOk() ) + + // print the preview into a metafile: + wxPrinterDC printerDC(preview->GetPrintDialogData().GetPrintData()); + wxEnhMetaFileDC metaDC(printerDC, + wxEmptyString, + printerDC.GetSize().x, printerDC.GetSize().y); + + if ( !(preview->*RenderPageIntoDC)(metaDC, pageNum) ) return false; - // compute scale factor - const float scaleX = float(bmp.GetWidth()) / float(pageWidth); - const float scaleY = float(bmp.GetHeight()) / float(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(), - pageWidth, pageHeight); + wxRect outRect(0, 0, bmp.GetWidth(), bmp.GetHeight()); + metafile->Play(&bmpDC, &outRect); - wxRect todo(0, 0, pageWidth, initialStep); // rect to render - int nextFinalLine = 0; // first not-yet-rendered output line - while ( todo.y < pageHeight ) - { - todo.SetBottom(wxMin(todo.GetBottom(), pageHeight - 1)); + delete metafile; - if ( !RenderPageFragment(preview, RenderPageIntoDC, - scaleX, scaleY, - &nextFinalLine, - printerDC, - bmpDC, - todo, - pageWidth, pageHeight, - 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(_T("printing"), - _T("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(_T("printing"), - _T("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; } - #endif // wxUSE_HIGH_QUALITY_PREVIEW_IN_WXMSW #endif // wxUSE_PRINTING_ARCHITECTURE diff --git a/src/msw/enhmeta.cpp b/src/msw/enhmeta.cpp index ce2ecc0ae8..a99636d7c6 100644 --- a/src/msw/enhmeta.cpp +++ b/src/msw/enhmeta.cpp @@ -226,6 +226,73 @@ wxEnhMetaFileDC::wxEnhMetaFileDC(const wxString& filename, } } +#if wxUSE_ENH_METAFILE_FROM_DC + +void PixelToHIMETRIC(LONG *x, LONG *y, HDC hdcRef) +{ + int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE), + iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE), + iWidthPels = GetDeviceCaps(hdcRef, HORZRES), + iHeightPels = GetDeviceCaps(hdcRef, VERTRES); + + *x *= (iWidthMM * 100); + *x /= iWidthPels; + *y *= (iHeightMM * 100); + *y /= iHeightPels; +} + +void HIMETRICToPixel(LONG *x, LONG *y, HDC hdcRef) +{ + int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE), + iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE), + iWidthPels = GetDeviceCaps(hdcRef, HORZRES), + iHeightPels = GetDeviceCaps(hdcRef, VERTRES); + + *x *= iWidthPels; + *x /= (iWidthMM * 100); + *y *= iHeightPels; + *y /= (iHeightMM * 100); +} + +wxEnhMetaFileDC::wxEnhMetaFileDC(const wxDC& referenceDC, + const wxString& filename, + int width, int height, + const wxString& description) +{ + m_width = width; + m_height = height; + HDC hdcRef = GetHdcOf(referenceDC); + + RECT rect; + RECT *pRect; + if ( width && height ) + { + rect.top = + rect.left = 0; + rect.right = width; + rect.bottom = height; + + // CreateEnhMetaFile() wants them in HIMETRIC + PixelToHIMETRIC(&rect.right, &rect.bottom, hdcRef); + + pRect = ▭ + } + else + { + // GDI will try to find out the size for us (not recommended) + pRect = (LPRECT)NULL; + } + + m_hDC = (WXHDC)::CreateEnhMetaFile(hdcRef, GetMetaFileName(filename), + pRect, description.wx_str()); + if ( !m_hDC ) + { + wxLogLastError(_T("CreateEnhMetaFile")); + } +} + +#endif + void wxEnhMetaFileDC::DoGetSize(int *width, int *height) const { if ( width )