/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_drawinglayer.hxx"

#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>

//////////////////////////////////////////////////////////////////////////////

using namespace com::sun::star;

//////////////////////////////////////////////////////////////////////////////

namespace
{
    sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, const basegfx::BColor& rColorB, double fDelta, double fDiscreteUnit)
    {
        // use color distance, assume to do every color step (full quality)
        sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));

        if(nSteps)
        {
            // calc discrete length to change color all 1.5 disctete units (pixels)
            const sal_uInt32 nDistSteps(basegfx::fround(fDelta / (fDiscreteUnit * 1.5)));

            nSteps = std::min(nSteps, nDistSteps);
        }

        // roughly cut when too big or too small
        nSteps = std::min(nSteps, sal_uInt32(255));
        nSteps = std::max(nSteps, sal_uInt32(1));

        return nSteps;
    }
} // end of anonymous namespace

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
    namespace primitive2d
    {
        Primitive2DSequence SvgGradientHelper::createSingleGradientEntryFill() const
        {
            const SvgGradientEntryVector& rEntries = getGradientEntries();
            const sal_uInt32 nCount(rEntries.size());
            Primitive2DSequence xRetval;

            if(nCount)
            {
                const SvgGradientEntry& rSingleEntry = rEntries[nCount - 1];
                const double fOpacity(rSingleEntry.getOpacity());

                if(fOpacity > 0.0)
                {
                    Primitive2DReference xRef(
                        new PolyPolygonColorPrimitive2D(
                            getPolyPolygon(), 
                            rSingleEntry.getColor()));

                    if(fOpacity < 1.0)
                    {
                        const Primitive2DSequence aContent(&xRef, 1);

                        xRef = Primitive2DReference(
                            new UnifiedTransparencePrimitive2D(
                                aContent, 
                                1.0 - fOpacity));
                    }

                    xRetval = Primitive2DSequence(&xRef, 1);
                }
            }
            else
            {
                OSL_ENSURE(false, "Single gradient entry construction without entry (!)");
            }

            return xRetval;
        }

        void SvgGradientHelper::checkPreconditions()
        {
            mbPreconditionsChecked = true;
            const SvgGradientEntryVector& rEntries = getGradientEntries();

            if(rEntries.empty())
            {
                // no fill at all
            }
            else
            {
                const sal_uInt32 nCount(rEntries.size());

                if(1 == nCount)
                {
                    // fill with single existing color
                    setSingleEntry();
                }
                else
                {
                    // sort maGradientEntries when more than one
                    std::sort(maGradientEntries.begin(), maGradientEntries.end());
                    
                    // gradient with at least two colors
                    bool bAllInvisible(true);

                    for(sal_uInt32 a(0); a < nCount; a++)
                    {
                        const SvgGradientEntry& rCandidate = rEntries[a];

                        if(basegfx::fTools::equalZero(rCandidate.getOpacity()))
                        {
                            // invisible
                            mbFullyOpaque = false;
                        }
                        else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0))
                        {
                            // completely opaque
                            bAllInvisible = false;
                        }
                        else
                        {
                            // opacity
                            bAllInvisible = false;
                            mbFullyOpaque = false;
                        }
                    }

                    if(bAllInvisible)
                    {
                        // all invisible, nothing to do
                    }
                    else
                    {
                        const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());

                        if(aPolyRange.isEmpty())
                        {
                            // no range to fill, nothing to do
                        }
                        else
                        {
                            const double fPolyWidth(aPolyRange.getWidth());
                            const double fPolyHeight(aPolyRange.getHeight());

                            if(basegfx::fTools::equalZero(fPolyWidth) || basegfx::fTools::equalZero(fPolyHeight))
                            {
                                // no width/height to fill, nothing to do
                            }
                            else
                            {
                                mbCreatesContent = true;
                            }
                        }
                    }
                }
            }
        }

        double SvgGradientHelper::createRun(
            Primitive2DVector& rTargetColor,
            Primitive2DVector& rTargetOpacity, 
            double fPos,
            double fMax,
            const SvgGradientEntryVector& rEntries,
            sal_Int32 nOffset) const
        {
            const sal_uInt32 nCount(rEntries.size());

            if(nCount)
            {
                const SvgGradientEntry& rStart = rEntries[0];
                const bool bCreateStartPad(fPos < 0.0 && Spread_pad == getSpreadMethod());
                const bool bCreateStartFill(rStart.getOffset() > 0.0);
                sal_uInt32 nIndex(0);

                if(bCreateStartPad || bCreateStartFill)
                {
                    const SvgGradientEntry aTemp(bCreateStartPad ? fPos : 0.0, rStart.getColor(), rStart.getOpacity());
                
                    createAtom(rTargetColor, rTargetOpacity, aTemp, rStart, nOffset);
                    fPos = rStart.getOffset();
                }

                while(fPos < 1.0 && nIndex + 1 < nCount)
                {
                    const SvgGradientEntry& rCandidateA = rEntries[nIndex++];
                    const SvgGradientEntry& rCandidateB = rEntries[nIndex];
                        
                    createAtom(rTargetColor, rTargetOpacity, rCandidateA, rCandidateB, nOffset);
                    fPos = rCandidateB.getOffset();
                }

                const SvgGradientEntry& rEnd = rEntries[nCount - 1];
                const bool bCreateEndPad(fPos < fMax && Spread_pad == getSpreadMethod());
                const bool bCreateEndFill(rEnd.getOffset() < 1.0);

                if(bCreateEndPad || bCreateEndFill)
                {
                    fPos = bCreateEndPad ? fMax : 1.0;
                    const SvgGradientEntry aTemp(fPos, rEnd.getColor(), rEnd.getOpacity());
                
                    createAtom(rTargetColor, rTargetOpacity, rEnd, aTemp, nOffset);
                }
            }
            else
            {
                OSL_ENSURE(false, "GradientAtom creation without ColorStops (!)");
                fPos = fMax;
            }

            return fPos;
        }

        Primitive2DSequence SvgGradientHelper::createResult(
            const Primitive2DVector& rTargetColor,
            const Primitive2DVector& rTargetOpacity,
            const basegfx::B2DHomMatrix& rUnitGradientToObject,
            bool bInvert) const
        {
            Primitive2DSequence xRetval;
            const Primitive2DSequence aTargetColorEntries(Primitive2DVectorToPrimitive2DSequence(rTargetColor, bInvert));
            const Primitive2DSequence aTargetOpacityEntries(Primitive2DVectorToPrimitive2DSequence(rTargetOpacity, bInvert));

            if(aTargetColorEntries.hasElements())
            {
                Primitive2DReference xRefContent;
                    
                if(aTargetOpacityEntries.hasElements())
                {
                    const Primitive2DReference xRefOpacity = new TransparencePrimitive2D(
                        aTargetColorEntries, 
                        aTargetOpacityEntries);
                        
                    xRefContent = new TransformPrimitive2D(
                        rUnitGradientToObject, 
                        Primitive2DSequence(&xRefOpacity, 1));
                }
                else
                {
                    xRefContent = new TransformPrimitive2D(
                        rUnitGradientToObject, 
                        aTargetColorEntries);
                }

                xRefContent = new MaskPrimitive2D(
                    getPolyPolygon(), 
                    Primitive2DSequence(&xRefContent, 1));

                xRetval = Primitive2DSequence(&xRefContent, 1);
            }

            return xRetval;
        }

        SvgGradientHelper::SvgGradientHelper(
            const basegfx::B2DPolyPolygon& rPolyPolygon,
            const SvgGradientEntryVector& rGradientEntries,
            const basegfx::B2DPoint& rStart,
            SpreadMethod aSpreadMethod)
        :   maPolyPolygon(rPolyPolygon),
            maGradientEntries(rGradientEntries),
            maStart(rStart),
            maSpreadMethod(aSpreadMethod),
            mbPreconditionsChecked(false),
            mbCreatesContent(false),
            mbSingleEntry(false),
            mbFullyOpaque(true)
        {
        }

        bool SvgGradientHelper::operator==(const SvgGradientHelper& rSvgGradientHelper) const
        {
            const SvgGradientHelper& rCompare = static_cast< const SvgGradientHelper& >(rSvgGradientHelper);

            return (getPolyPolygon() == rCompare.getPolyPolygon()
                && getGradientEntries() == rCompare.getGradientEntries()
                && getStart() == rCompare.getStart()
                && getSpreadMethod() == rCompare.getSpreadMethod());
        }

    } // end of namespace primitive2d
} // end of namespace drawinglayer

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
	namespace primitive2d
    {
        void SvgLinearGradientPrimitive2D::checkPreconditions()
        {
            // call parent
            SvgGradientHelper::checkPreconditions();

            if(getCreatesContent())
            {
                // Check Vector
                const basegfx::B2DVector aVector(getEnd() - getStart());

                if(basegfx::fTools::equalZero(aVector.getX()) && basegfx::fTools::equalZero(aVector.getY()))
                {
                    // fill with single color using last stop color
                    setSingleEntry();
                }
            }
        }

        void SvgLinearGradientPrimitive2D::createAtom(
            Primitive2DVector& rTargetColor,
            Primitive2DVector& rTargetOpacity, 
            const SvgGradientEntry& rFrom, 
            const SvgGradientEntry& rTo,
            sal_Int32 nOffset) const
        {
            // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
            if(rFrom.getOffset() == rTo.getOffset())
            {
                OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
            }
            else
            {
                rTargetColor.push_back(
                    new SvgLinearAtomPrimitive2D(
                        rFrom.getColor(), rFrom.getOffset() + nOffset,
                        rTo.getColor(), rTo.getOffset() + nOffset));

                const double fTransFrom(1.0 - rFrom.getOpacity());
                const double fTransTo(1.0 - rTo.getOpacity());

                rTargetOpacity.push_back(
                    new SvgLinearAtomPrimitive2D(
                        basegfx::BColor(fTransFrom, fTransFrom, fTransFrom), rFrom.getOffset() + nOffset,
                        basegfx::BColor(fTransTo,fTransTo, fTransTo), rTo.getOffset() + nOffset));
            }
        }

        Primitive2DSequence SvgLinearGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            Primitive2DSequence xRetval;

            if(!getPreconditionsChecked())
            {
                const_cast< SvgLinearGradientPrimitive2D* >(this)->checkPreconditions();
            }

            if(getSingleEntry())
            {
                // fill with last existing color
                xRetval = createSingleGradientEntryFill();
            }
            else if(getCreatesContent())
            {
                // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
                // invisible, width and height to fill are not empty
                const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
                const double fPolyWidth(aPolyRange.getWidth());
                const double fPolyHeight(aPolyRange.getHeight());

                // create ObjectTransform based on polygon range
                const basegfx::B2DHomMatrix aObjectTransform(
                    basegfx::tools::createScaleTranslateB2DHomMatrix(
                        fPolyWidth, fPolyHeight, 
                        aPolyRange.getMinX(), aPolyRange.getMinY()));

                // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given 
                // gradient vector defined by Start,End
                const basegfx::B2DVector aVector(getEnd() - getStart());
                const double fVectorLength(aVector.getLength());
                basegfx::B2DHomMatrix aUnitGradientToGradient;

                aUnitGradientToGradient.scale(fVectorLength, 1.0);
                aUnitGradientToGradient.rotate(atan2(aVector.getY(), aVector.getX()));
                aUnitGradientToGradient.translate(getStart().getX(), getStart().getY());

                // create full transform from unit gradient coordinates to object coordinates
                // including the SvgGradient transformation
                basegfx::B2DHomMatrix aUnitGradientToObject(aObjectTransform * aUnitGradientToGradient);

                // create inverse from it
                basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
                aObjectToUnitGradient.invert();

                // back-transform polygon to unit gradient coordinates and get
                // UnitRage. This is the range the gradient has to cover
                basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
                aUnitPoly.transform(aObjectToUnitGradient);
                const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());

                // prepare result vectors
                Primitive2DVector aTargetColor;
                Primitive2DVector aTargetOpacity;

                if(basegfx::fTools::more(aUnitRange.getWidth(), 0.0))
                {
                    // add a pre-multiply to aUnitGradientToObject to allow
                    // multiplication of the polygon(xl, 0.0, xr, 1.0)
                    const basegfx::B2DHomMatrix aPreMultiply(
                        basegfx::tools::createScaleTranslateB2DHomMatrix(
                            1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY()));
                    aUnitGradientToObject = aUnitGradientToObject * aPreMultiply;

                    // create central run, may also already do all necessary when
                    // Spread_pad is set as SpreadMethod and/or the range is smaller
                    double fPos(createRun(aTargetColor, aTargetOpacity, aUnitRange.getMinX(), aUnitRange.getMaxX(), getGradientEntries(), 0));

                    if(fPos < aUnitRange.getMaxX())
                    {
                        // can only happen when SpreadMethod is Spread_reflect or Spread_repeat,
                        // else the start and end pads are already created and fPos == aUnitRange.getMaxX().
                        // Its possible to express the repeated linear gradient by adding the
                        // transformed central run. Crete it this way
                        Primitive2DSequence aTargetColorEntries(Primitive2DVectorToPrimitive2DSequence(aTargetColor));
                        Primitive2DSequence aTargetOpacityEntries(Primitive2DVectorToPrimitive2DSequence(aTargetOpacity));
                        aTargetColor.clear();
                        aTargetOpacity.clear();

                        if(aTargetColorEntries.hasElements())
                        {
                            // add original central run as group primitive
                            aTargetColor.push_back(new GroupPrimitive2D(aTargetColorEntries));
                                
                            if(aTargetOpacityEntries.hasElements())
                            {
                                aTargetOpacity.push_back(new GroupPrimitive2D(aTargetOpacityEntries));
                            }

                            // add negative runs
                            fPos = 0.0;
                            sal_Int32 nOffset(0);

                            while(fPos > aUnitRange.getMinX())
                            {
                                fPos -= 1.0;
                                nOffset++;

                                basegfx::B2DHomMatrix aTransform;
                                const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));

                                if(bMirror)
                                {
                                    aTransform.scale(-1.0, 1.0);
                                    aTransform.translate(fPos + 1.0, 0.0);
                                }
                                else
                                {
                                    aTransform.translate(fPos, 0.0);
                                }

                                aTargetColor.push_back(new TransformPrimitive2D(aTransform, aTargetColorEntries));
                                
                                if(aTargetOpacityEntries.hasElements())
                                {
                                    aTargetOpacity.push_back(new TransformPrimitive2D(aTransform, aTargetOpacityEntries));
                                }
                            }

                            // add positive runs
                            fPos = 1.0;
                            nOffset = 1;

                            while(fPos < aUnitRange.getMaxX())
                            {
                                basegfx::B2DHomMatrix aTransform;
                                const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));

                                if(bMirror)
                                {
                                    aTransform.scale(-1.0, 1.0);
                                    aTransform.translate(fPos + 1.0, 0.0);
                                }
                                else
                                {
                                    aTransform.translate(fPos, 0.0);
                                }

                                aTargetColor.push_back(new TransformPrimitive2D(aTransform, aTargetColorEntries));
                                
                                if(aTargetOpacityEntries.hasElements())
                                {
                                    aTargetOpacity.push_back(new TransformPrimitive2D(aTransform, aTargetOpacityEntries));
                                }

                                fPos += 1.0;
                                nOffset++;
                            }
                        }
                    }
                }

                xRetval = createResult(aTargetColor, aTargetOpacity, aUnitGradientToObject);
            }

            return xRetval;
        }

        SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D(
            const basegfx::B2DPolyPolygon& rPolyPolygon,
            const SvgGradientEntryVector& rGradientEntries,
            const basegfx::B2DPoint& rStart,
            const basegfx::B2DPoint& rEnd,
            SpreadMethod aSpreadMethod)
        :   BufferedDecompositionPrimitive2D(),
            SvgGradientHelper(rPolyPolygon, rGradientEntries, rStart, aSpreadMethod),
            maEnd(rEnd)
        {
        }

        bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
        {
            const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);

            if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
            {
                const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive);

                return (getEnd() == rCompare.getEnd());
            }

            return false;
        }

        basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            // return ObjectRange
            return getPolyPolygon().getB2DRange();
        }

        // provide unique ID
        ImplPrimitrive2DIDBlock(SvgLinearGradientPrimitive2D, PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D)

    } // end of namespace primitive2d
} // end of namespace drawinglayer

//////////////////////////////////////////////////////////////////////////////

namespace drawinglayer
{
    namespace primitive2d
    {
        void SvgRadialGradientPrimitive2D::checkPreconditions()
        {
            // call parent
            SvgGradientHelper::checkPreconditions();

            if(getCreatesContent())
            {
                // Check Radius
                if(basegfx::fTools::equalZero(getRadius()))
                {
                    // fill with single color using last stop color
                    setSingleEntry();
                }
            }
        }

        void SvgRadialGradientPrimitive2D::createAtom(
            Primitive2DVector& rTargetColor,
            Primitive2DVector& rTargetOpacity, 
            const SvgGradientEntry& rFrom, 
            const SvgGradientEntry& rTo,
            sal_Int32 nOffset) const
        {
            // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
            if(rFrom.getOffset() == rTo.getOffset())
            {
                OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
            }
            else
            {
                const double fScaleFrom(rFrom.getOffset() + nOffset);
                const double fScaleTo(rTo.getOffset() + nOffset);

                if(isFocalSet())
                {
                    const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom));
                    const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo));
                        
                    rTargetColor.push_back(
                        new SvgRadialAtomPrimitive2D(
                            rFrom.getColor(), fScaleFrom, aTranslateFrom,
                            rTo.getColor(), fScaleTo, aTranslateTo));
                }
                else
                {
                    rTargetColor.push_back(
                        new SvgRadialAtomPrimitive2D(
                            rFrom.getColor(), fScaleFrom,
                            rTo.getColor(), fScaleTo));
                }

                const double fTransFrom(1.0 - rFrom.getOpacity());
                const double fTransTo(1.0 - rTo.getOpacity());
                const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom);
                const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo);

                if(isFocalSet())
                {
                    const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom));
                    const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo));
                        
                    rTargetOpacity.push_back(
                        new SvgRadialAtomPrimitive2D(
                            aColorFrom, fScaleFrom, aTranslateFrom,
                            aColorTo, fScaleTo, aTranslateTo));
                }
                else
                {
                    rTargetOpacity.push_back(
                        new SvgRadialAtomPrimitive2D(
                            aColorFrom, fScaleFrom,
                            aColorTo, fScaleTo));
                }
            }
        }

        const SvgGradientEntryVector& SvgRadialGradientPrimitive2D::getMirroredGradientEntries() const
        {
            if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
            {
                const_cast< SvgRadialGradientPrimitive2D* >(this)->createMirroredGradientEntries();
            }

            return maMirroredGradientEntries;
        }
            
        void SvgRadialGradientPrimitive2D::createMirroredGradientEntries()
        {
            if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
            {
                const sal_uInt32 nCount(getGradientEntries().size());
                maMirroredGradientEntries.clear();
                maMirroredGradientEntries.reserve(nCount);

                for(sal_uInt32 a(0); a < nCount; a++)
                {
                    const SvgGradientEntry& rCandidate = getGradientEntries()[nCount - 1 - a];

                    maMirroredGradientEntries.push_back(
                        SvgGradientEntry(
                            1.0 - rCandidate.getOffset(),
                            rCandidate.getColor(),
                            rCandidate.getOpacity()));
                }
            }
        }

        Primitive2DSequence SvgRadialGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            Primitive2DSequence xRetval;

            if(!getPreconditionsChecked())
            {
                const_cast< SvgRadialGradientPrimitive2D* >(this)->checkPreconditions();
            }

            if(getSingleEntry())
            {
                // fill with last existing color
                xRetval = createSingleGradientEntryFill();
            }
            else if(getCreatesContent())
            {
                // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
                // invisible, width and height to fill are not empty
                const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
                const double fPolyWidth(aPolyRange.getWidth());
                const double fPolyHeight(aPolyRange.getHeight());

                // create ObjectTransform based on polygon range
                const basegfx::B2DHomMatrix aObjectTransform(
                    basegfx::tools::createScaleTranslateB2DHomMatrix(
                        fPolyWidth, fPolyHeight, 
                        aPolyRange.getMinX(), aPolyRange.getMinY()));

                // create unit transform from unit vector to given linear gradient vector
                basegfx::B2DHomMatrix aUnitGradientToGradient;

                aUnitGradientToGradient.scale(getRadius(), getRadius());
                aUnitGradientToGradient.translate(getStart().getX(), getStart().getY());

                // create full transform from unit gradient coordinates to object coordinates
                // including the SvgGradient transformation
                basegfx::B2DHomMatrix aUnitGradientToObject(aObjectTransform * aUnitGradientToGradient);

                // create inverse from it
                basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
                aObjectToUnitGradient.invert();

                // back-transform polygon to unit gradient coordinates and get
                // UnitRage. This is the range the gradient has to cover
                basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
                aUnitPoly.transform(aObjectToUnitGradient);
                const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());

                // create range which the gradient has to cover to cover the whole given geometry.
                // For circle, go from 0.0 to max radius in all directions (the corners)
                double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength());
                fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength());
                fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength());
                fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength());

                // prepare result vectors
                Primitive2DVector aTargetColor;
                Primitive2DVector aTargetOpacity;

                if(0.0 < fMax)
                {
                    // prepare maFocalVector
                    if(isFocalSet())
                    {
                        const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax;
                    }

                    // create central run, may also already do all necessary when
                    // Spread_pad is set as SpreadMethod and/or the range is smaller
                    double fPos(createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), 0));

                    if(fPos < fMax)
                    {
                        // can only happen when SpreadMethod is Spread_reflect or Spread_repeat,
                        // else the start and end pads are already created and fPos == fMax.
                        // For radial there is no way to transform the already created
                        // central run, it needs to be created from 1.0 to fMax
                        sal_Int32 nOffset(1);

                        while(fPos < fMax)
                        {
                            const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));

                            if(bMirror)
                            {
                                createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getMirroredGradientEntries(), nOffset);
                            }
                            else
                            {
                                createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), nOffset);
                            }

                            nOffset++;
                            fPos += 1.0;
                        }
                    }
                }

                xRetval = createResult(aTargetColor, aTargetOpacity, aUnitGradientToObject, true);
            }

            return xRetval;
        }

        SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
            const basegfx::B2DPolyPolygon& rPolyPolygon,
            const SvgGradientEntryVector& rGradientEntries,
            const basegfx::B2DPoint& rStart,
            double fRadius,
            SpreadMethod aSpreadMethod,
            const basegfx::B2DPoint* pFocal)
        :   BufferedDecompositionPrimitive2D(),
            SvgGradientHelper(rPolyPolygon, rGradientEntries, rStart, aSpreadMethod),
            mfRadius(fRadius),
            maFocal(rStart),
            maFocalVector(0.0, 0.0),
            maFocalLength(0.0),
            maMirroredGradientEntries(),
            mbFocalSet(false)
        {
            if(pFocal)
            {
                maFocal = *pFocal;
                maFocalVector = maFocal - getStart();
                mbFocalSet = true;
            }
        }

        bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
        {
            const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);

            if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
            {
                const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive);

                if(getRadius() == rCompare.getRadius())
                {
                    if(isFocalSet() == rCompare.isFocalSet())
                    {
                        if(isFocalSet())
                        {
                            return getFocal() == rCompare.getFocal();
                        }
                        else
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            // return ObjectRange
            return getPolyPolygon().getB2DRange();
        }

        // provide unique ID
        ImplPrimitrive2DIDBlock(SvgRadialGradientPrimitive2D, PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D)

    } // end of namespace primitive2d
} // end of namespace drawinglayer

//////////////////////////////////////////////////////////////////////////////
// SvgLinearAtomPrimitive2D class

namespace drawinglayer
{
    namespace primitive2d
    {
        Primitive2DSequence SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            Primitive2DSequence xRetval;
            const double fDelta(getOffsetB() - getOffsetA());

            if(!basegfx::fTools::equalZero(fDelta))
            {
                // use one discrete unit for overlap (one pixel)
                const double fDiscreteUnit(getDiscreteUnit());

                // use color distance and discrete lengths to calculate step count
                const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit));

                // prepare loop and polygon (with overlap for linear gradients)
                double fStart(0.0);
                double fStep(fDelta / nSteps);
                const basegfx::B2DPolygon aPolygon(
                    basegfx::tools::createPolygonFromRect(
                        basegfx::B2DRange(
                            getOffsetA() - fDiscreteUnit, 
                            0.0, 
                            getOffsetA() + fStep + fDiscreteUnit, 
                            1.0)));

                // loop and create primitives
                xRetval.realloc(nSteps);

                for(sal_uInt32 a(0); a < nSteps; a++, fStart += fStep)
                {
                    basegfx::B2DPolygon aNew(aPolygon);

                    aNew.transform(basegfx::tools::createTranslateB2DHomMatrix(fStart, 0.0));
                    xRetval[a] = new PolyPolygonColorPrimitive2D(
                        basegfx::B2DPolyPolygon(aNew), 
                        basegfx::interpolate(getColorA(), getColorB(), fStart/fDelta));
                }
            }

            return xRetval;
        }

        SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D(
            const basegfx::BColor& aColorA, double fOffsetA,
            const basegfx::BColor& aColorB, double fOffsetB)
        :   DiscreteMetricDependentPrimitive2D(),
            maColorA(aColorA),
            maColorB(aColorB),
            mfOffsetA(fOffsetA),
            mfOffsetB(fOffsetB)
        {
            if(mfOffsetA > mfOffsetB)
            {
                OSL_ENSURE(false, "Wrong offset order (!)");
                ::std::swap(mfOffsetA, mfOffsetB);
            }
        }

        bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
        {
            if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
            {
                const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive);

                return (getColorA() == rCompare.getColorA()
                    && getColorB() == rCompare.getColorB()
                    && getOffsetA() == rCompare.getOffsetA()
                    && getOffsetB() == rCompare.getOffsetB());
            }

            return false;
        }

        // provide unique ID
        ImplPrimitrive2DIDBlock(SvgLinearAtomPrimitive2D, PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D)

    } // end of namespace primitive2d
} // end of namespace drawinglayer

//////////////////////////////////////////////////////////////////////////////
// SvgRadialAtomPrimitive2D class

namespace drawinglayer
{
    namespace primitive2d
    {
        Primitive2DSequence SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            Primitive2DSequence xRetval;
            const double fDeltaScale(getScaleB() - getScaleA());

            if(!basegfx::fTools::equalZero(fDeltaScale))
            {
                // use one discrete unit for overlap (one pixel)
                const double fDiscreteUnit(getDiscreteUnit());

                // use color distance and discrete lengths to calculate step count
                const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit));

                // prepare loop (outside to inside, full polygons, no polypolygons with holes)
                double fEndScale(getScaleB());
                double fStepScale(fDeltaScale / nSteps);

                // loop and create primitives
                xRetval.realloc(nSteps);

                for(sal_uInt32 a(0); a < nSteps; a++, fEndScale -= fStepScale)
                {
                    const double fUnitScale(fEndScale/fDeltaScale);
                    basegfx::B2DHomMatrix aTransform;

                    if(isTranslateSet())
                    {
                        const basegfx::B2DVector aTranslate(
                            basegfx::interpolate(
                                getTranslateA(), 
                                getTranslateB(), 
                                fUnitScale));

                        aTransform = basegfx::tools::createScaleTranslateB2DHomMatrix(
                            fEndScale,
                            fEndScale,
                            aTranslate.getX(),
                            aTranslate.getY());
                    }
                    else
                    {
                        aTransform = basegfx::tools::createScaleB2DHomMatrix(
                            fEndScale,
                            fEndScale);
                    }

                    basegfx::B2DPolygon aNew(basegfx::tools::createPolygonFromUnitCircle());

                    aNew.transform(aTransform);
                    xRetval[a] = new PolyPolygonColorPrimitive2D(
                        basegfx::B2DPolyPolygon(aNew), 
                        basegfx::interpolate(getColorA(), getColorB(), fUnitScale));
                }
            }

            return xRetval;
        }

        SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
            const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA,
            const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB)
        :   DiscreteMetricDependentPrimitive2D(),
            maColorA(aColorA),
            maColorB(aColorB),
            mfScaleA(fScaleA),
            mfScaleB(fScaleB),
            mpTranslate(0)
        {
            // check and evtl. set translations
            if(!rTranslateA.equal(rTranslateB))
            {
                mpTranslate = new VectorPair(rTranslateA, rTranslateB);
            }

            // scale A and B have to be positive
            mfScaleA = ::std::max(mfScaleA, 0.0);
            mfScaleB = ::std::max(mfScaleB, 0.0);

            // scale B has to be bigger than scale A; swap if different
            if(mfScaleA > mfScaleB)
            {
                OSL_ENSURE(false, "Wrong offset order (!)");
                ::std::swap(mfScaleA, mfScaleB);

                if(mpTranslate)
                {
                    ::std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB);
                }
            }
        }

        SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
            const basegfx::BColor& aColorA, double fScaleA,
            const basegfx::BColor& aColorB, double fScaleB)
        :   DiscreteMetricDependentPrimitive2D(),
            maColorA(aColorA),
            maColorB(aColorB),
            mfScaleA(fScaleA),
            mfScaleB(fScaleB),
            mpTranslate(0)
        {
            // scale A and B have to be positive
            mfScaleA = ::std::max(mfScaleA, 0.0);
            mfScaleB = ::std::max(mfScaleB, 0.0);

            // scale B has to be bigger than scale A; swap if different
            if(mfScaleA > mfScaleB)
            {
                OSL_ENSURE(false, "Wrong offset order (!)");
                ::std::swap(mfScaleA, mfScaleB);
            }
        }

        SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D()
        {
            if(mpTranslate)
            {
                delete mpTranslate;
                mpTranslate = 0;
            }
        }

        bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
        {
            if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
            {
                const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive);

                if(getColorA() == rCompare.getColorA()
                    && getColorB() == rCompare.getColorB()
                    && getScaleA() == rCompare.getScaleA()
                    && getScaleB() == rCompare.getScaleB())
                {
                    if(isTranslateSet() && rCompare.isTranslateSet())
                    {
                        return (getTranslateA() == rCompare.getTranslateA()
                            && getTranslateB() == rCompare.getTranslateB());
                    }
                    else if(!isTranslateSet() && !rCompare.isTranslateSet())
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        // provide unique ID
        ImplPrimitrive2DIDBlock(SvgRadialAtomPrimitive2D, PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D)

    } // end of namespace primitive2d
} // end of namespace drawinglayer

//////////////////////////////////////////////////////////////////////////////
// eof