using System; using System.ComponentModel; using System.Printing; using System.Runtime.InteropServices; namespace Elwig.Helpers.Printing { public partial class Win32Printer { private const int DM_OUT_BUFFER = 2; private const int HORZRES = 8; private const int VERTRES = 10; private const int LOGPIXELSX = 88; private const int LOGPIXELSY = 90; private const int DM_DUPLEX = 0x1000; private const short DMDUP_SIMPLEX = 1; private const short DMDUP_VERTICAL = 2; private const short DMDUP_HORIZONTAL = 3; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct DOCINFO { public int cbSize; [MarshalAs(UnmanagedType.LPWStr)] public string? lpszDocName; [MarshalAs(UnmanagedType.LPWStr)] public string? lpszOutput; [MarshalAs(UnmanagedType.LPWStr)] public string? lpszDatatype; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct DEVMODE { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string dmDeviceName; 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 dmLogPixels; public int dmBitsPerPel; public int dmPelsWidth; public int dmPelsHeight; public int dmDisplayFlags; 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; } [DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault); [DllImport("winspool.drv", SetLastError = true)] private static extern bool ClosePrinter(IntPtr hPrinter); [DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)] private static extern int DocumentProperties(IntPtr hwnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput, IntPtr pDevModeInput, int fMode); [DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr CreateDC(string? pDriver, string? pDevice, string? pOutput, IntPtr pInitData); [DllImport("gdi32.dll", SetLastError = true)] private static extern bool DeleteDC(IntPtr hdc); [DllImport("gdi32.dll", SetLastError = true)] private static extern int GetDeviceCaps(IntPtr hdc, int index); [DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern int StartDoc(IntPtr hdc, ref DOCINFO lpdi); [DllImport("gdi32.dll", SetLastError = true)] private static extern int EndDoc(IntPtr hdc); [DllImport("gdi32.dll", SetLastError = true)] private static extern int AbortDoc(IntPtr hdc); [DllImport("gdi32.dll", SetLastError = true)] private static extern int StartPage(IntPtr hdc); [DllImport("gdi32.dll", SetLastError = true)] private static extern int EndPage(IntPtr hdc); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GlobalFree(IntPtr hMem); [DllImport("kernel32.dll")] private static extern IntPtr GlobalLock(IntPtr hMem); [DllImport("kernel32.dll")] private static extern bool GlobalUnlock(IntPtr hMem); public class Printer : IDisposable { private readonly string _printerName; private readonly IntPtr _printerHandle; private readonly IntPtr _devMode; private readonly IntPtr _hdc; public IntPtr Hdc => _hdc; public int PixelWidth => GetDeviceCaps(_hdc, HORZRES); public int PixelHeight => GetDeviceCaps(_hdc, VERTRES); public int DpiX => GetDeviceCaps(_hdc, LOGPIXELSX); public int DpiY => GetDeviceCaps(_hdc, LOGPIXELSY); public Printer(string printerName) { _printerName = printerName; if (!OpenPrinter(_printerName, out _printerHandle, IntPtr.Zero)) { throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to open printer '{printerName}'."); } try { _devMode = CreateDevMode(); _hdc = CreateDC(null, _printerName, null, _devMode); if (_hdc == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to create printer DC for '{_printerName}'."); } catch { if (_devMode != IntPtr.Zero) Marshal.FreeHGlobal(_devMode); ClosePrinter(_printerHandle); throw; } } public bool TrySetDuplex(Duplexing duplex) { var devMode = Marshal.PtrToStructure(_devMode); if ((devMode.dmFields & DM_DUPLEX) != 0) { switch (duplex) { case Duplexing.OneSided: devMode.dmDuplex = DMDUP_SIMPLEX; break; case Duplexing.TwoSidedShortEdge: devMode.dmDuplex = DMDUP_HORIZONTAL; break; case Duplexing.TwoSidedLongEdge: devMode.dmDuplex = DMDUP_VERTICAL; break; } Marshal.StructureToPtr(devMode, _devMode, false); return true; } else { return false; } } private IntPtr CreateDevMode() { int size = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, IntPtr.Zero, IntPtr.Zero, 0); if (size <= 0) throw new Win32Exception(Marshal.GetLastWin32Error(), "DocumentProperties failed."); IntPtr devMode = Marshal.AllocHGlobal(size); int result = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, devMode, IntPtr.Zero, DM_OUT_BUFFER); if (result < 0) { Marshal.FreeHGlobal(devMode); throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to obtain printer DEVMODE."); } return devMode; } public void StartDocument(string documentName) { var docInfo = new DOCINFO { cbSize = Marshal.SizeOf(), lpszDocName = documentName, lpszDatatype = null, lpszOutput = null }; if (StartDoc(_hdc, ref docInfo) <= 0) throw new Win32Exception(Marshal.GetLastWin32Error(), "StartDoc failed."); } public void StartPage() { if (Win32Printer.StartPage(_hdc) <= 0) throw new Win32Exception(Marshal.GetLastWin32Error(), "StartPage failed."); } public void EndPage() { if (Win32Printer.EndPage(_hdc) <= 0) throw new Win32Exception(Marshal.GetLastWin32Error(), "EndPage failed."); } public void EndDocument() { if (EndDoc(_hdc) <= 0) throw new Win32Exception( Marshal.GetLastWin32Error(), "EndDoc failed."); } public void AbortDocument() { AbortDoc(_hdc); } public void Dispose() { if (_hdc != IntPtr.Zero) DeleteDC(_hdc); if (_devMode != IntPtr.Zero) Marshal.FreeHGlobal(_devMode); if (_printerHandle != IntPtr.Zero) ClosePrinter(_printerHandle); } } } }