/// <summary> /// Returns the Size of the given text using the specified font if not null, otherwise the font currently /// set in the dc is used. /// This method is used to get the size in points of a line of text; it uses GetTextExtentPoint32 function /// which computes the width and height of the text ignoring TAB\CR\LF characters. /// A text extent is the distance between the beginning of the space and a character that will fit in the space. /// </summary> public Size GetTextExtent(string?text, WindowsFont?font) { if (string.IsNullOrEmpty(text)) { return(Size.Empty); } Size size = new Size(); if (font != null) { DeviceContext.SelectFont(font); } Gdi32.GetTextExtentPoint32W(DeviceContext.Hdc, text, text.Length, ref size); // Unselect, but not from Measurement DC as it keeps the same // font selected for perf reasons. if (font != null && !MeasurementDCInfo.IsMeasurementDC(DeviceContext)) { DeviceContext.ResetFont(); } return(new Size(size.Width, size.Height)); }
public static Size MeasureText(IDeviceContext dc, string?text, Font?font, Size proposedSize) { if (dc == null) { throw new ArgumentNullException(nameof(dc)); } if (string.IsNullOrEmpty(text)) { return(Size.Empty); } 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); return(wg.MeasureText(text, wf, proposedSize)); } finally { dc.ReleaseHdc(); } }
/// <summary> /// Restores the device context to the specified state. The DC is restored by popping state information off a /// stack created by earlier calls to the SaveHdc function. /// The stack can contain the state information for several instances of the DC. If the state specified by the /// specified parameter is not at the top of the stack, RestoreDC deletes all state information between the top /// of the stack and the specified instance. /// Specifies the saved state to be restored. If this parameter is positive, nSavedDC represents a specific /// instance of the state to be restored. If this parameter is negative, nSavedDC represents an instance relative /// to the current state. For example, -1 restores the most recently saved state. /// See MSDN for more info. /// </summary> public void RestoreHdc() { // Note: Don't use the Hdc property here, it would force handle creation. Gdi32.RestoreDC(new HandleRef(this, _hDC), -1); Debug.Assert(_contextStack != null, "Someone is calling RestoreHdc() before SaveHdc()"); if (_contextStack != null) { GraphicsState g = (GraphicsState)_contextStack.Pop(); _hCurrentBmp = g.hBitmap; _hCurrentBrush = g.hBrush; _hCurrentPen = g.hPen; _hCurrentFont = g.hFont; if (g.font != null && g.font.IsAlive) { ActiveFont = g.font.Target as WindowsFont; } else { WindowsFont?previousFont = ActiveFont; ActiveFont = null; if (previousFont != null && MeasurementDCInfo.IsMeasurementDC(this)) { previousFont.Dispose(); } } } // in this case, GDI will copy back the previously saved font into the DC. // we dont actually know what the font is in our measurement DC so // we need to clear it off. MeasurementDCInfo.ResetIfIsMeasurementDC(_hDC); }
private static void DrawTextInternal( IDeviceContext dc, string?text, Font?font, Rectangle bounds, Color foreColor, Color backColor = default, 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 (string.IsNullOrEmpty(text) || 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, applyGraphicsState: false); using WindowsGraphics wg = WindowsGraphics.FromHdc(hdc); using WindowsFont? wf = WindowsGraphicsCacheManager.GetWindowsFont(font, quality); wg.DrawText(text, wf, bounds, foreColor, backColor, flags); }
internal void UpdateFont(WindowsFont?font) { if (LastUsedFont != font) { LastUsedFont = font; LeftTextMargin = -1; RightTextMargin = -1; } }
public static Size MeasureText(string?text, Font?font, Size proposedSize, TextFormatFlags flags) { if (string.IsNullOrEmpty(text)) { return(Size.Empty); } using WindowsFont? wf = WindowsGraphicsCacheManager.GetWindowsFont(font); return(WindowsGraphicsCacheManager.MeasurementGraphics.MeasureText(text, wf, proposedSize, GetTextFormatFlags(flags))); }
public static Size MeasureText(string?text, Font?font) { if (string.IsNullOrEmpty(text)) { return(Size.Empty); } using WindowsFont? wf = WindowsGraphicsCacheManager.GetWindowsFont(font); return(WindowsGraphicsCacheManager.MeasurementGraphics.MeasureText(text, wf)); }
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)); }
internal static bool IsFontInUse(WindowsFont?wf) { if (wf == null || t_activeDeviceContexts == null) { return(false); } for (int i = 0; i < t_activeDeviceContexts.Count; i++) { if (t_activeDeviceContexts[i] is DeviceContext dc && (dc.ActiveFont == wf || dc.IsFontOnContextStack(wf))) { return(true); } } return(false); }
/// <summary> /// Restores the device context to the specified state. The DC is restored by popping state information off a /// stack created by earlier calls to the SaveHdc function. /// The stack can contain the state information for several instances of the DC. If the state specified by the /// specified parameter is not at the top of the stack, RestoreDC deletes all state information between the top /// of the stack and the specified instance. /// Specifies the saved state to be restored. If this parameter is positive, nSavedDC represents a specific /// instance of the state to be restored. If this parameter is negative, nSavedDC represents an instance relative /// to the current state. For example, -1 restores the most recently saved state. /// See MSDN for more info. /// </summary> public void RestoreHdc() { #if TRACK_HDC bool result = #endif // Note: Don't use the Hdc property here, it would force handle creation. Gdi32.RestoreDC(new HandleRef(this, _hDC), -1); #if TRACK_HDC // Note: Winforms may call this method during app exit at which point the DC may have been finalized already causing this assert to popup. Debug.WriteLine(DbgUtil.StackTraceToStr(String.Format("ret[0]=DC.RestoreHdc(hDc=0x{1:x8})", result, unchecked ((int)this.hDC)))); #endif Debug.Assert(_contextStack != null, "Someone is calling RestoreHdc() before SaveHdc()"); if (_contextStack != null) { GraphicsState g = (GraphicsState)_contextStack.Pop(); _hCurrentBmp = g.hBitmap; _hCurrentBrush = g.hBrush; _hCurrentPen = g.hPen; _hCurrentFont = g.hFont; if (g.font != null && g.font.IsAlive) { ActiveFont = g.font.Target as WindowsFont; } else { WindowsFont?previousFont = ActiveFont; ActiveFont = null; if (previousFont != null && MeasurementDCInfo.IsMeasurementDC(this)) { previousFont.Dispose(); } } } #if OPTIMIZED_MEASUREMENTDC // in this case, GDI will copy back the previously saved font into the DC. // we dont actually know what the font is in our measurement DC so // we need to clear it off. MeasurementDCInfo.ResetIfIsMeasurementDC(_hDC); #endif }
/// <summary> /// Calculates the spacing required for drawing text w/o clipping parts of a glyph. /// </summary> public float GetOverhangPadding(WindowsFont?font) { // Some parts of a glyphs may be clipped depending on the font & font style, GDI+ adds 1/6 of tmHeight // to each size of the text bounding box when drawing text to account for that; we do it here as well. WindowsFont?tmpfont = font; if (tmpfont == null) { tmpfont = DeviceContext.Font; } float overhangPadding = tmpfont.Height / 6f; if (tmpfont != font) { tmpfont.Dispose(); } return(overhangPadding); }
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(); } }
/// <summary> /// Get the bounding box internal text padding to be used when drawing text. /// </summary> public User32.DRAWTEXTPARAMS GetTextMargins(WindowsFont?font) { // DrawText(Ex) adds a small space at the beginning of the text bounding box but not at the end, // this is more noticeable when the font has the italic style. We compensate with this factor. int leftMargin = 0; int rightMargin = 0; float overhangPadding; switch (TextPadding) { case TextPaddingOptions.GlyphOverhangPadding: // [overhang padding][Text][overhang padding][italic padding] overhangPadding = GetOverhangPadding(font); leftMargin = (int)Math.Ceiling(overhangPadding); rightMargin = (int)Math.Ceiling(overhangPadding * (1 + ItalicPaddingFactor)); break; case TextPaddingOptions.LeftAndRightPadding: // [2 * overhang padding][Text][2 * overhang padding][italic padding] overhangPadding = GetOverhangPadding(font); leftMargin = (int)Math.Ceiling(2 * overhangPadding); rightMargin = (int)Math.Ceiling(overhangPadding * (2 + ItalicPaddingFactor)); break; case TextPaddingOptions.NoPadding: default: break; } return(new User32.DRAWTEXTPARAMS { iLeftMargin = leftMargin, iRightMargin = rightMargin }); }
/// <summary> /// Returns the Size in logical units of the given text using the given Font. /// CR/LF/TAB are taken into account. /// </summary> public Size MeasureText(string?text, WindowsFont?font) => MeasureText(text, font, MaxSize, User32.DT.BOTTOM);
/// <summary> /// Draws the text in the given bounds, using the given Font, foreColor and backColor, and according to the specified /// TextFormatFlags flags. /// /// If font is null, the font currently selected in the hdc is used. /// /// If foreColor and/or backColor are Color.Empty, the hdc current text and/or background color are used. /// </summary> public void DrawText(string?text, WindowsFont?font, Rectangle bounds, Color foreColor, Color backColor, User32.DT flags) { if (string.IsNullOrEmpty(text) || foreColor == Color.Transparent) { return; } Debug.Assert(((uint)flags & GdiUnsupportedFlagMask) == 0, "Some custom flags were left over and are not GDI compliant!"); // DrawText requires default text alignment. if (DeviceContext.TextAlignment != default) { DeviceContext.TextAlignment = default; } // color empty means use the one currently selected in the dc. if (!foreColor.IsEmpty && foreColor != DeviceContext.TextColor) { DeviceContext.TextColor = foreColor; } if (font != null) { DeviceContext.SelectFont(font); } Gdi32.BKMODE newBackGndMode = (backColor.IsEmpty || backColor == Color.Transparent) ? Gdi32.BKMODE.TRANSPARENT : Gdi32.BKMODE.OPAQUE; if (DeviceContext.BackgroundMode != newBackGndMode) { DeviceContext.SetBackgroundMode(newBackGndMode); } if (newBackGndMode != Gdi32.BKMODE.TRANSPARENT && backColor != DeviceContext.BackgroundColor) { DeviceContext.BackgroundColor = backColor; } User32.DRAWTEXTPARAMS dtparams = GetTextMargins(font); bounds = AdjustForVerticalAlignment(DeviceContext, text, bounds, flags, ref dtparams); // Adjust unbounded rect to avoid overflow since Rectangle ctr does not do param validation. if (bounds.Width == MaxSize.Width) { bounds.Width -= bounds.X; } if (bounds.Height == MaxSize.Height) { bounds.Height -= bounds.Y; } var rect = new RECT(bounds); User32.DrawTextExW(DeviceContext, text, text.Length, ref rect, flags, ref dtparams); // No need to restore previous objects into the dc (see comments on top of the class). }
/// <summary> /// Draws the text in the given bounds, using the given Font and foreColor, and according to the specified flags. /// </summary> public void DrawText(string?text, WindowsFont?font, Rectangle bounds, Color color, User32.DT flags) => DrawText(text, font, bounds, color, Color.Empty, flags);
/// <summary> /// Returns the Size in logical units of the given text using the given Font, and according to the formatting flags. /// The proposed size is used to create a bounding rectangle as follows: /// - If there are multiple lines of text, DrawText uses the width of the rectangle pointed to by /// the lpRect parameter and extends the base of the rectangle to bound the last line of text. /// - If the largest word is wider than the rectangle, the width is expanded. /// - If the text is less than the width of the rectangle, the width is reduced. /// - If there is only one line of text, DrawText modifies the right side of the rectangle so that /// it bounds the last character in the line. /// If the font is null, the hdc's current font will be used. /// /// Note for vertical fonts (if ever supported): DrawTextEx uses GetTextExtentPoint32 for measuring the text and this /// function has the following limitation (from MSDN): /// - This function assumes that the text is horizontal, that is, that the escapement is always 0. This is true for both /// the horizontal and vertical measurements of the text. The application must convert it explicitly. /// </summary> public Size MeasureText(string?text, WindowsFont?font, Size proposedSize, User32.DT flags) { Debug.Assert(((uint)flags & GdiUnsupportedFlagMask) == 0, "Some custom flags were left over and are not GDI compliant!"); if (string.IsNullOrEmpty(text)) { return(Size.Empty); } // DrawText returns a rectangle useful for aligning, but not guaranteed to encompass all // pixels (its not a FitBlackBox, if the text is italicized, it will overhang on the right.) // So we need to account for this. User32.DRAWTEXTPARAMS dtparams; // use the cache if we've got it if (MeasurementDCInfo.IsMeasurementDC(DeviceContext)) { dtparams = MeasurementDCInfo.GetTextMargins(this, font); } else { dtparams = GetTextMargins(font); } // If Width / Height are < 0, we need to make them larger or DrawText will return // an unbounded measurement when we actually trying to make it very narrow. int minWidth = 1 + dtparams.iLeftMargin + dtparams.iRightMargin; if (proposedSize.Width <= minWidth) { proposedSize.Width = minWidth; } if (proposedSize.Height <= 0) { proposedSize.Height = 1; } var rect = new RECT(0, 0, proposedSize.Width, proposedSize.Height); if (font != null) { DeviceContext.SelectFont(font); } // If proposedSize.Height >= MaxSize.Height it is assumed bounds needed. If flags contain SINGLELINE and // VCENTER or BOTTOM options, DrawTextEx does not bind the rectangle to the actual text height since // it assumes the text is to be vertically aligned; we need to clear the VCENTER and BOTTOM flags to // get the actual text bounds. if (proposedSize.Height >= MaxSize.Height && (flags & User32.DT.SINGLELINE) != 0) { // Clear vertical-alignment flags. flags &= ~(User32.DT.BOTTOM | User32.DT.VCENTER); } if (proposedSize.Width == MaxSize.Width) { // PERF: No constraining width means no word break. // in this case, we dont care about word wrapping - there should be enough room to fit it all flags &= ~(User32.DT.WORDBREAK); } flags |= User32.DT.CALCRECT; User32.DrawTextExW(DeviceContext.Hdc, text, text.Length, ref rect, flags, ref dtparams); return(rect.Size); }
/// <summary> /// Draws the text at the specified point, using the given Font, foreColor and backColor. /// CR/LF are honored. /// </summary> public void DrawText(string?text, WindowsFont?font, Point pt, Color foreColor, Color backColor) => DrawText(text, font, pt, foreColor, backColor, User32.DT.DEFAULT);
/// <summary> /// Draws the text at the specified point, using the given Font and foreColor, and according to the /// specified flags. /// </summary> public void DrawText(string?text, WindowsFont?font, Point pt, Color foreColor, User32.DT flags) => DrawText(text, font, pt, foreColor, Color.Empty, flags);
/// <summary> /// Returns the Size in logical units of the given text using the given Font and using the specified rectangle /// as the text bounding box (see overload below for more info). /// TAB/CR/LF are taken into account. /// </summary> public Size MeasureText(string?text, WindowsFont?font, Size proposedSize) => MeasureText(text, font, proposedSize, User32.DT.BOTTOM);
/// <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> /// Draws the text centered in the given rectangle and using the given Font, foreColor and backColor. /// </summary> public void DrawText(string?text, WindowsFont?font, Rectangle bounds, Color foreColor, Color backColor) => DrawText(text, font, bounds, foreColor, backColor, User32.DT.CENTER | User32.DT.VCENTER);
/// <summary> /// Draws the text centered in the given rectangle and using the given Font and foreColor. /// </summary> public void DrawText(string?text, WindowsFont?font, Rectangle bounds, Color foreColor) => DrawText(text, font, bounds, foreColor, Color.Empty);
/// <summary> /// Draws the text at the specified point, using the given Font, foreColor and backColor, and according /// to the specified flags. /// </summary> public void DrawText(string?text, WindowsFont?font, Point pt, Color foreColor, Color backColor, User32.DT flags) => DrawText(text, font, new Rectangle(pt, MaxSize), foreColor, backColor, flags);