/* -*- 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 <drawinglayer/primitive2d/polygonprimitive2d.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <basegfx/polygon/b2dlinegeometry.hxx>
#include <com/sun/star/drawing/LineCap.hpp>
#include <comphelper/random.hxx>

#include <converters.hxx>

using namespace com::sun::star;
using namespace std;


namespace drawinglayer
{
    namespace primitive2d
    {
        PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const basegfx::BColor& rBColor)
        :   BasePrimitive2D(),
            maPolygon(rPolygon),
            maBColor(rBColor)
        {
        }

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

                return (getB2DPolygon() == rCompare.getB2DPolygon()
                    && getBColor() == rCompare.getBColor());
            }

            return false;
        }

        basegfx::B2DRange PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
        {
            // this is a hairline, thus the line width is view-dependent. Get range of polygon
            // as base size
            basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());

            if(!aRetval.isEmpty())
            {
                // Calculate view-dependent hairline width
                const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
                const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);

                if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
                {
                    aRetval.grow(fDiscreteHalfLineWidth);
                }
            }

            // return range
            return aRetval;
        }

        // provide unique ID
        ImplPrimitive2DIDBlock(PolygonHairlinePrimitive2D, PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D)

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


namespace drawinglayer
{
    namespace primitive2d
    {
        void PolygonMarkerPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const
        {
            // calculate logic DashLength
            const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(getDiscreteDashLength(), 0.0));
            const double fLogicDashLength(aDashVector.getX());

            if(fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB()))
            {
                // apply dashing; get line and gap snippets
                std::vector< double > aDash;
                basegfx::B2DPolyPolygon aDashedPolyPolyA;
                basegfx::B2DPolyPolygon aDashedPolyPolyB;

                aDash.push_back(fLogicDashLength);
                aDash.push_back(fLogicDashLength);
                basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA, &aDashedPolyPolyB, 2.0 * fLogicDashLength);

                rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyA, getRGBColorA()));
                rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyB, getRGBColorB()));
            }
            else
            {
                rContainer.push_back(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA()));
            }
        }

        PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const basegfx::BColor& rRGBColorA,
            const basegfx::BColor& rRGBColorB,
            double fDiscreteDashLength)
        :   BufferedDecompositionPrimitive2D(),
            maPolygon(rPolygon),
            maRGBColorA(rRGBColorA),
            maRGBColorB(rRGBColorB),
            mfDiscreteDashLength(fDiscreteDashLength),
            maLastInverseObjectToViewTransformation()
        {
        }

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

                return (getB2DPolygon() == rCompare.getB2DPolygon()
                    && getRGBColorA() == rCompare.getRGBColorA()
                    && getRGBColorB() == rCompare.getRGBColorB()
                    && getDiscreteDashLength() == rCompare.getDiscreteDashLength());
            }

            return false;
        }

        basegfx::B2DRange PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
        {
            // this is a hairline, thus the line width is view-dependent. Get range of polygon
            // as base size
            basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());

            if(!aRetval.isEmpty())
            {
                // Calculate view-dependent hairline width
                const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
                const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);

                if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
                {
                    aRetval.grow(fDiscreteHalfLineWidth);
                }
            }

            // return range
            return aRetval;
        }

        void PolygonMarkerPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const
        {
            ::osl::MutexGuard aGuard( m_aMutex );
            bool bNeedNewDecomposition(false);

            if(!getBuffered2DDecomposition().empty())
            {
                if(rViewInformation.getInverseObjectToViewTransformation() != maLastInverseObjectToViewTransformation)
                {
                    bNeedNewDecomposition = true;
                }
            }

            if(bNeedNewDecomposition)
            {
                // conditions of last local decomposition have changed, delete
                const_cast< PolygonMarkerPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer());
            }

            if(getBuffered2DDecomposition().empty())
            {
                // remember last used InverseObjectToViewTransformation
                PolygonMarkerPrimitive2D* pThat = const_cast< PolygonMarkerPrimitive2D* >(this);
                pThat->maLastInverseObjectToViewTransformation = rViewInformation.getInverseObjectToViewTransformation();
            }

            // use parent implementation
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
        }

        // provide unique ID
        ImplPrimitive2DIDBlock(PolygonMarkerPrimitive2D, PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D)

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

namespace drawinglayer
{
    double getRandomColorRange()
    {
        return comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX));
    }

    namespace primitive2d
    {
        void PolygonStrokePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            if(getB2DPolygon().count())
            {
                // #i102241# try to simplify before usage
                const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon()));
                basegfx::B2DPolyPolygon aHairLinePolyPolygon;

                if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
                {
                    // no line dashing, just copy
                    aHairLinePolyPolygon.append(aB2DPolygon);
                }
                else
                {
                    // apply LineStyle
                    basegfx::utils::applyLineDashing(
                        aB2DPolygon, getStrokeAttribute().getDotDashArray(),
                        &aHairLinePolyPolygon, nullptr, getStrokeAttribute().getFullDotDashLen());
                }

                const sal_uInt32 nCount(aHairLinePolyPolygon.count());

                if(!getLineAttribute().isDefault() && getLineAttribute().getWidth())
                {
                    // create fat line data
                    const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0);
                    const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin());
                    const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap());
                    basegfx::B2DPolyPolygon aAreaPolyPolygon;
                    const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle());

                    for(sal_uInt32 a(0); a < nCount; a++)
                    {
                        // New version of createAreaGeometry; now creates bezier polygons
                        aAreaPolyPolygon.append(basegfx::utils::createAreaGeometry(
                            aHairLinePolyPolygon.getB2DPolygon(a),
                            fHalfLineWidth,
                            aLineJoin,
                            aLineCap,
                            basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/ ,
                            0.4 /* default fMaxPartOfEdge*/ ,
                            fMiterMinimumAngle));
                    }

                    // create primitive
                    for(sal_uInt32 b(0); b < aAreaPolyPolygon.count(); b++)
                    {
                        // put into single polyPolygon primitives to make clear that this is NOT meant
                        // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a
                        // melting process may be used here one day.
                        const basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b));
                        const basegfx::BColor aColor(getLineAttribute().getColor());
                        rContainer.push_back(new PolyPolygonColorPrimitive2D(aNewPolyPolygon, aColor));
                    }
                }
                else
                {
                    rContainer.push_back(
                        new PolyPolygonHairlinePrimitive2D(
                            aHairLinePolyPolygon,
                            getLineAttribute().getColor()));
                }
            }
        }

        PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const attribute::LineAttribute& rLineAttribute,
            const attribute::StrokeAttribute& rStrokeAttribute)
        :   BufferedDecompositionPrimitive2D(),
            maPolygon(rPolygon),
            maLineAttribute(rLineAttribute),
            maStrokeAttribute(rStrokeAttribute)
        {
            // simplify curve segments: moved here to not need to use it
            // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
            maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
        }

        PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const attribute::LineAttribute& rLineAttribute)
        :   BufferedDecompositionPrimitive2D(),
            maPolygon(rPolygon),
            maLineAttribute(rLineAttribute),
            maStrokeAttribute()
        {
            // simplify curve segments: moved here to not need to use it
            // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
            maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
        }

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

                return (getB2DPolygon() == rCompare.getB2DPolygon()
                    && getLineAttribute() == rCompare.getLineAttribute()
                    && getStrokeAttribute() == rCompare.getStrokeAttribute());
            }

            return false;
        }

        basegfx::B2DRange PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
        {
            basegfx::B2DRange aRetval;

            if(getLineAttribute().getWidth())
            {
                bool bUseDecomposition(false);

                if(basegfx::B2DLineJoin::Miter == getLineAttribute().getLineJoin())
                {
                    // if line is mitered, use parent call since mitered line
                    // geometry may use more space than the geometry grown by half line width
                    bUseDecomposition = true;
                }

                if(!bUseDecomposition && css::drawing::LineCap_SQUARE == getLineAttribute().getLineCap())
                {
                    // when drawing::LineCap_SQUARE is used the below method to grow the polygon
                    // range by half line width will not work, so use decomposition. Interestingly,
                    // the grow method below works perfectly for LineCap_ROUND since the grow is in
                    // all directions and the rounded cap needs the same grow in all directions independent
                    // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE
                    bUseDecomposition = true;
                }

                if (bUseDecomposition)
                {
                    // get correct range by using the decomposition fallback, reasons see above cases

                    // ofz#947 to optimize calculating the range, turn any slow dashes into a solid line
                    // when just calculating bounds
                    attribute::StrokeAttribute aOrigStrokeAttribute = maStrokeAttribute;
                    const_cast<PolygonStrokePrimitive2D*>(this)->maStrokeAttribute = attribute::StrokeAttribute();
                    aRetval = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
                    const_cast<PolygonStrokePrimitive2D*>(this)->maStrokeAttribute = aOrigStrokeAttribute;
                }
                else
                {
                    // for all other B2DLINEJOIN_* get the range from the base geometry
                    // and expand by half the line width
                    aRetval = getB2DPolygon().getB2DRange();
                    aRetval.grow(getLineAttribute().getWidth() * 0.5);
                }
            }
            else
            {
                // this is a hairline, thus the line width is view-dependent. Get range of polygon
                // as base size
                aRetval = getB2DPolygon().getB2DRange();

                if(!aRetval.isEmpty())
                {
                    // Calculate view-dependent hairline width
                    const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
                    const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);

                    if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
                    {
                        aRetval.grow(fDiscreteHalfLineWidth);
                    }
                }
            }

            return aRetval;
        }

        // provide unique ID
        ImplPrimitive2DIDBlock(PolygonStrokePrimitive2D, PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D)

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


namespace drawinglayer
{
    namespace primitive2d
    {
        void PolygonWavePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            if(getB2DPolygon().count())
            {
                const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth()));
                const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight()));

                if(bHasWidth && bHasHeight)
                {
                    // create waveline curve
                    const basegfx::B2DPolygon aWaveline(basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight()));
                    rContainer.push_back(new PolygonStrokePrimitive2D(aWaveline, getLineAttribute(), getStrokeAttribute()));
                }
                else
                {
                    // flat waveline, decompose to simple line primitive
                    rContainer.push_back(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), getStrokeAttribute()));
                }
            }
        }

        PolygonWavePrimitive2D::PolygonWavePrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const attribute::LineAttribute& rLineAttribute,
            const attribute::StrokeAttribute& rStrokeAttribute,
            double fWaveWidth,
            double fWaveHeight)
        :   PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute),
            mfWaveWidth(fWaveWidth),
            mfWaveHeight(fWaveHeight)
        {
            if(mfWaveWidth < 0.0)
            {
                mfWaveWidth = 0.0;
            }

            if(mfWaveHeight < 0.0)
            {
                mfWaveHeight = 0.0;
            }
        }

        PolygonWavePrimitive2D::PolygonWavePrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const attribute::LineAttribute& rLineAttribute,
            double fWaveWidth,
            double fWaveHeight)
        :   PolygonStrokePrimitive2D(rPolygon, rLineAttribute),
            mfWaveWidth(fWaveWidth),
            mfWaveHeight(fWaveHeight)
        {
            if(mfWaveWidth < 0.0)
            {
                mfWaveWidth = 0.0;
            }

            if(mfWaveHeight < 0.0)
            {
                mfWaveHeight = 0.0;
            }
        }

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

                return (getWaveWidth() == rCompare.getWaveWidth()
                    && getWaveHeight() == rCompare.getWaveHeight());
            }

            return false;
        }

        basegfx::B2DRange PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
        {
            // get range of parent
            basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation));

            // if WaveHeight, grow by it
            if(basegfx::fTools::more(getWaveHeight(), 0.0))
            {
                aRetval.grow(getWaveHeight());
            }

            // if line width, grow by it
            if(basegfx::fTools::more(getLineAttribute().getWidth(), 0.0))
            {
                aRetval.grow(getLineAttribute().getWidth() * 0.5);
            }

            return aRetval;
        }

        // provide unique ID
        ImplPrimitive2DIDBlock(PolygonWavePrimitive2D, PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D)

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


namespace drawinglayer
{
    namespace primitive2d
    {
        void PolygonStrokeArrowPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
        {
            // copy local polygon, it may be changed
            basegfx::B2DPolygon aLocalPolygon(getB2DPolygon());
            aLocalPolygon.removeDoublePoints();
            basegfx::B2DPolyPolygon aArrowA;
            basegfx::B2DPolyPolygon aArrowB;

            if(!aLocalPolygon.isClosed() && aLocalPolygon.count() > 1)
            {
                // apply arrows
                const double fPolyLength(basegfx::utils::getLength(aLocalPolygon));
                double fStart(0.0);
                double fEnd(0.0);
                double fStartOverlap(0.0);
                double fEndOverlap(0.0);

                if(!getStart().isDefault() && getStart().isActive())
                {
                    // create start arrow primitive and consume
                    aArrowA = basegfx::utils::createAreaGeometryForLineStartEnd(
                        aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(),
                        fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart);

                    // create some overlapping, compromise between straight and peaked markers
                    // for marker width 0.3cm and marker line width 0.02cm
                    fStartOverlap = getStart().getWidth() / 15.0;
                }

                if(!getEnd().isDefault() && getEnd().isActive())
                {
                    // create end arrow primitive and consume
                    aArrowB = basegfx::utils::createAreaGeometryForLineStartEnd(
                        aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(),
                        fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd);

                    // create some overlapping
                    fEndOverlap = getEnd().getWidth() / 15.0;
                }

                if(0.0 != fStart || 0.0 != fEnd)
                {
                    // build new poly, consume something from old poly
                    aLocalPolygon = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart-fStartOverlap, fPolyLength - fEnd + fEndOverlap, fPolyLength);
                }
            }

            // add shaft
            rContainer.push_back(new
                PolygonStrokePrimitive2D(
                    aLocalPolygon, getLineAttribute(), getStrokeAttribute()));

            if(aArrowA.count())
            {
                rContainer.push_back(
                    new PolyPolygonColorPrimitive2D(
                        aArrowA, getLineAttribute().getColor()));
            }

            if(aArrowB.count())
            {
                rContainer.push_back(
                    new PolyPolygonColorPrimitive2D(
                        aArrowB, getLineAttribute().getColor()));
            }
        }

        PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const attribute::LineAttribute& rLineAttribute,
            const attribute::StrokeAttribute& rStrokeAttribute,
            const attribute::LineStartEndAttribute& rStart,
            const attribute::LineStartEndAttribute& rEnd)
        :   PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute),
            maStart(rStart),
            maEnd(rEnd)
        {
        }

        PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
            const basegfx::B2DPolygon& rPolygon,
            const attribute::LineAttribute& rLineAttribute,
            const attribute::LineStartEndAttribute& rStart,
            const attribute::LineStartEndAttribute& rEnd)
        :   PolygonStrokePrimitive2D(rPolygon, rLineAttribute),
            maStart(rStart),
            maEnd(rEnd)
        {
        }

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

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

            return false;
        }

        basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
        {
            if(getStart().isActive() || getEnd().isActive())
            {
                // use decomposition when line start/end is used
                return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
            }
            else
            {
                // get range from parent
                return PolygonStrokePrimitive2D::getB2DRange(rViewInformation);
            }
        }

        // provide unique ID
        ImplPrimitive2DIDBlock(PolygonStrokeArrowPrimitive2D, PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D)

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

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