|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Drawing;
namespace System.Windows.Forms;
internal sealed partial class FontCache
{
internal struct Data : IDisposable
{
// 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 byte True = 1;
private const byte False = 0;
public WeakReference<Font> Font { get; }
public HFONT HFONT { get; private set; }
public FONT_QUALITY Quality { get; }
private int? _tmHeight;
public Data(Font font, FONT_QUALITY quality)
{
Font = new WeakReference<Font>(font);
Quality = quality;
HFONT = FromFont(font, quality);
_tmHeight = null;
}
public unsafe int Height
{
get
{
if (!_tmHeight.HasValue)
{
using var screenDC = GdiCache.GetScreenHdc();
HDC hdc = screenDC.HDC;
using SelectObjectScope fontSelection = new(hdc, HFONT);
Debug.Assert(PInvoke.GetMapMode(hdc) == HDC_MAP_MODE.MM_TEXT);
TEXTMETRICW tm = default;
PInvoke.GetTextMetrics(hdc, &tm);
_tmHeight = tm.tmHeight;
}
return _tmHeight.Value;
}
}
public void Dispose()
{
if (!HFONT.IsNull)
{
PInvokeCore.DeleteObject(HFONT);
}
HFONT = default;
}
/// <summary>
/// Constructs 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).
/// </summary>
private static unsafe HFONT FromFont(Font font, FONT_QUALITY quality = FONT_QUALITY.DEFAULT_QUALITY)
{
string familyName = font.FontFamily.Name;
// Strip vertical-font mark from the name if needed.
if (familyName is not null && familyName.Length > 1 && familyName[0] == '@')
{
familyName = familyName[1..];
}
// Now, creating it using the Font.SizeInPoints makes it GraphicsUnit-independent.
Debug.Assert(font.SizeInPoints > 0.0f, "size has a negative value.");
// Get the font height from the specified size. The 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. (1 point = 1/72 inch.)
int pixelsY = (int)Math.Ceiling(ScaleHelper.InitialSystemDpi * font.SizeInPoints / 72);
// 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.
LOGFONTW logFont = new()
{
lfHeight = -pixelsY,
lfCharSet = (FONT_CHARSET)font.GdiCharSet,
lfQuality = quality,
lfWeight = (int)((font.Style & FontStyle.Bold) == FontStyle.Bold ? FW.BOLD : FW.NORMAL),
lfItalic = (font.Style & FontStyle.Italic) == FontStyle.Italic ? True : False,
lfUnderline = (font.Style & FontStyle.Underline) == FontStyle.Underline ? True : False,
lfStrikeOut = (font.Style & FontStyle.Strikeout) == FontStyle.Strikeout ? True : False,
FaceName = familyName,
// This is the only difference in what `Font.ToHfont()` ultimately does. GDI+ uses OUT_DEFAULT_PRECIS
// which appears to be the same thing as OUT_TT_PRECIS. GDI+ also applies the transforms from the
// screen device context
lfOutPrecision = FONT_OUTPUT_PRECISION.OUT_TT_PRECIS,
};
if (logFont.FaceName.IsEmpty)
{
logFont.FaceName = DefaultFaceName;
}
HFONT hfont = PInvokeCore.CreateFontIndirect(&logFont);
if (hfont.IsNull)
{
// Get the default font if we couldn't get what we requested.
logFont.FaceName = DefaultFaceName;
logFont.lfOutPrecision = FONT_OUTPUT_PRECISION.OUT_TT_ONLY_PRECIS;
hfont = PInvokeCore.CreateFontIndirect(&logFont);
Debug.Assert(!hfont.IsNull);
}
return hfont;
}
}
}
|