Impression de document sous Windows serveur 2008

Dans cet article différentes manières d’imprimer des documents seront présentées.

L’impression de document depuis un service Windows peut parfois être problématique, je vais traiter dans cet article les fichiers .TXT ainsi que les fichiers .PDF. Si pour les documents .TXT je n’ai pas eu de problèmes, il en a était autrement pour les document .PDF.

Impression de document .TXT :

Le framework .NET possède une classe qui permet d’imprimer des documents : PrintDocument.

Son utilisation est plutôt simple :

public static void PrintTXT(FileInfo file)
        {
            // Lecture et stockage du fichier
            stream2print = new StreamReader(file.FullName, System.Text.Encoding.UTF8);

            // Parametrage de l'impression
            PrintDocument doc = new PrintDocument();
            doc.PrintPage += new PrintPageEventHandler(pd_PrintPage);
            doc.PrinterSettings.PrinterName = pathPrinter;
            doc.DefaultPageSettings.Margins.Top = 0;
            doc.DefaultPageSettings.Margins.Left = 0;
            doc.DefaultPageSettings.Margins.Right = 0;
            doc.DefaultPageSettings.Margins.Bottom = 0;

            // Impression
            doc.Print();

            // Fermeture du flux
            if (stream2print != null) stream2print.Close();
        }

Dans cette fonction le programmeur a définit les paramètres de l’imprimante, ainsi qu’un handler qui sera appelé lorsque sera fait l’appel à doc.Print().

Le contenu de la fonction qui sera appelée dépendra de ce que veux faire le programmeur sur le document à imprimer:

private static void pd_PrintPage(object sender, PrintPageEventArgs ev)
        {
            float linesPerPage = 0;
            float yPos = 0;
            int count = 0;
            float leftMargin = ev.MarginBounds.Left;
            float topMargin = ev.MarginBounds.Top;
            string line = null;

            // Calculate the number of lines per page.
            linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics);

            // Print each line of the file.
            while (count < linesPerPage && ((line = stream2print.ReadLine()) != null) && (!line.Trim().StartsWith("SAUT_DE_PAGE")))
            {
                yPos = topMargin + (count * printFont.GetHeight(ev.Graphics));
                ev.Graphics.DrawString(line, printFont, Brushes.Black, leftMargin, yPos, new StringFormat());
                count++;
            }

            // If more lines exist, print another page.
            if (line != null) ev.HasMorePages = true;
            else ev.HasMorePages = false;
        }

Comme on peut le voir, dans ce cas là, des traitements peuvent être fait sur le document avant de l’imprimer.

Impression de .PDF :

Cette partie a été la plus complexe. En effet le framework ne donne pas d’outil pour  imprimer les PDF.

Il existe donc plusieurs possibilités :

  • Faire un appel à AcrobatReader
  • Faire un appel à FoxitReader
  • Envoyer un flux directement à l’imprimante
  • Convertir le PDF en un autre format qui sera gérable par PrintDocument.

Je vais expliquer chaque manière et leurs limitations dans un service Windows installé sur un Windows Serveur 2008. A chaque fois, on utilisera une imprimante réseau.

Acrobat Reader :

Acrobat ne possède pas de véritable interface de ligne de commande. Ce qui fait que lorsque Acrobat est appelé, il lance une interface graphique. Lorsque l’on lance Acrobat depuis un service Windows, le processus d’Acrobat va être lancé dans la session 0 qui est une session non graphique. Donc cette méthode est voué à l’échec.

Foxit Reader :

A la différence de Acrobat, Foxit possède une vrai interface de ligne de commande. Cependant je n’ai pas réussi à faire fonctionner Foxit. Le code étant sensiblement identique, je ne le met que pour le cas de Foxit:

public static bool PrintFile(string sFileName, string sPrinter)
        {
            string sArgs = " /t \"" + sFileName + "\" \"" + sPrinter + "\"";
            System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = @"C:\Program Files (x86)\Foxit Software\Foxit Reader\Foxit Reader.exe";
            startInfo.Arguments = sArgs;
            startInfo.CreateNoWindow = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.UseShellExecute = false;
            System.Diagnostics.Process proc = Process.Start(startInfo);
            proc.WaitForExit(10000); // Attente de 10 sec que le process finisse
            if (!proc.HasExited)
            {
                proc.Kill();
                proc.Dispose();
                return false;
            }
            return true;
        }

Il existe aussi un service sur les Windows serveur 2008 qui permet de lancé des process graphiques dans la session 0. Mais même avec cela, mon problème n’a pas été résolu. Le service se nomme « UI0Detect ».

Envoi de bytes directement à l’imprimante :

Cette méthode fonctionne correctement, cependant, elle ne permet pas de choisir le bac source du papier sur l’imprimante. Cette solution « bypass » toute la configuration du driver de l’imprimante. Le papier sortira donc toujours du même bac (bac défini par défaut sur l’imprimante).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;

namespace ConsoleApplication2
{    
    public class PrinterData
    {
        public short Duplex;
        public short source;
        public short Orientation;
        public short Size;
    }

    public class RawData
    {

        [StructLayout(LayoutKind.Sequential)]
        public struct PRINTER_DEFAULTS
        {
            public int pDatatype;
            public int pDevMode;
            public int DesiredAccess;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct PRINTER_INFO_2
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pServerName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pPrinterName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pShareName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pPortName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDriverName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pComment;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pLocation;
            public IntPtr pDevMode;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pSepFile;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pPrintProcessor;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDatatype;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pParameters;
            public IntPtr pSecurityDescriptor;
            public Int32 Attributes;
            public Int32 Priority;
            public Int32 DefaultPriority;
            public Int32 StartTime;
            public Int32 UntilTime;
            public Int32 Status;
            public Int32 cJobs;
            public Int32 AveragePPM;
        }

        public struct PRINTER_INFO_9
        {
            public IntPtr pDevMode;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct DEVMODE
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string pDeviceName;
            public short dmSpecVersion;
            public short dmDriverVersion;
            public short dmSize;
            public short dmDriverExtra;
            public int dmFields;
            public short dmOrientation;
            public short dmPaperSize;
            public short dmPaperLength;
            public short dmPaperWidth;
            public short dmScale;
            public short dmCopies;
            public short dmDefaultSource;
            public short dmPrintQuality;
            public short dmColor;
            public short dmDuplex;
            public short dmYResolution;
            public short dmTTOption;
            public short dmCollate;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string dmFormName;
            public short dmUnusedPadding;
            public int dmBitsPerPel;
            public int dmPelsWidth;
            public int dmPelsHeight;
            public int dmNup;
            public int dmDisplayFrequency;
            public int dmICMMethod;
            public int dmICMIntent;
            public int dmMediaType;
            public int dmDitherType;
            public int dmReserved1;
            public int dmReserved2;
            public int dmPanningWidth;
            public int dmPanningHeight;
        }

        private static PRINTER_DEFAULTS PrinterValues = new PRINTER_DEFAULTS();

        // Structure and API declarions:
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public class DOCINFOA
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDocName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pOutputFile;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDataType;
        }
        [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

        [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool ClosePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

        [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool EndDocPrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool StartPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool EndPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

        [DllImport("winspool.Drv", EntryPoint = "DocumentPropertiesA", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern int DocumentProperties(IntPtr hWnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput, ref IntPtr pDevModeInput, Int32 fMode);

        [DllImport("winspool.Drv", EntryPoint = "SetPrinterA", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetPrinter(IntPtr hPrinter, int level, IntPtr pPrinterInfoIn, Int32 fMode);

        [DllImport("winspool.Drv", EntryPoint = "GetPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        private static extern bool GetPrinter(IntPtr hPrinter, Int32 dwLevel, IntPtr pPrinter, Int32 dwBuf, out Int32 dwNeeded);

        [DllImport("winspool.Drv", EntryPoint = "DeviceCapabilitiesA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        private static extern Int32 DeviceCapabilities(string lpDeviceName,string lpPort,long iIndex,IntPtr lpOutput,IntPtr dev);

        #region "Constants"
        private const int DM_DUPLEX = 0x1000;
        private const int DM_IN_BUFFER = 8;
        private const int PRINTER_ACCESS_ADMINISTER = 0x4;
        private const int PRINTER_ACCESS_USE = 0x8;
        private const int DM_OUT_BUFFER = 2;
        private const int STANDARD_RIGHTS_REQUIRED = 0xF0000;
        private const int PRINTER_ALL_ACCESS =
            (STANDARD_RIGHTS_REQUIRED | PRINTER_ACCESS_ADMINISTER
            | PRINTER_ACCESS_USE);
        private const int DC_PAPERNAMES = 16;
        private const int DC_PAPERS = 2;
        private const int DC_BINNAMES = 12;
        private const int DC_BINS = 6;
        private const int DEFAULT_VALUES = 0;
        #endregion

        static int sizeOfDevMode = 0;
        static int intError;
        static Int32 nJunk;
        static int nBytesNeeded;
        static IntPtr hPrinter;
        static IntPtr ptrPrinterInfo;
        static IntPtr ptrDM;
        static IntPtr yDevModeData;
        static int lastError;
        static PRINTER_INFO_2 pinfo;
        static long nRet;

        // SendBytesToPrinter()
        // When the function is given a printer name and an unmanaged array
        // of bytes, the function sends those bytes to the print queue.
        // Returns true on success, false on failure.
        public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount, string filename, short valueBac)
         {
             Int32 dwError = 0, dwWritten = 0;
             IntPtr hPrinter = new IntPtr(0);
             DOCINFOA di = new DOCINFOA();
             bool bSuccess = false; // Assume failure unless you specifically succeed.
             di.pDocName = filename;
             di.pDataType = "RAW";
             if (OpenPrinter(PrinterName, out hPrinter, IntPtr.Zero))
             {
                if (StartDocPrinter(hPrinter, 1, di))
                {
                     // Start a page.
                     if (StartPagePrinter(hPrinter))
                     {
                         // Write your bytes.
                         bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
                         EndPagePrinter(hPrinter);
                     }
                     EndDocPrinter(hPrinter);
                 }
                 ClosePrinter(hPrinter);
             }
             // If you did not succeed, GetLastError may give more information
             // about why not.
             if (bSuccess == false)
             {
                 dwError = Marshal.GetLastWin32Error();
             }
             return bSuccess;
         }        
    }
}

Voir : http://support.microsoft.com/kb/322091

Il existe un moyen pour modifier le bac source de l’imprimante, mais comme je l’ai dis plus haut, le SendBytes vers l’imprimante bypass cette configuration.

Néanmoins pour ceux qui le veulent, je vais mettre le code correspondant:

Il faut dans un premier temps récupérer l’information sur la configuration de l’imprimante :

private static DEVMODE GetInfo(string PrinterName,out IntPtr printer)
        {
            pinfo = new PRINTER_INFO_2();

            PrinterData PData = new PrinterData();
            DEVMODE dm;
            const int PRINTER_ACCESS_ADMINISTER = 0x4;
            const int PRINTER_ACCESS_USE = 0x8;
            const int PRINTER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
                       PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE);

            PrinterValues.pDatatype = 0;
            PrinterValues.pDevMode = 0;
            PrinterValues.DesiredAccess = PRINTER_ALL_ACCESS;
            nRet = Convert.ToInt32(OpenPrinter(PrinterName,
                           out hPrinter, IntPtr.Zero));
            printer = hPrinter;
            if (nRet == 0)
            {
                //lastError = Marshal.GetLastWin32Error();
                //throw new Win32Exception(Marshal.GetLastWin32Error()); 
            }
            GetPrinter(hPrinter, 2, IntPtr.Zero, 0, out nBytesNeeded);
            if (nBytesNeeded <= 0)
            {
                throw new System.Exception("Unable to allocate memory");
            }
            else
            {
                // Allocate enough space for PRINTER_INFO_2... 
                //ptrPrinterInfo = Marshal.AllocCoTaskMem(nBytesNeeded);
                ptrPrinterInfo = Marshal.AllocHGlobal(nBytesNeeded);
                // The second GetPrinter fills in all the current settings, so all you 
                // need to do is modify what you're interested in...
                nRet = Convert.ToInt32(GetPrinter(hPrinter, 2,
                    ptrPrinterInfo, nBytesNeeded, out nJunk));
                if (nRet == 0)
                {
                    //lastError = Marshal.GetLastWin32Error();
                    //throw new Win32Exception(Marshal.GetLastWin32Error()); 
                }
                pinfo = (PRINTER_INFO_2)Marshal.PtrToStructure(ptrPrinterInfo,
                                                      typeof(PRINTER_INFO_2));

                //Ici on a l'info suffisante pour savoir quels sont els bacs present sur l'imprimante

                IntPtr Temp = new IntPtr();
                if (pinfo.pDevMode == IntPtr.Zero)
                {
                    // If GetPrinter didn't fill in the DEVMODE, try to get it by calling
                    // DocumentProperties...
                    IntPtr ptrZero = IntPtr.Zero;
                    //get the size of the devmode structure
                    sizeOfDevMode = DocumentProperties(IntPtr.Zero, hPrinter,
                                       PrinterName, ptrZero, ref ptrZero, 0);

                    ptrDM = Marshal.AllocCoTaskMem(sizeOfDevMode);
                    int i;
                    i = DocumentProperties(IntPtr.Zero, hPrinter, PrinterName, ptrDM, ref ptrZero, DM_OUT_BUFFER);
                    if ((i < 0) || (ptrDM == IntPtr.Zero))
                    {
                        //Cannot get the DEVMODE structure.
                        throw new System.Exception("Cannot get DEVMODE data");
                    }
                    pinfo.pDevMode = ptrDM;
                }
                intError = DocumentProperties(IntPtr.Zero, hPrinter,
                          PrinterName, IntPtr.Zero, ref Temp, 0);
                //IntPtr yDevModeData = Marshal.AllocCoTaskMem(i1);
                yDevModeData = Marshal.AllocHGlobal(intError);
                intError = DocumentProperties(IntPtr.Zero, hPrinter,
                         PrinterName, yDevModeData, ref Temp, 2);
                dm = (DEVMODE)Marshal.PtrToStructure(yDevModeData, typeof(DEVMODE));
                //nRet = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, yDevModeData
                // , ref yDevModeData, (DM_IN_BUFFER | DM_OUT_BUFFER));
                if ((nRet == 0) || (hPrinter == IntPtr.Zero))
                {                    
                    throw new Win32Exception(Marshal.GetLastWin32Error()); 
                }
                return dm;
            }
        }

J’utilise la structure PRINTER_INFO_2 car elle va me permettre de modifier la configuration pour 1 utilisateur. Si j’avais utilisé PRINTER_INFO_9, j’aurai modifié la configuration de l’imprimante pour tous les utilisateurs.

Il faut maintenant obtenir la liste de bac à papier :

private static bool GetBins(string strDeviceName, string strPort,IntPtr dm)
        {
            string strError = "";
            ArrayList BinNr = new ArrayList();
            ArrayList BinName = new ArrayList();

            // Bins
            int nRes = DeviceCapabilities(strDeviceName, strPort, DC_BINS, (IntPtr)null, dm);
            IntPtr pAddr = Marshal.AllocHGlobal((int)nRes * 2);
            nRes = DeviceCapabilities(strDeviceName, strPort, DC_BINS, pAddr, dm);
            if (nRes < 0)
            {
                strError = new Win32Exception(Marshal.GetLastWin32Error()).Message + "[" + strDeviceName + ": " + strPort + "]";
                return false;
            }
            short[] bins = new short[nRes];
            int offset = pAddr.ToInt32();
            for (int i = 0; i < nRes; i++)
            {
                BinNr.Add(Marshal.ReadInt16(new IntPtr(offset + i * 2)));
            }
            Marshal.FreeHGlobal(pAddr);

            // BinNames
            nRes = DeviceCapabilities(strDeviceName, strPort, DC_BINNAMES, (IntPtr)null, dm);
            pAddr = Marshal.AllocHGlobal((int)nRes * 24);
            nRes = DeviceCapabilities(strDeviceName, strPort, DC_BINNAMES, pAddr, dm);
            if (nRes < 0)
            {
                strError = new Win32Exception(Marshal.GetLastWin32Error()).Message + "[" + strDeviceName + ": " + strPort + "]";
                return false;
            }

            offset = pAddr.ToInt32();
            for (int i = 0; i < nRes; i++)
            {
                BinName.Add(Marshal.PtrToStringAnsi(new IntPtr(offset + i * 24)));
            }
            Marshal.FreeHGlobal(pAddr);

            return true;
        }

Cette fonction récupère la liste des bac (numéro et nom).

Comme auparavant on a déjà récupéré la structure PRINTER_INFO_2, on a plus qu’a modifier le paramètre defaultsource et restocker l’information sur l’imprimante :

mode.dmDefaultSource = 259;

                Marshal.StructureToPtr(mode, yDevModeData, true);
                pinfo.pDevMode = yDevModeData;
                pinfo.pSecurityDescriptor = IntPtr.Zero;
                /*update driver dependent part of the DEVMODE
                1 = DocumentProperties(IntPtr.Zero, hPrinter, sPrinterName, yDevModeData
                , ref pinfo.pDevMode, (DM_IN_BUFFER | DM_OUT_BUFFER));*/
                Marshal.StructureToPtr(pinfo, ptrPrinterInfo, true);
                lastError = Marshal.GetLastWin32Error();
                nRet = Convert.ToInt16(SetPrinter(hPrinter, 2, ptrPrinterInfo, 0));

Conversion du PDF :

Cette méthode est celle qui finalement celle que j’ai retenue et qui fonctionne.

Il y a plusieurs étapes à sa réalisation :

  • Convertir le PDF en PPM
  • Obtenir le Bitmap à partir du PPM
  • Assembler les bitmaps pour obtenir un TIFF
  • Envoyer le TIFF à l’imprimante via PrintDocument.

Pour faire cela, j’ai fait des recherches sur internet et j’ai trouvé un utilitaire qui permet la conversion de PDF vers PPM : XPDF. http://foolabs.com/xpdf/home.html

Cet outil converti chaque page contenu dans un PDF en un PPM. Je l’utilise de la sorte :

public static bool Convert2Image(string sFileName)
        {
            string sArgs = "\"" + sFileName + "\" " + "\"" + config.WDirectory + @"\Fichiers\Impression\MonImage.ppm" + "\"";
            System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo();
            string currentAssemblyDirectoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            startInfo.FileName = currentAssemblyDirectoryName + "\\pdftoppm.exe";
            startInfo.Arguments = sArgs;
            startInfo.CreateNoWindow = true;
            startInfo.RedirectStandardOutput = true;
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.UseShellExecute = false;
            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;
        }

Une fois les PPM obtenus, je récupère le bitmap et je le converti en TIFF. Pour récupérer le tiff je me suis inspiré d’un code que j’ai trouvé sur Code Project et je l’ai modifié/adapté pour mon besoin :

http://www.codeproject.com/Articles/18968/PixelMap-Class-and-PNM-Image-Viewer

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

using SysColor = System.Drawing.Color;
using SysRectangle = System.Drawing.Rectangle;

namespace PixelMapSpace
{
    class PixelMap : IDisposable
    {
        /// <summary>
        /// This struct contains the objects that are found in the header of .pbm, .pgm, and .ppm files.
        /// </summary>
        [Serializable]
        public struct PixelMapHeader
        {
            private string magicNumber;
            /// <summary>
            /// The "Magic Number" that identifies the type of Pixelmap. P1 = PBM (ASCII); P2 = PGM (ASCII); P3 = PPM (ASCII); P4 is not used;
            /// P5 = PGM (Binary); P6 = PPM (Binary).
            /// </summary>
            public string MagicNumber
            {
                get { return magicNumber; }
                set { magicNumber = value; }
            }

            private int width;
            /// <summary>
            /// The width of the image.
            /// </summary>
            public int Width
            {
                get { return width; }
                set { width = value; }
            }

            private int height;
            /// <summary>
            /// The height of the image.
            /// </summary>
            public int Height
            {
                get { return height; }
                set { height = value; }
            }

            private int depth;
            /// <summary>
            /// The depth (maximum color value in each channel) of the image.  This allows the format to represent 
            /// more than a single byte (0-255) for each color channel.
            /// </summary>
            public int Depth
            {
                get { return depth; }
                set { depth = value; }
            }
        }

        private Stream FileStream;
        private IntPtr pImageData;
        private PixelMapHeader header;
        /// <summary>
        /// The header portion of the PixelMap.
        /// </summary>
        public PixelMapHeader Header
        {
            get { return header; }
            set { header = value; }
        }

        private List<byte> imageData;
        /// <summary>
        /// The data portion of the PixelMap.
        /// </summary>
        public List<byte> ImageData
        {
            get { return imageData; }
            set { imageData = value; }
        }

        private PixelFormat pixelFormat;
        /// <summary>
        /// The pixel format used by the BitMap generated by this PixelMap.
        /// </summary>
        public PixelFormat PixelFormat
        {
            get { return pixelFormat; }
        }

        private int bytesPerPixel;
        /// <summary>
        /// The number of bytes per pixel.
        /// </summary>
        public int BytesPerPixel
        {
            get { return bytesPerPixel; }
        }

        private int stride;
        /// <summary>
        /// The stride of the scan across the image.  Typically this is width * bytesPerPixel, and is a multiple of 4.
        /// </summary>
        public int Stride
        {
            get { return stride; }
            set { stride = value; }
        }

        private Bitmap bitmap;
        /// <summary>
        /// The Bitmap created from the PixelMap.
        /// </summary>
        public Bitmap BitMap
        {
            get { return bitmap; }
        }

        /// <summary>
        /// A 24bpp Grey map (really only 8bpp of information, since R,G,B bytes are identical) created from the PixelMap.
        /// </summary>
        //public Bitmap GreyMap
        //{
        //    get { return CreateGreyMap(); }
        //}

        public PixelMap(string filename)
        {
            if (File.Exists(filename))
            {
                FromStream(filename);
            }
            else
            {
                throw new FileNotFoundException("The file " + filename + " does not exist", filename);
            }
        }

        public void Dispose()
        {
            if (BitMap != null)
                BitMap.Dispose();
            if (imageData != null)
            {
                imageData.Clear();
                imageData.Capacity = 1;
            }
            imageData = null;
            bitmap = null;

            try
            {
                Marshal.FreeHGlobal(pImageData);
            }
            catch(Exception){}

            GC.SuppressFinalize(this);
        }

        private void FromStream(string filename)
        {

            using (FileStream = new FileStream(filename, FileMode.Open))
            {
                //En faisant un using au niveau du FileStream, celui-ci sera détruit lors de la fin du using
                header = new PixelMapHeader();
                int headerItemCount = 0;
                using (BinaryReader binReader = new BinaryReader(FileStream))
                {
                    ReadHeader(ref headerItemCount, binReader, ref header);
                    ReadData(ref header);

                    //imageData = new List<byte>(header.Width * header.Height * bytesPerPixel);
                    stride = header.Width * bytesPerPixel;

                    FillData(ref header, binReader);

                    ReorderBGRtoRGB(ref header);

                    // 4. Create the BitMap
                    if (stride % 4 == 0)
                    {
                        CreateBitMap(ref header);
                    }
                    else
                    {
                        CreateBitmapOffSize();
                    }

                    // 5. Rotate the BitMap by 180 degrees so it is in the same orientation as the original image.
                    bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
                }
            }
        }

        private void ReadHeader(ref int headerItemCount, BinaryReader binReader, ref PixelMapHeader header)
        {
            //1. Read the Header.
            while (headerItemCount < 4)
            {
                char nextChar = (char)binReader.PeekChar();
                if (nextChar == '#')    // comment
                {
                    while (binReader.ReadChar() != '\n') ;  // ignore the rest of the line.
                }
                else if (Char.IsWhiteSpace(nextChar))   // whitespace
                {
                    binReader.ReadChar();   // ignore whitespace
                }
                else
                {
                    switch (headerItemCount)
                    {
                        case 0: // next item is Magic Number
                            // Read the first 2 characters and determine the type of pixelmap.
                            char[] chars = binReader.ReadChars(2);
                            header.MagicNumber = chars[0].ToString() + chars[1].ToString();
                            headerItemCount++;
                            break;
                        case 1: // next item is the width.
                            header.Width = ReadValue(binReader);
                            headerItemCount++;
                            break;
                        case 2: // next item is the height.
                            header.Height = ReadValue(binReader);
                            headerItemCount++;
                            break;
                        case 3: // next item is the depth.
                            if (header.MagicNumber == "P1" | header.MagicNumber == "P4")
                            {
                                // no depth value for PBM type.
                                headerItemCount++;
                            }
                            else
                            {
                                header.Depth = ReadValue(binReader);
                                headerItemCount++;
                            }
                            break;
                        default:
                            throw new Exception("Error parsing the file header.");
                    }
                }
            }
        }

        private void ReadData(ref PixelMapHeader header)
        {
            // 2. Read the image data.
            // 2.1 Size the imageData array to hold the image bytes.
            switch (header.MagicNumber)
            {
                case "P1": // 1 byte per pixel
                    pixelFormat = PixelFormat.Format8bppIndexed;
                    bytesPerPixel = 1;
                    break;
                case "P2": // 1 byte per pixel
                    pixelFormat = PixelFormat.Format8bppIndexed;
                    bytesPerPixel = 1;
                    break;
                case "P3": // 3 bytes per pixel
                    pixelFormat = PixelFormat.Format24bppRgb;
                    bytesPerPixel = 3;
                    break;
                case "P4":
                    throw new Exception("Binary .pbm (Magic Number P4) is not supported at this time.");
                case "P5":  // 1 byte per pixel
                    pixelFormat = PixelFormat.Format8bppIndexed;
                    bytesPerPixel = 1;
                    break;
                case "P6":  // 3 bytes per pixel
                    pixelFormat = PixelFormat.Format24bppRgb;
                    bytesPerPixel = 3;
                    break;
                default:
                    throw new Exception("Unknown Magic Number: " + header.MagicNumber);
            }
        }

        private void FillData(ref PixelMapHeader header, BinaryReader binReader)
        {
            int index;
            if (header.MagicNumber == "P1" | header.MagicNumber == "P2" | header.MagicNumber == "P3") // ASCII Encoding
            {
                int charsLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position);
                char[] charData = binReader.ReadChars(charsLeft);   // read all the data into an array in one go, for efficiency.
                string valueString = string.Empty;
                index = 0;
                for (int i = 0; i < charData.Length; i++)
                {
                    if (Char.IsWhiteSpace(charData[i])) // value is ignored if empty, or converted to byte and added to array otherwise.
                    {
                        if (valueString != string.Empty)
                        {
                            imageData.Add((byte)int.Parse(valueString));
                            valueString = string.Empty;
                            index++;
                        }
                    }
                    else // add the character to the end of the valueString.
                    {
                        valueString += charData[i];
                    }
                }
            }
            else // binary encoding.
            {
                int bytesLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position);
                //using (MemoryStream memory = new MemoryStream(binReader.ReadBytes(bytesLeft)))
                //{
                //    BinaryFormatter bf = new BinaryFormatter();
                //    imageData = (List<byte>)bf.Deserialize(memory);

                //}
                imageData = new List<byte>(binReader.ReadBytes(bytesLeft));
            }
        }

        private int ReadValue(BinaryReader binReader)
        {
            string value = string.Empty;
            while (!Char.IsWhiteSpace((char)binReader.PeekChar()))
            {
                value += binReader.ReadChar().ToString();
            }
            binReader.ReadByte();   // get rid of the whitespace.
            return int.Parse(value);
        }

        private void ReorderBGRtoRGB(ref PixelMapHeader header)
        {
            List<byte> tempData = new List<byte>(imageData.Count);
            for (int i = 0; i < imageData.Count; i++)
            {
                tempData.Add(imageData[imageData.Count - 1 - i]);
            }
            imageData = tempData;
            tempData = null;
        }

        private void CreateBitMap(ref PixelMapHeader header)
        {
            pImageData = Marshal.AllocHGlobal(imageData.Count);
            Marshal.Copy(imageData.ToArray(), 0, pImageData, imageData.Count);
            bitmap = new Bitmap(header.Width, header.Height, stride, pixelFormat, pImageData);
        }

        private void CreateBitmapOffSize()
        {
            bitmap = new Bitmap(header.Width, header.Height, PixelFormat.Format24bppRgb);
            SysColor sysColor = new SysColor();
            int red, green, blue, grey;
            int index;

            for (int x = 0; x < header.Width; x++)
            {
                for (int y = 0; y < header.Height; y++)
                {
                    index = x + y * header.Width;

                    switch (header.MagicNumber)
                    {
                        case "P1":
                            grey = (int)imageData[index];
                            if (grey == 1)
                            {
                                grey = 255;
                            }
                            sysColor = SysColor.FromArgb(grey, grey, grey);
                            break;
                        case "P2":
                            grey = (int)imageData[index];
                            sysColor = SysColor.FromArgb(grey, grey, grey);
                            break;
                        case "P3":
                            index = 3 * index;
                            blue = (int)imageData[index];
                            green = (int)imageData[index + 1];
                            red = (int)imageData[index + 2];
                            sysColor = SysColor.FromArgb(red, green, blue);
                            break;
                        case "P4":
                            break;
                        case "P5":
                            grey = (int)imageData[index];
                            sysColor = SysColor.FromArgb(grey, grey, grey);
                            break;
                        case "P6":
                            index = 3 * index;
                            blue = (int)imageData[index];
                            green = (int)imageData[index + 1];
                            red = (int)imageData[index + 2];
                            sysColor = SysColor.FromArgb(red, green, blue);
                            break;
                        default:
                            break;
                    }
                    bitmap.SetPixel(x, y, sysColor);
                }
            }
        }
    }
}

Maintenant on peut construire un TIFF qui sera envoyer à l’imprimante :

private void BuildTiff()
        {
            pix = new PixelMap(Files[0]);
            MemoryStream byteStream = new MemoryStream();

            pix.BitMap.Save(byteStream, ImageFormat.Tiff);
            //On va stocker cette page dans un objet image
            pix.Dispose();
            pix = null;
            GC.Collect();
            Thread.Sleep(1000);
            Image tiff = Image.FromStream(byteStream);

            ImageCodecInfo encoderInfo = ImageCodecInfo.GetImageEncoders().First(i => i.MimeType == "image/tiff");

            EncoderParameters encoderParams = new EncoderParameters(2);
            EncoderParameter parameter = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionLZW);
            encoderParams.Param[0] = parameter;
            parameter = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.MultiFrame);
            encoderParams.Param[1] = parameter;

            Trace.WriteLine("Avant sauvegarde tiff " + Files[0].ToString());
            try
            {
                tiff.Save(@"C:\TempTif.tif", encoderInfo, encoderParams);
                byteStream.Flush();
                byteStream.Close();
                byteStream.Dispose();
                byteStream = null;

            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
                Trace.WriteLine(ex.InnerException);
                if (tiff == null)
                    Trace.WriteLine("tiff est nul");
                if (encoderParams == null)
                    Trace.WriteLine("encoderParams est nul");
                if (parameter == null)
                    Trace.WriteLine("parameter est nul");
            }

            Trace.WriteLine("Apres sauvegarde tiff " + Files[0].ToString());
            if (Files.Length > 1)
                AddPageTiff(ref tiff);

            EncoderParameter SaveEncodeParam = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush);
            EncoderParameters EncoderParams = new EncoderParameters(1);
            EncoderParams.Param[0] = SaveEncodeParam;
            tiff.SaveAdd(EncoderParams);

            encoderParams.Dispose();
            parameter.Dispose();
            SaveEncodeParam.Dispose();
            EncoderParams.Dispose();

            encoderParams = null;
            parameter = null;
            SaveEncodeParam = null;
            EncoderParams = null;

            tiff.Dispose();
            tiff = null;

            PrintFile();

            File.Delete(@"C:\TempTif.tif");
        }

        private void AddPageTiff(ref Image tiff)
        {
            EncoderParameters EncoderParams = new EncoderParameters(2);
            EncoderParameter SaveEncodeParam = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage);
            EncoderParameter CompressionEncodeParam = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionLZW);
            EncoderParams.Param[0] = CompressionEncodeParam;
            EncoderParams.Param[1] = SaveEncodeParam;
            for (int i = 1; i < Files.Length; i++)
            {
                pix = new PixelMap(Files[i]);

                MemoryStream byteStream = new MemoryStream();

                pix.BitMap.Save(byteStream, ImageFormat.Tiff);               

                //On va stocker cette page dans un objet image
                Image tiff2add = Image.FromStream(byteStream);                

                tiff.SaveAdd(tiff2add, EncoderParams);

                pix.Dispose();
                pix = null;

                byteStream.Flush();
                byteStream.Close();
                byteStream.Dispose();
                byteStream = null;

                tiff2add.Dispose();
                tiff2add = null;                

                GC.Collect();
                Thread.Sleep(2000);
            }
            EncoderParams.Dispose();
            SaveEncodeParam.Dispose();
            CompressionEncodeParam.Dispose();

            EncoderParams = null;
            SaveEncodeParam = null;
            CompressionEncodeParam = null;
        }

L’envoi à l’imprimante se fait de la même manière que pour l’impression d’un .TXT, c’est à dire en utilisant PrintDocument.

private void PrintFile()
        {
            using (PrintDocument doc = new TiffDocument(@"C:\TempTif.tif"))
            {
                doc.PrintPage += new PrintPageEventHandler(pd_PrintImage);
                doc.PrinterSettings.PrinterName = PathPrinter;
                doc.DefaultPageSettings.Margins.Top = 0;
                doc.DefaultPageSettings.Margins.Left = 0;
                doc.DefaultPageSettings.Margins.Right = 0;
                doc.DefaultPageSettings.Margins.Bottom = 0;
                // Impression
                doc.Print();
                doc.PrintPage -= new PrintPageEventHandler(pd_PrintImage);
                doc.Dispose();
            }
        }

        private void pd_PrintImage(object sender, PrintPageEventArgs ev)
        {
            TiffDocument doc = (TiffDocument)sender;            
            using (Image i = Image.FromFile(doc.Tif))
            {
                i.SelectActiveFrame(FrameDimension.Page, doc.PageNumber);
                float newWidth = i.Width * 100 / i.HorizontalResolution;
                float newHeight = i.Height * 100 / i.VerticalResolution;

                float widthFactor = newWidth / ev.MarginBounds.Width;
                float heightFactor = newHeight / ev.MarginBounds.Height;

                if (widthFactor > 1 | heightFactor > 1)
                {
                    if (widthFactor > heightFactor)
                    {
                        newWidth = newWidth / widthFactor;
                        newHeight = newHeight / widthFactor;
                    }
                    else
                    {
                        newWidth = newWidth / heightFactor;
                        newHeight = newHeight / heightFactor;
                    }
                }

                ev.Graphics.DrawImage(i, 0, 0, (int)newWidth, (int)newHeight);
                doc.PageNumber++;
                if (doc.PageNumber < doc.NumberOfPages)
                {
                    // There is still at least one more page.
                    // Signal this event to fire again.
                    ev.HasMorePages = true;
                }
                else
                {
                    // Printing is complete.
                    ev.HasMorePages = false;
                }
            }
            doc.Dispose();
            doc = null;
        }

Avec cela vous pouvez imprimer des PDF depuis Service Windows installé sur un Windows Serveur 2008. Ceci n’est pas LA solution, mais une solution, néanmoins j’espère qu’elle vous sera utile.

2 commentaires

  1. Thank you, I’ve just been looking for info approximately this topic for a long time and yours is the greatest I’ve found out till now. However, what concerning the conclusion? Are you certain about the supply?

Répondre à katowiczak Annuler la réponse.