Printing Problem (Memory Issue / Duplex Print)

1609 views
Skip to first unread message

Jan Baarß

unread,
May 30, 2012, 6:58:41 AM5/30/12
to pdfne...@googlegroups.com

Hi,

I have a lot of trouble using the printing abilities provided by the PDFTron.

First I decided to use the standard .net printing library to print pdf documents.
That did work very well - apart from one problem.
After a while some of our users ran into a "out of memory" exceptions if they wanted to print.

So I got the advice to use the PDFTron Library to print pdf documents to solve the memory issue.
That solved the memory issue but produces some other issues.
Some of the properties I set at the PrintMode object will be ignored - for example duplex prints and copy count.
I tried your example on two different printers (that can print duplex) and it does not work.

I really need a solution that works in all cases.

Isn't there a way to use the standard .net printing library to print a PDFDoc without the mentioned memory issue?

Thanks for your help.

Kind regards,
Jan

Support

unread,
May 30, 2012, 5:52:50 PM5/30/12
to pdfne...@googlegroups.com
 
There are 4 ways to print using PDFNet.
 
1) The simples option is pdftron.PDF.Print.StartPrintJob(). This function spools the PDF as vector based XPS or EMF. This is good for perfomance and quality but StartPrintJob() does not offer all options available in low-level Windows APIs.
 
2) Another option is to use PDFDraw to generate bitmaps and .NET Print API for printing (I assume this is the option  you are currently using). This gives more control, however the printing is slower and spool file size could get huge (or some printers may run out of memory).
 
3) Another option is to use PDFDraw to send GDI+ commands to the printer device context (usign pdfdraw.SetRasterizerType(PDFRasterizer.Type.e_GDIPlus) and pdfdraw.DrawInRectGraphics() to draw PDF page on the printer HDC/Graphics object). This will usually resuly is faster printing and smaller spool size, however GDI/GDI+ do not support full PDF graphics model and some files may not render accurately.
 
4) The final option brings best qualities of option #1 together with low-level controls of platfrom API.
You would use  'pdftron.PDF.Convert.ToXps()' to convert PDF to XPS (which is a new Windows spool format) and XPS Print API to print the resulting XPS file (http://msdn.microsoft.com/en-us/library/windows/desktop/ff728890(v=vs.85).aspx). The only issue is that MS XPS Print API is only available as a COM interface. This is not a problem though since you can access the API via PInvoke from C# as shown below (also attached).
 
 
 
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
namespace XpsPrint
{
    /// <summary>
    /// A utility class to print a file via Windows  XpsPrint API.
    /// </summary>
    public class XpsPrintHelper
    {
        /// <summary>
        /// No ctor.
        /// </summary>
        private XpsPrintHelper()
        {
        }
        /// <summary>
        /// Sends a stream that contains a document in the XPS format to a printer using the XpsPrint API.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="printerName"></param>
        /// <param name="jobName">Job name. Can be null.</param>
        /// <param name="isWait">True to wait for the job to complete. False to return immediately after submitting the job.</param>
        /// <exception cref="Exception">Thrown if any error occurs.</exception>
        public static void Print(Stream stream, string printerName, string jobName, bool isWait)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");
            if (printerName == null)
                throw new ArgumentNullException("printerName");
            // Create an event that we will wait on until the job is complete.
            IntPtr completionEvent = CreateEvent(IntPtr.Zero, true, false, null);
            if (completionEvent == IntPtr.Zero)
                throw new Win32Exception();
            try
            {
                IXpsPrintJob job;
                IXpsPrintJobStream jobStream;
                StartJob(printerName, jobName, completionEvent, out job, out jobStream);
                CopyJob(stream, job, jobStream);
                if (isWait)
                {
                    WaitForJob(completionEvent);
                    CheckJobStatus(job);
                }
            }
            finally
            {
                if (completionEvent != IntPtr.Zero)
                    CloseHandle(completionEvent);
            }
        }
        private static void StartJob(string printerName, string jobName, IntPtr completionEvent, out IXpsPrintJob job, out IXpsPrintJobStream jobStream)
        {
            int result = StartXpsPrintJob(printerName, jobName, null, IntPtr.Zero, completionEvent,
                null, 0, out job, out jobStream, IntPtr.Zero);
            if (result != 0)
                throw new Win32Exception(result);
        }
        private static void CopyJob(Stream stream, IXpsPrintJob job, IXpsPrintJobStream jobStream)
        {
            try
            {
                byte[] buff = new byte[4096];
                while (true)
                {
                    uint read = (uint)stream.Read(buff, 0, buff.Length);
                    if (read == 0)
                        break;
                    uint written;
                    jobStream.Write(buff, read, out written);
                    if (read != written)
                        throw new Exception("Failed to copy data to the print job stream.");
                }
                // Indicate that the entire document has been copied.
                jobStream.Close();
            }
            catch (Exception)
            {
                // Cancel the job if we had any trouble submitting it.
                job.Cancel();
                throw;
            }
        }
        private static void WaitForJob(IntPtr completionEvent)
        {
            const int INFINITE = -1;
            switch (WaitForSingleObject(completionEvent, INFINITE))
            {
                case WAIT_RESULT.WAIT_OBJECT_0:
                    // Expected result, do nothing.
                    break;
                case WAIT_RESULT.WAIT_FAILED:
                    throw new Win32Exception();
                default:
                    throw new Exception("Unexpected result when waiting for the print job.");
            }
        }
        private static void CheckJobStatus(IXpsPrintJob job)
        {
            XPS_JOB_STATUS jobStatus;
            job.GetJobStatus(out jobStatus);
            switch (jobStatus.completion)
            {
                case XPS_JOB_COMPLETION.XPS_JOB_COMPLETED:
                    // Expected result, do nothing.
                    break;
                case XPS_JOB_COMPLETION.XPS_JOB_FAILED:
                    throw new Win32Exception(jobStatus.jobStatus);
                default:
                    throw new Exception("Unexpected print job status.");
            }
        }
        [DllImport("XpsPrint.dll", EntryPoint = "StartXpsPrintJob")]
        private static extern int StartXpsPrintJob(
            [MarshalAs(UnmanagedType.LPWStr)] String printerName,
            [MarshalAs(UnmanagedType.LPWStr)] String jobName,
            [MarshalAs(UnmanagedType.LPWStr)] String outputFileName,
            IntPtr progressEvent,   // HANDLE
            IntPtr completionEvent, // HANDLE
            [MarshalAs(UnmanagedType.LPArray)] byte[] printablePagesOn,
            UInt32 printablePagesOnCount,
            out IXpsPrintJob xpsPrintJob,
            out IXpsPrintJobStream documentStream,
            IntPtr printTicketStream);  // This is actually "out IXpsPrintJobStream", but we don't use it and just want to pass null, hence IntPtr.
        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
        [DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern WAIT_RESULT WaitForSingleObject(IntPtr handle, Int32 milliseconds);
        [DllImport("Kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);
    }
    [Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D")]  // This is IID of ISequenatialSteam.
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IXpsPrintJobStream
    {
        // ISequentualStream methods.
        void Read([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbRead);
        void Write([MarshalAs(UnmanagedType.LPArray)] byte[] pv, uint cb, out uint pcbWritten);
        // IXpsPrintJobStream methods.
        void Close();
    }
    [Guid("5ab89b06-8194-425f-ab3b-d7a96e350161")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IXpsPrintJob
    {
        void Cancel();
        void GetJobStatus(out XPS_JOB_STATUS jobStatus);
    }
    [StructLayout(LayoutKind.Sequential)]
    struct XPS_JOB_STATUS
    {
        public UInt32 jobId;
        public Int32 currentDocument;
        public Int32 currentPage;
        public Int32 currentPageTotal;
        public XPS_JOB_COMPLETION completion;
        public Int32 jobStatus; // UInt32
    };
    enum XPS_JOB_COMPLETION
    {
        XPS_JOB_IN_PROGRESS = 0,
        XPS_JOB_COMPLETED = 1,
        XPS_JOB_CANCELLED = 2,
        XPS_JOB_FAILED = 3
    }
    enum WAIT_RESULT
    {
        WAIT_OBJECT_0 = 0,
        WAIT_ABANDONED = 0x80,
        WAIT_TIMEOUT = 0x102,
        WAIT_FAILED = -1 // 0xFFFFFFFF
    }
}
 
 
---------
 
So it seems that option #4 would be ideal for your application.
XpsPrintHelper.zip
Reply all
Reply to author
Forward
0 new messages