File: misc\GDI\WindowsFont.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="WindowsFont.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
#if Microsoft_NAMESPACE
namespace System.Windows.Forms.Internal
#elif DRAWING_NAMESPACE
namespace System.Drawing.Internal
#else
namespace System.Experimental.Gdi
#endif
{
    using System;
    using System.Internal;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Text;
    using System.Security;
    using System.Security.Permissions;
    using System.Globalization;
    using System.Runtime.Versioning;
 
    /// <devdoc>
    ///     <para>
    ///         Encapsulates a GDI Font object.
    ///     </para>
    /// </devdoc>
#if Microsoft_PUBLIC_GRAPHICS_LIBRARY
    public
#else
    internal
#endif
    sealed partial class WindowsFont : MarshalByRefObject, ICloneable, IDisposable
    {
        const int LogFontNameOffset = 28;
        
        // Handle to the native Windows font object.
        // 
        private IntPtr hFont;
        private float  fontSize = -1.0f; //invalid value.
        private int    lineSpacing;
        private bool   ownHandle;
        private bool   ownedByCacheManager;
        private bool   everOwnedByCacheManager;
 
        private IntNativeMethods.LOGFONT logFont; 
        private FontStyle style;
 
        // Note: These defaults are according to the ones in GDI+ but those are not necessarily the same as the system
        // default font.  The GetSystemDefaultHFont() method should be used if needed.
        private const string defaultFaceName   = "Microsoft Sans Serif";
        private const float  defaultFontSize   = 8.25f;
        private const int    defaultFontHeight = 13;
 
#if GDI_FINALIZATION_WATCH
       private string AllocationSite = DbgUtil.StackTrace;
#endif
 
        /// <devdoc>
        ///     Creates the font handle.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        private void CreateFont()
        {
            Debug.Assert( hFont == IntPtr.Zero, "hFont is not null, this will generate a handle leak." );
            Debug.Assert( this.logFont != null, "WindowsFont.logFont not initialized." );
 
            this.hFont = IntUnsafeNativeMethods.CreateFontIndirect(this.logFont);
 
#if TRACK_HFONT
            Debug.WriteLine( DbgUtil.StackTraceToStr(String.Format( "HFONT[0x{0:x8}] = CreateFontIndirect( LOGFONT={1} )", (int) this.hFont, this.logFont)));
#endif
            if (this.hFont == IntPtr.Zero)
            {
                this.logFont.lfFaceName     = defaultFaceName;
                this.logFont.lfOutPrecision = IntNativeMethods.OUT_TT_ONLY_PRECIS; // True Type only.
 
                this.hFont = IntUnsafeNativeMethods.CreateFontIndirect(this.logFont);
 
#if TRACK_HFONT
            Debug.WriteLine( DbgUtil.StackTraceToStr(String.Format( "HFONT[0x{0:x8}] = CreateFontIndirect( LOGFONT={1} )", (int) this.hFont, this.logFont)));
#endif
 
            }
 
            // Update logFont height and other adjusted parameters.
            //
            IntUnsafeNativeMethods.GetObject(new HandleRef(this, this.hFont), this.logFont);
 
            // We created the hFont, we will delete it on dispose.
            this.ownHandle = true;
        }
 
        /// Constructors.
 
        /// <devdoc>
        ///     Contructor to construct font from a face name.
        /// </devdoc>>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public WindowsFont( string faceName ) :
            this(faceName, defaultFontSize, FontStyle.Regular, IntNativeMethods.DEFAULT_CHARSET, WindowsFontQuality.Default)
        {
         // Default size in Microsoft is 8.25f.  
        }
 
        /// <devdoc>
        ///     Contructor to construct font from a face name, a desired size and with the specified style.
        /// </devdoc>>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public WindowsFont( string faceName, float size ) :
            this(faceName, size, FontStyle.Regular, IntNativeMethods.DEFAULT_CHARSET, WindowsFontQuality.Default)
        {
        }
 
        /// <devdoc>
        ///     Contructor to construct font from a face name, a desired size and with the specified style.
        /// </devdoc>>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public WindowsFont( string faceName, float size, FontStyle style ) :
            this(faceName, size, style, IntNativeMethods.DEFAULT_CHARSET, WindowsFontQuality.Default)
        {
        }
        
        /// <devdoc>
        ///     Contructor to construct font from a face name, a desired size in points and with the specified style
        ///     and character set. The screen dc is used for calculating the font em height.
        /// </devdoc>>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public WindowsFont( string faceName, float size, FontStyle style, byte charSet, WindowsFontQuality fontQuality )
        {   
            Debug.Assert( size > 0.0f, "size has a negative value." );
            const byte True  = 1;
            const byte False = 0;
            this.logFont = new IntNativeMethods.LOGFONT();
 
            //
            // Get the font height from the specified size.  size is in point units and height in logical 
            // units (pixels when using MM_TEXT) so we need to make the conversion using the number of 
            // pixels per logical inch along the screen height.
            //
            int pixelsY = (int) Math.Ceiling( WindowsGraphicsCacheManager.MeasurementGraphics.DeviceContext.DpiY * size / 72); // 1 point = 1/72 inch.;
 
            //
            // The lfHeight represents the font cell height (line spacing) which includes the internal 
            // leading; we specify a negative size value (in pixels) for the height so the font mapper 
            // provides the closest match for the character height rather than the cell height (MSDN).
            //
            this.logFont.lfHeight       = -pixelsY;
            this.logFont.lfFaceName     = faceName != null ? faceName : defaultFaceName;
            this.logFont.lfCharSet      = charSet;
            this.logFont.lfOutPrecision = IntNativeMethods.OUT_TT_PRECIS;
            this.logFont.lfQuality      = (byte) fontQuality;
            this.logFont.lfWeight       = (style & FontStyle.Bold)      == FontStyle.Bold      ? IntNativeMethods.FW_BOLD : IntNativeMethods.FW_NORMAL;
            this.logFont.lfItalic       = (style & FontStyle.Italic)    == FontStyle.Italic    ? True : False;
            this.logFont.lfUnderline    = (style & FontStyle.Underline) == FontStyle.Underline ? True : False;
            this.logFont.lfStrikeOut    = (style & FontStyle.Strikeout) == FontStyle.Strikeout ? True : False;
 
            // Let the Size be recomputed to be consistent with the Height (there may be some precision loss coming from size to height).
            // this.fontSize = size;
            this.style    = style;
 
            CreateFont();
        }
 
        /// <devdoc>
        ///     Contructor to construct font from a LOGFONT structure.
        ///     Pass false in the createHandle param to create a 'compatible' font (handle-less, to be used for measuring/comparing) or
        ///     when the handle has already been created.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        private WindowsFont( IntNativeMethods.LOGFONT lf, bool createHandle )
        {
            Debug.Assert( lf != null, "lf is null" );
 
            this.logFont = lf;
 
            if (this.logFont.lfFaceName == null)
            {
                this.logFont.lfFaceName = defaultFaceName;
            }
 
            this.style = FontStyle.Regular;
            if (lf.lfWeight == IntNativeMethods.FW_BOLD)
            {
                this.style |= FontStyle.Bold;
            }
            if (lf.lfItalic == 1)
            {
                this.style |= FontStyle.Italic;
            }
            if (lf.lfUnderline == 1)
            {
                this.style |= FontStyle.Underline;
            }
            if (lf.lfStrikeOut == 1)
            {
                this.style |= FontStyle.Strikeout;
            }
 
            if( createHandle )
            {
                CreateFont();
            }
        }
 
        /// <devdoc>
        ///     Contructs a WindowsFont object from an existing System.Drawing.Font object (GDI+), based on the screen dc MapMode
        ///     and resolution (normally: MM_TEXT and 96 dpi).
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsFont FromFont(Font font)
        {
            return FromFont(font, WindowsFontQuality.Default);
        }
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsFont FromFont(Font font, WindowsFontQuality fontQuality) 
        {
            string familyName = font.FontFamily.Name;
 
            // Strip vertical-font mark from the name if needed.
            if (familyName != null && familyName.Length > 1 && familyName[0] == '@') 
            {
                familyName = familyName.Substring(1);
            }
 
            // Note: Creating the WindowsFont from Font using a LOGFONT structure from GDI+ (Font.ToLogFont(logFont)) may sound like
            // a better choice (more accurate) for doing this but tests show that is not the case (see WindowsFontTests test suite),
            // the results are the same.  Also, that approach has some issues when the Font is created in a different application 
            // domain since the LOGFONT cannot be marshalled properly, see VSW#451960.
            // Now, creating it using the Font.SizeInPoints makes it GraphicsUnit-independent.
 
            return new WindowsFont(familyName, font.SizeInPoints, font.Style, font.GdiCharSet, fontQuality);
        }
 
        
        /// <devdoc>
        ///     Creates a WindowsFont from the font selected in the supplied dc.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsFont FromHdc( IntPtr hdc )
        {
            IntPtr hFont = IntUnsafeNativeMethods.GetCurrentObject(new HandleRef(null, hdc), IntNativeMethods.OBJ_FONT);
 
            // don't call DeleteObject on handle from GetCurrentObject, it is the one selected in the hdc.
 
            return FromHfont( hFont );
        }
 
        /// <devdoc>
        ///     Creates a WindowsFont from the handle to a native GDI font.  It does not take ownership of the 
        ///     passed-in handle, the caller needs to delete the hFont when done with the WindowsFont.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsFont FromHfont( IntPtr hFont )
        {
            return FromHfont( hFont, false );
        }
 
        /// <devdoc>
        ///     Creates a WindowsFont from the handle to a native GDI font and optionally takes ownership of managing
        ///     the lifetime of the handle. 
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public static WindowsFont FromHfont( IntPtr hFont, bool takeOwnership )
        {
            IntNativeMethods.LOGFONT lf = new IntNativeMethods.LOGFONT();
            IntUnsafeNativeMethods.GetObject(new HandleRef(null, hFont), lf);
            
            WindowsFont wf = new WindowsFont( lf, /*createHandle*/ false );
            wf.hFont = hFont;
            wf.ownHandle = takeOwnership; // if true, hFont will be deleted on dispose.
 
            return wf;
        }
 
        ~WindowsFont()
        {
            Dispose(false);
        }
 
        public void Dispose()
        {
            Dispose(true);
        }
 
        internal void Dispose(bool disposing)
        {
            bool deletedHandle = false;
            if (this.ownHandle)
            {
                if (!ownedByCacheManager || !disposing) {
 
                    // If we were ever owned by the CacheManger and we're being disposed
                    // we can be sure that we're not in use by any DC's (otherwise Dispose() wouldn't have been called)                    
                    // skip the check IsFontInUse check in this case.
                    // Also skip the check if disposing == false, because the cache is thread-static
                    // and that means we're being called from the finalizer.
                    if (everOwnedByCacheManager || !disposing || !DeviceContexts.IsFontInUse(this)) {
                        Debug.Assert( this.hFont != IntPtr.Zero, "Unexpected null hFont." );
                        DbgUtil.AssertFinalization(this, disposing);
                        
                        IntUnsafeNativeMethods.DeleteObject(new HandleRef(this, this.hFont));
#if TRACK_HFONT
                        Debug.WriteLine( DbgUtil.StackTraceToStr(String.Format( "DeleteObject(HFONT[0x{0:x8}]))", (int) this.hFont)));
#endif
                        this.hFont = IntPtr.Zero;
                        this.ownHandle = false;
                        deletedHandle = true;
                    }
                }
            }
 
            if (disposing && (deletedHandle || !ownHandle))
            {
                GC.SuppressFinalize(this);
            }
        }
 
        /// <devdoc>
        ///    Returns a value indicating whether the specified object is a WindowsFont equivalent to this object.
        /// </devdoc>
        public override bool Equals( object font )
        {
            WindowsFont winFont = font as WindowsFont;
 
            if( winFont == null )
            {
                return false;
            }
 
            if( winFont == this )
            {
                return true;
            }
 
            // WARNING: don't use non-public fields/properties here, the passed-in font object could be a proxy in a
            //          remoting scenario and proxies cannot access internal or private members.  VSW#465265 & 465647
 
            // Compare params used to create the font.
            return  this.Name           == winFont.Name            &&
                    this.LogFontHeight  == winFont.LogFontHeight   && // Equivalent to comparing Size but always at hand.
                    this.Style          == winFont.Style           &&
                    this.CharSet        == winFont.CharSet         &&
                    this.Quality        == winFont.Quality;
        }
 
        /// <devdoc>
        ///    Gets the hash code for this WindowsFont.
        /// </devdoc>
        public override int GetHashCode() 
        {
            // similar to Font.GetHashCode().
            return (int)((((UInt32)this.Style   << 13) | ((UInt32)this.Style   >> 19)) ^
                         (((UInt32)this.CharSet << 26) | ((UInt32)this.CharSet >>  6)) ^
                         (((UInt32)this.Size    <<  7) | ((UInt32)this.Size    >> 25)));
        }
 
        /// <devdoc>
        ///     Clones this object.
        /// </devdoc>
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        public object Clone()
        {
            return new WindowsFont( this.logFont, true );
        }
 
        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture, "[{0}: Name={1}, Size={2} points, Height={3} pixels, Sytle={4}]", GetType().Name,  logFont.lfFaceName, this.Size, this.Height, this.Style); 
        }
 
        ////////////////////////////////////////////
        ///  Properties
        
        /// <devdoc>
        ///       Returns this object's native Win32 font handle.  Should NOT be deleted externally.
        ///       Compare with ToHfont method.
        /// </devdoc>
        public IntPtr Hfont
        { 
            get
            {
                //Assert removed. We need to be able to check for Hfont == IntPtr.Zero to determine if the object was disposed.
                //Debug.Assert(this.hFont != IntPtr.Zero, "hFont is null, are you using a disposed object?");
                return this.hFont;
            }
        }
 
        /// <devdoc>
        ///     Determines whether the font has the italic style or not.
        /// </devdoc>
        public bool Italic
        {
            get
            {
                return logFont.lfItalic == 1;
            }
        }
 
        public bool OwnedByCacheManager
        {
            get {
                return ownedByCacheManager;
            }
            set {
                if (value) {
                    everOwnedByCacheManager = true;
                }
                ownedByCacheManager = value;
            }
        }
 
        /// <devdoc>
        ///     Rendering quality.
        /// </devdoc>
        public WindowsFontQuality Quality
        {
            get
            {
                return (WindowsFontQuality) this.logFont.lfQuality;
            }
        }
 
        /// <devdoc>
        ///     Gets the font style.
        /// </devdoc>
        public FontStyle Style
        {
            get
            {
                return this.style;
            }
        }
 
        /// <devdoc>
        ///     Gets the line spacing (cell height) of this font in (screen) pixels using the screen resolution.
        ///     Gets the line spacing (cell height), in pixels (using the screen DC resolution), of this font. 
        ///     The line spacing is the vertical distance between the base lines of two consecutive lines of text. 
        ///     Thus, the line spacing includes the blank space between lines along with the height of the character 
        ///     itself.
        /// </devdoc>
        public int Height
        {
            // 
 
            get
            {
                if( this.lineSpacing == 0 )
                {
                    // Observe that the font text metrics are obtained using the resolution of the screen.
                    WindowsGraphics wg  = WindowsGraphicsCacheManager.MeasurementGraphics;
 
                    // No need to reset the font (if changed) since we always set the font before using the MeasurementGraphics
                    // in WindowsGraphics methods.
                    wg.DeviceContext.SelectFont(this);
 
                    IntNativeMethods.TEXTMETRIC tm = (IntNativeMethods.TEXTMETRIC) wg.GetTextMetrics();
                    this.lineSpacing = tm.tmHeight;
                }
 
                return this.lineSpacing;
            }
        }
 
        /// <devdoc>
        ///     Gets the font character set.  
        ///     This is used by the system font mapper when searching for the physical font that best matches the logical font.
        /// </devdoc>
        public byte CharSet
        {
            get
            {
                return logFont.lfCharSet;
            }
        }
 
        /// <devdoc>
        ///     Specifies the height, in logical units, of the font's character cell or character. The character height value (em height)
        ///     is the character cell height value minus the internal-leading value. 
        /// </devdoc>
        public int LogFontHeight
        {
            get
            {
                return logFont.lfHeight;
            }
        }
 
        /// <devdoc>
        ///     The font's face name.
        /// </devdoc>
        public string Name
        {
            get
            {
                return logFont.lfFaceName;
            }
        }
 
        /// <devdoc>
        ///     Gets the character height (as opposed to the cell height) of the font represented by this object in points.
        ///     Consider
        /// </devdoc>
        public float Size 
        {
            get
            {
                if( this.fontSize < 0.0f )
                {
                    WindowsGraphics wg  = WindowsGraphicsCacheManager.MeasurementGraphics;
 
                    // No need to reset the font (if changed) since we always set the font before using the MeasurementGraphics
                    // in WindowsGraphics methods.
                    wg.DeviceContext.SelectFont(this);
 
                    IntNativeMethods.TEXTMETRIC tm = (IntNativeMethods.TEXTMETRIC) wg.GetTextMetrics();
 
                    //
                    // Convert the font character height to points.  If lfHeight is negative, Windows 
                    // treats the absolute value of that number as a desired font height compatible with 
                    // the point size; in this case lfHeight will roughly match the tmHeight field of 
                    // the TEXTMETRIC structure less the tmInternalLeading field. 
                    //
                    int height = this.logFont.lfHeight > 0 ? tm.tmHeight : (tm.tmHeight - tm.tmInternalLeading);
 
                    // 
                    /*
                    switch (this.unit)
                    {
                        case GraphicsUnit.Pixel:       worldEmSize = height * dpi / 72.0f;   break;
                        case GraphicsUnit.Point:       worldEmSize = height * dpi / 72.0f;   break;
                        case GraphicsUnit.Inch:        worldEmSize = height * dpi;           break;
                        case GraphicsUnit.Document:    worldEmSize = height * dpi / 300.0f;  break;
                        case GraphicsUnit.Millimeter:  worldEmSize = height * dpi / 25.4f;   break;
                    }
                    */
                
                    this.fontSize = height * 72f / wg.DeviceContext.DpiY;
                }
                
                return this.fontSize;
            }
        }
 
        /// <devdoc>
        ///     Attempts to match the TextRenderingHint of the specified Graphics object with a LOGFONT.lfQuality value.
        /// </devdoc>
        public static WindowsFontQuality WindowsFontQualityFromTextRenderingHint(Graphics g)
        {
            if (g == null)
            {
                return WindowsFontQuality.Default;
            }
 
            switch (g.TextRenderingHint)
            {
                case TextRenderingHint.ClearTypeGridFit:
                    // See WindowsFontQuality enum for the flags supported in the different OS systems.
                    if (Environment.OSVersion.Version.Major == 5 && Environment.OSVersion.Version.Minor >= 1)
                    {
                        return WindowsFontQuality.ClearTypeNatural;
                    }
                    else
                    {
                        return WindowsFontQuality.ClearType;
                    }
 
                case TextRenderingHint.AntiAliasGridFit:
                    return WindowsFontQuality.AntiAliased;
 
                case TextRenderingHint.AntiAlias:
                    return WindowsFontQuality.AntiAliased;
 
                case TextRenderingHint.SingleBitPerPixelGridFit:
                    return WindowsFontQuality.Proof;
 
                case TextRenderingHint.SingleBitPerPixel:
                    return WindowsFontQuality.Draft;
 
                default:
                case TextRenderingHint.SystemDefault:
                    return WindowsFontQuality.Default;
                
            }
        }
    }
}