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); }
public static Size MeasureText( this DeviceContextHdcScope hdc, string?text, FontCache.FontScope font, Size proposedSize, User32.DT flags) => MeasureText(hdc.HDC, text, font, proposedSize, flags);
public static Size MeasureText( this DeviceContextHdcScope hdc, ReadOnlySpan <char> text, FontCache.Scope font, Size proposedSize, User32.DT flags) => MeasureText(hdc.HDC, text, font, proposedSize, 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 static Size MeasureText( this Gdi32.HDC hdc, ReadOnlySpan <char> text, FontCache.Scope font, Size proposedSize, User32.DT flags) { ValidateFlags(flags); if (text.IsEmpty) { 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 = 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); using var fontSelection = new Gdi32.SelectObjectScope(hdc, font.Object); // 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 >= TextRenderer.MaxSize.Height && (flags & User32.DT.SINGLELINE) != 0) { // Clear vertical-alignment flags. flags &= ~(User32.DT.BOTTOM | User32.DT.VCENTER); } if (proposedSize.Width == TextRenderer.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(hdc, text, ref rect, flags, ref dtparams); return(rect.Size); }
internal static void DrawTextInternal( IDeviceContext dc, ReadOnlySpan <char> 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 (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); }
private static Size MeasureTextInternal( IDeviceContext dc, ReadOnlySpan <char> text, Font?font, Size proposedSize, TextFormatFlags flags = TextFormatFlags.Bottom, bool blockModifyString = false) { if (dc is null) { throw new ArgumentNullException(nameof(dc)); } User32.DT drawTextFlags = GetTextFormatFlags(flags, blockModifyString); 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, drawTextFlags)); }
private static void DrawTextInternal( IDeviceContext dc, ReadOnlySpan<char> text, Font? font, Point pt, Color foreColor, Color backColor, User32.DT flags = User32.DT.DEFAULT) => DrawTextInternal(dc, text, font, new Rectangle(pt, MaxSize), foreColor, backColor, flags);
private static void DrawTextInternal( IDeviceContext dc, string?text, Font?font, Point pt, Color foreColor, Color backColor = default, User32.DT flags = User32.DT.DEFAULT) => DrawTextInternal(dc, text, font, new Rectangle(pt, WindowsGraphics.MaxSize), foreColor, backColor, flags);
public static void DrawText( this DeviceContextHdcScope hdc, string?text, FontCache.Scope font, Rectangle bounds, Color foreColor, User32.DT flags, Color backColor = default, TextPaddingOptions padding = default) => DrawText(hdc.HDC, text, font, bounds, foreColor, flags, backColor, padding);
public static extern HRESULT DrawThemeText( IntPtr hTheme, Gdi32.HDC hdc, int iPartId, int iStateId, string pszText, int iCharCount, User32.DT dwTextFlags, uint dwTextFlags2, ref RECT pRect);
public static void DrawString(Control context, Font font, string text, Rectangle textBounds, bool useMnemonics, GdiTextDrawMode textMode) { IntPtr hdc = User32.GetDC(context.Handle); try { Gdi32.SetBkColor(hdc, Gdi32.ToColorRef(context.BackColor)); Gdi32.SetTextColor(hdc, Gdi32.ToColorRef(context.ForeColor)); try { IntPtr hFont = font.ToHfont(); try { IntPtr hPrevFont = Gdi32.SelectObject(hdc, hFont); try { StringBuilder sb = new StringBuilder(text); RECT rect = textBounds; User32.DRAWTEXTPARAMS dtparams = new User32.DRAWTEXTPARAMS(); dtparams.cbSize = (uint)Marshal.SizeOf(typeof(User32.DRAWTEXTPARAMS)); User32.DT flags = textMode == GdiTextDrawMode.EndEllipsis ? User32.DT.END_ELLIPSIS : textMode == GdiTextDrawMode.WordBreak ? User32.DT.WORDBREAK | User32.DT.END_ELLIPSIS : User32.DT.WORDBREAK; if (useMnemonics) { flags |= User32.DT.NOPREFIX; } if (0 == User32.DrawTextEx(hdc, sb, sb.Length, ref rect, flags, ref dtparams)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } finally { Gdi32.SelectObject(hdc, hPrevFont); } } finally { Gdi32.DeleteObject(hFont); } } finally { } } finally { User32.ReleaseDC(context.Handle, hdc); } }
private static User32.DT GetTextFormatFlags(TextFormatFlags flags) { if (((uint)flags & WindowsGraphics.GdiUnsupportedFlagMask) == 0) { return((User32.DT)flags); } // Clear TextRenderer custom flags. User32.DT windowsGraphicsSupportedFlags = (User32.DT)(((uint)flags) & ~WindowsGraphics.GdiUnsupportedFlagMask); return(windowsGraphicsSupportedFlags); }
/// <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 static void DrawText( this Gdi32.HDC hdc, ReadOnlySpan <char> text, FontCache.Scope font, Rectangle bounds, Color foreColor, User32.DT flags, Color backColor = default, TextPaddingOptions padding = default) { if (text.IsEmpty || foreColor == Color.Transparent) { return; } ValidateFlags(flags); // DrawText requires default text alignment. using var alignment = new Gdi32.SetTextAlignmentScope(hdc, default); // Color empty means use the one currently selected in the dc. using var textColor = foreColor.IsEmpty ? default : new Gdi32.SetTextColorScope(hdc, foreColor); using var fontSelection = new Gdi32.SelectObjectScope(hdc, (Gdi32.HFONT)font); Gdi32.BKMODE newBackGroundMode = (backColor.IsEmpty || backColor == Color.Transparent) ? Gdi32.BKMODE.TRANSPARENT : Gdi32.BKMODE.OPAQUE; using var backgroundMode = new Gdi32.SetBkModeScope(hdc, newBackGroundMode); using var backgroundColor = newBackGroundMode != Gdi32.BKMODE.TRANSPARENT ? new Gdi32.SetBackgroundColorScope(hdc, backColor) : default; User32.DRAWTEXTPARAMS dtparams = GetTextMargins(font, padding); bounds = AdjustForVerticalAlignment(hdc, text, bounds, flags, ref dtparams); // Adjust unbounded rect to avoid overflow since Rectangle ctr does not do param validation. if (bounds.Width == TextRenderer.MaxSize.Width) { bounds.Width -= bounds.X; } if (bounds.Height == TextRenderer.MaxSize.Height) { bounds.Height -= bounds.Y; } RECT rect = bounds; User32.DrawTextExW(hdc, text, ref rect, flags, ref dtparams); }
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 HRESULT DrawThemeText( IHandle hTheme, Gdi32.HDC hdc, int iPartId, int iStateId, string pszText, int iCharCount, User32.DT dwTextFlags, uint dwTextFlags2, ref RECT pRect) { HRESULT hr = DrawThemeText(hTheme.Handle, hdc, iPartId, iStateId, pszText, iCharCount, dwTextFlags, dwTextFlags2, ref pRect); GC.KeepAlive(hTheme); return(hr); }
private static User32.DT GetTextFormatFlags(TextFormatFlags flags, bool blockModifyString = false) { if (blockModifyString && flags.HasFlag(TextFormatFlags.ModifyString)) { throw new ArgumentOutOfRangeException(nameof(flags), SR.TextFormatFlagsModifyStringNotAllowed); } if (((uint)flags & GdiUnsupportedFlagMask) == 0) { return((User32.DT)flags); } // Clear TextRenderer custom flags. User32.DT windowsGraphicsSupportedFlags = (User32.DT)((uint)flags & ~GdiUnsupportedFlagMask); return(windowsGraphicsSupportedFlags); }
private static (User32.DT Flags, TextPaddingOptions Padding) SplitTextFormatFlags(TextFormatFlags flags) { if (((uint)flags & GdiUnsupportedFlagMask) == 0) { return((User32.DT)flags, TextPaddingOptions.GlyphOverhangPadding); } // Clear TextRenderer custom flags. User32.DT windowsGraphicsSupportedFlags = (User32.DT)((uint)flags & ~GdiUnsupportedFlagMask); TextPaddingOptions padding = flags.HasFlag(TextFormatFlags.LeftAndRightPadding) ? TextPaddingOptions.LeftAndRightPadding : flags.HasFlag(TextFormatFlags.NoPadding) ? TextPaddingOptions.NoPadding : TextPaddingOptions.GlyphOverhangPadding; return(windowsGraphicsSupportedFlags, padding); }
/// <summary> /// The GDI DrawText does not do multiline alignment when User32.DT.SINGLELINE is not set. This /// adjustment is to workaround that limitation. We don't want to duplicate SelectObject calls here, /// so put your Font in the dc before calling this. /// /// AdjustForVerticalAlignment is only used when the text is multiline and it fits inside the bounds passed in. /// In that case we want the horizontal center of the multiline text to be at the horizontal center of the bounds. /// /// If the text is multiline and it does not fit inside the bounds passed in, then return the bounds that were passed in. /// This way we paint the top of the text at the top of the bounds passed in. /// </summary> public static Rectangle AdjustForVerticalAlignment( this Gdi32.HDC hdc, ReadOnlySpan <char> text, Rectangle bounds, User32.DT flags, ref User32.DRAWTEXTPARAMS dtparams) { ValidateFlags(flags); // Ok if any Top (Cannot test User32.DT.Top because it is 0), single line text or measuring text. bool isTop = (flags & User32.DT.BOTTOM) == 0 && (flags & User32.DT.VCENTER) == 0; if (isTop || ((flags & User32.DT.SINGLELINE) != 0) || ((flags & User32.DT.CALCRECT) != 0)) { return(bounds); } RECT rect = new RECT(bounds); // Get the text bounds. flags |= User32.DT.CALCRECT; int textHeight = User32.DrawTextExW(hdc, text, ref rect, flags, ref dtparams); // if the text does not fit inside the bounds then return the bounds that were passed in if (textHeight > bounds.Height) { return(bounds); } Rectangle adjustedBounds = bounds; if ((flags & User32.DT.VCENTER) != 0) { // Middle adjustedBounds.Y = adjustedBounds.Top + adjustedBounds.Height / 2 - textHeight / 2; } else { // Bottom adjustedBounds.Y = adjustedBounds.Bottom - textHeight; } return(adjustedBounds); }
private static Size MeasureTextInternal( ReadOnlySpan <char> text, Font?font, Size proposedSize, TextFormatFlags flags = TextFormatFlags.Bottom, bool blockModifyString = false) { User32.DT drawTextFlags = GetTextFormatFlags(flags, blockModifyString); if (text.IsEmpty) { return(Size.Empty); } using var screen = GdiCache.GetScreenHdc(); using var hfont = GdiCache.GetHFONT(font, Gdi32.QUALITY.DEFAULT, screen); return(screen.HDC.MeasureText(text, hfont, proposedSize, drawTextFlags)); }
/// <summary> /// The GDI DrawText does not do multiline alignment when User32.DT.SINGLELINE is not set. This /// adjustment is to workaround that limitation. We don't want to duplicate SelectObject calls here, /// so put your Font in the dc before calling this. /// /// AdjustForVerticalAlignment is only used when the text is multiline and it fits inside the bounds passed in. /// In that case we want the horizontal center of the multiline text to be at the horizontal center of the bounds. /// /// If the text is multiline and it does not fit inside the bounds passed in, then return the bounds that were passed in. /// This way we paint the top of the text at the top of the bounds passed in. /// </summary> public static Rectangle AdjustForVerticalAlignment( HandleRef hdc, string text, Rectangle bounds, User32.DT flags, ref User32.DRAWTEXTPARAMS dtparams) { Debug.Assert(((uint)flags & GdiUnsupportedFlagMask) == 0, "Some custom flags were left over and are not GDI compliant!"); // Ok if any Top (Cannot test User32.DT.Top because it is 0), single line text or measuring text. bool isTop = (flags & User32.DT.BOTTOM) == 0 && (flags & User32.DT.VCENTER) == 0; if (isTop || ((flags & User32.DT.SINGLELINE) != 0) || ((flags & User32.DT.CALCRECT) != 0)) { return(bounds); } RECT rect = new RECT(bounds); // Get the text bounds. flags |= User32.DT.CALCRECT; int textHeight = User32.DrawTextExW(hdc, text, text.Length, ref rect, flags, ref dtparams); // if the text does not fit inside the bounds then return the bounds that were passed in if (textHeight > bounds.Height) { return(bounds); } Rectangle adjustedBounds = bounds; if ((flags & User32.DT.VCENTER) != 0) // Middle { adjustedBounds.Y = adjustedBounds.Top + adjustedBounds.Height / 2 - textHeight / 2; } else // Bottom. { adjustedBounds.Y = adjustedBounds.Bottom - textHeight; } return(adjustedBounds); }
internal static void DrawTextInternal( PaintEventArgs e, string?text, Font?font, Rectangle bounds, Color foreColor, Color backColor = default, User32.DT flags = User32.DT.CENTER | User32.DT.VCENTER) { 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); } }
/// <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. #if OPTIMIZED_MEASUREMENTDC User32.DRAWTEXTPARAMS dtparams; // use the cache if we've got it if (MeasurementDCInfo.IsMeasurementDC(DeviceContext)) { dtparams = MeasurementDCInfo.GetTextMargins(this, font); } else { dtparams = GetTextMargins(font); } #else User32.DRAWTEXTPARAMS dtparams = GetTextMargins(font); #endif // 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 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!"); Debug.Assert((flags & User32.DT.CALCRECT) == 0, "CALCRECT flag is set, text won't be drawn"); HandleRef hdc = new HandleRef(DeviceContext, DeviceContext.Hdc); // 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(hdc, 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(hdc, 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> /// 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);
/// <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);
private static void ValidateFlags(User32.DT flags) { Debug.Assert(((uint)flags & GdiUnsupportedFlagMask) == 0, "Some custom flags were left over and are not GDI compliant!"); }