diff --git a/docs/changes.txt b/docs/changes.txt index 0420eade70..1552ec8731 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -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: diff --git a/include/wx/msw/printwin.h b/include/wx/msw/printwin.h index c5c2d0a603..7d7e466ed9 100644 --- a/include/wx/msw/printwin.h +++ b/include/wx/msw/printwin.h @@ -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) }; diff --git a/include/wx/prntbase.h b/include/wx/prntbase.h index e5f91f9e3d..81e32415b5 100644 --- a/include/wx/prntbase.h +++ b/include/wx/prntbase.h @@ -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: diff --git a/src/common/prntbase.cpp b/src/common/prntbase.cpp index f0b86a8c4a..12c4cee992 100644 --- a/src/common/prntbase.cpp +++ b/src/common/prntbase.cpp @@ -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) diff --git a/src/msw/printwin.cpp b/src/msw/printwin.cpp index 2ccf24961c..38e384468f 100644 --- a/src/msw/printwin.cpp +++ b/src/msw/printwin.cpp @@ -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()