diff options
Diffstat (limited to 'drawinglayer/source')
138 files changed, 10809 insertions, 4237 deletions
diff --git a/drawinglayer/source/animation/animationtiming.cxx b/drawinglayer/source/animation/animationtiming.cxx index c1471e43bd71..364ae899c9ca 100644 --- a/drawinglayer/source/animation/animationtiming.cxx +++ b/drawinglayer/source/animation/animationtiming.cxx @@ -116,7 +116,7 @@ namespace drawinglayer::animation double AnimationEntryLinear::getStateAtTime(double fTime) const { - if(basegfx::fTools::more(mfDuration, 0.0)) + if(mfDuration > 0.0) { const double fFactor(fTime / mfDuration); @@ -193,19 +193,15 @@ namespace drawinglayer::animation bool AnimationEntryList::operator==(const AnimationEntry& rCandidate) const { - const AnimationEntryList* pCompare = dynamic_cast< const AnimationEntryList* >(&rCandidate); + const AnimationEntryList* pCompare = dynamic_cast<const AnimationEntryList*>(&rCandidate); - if(pCompare && mfDuration == pCompare->mfDuration) + if (pCompare && mfDuration == pCompare->mfDuration) { - for(size_t a(0); a < maEntries.size(); a++) - { - if(!(*maEntries[a] == *pCompare->maEntries[a])) - { - return false; - } - } - - return true; + return std::equal(maEntries.cbegin(), maEntries.cend(), + pCompare->maEntries.cbegin(), pCompare->maEntries.cend(), + [](const auto& lhs, const auto& rhs) { + return *lhs == *rhs; + }); } return false; diff --git a/drawinglayer/source/attribute/fillgradientattribute.cxx b/drawinglayer/source/attribute/fillgradientattribute.cxx index b8b06e20bbbf..e02fdd4a5dad 100644 --- a/drawinglayer/source/attribute/fillgradientattribute.cxx +++ b/drawinglayer/source/attribute/fillgradientattribute.cxx @@ -18,8 +18,7 @@ */ #include <drawinglayer/attribute/fillgradientattribute.hxx> -#include <basegfx/color/bcolor.hxx> - +#include <basegfx/utils/gradienttools.hxx> namespace drawinglayer::attribute { @@ -31,29 +30,42 @@ namespace drawinglayer::attribute double mfOffsetX; double mfOffsetY; double mfAngle; - basegfx::BColor maStartColor; - basegfx::BColor maEndColor; - GradientStyle meStyle; + basegfx::BColorStops maColorStops; + css::awt::GradientStyle meStyle; sal_uInt16 mnSteps; ImpFillGradientAttribute( - GradientStyle eStyle, + css::awt::GradientStyle eStyle, double fBorder, double fOffsetX, double fOffsetY, double fAngle, - const basegfx::BColor& rStartColor, - const basegfx::BColor& rEndColor, + const basegfx::BColorStops& rColorStops, sal_uInt16 nSteps) : mfBorder(fBorder), mfOffsetX(fOffsetX), mfOffsetY(fOffsetY), mfAngle(fAngle), - maStartColor(rStartColor), - maEndColor(rEndColor), + maColorStops(rColorStops), // copy ColorStops meStyle(eStyle), mnSteps(nSteps) { + // Correct the local ColorStops. That will guarantee that the + // content does contain no offsets < 0.0, > 1.0 or double + // ones, also secures sorted arrangement and checks for + // double colors, too (see there for more information). + // This is what the usages of this in primitives need. + // Since FillGradientAttribute is read-only doing this + // once here in the constructor is sufficient + maColorStops.sortAndCorrect(); + + // sortAndCorrectColorStops is rigid and can return + // an empty result. To keep things simple, add a single + // fallback value + if (maColorStops.empty()) + { + maColorStops.emplace_back(0.0, basegfx::BColor()); + } } ImpFillGradientAttribute() @@ -61,19 +73,21 @@ namespace drawinglayer::attribute mfOffsetX(0.0), mfOffsetY(0.0), mfAngle(0.0), - meStyle(GradientStyle::Linear), + maColorStops(), + meStyle(css::awt::GradientStyle_LINEAR), mnSteps(0) { + // always add a fallback color, see above + maColorStops.emplace_back(0.0, basegfx::BColor()); } // data read access - GradientStyle getStyle() const { return meStyle; } + css::awt::GradientStyle getStyle() const { return meStyle; } double getBorder() const { return mfBorder; } double getOffsetX() const { return mfOffsetX; } double getOffsetY() const { return mfOffsetY; } double getAngle() const { return mfAngle; } - const basegfx::BColor& getStartColor() const { return maStartColor; } - const basegfx::BColor& getEndColor() const { return maEndColor; } + const basegfx::BColorStops& getColorStops() const { return maColorStops; } sal_uInt16 getSteps() const { return mnSteps; } bool operator==(const ImpFillGradientAttribute& rCandidate) const @@ -83,8 +97,7 @@ namespace drawinglayer::attribute && getOffsetX() == rCandidate.getOffsetX() && getOffsetY() == rCandidate.getOffsetY() && getAngle() == rCandidate.getAngle() - && getStartColor() == rCandidate.getStartColor() - && getEndColor() == rCandidate.getEndColor() + && getColorStops() == rCandidate.getColorStops() && getSteps() == rCandidate.getSteps()); } }; @@ -99,16 +112,15 @@ namespace drawinglayer::attribute } FillGradientAttribute::FillGradientAttribute( - GradientStyle eStyle, + css::awt::GradientStyle eStyle, double fBorder, double fOffsetX, double fOffsetY, double fAngle, - const basegfx::BColor& rStartColor, - const basegfx::BColor& rEndColor, + const basegfx::BColorStops& rColorStops, sal_uInt16 nSteps) : mpFillGradientAttribute(ImpFillGradientAttribute( - eStyle, fBorder, fOffsetX, fOffsetY, fAngle, rStartColor, rEndColor, nSteps)) + eStyle, fBorder, fOffsetX, fOffsetY, fAngle, rColorStops, nSteps)) { } @@ -128,6 +140,41 @@ namespace drawinglayer::attribute return mpFillGradientAttribute.same_object(theGlobalDefault()); } + // MCGR: Check if rendering cannot be handled by old vcl stuff + bool FillGradientAttribute::cannotBeHandledByVCL() const + { + // MCGR: If GradientStops are used, use decomposition since vcl is not able + // to render multi-color gradients + if (getColorStops().size() != 2) + { + return true; + } + + // MCGR: If GradientStops do not start and stop at traditional Start/EndColor, + // use decomposition since vcl is not able to render this + if (!getColorStops().empty()) + { + if (!basegfx::fTools::equalZero(getColorStops().front().getStopOffset()) + || !basegfx::fTools::equal(getColorStops().back().getStopOffset(), 1.0)) + { + return true; + } + } + + // VCL should be able to handle all styles, but for tdf#133477 the VCL result + // is different from processing the gradient manually by drawinglayer + // (and the Writer unittest for it fails). Keep using the drawinglayer code + // until somebody founds out what's wrong and fixes it. + if (getStyle() != css::awt::GradientStyle_LINEAR + && getStyle() != css::awt::GradientStyle_AXIAL + && getStyle() != css::awt::GradientStyle_RADIAL) + { + return true; + } + + return false; + } + FillGradientAttribute& FillGradientAttribute::operator=(const FillGradientAttribute&) = default; FillGradientAttribute& FillGradientAttribute::operator=(FillGradientAttribute&&) = default; @@ -141,14 +188,9 @@ namespace drawinglayer::attribute return rCandidate.mpFillGradientAttribute == mpFillGradientAttribute; } - const basegfx::BColor& FillGradientAttribute::getStartColor() const - { - return mpFillGradientAttribute->getStartColor(); - } - - const basegfx::BColor& FillGradientAttribute::getEndColor() const + const basegfx::BColorStops& FillGradientAttribute::getColorStops() const { - return mpFillGradientAttribute->getEndColor(); + return mpFillGradientAttribute->getColorStops(); } double FillGradientAttribute::getBorder() const @@ -171,7 +213,7 @@ namespace drawinglayer::attribute return mpFillGradientAttribute->getAngle(); } - GradientStyle FillGradientAttribute::getStyle() const + css::awt::GradientStyle FillGradientAttribute::getStyle() const { return mpFillGradientAttribute->getStyle(); } diff --git a/drawinglayer/source/attribute/fillgraphicattribute.cxx b/drawinglayer/source/attribute/fillgraphicattribute.cxx index b36520d4f981..300d6f6123f1 100644 --- a/drawinglayer/source/attribute/fillgraphicattribute.cxx +++ b/drawinglayer/source/attribute/fillgraphicattribute.cxx @@ -22,6 +22,7 @@ #include <algorithm> #include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <utility> #include <vcl/graph.hxx> namespace drawinglayer::attribute @@ -41,12 +42,12 @@ namespace drawinglayer::attribute double mfOffsetY; ImpFillGraphicAttribute( - const Graphic& rGraphic, + Graphic aGraphic, const basegfx::B2DRange& rGraphicRange, bool bTiling, double fOffsetX, double fOffsetY) - : maGraphic(rGraphic), + : maGraphic(std::move(aGraphic)), maGraphicRange(rGraphicRange), mbTiling(bTiling), mfOffsetX(fOffsetX), diff --git a/drawinglayer/source/attribute/fontattribute.cxx b/drawinglayer/source/attribute/fontattribute.cxx index 8ae3836d8aa7..c1f0ab000d86 100644 --- a/drawinglayer/source/attribute/fontattribute.cxx +++ b/drawinglayer/source/attribute/fontattribute.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/attribute/fontattribute.hxx> #include <rtl/ustring.hxx> +#include <utility> namespace drawinglayer::attribute { @@ -38,11 +39,11 @@ public: bool mbBiDiStrong : 1; // BiDi Flag bool mbMonospaced : 1; - ImpFontAttribute(const OUString& rFamilyName, const OUString& rStyleName, sal_uInt16 nWeight, - bool bSymbol, bool bVertical, bool bItalic, bool bMonospaced, bool bOutline, - bool bRTL, bool bBiDiStrong) - : maFamilyName(rFamilyName) - , maStyleName(rStyleName) + ImpFontAttribute(OUString aFamilyName, OUString aStyleName, sal_uInt16 nWeight, bool bSymbol, + bool bVertical, bool bItalic, bool bMonospaced, bool bOutline, bool bRTL, + bool bBiDiStrong) + : maFamilyName(std::move(aFamilyName)) + , maStyleName(std::move(aStyleName)) , mnWeight(nWeight) , mbSymbol(bSymbol) , mbVertical(bVertical) diff --git a/drawinglayer/source/attribute/linestartendattribute.cxx b/drawinglayer/source/attribute/linestartendattribute.cxx index 57a680db377c..33ac17c70599 100644 --- a/drawinglayer/source/attribute/linestartendattribute.cxx +++ b/drawinglayer/source/attribute/linestartendattribute.cxx @@ -20,6 +20,7 @@ #include <drawinglayer/attribute/linestartendattribute.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolypolygon.hxx> +#include <utility> namespace drawinglayer::attribute @@ -35,10 +36,10 @@ namespace drawinglayer::attribute ImpLineStartEndAttribute( double fWidth, - const basegfx::B2DPolyPolygon& rPolyPolygon, + basegfx::B2DPolyPolygon aPolyPolygon, bool bCentered) : mfWidth(fWidth), - maPolyPolygon(rPolyPolygon), + maPolyPolygon(std::move(aPolyPolygon)), mbCentered(bCentered) { } diff --git a/drawinglayer/source/attribute/sdrallattribute3d.cxx b/drawinglayer/source/attribute/sdrallattribute3d.cxx index 079f655796d9..8c74306ca1dd 100644 --- a/drawinglayer/source/attribute/sdrallattribute3d.cxx +++ b/drawinglayer/source/attribute/sdrallattribute3d.cxx @@ -18,21 +18,22 @@ */ #include <drawinglayer/attribute/sdrallattribute3d.hxx> +#include <utility> namespace drawinglayer::attribute { SdrLineFillShadowAttribute3D::SdrLineFillShadowAttribute3D( - const SdrLineAttribute& rLine, - const SdrFillAttribute& rFill, - const SdrLineStartEndAttribute& rLineStartEnd, - const SdrShadowAttribute& rShadow, - const FillGradientAttribute& rFillFloatTransGradient) - : maLine(rLine), - maFill(rFill), - maLineStartEnd(rLineStartEnd), - maShadow(rShadow), - maFillFloatTransGradient(rFillFloatTransGradient) + SdrLineAttribute aLine, + SdrFillAttribute aFill, + SdrLineStartEndAttribute aLineStartEnd, + SdrShadowAttribute aShadow, + FillGradientAttribute aFillFloatTransGradient) + : maLine(std::move(aLine)), + maFill(std::move(aFill)), + maLineStartEnd(std::move(aLineStartEnd)), + maShadow(std::move(aShadow)), + maFillFloatTransGradient(std::move(aFillFloatTransGradient)) { } diff --git a/drawinglayer/source/attribute/sdrfillattribute.cxx b/drawinglayer/source/attribute/sdrfillattribute.cxx index b582ee0a11d0..8cee8f98d1e9 100644 --- a/drawinglayer/source/attribute/sdrfillattribute.cxx +++ b/drawinglayer/source/attribute/sdrfillattribute.cxx @@ -22,6 +22,7 @@ #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> #include <drawinglayer/attribute/fillhatchattribute.hxx> #include <drawinglayer/attribute/fillgradientattribute.hxx> +#include <utility> namespace drawinglayer::attribute @@ -40,14 +41,14 @@ namespace drawinglayer::attribute ImpSdrFillAttribute( double fTransparence, const basegfx::BColor& rColor, - const FillGradientAttribute& rGradient, - const FillHatchAttribute& rHatch, - const SdrFillGraphicAttribute& rFillGraphic) + FillGradientAttribute aGradient, + FillHatchAttribute aHatch, + SdrFillGraphicAttribute aFillGraphic) : mfTransparence(fTransparence), maColor(rColor), - maGradient(rGradient), - maHatch(rHatch), - maFillGraphic(rFillGraphic) + maGradient(std::move(aGradient)), + maHatch(std::move(aHatch)), + maFillGraphic(std::move(aFillGraphic)) { } @@ -81,6 +82,11 @@ namespace drawinglayer::attribute static SdrFillAttribute::ImplType SINGLETON; return SINGLETON; } + SdrFillAttribute::ImplType& slideBackgroundFillGlobalDefault() + { + static SdrFillAttribute::ImplType SINGLETON2; + return SINGLETON2; + } } SdrFillAttribute::SdrFillAttribute( @@ -94,8 +100,10 @@ namespace drawinglayer::attribute { } - SdrFillAttribute::SdrFillAttribute() - : mpSdrFillAttribute(theGlobalDefault()) + SdrFillAttribute::SdrFillAttribute(bool bSlideBackgroundFill) + : mpSdrFillAttribute(bSlideBackgroundFill + ? slideBackgroundFillGlobalDefault() + : theGlobalDefault()) { } @@ -110,6 +118,11 @@ namespace drawinglayer::attribute return mpSdrFillAttribute.same_object(theGlobalDefault()); } + bool SdrFillAttribute::isSlideBackgroundFill() const + { + return mpSdrFillAttribute.same_object(slideBackgroundFillGlobalDefault()); + } + SdrFillAttribute& SdrFillAttribute::operator=(const SdrFillAttribute&) = default; SdrFillAttribute& SdrFillAttribute::operator=(SdrFillAttribute&&) = default; diff --git a/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx b/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx index 14f53cf03db3..b78f3e322c38 100644 --- a/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx +++ b/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx @@ -23,6 +23,7 @@ #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> #include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <utility> #include <vcl/graph.hxx> @@ -44,7 +45,7 @@ namespace drawinglayer::attribute bool mbLogSize : 1; ImpSdrFillGraphicAttribute( - const Graphic& rFillGraphic, + Graphic aFillGraphic, const basegfx::B2DVector& rGraphicLogicSize, const basegfx::B2DVector& rSize, const basegfx::B2DVector& rOffset, @@ -53,7 +54,7 @@ namespace drawinglayer::attribute bool bTiling, bool bStretch, bool bLogSize) - : maFillGraphic(rFillGraphic), + : maFillGraphic(std::move(aFillGraphic)), maGraphicLogicSize(rGraphicLogicSize), maSize(rSize), maOffset(rOffset), diff --git a/drawinglayer/source/attribute/sdrglowattribute.cxx b/drawinglayer/source/attribute/sdrglowattribute.cxx index 36339dac0933..c27390d64d6d 100644 --- a/drawinglayer/source/attribute/sdrglowattribute.cxx +++ b/drawinglayer/source/attribute/sdrglowattribute.cxx @@ -23,8 +23,6 @@ SdrGlowAttribute::SdrGlowAttribute(const SdrGlowAttribute&) = default; SdrGlowAttribute::SdrGlowAttribute(SdrGlowAttribute&&) = default; -SdrGlowAttribute::~SdrGlowAttribute() = default; - SdrGlowAttribute& SdrGlowAttribute::operator=(const SdrGlowAttribute&) = default; SdrGlowAttribute& SdrGlowAttribute::operator=(SdrGlowAttribute&&) = default; diff --git a/drawinglayer/source/attribute/sdrlightingattribute3d.cxx b/drawinglayer/source/attribute/sdrlightingattribute3d.cxx index 4f9b75cd1ff2..0f625756383c 100644 --- a/drawinglayer/source/attribute/sdrlightingattribute3d.cxx +++ b/drawinglayer/source/attribute/sdrlightingattribute3d.cxx @@ -140,7 +140,7 @@ namespace drawinglayer::attribute const Sdr3DLightAttribute& rLight(rLightVector[a]); const double fCosFac(rLight.getDirection().scalar(aEyeNormal)); - if(basegfx::fTools::more(fCosFac, 0.0)) + if(fCosFac > 0.0) { aRetval += (rLight.getColor() * rColor) * fCosFac; @@ -151,7 +151,7 @@ namespace drawinglayer::attribute aSpecularNormal.normalize(); double fCosFac2(aSpecularNormal.scalar(aEyeNormal)); - if(basegfx::fTools::more(fCosFac2, 0.0)) + if(fCosFac2 > 0.0) { fCosFac2 = pow(fCosFac2, static_cast<double>(nSpecularIntensity)); aRetval += rSpecular * fCosFac2; diff --git a/drawinglayer/source/attribute/sdrlinestartendattribute.cxx b/drawinglayer/source/attribute/sdrlinestartendattribute.cxx index aa052236cd72..911f8aef8bf5 100644 --- a/drawinglayer/source/attribute/sdrlinestartendattribute.cxx +++ b/drawinglayer/source/attribute/sdrlinestartendattribute.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/attribute/sdrlinestartendattribute.hxx> #include <basegfx/polygon/b2dpolypolygon.hxx> +#include <utility> namespace drawinglayer::attribute @@ -38,16 +39,16 @@ namespace drawinglayer::attribute bool mbEndCentered : 1; // Line is centered on line end point ImpSdrLineStartEndAttribute( - const basegfx::B2DPolyPolygon& rStartPolyPolygon, - const basegfx::B2DPolyPolygon& rEndPolyPolygon, + basegfx::B2DPolyPolygon aStartPolyPolygon, + basegfx::B2DPolyPolygon aEndPolyPolygon, double fStartWidth, double fEndWidth, bool bStartActive, bool bEndActive, bool bStartCentered, bool bEndCentered) - : maStartPolyPolygon(rStartPolyPolygon), - maEndPolyPolygon(rEndPolyPolygon), + : maStartPolyPolygon(std::move(aStartPolyPolygon)), + maEndPolyPolygon(std::move(aEndPolyPolygon)), mfStartWidth(fStartWidth), mfEndWidth(fEndWidth), mbStartActive(bStartActive), diff --git a/drawinglayer/source/attribute/sdrshadowattribute.cxx b/drawinglayer/source/attribute/sdrshadowattribute.cxx index 6e046f1f07c7..1eb1b3ea687c 100644 --- a/drawinglayer/source/attribute/sdrshadowattribute.cxx +++ b/drawinglayer/source/attribute/sdrshadowattribute.cxx @@ -20,6 +20,7 @@ #include <drawinglayer/attribute/sdrshadowattribute.hxx> #include <basegfx/vector/b2dvector.hxx> #include <basegfx/color/bcolor.hxx> +#include <docmodel/theme/FormatScheme.hxx> namespace drawinglayer::attribute @@ -32,6 +33,7 @@ namespace drawinglayer::attribute basegfx::B2DVector maSize; // [0.0 .. 2.0] double mfTransparence; // [0.0 .. 1.0], 0.0==no transp. sal_Int32 mnBlur; // [0 .. 180], radius of the blur + model::RectangleAlignment meAlignment{model::RectangleAlignment::Unset}; // alignment of the shadow basegfx::BColor maColor; // color of shadow ImpSdrShadowAttribute( @@ -39,11 +41,13 @@ namespace drawinglayer::attribute const basegfx::B2DVector& rSize, double fTransparence, sal_Int32 nBlur, + model::RectangleAlignment eAlignment, const basegfx::BColor& rColor) : maOffset(rOffset), maSize(rSize), mfTransparence(fTransparence), mnBlur(nBlur), + meAlignment(eAlignment), maColor(rColor) { } @@ -67,6 +71,7 @@ namespace drawinglayer::attribute && getSize() == rCandidate.getSize() && getTransparence() == rCandidate.getTransparence() && getBlur() == rCandidate.getBlur() + && meAlignment == rCandidate.meAlignment && getColor() == rCandidate.getColor()); } }; @@ -86,9 +91,10 @@ namespace drawinglayer::attribute const basegfx::B2DVector& rSize, double fTransparence, sal_Int32 nBlur, + model::RectangleAlignment eAlignment, const basegfx::BColor& rColor) : mpSdrShadowAttribute(ImpSdrShadowAttribute( - rOffset, rSize, fTransparence,nBlur, rColor)) + rOffset, rSize, fTransparence, nBlur, eAlignment, rColor)) { } @@ -141,6 +147,11 @@ namespace drawinglayer::attribute return mpSdrShadowAttribute->getBlur(); } + model::RectangleAlignment SdrShadowAttribute::getAlignment() const + { + return mpSdrShadowAttribute->meAlignment; + } + const basegfx::BColor& SdrShadowAttribute::getColor() const { return mpSdrShadowAttribute->getColor(); diff --git a/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx b/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx index abca8a310925..22a20f095d24 100644 --- a/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx +++ b/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx @@ -24,7 +24,6 @@ #include <com/sun/star/uno/XComponentContext.hpp> #include <cppuhelper/implbase2.hxx> #include <cppuhelper/supportsservice.hxx> -#include <comphelper/sequence.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <basegfx/numeric/ftools.hxx> #include <vcl/bitmapex.hxx> @@ -44,7 +43,7 @@ namespace drawinglayer::unorenderer namespace { class XPrimitive2DRenderer: - public cppu::WeakAggImplHelper2< + public cppu::WeakImplHelper< css::graphic::XPrimitive2DRenderer, css::lang::XServiceInfo> { public: @@ -100,7 +99,7 @@ namespace drawinglayer::unorenderer const double fWidth(aRange.getWidth()); const double fHeight(aRange.getHeight()); - if(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0)) + if(fWidth > 0.0 && fHeight > 0.0) { if(0 == DPI_X) { @@ -117,7 +116,15 @@ namespace drawinglayer::unorenderer MaximumQuadraticPixels = 500000; } - const auto aViewInformation2D = geometry::createViewInformation2D(aViewInformationSequence); + auto aViewInformation2D = geometry::createViewInformation2D(aViewInformationSequence); + + if(aViewInformation2D.getViewport().isEmpty()) + { + // we have a Viewport since we create a discrete pixel device, use it + // if none is given + aViewInformation2D.setViewport(aRange); + } + const sal_uInt32 nDiscreteWidth(basegfx::fround(o3tl::convert(fWidth, eRangeUnit, o3tl::Length::in) * DPI_X)); const sal_uInt32 nDiscreteHeight(basegfx::fround(o3tl::convert(fHeight, eRangeUnit, o3tl::Length::in) * DPI_Y)); @@ -133,7 +140,7 @@ namespace drawinglayer::unorenderer const primitive2d::Primitive2DReference xEmbedRef( new primitive2d::TransformPrimitive2D( aEmbedding, - comphelper::sequenceToContainer<primitive2d::Primitive2DContainer>(aPrimitive2DSequence))); + aPrimitive2DSequence)); primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; BitmapEx aBitmapEx( @@ -147,7 +154,7 @@ namespace drawinglayer::unorenderer if(!aBitmapEx.IsEmpty()) { aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); - aBitmapEx.SetPrefSize(Size(basegfx::fround(fWidth), basegfx::fround(fHeight))); + aBitmapEx.SetPrefSize(Size(basegfx::fround<tools::Long>(fWidth), basegfx::fround<tools::Long>(fHeight))); XBitmap = vcl::unotools::xBitmapFromBitmapEx(aBitmapEx); } } diff --git a/drawinglayer/source/dumper/XShapeDumper.cxx b/drawinglayer/source/dumper/XShapeDumper.cxx index 1434de2ba219..cf0da17e4398 100644 --- a/drawinglayer/source/dumper/XShapeDumper.cxx +++ b/drawinglayer/source/dumper/XShapeDumper.cxx @@ -1077,9 +1077,8 @@ void dumpInteropGrabBagAsElement(const uno::Sequence< beans::PropertyValue>& aIn { (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "InteropGrabBag" )); - sal_Int32 nLength = aInteropGrabBag.getLength(); - for (sal_Int32 i = 0; i < nLength; ++i) - dumpPropertyValueAsElement(aInteropGrabBag[i], xmlWriter); + for (const auto& item: aInteropGrabBag) + dumpPropertyValueAsElement(item, xmlWriter); (void)xmlTextWriterEndElement( xmlWriter ); } @@ -1957,7 +1956,7 @@ OUString XShapeDumper::dump(const uno::Reference<drawing::XShapes>& xPageShapes, (void)xmlTextWriterEndDocument( xmlWriter ); xmlFreeTextWriter( xmlWriter ); - return OUString::fromUtf8(aString.makeStringAndClear()); + return OUString::fromUtf8(aString); } OUString XShapeDumper::dump(const uno::Reference<drawing::XShape>& xPageShapes, bool bDumpInteropProperties) @@ -1981,7 +1980,7 @@ OUString XShapeDumper::dump(const uno::Reference<drawing::XShape>& xPageShapes, (void)xmlTextWriterEndDocument( xmlWriter ); xmlFreeTextWriter( xmlWriter ); - return OUString::fromUtf8(aString.makeStringAndClear()); + return OUString::fromUtf8(aString); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/geometry/viewinformation2d.cxx b/drawinglayer/source/geometry/viewinformation2d.cxx index 125689939b21..296e34a0def2 100644 --- a/drawinglayer/source/geometry/viewinformation2d.cxx +++ b/drawinglayer/source/geometry/viewinformation2d.cxx @@ -25,7 +25,12 @@ #include <com/sun/star/drawing/XDrawPage.hpp> #include <com/sun/star/geometry/AffineMatrix2D.hpp> #include <com/sun/star/geometry/RealRectangle2D.hpp> -#include <com/sun/star/uno/Sequence.hxx> +#include <o3tl/temporary.hxx> +#include <officecfg/Office/Common.hxx> +#include <unotools/configmgr.hxx> + +#include <atomic> +#include <utility> using namespace com::sun::star; @@ -39,6 +44,14 @@ constexpr OUStringLiteral g_PropertyName_Viewport = u"Viewport"; constexpr OUStringLiteral g_PropertyName_Time = u"Time"; constexpr OUStringLiteral g_PropertyName_VisualizedPage = u"VisualizedPage"; constexpr OUStringLiteral g_PropertyName_ReducedDisplayQuality = u"ReducedDisplayQuality"; +constexpr OUStringLiteral g_PropertyName_UseAntiAliasing = u"UseAntiAliasing"; +constexpr OUStringLiteral g_PropertyName_PixelSnapHairline = u"PixelSnapHairline"; +} + +namespace +{ +bool bForwardsAreInitialized(false); +bool bForwardPixelSnapHairline(true); } class ImpViewInformation2D @@ -74,34 +87,69 @@ protected: // the point in time double mfViewTime; + // color to use for automatic color + Color maAutoColor; + + // a hint that the View that is being painted has an active TextEdit. This + // is important for handling of TextHierarchyEditPrimitive2D to suppress + // the text for objects in TextEdit - the text is visualized by the + // active EditEngine/Outliner overlay, so it would be double visualized + bool mbTextEditActive : 1; + + // processed view is an EditView + bool mbEditViewActive : 1; + + // allow to reduce DisplayQuality (e.g. sw 3d fallback renderer for interactions) bool mbReducedDisplayQuality : 1; -public: - ImpViewInformation2D(const basegfx::B2DHomMatrix& rObjectTransformation, - const basegfx::B2DHomMatrix& rViewTransformation, - const basegfx::B2DRange& rViewport, - const uno::Reference<drawing::XDrawPage>& rxDrawPage, double fViewTime, - bool bReducedDisplayQuality) - : maObjectTransformation(rObjectTransformation) - , maViewTransformation(rViewTransformation) - , maViewport(rViewport) - , mxVisualizedPage(rxDrawPage) - , mfViewTime(fViewTime) - , mbReducedDisplayQuality(bReducedDisplayQuality) - { - } + // determine if to use AntiAliasing on target pixel device + bool mbUseAntiAliasing : 1; + + // determine if to use PixelSnapHairline on target pixel device + bool mbPixelSnapHairline : 1; +public: ImpViewInformation2D() - : mfViewTime(0.0) + : maObjectTransformation() + , maViewTransformation() + , maObjectToViewTransformation() + , maInverseObjectToViewTransformation() + , maViewport() + , maDiscreteViewport() + , mxVisualizedPage() + , mfViewTime(0.0) + , maAutoColor(COL_AUTO) + , mbTextEditActive(false) + , mbEditViewActive(false) , mbReducedDisplayQuality(false) + , mbUseAntiAliasing(ViewInformation2D::getGlobalAntiAliasing()) + , mbPixelSnapHairline(mbUseAntiAliasing && bForwardPixelSnapHairline) { } const basegfx::B2DHomMatrix& getObjectTransformation() const { return maObjectTransformation; } + void setObjectTransformation(const basegfx::B2DHomMatrix& rNew) + { + maObjectTransformation = rNew; + maObjectToViewTransformation.identity(); + maInverseObjectToViewTransformation.identity(); + } const basegfx::B2DHomMatrix& getViewTransformation() const { return maViewTransformation; } + void setViewTransformation(const basegfx::B2DHomMatrix& rNew) + { + maViewTransformation = rNew; + maDiscreteViewport.reset(); + maObjectToViewTransformation.identity(); + maInverseObjectToViewTransformation.identity(); + } const basegfx::B2DRange& getViewport() const { return maViewport; } + void setViewport(const basegfx::B2DRange& rNew) + { + maViewport = rNew; + maDiscreteViewport.reset(); + } const basegfx::B2DRange& getDiscreteViewport() const { @@ -143,10 +191,37 @@ public: } double getViewTime() const { return mfViewTime; } + void setViewTime(double fNew) + { + if (fNew >= 0.0) + { + mfViewTime = fNew; + } + } const uno::Reference<drawing::XDrawPage>& getVisualizedPage() const { return mxVisualizedPage; } + void setVisualizedPage(const uno::Reference<drawing::XDrawPage>& rNew) + { + mxVisualizedPage = rNew; + } + + Color getAutoColor() const { return maAutoColor; } + void setAutoColor(Color aNew) { maAutoColor = aNew; } + + bool getTextEditActive() const { return mbTextEditActive; } + void setTextEditActive(bool bNew) { mbTextEditActive = bNew; } + + bool getEditViewActive() const { return mbEditViewActive; } + void setEditViewActive(bool bNew) { mbEditViewActive = bNew; } bool getReducedDisplayQuality() const { return mbReducedDisplayQuality; } + void setReducedDisplayQuality(bool bNew) { mbReducedDisplayQuality = bNew; } + + bool getUseAntiAliasing() const { return mbUseAntiAliasing; } + void setUseAntiAliasing(bool bNew) { mbUseAntiAliasing = bNew; } + + bool getPixelSnapHairline() const { return mbPixelSnapHairline; } + void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; } bool operator==(const ImpViewInformation2D& rCandidate) const { @@ -154,7 +229,12 @@ public: && maViewTransformation == rCandidate.maViewTransformation && maViewport == rCandidate.maViewport && mxVisualizedPage == rCandidate.mxVisualizedPage - && mfViewTime == rCandidate.mfViewTime); + && mfViewTime == rCandidate.mfViewTime && maAutoColor == rCandidate.maAutoColor + && mbTextEditActive == rCandidate.mbTextEditActive + && mbEditViewActive == rCandidate.mbEditViewActive + && mbReducedDisplayQuality == rCandidate.mbReducedDisplayQuality + && mbUseAntiAliasing == rCandidate.mbUseAntiAliasing + && mbPixelSnapHairline == rCandidate.mbPixelSnapHairline); } }; @@ -167,20 +247,21 @@ ViewInformation2D::ImplType& theGlobalDefault() } } -ViewInformation2D::ViewInformation2D(const basegfx::B2DHomMatrix& rObjectTransformation, - const basegfx::B2DHomMatrix& rViewTransformation, - const basegfx::B2DRange& rViewport, - const uno::Reference<drawing::XDrawPage>& rxDrawPage, - double fViewTime, bool bReducedDisplayQuality) - : mpViewInformation2D(ImpViewInformation2D(rObjectTransformation, rViewTransformation, - rViewport, rxDrawPage, fViewTime, - bReducedDisplayQuality)) -{ -} - ViewInformation2D::ViewInformation2D() : mpViewInformation2D(theGlobalDefault()) { + if (!bForwardsAreInitialized) + { + bForwardsAreInitialized = true; + if (!comphelper::IsFuzzing()) + { + bForwardPixelSnapHairline + = officecfg::Office::Common::Drawinglayer::SnapHorVerLinesToDiscrete::get(); + } + } + + setUseAntiAliasing(ViewInformation2D::getGlobalAntiAliasing()); + setPixelSnapHairline(bForwardPixelSnapHairline); } ViewInformation2D::ViewInformation2D(const ViewInformation2D&) = default; @@ -203,23 +284,53 @@ const basegfx::B2DHomMatrix& ViewInformation2D::getObjectTransformation() const return mpViewInformation2D->getObjectTransformation(); } +void ViewInformation2D::setObjectTransformation(const basegfx::B2DHomMatrix& rNew) +{ + if (std::as_const(mpViewInformation2D)->getObjectTransformation() != rNew) + mpViewInformation2D->setObjectTransformation(rNew); +} + const basegfx::B2DHomMatrix& ViewInformation2D::getViewTransformation() const { return mpViewInformation2D->getViewTransformation(); } +void ViewInformation2D::setViewTransformation(const basegfx::B2DHomMatrix& rNew) +{ + if (std::as_const(mpViewInformation2D)->getViewTransformation() != rNew) + mpViewInformation2D->setViewTransformation(rNew); +} + const basegfx::B2DRange& ViewInformation2D::getViewport() const { return mpViewInformation2D->getViewport(); } +void ViewInformation2D::setViewport(const basegfx::B2DRange& rNew) +{ + if (rNew != std::as_const(mpViewInformation2D)->getViewport()) + mpViewInformation2D->setViewport(rNew); +} + double ViewInformation2D::getViewTime() const { return mpViewInformation2D->getViewTime(); } +void ViewInformation2D::setViewTime(double fNew) +{ + if (fNew != std::as_const(mpViewInformation2D)->getViewTime()) + mpViewInformation2D->setViewTime(fNew); +} + const uno::Reference<drawing::XDrawPage>& ViewInformation2D::getVisualizedPage() const { return mpViewInformation2D->getVisualizedPage(); } +void ViewInformation2D::setVisualizedPage(const uno::Reference<drawing::XDrawPage>& rNew) +{ + if (rNew != std::as_const(mpViewInformation2D)->getVisualizedPage()) + mpViewInformation2D->setVisualizedPage(rNew); +} + const basegfx::B2DHomMatrix& ViewInformation2D::getObjectToViewTransformation() const { return mpViewInformation2D->getObjectToViewTransformation(); @@ -240,55 +351,155 @@ bool ViewInformation2D::getReducedDisplayQuality() const return mpViewInformation2D->getReducedDisplayQuality(); } +void ViewInformation2D::setReducedDisplayQuality(bool bNew) +{ + if (bNew != std::as_const(mpViewInformation2D)->getReducedDisplayQuality()) + mpViewInformation2D->setReducedDisplayQuality(bNew); +} + +bool ViewInformation2D::getUseAntiAliasing() const +{ + return mpViewInformation2D->getUseAntiAliasing(); +} + +void ViewInformation2D::setUseAntiAliasing(bool bNew) +{ + if (bNew != std::as_const(mpViewInformation2D)->getUseAntiAliasing()) + mpViewInformation2D->setUseAntiAliasing(bNew); +} + +Color ViewInformation2D::getAutoColor() const { return mpViewInformation2D->getAutoColor(); } + +void ViewInformation2D::setAutoColor(Color aNew) { mpViewInformation2D->setAutoColor(aNew); } + +bool ViewInformation2D::getTextEditActive() const +{ + return mpViewInformation2D->getTextEditActive(); +} + +void ViewInformation2D::setTextEditActive(bool bNew) +{ + mpViewInformation2D->setTextEditActive(bNew); +} + +bool ViewInformation2D::getEditViewActive() const +{ + return mpViewInformation2D->getEditViewActive(); +} + +void ViewInformation2D::setEditViewActive(bool bNew) +{ + mpViewInformation2D->setEditViewActive(bNew); +} + +bool ViewInformation2D::getPixelSnapHairline() const +{ + return mpViewInformation2D->getPixelSnapHairline(); +} + +void ViewInformation2D::setPixelSnapHairline(bool bNew) +{ + if (bNew != std::as_const(mpViewInformation2D)->getPixelSnapHairline()) + mpViewInformation2D->setPixelSnapHairline(bNew); +} + +static std::atomic<bool>& globalAntiAliasing() +{ + static std::atomic<bool> g_GlobalAntiAliasing + = comphelper::IsFuzzing() || officecfg::Office::Common::Drawinglayer::AntiAliasing::get(); + return g_GlobalAntiAliasing; +} + +/** + * Some code like to turn this stuff on and off during a drawing operation + * so it can "tunnel" information down through several layers, + * so we don't want to actually do a config write all the time. + */ +void ViewInformation2D::setGlobalAntiAliasing(bool bAntiAliasing, bool bTemporary) +{ + if (globalAntiAliasing().compare_exchange_strong(o3tl::temporary(!bAntiAliasing), bAntiAliasing) + && !bTemporary) + { + auto batch = comphelper::ConfigurationChanges::create(); + officecfg::Office::Common::Drawinglayer::AntiAliasing::set(bAntiAliasing, batch); + batch->commit(); + } +} +bool ViewInformation2D::getGlobalAntiAliasing() { return globalAntiAliasing(); } + +void ViewInformation2D::forwardPixelSnapHairline(bool bPixelSnapHairline) +{ + bForwardPixelSnapHairline = bPixelSnapHairline; +} + ViewInformation2D createViewInformation2D(const css::uno::Sequence<css::beans::PropertyValue>& rViewParameters) { if (!rViewParameters.hasElements()) return ViewInformation2D(); - bool bReducedDisplayQuality = false; - basegfx::B2DHomMatrix aObjectTransformation; - basegfx::B2DHomMatrix aViewTransformation; - basegfx::B2DRange aViewport; - double fViewTime = 0.0; - uno::Reference<drawing::XDrawPage> xVisualizedPage; + ViewInformation2D aRetval; for (auto const& rPropertyValue : rViewParameters) { if (rPropertyValue.Name == g_PropertyName_ReducedDisplayQuality) { - rPropertyValue.Value >>= bReducedDisplayQuality; + bool bNew(false); + rPropertyValue.Value >>= bNew; + aRetval.setReducedDisplayQuality(bNew); + } + else if (rPropertyValue.Name == g_PropertyName_PixelSnapHairline) + { + bool bNew( + true); //SvtOptionsDrawinglayer::IsAntiAliasing() && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete()); + rPropertyValue.Value >>= bNew; + aRetval.setPixelSnapHairline(bNew); + } + else if (rPropertyValue.Name == g_PropertyName_UseAntiAliasing) + { + bool bNew(true); //SvtOptionsDrawinglayer::IsAntiAliasing()); + rPropertyValue.Value >>= bNew; + aRetval.setUseAntiAliasing(bNew); } else if (rPropertyValue.Name == g_PropertyName_ObjectTransformation) { css::geometry::AffineMatrix2D aAffineMatrix2D; rPropertyValue.Value >>= aAffineMatrix2D; - basegfx::unotools::homMatrixFromAffineMatrix(aObjectTransformation, aAffineMatrix2D); + basegfx::B2DHomMatrix aTransformation; + basegfx::unotools::homMatrixFromAffineMatrix(aTransformation, aAffineMatrix2D); + aRetval.setObjectTransformation(aTransformation); } else if (rPropertyValue.Name == g_PropertyName_ViewTransformation) { css::geometry::AffineMatrix2D aAffineMatrix2D; rPropertyValue.Value >>= aAffineMatrix2D; - basegfx::unotools::homMatrixFromAffineMatrix(aViewTransformation, aAffineMatrix2D); + basegfx::B2DHomMatrix aTransformation; + basegfx::unotools::homMatrixFromAffineMatrix(aTransformation, aAffineMatrix2D); + aRetval.setViewTransformation(aTransformation); } else if (rPropertyValue.Name == g_PropertyName_Viewport) { css::geometry::RealRectangle2D aUnoViewport; rPropertyValue.Value >>= aUnoViewport; - aViewport = basegfx::unotools::b2DRectangleFromRealRectangle2D(aUnoViewport); + const basegfx::B2DRange aViewport( + basegfx::unotools::b2DRectangleFromRealRectangle2D(aUnoViewport)); + aRetval.setViewport(aViewport); } else if (rPropertyValue.Name == g_PropertyName_Time) { + double fViewTime(0.0); rPropertyValue.Value >>= fViewTime; + aRetval.setViewTime(fViewTime); } else if (rPropertyValue.Name == g_PropertyName_VisualizedPage) { + css::uno::Reference<css::drawing::XDrawPage> xVisualizedPage; rPropertyValue.Value >>= xVisualizedPage; + aRetval.setVisualizedPage(xVisualizedPage); } } - return ViewInformation2D(aObjectTransformation, aViewTransformation, aViewport, xVisualizedPage, - fViewTime, bReducedDisplayQuality); + return aRetval; } } // end of namespace drawinglayer::geometry diff --git a/drawinglayer/source/geometry/viewinformation3d.cxx b/drawinglayer/source/geometry/viewinformation3d.cxx index a2a444ab3543..bfe601f976e8 100644 --- a/drawinglayer/source/geometry/viewinformation3d.cxx +++ b/drawinglayer/source/geometry/viewinformation3d.cxx @@ -23,6 +23,7 @@ #include <com/sun/star/geometry/AffineMatrix3D.hpp> #include <basegfx/utils/canvastools.hxx> #include <com/sun/star/uno/Sequence.hxx> +#include <utility> using namespace com::sun::star; @@ -69,50 +70,15 @@ namespace drawinglayer::geometry uno::Sequence< beans::PropertyValue > mxExtendedInformation; // the local UNO API strings - static OUString getNamePropertyObjectTransformation() - { - return "ObjectTransformation"; - } - - static OUString getNamePropertyOrientation() - { - return "Orientation"; - } - - static OUString getNamePropertyProjection() - { - return "Projection"; - } - - static OUString getNamePropertyProjection_30() - { - return "Projection30"; - } - - static OUString getNamePropertyProjection_31() - { - return "Projection31"; - } - - static OUString getNamePropertyProjection_32() - { - return "Projection32"; - } - - static OUString getNamePropertyProjection_33() - { - return "Projection33"; - } - - static OUString getNamePropertyDeviceToView() - { - return "DeviceToView"; - } - - static OUString getNamePropertyTime() - { - return "Time"; - } + static constexpr OUString OBJECT_TRANSFORMATION = u"ObjectTransformation"_ustr; + static constexpr OUString ORIENTATION = u"Orientation"_ustr; + static constexpr OUString PROJECTION = u"Projection"_ustr; + static constexpr OUString PROJECTION30 = u"Projection30"_ustr; + static constexpr OUString PROJECTION31 = u"Projection31"_ustr; + static constexpr OUString PROJECTION32 = u"Projection32"_ustr; + static constexpr OUString PROJECTION33 = u"Projection33"_ustr; + static constexpr OUString DEVICE_TO_VIEW = u"DeviceToView"_ustr; + static constexpr OUString TIME = u"Time"_ustr; // a central PropertyValue parsing method to allow transportation of // all ViewParameters using UNO API @@ -132,19 +98,19 @@ namespace drawinglayer::geometry { const beans::PropertyValue& rProp = rViewParameters[a]; - if(rProp.Name == getNamePropertyObjectTransformation()) + if(rProp.Name == OBJECT_TRANSFORMATION) { css::geometry::AffineMatrix3D aAffineMatrix3D; rProp.Value >>= aAffineMatrix3D; maObjectTransformation = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); } - else if(rProp.Name == getNamePropertyOrientation()) + else if(rProp.Name == ORIENTATION) { css::geometry::AffineMatrix3D aAffineMatrix3D; rProp.Value >>= aAffineMatrix3D; maOrientation = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); } - else if(rProp.Name == getNamePropertyProjection()) + else if(rProp.Name == PROJECTION) { // projection may be defined using a frustum in which case the last line of // the 4x4 matrix is not (0,0,0,1). Since AffineMatrix3D does not support that, @@ -163,37 +129,37 @@ namespace drawinglayer::geometry maProjection.set(3, 2, f_32); maProjection.set(3, 3, f_33); } - else if(rProp.Name == getNamePropertyProjection_30()) + else if(rProp.Name == PROJECTION30) { double f_30(0.0); rProp.Value >>= f_30; maProjection.set(3, 0, f_30); } - else if(rProp.Name == getNamePropertyProjection_31()) + else if(rProp.Name == PROJECTION31) { double f_31(0.0); rProp.Value >>= f_31; maProjection.set(3, 1, f_31); } - else if(rProp.Name == getNamePropertyProjection_32()) + else if(rProp.Name == PROJECTION32) { double f_32(0.0); rProp.Value >>= f_32; maProjection.set(3, 2, f_32); } - else if(rProp.Name == getNamePropertyProjection_33()) + else if(rProp.Name == PROJECTION33) { double f_33(1.0); rProp.Value >>= f_33; maProjection.set(3, 3, f_33); } - else if(rProp.Name == getNamePropertyDeviceToView()) + else if(rProp.Name == DEVICE_TO_VIEW) { css::geometry::AffineMatrix3D aAffineMatrix3D; rProp.Value >>= aAffineMatrix3D; maDeviceToView = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); } - else if(rProp.Name == getNamePropertyTime()) + else if(rProp.Name == TIME) { rProp.Value >>= mfViewTime; } @@ -210,16 +176,16 @@ namespace drawinglayer::geometry public: ImpViewInformation3D( - const basegfx::B3DHomMatrix& rObjectTransformation, - const basegfx::B3DHomMatrix& rOrientation, - const basegfx::B3DHomMatrix& rProjection, - const basegfx::B3DHomMatrix& rDeviceToView, + basegfx::B3DHomMatrix aObjectTransformation, + basegfx::B3DHomMatrix aOrientation, + basegfx::B3DHomMatrix aProjection, + basegfx::B3DHomMatrix aDeviceToView, double fViewTime, const uno::Sequence< beans::PropertyValue >& rExtendedParameters) - : maObjectTransformation(rObjectTransformation), - maOrientation(rOrientation), - maProjection(rProjection), - maDeviceToView(rDeviceToView), + : maObjectTransformation(std::move(aObjectTransformation)), + maOrientation(std::move(aOrientation)), + maProjection(std::move(aProjection)), + maDeviceToView(std::move(aDeviceToView)), mfViewTime(fViewTime) { impInterpretPropertyValues(rExtendedParameters); diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx new file mode 100644 index 000000000000..4a09d0490ce9 --- /dev/null +++ b/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx @@ -0,0 +1,157 @@ +/* -*- 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 <sal/config.h> + +#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + +namespace +{ +class LocalCallbackTimer : public salhelper::Timer +{ +protected: + drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D* pCustomer; + +public: + explicit LocalCallbackTimer( + drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D& rCustomer) + : pCustomer(&rCustomer) + { + } + + void clearCallback() { pCustomer = nullptr; } + +protected: + virtual void SAL_CALL onShot() override; +}; + +void SAL_CALL LocalCallbackTimer::onShot() +{ + if (nullptr != pCustomer) + flushBufferedDecomposition(*pCustomer); +} +} + +namespace drawinglayer::primitive2d +{ +void flushBufferedDecomposition(BufferedDecompositionGroupPrimitive2D& rTarget) +{ + rTarget.acquire(); + rTarget.setBuffered2DDecomposition(Primitive2DContainer()); + rTarget.release(); +} + +const Primitive2DContainer& +BufferedDecompositionGroupPrimitive2D::getBuffered2DDecomposition() const +{ + if (0 != maCallbackSeconds && maCallbackTimer.is()) + { + // decomposition was used, touch/restart time + maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); + } + + return maBuffered2DDecomposition; +} + +void BufferedDecompositionGroupPrimitive2D::setBuffered2DDecomposition(Primitive2DContainer&& rNew) +{ + if (0 == maCallbackSeconds) + { + // no flush used, just set + maBuffered2DDecomposition = std::move(rNew); + return; + } + + if (maCallbackTimer.is()) + { + if (rNew.empty()) + { + // stop timer + maCallbackTimer->stop(); + } + else + { + // decomposition changed, touch + maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); + if (!maCallbackTimer->isTicking()) + maCallbackTimer->start(); + } + } + else if (!rNew.empty()) + { + // decomposition defined/set/changed, init & start timer + maCallbackTimer.set(new LocalCallbackTimer(*this)); + maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); + maCallbackTimer->start(); + } + + // tdf#158913 need to secure change when flush/multithreading is in use + std::lock_guard Guard(maCallbackLock); + maBuffered2DDecomposition = std::move(rNew); +} + +BufferedDecompositionGroupPrimitive2D::BufferedDecompositionGroupPrimitive2D( + Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + , maCallbackTimer() + , maCallbackLock() + , maCallbackSeconds(0) +{ +} + +BufferedDecompositionGroupPrimitive2D::~BufferedDecompositionGroupPrimitive2D() +{ + if (maCallbackTimer.is()) + { + // no more decomposition, end callback + static_cast<LocalCallbackTimer*>(maCallbackTimer.get())->clearCallback(); + maCallbackTimer->stop(); + } +} + +void BufferedDecompositionGroupPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + if (getBuffered2DDecomposition().empty()) + { + Primitive2DContainer aNewSequence; + create2DDecomposition(aNewSequence, rViewInformation); + const_cast<BufferedDecompositionGroupPrimitive2D*>(this)->setBuffered2DDecomposition( + std::move(aNewSequence)); + } + + if (0 == maCallbackSeconds) + { + // no flush/multithreading is in use, just call + rVisitor.visit(getBuffered2DDecomposition()); + return; + } + + // tdf#158913 need to secure 'visit' when flush/multithreading is in use, + // so that the local non-ref-Counted instance of the decomposition gets not + // manipulated (e.g. deleted) + std::lock_guard Guard(maCallbackLock); + rVisitor.visit(getBuffered2DDecomposition()); +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx index 6846a3bcdd26..ba8a4606cc83 100644 --- a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx @@ -20,30 +20,133 @@ #include <sal/config.h> #include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx> -#include <drawinglayer/primitive2d/Tools.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> -#include <basegfx/utils/canvastools.hxx> -#include <comphelper/sequence.hxx> + +namespace +{ +class LocalCallbackTimer : public salhelper::Timer +{ +protected: + drawinglayer::primitive2d::BufferedDecompositionPrimitive2D* pCustomer; + +public: + explicit LocalCallbackTimer( + drawinglayer::primitive2d::BufferedDecompositionPrimitive2D& rCustomer) + : pCustomer(&rCustomer) + { + } + + void clearCallback() { pCustomer = nullptr; } + +protected: + virtual void SAL_CALL onShot() override; +}; + +void SAL_CALL LocalCallbackTimer::onShot() +{ + if (nullptr != pCustomer) + flushBufferedDecomposition(*pCustomer); +} +} namespace drawinglayer::primitive2d { -BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D() {} +void flushBufferedDecomposition(BufferedDecompositionPrimitive2D& rTarget) +{ + rTarget.acquire(); + rTarget.setBuffered2DDecomposition(nullptr); + rTarget.release(); +} + +const Primitive2DReference& BufferedDecompositionPrimitive2D::getBuffered2DDecomposition() const +{ + if (0 != maCallbackSeconds && maCallbackTimer.is()) + { + // decomposition was used, touch/restart time + maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); + } + + return maBuffered2DDecomposition; +} + +void BufferedDecompositionPrimitive2D::setBuffered2DDecomposition(Primitive2DReference rNew) +{ + if (0 == maCallbackSeconds) + { + // no flush used, just set + maBuffered2DDecomposition = std::move(rNew); + return; + } + + if (maCallbackTimer.is()) + { + if (!rNew) + { + // stop timer + maCallbackTimer->stop(); + } + else + { + // decomposition changed, touch + maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); + if (!maCallbackTimer->isTicking()) + maCallbackTimer->start(); + } + } + else if (rNew) + { + // decomposition defined/set/changed, init & start timer + maCallbackTimer.set(new LocalCallbackTimer(*this)); + maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); + maCallbackTimer->start(); + } + + // tdf#158913 need to secure change when flush/multithreading is in use + std::lock_guard Guard(maCallbackLock); + maBuffered2DDecomposition = std::move(rNew); +} + +BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D() + : maBuffered2DDecomposition() + , maCallbackTimer() + , maCallbackLock() + , maCallbackSeconds(0) +{ +} + +BufferedDecompositionPrimitive2D::~BufferedDecompositionPrimitive2D() +{ + if (maCallbackTimer.is()) + { + // no more decomposition, end callback + static_cast<LocalCallbackTimer*>(maCallbackTimer.get())->clearCallback(); + maCallbackTimer->stop(); + } +} void BufferedDecompositionPrimitive2D::get2DDecomposition( Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard(m_aMutex); - - if (getBuffered2DDecomposition().empty()) + if (!getBuffered2DDecomposition()) { - Primitive2DContainer aNewSequence; - create2DDecomposition(aNewSequence, rViewInformation); + Primitive2DReference aNew = create2DDecomposition(rViewInformation); const_cast<BufferedDecompositionPrimitive2D*>(this)->setBuffered2DDecomposition( - std::move(aNewSequence)); + std::move(aNew)); + } + + if (0 == maCallbackSeconds) + { + // no flush/multithreading is in use, just call + rVisitor.visit(getBuffered2DDecomposition()); + return; } - rVisitor.append(getBuffered2DDecomposition()); + // tdf#158913 need to secure 'visit' when flush/multithreading is in use, + // so that the local non-ref-Counted instance of the decomposition gets not + // manipulated (e.g. deleted) + std::lock_guard Guard(maCallbackLock); + rVisitor.visit(getBuffered2DDecomposition()); } } // end of namespace drawinglayer::primitive2d diff --git a/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx new file mode 100644 index 000000000000..e1dcac421344 --- /dev/null +++ b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx @@ -0,0 +1,98 @@ +/* -*- 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 "GlowSoftEgdeShadowTools.hxx" +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapFilter.hxx> +#include <vcl/BitmapBasicMorphologyFilter.hxx> +#include <vcl/BitmapFilterStackBlur.hxx> + +namespace drawinglayer::primitive2d +{ +/* Returns 8-bit alpha mask created from passed mask. + + Negative fErodeDilateRadius values mean erode, positive - dilate. + nTransparency defines minimal transparency level. +*/ +AlphaMask ProcessAndBlurAlphaMask(const AlphaMask& rMask, double fErodeDilateRadius, + double fBlurRadius, sal_uInt8 nTransparency, bool bConvertTo1Bit) +{ + // Invert it to operate in the transparency domain. Trying to update this method to + // work in the alpha domain is fraught with hazards. + AlphaMask tmpMask = rMask; + tmpMask.Invert(); + + // Only completely white pixels on the initial mask must be considered for transparency. Any + // other color must be treated as black. This creates 1-bit B&W bitmap. + BitmapEx mask(bConvertTo1Bit ? tmpMask.GetBitmap().CreateMask(COL_WHITE) : tmpMask.GetBitmap()); + + // Scaling down increases performance without noticeable quality loss. Additionally, + // current blur implementation can only handle blur radius between 2 and 254. + Size aSize = mask.GetSizePixel(); + double fScale = 1.0; + while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000) + { + fScale /= 2; + fBlurRadius /= 2; + fErodeDilateRadius /= 2; + aSize /= 2; + } + + // BmpScaleFlag::NearestNeighbor is important for following color replacement + mask.Scale(fScale, fScale, BmpScaleFlag::NearestNeighbor); + + if (fErodeDilateRadius > 0) + BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius)); + else if (fErodeDilateRadius < 0) + BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 0xFF)); + + if (nTransparency) + { + const Color aTransparency(nTransparency, nTransparency, nTransparency); + mask.Replace(COL_BLACK, aTransparency); + } + + // We need 8-bit grey mask for blurring + mask.Convert(BmpConversion::N8BitGreys); + + // calculate blurry effect + BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius)); + + mask.Scale(rMask.GetSizePixel()); + + // And switch to the alpha domain. + mask.Invert(); + + return AlphaMask(mask.GetBitmap()); +} + +drawinglayer::geometry::ViewInformation2D +expandB2DRangeAtViewInformation2D(const drawinglayer::geometry::ViewInformation2D& rViewInfo, + double nAmount) +{ + drawinglayer::geometry::ViewInformation2D aRetval(rViewInfo); + basegfx::B2DRange viewport(rViewInfo.getViewport()); + viewport.grow(nAmount); + aRetval.setViewport(viewport); + return aRetval; +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.hxx b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.hxx new file mode 100644 index 000000000000..b6a62be8863e --- /dev/null +++ b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/alpha.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + +namespace drawinglayer::primitive2d +{ +/* Returns 8-bit alpha mask created from passed mask. + + Negative fErodeDilateRadius values mean erode, positive - dilate. + nTransparency defines minimal transparency level. +*/ +AlphaMask ProcessAndBlurAlphaMask(const AlphaMask& rMask, double fErodeDilateRadius, + double fBlurRadius, sal_uInt8 nTransparency, + bool bConvertTo1Bit = true); + +drawinglayer::geometry::ViewInformation2D +expandB2DRangeAtViewInformation2D(const drawinglayer::geometry::ViewInformation2D& rViewInfo, + double nAmount); + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx index b3aed429ba0c..6c7cf4bb365a 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx @@ -20,15 +20,17 @@ #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -PolyPolygonColorPrimitive2D::PolyPolygonColorPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBColor) - : maPolyPolygon(rPolyPolygon) +PolyPolygonColorPrimitive2D::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon aPolyPolygon, + const basegfx::BColor& rBColor) + : maPolyPolygon(std::move(aPolyPolygon)) , maBColor(rBColor) { } @@ -60,6 +62,54 @@ sal_uInt32 PolyPolygonColorPrimitive2D::getPrimitive2DID() const return PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D; } +FilledRectanglePrimitive2D::FilledRectanglePrimitive2D(const basegfx::B2DRange& rB2DRange, + const basegfx::BColor& rBColor) + : BasePrimitive2D() + , maB2DRange(rB2DRange) + , maBColor(rBColor) +{ +} + +bool FilledRectanglePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const FilledRectanglePrimitive2D& rCompare( + static_cast<const FilledRectanglePrimitive2D&>(rPrimitive)); + + return (getB2DRange() == rCompare.getB2DRange() && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange FilledRectanglePrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + return getB2DRange(); +} + +sal_uInt32 FilledRectanglePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D; +} + +void FilledRectanglePrimitive2D::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 PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), getBColor()) }; + rVisitor.visit(aSequence); +} + } // end drawinglayer::primitive2d namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx index 3e88b6c0ddb0..4fe3321e62f8 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx @@ -23,13 +23,14 @@ #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <rtl/ref.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonGradientPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonGradientPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getFillGradient().isDefault()) { @@ -40,25 +41,25 @@ void PolyPolygonGradientPrimitive2D::create2DDecomposition( Primitive2DContainer aSubSequence{ pNewGradient }; // create mask primitive - rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), std::move(aSubSequence))); + return new MaskPrimitive2D(getB2DPolyPolygon(), std::move(aSubSequence)); } + return nullptr; } PolyPolygonGradientPrimitive2D::PolyPolygonGradientPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, - const attribute::FillGradientAttribute& rFillGradient) + const basegfx::B2DPolyPolygon& rPolyPolygon, attribute::FillGradientAttribute aFillGradient) : maPolyPolygon(rPolyPolygon) , maDefinitionRange(rPolyPolygon.getB2DRange()) - , maFillGradient(rFillGradient) + , maFillGradient(std::move(aFillGradient)) { } PolyPolygonGradientPrimitive2D::PolyPolygonGradientPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, - const attribute::FillGradientAttribute& rFillGradient) - : maPolyPolygon(rPolyPolygon) + basegfx::B2DPolyPolygon aPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + attribute::FillGradientAttribute aFillGradient) + : maPolyPolygon(std::move(aPolyPolygon)) , maDefinitionRange(rDefinitionRange) - , maFillGradient(rFillGradient) + , maFillGradient(std::move(aFillGradient)) { } diff --git a/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx index a65d1d08a0f0..31a7acb03d7c 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx @@ -24,30 +24,31 @@ #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <utility> #include <vcl/graph.hxx> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonGraphicPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonGraphicPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (getFillGraphic().isDefault()) - return; + return nullptr; const Graphic& rGraphic = getFillGraphic().getGraphic(); const GraphicType aType(rGraphic.GetType()); // is there a bitmap or a metafile (do we have content)? if (GraphicType::Bitmap != aType && GraphicType::GdiMetafile != aType) - return; + return nullptr; const Size aPrefSize(rGraphic.GetPrefSize()); // does content have a size? if (!(aPrefSize.Width() && aPrefSize.Height())) - return; + return nullptr; // create SubSequence with FillGraphicPrimitive2D based on polygon range const basegfx::B2DRange aOutRange(getB2DPolyPolygon().getB2DRange()); @@ -92,13 +93,13 @@ void PolyPolygonGraphicPrimitive2D::create2DDecomposition( } // embed to mask primitive - rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), Primitive2DContainer{ xSubRef })); + return new MaskPrimitive2D(getB2DPolyPolygon(), Primitive2DContainer{ xSubRef }); } PolyPolygonGraphicPrimitive2D::PolyPolygonGraphicPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + basegfx::B2DPolyPolygon aPolyPolygon, const basegfx::B2DRange& rDefinitionRange, const attribute::FillGraphicAttribute& rFillGraphic) - : maPolyPolygon(rPolyPolygon) + : maPolyPolygon(std::move(aPolyPolygon)) , maDefinitionRange(rDefinitionRange) , maFillGraphic(rFillGraphic) { diff --git a/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx index 5bc291676dbc..88b15160e2c0 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx @@ -18,34 +18,35 @@ */ #include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonHairlinePrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonHairlinePrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); const sal_uInt32 nCount(aPolyPolygon.count()); - if (nCount) + Primitive2DContainer aContainer; + for (sal_uInt32 a(0); a < nCount; a++) { - for (sal_uInt32 a(0); a < nCount; a++) - { - rContainer.push_back( - new PolygonHairlinePrimitive2D(aPolyPolygon.getB2DPolygon(a), getBColor())); - } + aContainer.push_back( + new PolygonHairlinePrimitive2D(aPolyPolygon.getB2DPolygon(a), getBColor())); } + return new GroupPrimitive2D(std::move(aContainer)); } -PolyPolygonHairlinePrimitive2D::PolyPolygonHairlinePrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBColor) - : maPolyPolygon(rPolyPolygon) +PolyPolygonHairlinePrimitive2D::PolyPolygonHairlinePrimitive2D(basegfx::B2DPolyPolygon aPolyPolygon, + const basegfx::BColor& rBColor) + : maPolyPolygon(std::move(aPolyPolygon)) , maBColor(rBColor) { } diff --git a/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx index 7fc0502c8f43..2379e54e3de8 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx @@ -23,13 +23,14 @@ #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> #include <rtl/ref.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonHatchPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonHatchPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getFillHatch().isDefault()) { @@ -40,27 +41,29 @@ void PolyPolygonHatchPrimitive2D::create2DDecomposition( Primitive2DContainer aSubSequence{ pNewHatch }; // create mask primitive - rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), std::move(aSubSequence))); + return new MaskPrimitive2D(getB2DPolyPolygon(), std::move(aSubSequence)); } + return nullptr; } PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D( const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBackgroundColor, - const attribute::FillHatchAttribute& rFillHatch) + attribute::FillHatchAttribute rFillHatch) : maPolyPolygon(rPolyPolygon) , maDefinitionRange(rPolyPolygon.getB2DRange()) , maBackgroundColor(rBackgroundColor) - , maFillHatch(rFillHatch) + , maFillHatch(std::move(rFillHatch)) { } -PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, - const basegfx::BColor& rBackgroundColor, const attribute::FillHatchAttribute& rFillHatch) - : maPolyPolygon(rPolyPolygon) +PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D(basegfx::B2DPolyPolygon aPolyPolygon, + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rBackgroundColor, + attribute::FillHatchAttribute rFillHatch) + : maPolyPolygon(std::move(aPolyPolygon)) , maDefinitionRange(rDefinitionRange) , maBackgroundColor(rBackgroundColor) - , maFillHatch(rFillHatch) + , maFillHatch(std::move(rFillHatch)) { } diff --git a/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx index 2381ed8f866c..9c9e3b1ae81f 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx @@ -18,36 +18,38 @@ */ #include <drawinglayer/primitive2d/PolyPolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonMarkerPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonMarkerPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); const sal_uInt32 nCount(aPolyPolygon.count()); - if (nCount) + Primitive2DContainer aContainer; + for (sal_uInt32 a(0); a < nCount; a++) { - for (sal_uInt32 a(0); a < nCount; a++) - { - rContainer.push_back(new PolygonMarkerPrimitive2D(aPolyPolygon.getB2DPolygon(a), - getRGBColorA(), getRGBColorB(), - getDiscreteDashLength())); - } + aContainer.push_back(new PolygonMarkerPrimitive2D(aPolyPolygon.getB2DPolygon(a), + getRGBColorA(), getRGBColorB(), + getDiscreteDashLength())); } + return new GroupPrimitive2D(std::move(aContainer)); } -PolyPolygonMarkerPrimitive2D::PolyPolygonMarkerPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rRGBColorA, - const basegfx::BColor& rRGBColorB, double fDiscreteDashLength) - : maPolyPolygon(rPolyPolygon) +PolyPolygonMarkerPrimitive2D::PolyPolygonMarkerPrimitive2D(basegfx::B2DPolyPolygon aPolyPolygon, + const basegfx::BColor& rRGBColorA, + const basegfx::BColor& rRGBColorB, + double fDiscreteDashLength) + : maPolyPolygon(std::move(aPolyPolygon)) , maRGBColorA(rRGBColorA) , maRGBColorB(rRGBColorB) , mfDiscreteDashLength(fDiscreteDashLength) diff --git a/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx index 3ad4840bd5bd..a5595a4bcaeb 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx @@ -25,16 +25,17 @@ #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonSelectionPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonSelectionPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (getTransparence() >= 1.0 || !getB2DPolyPolygon().count()) - return; + return nullptr; Primitive2DContainer aRetval; @@ -66,13 +67,13 @@ void PolyPolygonSelectionPrimitive2D::create2DDecomposition( aRetval = Primitive2DContainer{ aTrans }; } - rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + return new GroupPrimitive2D(std::move(aRetval)); } PolyPolygonSelectionPrimitive2D::PolyPolygonSelectionPrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rColor, - double fTransparence, double fDiscreteGrow, bool bFill) - : maPolyPolygon(rPolyPolygon) + basegfx::B2DPolyPolygon aPolyPolygon, const basegfx::BColor& rColor, double fTransparence, + double fDiscreteGrow, bool bFill) + : maPolyPolygon(std::move(aPolyPolygon)) , maColor(rColor) , mfTransparence(fTransparence) , mfDiscreteGrow(fabs(fDiscreteGrow)) diff --git a/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx index 91093f37278d..9c32d8853d8d 100644 --- a/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx @@ -21,40 +21,41 @@ #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -void PolyPolygonStrokePrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference PolyPolygonStrokePrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); const sal_uInt32 nCount(aPolyPolygon.count()); - if (nCount) + Primitive2DContainer aContainer; + for (sal_uInt32 a(0); a < nCount; a++) { - for (sal_uInt32 a(0); a < nCount; a++) - { - rContainer.push_back(new PolygonStrokePrimitive2D( - aPolyPolygon.getB2DPolygon(a), getLineAttribute(), getStrokeAttribute())); - } + aContainer.push_back(new PolygonStrokePrimitive2D( + aPolyPolygon.getB2DPolygon(a), getLineAttribute(), getStrokeAttribute())); } + return new GroupPrimitive2D(std::move(aContainer)); } PolyPolygonStrokePrimitive2D::PolyPolygonStrokePrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const attribute::LineAttribute& rLineAttribute, - const attribute::StrokeAttribute& rStrokeAttribute) - : maPolyPolygon(rPolyPolygon) + basegfx::B2DPolyPolygon aPolyPolygon, const attribute::LineAttribute& rLineAttribute, + attribute::StrokeAttribute aStrokeAttribute) + : maPolyPolygon(std::move(aPolyPolygon)) , maLineAttribute(rLineAttribute) - , maStrokeAttribute(rStrokeAttribute) + , maStrokeAttribute(std::move(aStrokeAttribute)) { } PolyPolygonStrokePrimitive2D::PolyPolygonStrokePrimitive2D( - const basegfx::B2DPolyPolygon& rPolyPolygon, const attribute::LineAttribute& rLineAttribute) - : maPolyPolygon(rPolyPolygon) + basegfx::B2DPolyPolygon aPolyPolygon, const attribute::LineAttribute& rLineAttribute) + : maPolyPolygon(std::move(aPolyPolygon)) , maLineAttribute(rLineAttribute) { } diff --git a/drawinglayer/source/primitive2d/Primitive2DContainer.cxx b/drawinglayer/source/primitive2d/Primitive2DContainer.cxx index 3ae4a9b3e3c4..48b0c625e1ba 100644 --- a/drawinglayer/source/primitive2d/Primitive2DContainer.cxx +++ b/drawinglayer/source/primitive2d/Primitive2DContainer.cxx @@ -21,30 +21,44 @@ #include <drawinglayer/primitive2d/Primitive2DContainer.hxx> #include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> using namespace css; namespace drawinglayer::primitive2d { -Primitive2DContainer Primitive2DContainer::maybeInvert(bool bInvert) const +Primitive2DContainer::Primitive2DContainer( + const css::uno::Sequence<css::uno::Reference<css::graphic::XPrimitive2D>>& rSource) { - const sal_uInt32 nSize(size()); - Primitive2DContainer aRetval; - - aRetval.resize(nSize); + for (const auto& rPrimitive : rSource) + append(static_cast<const UnoPrimitive2D*>(rPrimitive.get())->getBasePrimitive2D()); +} +Primitive2DContainer::Primitive2DContainer( + const std::deque<css::uno::Reference<css::graphic::XPrimitive2D>>& rSource) +{ + for (const auto& rPrimitive : rSource) + append(static_cast<const UnoPrimitive2D*>(rPrimitive.get())->getBasePrimitive2D()); +} - for (sal_uInt32 a(0); a < nSize; a++) +css::uno::Sequence<css::uno::Reference<css::graphic::XPrimitive2D>> +Primitive2DContainer::toSequence() const +{ + css::uno::Sequence<css::uno::Reference<css::graphic::XPrimitive2D>> aVal(size()); + auto p = aVal.getArray(); + for (const auto& rPrimitive : *this) { - aRetval[bInvert ? nSize - 1 - a : a] = (*this)[a]; + *p = new UnoPrimitive2D(rPrimitive); + ++p; } + return aVal; +} - // all entries taken over to Uno References as owners. To avoid - // errors with users of this mechanism to delete pointers to BasePrimitive2D - // itself, clear given vector - const_cast<Primitive2DContainer&>(*this).clear(); - - return aRetval; +Primitive2DContainer Primitive2DContainer::maybeInvert(bool bInvert) +{ + if (bInvert) + std::reverse(begin(), end()); + return std::move(*this); } // get B2DRange from a given Primitive2DSequence @@ -113,9 +127,27 @@ void Primitive2DContainer::append(Primitive2DContainer&& rSource) std::make_move_iterator(rSource.end())); } -void Primitive2DContainer::append(const Primitive2DSequence& rSource) +UnoPrimitive2D::~UnoPrimitive2D() {} + +css::uno::Sequence<::css::uno::Reference<::css::graphic::XPrimitive2D>> + SAL_CALL UnoPrimitive2D::getDecomposition( + const css::uno::Sequence<css::beans::PropertyValue>& rViewParameters) +{ + std::unique_lock aGuard(m_aMutex); + return mxPrimitive->getDecomposition(rViewParameters).toSequence(); +} + +css::geometry::RealRectangle2D SAL_CALL +UnoPrimitive2D::getRange(const css::uno::Sequence<css::beans::PropertyValue>& rViewParameters) +{ + std::unique_lock aGuard(m_aMutex); + return mxPrimitive->getRange(rViewParameters); +} + +sal_Int64 SAL_CALL UnoPrimitive2D::estimateUsage() { - this->insert(this->end(), rSource.begin(), rSource.end()); + std::unique_lock aGuard(m_aMutex); + return mxPrimitive->estimateUsage(); } } // end of namespace drawinglayer::primitive2d diff --git a/drawinglayer/source/primitive2d/Tools.cxx b/drawinglayer/source/primitive2d/Tools.cxx index 7be666a2cbd0..e20c34a6c4f7 100644 --- a/drawinglayer/source/primitive2d/Tools.cxx +++ b/drawinglayer/source/primitive2d/Tools.cxx @@ -21,7 +21,6 @@ #include <drawinglayer/primitive2d/baseprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> -#include <basegfx/utils/canvastools.hxx> using namespace css; @@ -35,9 +34,7 @@ getB2DRangeFromPrimitive2DReference(const Primitive2DReference& rCandidate, if (!rCandidate) return basegfx::B2DRange(); - // get C++ implementation base - const BasePrimitive2D* pCandidate(static_cast<BasePrimitive2D*>(rCandidate.get())); - return pCandidate->getB2DRange(aViewInformation); + return rCandidate->getB2DRange(aViewInformation); } bool arePrimitive2DReferencesEqual(const Primitive2DReference& rxA, const Primitive2DReference& rxB) @@ -54,10 +51,28 @@ bool arePrimitive2DReferencesEqual(const Primitive2DReference& rxA, const Primit return true; } - const BasePrimitive2D* pA(static_cast<const BasePrimitive2D*>(rxA.get())); - const BasePrimitive2D* pB(static_cast<const BasePrimitive2D*>(rxB.get())); + return rxA->operator==(*rxB); +} + +bool arePrimitive2DReferencesEqual(const css::uno::Reference<css::graphic::XPrimitive2D>& rxA, + const css::uno::Reference<css::graphic::XPrimitive2D>& rxB) +{ + const bool bAIs(rxA.is()); + + if (bAIs != rxB.is()) + { + return false; + } + + if (!bAIs) + { + return true; + } + + auto pA = static_cast<const UnoPrimitive2D*>(rxA.get()); + auto pB = static_cast<const UnoPrimitive2D*>(rxB.get()); - return pA->operator==(*pB); + return (*pA->getBasePrimitive2D()) == (*pB->getBasePrimitive2D()); } OUString idToString(sal_uInt32 nId) @@ -210,6 +225,14 @@ OUString idToString(sal_uInt32 nId) return "GLOWPRIMITIVE"; case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: return "SOFTEDGEPRIMITIVE"; + case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D: + return "LINERECTANGLEPRIMITIVE"; + case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D: + return "FILLEDRECTANGLEPRIMITIVE"; + case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D: + return "SINGLELINEPRIMITIVE"; + case PRIMITIVE2D_ID_EXCLUSIVEEDITVIEWPRIMITIVE2D: + return "EXCLUSIVEEDITVIEWPRIMITIVE2D"; default: return OUString::number((nId >> 16) & 0xFF) + "|" + OUString::number(nId & 0xFF); } diff --git a/drawinglayer/source/primitive2d/animatedprimitive2d.cxx b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx index adb66dddb36c..87f524180fbe 100644 --- a/drawinglayer/source/primitive2d/animatedprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx @@ -76,8 +76,7 @@ namespace drawinglayer::primitive2d nIndex = nLen - 1; } - const Primitive2DReference xRef(getChildren()[nIndex], uno::UNO_SET_THROW); - rVisitor.append(xRef); + rVisitor.visit(getChildren()[nIndex]); } // provide unique ID @@ -132,9 +131,9 @@ namespace drawinglayer::primitive2d const sal_uInt32 nCount(rmMatrixStack.size()); maMatrixStack.reserve(nCount); - for(sal_uInt32 a(0); a < nCount; a++) + for(const auto& a : rmMatrixStack) { - maMatrixStack.emplace_back(rmMatrixStack[a]); + maMatrixStack.emplace_back(a); } } @@ -185,7 +184,7 @@ namespace drawinglayer::primitive2d // create new transform primitive reference, return new sequence Primitive2DReference xRef(new TransformPrimitive2D(aTargetTransform, Primitive2DContainer(getChildren()))); - rVisitor.append(xRef); + rVisitor.visit(xRef); } else { diff --git a/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx b/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx index 68f31cb4c67a..ea1b2a56942a 100644 --- a/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx @@ -21,6 +21,7 @@ #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> @@ -30,13 +31,31 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void BackgroundColorPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference BackgroundColorPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { - if(!rViewInformation.getViewport().isEmpty()) + // transparency invalid or completely transparent, done + if(getTransparency() < 0.0 || getTransparency() >= 1.0) + return nullptr; + + // no viewport, not visible, done + if(rViewInformation.getViewport().isEmpty()) + return nullptr; + + // create decompose geometry + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rViewInformation.getViewport())); + Primitive2DReference aDecompose(new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aOutline), getBColor())); + + if(getTransparency() != 0.0) { - const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rViewInformation.getViewport())); - rContainer.push_back(new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aOutline), getBColor())); + // if used, embed decompose geometry to unified transparency + Primitive2DContainer aContent { aDecompose }; + aDecompose = + new UnifiedTransparencePrimitive2D( + std::move(aContent), + getTransparency()); } + + return aDecompose; } BackgroundColorPrimitive2D::BackgroundColorPrimitive2D( @@ -67,22 +86,19 @@ namespace drawinglayer::primitive2d void BackgroundColorPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - - if(!getBuffered2DDecomposition().empty() && (maLastViewport != rViewInformation.getViewport())) + if(getBuffered2DDecomposition() && (maLastViewport != rViewInformation.getViewport())) { // conditions of last local decomposition have changed, delete - const_cast< BackgroundColorPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< BackgroundColorPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember ViewRange const_cast< BackgroundColorPrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/baseprimitive2d.cxx b/drawinglayer/source/primitive2d/baseprimitive2d.cxx index 71a13346beff..a2e0eaf6b6ba 100644 --- a/drawinglayer/source/primitive2d/baseprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/baseprimitive2d.cxx @@ -19,75 +19,14 @@ #include <sal/config.h> +#include <drawinglayer/primitive2d/Primitive2DContainer.hxx> #include <drawinglayer/primitive2d/baseprimitive2d.hxx> #include <drawinglayer/primitive2d/Tools.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <basegfx/utils/canvastools.hxx> -#include <comphelper/sequence.hxx> -#include <cppuhelper/queryinterface.hxx> using namespace css; -BasePrimitive2DImplBase::~BasePrimitive2DImplBase() {} - -css::uno::Any BasePrimitive2DImplBase::queryInterface(css::uno::Type const& rType) -{ - css::uno::Any aReturn = ::cppu::queryInterface( - rType, static_cast<uno::XWeak*>(this), static_cast<lang::XComponent*>(this), - static_cast<lang::XTypeProvider*>(this), static_cast<graphic::XPrimitive2D*>(this), - static_cast<util::XAccounting*>(this)); - if (aReturn.hasValue()) - return aReturn; - return OWeakObject::queryInterface(rType); -} - -void BasePrimitive2DImplBase::acquire() noexcept { OWeakObject::acquire(); } - -void BasePrimitive2DImplBase::release() noexcept -{ - if (osl_atomic_decrement(&m_refCount) != 0) - return; - - // ensure no other references are created, via the weak connection point, from now on - disposeWeakConnectionPoint(); - // restore reference count: - osl_atomic_increment(&m_refCount); - // if (! rBHelper.bDisposed) { - // try { - // dispose(); - // } - // catch (RuntimeException const& exc) { // don't break throw () - // SAL_WARN( "cppuhelper", exc ); - // } - // OSL_ASSERT( rBHelper.bDisposed ); - // } - OWeakObject::release(); -} - -void BasePrimitive2DImplBase::dispose() {} - -void BasePrimitive2DImplBase::addEventListener( - css::uno::Reference<css::lang::XEventListener> const&) -{ - assert(false); -} -void BasePrimitive2DImplBase::removeEventListener( - css::uno::Reference<css::lang::XEventListener> const&) -{ - assert(false); -} - -css::uno::Sequence<css::uno::Type> BasePrimitive2DImplBase::getTypes() -{ - static const css::uno::Sequence<uno::Type> aTypeList{ - cppu::UnoType<uno::XWeak>::get(), cppu::UnoType<lang::XComponent>::get(), - cppu::UnoType<lang::XTypeProvider>::get(), cppu::UnoType<graphic::XPrimitive2D>::get(), - cppu::UnoType<util::XAccounting>::get() - }; - - return aTypeList; -} - namespace drawinglayer::primitive2d { BasePrimitive2D::BasePrimitive2D() {} @@ -112,15 +51,15 @@ public: : mrViewInformation(rViewInformation) { } - virtual void append(const Primitive2DReference& r) override + virtual void visit(const Primitive2DReference& r) override { maRetval.expand(getB2DRangeFromPrimitive2DReference(r, mrViewInformation)); } - virtual void append(const Primitive2DContainer& r) override + virtual void visit(const Primitive2DContainer& r) override { maRetval.expand(r.getB2DRange(mrViewInformation)); } - virtual void append(Primitive2DContainer&& r) override + virtual void visit(Primitive2DContainer&& r) override { maRetval.expand(r.getB2DRange(mrViewInformation)); } @@ -141,23 +80,23 @@ void BasePrimitive2D::get2DDecomposition( { } -css::uno::Sequence<::css::uno::Reference<::css::graphic::XPrimitive2D>> SAL_CALL +Primitive2DContainer BasePrimitive2D::getDecomposition(const uno::Sequence<beans::PropertyValue>& rViewParameters) { const auto aViewInformation = geometry::createViewInformation2D(rViewParameters); Primitive2DContainer aContainer; get2DDecomposition(aContainer, aViewInformation); - return comphelper::containerToSequence(aContainer); + return aContainer; } -css::geometry::RealRectangle2D SAL_CALL +css::geometry::RealRectangle2D BasePrimitive2D::getRange(const uno::Sequence<beans::PropertyValue>& rViewParameters) { const auto aViewInformation = geometry::createViewInformation2D(rViewParameters); return basegfx::unotools::rectangle2DFromB2DRectangle(getB2DRange(aViewInformation)); } -sal_Int64 SAL_CALL BasePrimitive2D::estimateUsage() +sal_Int64 BasePrimitive2D::estimateUsage() { return 0; // for now ignore the objects themselves } diff --git a/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx b/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx index 0a06010d31e2..7dc58c3fe00a 100644 --- a/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx @@ -20,15 +20,15 @@ #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <com/sun/star/awt/XBitmap.hpp> +#include <utility> using namespace com::sun::star; namespace drawinglayer::primitive2d { -BitmapPrimitive2D::BitmapPrimitive2D(const css::uno::Reference<css::awt::XBitmap>& rXBitmap, - const basegfx::B2DHomMatrix& rTransform) - : maXBitmap(rXBitmap) - , maTransform(rTransform) +BitmapPrimitive2D::BitmapPrimitive2D(BitmapEx xXBitmap, basegfx::B2DHomMatrix aTransform) + : maBitmap(std::move(xXBitmap)) + , maTransform(std::move(aTransform)) { } @@ -38,7 +38,7 @@ bool BitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { const BitmapPrimitive2D& rCompare = static_cast<const BitmapPrimitive2D&>(rPrimitive); - return (getXBitmap() == rCompare.getXBitmap() && getTransform() == rCompare.getTransform()); + return (getBitmap() == rCompare.getBitmap() && getTransform() == rCompare.getTransform()); } return false; @@ -52,21 +52,13 @@ BitmapPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInforma return aRetval; } -sal_Int64 SAL_CALL BitmapPrimitive2D::estimateUsage() +sal_Int64 BitmapPrimitive2D::estimateUsage() { - if (!getXBitmap().is()) + if (getBitmap().IsEmpty()) { return 0; } - - uno::Reference<util::XAccounting> const xAcc(getXBitmap(), uno::UNO_QUERY); - - if (!xAcc.is()) - { - return 0; - } - - return xAcc->estimateUsage(); + return getBitmap().GetSizeBytes(); } // provide unique ID diff --git a/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx b/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx index f26f690fb337..79e34ec972ce 100644 --- a/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx @@ -21,11 +21,13 @@ #include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <basegfx/polygon/b2dpolygon.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <rtl/math.hxx> #include <algorithm> +#include <utility> namespace drawinglayer::primitive2d @@ -87,14 +89,14 @@ namespace drawinglayer::primitive2d { rContainer.push_back( new PolygonStrokePrimitive2D( - aPolygon, + std::move(aPolygon), rLineAttribute)); } else { rContainer.push_back( new PolygonStrokePrimitive2D( - aPolygon, + std::move(aPolygon), rLineAttribute, rStrokeAttribute)); } @@ -112,10 +114,10 @@ namespace drawinglayer::primitive2d return fRetval; } - void BorderLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference BorderLinePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if (getStart().equal(getEnd()) || getBorderLines().empty()) - return; + return nullptr; // get data and vectors basegfx::B2DVector aVector(getEnd() - getStart()); @@ -124,6 +126,7 @@ namespace drawinglayer::primitive2d const double fFullWidth(getFullWidth()); double fOffset(fFullWidth * -0.5); + Primitive2DContainer aContainer; for(const auto& candidate : maBorderLines) { const double fWidth(candidate.getLineAttribute().getWidth()); @@ -141,7 +144,7 @@ namespace drawinglayer::primitive2d // start and end extends lead to an edge perpendicular to the line, so we can just use // a PolygonStrokePrimitive2D for representation addPolygonStrokePrimitive2D( - rContainer, + aContainer, aStart - (aVector * candidate.getStartLeft()), aEnd + (aVector * candidate.getEndLeft()), candidate.getLineAttribute(), @@ -162,7 +165,7 @@ namespace drawinglayer::primitive2d aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); - rContainer.push_back( + aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(aPolygon), candidate.getLineAttribute().getColor())); @@ -197,7 +200,7 @@ namespace drawinglayer::primitive2d aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); } - rContainer.push_back( + aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(aPolygon), candidate.getLineAttribute().getColor())); @@ -226,7 +229,7 @@ namespace drawinglayer::primitive2d aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin)); aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin)); - rContainer.push_back( + aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(aPolygon), candidate.getLineAttribute().getColor())); @@ -236,7 +239,7 @@ namespace drawinglayer::primitive2d } addPolygonStrokePrimitive2D( - rContainer, + aContainer, aStrokeStart, aStrokeEnd, candidate.getLineAttribute(), @@ -247,6 +250,7 @@ namespace drawinglayer::primitive2d fOffset += fWidth; } + return new GroupPrimitive2D(std::move(aContainer)); } bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D& rViewInformation) const @@ -266,38 +270,25 @@ namespace drawinglayer::primitive2d const basegfx::B2DPoint& rStart, const basegfx::B2DPoint& rEnd, std::vector< BorderLine >&& rBorderLines, - const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute) + drawinglayer::attribute::StrokeAttribute aStrokeAttribute) : maStart(rStart), maEnd(rEnd), maBorderLines(std::move(rBorderLines)), - maStrokeAttribute(rStrokeAttribute) + maStrokeAttribute(std::move(aStrokeAttribute)) { } bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { - if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) - { - const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive); + if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + return false; - if (getStart() == rCompare.getStart() - && getEnd() == rCompare.getEnd() - && getStrokeAttribute() == rCompare.getStrokeAttribute()) - { - if (getBorderLines().size() == rCompare.getBorderLines().size()) - { - for (size_t a(0); a < getBorderLines().size(); a++) - { - if (!(getBorderLines()[a] == rCompare.getBorderLines()[a])) - { - return false; - } - } - } - } - } + const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive); - return false; + return (getStart() == rCompare.getStart() + && getEnd() == rCompare.getEnd() + && getStrokeAttribute() == rCompare.getStrokeAttribute() + && getBorderLines() == rCompare.getBorderLines()); } // provide unique ID @@ -340,7 +331,7 @@ namespace drawinglayer::primitive2d // direction has to be equal -> cross product == 0.0 const basegfx::B2DVector aVT(pCandidateA->getEnd() - pCandidateA->getStart()); const basegfx::B2DVector aVC(pCandidateB->getEnd() - pCandidateB->getStart()); - if(!rtl::math::approxEqual(0.0, aVC.cross(aVT))) + if(aVC.cross(aVT) != 0) { return Primitive2DReference(); } @@ -417,12 +408,12 @@ namespace drawinglayer::primitive2d } } - return Primitive2DReference( + return new BorderLinePrimitive2D( pCandidateA->getStart(), pCandidateB->getEnd(), std::move(aMergedBorderLines), - pCandidateA->getStrokeAttribute())); + pCandidateA->getStrokeAttribute()); } } // end of namespace diff --git a/drawinglayer/source/primitive2d/controlprimitive2d.cxx b/drawinglayer/source/primitive2d/controlprimitive2d.cxx index 6eebc11a0fa5..730e522dc6f6 100644 --- a/drawinglayer/source/primitive2d/controlprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/controlprimitive2d.cxx @@ -19,25 +19,28 @@ #include <drawinglayer/primitive2d/controlprimitive2d.hxx> #include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <comphelper/processfactory.hxx> #include <com/sun/star/awt/XControl.hpp> #include <com/sun/star/uno/XComponentContext.hpp> #include <drawinglayer/geometry/viewinformation2d.hxx> +#include <utility> +#include <rtl/ustrbuf.hxx> #include <vcl/virdev.hxx> #include <vcl/svapp.hxx> #include <com/sun/star/awt/PosSize.hpp> #include <vcl/bitmapex.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> -#include <tools/diagnose_ex.h> +#include <comphelper/diagnose_ex.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dpolygon.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <svtools/optionsdrawinglayer.hxx> #include <vcl/window.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> #include <toolkit/helper/vclunohelper.hxx> +#include <officecfg/Office/Common.hxx> using namespace com::sun::star; @@ -96,7 +99,7 @@ namespace drawinglayer::primitive2d basegfx::B2DVector aDiscreteSize(rViewInformation.getObjectToViewTransformation() * aScale); // limit to a maximum square size, e.g. 300x150 pixels (45000) - const double fDiscreteMax(SvtOptionsDrawinglayer::GetQuadraticFormControlRenderLimit()); + const double fDiscreteMax(officecfg::Office::Common::Drawinglayer::QuadraticFormControlRenderLimit::get()); const double fDiscreteQuadratic(aDiscreteSize.getX() * aDiscreteSize.getY()); const bool bScaleUsed(fDiscreteQuadratic > fDiscreteMax); double fFactor(1.0); @@ -144,16 +147,11 @@ namespace drawinglayer::primitive2d if(xControl.is()) { uno::Reference<awt::XWindowPeer> xWindowPeer(xControl->getPeer()); - - VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindowPeer); - if (pWindow) + if (xWindowPeer) { - pWindow = pWindow->GetParent(); - - if(pWindow && MapUnit::Map100thMM == pWindow->GetMapMode().GetMapUnit()) - { - bUserIs100thmm = true; - } + uno::Reference<awt::XVclWindowPeer> xPeerProps(xWindowPeer, uno::UNO_QUERY_THROW); + uno::Any aAny = xPeerProps->getProperty("ParentIs100thmm"); // see VCLXWindow::getProperty + aAny >>= bUserIs100thmm; } } @@ -197,7 +195,7 @@ namespace drawinglayer::primitive2d // create primitive xRetval = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(aContent), + aContent, aBitmapTransform); } catch( const uno::Exception& ) @@ -217,16 +215,16 @@ namespace drawinglayer::primitive2d // create a gray placeholder hairline polygon in object size basegfx::B2DRange aObjectRange(0.0, 0.0, 1.0, 1.0); aObjectRange.transform(getTransform()); - const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aObjectRange)); + basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aObjectRange)); const basegfx::BColor aGrayTone(0xc0 / 255.0, 0xc0 / 255.0, 0xc0 / 255.0); // The replacement object may also get a text like 'empty group' here later - Primitive2DReference xRetval(new PolygonHairlinePrimitive2D(aOutline, aGrayTone)); + Primitive2DReference xRetval(new PolygonHairlinePrimitive2D(std::move(aOutline), aGrayTone)); return xRetval; } - void ControlPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference ControlPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { // try to create a bitmap decomposition. If that fails for some reason, // at least create a replacement decomposition. @@ -237,25 +235,28 @@ namespace drawinglayer::primitive2d xReference = createPlaceholderDecomposition(); } - rContainer.push_back(xReference); - } - - ControlPrimitive2D::ControlPrimitive2D( - const basegfx::B2DHomMatrix& rTransform, - const uno::Reference< awt::XControlModel >& rxControlModel) - : maTransform(rTransform), - mxControlModel(rxControlModel) - { + return xReference; } ControlPrimitive2D::ControlPrimitive2D( - const basegfx::B2DHomMatrix& rTransform, - const uno::Reference< awt::XControlModel >& rxControlModel, - const uno::Reference< awt::XControl >& rxXControl) - : maTransform(rTransform), - mxControlModel(rxControlModel), - mxXControl(rxXControl) + basegfx::B2DHomMatrix aTransform, + uno::Reference< awt::XControlModel > xControlModel, + uno::Reference<awt::XControl> xXControl, + ::std::u16string_view const rTitle, + ::std::u16string_view const rDescription, + void const*const pAnchorKey) + : maTransform(std::move(aTransform)), + mxControlModel(std::move(xControlModel)), + mxXControl(std::move(xXControl)) + , m_pAnchorStructureElementKey(pAnchorKey) { + ::rtl::OUStringBuffer buf(rTitle); + if (!rTitle.empty() && !rDescription.empty()) + { + buf.append(" - "); + } + buf.append(rDescription); + m_AltText = buf.makeStringAndClear(); } const uno::Reference< awt::XControl >& ControlPrimitive2D::getXControl() const @@ -271,38 +272,37 @@ namespace drawinglayer::primitive2d bool ControlPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { // use base class compare operator - if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) - { - const ControlPrimitive2D& rCompare = static_cast<const ControlPrimitive2D&>(rPrimitive); + if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + return false; - if(getTransform() == rCompare.getTransform()) - { - // check if ControlModel references both are/are not - bool bRetval(getControlModel().is() == rCompare.getControlModel().is()); + const ControlPrimitive2D& rCompare = static_cast<const ControlPrimitive2D&>(rPrimitive); - if(bRetval && getControlModel().is()) - { - // both exist, check for equality - bRetval = (getControlModel() == rCompare.getControlModel()); - } + if(getTransform() != rCompare.getTransform()) + return false; - if(bRetval) - { - // check if XControl references both are/are not - bRetval = (getXControl().is() == rCompare.getXControl().is()); - } + // check if ControlModel references both are/are not + if (getControlModel().is() != rCompare.getControlModel().is()) + return false; - if(bRetval && getXControl().is()) - { - // both exist, check for equality - bRetval = (getXControl() == rCompare.getXControl()); - } + if(getControlModel().is()) + { + // both exist, check for equality + if (getControlModel() != rCompare.getControlModel()) + return false; + } - return bRetval; - } + // check if XControl references both are/are not + if (getXControl().is() != rCompare.getXControl().is()) + return false; + + if(getXControl().is()) + { + // both exist, check for equality + if (getXControl() != rCompare.getXControl()) + return false; } - return false; + return true; } basegfx::B2DRange ControlPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const @@ -317,26 +317,24 @@ namespace drawinglayer::primitive2d { // this primitive is view-dependent related to the scaling. If scaling has changed, // destroy existing decomposition. To detect change, use size of unit size in view coordinates - std::unique_lock aGuard( m_aMutex ); const basegfx::B2DVector aNewScaling(rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); - if(!getBuffered2DDecomposition().empty()) + if(getBuffered2DDecomposition()) { if(!maLastViewScaling.equal(aNewScaling)) { // conditions of last local decomposition have changed, delete - const_cast< ControlPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< ControlPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember ViewTransformation const_cast< ControlPrimitive2D* >(this)->maLastViewScaling = aNewScaling; } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/cropprimitive2d.cxx b/drawinglayer/source/primitive2d/cropprimitive2d.cxx index 06a7e2726f04..da28d9e41663 100644 --- a/drawinglayer/source/primitive2d/cropprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/cropprimitive2d.cxx @@ -24,6 +24,7 @@ #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <utility> using namespace com::sun::star; @@ -33,13 +34,13 @@ namespace drawinglayer::primitive2d { CropPrimitive2D::CropPrimitive2D( Primitive2DContainer&& aChildren, - const basegfx::B2DHomMatrix& rTransformation, + basegfx::B2DHomMatrix aTransformation, double fCropLeft, double fCropTop, double fCropRight, double fCropBottom) : GroupPrimitive2D(std::move(aChildren)), - maTransformation(rTransformation), + maTransformation(std::move(aTransformation)), mfCropLeft(fCropLeft), mfCropTop(fCropTop), mfCropRight(fCropRight), @@ -126,7 +127,7 @@ namespace drawinglayer::primitive2d { // the new range is completely inside the old range (unit range), // so no masking is needed - rVisitor.append(xTransformPrimitive); + rVisitor.visit(xTransformPrimitive); } else { @@ -137,10 +138,10 @@ namespace drawinglayer::primitive2d // create maskPrimitive with aMaskPolyPolygon and aMaskContentVector const Primitive2DReference xMask( new MaskPrimitive2D( - aMaskPolyPolygon, + std::move(aMaskPolyPolygon), Primitive2DContainer { xTransformPrimitive })); - rVisitor.append(xMask); + rVisitor.visit(xMask); } } diff --git a/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx b/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx index 8a86f83204bd..66dcfcb403e1 100644 --- a/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx @@ -25,14 +25,14 @@ namespace drawinglayer::primitive2d { - void DiscreteBitmapPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference DiscreteBitmapPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { // use getViewTransformation() and getObjectTransformation() from // ObjectAndViewTransformationDependentPrimitive2D to create a BitmapPrimitive2D // with the correct mapping if(getBitmapEx().IsEmpty()) - return; + return nullptr; // get discrete size const Size& rSizePixel = getBitmapEx().GetSizePixel(); @@ -64,10 +64,10 @@ namespace drawinglayer::primitive2d aObjectTransform = aInverseObjectTransformation * aObjectTransform; // create BitmapPrimitive2D with now object-local coordinate data - rContainer.push_back( + return new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), - aObjectTransform)); + getBitmapEx(), + aObjectTransform); } DiscreteBitmapPrimitive2D::DiscreteBitmapPrimitive2D( diff --git a/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx b/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx index 5c562471a786..1ff96ce60886 100644 --- a/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx @@ -23,6 +23,7 @@ #include <basegfx/matrix/b2dhommatrixtools.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> +#include <osl/diagnose.h> #include <toolkit/helper/vclunohelper.hxx> @@ -31,6 +32,7 @@ namespace drawinglayer::primitive2d DiscreteShadow::DiscreteShadow(const BitmapEx& rBitmapEx) : maBitmapEx(rBitmapEx) { + maBitmapEx.Invert(); // convert transparency to alpha const Size& rBitmapSize = getBitmapEx().GetSizePixel(); if(rBitmapSize.Width() != rBitmapSize.Height() || rBitmapSize.Width() < 7) @@ -149,12 +151,12 @@ namespace drawinglayer::primitive2d namespace drawinglayer::primitive2d { - void DiscreteShadowPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference DiscreteShadowPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { Primitive2DContainer xRetval; if(getDiscreteShadow().getBitmapEx().IsEmpty()) - return; + return nullptr; const sal_Int32 nQuarter((getDiscreteShadow().getBitmapEx().GetSizePixel().Width() - 3) >> 2); const basegfx::B2DVector aScale(getTransform() * basegfx::B2DVector(1.0, 1.0)); @@ -168,90 +170,90 @@ namespace drawinglayer::primitive2d xRetval.resize(8); // TopLeft - xRetval[0] = Primitive2DReference( + xRetval[0] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTopLeft()), + getDiscreteShadow().getTopLeft(), basegfx::utils::createScaleTranslateB2DHomMatrix( fBigLenX, fBigLenY, -fBorderX, - -fBorderY))); + -fBorderY)); // Top - xRetval[1] = Primitive2DReference( + xRetval[1] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTop()), + getDiscreteShadow().getTop(), basegfx::utils::createScaleTranslateB2DHomMatrix( 1.0 - (2.0 * (fBorderX + fSingleX)) + fSingleX, fBorderY, fBorderX + fSingleX, - -fBorderY))); + -fBorderY)); // TopRight - xRetval[2] = Primitive2DReference( + xRetval[2] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTopRight()), + getDiscreteShadow().getTopRight(), basegfx::utils::createScaleTranslateB2DHomMatrix( fBigLenX, fBigLenY, 1.0 - fBorderX, - -fBorderY))); + -fBorderY)); // Right - xRetval[3] = Primitive2DReference( + xRetval[3] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getRight()), + getDiscreteShadow().getRight(), basegfx::utils::createScaleTranslateB2DHomMatrix( fBorderX, 1.0 - (2.0 * (fBorderY + fSingleY)) + fSingleY, 1.0 + fSingleX, - fBorderY + fSingleY))); + fBorderY + fSingleY)); // BottomRight - xRetval[4] = Primitive2DReference( + xRetval[4] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottomRight()), + getDiscreteShadow().getBottomRight(), basegfx::utils::createScaleTranslateB2DHomMatrix( fBigLenX, fBigLenY, 1.0 - (fBorderX + fSingleX) + fSingleX, - 1.0 - (fBorderY + fSingleY) + fSingleY))); + 1.0 - (fBorderY + fSingleY) + fSingleY)); // Bottom - xRetval[5] = Primitive2DReference( + xRetval[5] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottom()), + getDiscreteShadow().getBottom(), basegfx::utils::createScaleTranslateB2DHomMatrix( 1.0 - (2.0 * (fBorderX + fSingleX)) + fSingleX, fBorderY, fBorderX + fSingleX, - 1.0 + fSingleY))); + 1.0 + fSingleY)); // BottomLeft - xRetval[6] = Primitive2DReference( + xRetval[6] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottomLeft()), + getDiscreteShadow().getBottomLeft(), basegfx::utils::createScaleTranslateB2DHomMatrix( fBigLenX, fBigLenY, -fBorderX, - 1.0 - fBorderY))); + 1.0 - fBorderY)); // Left - xRetval[7] = Primitive2DReference( + xRetval[7] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getLeft()), + getDiscreteShadow().getLeft(), basegfx::utils::createScaleTranslateB2DHomMatrix( fBorderX, 1.0 - (2.0 * (fBorderY + fSingleY)) + fSingleY, -fBorderX, - fBorderY + fSingleY))); + fBorderY + fSingleY)); // put all in object transformation to get to target positions - rContainer.push_back( + return new TransformPrimitive2D( getTransform(), - std::move(xRetval))); + std::move(xRetval)); } DiscreteShadowPrimitive2D::DiscreteShadowPrimitive2D( diff --git a/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx b/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx index 0b217adf6b49..6c7ebd113a43 100644 --- a/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx @@ -21,11 +21,12 @@ #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/color/bcolor.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/geometry/viewinformation3d.hxx> #include <processor3d/shadow3dextractor.hxx> +#include <utility> using namespace com::sun::star; @@ -35,8 +36,6 @@ namespace drawinglayer::primitive2d { bool Embedded3DPrimitive2D::impGetShadow3D() const { - std::unique_lock aGuard( m_aMutex ); - // create on demand if(!mbShadow3DChecked && !getChildren3D().empty()) { @@ -60,25 +59,25 @@ namespace drawinglayer::primitive2d return !maShadowPrimitives.empty(); } - void Embedded3DPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference Embedded3DPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { // use info to create a yellow 2d rectangle, similar to empty 3d scenes and/or groups const basegfx::B2DRange aLocal2DRange(getB2DRange(rViewInformation)); - const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aLocal2DRange)); + basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aLocal2DRange)); const basegfx::BColor aYellow(1.0, 1.0, 0.0); - rContainer.push_back(new PolygonHairlinePrimitive2D(aOutline, aYellow)); + return new PolygonHairlinePrimitive2D(std::move(aOutline), aYellow); } Embedded3DPrimitive2D::Embedded3DPrimitive2D( - const primitive3d::Primitive3DContainer& rxChildren3D, - const basegfx::B2DHomMatrix& rObjectTransformation, - const geometry::ViewInformation3D& rViewInformation3D, + primitive3d::Primitive3DContainer aChildren3D, + basegfx::B2DHomMatrix aObjectTransformation, + geometry::ViewInformation3D aViewInformation3D, const basegfx::B3DVector& rLightNormal, double fShadowSlant, const basegfx::B3DRange& rScene3DRange) - : mxChildren3D(rxChildren3D), - maObjectTransformation(rObjectTransformation), - maViewInformation3D(rViewInformation3D), + : mxChildren3D(std::move(aChildren3D)), + maObjectTransformation(std::move(aObjectTransformation)), + maViewInformation3D(std::move(aViewInformation3D)), maLightNormal(rLightNormal), mfShadowSlant(fShadowSlant), maScene3DRange(rScene3DRange), diff --git a/drawinglayer/source/primitive2d/epsprimitive2d.cxx b/drawinglayer/source/primitive2d/epsprimitive2d.cxx index 9e876dad5376..760d5d764c41 100644 --- a/drawinglayer/source/primitive2d/epsprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/epsprimitive2d.cxx @@ -20,10 +20,11 @@ #include <drawinglayer/primitive2d/epsprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <utility> namespace drawinglayer::primitive2d { - void EpsPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference EpsPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { const GDIMetaFile& rSubstituteContent = getMetaFile(); @@ -33,19 +34,20 @@ namespace drawinglayer::primitive2d // To really use the Eps data, a renderer has to know and interpret this primitive // directly. - rContainer.push_back( + return new MetafilePrimitive2D( getEpsTransform(), - rSubstituteContent)); + rSubstituteContent); } + return nullptr; } EpsPrimitive2D::EpsPrimitive2D( - const basegfx::B2DHomMatrix& rEpsTransform, - const GfxLink& rGfxLink, + basegfx::B2DHomMatrix aEpsTransform, + GfxLink aGfxLink, const GDIMetaFile& rMetaFile) - : maEpsTransform(rEpsTransform), - maGfxLink(rGfxLink), + : maEpsTransform(std::move(aEpsTransform)), + maGfxLink(std::move(aGfxLink)), maMetaFile(rMetaFile) { } diff --git a/drawinglayer/source/primitive2d/exclusiveeditviewprimitive2d.cxx b/drawinglayer/source/primitive2d/exclusiveeditviewprimitive2d.cxx new file mode 100644 index 000000000000..a510b97b5b02 --- /dev/null +++ b/drawinglayer/source/primitive2d/exclusiveeditviewprimitive2d.cxx @@ -0,0 +1,55 @@ +/* -*- 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/exclusiveeditviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +ExclusiveEditViewPrimitive2D::ExclusiveEditViewPrimitive2D(Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) +{ +} + +basegfx::B2DRange +ExclusiveEditViewPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + return getChildren().getB2DRange(rViewInformation); +} + +void ExclusiveEditViewPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + // check if EditView is visualized. if yes, use content by calling parent class. if no, suppress it + if (rViewInformation.getEditViewActive()) + GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); +} + +// provide unique ID +sal_uInt32 ExclusiveEditViewPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_EXCLUSIVEEDITVIEWPRIMITIVE2D; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx index 7baf93bf5292..1166c8950881 100644 --- a/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx @@ -23,6 +23,9 @@ #include <texture/texture.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> +#include <utility> +#include <algorithm> using namespace com::sun::star; @@ -30,246 +33,232 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void FillGradientPrimitive2D::generateMatricesAndColors( - std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) const + // Get the OuterColor. Take into account that for css::awt::GradientStyle_AXIAL + // this is the last one due to inverted gradient usage (see constructor there) + basegfx::BColor FillGradientPrimitive2D::getOuterColor() const { - rEntries.clear(); - - // make sure steps is not too high/low - const basegfx::BColor aStart(getFillGradient().getStartColor()); - const basegfx::BColor aEnd(getFillGradient().getEndColor()); - const sal_uInt32 nMaxSteps(sal_uInt32((aStart.getMaximumDistance(aEnd) * 127.5) + 0.5)); - sal_uInt32 nSteps(getFillGradient().getSteps()); + if (getFillGradient().getColorStops().empty()) + return basegfx::BColor(); - if(nSteps == 0) - { - nSteps = nMaxSteps; - } + if (css::awt::GradientStyle_AXIAL == getFillGradient().getStyle()) + return getFillGradient().getColorStops().back().getStopColor(); - if(nSteps < 2) - { - nSteps = 2; - } + return getFillGradient().getColorStops().front().getStopColor(); + } - if(nSteps > nMaxSteps) + // Get the needed UnitPolygon dependent on the GradientStyle + basegfx::B2DPolygon FillGradientPrimitive2D::getUnitPolygon() const + { + if (css::awt::GradientStyle_RADIAL == getFillGradient().getStyle() + || css::awt::GradientStyle_ELLIPTICAL == getFillGradient().getStyle()) { - nSteps = nMaxSteps; + return basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), 1.0); } - nSteps = std::max(sal_uInt32(1), nSteps); + return basegfx::utils::createPolygonFromRect(basegfx::B2DRange(-1.0, -1.0, 1.0, 1.0)); + } + void FillGradientPrimitive2D::generateMatricesAndColors( + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) const + { switch(getFillGradient().getStyle()) { - case attribute::GradientStyle::Linear: + default: // GradientStyle_MAKE_FIXED_SIZE + case css::awt::GradientStyle_LINEAR: { texture::GeoTexSvxGradientLinear aGradient( getDefinitionRange(), getOutputRange(), - aStart, - aEnd, - nSteps, + getFillGradient().getSteps(), + getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getAngle()); - aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + aGradient.appendTransformationsAndColors(aCallback); break; } - case attribute::GradientStyle::Axial: + case css::awt::GradientStyle_AXIAL: { texture::GeoTexSvxGradientAxial aGradient( getDefinitionRange(), getOutputRange(), - aStart, - aEnd, - nSteps, + getFillGradient().getSteps(), + getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getAngle()); - aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + aGradient.appendTransformationsAndColors(aCallback); break; } - case attribute::GradientStyle::Radial: + case css::awt::GradientStyle_RADIAL: { texture::GeoTexSvxGradientRadial aGradient( getDefinitionRange(), - aStart, - aEnd, - nSteps, + getFillGradient().getSteps(), + getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY()); - aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + aGradient.appendTransformationsAndColors(aCallback); break; } - case attribute::GradientStyle::Elliptical: + case css::awt::GradientStyle_ELLIPTICAL: { texture::GeoTexSvxGradientElliptical aGradient( getDefinitionRange(), - aStart, - aEnd, - nSteps, + getFillGradient().getSteps(), + getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY(), getFillGradient().getAngle()); - aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + aGradient.appendTransformationsAndColors(aCallback); break; } - case attribute::GradientStyle::Square: + case css::awt::GradientStyle_SQUARE: { texture::GeoTexSvxGradientSquare aGradient( getDefinitionRange(), - aStart, - aEnd, - nSteps, + getFillGradient().getSteps(), + getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY(), getFillGradient().getAngle()); - aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + aGradient.appendTransformationsAndColors(aCallback); break; } - case attribute::GradientStyle::Rect: + case css::awt::GradientStyle_RECT: { texture::GeoTexSvxGradientRect aGradient( getDefinitionRange(), - aStart, - aEnd, - nSteps, + getFillGradient().getSteps(), + getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY(), getFillGradient().getAngle()); - aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + aGradient.appendTransformationsAndColors(aCallback); break; } } } - void FillGradientPrimitive2D::createOverlappingFill( - Primitive2DContainer& rContainer, - const std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, - const basegfx::BColor& rOuterColor, - const basegfx::B2DPolygon& rUnitPolygon) const - { - // create solid fill with outmost color - rContainer.push_back( - new PolyPolygonColorPrimitive2D( - basegfx::B2DPolyPolygon( - basegfx::utils::createPolygonFromRect(getOutputRange())), - rOuterColor)); - - // create solid fill steps - for(const auto &a : rEntries) - { - // create part polygon - basegfx::B2DPolygon aNewPoly(rUnitPolygon); - - aNewPoly.transform(a.maB2DHomMatrix); - - // create solid fill - rContainer.push_back( - new PolyPolygonColorPrimitive2D( - basegfx::B2DPolyPolygon(aNewPoly), - a.maBColor)); - } - } - - void FillGradientPrimitive2D::createNonOverlappingFill( - Primitive2DContainer& rContainer, - const std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, - const basegfx::BColor& rOuterColor, - const basegfx::B2DPolygon& rUnitPolygon) const + Primitive2DReference FillGradientPrimitive2D::createFill(bool bOverlapping) const { - // get outmost visible range from object - basegfx::B2DRange aOutmostRange(getOutputRange()); - basegfx::B2DPolyPolygon aCombinedPolyPoly; - - if(!rEntries.empty()) - { - // extend aOutmostRange with first polygon - basegfx::B2DPolygon aFirstPoly(rUnitPolygon); - - aFirstPoly.transform(rEntries[0].maB2DHomMatrix); - aCombinedPolyPoly.append(aFirstPoly); - aOutmostRange.expand(aFirstPoly.getB2DRange()); - } - - // add outmost range to combined polypolygon (in 1st place), create first primitive - aCombinedPolyPoly.insert(0, basegfx::utils::createPolygonFromRect(aOutmostRange)); - rContainer.push_back( - new PolyPolygonColorPrimitive2D( - aCombinedPolyPoly, - rOuterColor)); - - if(rEntries.empty()) - return; - - // reuse first polygon, it's the second one - aCombinedPolyPoly.remove(0); - - for(size_t a(0); a < rEntries.size() - 1; a++) + Primitive2DContainer aContainer; + if (bOverlapping) { - // create next inner polygon, combined with last one - basegfx::B2DPolygon aNextPoly(rUnitPolygon); - - aNextPoly.transform(rEntries[a + 1].maB2DHomMatrix); - aCombinedPolyPoly.append(aNextPoly); - - // create primitive with correct color - rContainer.push_back( + // OverlappingFill: create solid fill with outmost color + aContainer.push_back( new PolyPolygonColorPrimitive2D( - aCombinedPolyPoly, - rEntries[a].maBColor)); - - // reuse inner polygon, it's the 2nd one - aCombinedPolyPoly.remove(0); + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(getOutputRange())), + getOuterColor())); + + // create solid fill steps by providing callback as lambda + auto aCallback([&aContainer,this]( + const basegfx::B2DHomMatrix& rMatrix, + const basegfx::BColor& rColor) + { + // create part polygon + basegfx::B2DPolygon aNewPoly(getUnitPolygon()); + aNewPoly.transform(rMatrix); + + // create solid fill + aContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aNewPoly), + rColor)); + }); + + // call value generator to trigger callbacks + generateMatricesAndColors(aCallback); } - - // add last inner polygon with last color - rContainer.push_back( - new PolyPolygonColorPrimitive2D( - aCombinedPolyPoly, - rEntries[rEntries.size() - 1].maBColor)); - } - - void FillGradientPrimitive2D::createFill(Primitive2DContainer& rContainer, bool bOverlapping) const - { - // prepare shape of the Unit Polygon - basegfx::B2DPolygon aUnitPolygon; - - switch(getFillGradient().getStyle()) + else { - case attribute::GradientStyle::Radial: - case attribute::GradientStyle::Elliptical: + // NonOverlappingFill + if (getFillGradient().getColorStops().size() < 2) { - aUnitPolygon = basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), 1.0); - break; + // not really a gradient, we need to create a start primitive + // entry using the single color and the covered area + const basegfx::B2DRange aOutmostRange(getOutputRange()); + aContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aOutmostRange)), + getOuterColor())); } - default: // GradientStyle::Linear, attribute::GradientStyle::Axial, attribute::GradientStyle::Square, attribute::GradientStyle::Rect + else { - aUnitPolygon = basegfx::utils::createPolygonFromRect(basegfx::B2DRange(-1.0, -1.0, 1.0, 1.0)); - break; + // gradient with stops, prepare CombinedPolyPoly, use callback + basegfx::B2DPolyPolygon aCombinedPolyPoly; + basegfx::BColor aLastColor; + + auto aCallback([&aContainer,&aCombinedPolyPoly,&aLastColor,this]( + const basegfx::B2DHomMatrix& rMatrix, + const basegfx::BColor& rColor) + { + if (aContainer.empty()) + { + // 1st callback, init CombinedPolyPoly & create 1st entry + basegfx::B2DRange aOutmostRange(getOutputRange()); + + // expand aOutmostRange with transformed first polygon + // to ensure confinement + basegfx::B2DPolygon aFirstPoly(getUnitPolygon()); + aFirstPoly.transform(rMatrix); + aOutmostRange.expand(aFirstPoly.getB2DRange()); + + // build 1st combined polygon; outmost range 1st, then + // the shaped, transformed polygon + aCombinedPolyPoly.append(basegfx::utils::createPolygonFromRect(aOutmostRange)); + aCombinedPolyPoly.append(aFirstPoly); + + // create first primitive + aContainer.push_back( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + getOuterColor())); + + // save first polygon for re-use in next call, it's the second + // one, so remove 1st + aCombinedPolyPoly.remove(0); + + // remember color for next primitive creation + aLastColor = rColor; + } + else + { + // regular n-th callback, create combined entry by re-using + // CombinedPolyPoly and aLastColor + basegfx::B2DPolygon aNextPoly(getUnitPolygon()); + aNextPoly.transform(rMatrix); + aCombinedPolyPoly.append(aNextPoly); + + // create primitive with correct color + aContainer.push_back( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + aLastColor)); + + // prepare re-use of inner polygon, save color + aCombinedPolyPoly.remove(0); + aLastColor = rColor; + } + }); + + // call value generator to trigger callbacks + generateMatricesAndColors(aCallback); + + // add last inner polygon with last color + aContainer.push_back( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + aLastColor)); } } - - // get the transform matrices and colors (where colors - // will have one more entry that matrices) - std::vector< drawinglayer::texture::B2DHomMatrixAndBColor > aEntries; - basegfx::BColor aOuterColor; - - generateMatricesAndColors(aEntries, aOuterColor); - - if(bOverlapping) - { - createOverlappingFill(rContainer, aEntries, aOuterColor, aUnitPolygon); - } - else - { - createNonOverlappingFill(rContainer, aEntries, aOuterColor, aUnitPolygon); - } + return new GroupPrimitive2D(std::move(aContainer)); } - void FillGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference FillGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { // default creates overlapping fill which works with AntiAliasing and without. // The non-overlapping version does not create single filled polygons, but @@ -280,26 +269,27 @@ namespace drawinglayer::primitive2d if(!getFillGradient().isDefault()) { - createFill(rContainer, /*bOverlapping*/true); + return createFill(/*bOverlapping*/true); } + return nullptr; } FillGradientPrimitive2D::FillGradientPrimitive2D( const basegfx::B2DRange& rOutputRange, - const attribute::FillGradientAttribute& rFillGradient) + attribute::FillGradientAttribute aFillGradient) : maOutputRange(rOutputRange), maDefinitionRange(rOutputRange), - maFillGradient(rFillGradient) + maFillGradient(std::move(aFillGradient)) { } FillGradientPrimitive2D::FillGradientPrimitive2D( const basegfx::B2DRange& rOutputRange, const basegfx::B2DRange& rDefinitionRange, - const attribute::FillGradientAttribute& rFillGradient) + attribute::FillGradientAttribute aFillGradient) : maOutputRange(rOutputRange), maDefinitionRange(rDefinitionRange), - maFillGradient(rFillGradient) + maFillGradient(std::move(aFillGradient)) { } diff --git a/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx index 0f386eea29de..a553687e7787 100644 --- a/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx @@ -25,6 +25,7 @@ #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <primitive2d/graphicprimitivehelper2d.hxx> +#include <utility> #include <vcl/graph.hxx> @@ -33,24 +34,25 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void FillGraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference FillGraphicPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { const attribute::FillGraphicAttribute& rAttribute = getFillGraphic(); if(rAttribute.isDefault()) - return; + return nullptr; const Graphic& rGraphic = rAttribute.getGraphic(); if(GraphicType::Bitmap != rGraphic.GetType() && GraphicType::GdiMetafile != rGraphic.GetType()) - return; + return nullptr; const Size aSize(rGraphic.GetPrefSize()); if(!(aSize.Width() && aSize.Height())) - return; + return nullptr; // we have a graphic (bitmap or metafile) with some size + Primitive2DContainer aContainer; if(rAttribute.getTiling()) { // get object range and create tiling matrices @@ -69,11 +71,12 @@ namespace drawinglayer::primitive2d rGraphic, basegfx::B2DHomMatrix()); + rtl::Reference<GroupPrimitive2D> xGroup = new GroupPrimitive2D(std::move(xSeq)); for(const auto &a : aMatrices) { - rContainer.push_back(new TransformPrimitive2D( + aContainer.push_back(new TransformPrimitive2D( getTransformation() * a, - Primitive2DContainer(xSeq))); + *xGroup)); } } else @@ -84,17 +87,19 @@ namespace drawinglayer::primitive2d rAttribute.getGraphicRange().getRange(), rAttribute.getGraphicRange().getMinimum())); - create2DDecompositionOfGraphic(rContainer, + create2DDecompositionOfGraphic(aContainer, rGraphic, aObjectTransform); } + return new GroupPrimitive2D(std::move(aContainer)); } FillGraphicPrimitive2D::FillGraphicPrimitive2D( - const basegfx::B2DHomMatrix& rTransformation, + basegfx::B2DHomMatrix aTransformation, const attribute::FillGraphicAttribute& rFillGraphic) - : maTransformation(rTransformation), - maFillGraphic(rFillGraphic) + : maTransformation(std::move(aTransformation)), + maFillGraphic(rFillGraphic), + maOffsetXYCreatedBitmap() { } diff --git a/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx b/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx index 60d47c3ed559..c855460824e7 100644 --- a/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx @@ -19,12 +19,14 @@ #include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> #include <texture/texture.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dpolygon.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> +#include <utility> using namespace com::sun::star; @@ -32,10 +34,10 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void FillHatchPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference FillHatchPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if(getFillHatch().isDefault()) - return; + return nullptr; // create hatch const basegfx::BColor aHatchColor(getFillHatch().getColor()); @@ -99,12 +101,12 @@ namespace drawinglayer::primitive2d // prepare return value const bool bFillBackground(getFillHatch().isFillBackground()); - + Primitive2DContainer aContainer; // evtl. create filled background if(bFillBackground) { // create primitive for background - rContainer.push_back( + aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon( basegfx::utils::createPolygonFromRect(getOutputRange())), getBColor())); @@ -123,17 +125,18 @@ namespace drawinglayer::primitive2d aNewLine.append(rMatrix * aEnd); // create hairline - rContainer.push_back(new PolygonHairlinePrimitive2D(aNewLine, aHatchColor)); + aContainer.push_back(new PolygonHairlinePrimitive2D(std::move(aNewLine), aHatchColor)); } + return new GroupPrimitive2D(std::move(aContainer)); } FillHatchPrimitive2D::FillHatchPrimitive2D( const basegfx::B2DRange& rOutputRange, const basegfx::BColor& rBColor, - const attribute::FillHatchAttribute& rFillHatch) + attribute::FillHatchAttribute aFillHatch) : maOutputRange(rOutputRange), maDefinitionRange(rOutputRange), - maFillHatch(rFillHatch), + maFillHatch(std::move(aFillHatch)), maBColor(rBColor) { } @@ -142,10 +145,10 @@ namespace drawinglayer::primitive2d const basegfx::B2DRange& rOutputRange, const basegfx::B2DRange& rDefinitionRange, const basegfx::BColor& rBColor, - const attribute::FillHatchAttribute& rFillHatch) + attribute::FillHatchAttribute aFillHatch) : maOutputRange(rOutputRange), maDefinitionRange(rDefinitionRange), - maFillHatch(rFillHatch), + maFillHatch(std::move(aFillHatch)), maBColor(rBColor) { } @@ -173,10 +176,8 @@ namespace drawinglayer::primitive2d void FillHatchPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); bool bAdaptDistance(0 != getFillHatch().getMinimalDiscreteDistance()); - aGuard.unlock(); if(bAdaptDistance) { // behave view-dependent diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx index 6fe14c2222c1..6bf9dea8af83 100644 --- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -18,7 +18,18 @@ */ #include <drawinglayer/primitive2d/glowprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <drawinglayer/converters.hxx> +#include "GlowSoftEgdeShadowTools.hxx" + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif using namespace com::sun::star; @@ -26,15 +37,19 @@ namespace drawinglayer::primitive2d { GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius, Primitive2DContainer&& rChildren) - : GroupPrimitive2D(std::move(rChildren)) + : BufferedDecompositionGroupPrimitive2D(std::move(rChildren)) , maGlowColor(rGlowColor) , mfGlowRadius(fRadius) + , mfLastDiscreteGlowRadius(0.0) + , maLastClippedRange() { + // activate callback to flush buffered decomposition content + setCallbackSeconds(15); } bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { - if (BasePrimitive2D::operator==(rPrimitive)) + if (BufferedDecompositionGroupPrimitive2D::operator==(rPrimitive)) { const GlowPrimitive2D& rCompare = static_cast<const GlowPrimitive2D&>(rPrimitive); @@ -45,12 +60,294 @@ bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const return false; } +bool GlowPrimitive2D::prepareValuesAndcheckValidity( + basegfx::B2DRange& rGlowRange, basegfx::B2DRange& rClippedRange, + basegfx::B2DVector& rDiscreteGlowSize, double& rfDiscreteGlowRadius, + const geometry::ViewInformation2D& rViewInformation) const +{ + // no GlowRadius defined, done + if (getGlowRadius() <= 0.0) + return false; + + // no geometry, done + if (getChildren().empty()) + return false; + + // no pixel target, done + if (rViewInformation.getObjectToViewTransformation().isIdentity()) + return false; + + // get geometry range that defines area that needs to be pixelated + rGlowRange = getChildren().getB2DRange(rViewInformation); + + // no range of geometry, done + if (rGlowRange.isEmpty()) + return false; + + // extend range by GlowRadius in all directions + rGlowRange.grow(getGlowRadius()); + + // initialize ClippedRange to full GlowRange -> all is visible + rClippedRange = rGlowRange; + + // get Viewport and check if used. If empty, all is visible (see + // ViewInformation2D definition in viewinformation2d.hxx) + if (!rViewInformation.getViewport().isEmpty()) + { + // if used, extend by GlowRadius to ensure needed parts are included + basegfx::B2DRange aVisibleArea(rViewInformation.getViewport()); + aVisibleArea.grow(getGlowRadius()); + + // To do this correctly, it needs to be done in discrete coordinates. + // The object may be transformed relative to the original# + // ObjectTransformation, e.g. when re-used in shadow + aVisibleArea.transform(rViewInformation.getViewTransformation()); + rClippedRange.transform(rViewInformation.getObjectToViewTransformation()); + + // calculate ClippedRange + rClippedRange.intersect(aVisibleArea); + + // if GlowRange is completely outside of VisibleArea, ClippedRange + // will be empty and we are done + if (rClippedRange.isEmpty()) + return false; + + // convert result back to object coordinates + rClippedRange.transform(rViewInformation.getInverseObjectToViewTransformation()); + } + + // calculate discrete pixel size of GlowRange. If it's too small to visualize, we are done + rDiscreteGlowSize = rViewInformation.getObjectToViewTransformation() * rGlowRange.getRange(); + if (ceil(rDiscreteGlowSize.getX()) < 2.0 || ceil(rDiscreteGlowSize.getY()) < 2.0) + return false; + + // calculate discrete pixel size of GlowRadius. If it's too small to visualize, we are done + rfDiscreteGlowRadius = ceil( + (rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(getGlowRadius(), 0)) + .getLength()); + if (rfDiscreteGlowRadius < 1.0) + return false; + + return true; +} + +void GlowPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aGlowRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteGlowSize; + double fDiscreteGlowRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aGlowRange, aClippedRange, aDiscreteGlowSize, + fDiscreteGlowRadius, rViewInformation)) + return; + + // Create embedding transformation from object to top-left zero-aligned + // target pixel geometry (discrete form of ClippedRange) + // First, move to top-left of GlowRange + const sal_uInt32 nDiscreteGlowWidth(ceil(aDiscreteGlowSize.getX())); + const sal_uInt32 nDiscreteGlowHeight(ceil(aDiscreteGlowSize.getY())); + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aClippedRange.getMinX(), -aClippedRange.getMinY())); + // Second, scale to discrete bitmap size + // Even when using the offset from ClippedRange, we need to use the + // scaling from the full representation, thus from GlowRange + aEmbedding.scale(nDiscreteGlowWidth / aGlowRange.getWidth(), + nDiscreteGlowHeight / aGlowRange.getHeight()); + + // Embed content graphics to TransformPrimitive2D + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D(aEmbedding, Primitive2DContainer(getChildren()))); + primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef }; + + // Create BitmapEx using drawinglayer tooling, including a MaximumQuadraticPixel + // limitation to be safe and not go runtime/memory havoc. Use a pretty small + // limit due to this is glow functionality and will look good with bitmap scaling + // anyways. The value of 250.000 square pixels below maybe adapted as needed. + const basegfx::B2DVector aDiscreteClippedSize(rViewInformation.getObjectToViewTransformation() + * aClippedRange.getRange()); + const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX())); + const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY())); + const geometry::ViewInformation2D aViewInformation2D; + const sal_uInt32 nMaximumQuadraticPixels(250000); + + // I have now added a helper that just creates the mask without having + // to render the content, use it, it's faster + const AlphaMask aAlpha(::drawinglayer::createAlphaMask( + std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight, + nMaximumQuadraticPixels)); + + if (aAlpha.IsEmpty()) + return; + + const Size& rBitmapExSizePixel(aAlpha.GetSizePixel()); + + if (rBitmapExSizePixel.Width() <= 0 || rBitmapExSizePixel.Height() <= 0) + return; + + // We may have to take a corrective scaling into account when the + // MaximumQuadraticPixel limit was used/triggered + double fScale(1.0); + + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) + { + // scale in X and Y should be the same (see fReduceFactor in createAlphaMask), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + fScale = (fScaleX + fScaleY) * 0.5; + } + + // fDiscreteGlowRadius is the size of the halo from each side of the object. The halo is the + // border of glow color that fades from glow transparency level to fully transparent + // When blurring a sharp boundary (our case), it gets 50% of original intensity, and + // fades to both sides by the blur radius; thus blur radius is half of glow radius. + // Consider glow transparency (initial transparency near the object edge) + AlphaMask mask(ProcessAndBlurAlphaMask(aAlpha, fDiscreteGlowRadius * fScale / 2.0, + fDiscreteGlowRadius * fScale / 2.0, + 255 - getGlowColor().GetAlpha())); + + // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask + Bitmap bmp(aAlpha.GetSizePixel(), vcl::PixelFormat::N24_BPP); + bmp.Erase(getGlowColor()); + BitmapEx result(bmp, mask); + +#ifdef DBG_UTIL + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_glow.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(result); + } + } +#endif + + // Independent from discrete sizes of glow alpha creation, always + // map and project glow result to geometry range extended by glow + // radius, but to the eventually clipped instance (ClippedRange) + const primitive2d::Primitive2DReference xEmbedRefBitmap( + new BitmapPrimitive2D(result, basegfx::utils::createScaleTranslateB2DHomMatrix( + aClippedRange.getWidth(), aClippedRange.getHeight(), + aClippedRange.getMinX(), aClippedRange.getMinY()))); + + rContainer = primitive2d::Primitive2DContainer{ xEmbedRefBitmap }; +} + +// Using tooling class BufferedDecompositionGroupPrimitive2D now, so +// no more need to locally do the buffered get2DDecomposition here, +// see BufferedDecompositionGroupPrimitive2D::get2DDecomposition +void GlowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aGlowRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteGlowSize; + double fDiscreteGlowRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aGlowRange, aClippedRange, aDiscreteGlowSize, + fDiscreteGlowRadius, rViewInformation)) + return; + + if (!getBuffered2DDecomposition().empty()) + { + // First check is to detect if the last created decompose is capable + // to represent the now requested visualization. + // ClippedRange is the needed visualizationArea for the current glow + // effect, LastClippedRange is the one from the existing/last rendering. + // Check if last created area is sufficient and can be re-used + if (!maLastClippedRange.isEmpty() && !maLastClippedRange.isInside(aClippedRange)) + { + // To avoid unnecessary invalidations due to being *very* correct + // with HairLines (which are view-dependent and thus change the + // result(s) here slightly when changing zoom), add a slight unsharp + // component if we have a ViewTransform. The derivation is inside + // the range of half a pixel (due to one pixel hairline) + basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange); + + if (!rViewInformation.getObjectToViewTransformation().isIdentity()) + { + // Grow by view-dependent size of 1/2 pixel + const double fHalfPixel((rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(0.5, 0)) + .getLength()); + aLastClippedRangeAndHairline.grow(fHalfPixel); + } + + if (!aLastClippedRangeAndHairline.isInside(aClippedRange)) + { + // Conditions of last local decomposition have changed, delete + const_cast<GlowPrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + } + } + + if (!getBuffered2DDecomposition().empty()) + { + // Second check is to react on changes of the DiscreteGlowRadius when + // zooming in/out. + // Use the known last and current DiscreteGlowRadius to decide + // if the visualization can be re-used. Be a little 'creative' here + // and make it dependent on a *relative* change - it is not necessary + // to re-create everytime if the exact value is missed since zooming + // pixel-based glow effect is pretty good due to it's smooth nature + bool bFree(mfLastDiscreteGlowRadius <= 0.0 || fDiscreteGlowRadius <= 0.0); + + if (!bFree) + { + const double fDiff(fabs(mfLastDiscreteGlowRadius - fDiscreteGlowRadius)); + const double fLen(fabs(mfLastDiscreteGlowRadius) + fabs(fDiscreteGlowRadius)); + const double fRelativeChange(fDiff / fLen); + + // Use lower fixed values here to change more often, higher to change less often. + // Value is in the range of ]0.0 .. 1.0] + bFree = fRelativeChange >= 0.15; + } + + if (bFree) + { + // Conditions of last local decomposition have changed, delete + const_cast<GlowPrimitive2D*>(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if (getBuffered2DDecomposition().empty()) + { + // refresh last used DiscreteGlowRadius and ClippedRange to new remembered values + const_cast<GlowPrimitive2D*>(this)->mfLastDiscreteGlowRadius = fDiscreteGlowRadius; + const_cast<GlowPrimitive2D*>(this)->maLastClippedRange = aClippedRange; + } + + // call parent, that will check for empty, call create2DDecomposition and + // set as decomposition + BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); +} + basegfx::B2DRange GlowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const { - basegfx::B2DRange aRetval(GroupPrimitive2D::getB2DRange(rViewInformation)); + // Hint: Do *not* use GroupPrimitive2D::getB2DRange, that will (unnecessarily) + // use the decompose - what works, but is not needed here. + // We know the to-be-visualized geometry and the radius it needs to be extended, + // so simply calculate the exact needed range. + basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); + // We need additional space for the glow from all sides aRetval.grow(getGlowRadius()); + return aRetval; } diff --git a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx index 0d57512f2f86..8219d25d59bc 100644 --- a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx @@ -27,16 +27,17 @@ #include <primitive2d/graphicprimitivehelper2d.hxx> #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <utility> namespace drawinglayer::primitive2d { -void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, - const geometry::ViewInformation2D&) const +Primitive2DReference +GraphicPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D&) const { if (0 == getGraphicAttr().GetAlpha()) { // content is invisible, done - return; + return nullptr; } // do not apply mirroring from GraphicAttr to the Metafile by calling @@ -106,7 +107,7 @@ void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, if (aRetval.empty()) { // content is invisible, done - return; + return nullptr; } if (isAdjusted || isDrawMode) @@ -126,7 +127,7 @@ void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, if (aRetval.empty()) { // content is invisible, done - return; + return nullptr; } } @@ -138,10 +139,8 @@ void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, if (!basegfx::fTools::equalZero(fTransparency)) { - Primitive2DReference aUnifiedTransparence( - new UnifiedTransparencePrimitive2D(std::move(aRetval), fTransparency)); - - aRetval = Primitive2DContainer{ aUnifiedTransparence }; + aRetval = Primitive2DContainer{ new UnifiedTransparencePrimitive2D(std::move(aRetval), + fTransparency) }; } } @@ -157,33 +156,35 @@ void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, getGraphicAttr().GetBottomCrop())); // embed content in cropPrimitive - Primitive2DReference xPrimitive( - new CropPrimitive2D(std::move(aRetval), aTransform, - getGraphicAttr().GetLeftCrop() * aCropScaleFactor.getX(), - getGraphicAttr().GetTopCrop() * aCropScaleFactor.getY(), - getGraphicAttr().GetRightCrop() * aCropScaleFactor.getX(), - getGraphicAttr().GetBottomCrop() * aCropScaleFactor.getY())); - - aRetval = Primitive2DContainer{ xPrimitive }; + aRetval = Primitive2DContainer{ new CropPrimitive2D( + std::move(aRetval), aTransform, + getGraphicAttr().GetLeftCrop() * aCropScaleFactor.getX(), + getGraphicAttr().GetTopCrop() * aCropScaleFactor.getY(), + getGraphicAttr().GetRightCrop() * aCropScaleFactor.getX(), + getGraphicAttr().GetBottomCrop() * aCropScaleFactor.getY()) }; } - rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + return new GroupPrimitive2D(std::move(aRetval)); } -GraphicPrimitive2D::GraphicPrimitive2D(const basegfx::B2DHomMatrix& rTransform, +GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform, const GraphicObject& rGraphicObject, const GraphicAttr& rGraphicAttr) - : maTransform(rTransform) + : maTransform(std::move(aTransform)) , maGraphicObject(rGraphicObject) , maGraphicAttr(rGraphicAttr) { + // activate callback to flush buffered decomposition content + setCallbackSeconds(20); } -GraphicPrimitive2D::GraphicPrimitive2D(const basegfx::B2DHomMatrix& rTransform, +GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform, const GraphicObject& rGraphicObject) - : maTransform(rTransform) + : maTransform(std::move(aTransform)) , maGraphicObject(rGraphicObject) { + // activate callback to flush buffered decomposition content + setCallbackSeconds(20); } bool GraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx index c91dd683106b..d756e6e3b74f 100644 --- a/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx +++ b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx @@ -36,6 +36,7 @@ // helper class for animated graphics +#include <utility> #include <vcl/animate/Animation.hxx> #include <vcl/graph.hxx> #include <vcl/virdev.hxx> @@ -74,7 +75,7 @@ namespace drawinglayer::primitive2d Primitive2DReference maBufferedFirstFrame; /// buffering of all frames - Primitive2DContainer maBufferedPrimitives; + std::vector<Primitive2DReference> maBufferedPrimitives; bool mbBufferingAllowed; /// set if the animation is huge so that just always the next frame @@ -103,6 +104,10 @@ namespace drawinglayer::primitive2d maVirtualDeviceMask->EnableMapMode(false); maVirtualDevice->SetOutputSizePixel(aTarget); maVirtualDeviceMask->SetOutputSizePixel(aTarget); + + // tdf#156630 make erase calls fill with transparency + maVirtualDevice->SetBackground(COL_BLACK); + maVirtualDeviceMask->SetBackground(COL_ALPHA_TRANSPARENT); } maVirtualDevice->Erase(); @@ -115,13 +120,13 @@ namespace drawinglayer::primitive2d sal_uInt32 generateStepTime(sal_uInt32 nIndex) const { - const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(nIndex)); - sal_uInt32 nWaitTime(rAnimationBitmap.mnWait * 10); + const AnimationFrame& rAnimationFrame = maAnimation.Get(sal_uInt16(nIndex)); + sal_uInt32 nWaitTime(rAnimationFrame.mnWait * 10); // Take care of special value for MultiPage TIFFs. ATM these shall just // show their first page. Later we will offer some switching when object // is selected. - if (ANIMATION_TIMEOUT_ON_CLICK == rAnimationBitmap.mnWait) + if (ANIMATION_TIMEOUT_ON_CLICK == rAnimationFrame.mnWait) { // ATM the huge value would block the timer, so // use a long time to show first page (whole day) @@ -180,14 +185,13 @@ namespace drawinglayer::primitive2d } else { - const Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); + Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); + // tdf#156630 invert the alpha mask + aMaskBitmap.Invert(); // convert from transparency to alpha bitmap = BitmapEx(aMainBitmap, aMaskBitmap); } - return Primitive2DReference( - new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(bitmap), - getTransform())); + return new BitmapPrimitive2D(bitmap, getTransform()); } void checkSafeToBuffer(sal_uInt32 nIndex) @@ -249,14 +253,23 @@ namespace drawinglayer::primitive2d while (mnNextFrameToPrepare <= nTarget) { // prepare step - const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare)); + const AnimationFrame& rAnimationFrame = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare)); + + bool bSourceBlending = rAnimationFrame.meBlend == Blend::Source; + + if (bSourceBlending) + { + tools::Rectangle aArea(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx.GetSizePixel()); + maVirtualDevice->Erase(aArea); + maVirtualDeviceMask->Erase(aArea); + } - switch (rAnimationBitmap.meDisposal) + switch (rAnimationFrame.meDisposal) { case Disposal::Not: { - maVirtualDevice->DrawBitmapEx(rAnimationBitmap.maPositionPixel, rAnimationBitmap.maBitmapEx); - Bitmap aAlphaMask = rAnimationBitmap.maBitmapEx.GetAlpha(); + maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx); + AlphaMask aAlphaMask = rAnimationFrame.maBitmapEx.GetAlphaMask(); if (aAlphaMask.IsEmpty()) { @@ -267,8 +280,8 @@ namespace drawinglayer::primitive2d } else { - BitmapEx aExpandVisibilityMask(aAlphaMask, aAlphaMask); - maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + BitmapEx aExpandVisibilityMask(aAlphaMask.GetBitmap(), aAlphaMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask); } break; @@ -276,32 +289,31 @@ namespace drawinglayer::primitive2d case Disposal::Back: { // #i70772# react on no mask, for primitives, too. - const Bitmap & rMask(rAnimationBitmap.maBitmapEx.GetAlpha()); - const Bitmap & rContent(rAnimationBitmap.maBitmapEx.GetBitmap()); + const AlphaMask & rMask(rAnimationFrame.maBitmapEx.GetAlphaMask()); maVirtualDeviceMask->Erase(); - maVirtualDevice->DrawBitmap(rAnimationBitmap.maPositionPixel, rContent); + maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx); if (rMask.IsEmpty()) { - const ::tools::Rectangle aRect(rAnimationBitmap.maPositionPixel, rContent.GetSizePixel()); + const ::tools::Rectangle aRect(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx.GetSizePixel()); maVirtualDeviceMask->SetFillColor(COL_BLACK); maVirtualDeviceMask->SetLineColor(); maVirtualDeviceMask->DrawRect(aRect); } else { - BitmapEx aExpandVisibilityMask(rMask, rMask); - maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + BitmapEx aExpandVisibilityMask(rMask.GetBitmap(), rMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask); } break; } case Disposal::Previous: { - maVirtualDevice->DrawBitmapEx(rAnimationBitmap.maPositionPixel, rAnimationBitmap.maBitmapEx); - BitmapEx aExpandVisibilityMask(rAnimationBitmap.maBitmapEx.GetAlpha(), rAnimationBitmap.maBitmapEx.GetAlpha()); - maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx); + BitmapEx aExpandVisibilityMask(rAnimationFrame.maBitmapEx.GetAlphaMask().GetBitmap(), rAnimationFrame.maBitmapEx.GetAlphaMask()); + maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask); break; } } @@ -342,7 +354,8 @@ namespace drawinglayer::primitive2d /// constructor AnimatedGraphicPrimitive2D( const Graphic& rGraphic, - const basegfx::B2DHomMatrix& rTransform); + basegfx::B2DHomMatrix aTransform); + virtual ~AnimatedGraphicPrimitive2D(); /// data read access const basegfx::B2DHomMatrix& getTransform() const { return maTransform; } @@ -358,12 +371,12 @@ namespace drawinglayer::primitive2d AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D( const Graphic& rGraphic, - const basegfx::B2DHomMatrix& rTransform) + basegfx::B2DHomMatrix aTransform) : AnimatedSwitchPrimitive2D( animation::AnimationEntryList(), Primitive2DContainer(), false), - maTransform(rTransform), + maTransform(std::move(aTransform)), maGraphic(rGraphic), maAnimation(rGraphic.GetAnimation()), maVirtualDevice(*Application::GetDefaultDevice()), @@ -402,10 +415,27 @@ namespace drawinglayer::primitive2d // prepare buffer space if (mbBufferingAllowed && isValidData()) { - maBufferedPrimitives = Primitive2DContainer(maAnimation.Count()); + maBufferedPrimitives.resize(maAnimation.Count()); } } + AnimatedGraphicPrimitive2D::~AnimatedGraphicPrimitive2D() + { + // Related: tdf#158807 mutex must be locked when disposing a VirtualDevice + // If the following .ppt document is opened in a debug build + // and the document is left open for a minute or two without + // changing any content, this destructor will be called on a + // non-main thread with the mutex unlocked: + // https://bugs.documentfoundation.org/attachment.cgi?id=46801 + // This hits an assert in VirtualDevice::ReleaseGraphics() so + // explicitly lock the mutex and explicitly dispose and clear + // the VirtualDevice instances variables. + const SolarMutexGuard aSolarGuard; + + maVirtualDevice.disposeAndClear(); + maVirtualDeviceMask.disposeAndClear(); + } + bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { // do not use 'GroupPrimitive2D::operator==' here, that would compare @@ -442,7 +472,7 @@ namespace drawinglayer::primitive2d if (aRetval.is()) { - rVisitor.append(aRetval); + rVisitor.visit(aRetval); return; } @@ -461,14 +491,14 @@ namespace drawinglayer::primitive2d if (aRetval.is()) { - rVisitor.append(aRetval); + rVisitor.visit(aRetval); return; } // did not work (not buffered and not 1st frame), create from buffer aRetval = createFromBuffer(); - rVisitor.append(aRetval); + rVisitor.visit(aRetval); } } // end of namespace @@ -489,17 +519,16 @@ namespace drawinglayer::primitive2d if(rGraphic.IsAnimated()) { // prepare specialized AnimatedGraphicPrimitive2D - aRetval.resize(1); - aRetval[0] = new AnimatedGraphicPrimitive2D( + aRetval = Primitive2DContainer { new AnimatedGraphicPrimitive2D( rGraphic, - rTransform); + rTransform) }; } else if(rGraphic.getVectorGraphicData()) { // embedded Vector Graphic Data fill, create embed transform const basegfx::B2DRange& rSvgRange(rGraphic.getVectorGraphicData()->getRange()); - if(basegfx::fTools::more(rSvgRange.getWidth(), 0.0) && basegfx::fTools::more(rSvgRange.getHeight(), 0.0)) + if(rSvgRange.getWidth() > 0.0 && rSvgRange.getHeight() > 0.0) { // translate back to origin, scale to unit coordinates basegfx::B2DHomMatrix aEmbedVectorGraphic( @@ -515,18 +544,16 @@ namespace drawinglayer::primitive2d aEmbedVectorGraphic = rTransform * aEmbedVectorGraphic; // add Vector Graphic Data primitives embedded - aRetval.resize(1); - aRetval[0] = new TransformPrimitive2D( + aRetval = Primitive2DContainer { new TransformPrimitive2D( aEmbedVectorGraphic, - rGraphic.getVectorGraphicData()->getPrimitive2DSequence()); + Primitive2DContainer(rGraphic.getVectorGraphicData()->getPrimitive2DSequence()))}; } } else { - aRetval.resize(1); - aRetval[0] = new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(rGraphic.GetBitmapEx()), - rTransform); + aRetval = Primitive2DContainer { new BitmapPrimitive2D( + rGraphic.GetBitmapEx(), + rTransform) }; } break; @@ -537,10 +564,9 @@ namespace drawinglayer::primitive2d // create MetafilePrimitive2D const GDIMetaFile& rMetafile = rGraphic.GetGDIMetaFile(); - aRetval.resize(1); - aRetval[0] = new MetafilePrimitive2D( + aRetval = Primitive2DContainer { new MetafilePrimitive2D( rTransform, - rMetafile); + rMetafile) }; // #i100357# find out if clipping is needed for this primitive. Unfortunately, // there exist Metafiles who's content is bigger than the proposed PrefSize set @@ -557,10 +583,11 @@ namespace drawinglayer::primitive2d basegfx::B2DPolygon aMaskPolygon(basegfx::utils::createUnitPolygon()); aMaskPolygon.transform(rTransform); - Primitive2DReference mask = new MaskPrimitive2D( - basegfx::B2DPolyPolygon(aMaskPolygon), - std::move(aRetval)); - aRetval = Primitive2DContainer { mask }; + aRetval = Primitive2DContainer { + new MaskPrimitive2D( + basegfx::B2DPolyPolygon(aMaskPolygon), + std::move(aRetval)) + }; } break; } @@ -572,7 +599,7 @@ namespace drawinglayer::primitive2d } } - rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + rContainer.append(std::move(aRetval)); } Primitive2DContainer create2DColorModifierEmbeddingsAsNeeded( diff --git a/drawinglayer/source/primitive2d/gridprimitive2d.cxx b/drawinglayer/source/primitive2d/gridprimitive2d.cxx index 515263e0d475..1a996188f03c 100644 --- a/drawinglayer/source/primitive2d/gridprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/gridprimitive2d.cxx @@ -20,9 +20,11 @@ #include <drawinglayer/primitive2d/gridprimitive2d.hxx> #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <utility> using namespace com::sun::star; @@ -30,10 +32,10 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void GridPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference GridPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { if(!(!rViewInformation.getViewport().isEmpty() && getWidth() > 0.0 && getHeight() > 0.0)) - return; + return nullptr; // decompose grid matrix to get logic size basegfx::B2DVector aScale, aTranslate; @@ -158,7 +160,7 @@ namespace drawinglayer::primitive2d } if(aExtendedViewport.isEmpty()) - return; + return nullptr; // prepare point vectors for point and cross markers std::vector< basegfx::B2DPoint > aPositionsPoint; @@ -228,29 +230,31 @@ namespace drawinglayer::primitive2d const sal_uInt32 nCountCross(aPositionsCross.size()); // add PointArrayPrimitive2D if point markers were added + Primitive2DContainer aContainer; if(nCountPoint) { - rContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsPoint), getBColor())); + aContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsPoint), getBColor())); } // add MarkerArrayPrimitive2D if cross markers were added if(!nCountCross) - return; + return new GroupPrimitive2D(std::move(aContainer)); if(!getSubdivisionsX() && !getSubdivisionsY()) { // no subdivisions, so fall back to points at grid positions, no need to // visualize a difference between divisions and sub-divisions - rContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsCross), getBColor())); + aContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsCross), getBColor())); } else { - rContainer.push_back(new MarkerArrayPrimitive2D(std::move(aPositionsCross), getCrossMarker())); + aContainer.push_back(new MarkerArrayPrimitive2D(std::move(aPositionsCross), getCrossMarker())); } + return new GroupPrimitive2D(std::move(aContainer)); } GridPrimitive2D::GridPrimitive2D( - const basegfx::B2DHomMatrix& rTransform, + basegfx::B2DHomMatrix aTransform, double fWidth, double fHeight, double fSmallestViewDistance, @@ -259,7 +263,7 @@ namespace drawinglayer::primitive2d sal_uInt32 nSubdivisionsY, const basegfx::BColor& rBColor, const BitmapEx& rCrossMarker) - : maTransform(rTransform), + : maTransform(std::move(aTransform)), mfWidth(fWidth), mfHeight(fHeight), mfSmallestViewDistance(fSmallestViewDistance), @@ -305,18 +309,16 @@ namespace drawinglayer::primitive2d void GridPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - - if(!getBuffered2DDecomposition().empty()) + if(getBuffered2DDecomposition()) { if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) { // conditions of last local decomposition have changed, delete - const_cast< GridPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< GridPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember ViewRange and ViewTransformation const_cast< GridPrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); @@ -324,7 +326,6 @@ namespace drawinglayer::primitive2d } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/groupprimitive2d.cxx b/drawinglayer/source/primitive2d/groupprimitive2d.cxx index 8c16d848fdff..e6428c09e8cd 100644 --- a/drawinglayer/source/primitive2d/groupprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/groupprimitive2d.cxx @@ -31,7 +31,7 @@ namespace drawinglayer::primitive2d { } - /** The compare opertator uses the Sequence::==operator, so only checking if + /** The compare operator uses the Sequence::==operator, so only checking if the references are equal. All non-equal references are interpreted as non-equal. */ @@ -53,16 +53,13 @@ namespace drawinglayer::primitive2d getChildren(rVisitor); } - sal_Int64 SAL_CALL GroupPrimitive2D::estimateUsage() + sal_Int64 GroupPrimitive2D::estimateUsage() { size_t nRet(0); for (auto& it : getChildren()) { - uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY); - if (xAcc.is()) - { - nRet += xAcc->estimateUsage(); - } + if (it) + nRet += it->estimateUsage(); } return nRet; } diff --git a/drawinglayer/source/primitive2d/helplineprimitive2d.cxx b/drawinglayer/source/primitive2d/helplineprimitive2d.cxx index ba1a680560bb..047084eb1469 100644 --- a/drawinglayer/source/primitive2d/helplineprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/helplineprimitive2d.cxx @@ -19,10 +19,11 @@ #include <drawinglayer/primitive2d/helplineprimitive2d.hxx> #include <basegfx/polygon/b2dpolygon.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <basegfx/polygon/b2dpolygonclipper.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> using namespace com::sun::star; @@ -30,14 +31,14 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void HelplinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference HelplinePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { if(rViewInformation.getViewport().isEmpty() || getDirection().equalZero()) - return; + return nullptr; // position to view coordinates, DashLen and DashLen in logic const basegfx::B2DPoint aViewPosition(rViewInformation.getObjectToViewTransformation() * getPosition()); - + Primitive2DReference xRet; switch(getStyle()) { default : // HelplineStyle2D::Point @@ -52,7 +53,7 @@ namespace drawinglayer::primitive2d aLineA.append(aStartA); aLineA.append(aEndA); aLineA.transform(rViewInformation.getInverseObjectToViewTransformation()); - rContainer.push_back(new PolygonMarkerPrimitive2D(aLineA, getRGBColA(), getRGBColB(), getDiscreteDashLength())); + auto xMarker1 = new PolygonMarkerPrimitive2D(std::move(aLineA), getRGBColA(), getRGBColB(), getDiscreteDashLength()); const basegfx::B2DVector aPerpendicularNormalizedDirection(basegfx::getPerpendicular(aNormalizedDirection)); const basegfx::B2DPoint aStartB(aViewPosition - aPerpendicularNormalizedDirection); @@ -61,8 +62,9 @@ namespace drawinglayer::primitive2d aLineB.append(aStartB); aLineB.append(aEndB); aLineB.transform(rViewInformation.getInverseObjectToViewTransformation()); - rContainer.push_back(new PolygonMarkerPrimitive2D(aLineB, getRGBColA(), getRGBColB(), getDiscreteDashLength())); + auto xMarker2 = new PolygonMarkerPrimitive2D(std::move(aLineB), getRGBColA(), getRGBColB(), getDiscreteDashLength()); + xRet = new GroupPrimitive2D(Primitive2DContainer{xMarker1, xMarker2}); break; } case HelplineStyle2D::Line : @@ -106,18 +108,20 @@ namespace drawinglayer::primitive2d { // clip against visible area const basegfx::B2DPolyPolygon aResult(basegfx::utils::clipPolygonOnRange(aLine, rViewInformation.getDiscreteViewport(), true, true)); - + Primitive2DContainer aContainer; for(sal_uInt32 a(0); a < aResult.count(); a++) { basegfx::B2DPolygon aPart(aResult.getB2DPolygon(a)); aPart.transform(rViewInformation.getInverseObjectToViewTransformation()); - rContainer.push_back(new PolygonMarkerPrimitive2D(aPart, getRGBColA(), getRGBColB(), getDiscreteDashLength())); + aContainer.push_back(new PolygonMarkerPrimitive2D(std::move(aPart), getRGBColA(), getRGBColB(), getDiscreteDashLength())); } + xRet = new GroupPrimitive2D(std::move(aContainer)); } break; } } + return xRet; } HelplinePrimitive2D::HelplinePrimitive2D( @@ -155,18 +159,16 @@ namespace drawinglayer::primitive2d void HelplinePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - - if(!getBuffered2DDecomposition().empty()) + if(getBuffered2DDecomposition()) { if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) { // conditions of last local decomposition have changed, delete - const_cast< HelplinePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< HelplinePrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember ViewRange and ViewTransformation const_cast< HelplinePrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); @@ -174,7 +176,6 @@ namespace drawinglayer::primitive2d } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx b/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx index ab66cb223a66..1702b16496d8 100644 --- a/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx @@ -22,6 +22,7 @@ #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <toolkit/helper/vclunohelper.hxx> @@ -30,19 +31,19 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void MarkerArrayPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference MarkerArrayPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { const std::vector< basegfx::B2DPoint >& rPositions = getPositions(); const sal_uInt32 nMarkerCount(rPositions.size()); if(!nMarkerCount || getMarker().IsEmpty()) - return; + return nullptr; // get pixel size Size aBitmapSize(getMarker().GetSizePixel()); if(!(aBitmapSize.Width() && aBitmapSize.Height())) - return; + return nullptr; // get logic half pixel size basegfx::B2DVector aLogicHalfSize(rViewInformation.getInverseObjectToViewTransformation() * @@ -51,9 +52,9 @@ namespace drawinglayer::primitive2d // use half size for expand aLogicHalfSize *= 0.5; - for(sal_uInt32 a(0); a < nMarkerCount; a++) + Primitive2DContainer aContainer; + for(const auto& rPosition : rPositions) { - const basegfx::B2DPoint& rPosition(rPositions[a]); const basegfx::B2DRange aRange(rPosition - aLogicHalfSize, rPosition + aLogicHalfSize); basegfx::B2DHomMatrix aTransform; @@ -62,11 +63,12 @@ namespace drawinglayer::primitive2d aTransform.set(0, 2, aRange.getMinX()); aTransform.set(1, 2, aRange.getMinY()); - rContainer.push_back( + aContainer.push_back( new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getMarker()), + getMarker(), aTransform)); } + return new GroupPrimitive2D(std::move(aContainer)); } MarkerArrayPrimitive2D::MarkerArrayPrimitive2D( diff --git a/drawinglayer/source/primitive2d/maskprimitive2d.cxx b/drawinglayer/source/primitive2d/maskprimitive2d.cxx index 842085168827..630548861a90 100644 --- a/drawinglayer/source/primitive2d/maskprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/maskprimitive2d.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <utility> using namespace com::sun::star; @@ -27,10 +28,10 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { MaskPrimitive2D::MaskPrimitive2D( - const basegfx::B2DPolyPolygon& rMask, + basegfx::B2DPolyPolygon aMask, Primitive2DContainer&& aChildren) : GroupPrimitive2D(std::move(aChildren)), - maMask(rMask) + maMask(std::move(aMask)) { } diff --git a/drawinglayer/source/primitive2d/mediaprimitive2d.cxx b/drawinglayer/source/primitive2d/mediaprimitive2d.cxx index 108f53bf1431..eb70c7602c8c 100644 --- a/drawinglayer/source/primitive2d/mediaprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/mediaprimitive2d.cxx @@ -21,6 +21,7 @@ #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <utility> #include <vcl/GraphicObject.hxx> #include <drawinglayer/primitive2d/graphicprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> @@ -31,7 +32,7 @@ namespace drawinglayer::primitive2d { - void MediaPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference MediaPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { Primitive2DContainer xRetval; xRetval.resize(1); @@ -82,25 +83,26 @@ namespace drawinglayer::primitive2d aTransform.translate(aDestRange.getMinX(), aDestRange.getMinY()); // add transform primitive - Primitive2DReference aScaled(new TransformPrimitive2D(aTransform, std::move(xRetval))); - xRetval = Primitive2DContainer { aScaled }; + xRetval = Primitive2DContainer { + new TransformPrimitive2D(aTransform, std::move(xRetval)) // Scaled + }; } } - rContainer.insert(rContainer.end(), xRetval.begin(), xRetval.end()); + return new GroupPrimitive2D(std::move(xRetval)); } MediaPrimitive2D::MediaPrimitive2D( - const basegfx::B2DHomMatrix& rTransform, - const OUString& rURL, + basegfx::B2DHomMatrix aTransform, + OUString aURL, const basegfx::BColor& rBackgroundColor, sal_uInt32 nDiscreteBorder, - const Graphic &rSnapshot) - : maTransform(rTransform), - maURL(rURL), + Graphic aSnapshot) + : maTransform(std::move(aTransform)), + maURL(std::move(aURL)), maBackgroundColor(rBackgroundColor), mnDiscreteBorder(nDiscreteBorder), - maSnapshot(rSnapshot) + maSnapshot(std::move(aSnapshot)) { } @@ -113,7 +115,8 @@ namespace drawinglayer::primitive2d return (getTransform() == rCompare.getTransform() && maURL == rCompare.maURL && getBackgroundColor() == rCompare.getBackgroundColor() - && getDiscreteBorder() == rCompare.getDiscreteBorder()); + && getDiscreteBorder() == rCompare.getDiscreteBorder() + && maSnapshot.IsNone() == rCompare.maSnapshot.IsNone()); } return false; diff --git a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx index eddb02375d0a..46ddf6582571 100644 --- a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx @@ -18,6 +18,7 @@ */ #include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <utility> #include <wmfemfhelper.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> @@ -30,67 +31,67 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void MetafilePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference MetafilePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { // Interpret the Metafile and get the content. There should be only one target, as in the start condition, // but iterating will be the right thing to do when some push/pop is not closed Primitive2DContainer xRetval(wmfemfhelper::interpretMetafile(getMetaFile(), rViewInformation)); - if(!xRetval.empty()) + if(xRetval.empty()) + return nullptr; + + // get target size + const ::tools::Rectangle aMtfTarget(getMetaFile().GetPrefMapMode().GetOrigin(), getMetaFile().GetPrefSize()); + const basegfx::B2DRange aMtfRange(vcl::unotools::b2DRectangleFromRectangle(aMtfTarget)); + + // tdf#113197 get content range and check if we have an overlap with + // defined target range (aMtfRange) + if (!aMtfRange.isEmpty()) { - // get target size - const ::tools::Rectangle aMtfTarget(getMetaFile().GetPrefMapMode().GetOrigin(), getMetaFile().GetPrefSize()); - const basegfx::B2DRange aMtfRange(vcl::unotools::b2DRectangleFromRectangle(aMtfTarget)); + const basegfx::B2DRange aContentRange(xRetval.getB2DRange(rViewInformation)); - // tdf#113197 get content range and check if we have an overlap with - // defined target range (aMtfRange) - if (!aMtfRange.isEmpty()) + // also test equal since isInside gives also true for equal + if (!aMtfRange.equal(aContentRange) && !aMtfRange.isInside(aContentRange)) { - const basegfx::B2DRange aContentRange(xRetval.getB2DRange(rViewInformation)); - - // also test equal since isInside gives also true for equal - if (!aMtfRange.equal(aContentRange) && !aMtfRange.isInside(aContentRange)) - { - // contentRange is partly larger than aMtfRange (stuff sticks - // outside), clipping is needed - const drawinglayer::primitive2d::Primitive2DReference xMask( - new drawinglayer::primitive2d::MaskPrimitive2D( - basegfx::B2DPolyPolygon( - basegfx::utils::createPolygonFromRect( - aMtfRange)), - std::move(xRetval))); - - xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask }; - } + // contentRange is partly larger than aMtfRange (stuff sticks + // outside), clipping is needed + const drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect( + aMtfRange)), + std::move(xRetval))); + + xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask }; } + } - // create transformation - basegfx::B2DHomMatrix aAdaptedTransform; - - aAdaptedTransform.translate(-aMtfTarget.Left(), -aMtfTarget.Top()); - aAdaptedTransform.scale( - aMtfTarget.getWidth() ? 1.0 / aMtfTarget.getWidth() : 1.0, - aMtfTarget.getHeight() ? 1.0 / aMtfTarget.getHeight() : 1.0); - aAdaptedTransform = getTransform() * aAdaptedTransform; + // create transformation + basegfx::B2DHomMatrix aAdaptedTransform; - // embed to target transformation - const Primitive2DReference aEmbeddedTransform( - new TransformPrimitive2D( - aAdaptedTransform, - std::move(xRetval))); + aAdaptedTransform.translate(-aMtfTarget.Left(), -aMtfTarget.Top()); + aAdaptedTransform.scale( + aMtfTarget.getOpenWidth() ? 1.0 / aMtfTarget.getOpenWidth() : 1.0, + aMtfTarget.getOpenHeight() ? 1.0 / aMtfTarget.getOpenHeight() : 1.0); + aAdaptedTransform = getTransform() * aAdaptedTransform; - xRetval = Primitive2DContainer { aEmbeddedTransform }; - } + // embed to target transformation + const Primitive2DReference aEmbeddedTransform( + new TransformPrimitive2D( + aAdaptedTransform, + std::move(xRetval))); - rContainer.insert(rContainer.end(), xRetval.begin(), xRetval.end()); + return aEmbeddedTransform; } MetafilePrimitive2D::MetafilePrimitive2D( - const basegfx::B2DHomMatrix& rMetaFileTransform, + basegfx::B2DHomMatrix aMetaFileTransform, const GDIMetaFile& rMetaFile) - : maMetaFileTransform(rMetaFileTransform), + : maMetaFileTransform(std::move(aMetaFileTransform)), maMetaFile(rMetaFile) { + // activate callback to flush buffered decomposition content + setCallbackSeconds(20); } bool MetafilePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx b/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx index 2d0f572dad39..9786f9164e7f 100644 --- a/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <utility> using namespace com::sun::star; @@ -28,9 +29,9 @@ namespace drawinglayer::primitive2d { ModifiedColorPrimitive2D::ModifiedColorPrimitive2D( Primitive2DContainer&& aChildren, - const basegfx::BColorModifierSharedPtr& rColorModifier) + basegfx::BColorModifierSharedPtr xColorModifier) : GroupPrimitive2D(std::move(aChildren)), - maColorModifier(rColorModifier) + maColorModifier(std::move(xColorModifier)) { } diff --git a/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx b/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx index 577f3171a3dd..0c91957766a4 100644 --- a/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx @@ -18,6 +18,7 @@ */ #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <utility> using namespace com::sun::star; @@ -25,13 +26,13 @@ namespace drawinglayer::primitive2d { ObjectInfoPrimitive2D::ObjectInfoPrimitive2D( Primitive2DContainer&& aChildren, - const OUString& rName, - const OUString& rTitle, - const OUString& rDesc) + OUString aName, + OUString aTitle, + OUString aDesc) : GroupPrimitive2D(std::move(aChildren)), - maName(rName), - maTitle(rTitle), - maDesc(rDesc) + maName(std::move(aName)), + maTitle(std::move(aTitle)), + maDesc(std::move(aDesc)) { } diff --git a/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx b/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx index 7aa1a440555e..a4280ea1aba0 100644 --- a/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx @@ -24,6 +24,7 @@ #include <basegfx/polygon/b2dpolygon.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <utility> using namespace com::sun::star; @@ -31,22 +32,22 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { - void PagePreviewPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference PagePreviewPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { Primitive2DContainer aContent(getPageContent()); if(!(!aContent.empty() - && basegfx::fTools::more(getContentWidth(), 0.0) - && basegfx::fTools::more(getContentHeight(), 0.0))) - return; + && getContentWidth() > 0.0) + && getContentHeight() > 0.0) + return nullptr; // the decomposed matrix will be needed basegfx::B2DVector aScale, aTranslate; double fRotate, fShearX; getTransform().decompose(aScale, aTranslate, fRotate, fShearX); - if(!(basegfx::fTools::more(aScale.getX(), 0.0) && basegfx::fTools::more(aScale.getY(), 0.0))) - return; + if(!(aScale.getX() > 0.0 && aScale.getY() > 0.0)) + return nullptr; // check if content overlaps with target size and needs to be embedded with a // clipping primitive @@ -97,18 +98,18 @@ namespace drawinglayer::primitive2d aPageTrans = aCombined * aPageTrans; // embed in necessary transformation to map from SdrPage to SdrPageObject - rContainer.push_back(new TransformPrimitive2D(aPageTrans, std::move(aContent))); + return new TransformPrimitive2D(aPageTrans, std::move(aContent)); } PagePreviewPrimitive2D::PagePreviewPrimitive2D( - const css::uno::Reference< css::drawing::XDrawPage >& rxDrawPage, - const basegfx::B2DHomMatrix& rTransform, + css::uno::Reference< css::drawing::XDrawPage > xDrawPage, + basegfx::B2DHomMatrix aTransform, double fContentWidth, double fContentHeight, Primitive2DContainer&& rPageContent) - : mxDrawPage(rxDrawPage), + : mxDrawPage(std::move(xDrawPage)), maPageContent(std::move(rPageContent)), - maTransform(rTransform), + maTransform(std::move(aTransform)), mfContentWidth(fContentWidth), mfContentHeight(fContentHeight) { diff --git a/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx index cd4c58d11127..516b0042d960 100644 --- a/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx @@ -29,6 +29,7 @@ #include <toolkit/helper/vclunohelper.hxx> #include <drawinglayer/converters.hxx> +#include <utility> using namespace com::sun::star; @@ -125,11 +126,11 @@ namespace drawinglayer::primitive2d if(0 != mnDiscreteWidth && 0 != mnDiscreteHeight) { const geometry::ViewInformation2D aViewInformation2D; - primitive2d::Primitive2DReference xEmbedRef( - new primitive2d::TransformPrimitive2D( - basegfx::utils::createScaleB2DHomMatrix(mnDiscreteWidth, mnDiscreteHeight), - Primitive2DContainer(getChildren()))); - primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; + primitive2d::Primitive2DContainer xEmbedSeq { + new primitive2d::TransformPrimitive2D( + basegfx::utils::createScaleB2DHomMatrix(mnDiscreteWidth, mnDiscreteHeight), + Primitive2DContainer(getChildren())) + }; const BitmapEx aBitmapEx( convertToBitmapEx( @@ -147,7 +148,7 @@ namespace drawinglayer::primitive2d { const primitive2d::Primitive2DReference xEmbedRefBitmap( new primitive2d::BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(aBitmapEx), + aBitmapEx, basegfx::B2DHomMatrix())); aContent = primitive2d::Primitive2DContainer { xEmbedRefBitmap }; } @@ -201,20 +202,20 @@ namespace drawinglayer::primitive2d nWidth * nHeight); } - void PatternFillPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference PatternFillPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { Primitive2DContainer aRetval; if(getChildren().empty()) - return; + return nullptr; if(!(!getReferenceRange().isEmpty() && getReferenceRange().getWidth() > 0.0 && getReferenceRange().getHeight() > 0.0)) - return; + return nullptr; const basegfx::B2DRange aMaskRange(getMask().getB2DRange()); if(!(!aMaskRange.isEmpty() && aMaskRange.getWidth() > 0.0 && aMaskRange.getHeight() > 0.0)) - return; + return nullptr; // create tiling matrices std::vector< basegfx::B2DHomMatrix > aMatrices; @@ -243,28 +244,25 @@ namespace drawinglayer::primitive2d aMaskRange.getRange(), aMaskRange.getMinimum())); - Primitive2DReference xRef( - new TransformPrimitive2D( - aMaskTransform, - std::move(aRetval))); - - aRetval = Primitive2DContainer { xRef }; + aRetval = Primitive2DContainer { + new TransformPrimitive2D( + aMaskTransform, + std::move(aRetval)) + }; } // embed result in mask - { - rContainer.push_back( - new MaskPrimitive2D( - getMask(), - std::move(aRetval))); - } + return + new MaskPrimitive2D( + getMask(), + std::move(aRetval)); } PatternFillPrimitive2D::PatternFillPrimitive2D( - const basegfx::B2DPolyPolygon& rMask, + basegfx::B2DPolyPolygon aMask, Primitive2DContainer&& rChildren, const basegfx::B2DRange& rReferenceRange) - : maMask(rMask), + : maMask(std::move(aMask)), maChildren(std::move(rChildren)), maReferenceRange(rReferenceRange), mnDiscreteWidth(0), @@ -343,24 +341,19 @@ namespace drawinglayer::primitive2d PatternFillPrimitive2D* pThat = const_cast< PatternFillPrimitive2D* >(this); pThat->mnDiscreteWidth = nW; pThat->mnDiscreteHeight = nH; - pThat->setBuffered2DDecomposition(Primitive2DContainer()); + pThat->setBuffered2DDecomposition(nullptr); } // call parent BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } - sal_Int64 SAL_CALL PatternFillPrimitive2D::estimateUsage() + sal_Int64 PatternFillPrimitive2D::estimateUsage() { size_t nRet(0); for (auto& it : getChildren()) - { - uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY); - if (xAcc.is()) - { - nRet += xAcc->estimateUsage(); - } - } + if (it) + nRet += it->estimateUsage(); return nRet; } diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx index 25e78edffff4..ab6833a44ffa 100644 --- a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx @@ -17,22 +17,48 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#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) { } @@ -57,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; @@ -80,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() @@ -100,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) @@ -164,10 +296,9 @@ void PolygonMarkerPrimitive2D::get2DDecomposition( Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard(m_aMutex); bool bNeedNewDecomposition(false); - if (!getBuffered2DDecomposition().empty()) + if (getBuffered2DDecomposition()) { if (rViewInformation.getInverseObjectToViewTransformation() != maLastInverseObjectToViewTransformation) @@ -179,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); @@ -192,7 +322,6 @@ void PolygonMarkerPrimitive2D::get2DDecomposition( } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } @@ -206,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())); @@ -250,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 @@ -280,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 @@ -309,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()) { @@ -329,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; } @@ -336,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 @@ -382,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())); @@ -394,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()); } } @@ -485,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()); @@ -536,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( diff --git a/drawinglayer/source/primitive2d/primitivetools2d.cxx b/drawinglayer/source/primitive2d/primitivetools2d.cxx index c4ab4f5c800b..04a91fe9b55b 100644 --- a/drawinglayer/source/primitive2d/primitivetools2d.cxx +++ b/drawinglayer/source/primitive2d/primitivetools2d.cxx @@ -26,26 +26,23 @@ namespace drawinglayer::primitive2d { void DiscreteMetricDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - // get the current DiscreteUnit, look at X and Y and use the maximum const basegfx::B2DVector aDiscreteVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); const double fDiscreteUnit(std::min(fabs(aDiscreteVector.getX()), fabs(aDiscreteVector.getY()))); - if(!getBuffered2DDecomposition().empty() && !basegfx::fTools::equal(fDiscreteUnit, getDiscreteUnit())) + if(getBuffered2DDecomposition() && !basegfx::fTools::equal(fDiscreteUnit, getDiscreteUnit())) { // conditions of last local decomposition have changed, delete - const_cast< DiscreteMetricDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< DiscreteMetricDependentPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember new valid DiscreteUnit const_cast< DiscreteMetricDependentPrimitive2D* >(this)->mfDiscreteUnit = fDiscreteUnit; } // call base implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } @@ -54,75 +51,67 @@ namespace drawinglayer::primitive2d void ViewportDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - // get the current Viewport const basegfx::B2DRange& rViewport = rViewInformation.getViewport(); - if(!getBuffered2DDecomposition().empty() && !rViewport.equal(getViewport())) + if(getBuffered2DDecomposition() && !rViewport.equal(getViewport())) { // conditions of last local decomposition have changed, delete - const_cast< ViewportDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< ViewportDependentPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember new valid DiscreteUnit const_cast< ViewportDependentPrimitive2D* >(this)->maViewport = rViewport; } // call base implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } void ViewTransformationDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - // get the current ViewTransformation const basegfx::B2DHomMatrix& rViewTransformation = rViewInformation.getViewTransformation(); - if(!getBuffered2DDecomposition().empty() && rViewTransformation != getViewTransformation()) + if(getBuffered2DDecomposition() && rViewTransformation != getViewTransformation()) { // conditions of last local decomposition have changed, delete - const_cast< ViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< ViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember new valid ViewTransformation const_cast< ViewTransformationDependentPrimitive2D* >(this)->maViewTransformation = rViewTransformation; } // call base implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } void ObjectAndViewTransformationDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - // get the current ViewTransformation const basegfx::B2DHomMatrix& rViewTransformation = rViewInformation.getViewTransformation(); - if(!getBuffered2DDecomposition().empty() && rViewTransformation != getViewTransformation()) + if(getBuffered2DDecomposition() && rViewTransformation != getViewTransformation()) { // conditions of last local decomposition have changed, delete - const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } // get the current ObjectTransformation const basegfx::B2DHomMatrix& rObjectTransformation = rViewInformation.getObjectTransformation(); - if(!getBuffered2DDecomposition().empty() && rObjectTransformation != getObjectTransformation()) + if(getBuffered2DDecomposition() && rObjectTransformation != getObjectTransformation()) { // conditions of last local decomposition have changed, delete - const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { // remember new valid ViewTransformation, and ObjectTransformation const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->maViewTransformation = rViewTransformation; @@ -130,7 +119,6 @@ namespace drawinglayer::primitive2d } // call base implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx index 870d187b222c..11807e459b7f 100644 --- a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx @@ -23,17 +23,21 @@ #include <basegfx/matrix/b2dhommatrix.hxx> #include <drawinglayer/attribute/sdrlightattribute3d.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <processor3d/zbufferprocessor3d.hxx> #include <processor3d/shadow3dextractor.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <svtools/optionsdrawinglayer.hxx> #include <processor3d/geometry2dextractor.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <basegfx/raster/bzpixelraster.hxx> +#include <utility> #include <vcl/BitmapTools.hxx> #include <comphelper/threadpool.hxx> +#include <comphelper/lok.hxx> #include <toolkit/helper/vclunohelper.hxx> +#include <officecfg/Office/Common.hxx> using namespace com::sun::star; @@ -209,8 +213,9 @@ namespace drawinglayer::primitive2d } } - void ScenePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + Primitive2DReference ScenePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const { + Primitive2DContainer aContainer; // create 2D shadows from contained 3D primitives. This creates the shadow primitives on demand and tells if // there are some or not. Do this at start, the shadow might still be visible even when the scene is not if(impGetShadow3D()) @@ -223,7 +228,7 @@ namespace drawinglayer::primitive2d if(aViewRange.isEmpty() || aShadow2DRange.overlaps(aViewRange)) { // add extracted 2d shadows (before 3d scene creations itself) - rContainer.insert(rContainer.end(), maShadowPrimitives.begin(), maShadowPrimitives.end()); + aContainer.append(maShadowPrimitives); } } @@ -235,13 +240,13 @@ namespace drawinglayer::primitive2d calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); if(aVisibleDiscreteRange.isEmpty()) - return; + return new GroupPrimitive2D(std::move(aContainer)); // test if discrete view size (pixel) maybe too big and limit it double fViewSizeX(aVisibleDiscreteRange.getWidth()); double fViewSizeY(aVisibleDiscreteRange.getHeight()); const double fViewVisibleArea(fViewSizeX * fViewSizeY); - const double fMaximumVisibleArea(SvtOptionsDrawinglayer::GetQuadratic3DRenderLimit()); + const double fMaximumVisibleArea(officecfg::Office::Common::Drawinglayer::Quadratic3DRenderLimit::get()); double fReduceFactor(1.0); if(fViewVisibleArea > fMaximumVisibleArea) @@ -279,7 +284,7 @@ namespace drawinglayer::primitive2d // determine the oversample value static const sal_uInt16 nDefaultOversampleValue(3); - const sal_uInt16 nOversampleValue(SvtOptionsDrawinglayer::IsAntiAliasing() ? nDefaultOversampleValue : 0); + sal_uInt16 nOversampleValue(SvtOptionsDrawinglayer::IsAntiAliasing() ? nDefaultOversampleValue : 0); geometry::ViewInformation3D aViewInformation3D(getViewInformation3D()); { @@ -351,15 +356,65 @@ namespace drawinglayer::primitive2d const double fLogicY((aInverseOToV * basegfx::B2DVector(0.0, aDiscreteRange.getHeight() * fReduceFactor)).getLength()); // generate ViewSizes - const double fFullViewSizeX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fLogicX, 0.0)).getLength()); - const double fFullViewSizeY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fLogicY)).getLength()); + double fFullViewSizeX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fLogicX, 0.0)).getLength()); + double fFullViewSizeY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fLogicY)).getLength()); // generate RasterWidth and RasterHeight for visible part - const sal_Int32 nRasterWidth(basegfx::fround(fFullViewSizeX * aUnitVisibleRange.getWidth()) + 1); - const sal_Int32 nRasterHeight(basegfx::fround(fFullViewSizeY * aUnitVisibleRange.getHeight()) + 1); + sal_Int32 nRasterWidth(basegfx::fround(fFullViewSizeX * aUnitVisibleRange.getWidth()) + 1); + sal_Int32 nRasterHeight(basegfx::fround(fFullViewSizeY * aUnitVisibleRange.getHeight()) + 1); + + if(!rViewInformation.getReducedDisplayQuality() && comphelper::LibreOfficeKit::isActive()) + { + // for this purpose allow reduced 3D quality and make a compromise + // between quality and speed. This is balanced between those two + // targets, fine-tuning/experimenting can be done with the values + // below. + + // define some values which allow fine-tuning this feature + static const double fMin(80.0); + static const double fSqareMin(fMin * fMin); + static const double fMax(800.0); + static const double fSqareMax(fMax * fMax); + static const double fMaxReduction(0.65); + + // get the square pixels (work on pixel numbers to get same + // behaviour independent of width/height relations) + const double fSquarePixels(nRasterWidth * nRasterHeight); + + if (fSquarePixels > fSqareMin) + { + // only reduce at all when more than fSqareMin pixels needed + double fReduction(fMaxReduction); + + if (fSquarePixels < fSqareMax) + { + // range between fSqareMin and fSqareMax, calculate a + // linear interpolated reduction based on square root + fReduction = sqrt(fSquarePixels); // [fMin .. fMax] + fReduction = fReduction - fMin; // [0 .. (fMax - fMin)] + fReduction = fReduction / (fMax - fMin); // [0 .. 1] + fReduction = 1.0 - (fReduction * (1.0 - fMaxReduction)); // [1 .. fMaxReduction] + + // reduce oversampling for this range + if(nOversampleValue > 2) + nOversampleValue--; + } + else + { + // more than fSqareMax pixels, disable oversampling + nOversampleValue = 0; + } + + // adapt needed values to reduction + nRasterWidth = basegfx::fround(fReduction * nRasterWidth); + nRasterHeight = basegfx::fround(fReduction * nRasterHeight); + fFullViewSizeX *= fReduction; + fFullViewSizeY *= fReduction; + } + } if(!(nRasterWidth && nRasterHeight)) - return; + return new GroupPrimitive2D(std::move(aContainer)); // create view unit buffer basegfx::BZPixelRaster aBZPixelRaster( @@ -367,7 +422,7 @@ namespace drawinglayer::primitive2d nOversampleValue ? nRasterHeight * nOversampleValue : nRasterHeight); // check for parallel execution possibilities - static bool bMultithreadAllowed = false; // loplugin:constvars:ignore + static bool bMultithreadAllowed = true; // loplugin:constvars:ignore sal_Int32 nThreadCount(0); comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool()); @@ -455,7 +510,7 @@ namespace drawinglayer::primitive2d const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel()); if(!(aBitmapSizePixel.getWidth() && aBitmapSizePixel.getHeight())) - return; + return new GroupPrimitive2D(std::move(aContainer)); // create transform for the created bitmap in discrete coordinates first. basegfx::B2DHomMatrix aNew2DTransform; @@ -469,9 +524,9 @@ namespace drawinglayer::primitive2d aNew2DTransform *= aInverseOToV; // create bitmap primitive and add - rContainer.push_back( + aContainer.push_back( new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(maOldRenderedBitmap), + maOldRenderedBitmap, aNew2DTransform)); // test: Allow to add an outline in the debugger when tests are needed @@ -481,8 +536,9 @@ namespace drawinglayer::primitive2d { basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon()); aOutline.transform(aNew2DTransform); - rContainer.push_back(new PolygonHairlinePrimitive2D(aOutline, basegfx::BColor(1.0, 0.0, 0.0))); + aContainer.push_back(new PolygonHairlinePrimitive2D(std::move(aOutline), basegfx::BColor(1.0, 0.0, 0.0))); } + return new GroupPrimitive2D(std::move(aContainer)); } Primitive2DContainer ScenePrimitive2D::getGeometry2D() const @@ -509,8 +565,6 @@ namespace drawinglayer::primitive2d Primitive2DContainer ScenePrimitive2D::getShadow2D() const { - std::unique_lock aGuard( m_aMutex ); - Primitive2DContainer aRetval; // create 2D shadows from contained 3D primitives @@ -525,60 +579,61 @@ namespace drawinglayer::primitive2d bool ScenePrimitive2D::tryToCheckLastVisualisationDirectHit(const basegfx::B2DPoint& rLogicHitPoint, bool& o_rResult) const { - if(!maOldRenderedBitmap.IsEmpty() && !maOldUnitVisiblePart.isEmpty()) - { - basegfx::B2DHomMatrix aInverseSceneTransform(getObjectTransformation()); - aInverseSceneTransform.invert(); - const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * rLogicHitPoint); - - if(maOldUnitVisiblePart.isInside(aRelativePoint)) - { - // calculate coordinates relative to visualized part - double fDivisorX(maOldUnitVisiblePart.getWidth()); - double fDivisorY(maOldUnitVisiblePart.getHeight()); + if(maOldRenderedBitmap.IsEmpty() || maOldUnitVisiblePart.isEmpty()) + return false; - if(basegfx::fTools::equalZero(fDivisorX)) - { - fDivisorX = 1.0; - } + basegfx::B2DHomMatrix aInverseSceneTransform(getObjectTransformation()); + aInverseSceneTransform.invert(); + const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * rLogicHitPoint); - if(basegfx::fTools::equalZero(fDivisorY)) - { - fDivisorY = 1.0; - } + if(!maOldUnitVisiblePart.isInside(aRelativePoint)) + return false; - const double fRelativeX((aRelativePoint.getX() - maOldUnitVisiblePart.getMinX()) / fDivisorX); - const double fRelativeY((aRelativePoint.getY() - maOldUnitVisiblePart.getMinY()) / fDivisorY); + // calculate coordinates relative to visualized part + double fDivisorX(maOldUnitVisiblePart.getWidth()); + double fDivisorY(maOldUnitVisiblePart.getHeight()); - // combine with real BitmapSizePixel to get bitmap coordinates - const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel()); - const sal_Int32 nX(basegfx::fround(fRelativeX * aBitmapSizePixel.Width())); - const sal_Int32 nY(basegfx::fround(fRelativeY * aBitmapSizePixel.Height())); + if(basegfx::fTools::equalZero(fDivisorX)) + { + fDivisorX = 1.0; + } - // try to get a statement about transparency in that pixel - o_rResult = (0 != maOldRenderedBitmap.GetAlpha(nX, nY)); - return true; - } + if(basegfx::fTools::equalZero(fDivisorY)) + { + fDivisorY = 1.0; } - return false; + const double fRelativeX((aRelativePoint.getX() - maOldUnitVisiblePart.getMinX()) / fDivisorX); + const double fRelativeY((aRelativePoint.getY() - maOldUnitVisiblePart.getMinY()) / fDivisorY); + + // combine with real BitmapSizePixel to get bitmap coordinates + const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel()); + const sal_Int32 nX(basegfx::fround(fRelativeX * aBitmapSizePixel.Width())); + const sal_Int32 nY(basegfx::fround(fRelativeY * aBitmapSizePixel.Height())); + + // try to get a statement about transparency in that pixel + o_rResult = (0 != maOldRenderedBitmap.GetAlpha(nX, nY)); + return true; } ScenePrimitive2D::ScenePrimitive2D( - const primitive3d::Primitive3DContainer& rxChildren3D, - const attribute::SdrSceneAttribute& rSdrSceneAttribute, - const attribute::SdrLightingAttribute& rSdrLightingAttribute, - const basegfx::B2DHomMatrix& rObjectTransformation, - const geometry::ViewInformation3D& rViewInformation3D) - : mxChildren3D(rxChildren3D), - maSdrSceneAttribute(rSdrSceneAttribute), - maSdrLightingAttribute(rSdrLightingAttribute), - maObjectTransformation(rObjectTransformation), - maViewInformation3D(rViewInformation3D), + primitive3d::Primitive3DContainer aChildren3D, + attribute::SdrSceneAttribute aSdrSceneAttribute, + attribute::SdrLightingAttribute aSdrLightingAttribute, + basegfx::B2DHomMatrix aObjectTransformation, + geometry::ViewInformation3D aViewInformation3D) + : BufferedDecompositionPrimitive2D(), + mxChildren3D(std::move(aChildren3D)), + maSdrSceneAttribute(std::move(aSdrSceneAttribute)), + maSdrLightingAttribute(std::move(aSdrLightingAttribute)), + maObjectTransformation(std::move(aObjectTransformation)), + maViewInformation3D(std::move(aViewInformation3D)), mbShadow3DChecked(false), mfOldDiscreteSizeX(0.0), mfOldDiscreteSizeY(0.0) { + // activate callback to flush buffered decomposition content + setCallbackSeconds(45); } bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const @@ -626,15 +681,13 @@ namespace drawinglayer::primitive2d void ScenePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard( m_aMutex ); - // get the involved ranges (see helper method calculateDiscreteSizes for details) basegfx::B2DRange aDiscreteRange; basegfx::B2DRange aUnitVisibleRange; bool bNeedNewDecomposition(false); bool bDiscreteSizesAreCalculated(false); - if(!getBuffered2DDecomposition().empty()) + if(getBuffered2DDecomposition()) { basegfx::B2DRange aVisibleDiscreteRange; calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); @@ -662,10 +715,10 @@ namespace drawinglayer::primitive2d if(bNeedNewDecomposition) { // conditions of last local decomposition have changed, delete - const_cast< ScenePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + const_cast< ScenePrimitive2D* >(this)->setBuffered2DDecomposition(nullptr); } - if(getBuffered2DDecomposition().empty()) + if(!getBuffered2DDecomposition()) { if(!bDiscreteSizesAreCalculated) { @@ -681,7 +734,6 @@ namespace drawinglayer::primitive2d } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx b/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx index 5080a92a8cd6..7768c1f6af2b 100644 --- a/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx +++ b/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx @@ -84,7 +84,7 @@ namespace drawinglayer::primitive2d if(bFilled) { xReference = new PolyPolygonColorPrimitive2D( - aScaledOutline, + std::move(aScaledOutline), basegfx::BColor(0.0, 0.0, 0.0)); } else @@ -92,13 +92,12 @@ namespace drawinglayer::primitive2d const basegfx::BColor aGrayTone(0xc0 / 255.0, 0xc0 / 255.0, 0xc0 / 255.0); xReference = new PolyPolygonHairlinePrimitive2D( - aScaledOutline, + std::move(aScaledOutline), aGrayTone); } // create HiddenGeometryPrimitive2D - return Primitive2DReference( - new HiddenGeometryPrimitive2D(Primitive2DContainer { xReference })); + return new HiddenGeometryPrimitive2D(Primitive2DContainer { xReference }); } } // end of namespace diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx index 6ea066b35754..5de34c5440b6 100644 --- a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx @@ -22,72 +22,382 @@ #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <drawinglayer/converters.hxx> +#include "GlowSoftEgdeShadowTools.hxx" + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif #include <memory> +#include <utility> using namespace com::sun::star; - namespace drawinglayer::primitive2d { - ShadowPrimitive2D::ShadowPrimitive2D( - const basegfx::B2DHomMatrix& rShadowTransform, - const basegfx::BColor& rShadowColor, - double fShadowBlur, - Primitive2DContainer&& aChildren) - : GroupPrimitive2D(std::move(aChildren)), - maShadowTransform(rShadowTransform), - maShadowColor(rShadowColor), - mfShadowBlur(fShadowBlur) +ShadowPrimitive2D::ShadowPrimitive2D(basegfx::B2DHomMatrix aShadowTransform, + const basegfx::BColor& rShadowColor, double fShadowBlur, + Primitive2DContainer&& aChildren) + : BufferedDecompositionGroupPrimitive2D(std::move(aChildren)) + , maShadowTransform(std::move(aShadowTransform)) + , maShadowColor(rShadowColor) + , mfShadowBlur(fShadowBlur) + , mfLastDiscreteBlurRadius(0.0) + , maLastClippedRange() +{ + // activate callback to flush buffered decomposition content + setCallbackSeconds(15); +} + +bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionGroupPrimitive2D::operator==(rPrimitive)) + { + const ShadowPrimitive2D& rCompare = static_cast<const ShadowPrimitive2D&>(rPrimitive); + + return (getShadowTransform() == rCompare.getShadowTransform() + && getShadowColor() == rCompare.getShadowColor() + && getShadowBlur() == rCompare.getShadowBlur()); + } + + return false; +} + +// Helper to get the to-be-shadowed geometry completely embedded to +// a ModifiedColorPrimitive2D (change to ShadowColor) and TransformPrimitive2D +// (direction/offset/transformation of shadow). Since this is used pretty +// often, pack into a helper +void ShadowPrimitive2D::getFullyEmbeddedShadowPrimitives(Primitive2DContainer& rContainer) const +{ + if (getChildren().empty()) + return; + + // create a modifiedColorPrimitive containing the shadow color and the content + const basegfx::BColorModifierSharedPtr aBColorModifier + = std::make_shared<basegfx::BColorModifier_replace>(getShadowColor()); + const Primitive2DReference xRefA( + new ModifiedColorPrimitive2D(Primitive2DContainer(getChildren()), aBColorModifier)); + Primitive2DContainer aSequenceB{ xRefA }; + + // build transformed primitiveVector with shadow offset and add to target + rContainer.visit(new TransformPrimitive2D(getShadowTransform(), std::move(aSequenceB))); +} + +bool ShadowPrimitive2D::prepareValuesAndcheckValidity( + basegfx::B2DRange& rBlurRange, basegfx::B2DRange& rClippedRange, + basegfx::B2DVector& rDiscreteBlurSize, double& rfDiscreteBlurRadius, + const geometry::ViewInformation2D& rViewInformation) const +{ + // no BlurRadius defined, done + if (getShadowBlur() <= 0.0) + return false; + + // no geometry, done + if (getChildren().empty()) + return false; + + // no pixel target, done + if (rViewInformation.getObjectToViewTransformation().isIdentity()) + return false; + + // get fully embedded ShadowPrimitive + Primitive2DContainer aEmbedded; + getFullyEmbeddedShadowPrimitives(aEmbedded); + + // get geometry range that defines area that needs to be pixelated + rBlurRange = aEmbedded.getB2DRange(rViewInformation); + + // no range of geometry, done + if (rBlurRange.isEmpty()) + return false; + + // extend range by BlurRadius in all directions + rBlurRange.grow(getShadowBlur()); + + // initialize ClippedRange to full BlurRange -> all is visible + rClippedRange = rBlurRange; + + // get Viewport and check if used. If empty, all is visible (see + // ViewInformation2D definition in viewinformation2d.hxx) + if (!rViewInformation.getViewport().isEmpty()) + { + // if used, extend by BlurRadius to ensure needed parts are included + basegfx::B2DRange aVisibleArea(rViewInformation.getViewport()); + aVisibleArea.grow(getShadowBlur()); + + // calculate ClippedRange + rClippedRange.intersect(aVisibleArea); + + // if BlurRange is completely outside of VisibleArea, ClippedRange + // will be empty and we are done + if (rClippedRange.isEmpty()) + return false; + } + + // calculate discrete pixel size of BlurRange. If it's too small to visualize, we are done + rDiscreteBlurSize = rViewInformation.getObjectToViewTransformation() * rBlurRange.getRange(); + if (ceil(rDiscreteBlurSize.getX()) < 2.0 || ceil(rDiscreteBlurSize.getY()) < 2.0) + return false; + + // calculate discrete pixel size of BlurRadius. If it's too small to visualize, we are done + rfDiscreteBlurRadius = ceil( + (rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(getShadowBlur(), 0)) + .getLength()); + if (rfDiscreteBlurRadius < 1.0) + return false; + + return true; +} + +void ShadowPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +{ + if (getShadowBlur() <= 0.0) + { + // Normal (non-blurred) shadow is already completely + // handled by get2DDecomposition and not buffered. It + // does not need to be since it's a simple embedding + // to a ModifiedColorPrimitive2D and TransformPrimitive2D + return; + } + + // from here on we process a blurred shadow + basegfx::B2DRange aBlurRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteBlurSize; + double fDiscreteBlurRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aBlurRange, aClippedRange, aDiscreteBlurSize, + fDiscreteBlurRadius, rViewInformation)) + return; + + // Create embedding transformation from object to top-left zero-aligned + // target pixel geometry (discrete form of ClippedRange) + // First, move to top-left of BlurRange + const sal_uInt32 nDiscreteBlurWidth(ceil(aDiscreteBlurSize.getX())); + const sal_uInt32 nDiscreteBlurHeight(ceil(aDiscreteBlurSize.getY())); + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aClippedRange.getMinX(), -aClippedRange.getMinY())); + // Second, scale to discrete bitmap size + // Even when using the offset from ClippedRange, we need to use the + // scaling from the full representation, thus from BlurRange + aEmbedding.scale(nDiscreteBlurWidth / aBlurRange.getWidth(), + nDiscreteBlurHeight / aBlurRange.getHeight()); + + // Get fully embedded ShadowPrimitives. This will also embed to + // ModifiedColorPrimitive2D (what is not urgently needed) to create + // the alpha channel, but a paint with all colors set to a single + // one (like shadowColor here) is often less expensive due to possible + // simplifications painting the primitives (e.g. gradient) + Primitive2DContainer aEmbedded; + getFullyEmbeddedShadowPrimitives(aEmbedded); + + // Embed content graphics to TransformPrimitive2D + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D(aEmbedding, std::move(aEmbedded))); + primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef }; + + // Create BitmapEx using drawinglayer tooling, including a MaximumQuadraticPixel + // limitation to be safe and not go runtime/memory havoc. Use a pretty small + // limit due to this is Blurred Shadow functionality and will look good with bitmap + // scaling anyways. The value of 250.000 square pixels below maybe adapted as needed. + const basegfx::B2DVector aDiscreteClippedSize(rViewInformation.getObjectToViewTransformation() + * aClippedRange.getRange()); + const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX())); + const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY())); + const geometry::ViewInformation2D aViewInformation2D; + const sal_uInt32 nMaximumQuadraticPixels(250000); + + // I have now added a helper that just creates the mask without having + // to render the content, use it, it's faster + const AlphaMask aAlpha(::drawinglayer::createAlphaMask( + std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight, + nMaximumQuadraticPixels)); + + // if we have no shadow, we are done + if (aAlpha.IsEmpty()) + return; + + const Size& rBitmapExSizePixel(aAlpha.GetSizePixel()); + if (!(rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0)) + return; + + // We may have to take a corrective scaling into account when the + // MaximumQuadraticPixel limit was used/triggered + double fScale(1.0); + + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) + { + // scale in X and Y should be the same (see fReduceFactor in createAlphaMask), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + fScale = (fScaleX + fScaleY) * 0.5; + } + + // Use the Alpha as base to blur and apply the effect + const AlphaMask mask(drawinglayer::primitive2d::ProcessAndBlurAlphaMask( + aAlpha, 0, fDiscreteBlurRadius * fScale, 0, false)); + + // The end result is the bitmap filled with blur color and blurred 8-bit alpha mask + Bitmap bmp(aAlpha.GetSizePixel(), vcl::PixelFormat::N24_BPP); + bmp.Erase(Color(getShadowColor())); + BitmapEx result(bmp, mask); + +#ifdef DBG_UTIL + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) { + SvFileStream aNew(sDumpPath + "test_shadowblur.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(result); } + } +#endif + + // Independent from discrete sizes of blur alpha creation, always + // map and project blur result to geometry range extended by blur + // radius, but to the eventually clipped instance (ClippedRange) + const primitive2d::Primitive2DReference xEmbedRefBitmap( + new BitmapPrimitive2D(result, basegfx::utils::createScaleTranslateB2DHomMatrix( + aClippedRange.getWidth(), aClippedRange.getHeight(), + aClippedRange.getMinX(), aClippedRange.getMinY()))); + + rContainer = primitive2d::Primitive2DContainer{ xEmbedRefBitmap }; +} + +void ShadowPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + if (getShadowBlur() <= 0.0) + { + // normal (non-blurred) shadow + if (getChildren().empty()) + return; + + // get fully embedded ShadowPrimitives + Primitive2DContainer aEmbedded; + getFullyEmbeddedShadowPrimitives(aEmbedded); + + rVisitor.visit(aEmbedded); + return; + } + + // here we have a blurred shadow, check conditions of last + // buffered decompose and decide re-use or re-create by using + // setBuffered2DDecomposition to reset local buffered version + basegfx::B2DRange aBlurRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteBlurSize; + double fDiscreteBlurRadius(0.0); - bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aBlurRange, aClippedRange, aDiscreteBlurSize, + fDiscreteBlurRadius, rViewInformation)) + return; + + if (!getBuffered2DDecomposition().empty()) + { + // First check is to detect if the last created decompose is capable + // to represent the now requested visualization (see similar + // implementation at GlowPrimitive2D). + if (!maLastClippedRange.isEmpty() && !maLastClippedRange.isInside(aClippedRange)) { - if(BasePrimitive2D::operator==(rPrimitive)) - { - const ShadowPrimitive2D& rCompare = static_cast< const ShadowPrimitive2D& >(rPrimitive); + basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange); - return (getShadowTransform() == rCompare.getShadowTransform() - && getShadowColor() == rCompare.getShadowColor() - && getShadowBlur() == rCompare.getShadowBlur()); + if (!rViewInformation.getObjectToViewTransformation().isIdentity()) + { + // Grow by view-dependent size of 1/2 pixel + const double fHalfPixel((rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(0.5, 0)) + .getLength()); + aLastClippedRangeAndHairline.grow(fHalfPixel); } - return false; + if (!aLastClippedRangeAndHairline.isInside(aClippedRange)) + { + // Conditions of last local decomposition have changed, delete + const_cast<ShadowPrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } } + } - basegfx::B2DRange ShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const - { - basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); - aRetval.grow(getShadowBlur()); - aRetval.transform(getShadowTransform()); - return aRetval; - } + if (!getBuffered2DDecomposition().empty()) + { + // Second check is to react on changes of the DiscreteSoftRadius when + // zooming in/out (see similar implementation at ShadowPrimitive2D). + bool bFree(mfLastDiscreteBlurRadius <= 0.0 || fDiscreteBlurRadius <= 0.0); - void ShadowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const + if (!bFree) { - if(getChildren().empty()) - return; - - // create a modifiedColorPrimitive containing the shadow color and the content - const basegfx::BColorModifierSharedPtr aBColorModifier = - std::make_shared<basegfx::BColorModifier_replace>( - getShadowColor()); - const Primitive2DReference xRefA( - new ModifiedColorPrimitive2D( - Primitive2DContainer(getChildren()), - aBColorModifier)); - Primitive2DContainer aSequenceB { xRefA }; - - // build transformed primitiveVector with shadow offset and add to target - rVisitor.append(new TransformPrimitive2D(getShadowTransform(), std::move(aSequenceB))); + const double fDiff(fabs(mfLastDiscreteBlurRadius - fDiscreteBlurRadius)); + const double fLen(fabs(mfLastDiscreteBlurRadius) + fabs(fDiscreteBlurRadius)); + const double fRelativeChange(fDiff / fLen); + + // Use lower fixed values here to change more often, higher to change less often. + // Value is in the range of ]0.0 .. 1.0] + bFree = fRelativeChange >= 0.15; } - // provide unique ID - sal_uInt32 ShadowPrimitive2D::getPrimitive2DID() const + if (bFree) { - return PRIMITIVE2D_ID_SHADOWPRIMITIVE2D; + // Conditions of last local decomposition have changed, delete + const_cast<ShadowPrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); } + } + + if (getBuffered2DDecomposition().empty()) + { + // refresh last used DiscreteBlurRadius and ClippedRange to new remembered values + const_cast<ShadowPrimitive2D*>(this)->mfLastDiscreteBlurRadius = fDiscreteBlurRadius; + const_cast<ShadowPrimitive2D*>(this)->maLastClippedRange = aClippedRange; + } + + // call parent, that will check for empty, call create2DDecomposition and + // set as decomposition + BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); +} + +basegfx::B2DRange +ShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // Hint: Do *not* use GroupPrimitive2D::getB2DRange, that will (unnecessarily) + // use the decompose - what works, but is not needed here. + // We know the to-be-visualized geometry and the radius it needs to be extended, + // so simply calculate the exact needed range. + basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); + + if (getShadowBlur() > 0.0) + { + // blurred shadow, that extends the geometry + aRetval.grow(getShadowBlur()); + } + + aRetval.transform(getShadowTransform()); + return aRetval; +} + +// provide unique ID +sal_uInt32 ShadowPrimitive2D::getPrimitive2DID() const { return PRIMITIVE2D_ID_SHADOWPRIMITIVE2D; } } // end of namespace diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx index 98a3bea752eb..e6f92f312f59 100644 --- a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx @@ -18,20 +18,34 @@ */ #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <drawinglayer/converters.hxx> +#include "GlowSoftEgdeShadowTools.hxx" + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif namespace drawinglayer::primitive2d { SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& aChildren) - : GroupPrimitive2D(std::move(aChildren)) + : BufferedDecompositionGroupPrimitive2D(std::move(aChildren)) , mfRadius(fRadius) + , mfLastDiscreteSoftRadius(0.0) + , maLastClippedRange() { + // activate callback to flush buffered decomposition content + setCallbackSeconds(15); } bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { - if (GroupPrimitive2D::operator==(rPrimitive)) + if (BufferedDecompositionGroupPrimitive2D::operator==(rPrimitive)) { auto& rCompare = static_cast<const SoftEdgePrimitive2D&>(rPrimitive); return getRadius() == rCompare.getRadius(); @@ -40,27 +54,300 @@ bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const return false; } -void SoftEdgePrimitive2D::get2DDecomposition( - Primitive2DDecompositionVisitor& rVisitor, +bool SoftEdgePrimitive2D::prepareValuesAndcheckValidity( + basegfx::B2DRange& rSoftRange, basegfx::B2DRange& rClippedRange, + basegfx::B2DVector& rDiscreteSoftSize, double& rfDiscreteSoftRadius, const geometry::ViewInformation2D& rViewInformation) const { + // no SoftRadius defined, done + if (getRadius() <= 0.0) + return false; + + // no geometry, done if (getChildren().empty()) + return false; + + // no pixel target, done + if (rViewInformation.getObjectToViewTransformation().isIdentity()) + return false; + + // get geometry range that defines area that needs to be pixelated + rSoftRange = getChildren().getB2DRange(rViewInformation); + + // no range of geometry, done + if (rSoftRange.isEmpty()) + return false; + + // initialize ClippedRange to full SoftRange -> all is visible + rClippedRange = rSoftRange; + + // get Viewport and check if used. If empty, all is visible (see + // ViewInformation2D definition in viewinformation2d.hxx) + if (!rViewInformation.getViewport().isEmpty()) + { + // if used, extend by SoftRadius to ensure needed parts are included + // that are not visible, but influence the visible parts + basegfx::B2DRange aVisibleArea(rViewInformation.getViewport()); + aVisibleArea.grow(getRadius() * 2); + + // To do this correctly, it needs to be done in discrete coordinates. + // The object may be transformed relative to the original# + // ObjectTransformation, e.g. when re-used in shadow + aVisibleArea.transform(rViewInformation.getViewTransformation()); + rClippedRange.transform(rViewInformation.getObjectToViewTransformation()); + + // calculate ClippedRange + rClippedRange.intersect(aVisibleArea); + + // if SoftRange is completely outside of VisibleArea, ClippedRange + // will be empty and we are done + if (rClippedRange.isEmpty()) + return false; + + // convert result back to object coordinates + rClippedRange.transform(rViewInformation.getInverseObjectToViewTransformation()); + } + + // calculate discrete pixel size of SoftRange. If it's too small to visualize, we are done + rDiscreteSoftSize = rViewInformation.getObjectToViewTransformation() * rSoftRange.getRange(); + if (ceil(rDiscreteSoftSize.getX()) < 2.0 || ceil(rDiscreteSoftSize.getY()) < 2.0) + return false; + + // calculate discrete pixel size of SoftRadius. If it's too small to visualize, we are done + rfDiscreteSoftRadius = ceil( + (rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(getRadius(), 0)) + .getLength()); + if (rfDiscreteSoftRadius < 1.0) + return false; + + return true; +} + +void SoftEdgePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +{ + // Use endless while-loop-and-break mechanism due to having multiple + // exit scenarios that all have to do the same thing when exiting + while (true) + { + basegfx::B2DRange aSoftRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteSoftSize; + double fDiscreteSoftRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aSoftRange, aClippedRange, aDiscreteSoftSize, + fDiscreteSoftRadius, rViewInformation)) + break; + + // Create embedding transformation from object to top-left zero-aligned + // target pixel geometry (discrete form of ClippedRange) + // First, move to top-left of SoftRange + const sal_uInt32 nDiscreteSoftWidth(ceil(aDiscreteSoftSize.getX())); + const sal_uInt32 nDiscreteSoftHeight(ceil(aDiscreteSoftSize.getY())); + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aClippedRange.getMinX(), -aClippedRange.getMinY())); + // Second, scale to discrete bitmap size + // Even when using the offset from ClippedRange, we need to use the + // scaling from the full representation, thus from SoftRange + aEmbedding.scale(nDiscreteSoftWidth / aSoftRange.getWidth(), + nDiscreteSoftHeight / aSoftRange.getHeight()); + + // Embed content graphics to TransformPrimitive2D + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D(aEmbedding, Primitive2DContainer(getChildren()))); + primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef }; + + // Create BitmapEx using drawinglayer tooling, including a MaximumQuadraticPixel + // limitation to be safe and not go runtime/memory havoc. Use a pretty small + // limit due to this is softEdge functionality and will look good with bitmap scaling + // anyways. The value of 250.000 square pixels below maybe adapted as needed. + const basegfx::B2DVector aDiscreteClippedSize( + rViewInformation.getObjectToViewTransformation() * aClippedRange.getRange()); + const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX())); + const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY())); + const geometry::ViewInformation2D aViewInformation2D; + const sal_uInt32 nMaximumQuadraticPixels(250000); + // tdf#156808 force an alpha mask to be created even if it has no alpha + // We need an alpha mask, even if it is totally opaque, so that + // drawinglayer::primitive2d::ProcessAndBlurAlphaMask() can be called. + // Otherwise, blurring of edges will fail in cases like running in a + // slideshow or exporting to PDF. + const BitmapEx aBitmapEx(::drawinglayer::convertToBitmapEx( + std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight, + nMaximumQuadraticPixels, true)); + + if (aBitmapEx.IsEmpty()) + break; + + // Get BitmapEx and check size. If no content, we are done + const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel()); + if (!(rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0)) + break; + + // We may have to take a corrective scaling into account when the + // MaximumQuadraticPixel limit was used/triggered + double fScale(1.0); + + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) + { + // scale in X and Y should be the same (see fReduceFactor in convertToBitmapEx), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + fScale = (fScaleX + fScaleY) * 0.5; + } + + // Get the Alpha and use as base to blur and apply the effect + AlphaMask aMask(aBitmapEx.GetAlphaMask()); + if (aMask.IsEmpty()) // There is no mask, fully opaque + break; + AlphaMask blurMask(drawinglayer::primitive2d::ProcessAndBlurAlphaMask( + aMask, -fDiscreteSoftRadius * fScale, fDiscreteSoftRadius * fScale, 0)); + aMask.BlendWith(blurMask); + + // The end result is the original bitmap with blurred 8-bit alpha mask + BitmapEx result(aBitmapEx.GetBitmap(), aMask); + +#ifdef DBG_UTIL + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_softedge.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(result); + } + } +#endif + + // Independent from discrete sizes of soft alpha creation, always + // map and project soft result to geometry range extended by soft + // radius, but to the eventually clipped instance (ClippedRange) + const primitive2d::Primitive2DReference xEmbedRefBitmap( + new BitmapPrimitive2D(result, basegfx::utils::createScaleTranslateB2DHomMatrix( + aClippedRange.getWidth(), aClippedRange.getHeight(), + aClippedRange.getMinX(), aClippedRange.getMinY()))); + + rContainer = primitive2d::Primitive2DContainer{ xEmbedRefBitmap }; + + // we made it, return return; + } + + // creation failed for some of many possible reasons, use original + // content, so the unmodified original geometry will be the result, + // just without any softEdge effect + rContainer = getChildren(); +} - if (!mbInMaskGeneration) +void SoftEdgePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + // Use endless while-loop-and-break mechanism due to having multiple + // exit scenarios that all have to do the same thing when exiting + while (true) { - GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + basegfx::B2DRange aSoftRange; + basegfx::B2DRange aClippedRange; + basegfx::B2DVector aDiscreteSoftSize; + double fDiscreteSoftRadius(0.0); + + // Check various validity details and calculate/prepare values. If false, we are done + if (!prepareValuesAndcheckValidity(aSoftRange, aClippedRange, aDiscreteSoftSize, + fDiscreteSoftRadius, rViewInformation)) + break; + + if (!getBuffered2DDecomposition().empty()) + { + // First check is to detect if the last created decompose is capable + // to represent the now requested visualization (see similar + // implementation at GlowPrimitive2D). + if (!maLastClippedRange.isEmpty() && !maLastClippedRange.isInside(aClippedRange)) + { + basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange); + + if (!rViewInformation.getObjectToViewTransformation().isIdentity()) + { + // Grow by view-dependent size of 1/2 pixel + const double fHalfPixel((rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(0.5, 0)) + .getLength()); + aLastClippedRangeAndHairline.grow(fHalfPixel); + } + + if (!aLastClippedRangeAndHairline.isInside(aClippedRange)) + { + // Conditions of last local decomposition have changed, delete + const_cast<SoftEdgePrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + } + } + + if (!getBuffered2DDecomposition().empty()) + { + // Second check is to react on changes of the DiscreteSoftRadius when + // zooming in/out (see similar implementation at GlowPrimitive2D). + bool bFree(mfLastDiscreteSoftRadius <= 0.0 || fDiscreteSoftRadius <= 0.0); + + if (!bFree) + { + const double fDiff(fabs(mfLastDiscreteSoftRadius - fDiscreteSoftRadius)); + const double fLen(fabs(mfLastDiscreteSoftRadius) + fabs(fDiscreteSoftRadius)); + const double fRelativeChange(fDiff / fLen); + + // Use a lower value here, soft edge keeps it's content so avoid that it gets too + // unsharp in the pixel visualization + // Value is in the range of ]0.0 .. 1.0] + bFree = fRelativeChange >= 0.075; + } + + if (bFree) + { + // Conditions of last local decomposition have changed, delete + const_cast<SoftEdgePrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + } + + if (getBuffered2DDecomposition().empty()) + { + // refresh last used DiscreteSoftRadius and ClippedRange to new remembered values + const_cast<SoftEdgePrimitive2D*>(this)->mfLastDiscreteSoftRadius = fDiscreteSoftRadius; + const_cast<SoftEdgePrimitive2D*>(this)->maLastClippedRange = aClippedRange; + } + + // call parent, that will check for empty, call create2DDecomposition and + // set as decomposition + BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + + // we made it, return return; } - // create a modifiedColorPrimitive containing the *black* color and the content. Using black - // on white allows creating useful mask in VclPixelProcessor2D::processSoftEdgePrimitive2D. - basegfx::BColorModifierSharedPtr aBColorModifier - = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor()); + // No soft edge needed for some of many possible reasons, use original content + rVisitor.visit(getChildren()); +} - const Primitive2DReference xRef( - new ModifiedColorPrimitive2D(Primitive2DContainer(getChildren()), aBColorModifier)); - rVisitor.append(xRef); +basegfx::B2DRange +SoftEdgePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // Hint: Do *not* use GroupPrimitive2D::getB2DRange, that will (unnecessarily) + // use the decompose - what works, but is not needed here. + // We know the to-be-visualized geometry and the radius it needs to be extended, + // so simply calculate the exact needed range. + return getChildren().getB2DRange(rViewInformation); } sal_uInt32 SoftEdgePrimitive2D::getPrimitive2DID() const diff --git a/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx index c82b0088e29d..783a54a4c409 100644 --- a/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx @@ -30,12 +30,21 @@ namespace drawinglayer::primitive2d const vcl::PDFWriter::StructElement& rStructureElement, bool bBackground, bool bIsImage, - Primitive2DContainer&& aChildren) + bool bIsDecorative, + Primitive2DContainer&& aChildren, + void const*const pAnchorStructureElementKey, + ::std::vector<sal_Int32> const*const pAnnotIds) : GroupPrimitive2D(std::move(aChildren)), maStructureElement(rStructureElement), mbBackground(bBackground), - mbIsImage(bIsImage) + mbIsImage(bIsImage), + mbIsDecorative(bIsDecorative) + , m_pAnchorStructureElementKey(pAnchorStructureElementKey) { + if (pAnnotIds) + { + m_AnnotIds = *pAnnotIds; + } } bool StructureTagPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const @@ -57,6 +66,13 @@ namespace drawinglayer::primitive2d return PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D; } + bool StructureTagPrimitive2D::isTaggedSdrObject() const + { + // note at the moment *all* StructureTagPrimitive2D are created for + // SdrObjects - if that ever changes, need another condition here + return !isBackground() || isImage(); + } + } // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx b/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx index 503d07688ecd..24f979ce2c9d 100644 --- a/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx @@ -28,8 +28,10 @@ #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> +#include <osl/diagnose.h> #include <sal/log.hxx> #include <cmath> +#include <utility> #include <vcl/skia/SkiaHelper.hxx> using namespace com::sun::star; @@ -61,7 +63,7 @@ namespace namespace drawinglayer::primitive2d { - void SvgGradientHelper::createSingleGradientEntryFill(Primitive2DContainer& rContainer) const + Primitive2DReference SvgGradientHelper::createSingleGradientEntryFill() const { const SvgGradientEntryVector& rEntries = getGradientEntries(); const sal_uInt32 nCount(rEntries.size()); @@ -82,19 +84,20 @@ namespace drawinglayer::primitive2d { Primitive2DContainer aContent { xRef }; - xRef = Primitive2DReference( + xRef = new UnifiedTransparencePrimitive2D( std::move(aContent), - 1.0 - fOpacity)); + 1.0 - fOpacity); } - rContainer.push_back(xRef); + return xRef; } } else { OSL_ENSURE(false, "Single gradient entry construction without entry (!)"); } + return nullptr; } void SvgGradientHelper::checkPreconditions() @@ -303,18 +306,17 @@ namespace drawinglayer::primitive2d } } - void SvgGradientHelper::createResult( - Primitive2DContainer& rContainer, - const Primitive2DContainer& rTargetColor, - const Primitive2DContainer& rTargetOpacity, + Primitive2DReference SvgGradientHelper::createResult( + Primitive2DContainer aTargetColor, + Primitive2DContainer aTargetOpacity, const basegfx::B2DHomMatrix& rUnitGradientToObject, bool bInvert) const { - Primitive2DContainer aTargetColorEntries(rTargetColor.maybeInvert(bInvert)); - Primitive2DContainer aTargetOpacityEntries(rTargetOpacity.maybeInvert(bInvert)); + Primitive2DContainer aTargetColorEntries(aTargetColor.maybeInvert(bInvert)); + Primitive2DContainer aTargetOpacityEntries(aTargetOpacity.maybeInvert(bInvert)); if(aTargetColorEntries.empty()) - return; + return nullptr; Primitive2DReference xRefContent; @@ -335,20 +337,20 @@ namespace drawinglayer::primitive2d std::move(aTargetColorEntries)); } - rContainer.push_back(new MaskPrimitive2D( + return new MaskPrimitive2D( getPolyPolygon(), - Primitive2DContainer { xRefContent })); + Primitive2DContainer { xRefContent }); } SvgGradientHelper::SvgGradientHelper( - const basegfx::B2DHomMatrix& rGradientTransform, - const basegfx::B2DPolyPolygon& rPolyPolygon, + basegfx::B2DHomMatrix aGradientTransform, + basegfx::B2DPolyPolygon aPolyPolygon, SvgGradientEntryVector&& rGradientEntries, const basegfx::B2DPoint& rStart, bool bUseUnitCoordinates, SpreadMethod aSpreadMethod) - : maGradientTransform(rGradientTransform), - maPolyPolygon(rPolyPolygon), + : maGradientTransform(std::move(aGradientTransform)), + maPolyPolygon(std::move(aPolyPolygon)), maGradientEntries(std::move(rGradientEntries)), maStart(rStart), maSpreadMethod(aSpreadMethod), @@ -464,7 +466,7 @@ namespace drawinglayer::primitive2d } } - void SvgLinearGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference SvgLinearGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if(!getPreconditionsChecked()) { @@ -474,7 +476,7 @@ namespace drawinglayer::primitive2d if(getSingleEntry()) { // fill with last existing color - createSingleGradientEntryFill(rContainer); + return createSingleGradientEntryFill(); } else if(getCreatesContent()) { @@ -537,7 +539,7 @@ namespace drawinglayer::primitive2d Primitive2DContainer aTargetColor; Primitive2DContainer aTargetOpacity; - if(basegfx::fTools::more(aUnitRange.getWidth(), 0.0)) + if(aUnitRange.getWidth() > 0.0) { // add a pre-multiply to aUnitGradientToObject to allow // multiplication of the polygon(xl, 0.0, xr, 1.0) @@ -554,8 +556,9 @@ namespace drawinglayer::primitive2d aUnitRange.getMaxX()); } - createResult(rContainer, aTargetColor, aTargetOpacity, aUnitGradientToObject); + return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject); } + return nullptr; } SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D( @@ -686,7 +689,7 @@ namespace drawinglayer::primitive2d } } - void SvgRadialGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference SvgRadialGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if(!getPreconditionsChecked()) { @@ -696,7 +699,7 @@ namespace drawinglayer::primitive2d if(getSingleEntry()) { // fill with last existing color - createSingleGradientEntryFill(rContainer); + return createSingleGradientEntryFill(); } else if(getCreatesContent()) { @@ -780,8 +783,9 @@ namespace drawinglayer::primitive2d fMax); } - createResult(rContainer, aTargetColor, aTargetOpacity, aUnitGradientToObject, true); + return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject, true); } + return nullptr; } SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D( @@ -816,22 +820,22 @@ namespace drawinglayer::primitive2d { const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); - if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper)) - { - const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive); + if(!pSvgGradientHelper || !SvgGradientHelper::operator==(*pSvgGradientHelper)) + return false; + + const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive); - if(getRadius() == rCompare.getRadius()) + if(getRadius() == rCompare.getRadius()) + { + if(isFocalSet() == rCompare.isFocalSet()) { - if(isFocalSet() == rCompare.isFocalSet()) + if(isFocalSet()) + { + return getFocal() == rCompare.getFocal(); + } + else { - if(isFocalSet()) - { - return getFocal() == rCompare.getFocal(); - } - else - { - return true; - } + return true; } } } @@ -858,12 +862,12 @@ namespace drawinglayer::primitive2d namespace drawinglayer::primitive2d { - void SvgLinearAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { const double fDelta(getOffsetB() - getOffsetA()); if(basegfx::fTools::equalZero(fDelta)) - return; + return nullptr; // use one discrete unit for overlap (one pixel) const double fDiscreteUnit(getDiscreteUnit()); @@ -897,15 +901,18 @@ namespace drawinglayer::primitive2d double fUnitScale(0.0); const double fUnitStep(1.0 / nSteps); + Primitive2DContainer aContainer; + aContainer.resize(nSteps); for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) { basegfx::B2DPolygon aNew(aPolygon); aNew.transform(basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0)); - rContainer.push_back(new PolyPolygonColorPrimitive2D( + aContainer[a] = new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(aNew), - basegfx::interpolate(getColorA(), getColorB(), fUnitScale))); + basegfx::interpolate(getColorA(), getColorB(), fUnitScale)); } + return new GroupPrimitive2D(std::move(aContainer)); } SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D( @@ -951,12 +958,12 @@ namespace drawinglayer::primitive2d namespace drawinglayer::primitive2d { - void SvgRadialAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { const double fDeltaScale(getScaleB() - getScaleA()); if(basegfx::fTools::equalZero(fDeltaScale)) - return; + return nullptr; // use one discrete unit for overlap (one pixel) const double fDiscreteUnit(getDiscreteUnit()); @@ -968,6 +975,8 @@ namespace drawinglayer::primitive2d double fUnitScale(0.0); const double fUnitStep(1.0 / nSteps); + Primitive2DContainer aContainer; + aContainer.resize(nSteps); for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) { basegfx::B2DHomMatrix aTransform; @@ -997,10 +1006,11 @@ namespace drawinglayer::primitive2d basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle()); aNew.transform(aTransform); - rContainer.push_back(new PolyPolygonColorPrimitive2D( + aContainer[a] = new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(aNew), - basegfx::interpolate(getColorB(), getColorA(), fUnitScale))); + basegfx::interpolate(getColorB(), getColorA(), fUnitScale)); } + return new GroupPrimitive2D(std::move(aContainer)); } SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( @@ -1060,24 +1070,24 @@ namespace drawinglayer::primitive2d bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { - if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) - { - const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive); + if(!DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + return false; - if(getColorA() == rCompare.getColorA() - && getColorB() == rCompare.getColorB() - && getScaleA() == rCompare.getScaleA() - && getScaleB() == rCompare.getScaleB()) + 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()) { - if(isTranslateSet() && rCompare.isTranslateSet()) - { - return (getTranslateA() == rCompare.getTranslateA() - && getTranslateB() == rCompare.getTranslateB()); - } - else if(!isTranslateSet() && !rCompare.isTranslateSet()) - { - return true; - } + return (getTranslateA() == rCompare.getTranslateA() + && getTranslateB() == rCompare.getTranslateB()); + } + else if(!isTranslateSet() && !rCompare.isTranslateSet()) + { + return true; } } diff --git a/drawinglayer/source/primitive2d/textbreakuphelper.cxx b/drawinglayer/source/primitive2d/textbreakuphelper.cxx index 9c4424b8d01e..8f92d9817a0e 100644 --- a/drawinglayer/source/primitive2d/textbreakuphelper.cxx +++ b/drawinglayer/source/primitive2d/textbreakuphelper.cxx @@ -58,6 +58,7 @@ namespace drawinglayer::primitive2d // prepare values for new portion basegfx::B2DHomMatrix aNewTransform; std::vector< double > aNewDXArray; + std::vector< sal_Bool > aNewKashidaArray; const bool bNewStartIsNotOldStart(nIndex > mrSource.getTextPosition()); if(!mbNoDXArray) @@ -68,6 +69,13 @@ namespace drawinglayer::primitive2d mrSource.getDXArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition())); } + if(!mbNoDXArray && !mrSource.getKashidaArray().empty()) + { + aNewKashidaArray = std::vector< sal_Bool >( + mrSource.getKashidaArray().begin() + (nIndex - mrSource.getTextPosition()), + mrSource.getKashidaArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition())); + } + if(bNewStartIsNotOldStart) { // needs to be moved to a new start position @@ -137,6 +145,7 @@ namespace drawinglayer::primitive2d nIndex, nLength, std::move(aNewDXArray), + std::move(aNewKashidaArray), mrSource.getFontAttribute(), mrSource.getLocale(), mrSource.getFontColor(), @@ -168,6 +177,7 @@ namespace drawinglayer::primitive2d nIndex, nLength, std::move(aNewDXArray), + std::move(aNewKashidaArray), mrSource.getFontAttribute(), mrSource.getLocale(), mrSource.getFontColor())); @@ -261,14 +271,14 @@ namespace drawinglayer::primitive2d mxResult = aTempResult; } - const Primitive2DContainer& TextBreakupHelper::getResult(BreakupUnit aBreakupUnit) const + Primitive2DContainer TextBreakupHelper::extractResult(BreakupUnit aBreakupUnit) { if(mxResult.empty()) { - const_cast< TextBreakupHelper* >(this)->breakup(aBreakupUnit); + breakup(aBreakupUnit); } - return mxResult; + return std::move(mxResult); } } // end of namespace diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx index 10cf07b4a8c0..07181bbf2a17 100644 --- a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx @@ -30,26 +30,41 @@ namespace drawinglayer::primitive2d { void TextDecoratedPortionPrimitive2D::impCreateGeometryContent( - std::vector< Primitive2DReference >& rTarget, + Primitive2DContainer& rTarget, basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, const OUString& rText, sal_Int32 nTextPosition, sal_Int32 nTextLength, const std::vector< double >& rDXArray, + const std::vector< sal_Bool >& rKashidaArray, const attribute::FontAttribute& rFontAttribute) const { // create the SimpleTextPrimitive needed in any case - rTarget.push_back(Primitive2DReference( + rTarget.push_back( new TextSimplePortionPrimitive2D( rDecTrans.getB2DHomMatrix(), rText, nTextPosition, nTextLength, std::vector(rDXArray), + std::vector(rKashidaArray), rFontAttribute, getLocale(), - getFontColor()))); + getFontColor())); + CreateDecorationGeometryContent(rTarget, rDecTrans, rText, + nTextPosition, nTextLength, + rDXArray); + } + + void TextDecoratedPortionPrimitive2D::CreateDecorationGeometryContent( + Primitive2DContainer& rTarget, + basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, + const OUString& rText, + sal_Int32 nTextPosition, + sal_Int32 nTextLength, + const std::vector< double >& rDXArray) const + { // see if something else needs to be done const bool bOverlineUsed(TEXT_LINE_NONE != getFontOverline()); const bool bUnderlineUsed(TEXT_LINE_NONE != getFontUnderline()); @@ -92,27 +107,27 @@ namespace drawinglayer::primitive2d if(bOverlineUsed) { // create primitive geometry for overline - rTarget.push_back(Primitive2DReference( + rTarget.push_back( new TextLinePrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, aTextLayouter.getOverlineOffset(), aTextLayouter.getOverlineHeight(), getFontOverline(), - getOverlineColor()))); + getOverlineColor())); } if(bUnderlineUsed) { // create primitive geometry for underline - rTarget.push_back(Primitive2DReference( + rTarget.push_back( new TextLinePrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, aTextLayouter.getUnderlineOffset(), aTextLayouter.getUnderlineHeight(), getFontUnderline(), - getTextlineColor()))); + getTextlineColor())); } if(!bStrikeoutUsed) @@ -124,45 +139,44 @@ namespace drawinglayer::primitive2d // strikeout with character const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); - rTarget.push_back(Primitive2DReference( + rTarget.push_back( new TextCharacterStrikeoutPrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, getFontColor(), aStrikeoutChar, getFontAttribute(), - getLocale()))); + getLocale())); } else { // strikeout with geometry - rTarget.push_back(Primitive2DReference( + rTarget.push_back( new TextGeometryStrikeoutPrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, getFontColor(), aTextLayouter.getUnderlineHeight(), aTextLayouter.getStrikeoutOffset(), - getTextStrikeout()))); + getTextStrikeout())); } // TODO: Handle Font Emphasis Above/Below } - void TextDecoratedPortionPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference TextDecoratedPortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if(getWordLineMode()) { // support for single word mode; split to single word primitives // using TextBreakupHelper - const TextBreakupHelper aTextBreakupHelper(*this); - const Primitive2DContainer& aBroken(aTextBreakupHelper.getResult(BreakupUnit::Word)); + TextBreakupHelper aTextBreakupHelper(*this); + Primitive2DContainer aBroken(aTextBreakupHelper.extractResult(BreakupUnit::Word)); if(!aBroken.empty()) { // was indeed split to several words, use as result - rContainer.insert(rContainer.end(), aBroken.begin(), aBroken.end()); - return; + return new GroupPrimitive2D(std::move(aBroken)); } else { @@ -170,7 +184,6 @@ namespace drawinglayer::primitive2d // decompose local entity } } - std::vector< Primitive2DReference > aNewPrimitives; basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); Primitive2DContainer aRetval; @@ -190,115 +203,113 @@ namespace drawinglayer::primitive2d getFontAttribute().getBiDiStrong()); // handle as one word - impCreateGeometryContent(aNewPrimitives, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); + impCreateGeometryContent(aRetval, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), getKashidaArray(), aNewFontAttribute); + + // Handle Shadow, Outline and TextRelief + if(aRetval.empty()) + return nullptr; - // convert to Primitive2DSequence - const sal_uInt32 nMemberCount(aNewPrimitives.size()); + // outline AND shadow depend on NO TextRelief (see dialog) + const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief()); + const bool bHasShadow(!bHasTextRelief && getShadow()); + const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline()); - if(nMemberCount) + if(bHasShadow || bHasTextRelief || bHasOutline) { - aRetval.resize(nMemberCount); + Primitive2DReference aShadow; - for(sal_uInt32 a(0); a < nMemberCount; a++) + if(bHasShadow) { - aRetval[a] = aNewPrimitives[a]; + // create shadow with current content (in aRetval). Text shadow + // is constant, relative to font size, rotated with the text and has a + // constant color. + // shadow parameter values + static const double fFactor(1.0 / 24.0); + const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); + static basegfx::BColor aShadowColor(0.3, 0.3, 0.3); + + // prepare shadow transform matrix + const basegfx::B2DHomMatrix aShadowTransform(basegfx::utils::createTranslateB2DHomMatrix( + fTextShadowOffset, fTextShadowOffset)); + + // create shadow primitive + aShadow = new ShadowPrimitive2D( + aShadowTransform, + aShadowColor, + 0, // fShadowBlur = 0, there's no blur for text shadow yet. + Primitive2DContainer(aRetval)); } - } - - // Handle Shadow, Outline and TextRelief - if(!aRetval.empty()) - { - // outline AND shadow depend on NO TextRelief (see dialog) - const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief()); - const bool bHasShadow(!bHasTextRelief && getShadow()); - const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline()); - if(bHasShadow || bHasTextRelief || bHasOutline) + if(bHasTextRelief) { - Primitive2DReference aShadow; + // create emboss using an own helper primitive since this will + // be view-dependent + const basegfx::BColor aBBlack(0.0, 0.0, 0.0); + const bool bDefaultTextColor(aBBlack == getFontColor()); + TextEffectStyle2D aTextEffectStyle2D(TextEffectStyle2D::ReliefEmbossed); - if(bHasShadow) + if(bDefaultTextColor) { - // create shadow with current content (in aRetval). Text shadow - // is constant, relative to font size, rotated with the text and has a - // constant color. - // shadow parameter values - static const double fFactor(1.0 / 24.0); - const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); - static basegfx::BColor aShadowColor(0.3, 0.3, 0.3); - - // prepare shadow transform matrix - const basegfx::B2DHomMatrix aShadowTransform(basegfx::utils::createTranslateB2DHomMatrix( - fTextShadowOffset, fTextShadowOffset)); - - // create shadow primitive - aShadow = new ShadowPrimitive2D( - aShadowTransform, - aShadowColor, - 0, // fShadowBlur = 0, there's no blur for text shadow yet. - Primitive2DContainer(aRetval)); - } - - if(bHasTextRelief) - { - // create emboss using an own helper primitive since this will - // be view-dependent - const basegfx::BColor aBBlack(0.0, 0.0, 0.0); - const bool bDefaultTextColor(aBBlack == getFontColor()); - TextEffectStyle2D aTextEffectStyle2D(TextEffectStyle2D::ReliefEmbossed); - - if(bDefaultTextColor) + if(TEXT_RELIEF_ENGRAVED == getTextRelief()) { - if(TEXT_RELIEF_ENGRAVED == getTextRelief()) - { - aTextEffectStyle2D = TextEffectStyle2D::ReliefEngravedDefault; - } - else - { - aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossedDefault; - } + aTextEffectStyle2D = TextEffectStyle2D::ReliefEngravedDefault; } else { - if(TEXT_RELIEF_ENGRAVED == getTextRelief()) - { - aTextEffectStyle2D = TextEffectStyle2D::ReliefEngraved; - } - else - { - aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossed; - } + aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossedDefault; } - Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( - std::move(aRetval), - aDecTrans.getTranslate(), - aDecTrans.getRotate(), - aTextEffectStyle2D)); - aRetval = Primitive2DContainer { aNewTextEffect }; + aRetval = Primitive2DContainer { + new TextEffectPrimitive2D( + std::move(aRetval), + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + aTextEffectStyle2D) + }; } - else if(bHasOutline) + else { // create outline using an own helper primitive since this will // be view-dependent - Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + aRetval = Primitive2DContainer { + new TextEffectPrimitive2D( + std::move(aRetval), + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + TextEffectStyle2D::Outline) + }; + } + + aRetval = Primitive2DContainer { + Primitive2DReference(new TextEffectPrimitive2D( std::move(aRetval), aDecTrans.getTranslate(), aDecTrans.getRotate(), - TextEffectStyle2D::Outline)); - aRetval = Primitive2DContainer { aNewTextEffect }; - } + aTextEffectStyle2D)) + }; + } + else if(bHasOutline) + { + // create outline using an own helper primitive since this will + // be view-dependent + aRetval = Primitive2DContainer { + Primitive2DReference(new TextEffectPrimitive2D( + std::move(aRetval), + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + TextEffectStyle2D::Outline)) + }; + } - if(aShadow.is()) - { - // put shadow in front if there is one to paint timely before - // but placed behind content - aRetval.insert(aRetval.begin(), aShadow); - } + if(aShadow.is()) + { + // put shadow in front if there is one to paint timely before + // but placed behind content + aRetval.insert(aRetval.begin(), aShadow); } } - rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + return new GroupPrimitive2D(std::move(aRetval)); } TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D( @@ -308,6 +319,7 @@ namespace drawinglayer::primitive2d sal_Int32 nTextPosition, sal_Int32 nTextLength, std::vector< double >&& rDXArray, + std::vector< sal_Bool >&& rKashidaArray, const attribute::FontAttribute& rFontAttribute, const css::lang::Locale& rLocale, const basegfx::BColor& rFontColor, @@ -326,7 +338,7 @@ namespace drawinglayer::primitive2d bool bEmphasisMarkBelow, TextRelief eTextRelief, bool bShadow) - : TextSimplePortionPrimitive2D(rNewTransform, rText, nTextPosition, nTextLength, std::move(rDXArray), rFontAttribute, rLocale, rFontColor, false, 0, rFillColor), + : TextSimplePortionPrimitive2D(rNewTransform, rText, nTextPosition, nTextLength, std::move(rDXArray), std::move(rKashidaArray), rFontAttribute, rLocale, rFontColor, false, 0, rFillColor), maOverlineColor(rOverlineColor), maTextlineColor(rTextlineColor), meFontOverline(eFontOverline), diff --git a/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx b/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx index d05e727ce50c..bd123d34315d 100644 --- a/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx @@ -28,8 +28,8 @@ namespace drawinglayer::primitive2d { const double fDiscreteSize(1.1); -void TextEffectPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +Primitive2DReference TextEffectPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& rViewInformation) const { // get the distance of one discrete units from target display. Use between 1.0 and sqrt(2) to // have good results on rotated objects, too @@ -37,10 +37,10 @@ void TextEffectPrimitive2D::create2DDecomposition( * basegfx::B2DVector(fDiscreteSize, fDiscreteSize)); const basegfx::B2DVector aDiagonalDistance(aDistance * (1.0 / 1.44)); + Primitive2DContainer aContainer; switch (getTextEffectStyle2D()) { case TextEffectStyle2D::ReliefEmbossed: - case TextEffectStyle2D::ReliefEngraved: case TextEffectStyle2D::ReliefEmbossedDefault: case TextEffectStyle2D::ReliefEngravedDefault: { @@ -84,14 +84,14 @@ void TextEffectPrimitive2D::create2DDecomposition( const Primitive2DReference xModifiedColor(new ModifiedColorPrimitive2D( Primitive2DContainer(getTextContent()), aBColorModifierToGray)); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer{ xModifiedColor })); // add original, too const basegfx::BColorModifierSharedPtr aBColorModifierToWhite = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(1.0)); - rContainer.push_back(new ModifiedColorPrimitive2D( + aContainer.push_back(new ModifiedColorPrimitive2D( Primitive2DContainer(getTextContent()), aBColorModifierToWhite)); } else @@ -103,11 +103,11 @@ void TextEffectPrimitive2D::create2DDecomposition( const Primitive2DReference xModifiedColor(new ModifiedColorPrimitive2D( Primitive2DContainer(getTextContent()), aBColorModifierToGray)); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer{ xModifiedColor })); // add original, too - rContainer.push_back(new GroupPrimitive2D(Primitive2DContainer(getTextContent()))); + aContainer.push_back(new GroupPrimitive2D(Primitive2DContainer(getTextContent()))); } break; @@ -119,53 +119,54 @@ void TextEffectPrimitive2D::create2DDecomposition( aTransform.set(0, 2, aDistance.getX()); aTransform.set(1, 2, 0.0); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, aDiagonalDistance.getX()); aTransform.set(1, 2, aDiagonalDistance.getY()); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, 0.0); aTransform.set(1, 2, aDistance.getY()); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, -aDiagonalDistance.getX()); aTransform.set(1, 2, aDiagonalDistance.getY()); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, -aDistance.getX()); aTransform.set(1, 2, 0.0); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, -aDiagonalDistance.getX()); aTransform.set(1, 2, -aDiagonalDistance.getY()); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, 0.0); aTransform.set(1, 2, -aDistance.getY()); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); aTransform.set(0, 2, aDiagonalDistance.getX()); aTransform.set(1, 2, -aDiagonalDistance.getY()); - rContainer.push_back( + aContainer.push_back( new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); // at last, place original over it, but force to white const basegfx::BColorModifierSharedPtr aBColorModifierToWhite = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(1.0, 1.0, 1.0)); - rContainer.push_back(new ModifiedColorPrimitive2D( + aContainer.push_back(new ModifiedColorPrimitive2D( Primitive2DContainer(getTextContent()), aBColorModifierToWhite)); break; } } + return new GroupPrimitive2D(std::move(aContainer)); } TextEffectPrimitive2D::TextEffectPrimitive2D(Primitive2DContainer&& rTextContent, @@ -213,19 +214,16 @@ void TextEffectPrimitive2D::get2DDecomposition( Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - std::unique_lock aGuard(m_aMutex); - - if (!getBuffered2DDecomposition().empty()) + if (getBuffered2DDecomposition()) { if (maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) { // conditions of last local decomposition have changed, delete - const_cast<TextEffectPrimitive2D*>(this)->setBuffered2DDecomposition( - Primitive2DContainer()); + const_cast<TextEffectPrimitive2D*>(this)->setBuffered2DDecomposition(nullptr); } } - if (getBuffered2DDecomposition().empty()) + if (!getBuffered2DDecomposition()) { // remember ViewRange and ViewTransformation const_cast<TextEffectPrimitive2D*>(this)->maLastObjectToViewTransformation @@ -233,7 +231,6 @@ void TextEffectPrimitive2D::get2DDecomposition( } // use parent implementation - aGuard.unlock(); BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } diff --git a/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx b/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx index c7af9562adc1..655918904cfb 100644 --- a/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx @@ -136,9 +136,18 @@ namespace drawinglayer::primitive2d } - TextHierarchyEditPrimitive2D::TextHierarchyEditPrimitive2D(Primitive2DContainer&& aChildren) - : GroupPrimitive2D(std::move(aChildren)) + TextHierarchyEditPrimitive2D::TextHierarchyEditPrimitive2D(Primitive2DContainer&& aContent) + : GroupPrimitive2D(std::move(aContent)) + { + } + + void TextHierarchyEditPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const { + // check if TextEdit is active. If not, process. If yes, suppress the content + if (!rViewInformation.getTextEditActive()) + GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); } // provide unique ID diff --git a/drawinglayer/source/primitive2d/textlayoutdevice.cxx b/drawinglayer/source/primitive2d/textlayoutdevice.cxx index 60370e722941..3daecb4bec62 100644 --- a/drawinglayer/source/primitive2d/textlayoutdevice.cxx +++ b/drawinglayer/source/primitive2d/textlayoutdevice.cxx @@ -22,6 +22,8 @@ #include <algorithm> #include <com/sun/star/uno/XComponentContext.hpp> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> #include <drawinglayer/attribute/fontattribute.hxx> #include <drawinglayer/primitive2d/textlayoutdevice.hxx> #include <comphelper/processfactory.hxx> @@ -29,6 +31,7 @@ #include <osl/diagnose.h> #include <tools/gen.hxx> #include <vcl/canvastools.hxx> +#include <vcl/kernarray.hxx> #include <vcl/timer.hxx> #include <vcl/virdev.hxx> #include <vcl/font.hxx> @@ -161,62 +164,84 @@ TextLayouterDevice::TextLayouterDevice() TextLayouterDevice::~TextLayouterDevice() COVERITY_NOEXCEPT_FALSE { releaseGlobalVirtualDevice(); } -void TextLayouterDevice::setFont(const vcl::Font& rFont) { mrDevice.SetFont(rFont); } +void TextLayouterDevice::setFont(const vcl::Font& rFont) +{ + mrDevice.SetFont(rFont); + mnFontScalingFixX = 1.0; + mnFontScalingFixY = 1.0; +} void TextLayouterDevice::setFontAttribute(const attribute::FontAttribute& rFontAttribute, double fFontScaleX, double fFontScaleY, const css::lang::Locale& rLocale) { - setFont(getVclFontFromFontAttribute(rFontAttribute, fFontScaleX, fFontScaleY, 0.0, rLocale)); + vcl::Font aFont + = getVclFontFromFontAttribute(rFontAttribute, fFontScaleX, fFontScaleY, 0.0, rLocale); + setFont(aFont); + Size aFontSize = aFont.GetFontSize(); + if (aFontSize.Height()) + { + mnFontScalingFixY = fFontScaleY / aFontSize.Height(); + // aFontSize.Width() is 0 for uninformly scaled fonts: see getVclFontFromFontAttribute + mnFontScalingFixX + = fFontScaleX / (aFontSize.Width() ? aFontSize.Width() : aFontSize.Height()); + } + else + { + mnFontScalingFixX = mnFontScalingFixY = 1.0; + } } double TextLayouterDevice::getOverlineOffset() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); double fRet = (rMetric.GetInternalLeading() / 2.0) - rMetric.GetAscent(); - return fRet; + return fRet * mnFontScalingFixY; } double TextLayouterDevice::getUnderlineOffset() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); double fRet = rMetric.GetDescent() / 2.0; - return fRet; + return fRet * mnFontScalingFixY; } double TextLayouterDevice::getStrikeoutOffset() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); double fRet = (rMetric.GetAscent() - rMetric.GetInternalLeading()) / 3.0; - return fRet; + return fRet * mnFontScalingFixY; } double TextLayouterDevice::getOverlineHeight() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); double fRet = rMetric.GetInternalLeading() / 2.5; - return fRet; + return fRet * mnFontScalingFixY; } double TextLayouterDevice::getUnderlineHeight() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); double fRet = rMetric.GetDescent() / 4.0; - return fRet; + return fRet * mnFontScalingFixY; } -double TextLayouterDevice::getTextHeight() const { return mrDevice.GetTextHeight(); } +double TextLayouterDevice::getTextHeight() const +{ + return mrDevice.GetTextHeightDouble() * mnFontScalingFixY; +} double TextLayouterDevice::getTextWidth(const OUString& rText, sal_uInt32 nIndex, sal_uInt32 nLength) const { - return mrDevice.GetTextWidth(rText, nIndex, nLength); + return mrDevice.GetTextWidthDouble(rText, nIndex, nLength) * mnFontScalingFixX; } void TextLayouterDevice::getTextOutlines(basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector, const OUString& rText, sal_uInt32 nIndex, - sal_uInt32 nLength, - const std::vector<double>& rDXArray) const + sal_uInt32 nLength, const std::vector<double>& rDXArray, + const std::vector<sal_Bool>& rKashidaArray) const { const sal_uInt32 nDXArrayCount(rDXArray.size()); sal_uInt32 nTextLength(nLength); @@ -231,20 +256,26 @@ void TextLayouterDevice::getTextOutlines(basegfx::B2DPolyPolygonVector& rB2DPoly { OSL_ENSURE(nDXArrayCount == nTextLength, "DXArray size does not correspond to text portion size (!)"); - std::vector<sal_Int32> aIntegerDXArray(nDXArrayCount); + KernArray aIntegerDXArray; + aIntegerDXArray.reserve(nDXArrayCount); for (sal_uInt32 a(0); a < nDXArrayCount; a++) - { - aIntegerDXArray[a] = basegfx::fround(rDXArray[a]); - } + aIntegerDXArray.push_back(basegfx::fround(rDXArray[a])); mrDevice.GetTextOutlines(rB2DPolyPolyVector, rText, nIndex, nIndex, nLength, 0, - aIntegerDXArray); + aIntegerDXArray, rKashidaArray); } else { mrDevice.GetTextOutlines(rB2DPolyPolyVector, rText, nIndex, nIndex, nLength); } + if (!rtl_math_approxEqual(mnFontScalingFixY, 1.0) + || !rtl_math_approxEqual(mnFontScalingFixX, 1.0)) + { + auto scale = basegfx::utils::createScaleB2DHomMatrix(mnFontScalingFixX, mnFontScalingFixY); + for (auto& poly : rB2DPolyPolyVector) + poly.transform(scale); + } } basegfx::B2DRange TextLayouterDevice::getTextBoundRect(const OUString& rText, sal_uInt32 nIndex, @@ -260,15 +291,15 @@ basegfx::B2DRange TextLayouterDevice::getTextBoundRect(const OUString& rText, sa if (nTextLength) { - ::tools::Rectangle aRect; - + basegfx::B2DRange aRect; mrDevice.GetTextBoundRect(aRect, rText, nIndex, nIndex, nLength); - - // #i104432#, #i102556# take empty results into account - if (!aRect.IsEmpty()) + if (!rtl_math_approxEqual(mnFontScalingFixY, 1.0) + || !rtl_math_approxEqual(mnFontScalingFixX, 1.0)) { - return vcl::unotools::b2DRectangleFromRectangle(aRect); + aRect.transform( + basegfx::utils::createScaleB2DHomMatrix(mnFontScalingFixX, mnFontScalingFixY)); } + return aRect; } return basegfx::B2DRange(); @@ -277,13 +308,13 @@ basegfx::B2DRange TextLayouterDevice::getTextBoundRect(const OUString& rText, sa double TextLayouterDevice::getFontAscent() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); - return rMetric.GetAscent(); + return rMetric.GetAscent() * mnFontScalingFixY; } double TextLayouterDevice::getFontDescent() const { const ::FontMetric& rMetric = mrDevice.GetFontMetric(); - return rMetric.GetDescent(); + return rMetric.GetDescent() * mnFontScalingFixY; } void TextLayouterDevice::addTextRectActions(const ::tools::Rectangle& rRectangle, @@ -294,30 +325,7 @@ void TextLayouterDevice::addTextRectActions(const ::tools::Rectangle& rRectangle } std::vector<double> TextLayouterDevice::getTextArray(const OUString& rText, sal_uInt32 nIndex, - sal_uInt32 nLength) const -{ - std::vector<double> aRetval; - sal_uInt32 nTextLength(nLength); - const sal_uInt32 nStringLength(rText.getLength()); - - if (nTextLength + nIndex > nStringLength) - { - nTextLength = nStringLength - nIndex; - } - - if (nTextLength) - { - aRetval.reserve(nTextLength); - std::vector<sal_Int32> aArray(nTextLength); - mrDevice.GetTextArray(rText, &aArray, nIndex, nLength); - aRetval.assign(aArray.begin(), aArray.end()); - } - - return aRetval; -} - -std::vector<double> TextLayouterDevice::getCaretPositions(const OUString& rText, sal_uInt32 nIndex, - sal_uInt32 nLength) const + sal_uInt32 nLength, bool bCaret) const { std::vector<double> aRetval; sal_uInt32 nTextLength(nLength); @@ -330,10 +338,11 @@ std::vector<double> TextLayouterDevice::getCaretPositions(const OUString& rText, if (nTextLength) { - aRetval.reserve(2 * nTextLength); - std::vector<sal_Int32> aArray(2 * nTextLength); - mrDevice.GetCaretPositions(rText, aArray.data(), nIndex, nLength); - aRetval.assign(aArray.begin(), aArray.end()); + KernArray aArray; + mrDevice.GetTextArray(rText, &aArray, nIndex, nTextLength, bCaret); + aRetval.reserve(aArray.size()); + for (size_t i = 0, nEnd = aArray.size(); i < nEnd; ++i) + aRetval.push_back(aArray[i] * mnFontScalingFixX); } return aRetval; diff --git a/drawinglayer/source/primitive2d/textlineprimitive2d.cxx b/drawinglayer/source/primitive2d/textlineprimitive2d.cxx index ea23ad25b684..496af0706520 100644 --- a/drawinglayer/source/primitive2d/textlineprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textlineprimitive2d.cxx @@ -22,16 +22,17 @@ #include <drawinglayer/attribute/strokeattribute.hxx> #include <drawinglayer/attribute/lineattribute.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> +#include <utility> namespace drawinglayer::primitive2d { - void TextLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference TextLinePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { if(TEXT_LINE_NONE == getTextLine()) - return; + return nullptr; bool bDoubleLine(false); bool bWaveLine(false); @@ -207,18 +208,19 @@ namespace drawinglayer::primitive2d fWaveWidth *= 2.0; } - aNewPrimitive = Primitive2DReference(new PolygonWavePrimitive2D(aLine, aLineAttribute, aStrokeAttribute, fWaveWidth, fWaveWidth * 0.5)); + aNewPrimitive = new PolygonWavePrimitive2D(aLine, aLineAttribute, aStrokeAttribute, fWaveWidth, fWaveWidth * 0.5); } else { - aNewPrimitive = Primitive2DReference(new PolygonStrokePrimitive2D(aLine, aLineAttribute, aStrokeAttribute)); + aNewPrimitive = new PolygonStrokePrimitive2D(std::move(aLine), aLineAttribute, std::move(aStrokeAttribute)); } - // add primitive - rContainer.push_back(aNewPrimitive); - if(!bDoubleLine) - return; + return aNewPrimitive; + + // add primitive + Primitive2DContainer aContainer; + aContainer.push_back(aNewPrimitive); // double line, create 2nd primitive with offset using TransformPrimitive based on // already created NewPrimitive @@ -243,17 +245,18 @@ namespace drawinglayer::primitive2d // add transform primitive Primitive2DContainer aContent { aNewPrimitive }; - rContainer.push_back( new TransformPrimitive2D(aTransform, std::move(aContent)) ); + aContainer.push_back(new TransformPrimitive2D(aTransform, std::move(aContent))); + return new GroupPrimitive2D(std::move(aContainer)); } TextLinePrimitive2D::TextLinePrimitive2D( - const basegfx::B2DHomMatrix& rObjectTransformation, + basegfx::B2DHomMatrix aObjectTransformation, double fWidth, double fOffset, double fHeight, TextLine eTextLine, const basegfx::BColor& rLineColor) - : maObjectTransformation(rObjectTransformation), + : maObjectTransformation(std::move(aObjectTransformation)), mfWidth(fWidth), mfOffset(fOffset), mfHeight(fHeight), diff --git a/drawinglayer/source/primitive2d/textprimitive2d.cxx b/drawinglayer/source/primitive2d/textprimitive2d.cxx index 4e584f2b6e92..2d91785c5287 100644 --- a/drawinglayer/source/primitive2d/textprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textprimitive2d.cxx @@ -22,8 +22,11 @@ #include <basegfx/polygon/b2dpolypolygon.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/groupprimitive2d.hxx> #include <primitive2d/texteffectprimitive2d.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <utility> +#include <osl/diagnose.h> using namespace com::sun::star; @@ -50,7 +53,7 @@ basegfx::B2DVector getCorrectedScaleAndFontScale(basegfx::B2DVector& rScale) rScale.setY(1.0 / fDefaultFontScale); aFontScale.setY(fDefaultFontScale); } - else if (basegfx::fTools::less(aFontScale.getY(), 0.0)) + else if (aFontScale.getY() < 0.0) { // negative font height; invert and adapt scale to get back to original scaling aFontScale.setY(-aFontScale.getY()); @@ -98,7 +101,7 @@ void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation( // handle special case: If scale is negative in (x,y) (3rd quadrant), it can // be expressed as rotation by PI - if (basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0)) + if (aScale.getX() < 0.0 && aScale.getY() < 0.0) { aScale = basegfx::absolute(aScale); fRotate += M_PI; @@ -130,13 +133,13 @@ void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation( // get the text outlines aTextLayouter.getTextOutlines(rTarget, getText(), getTextPosition(), getTextLength(), - aScaledDXArray); + aScaledDXArray, getKashidaArray()); } else { // get the text outlines aTextLayouter.getTextOutlines(rTarget, getText(), getTextPosition(), getTextLength(), - getDXArray()); + getDXArray(), getKashidaArray()); } // create primitives for the outlines @@ -150,13 +153,12 @@ void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation( } } -void TextSimplePortionPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +Primitive2DReference TextSimplePortionPrimitive2D::create2DDecomposition( + const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getTextLength()) - return; + return nullptr; - Primitive2DContainer aRetval; basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; basegfx::B2DHomMatrix aPolygonTransform; @@ -167,9 +169,10 @@ void TextSimplePortionPrimitive2D::create2DDecomposition( const sal_uInt32 nCount(aB2DPolyPolyVector.size()); if (!nCount) - return; + return nullptr; // alloc space for the primitives + Primitive2DContainer aRetval; aRetval.resize(nCount); // color-filled polypolygons @@ -189,28 +192,27 @@ void TextSimplePortionPrimitive2D::create2DDecomposition( aPolygonTransform.decompose(aScale, aTranslate, fRotate, fShearX); // create outline text effect with current content and replace - Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( - std::move(aRetval), aTranslate, fRotate, TextEffectStyle2D::Outline)); - - aRetval = Primitive2DContainer{ aNewTextEffect }; + return new TextEffectPrimitive2D(std::move(aRetval), aTranslate, fRotate, + TextEffectStyle2D::Outline); } - rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + return new GroupPrimitive2D(std::move(aRetval)); } TextSimplePortionPrimitive2D::TextSimplePortionPrimitive2D( - const basegfx::B2DHomMatrix& rNewTransform, const OUString& rText, sal_Int32 nTextPosition, - sal_Int32 nTextLength, std::vector<double>&& rDXArray, - const attribute::FontAttribute& rFontAttribute, const css::lang::Locale& rLocale, + basegfx::B2DHomMatrix rNewTransform, OUString rText, sal_Int32 nTextPosition, + sal_Int32 nTextLength, std::vector<double>&& rDXArray, std::vector<sal_Bool>&& rKashidaArray, + attribute::FontAttribute aFontAttribute, css::lang::Locale aLocale, const basegfx::BColor& rFontColor, bool bFilled, tools::Long nWidthToFill, const Color& rTextFillColor) - : maTextTransform(rNewTransform) - , maText(rText) + : maTextTransform(std::move(rNewTransform)) + , maText(std::move(rText)) , mnTextPosition(nTextPosition) , mnTextLength(nTextLength) , maDXArray(std::move(rDXArray)) - , maFontAttribute(rFontAttribute) - , maLocale(rLocale) + , maKashidaArray(std::move(rKashidaArray)) + , maFontAttribute(std::move(aFontAttribute)) + , maLocale(std::move(aLocale)) , maFontColor(rFontColor) , mbFilled(bFilled) , mnWidthToFill(nWidthToFill) @@ -240,6 +242,7 @@ bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) && getTextPosition() == rCompare.getTextPosition() && getTextLength() == rCompare.getTextLength() && getDXArray() == rCompare.getDXArray() + && getKashidaArray() == rCompare.getKashidaArray() && getFontAttribute() == rCompare.getFontAttribute() && LocalesAreEqual(getLocale(), rCompare.getLocale()) && getFontColor() == rCompare.getFontColor() && mbFilled == rCompare.mbFilled diff --git a/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx index 1c4983a87c18..41f7299c4486 100644 --- a/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx @@ -24,18 +24,20 @@ #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> #include <drawinglayer/attribute/lineattribute.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <osl/diagnose.h> #include <rtl/ustrbuf.hxx> +#include <utility> namespace drawinglayer::primitive2d { BaseTextStrikeoutPrimitive2D::BaseTextStrikeoutPrimitive2D( - const basegfx::B2DHomMatrix& rObjectTransformation, + basegfx::B2DHomMatrix aObjectTransformation, double fWidth, const basegfx::BColor& rFontColor) - : maObjectTransformation(rObjectTransformation), + : maObjectTransformation(std::move(aObjectTransformation)), mfWidth(fWidth), maFontColor(rFontColor) { @@ -56,7 +58,7 @@ namespace drawinglayer::primitive2d } - void TextCharacterStrikeoutPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference TextCharacterStrikeoutPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { // strikeout with character const OUString aSingleCharString(getStrikeoutChar()); @@ -88,16 +90,17 @@ namespace drawinglayer::primitive2d } auto len = aStrikeoutString.getLength(); - rContainer.push_back( + return new TextSimplePortionPrimitive2D( getObjectTransformation(), aStrikeoutString.makeStringAndClear(), 0, len, std::move(aDXArray), + {}, getFontAttribute(), getLocale(), - getFontColor())); + getFontColor()); } TextCharacterStrikeoutPrimitive2D::TextCharacterStrikeoutPrimitive2D( @@ -105,12 +108,12 @@ namespace drawinglayer::primitive2d double fWidth, const basegfx::BColor& rFontColor, sal_Unicode aStrikeoutChar, - const attribute::FontAttribute& rFontAttribute, - const css::lang::Locale& rLocale) + attribute::FontAttribute aFontAttribute, + css::lang::Locale aLocale) : BaseTextStrikeoutPrimitive2D(rObjectTransformation, fWidth, rFontColor), maStrikeoutChar(aStrikeoutChar), - maFontAttribute(rFontAttribute), - maLocale(rLocale) + maFontAttribute(std::move(aFontAttribute)), + maLocale(std::move(aLocale)) { } @@ -134,7 +137,7 @@ namespace drawinglayer::primitive2d return PRIMITIVE2D_ID_TEXTCHARACTERSTRIKEOUTPRIMITIVE2D; } - void TextGeometryStrikeoutPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference TextGeometryStrikeoutPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { OSL_ENSURE(TEXT_STRIKEOUT_SLASH != getTextStrikeout() && TEXT_STRIKEOUT_X != getTextStrikeout(), "Wrong TEXT_STRIKEOUT type; a TextCharacterStrikeoutPrimitive2D should be used (!)"); @@ -188,8 +191,7 @@ namespace drawinglayer::primitive2d // add primitive const attribute::LineAttribute aLineAttribute(getFontColor(), fStrikeoutHeight, basegfx::B2DLineJoin::NONE); - Primitive2DContainer xRetval(1); - xRetval[0] = new PolygonStrokePrimitive2D(aStrikeoutLine, aLineAttribute); + Primitive2DReference xRetval = new PolygonStrokePrimitive2D(std::move(aStrikeoutLine), aLineAttribute); if(bDoubleLine) { @@ -210,13 +212,13 @@ namespace drawinglayer::primitive2d aTransform.translate(aTranslate.getX(), aTranslate.getY()); // add transform primitive - xRetval.push_back( + xRetval = new TransformPrimitive2D( aTransform, - Primitive2DContainer(xRetval))); + Primitive2DContainer{xRetval}); } - rContainer.insert(rContainer.end(), xRetval.begin(), xRetval.end()); + return xRetval; } TextGeometryStrikeoutPrimitive2D::TextGeometryStrikeoutPrimitive2D( diff --git a/drawinglayer/source/primitive2d/transformprimitive2d.cxx b/drawinglayer/source/primitive2d/transformprimitive2d.cxx index ddd1594e270e..8c36fa9a73b9 100644 --- a/drawinglayer/source/primitive2d/transformprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/transformprimitive2d.cxx @@ -19,6 +19,8 @@ #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <utility> using namespace com::sun::star; @@ -27,20 +29,29 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { TransformPrimitive2D::TransformPrimitive2D( - const basegfx::B2DHomMatrix& rTransformation, + basegfx::B2DHomMatrix aTransformation, Primitive2DContainer&& aChildren) - : GroupPrimitive2D(std::move(aChildren)), - maTransformation(rTransformation) + : maTransformation(std::move(aTransformation)), + mxChildren(new GroupPrimitive2D(std::move(aChildren))) + { + } + + TransformPrimitive2D::TransformPrimitive2D( + basegfx::B2DHomMatrix aTransformation, + GroupPrimitive2D& rChildren) + : maTransformation(std::move(aTransformation)), + mxChildren(&rChildren) { } bool TransformPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { - if(GroupPrimitive2D::operator==(rPrimitive)) + if(BasePrimitive2D::operator==(rPrimitive)) { const TransformPrimitive2D& rCompare = static_cast< const TransformPrimitive2D& >(rPrimitive); - return (getTransformation() == rCompare.getTransformation()); + return maTransformation == rCompare.maTransformation + && arePrimitive2DReferencesEqual(mxChildren, rCompare.mxChildren); } return false; diff --git a/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx b/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx index c50fcb008435..4a8383a5a354 100644 --- a/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx @@ -21,10 +21,10 @@ #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/color/bcolor.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> using namespace com::sun::star; @@ -85,15 +85,15 @@ namespace drawinglayer::primitive2d // I will take the last one here. The small overhead of two primitives will only be // used when UnifiedTransparencePrimitive2D is not handled directly. const basegfx::B2DRange aPolygonRange(getChildren().getB2DRange(rViewInformation)); - const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(aPolygonRange)); + basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(aPolygonRange)); const basegfx::BColor aGray(getTransparence(), getTransparence(), getTransparence()); Primitive2DContainer aTransparenceContent(2); - aTransparenceContent[0] = Primitive2DReference(new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), aGray)); - aTransparenceContent[1] = Primitive2DReference(new PolygonHairlinePrimitive2D(aPolygon, aGray)); + aTransparenceContent[0] = new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), aGray); + aTransparenceContent[1] = new PolygonHairlinePrimitive2D(std::move(aPolygon), aGray); // create sub-transparence group with a gray-colored rectangular fill polygon - rVisitor.append(new TransparencePrimitive2D(Primitive2DContainer(getChildren()), std::move(aTransparenceContent))); + rVisitor.visit(new TransparencePrimitive2D(Primitive2DContainer(getChildren()), std::move(aTransparenceContent))); } else { diff --git a/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx b/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx index 6e7e134f2fa2..9e3af10415dd 100644 --- a/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx @@ -31,190 +31,186 @@ namespace drawinglayer::primitive2d { - void WallpaperBitmapPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference WallpaperBitmapPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { + + if(getLocalObjectRange().isEmpty() || getBitmapEx().IsEmpty()) + return nullptr; + Primitive2DReference aRetval; - if(!getLocalObjectRange().isEmpty() && !getBitmapEx().IsEmpty()) - { - // get bitmap PIXEL size - const Size& rPixelSize = getBitmapEx().GetSizePixel(); + // get bitmap PIXEL size + const Size& rPixelSize = getBitmapEx().GetSizePixel(); - if(rPixelSize.Width() > 0 && rPixelSize.Height() > 0) + if(rPixelSize.Width() <= 0 || rPixelSize.Height() <= 0) + return nullptr; + + if(WallpaperStyle::Scale == getWallpaperStyle()) + { + // shortcut for scale; use simple BitmapPrimitive2D + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, getLocalObjectRange().getWidth()); + aObjectTransform.set(1, 1, getLocalObjectRange().getHeight()); + aObjectTransform.set(0, 2, getLocalObjectRange().getMinX()); + aObjectTransform.set(1, 2, getLocalObjectRange().getMinY()); + + aRetval.set( + new BitmapPrimitive2D( + getBitmapEx(), + aObjectTransform)); + } + else + { + // transform to logic size + basegfx::B2DHomMatrix aInverseViewTransformation(getViewTransformation()); + aInverseViewTransformation.invert(); + basegfx::B2DVector aLogicSize(rPixelSize.Width(), rPixelSize.Height()); + aLogicSize = aInverseViewTransformation * aLogicSize; + + // apply layout + basegfx::B2DPoint aTargetTopLeft(getLocalObjectRange().getMinimum()); + bool bUseTargetTopLeft(true); + bool bNeedsClipping(false); + + switch(getWallpaperStyle()) { - if(WallpaperStyle::Scale == getWallpaperStyle()) + default: //case WallpaperStyle::Tile :, also WallpaperStyle::NONE and WallpaperStyle::ApplicationGradient + { + bUseTargetTopLeft = false; + break; + } + case WallpaperStyle::Scale : + { + // handled by shortcut above + break; + } + case WallpaperStyle::TopLeft : + { + // nothing to do + break; + } + case WallpaperStyle::Top : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setX(aCenter.getX() - (aLogicSize.getX() * 0.5)); + break; + } + case WallpaperStyle::TopRight : { - // shortcut for scale; use simple BitmapPrimitive2D - basegfx::B2DHomMatrix aObjectTransform; + aTargetTopLeft.setX(getLocalObjectRange().getMaxX() - aLogicSize.getX()); + break; + } + case WallpaperStyle::Left : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setY(aCenter.getY() - (aLogicSize.getY() * 0.5)); + break; + } + case WallpaperStyle::Center : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft = aCenter - (aLogicSize * 0.5); + break; + } + case WallpaperStyle::Right : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setX(getLocalObjectRange().getMaxX() - aLogicSize.getX()); + aTargetTopLeft.setY(aCenter.getY() - (aLogicSize.getY() * 0.5)); + break; + } + case WallpaperStyle::BottomLeft : + { + aTargetTopLeft.setY(getLocalObjectRange().getMaxY() - aLogicSize.getY()); + break; + } + case WallpaperStyle::Bottom : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setX(aCenter.getX() - (aLogicSize.getX() * 0.5)); + aTargetTopLeft.setY(getLocalObjectRange().getMaxY() - aLogicSize.getY()); + break; + } + case WallpaperStyle::BottomRight : + { + aTargetTopLeft = getLocalObjectRange().getMaximum() - aLogicSize; + break; + } + } - aObjectTransform.set(0, 0, getLocalObjectRange().getWidth()); - aObjectTransform.set(1, 1, getLocalObjectRange().getHeight()); - aObjectTransform.set(0, 2, getLocalObjectRange().getMinX()); - aObjectTransform.set(1, 2, getLocalObjectRange().getMinY()); + if(bUseTargetTopLeft) + { + // fill target range + const basegfx::B2DRange aTargetRange(aTargetTopLeft, aTargetTopLeft + aLogicSize); - Primitive2DReference xReference( - new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), - aObjectTransform)); + // create aligned, single BitmapPrimitive2D + basegfx::B2DHomMatrix aObjectTransform; - aRetval = xReference; - } - else + aObjectTransform.set(0, 0, aTargetRange.getWidth()); + aObjectTransform.set(1, 1, aTargetRange.getHeight()); + aObjectTransform.set(0, 2, aTargetRange.getMinX()); + aObjectTransform.set(1, 2, aTargetRange.getMinY()); + + aRetval.set( + new BitmapPrimitive2D( + getBitmapEx(), + aObjectTransform)); + + // clip when not completely inside object range + bNeedsClipping = !getLocalObjectRange().isInside(aTargetRange); + } + else + { + // WallpaperStyle::Tile, WallpaperStyle::NONE, WallpaperStyle::ApplicationGradient + // convert to relative positions + const basegfx::B2DVector aRelativeSize( + aLogicSize.getX() / (getLocalObjectRange().getWidth() ? getLocalObjectRange().getWidth() : 1.0), + aLogicSize.getY() / (getLocalObjectRange().getHeight() ? getLocalObjectRange().getHeight() : 1.0)); + basegfx::B2DPoint aRelativeTopLeft(0.0, 0.0); + + if(WallpaperStyle::Tile != getWallpaperStyle()) { - // transform to logic size - basegfx::B2DHomMatrix aInverseViewTransformation(getViewTransformation()); - aInverseViewTransformation.invert(); - basegfx::B2DVector aLogicSize(rPixelSize.Width(), rPixelSize.Height()); - aLogicSize = aInverseViewTransformation * aLogicSize; - - // apply layout - basegfx::B2DPoint aTargetTopLeft(getLocalObjectRange().getMinimum()); - bool bUseTargetTopLeft(true); - bool bNeedsClipping(false); - - switch(getWallpaperStyle()) - { - default: //case WallpaperStyle::Tile :, also WallpaperStyle::NONE and WallpaperStyle::ApplicationGradient - { - bUseTargetTopLeft = false; - break; - } - case WallpaperStyle::Scale : - { - // handled by shortcut above - break; - } - case WallpaperStyle::TopLeft : - { - // nothing to do - break; - } - case WallpaperStyle::Top : - { - const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); - aTargetTopLeft.setX(aCenter.getX() - (aLogicSize.getX() * 0.5)); - break; - } - case WallpaperStyle::TopRight : - { - aTargetTopLeft.setX(getLocalObjectRange().getMaxX() - aLogicSize.getX()); - break; - } - case WallpaperStyle::Left : - { - const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); - aTargetTopLeft.setY(aCenter.getY() - (aLogicSize.getY() * 0.5)); - break; - } - case WallpaperStyle::Center : - { - const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); - aTargetTopLeft = aCenter - (aLogicSize * 0.5); - break; - } - case WallpaperStyle::Right : - { - const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); - aTargetTopLeft.setX(getLocalObjectRange().getMaxX() - aLogicSize.getX()); - aTargetTopLeft.setY(aCenter.getY() - (aLogicSize.getY() * 0.5)); - break; - } - case WallpaperStyle::BottomLeft : - { - aTargetTopLeft.setY(getLocalObjectRange().getMaxY() - aLogicSize.getY()); - break; - } - case WallpaperStyle::Bottom : - { - const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); - aTargetTopLeft.setX(aCenter.getX() - (aLogicSize.getX() * 0.5)); - aTargetTopLeft.setY(getLocalObjectRange().getMaxY() - aLogicSize.getY()); - break; - } - case WallpaperStyle::BottomRight : - { - aTargetTopLeft = getLocalObjectRange().getMaximum() - aLogicSize; - break; - } - } - - if(bUseTargetTopLeft) - { - // fill target range - const basegfx::B2DRange aTargetRange(aTargetTopLeft, aTargetTopLeft + aLogicSize); - - // create aligned, single BitmapPrimitive2D - basegfx::B2DHomMatrix aObjectTransform; - - aObjectTransform.set(0, 0, aTargetRange.getWidth()); - aObjectTransform.set(1, 1, aTargetRange.getHeight()); - aObjectTransform.set(0, 2, aTargetRange.getMinX()); - aObjectTransform.set(1, 2, aTargetRange.getMinY()); - - Primitive2DReference xReference( - new BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), - aObjectTransform)); - aRetval = xReference; - - // clip when not completely inside object range - bNeedsClipping = !getLocalObjectRange().isInside(aTargetRange); - } - else - { - // WallpaperStyle::Tile, WallpaperStyle::NONE, WallpaperStyle::ApplicationGradient - // convert to relative positions - const basegfx::B2DVector aRelativeSize( - aLogicSize.getX() / (getLocalObjectRange().getWidth() ? getLocalObjectRange().getWidth() : 1.0), - aLogicSize.getY() / (getLocalObjectRange().getHeight() ? getLocalObjectRange().getHeight() : 1.0)); - basegfx::B2DPoint aRelativeTopLeft(0.0, 0.0); - - if(WallpaperStyle::Tile != getWallpaperStyle()) - { - aRelativeTopLeft.setX(0.5 - aRelativeSize.getX()); - aRelativeTopLeft.setY(0.5 - aRelativeSize.getY()); - } - - // prepare FillGraphicAttribute - const attribute::FillGraphicAttribute aFillGraphicAttribute( - Graphic(getBitmapEx()), - basegfx::B2DRange(aRelativeTopLeft, aRelativeTopLeft+ aRelativeSize), - true); - - // create ObjectTransform - const basegfx::B2DHomMatrix aObjectTransform( - basegfx::utils::createScaleTranslateB2DHomMatrix( - getLocalObjectRange().getRange(), - getLocalObjectRange().getMinimum())); - - // create FillBitmapPrimitive - const drawinglayer::primitive2d::Primitive2DReference xFillBitmap( - new drawinglayer::primitive2d::FillGraphicPrimitive2D( - aObjectTransform, - aFillGraphicAttribute)); - aRetval = xFillBitmap; - - // always embed tiled fill to clipping - bNeedsClipping = true; - } - - if(bNeedsClipping) - { - // embed to clipping; this is necessary for tiled fills - const basegfx::B2DPolyPolygon aPolyPolygon( - basegfx::utils::createPolygonFromRect(getLocalObjectRange())); - const drawinglayer::primitive2d::Primitive2DReference xClippedFill( - new drawinglayer::primitive2d::MaskPrimitive2D( - aPolyPolygon, - { aRetval })); - aRetval = xClippedFill; - } + aRelativeTopLeft.setX(0.5 - aRelativeSize.getX()); + aRelativeTopLeft.setY(0.5 - aRelativeSize.getY()); } + + // prepare FillGraphicAttribute + const attribute::FillGraphicAttribute aFillGraphicAttribute( + Graphic(getBitmapEx()), + basegfx::B2DRange(aRelativeTopLeft, aRelativeTopLeft+ aRelativeSize), + true); + + // create ObjectTransform + const basegfx::B2DHomMatrix aObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + getLocalObjectRange().getRange(), + getLocalObjectRange().getMinimum())); + + // create FillBitmapPrimitive + aRetval.set( + new drawinglayer::primitive2d::FillGraphicPrimitive2D( + aObjectTransform, + aFillGraphicAttribute)); + + // always embed tiled fill to clipping + bNeedsClipping = true; + } + + if(bNeedsClipping) + { + // embed to clipping; this is necessary for tiled fills + basegfx::B2DPolyPolygon aPolyPolygon( + basegfx::utils::createPolygonFromRect(getLocalObjectRange())); + const drawinglayer::primitive2d::Primitive2DReference xClippedFill( + new drawinglayer::primitive2d::MaskPrimitive2D( + std::move(aPolyPolygon), + { aRetval })); + aRetval = xClippedFill; } } - if (aRetval.is()) - rContainer.push_back(aRetval); + return aRetval; } WallpaperBitmapPrimitive2D::WallpaperBitmapPrimitive2D( diff --git a/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx b/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx index 11a5b1929870..6084814ae71f 100644 --- a/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx @@ -19,14 +19,15 @@ #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> #include <basegfx/polygon/b2dpolygon.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> +#include <utility> namespace drawinglayer::primitive2d { - void WrongSpellPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + Primitive2DReference WrongSpellPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { // ATM this decompose is view-independent, what the original VCL-Display is not. To mimic // the old behaviour here if wanted it is necessary to add get2DDecomposition and implement @@ -64,15 +65,15 @@ namespace drawinglayer::primitive2d const attribute::LineAttribute aLineAttribute(getColor()); // create the waveline primitive - rContainer.push_back(new PolygonWavePrimitive2D(aPolygon, aLineAttribute, fWaveWidth, 0.5 * fWaveWidth)); + return new PolygonWavePrimitive2D(aPolygon, aLineAttribute, fWaveWidth, 0.5 * fWaveWidth); } WrongSpellPrimitive2D::WrongSpellPrimitive2D( - const basegfx::B2DHomMatrix& rTransformation, + basegfx::B2DHomMatrix aTransformation, double fStart, double fStop, const basegfx::BColor& rColor) - : maTransformation(rTransformation), + : maTransformation(std::move(aTransformation)), mfStart(fStart), mfStop(fStop), maColor(rColor) diff --git a/drawinglayer/source/primitive3d/baseprimitive3d.cxx b/drawinglayer/source/primitive3d/baseprimitive3d.cxx index 5bd980012a61..c2c8cc9f7e0f 100644 --- a/drawinglayer/source/primitive3d/baseprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/baseprimitive3d.cxx @@ -29,7 +29,6 @@ using namespace com::sun::star; namespace drawinglayer::primitive3d { BasePrimitive3D::BasePrimitive3D() - : BasePrimitive3DImplBase(m_aMutex) { } @@ -76,7 +75,7 @@ namespace drawinglayer::primitive3d Primitive3DContainer BufferedDecompositionPrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); if(getBuffered3DDecomposition().empty()) { diff --git a/drawinglayer/source/primitive3d/groupprimitive3d.cxx b/drawinglayer/source/primitive3d/groupprimitive3d.cxx index 69291e76872e..a3056e3ed432 100644 --- a/drawinglayer/source/primitive3d/groupprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/groupprimitive3d.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/primitive3d/groupprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -26,12 +27,12 @@ using namespace com::sun::star; namespace drawinglayer::primitive3d { - GroupPrimitive3D::GroupPrimitive3D( const Primitive3DContainer& rChildren ) - : maChildren(rChildren) + GroupPrimitive3D::GroupPrimitive3D( Primitive3DContainer aChildren ) + : maChildren(std::move(aChildren)) { } - /** The compare opertator uses the Sequence::==operator, so only checking if + /** The compare operator uses the Sequence::==operator, so only checking if the references are equal. All non-equal references are interpreted as non-equal. */ diff --git a/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx b/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx index a699f71706bb..3e0abc582732 100644 --- a/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx @@ -29,6 +29,7 @@ #include <basegfx/matrix/b3dhommatrix.hxx> #include <drawinglayer/primitive3d/polygonprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -267,13 +268,13 @@ namespace drawinglayer::primitive3d } HatchTexturePrimitive3D::HatchTexturePrimitive3D( - const attribute::FillHatchAttribute& rHatch, + attribute::FillHatchAttribute aHatch, const Primitive3DContainer& rChildren, const basegfx::B2DVector& rTextureSize, bool bModulate, bool bFilter) : TexturePrimitive3D(rChildren, rTextureSize, bModulate, bFilter), - maHatch(rHatch) + maHatch(std::move(aHatch)) { } @@ -291,7 +292,7 @@ namespace drawinglayer::primitive3d Primitive3DContainer HatchTexturePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); if(getBuffered3DDecomposition().empty()) { diff --git a/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx b/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx index 255f0235c911..54fa166f055d 100644 --- a/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/primitive3d/modifiedcolorprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -28,9 +29,9 @@ namespace drawinglayer::primitive3d { ModifiedColorPrimitive3D::ModifiedColorPrimitive3D( const Primitive3DContainer& rChildren, - const basegfx::BColorModifierSharedPtr& rColorModifier) + basegfx::BColorModifierSharedPtr xColorModifier) : GroupPrimitive3D(rChildren), - maColorModifier(rColorModifier) + maColorModifier(std::move(xColorModifier)) { } diff --git a/drawinglayer/source/primitive3d/polygonprimitive3d.cxx b/drawinglayer/source/primitive3d/polygonprimitive3d.cxx index 8fc2b4d31298..6127ac77666b 100644 --- a/drawinglayer/source/primitive3d/polygonprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/polygonprimitive3d.cxx @@ -22,6 +22,7 @@ #include <basegfx/polygon/b3dpolypolygon.hxx> #include <primitive3d/polygontubeprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -30,9 +31,9 @@ using namespace com::sun::star; namespace drawinglayer::primitive3d { PolygonHairlinePrimitive3D::PolygonHairlinePrimitive3D( - const basegfx::B3DPolygon& rPolygon, + basegfx::B3DPolygon aPolygon, const basegfx::BColor& rBColor) - : maPolygon(rPolygon), + : maPolygon(std::move(aPolygon)), maBColor(rBColor) { } @@ -117,12 +118,12 @@ namespace drawinglayer::primitive3d } PolygonStrokePrimitive3D::PolygonStrokePrimitive3D( - const basegfx::B3DPolygon& rPolygon, + basegfx::B3DPolygon aPolygon, const attribute::LineAttribute& rLineAttribute, - const attribute::StrokeAttribute& rStrokeAttribute) - : maPolygon(rPolygon), + attribute::StrokeAttribute aStrokeAttribute) + : maPolygon(std::move(aPolygon)), maLineAttribute(rLineAttribute), - maStrokeAttribute(rStrokeAttribute) + maStrokeAttribute(std::move(aStrokeAttribute)) { } diff --git a/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx b/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx index d8d3cc9c8b3e..672c78463a7d 100644 --- a/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx @@ -92,9 +92,8 @@ namespace drawinglayer::primitive3d aNewPolygon.setClosed(true); - const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); - const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, m_aLineMaterial, false)); - m_aLineTubeList[a] = xRef; + basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); + m_aLineTubeList[a] = new PolyPolygonMaterialPrimitive3D(std::move(aNewPolyPolygon), m_aLineMaterial, false); aLastLeft = aNextLeft; aLastRight = aNextRight; @@ -167,9 +166,8 @@ namespace drawinglayer::primitive3d aNewPolygon.setClosed(true); - const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); - const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, m_aLineMaterial, false)); - m_aLineCapList[a] = xRef; + basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); + m_aLineCapList[a] = new PolyPolygonMaterialPrimitive3D(std::move(aNewPolyPolygon), m_aLineMaterial, false); aLast = aNext; } @@ -254,12 +252,12 @@ namespace drawinglayer::primitive3d for (sal_uInt32 a = 0; a < nCount; ++a) { const basegfx::B3DPolygon& aPartPolygon(aSphere.getB3DPolygon(a)); - const basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon); + basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon); // need to create one primitive per Polygon since the primitive // is for planar PolyPolygons which is definitely not the case here m_aLineCapRoundList[a] = new PolyPolygonMaterialPrimitive3D( - aPartPolyPolygon, + std::move(aPartPolyPolygon), rMaterial, false); } @@ -306,8 +304,8 @@ namespace drawinglayer::primitive3d for(sal_uInt32 a(0); a < aSphere.count(); a++) { const basegfx::B3DPolygon& aPartPolygon(aSphere.getB3DPolygon(a)); - const basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon); - aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aPartPolyPolygon, rMaterial, false)); + basegfx::B3DPolyPolygon aPartPolyPolygon(aPartPolygon); + aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(std::move(aPartPolyPolygon), rMaterial, false)); } } else @@ -453,8 +451,8 @@ namespace drawinglayer::primitive3d // create primitive if(aNewPolygon.count()) { - const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); - aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, rMaterial, false)); + basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); + aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(std::move(aNewPolyPolygon), rMaterial, false)); } if(bMiter && aMiterPolygon.count()) @@ -466,8 +464,8 @@ namespace drawinglayer::primitive3d } // create primitive - const basegfx::B3DPolyPolygon aMiterPolyPolygon(aMiterPolygon); - aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aMiterPolyPolygon, rMaterial, false)); + basegfx::B3DPolyPolygon aMiterPolyPolygon(aMiterPolygon); + aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(std::move(aMiterPolyPolygon), rMaterial, false)); } // prepare next step @@ -487,10 +485,7 @@ namespace drawinglayer::primitive3d Primitive3DContainer aRetval(aResultVector.size()); - for(size_t a(0); a < aResultVector.size(); a++) - { - aRetval[a] = Primitive3DReference(aResultVector[a]); - } + std::transform(aResultVector.cbegin(), aResultVector.cend(), aRetval.begin(), [](auto &rResult){return Primitive3DReference(rResult);}); return aRetval; } @@ -522,7 +517,7 @@ using namespace com::sun::star; if(nPointCount) { - if(basegfx::fTools::more(getRadius(), 0.0)) + if(getRadius() > 0.0) { const attribute::MaterialAttribute3D aMaterial(getBColor()); static const sal_uInt32 nSegments(8); // default for 3d line segments, for more quality just raise this value (in even steps) @@ -539,7 +534,7 @@ using namespace com::sun::star; const basegfx::B3DVector aForw(aNext - aCurr); const double fForwLen(aForw.getLength()); - if(basegfx::fTools::more(fForwLen, 0.0)) + if(fForwLen > 0.0) { // find out if linecap is active const bool bFirst(!a); @@ -601,7 +596,7 @@ using namespace com::sun::star; aSequence = getLineCapSegments(nSegments, aMaterial); } - aResultVector.push_back(new TransformPrimitive3D(aCapTrans, aSequence)); + aResultVector.push_back(new TransformPrimitive3D(std::move(aCapTrans), aSequence)); } else { @@ -638,7 +633,7 @@ using namespace com::sun::star; // line start edge, build transformed primitiveVector3D aResultVector.push_back( new TransformPrimitive3D( - aSphereTrans, + std::move(aSphereTrans), aNewList)); } } @@ -646,7 +641,7 @@ using namespace com::sun::star; // create line segments, build transformed primitiveVector3D aResultVector.push_back( new TransformPrimitive3D( - aTubeTrans, + std::move(aTubeTrans), getLineTubeSegments(nSegments, aMaterial))); if(bNoLineJoin || (!bClosed && bLast)) @@ -689,7 +684,7 @@ using namespace com::sun::star; aResultVector.push_back( new TransformPrimitive3D( - aBackCapTrans, + std::move(aBackCapTrans), aSequence)); } } @@ -709,10 +704,7 @@ using namespace com::sun::star; // prepare return value Primitive3DContainer aRetval(aResultVector.size()); - for(size_t a(0); a < aResultVector.size(); a++) - { - aRetval[a] = Primitive3DReference(aResultVector[a]); - } + std::transform(aResultVector.cbegin(), aResultVector.cend(), aRetval.begin(), [](auto &rResult){return Primitive3DReference(rResult);}); return aRetval; } @@ -751,7 +743,7 @@ using namespace com::sun::star; Primitive3DContainer PolygonTubePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); if(getLast3DDecomposition().empty()) { diff --git a/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx b/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx index 41e62b611405..09c8fb515933 100644 --- a/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx @@ -20,6 +20,7 @@ #include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> #include <basegfx/polygon/b3dpolypolygontools.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -28,10 +29,10 @@ using namespace com::sun::star; namespace drawinglayer::primitive3d { PolyPolygonMaterialPrimitive3D::PolyPolygonMaterialPrimitive3D( - const basegfx::B3DPolyPolygon& rPolyPolygon, + basegfx::B3DPolyPolygon aPolyPolygon, const attribute::MaterialAttribute3D& rMaterial, bool bDoubleSided) - : maPolyPolygon(rPolyPolygon), + : maPolyPolygon(std::move(aPolyPolygon)), maMaterial(rMaterial), mbDoubleSided(bDoubleSided) { diff --git a/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx b/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx index ec6bfaf3c8a4..7690dd300f6e 100644 --- a/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx +++ b/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx @@ -144,8 +144,7 @@ namespace drawinglayer::primitive3d for(sal_uInt32 a(0); a < aScaledPolyPolygon.count(); a++) { - const Primitive3DReference xRef(new PolygonStrokePrimitive3D(aScaledPolyPolygon.getB3DPolygon(a), aLineAttribute, aStrokeAttribute)); - aRetval[a] = xRef; + aRetval[a] = new PolygonStrokePrimitive3D(aScaledPolyPolygon.getB3DPolygon(a), aLineAttribute, aStrokeAttribute); } if(0.0 != rLine.getTransparence()) @@ -185,7 +184,7 @@ namespace drawinglayer::primitive3d } const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D( - aScaledPolyPolygon, + std::move(aScaledPolyPolygon), aSdr3DObjectAttribute.getMaterial(), aSdr3DObjectAttribute.getDoubleSided())); aRetval[a] = xRef; diff --git a/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx b/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx index 2993b1dd27c3..a7015ff8e578 100644 --- a/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx +++ b/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx @@ -312,26 +312,26 @@ namespace // polygon is closed, one of the points is a member const sal_uInt32 nPointCount(rPoly.count()); - if(nPointCount) - { - basegfx::B2DPoint aCurrent(rPoly.getB2DPoint(0)); - const basegfx::B2DVector aVector(rEnd - rStart); + if(!nPointCount) + return false; - for(sal_uInt32 a(0); a < nPointCount; a++) - { - const sal_uInt32 nNextIndex((a + 1) % nPointCount); - const basegfx::B2DPoint aNext(rPoly.getB2DPoint(nNextIndex)); - const basegfx::B2DVector aEdgeVector(aNext - aCurrent); + basegfx::B2DPoint aCurrent(rPoly.getB2DPoint(0)); + const basegfx::B2DVector aVector(rEnd - rStart); - if(basegfx::utils::findCut( - rStart, aVector, - aCurrent, aEdgeVector) != CutFlagValue::NONE) - { - return true; - } + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const basegfx::B2DPoint aNext(rPoly.getB2DPoint(nNextIndex)); + const basegfx::B2DVector aEdgeVector(aNext - aCurrent); - aCurrent = aNext; + if(basegfx::utils::findCut( + rStart, aVector, + aCurrent, aEdgeVector) != CutFlagValue::NONE) + { + return true; } + + aCurrent = aNext; } return false; diff --git a/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx index 919fbcf5ab4a..ed0e8d41c4ab 100644 --- a/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx @@ -30,6 +30,7 @@ #include <drawinglayer/attribute/sdrfillattribute.hxx> #include <drawinglayer/attribute/sdrlineattribute.hxx> #include <drawinglayer/attribute/sdrshadowattribute.hxx> +#include <utility> using namespace com::sun::star; @@ -375,7 +376,7 @@ namespace drawinglayer::primitive3d // again when no longer geometry is needed for non-visible 3D objects as it is now for chart if(getPolyPolygon().count() && maSlices.empty()) { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); const_cast< SdrExtrudePrimitive3D& >(*this).impCreateSlices(); } @@ -388,7 +389,7 @@ namespace drawinglayer::primitive3d const basegfx::B2DVector& rTextureSize, const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute, - const basegfx::B2DPolyPolygon& rPolyPolygon, + basegfx::B2DPolyPolygon aPolyPolygon, double fDepth, double fDiagonal, double fBackScale, @@ -398,7 +399,7 @@ namespace drawinglayer::primitive3d bool bCloseFront, bool bCloseBack) : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), - maPolyPolygon(rPolyPolygon), + maPolyPolygon(std::move(aPolyPolygon)), mfDepth(fDepth), mfDiagonal(fDiagonal), mfBackScale(fBackScale), @@ -409,13 +410,13 @@ namespace drawinglayer::primitive3d mbCloseBack(bCloseBack) { // make sure depth is positive - if(basegfx::fTools::lessOrEqual(getDepth(), 0.0)) + if(getDepth() <= 0.0) { mfDepth = 0.0; } // make sure the percentage value getDiagonal() is between 0.0 and 1.0 - if(basegfx::fTools::lessOrEqual(getDiagonal(), 0.0)) + if(getDiagonal() <= 0.0) { mfDiagonal = 0.0; } @@ -480,7 +481,7 @@ namespace drawinglayer::primitive3d (!getBuffered3DDecomposition().empty() && *mpLastRLGViewInformation != rViewInformation)) { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); // conditions of last local decomposition with reduced lines have changed. Remember // new one and clear current decompositiopn diff --git a/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx index 2115ebc3abe7..682ea0c54042 100644 --- a/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx @@ -26,6 +26,7 @@ #include <drawinglayer/attribute/sdrfillattribute.hxx> #include <drawinglayer/attribute/sdrlineattribute.hxx> #include <drawinglayer/attribute/sdrshadowattribute.hxx> +#include <utility> using namespace com::sun::star; @@ -227,7 +228,7 @@ namespace drawinglayer::primitive3d // again when no longer geometry is needed for non-visible 3D objects as it is now for chart if(getPolyPolygon().count() && maSlices.empty()) { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); const_cast< SdrLathePrimitive3D& >(*this).impCreateSlices(); } @@ -240,7 +241,7 @@ namespace drawinglayer::primitive3d const basegfx::B2DVector& rTextureSize, const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute, - const basegfx::B2DPolyPolygon& rPolyPolygon, + basegfx::B2DPolyPolygon aPolyPolygon, sal_uInt32 nHorizontalSegments, sal_uInt32 nVerticalSegments, double fDiagonal, @@ -252,7 +253,7 @@ namespace drawinglayer::primitive3d bool bCloseFront, bool bCloseBack) : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), - maPolyPolygon(rPolyPolygon), + maPolyPolygon(std::move(aPolyPolygon)), mnHorizontalSegments(nHorizontalSegments), mnVerticalSegments(nVerticalSegments), mfDiagonal(fDiagonal), @@ -265,13 +266,13 @@ namespace drawinglayer::primitive3d mbCloseBack(bCloseBack) { // make sure Rotation is positive - if(basegfx::fTools::lessOrEqual(getRotation(), 0.0)) + if(getRotation() <= 0.0) { mfRotation = 0.0; } // make sure the percentage value getDiagonal() is between 0.0 and 1.0 - if(basegfx::fTools::lessOrEqual(getDiagonal(), 0.0)) + if(getDiagonal() <= 0.0) { mfDiagonal = 0.0; } @@ -338,7 +339,7 @@ namespace drawinglayer::primitive3d (!getBuffered3DDecomposition().empty() && *mpLastRLGViewInformation != rViewInformation)) { - ::osl::MutexGuard aGuard( m_aMutex ); + std::unique_lock aGuard( m_aMutex ); // conditions of last local decomposition with reduced lines have changed. Remember // new one and clear current decompositiopn diff --git a/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx index 010083b42291..9219cc970c86 100644 --- a/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx @@ -24,6 +24,7 @@ #include <drawinglayer/attribute/sdrfillattribute.hxx> #include <drawinglayer/attribute/sdrlineattribute.hxx> #include <drawinglayer/attribute/sdrshadowattribute.hxx> +#include <utility> using namespace com::sun::star; @@ -117,13 +118,13 @@ namespace drawinglayer::primitive3d } SdrPolyPolygonPrimitive3D::SdrPolyPolygonPrimitive3D( - const basegfx::B3DPolyPolygon& rPolyPolygon3D, + basegfx::B3DPolyPolygon aPolyPolygon3D, const basegfx::B3DHomMatrix& rTransform, const basegfx::B2DVector& rTextureSize, const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute) : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), - maPolyPolygon3D(rPolyPolygon3D) + maPolyPolygon3D(std::move(aPolyPolygon3D)) { } diff --git a/drawinglayer/source/primitive3d/sdrprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrprimitive3d.cxx index 54fd2cf4b206..b4007f1a8705 100644 --- a/drawinglayer/source/primitive3d/sdrprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/sdrprimitive3d.cxx @@ -20,6 +20,7 @@ #include <drawinglayer/primitive3d/sdrprimitive3d.hxx> #include <basegfx/polygon/b3dpolypolygontools.hxx> #include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <utility> using namespace com::sun::star; @@ -75,13 +76,13 @@ namespace drawinglayer::primitive3d } SdrPrimitive3D::SdrPrimitive3D( - const basegfx::B3DHomMatrix& rTransform, + basegfx::B3DHomMatrix aTransform, const basegfx::B2DVector& rTextureSize, - const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + attribute::SdrLineFillShadowAttribute3D aSdrLFSAttribute, const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute) - : maTransform(rTransform), + : maTransform(std::move(aTransform)), maTextureSize(rTextureSize), - maSdrLFSAttribute(rSdrLFSAttribute), + maSdrLFSAttribute(std::move(aSdrLFSAttribute)), maSdr3DObjectAttribute(rSdr3DObjectAttribute) { } diff --git a/drawinglayer/source/primitive3d/shadowprimitive3d.cxx b/drawinglayer/source/primitive3d/shadowprimitive3d.cxx index cca2e3c6f07f..3c3a07ef7a2f 100644 --- a/drawinglayer/source/primitive3d/shadowprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/shadowprimitive3d.cxx @@ -19,6 +19,7 @@ #include <primitive3d/shadowprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -27,13 +28,13 @@ using namespace com::sun::star; namespace drawinglayer::primitive3d { ShadowPrimitive3D::ShadowPrimitive3D( - const basegfx::B2DHomMatrix& rShadowTransform, + basegfx::B2DHomMatrix aShadowTransform, const basegfx::BColor& rShadowColor, double fShadowTransparence, bool bShadow3D, const Primitive3DContainer& rChildren) : GroupPrimitive3D(rChildren), - maShadowTransform(rShadowTransform), + maShadowTransform(std::move(aShadowTransform)), maShadowColor(rShadowColor), mfShadowTransparence(fShadowTransparence), mbShadow3D(bShadow3D) diff --git a/drawinglayer/source/primitive3d/textureprimitive3d.cxx b/drawinglayer/source/primitive3d/textureprimitive3d.cxx index 7c9226bf40de..ceeca0489487 100644 --- a/drawinglayer/source/primitive3d/textureprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/textureprimitive3d.cxx @@ -20,6 +20,8 @@ #include <primitive3d/textureprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> #include <basegfx/color/bcolor.hxx> +#include <basegfx/utils/gradienttools.hxx> +#include <utility> using namespace com::sun::star; @@ -91,7 +93,13 @@ namespace drawinglayer::primitive3d { // create TransparenceTexturePrimitive3D with fixed transparence as replacement const basegfx::BColor aGray(getTransparence(), getTransparence(), getTransparence()); - const attribute::FillGradientAttribute aFillGradient(attribute::GradientStyle::Linear, 0.0, 0.0, 0.0, 0.0, aGray, aGray); + + // create ColorStops with StartColor == EndColor == aGray + const basegfx::BColorStops aColorStops { + basegfx::BColorStop(0.0, aGray), + basegfx::BColorStop(1.0, aGray) }; + + const attribute::FillGradientAttribute aFillGradient(css::awt::GradientStyle_LINEAR, 0.0, 0.0, 0.0, 0.0, aColorStops); const Primitive3DReference xRef(new TransparenceTexturePrimitive3D(aFillGradient, getChildren(), getTextureSize())); return { xRef }; } @@ -108,13 +116,13 @@ namespace drawinglayer::primitive3d GradientTexturePrimitive3D::GradientTexturePrimitive3D( - const attribute::FillGradientAttribute& rGradient, + attribute::FillGradientAttribute aGradient, const Primitive3DContainer& rChildren, const basegfx::B2DVector& rTextureSize, bool bModulate, bool bFilter) : TexturePrimitive3D(rChildren, rTextureSize, bModulate, bFilter), - maGradient(rGradient) + maGradient(std::move(aGradient)) { } diff --git a/drawinglayer/source/primitive3d/transformprimitive3d.cxx b/drawinglayer/source/primitive3d/transformprimitive3d.cxx index 1ddb919bf2e1..c7b926d8dc18 100644 --- a/drawinglayer/source/primitive3d/transformprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/transformprimitive3d.cxx @@ -19,6 +19,7 @@ #include <drawinglayer/primitive3d/transformprimitive3d.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <utility> using namespace com::sun::star; @@ -27,10 +28,10 @@ using namespace com::sun::star; namespace drawinglayer::primitive3d { TransformPrimitive3D::TransformPrimitive3D( - const basegfx::B3DHomMatrix& rTransformation, + basegfx::B3DHomMatrix aTransformation, const Primitive3DContainer& rChildren) : GroupPrimitive3D(rChildren), - maTransformation(rTransformation) + maTransformation(std::move(aTransformation)) { } diff --git a/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx new file mode 100644 index 000000000000..7d001cfd67fd --- /dev/null +++ b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx @@ -0,0 +1,302 @@ +/* -*- 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/processor2d/SDPRProcessor2dTools.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/graph.hxx> +#include <basegfx/range/b2drange.hxx> + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif + +namespace drawinglayer::processor2d +{ +void setOffsetXYCreatedBitmap( + drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const BitmapEx& rBitmap) +{ + rFillGraphicPrimitive2D.impSetOffsetXYCreatedBitmap(rBitmap); +} + +void takeCareOfOffsetXY( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + BitmapEx& rTarget, basegfx::B2DRange& rFillUnitRange) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillGraphicPrimitive2D.getFillGraphic()); + const bool bOffsetXIsUsed(rFillGraphicAttribute.getOffsetX() > 0.0 + && rFillGraphicAttribute.getOffsetX() < 1.0); + const bool bOffsetYIsUsed(rFillGraphicAttribute.getOffsetY() > 0.0 + && rFillGraphicAttribute.getOffsetY() < 1.0); + + if (bOffsetXIsUsed) + { + if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + const Size& rSize(rTarget.GetSizePixel()); + const tools::Long w(rSize.Width()); + const tools::Long a( + basegfx::fround<tools::Long>(w * (1.0 - rFillGraphicAttribute.getOffsetX()))); + + if (0 != a && w != a) + { + const tools::Long h(rSize.Height()); + const tools::Long b(w - a); + BitmapEx aTarget(Size(w, h * 2), rTarget.getPixelFormat()); + + aTarget.SetPrefSize( + Size(rTarget.GetPrefSize().Width(), rTarget.GetPrefSize().Height() * 2)); + const tools::Rectangle aSrcDst(Point(), rSize); + aTarget.CopyPixel(aSrcDst, // Dst + aSrcDst, // Src + rTarget); + const Size aSizeA(b, h); + aTarget.CopyPixel(tools::Rectangle(Point(0, h), aSizeA), // Dst + tools::Rectangle(Point(a, 0), aSizeA), // Src + rTarget); + const Size aSizeB(a, h); + aTarget.CopyPixel(tools::Rectangle(Point(b, h), aSizeB), // Dst + tools::Rectangle(Point(), aSizeB), // Src + rTarget); + + setOffsetXYCreatedBitmap( + const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>( + rFillGraphicPrimitive2D), + aTarget); + } + } + + if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap(); + rFillUnitRange.expand(basegfx::B2DPoint( + rFillUnitRange.getMinX(), rFillUnitRange.getMaxY() + rFillUnitRange.getHeight())); + } + } + else if (bOffsetYIsUsed) + { + if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + const Size& rSize(rTarget.GetSizePixel()); + const tools::Long h(rSize.Height()); + const tools::Long a( + basegfx::fround<tools::Long>(h * (1.0 - rFillGraphicAttribute.getOffsetY()))); + + if (0 != a && h != a) + { + const tools::Long w(rSize.Width()); + const tools::Long b(h - a); + BitmapEx aTarget(Size(w * 2, h), rTarget.getPixelFormat()); + + aTarget.SetPrefSize( + Size(rTarget.GetPrefSize().Width() * 2, rTarget.GetPrefSize().Height())); + const tools::Rectangle aSrcDst(Point(), rSize); + aTarget.CopyPixel(aSrcDst, // Dst + aSrcDst, // Src + rTarget); + const Size aSizeA(w, b); + aTarget.CopyPixel(tools::Rectangle(Point(w, 0), aSizeA), // Dst + tools::Rectangle(Point(0, a), aSizeA), // Src + rTarget); + const Size aSizeB(w, a); + aTarget.CopyPixel(tools::Rectangle(Point(w, b), aSizeB), // Dst + tools::Rectangle(Point(), aSizeB), // Src + rTarget); + + setOffsetXYCreatedBitmap( + const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>( + rFillGraphicPrimitive2D), + aTarget); + } + } + + if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap(); + rFillUnitRange.expand(basegfx::B2DPoint( + rFillUnitRange.getMaxX() + rFillUnitRange.getWidth(), rFillUnitRange.getMinY())); + } + } +} + +bool prepareBitmapForDirectRender( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, BitmapEx& rTarget, + basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillGraphicPrimitive2D.getFillGraphic()); + const Graphic& rGraphic(rFillGraphicAttribute.getGraphic()); + + if (rFillGraphicAttribute.isDefault() || rGraphic.IsNone()) + { + // default attributes or GraphicType::NONE, so no fill .-> done + return false; + } + + if (!rFillGraphicAttribute.getTiling()) + { + // If no tiling used, the Graphic will need to be painted just once. This + // is perfectly done using the decomposition, so use it. + // What we want to do here is to optimize tiled paint, for two reasons: + // (a) speed: draw one tile, repeat -> obvious + // (b) correctness: not so obvious, but since in AAed paint the same edge + // of touching polygons both AAed do *not* sum up, but get blended by + // multiplication (0.5 * 0.5 -> 0.25) the connection will stay visible, + // not only with filled polygons, but also with bitmaps + // Signal that paint is needed + return true; + } + + if (rFillUnitRange.isEmpty()) + { + // no fill range definition, no fill, done + return false; + } + + const basegfx::B2DHomMatrix aLocalTransform(rViewInformation2D.getObjectToViewTransformation() + * rFillGraphicPrimitive2D.getTransformation()); + const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport()); + + if (!rDiscreteViewPort.isEmpty()) + { + // calculate discrete covered pixel area + basegfx::B2DRange aDiscreteRange(basegfx::B2DRange::getUnitB2DRange()); + aDiscreteRange.transform(aLocalTransform); + + if (!rDiscreteViewPort.overlaps(rDiscreteViewPort)) + { + // we have a Viewport and visible range of geometry is outside -> not visible, done + return false; + } + } + + if (GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated()) + { + // Need to prepare specialized AnimatedGraphicPrimitive2D, + // cannot handle here. Signal that paint is needed + return true; + } + + if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.getVectorGraphicData()) + { + // bitmap graphic, always handle locally, so get bitmap data independent + // if it'sie or it's discrete display size + rTarget = rGraphic.GetBitmapEx(); + } + else + { + // Vector Graphic Data fill, including metafile: + // We can know about discrete pixel size here, calculate and use it. + // To do so, using Vectors is sufficient to get the lengths. It is + // not necessary to transform the whole target coordinate system. + const basegfx::B2DVector aDiscreteXAxis( + aLocalTransform + * basegfx::B2DVector(rFillUnitRange.getMaxX() - rFillUnitRange.getMinX(), 0.0)); + const basegfx::B2DVector aDiscreteYAxis( + aLocalTransform + * basegfx::B2DVector(0.0, rFillUnitRange.getMaxY() - rFillUnitRange.getMinY())); + + // get and ensure minimal size + const double fDiscreteWidth(std::max(1.0, aDiscreteXAxis.getLength())); + const double fDiscreteHeight(std::max(1.0, aDiscreteYAxis.getLength())); + + // compare with a big visualization size in discrete pixels + const double fTargetDiscreteArea(fDiscreteWidth * fDiscreteHeight); + + if (fTargetDiscreteArea > fBigDiscreteArea) + { + // When the vector data is visualized big it is better to not handle here + // but use decomposition fallback which then will visualize the vector data + // directly -> better quality, acceptable number of tile repeat(s) + // signal that paint is needed + return true; + } + else + { + // If visualized small, the amount of repeated fills gets expensive, so + // in that case use a Bitmap and the Brush technique below. + // The Bitmap may be created here exactly for the needed target size + // (using local D2DBitmapPixelProcessor2D and the vector data), + // but since we have a HW renderer and re-use of system-dependent data + // at BitmapEx is possible, just get the default fallback Bitmap from the + // vector data to continue. Trust the existing converters for now to + // do something with good quality. + rTarget = rGraphic.GetBitmapEx(); + } + } + + if (rTarget.IsEmpty() || rTarget.GetSizePixel().IsEmpty()) + { + // no pixel data, done + return false; + } + + // react if OffsetX/OffsetY of the FillGraphicAttribute is used + takeCareOfOffsetXY(rFillGraphicPrimitive2D, rTarget, rFillUnitRange); + +#ifdef DBG_UTIL + // allow to check bitmap data, e.g. control OffsetX/OffsetY stuff + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_getreplacement.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(rTarget); + } + } +#endif + + // signal to render it + return true; +} + +void calculateDiscreteVisibleRange( + basegfx::B2DRange& rDiscreteVisibleRange, const basegfx::B2DRange& rContentRange, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +{ + if (rContentRange.isEmpty()) + { + // no content, done + rDiscreteVisibleRange.reset(); + return; + } + + basegfx::B2DRange aDiscreteRange(rContentRange); + aDiscreteRange.transform(rViewInformation2D.getObjectToViewTransformation()); + const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport()); + rDiscreteVisibleRange = aDiscreteRange; + + if (!rDiscreteViewPort.isEmpty()) + { + rDiscreteVisibleRange.intersect(rDiscreteViewPort); + } +} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/baseprocessor2d.cxx b/drawinglayer/source/processor2d/baseprocessor2d.cxx index a078fe882e9e..13d6b4c63b08 100644 --- a/drawinglayer/source/processor2d/baseprocessor2d.cxx +++ b/drawinglayer/source/processor2d/baseprocessor2d.cxx @@ -17,8 +17,9 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ +#include <drawinglayer/primitive2d/Primitive2DContainer.hxx> #include <drawinglayer/processor2d/baseprocessor2d.hxx> -#include <comphelper/sequence.hxx> +#include <utility> using namespace com::sun::star; @@ -30,8 +31,8 @@ namespace drawinglayer::processor2d { } - BaseProcessor2D::BaseProcessor2D(const geometry::ViewInformation2D& rViewInformation) - : maViewInformation2D(rViewInformation) + BaseProcessor2D::BaseProcessor2D(geometry::ViewInformation2D aViewInformation) + : maViewInformation2D(std::move(aViewInformation)) { } @@ -46,16 +47,16 @@ namespace drawinglayer::processor2d } // Primitive2DDecompositionVisitor - void BaseProcessor2D::append(const primitive2d::Primitive2DReference& rCandidate) + void BaseProcessor2D::visit(const primitive2d::Primitive2DReference& rCandidate) { - const primitive2d::BasePrimitive2D* pBasePrimitive = static_cast< const primitive2d::BasePrimitive2D* >(rCandidate.get()); - processBasePrimitive2D(*pBasePrimitive); + if (rCandidate) + processBasePrimitive2D(*rCandidate); } - void BaseProcessor2D::append(const primitive2d::Primitive2DContainer& rContainer) + void BaseProcessor2D::visit(const primitive2d::Primitive2DContainer& rContainer) { process(rContainer); } - void BaseProcessor2D::append(primitive2d::Primitive2DContainer&& rCandidate) + void BaseProcessor2D::visit(primitive2d::Primitive2DContainer&& rCandidate) { process(rCandidate); } @@ -64,9 +65,8 @@ namespace drawinglayer::processor2d { for (const primitive2d::Primitive2DReference& rCandidate : rSource) { - const primitive2d::BasePrimitive2D* pBasePrimitive = static_cast< const primitive2d::BasePrimitive2D* >(rCandidate.get()); - if (pBasePrimitive) - processBasePrimitive2D(*pBasePrimitive); + if (rCandidate) + processBasePrimitive2D(*rCandidate); } } diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx new file mode 100644 index 000000000000..89cb21ddcb74 --- /dev/null +++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx @@ -0,0 +1,975 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx> +#include <sal/log.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/cairo.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/converters.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> +#include <vcl/BitmapReadAccess.hxx> + +using namespace com::sun::star; + +namespace +{ +basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D& rViewInformation, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if (bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation(); + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +void addB2DPolygonToPathGeometry(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D* pViewInformation) +{ + // short circuit if there is nothing to do + const sal_uInt32 nPointCount(rPolygon.count()); + + const bool bHasCurves(rPolygon.areControlPointsUsed()); + const bool bClosePath(rPolygon.isClosed()); + basegfx::B2DPoint aLast; + + for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++) + { + int nClosedIdx = nPointIdx; + if (nPointIdx >= nPointCount) + { + // prepare to close last curve segment if needed + if (bClosePath && (nPointIdx == nPointCount)) + { + nClosedIdx = 0; + } + else + { + break; + } + } + + const basegfx::B2DPoint aPoint(nullptr == pViewInformation + ? rPolygon.getB2DPoint(nClosedIdx) + : impPixelSnap(rPolygon, *pViewInformation, nClosedIdx)); + + if (!nPointIdx) + { + // first point => just move there + cairo_move_to(cr, aPoint.getX(), aPoint.getY()); + aLast = aPoint; + continue; + } + + bool bPendingCurve(false); + + if (bHasCurves) + { + bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx); + bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx); + } + + if (!bPendingCurve) // line segment + { + cairo_line_to(cr, aPoint.getX(), aPoint.getY()); + } + else // cubic bezier segment + { + basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx); + basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx); + + // tdf#99165 if the control points are 'empty', create the mathematical + // correct replacement ones to avoid problems with the graphical sub-system + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if (aCP1.equal(aLast)) + { + aCP1 = aLast + ((aCP2 - aLast) * 0.0005); + } + + if (aCP2.equal(aPoint)) + { + aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005); + } + + cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(), + aPoint.getY()); + } + + aLast = aPoint; + } + + if (bClosePath) + { + cairo_close_path(cr); + } +} + +// split alpha remains as a constant irritant +std::vector<sal_uInt8> createBitmapData(const BitmapEx& rBitmapEx) +{ + const Size& rSizePixel(rBitmapEx.GetSizePixel()); + const bool bAlpha(rBitmapEx.IsAlpha()); + const sal_uInt32 nStride + = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, rSizePixel.Width()); + std::vector<sal_uInt8> aData(nStride * rSizePixel.Height()); + + if (bAlpha) + { + Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap()); + BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap()); + BitmapScopedReadAccess pAlphaReadAccess(aSrcAlpha); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + unsigned char* pPixelData = aData.data() + (nStride * y); + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x)); + const sal_uInt16 nAlpha(255 - aAlpha.GetRed()); + + pPixelData[SVP_CAIRO_RED] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed()); + pPixelData[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen()); + pPixelData[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue()); + pPixelData[SVP_CAIRO_ALPHA] = nAlpha; + pPixelData += 4; + } + } + } + else + { + BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap()); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + unsigned char* pPixelData = aData.data() + (nStride * y); + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + pPixelData[SVP_CAIRO_RED] = aColor.GetRed(); + pPixelData[SVP_CAIRO_GREEN] = aColor.GetGreen(); + pPixelData[SVP_CAIRO_BLUE] = aColor.GetBlue(); + pPixelData[SVP_CAIRO_ALPHA] = 255; + pPixelData += 4; + } + } + } + + return aData; +} +} + +namespace drawinglayer::processor2d +{ +CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT(nullptr) +{ +} + +CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, + cairo_surface_t* pTarget) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT(nullptr) +{ + if (pTarget) + { + cairo_t* pRT = cairo_create(pTarget); + cairo_set_antialias(pRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT + : CAIRO_ANTIALIAS_NONE); + setRenderTarget(pRT); + } +} + +CairoPixelProcessor2D::~CairoPixelProcessor2D() +{ + if (mpRT) + cairo_destroy(mpRT); +} + +void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon()); + + if (!rPolygon.count()) + return; + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + + // TODO: Unfortunately Direct2D paint of one pixel wide lines does not + // correctly and completely blend 100% over the background. Experimenting + // shows that a value around/slightly below 2.0 is needed which hints that + // alpha blending the half-shifted lines (see fAAOffset above) is involved. + // To get correct blending I try to use just wider hairlines for now. This + // may need to be improved - or balanced (trying sqrt(2) now...) + cairo_set_line_width(mpRT, 1.44f); + + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D) +{ + const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon()); + const sal_uInt32 nCount(rPolyPolygon.count()); + + if (!nCount) + return; + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + + for (const auto& rPolygon : rPolyPolygon) + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + cairo_fill(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processBitmapPrimitive2D( + const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + // check if graphic content is inside discrete local ViewPort + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() * rBitmapCandidate.getTransform()); + + if (!rDiscreteViewPort.isEmpty()) + { + basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + + aUnitRange.transform(aLocalTransform); + + if (!aUnitRange.overlaps(rDiscreteViewPort)) + { + // content is outside discrete local ViewPort + return; + } + } + + BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); + + if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty()) + { + return; + } + + if (maBColorModifierStack.count()) + { + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> xTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + processPolyPolygonColorPrimitive2D(*xTemp); + return; + } + } + + // nasty copy of bitmap data + std::vector<sal_uInt8> aPixelData(createBitmapData(aBitmapEx)); + const Size& rSizePixel(aBitmapEx.GetSizePixel()); + cairo_surface_t* pBitmapSurface = cairo_image_surface_create_for_data( + aPixelData.data(), CAIRO_FORMAT_ARGB32, rSizePixel.Width(), rSizePixel.Height(), + cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, rSizePixel.Width())); + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), + aLocalTransform.d(), aLocalTransform.e() + fAAOffset, + aLocalTransform.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + // destinationRectangle is part of transformation above, so use UnitRange + cairo_rectangle(mpRT, 0, 0, 1, 1); + cairo_clip(mpRT); + + cairo_set_source_surface(mpRT, pBitmapSurface, 0, 0); + // get the pattern created by cairo_set_source_surface + cairo_pattern_t* sourcepattern = cairo_get_source(mpRT); + cairo_pattern_get_matrix(sourcepattern, &aMatrix); + // scale to match the current transformation + cairo_matrix_scale(&aMatrix, rSizePixel.Width(), rSizePixel.Height()); + cairo_pattern_set_matrix(sourcepattern, &aMatrix); + + cairo_paint(mpRT); + + cairo_surface_destroy(pBitmapSurface); + + cairo_restore(mpRT); +} + +namespace +{ +// This bit-tweaking looping is unpleasant and unfortunate +void LuminanceToAlpha(cairo_surface_t* pMask) +{ + cairo_surface_flush(pMask); + + int nWidth = cairo_image_surface_get_width(pMask); + int nHeight = cairo_image_surface_get_height(pMask); + int nStride = cairo_image_surface_get_stride(pMask); + unsigned char* mask_surface_data = cairo_image_surface_get_data(pMask); + + // include/basegfx/color/bcolormodifier.hxx + const double nRedMul = 0.2125 / 255.0; + const double nGreenMul = 0.7154 / 255.0; + const double nBlueMul = 0.0721 / 255.0; + for (int y = 0; y < nHeight; ++y) + { + unsigned char* pMaskPixelData = mask_surface_data + (nStride * y); + for (int x = 0; x < nWidth; ++x) + { + double fLuminance = pMaskPixelData[SVP_CAIRO_RED] * nRedMul + + pMaskPixelData[SVP_CAIRO_GREEN] * nGreenMul + + pMaskPixelData[SVP_CAIRO_BLUE] * nBlueMul; + // Only this alpha channel is taken into account by cairo_mask_surface + // so reuse this surface for the alpha result + pMaskPixelData[SVP_CAIRO_ALPHA] = 255.0 * fLuminance; + pMaskPixelData += 4; + } + } + + cairo_surface_mark_dirty(pMask); +} +} + +void CairoPixelProcessor2D::processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + return; + + if (rTransCandidate.getTransparence().empty()) + return; + + cairo_surface_t* pTarget = cairo_get_target(mpRT); + + double clip_x1, clip_x2, clip_y1, clip_y2; + cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2); + + // calculate visible range, create only for that range + basegfx::B2DRange aDiscreteRange( + rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); + aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DRange aViewRange(basegfx::B2DPoint(clip_x1, clip_y1), + basegfx::B2DPoint(clip_x2, clip_y2)); + basegfx::B2DRange aVisibleRange(aDiscreteRange); + aVisibleRange.intersect(aViewRange); + + if (aVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + const basegfx::B2DHomMatrix aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aVisibleRange.getMinX(), -aVisibleRange.getMinY())); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setViewTransformation(aEmbedTransform + * getViewInformation2D().getViewTransformation()); + // draw mask to temporary surface + cairo_surface_t* pMask = cairo_surface_create_similar_image(pTarget, CAIRO_FORMAT_ARGB32, + ceil(aVisibleRange.getWidth()), + ceil(aVisibleRange.getHeight())); + CairoPixelProcessor2D aMaskRenderer(aViewInformation2D, pMask); + aMaskRenderer.process(rTransCandidate.getTransparence()); + + // convert mask to something cairo can use + LuminanceToAlpha(pMask); + + // draw content to temporary surface + cairo_surface_t* pContent = cairo_surface_create_similar( + pTarget, cairo_surface_get_content(pTarget), ceil(aVisibleRange.getWidth()), + ceil(aVisibleRange.getHeight())); + CairoPixelProcessor2D aContent(aViewInformation2D, pContent); + aContent.process(rTransCandidate.getChildren()); + + // munge the temporary surfaces to our target surface + cairo_set_source_surface(mpRT, pContent, aVisibleRange.getMinX(), aVisibleRange.getMinY()); + cairo_mask_surface(mpRT, pMask, aVisibleRange.getMinX(), aVisibleRange.getMinY()); + + cairo_surface_destroy(pContent); + cairo_surface_destroy(pMask); +} + +void CairoPixelProcessor2D::processMaskPrimitive2DPixel( + const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + if (rMaskCandidate.getChildren().empty()) + return; + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (!aMask.count()) + return; + + double clip_x1, clip_x2, clip_y1, clip_y2; + cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2); + + basegfx::B2DRange aMaskRange(aMask.getB2DRange()); + aMaskRange.transform(getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DRange aViewRange(basegfx::B2DPoint(clip_x1, clip_y1), + basegfx::B2DPoint(clip_x2, clip_y2)); + + if (!aViewRange.overlaps(aMaskRange)) + return; + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e(), rObjectToView.f()); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + // put mask as path + for (const auto& rPolygon : aMask) + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + // clip to this mask + cairo_clip(mpRT); + + process(rMaskCandidate.getChildren()); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processPointArrayPrimitive2D( + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions()); + if (rPositions.empty()) + return; + + const basegfx::BColor aPointColor( + maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor())); + cairo_set_source_rgb(mpRT, aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue()); + + // To really paint a single pixel I found nothing better than + // switch off AA and draw a pixel-aligned rectangle + const cairo_antialias_t eOldAAMode(cairo_get_antialias(mpRT)); + cairo_set_antialias(mpRT, CAIRO_ANTIALIAS_NONE); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscretePos(getViewInformation2D().getObjectToViewTransformation() + * pos); + const double fX(ceil(aDiscretePos.getX())); + const double fY(ceil(aDiscretePos.getY())); + + cairo_rectangle(mpRT, fX, fY, 1, 1); + cairo_fill(mpRT); + } + + cairo_set_antialias(mpRT, eOldAAMode); +} + +void CairoPixelProcessor2D::processModifiedColorPrimitive2D( + const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate) +{ + if (!rModifiedCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } +} + +void CairoPixelProcessor2D::processTransformPrimitive2D( + const primitive2d::TransformPrimitive2D& rTransformCandidate) +{ + // remember current transformation and ViewInformation + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); +} + +void CairoPixelProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon()); + const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute()); + + if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0) + { + // no geometry, done + return; + } + + // get some values early that might be used for decisions + const bool bHairline(0.0 == rLineAttribute.getWidth()); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + const double fDiscreteLineWidth( + bHairline + ? 1.0 + : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength()); + + // Here for every combination which the system-specific implementation is not + // capable of visualizing, use the (for decomposable Primitives always possible) + // fallback to the decomposition. + if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5) + { + // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem + // knows that (so far), so fallback to decomposition. This is only needed if + // LineJoin will be used, so also check for discrete LineWidth before falling back + process(rPolygonStrokeCandidate); + return; + } + + // This is a method every system-specific implementation of a decomposable Primitive + // can use to allow simple optical control of paint implementation: + // Create a copy, e.g. change color to 'red' as here and paint before the system + // paints it using the decomposition. That way you can - if active - directly + // optically compare if the system-specific solution is geometrically identical to + // the decomposition (which defines our interpretation that we need to visualize). + // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case + // we create a half-transparent paint to better support visual control + static bool bRenderDecomposeForCompareInRed(false); + + if (bRenderDecomposeForCompareInRed) + { + const attribute::LineAttribute aRed( + basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(), + rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle()); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xCopy( + new primitive2d::PolygonStrokePrimitive2D( + rPolygonStrokeCandidate.getB2DPolygon(), aRed, + rPolygonStrokeCandidate.getStrokeAttribute())); + process(*xCopy); + } + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + // setup line attributes + cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER; + switch (rLineAttribute.getLineJoin()) + { + case basegfx::B2DLineJoin::Bevel: + eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL; + break; + case basegfx::B2DLineJoin::Round: + eCairoLineJoin = CAIRO_LINE_JOIN_ROUND; + break; + case basegfx::B2DLineJoin::NONE: + case basegfx::B2DLineJoin::Miter: + eCairoLineJoin = CAIRO_LINE_JOIN_MITER; + break; + } + + // convert miter minimum angle to miter limit + double fMiterLimit + = 1.0 / sin(std::max(rLineAttribute.getMiterMinimumAngle(), 0.01 * M_PI) / 2.0); + + // setup cap attribute + cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT); + + switch (rLineAttribute.getLineCap()) + { + default: // css::drawing::LineCap_BUTT: + { + eCairoLineCap = CAIRO_LINE_CAP_BUTT; + break; + } + case css::drawing::LineCap_ROUND: + { + eCairoLineCap = CAIRO_LINE_CAP_ROUND; + break; + } + case css::drawing::LineCap_SQUARE: + { + eCairoLineCap = CAIRO_LINE_CAP_SQUARE; + break; + } + } + + basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rLineAttribute.getColor())); + if (bRenderDecomposeForCompareInRed) + aLineColor.setRed(0.5); + + cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue()); + + cairo_set_line_join(mpRT, eCairoLineJoin); + cairo_set_line_cap(mpRT, eCairoLineCap); + + // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D + cairo_set_line_width(mpRT, bHairline ? 1.44 : fDiscreteLineWidth); + cairo_set_miter_limit(mpRT, fMiterLimit); + + const attribute::StrokeAttribute& rStrokeAttribute( + rPolygonStrokeCandidate.getStrokeAttribute()); + const bool bDashUsed(!rStrokeAttribute.isDefault() + && !rStrokeAttribute.getDotDashArray().empty() + && 0.0 < rStrokeAttribute.getFullDotDashLen()); + if (bDashUsed) + { + const std::vector<double>& rStroke = rStrokeAttribute.getDotDashArray(); + cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0); + } + + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processLineRectanglePrimitive2D( + const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D) +{ + if (rLineRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + + const double fDiscreteLineWidth((getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.44, 0.0)) + .getLength()); + cairo_set_line_width(mpRT, fDiscreteLineWidth); + + const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange()); + cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(), + rRange.getHeight()); + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processFilledRectanglePrimitive2D( + const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D) +{ + if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + + const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange()); + cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(), + rRange.getHeight()); + cairo_fill(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processSingleLinePrimitive2D( + const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D) +{ + cairo_save(mpRT); + + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue()); + + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DPoint aStart(rObjectToView * rSingleLinePrimitive2D.getStart()); + const basegfx::B2DPoint aEnd(rObjectToView * rSingleLinePrimitive2D.getEnd()); + + cairo_set_line_width(mpRT, 1.44f); + + cairo_move_to(mpRT, aStart.getX() + fAAOffset, aStart.getY() + fAAOffset); + cairo_line_to(mpRT, aEnd.getX() + fAAOffset, aEnd.getY() + fAAOffset); + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + switch (rCandidate.getPrimitive2DID()) + { + // geometry that *has* to be processed + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + processBitmapPrimitive2D( + static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + processPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + // embedding/groups that *have* to be processed + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + processTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + { + // TODO: fallback is at VclPixelProcessor2D::processInvertPrimitive2D, so + // not in reach. Ignore for now. + // processInvertPrimitive2D(rCandidate); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + processMaskPrimitive2DPixel( + static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + processModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + processTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } +#if 0 + // geometry that *may* be processed due to being able to do it better + // then using the decomposition + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + processMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + { + processBackgroundColorPrimitive2D( + static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate)); + break; + } +#endif + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D: + { + processLineRectanglePrimitive2D( + static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D: + { + processFilledRectanglePrimitive2D( + static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D: + { + processSingleLinePrimitive2D( + static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate)); + break; + } + + // continue with decompose + default: + { + SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( + rCandidate.getPrimitive2DID())); + // process recursively + process(rCandidate); + break; + } + } +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/drawinglayer/source/processor2d/contourextractor2d.cxx b/drawinglayer/source/processor2d/contourextractor2d.cxx index 5fc51695ddc5..65e8ef86a29e 100644 --- a/drawinglayer/source/processor2d/contourextractor2d.cxx +++ b/drawinglayer/source/processor2d/contourextractor2d.cxx @@ -19,7 +19,7 @@ #include <drawinglayer/processor2d/contourextractor2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> @@ -123,12 +123,8 @@ namespace drawinglayer::processor2d const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); // create new local ViewInformation2D - const geometry::ViewInformation2D aViewInformation2D( - getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), - getViewInformation2D().getViewTransformation(), - getViewInformation2D().getViewport(), - getViewInformation2D().getVisualizedPage(), - getViewInformation2D().getViewTime()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); updateViewInformation(aViewInformation2D); // process content diff --git a/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx new file mode 100644 index 000000000000..6bfc95878332 --- /dev/null +++ b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx @@ -0,0 +1,2191 @@ +/* -*- 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 <sal/config.h> + +// win-specific +#include <prewin.h> +#include <d2d1.h> +#include <d2d1_1.h> +#include <postwin.h> + +#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx> +#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx> +#include <sal/log.hxx> +#include <vcl/outdev.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/invertprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/converters.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/svapp.hxx> + +using namespace com::sun::star; + +namespace +{ +class ID2D1GlobalFactoryProvider +{ + sal::systools::COMReference<ID2D1Factory> mpD2DFactory; + +public: + ID2D1GlobalFactoryProvider() + : mpD2DFactory(nullptr) + { + const HRESULT hr(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof(ID2D1Factory), nullptr, + reinterpret_cast<void**>(&mpD2DFactory))); + + if (!SUCCEEDED(hr)) + mpD2DFactory.clear(); + } + + sal::systools::COMReference<ID2D1Factory>& getID2D1Factory() { return mpD2DFactory; } +}; + +ID2D1GlobalFactoryProvider aID2D1GlobalFactoryProvider; + +class ID2D1GlobalRenderTargetProvider +{ + sal::systools::COMReference<ID2D1DCRenderTarget> mpID2D1DCRenderTarget; + +public: + ID2D1GlobalRenderTargetProvider() + : mpID2D1DCRenderTarget() + { + } + + sal::systools::COMReference<ID2D1DCRenderTarget>& getID2D1DCRenderTarget() + { + if (!mpID2D1DCRenderTarget && aID2D1GlobalFactoryProvider.getID2D1Factory()) + { + const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT)); + + const HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget( + &aRTProps, &mpID2D1DCRenderTarget)); + + // interestingly this ID2D1DCRenderTarget already works and can hold + // created ID2D1Bitmap(s) in RenderTarget-specific form, *without* + // any call to "BindDC", thus *without* the need of a real HDC - nice :-) + // When that would be needed, Application::GetDefaultDevice() would need + // to have a HDC that is valid during LO's lifetime. + + if (!SUCCEEDED(hr)) + mpID2D1DCRenderTarget.clear(); + } + + return mpID2D1DCRenderTarget; + } +}; + +ID2D1GlobalRenderTargetProvider aID2D1GlobalRenderTargetProvider; + +class SystemDependentData_ID2D1PathGeometry : public basegfx::SystemDependentData +{ +private: + sal::systools::COMReference<ID2D1PathGeometry> mpID2D1PathGeometry; + +public: + SystemDependentData_ID2D1PathGeometry( + sal::systools::COMReference<ID2D1PathGeometry>& rID2D1PathGeometry) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , mpID2D1PathGeometry(rID2D1PathGeometry) + { + } + + const sal::systools::COMReference<ID2D1PathGeometry>& getID2D1PathGeometry() const + { + return mpID2D1PathGeometry; + } + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +sal_Int64 SystemDependentData_ID2D1PathGeometry::estimateUsageInBytes() const +{ + sal_Int64 aRetval(0); + + if (getID2D1PathGeometry()) + { + UINT32 nCount(0); + const HRESULT hr(getID2D1PathGeometry()->GetSegmentCount(&nCount)); + + if (SUCCEEDED(hr)) + { + // without completely receiving and tracing the GeometrySink + // do a rough estimation - each segment is 2D, so has two doubles. + // Some are beziers, so add some guessed buffer for two additional + // control points + aRetval = static_cast<sal_Int64>(nCount) * (6 * sizeof(double)); + } + } + + return aRetval; +} + +basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D& rViewInformation, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if (bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation(); + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +void addB2DPolygonToPathGeometry(sal::systools::COMReference<ID2D1GeometrySink>& rSink, + const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D* pViewInformation) +{ + const sal_uInt32 nPointCount(rPolygon.count()); + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1); + basegfx::B2DCubicBezier aEdge; + + for (sal_uInt32 a(0); a < nEdgeCount; a++) + { + rPolygon.getBezierSegment(a, aEdge); + + const basegfx::B2DPoint aEndPoint( + nullptr == pViewInformation + ? aEdge.getEndPoint() + : impPixelSnap(rPolygon, *pViewInformation, (a + 1) % nPointCount)); + + if (aEdge.isBezier()) + { + rSink->AddBezier( + D2D1::BezierSegment(D2D1::Point2F(aEdge.getControlPointA().getX(), + aEdge.getControlPointA().getY()), //C1 + D2D1::Point2F(aEdge.getControlPointB().getX(), + aEdge.getControlPointB().getY()), //c2 + D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY()))); //end + } + else + { + rSink->AddLine(D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY())); + } + } +} + +std::shared_ptr<SystemDependentData_ID2D1PathGeometry> +getOrCreatePathGeometry(const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) +{ + // try to access buffered data + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + rPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>()); + + if (pSystemDependentData_ID2D1PathGeometry) + { + if (rViewInformation.getPixelSnapHairline()) + { + // do not buffer when PixelSnap is active + pSystemDependentData_ID2D1PathGeometry.reset(); + } + else + { + // use and return buffered data + return pSystemDependentData_ID2D1PathGeometry; + } + } + + sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry; + HRESULT hr( + aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry)); + const sal_uInt32 nPointCount(rPolygon.count()); + + if (SUCCEEDED(hr) && nPointCount) + { + sal::systools::COMReference<ID2D1GeometrySink> pSink; + hr = pID2D1PathGeometry->Open(&pSink); + + if (SUCCEEDED(hr) && pSink) + { + const basegfx::B2DPoint aStart(rViewInformation.getPixelSnapHairline() + ? rPolygon.getB2DPoint(0) + : impPixelSnap(rPolygon, rViewInformation, 0)); + + pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()), + D2D1_FIGURE_BEGIN_HOLLOW); + addB2DPolygonToPathGeometry(pSink, rPolygon, &rViewInformation); + pSink->EndFigure(rPolygon.isClosed() ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN); + pSink->Close(); + } + } + + // add to buffering mechanism + if (pID2D1PathGeometry) + { + if (rViewInformation.getPixelSnapHairline() || nPointCount <= 4) + { + // do not buffer when PixelSnap is active or small polygon + return std::make_shared<SystemDependentData_ID2D1PathGeometry>(pID2D1PathGeometry); + } + else + { + return rPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>( + pID2D1PathGeometry); + } + } + + return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>(); +} + +std::shared_ptr<SystemDependentData_ID2D1PathGeometry> +getOrCreateFillGeometry(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + // try to access buffered data + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + rPolyPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>()); + + if (pSystemDependentData_ID2D1PathGeometry) + { + // use and return buffered data + return pSystemDependentData_ID2D1PathGeometry; + } + + sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry; + HRESULT hr( + aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry)); + const sal_uInt32 nCount(rPolyPolygon.count()); + + if (SUCCEEDED(hr) && nCount) + { + sal::systools::COMReference<ID2D1GeometrySink> pSink; + hr = pID2D1PathGeometry->Open(&pSink); + + if (SUCCEEDED(hr) && pSink) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + const basegfx::B2DPolygon& rPolygon(rPolyPolygon.getB2DPolygon(a)); + const sal_uInt32 nPointCount(rPolygon.count()); + + if (nPointCount) + { + const basegfx::B2DPoint aStart(rPolygon.getB2DPoint(0)); + + pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()), + D2D1_FIGURE_BEGIN_FILLED); + addB2DPolygonToPathGeometry(pSink, rPolygon, nullptr); + pSink->EndFigure(D2D1_FIGURE_END_CLOSED); + } + } + + pSink->Close(); + } + } + + // add to buffering mechanism + if (pID2D1PathGeometry) + { + return rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>( + pID2D1PathGeometry); + } + + return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>(); +} + +class SystemDependentData_ID2D1Bitmap : public basegfx::SystemDependentData +{ +private: + sal::systools::COMReference<ID2D1Bitmap> mpD2DBitmap; + const std::shared_ptr<SalBitmap> maAssociatedAlpha; + +public: + SystemDependentData_ID2D1Bitmap(sal::systools::COMReference<ID2D1Bitmap>& rD2DBitmap, + const std::shared_ptr<SalBitmap>& rAssociatedAlpha) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , mpD2DBitmap(rD2DBitmap) + , maAssociatedAlpha(rAssociatedAlpha) + { + } + + const sal::systools::COMReference<ID2D1Bitmap>& getID2D1Bitmap() const { return mpD2DBitmap; } + const std::shared_ptr<SalBitmap>& getAssociatedAlpha() const { return maAssociatedAlpha; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +sal_Int64 SystemDependentData_ID2D1Bitmap::estimateUsageInBytes() const +{ + sal_Int64 aRetval(0); + + if (getID2D1Bitmap()) + { + // use factor 4 for RGBA_8 as estimation + const D2D1_SIZE_U aSizePixel(getID2D1Bitmap()->GetPixelSize()); + aRetval = static_cast<sal_Int64>(aSizePixel.width) + * static_cast<sal_Int64>(aSizePixel.height) * 4; + } + + return aRetval; +} + +sal::systools::COMReference<ID2D1Bitmap> createB2DBitmap(const BitmapEx& rBitmapEx) +{ + const Size& rSizePixel(rBitmapEx.GetSizePixel()); + const bool bAlpha(rBitmapEx.IsAlpha()); + const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height()); + std::unique_ptr<sal_uInt32[]> aData(new sal_uInt32[nPixelCount]); + sal_uInt32* pTarget = aData.get(); + + if (bAlpha) + { + Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap()); + BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap()); + BitmapScopedReadAccess pAlphaReadAccess(aSrcAlpha); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x)); + const sal_uInt16 nAlpha(aAlpha.GetRed()); + + *pTarget++ = sal_uInt32(BitmapColor( + ColorAlpha, sal_uInt8((sal_uInt16(aColor.GetRed()) * nAlpha) >> 8), + sal_uInt8((sal_uInt16(aColor.GetGreen()) * nAlpha) >> 8), + sal_uInt8((sal_uInt16(aColor.GetBlue()) * nAlpha) >> 8), aAlpha.GetRed())); + } + } + } + else + { + BitmapScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.GetBitmap())); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + *pTarget++ = sal_uInt32(aColor); + } + } + } + + // use GlobalRenderTarget to allow usage combined with + // the Direct2D CreateSharedBitmap-mechanism. This is needed + // since ID2D1Bitmap is a ID2D1RenderTarget-dependent resource + // and thus - in principle - would have to be re-created for + // *each* new ID2D1RenderTarget, that means for *each* new + // target HDC, resp. OutputDevice + sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap; + + if (aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget()) + { + const HRESULT hr(aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget()->CreateBitmap( + D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0], + rSizePixel.Width() * sizeof(sal_uInt32), + D2D1::BitmapProperties( + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, // DXGI_FORMAT + bAlpha ? D2D1_ALPHA_MODE_PREMULTIPLIED + : D2D1_ALPHA_MODE_IGNORE)), // D2D1_ALPHA_MODE + &pID2D1Bitmap)); + + if (!SUCCEEDED(hr)) + pID2D1Bitmap.clear(); + } + + return pID2D1Bitmap; +} + +sal::systools::COMReference<ID2D1Bitmap> +getOrCreateB2DBitmap(sal::systools::COMReference<ID2D1RenderTarget>& rRT, const BitmapEx& rBitmapEx) +{ + const basegfx::SystemDependentDataHolder* pHolder( + rBitmapEx.GetBitmap().accessSystemDependentDataHolder()); + std::shared_ptr<SystemDependentData_ID2D1Bitmap> pSystemDependentData_ID2D1Bitmap; + + if (nullptr != pHolder) + { + // try to access SystemDependentDataHolder and buffered data + pSystemDependentData_ID2D1Bitmap + = std::static_pointer_cast<SystemDependentData_ID2D1Bitmap>( + pHolder->getSystemDependentData( + typeid(SystemDependentData_ID2D1Bitmap).hash_code())); + + // check data validity for associated Alpha + if (pSystemDependentData_ID2D1Bitmap + && pSystemDependentData_ID2D1Bitmap->getAssociatedAlpha() + != rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap()) + { + // AssociatedAlpha did change, data invalid + pSystemDependentData_ID2D1Bitmap.reset(); + } + } + + if (!pSystemDependentData_ID2D1Bitmap) + { + // have to create newly + sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap(createB2DBitmap(rBitmapEx)); + + if (pID2D1Bitmap) + { + // creation worked, create SystemDependentData_ID2D1Bitmap + pSystemDependentData_ID2D1Bitmap = std::make_shared<SystemDependentData_ID2D1Bitmap>( + pID2D1Bitmap, rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap()); + + // only add if feasible + if (nullptr != pHolder + && pSystemDependentData_ID2D1Bitmap->calculateCombinedHoldCyclesInSeconds() > 0) + { + basegfx::SystemDependentData_SharedPtr r2(pSystemDependentData_ID2D1Bitmap); + const_cast<basegfx::SystemDependentDataHolder*>(pHolder) + ->addOrReplaceSystemDependentData(r2); + } + } + } + + sal::systools::COMReference<ID2D1Bitmap> pWrappedD2DBitmap; + + if (pSystemDependentData_ID2D1Bitmap) + { + // embed to CreateSharedBitmap, that makes it usable on + // the specified RenderTarget + const HRESULT hr(rRT->CreateSharedBitmap( + __uuidof(ID2D1Bitmap), + static_cast<void*>(pSystemDependentData_ID2D1Bitmap->getID2D1Bitmap()), nullptr, + &pWrappedD2DBitmap)); + + if (!SUCCEEDED(hr)) + pWrappedD2DBitmap.clear(); + } + + return pWrappedD2DBitmap; +} + +// This is a simple local derivation of D2DPixelProcessor2D to be used +// when sub-content needs to be rendered to pixels. Hand over the adapted +// ViewInformation2D, a pixel size and the parent RenderTarget. It will +// locally create and use a ID2D1BitmapRenderTarget to render the stuff +// (you need to call process() with the primitives to be painted of +// course). Then use the local helper getID2D1Bitmap() to access the +// ID2D1Bitmap which was the target of that operation. +class D2DBitmapPixelProcessor2D final : public drawinglayer::processor2d::D2DPixelProcessor2D +{ + // the local ID2D1BitmapRenderTarget + sal::systools::COMReference<ID2D1BitmapRenderTarget> mpBitmapRenderTarget; + +public: + // helper class to create another instance of D2DPixelProcessor2D for + // creating helper-ID2D1Bitmap's for a given ID2D1RenderTarget + D2DBitmapPixelProcessor2D(const drawinglayer::geometry::ViewInformation2D& rViewInformation, + sal_uInt32 nWidth, sal_uInt32 nHeight, + const sal::systools::COMReference<ID2D1RenderTarget>& rParent) + : drawinglayer::processor2d::D2DPixelProcessor2D(rViewInformation) + , mpBitmapRenderTarget() + { + if (0 == nWidth || 0 == nHeight) + { + // no width/height, done + increaseError(); + } + + if (!hasError()) + { + // Allocate compatible RGBA render target + const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(nWidth, nHeight)); + const HRESULT hr(rParent->CreateCompatibleRenderTarget( + nullptr, &aRenderTargetSizePixel, nullptr, + D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &mpBitmapRenderTarget)); + + if (!SUCCEEDED(hr) || nullptr == mpBitmapRenderTarget) + { + // did not work, done + increaseError(); + } + else + { + sal::systools::COMReference<ID2D1RenderTarget> pRT; + mpBitmapRenderTarget->QueryInterface(__uuidof(ID2D1RenderTarget), + reinterpret_cast<void**>(&pRT)); + setRenderTarget(pRT); + } + } + + if (hasRenderTarget()) + { + // set Viewort if none was given. We have a fixed pixel target, s we know the + // exact Viewport to work on + if (getViewInformation2D().getViewport().isEmpty()) + { + drawinglayer::geometry::ViewInformation2D aViewInformation(getViewInformation2D()); + basegfx::B2DRange aViewport(0.0, 0.0, nWidth, nHeight); + basegfx::B2DHomMatrix aInvViewTransform(aViewInformation.getViewTransformation()); + + aInvViewTransform.invert(); + aViewport.transform(aInvViewTransform); + aViewInformation.setViewport(aViewport); + updateViewInformation(aViewInformation); + } + + // clear as render preparation + getRenderTarget()->BeginDraw(); + getRenderTarget()->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); + getRenderTarget()->EndDraw(); + } + } + + sal::systools::COMReference<ID2D1Bitmap> getID2D1Bitmap() const + { + sal::systools::COMReference<ID2D1Bitmap> pResult; + + // access the resulting bitmap if exists + if (mpBitmapRenderTarget) + { + mpBitmapRenderTarget->GetBitmap(&pResult); + } + + return pResult; + } +}; + +bool createBitmapSubContent(sal::systools::COMReference<ID2D1Bitmap>& rResult, + basegfx::B2DRange& rDiscreteVisibleRange, + const drawinglayer::primitive2d::Primitive2DContainer& rContent, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, + const sal::systools::COMReference<ID2D1RenderTarget>& rRenderTarget) +{ + if (rContent.empty() || !rRenderTarget) + { + // no content or no render target, done + return false; + } + + drawinglayer::processor2d::calculateDiscreteVisibleRange( + rDiscreteVisibleRange, rContent.getB2DRange(rViewInformation2D), rViewInformation2D); + + if (rDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return false; + } + + // Use a temporary second instance of a D2DBitmapPixelProcessor2D with adapted + // ViewInformation2D, it will create the needed ID2D1BitmapRenderTarget + // locally and Clear() it. + drawinglayer::geometry::ViewInformation2D aAdaptedViewInformation2D(rViewInformation2D); + const double fTargetWidth(ceil(rDiscreteVisibleRange.getWidth())); + const double fTargetHeight(ceil(rDiscreteVisibleRange.getHeight())); + + { + // create adapted ViewTransform, needs to be offset in discrete coordinates, + // so multiply from left + basegfx::B2DHomMatrix aAdapted( + basegfx::utils::createTranslateB2DHomMatrix(-rDiscreteVisibleRange.getMinX(), + -rDiscreteVisibleRange.getMinY()) + * rViewInformation2D.getViewTransformation()); + aAdaptedViewInformation2D.setViewTransformation(aAdapted); + + // reset Viewport (world coordinates), so the helper renderer will create it's + // own based on it's given internal discrete size + aAdaptedViewInformation2D.setViewport(basegfx::B2DRange()); + } + + D2DBitmapPixelProcessor2D aSubContentRenderer(aAdaptedViewInformation2D, fTargetWidth, + fTargetHeight, rRenderTarget); + + if (!aSubContentRenderer.valid()) + { + // did not work, done + return false; + } + + // render sub-content recursively + aSubContentRenderer.process(rContent); + + // grab Bitmap & prepare results from RGBA content rendering + rResult = aSubContentRenderer.getID2D1Bitmap(); + return true; +} +} + +namespace drawinglayer::processor2d +{ +D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT() + , mnRecursionCounter(0) + , mnErrorCounter(0) +{ +} + +D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, + HDC aHdc) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT() + , mnRecursionCounter(0) + , mnErrorCounter(0) +{ + sal::systools::COMReference<ID2D1DCRenderTarget> pDCRT; + tools::Long aOutWidth(0), aOutHeight(0); + + if (aHdc) + { + aOutWidth = GetDeviceCaps(aHdc, HORZRES); + aOutHeight = GetDeviceCaps(aHdc, VERTRES); + } + + if (aOutWidth > 0 && aOutHeight > 0 && aID2D1GlobalFactoryProvider.getID2D1Factory()) + { + const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT)); + + const HRESULT hr( + aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(&aRTProps, &pDCRT)); + + if (!SUCCEEDED(hr)) + pDCRT.clear(); + } + + if (pDCRT) + { + const RECT rc( + { 0, 0, o3tl::narrowing<LONG>(aOutWidth), o3tl::narrowing<LONG>(aOutHeight) }); + const HRESULT hr(pDCRT->BindDC(aHdc, &rc)); + + if (!SUCCEEDED(hr)) + pDCRT.clear(); + } + + if (pDCRT) + { + if (rViewInformation.getUseAntiAliasing()) + { + D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + pDCRT->SetAntialiasMode(eAAMode); + } + else + { + D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_ALIASED; + pDCRT->SetAntialiasMode(eAAMode); + } + + // since ID2D1DCRenderTarget depends on the transformation + // set at hdc, be careful and reset it to identity + XFORM aXForm; + aXForm.eM11 = 1.0; + aXForm.eM12 = 0.0; + aXForm.eM21 = 0.0; + aXForm.eM22 = 1.0; + aXForm.eDx = 0.0; + aXForm.eDy = 0.0; + SetWorldTransform(aHdc, &aXForm); + } + + if (pDCRT) + { + sal::systools::COMReference<ID2D1RenderTarget> pRT; + pDCRT->QueryInterface(__uuidof(ID2D1RenderTarget), reinterpret_cast<void**>(&pRT)); + setRenderTarget(pRT); + } + else + { + increaseError(); + } +} + +void D2DPixelProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon()); + + if (!rPolygon.count()) + { + // no geometry, done + return; + } + + bool bDone(false); + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + getOrCreatePathGeometry(rPolygon, getViewInformation2D())); + + if (pSystemDependentData_ID2D1PathGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset), + &pTransformedGeometry)); + + if (SUCCEEDED(hr) && pTransformedGeometry) + { + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush); + + if (SUCCEEDED(hr) && pColorBrush) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + // TODO: Unfortunately Direct2D paint of one pixel wide lines does not + // correctly and completely blend 100% over the background. Experimenting + // shows that a value around/slightly below 2.0 is needed which hints that + // alpha blending the half-shifted lines (see fAAOffset above) is involved. + // To get correct blending I try to use just wider hairlines for now. This + // may need to be improved - or balanced (trying sqrt(2) now...) + getRenderTarget()->DrawGeometry(pTransformedGeometry, pColorBrush, 1.44f); + bDone = true; + } + } + } + + if (!bDone) + increaseError(); +} + +bool D2DPixelProcessor2D::drawPolyPolygonColorTransformed( + const basegfx::B2DHomMatrix& rTansformation, const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::BColor& rColor) +{ + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + getOrCreateFillGeometry(rPolyPolygon)); + + if (pSystemDependentData_ID2D1PathGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + basegfx::B2DHomMatrix aTansformation(getViewInformation2D().getObjectToViewTransformation() + * rTansformation); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(aTansformation.a(), aTansformation.b(), aTansformation.c(), + aTansformation.d(), aTansformation.e() + fAAOffset, + aTansformation.f() + fAAOffset), + &pTransformedGeometry)); + + if (SUCCEEDED(hr) && pTransformedGeometry) + { + const basegfx::BColor aFillColor(maBColorModifierStack.getModifiedColor(rColor)); + const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(), + aFillColor.getBlue()); + + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush); + + if (SUCCEEDED(hr) && pColorBrush) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->FillGeometry(pTransformedGeometry, pColorBrush); + return true; + } + } + } + + return false; +} + +void D2DPixelProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D) +{ + const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon()); + const sal_uInt32 nCount(rPolyPolygon.count()); + + if (!nCount) + { + // no geometry, done + return; + } + + const bool bDone(drawPolyPolygonColorTransformed(basegfx::B2DHomMatrix(), rPolyPolygon, + rPolyPolygonColorPrimitive2D.getBColor())); + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processBitmapPrimitive2D( + const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + // check if graphic content is inside discrete local ViewPort + if (!getViewInformation2D().getDiscreteViewport().isEmpty()) + { + // calculate logic object range, remember: the helper below will + // transform using getObjectToViewTransformation, so the bitmap-local + // transform would be missing + basegfx::B2DRange aDiscreteVisibleRange(basegfx::B2DRange::getUnitB2DRange()); + aDiscreteVisibleRange.transform(rBitmapCandidate.getTransform()); + + // calculate visible range + calculateDiscreteVisibleRange(aDiscreteVisibleRange, aDiscreteVisibleRange, + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + } + + BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); + + if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty()) + { + // no pixel data, done + return; + } + + if (maBColorModifierStack.count()) + { + // need to apply ColorModifier to Bitmap data + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it (any input works) + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + + // draw as Polygon, done + processPolyPolygonColorPrimitive2D(*aTemp); + return; + } + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), aBitmapEx)); + + if (pD2DBitmap) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() + * rBitmapCandidate.getTransform()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + + // destinationRectangle is part of transformation above, so use UnitRange + getRenderTarget()->DrawBitmap(pD2DBitmap, D2D1::RectF(0.0, 0.0, 1.0, 1.0)); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_Direct( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface. + // Only then can we use ID2D1Effect/CLSID_D2D1LuminanceToAlpha and it makes + // sense to try to do it this way in this implementation + sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext; + getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext), + reinterpret_cast<void**>(&pID2D1DeviceContext)); + sal::systools::COMReference<ID2D1Bitmap> pRetval; + + if (!pID2D1DeviceContext) + { + // no, done - tell caller to use fallback by returning empty - we have + // not the preconditions for this + return pRetval; + } + + // Release early + pID2D1DeviceContext.clear(); + basegfx::B2DRange aDiscreteVisibleRange; + + if (!createBitmapSubContent(pRetval, aDiscreteVisibleRange, rTransCandidate.getTransparence(), + getViewInformation2D(), getRenderTarget()) + || !pRetval) + { + // return of false means no display needed, return + return pRetval; + } + + // Now we need a target to render this to, using the ID2D1Effect tooling. + // We can directly apply the effect to an alpha-only 8bit target here, + // so create one (no RGBA needed for this). + // We need another render target: I tried to render pInBetweenResult + // to pContent again, but that does not work due to the bitmap + // fetched being probably only an internal reference to the + // ID2D1BitmapRenderTarget, thus it would draw onto itself -> chaos + sal::systools::COMReference<ID2D1BitmapRenderTarget> pContent; + const D2D1_PIXEL_FORMAT aAlphaFormat( + D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT)); + const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(ceil(aDiscreteVisibleRange.getWidth()), + ceil(aDiscreteVisibleRange.getHeight()))); + const HRESULT hr(getRenderTarget()->CreateCompatibleRenderTarget( + nullptr, &aRenderTargetSizePixel, &aAlphaFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, + &pContent)); + + if (SUCCEEDED(hr) && pContent) + { + // try to access ID2D1DeviceContext of that target, we need that *now* + pContent->QueryInterface(__uuidof(ID2D1DeviceContext), + reinterpret_cast<void**>(&pID2D1DeviceContext)); + + if (pID2D1DeviceContext) + { + // create the effect + sal::systools::COMReference<ID2D1Effect> pLuminanceToAlpha; + pID2D1DeviceContext->CreateEffect(CLSID_D2D1LuminanceToAlpha, &pLuminanceToAlpha); + + if (pLuminanceToAlpha) + { + // chain effect stuff together & paint it + pLuminanceToAlpha->SetInput(0, pRetval); + + pID2D1DeviceContext->BeginDraw(); + pID2D1DeviceContext->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); + pID2D1DeviceContext->DrawImage(pLuminanceToAlpha); + pID2D1DeviceContext->EndDraw(); + + // grab result + pContent->GetBitmap(&pRetval); + } + } + } + + return pRetval; +} + +sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_B2DBitmap( + const primitive2d::TransparencePrimitive2D& rTransCandidate, + const basegfx::B2DRange& rVisibleRange, D2D1_MATRIX_3X2_F& rMaskScale) +{ + // Use this fallback that will also use a pixel processor indirectly, + // but allows to get the AlphaMask as vcl Bitmap using existing tooling + const sal_uInt32 nDiscreteClippedWidth(ceil(rVisibleRange.getWidth())); + const sal_uInt32 nDiscreteClippedHeight(ceil(rVisibleRange.getHeight())); + const sal_uInt32 nMaximumQuadraticPixels(250000); + + // Embed content graphics to TransformPrimitive2D + const basegfx::B2DHomMatrix aAlphaEmbedding( + basegfx::utils::createTranslateB2DHomMatrix(-rVisibleRange.getMinX(), + -rVisibleRange.getMinY()) + * getViewInformation2D().getObjectToViewTransformation()); + const primitive2d::Primitive2DReference xAlphaEmbedRef(new primitive2d::TransformPrimitive2D( + aAlphaEmbedding, + drawinglayer::primitive2d::Primitive2DContainer(rTransCandidate.getTransparence()))); + drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq{ xAlphaEmbedRef }; + + // use empty ViewInformation to have neutral transformation + const geometry::ViewInformation2D aEmptyViewInformation2D; + + // use new mode to create AlphaChannel (not just AlphaMask) for transparency channel + const AlphaMask aAlpha(::drawinglayer::createAlphaMask( + std::move(xEmbedSeq), aEmptyViewInformation2D, nDiscreteClippedWidth, + nDiscreteClippedHeight, nMaximumQuadraticPixels, true)); + sal::systools::COMReference<ID2D1Bitmap> pRetval; + + if (aAlpha.IsEmpty()) + { + // if we have no content we are done + return pRetval; + } + + // use alpha data to create the ID2D1Bitmap + const Size& rSizePixel(aAlpha.GetSizePixel()); + const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height()); + std::unique_ptr<sal_uInt8[]> aData(new sal_uInt8[nPixelCount]); + sal_uInt8* pTarget = aData.get(); + Bitmap aSrcAlpha(aAlpha.GetBitmap()); + BitmapScopedReadAccess pReadAccess(aSrcAlpha); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + *pTarget++ = aColor.GetLuminance(); + } + } + + const D2D1_BITMAP_PROPERTIES aBmProps(D2D1::BitmapProperties( + D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED))); + const HRESULT hr(getRenderTarget()->CreateBitmap( + D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0], + rSizePixel.Width() * sizeof(sal_uInt8), &aBmProps, &pRetval)); + + if (!SUCCEEDED(hr) || !pRetval) + { + // did not work, done + return pRetval; + } + + // create needed adapted transformation for alpha brush. + // We may have to take a corrective scaling into account when the + // MaximumQuadraticPixel limit was used/triggered + const Size& rBitmapExSizePixel(aAlpha.GetSizePixel()); + + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) + { + // scale in X and Y should be the same (see fReduceFactor in createAlphaMask), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + const double fScale(1.0 / ((fScaleX + fScaleY) * 0.5)); + rMaskScale = D2D1::Matrix3x2F::Scale(fScale, fScale); + } + + return pRetval; +} + +void D2DPixelProcessor2D::processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + { + // no content, done + return; + } + + if (rTransCandidate.getTransparence().empty()) + { + // no mask (so nothing visible), done + return; + } + + // calculate visible range, create only for that range + basegfx::B2DRange aDiscreteVisibleRange; + calculateDiscreteVisibleRange(aDiscreteVisibleRange, + rTransCandidate.getChildren().getB2DRange(getViewInformation2D()), + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + // try to create directly, this needs the current mpRT to be a ID2D1DeviceContext/d2d1_1 + // what is not guaranteed but usually works for more modern windows (after 7) + sal::systools::COMReference<ID2D1Bitmap> pAlphaBitmap(implCreateAlpha_Direct(rTransCandidate)); + D2D1_MATRIX_3X2_F aMaskScale(D2D1::Matrix3x2F::Identity()); + + if (!pAlphaBitmap) + { + // did not work, use more expensive fallback to existing tooling + pAlphaBitmap + = implCreateAlpha_B2DBitmap(rTransCandidate, aDiscreteVisibleRange, aMaskScale); + } + + if (!pAlphaBitmap) + { + // could not create alpha channel, error + increaseError(); + return; + } + + sal::systools::COMReference<ID2D1Layer> pLayer; + HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer)); + bool bDone(false); + + if (SUCCEEDED(hr) && pLayer) + { + sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush; + hr = getRenderTarget()->CreateBitmapBrush(pAlphaBitmap, &pBitmapBrush); + + if (SUCCEEDED(hr) && pBitmapBrush) + { + // apply MaskScale to Brush, maybe used if implCreateAlpha_B2DBitmap was needed + pBitmapBrush->SetTransform(aMaskScale); + + // need to set transform offset for Layer initialization, we work + // in discrete device coordinates + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Translation( + floor(aDiscreteVisibleRange.getMinX()), floor(aDiscreteVisibleRange.getMinY()))); + + getRenderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::Matrix3x2F::Identity(), 1.0, + pBitmapBrush), + pLayer); + + // ... but need to reset to paint content unchanged + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + + // draw content recursively + process(rTransCandidate.getChildren()); + + getRenderTarget()->PopLayer(); + bDone = true; + } + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + { + // no content, done + return; + } + + if (0.0 == rTransCandidate.getTransparence()) + { + // not transparent at all, use content + process(rTransCandidate.getChildren()); + return; + } + + if (rTransCandidate.getTransparence() < 0.0 || rTransCandidate.getTransparence() > 1.0) + { + // invalid transparence, done + return; + } + + // calculate visible range + basegfx::B2DRange aDiscreteVisibleRange; + calculateDiscreteVisibleRange(aDiscreteVisibleRange, + rTransCandidate.getChildren().getB2DRange(getViewInformation2D()), + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Layer> pLayer; + const HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer)); + + if (SUCCEEDED(hr) && pLayer) + { + // need to set correct transform for Layer initialization, we work + // in discrete device coordinates + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->PushLayer( + D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::IdentityMatrix(), + 1.0 - rTransCandidate.getTransparence()), // opacity + pLayer); + process(rTransCandidate.getChildren()); + getRenderTarget()->PopLayer(); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processMaskPrimitive2DPixel( + const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + if (rMaskCandidate.getChildren().empty()) + { + // no content, done + return; + } + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (!aMask.count()) + { + // no mask (so nothing inside), done + return; + } + + // calculate visible range + basegfx::B2DRange aDiscreteVisibleRange; + calculateDiscreteVisibleRange(aDiscreteVisibleRange, aMask.getB2DRange(), + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + bool bDone(false); + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1MaskGeometry( + getOrCreateFillGeometry(rMaskCandidate.getMask())); + + if (pSystemDependentData_ID2D1MaskGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedMaskGeometry; + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1MaskGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e(), rObjectToView.f()), + &pTransformedMaskGeometry)); + + if (SUCCEEDED(hr) && pTransformedMaskGeometry) + { + sal::systools::COMReference<ID2D1Layer> pLayer; + hr = getRenderTarget()->CreateLayer(nullptr, &pLayer); + + if (SUCCEEDED(hr) && pLayer) + { + // need to set correct transform for Layer initialization, we work + // in discrete device coordinates + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->PushLayer( + D2D1::LayerParameters(D2D1::InfiniteRect(), pTransformedMaskGeometry), pLayer); + process(rMaskCandidate.getChildren()); + getRenderTarget()->PopLayer(); + bDone = true; + } + } + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processPointArrayPrimitive2D( + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions()); + + if (rPositions.empty()) + { + // no geometry, done + return; + } + + const basegfx::BColor aPointColor( + maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor())); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + D2D1::ColorF aD2DColor(aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue()); + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + + // To really paint a single pixel I found nothing better than + // switch off AA and draw a pixel-aligned rectangle + const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode()); + getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscretePos( + getViewInformation2D().getObjectToViewTransformation() * pos); + const double fX(ceil(aDiscretePos.getX())); + const double fY(ceil(aDiscretePos.getY())); + const D2D1_RECT_F rect = { FLOAT(fX), FLOAT(fY), FLOAT(fX), FLOAT(fY) }; + + getRenderTarget()->DrawRectangle(&rect, pColorBrush); + } + + getRenderTarget()->SetAntialiasMode(aOldAAMode); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processMarkerArrayPrimitive2D( + const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions(rMarkerArrayCandidate.getPositions()); + + if (rPositions.empty()) + { + // no geometry, done + return; + } + + const BitmapEx& rMarker(rMarkerArrayCandidate.getMarker()); + + if (rMarker.IsEmpty()) + { + // no marker defined, done + return; + } + + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), rMarker)); + bool bDone(false); + + if (pD2DBitmap) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + const Size& rSizePixel(rMarker.GetSizePixel()); + const tools::Long nMiX((rSizePixel.Width() / 2) + 1); + const tools::Long nMiY((rSizePixel.Height() / 2) + 1); + const tools::Long nPlX(rSizePixel.Width() - nMiX); + const tools::Long nPlY(rSizePixel.Height() - nMiY); + + // draw with non-AA to show unhampered, clear, non-scaled marker + const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode()); + getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscretePos( + getViewInformation2D().getObjectToViewTransformation() * pos); + const double fX(ceil(aDiscretePos.getX())); + const double fY(ceil(aDiscretePos.getY())); + const D2D1_RECT_F rect + = { FLOAT(fX - nMiX), FLOAT(fY - nMiY), FLOAT(fX + nPlX), FLOAT(fY + nPlY) }; + + getRenderTarget()->DrawBitmap(pD2DBitmap, &rect); + } + + getRenderTarget()->SetAntialiasMode(aOldAAMode); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processBackgroundColorPrimitive2D( + const primitive2d::BackgroundColorPrimitive2D& rBackgroundColorCandidate) +{ + // check for allowed range [0.0 .. 1.0[ + if (rBackgroundColorCandidate.getTransparency() < 0.0 + || rBackgroundColorCandidate.getTransparency() >= 1.0) + return; + + const D2D1::ColorF aD2DColor(rBackgroundColorCandidate.getBColor().getRed(), + rBackgroundColorCandidate.getBColor().getGreen(), + rBackgroundColorCandidate.getBColor().getBlue(), + 1.0 - rBackgroundColorCandidate.getTransparency()); + + getRenderTarget()->Clear(aD2DColor); +} + +void D2DPixelProcessor2D::processModifiedColorPrimitive2D( + const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate) +{ + if (!rModifiedCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } +} + +void D2DPixelProcessor2D::processTransformPrimitive2D( + const primitive2d::TransformPrimitive2D& rTransformCandidate) +{ + // remember current transformation and ViewInformation + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); +} + +void D2DPixelProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon()); + const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute()); + + if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0) + { + // no geometry, done + return; + } + + // get some values early that might be used for decisions + const bool bHairline(0.0 == rLineAttribute.getWidth()); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + const double fDiscreteLineWidth( + bHairline + ? 1.0 + : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength()); + + // Here for every combination which the system-specific implementation is not + // capable of visualizing, use the (for decomposable Primitives always possible) + // fallback to the decomposition. + if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5) + { + // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem + // knows that (so far), so fallback to decomposition. This is only needed if + // LineJoin will be used, so also check for discrete LineWidth before falling back + process(rPolygonStrokeCandidate); + return; + } + + // This is a method every system-specific implementation of a decomposable Primitive + // can use to allow simple optical control of paint implementation: + // Create a copy, e.g. change color to 'red' as here and paint before the system + // paints it using the decomposition. That way you can - if active - directly + // optically compare if the system-specific solution is geometrically identical to + // the decomposition (which defines our interpretation that we need to visualize). + // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case + // we create a half-transparent paint to better support visual control + static bool bRenderDecomposeForCompareInRed(false); + + if (bRenderDecomposeForCompareInRed) + { + const attribute::LineAttribute aRed( + basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(), + rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle()); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> aCopy( + new primitive2d::PolygonStrokePrimitive2D( + rPolygonStrokeCandidate.getB2DPolygon(), aRed, + rPolygonStrokeCandidate.getStrokeAttribute())); + process(*aCopy); + } + + bool bDone(false); + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + getOrCreatePathGeometry(rPolygon, getViewInformation2D())); + + if (pSystemDependentData_ID2D1PathGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset), + &pTransformedGeometry)); + + if (SUCCEEDED(hr) && pTransformedGeometry) + { + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rLineAttribute.getColor())); + D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(), + aLineColor.getBlue()); + + if (bRenderDecomposeForCompareInRed) + { + aD2DColor.a = 0.5; + } + + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush); + + if (SUCCEEDED(hr) && pColorBrush) + { + sal::systools::COMReference<ID2D1StrokeStyle> pStrokeStyle; + D2D1_CAP_STYLE aCapStyle(D2D1_CAP_STYLE_FLAT); + D2D1_LINE_JOIN aLineJoin(D2D1_LINE_JOIN_MITER); + const attribute::StrokeAttribute& rStrokeAttribute( + rPolygonStrokeCandidate.getStrokeAttribute()); + const bool bDashUsed(!rStrokeAttribute.isDefault() + && !rStrokeAttribute.getDotDashArray().empty() + && 0.0 < rStrokeAttribute.getFullDotDashLen()); + D2D1_DASH_STYLE aDashStyle(bDashUsed ? D2D1_DASH_STYLE_CUSTOM + : D2D1_DASH_STYLE_SOLID); + std::vector<float> dashes; + float miterLimit(1.0); + + switch (rLineAttribute.getLineCap()) + { + case css::drawing::LineCap_ROUND: + aCapStyle = D2D1_CAP_STYLE_ROUND; + break; + case css::drawing::LineCap_SQUARE: + aCapStyle = D2D1_CAP_STYLE_SQUARE; + break; + default: + break; + } + + switch (rLineAttribute.getLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + break; + case basegfx::B2DLineJoin::Bevel: + aLineJoin = D2D1_LINE_JOIN_BEVEL; + break; + case basegfx::B2DLineJoin::Miter: + { + // for basegfx::B2DLineJoin::Miter there are two problems: + // (1) MS uses D2D1_LINE_JOIN_MITER which handles the cut-off when MiterLimit is hit not by + // fallback to Bevel, but by cutting miter geometry at the defined distance. That is + // nice, but not what we need or is the standard for other graphic systems. Luckily there + // is also D2D1_LINE_JOIN_MITER_OR_BEVEL and (after some search) the page + // https://learn.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_line_join + // which gives some explanation, so that is what we need to use here. + // (2) Instead of using an angle in radians (15 deg default) MS uses + // "miterLimit is relative to 1/2 LineWidth", so a length. After some experimenting + // it shows that the (better understandable) angle has to be converted to the length + // that a miter prolongation would have at that angle, so use some trigonometry. + // Unfortunately there is also some'precision' problem (probably), so I had to + // experimentally come to a correction value around 0.9925. Since that seems to + // be no obvious numerical value involved somehow (and as long as I find no other + // explanation) I will have to use that. + // NOTE: To find that correction value I usd that handy bRenderDecomposeForCompareInRed + // and changes in debugger - as work tipp + // With both done I can use Direct2D for Miter completely - what is good for speed. + aLineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL; + + // snap absolute value of angle in radians to [0.0 .. PI] + double fVal(::basegfx::snapToZeroRange( + fabs(rLineAttribute.getMiterMinimumAngle()), M_PI)); + + // cut at 0.0 and PI since sin would be zero ('endless' miter) + const double fSmallValue(M_PI * 0.0000001); + fVal = std::max(fSmallValue, fVal); + fVal = std::min(M_PI - fSmallValue, fVal); + + // get relative length + fVal = 1.0 / sin(fVal); + + // use for miterLimit, we need factor 2.0 (relative to double LineWidth) + // and the correction mentioned in (2) above + const double fCorrector(2.0 * 0.9925); + + miterLimit = fVal * fCorrector; + break; + } + case basegfx::B2DLineJoin::Round: + aLineJoin = D2D1_LINE_JOIN_ROUND; + break; + default: + break; + } + + if (bDashUsed) + { + // dashes need to be discrete and relative to LineWidth + for (auto& value : rStrokeAttribute.getDotDashArray()) + { + dashes.push_back( + (rObjectToView * basegfx::B2DVector(value, 0.0)).getLength() + / fDiscreteLineWidth); + } + } + + hr = aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateStrokeStyle( + D2D1::StrokeStyleProperties(aCapStyle, // startCap + aCapStyle, // endCap + aCapStyle, // dashCap + aLineJoin, // lineJoin + miterLimit, // miterLimit + aDashStyle, // dashStyle + 0.0f), // dashOffset + bDashUsed ? dashes.data() : nullptr, bDashUsed ? dashes.size() : 0, + &pStrokeStyle); + + if (SUCCEEDED(hr) && pStrokeStyle) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->DrawGeometry( + pTransformedGeometry, pColorBrush, + // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D + bHairline ? 1.44 : fDiscreteLineWidth, pStrokeStyle); + bDone = true; + } + } + } + } + + if (!bDone) + { + // fallback to decomposition + process(rPolygonStrokeCandidate); + } +} + +void D2DPixelProcessor2D::processLineRectanglePrimitive2D( + const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D) +{ + if (rLineRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange()); + const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()), + FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) }; + const double fDiscreteLineWidth( + (getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.44, 0.0)) + .getLength()); + + getRenderTarget()->DrawRectangle(&rect, pColorBrush, fDiscreteLineWidth); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processFilledRectanglePrimitive2D( + const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D) +{ + if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange()); + const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()), + FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) }; + + getRenderTarget()->FillRectangle(&rect, pColorBrush); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processSingleLinePrimitive2D( + const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D) +{ + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DPoint aStart(aLocalTransform * rSingleLinePrimitive2D.getStart()); + const basegfx::B2DPoint aEnd(aLocalTransform * rSingleLinePrimitive2D.getEnd()); + + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + const D2D1_POINT_2F aD2D1Start + = { FLOAT(aStart.getX() + fAAOffset), FLOAT(aStart.getY() + fAAOffset) }; + const D2D1_POINT_2F aD2D1End + = { FLOAT(aEnd.getX() + fAAOffset), FLOAT(aEnd.getY() + fAAOffset) }; + + getRenderTarget()->DrawLine(aD2D1Start, aD2D1End, pColorBrush, 1.44f); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processFillGraphicPrimitive2D( + const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D) +{ + BitmapEx aPreparedBitmap; + basegfx::B2DRange aFillUnitRange(rFillGraphicPrimitive2D.getFillGraphic().getGraphicRange()); + static double fBigDiscreteArea(300.0 * 300.0); + + // use tooling to do various checks and prepare tiled rendering, see + // description of method, parameters and return value there + if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D, getViewInformation2D(), + aPreparedBitmap, aFillUnitRange, fBigDiscreteArea)) + { + // no output needed, done + return; + } + + if (aPreparedBitmap.IsEmpty()) + { + // output needed and Bitmap data empty, so no bitmap data based + // tiled rendering is suggested. Use fallback for paint (decomposition) + process(rFillGraphicPrimitive2D); + return; + } + + // render tiled using the prepared Bitmap data + if (maBColorModifierStack.count()) + { + // need to apply ColorModifier to Bitmap data + aPreparedBitmap = aPreparedBitmap.ModifyBitmapEx(maBColorModifierStack); + + if (aPreparedBitmap.IsEmpty()) + { + // color gets completely replaced, get it (any input works) + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + // what we still need to apply is the object transform from the + // local primitive, that is not part of DisplayInfo yet + aPolygon.transform(rFillGraphicPrimitive2D.getTransformation()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + + // draw as colored Polygon, done + processPolyPolygonColorPrimitive2D(*aTemp); + return; + } + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap)); + + if (pD2DBitmap) + { + sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush; + const HRESULT hr(getRenderTarget()->CreateBitmapBrush(pD2DBitmap, &pBitmapBrush)); + + if (SUCCEEDED(hr) && pBitmapBrush) + { + // set extended to repeat/wrap AKA tiling + pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_WRAP); + pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_WRAP); + + // set interpolation mode + // NOTE: This uses D2D1_BITMAP_INTERPOLATION_MODE, but there seem to be + // advanced modes when using D2D1_INTERPOLATION_MODE, but that needs + // D2D1_BITMAP_BRUSH_PROPERTIES1 and ID2D1BitmapBrush1 + sal::systools::COMReference<ID2D1BitmapBrush1> pBrush1; + pBitmapBrush->QueryInterface(__uuidof(ID2D1BitmapBrush1), + reinterpret_cast<void**>(&pBrush1)); + + if (pBrush1) + { + pBrush1->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR); + } + else + { + pBitmapBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR); + } + + // set BitmapBrush transformation relative to it's PixelSize and + // the used FillUnitRange. Since we use unit coordinates here this + // is pretty simple + const D2D1_SIZE_U aBMSPixel(pD2DBitmap->GetPixelSize()); + const double fScaleX((aFillUnitRange.getMaxX() - aFillUnitRange.getMinX()) + / aBMSPixel.width); + const double fScaleY((aFillUnitRange.getMaxY() - aFillUnitRange.getMinY()) + / aBMSPixel.height); + const D2D1_MATRIX_3X2_F aBTrans(D2D1::Matrix3x2F( + fScaleX, 0.0, 0.0, fScaleY, aFillUnitRange.getMinX(), aFillUnitRange.getMinY())); + pBitmapBrush->SetTransform(&aBTrans); + + // set transform to ObjectToWorld to be able to paint in unit coordinates, so + // evtl. shear/rotate in that transform is used and does not influence the + // orthogonal and unit-oriented brush handling + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() + * rFillGraphicPrimitive2D.getTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + + // use unit rectangle, transformation is already set to include ObjectToWorld + const D2D1_RECT_F rect = { FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(1.0) }; + + // draw as unit rectangle as brush filled rectangle + getRenderTarget()->FillRectangle(&rect, pBitmapBrush); + bDone = true; + } + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processFillGradientPrimitive2D( + const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D) +{ + // draw all-covering initial BG polygon 1st + bool bDone(drawPolyPolygonColorTransformed( + basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(rFillGradientPrimitive2D.getOutputRange())), + rFillGradientPrimitive2D.getOuterColor())); + + if (bDone) + { + const basegfx::B2DPolyPolygon aForm(rFillGradientPrimitive2D.getUnitPolygon()); + + // paint solid fill steps by providing callback as lambda + auto aCallback([&aForm, &bDone, this](const basegfx::B2DHomMatrix& rMatrix, + const basegfx::BColor& rColor) { + if (bDone) + { + bDone = drawPolyPolygonColorTransformed(rMatrix, aForm, rColor); + } + }); + + // call value generator to trigger callbacks + rFillGradientPrimitive2D.generateMatricesAndColors(aCallback); + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processInvertPrimitive2D( + const primitive2d::InvertPrimitive2D& rInvertPrimitive2D) +{ + if (rInvertPrimitive2D.getChildren().empty()) + { + // no content, done + return; + } + + // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface. + // Only with ID2D1DeviceContext we can use ::DrawImage which supports + // D2D1_COMPOSITE_MODE_XOR + sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext; + getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext), + reinterpret_cast<void**>(&pID2D1DeviceContext)); + + if (!pID2D1DeviceContext) + { + // TODO: We have *no* ID2D1DeviceContext and cannot use D2D1_COMPOSITE_MODE_XOR, + // so there is currently no (simple?) way to solve this, there is no 'Invert' method. + // It may be possible to convert to a WICBitmap (gets read access) and do the invert + // there, but that needs experimenting and is probably not performant - but doable. + increaseError(); + return; + } + + sal::systools::COMReference<ID2D1Bitmap> pInBetweenResult; + basegfx::B2DRange aDiscreteVisibleRange; + + // create in-between result in discrete coordinates, clipped against visible + // part of ViewInformation (if available) + if (!createBitmapSubContent(pInBetweenResult, aDiscreteVisibleRange, + rInvertPrimitive2D.getChildren(), getViewInformation2D(), + getRenderTarget())) + { + // return of false means no display needed, return + return; + } + + bool bDone(false); + + if (pInBetweenResult) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + const D2D1_POINT_2F aTopLeft = { FLOAT(floor(aDiscreteVisibleRange.getMinX())), + FLOAT(floor(aDiscreteVisibleRange.getMinY())) }; + + pID2D1DeviceContext->DrawImage(pInBetweenResult, aTopLeft, D2D1_INTERPOLATION_MODE_LINEAR, + D2D1_COMPOSITE_MODE_XOR); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + if (0 == mnRecursionCounter) + getRenderTarget()->BeginDraw(); + mnRecursionCounter++; + + switch (rCandidate.getPrimitive2DID()) + { + // Geometry that *has* to be processed + // + // These Primitives have *no* decompose implementation, so these are the basic ones + // Just four to go to make a processor work completely (but not optimized) + // NOTE: This *could* theoretically be reduced to one and all could implement + // a decompose to pixel data, but that seemed not to make sense to me when + // I designed this. Thus these four are the lowest-level best representation + // from my POV + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + processBitmapPrimitive2D( + static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + processPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + + // Embedding/groups that *have* to be processed + // + // These represent qualifiers for freely defined content, e.g. making + // any combination of primitives freely transformed or transparent + // NOTE: PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D and + // PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D are pretty much default- + // implementations that can and are re-used in all processors. + // So - with these and PRIMITIVE2D_ID_INVERTPRIMITIVE2D marked to + // be removed in the future - just three really to be implemented + // locally specifically + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + processTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + { + // We urgently should get rid of XOR paint, modern graphic systems + // allow no read access to the pixel targets, but that's naturally + // a precondition for XOR. While we can do that for the office's + // visualization, we can in principle *not* fully avoid getting + // stuff that needs/defines XOR paint, e.g. EMF/WMF imports, so + // we *have* to support it (for now - sigh)... + processInvertPrimitive2D( + static_cast<const primitive2d::InvertPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + processMaskPrimitive2DPixel( + static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + processModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + processTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } + + // Geometry that *may* be processed due to being able to do it better + // then using the decomposition. + // NOTE: In these implementations you could always call what the default + // case below does - call process(rCandidate) to use the decomposition. + // So these impls should only do something if they can do it better/ + // faster that the decomposition. So some of them check if they could + // - and if not - use exactly that. + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + // transparence with a fixed alpha for all content, can be done + // significally faster + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + // can be done simpler and without AA better locally + processMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + { + // reset to a color, can be done more effectively locally, would + // else decompose to a polygon fill + processBackgroundColorPrimitive2D( + static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + // fat and stroked lines - much better doable locally, would decompose + // to the full line geometry creation (tessellation) + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D: + { + // simple primitive to support future fast callbacks from OutputDevice + // (see 'Example POC' in Gerrit), decomposes to polygon primitive + processLineRectanglePrimitive2D( + static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D: + { + // simple primitive to support future fast callbacks from OutputDevice + // (see 'Example POC' in Gerrit), decomposes to filled polygon primitive + processFilledRectanglePrimitive2D( + static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D: + { + // simple primitive to support future fast callbacks from OutputDevice + // (see 'Example POC' in Gerrit), decomposes to polygon primitive + processSingleLinePrimitive2D( + static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + { + processFillGraphicPrimitive2D( + static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D: + { + processFillGradientPrimitive2D( + static_cast<const primitive2d::FillGradientPrimitive2D&>(rCandidate)); + break; + } + + // continue with decompose as fallback + default: + { + SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( + rCandidate.getPrimitive2DID())); + // process recursively + process(rCandidate); + break; + } + } + + mnRecursionCounter--; + if (0 == mnRecursionCounter) + getRenderTarget()->EndDraw(); +} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/getdigitlanguage.cxx b/drawinglayer/source/processor2d/getdigitlanguage.cxx index 858284b23e91..464fbf642af4 100644 --- a/drawinglayer/source/processor2d/getdigitlanguage.cxx +++ b/drawinglayer/source/processor2d/getdigitlanguage.cxx @@ -18,7 +18,7 @@ #include "getdigitlanguage.hxx" LanguageType drawinglayer::detail::getDigitLanguage() { - switch (SvtCTLOptions().GetCTLTextNumerals()) { + switch (SvtCTLOptions::GetCTLTextNumerals()) { case SvtCTLOptions::NUMERALS_ARABIC: return LANGUAGE_ENGLISH; case SvtCTLOptions::NUMERALS_HINDI: diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx index 9f838a7e1b61..d4f14a13ce6c 100644 --- a/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx +++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx @@ -50,8 +50,10 @@ bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWron * basegfx::B2DPoint(rWrongSpellCandidate.getStart(), 0.0)); const basegfx::B2DPoint aStop(aLocalTransform * basegfx::B2DPoint(rWrongSpellCandidate.getStop(), 0.0)); - const Point aVclStart(basegfx::fround(aStart.getX()), basegfx::fround(aStart.getY())); - const Point aVclStop(basegfx::fround(aStop.getX()), basegfx::fround(aStop.getY())); + const Point aVclStart(basegfx::fround<tools::Long>(aStart.getX()), + basegfx::fround<tools::Long>(aStart.getY())); + const Point aVclStop(basegfx::fround<tools::Long>(aStop.getX()), + basegfx::fround<tools::Long>(aStop.getY())); // #i101075# draw it. Do not forget to use the evtl. offsetted origin of the target device, // e.g. when used with mask/transparence buffer device diff --git a/drawinglayer/source/processor2d/hittestprocessor2d.cxx b/drawinglayer/source/processor2d/hittestprocessor2d.cxx index ddccfb988654..cf6d3eec9447 100644 --- a/drawinglayer/source/processor2d/hittestprocessor2d.cxx +++ b/drawinglayer/source/processor2d/hittestprocessor2d.cxx @@ -20,7 +20,9 @@ #include <drawinglayer/processor2d/hittestprocessor2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> @@ -39,27 +41,25 @@ namespace drawinglayer::processor2d { HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation, const basegfx::B2DPoint& rLogicHitPosition, - double fLogicHitTolerance, + const basegfx::B2DVector& rLogicHitTolerancePerAxis, bool bHitTextOnly) : BaseProcessor2D(rViewInformation), - mfDiscreteHitTolerance(0.0), + maDiscreteHitTolerancePerAxis(rLogicHitTolerancePerAxis), mbCollectHitStack(false), mbHit(false), mbHitTextOnly(bHitTextOnly) { - // init hit tolerance - mfDiscreteHitTolerance = fLogicHitTolerance; + // ensure input parameters for hit tolerance is >= 0.0 + if (maDiscreteHitTolerancePerAxis.getX() < 0.0) + maDiscreteHitTolerancePerAxis.setX(0.0); + if (maDiscreteHitTolerancePerAxis.getY() < 0.0) + maDiscreteHitTolerancePerAxis.setY(0.0); - if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0)) - { - // ensure input parameter for hit tolerance is >= 0.0 - mfDiscreteHitTolerance = 0.0; - } - else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0)) + if (!maDiscreteHitTolerancePerAxis.equalZero()) { // generate discrete hit tolerance - mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation() - * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength(); + maDiscreteHitTolerancePerAxis + = getViewInformation2D().getObjectToViewTransformation() * rLogicHitTolerancePerAxis; } // generate discrete hit position @@ -72,7 +72,7 @@ namespace drawinglayer::processor2d bool HitTestProcessor2D::checkHairlineHitWithTolerance( const basegfx::B2DPolygon& rPolygon, - double fDiscreteHitTolerance) const + const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const { basegfx::B2DPolygon aLocalPolygon(rPolygon); aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); @@ -80,9 +80,9 @@ namespace drawinglayer::processor2d // get discrete range basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange()); - if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0)) + if(rDiscreteHitTolerancePerAxis.getX() > 0 || rDiscreteHitTolerancePerAxis.getY() > 0) { - aPolygonRange.grow(fDiscreteHitTolerance); + aPolygonRange.grow(rDiscreteHitTolerancePerAxis); } // do rough range test first @@ -92,7 +92,7 @@ namespace drawinglayer::processor2d return basegfx::utils::isInEpsilonRange( aLocalPolygon, getDiscreteHitPosition(), - fDiscreteHitTolerance); + std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY())); } return false; @@ -100,7 +100,7 @@ namespace drawinglayer::processor2d bool HitTestProcessor2D::checkFillHitWithTolerance( const basegfx::B2DPolyPolygon& rPolyPolygon, - double fDiscreteHitTolerance) const + const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const { bool bRetval(false); basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon); @@ -108,11 +108,13 @@ namespace drawinglayer::processor2d // get discrete range basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange()); - const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0)); - if(bDiscreteHitToleranceUsed) + const bool bDiscreteHitToleranceUsed(rDiscreteHitTolerancePerAxis.getX() > 0 + || rDiscreteHitTolerancePerAxis.getY() > 0); + + if (bDiscreteHitToleranceUsed) { - aPolygonRange.grow(fDiscreteHitTolerance); + aPolygonRange.grow(rDiscreteHitTolerancePerAxis); } // do rough range test first @@ -123,7 +125,7 @@ namespace drawinglayer::processor2d basegfx::utils::isInEpsilonRange( aLocalPolyPolygon, getDiscreteHitPosition(), - fDiscreteHitTolerance)) + std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY()))) { bRetval = true; } @@ -231,12 +233,8 @@ namespace drawinglayer::processor2d const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); // create new local ViewInformation2D containing transformation - const geometry::ViewInformation2D aViewInformation2D( - getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), - getViewInformation2D().getViewTransformation(), - getViewInformation2D().getViewport(), - getViewInformation2D().getVisualizedPage(), - getViewInformation2D().getViewTime()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); updateViewInformation(aViewInformation2D); // process child content recursively @@ -293,10 +291,10 @@ namespace drawinglayer::processor2d { // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() - * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0)); + * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, rLineAttribute.getWidth() * 0.5)); mbHit = checkHairlineHitWithTolerance( rPolygonCandidate.getB2DPolygon(), - getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength()); + getDiscreteHitTolerance() + aDiscreteHalfLineVector); } } else @@ -330,11 +328,11 @@ namespace drawinglayer::processor2d } const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() - * basegfx::B2DVector(fLogicHitTolerance, 0.0)); + * basegfx::B2DVector(fLogicHitTolerance, fLogicHitTolerance)); mbHit = checkHairlineHitWithTolerance( rPolygonCandidate.getB2DPolygon(), - getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength()); + getDiscreteHitTolerance() + aDiscreteHalfLineVector); } break; @@ -430,7 +428,7 @@ namespace drawinglayer::processor2d if(!aRange.isEmpty()) { const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate)); - const BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapCandidate.getXBitmap())); + const BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); const Size& rSizePixel(aBitmapEx.GetSizePixel()); // When tiled rendering, don't bother with the pixel size of the candidate. @@ -518,7 +516,8 @@ namespace drawinglayer::processor2d const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]); const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition()); - if(aDistance.getLength() <= getDiscreteHitTolerance()) + if (aDistance.getLength() <= std::max(getDiscreteHitTolerance().getX(), + getDiscreteHitTolerance().getY())) { mbHit = true; } @@ -540,7 +539,7 @@ namespace drawinglayer::processor2d { /// push candidate to HitStack to create it. This only happens when a hit is found and /// creating the HitStack was requested (see collectHitStack) - maHitStack.append(primitive2d::Primitive2DReference(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate))); + maHitStack.append(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate)); } } diff --git a/drawinglayer/source/processor2d/linegeometryextractor2d.cxx b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx index 1123833cfc99..11af79725b41 100644 --- a/drawinglayer/source/processor2d/linegeometryextractor2d.cxx +++ b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx @@ -19,7 +19,7 @@ #include <drawinglayer/processor2d/linegeometryextractor2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> @@ -84,12 +84,8 @@ namespace drawinglayer::processor2d const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); // create new transformations for CurrentTransformation and for local ViewInformation2D - const geometry::ViewInformation2D aViewInformation2D( - getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), - getViewInformation2D().getViewTransformation(), - getViewInformation2D().getViewport(), - getViewInformation2D().getVisualizedPage(), - getViewInformation2D().getViewTime()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); updateViewInformation(aViewInformation2D); // process content diff --git a/drawinglayer/source/processor2d/processor2dtools.cxx b/drawinglayer/source/processor2d/processor2dtools.cxx index 7bc0f5fa0536..cf823b005ed8 100644 --- a/drawinglayer/source/processor2d/processor2dtools.cxx +++ b/drawinglayer/source/processor2d/processor2dtools.cxx @@ -18,45 +18,75 @@ */ #include <drawinglayer/processor2d/processor2dtools.hxx> #include <vcl/gdimtf.hxx> +#include <vcl/sysdata.hxx> #include "vclpixelprocessor2d.hxx" #include "vclmetafileprocessor2d.hxx" +#include <config_vclplug.h> +#if defined(_WIN32) +#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx> +#elif USE_HEADLESS_CODE +#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx> +#endif using namespace com::sun::star; - namespace drawinglayer::processor2d { - std::unique_ptr<BaseProcessor2D> createPixelProcessor2DFromOutputDevice( - OutputDevice& rTargetOutDev, - const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +std::unique_ptr<BaseProcessor2D> createPixelProcessor2DFromOutputDevice( + OutputDevice& rTargetOutDev, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +{ + static const bool bTestSystemPrimitiveRenderer(nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER")); + if(bTestSystemPrimitiveRenderer) + { + drawinglayer::geometry::ViewInformation2D aViewInformation2D(rViewInformation2D); + // if mnOutOffX/mnOutOffY is set (a 'hack' to get a cheap additional offset), apply it additionally + if(0 != rTargetOutDev.GetOutOffXPixel() || 0 != rTargetOutDev.GetOutOffYPixel()) { - // create Pixel Vcl-Processor - return std::make_unique<VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev); + basegfx::B2DHomMatrix aTransform(aViewInformation2D.getViewTransformation()); + aTransform.translate(rTargetOutDev.GetOutOffXPixel(), rTargetOutDev.GetOutOffYPixel()); + aViewInformation2D.setViewTransformation(aTransform); } +#if defined(_WIN32) + SystemGraphicsData aData(rTargetOutDev.GetSystemGfxData()); + std::unique_ptr<D2DPixelProcessor2D> aRetval( + std::make_unique<D2DPixelProcessor2D>(aViewInformation2D, aData.hDC)); + if (aRetval->valid()) + return aRetval; +#elif USE_HEADLESS_CODE + SystemGraphicsData aData(rTargetOutDev.GetSystemGfxData()); + std::unique_ptr<CairoPixelProcessor2D> aRetval( + std::make_unique<CairoPixelProcessor2D>(aViewInformation2D, static_cast<cairo_surface_t*>(aData.pSurface))); + if (aRetval->valid()) + return aRetval; +#endif + } - std::unique_ptr<BaseProcessor2D> createProcessor2DFromOutputDevice( - OutputDevice& rTargetOutDev, - const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) - { - const GDIMetaFile* pMetaFile = rTargetOutDev.GetConnectMetaFile(); - const bool bOutputToRecordingMetaFile(pMetaFile && pMetaFile->IsRecord() && !pMetaFile->IsPause()); - - if(bOutputToRecordingMetaFile) - { - // create MetaFile Vcl-Processor and process - return std::make_unique<VclMetafileProcessor2D>(rViewInformation2D, rTargetOutDev); - } - else - { - // create Pixel Vcl-Processor - return createPixelProcessor2DFromOutputDevice( - rTargetOutDev, - rViewInformation2D); - } - } + // create Pixel Vcl-Processor + return std::make_unique<VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev); +} -} // end of namespace +std::unique_ptr<BaseProcessor2D> createProcessor2DFromOutputDevice( + OutputDevice& rTargetOutDev, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +{ + const GDIMetaFile* pMetaFile = rTargetOutDev.GetConnectMetaFile(); + const bool bOutputToRecordingMetaFile(pMetaFile && pMetaFile->IsRecord() + && !pMetaFile->IsPause()); + if (bOutputToRecordingMetaFile) + { + // create MetaFile Vcl-Processor and process + return std::make_unique<VclMetafileProcessor2D>(rViewInformation2D, rTargetOutDev); + } + else + { + // create Pixel Vcl-Processor + return createPixelProcessor2DFromOutputDevice(rTargetOutDev, rViewInformation2D); + } +} + +} // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/processorfromoutputdevice.cxx b/drawinglayer/source/processor2d/processorfromoutputdevice.cxx deleted file mode 100644 index c8433753aeff..000000000000 --- a/drawinglayer/source/processor2d/processorfromoutputdevice.cxx +++ /dev/null @@ -1,50 +0,0 @@ -/* -*- 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 <vcl/outdev.hxx> -#include <vcl/gdimtf.hxx> -#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> -#include "vclmetafileprocessor2d.hxx" -#include "vclpixelprocessor2d.hxx" - -using namespace com::sun::star; - -namespace drawinglayer::processor2d -{ - std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> createBaseProcessor2DFromOutputDevice( - OutputDevice& rTargetOutDev, - const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) - { - const GDIMetaFile* pMetaFile = rTargetOutDev.GetConnectMetaFile(); - const bool bOutputToRecordingMetaFile(pMetaFile && pMetaFile->IsRecord() && !pMetaFile->IsPause()); - - if(bOutputToRecordingMetaFile) - { - // create MetaFile Vcl-Processor and process - return std::make_unique<drawinglayer::processor2d::VclMetafileProcessor2D>(rViewInformation2D, rTargetOutDev); - } - else - { - // create Pixel Vcl-Processor - return std::make_unique<drawinglayer::processor2d::VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev); - } - } -} // end of namespace - -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx index a2943fd8ac16..1a091d027a85 100644 --- a/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx +++ b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx @@ -19,65 +19,56 @@ #include <drawinglayer/processor2d/textaspolygonextractor2d.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> namespace drawinglayer::processor2d { + void TextAsPolygonExtractor2D::processTextPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + // PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D + // TextDecoratedPortionPrimitive2D can produce the following primitives + // when being decomposed: + // + // - TextSimplePortionPrimitive2D + // - PolygonWavePrimitive2D + // - PolygonStrokePrimitive2D + // - PolygonStrokePrimitive2D + // - PolyPolygonColorPrimitive2D + // - PolyPolygonHairlinePrimitive2D + // - PolygonHairlinePrimitive2D + // - ShadowPrimitive2D + // - ModifiedColorPrimitive2D + // - TransformPrimitive2D + // - TextEffectPrimitive2D + // - ModifiedColorPrimitive2D + // - TransformPrimitive2D + // - GroupPrimitive2D + + // PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D + // TextSimplePortionPrimitive2D can produce the following primitives + // when being decomposed: + // + // - PolyPolygonColorPrimitive2D + // - TextEffectPrimitive2D + // - ModifiedColorPrimitive2D + // - TransformPrimitive2D + // - GroupPrimitive2D + + // encapsulate with flag and use decomposition + mnInText++; + process(rCandidate); + mnInText--; + } + void TextAsPolygonExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) { - switch(rCandidate.getPrimitive2DID()) + switch (rCandidate.getPrimitive2DID()) { - case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D : - { - // TextDecoratedPortionPrimitive2D can produce the following primitives - // when being decomposed: - // - // - TextSimplePortionPrimitive2D - // - PolygonWavePrimitive2D - // - PolygonStrokePrimitive2D - // - PolygonStrokePrimitive2D - // - PolyPolygonColorPrimitive2D - // - PolyPolygonHairlinePrimitive2D - // - PolygonHairlinePrimitive2D - // - ShadowPrimitive2D - // - ModifiedColorPrimitive2D - // - TransformPrimitive2D - // - TextEffectPrimitive2D - // - ModifiedColorPrimitive2D - // - TransformPrimitive2D - // - GroupPrimitive2D - - // encapsulate with flag and use decomposition - mnInText++; - process(rCandidate); - mnInText--; - - break; - } - case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D : - { - // TextSimplePortionPrimitive2D can produce the following primitives - // when being decomposed: - // - // - PolyPolygonColorPrimitive2D - // - TextEffectPrimitive2D - // - ModifiedColorPrimitive2D - // - TransformPrimitive2D - // - GroupPrimitive2D - - // encapsulate with flag and use decomposition - mnInText++; - process(rCandidate); - mnInText--; - - break; - } - // as can be seen from the TextSimplePortionPrimitive2D and the // TextDecoratedPortionPrimitive2D, inside of the mnInText marks // the following primitives can occur containing geometry data @@ -177,12 +168,8 @@ namespace drawinglayer::processor2d const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); // create new transformations for CurrentTransformation and for local ViewInformation2D - const geometry::ViewInformation2D aViewInformation2D( - getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), - getViewInformation2D().getViewTransformation(), - getViewInformation2D().getViewport(), - getViewInformation2D().getVisualizedPage(), - getViewInformation2D().getViewTime()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); updateViewInformation(aViewInformation2D); // process content @@ -194,29 +181,14 @@ namespace drawinglayer::processor2d break; } - // ignorable primitives - case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : - case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : - case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : - case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : - case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : - case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : - case PRIMITIVE2D_ID_MASKPRIMITIVE2D : - { + default: + TextExtractor2D::processBasePrimitive2D(rCandidate); break; - } - - default : - { - // process recursively - process(rCandidate); - break; - } } } TextAsPolygonExtractor2D::TextAsPolygonExtractor2D(const geometry::ViewInformation2D& rViewInformation) - : BaseProcessor2D(rViewInformation), + : TextExtractor2D(rViewInformation), maBColorModifierStack(), mnInText(0) { diff --git a/drawinglayer/source/processor2d/textextractor2d.cxx b/drawinglayer/source/processor2d/textextractor2d.cxx new file mode 100644 index 000000000000..105014a750ca --- /dev/null +++ b/drawinglayer/source/processor2d/textextractor2d.cxx @@ -0,0 +1,88 @@ +/* -*- 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/processor2d/textextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + +namespace drawinglayer::processor2d +{ +void TextExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + switch (rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + { + processTextPrimitive2D(rCandidate); + break; + } + + // usage of transformation stack is needed + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + // remember current transformation and ViewInformation + const primitive2d::TransformPrimitive2D& rTransformCandidate( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for CurrentTransformation and for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation( + getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + + // ignorable primitives + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D: + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + break; + + default: + { + // process recursively + process(rCandidate); + break; + } + } +} + +TextExtractor2D::TextExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation) +{ +} + +TextExtractor2D::~TextExtractor2D() {} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx index 9129271bcb6b..28d383230eef 100644 --- a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx @@ -28,30 +28,39 @@ #include <basegfx/range/b2drange.hxx> #include <vcl/bitmapex.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> -#include <tools/stream.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> #include <vcl/timer.hxx> -#include <cppuhelper/basemutex.hxx> #include <vcl/lazydelete.hxx> #include <vcl/dibtools.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <mutex> -// buffered VDev usage +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#endif +// #define SPEED_COMPARE +#ifdef SPEED_COMPARE +#include <tools/time.hxx> +#endif + +// buffered VDev usage namespace { -class VDevBuffer : public Timer, protected cppu::BaseMutex +class VDevBuffer : public Timer { private: struct Entry { VclPtr<VirtualDevice> buf; - bool isTransparent = false; - Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent) + Entry(const VclPtr<VirtualDevice>& vdev) : buf(vdev) - , isTransparent(bTransparent) { } }; + std::mutex m_aMutex; + // available buffers std::vector<Entry> maFreeBuffers; @@ -63,11 +72,13 @@ private: // virtualdevice because that isn't safe to do at least for Gtk2 std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates; + static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size); + public: VDevBuffer(); virtual ~VDevBuffer() override; - VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bTransparent); + VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel); void free(VirtualDevice& rDevice); // Timer virtuals @@ -82,7 +93,7 @@ VDevBuffer::VDevBuffer() VDevBuffer::~VDevBuffer() { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); Stop(); while (!maFreeBuffers.empty()) @@ -98,10 +109,37 @@ VDevBuffer::~VDevBuffer() } } -VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel, - bool bTransparent) +bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel) { - ::osl::MutexGuard aGuard(m_aMutex); + if (device->GetOutputWidthPixel() >= rSizePixel.getWidth() + && device->GetOutputHeightPixel() >= rSizePixel.getHeight()) + { + bool requireSmall = false; +#if defined(UNX) + // HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface(). + // Make sure to not reuse a larger device when a small one should be preferred. + if (device->GetRenderBackendName() == "svp") + requireSmall = true; +#endif + // The same for Skia, see renderMethodToUseForSize(). + if (SkiaHelper::isVCLSkiaEnabled()) + requireSmall = true; + if (requireSmall) + { + if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32 + && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32)) + { + return false; + } + } + return true; + } + return false; +} + +VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel) +{ + std::unique_lock aGuard(m_aMutex); VclPtr<VirtualDevice> pRetval; sal_Int32 nBits = rOutDev.GetBitCount(); @@ -115,7 +153,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize { assert(a->buf && "Empty pointer in VDevBuffer (!)"); - if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent) + if (nBits == a->buf->GetBitCount()) { // candidate is valid due to bit depth if (aFound != maFreeBuffers.end()) @@ -124,9 +162,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize if (bOkay) { // found is valid - const bool bCandidateOkay( - a->buf->GetOutputWidthPixel() >= rSizePixel.getWidth() - && a->buf->GetOutputHeightPixel() >= rSizePixel.getHeight()); + const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel); if (bCandidateOkay) { @@ -151,16 +187,14 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize { // found is invalid, use candidate aFound = a; - bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth() - && aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight(); + bOkay = isSizeSuitable(aFound->buf, rSizePixel); } } else { // none yet, use candidate aFound = a; - bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth() - && aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight(); + bOkay = isSizeSuitable(aFound->buf, rSizePixel); } } } @@ -200,9 +234,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize // no success yet, create new buffer if (!pRetval) { - pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT, - bTransparent ? DeviceFormat::DEFAULT - : DeviceFormat::NONE); + pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::WITHOUT_ALPHA); maDeviceTemplates[pRetval] = &rOutDev; pRetval->SetOutputSizePixel(rSizePixel, true); } @@ -214,14 +246,14 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize } // remember allocated buffer - maUsedBuffers.emplace_back(pRetval, bTransparent); + maUsedBuffers.emplace_back(pRetval); return pRetval; } void VDevBuffer::free(VirtualDevice& rDevice) { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); const auto aUsedFound = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(), [&rDevice](const Entry& el) { return el.buf == &rDevice; }); @@ -239,7 +271,7 @@ void VDevBuffer::free(VirtualDevice& rDevice) void VDevBuffer::Invoke() { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); while (!maFreeBuffers.empty()) { @@ -249,13 +281,103 @@ void VDevBuffer::Invoke() maFreeBuffers.pop_back(); } } + +#ifdef SPEED_COMPARE +void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel, + OutputDevice& rOutDev) +{ + const int nAvInd(500); + static double fFactors[nAvInd]; + static int nIndex(nAvInd + 1); + static int nRepeat(5); + static int nWorseTotal(0); + static int nBetterTotal(0); + int a(0); + + const Size aSizePixel(rDestPixel.GetSize()); + + // init statics + if (nIndex > nAvInd) + { + for (a = 0; a < nAvInd; a++) + fFactors[a] = 1.0; + nIndex = 0; + } + + // get start time + const sal_uInt64 nTimeA(tools::Time::GetSystemTicks()); + + // loop nRepeat times to get somewhat better timings, else + // numbers are pretty small + for (a = 0; a < nRepeat; a++) + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + rOutDev.DrawBitmapEx(rDestPixel.TopLeft(), BitmapEx(rContent, aAlphaMask)); + } + + // get intermediate time + const sal_uInt64 nTimeB(tools::Time::GetSystemTicks()); + + // loop nRepeat times + for (a = 0; a < nRepeat; a++) + { + // New method using DrawTransformedBitmapEx & fTrans directly + rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()), + BitmapEx(rContent), 1 - fTrans); + } + + // get end time + const sal_uInt64 nTimeC(tools::Time::GetSystemTicks()); + + // calculate deltas + const sal_uInt64 nTimeFormer(nTimeB - nTimeA); + const sal_uInt64 nTimeNew(nTimeC - nTimeB); + + // compare & note down + if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew) + { + if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500) + { + nRepeat += 1; + SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat); + return; + } + + const double fNewFactor((double)nTimeFormer / nTimeNew); + fFactors[nIndex % nAvInd] = fNewFactor; + nIndex++; + double fAverage(0.0); + { + for (a = 0; a < nAvInd; a++) + fAverage += fFactors[a]; + fAverage /= nAvInd; + } + if (fNewFactor < 1.0) + nWorseTotal++; + else + nBetterTotal++; + + char buf[300]; + sprintf(buf, + "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, " + "WorseTotal: %d, BetterTotal: %d)", + nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER", + fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal, + nBetterTotal); + SAL_INFO("drawinglayer.processor2d", buf); + } +} +#endif } // support for rendering Bitmap and BitmapEx contents - namespace drawinglayer { -// static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D) +// static global VDev buffer for VclProcessor2D/VclPixelProcessor2D VDevBuffer& getVDevBuffer() { // secure global instance with Vcl's safe destroyer of external (seen by @@ -272,22 +394,28 @@ impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& { basegfx::B2DRange aRangePixel(rRange); aRangePixel.transform(mrOutDev.GetViewTransformation()); - const ::tools::Rectangle aRectPixel(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()), - ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY())); - const Point aEmptyPoint; - maDestPixel = ::tools::Rectangle(aEmptyPoint, mrOutDev.GetOutputSizePixel()); - maDestPixel.Intersection(aRectPixel); + maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()), + ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY())); + maDestPixel.Intersection(tools::Rectangle{ Point{}, mrOutDev.GetOutputSizePixel() }); if (!isVisible()) return; - mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true); + mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); // #i93485# assert when copying from window to VDev is used SAL_WARN_IF( mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer", "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)"); + // initialize buffer by blitting content of source to prepare for + // transparence/ copying back + const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled()); + mrOutDev.EnableMapMode(false); + mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(), + maDestPixel.GetSize(), mrOutDev); + mrOutDev.EnableMapMode(bWasEnabledSrc); + MapMode aNewMapMode(mrOutDev.GetMapMode()); const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft())); @@ -323,23 +451,18 @@ void impBufferDevice::paint(double fTrans) const Point aEmptyPoint; const Size aSizePixel(maDestPixel.GetSize()); const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled()); -#ifdef DBG_UTIL - static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore -#endif mrOutDev.EnableMapMode(false); mpContent->EnableMapMode(false); #ifdef DBG_UTIL - if (bDoSaveForVisualControl) + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + + if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\content.bmp", -#else - "~/content.bmp", -#endif - StreamMode::WRITE | StreamMode::TRUNC); + SvFileStream aNew(sDumpPath + "content.bmp", StreamMode::WRITE | StreamMode::TRUNC); Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); WriteDIB(aContent, aNew, false, true); } @@ -353,32 +476,86 @@ void impBufferDevice::paint(double fTrans) { mpAlpha->EnableMapMode(false); AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel)); + aAlphaMask.Invert(); // convert transparency to alpha #ifdef DBG_UTIL - if (bDoSaveForVisualControl) + if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\transparence.bmp", -#else - "~/transparence.bmp", -#endif - StreamMode::WRITE | StreamMode::TRUNC); + SvFileStream aNew(sDumpPath + "transparence.bmp", + StreamMode::WRITE | StreamMode::TRUNC); WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true); } #endif - BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel)); - aAlphaMask.BlendWith(aContent.GetAlpha()); - mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask)); + Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); } else if (0.0 != fTrans) { - basegfx::B2DHomMatrix trans, scale; - trans.translate(maDestPixel.TopLeft().X(), maDestPixel.TopLeft().Y()); - scale.scale(aSizePixel.Width(), aSizePixel.Height()); - mrOutDev.DrawTransformedBitmapEx( - trans * scale, mpContent->GetBitmapEx(aEmptyPoint, aSizePixel), 1 - fTrans); + const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + +#ifdef SPEED_COMPARE + static bool bCompareFormerAndNewTimings(true); + + if (bCompareFormerAndNewTimings) + { + doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev); + } + else +#endif + // Note: this extra scope is needed due to 'clang plugin indentation'. It complains + // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'. + // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this. + { + // For the case we have a unified transparency value there is a former + // and new method to paint that which can be used. To decide on measurements, + // I added 'doSpeedCompare' above which can be activated by defining + // SPEED_COMPARE at the top of this file. + // I added the used Testdoc: blurplay3.odg as + // https://bugs.documentfoundation.org/attachment.cgi?id=182463 + // I did measure on + // + // Linux Dbg: + // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934) + // + // Linux Pro: + // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337) + // + // Win Dbg: + // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428) + // + // Win Pro: + // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909) + // + // Note: I am aware that the Dbg are of limited usefulness, but include them here + // for reference. + // + // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New + // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the + // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will + // use Former for now. + // + // To easily allow to change this (maybe system-dependent) I add a static switch here, + // also for eventually experimenting (hint: can be changed in the debugger). + static bool bUseNew(false); + + if (bUseNew) + { + // New method using DrawTransformedBitmapEx & fTrans directly + mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + maDestPixel.TopLeft().X(), + maDestPixel.TopLeft().Y()), + BitmapEx(aContent), 1 - fTrans); + } + else + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); + } + } } else { @@ -402,7 +579,7 @@ VirtualDevice& impBufferDevice::getTransparence() "impBufferDevice: No content, check isVisible() before accessing (!)"); if (!mpAlpha) { - mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false); + mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); mpAlpha->SetMapMode(mpContent->GetMapMode()); // copy AA flag for new target; masking needs to be smooth diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx index 3b5d30415cc2..618fb38209a5 100644 --- a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx @@ -21,12 +21,73 @@ #include <vcl/virdev.hxx> -namespace basegfx -{ -class B2DRange; -} - -// support methods for vcl direct gradient rendering +// Helper class *exclusively* for VclProcessor2D. It should only +// be used internally, see current four usages. It is used to +// render something with mask or transparence (see MaskPrimitive2D, +// UnifiedTransparencePrimitive2D and TransparencePrimitive2D) or +// as tooling for preparing pixelized output in the renderer +// (see PatternFillPrimitive2D) if that is faster. +// +// To do so, initializing this instance takes over a lot of work +// from you: +// - It initializes a 'Content' VDev which is buffered (since it had +// shown that re-allocating all the time is slower). It checks +// visibility and all usages initializing this should check for +// isVisible() after construction. +// - It pre-initializes the 'Content' VDev with blitting the content +// of the target VDev. +// - It offers to get a 'Transparence' VDev (also from the buffer) if +// needed. +// +// If 'Transparence' is/was used, it combines as needed to paint +// all buffered stuff to target VDev when calling paint(). +// Caution: It is designed to use *either* a fixed transparence in +// the paint()-call *or* a fill TransparenceChannel using a +// 'Transparence' VDev. It is *not* designed to use/combine +// both - it's simply not needed for it's intended purpose/usage. +// +// Painting transparent works based on a simple principle: It first +// blits the original content of the target VDev. Then the content +// is painted on top of that, plus a Transparence/Mask prepared. +// The combination always works since unchanged pixels will not +// change, independent of the transparence value [0..255] it gets +// mixed with. Or the other way around: Only pixels changed on the +// Content *can* be changed by transparence values. +// +// This is 2.5 times faster than first painting to a +// 'combined' VDev that supports transparency, as is used by the +// presentation engine. +// For the mentioned factor refer to: +// Patch to demonstrate former and now repaint differences +// https://gerrit.libreoffice.org/c/core/+/129301 +// git fetch https://git.libreoffice.org/core refs/changes/01/129301/3 && git cherry-pick FETCH_HEAD +// +// Note: This principle only works when the target is RGB, so +// useful for EditViews like for PrimitiveRenderers where this is +// the case. For usage with GBA targets the starting conditions +// would have to be modified to not blend against the initial color +// of 'Content' (usually COL_WHITE). +// +// After having finished the rework of ShadowPrimitive2D, +// SoftEdgePrimitive2D and GlowPrimitive2D (see commits:) +// e735ad1c57cddaf17d6ffc0cf15b5e14fa63c4ad +// 707b0c328a282d993fa33b618083d20b6c521de6 +// c2d1458723c66c2fd717a112f89f773226adc841 +// which used the impBufferDevice in such a mode combined with +// mentioned 'combined' transparence VDev it is now possible +// to return to this former, much faster method. +// +// Please do *not* hack/use this helper class, better create +// a new one fitting your/the intended purpose. I do not want +// to see losing performance by this getting modified again. +// +// Note: Using that 'combined' transparence VDev is not really +// recommended, it may vanish anytime. That it works with +// PrimitiveRenderers *at all* is neither designed nor tested +// or recommended - it's pure coincidence. +// +// I hope that for the future all this will vanish by getting to +// fully RGBA-capable devices - what is planned and makes sense. namespace drawinglayer { diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index b679768c9c3b..d93d98fef51a 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -24,27 +24,30 @@ #include <rtl/ustring.hxx> #include <tools/gen.hxx> #include <tools/stream.hxx> -#include <tools/diagnose_ex.h> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/flagguard.hxx> #include <comphelper/processfactory.hxx> #include <config_global.h> #include <basegfx/polygon/b2dpolygonclipper.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/utils/gradienttools.hxx> #include <vcl/virdev.hxx> #include <vcl/gdimtf.hxx> #include <vcl/gradient.hxx> #include <vcl/graphictools.hxx> #include <vcl/metaact.hxx> #include <vcl/graph.hxx> // for PDFExtOutDevData Graphic support -#include <toolkit/helper/formpdfexport.hxx> // for PDFExtOutDevData Graphic support +#include <vcl/formpdfexport.hxx> // for PDFExtOutDevData Graphic support #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> @@ -61,6 +64,9 @@ #include <drawinglayer/primitive2d/epsprimitive2d.hxx> #include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata +#include <drawinglayer/converters.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/vcompat.hxx> #include <com/sun/star/awt/XControl.hpp> #include <com/sun/star/i18n/BreakIterator.hpp> @@ -220,8 +226,10 @@ VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContaine aPrimitiveRange.transform(maCurrentTransformation); const tools::Rectangle aPrimitiveRectangle( - basegfx::fround(aPrimitiveRange.getMinX()), basegfx::fround(aPrimitiveRange.getMinY()), - basegfx::fround(aPrimitiveRange.getMaxX()), basegfx::fround(aPrimitiveRange.getMaxY())); + basegfx::fround<tools::Long>(aPrimitiveRange.getMinX()), + basegfx::fround<tools::Long>(aPrimitiveRange.getMinY()), + basegfx::fround<tools::Long>(aPrimitiveRange.getMaxX()), + basegfx::fround<tools::Long>(aPrimitiveRange.getMaxY())); ScopedVclPtrInstance<VirtualDevice> aContentVDev; MapMode aNewMapMode(pLastOutputDevice->GetMapMode()); @@ -256,19 +264,20 @@ void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( Gradient& o_rVCLGradient, const attribute::FillGradientAttribute& rFiGrAtt, bool bIsTransparenceGradient) const { + const basegfx::BColor aStartColor(rFiGrAtt.getColorStops().front().getStopColor()); + const basegfx::BColor aEndColor(rFiGrAtt.getColorStops().back().getStopColor()); + if (bIsTransparenceGradient) { // it's about transparence channel intensities (black/white), do not use color modifier - o_rVCLGradient.SetStartColor(Color(rFiGrAtt.getStartColor())); - o_rVCLGradient.SetEndColor(Color(rFiGrAtt.getEndColor())); + o_rVCLGradient.SetStartColor(Color(aStartColor)); + o_rVCLGradient.SetEndColor(Color(aEndColor)); } else { // use color modifier to influence start/end color of gradient - o_rVCLGradient.SetStartColor( - Color(maBColorModifierStack.getModifiedColor(rFiGrAtt.getStartColor()))); - o_rVCLGradient.SetEndColor( - Color(maBColorModifierStack.getModifiedColor(rFiGrAtt.getEndColor()))); + o_rVCLGradient.SetStartColor(Color(maBColorModifierStack.getModifiedColor(aStartColor))); + o_rVCLGradient.SetEndColor(Color(maBColorModifierStack.getModifiedColor(aEndColor))); } o_rVCLGradient.SetAngle( @@ -281,40 +290,7 @@ void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( // defaults for intensity; those were computed into the start/end colors already o_rVCLGradient.SetStartIntensity(100); o_rVCLGradient.SetEndIntensity(100); - - switch (rFiGrAtt.getStyle()) - { - default: // attribute::GradientStyle::Linear : - { - o_rVCLGradient.SetStyle(GradientStyle::Linear); - break; - } - case attribute::GradientStyle::Axial: - { - o_rVCLGradient.SetStyle(GradientStyle::Axial); - break; - } - case attribute::GradientStyle::Radial: - { - o_rVCLGradient.SetStyle(GradientStyle::Radial); - break; - } - case attribute::GradientStyle::Elliptical: - { - o_rVCLGradient.SetStyle(GradientStyle::Elliptical); - break; - } - case attribute::GradientStyle::Square: - { - o_rVCLGradient.SetStyle(GradientStyle::Square); - break; - } - case attribute::GradientStyle::Rect: - { - o_rVCLGradient.SetStyle(GradientStyle::Rect); - break; - } - } + o_rVCLGradient.SetStyle(rFiGrAtt.getStyle()); } void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) @@ -325,7 +301,7 @@ void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGr WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill); mpMetaFile->AddAction(new MetaCommentAction( - "XPATHFILL_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + "XPATHFILL_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), aMemStm.TellEnd())); mnSvtGraphicFillCount++; } @@ -336,7 +312,7 @@ void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill const* pSvtGrap if (pSvtGraphicFill && mnSvtGraphicFillCount) { mnSvtGraphicFillCount--; - mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END")); + mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END"_ostr)); } } @@ -509,7 +485,7 @@ void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke const* pS WriteSvtGraphicStroke(aMemStm, *pSvtGraphicStroke); mpMetaFile->AddAction(new MetaCommentAction( - "XPATHSTROKE_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + "XPATHSTROKE_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), aMemStm.TellEnd())); mnSvtGraphicStrokeCount++; } @@ -520,7 +496,7 @@ void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke const* pSvt if (pSvtGraphicStroke && mnSvtGraphicStrokeCount) { mnSvtGraphicStrokeCount--; - mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END")); + mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END"_ostr)); } } @@ -546,7 +522,8 @@ void VclMetafileProcessor2D::popList() } // init static break iterator -uno::Reference<css::i18n::XBreakIterator> VclMetafileProcessor2D::mxBreakIterator; +vcl::DeleteOnDeinit<uno::Reference<css::i18n::XBreakIterator>> + VclMetafileProcessor2D::mxBreakIterator; VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev) @@ -718,17 +695,7 @@ VclMetafileProcessor2D::~VclMetafileProcessor2D() - UnoControlPDFExportContact is only created when PDFExtOutDevData is used at the target and uno control data is created in UnoControlPDFExportContact::do_PaintObject. - This may be added in primitive MetaFile renderer. - Adding support... - OOps, the necessary helper stuff is in svx/source/form/formpdxexport.cxx in namespace - svxform. Have to talk to FS if this has to be like that. Especially since - vcl::PDFWriter::AnyWidget is filled out, which is already part of vcl. - Wrote an eMail to FS, he is on vacation currently. I see no reason why not to move - that stuff to somewhere else, maybe tools or svtools ?!? We will see... - Moved to toolkit, so I have to link against it. I tried VCL first, but it did - not work since VCLUnoHelper::CreateFont is unresolved in VCL (!). Other than the name - may imply, it is defined in toolkit (!). Since toolkit is linked against VCL itself, - the lowest movement plane is toolkit. + This was added in primitive MetaFile renderer. Checked form control export, it works well. Done. - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are @@ -836,8 +803,17 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi } case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: { - processPolyPolygonGraphicPrimitive2D( - static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + if (maBColorModifierStack.count()) + { + // tdf#151104 unfortunately processPolyPolygonGraphicPrimitive2D below + // does not support an active BColorModifierStack, so use the default + process(rCandidate); + } + else + { + processPolyPolygonGraphicPrimitive2D( + static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + } break; } case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: @@ -923,17 +899,10 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi } case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: { - RenderObjectInfoPrimitive2D( + processObjectInfoPrimitive2D( static_cast<const primitive2d::ObjectInfoPrimitive2D&>(rCandidate)); break; } - case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: - case PRIMITIVE2D_ID_GLOWPRIMITIVE2D: - case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: - { - processPrimitive2DOnPixelProcessor(rCandidate); - break; - } default: { // process recursively @@ -943,6 +912,51 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi } } +void VclMetafileProcessor2D::processObjectInfoPrimitive2D( + primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D) +{ + // tdf#154982 process content first, so this object overrides any nested one + process(rObjectInfoPrimitive2D.getChildren()); + + // currently StructureTagPrimitive2D is only used for SdrObjects - have to + // avoid adding Alt text if the SdrObject is not actually tagged, as it + // would then end up on an unrelated structure element. + if (mpCurrentStructureTag && mpCurrentStructureTag->isTaggedSdrObject()) + { + // Create image alternative description from ObjectInfoPrimitive2D info + // for PDF export, for the currently active SdrObject's structure element + if (mpPDFExtOutDevData->GetIsExportTaggedPDF()) + { + OUString aAlternateDescription; + + if (!rObjectInfoPrimitive2D.getTitle().isEmpty()) + { + aAlternateDescription += rObjectInfoPrimitive2D.getTitle(); + } + + if (!rObjectInfoPrimitive2D.getDesc().isEmpty()) + { + if (!aAlternateDescription.isEmpty()) + { + aAlternateDescription += " - "; + } + + aAlternateDescription += rObjectInfoPrimitive2D.getDesc(); + } + + // Use SetAlternateText to set it. This will work as long as some + // structure is used (see PDFWriterImpl::setAlternateText and + // m_nCurrentStructElement - tagged PDF export works with this in + // Draw/Impress/Writer, but not in Calc due to too less structure...) + //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..? + if (!aAlternateDescription.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(aAlternateDescription); + } + } + } +} + void VclMetafileProcessor2D::processGraphicPrimitive2D( const primitive2d::GraphicPrimitive2D& rGraphicPrimitive) { @@ -1034,38 +1048,6 @@ void VclMetafileProcessor2D::processGraphicPrimitive2D( sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY()))); } - // Create image alternative description from ObjectInfoPrimitive2D info - // for PDF export - if (mpPDFExtOutDevData->GetIsExportTaggedPDF() && nullptr != getObjectInfoPrimitive2D()) - { - OUString aAlternateDescription; - - if (!getObjectInfoPrimitive2D()->getTitle().isEmpty()) - { - aAlternateDescription += getObjectInfoPrimitive2D()->getTitle(); - } - - if (!getObjectInfoPrimitive2D()->getDesc().isEmpty()) - { - if (!aAlternateDescription.isEmpty()) - { - aAlternateDescription += " - "; - } - - aAlternateDescription += getObjectInfoPrimitive2D()->getDesc(); - } - - // Use SetAlternateText to set it. This will work as long as some - // structure is used (see PDFWriterImpl::setAlternateText and - // m_nCurrentStructElement - tagged PDF export works with this in - // Draw/Impress/Writer, but not in Calc due to too less structure...) - //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..? - if (!aAlternateDescription.isEmpty()) - { - mpPDFExtOutDevData->SetAlternateText(aAlternateDescription); - } - } - // #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped // object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded, // uncropped region. Thus, correct order is aCropRect, aCurrentRect @@ -1089,7 +1071,7 @@ void VclMetafileProcessor2D::processControlPrimitive2D( uno::Reference<beans::XPropertySetInfo> xPropertyInfo( xModelProperties.is() ? xModelProperties->getPropertySetInfo() : uno::Reference<beans::XPropertySetInfo>()); - static const OUStringLiteral sPrintablePropertyName(u"Printable"); + static constexpr OUString sPrintablePropertyName(u"Printable"_ustr); if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(sPrintablePropertyName)) { @@ -1108,14 +1090,25 @@ void VclMetafileProcessor2D::processControlPrimitive2D( if (!bIsPrintableControl) return; + ::std::optional<sal_Int32> oAnchorParent; + if (mpPDFExtOutDevData) + { + if (rControlPrimitive.GetAnchorStructureElementKey()) + { + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( + rControlPrimitive.GetAnchorStructureElementKey()); + oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + } + } + const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields()); bool bDoProcessRecursively(true); + bool bDecorative = (mpCurrentStructureTag && mpCurrentStructureTag->isDecorative()); - if (bPDFExport) + if (bPDFExport && !bDecorative) { // PDF export. Emulate data handling from UnoControlPDFExportContact - // I have now moved describePDFControl to toolkit, thus i can implement the PDF - // form control support now as follows std::unique_ptr<vcl::PDFWriter::AnyWidget> pPDFControl( ::toolkitform::describePDFControl(rXControl, *mpPDFExtOutDevData)); @@ -1135,9 +1128,37 @@ void VclMetafileProcessor2D::processControlPrimitive2D( mpOutputDevice->GetMapMode()); pPDFControl->TextFont.SetFontSize(aFontSize); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); + vcl::PDFWriter::StructAttributeValue role; + switch (pPDFControl->Type) + { + case vcl::PDFWriter::PushButton: + role = vcl::PDFWriter::Pb; + break; + case vcl::PDFWriter::RadioButton: + role = vcl::PDFWriter::Rb; + break; + case vcl::PDFWriter::CheckBox: + role = vcl::PDFWriter::Cb; + break; + default: // there is a paucity of roles, tv is the catch-all one + role = vcl::PDFWriter::Tv; + break; + } + // ISO 14289-1:2014, Clause: 7.18.4 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Role, role); + // ISO 14289-1:2014, Clause: 7.18.1 + OUString const& rAltText(rControlPrimitive.GetAltText()); + if (!rAltText.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(rAltText); + } mpPDFExtOutDevData->CreateControl(*pPDFControl); mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject); // do not process recursively @@ -1151,6 +1172,31 @@ void VclMetafileProcessor2D::processControlPrimitive2D( } } + if (!bDoProcessRecursively) + { + return; + } + + if (mpPDFExtOutDevData) + { // no corresponding PDF Form, use Figure instead + if (!bDecorative) + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Figure); + else + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement); + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, vcl::PDFWriter::Block); + auto const range(rControlPrimitive.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect(basegfx::fround<tools::Long>(range.getMinX()), + basegfx::fround<tools::Long>(range.getMinY()), + basegfx::fround<tools::Long>(range.getMaxX()), + basegfx::fround<tools::Long>(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + OUString const& rAltText(rControlPrimitive.GetAltText()); + if (!rAltText.isEmpty() && !bDecorative) + { + mpPDFExtOutDevData->SetAlternateText(rAltText); + } + } + // #i93169# used flag the wrong way; true means that nothing was done yet if (bDoProcessRecursively) { @@ -1195,6 +1241,15 @@ void VclMetafileProcessor2D::processControlPrimitive2D( { process(rControlPrimitive); } + + if (mpPDFExtOutDevData) + { + mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } + } } void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( @@ -1202,7 +1257,7 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( { // support for FIELD_SEQ_BEGIN, FIELD_SEQ_END and URL. It wraps text primitives (but is not limited to) // thus do the MetafileAction embedding stuff but just handle recursively. - const OString aCommentStringCommon("FIELD_SEQ_BEGIN"); + static constexpr OString aCommentStringCommon("FIELD_SEQ_BEGIN"_ostr); OUString aURL; switch (rFieldPrimitive.getType()) @@ -1214,7 +1269,7 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( } case drawinglayer::primitive2d::FIELD_TYPE_PAGE: { - mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField")); + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField"_ostr)); break; } case drawinglayer::primitive2d::FIELD_TYPE_URL: @@ -1238,7 +1293,7 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( process(rContent); // for the end comment the type is not relevant yet, they are all the same. Just add. - mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END")); + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END"_ostr)); if (!(mpPDFExtOutDevData && drawinglayer::primitive2d::FIELD_TYPE_URL == rFieldPrimitive.getType())) @@ -1251,7 +1306,8 @@ void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( static_cast<sal_Int32>(ceil(aViewRange.getMaxX())), static_cast<sal_Int32>(ceil(aViewRange.getMaxY()))); vcl::PDFExtOutDevBookmarkEntry aBookmark; - aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic); + OUString const content(rFieldPrimitive.getValue("Representation")); + aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic, content); aBookmark.aBookmark = aURL; std::vector<vcl::PDFExtOutDevBookmarkEntry>& rBookmarks = mpPDFExtOutDevData->GetBookmarks(); rBookmarks.push_back(aBookmark); @@ -1262,7 +1318,7 @@ void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D( { // process recursively and add MetaFile comment process(rLinePrimitive); - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL"_ostr)); } void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( @@ -1272,14 +1328,14 @@ void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( if (mbInListItem) { maListElements.push(vcl::PDFWriter::LILabel); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LILabel); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LILabel); } // process recursively and add MetaFile comment process(rBulletPrimitive); // in Outliner::PaintBullet(), a MetafileComment for bullets is added, too. The // "XTEXT_EOC" is used, use here, too. - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC"_ostr)); if (mbInListItem) { @@ -1295,7 +1351,7 @@ void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive) { - const OString aCommentString("XTEXT_EOP"); + static constexpr OString aCommentString("XTEXT_EOP"_ostr); static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore if (nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport) @@ -1311,7 +1367,7 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( { // No Tagged PDF -> Dump as Paragraph // Emulate data handling from old ImpEditEngine::Paint - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); // Process recursively and add MetaFile comment process(rParagraphPrimitive); @@ -1337,7 +1393,7 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a) { maListElements.push(vcl::PDFWriter::List); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::List); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::List); } } else // if(nNewOutlineLevel < mnCurrentOutlineLevel) @@ -1368,13 +1424,13 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( { // Dump as ListItem maListElements.push(vcl::PDFWriter::ListItem); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::ListItem); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::ListItem); mbInListItem = true; } else { // Dump as Paragraph - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); } // Process recursively and add MetaFile comment @@ -1391,7 +1447,7 @@ void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive) { // add MetaFile comment, process recursively and add MetaFile comment - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN"_ostr)); process(rBlockPrimitive); if (mnCurrentOutlineLevel >= 0) @@ -1403,7 +1459,7 @@ void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( } } - mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END")); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END"_ostr)); } void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( @@ -1418,7 +1474,7 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( if (mbInListItem && mbBulletPresent) { maListElements.push(vcl::PDFWriter::LIBody); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LIBody); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LIBody); } // directdraw of text simple portion; use default processing @@ -1433,12 +1489,13 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( // #i101169# if(pTextDecoratedCandidate) { // support for TEXT_ MetaFile actions only for decorated texts - if (!mxBreakIterator.is()) + if (!mxBreakIterator.get() || !mxBreakIterator.get()->get()) { uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext()); - mxBreakIterator = i18n::BreakIterator::create(xContext); + mxBreakIterator.set(i18n::BreakIterator::create(xContext)); } + auto& rBreakIterator = *mxBreakIterator.get()->get(); const OUString& rTxt = rTextCandidate.getText(); const sal_Int32 nTextLength(rTextCandidate.getTextLength()); // rTxt.getLength()); @@ -1449,16 +1506,16 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( const sal_Int32 nTextPosition(rTextCandidate.getTextPosition()); sal_Int32 nDone; - sal_Int32 nNextCellBreak(mxBreakIterator->nextCharacters( + sal_Int32 nNextCellBreak(rBreakIterator.nextCharacters( rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, nDone)); - css::i18n::Boundary nNextWordBoundary(mxBreakIterator->getWordBoundary( + css::i18n::Boundary nNextWordBoundary(rBreakIterator.getWordBoundary( rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true)); sal_Int32 nNextSentenceBreak( - mxBreakIterator->endOfSentence(rTxt, nTextPosition, rLocale)); - const OString aCommentStringA("XTEXT_EOC"); - const OString aCommentStringB("XTEXT_EOW"); - const OString aCommentStringC("XTEXT_EOS"); + rBreakIterator.endOfSentence(rTxt, nTextPosition, rLocale)); + static constexpr OStringLiteral aCommentStringA("XTEXT_EOC"); + static constexpr OStringLiteral aCommentStringB("XTEXT_EOW"); + static constexpr OStringLiteral aCommentStringC("XTEXT_EOS"); for (sal_Int32 i(nTextPosition); i < nTextPosition + nTextLength; i++) { @@ -1467,21 +1524,21 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( { mpMetaFile->AddAction( new MetaCommentAction(aCommentStringA, i - nTextPosition)); - nNextCellBreak = mxBreakIterator->nextCharacters( + nNextCellBreak = rBreakIterator.nextCharacters( rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); } if (i == nNextWordBoundary.endPos) { mpMetaFile->AddAction( new MetaCommentAction(aCommentStringB, i - nTextPosition)); - nNextWordBoundary = mxBreakIterator->getWordBoundary( + nNextWordBoundary = rBreakIterator.getWordBoundary( rTxt, i + 1, rLocale, css::i18n::WordType::ANY_WORD, true); } if (i == nNextSentenceBreak) { mpMetaFile->AddAction( new MetaCommentAction(aCommentStringC, i - nTextPosition)); - nNextSentenceBreak = mxBreakIterator->endOfSentence(rTxt, i + 1, rLocale); + nNextSentenceBreak = rBreakIterator.endOfSentence(rTxt, i + 1, rLocale); } } } @@ -1500,9 +1557,11 @@ void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D( basegfx::B2DPolygon aLeft, aRight; splitLinePolygon(rBasePolygon, aLeft, aRight); rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPLeft( - new primitive2d::PolygonHairlinePrimitive2D(aLeft, rHairlinePrimitive.getBColor())); + new primitive2d::PolygonHairlinePrimitive2D(std::move(aLeft), + rHairlinePrimitive.getBColor())); rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight( - new primitive2d::PolygonHairlinePrimitive2D(aRight, rHairlinePrimitive.getBColor())); + new primitive2d::PolygonHairlinePrimitive2D(std::move(aRight), + rHairlinePrimitive.getBColor())); processBasePrimitive2D(*xPLeft); processBasePrimitive2D(*xPRight); @@ -1550,10 +1609,12 @@ void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( basegfx::B2DPolygon aLeft, aRight; splitLinePolygon(rBasePolygon, aLeft, aRight); rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPLeft( - new primitive2d::PolygonStrokePrimitive2D(aLeft, rStrokePrimitive.getLineAttribute(), + new primitive2d::PolygonStrokePrimitive2D(std::move(aLeft), + rStrokePrimitive.getLineAttribute(), rStrokePrimitive.getStrokeAttribute())); rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight( - new primitive2d::PolygonStrokePrimitive2D(aRight, rStrokePrimitive.getLineAttribute(), + new primitive2d::PolygonStrokePrimitive2D(std::move(aRight), + rStrokePrimitive.getLineAttribute(), rStrokePrimitive.getStrokeAttribute())); processBasePrimitive2D(*xPLeft); @@ -1572,7 +1633,7 @@ void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( const attribute::LineAttribute& rLine = rStrokePrimitive.getLineAttribute(); // create MetaPolyLineActions, but without LineStyle::Dash - if (basegfx::fTools::more(rLine.getWidth(), 0.0)) + if (rLine.getWidth() > 0.0) { const attribute::StrokeAttribute& rStroke = rStrokePrimitive.getStrokeAttribute(); @@ -1583,7 +1644,7 @@ void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( // use the transformed line width LineInfo aLineInfo(LineStyle::Solid, - basegfx::fround(getTransformedLineWidth(rLine.getWidth()))); + std::round(getTransformedLineWidth(rLine.getWidth()))); aLineInfo.SetLineJoin(rLine.getLineJoin()); aLineInfo.SetLineCap(rLine.getLineCap()); @@ -1753,6 +1814,16 @@ void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D( // need to handle PolyPolygonGraphicPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END basegfx::B2DPolyPolygon aLocalPolyPolygon(rBitmapCandidate.getB2DPolyPolygon()); + if (!rBitmapCandidate.getDefinitionRange().isEmpty() + && aLocalPolyPolygon.getB2DRange() != rBitmapCandidate.getDefinitionRange()) + { + // The range which defines the bitmap fill is defined and different from the + // range of the defining geometry (e.g. used for FillStyle UseSlideBackground). + // This cannot be done calling vcl, thus use decomposition here directly + process(rBitmapCandidate); + return; + } + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; @@ -1903,7 +1974,7 @@ void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( aToolsPolyPolygon, Hatch(aHatchStyle, Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), - basegfx::fround(rFillHatchAttribute.getDistance()), + basegfx::fround<tools::Long>(rFillHatchAttribute.getDistance()), Degree10(basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttribute.getAngle()))))); impEndSvtGraphicFill(pSvtGraphicFill.get()); @@ -1912,42 +1983,126 @@ void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate) { - basegfx::B2DVector aScale, aTranslate; - double fRotate, fShearX; + bool useDecompose(false); - maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + if (!useDecompose) + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + // detect if transformation is rotated, sheared or mirrored in X and/or Y + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) + || aScale.getX() < 0.0 || aScale.getY() < 0.0) + { + // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. + // This is because VCL Gradient mechanism does *not* support to rotate the gradient + // with objects and this case is not expressible in a Metafile (and cannot be added + // since the FileFormats used, e.g. *.wmf, do not support it either). + // Such cases happen when a graphic object uses a Metafile as graphic information or + // a fill style definition uses a Metafile. In this cases the graphic content is + // rotated with the graphic or filled object; this is not supported by the target + // format of this conversion renderer - Metafiles. + // To solve this, not a Gradient is written, but the decomposition of this object + // is written to the Metafile. This is the PolyPolygons building the gradient fill. + // These will need more space and time, but the result will be as if the Gradient + // was rotated with the object. + // This mechanism is used by all exporters still not using Primitives (e.g. Print, + // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile + // transfers. One more reason to *change* these to primitives. + // BTW: One more example how useful the principles of primitives are; the decomposition + // is by definition a simpler, maybe more expensive representation of the same content. + useDecompose = true; + } + } - if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)) + // tdf#150551 for PDF export, use the decomposition for better gradient visualization + if (!useDecompose && nullptr != mpPDFExtOutDevData) { - // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. - // This is because VCL Gradient mechanism does *not* support to rotate the gradient - // with objects and this case is not expressible in a Metafile (and cannot be added - // since the FileFormats used, e.g. *.wmf, do not support it either). - // Such cases happen when a graphic object uses a Metafile as graphic information or - // a fill style definition uses a Metafile. In this cases the graphic content is - // rotated with the graphic or filled object; this is not supported by the target - // format of this conversion renderer - Metafiles. - // To solve this, not a Gradient is written, but the decomposition of this object - // is written to the Metafile. This is the PolyPolygons building the gradient fill. - // These will need more space and time, but the result will be as if the Gradient - // was rotated with the object. - // This mechanism is used by all exporters still not using Primitives (e.g. Print, - // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile - // transfers. One more reason to *change* these to primitives. - // BTW: One more example how useful the principles of primitives are; the decomposition - // is by definition a simpler, maybe more expensive representation of the same content. - process(rGradientCandidate); - return; + useDecompose = true; } basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon()); - if (aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) + if (!useDecompose && aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) { // the range which defines the gradient is different from the range of the // geometry (used for writer frames). This cannot be done calling vcl, thus use // decomposition here + useDecompose = true; + } + + const attribute::FillGradientAttribute& rFillGradient(rGradientCandidate.getFillGradient()); + + if (!useDecompose && rFillGradient.cannotBeHandledByVCL()) + { + // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient, + // that will *not* be capable of representing this properly. Use the + // correct decomposition instead + useDecompose = true; + } + + if (useDecompose) + { + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + + // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // write the color stops to a memory stream + SvMemoryStream aMemStm; + VersionCompatWrite aCompat(aMemStm, 1); + + const basegfx::BColorStops& rColorStops(rFillGradient.getColorStops()); + sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size())); + aMemStm.WriteUInt16(nTmp); + + for (auto const& rCand : rColorStops) + { + aMemStm.WriteDouble(rCand.getStopOffset()); + const basegfx::BColor& rColor(rCand.getStopColor()); + aMemStm.WriteDouble(rColor.getRed()); + aMemStm.WriteDouble(rColor.getGreen()); + aMemStm.WriteDouble(rColor.getBlue()); + } + + // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END' + // that is capable of holding the new color step information, plus the + // already used MetaActionType::GRADIENTEX. + // With that combination only places that know about that new BGRAD_SEQ_* will + // use it while all others will work on the created decomposition of the + // gradient for compatibility - which are single-color filled polygons + pMetaFile->AddAction(new MetaCommentAction( + "BGRAD_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + + // create MetaActionType::GRADIENTEX + // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and + // basegfx::BGradient here directly, but may have to add streaming OPs + // for these, so for now just go with what we use all the time. The real + // work for improvement should not go to this 'compromise' but to a real + // re-work of the SVG export (or/and others) to no longer work on metafiles + // but on UNO API or primitives (whatever fits best to the specific export) + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + Gradient aVCLGradient; + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); + aLocalPolyPolygon.transform(maCurrentTransformation); + const tools::PolyPolygon aToolsPolyPolygon( + getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); + mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); + } + + // use decompose to draw, will create PolyPolygon ColorFill actions process(rGradientCandidate); + + // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // close the BGRAD_SEQ_* actions range + pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END"_ostr)); + } + return; } @@ -1959,8 +2114,7 @@ void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( // it is safest to use the VCL OutputDevice::DrawGradient method which creates those. // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon Gradient aVCLGradient; - impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rGradientCandidate.getFillGradient(), - false); + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); aLocalPolyPolygon.transform(maCurrentTransformation); // #i82145# ATM VCL printing of gradients using curved shapes does not work, @@ -1981,16 +2135,16 @@ void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( switch (aVCLGradient.GetStyle()) { - default: // GradientStyle::Linear: - case GradientStyle::Axial: + default: // css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: eGrad = SvtGraphicFill::GradientType::Linear; break; - case GradientStyle::Radial: - case GradientStyle::Elliptical: + case css::awt::GradientStyle_RADIAL: + case css::awt::GradientStyle_ELLIPTICAL: eGrad = SvtGraphicFill::GradientType::Radial; break; - case GradientStyle::Square: - case GradientStyle::Rect: + case css::awt::GradientStyle_SQUARE: + case css::awt::GradientStyle_RECT: eGrad = SvtGraphicFill::GradientType::Rectangular; break; } @@ -2115,7 +2269,7 @@ void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( if (!bForceToMetafile && 1 == rContent.size()) { - const primitive2d::Primitive2DReference xReference(rContent[0]); + const primitive2d::Primitive2DReference xReference(rContent.front()); pPoPoColor = dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( xReference.get()); } @@ -2159,6 +2313,11 @@ void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( // various content, create content-metafile GDIMetaFile aContentMetafile; + + // tdf#155479 always forward propagate SVG flag for sub-content, + // it may contain cannotBeHandledByVCL gradients or transparencyGradients + aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); + const tools::Rectangle aPrimitiveRectangle( impDumpToMetaFile(rContent, aContentMetafile)); @@ -2172,7 +2331,7 @@ void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( basegfx::fround(rUniTransparenceCandidate.getTransparence() * 255.0))); const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); - aVCLGradient.SetStyle(GradientStyle::Linear); + aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR); aVCLGradient.SetStartColor(aTransColor); aVCLGradient.SetEndColor(aTransColor); aVCLGradient.SetAngle(0_deg10); @@ -2202,29 +2361,82 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( // FillGradientPrimitive2D and reconstruct the gradient. // If that detection goes wrong, I have to create a transparence-blended bitmap. Eventually // do that in stripes, else RenderTransparencePrimitive2D may just be used - const primitive2d::Primitive2DContainer& rContent = rTransparenceCandidate.getChildren(); - const primitive2d::Primitive2DContainer& rTransparence - = rTransparenceCandidate.getTransparence(); + const primitive2d::Primitive2DContainer& rContent(rTransparenceCandidate.getChildren()); + const primitive2d::Primitive2DContainer& rTransparence( + rTransparenceCandidate.getTransparence()); if (rContent.empty() || rTransparence.empty()) return; // try to identify a single FillGradientPrimitive2D in the - // transparence part of the primitive - const primitive2d::FillGradientPrimitive2D* pFiGradient = nullptr; + // transparence part of the primitive. The hope is to handle + // the more specific case in a better way than the general + // TransparencePrimitive2D which has strongly separated + // definitions for transparency and content, both completely + // free definable by primitives + const primitive2d::FillGradientPrimitive2D* pFiGradient(nullptr); static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore + // check for single FillGradientPrimitive2D if (!bForceToBigTransparentVDev && 1 == rTransparence.size()) { - const primitive2d::Primitive2DReference xReference(rTransparence[0]); - pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>(xReference.get()); + pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>( + rTransparence.front().get()); + + // check also for correct ID to exclude derived implementations + if (pFiGradient + && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D != pFiGradient->getPrimitive2DID()) + pFiGradient = nullptr; } - // Check also for correct ID to exclude derived implementations - if (pFiGradient && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D == pFiGradient->getPrimitive2DID()) + // tdf#155479 preps for holding extra-MCGR infos + bool bSVGTransparencyColorStops(false); + basegfx::BColorStops aSVGTransparencyColorStops; + + // MCGR: tdf#155437 If we have identified a transparency gradient, + // check if VCL is able to handle it at all + if (nullptr != pFiGradient && pFiGradient->getFillGradient().cannotBeHandledByVCL()) { - // various content, create content-metafile + // If not, reset the pointer and do not make use of this special case. + // Adding a gradient in incomplete state that can not be handled by vcl + // makes no sense and will knowingly lead to errors, especially with + // MCGR extended possibilities. I checked what happens with the + // MetaFloatTransparentAction added by OutputDevice::DrawTransparent, but + // in most cases it gets converted to bitmap or even ignored, see e.g. + // - vcl/source/gdi/pdfwriter_impl2.cxx for PDF export + // - vcl/source/filter/wmf/wmfwr.cxx -> does ignore TransparenceGradient completely + // - vcl/source/filter/wmf/emfwr.cxx -> same + // - vcl/source/filter/eps/eps.cxx -> same + // NOTE: Theoretically it would be possible to make the new extended Gradient data + // available in metafiles, with the known limitations (not backward comp, all + // places using it would need adaption, ...), but combined with knowing that nearly + // all usages ignore or render it locally anyways makes that a non-option. + + // tdf#155479 Yepp, as already mentioned above we need to add + // some MCGR infos in case of SVG export, prepare that here + if (mpOutputDevice->GetConnectMetaFile()->getSVG()) + { + // for SVG, do not use decompose & prep extra data + bSVGTransparencyColorStops = true; + aSVGTransparencyColorStops = pFiGradient->getFillGradient().getColorStops(); + } + else + { + // use decomposition + pFiGradient = nullptr; + } + } + + if (nullptr != pFiGradient) + { + // this combination of Gradient can be expressed/handled by + // vcl/metafile, so add it directly. various content, create content-metafile GDIMetaFile aContentMetafile; + + // tdf#155479 always forward propagate SVG flag for sub-content, + // it may contain cannotBeHandledByVCL gradients or transparencyGradients + aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); + const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile)); // re-create a VCL-gradient from FillGradientPrimitive2D @@ -2232,126 +2444,192 @@ void VclMetafileProcessor2D::processTransparencePrimitive2D( impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(), true); - // render it to VCL - mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), - aPrimitiveRectangle.GetSize(), aVCLGradient); - } - else - { - // sub-transparence group. Draw to VDev first. - // this may get refined to tiling when resolution is too big here - - // need to avoid switching off MapMode stuff here; maybe need another - // tooling class, cannot just do the same as with the pixel renderer. - // Need to experiment... - - // Okay, basic implementation finished and tested. The DPI stuff was hard - // and not easy to find out that it's needed. - // Since this will not yet happen normally (as long as no one constructs - // transparence primitives with non-trivial transparence content) i will for now not - // refine to tiling here. - - basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D())); - aViewRange.transform(maCurrentTransformation); - const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aViewRange.getMinX())), - static_cast<sal_Int32>(floor(aViewRange.getMinY())), - static_cast<sal_Int32>(ceil(aViewRange.getMaxX())), - static_cast<sal_Int32>(ceil(aViewRange.getMaxY()))); - const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(aRectLogic)); - Size aSizePixel(aRectPixel.GetSize()); - ScopedVclPtrInstance<VirtualDevice> aBufferDevice; - const sal_uInt32 nMaxSquarePixels(500000); - const sal_uInt32 nViewVisibleArea(aSizePixel.getWidth() * aSizePixel.getHeight()); - double fReduceFactor(1.0); - - if (nViewVisibleArea > nMaxSquarePixels) - { - // reduce render size - fReduceFactor = sqrt(double(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); - aSizePixel = Size( - basegfx::fround(static_cast<double>(aSizePixel.getWidth()) * fReduceFactor), - basegfx::fround(static_cast<double>(aSizePixel.getHeight()) * fReduceFactor)); - } - - if (aBufferDevice->SetOutputSizePixel(aSizePixel)) - { - // create and set MapModes for target devices - MapMode aNewMapMode(mpOutputDevice->GetMapMode()); - aNewMapMode.SetOrigin(Point(-aRectLogic.Left(), -aRectLogic.Top())); - aBufferDevice->SetMapMode(aNewMapMode); - - // prepare view transformation for target renderers - // ATTENTION! Need to apply another scaling because of the potential DPI differences - // between Printer and VDev (mpOutputDevice and aBufferDevice here). - // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used. - basegfx::B2DHomMatrix aViewTransform(aBufferDevice->GetViewTransformation()); - const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const Size aDPINew(aBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const double fDPIXChange(static_cast<double>(aDPIOld.getWidth()) - / static_cast<double>(aDPINew.getWidth())); - const double fDPIYChange(static_cast<double>(aDPIOld.getHeight()) - / static_cast<double>(aDPINew.getHeight())); - - if (!basegfx::fTools::equal(fDPIXChange, 1.0) - || !basegfx::fTools::equal(fDPIYChange, 1.0)) - { - aViewTransform.scale(fDPIXChange, fDPIYChange); - } - - // also take scaling from Size reduction into account - if (!basegfx::fTools::equal(fReduceFactor, 1.0)) - { - aViewTransform.scale(fReduceFactor, fReduceFactor); - } - - // create view information and pixel renderer. Reuse known ViewInformation - // except new transformation and range - const geometry::ViewInformation2D aViewInfo( - getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange, - getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime()); - - VclPixelProcessor2D aBufferProcessor(aViewInfo, *aBufferDevice); - - // draw content using pixel renderer - const Point aEmptyPoint; - aBufferProcessor.process(rContent); - const Bitmap aBmContent(aBufferDevice->GetBitmap(aEmptyPoint, aSizePixel)); - - // draw transparence using pixel renderer - aBufferDevice->Erase(); - aBufferProcessor.process(rTransparence); - const AlphaMask aBmAlpha(aBufferDevice->GetBitmap(aEmptyPoint, aSizePixel)); + if (bSVGTransparencyColorStops) + { + // tdf#155479 create action directly & add extra + // MCGR infos to the metafile, do that by adding - ONLY in + // case of SVG export - to the MetaFileAction. For that + // reason, do what OutputDevice::DrawTransparent will do, + // but locally. + // NOTE: That would be good for this whole + // VclMetafileProcessor2D anyways to allow to get it + // completely independent from OutputDevice in the long run + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + rtl::Reference<::MetaFloatTransparentAction> pAction( + new MetaFloatTransparentAction(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient)); - // paint - mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), - BitmapEx(aBmContent, aBmAlpha)); + pAction->addSVGTransparencyColorStops(aSVGTransparencyColorStops); + pMetaFile->AddAction(pAction); } + else + { + // render it to VCL (creates MetaFloatTransparentAction) + mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient); + } + return; } + + // Here we need to create a correct replacement visualization for the + // TransparencePrimitive2D for the target metafile. + // I replaced the n'th iteration to convert-to-bitmap which was + // used here by using the existing tooling. The orig here was also producing + // transparency errors with test-file from tdf#155437 on the right part of the + // image. + // Just rely on existing tooling doing the right thing in one place, so also + // corrections/optimizations can be in one single place + + // Start by getting logic range of content, transform object-to-world, then world-to-view + // to get to discrete values ('pixels'). Matrix multiplication is right-to-left (and not + // commutative) + basegfx::B2DRange aLogicRange(rTransparenceCandidate.getB2DRange(getViewInformation2D())); + aLogicRange.transform(mpOutputDevice->GetViewTransformation() * maCurrentTransformation); + + // expand in discrete coordinates to next-bigger 'pixel' boundaries and remember + // created discrete range + aLogicRange.expand( + basegfx::B2DPoint(floor(aLogicRange.getMinX()), floor(aLogicRange.getMinY()))); + aLogicRange.expand(basegfx::B2DPoint(ceil(aLogicRange.getMaxX()), ceil(aLogicRange.getMaxY()))); + const basegfx::B2DRange aDiscreteRange(aLogicRange); + + // transform back from discrete to world coordinates: this creates the + // pixel-boundaries extended logic range we need to cover all content + // reliably + aLogicRange.transform(mpOutputDevice->GetInverseViewTransformation()); + + // create transform embedding for renderer. Goal is to translate what we + // want to paint to top/left 0/0 and the calculated discrete size + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aLogicRange.getMinX(), -aLogicRange.getMinY())); + const double fLogicWidth( + basegfx::fTools::equalZero(aLogicRange.getWidth()) ? 1.0 : aLogicRange.getWidth()); + const double fLogicHeight( + basegfx::fTools::equalZero(aLogicRange.getHeight()) ? 1.0 : aLogicRange.getHeight()); + aEmbedding.scale(aDiscreteRange.getWidth() / fLogicWidth, + aDiscreteRange.getHeight() / fLogicHeight); + + // use the whole TransparencePrimitive2D as input (no need to create a new + // one with the sub-contents, these are ref-counted) and add to embedding + // primitive2d::TransparencePrimitive2D& rTrCand(); + primitive2d::Primitive2DContainer xEmbedSeq{ &const_cast<primitive2d::TransparencePrimitive2D&>( + rTransparenceCandidate) }; + + // tdf#158743 when embedding, do not forget to 1st apply the evtl. used + // CurrentTransformation (right-to-left, apply that 1st) + xEmbedSeq = primitive2d::Primitive2DContainer{ new primitive2d::TransformPrimitive2D( + aEmbedding * maCurrentTransformation, std::move(xEmbedSeq)) }; + + // use empty ViewInformation & a useful MaximumQuadraticPixels + // limitation to paint the content + const auto aViewInformation2D(geometry::createViewInformation2D({})); + const sal_uInt32 nMaximumQuadraticPixels(500000); + const BitmapEx aBitmapEx(convertToBitmapEx( + std::move(xEmbedSeq), aViewInformation2D, basegfx::fround(aDiscreteRange.getWidth()), + basegfx::fround(aDiscreteRange.getHeight()), nMaximumQuadraticPixels)); + + // add to target metafile (will create MetaFloatTransparentAction) + mpOutputDevice->DrawBitmapEx(Point(basegfx::fround<tools::Long>(aLogicRange.getMinX()), + basegfx::fround<tools::Long>(aLogicRange.getMinY())), + Size(basegfx::fround<tools::Long>(aLogicRange.getWidth()), + basegfx::fround<tools::Long>(aLogicRange.getHeight())), + aBitmapEx); } void VclMetafileProcessor2D::processStructureTagPrimitive2D( const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate) { + ::comphelper::ValueRestorationGuard const g(mpCurrentStructureTag, &rStructureTagCandidate); + // structured tag primitive const vcl::PDFWriter::StructElement& rTagElement(rStructureTagCandidate.getStructureElement()); bool bTagUsed((vcl::PDFWriter::NonStructElement != rTagElement)); + ::std::optional<sal_Int32> oAnchorParent; + + if (!rStructureTagCandidate.isTaggedSdrObject()) + { + bTagUsed = false; + } if (mpPDFExtOutDevData && bTagUsed) { // foreground object: tag as regular structure element if (!rStructureTagCandidate.isBackground()) { - mpPDFExtOutDevData->BeginStructureElement(rTagElement); + if (rStructureTagCandidate.GetAnchorStructureElementKey() != nullptr) + { + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( + rStructureTagCandidate.GetAnchorStructureElementKey()); + oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + } + mpPDFExtOutDevData->WrapBeginStructureElement(rTagElement); + switch (rTagElement) + { + case vcl::PDFWriter::H1: + case vcl::PDFWriter::H2: + case vcl::PDFWriter::H3: + case vcl::PDFWriter::H4: + case vcl::PDFWriter::H5: + case vcl::PDFWriter::H6: + case vcl::PDFWriter::Paragraph: + case vcl::PDFWriter::Heading: + case vcl::PDFWriter::Caption: + case vcl::PDFWriter::BlockQuote: + case vcl::PDFWriter::Table: + case vcl::PDFWriter::TableRow: + case vcl::PDFWriter::Formula: + case vcl::PDFWriter::Figure: + case vcl::PDFWriter::Annot: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, + vcl::PDFWriter::Block); + break; + case vcl::PDFWriter::TableData: + case vcl::PDFWriter::TableHeader: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, + vcl::PDFWriter::Inline); + break; + default: + break; + } + switch (rTagElement) + { + case vcl::PDFWriter::Table: + case vcl::PDFWriter::Formula: + case vcl::PDFWriter::Figure: + case vcl::PDFWriter::Annot: + { + auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect( + basegfx::fround<tools::Long>(range.getMinX()), + basegfx::fround<tools::Long>(range.getMinY()), + basegfx::fround<tools::Long>(range.getMaxX()), + basegfx::fround<tools::Long>(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + break; + } + default: + break; + } + if (rTagElement == vcl::PDFWriter::Annot) + { + mpPDFExtOutDevData->SetStructureAnnotIds(rStructureTagCandidate.GetAnnotIds()); + } + if (rTagElement == vcl::PDFWriter::TableHeader) + { + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, + vcl::PDFWriter::Column); + } } // background object else { // background image: tag as artifact if (rStructureTagCandidate.isImage()) - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::NonStructElement); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement); // any other background object: do not tag else - bTagUsed = false; + assert(false); } } @@ -2362,104 +2640,11 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D( { // write end tag mpPDFExtOutDevData->EndStructureElement(); - } -} - -VclPtr<VirtualDevice> -VclMetafileProcessor2D::CreateBufferDevice(const basegfx::B2DRange& rCandidateRange, - geometry::ViewInformation2D& rViewInfo, - tools::Rectangle& rRectLogic, Size& rSizePixel) const -{ - constexpr double fMaxSquarePixels = 500000; - basegfx::B2DRange aViewRange(rCandidateRange); - aViewRange.transform(maCurrentTransformation); - rRectLogic = tools::Rectangle(static_cast<tools::Long>(std::floor(aViewRange.getMinX())), - static_cast<tools::Long>(std::floor(aViewRange.getMinY())), - static_cast<tools::Long>(std::ceil(aViewRange.getMaxX())), - static_cast<tools::Long>(std::ceil(aViewRange.getMaxY()))); - const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(rRectLogic)); - rSizePixel = aRectPixel.GetSize(); - const double fViewVisibleArea(rSizePixel.getWidth() * rSizePixel.getHeight()); - double fReduceFactor(1.0); - - if (fViewVisibleArea > fMaxSquarePixels) - { - // reduce render size - fReduceFactor = sqrt(fMaxSquarePixels / fViewVisibleArea); - rSizePixel = Size(basegfx::fround(rSizePixel.getWidth() * fReduceFactor), - basegfx::fround(rSizePixel.getHeight() * fReduceFactor)); - } - - VclPtrInstance<VirtualDevice> pBufferDevice(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT); - if (pBufferDevice->SetOutputSizePixel(rSizePixel)) - { - // create and set MapModes for target devices - MapMode aNewMapMode(mpOutputDevice->GetMapMode()); - aNewMapMode.SetOrigin(Point(-rRectLogic.Left(), -rRectLogic.Top())); - pBufferDevice->SetMapMode(aNewMapMode); - - // prepare view transformation for target renderers - // ATTENTION! Need to apply another scaling because of the potential DPI differences - // between Printer and VDev (mpOutputDevice and pBufferDevice here). - // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used. - basegfx::B2DHomMatrix aViewTransform(pBufferDevice->GetViewTransformation()); - const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const Size aDPINew(pBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); - const double fDPIXChange(static_cast<double>(aDPIOld.getWidth()) - / static_cast<double>(aDPINew.getWidth())); - const double fDPIYChange(static_cast<double>(aDPIOld.getHeight()) - / static_cast<double>(aDPINew.getHeight())); - - if (!basegfx::fTools::equal(fDPIXChange, 1.0) || !basegfx::fTools::equal(fDPIYChange, 1.0)) - { - aViewTransform.scale(fDPIXChange, fDPIYChange); - } - - // also take scaling from Size reduction into account - if (!basegfx::fTools::equal(fReduceFactor, 1.0)) + if (oAnchorParent) { - aViewTransform.scale(fReduceFactor, fReduceFactor); + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); } - - // create view information and pixel renderer. Reuse known ViewInformation - // except new transformation and range - rViewInfo = geometry::ViewInformation2D( - getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange, - getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime()); - } - else - pBufferDevice.disposeAndClear(); - -#if HAVE_P1155R3 - return pBufferDevice; -#else - return std::move(pBufferDevice); -#endif -} - -void VclMetafileProcessor2D::processPrimitive2DOnPixelProcessor( - const primitive2d::BasePrimitive2D& rCandidate) -{ - basegfx::B2DRange aViewRange(rCandidate.getB2DRange(getViewInformation2D())); - geometry::ViewInformation2D aViewInfo; - tools::Rectangle aRectLogic; - Size aSizePixel; - auto pBufferDevice(CreateBufferDevice(aViewRange, aViewInfo, aRectLogic, aSizePixel)); - if (pBufferDevice) - { - VclPixelProcessor2D aBufferProcessor(aViewInfo, *pBufferDevice, maBColorModifierStack); - - // draw content using pixel renderer - primitive2d::Primitive2DReference aRef( - &const_cast<primitive2d::BasePrimitive2D&>(rCandidate)); - aBufferProcessor.process({ aRef }); - const BitmapEx aBmContent(pBufferDevice->GetBitmapEx(Point(), aSizePixel)); - mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), aBmContent); - - // aBufferProcessor dtor pops state off pBufferDevice pushed on by its ctor, let - // pBufferDevice live past aBufferProcessor scope to avoid warnings } - pBufferDevice.disposeAndClear(); } } // end of namespace diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx index 06fd61e18309..c315281ebf61 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx @@ -25,6 +25,7 @@ #include <com/sun/star/i18n/XBreakIterator.hpp> #include <basegfx/polygon/b2dpolypolygon.hxx> #include <vcl/pdfextoutdevdata.hxx> // vcl::PDFExtOutDevData support +#include <vcl/lazydelete.hxx> class GDIMetaFile; namespace tools @@ -63,6 +64,7 @@ class PolyPolygonColorPrimitive2D; class MaskPrimitive2D; class UnifiedTransparencePrimitive2D; class TransparencePrimitive2D; +class ObjectInfoPrimitive2D; class StructureTagPrimitive2D; } @@ -142,12 +144,10 @@ private: const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate); void processTransparencePrimitive2D( const primitive2d::TransparencePrimitive2D& rTransparenceCandidate); + void + processObjectInfoPrimitive2D(const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D); void processStructureTagPrimitive2D( const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate); - void processPrimitive2DOnPixelProcessor(const primitive2d::BasePrimitive2D& rCandidate); - VclPtr<VirtualDevice> CreateBufferDevice(const basegfx::B2DRange& rCandidateRange, - geometry::ViewInformation2D& rViewInfo, - tools::Rectangle& rRectLogic, Size& rSizePixel) const; /// Convert the fWidth to the same space as its coordinates. double getTransformedLineWidth(double fWidth) const; @@ -176,7 +176,7 @@ private: constructed VclMetafileProcessor2D. It's still incarnated on demand, but exists for OOo runtime now by purpose. */ - static css::uno::Reference<css::i18n::XBreakIterator> mxBreakIterator; + static vcl::DeleteOnDeinit<css::uno::Reference<css::i18n::XBreakIterator>> mxBreakIterator; /* vcl::PDFExtOutDevData support For the first step, some extra actions at vcl::PDFExtOutDevData need to @@ -195,6 +195,8 @@ private: std::stack<vcl::PDFWriter::StructElement> maListElements; + primitive2d::StructureTagPrimitive2D const* mpCurrentStructureTag = nullptr; + protected: /* the local processor for BasePrimitive2D-Implementation based primitives, called from the common process()-implementation diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index 139bb0ba080a..754b8cef2592 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -23,21 +23,21 @@ #include <comphelper/lok.hxx> #include <sal/log.hxx> -#include <vcl/BitmapBasicMorphologyFilter.hxx> -#include <vcl/BitmapFilterStackBlur.hxx> #include <vcl/outdev.hxx> #include <vcl/hatch.hxx> #include <vcl/canvastools.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/gradienttools.hxx> #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/Tools.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> @@ -57,35 +57,28 @@ #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> #include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> #include <drawinglayer/primitive2d/epsprimitive2d.hxx> -#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> #include <drawinglayer/primitive2d/shadowprimitive2d.hxx> #include <drawinglayer/primitive2d/patternfillprimitive2d.hxx> #include <com/sun/star/awt/XWindow2.hpp> #include <com/sun/star/awt/XControl.hpp> -#include <svtools/optionsdrawinglayer.hxx> +#include <officecfg/Office/Common.hxx> #include <vcl/gradient.hxx> using namespace com::sun::star; namespace drawinglayer::processor2d { -struct VclPixelProcessor2D::Impl -{ - AntialiasingFlags m_nOrigAntiAliasing; - - explicit Impl(OutputDevice const& rOutDev) - : m_nOrigAntiAliasing(rOutDev.GetAntialiasing()) - { - } -}; - VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev, const basegfx::BColorModifierStack& rInitStack) : VclProcessor2D(rViewInformation, rOutDev, rInitStack) - , m_pImpl(new Impl(rOutDev)) + , m_nOrigAntiAliasing(rOutDev.GetAntialiasing()) + , m_bRenderSimpleTextDirect( + officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get()) + , m_bRenderDecoratedTextDirect( + officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get()) { // prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels maCurrentTransformation = rViewInformation.getObjectToViewTransformation(); @@ -95,13 +88,13 @@ VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rVie mpOutputDevice->SetMapMode(); // react on AntiAliasing settings - if (SvtOptionsDrawinglayer::IsAntiAliasing()) + if (rViewInformation.getUseAntiAliasing()) { - mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing | AntialiasingFlags::Enable); + mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing | AntialiasingFlags::Enable); } else { - mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing & ~AntialiasingFlags::Enable); + mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing & ~AntialiasingFlags::Enable); } } @@ -111,7 +104,7 @@ VclPixelProcessor2D::~VclPixelProcessor2D() mpOutputDevice->Pop(); // restore AntiAliasing - mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing); + mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing); } void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect( @@ -126,7 +119,11 @@ void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect( const basegfx::BColor aPolygonColor( maBColorModifierStack.getModifiedColor(rSource.getBColor())); - mpOutputDevice->SetFillColor(Color(aPolygonColor)); + if (comphelper::LibreOfficeKit::isActive() && aPolygonColor.isAutomatic()) + mpOutputDevice->SetFillColor(getViewInformation2D().getAutoColor()); + else + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(), fTransparency); @@ -196,32 +193,6 @@ bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect( rSource.getLineAttribute().getMiterMinimumAngle()); } -namespace -{ -GradientStyle convertGradientStyle(drawinglayer::attribute::GradientStyle eGradientStyle) -{ - switch (eGradientStyle) - { - case drawinglayer::attribute::GradientStyle::Axial: - return GradientStyle::Axial; - case drawinglayer::attribute::GradientStyle::Radial: - return GradientStyle::Radial; - case drawinglayer::attribute::GradientStyle::Elliptical: - return GradientStyle::Elliptical; - case drawinglayer::attribute::GradientStyle::Square: - return GradientStyle::Square; - case drawinglayer::attribute::GradientStyle::Rect: - return GradientStyle::Rect; - case drawinglayer::attribute::GradientStyle::Linear: - return GradientStyle::Linear; - default: - assert(false); - return GradientStyle::Linear; - } -} - -} // end anonymous namespace - void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) { switch (rCandidate.getPrimitive2DID()) @@ -367,17 +338,6 @@ void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate)); break; } - case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D: - { - // #i97628# - // This primitive means that the content is derived from an active text edit, - // not from model data itself. Some renderers need to suppress this content, e.g. - // the pixel renderer used for displaying the edit view (like this one). It's - // not to be suppressed by the MetaFile renderers, so that the edited text is - // part of the MetaFile, e.g. needed for presentation previews. - // Action: Ignore here, do nothing. - break; - } case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: { processInvertPrimitive2D(rCandidate); @@ -406,24 +366,6 @@ void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate)); break; } - case PRIMITIVE2D_ID_GLOWPRIMITIVE2D: - { - processGlowPrimitive2D( - static_cast<const drawinglayer::primitive2d::GlowPrimitive2D&>(rCandidate)); - break; - } - case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: - { - processSoftEdgePrimitive2D( - static_cast<const drawinglayer::primitive2d::SoftEdgePrimitive2D&>(rCandidate)); - break; - } - case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: - { - processShadowPrimitive2D( - static_cast<const drawinglayer::primitive2d::ShadowPrimitive2D&>(rCandidate)); - break; - } case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D: { processFillGradientPrimitive2D( @@ -465,7 +407,7 @@ void VclPixelProcessor2D::processTextSimplePortionPrimitive2D( const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); adaptTextToFillDrawMode(); - if (SvtOptionsDrawinglayer::IsRenderSimpleTextDirect()) + if (SAL_LIKELY(m_bRenderSimpleTextDirect)) { RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); } @@ -485,7 +427,7 @@ void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D( const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); adaptTextToFillDrawMode(); - if (SvtOptionsDrawinglayer::IsRenderDecoratedTextDirect()) + if (SAL_LIKELY(m_bRenderDecoratedTextDirect)) { RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); } @@ -537,15 +479,31 @@ void VclPixelProcessor2D::processBitmapPrimitive2D( void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D( const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate) { - // direct draw of gradient - const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient()); - basegfx::BColor aStartColor(maBColorModifierStack.getModifiedColor(rGradient.getStartColor())); - basegfx::BColor aEndColor(maBColorModifierStack.getModifiedColor(rGradient.getEndColor())); basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + // no geometry, no need to render, done if (!aLocalPolyPolygon.count()) return; + // *try* direct draw (AKA using old VCL stuff) to render gradient + const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient()); + + // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL + // so use decomposition + // NOTE: There may be even more reasons to detect, e.g. a ViewTransformation + // that uses shear/rotate/mirror (what VCL cannot handle at all), see + // other checks already in processFillGradientPrimitive2D + if (rGradient.cannotBeHandledByVCL()) + { + process(rPolygonCandidate); + return; + } + + basegfx::BColor aStartColor( + maBColorModifierStack.getModifiedColor(rGradient.getColorStops().front().getStopColor())); + basegfx::BColor aEndColor( + maBColorModifierStack.getModifiedColor(rGradient.getColorStops().back().getStopColor())); + if (aStartColor == aEndColor) { // no gradient at all, draw as polygon in AA and non-AA case @@ -553,12 +511,11 @@ void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D( mpOutputDevice->SetLineColor(); mpOutputDevice->SetFillColor(Color(aStartColor)); mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + return; } - else - { - // use the primitive decomposition of the metafile - process(rPolygonCandidate); - } + + // use the primitive decomposition + process(rPolygonCandidate); } void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D( @@ -573,7 +530,7 @@ void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D( // when AA is on and this filled polygons are the result of stroked line geometry, // draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons // Caution: This is needed in both cases (!) - if (!(mnPolygonStrokePrimitive2D && SvtOptionsDrawinglayer::IsAntiAliasing() + if (!(mnPolygonStrokePrimitive2D && getViewInformation2D().getUseAntiAliasing() && (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::Enable))) return; @@ -619,9 +576,7 @@ void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D( if (1 == rContent.size()) { - const primitive2d::Primitive2DReference xReference(rContent[0]); - const primitive2d::BasePrimitive2D* pBasePrimitive - = static_cast<const primitive2d::BasePrimitive2D*>(xReference.get()); + const primitive2d::BasePrimitive2D* pBasePrimitive = rContent.front().get(); switch (pBasePrimitive->getPrimitive2DID()) { @@ -790,7 +745,7 @@ void VclPixelProcessor2D::processPolygonStrokePrimitive2D( void VclPixelProcessor2D::processFillHatchPrimitive2D( const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive) { - if (SvtOptionsDrawinglayer::IsAntiAliasing()) + if (getViewInformation2D().getUseAntiAliasing()) { // if AA is used (or ignore smoothing is on), there is no need to smooth // hatch painting, use decomposition @@ -957,8 +912,7 @@ void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimit void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) { // #i98289# - const bool bForceLineSnap(SvtOptionsDrawinglayer::IsAntiAliasing() - && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete()); + const bool bForceLineSnap(getViewInformation2D().getPixelSnapHairline()); const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing()); if (bForceLineSnap) @@ -974,247 +928,116 @@ void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrim } } -namespace +void VclPixelProcessor2D::processFillGradientPrimitive2D( + const primitive2d::FillGradientPrimitive2D& rPrimitive) { -/* Returns 8-bit alpha mask created from passed mask. + const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient(); + bool useDecompose(false); - Negative fErodeDilateRadius values mean erode, positive - dilate. - nTransparency defines minimal transparency level. -*/ -AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double fErodeDilateRadius, - double fBlurRadius, sal_uInt8 nTransparency) -{ - // Only completely white pixels on the initial mask must be considered for transparency. Any - // other color must be treated as black. This creates 1-bit B&W bitmap. - BitmapEx mask(rMask.CreateMask(COL_WHITE)); - - // Scaling down increases performance without noticeable quality loss. Additionally, - // current blur implementation can only handle blur radius between 2 and 254. - Size aSize = mask.GetSizePixel(); - double fScale = 1.0; - while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000) + // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL + // so use decomposition + if (rFillGradient.cannotBeHandledByVCL()) { - fScale /= 2; - fBlurRadius /= 2; - fErodeDilateRadius /= 2; - aSize.setHeight(aSize.Height() / 2); - aSize.setWidth(aSize.Width() / 2); + useDecompose = true; } - // BmpScaleFlag::Fast is important for following color replacement - mask.Scale(fScale, fScale, BmpScaleFlag::Fast); - - if (fErodeDilateRadius > 0) - BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius)); - else if (fErodeDilateRadius < 0) - BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 0xFF)); - - if (nTransparency) + // tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions, + // what should be clear due to being developed to extend/replace them in + // capabilities & precision. + // It is e.g. not capable to correctly paint if the OutputRange is not completely + // inside the DefinitionRange, thus forcing to paint gradient parts *outside* the + // DefinitionRange. + // This happens for Writer with Frames anchored in Frames (and was broken due to + // falling back to VCL Gradient paint here), and for the new SlideBackgroundFill + // Fill mode for objects using it that reach outside the page (which is the + // DefinitionRange in that case). + // I see no real reason to fallback here to OutputDevice::DrawGradient and VCL + // gradient paint at all (system-dependent renderers wouldn't in the future), but + // will for convenience only add that needed additional correcting case + if (!useDecompose && !rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange())) { - const Color aTransparency(nTransparency, nTransparency, nTransparency); - mask.Replace(COL_BLACK, aTransparency); + useDecompose = true; } - // We need 8-bit grey mask for blurring - mask.Convert(BmpConversion::N8BitGreys); - - // calculate blurry effect - BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius)); - - mask.Scale(rMask.GetSizePixel()); - - return AlphaMask(mask.GetBitmap()); -} -} - -void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate) -{ - basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); - aRange.transform(maCurrentTransformation); - basegfx::B2DVector aGlowRadiusVector(rCandidate.getGlowRadius(), 0); - // Calculate the pixel size of glow radius in current transformation - aGlowRadiusVector *= maCurrentTransformation; - // Glow radius is the size of the halo from each side of the object. The halo is the - // border of glow color that fades from glow transparency level to fully transparent - // When blurring a sharp boundary (our case), it gets 50% of original intensity, and - // fades to both sides by the blur radius; thus blur radius is half of glow radius. - const double fBlurRadius = aGlowRadiusVector.getLength() / 2; - // Consider glow transparency (initial transparency near the object edge) - const sal_uInt8 nAlpha = rCandidate.getGlowColor().GetAlpha(); - - impBufferDevice aBufferDevice(*mpOutputDevice, aRange); - if (aBufferDevice.isVisible()) + // tdf#151081 need to use regular primitive decomposition when the gradient + // is transformed in any other way then just translate & scale + if (!useDecompose) { - // remember last OutDev and set to content - OutputDevice* pLastOutputDevice = mpOutputDevice; - mpOutputDevice = &aBufferDevice.getContent(); - // We don't need antialiased mask here, which would only make effect thicker - const auto aPrevAA = mpOutputDevice->GetAntialiasing(); - mpOutputDevice->SetAntialiasing(AntialiasingFlags::NONE); - process(rCandidate); + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; - // Limit the bitmap size to the visible area. - basegfx::B2DRange viewRange(getViewInformation2D().getDiscreteViewport()); - basegfx::B2DRange bitmapRange(aRange); - bitmapRange.intersect(viewRange); - if (!bitmapRange.isEmpty()) - { - const tools::Rectangle aRect( - static_cast<tools::Long>(std::floor(bitmapRange.getMinX())), - static_cast<tools::Long>(std::floor(bitmapRange.getMinY())), - static_cast<tools::Long>(std::ceil(bitmapRange.getMaxX())), - static_cast<tools::Long>(std::ceil(bitmapRange.getMaxY()))); - BitmapEx bmpEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize()); - mpOutputDevice->SetAntialiasing(aPrevAA); - - AlphaMask mask - = ProcessAndBlurAlphaMask(bmpEx.GetAlpha(), fBlurRadius, fBlurRadius, 255 - nAlpha); - - // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask - const basegfx::BColor aGlowColor( - maBColorModifierStack.getModifiedColor(rCandidate.getGlowColor().getBColor())); - Bitmap bmp = bmpEx.GetBitmap(); - bmp.Erase(Color(aGlowColor)); - BitmapEx result(bmp, mask); - - // back to old OutDev - mpOutputDevice = pLastOutputDevice; - mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result); - } - else + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + // detect if transformation is rotated, sheared or mirrored in X and/or Y + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) + || aScale.getX() < 0.0 || aScale.getY() < 0.0) { - mpOutputDevice = pLastOutputDevice; + useDecompose = true; } } - else - SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible"); -} -void VclPixelProcessor2D::processSoftEdgePrimitive2D( - const primitive2d::SoftEdgePrimitive2D& rCandidate) -{ - // TODO: don't limit the object at view range. This is needed to not blur objects at window - // borders, where they don't end. Ideally, process the full object once at maximal reasonable - // resolution, and store the resulting alpha mask in primitive's cache; then reuse it later, - // applying the transform. - basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); - aRange.transform(maCurrentTransformation); - basegfx::B2DVector aRadiusVector(rCandidate.getRadius(), 0); - // Calculate the pixel size of soft edge radius in current transformation - aRadiusVector *= maCurrentTransformation; - // Blur radius is equal to soft edge radius - const double fBlurRadius = aRadiusVector.getLength(); - - impBufferDevice aBufferDevice(*mpOutputDevice, aRange); - if (aBufferDevice.isVisible()) + if (useDecompose) { - // remember last OutDev and set to content - OutputDevice* pLastOutputDevice = mpOutputDevice; - mpOutputDevice = &aBufferDevice.getContent(); - // Since the effect converts all children to bitmap, we can't disable antialiasing here, - // because it would result in poor quality in areas not affected by the effect - process(rCandidate); + // default is to use the direct render below. For security, + // keep the (simple) fallback to decompose in place here + static bool bTryDirectRender(true); - // Limit the bitmap size to the visible area. - basegfx::B2DRange viewRange(getViewInformation2D().getDiscreteViewport()); - basegfx::B2DRange bitmapRange(aRange); - bitmapRange.intersect(viewRange); - if (!bitmapRange.isEmpty()) + if (bTryDirectRender) { - const tools::Rectangle aRect( - static_cast<tools::Long>(std::floor(bitmapRange.getMinX())), - static_cast<tools::Long>(std::floor(bitmapRange.getMinY())), - static_cast<tools::Long>(std::ceil(bitmapRange.getMaxX())), - static_cast<tools::Long>(std::ceil(bitmapRange.getMaxY()))); - BitmapEx bitmap = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize()); - - AlphaMask aMask = bitmap.GetAlpha(); - AlphaMask blurMask = ProcessAndBlurAlphaMask(aMask, -fBlurRadius, fBlurRadius, 0); - - aMask.BlendWith(blurMask); - - // The end result is the original bitmap with blurred 8-bit alpha mask - BitmapEx result(bitmap.GetBitmap(), aMask); - - // back to old OutDev - mpOutputDevice = pLastOutputDevice; - mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result); + // MCGR: Avoid one level of primitive creation, use FillGradientPrimitive2D + // tooling to directly create needed geometry & color for getting better + // performance (partially compensate for potentially more expensive multi + // color gradients). + // To handle a primitive that needs paint, either use decompose, or - when you + // do not want that for any reason, e.g. extra primitives created - implement + // a direct handling in your primitive renderer. This is always possible + // since primitives by definition are self-contained what means they have all + // needed data locally available to do so. + // The question is the complexity to invest - the implemented decompose + // is always a good hint of what is needed to do this. In this case I decided + // to add some tooling methods to the primitive itself to support this. These + // are used in decompose and can be used - as here now - for direct handling, + // too. This is always a possibility in primitive handling - you can, but do not + // have to. + mpOutputDevice->SetFillColor( + Color(maBColorModifierStack.getModifiedColor(rPrimitive.getOuterColor()))); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawTransparent( + maCurrentTransformation, + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(rPrimitive.getOutputRange())), + 0.0); + + // paint solid fill steps by providing callback as lambda + auto aCallback([&rPrimitive, this](const basegfx::B2DHomMatrix& rMatrix, + const basegfx::BColor& rColor) { + // create part polygon + basegfx::B2DPolygon aNewPoly(rPrimitive.getUnitPolygon()); + aNewPoly.transform(rMatrix); + + // create solid fill + mpOutputDevice->SetFillColor(Color(maBColorModifierStack.getModifiedColor(rColor))); + mpOutputDevice->DrawTransparent(maCurrentTransformation, + basegfx::B2DPolyPolygon(aNewPoly), 0.0); + }); + + // call value generator to trigger callbacks + rPrimitive.generateMatricesAndColors(aCallback); } else { - mpOutputDevice = pLastOutputDevice; + // use the decompose + process(rPrimitive); } - } - else - SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible"); -} - -void VclPixelProcessor2D::processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate) -{ - if (rCandidate.getShadowBlur() == 0) - { - process(rCandidate); - return; - } - - basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); - aRange.transform(maCurrentTransformation); - basegfx::B2DVector aBlurRadiusVector(rCandidate.getShadowBlur(), 0); - aBlurRadiusVector *= maCurrentTransformation; - const double fBlurRadius = aBlurRadiusVector.getLength(); - - impBufferDevice aBufferDevice(*mpOutputDevice, aRange); - if (aBufferDevice.isVisible() && !aRange.isEmpty()) - { - OutputDevice* pLastOutputDevice = mpOutputDevice; - mpOutputDevice = &aBufferDevice.getContent(); - - process(rCandidate); - const tools::Rectangle aRect(static_cast<tools::Long>(std::floor(aRange.getMinX())), - static_cast<tools::Long>(std::floor(aRange.getMinY())), - static_cast<tools::Long>(std::ceil(aRange.getMaxX())), - static_cast<tools::Long>(std::ceil(aRange.getMaxY()))); - - BitmapEx bitmapEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize()); - - AlphaMask mask = ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0, fBlurRadius, 0); - - const basegfx::BColor aShadowColor( - maBColorModifierStack.getModifiedColor(rCandidate.getShadowColor())); - - Bitmap bitmap = bitmapEx.GetBitmap(); - bitmap.Erase(Color(aShadowColor)); - BitmapEx result(bitmap, mask); - - mpOutputDevice = pLastOutputDevice; - mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result); - } - else - SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible"); -} - -void VclPixelProcessor2D::processFillGradientPrimitive2D( - const primitive2d::FillGradientPrimitive2D& rPrimitive) -{ - const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient(); - - // VCL should be able to handle all styles, but for tdf#133477 the VCL result - // is different from processing the gradient manually by drawinglayer - // (and the Writer unittest for it fails). Keep using the drawinglayer code - // until somebody founds out what's wrong and fixes it. - if (rFillGradient.getStyle() != drawinglayer::attribute::GradientStyle::Linear - && rFillGradient.getStyle() != drawinglayer::attribute::GradientStyle::Axial - && rFillGradient.getStyle() != drawinglayer::attribute::GradientStyle::Radial) - { - process(rPrimitive); return; } - GradientStyle eGradientStyle = convertGradientStyle(rFillGradient.getStyle()); - - Gradient aGradient(eGradientStyle, Color(rFillGradient.getStartColor()), - Color(rFillGradient.getEndColor())); + // try to use vcl - since vcl uses the old gradient paint mechanisms this may + // create wrong geometries. If so, add another case above for useDecompose + Gradient aGradient(rFillGradient.getStyle(), + Color(rFillGradient.getColorStops().front().getStopColor()), + Color(rFillGradient.getColorStops().back().getStopColor())); aGradient.SetAngle(Degree10(static_cast<int>(basegfx::rad2deg<10>(rFillGradient.getAngle())))); aGradient.SetBorder(rFillGradient.getBorder() * 100); @@ -1263,11 +1086,13 @@ void VclPixelProcessor2D::processPatternFillPrimitive2D( tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange); // Unless smooth edges are needed, simply use clipping. - if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing()) + if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing()) { mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); - mpOutputDevice->DrawWallpaper(aMaskRect, Wallpaper(aTileImage)); + Wallpaper aWallpaper(aTileImage); + aWallpaper.SetColor(COL_TRANSPARENT); + mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper); mpOutputDevice->Pop(); return; } @@ -1290,7 +1115,11 @@ void VclPixelProcessor2D::processPatternFillPrimitive2D( mpOutputDevice->DrawRect(aMaskRect); } else - mpOutputDevice->DrawWallpaper(aMaskRect, Wallpaper(aTileImage)); + { + Wallpaper aWallpaper(aTileImage); + aWallpaper.SetColor(COL_TRANSPARENT); + mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper); + } // back to old OutDev mpOutputDevice = pLastOutputDevice; diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx index eaf212c8e5b1..185ed52ed897 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx @@ -39,9 +39,6 @@ class PolygonStrokePrimitive2D; class FillHatchPrimitive2D; class BackgroundColorPrimitive2D; class BorderLinePrimitive2D; -class GlowPrimitive2D; -class ShadowPrimitive2D; -class SoftEdgePrimitive2D; class FillGradientPrimitive2D; class PatternFillPrimitive2D; } @@ -56,8 +53,10 @@ namespace drawinglayer::processor2d */ class VclPixelProcessor2D final : public VclProcessor2D { - struct Impl; - std::unique_ptr<Impl> m_pImpl; + AntialiasingFlags m_nOrigAntiAliasing; + + bool m_bRenderSimpleTextDirect; + bool m_bRenderDecoratedTextDirect; /* the local processor for BasePrimitive2D-Implementation based primitives, called from the common process()-implementation @@ -98,9 +97,6 @@ class VclPixelProcessor2D final : public VclProcessor2D processBorderLinePrimitive2D(const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder); void processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate); void processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate); - void processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate); - void processSoftEdgePrimitive2D(const primitive2d::SoftEdgePrimitive2D& rCandidate); - void processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate); void processFillGradientPrimitive2D(const primitive2d::FillGradientPrimitive2D& rPrimitive); void processPatternFillPrimitive2D(const primitive2d::PatternFillPrimitive2D& rPrimitive); diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx b/drawinglayer/source/processor2d/vclprocessor2d.cxx index 5d3f68d29a4a..3cfec4af8b8d 100644 --- a/drawinglayer/source/processor2d/vclprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx @@ -23,9 +23,14 @@ #include "vclhelperbufferdevice.hxx" #include <cmath> #include <comphelper/string.hxx> +#include <comphelper/lok.hxx> #include <svtools/optionsdrawinglayer.hxx> #include <tools/debug.hxx> +#include <tools/fract.hxx> +#include <utility> +#include <vcl/glyphitemcache.hxx> #include <vcl/graph.hxx> +#include <vcl/kernarray.hxx> #include <vcl/outdev.hxx> #include <rtl/ustrbuf.hxx> #include <sal/log.hxx> @@ -38,9 +43,10 @@ #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> @@ -89,6 +95,29 @@ sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, } } +namespace +{ +/** helper to convert a MapMode to a transformation */ +basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode) +{ + basegfx::B2DHomMatrix aMapping; + const Fraction aNoScale(1, 1); + const Point& rOrigin(rMapMode.GetOrigin()); + + if (0 != rOrigin.X() || 0 != rOrigin.Y()) + { + aMapping.translate(rOrigin.X(), rOrigin.Y()); + } + + if (rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale) + { + aMapping.scale(double(rMapMode.GetScaleX()), double(rMapMode.GetScaleY())); + } + + return aMapping; +} +} + namespace drawinglayer::processor2d { // rendering support @@ -106,14 +135,14 @@ void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( basegfx::B2DVector aFontScaling, aTranslate; double fRotate, fShearX; aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX); + bool bPrimitiveAccepted(false); // tdf#95581: Assume tiny shears are rounding artefacts or whatever and can be ignored, // especially if the effect is less than a pixel. if (std::abs(aFontScaling.getY() * fShearX) < 1) { - if (basegfx::fTools::less(aFontScaling.getX(), 0.0) - && basegfx::fTools::less(aFontScaling.getY(), 0.0)) + if (aFontScaling.getX() < 0.0 && aFontScaling.getY() < 0.0) { // handle special case: If scale is negative in (x,y) (3rd quadrant), it can // be expressed as rotation by PI. Use this since the Font rendering will not @@ -122,25 +151,43 @@ void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( fRotate += M_PI; } - if (basegfx::fTools::more(aFontScaling.getX(), 0.0) - && basegfx::fTools::more(aFontScaling.getY(), 0.0)) + if (aFontScaling.getX() > 0.0 && aFontScaling.getY() > 0.0) { - // Get the VCL font (use FontHeight as FontWidth) - vcl::Font aFont(primitive2d::getVclFontFromFontAttribute( - rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(), - fRotate, rTextCandidate.getLocale())); + double fIgnoreRotate, fIgnoreShearX; + + basegfx::B2DVector aFontSize, aTextTranslate; + rTextCandidate.getTextTransform().decompose(aFontSize, aTextTranslate, fIgnoreRotate, + fIgnoreShearX); + + // tdf#153092 Ideally we don't have to scale the font and dxarray, but we might have + // to nevertheless if dealing with non integer sizes + const bool bScaleFont(aFontSize.getY() != std::round(aFontSize.getY()) + || comphelper::LibreOfficeKit::isActive()); + vcl::Font aFont; + + // Get the VCL font + if (!bScaleFont) + { + aFont = primitive2d::getVclFontFromFontAttribute( + rTextCandidate.getFontAttribute(), aFontSize.getX(), aFontSize.getY(), fRotate, + rTextCandidate.getLocale()); + } + else + { + aFont = primitive2d::getVclFontFromFontAttribute( + rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(), + fRotate, rTextCandidate.getLocale()); + } // Don't draw fonts without height - if (aFont.GetFontHeight() <= 0) + Size aResultFontSize = aFont.GetFontSize(); + if (aResultFontSize.Height() <= 0) return; // set FillColor Attribute const Color aFillColor(rTextCandidate.getTextFillColor()); - if (aFillColor != COL_TRANSPARENT) - { - aFont.SetFillColor(aFillColor); - aFont.SetTransparent(false); - } + aFont.SetTransparent(aFillColor.IsTransparent()); + aFont.SetFillColor(aFillColor); // handle additional font attributes const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr; @@ -247,27 +294,27 @@ void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( aFont.SetShadow(true); } - // create transformed integer DXArray in view coordinate system - std::vector<sal_Int32> aTransformedDXArray; + // create integer DXArray + KernArray aDXArray; if (!rTextCandidate.getDXArray().empty()) { - aTransformedDXArray.reserve(rTextCandidate.getDXArray().size()); - const basegfx::B2DVector aPixelVector(maCurrentTransformation - * basegfx::B2DVector(1.0, 0.0)); - const double fPixelVectorFactor(aPixelVector.getLength()); - - for (auto const& elem : rTextCandidate.getDXArray()) + double fPixelVectorFactor(1.0); + if (bScaleFont) { - aTransformedDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor)); + const basegfx::B2DVector aPixelVector(maCurrentTransformation + * basegfx::B2DVector(1.0, 0.0)); + fPixelVectorFactor = aPixelVector.getLength(); } + + aDXArray.reserve(rTextCandidate.getDXArray().size()); + for (auto const& elem : rTextCandidate.getDXArray()) + aDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor)); } // set parameters and paint text snippet const basegfx::BColor aRGBFontColor( maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor())); - const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0)); - const Point aStartPoint(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY())); const vcl::text::ComplexTextLayoutFlags nOldLayoutMode(mpOutputDevice->GetLayoutMode()); if (rTextCandidate.getFontAttribute().getRTL()) @@ -279,47 +326,126 @@ void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( mpOutputDevice->SetLayoutMode(nRTLLayoutMode); } - mpOutputDevice->SetFont(aFont); - mpOutputDevice->SetTextColor(Color(aRGBFontColor)); - OUString aText(rTextCandidate.getText()); sal_Int32 nPos = rTextCandidate.getTextPosition(); sal_Int32 nLen = rTextCandidate.getTextLength(); + // this contraption is used in editeng, with format paragraph used to + // set a tab with a tab-fill character if (rTextCandidate.isFilled()) { - basegfx::B2DVector aOldFontScaling, aOldTranslate; - double fOldRotate, fOldShearX; - rTextCandidate.getTextTransform().decompose(aOldFontScaling, aOldTranslate, - fOldRotate, fOldShearX); + tools::Long nWidthToFill = rTextCandidate.getWidthToFill(); - tools::Long nWidthToFill = static_cast<tools::Long>( - rTextCandidate.getWidthToFill() * aFontScaling.getX() / aOldFontScaling.getX()); - - tools::Long nWidth = mpOutputDevice->GetTextArray(rTextCandidate.getText(), - &aTransformedDXArray, 0, 1); - tools::Long nChars = 2; + tools::Long nWidth = basegfx::fround<tools::Long>( + mpOutputDevice->GetTextArray(rTextCandidate.getText(), &aDXArray, 0, 1)); + sal_Int32 nChars = 2; if (nWidth) nChars = nWidthToFill / nWidth; - OUStringBuffer aFilled; + OUStringBuffer aFilled(nChars); comphelper::string::padToLength(aFilled, nChars, aText[0]); aText = aFilled.makeStringAndClear(); nPos = 0; nLen = nChars; - if (!aTransformedDXArray.empty()) + if (!aDXArray.empty()) { - sal_Int32 nDX = aTransformedDXArray[0]; - aTransformedDXArray.resize(nLen); + sal_Int32 nDX = aDXArray[0]; + aDXArray.resize(nLen); for (sal_Int32 i = 1; i < nLen; ++i) - aTransformedDXArray[i] = aTransformedDXArray[i - 1] + nDX; + aDXArray.set(i, aDXArray[i - 1] + nDX); } } - if (!aTransformedDXArray.empty()) + Point aStartPoint; + bool bChangeMapMode(false); + if (!bScaleFont) { - mpOutputDevice->DrawTextArray(aStartPoint, aText, aTransformedDXArray, nPos, nLen); + basegfx::B2DHomMatrix aCombinedTransform( + getTransformFromMapMode(mpOutputDevice->GetMapMode()) + * maCurrentTransformation); + + basegfx::B2DVector aCurrentScaling, aCurrentTranslate; + double fCurrentRotate; + aCombinedTransform.decompose(aCurrentScaling, aCurrentTranslate, fCurrentRotate, + fIgnoreShearX); + + const Point aOrigin( + basegfx::fround<tools::Long>(aCurrentTranslate.getX() / aCurrentScaling.getX()), + basegfx::fround<tools::Long>(aCurrentTranslate.getY() + / aCurrentScaling.getY())); + + Fraction aScaleX(aCurrentScaling.getX()); + if (!aScaleX.IsValid()) + { + SAL_WARN("drawinglayer", "invalid X Scale"); + return; + } + + Fraction aScaleY(aCurrentScaling.getY()); + if (!aScaleY.IsValid()) + { + SAL_WARN("drawinglayer", "invalid Y Scale"); + return; + } + + MapMode aMapMode(mpOutputDevice->GetMapMode().GetMapUnit(), aOrigin, aScaleX, + aScaleY); + + if (fCurrentRotate) + aTextTranslate *= basegfx::utils::createRotateB2DHomMatrix(fCurrentRotate); + aStartPoint = Point(basegfx::fround<tools::Long>(aTextTranslate.getX()), + basegfx::fround<tools::Long>(aTextTranslate.getY())); + + bChangeMapMode = aMapMode != mpOutputDevice->GetMapMode(); + if (bChangeMapMode) + { + mpOutputDevice->Push(vcl::PushFlags::MAPMODE); + mpOutputDevice->SetRelativeMapMode(aMapMode); + } + } + else + { + const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0)); + double aPointX = aPoint.getX(), aPointY = aPoint.getY(); + + // aFont has an integer size; we must scale a bit for precision + double nFontScalingFixY = aFontScaling.getY() / aResultFontSize.Height(); + double nFontScalingFixX = aFontScaling.getX() + / (aResultFontSize.Width() ? aResultFontSize.Width() + : aResultFontSize.Height()); + + if (!rtl_math_approxEqual(nFontScalingFixY, 1.0) + || !rtl_math_approxEqual(nFontScalingFixX, 1.0)) + { + MapMode aMapMode = mpOutputDevice->GetMapMode(); + aMapMode.SetScaleX(aMapMode.GetScaleX() * nFontScalingFixX); + aMapMode.SetScaleY(aMapMode.GetScaleY() * nFontScalingFixY); + + mpOutputDevice->Push(vcl::PushFlags::MAPMODE); + mpOutputDevice->SetRelativeMapMode(aMapMode); + bChangeMapMode = true; + + aPointX /= nFontScalingFixX; + aPointY /= nFontScalingFixY; + } + + aStartPoint = Point(basegfx::fround<tools::Long>(aPointX), + basegfx::fround<tools::Long>(aPointY)); + } + + // tdf#152990 set the font after the MapMode is (potentially) set so canvas uses the desired + // font size + mpOutputDevice->SetFont(aFont); + mpOutputDevice->SetTextColor(Color(aRGBFontColor)); + + if (!aDXArray.empty()) + { + const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs( + mpOutputDevice, aText, nPos, nLen); + mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray, + rTextCandidate.getKashidaArray(), nPos, nLen, + SalLayoutFlags::NONE, pGlyphs); } else { @@ -331,6 +457,9 @@ void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( mpOutputDevice->SetLayoutMode(nOldLayoutMode); } + if (bChangeMapMode) + mpOutputDevice->Pop(); + bPrimitiveAccepted = true; } } @@ -354,8 +483,7 @@ void VclProcessor2D::RenderPolygonHairlinePrimitive2D( basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); aLocalPolygon.transform(maCurrentTransformation); - if (bPixelBased && SvtOptionsDrawinglayer::IsAntiAliasing() - && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete()) + if (bPixelBased && getViewInformation2D().getPixelSnapHairline()) { // #i98289# // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete @@ -371,7 +499,7 @@ void VclProcessor2D::RenderPolygonHairlinePrimitive2D( // direct draw of transformed BitmapEx primitive void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate) { - BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapCandidate.getXBitmap())); + BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation * rBitmapCandidate.getTransform()); @@ -405,243 +533,224 @@ void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2 void VclProcessor2D::RenderFillGraphicPrimitive2D( const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate) { + bool bPrimitiveAccepted = RenderFillGraphicPrimitive2DImpl(rFillBitmapCandidate); + + if (!bPrimitiveAccepted) + { + // do not accept, use decomposition + process(rFillBitmapCandidate); + } +} + +bool VclProcessor2D::RenderFillGraphicPrimitive2DImpl( + const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate) +{ const attribute::FillGraphicAttribute& rFillGraphicAttribute( rFillBitmapCandidate.getFillGraphic()); - bool bPrimitiveAccepted(false); // #121194# when tiling is used and content is bitmap-based, do direct tiling in the // renderer on pixel base to ensure tight fitting. Do not do this when // the fill is rotated or sheared. - if (rFillGraphicAttribute.getTiling()) - { - // content is bitmap(ex) - // - // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use - // the primitive representation of the vector data directly. - // - // when graphic is animated, force decomposition to use the correct graphic, else - // fill style will not be animated - if (GraphicType::Bitmap == rFillGraphicAttribute.getGraphic().GetType() - && !rFillGraphicAttribute.getGraphic().getVectorGraphicData() - && !rFillGraphicAttribute.getGraphic().IsAnimated()) - { - // decompose matrix to check for shear, rotate and mirroring - basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation - * rFillBitmapCandidate.getTransformation()); - basegfx::B2DVector aScale, aTranslate; - double fRotate, fShearX; - aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX); - - // when nopt rotated/sheared - if (basegfx::fTools::equalZero(fRotate) && basegfx::fTools::equalZero(fShearX)) - { - // no shear or rotate, draw direct in pixel coordinates - bPrimitiveAccepted = true; + if (!rFillGraphicAttribute.getTiling()) + return false; + + // content is bitmap(ex) + // + // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use + // the primitive representation of the vector data directly. + // + // when graphic is animated, force decomposition to use the correct graphic, else + // fill style will not be animated + if (GraphicType::Bitmap != rFillGraphicAttribute.getGraphic().GetType() + || rFillGraphicAttribute.getGraphic().getVectorGraphicData() + || rFillGraphicAttribute.getGraphic().IsAnimated()) + return false; + + // decompose matrix to check for shear, rotate and mirroring + basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rFillBitmapCandidate.getTransformation()); + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX); - // transform object range to device coordinates (pixels). Use - // the device transformation for better accuracy - basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale); - aObjectRange.transform(mpOutputDevice->GetViewTransformation()); + // when nopt rotated/sheared + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)) + return false; - // extract discrete size of object - const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth())); - const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight())); + // no shear or rotate, draw direct in pixel coordinates - // only do something when object has a size in discrete units - if (nOWidth > 0 && nOHeight > 0) - { - // transform graphic range to device coordinates (pixels). Use - // the device transformation for better accuracy - basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange()); - aGraphicRange.transform(mpOutputDevice->GetViewTransformation() - * aLocalTransform); - - // extract discrete size of graphic - // caution: when getting to zero, nothing would be painted; thus, do not allow this - const sal_Int32 nBWidth( - std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth()))); - const sal_Int32 nBHeight( - std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight()))); - - // only do something when bitmap fill has a size in discrete units - if (nBWidth > 0 && nBHeight > 0) - { - // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it - // in vcl many times, create a size-optimized version - const Size aNeededBitmapSizePixel(nBWidth, nBHeight); - BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx()); - const bool bPreScaled(nBWidth * nBHeight < (250 * 250)); - - // ... but only up to a maximum size, else it gets too expensive - if (bPreScaled) - { - // if color depth is below 24bit, expand before scaling for better quality. - // This is even needed for low colors, else the scale will produce - // a bitmap in gray or Black/White (!) - if (isPalettePixelFormat(aBitmapEx.getPixelFormat())) - { - aBitmapEx.Convert(BmpConversion::N24Bit); - } + // transform object range to device coordinates (pixels). Use + // the device transformation for better accuracy + basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale); + aObjectRange.transform(mpOutputDevice->GetViewTransformation()); - aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate); - } + // extract discrete size of object + const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth())); + const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight())); - bool bPainted(false); + // only do something when object has a size in discrete units + if (nOWidth <= 0 || nOHeight <= 0) + return true; - if (maBColorModifierStack.count()) - { - // when color modifier, apply to bitmap - aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + // transform graphic range to device coordinates (pixels). Use + // the device transformation for better accuracy + basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange()); + aGraphicRange.transform(mpOutputDevice->GetViewTransformation() * aLocalTransform); - // impModifyBitmapEx uses empty bitmap as sign to return that - // the content will be completely replaced to mono color, use shortcut - if (aBitmapEx.IsEmpty()) - { - // color gets completely replaced, get it - const basegfx::BColor aModifiedColor( - maBColorModifierStack.getModifiedColor(basegfx::BColor())); - basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); - aPolygon.transform(aLocalTransform); + // extract discrete size of graphic + // caution: when getting to zero, nothing would be painted; thus, do not allow this + const sal_Int32 nBWidth(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth()))); + const sal_Int32 nBHeight(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight()))); - mpOutputDevice->SetFillColor(Color(aModifiedColor)); - mpOutputDevice->SetLineColor(); - mpOutputDevice->DrawPolygon(aPolygon); + // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it + // in vcl many times, create a size-optimized version + const Size aNeededBitmapSizePixel(nBWidth, nBHeight); + BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx()); + const bool bPreScaled(nBWidth * nBHeight < (250 * 250)); - bPainted = true; - } - } + // ... but only up to a maximum size, else it gets too expensive + if (bPreScaled) + { + // if color depth is below 24bit, expand before scaling for better quality. + // This is even needed for low colors, else the scale will produce + // a bitmap in gray or Black/White (!) + if (isPalettePixelFormat(aBitmapEx.getPixelFormat())) + { + aBitmapEx.Convert(BmpConversion::N24Bit); + } - if (!bPainted) - { - sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX())); - sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY())); - const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX())); - const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY())); - sal_Int32 nPosX(0); - sal_Int32 nPosY(0); - - if (nBLeft > nOLeft) - { - const sal_Int32 nDiff((nBLeft / nBWidth) + 1); + aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate); + } - nPosX -= nDiff; - nBLeft -= nDiff * nBWidth; - } + if (maBColorModifierStack.count()) + { + // when color modifier, apply to bitmap + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); - if (nBLeft + nBWidth <= nOLeft) - { - const sal_Int32 nDiff(-nBLeft / nBWidth); + // ModifyBitmapEx uses empty bitmap as sign to return that + // the content will be completely replaced to mono color, use shortcut + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); - nPosX += nDiff; - nBLeft += nDiff * nBWidth; - } + mpOutputDevice->SetFillColor(Color(aModifiedColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aPolygon); - if (nBTop > nOTop) - { - const sal_Int32 nDiff((nBTop / nBHeight) + 1); + return true; + } + } - nPosY -= nDiff; - nBTop -= nDiff * nBHeight; - } + sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX())); + sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY())); + const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX())); + const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY())); + sal_Int32 nPosX(0); + sal_Int32 nPosY(0); - if (nBTop + nBHeight <= nOTop) - { - const sal_Int32 nDiff(-nBTop / nBHeight); + if (nBLeft > nOLeft) + { + const sal_Int32 nDiff((nBLeft / nBWidth) + 1); - nPosY += nDiff; - nBTop += nDiff * nBHeight; - } + nPosX -= nDiff; + nBLeft -= nDiff * nBWidth; + } - // prepare OutDev - const Point aEmptyPoint(0, 0); - const ::tools::Rectangle aVisiblePixel( - aEmptyPoint, mpOutputDevice->GetOutputSizePixel()); - const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled()); - mpOutputDevice->EnableMapMode(false); + if (nBLeft + nBWidth <= nOLeft) + { + const sal_Int32 nDiff(-nBLeft / nBWidth); - // check if offset is used - const sal_Int32 nOffsetX( - basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth)); + nPosX += nDiff; + nBLeft += nDiff * nBWidth; + } - if (nOffsetX) - { - // offset in X, so iterate over Y first and draw lines - for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight; - nYPos += nBHeight, nPosY++) - { - for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX - : nBLeft); - nXPos < nOLeft + nOWidth; nXPos += nBWidth) - { - const ::tools::Rectangle aOutRectPixel( - Point(nXPos, nYPos), aNeededBitmapSizePixel); - - if (aOutRectPixel.Overlaps(aVisiblePixel)) - { - if (bPreScaled) - { - mpOutputDevice->DrawBitmapEx( - aOutRectPixel.TopLeft(), aBitmapEx); - } - else - { - mpOutputDevice->DrawBitmapEx( - aOutRectPixel.TopLeft(), aNeededBitmapSizePixel, - aBitmapEx); - } - } - } - } - } - else - { - // check if offset is used - const sal_Int32 nOffsetY( - basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight)); + if (nBTop > nOTop) + { + const sal_Int32 nDiff((nBTop / nBHeight) + 1); - // possible offset in Y, so iterate over X first and draw columns - for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth; - nXPos += nBWidth, nPosX++) - { - for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY - : nBTop); - nYPos < nOTop + nOHeight; nYPos += nBHeight) - { - const ::tools::Rectangle aOutRectPixel( - Point(nXPos, nYPos), aNeededBitmapSizePixel); - - if (aOutRectPixel.Overlaps(aVisiblePixel)) - { - if (bPreScaled) - { - mpOutputDevice->DrawBitmapEx( - aOutRectPixel.TopLeft(), aBitmapEx); - } - else - { - mpOutputDevice->DrawBitmapEx( - aOutRectPixel.TopLeft(), aNeededBitmapSizePixel, - aBitmapEx); - } - } - } - } - } + nPosY -= nDiff; + nBTop -= nDiff * nBHeight; + } - // restore OutDev - mpOutputDevice->EnableMapMode(bWasEnabled); - } + if (nBTop + nBHeight <= nOTop) + { + const sal_Int32 nDiff(-nBTop / nBHeight); + + nPosY += nDiff; + nBTop += nDiff * nBHeight; + } + + // prepare OutDev + const Point aEmptyPoint(0, 0); + // the visible rect, in pixels + const ::tools::Rectangle aVisiblePixel(aEmptyPoint, mpOutputDevice->GetOutputSizePixel()); + const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled()); + mpOutputDevice->EnableMapMode(false); + + // check if offset is used + const sal_Int32 nOffsetX(basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth)); + + if (nOffsetX) + { + // offset in X, so iterate over Y first and draw lines + for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight; nYPos += nBHeight, nPosY++) + { + for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX : nBLeft); + nXPos < nOLeft + nOWidth; nXPos += nBWidth) + { + const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel); + + if (aOutRectPixel.Overlaps(aVisiblePixel)) + { + if (bPreScaled) + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx); + } + else + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), + aNeededBitmapSizePixel, aBitmapEx); } } } } } - - if (!bPrimitiveAccepted) + else { - // do not accept, use decomposition - process(rFillBitmapCandidate); + // check if offset is used + const sal_Int32 nOffsetY(basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight)); + + // possible offset in Y, so iterate over X first and draw columns + for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth; nXPos += nBWidth, nPosX++) + { + for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY : nBTop); + nYPos < nOTop + nOHeight; nYPos += nBHeight) + { + const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel); + + if (aOutRectPixel.Overlaps(aVisiblePixel)) + { + if (bPreScaled) + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx); + } + else + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), + aNeededBitmapSizePixel, aBitmapEx); + } + } + } + } } + + // restore OutDev + mpOutputDevice->EnableMapMode(bWasEnabled); + return true; } // direct draw of Graphic @@ -773,7 +882,7 @@ void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive aMask.transform(maCurrentTransformation); // Unless smooth edges are needed, simply use clipping. - if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing()) + if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing()) { mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); @@ -889,7 +998,7 @@ void VclProcessor2D::RenderTransparencePrimitive2D( process(rTransCandidate.getTransparence()); // back to old color stack - maBColorModifierStack = aLastBColorModifierStack; + maBColorModifierStack = std::move(aLastBColorModifierStack); // back to old OutDev mpOutputDevice = pLastOutputDevice; @@ -909,10 +1018,9 @@ void VclProcessor2D::RenderTransformPrimitive2D( // create new transformations for CurrentTransformation // and for local ViewInformation2D maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation(); - const geometry::ViewInformation2D aViewInformation2D( - getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), - getViewInformation2D().getViewTransformation(), getViewInformation2D().getViewport(), - getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); updateViewInformation(aViewInformation2D); // process content @@ -931,10 +1039,8 @@ void VclProcessor2D::RenderPagePreviewPrimitive2D( const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); // create new local ViewInformation2D - const geometry::ViewInformation2D aViewInformation2D( - getViewInformation2D().getObjectTransformation(), - getViewInformation2D().getViewTransformation(), getViewInformation2D().getViewport(), - rPagePreviewCandidate.getXDrawPage(), getViewInformation2D().getViewTime()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setVisualizedPage(rPagePreviewCandidate.getXDrawPage()); updateViewInformation(aViewInformation2D); // process decomposed content @@ -980,8 +1086,8 @@ void VclProcessor2D::RenderMarkerArrayPrimitive2D( { const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos) - aDiscreteHalfSize); - const Point aDiscretePoint(basegfx::fround(aDiscreteTopLeft.getX()), - basegfx::fround(aDiscreteTopLeft.getY())); + const Point aDiscretePoint(basegfx::fround<tools::Long>(aDiscreteTopLeft.getX()), + basegfx::fround<tools::Long>(aDiscreteTopLeft.getY())); mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker); } @@ -1001,8 +1107,8 @@ void VclProcessor2D::RenderPointArrayPrimitive2D( for (auto const& pos : rPositions) { const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos); - const Point aPos(basegfx::fround(aViewPosition.getX()), - basegfx::fround(aViewPosition.getY())); + const Point aPos(basegfx::fround<tools::Long>(aViewPosition.getX()), + basegfx::fround<tools::Long>(aViewPosition.getY())); mpOutputDevice->DrawPixel(aPos, aVCLColor); } @@ -1017,7 +1123,7 @@ void VclProcessor2D::RenderPolygonStrokePrimitive2D( const double fLineWidth(rLineAttribute.getWidth()); bool bDone(false); - if (basegfx::fTools::more(fLineWidth, 0.0)) + if (fLineWidth > 0.0) { const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation * basegfx::B2DVector(fLineWidth, 0.0)); @@ -1048,7 +1154,7 @@ void VclProcessor2D::RenderPolygonStrokePrimitive2D( if (nCount) { - const bool bAntiAliased(SvtOptionsDrawinglayer::IsAntiAliasing()); + const bool bAntiAliased(getViewInformation2D().getUseAntiAliasing()); aHairlinePolyPolygon.transform(maCurrentTransformation); if (bAntiAliased) @@ -1261,26 +1367,12 @@ void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEp } } -void VclProcessor2D::RenderObjectInfoPrimitive2D( - const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D) -{ - // remember current ObjectInfoPrimitive2D and set new current one (build a stack - push) - const primitive2d::ObjectInfoPrimitive2D* pLast(getObjectInfoPrimitive2D()); - mpObjectInfoPrimitive2D = &rObjectInfoPrimitive2D; - - // process content - process(rObjectInfoPrimitive2D.getChildren()); - - // restore current ObjectInfoPrimitive2D (pop) - mpObjectInfoPrimitive2D = pLast; -} - void VclProcessor2D::RenderSvgLinearAtomPrimitive2D( const primitive2d::SvgLinearAtomPrimitive2D& rCandidate) { const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA()); - if (!basegfx::fTools::more(fDelta, 0.0)) + if (fDelta <= 0.0) return; const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA())); @@ -1290,7 +1382,7 @@ void VclProcessor2D::RenderSvgLinearAtomPrimitive2D( const basegfx::B2DVector aDiscreteVector( getViewInformation2D().getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); - const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / 1.414213562373)); + const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2)); // use color distance and discrete lengths to calculate step count const sal_uInt32 nSteps(calculateStepsForSvgGradient(aColorA, aColorB, fDelta, fDiscreteUnit)); @@ -1324,7 +1416,7 @@ void VclProcessor2D::RenderSvgRadialAtomPrimitive2D( { const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA()); - if (!basegfx::fTools::more(fDeltaScale, 0.0)) + if (fDeltaScale <= 0.0) return; const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA())); @@ -1334,7 +1426,7 @@ void VclProcessor2D::RenderSvgRadialAtomPrimitive2D( const basegfx::B2DVector aDiscreteVector( getViewInformation2D().getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); - const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / 1.414213562373)); + const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2)); // use color distance and discrete lengths to calculate step count const sal_uInt32 nSteps( @@ -1475,13 +1567,11 @@ void VclProcessor2D::adaptTextToFillDrawMode() const // process support VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, - OutputDevice& rOutDev, - const basegfx::BColorModifierStack& rInitStack) + OutputDevice& rOutDev, basegfx::BColorModifierStack aInitStack) : BaseProcessor2D(rViewInformation) , mpOutputDevice(&rOutDev) - , maBColorModifierStack(rInitStack) + , maBColorModifierStack(std::move(aInitStack)) , mnPolygonStrokePrimitive2D(0) - , mpObjectInfoPrimitive2D(nullptr) { // set digit language, derived from SvtCTLOptions to have the correct // number display for arabic/hindi numerals diff --git a/drawinglayer/source/processor2d/vclprocessor2d.hxx b/drawinglayer/source/processor2d/vclprocessor2d.hxx index 7e251aa0ca13..d2f0a69157fb 100644 --- a/drawinglayer/source/processor2d/vclprocessor2d.hxx +++ b/drawinglayer/source/processor2d/vclprocessor2d.hxx @@ -46,7 +46,6 @@ class PolygonStrokePrimitive2D; class ControlPrimitive2D; class PagePreviewPrimitive2D; class EpsPrimitive2D; -class ObjectInfoPrimitive2D; class SvgLinearAtomPrimitive2D; class SvgRadialAtomPrimitive2D; } @@ -76,9 +75,6 @@ protected: // PolygonStrokePrimitive2D's decompositions (normally only one) sal_uInt32 mnPolygonStrokePrimitive2D; - // currently used ObjectInfoPrimitive2D - const primitive2d::ObjectInfoPrimitive2D* mpObjectInfoPrimitive2D; - // common VCL rendering support void RenderTextSimpleOrDecoratedPortionPrimitive2D( const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate); @@ -105,8 +101,6 @@ protected: void RenderPolygonStrokePrimitive2D( const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate); void RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D); - void - RenderObjectInfoPrimitive2D(const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D); void RenderSvgLinearAtomPrimitive2D(const primitive2d::SvgLinearAtomPrimitive2D& rCandidate); void RenderSvgRadialAtomPrimitive2D(const primitive2d::SvgRadialAtomPrimitive2D& rCandidate); @@ -117,14 +111,12 @@ protected: public: // constructor/destructor VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev, - const basegfx::BColorModifierStack& rInitStack = basegfx::BColorModifierStack()); + basegfx::BColorModifierStack aInitStack = basegfx::BColorModifierStack()); virtual ~VclProcessor2D() override; - // access to currently used ObjectInfoPrimitive2D - const primitive2d::ObjectInfoPrimitive2D* getObjectInfoPrimitive2D() const - { - return mpObjectInfoPrimitive2D; - } +private: + bool RenderFillGraphicPrimitive2DImpl( + const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate); }; } // end of namespace drawinglayer::processor2d diff --git a/drawinglayer/source/processor3d/baseprocessor3d.cxx b/drawinglayer/source/processor3d/baseprocessor3d.cxx index 0420f02ca37d..a9638dc92bb4 100644 --- a/drawinglayer/source/processor3d/baseprocessor3d.cxx +++ b/drawinglayer/source/processor3d/baseprocessor3d.cxx @@ -18,7 +18,7 @@ */ #include <drawinglayer/processor3d/baseprocessor3d.hxx> -#include <comphelper/sequence.hxx> +#include <utility> using namespace com::sun::star; @@ -30,8 +30,8 @@ namespace drawinglayer::processor3d { } - BaseProcessor3D::BaseProcessor3D(const geometry::ViewInformation3D& rViewInformation) - : maViewInformation3D(rViewInformation) + BaseProcessor3D::BaseProcessor3D(geometry::ViewInformation3D aViewInformation) + : maViewInformation3D(std::move(aViewInformation)) { } diff --git a/drawinglayer/source/processor3d/defaultprocessor3d.cxx b/drawinglayer/source/processor3d/defaultprocessor3d.cxx index b9159c46c73f..498cc93d86cc 100644 --- a/drawinglayer/source/processor3d/defaultprocessor3d.cxx +++ b/drawinglayer/source/processor3d/defaultprocessor3d.cxx @@ -59,106 +59,82 @@ namespace drawinglayer::processor3d // create texture const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getGradient(); const basegfx::B2DRange aOutlineRange(0.0, 0.0, rPrimitive.getTextureSize().getX(), rPrimitive.getTextureSize().getY()); - const attribute::GradientStyle aGradientStyle(rFillGradient.getStyle()); - sal_uInt32 nSteps(rFillGradient.getSteps()); - const basegfx::BColor& aStart(rFillGradient.getStartColor()); - const basegfx::BColor& aEnd(rFillGradient.getEndColor()); - const sal_uInt32 nMaxSteps(sal_uInt32((aStart.getMaximumDistance(aEnd) * 127.5) + 0.5)); + const css::awt::GradientStyle aGradientStyle(rFillGradient.getStyle()); std::shared_ptr< texture::GeoTexSvx > pNewTex; + basegfx::BColor aSingleColor; - if(nMaxSteps) + if (!rFillGradient.getColorStops().isSingleColor(aSingleColor)) { - // there IS a color distance - if(nSteps == 0) - { - nSteps = nMaxSteps; - } - - if(nSteps < 2) - { - nSteps = 2; - } - - if(nSteps > nMaxSteps) - { - nSteps = nMaxSteps; - } - switch(aGradientStyle) { - case attribute::GradientStyle::Linear: + default: // GradientStyle_MAKE_FIXED_SIZE + case css::awt::GradientStyle_LINEAR: { pNewTex = std::make_shared<texture::GeoTexSvxGradientLinear>( aOutlineRange, aOutlineRange, - aStart, - aEnd, - nSteps, + rFillGradient.getSteps(), + rFillGradient.getColorStops(), rFillGradient.getBorder(), rFillGradient.getAngle()); break; } - case attribute::GradientStyle::Axial: + case css::awt::GradientStyle_AXIAL: { pNewTex = std::make_shared<texture::GeoTexSvxGradientAxial>( aOutlineRange, aOutlineRange, - aStart, - aEnd, - nSteps, + rFillGradient.getSteps(), + rFillGradient.getColorStops(), rFillGradient.getBorder(), rFillGradient.getAngle()); break; } - case attribute::GradientStyle::Radial: + case css::awt::GradientStyle_RADIAL: { pNewTex = std::make_shared<texture::GeoTexSvxGradientRadial>( aOutlineRange, - aStart, - aEnd, - nSteps, + rFillGradient.getSteps(), + rFillGradient.getColorStops(), rFillGradient.getBorder(), rFillGradient.getOffsetX(), rFillGradient.getOffsetY()); break; } - case attribute::GradientStyle::Elliptical: + case css::awt::GradientStyle_ELLIPTICAL: { pNewTex = std::make_shared<texture::GeoTexSvxGradientElliptical>( aOutlineRange, - aStart, - aEnd, - nSteps, + rFillGradient.getSteps(), + rFillGradient.getColorStops(), rFillGradient.getBorder(), rFillGradient.getOffsetX(), rFillGradient.getOffsetY(), rFillGradient.getAngle()); break; } - case attribute::GradientStyle::Square: + case css::awt::GradientStyle_SQUARE: { pNewTex = std::make_shared<texture::GeoTexSvxGradientSquare>( aOutlineRange, - aStart, - aEnd, - nSteps, + rFillGradient.getSteps(), + rFillGradient.getColorStops(), rFillGradient.getBorder(), rFillGradient.getOffsetX(), rFillGradient.getOffsetY(), rFillGradient.getAngle()); break; } - case attribute::GradientStyle::Rect: + case css::awt::GradientStyle_RECT: { pNewTex = std::make_shared<texture::GeoTexSvxGradientRect>( aOutlineRange, - aStart, - aEnd, - nSteps, + rFillGradient.getSteps(), + rFillGradient.getColorStops(), rFillGradient.getBorder(), rFillGradient.getOffsetX(), rFillGradient.getOffsetY(), @@ -171,8 +147,8 @@ namespace drawinglayer::processor3d } else { - // no color distance -> same color, use simple texture - pNewTex = std::make_shared<texture::GeoTexSvxMono>(aStart, 1.0 - aStart.luminance()); + // only one color, so no real gradient -> use simple texture + pNewTex = std::make_shared<texture::GeoTexSvxMono>(aSingleColor, 1.0 - aSingleColor.luminance()); mbSimpleTextureActive = true; } @@ -214,7 +190,7 @@ namespace drawinglayer::processor3d // rescue values const bool bOldModulate(getModulate()); mbModulate = rPrimitive.getModulate(); const bool bOldFilter(getFilter()); mbFilter = rPrimitive.getFilter(); - std::shared_ptr< texture::GeoTexSvx > pOldTex = mpGeoTexSvx; + std::shared_ptr<texture::GeoTexSvx> xOldTex(mpGeoTexSvx); // calculate logic pixel size in object coordinates. Create transformation view // to object by inverting ObjectToView @@ -242,7 +218,7 @@ namespace drawinglayer::processor3d // restore values mbModulate = bOldModulate; mbFilter = bOldFilter; - mpGeoTexSvx = pOldTex; + mpGeoTexSvx = std::move(xOldTex); } void DefaultProcessor3D::impRenderBitmapTexturePrimitive3D(const primitive3d::BitmapTexturePrimitive3D& rPrimitive) @@ -293,7 +269,7 @@ namespace drawinglayer::processor3d // restore values mbModulate = bOldModulate; mbFilter = bOldFilter; - mpGeoTexSvx = pOldTex; + mpGeoTexSvx = std::move(pOldTex); } void DefaultProcessor3D::impRenderModifiedColorPrimitive3D(const primitive3d::ModifiedColorPrimitive3D& rModifiedCandidate) diff --git a/drawinglayer/source/processor3d/geometry2dextractor.cxx b/drawinglayer/source/processor3d/geometry2dextractor.cxx index 3fb3d0d19f12..6959df6405e6 100644 --- a/drawinglayer/source/processor3d/geometry2dextractor.cxx +++ b/drawinglayer/source/processor3d/geometry2dextractor.cxx @@ -23,11 +23,12 @@ #include <drawinglayer/primitive3d/modifiedcolorprimitive3d.hxx> #include <drawinglayer/primitive3d/polygonprimitive3d.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <primitive3d/textureprimitive3d.hxx> +#include <utility> using namespace com::sun::star; @@ -89,7 +90,7 @@ namespace drawinglayer::processor3d { a2DHairline.transform(getObjectTransformation()); const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(rPrimitive.getBColor())); - const primitive2d::Primitive2DReference xRef(new primitive2d::PolygonHairlinePrimitive2D(a2DHairline, aModifiedColor)); + const primitive2d::Primitive2DReference xRef(new primitive2d::PolygonHairlinePrimitive2D(std::move(a2DHairline), aModifiedColor)); maPrimitive2DSequence.push_back(xRef); } break; @@ -104,7 +105,7 @@ namespace drawinglayer::processor3d { a2DFill.transform(getObjectTransformation()); const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(rPrimitive.getMaterial().getColor())); - const primitive2d::Primitive2DReference xRef(new primitive2d::PolyPolygonColorPrimitive2D(a2DFill, aModifiedColor)); + const primitive2d::Primitive2DReference xRef(new primitive2d::PolyPolygonColorPrimitive2D(std::move(a2DFill), aModifiedColor)); maPrimitive2DSequence.push_back(xRef); } break; @@ -141,9 +142,9 @@ namespace drawinglayer::processor3d Geometry2DExtractingProcessor::Geometry2DExtractingProcessor( const geometry::ViewInformation3D& rViewInformation, - const basegfx::B2DHomMatrix& rObjectTransformation) + basegfx::B2DHomMatrix aObjectTransformation) : BaseProcessor3D(rViewInformation), - maObjectTransformation(rObjectTransformation), + maObjectTransformation(std::move(aObjectTransformation)), maBColorModifierStack() { } diff --git a/drawinglayer/source/processor3d/shadow3dextractor.cxx b/drawinglayer/source/processor3d/shadow3dextractor.cxx index bed08b9688fb..c4c0e0ba1249 100644 --- a/drawinglayer/source/processor3d/shadow3dextractor.cxx +++ b/drawinglayer/source/processor3d/shadow3dextractor.cxx @@ -24,12 +24,14 @@ #include <drawinglayer/primitive3d/transformprimitive3d.hxx> #include <drawinglayer/primitive3d/polygonprimitive3d.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <osl/diagnose.h> #include <rtl/ref.hxx> +#include <utility> using namespace com::sun::star; @@ -154,7 +156,7 @@ namespace drawinglayer::processor3d a2DHairline.transform(getObjectTransformation()); mpPrimitive2DSequence->push_back( new primitive2d::PolygonHairlinePrimitive2D( - a2DHairline, + std::move(a2DHairline), basegfx::BColor())); } } @@ -185,7 +187,7 @@ namespace drawinglayer::processor3d a2DFill.transform(getObjectTransformation()); mpPrimitive2DSequence->push_back( new primitive2d::PolyPolygonColorPrimitive2D( - a2DFill, + std::move(a2DFill), basegfx::BColor())); } } @@ -202,13 +204,13 @@ namespace drawinglayer::processor3d Shadow3DExtractingProcessor::Shadow3DExtractingProcessor( const geometry::ViewInformation3D& rViewInformation, - const basegfx::B2DHomMatrix& rObjectTransformation, + basegfx::B2DHomMatrix aObjectTransformation, const basegfx::B3DVector& rLightNormal, double fShadowSlant, const basegfx::B3DRange& rContained3DRange) : BaseProcessor3D(rViewInformation), mpPrimitive2DSequence(&maPrimitive2DSequence), - maObjectTransformation(rObjectTransformation), + maObjectTransformation(std::move(aObjectTransformation)), maLightNormal(rLightNormal), mfLightPlaneScalar(0.0), mbShadowProjectionIsValid(false), diff --git a/drawinglayer/source/processor3d/zbufferprocessor3d.cxx b/drawinglayer/source/processor3d/zbufferprocessor3d.cxx index fb74a233e283..b9cb8ffb7a1d 100644 --- a/drawinglayer/source/processor3d/zbufferprocessor3d.cxx +++ b/drawinglayer/source/processor3d/zbufferprocessor3d.cxx @@ -27,7 +27,9 @@ #include <basegfx/polygon/b3dpolygontools.hxx> #include <basegfx/polygon/b3dpolypolygontools.hxx> #include <drawinglayer/attribute/sdrlightingattribute3d.hxx> +#include <o3tl/safeint.hxx> #include <svtools/optionsdrawinglayer.hxx> +#include <utility> using namespace com::sun::star; @@ -269,7 +271,7 @@ void ZBufferRasterConverter3D::processLineSpan(const basegfx::RasterConversionLi if(nSpanCount & 0x0001) return; - if(nLine < 0 || nLine >= static_cast<sal_Int32>(mrBuffer.getHeight())) + if(nLine < 0 || o3tl::make_unsigned(nLine) >= mrBuffer.getHeight()) return; sal_uInt32 nXA(std::min(mrBuffer.getWidth(), static_cast<sal_uInt32>(std::max(sal_Int32(0), basegfx::fround(rA.getX().getVal()))))); @@ -374,16 +376,16 @@ private: public: RasterPrimitive3D( - const std::shared_ptr< drawinglayer::texture::GeoTexSvx >& pGeoTexSvx, - const std::shared_ptr< drawinglayer::texture::GeoTexSvx >& pTransparenceGeoTexSvx, + std::shared_ptr< drawinglayer::texture::GeoTexSvx > pGeoTexSvx, + std::shared_ptr< drawinglayer::texture::GeoTexSvx > pTransparenceGeoTexSvx, const drawinglayer::attribute::MaterialAttribute3D& rMaterial, const basegfx::B3DPolyPolygon& rPolyPolygon, bool bModulate, bool bFilter, bool bSimpleTextureActive, bool bIsLine) - : mpGeoTexSvx(pGeoTexSvx), - mpTransparenceGeoTexSvx(pTransparenceGeoTexSvx), + : mpGeoTexSvx(std::move(pGeoTexSvx)), + mpTransparenceGeoTexSvx(std::move(pTransparenceGeoTexSvx)), maMaterial(rMaterial), maPolyPolygon(rPolyPolygon), mfCenterZ(basegfx::utils::getRange(rPolyPolygon).getCenter().getZ()), diff --git a/drawinglayer/source/texture/texture.cxx b/drawinglayer/source/texture/texture.cxx index b3477f642880..b965e0e4b933 100644 --- a/drawinglayer/source/texture/texture.cxx +++ b/drawinglayer/source/texture/texture.cxx @@ -20,6 +20,7 @@ #include <sal/config.h> #include <algorithm> +#include <limits> #include <texture/texture.hxx> #include <basegfx/numeric/ftools.hxx> @@ -71,13 +72,14 @@ namespace drawinglayer::texture GeoTexSvxGradient::GeoTexSvxGradient( const basegfx::B2DRange& rDefinitionRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder) - : maDefinitionRange(rDefinitionRange), - maStart(rStart), - maEnd(rEnd), - mfBorder(fBorder) + : maDefinitionRange(rDefinitionRange) + , mnRequestedSteps(nRequestedSteps) + , mnColorStops(rColorStops) + , mfBorder(fBorder) + , maLastColorStopRange() { } @@ -92,26 +94,26 @@ namespace drawinglayer::texture return (pCompare && maGradientInfo == pCompare->maGradientInfo && maDefinitionRange == pCompare->maDefinitionRange + && mnRequestedSteps == pCompare->mnRequestedSteps + && mnColorStops == pCompare->mnColorStops && mfBorder == pCompare->mfBorder); } - GeoTexSvxGradientLinear::GeoTexSvxGradientLinear( const basegfx::B2DRange& rDefinitionRange, const basegfx::B2DRange& rOutputRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, - sal_uInt32 nSteps, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder, double fAngle) - : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder), - mfUnitMinX(0.0), - mfUnitWidth(1.0), - mfUnitMaxY(1.0) + : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) + , mfUnitMinX(0.0) + , mfUnitWidth(1.0) + , mfUnitMaxY(1.0) { maGradientInfo = basegfx::utils::createLinearODFGradientInfo( rDefinitionRange, - nSteps, + nRequestedSteps, fBorder, fAngle); @@ -131,16 +133,30 @@ namespace drawinglayer::texture } void GeoTexSvxGradientLinear::appendTransformationsAndColors( - std::vector< B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) { - rOuterColor = maStart; + // no color at all, done + if (mnColorStops.empty()) + return; - if(!maGradientInfo.getSteps()) + // only one color, done + if (mnColorStops.size() < 2) return; - const double fStripeWidth(1.0 / maGradientInfo.getSteps()); - B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + // check if we need last-ColorStop-correction + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); + + if (bPenultimateUsed) + { + // Here we need to temporarily add a ColorStop entry with the + // same color as the last entry to correctly 'close' the + // created gradient geometry. + // The simplest way is to temporarily add an entry to the local + // ColorStops for this at 1.0 (using same color) + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); + } + + // prepare unit range transform basegfx::B2DHomMatrix aPattern; // bring from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1] @@ -151,54 +167,106 @@ namespace drawinglayer::texture aPattern.scale(mfUnitWidth, 1.0); aPattern.translate(mfUnitMinX, 0.0); - for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + // outer loop over ColorStops, each is from cs_l to cs_r + for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++) { - const double fPos(fStripeWidth * a); - basegfx::B2DHomMatrix aNew(aPattern); - - // scale and translate in Y - double fHeight(1.0 - fPos); - - if(a + 1 == maGradientInfo.getSteps() && mfUnitMaxY > 1.0) + // get offsets + const double fOffsetStart(cs_l->getStopOffset()); + const double fOffsetEnd(cs_r->getStopOffset()); + + // same offset, empty BColorStopRange, continue with next step + if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) + continue; + + // get colors & calculate steps + const basegfx::BColor aCStart(cs_l->getStopColor()); + const basegfx::BColor aCEnd(cs_r->getStopColor()); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + maGradientInfo.getRequestedSteps(), aCStart, aCEnd)); + + // calculate StripeWidth + // nSteps is >= 1, see getRequestedSteps, so no check needed here + const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps); + + // for the 1st color range we do not need to create the 1st step + // since it will be equal to StartColor and thus OuterColor, so + // will be painted by the 1st, always-created background polygon + // colored using OuterColor. + // We *need* to create this though for all 'inner' color ranges + // to get a correct start + const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0); + + for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++) { - fHeight += mfUnitMaxY - 1.0; + // calculate pos in Y + const double fPos(fOffsetStart + (fStripeWidth * innerLoop)); + + // scale and translate in Y. For GradientLinear we always have + // the full height + double fHeight(1.0 - fPos); + + if (mfUnitMaxY > 1.0) + { + // extend when difference between definition and OutputRange exists + fHeight += mfUnitMaxY - 1.0; + } + + basegfx::B2DHomMatrix aNew(aPattern); + aNew.scale(1.0, fHeight); + aNew.translate(0.0, fPos); + + // set and add at target + aCallback( + maGradientInfo.getTextureTransform() * aNew, + 1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1)))); } + } - aNew.scale(1.0, fHeight); - aNew.translate(0.0, fPos); - - // set at target - aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * aNew; - - // interpolate and set color - aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); - - rEntries.push_back(aB2DHomMatrixAndBColor); + if (bPenultimateUsed) + { + // correct temporary change + mnColorStops.pop_back(); } } void GeoTexSvxGradientLinear::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const { - const double fScaler(basegfx::utils::getLinearGradientAlpha(rUV, maGradientInfo)); + // no color at all, done + if (mnColorStops.empty()) + return; - rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + // just single color, done + if (mnColorStops.size() < 2) + { + rBColor = mnColorStops.front().getStopColor(); + return; + } + + // texture-back-transform X/Y -> t [0.0..1.0] and determine color + const double fScaler(basegfx::utils::getLinearGradientAlpha(rUV, maGradientInfo)); + rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange); } GeoTexSvxGradientAxial::GeoTexSvxGradientAxial( const basegfx::B2DRange& rDefinitionRange, const basegfx::B2DRange& rOutputRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, - sal_uInt32 nSteps, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder, double fAngle) - : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder), - mfUnitMinX(0.0), - mfUnitWidth(1.0) + : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) + , mfUnitMinX(0.0) + , mfUnitWidth(1.0) { + // ARGH! GradientAxial uses the ColorStops in reverse order compared + // with the other gradients. Either stay 'thinking reverse' for the + // rest of time or adapt it here and go in same order as the other five, + // so unifications/tooling will be possible + mnColorStops.reverseColorStops(); + maGradientInfo = basegfx::utils::createAxialODFGradientInfo( rDefinitionRange, - nSteps, + nRequestedSteps, fBorder, fAngle); @@ -217,65 +285,118 @@ namespace drawinglayer::texture } void GeoTexSvxGradientAxial::appendTransformationsAndColors( - std::vector< B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) { - rOuterColor = maEnd; + // no color at all, done + if (mnColorStops.empty()) + return; - if(!maGradientInfo.getSteps()) + // only one color, done + if (mnColorStops.size() < 2) return; - const double fStripeWidth(1.0 / maGradientInfo.getSteps()); - B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + // check if we need last-ColorStop-correction + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); + + if (bPenultimateUsed) + { + // temporarily add a ColorStop entry + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); + } + + // prepare unit range transform + basegfx::B2DHomMatrix aPattern; + + // bring in X from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1] + aPattern.scale(0.5, 1.0); + aPattern.translate(0.5, 0.0); + + // scale/translate in X + aPattern.scale(mfUnitWidth, 1.0); + aPattern.translate(mfUnitMinX, 0.0); - for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + // outer loop over ColorStops, each is from cs_l to cs_r + for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++) { - const double fPos(fStripeWidth * a); - basegfx::B2DHomMatrix aNew; + // get offsets + const double fOffsetStart(cs_l->getStopOffset()); + const double fOffsetEnd(cs_r->getStopOffset()); - // bring in X from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1] - aNew.scale(0.5, 1.0); - aNew.translate(0.5, 0.0); + // same offset, empty BColorStopRange, continue with next step + if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) + continue; - // scale/translate in X - aNew.scale(mfUnitWidth, 1.0); - aNew.translate(mfUnitMinX, 0.0); + // get colors & calculate steps + const basegfx::BColor aCStart(cs_l->getStopColor()); + const basegfx::BColor aCEnd(cs_r->getStopColor()); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + maGradientInfo.getRequestedSteps(), aCStart, aCEnd)); - // already centered in Y on X-Axis, just scale in Y - aNew.scale(1.0, 1.0 - fPos); + // calculate StripeWidth + // nSteps is >= 1, see getRequestedSteps, so no check needed here + const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps); - // set at target - aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * aNew; + // for the 1st color range we do not need to create the 1st step, see above + const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0); + + for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++) + { + // calculate pos in Y + const double fPos(fOffsetStart + (fStripeWidth * innerLoop)); - // interpolate and set color - aB2DHomMatrixAndBColor.maBColor = interpolate(maEnd, maStart, double(a) / double(maGradientInfo.getSteps() - 1)); + // already centered in Y on X-Axis, just scale in Y + basegfx::B2DHomMatrix aNew(aPattern); + aNew.scale(1.0, 1.0 - fPos); - rEntries.push_back(aB2DHomMatrixAndBColor); + // set and add at target + aCallback( + maGradientInfo.getTextureTransform() * aNew, + 1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1)))); + } + } + + if (bPenultimateUsed) + { + // correct temporary change + mnColorStops.pop_back(); } } void GeoTexSvxGradientAxial::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const { + // no color at all, done + if (mnColorStops.empty()) + return; + + // just single color, done + if (mnColorStops.size() < 2) + { + // we use the reverse ColorSteps here, so use front value + rBColor = mnColorStops.front().getStopColor(); + return; + } + + // texture-back-transform X/Y -> t [0.0..1.0] and determine color const double fScaler(basegfx::utils::getAxialGradientAlpha(rUV, maGradientInfo)); - rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + // we use the reverse ColorSteps here, so mirror scaler value + rBColor = mnColorStops.getInterpolatedBColor(1.0 - fScaler, mnRequestedSteps, maLastColorStopRange); } GeoTexSvxGradientRadial::GeoTexSvxGradientRadial( const basegfx::B2DRange& rDefinitionRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, - sal_uInt32 nSteps, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY) - : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) { maGradientInfo = basegfx::utils::createRadialODFGradientInfo( rDefinitionRange, basegfx::B2DVector(fOffsetX,fOffsetY), - nSteps, + nRequestedSteps, fBorder); } @@ -284,49 +405,100 @@ namespace drawinglayer::texture } void GeoTexSvxGradientRadial::appendTransformationsAndColors( - std::vector< B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) { - rOuterColor = maStart; + // no color at all, done + if (mnColorStops.empty()) + return; - if(!maGradientInfo.getSteps()) + // only one color, done + if (mnColorStops.size() < 2) return; - const double fStepSize(1.0 / maGradientInfo.getSteps()); - B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + // check if we need last-ColorStop-correction + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); + + if (bPenultimateUsed) + { + // temporarily add a ColorStop entry + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); + } + + // outer loop over ColorStops, each is from cs_l to cs_r + for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++) + { + // get offsets + const double fOffsetStart(cs_l->getStopOffset()); + const double fOffsetEnd(cs_r->getStopOffset()); + + // same offset, empty BColorStopRange, continue with next step + if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) + continue; + + // get colors & calculate steps + const basegfx::BColor aCStart(cs_l->getStopColor()); + const basegfx::BColor aCEnd(cs_r->getStopColor()); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + maGradientInfo.getRequestedSteps(), aCStart, aCEnd)); + + // calculate StripeWidth + const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps); + + // get correct start for inner loop (see above) + const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0); + + for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++) + { + // calculate size/radius + const double fSize(1.0 - (fOffsetStart + (fStripeWidth * innerLoop))); + + // set and add at target + aCallback( + maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fSize, fSize), + 1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1)))); + } + } - for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + if (bPenultimateUsed) { - const double fSize(1.0 - (fStepSize * a)); - aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fSize, fSize); - aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); - rEntries.push_back(aB2DHomMatrixAndBColor); + // correct temporary change + mnColorStops.pop_back(); } } void GeoTexSvxGradientRadial::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const { - const double fScaler(basegfx::utils::getRadialGradientAlpha(rUV, maGradientInfo)); + // no color at all, done + if (mnColorStops.empty()) + return; + + // just single color, done + if (mnColorStops.size() < 2) + { + rBColor = mnColorStops.front().getStopColor(); + return; + } - rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + // texture-back-transform X/Y -> t [0.0..1.0] and determine color + const double fScaler(basegfx::utils::getRadialGradientAlpha(rUV, maGradientInfo)); + rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange); } GeoTexSvxGradientElliptical::GeoTexSvxGradientElliptical( const basegfx::B2DRange& rDefinitionRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, - sal_uInt32 nSteps, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY, double fAngle) - : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) { maGradientInfo = basegfx::utils::createEllipticalODFGradientInfo( rDefinitionRange, basegfx::B2DVector(fOffsetX,fOffsetY), - nSteps, + nRequestedSteps, fBorder, fAngle); } @@ -336,67 +508,107 @@ namespace drawinglayer::texture } void GeoTexSvxGradientElliptical::appendTransformationsAndColors( - std::vector< B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) { - rOuterColor = maStart; + // no color at all, done + if (mnColorStops.empty()) + return; - if(!maGradientInfo.getSteps()) + // only one color, done + if (mnColorStops.size() < 2) return; - double fWidth(1.0); - double fHeight(1.0); - double fIncrementX(0.0); - double fIncrementY(0.0); + // check if we need last-ColorStop-correction + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); - if(maGradientInfo.getAspectRatio() > 1.0) + if (bPenultimateUsed) { - fIncrementY = fHeight / maGradientInfo.getSteps(); - fIncrementX = fIncrementY / maGradientInfo.getAspectRatio(); - } - else - { - fIncrementX = fWidth / maGradientInfo.getSteps(); - fIncrementY = fIncrementX * maGradientInfo.getAspectRatio(); + // temporarily add a ColorStop entry + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); } - B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + // prepare vars dependent on aspect ratio + const double fAR(maGradientInfo.getAspectRatio()); + const bool bMTO(fAR > 1.0); - for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + // outer loop over ColorStops, each is from cs_l to cs_r + for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++) { - // next step - fWidth -= fIncrementX; - fHeight -= fIncrementY; + // get offsets + const double fOffsetStart(cs_l->getStopOffset()); + const double fOffsetEnd(cs_r->getStopOffset()); + + // same offset, empty BColorStopRange, continue with next step + if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) + continue; - aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fWidth, fHeight); - aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); - rEntries.push_back(aB2DHomMatrixAndBColor); + // get colors & calculate steps + const basegfx::BColor aCStart(cs_l->getStopColor()); + const basegfx::BColor aCEnd(cs_r->getStopColor()); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + maGradientInfo.getRequestedSteps(), aCStart, aCEnd)); + + // calculate StripeWidth + const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps); + + // get correct start for inner loop (see above) + const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0); + + for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++) + { + // calculate offset position for entry + const double fSize(fOffsetStart + (fStripeWidth * innerLoop)); + + // set and add at target + aCallback( + maGradientInfo.getTextureTransform() + * basegfx::utils::createScaleB2DHomMatrix( + 1.0 - (bMTO ? fSize / fAR : fSize), + 1.0 - (bMTO ? fSize : fSize * fAR)), + 1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1)))); + } + } + + if (bPenultimateUsed) + { + // correct temporary change + mnColorStops.pop_back(); } } void GeoTexSvxGradientElliptical::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const { - const double fScaler(basegfx::utils::getEllipticalGradientAlpha(rUV, maGradientInfo)); + // no color at all, done + if (mnColorStops.empty()) + return; - rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + // just single color, done + if (mnColorStops.size() < 2) + { + rBColor = mnColorStops.front().getStopColor(); + return; + } + + // texture-back-transform X/Y -> t [0.0..1.0] and determine color + const double fScaler(basegfx::utils::getEllipticalGradientAlpha(rUV, maGradientInfo)); + rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange); } GeoTexSvxGradientSquare::GeoTexSvxGradientSquare( const basegfx::B2DRange& rDefinitionRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, - sal_uInt32 nSteps, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY, double fAngle) - : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) { maGradientInfo = basegfx::utils::createSquareODFGradientInfo( rDefinitionRange, basegfx::B2DVector(fOffsetX,fOffsetY), - nSteps, + nRequestedSteps, fBorder, fAngle); } @@ -406,49 +618,100 @@ namespace drawinglayer::texture } void GeoTexSvxGradientSquare::appendTransformationsAndColors( - std::vector< B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) { - rOuterColor = maStart; + // no color at all, done + if (mnColorStops.empty()) + return; - if(!maGradientInfo.getSteps()) + // only one color, done + if (mnColorStops.size() < 2) return; - const double fStepSize(1.0 / maGradientInfo.getSteps()); - B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + // check if we need last-ColorStop-correction + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); - for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + if (bPenultimateUsed) { - const double fSize(1.0 - (fStepSize * a)); - aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fSize, fSize); - aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); - rEntries.push_back(aB2DHomMatrixAndBColor); + // temporarily add a ColorStop entry + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); + } + + // outer loop over ColorStops, each is from cs_l to cs_r + for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++) + { + // get offsets + const double fOffsetStart(cs_l->getStopOffset()); + const double fOffsetEnd(cs_r->getStopOffset()); + + // same offset, empty BColorStopRange, continue with next step + if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) + continue; + + // get colors & calculate steps + const basegfx::BColor aCStart(cs_l->getStopColor()); + const basegfx::BColor aCEnd(cs_r->getStopColor()); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + maGradientInfo.getRequestedSteps(), aCStart, aCEnd)); + + // calculate StripeWidth + const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps); + + // get correct start for inner loop (see above) + const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0); + + for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++) + { + // calculate size/radius + const double fSize(1.0 - (fOffsetStart + (fStripeWidth * innerLoop))); + + // set and add at target + aCallback( + maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fSize, fSize), + 1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1)))); + } + } + + if (bPenultimateUsed) + { + // correct temporary change + mnColorStops.pop_back(); } } void GeoTexSvxGradientSquare::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const { - const double fScaler(basegfx::utils::getSquareGradientAlpha(rUV, maGradientInfo)); + // no color at all, done + if (mnColorStops.empty()) + return; + + // just single color, done + if (mnColorStops.size() < 2) + { + rBColor = mnColorStops.front().getStopColor(); + return; + } - rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + // texture-back-transform X/Y -> t [0.0..1.0] and determine color + const double fScaler(basegfx::utils::getSquareGradientAlpha(rUV, maGradientInfo)); + rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange); } GeoTexSvxGradientRect::GeoTexSvxGradientRect( const basegfx::B2DRange& rDefinitionRange, - const basegfx::BColor& rStart, - const basegfx::BColor& rEnd, - sal_uInt32 nSteps, + sal_uInt32 nRequestedSteps, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY, double fAngle) - : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) { maGradientInfo = basegfx::utils::createRectangularODFGradientInfo( rDefinitionRange, basegfx::B2DVector(fOffsetX,fOffsetY), - nSteps, + nRequestedSteps, fBorder, fAngle); } @@ -458,49 +721,90 @@ namespace drawinglayer::texture } void GeoTexSvxGradientRect::appendTransformationsAndColors( - std::vector< B2DHomMatrixAndBColor >& rEntries, - basegfx::BColor& rOuterColor) + std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) { - rOuterColor = maStart; + // no color at all, done + if (mnColorStops.empty()) + return; - if(!maGradientInfo.getSteps()) + // only one color, done + if (mnColorStops.size() < 2) return; - double fWidth(1.0); - double fHeight(1.0); - double fIncrementX(0.0); - double fIncrementY(0.0); + // check if we need last-ColorStop-correction + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); - if(maGradientInfo.getAspectRatio() > 1.0) - { - fIncrementY = fHeight / maGradientInfo.getSteps(); - fIncrementX = fIncrementY / maGradientInfo.getAspectRatio(); - } - else + if (bPenultimateUsed) { - fIncrementX = fWidth / maGradientInfo.getSteps(); - fIncrementY = fIncrementX * maGradientInfo.getAspectRatio(); + // temporarily add a ColorStop entry + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); } - B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + // prepare vars dependent on aspect ratio + const double fAR(maGradientInfo.getAspectRatio()); + const bool bMTO(fAR > 1.0); - for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + // outer loop over ColorStops, each is from cs_l to cs_r + for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++) { - // next step - fWidth -= fIncrementX; - fHeight -= fIncrementY; + // get offsets + const double fOffsetStart(cs_l->getStopOffset()); + const double fOffsetEnd(cs_r->getStopOffset()); + + // same offset, empty BColorStopRange, continue with next step + if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) + continue; + + // get colors & calculate steps + const basegfx::BColor aCStart(cs_l->getStopColor()); + const basegfx::BColor aCEnd(cs_r->getStopColor()); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + maGradientInfo.getRequestedSteps(), aCStart, aCEnd)); + + // calculate StripeWidth + const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps); - aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fWidth, fHeight); - aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); - rEntries.push_back(aB2DHomMatrixAndBColor); + // get correct start for inner loop (see above) + const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0); + + for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++) + { + // calculate offset position for entry + const double fSize(fOffsetStart + (fStripeWidth * innerLoop)); + + // set and add at target + aCallback( + maGradientInfo.getTextureTransform() + * basegfx::utils::createScaleB2DHomMatrix( + 1.0 - (bMTO ? fSize / fAR : fSize), + 1.0 - (bMTO ? fSize : fSize * fAR)), + 1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1)))); + } + } + + if (bPenultimateUsed) + { + // correct temporary change + mnColorStops.pop_back(); } } void GeoTexSvxGradientRect::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const { - const double fScaler(basegfx::utils::getRectangularGradientAlpha(rUV, maGradientInfo)); + // no color at all, done + if (mnColorStops.empty()) + return; - rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + // just single color, done + if (mnColorStops.size() < 2) + { + rBColor = mnColorStops.front().getStopColor(); + return; + } + + // texture-back-transform X/Y -> t [0.0..1.0] and determine color + const double fScaler(basegfx::utils::getRectangularGradientAlpha(rUV, maGradientInfo)); + rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange); } @@ -627,8 +931,20 @@ namespace drawinglayer::texture double GeoTexSvxHatch::getDistanceToHatch(const basegfx::B2DPoint& rUV) const { - const basegfx::B2DPoint aCoor(getBackTextureTransform() * rUV); - return fmod(aCoor.getY(), mfDistance); + // the below is an inlined and optimised version of + // const basegfx::B2DPoint aCoor(getBackTextureTransform() * rUV); + // return fmod(aCoor.getY(), mfDistance); + + const basegfx::B2DHomMatrix& rMat = getBackTextureTransform(); + double fX = rUV.getX(); + double fY = rUV.getY(); + + double fTempY( + rMat.get(1, 0) * fX + + rMat.get(1, 1) * fY + + rMat.get(1, 2)); + + return fmod(fTempY, mfDistance); } const basegfx::B2DHomMatrix& GeoTexSvxHatch::getBackTextureTransform() const @@ -708,7 +1024,7 @@ namespace drawinglayer::texture sal_Int32 nPosX(0); sal_Int32 nPosY(0); - if(basegfx::fTools::more(fStartX, 0.0)) + if(fStartX > 0.0) { const sal_Int32 nDiff(static_cast<sal_Int32>(floor(fStartX / fWidth)) + 1); @@ -716,7 +1032,7 @@ namespace drawinglayer::texture fStartX -= nDiff * fWidth; } - if(basegfx::fTools::less(fStartX + fWidth, 0.0)) + if((fStartX + fWidth) < 0.0) { const sal_Int32 nDiff(static_cast<sal_Int32>(floor(-fStartX / fWidth))); @@ -724,7 +1040,7 @@ namespace drawinglayer::texture fStartX += nDiff * fWidth; } - if(basegfx::fTools::more(fStartY, 0.0)) + if(fStartY > 0.0) { const sal_Int32 nDiff(static_cast<sal_Int32>(floor(fStartY / fHeight)) + 1); @@ -732,7 +1048,7 @@ namespace drawinglayer::texture fStartY -= nDiff * fHeight; } - if(basegfx::fTools::less(fStartY + fHeight, 0.0)) + if((fStartY + fHeight) < 0.0) { const sal_Int32 nDiff(static_cast<sal_Int32>(floor(-fStartY / fHeight))); diff --git a/drawinglayer/source/texture/texture3d.cxx b/drawinglayer/source/texture/texture3d.cxx index 0a83937991d2..4cbcab9d1c5d 100644 --- a/drawinglayer/source/texture/texture3d.cxx +++ b/drawinglayer/source/texture/texture3d.cxx @@ -26,6 +26,7 @@ #include <vcl/BitmapTools.hxx> #include <primitive3d/hatchtextureprimitive3d.hxx> #include <sal/log.hxx> +#include <osl/diagnose.h> namespace drawinglayer::texture { @@ -74,12 +75,12 @@ namespace drawinglayer::texture if(mbIsAlpha) { - maTransparence = rBitmapEx.GetAlpha().GetBitmap(); - mpReadTransparence = Bitmap::ScopedReadAccess(maTransparence); + maTransparence = rBitmapEx.GetAlphaMask().GetBitmap(); + mpReadTransparence = maTransparence; } if (!maBitmap.IsEmpty()) - mpReadBitmap = Bitmap::ScopedReadAccess(maBitmap); + mpReadBitmap = maBitmap; SAL_WARN_IF(!mpReadBitmap, "drawinglayer", "GeoTexSvxBitmapEx: Got no read access to Bitmap"); if (mpReadBitmap) { @@ -102,7 +103,7 @@ namespace drawinglayer::texture { } - sal_uInt8 GeoTexSvxBitmapEx::impGetTransparence(sal_Int32 rX, sal_Int32 rY) const + sal_uInt8 GeoTexSvxBitmapEx::impGetAlpha(sal_Int32 rX, sal_Int32 rY) const { if(mbIsAlpha) { @@ -147,10 +148,10 @@ namespace drawinglayer::texture if(mbIsAlpha) { - // when we have a transparence, make use of it - const sal_uInt8 aLuminance(impGetTransparence(nX, nY)); + // when we have alpha, make use of it + const sal_uInt8 aAlpha(impGetAlpha(nX, nY)); - rfOpacity = (static_cast<double>(0xff - aLuminance) * (1.0 / 255.0)); + rfOpacity = (static_cast<double>(aAlpha) * (1.0 / 255.0)); } else { @@ -172,8 +173,8 @@ namespace drawinglayer::texture if(mbIsAlpha) { // this texture has an alpha part, use it - const sal_uInt8 aLuminance(impGetTransparence(nX, nY)); - const double fNewOpacity(static_cast<double>(0xff - aLuminance) * (1.0 / 255.0)); + const sal_uInt8 aAlpha(impGetAlpha(nX, nY)); + const double fNewOpacity(static_cast<double>(aAlpha) * (1.0 / 255.0)); rfOpacity = 1.0 - ((1.0 - fNewOpacity) * (1.0 - rfOpacity)); } diff --git a/drawinglayer/source/tools/converters.cxx b/drawinglayer/source/tools/converters.cxx index 382b81197526..cf339f8aaa20 100644 --- a/drawinglayer/source/tools/converters.cxx +++ b/drawinglayer/source/tools/converters.cxx @@ -23,153 +23,360 @@ #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/processor2d/baseprocessor2d.hxx> #include <drawinglayer/processor2d/processor2dtools.hxx> +#include <vcl/svapp.hxx> #include <vcl/virdev.hxx> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <comphelper/diagnose_ex.hxx> #include <drawinglayer/converters.hxx> #ifdef DBG_UTIL #include <tools/stream.hxx> -#include <vcl/pngwrite.hxx> +// #include <vcl/filter/PngImageWriter.hxx> +#include <vcl/dibtools.hxx> #endif +// #include <vcl/BitmapReadAccess.hxx> + +namespace +{ +bool implPrepareConversion(drawinglayer::primitive2d::Primitive2DContainer& rSequence, + sal_uInt32& rnDiscreteWidth, sal_uInt32& rnDiscreteHeight, + const sal_uInt32 nMaxSquarePixels) +{ + if (rSequence.empty()) + return false; + + if (rnDiscreteWidth <= 0 || rnDiscreteHeight <= 0) + return false; + + const sal_uInt32 nViewVisibleArea(rnDiscreteWidth * rnDiscreteHeight); + + if (nViewVisibleArea > nMaxSquarePixels) + { + // reduce render size + double fReduceFactor + = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); + rnDiscreteWidth = basegfx::fround(static_cast<double>(rnDiscreteWidth) * fReduceFactor); + rnDiscreteHeight = basegfx::fround(static_cast<double>(rnDiscreteHeight) * fReduceFactor); + + const drawinglayer::primitive2d::Primitive2DReference aEmbed( + new drawinglayer::primitive2d::TransformPrimitive2D( + basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor), + std::move(rSequence))); + + rSequence = drawinglayer::primitive2d::Primitive2DContainer{ aEmbed }; + } + + return true; +} + +AlphaMask implcreateAlphaMask(drawinglayer::primitive2d::Primitive2DContainer& rSequence, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, + const Size& rSizePixel, bool bUseLuminance) +{ + ScopedVclPtrInstance<VirtualDevice> pContent; + + // prepare vdev + if (!pContent->SetOutputSizePixel(rSizePixel, false)) + { + SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << rSizePixel.Width() << "x" + << rSizePixel.Height()); + return AlphaMask(); + } + + // create pixel processor, also already takes care of AAing and + // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If + // not wanted, change after this call as needed + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pContentProcessor + = drawinglayer::processor2d::createPixelProcessor2DFromOutputDevice(*pContent, + rViewInformation2D); + + // prepare for mask creation + pContent->SetMapMode(MapMode(MapUnit::MapPixel)); + + // set transparency to all white (fully transparent) + pContent->Erase(); + + basegfx::BColorModifierSharedPtr aBColorModifier; + if (bUseLuminance) + { + // new mode: bUseLuminance allows simple creation of alpha channels + // for any content (e.g. gradients) + aBColorModifier = std::make_shared<basegfx::BColorModifier_luminance_to_alpha>(); + } + else + { + // Embed primitives to paint them black (fully opaque) + aBColorModifier + = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0.0, 0.0, 0.0)); + } + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::ModifiedColorPrimitive2D(std::move(rSequence), + aBColorModifier)); + const drawinglayer::primitive2d::Primitive2DContainer xSeq{ xRef }; + + // render + pContentProcessor->process(xSeq); + pContentProcessor.reset(); + + // get alpha channel from vdev + pContent->EnableMapMode(false); + const Point aEmptyPoint; + + // Convert from transparency->alpha. + // FIXME in theory I should be able to directly construct alpha by using black as background + // and white as foreground, but that doesn't work for some reason. + Bitmap aContentBitmap = pContent->GetBitmap(aEmptyPoint, rSizePixel); + aContentBitmap.Invert(); + + return AlphaMask(aContentBitmap); +} +} + namespace drawinglayer { +AlphaMask createAlphaMask(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, + sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, + sal_uInt32 nMaxSquarePixels, bool bUseLuminance) +{ + drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); - BitmapEx convertToBitmapEx( - drawinglayer::primitive2d::Primitive2DContainer&& rSeq, - const geometry::ViewInformation2D& rViewInformation2D, - sal_uInt32 nDiscreteWidth, - sal_uInt32 nDiscreteHeight, - sal_uInt32 nMaxSquarePixels) + if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels)) { - BitmapEx aRetval; + return AlphaMask(); + } - if(!rSeq.empty() && nDiscreteWidth && nDiscreteHeight) - { - // get destination size in pixels - const MapMode aMapModePixel(MapUnit::MapPixel); - const sal_uInt32 nViewVisibleArea(nDiscreteWidth * nDiscreteHeight); - drawinglayer::primitive2d::Primitive2DContainer aSequence; - - if(nViewVisibleArea > nMaxSquarePixels) - { - // reduce render size - double fReduceFactor = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); - nDiscreteWidth = basegfx::fround(static_cast<double>(nDiscreteWidth) * fReduceFactor); - nDiscreteHeight = basegfx::fround(static_cast<double>(nDiscreteHeight) * fReduceFactor); - - const drawinglayer::primitive2d::Primitive2DReference aEmbed( - new drawinglayer::primitive2d::TransformPrimitive2D( - basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor), - std::move(rSeq))); - - aSequence = drawinglayer::primitive2d::Primitive2DContainer { aEmbed }; - } - else - aSequence = std::move(rSeq); - - const Point aEmptyPoint; - const Size aSizePixel(nDiscreteWidth, nDiscreteHeight); - ScopedVclPtrInstance< VirtualDevice > pContent; - - // prepare vdev - pContent->SetOutputSizePixel(aSizePixel, false); - pContent->SetMapMode(aMapModePixel); - - // set to all white - pContent->SetBackground(Wallpaper(COL_WHITE)); - pContent->Erase(); - - // create pixel processor, also already takes care of AAing and - // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If - // not wanted, change after this call as needed - std::unique_ptr<processor2d::BaseProcessor2D> pContentProcessor = processor2d::createPixelProcessor2DFromOutputDevice( - *pContent, - rViewInformation2D); + const Size aSizePixel(nDiscreteWidth, nDiscreteHeight); -#ifdef DBG_UTIL - static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore -#endif - // render content - pContentProcessor->process(aSequence); + return implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel, bUseLuminance); +} - // get content - pContent->EnableMapMode(false); - const Bitmap aContent(pContent->GetBitmap(aEmptyPoint, aSizePixel)); +BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, + sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, + sal_uInt32 nMaxSquarePixels, bool bForceAlphaMaskCreation) +{ + drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); + + if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels)) + { + return BitmapEx(); + } + + const Point aEmptyPoint; + const Size aSizePixel(nDiscreteWidth, nDiscreteHeight); + + // Create target VirtualDevice. Go back to using a simple RGB + // target version (compared with former version, see history). + // Reasons are manyfold: + // - Avoid the RGBA mode for VirtualDevice (two VDevs) + // - It's not suggested to be used outside presentation engine + // - It only works *by chance* with VCLPrimitiveRenderer + // - Usage of two-VDev alpha-VDev avoided alpha blending against + // COL_WHITE in the 1st layer of targets (not in buffers below) + // but is kind of a 'hack' doing so + // - Other renderers (system-dependent PrimitiveRenderers, other + // than the VCL-based ones) will probably not support splitted + // VDevs for content/alpha, so require a method that works with + // RGB targeting (for now) + // - Less resource usage, better speed (no 2 VDevs, no merge of + // AlphaChannels) + // As long as not all our mechanisms are changed to RGBA completely, + // mixing these is just too dangerous and expensive and may to wrong + // or deliver bad quality results. + // Nonetheless we need a RGBA result here. Luckily we are able to + // create a complete and valid AlphaChannel using 'createAlphaMask' + // above. + // When we know the content (RGB result from renderer), alpha + // (result from createAlphaMask) and the start condition (content + // rendered against COL_WHITE), it is possible to calculate back + // the content, quasi 'remove' that initial blending against + // COL_WHITE. + // That is what the helper Bitmap::RemoveBlendedStartColor does. + // Luckily we only need it for this 'convertToBitmapEx', not in + // any other rendering. It could be further optimized, too. + // This gives good results, it is in principle comparable with + // the results using pre-multiplied alpha tooling, also reducing + // the range of values where high alpha values are used. + ScopedVclPtrInstance<VirtualDevice> pContent(*Application::GetDefaultDevice()); + + // prepare vdev + if (!pContent->SetOutputSizePixel(aSizePixel, false)) + { + SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << aSizePixel.Width() << "x" + << aSizePixel.Height()); + return BitmapEx(); + } + + // We map to pixel, use that MapMode. Init by erasing. + pContent->SetMapMode(MapMode(MapUnit::MapPixel)); + pContent->Erase(); + + // create pixel processor, also already takes care of AAing and + // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If + // not wanted, change after this call as needed + std::unique_ptr<processor2d::BaseProcessor2D> pContentProcessor + = processor2d::createPixelProcessor2DFromOutputDevice(*pContent, rViewInformation2D); + + // render content + pContentProcessor->process(aSequence); + + // create final BitmapEx result (content) + Bitmap aRetval(pContent->GetBitmap(aEmptyPoint, aSizePixel)); #ifdef DBG_UTIL - if(bDoSaveForVisualControl) - { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\test_content.png" -#else - "~/test_content.png" -#endif - , StreamMode::WRITE|StreamMode::TRUNC); - BitmapEx aContentEx(aContent); - vcl::PNGWriter aPNGWriter(aContentEx); - aPNGWriter.Write(aNew); - } -#endif - // prepare for mask creation - pContent->SetMapMode(aMapModePixel); - - // set alpha to all white (fully transparent) - pContent->Erase(); - - // embed primitives to paint them black - const primitive2d::Primitive2DReference xRef( - new primitive2d::ModifiedColorPrimitive2D( - std::move(aSequence), - std::make_shared<basegfx::BColorModifier_replace>( - basegfx::BColor(0.0, 0.0, 0.0)))); - const primitive2d::Primitive2DContainer xSeq { xRef }; - - // render - pContentProcessor->process(xSeq); - pContentProcessor.reset(); - - // get alpha channel from vdev - pContent->EnableMapMode(false); - const Bitmap aAlpha(pContent->GetBitmap(aEmptyPoint, aSizePixel)); -#ifdef DBG_UTIL - if(bDoSaveForVisualControl) - { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\test_alpha.png" -#else - "~/test_alpha.png" -#endif - , StreamMode::WRITE|StreamMode::TRUNC); - BitmapEx aAlphaEx(aAlpha); - vcl::PNGWriter aPNGWriter(aAlphaEx); - aPNGWriter.Write(aNew); - } + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_content.bmp", + StreamMode::WRITE | StreamMode::TRUNC); + WriteDIB(aRetval, aNew, false, true); + } + } #endif - // create BitmapEx result - aRetval = BitmapEx(aContent, AlphaMask(aAlpha)); + // Create the AlphaMask using a method that does this always correct (also used + // now in GlowPrimitive2D and ShadowPrimitive2D which both only need the + // AlphaMask to do their job, so speeding that up, too). + AlphaMask aAlpha(implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel, false)); + #ifdef DBG_UTIL - if(bDoSaveForVisualControl) - { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\test_combined.png" -#else - "~/test_combined.png" -#endif - , StreamMode::WRITE|StreamMode::TRUNC); - vcl::PNGWriter aPNGWriter(aRetval); - aPNGWriter.Write(aNew); - } + if (bDoSaveForVisualControl) + { + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_alpha.bmp", StreamMode::WRITE | StreamMode::TRUNC); + WriteDIB(aAlpha.GetBitmap(), aNew, false, true); + } + } #endif + + if (bForceAlphaMaskCreation || aAlpha.hasAlpha()) + { + // Need to correct content using known alpha to get to background-free + // RGBA result, usable e.g. in PNG export(s) or convert-to-bitmap. + // Now that vcl supports bitmaps with an alpha channel, only apply + // this correction to bitmaps without an alpha channel. + if (pContent->GetBitCount() < 32) + { + aRetval.RemoveBlendedStartColor(COL_BLACK, aAlpha); + } + else + { + // tdf#157558 invert and remove blended white color + // Before commit 81994cb2b8b32453a92bcb011830fcb884f22ff3, + // RemoveBlendedStartColor(COL_BLACK, aAlpha) would darken + // the bitmap when running a slideshow, printing, or exporting + // to PDF. To get the same effect, the alpha mask must be + // inverted, RemoveBlendedStartColor(COL_WHITE, aAlpha) + // called, and the alpha mask uninverted. + aAlpha.Invert(); + aRetval.RemoveBlendedStartColor(COL_WHITE, aAlpha); + aAlpha.Invert(); } + // return combined result + return BitmapEx(aRetval, aAlpha); + } + else + return BitmapEx(aRetval); +} + +BitmapEx convertPrimitive2DContainerToBitmapEx(primitive2d::Primitive2DContainer&& rSequence, + const basegfx::B2DRange& rTargetRange, + sal_uInt32 nMaximumQuadraticPixels, + const o3tl::Length eTargetUnit, + const std::optional<Size>& rTargetDPI) +{ + if (rSequence.empty()) + return BitmapEx(); - return aRetval; + try + { + css::geometry::RealRectangle2D aRealRect; + aRealRect.X1 = rTargetRange.getMinX(); + aRealRect.Y1 = rTargetRange.getMinY(); + aRealRect.X2 = rTargetRange.getMaxX(); + aRealRect.Y2 = rTargetRange.getMaxY(); + + // get system DPI + Size aDPI( + Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch))); + if (rTargetDPI.has_value()) + { + aDPI = *rTargetDPI; + } + + ::sal_uInt32 DPI_X = aDPI.getWidth(); + ::sal_uInt32 DPI_Y = aDPI.getHeight(); + const basegfx::B2DRange aRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2); + const double fWidth(aRange.getWidth()); + const double fHeight(aRange.getHeight()); + + if (!(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0))) + return BitmapEx(); + + if (0 == DPI_X) + { + DPI_X = 75; + } + + if (0 == DPI_Y) + { + DPI_Y = 75; + } + + if (0 == nMaximumQuadraticPixels) + { + nMaximumQuadraticPixels = 500000; + } + + const auto aViewInformation2D = geometry::createViewInformation2D({}); + const sal_uInt32 nDiscreteWidth( + basegfx::fround(o3tl::convert(fWidth, eTargetUnit, o3tl::Length::in) * DPI_X)); + const sal_uInt32 nDiscreteHeight( + basegfx::fround(o3tl::convert(fHeight, eTargetUnit, o3tl::Length::in) * DPI_Y)); + + basegfx::B2DHomMatrix aEmbedding( + basegfx::utils::createTranslateB2DHomMatrix(-aRange.getMinX(), -aRange.getMinY())); + + aEmbedding.scale(nDiscreteWidth / fWidth, nDiscreteHeight / fHeight); + + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D(aEmbedding, std::move(rSequence))); + primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef }; + + BitmapEx aBitmapEx(convertToBitmapEx(std::move(xEmbedSeq), aViewInformation2D, + nDiscreteWidth, nDiscreteHeight, + nMaximumQuadraticPixels)); + + if (aBitmapEx.IsEmpty()) + return BitmapEx(); + aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + aBitmapEx.SetPrefSize(Size(basegfx::fround<tools::Long>(fWidth), basegfx::fround<tools::Long>(fHeight))); + + return aBitmapEx; + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!"); + } + catch (const std::exception& e) + { + SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what()); } + return BitmapEx(); +} } // end of namespace drawinglayer /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpbrush.cxx b/drawinglayer/source/tools/emfpbrush.cxx index 4acc311345a8..c79b0ded0748 100644 --- a/drawinglayer/source/tools/emfpbrush.cxx +++ b/drawinglayer/source/tools/emfpbrush.cxx @@ -113,17 +113,12 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tCenter color: 0x" << std::hex << color << std::dec); s.ReadFloat(firstPointX).ReadFloat(firstPointY); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tCenter point: " << firstPointX << "," << firstPointY); - s.ReadInt32(surroundColorsNumber); + s.ReadUInt32(surroundColorsNumber); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t number of surround colors: " << surroundColorsNumber); - if (surroundColorsNumber<0 || o3tl::make_unsigned(surroundColorsNumber)>SAL_MAX_INT32 / sizeof(::Color)) - { - surroundColorsNumber = SAL_MAX_INT32 / sizeof(::Color); - } - surroundColors.reset( new ::Color[surroundColorsNumber] ); - for (int i = 0; i < surroundColorsNumber; i++) + for (sal_uInt32 i = 0; i < surroundColorsNumber; i++) { s.ReadUInt32(color); surroundColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); @@ -132,7 +127,7 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tSurround color[" << i << "]: 0x" << std::hex << color << std::dec); } - if (additionalFlags & 0x01) + if (additionalFlags & 0x01) // BrushDataPath { sal_Int32 pathLength; @@ -180,60 +175,53 @@ namespace emfplushelper << aBounds.getWidth() << "x" << aBounds.getHeight()); } - if (additionalFlags & 0x02) + if (additionalFlags & 0x02) // BrushDataTransform { EmfPlusHelperData::readXForm(s, brush_transformation); hasTransformation = true; SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse brush transformation: " << brush_transformation); } - if (additionalFlags & 0x08) + // BrushDataPresetColors and BrushDataBlendFactorsH + if ((additionalFlags & 0x04) && (additionalFlags & 0x08)) { - s.ReadInt32(blendPoints); + SAL_WARN("drawinglayer.emf", "EMF+\t Brush must not contain both BrushDataPresetColors and BrushDataBlendFactorsH"); + return; + } + if (additionalFlags & 0x08) // BrushDataBlendFactorsH + { + s.ReadUInt32(blendPoints); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tuse blend, points: " << blendPoints); - if (blendPoints<0 || o3tl::make_unsigned(blendPoints)>SAL_MAX_INT32 / (2 * sizeof(float))) - blendPoints = SAL_MAX_INT32 / (2 * sizeof(float)); blendPositions.reset( new float[2 * blendPoints] ); blendFactors = blendPositions.get() + blendPoints; - for (int i = 0; i < blendPoints; i++) + for (sal_uInt32 i = 0; i < blendPoints; i++) { s.ReadFloat(blendPositions[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tposition[" << i << "]: " << blendPositions[i]); } - for (int i = 0; i < blendPoints; i++) + for (sal_uInt32 i = 0; i < blendPoints; i++) { s.ReadFloat(blendFactors[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tFactor[" << i << "]: " << blendFactors[i]); } } - if (additionalFlags & 0x04) + if (additionalFlags & 0x04) // BrushDataPresetColors { - s.ReadInt32(colorblendPoints); + s.ReadUInt32(colorblendPoints); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse color blend, points: " << colorblendPoints); - - if (colorblendPoints<0 || o3tl::make_unsigned(colorblendPoints)>SAL_MAX_INT32 / sizeof(float)) - { - colorblendPoints = SAL_MAX_INT32 / sizeof(float); - } - - if (o3tl::make_unsigned(colorblendPoints) > SAL_MAX_INT32 / sizeof(::Color)) - { - colorblendPoints = SAL_MAX_INT32 / sizeof(::Color); - } - colorblendPositions.reset( new float[colorblendPoints] ); colorblendColors.reset( new ::Color[colorblendPoints] ); - for (int i = 0; i < colorblendPoints; i++) + for (sal_uInt32 i = 0; i < colorblendPoints; i++) { s.ReadFloat(colorblendPositions[i]); SAL_INFO("drawinglayer.emf", "EMF+\tposition[" << i << "]: " << colorblendPositions[i]); } - for (int i = 0; i < colorblendPoints; i++) + for (sal_uInt32 i = 0; i < colorblendPoints; i++) { s.ReadUInt32(color); colorblendColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); @@ -262,29 +250,33 @@ namespace emfplushelper s.ReadUInt32(color); s.ReadUInt32(color); - if (additionalFlags & 0x02) + if (additionalFlags & 0x02) //BrushDataTransform { EmfPlusHelperData::readXForm(s, brush_transformation); hasTransformation = true; SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse brush transformation: " << brush_transformation); } + // BrushDataPresetColors and BrushDataBlendFactorsH + if ((additionalFlags & 0x04) && (additionalFlags & 0x08)) + { + SAL_WARN("drawinglayer.emf", "EMF+\t Brush must not contain both BrushDataPresetColors and BrushDataBlendFactorsH"); + return; + } - if (additionalFlags & 0x08) + if (additionalFlags & 0x08) // BrushDataBlendFactorsH { - s.ReadInt32(blendPoints); + s.ReadUInt32(blendPoints); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse blend, points: " << blendPoints); - if (blendPoints<0 || o3tl::make_unsigned(blendPoints)>SAL_MAX_INT32 / (2 * sizeof(float))) - blendPoints = SAL_MAX_INT32 / (2 * sizeof(float)); blendPositions.reset( new float[2 * blendPoints] ); blendFactors = blendPositions.get() + blendPoints; - for (int i = 0; i < blendPoints; i++) + for (sal_uInt32 i = 0; i < blendPoints; i++) { s.ReadFloat(blendPositions[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPosition[" << i << "]: " << blendPositions[i]); } - for (int i = 0; i < blendPoints; i++) + for (sal_uInt32 i = 0; i < blendPoints; i++) { s.ReadFloat(blendFactors[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tFactor[" << i << "]: " << blendFactors[i]); @@ -293,29 +285,18 @@ namespace emfplushelper if (additionalFlags & 0x04) { - s.ReadInt32(colorblendPoints); + s.ReadUInt32(colorblendPoints); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse color blend, points: " << colorblendPoints); - - if (colorblendPoints<0 || o3tl::make_unsigned(colorblendPoints)>SAL_MAX_INT32 / sizeof(float)) - { - colorblendPoints = SAL_MAX_INT32 / sizeof(float); - } - - if (o3tl::make_unsigned(colorblendPoints) > SAL_MAX_INT32 / sizeof(::Color)) - { - colorblendPoints = sal_uInt32(SAL_MAX_INT32) / sizeof(::Color); - } - colorblendPositions.reset( new float[colorblendPoints] ); colorblendColors.reset( new ::Color[colorblendPoints] ); - for (int i = 0; i < colorblendPoints; i++) + for (sal_uInt32 i = 0; i < colorblendPoints; i++) { s.ReadFloat(colorblendPositions[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPosition[" << i << "]: " << colorblendPositions[i]); } - for (int i = 0; i < colorblendPoints; i++) + for (sal_uInt32 i = 0; i < colorblendPoints; i++) { s.ReadUInt32(color); colorblendColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); diff --git a/drawinglayer/source/tools/emfpbrush.hxx b/drawinglayer/source/tools/emfpbrush.hxx index ec4ab11732f2..aee3fe02f60e 100644 --- a/drawinglayer/source/tools/emfpbrush.hxx +++ b/drawinglayer/source/tools/emfpbrush.hxx @@ -90,7 +90,7 @@ namespace emfplushelper BrushTypeLinearGradient = 0x00000004 }; - struct EMFPPath; + class EMFPPath; struct EMFPBrush : public EMFPObject { @@ -104,13 +104,13 @@ namespace emfplushelper ::Color secondColor; // first color is stored in solidColor; basegfx::B2DHomMatrix brush_transformation; bool hasTransformation; - sal_Int32 blendPoints; + sal_uInt32 blendPoints; std::unique_ptr<float[]> blendPositions; float* blendFactors; - sal_Int32 colorblendPoints; + sal_uInt32 colorblendPoints; std::unique_ptr<float[]> colorblendPositions; std::unique_ptr<::Color[]> colorblendColors; - sal_Int32 surroundColorsNumber; + sal_uInt32 surroundColorsNumber; std::unique_ptr<::Color[]> surroundColors; std::unique_ptr<EMFPPath> path; EmfPlusHatchStyle hatchStyle; diff --git a/drawinglayer/source/tools/emfpcustomlinecap.cxx b/drawinglayer/source/tools/emfpcustomlinecap.cxx index e24cbcc32cb1..e457a36cc2c4 100644 --- a/drawinglayer/source/tools/emfpcustomlinecap.cxx +++ b/drawinglayer/source/tools/emfpcustomlinecap.cxx @@ -21,6 +21,7 @@ #include "emfpcustomlinecap.hxx" #include "emfppath.hxx" #include "emfppen.hxx" +#include <basegfx/matrix/b2dhommatrixtools.hxx> using namespace ::com::sun::star; using namespace ::basegfx; @@ -39,19 +40,11 @@ namespace emfplushelper , strokeEndCap(0) , strokeJoin(0) , miterLimit(0.0) + , widthScale(0.0) , mbIsFilled(false) { } - void EMFPCustomLineCap::SetAttributes(rendering::StrokeAttributes& aAttributes) - { - aAttributes.StartCapType = EMFPPen::lcl_convertStrokeCap(strokeStartCap); - aAttributes.EndCapType = EMFPPen::lcl_convertStrokeCap(strokeEndCap); - aAttributes.JoinType = EMFPPen::lcl_convertLineJoinType(strokeJoin); - - aAttributes.MiterLimit = miterLimit; - } - void EMFPCustomLineCap::ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill) { sal_Int32 pathLength; @@ -66,6 +59,8 @@ namespace emfplushelper EMFPPath path(pathPoints); path.Read(s, pathFlags); polygon = path.GetPolygon(rR, false); + // rotate polygon by 180 degrees + polygon.transform(basegfx::utils::createRotateB2DHomMatrix(M_PI)); mbIsFilled = bFill; } @@ -80,7 +75,6 @@ namespace emfplushelper { sal_uInt32 customLineCapDataFlags, baseCap; float baseInset; - float widthScale; float fillHotSpotX, fillHotSpotY, strokeHotSpotX, strokeHotSpotY; s.ReadUInt32(customLineCapDataFlags).ReadUInt32(baseCap).ReadFloat(baseInset) @@ -91,11 +85,6 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t\tcustomLineCapDataFlags: 0x" << std::hex << customLineCapDataFlags); SAL_INFO("drawinglayer.emf", "EMF+\t\tbaseCap: 0x" << std::hex << baseCap); SAL_INFO("drawinglayer.emf", "EMF+\t\tbaseInset: " << baseInset); - SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeStartCap: 0x" << std::hex << strokeStartCap); - SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeEndCap: 0x" << std::hex << strokeEndCap); - SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeJoin: 0x" << std::hex << strokeJoin); - SAL_INFO("drawinglayer.emf", "EMF+\t\tmiterLimit: " << miterLimit); - SAL_INFO("drawinglayer.emf", "EMF+\t\twidthScale: " << widthScale); if (customLineCapDataFlags & EmfPlusCustomLineCapDataFillPath) { @@ -112,16 +101,20 @@ namespace emfplushelper // TODO only reads the data, does not use them [I've had // no test document to be able to implement it] - sal_Int32 width, height, middleInset, fillState, lineStartCap; - sal_Int32 lineEndCap, lineJoin, widthScale; - float fillHotSpotX, fillHotSpotY, lineHotSpotX, lineHotSpotY; + sal_Int32 fillState; + float width, height, middleInset, unusedHotSpot; - s.ReadInt32(width).ReadInt32(height).ReadInt32(middleInset).ReadInt32(fillState).ReadInt32(lineStartCap) - .ReadInt32(lineEndCap).ReadInt32(lineJoin).ReadFloat(miterLimit).ReadInt32(widthScale) - .ReadFloat(fillHotSpotX).ReadFloat(fillHotSpotY).ReadFloat(lineHotSpotX).ReadFloat(lineHotSpotY); + s.ReadFloat(width).ReadFloat(height).ReadFloat(middleInset).ReadInt32(fillState).ReadUInt32(strokeStartCap) + .ReadUInt32(strokeEndCap).ReadUInt32(strokeJoin).ReadFloat(miterLimit).ReadFloat(widthScale) + .ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot); SAL_INFO("drawinglayer.emf", "EMF+\t\tTODO - actually read EmfPlusCustomLineCapArrowData object (section 2.2.2.12)"); } + SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeStartCap: 0x" << std::hex << strokeStartCap); + SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeEndCap: 0x" << std::hex << strokeEndCap); + SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeJoin: 0x" << std::hex << strokeJoin); + SAL_INFO("drawinglayer.emf", "EMF+\t\tmiterLimit: " << miterLimit); + SAL_INFO("drawinglayer.emf", "EMF+\t\twidthScale: " << widthScale); } } diff --git a/drawinglayer/source/tools/emfpcustomlinecap.hxx b/drawinglayer/source/tools/emfpcustomlinecap.hxx index a42e0ab4ef46..22ed6be6dd4d 100644 --- a/drawinglayer/source/tools/emfpcustomlinecap.hxx +++ b/drawinglayer/source/tools/emfpcustomlinecap.hxx @@ -19,7 +19,6 @@ #pragma once -#include <com/sun/star/rendering/StrokeAttributes.hpp> #include "emfphelperdata.hxx" namespace emfplushelper @@ -28,13 +27,12 @@ namespace emfplushelper { sal_uInt32 type; sal_uInt32 strokeStartCap, strokeEndCap, strokeJoin; - float miterLimit; + float miterLimit, widthScale; basegfx::B2DPolyPolygon polygon; bool mbIsFilled; EMFPCustomLineCap(); - void SetAttributes(com::sun::star::rendering::StrokeAttributes& aAttributes); void ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill); void Read(SvStream& s, EmfPlusHelperData const & rR); }; diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx index d2d082424493..1d95f0a8f38a 100644 --- a/drawinglayer/source/tools/emfphelperdata.cxx +++ b/drawinglayer/source/tools/emfphelperdata.cxx @@ -29,6 +29,7 @@ #include "emfpstringformat.hxx" #include <basegfx/curve/b2dcubicbezier.hxx> #include <wmfemfhelper.hxx> +#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> @@ -79,6 +80,9 @@ namespace emfplushelper case EmfPlusRecordTypeDrawRects: return "EmfPlusRecordTypeDrawRects"; case EmfPlusRecordTypeFillPolygon: return "EmfPlusRecordTypeFillPolygon"; case EmfPlusRecordTypeDrawLines: return "EmfPlusRecordTypeDrawLines"; + case EmfPlusRecordTypeFillClosedCurve: return "EmfPlusRecordTypeFillClosedCurve"; + case EmfPlusRecordTypeDrawClosedCurve: return "EmfPlusRecordTypeDrawClosedCurve"; + case EmfPlusRecordTypeDrawCurve: return "EmfPlusRecordTypeDrawCurve"; case EmfPlusRecordTypeFillEllipse: return "EmfPlusRecordTypeFillEllipse"; case EmfPlusRecordTypeDrawEllipse: return "EmfPlusRecordTypeDrawEllipse"; case EmfPlusRecordTypeFillPie: return "EmfPlusRecordTypeFillPie"; @@ -225,33 +229,33 @@ namespace emfplushelper { } - float EmfPlusHelperData::getUnitToPixelMultiplier(const UnitType aUnitType, const sal_uInt32 aDPI) + double EmfPlusHelperData::unitToPixel(double n, sal_uInt32 aUnitType, Direction d) { - switch (aUnitType) + switch (static_cast<UnitType>(aUnitType)) { case UnitTypePixel: - return 1.0f; + return n; case UnitTypePoint: - return aDPI / 72.0; + return o3tl::convert(n, o3tl::Length::pt, o3tl::Length::in) * DPI(d); case UnitTypeInch: - return aDPI; + return n * DPI(d); case UnitTypeMillimeter: - return aDPI / 25.4; + return o3tl::convert(n, o3tl::Length::mm, o3tl::Length::in) * DPI(d); case UnitTypeDocument: - return aDPI / 300.0; + return n * DPI(d) / 300.0; case UnitTypeWorld: case UnitTypeDisplay: SAL_WARN("drawinglayer.emf", "EMF+\t Converting to World/Display."); - return 1.0f; + return n; default: SAL_WARN("drawinglayer.emf", "EMF+\tTODO Unimplemented support of Unit Type: 0x" << std::hex << aUnitType); - return 1.0f; + return n; } } @@ -277,7 +281,7 @@ namespace emfplushelper EMFPPen *pen = new EMFPPen(); maEMFPObjects[index].reset(pen); pen->Read(rObjectStream, *this); - pen->penWidth = pen->penWidth * getUnitToPixelMultiplier(static_cast<UnitType>(pen->penUnit), mnHDPI); + pen->penWidth = unitToPixel(pen->penWidth, pen->penUnit, Direction::horizontal); break; } case EmfPlusObjectTypePath: @@ -321,7 +325,7 @@ namespace emfplushelper font->fontFlags = 0; font->Read(rObjectStream); // tdf#113624 Convert unit to Pixels - font->emSize = font->emSize * getUnitToPixelMultiplier(static_cast<UnitType>(font->sizeUnit), mnHDPI); + font->emSize = unitToPixel(font->emSize, font->sizeUnit, Direction::horizontal); break; } @@ -442,6 +446,10 @@ namespace emfplushelper maMapTransform *= basegfx::utils::createScaleTranslateB2DHomMatrix(100.0 * mnMmX / mnPixX, 100.0 * mnMmY / mnPixY, double(-mnFrameLeft), double(-mnFrameTop)); maMapTransform *= maBaseTransform; + + // Used only for performance optimization, to do not calculate it every line draw + mdExtractedXScale = std::hypot(maMapTransform.a(), maMapTransform.b()); + mdExtractedYScale = std::hypot(maMapTransform.c(), maMapTransform.d()); } ::basegfx::B2DPoint EmfPlusHelperData::Map(double ix, double iy) const @@ -459,7 +467,7 @@ namespace emfplushelper } else // we use a brush { - const EMFPBrush* brush = static_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get()); + const EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get()); if (brush) { color = brush->GetColor(); @@ -486,203 +494,175 @@ namespace emfplushelper map[ index ] = state; } - void EmfPlusHelperData::GraphicStatePop(GraphicStateMap& map, sal_Int32 index, wmfemfhelper::PropertyHolder& rState) + void EmfPlusHelperData::GraphicStatePop(GraphicStateMap& map, sal_Int32 index) { - GraphicStateMap::iterator iter = map.find( index ); + GraphicStateMap::iterator iter = map.find(index); - if ( iter != map.end() ) + if (iter != map.end()) { wmfemfhelper::PropertyHolder state = iter->second; maWorldTransform = state.getTransformation(); - rState.setClipPolyPolygon( state.getClipPolyPolygon() ); + if (state.getClipPolyPolygonActive()) + { + SAL_INFO("drawinglayer.emf", + "EMF+\t Restore clipping region to saved in index: " << index); + wmfemfhelper::HandleNewClipRegion(state.getClipPolyPolygon(), mrTargetHolders, + mrPropertyHolders); + } + else + { + SAL_INFO("drawinglayer.emf", "EMF+\t Disable clipping"); + wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders, + mrPropertyHolders); + } mappingChanged(); - SAL_INFO("drawinglayer.emf", "EMF+\t\tStack index: " << index << " found, maWorldTransform: " << maWorldTransform); + SAL_INFO("drawinglayer.emf", + "EMF+\t\tStack index: " << index + << " found, maWorldTransform: " << maWorldTransform); } } - void EmfPlusHelperData::EMFPPlusDrawPolygon(const ::basegfx::B2DPolyPolygon& polygon, sal_uInt32 penIndex) + drawinglayer::attribute::LineStartEndAttribute + EmfPlusHelperData::CreateLineEnd(const sal_Int32 aCap, const float aPenWidth) const { - const EMFPPen* pen = dynamic_cast<EMFPPen*>(maEMFPObjects[penIndex & 0xff].get()); - SAL_WARN_IF(!pen, "drawinglayer.emf", "emf+ missing pen"); - - if (!(pen && polygon.count())) - return; - - // we need a line join attribute - basegfx::B2DLineJoin lineJoin = basegfx::B2DLineJoin::Round; - if (pen->penDataFlags & EmfPlusPenDataJoin) // additional line join information + const double pw = mdExtractedYScale * aPenWidth; + if (aCap == LineCapTypeSquare) { - lineJoin = static_cast<basegfx::B2DLineJoin>(EMFPPen::lcl_convertLineJoinType(pen->lineJoin)); + basegfx::B2DPolygon aCapPolygon( + { {-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} }); + aCapPolygon.setClosed(true); + return drawinglayer::attribute::LineStartEndAttribute( + pw, basegfx::B2DPolyPolygon(aCapPolygon), true); } - - // we need a line cap attribute - css::drawing::LineCap lineCap = css::drawing::LineCap_BUTT; - if (pen->penDataFlags & EmfPlusPenDataStartCap) // additional line cap information + else if (aCap == LineCapTypeRound) { - lineCap = static_cast<css::drawing::LineCap>(EMFPPen::lcl_convertStrokeCap(pen->startCap)); - SAL_WARN_IF(pen->startCap != pen->endCap, "drawinglayer.emf", "emf+ pen uses different start and end cap"); + basegfx::B2DPolygon aCapPolygon( + { {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.9236, -0.3827}, + {0.7071, -0.7071}, {0.3827, -0.9236}, {0.0, -1.0}, {-0.3827, -0.9236}, + {-0.7071, -0.7071}, {-0.9236, -0.3827}, {-1.0, 0.0} }); + aCapPolygon.setClosed(true); + return drawinglayer::attribute::LineStartEndAttribute( + pw, basegfx::B2DPolyPolygon(aCapPolygon), true); } - - const double transformedPenWidth = maMapTransform.get(0, 0) * pen->penWidth; - drawinglayer::attribute::LineAttribute lineAttribute(pen->GetColor().getBColor(), - transformedPenWidth, - lineJoin, - lineCap); - - drawinglayer::attribute::StrokeAttribute aStrokeAttribute; - if (pen->penDataFlags & EmfPlusPenDataLineStyle && pen->dashStyle != EmfPlusLineStyleCustom) // pen has a predefined line style + else if (aCap == LineCapTypeTriangle) { - // short writing - const double pw = maMapTransform.get(1, 1) * pen->penWidth; - // taken from the old cppcanvas implementation and multiplied with pen width - const std::vector<double> dash = { 3*pw, 3*pw }; - const std::vector<double> dot = { pw, 3*pw }; - const std::vector<double> dashdot = { 3*pw, 3*pw, pw, 3*pw }; - const std::vector<double> dashdotdot = { 3*pw, 3*pw, pw, 3*pw, pw, 3*pw }; - - switch (pen->dashStyle) - { - case EmfPlusLineStyleSolid: // do nothing special, use default stroke attribute - break; - case EmfPlusLineStyleDash: - aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dash)); - break; - case EmfPlusLineStyleDot: - aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dot)); - break; - case EmfPlusLineStyleDashDot: - aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dashdot)); - break; - case EmfPlusLineStyleDashDotDot: - aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dashdotdot)); - break; - } + basegfx::B2DPolygon aCapPolygon( + { {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.0, -1.0}, {-1.0, 0.0} }); + aCapPolygon.setClosed(true); + return drawinglayer::attribute::LineStartEndAttribute( + pw, basegfx::B2DPolyPolygon(aCapPolygon), true); } - else if (pen->penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line + else if (aCap == LineCapTypeSquareAnchor) { - // StrokeAttribute needs a double vector while the pen provides a float vector - std::vector<double> aPattern(pen->dashPattern.size()); - for (size_t i=0; i<aPattern.size(); i++) - { - // convert from float to double and multiply with the adjusted pen width - aPattern[i] = maMapTransform.get(1, 1) * pen->penWidth * pen->dashPattern[i]; - } - aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::move(aPattern)); + basegfx::B2DPolygon aCapPolygon( + { {-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} }); + aCapPolygon.setClosed(true); + return drawinglayer::attribute::LineStartEndAttribute( + 1.5 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true); } - - if (!pen->GetColor().IsTransparent()) + else if (aCap == LineCapTypeRoundAnchor) { - mrTargetHolders.Current().append( - new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( - polygon, - lineAttribute, - aStrokeAttribute)); + const basegfx::B2DPolygon aCapPolygon + = ::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(0.0, 0.0), 1.0, 1.0); + return drawinglayer::attribute::LineStartEndAttribute( + 2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true); } - else + else if (aCap == LineCapTypeDiamondAnchor) { - const drawinglayer::primitive2d::Primitive2DReference aPrimitive( - new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( - polygon, - lineAttribute, - aStrokeAttribute)); - - mrTargetHolders.Current().append( - new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( - drawinglayer::primitive2d::Primitive2DContainer { aPrimitive }, - (255 - pen->GetColor().GetAlpha()) / 255.0)); + basegfx::B2DPolygon aCapPolygon({ {0.0, -1.0}, {1.0, 0.0}, {0.5, 0.5}, + {0.5, 1.0}, {-0.5, 1.0}, {-0.5, 0.5}, + {-1.0, 0.0} }); + aCapPolygon.setClosed(true); + return drawinglayer::attribute::LineStartEndAttribute( + 2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true); } - - if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap) && (pen->customStartCap->polygon.begin()->count() > 1)) + else if (aCap == LineCapTypeArrowAnchor) { - SAL_WARN("drawinglayer.emf", "EMF+\tCustom Start Line Cap"); - ::basegfx::B2DPolyPolygon startCapPolygon(pen->customStartCap->polygon); - - // get the gradient of the first line in the polypolygon - double x1 = polygon.begin()->getB2DPoint(0).getX(); - double y1 = polygon.begin()->getB2DPoint(0).getY(); - double x2 = polygon.begin()->getB2DPoint(1).getX(); - double y2 = polygon.begin()->getB2DPoint(1).getY(); - - if ((x2 - x1) != 0) - { - double gradient = (y2 - y1) / (x2 - x1); - - // now we get the angle that we need to rotate the arrow by - double angle = (M_PI / 2) - atan(gradient); + basegfx::B2DPolygon aCapPolygon({ {0.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} }); + aCapPolygon.setClosed(true); + return drawinglayer::attribute::LineStartEndAttribute( + 2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true); + } + return drawinglayer::attribute::LineStartEndAttribute(); + } - // rotate the arrow - startCapPolygon.transform(basegfx::utils::createRotateB2DHomMatrix(angle)); - } + void EmfPlusHelperData::EMFPPlusDrawPolygon(const ::basegfx::B2DPolyPolygon& polygon, + sal_uInt32 penIndex) + { + const EMFPPen* pen = dynamic_cast<EMFPPen*>(maEMFPObjects[penIndex & 0xff].get()); + SAL_WARN_IF(!pen, "drawinglayer.emf", "emf+ missing pen"); - startCapPolygon.transform(maMapTransform); + if (!(pen && polygon.count())) + return; - basegfx::B2DHomMatrix tran(pen->penWidth, 0.0, polygon.begin()->getB2DPoint(0).getX(), - 0.0, pen->penWidth, polygon.begin()->getB2DPoint(0).getY()); - startCapPolygon.transform(tran); + const double transformedPenWidth = mdExtractedYScale * pen->penWidth; + drawinglayer::attribute::LineAttribute lineAttribute( + pen->GetColor().getBColor(), transformedPenWidth, pen->maLineJoin, + css::drawing::LineCap_BUTT, //TODO implement PenDataDashedLineCap support here + pen->fMiterMinimumAngle); - if (pen->customStartCap->mbIsFilled) - { - mrTargetHolders.Current().append( - new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( - startCapPolygon, - pen->GetColor().getBColor())); - } + drawinglayer::attribute::LineStartEndAttribute aStart; + if (pen->penDataFlags & EmfPlusPenDataStartCap) + { + if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap) + && (pen->customStartCap->polygon.begin()->count() > 1)) + aStart = drawinglayer::attribute::LineStartEndAttribute( + pen->customStartCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale + * pen->customStartCap->widthScale * pen->penWidth, + pen->customStartCap->polygon, false); else - { - mrTargetHolders.Current().append( - new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( - startCapPolygon, - lineAttribute, - aStrokeAttribute)); - } + aStart = EmfPlusHelperData::CreateLineEnd(pen->startCap, pen->penWidth); } - if ((pen->penDataFlags & EmfPlusPenDataCustomEndCap) && (pen->customEndCap->polygon.begin()->count() > 1)) + drawinglayer::attribute::LineStartEndAttribute aEnd; + if (pen->penDataFlags & EmfPlusPenDataEndCap) { - SAL_WARN("drawinglayer.emf", "EMF+\tCustom End Line Cap"); - - ::basegfx::B2DPolyPolygon endCapPolygon(pen->customEndCap->polygon); - - // get the gradient of the first line in the polypolygon - double x1 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getX(); - double y1 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getY(); - double x2 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 2).getX(); - double y2 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 2).getY(); + if ((pen->penDataFlags & EmfPlusPenDataCustomEndCap) + && (pen->customEndCap->polygon.begin()->count() > 1)) + aEnd = drawinglayer::attribute::LineStartEndAttribute( + pen->customEndCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale + * pen->customEndCap->widthScale * pen->penWidth, + pen->customEndCap->polygon, false); + else + aEnd = EmfPlusHelperData::CreateLineEnd(pen->endCap, pen->penWidth); + } - if ((x2 - x1) != 0) + if (pen->GetColor().IsTransparent()) + { + drawinglayer::primitive2d::Primitive2DContainer aContainer; + if (aStart.isDefault() && aEnd.isDefault()) + aContainer.append( + new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( + polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale))); + else { - double gradient = (y2 - y1) / (x2 - x1); - - // now we get the angle that we need to rotate the arrow by - double angle = (M_PI / 2) - atan(gradient); - - // rotate the arrow - endCapPolygon.transform(basegfx::utils::createRotateB2DHomMatrix(angle)); + aContainer.resize(polygon.count()); + for (sal_uInt32 i = 0; i < polygon.count(); i++) + aContainer[i] = + new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D( + polygon.getB2DPolygon(i), lineAttribute, + pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd); } - - endCapPolygon.transform(maMapTransform); - basegfx::B2DHomMatrix tran(pen->penWidth, 0.0, polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getX(), - 0.0, pen->penWidth, polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getY()); - endCapPolygon.transform(tran); - - if (pen->customEndCap->mbIsFilled) - { + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(aContainer), (255 - pen->GetColor().GetAlpha()) / 255.0)); + } + else + { + if (aStart.isDefault() && aEnd.isDefault()) mrTargetHolders.Current().append( - new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( - endCapPolygon, - pen->GetColor().getBColor())); - } + new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( + polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale))); else - { - mrTargetHolders.Current().append( - new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( - endCapPolygon, - lineAttribute, - aStrokeAttribute)); - } + for (sal_uInt32 i = 0; i < polygon.count(); i++) + { + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D( + polygon.getB2DPolygon(i), lineAttribute, + pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd)); + } } - mrPropertyHolders.Current().setLineColor(pen->GetColor().getBColor()); mrPropertyHolders.Current().setLineColorActive(true); mrPropertyHolders.Current().setFillColorActive(false); @@ -735,7 +715,7 @@ namespace emfplushelper } else // use Brush { - EMFPBrush* brush = static_cast<EMFPBrush*>( maEMFPObjects[brushIndexOrColor & 0xff].get() ); + EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get()); SAL_INFO("drawinglayer.emf", "EMF+\t\t Fill polygon, brush slot: " << brushIndexOrColor << " (brush type: " << (brush ? brush->GetType() : -1) << ")"); // give up in case something wrong happened @@ -826,23 +806,14 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t\tUse blend"); // store the blendpoints in the vector - for (int i = 0; i < brush->blendPoints; i++) + for (sal_uInt32 i = 0; i < brush->blendPoints; i++) { - double aBlendPoint; + const double aBlendPoint = brush->blendPositions[i]; basegfx::BColor aColor; - if (brush->type == BrushTypeLinearGradient) - { - aBlendPoint = brush->blendPositions [i]; - } - else - { - // seems like SvgRadialGradientPrimitive2D needs doubled, inverted radius - aBlendPoint = 2. * ( 1. - brush->blendPositions [i] ); - } - aColor.setGreen( aStartColor.getGreen() + brush->blendFactors[i] * ( aEndColor.getGreen() - aStartColor.getGreen() ) ); - aColor.setBlue ( aStartColor.getBlue() + brush->blendFactors[i] * ( aEndColor.getBlue() - aStartColor.getBlue() ) ); - aColor.setRed ( aStartColor.getRed() + brush->blendFactors[i] * ( aEndColor.getRed() - aStartColor.getRed() ) ); - const double aAlpha = brush->solidColor.GetAlpha() + brush->blendFactors[i] * ( brush->secondColor.GetAlpha() - brush->solidColor.GetAlpha() ); + aColor.setGreen(aStartColor.getGreen() + brush->blendFactors[i] * (aEndColor.getGreen() - aStartColor.getGreen())); + aColor.setBlue (aStartColor.getBlue() + brush->blendFactors[i] * (aEndColor.getBlue() - aStartColor.getBlue())); + aColor.setRed (aStartColor.getRed() + brush->blendFactors[i] * (aEndColor.getRed() - aStartColor.getRed())); + const double aAlpha = brush->solidColor.GetAlpha() + brush->blendFactors[i] * (brush->secondColor.GetAlpha() - brush->solidColor.GetAlpha()); aVector.emplace_back(aBlendPoint, aColor, aAlpha / 255.0); } } @@ -851,35 +822,17 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t\tUse color blend"); // store the colorBlends in the vector - for (int i = 0; i < brush->colorblendPoints; i++) + for (sal_uInt32 i = 0; i < brush->colorblendPoints; i++) { - double aBlendPoint; - basegfx::BColor aColor; - if (brush->type == BrushTypeLinearGradient) - { - aBlendPoint = brush->colorblendPositions [i]; - } - else - { - // seems like SvgRadialGradientPrimitive2D needs doubled, inverted radius - aBlendPoint = 2. * ( 1. - brush->colorblendPositions [i] ); - } - aColor = brush->colorblendColors[i].getBColor(); - aVector.emplace_back(aBlendPoint, aColor, brush->colorblendColors[i].GetAlpha() / 255.0 ); + const double aBlendPoint = brush->colorblendPositions[i]; + const basegfx::BColor aColor = brush->colorblendColors[i].getBColor(); + aVector.emplace_back(aBlendPoint, aColor, brush->colorblendColors[i].GetAlpha() / 255.0); } } else // ok, no extra points: just start and end { - if (brush->type == BrushTypeLinearGradient) - { - aVector.emplace_back(0.0, aStartColor, brush->solidColor.GetAlpha() / 255.0); - aVector.emplace_back(1.0, aEndColor, brush->secondColor.GetAlpha() / 255.0); - } - else // again, here reverse - { - aVector.emplace_back(0.0, aEndColor, brush->secondColor.GetAlpha() / 255.0); - aVector.emplace_back(1.0, aStartColor, brush->solidColor.GetAlpha() / 255.0); - } + aVector.emplace_back(0.0, aStartColor, brush->solidColor.GetAlpha() / 255.0); + aVector.emplace_back(1.0, aEndColor, brush->secondColor.GetAlpha() / 255.0); } // get the polygon range to be able to map the start/end/center point correctly @@ -930,7 +883,7 @@ namespace emfplushelper aSpreadMethod)); } else // BrushTypePathGradient - { + { // TODO The PathGradient is not implemented, and Radial Gradient is used instead basegfx::B2DPoint aCenterPoint = Map(brush->firstPointX, brush->firstPointY); aCenterPoint = aPolygonTransformation * aCenterPoint; @@ -941,9 +894,9 @@ namespace emfplushelper polygon, std::move(aVector), aCenterPoint, - 0.5, // relative radius - true, // use UnitCoordinates to stretch the gradient - drawinglayer::primitive2d::SpreadMethod::Repeat, + 0.7, // relative radius little bigger to cover all elements + true, // use UnitCoordinates to stretch the gradient + drawinglayer::primitive2d::SpreadMethod::Pad, nullptr)); } } @@ -971,6 +924,8 @@ namespace emfplushelper mnMmY(0), mbMultipart(false), mMFlags(0), + mdExtractedXScale(1.0), + mdExtractedYScale(1.0), mrTargetHolders(rTargetHolders), mrPropertyHolders(rPropertyHolders), bIsGetDCProcessing(false) @@ -1000,14 +955,8 @@ namespace emfplushelper } case EmfPlusCombineModeIntersect: { - if (leftPolygon.count()) - { - aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( - leftPolygon, - rightPolygon, - true, - false); - } + aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( + leftPolygon, rightPolygon, true, false); break; } case EmfPlusCombineModeUnion: @@ -1080,8 +1029,18 @@ namespace emfplushelper if (bIsGetDCProcessing) { - SAL_INFO("drawinglayer.emf", "EMF+\t reset the current clipping region for the world space to infinity."); - wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders, mrPropertyHolders); + if (aGetDCState.getClipPolyPolygonActive()) + { + SAL_INFO("drawinglayer.emf", "EMF+\t Restore region to GetDC saved"); + wmfemfhelper::HandleNewClipRegion(aGetDCState.getClipPolyPolygon(), mrTargetHolders, + mrPropertyHolders); + } + else + { + SAL_INFO("drawinglayer.emf", "EMF+\t Disable clipping"); + wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders, + mrPropertyHolders); + } bIsGetDCProcessing = false; } if (type == EmfPlusRecordTypeObject && ((mbMultipart && (flags & 0x7fff) == (mMFlags & 0x7fff)) || (flags & 0x8000))) @@ -1117,14 +1076,15 @@ namespace emfplushelper { case EmfPlusRecordTypeHeader: { - sal_uInt32 header, version; + sal_uInt32 version, emfPlusFlags; + SAL_INFO("drawinglayer.emf", "EMF+\tDual: " << ((flags & 1) ? "true" : "false")); - rMS.ReadUInt32(header).ReadUInt32(version).ReadUInt32(mnHDPI).ReadUInt32(mnVDPI); - SAL_INFO("drawinglayer.emf", "EMF+\tHeader: 0x" << std::hex << header); - SAL_INFO("drawinglayer.emf", "EMF+\tVersion: " << std::dec << version); + rMS.ReadUInt32(version).ReadUInt32(emfPlusFlags).ReadUInt32(mnHDPI).ReadUInt32(mnVDPI); + SAL_INFO("drawinglayer.emf", "EMF+\tVersion: 0x" << std::hex << version); + SAL_INFO("drawinglayer.emf", "EMF+\tEmf+ Flags: 0x" << emfPlusFlags << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\tMetafile was recorded with a reference device context for " << ((emfPlusFlags & 1) ? "video display" : "printer")); SAL_INFO("drawinglayer.emf", "EMF+\tHorizontal DPI: " << mnHDPI); SAL_INFO("drawinglayer.emf", "EMF+\tVertical DPI: " << mnVDPI); - SAL_INFO("drawinglayer.emf", "EMF+\tDual: " << ((flags & 1) ? "true" : "false")); break; } case EmfPlusRecordTypeEndOfFile: @@ -1160,6 +1120,7 @@ namespace emfplushelper case EmfPlusRecordTypeGetDC: { bIsGetDCProcessing = true; + aGetDCState = mrPropertyHolders.Current(); SAL_INFO("drawinglayer.emf", "EMF+\tAlready used in svtools wmf/emf filter parser"); break; } @@ -1257,7 +1218,11 @@ namespace emfplushelper rMS.ReadUInt32(brushIndexOrColor); SAL_INFO("drawinglayer.emf", "EMF+\t FillRegion slot: " << index); - EMFPPlusFillPolygon(static_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get())->regionPolyPolygon, flags & 0x8000, brushIndexOrColor); + EMFPRegion* region = dynamic_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get()); + if (region) + EMFPPlusFillPolygon(region->regionPolyPolygon, flags & 0x8000, brushIndexOrColor); + else + SAL_WARN("drawinglayer.emf", "EMF+\tEmfPlusRecordTypeFillRegion missing region"); } break; case EmfPlusRecordTypeDrawEllipse: @@ -1292,7 +1257,9 @@ namespace emfplushelper { // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used sal_uInt32 brushIndexOrColor = 999; - sal_Int32 rectangles; + ::basegfx::B2DPolyPolygon polyPolygon; + sal_uInt32 rectangles; + float x, y, width, height; const bool isColor = (flags & 0x8000); ::basegfx::B2DPolygon polygon; @@ -1307,11 +1274,9 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t DrawRects"); } - rMS.ReadInt32(rectangles); - - for (int i = 0; i < rectangles; i++) + rMS.ReadUInt32(rectangles); + for (sal_uInt32 i = 0; i < rectangles; i++) { - float x, y, width, height; ReadRectangle(rMS, x, y, width, height, bool(flags & 0x4000)); polygon.clear(); polygon.append(Map(x, y)); @@ -1321,37 +1286,34 @@ namespace emfplushelper polygon.setClosed(true); SAL_INFO("drawinglayer.emf", "EMF+\t\t rectangle: " << x << ", "<< y << " " << width << "x" << height); - - ::basegfx::B2DPolyPolygon polyPolygon(polygon); - if (type == EmfPlusRecordTypeFillRects) - EMFPPlusFillPolygon(polyPolygon, isColor, brushIndexOrColor); - else - EMFPPlusDrawPolygon(polyPolygon, flags & 0xff); + polyPolygon.append(polygon); } + if (type == EmfPlusRecordTypeFillRects) + EMFPPlusFillPolygon(polyPolygon, isColor, brushIndexOrColor); + else + EMFPPlusDrawPolygon(polyPolygon, flags & 0xff); break; } case EmfPlusRecordTypeFillPolygon: { - const sal_uInt8 index = flags & 0xff; sal_uInt32 brushIndexOrColor, points; rMS.ReadUInt32(brushIndexOrColor); rMS.ReadUInt32(points); - SAL_INFO("drawinglayer.emf", "EMF+\t FillPolygon in slot: " << index << " points: " << points); + SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points); SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << " : 0x" << std::hex << brushIndexOrColor << std::dec); EMFPPath path(points, true); path.Read(rMS, flags); EMFPPlusFillPolygon(path.GetPolygon(*this), flags & 0x8000, brushIndexOrColor); - break; } case EmfPlusRecordTypeDrawLines: { sal_uInt32 points; rMS.ReadUInt32(points); - SAL_INFO("drawinglayer.emf", "EMF+\t DrawLines in slot: " << (flags & 0xff) << " points: " << points); + SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points); EMFPPath path(points, true); path.Read(rMS, flags); @@ -1379,46 +1341,98 @@ namespace emfplushelper { sal_uInt32 aCount; float x1, y1, x2, y2, x3, y3, x4, y4; - ::basegfx::B2DPoint aStartPoint, aControlPointA, aControlPointB, aEndPoint; ::basegfx::B2DPolygon aPolygon; rMS.ReadUInt32(aCount); SAL_INFO("drawinglayer.emf", "EMF+\t DrawBeziers slot: " << (flags & 0xff)); SAL_INFO("drawinglayer.emf", "EMF+\t Number of points: " << aCount); - SAL_WARN_IF((aCount - 1) % 3 != 0, "drawinglayer.emf", "EMF+\t Bezier Draw not support number of points other than 4, 7, 10, 13, 16..."); + SAL_WARN_IF((aCount - 1) % 3 != 0, "drawinglayer.emf", + "EMF+\t Bezier Draw not support number of points other than 4, 7, " + "10, 13, 16..."); if (aCount < 4) { - SAL_WARN("drawinglayer.emf", "EMF+\t Bezier Draw does not support less than 4 points. Number of points: " << aCount); + SAL_WARN("drawinglayer.emf", "EMF+\t Bezier Draw does not support less " + "than 4 points. Number of points: " + << aCount); break; } ReadPoint(rMS, x1, y1, flags); // We need to add first starting point - aStartPoint = Map(x1, y1); - aPolygon.append(aStartPoint); - + aPolygon.append(Map(x1, y1)); + SAL_INFO("drawinglayer.emf", + "EMF+\t Bezier starting point: " << x1 << "," << y1); for (sal_uInt32 i = 4; i <= aCount; i += 3) { ReadPoint(rMS, x2, y2, flags); ReadPoint(rMS, x3, y3, flags); ReadPoint(rMS, x4, y4, flags); - SAL_INFO("drawinglayer.emf", "EMF+\t Bezier points: " << x1 << "," << y1 << " " << x2 << "," << y2 << " " << x3 << "," << y3 << " " << x4 << "," << y4); - - aStartPoint = Map(x1, y1); - aControlPointA = Map(x2, y2); - aControlPointB = Map(x3, y3); - aEndPoint = Map(x4, y4); - - ::basegfx::B2DCubicBezier cubicBezier(aStartPoint, aControlPointA, aControlPointB, aEndPoint); - cubicBezier.adaptiveSubdivideByDistance(aPolygon, 10.0); + SAL_INFO("drawinglayer.emf", + "EMF+\t Bezier points: " << x2 << "," << y2 << " " << x3 << "," + << y3 << " " << x4 << "," << y4); + aPolygon.appendBezierSegment(Map(x2, y2), Map(x3, y3), Map(x4, y4)); + } + EMFPPlusDrawPolygon(::basegfx::B2DPolyPolygon(aPolygon), flags & 0xff); + break; + } + case EmfPlusRecordTypeDrawCurve: + { + sal_uInt32 aOffset, aNumSegments, points; + float aTension; + rMS.ReadFloat(aTension); + rMS.ReadUInt32(aOffset); + rMS.ReadUInt32(aNumSegments); + rMS.ReadUInt32(points); + SAL_WARN("drawinglayer.emf", + "EMF+\t Tension: " << aTension << " Offset: " << aOffset + << " NumSegments: " << aNumSegments + << " Points: " << points); - EMFPPlusDrawPolygon(::basegfx::B2DPolyPolygon(aPolygon), flags & 0xff); + EMFPPath path(points, true); + path.Read(rMS, flags); - // The ending coordinate of one Bezier curve is the starting coordinate of the next. - x1 = x4; - y1 = y4; + if (points >= 2) + EMFPPlusDrawPolygon( + path.GetCardinalSpline(*this, aTension, aOffset, aNumSegments), + flags & 0xff); + else + SAL_WARN("drawinglayer.emf", "Not enough number of points"); + break; + } + case EmfPlusRecordTypeDrawClosedCurve: + case EmfPlusRecordTypeFillClosedCurve: + { + // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used + sal_uInt32 brushIndexOrColor = 999, points; + float aTension; + if (type == EmfPlusRecordTypeFillClosedCurve) + { + rMS.ReadUInt32(brushIndexOrColor); + SAL_INFO( + "drawinglayer.emf", + "EMF+\t Fill Mode: " << (flags & 0x2000 ? "Winding" : "Alternate")); } + rMS.ReadFloat(aTension); + rMS.ReadUInt32(points); + SAL_WARN("drawinglayer.emf", + "EMF+\t Tension: " << aTension << " Points: " << points); + SAL_INFO("drawinglayer.emf", + "EMF+\t " << (flags & 0x8000 ? "Color" : "Brush index") << " : 0x" + << std::hex << brushIndexOrColor << std::dec); + if (points < 3) + { + SAL_WARN("drawinglayer.emf", "Not enough number of points"); + break; + } + EMFPPath path(points, true); + path.Read(rMS, flags); + if (type == EmfPlusRecordTypeFillClosedCurve) + EMFPPlusFillPolygon(path.GetClosedCardinalSpline(*this, aTension), + flags & 0x8000, brushIndexOrColor); + else + EMFPPlusDrawPolygon(path.GetClosedCardinalSpline(*this, aTension), + flags & 0xff); break; } case EmfPlusRecordTypeDrawImage: @@ -1435,16 +1449,16 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t TODO: use image attributes"); // Source unit of measurement type must be 1 pixel - if (sourceUnit == UnitTypePixel && maEMFPObjects[flags & 0xff]) + if (EMFPImage* image = sourceUnit == UnitTypePixel ? + dynamic_cast<EMFPImage*>(maEMFPObjects[flags & 0xff].get()) : + nullptr) { - EMFPImage& image - = *static_cast<EMFPImage*>(maEMFPObjects[flags & 0xff].get()); float sx, sy, sw, sh; ReadRectangle(rMS, sx, sy, sw, sh); ::tools::Rectangle aSource(Point(sx, sy), Size(sw + 1, sh + 1)); SAL_INFO("drawinglayer.emf", - "EMF+\t " + "EMF+\t " << (type == EmfPlusRecordTypeDrawImage ? "DrawImage" : "DrawImagePoints") << " source rectangle: " << sx << "," << sy << " " << sw << "x" @@ -1488,9 +1502,9 @@ namespace emfplushelper SAL_INFO("drawinglayer.emf", "EMF+\t Rectangle: " << dx << "," << dy << " " << dw << "x" << dh); Size aSize; - if (image.type == ImageDataTypeBitmap) + if (image->type == ImageDataTypeBitmap) { - aSize = image.graphic.GetBitmapEx().GetSizePixel(); + aSize = image->graphic.GetBitmapEx().GetSizePixel(); SAL_INFO("drawinglayer.emf", "EMF+\t Bitmap size: " << aSize.Width() << "x" << aSize.Height()); @@ -1522,36 +1536,36 @@ namespace emfplushelper SAL_INFO( "drawinglayer.emf", "EMF+\t TODO: Add support for SrcRect to ImageDataTypeMetafile"); - ::basegfx::B2DPoint aDstPoint(dx, dy); - ::basegfx::B2DSize aDstSize(dw, dh); + const ::basegfx::B2DPoint aDstPoint(dx, dy); + const ::basegfx::B2DSize aDstSize(dw, dh); const basegfx::B2DHomMatrix aTransformMatrix = maMapTransform * basegfx::B2DHomMatrix( - /* Row 0, Column 0 */ aDstSize.getX(), + /* Row 0, Column 0 */ aDstSize.getWidth(), /* Row 0, Column 1 */ fShearX, /* Row 0, Column 2 */ aDstPoint.getX(), /* Row 1, Column 0 */ fShearY, - /* Row 1, Column 1 */ aDstSize.getY(), + /* Row 1, Column 1 */ aDstSize.getHeight(), /* Row 1, Column 2 */ aDstPoint.getY()); - if (image.type == ImageDataTypeBitmap) + if (image->type == ImageDataTypeBitmap) { - BitmapEx aBmp(image.graphic.GetBitmapEx()); + BitmapEx aBmp(image->graphic.GetBitmapEx()); aBmp.Crop(aSource); aSize = aBmp.GetSizePixel(); if (aSize.Width() > 0 && aSize.Height() > 0) { mrTargetHolders.Current().append( new drawinglayer::primitive2d::BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(aBmp), aTransformMatrix)); + aBmp, aTransformMatrix)); } else SAL_WARN("drawinglayer.emf", "EMF+\t warning: empty bitmap"); } - else if (image.type == ImageDataTypeMetafile) + else if (image->type == ImageDataTypeMetafile) { - GDIMetaFile aGDI(image.graphic.GetGDIMetaFile()); + GDIMetaFile aGDI(image->graphic.GetGDIMetaFile()); aGDI.Clip(aSource); mrTargetHolders.Current().append( new drawinglayer::primitive2d::MetafilePrimitive2D(aTransformMatrix, @@ -1586,7 +1600,7 @@ namespace emfplushelper // get the stringFormat from the Object table ( this is OPTIONAL and may be nullptr ) const EMFPStringFormat *stringFormat = dynamic_cast<EMFPStringFormat*>(maEMFPObjects[formatId & 0xff].get()); // get the font from the flags - const EMFPFont *font = static_cast< EMFPFont* >( maEMFPObjects[flags & 0xff].get() ); + const EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get()); if (!font) { break; @@ -1648,7 +1662,7 @@ namespace emfplushelper } const basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix( - ::basegfx::B2DSize(font->emSize, font->emSize), + ::basegfx::B2DVector(font->emSize, font->emSize), ::basegfx::B2DPoint(lx + stringAlignmentHorizontalOffset, ly + stringAlignmentVerticalOffset)); @@ -1690,6 +1704,7 @@ namespace emfplushelper 0, // text always starts at 0 stringLength, std::move(emptyVector), // EMF-PLUS has no DX-array + {}, fontAttribute, locale, color.getBColor(), // Font Color @@ -1709,6 +1724,7 @@ namespace emfplushelper 0, // text always starts at 0 stringLength, std::move(emptyVector), // EMF-PLUS has no DX-array + {}, fontAttribute, locale, color.getBColor()); @@ -1739,8 +1755,8 @@ namespace emfplushelper } else { - mnMmX *= mfPageScale * getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnHDPI); - mnMmY *= mfPageScale * getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnVDPI); + mnMmX = std::round(unitToPixel(static_cast<double>(mnMmX) * mfPageScale, flags, Direction::horizontal)); + mnMmY = std::round(unitToPixel(static_cast<double>(mnMmY) * mfPageScale, flags, Direction::vertical)); mappingChanged(); } break; @@ -1815,7 +1831,7 @@ namespace emfplushelper rMS.ReadUInt32(stackIndex); SAL_INFO("drawinglayer.emf", "EMF+\t Restore stack index: " << stackIndex); - GraphicStatePop(mGSStack, stackIndex, mrPropertyHolders.Current()); + GraphicStatePop(mGSStack, stackIndex); break; } case EmfPlusRecordTypeBeginContainer: @@ -1837,12 +1853,12 @@ namespace emfplushelper SAL_WARN("drawinglayer.emf", "EMF+\t file error. UnitTypeDisplay and UnitTypeWorld are not supported by BeginContainer in EMF+ specification."); break; } - const float aPageScaleX = getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnHDPI); - const float aPageScaleY = getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnVDPI); GraphicStatePush(mGSContainerStack, stackIndex); const basegfx::B2DHomMatrix transform = basegfx::utils::createScaleTranslateB2DHomMatrix( - aPageScaleX * ( dw / sw ), aPageScaleY * ( dh / sh ), - aPageScaleX * ( dx - sx ), aPageScaleY * ( dy - sy) ); + unitToPixel(static_cast<double>(dw) / sw, flags, Direction::horizontal), + unitToPixel(static_cast<double>(dh) / sh, flags, Direction::vertical), + unitToPixel(static_cast<double>(dx) - sx, flags, Direction::horizontal), + unitToPixel(static_cast<double>(dy) - sy, flags, Direction::vertical)); maWorldTransform *= transform; mappingChanged(); break; @@ -1862,7 +1878,7 @@ namespace emfplushelper rMS.ReadUInt32(stackIndex); SAL_INFO("drawinglayer.emf", "EMF+\t End Container stack index: " << stackIndex); - GraphicStatePop(mGSContainerStack, stackIndex, mrPropertyHolders.Current()); + GraphicStatePop(mGSContainerStack, stackIndex); break; } case EmfPlusRecordTypeSetWorldTransform: @@ -1997,62 +2013,95 @@ namespace emfplushelper break; } case EmfPlusRecordTypeSetClipRect: - { - int combineMode = (flags >> 8) & 0xf; - - SAL_INFO("drawinglayer.emf", "EMF+\t SetClipRect combine mode: " << combineMode); - - float dx, dy, dw, dh; - ReadRectangle(rMS, dx, dy, dw, dh); - SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh); - ::basegfx::B2DPoint mappedPoint1(Map(dx, dy)); - ::basegfx::B2DPoint mappedPoint2(Map(dx + dw, dy + dh)); - - ::basegfx::B2DPolyPolygon polyPolygon( - ::basegfx::utils::createPolygonFromRect( - ::basegfx::B2DRectangle( - mappedPoint1.getX(), - mappedPoint1.getY(), - mappedPoint2.getX(), - mappedPoint2.getY()))); - - HandleNewClipRegion(combineClip(mrPropertyHolders.Current().getClipPolyPolygon(), - combineMode, polyPolygon), mrTargetHolders, mrPropertyHolders); - break; - } case EmfPlusRecordTypeSetClipPath: + case EmfPlusRecordTypeSetClipRegion: { int combineMode = (flags >> 8) & 0xf; - SAL_INFO("drawinglayer.emf", "EMF+\t SetClipPath combine mode: " << combineMode); - SAL_INFO("drawinglayer.emf", "EMF+\t Path in slot: " << (flags & 0xff)); - - EMFPPath *path = static_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get()); - if (!path) + ::basegfx::B2DPolyPolygon polyPolygon; + if (type == EmfPlusRecordTypeSetClipRect) { - SAL_WARN("drawinglayer.emf", "EMF+\t TODO Unable to find path in slot: " << (flags & 0xff)); - break; - } + SAL_INFO("drawinglayer.emf", "EMF+\t SetClipRect"); - ::basegfx::B2DPolyPolygon& clipPoly(path->GetPolygon(*this)); + float dx, dy, dw, dh; + ReadRectangle(rMS, dx, dy, dw, dh); + SAL_INFO("drawinglayer.emf", + "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh); + ::basegfx::B2DPoint mappedPoint1(Map(dx, dy)); + ::basegfx::B2DPoint mappedPoint2(Map(dx + dw, dy + dh)); + + polyPolygon + = ::basegfx::B2DPolyPolygon(::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle(mappedPoint1.getX(), mappedPoint1.getY(), + mappedPoint2.getX(), mappedPoint2.getY()))); + } + else if (type == EmfPlusRecordTypeSetClipPath) + { + SAL_INFO("drawinglayer.emf", "EMF+\tSetClipPath " << (flags & 0xff)); - HandleNewClipRegion(combineClip(mrPropertyHolders.Current().getClipPolyPolygon(), - combineMode, clipPoly), mrTargetHolders, mrPropertyHolders); - break; - } - case EmfPlusRecordTypeSetClipRegion: - { - int combineMode = (flags >> 8) & 0xf; - SAL_INFO("drawinglayer.emf", "EMF+\t Region in slot: " << (flags & 0xff)); + EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get()); + if (!path) + { + SAL_WARN("drawinglayer.emf", + "EMF+\t TODO Unable to find path in slot: " << (flags & 0xff)); + break; + } + polyPolygon = path->GetPolygon(*this); + } + else if (type == EmfPlusRecordTypeSetClipRegion) + { + SAL_INFO("drawinglayer.emf", "EMF+\t Region in slot: " << (flags & 0xff)); + EMFPRegion* region + = dynamic_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get()); + if (!region) + { + SAL_WARN( + "drawinglayer.emf", + "EMF+\t TODO Unable to find region in slot: " << (flags & 0xff)); + break; + } + polyPolygon = region->regionPolyPolygon; + } SAL_INFO("drawinglayer.emf", "EMF+\t Combine mode: " << combineMode); - EMFPRegion *region = static_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get()); - if (!region) + ::basegfx::B2DPolyPolygon aClippedPolyPolygon; + if (mrPropertyHolders.Current().getClipPolyPolygonActive()) { - SAL_WARN("drawinglayer.emf", "EMF+\t TODO Unable to find region in slot: " << (flags & 0xff)); - break; + aClippedPolyPolygon + = combineClip(mrPropertyHolders.Current().getClipPolyPolygon(), + combineMode, polyPolygon); } - - HandleNewClipRegion(combineClip(mrPropertyHolders.Current().getClipPolyPolygon(), - combineMode, region->regionPolyPolygon), mrTargetHolders, mrPropertyHolders); + else + { + //Combine with infinity + switch (combineMode) + { + case EmfPlusCombineModeReplace: + case EmfPlusCombineModeIntersect: + { + aClippedPolyPolygon = polyPolygon; + break; + } + case EmfPlusCombineModeUnion: + { + // Disable clipping as the clipping is infinity + aClippedPolyPolygon = ::basegfx::B2DPolyPolygon(); + break; + } + case EmfPlusCombineModeXOR: + case EmfPlusCombineModeComplement: + { + //TODO It is not correct and it should be fixed + aClippedPolyPolygon = polyPolygon; + break; + } + case EmfPlusCombineModeExclude: + { + //TODO It is not correct and it should be fixed + aClippedPolyPolygon = ::basegfx::B2DPolyPolygon(); + break; + } + } + } + HandleNewClipRegion(aClippedPolyPolygon, mrTargetHolders, mrPropertyHolders); break; } case EmfPlusRecordTypeOffsetClip: @@ -2113,7 +2162,7 @@ namespace emfplushelper } // get the font from the flags - EMFPFont *font = static_cast< EMFPFont* >( maEMFPObjects[flags & 0xff].get() ); + EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get()); if (!font) { break; @@ -2154,7 +2203,7 @@ namespace emfplushelper aDXArray.push_back(0); basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix( - ::basegfx::B2DSize(font->emSize, font->emSize), + ::basegfx::B2DVector(font->emSize, font->emSize), ::basegfx::B2DPoint(charsPosX[pos], charsPosY[pos])); if (hasMatrix) transformMatrix *= transform; @@ -2169,6 +2218,7 @@ namespace emfplushelper pos, // take character at current pos aLength, // use determined length std::move(aDXArray), // generated DXArray + {}, fontAttribute, Application::GetSettings().GetLanguageTag().getLocale(), color.getBColor(), @@ -2188,6 +2238,7 @@ namespace emfplushelper pos, // take character at current pos aLength, // use determined length std::move(aDXArray), // generated DXArray + {}, fontAttribute, Application::GetSettings().GetLanguageTag().getLocale(), color.getBColor()); diff --git a/drawinglayer/source/tools/emfphelperdata.hxx b/drawinglayer/source/tools/emfphelperdata.hxx index cf3474b5b1a7..d2ed1008b67b 100644 --- a/drawinglayer/source/tools/emfphelperdata.hxx +++ b/drawinglayer/source/tools/emfphelperdata.hxx @@ -21,6 +21,7 @@ #include <wmfemfhelper.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/attribute/linestartendattribute.hxx> #include <tools/stream.hxx> #include <basegfx/point/b2dpoint.hxx> #include <map> @@ -53,9 +54,9 @@ namespace emfplushelper #define EmfPlusRecordTypeFillRegion 0x4013 #define EmfPlusRecordTypeFillPath 0x4014 #define EmfPlusRecordTypeDrawPath 0x4015 - //TODO EmfPlusRecordTypeFillClosedCurve 0x4016 - //TODO EmfPlusRecordTypeDrawClosedCurve 0x4017 - //TODO EmfPlusRecordTypeDrawCurve 0x4018 + #define EmfPlusRecordTypeFillClosedCurve 0x4016 + #define EmfPlusRecordTypeDrawClosedCurve 0x4017 + #define EmfPlusRecordTypeDrawCurve 0x4018 #define EmfPlusRecordTypeDrawBeziers 0x4019 #define EmfPlusRecordTypeDrawImage 0x401A #define EmfPlusRecordTypeDrawImagePoints 0x401B @@ -210,9 +211,15 @@ namespace emfplushelper GraphicStateMap mGSStack; GraphicStateMap mGSContainerStack; + /* Performance optimizators */ + /* Extracted Scale values from Transformation Matrix */ + double mdExtractedXScale; + double mdExtractedYScale; + /// data holders wmfemfhelper::TargetHolders& mrTargetHolders; wmfemfhelper::PropertyHolders& mrPropertyHolders; + wmfemfhelper::PropertyHolder aGetDCState; bool bIsGetDCProcessing; // readers @@ -224,7 +231,10 @@ namespace emfplushelper // stack actions void GraphicStatePush(GraphicStateMap& map, sal_Int32 index); - void GraphicStatePop (GraphicStateMap& map, sal_Int32 index, wmfemfhelper::PropertyHolder& rState); + void GraphicStatePop(GraphicStateMap& map, sal_Int32 index); + + drawinglayer::attribute::LineStartEndAttribute CreateLineEnd(const sal_Int32 aCap, + const float aPenWidth) const; // primitive creators void EMFPPlusDrawPolygon(const ::basegfx::B2DPolyPolygon& polygon, sal_uInt32 penIndex); @@ -234,6 +244,14 @@ namespace emfplushelper // helper functions Color EMFPGetBrushColorOrARGBColor(const sal_uInt16 flags, const sal_uInt32 brushIndexOrColor) const; + enum class Direction + { + horizontal, + vertical + }; + sal_uInt32 DPI(Direction d) { return d == Direction::horizontal ? mnHDPI : mnVDPI; } + double unitToPixel(double n, sal_uInt32 aUnitType, Direction d); + public: EmfPlusHelperData( SvMemoryStream& rMS, @@ -252,8 +270,6 @@ namespace emfplushelper static void ReadRectangle(SvStream& s, float& x, float& y, float &width, float& height, bool bCompressed = false); static bool readXForm(SvStream& rIn, basegfx::B2DHomMatrix& rTarget); static ::basegfx::B2DPolyPolygon combineClip(::basegfx::B2DPolyPolygon const & leftPolygon, int combineMode, ::basegfx::B2DPolyPolygon const & rightPolygon); - - static float getUnitToPixelMultiplier(const UnitType aUnitType, const sal_uInt32 aDPI); }; } diff --git a/drawinglayer/source/tools/emfpimage.cxx b/drawinglayer/source/tools/emfpimage.cxx index a3300168e7f6..67a0cef99ed2 100644 --- a/drawinglayer/source/tools/emfpimage.cxx +++ b/drawinglayer/source/tools/emfpimage.cxx @@ -38,7 +38,7 @@ namespace emfplushelper { // non native formats GraphicFilter filter; - filter.ImportGraphic(graphic, OUString(), s); + filter.ImportGraphic(graphic, u"", s); SAL_INFO("drawinglayer.emf", "EMF+\tbitmap width: " << graphic.GetSizePixel().Width() << " height: " << graphic.GetSizePixel().Height()); } } @@ -58,7 +58,7 @@ namespace emfplushelper GraphicFilter filter; // workaround buggy metafiles, which have wrong mfSize set (n#705956 for example) SvMemoryStream mfStream(const_cast<char *>(static_cast<char const *>(s.GetData()) + s.Tell()), dataSize, StreamMode::READ); - filter.ImportGraphic(graphic, OUString(), mfStream); + filter.ImportGraphic(graphic, u"", mfStream); // debug code - write the stream to debug file /tmp/emf-stream.emf #if OSL_DEBUG_LEVEL > 1 diff --git a/drawinglayer/source/tools/emfpimageattributes.cxx b/drawinglayer/source/tools/emfpimageattributes.cxx index c13da361bf1a..11c1f47173e4 100644 --- a/drawinglayer/source/tools/emfpimageattributes.cxx +++ b/drawinglayer/source/tools/emfpimageattributes.cxx @@ -22,7 +22,6 @@ #include "emfpimageattributes.hxx" using namespace ::com::sun::star; -using namespace ::basegfx; namespace emfplushelper { diff --git a/drawinglayer/source/tools/emfppath.cxx b/drawinglayer/source/tools/emfppath.cxx index 4da379004fba..e7c4a5512c76 100644 --- a/drawinglayer/source/tools/emfppath.cxx +++ b/drawinglayer/source/tools/emfppath.cxx @@ -20,7 +20,6 @@ #include <basegfx/point/b2dpoint.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolypolygon.hxx> -#include <o3tl/safeint.hxx> #include <sal/log.hxx> #include "emfppath.hxx" @@ -35,6 +34,13 @@ namespace namespace emfplushelper { + typedef double matrix [4][4]; + + constexpr sal_uInt32 nDetails = 8; + constexpr double alpha[nDetails] + = { 1. / nDetails, 2. / nDetails, 3. / nDetails, 4. / nDetails, + 5. / nDetails, 6. / nDetails, 7. / nDetails, 8. / nDetails }; + // see 2.2.2.21 EmfPlusInteger7 // 2.2.2.22 EmfPlusInteger15 // and 2.2.2.37 EmfPlusPointR Object @@ -66,7 +72,6 @@ namespace emfplushelper } nPoints = _nPoints; - pPoints.reset( new float [nPoints*2] ); if (!bLines) pPointTypes.reset( new sal_uInt8 [_nPoints] ); @@ -78,7 +83,8 @@ namespace emfplushelper void EMFPPath::Read (SvStream& s, sal_uInt32 pathFlags) { - for (sal_uInt32 i = 0; i < nPoints; i ++) + float fx, fy; + for (sal_uInt32 i = 0; i < nPoints; i++) { if (pathFlags & 0x800) { @@ -87,8 +93,8 @@ namespace emfplushelper // If 0x800 bit is set, the 0x4000 bit is undefined and must be ignored sal_Int32 x = GetEmfPlusInteger(s); sal_Int32 y = GetEmfPlusInteger(s); - pPoints [i*2] = x; - pPoints [i*2 + 1] = y; + xPoints.push_back(x); + yPoints.push_back(y); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t" << i << ". EmfPlusPointR [x,y]: " << x << ", " << y); } else if (pathFlags & 0x4000) @@ -96,16 +102,18 @@ namespace emfplushelper // EMFPlusPoint: stored in signed short 16bit integer format sal_Int16 x, y; - s.ReadInt16( x ).ReadInt16( y ); - SAL_INFO ("drawinglayer.emf", "EMF+\t\t\t" << i << ". EmfPlusPoint [x,y]: " << x << ", " << y); - pPoints [i*2] = x; - pPoints [i*2 + 1] = y; + s.ReadInt16(x).ReadInt16(y); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t" << i << ". EmfPlusPoint [x,y]: " << x << ", " << y); + xPoints.push_back(x); + yPoints.push_back(y); } else { // EMFPlusPointF: stored in Single (float) format - s.ReadFloat( pPoints [i*2] ).ReadFloat( pPoints [i*2 + 1] ); - SAL_INFO("drawinglayer.emf", "EMF+\t" << i << ". EMFPlusPointF [x,y]: " << pPoints [i * 2] << ", " << pPoints [i * 2 + 1]); + s.ReadFloat(fx).ReadFloat(fy); + SAL_INFO("drawinglayer.emf", "EMF+\t" << i << ". EMFPlusPointF [x,y]: " << fx << ", " << fy); + xPoints.push_back(fx); + yPoints.push_back(fy); } } @@ -129,7 +137,7 @@ namespace emfplushelper ::basegfx::B2DPoint prev, mapped; bool hasPrev = false; - for (sal_uInt32 i = 0; i < nPoints; i ++) + for (sal_uInt32 i = 0; i < nPoints; i++) { if (p && pPointTypes && (pPointTypes [i] == 0)) { @@ -140,9 +148,9 @@ namespace emfplushelper } if (bMapIt) - mapped = rR.Map (pPoints [i*2], pPoints [i*2 + 1]); + mapped = rR.Map(xPoints[i], yPoints [i]); else - mapped = ::basegfx::B2DPoint (pPoints [i*2], pPoints [i*2 + 1]); + mapped = ::basegfx::B2DPoint(xPoints[i], yPoints[i]); if (pPointTypes) { @@ -168,7 +176,7 @@ namespace emfplushelper } polygon.append (mapped); - SAL_INFO ("drawinglayer.emf", "EMF+\t\tPoint: " << pPoints [i*2] << "," << pPoints [i*2 + 1] << " mapped: " << mapped.getX () << ":" << mapped.getY ()); + SAL_INFO ("drawinglayer.emf", "EMF+\t\tPoint: " << xPoints[i] << "," << yPoints[i] << " mapped: " << mapped.getX () << ":" << mapped.getY ()); if (hasPrev) { @@ -222,6 +230,91 @@ namespace emfplushelper return aPolygon; } + + static void GetCardinalMatrix(float tension, matrix& m) + { + m[0][1] = 2. - tension; + m[0][2] = tension - 2.; + m[1][0] = 2. * tension; + m[1][1] = tension - 3.; + m[1][2] = 3. - 2. * tension; + m[3][1] = 1.; + m[0][3] = m[2][2] = tension; + m[0][0] = m[1][3] = m[2][0] = -tension; + m[2][1] = m[2][3] = m[3][0] = m[3][2] = m[3][3] = 0.; + } + + static double calculateSplineCoefficients(float p0, float p1, float p2, float p3, sal_uInt32 step, matrix m) + { + double a = m[0][0] * p0 + m[0][1] * p1 + m[0][2] * p2 + m[0][3] * p3; + double b = m[1][0] * p0 + m[1][1] * p1 + m[1][2] * p2 + m[1][3] * p3; + double c = m[2][0] * p0 + m[2][2] * p2; + double d = p1; + return (d + alpha[step] * (c + alpha[step] * (b + alpha[step] * a))); + } + + ::basegfx::B2DPolyPolygon& EMFPPath::GetCardinalSpline(EmfPlusHelperData const& rR, float fTension, + sal_uInt32 aOffset, sal_uInt32 aNumSegments) + { + ::basegfx::B2DPolygon polygon; + matrix mat; + double x, y; + if (aNumSegments >= nPoints) + aNumSegments = nPoints - 1; + GetCardinalMatrix(fTension, mat); + // duplicate first point + xPoints.push_front(xPoints.front()); + yPoints.push_front(yPoints.front()); + // duplicate last point + xPoints.push_back(xPoints.back()); + yPoints.push_back(yPoints.back()); + + for (sal_uInt32 i = 3 + aOffset; i < aNumSegments + 3; i++) + { + for (sal_uInt32 s = 0; s < nDetails; s++) + { + x = calculateSplineCoefficients(xPoints[i - 3], xPoints[i - 2], xPoints[i - 1], + xPoints[i], s, mat); + y = calculateSplineCoefficients(yPoints[i - 3], yPoints[i - 2], yPoints[i - 1], + yPoints[i], s, mat); + polygon.append(rR.Map(x, y)); + } + } + if (polygon.count()) + aPolygon.append(polygon); + return aPolygon; + } + + ::basegfx::B2DPolyPolygon& EMFPPath::GetClosedCardinalSpline(EmfPlusHelperData const& rR, float fTension) + { + ::basegfx::B2DPolygon polygon; + matrix mat; + double x, y; + GetCardinalMatrix(fTension, mat); + // add three first points at the end + xPoints.push_back(xPoints[0]); + yPoints.push_back(yPoints[0]); + xPoints.push_back(xPoints[1]); + yPoints.push_back(yPoints[1]); + xPoints.push_back(xPoints[2]); + yPoints.push_back(yPoints[2]); + + for (sal_uInt32 i = 3; i < nPoints + 3; i++) + { + for (sal_uInt32 s = 0; s < nDetails; s++) + { + x = calculateSplineCoefficients(xPoints[i - 3], xPoints[i - 2], xPoints[i - 1], + xPoints[i], s, mat); + y = calculateSplineCoefficients(yPoints[i - 3], yPoints[i - 2], yPoints[i - 1], + yPoints[i], s, mat); + polygon.append(rR.Map(x, y)); + } + } + polygon.setClosed(true); + if (polygon.count()) + aPolygon.append(polygon); + return aPolygon; + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfppath.hxx b/drawinglayer/source/tools/emfppath.hxx index 8db095a21a69..e6104fcb1fc7 100644 --- a/drawinglayer/source/tools/emfppath.hxx +++ b/drawinglayer/source/tools/emfppath.hxx @@ -23,13 +23,14 @@ namespace emfplushelper { - struct EMFPPath : public EMFPObject + class EMFPPath : public EMFPObject { ::basegfx::B2DPolyPolygon aPolygon; sal_uInt32 nPoints; - std::unique_ptr<float[]> pPoints; + std::deque<float> xPoints, yPoints; std::unique_ptr<sal_uInt8[]> pPointTypes; + public: EMFPPath(sal_uInt32 _nPoints, bool bLines = false); virtual ~EMFPPath() override; @@ -37,6 +38,9 @@ namespace emfplushelper void Read(SvStream& s, sal_uInt32 pathFlags); ::basegfx::B2DPolyPolygon& GetPolygon(EmfPlusHelperData const & rR, bool bMapIt = true, bool bAddLineToCloseShape = false); + ::basegfx::B2DPolyPolygon& GetCardinalSpline(EmfPlusHelperData const& rR, float fTension, + sal_uInt32 aOffset, sal_uInt32 aNumSegments); + ::basegfx::B2DPolyPolygon& GetClosedCardinalSpline(EmfPlusHelperData const& rR, float fTension); }; } diff --git a/drawinglayer/source/tools/emfppen.cxx b/drawinglayer/source/tools/emfppen.cxx index d41dca676d4e..adfee3bd3706 100644 --- a/drawinglayer/source/tools/emfppen.cxx +++ b/drawinglayer/source/tools/emfppen.cxx @@ -18,7 +18,6 @@ */ #include <com/sun/star/rendering/PathCapType.hpp> -#include <com/sun/star/rendering/PathJoinType.hpp> #include <o3tl/safeint.hxx> #include <sal/log.hxx> #include <rtl/ustrbuf.hxx> @@ -31,26 +30,6 @@ using namespace ::basegfx; namespace emfplushelper { - namespace { - - enum EmfPlusPenData - { - PenDataTransform = 0x00000001, - PenDataStartCap = 0x00000002, - PenDataEndCap = 0x00000004, - PenDataJoin = 0x00000008, - PenDataMiterLimit = 0x00000010, - PenDataLineStyle = 0x00000020, - PenDataDashedLineCap = 0x00000040, - PenDataDashedLineOffset = 0x00000080, - PenDataDashedLine = 0x00000100, - PenDataAlignment = 0x00000200, - PenDataCompoundLine = 0x00000400, - PenDataCustomStartCap = 0x00000800, - PenDataCustomEndCap = 0x00001000 - }; - - } EMFPPen::EMFPPen() : penDataFlags(0) @@ -58,8 +37,8 @@ namespace emfplushelper , penWidth(0.0) , startCap(0) , endCap(0) - , lineJoin(0) - , miterLimit(0.0) + , maLineJoin(basegfx::B2DLineJoin::Miter) + , fMiterMinimumAngle(basegfx::deg2rad(5.0)) , dashStyle(0) , dashCap(0) , dashOffset(0.0) @@ -138,18 +117,6 @@ namespace emfplushelper return ""; } - static OUString LineJoinTypeToString(sal_uInt32 jointype) - { - switch (jointype) - { - case LineJoinTypeMiter: return "LineJoinTypeMiter"; - case LineJoinTypeBevel: return "LineJoinTypeBevel"; - case LineJoinTypeRound: return "LineJoinTypeRound"; - case LineJoinTypeMiterClipped: return "LineJoinTypeMiterClipped"; - } - return ""; - } - static OUString DashedLineCapTypeToString(sal_uInt32 dashedlinecaptype) { switch (dashedlinecaptype) @@ -174,38 +141,49 @@ namespace emfplushelper return ""; } - /// Convert stroke caps between EMF+ and rendering API - sal_Int8 EMFPPen::lcl_convertStrokeCap(sal_uInt32 nEmfStroke) + drawinglayer::attribute::StrokeAttribute + EMFPPen::GetStrokeAttribute(const double aTransformation) const { - switch (nEmfStroke) + if (penDataFlags & EmfPlusPenDataLineStyle // pen has a predefined line style + && dashStyle != EmfPlusLineStyleCustom) { - case EmfPlusLineCapTypeSquare: return rendering::PathCapType::SQUARE; - case EmfPlusLineCapTypeRound: return rendering::PathCapType::ROUND; + const double pw = aTransformation * penWidth; + switch (dashStyle) + { + case EmfPlusLineStyleDash: + // [-loplugin:redundantfcast] false positive: + return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw }); + case EmfPlusLineStyleDot: + // [-loplugin:redundantfcast] false positive: + return drawinglayer::attribute::StrokeAttribute({ pw, pw }); + case EmfPlusLineStyleDashDot: + // [-loplugin:redundantfcast] false positive: + return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw }); + case EmfPlusLineStyleDashDotDot: + // [-loplugin:redundantfcast] false positive: + return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw, pw, pw }); + } } - - // we have no mapping for EmfPlusLineCapTypeTriangle = 0x00000003, - // so return BUTT always - return rendering::PathCapType::BUTT; - } - - sal_Int8 EMFPPen::lcl_convertLineJoinType(sal_uInt32 nEmfLineJoin) - { - switch (nEmfLineJoin) + else if (penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line { - case EmfPlusLineJoinTypeMiter: // fall-through - case EmfPlusLineJoinTypeMiterClipped: return rendering::PathJoinType::MITER; - case EmfPlusLineJoinTypeBevel: return rendering::PathJoinType::BEVEL; - case EmfPlusLineJoinTypeRound: return rendering::PathJoinType::ROUND; + const double pw = aTransformation * penWidth; + // StrokeAttribute needs a double vector while the pen provides a float vector + std::vector<double> aPattern(dashPattern.size()); + for (size_t i = 0; i < aPattern.size(); i++) + { + // convert from float to double and multiply with the adjusted pen width + aPattern[i] = pw * dashPattern[i]; + } + return drawinglayer::attribute::StrokeAttribute(std::move(aPattern)); } - - assert(false); // Line Join type isn't in specification. - return 0; + // EmfPlusLineStyleSolid: - do nothing special, use default stroke attribute + return drawinglayer::attribute::StrokeAttribute(); } void EMFPPen::Read(SvStream& s, EmfPlusHelperData const & rR) { + sal_Int32 lineJoin = EmfPlusLineJoinTypeMiter; sal_uInt32 graphicsVersion, penType; - int i; s.ReadUInt32(graphicsVersion).ReadUInt32(penType).ReadUInt32(penDataFlags).ReadUInt32(penUnit).ReadFloat(penWidth); SAL_INFO("drawinglayer.emf", "EMF+\t\tGraphics version: 0x" << std::hex << graphicsVersion); SAL_INFO("drawinglayer.emf", "EMF+\t\tType: " << penType); @@ -220,13 +198,13 @@ namespace emfplushelper : 0.05f; // 0.05f is taken from old EMF+ implementation (case of Unit == Pixel etc.) } - if (penDataFlags & PenDataTransform) + if (penDataFlags & EmfPlusPenDataTransform) { EmfPlusHelperData::readXForm(s, pen_transformation); SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataTransform: " << pen_transformation); } - if (penDataFlags & PenDataStartCap) + if (penDataFlags & EmfPlusPenDataStartCap) { s.ReadInt32(startCap); SAL_INFO("drawinglayer.emf", "EMF+\t\tstartCap: " << LineCapTypeToString(startCap) << " (0x" << std::hex << startCap << ")"); @@ -236,7 +214,7 @@ namespace emfplushelper startCap = 0; } - if (penDataFlags & PenDataEndCap) + if (penDataFlags & EmfPlusPenDataEndCap) { s.ReadInt32(endCap); SAL_INFO("drawinglayer.emf", "EMF+\t\tendCap: " << LineCapTypeToString(endCap) << " (0x" << std::hex << startCap << ")"); @@ -246,27 +224,52 @@ namespace emfplushelper endCap = 0; } - if (penDataFlags & PenDataJoin) + if (penDataFlags & EmfPlusPenDataJoin) { s.ReadInt32(lineJoin); - SAL_WARN("drawinglayer.emf", "EMF+\t\tTODO PenDataJoin: " << LineJoinTypeToString(lineJoin) << " (0x" << std::hex << lineJoin << ")"); + SAL_INFO("drawinglayer.emf", "EMF+\t\t LineJoin: " << lineJoin); + switch (lineJoin) + { + case EmfPlusLineJoinTypeBevel: + maLineJoin = basegfx::B2DLineJoin::Bevel; + break; + case EmfPlusLineJoinTypeRound: + maLineJoin = basegfx::B2DLineJoin::Round; + break; + case EmfPlusLineJoinTypeMiter: + case EmfPlusLineJoinTypeMiterClipped: + default: // If nothing set, then apply Miter (based on MS Paint) + maLineJoin = basegfx::B2DLineJoin::Miter; + break; + } } else - { - lineJoin = 0; - } + maLineJoin = basegfx::B2DLineJoin::Miter; - if (penDataFlags & PenDataMiterLimit) + if (penDataFlags & EmfPlusPenDataMiterLimit) { + float miterLimit; s.ReadFloat(miterLimit); - SAL_WARN("drawinglayer.emf", "EMF+\t\tTODO PenDataMiterLimit: " << std::dec << miterLimit); + + // EMF+ JoinTypeMiterClipped is working as our B2DLineJoin::Miter + // For EMF+ LineJoinTypeMiter we are simulating it by changing angle + if (lineJoin == EmfPlusLineJoinTypeMiter) + miterLimit = 3.0 * miterLimit; + // asin angle must be in range [-1, 1] + if (abs(miterLimit) > 1.0) + fMiterMinimumAngle = 2.0 * asin(1.0 / miterLimit); + else + // enable miter limit for all angles + fMiterMinimumAngle = basegfx::deg2rad(180.0); + SAL_INFO("drawinglayer.emf", + "EMF+\t\t MiterLimit: " << std::dec << miterLimit + << ", Miter minimum angle (rad): " << fMiterMinimumAngle); } else - { - miterLimit = 0; - } + fMiterMinimumAngle = basegfx::deg2rad(5.0); + - if (penDataFlags & PenDataLineStyle) + if (penDataFlags & EmfPlusPenDataLineStyle) { s.ReadInt32(dashStyle); SAL_INFO("drawinglayer.emf", "EMF+\t\tdashStyle: " << DashedLineCapTypeToString(dashStyle) << " (0x" << std::hex << dashStyle << ")"); @@ -276,7 +279,7 @@ namespace emfplushelper dashStyle = 0; } - if (penDataFlags & PenDataDashedLineCap) + if (penDataFlags & EmfPlusPenDataDashedLineCap) { s.ReadInt32(dashCap); SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineCap: 0x" << std::hex << dashCap); @@ -286,7 +289,7 @@ namespace emfplushelper dashCap = 0; } - if (penDataFlags & PenDataDashedLineOffset) + if (penDataFlags & EmfPlusPenDataDashedLineOffset) { s.ReadFloat(dashOffset); SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineOffset: 0x" << std::hex << dashOffset); @@ -296,29 +299,24 @@ namespace emfplushelper dashOffset = 0; } - if (penDataFlags & PenDataDashedLine) + if (penDataFlags & EmfPlusPenDataDashedLine) { dashStyle = EmfPlusLineStyleCustom; - sal_Int32 dashPatternLen; + sal_uInt32 dashPatternLen; - s.ReadInt32(dashPatternLen); + s.ReadUInt32(dashPatternLen); SAL_INFO("drawinglayer.emf", "EMF+\t\t\tdashPatternLen: " << dashPatternLen); - if (dashPatternLen<0 || o3tl::make_unsigned(dashPatternLen)>SAL_MAX_INT32 / sizeof(float)) - { - dashPatternLen = SAL_MAX_INT32 / sizeof(float); - } - dashPattern.resize( dashPatternLen ); - for (i = 0; i < dashPatternLen; i++) + for (sal_uInt32 i = 0; i < dashPatternLen; i++) { s.ReadFloat(dashPattern[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tdashPattern[" << i << "]: " << dashPattern[i]); } } - if (penDataFlags & PenDataAlignment) + if (penDataFlags & EmfPlusPenDataAlignment) { s.ReadInt32(alignment); SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataAlignment: " << PenAlignmentToString(alignment) << " (0x" << std::hex << alignment << ")"); @@ -328,29 +326,24 @@ namespace emfplushelper alignment = 0; } - if (penDataFlags & PenDataCompoundLine) + if (penDataFlags & EmfPlusPenDataCompoundLine) { SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataCompoundLine"); - sal_Int32 compoundArrayLen; - s.ReadInt32(compoundArrayLen); - - if (compoundArrayLen<0 || o3tl::make_unsigned(compoundArrayLen)>SAL_MAX_INT32 / sizeof(float)) - { - compoundArrayLen = SAL_MAX_INT32 / sizeof(float); - } + sal_uInt32 compoundArrayLen; + s.ReadUInt32(compoundArrayLen); compoundArray.resize(compoundArrayLen); - for (i = 0; i < compoundArrayLen; i++) + for (sal_uInt32 i = 0; i < compoundArrayLen; i++) { s.ReadFloat(compoundArray[i]); SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tcompoundArray[" << i << "]: " << compoundArray[i]); } } - if (penDataFlags & PenDataCustomStartCap) + if (penDataFlags & EmfPlusPenDataCustomStartCap) { - s.ReadInt32(customStartCapLen); + s.ReadUInt32(customStartCapLen); SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomStartCapLen: " << customStartCapLen); sal_uInt64 const pos = s.Tell(); @@ -365,9 +358,9 @@ namespace emfplushelper customStartCapLen = 0; } - if (penDataFlags & PenDataCustomEndCap) + if (penDataFlags & EmfPlusPenDataCustomEndCap) { - s.ReadInt32(customEndCapLen); + s.ReadUInt32(customEndCapLen); SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomEndCapLen: " << customEndCapLen); sal_uInt64 const pos = s.Tell(); diff --git a/drawinglayer/source/tools/emfppen.hxx b/drawinglayer/source/tools/emfppen.hxx index 05b2fc376d7d..31812c8b0c0e 100644 --- a/drawinglayer/source/tools/emfppen.hxx +++ b/drawinglayer/source/tools/emfppen.hxx @@ -19,6 +19,7 @@ #pragma once +#include <drawinglayer/attribute/strokeattribute.hxx> #include "emfpbrush.hxx" #include <vector> @@ -26,6 +27,7 @@ namespace emfplushelper { const sal_uInt32 EmfPlusLineCapTypeSquare = 0x00000001; const sal_uInt32 EmfPlusLineCapTypeRound = 0x00000002; + const sal_uInt32 EmfPlusLineCapTypeTriangle = 0x00000003; const sal_uInt32 EmfPlusLineJoinTypeMiter = 0x00000000; const sal_uInt32 EmfPlusLineJoinTypeBevel = 0x00000001; @@ -102,17 +104,17 @@ namespace emfplushelper float penWidth; sal_Int32 startCap; sal_Int32 endCap; - sal_Int32 lineJoin; - float miterLimit; + basegfx::B2DLineJoin maLineJoin; + double fMiterMinimumAngle; sal_Int32 dashStyle; sal_Int32 dashCap; float dashOffset; std::vector<float> dashPattern; sal_Int32 alignment; std::vector<float> compoundArray; - sal_Int32 customStartCapLen; + sal_uInt32 customStartCapLen; std::unique_ptr<EMFPCustomLineCap> customStartCap; - sal_Int32 customEndCapLen; + sal_uInt32 customEndCapLen; std::unique_ptr<EMFPCustomLineCap> customEndCap; EMFPPen(); @@ -121,8 +123,7 @@ namespace emfplushelper void Read(SvStream& s, EmfPlusHelperData const & rR); - static sal_Int8 lcl_convertStrokeCap(sal_uInt32 nEmfStroke); - static sal_Int8 lcl_convertLineJoinType(sal_uInt32 nEmfLineJoin); + drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const double aTransformation) const; }; } diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx index 2e2bb887a3d4..5658df3312d9 100644 --- a/drawinglayer/source/tools/primitive2dxmldump.cxx +++ b/drawinglayer/source/tools/primitive2dxmldump.cxx @@ -13,7 +13,9 @@ #include <tools/stream.hxx> #include <tools/XmlWriter.hxx> +#include <math.h> #include <memory> +#include <libxml/parser.h> #include <sal/log.hxx> #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> @@ -21,25 +23,32 @@ #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> #include <drawinglayer/primitive2d/Tools.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> #include <primitive2d/textlineprimitive2d.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> +#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> #include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/sceneprimitive2d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/attribute/lineattribute.hxx> #include <drawinglayer/attribute/fontattribute.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/utils/gradienttools.hxx> #include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx> #include <toolkit/helper/vclunohelper.hxx> @@ -49,7 +58,6 @@ #include <drawinglayer/primitive3d/sdrextrudeprimitive3d.hxx> #include <drawinglayer/attribute/sdrlightattribute3d.hxx> #include <drawinglayer/attribute/sdrfillattribute.hxx> -#include <drawinglayer/attribute/fillgraphicattribute.hxx> #include <drawinglayer/attribute/fillhatchattribute.hxx> #include <drawinglayer/attribute/fillgradientattribute.hxx> #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> @@ -77,9 +85,9 @@ void writeMatrix(::tools::XmlWriter& rWriter, const basegfx::B2DHomMatrix& rMatr rWriter.attribute("xy21", rMatrix.get(1, 0)); rWriter.attribute("xy22", rMatrix.get(1, 1)); rWriter.attribute("xy23", rMatrix.get(1, 2)); - rWriter.attribute("xy31", rMatrix.get(2, 0)); - rWriter.attribute("xy32", rMatrix.get(2, 1)); - rWriter.attribute("xy33", rMatrix.get(2, 2)); + rWriter.attribute("xy31", 0); + rWriter.attribute("xy32", 0); + rWriter.attribute("xy33", 1); } void writeMatrix3D(::tools::XmlWriter& rWriter, const basegfx::B3DHomMatrix& rMatrix) @@ -132,6 +140,24 @@ void writePolyPolygon(::tools::XmlWriter& rWriter, const basegfx::B2DPolyPolygon rWriter.endElement(); } +void writeStrokeAttribute(::tools::XmlWriter& rWriter, + const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute) +{ + if (!rStrokeAttribute.getDotDashArray().empty()) + { + rWriter.startElement("stroke"); + + OUString sDotDash; + for (double fDotDash : rStrokeAttribute.getDotDashArray()) + { + sDotDash += OUString::number(lround(fDotDash)) + " "; + } + rWriter.attribute("dotDashArray", sDotDash); + rWriter.attribute("fullDotDashLength", rStrokeAttribute.getFullDotDashLen()); + rWriter.endElement(); + } +} + void writeLineAttribute(::tools::XmlWriter& rWriter, const drawinglayer::attribute::LineAttribute& rLineAttribute) { @@ -141,36 +167,41 @@ void writeLineAttribute(::tools::XmlWriter& rWriter, switch (rLineAttribute.getLineJoin()) { case basegfx::B2DLineJoin::NONE: - rWriter.attribute("linejoin", "NONE"); + rWriter.attribute("linejoin", "NONE"_ostr); break; case basegfx::B2DLineJoin::Bevel: - rWriter.attribute("linejoin", "Bevel"); + rWriter.attribute("linejoin", "Bevel"_ostr); break; case basegfx::B2DLineJoin::Miter: - rWriter.attribute("linejoin", "Miter"); + { + rWriter.attribute("linejoin", "Miter"_ostr); + rWriter.attribute("miterangle", + basegfx::rad2deg(rLineAttribute.getMiterMinimumAngle())); break; + } case basegfx::B2DLineJoin::Round: - rWriter.attribute("linejoin", "Round"); + rWriter.attribute("linejoin", "Round"_ostr); break; default: - rWriter.attribute("linejoin", "Unknown"); + rWriter.attribute("linejoin", "Unknown"_ostr); break; } switch (rLineAttribute.getLineCap()) { case css::drawing::LineCap::LineCap_BUTT: - rWriter.attribute("linecap", "BUTT"); + rWriter.attribute("linecap", "BUTT"_ostr); break; case css::drawing::LineCap::LineCap_ROUND: - rWriter.attribute("linecap", "ROUND"); + rWriter.attribute("linecap", "ROUND"_ostr); break; case css::drawing::LineCap::LineCap_SQUARE: - rWriter.attribute("linecap", "SQUARE"); + rWriter.attribute("linecap", "SQUARE"_ostr); break; default: - rWriter.attribute("linecap", "Unknown"); + rWriter.attribute("linecap", "Unknown"_ostr); break; } + rWriter.endElement(); } @@ -188,34 +219,34 @@ void writeSdrLineAttribute(::tools::XmlWriter& rWriter, switch (rLineAttribute.getJoin()) { case basegfx::B2DLineJoin::NONE: - rWriter.attribute("linejoin", "NONE"); + rWriter.attribute("linejoin", "NONE"_ostr); break; case basegfx::B2DLineJoin::Bevel: - rWriter.attribute("linejoin", "Bevel"); + rWriter.attribute("linejoin", "Bevel"_ostr); break; case basegfx::B2DLineJoin::Miter: - rWriter.attribute("linejoin", "Miter"); + rWriter.attribute("linejoin", "Miter"_ostr); break; case basegfx::B2DLineJoin::Round: - rWriter.attribute("linejoin", "Round"); + rWriter.attribute("linejoin", "Round"_ostr); break; default: - rWriter.attribute("linejoin", "Unknown"); + rWriter.attribute("linejoin", "Unknown"_ostr); break; } switch (rLineAttribute.getCap()) { case css::drawing::LineCap::LineCap_BUTT: - rWriter.attribute("linecap", "BUTT"); + rWriter.attribute("linecap", "BUTT"_ostr); break; case css::drawing::LineCap::LineCap_ROUND: - rWriter.attribute("linecap", "ROUND"); + rWriter.attribute("linecap", "ROUND"_ostr); break; case css::drawing::LineCap::LineCap_SQUARE: - rWriter.attribute("linecap", "SQUARE"); + rWriter.attribute("linecap", "SQUARE"_ostr); break; default: - rWriter.attribute("linecap", "Unknown"); + rWriter.attribute("linecap", "Unknown"_ostr); break; } @@ -249,23 +280,24 @@ void writeSdrFillAttribute(::tools::XmlWriter& rWriter, rWriter.startElement("gradient"); switch (rGradient.getStyle()) { - case drawinglayer::attribute::GradientStyle::Linear: - rWriter.attribute("style", "Linear"); + default: // GradientStyle_MAKE_FIXED_SIZE + case css::awt::GradientStyle_LINEAR: + rWriter.attribute("style", "Linear"_ostr); break; - case drawinglayer::attribute::GradientStyle::Axial: - rWriter.attribute("style", "Axial"); + case css::awt::GradientStyle_AXIAL: + rWriter.attribute("style", "Axial"_ostr); break; - case drawinglayer::attribute::GradientStyle::Radial: - rWriter.attribute("style", "Radial"); + case css::awt::GradientStyle_RADIAL: + rWriter.attribute("style", "Radial"_ostr); break; - case drawinglayer::attribute::GradientStyle::Elliptical: - rWriter.attribute("style", "Elliptical"); + case css::awt::GradientStyle_ELLIPTICAL: + rWriter.attribute("style", "Elliptical"_ostr); break; - case drawinglayer::attribute::GradientStyle::Square: - rWriter.attribute("style", "Square"); + case css::awt::GradientStyle_SQUARE: + rWriter.attribute("style", "Square"_ostr); break; - case drawinglayer::attribute::GradientStyle::Rect: - rWriter.attribute("style", "Rect"); + case css::awt::GradientStyle_RECT: + rWriter.attribute("style", "Rect"_ostr); break; } rWriter.attribute("border", rGradient.getBorder()); @@ -273,8 +305,23 @@ void writeSdrFillAttribute(::tools::XmlWriter& rWriter, rWriter.attribute("offsetY", rGradient.getOffsetY()); rWriter.attribute("angle", rGradient.getAngle()); rWriter.attribute("steps", rGradient.getSteps()); - rWriter.attribute("startColor", convertColorToString(rGradient.getStartColor())); - rWriter.attribute("endColor", convertColorToString(rGradient.getEndColor())); + + auto const& rColorStops(rGradient.getColorStops()); + for (size_t a(0); a < rColorStops.size(); a++) + { + if (0 == a) + rWriter.attribute("startColor", + convertColorToString(rColorStops[a].getStopColor())); + else if (rColorStops.size() == a + 1) + rWriter.attribute("endColor", convertColorToString(rColorStops[a].getStopColor())); + else + { + rWriter.startElement("colorStop"); + rWriter.attribute("stopOffset", rColorStops[a].getStopOffset()); + rWriter.attribute("stopColor", convertColorToString(rColorStops[a].getStopColor())); + rWriter.endElement(); + } + } rWriter.endElement(); } @@ -285,13 +332,13 @@ void writeSdrFillAttribute(::tools::XmlWriter& rWriter, switch (rHatch.getStyle()) { case drawinglayer::attribute::HatchStyle::Single: - rWriter.attribute("style", "Single"); + rWriter.attribute("style", "Single"_ostr); break; case drawinglayer::attribute::HatchStyle::Double: - rWriter.attribute("style", "Double"); + rWriter.attribute("style", "Double"_ostr); break; case drawinglayer::attribute::HatchStyle::Triple: - rWriter.attribute("style", "Triple"); + rWriter.attribute("style", "Triple"_ostr); break; } rWriter.attribute("distance", rHatch.getDistance()); @@ -318,19 +365,19 @@ void writeShadeMode(::tools::XmlWriter& rWriter, const css::drawing::ShadeMode& switch (rMode) { case css::drawing::ShadeMode_FLAT: - rWriter.attribute("shadeMode", "Flat"); + rWriter.attribute("shadeMode", "Flat"_ostr); break; case css::drawing::ShadeMode_SMOOTH: - rWriter.attribute("shadeMode", "Smooth"); + rWriter.attribute("shadeMode", "Smooth"_ostr); break; case css::drawing::ShadeMode_PHONG: - rWriter.attribute("shadeMode", "Phong"); + rWriter.attribute("shadeMode", "Phong"_ostr); break; case css::drawing::ShadeMode_DRAFT: - rWriter.attribute("shadeMode", "Draft"); + rWriter.attribute("shadeMode", "Draft"_ostr); break; default: - rWriter.attribute("shadeMode", "Undefined"); + rWriter.attribute("shadeMode", "Undefined"_ostr); break; } } @@ -340,13 +387,13 @@ void writeProjectionMode(::tools::XmlWriter& rWriter, const css::drawing::Projec switch (rMode) { case css::drawing::ProjectionMode_PARALLEL: - rWriter.attribute("projectionMode", "Parallel"); + rWriter.attribute("projectionMode", "Parallel"_ostr); break; case css::drawing::ProjectionMode_PERSPECTIVE: - rWriter.attribute("projectionMode", "Perspective"); + rWriter.attribute("projectionMode", "Perspective"_ostr); break; default: - rWriter.attribute("projectionMode", "Undefined"); + rWriter.attribute("projectionMode", "Undefined"_ostr); break; } } @@ -356,16 +403,16 @@ void writeNormalsKind(::tools::XmlWriter& rWriter, const css::drawing::NormalsKi switch (rKind) { case css::drawing::NormalsKind_SPECIFIC: - rWriter.attribute("normalsKind", "Specific"); + rWriter.attribute("normalsKind", "Specific"_ostr); break; case css::drawing::NormalsKind_FLAT: - rWriter.attribute("normalsKind", "Flat"); + rWriter.attribute("normalsKind", "Flat"_ostr); break; case css::drawing::NormalsKind_SPHERE: - rWriter.attribute("normalsKind", "Sphere"); + rWriter.attribute("normalsKind", "Sphere"_ostr); break; default: - rWriter.attribute("normalsKind", "Undefined"); + rWriter.attribute("normalsKind", "Undefined"_ostr); break; } } @@ -376,16 +423,16 @@ void writeTextureProjectionMode(::tools::XmlWriter& rWriter, const char* pElemen switch (rMode) { case css::drawing::TextureProjectionMode_OBJECTSPECIFIC: - rWriter.attribute(pElement, "Specific"); + rWriter.attribute(pElement, "Specific"_ostr); break; case css::drawing::TextureProjectionMode_PARALLEL: - rWriter.attribute(pElement, "Parallel"); + rWriter.attribute(pElement, "Parallel"_ostr); break; case css::drawing::TextureProjectionMode_SPHERE: - rWriter.attribute(pElement, "Sphere"); + rWriter.attribute(pElement, "Sphere"_ostr); break; default: - rWriter.attribute(pElement, "Undefined"); + rWriter.attribute(pElement, "Undefined"_ostr); break; } } @@ -395,16 +442,16 @@ void writeTextureKind(::tools::XmlWriter& rWriter, const css::drawing::TextureKi switch (rKind) { case css::drawing::TextureKind2_LUMINANCE: - rWriter.attribute("textureKind", "Luminance"); + rWriter.attribute("textureKind", "Luminance"_ostr); break; case css::drawing::TextureKind2_INTENSITY: - rWriter.attribute("textureKind", "Intensity"); + rWriter.attribute("textureKind", "Intensity"_ostr); break; case css::drawing::TextureKind2_COLOR: - rWriter.attribute("textureKind", "Color"); + rWriter.attribute("textureKind", "Color"_ostr); break; default: - rWriter.attribute("textureKind", "Undefined"); + rWriter.attribute("textureKind", "Undefined"_ostr); break; } } @@ -414,16 +461,16 @@ void writeTextureMode(::tools::XmlWriter& rWriter, const css::drawing::TextureMo switch (rMode) { case css::drawing::TextureMode_REPLACE: - rWriter.attribute("textureMode", "Replace"); + rWriter.attribute("textureMode", "Replace"_ostr); break; case css::drawing::TextureMode_MODULATE: - rWriter.attribute("textureMode", "Modulate"); + rWriter.attribute("textureMode", "Modulate"_ostr); break; case css::drawing::TextureMode_BLEND: - rWriter.attribute("textureMode", "Blend"); + rWriter.attribute("textureMode", "Blend"_ostr); break; default: - rWriter.attribute("textureMode", "Undefined"); + rWriter.attribute("textureMode", "Undefined"_ostr); break; } } @@ -439,6 +486,25 @@ void writeMaterialAttribute(::tools::XmlWriter& rWriter, rWriter.endElement(); } +void writeSpreadMethod(::tools::XmlWriter& rWriter, + const drawinglayer::primitive2d::SpreadMethod& rSpreadMethod) +{ + switch (rSpreadMethod) + { + case drawinglayer::primitive2d::SpreadMethod::Pad: + rWriter.attribute("spreadmethod", "pad"_ostr); + break; + case drawinglayer::primitive2d::SpreadMethod::Reflect: + rWriter.attribute("spreadmethod", "reflect"_ostr); + break; + case drawinglayer::primitive2d::SpreadMethod::Repeat: + rWriter.attribute("spreadmethod", "repeat"_ostr); + break; + default: + rWriter.attribute("spreadmethod", "unknown"_ostr); + } +} + } // end anonymous namespace Primitive2dXmlDump::Primitive2dXmlDump() @@ -551,8 +617,7 @@ public: default: { rWriter.startElement("unhandled"); - rWriter.attribute("id", - OUStringToOString(sCurrentElementTag, RTL_TEXTENCODING_UTF8)); + rWriter.attribute("id", sCurrentElementTag); rWriter.attribute("idNumber", nId); drawinglayer::geometry::ViewInformation3D aViewInformation3D; @@ -598,20 +663,26 @@ xmlDocUniquePtr Primitive2dXmlDump::dumpAndParse( return xmlDocUniquePtr(xmlParseDoc(reinterpret_cast<xmlChar*>(pBuffer.get()))); } +OUString Primitive2dXmlDump::idToString(sal_uInt32 nId) +{ + return drawinglayer::primitive2d::idToString(nId); +} + void Primitive2dXmlDump::decomposeAndWrite( const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence, ::tools::XmlWriter& rWriter) { - for (size_t i = 0; i < rPrimitive2DSequence.size(); i++) + for (auto const& i : rPrimitive2DSequence) { - drawinglayer::primitive2d::Primitive2DReference xPrimitive2DReference - = rPrimitive2DSequence[i]; - const BasePrimitive2D* pBasePrimitive - = static_cast<const BasePrimitive2D*>(xPrimitive2DReference.get()); + const BasePrimitive2D* pBasePrimitive = i.get(); sal_uInt32 nId = pBasePrimitive->getPrimitive2DID(); if (nId < maFilter.size() && maFilter[nId]) continue; + // handled by subclass + if (decomposeAndWrite(*pBasePrimitive, rWriter)) + continue; + OUString sCurrentElementTag = drawinglayer::primitive2d::idToString(nId); switch (nId) @@ -623,7 +694,7 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.startElement("bitmap"); writeMatrix(rWriter, rBitmapPrimitive2D.getTransform()); - const BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapPrimitive2D.getXBitmap())); + const BitmapEx aBitmapEx(rBitmapPrimitive2D.getBitmap()); const Size& rSizePixel(aBitmapEx.GetSizePixel()); rWriter.attribute("height", rSizePixel.getHeight()); @@ -706,6 +777,49 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.endElement(); } break; + + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D: + { + const PolygonStrokeArrowPrimitive2D& rPolygonStrokeArrowPrimitive2D + = dynamic_cast<const PolygonStrokeArrowPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("polygonstrokearrow"); + + rWriter.startElement("polygon"); + rWriter.content(basegfx::utils::exportToSvgPoints( + rPolygonStrokeArrowPrimitive2D.getB2DPolygon())); + rWriter.endElement(); + + if (rPolygonStrokeArrowPrimitive2D.getStart().getB2DPolyPolygon().count()) + { + rWriter.startElement("linestartattribute"); + rWriter.attribute("width", + rPolygonStrokeArrowPrimitive2D.getStart().getWidth()); + rWriter.attribute("centered", + static_cast<sal_Int32>( + rPolygonStrokeArrowPrimitive2D.getStart().isCentered())); + writePolyPolygon(rWriter, + rPolygonStrokeArrowPrimitive2D.getStart().getB2DPolyPolygon()); + rWriter.endElement(); + } + + if (rPolygonStrokeArrowPrimitive2D.getEnd().getB2DPolyPolygon().count()) + { + rWriter.startElement("lineendattribute"); + rWriter.attribute("width", rPolygonStrokeArrowPrimitive2D.getEnd().getWidth()); + rWriter.attribute("centered", + static_cast<sal_Int32>( + rPolygonStrokeArrowPrimitive2D.getEnd().isCentered())); + writePolyPolygon(rWriter, + rPolygonStrokeArrowPrimitive2D.getEnd().getB2DPolyPolygon()); + rWriter.endElement(); + } + + writeLineAttribute(rWriter, rPolygonStrokeArrowPrimitive2D.getLineAttribute()); + writeStrokeAttribute(rWriter, rPolygonStrokeArrowPrimitive2D.getStrokeAttribute()); + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: { const PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D @@ -718,14 +832,7 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.endElement(); writeLineAttribute(rWriter, rPolygonStrokePrimitive2D.getLineAttribute()); - - rWriter.startElement("stroke"); - const drawinglayer::attribute::StrokeAttribute& aStrokeAttribute - = rPolygonStrokePrimitive2D.getStrokeAttribute(); - rWriter.attribute("fulldotdashlen", aStrokeAttribute.getFullDotDashLen()); - //rWriter.attribute("dotdasharray", aStrokeAttribute.getDotDashArray()); - rWriter.endElement(); - + writeStrokeAttribute(rWriter, rPolygonStrokePrimitive2D.getStrokeAttribute()); rWriter.endElement(); } break; @@ -736,9 +843,7 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.startElement("polypolygonstroke"); writeLineAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getLineAttribute()); - - //getStrokeAttribute() - + writeStrokeAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getStrokeAttribute()); writePolyPolygon(rWriter, rPolyPolygonStrokePrimitive2D.getB2DPolyPolygon()); rWriter.endElement(); @@ -823,6 +928,15 @@ void Primitive2dXmlDump::decomposeAndWrite( const drawinglayer::attribute::FontAttribute& aFontAttribute = rTextSimplePortionPrimitive2D.getFontAttribute(); rWriter.attribute("familyname", aFontAttribute.getFamilyName()); + const std::vector<double> aDx = rTextSimplePortionPrimitive2D.getDXArray(); + if (aDx.size()) + { + for (size_t iDx = 0; iDx < aDx.size(); ++iDx) + { + OString sName = "dx" + OString::number(iDx); + rWriter.attribute(sName, OString::number(aDx[iDx])); + } + } rWriter.endElement(); } break; @@ -872,18 +986,46 @@ void Primitive2dXmlDump::decomposeAndWrite( } break; + case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D: + { + const StructureTagPrimitive2D& rStructureTagPrimitive2D + = dynamic_cast<const StructureTagPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("structuretag"); + rWriter.attribute("structureelement", + rStructureTagPrimitive2D.getStructureElement()); + + decomposeAndWrite(rStructureTagPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D: { const SvgRadialGradientPrimitive2D& rSvgRadialGradientPrimitive2D = dynamic_cast<const SvgRadialGradientPrimitive2D&>(*pBasePrimitive); rWriter.startElement("svgradialgradient"); - basegfx::B2DPoint aFocusAttribute = rSvgRadialGradientPrimitive2D.getFocal(); + if (rSvgRadialGradientPrimitive2D.isFocalSet()) + { + basegfx::B2DPoint aFocalAttribute = rSvgRadialGradientPrimitive2D.getFocal(); + rWriter.attribute("focalx", aFocalAttribute.getX()); + rWriter.attribute("focaly", aFocalAttribute.getY()); + } + basegfx::B2DPoint aStartPoint = rSvgRadialGradientPrimitive2D.getStart(); + rWriter.attribute("startx", aStartPoint.getX()); + rWriter.attribute("starty", aStartPoint.getY()); rWriter.attribute("radius", OString::number(rSvgRadialGradientPrimitive2D.getRadius())); - rWriter.attribute("focusx", aFocusAttribute.getX()); - rWriter.attribute("focusy", aFocusAttribute.getY()); + writeSpreadMethod(rWriter, rSvgRadialGradientPrimitive2D.getSpreadMethod()); + rWriter.attributeDouble( + "opacity", + rSvgRadialGradientPrimitive2D.getGradientEntries().front().getOpacity()); + + rWriter.startElement("transform"); + writeMatrix(rWriter, rSvgRadialGradientPrimitive2D.getGradientTransform()); + rWriter.endElement(); + writePolyPolygon(rWriter, rSvgRadialGradientPrimitive2D.getPolyPolygon()); rWriter.endElement(); } break; @@ -900,7 +1042,7 @@ void Primitive2dXmlDump::decomposeAndWrite( rWriter.attribute("starty", aStartAttribute.getY()); rWriter.attribute("endx", aEndAttribute.getX()); rWriter.attribute("endy", aEndAttribute.getY()); - //rWriter.attribute("spreadmethod", (int)rSvgLinearGradientPrimitive2D.getSpreadMethod()); + writeSpreadMethod(rWriter, rSvgLinearGradientPrimitive2D.getSpreadMethod()); rWriter.attributeDouble( "opacity", rSvgLinearGradientPrimitive2D.getGradientEntries().front().getOpacity()); @@ -993,11 +1135,17 @@ void Primitive2dXmlDump::decomposeAndWrite( case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: { // ShadowPrimitive2D. + const ShadowPrimitive2D& rShadowPrimitive2D + = dynamic_cast<const ShadowPrimitive2D&>(*pBasePrimitive); rWriter.startElement("shadow"); - drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; - pBasePrimitive->get2DDecomposition(aPrimitiveContainer, - drawinglayer::geometry::ViewInformation2D()); - decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.attribute("color", + convertColorToString(rShadowPrimitive2D.getShadowColor())); + rWriter.attributeDouble("blur", rShadowPrimitive2D.getShadowBlur()); + + rWriter.startElement("transform"); + writeMatrix(rWriter, rShadowPrimitive2D.getShadowTransform()); + rWriter.endElement(); + rWriter.endElement(); break; } @@ -1005,11 +1153,26 @@ void Primitive2dXmlDump::decomposeAndWrite( case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: { // ModifiedColorPrimitive2D. + const ModifiedColorPrimitive2D& rModifiedColorPrimitive2D + = dynamic_cast<const ModifiedColorPrimitive2D&>(*pBasePrimitive); rWriter.startElement("modifiedColor"); - drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; - pBasePrimitive->get2DDecomposition(aPrimitiveContainer, - drawinglayer::geometry::ViewInformation2D()); - decomposeAndWrite(aPrimitiveContainer, rWriter); + const basegfx::BColorModifierSharedPtr& aColorModifier + = rModifiedColorPrimitive2D.getColorModifier(); + rWriter.attribute("modifier", aColorModifier->getModifierName()); + + decomposeAndWrite(rModifiedColorPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + break; + } + case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: + { + // SoftEdgePrimitive2D. + const SoftEdgePrimitive2D& rSoftEdgePrimitive2D + = dynamic_cast<const SoftEdgePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("softedge"); + rWriter.attribute("radius", OUString::number(rSoftEdgePrimitive2D.getRadius())); + + decomposeAndWrite(rSoftEdgePrimitive2D.getChildren(), rWriter); rWriter.endElement(); break; } @@ -1053,29 +1216,10 @@ void Primitive2dXmlDump::decomposeAndWrite( default: { - OString aName("unhandled"); - switch (nId) - { - case PRIMITIVE2D_ID_RANGE_SVX | 14: // PRIMITIVE2D_ID_SDRCELLPRIMITIVE2D - { - aName = "sdrCell"; - break; - } - } - rWriter.startElement(aName); - rWriter.attribute("id", - OUStringToOString(sCurrentElementTag, RTL_TEXTENCODING_UTF8)); + rWriter.startElement("unhandled"); + rWriter.attribute("id", sCurrentElementTag); rWriter.attribute("idNumber", nId); - auto pBufferedDecomposition - = dynamic_cast<const BufferedDecompositionPrimitive2D*>(pBasePrimitive); - if (pBufferedDecomposition) - { - rWriter.attribute( - "transparenceForShadow", - OString::number(pBufferedDecomposition->getTransparenceForShadow())); - } - drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; pBasePrimitive->get2DDecomposition(aPrimitiveContainer, drawinglayer::geometry::ViewInformation2D()); diff --git a/drawinglayer/source/tools/wmfemfhelper.cxx b/drawinglayer/source/tools/wmfemfhelper.cxx index 22b1d4217413..13634c9ad1a5 100644 --- a/drawinglayer/source/tools/wmfemfhelper.cxx +++ b/drawinglayer/source/tools/wmfemfhelper.cxx @@ -21,10 +21,12 @@ #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> #include <vcl/lineinfo.hxx> #include <vcl/metaact.hxx> -#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/gradienttools.hxx> #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> @@ -75,7 +77,7 @@ namespace drawinglayer::primitive2d { protected: /// local decomposition. - virtual void create2DDecomposition(Primitive2DContainer& rContainer, + virtual Primitive2DReference create2DDecomposition( const geometry::ViewInformation2D& rViewInformation) const override; public: @@ -90,14 +92,14 @@ namespace drawinglayer::primitive2d } - void NonOverlappingFillGradientPrimitive2D::create2DDecomposition( - Primitive2DContainer& rContainer, + Primitive2DReference NonOverlappingFillGradientPrimitive2D::create2DDecomposition( const geometry::ViewInformation2D& /*rViewInformation*/) const { if (!getFillGradient().isDefault()) { - createFill(rContainer, false); + return createFill(false); } + return nullptr; } } // end of namespace @@ -332,15 +334,7 @@ namespace wmfemfhelper drawinglayer::primitive2d::Primitive2DContainer TargetHolder::getPrimitive2DSequence(const PropertyHolder& rPropertyHolder) { - const sal_uInt32 nCount(aTargets.size()); - drawinglayer::primitive2d::Primitive2DContainer xRetval(nCount); - - for (sal_uInt32 a(0); a < nCount; a++) - { - xRetval[a] = aTargets[a].get(); - } - // Since we have released them from the list - aTargets.clear(); + drawinglayer::primitive2d::Primitive2DContainer xRetval = std::move(aTargets); if (!xRetval.empty() && rPropertyHolder.getClipPolyPolygonActive()) { @@ -348,12 +342,11 @@ namespace wmfemfhelper if (rClipPolyPolygon.count()) { - drawinglayer::primitive2d::Primitive2DReference xMask( - new drawinglayer::primitive2d::MaskPrimitive2D( - rClipPolyPolygon, - std::move(xRetval))); - - xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask }; + xRetval = drawinglayer::primitive2d::Primitive2DContainer{ + new drawinglayer::primitive2d::MaskPrimitive2D( + rClipPolyPolygon, + std::move(xRetval)) + }; } } @@ -476,7 +469,7 @@ namespace wmfemfhelper aLinePolygon.transform(rProperties.getTransformation()); rTarget.append( new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( - aLinePolygon, + std::move(aLinePolygon), rProperties.getLineColor())); } } @@ -493,7 +486,7 @@ namespace wmfemfhelper aFillPolyPolygon.transform(rProperties.getTransformation()); rTarget.append( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( - aFillPolyPolygon, + std::move(aFillPolyPolygon), rProperties.getFillColor())); } } @@ -515,7 +508,7 @@ namespace wmfemfhelper { basegfx::B2DPolygon aLinePolygon(rLinePolygon); aLinePolygon.transform(rProperties.getTransformation()); - const drawinglayer::attribute::LineAttribute aLineAttribute( + drawinglayer::attribute::LineAttribute aLineAttribute( rProperties.getLineColor(), bWidthUsed ? rLineInfo.GetWidth() : 0.0, rLineInfo.GetLineJoin(), @@ -523,39 +516,23 @@ namespace wmfemfhelper if(bDashDotUsed) { - std::vector< double > fDotDashArray; - const double fDashLen(rLineInfo.GetDashLen()); - const double fDotLen(rLineInfo.GetDotLen()); - const double fDistance(rLineInfo.GetDistance()); - - for(sal_uInt16 a(0); a < rLineInfo.GetDashCount(); a++) - { - fDotDashArray.push_back(fDashLen); - fDotDashArray.push_back(fDistance); - } - - for(sal_uInt16 b(0); b < rLineInfo.GetDotCount(); b++) - { - fDotDashArray.push_back(fDotLen); - fDotDashArray.push_back(fDistance); - } - + std::vector< double > fDotDashArray = rLineInfo.GetDotDashArray(); const double fAccumulated(std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0)); - const drawinglayer::attribute::StrokeAttribute aStrokeAttribute( + drawinglayer::attribute::StrokeAttribute aStrokeAttribute( std::move(fDotDashArray), fAccumulated); rTarget.append( new drawinglayer::primitive2d::PolygonStrokePrimitive2D( - aLinePolygon, - aLineAttribute, - aStrokeAttribute)); + std::move(aLinePolygon), + std::move(aLineAttribute), + std::move(aStrokeAttribute))); } else { rTarget.append( new drawinglayer::primitive2d::PolygonStrokePrimitive2D( - aLinePolygon, + std::move(aLinePolygon), aLineAttribute)); } } @@ -648,7 +625,7 @@ namespace wmfemfhelper rTarget.append( new drawinglayer::primitive2d::BitmapPrimitive2D( - VCLUnoHelper::CreateVCLXBitmap(rBitmapEx), + rBitmapEx, aObjectTransform)); } @@ -663,8 +640,8 @@ namespace wmfemfhelper aWhite, rMaskColor }; - Bitmap aMask(rBitmap.CreateMask(aWhite)); - Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N1_BPP, &aBiLevelPalette); + AlphaMask aMask(rBitmap.CreateAlphaMask(aWhite)); + Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aBiLevelPalette); aSolid.Erase(rMaskColor); @@ -696,50 +673,13 @@ namespace wmfemfhelper aEnd = interpolate(aBlack, aEnd, static_cast<double>(nEndIntens) * 0.01); } - drawinglayer::attribute::GradientStyle aGradientStyle(drawinglayer::attribute::GradientStyle::Rect); - - switch(rGradient.GetStyle()) - { - case GradientStyle::Linear : - { - aGradientStyle = drawinglayer::attribute::GradientStyle::Linear; - break; - } - case GradientStyle::Axial : - { - aGradientStyle = drawinglayer::attribute::GradientStyle::Axial; - break; - } - case GradientStyle::Radial : - { - aGradientStyle = drawinglayer::attribute::GradientStyle::Radial; - break; - } - case GradientStyle::Elliptical : - { - aGradientStyle = drawinglayer::attribute::GradientStyle::Elliptical; - break; - } - case GradientStyle::Square : - { - aGradientStyle = drawinglayer::attribute::GradientStyle::Square; - break; - } - default : // GradientStyle::Rect - { - aGradientStyle = drawinglayer::attribute::GradientStyle::Rect; - break; - } - } - return drawinglayer::attribute::FillGradientAttribute( - aGradientStyle, + rGradient.GetStyle(), static_cast<double>(rGradient.GetBorder()) * 0.01, static_cast<double>(rGradient.GetOfsX()) * 0.01, static_cast<double>(rGradient.GetOfsY()) * 0.01, toRadians(rGradient.GetAngle()), - aStart, - aEnd, + basegfx::BColorStops(aStart, aEnd), rGradient.GetSteps()); } @@ -939,12 +879,13 @@ namespace wmfemfhelper const Gradient& rGradient, PropertyHolder const & rPropertyHolder) { - const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + basegfx::BColor aSingleColor; - if(aAttribute.getStartColor() == aAttribute.getEndColor()) + if (aAttribute.getColorStops().isSingleColor(aSingleColor)) { // not really a gradient. Create filled rectangle - return CreateColorWallpaper(rRange, aAttribute.getStartColor(), rPropertyHolder); + return CreateColorWallpaper(rRange, aSingleColor, rPropertyHolder); } else { @@ -952,7 +893,7 @@ namespace wmfemfhelper rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pRetval( new drawinglayer::primitive2d::FillGradientPrimitive2D( rRange, - aAttribute)); + std::move(aAttribute))); if(!rPropertyHolder.getTransformation().isIdentity()) { @@ -1099,6 +1040,7 @@ namespace wmfemfhelper sal_uInt16 nTextStart, sal_uInt16 nTextLength, std::vector< double >&& rDXArray, + std::vector< sal_Bool >&& rKashidaArray, TargetHolder& rTarget, PropertyHolder const & rProperty) { @@ -1124,7 +1066,7 @@ namespace wmfemfhelper // prepare FontColor and Locale const basegfx::BColor aFontColor(rProperty.getTextColor()); const Color aFillColor(rFont.GetFillColor()); - const css::lang::Locale aLocale(LanguageTag(rProperty.getLanguageType()).getLocale()); + css::lang::Locale aLocale(LanguageTag(rProperty.getLanguageType()).getLocale()); const bool bWordLineMode(rFont.IsWordLineMode()); const bool bDecoratedIsNeeded( @@ -1183,6 +1125,7 @@ namespace wmfemfhelper nTextStart, nTextLength, std::move(rDXArray), + std::move(rKashidaArray), aFontAttribute, aLocale, aFontColor, @@ -1211,8 +1154,9 @@ namespace wmfemfhelper nTextStart, nTextLength, std::vector(rDXArray), - aFontAttribute, - aLocale, + std::vector(rKashidaArray), + std::move(aFontAttribute), + std::move(aLocale), aFontColor); } } @@ -1256,7 +1200,7 @@ namespace wmfemfhelper // prepare Primitive2DSequence, put text in foreground drawinglayer::primitive2d::Primitive2DContainer aSequence(2); - aSequence[1] = drawinglayer::primitive2d::Primitive2DReference(pResult); + aSequence[1] = pResult; // prepare filled polygon basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aTextRange)); @@ -1314,7 +1258,7 @@ namespace wmfemfhelper if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)) return; - std::vector< drawinglayer::primitive2d::BasePrimitive2D* > aTargetVector; + drawinglayer::primitive2d::Primitive2DContainer aTargets; basegfx::B2DVector aAlignmentOffset(0.0, 0.0); drawinglayer::attribute::FontAttribute aFontAttribute; basegfx::B2DHomMatrix aTextTransform; @@ -1336,7 +1280,7 @@ namespace wmfemfhelper if(bOverlineUsed) { // create primitive geometry for overline - aTargetVector.push_back( + aTargets.push_back( new drawinglayer::primitive2d::TextLinePrimitive2D( aTextTransform, fLineWidth, @@ -1349,7 +1293,7 @@ namespace wmfemfhelper if(bUnderlineUsed) { // create primitive geometry for underline - aTargetVector.push_back( + aTargets.push_back( new drawinglayer::primitive2d::TextLinePrimitive2D( aTextTransform, fLineWidth, @@ -1368,22 +1312,22 @@ namespace wmfemfhelper // strikeout with character const sal_Unicode aStrikeoutChar( drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout ? '/' : 'X'); - const css::lang::Locale aLocale(LanguageTag( + css::lang::Locale aLocale(LanguageTag( rProperty.getLanguageType()).getLocale()); - aTargetVector.push_back( + aTargets.push_back( new drawinglayer::primitive2d::TextCharacterStrikeoutPrimitive2D( aTextTransform, fLineWidth, rProperty.getTextColor(), aStrikeoutChar, - aFontAttribute, - aLocale)); + std::move(aFontAttribute), + std::move(aLocale))); } else { // strikeout with geometry - aTargetVector.push_back( + aTargets.push_back( new drawinglayer::primitive2d::TextGeometryStrikeoutPrimitive2D( aTextTransform, fLineWidth, @@ -1394,31 +1338,21 @@ namespace wmfemfhelper } } - if(aTargetVector.empty()) + if(aTargets.empty()) return; // add created text primitive to target if(rProperty.getTransformation().isIdentity()) { - for(drawinglayer::primitive2d::BasePrimitive2D* a : aTargetVector) - { - rTarget.append(a); - } + rTarget.append(std::move(aTargets)); } else { // when a transformation is set, embed to it - drawinglayer::primitive2d::Primitive2DContainer xTargets(aTargetVector.size()); - - for(size_t a(0); a < aTargetVector.size(); a++) - { - xTargets[a] = drawinglayer::primitive2d::Primitive2DReference(aTargetVector[a]); - } - rTarget.append( new drawinglayer::primitive2d::TransformPrimitive2D( rProperty.getTransformation(), - std::move(xTargets))); + std::move(aTargets))); } } @@ -1563,7 +1497,6 @@ namespace wmfemfhelper } else { - aLineInfo.SetLineJoin(basegfx::B2DLineJoin::NONE); // It were lines; force to NONE createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current()); aLinePolygon.clear(); aLineInfo = pA->GetLineInfo(); @@ -1578,16 +1511,14 @@ namespace wmfemfhelper aLinePolygon.append(aEnd); } - nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction); + nAction++; + if (nAction < nCount) + pAction = rMetaFile.GetAction(nAction); } nAction--; - - if(aLinePolygon.count()) - { - aLineInfo.SetLineJoin(basegfx::B2DLineJoin::NONE); // It were lines; force to NONE + if (aLinePolygon.count()) createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current()); - } } break; @@ -1805,6 +1736,7 @@ namespace wmfemfhelper nTextIndex, nTextLength, std::move(aDXArray), + {}, rTargetHolders.Current(), rPropertyHolders.Current()); } @@ -1828,7 +1760,8 @@ namespace wmfemfhelper { // prepare DXArray (if used) std::vector< double > aDXArray; - const std::vector<sal_Int32> & rDXArray = pA->GetDXArray(); + const KernArray& rDXArray = pA->GetDXArray(); + std::vector< sal_Bool > aKashidaArray = pA->GetKashidaArray(); if(!rDXArray.empty()) { @@ -1846,6 +1779,7 @@ namespace wmfemfhelper nTextIndex, nTextLength, std::move(aDXArray), + std::move(aKashidaArray), rTargetHolders.Current(), rPropertyHolders.Current()); } @@ -1909,6 +1843,7 @@ namespace wmfemfhelper nTextIndex, nTextLength, std::move(aTextArray), + {}, rTargetHolders.Current(), rPropertyHolders.Current()); } @@ -2114,10 +2049,11 @@ namespace wmfemfhelper if(!aRange.isEmpty()) { const Gradient& rGradient = pA->GetGradient(); - const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + basegfx::BColor aSingleColor; - if(aAttribute.getStartColor() == aAttribute.getEndColor()) + if (aAttribute.getColorStops().isSingleColor(aSingleColor)) { // not really a gradient. Create filled rectangle createFillPrimitive( @@ -2146,7 +2082,7 @@ namespace wmfemfhelper xGradient[0] = drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::FillGradientPrimitive2D( aRange, - aAttribute)); + std::move(aAttribute))); } // #i112300# clip against polygon representing the rectangle from @@ -2155,7 +2091,7 @@ namespace wmfemfhelper aOutline.transform(rPropertyHolders.Current().getTransformation()); rTargetHolders.Current().append( new drawinglayer::primitive2d::MaskPrimitive2D( - aOutline, + std::move(aOutline), std::move(xGradient))); } } @@ -2172,7 +2108,7 @@ namespace wmfemfhelper if(aOutline.count()) { const Hatch& rHatch = pA->GetHatch(); - const drawinglayer::attribute::FillHatchAttribute aAttribute(createFillHatchAttribute(rHatch)); + drawinglayer::attribute::FillHatchAttribute aAttribute(createFillHatchAttribute(rHatch)); aOutline.transform(rPropertyHolders.Current().getTransformation()); @@ -2181,11 +2117,11 @@ namespace wmfemfhelper new drawinglayer::primitive2d::FillHatchPrimitive2D( aObjectRange, basegfx::BColor(), - aAttribute)); + std::move(aAttribute))); rTargetHolders.Current().append( new drawinglayer::primitive2d::MaskPrimitive2D( - aOutline, + std::move(aOutline), drawinglayer::primitive2d::Primitive2DContainer { aFillHatch })); } @@ -2825,15 +2761,16 @@ namespace wmfemfhelper // check if gradient is a real gradient const Gradient& rGradient = pA->GetGradient(); - const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + basegfx::BColor aSingleColor; - if(aAttribute.getStartColor() == aAttribute.getEndColor()) + if (aAttribute.getColorStops().isSingleColor(aSingleColor)) { // not really a gradient; create UnifiedTransparencePrimitive2D rTargetHolders.Current().append( new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( std::move(xSubContent), - aAttribute.getStartColor().luminance())); + aSingleColor.luminance())); } else { @@ -2845,7 +2782,7 @@ namespace wmfemfhelper const drawinglayer::primitive2d::Primitive2DReference xTransparence( new drawinglayer::primitive2d::FillGradientPrimitive2D( aRange, - aAttribute)); + std::move(aAttribute))); // create transparence primitive rTargetHolders.Current().append( @@ -2945,15 +2882,16 @@ namespace wmfemfhelper // get and check if gradient is a real gradient const Gradient& rGradient = pMetaGradientExAction->GetGradient(); - const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + basegfx::BColor aSingleColor; - if(aAttribute.getStartColor() == aAttribute.getEndColor()) + if (aAttribute.getColorStops().isSingleColor(aSingleColor)) { // not really a gradient rTargetHolders.Current().append( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( - aPolyPolygon, - aAttribute.getStartColor())); + std::move(aPolyPolygon), + aSingleColor)); } else { @@ -2961,7 +2899,7 @@ namespace wmfemfhelper rTargetHolders.Current().append( new drawinglayer::primitive2d::PolyPolygonGradientPrimitive2D( aPolyPolygon, - aAttribute)); + std::move(aAttribute))); } } } |