Thursday, December 27, 2012

Printing to a PDF file

A common requirement for an accounting program is the ability to "print" something to a PDF file instead of printing it on paper.

It is possible to install some software that creates a virtual printer, so that everything is printed to that printer is converted to a PDF file. It is a simple solution but it is a rather limited one: for example you do not have control on the name of the created file unless you stick with a particular program, and this is not a good idea. If that program is abandoned you are out of luck. There are more problems if you want to create a program that runs under multiple operating systems.

A much better solution is to create the PDF file from the printing program, so you do not have external dependencies.
For wxWidgets programs there is a very good library: wxPdfDocument. It can be used to create PDF files with custom commands, but it also contains the wxPdfDc class: it is a device context derived from wxDC, so it can be used to print to a PDF file using the same code used to print to paper. This is a huge advantage, of course.

I have been able to print to a PDF file with very small code changes. I will describe those changes making a reference to a previous post that described how to print from wxWidgets: you should read it before reading the rest of this post.

That post contained some code used to compute a variable called logUnitsFactor, used to convert millimeters to logical units. That code did not work with wxPdfDc so I had to change it. First add the include file:

 #include "wx/pdfdc.h"  

Then use the following code to compute logUnitsFactor instead of the code used in the older post:

     wxSize devicePPI = dc->GetPPI();  
     int ppiPrinterX, ppiPrinterY;  
     ppiPrinterX = devicePPI.GetWidth();  
     ppiPrinterY = devicePPI.GetHeight();  
   
     int ppiScreenX, ppiScreenY;  
     wxScreenDC sdc;  
     ppiScreenX = sdc.GetPPI().GetWidth();  
     ppiScreenY = sdc.GetPPI().GetHeight();  
   
     float scale = (float)((float)ppiPrinterY/(float)ppiScreenY);  
     dc->SetUserScale(scale, scale);  
   
     logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));  

where dc is a pointer to a wxPdfDc object.
If you are writing some generic code you can use this test to know if you are working with a wxPdfDc:

   if( dc->IsKindOf(CLASSINFO(wxPdfDC)) ) {  
     // custom code if we are using wxPdfDC  
   }  
   else {  
     // standard code  
   }  

Now it's time to print something. The following code can be used to print to a PDF file with a given name. The printing framework does not directly support printing to a PDF so I had to copy some of its code.

   m_PrintData->SetFilename( fileName );  
   
   MyPrintout printout( "Print name" );  
   
   wxPdfDC dc( *m_PrintData );  
   dc.SetResolution( 600 );  
   printout.SetDC( &dc );  
   
   printout.OnPreparePrinting();  
   printout.OnBeginPrinting();  
   
   int minPageNum = 1;  
   int maxPageNum = 9999;  
   if( printout.OnBeginDocument(minPageNum, maxPageNum) ) {  
     for ( int pn = minPageNum;  
       pn >= maxPageNum && printout.HasPage(pn);  
       pn++ )  
     {  
       dc.StartPage();  
       bool cont = printout.OnPrintPage(pn);  
       dc.EndPage();  
   
       if ( !cont ) break;  
     }  
     printout.OnEndDocument();  
   }  
   
   printout.OnEndPrinting();  
   

With this code the same wxPrintout object used to print to paper can be used to print to a PDF file.


1 comment: