Sonntag, 27. April 2014

XPS printing quality


This post is about printing XPS-files using C# and the quality issues you can have if your printer doesn’t support direct printing of XPS-files.

Let’s assume we created and saved a XPS-file and now we’d like to print this file using Visual C#. Based on http://msdn.microsoft.com/en-us/library/ms742418(v=vs.110).aspx this approach should work:

System.Windows.Xps.Packaging.XpsDocument xpsdocument
  = new System.Windows.Xps.Packaging.XpsDocument(
    filename,
    System.IO.FileAccess.Read);
PrintServer printserver
  = new PrintServer(printservername);
PrintQueue printqueue
  = printserver.GetPrintQueue(printqueuename);
PrintTicket printticket
  = printqueue.DefaultPrintTicket;
System.Windows.Xps.XpsDocumentWriter xpsdocumentwriter
  = PrintQueue.CreateXpsDocumentWriter(printqueue);
xpsdocumentwriter.Write(
  xpsdocument.GetFixedDocumentSequence());


But there can be quality-issues depending on
printqueue.IsXpsDevice
(printer supports direct XPS-printing): e.g. bad image quality (96 DPI) or wrong fonts.

There can be found many discussions when searching the web, but the most interesting point is, that there are no such issues when printing a XPS-file using Windows XPS-Viewer.

Based on https://gist.github.com/bgrainger/1541424 printing works fine using this approach:


  // taken from https://gist.github.com/bgrainger/1541424
  internal static class NativeMethods
  {
    [System.Runtime.InteropServices.DllImport(
      "XpsPrint.dll",
      ExactSpelling = true,
      CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    public static extern int StartXpsPrintJob(
      string printerName,
      string jobName,
      string outputFileName,
      IntPtr progressEvent,
      Microsoft.Win32.SafeHandles.SafeWaitHandle completionEvent,
      [System.Runtime.InteropServices.MarshalAs(
        System.Runtime.InteropServices.UnmanagedType.LPArray)]
      byte[] printablePagesOn,
      int printablePagesOnCount,
      out IXpsPrintJob xpsPrintJob,
      out IXpsPrintJobStream documentStream,
      out IXpsPrintJobStream printTicketStream);
  }
 
  [System.Runtime.InteropServices.ComImport,
    System.Runtime.InteropServices.Guid(
      "E974D26D-3D9B-4D47-88CC-3872F2DC3585"),
    System.Runtime.InteropServices.ClassInterface(
      System.Runtime.InteropServices.ClassInterfaceType.None)]
  internal class XpsOMObjectFactory { }
 
  [System.Runtime.InteropServices.ComImport,
    System.Runtime.InteropServices.Guid(
      "F9B2A685-A50D-4FC2-B764-B56E093EA0CA"),
    System.Runtime.InteropServices.InterfaceType(
      System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
  internal interface IXpsOMObjectFactory
  {
    void CreatePackage();
 
    [return: System.Runtime.InteropServices.MarshalAs(
      System.Runtime.InteropServices.UnmanagedType.Interface)]
    IXpsOMPackage CreatePackageFromFile(
      [System.Runtime.InteropServices.MarshalAs(
        System.Runtime.InteropServices.UnmanagedType.LPWStr)]
      string filename,
      bool reuseObjects);
 
    void CreatePackageFromStream();
    void CreateStoryFragmentsResource();
    void CreateDocumentStructureResource();
    void CreateSignatureBlockResource();
    void CreateRemoteDictionaryResource();
    void CreateRemoteDictionaryResourceFromStream();
    void CreatePartResources();
    void CreateDocumentSequence();
    void CreateDocument();
    void CreatePageReference();
    void CreatePage();
    void CreatePageFromStream();
    void CreateCanvas();
    void CreateGlyphs();
    void CreatePath();
    void CreateGeometry();
    void CreateGeometryFigure();
    void CreateMatrixTransform();
    void CreateSolidColorBrush();
    void CreateColorProfileResource();
    void CreateImageBrush();
    void CreateVisualBrush();
    void CreateImageResource();
    void CreatePrintTicketResource();
    void CreateFontResource();
    void CreateGradientStop();
    void CreateLinearGradientBrush();
    void CreateRadialGradientBrush();
    void CreateCoreProperties();
    void CreateDictionary();
    void CreatePartUriCollection();
    void CreatePackageWriterOnFile();
    void CreatePackageWriterOnStream();
    void CreatePartUri();
    void CreateReadOnlyStreamOnFile();
  }
 
  [System.Runtime.InteropServices.ComImport,
    System.Runtime.InteropServices.Guid(
      "18C3DF65-81E1-4674-91DC-FC452F5A416F"),
    System.Runtime.InteropServices.InterfaceType(
      System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
  internal interface IXpsOMPackage
  {
    void GetDocumentSequence();
    void SetDocumentSequence();
    void GetCoreProperties();
    void SetCoreProperties();
    void GetDiscardControlPartName();
    void SetDiscardControlPartName();
    void GetThumbnailResource();
    void SetThumbnailResource();
    void WriteToFile();
 
    void WriteToStream(
      IXpsPrintJobStream stream,
      bool optimizeMarkupSize);
  };
 
  // NOTE: It appears that the IID for IXpsPrintJobStream specified in XpsPrint.h --  
  // MIDL_INTERFACE("7a77dc5f-45d6-4dff-9307-d8cb846347ca") -- is not correct, or the object
  // doesn't implement QueryInterface correctly. However, we can QI for ISequentialStream and
  // successfully (at least in Windows 7 SP1 x86) call the Close method as if it existed on that
  // interface.
  // That is, we obtain the ISequentialStream interface, but work with it as the IXpsPrintJobStream interface.
  // Thanks to http://stackoverflow.com/questions/6123507/xps-printing-from-windows-service for this tip.
  [System.Runtime.InteropServices.ComImport,
    System.Runtime.InteropServices.Guid(
      "0C733A30-2A1C-11CE-ADE5-00AA0044773D"),
    System.Runtime.InteropServices.InterfaceType(
      System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
  internal interface IXpsPrintJobStream
  {
    // ISequentialStream methods
    void Read(
      [System.Runtime.InteropServices.MarshalAs(
        System.Runtime.InteropServices.UnmanagedType.LPArray)]
      byte[] pv,
      uint cb,
      out uint pcbRead);

    void Write(
      [System.Runtime.InteropServices.MarshalAs(
        System.Runtime.InteropServices.UnmanagedType.LPArray)]
      byte[] pv,
      uint cb,
      out uint pcbWritten);

 
    // IXpsPrintJobStream methods
    void Close();
  }
 
  [System.Runtime.InteropServices.ComImport,
    System.Runtime.InteropServices.Guid(
      "5AB89B06-8194-425F-AB3B-D7A96E350161"),
    System.Runtime.InteropServices.InterfaceType(
      System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
  internal interface IXpsPrintJob
  {
    void Cancel();
    IntPtr GetJobStatus();
  };
 
  // END taken from https://gist.github.com/bgrainger/1541424


  public class SamplePrinter
  {
    public void Print(
      string filename,
      string printservername,
      string printqueuename)
    {
      PrintServer printserver
        = new PrintServer(printservername);
      PrintQueue printqueue
        = printserver.GetPrintQueue(printqueuename);
      PrintTicket printticket
        = printqueue.DefaultPrintTicket;
     
      if (printqueue.IsXpsDevice)
      {
        System.Windows.Xps.
XpsDocumentWriter xpsdocumentwriter
          = PrintQueue.CreateXpsDocumentWriter(printqueue);
        xpsdocumentwriter.Write(
          new System.Windows.Xps.Packaging.XpsDocument(
            filename,
            System.IO.FileAccess.Read).GetFixedDocumentSequence());
       
      }
      else
        this.PrintExtern(
          filename,
          printqueuename,
          printqueue.DefaultPrintTicket);
    }
 
 
  // taken from https://gist.github.com/bgrainger/1541424
  ///
  /// Prints the specified XPS document to a printer using the native XPS Print API.
  ///
  /// The path to the XPS document.
  /// The printer name.
  /// A PrintTicket with settings for this print job.
  /// true
if the document was successfully printed; otherwise, false.
  /// This method should be called from a background thread.

  public bool PrintExtern(
    string xpsFilePath,    string printerName,    PrintTicket printTicket)
  {
    // try to create the XPS Object Model factory (only available on Windows 7 and Vista with the Platform Update)
    IXpsOMObjectFactory xpsFactory = null;
    try
    {
      xpsFactory
        = (
IXpsOMObjectFactory)new XpsOMObjectFactory();
    }
    catch (System.Runtime.InteropServices.COMException)
    {
      // OS doesn't support XPS Document API
      return false;
    }
 
    bool success = false;
    IXpsOMPackage package = null;
 
    try
    {
      // load the saved document as a native XpsOMPackage
      package
        = xpsFactory.CreatePackageFromFile(
            xpsFilePath,
          false);
 
       using (ManualResetEvent handle
                = new ManualResetEvent(false))
      {
        // attempt to start the print job
        IXpsPrintJob printJob;
        IXpsPrintJobStream
        docStream,
          ticketStream;
 
        int hresult
          = NativeMethods.StartXpsPrintJob(
            printerName,
            0 "somename",
            null,
          IntPtr.Zero,
            handle.SafeWaitHandle,
          null, 0, out printJob, out docStream, out ticketStream);
 
        // check for success (NOTE: checking HRESULT value directly instead of calling Marshal.ThrowExceptionForHR to avoid proliferation of 'catch' blocks)
        if (hresult >= 0)
        {
          // write the current printer settings to the print ticket stream
          byte[] ticketData = printTicket.GetXmlStream().ToArray();
          uint bytesWritten;
          ticketStream.Write(ticketData, (uint)ticketData.Length, out bytesWritten);
          ticketStream.Close();
 
          // write the XPS package to the document stream
          package.WriteToStream(docStream, false);
          docStream.Close();
 
          // wait for printing to finish
          handle.WaitOne();
          success = true;
        }
      }
    }
    catch (System.Runtime.InteropServices.COMException)
    {
      // printing failed
    }
    catch (DllNotFoundException)
    {
      // OS doesn't support XPS Print API
    }
    catch (EntryPointNotFoundException)
    {
      // OS doesn't support XPS Print API
    }
     // force the XPS package to be released, so that the temporary file can be deleted
    if (package != null)
      System.Runtime.InteropServices.Marshal.FinalReleaseComObject(package);
 
    return success;
  }
  // END taken from https://gist.github.com/bgrainger/1541424
}


The code is kind of “quick and dirty” but works, at least when using Windows 7.