- Understanding Device Contexts
- Drawing Tools
- Device Context Drawing Functions
- Using the Printing Framework
- 3D Graphics with wxGLCanvas
- Summary
Using the Printing Framework
As we've seen, wxPrinterDC can be created and used directly. However, a more flexible method is to use the wxWidgets printing framework to "drive" printing. The main task for the developer is to derive a new class from wxPrintout, overriding functions that specify how to print a page (OnPrintPage), how many pages there are (GetPageInfo), document setup (OnPreparePrinting), and so on. The wxWidgets printing framework will show the print dialog, create the printer device context, and call appropriate wxPrintout functions when appropriate. The same printout class can be used for both printing and preview.
To start printing, a wxPrintout object is passed to a wxPrinter object, and Print is called to kick off the printing process, showing a print dialog before printing the pages specified by the layout object and the user. For example:
// A global object storing print settings wxPrintDialogData g_printDialogData; // Handler for Print menu item void MyFrame::OnPrint(wxCommandEvent& event) { wxPrinter printer(& g_printDialogData); MyPrintout printout(wxT("My printout")); if (!printer.Print(this, &printout, true)) { if (wxPrinter::GetLastError() == wxPRINTER_ERROR) wxMessageBox(wxT("There was a problem printing.\nPerhaps your current printer is not set correctly?"), wxT("Printing"), wxOK); else wxMessageBox(wxT("You cancelled printing"), wxT("Printing"), wxOK); } else { (*g_printDialogData) = printer.GetPrintDialogData(); } }
Because the Print function returns only after all pages have been rendered and sent to the printer, the printout object can be created on the stack.
The wxPrintDialogData class stores data related to the print dialog, such as the pages the user selected for printing and the number of copies to be printed. It's a good idea to keep a global wxPrintDialogData object in your application to store the last settings selected by the user. You can pass a pointer to this data to wxPrinter to be used in the print dialog, and then if printing is successful, copy the settings back from wxPrinter to your global object, as in the previous example. (In a real application, g_printDialogData would probably be a data member of your application class.) See Chapter 8, "Using Standard Dialogs," for more about print and page dialogs and how to use them.
To preview the document, create a wxPrintPreview object, passing two printout objects to it: one for the preview and one to use for printing if the user requests it. You can also pass a wxPrintDialogData object so that the preview picks up settings that the user chose earlier. Then pass the preview object to wxPreviewFrame, call the frame's Initialize function, and show the frame. For example:
// Handler for Preview menu item void MyFrame::OnPreview(wxCommandEvent& event) { wxPrintPreview *preview = new wxPrintPreview( new MyPrintout, new MyPrintout, & g_printDialogData); if (!preview->Ok()) { delete preview; wxMessageBox(wxT("There was a problem previewing.\nPerhaps your current printer is not set correctly?"), wxT("Previewing"), wxOK); return; } wxPreviewFrame *frame = new wxPreviewFrame(preview, this, wxT("Demo Print Preview")); frame->Centre(wxBOTH); frame->Initialize(); frame->Show(true); }
When the preview frame is initialized, it disables all other top-level windows in order to avoid actions that might cause the document to be edited after the print or preview process has started. Closing the frame automatically destroys the two printout objects. Figure 5-9 shows the print preview window, with a control bar along the top providing page navigation, printing, and zoom control.
Figure 5-9 Print preview window
More on wxPrintout
When creating a printout object, the application can pass an optional title that will appear in the print manager under some operating systems. You will need to provide at least GetPageInfo, HasPage, and OnPrintPage, but you can override any of the other methods below as well.
GetPageInfo should be overridden to return minPage, maxPage, pageFrom, and pageTo. The first two integers represent the range supported by this printout object for the current document, and the second two integers represent a user selection (not currently used by wxWidgets). The default values for minPage and maxPage are 1 and 32,000, respectively. However, the printout will stop printing if HasPage returns false. Typically, your OnPreparePrinting function will calculate the values returned by GetPageInfo and will look something like this:
void MyPrintout::GetPageInfo(int *minPage, int *maxPage, int *pageFrom, int *pageTo) { *minPage = 1; *maxPage = m_numPages; *pageFrom = 1; *pageTo = m_numPages; }
HasPage must return false if the argument is outside the current page range. Often its implementation will look like this, where m_numPages has been calculated in OnPreparePrinting:
bool MyPrintout::HasPage(int pageNum) { return (pageNum >= 1 && pageNum <= m_numPages); }
OnPreparePrinting is called before the print or preview process commences, and overriding it enables the application to do various setup tasks, including calculating the number of pages in the document. OnPreparePrinting can call wxPrintout functions such as GetDC, GetPageSizeMM, IsPreview, and so on to get the information it needs.
OnBeginDocument is called with the start and end page numbers when each document copy is about to be printed, and if overridden, it must call the base wxPrintout::OnBeginDocument function. Similarly, wxPrintout::OnEndDocument must be called if overridden.
OnBeginPrinting is called once for the printing cycle, regardless of the number of copies, and OnEndPrinting is called at the end.
OnPrintPage is passed a page number, and the application should override it to return true if the page was successfully printed (returning false cancels the print job). This function will use wxPrintout::GetDC to get the device context to draw on.
The following are the utility functions you can use in your overridden functions, and they do not need to be overridden.
IsPreview can be called to determine whether this is a real print task or a preview.
GetDC returns a suitable device context for the current task. When printing, a wxPrinterDC will be returned, and when previewing, a wxMemoryDC will be returned because a preview is rendered into a bitmap via a memory device context.
GetPageSizeMM returns the size of the printer page in millimeters, whereas GetPageSizePixels returns the size in pixels (the maximum resolution of the printer). For a preview, this will not be the same as the size returned by wxDC::GetSize, which will return the preview bitmap size.
GetPPIPrinter returns the number of pixels per logical inch for the current device context, and GetPPIScreen returns the number of pixels per logical inch of the screen.
Scaling for Printing and Previewing
When drawing on a window, you probably don't concern yourself about scaling your graphics because displays tend to have similar resolutions. However, there are several factors to take into account when drawing to a printer:
- You need to scale and position graphics to fit the width of the page, and break the graphics into pages if necessary.
- Fonts are based on screen resolution, so when drawing text, you need to set a scale so that the printer device context matches the screen resolution. Dividing the printer resolution (GetPPIPrinter) by the screen resolution (GetPPIScreen) can give a suitable scaling factor for drawing text.
- When rendering the preview, wxWidgets uses a wxMemoryDC to draw into a bitmap. The size of the bitmap (returned by wxDC::GetSize) depends on the zoom scale, and an extra scale factor must be calculated to deal with this. Divide the size returned by GetSize by the actual page size returned by GetPageSizePixels to get this scale factor. This value should be multiplied by any other scale you calculated.
You can use wxDC::SetUserScale to let the device context perform the scaling for subsequent graphics operations and wxDC::SetDeviceOrigin to set the origin (for example, to center a graphic on a page). You can keep calling these scaling and device origin functions for different parts of your graphics, on the same page if necessary.
The wxWidgets sample in samples/printing shows how to do scaling. The following example shows a function adapted from the printing sample, which scales and positions a 200x200 pixel graphic on a printer or preview device context.
void MyPrintout::DrawPageOne(wxDC *dc) { // You might use THIS code if you were scaling // graphics of known size to fit on the page. // We know the graphic is 200x200. If we didn't know this, // we'd need to calculate it. float maxX = 200; float maxY = 200; // Let's have at least 50 device units margin float marginX = 50; float marginY = 50; // Add the margin to the graphic size maxX += (2*marginX); maxY += (2*marginY); // Get the size of the DC in pixels int w, h; dc->GetSize(&w, &h); // Calculate a suitable scaling factor float scaleX=(float)(w/maxX); float scaleY=(float)(h/maxY); // Use x or y scaling factor, whichever fits on the DC float actualScale = wxMin(scaleX,scaleY); // Calculate the position on the DC for centring the graphic float posX = (float)((w - (200*actualScale))/2.0); float posY = (float)((h - (200*actualScale))/2.0); // Set the scale and origin dc->SetUserScale(actualScale, actualScale); dc->SetDeviceOrigin( (long)posX, (long)posY ); // Now do the actual drawing dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); dc.SetFont(wxGetApp().m_testFont); dc.SetBackgroundMode(wxTRANSPARENT); dc.SetBrush(*wxCYAN_BRUSH); dc.SetPen(*wxRED_PEN); dc.DrawRectangle(0, 30, 200, 100); dc.DrawText( wxT("Rectangle 200 by 100"), 40, 40); dc.SetPen( wxPen(*wxBLACK,0,wxDOT_DASH) ); dc.DrawEllipse(50, 140, 100, 50); dc.SetPen(*wxRED_PEN); dc.DrawText( wxT("Test message: this is in 10 point text"), 10, 180); }
In this code, we simply use wxDC::GetSize to get the preview or printer resolution so we can fit the graphic on the page. In this example, we're not interested in the points-per-inch printer resolution, as we might be if we were drawing text or lines of a specific length in millimeters, because the graphic doesn't have to be a precise size: it's just scaled to fit the available space.
Next, we'll show code that prints text at a size to match how it appears on the screen and that also draws lines that have a precise length, rather than simply being scaled to fit.
void MyPrintout::DrawPageTwo(wxDC *dc) { // You might use THIS code to set the printer DC to roughly // reflect the screen text size. This page also draws lines of // actual length 5cm on the page. // Get the logical pixels per inch of screen and printer int ppiScreenX, ppiScreenY; GetPPIScreen(&ppiScreenX, &ppiScreenY); int ppiPrinterX, ppiPrinterY; GetPPIPrinter(&ppiPrinterX, &ppiPrinterY); // This scales the DC so that the printout roughly represents the // the screen scaling. float scale = (float)((float)ppiPrinterX/(float)ppiScreenX); // Now we have to check in case our real page size is reduced // (e.g. because we're drawing to a print preview memory DC) int pageWidth, pageHeight; int w, h; dc->GetSize(&w, &h); GetPageSizePixels(&pageWidth, &pageHeight); // If printer pageWidth == current DC width, then this doesn't // change. But w might be the preview bitmap width, // so scale down. float overallScale = scale * (float)(w/(float)pageWidth); dc->SetUserScale(overallScale, overallScale); // Calculate conversion factor for converting millimetres into // logical units. // There are approx. 25.4 mm to the inch. There are ppi // device units to the inch. Therefore 1 mm corresponds to // ppi/25.4 device units. We also divide by the // screen-to-printer scaling factor, because we need to // unscale to pass logical units to DrawLine. // Draw 50 mm by 50 mm L shape float logUnitsFactor = (float)(ppiPrinterX/(scale*25.4)); float logUnits = (float)(50*logUnitsFactor); dc->SetPen(* wxBLACK_PEN); dc->DrawLine(50, 250, (long)(50.0 + logUnits), 250); dc->DrawLine(50, 250, 50, (long)(250.0 + logUnits)); dc->SetBackgroundMode(wxTRANSPARENT); dc->SetBrush(*wxTRANSPARENT_BRUSH); dc->SetFont(wxGetApp().m_testFont); dc->DrawText(wxT("Some test text"), 200, 300 ); }
Printing Under Unix with GTK+
Unlike Mac OS X and Windows, Unix does not provide a standard way to display text and graphics onscreen and print it using the same API. Instead, screen display is done via the X11 library (via GTK+ and wxWidgets), whereas printing has to be done by sending a file of PostScript commands to the printer. Fonts are particularly tricky to handle; until recently, only a small number of applications have offered WYSIWYG (What You See Is What You Get) under Unix. In the past, wxWidgets offered its own printing implementation using PostScript that never fully matched the screen display.
From version 2.8, the GNOME Free Software Desktop Project provides printing support through the libgnomeprint and libgnomeprintui libraries by which most printing problems are solved. Beginning with version 2.5.4, the GTK+ port of wxWidgets can make use of these libraries if wxWidgets is configured accordingly and if the libraries are present. You need to configure wxWidgets with the --with-gnomeprint switch, which will cause your application to search for the GNOME print libraries at runtime. If they are found, printing will be done through these; otherwise, the application will fall back to the old PostScript printing code. Note that the application will not require the GNOME print libraries to be installed in order to run (there is no dependency on these libraries).