/* -*- 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <memory>
#include <tools/vcompat.hxx>
#include <tools/stream.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/region.hxx>
#include <regionband.hxx>

#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygonclipper.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <tools/poly.hxx>
#include <comphelper/configuration.hxx>

namespace
{
    /** Return <TRUE/> when the given polygon is rectilinear and oriented so that
        all sides are either horizontal or vertical.
    */
    bool ImplIsPolygonRectilinear (const tools::PolyPolygon& rPolyPoly)
    {
        // Iterate over all polygons.
        const sal_uInt16 nPolyCount = rPolyPoly.Count();
        for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
        {
            const tools::Polygon&  aPoly = rPolyPoly.GetObject(nPoly);

            // Iterate over all edges of the current polygon.
            const sal_uInt16 nSize = aPoly.GetSize();

            if (nSize < 2)
                continue;
            Point aPoint (aPoly.GetPoint(0));
            const Point aLastPoint (aPoint);
            for (sal_uInt16 nPoint = 1; nPoint < nSize; ++nPoint)
            {
                const Point aNextPoint (aPoly.GetPoint(nPoint));
                // When there is at least one edge that is neither vertical nor
                // horizontal then the entire polygon is not rectilinear (and
                // oriented along primary axes.)
                if (aPoint.X() != aNextPoint.X() && aPoint.Y() != aNextPoint.Y())
                    return false;

                aPoint = aNextPoint;
            }
            // Compare closing edge.
            if (aLastPoint.X() != aPoint.X() && aLastPoint.Y() != aPoint.Y())
                return false;
        }
        return true;
    }

    /** Convert a rectilinear polygon (that is oriented along the primary axes)
        to a list of bands.  For this special form of polygon we can use an
        optimization that prevents the creation of one band per y value.
        However, it still is possible that some temporary bands are created that
        later can be optimized away.
        @param rPolyPolygon
            A set of zero, one, or more polygons, nested or not, that are
            converted into a list of bands.
        @return
            A new RegionBand object is returned that contains the bands that
            represent the given poly-polygon.
    */
    std::shared_ptr<RegionBand> ImplRectilinearPolygonToBands(const tools::PolyPolygon& rPolyPoly)
    {
        OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly));

        // Create a new RegionBand object as container of the bands.
        std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
        tools::Long nLineId = 0;

        // Iterate over all polygons.
        const sal_uInt16 nPolyCount = rPolyPoly.Count();
        for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
        {
            const tools::Polygon&  aPoly = rPolyPoly.GetObject(nPoly);

            // Iterate over all edges of the current polygon.
            const sal_uInt16 nSize = aPoly.GetSize();
            if (nSize < 2)
                continue;
            // Avoid fetching every point twice (each point is the start point
            // of one and the end point of another edge.)
            Point aStart (aPoly.GetPoint(0));
            Point aEnd;
            for (sal_uInt16 nPoint = 1; nPoint <= nSize; ++nPoint, aStart=aEnd)
            {
                // We take the implicit closing edge into account by mapping
                // index nSize to 0.
                aEnd = aPoly.GetPoint(nPoint%nSize);
                if (aStart.Y() == aEnd.Y())
                {
                    // Horizontal lines are ignored.
                    continue;
                }

                // At this point the line has to be vertical.
                OSL_ASSERT(aStart.X() == aEnd.X());

                // Sort y-coordinates to simplify the algorithm and store the
                // direction separately.  The direction is calculated as it is
                // in other places (but seems to be the wrong way.)
                const tools::Long nTop (::std::min(aStart.Y(), aEnd.Y()));
                const tools::Long nBottom (::std::max(aStart.Y(), aEnd.Y()));
                const LineType eLineType (aStart.Y() > aEnd.Y() ? LineType::Descending : LineType::Ascending);

                // Make sure that the current line is covered by bands.
                pRegionBand->ImplAddMissingBands(nTop,nBottom);

                // Find top-most band that may contain nTop.
                ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
                while (pBand!=nullptr && pBand->mnYBottom < nTop)
                    pBand = pBand->mpNextBand;
                ImplRegionBand* pTopBand = pBand;
                // If necessary split the band at nTop so that nTop is contained
                // in the lower band.
                if (pBand!=nullptr
                       // Prevent the current band from becoming 0 pixel high
                    && pBand->mnYTop<nTop
                       // this allows the lowest pixel of the band to be split off
                    && pBand->mnYBottom>=nTop
                       // do not split a band that is just one pixel high
                    && pBand->mnYTop<pBand->mnYBottom-1)
                {
                    // Split the top band.
                    pTopBand = pBand->SplitBand(nTop);
                }

                // Advance to band that may contain nBottom.
                while (pBand!=nullptr && pBand->mnYBottom < nBottom)
                    pBand = pBand->mpNextBand;
                // The lowest band may have to be split at nBottom so that
                // nBottom itself remains in the upper band.
                if (pBand!=nullptr
                       // allow the current band becoming 1 pixel high
                    && pBand->mnYTop<=nBottom
                       // prevent splitting off a band that is 0 pixel high
                    && pBand->mnYBottom>nBottom
                       // do not split a band that is just one pixel high
                    && pBand->mnYTop<pBand->mnYBottom-1)
                {
                    // Split the bottom band.
                    pBand->SplitBand(nBottom+1);
                }

                // Note that we remember the top band (in pTopBand) but not the
                // bottom band.  The later can be determined by comparing y
                // coordinates.

                // Add the x-value as point to all bands in the nTop->nBottom range.
                for (pBand=pTopBand; pBand!=nullptr&&pBand->mnYTop<=nBottom; pBand=pBand->mpNextBand)
                    pBand->InsertPoint(aStart.X(), nLineId++, true, eLineType);
            }
        }

        return pRegionBand;
    }

    /** Convert a general polygon (one for which ImplIsPolygonRectilinear()
        returns <FALSE/>) to bands.
    */
    std::shared_ptr<RegionBand> ImplGeneralPolygonToBands(const tools::PolyPolygon& rPolyPoly, const tools::Rectangle& rPolygonBoundingBox)
    {
        tools::Long nLineID = 0;

        // initialisation and creation of Bands
        std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
        pRegionBand->CreateBandRange(rPolygonBoundingBox.Top(), rPolygonBoundingBox.Bottom());

        // insert polygons
        const sal_uInt16 nPolyCount = rPolyPoly.Count();

        for ( sal_uInt16 nPoly = 0; nPoly < nPolyCount; nPoly++ )
        {
            // get reference to current polygon
            const tools::Polygon&  aPoly = rPolyPoly.GetObject( nPoly );
            const sal_uInt16    nSize = aPoly.GetSize();

            // not enough points ( <= 2 )? -> nothing to do!
            if ( nSize <= 2 )
                continue;

            // band the polygon
            for ( sal_uInt16 nPoint = 1; nPoint < nSize; nPoint++ )
            {
                pRegionBand->InsertLine( aPoly.GetPoint(nPoint-1), aPoly.GetPoint(nPoint), nLineID++ );
            }

            // close polygon with line from first point to last point, if necessary
            const Point rLastPoint = aPoly.GetPoint(nSize-1);
            const Point rFirstPoint = aPoly.GetPoint(0);

            if ( rLastPoint != rFirstPoint )
            {
                pRegionBand->InsertLine( rLastPoint, rFirstPoint, nLineID++ );
            }
        }

        return pRegionBand;
    }
} // end of anonymous namespace

namespace vcl {

bool vcl::Region::IsEmpty() const
{
    return !mbIsNull && !mpB2DPolyPolygon && !mpPolyPolygon && !mpRegionBand;
}


static std::shared_ptr<RegionBand> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon& rPolyPolygon)
{
    std::shared_ptr<RegionBand> pRetval;

    if(rPolyPolygon.Count())
    {
        // ensure to subdivide when bezier segments are used, it's going to
        // be expanded to rectangles
        tools::PolyPolygon aPolyPolygon;

        rPolyPolygon.AdaptiveSubdivide(aPolyPolygon);

        if(aPolyPolygon.Count())
        {
            const tools::Rectangle aRect(aPolyPolygon.GetBoundRect());

            if(!aRect.IsEmpty())
            {
                if(ImplIsPolygonRectilinear(aPolyPolygon))
                {
                    // For rectilinear polygons there is an optimized band conversion.
                    pRetval = ImplRectilinearPolygonToBands(aPolyPolygon);
                }
                else
                {
                    pRetval = ImplGeneralPolygonToBands(aPolyPolygon, aRect);
                }

                // Convert points into seps.
                if(pRetval)
                {
                    pRetval->processPoints();

                    // Optimize list of bands.  Adjacent bands with identical lists
                    // of seps are joined.
                    if(!pRetval->OptimizeBandList())
                    {
                        pRetval.reset();
                    }
                }
            }
        }
    }

    return pRetval;
}

tools::PolyPolygon vcl::Region::ImplCreatePolyPolygonFromRegionBand() const
{
    tools::PolyPolygon aRetval;

    if(getRegionBand())
    {
        RectangleVector aRectangles;
        GetRegionRectangles(aRectangles);

        for (auto const& rectangle : aRectangles)
        {
            aRetval.Insert( tools::Polygon(rectangle) );
        }
    }
    else
    {
        OSL_ENSURE(false, "Called with no local RegionBand (!)");
    }

    return aRetval;
}

basegfx::B2DPolyPolygon vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const
{
    tools::PolyPolygon aPoly(ImplCreatePolyPolygonFromRegionBand());

    return aPoly.getB2DPolyPolygon();
}

Region::Region(bool bIsNull)
:   mbIsNull(bIsNull)
{
}

Region::Region(const tools::Rectangle& rRect)
:   mbIsNull(false)
{
    if (!rRect.IsEmpty())
        mpRegionBand = std::make_shared<RegionBand>(rRect);
}

Region::Region(const tools::Polygon& rPolygon)
:   mbIsNull(false)
{

    if(rPolygon.GetSize())
    {
        ImplCreatePolyPolyRegion(tools::PolyPolygon(rPolygon));
    }
}

Region::Region(const tools::PolyPolygon& rPolyPoly)
:   mbIsNull(false)
{

    if(rPolyPoly.Count())
    {
        ImplCreatePolyPolyRegion(rPolyPoly);
    }
}

Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
:   mbIsNull(false)
{

    if(rPolyPoly.count())
    {
        ImplCreatePolyPolyRegion(rPolyPoly);
    }
}

Region::Region(const vcl::Region&) = default;

Region::Region(vcl::Region&& rRegion) noexcept
:   mpB2DPolyPolygon(std::move(rRegion.mpB2DPolyPolygon)),
    mpPolyPolygon(std::move(rRegion.mpPolyPolygon)),
    mpRegionBand(std::move(rRegion.mpRegionBand)),
    mbIsNull(rRegion.mbIsNull)
{
    rRegion.mbIsNull = true;
}

Region::~Region() = default;

void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon& rPolyPoly )
{
    const sal_uInt16 nPolyCount = rPolyPoly.Count();

    if(!nPolyCount)
        return;

    // polypolygon empty? -> empty region
    const tools::Rectangle aRect(rPolyPoly.GetBoundRect());

    if(aRect.IsEmpty())
        return;

    // width OR height == 1 ? => Rectangular region
    if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
    {
        mpRegionBand = std::make_shared<RegionBand>(aRect);
    }
    else
    {
        mpPolyPolygon = rPolyPoly;
    }

    mbIsNull = false;
}

void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
{
    if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
    {
        mpB2DPolyPolygon = rPolyPoly;
        mbIsNull = false;
    }
}

void vcl::Region::Move( tools::Long nHorzMove, tools::Long nVertMove )
{
    if(IsNull() || IsEmpty())
    {
        // empty or null need no move
        return;
    }

    if(!nHorzMove && !nVertMove)
    {
        // no move defined
        return;
    }

    if(getB2DPolyPolygon())
    {
        basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());

        aPoly.translate(nHorzMove, nVertMove);
        if (aPoly.count())
            mpB2DPolyPolygon = aPoly;
        else
            mpB2DPolyPolygon.reset();
        mpPolyPolygon.reset();
        mpRegionBand.reset();
    }
    else if(getPolyPolygon())
    {
        tools::PolyPolygon aPoly(*getPolyPolygon());

        aPoly.Move(nHorzMove, nVertMove);
        mpB2DPolyPolygon.reset();
        if (aPoly.Count())
            mpPolyPolygon = aPoly;
        else
            mpPolyPolygon.reset();
        mpRegionBand.reset();
    }
    else if(getRegionBand())
    {
        std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());

        pNew->Move(nHorzMove, nVertMove);
        mpB2DPolyPolygon.reset();
        mpPolyPolygon.reset();
        mpRegionBand = std::move(pNew);
    }
    else
    {
        OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
    }
}

void vcl::Region::Scale( double fScaleX, double fScaleY )
{
    if(IsNull() || IsEmpty())
    {
        // empty or null need no scale
        return;
    }

    if(basegfx::fTools::equalZero(fScaleX) && basegfx::fTools::equalZero(fScaleY))
    {
        // no scale defined
        return;
    }

    if(getB2DPolyPolygon())
    {
        basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());

        aPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX, fScaleY));
        if (aPoly.count())
            mpB2DPolyPolygon = aPoly;
        else
            mpB2DPolyPolygon.reset();
        mpPolyPolygon.reset();
        mpRegionBand.reset();
    }
    else if(getPolyPolygon())
    {
        tools::PolyPolygon aPoly(*getPolyPolygon());

        aPoly.Scale(fScaleX, fScaleY);
        mpB2DPolyPolygon.reset();
        if (aPoly.Count())
            mpPolyPolygon = aPoly;
        else
            mpPolyPolygon.reset();
        mpRegionBand.reset();
    }
    else if(getRegionBand())
    {
        std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());

        pNew->Scale(fScaleX, fScaleY);
        mpB2DPolyPolygon.reset();
        mpPolyPolygon.reset();
        mpRegionBand = std::move(pNew);
    }
    else
    {
        OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
    }
}

void vcl::Region::Union( const tools::Rectangle& rRect )
{
    if(rRect.IsEmpty())
    {
        // empty rectangle will not expand the existing union, nothing to do
        return;
    }

    if(IsEmpty())
    {
        // no local data, the union will be equal to source. Create using rectangle
        *this = rRect;
        return;
    }

    if(HasPolyPolygonOrB2DPolyPolygon())
    {
        // get this B2DPolyPolygon, solve on polygon base
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);

        if(!aThisPolyPoly.count())
        {
            // no local polygon, use the rectangle as new region
            *this = rRect;
        }
        else
        {
            // get the other B2DPolyPolygon and use logical Or-Operation
            const basegfx::B2DPolygon aRectPoly(
                basegfx::utils::createPolygonFromRect(
                        vcl::unotools::b2DRectangleFromRectangle(rRect)));
            const basegfx::B2DPolyPolygon aClip(
                basegfx::utils::solvePolygonOperationOr(
                    aThisPolyPoly,
                    basegfx::B2DPolyPolygon(aRectPoly)));
            *this = vcl::Region(aClip);
        }

        return;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // no region band, create using the rectangle
        *this = rRect;
        return;
    }

    std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*pCurrent);

    // get justified rectangle
    const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
    const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
    const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
    const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));

    // insert bands if the boundaries are not already in the list
    pNew->InsertBands(nTop, nBottom);

    // process union
    pNew->Union(nLeft, nTop, nRight, nBottom);

    // cleanup
    if(!pNew->OptimizeBandList())
    {
        pNew.reset();
    }

    mpRegionBand = std::move(pNew);
}

void vcl::Region::Intersect( const tools::Rectangle& rRect )
{
    if ( rRect.IsEmpty() )
    {
        // empty rectangle will create empty region
        SetEmpty();
        return;
    }

    if(IsNull())
    {
        // null region (everything) intersect with rect will give rect
        *this = rRect;
        return;
    }

    if(IsEmpty())
    {
        // no content, cannot get more empty
        return;
    }

    if(HasPolyPolygonOrB2DPolyPolygon())
    {
        // if polygon data prefer double precision, the other will be lost (if buffered)
        if(getB2DPolyPolygon())
        {
            const basegfx::B2DPolyPolygon aPoly(
                basegfx::utils::clipPolyPolygonOnRange(
                    *getB2DPolyPolygon(),
                    basegfx::B2DRange(
                        rRect.Left(),
                        rRect.Top(),
                        rRect.Right() + 1,
                        rRect.Bottom() + 1),
                    true,
                    false));

            if (aPoly.count())
                mpB2DPolyPolygon = aPoly;
            else
                mpB2DPolyPolygon.reset();
            mpPolyPolygon.reset();
            mpRegionBand.reset();
        }
        else // if(getPolyPolygon())
        {
            tools::PolyPolygon aPoly(*getPolyPolygon());

            // use the PolyPolygon::Clip method for rectangles, this is
            // fairly simple (does not even use GPC) and saves us from
            // unnecessary banding
            aPoly.Clip(rRect);

            mpB2DPolyPolygon.reset();
            if (aPoly.Count())
                mpPolyPolygon = aPoly;
            else
                mpPolyPolygon.reset();
            mpRegionBand.reset();
        }

        return;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // region is empty -> nothing to do!
        return;
    }

    std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));

    // get justified rectangle
    const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
    const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
    const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
    const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));

    // insert bands if the boundaries are not already in the list
    pNew->InsertBands(nTop, nBottom);

    // process intersect
    pNew->Intersect(nLeft, nTop, nRight, nBottom);

    // cleanup
    if(!pNew->OptimizeBandList())
    {
        pNew.reset();
    }

    mpRegionBand = std::move(pNew);
}

void vcl::Region::Exclude( const tools::Rectangle& rRect )
{
    if ( rRect.IsEmpty() )
    {
        // excluding nothing will do no change
        return;
    }

    if(IsEmpty())
    {
        // cannot exclude from empty, done
        return;
    }

    if(IsNull())
    {
        // error; cannot exclude from null region since this is not representable
        // in the data
        OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
        return;
    }

    if( HasPolyPolygonOrB2DPolyPolygon() )
    {
        // get this B2DPolyPolygon
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);

        if(!aThisPolyPoly.count())
        {
            // when local polygon is empty, nothing can be excluded
            return;
        }

        // get the other B2DPolyPolygon
        const basegfx::B2DPolygon aRectPoly(
            basegfx::utils::createPolygonFromRect(
                vcl::unotools::b2DRectangleFromRectangle(rRect)));
        const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
        const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly);

        *this = vcl::Region(aClip);

        return;
    }

    // only region band mode possibility left here or null/empty
    if(!mpRegionBand)
    {
        // empty? -> done!
        return;
    }

    std::shared_ptr<RegionBand>& pNew = mpRegionBand;
    // only make a copy if someone else is also using it
    if (pNew.use_count() > 1)
        pNew = std::make_shared<RegionBand>(*pNew);

    // get justified rectangle
    const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
    const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
    const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
    const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));

    // insert bands if the boundaries are not already in the list
    pNew->InsertBands(nTop, nBottom);

    // process exclude
    pNew->Exclude(nLeft, nTop, nRight, nBottom);

    // cleanup
    if(!pNew->OptimizeBandList())
        pNew.reset();
}

void vcl::Region::XOr( const tools::Rectangle& rRect )
{
    if ( rRect.IsEmpty() )
    {
        // empty rectangle will not change local content
        return;
    }

    if(IsEmpty())
    {
        // rRect will be the xored-form (local off, rect on)
        *this = rRect;
        return;
    }

    if(IsNull())
    {
        // error; cannot exclude from null region since this is not representable
        // in the data
        OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
        return;
    }

    if( HasPolyPolygonOrB2DPolyPolygon() )
    {
        // get this B2DPolyPolygon
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );

        if(!aThisPolyPoly.count())
        {
            // no local content, XOr will be equal to rectangle
            *this = rRect;
            return;
        }

        // get the other B2DPolyPolygon
        const basegfx::B2DPolygon aRectPoly(
            basegfx::utils::createPolygonFromRect(
                vcl::unotools::b2DRectangleFromRectangle(rRect)));
        const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
        const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly);

        *this = vcl::Region(aClip);

        return;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // rRect will be the xored-form (local off, rect on)
        *this = rRect;
        return;
    }

    // only region band mode possibility left here or null/empty
    std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*getRegionBand()));

    // get justified rectangle
    const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
    const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
    const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
    const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));

    // insert bands if the boundaries are not already in the list
    pNew->InsertBands(nTop, nBottom);

    // process xor
    pNew->XOr(nLeft, nTop, nRight, nBottom);

    // cleanup
    if(!pNew->OptimizeBandList())
    {
        pNew.reset();
    }

    mpRegionBand = std::move(pNew);
}

void vcl::Region::Union( const vcl::Region& rRegion )
{
    if(rRegion.IsEmpty())
    {
        // no extension at all
        return;
    }

    if(rRegion.IsNull())
    {
        // extending with null region -> null region
        *this = vcl::Region(true);
        return;
    }

    if(IsEmpty())
    {
        // local is empty, union will give source region
        *this = rRegion;
        return;
    }

    if(IsNull())
    {
        // already fully expanded (is null region), cannot be extended
        return;
    }

    if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
    {
        // get this B2DPolyPolygon
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);

        if(!aThisPolyPoly.count())
        {
            // when no local content, union will be equal to rRegion
            *this = rRegion;
            return;
        }

        // get the other B2DPolyPolygon
        basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
        aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly);

        // use logical OR operation
        basegfx::B2DPolyPolygon aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly, aOtherPolyPoly));

        *this = vcl::Region( aClip );
        return;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // local is empty, union will give source region
        *this = rRegion;
        return;
    }

    const RegionBand* pSource = rRegion.getRegionBand();

    if(!pSource)
    {
        // no extension at all
        return;
    }

    // prepare source and target
    std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));

    // union with source
    pNew->Union(*pSource);

    // cleanup
    if(!pNew->OptimizeBandList())
    {
        pNew.reset();
    }

    mpRegionBand = std::move(pNew);
}

void vcl::Region::Intersect( const vcl::Region& rRegion )
{
    // same instance data? -> nothing to do!
    if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
    {
        return;
    }

    if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
    {
        return;
    }

    if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
    {
        return;
    }

    if(rRegion.IsNull())
    {
        // source region is null-region, intersect will not change local region
        return;
    }

    if(IsNull())
    {
        // when local region is null-region, intersect will be equal to source
        *this = rRegion;
        return;
    }

    if(rRegion.IsEmpty())
    {
        // source region is empty, intersection will always be empty
        SetEmpty();
        return;
    }

    if(IsEmpty())
    {
        // local region is empty, cannot get more empty than that. Nothing to do
        return;
    }

    if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
    {
        // get this B2DPolyPolygon
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        if(!aThisPolyPoly.count())
        {
            // local region is empty, cannot get more empty than that. Nothing to do
            return;
        }

        // get the other B2DPolyPolygon
        basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());

        if(!aOtherPolyPoly.count())
        {
            // source region is empty, intersection will always be empty
            SetEmpty();
            return;
        }

        static size_t gPointLimit = !comphelper::IsFuzzing() ? SAL_MAX_SIZE : 8192;
        size_t nPointLimit(gPointLimit);
        const basegfx::B2DPolyPolygon aClip(
            basegfx::utils::clipPolyPolygonOnPolyPolygon(
                aOtherPolyPoly,
                aThisPolyPoly,
                true,
                false,
                &nPointLimit));
        *this = vcl::Region( aClip );
        return;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // local region is empty, cannot get more empty than that. Nothing to do
        return;
    }

    const RegionBand* pSource = rRegion.getRegionBand();

    if(!pSource)
    {
        // source region is empty, intersection will always be empty
        SetEmpty();
        return;
    }

    // both RegionBands exist and are not empty
    if(pCurrent->getRectangleCount() + 2 < pSource->getRectangleCount())
    {
        // when we have less rectangles, turn around the call
        vcl::Region aTempRegion = rRegion;
        aTempRegion.Intersect( *this );
        *this = std::move(aTempRegion);
    }
    else
    {
        // prepare new regionBand
        std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));

        // intersect with source
        pNew->Intersect(*pSource);

        // cleanup
        if(!pNew->OptimizeBandList())
        {
            pNew.reset();
        }

        mpRegionBand = std::move(pNew);
    }
}

void vcl::Region::Exclude( const vcl::Region& rRegion )
{
    if ( rRegion.IsEmpty() )
    {
        // excluding nothing will do no change
        return;
    }

    if ( rRegion.IsNull() )
    {
        // excluding everything will create empty region
        SetEmpty();
        return;
    }

    if(IsEmpty())
    {
        // cannot exclude from empty, done
        return;
    }

    if(IsNull())
    {
        // error; cannot exclude from null region since this is not representable
        // in the data
        OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
        return;
    }

    if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
    {
        // get this B2DPolyPolygon
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        if(!aThisPolyPoly.count())
        {
            // cannot exclude from empty, done
            return;
        }

        aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );

        // get the other B2DPolyPolygon
        basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
        aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );

        basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly, aOtherPolyPoly );
        *this = vcl::Region( aClip );
        return;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // cannot exclude from empty, done
        return;
    }

    const RegionBand* pSource = rRegion.getRegionBand();

    if(!pSource)
    {
        // excluding nothing will do no change
        return;
    }

    // prepare source and target
    std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));

    // union with source
    const bool bSuccess(pNew->Exclude(*pSource));

    // cleanup
    if(!bSuccess)
    {
        pNew.reset();
    }

    mpRegionBand = std::move(pNew);
}

bool vcl::Region::XOr( const vcl::Region& rRegion )
{
    if ( rRegion.IsEmpty() )
    {
        // empty region will not change local content
        return true;
    }

    if ( rRegion.IsNull() )
    {
        // error; cannot exclude null region from local since this is not representable
        // in the data
        OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
        return true;
    }

    if(IsEmpty())
    {
        // rRect will be the xored-form (local off, rect on)
        *this = rRegion;
        return true;
    }

    if(IsNull())
    {
        // error: cannot exclude from null region since this is not representable
        // in the data
        OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
        return false;
    }

    if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
    {
        // get this B2DPolyPolygon
        basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());

        if(!aThisPolyPoly.count())
        {
            // rRect will be the xored-form (local off, rect on)
            *this = rRegion;
            return true;
        }

        aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );

        // get the other B2DPolyPolygon
        basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
        aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );

        basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor( aThisPolyPoly, aOtherPolyPoly );
        *this = vcl::Region( aClip );
        return true;
    }

    // only region band mode possibility left here or null/empty
    const RegionBand* pCurrent = getRegionBand();

    if(!pCurrent)
    {
        // rRect will be the xored-form (local off, rect on)
        *this = rRegion;
        return true;
    }

    const RegionBand* pSource = rRegion.getRegionBand();

    if(!pSource)
    {
        // empty region will not change local content
        return true;
    }

    // prepare source and target
    std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));

    // union with source
    pNew->XOr(*pSource);

    // cleanup
    if(!pNew->OptimizeBandList())
    {
        pNew.reset();
    }

    mpRegionBand = std::move(pNew);

    return true;
}

tools::Rectangle vcl::Region::GetBoundRect() const
{
    if(IsEmpty())
    {
        // no internal data? -> region is empty!
        return tools::Rectangle();
    }

    if(IsNull())
    {
        // error; null region has no BoundRect
        // OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)");
        return tools::Rectangle();
    }

    // prefer double precision source
    if(getB2DPolyPolygon())
    {
        const basegfx::B2DRange aRange(getB2DPolyPolygon()->getB2DRange());

        if(aRange.isEmpty())
        {
            // emulate PolyPolygon::GetBoundRect() when empty polygon
            return tools::Rectangle();
        }
        else
        {
            // #i122149# corrected rounding, no need for ceil() and floor() here
            return tools::Rectangle(
                basegfx::fround<tools::Long>(aRange.getMinX()), basegfx::fround<tools::Long>(aRange.getMinY()),
                basegfx::fround<tools::Long>(aRange.getMaxX()), basegfx::fround<tools::Long>(aRange.getMaxY()));
        }
    }

    if(getPolyPolygon())
    {
        return getPolyPolygon()->GetBoundRect();
    }

    if(getRegionBand())
    {
        return getRegionBand()->GetBoundRect();
    }

    return tools::Rectangle();
}

tools::PolyPolygon vcl::Region::GetAsPolyPolygon() const
{
    if(getPolyPolygon())
    {
        return *getPolyPolygon();
    }

    if(getB2DPolyPolygon())
    {
        // the polygon needs to be converted, buffer the down conversion
        const tools::PolyPolygon aPolyPolgon(*getB2DPolyPolygon());
        const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;

        return *getPolyPolygon();
    }

    if(getRegionBand())
    {
        // the BandRegion needs to be converted, buffer the conversion
        const tools::PolyPolygon aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
        const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;

        return *getPolyPolygon();
    }

    return tools::PolyPolygon();
}

basegfx::B2DPolyPolygon vcl::Region::GetAsB2DPolyPolygon() const
{
    if(getB2DPolyPolygon())
    {
        return *getB2DPolyPolygon();
    }

    if(getPolyPolygon())
    {
        // the polygon needs to be converted, buffer the up conversion. This will be preferred from now.
        const basegfx::B2DPolyPolygon aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon());
        const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;

        return *getB2DPolyPolygon();
    }

    if(getRegionBand())
    {
        // the BandRegion needs to be converted, buffer the conversion
        const basegfx::B2DPolyPolygon aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
        const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;

        return *getB2DPolyPolygon();
    }

    return basegfx::B2DPolyPolygon();
}

const RegionBand* vcl::Region::GetAsRegionBand() const
{
    if(!getRegionBand())
    {
        if(getB2DPolyPolygon())
        {
            // convert B2DPolyPolygon to RegionBand, buffer it and return it
            const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon()));
        }
        else if(getPolyPolygon())
        {
            // convert B2DPolyPolygon to RegionBand, buffer it and return it
            const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon());
        }
    }

    return getRegionBand();
}

bool vcl::Region::Contains( const Point& rPoint ) const
{
    if(IsEmpty())
    {
        // no point can be in empty region
        return false;
    }

    if(IsNull())
    {
        // all points are inside null-region
        return true;
    }

    // Too expensive (?)
    //if(mpImplRegion->getRegionPolyPoly())
    //{
    //  return mpImplRegion->getRegionPolyPoly()->Contains( rPoint );
    //}

    // ensure RegionBand existence
    const RegionBand* pRegionBand = GetAsRegionBand();

    if(pRegionBand)
    {
        return pRegionBand->Contains(rPoint);
    }

    return false;
}

bool vcl::Region::Overlaps( const tools::Rectangle& rRect ) const
{
    if(IsEmpty())
    {
        // nothing can be over something empty
        return false;
    }

    if(IsNull())
    {
        // everything is over null region
        return true;
    }

    // Can we optimize this ??? - is used in StarDraw for brushes pointers
    // Why we have no IsOver for Regions ???
    // create region from rectangle and intersect own region
    vcl::Region aRegion(rRect);
    aRegion.Intersect( *this );

    // rectangle is over if include is not empty
    return !aRegion.IsEmpty();
}

bool vcl::Region::IsRectangle() const
{
    if( IsEmpty() || IsNull() )
        return false;

    if( getB2DPolyPolygon() )
        return basegfx::utils::isRectangle( *getB2DPolyPolygon() );

    if( getPolyPolygon() )
        return getPolyPolygon()->IsRect();

    if( getRegionBand() )
        return (getRegionBand()->getRectangleCount() == 1);

    return false;
}

void vcl::Region::SetNull()
{
    // reset all content
    mpB2DPolyPolygon.reset();
    mpPolyPolygon.reset();
    mpRegionBand.reset();
    mbIsNull = true;
}

void vcl::Region::SetEmpty()
{
    // reset all content
    mpB2DPolyPolygon.reset();
    mpPolyPolygon.reset();
    mpRegionBand.reset();
    mbIsNull = false;
}

Region& vcl::Region::operator=( const vcl::Region& ) = default;

Region& vcl::Region::operator=( vcl::Region&& rRegion ) noexcept
{
    mpB2DPolyPolygon = std::move(rRegion.mpB2DPolyPolygon);
    mpPolyPolygon = std::move(rRegion.mpPolyPolygon);
    mpRegionBand = std::move(rRegion.mpRegionBand);
    mbIsNull = rRegion.mbIsNull;
    rRegion.mbIsNull = true;

    return *this;
}

Region& vcl::Region::operator=( const tools::Rectangle& rRect )
{
    mpB2DPolyPolygon.reset();
    mpPolyPolygon.reset();
    if (!rRect.IsEmpty())
        mpRegionBand = std::make_shared<RegionBand>(rRect);
    else
        mpRegionBand.reset();
    mbIsNull = false;

    return *this;
}

bool vcl::Region::operator==( const vcl::Region& rRegion ) const
{
    if(IsNull() && rRegion.IsNull())
    {
        // both are null region
        return true;
    }

    if(IsEmpty() && rRegion.IsEmpty())
    {
        // both are empty
        return true;
    }

    if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
    {
        // same instance data? -> equal
        return true;
    }

    if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
    {
        // same instance data? -> equal
        return true;
    }

    if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
    {
        // same instance data? -> equal
        return true;
    }

    if(IsNull() || IsEmpty())
    {
        return false;
    }

    if(rRegion.IsNull() || rRegion.IsEmpty())
    {
        return false;
    }

    if(rRegion.getB2DPolyPolygon() || getB2DPolyPolygon())
    {
        // one of both has a B2DPolyPolygon based region, ensure both have it
        // by evtl. conversion
        GetAsB2DPolyPolygon();
        rRegion.GetAsB2DPolyPolygon();

        return *rRegion.getB2DPolyPolygon() == *getB2DPolyPolygon();
    }

    if(rRegion.getPolyPolygon() || getPolyPolygon())
    {
        // one of both has a B2DPolyPolygon based region, ensure both have it
        // by evtl. conversion
        GetAsPolyPolygon();
        rRegion.GetAsPolyPolygon();

        return *rRegion.getPolyPolygon() == *getPolyPolygon();
    }

    // both are not empty or null (see above) and if content supported polygon
    // data the comparison is already done. Only both on RegionBand base can be left,
    // but better check
    if(rRegion.getRegionBand() && getRegionBand())
    {
        return *rRegion.getRegionBand() == *getRegionBand();
    }

    // should not happen, but better deny equality
    return false;
}

SvStream& ReadRegion(SvStream& rIStrm, vcl::Region& rRegion)
{
    VersionCompatRead aCompat(rIStrm);
    sal_uInt16 nVersion(0);
    sal_uInt16 nTmp16(0);

    // clear region to be loaded
    rRegion.SetEmpty();

    // get version of streamed region
    rIStrm.ReadUInt16( nVersion );

    // get type of region
    rIStrm.ReadUInt16( nTmp16 );

    enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
    auto eStreamedType = nTmp16;

    switch (eStreamedType)
    {
        case REGION_NULL:
        {
            rRegion.SetNull();
            break;
        }

        case REGION_EMPTY:
        {
            rRegion.SetEmpty();
            break;
        }

        default:
        {
            std::shared_ptr<RegionBand> xNewRegionBand(std::make_shared<RegionBand>());
            bool bSuccess = xNewRegionBand->load(rIStrm);
            rRegion.mpRegionBand = std::move(xNewRegionBand);

            bool bHasPolyPolygon(false);
            if (aCompat.GetVersion() >= 2)
            {
                rIStrm.ReadCharAsBool( bHasPolyPolygon );

                if (bHasPolyPolygon)
                {
                    tools::PolyPolygon aNewPoly;
                    ReadPolyPolygon(rIStrm, aNewPoly);
                    const auto nPolygons = aNewPoly.Count();
                    if (nPolygons > 128)
                    {
                        SAL_WARN("vcl.gdi", "suspiciously high no of polygons in clip:" << nPolygons);
                        if (comphelper::IsFuzzing())
                            aNewPoly.Clear();
                    }
                    rRegion.mpPolyPolygon = aNewPoly;
                }
            }

            if (!bSuccess && !bHasPolyPolygon)
            {
                SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon);
                rRegion.SetNull();
            }

            break;
        }
    }

    return rIStrm;
}

SvStream& WriteRegion( SvStream& rOStrm, const vcl::Region& rRegion )
{
    const sal_uInt16 nVersion(2);
    VersionCompatWrite aCompat(rOStrm, nVersion);

    // put version
    rOStrm.WriteUInt16( nVersion );

    // put type
    enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
    RegionType aRegionType(REGION_COMPLEX);
    bool bEmpty(rRegion.IsEmpty());

    if(!bEmpty && rRegion.getB2DPolyPolygon() && 0 == rRegion.getB2DPolyPolygon()->count())
    {
        OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)");
        bEmpty = true;
    }

    if(!bEmpty && rRegion.getPolyPolygon() && 0 == rRegion.getPolyPolygon()->Count())
    {
        OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)");
        bEmpty = true;
    }

    if(bEmpty)
    {
        aRegionType = REGION_EMPTY;
    }
    else if(rRegion.IsNull())
    {
        aRegionType = REGION_NULL;
    }
    else if(rRegion.getRegionBand() && rRegion.getRegionBand()->isSingleRectangle())
    {
        aRegionType = REGION_RECTANGLE;
    }

    rOStrm.WriteUInt16( aRegionType );

    // get RegionBand
    const RegionBand* pRegionBand = rRegion.getRegionBand();

    if(pRegionBand)
    {
        pRegionBand->save(rOStrm);
    }
    else
    {
        // for compatibility, write an empty RegionBand (will only write
        // the end marker STREAMENTRY_END, but this *is* needed)
        const RegionBand aRegionBand;

        aRegionBand.save(rOStrm);
    }

    // write polypolygon if available
    const bool bHasPolyPolygon(rRegion.HasPolyPolygonOrB2DPolyPolygon());
    rOStrm.WriteBool( bHasPolyPolygon );

    if(bHasPolyPolygon)
    {
        // #i105373#
        tools::PolyPolygon aNoCurvePolyPolygon;
        rRegion.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);

        WritePolyPolygon( rOStrm, aNoCurvePolyPolygon );
    }

    return rOStrm;
}

void vcl::Region::GetRegionRectangles(RectangleVector& rTarget) const
{
    // clear returnvalues
    rTarget.clear();

    // ensure RegionBand existence
    const RegionBand* pRegionBand = GetAsRegionBand();

    if(pRegionBand)
    {
        pRegionBand->GetRegionRectangles(rTarget);
    }
}

static bool ImplPolygonRectTest( const tools::Polygon& rPoly, tools::Rectangle* pRectOut = nullptr )
{
    bool bIsRect = false;
    const Point* pPoints = rPoly.GetConstPointAry();
    sal_uInt16 nPoints = rPoly.GetSize();

    if( nPoints == 4 || (nPoints == 5 && pPoints[0] == pPoints[4]) )
    {
        tools::Long nX1 = pPoints[0].X(), nX2 = pPoints[2].X(), nY1 = pPoints[0].Y(), nY2 = pPoints[2].Y();

        if( ( (pPoints[1].X() == nX1 && pPoints[3].X() == nX2) && (pPoints[1].Y() == nY2 && pPoints[3].Y() == nY1) )
         || ( (pPoints[1].X() == nX2 && pPoints[3].X() == nX1) && (pPoints[1].Y() == nY1 && pPoints[3].Y() == nY2) ) )
        {
            bIsRect = true;

            if( pRectOut )
            {
                tools::Long nSwap;

                if( nX2 < nX1 )
                {
                    nSwap = nX2;
                    nX2 = nX1;
                    nX1 = nSwap;
                }

                if( nY2 < nY1 )
                {
                    nSwap = nY2;
                    nY2 = nY1;
                    nY1 = nSwap;
                }

                if( nX2 != nX1 )
                {
                    nX2--;
                }

                if( nY2 != nY1 )
                {
                    nY2--;
                }

                pRectOut->SetLeft( nX1 );
                pRectOut->SetRight( nX2 );
                pRectOut->SetTop( nY1 );
                pRectOut->SetBottom( nY2 );
            }
        }
    }

    return bIsRect;
}

vcl::Region vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon& rPolyPoly )
{
    //return vcl::Region( rPolyPoly );

    // check if it's worth extracting the XOr'ing the Rectangles
    // empiricism shows that break even between XOr'ing rectangles separately
    // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
    int nPolygonRects = 0, nPolygonPolygons = 0;
    int nPolygons = rPolyPoly.Count();

    for( int i = 0; i < nPolygons; i++ )
    {
        const tools::Polygon& rPoly = rPolyPoly[i];

        if( ImplPolygonRectTest( rPoly ) )
        {
            nPolygonRects++;
        }
        else
        {
            nPolygonPolygons++;
        }
    }

    if( nPolygonPolygons > nPolygonRects )
    {
        return vcl::Region( rPolyPoly );
    }

    vcl::Region aResult;
    tools::Rectangle aRect;

    for( int i = 0; i < nPolygons; i++ )
    {
        const tools::Polygon& rPoly = rPolyPoly[i];

        if( ImplPolygonRectTest( rPoly, &aRect ) )
        {
            aResult.XOr( aRect );
        }
        else
        {
            aResult.XOr( vcl::Region(rPoly) );
        }
    }

    return aResult;
}

} /* namespace vcl */

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