File: MS\Internal\Printing\Configuration\WinSpoolPrinterCapabilities.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\ReachFramework\ReachFramework.csproj (ReachFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Runtime.InteropServices;
using MS.Internal.PrintWin32Thunk;
 
/*++
All rights reserved.
 
--*/
 
namespace MS.Internal.Printing.Configuration
{
    /// <summary>
    /// Helper class to obtain printer capabilities from the WINSPOOL API
    /// </summary>
    internal class WinSpoolPrinterCapabilities
    {
        public WinSpoolPrinterCapabilities(string deviceName, string driverName, string portName, DevMode devMode)
        {
            this._deviceName = deviceName;
            this._driverName = driverName;
            this._portName = portName;
            if (devMode != null)
            {
                SafeMemoryHandle buffer = SafeMemoryHandle.Create(devMode.ByteData.Length);
                buffer.CopyFromArray(devMode.ByteData, 0, devMode.ByteData.Length);
                this._devMode = buffer;
            }
            else
            {
                this._devMode = SafeMemoryHandle.Null;
            }
        }
 
        public void Release()
        {
            if (this._devMode != null)
            {
                this._devMode.Dispose();
                this._devMode = null;
            }
        }
 
        /// <summary>
        /// Gets printer settings from GetDeviceCaps
        /// </summary>
        /// <param name="logicalPixelsX">LOGPIXELSX device capability</param>
        /// <param name="logicalPixelsY">LOGPIXELSY device capability</param>
        /// <param name="physicalWidth">PHYSICALWIDTH device capability</param>
        /// <param name="physicalHeight">PHYSICALHEIGHT device capability</param>
        /// <param name="physicalOffsetX">PHYSICALOFFSETX device capability</param>
        /// <param name="physicalOffsetY">PHYSICALOFFSETY device capability</param>
        /// <param name="horizontalResolution">HORZRES device capability</param>
        /// <param name="verticalResolution">VERTRES device capability</param>
        /// <returns>True if the call succedes</returns>
        public bool TryGetDeviceCapabilities(
            out int logicalPixelsX, out int logicalPixelsY,
            out int physicalWidth, out int physicalHeight,
            out int physicalOffsetX, out int physicalOffsetY,
            out int horizontalResolution, out int verticalResolution)
        {
            IntPtr hdc = UnsafeNativeMethods.CreateICW(this._driverName, this._deviceName, null, this._devMode);
            if (hdc != IntPtr.Zero)
            {
                HandleRef hdcRef = new HandleRef(this, hdc);
                
                try
                {
                    logicalPixelsX = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.LOGPIXELSX);
                    logicalPixelsY = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.LOGPIXELSY);
                    physicalWidth = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.PHYSICALWIDTH);
                    physicalHeight = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.PHYSICALHEIGHT);
                    physicalOffsetX = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.PHYSICALOFFSETX);
                    physicalOffsetY = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.PHYSICALOFFSETY);
                    horizontalResolution = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.HORZRES);
                    verticalResolution = UnsafeNativeMethods.GetDeviceCaps(hdcRef, DeviceCap.VERTRES);
                    return true;
                }
                finally
                {
                     UnsafeNativeMethods.DeleteDC(hdcRef);
                }
            }
            else
            {
                logicalPixelsX = 0;
                logicalPixelsY = 0;
                physicalWidth = 0;
                physicalHeight = 0;
                physicalOffsetX = 0;
                physicalOffsetY = 0;
                horizontalResolution = 0;
                verticalResolution = 0;
                return false;
            }
        }
    
        /// <summary>
        /// Get the minimum page width and height
        /// </summary>
        /// <param name="minPageWidth">Is set to the minimum page width</param>
        /// <param name="minPageHeight">Is set to the minimum page height</param>
        public void GetMinExtent(out int minPageWidth, out int minPageHeight)
        {
            uint minExtent = GetIntCapability(DeviceCapability.DC_MINEXTENT);
            minPageWidth = (int)(minExtent & 0x0000FFFF);
            minPageHeight = (int)((minExtent & 0xFFFF0000) >> 16);
        }
 
        /// <summary>
        /// Get the maximum page width and height
        /// </summary>
        /// <param name="maxPaperWidth">Is set to the maximum page width</param>
        /// <param name="maxPaperHeight">Is set to the maximum page height</param>
        public void GetMaxExtent(out int maxPaperWidth, out int maxPaperHeight)
        {
            uint maxExtent = GetIntCapability(DeviceCapability.DC_MAXEXTENT);
            maxPaperWidth = (int)(maxExtent & 0x0000FFFF);
            maxPaperHeight = (int)((maxExtent & 0xFFFF0000) >> 16);
        }
 
        /// <summary>
        /// Get the DC_COLLATE capability
        /// </summary>
        public bool CanCollate
        {
            get { return GetBoolCapability(DeviceCapability.DC_COLLATE); }
        }
 
        /// <summary>
        /// Get DC_COLORDEVICE the capability
        /// </summary>
        public bool HasColor
        {
            get { return GetBoolCapability(DeviceCapability.DC_COLORDEVICE); }
        }
 
        /// <summary>
        /// Get the minimum number of copies the device can print per job
        /// </summary>
        public int MinCopies
        {
            get { return 1; }
        }
 
        /// <summary>
        /// Get DC_COPIES the capability
        /// </summary>
        public int MaxCopies
        {
            get { return (int)GetIntCapability(DeviceCapability.DC_COPIES); }
        }
 
        /// <summary>
        /// Get DC_DUPLEX the capability
        /// </summary>
        public bool CanDuplex
        {
            get { return GetBoolCapability(DeviceCapability.DC_DUPLEX); }
        }
 
        /// <summary>
        /// Get DM_ICMINTENT the capability
        /// </summary>
        public bool HasICMIntent
        {
            get { return GetBoolCapability(DevModeFields.DM_ICMINTENT); }
        }
 
        /// <summary>
        /// Get DM_ICMMETHOD the capability
        /// </summary>
        public bool HasICMMethod
        {
            get { return GetBoolCapability(DevModeFields.DM_ICMMETHOD); }
        }
 
        /// <summary>
        /// Get the array of DC_BINS capability
        /// </summary>
        public IList<short> Bins
        {
            get { return GetArrayCapability<short>(DeviceCapability.DC_BINS, ReadWORDArray); }
        }
 
        /// <summary>
        /// Get the array of DC_BINNAMES capability
        /// </summary>
        public IList<string> BinNames
        {
            get 
            {
                // 24 chars, 2 bytes per char
                return GetArrayCapability<string>(DeviceCapability.DC_BINNAMES, ReadUnicodeStringArray, 24 * 2); 
            }
        }
 
        public IList<uint> NUp
        {
            get { return GetArrayCapability<uint>(DeviceCapability.DC_NUP, ReadDWORDArray); }
        }
 
        /// <summary>
        /// Get the array of DC_PAPERS capability
        /// </summary>
        public IList<short> Papers
        {
            get { return GetArrayCapability<short>(DeviceCapability.DC_PAPERS, ReadWORDArray); }
        }
 
        /// <summary>
        /// Get the array of DC_PAPERNAMES capability
        /// </summary>
        public IList<string> PaperNames
        {
            get
            {
                // 64 chars, 2 bytes per char
                return GetArrayCapability<string>(DeviceCapability.DC_PAPERNAMES, ReadUnicodeStringArray, 64 * 2); 
            }
        }
 
        /// <summary>
        /// Get the array of DC_PAPERSIZE capability
        /// </summary>
        public IList<DC_PAPER_SIZE> PaperSizes
        {
            get { return GetArrayCapability<DC_PAPER_SIZE>(DeviceCapability.DC_PAPERSIZE, ReadDC_PAPER_SIZEArray); }
        }
 
        /// <summary>
        /// Get the array of DC_PAPERSIZE capability
        /// </summary>
        public IList<uint> MediaTypes
        {
            get { return GetArrayCapability<uint>(DeviceCapability.DC_MEDIATYPES, ReadDWORDArray); }
        }
 
        /// <summary>
        /// Get the array of DC_MEDIATYPENAMES capability
        /// </summary>
        public IList<string> MediaTypeNames
        {
            get 
            {
                // 64 chars, 2 bytes per char
                return GetArrayCapability<string>(DeviceCapability.DC_MEDIATYPENAMES, ReadUnicodeStringArray, 64 * 2); 
            }
        }
 
        /// <summary>
        /// Get the DC_ORIENTATION capability
        /// </summary>
        public int LandscapeOrientation
        {
            get { return (int)GetIntCapability(DeviceCapability.DC_ORIENTATION); }
        }
 
        /// <summary>
        /// Get the array of DC_ENUMRESOLUTIONS capability
        /// </summary>
        public IList<DC_RESOLUTION> Resolutions
        {
            get { return GetArrayCapability<DC_RESOLUTION>(DeviceCapability.DC_ENUMRESOLUTIONS, ReadDC_RESOLUTIONArray); }
        }
 
        /// <summary>
        /// Get the DM_SCALE capability
        /// </summary>
        public bool CanScale
        {
            get { return GetBoolCapability(DevModeFields.DM_SCALE); }
        }
 
        /// <summary>
        /// Get the DC_TRUETYPE capability
        /// </summary>
        public bool TrueType
        {
            get { return GetBoolCapability(DeviceCapability.DC_TRUETYPE); }
        }
 
 
        public DevModeFields Fields
        {
            get
            {
                return (DevModeFields)GetIntCapability(DeviceCapability.DC_FIELDS);
            }
        }
 
        /// <summary>
        /// Get the default paper width and height
        /// </summary>
        /// <param name="defaultDevMode">devMode that contains the code for the default paper size</param>
        /// <param name="mediaSizeCodes">List of supported paperSizeCodes</param>
        /// <param name="mediaSizes">List of supported paperSize widths and heights</param>
        /// <param name="defaultPaperSize">Out parameter that recieves the default paper width and height</param>
        /// <returns>True if the call succeded</returns>
        public bool GetDefaultPaperSize(DevMode defaultDevMode, IList<short> paperSizeCodes, IList<DC_PAPER_SIZE> paperSizes, out DC_PAPER_SIZE defaultPaperSize)
        {
            defaultPaperSize = new DC_PAPER_SIZE();
 
            if (defaultDevMode == null)
            {
                if (paperSizes.Count > 0)
                {
                    defaultPaperSize = paperSizes[0];
                    return true;
                }
 
                return false;
            }
            else
            {
                bool hasWidth = false;
                hasWidth = defaultDevMode.IsFieldSet(DevModeFields.DM_PAPERWIDTH);
                if (hasWidth)
                {
                    defaultPaperSize.Width = defaultDevMode.PaperWidth;
                }
 
                bool hasHeight = false;
                hasHeight = defaultDevMode.IsFieldSet(DevModeFields.DM_PAPERWIDTH);
                if (hasHeight)
                {
                    defaultPaperSize.Height = defaultDevMode.PaperLength;
                }
 
                if (!hasWidth || !hasHeight)
                {
                    if (defaultDevMode.IsFieldSet(DevModeFields.DM_PAPERSIZE))
                    {
                        int defaultPaperSizeIndex = paperSizeCodes.IndexOf(defaultDevMode.PaperSize);
                        if (0 <= defaultPaperSizeIndex && defaultPaperSizeIndex < paperSizes.Count)
                        {
                            if (!hasWidth)
                            {
                                defaultPaperSize.Width = paperSizes[defaultPaperSizeIndex].Width;
                            }
 
                            if (!hasHeight)
                            {
                                defaultPaperSize.Height = paperSizes[defaultPaperSizeIndex].Height;
                            }
 
                            hasWidth = hasHeight = true;
                        }
                    }
                }
 
                return hasWidth && hasHeight;
            }
        }
 
        private bool GetBoolCapability(DeviceCapability capability)
        {
            return 0 != GetIntCapability(capability);
        }
 
        private bool GetBoolCapability(DevModeFields capability)
        {
            // Several capabilities are packed into the DC_FIELDS capability
            // Call it once and cache the result to save on extra calls to GetIntCapability
            if (!this._dmFieldsIsCached)
            {
                this._dmFields = (DevModeFields)GetIntCapability(DeviceCapability.DC_FIELDS);
                this._dmFieldsIsCached = true;
            }
 
            return (capability & this._dmFields) == capability;
        }
 
        /// <summary>
        /// Gets a device capability represented as an integer
        /// </summary>
        /// <param name="capability">Capability to retrieve</param>
        /// <returns>value of capability specified in capability argument</returns>
        private uint GetIntCapability(DeviceCapability capability)
        {
            return UnsafeNativeMethods.DeviceCapabilitiesW(this._deviceName, this._portName, capability, SafeMemoryHandle.Null, this._devMode);
        }
 
        /// <summary>
        /// Gets a device capability represented as an array
        /// </summary>
        /// <typeparam name="T">Capability item type</typeparam>
        /// <param name="capability">Capability to retrieve</param>
        /// <param name="readItem">Delegate to marshal a single capability item from unmanaged memory</param>
        /// <returns>An array of capabilities specified by the capability arg or null if the call fails</returns>
        private T[] GetArrayCapability<T>(DeviceCapability capability, ReadArray<T> readItem) where T : struct
        {
            return GetArrayCapability<T>(capability, readItem, Marshal.SizeOf(typeof(T)));
        }
 
        /// <summary>
        /// Delegate to marshal a single capability item from unmanaged memory
        /// </summary>
        /// <typeparam name="T">Capability item type</typeparam>
        /// <param name="src">Pointer to unmanaged memory</param>
        /// <param name="itemByteSize">Bytes per item</param>
        /// <returns>Capability marshaled from src</returns>
        private delegate T[] ReadArray<T>(HGlobalBuffer buffer, int itemByteSize);
 
        /// <summary>
        /// Gets a device capability represented as an array
        /// </summary>
        /// <param name="capability">Capability to retrieve</param>
        /// <returns>value of capability specified in capability argument</returns>
        private T[] GetArrayCapability<T>(DeviceCapability capability, ReadArray<T> readArray, int itemByteSize)
        {
            uint numOutputs = UnsafeNativeMethods.DeviceCapabilitiesW(this._deviceName, this._portName, capability, SafeMemoryHandle.Null, this._devMode);
 
            if (numOutputs < 1)
            {
                return Array.Empty<T>();
            }
 
            HGlobalBuffer buffer = new HGlobalBuffer((int)(numOutputs * itemByteSize));
            try
            {
                numOutputs = UnsafeNativeMethods.DeviceCapabilitiesW(this._deviceName, this._portName, capability, buffer.Handle, this._devMode);
                if (numOutputs >= 0)
                {
                    return readArray(buffer, itemByteSize);
                }
            }
            finally
            {
                buffer.Release();
            }
 
            return null;
        }
 
        private uint[] ReadDWORDArray(HGlobalBuffer buffer, int itemByteSize)
        {
            int nItems = buffer.Length / itemByteSize;            
            uint[] result = new uint[nItems];
 
            bool shouldRelease = false;
            buffer.Handle.DangerousAddRef(ref shouldRelease);
            try 
            {
                IntPtr baseAddr = buffer.Handle.DangerousGetHandle();
                for (int i = 0, offset = 0; i < nItems; i++, offset += itemByteSize)
                {
                    result[i] = (uint)Marshal.ReadInt32(baseAddr, offset);
                }
                
                return result;
            }
            finally
            {
                if(shouldRelease)
                {
                    buffer.Handle.DangerousRelease();
                }
            }
        }
 
        private short[] ReadWORDArray(HGlobalBuffer buffer, int itemByteSize)
        {
            int nItems = buffer.Length / itemByteSize;
            short[] result = new short[nItems];
 
            bool shouldRelease = false;
            buffer.Handle.DangerousAddRef(ref shouldRelease);
            try 
            {
                IntPtr baseAddr = buffer.Handle.DangerousGetHandle();
                for (int i = 0, offset = 0; i < nItems; i++, offset += itemByteSize)
                {
                    result[i] = Marshal.ReadInt16(baseAddr, offset);
                }
 
                return result;
            }
            finally
            {
                if(shouldRelease)
                {
                    buffer.Handle.DangerousRelease();
                }
            }
        }
 
        private static DC_RESOLUTION[] ReadDC_RESOLUTIONArray(HGlobalBuffer buffer, int itemByteSize)
        {
            int nItems = buffer.Length / itemByteSize;
            DC_RESOLUTION[] result = new DC_RESOLUTION[nItems];
 
            bool shouldRelease = false;
            buffer.Handle.DangerousAddRef(ref shouldRelease);
            try 
            {
                IntPtr baseAddr = buffer.Handle.DangerousGetHandle();
                for (int i = 0, offset = 0; i < nItems; i++, offset += itemByteSize)
                {
                    int x = Marshal.ReadInt32(baseAddr, offset);
                    int y = Marshal.ReadInt32(baseAddr, offset + (itemByteSize / 2));
                    result[i] = new DC_RESOLUTION(x, y);
                }
 
                return result;
            }
            finally
            {
                if(shouldRelease)
                {
                    buffer.Handle.DangerousRelease();
                }
            }
        }
 
        private static DC_PAPER_SIZE[] ReadDC_PAPER_SIZEArray(HGlobalBuffer buffer, int itemByteSize)
        {
            int nItems = buffer.Length / itemByteSize;
            DC_PAPER_SIZE[] result = new DC_PAPER_SIZE[nItems];
 
            bool shouldRelease = false;
            buffer.Handle.DangerousAddRef(ref shouldRelease);
            try 
            {
                IntPtr baseAddr = buffer.Handle.DangerousGetHandle();
                for (int i = 0, offset = 0; i < nItems; i++, offset += itemByteSize)
                {
                    int w = Marshal.ReadInt32(baseAddr, offset);
                    int h = Marshal.ReadInt32(baseAddr, offset + (itemByteSize / 2));
                    result[i] = new DC_PAPER_SIZE(w, h);
                }
 
                return result;
            }
            finally
            {
                if(shouldRelease)
                {
                    buffer.Handle.DangerousRelease();
                }
            }
        }
 
        private static string [] ReadUnicodeStringArray(HGlobalBuffer buffer, int itemByteSize)
        {
            int nItems = buffer.Length / itemByteSize;
            string[] result = new string[nItems];
 
            bool shouldRelease = false;
            buffer.Handle.DangerousAddRef(ref shouldRelease);
            try 
            {
                IntPtr baseAddr = buffer.Handle.DangerousGetHandle();
                for (int i = 0, offset = 0; i < nItems; i++, offset += itemByteSize)
                {
                    IntPtr strAddr = new IntPtr(baseAddr.ToInt64() + offset);
                    string str = Marshal.PtrToStringUni(strAddr, itemByteSize / 2);
 
                    //Marshal.PtrToStringUni(IntPtr) Copies until '/0'
                    //Marshal.PtrToStringUni(IntPtr, int len) Copies all characters in the string including any '\0'
                    // What we want is to copy until '/0' but no more than len chars
 
                    int index = str.IndexOf('\0');
                    if (index >= 0)
                    {
                        str = str.Substring(0, index);
                    }
                    result[i] = str;
                }
 
                return result;
            }
            finally
            {
                if(shouldRelease)
                {
                    buffer.Handle.DangerousRelease();
                }
            }
        }
 
        #region Private Fields
 
        private bool _dmFieldsIsCached;
 
        private string _deviceName;
 
        private string _driverName;
 
        private string _portName;
 
        private DevModeFields _dmFields;
 
        SafeMemoryHandle _devMode;
 
        #endregion
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct DC_PAPER_SIZE
    {
        public DC_PAPER_SIZE(int width, int height)
        {
            this.Width = width;
            this.Height = height;
        }
 
        public int Width;
        public int Height;
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct DC_RESOLUTION
    {
        public DC_RESOLUTION(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
 
        public int x;
        public int y;
    }
}