File: System\Drawing\Pen.cs
Web Access
Project: src\src\System.Drawing.Common\src\System.Drawing.Common.csproj (System.Drawing.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Drawing.Internal;
 
namespace System.Drawing;
 
/// <summary>
///  Defines an object used to draw lines and curves.
/// </summary>
public sealed unsafe class Pen : MarshalByRefObject, ICloneable, IDisposable, ISystemColorTracker
{
    // Handle to native GDI+ pen object.
    private GpPen* _nativePen;
 
    // GDI+ doesn't understand system colors, so we need to cache the value here.
    private Color _color;
    private bool _immutable;
 
    // Tracks whether the dash style has been changed to something else than Solid during the lifetime of this object.
    private bool _dashStyleWasOrIsNotSolid;
 
    /// <summary>
    ///  Creates a Pen from a native GDI+ object.
    /// </summary>
    private Pen(GpPen* nativePen) => SetNativePen(nativePen);
 
    internal Pen(Color color, bool immutable) : this(color) => _immutable = immutable;
 
    /// <summary>
    ///  Initializes a new instance of the Pen class with the specified <see cref='Color'/>.
    /// </summary>
    public Pen(Color color) : this(color, (float)1.0)
    {
    }
 
    /// <summary>
    ///  Initializes a new instance of the <see cref='Pen'/> class with the specified
    ///  <see cref='Color'/> and <see cref='Width'/>.
    /// </summary>
    public Pen(Color color, float width)
    {
        _color = color;
 
        GpPen* pen;
        PInvoke.GdipCreatePen1((uint)color.ToArgb(), width, (int)GraphicsUnit.World, &pen).ThrowIfFailed();
        SetNativePen(pen);
 
        if (_color.IsSystemColor)
        {
            SystemColorTracker.Add(this);
        }
    }
 
    /// <summary>
    ///  Initializes a new instance of the Pen class with the specified <see cref='Brush'/>.
    /// </summary>
    public Pen(Brush brush) : this(brush, (float)1.0)
    {
    }
 
    /// <summary>
    ///  Initializes a new instance of the <see cref='Pen'/> class with the specified <see cref='Drawing.Brush'/> and width.
    /// </summary>
    public Pen(Brush brush, float width)
    {
        ArgumentNullException.ThrowIfNull(brush);
        GpPen* pen;
        PInvoke.GdipCreatePen2(brush.NativeBrush, width, (int)GraphicsUnit.World, &pen).ThrowIfFailed();
        GC.KeepAlive(brush);
        SetNativePen(pen);
    }
 
    internal void SetNativePen(GpPen* nativePen)
    {
        Debug.Assert(nativePen is not null);
        _nativePen = nativePen;
    }
 
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    internal GpPen* NativePen => _nativePen;
 
    /// <summary>
    ///  Creates an exact copy of this <see cref='Pen'/>.
    /// </summary>
    public object Clone()
    {
        GpPen* clonedPen;
        PInvoke.GdipClonePen(NativePen, &clonedPen).ThrowIfFailed();
        GC.KeepAlive(this);
        return new Pen(clonedPen);
    }
 
    /// <summary>
    ///  Cleans up Windows resources for this <see cref='Pen'/>.
    /// </summary>
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
 
    private void Dispose(bool disposing)
    {
        if (!disposing)
        {
            // If we are finalizing, then we will be unreachable soon. Finalize calls dispose to
            // release resources, so we must make sure that during finalization we are
            // not immutable.
            _immutable = false;
        }
        else if (_immutable)
        {
            throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
        }
 
        if (_nativePen is not null)
        {
            Status status = !Gdip.Initialized ? Status.Ok : PInvoke.GdipDeletePen(NativePen);
            _nativePen = null;
            Debug.Assert(status == Status.Ok, $"GDI+ returned an error status: {status}");
        }
    }
 
    /// <summary>
    ///  Cleans up Windows resources for this <see cref='Pen'/>.
    /// </summary>
    ~Pen() => Dispose(disposing: false);
 
    /// <summary>
    ///  Gets or sets the width of this <see cref='Pen'/>.
    /// </summary>
    public float Width
    {
        get
        {
            float width;
            PInvoke.GdipGetPenWidth(NativePen, &width).ThrowIfFailed();
            GC.KeepAlive(this);
            return width;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenWidth(NativePen, value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Sets the values that determine the style of cap used to end lines drawn by this <see cref='Pen'/>.
    /// </summary>
    public void SetLineCap(LineCap startCap, LineCap endCap, DashCap dashCap)
    {
        if (_immutable)
        {
            throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
        }
 
        PInvoke.GdipSetPenLineCap197819(
            NativePen,
            (GdiPlus.LineCap)startCap,
            (GdiPlus.LineCap)endCap,
            (GdiPlus.DashCap)dashCap).ThrowIfFailed();
 
        GC.KeepAlive(this);
    }
 
    /// <summary>
    ///  Gets or sets the cap style used at the beginning of lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public LineCap StartCap
    {
        get
        {
            LineCap startCap;
            PInvoke.GdipGetPenStartCap(NativePen, (GdiPlus.LineCap*)&startCap).ThrowIfFailed();
            GC.KeepAlive(this);
            return startCap;
        }
        set
        {
            switch (value)
            {
                case LineCap.Flat:
                case LineCap.Square:
                case LineCap.Round:
                case LineCap.Triangle:
                case LineCap.NoAnchor:
                case LineCap.SquareAnchor:
                case LineCap.RoundAnchor:
                case LineCap.DiamondAnchor:
                case LineCap.ArrowAnchor:
                case LineCap.AnchorMask:
                case LineCap.Custom:
                    break;
                default:
                    throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(LineCap));
            }
 
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenStartCap(NativePen, (GdiPlus.LineCap)value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets the cap style used at the end of lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public LineCap EndCap
    {
        get
        {
            LineCap endCap;
            PInvoke.GdipGetPenEndCap(NativePen, (GdiPlus.LineCap*)&endCap).ThrowIfFailed();
            GC.KeepAlive(this);
            return endCap;
        }
        set
        {
            switch (value)
            {
                case LineCap.Flat:
                case LineCap.Square:
                case LineCap.Round:
                case LineCap.Triangle:
                case LineCap.NoAnchor:
                case LineCap.SquareAnchor:
                case LineCap.RoundAnchor:
                case LineCap.DiamondAnchor:
                case LineCap.ArrowAnchor:
                case LineCap.AnchorMask:
                case LineCap.Custom:
                    break;
                default:
                    throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(LineCap));
            }
 
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenEndCap(NativePen, (GdiPlus.LineCap)value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets a custom cap style to use at the beginning of lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public CustomLineCap CustomStartCap
    {
        get
        {
            GpCustomLineCap* lineCap;
            PInvoke.GdipGetPenCustomStartCap(NativePen, &lineCap).ThrowIfFailed();
            GC.KeepAlive(this);
            return CustomLineCap.CreateCustomLineCapObject(lineCap);
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenCustomStartCap(NativePen, value is null ? null : value._nativeCap).ThrowIfFailed();
            GC.KeepAlive(value);
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets a custom cap style to use at the end of lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public CustomLineCap CustomEndCap
    {
        get
        {
            GpCustomLineCap* lineCap;
            PInvoke.GdipGetPenCustomEndCap(NativePen, &lineCap).ThrowIfFailed();
            GC.KeepAlive(this);
            return CustomLineCap.CreateCustomLineCapObject(lineCap);
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenCustomEndCap(NativePen, value is null ? null : value._nativeCap).ThrowIfFailed();
            GC.KeepAlive(value);
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets the cap style used at the beginning or end of dashed lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public DashCap DashCap
    {
        get
        {
            DashCap dashCap;
            PInvoke.GdipGetPenDashCap197819(NativePen, (GdiPlus.DashCap*)&dashCap).ThrowIfFailed();
            GC.KeepAlive(this);
            return dashCap;
        }
        set
        {
            if (value is not DashCap.Flat and not DashCap.Round and not DashCap.Triangle)
            {
                throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DashCap));
            }
 
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenDashCap197819(NativePen, (GdiPlus.DashCap)value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets the join style for the ends of two overlapping lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public LineJoin LineJoin
    {
        get
        {
            LineJoin lineJoin;
            PInvoke.GdipGetPenLineJoin(NativePen, (GdiPlus.LineJoin*)&lineJoin).ThrowIfFailed();
            GC.KeepAlive(this);
            return lineJoin;
        }
        set
        {
            if (value is < LineJoin.Miter or > LineJoin.MiterClipped)
            {
                throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(LineJoin));
            }
 
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenLineJoin(NativePen, (GdiPlus.LineJoin)value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets the limit of the thickness of the join on a mitered corner.
    /// </summary>
    public float MiterLimit
    {
        get
        {
            float miterLimit;
            PInvoke.GdipGetPenMiterLimit(NativePen, &miterLimit).ThrowIfFailed();
            GC.KeepAlive(this);
            return miterLimit;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenMiterLimit(NativePen, value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets the alignment for objects drawn with this <see cref='Pen'/>.
    /// </summary>
    public PenAlignment Alignment
    {
        get
        {
            PenAlignment penMode;
            PInvoke.GdipGetPenMode(NativePen, (GdiPlus.PenAlignment*)&penMode).ThrowIfFailed();
            GC.KeepAlive(this);
            return penMode;
        }
        set
        {
            if (value is < PenAlignment.Center or > PenAlignment.Right)
            {
                throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(PenAlignment));
            }
 
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenMode(NativePen, (GdiPlus.PenAlignment)value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets the geometrical transform for objects drawn with this <see cref='Pen'/>.
    /// </summary>
    public Matrix Transform
    {
        get
        {
            Matrix matrix = new();
            PInvoke.GdipGetPenTransform(NativePen, matrix.NativeMatrix).ThrowIfFailed();
            GC.KeepAlive(this);
            return matrix;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            ArgumentNullException.ThrowIfNull(value);
 
            PInvoke.GdipSetPenTransform(NativePen, value.NativeMatrix).ThrowIfFailed();
            GC.KeepAlive(value);
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Resets the geometric transform for this <see cref='Pen'/> to identity.
    /// </summary>
    public void ResetTransform()
    {
        PInvoke.GdipResetPenTransform(NativePen).ThrowIfFailed();
        GC.KeepAlive(this);
    }
 
    /// <summary>
    ///  Multiplies the transform matrix for this <see cref='Pen'/> by the specified <see cref='Matrix'/>.
    /// </summary>
    public void MultiplyTransform(Matrix matrix) => MultiplyTransform(matrix, MatrixOrder.Prepend);
 
    /// <summary>
    ///  Multiplies the transform matrix for this <see cref='Pen'/> by the specified <see cref='Matrix'/> in the specified order.
    /// </summary>
    public void MultiplyTransform(Matrix matrix, MatrixOrder order)
    {
        ArgumentNullException.ThrowIfNull(matrix);
 
        if (matrix.NativeMatrix is null)
        {
            // Disposed matrices should result in a no-op.
            return;
        }
 
        PInvoke.GdipMultiplyPenTransform(NativePen, matrix.NativeMatrix, (GdiPlus.MatrixOrder)order).ThrowIfFailed();
        GC.KeepAlive(matrix);
        GC.KeepAlive(this);
    }
 
    /// <summary>
    ///  Translates the local geometrical transform by the specified dimensions. This method prepends the translation
    ///  to the transform.
    /// </summary>
    public void TranslateTransform(float dx, float dy) => TranslateTransform(dx, dy, MatrixOrder.Prepend);
 
    /// <summary>
    ///  Translates the local geometrical transform by the specified dimensions in the specified order.
    /// </summary>
    public void TranslateTransform(float dx, float dy, MatrixOrder order)
    {
        PInvoke.GdipTranslatePenTransform(NativePen, dx, dy, (GdiPlus.MatrixOrder)order).ThrowIfFailed();
        GC.KeepAlive(this);
    }
 
    /// <summary>
    ///  Scales the local geometric transform by the specified amounts. This method prepends the scaling matrix to the transform.
    /// </summary>
    public void ScaleTransform(float sx, float sy) => ScaleTransform(sx, sy, MatrixOrder.Prepend);
 
    /// <summary>
    ///  Scales the local geometric transform by the specified amounts in the specified order.
    /// </summary>
    public void ScaleTransform(float sx, float sy, MatrixOrder order)
    {
        PInvoke.GdipScalePenTransform(NativePen, sx, sy, (GdiPlus.MatrixOrder)order).ThrowIfFailed();
        GC.KeepAlive(this);
    }
 
    /// <summary>
    ///  Rotates the local geometric transform by the specified amount. This method prepends the rotation to the transform.
    /// </summary>
    public void RotateTransform(float angle) => RotateTransform(angle, MatrixOrder.Prepend);
 
    /// <summary>
    ///  Rotates the local geometric transform by the specified amount in the specified order.
    /// </summary>
    public void RotateTransform(float angle, MatrixOrder order)
    {
        PInvoke.GdipRotatePenTransform(NativePen, angle, (GdiPlus.MatrixOrder)order).ThrowIfFailed();
        GC.KeepAlive(this);
    }
 
    private void InternalSetColor(Color value)
    {
        PInvoke.GdipSetPenColor(NativePen, (uint)_color.ToArgb()).ThrowIfFailed();
        GC.KeepAlive(this);
        _color = value;
    }
 
    /// <summary>
    ///  Gets the style of lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public Drawing2D.PenType PenType
    {
        get
        {
            GdiPlus.PenType type;
            PInvoke.GdipGetPenFillType(NativePen, &type).ThrowIfFailed();
            GC.KeepAlive(this);
            return (Drawing2D.PenType)type;
        }
    }
 
    /// <summary>
    ///  Gets or sets the color of this <see cref='Pen'/>.
    /// </summary>
    public Color Color
    {
        get
        {
            if (_color == Color.Empty)
            {
                if (PenType != Drawing2D.PenType.SolidColor)
                {
                    throw new ArgumentException(SR.GdiplusInvalidParameter);
                }
 
                ARGB color;
                PInvoke.GdipGetPenColor(NativePen, (uint*)&color).ThrowIfFailed();
                GC.KeepAlive(this);
                _color = color;
            }
 
            // GDI+ doesn't understand system colors, so we can't use GdipGetPenColor in the general case.
            return _color;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            if (value != _color)
            {
                Color oldColor = _color;
                _color = value;
                InternalSetColor(value);
 
                // NOTE: We never remove pens from the active list, so if someone is
                // changing their pen colors a lot, this could be a problem.
                if (value.IsSystemColor && !oldColor.IsSystemColor)
                {
                    SystemColorTracker.Add(this);
                }
            }
        }
    }
 
    /// <summary>
    ///  Gets or sets the <see cref='Drawing.Brush'/> that determines attributes of this <see cref='Pen'/>.
    /// </summary>
    public Brush Brush
    {
        get
        {
            Brush? brush = null;
 
            switch (PenType)
            {
                case Drawing2D.PenType.SolidColor:
                    brush = new SolidBrush((GpSolidFill*)GetNativeBrush());
                    break;
 
                case Drawing2D.PenType.HatchFill:
                    brush = new HatchBrush((GpHatch*)GetNativeBrush());
                    break;
 
                case Drawing2D.PenType.TextureFill:
                    brush = new TextureBrush((GpTexture*)GetNativeBrush());
                    break;
 
                case Drawing2D.PenType.PathGradient:
                    brush = new PathGradientBrush((GpPathGradient*)GetNativeBrush());
                    break;
 
                case Drawing2D.PenType.LinearGradient:
                    brush = new LinearGradientBrush((GpLineGradient*)GetNativeBrush());
                    break;
 
                default:
                    break;
            }
 
            return brush!;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            ArgumentNullException.ThrowIfNull(value);
            PInvoke.GdipSetPenBrushFill(NativePen, value.Pointer()).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    private GpBrush* GetNativeBrush()
    {
        GpBrush* nativeBrush;
        PInvoke.GdipGetPenBrushFill(NativePen, &nativeBrush).ThrowIfFailed();
        GC.KeepAlive(this);
        return nativeBrush;
    }
 
    /// <summary>
    ///  Gets or sets the style used for dashed lines drawn with this <see cref='Pen'/>.
    /// </summary>
    public DashStyle DashStyle
    {
        get
        {
            DashStyle dashStyle;
            PInvoke.GdipGetPenDashStyle(NativePen, (GdiPlus.DashStyle*)&dashStyle).ThrowIfFailed();
            GC.KeepAlive(this);
            return dashStyle;
        }
        set
        {
            if (value is < DashStyle.Solid or > DashStyle.Custom)
            {
                throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DashStyle));
            }
 
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenDashStyle(NativePen, (GdiPlus.DashStyle)value).ThrowIfFailed();
            GC.KeepAlive(this);
 
            // If we just set the pen style to Custom without defining the custom dash pattern,
            // make sure that we can return a valid value.
            if (value == DashStyle.Custom)
            {
                EnsureValidDashPattern();
            }
 
            if (value != DashStyle.Solid)
            {
                _dashStyleWasOrIsNotSolid = true;
            }
        }
    }
 
    /// <summary>
    ///  This method is called after the user sets the pen's dash style to custom. Here, we make sure that there
    ///  is a default value set for the custom pattern.
    /// </summary>
    private void EnsureValidDashPattern()
    {
        int count;
        PInvoke.GdipGetPenDashCount(NativePen, &count);
        GC.KeepAlive(this);
 
        if (count == 0)
        {
            // Set to a solid pattern.
            DashPattern = [1];
        }
    }
 
    /// <summary>
    ///  Gets or sets the distance from the start of a line to the beginning of a dash pattern.
    /// </summary>
    public float DashOffset
    {
        get
        {
            float dashOffset;
            PInvoke.GdipGetPenDashOffset(NativePen, &dashOffset).ThrowIfFailed();
            GC.KeepAlive(this);
            return dashOffset;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            PInvoke.GdipSetPenDashOffset(NativePen, value).ThrowIfFailed();
            GC.KeepAlive(this);
        }
    }
 
    /// <summary>
    ///  Gets or sets an array of custom dashes and spaces. The dashes are made up of line segments.
    /// </summary>
    public float[] DashPattern
    {
        get
        {
            int count;
            PInvoke.GdipGetPenDashCount(NativePen, &count).ThrowIfFailed();
            GC.KeepAlive(this);
 
            float[] pattern;
 
            // Don't call GdipGetPenDashArray with a 0 count
            if (count > 0)
            {
                pattern = new float[count];
                fixed (float* p = pattern)
                {
                    PInvoke.GdipGetPenDashArray(NativePen, p, count).ThrowIfFailed();
                }
            }
            else if (DashStyle == DashStyle.Solid && !_dashStyleWasOrIsNotSolid)
            {
                // Most likely we're replicating an existing System.Drawing bug here, it doesn't make much sense to
                // ask for a dash pattern when using a solid dash.
                throw new InvalidOperationException();
            }
            else if (DashStyle == DashStyle.Solid)
            {
                pattern = [];
            }
            else
            {
                // Special case (not handled inside GDI+)
                pattern = [1.0f];
            }
 
            GC.KeepAlive(this);
            return pattern;
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            if (value is null || value.Length == 0)
            {
                throw new ArgumentException(SR.InvalidDashPattern);
            }
 
            fixed (float* f = value)
            {
                PInvoke.GdipSetPenDashArray(NativePen, f, value.Length).ThrowIfFailed();
                GC.KeepAlive(this);
            }
        }
    }
 
    /// <summary>
    ///  Gets or sets an array of custom dashes and spaces. The dashes are made up of line segments.
    /// </summary>
    public float[] CompoundArray
    {
        get
        {
            int count;
            PInvoke.GdipGetPenCompoundCount(NativePen, &count).ThrowIfFailed();
 
            if (count == 0)
            {
                return [];
            }
 
            float[] array = new float[count];
            fixed (float* f = array)
            {
                PInvoke.GdipGetPenCompoundArray(NativePen, f, count).ThrowIfFailed();
                GC.KeepAlive(this);
                return array;
            }
        }
        set
        {
            if (_immutable)
            {
                throw new ArgumentException(SR.Format(SR.CantChangeImmutableObjects, nameof(Pen)));
            }
 
            ArgumentNullException.ThrowIfNull(value);
            fixed (float* f = value)
            {
                PInvoke.GdipSetPenCompoundArray(NativePen, f, value.Length).ThrowIfFailed();
                GC.KeepAlive(this);
            }
        }
    }
 
    void ISystemColorTracker.OnSystemColorChanged()
    {
        if (NativePen is not null)
        {
            InternalSetColor(_color);
        }
    }
}