diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Gdi32/Interop.StockObject.cs b/src/System.Windows.Forms.Primitives/src/Interop/Gdi32/Interop.StockObject.cs
index 4e889903d5c..7c0b93c57ed 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/Gdi32/Interop.StockObject.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/Gdi32/Interop.StockObject.cs
@@ -6,27 +6,33 @@ internal static partial class Interop
{
internal static partial class Gdi32
{
- internal enum StockObject : uint
+ ///
+ /// Stock GDI object identifiers.
+ ///
+ ///
+ /// Note that in metafile records these values are OR'ed with 0x80000000.
+ ///
+ internal enum StockObject : int
{
- WHITE_BRUSH = 0x80000000, // 0x00FFFFFF
- LTGRAY_BRUSH = 0x80000001, // 0x00C0C0C0
- GRAY_BRUSH = 0x80000002, // 0x00808080
- DKGRAY_BRUSH = 0x80000003, // 0x00404040
- BLACK_BRUSH = 0x80000004, // 0x00404040
- NULL_BRUSH = 0x80000005, // Same as HOLLOW_BRUSH
- WHITE_PEN = 0x80000006, // 0x00FFFFFF
- BLACK_PEN = 0x80000007, // 0x00000000
- NULL_PEN = 0x80000008,
- OEM_FIXED_FONT = 0x8000000A,
- ANSI_FIXED_FONT = 0x8000000B,
- ANSI_VAR_FONT = 0x8000000C,
- SYSTEM_FONT = 0x8000000D,
- DEVICE_DEFAULT_FONT = 0x8000000E,
- DEFAULT_PALETTE = 0x8000000F,
- SYSTEM_FIXED_FONT = 0x80000010,
- DEFAULT_GUI_FONT = 0x80000011,
- DC_BRUSH = 0x80000012,
- DC_PEN = 0x80000013
+ WHITE_BRUSH = 0x00000000, // 0x00FFFFFF
+ LTGRAY_BRUSH = 0x00000001, // 0x00C0C0C0
+ GRAY_BRUSH = 0x00000002, // 0x00808080
+ DKGRAY_BRUSH = 0x00000003, // 0x00404040
+ BLACK_BRUSH = 0x00000004, // 0x00000000
+ NULL_BRUSH = 0x00000005, // Same as HOLLOW_BRUSH
+ WHITE_PEN = 0x00000006, // 0x00FFFFFF
+ BLACK_PEN = 0x00000007, // 0x00000000
+ NULL_PEN = 0x00000008,
+ OEM_FIXED_FONT = 0x0000000A,
+ ANSI_FIXED_FONT = 0x0000000B,
+ ANSI_VAR_FONT = 0x0000000C,
+ SYSTEM_FONT = 0x0000000D,
+ DEVICE_DEFAULT_FONT = 0x0000000E,
+ DEFAULT_PALETTE = 0x0000000F,
+ SYSTEM_FIXED_FONT = 0x00000010,
+ DEFAULT_GUI_FONT = 0x00000011,
+ DC_BRUSH = 0x00000012,
+ DC_PEN = 0x00000013
}
}
}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/EmfRecord.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/EmfRecord.cs
index 5a6e1d139df..4530c71baef 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/EmfRecord.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/EmfRecord.cs
@@ -91,6 +91,20 @@ public EMREXTTEXTOUTW* ExtTextOutWRecord
=> Type == Gdi32.EMR.EXTTEXTOUTW ? (EMREXTTEXTOUTW*)_lpmr : null;
public EMRENUMRECORD* SetMapModeRecord
=> Type == Gdi32.EMR.SETMAPMODE ? (EMRENUMRECORD*)_lpmr : null;
+ public EMRRECTRECORD* FillPathRecord
+ => Type == Gdi32.EMR.FILLPATH ? (EMRRECTRECORD*)_lpmr : null;
+ public EMRRECTRECORD* StrokeAndFillPathRecord
+ => Type == Gdi32.EMR.STROKEANDFILLPATH ? (EMRRECTRECORD*)_lpmr : null;
+ public EMRRECTRECORD* StrokePathRecord
+ => Type == Gdi32.EMR.STROKEPATH ? (EMRRECTRECORD*)_lpmr : null;
+ public EMRRECTRECORD* ExcludeClipRectRecord
+ => Type == Gdi32.EMR.EXCLUDECLIPRECT ? (EMRRECTRECORD*)_lpmr : null;
+ public EMRRECTRECORD* IntersetClipRectRecord
+ => Type == Gdi32.EMR.INTERSECTCLIPRECT ? (EMRRECTRECORD*)_lpmr : null;
+ public EMRRECTRECORD* EllipseRecord
+ => Type == Gdi32.EMR.ELLIPSE ? (EMRRECTRECORD*)_lpmr : null;
+ public EMRRECTRECORD* RectangleRecord
+ => Type == Gdi32.EMR.RECTANGLE ? (EMRRECTRECORD*)_lpmr : null;
public override string ToString() => Type switch
{
@@ -119,6 +133,13 @@ public EMRENUMRECORD* SetMapModeRecord
Gdi32.EMR.EXTCREATEFONTINDIRECTW => ExtCreateFontIndirectWRecord->ToString(),
Gdi32.EMR.EXTTEXTOUTW => ExtTextOutWRecord->ToString(),
Gdi32.EMR.SETMAPMODE => SetMapModeRecord->ToString(),
+ Gdi32.EMR.FILLPATH => FillPathRecord->ToString(),
+ Gdi32.EMR.STROKEANDFILLPATH => StrokeAndFillPathRecord->ToString(),
+ Gdi32.EMR.STROKEPATH => StrokePathRecord->ToString(),
+ Gdi32.EMR.EXCLUDECLIPRECT => ExcludeClipRectRecord->ToString(),
+ Gdi32.EMR.INTERSECTCLIPRECT => IntersetClipRectRecord->ToString(),
+ Gdi32.EMR.ELLIPSE => EllipseRecord->ToString(),
+ Gdi32.EMR.RECTANGLE => RectangleRecord->ToString(),
_ => $"[EMR{Type}]"
};
}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRINDEXRECORD.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRINDEXRECORD.cs
index d6354a0db29..ca458b4a2a1 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRINDEXRECORD.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRINDEXRECORD.cs
@@ -26,7 +26,7 @@ internal struct EMRINDEXRECORD
public uint index;
public bool IsStockObject => (index & 0x80000000) != 0;
- public Gdi32.StockObject StockObject => (Gdi32.StockObject)index;
+ public Gdi32.StockObject StockObject => (Gdi32.StockObject)(index & ~0x80000000);
public override string ToString()
=> IsStockObject
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRRECTRECORD.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRRECTRECORD.cs
new file mode 100644
index 00000000000..1a53cd46d6e
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/RecordTypes/EMRRECTRECORD.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+using System.Drawing;
+using System.Runtime.InteropServices;
+using static Interop;
+
+namespace System.Windows.Forms.Metafiles
+{
+ ///
+ /// Record that just has a single value.
+ ///
+ ///
+ /// Not an actual Win32 define, encapsulates:
+ ///
+ /// - EMRFILLPATH
+ /// - EMRSTROKEANDFILLPATH
+ /// - EMRSTROKEPATH
+ /// - EMREXCLUDECLIPRECT
+ /// - EMRINTERSECTCLIPRECT
+ /// - EMRELLIPSE
+ /// - EMRRECTANGLE
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct EMRRECTRECORD
+ {
+ public EMR emr;
+ public RECT rect;
+
+ public override string ToString() => $"[EMR{emr.iType}] RECT: {rect}";
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/RectangleValidator.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/RectangleValidator.cs
new file mode 100644
index 00000000000..769ce917cd2
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/RectangleValidator.cs
@@ -0,0 +1,143 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+using System.Drawing;
+using Xunit;
+using static Interop;
+
+namespace System.Windows.Forms.Metafiles
+{
+ internal sealed class RectangleValidator : IEmfValidator
+ {
+ private readonly Flags _validate;
+ private readonly Rectangle _bounds;
+ private readonly int _penWidth;
+ private readonly Color _penColor;
+ private readonly Gdi32.PS _penStyle;
+ private readonly Gdi32.R2 _rop2;
+ private readonly Gdi32.BKMODE _backgroundMode;
+ private readonly Gdi32.BS _brushStyle;
+ private readonly Color _brushColor;
+
+ public RectangleValidator(
+ RECT bounds,
+ Color penColor,
+ Color brushColor,
+ int penWidth = 1,
+ Gdi32.PS penStyle = default,
+ Gdi32.R2 rop2 = Gdi32.R2.COPYPEN,
+ Gdi32.BKMODE backgroundMode = Gdi32.BKMODE.TRANSPARENT,
+ Gdi32.BS brushStyle = default,
+ Flags validate = default)
+ {
+ _bounds = bounds;
+ _brushColor = brushColor;
+ _brushStyle = brushStyle;
+ _penWidth = penWidth;
+ _penColor = penColor;
+ _penStyle = penStyle;
+ _rop2 = rop2;
+ _backgroundMode = backgroundMode;
+
+ if (validate != default)
+ {
+ _validate = validate;
+ return;
+ }
+
+ // Default values for all of these are valid expectations so we always turn them on.
+ _validate = Flags.Bounds | Flags.PenStyle | Flags.BrushStyle;
+
+ if (penWidth != 0)
+ {
+ _validate |= Flags.PenWidth;
+ }
+
+ if (!penColor.IsEmpty)
+ {
+ _validate |= Flags.PenColor;
+ }
+
+ if (!brushColor.IsEmpty)
+ {
+ _validate |= Flags.BrushColor;
+ }
+
+ if (backgroundMode != default)
+ {
+ _validate |= Flags.BackgroundMode;
+ }
+
+ if (_rop2 != default)
+ {
+ _validate |= Flags.RopMode;
+ }
+ }
+
+ public bool ShouldValidate(Gdi32.EMR recordType) => recordType == Gdi32.EMR.RECTANGLE;
+
+ public unsafe void Validate(ref EmfRecord record, DeviceContextState state, out bool complete)
+ {
+ // We're only checking one TextOut record, so this call completes our work.
+ complete = true;
+
+ EMRRECTRECORD* rectangle = record.RectangleRecord;
+
+ if (_validate.HasFlag(Flags.Bounds))
+ {
+ Assert.Equal(_bounds, (Rectangle)rectangle->rect);
+ }
+
+ if (_validate.HasFlag(Flags.BrushColor))
+ {
+ Assert.Equal((COLORREF)_brushColor, state.SelectedBrush.lbColor);
+ }
+
+ if (_validate.HasFlag(Flags.BrushStyle))
+ {
+ Assert.Equal(_brushStyle, state.SelectedBrush.lbStyle);
+ }
+
+ if (_validate.HasFlag(Flags.PenColor))
+ {
+ Assert.Equal((COLORREF)_penColor, state.SelectedPen.lopnColor);
+ }
+
+ if (_validate.HasFlag(Flags.PenWidth))
+ {
+ Assert.Equal(_penWidth, state.SelectedPen.lopnWidth.X);
+ }
+
+ if (_validate.HasFlag(Flags.PenStyle))
+ {
+ Assert.Equal(_penStyle, state.SelectedPen.lopnStyle);
+ }
+
+ if (_validate.HasFlag(Flags.BackgroundMode))
+ {
+ Assert.Equal(_backgroundMode, state.BackgroundMode);
+ }
+
+ if (_validate.HasFlag(Flags.RopMode))
+ {
+ Assert.Equal(_rop2, state.Rop2Mode);
+ }
+ }
+
+ [Flags]
+ public enum Flags : uint
+ {
+ Bounds = 0b00000000_00000000_00000000_00000001,
+ PenColor = 0b00000000_00000000_00000000_00000100,
+ PenWidth = 0b00000000_00000000_00000000_00001000,
+ PenStyle = 0b00000000_00000000_00000000_00010000,
+ RopMode = 0b00000000_00000000_00000000_00100000,
+ BackgroundMode = 0b00000000_00000000_00000000_01000000,
+ BrushColor = 0b00000000_00000000_00000000_10000000,
+ BrushStyle = 0b00000000_00000000_00000001_00000000
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/Validate.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/Validate.cs
index 89ed5358478..f21c36e274f 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/Validate.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Metafiles/Validators/Validate.cs
@@ -5,6 +5,7 @@
#nullable enable
using System.Drawing;
+using System.Net;
using static Interop;
namespace System.Windows.Forms.Metafiles
@@ -56,6 +57,26 @@ internal static IEmfValidator LineTo(
backgroundMode,
validate);
+ internal static IEmfValidator Rectangle(
+ RECT bounds,
+ Color penColor,
+ Color brushColor,
+ int penWidth = 1,
+ Gdi32.PS penStyle = default,
+ Gdi32.R2 rop2 = Gdi32.R2.COPYPEN,
+ Gdi32.BKMODE backgroundMode = Gdi32.BKMODE.TRANSPARENT,
+ Gdi32.BS brushStyle = default,
+ RectangleValidator.Flags validate = default) => new RectangleValidator(
+ bounds,
+ penColor,
+ brushColor,
+ penWidth,
+ penStyle,
+ rop2,
+ backgroundMode,
+ brushStyle,
+ validate);
+
///
/// Simple wrapper to allow doing an arbitrary action for a given .
///
diff --git a/src/System.Windows.Forms.Primitives/tests/UnitTests/Interop/Gdi32/GetStockObjectTests.cs b/src/System.Windows.Forms.Primitives/tests/UnitTests/Interop/Gdi32/GetStockObjectTests.cs
new file mode 100644
index 00000000000..79cc55cb320
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/UnitTests/Interop/Gdi32/GetStockObjectTests.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+using static Interop;
+
+namespace System.Windows.Forms.Gdi32Tests
+{
+ public class GetStockObjectTests
+ {
+ [Theory]
+ [InlineData((int)Gdi32.StockObject.BLACK_BRUSH, 0x00000000, (uint)Gdi32.BS.SOLID)]
+ [InlineData((int)Gdi32.StockObject.NULL_BRUSH, 0x00000000, (uint)Gdi32.BS.HOLLOW)]
+ [InlineData((int)Gdi32.StockObject.WHITE_BRUSH, 0x00FFFFFF, (uint)Gdi32.BS.SOLID)]
+ public void GetStockBrushes(int id, uint color, uint brushStyle)
+ {
+ Gdi32.HGDIOBJ hgdiobj = Gdi32.GetStockObject((Gdi32.StockObject)id);
+ Assert.False(hgdiobj.IsNull);
+
+ Gdi32.GetObjectW(hgdiobj, out Gdi32.LOGBRUSH logBrush);
+ Assert.Equal(color, logBrush.lbColor);
+ Assert.Equal((Gdi32.BS)brushStyle, logBrush.lbStyle);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ButtonRenderingTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ButtonRenderingTests.cs
index 3acb5bbdb6f..3c9859b2072 100644
--- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ButtonRenderingTests.cs
+++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ButtonRenderingTests.cs
@@ -91,5 +91,34 @@ public unsafe void CaptureButtonOnForm()
var details = emf.RecordsToString();
}
+
+ [WinFormsFact]
+ public unsafe void Button_FlatStyle_WithText_Rectangle()
+ {
+ using Button button = new Button
+ {
+ Text = "Flat Style",
+ FlatStyle = FlatStyle.Flat,
+ };
+
+ using var emf = new EmfScope();
+ DeviceContextState state = new DeviceContextState(emf);
+
+ Rectangle bounds = button.Bounds;
+
+ button.PrintToMetafile(emf);
+
+ emf.Validate(
+ state,
+ Validate.SkipType(Gdi32.EMR.BITBLT),
+ Validate.TextOut("Flat Style"),
+ Validate.Rectangle(
+ new Rectangle(0, 0, button.Width - 2, button.Height - 2),
+ penColor: Color.Black,
+ penStyle: Gdi32.PS.ENDCAP_ROUND,
+ brushColor: Color.Empty, // Color doesn't really matter as we're using a null brush
+ brushStyle: Gdi32.BS.NULL, // Regressed in https://github.com/dotnet/winforms/pull/3667
+ rop2: Gdi32.R2.COPYPEN));
+ }
}
}