diff options
Diffstat (limited to 'drawinglayer/source/primitive2d/polygonprimitive2d.cxx')
-rw-r--r-- | drawinglayer/source/primitive2d/polygonprimitive2d.cxx | 381 |
1 files changed, 299 insertions, 82 deletions
diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx index 1cbde11ae96b..ab6833a44ffa 100644 --- a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx @@ -24,18 +24,41 @@ #include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <basegfx/polygon/b2dlinegeometry.hxx> #include <com/sun/star/drawing/LineCap.hpp> +#include <utility> using namespace com::sun::star; +namespace +{ +void implGrowHairline(basegfx::B2DRange& rRange, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) +{ + if (!rRange.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)) + { + rRange.grow(fDiscreteHalfLineWidth); + } + } +} +} + namespace drawinglayer::primitive2d { -PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(const basegfx::B2DPolygon& rPolygon, +PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(basegfx::B2DPolygon aPolygon, const basegfx::BColor& rBColor) - : maPolygon(rPolygon) + : maPolygon(std::move(aPolygon)) , maBColor(rBColor) { } @@ -60,18 +83,8 @@ PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rView // 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); - } - } + // Calculate and grow by view-dependent hairline width + implGrowHairline(aRetval, rViewInformation); // return range return aRetval; @@ -83,8 +96,120 @@ sal_uInt32 PolygonHairlinePrimitive2D::getPrimitive2DID() const return PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D; } -void PolygonMarkerPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +SingleLinePrimitive2D::SingleLinePrimitive2D(const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + const basegfx::BColor& rBColor) + : BasePrimitive2D() + , maStart(rStart) + , maEnd(rEnd) + , maBColor(rBColor) +{ +} + +bool SingleLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const SingleLinePrimitive2D& rCompare( + static_cast<const SingleLinePrimitive2D&>(rPrimitive)); + + return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd() + && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange +SingleLinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval(getStart(), getEnd()); + + // Calculate and grow by view-dependent hairline width + implGrowHairline(aRetval, rViewInformation); + + return aRetval; +} + +sal_uInt32 SingleLinePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D; +} + +void SingleLinePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (getStart() == getEnd()) + { + // single point + Primitive2DContainer aSequence = { new PointArrayPrimitive2D( + std::vector<basegfx::B2DPoint>{ getStart() }, getBColor()) }; + rVisitor.visit(aSequence); + } + else + { + // line + Primitive2DContainer aSequence = { new PolygonHairlinePrimitive2D( + basegfx::B2DPolygon{ getStart(), getEnd() }, getBColor()) }; + rVisitor.visit(aSequence); + } +} + +LineRectanglePrimitive2D::LineRectanglePrimitive2D(const basegfx::B2DRange& rB2DRange, + const basegfx::BColor& rBColor) + : BasePrimitive2D() + , maB2DRange(rB2DRange) + , maBColor(rBColor) +{ +} + +bool LineRectanglePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const LineRectanglePrimitive2D& rCompare( + static_cast<const LineRectanglePrimitive2D&>(rPrimitive)); + + return (getB2DRange() == rCompare.getB2DRange() && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange +LineRectanglePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval(getB2DRange()); + + // Calculate and grow by view-dependent hairline width + implGrowHairline(aRetval, rViewInformation); + + return aRetval; +} + +sal_uInt32 LineRectanglePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D; +} + +void LineRectanglePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(getB2DRange())); + Primitive2DContainer aSequence = { new PolygonHairlinePrimitive2D(aPolygon, getBColor()) }; + rVisitor.visit(aSequence); +} + +Primitive2DReference PolygonMarkerPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& rViewInformation) const { // calculate logic DashLength const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation() @@ -103,20 +228,24 @@ void PolygonMarkerPrimitive2D::create2DDecomposition( basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA, &aDashedPolyPolyB, 2.0 * fLogicDashLength); - rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyA, getRGBColorA())); - rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyB, getRGBColorB())); + Primitive2DContainer aContainer; + aContainer.push_back( + new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyA), getRGBColorA())); + aContainer.push_back( + new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyB), getRGBColorB())); + return new GroupPrimitive2D(std::move(aContainer)); } else { - rContainer.push_back(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA())); + return new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA()); } } -PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(const basegfx::B2DPolygon& rPolygon, +PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(basegfx::B2DPolygon aPolygon, const basegfx::BColor& rRGBColorA, const basegfx::BColor& rRGBColorB, double fDiscreteDashLength) - : maPolygon(rPolygon) + : maPolygon(std::move(aPolygon)) , maRGBColorA(rRGBColorA) , maRGBColorB(rRGBColorB) , mfDiscreteDashLength(fDiscreteDashLength) @@ -169,7 +298,7 @@ void PolygonMarkerPrimitive2D::get2DDecomposition( { bool bNeedNewDecomposition(false); - if (!getBuffered2DDecomposition().empty()) + if (getBuffered2DDecomposition()) { if (rViewInformation.getInverseObjectToViewTransformation() != maLastInverseObjectToViewTransformation) @@ -181,11 +310,10 @@ void PolygonMarkerPrimitive2D::get2DDecomposition( if (bNeedNewDecomposition) { // conditions of last local decomposition have changed, delete - const_cast<PolygonMarkerPrimitive2D*>(this)->setBuffered2DDecomposition( - Primitive2DContainer()); + const_cast<PolygonMarkerPrimitive2D*>(this)->setBuffered2DDecomposition(nullptr); } - if (getBuffered2DDecomposition().empty()) + if (!getBuffered2DDecomposition()) { // remember last used InverseObjectToViewTransformation PolygonMarkerPrimitive2D* pThat = const_cast<PolygonMarkerPrimitive2D*>(this); @@ -207,11 +335,11 @@ sal_uInt32 PolygonMarkerPrimitive2D::getPrimitive2DID() const namespace drawinglayer::primitive2d { -void PolygonStrokePrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolygonStrokePrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getB2DPolygon().count()) - return; + return nullptr; // #i102241# try to simplify before usage const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon())); @@ -251,29 +379,33 @@ void PolygonStrokePrimitive2D::create2DDecomposition( } // create primitive + Primitive2DContainer aContainer; 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)); + basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b)); const basegfx::BColor aColor(getLineAttribute().getColor()); - rContainer.push_back(new PolyPolygonColorPrimitive2D(aNewPolyPolygon, aColor)); + aContainer.push_back( + new PolyPolygonColorPrimitive2D(std::move(aNewPolyPolygon), aColor)); } + return new GroupPrimitive2D(std::move(aContainer)); } else { - rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aHairLinePolyPolygon, - getLineAttribute().getColor())); + return new PolyPolygonHairlinePrimitive2D(std::move(aHairLinePolyPolygon), + getLineAttribute().getColor()); } } -PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( - const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute, - const attribute::StrokeAttribute& rStrokeAttribute) - : maPolygon(rPolygon) +PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon, + const attribute::LineAttribute& rLineAttribute, + attribute::StrokeAttribute aStrokeAttribute) + : maPolygon(std::move(aPolygon)) , maLineAttribute(rLineAttribute) - , maStrokeAttribute(rStrokeAttribute) + , maStrokeAttribute(std::move(aStrokeAttribute)) + , maBufferedRange() { // MM01: keep these - these are no curve-decompposers but just checks // simplify curve segments: moved here to not need to use it @@ -281,10 +413,11 @@ PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); } -PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(const basegfx::B2DPolygon& rPolygon, +PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon, const attribute::LineAttribute& rLineAttribute) - : maPolygon(rPolygon) + : maPolygon(std::move(aPolygon)) , maLineAttribute(rLineAttribute) + , maBufferedRange() { // MM01: keep these - these are no curve-decompposers but just checks // simplify curve segments: moved here to not need to use it @@ -310,7 +443,11 @@ bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) con basegfx::B2DRange PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const { - basegfx::B2DRange aRetval; + if (!maBufferedRange.isEmpty()) + { + // use the view-independent, buffered B2DRange + return maBufferedRange; + } if (getLineAttribute().getWidth()) { @@ -330,6 +467,20 @@ PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewIn // 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 + + // NOTE: I thought about using [sqrt(2) * 0.5] a a factor for LineCap_SQUARE and not + // set bUseDecomposition. I even tried that it works. Then an auto-test failing showed + // not only that view-dependent stuff needs to be considered (what is done for the + // hairline case below), *BUT* also e.g. conversions to PNG/exports use the B2DRange + // of the geometry, so: + // - expanding by 1/2 LineWidth is OK for rounded + // - expanding by more (like sqrt(2) * 0.5 * LineWidth) immediately extends the size + // of e.g. geometry converted to PNG, plus many similar cases that cannot be thought + // of in advance. + // This means that converting those thought-experiment examples in (4) will and do lead + // to bigger e.g. Bitmap conversion(s), not avoiding but painting the free space. That + // could only be done by correctly and fully decomposing the geometry, including + // stroke, and accepting the cost... bUseDecomposition = true; } @@ -337,44 +488,108 @@ PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewIn { // 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; + // It is not a good idea to temporarily (re)set the PolygonStrokePrimitive2D + // to default. While it is understandable to use the speed advantage, it is + // bad for quite some reasons: + // + // (1) As described in include/drawinglayer/primitive2d/baseprimitive2d.hxx + // a Primitive is "noncopyable to make clear that a primitive is a read-only + // instance and copying or changing values is not intended". This is the base + // assumption for many decisions for Primitive handling. + // (2) For example, that the decomposition is *always* re-usable. It cannot change + // and is correct when it already exists since the values the decomposition is + // based on cannot change. + // (3) If this *is* done (like it was here) and the Primitive is derived from + // BufferedDecompositionPrimitive2D and thus buffers it's decomposition, + // the risk is that in this case the *wrong* decomposition will be used by + // other PrimitiveProcessors. Maybe not by the VclPixelProcessor2D/VclProcessor2D + // since it handles this primitive directly - not even sure for all cases. + // Sooner or later another PrimitiveProcessor will re-use this wrong temporary + // decomposition, and as an error, a non-stroked line will be painted/used. + // (4) The B2DRange is not strictly defined as minimal bound for the geometry, + // but it should be as small/tight as possible. Making it larger risks more + // area to be invalidated (repaint) and processed (all geometric stuff,l may + // include future and existing exports to other formats which are or will be + // implemented as PrimitiveProcessor). It is easy to imagine cases with much + // too large B2DRange - a line with a pattern that would solve to a single + // small start-rectangle and rest is empty, or a circle with a stroke that + // makes only a quarter of it visible. + // + // The reason to do this is speed, what is a good argument. But speed should + // only be used if the pair of [correctness/speed] does not sacrifice the correctness + // over the speed. + // Luckily there are alternatives to solve this and to keep [correctness/speed] + // valid: + // + // (a) Reset the temporary decomposition after having const-casted and + // changed maStrokeAttribute. + // Disadvantage: More const-cast hacks, plus this temporary decomposition + // will be potentially done repeatedly (every time + // PolygonStrokePrimitive2D::getB2DRange is called) + // (b) Use a temporary, local PolygonStrokePrimitive2D here, with neutral + // PolygonStrokePrimitive2D and call ::getB2DRange() at it. That way + // the buffered decomposition will not be harmed. + // Disadvantage: Same as (a), decomposition will be potentially done repeatedly + // (c) Use a temporary, local PolygonStrokePrimitive2D and buffer B2DRange + // locally for this Primitive. Due to (1)/(2) this cannot change, so + // when calculated once it is totally legal to use it. + // + // Thus here I would use (c): It accepts the disadvantages of (4) over speed, but + // avoids the errors/problems from (1-4). + // Additional argument for this: The hairline case below *also* uses the full + // B2DRange of the polygon, ignoring an evtl. stroke, so (4) applies + if (!getStrokeAttribute().isDefault()) + { + // only do this if StrokeAttribute is used, else recursion may happen (!) + const rtl::Reference<primitive2d::PolygonStrokePrimitive2D> + aTemporaryPrimitiveWithoutStroke(new primitive2d::PolygonStrokePrimitive2D( + getB2DPolygon(), getLineAttribute())); + maBufferedRange + = aTemporaryPrimitiveWithoutStroke + ->BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + } + else + { + // fallback to normal decompose, that result can be used for visualization + // later, too. Still buffer B2DRange in maBufferedRange, so it needs to be + // merged into one B2DRange only once + maBufferedRange = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + } } 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); + // and expand by half the line width. + maBufferedRange = getB2DPolygon().getB2DRange(); + maBufferedRange.grow(getLineAttribute().getWidth() * 0.5); } + + return maBufferedRange; } - else + + // It is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size. + // CAUTION: Since a hairline *is* view-dependent, + // - either use maBufferedRange, additionally remember view-dependent + // factor & reset if that changes + // - or do not buffer for hairline -> not really needed, the range is buffered + // in the B2DPolygon, no decomposition is needed and a simple grow is cheap + basegfx::B2DRange aHairlineRange = getB2DPolygon().getB2DRange(); + + if (!aHairlineRange.isEmpty()) { - // this is a hairline, thus the line width is view-dependent. Get range of polygon - // as base size - aRetval = getB2DPolygon().getB2DRange(); + // 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 (!aRetval.isEmpty()) + if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) { - // 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); - } + aHairlineRange.grow(fDiscreteHalfLineWidth); } } - return aRetval; + return aHairlineRange; } // provide unique ID @@ -383,11 +598,11 @@ sal_uInt32 PolygonStrokePrimitive2D::getPrimitive2DID() const return PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D; } -void PolygonWavePrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolygonWavePrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getB2DPolygon().count()) - return; + return nullptr; const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth())); const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight())); @@ -395,16 +610,16 @@ void PolygonWavePrimitive2D::create2DDecomposition( if (bHasWidth && bHasHeight) { // create waveline curve - const basegfx::B2DPolygon aWaveline( + basegfx::B2DPolygon aWaveline( basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight())); - rContainer.push_back( - new PolygonStrokePrimitive2D(aWaveline, getLineAttribute(), getStrokeAttribute())); + return new PolygonStrokePrimitive2D(std::move(aWaveline), getLineAttribute(), + getStrokeAttribute()); } else { // flat waveline, decompose to simple line primitive - rContainer.push_back(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), - getStrokeAttribute())); + return new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), + getStrokeAttribute()); } } @@ -486,8 +701,8 @@ sal_uInt32 PolygonWavePrimitive2D::getPrimitive2DID() const return PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D; } -void PolygonStrokeArrowPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolygonStrokeArrowPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { // copy local polygon, it may be changed basegfx::B2DPolygon aLocalPolygon(getB2DPolygon()); @@ -537,20 +752,22 @@ void PolygonStrokeArrowPrimitive2D::create2DDecomposition( } // add shaft - rContainer.push_back( - new PolygonStrokePrimitive2D(aLocalPolygon, getLineAttribute(), getStrokeAttribute())); + Primitive2DContainer aContainer; + aContainer.push_back(new PolygonStrokePrimitive2D(std::move(aLocalPolygon), getLineAttribute(), + getStrokeAttribute())); if (aArrowA.count()) { - rContainer.push_back( - new PolyPolygonColorPrimitive2D(aArrowA, getLineAttribute().getColor())); + aContainer.push_back( + new PolyPolygonColorPrimitive2D(std::move(aArrowA), getLineAttribute().getColor())); } if (aArrowB.count()) { - rContainer.push_back( - new PolyPolygonColorPrimitive2D(aArrowB, getLineAttribute().getColor())); + aContainer.push_back( + new PolyPolygonColorPrimitive2D(std::move(aArrowB), getLineAttribute().getColor())); } + return new GroupPrimitive2D(std::move(aContainer)); } PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( |