// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Specialized;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms.Layout;

namespace System.Windows.Forms.ButtonInternal;

internal abstract partial class ButtonBaseAdapter
{
    internal partial class LayoutOptions
    {
        private static readonly int s_combineCheck = BitVector32.CreateMask();
        private static readonly int s_combineImageText = BitVector32.CreateMask(s_combineCheck);

        private bool _disableWordWrapping;

        internal bool DisableWordWrapping
        {
            get => _disableWordWrapping;
            set => _disableWordWrapping = value;
        }

        // If this is changed to a property callers will need to be updated
        // as they modify fields in the Rectangle.
        public Rectangle Client;

        public bool GrowBorderBy1PxWhenDefault { get; set; }
        public bool IsDefault { get; set; }
        public int BorderSize { get; set; }
        public int PaddingSize { get; set; }
        public bool MaxFocus { get; set; }
        public bool FocusOddEvenFixup { get; set; }
        public Font Font { get; set; } = null!;
        public string? Text { get; set; }
        public Size ImageSize { get; set; }
        public int CheckSize { get; set; }
        public int CheckPaddingSize { get; set; }
        public ContentAlignment CheckAlign { get; set; }
        public ContentAlignment ImageAlign { get; set; }
        public ContentAlignment TextAlign { get; set; }
        public TextImageRelation TextImageRelation { get; set; }
        public bool HintTextUp { get; set; }
        public bool TextOffset { get; set; }
        public bool ShadowedText { get; set; }
        public bool LayoutRTL { get; set; }
        public bool VerticalText { get; set; }
        public bool UseCompatibleTextRendering { get; set; }
        public bool ClipImagesToClient { get; set; }
        public bool EnsureImagePreferredSizeInset { get; set; }

        /// <summary>
        ///  .NET Framework 1.0/1.1 compatibility
        /// </summary>
        public bool DotNetOneButtonCompat { get; set; } = true;
        public TextFormatFlags GdiTextFormatFlags { get; set; } = TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl;
        public StringFormatFlags GdiPlusFormatFlags { get; set; }
        public StringTrimming GdiPlusTrimming { get; set; }
        public HotkeyPrefix GdiPlusHotkeyPrefix { get; set; }

        /// <summary>
        ///  Horizontal alignment.
        /// </summary>
        public StringAlignment GdiPlusAlignment { get; set; }

        /// <summary>
        ///  Vertical alignment.
        /// </summary>
        public StringAlignment GdiPlusLineAlignment { get; set; }

        /// <summary>
        ///  We don't cache the <see cref="StringFormat"/> itself because we don't have a deterministic way of
        ///  disposing it, instead we cache the flags that make it up and create it on demand so it can be disposed
        ///  by calling code.
        /// </summary>
        public StringFormat StringFormat
        {
            get
            {
                StringFormat format = new()
                {
                    FormatFlags = GdiPlusFormatFlags,
                    Trimming = GdiPlusTrimming,
                    HotkeyPrefix = GdiPlusHotkeyPrefix,
                    Alignment = GdiPlusAlignment,
                    LineAlignment = GdiPlusLineAlignment
                };

                if (_disableWordWrapping)
                {
                    format.FormatFlags |= StringFormatFlags.NoWrap;
                }

                return format;
            }
            set
            {
                GdiPlusFormatFlags = value.FormatFlags;
                GdiPlusTrimming = value.Trimming;
                GdiPlusHotkeyPrefix = value.HotkeyPrefix;
                GdiPlusAlignment = value.Alignment;
                GdiPlusLineAlignment = value.LineAlignment;
            }
        }

        public TextFormatFlags TextFormatFlags =>
            _disableWordWrapping ? GdiTextFormatFlags & ~TextFormatFlags.WordBreak : GdiTextFormatFlags;

        /// <summary>
        ///  TextImageInset compensates for two factors: 3d text when the button is disabled,
        ///  and moving text on 3d-look buttons. These factors make the text require a couple
        ///  more pixels of space. We inset image by the same amount so they line up.
        /// </summary>
        public int TextImageInset { get; set; } = 2;

        public Padding Padding { get; set; }

        /// <summary>
        ///  Uses <see cref="CheckAlign"/>, <see cref="ImageAlign"/>, and <see cref="TextAlign"/> to compose
        ///  <paramref name="checkSize"/>, <paramref name="imageSize"/>, and <paramref name="textSize"/> into
        ///  the preferred size.
        /// </summary>
        private Size Compose(Size checkSize, Size imageSize, Size textSize)
        {
            Composition hComposition = GetHorizontalComposition();
            Composition vComposition = GetVerticalComposition();
            return new Size(
                xCompose(hComposition, checkSize.Width, imageSize.Width, textSize.Width),
                xCompose(vComposition, checkSize.Height, imageSize.Height, textSize.Height));

            static int xCompose(Composition composition, int checkSize, int imageSize, int textSize)
            {
                switch (composition)
                {
                    case Composition.NoneCombined:
                        return checkSize + imageSize + textSize;
                    case Composition.CheckCombined:
                        return Math.Max(checkSize, imageSize + textSize);
                    case Composition.TextImageCombined:
                        return Math.Max(imageSize, textSize) + checkSize;
                    case Composition.AllCombined:
                        return Math.Max(Math.Max(checkSize, imageSize), textSize);
                    default:
                        Debug.Fail(string.Format(SR.InvalidArgument, nameof(composition), composition.ToString()));
                        return -7107;
                }
            }
        }

        /// <summary>
        ///  Uses <see cref="CheckAlign"/>, <see cref="ImageAlign"/>, and <see cref="TextAlign"/> to decompose
        ///  <paramref name="proposedSize"/> into the space left over for text.
        /// </summary>
        private Size Decompose(Size checkSize, Size imageSize, Size proposedSize)
        {
            Composition hComposition = GetHorizontalComposition();
            Composition vComposition = GetVerticalComposition();
            return new Size(
                xDecompose(hComposition, checkSize.Width, imageSize.Width, proposedSize.Width),
                xDecompose(vComposition, checkSize.Height, imageSize.Height, proposedSize.Height));

            static int xDecompose(Composition composition, int checkSize, int imageSize, int proposedSize)
            {
                switch (composition)
                {
                    case Composition.NoneCombined:
                        return proposedSize - (checkSize + imageSize);
                    case Composition.CheckCombined:
                        return proposedSize - imageSize;
                    case Composition.TextImageCombined:
                        return proposedSize - checkSize;
                    case Composition.AllCombined:
                        return proposedSize;
                    default:
                        Debug.Fail(string.Format(SR.InvalidArgument, nameof(composition), composition.ToString()));
                        return -7109;
                }
            }
        }

        private Composition GetHorizontalComposition()
        {
            BitVector32 action = default;

            // Checks reserve space horizontally if possible, so only AnyLeft/AnyRight prevents combination.
            action[s_combineCheck] =
                CheckAlign == ContentAlignment.MiddleCenter || !LayoutUtils.IsHorizontalAlignment(CheckAlign);

            action[s_combineImageText] = !LayoutUtils.IsHorizontalRelation(TextImageRelation);
            return (Composition)action.Data;
        }

        internal Size GetPreferredSizeCore(Size proposedSize)
        {
            // Get space required for border and padding.
            int linearBorderAndPadding = BorderSize * 2 + PaddingSize * 2;
            if (GrowBorderBy1PxWhenDefault)
            {
                linearBorderAndPadding += 2;
            }

            Size bordersAndPadding = new(linearBorderAndPadding, linearBorderAndPadding);
            proposedSize -= bordersAndPadding;

            // Get space required for check.
            int checkSizeLinear = FullCheckSize;
            Size checkSize = checkSizeLinear > 0 ? new(checkSizeLinear + 1, checkSizeLinear) : Size.Empty;

            // Get space required for Image - textImageInset compensated for by expanding image.
            Size textImageInsetSize = new(TextImageInset * 2, TextImageInset * 2);
            Size requiredImageSize = (ImageSize != Size.Empty) ? ImageSize + textImageInsetSize : Size.Empty;

            // Pack Text into remaining space
            proposedSize -= textImageInsetSize;
            proposedSize = Decompose(checkSize, requiredImageSize, proposedSize);

            Size textSize = Size.Empty;

            if (!string.IsNullOrEmpty(Text))
            {
                // When Button.AutoSizeMode is set to GrowOnly TableLayoutPanel expects buttons not to
                // automatically wrap on word break. If there's enough room for the text to word-wrap then it
                // will happen but the layout would not be adjusted to allow text wrapping. If someone has a
                // carriage return in the text we'll honor that for preferred size, but we won't wrap based
                // on constraints.
                bool disableWordWrapping = _disableWordWrapping;
                try
                {
                    _disableWordWrapping = true;
                    textSize = GetTextSize(proposedSize) + textImageInsetSize;
                }
                finally
                {
                    _disableWordWrapping = disableWordWrapping;
                }
            }

            // Combine pieces to get final preferred size.
            Size requiredSize = Compose(checkSize, ImageSize, textSize);
            if (EnsureImagePreferredSizeInset && ImageSize != Size.Empty)
            {
                Size imageRequiredSize = Compose(checkSize, requiredImageSize, Size.Empty);
                requiredSize = LayoutUtils.UnionSizes(requiredSize, imageRequiredSize);
            }

            requiredSize += bordersAndPadding;

            return requiredSize;
        }

        private Composition GetVerticalComposition()
        {
            BitVector32 action = default;

            // Checks reserve space horizontally if possible, so only Top/Bottom prevents combination.
            action[s_combineCheck] = CheckAlign == ContentAlignment.MiddleCenter || !LayoutUtils.IsVerticalAlignment(CheckAlign);
            action[s_combineImageText] = !LayoutUtils.IsVerticalRelation(TextImageRelation);
            return (Composition)action.Data;
        }

        private int FullBorderSize => OnePixExtraBorder ? BorderSize++ : BorderSize;

        private bool OnePixExtraBorder => GrowBorderBy1PxWhenDefault && IsDefault;

        internal LayoutData Layout()
        {
            LayoutData layout = new(this)
            {
                Client = Client
            };

            // Subtract border size from layout area.
            int fullBorderSize = FullBorderSize;
            layout.Face = Rectangle.Inflate(layout.Client, -fullBorderSize, -fullBorderSize);

            // CheckBounds, CheckArea, Field.
            CalcCheckmarkRectangle(layout);

            // ImageBounds, ImageLocation, TextBounds.
            LayoutTextAndImage(layout);

            // Focus.
            if (MaxFocus)
            {
                layout.Focus = layout.Field;
                layout.Focus.Inflate(-1, -1);

                // Adjust for padding.
                layout.Focus = LayoutUtils.InflateRect(layout.Focus, Padding);
            }
            else
            {
                Rectangle textAdjusted = new(
                    layout.TextBounds.X - 1,
                    layout.TextBounds.Y - 1,
                    layout.TextBounds.Width + 2,
                    layout.TextBounds.Height + 3);

                layout.Focus = ImageSize != Size.Empty
                    ? Rectangle.Union(textAdjusted, layout.ImageBounds)
                    : textAdjusted;
            }

            if (FocusOddEvenFixup)
            {
                if (layout.Focus.Height % 2 == 0)
                {
                    layout.Focus.Y++;
                    layout.Focus.Height--;
                }

                if (layout.Focus.Width % 2 == 0)
                {
                    layout.Focus.X++;
                    layout.Focus.Width--;
                }
            }

            return layout;
        }

        private TextImageRelation RtlTranslateRelation(TextImageRelation relation)
        {
            // If RTL, we swap ImageBeforeText and TextBeforeImage.
            if (LayoutRTL)
            {
                switch (relation)
                {
                    case TextImageRelation.ImageBeforeText:
                        return TextImageRelation.TextBeforeImage;
                    case TextImageRelation.TextBeforeImage:
                        return TextImageRelation.ImageBeforeText;
                }
            }

            return relation;
        }

        internal ContentAlignment RtlTranslateContent(ContentAlignment align)
        {
            if (LayoutRTL)
            {
                ContentAlignment[][] mapping =
                [
                    [ContentAlignment.TopLeft, ContentAlignment.TopRight],
                    [ContentAlignment.MiddleLeft, ContentAlignment.MiddleRight],
                    [ContentAlignment.BottomLeft, ContentAlignment.BottomRight],
                ];

                for (int i = 0; i < 3; ++i)
                {
                    if (mapping[i][0] == align)
                    {
                        return mapping[i][1];
                    }
                    else if (mapping[i][1] == align)
                    {
                        return mapping[i][0];
                    }
                }
            }

            return align;
        }

        private int FullCheckSize => CheckSize + CheckPaddingSize;

        private void CalcCheckmarkRectangle(LayoutData layout)
        {
            int checkSizeFull = FullCheckSize;
            layout.CheckBounds = new Rectangle(Client.X, Client.Y, checkSizeFull, checkSizeFull);

            // Translate checkAlign for Rtl applications
            ContentAlignment align = RtlTranslateContent(CheckAlign);

            Rectangle field = Rectangle.Inflate(layout.Face, -PaddingSize, -PaddingSize);

            layout.Field = field;

            if (checkSizeFull <= 0)
            {
                return;
            }

            if ((align & LayoutUtils.AnyRight) != 0)
            {
                layout.CheckBounds.X = (field.X + field.Width) - layout.CheckBounds.Width;
            }
            else if ((align & LayoutUtils.AnyCenter) != 0)
            {
                layout.CheckBounds.X = field.X + (field.Width - layout.CheckBounds.Width) / 2;
            }

            if ((align & LayoutUtils.AnyBottom) != 0)
            {
                layout.CheckBounds.Y = (field.Y + field.Height) - layout.CheckBounds.Height;
            }
            else if ((align & LayoutUtils.AnyTop) != 0)
            {
                layout.CheckBounds.Y = field.Y + 2; // + 2: this needs to be aligned to the text (
            }
            else
            {
                layout.CheckBounds.Y = field.Y + (field.Height - layout.CheckBounds.Height) / 2;
            }

            switch (align)
            {
                case ContentAlignment.TopLeft:
                case ContentAlignment.MiddleLeft:
                case ContentAlignment.BottomLeft:
                    layout.CheckArea.X = field.X;
                    layout.CheckArea.Width = checkSizeFull + 1;

                    layout.CheckArea.Y = field.Y;
                    layout.CheckArea.Height = field.Height;

                    layout.Field.X += checkSizeFull + 1;
                    layout.Field.Width -= checkSizeFull + 1;
                    break;
                case ContentAlignment.TopRight:
                case ContentAlignment.MiddleRight:
                case ContentAlignment.BottomRight:
                    layout.CheckArea.X = field.X + field.Width - checkSizeFull;
                    layout.CheckArea.Width = checkSizeFull + 1;

                    layout.CheckArea.Y = field.Y;
                    layout.CheckArea.Height = field.Height;

                    layout.Field.Width -= checkSizeFull + 1;
                    break;
                case ContentAlignment.TopCenter:
                    layout.CheckArea.X = field.X;
                    layout.CheckArea.Width = field.Width;

                    layout.CheckArea.Y = field.Y;
                    layout.CheckArea.Height = checkSizeFull;

                    layout.Field.Y += checkSizeFull;
                    layout.Field.Height -= checkSizeFull;
                    break;

                case ContentAlignment.BottomCenter:
                    layout.CheckArea.X = field.X;
                    layout.CheckArea.Width = field.Width;

                    layout.CheckArea.Y = field.Y + field.Height - checkSizeFull;
                    layout.CheckArea.Height = checkSizeFull;

                    layout.Field.Height -= checkSizeFull;
                    break;

                case ContentAlignment.MiddleCenter:
                    layout.CheckArea = layout.CheckBounds;
                    break;
            }

            layout.CheckBounds.Width -= CheckPaddingSize;
            layout.CheckBounds.Height -= CheckPaddingSize;
        }

        /// <summary>
        ///  Maps an image align to the set of <see cref="Forms.TextImageRelation"/>s that represent the same edge.
        ///  For example, <see cref="ContentAlignment.TopLeft"/> maps to <see cref="TextImageRelation.ImageAboveText"/>
        ///  and <see cref="TextImageRelation.ImageBeforeText"/>.
        /// </summary>
        private static readonly TextImageRelation[] s_imageAlignToRelation =
        [
            TextImageRelation.ImageAboveText | TextImageRelation.ImageBeforeText,     // TopLeft
            TextImageRelation.ImageAboveText,                                         // TopCenter
            TextImageRelation.ImageAboveText | TextImageRelation.TextBeforeImage,     // TopRight
            0,                                                                        // Invalid
            TextImageRelation.ImageBeforeText,                                        // MiddleLeft
            0,                                                                        // MiddleCenter
            TextImageRelation.TextBeforeImage,                                        // MiddleRight
            0,                                                                        // Invalid
            TextImageRelation.TextAboveImage | TextImageRelation.ImageBeforeText,     // BottomLeft
            TextImageRelation.TextAboveImage,                                         // BottomCenter
            TextImageRelation.TextAboveImage | TextImageRelation.TextBeforeImage      // BottomRight
        ];

        private static TextImageRelation ImageAlignToRelation(ContentAlignment alignment)
            => s_imageAlignToRelation[LayoutUtils.ContentAlignmentToIndex(alignment)];

        private static TextImageRelation TextAlignToRelation(ContentAlignment alignment)
            => LayoutUtils.GetOppositeTextImageRelation(ImageAlignToRelation(alignment));

        internal void LayoutTextAndImage(LayoutData layout)
        {
            // Translate for Rtl applications. This shadows the member variables.
            ContentAlignment imageAlign = RtlTranslateContent(ImageAlign);
            ContentAlignment textAlign = RtlTranslateContent(TextAlign);
            TextImageRelation textImageRelation = RtlTranslateRelation(TextImageRelation);

            // Figure out the maximum bounds for text & image.
            Rectangle maxBounds = Rectangle.Inflate(layout.Field, -TextImageInset, -TextImageInset);
            if (OnePixExtraBorder)
            {
                maxBounds.Inflate(1, 1);
            }

            // Compute the final image and text bounds.
            if (ImageSize == Size.Empty || Text is null || Text.Length == 0 || textImageRelation == TextImageRelation.Overlay)
            {
                // Do not worry about text/image overlaying
                Size textSize = GetTextSize(maxBounds.Size);

                // For .NET Framework 1.1 compatibility.
                Size size = ImageSize;
                if (layout.Options.DotNetOneButtonCompat && ImageSize != Size.Empty)
                {
                    size = new Size(size.Width + 1, size.Height + 1);
                }

                layout.ImageBounds = LayoutUtils.Align(size, maxBounds, imageAlign);
                layout.TextBounds = LayoutUtils.Align(textSize, maxBounds, textAlign);
            }
            else
            {
                // Rearrange text/image to prevent overlay. Pack text into maxBounds - space reserved for image.
                Size maxTextSize = LayoutUtils.SubAlignedRegion(maxBounds.Size, ImageSize, textImageRelation);
                Size textSize = GetTextSize(maxTextSize);
                Rectangle maxCombinedBounds = maxBounds;

                // Combine text & image into one rectangle that we center within maxBounds.
                Size combinedSize = LayoutUtils.AddAlignedRegion(textSize, ImageSize, textImageRelation);
                maxCombinedBounds.Size = LayoutUtils.UnionSizes(maxCombinedBounds.Size, combinedSize);
                Rectangle combinedBounds = LayoutUtils.Align(combinedSize, maxCombinedBounds, ContentAlignment.MiddleCenter);

                // ImageEdge indicates whether the combination of ImageAlign and TextImageRelation place
                // the image along the edge of the control. If so, we can increase the space for text.
                bool imageEdge = (AnchorStyles)(ImageAlignToRelation(imageAlign) & textImageRelation) != AnchorStyles.None;

                // TextEdge indicates whether the combination of TextAlign and TextImageRelation place
                // the text along the edge of the control. If so, we can increase the space for image.
                bool textEdge = (AnchorStyles)(TextAlignToRelation(textAlign) & textImageRelation) != AnchorStyles.None;

                if (imageEdge)
                {
                    // Just split imageSize off of maxCombinedBounds.
                    LayoutUtils.SplitRegion(
                        maxCombinedBounds,
                        ImageSize,
                        (AnchorStyles)textImageRelation,
                        out layout.ImageBounds,
                        out layout.TextBounds);
                }
                else if (textEdge)
                {
                    // Just split textSize off of maxCombinedBounds.
                    LayoutUtils.SplitRegion(
                        maxCombinedBounds,
                        textSize,
                        (AnchorStyles)LayoutUtils.GetOppositeTextImageRelation(textImageRelation),
                        out layout.TextBounds,
                        out layout.ImageBounds);
                }
                else
                {
                    // Expand the adjacent regions to maxCombinedBounds (centered) and split the rectangle into
                    // imageBounds and textBounds.
                    LayoutUtils.SplitRegion(
                        combinedBounds,
                        ImageSize,
                        (AnchorStyles)textImageRelation,
                        out layout.ImageBounds,
                        out layout.TextBounds);
                    LayoutUtils.ExpandRegionsToFillBounds(
                        maxCombinedBounds,
                        (AnchorStyles)textImageRelation,
                        ref layout.ImageBounds,
                        ref layout.TextBounds);
                }

                // Align text/image within their regions.
                layout.ImageBounds = LayoutUtils.Align(ImageSize, layout.ImageBounds, imageAlign);
                layout.TextBounds = LayoutUtils.Align(textSize, layout.TextBounds, textAlign);
            }

            // Don't call "layout.imageBounds = Rectangle.Intersect(layout.imageBounds, maxBounds);"
            // because that is a breaking change that causes images to be scaled to the dimensions of the control.
            // adjust textBounds so that the text is still visible even if the image is larger than the button's size

            // Why do we intersect with layout.field for textBounds while we intersect with maxBounds for imageBounds?
            // this is because there are some legacy code which squeezes the button so small that text will get clipped
            // if we intersect with maxBounds. Have to do this for backward compatibility.

            if (textImageRelation is TextImageRelation.TextBeforeImage or TextImageRelation.ImageBeforeText)
            {
                // Adjust the vertical position of textBounds so that the text doesn't fall off the boundary of the button
                int textBottom = Math.Min(layout.TextBounds.Bottom, layout.Field.Bottom);
                layout.TextBounds.Y = Math.Max(
                    Math.Min(layout.TextBounds.Y, layout.Field.Y + (layout.Field.Height - layout.TextBounds.Height) / 2),
                    layout.Field.Y);
                layout.TextBounds.Height = textBottom - layout.TextBounds.Y;
            }

            if (textImageRelation is TextImageRelation.TextAboveImage or TextImageRelation.ImageAboveText)
            {
                // Adjust the horizontal position of textBounds so that the text doesn't fall off the boundary of the button
                int textRight = Math.Min(layout.TextBounds.Right, layout.Field.Right);
                layout.TextBounds.X = Math.Max(
                    Math.Min(layout.TextBounds.X, layout.Field.X + (layout.Field.Width - layout.TextBounds.Width) / 2),
                    layout.Field.X);
                layout.TextBounds.Width = textRight - layout.TextBounds.X;
            }

            if (textImageRelation == TextImageRelation.ImageBeforeText && layout.ImageBounds.Size.Width != 0)
            {
                // Squeezes imageBounds.Width so that text is visible
                layout.ImageBounds.Width = Math.Max(
                    0,
                    Math.Min(maxBounds.Width - layout.TextBounds.Width, layout.ImageBounds.Width));
                layout.TextBounds.X = layout.ImageBounds.X + layout.ImageBounds.Width;
            }

            if (textImageRelation == TextImageRelation.ImageAboveText && layout.ImageBounds.Size.Height != 0)
            {
                // Squeezes imageBounds.Height so that the text is visible
                layout.ImageBounds.Height = Math.Max(
                    0,
                    Math.Min(maxBounds.Height - layout.TextBounds.Height, layout.ImageBounds.Height));
                layout.TextBounds.Y = layout.ImageBounds.Y + layout.ImageBounds.Height;
            }

            // Make sure that textBound is contained in layout.field
            layout.TextBounds = Rectangle.Intersect(layout.TextBounds, layout.Field);
            if (HintTextUp)
            {
                layout.TextBounds.Y--;
            }

            if (TextOffset)
            {
                layout.TextBounds.Offset(1, 1);
            }

            // For .NET Framework 1.1 compatibility.
            if (layout.Options.DotNetOneButtonCompat)
            {
                layout.ImageStart = layout.ImageBounds.Location;
                layout.ImageBounds = Rectangle.Intersect(layout.ImageBounds, layout.Field);
            }
            else if (!Application.RenderWithVisualStyles)
            {
                // Not sure why this is here, but we can't remove it, since it might break
                // ToolStrips on non-themed machines
                layout.TextBounds.X++;
            }

            // Clip
            int bottom;

            // If we are using GDI to measure text, then we can get into a situation, where
            // the proposed height is ignore. In this case, we want to clip it against maxBounds.
            if (!UseCompatibleTextRendering)
            {
                bottom = Math.Min(layout.TextBounds.Bottom, maxBounds.Bottom);
                layout.TextBounds.Y = Math.Max(layout.TextBounds.Y, maxBounds.Y);
            }
            else
            {
                // If we are using GDI+ (like .NET Framework 1.1), then use the old code.
                // This ensures that we have pixel-level rendering compatibility.
                bottom = Math.Min(layout.TextBounds.Bottom, layout.Field.Bottom);
                layout.TextBounds.Y = Math.Max(layout.TextBounds.Y, layout.Field.Y);
            }

            layout.TextBounds.Height = bottom - layout.TextBounds.Y;
        }

        protected virtual Size GetTextSize(Size proposedSize)
        {
            // Set the Prefix field of TextFormatFlags
            proposedSize = LayoutUtils.FlipSizeIf(VerticalText, proposedSize);
            Size textSize = Size.Empty;

            if (UseCompatibleTextRendering)
            {
                // GDI+ text rendering.
                using var screen = GdiCache.GetScreenDCGraphics();
                using StringFormat stringFormat = StringFormat;
                textSize = Size.Ceiling(
                    screen.Graphics.MeasureString(Text, Font, new SizeF(proposedSize.Width, proposedSize.Height),
                    stringFormat));
            }
            else if (!string.IsNullOrEmpty(Text))
            {
                // GDI text rendering (.NET Framework 2.0 feature).
                textSize = TextRenderer.MeasureText(Text, Font, proposedSize, TextFormatFlags);
            }

            // Else skip calling MeasureText, it should return 0,0

            return LayoutUtils.FlipSizeIf(VerticalText, textSize);
        }

#if DEBUG
        public override string ToString() =>
            $$"""
                { client = {{Client}}
                OnePixExtraBorder = {{OnePixExtraBorder}}
                borderSize = {{BorderSize}}
                paddingSize = {{PaddingSize}}
                maxFocus = {{MaxFocus}}
                font = {{Font}}
                text = {{Text}}
                imageSize = {{ImageSize}}
                checkSize = {{CheckSize}}
                checkPaddingSize = {{CheckPaddingSize}}
                checkAlign = {{CheckAlign}}
                imageAlign = {{ImageAlign}}
                textAlign = {{TextAlign}}
                textOffset = {{TextOffset}}
                shadowedText = {{ShadowedText}}
                textImageRelation = {{TextImageRelation}}
                layoutRTL = {{LayoutRTL}} }
                """;
#endif
    }
}
