public Data(Font font, Gdi32.QUALITY quality) { Font = new WeakReference <Font>(font); Quality = quality; HFONT = FromFont(font, quality); _tmHeight = null; }
internal static void DrawTextInternal( Gdi32.HDC hdc, string?text, Font?font, Rectangle bounds, Color foreColor, Gdi32.QUALITY fontQuality, TextFormatFlags flags) => DrawTextInternal(hdc, text, font, bounds, foreColor, fontQuality, Color.Empty, flags);
/// <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 Gdi32.HFONT FromFont(Font font, Gdi32.QUALITY quality = Gdi32.QUALITY.DEFAULT) { 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); } // 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(DpiHelper.DeviceDpi * 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. User32.LOGFONTW logFont = new User32.LOGFONTW { lfHeight = -pixelsY, lfCharSet = font.GdiCharSet, lfOutPrecision = Gdi32.OUT_PRECIS.TT, lfQuality = quality, lfWeight = (font.Style & FontStyle.Bold) == FontStyle.Bold ? Gdi32.FW.BOLD : Gdi32.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 }; if (logFont.FaceName.IsEmpty) { logFont.FaceName = DefaultFaceName; } Gdi32.HFONT hfont = Gdi32.CreateFontIndirectW(ref logFont); if (hfont.IsNull) { // Get the default font if we couldn't get what we requested. logFont.FaceName = DefaultFaceName; logFont.lfOutPrecision = Gdi32.OUT_PRECIS.TT_ONLY; hfont = Gdi32.CreateFontIndirectW(ref logFont); Debug.Assert(!hfont.IsNull); } return(hfont); }
public static FontCache.Scope GetHFONT(Font?font, Gdi32.QUALITY quality, Gdi32.HDC hdc) { if (font != null) { return(GetHFONT(font, quality)); } // Font is null, build off of the specified HDC's current font. Gdi32.HFONT hfont = (Gdi32.HFONT)Gdi32.GetCurrentObject(hdc, Gdi32.OBJ.FONT); return(new FontCache.Scope(hfont)); }
public static void DrawText(IDeviceContext dc, string?text, Font?font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics); using var wgr = new WindowsGraphicsWrapper(dc, flags); using WindowsFont? wf = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality); wgr.WindowsGraphics.DrawText(text, wf, pt, foreColor, backColor, GetTextFormatFlags(flags)); }
private static void DrawTextInternal( Gdi32.HDC hdc, ReadOnlySpan <char> text, Font?font, Rectangle bounds, Color foreColor, Gdi32.QUALITY fontQuality, Color backColor, TextFormatFlags flags) { using var hfont = GdiCache.GetHFONT(font, fontQuality, hdc); hdc.DrawText(text, hfont, bounds, foreColor, flags, backColor); }
internal static void DrawTextInternal( Gdi32.HDC hdc, string?text, Font?font, Rectangle bounds, Color foreColor, Gdi32.QUALITY fontQuality, Color backColor, User32.DT flags) { using var hfont = GdiCache.GetHFONT(font, fontQuality); hdc.DrawText(text, hfont, bounds, foreColor, flags, backColor); }
public static void DrawText(IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics); using (WindowsGraphicsWrapper wgr = new WindowsGraphicsWrapper(dc, flags)) { using (WindowsFont wf = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality)) { wgr.WindowsGraphics.DrawText(text, wf, bounds, foreColor, GetTextFormatFlags(flags)); } } }
public static Size MeasureText(IDeviceContext dc, string?text, Font?font, Size proposedSize, TextFormatFlags flags) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } if (string.IsNullOrEmpty(text)) { return(Size.Empty); } Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics); using var wgr = new WindowsGraphicsWrapper(dc, flags); using var wf = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality); return(wgr.WindowsGraphics.MeasureText(text, wf, proposedSize, GetTextFormatFlags(flags))); }
/// <summary> /// 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). /// </summary> public static WindowsFont FromFont(Font font, Gdi32.QUALITY fontQuality = Gdi32.QUALITY.DEFAULT) { 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. // 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. 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(WindowsGraphicsCacheManager.MeasurementGraphics.DeviceContext.DpiY * 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 (MSDN). User32.LOGFONTW logFont = new User32.LOGFONTW() { lfHeight = -pixelsY, lfCharSet = font.GdiCharSet, lfOutPrecision = Gdi32.OUT_PRECIS.TT, lfQuality = fontQuality, lfWeight = (font.Style & FontStyle.Bold) == FontStyle.Bold ? Gdi32.FW.BOLD : Gdi32.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 }; return(new WindowsFont(logFont, font.Style, createHandle: true)); }
private static Size MeasureTextInternal( IDeviceContext dc, ReadOnlySpan<char> text, Font? font, Size proposedSize, TextFormatFlags flags = TextFormatFlags.Bottom) { if (dc is null) throw new ArgumentNullException(nameof(dc)); if (text.IsEmpty) return Size.Empty; // This MUST come before retreiving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc); using var hdc = new DeviceContextHdcScope(dc); using var hfont = GdiCache.GetHFONT(font, quality, hdc); return hdc.MeasureText(text, hfont, proposedSize, GetTextFormatFlags(flags)); }
public static void DrawText(IDeviceContext dc, string?text, Font?font, Rectangle bounds, Color foreColor) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics); IntPtr hdc = dc.GetHdc(); try { using WindowsGraphics wg = WindowsGraphics.FromHdc(hdc); using WindowsFont? wf = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality); wg.DrawText(text, wf, bounds, foreColor); } finally { dc.ReleaseHdc(); } }
private static Size MeasureTextInternal( IDeviceContext dc, ReadOnlySpan <char> text, Font?font, Size proposedSize, TextFormatFlags flags = TextFormatFlags.Bottom) { ArgumentNullException.ThrowIfNull(dc); if (text.IsEmpty) { return(Size.Empty); } // This MUST come before retrieving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc); // Applying state may not impact text size measurements. Rather than risk missing some // case we'll apply as we have historically to avoid surprise regressions. using var hdc = new DeviceContextHdcScope(dc, GetApplyStateFlags(dc, flags)); using var hfont = GdiCache.GetHFONT(font, quality, hdc); return(hdc.HDC.MeasureText(text, hfont, proposedSize, flags)); }
internal static void DrawTextInternal( IDeviceContext dc, ReadOnlySpan<char> text, Font? font, Rectangle bounds, Color foreColor, Color backColor, User32.DT flags = User32.DT.CENTER | User32.DT.VCENTER) { if (dc is null) throw new ArgumentNullException(nameof(dc)); // Avoid creating the HDC, etc if we're not going to do any drawing if (text.IsEmpty || foreColor == Color.Transparent) return; // This MUST come before retreiving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc); using var hdc = new DeviceContextHdcScope(dc); DrawTextInternal(hdc, text, font, bounds, foreColor, quality, backColor, flags); }
internal static void DrawTextInternal( PaintEventArgs e, string?text, Font?font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter) { Gdi32.HDC hdc = e.HDC; if (hdc.IsNull) { // This MUST come before retreiving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(e.GraphicsInternal); using var graphicsHdc = new DeviceContextHdcScope(e.GraphicsInternal, applyGraphicsState: false); DrawTextInternal(graphicsHdc, text, font, bounds, foreColor, quality, backColor, flags); } else { DrawTextInternal(hdc, text, font, bounds, foreColor, DefaultQuality, backColor, flags); } }
internal static void DrawTextInternal( IDeviceContext dc, ReadOnlySpan <char> text, Font?font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter) { ArgumentNullException.ThrowIfNull(dc); // Avoid creating the HDC, etc if we're not going to do any drawing if (text.IsEmpty || foreColor == Color.Transparent) { return; } // This MUST come before retrieving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc); using var hdc = new DeviceContextHdcScope(dc, GetApplyStateFlags(dc, flags)); DrawTextInternal(hdc, text, font, bounds, foreColor, quality, backColor, flags); }
private static Size MeasureTextInternal( IDeviceContext dc, string?text, Font?font, Size proposedSize, TextFormatFlags flags = TextFormatFlags.Bottom) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } if (string.IsNullOrEmpty(text)) { return(Size.Empty); } // This MUST come before retreiving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc); using var hdc = new DeviceContextHdcScope(dc); using var hfont = GdiCache.GetHFONT(font, quality); return(hdc.MeasureText(text, hfont, proposedSize, GetTextFormatFlags(flags))); }
private static Size MeasureTextInternal( IDeviceContext dc, string?text, Font?font, Size proposedSize, TextFormatFlags flags = TextFormatFlags.Bottom) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } if (string.IsNullOrEmpty(text)) { return(Size.Empty); } // This MUST come before retreiving the HDC, which locks the Graphics object Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc); using var wgr = new WindowsGraphicsWrapper(dc, flags); using var wf = WindowsGraphicsCacheManager.GetWindowsFont(font, quality); return(wgr.WindowsGraphics.MeasureText(text, wf, proposedSize, GetTextFormatFlags(flags))); }
/// <summary> /// Gets a ref-counting scope containing the <see cref="Gdi32.HFONT"/> that matches the specified /// <paramref name="font"/> and <paramref name="quality"/>. The scope MUST be disposed to release the ref /// count accurately. Use the result in a using statement to avoid leaking fonts. /// </summary> public FontScope GetHFONT(Font font, Gdi32.QUALITY quality) { if (font is null) { throw new ArgumentNullException(nameof(font)); } if (_clean) { _lock.EnterWriteLock(); try { Clean(); } finally { _lock.ExitWriteLock(); } } _lock.EnterReadLock(); try { if (Find(font, quality, out FontScope scope)) { return(scope); } } finally { _lock.ExitReadLock(); } _lock.EnterWriteLock(); try { return(Add(font, quality)); } finally { _lock.ExitWriteLock(); } bool Find(Font font, Gdi32.QUALITY quality, out FontScope scope) { scope = default; LinkedListNode <Data>?node = _list.First; while (node != null) { Data data = node.Value; if (!data.Font.TryGetTarget(out Font? currentFont)) { // Found a dead node, mark ourselves for cleaning _clean = true; } else if (font == currentFont && quality == data.Quality) { // Current node is still alive scope = new FontScope(data); return(true); } node = node.Next; } return(false); } FontScope Add(Font font, Gdi32.QUALITY quality) { // Need to try and find the node again as it may have been created on another thread. if (Find(font, quality, out FontScope scope)) { return(scope); } Data node = new Data(font, quality); _list.AddFirst(node); _count++; if (_count > _softLimit) { _clean = true; } return(new FontScope(node)); } void Clean() { if (!_clean) { return; } _clean = false; // Get rid of all collected Font nodes where the HFONT is unused LinkedListNode <Data>?node = _list.First; while (node != null) { Data data = node.Value; LinkedListNode <Data>?nextNode = node.Next; if (!data.Font.TryGetTarget(out Font? _)) { if (data.RefCount == 0) { // Font was garbage collected, and the HFONT isn't currently used Gdi32.DeleteObject(data.HFONT); _list.Remove(node); _count--; } else { // Mark ourselves for clean again so we can get this later _clean = true; } } node = nextNode; } // If the count is over hard limit, expunge excess entries from the end of the list // where the ref count is 0. int overage = _count - _hardLimit; if (overage <= 0) { return; } node = _list.Last; while (node != null && overage > 0) { Data data = node.Value; LinkedListNode <Data>?priorNode = node.Previous; if (data.RefCount == 0) { // HFONT isn't currently in use, remove the node. Gdi32.DeleteObject(data.HFONT); _list.Remove(node); _count--; overage--; } node = priorNode; } _clean |= _count > _softLimit; } }
/// <summary> /// Get the cached WindowsFont associated with the specified font if one exists, otherwise create one and /// add it to the cache. /// </summary> public static WindowsFont?GetWindowsFont(Font?font, Gdi32.QUALITY fontQuality = Gdi32.QUALITY.DEFAULT) { if (font == null) { return(null); } // First check if font is in the cache. int count = 0; int index = t_currentIndex; // Search by index of most recently added object. while (count < WindowsFontCache.Count) { if (WindowsFontCache[index].Key.Equals(font)) // don't do shallow comparison, we could miss cloned fonts. { // We got a Font in the cache, let's see if we have a WindowsFont with the same quality as required by the caller. // WARNING: It is not expected that the WindowsFont is disposed externally since it is created by this class. Debug.Assert(WindowsFontCache[index].Value.Hfont != IntPtr.Zero, "Cached WindowsFont was disposed, enable GDI_FINALIZATION_WATCH to track who did it!"); WindowsFont wf = WindowsFontCache[index].Value; if (wf.Quality == fontQuality) { return(wf); } } index--; count++; if (index < 0) { index = CacheSize - 1; } } // Font is not in the cache, let's add it. WindowsFont winFont = WindowsFont.FromFont(font, fontQuality); KeyValuePair <Font, WindowsFont> newEntry = new KeyValuePair <Font, WindowsFont>(font, winFont); t_currentIndex++; if (t_currentIndex == CacheSize) { t_currentIndex = 0; } if (WindowsFontCache.Count == CacheSize) // No more room, update current index. { WindowsFont?wfont = null; // Go through the existing fonts in the cache, and see if any // are not in use by a DC. If one isn't, replace that. If // all are in use, new up a new font and do not cache it. bool finished = false; int startIndex = t_currentIndex; int loopIndex = startIndex + 1; while (!finished) { if (loopIndex >= CacheSize) { loopIndex = 0; } if (loopIndex == startIndex) { finished = true; } wfont = WindowsFontCache[loopIndex].Value; if (!DeviceContexts.IsFontInUse(wfont)) { t_currentIndex = loopIndex; finished = true; break; } else { loopIndex++; wfont = null; } } if (wfont != null) { WindowsFontCache[t_currentIndex] = newEntry; winFont.OwnedByCacheManager = true; wfont.OwnedByCacheManager = false; wfont.Dispose(); } else { // do not cache font - caller is ALWAYS responsible for // disposing now. If it is owned by the CM, it will not // disposed. winFont.OwnedByCacheManager = false; } } else { winFont.OwnedByCacheManager = true; WindowsFontCache.Add(newEntry); } return(winFont); }
/// <summary> /// Gets a cached <see cref="Gdi32.HFONT"/> based off of the given <paramref name="font"/> and /// <paramref name="quality"/>. /// </summary> /// <remarks> /// Use in a using statement for proper cleanup. /// /// When disposed the <see cref="Gdi32.HFONT"/> will be returned to the cache. If you must pass the scope to /// another method it must be passed by reference or you risk double disposal and accidentally returning extra /// copies to the cache. /// </remarks> public static FontCache.FontScope GetHFONT(Font?font, Gdi32.QUALITY quality = Gdi32.QUALITY.DEFAULT) => font is null ? default : s_fontCache.GetHFONT(font, quality);
/// <summary> /// Gets a cached <see cref="Gdi32.HFONT"/> based off of the given <paramref name="font"/> and /// <paramref name="quality"/>. /// </summary> /// <remarks> /// Use in a using statement for proper cleanup. /// /// When disposed the <see cref="Gdi32.HFONT"/> will be returned to the cache. If you must pass the scope to /// another method it must be passed by reference or you risk double disposal and accidentally returning extra /// copies to the cache. /// </remarks> public static FontCache.Scope GetHFONT(Font?font, Gdi32.QUALITY quality = Gdi32.QUALITY.DEFAULT) { Debug.Assert(font != null); return(font is null ? new FontCache.Scope() : s_fontCache.GetEntry(font, quality)); }