Background printing of PDF documents

Posted by Tobias on December 29, 2008 00:00 CET Updated on April 22, 2013 21:52 CEST 14 Comments Add a comment

Contents

I was asked to create some software to handle printing of PDF documents and after scouring the Internet for several hours I realized that this wasn't as easy as I had initially though. There are several methods of doing background printing so I tested all I could find and measured the performance. This implementation was going to be a Windows service written in C# which could pick up newly created files in a directory, do some processing and then print them. The service needed to be threaded to be able to handle multiple files at the same time so I was looking for thread safe components. Another thing high up on the wishing list was the use of open source or free software.

The performance testing was done by giving the service 40 single page PDF documents and measuring the time taken until the last file was processed and sent to the printer queue.

Using Adobe Reader

Version 6 of Adobe Reader supported command line printing; sadly this was removed in newer versions. I could still launch the application using System.Diagnostics.Process class and supplying the "PrintTo" verb so I tested this. Using one thread to print all 40 documents took about 12 minutes with this method. There was however several problems using this method:

  1. Adobe Reader will not close after the file has been printed and there is no easy way of determining when the application is finished printing. We could wait for a fixed time or wait until the application is idle and then kill it. I decided to wait until the application was idle which worked satisfactory.
  2. Adobe Reader does not seem to be thread safe. When using two or more threads, files sent to Adobe Reader at the same time was ignored.
  3. There seemed to be a lot of memory overhead when launching Reader.

These problems led me to continue searching for other solutions.

Using Ghostscript

This method solves all problems I had with Adobe Reader but unfortunately introduces new ones. Ghostscript has a command line interface (gswin32c.exe) which will print a file and then exit. There were no problems using several threads and memory usage seemed to be acceptable. The problem with using Ghostscript was processing time and the size of the spooling file sent to the printer. In some cases the file size difference between Ghostscript and Adobe Reader was a factor of 20. Also, when printing documents with several pages Ghostscript seems to process each page individually. This took way too much time. This method was however twice as fast as Adobe Reader when printing the 40 test documents and finished in about 6 minutes.

Using Adobe Acrobat SDK

So I was back to Adobe and tested their SDK. I can't begin to explain the frustration I felt using their so called SDK and in the end I can't recommend it to anyone.

First, their SDK isn't really an SDK as much as it is a lot of sample code. To be able to use their APIs you need to install the full Acrobat package which is a hassle to obtain just to test it out.

Second, the SDK is .NET compatible but not much more. Their class structure is a complete mess and horrible to work with.

Third, there is no apparent way of supplying the destination printer using the APIs. There is a PrintPagesSilent method but it always prints to the default printer. Somewhere in some developers manual I found out that you have to go through JavaScript to be able to set a destination printer, which is completely insane. Why not add it as an optional parameter to the PrintPagesSilent method? We could of course change the default printer but that wouldn't be thread safe.

Finally, they charge you money for this...

Using Foxit Reader

Finally I tested printing with Foxit Reader which is freely available at www.foxitsoftware.com. It has a command line interface for printing, uses less memory than the previous alternatives and it's also thread safe. When printing the 40 test documents using 3 threads, all documents were processed in less than 1 min 30 sec. Using the command line Foxit Reader will exit after printing the document.

The printer name must be supplied exactly as written in the standard Windows print dialog. This was a minor problem because the printer name supplied with the files was actually the share name. Therefore I wrote a method which fetches all installed printers and matches the names. In order to handle installed printers two basic classes were created: Printer and PrinterList. The Printer class basically contains two strings to store printer name and share name. The PrinterList class is a subclass of System.Collection.Generic.List to hold the instances of Printer and also to do various finding tasks.

Example code

/// 
/// Prints a file using Foxit Reader.
/// 
///Path to file
///Name of printer
public bool PrintFile(string sFileName, string sPrinter)
{
    string sArgs = " /t \"" + sFileName + "\" \"" + sPrinter + "\"";
    System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.FileName = @"C:\Program Files\Foxit Software\Foxit Reader\Foxit Reader.exe";
    startInfo.Arguments = sArgs;
    startInfo.CreateNoWindow = true;
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    System.Diagnostics.Process proc = Process.Start(startInfo);
    proc.WaitForExit(10000); // Wait a maximum of 10 sec for the process to finish
    if (!proc.HasExited)
    {
        proc.Kill();
        proc.Dispose();
        return false;
    }
    return true;
}

/// 
/// Get a list of installed printers.
/// 
/// A subclass of List which contains instances of the Printer class.
public PrinterList GetPrinterList()
{
    PrinterList lstPrinters = new PrinterList(); // Custom subclass of List
    using (ManagementClass printerClass = new ManagementClass("win32_printer"))
    {
        ManagementObjectCollection printers = printerClass.GetInstances();
        foreach (ManagementObject printer in printers)
        {
            if ((bool)printer["Shared"] == true)
               lstPrinters.Add(new Printer((string)printer["Name"], (string)printer["ShareName"]));
        }
    }
    return lstPrinters;
}

Conclusion

Background printing of PDF documents isn't very difficult once you have found the right tools for the task. Ghostscript works fine when printing small documents and it's highly customizable. Sadly Ghostscript will choke when you give it larger documents. Spooling a PDF containing 60 pages using Ghostscript takes minutes instead of a couple of seconds using alternative methods. Adobe Reader has been broken to be able to sell their so called SDK so Foxit Reader was perfect for this project.

Post your comment

14 Comments (newest first)

Posted by Dan on May 9, 2017 21:39 CEST

Thank you Tobias I also fond that this solution with Foxit worked well for me too. I had tried Sumatra with no luck. I did notice that the executable name changed slightly so I had to remove a space. So far everything else seems to be working for my simple needs. Thanks for sharing!

Posted by ayala on March 30, 2017 08:49 CEST

how can i print from a specify tray with foxit reader using c#

Posted by icaro on June 2, 2016 19:28 CEST

Is there a way to specify the tray in the command line argument? Any other idea to dynamically indicate the tray a document should get the paper. Thanks a lot.

Posted by lorretadt on May 3, 2016 07:58 CEST

c# acrobat print pdf with comments is the most helpful software can be download on rasteredge page http://www.rasteredge.com/how-to/csharp-imaging/pdf-annotate-sticky-note/

Posted by lorretadt on May 3, 2016 07:58 CEST

c# acrobat print pdf with comments is the most helpful software can be download on rasteredge page http://www.rasteredge.com/how-to/csharp-imaging/pdf-annotate-sticky-note/

Posted by lorretadt on April 20, 2016 09:09 CEST

You can also read an interesting c# acrobat remove text from pdf on this page http://www.rasteredge.com/how-to/csharp-imaging/pdf-text-edit-delete/

Posted by Forumisto on March 27, 2015 13:46 CET

How I can print duplex with FoxitReader? Thanks

Posted by Marcus on May 13, 2014 16:13 CEST

Depending on your requirements, there can be a very easy way to print a PDF from code. If you browse to a network printer in windows explorer such as \\printserver\printerABC123 you'll find that it brings up the print queue. Now all you need to do us use a file copy method, like C#'s File.Copy() and copy your PDF into that queue as if it were a simple file share. The file will now print fine. This works on PDF, txt, maybe others, but not on word docs, excel, etc

Posted by Saurabh on April 29, 2014 12:03 CEST

Dear sir, I have also tested the same and I find the same. Now the problem is, when I want to print multiple copies of same pdf using foxit reader, there is no command line argument for that. Please help me out from this situation. Thanks in Advance.

Posted by fretje on February 24, 2014 16:20 CET

I'm wondering... why do you still kill the process when you say that foxit reader does exit nicely after printing?

Posted by miliu on January 17, 2014 18:42 CET

Following Brian's suggestion, I gave it a try using SumatraPDF (v 2.4). It prints silently (which is great) using command line but I only get empty pages. I can open the PDF file first and print from the main window, but still empty pages. I googled around and found a lot of discussions around this problem since 2010. Anybody got clue?

Posted by Brian on October 24, 2013 06:55 CEST

I just found Sumatra PDF http://blog.kowalczyk.info/software/sumatrapdf/free-pdf-reader.html just need the put the .exe file on the file system and call it from a command line. Runs install free

Posted by Tobias on June 1, 2013 20:34 CEST

That's great information BP, I've never run into that problem myself but please keep us posted if you find a solution.

Posted by BP on May 29, 2013 21:02 CEST

Beware that if you are looking for a total background, unattended solution, that if the document requires a password or is unopenable for some reason, foxit will throw up a dialog and the process will remain locked until the dialog is dismissed. For running in an unattended environment it would be best for this to be returned from the process as an error, which it is not. I have not found a solution to this issue yet but will try to drop any hints I find here if I remember. :)

Read More