// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Drawing.Interop;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;

namespace System.Drawing;

/// <summary>
///  Defines a particular format for text, including font face, size, and style attributes.
/// </summary>
[Editor($"System.Drawing.Design.FontEditor, {AssemblyRef.SystemDrawingDesign}",
        $"System.Drawing.Design.UITypeEditor, {AssemblyRef.SystemDrawing}")]
[TypeConverter(typeof(FontConverter))]
[Serializable]
[Runtime.CompilerServices.TypeForwardedFrom(AssemblyRef.SystemDrawing)]
public sealed unsafe class Font : MarshalByRefObject, ICloneable, IDisposable, ISerializable, IPointer<GpFont>
{
    [NonSerialized]
    private GpFont* _nativeFont;
    private float _fontSize;
    private FontStyle _fontStyle;
    private FontFamily _fontFamily = null!;
    private GraphicsUnit _fontUnit;
    private byte _gdiCharSet = (byte)FONT_CHARSET.DEFAULT_CHARSET;
    private bool _gdiVerticalFont;
    private string _systemFontName = string.Empty;
    private string? _originalFontName;

    nint IPointer<GpFont>.Pointer => (nint)_nativeFont;

    /// <summary>
    ///  Gets the em-size of this <see cref="Font"/> measured in the units specified by the <see cref="Unit"/> property.
    /// </summary>
    public float Size => _fontSize;

    /// <summary>
    ///  Gets style information for this <see cref='Font'/>.
    /// </summary>
    [Browsable(false)]
    public FontStyle Style => _fontStyle;

    /// <summary>
    ///  Gets a value indicating whether this <see cref='Font'/> is bold.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public bool Bold => (Style & FontStyle.Bold) != 0;

    /// <summary>
    ///  Gets a value indicating whether this <see cref='Font'/> is Italic.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public bool Italic => (Style & FontStyle.Italic) != 0;

    /// <summary>
    ///  Gets a value indicating whether this <see cref='Font'/> is strikeout (has a line through it).
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public bool Strikeout => (Style & FontStyle.Strikeout) != 0;

    /// <summary>
    ///  Gets a value indicating whether this <see cref='Font'/> is underlined.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public bool Underline => (Style & FontStyle.Underline) != 0;

    /// <summary>
    ///  Gets the <see cref='Drawing.FontFamily'/> of this <see cref='Font'/>.
    /// </summary>
    [Browsable(false)]
    public FontFamily FontFamily => _fontFamily;

    /// <summary>
    ///  Gets the face name of this <see cref='Font'/> .
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Editor($"System.Drawing.Design.FontNameEditor, {AssemblyRef.SystemDrawingDesign}",
            $"System.Drawing.Design.UITypeEditor, {AssemblyRef.SystemDrawing}")]
    [TypeConverter(typeof(FontConverter.FontNameConverter))]
    public string Name => FontFamily.Name;

    /// <summary>
    ///  Gets the unit of measure for this <see cref='Font'/>.
    /// </summary>
    [TypeConverter(typeof(FontConverter.FontUnitConverter))]
    public GraphicsUnit Unit => _fontUnit;

    /// <summary>
    ///  Returns the GDI char set for this instance of a font. This will only
    ///  be valid if this font was created from a classic GDI font definition,
    ///  like a LOGFONT or HFONT, or it was passed into the constructor.
    ///
    ///  This is here for compatibility with native Win32 intrinsic controls
    ///  on non-Unicode platforms.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public byte GdiCharSet => _gdiCharSet;

    /// <summary>
    ///  Determines if this font was created to represent a GDI vertical font. This will only be valid if this font
    ///  was created from a classic GDIfont definition, like a LOGFONT or HFONT, or it was passed into the constructor.
    ///
    ///  This is here for compatibility with native Win32 intrinsic controls on non-Unicode platforms.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public bool GdiVerticalFont => _gdiVerticalFont;

    /// <summary>
    ///  This property is required by the framework and not intended to be used directly.
    /// </summary>
    [Browsable(false)]
    public string? OriginalFontName => _originalFontName;

    /// <summary>
    ///  Gets the name of this <see cref='Font'/>.
    /// </summary>
    [Browsable(false)]
    public string SystemFontName => _systemFontName;

    /// <summary>
    ///  Returns true if this <see cref='Font'/> is a SystemFont.
    /// </summary>
    [Browsable(false)]
    public bool IsSystemFont => !string.IsNullOrEmpty(_systemFontName);

    /// <summary>
    ///  Gets the height of this <see cref='Font'/>.
    /// </summary>
    [Browsable(false)]
    public int Height => (int)Math.Ceiling(GetHeight());

    /// <summary>
    ///  Get native GDI+ object pointer. This property triggers the creation of the GDI+ native object if not initialized yet.
    /// </summary>
    internal GpFont* NativeFont => _nativeFont;

    /// <summary>
    ///  Cleans up Windows resources for this <see cref='Font'/>.
    /// </summary>
    ~Font() => Dispose(false);

    private Font(SerializationInfo info, StreamingContext context)
    {
        string name = info.GetString("Name")!; // Do not rename (binary serialization)
        FontStyle style = (FontStyle)info.GetValue("Style", typeof(FontStyle))!; // Do not rename (binary serialization)
        GraphicsUnit unit = (GraphicsUnit)info.GetValue("Unit", typeof(GraphicsUnit))!; // Do not rename (binary serialization)
        float size = info.GetSingle("Size"); // Do not rename (binary serialization)

        Initialize(name, size, style, unit, (byte)FONT_CHARSET.DEFAULT_CHARSET, IsVerticalName(name));
    }

    void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context)
    {
        string name = string.IsNullOrEmpty(OriginalFontName) ? Name : OriginalFontName;
        si.AddValue("Name", name); // Do not rename (binary serialization)
        si.AddValue("Size", Size); // Do not rename (binary serialization)
        si.AddValue("Style", Style); // Do not rename (binary serialization)
        si.AddValue("Unit", Unit); // Do not rename (binary serialization)
    }

    private static bool IsVerticalName(string familyName) => familyName?.Length > 0 && familyName[0] == '@';

    /// <summary>
    ///  Cleans up Windows resources for this <see cref='Font'/>.
    /// </summary>
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (_nativeFont is not null)
        {
            try
            {
#if DEBUG
                Status status = !Gdip.Initialized ? Status.Ok :
#endif
                PInvokeGdiPlus.GdipDeleteFont(_nativeFont);
#if DEBUG
                Debug.Assert(status == Status.Ok, $"GDI+ returned an error status: {status}");
#endif
            }
            catch (Exception ex) when (!ClientUtils.IsCriticalException(ex))
            {
            }
            finally
            {
                _nativeFont = null;
            }
        }
    }

    /// <summary>
    ///  Returns the height of this Font in the specified graphics context.
    /// </summary>
    public float GetHeight(Graphics graphics)
    {
        ArgumentNullException.ThrowIfNull(graphics);
        if (graphics.NativeGraphics is null)
        {
            throw new ArgumentException(message: null, nameof(graphics));
        }

        float height;
        PInvokeGdiPlus.GdipGetFontHeight(NativeFont, graphics.Pointer(), &height).ThrowIfFailed();
        GC.KeepAlive(this);
        GC.KeepAlive(graphics);
        return height;
    }

    public float GetHeight(float dpi)
    {
        float height;
        PInvokeGdiPlus.GdipGetFontHeightGivenDPI(NativeFont, dpi, &height).ThrowIfFailed();
        GC.KeepAlive(this);
        return height;
    }

    /// <summary>
    ///  Returns a value indicating whether the specified object is a <see cref='Font'/> equivalent to this
    ///  <see cref='Font'/>.
    /// </summary>
    public override bool Equals([NotNullWhen(true)] object? obj)
    {
        if (obj == this)
        {
            return true;
        }

        if (obj is not Font font)
        {
            return false;
        }

        // Note: If this and/or the passed-in font are disposed, this method can still return true since we check for cached properties
        // here.
        // We need to call properties on the passed-in object since it could be a proxy in a remoting scenario and proxies don't
        // have access to private/internal fields.
        return font.FontFamily.Equals(FontFamily)
            && font.GdiVerticalFont == GdiVerticalFont
            && font.GdiCharSet == GdiCharSet
            && font.Style == Style
            && font.Size == Size
            && font.Unit == Unit;
    }

    /// <summary>
    ///  Gets the hash code for this <see cref='Font'/>.
    /// </summary>
    public override int GetHashCode() => HashCode.Combine(Name, Style, Size, Unit);

    /// <summary>
    ///  Returns a human-readable string representation of this <see cref='Font'/>.
    /// </summary>
    public override string ToString() =>
        $"[{GetType().Name}: Name={FontFamily.Name}, Size={_fontSize}, Units={(int)_fontUnit}, GdiCharSet={_gdiCharSet}, GdiVerticalFont={_gdiVerticalFont}]";

    // This is used by SystemFonts when constructing a system Font objects.
    internal void SetSystemFontName(string systemFontName) => _systemFontName = systemFontName;

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void ToLogFont(object logFont, Graphics graphics)
    {
        ArgumentNullException.ThrowIfNull(logFont);

        Type type = logFont.GetType();
        int nativeSize = sizeof(LOGFONT);

        // Marshal is necessary here. ToLogFont(LogFont, Graphics) is the marshal free version.

#pragma warning disable CA1421 // This method uses runtime marshalling even when the 'DisableRuntimeMarshallingAttribute' is applied
        if (Marshal.SizeOf(type) != nativeSize)
        {
            // If we don't actually have an object that is LOGFONT in size, trying to pass
            // it to GDI+ is likely to cause an AV.
            throw new ArgumentException(null, nameof(logFont));
        }

        ToLogFont(out LOGFONT nativeLogFont, graphics);

        // PtrToStructure requires that the passed in object not be a value type.
        if (!type.IsValueType)
        {
            Marshal.PtrToStructure(new IntPtr(&nativeLogFont), logFont);
        }
        else
        {
            GCHandle handle = GCHandle.Alloc(logFont, GCHandleType.Pinned);
            Buffer.MemoryCopy(&nativeLogFont, (byte*)handle.AddrOfPinnedObject(), nativeSize, nativeSize);
            handle.Free();
        }
#pragma warning restore CA1421
    }

#if NET8_0_OR_GREATER
    public
#else
    private
#endif
    void ToLogFont(out LOGFONT logFont, Graphics graphics)
    {
        ArgumentNullException.ThrowIfNull(graphics);

        fixed (LOGFONT* lf = &logFont)
        {
            PInvokeGdiPlus.GdipGetLogFont(NativeFont, graphics.Pointer(), (LOGFONTW*)lf).ThrowIfFailed();
            GC.KeepAlive(this);
            GC.KeepAlive(graphics);
        }

        // Prefix the string with '@' if this is a gdiVerticalFont.
        if (_gdiVerticalFont)
        {
            Span<char> faceName = logFont.lfFaceName;
            faceName[..^1].CopyTo(faceName[1..]);
            faceName[0] = '@';

            // Docs require this to be null terminated
            faceName[^1] = '\0';
        }

        if (logFont.lfCharSet == 0)
        {
            logFont.lfCharSet = _gdiCharSet;
        }
    }

    /// <summary>
    ///  Creates the GDI+ native font object.
    /// </summary>
    private void CreateNativeFont()
    {
        Debug.Assert(_nativeFont is null, "nativeFont already initialized, this will generate a handle leak.");
        Debug.Assert(_fontFamily is not null, "fontFamily not initialized.");

        // Note: GDI+ creates singleton font family objects (from the corresponding font file) and reference count them so
        // if creating the font object from an external FontFamily, this object's FontFamily will share the same native object.

        GpFont* font;
        Status status = PInvokeGdiPlus.GdipCreateFont(_fontFamily.Pointer(), _fontSize, (int)_fontStyle, (Unit)_fontUnit, &font);
        GC.KeepAlive(this);
        _nativeFont = font;

        // Special case this common error message to give more information
        if (status == Status.FontStyleNotFound)
        {
            throw new ArgumentException(SR.Format(SR.GdiplusFontStyleNotFound, _fontFamily.Name, _fontStyle.ToString()));
        }
        else if (status != Status.Ok)
        {
            throw status.GetException();
        }
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class from the specified existing <see cref='Font'/>
    ///  and <see cref='FontStyle'/>.
    /// </summary>
    public Font(Font prototype, FontStyle newStyle)
    {
        // Copy over the originalFontName because it won't get initialized
        _originalFontName = prototype.OriginalFontName;
        Initialize(prototype.FontFamily, prototype.Size, newStyle, prototype.Unit, (byte)FONT_CHARSET.DEFAULT_CHARSET, false);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(FontFamily family, float emSize, FontStyle style, GraphicsUnit unit)
    {
        Initialize(family, emSize, style, unit, (byte)FONT_CHARSET.DEFAULT_CHARSET, false);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(FontFamily family, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet)
    {
        Initialize(family, emSize, style, unit, gdiCharSet, false);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(FontFamily family, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet, bool gdiVerticalFont)
    {
        Initialize(family, emSize, style, unit, gdiCharSet, gdiVerticalFont);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(string familyName, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet)
    {
        Initialize(familyName, emSize, style, unit, gdiCharSet, IsVerticalName(familyName));
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(string familyName, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet, bool gdiVerticalFont)
    {
        if (float.IsNaN(emSize) || float.IsInfinity(emSize) || emSize <= 0)
        {
            throw new ArgumentException(SR.Format(SR.InvalidBoundArgument, nameof(emSize), emSize, 0, "System.Single.MaxValue"), nameof(emSize));
        }

        Initialize(familyName, emSize, style, unit, gdiCharSet, gdiVerticalFont);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(FontFamily family, float emSize, FontStyle style)
    {
        Initialize(family, emSize, style, GraphicsUnit.Point, (byte)FONT_CHARSET.DEFAULT_CHARSET, false);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(FontFamily family, float emSize, GraphicsUnit unit)
    {
        Initialize(family, emSize, FontStyle.Regular, unit, (byte)FONT_CHARSET.DEFAULT_CHARSET, false);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(FontFamily family, float emSize)
    {
        Initialize(family, emSize, FontStyle.Regular, GraphicsUnit.Point, (byte)FONT_CHARSET.DEFAULT_CHARSET, false);
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(string familyName, float emSize, FontStyle style, GraphicsUnit unit)
    {
        Initialize(familyName, emSize, style, unit, (byte)FONT_CHARSET.DEFAULT_CHARSET, IsVerticalName(familyName));
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(string familyName, float emSize, FontStyle style)
    {
        Initialize(familyName, emSize, style, GraphicsUnit.Point, (byte)FONT_CHARSET.DEFAULT_CHARSET, IsVerticalName(familyName));
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(string familyName, float emSize, GraphicsUnit unit)
    {
        Initialize(familyName, emSize, FontStyle.Regular, unit, (byte)FONT_CHARSET.DEFAULT_CHARSET, IsVerticalName(familyName));
    }

    /// <summary>
    ///  Initializes a new instance of the <see cref='Font'/> class with the specified attributes.
    /// </summary>
    public Font(string familyName, float emSize)
    {
        Initialize(familyName, emSize, FontStyle.Regular, GraphicsUnit.Point, (byte)FONT_CHARSET.DEFAULT_CHARSET, IsVerticalName(familyName));
    }

    /// <summary>
    ///  Constructor to initialize fields from an existing native GDI+ object reference. Used by ToLogFont.
    /// </summary>
    private Font(GpFont* nativeFont, byte gdiCharSet, bool gdiVerticalFont)
    {
        Debug.Assert(_nativeFont is null, "GDI+ native font already initialized, this will generate a handle leak");
        Debug.Assert(nativeFont is not null, "nativeFont is null");

        _nativeFont = nativeFont;
        GraphicsUnit unit;
        float size;
        FontStyle style;
        GpFontFamily* family;
        PInvokeGdiPlus.GdipGetFontUnit(_nativeFont, (Unit*)&unit).ThrowIfFailed();
        PInvokeGdiPlus.GdipGetFontSize(_nativeFont, &size).ThrowIfFailed();
        PInvokeGdiPlus.GdipGetFontStyle(_nativeFont, (int*)&style).ThrowIfFailed();
        PInvokeGdiPlus.GdipGetFamily(_nativeFont, &family).ThrowIfFailed();

        // Fonts from native HFONTs are always from the installed font collection.
        SetFontFamily(new FontFamily(family, fromInstalledFontCollection: true));
        Initialize(_fontFamily, size, style, unit, gdiCharSet, gdiVerticalFont);
    }

    private void Initialize(string familyName, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet, bool gdiVerticalFont)
    {
        _originalFontName = familyName;

        ReadOnlySpan<char> name = familyName;

        // Strip the vertical tag ('@') if present.
        if (name.Length > 1 && name[0] == '@')
        {
            name = name[1..];
        }

        SetFontFamily(new FontFamily(name, createDefaultOnFail: true));
        Initialize(_fontFamily, emSize, style, unit, gdiCharSet, gdiVerticalFont);
    }

    private void Initialize(FontFamily family, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet, bool gdiVerticalFont)
    {
        ArgumentNullException.ThrowIfNull(family);

        if (float.IsNaN(emSize) || float.IsInfinity(emSize) || emSize <= 0)
        {
            throw new ArgumentException(SR.Format(SR.InvalidBoundArgument, nameof(emSize), emSize, 0, "System.Single.MaxValue"), nameof(emSize));
        }

        Status status;

        _fontSize = emSize;
        _fontStyle = style;
        _fontUnit = unit;
        _gdiCharSet = gdiCharSet;
        _gdiVerticalFont = gdiVerticalFont;

        if (_fontFamily is null)
        {
            SetFontFamily(family.Clone());
        }

        if (_nativeFont is null)
        {
            CreateNativeFont();
        }

        // Get actual size.
        float size;
        status = PInvokeGdiPlus.GdipGetFontSize(_nativeFont, &size);
        _fontSize = size;
        GC.KeepAlive(this);
        Gdip.CheckStatus(status);
    }

    /// <summary>
    ///  Creates a <see cref='Font'/> from the specified Windows handle.
    /// </summary>
    public static Font FromHfont(IntPtr hfont)
    {
        PInvokeCore.GetObject((HGDIOBJ)hfont, out LOGFONT logFont);

        using var hdc = GetDcScope.ScreenDC;
        return FromLogFont(in logFont, hdc);
    }

    /// <summary>
    ///  Creates a <see cref="Font"/> from the given LOGFONT using the screen device context.
    /// </summary>
    /// <param name="lf">A boxed LOGFONT.</param>
    /// <returns>The newly created <see cref="Font"/>.</returns>
    public static Font FromLogFont(object lf)
    {
        using var hdc = GetDcScope.ScreenDC;
        return FromLogFont(lf, hdc);
    }

#if NET8_0_OR_GREATER
    public
#else
    internal
#endif
    static Font FromLogFont(in LOGFONT logFont)
    {
        using var hdc = GetDcScope.ScreenDC;
        return FromLogFont(logFont, hdc);
    }

#if NET8_0_OR_GREATER
    public
#else
    internal
#endif
    static Font FromLogFont(in LOGFONT logFont, IntPtr hdc)
    {
        Status status;
        GpFont* font;
        fixed (LOGFONT* lf = &logFont)
        {
            status = PInvokeGdiPlus.GdipCreateFontFromLogfont((HDC)hdc, (LOGFONTW*)lf, &font);
        }

        // Special case this incredibly common error message to give more information
        if (status == Status.NotTrueTypeFont)
        {
            throw new ArgumentException(SR.GdiplusNotTrueTypeFont_NoName);
        }
        else if (status != Status.Ok)
        {
            throw Gdip.StatusException(status);
        }

        // GDI+ returns font = 0 even though the status is Ok.
        if (font is null)
        {
            throw new ArgumentException(SR.Format(SR.GdiplusNotTrueTypeFont, logFont.AsString()));
        }

        return new Font(font, logFont.lfCharSet, logFont.IsGdiVerticalFont);
    }

    /// <summary>
    ///  Creates a <see cref="Font"/> from the given LOGFONT using the given device context.
    /// </summary>
    /// <param name="lf">A boxed LOGFONT.</param>
    /// <param name="hdc">Handle to a device context (HDC).</param>
    /// <returns>The newly created <see cref="Font"/>.</returns>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static Font FromLogFont(object lf, IntPtr hdc)
    {
        ArgumentNullException.ThrowIfNull(lf);

        if (lf is LOGFONT logFont)
        {
            // A boxed LOGFONT, just use it to create the font
            return FromLogFont(in logFont, hdc);
        }

        Type type = lf.GetType();
        int nativeSize = sizeof(LOGFONT);

        // Marshal is necessary here. FromLogFont(LogFont, IntPtr) is the marshal free version.
#pragma warning disable CA1421 // This method uses runtime marshalling even when the 'DisableRuntimeMarshallingAttribute' is applied
        if (Marshal.SizeOf(type) != nativeSize)
        {
            // If we don't actually have an object that is LOGFONT in size, trying to pass
            // it to GDI+ is likely to cause an AV.
            throw new ArgumentException(null, nameof(lf));
        }

        // Now that we know the marshalled size is the same as LOGFONT, copy in the data
        logFont = default;

        Marshal.StructureToPtr(lf, new IntPtr(&logFont), fDeleteOld: false);
#pragma warning restore CA1421

        return FromLogFont(in logFont, hdc);
    }

    /// <summary>
    ///  Creates a <see cref="Font"/> from the specified handle to a device context (HDC).
    /// </summary>
    /// <returns>The newly created <see cref="Font"/>.</returns>
    public static Font FromHdc(IntPtr hdc)
    {
        GpFont* font;
        Status status = PInvokeGdiPlus.GdipCreateFontFromDC((HDC)hdc, &font);

        // Special case this incredibly common error message to give more information
        if (status == Status.NotTrueTypeFont)
        {
            throw new ArgumentException(SR.GdiplusNotTrueTypeFont_NoName);
        }
        else if (status != Status.Ok)
        {
            throw Gdip.StatusException(status);
        }

        return new Font(font, 0, gdiVerticalFont: false);
    }

    /// <summary>
    ///  Creates an exact copy of this <see cref='Font'/>.
    /// </summary>
    public object Clone()
    {
        GpFont* font;
        PInvokeGdiPlus.GdipCloneFont(_nativeFont, &font).ThrowIfFailed();
        GC.KeepAlive(this);
        return new Font(font, _gdiCharSet, _gdiVerticalFont);
    }

    private void SetFontFamily(FontFamily family)
    {
        _fontFamily = family;

        // GDI+ creates ref-counted singleton FontFamily objects based on the family name so all managed
        // objects with same family name share the underlying GDI+ native pointer. The unmanaged object is
        // destroyed when its ref-count gets to zero.
        //
        // Make sure _fontFamily is not finalized so the underlying singleton object is kept alive.
        GC.SuppressFinalize(_fontFamily);
    }

    public void ToLogFont(object logFont)
    {
        using var hdc = GetDcScope.ScreenDC;
        using Graphics graphics = Graphics.FromHdcInternal(hdc);
        ToLogFont(logFont, graphics);
    }

#if NET8_0_OR_GREATER
    public void ToLogFont(out LOGFONT logFont)
    {
        using var hdc = GetDcScope.ScreenDC;
        using Graphics graphics = Graphics.FromHdcInternal(hdc);
        ToLogFont(out logFont, graphics);
    }
#endif

    /// <summary>
    ///  Returns a handle to this <see cref='Font'/>.
    /// </summary>
    public IntPtr ToHfont()
    {
        using var hdc = GetDcScope.ScreenDC;
        using Graphics graphics = Graphics.FromHdcInternal(hdc);
        ToLogFont(out LOGFONT lf, graphics);
        HFONT handle = PInvokeCore.CreateFontIndirect((LOGFONTW*)&lf);
        return handle.IsNull ? throw new Win32Exception() : handle;
    }

    public float GetHeight()
    {
        using var hdc = GetDcScope.ScreenDC;
        using Graphics graphics = Graphics.FromHdcInternal(hdc);
        return GetHeight(graphics);
    }

    /// <summary>
    ///  Gets the size, in points, of this <see cref='Font'/>.
    /// </summary>
    [Browsable(false)]
    public float SizeInPoints
    {
        get
        {
            if (Unit == GraphicsUnit.Point)
            {
                return Size;
            }

            using var hdc = GetDcScope.ScreenDC;
            using Graphics graphics = Graphics.FromHdcInternal(hdc);

            float pixelsPerPoint = (float)(graphics.DpiY / 72.0);
            float lineSpacingInPixels = GetHeight(graphics);
            float emHeightInPixels = lineSpacingInPixels * FontFamily.GetEmHeight(Style) / FontFamily.GetLineSpacing(Style);

            return emHeightInPixels / pixelsPerPoint;
        }
    }
}
