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

// Graphics class testing unit
//
// Authors:
//   Jordi Mas, jordi@ximian.com
//   Sebastien Pouliot  <sebastien@ximian.com>
//
// Copyright (C) 2005-2008 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;

namespace MonoTests.System.Drawing;

public class GraphicsTest : IDisposable
{
    private RectangleF[] _rects;
    private readonly Font _font;

    public GraphicsTest()
    {
        try
        {
            _font = new Font("Arial", 12);
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _font?.Dispose();
    }

    private static bool IsEmptyBitmap(Bitmap bitmap, out int x, out int y)
    {
        bool result = true;
        int empty = Color.Empty.ToArgb();
        for (y = 0; y < bitmap.Height; y++)
        {
            for (x = 0; x < bitmap.Width; x++)
            {
                if (bitmap.GetPixel(x, y).ToArgb() != empty)
                    return false;
            }
        }

        x = -1;
        y = -1;
        return result;
    }

    private static void CheckForEmptyBitmap(Bitmap bitmap)
    {
        if (!IsEmptyBitmap(bitmap, out int x, out int y))
            Assert.Fail($"Position {x},{y}");
    }

    private static void CheckForNonEmptyBitmap(Bitmap bitmap)
    {
        if (IsEmptyBitmap(bitmap, out int _, out int _))
            Assert.True(false);
    }

    private static void AssertEquals(string msg, object expected, object actual)
    {
        actual.Should().Be(expected, msg);
    }

    private static void AssertEquals(string msg, double expected, double actual, int precision)
    {
        actual.Should().BeApproximately(expected, precision, msg);
    }

    [Fact]
    public void DefaultProperties()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        using Region r = new();
        Assert.Equal(r.GetBounds(g), g.ClipBounds);
        Assert.Equal(CompositingMode.SourceOver, g.CompositingMode);
        Assert.Equal(CompositingQuality.Default, g.CompositingQuality);
        Assert.Equal(InterpolationMode.Bilinear, g.InterpolationMode);
        Assert.Equal(1, g.PageScale);
        Assert.Equal(GraphicsUnit.Display, g.PageUnit);
        Assert.Equal(PixelOffsetMode.Default, g.PixelOffsetMode);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);
        Assert.Equal(SmoothingMode.None, g.SmoothingMode);
        Assert.Equal(TextRenderingHint.SystemDefault, g.TextRenderingHint);
    }

    [Fact]
    public void SetGetProperties()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        g.CompositingMode = CompositingMode.SourceCopy;
        g.CompositingQuality = CompositingQuality.GammaCorrected;
        g.InterpolationMode = InterpolationMode.HighQualityBilinear;
        g.PageScale = 2;
        g.PageUnit = GraphicsUnit.Inch;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.RenderingOrigin = new Point(10, 20);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextRenderingHint = TextRenderingHint.SystemDefault;

        // Clipping set/get tested in clipping functions
        Assert.Equal(CompositingMode.SourceCopy, g.CompositingMode);
        Assert.Equal(CompositingQuality.GammaCorrected, g.CompositingQuality);
        Assert.Equal(InterpolationMode.HighQualityBilinear, g.InterpolationMode);
        Assert.Equal(2, g.PageScale);
        Assert.Equal(GraphicsUnit.Inch, g.PageUnit);
        Assert.Equal(PixelOffsetMode.Half, g.PixelOffsetMode);
        Assert.Equal(new Point(10, 20), g.RenderingOrigin);
        Assert.Equal(SmoothingMode.AntiAlias, g.SmoothingMode);
        Assert.Equal(TextRenderingHint.SystemDefault, g.TextRenderingHint);
    }

    // Properties
    [Fact]
    public void Clip()
    {
        RectangleF[] rects;
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        g.Clip = new Region(new Rectangle(50, 40, 210, 220));
        rects = g.Clip.GetRegionScans(new Matrix());

        Assert.Single(rects);
        Assert.Equal(50, rects[0].X);
        Assert.Equal(40, rects[0].Y);
        Assert.Equal(210, rects[0].Width);
        Assert.Equal(220, rects[0].Height);
    }

    [Fact]
    public void Clip_NotAReference()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.True(g.Clip.IsInfinite(g));
        g.Clip.IsEmpty(g);
        Assert.False(g.Clip.IsEmpty(g));
        Assert.True(g.Clip.IsInfinite(g));
    }

    [Fact]
    public void ExcludeClip()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        g.Clip = new Region(new RectangleF(10, 10, 100, 100));
        g.ExcludeClip(new Rectangle(40, 60, 100, 20));
        _rects = g.Clip.GetRegionScans(new Matrix());

        Assert.Equal(3, _rects.Length);

        Assert.Equal(10, _rects[0].X);
        Assert.Equal(10, _rects[0].Y);
        Assert.Equal(100, _rects[0].Width);
        Assert.Equal(50, _rects[0].Height);

        Assert.Equal(10, _rects[1].X);
        Assert.Equal(60, _rects[1].Y);
        Assert.Equal(30, _rects[1].Width);
        Assert.Equal(20, _rects[1].Height);

        Assert.Equal(10, _rects[2].X);
        Assert.Equal(80, _rects[2].Y);
        Assert.Equal(100, _rects[2].Width);
        Assert.Equal(30, _rects[2].Height);
    }

    [Fact]
    public void IntersectClip()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        g.Clip = new Region(new RectangleF(260, 30, 60, 80));
        g.IntersectClip(new Rectangle(290, 40, 60, 80));
        _rects = g.Clip.GetRegionScans(new Matrix());

        Assert.Single(_rects);

        Assert.Equal(290, _rects[0].X);
        Assert.Equal(40, _rects[0].Y);
        Assert.Equal(30, _rects[0].Width);
        Assert.Equal(70, _rects[0].Height);
    }

    [Fact]
    public void ResetClip()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        g.Clip = new Region(new RectangleF(260, 30, 60, 80));
        g.IntersectClip(new Rectangle(290, 40, 60, 80));
        g.ResetClip();
        _rects = g.Clip.GetRegionScans(new Matrix());

        Assert.Single(_rects);

        Assert.Equal(-4194304, _rects[0].X);
        Assert.Equal(-4194304, _rects[0].Y);
        Assert.Equal(8388608, _rects[0].Width);
        Assert.Equal(8388608, _rects[0].Height);
    }

    [Fact]
    public void SetClip()
    {
        RectangleF[] rects;
        using Bitmap bmp = new(200, 200);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            // Region
            g.SetClip(new Region(new Rectangle(50, 40, 210, 220)), CombineMode.Replace);
            rects = g.Clip.GetRegionScans(new Matrix());
            Assert.Single(rects);
            Assert.Equal(50, rects[0].X);
            Assert.Equal(40, rects[0].Y);
            Assert.Equal(210, rects[0].Width);
            Assert.Equal(220, rects[0].Height);
        }

        // RectangleF
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.SetClip(new RectangleF(50, 40, 210, 220));
            rects = g.Clip.GetRegionScans(new Matrix());
            Assert.Single(rects);
            Assert.Equal(50, rects[0].X);
            Assert.Equal(40, rects[0].Y);
            Assert.Equal(210, rects[0].Width);
            Assert.Equal(220, rects[0].Height);
        }

        // Rectangle
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.SetClip(new Rectangle(50, 40, 210, 220));
            rects = g.Clip.GetRegionScans(new Matrix());
            Assert.Single(rects);
            Assert.Equal(50, rects[0].X);
            Assert.Equal(40, rects[0].Y);
            Assert.Equal(210, rects[0].Width);
            Assert.Equal(220, rects[0].Height);
        }
    }

    [Fact]
    public void SetSaveReset()
    {
        using Bitmap bmp = new(200, 200);
        using Graphics g = Graphics.FromImage(bmp);
        GraphicsState state_default, state_modified;

        state_default = g.Save(); // Default

        g.CompositingMode = CompositingMode.SourceCopy;
        g.CompositingQuality = CompositingQuality.GammaCorrected;
        g.InterpolationMode = InterpolationMode.HighQualityBilinear;
        g.PageScale = 2;
        g.PageUnit = GraphicsUnit.Inch;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.Clip = new Region(new Rectangle(0, 0, 100, 100));
        g.RenderingOrigin = new Point(10, 20);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

        state_modified = g.Save(); // Modified

        g.CompositingMode = CompositingMode.SourceOver;
        g.CompositingQuality = CompositingQuality.Default;
        g.InterpolationMode = InterpolationMode.Bilinear;
        g.PageScale = 5;
        g.PageUnit = GraphicsUnit.Display;
        g.PixelOffsetMode = PixelOffsetMode.Default;
        g.Clip = new Region(new Rectangle(1, 2, 20, 25));
        g.RenderingOrigin = new Point(5, 6);
        g.SmoothingMode = SmoothingMode.None;
        g.TextRenderingHint = TextRenderingHint.SystemDefault;

        g.Restore(state_modified);

        Assert.Equal(CompositingMode.SourceCopy, g.CompositingMode);
        Assert.Equal(CompositingQuality.GammaCorrected, g.CompositingQuality);
        Assert.Equal(InterpolationMode.HighQualityBilinear, g.InterpolationMode);
        Assert.Equal(2, g.PageScale);
        Assert.Equal(GraphicsUnit.Inch, g.PageUnit);
        Assert.Equal(PixelOffsetMode.Half, g.PixelOffsetMode);
        Assert.Equal(new Point(10, 20), g.RenderingOrigin);
        Assert.Equal(SmoothingMode.AntiAlias, g.SmoothingMode);
        Assert.Equal(TextRenderingHint.ClearTypeGridFit, g.TextRenderingHint);
        Assert.Equal(0, (int)g.ClipBounds.X);
        Assert.Equal(0, (int)g.ClipBounds.Y);

        g.Restore(state_default);

        Assert.Equal(CompositingMode.SourceOver, g.CompositingMode);
        Assert.Equal(CompositingQuality.Default, g.CompositingQuality);
        Assert.Equal(InterpolationMode.Bilinear, g.InterpolationMode);
        Assert.Equal(1, g.PageScale);
        Assert.Equal(GraphicsUnit.Display, g.PageUnit);
        Assert.Equal(PixelOffsetMode.Default, g.PixelOffsetMode);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);
        Assert.Equal(SmoothingMode.None, g.SmoothingMode);
        Assert.Equal(TextRenderingHint.SystemDefault, g.TextRenderingHint);

        Region r = new();
        Assert.Equal(r.GetBounds(g), g.ClipBounds);
    }

    [Fact]
    public void LoadIndexed_BmpFile()
    {
        // Tests that we can load an indexed file, but...
        string sInFile = Helpers.GetTestBitmapPath("almogaver1bit.bmp");
        // note: file is misnamed (it's a 4bpp bitmap)
        using Image img = Image.FromFile(sInFile);
        Assert.Equal(PixelFormat.Format4bppIndexed, img.PixelFormat);
        Exception exception = AssertExtensions.Throws<ArgumentException, Exception>(() => Graphics.FromImage(img));
        if (exception is ArgumentException argumentException)
            Assert.Equal("image", argumentException.ParamName);
    }

    private class BitmapAndGraphics : IDisposable
    {
        private readonly Bitmap _bitmap;
        public Graphics Graphics { get; }
        public BitmapAndGraphics(int width, int height)
        {
            _bitmap = new Bitmap(width, height);
            Graphics = Graphics.FromImage(_bitmap);
            Graphics.Clip = new Region(new Rectangle(0, 0, width, height));
        }

        public void Dispose() { Graphics.Dispose(); _bitmap.Dispose(); }
    }

    private static void Compare(string msg, RectangleF b1, RectangleF b2)
    {
        AssertEquals(msg + ".compare.X", b1.X, b2.X);
        AssertEquals(msg + ".compare.Y", b1.Y, b2.Y);
        AssertEquals(msg + ".compare.Width", b1.Width, b2.Width);
        AssertEquals(msg + ".compare.Height", b1.Height, b2.Height);
    }

    [Fact]
    public void Clip_GetBounds()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        RectangleF bounds = g.Clip.GetBounds(g);
        Assert.Equal(0, bounds.X);
        Assert.Equal(0, bounds.Y);
        Assert.Equal(16, bounds.Width);
        Assert.Equal(16, bounds.Height);
        Assert.True(g.Transform.IsIdentity);
    }

    [Fact]
    public void Clip_TranslateTransform()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.TranslateTransform(12.22f, 10.10f);
        RectangleF bounds = g.Clip.GetBounds(g);
        Compare("translate", bounds, g.ClipBounds);
        Assert.Equal(-12.2200003f, bounds.X);
        Assert.Equal(-10.1000004f, bounds.Y);
        Assert.Equal(16, bounds.Width);
        Assert.Equal(16, bounds.Height);
        float[] elements = g.Transform.Elements;
        Assert.Equal(1, elements[0]);
        Assert.Equal(0, elements[1]);
        Assert.Equal(0, elements[2]);
        Assert.Equal(1, elements[3]);
        Assert.Equal(12.2200003f, elements[4]);
        Assert.Equal(10.1000004f, elements[5]);

        g.ResetTransform();
        bounds = g.Clip.GetBounds(g);
        Compare("reset", bounds, g.ClipBounds);
        Assert.Equal(0, bounds.X);
        Assert.Equal(0, bounds.Y);
        Assert.Equal(16, bounds.Width);
        Assert.Equal(16, bounds.Height);
        Assert.True(g.Transform.IsIdentity);
    }

    [Fact]
    public void Transform_NonInvertibleMatrix()
    {
        using Matrix matrix = new(123, 24, 82, 16, 47, 30);
        using BitmapAndGraphics b = new(16, 16);
        Assert.False(matrix.IsInvertible);

        var g = b.Graphics;
        Assert.Throws<ArgumentException>(() => g.Transform = matrix);
    }

    [Fact]
    public void Multiply_NonInvertibleMatrix()
    {
        using Matrix matrix = new(123, 24, 82, 16, 47, 30);
        using BitmapAndGraphics b = new(16, 16);
        Assert.False(matrix.IsInvertible);

        var g = b.Graphics;
        Assert.Throws<ArgumentException>(() => g.MultiplyTransform(matrix));
    }

    private static void CheckBounds(string msg, RectangleF bounds, float x, float y, float w, float h)
    {
        AssertEquals(msg + ".X", x, bounds.X, 1);
        AssertEquals(msg + ".Y", y, bounds.Y, 1);
        AssertEquals(msg + ".Width", w, bounds.Width, 1);
        AssertEquals(msg + ".Height", h, bounds.Height, 1);
    }

    [Fact]
    public void ClipBounds()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        CheckBounds("graphics.ClipBounds", g.ClipBounds, 0, 0, 16, 16);
        CheckBounds("graphics.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 16);

        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        CheckBounds("clip.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        CheckBounds("clip.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void ClipBounds_Rotate()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        g.RotateTransform(90);
        CheckBounds("rotate.ClipBounds", g.ClipBounds, 0, -8, 8, 8);
        CheckBounds("rotate.Clip.GetBounds", g.Clip.GetBounds(g), 0, -8, 8, 8);

        g.Transform = new Matrix();
        CheckBounds("identity.ClipBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
        CheckBounds("identity.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void ClipBounds_Scale()
    {
        RectangleF clip = new Rectangle(0, 0, 8, 8);
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Clip = new Region(clip);
        g.ScaleTransform(0.25f, 0.5f);
        CheckBounds("scale.ClipBounds", g.ClipBounds, 0, 0, 32, 16);
        CheckBounds("scale.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 32, 16);

        g.SetClip(clip);
        CheckBounds("setclip.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        CheckBounds("setclip.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void ClipBounds_Translate()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        using Region clone = g.Clip.Clone();
        g.TranslateTransform(8, 8);
        CheckBounds("translate.ClipBounds", g.ClipBounds, -8, -8, 8, 8);
        CheckBounds("translate.Clip.GetBounds", g.Clip.GetBounds(g), -8, -8, 8, 8);

        g.SetClip(clone, CombineMode.Replace);
        CheckBounds("setclip.ClipBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
        CheckBounds("setclip.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void ClipBounds_Transform_Translation()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        g.Transform = new Matrix(1, 0, 0, 1, 8, 8);
        CheckBounds("transform.ClipBounds", g.ClipBounds, -8, -8, 8, 8);
        CheckBounds("transform.Clip.GetBounds", g.Clip.GetBounds(g), -8, -8, 8, 8);

        g.ResetTransform();
        CheckBounds("reset.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        CheckBounds("reset.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void ClipBounds_Transform_Scale()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        g.Transform = new Matrix(0.5f, 0, 0, 0.25f, 0, 0);
        CheckBounds("scale.ClipBounds", g.ClipBounds, 0, 0, 16, 32);
        CheckBounds("scale.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 32);

        g.ResetClip();
        // see next test for ClipBounds
        CheckBounds("resetclip.Clip.GetBounds", g.Clip.GetBounds(g), -4194304, -4194304, 8388608, 8388608);
        Assert.True(g.Clip.IsInfinite(g));
    }

    [Fact]
    public void ClipBounds_Multiply()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        g.Transform = new Matrix(1, 0, 0, 1, 8, 8);
        g.MultiplyTransform(g.Transform);
        CheckBounds("multiply.ClipBounds", g.ClipBounds, -16, -16, 8, 8);
        CheckBounds("multiply.Clip.GetBounds", g.Clip.GetBounds(g), -16, -16, 8, 8);

        g.ResetTransform();
        CheckBounds("reset.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        CheckBounds("reset.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void ClipBounds_Cumulative_Effects()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        CheckBounds("graphics.ClipBounds", g.ClipBounds, 0, 0, 16, 16);
        CheckBounds("graphics.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 16);

        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        CheckBounds("clip.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        CheckBounds("clip.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);

        g.RotateTransform(90);
        CheckBounds("rotate.ClipBounds", g.ClipBounds, 0, -8, 8, 8);
        CheckBounds("rotate.Clip.GetBounds", g.Clip.GetBounds(g), 0, -8, 8, 8);

        g.ScaleTransform(0.25f, 0.5f);
        CheckBounds("scale.ClipBounds", g.ClipBounds, 0, -16, 32, 16);
        CheckBounds("scale.Clip.GetBounds", g.Clip.GetBounds(g), 0, -16, 32, 16);

        g.TranslateTransform(8, 8);
        CheckBounds("translate.ClipBounds", g.ClipBounds, -8, -24, 32, 16);
        CheckBounds("translate.Clip.GetBounds", g.Clip.GetBounds(g), -8, -24, 32, 16);

        g.MultiplyTransform(g.Transform);
        CheckBounds("multiply.ClipBounds", g.ClipBounds, -104, -56, 64, 64);
        CheckBounds("multiply.Clip.GetBounds", g.Clip.GetBounds(g), -104, -56, 64, 64);

        g.ResetTransform();
        CheckBounds("reset.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        CheckBounds("reset.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);
    }

    [Fact]
    public void Clip_TranslateTransform_BoundsChange()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        CheckBounds("graphics.ClipBounds", g.ClipBounds, 0, 0, 16, 16);
        CheckBounds("graphics.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 16);
        g.TranslateTransform(-16, -16);
        CheckBounds("translated.ClipBounds", g.ClipBounds, 16, 16, 16, 16);
        CheckBounds("translated.Clip.GetBounds", g.Clip.GetBounds(g), 16, 16, 16, 16);

        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        // ClipBounds isn't affected by a previous translation
        CheckBounds("rectangle.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        // Clip.GetBounds isn't affected by a previous translation
        CheckBounds("rectangle.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);

        g.ResetTransform();
        CheckBounds("reseted.ClipBounds", g.ClipBounds, -16, -16, 8, 8);
        CheckBounds("reseted.Clip.GetBounds", g.Clip.GetBounds(g), -16, -16, 8, 8);
    }

    [Fact]
    public void Clip_RotateTransform_BoundsChange()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        CheckBounds("graphics.ClipBounds", g.ClipBounds, 0, 0, 16, 16);
        CheckBounds("graphics.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 16);
        // we select a "simple" angle because the region will be converted into
        // a bitmap (well for libgdiplus) and we would lose precision after that
        g.RotateTransform(90);
        CheckBounds("rotated.ClipBounds", g.ClipBounds, 0, -16, 16, 16);
        CheckBounds("rotated.Clip.GetBounds", g.Clip.GetBounds(g), 0, -16, 16, 16);
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        // ClipBounds isn't affected by a previous rotation (90)
        CheckBounds("rectangle.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        // Clip.GetBounds isn't affected by a previous rotation
        CheckBounds("rectangle.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);

        g.ResetTransform();
        CheckBounds("reseted.ClipBounds", g.ClipBounds, -8, 0, 8, 8);
        CheckBounds("reseted.Clip.GetBounds", g.Clip.GetBounds(g), -8, 0, 8, 8);
    }

    [Fact]
    public void Clip_ScaleTransform_NoBoundsChange()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        CheckBounds("graphics.ClipBounds", g.ClipBounds, 0, 0, 16, 16);
        CheckBounds("graphics.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 16);
        g.ScaleTransform(2, 0.5f);
        CheckBounds("scaled.ClipBounds", g.ClipBounds, 0, 0, 8, 32);
        CheckBounds("scaled.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 32);
        g.Clip = new Region(new Rectangle(0, 0, 8, 8));
        // ClipBounds isn't affected by a previous scaling
        CheckBounds("rectangle.ClipBounds", g.ClipBounds, 0, 0, 8, 8);
        // Clip.GetBounds isn't affected by a previous scaling
        CheckBounds("rectangle.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 8, 8);

        g.ResetTransform();
        CheckBounds("reseted.ClipBounds", g.ClipBounds, 0, 0, 16, 4);
        CheckBounds("reseted.Clip.GetBounds", g.Clip.GetBounds(g), 0, 0, 16, 4);
    }

    [Fact]
    public void ScaleTransform_X0()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        Assert.Throws<ArgumentException>(() => g.ScaleTransform(0, 1));
    }

    [Fact]
    public void ScaleTransform_Y0()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        Assert.Throws<ArgumentException>(() => g.ScaleTransform(1, 0));
    }

    [Fact]
    public void TranslateTransform_Order()
    {
        using BitmapAndGraphics b = new(16, 16);
        var g = b.Graphics;
        g.Transform = new Matrix(1, 2, 3, 4, 5, 6);
        g.TranslateTransform(3, -3);
        float[] elements = g.Transform.Elements;
        Assert.Equal(1, elements[0]);
        Assert.Equal(2, elements[1]);
        Assert.Equal(3, elements[2]);
        Assert.Equal(4, elements[3]);
        Assert.Equal(-1, elements[4]);
        Assert.Equal(0, elements[5]);

        g.Transform = new Matrix(1, 2, 3, 4, 5, 6);
        g.TranslateTransform(3, -3, MatrixOrder.Prepend);
        elements = g.Transform.Elements;
        Assert.Equal(1, elements[0]);
        Assert.Equal(2, elements[1]);
        Assert.Equal(3, elements[2]);
        Assert.Equal(4, elements[3]);
        Assert.Equal(-1, elements[4]);
        Assert.Equal(0, elements[5]);

        g.Transform = new Matrix(1, 2, 3, 4, 5, 6);
        g.TranslateTransform(3, -3, MatrixOrder.Append);
        elements = g.Transform.Elements;
        Assert.Equal(1, elements[0]);
        Assert.Equal(2, elements[1]);
        Assert.Equal(3, elements[2]);
        Assert.Equal(4, elements[3]);
        Assert.Equal(8, elements[4]);
        Assert.Equal(3, elements[5]);
    }

    private static readonly PointF[] s_smallCurveF = [new(0, 0), new(15, 5), new(5, 15)];
    private static readonly Point[] s_tooSmallCurve = [new(0, 0), new(15, 5)];
    private static readonly PointF[] s_largeCurveF = [new(0, 0), new(15, 5), new(5, 15), new(0, 20)];

    [Fact]
    public void DrawCurve_NotEnoughPoints()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        CheckForEmptyBitmap(bitmap);
        g.DrawCurve(Pens.Black, s_tooSmallCurve, 0.5f);
        CheckForNonEmptyBitmap(bitmap);
        // so a "curve" can be drawn with less than 3 points!
        // actually I used to call that a line... (and it's not related to tension)
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawCurve_SinglePoint()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.DrawCurve(Pens.Black, [new(10, 10)], 0.5f));

        // a single point isn't enough
    }

    [Fact]
    public void DrawCurve3_NotEnoughPoints()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.DrawCurve(Pens.Black, s_tooSmallCurve, 0, 2, 0.5f));

        // aha, this is API dependent
    }

    [Fact]
    public void DrawCurve_NegativeTension()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        // documented as bigger (or equals) to 0
        g.DrawCurve(Pens.Black, s_smallCurveF, -0.9f);
        CheckForNonEmptyBitmap(bitmap);
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawCurve_PositiveTension()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        g.DrawCurve(Pens.Black, s_smallCurveF, 0.9f);
        // this is not the same as -1
        CheckForNonEmptyBitmap(bitmap);
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawCurve_ZeroSegments()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.DrawCurve(Pens.Black, s_smallCurveF, 0, 0));
    }

    [Fact]
    public void DrawCurve_NegativeSegments()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.DrawCurve(Pens.Black, s_smallCurveF, 0, -1));
    }

    [Fact]
    public void DrawCurve_OffsetTooLarge()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        // starting offset 1 doesn't give 3 points to make a curve
        Assert.Throws<ArgumentException>(() => g.DrawCurve(Pens.Black, s_smallCurveF, 1, 2));

        // and in this case 2 points aren't enough to draw something
    }

    [Fact]
    public void DrawCurve_Offset_0()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        g.DrawCurve(Pens.Black, s_largeCurveF, 0, 2, 0.5f);
        CheckForNonEmptyBitmap(bitmap);
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawCurve_Offset_1()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        g.DrawCurve(Pens.Black, s_largeCurveF, 1, 2, 0.5f);
        CheckForNonEmptyBitmap(bitmap);
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawCurve_Offset_2()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        // it works even with two points because we know the previous ones
        g.DrawCurve(Pens.Black, s_largeCurveF, 2, 1, 0.5f);
        CheckForNonEmptyBitmap(bitmap);
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawRectangle_Negative()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using Pen pen = new(Color.Red);
        g.DrawRectangle(pen, 5, 5, -10, -10);
        g.DrawRectangle(pen, 0.0f, 0.0f, 5.0f, -10.0f);
        g.DrawRectangle(pen, new Rectangle(15, 0, -10, 5));
        CheckForEmptyBitmap(bitmap);
        pen.Dispose();
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void DrawRectangles_Negative()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using Pen pen = new(Color.Red);
        Rectangle[] rects = [
            new(5, 5, -10, -10),
                new(0, 0, 5, -10)
        ];
        RectangleF[] rectf = [
            new(0.0f, 5.0f, -10.0f, -10.0f),
                new(15.0f, 0.0f, -10.0f, 5.0f)
        ];
        g.DrawRectangles(pen, rects);
        g.DrawRectangles(pen, rectf);
        CheckForEmptyBitmap(bitmap);
        pen.Dispose();
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void FillRectangle_Negative()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using SolidBrush brush = new(Color.Red);
        g.FillRectangle(brush, 5, 5, -10, -10);
        g.FillRectangle(brush, 0.0f, 0.0f, 5.0f, -10.0f);
        g.FillRectangle(brush, new Rectangle(15, 0, -10, 5));
        CheckForEmptyBitmap(bitmap);
        brush.Dispose();
        g.Dispose();
        bitmap.Dispose();
    }

    [Fact]
    public void FillRectangles_Negative()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using SolidBrush brush = new(Color.Red);
        Rectangle[] rects = [
            new(5, 5, -10, -10),
                new(0, 0, 5, -10)
        ];

        RectangleF[] rectf = [
            new(0.0f, 5.0f, -10.0f, -10.0f),
                new(15.0f, 0.0f, -10.0f, 5.0f)
        ];

        g.FillRectangles(brush, rects);
        g.FillRectangles(brush, rectf);
        CheckForEmptyBitmap(bitmap);
        brush.Dispose();
        g.Dispose();
        bitmap.Dispose();
    }

    private static void CheckDefaultProperties(string message, Graphics g)
    {
        Assert.True(g.Clip.IsInfinite(g), message + ".Clip.IsInfinite");
        AssertEquals(message + ".CompositingMode", CompositingMode.SourceOver, g.CompositingMode);
        AssertEquals(message + ".CompositingQuality", CompositingQuality.Default, g.CompositingQuality);
        AssertEquals(message + ".InterpolationMode", InterpolationMode.Bilinear, g.InterpolationMode);
        AssertEquals(message + ".PageScale", 1.0f, g.PageScale);
        AssertEquals(message + ".PageUnit", GraphicsUnit.Display, g.PageUnit);
        AssertEquals(message + ".PixelOffsetMode", PixelOffsetMode.Default, g.PixelOffsetMode);
        AssertEquals(message + ".SmoothingMode", SmoothingMode.None, g.SmoothingMode);
        AssertEquals(message + ".TextContrast", 4, g.TextContrast);
        AssertEquals(message + ".TextRenderingHint", TextRenderingHint.SystemDefault, g.TextRenderingHint);
        Assert.True(g.Transform.IsIdentity, message + ".Transform.IsIdentity");
    }

    private static void CheckCustomProperties(string message, Graphics g)
    {
        Assert.False(g.Clip.IsInfinite(g), message + ".Clip.IsInfinite");
        AssertEquals(message + ".CompositingMode", CompositingMode.SourceCopy, g.CompositingMode);
        AssertEquals(message + ".CompositingQuality", CompositingQuality.HighQuality, g.CompositingQuality);
        AssertEquals(message + ".InterpolationMode", InterpolationMode.HighQualityBicubic, g.InterpolationMode);
        AssertEquals(message + ".PageScale", 0.5f, g.PageScale);
        AssertEquals(message + ".PageUnit", GraphicsUnit.Inch, g.PageUnit);
        AssertEquals(message + ".PixelOffsetMode", PixelOffsetMode.Half, g.PixelOffsetMode);
        AssertEquals(message + ".RenderingOrigin", new Point(-1, -1), g.RenderingOrigin);
        AssertEquals(message + ".SmoothingMode", SmoothingMode.AntiAlias, g.SmoothingMode);
        AssertEquals(message + ".TextContrast", 0, g.TextContrast);
        AssertEquals(message + ".TextRenderingHint", TextRenderingHint.AntiAlias, g.TextRenderingHint);
        Assert.False(g.Transform.IsIdentity, message + ".Transform.IsIdentity");
    }

    private static void CheckMatrix(string message, Matrix m, float xx, float yx, float xy, float yy, float x0, float y0)
    {
        float[] elements = m.Elements;
        AssertEquals(message + ".Matrix.xx", xx, elements[0], 2);
        AssertEquals(message + ".Matrix.yx", yx, elements[1], 2);
        AssertEquals(message + ".Matrix.xy", xy, elements[2], 2);
        AssertEquals(message + ".Matrix.yy", yy, elements[3], 2);
        AssertEquals(message + ".Matrix.x0", x0, elements[4], 2);
        AssertEquals(message + ".Matrix.y0", y0, elements[5], 2);
    }

    [Fact]
    public void BeginContainer()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        CheckDefaultProperties("default", g);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);

        g.Clip = new Region(new Rectangle(10, 10, 10, 10));
        g.CompositingMode = CompositingMode.SourceCopy;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.PageScale = 0.5f;
        g.PageUnit = GraphicsUnit.Inch;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.RenderingOrigin = new Point(-1, -1);
        g.RotateTransform(45);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextContrast = 0;
        g.TextRenderingHint = TextRenderingHint.AntiAlias;
        CheckCustomProperties("modified", g);
        CheckMatrix("modified.Transform", g.Transform, 0.707f, 0.707f, -0.707f, 0.707f, 0, 0);

        GraphicsContainer gc = g.BeginContainer();
        // things gets reseted after calling BeginContainer
        CheckDefaultProperties("BeginContainer", g);
        // but not everything
        Assert.Equal(new Point(-1, -1), g.RenderingOrigin);

        g.EndContainer(gc);
        CheckCustomProperties("EndContainer", g);
    }

    [Fact]
    public void BeginContainer_Rect()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        CheckDefaultProperties("default", g);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);

        g.Clip = new Region(new Rectangle(10, 10, 10, 10));
        g.CompositingMode = CompositingMode.SourceCopy;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.PageScale = 0.5f;
        g.PageUnit = GraphicsUnit.Inch;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.RenderingOrigin = new Point(-1, -1);
        g.RotateTransform(45);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextContrast = 0;
        g.TextRenderingHint = TextRenderingHint.AntiAlias;
        CheckCustomProperties("modified", g);
        CheckMatrix("modified.Transform", g.Transform, 0.707f, 0.707f, -0.707f, 0.707f, 0, 0);

        GraphicsContainer gc = g.BeginContainer(new Rectangle(10, 20, 30, 40), new Rectangle(10, 20, 300, 400), GraphicsUnit.Millimeter);
        // things gets reseted after calling BeginContainer
        CheckDefaultProperties("BeginContainer", g);
        // but not everything
        Assert.Equal(new Point(-1, -1), g.RenderingOrigin);

        g.EndContainer(gc);
        CheckCustomProperties("EndContainer", g);
        CheckMatrix("EndContainer.Transform", g.Transform, 0.707f, 0.707f, -0.707f, 0.707f, 0, 0);
    }

    [Fact]
    public void BeginContainer_RectF()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        CheckDefaultProperties("default", g);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);

        g.Clip = new Region(new Rectangle(10, 10, 10, 10));
        g.CompositingMode = CompositingMode.SourceCopy;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.PageScale = 0.5f;
        g.PageUnit = GraphicsUnit.Inch;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.RenderingOrigin = new Point(-1, -1);
        g.RotateTransform(45);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextContrast = 0;
        g.TextRenderingHint = TextRenderingHint.AntiAlias;
        CheckCustomProperties("modified", g);
        CheckMatrix("modified.Transform", g.Transform, 0.707f, 0.707f, -0.707f, 0.707f, 0, 0);

        GraphicsContainer gc = g.BeginContainer(new RectangleF(40, 30, 20, 10), new RectangleF(10, 20, 30, 40), GraphicsUnit.Inch);
        // things gets reseted after calling BeginContainer
        CheckDefaultProperties("BeginContainer", g);
        // but not everything
        Assert.Equal(new Point(-1, -1), g.RenderingOrigin);

        g.EndContainer(gc);
        CheckCustomProperties("EndContainer", g);
    }

    private static void BeginContainer_GraphicsUnit(GraphicsUnit unit)
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        g.BeginContainer(new RectangleF(40, 30, 20, 10), new RectangleF(10, 20, 30, 40), unit);
    }

    [Fact]
    public void BeginContainer_GraphicsUnit_Display()
    {
        Assert.Throws<ArgumentException>(() => BeginContainer_GraphicsUnit(GraphicsUnit.Display));
    }

    [Fact]
    public void BeginContainer_GraphicsUnit_Valid()
    {
        BeginContainer_GraphicsUnit(GraphicsUnit.Document);
        BeginContainer_GraphicsUnit(GraphicsUnit.Inch);
        BeginContainer_GraphicsUnit(GraphicsUnit.Millimeter);
        BeginContainer_GraphicsUnit(GraphicsUnit.Pixel);
        BeginContainer_GraphicsUnit(GraphicsUnit.Point);
    }

    [Fact]
    public void BeginContainer_GraphicsUnit_World()
    {
        Assert.Throws<ArgumentException>(() => BeginContainer_GraphicsUnit(GraphicsUnit.World));
    }

    [Fact]
    public void BeginContainer_GraphicsUnit_Bad()
    {
        Assert.Throws<ArgumentException>(() => BeginContainer_GraphicsUnit((GraphicsUnit)int.MinValue));
    }

    [Fact]
    public void EndContainer_Null()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.EndContainer(null));
    }

    [Fact]
    public void Save()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        CheckDefaultProperties("default", g);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);

        GraphicsState gs1 = g.Save();
        // nothing is changed after a save
        CheckDefaultProperties("save1", g);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);

        g.Clip = new Region(new Rectangle(10, 10, 10, 10));
        g.CompositingMode = CompositingMode.SourceCopy;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.PageScale = 0.5f;
        g.PageUnit = GraphicsUnit.Inch;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.RenderingOrigin = new Point(-1, -1);
        g.RotateTransform(45);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextContrast = 0;
        g.TextRenderingHint = TextRenderingHint.AntiAlias;
        CheckCustomProperties("modified", g);
        CheckMatrix("modified.Transform", g.Transform, 0.707f, 0.707f, -0.707f, 0.707f, 0, 0);

        GraphicsState gs2 = g.Save();
        CheckCustomProperties("save2", g);

        g.Restore(gs2);
        CheckCustomProperties("restored1", g);
        CheckMatrix("restored1.Transform", g.Transform, 0.707f, 0.707f, -0.707f, 0.707f, 0, 0);

        g.Restore(gs1);
        CheckDefaultProperties("restored2", g);
        Assert.Equal(new Point(0, 0), g.RenderingOrigin);
    }

    [Fact]
    public void Restore_Null()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<NullReferenceException>(() => g.Restore(null));
    }

    [Fact]
    public void FillRectangles_BrushNull_Rectangle()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.FillRectangles(null, new Rectangle[1]));
    }

    [Fact]
    public void FillRectangles_Rectangle_Null()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.FillRectangles(Brushes.Red, (Rectangle[])null));
    }

    [Fact]
    public void FillRectanglesZeroRectangle()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.FillRectangles(Brushes.Red, Array.Empty<Rectangle>()));
    }

    [Fact]
    public void FillRectangles_BrushNull_RectangleF()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.FillRectangles(null, new RectangleF[1]));
    }

    [Fact]
    public void FillRectangles_RectangleF_Null()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.FillRectangles(Brushes.Red, (RectangleF[])null));
    }

    [Fact]
    public void FillRectanglesZeroRectangleF()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.FillRectangles(Brushes.Red, Array.Empty<RectangleF>()));
    }

    [Fact]
    public void FillRectangles_NormalBehavior()
    {
        using Bitmap bitmap = new(20, 20);
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.Clear(Color.Fuchsia);
            Rectangle rect = new(5, 5, 10, 10);
            g.Clip = new Region(rect);
            g.FillRectangle(Brushes.Red, rect);
        }

        Assert.Equal(Color.Red.ToArgb(), bitmap.GetPixel(5, 5).ToArgb());
        Assert.Equal(Color.Red.ToArgb(), bitmap.GetPixel(14, 5).ToArgb());
        Assert.Equal(Color.Red.ToArgb(), bitmap.GetPixel(5, 14).ToArgb());
        Assert.Equal(Color.Red.ToArgb(), bitmap.GetPixel(14, 14).ToArgb());

        Assert.Equal(Color.Fuchsia.ToArgb(), bitmap.GetPixel(15, 5).ToArgb());
        Assert.Equal(Color.Fuchsia.ToArgb(), bitmap.GetPixel(5, 15).ToArgb());
        Assert.Equal(Color.Fuchsia.ToArgb(), bitmap.GetPixel(15, 15).ToArgb());
    }

    private static Bitmap FillDrawRectangle(float width)
    {
        Bitmap bitmap = new(20, 20);
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.Clear(Color.Red);
            Rectangle rect = new(5, 5, 10, 10);
            g.FillRectangle(Brushes.Green, rect);
            if (width >= 0)
            {
                using Pen pen = new(Color.Blue, width);
                g.DrawRectangle(pen, rect);
            }
            else
            {
                g.DrawRectangle(Pens.Blue, rect);
            }
        }

        return bitmap;
    }

    [Fact]
    public void FillDrawRectangle_Width_Default()
    {
        // default pen size
        using Bitmap bitmap = FillDrawRectangle(float.MinValue);
        // NW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 6).ToArgb());
        // N
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 6).ToArgb());
        // NE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 6).ToArgb());
        // E
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 9).ToArgb());
        // SE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 14).ToArgb());
        // S
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 14).ToArgb());
        // SW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 14).ToArgb());
        // W
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 9).ToArgb());
    }

    [Fact]
    public void FillDrawRectangle_Width_2()
    {
        // even pen size
        using Bitmap bitmap = FillDrawRectangle(2.0f);
        // NW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 6).ToArgb());
        // N
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 6).ToArgb());
        // NE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(13, 6).ToArgb());
        // E
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(13, 9).ToArgb());
        // SE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 14).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(13, 13).ToArgb());
        // S
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 14).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 13).ToArgb());
        // SW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 14).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 13).ToArgb());
        // W
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 9).ToArgb());
    }

    [Fact]
    public void FillDrawRectangle_Width_3()
    {
        // odd pen size
        using Bitmap bitmap = FillDrawRectangle(3.0f);
        // NW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 5).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(6, 6).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(7, 7).ToArgb());
        // N
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 5).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 6).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 7).ToArgb());
        // NE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(17, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(16, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 5).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 6).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(13, 7).ToArgb());
        // E
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(17, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(16, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(13, 9).ToArgb());
        // SE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(17, 17).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(16, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 14).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(13, 13).ToArgb());
        // S
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 17).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 14).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 13).ToArgb());
        // SW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 17).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(6, 14).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(7, 13).ToArgb());
        // W
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(6, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(7, 9).ToArgb());
    }

    // reverse, draw the fill over
    private static Bitmap DrawFillRectangle(float width)
    {
        Bitmap bitmap = new(20, 20);
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.Clear(Color.Red);
            Rectangle rect = new(5, 5, 10, 10);
            if (width >= 0)
            {
                using Pen pen = new(Color.Blue, width);
                g.DrawRectangle(pen, rect);
            }
            else
            {
                g.DrawRectangle(Pens.Blue, rect);
            }

            g.FillRectangle(Brushes.Green, rect);
        }

        return bitmap;
    }

    [Fact]
    public void DrawFillRectangle_Width_Default()
    {
        // default pen size
        using Bitmap bitmap = DrawFillRectangle(float.MinValue);
        // NW - no blue border
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 6).ToArgb());
        // N - no blue border
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 6).ToArgb());
        // NE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 5).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 6).ToArgb());
        // E
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 9).ToArgb());
        // SE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 14).ToArgb());
        // S
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 14).ToArgb());
        // SW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 14).ToArgb());
        // W - no blue border
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 9).ToArgb());
    }

    [Fact]
    public void DrawFillRectangle_Width_2()
    {
        // even pen size
        using Bitmap bitmap = DrawFillRectangle(2.0f);
        // looks like a one pixel border - but enlarged
        // NW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 5).ToArgb());
        // N
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 5).ToArgb());
        // NE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 5).ToArgb());
        // E
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 9).ToArgb());
        // SE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 14).ToArgb());
        // S
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 14).ToArgb());
        // SW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 14).ToArgb());
        // W
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 9).ToArgb());
    }

    [Fact]
    public void DrawFillRectangle_Width_3()
    {
        // odd pen size
        using Bitmap bitmap = DrawFillRectangle(3.0f);
        // NW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 5).ToArgb());
        // N
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 5).ToArgb());
        // NE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(17, 3).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(16, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 4).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 5).ToArgb());
        // E
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(17, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(16, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 9).ToArgb());
        // SE
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(17, 17).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(16, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(14, 14).ToArgb());
        // S
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(9, 17).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(9, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(9, 14).ToArgb());
        // SW
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 17).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 16).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 15).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(6, 14).ToArgb());
        // W
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(3, 9).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(4, 9).ToArgb());
        Assert.Equal(0xFF008000, (uint)bitmap.GetPixel(5, 9).ToArgb());
    }

    private static Bitmap DrawLines(float width)
    {
        Bitmap bitmap = new(20, 20);
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.Clear(Color.Red);
            Point[] pts = [new(5, 5), new(15, 5), new(15, 15)];
            if (width >= 0)
            {
                using Pen pen = new(Color.Blue, width);
                g.DrawLines(pen, pts);
            }
            else
            {
                g.DrawLines(Pens.Blue, pts);
            }
        }

        return bitmap;
    }

    [Fact]
    public void DrawLines_Width_Default()
    {
        // default pen size
        using Bitmap bitmap = DrawLines(float.MinValue);
        // start
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 4).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 5).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(4, 6).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(5, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(5, 5).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(5, 6).ToArgb());
        // middle
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(14, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(14, 5).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(14, 6).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(15, 4).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 5).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 6).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 4).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 5).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 6).ToArgb());
        // end
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(14, 15).ToArgb());
        Assert.Equal(0xFF0000FF, (uint)bitmap.GetPixel(15, 15).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 15).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(14, 16).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(15, 16).ToArgb());
        Assert.Equal(0xFFFF0000, (uint)bitmap.GetPixel(16, 16).ToArgb());
    }

    [Fact]
    public void MeasureString_StringFont()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        SizeF size = g.MeasureString(null, _font);
        Assert.True(size.IsEmpty);
        size = g.MeasureString(string.Empty, _font);
        Assert.True(size.IsEmpty);
        g.MeasureString(string.Empty.AsSpan(), _font);
        Assert.True(size.IsEmpty);

        // null font
        size = g.MeasureString(null, null);
        Assert.True(size.IsEmpty);
        size = g.MeasureString(string.Empty, null);
        Assert.True(size.IsEmpty);
        g.MeasureString(string.Empty.AsSpan(), null);
        Assert.True(size.IsEmpty);
    }

    [Fact]
    public void MeasureString_StringFont_Null()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.MeasureString("a", null));
        Assert.Throws<ArgumentNullException>(() => g.MeasureString("a".AsSpan(), null));
    }

    [Fact]
    public void MeasureString_StringFontSizeF()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        SizeF size = g.MeasureString("a", _font, SizeF.Empty);
        Assert.False(size.IsEmpty);

        size = g.MeasureString("a".AsSpan(), _font, SizeF.Empty);
        Assert.False(size.IsEmpty);

        size = g.MeasureString(string.Empty, _font, SizeF.Empty);
        Assert.True(size.IsEmpty);

        size = g.MeasureString(string.Empty.AsSpan(), _font, SizeF.Empty);
        Assert.True(size.IsEmpty);
    }

    private void MeasureString_StringFontInt(string s, bool useSpan)
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        SizeF size0 = useSpan ? g.MeasureString(s.AsSpan(), _font, 0) : g.MeasureString(s, _font, 0);
        SizeF sizeN = useSpan ? g.MeasureString(s.AsSpan(), _font, int.MinValue) : g.MeasureString(s, _font, int.MinValue);
        SizeF sizeP = useSpan ? g.MeasureString(s.AsSpan(), _font, int.MaxValue) : g.MeasureString(s, _font, int.MaxValue);
        Assert.Equal(size0, sizeN);
        Assert.Equal(size0, sizeP);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_StringFontInt_ShortString(bool useSpan)
    {
        MeasureString_StringFontInt("a", useSpan);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_StringFontInt_LongString(bool useSpan)
    {
        MeasureString_StringFontInt("A very long string...", useSpan);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_StringFormat_Alignment(bool useSpan)
    {
        string text = "Hello Mono::";

        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.Alignment = StringAlignment.Near;
        SizeF near = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.Alignment = StringAlignment.Center;
        SizeF center = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.Alignment = StringAlignment.Far;
        SizeF far = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        Assert.Equal((double)near.Width, center.Width, 1);
        Assert.Equal((double)near.Height, center.Height, 1);

        Assert.Equal((double)center.Width, far.Width, 1);
        Assert.Equal((double)center.Height, far.Height, 1);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_StringFormat_Alignment_DirectionVertical(bool useSpan)
    {
        string text = "Hello Mono::";
        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.FormatFlags = StringFormatFlags.DirectionVertical;

        string_format.Alignment = StringAlignment.Near;
        SizeF near = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.Alignment = StringAlignment.Center;
        SizeF center = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.Alignment = StringAlignment.Far;
        SizeF far = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        Assert.Equal((double)near.Width, center.Width, 0);
        Assert.Equal((double)near.Height, center.Height, 0);

        Assert.Equal((double)center.Width, far.Width, 0);
        Assert.Equal((double)center.Height, far.Height, 0);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_StringFormat_LineAlignment(bool useSpan)
    {
        string text = "Hello Mono::";
        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.LineAlignment = StringAlignment.Near;
        SizeF near = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.LineAlignment = StringAlignment.Center;
        SizeF center = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.LineAlignment = StringAlignment.Far;
        SizeF far = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        Assert.Equal((double)near.Width, center.Width, 1);
        Assert.Equal((double)near.Height, center.Height, 1);

        Assert.Equal((double)center.Width, far.Width, 1);
        Assert.Equal((double)center.Height, far.Height, 1);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_StringFormat_LineAlignment_DirectionVertical(bool useSpan)
    {
        string text = "Hello Mono::";
        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.FormatFlags = StringFormatFlags.DirectionVertical;

        string_format.LineAlignment = StringAlignment.Near;
        SizeF near = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.LineAlignment = StringAlignment.Center;
        SizeF center = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        string_format.LineAlignment = StringAlignment.Far;
        SizeF far = useSpan
            ? g.MeasureString(text.AsSpan(), _font, int.MaxValue, string_format)
            : g.MeasureString(text, _font, int.MaxValue, string_format);

        Assert.Equal((double)near.Width, center.Width, 1);
        Assert.Equal((double)near.Height, center.Height, 1);

        Assert.Equal((double)center.Width, far.Width, 1);
        Assert.Equal((double)center.Height, far.Height, 1);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_CharactersFitted(bool useSpan)
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string s = "aaa aa aaaa a aaa";
        SizeF size = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);

        SizeF size2 = useSpan
            ? g.MeasureString(s.AsSpan(), _font, new SizeF(80, size.Height), null, out int chars, out int lines)
            : g.MeasureString(s, _font, new SizeF(80, size.Height), null, out chars, out lines);

        // in pixels
        Assert.True(size2.Width < size.Width);
        Assert.Equal((double)size2.Height, size.Height);

        Assert.Equal(1, lines);
        // documentation seems to suggest chars is total length
        Assert.True(chars < s.Length);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureString_Whitespace(bool useSpan)
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string s = string.Empty;
        SizeF size = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
        Assert.Equal(0, size.Height);
        Assert.Equal(0, size.Width);

        s += " ";
        SizeF expected = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
        for (int i = 1; i < 10; i++)
        {
            s += " ";
            size = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
            Assert.Equal((double)expected.Height, size.Height, 1);
            Assert.Equal((double)expected.Width, size.Width, 1);
        }

        s = "a";
        expected = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
        s = " " + s;
        size = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
        float space_width = size.Width - expected.Width;
        for (int i = 1; i < 10; i++)
        {
            size = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
            Assert.Equal((double)expected.Height, size.Height, 1);
            Assert.Equal((double)expected.Width + i * space_width, size.Width, 1);
            s = " " + s;
        }

        s = "a";
        expected = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
        for (int i = 1; i < 10; i++)
        {
            s += " ";
            size = useSpan ? g.MeasureString(s.AsSpan(), _font) : g.MeasureString(s, _font);
            Assert.Equal((double)expected.Height, size.Height, 1);
            Assert.Equal((double)expected.Width, size.Width, 1);
        }
    }

    [Fact]
    public void MeasureCharacterRanges_NullOrEmptyText()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Region[] regions = g.MeasureCharacterRanges(null, _font, default, null);
        Assert.Empty(regions);

        regions = g.MeasureCharacterRanges(string.Empty, _font, default, null);
        Assert.Empty(regions);
        regions = g.MeasureCharacterRanges(string.Empty.AsSpan(), _font, default, null);
        Assert.Empty(regions);

        // null font is ok with null or empty string
        regions = g.MeasureCharacterRanges(null, null, default, null);
        Assert.Empty(regions);

        regions = g.MeasureCharacterRanges(string.Empty, null, default, null);
        Assert.Empty(regions);
        regions = g.MeasureCharacterRanges(string.Empty.AsSpan(), null, default, null);
        Assert.Empty(regions);
    }

    [Fact]
    public void MeasureCharacterRanges_EmptyStringFormat()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        // string format without character ranges
        Region[] regions = g.MeasureCharacterRanges("Mono", _font, default, new StringFormat());
        Assert.Empty(regions);

        g.MeasureCharacterRanges("Mono".AsSpan(), _font, default, new StringFormat());
        Assert.Empty(regions);
    }

    [Fact]
    public void MeasureCharacterRanges_FontNull()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentNullException>(() => g.MeasureCharacterRanges("a", null, default, null));
        Assert.Throws<ArgumentNullException>(() => g.MeasureCharacterRanges("a".AsSpan(), null, default, null));
    }

    [Fact]
    public void MeasureCharacterRanges_TwoLines()
    {
        string text = "this\nis a test";
        CharacterRange[] ranges = [new CharacterRange(0, 5), new CharacterRange(5, 9)];

        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.FormatFlags = StringFormatFlags.NoClip;
        string_format.SetMeasurableCharacterRanges(ranges);

        SizeF size = g.MeasureString(text, _font, new Point(0, 0), string_format);
        RectangleF layout_rect = new(0.0f, 0.0f, size.Width, size.Height);
        Region[] regions = g.MeasureCharacterRanges(text, _font, layout_rect, string_format);

        Assert.Equal(2, regions.Length);
        Assert.Equal(regions[0].GetBounds(g).Height, regions[1].GetBounds(g).Height);

        regions = g.MeasureCharacterRanges(text.AsSpan(), _font, layout_rect, string_format);

        Assert.Equal(2, regions.Length);
        Assert.Equal(regions[0].GetBounds(g).Height, regions[1].GetBounds(g).Height);
    }

    private void MeasureCharacterRanges(string text, int first, int length, bool useSpan)
    {
        CharacterRange[] ranges = [new CharacterRange(first, length)];

        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.FormatFlags = StringFormatFlags.NoClip;
        string_format.SetMeasurableCharacterRanges(ranges);

        SizeF size = useSpan
            ? g.MeasureString(text.AsSpan(), _font, new Point(0, 0), string_format)
            : g.MeasureString(text, _font, new Point(0, 0), string_format);
        RectangleF layout_rect = new(0.0f, 0.0f, size.Width, size.Height);
        if (useSpan)
        {
            g.MeasureCharacterRanges(text.AsSpan(), _font, layout_rect, string_format);
        }
        else
        {
            g.MeasureCharacterRanges(text, _font, layout_rect, string_format);
        }
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureCharacterRanges_FirstTooFar(bool useSpan)
    {
        string text = "this\nis a test";
        Assert.Throws<ArgumentException>(() => MeasureCharacterRanges(text, text.Length, 1, useSpan));
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureCharacterRanges_LengthTooLong(bool useSpan)
    {
        string text = "this\nis a test";
        Assert.Throws<ArgumentException>(() => MeasureCharacterRanges(text, 0, text.Length + 1, useSpan));
    }

    [Fact]
    public void MeasureCharacterRanges_Prefix()
    {
        string text = "Hello &Mono::";
        CharacterRange[] ranges = [new CharacterRange(5, 4)];

        using StringFormat string_format = new();
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        string_format.SetMeasurableCharacterRanges(ranges);

        SizeF size = g.MeasureString(text, _font, new Point(0, 0), string_format);
        RectangleF layout_rect = new(0.0f, 0.0f, size.Width, size.Height);

        // here & is part of the measure and visible
        string_format.HotkeyPrefix = HotkeyPrefix.None;
        Region[] regions = g.MeasureCharacterRanges(text, _font, layout_rect, string_format);
        RectangleF bounds_none = regions[0].GetBounds(g);

        // here & is part of the measure (range) but visible as an underline
        string_format.HotkeyPrefix = HotkeyPrefix.Show;
        regions = g.MeasureCharacterRanges(text, _font, layout_rect, string_format);
        RectangleF bounds_show = regions[0].GetBounds(g);
        Assert.True(bounds_show.Width < bounds_none.Width);

        regions = g.MeasureCharacterRanges(text.AsSpan(), _font, layout_rect, string_format);
        bounds_show = regions[0].GetBounds(g);
        Assert.True(bounds_show.Width < bounds_none.Width);

        // here & is part of the measure (range) but invisible
        string_format.HotkeyPrefix = HotkeyPrefix.Hide;
        regions = g.MeasureCharacterRanges(text, _font, layout_rect, string_format);
        RectangleF bounds_hide = regions[0].GetBounds(g);
        Assert.Equal((double)bounds_hide.Width, bounds_show.Width);

        g.MeasureCharacterRanges(text.AsSpan(), _font, layout_rect, string_format);
        bounds_hide = regions[0].GetBounds(g);
        Assert.Equal((double)bounds_hide.Width, bounds_show.Width);
    }

    [Fact]
    public void MeasureCharacterRanges_NullStringFormat()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        Assert.Throws<ArgumentException>(() => g.MeasureCharacterRanges("Mono", _font, default, null));
        Assert.Throws<ArgumentException>(() => g.MeasureCharacterRanges("Mono".AsSpan(), _font, default, null));
    }

    private static readonly CharacterRange[] s_ranges = [new(0, 1), new(1, 1), new(2, 1)];

    private static Region[] Measure_Helper(Graphics gfx, RectangleF rect, bool useSpan)
    {
        using StringFormat format = StringFormat.GenericTypographic;
        format.SetMeasurableCharacterRanges(s_ranges);

        using Font font = new(FontFamily.GenericSerif, 11.0f);
        return useSpan
            ? gfx.MeasureCharacterRanges("abc".AsSpan(), font, rect, format)
            : gfx.MeasureCharacterRanges("abc", font, rect, format);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void Measure(bool useSpan)
    {
        using Graphics gfx = Graphics.FromImage(new Bitmap(1, 1));
        Region[] zero = Measure_Helper(gfx, new RectangleF(0, 0, 0, 0), useSpan);
        Assert.Equal(3, zero.Length);

        Region[] small = Measure_Helper(gfx, new RectangleF(0, 0, 100, 100), useSpan);
        Assert.Equal(3, small.Length);
        for (int i = 0; i < 3; i++)
        {
            RectangleF zb = zero[i].GetBounds(gfx);
            RectangleF sb = small[i].GetBounds(gfx);
            Assert.Equal(sb.X, zb.X);
            Assert.Equal(sb.Y, zb.Y);
            Assert.Equal((double)sb.Width, zb.Width);
            Assert.Equal((double)sb.Height, zb.Height);
        }

        Region[] max = Measure_Helper(gfx, new RectangleF(0, 0, float.MaxValue, float.MaxValue), useSpan);
        Assert.Equal(3, max.Length);
        for (int i = 0; i < 3; i++)
        {
            RectangleF zb = zero[i].GetBounds(gfx);
            RectangleF mb = max[i].GetBounds(gfx);
            Assert.Equal(mb.X, zb.X);
            Assert.Equal(mb.Y, zb.Y);
            Assert.Equal((double)mb.Width, zb.Width);
            Assert.Equal((double)mb.Height, zb.Height);
        }
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void MeasureLimits(bool useSpan)
    {
        using Graphics gfx = Graphics.FromImage(new Bitmap(1, 1));
        Region[] min = Measure_Helper(gfx, new RectangleF(0, 0, float.MinValue, float.MinValue), useSpan);
        Assert.Equal(3, min.Length);
        for (int i = 0; i < 3; i++)
        {
            RectangleF mb = min[i].GetBounds(gfx);
            Assert.Equal(-4194304.0f, mb.X);
            Assert.Equal(-4194304.0f, mb.Y);
            Assert.Equal(8388608.0f, mb.Width);
            Assert.Equal(8388608.0f, mb.Height);
        }

        Region[] neg = Measure_Helper(gfx, new RectangleF(0, 0, -20, -20), useSpan);
        Assert.Equal(3, neg.Length);
        for (int i = 0; i < 3; i++)
        {
            RectangleF mb = neg[i].GetBounds(gfx);
            Assert.Equal(-4194304.0f, mb.X);
            Assert.Equal(-4194304.0f, mb.Y);
            Assert.Equal(8388608.0f, mb.Width);
            Assert.Equal(8388608.0f, mb.Height);
        }
    }

    [Fact]
    public void DrawString_EndlessLoop()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using StringFormat fmt = new();
        Rectangle rect = Rectangle.Empty;
        rect.Location = new Point(10, 10);
        rect.Size = new Size(1, 20);
        fmt.Alignment = StringAlignment.Center;
        fmt.LineAlignment = StringAlignment.Center;
        fmt.FormatFlags = StringFormatFlags.NoWrap;
        fmt.Trimming = StringTrimming.EllipsisWord;
        g.DrawString("Test String", _font, Brushes.Black, rect, fmt);
        g.DrawString("Test String".AsSpan(), _font, Brushes.Black, rect, fmt);
    }

    [Fact]
    public void DrawString_EndlessLoop_Wrapping()
    {
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using StringFormat fmt = new();
        Rectangle rect = Rectangle.Empty;
        rect.Location = new Point(10, 10);
        rect.Size = new Size(1, 20);
        fmt.Alignment = StringAlignment.Center;
        fmt.LineAlignment = StringAlignment.Center;
        fmt.Trimming = StringTrimming.EllipsisWord;
        g.DrawString("Test String", _font, Brushes.Black, rect, fmt);
        g.DrawString("Test String".AsSpan(), _font, Brushes.Black, rect, fmt);
    }

    [Fact]
    public void MeasureString_Wrapping_Dots()
    {
        string text = "this is really long text........................................... with a lot o periods.";
        using Bitmap bitmap = new(20, 20);
        using Graphics g = Graphics.FromImage(bitmap);
        using StringFormat format = new();
        format.Alignment = StringAlignment.Center;
        SizeF sz = g.MeasureString(text, _font, 80, format);
        Assert.True(sz.Width <= 80);
        Assert.True(sz.Height > _font.Height * 2);

        sz = g.MeasureString(text.AsSpan(), _font, 80, format);
        Assert.True(sz.Width <= 80);
        Assert.True(sz.Height > _font.Height * 2);
    }

    [Fact]
    public void GetReleaseHdcInternal()
    {
        using Bitmap b = new(10, 10);
        using Graphics g = Graphics.FromImage(b);
        IntPtr hdc1 = g.GetHdc();
        g.ReleaseHdcInternal(hdc1);
        IntPtr hdc2 = g.GetHdc();
        g.ReleaseHdcInternal(hdc2);
        Assert.Equal(hdc1, hdc2);
    }

    [Fact]
    public void ReleaseHdcInternal_IntPtrZero()
    {
        using Bitmap b = new(10, 10);
        using Graphics g = Graphics.FromImage(b);
        Assert.Throws<ArgumentException>(() => g.ReleaseHdcInternal(IntPtr.Zero));
    }

    [Fact]
    public void ReleaseHdcInternal_TwoTimes()
    {
        using Bitmap b = new(10, 10);
        using Graphics g = Graphics.FromImage(b);
        IntPtr hdc = g.GetHdc();
        g.ReleaseHdcInternal(hdc);
        Assert.Throws<ArgumentException>(() => g.ReleaseHdcInternal(hdc));
    }

    [Fact]
    public void TestReleaseHdc()
    {
        using Bitmap b = new(10, 10);
        using Graphics g = Graphics.FromImage(b);
        IntPtr hdc1 = g.GetHdc();
        g.ReleaseHdc();
        IntPtr hdc2 = g.GetHdc();
        g.ReleaseHdc();
        Assert.Equal(hdc1, hdc2);
    }

    [Fact]
    public void TestReleaseHdcException()
    {
        using Bitmap b = new(10, 10);
        using Graphics g = Graphics.FromImage(b);
        Assert.Throws<ArgumentException>(g.ReleaseHdc);
    }

    [Fact]
    public void TestReleaseHdcException2()
    {
        using Bitmap b = new(10, 10);
        using Graphics g = Graphics.FromImage(b);
        g.GetHdc();
        g.ReleaseHdc();
        Assert.Throws<ArgumentException>(g.ReleaseHdc);
    }

    [Fact]
    public void VisibleClipBound()
    {
        // see #78958
        using Bitmap bmp = new(100, 100);
        using Graphics g = Graphics.FromImage(bmp);
        RectangleF noclip = g.VisibleClipBounds;
        Assert.Equal(0, noclip.X);
        Assert.Equal(0, noclip.Y);
        Assert.Equal(100, noclip.Width);
        Assert.Equal(100, noclip.Height);

        // note: libgdiplus regions are precise to multiple of multiple of 8
        g.Clip = new Region(new RectangleF(0, 0, 32, 32));
        RectangleF clip = g.VisibleClipBounds;
        Assert.Equal(0, clip.X);
        Assert.Equal(0, clip.Y);
        Assert.Equal(32.0, clip.Width, 4);
        Assert.Equal(32.0, clip.Height, 4);

        g.RotateTransform(90);
        RectangleF rotclip = g.VisibleClipBounds;
        Assert.Equal(0.0, rotclip.X, 4);
        Assert.Equal(-32.0, rotclip.Y, 4);
        Assert.Equal(32.0, rotclip.Width, 4);
        Assert.Equal(32.0, rotclip.Height, 4);
    }

    [Fact]
    public void VisibleClipBound_BigClip()
    {
        using Bitmap bmp = new(100, 100);
        using Graphics g = Graphics.FromImage(bmp);
        RectangleF noclip = g.VisibleClipBounds;
        Assert.Equal(0, noclip.X);
        Assert.Equal(0, noclip.Y);
        Assert.Equal(100, noclip.Width);
        Assert.Equal(100, noclip.Height);

        // clip is larger than bitmap
        g.Clip = new Region(new RectangleF(0, 0, 200, 200));
        RectangleF clipbound = g.ClipBounds;
        Assert.Equal(0, clipbound.X);
        Assert.Equal(0, clipbound.Y);
        Assert.Equal(200, clipbound.Width);
        Assert.Equal(200, clipbound.Height);

        RectangleF clip = g.VisibleClipBounds;
        Assert.Equal(0, clip.X);
        Assert.Equal(0, clip.Y);
        Assert.Equal(100, clip.Width);
        Assert.Equal(100, clip.Height);

        g.RotateTransform(90);
        RectangleF rotclipbound = g.ClipBounds;
        Assert.Equal(0.0, rotclipbound.X, 4);
        Assert.Equal(-200.0, rotclipbound.Y, 4);
        Assert.Equal(200.0, rotclipbound.Width, 4);
        Assert.Equal(200.0, rotclipbound.Height, 4);

        RectangleF rotclip = g.VisibleClipBounds;
        Assert.Equal(0.0, rotclip.X, 4);
        Assert.Equal(-100.0, rotclip.Y, 4);
        Assert.Equal(100.0, rotclip.Width, 4);
        Assert.Equal(100.0, rotclip.Height, 4);
    }

    [Fact]
    public void Rotate()
    {
        using Bitmap bmp = new(100, 50);
        using Graphics g = Graphics.FromImage(bmp);
        RectangleF vcb = g.VisibleClipBounds;
        Assert.Equal(0, vcb.X);
        Assert.Equal(0, vcb.Y);
        Assert.Equal(100.0, vcb.Width, 4);
        Assert.Equal(50.0, vcb.Height, 4);

        g.RotateTransform(90);
        RectangleF rvcb = g.VisibleClipBounds;
        Assert.Equal(0.0, rvcb.X, 4);
        Assert.Equal(-100.0, rvcb.Y, 4);
        Assert.Equal(50.0, rvcb.Width, 4);
        Assert.Equal(100.0, rvcb.Height, 4);
    }

    [Fact]
    public void Scale()
    {
        using Bitmap bmp = new(100, 50);
        using Graphics g = Graphics.FromImage(bmp);
        RectangleF vcb = g.VisibleClipBounds;
        Assert.Equal(0, vcb.X);
        Assert.Equal(0, vcb.Y);
        Assert.Equal(100, vcb.Width);
        Assert.Equal(50, vcb.Height);

        g.ScaleTransform(2, 0.5f);
        RectangleF svcb = g.VisibleClipBounds;
        Assert.Equal(0, svcb.X);
        Assert.Equal(0, svcb.Y);
        Assert.Equal(50, svcb.Width);
        Assert.Equal(100, svcb.Height);
    }

    [Fact]
    public void Translate()
    {
        using Bitmap bmp = new(100, 50);
        using Graphics g = Graphics.FromImage(bmp);
        RectangleF vcb = g.VisibleClipBounds;
        Assert.Equal(0, vcb.X);
        Assert.Equal(0, vcb.Y);
        Assert.Equal(100, vcb.Width);
        Assert.Equal(50, vcb.Height);

        g.TranslateTransform(-25, 25);
        RectangleF tvcb = g.VisibleClipBounds;
        Assert.Equal(25, tvcb.X);
        Assert.Equal(-25, tvcb.Y);
        Assert.Equal(100, tvcb.Width);
        Assert.Equal(50, tvcb.Height);
    }

    [Fact]
    public void DrawIcon_NullRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawIcon(null, new Rectangle(0, 0, 32, 32)));
    }

    [Fact]
    public void DrawIcon_IconRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawIcon(SystemIcons.Application, new Rectangle(0, 0, 40, 20));
        // Rectangle is empty when X, Y, Width and Height == 0
        // (yep X and Y too, RectangleF only checks for Width and Height)
        g.DrawIcon(SystemIcons.Asterisk, new Rectangle(0, 0, 0, 0));
        // so this one is half-empty ;-)
        g.DrawIcon(SystemIcons.Error, new Rectangle(20, 40, 0, 0));
        // negative width or height isn't empty (for Rectangle)
        g.DrawIconUnstretched(SystemIcons.WinLogo, new Rectangle(10, 20, -1, 0));
        g.DrawIconUnstretched(SystemIcons.WinLogo, new Rectangle(20, 10, 0, -1));
    }

    [Fact]
    public void DrawIcon_NullIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawIcon(null, 4, 2));
    }

    [Fact]
    public void DrawIcon_IconIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawIcon(SystemIcons.Exclamation, 4, 2);
        g.DrawIcon(SystemIcons.Hand, 0, 0);
    }

    [Fact]
    public void DrawIconUnstretched_NullRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawIconUnstretched(null, new Rectangle(0, 0, 40, 20)));
    }

    [Fact]
    public void DrawIconUnstretched_IconRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawIconUnstretched(SystemIcons.Information, new Rectangle(0, 0, 40, 20));
        // Rectangle is empty when X, Y, Width and Height == 0
        // (yep X and Y too, RectangleF only checks for Width and Height)
        g.DrawIconUnstretched(SystemIcons.Question, new Rectangle(0, 0, 0, 0));
        // so this one is half-empty ;-)
        g.DrawIconUnstretched(SystemIcons.Warning, new Rectangle(20, 40, 0, 0));
        // negative width or height isn't empty (for Rectangle)
        g.DrawIconUnstretched(SystemIcons.WinLogo, new Rectangle(10, 20, -1, 0));
        g.DrawIconUnstretched(SystemIcons.WinLogo, new Rectangle(20, 10, 0, -1));
    }

    [Fact]
    public void DrawImage_NullRectangleF()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, new RectangleF(0, 0, 0, 0)));
    }

    [Fact]
    public void DrawImage_ImageRectangleF()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, new RectangleF(0, 0, 0, 0));
        g.DrawImage(bmp, new RectangleF(20, 40, 0, 0));
        g.DrawImage(bmp, new RectangleF(10, 20, -1, 0));
        g.DrawImage(bmp, new RectangleF(20, 10, 0, -1));
    }

    [Fact]
    public void DrawImage_NullPointF()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, new PointF(0, 0)));
    }

    [Fact]
    public void DrawImage_ImagePointF()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, new PointF(0, 0));
    }

    [Fact]
    public void DrawImage_NullPointFArray()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, Array.Empty<PointF>()));
    }

    [Fact]
    public void DrawImage_ImagePointFArrayNull()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(bmp, (PointF[])null));
    }

    [Fact]
    public void DrawImage_ImagePointFArrayEmpty()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentException>(() => g.DrawImage(bmp, Array.Empty<PointF>()));
    }

    [Fact]
    public void DrawImage_ImagePointFArray()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, new PointF[]
        {
                    new(0, 0), new(1, 1), new(2, 2)
        });
    }

    [Fact]
    public void DrawImage_NullRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, new Rectangle(0, 0, 0, 0)));
    }

    [Fact]
    public void DrawImage_ImageRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        // Rectangle is empty when X, Y, Width and Height == 0
        // (yep X and Y too, RectangleF only checks for Width and Height)
        g.DrawImage(bmp, new Rectangle(0, 0, 0, 0));
        // so this one is half-empty ;-)
        g.DrawImage(bmp, new Rectangle(20, 40, 0, 0));
        // negative width or height isn't empty (for Rectangle)
        g.DrawImage(bmp, new Rectangle(10, 20, -1, 0));
        g.DrawImage(bmp, new Rectangle(20, 10, 0, -1));
    }

    [Fact]
    public void DrawImage_NullPoint()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, new Point(0, 0)));
    }

    [Fact]
    public void DrawImage_ImagePoint()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, new Point(0, 0));
    }

    [Fact]
    public void DrawImage_NullPointArray()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, Array.Empty<Point>()));
    }

    [Fact]
    public void DrawImage_ImagePointArrayNull()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(bmp, (Point[])null));
    }

    [Fact]
    public void DrawImage_ImagePointArrayEmpty()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentException>(() => g.DrawImage(bmp, Array.Empty<Point>()));
    }

    [Fact]
    public void DrawImage_ImagePointArray()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, [new(0, 0), new(1, 1), new(2, 2)]);
    }

    [Fact]
    public void DrawImage_NullIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, int.MaxValue, int.MinValue));
    }

    [Fact]
    public void DrawImage_ImageIntInt_Overflow()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<OverflowException>(() => g.DrawImage(bmp, int.MaxValue, int.MinValue));
    }

    [Fact]
    public void DrawImage_ImageIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, -40, -40);
    }

    [Fact]
    public void DrawImage_NullFloat()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, float.MaxValue, float.MinValue));
    }

    [Fact]
    public void DrawImage_ImageFloatFloat_Overflow()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<OverflowException>(() => g.DrawImage(bmp, float.MaxValue, float.MinValue));
    }

    [Fact]
    public void DrawImage_ImageFloatFloat()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, -40.0f, -40.0f);
    }

    [Fact]
    public void DrawImage_NullRectangleRectangleGraphicsUnit()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, default(Rectangle), default, GraphicsUnit.Display));
    }

    private static void DrawImage_ImageRectangleRectangleGraphicsUnit(GraphicsUnit unit)
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Rectangle r = new(0, 0, 40, 40);
        g.DrawImage(bmp, r, r, unit);
    }

    [Fact]
    public void DrawImage_ImageRectangleRectangleGraphicsUnit_Display()
    {
        Assert.Throws<ArgumentException>(() => DrawImage_ImageRectangleRectangleGraphicsUnit(GraphicsUnit.Display));
    }

    [Fact]
    public void DrawImage_ImageRectangleRectangleGraphicsUnit_Pixel()
    {
        // this unit works
        DrawImage_ImageRectangleRectangleGraphicsUnit(GraphicsUnit.Pixel);
    }

    [Fact]
    public void DrawImage_ImageRectangleRectangleGraphicsUnit_World()
    {
        Assert.Throws<ArgumentException>(() => DrawImage_ImageRectangleRectangleGraphicsUnit(GraphicsUnit.World));
    }

    [Fact]
    public void DrawImage_NullPointRectangleGraphicsUnit()
    {
        Rectangle r = new(1, 2, 3, 4);
        Point[] pts = [new(1, 1), new(2, 2), new(3, 3)];
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, pts, r, GraphicsUnit.Pixel));
    }

    private static void DrawImage_ImagePointRectangleGraphicsUnit(Point[] pts)
    {
        Rectangle r = new(1, 2, 3, 4);
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, pts, r, GraphicsUnit.Pixel);
    }

    [Fact]
    public void DrawImage_ImageNullRectangleGraphicsUnit()
    {
        Assert.Throws<ArgumentNullException>(() => DrawImage_ImagePointRectangleGraphicsUnit(null));
    }

    [Fact]
    public void DrawImage_ImagePoint0RectangleGraphicsUnit()
    {
        Assert.Throws<ArgumentException>(() => DrawImage_ImagePointRectangleGraphicsUnit([]));
    }

    [Fact]
    public void DrawImage_ImagePoint1RectangleGraphicsUnit()
    {
        Point p = new(1, 1);
        Assert.Throws<ArgumentException>(() => DrawImage_ImagePointRectangleGraphicsUnit([p]));
    }

    [Fact]
    public void DrawImage_ImagePoint2RectangleGraphicsUnit()
    {
        Point p = new(1, 1);
        Assert.Throws<ArgumentException>(() => DrawImage_ImagePointRectangleGraphicsUnit([p, p]));
    }

    [Fact]
    public void DrawImage_ImagePoint3RectangleGraphicsUnit()
    {
        Point p = new(1, 1);
        DrawImage_ImagePointRectangleGraphicsUnit([p, p, p]);
    }

    [Fact]
    public void DrawImage_ImagePoint4RectangleGraphicsUnit()
    {
        Point p = new(1, 1);
        Assert.Throws<NotImplementedException>(() => DrawImage_ImagePointRectangleGraphicsUnit([p, p, p, p]));
    }

    [Fact]
    public void DrawImage_NullPointFRectangleGraphicsUnit()
    {
        Rectangle r = new(1, 2, 3, 4);
        PointF[] pts = [new(1, 1), new(2, 2), new(3, 3)];
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImage(null, pts, r, GraphicsUnit.Pixel));
    }

    private static void DrawImage_ImagePointFRectangleGraphicsUnit(PointF[] pts)
    {
        Rectangle r = new(1, 2, 3, 4);
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, pts, r, GraphicsUnit.Pixel);
    }

    [Fact]
    public void DrawImage_ImageNullFRectangleGraphicsUnit()
    {
        Assert.Throws<ArgumentNullException>(() => DrawImage_ImagePointFRectangleGraphicsUnit(null));
    }

    [Fact]
    public void DrawImage_ImagePointF0RectangleGraphicsUnit()
    {
        Assert.Throws<ArgumentException>(() => DrawImage_ImagePointFRectangleGraphicsUnit([]));
    }

    [Fact]
    public void DrawImage_ImagePointF1RectangleGraphicsUnit()
    {
        PointF p = new(1, 1);
        Assert.Throws<ArgumentException>(() => DrawImage_ImagePointFRectangleGraphicsUnit([p]));
    }

    [Fact]
    public void DrawImage_ImagePointF2RectangleGraphicsUnit()
    {
        PointF p = new(1, 1);
        Assert.Throws<ArgumentException>(() => DrawImage_ImagePointFRectangleGraphicsUnit([p, p]));
    }

    [Fact]
    public void DrawImage_ImagePointF3RectangleGraphicsUnit()
    {
        PointF p = new(1, 1);
        DrawImage_ImagePointFRectangleGraphicsUnit([p, p, p]);
    }

    [Fact]
    public void DrawImage_ImagePointF4RectangleGraphicsUnit()
    {
        PointF p = new(1, 1);
        Assert.Throws<NotImplementedException>(() => DrawImage_ImagePointFRectangleGraphicsUnit([p, p, p, p]));
    }

    [Fact]
    public void DrawImage_ImagePointRectangleGraphicsUnitNull()
    {
        Point p = new(1, 1);
        Point[] pts = [p, p, p];
        Rectangle r = new(1, 2, 3, 4);
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImage(bmp, pts, r, GraphicsUnit.Pixel, null);
    }

    [Fact]
    public void DrawImage_ImagePointRectangleGraphicsUnitAttributes()
    {
        Point p = new(1, 1);
        Point[] pts = [p, p, p];
        Rectangle r = new(1, 2, 3, 4);
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        using ImageAttributes ia = new();
        g.DrawImage(bmp, pts, r, GraphicsUnit.Pixel, ia);
    }

    [Fact]
    public void DrawImageUnscaled_NullPoint()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImageUnscaled(null, new Point(0, 0)));
    }

    [Fact]
    public void DrawImageUnscaled_ImagePoint()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImageUnscaled(bmp, new Point(0, 0));
    }

    [Fact]
    public void DrawImageUnscaled_NullRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImageUnscaled(null, new Rectangle(0, 0, -1, -1)));
    }

    [Fact]
    public void DrawImageUnscaled_ImageRectangle()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImageUnscaled(bmp, new Rectangle(0, 0, -1, -1));
    }

    [Fact]
    public void DrawImageUnscaled_NullIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImageUnscaled(null, 0, 0));
    }

    [Fact]
    public void DrawImageUnscaled_ImageIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImageUnscaled(bmp, 0, 0);
    }

    [Fact]
    public void DrawImageUnscaled_NullIntIntIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImageUnscaled(null, 0, 0, -1, -1));
    }

    [Fact]
    public void DrawImageUnscaled_ImageIntIntIntInt()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        g.DrawImageUnscaled(bmp, 0, 0, -1, -1);
    }

    [Fact]
    public void DrawImageUnscaledAndClipped_Null()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawImageUnscaledAndClipped(null, new Rectangle(0, 0, 0, 0)));
    }

    [Fact]
    public void DrawImageUnscaledAndClipped()
    {
        using Bitmap bmp = new(40, 40);
        using Graphics g = Graphics.FromImage(bmp);
        // Rectangle is empty when X, Y, Width and Height == 0
        // (yep X and Y too, RectangleF only checks for Width and Height)
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(0, 0, 0, 0));
        // so this one is half-empty ;-)
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(20, 40, 0, 0));
        // negative width or height isn't empty (for Rectangle)
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(10, 20, -1, 0));
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(20, 10, 0, -1));
        // smaller
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(0, 0, 10, 20));
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(0, 0, 40, 10));
        g.DrawImageUnscaledAndClipped(bmp, new Rectangle(0, 0, 80, 20));
    }

    [Fact]
    public void DrawPath_Pen_Null()
    {
        using Bitmap bmp = new(20, 20);
        using Graphics g = Graphics.FromImage(bmp);
        using GraphicsPath path = new();
        Assert.Throws<ArgumentNullException>(() => g.DrawPath(null, path));
    }

    [Fact]
    public void DrawPath_Path_Null()
    {
        using Bitmap bmp = new(20, 20);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.DrawPath(Pens.Black, null));
    }

    [Fact]
    public void DrawPath_Arcs()
    {
        using Bitmap bmp = new(20, 20);
        using Graphics g = Graphics.FromImage(bmp);
        using GraphicsPath path = new();
        int d = 5;
        Rectangle baserect = new(0, 0, 19, 19);
        Rectangle arcrect = new(baserect.Location, new Size(d, d));

        path.AddArc(arcrect, 180, 90);
        arcrect.X = baserect.Right - d;
        path.AddArc(arcrect, 270, 90);
        arcrect.Y = baserect.Bottom - d;
        path.AddArc(arcrect, 0, 90);
        arcrect.X = baserect.Left;
        path.AddArc(arcrect, 90, 90);
        path.CloseFigure();
        g.Clear(Color.White);
        g.DrawPath(Pens.SteelBlue, path);

        Assert.Equal(-12156236, bmp.GetPixel(0, 9).ToArgb());
        Assert.Equal(-1, bmp.GetPixel(1, 9).ToArgb());
    }

    [Fact]
    public void FillPath_Brush_Null()
    {
        using Bitmap bmp = new(20, 20);
        using Graphics g = Graphics.FromImage(bmp);
        using GraphicsPath path = new();
        Assert.Throws<ArgumentNullException>(() => g.FillPath(null, path));
    }

    [Fact]
    public void FillPath_Path_Null()
    {
        using Bitmap bmp = new(20, 20);
        using Graphics g = Graphics.FromImage(bmp);
        Assert.Throws<ArgumentNullException>(() => g.FillPath(Brushes.Black, null));
    }

    [Fact]
    public void FillPath_Arcs()
    {
        using Bitmap bmp = new(20, 20);
        using Graphics g = Graphics.FromImage(bmp);
        using GraphicsPath path = new();
        int d = 5;
        Rectangle baserect = new(0, 0, 19, 19);
        Rectangle arcrect = new(baserect.Location, new Size(d, d));

        path.AddArc(arcrect, 180, 90);
        arcrect.X = baserect.Right - d;
        path.AddArc(arcrect, 270, 90);
        arcrect.Y = baserect.Bottom - d;
        path.AddArc(arcrect, 0, 90);
        arcrect.X = baserect.Left;
        path.AddArc(arcrect, 90, 90);
        path.CloseFigure();
        g.Clear(Color.White);
        g.FillPath(Brushes.SteelBlue, path);

        Assert.Equal(-12156236, bmp.GetPixel(0, 9).ToArgb());
        Assert.Equal(-12156236, bmp.GetPixel(1, 9).ToArgb());
    }

    [Fact]
    public void TransformPoints()
    {
        using Bitmap bmp = new(10, 10);
        using Graphics g = Graphics.FromImage(bmp);
        Point[] pts = new Point[5];
        PointF[] ptf = new PointF[5];
        for (int i = 0; i < 5; i++)
        {
            pts[i] = new Point(i, i);
            ptf[i] = new PointF(i, i);
        }

        g.TransformPoints(CoordinateSpace.Page, CoordinateSpace.Device, pts);
        g.TransformPoints(CoordinateSpace.Page, CoordinateSpace.Device, ptf);

        for (int i = 0; i < 5; i++)
        {
            Assert.Equal(i, pts[i].X);
            Assert.Equal(i, pts[i].Y);
            Assert.Equal(i, ptf[i].X);
            Assert.Equal(i, ptf[i].Y);
        }
    }

    [Fact]
    public void Dpi()
    {
        float x, y;
        using Bitmap bmp = new(10, 10);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            x = g.DpiX - 10;
            y = g.DpiY + 10;
        }

        bmp.SetResolution(x, y);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            Assert.Equal(x, g.DpiX);
            Assert.Equal(y, g.DpiY);
        }
    }

    [Fact]
    public void GetReleaseHdc()
    {
        using Bitmap b = new(100, 100);
        using Graphics g = Graphics.FromImage(b);
        IntPtr hdc1 = g.GetHdc();
        g.ReleaseHdc(hdc1);
        IntPtr hdc2 = g.GetHdc();
        g.ReleaseHdc(hdc2);
        Assert.Equal(hdc1, hdc2);
    }
}
