using System.Drawing; using System.Windows.Forms; using Vanara.PInvoke; using static Vanara.PInvoke.Gdi32; using static Vanara.PInvoke.User32; namespace Vanara.Extensions; /// Extensions to Graphics related classes. public static partial class GraphicsExtension { /// Builds a from a set of variables. /// The of the text. /// if set to true if text should be in a single line. /// if set to true if line is trimmed with an ellipsis. /// if set to true to display a mnemonic. /// The value. /// if set to true show keyboard cues. /// The resulting . public static TextFormatFlags BuildTextFormatFlags(ContentAlignment textAlign, bool singleLine, bool showEllipsis, bool useMnemonic, RightToLeft rtl, bool showKeyboardCues) { var tff = TextFormatFlags.GlyphOverhangPadding | TextFormatFlags.WordBreak; var align = (int)textAlign; if ((align & 0x007) != 0) // Top tff |= TextFormatFlags.Top; else if ((align & 0x070) != 0) // Middle tff |= TextFormatFlags.VerticalCenter; else // Bottom tff |= TextFormatFlags.Bottom; if ((align & 0x111) != 0) // Left tff |= TextFormatFlags.Left; else if ((align & 0x222) != 0) // Center tff |= TextFormatFlags.HorizontalCenter; else // Right tff |= TextFormatFlags.Right; if (singleLine) tff |= TextFormatFlags.SingleLine; if (showEllipsis) tff |= TextFormatFlags.EndEllipsis | TextFormatFlags.WordEllipsis; if (rtl == RightToLeft.Yes) tff |= TextFormatFlags.RightToLeft; if (!useMnemonic) return tff | TextFormatFlags.NoPrefix; if (!showKeyboardCues) tff |= TextFormatFlags.HidePrefix; return tff; } /// A method used to calculate layout for Image and Text content with standard options. /// The bounding Rectangle within which to draw. /// The text to draw. /// The font used to draw text. /// The image to draw (this may be null). /// The vertical and horizontal alignment of the text. /// The vertical and horizontal alignment of the image. /// The placement of the image and text relative to each other. /// set true if text should wrap. /// The size in pixels of the glow around text. /// The format. /// The actual text bounds. /// The actual image bounds. public static void CalcImageAndTextBounds(Rectangle bounds, string? text, Font font, Image? image, ContentAlignment textAlignment, ContentAlignment imageAlignment, TextImageRelation textImageRelation, bool wordWrap, int glowSize, ref TextFormatFlags format, out Rectangle actualTextBounds, out Rectangle actualImageBounds) { var horizontalRelation = (int)textImageRelation > 2; var imageHasPreference = textImageRelation is TextImageRelation.ImageBeforeText or TextImageRelation.ImageAboveText; var preferredAlignmentValue = imageHasPreference ? (int)imageAlignment : (int)textAlignment; var contentRectangle = bounds; format |= TextFormatFlags.TextBoxControl | (wordWrap ? TextFormatFlags.WordBreak : TextFormatFlags.SingleLine); // Get ImageSize var imageSize = image?.Size ?? Size.Empty; // Get AvailableTextSize var availableTextSize = horizontalRelation ? new Size(bounds.Width - imageSize.Width, bounds.Height) : new Size(bounds.Width, bounds.Height - imageSize.Height); // Get ActualTextSize var actualTextSize = text?.Length > 0 ? TextRenderer.MeasureText(text, font, availableTextSize, format) : Size.Empty; // Get ContentRectangle based upon TextImageRelation if (textImageRelation != 0) { // Get ContentSize var contentSize = horizontalRelation ? new Size(imageSize.Width + actualTextSize.Width, Math.Max(imageSize.Height, availableTextSize.Height)) : new Size(Math.Max(imageSize.Width, availableTextSize.Width), imageSize.Height + actualTextSize.Height); // Get ContentLocation var contentLocation = bounds.Location; if (horizontalRelation) { if (preferredAlignmentValue % 15 == 1) contentLocation.X = bounds.Left; else if (preferredAlignmentValue % 15 == 2) contentLocation.X = bounds.Left + (bounds.Width / 2 - contentSize.Width / 2); else if (preferredAlignmentValue % 15 == 4) contentLocation.X = bounds.Right - contentSize.Width; } else { if (preferredAlignmentValue <= 4) contentLocation.Y = bounds.Top; else if (preferredAlignmentValue >= 256) contentLocation.Y = bounds.Bottom - contentSize.Height; else contentLocation.Y = bounds.Top + (bounds.Height / 2 - contentSize.Height / 2); } contentRectangle = new Rectangle(contentLocation, contentSize); } actualImageBounds = System.Drawing.Rectangle.Empty; if (image != null) { // Get ActualImageBounds actualImageBounds = new Rectangle(bounds.Location, imageSize); if (horizontalRelation) { actualImageBounds.X = imageHasPreference ? contentRectangle.X : contentRectangle.Right - imageSize.Width; actualImageBounds.Y = (int)imageAlignment <= 4 ? contentRectangle.Y : (int)imageAlignment >= 256 ? contentRectangle.Bottom - imageSize.Height : contentRectangle.Y + contentRectangle.Height / 2 - imageSize.Height / 2; } else if (textImageRelation == 0) { if ((int)imageAlignment <= 4) actualImageBounds.Y = bounds.Top; else if ((int)imageAlignment >= 256) actualImageBounds.Y = bounds.Bottom - imageSize.Height; else actualImageBounds.Y = bounds.Top + (bounds.Height / 2 - imageSize.Height / 2); if ((int)imageAlignment % 15 == 1) actualImageBounds.X = bounds.Left; else if ((int)imageAlignment % 15 == 2) actualImageBounds.X = bounds.Left + (bounds.Width / 2 - imageSize.Width / 2); else if ((int)imageAlignment % 15 == 4) actualImageBounds.X = bounds.Right - imageSize.Width; } else { actualImageBounds.Y = imageHasPreference ? contentRectangle.Y : contentRectangle.Bottom - imageSize.Height; actualImageBounds.X = (int)imageAlignment % 15 == 1 ? contentRectangle.X : (int)imageAlignment % 15 == 2 ? contentRectangle.X + contentRectangle.Width / 2 - imageSize.Width / 2 : contentRectangle.Right - imageSize.Width; } } // Get ActualTextBounds actualTextBounds = System.Drawing.Rectangle.Empty; if (!(text?.Length > 0)) return; actualTextBounds = new Rectangle(Point.Empty, actualTextSize); if (horizontalRelation) { actualTextBounds.X = imageHasPreference ? contentRectangle.Right - actualTextSize.Width : contentRectangle.X; actualTextBounds.Y = (int)textAlignment <= 4 ? contentRectangle.Y : (int)textAlignment >= 256 ? contentRectangle.Bottom - actualTextSize.Height : contentRectangle.Y + contentRectangle.Height / 2 - actualTextSize.Height / 2; } else if (textImageRelation == 0) { if ((int)textAlignment <= 4) actualTextBounds.Y = bounds.Top; else if ((int)textAlignment >= 256) actualTextBounds.Y = bounds.Bottom - actualTextSize.Height; else actualTextBounds.Y = bounds.Top + (bounds.Height / 2 - actualTextSize.Height / 2); if ((int)textAlignment % 15 == 1) actualTextBounds.X = bounds.Left; else if ((int)textAlignment % 15 == 2) actualTextBounds.X = bounds.Left + (bounds.Width / 2 - actualTextSize.Width / 2); else if ((int)textAlignment % 15 == 4) actualTextBounds.X = bounds.Right - actualTextSize.Width; } else { actualTextBounds.Y = imageHasPreference ? contentRectangle.Bottom - actualTextSize.Height : contentRectangle.Y; actualTextBounds.X = (int)textAlignment % 15 == 1 ? contentRectangle.X : (int)textAlignment % 15 == 2 ? contentRectangle.X + contentRectangle.Width / 2 - actualTextSize.Width / 2 : contentRectangle.Right - actualTextSize.Width; } } /// A method used to draw standard Image and Text content with standard layout options. /// The Graphics object on which to draw. /// The bounding Rectangle within which to draw. /// The text to draw. /// The font used to draw text. /// The image to draw (this may be null). /// The vertical and horizontal alignment of the text. /// The vertical and horizontal alignment of the image. /// The placement of the image and text relative to each other. /// The color to draw the text. /// set true if text should wrap. /// The size in pixels of the glow around text. /// Set false to draw image grayed out. /// The used to format the text. public static void DrawImageAndText(this Graphics graphics, Rectangle bounds, string? text, Font font, Image? image, ContentAlignment textAlignment, ContentAlignment imageAlignment, TextImageRelation textImageRelation, Color textColor, bool wordWrap, int glowSize, bool enabled = true, TextFormatFlags format = TextFormatFlags.TextBoxControl) { CalcImageAndTextBounds(bounds, text, font, image, textAlignment, imageAlignment, textImageRelation, wordWrap, glowSize, ref format, out Rectangle tRect, out Rectangle iRect); // Draw Image if (image != null) { if (enabled) graphics.DrawImage(image, iRect); else ControlPaint.DrawImageDisabled(graphics, image, iRect.X, iRect.Y, Color.Transparent); } // Draw text if (text?.Length > 0) TextRenderer.DrawText(graphics, text, font, tRect, textColor, format); } /// The alignment of the new rectangle. /// The initial bounding rectangle. /// The size of the output rectangle. /// /// A rectangle of fit into according to the alignment specified by . /// [Obsolete("Please use matching function in Vanara.Extensions.GdiExtension.")] public static Rectangle GetRectangleFromAlignment(ContentAlignment alignment, Rectangle bounds, Size size) => GdiExtension.GetRectangleFromAlignment(alignment, bounds, size); /// Gets a transparent bitmap given two non-transparent bitmaps drawn against a white and black background respectively. /// A non-transparent bitmap drawn against a white background. /// A non-transparent bitmap drawn against a black background. /// A 32-bit bitmap with an alpha channel values that are set based on white and black bitmap interpolation. /// Bitmaps must be of the same size and their pixel format must be Format32bppArgb. [Obsolete("Please use matching function in Vanara.Extensions.GdiExtension.")] public static Bitmap GetTransparentBitmap(Bitmap whiteBmp, Bitmap blackBmp) => GdiExtension.GetTransparentBitmap(whiteBmp, blackBmp); /// /// Provides the size, in pixels, of the specified text when drawn with the specified font and formatting instructions, using the /// specified size to create the initial bounding rectangle for the text. /// /// The device context object. /// The text to measure. /// The to apply to the measured text. /// The of the initial bounding rectangle. /// The formatting instructions to apply to the measured text. /// /// The return value is the text height in logical units. If or is specified, the return value is the offset to the bottom of the drawn text. /// public static int MeasureText(this IDeviceContext dc, StringBuilder text, Font font, Size proposedSize, TextFormatFlags flags) { using var hdc = new SafeTempHDC(dc); using var ctx = new GdiObjectContext(hdc, (HFONT)font.ToHfont()); return Win32Error.ThrowLastErrorIf(DrawTextEx(hdc, text, text.Length, new RECT(0, 0, proposedSize.Width, proposedSize.Height), (DrawTextFlags)(int)flags | DrawTextFlags.DT_CALCRECT), h => h == 0); } }