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.


Saturday, December 1, 2012

CMake: using an external library

There are plenty of software libraries designed to accomplish useful tasks, so using them is a common requirement. This is not particularly difficult to do, and after some testing I have developed the following solution. It works for any library, not only for wxWidgets ones, but for this example I will show how to use wxPdfDocument, an excellent library for PDF files creation.

Here is the CMakeFiles.txt code:

 # look for wxPdfDocument  
 #  ...include  
 find_path( WXPDFDOC_INCLUDE_PATH pdfdc29.h DOC "wxPdfDocument include files path" )  
 if( NOT WXPDFDOC_INCLUDE_PATH )  
  message( FATAL_ERROR "Unable to find wxPdfDocument include path" )  
 else( NOT WXPDFDOC_INCLUDE_PATH )  
  # up une folder to include <wx/....h>  
  set( WXPDFDOC_INCLUDE_PATH ${WXPDFDOC_INCLUDE_PATH}/.. )  
  message( STATUS "wxPdfDocument include path: " ${WXPDFDOC_INCLUDE_PATH} )  
 endif( NOT WXPDFDOC_INCLUDE_PATH )  
 #  ...libraries  
 if(WIN32)  
  find_file( WXPDFDOC_LIB_PATH_DEBUG wxcode_msw29ud_pdfdoc.lib DOC "Path to the DEBUG wxPdfDocument library" )  
  find_file( WXPDFDOC_LIB_PATH_RELEASE wxcode_msw29u_pdfdoc.lib DOC "Path to the RELEASE wxPdfDocument library" )  
  if( NOT WXPDFDOC_LIB_PATH_DEBUG OR NOT WXPDFDOC_LIB_PATH_RELEASE )  
   message( FATAL_ERROR "Unable to find wxPdfDocument library files" )  
  else ( NOT WXPDFDOC_LIB_PATH_DEBUG OR NOT WXPDFDOC_LIB_PATH_RELEASE )  
   message( STATUS "wxPdfDocument debug  library: " ${WXPDFDOC_LIB_PATH_DEBUG} )  
   message( STATUS "wxPdfDocument release library: " ${WXPDFDOC_LIB_PATH_RELEASE} )  
  endif ( NOT WXPDFDOC_LIB_PATH_DEBUG OR NOT WXPDFDOC_LIB_PATH_RELEASE )  
 else(WIN32)  
  find_library( WXPDFDOC_LIB_PATH libwxcode_gtk2u_pdfdoc-2.9.a )  
  if( NOT WXPDFDOC_LIB_PATH )  
   message( FATAL_ERROR "Unable to find wxPdfDocument library file" )  
  else( NOT WXPDFDOC_LIB_PATH )  
   message( STATUS "wxPdfDocument library: " ${WXPDFDOC_LIB_PATH} )  
  endif( NOT WXPDFDOC_LIB_PATH )  
 endif(WIN32)  
   
 # link and include  
 if(WIN32)  
  target_link_libraries( GeASt debug ${WXPDFDOC_LIB_PATH_DEBUG} optimized ${WXPDFDOC_LIB_PATH_RELEASE} )  
 else(WIN32)  
  target_link_libraries( GeASt ${WXPDFDOC_LIB_PATH} )  
 endif(WIN32)  
 include_directories( ${WXPDFDOC_INCLUDE_PATH} )  
 


The code looks for the include files and the library file(s), then it uses those information to set the include path and to link the libraries.

The behaviour is different for Windows and for *nix operating systems. Under Windows there are two different libraries, one for the debug and one for the release build. Under Linux and Mac there is a single library file.

An important note for wxWidgets users: always link the wxWidgets libraries AFTER all other libraries, otherwise you might get linker errors (undefined symbols). This happened to me under Linux.

This means that the

 target_link_libraries(GeASt ${wxWidgets_LIBRARIES})  
   


line must be the last TARGET_LINK_LIBRARIES line.