/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <tools/color.hxx>
#include <test/bootstrapfixture.hxx>
#include <test/outputdevice.hxx>

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/vector/b2enums.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>

#include <vcl/gradient.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/print.hxx>
#include <vcl/rendercontext/RasterOp.hxx>
#include <vcl/virdev.hxx>
#include <vcl/window.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/metaact.hxx>

#include <vcl/BitmapWriteAccess.hxx>
#include <bufferdevice.hxx>
#include <window.h>

const size_t INITIAL_SETUP_ACTION_COUNT = 5;

class VclOutdevTest : public test::BootstrapFixture
{
public:
    VclOutdevTest()
        : BootstrapFixture(true, false)
    {
    }
};

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testGetReadableFontColorPrinter)
{
    ScopedVclPtrInstance<Printer> pPrinter;
    CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPrinter->GetReadableFontColor(COL_WHITE, COL_WHITE));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testGetReadableFontColorWindow)
{
    ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
    CPPUNIT_ASSERT_EQUAL(COL_WHITE,
                         pWindow->GetOutDev()->GetReadableFontColor(COL_WHITE, COL_BLACK));
    CPPUNIT_ASSERT_EQUAL(COL_BLACK,
                         pWindow->GetOutDev()->GetReadableFontColor(COL_WHITE, COL_WHITE));
    CPPUNIT_ASSERT_EQUAL(COL_WHITE,
                         pWindow->GetOutDev()->GetReadableFontColor(COL_BLACK, COL_BLACK));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testPrinterBackgroundColor)
{
    ScopedVclPtrInstance<Printer> pPrinter;
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pPrinter->GetBackgroundColor());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testWindowBackgroundColor)
{
    ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
    pWindow->SetBackground(Wallpaper(COL_WHITE));
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pWindow->GetBackgroundColor());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testVirtualDevice)
{
// TODO: This unit test is not executed for macOS unless bitmap scaling is implemented
#ifndef MACOSX
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    pVDev->SetOutputSizePixel(Size(32, 32));
    pVDev->SetBackground(Wallpaper(COL_WHITE));

    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetBackgroundColor());

    pVDev->Erase();
    pVDev->DrawPixel(Point(1, 2), COL_BLUE);
    pVDev->DrawPixel(Point(31, 30), COL_RED);

    Size aSize = pVDev->GetOutputSizePixel();
    CPPUNIT_ASSERT_EQUAL(Size(32, 32), aSize);

    Bitmap aBmp = pVDev->GetBitmap(Point(), aSize);

#if 0
    OUString rFileName("/tmp/foo-unx.png");
    try {
        vcl::PNGWriter aWriter( aBmp );
        SvFileStream sOutput( rFileName, StreamMode::WRITE );
        aWriter.Write( sOutput );
        sOutput.Close();
    } catch (...) {
        SAL_WARN("vcl", "Error writing png to " << rFileName);
    }
#endif

    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(0, 0)));
#if !defined _WIN32 //TODO: various failures on Windows tinderboxes
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, pVDev->GetPixel(Point(1, 2)));
    CPPUNIT_ASSERT_EQUAL(COL_RED, pVDev->GetPixel(Point(31, 30)));
#endif
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetPixel(Point(30, 31)));

    // Gotcha: y and x swap for BitmapReadAccess: deep joy.
    BitmapScopedReadAccess pAcc(aBmp);
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, static_cast<Color>(pAcc->GetPixel(0, 0)));
#if !defined _WIN32 //TODO: various failures on Windows tinderboxes
    CPPUNIT_ASSERT_EQUAL(COL_BLUE, static_cast<Color>(pAcc->GetPixel(2, 1)));
    CPPUNIT_ASSERT_EQUAL(COL_RED, static_cast<Color>(pAcc->GetPixel(30, 31)));
#endif
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, static_cast<Color>(pAcc->GetPixel(31, 30)));

#if 0
    VclPtr<vcl::Window> pWin = VclPtr<WorkWindow>::Create( (vcl::Window *)nullptr );
    CPPUNIT_ASSERT( pWin );
    OutputDevice *pOutDev = pWin.get();
#endif
#endif
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testUseAfterDispose)
{
    // Create a virtual device, enable map mode then dispose it.
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    pVDev->EnableMapMode();

    pVDev->disposeOnce();

    // Make sure that these don't crash after dispose.
    pVDev->GetInverseViewTransformation();

    pVDev->GetViewTransformation();
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawInvertedBitmap)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetRasterOp(RasterOp::Invert);
    pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
                      MetaActionType::BMP);

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::RASTEROP, pAction->GetType());
    auto pRasterOpAction = static_cast<MetaRasterOpAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(RasterOp::Invert, pRasterOpAction->GetRasterOp());

    pAction = aMtf.GetAction(1);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::RECT, pAction->GetType());
    auto pRectAction = static_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 0), Size(10, 10)), pRectAction->GetRect());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawBlackBitmap)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
    aBitmap.Erase(COL_RED);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetDrawMode(DrawModeFlags::BlackBitmap);
    pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
                      MetaActionType::BMP);

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::PUSH, pAction->GetType());
    auto pPushAction = static_cast<MetaPushAction*>(pAction);
    bool bLineFillFlag
        = ((vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR) == pPushAction->GetFlags());
    CPPUNIT_ASSERT_MESSAGE("Push flags not LINECOLOR | FILLCOLOR", bLineFillFlag);

    pAction = aMtf.GetAction(1);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
    auto pLineColorAction = static_cast<MetaLineColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(COL_BLACK, pLineColorAction->GetColor());

    pAction = aMtf.GetAction(2);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
    auto pFillColorAction = static_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(COL_BLACK, pFillColorAction->GetColor());

    pAction = aMtf.GetAction(3);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::RECT, pAction->GetType());
    auto pRectAction = static_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 0), Size(10, 10)), pRectAction->GetRect());

    pAction = aMtf.GetAction(4);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::POP, pAction->GetType());

    // test to see if the color is black
    Bitmap aBlackBmp(pVDev->GetBitmap(Point(0, 0), Size(10, 10)));
    BitmapScopedReadAccess pReadAccess(aBlackBmp);
    const BitmapColor aColor = pReadAccess->GetColor(0, 0);
    CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_BLACK), aColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawWhiteBitmap)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetDrawMode(DrawModeFlags::WhiteBitmap);
    pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
                      MetaActionType::BMP);

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::PUSH, pAction->GetType());
    auto pPushAction = static_cast<MetaPushAction*>(pAction);
    bool bLineFillFlag
        = ((vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR) == pPushAction->GetFlags());
    CPPUNIT_ASSERT_MESSAGE("Push flags not LINECOLOR | FILLCOLOR", bLineFillFlag);

    pAction = aMtf.GetAction(1);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
    auto pLineColorAction = static_cast<MetaLineColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pLineColorAction->GetColor());

    pAction = aMtf.GetAction(2);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
    auto pFillColorAction = static_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pFillColorAction->GetColor());

    pAction = aMtf.GetAction(3);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::RECT, pAction->GetType());
    auto pRectAction = static_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 0), Size(10, 10)), pRectAction->GetRect());

    pAction = aMtf.GetAction(4);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::POP, pAction->GetType());

    // test to see if the color is white
    Bitmap aWhiteBmp(pVDev->GetBitmap(Point(0, 0), Size(10, 10)));
    BitmapScopedReadAccess pReadAccess(aWhiteBmp);
    const BitmapColor aColor = pReadAccess->GetColor(0, 0);
    CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_WHITE), aColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawBitmap)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
                      MetaActionType::BMP);

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::BMP, pAction->GetType());
    auto pBmpAction = static_cast<MetaBmpAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(Size(16, 16), pBmpAction->GetBitmap().GetSizePixel());
    CPPUNIT_ASSERT_EQUAL(Point(0, 0), pBmpAction->GetPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawScaleBitmap)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->DrawBitmap(Point(5, 5), Size(10, 10), Point(0, 0), Size(10, 10), aBitmap,
                      MetaActionType::BMPSCALE);

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPSCALE, pAction->GetType());
    auto pBmpScaleAction = static_cast<MetaBmpScaleAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(Size(16, 16), pBmpScaleAction->GetBitmap().GetSizePixel());
    CPPUNIT_ASSERT_EQUAL(Point(5, 5), pBmpScaleAction->GetPoint());
    CPPUNIT_ASSERT_EQUAL(Size(10, 10), pBmpScaleAction->GetSize());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawScalePartBitmap)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->DrawBitmap(Point(0, 0), Size(10, 10), Point(5, 5), Size(10, 10), aBitmap,
                      MetaActionType::BMPSCALEPART);

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPSCALEPART, pAction->GetType());
    auto pBmpScalePartAction = static_cast<MetaBmpScalePartAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(Size(16, 16), pBmpScalePartAction->GetBitmap().GetSizePixel());
    CPPUNIT_ASSERT_EQUAL(Point(5, 5), pBmpScalePartAction->GetSrcPoint());
    CPPUNIT_ASSERT_EQUAL(Size(10, 10), pBmpScalePartAction->GetSrcSize());
    CPPUNIT_ASSERT_EQUAL(Point(0, 0), pBmpScalePartAction->GetDestPoint());
    CPPUNIT_ASSERT_EQUAL(Size(10, 10), pBmpScalePartAction->GetDestSize());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGrayBitmap)
{
    // draw a red 1x1 bitmap
    Bitmap aBmp(Size(1, 1), vcl::PixelFormat::N24_BPP);
    aBmp.Erase(COL_RED);

    // check to ensure that the bitmap is red
    {
        BitmapScopedReadAccess pReadAccess(aBmp);
        const BitmapColor aColor = pReadAccess->GetColor(0, 0);
        CPPUNIT_ASSERT_EQUAL(BitmapColor(COL_RED), aColor);
    }

    ScopedVclPtrInstance<VirtualDevice> pVDev;

    pVDev->SetDrawMode(DrawModeFlags::GrayBitmap);
    pVDev->DrawBitmap(Point(0, 0), Size(1, 1), Point(0, 0), Size(1, 1), aBmp, MetaActionType::BMP);

    // should be a grey
    Bitmap aVDevBmp(pVDev->GetBitmap(Point(), Size(1, 1)));
    {
        BitmapScopedReadAccess pReadAccess(aVDevBmp);
        const BitmapColor aColor = pReadAccess->GetColor(0, 0);
        CPPUNIT_ASSERT_EQUAL(BitmapColor(0x26, 0x26, 0x26), aColor);
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawTransformedBitmapExScale)
{
    // Given a 100x100 bitmap:
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(100, 100), vcl::PixelFormat::N24_BPP);
    basegfx::B2DVector aScale(20, 80);
    basegfx::B2DVector aTranslate(80, 0);
    double fRotate = M_PI / 2;
    double fShearX = 0;
    basegfx::B2DHomMatrix aMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
        aScale, fShearX, fRotate, aTranslate);
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    // When drawing that with a transform:
    pVDev->DrawTransformedBitmapEx(aMatrix, aBitmap);

    // Then make sure the bitmap recorded in the metafile doesn't get a scaled down width:
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aMtf.GetActionSize());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPEXSCALE, pAction->GetType());
    auto pBitmapAction = static_cast<MetaBmpExScaleAction*>(pAction);
    const Bitmap& rBitmapEx = pBitmapAction->GetBitmap();
    Size aTransformedSize = rBitmapEx.GetSizePixel();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected greater or equal than: 100
    // - Actual  : 80
    // i.e. an unwanted scaling down lead to blurry presentation output.
    CPPUNIT_ASSERT_GREATEREQUAL(static_cast<tools::Long>(100), aTransformedSize.getWidth());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawTransformedBitmapEx)
{
    // Create a virtual device, and connect a metafile to it.
    // Also create a 16x16 bitmap.
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
    {
        // Fill the top left quarter with black.
        BitmapScopedWriteAccess pWriteAccess(aBitmap);
        pWriteAccess->Erase(COL_WHITE);
        for (int i = 0; i < 8; ++i)
        {
            for (int j = 0; j < 8; ++j)
            {
                pWriteAccess->SetPixel(j, i, COL_BLACK);
            }
        }
    }
    basegfx::B2DHomMatrix aMatrix;
    aMatrix.scale(8, 8);
    // Rotate 90 degrees clockwise, so the black part goes to the top right.
    aMatrix.rotate(M_PI / 2);
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    // Draw the rotated bitmap on the vdev.
    pVDev->DrawTransformedBitmapEx(aMatrix, aBitmap);
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aMtf.GetActionSize());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPEXSCALE, pAction->GetType());
    auto pBitmapAction = static_cast<MetaBmpExScaleAction*>(pAction);
    const Bitmap& rBitmapEx = pBitmapAction->GetBitmap();
    Size aTransformedSize = rBitmapEx.GetSizePixel();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 16x16
    // - Actual  : 8x8
    // I.e. the bitmap before scaling was already scaled down, just because it was rotated.
    CPPUNIT_ASSERT_EQUAL(Size(16, 16), aTransformedSize);

    aBitmap = rBitmapEx.CreateColorBitmap();
    BitmapScopedReadAccess pAccess(aBitmap);
    for (int i = 0; i < 16; ++i)
    {
        for (int j = 0; j < 16; ++j)
        {
            BitmapColor aColor = pAccess->GetPixel(j, i);
            Color aExpected = i >= 8 && j < 8 ? COL_BLACK : COL_WHITE;
            std::stringstream ss;
            ss << "Color is expected to be ";
            ss << ((aExpected == COL_WHITE) ? "white" : "black");
            ss << ", is " << aColor.AsRGBHexString();
            ss << " (row " << j << ", col " << i << ")";
            // Without the accompanying fix in place, this test would have failed with:
            // - Expected: c[00000000]
            // - Actual  : c[ffffff00]
            // - Color is expected to be black, is ffffff (row 0, col 8)
            // i.e. the top right quarter of the image was not fully black, there was a white first
            // row.
            CPPUNIT_ASSERT_EQUAL_MESSAGE(ss.str(), aExpected, Color(aColor));
        }
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawTransformedBitmapExFlip)
{
    // Create a virtual device, and connect a metafile to it.
    // Also create a 16x16 bitmap.
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    Bitmap aBitmap(Size(16, 16), vcl::PixelFormat::N24_BPP);
    {
        // Fill the top left quarter with black.
        BitmapScopedWriteAccess pWriteAccess(aBitmap);
        pWriteAccess->Erase(COL_WHITE);
        for (int i = 0; i < 8; ++i)
        {
            for (int j = 0; j < 8; ++j)
            {
                pWriteAccess->SetPixel(j, i, COL_BLACK);
            }
        }
    }
    basegfx::B2DHomMatrix aMatrix;
    // Negative y scale: bitmap should be upside down, so the black part goes to the bottom left.
    aMatrix.scale(8, -8);
    // Rotate 90 degrees clockwise, so the black part goes back to the top left.
    aMatrix.rotate(M_PI / 2);
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    // Draw the scaled and rotated bitmap on the vdev.
    pVDev->DrawTransformedBitmapEx(aMatrix, aBitmap);
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aMtf.GetActionSize());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::BMPEXSCALE, pAction->GetType());
    auto pBitmapAction = static_cast<MetaBmpExScaleAction*>(pAction);
    const Bitmap& rBitmapEx = pBitmapAction->GetBitmap();

    aBitmap = rBitmapEx.CreateColorBitmap();
    BitmapScopedReadAccess pAccess(aBitmap);
    int nX = 8 * 0.25;
    int nY = 8 * 0.25;
    BitmapColor aColor = pAccess->GetPixel(nY, nX);
    std::stringstream ss;
    ss << "Color is expected to be black, is " << aColor.AsRGBHexString();
    ss << " (row " << nY << ", col " << nX << ")";
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: c[00000000]
    // - Actual  : c[ffffff00]
    // - Color is expected to be black, is ffffff (row 2, col 2)
    // i.e. the top left quarter of the image was not black, due to a missing flip.
    CPPUNIT_ASSERT_EQUAL_MESSAGE(ss.str(), COL_BLACK, Color(aColor));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRTL)
{
    ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
    pWindow->EnableRTL();
    vcl::RenderContext& rRenderContext = *pWindow->GetOutDev();
    vcl::BufferDevice pBuffer(pWindow, rRenderContext);

    // Without the accompanying fix in place, this test would have failed, because the RTL status
    // from pWindow was not propagated to pBuffer.
    CPPUNIT_ASSERT(pBuffer->IsRTLEnabled());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRTLGuard)
{
    ScopedVclPtrInstance<vcl::Window> pWindow(nullptr, WB_APP | WB_STDWORK);
    pWindow->EnableRTL();
    pWindow->RequestDoubleBuffering(true);
    ImplFrameData* pFrameData = pWindow->ImplGetWindowImpl()->mpFrameData;
    vcl::PaintBufferGuard aGuard(pFrameData, pWindow);
    // Without the accompanying fix in place, this test would have failed, because the RTL status
    // from pWindow was not propagated to aGuard.
    CPPUNIT_ASSERT(aGuard.GetRenderContext()->IsRTLEnabled());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDefaultFillColor)
{
    // Create a virtual device, and connect a metafile to it.
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT(pVDev->IsFillColor());
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetFillColor());

    pVDev->SetFillColor();
    CPPUNIT_ASSERT(!pVDev->IsFillColor());
    CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetFillColor());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
    auto pFillAction = static_cast<MetaFillColorAction*>(pAction);
    const Color& rColor = pFillAction->GetColor();
    CPPUNIT_ASSERT_EQUAL(Color(), rColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testTransparentFillColor)
{
    // Create a virtual device, and connect a metafile to it.
    ScopedVclPtrInstance<VirtualDevice> pVDev(DeviceFormat::WITH_ALPHA);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT(pVDev->IsFillColor());
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetFillColor());

    pVDev->SetFillColor(COL_TRANSPARENT);
    CPPUNIT_ASSERT(pVDev->IsFillColor());
    CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetFillColor());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
    auto pFillAction = static_cast<MetaFillColorAction*>(pAction);
    const Color& rColor = pFillAction->GetColor();
    CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, rColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testFillColor)
{
    // Create a virtual device, and connect a metafile to it.
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT(pVDev->IsFillColor());
    CPPUNIT_ASSERT_EQUAL(COL_WHITE, pVDev->GetFillColor());

    pVDev->SetFillColor(COL_RED);
    CPPUNIT_ASSERT(pVDev->IsFillColor());
    CPPUNIT_ASSERT_EQUAL(COL_RED, pVDev->GetFillColor());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::FILLCOLOR, pAction->GetType());
    auto pFillAction = static_cast<MetaFillColorAction*>(pAction);
    const Color& rColor = pFillAction->GetColor();
    CPPUNIT_ASSERT_EQUAL(COL_RED, rColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDefaultLineColor)
{
    // Create a virtual device, and connect a metafile to it.
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT(pVDev->IsLineColor());
    CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetLineColor());

    pVDev->SetLineColor();
    CPPUNIT_ASSERT(!pVDev->IsLineColor());
    CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, pVDev->GetLineColor());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
    auto pLineAction = static_cast<MetaLineColorAction*>(pAction);
    const Color& rColor = pLineAction->GetColor();
    CPPUNIT_ASSERT_EQUAL(Color(), rColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testLineColor)
{
    // Create a virtual device, and connect a metafile to it.
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT(pVDev->IsLineColor());
    CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetLineColor());

    pVDev->SetLineColor(COL_RED);
    CPPUNIT_ASSERT(pVDev->IsLineColor());
    CPPUNIT_ASSERT_EQUAL(COL_RED, pVDev->GetLineColor());
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::LINECOLOR, pAction->GetType());
    auto pLineAction = static_cast<MetaLineColorAction*>(pAction);
    const Color& rColor = pLineAction->GetColor();
    CPPUNIT_ASSERT_EQUAL(COL_RED, rColor);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testFont)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    // Use Dejavu fonts, they are shipped with LO, so they should be ~always available.
    // Use Sans variant for simpler glyph shapes (no serifs).
    vcl::Font font(u"DejaVu Sans"_ustr, u"Book"_ustr, Size(0, 36));
    font.SetColor(COL_BLACK);
    font.SetFillColor(COL_RED);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetFont(font);
    bool bSameFont(font == pVDev->GetFont());
    CPPUNIT_ASSERT_MESSAGE("Font is not the same", bSameFont);

    // four actions:
    // 1. Font action
    // 2. Text alignment action
    // 3. Text fill color action
    // 4. As not COL_TRANSPARENT (means use system font color), font color action
    size_t nActionsExpected = 4;
    CPPUNIT_ASSERT_EQUAL(nActionsExpected, aMtf.GetActionSize());

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::FONT, pAction->GetType());
    auto pFontAction = static_cast<MetaFontAction*>(pAction);
    bool bSameMetaFont = (font == pFontAction->GetFont());
    CPPUNIT_ASSERT_MESSAGE("Metafile font is not the same", bSameMetaFont);

    pAction = aMtf.GetAction(1);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTALIGN, pAction->GetType());
    auto pTextAlignAction = static_cast<MetaTextAlignAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(font.GetAlignment(), pTextAlignAction->GetTextAlign());

    pAction = aMtf.GetAction(2);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTFILLCOLOR, pAction->GetType());
    auto pTextFillColorAction = static_cast<MetaTextFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(COL_RED, pTextFillColorAction->GetColor());

    pAction = aMtf.GetAction(3);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTCOLOR, pAction->GetType());
    auto pTextColorAction = static_cast<MetaTextColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(COL_BLACK, pTextColorAction->GetColor());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testTransparentFont)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    // Use Dejavu fonts, they are shipped with LO, so they should be ~always available.
    // Use Sans variant for simpler glyph shapes (no serifs).
    vcl::Font font(u"DejaVu Sans"_ustr, u"Book"_ustr, Size(0, 36));
    font.SetColor(COL_TRANSPARENT);

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetFont(font);

    // three actions as it sets the colour to the default system color (and doesn't add a text color action):
    // 1. Font action
    // 2. Text alignment action
    // 3. Text fill color action
    size_t nActionsExpected = 3;
    CPPUNIT_ASSERT_EQUAL(nActionsExpected, aMtf.GetActionSize());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDefaultRefPoint)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetRefPoint();

    CPPUNIT_ASSERT(!pVDev->IsRefPoint());
    CPPUNIT_ASSERT_EQUAL(Point(), pVDev->GetRefPoint());

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::REFPOINT, pAction->GetType());
    auto pRefPointAction = static_cast<MetaRefPointAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(Point(), pRefPointAction->GetRefPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRefPoint)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetRefPoint(Point(10, 20));

    CPPUNIT_ASSERT(pVDev->IsRefPoint());
    CPPUNIT_ASSERT_EQUAL(Point(10, 20), pVDev->GetRefPoint());

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::REFPOINT, pAction->GetType());
    auto pRefPointAction = static_cast<MetaRefPointAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(Point(10, 20), pRefPointAction->GetRefPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testRasterOp)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetRasterOp(RasterOp::Invert);

    CPPUNIT_ASSERT_EQUAL(RasterOp::Invert, pVDev->GetRasterOp());
    CPPUNIT_ASSERT(pVDev->IsLineColor());
    CPPUNIT_ASSERT(pVDev->IsFillColor());

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::RASTEROP, pAction->GetType());
    auto pRasterOpAction = static_cast<MetaRasterOpAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(RasterOp::Invert, pRasterOpAction->GetRasterOp());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testOutputFlag)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    CPPUNIT_ASSERT(pVDev->IsOutputEnabled());
    CPPUNIT_ASSERT(pVDev->IsDeviceOutputNecessary());

    pVDev->EnableOutput(false);

    CPPUNIT_ASSERT(!pVDev->IsOutputEnabled());
    CPPUNIT_ASSERT(!pVDev->IsDeviceOutputNecessary());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testAntialias)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    CPPUNIT_ASSERT_EQUAL(AntialiasingFlags::NONE, pVDev->GetAntialiasing());

    pVDev->SetAntialiasing(AntialiasingFlags::Enable);

    CPPUNIT_ASSERT_EQUAL(AntialiasingFlags::Enable, pVDev->GetAntialiasing());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawMode)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    CPPUNIT_ASSERT_EQUAL(DrawModeFlags::Default, pVDev->GetDrawMode());

    pVDev->SetDrawMode(DrawModeFlags::BlackLine);

    CPPUNIT_ASSERT_EQUAL(DrawModeFlags::BlackLine, pVDev->GetDrawMode());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testLayoutMode)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT_EQUAL(vcl::text::ComplexTextLayoutFlags::Default, pVDev->GetLayoutMode());

    pVDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl);

    CPPUNIT_ASSERT_EQUAL(vcl::text::ComplexTextLayoutFlags::BiDiRtl, pVDev->GetLayoutMode());

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::LAYOUTMODE, pAction->GetType());
    auto pLayoutModeAction = static_cast<MetaLayoutModeAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(vcl::text::ComplexTextLayoutFlags::BiDiRtl,
                         pLayoutModeAction->GetLayoutMode());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDigitLanguage)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;

    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    CPPUNIT_ASSERT_EQUAL(LANGUAGE_SYSTEM, pVDev->GetDigitLanguage());

    pVDev->SetDigitLanguage(LANGUAGE_GERMAN);

    CPPUNIT_ASSERT_EQUAL(LANGUAGE_GERMAN, pVDev->GetDigitLanguage());

    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL(MetaActionType::TEXTLANGUAGE, pAction->GetType());
    auto pTextLanguageAction = static_cast<MetaTextLanguageAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL(LANGUAGE_GERMAN, pTextLanguageAction->GetTextLanguage());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testStackFunctions)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->Push();
    MetaAction* pAction = aMtf.GetAction(0);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Push action", MetaActionType::PUSH, pAction->GetType());

    pVDev->SetLineColor(COL_RED);
    pVDev->SetFillColor(COL_GREEN);
    pVDev->SetTextColor(COL_BROWN);
    pVDev->SetTextFillColor(COL_BLUE);
    pVDev->SetTextLineColor(COL_MAGENTA);
    pVDev->SetOverlineColor(COL_YELLOW);
    pVDev->SetTextAlign(TextAlign::ALIGN_TOP);
    pVDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl);
    pVDev->SetDigitLanguage(LANGUAGE_FRENCH);
    pVDev->SetRasterOp(RasterOp::N0);
    pVDev->SetMapMode(MapMode(MapUnit::MapTwip));
    pVDev->SetRefPoint(Point(10, 10));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Text color", COL_BROWN, pVDev->GetTextColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Text fill color", COL_BLUE, pVDev->GetTextFillColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Text line color", COL_MAGENTA, pVDev->GetTextLineColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Text overline color", COL_YELLOW, pVDev->GetOverlineColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Layout mode", vcl::text::ComplexTextLayoutFlags::BiDiRtl,
                                 pVDev->GetLayoutMode());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Language", LANGUAGE_FRENCH, pVDev->GetDigitLanguage());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Raster operation", RasterOp::N0, pVDev->GetRasterOp());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Map mode", MapMode(MapUnit::MapTwip), pVDev->GetMapMode());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Ref point", Point(10, 10), pVDev->GetRefPoint());

    pVDev->Pop();
    pAction = aMtf.GetAction(13);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Pop action", MetaActionType::POP, pAction->GetType());

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default line color", COL_BLACK, pVDev->GetLineColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default fill color", COL_WHITE, pVDev->GetFillColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default text color", COL_BLACK, pVDev->GetTextColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default text fill color", Color(ColorTransparency, 0xFFFFFFFF),
                                 pVDev->GetTextFillColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default text line color", Color(ColorTransparency, 0xFFFFFFFF),
                                 pVDev->GetTextLineColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default overline color", Color(ColorTransparency, 0xFFFFFFFF),
                                 pVDev->GetOverlineColor());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default layout mode", vcl::text::ComplexTextLayoutFlags::Default,
                                 pVDev->GetLayoutMode());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default language", LANGUAGE_SYSTEM, pVDev->GetDigitLanguage());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default raster operation", RasterOp::OverPaint,
                                 pVDev->GetRasterOp());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default map mode", MapMode(MapUnit::MapPixel),
                                 pVDev->GetMapMode());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Default ref point", Point(0, 0), pVDev->GetRefPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testSystemTextColor)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;

        pVDev->SetSystemTextColor(SystemTextColorFlags::NONE, true);
        CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetTextColor());
        pVDev->SetSystemTextColor(SystemTextColorFlags::Mono, false);
        CPPUNIT_ASSERT_EQUAL(COL_BLACK, pVDev->GetTextColor());
    }

    {
        ScopedVclPtrInstance<Printer> pPrinter;
        pPrinter->SetSystemTextColor(SystemTextColorFlags::NONE, true);
        CPPUNIT_ASSERT_EQUAL(COL_BLACK, pPrinter->GetTextColor());
    }
}

namespace
{
class WaveLineTester : public OutputDevice
{
public:
    WaveLineTester()
        : OutputDevice(OUTDEV_VIRDEV)
    {
    }

    bool AcquireGraphics() const override { return true; }
    void ReleaseGraphics(bool) override {}
    bool UsePolyPolygonForComplexGradient() override { return false; }
    bool CanAnimate() const override { return false; }
    virtual bool HasAlpha() const override { return false; }

    bool testShouldDrawWavePixelAsRect(tools::Long nLineWidth)
    {
        return shouldDrawWavePixelAsRect(nLineWidth);
    }

    Size testGetWaveLineSize(tools::Long nLineWidth) { return GetWaveLineSize(nLineWidth); }
};

class WaveLineTesterPrinter : public Printer
{
public:
    WaveLineTesterPrinter() {}

    bool AcquireGraphics() const { return true; }
    void ReleaseGraphics(bool) {}
    bool UsePolyPolygonForComplexGradient() { return false; }

    Size testGetWaveLineSize(tools::Long nLineWidth) { return GetWaveLineSize(nLineWidth); }
};
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testShouldDrawWavePixelAsRect)
{
    ScopedVclPtrInstance<WaveLineTester> pTestOutDev;

    CPPUNIT_ASSERT(!pTestOutDev->testShouldDrawWavePixelAsRect(0));
    CPPUNIT_ASSERT(!pTestOutDev->testShouldDrawWavePixelAsRect(1));

    CPPUNIT_ASSERT(pTestOutDev->testShouldDrawWavePixelAsRect(10));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testGetWaveLineSize)
{
    {
        ScopedVclPtrInstance<WaveLineTester> pTestOutDev;

        pTestOutDev->SetDPIX(96);
        pTestOutDev->SetDPIY(96);

        CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestOutDev->testGetWaveLineSize(0));
        CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestOutDev->testGetWaveLineSize(1));

        CPPUNIT_ASSERT_EQUAL(Size(10, 10), pTestOutDev->testGetWaveLineSize(10));
    }

    {
        ScopedVclPtrInstance<WaveLineTesterPrinter> pTestOutDev;

        pTestOutDev->SetDPIX(96);
        pTestOutDev->SetDPIY(96);

        CPPUNIT_ASSERT_EQUAL(Size(0, 0), pTestOutDev->testGetWaveLineSize(0));
        CPPUNIT_ASSERT_EQUAL(Size(1, 1), pTestOutDev->testGetWaveLineSize(1));

        CPPUNIT_ASSERT_EQUAL(Size(10, 10), pTestOutDev->testGetWaveLineSize(10));
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testErase)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    // this actually triggers Erase()
    pVDev->SetOutputSizePixel(Size(10, 10));
    pVDev->Erase();

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (start)", MetaActionType::LINECOLOR,
                                 pAction->GetType());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (start)", MetaActionType::FILLCOLOR,
                                 pAction->GetType());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 2);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action", MetaActionType::RECT, pAction->GetType());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 3);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action (end)", MetaActionType::FILLCOLOR,
                                 pAction->GetType());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 4);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (end)", MetaActionType::LINECOLOR,
                                 pAction->GetType());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPixel)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        // triggers an Erase()
        pVDev->SetOutputSizePixel(Size(10, 10));
        pVDev->SetLineColor(COL_RED);
        pVDev->DrawPixel(Point(0, 0), COL_GREEN);

        CPPUNIT_ASSERT_EQUAL_MESSAGE("Color not green", COL_GREEN, pVDev->GetPixel(Point(0, 0)));

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pixel action", MetaActionType::PIXEL,
                                     pAction->GetType());
        MetaPixelAction* pPixelAction = dynamic_cast<MetaPixelAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Pixel action has incorrect position", Point(0, 0),
                                     pPixelAction->GetPoint());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Pixel action is wrong color", COL_GREEN,
                                     pPixelAction->GetColor());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(1, 1));
        pVDev->SetLineColor(COL_RED);
        pVDev->DrawPixel(Point(0, 0));

        CPPUNIT_ASSERT_EQUAL_MESSAGE("Color not red", COL_RED, pVDev->GetPixel(Point(0, 0)));

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a point action", MetaActionType::POINT,
                                     pAction->GetType());
        MetaPointAction* pPointAction = dynamic_cast<MetaPointAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Pixel action has incorrect position", Point(0, 0),
                                     pPointAction->GetPoint());
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawLine)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(10, 100));
        pVDev->DrawLine(Point(0, 0), Point(0, 50));

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line action", MetaActionType::LINE, pAction->GetType());
        MetaLineAction* pLineAction = dynamic_cast<MetaLineAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 0),
                                     pLineAction->GetStartPoint());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 50),
                                     pLineAction->GetEndPoint());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        LineInfo aLineInfo(LineStyle::Dash, 10);
        aLineInfo.SetDashCount(5);
        aLineInfo.SetDashLen(10);
        aLineInfo.SetDotCount(3);
        aLineInfo.SetDotLen(13);
        aLineInfo.SetDistance(8);
        aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel);
        aLineInfo.SetLineCap(css::drawing::LineCap_BUTT);

        pVDev->SetOutputSizePixel(Size(100, 100));
        pVDev->DrawLine(Point(0, 0), Point(0, 50), aLineInfo);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line action", MetaActionType::LINE, pAction->GetType());
        MetaLineAction* pLineAction = dynamic_cast<MetaLineAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 0),
                                     pLineAction->GetStartPoint());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line start has incorrect position", Point(0, 50),
                                     pLineAction->GetEndPoint());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash count wrong", static_cast<sal_uInt16>(5),
                                     pLineAction->GetLineInfo().GetDashCount());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash len wrong", static_cast<double>(10),
                                     pLineAction->GetLineInfo().GetDashLen());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot count wrong", static_cast<sal_uInt16>(3),
                                     pLineAction->GetLineInfo().GetDotCount());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot len wrong", static_cast<double>(13),
                                     pLineAction->GetLineInfo().GetDotLen());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Distance wrong", static_cast<double>(8),
                                     pLineAction->GetLineInfo().GetDistance());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line join", basegfx::B2DLineJoin::Bevel,
                                     pLineAction->GetLineInfo().GetLineJoin());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line cap", css::drawing::LineCap_BUTT,
                                     pLineAction->GetLineInfo().GetLineCap());
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawRect)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));
        pVDev->DrawRect(tools::Rectangle(Point(0, 0), Size(50, 60)));

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action", MetaActionType::RECT, pAction->GetType());
        MetaRectAction* pRectAction = dynamic_cast<MetaRectAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(0, 0), Size(50, 60)),
                                     pRectAction->GetRect());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));
        pVDev->DrawRect(tools::Rectangle(Point(0, 0), Size(50, 60)), 5, 10);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action", MetaActionType::ROUNDRECT,
                                     pAction->GetType());
        MetaRoundRectAction* pRectAction = dynamic_cast<MetaRoundRectAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(0, 0), Size(50, 60)),
                                     pRectAction->GetRect());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Horizontal round rect wrong", static_cast<sal_uInt32>(5),
                                     pRectAction->GetHorzRound());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Vertical round rect wrong", static_cast<sal_uInt32>(10),
                                     pRectAction->GetVertRound());
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawEllipse)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawEllipse(tools::Rectangle(Point(0, 0), Size(50, 60)));

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a ellipse action", MetaActionType::ELLIPSE,
                                 pAction->GetType());
    MetaEllipseAction* pEllipseAction = dynamic_cast<MetaEllipseAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Ellipse rect wrong", tools::Rectangle(Point(0, 0), Size(50, 60)),
                                 pEllipseAction->GetRect());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPie)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::Rectangle aRect(Point(0, 0), Size(50, 60));

    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawPie(aRect, aRect.TopRight(), aRect.TopCenter());

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pie action", MetaActionType::PIE, pAction->GetType());
    MetaPieAction* pPieAction = dynamic_cast<MetaPieAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Pie rect wrong", aRect, pPieAction->GetRect());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Pie start point wrong", aRect.TopRight(),
                                 pPieAction->GetStartPoint());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Pie end point wrong", aRect.TopCenter(),
                                 pPieAction->GetEndPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawChord)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::Rectangle aRect(Point(21, 22), Size(4, 4));
    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawChord(aRect, Point(30, 31), Point(32, 33));

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a chord action", MetaActionType::CHORD, pAction->GetType());
    MetaChordAction* pChordAction = dynamic_cast<MetaChordAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Chord rect wrong", aRect, pChordAction->GetRect());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Chord start point wrong", Point(30, 31),
                                 pChordAction->GetStartPoint());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Chord end point wrong", Point(32, 33),
                                 pChordAction->GetEndPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawArc)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::Rectangle aRect(Point(1, 2), Size(4, 4));

    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawArc(aRect, Point(10, 11), Point(12, 13));

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a arc action", MetaActionType::ARC, pAction->GetType());
    MetaArcAction* pArcAction = dynamic_cast<MetaArcAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Arc rect wrong", aRect, pArcAction->GetRect());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Arc start point wrong", Point(10, 11),
                                 pArcAction->GetStartPoint());
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Arc end point wrong", Point(12, 13), pArcAction->GetEndPoint());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawCheckered)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawCheckered(Point(0, 0), Size(100, 100), 20, COL_BLACK, COL_WHITE);

    size_t nIndex = INITIAL_SETUP_ACTION_COUNT;

    MetaAction* pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not push action", MetaActionType::PUSH, pAction->GetType());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color", MetaActionType::LINECOLOR, pAction->GetType());

    // Row 1
    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 1",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    MetaFillColorAction* pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not first rect, row 1", MetaActionType::RECT, pAction->GetType());
    MetaRectAction* pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 1 not correct",
                                 tools::Rectangle(Point(0, 0), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 2",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not second rect, row 1", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 1 not correct",
                                 tools::Rectangle(Point(0, 20), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 3",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not third rect, row 1", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 1 not correct",
                                 tools::Rectangle(Point(0, 40), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 4",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 4", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not fourth rect, row 1", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 1 not correct",
                                 tools::Rectangle(Point(0, 60), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 1, rect 5",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 1, rect 5", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not fifth rect, row 1", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 1 not correct",
                                 tools::Rectangle(Point(0, 80), Size(21, 21)),
                                 pRectAction->GetRect());

    // Row 2
    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 1",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, first rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 2 not correct",
                                 tools::Rectangle(Point(20, 0), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 2",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, second rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 2 not correct",
                                 tools::Rectangle(Point(20, 20), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 3",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, third rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 2 not correct",
                                 tools::Rectangle(Point(20, 40), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 4",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 4", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fourth rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 2 not correct",
                                 tools::Rectangle(Point(20, 60), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 5",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 5", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fifth rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 2 not correct",
                                 tools::Rectangle(Point(20, 80), Size(21, 21)),
                                 pRectAction->GetRect());

    // Row 3
    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 1",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, first rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 3 not correct",
                                 tools::Rectangle(Point(40, 0), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 2",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, second rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 3 not correct",
                                 tools::Rectangle(Point(40, 20), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 3",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, third rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 3 not correct",
                                 tools::Rectangle(Point(40, 40), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 4",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 4", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, fourth rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 3 not correct",
                                 tools::Rectangle(Point(40, 60), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 3, rect 5",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 3, rect 5", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 3, fifth rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 3 not correct",
                                 tools::Rectangle(Point(40, 80), Size(21, 21)),
                                 pRectAction->GetRect());

    // Row 4
    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 1",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, first rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 4 not correct",
                                 tools::Rectangle(Point(60, 0), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 2",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, second rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 2 not correct",
                                 tools::Rectangle(Point(60, 20), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 3",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, third rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 4 not correct",
                                 tools::Rectangle(Point(60, 40), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 4",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 4", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, fourth rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 4 not correct",
                                 tools::Rectangle(Point(60, 60), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 4, rect 5",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 4, rect 5", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 4, fifth rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 4 not correct",
                                 tools::Rectangle(Point(60, 80), Size(21, 21)),
                                 pRectAction->GetRect());

    // Row 5
    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 5, rect 1",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 5, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 5, first rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 1, row 5 not correct",
                                 tools::Rectangle(Point(80, 0), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 5, rect 2",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 5, rect 1", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, second rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 2, row 2 not correct",
                                 tools::Rectangle(Point(80, 20), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 3",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 1", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, third rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 3, row 2 not correct",
                                 tools::Rectangle(Point(80, 40), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 4",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 4", COL_BLACK,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fourth rect", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 4, row 2 not correct",
                                 tools::Rectangle(Point(80, 60), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a fill color action for row 2, rect 5",
                                 MetaActionType::FILLCOLOR, pAction->GetType());
    pFillColorAction = dynamic_cast<MetaFillColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Fill color wrong for row 2, rect 5", COL_WHITE,
                                 pFillColorAction->GetColor());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not row 2, fifth rect", MetaActionType::RECT, pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rect 5, row 2 not correct",
                                 tools::Rectangle(Point(80, 80), Size(21, 21)),
                                 pRectAction->GetRect());

    nIndex++;
    pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not pop", MetaActionType::POP, pAction->GetType());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawBorder)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawBorder(tools::Rectangle(Point(0, 0), Size(50, 60)));

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (light gray)", MetaActionType::LINECOLOR,
                                 pAction->GetType());
    MetaLineColorAction* pLineColorAction = dynamic_cast<MetaLineColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not light gray", COL_LIGHTGRAY, pLineColorAction->GetColor());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 1);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action (light gray border)", MetaActionType::RECT,
                                 pAction->GetType());
    MetaRectAction* pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(1, 1), Size(49, 59)),
                                 pRectAction->GetRect());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 2);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a line color action (gray)", MetaActionType::LINECOLOR,
                                 pAction->GetType());
    pLineColorAction = dynamic_cast<MetaLineColorAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not gray", COL_GRAY, pLineColorAction->GetColor());

    pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT + 3);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a rect action (gray border)", MetaActionType::RECT,
                                 pAction->GetType());
    pRectAction = dynamic_cast<MetaRectAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Rectangle wrong", tools::Rectangle(Point(0, 0), Size(49, 59)),
                                 pRectAction->GetRect());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawWaveLine)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    pVDev->SetOutputSizePixel(Size(100, 100));
    pVDev->DrawWaveLine(Point(0, 0), Point(50, 0));

    MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a bitmap action", MetaActionType::BMPEXSCALEPART,
                                 pAction->GetType());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolyLine)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));
        tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
            tools::Rectangle(Point(10, 10), Size(80, 8))));

        pVDev->DrawPolyLine(aPolygon);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYLINE,
                                     pAction->GetType());
        MetaPolyLineAction* pPolyLineAction = dynamic_cast<MetaPolyLineAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polyline action is wrong", aPolygon,
                                     pPolyLineAction->GetPolygon());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));

        tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
            tools::Rectangle(Point(10, 10), Size(80, 8))));

        LineInfo aLineInfo(LineStyle::Dash, 10);
        aLineInfo.SetDashCount(5);
        aLineInfo.SetDashLen(10);
        aLineInfo.SetDotCount(3);
        aLineInfo.SetDotLen(13);
        aLineInfo.SetDistance(8);
        aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel);
        aLineInfo.SetLineCap(css::drawing::LineCap_BUTT);

        pVDev->DrawPolyLine(aPolygon, aLineInfo);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYLINE,
                                     pAction->GetType());
        MetaPolyLineAction* pPolyLineAction = dynamic_cast<MetaPolyLineAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polyline action is wrong", aPolygon,
                                     pPolyLineAction->GetPolygon());

        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash count wrong", static_cast<sal_uInt16>(5),
                                     pPolyLineAction->GetLineInfo().GetDashCount());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash len wrong", static_cast<double>(10),
                                     pPolyLineAction->GetLineInfo().GetDashLen());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot count wrong", static_cast<sal_uInt16>(3),
                                     pPolyLineAction->GetLineInfo().GetDotCount());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot len wrong", static_cast<double>(13),
                                     pPolyLineAction->GetLineInfo().GetDotLen());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Distance wrong", static_cast<double>(8),
                                     pPolyLineAction->GetLineInfo().GetDistance());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line join", basegfx::B2DLineJoin::Bevel,
                                     pPolyLineAction->GetLineInfo().GetLineJoin());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line cap", css::drawing::LineCap_BUTT,
                                     pPolyLineAction->GetLineInfo().GetLineCap());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));

        basegfx::B2DPolygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
                                         tools::Rectangle(Point(10, 10), Size(80, 8)))
                                         .getB2DPolygon());

        LineInfo aLineInfo(LineStyle::Dash, 10);
        aLineInfo.SetDashCount(5);
        aLineInfo.SetDashLen(10);
        aLineInfo.SetDotCount(3);
        aLineInfo.SetDotLen(13);
        aLineInfo.SetDistance(8);
        aLineInfo.SetLineJoin(basegfx::B2DLineJoin::Bevel);
        aLineInfo.SetLineCap(css::drawing::LineCap_BUTT);

        pVDev->DrawPolyLine(aPolygon, 3, basegfx::B2DLineJoin::Bevel, css::drawing::LineCap_BUTT,
                            basegfx::deg2rad(15.0));

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYLINE,
                                     pAction->GetType());
        MetaPolyLineAction* pPolyLineAction = dynamic_cast<MetaPolyLineAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polyline action is wrong", aPolygon,
                                     pPolyLineAction->GetPolygon().getB2DPolygon());

        CPPUNIT_ASSERT_EQUAL_MESSAGE("Width wrong", static_cast<double>(3),
                                     pPolyLineAction->GetLineInfo().GetWidth());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash count wrong", static_cast<sal_uInt16>(0),
                                     pPolyLineAction->GetLineInfo().GetDashCount());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dash len wrong", static_cast<double>(0),
                                     pPolyLineAction->GetLineInfo().GetDashLen());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot count wrong", static_cast<sal_uInt16>(0),
                                     pPolyLineAction->GetLineInfo().GetDotCount());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Dot len wrong", static_cast<double>(0),
                                     pPolyLineAction->GetLineInfo().GetDotLen());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Distance wrong", static_cast<double>(0),
                                     pPolyLineAction->GetLineInfo().GetDistance());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line join", basegfx::B2DLineJoin::Bevel,
                                     pPolyLineAction->GetLineInfo().GetLineJoin());
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Line cap", css::drawing::LineCap_BUTT,
                                     pPolyLineAction->GetLineInfo().GetLineCap());
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolygon)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));
        tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
            tools::Rectangle(Point(10, 10), Size(80, 8))));

        pVDev->DrawPolygon(aPolygon);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
                                     pAction->GetType());
        MetaPolygonAction* pPolygonAction = dynamic_cast<MetaPolygonAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polygon action is wrong", aPolygon,
                                     pPolygonAction->GetPolygon());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));
        tools::Polygon aPolygon(vcl::test::OutputDeviceTestCommon::createClosedBezierLoop(
            tools::Rectangle(Point(10, 10), Size(80, 8))));

        pVDev->DrawPolygon(aPolygon);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polygon action", MetaActionType::POLYGON,
                                     pAction->GetType());
        MetaPolygonAction* pPolygonAction = dynamic_cast<MetaPolygonAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Polygon in polygon action is wrong", aPolygon,
                                     pPolygonAction->GetPolygon());
    }
}

static tools::PolyPolygon createPolyPolygon()
{
    tools::Polygon aPolygon(4);

    aPolygon.SetPoint(Point(1, 8), 0);
    aPolygon.SetPoint(Point(2, 7), 1);
    aPolygon.SetPoint(Point(3, 6), 2);
    aPolygon.SetPoint(Point(4, 5), 3);

    tools::PolyPolygon aPolyPolygon(aPolygon);
    aPolyPolygon.Optimize(PolyOptimizeFlags::CLOSE);

    return aPolyPolygon;
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolyPolygon)
{
    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));

        tools::PolyPolygon aPolyPolygon = createPolyPolygon();

        pVDev->DrawPolyPolygon(aPolyPolygon);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action", MetaActionType::POLYPOLYGON,
                                     pAction->GetType());

        MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast<MetaPolyPolygonAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not the same polypolygon in polypolygon action", aPolyPolygon,
                                     pPolyPolygonAction->GetPolyPolygon());
    }

    {
        ScopedVclPtrInstance<VirtualDevice> pVDev;
        GDIMetaFile aMtf;
        aMtf.Record(pVDev.get());

        pVDev->SetOutputSizePixel(Size(100, 100));

        tools::PolyPolygon aPolyPolygon = createPolyPolygon();

        basegfx::B2DPolyPolygon aB2DPolyPolygon(aPolyPolygon.getB2DPolyPolygon());

        pVDev->DrawPolyPolygon(aB2DPolyPolygon);

        MetaAction* pAction = aMtf.GetAction(INITIAL_SETUP_ACTION_COUNT);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a polypolygon action", MetaActionType::POLYPOLYGON,
                                     pAction->GetType());

        /* these should match, but the equality operator does not work on PolyPolygon for some reason

        MetaPolyPolygonAction* pPolyPolygonAction = dynamic_cast<MetaPolyPolygonAction*>(pAction);
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Not the same polypolygon in polypolygon action", aPolyPolygon,
                                     pPolyPolygonAction->GetPolyPolygon());
        */
    }
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawRectAlpha1)
{
    Size aSize(100, 100);
    ScopedVclPtrInstance<VirtualDevice> pVDev(DeviceFormat::WITH_ALPHA);
    pVDev->SetOutputSizePixel(aSize, true, true);

    const Color RED_OPAQUE(ColorAlpha, 255, 255, 0, 0); // opaque red
    pVDev->SetLineColor(RED_OPAQUE);
    pVDev->SetFillColor(RED_OPAQUE);
    pVDev->DrawRect(tools::Rectangle(0, 0, 100, 100));

    CPPUNIT_ASSERT_EQUAL(RED_OPAQUE, pVDev->GetPixel(Point(0, 0)));
    CPPUNIT_ASSERT_EQUAL(RED_OPAQUE, pVDev->GetPixel(Point(1, 1)));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawRectAlpha2)
{
    Size aSize(100, 100);
    ScopedVclPtrInstance<VirtualDevice> pVDev(DeviceFormat::WITH_ALPHA);
    pVDev->SetOutputSizePixel(aSize, true, true);

    const Color RED_TRANSPARENT(ColorAlpha, 127, 255, 0, 0); // 50% transparent red
    pVDev->SetLineColor(RED_TRANSPARENT);
    pVDev->SetFillColor(RED_TRANSPARENT);
    pVDev->DrawRect(tools::Rectangle(0, 0, 100, 100));

    CPPUNIT_ASSERT_EQUAL(RED_TRANSPARENT, pVDev->GetPixel(Point(0, 0)));
    CPPUNIT_ASSERT_EQUAL(RED_TRANSPARENT, pVDev->GetPixel(Point(1, 1)));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolyPolygonAlpha1)
{
    Size aSize(100, 100);
    ScopedVclPtrInstance<VirtualDevice> pVDev(DeviceFormat::WITH_ALPHA);
    pVDev->SetOutputSizePixel(aSize, /*bErase*/ true, /*bAlphaMaskTransparent*/ true);

    // create a square polypolygon
    tools::Polygon aPolygon(4);
    aPolygon.SetPoint(Point(0, 0), 0);
    aPolygon.SetPoint(Point(0, 100), 1);
    aPolygon.SetPoint(Point(100, 100), 2);
    aPolygon.SetPoint(Point(0, 0), 3);
    tools::PolyPolygon aPolyPolygon(aPolygon);
    aPolyPolygon.Optimize(PolyOptimizeFlags::CLOSE);
    basegfx::B2DPolyPolygon aB2DPolyPolygon(aPolyPolygon.getB2DPolyPolygon());

    const Color RED_OPAQUE(ColorAlpha, 255, 255, 0, 0); // opaque red

    pVDev->SetLineColor(RED_OPAQUE);
    pVDev->SetFillColor(RED_OPAQUE);
    pVDev->DrawPolyPolygon(aB2DPolyPolygon);

    CPPUNIT_ASSERT_EQUAL(RED_OPAQUE, pVDev->GetPixel(Point(0, 0)));
    CPPUNIT_ASSERT_EQUAL(RED_OPAQUE, pVDev->GetPixel(Point(1, 1)));
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawPolyPolygonAlpha2)
{
    Size aSize(100, 100);
    ScopedVclPtrInstance<VirtualDevice> pVDev(DeviceFormat::WITH_ALPHA);
    pVDev->SetOutputSizePixel(aSize, /*bErase*/ true, /*bAlphaMaskTransparent*/ true);

    // create a square polypolygon
    tools::Polygon aPolygon(4);
    aPolygon.SetPoint(Point(0, 0), 0);
    aPolygon.SetPoint(Point(0, 100), 1);
    aPolygon.SetPoint(Point(100, 100), 2);
    aPolygon.SetPoint(Point(0, 0), 3);
    tools::PolyPolygon aPolyPolygon(aPolygon);
    aPolyPolygon.Optimize(PolyOptimizeFlags::CLOSE);
    basegfx::B2DPolyPolygon aB2DPolyPolygon(aPolyPolygon.getB2DPolyPolygon());

    const Color RED_TRANSPARENT(ColorAlpha, 127, 255, 0, 0); // 50% transparent red

    pVDev->SetLineColor(RED_TRANSPARENT);
    pVDev->SetFillColor(RED_TRANSPARENT);
    pVDev->DrawPolyPolygon(aB2DPolyPolygon);

    CPPUNIT_ASSERT_EQUAL(RED_TRANSPARENT, pVDev->GetPixel(Point(0, 0)));
    CPPUNIT_ASSERT_EQUAL(RED_TRANSPARENT, pVDev->GetPixel(Point(1, 1)));
}

static size_t ClipGradientTest(GDIMetaFile& rMtf, size_t nIndex)
{
    MetaAction* pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a comment action", MetaActionType::COMMENT,
                                 pAction->GetType());

    nIndex++;
    pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradientex action", MetaActionType::GRADIENTEX,
                                 pAction->GetType());

    // clip gradient
    nIndex++;
    pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a push action", MetaActionType::PUSH, pAction->GetType());
    MetaPushAction* pPushAction = dynamic_cast<MetaPushAction*>(pAction);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not using CLIPREGION push flags", vcl::PushFlags::CLIPREGION,
                                 pPushAction->GetFlags());

    nIndex++;
    pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a CLIPREGION action", MetaActionType::CLIPREGION,
                                 pAction->GetType());

    nIndex++;
    pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action", MetaActionType::GRADIENT,
                                 pAction->GetType());

    nIndex++;
    pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a pop action", MetaActionType::POP, pAction->GetType());

    nIndex++;
    pAction = rMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a comment action", MetaActionType::COMMENT,
                                 pAction->GetType());

    return nIndex;
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_rect_linear)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::Rectangle aRect(Point(10, 10), Size(40, 40));
    pVDev->SetOutputSizePixel(Size(100, 100));

    Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_RED, COL_WHITE);
    aGradient.SetBorder(100);

    pVDev->DrawGradient(aRect, aGradient);

    size_t nIndex = INITIAL_SETUP_ACTION_COUNT;

    MetaAction* pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action (rectangle area)", MetaActionType::GRADIENT,
                                 pAction->GetType());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_rect_axial)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::Rectangle aRect(Point(10, 10), Size(40, 40));
    pVDev->SetOutputSizePixel(Size(100, 100));

    Gradient aGradient(css::awt::GradientStyle_AXIAL, COL_RED, COL_WHITE);
    aGradient.SetBorder(100);

    pVDev->DrawGradient(aRect, aGradient);

    size_t nIndex = INITIAL_SETUP_ACTION_COUNT;

    MetaAction* pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action (rectangle area)", MetaActionType::GRADIENT,
                                 pAction->GetType());
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_polygon_linear)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::PolyPolygon aPolyPolygon = createPolyPolygon();

    pVDev->SetOutputSizePixel(Size(100, 100));

    Gradient aGradient(css::awt::GradientStyle_LINEAR, COL_RED, COL_WHITE);
    aGradient.SetBorder(100);

    pVDev->DrawGradient(aPolyPolygon, aGradient);

    ClipGradientTest(aMtf, INITIAL_SETUP_ACTION_COUNT);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_polygon_axial)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::PolyPolygon aPolyPolygon = createPolyPolygon();

    pVDev->SetOutputSizePixel(Size(100, 100));

    Gradient aGradient(css::awt::GradientStyle_AXIAL, COL_RED, COL_WHITE);
    aGradient.SetBorder(100);

    pVDev->DrawGradient(aPolyPolygon, aGradient);

    ClipGradientTest(aMtf, INITIAL_SETUP_ACTION_COUNT);
}

CPPUNIT_TEST_FIXTURE(VclOutdevTest, testDrawGradient_rect_complex)
{
    ScopedVclPtrInstance<VirtualDevice> pVDev;
    GDIMetaFile aMtf;
    aMtf.Record(pVDev.get());

    tools::Rectangle aRect(Point(10, 10), Size(40, 40));
    pVDev->SetOutputSizePixel(Size(1000, 1000));

    Gradient aGradient(css::awt::GradientStyle_SQUARE, COL_RED, COL_WHITE);
    aGradient.SetBorder(10);
    pVDev->DrawGradient(aRect, aGradient);

    size_t nIndex = INITIAL_SETUP_ACTION_COUNT;

    MetaAction* pAction = aMtf.GetAction(nIndex);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Not a gradient action (rectangle area)", MetaActionType::GRADIENT,
                                 pAction->GetType());
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
