diff options
Diffstat (limited to '')
163 files changed, 45180 insertions, 0 deletions
diff --git a/drawinglayer/source/animation/animationtiming.cxx b/drawinglayer/source/animation/animationtiming.cxx new file mode 100644 index 000000000..c1471e43b --- /dev/null +++ b/drawinglayer/source/animation/animationtiming.cxx @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> + +#include <drawinglayer/animation/animationtiming.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace drawinglayer::animation +{ + + + AnimationEntry::AnimationEntry() + { + } + + AnimationEntry::~AnimationEntry() + { + } + + + AnimationEntryFixed::AnimationEntryFixed(double fDuration, double fState) + : mfDuration(fDuration), + mfState(fState) + { + } + + AnimationEntryFixed::~AnimationEntryFixed() + { + } + + std::unique_ptr<AnimationEntry> AnimationEntryFixed::clone() const + { + return std::make_unique<AnimationEntryFixed>(mfDuration, mfState); + } + + bool AnimationEntryFixed::operator==(const AnimationEntry& rCandidate) const + { + const AnimationEntryFixed* pCompare = dynamic_cast< const AnimationEntryFixed* >(&rCandidate); + + return (pCompare + && basegfx::fTools::equal(mfDuration, pCompare->mfDuration) + && basegfx::fTools::equal(mfState, pCompare->mfState)); + } + + double AnimationEntryFixed::getDuration() const + { + return mfDuration; + } + + double AnimationEntryFixed::getStateAtTime(double /*fTime*/) const + { + return mfState; + } + + double AnimationEntryFixed::getNextEventTime(double fTime) const + { + if(basegfx::fTools::less(fTime, mfDuration)) + { + return mfDuration; + } + else + { + return 0.0; + } + } + + + AnimationEntryLinear::AnimationEntryLinear(double fDuration, double fFrequency, double fStart, double fStop) + : mfDuration(fDuration), + mfFrequency(fFrequency), + mfStart(fStart), + mfStop(fStop) + { + } + + AnimationEntryLinear::~AnimationEntryLinear() + { + } + + std::unique_ptr<AnimationEntry> AnimationEntryLinear::clone() const + { + return std::make_unique<AnimationEntryLinear>(mfDuration, mfFrequency, mfStart, mfStop); + } + + bool AnimationEntryLinear::operator==(const AnimationEntry& rCandidate) const + { + const AnimationEntryLinear* pCompare = dynamic_cast< const AnimationEntryLinear* >(&rCandidate); + + return (pCompare + && basegfx::fTools::equal(mfDuration, pCompare->mfDuration) + && basegfx::fTools::equal(mfStart, pCompare->mfStart) + && basegfx::fTools::equal(mfStop, pCompare->mfStop)); + } + + double AnimationEntryLinear::getDuration() const + { + return mfDuration; + } + + double AnimationEntryLinear::getStateAtTime(double fTime) const + { + if(basegfx::fTools::more(mfDuration, 0.0)) + { + const double fFactor(fTime / mfDuration); + + if(fFactor > 1.0) + { + return mfStop; + } + else + { + return mfStart + ((mfStop - mfStart) * fFactor); + } + } + else + { + return mfStart; + } + } + + double AnimationEntryLinear::getNextEventTime(double fTime) const + { + if(basegfx::fTools::less(fTime, mfDuration)) + { + // use the simple solution: just add the frequency. More correct (but also more + // complicated) would be to calculate the slice of time we are in and when this + // slice will end. For the animations, this makes no quality difference. + fTime += mfFrequency; + + if(basegfx::fTools::more(fTime, mfDuration)) + { + fTime = mfDuration; + } + + return fTime; + } + else + { + return 0.0; + } + } + + + AnimationEntryList::Entries::size_type AnimationEntryList::impGetIndexAtTime(double fTime, double &rfAddedTime) const + { + Entries::size_type nIndex(0); + + while(nIndex < maEntries.size() && basegfx::fTools::lessOrEqual(rfAddedTime + maEntries[nIndex]->getDuration(), fTime)) + { + rfAddedTime += maEntries[nIndex++]->getDuration(); + } + + return nIndex; + } + + AnimationEntryList::AnimationEntryList() + : mfDuration(0.0) + { + } + + AnimationEntryList::~AnimationEntryList() + { + } + + std::unique_ptr<AnimationEntry> AnimationEntryList::clone() const + { + std::unique_ptr<AnimationEntryList> pNew(std::make_unique<AnimationEntryList>()); + + for(const auto &i : maEntries) + { + pNew->append(*i); + } + + return pNew; + } + + bool AnimationEntryList::operator==(const AnimationEntry& rCandidate) const + { + const AnimationEntryList* pCompare = dynamic_cast< const AnimationEntryList* >(&rCandidate); + + 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 false; + } + + void AnimationEntryList::append(const AnimationEntry& rCandidate) + { + const double fDuration(rCandidate.getDuration()); + + if(!basegfx::fTools::equalZero(fDuration)) + { + maEntries.push_back(rCandidate.clone()); + mfDuration += fDuration; + } + } + + double AnimationEntryList::getDuration() const + { + return mfDuration; + } + + double AnimationEntryList::getStateAtTime(double fTime) const + { + if(!basegfx::fTools::equalZero(mfDuration)) + { + double fAddedTime(0.0); + const auto nIndex(impGetIndexAtTime(fTime, fAddedTime)); + + if(nIndex < maEntries.size()) + { + return maEntries[nIndex]->getStateAtTime(fTime - fAddedTime); + } + } + + return 0.0; + } + + double AnimationEntryList::getNextEventTime(double fTime) const + { + double fNewTime(0.0); + + if(!basegfx::fTools::equalZero(mfDuration)) + { + double fAddedTime(0.0); + const auto nIndex(impGetIndexAtTime(fTime, fAddedTime)); + + if(nIndex < maEntries.size()) + { + fNewTime = maEntries[nIndex]->getNextEventTime(fTime - fAddedTime) + fAddedTime; + } + } + + return fNewTime; + } + + + AnimationEntryLoop::AnimationEntryLoop(sal_uInt32 nRepeat) + : mnRepeat(nRepeat) + { + } + + AnimationEntryLoop::~AnimationEntryLoop() + { + } + + std::unique_ptr<AnimationEntry> AnimationEntryLoop::clone() const + { + std::unique_ptr<AnimationEntryLoop> pNew(std::make_unique<AnimationEntryLoop>(mnRepeat)); + + for(const auto &i : maEntries) + { + pNew->append(*i); + } + + return pNew; + } + + bool AnimationEntryLoop::operator==(const AnimationEntry& rCandidate) const + { + const AnimationEntryLoop* pCompare = dynamic_cast< const AnimationEntryLoop* >(&rCandidate); + + return (pCompare + && mnRepeat == pCompare->mnRepeat + && AnimationEntryList::operator==(rCandidate)); + } + + double AnimationEntryLoop::getDuration() const + { + return (mfDuration * static_cast<double>(mnRepeat)); + } + + double AnimationEntryLoop::getStateAtTime(double fTime) const + { + if(mnRepeat && !basegfx::fTools::equalZero(mfDuration)) + { + const sal_uInt32 nCurrentLoop(static_cast<sal_uInt32>(fTime / mfDuration)); + + if(nCurrentLoop > mnRepeat) + { + return 1.0; + } + else + { + const double fTimeAtLoopStart(static_cast<double>(nCurrentLoop) * mfDuration); + const double fRelativeTime(fTime - fTimeAtLoopStart); + return AnimationEntryList::getStateAtTime(fRelativeTime); + } + } + + return 0.0; + } + + double AnimationEntryLoop::getNextEventTime(double fTime) const + { + double fNewTime(0.0); + + if(mnRepeat && !basegfx::fTools::equalZero(mfDuration)) + { + const sal_uInt32 nCurrentLoop(static_cast<sal_uInt32>(fTime / mfDuration)); + + if(nCurrentLoop <= mnRepeat) + { + const double fTimeAtLoopStart(static_cast<double>(nCurrentLoop) * mfDuration); + const double fRelativeTime(fTime - fTimeAtLoopStart); + const double fNextEventAtLoop(AnimationEntryList::getNextEventTime(fRelativeTime)); + + if(!basegfx::fTools::equalZero(fNextEventAtLoop)) + { + fNewTime = fNextEventAtLoop + fTimeAtLoopStart; + } + } + } + + return fNewTime; + } +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/fillgradientattribute.cxx b/drawinglayer/source/attribute/fillgradientattribute.cxx new file mode 100644 index 000000000..b8b06e20b --- /dev/null +++ b/drawinglayer/source/attribute/fillgradientattribute.cxx @@ -0,0 +1,186 @@ +/* -*- 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/attribute/fillgradientattribute.hxx> +#include <basegfx/color/bcolor.hxx> + + +namespace drawinglayer::attribute +{ + class ImpFillGradientAttribute + { + public: + // data definitions + double mfBorder; + double mfOffsetX; + double mfOffsetY; + double mfAngle; + basegfx::BColor maStartColor; + basegfx::BColor maEndColor; + GradientStyle meStyle; + sal_uInt16 mnSteps; + + ImpFillGradientAttribute( + GradientStyle eStyle, + double fBorder, + double fOffsetX, + double fOffsetY, + double fAngle, + const basegfx::BColor& rStartColor, + const basegfx::BColor& rEndColor, + sal_uInt16 nSteps) + : mfBorder(fBorder), + mfOffsetX(fOffsetX), + mfOffsetY(fOffsetY), + mfAngle(fAngle), + maStartColor(rStartColor), + maEndColor(rEndColor), + meStyle(eStyle), + mnSteps(nSteps) + { + } + + ImpFillGradientAttribute() + : mfBorder(0.0), + mfOffsetX(0.0), + mfOffsetY(0.0), + mfAngle(0.0), + meStyle(GradientStyle::Linear), + mnSteps(0) + { + } + + // data read access + 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; } + sal_uInt16 getSteps() const { return mnSteps; } + + bool operator==(const ImpFillGradientAttribute& rCandidate) const + { + return (getStyle() == rCandidate.getStyle() + && getBorder() == rCandidate.getBorder() + && getOffsetX() == rCandidate.getOffsetX() + && getOffsetY() == rCandidate.getOffsetY() + && getAngle() == rCandidate.getAngle() + && getStartColor() == rCandidate.getStartColor() + && getEndColor() == rCandidate.getEndColor() + && getSteps() == rCandidate.getSteps()); + } + }; + + namespace + { + FillGradientAttribute::ImplType& theGlobalDefault() + { + static FillGradientAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + FillGradientAttribute::FillGradientAttribute( + GradientStyle eStyle, + double fBorder, + double fOffsetX, + double fOffsetY, + double fAngle, + const basegfx::BColor& rStartColor, + const basegfx::BColor& rEndColor, + sal_uInt16 nSteps) + : mpFillGradientAttribute(ImpFillGradientAttribute( + eStyle, fBorder, fOffsetX, fOffsetY, fAngle, rStartColor, rEndColor, nSteps)) + { + } + + FillGradientAttribute::FillGradientAttribute() + : mpFillGradientAttribute(theGlobalDefault()) + { + } + + FillGradientAttribute::FillGradientAttribute(const FillGradientAttribute&) = default; + + FillGradientAttribute::FillGradientAttribute(FillGradientAttribute&&) = default; + + FillGradientAttribute::~FillGradientAttribute() = default; + + bool FillGradientAttribute::isDefault() const + { + return mpFillGradientAttribute.same_object(theGlobalDefault()); + } + + FillGradientAttribute& FillGradientAttribute::operator=(const FillGradientAttribute&) = default; + + FillGradientAttribute& FillGradientAttribute::operator=(FillGradientAttribute&&) = default; + + bool FillGradientAttribute::operator==(const FillGradientAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpFillGradientAttribute == mpFillGradientAttribute; + } + + const basegfx::BColor& FillGradientAttribute::getStartColor() const + { + return mpFillGradientAttribute->getStartColor(); + } + + const basegfx::BColor& FillGradientAttribute::getEndColor() const + { + return mpFillGradientAttribute->getEndColor(); + } + + double FillGradientAttribute::getBorder() const + { + return mpFillGradientAttribute->getBorder(); + } + + double FillGradientAttribute::getOffsetX() const + { + return mpFillGradientAttribute->getOffsetX(); + } + + double FillGradientAttribute::getOffsetY() const + { + return mpFillGradientAttribute->getOffsetY(); + } + + double FillGradientAttribute::getAngle() const + { + return mpFillGradientAttribute->getAngle(); + } + + GradientStyle FillGradientAttribute::getStyle() const + { + return mpFillGradientAttribute->getStyle(); + } + + sal_uInt16 FillGradientAttribute::getSteps() const + { + return mpFillGradientAttribute->getSteps(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/fillgraphicattribute.cxx b/drawinglayer/source/attribute/fillgraphicattribute.cxx new file mode 100644 index 000000000..b36520d4f --- /dev/null +++ b/drawinglayer/source/attribute/fillgraphicattribute.cxx @@ -0,0 +1,156 @@ +/* -*- 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 <algorithm> + +#include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <vcl/graph.hxx> + +namespace drawinglayer::attribute +{ + class ImpFillGraphicAttribute + { + public: + // data definitions + Graphic maGraphic; + basegfx::B2DRange maGraphicRange; + + bool mbTiling : 1; + + // tiling definitions, offsets in X/Y in percent for each 2nd row. + // If both are set, Y is ignored (X has precedence) + double mfOffsetX; + double mfOffsetY; + + ImpFillGraphicAttribute( + const Graphic& rGraphic, + const basegfx::B2DRange& rGraphicRange, + bool bTiling, + double fOffsetX, + double fOffsetY) + : maGraphic(rGraphic), + maGraphicRange(rGraphicRange), + mbTiling(bTiling), + mfOffsetX(fOffsetX), + mfOffsetY(fOffsetY) + { + // access once to ensure that the buffered bitmap exists, else + // the SolarMutex may be needed to create it. This may not be + // available when a renderer works with multi-threading. + // When changing this, please check if it is still possible to + // use a metafile as texture for a 3D object + maGraphic.GetBitmapEx(); + } + + ImpFillGraphicAttribute() + : mbTiling(false), + mfOffsetX(0.0), + mfOffsetY(0.0) + { + } + + // data read access + const Graphic& getGraphic() const { return maGraphic; } + const basegfx::B2DRange& getGraphicRange() const { return maGraphicRange; } + bool getTiling() const { return mbTiling; } + double getOffsetX() const { return mfOffsetX; } + double getOffsetY() const { return mfOffsetY; } + + bool operator==(const ImpFillGraphicAttribute& rCandidate) const + { + return (getGraphic() == rCandidate.getGraphic() + && getGraphicRange() == rCandidate.getGraphicRange() + && getTiling() == rCandidate.getTiling() + && getOffsetX() == rCandidate.getOffsetX() + && getOffsetY() == rCandidate.getOffsetY()); + } + }; + + namespace + { + FillGraphicAttribute::ImplType& theGlobalDefault() + { + static FillGraphicAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + FillGraphicAttribute::FillGraphicAttribute( + const Graphic& rGraphic, + const basegfx::B2DRange& rGraphicRange, + bool bTiling, + double fOffsetX, + double fOffsetY) + : mpFillGraphicAttribute(ImpFillGraphicAttribute( + rGraphic, rGraphicRange, bTiling, + std::clamp(fOffsetX, 0.0, 1.0), + std::clamp(fOffsetY, 0.0, 1.0))) + { + } + + FillGraphicAttribute::FillGraphicAttribute(const FillGraphicAttribute&) = default; + + FillGraphicAttribute::~FillGraphicAttribute() = default; + + bool FillGraphicAttribute::isDefault() const + { + return mpFillGraphicAttribute.same_object(theGlobalDefault()); + } + + FillGraphicAttribute& FillGraphicAttribute::operator=(const FillGraphicAttribute&) = default; + + bool FillGraphicAttribute::operator==(const FillGraphicAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpFillGraphicAttribute == mpFillGraphicAttribute; + } + + const Graphic& FillGraphicAttribute::getGraphic() const + { + return mpFillGraphicAttribute->getGraphic(); + } + + const basegfx::B2DRange& FillGraphicAttribute::getGraphicRange() const + { + return mpFillGraphicAttribute->getGraphicRange(); + } + + bool FillGraphicAttribute::getTiling() const + { + return mpFillGraphicAttribute->getTiling(); + } + + double FillGraphicAttribute::getOffsetX() const + { + return mpFillGraphicAttribute->getOffsetX(); + } + + double FillGraphicAttribute::getOffsetY() const + { + return mpFillGraphicAttribute->getOffsetY(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/fillhatchattribute.cxx b/drawinglayer/source/attribute/fillhatchattribute.cxx new file mode 100644 index 000000000..bc892a024 --- /dev/null +++ b/drawinglayer/source/attribute/fillhatchattribute.cxx @@ -0,0 +1,166 @@ +/* -*- 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/attribute/fillhatchattribute.hxx> +#include <basegfx/color/bcolor.hxx> + + +namespace drawinglayer::attribute +{ + class ImpFillHatchAttribute + { + public: + // data definitions + HatchStyle meStyle; + double mfDistance; + double mfAngle; + basegfx::BColor maColor; + sal_uInt32 mnMinimalDiscreteDistance; + + bool mbFillBackground : 1; + + ImpFillHatchAttribute( + HatchStyle eStyle, + double fDistance, + double fAngle, + const basegfx::BColor& rColor, + sal_uInt32 nMinimalDiscreteDistance, + bool bFillBackground) + : meStyle(eStyle), + mfDistance(fDistance), + mfAngle(fAngle), + maColor(rColor), + mnMinimalDiscreteDistance(nMinimalDiscreteDistance), + mbFillBackground(bFillBackground) + { + } + + ImpFillHatchAttribute() + : meStyle(HatchStyle::Single), + mfDistance(0.0), + mfAngle(0.0), + mnMinimalDiscreteDistance(3), // same as VCL + mbFillBackground(false) + { + } + + // data read access + HatchStyle getStyle() const { return meStyle; } + double getDistance() const { return mfDistance; } + double getAngle() const { return mfAngle; } + const basegfx::BColor& getColor() const { return maColor; } + sal_uInt32 getMinimalDiscreteDistance() const { return mnMinimalDiscreteDistance; } + bool isFillBackground() const { return mbFillBackground; } + + bool operator==(const ImpFillHatchAttribute& rCandidate) const + { + return (getStyle() == rCandidate.getStyle() + && getDistance() == rCandidate.getDistance() + && getAngle() == rCandidate.getAngle() + && getColor() == rCandidate.getColor() + && getMinimalDiscreteDistance() == rCandidate.getMinimalDiscreteDistance() + && isFillBackground() == rCandidate.isFillBackground()); + } + }; + + namespace + { + FillHatchAttribute::ImplType& theGlobalDefault() + { + static FillHatchAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + FillHatchAttribute::FillHatchAttribute( + HatchStyle eStyle, + double fDistance, + double fAngle, + const basegfx::BColor& rColor, + sal_uInt32 nMinimalDiscreteDistance, + bool bFillBackground) + : mpFillHatchAttribute(ImpFillHatchAttribute( + eStyle, fDistance, fAngle, rColor, + nMinimalDiscreteDistance, bFillBackground)) + { + } + + FillHatchAttribute::FillHatchAttribute() + : mpFillHatchAttribute(theGlobalDefault()) + { + } + + FillHatchAttribute::FillHatchAttribute(const FillHatchAttribute&) = default; + + FillHatchAttribute::FillHatchAttribute(FillHatchAttribute&&) = default; + + FillHatchAttribute::~FillHatchAttribute() = default; + + bool FillHatchAttribute::isDefault() const + { + return mpFillHatchAttribute.same_object(theGlobalDefault()); + } + + FillHatchAttribute& FillHatchAttribute::operator=(const FillHatchAttribute&) = default; + + FillHatchAttribute& FillHatchAttribute::operator=(FillHatchAttribute&&) = default; + + bool FillHatchAttribute::operator==(const FillHatchAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpFillHatchAttribute == mpFillHatchAttribute; + } + + // data read access + HatchStyle FillHatchAttribute::getStyle() const + { + return mpFillHatchAttribute->getStyle(); + } + + double FillHatchAttribute::getDistance() const + { + return mpFillHatchAttribute->getDistance(); + } + + double FillHatchAttribute::getAngle() const + { + return mpFillHatchAttribute->getAngle(); + } + + const basegfx::BColor& FillHatchAttribute::getColor() const + { + return mpFillHatchAttribute->getColor(); + } + + sal_uInt32 FillHatchAttribute::getMinimalDiscreteDistance() const + { + return mpFillHatchAttribute->getMinimalDiscreteDistance(); + } + + bool FillHatchAttribute::isFillBackground() const + { + return mpFillHatchAttribute->isFillBackground(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/fontattribute.cxx b/drawinglayer/source/attribute/fontattribute.cxx new file mode 100644 index 000000000..8ae3836d8 --- /dev/null +++ b/drawinglayer/source/attribute/fontattribute.cxx @@ -0,0 +1,151 @@ +/* -*- 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/attribute/fontattribute.hxx> +#include <rtl/ustring.hxx> + +namespace drawinglayer::attribute +{ +class ImpFontAttribute +{ +public: + /// core data + OUString maFamilyName; // Font Family Name + OUString maStyleName; // Font Style Name + sal_uInt16 mnWeight; // Font weight + + bool mbSymbol : 1; // Symbol Font Flag + bool mbVertical : 1; // Vertical Text Flag + bool mbItalic : 1; // Italic Flag + bool mbOutline : 1; // Outline Flag + bool mbRTL : 1; // RTL Flag + 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) + , mnWeight(nWeight) + , mbSymbol(bSymbol) + , mbVertical(bVertical) + , mbItalic(bItalic) + , mbOutline(bOutline) + , mbRTL(bRTL) + , mbBiDiStrong(bBiDiStrong) + , mbMonospaced(bMonospaced) + { + } + + ImpFontAttribute() + : mnWeight(0) + , mbSymbol(false) + , mbVertical(false) + , mbItalic(false) + , mbOutline(false) + , mbRTL(false) + , mbBiDiStrong(false) + , mbMonospaced(false) + { + } + + // data read access + const OUString& getFamilyName() const { return maFamilyName; } + const OUString& getStyleName() const { return maStyleName; } + sal_uInt16 getWeight() const { return mnWeight; } + bool getSymbol() const { return mbSymbol; } + bool getVertical() const { return mbVertical; } + bool getItalic() const { return mbItalic; } + bool getOutline() const { return mbOutline; } + bool getRTL() const { return mbRTL; } + bool getBiDiStrong() const { return mbBiDiStrong; } + bool getMonospaced() const { return mbMonospaced; } + + bool operator==(const ImpFontAttribute& rCompare) const + { + return (getFamilyName() == rCompare.getFamilyName() + && getStyleName() == rCompare.getStyleName() && getWeight() == rCompare.getWeight() + && getSymbol() == rCompare.getSymbol() && getVertical() == rCompare.getVertical() + && getItalic() == rCompare.getItalic() && getOutline() == rCompare.getOutline() + && getRTL() == rCompare.getRTL() && getBiDiStrong() == rCompare.getBiDiStrong() + && getMonospaced() == rCompare.getMonospaced()); + } +}; + +namespace +{ +FontAttribute::ImplType& theGlobalDefault() +{ + static FontAttribute::ImplType SINGLETON; + return SINGLETON; +} +} + +FontAttribute::FontAttribute(const OUString& rFamilyName, const OUString& rStyleName, + sal_uInt16 nWeight, bool bSymbol, bool bVertical, bool bItalic, + bool bMonospaced, bool bOutline, bool bRTL, bool bBiDiStrong) + : mpFontAttribute(ImpFontAttribute(rFamilyName, rStyleName, nWeight, bSymbol, bVertical, + bItalic, bMonospaced, bOutline, bRTL, bBiDiStrong)) +{ +} + +FontAttribute::FontAttribute() + : mpFontAttribute(theGlobalDefault()) +{ +} + +FontAttribute::FontAttribute(const FontAttribute&) = default; + +FontAttribute::FontAttribute(FontAttribute&&) = default; + +FontAttribute::~FontAttribute() = default; + +FontAttribute& FontAttribute::operator=(const FontAttribute&) = default; + +FontAttribute& FontAttribute::operator=(FontAttribute&&) = default; + +bool FontAttribute::operator==(const FontAttribute& rCandidate) const +{ + return rCandidate.mpFontAttribute == mpFontAttribute; +} + +const OUString& FontAttribute::getFamilyName() const { return mpFontAttribute->getFamilyName(); } + +const OUString& FontAttribute::getStyleName() const { return mpFontAttribute->getStyleName(); } + +sal_uInt16 FontAttribute::getWeight() const { return mpFontAttribute->getWeight(); } + +bool FontAttribute::getSymbol() const { return mpFontAttribute->getSymbol(); } + +bool FontAttribute::getVertical() const { return mpFontAttribute->getVertical(); } + +bool FontAttribute::getItalic() const { return mpFontAttribute->getItalic(); } + +bool FontAttribute::getOutline() const { return mpFontAttribute->getOutline(); } + +bool FontAttribute::getRTL() const { return mpFontAttribute->getRTL(); } + +bool FontAttribute::getBiDiStrong() const { return mpFontAttribute->getBiDiStrong(); } + +bool FontAttribute::getMonospaced() const { return mpFontAttribute->getMonospaced(); } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/lineattribute.cxx b/drawinglayer/source/attribute/lineattribute.cxx new file mode 100644 index 000000000..d4f2418c1 --- /dev/null +++ b/drawinglayer/source/attribute/lineattribute.cxx @@ -0,0 +1,152 @@ +/* -*- 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/attribute/lineattribute.hxx> +#include <basegfx/color/bcolor.hxx> + + +namespace drawinglayer::attribute +{ + class ImpLineAttribute + { + public: + // data definitions + basegfx::BColor maColor; // color + double mfWidth; // absolute line width + basegfx::B2DLineJoin meLineJoin; // type of LineJoin + css::drawing::LineCap meLineCap; // BUTT, ROUND, or SQUARE + double mfMiterMinimumAngle; // as needed for createAreaGeometry + + ImpLineAttribute( + const basegfx::BColor& rColor, + double fWidth, + basegfx::B2DLineJoin aB2DLineJoin, + css::drawing::LineCap aLineCap, + double fMiterMinimumAngle) + : maColor(rColor), + mfWidth(fWidth), + meLineJoin(aB2DLineJoin), + meLineCap(aLineCap), + mfMiterMinimumAngle(fMiterMinimumAngle) + { + } + + ImpLineAttribute() + : mfWidth(0.0), + meLineJoin(basegfx::B2DLineJoin::Round), + meLineCap(css::drawing::LineCap_BUTT), + mfMiterMinimumAngle(basegfx::deg2rad(15.0)) + { + } + + // data read access + const basegfx::BColor& getColor() const { return maColor; } + double getWidth() const { return mfWidth; } + basegfx::B2DLineJoin getLineJoin() const { return meLineJoin; } + css::drawing::LineCap getLineCap() const { return meLineCap; } + double getMiterMinimumAngle() const { return mfMiterMinimumAngle; } + + bool operator==(const ImpLineAttribute& rCandidate) const + { + return (getColor() == rCandidate.getColor() + && getWidth() == rCandidate.getWidth() + && getLineJoin() == rCandidate.getLineJoin() + && getLineCap() == rCandidate.getLineCap() + && getMiterMinimumAngle() == rCandidate.getMiterMinimumAngle()); + } + }; + + namespace + { + LineAttribute::ImplType& theGlobalDefault() + { + static LineAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + LineAttribute::LineAttribute( + const basegfx::BColor& rColor, + double fWidth, + basegfx::B2DLineJoin aB2DLineJoin, + css::drawing::LineCap aLineCap, + double fMiterMinimumAngle) + : mpLineAttribute( + ImpLineAttribute( + rColor, + fWidth, + aB2DLineJoin, + aLineCap, + fMiterMinimumAngle)) + { + } + + LineAttribute::LineAttribute() + : mpLineAttribute(theGlobalDefault()) + { + } + + LineAttribute::LineAttribute(const LineAttribute&) = default; + + LineAttribute::~LineAttribute() = default; + + bool LineAttribute::isDefault() const + { + return mpLineAttribute.same_object(theGlobalDefault()); + } + + LineAttribute& LineAttribute::operator=(const LineAttribute&) = default; + + bool LineAttribute::operator==(const LineAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpLineAttribute == mpLineAttribute; + } + + const basegfx::BColor& LineAttribute::getColor() const + { + return mpLineAttribute->getColor(); + } + + double LineAttribute::getWidth() const + { + return mpLineAttribute->getWidth(); + } + + basegfx::B2DLineJoin LineAttribute::getLineJoin() const + { + return mpLineAttribute->getLineJoin(); + } + + css::drawing::LineCap LineAttribute::getLineCap() const + { + return mpLineAttribute->getLineCap(); + } + + double LineAttribute::getMiterMinimumAngle() const + { + return mpLineAttribute->getMiterMinimumAngle(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/linestartendattribute.cxx b/drawinglayer/source/attribute/linestartendattribute.cxx new file mode 100644 index 000000000..57a680db3 --- /dev/null +++ b/drawinglayer/source/attribute/linestartendattribute.cxx @@ -0,0 +1,132 @@ +/* -*- 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/attribute/linestartendattribute.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + + +namespace drawinglayer::attribute +{ + class ImpLineStartEndAttribute + { + public: + // data definitions + double mfWidth; // absolute line StartEndGeometry base width + basegfx::B2DPolyPolygon maPolyPolygon; // the StartEndGeometry PolyPolygon + + bool mbCentered : 1; // use centered to lineStart/End point? + + ImpLineStartEndAttribute( + double fWidth, + const basegfx::B2DPolyPolygon& rPolyPolygon, + bool bCentered) + : mfWidth(fWidth), + maPolyPolygon(rPolyPolygon), + mbCentered(bCentered) + { + } + + ImpLineStartEndAttribute() + : mfWidth(0.0), + mbCentered(false) + { + } + + // data read access + double getWidth() const { return mfWidth; } + const basegfx::B2DPolyPolygon& getB2DPolyPolygon() const { return maPolyPolygon; } + bool isCentered() const { return mbCentered; } + + bool operator==(const ImpLineStartEndAttribute& rCandidate) const + { + return (basegfx::fTools::equal(getWidth(), rCandidate.getWidth()) + && getB2DPolyPolygon() == rCandidate.getB2DPolyPolygon() + && isCentered() == rCandidate.isCentered()); + } + }; + + namespace + { + LineStartEndAttribute::ImplType& theGlobalDefault() + { + static LineStartEndAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + LineStartEndAttribute::LineStartEndAttribute( + double fWidth, + const basegfx::B2DPolyPolygon& rPolyPolygon, + bool bCentered) + : mpLineStartEndAttribute(ImpLineStartEndAttribute( + fWidth, rPolyPolygon, bCentered)) + { + } + + LineStartEndAttribute::LineStartEndAttribute() + : mpLineStartEndAttribute(theGlobalDefault()) + { + } + + LineStartEndAttribute::LineStartEndAttribute(const LineStartEndAttribute&) = default; + + LineStartEndAttribute::~LineStartEndAttribute() = default; + + bool LineStartEndAttribute::isDefault() const + { + return mpLineStartEndAttribute.same_object(theGlobalDefault()); + } + + LineStartEndAttribute& LineStartEndAttribute::operator=(const LineStartEndAttribute&) = default; + + bool LineStartEndAttribute::operator==(const LineStartEndAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpLineStartEndAttribute == mpLineStartEndAttribute; + } + + double LineStartEndAttribute::getWidth() const + { + return mpLineStartEndAttribute->getWidth(); + } + + const basegfx::B2DPolyPolygon& LineStartEndAttribute::getB2DPolyPolygon() const + { + return mpLineStartEndAttribute->getB2DPolyPolygon(); + } + + bool LineStartEndAttribute::isCentered() const + { + return mpLineStartEndAttribute->isCentered(); + } + + bool LineStartEndAttribute::isActive() const + { + return (0.0 != getWidth() + && 0 != getB2DPolyPolygon().count() + && 0 != getB2DPolyPolygon().getB2DPolygon(0).count()); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/materialattribute3d.cxx b/drawinglayer/source/attribute/materialattribute3d.cxx new file mode 100644 index 000000000..7206ad54b --- /dev/null +++ b/drawinglayer/source/attribute/materialattribute3d.cxx @@ -0,0 +1,133 @@ +/* -*- 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/attribute/materialattribute3d.hxx> +#include <basegfx/color/bcolor.hxx> + + +namespace drawinglayer::attribute +{ + class ImpMaterialAttribute3D + { + public: + // materialAttribute3D definitions + basegfx::BColor maColor; // object color + basegfx::BColor maSpecular; // material specular color + basegfx::BColor maEmission; // material emissive color + sal_uInt16 mnSpecularIntensity; // material specular intensity [0..128] + + ImpMaterialAttribute3D(const basegfx::BColor& rColor, const basegfx::BColor& rSpecular, const basegfx::BColor& rEmission, sal_uInt16 nSpecularIntensity) + : maColor(rColor), + maSpecular(rSpecular), + maEmission(rEmission), + mnSpecularIntensity(nSpecularIntensity) + { + } + + explicit ImpMaterialAttribute3D(const basegfx::BColor& rColor) + : maColor(rColor), + maSpecular(1.0, 1.0, 1.0), + mnSpecularIntensity(15) + { + } + + ImpMaterialAttribute3D() + : mnSpecularIntensity(0) + { + } + + // data read access + const basegfx::BColor& getColor() const { return maColor; } + const basegfx::BColor& getSpecular() const { return maSpecular; } + const basegfx::BColor& getEmission() const { return maEmission; } + sal_uInt16 getSpecularIntensity() const { return mnSpecularIntensity; } + + bool operator==(const ImpMaterialAttribute3D& rCandidate) const + { + return (getColor() == rCandidate.getColor() + && getSpecular() == rCandidate.getSpecular() + && getEmission() == rCandidate.getEmission() + && getSpecularIntensity() == rCandidate.getSpecularIntensity()); + } + }; + + namespace + { + MaterialAttribute3D::ImplType& theGlobalDefault() + { + static MaterialAttribute3D::ImplType SINGLETON; + return SINGLETON; + } + } + + MaterialAttribute3D::MaterialAttribute3D( + const basegfx::BColor& rColor, + const basegfx::BColor& rSpecular, + const basegfx::BColor& rEmission, + sal_uInt16 nSpecularIntensity) + : mpMaterialAttribute3D(ImpMaterialAttribute3D( + rColor, rSpecular, rEmission, nSpecularIntensity)) + { + } + + MaterialAttribute3D::MaterialAttribute3D( + const basegfx::BColor& rColor) + : mpMaterialAttribute3D(ImpMaterialAttribute3D(rColor)) + { + } + + MaterialAttribute3D::MaterialAttribute3D() + : mpMaterialAttribute3D(theGlobalDefault()) + { + } + + MaterialAttribute3D::MaterialAttribute3D(const MaterialAttribute3D&) = default; + + MaterialAttribute3D::~MaterialAttribute3D() = default; + + MaterialAttribute3D& MaterialAttribute3D::operator=(const MaterialAttribute3D&) = default; + + bool MaterialAttribute3D::operator==(const MaterialAttribute3D& rCandidate) const + { + return rCandidate.mpMaterialAttribute3D == mpMaterialAttribute3D; + } + + const basegfx::BColor& MaterialAttribute3D::getColor() const + { + return mpMaterialAttribute3D->getColor(); + } + + const basegfx::BColor& MaterialAttribute3D::getSpecular() const + { + return mpMaterialAttribute3D->getSpecular(); + } + + const basegfx::BColor& MaterialAttribute3D::getEmission() const + { + return mpMaterialAttribute3D->getEmission(); + } + + sal_uInt16 MaterialAttribute3D::getSpecularIntensity() const + { + return mpMaterialAttribute3D->getSpecularIntensity(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrallattribute3d.cxx b/drawinglayer/source/attribute/sdrallattribute3d.cxx new file mode 100644 index 000000000..079f65579 --- /dev/null +++ b/drawinglayer/source/attribute/sdrallattribute3d.cxx @@ -0,0 +1,59 @@ +/* -*- 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/attribute/sdrallattribute3d.hxx> + + +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) + { + } + + SdrLineFillShadowAttribute3D::SdrLineFillShadowAttribute3D() + : maLine(), + maFill(), + maLineStartEnd(), + maShadow(), + maFillFloatTransGradient() + { + } + + bool SdrLineFillShadowAttribute3D::operator==(const SdrLineFillShadowAttribute3D& rCandidate) const + { + return(getLine() == rCandidate.getLine() + && getFill() == rCandidate.getFill() + && maLineStartEnd == rCandidate.maLineStartEnd + && getShadow() == rCandidate.getShadow() + && getFillFloatTransGradient() == rCandidate.getFillFloatTransGradient()); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrfillattribute.cxx b/drawinglayer/source/attribute/sdrfillattribute.cxx new file mode 100644 index 000000000..131061099 --- /dev/null +++ b/drawinglayer/source/attribute/sdrfillattribute.cxx @@ -0,0 +1,165 @@ +/* -*- 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/attribute/sdrfillattribute.hxx> +#include <basegfx/color/bcolor.hxx> +#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> +#include <drawinglayer/attribute/fillhatchattribute.hxx> +#include <drawinglayer/attribute/fillgradientattribute.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrFillAttribute + { + public: + // fill definitions + double mfTransparence; // [0.0 .. 1.0], 0.0==no transp. + basegfx::BColor maColor; // fill color + FillGradientAttribute maGradient; // fill gradient (if used) + FillHatchAttribute maHatch; // fill hatch (if used) + SdrFillGraphicAttribute maFillGraphic; // fill graphic (if used) + + public: + ImpSdrFillAttribute( + double fTransparence, + const basegfx::BColor& rColor, + const FillGradientAttribute& rGradient, + const FillHatchAttribute& rHatch, + const SdrFillGraphicAttribute& rFillGraphic) + : mfTransparence(fTransparence), + maColor(rColor), + maGradient(rGradient), + maHatch(rHatch), + maFillGraphic(rFillGraphic) + { + } + + ImpSdrFillAttribute() + : mfTransparence(0.0) + { + } + + // data read access + double getTransparence() const { return mfTransparence; } + const basegfx::BColor& getColor() const { return maColor; } + const FillGradientAttribute& getGradient() const { return maGradient; } + const FillHatchAttribute& getHatch() const { return maHatch; } + const SdrFillGraphicAttribute& getFillGraphic() const { return maFillGraphic; } + + // compare operator + bool operator==(const ImpSdrFillAttribute& rCandidate) const + { + return(getTransparence() == rCandidate.getTransparence() + && getColor() == rCandidate.getColor() + && getGradient() == rCandidate.getGradient() + && getHatch() == rCandidate.getHatch() + && getFillGraphic() == rCandidate.getFillGraphic()); + } + }; + + namespace + { + SdrFillAttribute::ImplType& theGlobalDefault() + { + static SdrFillAttribute::ImplType SINGLETON; + return SINGLETON; + } + SdrFillAttribute::ImplType& slideBackgroundFillGlobalDefault() + { + static SdrFillAttribute::ImplType SINGLETON2; + return SINGLETON2; + } + } + + SdrFillAttribute::SdrFillAttribute( + double fTransparence, + const basegfx::BColor& rColor, + const FillGradientAttribute& rGradient, + const FillHatchAttribute& rHatch, + const SdrFillGraphicAttribute& rFillGraphic) + : mpSdrFillAttribute(ImpSdrFillAttribute( + fTransparence, rColor, rGradient, rHatch, rFillGraphic)) + { + } + + SdrFillAttribute::SdrFillAttribute(bool bSlideBackgroundFill) + : mpSdrFillAttribute(bSlideBackgroundFill + ? slideBackgroundFillGlobalDefault() + : theGlobalDefault()) + { + } + + SdrFillAttribute::SdrFillAttribute(const SdrFillAttribute&) = default; + + SdrFillAttribute::SdrFillAttribute(SdrFillAttribute&&) = default; + + SdrFillAttribute::~SdrFillAttribute() = default; + + bool SdrFillAttribute::isDefault() const + { + 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; + + bool SdrFillAttribute::operator==(const SdrFillAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpSdrFillAttribute == mpSdrFillAttribute; + } + + double SdrFillAttribute::getTransparence() const + { + return mpSdrFillAttribute->getTransparence(); + } + + const basegfx::BColor& SdrFillAttribute::getColor() const + { + return mpSdrFillAttribute->getColor(); + } + + const FillGradientAttribute& SdrFillAttribute::getGradient() const + { + return mpSdrFillAttribute->getGradient(); + } + + const FillHatchAttribute& SdrFillAttribute::getHatch() const + { + return mpSdrFillAttribute->getHatch(); + } + + const SdrFillGraphicAttribute& SdrFillAttribute::getFillGraphic() const + { + return mpSdrFillAttribute->getFillGraphic(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx b/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx new file mode 100644 index 000000000..14f53cf03 --- /dev/null +++ b/drawinglayer/source/attribute/sdrfillgraphicattribute.cxx @@ -0,0 +1,304 @@ +/* -*- 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 <algorithm> + +#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> +#include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <vcl/graph.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrFillGraphicAttribute + { + public: + // data definitions + Graphic maFillGraphic; + basegfx::B2DVector maGraphicLogicSize; + basegfx::B2DVector maSize; + basegfx::B2DVector maOffset; + basegfx::B2DVector maOffsetPosition; + basegfx::B2DVector maRectPoint; + + bool mbTiling : 1; + bool mbStretch : 1; + bool mbLogSize : 1; + + ImpSdrFillGraphicAttribute( + const Graphic& rFillGraphic, + const basegfx::B2DVector& rGraphicLogicSize, + const basegfx::B2DVector& rSize, + const basegfx::B2DVector& rOffset, + const basegfx::B2DVector& rOffsetPosition, + const basegfx::B2DVector& rRectPoint, + bool bTiling, + bool bStretch, + bool bLogSize) + : maFillGraphic(rFillGraphic), + maGraphicLogicSize(rGraphicLogicSize), + maSize(rSize), + maOffset(rOffset), + maOffsetPosition(rOffsetPosition), + maRectPoint(rRectPoint), + mbTiling(bTiling), + mbStretch(bStretch), + mbLogSize(bLogSize) + { + } + + ImpSdrFillGraphicAttribute() + : mbTiling(false), + mbStretch(false), + mbLogSize(false) + { + } + + // data read access + const Graphic& getFillGraphic() const { return maFillGraphic; } + const basegfx::B2DVector& getGraphicLogicSize() const { return maGraphicLogicSize; } + const basegfx::B2DVector& getSize() const { return maSize; } + const basegfx::B2DVector& getOffset() const { return maOffset; } + const basegfx::B2DVector& getOffsetPosition() const { return maOffsetPosition; } + const basegfx::B2DVector& getRectPoint() const { return maRectPoint; } + bool getTiling() const { return mbTiling; } + bool getStretch() const { return mbStretch; } + + bool operator==(const ImpSdrFillGraphicAttribute& rCandidate) const + { + return (getFillGraphic() == rCandidate.getFillGraphic() + && getGraphicLogicSize() == rCandidate.getGraphicLogicSize() + && getSize() == rCandidate.getSize() + && getOffset() == rCandidate.getOffset() + && getOffsetPosition() == rCandidate.getOffsetPosition() + && getRectPoint() == rCandidate.getRectPoint() + && getTiling() == rCandidate.getTiling() + && getStretch() == rCandidate.getStretch() + && mbLogSize == rCandidate.mbLogSize); + } + }; + + namespace + { + SdrFillGraphicAttribute::ImplType& theGlobalDefault() + { + static SdrFillGraphicAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + SdrFillGraphicAttribute::SdrFillGraphicAttribute( + const Graphic& rFillGraphic, + const basegfx::B2DVector& rGraphicLogicSize, + const basegfx::B2DVector& rSize, + const basegfx::B2DVector& rOffset, + const basegfx::B2DVector& rOffsetPosition, + const basegfx::B2DVector& rRectPoint, + bool bTiling, + bool bStretch, + bool bLogSize) + : mpSdrFillGraphicAttribute( + ImpSdrFillGraphicAttribute( + rFillGraphic, + rGraphicLogicSize, + rSize, + rOffset, + rOffsetPosition, + rRectPoint, + bTiling, + bStretch, + bLogSize)) + { + } + + SdrFillGraphicAttribute::SdrFillGraphicAttribute() + : mpSdrFillGraphicAttribute(theGlobalDefault()) + { + } + + SdrFillGraphicAttribute::SdrFillGraphicAttribute(const SdrFillGraphicAttribute&) = default; + + SdrFillGraphicAttribute::SdrFillGraphicAttribute(SdrFillGraphicAttribute&&) = default; + + SdrFillGraphicAttribute::~SdrFillGraphicAttribute() = default; + + bool SdrFillGraphicAttribute::isDefault() const + { + return mpSdrFillGraphicAttribute.same_object(theGlobalDefault()); + } + + SdrFillGraphicAttribute& SdrFillGraphicAttribute::operator=(const SdrFillGraphicAttribute&) = default; + + SdrFillGraphicAttribute& SdrFillGraphicAttribute::operator=(SdrFillGraphicAttribute&&) = default; + + bool SdrFillGraphicAttribute::operator==(const SdrFillGraphicAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpSdrFillGraphicAttribute == mpSdrFillGraphicAttribute; + } + + const Graphic& SdrFillGraphicAttribute::getFillGraphic() const + { + return mpSdrFillGraphicAttribute->getFillGraphic(); + } + + const basegfx::B2DVector& SdrFillGraphicAttribute::getGraphicLogicSize() const + { + return mpSdrFillGraphicAttribute->getGraphicLogicSize(); + } + + const basegfx::B2DVector& SdrFillGraphicAttribute::getSize() const + { + return mpSdrFillGraphicAttribute->getSize(); + } + + const basegfx::B2DVector& SdrFillGraphicAttribute::getOffset() const + { + return mpSdrFillGraphicAttribute->getOffset(); + } + + const basegfx::B2DVector& SdrFillGraphicAttribute::getOffsetPosition() const + { + return mpSdrFillGraphicAttribute->getOffsetPosition(); + } + + const basegfx::B2DVector& SdrFillGraphicAttribute::getRectPoint() const + { + return mpSdrFillGraphicAttribute->getRectPoint(); + } + + bool SdrFillGraphicAttribute::getTiling() const + { + return mpSdrFillGraphicAttribute->getTiling(); + } + + FillGraphicAttribute SdrFillGraphicAttribute::createFillGraphicAttribute(const basegfx::B2DRange& rRange) const + { + // get logical size of bitmap (before possibly expanding it) + Graphic aGraphic(getFillGraphic()); + + // init values with defaults for stretched + basegfx::B2DPoint aBitmapSize(1.0, 1.0); + basegfx::B2DVector aBitmapTopLeft(0.0, 0.0); + + // are changes needed? When stretched we are already done, all other values will have no influence + if(getTiling() || !mpSdrFillGraphicAttribute->getStretch()) + { + // init values with range sizes + const double fRangeWidth(0.0 != rRange.getWidth() ? rRange.getWidth() : 1.0); + const double fRangeHeight(0.0 != rRange.getHeight() ? rRange.getHeight() : 1.0); + aBitmapSize = basegfx::B2DPoint(fRangeWidth, fRangeHeight); + + // size changes + if(0.0 != getSize().getX()) + { + if(getSize().getX() < 0.0) + { + aBitmapSize.setX(aBitmapSize.getX() * (getSize().getX() * -0.01)); + } + else + { + aBitmapSize.setX(getSize().getX()); + } + } + else + { + // #i124002# use GraphicLogicSize directly, do not try to use GetPrefSize + // of the graphic, that may not be adapted to the MapMode of the target + aBitmapSize.setX(getGraphicLogicSize().getX()); + } + + if(0.0 != getSize().getY()) + { + if(getSize().getY() < 0.0) + { + aBitmapSize.setY(aBitmapSize.getY() * (getSize().getY() * -0.01)); + } + else + { + aBitmapSize.setY(getSize().getY()); + } + } + else + { + // #i124002# use GraphicLogicSize directly, do not try to use GetPrefSize + // of the graphic, that may not be adapted to the MapMode of the target + aBitmapSize.setY(getGraphicLogicSize().getY()); + } + + // position changes X + if(0.0 == getRectPoint().getX()) + { + aBitmapTopLeft.setX((fRangeWidth - aBitmapSize.getX()) * 0.5); + } + else if(1.0 == getRectPoint().getX()) + { + aBitmapTopLeft.setX(fRangeWidth - aBitmapSize.getX()); + } + + // offset positions are only meaningful when tiled + if(getTiling() && 0.0 != getOffsetPosition().getX()) + { + aBitmapTopLeft.setX(aBitmapTopLeft.getX() + (aBitmapSize.getX() * (getOffsetPosition().getX() * 0.01))); + } + + // position changes Y + if(0.0 == getRectPoint().getY()) + { + aBitmapTopLeft.setY((fRangeHeight - aBitmapSize.getY()) * 0.5); + } + else if(1.0 == getRectPoint().getY()) + { + aBitmapTopLeft.setY(fRangeHeight - aBitmapSize.getY()); + } + + // offset positions are only meaningful when tiled + if(getTiling() && 0.0 != getOffsetPosition().getY()) + { + aBitmapTopLeft.setY(aBitmapTopLeft.getY() + (aBitmapSize.getY() * (getOffsetPosition().getY() * 0.01))); + } + + // apply bitmap size scaling to unit rectangle + aBitmapTopLeft.setX(aBitmapTopLeft.getX() / fRangeWidth); + aBitmapTopLeft.setY(aBitmapTopLeft.getY() / fRangeHeight); + aBitmapSize.setX(aBitmapSize.getX() / fRangeWidth); + aBitmapSize.setY(aBitmapSize.getY() / fRangeHeight); + } + + // get offset in percent + const double fOffsetX(std::clamp(getOffset().getX() * 0.01, 0.0, 1.0)); + const double fOffsetY(std::clamp(getOffset().getY() * 0.01, 0.0, 1.0)); + + // create FillGraphicAttribute + return FillGraphicAttribute( + aGraphic, + basegfx::B2DRange(aBitmapTopLeft, aBitmapTopLeft + aBitmapSize), + getTiling(), + fOffsetX, + fOffsetY); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrglowattribute.cxx b/drawinglayer/source/attribute/sdrglowattribute.cxx new file mode 100644 index 000000000..c27390d64 --- /dev/null +++ b/drawinglayer/source/attribute/sdrglowattribute.cxx @@ -0,0 +1,37 @@ +/* -*- 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/. + */ + +#include <drawinglayer/attribute/sdrglowattribute.hxx> + +namespace drawinglayer::attribute +{ +SdrGlowAttribute::SdrGlowAttribute(sal_Int32 nRadius, const Color& rColor) + : m_nRadius(nRadius) + , m_color(rColor) +{ +} + +SdrGlowAttribute::SdrGlowAttribute() = default; + +SdrGlowAttribute::SdrGlowAttribute(const SdrGlowAttribute&) = default; + +SdrGlowAttribute::SdrGlowAttribute(SdrGlowAttribute&&) = default; + +SdrGlowAttribute& SdrGlowAttribute::operator=(const SdrGlowAttribute&) = default; + +SdrGlowAttribute& SdrGlowAttribute::operator=(SdrGlowAttribute&&) = default; + +bool SdrGlowAttribute::operator==(const SdrGlowAttribute& rCandidate) const +{ + return m_nRadius == rCandidate.m_nRadius && m_color == rCandidate.m_color; +} + +} // end of namespace drawinglayer::attribute + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrlightattribute3d.cxx b/drawinglayer/source/attribute/sdrlightattribute3d.cxx new file mode 100644 index 000000000..511590f5f --- /dev/null +++ b/drawinglayer/source/attribute/sdrlightattribute3d.cxx @@ -0,0 +1,96 @@ +/* -*- 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/attribute/sdrlightattribute3d.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/vector/b3dvector.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdr3DLightAttribute + { + public: + // 3D light attribute definitions + basegfx::BColor maColor; + basegfx::B3DVector maDirection; + + bool mbSpecular : 1; + + ImpSdr3DLightAttribute( + const basegfx::BColor& rColor, + const basegfx::B3DVector& rDirection, + bool bSpecular) + : maColor(rColor), + maDirection(rDirection), + mbSpecular(bSpecular) + { + } + + // data read access + const basegfx::BColor& getColor() const { return maColor; } + const basegfx::B3DVector& getDirection() const { return maDirection; } + bool getSpecular() const { return mbSpecular; } + + bool operator==(const ImpSdr3DLightAttribute& rCandidate) const + { + return (getColor() == rCandidate.getColor() + && getDirection() == rCandidate.getDirection() + && getSpecular() == rCandidate.getSpecular()); + } + }; + + Sdr3DLightAttribute::Sdr3DLightAttribute( + const basegfx::BColor& rColor, + const basegfx::B3DVector& rDirection, + bool bSpecular) + : mpSdr3DLightAttribute(ImpSdr3DLightAttribute( + rColor, rDirection, bSpecular)) + { + } + + Sdr3DLightAttribute::Sdr3DLightAttribute(const Sdr3DLightAttribute&) = default; + + Sdr3DLightAttribute::~Sdr3DLightAttribute() = default; + + Sdr3DLightAttribute& Sdr3DLightAttribute::operator=(const Sdr3DLightAttribute&) = default; + + bool Sdr3DLightAttribute::operator==(const Sdr3DLightAttribute& rCandidate) const + { + return rCandidate.mpSdr3DLightAttribute == mpSdr3DLightAttribute; + } + + const basegfx::BColor& Sdr3DLightAttribute::getColor() const + { + return mpSdr3DLightAttribute->getColor(); + } + + const basegfx::B3DVector& Sdr3DLightAttribute::getDirection() const + { + return mpSdr3DLightAttribute->getDirection(); + } + + bool Sdr3DLightAttribute::getSpecular() const + { + return mpSdr3DLightAttribute->getSpecular(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrlightingattribute3d.cxx b/drawinglayer/source/attribute/sdrlightingattribute3d.cxx new file mode 100644 index 000000000..4f9b75cd1 --- /dev/null +++ b/drawinglayer/source/attribute/sdrlightingattribute3d.cxx @@ -0,0 +1,172 @@ +/* -*- 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/attribute/sdrlightingattribute3d.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/vector/b3dvector.hxx> +#include <drawinglayer/attribute/sdrlightattribute3d.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrLightingAttribute + { + public: + // 3D light attribute definitions + basegfx::BColor maAmbientLight; + std::vector< Sdr3DLightAttribute > maLightVector; + + ImpSdrLightingAttribute( + const basegfx::BColor& rAmbientLight, + std::vector< Sdr3DLightAttribute >&& rLightVector) + : maAmbientLight(rAmbientLight), + maLightVector(std::move(rLightVector)) + { + } + + ImpSdrLightingAttribute() + { + } + + // data read access + const basegfx::BColor& getAmbientLight() const { return maAmbientLight; } + const std::vector< Sdr3DLightAttribute >& getLightVector() const { return maLightVector; } + + bool operator==(const ImpSdrLightingAttribute& rCandidate) const + { + return (getAmbientLight() == rCandidate.getAmbientLight() + && getLightVector() == rCandidate.getLightVector()); + } + }; + + namespace + { + SdrLightingAttribute::ImplType& theGlobalDefault() + { + static SdrLightingAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + SdrLightingAttribute::SdrLightingAttribute( + const basegfx::BColor& rAmbientLight, + std::vector< Sdr3DLightAttribute >&& rLightVector) + : mpSdrLightingAttribute(ImpSdrLightingAttribute( + rAmbientLight, std::move(rLightVector))) + { + } + + SdrLightingAttribute::SdrLightingAttribute() + : mpSdrLightingAttribute(theGlobalDefault()) + { + } + + SdrLightingAttribute::SdrLightingAttribute(const SdrLightingAttribute&) = default; + + SdrLightingAttribute::SdrLightingAttribute(SdrLightingAttribute&&) = default; + + SdrLightingAttribute::~SdrLightingAttribute() = default; + + + bool SdrLightingAttribute::isDefault() const + { + return mpSdrLightingAttribute.same_object(theGlobalDefault()); + } + + SdrLightingAttribute& SdrLightingAttribute::operator=(const SdrLightingAttribute&) = default; + + SdrLightingAttribute& SdrLightingAttribute::operator=(SdrLightingAttribute&&) = default; + + bool SdrLightingAttribute::operator==(const SdrLightingAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpSdrLightingAttribute == mpSdrLightingAttribute; + } + + const std::vector< Sdr3DLightAttribute >& SdrLightingAttribute::getLightVector() const + { + return mpSdrLightingAttribute->getLightVector(); + } + + const basegfx::BColor& SdrLightingAttribute::getAmbientLightColor() const + { + return mpSdrLightingAttribute->maAmbientLight; + } + + // color model solver + basegfx::BColor SdrLightingAttribute::solveColorModel( + const basegfx::B3DVector& rNormalInEyeCoordinates, + const basegfx::BColor& rColor, const basegfx::BColor& rSpecular, + const basegfx::BColor& rEmission, sal_uInt16 nSpecularIntensity) const + { + // initialize with emissive color + basegfx::BColor aRetval(rEmission); + + // take care of global ambient light + aRetval += mpSdrLightingAttribute->getAmbientLight() * rColor; + + const std::vector<Sdr3DLightAttribute>& rLightVector = mpSdrLightingAttribute->getLightVector(); + + // prepare light access. Is there a light? + const sal_uInt32 nLightCount(rLightVector.size()); + + if(nLightCount && !rNormalInEyeCoordinates.equalZero()) + { + // prepare normal + basegfx::B3DVector aEyeNormal(rNormalInEyeCoordinates); + aEyeNormal.normalize(); + + for(sal_uInt32 a(0); a < nLightCount; a++) + { + const Sdr3DLightAttribute& rLight(rLightVector[a]); + const double fCosFac(rLight.getDirection().scalar(aEyeNormal)); + + if(basegfx::fTools::more(fCosFac, 0.0)) + { + aRetval += (rLight.getColor() * rColor) * fCosFac; + + if(rLight.getSpecular()) + { + // expand by (0.0, 0.0, 1.0) in Z + basegfx::B3DVector aSpecularNormal(rLight.getDirection().getX(), rLight.getDirection().getY(), rLight.getDirection().getZ() + 1.0); + aSpecularNormal.normalize(); + double fCosFac2(aSpecularNormal.scalar(aEyeNormal)); + + if(basegfx::fTools::more(fCosFac2, 0.0)) + { + fCosFac2 = pow(fCosFac2, static_cast<double>(nSpecularIntensity)); + aRetval += rSpecular * fCosFac2; + } + } + } + } + } + + // clamp to color space before usage + aRetval.clamp(); + + return aRetval; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrlineattribute.cxx b/drawinglayer/source/attribute/sdrlineattribute.cxx new file mode 100644 index 000000000..f8d4aefb0 --- /dev/null +++ b/drawinglayer/source/attribute/sdrlineattribute.cxx @@ -0,0 +1,181 @@ +/* -*- 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/attribute/sdrlineattribute.hxx> +#include <basegfx/color/bcolor.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrLineAttribute + { + public: + // line definitions + double mfWidth; // 1/100th mm, 0.0==hair + double mfTransparence; // [0.0 .. 1.0], 0.0==no transp. + double mfFullDotDashLen; // sum of maDotDashArray (for convenience) + basegfx::BColor maColor; // color of line + std::vector< double > maDotDashArray; // array of double which defines the dot-dash pattern + basegfx::B2DLineJoin meJoin; // B2DLINEJOIN_* defines + css::drawing::LineCap meCap; // BUTT, ROUND, or SQUARE + + ImpSdrLineAttribute( + basegfx::B2DLineJoin eJoin, + double fWidth, + double fTransparence, + const basegfx::BColor& rColor, + css::drawing::LineCap eCap, + std::vector< double >&& rDotDashArray, + double fFullDotDashLen) + : mfWidth(fWidth), + mfTransparence(fTransparence), + mfFullDotDashLen(fFullDotDashLen), + maColor(rColor), + maDotDashArray(std::move(rDotDashArray)), + meJoin(eJoin), + meCap(eCap) + { + } + + ImpSdrLineAttribute() + : mfWidth(0.0), + mfTransparence(0.0), + mfFullDotDashLen(0.0), + meJoin(basegfx::B2DLineJoin::Round), + meCap(css::drawing::LineCap_BUTT) + { + } + + // data read access + basegfx::B2DLineJoin getJoin() const { return meJoin; } + double getWidth() const { return mfWidth; } + double getTransparence() const { return mfTransparence; } + const basegfx::BColor& getColor() const { return maColor; } + css::drawing::LineCap getCap() const { return meCap; } + const std::vector< double >& getDotDashArray() const { return maDotDashArray; } + double getFullDotDashLen() const { return mfFullDotDashLen; } + + bool operator==(const ImpSdrLineAttribute& rCandidate) const + { + return (getJoin() == rCandidate.getJoin() + && getWidth() == rCandidate.getWidth() + && getTransparence() == rCandidate.getTransparence() + && getColor() == rCandidate.getColor() + && getCap() == rCandidate.getCap() + && getDotDashArray() == rCandidate.getDotDashArray()); + } + }; + + namespace + { + SdrLineAttribute::ImplType& theGlobalDefault() + { + static SdrLineAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + SdrLineAttribute::SdrLineAttribute( + basegfx::B2DLineJoin eJoin, + double fWidth, + double fTransparence, + const basegfx::BColor& rColor, + css::drawing::LineCap eCap, + std::vector< double >&& rDotDashArray, + double fFullDotDashLen) + : mpSdrLineAttribute( + ImpSdrLineAttribute( + eJoin, + fWidth, + fTransparence, + rColor, + eCap, + std::move(rDotDashArray), + fFullDotDashLen)) + + { + } + + SdrLineAttribute::SdrLineAttribute() + : mpSdrLineAttribute(theGlobalDefault()) + { + } + + SdrLineAttribute::SdrLineAttribute(const SdrLineAttribute&) = default; + + SdrLineAttribute::SdrLineAttribute(SdrLineAttribute&&) = default; + + SdrLineAttribute::~SdrLineAttribute() = default; + + bool SdrLineAttribute::isDefault() const + { + return mpSdrLineAttribute.same_object(theGlobalDefault()); + } + + SdrLineAttribute& SdrLineAttribute::operator=(const SdrLineAttribute&) = default; + + SdrLineAttribute& SdrLineAttribute::operator=(SdrLineAttribute&&) = default; + + bool SdrLineAttribute::operator==(const SdrLineAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpSdrLineAttribute == mpSdrLineAttribute; + } + + basegfx::B2DLineJoin SdrLineAttribute::getJoin() const + { + return mpSdrLineAttribute->getJoin(); + } + + double SdrLineAttribute::getWidth() const + { + return mpSdrLineAttribute->getWidth(); + } + + double SdrLineAttribute::getTransparence() const + { + return mpSdrLineAttribute->getTransparence(); + } + + const basegfx::BColor& SdrLineAttribute::getColor() const + { + return mpSdrLineAttribute->getColor(); + } + + const std::vector< double >& SdrLineAttribute::getDotDashArray() const + { + return mpSdrLineAttribute->getDotDashArray(); + } + + double SdrLineAttribute::getFullDotDashLen() const + { + return mpSdrLineAttribute->getFullDotDashLen(); + } + + css::drawing::LineCap SdrLineAttribute::getCap() const + { + return mpSdrLineAttribute->getCap(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrlinestartendattribute.cxx b/drawinglayer/source/attribute/sdrlinestartendattribute.cxx new file mode 100644 index 000000000..aa052236c --- /dev/null +++ b/drawinglayer/source/attribute/sdrlinestartendattribute.cxx @@ -0,0 +1,187 @@ +/* -*- 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/attribute/sdrlinestartendattribute.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrLineStartEndAttribute + { + public: + // line arrow definitions + basegfx::B2DPolyPolygon maStartPolyPolygon; // start Line PolyPolygon + basegfx::B2DPolyPolygon maEndPolyPolygon; // end Line PolyPolygon + double mfStartWidth; // 1/100th mm + double mfEndWidth; // 1/100th mm + + bool mbStartActive : 1; // start of Line is active + bool mbEndActive : 1; // end of Line is active + bool mbStartCentered : 1; // Line is centered on line start point + bool mbEndCentered : 1; // Line is centered on line end point + + ImpSdrLineStartEndAttribute( + const basegfx::B2DPolyPolygon& rStartPolyPolygon, + const basegfx::B2DPolyPolygon& rEndPolyPolygon, + double fStartWidth, + double fEndWidth, + bool bStartActive, + bool bEndActive, + bool bStartCentered, + bool bEndCentered) + : maStartPolyPolygon(rStartPolyPolygon), + maEndPolyPolygon(rEndPolyPolygon), + mfStartWidth(fStartWidth), + mfEndWidth(fEndWidth), + mbStartActive(bStartActive), + mbEndActive(bEndActive), + mbStartCentered(bStartCentered), + mbEndCentered(bEndCentered) + { + } + + ImpSdrLineStartEndAttribute() + : mfStartWidth(0.0), + mfEndWidth(0.0), + mbStartActive(false), + mbEndActive(false), + mbStartCentered(false), + mbEndCentered(false) + { + } + + // data read access + const basegfx::B2DPolyPolygon& getStartPolyPolygon() const { return maStartPolyPolygon; } + const basegfx::B2DPolyPolygon& getEndPolyPolygon() const { return maEndPolyPolygon; } + double getStartWidth() const { return mfStartWidth; } + double getEndWidth() const { return mfEndWidth; } + bool isStartActive() const { return mbStartActive; } + bool isEndActive() const { return mbEndActive; } + bool isStartCentered() const { return mbStartCentered; } + bool isEndCentered() const { return mbEndCentered; } + + bool operator==(const ImpSdrLineStartEndAttribute& rCandidate) const + { + return (getStartPolyPolygon() == rCandidate.getStartPolyPolygon() + && getEndPolyPolygon() == rCandidate.getEndPolyPolygon() + && getStartWidth() == rCandidate.getStartWidth() + && getEndWidth() == rCandidate.getEndWidth() + && isStartActive() == rCandidate.isStartActive() + && isEndActive() == rCandidate.isEndActive() + && isStartCentered() == rCandidate.isStartCentered() + && isEndCentered() == rCandidate.isEndCentered()); + } + }; + + namespace + { + SdrLineStartEndAttribute::ImplType& theGlobalDefault() + { + static SdrLineStartEndAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + SdrLineStartEndAttribute::SdrLineStartEndAttribute( + const basegfx::B2DPolyPolygon& rStartPolyPolygon, + const basegfx::B2DPolyPolygon& rEndPolyPolygon, + double fStartWidth, + double fEndWidth, + bool bStartActive, + bool bEndActive, + bool bStartCentered, + bool bEndCentered) + : mpSdrLineStartEndAttribute(ImpSdrLineStartEndAttribute( + rStartPolyPolygon, rEndPolyPolygon, fStartWidth, fEndWidth, bStartActive, bEndActive, bStartCentered, bEndCentered)) + { + } + + SdrLineStartEndAttribute::SdrLineStartEndAttribute() + : mpSdrLineStartEndAttribute(theGlobalDefault()) + { + } + + SdrLineStartEndAttribute::SdrLineStartEndAttribute(const SdrLineStartEndAttribute&) = default; + + SdrLineStartEndAttribute::SdrLineStartEndAttribute(SdrLineStartEndAttribute&&) = default; + + SdrLineStartEndAttribute::~SdrLineStartEndAttribute() = default; + + bool SdrLineStartEndAttribute::isDefault() const + { + return mpSdrLineStartEndAttribute.same_object(theGlobalDefault()); + } + + SdrLineStartEndAttribute& SdrLineStartEndAttribute::operator=(const SdrLineStartEndAttribute&) = default; + + SdrLineStartEndAttribute& SdrLineStartEndAttribute::operator=(SdrLineStartEndAttribute&&) = default; + + bool SdrLineStartEndAttribute::operator==(const SdrLineStartEndAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpSdrLineStartEndAttribute == mpSdrLineStartEndAttribute; + } + + const basegfx::B2DPolyPolygon& SdrLineStartEndAttribute::getStartPolyPolygon() const + { + return mpSdrLineStartEndAttribute->getStartPolyPolygon(); + } + + const basegfx::B2DPolyPolygon& SdrLineStartEndAttribute::getEndPolyPolygon() const + { + return mpSdrLineStartEndAttribute->getEndPolyPolygon(); + } + + double SdrLineStartEndAttribute::getStartWidth() const + { + return mpSdrLineStartEndAttribute->getStartWidth(); + } + + double SdrLineStartEndAttribute::getEndWidth() const + { + return mpSdrLineStartEndAttribute->getEndWidth(); + } + + bool SdrLineStartEndAttribute::isStartActive() const + { + return mpSdrLineStartEndAttribute->isStartActive(); + } + + bool SdrLineStartEndAttribute::isEndActive() const + { + return mpSdrLineStartEndAttribute->isEndActive(); + } + + bool SdrLineStartEndAttribute::isStartCentered() const + { + return mpSdrLineStartEndAttribute->isStartCentered(); + } + + bool SdrLineStartEndAttribute::isEndCentered() const + { + return mpSdrLineStartEndAttribute->isEndCentered(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrobjectattribute3d.cxx b/drawinglayer/source/attribute/sdrobjectattribute3d.cxx new file mode 100644 index 000000000..dad8a07ee --- /dev/null +++ b/drawinglayer/source/attribute/sdrobjectattribute3d.cxx @@ -0,0 +1,184 @@ +/* -*- 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/attribute/sdrobjectattribute3d.hxx> +#include <drawinglayer/attribute/materialattribute3d.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdr3DObjectAttribute + { + public: + // 3D object attribute definitions + css::drawing::NormalsKind maNormalsKind; // normals type (0..2) + css::drawing::TextureProjectionMode maTextureProjectionX; // texture projection type X (0..2) + css::drawing::TextureProjectionMode maTextureProjectionY; // texture projection type Y (0..2) + css::drawing::TextureKind2 maTextureKind; // texture kind (see uno API) + css::drawing::TextureMode maTextureMode; // texture kind (see uno API) + MaterialAttribute3D maMaterial; // object, specular and emissive colors, SpecularIntensity + + bool mbNormalsInvert : 1; // invert normals + bool mbDoubleSided : 1; // surfaces are double sided + bool mbShadow3D : 1; // display shadow in 3D (if on), params for that are at scene + bool mbTextureFilter : 1; // filter texture to make more smooth + bool mbReducedLineGeometry : 1; // use reduced line geometry (object specific) + + ImpSdr3DObjectAttribute( + css::drawing::NormalsKind aNormalsKind, + css::drawing::TextureProjectionMode aTextureProjectionX, + css::drawing::TextureProjectionMode aTextureProjectionY, + css::drawing::TextureKind2 aTextureKind, + css::drawing::TextureMode aTextureMode, + const MaterialAttribute3D& rMaterial, + bool bNormalsInvert, + bool bDoubleSided, + bool bShadow3D, + bool bTextureFilter, + bool bReducedLineGeometry) + : maNormalsKind(aNormalsKind), + maTextureProjectionX(aTextureProjectionX), + maTextureProjectionY(aTextureProjectionY), + maTextureKind(aTextureKind), + maTextureMode(aTextureMode), + maMaterial(rMaterial), + mbNormalsInvert(bNormalsInvert), + mbDoubleSided(bDoubleSided), + mbShadow3D(bShadow3D), + mbTextureFilter(bTextureFilter), + mbReducedLineGeometry(bReducedLineGeometry) + { + } + + // data read access + css::drawing::NormalsKind getNormalsKind() const { return maNormalsKind; } + css::drawing::TextureProjectionMode getTextureProjectionX() const { return maTextureProjectionX; } + css::drawing::TextureProjectionMode getTextureProjectionY() const { return maTextureProjectionY; } + css::drawing::TextureKind2 getTextureKind() const { return maTextureKind; } + css::drawing::TextureMode getTextureMode() const { return maTextureMode; } + const MaterialAttribute3D& getMaterial() const { return maMaterial; } + bool getNormalsInvert() const { return mbNormalsInvert; } + bool getDoubleSided() const { return mbDoubleSided; } + bool getShadow3D() const { return mbShadow3D; } + bool getTextureFilter() const { return mbTextureFilter; } + bool getReducedLineGeometry() const { return mbReducedLineGeometry; } + + bool operator==(const ImpSdr3DObjectAttribute& rCandidate) const + { + return (getNormalsKind() == rCandidate.getNormalsKind() + && getTextureProjectionX() == rCandidate.getTextureProjectionX() + && getTextureProjectionY() == rCandidate.getTextureProjectionY() + && getTextureKind() == rCandidate.getTextureKind() + && getTextureMode() == rCandidate.getTextureMode() + && getMaterial() == rCandidate.getMaterial() + && getNormalsInvert() == rCandidate.getNormalsInvert() + && getDoubleSided() == rCandidate.getDoubleSided() + && getShadow3D() == rCandidate.getShadow3D() + && getTextureFilter() == rCandidate.getTextureFilter() + && getReducedLineGeometry() == rCandidate.getReducedLineGeometry()); + } + }; + + Sdr3DObjectAttribute::Sdr3DObjectAttribute( + css::drawing::NormalsKind aNormalsKind, + css::drawing::TextureProjectionMode aTextureProjectionX, + css::drawing::TextureProjectionMode aTextureProjectionY, + css::drawing::TextureKind2 aTextureKind, + css::drawing::TextureMode aTextureMode, + const MaterialAttribute3D& rMaterial, + bool bNormalsInvert, + bool bDoubleSided, + bool bShadow3D, + bool bTextureFilter, + bool bReducedLineGeometry) + : mpSdr3DObjectAttribute(ImpSdr3DObjectAttribute( + aNormalsKind, aTextureProjectionX, aTextureProjectionY, aTextureKind, aTextureMode, + rMaterial, bNormalsInvert, bDoubleSided, bShadow3D, bTextureFilter, bReducedLineGeometry)) + { + } + + Sdr3DObjectAttribute::Sdr3DObjectAttribute(const Sdr3DObjectAttribute&) = default; + + Sdr3DObjectAttribute::~Sdr3DObjectAttribute() = default; + + Sdr3DObjectAttribute& Sdr3DObjectAttribute::operator=(const Sdr3DObjectAttribute&) = default; + + bool Sdr3DObjectAttribute::operator==(const Sdr3DObjectAttribute& rCandidate) const + { + return rCandidate.mpSdr3DObjectAttribute == mpSdr3DObjectAttribute; + } + + css::drawing::NormalsKind Sdr3DObjectAttribute::getNormalsKind() const + { + return mpSdr3DObjectAttribute->getNormalsKind(); + } + + css::drawing::TextureProjectionMode Sdr3DObjectAttribute::getTextureProjectionX() const + { + return mpSdr3DObjectAttribute->getTextureProjectionX(); + } + + css::drawing::TextureProjectionMode Sdr3DObjectAttribute::getTextureProjectionY() const + { + return mpSdr3DObjectAttribute->getTextureProjectionY(); + } + + css::drawing::TextureKind2 Sdr3DObjectAttribute::getTextureKind() const + { + return mpSdr3DObjectAttribute->getTextureKind(); + } + + css::drawing::TextureMode Sdr3DObjectAttribute::getTextureMode() const + { + return mpSdr3DObjectAttribute->getTextureMode(); + } + + const MaterialAttribute3D& Sdr3DObjectAttribute::getMaterial() const + { + return mpSdr3DObjectAttribute->getMaterial(); + } + + bool Sdr3DObjectAttribute::getNormalsInvert() const + { + return mpSdr3DObjectAttribute->getNormalsInvert(); + } + + bool Sdr3DObjectAttribute::getDoubleSided() const + { + return mpSdr3DObjectAttribute->getDoubleSided(); + } + + bool Sdr3DObjectAttribute::getShadow3D() const + { + return mpSdr3DObjectAttribute->getShadow3D(); + } + + bool Sdr3DObjectAttribute::getTextureFilter() const + { + return mpSdr3DObjectAttribute->getTextureFilter(); + } + + bool Sdr3DObjectAttribute::getReducedLineGeometry() const + { + return mpSdr3DObjectAttribute->getReducedLineGeometry(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrsceneattribute3d.cxx b/drawinglayer/source/attribute/sdrsceneattribute3d.cxx new file mode 100644 index 000000000..840fe2e30 --- /dev/null +++ b/drawinglayer/source/attribute/sdrsceneattribute3d.cxx @@ -0,0 +1,147 @@ +/* -*- 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/attribute/sdrsceneattribute3d.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrSceneAttribute + { + public: + // 3D scene attribute definitions + double mfDistance; + double mfShadowSlant; + css::drawing::ProjectionMode maProjectionMode; + css::drawing::ShadeMode maShadeMode; + + bool mbTwoSidedLighting : 1; + + public: + ImpSdrSceneAttribute( + double fDistance, + double fShadowSlant, + css::drawing::ProjectionMode aProjectionMode, + css::drawing::ShadeMode aShadeMode, + bool bTwoSidedLighting) + : mfDistance(fDistance), + mfShadowSlant(fShadowSlant), + maProjectionMode(aProjectionMode), + maShadeMode(aShadeMode), + mbTwoSidedLighting(bTwoSidedLighting) + { + } + + ImpSdrSceneAttribute() + : mfDistance(0.0), + mfShadowSlant(0.0), + maProjectionMode(css::drawing::ProjectionMode_PARALLEL), + maShadeMode(css::drawing::ShadeMode_FLAT), + mbTwoSidedLighting(false) + { + } + + // data read access + double getShadowSlant() const { return mfShadowSlant; } + css::drawing::ProjectionMode getProjectionMode() const { return maProjectionMode; } + css::drawing::ShadeMode getShadeMode() const { return maShadeMode; } + bool getTwoSidedLighting() const { return mbTwoSidedLighting; } + + bool operator==(const ImpSdrSceneAttribute& rCandidate) const + { + return (mfDistance == rCandidate.mfDistance + && getShadowSlant() == rCandidate.getShadowSlant() + && getProjectionMode() == rCandidate.getProjectionMode() + && getShadeMode() == rCandidate.getShadeMode() + && getTwoSidedLighting() == rCandidate.getTwoSidedLighting()); + } + }; + + namespace + { + SdrSceneAttribute::ImplType& theGlobalDefault() + { + static SdrSceneAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + SdrSceneAttribute::SdrSceneAttribute( + double fDistance, + double fShadowSlant, + css::drawing::ProjectionMode aProjectionMode, + css::drawing::ShadeMode aShadeMode, + bool bTwoSidedLighting) + : mpSdrSceneAttribute(ImpSdrSceneAttribute( + fDistance, fShadowSlant, aProjectionMode, aShadeMode, bTwoSidedLighting)) + { + } + + SdrSceneAttribute::SdrSceneAttribute() + : mpSdrSceneAttribute(theGlobalDefault()) + { + } + + SdrSceneAttribute::SdrSceneAttribute(const SdrSceneAttribute&) = default; + + SdrSceneAttribute::SdrSceneAttribute(SdrSceneAttribute&&) = default; + + SdrSceneAttribute::~SdrSceneAttribute() = default; + + bool SdrSceneAttribute::isDefault() const + { + return mpSdrSceneAttribute.same_object(theGlobalDefault()); + } + + SdrSceneAttribute& SdrSceneAttribute::operator=(const SdrSceneAttribute&) = default; + + SdrSceneAttribute& SdrSceneAttribute::operator=(SdrSceneAttribute&&) = default; + + bool SdrSceneAttribute::operator==(const SdrSceneAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpSdrSceneAttribute == mpSdrSceneAttribute; + } + + double SdrSceneAttribute::getShadowSlant() const + { + return mpSdrSceneAttribute->getShadowSlant(); + } + + css::drawing::ProjectionMode SdrSceneAttribute::getProjectionMode() const + { + return mpSdrSceneAttribute->getProjectionMode(); + } + + css::drawing::ShadeMode SdrSceneAttribute::getShadeMode() const + { + return mpSdrSceneAttribute->getShadeMode(); + } + + bool SdrSceneAttribute::getTwoSidedLighting() const + { + return mpSdrSceneAttribute->getTwoSidedLighting(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/sdrshadowattribute.cxx b/drawinglayer/source/attribute/sdrshadowattribute.cxx new file mode 100644 index 000000000..6e046f1f0 --- /dev/null +++ b/drawinglayer/source/attribute/sdrshadowattribute.cxx @@ -0,0 +1,151 @@ +/* -*- 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/attribute/sdrshadowattribute.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/color/bcolor.hxx> + + +namespace drawinglayer::attribute +{ + class ImpSdrShadowAttribute + { + public: + // shadow definitions + basegfx::B2DVector maOffset; // shadow offset 1/100th mm + 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 + basegfx::BColor maColor; // color of shadow + + ImpSdrShadowAttribute( + const basegfx::B2DVector& rOffset, + const basegfx::B2DVector& rSize, + double fTransparence, + sal_Int32 nBlur, + const basegfx::BColor& rColor) + : maOffset(rOffset), + maSize(rSize), + mfTransparence(fTransparence), + mnBlur(nBlur), + maColor(rColor) + { + } + + ImpSdrShadowAttribute() + : mfTransparence(0.0), + mnBlur(0) + { + } + + // data read access + const basegfx::B2DVector& getOffset() const { return maOffset; } + const basegfx::B2DVector& getSize() const { return maSize; } + double getTransparence() const { return mfTransparence; } + sal_Int32 getBlur() const { return mnBlur; } + const basegfx::BColor& getColor() const { return maColor; } + + bool operator==(const ImpSdrShadowAttribute& rCandidate) const + { + return (getOffset() == rCandidate.getOffset() + && getSize() == rCandidate.getSize() + && getTransparence() == rCandidate.getTransparence() + && getBlur() == rCandidate.getBlur() + && getColor() == rCandidate.getColor()); + } + }; + + namespace + { + SdrShadowAttribute::ImplType& theGlobalDefault() + { + static SdrShadowAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + + SdrShadowAttribute::SdrShadowAttribute( + const basegfx::B2DVector& rOffset, + const basegfx::B2DVector& rSize, + double fTransparence, + sal_Int32 nBlur, + const basegfx::BColor& rColor) + : mpSdrShadowAttribute(ImpSdrShadowAttribute( + rOffset, rSize, fTransparence,nBlur, rColor)) + { + } + + SdrShadowAttribute::SdrShadowAttribute() + : mpSdrShadowAttribute(theGlobalDefault()) + { + } + + SdrShadowAttribute::SdrShadowAttribute(const SdrShadowAttribute&) = default; + + SdrShadowAttribute::SdrShadowAttribute(SdrShadowAttribute&&) = default; + + SdrShadowAttribute::~SdrShadowAttribute() = default; + + bool SdrShadowAttribute::isDefault() const + { + return mpSdrShadowAttribute.same_object(theGlobalDefault()); + } + + SdrShadowAttribute& SdrShadowAttribute::operator=(const SdrShadowAttribute&) = default; + + SdrShadowAttribute& SdrShadowAttribute::operator=(SdrShadowAttribute&&) = default; + + bool SdrShadowAttribute::operator==(const SdrShadowAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return mpSdrShadowAttribute == rCandidate.mpSdrShadowAttribute; + } + + const basegfx::B2DVector& SdrShadowAttribute::getOffset() const + { + return mpSdrShadowAttribute->getOffset(); + } + + const basegfx::B2DVector& SdrShadowAttribute::getSize() const + { + return mpSdrShadowAttribute->getSize(); + } + + double SdrShadowAttribute::getTransparence() const + { + return mpSdrShadowAttribute->getTransparence(); + } + + sal_Int32 SdrShadowAttribute::getBlur() const + { + return mpSdrShadowAttribute->getBlur(); + } + + const basegfx::BColor& SdrShadowAttribute::getColor() const + { + return mpSdrShadowAttribute->getColor(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/attribute/strokeattribute.cxx b/drawinglayer/source/attribute/strokeattribute.cxx new file mode 100644 index 000000000..d925eb65a --- /dev/null +++ b/drawinglayer/source/attribute/strokeattribute.cxx @@ -0,0 +1,125 @@ +/* -*- 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/attribute/strokeattribute.hxx> +#include <numeric> + + +namespace drawinglayer::attribute +{ + class ImpStrokeAttribute + { + public: + // data definitions + std::vector< double > maDotDashArray; // array of double which defines the dot-dash pattern + double mfFullDotDashLen; // sum of maDotDashArray (for convenience) + + ImpStrokeAttribute( + std::vector< double >&& rDotDashArray, + double fFullDotDashLen) + : maDotDashArray(std::move(rDotDashArray)), + mfFullDotDashLen(fFullDotDashLen) + { + } + + ImpStrokeAttribute() + : mfFullDotDashLen(0.0) + { + } + + // data read access + const std::vector< double >& getDotDashArray() const { return maDotDashArray; } + double getFullDotDashLen() const + { + if(0.0 == mfFullDotDashLen && !maDotDashArray.empty()) + { + // calculate length on demand + const double fAccumulated(std::accumulate(maDotDashArray.begin(), maDotDashArray.end(), 0.0)); + const_cast< ImpStrokeAttribute* >(this)->mfFullDotDashLen = fAccumulated; + } + + return mfFullDotDashLen; + } + + bool operator==(const ImpStrokeAttribute& rCandidate) const + { + return (getDotDashArray() == rCandidate.getDotDashArray() + && getFullDotDashLen() == rCandidate.getFullDotDashLen()); + } + }; + + namespace + { + StrokeAttribute::ImplType& theGlobalDefault() + { + static StrokeAttribute::ImplType SINGLETON; + return SINGLETON; + } + } + + StrokeAttribute::StrokeAttribute( + std::vector< double >&& rDotDashArray, + double fFullDotDashLen) + : mpStrokeAttribute(ImpStrokeAttribute( + std::move(rDotDashArray), fFullDotDashLen)) + { + } + + StrokeAttribute::StrokeAttribute() + : mpStrokeAttribute(theGlobalDefault()) + { + } + + StrokeAttribute::StrokeAttribute(const StrokeAttribute&) = default; + + StrokeAttribute::StrokeAttribute(StrokeAttribute&&) = default; + + StrokeAttribute::~StrokeAttribute() = default; + + bool StrokeAttribute::isDefault() const + { + return mpStrokeAttribute.same_object(theGlobalDefault()); + } + + StrokeAttribute& StrokeAttribute::operator=(const StrokeAttribute&) = default; + + StrokeAttribute& StrokeAttribute::operator=(StrokeAttribute&&) = default; + + bool StrokeAttribute::operator==(const StrokeAttribute& rCandidate) const + { + // tdf#87509 default attr is always != non-default attr, even with same values + if(rCandidate.isDefault() != isDefault()) + return false; + + return rCandidate.mpStrokeAttribute == mpStrokeAttribute; + } + + const std::vector< double >& StrokeAttribute::getDotDashArray() const + { + return mpStrokeAttribute->getDotDashArray(); + } + + double StrokeAttribute::getFullDotDashLen() const + { + return mpStrokeAttribute->getFullDotDashLen(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx b/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx new file mode 100644 index 000000000..be58c77dd --- /dev/null +++ b/drawinglayer/source/drawinglayeruno/xprimitive2drenderer.cxx @@ -0,0 +1,183 @@ +/* -*- 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 <com/sun/star/graphic/XPrimitive2DRenderer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/implbase2.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + +#include <drawinglayer/converters.hxx> +#include <comphelper/sequenceashashmap.hxx> + +using namespace ::com::sun::star; + + +namespace drawinglayer::unorenderer +{ + namespace { + + class XPrimitive2DRenderer: + public cppu::WeakAggImplHelper2< + css::graphic::XPrimitive2DRenderer, css::lang::XServiceInfo> + { + public: + XPrimitive2DRenderer(); + + XPrimitive2DRenderer(const XPrimitive2DRenderer&) = delete; + const XPrimitive2DRenderer& operator=(const XPrimitive2DRenderer&) = delete; + + // XPrimitive2DRenderer + virtual uno::Reference< rendering::XBitmap > SAL_CALL rasterize( + const uno::Sequence< uno::Reference< graphic::XPrimitive2D > >& Primitive2DSequence, + const uno::Sequence< beans::PropertyValue >& aViewInformationSequence, + ::sal_uInt32 DPI_X, + ::sal_uInt32 DPI_Y, + const css::geometry::RealRectangle2D& Range, + ::sal_uInt32 MaximumQuadraticPixels) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString&) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + }; + + } + + XPrimitive2DRenderer::XPrimitive2DRenderer() + { + } + + uno::Reference< rendering::XBitmap > XPrimitive2DRenderer::rasterize( + const uno::Sequence< uno::Reference< graphic::XPrimitive2D > >& aPrimitive2DSequence, + const uno::Sequence< beans::PropertyValue >& aViewInformationSequence, + ::sal_uInt32 DPI_X, + ::sal_uInt32 DPI_Y, + const css::geometry::RealRectangle2D& Range, + ::sal_uInt32 MaximumQuadraticPixels) + { + o3tl::Length eRangeUnit = o3tl::Length::mm100; + comphelper::SequenceAsHashMap aViewInformationMap(aViewInformationSequence); + auto it = aViewInformationMap.find("RangeUnit"); + if (it != aViewInformationMap.end()) + { + sal_Int32 nVal{}; + it->second >>= nVal; + eRangeUnit = static_cast<o3tl::Length>(nVal); + } + + uno::Reference< rendering::XBitmap > XBitmap; + + if(aPrimitive2DSequence.hasElements()) + { + const basegfx::B2DRange aRange(Range.X1, Range.Y1, Range.X2, Range.Y2); + const double fWidth(aRange.getWidth()); + const double fHeight(aRange.getHeight()); + + if(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0)) + { + if(0 == DPI_X) + { + DPI_X = 75; + } + + if(0 == DPI_Y) + { + DPI_Y = 75; + } + + if(0 == MaximumQuadraticPixels) + { + MaximumQuadraticPixels = 500000; + } + + const auto aViewInformation2D = geometry::createViewInformation2D(aViewInformationSequence); + 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)); + + basegfx::B2DHomMatrix aEmbedding( + basegfx::utils::createTranslateB2DHomMatrix( + -aRange.getMinX(), + -aRange.getMinY())); + + aEmbedding.scale( + nDiscreteWidth / fWidth, + nDiscreteHeight / fHeight); + + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D( + aEmbedding, + aPrimitive2DSequence)); + primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; + + BitmapEx aBitmapEx( + convertToBitmapEx( + std::move(xEmbedSeq), + aViewInformation2D, + nDiscreteWidth, + nDiscreteHeight, + MaximumQuadraticPixels)); + + if(!aBitmapEx.IsEmpty()) + { + aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + aBitmapEx.SetPrefSize(Size(basegfx::fround(fWidth), basegfx::fround(fHeight))); + XBitmap = vcl::unotools::xBitmapFromBitmapEx(aBitmapEx); + } + } + } + + return XBitmap; + } + + OUString SAL_CALL XPrimitive2DRenderer::getImplementationName() + { + return "drawinglayer::unorenderer::XPrimitive2DRenderer"; + } + + sal_Bool SAL_CALL XPrimitive2DRenderer::supportsService(const OUString& rServiceName) + { + return cppu::supportsService(this, rServiceName); + } + + uno::Sequence< OUString > SAL_CALL XPrimitive2DRenderer::getSupportedServiceNames() + { + return { "com.sun.star.graphic.Primitive2DTools" }; + } + +} // end of namespace + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +drawinglayer_XPrimitive2DRenderer( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new drawinglayer::unorenderer::XPrimitive2DRenderer()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/dumper/EnhancedShapeDumper.cxx b/drawinglayer/source/dumper/EnhancedShapeDumper.cxx new file mode 100644 index 000000000..6e83f15db --- /dev/null +++ b/drawinglayer/source/dumper/EnhancedShapeDumper.cxx @@ -0,0 +1,1081 @@ +/* -*- 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/. + */ + +#include "EnhancedShapeDumper.hxx" +#include <com/sun/star/beans/XPropertySet.hpp> + +using namespace com::sun::star; + + +// ---------- EnhancedCustomShapeExtrusion.idl ---------- + + +void EnhancedShapeDumper::dumpEnhancedCustomShapeExtrusionService(const uno::Reference< beans::XPropertySet >& xPropSet) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("Extrusion"); + bool bExtrusion; + if(anotherAny >>= bExtrusion) + dumpExtrusionAsAttribute(bExtrusion); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Brightness"); + double aBrightness = double(); + if(anotherAny >>= aBrightness) + dumpBrightnessAsAttribute(aBrightness); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Depth"); + drawing::EnhancedCustomShapeParameterPair aDepth; + if(anotherAny >>= aDepth) + dumpDepthAsElement(aDepth); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Diffusion"); + double aDiffusion = double(); + if(anotherAny >>= aDiffusion) + dumpDiffusionAsAttribute(aDiffusion); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("NumberOfLineSegments"); + sal_Int32 aNumberOfLineSegments = sal_Int32(); + if(anotherAny >>= aNumberOfLineSegments) + dumpNumberOfLineSegmentsAsAttribute(aNumberOfLineSegments); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LightFace"); + bool bLightFace; + if(anotherAny >>= bLightFace) + dumpLightFaceAsAttribute(bLightFace); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FirstLightHarsh"); + bool bFirstLightHarsh; + if(anotherAny >>= bFirstLightHarsh) + dumpFirstLightHarshAsAttribute(bFirstLightHarsh); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("SecondLightHarsh"); + bool bSecondLightHarsh; + if(anotherAny >>= bSecondLightHarsh) + dumpSecondLightHarshAsAttribute(bSecondLightHarsh); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FirstLightLevel"); + double aFirstLightLevel = double(); + if(anotherAny >>= aFirstLightLevel) + dumpFirstLightLevelAsAttribute(aFirstLightLevel); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("SecondLightLevel"); + double aSecondLightLevel = double(); + if(anotherAny >>= aSecondLightLevel) + dumpSecondLightLevelAsAttribute(aSecondLightLevel); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FirstLightDirection"); + drawing::Direction3D aFirstLightDirection; + if(anotherAny >>= aFirstLightDirection) + dumpFirstLightDirectionAsElement(aFirstLightDirection); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("SecondLightDirection"); + drawing::Direction3D aSecondLightDirection; + if(anotherAny >>= aSecondLightDirection) + dumpSecondLightDirectionAsElement(aSecondLightDirection); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Metal"); + bool bMetal; + if(anotherAny >>= bMetal) + dumpMetalAsAttribute(bMetal); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ShadeMode"); + drawing::ShadeMode eShadeMode; + if(anotherAny >>= eShadeMode) + dumpShadeModeAsAttribute(eShadeMode); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RotateAngle"); + drawing::EnhancedCustomShapeParameterPair aRotateAngle; + if(anotherAny >>= aRotateAngle) + dumpRotateAngleAsElement(aRotateAngle); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RotationCenter"); + drawing::Direction3D aRotationCenter; + if(anotherAny >>= aRotationCenter) + dumpRotationCenterAsElement(aRotationCenter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Shininess"); + double aShininess = double(); + if(anotherAny >>= aShininess) + dumpShininessAsAttribute(aShininess); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Skew"); + drawing::EnhancedCustomShapeParameterPair aSkew; + if(anotherAny >>= aSkew) + dumpSkewAsElement(aSkew); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Specularity"); + double aSpecularity = double(); + if(anotherAny >>= aSpecularity) + dumpSpecularityAsAttribute(aSpecularity); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ProjectionMode"); + drawing::ProjectionMode eProjectionMode; + if(anotherAny >>= eProjectionMode) + dumpProjectionModeAsAttribute(eProjectionMode); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ViewPoint"); + drawing::Position3D aViewPoint; + if(anotherAny >>= aViewPoint) + dumpViewPointAsElement(aViewPoint); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Origin"); + drawing::EnhancedCustomShapeParameterPair aOrigin; + if(anotherAny >>= aOrigin) + dumpOriginAsElement(aOrigin); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ExtrusionColor"); + bool bExtrusionColor; + if(anotherAny >>= bExtrusionColor) + dumpExtrusionColorAsAttribute(bExtrusionColor); + } +} +void EnhancedShapeDumper::dumpExtrusionAsAttribute(bool bExtrusion) +{ + if(bExtrusion) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("extrusion"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("extrusion"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpBrightnessAsAttribute(double aBrightness) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("brightness"), "%f", aBrightness); +} + +void EnhancedShapeDumper::dumpEnhancedCustomShapeParameterPair( + const drawing::EnhancedCustomShapeParameterPair& aParameterPair) +{ + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "First" )); + dumpEnhancedCustomShapeParameter(aParameterPair.First); + (void)xmlTextWriterEndElement( xmlWriter ); + } + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Second" )); + dumpEnhancedCustomShapeParameter(aParameterPair.Second); + (void)xmlTextWriterEndElement( xmlWriter ); + } +} + +void EnhancedShapeDumper::dumpDepthAsElement(const drawing::EnhancedCustomShapeParameterPair& aDepth) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Depth" )); + dumpEnhancedCustomShapeParameterPair(aDepth); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpDiffusionAsAttribute(double aDiffusion) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("diffusion"), "%f", aDiffusion); +} + +void EnhancedShapeDumper::dumpNumberOfLineSegmentsAsAttribute(sal_Int32 aNumberOfLineSegments) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("numberOfLineSegments"), "%" SAL_PRIdINT32, aNumberOfLineSegments); +} + +void EnhancedShapeDumper::dumpLightFaceAsAttribute(bool bLightFace) +{ + if(bLightFace) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lightFace"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lightFace"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpFirstLightHarshAsAttribute(bool bFirstLightHarsh) +{ + if(bFirstLightHarsh) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("firstLightHarsh"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("firstLightHarsh"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpSecondLightHarshAsAttribute(bool bSecondLightHarsh) +{ + if(bSecondLightHarsh) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("secondLightHarsh"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("secondLightHarsh"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpFirstLightLevelAsAttribute(double aFirstLightLevel) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("firstLightLevel"), "%f", aFirstLightLevel); +} + +void EnhancedShapeDumper::dumpSecondLightLevelAsAttribute(double aSecondLightLevel) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("secondLightLevel"), "%f", aSecondLightLevel); +} + +void EnhancedShapeDumper::dumpDirection3D(drawing::Direction3D aDirection3D) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("directionX"), "%f", aDirection3D.DirectionX); + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("directionY"), "%f", aDirection3D.DirectionY); + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("directionZ"), "%f", aDirection3D.DirectionZ); +} + +void EnhancedShapeDumper::dumpFirstLightDirectionAsElement(drawing::Direction3D aFirstLightDirection) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "FirstLightDirection" )); + dumpDirection3D(aFirstLightDirection); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpSecondLightDirectionAsElement(drawing::Direction3D aSecondLightDirection) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "SecondLightDirection" )); + dumpDirection3D(aSecondLightDirection); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpMetalAsAttribute(bool bMetal) +{ + if(bMetal) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("metal"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("metal"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpShadeModeAsAttribute(drawing::ShadeMode eShadeMode) +{ + switch(eShadeMode) + { + case drawing::ShadeMode_FLAT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shadeMode"), "%s", "FLAT"); + break; + case drawing::ShadeMode_PHONG: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shadeMode"), "%s", "PHONG"); + break; + case drawing::ShadeMode_SMOOTH: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shadeMode"), "%s", "SMOOTH"); + break; + case drawing::ShadeMode_DRAFT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shadeMode"), "%s", "DRAFT"); + break; + default: + break; + } +} + +void EnhancedShapeDumper::dumpRotateAngleAsElement(const drawing::EnhancedCustomShapeParameterPair& aRotateAngle) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RotateAngle" )); + dumpEnhancedCustomShapeParameterPair(aRotateAngle); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRotationCenterAsElement(drawing::Direction3D aRotationCenter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RotationCenter" )); + dumpDirection3D(aRotationCenter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpShininessAsAttribute(double aShininess) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shininess"), "%f", aShininess); +} + +void EnhancedShapeDumper::dumpSkewAsElement(const drawing::EnhancedCustomShapeParameterPair& aSkew) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Skew" )); + dumpEnhancedCustomShapeParameterPair(aSkew); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpSpecularityAsAttribute(double aSpecularity) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("specularity"), "%f", aSpecularity); +} + +void EnhancedShapeDumper::dumpProjectionModeAsAttribute(drawing::ProjectionMode eProjectionMode) +{ + switch(eProjectionMode) + { + case drawing::ProjectionMode_PARALLEL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("projectionMode"), "%s", "PARALLEL"); + break; + case drawing::ProjectionMode_PERSPECTIVE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("projectionMode"), "%s", "PERSPECTIVE"); + break; + default: + break; + } +} + +void EnhancedShapeDumper::dumpViewPointAsElement(drawing::Position3D aViewPoint) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "ViewPoint" )); + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("positionX"), "%f", aViewPoint.PositionX); + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("positionY"), "%f", aViewPoint.PositionY); + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("positionZ"), "%f", aViewPoint.PositionZ); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpOriginAsElement(const drawing::EnhancedCustomShapeParameterPair& aOrigin) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Origin" )); + dumpEnhancedCustomShapeParameterPair(aOrigin); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpExtrusionColorAsAttribute(bool bExtrusionColor) +{ + if(bExtrusionColor) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("extrusionColor"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("extrusionColor"), "%s", "false"); +} + + +// ---------- EnhancedCustomShapeGeometry.idl ----------- + + +void EnhancedShapeDumper::dumpEnhancedCustomShapeGeometryService(const uno::Reference< beans::XPropertySet >& xPropSet) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("Type"); + OUString sType; + if(anotherAny >>= sType) + dumpTypeAsAttribute(sType); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ViewBox"); + awt::Rectangle aViewBox; + if(anotherAny >>= aViewBox) + dumpViewBoxAsElement(aViewBox); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("MirroredX"); + bool bMirroredX; + if(anotherAny >>= bMirroredX) + dumpMirroredXAsAttribute(bMirroredX); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("MirroredY"); + bool bMirroredY; + if(anotherAny >>= bMirroredY) + dumpMirroredYAsAttribute(bMirroredY); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextRotateAngle"); + double aTextRotateAngle = double(); + if(anotherAny >>= aTextRotateAngle) + dumpTextRotateAngleAsAttribute(aTextRotateAngle); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("AdjustmentValues"); + uno::Sequence< drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentValues; + if(anotherAny >>= aAdjustmentValues) + dumpAdjustmentValuesAsElement(aAdjustmentValues); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Extrusion"); + uno::Sequence< beans::PropertyValue > aExtrusion; + if(anotherAny >>= aExtrusion) + dumpExtrusionAsElement(aExtrusion); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Path"); + uno::Sequence< beans::PropertyValue > aPath; + if(anotherAny >>= aPath) + dumpPathAsElement(aPath); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextPath"); + uno::Sequence< beans::PropertyValue > aTextPath; + if(anotherAny >>= aTextPath) + dumpTextPathAsElement(aTextPath); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Equations"); + uno::Sequence< OUString > aEquations; + if(anotherAny >>= aEquations) + dumpEquationsAsElement(aEquations); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Handles"); + uno::Sequence< beans::PropertyValues > aHandles; + if(anotherAny >>= aHandles) + dumpHandlesAsElement(aHandles); + } +} +void EnhancedShapeDumper::dumpTypeAsAttribute(std::u16string_view sType) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("type"), "%s", + OUStringToOString(sType, RTL_TEXTENCODING_UTF8).getStr()); +} + +void EnhancedShapeDumper::dumpViewBoxAsElement(awt::Rectangle aViewBox) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "ViewBox" )); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("x"), "%" SAL_PRIdINT32, aViewBox.X); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("y"), "%" SAL_PRIdINT32, aViewBox.Y); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("width"), "%" SAL_PRIdINT32, aViewBox.Width); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("height"), "%" SAL_PRIdINT32, aViewBox.Height); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpMirroredXAsAttribute(bool bMirroredX) +{ + if(bMirroredX) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("mirroredX"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("mirroredX"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpMirroredYAsAttribute(bool bMirroredY) +{ + if(bMirroredY) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("mirroredY"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("mirroredY"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpTextRotateAngleAsAttribute(double aTextRotateAngle) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textRotateAngle"), "%f", aTextRotateAngle); +} + +void EnhancedShapeDumper::dumpAdjustmentValuesAsElement(const uno::Sequence< drawing::EnhancedCustomShapeAdjustmentValue>& aAdjustmentValues) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "AdjustmentValues" )); + for (const auto& i : aAdjustmentValues) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "EnhancedCustomShapeAdjustmentValue" )); + uno::Any aAny = i.Value; + OUString sValue; + float fValue; + sal_Int32 nValue; + bool bValue; + if(aAny >>= sValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", + OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr()); + } + else if(aAny >>= nValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%" SAL_PRIdINT32, nValue); + } + else if(aAny >>= fValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%f", fValue); + } + else if(aAny >>= bValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", (bValue? "true": "false")); + } + + switch(i.State) + { + case beans::PropertyState_DIRECT_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "DIRECT_VALUE"); + break; + case beans::PropertyState_DEFAULT_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "DEFAULT_VALUE"); + break; + case beans::PropertyState_AMBIGUOUS_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "AMBIGUOUS_VALUE"); + break; + default: + break; + } + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpPropertyValueAsElement(const beans::PropertyValue& aPropertyValue) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "PropertyValue" )); + + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("name"), "%s", + OUStringToOString(aPropertyValue.Name, RTL_TEXTENCODING_UTF8).getStr()); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("handle"), "%" SAL_PRIdINT32, aPropertyValue.Handle); + + uno::Any aAny = aPropertyValue.Value; + OUString sValue; + if(aAny >>= sValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", + OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr()); + } + switch(aPropertyValue.State) + { + case beans::PropertyState_DIRECT_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "DIRECT_VALUE"); + break; + case beans::PropertyState_DEFAULT_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "DEFAULT_VALUE"); + break; + case beans::PropertyState_AMBIGUOUS_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "AMBIGUOUS_VALUE"); + break; + default: + break; + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpExtrusionAsElement(const uno::Sequence< beans::PropertyValue >& aExtrusion) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Extrusion" )); + for (const auto& i : aExtrusion) + { + dumpPropertyValueAsElement(i); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpPathAsElement(const uno::Sequence< beans::PropertyValue >& aPath) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Path" )); + for (const auto& i : aPath) + { + dumpPropertyValueAsElement(i); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpTextPathAsElement(const uno::Sequence< beans::PropertyValue >& aTextPath) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "TextPath" )); + for (const auto& i : aTextPath) + { + dumpPropertyValueAsElement(i); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpEquationsAsElement(const uno::Sequence< OUString >& aEquations) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Equations" )); + for (const auto& i : aEquations) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("name"), "%s", + OUStringToOString(i, RTL_TEXTENCODING_UTF8).getStr()); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +// PropertyValues specifies a sequence of PropertyValue instances. +// so in this case it's a Sequence of a Sequence of a PropertyValue instances. +// Welcome to Sequenception again. +void EnhancedShapeDumper::dumpHandlesAsElement(const uno::Sequence< beans::PropertyValues >& aHandles) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Handles" )); + for (const auto& i : aHandles) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "PropertyValues" )); + uno::Sequence< beans::PropertyValue > propertyValueSequence = i; + for (const auto& j : propertyValueSequence) + { + dumpPropertyValueAsElement(j); + } + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + + +// ---------- EnhancedCustomShapeHandle.idl ----------- + + +void EnhancedShapeDumper::dumpEnhancedCustomShapeHandleService(const uno::Reference< beans::XPropertySet >& xPropSet) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("MirroredX"); + bool bMirroredX; + if(anotherAny >>= bMirroredX) + dumpMirroredXAsAttribute(bMirroredX); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("MirroredY"); + bool bMirroredY; + if(anotherAny >>= bMirroredY) + dumpMirroredYAsAttribute(bMirroredY); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Switched"); + bool bSwitched; + if(anotherAny >>= bSwitched) + dumpSwitchedAsAttribute(bSwitched); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Position"); + drawing::EnhancedCustomShapeParameterPair aPosition; + if(anotherAny >>= aPosition) + dumpPositionAsElement(aPosition); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Polar"); + drawing::EnhancedCustomShapeParameterPair aPolar; + if(anotherAny >>= aPolar) + dumpPolarAsElement(aPolar); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RefX"); + sal_Int32 aRefX = sal_Int32(); + if(anotherAny >>= aRefX) + dumpRefXAsAttribute(aRefX); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RefY"); + sal_Int32 aRefY = sal_Int32(); + if(anotherAny >>= aRefY) + dumpRefYAsAttribute(aRefY); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RefAngle"); + sal_Int32 aRefAngle = sal_Int32(); + if(anotherAny >>= aRefAngle) + dumpRefAngleAsAttribute(aRefAngle); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RefR"); + sal_Int32 aRefR = sal_Int32(); + if(anotherAny >>= aRefR) + dumpRefRAsAttribute(aRefR); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RangeXMinimum"); + drawing::EnhancedCustomShapeParameter aRangeXMinimum; + if(anotherAny >>= aRangeXMinimum) + dumpRangeXMinimumAsElement(aRangeXMinimum); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RangeXMaximum"); + drawing::EnhancedCustomShapeParameter aRangeXMaximum; + if(anotherAny >>= aRangeXMaximum) + dumpRangeXMaximumAsElement(aRangeXMaximum); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RangeYMinimum"); + drawing::EnhancedCustomShapeParameter aRangeYMinimum; + if(anotherAny >>= aRangeYMinimum) + dumpRangeYMinimumAsElement(aRangeYMinimum); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RangeYMaximum"); + drawing::EnhancedCustomShapeParameter aRangeYMaximum; + if(anotherAny >>= aRangeYMaximum) + dumpRangeYMaximumAsElement(aRangeYMaximum); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RadiusRangeMinimum"); + drawing::EnhancedCustomShapeParameter aRadiusRangeMinimum; + if(anotherAny >>= aRadiusRangeMinimum) + dumpRadiusRangeMinimumAsElement(aRadiusRangeMinimum); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("RadiusRangeMaximum"); + drawing::EnhancedCustomShapeParameter aRadiusRangeMaximum; + if(anotherAny >>= aRadiusRangeMaximum) + dumpRadiusRangeMaximumAsElement(aRadiusRangeMaximum); + } +} + +void EnhancedShapeDumper::dumpSwitchedAsAttribute(bool bSwitched) +{ + if(bSwitched) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("switched"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("switched"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpPositionAsElement(const drawing::EnhancedCustomShapeParameterPair& aPosition) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Position" )); + dumpEnhancedCustomShapeParameterPair(aPosition); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpPolarAsElement(const drawing::EnhancedCustomShapeParameterPair& aPolar) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Polar" )); + dumpEnhancedCustomShapeParameterPair(aPolar); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRefXAsAttribute(sal_Int32 aRefX) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("refX"), "%" SAL_PRIdINT32, aRefX); +} + +void EnhancedShapeDumper::dumpRefYAsAttribute(sal_Int32 aRefY) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("refY"), "%" SAL_PRIdINT32, aRefY); +} + +void EnhancedShapeDumper::dumpRefAngleAsAttribute(sal_Int32 aRefAngle) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("refAngle"), "%" SAL_PRIdINT32, aRefAngle); +} + +void EnhancedShapeDumper::dumpRefRAsAttribute(sal_Int32 aRefR) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("refR"), "%" SAL_PRIdINT32, aRefR); +} + +void EnhancedShapeDumper::dumpEnhancedCustomShapeParameter( + const drawing::EnhancedCustomShapeParameter& aParameter) +{ + uno::Any aAny = aParameter.Value; + OUString sValue; + float fValue; + sal_Int32 nValue; + bool bValue; + if(aAny >>= sValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", + OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr()); + } + else if(aAny >>= nValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%" SAL_PRIdINT32, nValue); + } + else if(aAny >>= fValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%f", fValue); + } + else if(aAny >>= bValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", (bValue? "true": "false")); + } + sal_Int32 aType = aParameter.Type; + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("type"), "%" SAL_PRIdINT32, aType); +} + +void EnhancedShapeDumper::dumpRangeXMinimumAsElement(const drawing::EnhancedCustomShapeParameter& aRangeXMinimum) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RangeXMinimum" )); + dumpEnhancedCustomShapeParameter(aRangeXMinimum); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRangeXMaximumAsElement(const drawing::EnhancedCustomShapeParameter& aRangeXMaximum) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RangeXMaximum" )); + dumpEnhancedCustomShapeParameter(aRangeXMaximum); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRangeYMinimumAsElement(const drawing::EnhancedCustomShapeParameter& aRangeYMinimum) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RangeYMinimum" )); + dumpEnhancedCustomShapeParameter(aRangeYMinimum); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRangeYMaximumAsElement(const drawing::EnhancedCustomShapeParameter& aRangeYMaximum) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RangeYMaximum" )); + dumpEnhancedCustomShapeParameter(aRangeYMaximum); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRadiusRangeMinimumAsElement(const drawing::EnhancedCustomShapeParameter& aRadiusRangeMinimum) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RadiusRangeMinimum" )); + dumpEnhancedCustomShapeParameter(aRadiusRangeMinimum); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpRadiusRangeMaximumAsElement(const drawing::EnhancedCustomShapeParameter& aRadiusRangeMaximum) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "RadiusRangeMaximum" )); + dumpEnhancedCustomShapeParameter(aRadiusRangeMaximum); + (void)xmlTextWriterEndElement( xmlWriter ); +} + + +// ---------- EnhancedCustomShapePath.idl --------------- + + +void EnhancedShapeDumper::dumpEnhancedCustomShapePathService(const uno::Reference< beans::XPropertySet >& xPropSet) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("Coordinates"); + uno::Sequence< drawing::EnhancedCustomShapeParameterPair > aCoordinates; + if(anotherAny >>= aCoordinates) + dumpCoordinatesAsElement(aCoordinates); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Segments"); + uno::Sequence< drawing::EnhancedCustomShapeSegment > aSegments; + if(anotherAny >>= aSegments) + dumpSegmentsAsElement(aSegments); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("StretchX"); + sal_Int32 aStretchX = sal_Int32(); + if(anotherAny >>= aStretchX) + dumpStretchXAsAttribute(aStretchX); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("StretchY"); + sal_Int32 aStretchY = sal_Int32(); + if(anotherAny >>= aStretchY) + dumpStretchYAsAttribute(aStretchY); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextFrames"); + uno::Sequence< drawing::EnhancedCustomShapeTextFrame > aTextFrames; + if(anotherAny >>= aTextFrames) + dumpTextFramesAsElement(aTextFrames); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("GluePoints"); + uno::Sequence< drawing::EnhancedCustomShapeParameterPair > aGluePoints; + if(anotherAny >>= aGluePoints) + dumpGluePointsAsElement(aGluePoints); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("GluePointLeavingDirections"); + uno::Sequence< double > aGluePointLeavingDirections; + if(anotherAny >>= aGluePointLeavingDirections) + dumpGluePointLeavingDirectionsAsElement(aGluePointLeavingDirections); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("GluePointType"); + sal_Int32 aGluePointType = sal_Int32(); + if(anotherAny >>= aGluePointType) + dumpGluePointTypeAsAttribute(aGluePointType); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ExtrusionAllowed"); + bool bExtrusionAllowed; + if(anotherAny >>= bExtrusionAllowed) + dumpExtrusionAllowedAsAttribute(bExtrusionAllowed); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ConcentricGradientFillAllowed"); + bool bConcentricGradientFillAllowed; + if(anotherAny >>= bConcentricGradientFillAllowed) + dumpConcentricGradientFillAllowedAsAttribute(bConcentricGradientFillAllowed); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextPathAllowed"); + bool bTextPathAllowed; + if(anotherAny >>= bTextPathAllowed) + dumpTextPathAllowedAsAttribute(bTextPathAllowed); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("SubViewSize"); + uno::Sequence< awt::Size > aSubViewSize; + if(anotherAny >>= aSubViewSize) + dumpSubViewSizeAsElement(aSubViewSize); + } +} + +void EnhancedShapeDumper::dumpCoordinatesAsElement(const uno::Sequence< drawing::EnhancedCustomShapeParameterPair >& aCoordinates) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Coordinates" )); + for (const auto& i : aCoordinates) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "EnhancedCustomShapeParameterPair" )); + dumpEnhancedCustomShapeParameterPair(i); + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpSegmentsAsElement(const uno::Sequence< drawing::EnhancedCustomShapeSegment >& aSegments) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Segments" )); + for (const auto& i : aSegments) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "EnhancedCustomShapeSegment" )); + sal_Int32 aCommand = i.Command; + sal_Int32 aCount = i.Count; + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("command"), "%" SAL_PRIdINT32, aCommand); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("count"), "%" SAL_PRIdINT32, aCount); + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpStretchXAsAttribute(sal_Int32 aStretchX) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("stretchX"), "%" SAL_PRIdINT32, aStretchX); +} + +void EnhancedShapeDumper::dumpStretchYAsAttribute(sal_Int32 aStretchY) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("stretchY"), "%" SAL_PRIdINT32, aStretchY); +} + +void EnhancedShapeDumper::dumpTextFramesAsElement(const uno::Sequence< drawing::EnhancedCustomShapeTextFrame >& aTextFrames) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "TextFrames" )); + for (const auto& i : aTextFrames) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "EnhancedCustomShapeTextFrame" )); + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "TopLeft" )); + dumpEnhancedCustomShapeParameterPair(i.TopLeft); + (void)xmlTextWriterEndElement( xmlWriter ); + + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "BottomRight" )); + dumpEnhancedCustomShapeParameterPair(i.BottomRight); + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpGluePointsAsElement(const uno::Sequence< drawing::EnhancedCustomShapeParameterPair >& aGluePoints) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "GluePoints" )); + for (const auto& i : aGluePoints) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "EnhancedCustomShapeParameterPair" )); + dumpEnhancedCustomShapeParameterPair(i); + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpGluePointLeavingDirectionsAsElement(const uno::Sequence< double >& aGluePointLeavingDirections) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "GluePointLeavingDirections" )); + for (const auto& i : aGluePointLeavingDirections) + { + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("value"), "%f", i); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void EnhancedShapeDumper::dumpGluePointTypeAsAttribute(sal_Int32 aGluePointType) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("gluePointType"), "%" SAL_PRIdINT32, aGluePointType); +} + +void EnhancedShapeDumper::dumpExtrusionAllowedAsAttribute(bool bExtrusionAllowed) +{ + if(bExtrusionAllowed) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("extrusionAllowed"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("extrusionAllowed"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpConcentricGradientFillAllowedAsAttribute(bool bConcentricGradientFillAllowed) +{ + if(bConcentricGradientFillAllowed) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("concentricGradientFillAllowed"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("concentricGradientFillAllowed"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpTextPathAllowedAsAttribute(bool bTextPathAllowed) +{ + if(bTextPathAllowed) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPathAllowed"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPathAllowed"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpSubViewSizeAsElement(const uno::Sequence< awt::Size >& aSubViewSize) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "SubViewSize" )); + for (const auto& i : aSubViewSize) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Size" )); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("width"), "%" SAL_PRIdINT32, i.Width); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("height"), "%" SAL_PRIdINT32, i.Height); + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + + +// ---------- EnhancedCustomShapeTextPath.idl --------------- + + +void EnhancedShapeDumper::dumpEnhancedCustomShapeTextPathService(const uno::Reference< beans::XPropertySet >& xPropSet) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextPath"); + bool bTextPath; + if(anotherAny >>= bTextPath) + dumpTextPathAsAttribute(bTextPath); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextPathMode"); + drawing::EnhancedCustomShapeTextPathMode eTextPathMode; + if(anotherAny >>= eTextPathMode) + dumpTextPathModeAsAttribute(eTextPathMode); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ScaleX"); + bool bScaleX; + if(anotherAny >>= bScaleX) + dumpScaleXAsAttribute(bScaleX); + } +} + +void EnhancedShapeDumper::dumpTextPathAsAttribute(bool bTextPath) +{ + if(bTextPath) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPath"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPath"), "%s", "false"); +} + +void EnhancedShapeDumper::dumpTextPathModeAsAttribute(drawing::EnhancedCustomShapeTextPathMode eTextPathMode) +{ + switch(eTextPathMode) + { + case drawing::EnhancedCustomShapeTextPathMode_NORMAL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPathMode"), "%s", "NORMAL"); + break; + case drawing::EnhancedCustomShapeTextPathMode_PATH: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPathMode"), "%s", "PATH"); + break; + case drawing::EnhancedCustomShapeTextPathMode_SHAPE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textPathMode"), "%s", "SHAPE"); + break; + default: + break; + } +} + +void EnhancedShapeDumper::dumpScaleXAsAttribute(bool bScaleX) +{ + if(bScaleX) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("scaleX"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("scaleX"), "%s", "false"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/dumper/EnhancedShapeDumper.hxx b/drawinglayer/source/dumper/EnhancedShapeDumper.hxx new file mode 100644 index 000000000..f6c8453e9 --- /dev/null +++ b/drawinglayer/source/dumper/EnhancedShapeDumper.hxx @@ -0,0 +1,133 @@ +/* -*- 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/. + */ +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <libxml/xmlwriter.h> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/Direction3D.hpp> +#include <com/sun/star/drawing/ShadeMode.hpp> +#include <com/sun/star/drawing/ProjectionMode.hpp> +#include <com/sun/star/drawing/Position3D.hpp> + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/PropertyValues.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameter.hpp> + +#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp> +#include <com/sun/star/awt/Size.hpp> + +#include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp> + +class EnhancedShapeDumper +{ +public: + explicit EnhancedShapeDumper(xmlTextWriterPtr writer) + : xmlWriter(writer) + { + } + + // auxiliary functions + void dumpEnhancedCustomShapeParameterPair( + const css::drawing::EnhancedCustomShapeParameterPair& aParameterPair); + void dumpDirection3D(css::drawing::Direction3D aDirection3D); + void dumpPropertyValueAsElement(const css::beans::PropertyValue& aPropertyValue); + void + dumpEnhancedCustomShapeParameter(const css::drawing::EnhancedCustomShapeParameter& aParameter); + + // EnhancedCustomShapeExtrusion.idl + void dumpEnhancedCustomShapeExtrusionService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet); + void dumpExtrusionAsAttribute(bool bExtrusion); + void dumpBrightnessAsAttribute(double aBrightness); + void dumpDepthAsElement(const css::drawing::EnhancedCustomShapeParameterPair& aDepth); + void dumpDiffusionAsAttribute(double aDiffusion); + void dumpNumberOfLineSegmentsAsAttribute(sal_Int32 aNumberOfLineSegments); + void dumpLightFaceAsAttribute(bool bLightFace); + void dumpFirstLightHarshAsAttribute(bool bFirstLightHarsh); + void dumpSecondLightHarshAsAttribute(bool bSecondLightHarsh); + void dumpFirstLightLevelAsAttribute(double aFirstLightLevel); + void dumpSecondLightLevelAsAttribute(double aSecondLightLevel); + void dumpFirstLightDirectionAsElement(css::drawing::Direction3D aFirstLightDirection); + void dumpSecondLightDirectionAsElement(css::drawing::Direction3D aSecondLightDirection); + void dumpMetalAsAttribute(bool bMetal); + void dumpShadeModeAsAttribute(css::drawing::ShadeMode eShadeMode); + void dumpRotateAngleAsElement(const css::drawing::EnhancedCustomShapeParameterPair& aRotateAngle); + void dumpRotationCenterAsElement(css::drawing::Direction3D aRotationCenter); + void dumpShininessAsAttribute(double aShininess); + void dumpSkewAsElement(const css::drawing::EnhancedCustomShapeParameterPair& aSkew); + void dumpSpecularityAsAttribute(double aSpecularity); + void dumpProjectionModeAsAttribute(css::drawing::ProjectionMode eProjectionMode); + void dumpViewPointAsElement(css::drawing::Position3D aViewPoint); + void dumpOriginAsElement(const css::drawing::EnhancedCustomShapeParameterPair& aOrigin); + void dumpExtrusionColorAsAttribute(bool bExtrusionColor); + + // EnhancedCustomShapeGeometry.idl + void dumpEnhancedCustomShapeGeometryService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet); + void dumpTypeAsAttribute(std::u16string_view sType); + void dumpViewBoxAsElement(css::awt::Rectangle aViewBox); + void dumpMirroredXAsAttribute(bool bMirroredX); // also used in EnhancedCustomShapeHandle + void dumpMirroredYAsAttribute(bool bMirroredY); // also used in EnhancedCustomShapeHandle + void dumpTextRotateAngleAsAttribute(double aTextRotateAngle); + void dumpAdjustmentValuesAsElement(const css::uno::Sequence< css::drawing::EnhancedCustomShapeAdjustmentValue>& aAdjustmentValues); + void dumpExtrusionAsElement(const css::uno::Sequence< css::beans::PropertyValue >& aExtrusion); + void dumpPathAsElement(const css::uno::Sequence< css::beans::PropertyValue >& aPath); + void dumpTextPathAsElement(const css::uno::Sequence< css::beans::PropertyValue >& aTextPath); + void dumpEquationsAsElement(const css::uno::Sequence< OUString >& aEquations); + void dumpHandlesAsElement(const css::uno::Sequence< css::beans::PropertyValues >& aHandles); + + // EnhancedCustomShapeHandle.idl + void dumpEnhancedCustomShapeHandleService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet); + void dumpSwitchedAsAttribute(bool bSwitched); + void dumpPositionAsElement(const css::drawing::EnhancedCustomShapeParameterPair& aPosition); + void dumpPolarAsElement(const css::drawing::EnhancedCustomShapeParameterPair& aPolar); + void dumpRefXAsAttribute(sal_Int32 aRefX); + void dumpRefYAsAttribute(sal_Int32 aRefY); + void dumpRefAngleAsAttribute(sal_Int32 aRefAngle); + void dumpRefRAsAttribute(sal_Int32 aRefR); + void dumpRangeXMinimumAsElement(const css::drawing::EnhancedCustomShapeParameter& aRangeXMinimum); + void dumpRangeXMaximumAsElement(const css::drawing::EnhancedCustomShapeParameter& aRangeXMaximum); + void dumpRangeYMinimumAsElement(const css::drawing::EnhancedCustomShapeParameter& aRangeYMinimum); + void dumpRangeYMaximumAsElement(const css::drawing::EnhancedCustomShapeParameter& aRangeXMaximum); + void dumpRadiusRangeMinimumAsElement(const css::drawing::EnhancedCustomShapeParameter& aRadiusRangeMinimum); + void dumpRadiusRangeMaximumAsElement(const css::drawing::EnhancedCustomShapeParameter& aRadiusRangeMaximum); + + // EnhancedCustomShapePath.idl + void dumpEnhancedCustomShapePathService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet); + void dumpCoordinatesAsElement(const css::uno::Sequence< css::drawing::EnhancedCustomShapeParameterPair >& aCoordinates); + void dumpSegmentsAsElement(const css::uno::Sequence< css::drawing::EnhancedCustomShapeSegment >& aSegments); + void dumpStretchXAsAttribute(sal_Int32 aStretchX); + void dumpStretchYAsAttribute(sal_Int32 aStretchY); + void dumpTextFramesAsElement(const css::uno::Sequence< css::drawing::EnhancedCustomShapeTextFrame >& aTextFrames); + void dumpGluePointsAsElement(const css::uno::Sequence< css::drawing::EnhancedCustomShapeParameterPair >& aGluePoints); + void dumpGluePointLeavingDirectionsAsElement(const css::uno::Sequence< double >& aGluePointLeavingDirections); + void dumpGluePointTypeAsAttribute(sal_Int32 aGluePointType); + void dumpExtrusionAllowedAsAttribute(bool bExtrusionAllowed); + void dumpConcentricGradientFillAllowedAsAttribute(bool bConcentricGradientFillAllowed); + void dumpTextPathAllowedAsAttribute(bool bTextPathAllowed); + void dumpSubViewSizeAsElement(const css::uno::Sequence< css::awt::Size >& aSubViewSize); + + // EnhancedCustomShapePath.idl + void dumpEnhancedCustomShapeTextPathService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet); + void dumpTextPathAsAttribute(bool bTextPath); + void dumpTextPathModeAsAttribute(css::drawing::EnhancedCustomShapeTextPathMode eTextPathMode); + void dumpScaleXAsAttribute(bool bScaleX); + +private: + xmlTextWriterPtr xmlWriter; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/dumper/XShapeDumper.cxx b/drawinglayer/source/dumper/XShapeDumper.cxx new file mode 100644 index 000000000..d0e2cf97a --- /dev/null +++ b/drawinglayer/source/dumper/XShapeDumper.cxx @@ -0,0 +1,1986 @@ +/* -*- 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/. + */ + +#include <drawinglayer/XShapeDumper.hxx> +#include "EnhancedShapeDumper.hxx" +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/awt/Gradient.hpp> +#include <com/sun/star/drawing/Hatch.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/drawing/RectanglePoint.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> + +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/LineDash.hpp> +#include <com/sun/star/drawing/LineJoint.hpp> +#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> + +#include <com/sun/star/drawing/PolygonKind.hpp> + +#include <com/sun/star/drawing/TextFitToSizeType.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/TextAnimationDirection.hpp> +#include <com/sun/star/drawing/TextAnimationKind.hpp> +#include <com/sun/star/text/WritingMode.hpp> + +#include <com/sun/star/drawing/HomogenMatrixLine3.hpp> +#include <com/sun/star/drawing/HomogenMatrix3.hpp> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/text/XText.hpp> +#include <rtl/strbuf.hxx> +#include <libxml/xmlwriter.h> +#include <iostream> +#include <string_view> +#include <rtl/ustring.hxx> + +#define DEBUG_DUMPER 0 + +using namespace com::sun::star; + +namespace { + +// FUNCTION DECLARATIONS + +// auxiliary functions +void dumpGradientProperty(const css::awt::Gradient& rGradient, xmlTextWriterPtr xmlWriter); +void dumpPolyPolygonBezierCoords(const css::drawing::PolyPolygonBezierCoords& rPolyPolygonBezierCoords, xmlTextWriterPtr xmlWriter); +void dumpPointSequenceSequence(const css::drawing::PointSequenceSequence& rPointSequenceSequence, const uno::Sequence<uno::Sequence<drawing::PolygonFlags> >*, xmlTextWriterPtr xmlWriter); +void dumpPropertyValueAsElement(const beans::PropertyValue& rPropertyValue, xmlTextWriterPtr xmlWriter); + +// FillProperties.idl +void dumpFillStyleAsAttribute(css::drawing::FillStyle eFillStyle, xmlTextWriterPtr xmlWriter); +void dumpFillColorAsAttribute(sal_Int32 aColor, xmlTextWriterPtr xmlWriter); +void dumpFillTransparenceAsAttribute(sal_Int32 aTransparence, xmlTextWriterPtr xmlWriter); +void dumpFillTransparenceGradientNameAsAttribute(std::u16string_view sTranspGradName, xmlTextWriterPtr xmlWriter); +void dumpFillTransparenceGradientAsElement(const css::awt::Gradient& rTranspGrad, xmlTextWriterPtr xmlWriter); +void dumpFillGradientNameAsAttribute(std::u16string_view sGradName, xmlTextWriterPtr xmlWriter); +void dumpFillGradientAsElement(const css::awt::Gradient& rGradient, xmlTextWriterPtr xmlWriter); +void dumpFillHatchAsElement(const css::drawing::Hatch& rHatch, xmlTextWriterPtr xmlWriter); +void dumpFillBackgroundAsAttribute(bool bBackground, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapAsElement(const css::uno::Reference<css::awt::XBitmap>& xBitmap, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapPositionOffsetXAsAttribute(sal_Int32 aBitmapPositionOffsetX, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapPositionOffsetYAsAttribute(sal_Int32 aBitmapPositionOffsetY, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapOffsetXAsAttribute(sal_Int32 aBitmapOffsetX, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapOffsetYAsAttribute(sal_Int32 aBitmapOffsetY, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapRectanglePointAsAttribute(css::drawing::RectanglePoint eBitmapRectanglePoint, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapLogicalSizeAsAttribute(bool bBitmapLogicalSize, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapSizeXAsAttribute(sal_Int32 aBitmapSizeX, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapSizeYAsAttribute(sal_Int32 aBitmapSizeY, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapModeAsAttribute(css::drawing::BitmapMode eBitmapMode, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapStretchAsAttribute(bool bBitmapStretch, xmlTextWriterPtr xmlWriter); +void dumpFillBitmapTileAsAttribute(bool bBitmapTile, xmlTextWriterPtr xmlWriter); + +// LineProperties.idl +void dumpLineStyleAsAttribute(css::drawing::LineStyle eLineStyle, xmlTextWriterPtr xmlWriter); +void dumpLineDashAsElement(const css::drawing::LineDash& rLineDash, xmlTextWriterPtr xmlWriter); +void dumpLineDashNameAsAttribute(std::u16string_view sLineDashName, xmlTextWriterPtr xmlWriter); +void dumpLineColorAsAttribute(sal_Int32 aLineColor, xmlTextWriterPtr xmlWriter); +void dumpLineTransparenceAsAttribute(sal_Int32 aLineTransparence, xmlTextWriterPtr xmlWriter); +void dumpLineWidthAsAttribute(sal_Int32 aLineWidth, xmlTextWriterPtr xmlWriter); +void dumpLineJointAsAttribute(css::drawing::LineJoint eLineJoint, xmlTextWriterPtr xmlWriter); +void dumpLineStartNameAsAttribute(std::u16string_view sLineStartName, xmlTextWriterPtr xmlWriter); +void dumpLineEndNameAsAttribute(std::u16string_view sLineEndName, xmlTextWriterPtr xmlWriter); +void dumpLineStartAsElement(const css::drawing::PolyPolygonBezierCoords& rLineStart, xmlTextWriterPtr xmlWriter); +void dumpLineEndAsElement(const css::drawing::PolyPolygonBezierCoords& rLineEnd, xmlTextWriterPtr xmlWriter); +void dumpLineStartCenterAsAttribute(bool bLineStartCenter, xmlTextWriterPtr xmlWriter); +void dumpLineStartWidthAsAttribute(sal_Int32 aLineStartWidth, xmlTextWriterPtr xmlWriter); +void dumpLineEndCenterAsAttribute(bool bLineEndCenter, xmlTextWriterPtr xmlWriter); +void dumpLineEndWidthAsAttribute(sal_Int32 aLineEndWidth, xmlTextWriterPtr xmlWriter); + +// PolyPolygonDescriptor.idl +void dumpPolygonKindAsAttribute(css::drawing::PolygonKind ePolygonKind, xmlTextWriterPtr xmlWriter); +void dumpPolyPolygonAsElement(const css::drawing::PointSequenceSequence& rPolyPolygon, xmlTextWriterPtr xmlWriter); +void dumpGeometryAsElement(const css::drawing::PointSequenceSequence& rGeometry, xmlTextWriterPtr xmlWriter); + +// CharacterProperties.idl +void dumpCharHeightAsAttribute(float fHeight, xmlTextWriterPtr xmlWriter); +void dumpCharColorAsAttribute(sal_Int32 aColor, xmlTextWriterPtr xmlWriter); + +// TextProperties.idl +void dumpIsNumberingAsAttribute(bool bIsNumbering, xmlTextWriterPtr xmlWriter); +void dumpTextAutoGrowHeightAsAttribute(bool bTextAutoGrowHeight, xmlTextWriterPtr xmlWriter); +void dumpTextAutoGrowWidthAsAttribute(bool bTextAutoGrowWidth, xmlTextWriterPtr xmlWriter); +void dumpTextContourFrameAsAttribute(bool bTextContourFrame, xmlTextWriterPtr xmlWriter); +void dumpTextFitToSizeAsAttribute(css::drawing::TextFitToSizeType eTextFitToSize, xmlTextWriterPtr xmlWriter); +void dumpTextHorizontalAdjustAsAttribute(css::drawing::TextHorizontalAdjust eTextHorizontalAdjust, xmlTextWriterPtr xmlWriter); +void dumpTextVerticalAdjustAsAttribute(css::drawing::TextVerticalAdjust eTextVerticalAdjust, xmlTextWriterPtr xmlWriter); +void dumpTextLeftDistanceAsAttribute(sal_Int32 aTextLeftDistance, xmlTextWriterPtr xmlWriter); +void dumpTextRightDistanceAsAttribute(sal_Int32 aTextRightDistance, xmlTextWriterPtr xmlWriter); +void dumpTextUpperDistanceAsAttribute(sal_Int32 aTextUpperDistance, xmlTextWriterPtr xmlWriter); +void dumpTextLowerDistanceAsAttribute(sal_Int32 aTextLowerDistance, xmlTextWriterPtr xmlWriter); +void dumpTextMaximumFrameHeightAsAttribute(sal_Int32 aTextMaximumFrameHeight, xmlTextWriterPtr xmlWriter); +void dumpTextMaximumFrameWidthAsAttribute(sal_Int32 aTextMaximumFrameWidth, xmlTextWriterPtr xmlWriter); +void dumpTextMinimumFrameHeightAsAttribute(sal_Int32 aTextMinimumFrameHeight, xmlTextWriterPtr xmlWriter); +void dumpTextMinimumFrameWidthAsAttribute(sal_Int32 aTextMinimumFrameWidth, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationAmountAsAttribute(sal_Int32 aTextAnimationAmount, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationCountAsAttribute(sal_Int32 aTextAnimationCount, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationDelayAsAttribute(sal_Int32 aTextAnimationDelay, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationDirectionAsAttribute(css::drawing::TextAnimationDirection eTextAnimationDirection, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationKindAsAttribute(css::drawing::TextAnimationKind eTextAnimationKind, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationStartInsideAsAttribute(bool bTextAnimationStartInside, xmlTextWriterPtr xmlWriter); +void dumpTextAnimationStopInsideAsAttribute(bool bTextAnimationStopInside, xmlTextWriterPtr xmlWriter); +void dumpTextWritingModeAsAttribute(css::text::WritingMode eWritingMode, xmlTextWriterPtr xmlWriter); + +// ShadowProperties.idl +void dumpShadowAsAttribute(bool bShadow, xmlTextWriterPtr xmlWriter); +void dumpShadowColorAsAttribute(sal_Int32 aShadowColor, xmlTextWriterPtr xmlWriter); +void dumpShadowTransparenceAsAttribute(sal_Int32 aShadowTransparence, xmlTextWriterPtr xmlWriter); +void dumpShadowXDistanceAsAttribute(sal_Int32 aShadowXDistance, xmlTextWriterPtr xmlWriter); +void dumpShadowYDistanceAsAttribute(sal_Int32 aShadowYDistance, xmlTextWriterPtr xmlWriter); + +//Shape.idl +void dumpZOrderAsAttribute(sal_Int32 aZOrder, xmlTextWriterPtr xmlWriter); +void dumpLayerIDAsAttribute(sal_Int32 aLayerID, xmlTextWriterPtr xmlWriter); +void dumpLayerNameAsAttribute(std::u16string_view sLayerName, xmlTextWriterPtr xmlWriter); +void dumpVisibleAsAttribute(bool bVisible, xmlTextWriterPtr xmlWriter); +void dumpPrintableAsAttribute(bool bPrintable, xmlTextWriterPtr xmlWriter); +void dumpMoveProtectAsAttribute(bool bMoveProtect, xmlTextWriterPtr xmlWriter); +void dumpNameAsAttribute(std::u16string_view sName, xmlTextWriterPtr xmlWriter); +void dumpSizeProtectAsAttribute(bool bSizeProtect, xmlTextWriterPtr xmlWriter); +void dumpHomogenMatrixLine3(const css::drawing::HomogenMatrixLine3& rLine, xmlTextWriterPtr xmlWriter); +void dumpTransformationAsElement(const css::drawing::HomogenMatrix3& rTransformation, xmlTextWriterPtr xmlWriter); +void dumpNavigationOrderAsAttribute(sal_Int32 aNavigationOrder, xmlTextWriterPtr xmlWriter); +void dumpHyperlinkAsAttribute(std::u16string_view sHyperlink, xmlTextWriterPtr xmlWriter); +void dumpInteropGrabBagAsElement(const uno::Sequence< beans::PropertyValue>& aInteropGrabBag, xmlTextWriterPtr xmlWriter); + +// CustomShape.idl +void dumpCustomShapeEngineAsAttribute(std::u16string_view sCustomShapeEngine, xmlTextWriterPtr xmlWriter); +void dumpCustomShapeDataAsAttribute( + std::u16string_view sCustomShapeData, xmlTextWriterPtr xmlWriter); +void dumpCustomShapeGeometryAsElement(const css::uno::Sequence< css::beans::PropertyValue>& aCustomShapeGeometry, xmlTextWriterPtr xmlWriter); +void dumpCustomShapeReplacementURLAsAttribute(std::u16string_view sCustomShapeReplacementURL, xmlTextWriterPtr xmlWriter); + +// XShape.idl +void dumpPositionAsAttribute(const css::awt::Point& rPoint, xmlTextWriterPtr xmlWriter); +void dumpSizeAsAttribute(const css::awt::Size& rSize, xmlTextWriterPtr xmlWriter); + +// the rest +void dumpShapeDescriptorAsAttribute( const css::uno::Reference< css::drawing::XShapeDescriptor >& xDescr, xmlTextWriterPtr xmlWriter ); +void dumpXShape(const css::uno::Reference< css::drawing::XShape >& xShape, xmlTextWriterPtr xmlWriter, bool bDumpInteropProperties); +void dumpXShapes( const css::uno::Reference< css::drawing::XShapes >& xShapes, xmlTextWriterPtr xmlWriter, bool bDumpInteropProperties ); +void dumpTextPropertiesService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); +void dumpFillPropertiesService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); +void dumpLinePropertiesService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); +void dumpShadowPropertiesService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); +void dumpPolyPolygonDescriptorService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); +void dumpShapeService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter, bool bDumpInteropProperties); +void dumpPolyPolygonBezierDescriptorService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); +void dumpCustomShapeService(const css::uno::Reference< css::beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter); + + +int writeCallback(void* pContext, const char* sBuffer, int nLen) +{ + OStringBuffer* pBuffer = static_cast<OStringBuffer*>(pContext); + pBuffer->append(sBuffer); + return nLen; +} + +int closeCallback(void* ) +{ + return 0; +} + +bool m_bNameDumped; + + +// ---------- FillProperties.idl ---------- + +void dumpFillStyleAsAttribute(drawing::FillStyle eFillStyle, xmlTextWriterPtr xmlWriter) +{ + switch(eFillStyle) + { + case drawing::FillStyle_NONE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillStyle"), "%s", "NONE"); + break; + case drawing::FillStyle_SOLID: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillStyle"), "%s", "SOLID"); + break; + case drawing::FillStyle_GRADIENT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillStyle"), "%s", "GRADIENT"); + break; + case drawing::FillStyle_HATCH: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillStyle"), "%s", "HATCH"); + break; + case drawing::FillStyle_BITMAP: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillStyle"), "%s", "BITMAP"); + break; + default: + break; + } +} + +void dumpFillColorAsAttribute(sal_Int32 aColor, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillColor"), "%06x", static_cast<unsigned int>(aColor)); +} + +void dumpFillTransparenceAsAttribute(sal_Int32 aTransparence, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillTransparence"), "%" SAL_PRIdINT32, aTransparence); +} + +void dumpFillTransparenceGradientNameAsAttribute(std::u16string_view sTranspGradName, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillTransparenceGradientName"), "%s", + OUStringToOString(sTranspGradName, RTL_TEXTENCODING_UTF8).getStr()); +} + +//because there's more awt::Gradient properties to dump +void dumpGradientProperty(const awt::Gradient& rGradient, xmlTextWriterPtr xmlWriter) +{ + switch (rGradient.Style) //enum GradientStyle + { + case awt::GradientStyle_LINEAR: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "LINEAR"); + break; + case awt::GradientStyle_AXIAL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "AXIAL"); + break; + case awt::GradientStyle_RADIAL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "RADIAL"); + break; + case awt::GradientStyle_ELLIPTICAL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "ELLIPTICAL"); + break; + case awt::GradientStyle_SQUARE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "SQUARE"); + break; + case awt::GradientStyle_RECT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "RECT"); + break; + default: + break; + } + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("startColor"), "%06x", static_cast<unsigned int>(rGradient.StartColor)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("endColor"), "%06x", static_cast<unsigned int>(rGradient.EndColor)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("angle"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.Angle)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("border"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.Border)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("xOffset"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.XOffset)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("yOffset"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.YOffset)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("startIntensity"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.StartIntensity)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("endIntensity"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.EndIntensity)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("stepCount"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rGradient.StepCount)); +} + +void dumpFillTransparenceGradientAsElement(const awt::Gradient& rTranspGrad, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "FillTransparenceGradient" )); + dumpGradientProperty(rTranspGrad, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpFillGradientNameAsAttribute(std::u16string_view sGradName, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillGradientName"), "%s", + OUStringToOString(sGradName, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpFillGradientAsElement(const awt::Gradient& rGradient, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "FillGradient" )); + dumpGradientProperty(rGradient, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpFillHatchAsElement(const drawing::Hatch& rHatch, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "FillHatch" )); + switch (rHatch.Style) + { + case drawing::HatchStyle_SINGLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "SINGLE"); + break; + case drawing::HatchStyle_DOUBLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "DOUBLE"); + break; + case drawing::HatchStyle_TRIPLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "TRIPLE"); + break; + default: + break; + } + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("color"), "%06x", static_cast<unsigned int>(rHatch.Color)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("distance"), "%" SAL_PRIdINT32, rHatch.Distance); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("angle"), "%" SAL_PRIdINT32, rHatch.Angle); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpFillBackgroundAsAttribute(bool bBackground, xmlTextWriterPtr xmlWriter) +{ + if(bBackground) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBackground"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBackground"), "%s", "false"); +} + +void dumpFillBitmapAsElement(const uno::Reference<awt::XBitmap>& xBitmap, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "FillBitmap" )); + if (xBitmap.is()) + { + awt::Size const aSize = xBitmap->getSize(); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("width"), "%" SAL_PRIdINT32, aSize.Width); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("height"), "%" SAL_PRIdINT32, aSize.Height); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpFillBitmapPositionOffsetXAsAttribute(sal_Int32 aBitmapPositionOffsetX, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillBitmapPositionOffsetX"), "%" SAL_PRIdINT32, aBitmapPositionOffsetX); +} + +void dumpFillBitmapPositionOffsetYAsAttribute(sal_Int32 aBitmapPositionOffsetY, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillBitmapPositionOffsetY"), "%" SAL_PRIdINT32, aBitmapPositionOffsetY); +} + +void dumpFillBitmapOffsetXAsAttribute(sal_Int32 aBitmapOffsetX, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillBitmapOffsetX"), "%" SAL_PRIdINT32, aBitmapOffsetX); +} + +void dumpFillBitmapOffsetYAsAttribute(sal_Int32 aBitmapOffsetY, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillBitmapOffsetY"), "%" SAL_PRIdINT32, aBitmapOffsetY); +} + +void dumpFillBitmapRectanglePointAsAttribute(drawing::RectanglePoint eBitmapRectanglePoint, xmlTextWriterPtr xmlWriter) +{ + switch(eBitmapRectanglePoint) + { + case drawing::RectanglePoint_LEFT_TOP: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "LEFT_TOP"); + break; + case drawing::RectanglePoint_MIDDLE_TOP: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "MIDDLE_TOP"); + break; + case drawing::RectanglePoint_RIGHT_TOP: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "RIGHT_TOP"); + break; + case drawing::RectanglePoint_LEFT_MIDDLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "LEFT_MIDDLE"); + break; + case drawing::RectanglePoint_MIDDLE_MIDDLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "MIDDLE_MIDDLE"); + break; + case drawing::RectanglePoint_RIGHT_MIDDLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "RIGHT_MIDDLE"); + break; + case drawing::RectanglePoint_LEFT_BOTTOM: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "LEFT_BOTTOM"); + break; + case drawing::RectanglePoint_MIDDLE_BOTTOM: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "MIDDLE_BOTTOM"); + break; + case drawing::RectanglePoint_RIGHT_BOTTOM: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapRectanglePoint"), "%s", "RIGHT_BOTTOM"); + break; + default: + break; + } +} + +void dumpFillBitmapLogicalSizeAsAttribute(bool bBitmapLogicalSize, xmlTextWriterPtr xmlWriter) +{ + if(bBitmapLogicalSize) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapLogicalSize"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapLogicalSize"), "%s", "false"); +} + +void dumpFillBitmapSizeXAsAttribute(sal_Int32 aBitmapSizeX, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillBitmapSizeX"), "%" SAL_PRIdINT32, aBitmapSizeX); +} + +void dumpFillBitmapSizeYAsAttribute(sal_Int32 aBitmapSizeY, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fillBitmapSizeY"), "%" SAL_PRIdINT32, aBitmapSizeY); +} + +void dumpFillBitmapModeAsAttribute(drawing::BitmapMode eBitmapMode, xmlTextWriterPtr xmlWriter) +{ + switch(eBitmapMode) + { + case drawing::BitmapMode_REPEAT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapMode"), "%s", "REPEAT"); + break; + case drawing::BitmapMode_STRETCH: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapMode"), "%s", "STRETCH"); + break; + case drawing::BitmapMode_NO_REPEAT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapMode"), "%s", "NO_REPEAT"); + break; + default: + break; + } +} + +void dumpFillBitmapStretchAsAttribute(bool bBitmapStretch, xmlTextWriterPtr xmlWriter) +{ + if(bBitmapStretch) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapStretch"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapStretch"), "%s", "false"); +} + +void dumpFillBitmapTileAsAttribute(bool bBitmapTile, xmlTextWriterPtr xmlWriter) +{ + if(bBitmapTile) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapTile"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fillBitmapTile"), "%s", "false"); +} + + +// ---------- LineProperties.idl ---------- + + +void dumpLineStyleAsAttribute(drawing::LineStyle eLineStyle, xmlTextWriterPtr xmlWriter) +{ + switch(eLineStyle) + { + case drawing::LineStyle_NONE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineStyle"), "%s", "NONE"); + break; + case drawing::LineStyle_SOLID: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineStyle"), "%s", "SOLID"); + break; + case drawing::LineStyle_DASH: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineStyle"), "%s", "DASH"); + break; + default: + break; + } +} + +void dumpLineDashAsElement(const drawing::LineDash& rLineDash, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "LineDash" )); + switch (rLineDash.Style) + { + case drawing::DashStyle_RECT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "RECT"); + break; + case drawing::DashStyle_ROUND: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "ROUND"); + break; + case drawing::DashStyle_RECTRELATIVE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "RECTRELATIVE"); + break; + case drawing::DashStyle_ROUNDRELATIVE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("style"), "%s", "ROUNDRELATIVE"); + break; + default: + break; + } + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("dots"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rLineDash.Dots)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("dotLen"), "%" SAL_PRIdINT32, rLineDash.DotLen); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("dashes"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(rLineDash.Dashes)); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("dashLen"), "%" SAL_PRIdINT32, rLineDash.DashLen); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("distance"), "%" SAL_PRIdINT32, rLineDash.Distance); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpLineDashNameAsAttribute(std::u16string_view sLineDashName, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineDashName"), "%s", + OUStringToOString(sLineDashName, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpLineColorAsAttribute(sal_Int32 aLineColor, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineColor"), "%06x", static_cast<unsigned int>(aLineColor)); +} + +void dumpLineTransparenceAsAttribute(sal_Int32 aLineTransparence, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineTransparence"), "%" SAL_PRIdINT32, aLineTransparence); +} + +void dumpLineWidthAsAttribute(sal_Int32 aLineWidth, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineWidth"), "%" SAL_PRIdINT32, aLineWidth); +} + +void dumpLineJointAsAttribute(drawing::LineJoint eLineJoint, xmlTextWriterPtr xmlWriter) +{ + switch(eLineJoint) + { + case drawing::LineJoint_NONE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineJoint"), "%s", "NONE"); + break; + case drawing::LineJoint_MIDDLE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineJoint"), "%s", "MIDDLE"); + break; + case drawing::LineJoint_BEVEL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineJoint"), "%s", "BEVEL"); + break; + case drawing::LineJoint_MITER: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineJoint"), "%s", "MITER"); + break; + case drawing::LineJoint_ROUND: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineJoint"), "%s", "ROUND"); + break; + default: + break; + } +} + +void dumpLineStartNameAsAttribute(std::u16string_view sLineStartName, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineStartName"), "%s", + OUStringToOString(sLineStartName, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpLineEndNameAsAttribute(std::u16string_view sLineEndName, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineEndName"), "%s", + OUStringToOString(sLineEndName, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpPolyPolygonBezierCoords(const drawing::PolyPolygonBezierCoords& rPolyPolygonBezierCoords, xmlTextWriterPtr xmlWriter) +{ + dumpPointSequenceSequence(rPolyPolygonBezierCoords.Coordinates, &rPolyPolygonBezierCoords.Flags, xmlWriter); +} + +void dumpLineStartAsElement(const drawing::PolyPolygonBezierCoords& rLineStart, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "LineStart" )); + dumpPolyPolygonBezierCoords(rLineStart, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpLineEndAsElement(const drawing::PolyPolygonBezierCoords& rLineEnd, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "LineEnd" )); + dumpPolyPolygonBezierCoords(rLineEnd, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpLineStartCenterAsAttribute(bool bLineStartCenter, xmlTextWriterPtr xmlWriter) +{ + if(bLineStartCenter) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineStartCenter"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineStartCenter"), "%s", "false"); +} + +void dumpLineStartWidthAsAttribute(sal_Int32 aLineStartWidth, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineStartWidth"), "%" SAL_PRIdINT32, aLineStartWidth); +} + +void dumpLineEndCenterAsAttribute(bool bLineEndCenter, xmlTextWriterPtr xmlWriter) +{ + if(bLineEndCenter) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineEndCenter"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("lineEndCenter"), "%s", "false"); +} + +void dumpLineEndWidthAsAttribute(sal_Int32 aLineEndWidth, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("lineEndWidth"), "%" SAL_PRIdINT32, aLineEndWidth); +} + + +// ---------- PolyPolygonDescriptor.idl ---------- + + +void dumpPolygonKindAsAttribute(drawing::PolygonKind ePolygonKind, xmlTextWriterPtr xmlWriter) +{ + switch(ePolygonKind) + { + case drawing::PolygonKind_LINE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "LINE"); + break; + case drawing::PolygonKind_POLY: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "POLY"); + break; + case drawing::PolygonKind_PLIN: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "PLIN"); + break; + case drawing::PolygonKind_PATHLINE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "PATHLINE"); + break; + case drawing::PolygonKind_PATHFILL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "PATHFILL"); + break; + case drawing::PolygonKind_FREELINE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "FREELINE"); + break; + case drawing::PolygonKind_FREEFILL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "FREEFILL"); + break; + case drawing::PolygonKind_PATHPOLY: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "PATHPOLY"); + break; + case drawing::PolygonKind_PATHPLIN: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonKind"), "%s", "PATHPLIN"); + break; + default: + break; + } +} + +void dumpPointSequenceSequence(const drawing::PointSequenceSequence& aPointSequenceSequence, const uno::Sequence<uno::Sequence< drawing::PolygonFlags > >* pFlags, xmlTextWriterPtr xmlWriter) +{ + // LibreOffice proudly presents - The Sequenception + uno::Sequence<uno::Sequence< awt::Point > > pointSequenceSequence = aPointSequenceSequence; + sal_Int32 nPointsSequence = pointSequenceSequence.getLength(); + + for (sal_Int32 i = 0; i < nPointsSequence; ++i) + { + uno::Sequence< awt::Point > pointSequence = pointSequenceSequence[i]; + sal_Int32 nPoints = pointSequence.getLength(); + + uno::Sequence< drawing::PolygonFlags> flagsSequence; + if(pFlags) + flagsSequence = (*pFlags)[i]; + + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "pointSequence" )); + + for(sal_Int32 j = 0; j < nPoints; ++j) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "point" )); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("positionX"), "%" SAL_PRIdINT32, pointSequence[j].X); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("positionY"), "%" SAL_PRIdINT32, pointSequence[j].Y); + + if(pFlags) + { + switch(flagsSequence[j]) + { + case drawing::PolygonFlags_NORMAL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonFlags"), "%s", "NORMAL"); + break; + case drawing::PolygonFlags_SMOOTH: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonFlags"), "%s", "SMOOTH"); + break; + case drawing::PolygonFlags_CONTROL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonFlags"), "%s", "CONTROL"); + break; + case drawing::PolygonFlags_SYMMETRIC: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("polygonFlags"), "%s", "SYMMETRIC"); + break; + default: + break; + } + } + + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); + } +} + +void dumpPolyPolygonAsElement(const drawing::PointSequenceSequence& rPolyPolygon, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "PolyPolygon" )); + dumpPointSequenceSequence(rPolyPolygon, nullptr, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpGeometryAsElement(const drawing::PointSequenceSequence& aGeometry, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Geometry" )); + dumpPointSequenceSequence(aGeometry, nullptr, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); +} + +// CharacterProperties.idl +void dumpCharHeightAsAttribute(float fHeight, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("fontHeight"), "%f", fHeight ); +} + +void dumpCharColorAsAttribute(sal_Int32 aColor, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("fontColor"), "%06x", static_cast<unsigned int>(aColor)); +} + + +// ---------- TextProperties.idl ---------- + + +void dumpIsNumberingAsAttribute(bool bIsNumbering, xmlTextWriterPtr xmlWriter) +{ + if(bIsNumbering) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("isNumbering"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("isNumbering"), "%s", "false"); +} + +void dumpTextAutoGrowHeightAsAttribute(bool bTextAutoGrowHeight, xmlTextWriterPtr xmlWriter) +{ + if(bTextAutoGrowHeight) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAutoGrowHeight"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAutoGrowHeight"), "%s", "false"); +} + +void dumpTextAutoGrowWidthAsAttribute(bool bTextAutoGrowWidth, xmlTextWriterPtr xmlWriter) +{ + if(bTextAutoGrowWidth) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAutoGrowWidth"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAutoGrowWidth"), "%s", "false"); +} + +void dumpTextContourFrameAsAttribute(bool bTextContourFrame, xmlTextWriterPtr xmlWriter) +{ + if(bTextContourFrame) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textContourFrame"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textContourFrame"), "%s", "false"); +} + +void dumpTextFitToSizeAsAttribute(drawing::TextFitToSizeType eTextFitToSize, xmlTextWriterPtr xmlWriter) +{ + switch(eTextFitToSize) + { + case drawing::TextFitToSizeType_NONE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textFitToSize"), "%s", "NONE"); + break; + case drawing::TextFitToSizeType_PROPORTIONAL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textFitToSize"), "%s", "PROPORTIONAL"); + break; + case drawing::TextFitToSizeType_ALLLINES: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textFitToSize"), "%s", "ALLLINES"); + break; + case drawing::TextFitToSizeType_AUTOFIT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textFitToSize"), "%s", "AUTOFIT"); + break; + default: + break; + } +} + +void dumpTextHorizontalAdjustAsAttribute(drawing::TextHorizontalAdjust eTextHorizontalAdjust, xmlTextWriterPtr xmlWriter) +{ + switch(eTextHorizontalAdjust) + { + case drawing::TextHorizontalAdjust_LEFT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textHorizontalAdjust"), "%s", "LEFT"); + break; + case drawing::TextHorizontalAdjust_CENTER: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textHorizontalAdjust"), "%s", "CENTER"); + break; + case drawing::TextHorizontalAdjust_RIGHT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textHorizontalAdjust"), "%s", "RIGHT"); + break; + case drawing::TextHorizontalAdjust_BLOCK: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textHorizontalAdjust"), "%s", "BLOCK"); + break; + default: + break; + } +} + +void dumpTextVerticalAdjustAsAttribute(drawing::TextVerticalAdjust eTextVerticalAdjust, xmlTextWriterPtr xmlWriter) +{ + switch(eTextVerticalAdjust) + { + case drawing::TextVerticalAdjust_TOP: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textVerticalAdjust"), "%s", "TOP"); + break; + case drawing::TextVerticalAdjust_CENTER: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textVerticalAdjust"), "%s", "CENTER"); + break; + case drawing::TextVerticalAdjust_BOTTOM: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textVerticalAdjust"), "%s", "BOTTOM"); + break; + case drawing::TextVerticalAdjust_BLOCK: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textVerticalAdjust"), "%s", "BLOCK"); + break; + default: + break; + } +} + +void dumpTextLeftDistanceAsAttribute(sal_Int32 aTextLeftDistance, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textLeftDistance"), "%" SAL_PRIdINT32, aTextLeftDistance); +} + +void dumpTextRightDistanceAsAttribute(sal_Int32 aTextRightDistance, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textRightDistance"), "%" SAL_PRIdINT32, aTextRightDistance); +} + +void dumpTextUpperDistanceAsAttribute(sal_Int32 aTextUpperDistance, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textUpperDistance"), "%" SAL_PRIdINT32, aTextUpperDistance); +} + +void dumpTextLowerDistanceAsAttribute(sal_Int32 aTextLowerDistance, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textLowerDistance"), "%" SAL_PRIdINT32, aTextLowerDistance); +} + +void dumpTextMaximumFrameHeightAsAttribute(sal_Int32 aTextMaximumFrameHeight, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textMaximumFrameHeight"), "%" SAL_PRIdINT32, aTextMaximumFrameHeight); +} + +void dumpTextMaximumFrameWidthAsAttribute(sal_Int32 aTextMaximumFrameWidth, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textMaximumFrameWidth"), "%" SAL_PRIdINT32, aTextMaximumFrameWidth); +} + +void dumpTextMinimumFrameHeightAsAttribute(sal_Int32 aTextMinimumFrameHeight, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textMinimumFrameHeight"), "%" SAL_PRIdINT32, aTextMinimumFrameHeight); +} + +void dumpTextMinimumFrameWidthAsAttribute(sal_Int32 aTextMinimumFrameWidth, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textMinimumFrameWidth"), "%" SAL_PRIdINT32, aTextMinimumFrameWidth); +} + +void dumpTextAnimationAmountAsAttribute(sal_Int32 aTextAnimationAmount, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textAnimationAmount"), "%" SAL_PRIdINT32, aTextAnimationAmount); +} + +void dumpTextAnimationCountAsAttribute(sal_Int32 aTextAnimationCount, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textAnimationCount"), "%" SAL_PRIdINT32, aTextAnimationCount); +} + +void dumpTextAnimationDelayAsAttribute(sal_Int32 aTextAnimationDelay, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("textAnimationDelay"), "%" SAL_PRIdINT32, aTextAnimationDelay); +} + +void dumpTextAnimationDirectionAsAttribute(drawing::TextAnimationDirection eTextAnimationDirection, xmlTextWriterPtr xmlWriter) +{ + switch(eTextAnimationDirection) + { + case drawing::TextAnimationDirection_LEFT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationDirection"), "%s", "LEFT"); + break; + case drawing::TextAnimationDirection_RIGHT: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationDirection"), "%s", "RIGHT"); + break; + case drawing::TextAnimationDirection_UP: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationDirection"), "%s", "UP"); + break; + case drawing::TextAnimationDirection_DOWN: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationDirection"), "%s", "DOWN"); + break; + default: + break; + } +} + +void dumpTextAnimationKindAsAttribute(drawing::TextAnimationKind eTextAnimationKind, xmlTextWriterPtr xmlWriter) +{ + switch(eTextAnimationKind) + { + case drawing::TextAnimationKind_NONE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationKind"), "%s", "NONE"); + break; + case drawing::TextAnimationKind_BLINK: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationKind"), "%s", "BLINK"); + break; + case drawing::TextAnimationKind_SCROLL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationKind"), "%s", "SCROLL"); + break; + case drawing::TextAnimationKind_ALTERNATE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationKind"), "%s", "ALTERNATE"); + break; + case drawing::TextAnimationKind_SLIDE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationKind"), "%s", "SLIDE"); + break; + default: + break; + } +} + +void dumpTextAnimationStartInsideAsAttribute(bool bTextAnimationStartInside, xmlTextWriterPtr xmlWriter) +{ + if(bTextAnimationStartInside) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationStartInside"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationStartInside"), "%s", "false"); +} + +void dumpTextAnimationStopInsideAsAttribute(bool bTextAnimationStopInside, xmlTextWriterPtr xmlWriter) +{ + if(bTextAnimationStopInside) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationStopInside"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textAnimationStopInside"), "%s", "false"); +} + +void dumpTextWritingModeAsAttribute(text::WritingMode eTextWritingMode, xmlTextWriterPtr xmlWriter) +{ + switch(eTextWritingMode) + { + case text::WritingMode_LR_TB: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textWritingMode"), "%s", "LR_TB"); + break; + case text::WritingMode_RL_TB: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textWritingMode"), "%s", "RL_TB"); + break; + case text::WritingMode_TB_RL: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("textWritingMode"), "%s", "TB_RL"); + break; + default: + break; + } +} + + +// ---------- ShadowProperties.idl ---------- + + +void dumpShadowAsAttribute(bool bShadow, xmlTextWriterPtr xmlWriter) +{ + if(bShadow) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shadow"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("shadow"), "%s", "false"); +} + +void dumpShadowColorAsAttribute(sal_Int32 aShadowColor, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("shadowColor"), "%06x", static_cast<unsigned int>(aShadowColor)); +} + +void dumpShadowTransparenceAsAttribute(sal_Int32 aShadowTransparence, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("shadowTransparence"), "%" SAL_PRIdINT32, aShadowTransparence); +} + +void dumpShadowXDistanceAsAttribute(sal_Int32 aShadowXDistance, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("shadowXDistance"), "%" SAL_PRIdINT32, aShadowXDistance); +} + +void dumpShadowYDistanceAsAttribute(sal_Int32 aShadowYDistance, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("shadowYDistance"), "%" SAL_PRIdINT32, aShadowYDistance); +} + + +// ---------- Shape.idl ---------- + + +void dumpZOrderAsAttribute(sal_Int32 aZOrder, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("zOrder"), "%" SAL_PRIdINT32, aZOrder); +} + +void dumpLayerIDAsAttribute(sal_Int32 aLayerID, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("layerID"), "%" SAL_PRIdINT32, aLayerID); +} + +void dumpLayerNameAsAttribute(std::u16string_view sLayerName, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("layerName"), "%s", + OUStringToOString(sLayerName, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpVisibleAsAttribute(bool bVisible, xmlTextWriterPtr xmlWriter) +{ + if(bVisible) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("visible"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("visible"), "%s", "false"); +} + +void dumpPrintableAsAttribute(bool bPrintable, xmlTextWriterPtr xmlWriter) +{ + if(bPrintable) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("printable"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("printable"), "%s", "false"); +} + +void dumpMoveProtectAsAttribute(bool bMoveProtect, xmlTextWriterPtr xmlWriter) +{ + if(bMoveProtect) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("moveProtect"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("moveProtect"), "%s", "false"); +} + +void dumpNameAsAttribute(std::u16string_view sName, xmlTextWriterPtr xmlWriter) +{ + if(!sName.empty() && !m_bNameDumped) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("name"), "%s", OUStringToOString(sName, RTL_TEXTENCODING_UTF8).getStr()); + m_bNameDumped = true; + } +} + +void dumpSizeProtectAsAttribute(bool bSizeProtect, xmlTextWriterPtr xmlWriter) +{ + if(bSizeProtect) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("sizeProtect"), "%s", "true"); + else + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("sizeProtect"), "%s", "false"); +} + +void dumpHomogenMatrixLine3(const drawing::HomogenMatrixLine3& rHomogenMatrixLine3, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("column1"), "%f", rHomogenMatrixLine3.Column1); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("column2"), "%f", rHomogenMatrixLine3.Column2); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("column3"), "%f", rHomogenMatrixLine3.Column3); +} + +void dumpTransformationAsElement(const drawing::HomogenMatrix3& rTransformation, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Transformation" )); + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Line1" )); + dumpHomogenMatrixLine3(rTransformation.Line1, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Line2" )); + dumpHomogenMatrixLine3(rTransformation.Line2, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "Line3" )); + dumpHomogenMatrixLine3(rTransformation.Line3, xmlWriter); + (void)xmlTextWriterEndElement( xmlWriter ); + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpNavigationOrderAsAttribute(sal_Int32 aNavigationOrder, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("navigationOrder"), "%" SAL_PRIdINT32, aNavigationOrder); +} + +void dumpHyperlinkAsAttribute(std::u16string_view sHyperlink, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("hyperlink"), "%s", + OUStringToOString(sHyperlink, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpInteropGrabBagAsElement(const uno::Sequence< beans::PropertyValue>& aInteropGrabBag, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "InteropGrabBag" )); + + for (const auto& item: aInteropGrabBag) + dumpPropertyValueAsElement(item, xmlWriter); + + (void)xmlTextWriterEndElement( xmlWriter ); +} + + +// ---------- XShape.idl ---------- + + +void dumpPositionAsAttribute(const awt::Point& rPoint, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("positionX"), "%" SAL_PRIdINT32, rPoint.X); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("positionY"), "%" SAL_PRIdINT32, rPoint.Y); +} + +void dumpSizeAsAttribute(const awt::Size& rSize, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("sizeX"), "%" SAL_PRIdINT32, rSize.Width); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("sizeY"), "%" SAL_PRIdINT32, rSize.Height); +} + +void dumpShapeDescriptorAsAttribute( const uno::Reference< drawing::XShapeDescriptor >& xDescr, xmlTextWriterPtr xmlWriter ) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("type"), "%s", + OUStringToOString(xDescr->getShapeType(), RTL_TEXTENCODING_UTF8).getStr()); +} + + +// ---------- CustomShape.idl ---------- + + +void dumpCustomShapeEngineAsAttribute(std::u16string_view sCustomShapeEngine, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("customShapeEngine"), "%s", + OUStringToOString(sCustomShapeEngine, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpCustomShapeDataAsAttribute( + std::u16string_view sCustomShapeData, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("customShapeData"), "%s", + OUStringToOString(sCustomShapeData, RTL_TEXTENCODING_UTF8).getStr()); +} + +void dumpPropertyValueAsElement(const beans::PropertyValue& rPropertyValue, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "PropertyValue" )); + + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("name"), "%s", + OUStringToOString(rPropertyValue.Name, RTL_TEXTENCODING_UTF8).getStr()); + + uno::Any aAny = rPropertyValue.Value; + if(OUString sValue; aAny >>= sValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", + OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr()); + } + else if(sal_Int32 nValue; aAny >>= nValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%" SAL_PRIdINT32, nValue); + } + else if(float fValue; aAny >>= fValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%f", fValue); + } + else if(bool bValue; aAny >>= bValue) + { + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("value"), "%s", (bValue? "true": "false")); + } + else if(awt::Rectangle aRectangleValue; + rPropertyValue.Name == "ViewBox" && (aAny >>= aRectangleValue)) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpViewBoxAsElement(aRectangleValue); + } + else if(uno::Sequence< drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentValues; + rPropertyValue.Name == "AdjustmentValues" && (aAny >>= aAdjustmentValues)) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpAdjustmentValuesAsElement(aAdjustmentValues); + } + else if(uno::Sequence< drawing::EnhancedCustomShapeParameterPair > aCoordinates; + rPropertyValue.Name == "Coordinates" && (aAny >>= aCoordinates)) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpCoordinatesAsElement(aCoordinates); + } + else if(uno::Sequence< drawing::EnhancedCustomShapeSegment > aSegments; + rPropertyValue.Name == "Segments" && (aAny >>= aSegments)) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpSegmentsAsElement(aSegments); + } + else if(uno::Sequence< beans::PropertyValue > aPropSeq; aAny >>= aPropSeq) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( OUStringToOString(rPropertyValue.Name, RTL_TEXTENCODING_UTF8).getStr() )); + + sal_Int32 i = 0, nCount = aPropSeq.getLength(); + for ( ; i < nCount; i++ ) + dumpPropertyValueAsElement(aPropSeq[ i ], xmlWriter); + + (void)xmlTextWriterEndElement(xmlWriter); + } + + // TODO: Add here dumping of XDocument for future OOX Smart-Art + // properties. + + // TODO more, if necessary + + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("handle"), "%" SAL_PRIdINT32, rPropertyValue.Handle); + + switch(rPropertyValue.State) + { + case beans::PropertyState_DIRECT_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "DIRECT_VALUE"); + break; + case beans::PropertyState_DEFAULT_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "DEFAULT_VALUE"); + break; + case beans::PropertyState_AMBIGUOUS_VALUE: + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("propertyState"), "%s", "AMBIGUOUS_VALUE"); + break; + default: + break; + } + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpCustomShapeGeometryAsElement(const uno::Sequence< beans::PropertyValue>& aCustomShapeGeometry, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "CustomShapeGeometry" )); + + sal_Int32 nLength = aCustomShapeGeometry.getLength(); + for (sal_Int32 i = 0; i < nLength; ++i) + dumpPropertyValueAsElement(aCustomShapeGeometry[i], xmlWriter); + + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpCustomShapeReplacementURLAsAttribute(std::u16string_view sCustomShapeReplacementURL, xmlTextWriterPtr xmlWriter) +{ + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST("customShapeReplacementURL"), "%s", + OUStringToOString(sCustomShapeReplacementURL, RTL_TEXTENCODING_UTF8).getStr()); +} + +// methods dumping whole services + +void dumpTextPropertiesService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + uno::Reference< beans::XPropertySetInfo> xInfo = xPropSet->getPropertySetInfo(); + if(xInfo->hasPropertyByName("CharHeight")) + { + uno::Any anotherAny = xPropSet->getPropertyValue("CharHeight"); + float fHeight; + if(anotherAny >>= fHeight) + dumpCharHeightAsAttribute(fHeight, xmlWriter); + } + if(xInfo->hasPropertyByName("CharColor")) + { + uno::Any anotherAny = xPropSet->getPropertyValue("CharColor"); + sal_Int32 aColor = sal_Int32(); + if(anotherAny >>= aColor) + dumpCharColorAsAttribute(aColor, xmlWriter); + } + // TODO - more properties from CharacterProperties.idl (similar to above) + + if(xInfo->hasPropertyByName("IsNumbering")) + { + uno::Any anotherAny = xPropSet->getPropertyValue("IsNumbering"); + bool bIsNumbering; + if(anotherAny >>= bIsNumbering) + dumpIsNumberingAsAttribute(bIsNumbering, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAutoGrowHeight"); + bool bTextAutoGrowHeight; + if(anotherAny >>= bTextAutoGrowHeight) + dumpTextAutoGrowHeightAsAttribute(bTextAutoGrowHeight, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAutoGrowWidth"); + bool bTextAutoGrowWidth; + if(anotherAny >>= bTextAutoGrowWidth) + dumpTextAutoGrowWidthAsAttribute(bTextAutoGrowWidth, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextContourFrame"); + bool bTextContourFrame; + if(anotherAny >>= bTextContourFrame) + dumpTextContourFrameAsAttribute(bTextContourFrame, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextFitToSize"); + drawing::TextFitToSizeType eTextFitToSize; + if(anotherAny >>= eTextFitToSize) + dumpTextFitToSizeAsAttribute(eTextFitToSize, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextHorizontalAdjust"); + drawing::TextHorizontalAdjust eTextHorizontalAdjust; + if(anotherAny >>= eTextHorizontalAdjust) + dumpTextHorizontalAdjustAsAttribute(eTextHorizontalAdjust, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextVerticalAdjust"); + drawing::TextVerticalAdjust eTextVerticalAdjust; + if(anotherAny >>= eTextVerticalAdjust) + dumpTextVerticalAdjustAsAttribute(eTextVerticalAdjust, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextLeftDistance"); + sal_Int32 aTextLeftDistance = sal_Int32(); + if(anotherAny >>= aTextLeftDistance) + dumpTextLeftDistanceAsAttribute(aTextLeftDistance, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextRightDistance"); + sal_Int32 aTextRightDistance = sal_Int32(); + if(anotherAny >>= aTextRightDistance) + dumpTextRightDistanceAsAttribute(aTextRightDistance, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextUpperDistance"); + sal_Int32 aTextUpperDistance = sal_Int32(); + if(anotherAny >>= aTextUpperDistance) + dumpTextUpperDistanceAsAttribute(aTextUpperDistance, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextLowerDistance"); + sal_Int32 aTextLowerDistance = sal_Int32(); + if(anotherAny >>= aTextLowerDistance) + dumpTextLowerDistanceAsAttribute(aTextLowerDistance, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextMaximumFrameHeight"); + sal_Int32 aTextMaximumFrameHeight = sal_Int32(); + if(anotherAny >>= aTextMaximumFrameHeight) + dumpTextMaximumFrameHeightAsAttribute(aTextMaximumFrameHeight, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextMaximumFrameWidth"); + sal_Int32 aTextMaximumFrameWidth = sal_Int32(); + if(anotherAny >>= aTextMaximumFrameWidth) + dumpTextMaximumFrameWidthAsAttribute(aTextMaximumFrameWidth, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextMinimumFrameHeight"); + sal_Int32 aTextMinimumFrameHeight = sal_Int32(); + if(anotherAny >>= aTextMinimumFrameHeight) + dumpTextMinimumFrameHeightAsAttribute(aTextMinimumFrameHeight, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextMinimumFrameWidth"); + sal_Int32 aTextMinimumFrameWidth = sal_Int32(); + if(anotherAny >>= aTextMinimumFrameWidth) + dumpTextMinimumFrameWidthAsAttribute(aTextMinimumFrameWidth, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationAmount"); + sal_Int32 aTextAnimationAmount = sal_Int32(); + if(anotherAny >>= aTextAnimationAmount) + dumpTextAnimationAmountAsAttribute(aTextAnimationAmount, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationCount"); + sal_Int32 aTextAnimationCount = sal_Int32(); + if(anotherAny >>= aTextAnimationCount) + dumpTextAnimationCountAsAttribute(aTextAnimationCount, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationDelay"); + sal_Int32 aTextAnimationDelay = sal_Int32(); + if(anotherAny >>= aTextAnimationDelay) + dumpTextAnimationDelayAsAttribute(aTextAnimationDelay, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationDirection"); + drawing::TextAnimationDirection eTextAnimationDirection; + if(anotherAny >>= eTextAnimationDirection) + dumpTextAnimationDirectionAsAttribute(eTextAnimationDirection, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationKind"); + drawing::TextAnimationKind eTextAnimationKind; + if(anotherAny >>= eTextAnimationKind) + dumpTextAnimationKindAsAttribute(eTextAnimationKind, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationStartInside"); + bool bTextAnimationStartInside; + if(anotherAny >>= bTextAnimationStartInside) + dumpTextAnimationStartInsideAsAttribute(bTextAnimationStartInside, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextAnimationStopInside"); + bool bTextAnimationStopInside; + if(anotherAny >>= bTextAnimationStopInside) + dumpTextAnimationStopInsideAsAttribute(bTextAnimationStopInside, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("TextWritingMode"); + text::WritingMode eTextWritingMode; + if(anotherAny >>= eTextWritingMode) + dumpTextWritingModeAsAttribute(eTextWritingMode, xmlWriter); + } +} + +void dumpFillPropertiesService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillStyle"); + drawing::FillStyle eFillStyle; + if(anotherAny >>= eFillStyle) + dumpFillStyleAsAttribute(eFillStyle, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillColor"); + sal_Int32 aColor = sal_Int32(); + if(anotherAny >>= aColor) + dumpFillColorAsAttribute(aColor, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillTransparence"); + sal_Int32 aTransparence = sal_Int32(); + if(anotherAny >>= aTransparence) + dumpFillTransparenceAsAttribute(aTransparence, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillTransparenceGradientName"); + OUString sTranspGradName; + if(anotherAny >>= sTranspGradName) + dumpFillTransparenceGradientNameAsAttribute(sTranspGradName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillTransparenceGradient"); + awt::Gradient aTranspGrad; + if(anotherAny >>= aTranspGrad) + dumpFillTransparenceGradientAsElement(aTranspGrad, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillGradientName"); + OUString sGradName; + if(anotherAny >>= sGradName) + dumpFillGradientNameAsAttribute(sGradName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillGradient"); + awt::Gradient aGradient; + if(anotherAny >>= aGradient) + dumpFillGradientAsElement(aGradient, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillHatchName"); + OUString sHatchName; + if(anotherAny >>= sHatchName) + dumpFillGradientNameAsAttribute(sHatchName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillHatch"); + drawing::Hatch aHatch; + if(anotherAny >>= aHatch) + dumpFillHatchAsElement(aHatch, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBackground"); + bool bFillBackground; + if(anotherAny >>= bFillBackground) + dumpFillBackgroundAsAttribute(bFillBackground, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapName"); + OUString sBitmapName; + if(anotherAny >>= sBitmapName) + dumpFillGradientNameAsAttribute(sBitmapName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmap"); + uno::Reference<awt::XBitmap> xBitmap; + if(anotherAny >>= xBitmap) + dumpFillBitmapAsElement(xBitmap, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapPositionOffsetX"); + sal_Int32 aBitmapPositionOffsetX = sal_Int32(); + if(anotherAny >>= aBitmapPositionOffsetX) + dumpFillBitmapPositionOffsetXAsAttribute(aBitmapPositionOffsetX, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapPositionOffsetY"); + sal_Int32 aBitmapPositionOffsetY = sal_Int32(); + if(anotherAny >>= aBitmapPositionOffsetY) + dumpFillBitmapPositionOffsetYAsAttribute(aBitmapPositionOffsetY, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapOffsetX"); + sal_Int32 aBitmapOffsetX = sal_Int32(); + if(anotherAny >>= aBitmapOffsetX) + dumpFillBitmapOffsetXAsAttribute(aBitmapOffsetX, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapOffsetY"); + sal_Int32 aBitmapOffsetY = sal_Int32(); + if(anotherAny >>= aBitmapOffsetY) + dumpFillBitmapOffsetYAsAttribute(aBitmapOffsetY, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapRectanglePoint"); + drawing::RectanglePoint eBitmapRectanglePoint; + if(anotherAny >>= eBitmapRectanglePoint) + dumpFillBitmapRectanglePointAsAttribute(eBitmapRectanglePoint, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapLogicalSize"); + bool bBitmapLogicalSize; + if(anotherAny >>= bBitmapLogicalSize) + dumpFillBitmapLogicalSizeAsAttribute(bBitmapLogicalSize, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapSizeX"); + sal_Int32 aBitmapSizeX = sal_Int32(); + if(anotherAny >>= aBitmapSizeX) + dumpFillBitmapSizeXAsAttribute(aBitmapSizeX, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapSizeY"); + sal_Int32 aBitmapSizeY = sal_Int32(); + if(anotherAny >>= aBitmapSizeY) + dumpFillBitmapSizeYAsAttribute(aBitmapSizeY, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapMode"); + drawing::BitmapMode eBitmapMode; + if(anotherAny >>= eBitmapMode) + dumpFillBitmapModeAsAttribute(eBitmapMode, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapStretch"); + bool bBitmapStretch; + if(anotherAny >>= bBitmapStretch) + dumpFillBitmapStretchAsAttribute(bBitmapStretch, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("FillBitmapTile"); + bool bBitmapTile; + if(anotherAny >>= bBitmapTile) + dumpFillBitmapTileAsAttribute(bBitmapTile, xmlWriter); + } +} + +void dumpLinePropertiesService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineStyle"); + drawing::LineStyle eLineStyle; + if(anotherAny >>= eLineStyle) + dumpLineStyleAsAttribute(eLineStyle, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineDash"); + drawing::LineDash aLineDash; + if(anotherAny >>= aLineDash) + dumpLineDashAsElement(aLineDash, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineDashName"); + OUString sLineDashName; + if(anotherAny >>= sLineDashName) + dumpLineDashNameAsAttribute(sLineDashName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineColor"); + sal_Int32 aLineColor = sal_Int32(); + if(anotherAny >>= aLineColor) + dumpLineColorAsAttribute(aLineColor, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineTransparence"); + sal_Int32 aLineTransparence = sal_Int32(); + if(anotherAny >>= aLineTransparence) + dumpLineTransparenceAsAttribute(aLineTransparence, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineWidth"); + sal_Int32 aLineWidth = sal_Int32(); + if(anotherAny >>= aLineWidth) + dumpLineWidthAsAttribute(aLineWidth, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineJoint"); + drawing::LineJoint eLineJoint; + if(anotherAny >>= eLineJoint) + dumpLineJointAsAttribute(eLineJoint, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineStartName"); + OUString sLineStartName; + if(anotherAny >>= sLineStartName) + dumpLineStartNameAsAttribute(sLineStartName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineEndName"); + OUString sLineEndName; + if(anotherAny >>= sLineEndName) + dumpLineEndNameAsAttribute(sLineEndName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineStart"); + drawing::PolyPolygonBezierCoords aLineStart; + if(anotherAny >>= aLineStart) + dumpLineStartAsElement(aLineStart, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineEnd"); + drawing::PolyPolygonBezierCoords aLineEnd; + if(anotherAny >>= aLineEnd) + dumpLineEndAsElement(aLineEnd, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineStartCenter"); + bool bLineStartCenter; + if(anotherAny >>= bLineStartCenter) + dumpLineStartCenterAsAttribute(bLineStartCenter, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineStartWidth"); + sal_Int32 aLineStartWidth = sal_Int32(); + if(anotherAny >>= aLineStartWidth) + dumpLineStartWidthAsAttribute(aLineStartWidth, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineEndCenter"); + bool bLineEndCenter; + if(anotherAny >>= bLineEndCenter) + dumpLineEndCenterAsAttribute(bLineEndCenter, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LineEndWidth"); + sal_Int32 aLineEndWidth = sal_Int32(); + if(anotherAny >>= aLineEndWidth) + dumpLineEndWidthAsAttribute(aLineEndWidth, xmlWriter); + } +} + +void dumpShadowPropertiesService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("Shadow"); + bool bShadow; + if(anotherAny >>= bShadow) + dumpShadowAsAttribute(bShadow, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ShadowColor"); + sal_Int32 aShadowColor = sal_Int32(); + if(anotherAny >>= aShadowColor) + dumpShadowColorAsAttribute(aShadowColor, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ShadowTransparence"); + sal_Int32 aShadowTransparence = sal_Int32(); + if(anotherAny >>= aShadowTransparence) + dumpShadowTransparenceAsAttribute(aShadowTransparence, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ShadowXDistance"); + sal_Int32 aShadowXDistance = sal_Int32(); + if(anotherAny >>= aShadowXDistance) + dumpShadowXDistanceAsAttribute(aShadowXDistance, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("ShadowYDistance"); + sal_Int32 aShadowYDistance = sal_Int32(); + if(anotherAny >>= aShadowYDistance) + dumpShadowYDistanceAsAttribute(aShadowYDistance, xmlWriter); + } +} + +void dumpPolyPolygonDescriptorService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("PolygonKind"); + drawing::PolygonKind ePolygonKind; + if(anotherAny >>= ePolygonKind) + dumpPolygonKindAsAttribute(ePolygonKind, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("PolyPolygon"); + drawing::PointSequenceSequence aPolyPolygon; + if(anotherAny >>= aPolyPolygon) + dumpPolyPolygonAsElement(aPolyPolygon, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Geometry"); + drawing::PointSequenceSequence aGeometry; + if(anotherAny >>= aGeometry) + dumpGeometryAsElement(aGeometry, xmlWriter); + } +} + +void dumpShapeService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter, bool bDumpInteropProperties) +{ + uno::Reference< beans::XPropertySetInfo> xInfo = xPropSet->getPropertySetInfo(); + { + uno::Any anotherAny = xPropSet->getPropertyValue("ZOrder"); + sal_Int32 aZOrder = sal_Int32(); + if(anotherAny >>= aZOrder) + dumpZOrderAsAttribute(aZOrder, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LayerID"); + sal_Int32 aLayerID = sal_Int32(); + if(anotherAny >>= aLayerID) + dumpLayerIDAsAttribute(aLayerID, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("LayerName"); + OUString sLayerName; + if(anotherAny >>= sLayerName) + dumpLayerNameAsAttribute(sLayerName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Visible"); + bool bVisible; + if(anotherAny >>= bVisible) + dumpVisibleAsAttribute(bVisible, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Printable"); + bool bPrintable; + if(anotherAny >>= bPrintable) + dumpPrintableAsAttribute(bPrintable, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("MoveProtect"); + bool bMoveProtect; + if(anotherAny >>= bMoveProtect) + dumpMoveProtectAsAttribute(bMoveProtect, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Name"); + OUString sName; + if(anotherAny >>= sName) + dumpNameAsAttribute(sName, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("SizeProtect"); + bool bSizeProtect; + if(anotherAny >>= bSizeProtect) + dumpSizeProtectAsAttribute(bSizeProtect, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Transformation"); + drawing::HomogenMatrix3 aTransformation; + if(anotherAny >>= aTransformation) + dumpTransformationAsElement(aTransformation, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("NavigationOrder"); + sal_Int32 aNavigationOrder = sal_Int32(); + if(anotherAny >>= aNavigationOrder) + dumpNavigationOrderAsAttribute(aNavigationOrder, xmlWriter); + } + if(xInfo->hasPropertyByName("Hyperlink")) + { + uno::Any anotherAny = xPropSet->getPropertyValue("Hyperlink"); + OUString sHyperlink; + if(anotherAny >>= sHyperlink) + dumpHyperlinkAsAttribute(sHyperlink, xmlWriter); + } + if(xInfo->hasPropertyByName("InteropGrabBag") && bDumpInteropProperties) + { + uno::Any anotherAny = xPropSet->getPropertyValue("InteropGrabBag"); + uno::Sequence< beans::PropertyValue> aInteropGrabBag; + if(anotherAny >>= aInteropGrabBag) + dumpInteropGrabBagAsElement(aInteropGrabBag, xmlWriter); + } +} + +void dumpPolyPolygonBezierDescriptorService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + { + uno::Any anotherAny = xPropSet->getPropertyValue("PolygonKind"); + drawing::PolygonKind ePolygonKind; + if(anotherAny >>= ePolygonKind) + dumpPolygonKindAsAttribute(ePolygonKind, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("PolyPolygonBezier"); + drawing::PolyPolygonBezierCoords aPolyPolygonBezier; + if(anotherAny >>= aPolyPolygonBezier) + dumpPolyPolygonBezierCoords(aPolyPolygonBezier, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("Geometry"); + drawing::PolyPolygonBezierCoords aGeometry; + if(anotherAny >>= aGeometry) + dumpPolyPolygonBezierCoords(aGeometry, xmlWriter); + } +} + +void dumpCustomShapeService(const uno::Reference< beans::XPropertySet >& xPropSet, xmlTextWriterPtr xmlWriter) +{ + uno::Reference< beans::XPropertySetInfo> xInfo = xPropSet->getPropertySetInfo(); + { + uno::Any anotherAny = xPropSet->getPropertyValue("CustomShapeEngine"); + OUString sCustomShapeEngine; + if(anotherAny >>= sCustomShapeEngine) + dumpCustomShapeEngineAsAttribute(sCustomShapeEngine, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("CustomShapeData"); + OUString sCustomShapeData; + if(anotherAny >>= sCustomShapeData) + dumpCustomShapeDataAsAttribute(sCustomShapeData, xmlWriter); + } + { + uno::Any anotherAny = xPropSet->getPropertyValue("CustomShapeGeometry"); + uno::Sequence< beans::PropertyValue> aCustomShapeGeometry; + if(anotherAny >>= aCustomShapeGeometry) + dumpCustomShapeGeometryAsElement(aCustomShapeGeometry, xmlWriter); + } + if(xInfo->hasPropertyByName("CustomShapeReplacementURL")) + { + uno::Any anotherAny = xPropSet->getPropertyValue("CustomShapeReplacementURL"); + OUString sCustomShapeReplacementURL; + if(anotherAny >>= sCustomShapeReplacementURL) + dumpCustomShapeReplacementURLAsAttribute(sCustomShapeReplacementURL, xmlWriter); + } +} + +void dumpXShape(const uno::Reference< drawing::XShape >& xShape, xmlTextWriterPtr xmlWriter, bool bDumpInteropProperties) +{ + (void)xmlTextWriterStartElement( xmlWriter, BAD_CAST( "XShape" ) ); + uno::Reference< beans::XPropertySet > xPropSet(xShape, uno::UNO_QUERY_THROW); + OUString aName; + m_bNameDumped = false; + + dumpPositionAsAttribute(xShape->getPosition(), xmlWriter); + dumpSizeAsAttribute(xShape->getSize(), xmlWriter); + uno::Reference< drawing::XShapeDescriptor > xDescr(xShape, uno::UNO_QUERY_THROW); + dumpShapeDescriptorAsAttribute(xDescr, xmlWriter); + + // uno::Sequence<beans::Property> aProperties = xPropSetInfo->getProperties(); + + uno::Reference< lang::XServiceInfo > xServiceInfo( xShape, uno::UNO_QUERY_THROW ); + + uno::Reference< beans::XPropertySetInfo> xInfo = xPropSet->getPropertySetInfo(); + if(xInfo->hasPropertyByName("Name")) + { + uno::Any aAny = xPropSet->getPropertyValue("Name"); + if ((aAny >>= aName) && !aName.isEmpty()) + { + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("name"), "%s", OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr()); + m_bNameDumped = true; + } + } + + try + { + if (xServiceInfo->supportsService("com.sun.star.drawing.Text")) + { + uno::Reference< text::XText > xText(xShape, uno::UNO_QUERY_THROW); + OUString aText = xText->getString(); + if(!aText.isEmpty()) + (void)xmlTextWriterWriteFormatAttribute( xmlWriter, BAD_CAST("text"), "%s", OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr()); + } + if(xServiceInfo->supportsService("com.sun.star.drawing.TextProperties")) + dumpTextPropertiesService(xPropSet, xmlWriter); + + if(xServiceInfo->supportsService("com.sun.star.drawing.GroupShape")) + { + uno::Reference< drawing::XShapes > xShapes(xShape, uno::UNO_QUERY_THROW); + dumpXShapes(xShapes, xmlWriter, bDumpInteropProperties); + } + if(xServiceInfo->supportsService("com.sun.star.drawing.FillProperties")) + dumpFillPropertiesService(xPropSet, xmlWriter); + + if(xServiceInfo->supportsService("com.sun.star.drawing.LineProperties")) + dumpLinePropertiesService(xPropSet, xmlWriter); + + if(xServiceInfo->supportsService("com.sun.star.drawing.PolyPolygonDescriptor")) + dumpPolyPolygonDescriptorService(xPropSet, xmlWriter); + + if(xServiceInfo->supportsService("com.sun.star.drawing.ShadowProperties")) + dumpShadowPropertiesService(xPropSet, xmlWriter); + + if(xServiceInfo->supportsService("com.sun.star.drawing.Shape")) + dumpShapeService(xPropSet, xmlWriter, bDumpInteropProperties); + + if(xServiceInfo->supportsService("com.sun.star.drawing.PolyPolygonBezierDescriptor")) + dumpPolyPolygonBezierDescriptorService(xPropSet, xmlWriter); + + if(xServiceInfo->supportsService("com.sun.star.drawing.CustomShape")) + dumpCustomShapeService(xPropSet, xmlWriter); + + // EnhancedShapeDumper used + + if(xServiceInfo->supportsService("com.sun.star.drawing.EnhancedCustomShapeExtrusion")) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpEnhancedCustomShapeExtrusionService(xPropSet); + } + if(xServiceInfo->supportsService("com.sun.star.drawing.EnhancedCustomShapeGeometry")) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpEnhancedCustomShapeGeometryService(xPropSet); + } + if(xServiceInfo->supportsService("com.sun.star.drawing.EnhancedCustomShapeHandle")) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpEnhancedCustomShapeHandleService(xPropSet); + } + if(xServiceInfo->supportsService("com.sun.star.drawing.EnhancedCustomShapePath")) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpEnhancedCustomShapePathService(xPropSet); + } + if(xServiceInfo->supportsService("com.sun.star.drawing.EnhancedCustomShapeTextPath")) + { + EnhancedShapeDumper enhancedDumper(xmlWriter); + enhancedDumper.dumpEnhancedCustomShapeTextPathService(xPropSet); + } + } // end of the 'try' block + catch (const beans::UnknownPropertyException& e) + { + std::cout << "Exception caught in XShapeDumper.cxx: " << e.Message << std::endl; + } + + #if DEBUG_DUMPER + uno::Sequence< OUString > aServiceNames = xServiceInfo->getSupportedServiceNames(); + sal_Int32 nServices = aServiceNames.getLength(); + for (sal_Int32 i = 0; i < nServices; ++i) + { + (void)xmlTextWriterStartElement(xmlWriter, BAD_CAST( "ServiceName" )); + (void)xmlTextWriterWriteFormatAttribute(xmlWriter, BAD_CAST( "name" ), "%s", OUStringToOString(aServiceNames[i], RTL_TEXTENCODING_UTF8).getStr()); + (void)xmlTextWriterEndElement( xmlWriter ); + } + #endif + + (void)xmlTextWriterEndElement( xmlWriter ); +} + +void dumpXShapes( const uno::Reference< drawing::XShapes >& xShapes, xmlTextWriterPtr xmlWriter, bool bDumpInteropProperties ) +{ + (void)xmlTextWriterStartElement( xmlWriter, BAD_CAST( "XShapes" ) ); + uno::Reference< container::XIndexAccess > xIA( xShapes, uno::UNO_QUERY_THROW); + sal_Int32 nLength = xIA->getCount(); + for (sal_Int32 i = 0; i < nLength; ++i) + { + uno::Reference< drawing::XShape > xShape( xIA->getByIndex( i ), uno::UNO_QUERY_THROW ); + dumpXShape( xShape, xmlWriter, bDumpInteropProperties ); + } + + (void)xmlTextWriterEndElement( xmlWriter ); +} +} //end of namespace + +OUString XShapeDumper::dump(const uno::Reference<drawing::XShapes>& xPageShapes, bool bDumpInteropProperties) +{ + OStringBuffer aString; + xmlOutputBufferPtr xmlOutBuffer = xmlOutputBufferCreateIO( writeCallback, closeCallback, &aString, nullptr ); + xmlTextWriterPtr xmlWriter = xmlNewTextWriter( xmlOutBuffer ); + xmlTextWriterSetIndent( xmlWriter, 1 ); + + (void)xmlTextWriterStartDocument( xmlWriter, nullptr, nullptr, nullptr ); + + try + { + dumpXShapes( xPageShapes, xmlWriter, bDumpInteropProperties ); + } + catch (const beans::UnknownPropertyException& e) + { + std::cout << "Exception caught in XShapeDumper: " << e.Message << std::endl; + } + + (void)xmlTextWriterEndDocument( xmlWriter ); + xmlFreeTextWriter( xmlWriter ); + + return OUString::fromUtf8(aString.makeStringAndClear()); +} + +OUString XShapeDumper::dump(const uno::Reference<drawing::XShape>& xPageShapes, bool bDumpInteropProperties) +{ + OStringBuffer aString; + xmlOutputBufferPtr xmlOutBuffer = xmlOutputBufferCreateIO( writeCallback, closeCallback, &aString, nullptr ); + xmlTextWriterPtr xmlWriter = xmlNewTextWriter( xmlOutBuffer ); + xmlTextWriterSetIndent( xmlWriter, 1 ); + + (void)xmlTextWriterStartDocument( xmlWriter, nullptr, nullptr, nullptr ); + + try + { + dumpXShape( xPageShapes, xmlWriter, bDumpInteropProperties ); + } + catch (const beans::UnknownPropertyException& e) + { + std::cout << "Exception caught in XShapeDumper: " << e.Message << std::endl; + } + + (void)xmlTextWriterEndDocument( xmlWriter ); + xmlFreeTextWriter( xmlWriter ); + + return OUString::fromUtf8(aString.makeStringAndClear()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/geometry/viewinformation2d.cxx b/drawinglayer/source/geometry/viewinformation2d.cxx new file mode 100644 index 000000000..bcd6f020f --- /dev/null +++ b/drawinglayer/source/geometry/viewinformation2d.cxx @@ -0,0 +1,295 @@ +/* -*- 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/geometry/viewinformation2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/geometry/RealRectangle2D.hpp> + +using namespace com::sun::star; + +namespace drawinglayer::geometry +{ +namespace +{ +constexpr OUStringLiteral g_PropertyName_ObjectTransformation = u"ObjectTransformation"; +constexpr OUStringLiteral g_PropertyName_ViewTransformation = u"ViewTransformation"; +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"; +} + +class ImpViewInformation2D +{ +private: + // ViewInformation2D implementation can change refcount, so we have only + // two memory regions for pairs of ViewInformation2D/ImpViewInformation2D + friend class ::drawinglayer::geometry::ViewInformation2D; + +protected: + // the object transformation + basegfx::B2DHomMatrix maObjectTransformation; + + // the view transformation + basegfx::B2DHomMatrix maViewTransformation; + + // the ObjectToView and it's inverse, both on demand from ObjectTransformation + // and ViewTransformation + basegfx::B2DHomMatrix maObjectToViewTransformation; + basegfx::B2DHomMatrix maInverseObjectToViewTransformation; + + // the visible range and the on-demand one in ViewCoordinates + basegfx::B2DRange maViewport; + basegfx::B2DRange maDiscreteViewport; + + // the DrawPage which is target of visualisation. This is needed e.g. for + // the view-dependent decomposition of PageNumber TextFields. + // This parameter is buffered here, but mainly resides in mxExtendedInformation, + // so it will be interpreted, but held there. It will also not be added + // to mxExtendedInformation in impFillViewInformationFromContent (it's there already) + uno::Reference<drawing::XDrawPage> mxVisualizedPage; + + // the point in time + double mfViewTime; + + 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) + { + } + + ImpViewInformation2D() + : mfViewTime(0.0) + , mbReducedDisplayQuality(false) + { + } + + const basegfx::B2DHomMatrix& getObjectTransformation() const { return maObjectTransformation; } + + const basegfx::B2DHomMatrix& getViewTransformation() const { return maViewTransformation; } + + const basegfx::B2DRange& getViewport() const { return maViewport; } + + const basegfx::B2DRange& getDiscreteViewport() const + { + if (maDiscreteViewport.isEmpty() && !maViewport.isEmpty()) + { + basegfx::B2DRange aDiscreteViewport(maViewport); + aDiscreteViewport.transform(getViewTransformation()); + const_cast<ImpViewInformation2D*>(this)->maDiscreteViewport = aDiscreteViewport; + } + + return maDiscreteViewport; + } + + const basegfx::B2DHomMatrix& getObjectToViewTransformation() const + { + if (maObjectToViewTransformation.isIdentity() + && (!maObjectTransformation.isIdentity() || !maViewTransformation.isIdentity())) + { + basegfx::B2DHomMatrix aObjectToView(maViewTransformation * maObjectTransformation); + const_cast<ImpViewInformation2D*>(this)->maObjectToViewTransformation = aObjectToView; + } + + return maObjectToViewTransformation; + } + + const basegfx::B2DHomMatrix& getInverseObjectToViewTransformation() const + { + if (maInverseObjectToViewTransformation.isIdentity() + && (!maObjectTransformation.isIdentity() || !maViewTransformation.isIdentity())) + { + basegfx::B2DHomMatrix aInverseObjectToView(maViewTransformation + * maObjectTransformation); + aInverseObjectToView.invert(); + const_cast<ImpViewInformation2D*>(this)->maInverseObjectToViewTransformation + = aInverseObjectToView; + } + + return maInverseObjectToViewTransformation; + } + + double getViewTime() const { return mfViewTime; } + + const uno::Reference<drawing::XDrawPage>& getVisualizedPage() const { return mxVisualizedPage; } + + bool getReducedDisplayQuality() const { return mbReducedDisplayQuality; } + + bool operator==(const ImpViewInformation2D& rCandidate) const + { + return (maObjectTransformation == rCandidate.maObjectTransformation + && maViewTransformation == rCandidate.maViewTransformation + && maViewport == rCandidate.maViewport + && mxVisualizedPage == rCandidate.mxVisualizedPage + && mfViewTime == rCandidate.mfViewTime); + } +}; + +namespace +{ +ViewInformation2D::ImplType& theGlobalDefault() +{ + static ViewInformation2D::ImplType SINGLETON; + return SINGLETON; +} +} + +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()) +{ +} + +ViewInformation2D::ViewInformation2D(const ViewInformation2D&) = default; + +ViewInformation2D::ViewInformation2D(ViewInformation2D&&) = default; + +ViewInformation2D::~ViewInformation2D() = default; + +ViewInformation2D& ViewInformation2D::operator=(const ViewInformation2D&) = default; + +ViewInformation2D& ViewInformation2D::operator=(ViewInformation2D&&) = default; + +bool ViewInformation2D::operator==(const ViewInformation2D& rCandidate) const +{ + return rCandidate.mpViewInformation2D == mpViewInformation2D; +} + +const basegfx::B2DHomMatrix& ViewInformation2D::getObjectTransformation() const +{ + return mpViewInformation2D->getObjectTransformation(); +} + +const basegfx::B2DHomMatrix& ViewInformation2D::getViewTransformation() const +{ + return mpViewInformation2D->getViewTransformation(); +} + +const basegfx::B2DRange& ViewInformation2D::getViewport() const +{ + return mpViewInformation2D->getViewport(); +} + +double ViewInformation2D::getViewTime() const { return mpViewInformation2D->getViewTime(); } + +const uno::Reference<drawing::XDrawPage>& ViewInformation2D::getVisualizedPage() const +{ + return mpViewInformation2D->getVisualizedPage(); +} + +const basegfx::B2DHomMatrix& ViewInformation2D::getObjectToViewTransformation() const +{ + return mpViewInformation2D->getObjectToViewTransformation(); +} + +const basegfx::B2DHomMatrix& ViewInformation2D::getInverseObjectToViewTransformation() const +{ + return mpViewInformation2D->getInverseObjectToViewTransformation(); +} + +const basegfx::B2DRange& ViewInformation2D::getDiscreteViewport() const +{ + return mpViewInformation2D->getDiscreteViewport(); +} + +bool ViewInformation2D::getReducedDisplayQuality() const +{ + return mpViewInformation2D->getReducedDisplayQuality(); +} + +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; + + for (auto const& rPropertyValue : rViewParameters) + { + if (rPropertyValue.Name == g_PropertyName_ReducedDisplayQuality) + { + rPropertyValue.Value >>= bReducedDisplayQuality; + } + else if (rPropertyValue.Name == g_PropertyName_ObjectTransformation) + { + css::geometry::AffineMatrix2D aAffineMatrix2D; + rPropertyValue.Value >>= aAffineMatrix2D; + basegfx::unotools::homMatrixFromAffineMatrix(aObjectTransformation, aAffineMatrix2D); + } + else if (rPropertyValue.Name == g_PropertyName_ViewTransformation) + { + css::geometry::AffineMatrix2D aAffineMatrix2D; + rPropertyValue.Value >>= aAffineMatrix2D; + basegfx::unotools::homMatrixFromAffineMatrix(aViewTransformation, aAffineMatrix2D); + } + else if (rPropertyValue.Name == g_PropertyName_Viewport) + { + css::geometry::RealRectangle2D aUnoViewport; + rPropertyValue.Value >>= aUnoViewport; + aViewport = basegfx::unotools::b2DRectangleFromRealRectangle2D(aUnoViewport); + } + else if (rPropertyValue.Name == g_PropertyName_Time) + { + rPropertyValue.Value >>= fViewTime; + } + else if (rPropertyValue.Name == g_PropertyName_VisualizedPage) + { + rPropertyValue.Value >>= xVisualizedPage; + } + } + + return ViewInformation2D(aObjectTransformation, aViewTransformation, aViewport, xVisualizedPage, + fViewTime, bReducedDisplayQuality); +} + +} // end of namespace drawinglayer::geometry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/geometry/viewinformation3d.cxx b/drawinglayer/source/geometry/viewinformation3d.cxx new file mode 100644 index 000000000..a2a444ab3 --- /dev/null +++ b/drawinglayer/source/geometry/viewinformation3d.cxx @@ -0,0 +1,366 @@ +/* -*- 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/geometry/viewinformation3d.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/geometry/AffineMatrix3D.hpp> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/uno/Sequence.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::geometry +{ + /** Implementation class for ViewInformation3D + */ + class ImpViewInformation3D + { + private: + // ViewInformation3D implementation can change refcount, so we have only + // two memory regions for pairs of ViewInformation3D/ImpViewInformation3D + friend class ::drawinglayer::geometry::ViewInformation3D; + + // the 3D transformations + // Object to World. This may change and being adapted when entering 3D transformation + // groups + basegfx::B3DHomMatrix maObjectTransformation; + + // World to Camera. This includes VRP, VPN and VUV camera coordinate system + basegfx::B3DHomMatrix maOrientation; + + // Camera to Device with X,Y and Z [-1.0 .. 1.0]. This is the + // 3D to 2D projection which may be parallel or perspective. When it is perspective, + // the last line of the homogen matrix will NOT be unused + basegfx::B3DHomMatrix maProjection; + + // Device to View with X,Y and Z [0.0 .. 1.0]. This converts from -1 to 1 coordinates + // in camera coordinate system to 0 to 1 in unit 2D coordinates. This way it stays + // view-independent. To get discrete coordinates, the 2D transformation of a scene + // as 2D object needs to be involved + basegfx::B3DHomMatrix maDeviceToView; + + // Object to View is the linear combination of all four transformations. It's + // buffered to avoid too much matrix multiplying and created on demand + basegfx::B3DHomMatrix maObjectToView; + + // the point in time + double mfViewTime; + + // the extra PropertyValues; does not contain the transformations + 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"; + } + + // a central PropertyValue parsing method to allow transportation of + // all ViewParameters using UNO API + void impInterpretPropertyValues(const uno::Sequence< beans::PropertyValue >& rViewParameters) + { + if(!rViewParameters.hasElements()) + return; + + const sal_Int32 nCount(rViewParameters.getLength()); + sal_Int32 nExtendedInsert(0); + + // prepare extended information for filtering. Maximum size is nCount + mxExtendedInformation.realloc(nCount); + auto pExtendedInformation = mxExtendedInformation.getArray(); + + for(sal_Int32 a(0); a < nCount; a++) + { + const beans::PropertyValue& rProp = rViewParameters[a]; + + if(rProp.Name == getNamePropertyObjectTransformation()) + { + css::geometry::AffineMatrix3D aAffineMatrix3D; + rProp.Value >>= aAffineMatrix3D; + maObjectTransformation = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); + } + else if(rProp.Name == getNamePropertyOrientation()) + { + css::geometry::AffineMatrix3D aAffineMatrix3D; + rProp.Value >>= aAffineMatrix3D; + maOrientation = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); + } + else if(rProp.Name == getNamePropertyProjection()) + { + // 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, + // these four values need to be treated extra + const double f_30(maProjection.get(3, 0)); + const double f_31(maProjection.get(3, 1)); + const double f_32(maProjection.get(3, 2)); + const double f_33(maProjection.get(3, 3)); + + css::geometry::AffineMatrix3D aAffineMatrix3D; + rProp.Value >>= aAffineMatrix3D; + maProjection = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); + + maProjection.set(3, 0, f_30); + maProjection.set(3, 1, f_31); + maProjection.set(3, 2, f_32); + maProjection.set(3, 3, f_33); + } + else if(rProp.Name == getNamePropertyProjection_30()) + { + double f_30(0.0); + rProp.Value >>= f_30; + maProjection.set(3, 0, f_30); + } + else if(rProp.Name == getNamePropertyProjection_31()) + { + double f_31(0.0); + rProp.Value >>= f_31; + maProjection.set(3, 1, f_31); + } + else if(rProp.Name == getNamePropertyProjection_32()) + { + double f_32(0.0); + rProp.Value >>= f_32; + maProjection.set(3, 2, f_32); + } + else if(rProp.Name == getNamePropertyProjection_33()) + { + double f_33(1.0); + rProp.Value >>= f_33; + maProjection.set(3, 3, f_33); + } + else if(rProp.Name == getNamePropertyDeviceToView()) + { + css::geometry::AffineMatrix3D aAffineMatrix3D; + rProp.Value >>= aAffineMatrix3D; + maDeviceToView = basegfx::unotools::homMatrixFromAffineMatrix3D(aAffineMatrix3D); + } + else if(rProp.Name == getNamePropertyTime()) + { + rProp.Value >>= mfViewTime; + } + else + { + // extra information; add to filtered information + pExtendedInformation[nExtendedInsert++] = rProp; + } + } + + // extra information size is now known; realloc to final size + mxExtendedInformation.realloc(nExtendedInsert); + } + + public: + ImpViewInformation3D( + const basegfx::B3DHomMatrix& rObjectTransformation, + const basegfx::B3DHomMatrix& rOrientation, + const basegfx::B3DHomMatrix& rProjection, + const basegfx::B3DHomMatrix& rDeviceToView, + double fViewTime, + const uno::Sequence< beans::PropertyValue >& rExtendedParameters) + : maObjectTransformation(rObjectTransformation), + maOrientation(rOrientation), + maProjection(rProjection), + maDeviceToView(rDeviceToView), + mfViewTime(fViewTime) + { + impInterpretPropertyValues(rExtendedParameters); + } + + explicit ImpViewInformation3D(const uno::Sequence< beans::PropertyValue >& rViewParameters) + : mfViewTime() + { + impInterpretPropertyValues(rViewParameters); + } + + ImpViewInformation3D() + : mfViewTime() + { + } + + const basegfx::B3DHomMatrix& getObjectTransformation() const { return maObjectTransformation; } + const basegfx::B3DHomMatrix& getOrientation() const { return maOrientation; } + const basegfx::B3DHomMatrix& getProjection() const { return maProjection; } + const basegfx::B3DHomMatrix& getDeviceToView() const { return maDeviceToView; } + double getViewTime() const { return mfViewTime; } + + const basegfx::B3DHomMatrix& getObjectToView() const + { + // on demand WorldToView creation + + if(maObjectToView.isIdentity()) + { + const_cast< ImpViewInformation3D* >(this)->maObjectToView = maDeviceToView * maProjection * maOrientation * maObjectTransformation; + } + + return maObjectToView; + } + + const uno::Sequence< beans::PropertyValue >& getExtendedInformationSequence() const + { + return mxExtendedInformation; + } + + bool operator==(const ImpViewInformation3D& rCandidate) const + { + return (maObjectTransformation == rCandidate.maObjectTransformation + && maOrientation == rCandidate.maOrientation + && maProjection == rCandidate.maProjection + && maDeviceToView == rCandidate.maDeviceToView + && mfViewTime == rCandidate.mfViewTime + && mxExtendedInformation == rCandidate.mxExtendedInformation); + } + }; +} // end of namespace drawinglayer::geometry + + +namespace drawinglayer::geometry +{ + namespace + { + ViewInformation3D::ImplType& theGlobalDefault() + { + static ViewInformation3D::ImplType SINGLETON; + return SINGLETON; + } + } + + ViewInformation3D::ViewInformation3D( + const basegfx::B3DHomMatrix& rObjectObjectTransformation, + const basegfx::B3DHomMatrix& rOrientation, + const basegfx::B3DHomMatrix& rProjection, + const basegfx::B3DHomMatrix& rDeviceToView, + double fViewTime, + const uno::Sequence< beans::PropertyValue >& rExtendedParameters) + : mpViewInformation3D(ImpViewInformation3D( + rObjectObjectTransformation, rOrientation, rProjection, + rDeviceToView, fViewTime, rExtendedParameters)) + { + } + + ViewInformation3D::ViewInformation3D(const uno::Sequence< beans::PropertyValue >& rViewParameters) + : mpViewInformation3D(ImpViewInformation3D(rViewParameters)) + { + } + + ViewInformation3D::ViewInformation3D() + : mpViewInformation3D(theGlobalDefault()) + { + } + + ViewInformation3D::ViewInformation3D(const ViewInformation3D&) = default; + + ViewInformation3D::ViewInformation3D(ViewInformation3D&&) = default; + + ViewInformation3D::~ViewInformation3D() = default; + + bool ViewInformation3D::isDefault() const + { + return mpViewInformation3D.same_object(theGlobalDefault()); + } + + ViewInformation3D& ViewInformation3D::operator=(const ViewInformation3D&) = default; + + ViewInformation3D& ViewInformation3D::operator=(ViewInformation3D&&) = default; + + bool ViewInformation3D::operator==(const ViewInformation3D& rCandidate) const + { + return rCandidate.mpViewInformation3D == mpViewInformation3D; + } + + const basegfx::B3DHomMatrix& ViewInformation3D::getObjectTransformation() const + { + return mpViewInformation3D->getObjectTransformation(); + } + + const basegfx::B3DHomMatrix& ViewInformation3D::getOrientation() const + { + return mpViewInformation3D->getOrientation(); + } + + const basegfx::B3DHomMatrix& ViewInformation3D::getProjection() const + { + return mpViewInformation3D->getProjection(); + } + + const basegfx::B3DHomMatrix& ViewInformation3D::getDeviceToView() const + { + return mpViewInformation3D->getDeviceToView(); + } + + const basegfx::B3DHomMatrix& ViewInformation3D::getObjectToView() const + { + return mpViewInformation3D->getObjectToView(); + } + + double ViewInformation3D::getViewTime() const + { + return mpViewInformation3D->getViewTime(); + } + + const uno::Sequence< beans::PropertyValue >& ViewInformation3D::getExtendedInformationSequence() const + { + return mpViewInformation3D->getExtendedInformationSequence(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx new file mode 100644 index 000000000..76fa1a976 --- /dev/null +++ b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx @@ -0,0 +1,46 @@ +/* -*- 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/BufferedDecompositionPrimitive2D.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + +namespace drawinglayer::primitive2d +{ +BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D() {} + +void BufferedDecompositionPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + if (getBuffered2DDecomposition().empty()) + { + Primitive2DContainer aNewSequence; + create2DDecomposition(aNewSequence, rViewInformation); + const_cast<BufferedDecompositionPrimitive2D*>(this)->setBuffered2DDecomposition( + std::move(aNewSequence)); + } + + rVisitor.visit(getBuffered2DDecomposition()); +} + +} // 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 new file mode 100644 index 000000000..b3aed429b --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx @@ -0,0 +1,65 @@ +/* -*- 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/PolyPolygonColorPrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +PolyPolygonColorPrimitive2D::PolyPolygonColorPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBColor) + : maPolyPolygon(rPolyPolygon) + , maBColor(rBColor) +{ +} + +bool PolyPolygonColorPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonColorPrimitive2D& rCompare + = static_cast<const PolyPolygonColorPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonColorPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // return range + return basegfx::utils::getRange(getB2DPolyPolygon()); +} + +// provide unique ID +sal_uInt32 PolyPolygonColorPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D; +} + +} // 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 new file mode 100644 index 000000000..3e88b6c0d --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.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/primitive2d/PolyPolygonGradientPrimitive2D.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <rtl/ref.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonGradientPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getFillGradient().isDefault()) + { + // create SubSequence with FillGradientPrimitive2D + const basegfx::B2DRange aPolyPolygonRange(getB2DPolyPolygon().getB2DRange()); + rtl::Reference<FillGradientPrimitive2D> pNewGradient = new FillGradientPrimitive2D( + aPolyPolygonRange, getDefinitionRange(), getFillGradient()); + Primitive2DContainer aSubSequence{ pNewGradient }; + + // create mask primitive + rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), std::move(aSubSequence))); + } +} + +PolyPolygonGradientPrimitive2D::PolyPolygonGradientPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, + const attribute::FillGradientAttribute& rFillGradient) + : maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rPolyPolygon.getB2DRange()) + , maFillGradient(rFillGradient) +{ +} + +PolyPolygonGradientPrimitive2D::PolyPolygonGradientPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + const attribute::FillGradientAttribute& rFillGradient) + : maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rDefinitionRange) + , maFillGradient(rFillGradient) +{ +} + +bool PolyPolygonGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonGradientPrimitive2D& rCompare + = static_cast<const PolyPolygonGradientPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillGradient() == rCompare.getFillGradient()); + } + + return false; +} + +// provide unique ID +sal_uInt32 PolyPolygonGradientPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx new file mode 100644 index 000000000..a65d1d08a --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx @@ -0,0 +1,130 @@ +/* -*- 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/PolyPolygonGraphicPrimitive2D.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <vcl/graph.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonGraphicPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (getFillGraphic().isDefault()) + return; + + 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; + + const Size aPrefSize(rGraphic.GetPrefSize()); + + // does content have a size? + if (!(aPrefSize.Width() && aPrefSize.Height())) + return; + + // create SubSequence with FillGraphicPrimitive2D based on polygon range + const basegfx::B2DRange aOutRange(getB2DPolyPolygon().getB2DRange()); + const basegfx::B2DHomMatrix aNewObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix(aOutRange.getRange(), + aOutRange.getMinimum())); + Primitive2DReference xSubRef; + + if (aOutRange != getDefinitionRange()) + { + // we want to paint (tiled) content which is defined relative to DefinitionRange + // with the same tiling and offset(s) in the target range of the geometry (the + // polygon). The range given in the local FillGraphicAttribute defines the position + // of the graphic in unit coordinates relative to the DefinitionRange. Transform + // this using DefinitionRange to get to the global definition and then with the + // inverse transformation from the target range to go to unit coordinates relative + // to that target coordinate system. + basegfx::B2DRange aAdaptedRange(getFillGraphic().getGraphicRange()); + + const basegfx::B2DHomMatrix aFromDefinitionRangeToGlobal( + basegfx::utils::createScaleTranslateB2DHomMatrix(getDefinitionRange().getRange(), + getDefinitionRange().getMinimum())); + + aAdaptedRange.transform(aFromDefinitionRangeToGlobal); + + basegfx::B2DHomMatrix aFromGlobalToOutRange( + basegfx::utils::createScaleTranslateB2DHomMatrix(aOutRange.getRange(), + aOutRange.getMinimum())); + aFromGlobalToOutRange.invert(); + + aAdaptedRange.transform(aFromGlobalToOutRange); + + const drawinglayer::attribute::FillGraphicAttribute aAdaptedFillGraphicAttribute( + getFillGraphic().getGraphic(), aAdaptedRange, getFillGraphic().getTiling(), + getFillGraphic().getOffsetX(), getFillGraphic().getOffsetY()); + + xSubRef = new FillGraphicPrimitive2D(aNewObjectTransform, aAdaptedFillGraphicAttribute); + } + else + { + xSubRef = new FillGraphicPrimitive2D(aNewObjectTransform, getFillGraphic()); + } + + // embed to mask primitive + rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), Primitive2DContainer{ xSubRef })); +} + +PolyPolygonGraphicPrimitive2D::PolyPolygonGraphicPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + const attribute::FillGraphicAttribute& rFillGraphic) + : maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rDefinitionRange) + , maFillGraphic(rFillGraphic) +{ +} + +bool PolyPolygonGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonGraphicPrimitive2D& rCompare + = static_cast<const PolyPolygonGraphicPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillGraphic() == rCompare.getFillGraphic()); + } + + return false; +} + +// provide unique ID +sal_uInt32 PolyPolygonGraphicPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx new file mode 100644 index 000000000..2bfed5fb1 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx @@ -0,0 +1,82 @@ +/* -*- 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/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonHairlinePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if (nCount) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + rContainer.push_back( + new PolygonHairlinePrimitive2D(aPolyPolygon.getB2DPolygon(a), getBColor())); + } + } +} + +PolyPolygonHairlinePrimitive2D::PolyPolygonHairlinePrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBColor) + : maPolyPolygon(rPolyPolygon) + , maBColor(rBColor) +{ +} + +bool PolyPolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonHairlinePrimitive2D& rCompare + = static_cast<const PolyPolygonHairlinePrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonHairlinePrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // return range + return basegfx::utils::getRange(getB2DPolyPolygon()); +} + +// provide unique ID +sal_uInt32 PolyPolygonHairlinePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx new file mode 100644 index 000000000..7fc0502c8 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx @@ -0,0 +1,91 @@ +/* -*- 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/PolyPolygonHatchPrimitive2D.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> +#include <rtl/ref.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonHatchPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getFillHatch().isDefault()) + { + // create SubSequence with FillHatchPrimitive2D + const basegfx::B2DRange aPolyPolygonRange(getB2DPolyPolygon().getB2DRange()); + rtl::Reference<FillHatchPrimitive2D> pNewHatch = new FillHatchPrimitive2D( + aPolyPolygonRange, getDefinitionRange(), getBackgroundColor(), getFillHatch()); + Primitive2DContainer aSubSequence{ pNewHatch }; + + // create mask primitive + rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), std::move(aSubSequence))); + } +} + +PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBackgroundColor, + const attribute::FillHatchAttribute& rFillHatch) + : maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rPolyPolygon.getB2DRange()) + , maBackgroundColor(rBackgroundColor) + , maFillHatch(rFillHatch) +{ +} + +PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rBackgroundColor, const attribute::FillHatchAttribute& rFillHatch) + : maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rDefinitionRange) + , maBackgroundColor(rBackgroundColor) + , maFillHatch(rFillHatch) +{ +} + +bool PolyPolygonHatchPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonHatchPrimitive2D& rCompare + = static_cast<const PolyPolygonHatchPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getBackgroundColor() == rCompare.getBackgroundColor() + && getFillHatch() == rCompare.getFillHatch()); + } + + return false; +} + +// provide unique ID +sal_uInt32 PolyPolygonHatchPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx new file mode 100644 index 000000000..4f9f83298 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.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/primitive2d/PolyPolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonMarkerPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if (nCount) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + rContainer.push_back(new PolygonMarkerPrimitive2D(aPolyPolygon.getB2DPolygon(a), + getRGBColorA(), getRGBColorB(), + getDiscreteDashLength())); + } + } +} + +PolyPolygonMarkerPrimitive2D::PolyPolygonMarkerPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rRGBColorA, + const basegfx::BColor& rRGBColorB, double fDiscreteDashLength) + : maPolyPolygon(rPolyPolygon) + , maRGBColorA(rRGBColorA) + , maRGBColorB(rRGBColorB) + , mfDiscreteDashLength(fDiscreteDashLength) +{ +} + +bool PolyPolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonMarkerPrimitive2D& rCompare + = static_cast<const PolyPolygonMarkerPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getRGBColorA() == rCompare.getRGBColorA() + && getRGBColorB() == rCompare.getRGBColorB() + && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonMarkerPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // return range + return basegfx::utils::getRange(getB2DPolyPolygon()); +} + +// provide unique ID +sal_uInt32 PolyPolygonMarkerPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONMARKERPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx new file mode 100644 index 000000000..d5ed17980 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx @@ -0,0 +1,125 @@ +/* -*- 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/PolyPolygonSelectionPrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonSelectionPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (getTransparence() >= 1.0 || !getB2DPolyPolygon().count()) + return; + + Primitive2DContainer aRetval; + + if (getFill() && getB2DPolyPolygon().isClosed()) + { + // create fill primitive + const Primitive2DReference aFill( + new PolyPolygonColorPrimitive2D(getB2DPolyPolygon(), getColor())); + + aRetval = Primitive2DContainer{ aFill }; + } + + if (getDiscreteGrow() > 0.0) + { + const attribute::LineAttribute aLineAttribute(getColor(), + getDiscreteGrow() * getDiscreteUnit() * 2.0); + const Primitive2DReference aFatLine( + new PolyPolygonStrokePrimitive2D(getB2DPolyPolygon(), aLineAttribute)); + + aRetval.push_back(aFatLine); + } + + // embed filled to transparency (if used) + if (!aRetval.empty() && getTransparence() > 0.0) + { + const Primitive2DReference aTrans( + new UnifiedTransparencePrimitive2D(std::move(aRetval), getTransparence())); + + aRetval = Primitive2DContainer{ aTrans }; + } + + rContainer.append(std::move(aRetval)); +} + +PolyPolygonSelectionPrimitive2D::PolyPolygonSelectionPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rColor, + double fTransparence, double fDiscreteGrow, bool bFill) + : maPolyPolygon(rPolyPolygon) + , maColor(rColor) + , mfTransparence(fTransparence) + , mfDiscreteGrow(fabs(fDiscreteGrow)) + , mbFill(bFill) +{ +} + +bool PolyPolygonSelectionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonSelectionPrimitive2D& rCompare + = static_cast<const PolyPolygonSelectionPrimitive2D&>(rPrimitive); + + return ( + getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() && getColor() == rCompare.getColor() + && getTransparence() == rCompare.getTransparence() + && getDiscreteGrow() == rCompare.getDiscreteGrow() && getFill() == rCompare.getFill()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonSelectionPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval(basegfx::utils::getRange(getB2DPolyPolygon())); + + if (getDiscreteGrow() > 0.0) + { + // get the current DiscreteUnit (not sure if getDiscreteUnit() is updated here, better go safe way) + const double fDiscreteUnit( + (rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)) + .getLength()); + + aRetval.grow(fDiscreteUnit * getDiscreteGrow()); + } + + return aRetval; +} + +// provide unique ID +sal_uInt32 PolyPolygonSelectionPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONSELECTIONPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx new file mode 100644 index 000000000..7191f3085 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx @@ -0,0 +1,100 @@ +/* -*- 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/PolyPolygonStrokePrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonStrokePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if (nCount) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + rContainer.push_back(new PolygonStrokePrimitive2D( + aPolyPolygon.getB2DPolygon(a), getLineAttribute(), getStrokeAttribute())); + } + } +} + +PolyPolygonStrokePrimitive2D::PolyPolygonStrokePrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + : maPolyPolygon(rPolyPolygon) + , maLineAttribute(rLineAttribute) + , maStrokeAttribute(rStrokeAttribute) +{ +} + +PolyPolygonStrokePrimitive2D::PolyPolygonStrokePrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const attribute::LineAttribute& rLineAttribute) + : maPolyPolygon(rPolyPolygon) + , maLineAttribute(rLineAttribute) +{ +} + +bool PolyPolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonStrokePrimitive2D& rCompare + = static_cast<const PolyPolygonStrokePrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getLineAttribute() == rCompare.getLineAttribute() + && getStrokeAttribute() == rCompare.getStrokeAttribute()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonStrokePrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // get range of it (subdivided) + basegfx::B2DRange aRetval(basegfx::utils::getRange(getB2DPolyPolygon())); + + // if width, grow by line width + if (getLineAttribute().getWidth()) + { + aRetval.grow(getLineAttribute().getWidth() / 2.0); + } + + return aRetval; +} + +// provide unique ID +sal_uInt32 PolyPolygonStrokePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/Primitive2DContainer.cxx b/drawinglayer/source/primitive2d/Primitive2DContainer.cxx new file mode 100644 index 000000000..48b0c625e --- /dev/null +++ b/drawinglayer/source/primitive2d/Primitive2DContainer.cxx @@ -0,0 +1,155 @@ +/* -*- 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/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( + const css::uno::Sequence<css::uno::Reference<css::graphic::XPrimitive2D>>& rSource) +{ + 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()); +} + +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) + { + *p = new UnoPrimitive2D(rPrimitive); + ++p; + } + return aVal; +} + +Primitive2DContainer Primitive2DContainer::maybeInvert(bool bInvert) +{ + if (bInvert) + std::reverse(begin(), end()); + return std::move(*this); +} + +// get B2DRange from a given Primitive2DSequence +basegfx::B2DRange +Primitive2DContainer::getB2DRange(const geometry::ViewInformation2D& aViewInformation) const +{ + basegfx::B2DRange aRetval; + + if (!empty()) + { + const sal_Int32 nCount(size()); + + for (sal_Int32 a(0); a < nCount; a++) + { + aRetval.expand(getB2DRangeFromPrimitive2DReference((*this)[a], aViewInformation)); + } + } + + return aRetval; +} + +bool Primitive2DContainer::operator==(const Primitive2DContainer& rB) const +{ + const bool bAHasElements(!empty()); + + if (bAHasElements != !rB.empty()) + { + return false; + } + + if (!bAHasElements) + { + return true; + } + + const size_t nCount(size()); + + if (nCount != rB.size()) + { + return false; + } + + for (size_t a(0); a < nCount; a++) + { + if (!arePrimitive2DReferencesEqual((*this)[a], rB[a])) + { + return false; + } + } + + return true; +} + +Primitive2DContainer::~Primitive2DContainer() {} + +void Primitive2DContainer::append(const Primitive2DReference& rSource) { push_back(rSource); } + +void Primitive2DContainer::append(const Primitive2DContainer& rSource) +{ + insert(end(), rSource.begin(), rSource.end()); +} + +void Primitive2DContainer::append(Primitive2DContainer&& rSource) +{ + this->insert(this->end(), std::make_move_iterator(rSource.begin()), + std::make_move_iterator(rSource.end())); +} + +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() +{ + std::unique_lock aGuard(m_aMutex); + return mxPrimitive->estimateUsage(); +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/Tools.cxx b/drawinglayer/source/primitive2d/Tools.cxx new file mode 100644 index 000000000..6a3482319 --- /dev/null +++ b/drawinglayer/source/primitive2d/Tools.cxx @@ -0,0 +1,235 @@ +/* -*- 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/Tools.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + +using namespace css; + +namespace drawinglayer::primitive2d +{ +// get B2DRange from a given Primitive2DReference +basegfx::B2DRange +getB2DRangeFromPrimitive2DReference(const Primitive2DReference& rCandidate, + const geometry::ViewInformation2D& aViewInformation) +{ + if (!rCandidate) + return basegfx::B2DRange(); + + return rCandidate->getB2DRange(aViewInformation); +} + +bool arePrimitive2DReferencesEqual(const Primitive2DReference& rxA, const Primitive2DReference& rxB) +{ + const bool bAIs(rxA.is()); + + if (bAIs != rxB.is()) + { + return false; + } + + if (!bAIs) + { + return true; + } + + 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->getBasePrimitive2D()) == (*pB->getBasePrimitive2D()); +} + +OUString idToString(sal_uInt32 nId) +{ + switch (nId) + { + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + return "TRANSPARENCE"; + case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D: + return "ANIMATEDSWITCH"; + case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D: + return "ANIMATEDBLINK"; + case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D: + return "ANIMATEDINTERPOLATE"; + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + return "BACKGROUNDCOLOR"; + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + return "BITMAP"; + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + return "CONTROL"; + case PRIMITIVE2D_ID_EMBEDDED3DPRIMITIVE2D: + return "EMBEDDED3D"; + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + return "FILLGRAPHIC"; + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D: + return "FILLGRADIENT"; + case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D: + return "FILLHATCH"; + case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D: + return "GRAPHIC"; + case PRIMITIVE2D_ID_GRIDPRIMITIVE2D: + return "GRID"; + case PRIMITIVE2D_ID_GROUPPRIMITIVE2D: + return "GROUP"; + case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D: + return "HELPLINE"; + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + return "MARKERARRAY"; + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + return "MASK"; + case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D: + return "MEDIA"; + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: + return "METAFILE"; + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + return "MODIFIEDCOLOR"; + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + return "POLYGONHAIRLINE"; + case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D: + return "POLYGONMARKER"; + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + return "POLYGONSTROKE"; + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D: + return "POLYGONSTROKEARROW"; + case PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D: + return "POLYPOLYGONSTROKE"; + case PRIMITIVE2D_ID_POLYPOLYGONSTROKEARROWPRIMITIVE2D: + return "POLYPOLYGONSTROKEARROW"; + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + return "POLYPOLYGONCOLOR"; + case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: + return "POLYPOLYGONGRADIENT"; + case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: + return "POLYPOLYGONHATCH"; + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + return "POLYPOLYGONGRAPHIC"; + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D: + return "SCENE"; + case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: + return "SHADOW"; + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + return "TEXTSIMPLEPORTION"; + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + return "TEXTDECORATEDPORTION"; + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + return "TRANSFORM"; + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + return "UNIFIEDTRANSPARENCE"; + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + return "POINTARRAY"; + case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D: + return "TEXTHIERARCHYFIELD"; + case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D: + return "TEXTHIERARCHYLINE"; + case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D: + return "TEXTHIERARCHYPARAGRAPH"; + case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D: + return "TEXTHIERARCHYBLOCK"; + case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D: + return "TEXTHIERARCHYEDIT"; + case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D: + return "POLYGONWAVE"; + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + return "WRONGSPELL"; + case PRIMITIVE2D_ID_TEXTEFFECTPRIMITIVE2D: + return "TEXTEFFECT"; + case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D: + return "TEXTHIERARCHYBULLET"; + case PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D: + return "POLYPOLYGONHAIRLINE"; + case PRIMITIVE2D_ID_EXECUTEPRIMITIVE2D: + return "EXECUTE"; + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: + return "PAGEPREVIEW"; + case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D: + return "STRUCTURETAG"; + case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D: + return "BORDERLINE"; + case PRIMITIVE2D_ID_POLYPOLYGONMARKERPRIMITIVE2D: + return "POLYPOLYGONMARKER"; + case PRIMITIVE2D_ID_HITTESTPRIMITIVE2D: + return "HITTEST"; + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + return "INVERT"; + case PRIMITIVE2D_ID_DISCRETEBITMAPPRIMITIVE2D: + return "DISCRETEBITMAP"; + case PRIMITIVE2D_ID_WALLPAPERBITMAPPRIMITIVE2D: + return "WALLPAPERBITMAP"; + case PRIMITIVE2D_ID_TEXTLINEPRIMITIVE2D: + return "TEXTLINE"; + case PRIMITIVE2D_ID_TEXTCHARACTERSTRIKEOUTPRIMITIVE2D: + return "TEXTCHARACTERSTRIKEOUT"; + case PRIMITIVE2D_ID_TEXTGEOMETRYSTRIKEOUTPRIMITIVE2D: + return "TEXTGEOMETRYSTRIKEOUT"; + case PRIMITIVE2D_ID_EPSPRIMITIVE2D: + return "EPS"; + case PRIMITIVE2D_ID_DISCRETESHADOWPRIMITIVE2D: + return "DISCRETESHADOW"; + case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D: + return "HIDDENGEOMETRY"; + case PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D: + return "SVGLINEARGRADIENT"; + case PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D: + return "SVGRADIALGRADIENT"; + case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D: + return "SVGLINEARATOM"; + case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D: + return "SVGRADIALATOM"; + case PRIMITIVE2D_ID_CROPPRIMITIVE2D: + return "CROP"; + case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D: + return "PATTERNFILL"; + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: + return "OBJECTINFO"; + case PRIMITIVE2D_ID_POLYPOLYGONSELECTIONPRIMITIVE2D: + return "POLYPOLYGONSELECTION"; + case PRIMITIVE2D_ID_PAGEHIERARCHYPRIMITIVE2D: + return "PAGEHIERARCHY"; + case PRIMITIVE2D_ID_GLOWPRIMITIVE2D: + return "GLOWPRIMITIVE"; + case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: + return "SOFTEDGEPRIMITIVE"; + default: + return OUString::number((nId >> 16) & 0xFF) + "|" + OUString::number(nId & 0xFF); + } +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/animatedprimitive2d.cxx b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx new file mode 100644 index 000000000..67349a834 --- /dev/null +++ b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx @@ -0,0 +1,203 @@ +/* -*- 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/animatedprimitive2d.hxx> +#include <drawinglayer/animation/animationtiming.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void AnimatedSwitchPrimitive2D::setAnimationEntry(const animation::AnimationEntry& rNew) + { + // clone given animation description + mpAnimationEntry = rNew.clone(); + } + + AnimatedSwitchPrimitive2D::AnimatedSwitchPrimitive2D( + const animation::AnimationEntry& rAnimationEntry, + Primitive2DContainer&& aChildren, + bool bIsTextAnimation) + : GroupPrimitive2D(std::move(aChildren)), + mbIsTextAnimation(bIsTextAnimation) + { + // clone given animation description + mpAnimationEntry = rAnimationEntry.clone(); + } + + AnimatedSwitchPrimitive2D::~AnimatedSwitchPrimitive2D() + { + } + + bool AnimatedSwitchPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const AnimatedSwitchPrimitive2D& rCompare = static_cast< const AnimatedSwitchPrimitive2D& >(rPrimitive); + + return (getAnimationEntry() == rCompare.getAnimationEntry()); + } + + return false; + } + + void AnimatedSwitchPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(getChildren().empty()) + return; + + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + const sal_uInt32 nLen(getChildren().size()); + sal_uInt32 nIndex(basegfx::fround(fState * static_cast<double>(nLen))); + + if(nIndex >= nLen) + { + nIndex = nLen - 1; + } + + const Primitive2DReference xRef(getChildren()[nIndex], uno::UNO_SET_THROW); + rVisitor.visit(xRef); + } + + // provide unique ID + sal_uInt32 AnimatedSwitchPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D; + } + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + AnimatedBlinkPrimitive2D::AnimatedBlinkPrimitive2D( + const animation::AnimationEntry& rAnimationEntry, + Primitive2DContainer&& aChildren) + : AnimatedSwitchPrimitive2D(rAnimationEntry, std::move(aChildren), true/*bIsTextAnimation*/) + { + } + + void AnimatedBlinkPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(!getChildren().empty()) + { + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + + if(fState < 0.5) + { + getChildren(rVisitor); + } + } + } + + // provide unique ID + sal_uInt32 AnimatedBlinkPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D; + } + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + AnimatedInterpolatePrimitive2D::AnimatedInterpolatePrimitive2D( + const std::vector< basegfx::B2DHomMatrix >& rmMatrixStack, + const animation::AnimationEntry& rAnimationEntry, + Primitive2DContainer&& aChildren) + : AnimatedSwitchPrimitive2D(rAnimationEntry, std::move(aChildren), true/*bIsTextAnimation*/) + { + // copy matrices to locally pre-decomposed matrix stack + const sal_uInt32 nCount(rmMatrixStack.size()); + maMatrixStack.reserve(nCount); + + for(const auto& a : rmMatrixStack) + { + maMatrixStack.emplace_back(a); + } + } + + void AnimatedInterpolatePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + const sal_uInt32 nSize(maMatrixStack.size()); + + if(nSize) + { + double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + + if(fState < 0.0) + { + fState = 0.0; + } + else if(fState > 1.0) + { + fState = 1.0; + } + + const double fIndex(fState * static_cast<double>(nSize - 1)); + const sal_uInt32 nIndA(sal_uInt32(floor(fIndex))); + const double fOffset(fIndex - static_cast<double>(nIndA)); + basegfx::B2DHomMatrix aTargetTransform; + std::vector< basegfx::utils::B2DHomMatrixBufferedDecompose >::const_iterator aMatA(maMatrixStack.begin() + nIndA); + + if(basegfx::fTools::equalZero(fOffset)) + { + // use matrix from nIndA directly + aTargetTransform = aMatA->getB2DHomMatrix(); + } + else + { + // interpolate. Get involved buffered decomposed matrices + const sal_uInt32 nIndB((nIndA + 1) % nSize); + std::vector< basegfx::utils::B2DHomMatrixBufferedDecompose >::const_iterator aMatB(maMatrixStack.begin() + nIndB); + + // interpolate for fOffset [0.0 .. 1.0[ + const basegfx::B2DVector aScale(basegfx::interpolate(aMatA->getScale(), aMatB->getScale(), fOffset)); + const basegfx::B2DVector aTranslate(basegfx::interpolate(aMatA->getTranslate(), aMatB->getTranslate(), fOffset)); + const double fRotate(((aMatB->getRotate() - aMatA->getRotate()) * fOffset) + aMatA->getRotate()); + const double fShearX(((aMatB->getShearX() - aMatA->getShearX()) * fOffset) + aMatA->getShearX()); + + // build matrix for state + aTargetTransform = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, fShearX, fRotate, aTranslate); + } + + // create new transform primitive reference, return new sequence + Primitive2DReference xRef(new TransformPrimitive2D(aTargetTransform, Primitive2DContainer(getChildren()))); + rVisitor.visit(xRef); + } + else + { + getChildren(rVisitor); + } + } + + // provide unique ID + sal_uInt32 AnimatedInterpolatePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D; + } +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx b/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx new file mode 100644 index 000000000..8f68d9e73 --- /dev/null +++ b/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx @@ -0,0 +1,94 @@ +/* -*- 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/backgroundcolorprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void BackgroundColorPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + if(!rViewInformation.getViewport().isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rViewInformation.getViewport())); + rContainer.push_back(new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aOutline), getBColor())); + } + } + + BackgroundColorPrimitive2D::BackgroundColorPrimitive2D( + const basegfx::BColor& rBColor, + double fTransparency) + : maBColor(rBColor), + mfTransparency(fTransparency) + { + } + + bool BackgroundColorPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const BackgroundColorPrimitive2D& rCompare = static_cast<const BackgroundColorPrimitive2D&>(rPrimitive); + + return (getBColor() == rCompare.getBColor() && getTransparency() == rCompare.getTransparency()); + } + + return false; + } + + basegfx::B2DRange BackgroundColorPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // always as big as the view + return rViewInformation.getViewport(); + } + + void BackgroundColorPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(!getBuffered2DDecomposition().empty() && (maLastViewport != rViewInformation.getViewport())) + { + // conditions of last local decomposition have changed, delete + const_cast< BackgroundColorPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange + const_cast< BackgroundColorPrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + sal_uInt32 BackgroundColorPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/baseprimitive2d.cxx b/drawinglayer/source/primitive2d/baseprimitive2d.cxx new file mode 100644 index 000000000..a2e0eaf6b --- /dev/null +++ b/drawinglayer/source/primitive2d/baseprimitive2d.cxx @@ -0,0 +1,106 @@ +/* -*- 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/Primitive2DContainer.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/utils/canvastools.hxx> + +using namespace css; + +namespace drawinglayer::primitive2d +{ +BasePrimitive2D::BasePrimitive2D() {} + +BasePrimitive2D::~BasePrimitive2D() {} + +bool BasePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + return (getPrimitive2DID() == rPrimitive.getPrimitive2DID()); +} + +namespace +{ +// Visitor class to get the B2D range from a tree of Primitive2DReference's +// +class B2DRangeVisitor : public Primitive2DDecompositionVisitor +{ +public: + const geometry::ViewInformation2D& mrViewInformation; + basegfx::B2DRange maRetval; + B2DRangeVisitor(const geometry::ViewInformation2D& rViewInformation) + : mrViewInformation(rViewInformation) + { + } + virtual void visit(const Primitive2DReference& r) override + { + maRetval.expand(getB2DRangeFromPrimitive2DReference(r, mrViewInformation)); + } + virtual void visit(const Primitive2DContainer& r) override + { + maRetval.expand(r.getB2DRange(mrViewInformation)); + } + virtual void visit(Primitive2DContainer&& r) override + { + maRetval.expand(r.getB2DRange(mrViewInformation)); + } +}; +} + +basegfx::B2DRange +BasePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + B2DRangeVisitor aVisitor(rViewInformation); + get2DDecomposition(aVisitor, rViewInformation); + return aVisitor.maRetval; +} + +void BasePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& /*rVisitor*/, + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ +} + +Primitive2DContainer +BasePrimitive2D::getDecomposition(const uno::Sequence<beans::PropertyValue>& rViewParameters) +{ + const auto aViewInformation = geometry::createViewInformation2D(rViewParameters); + Primitive2DContainer aContainer; + get2DDecomposition(aContainer, aViewInformation); + return aContainer; +} + +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 BasePrimitive2D::estimateUsage() +{ + return 0; // for now ignore the objects themselves +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx b/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx new file mode 100644 index 000000000..6f256b6e1 --- /dev/null +++ b/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx @@ -0,0 +1,77 @@ +/* -*- 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/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <com/sun/star/awt/XBitmap.hpp> + +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) +{ +} + +bool BitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const BitmapPrimitive2D& rCompare = static_cast<const BitmapPrimitive2D&>(rPrimitive); + + return (getXBitmap() == rCompare.getXBitmap() && getTransform() == rCompare.getTransform()); + } + + return false; +} + +basegfx::B2DRange +BitmapPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(maTransform); + return aRetval; +} + +sal_Int64 BitmapPrimitive2D::estimateUsage() +{ + if (!getXBitmap().is()) + { + return 0; + } + + uno::Reference<util::XAccounting> const xAcc(getXBitmap(), uno::UNO_QUERY); + + if (!xAcc.is()) + { + return 0; + } + + return xAcc->estimateUsage(); +} + +// provide unique ID +sal_uInt32 BitmapPrimitive2D::getPrimitive2DID() const { return PRIMITIVE2D_ID_BITMAPPRIMITIVE2D; } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx b/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx new file mode 100644 index 000000000..7ede1f2c3 --- /dev/null +++ b/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx @@ -0,0 +1,430 @@ +/* -*- 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/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <rtl/math.hxx> + +#include <algorithm> + + +namespace drawinglayer::primitive2d +{ + BorderLine::BorderLine( + const drawinglayer::attribute::LineAttribute& rLineAttribute, + double fStartLeft, + double fStartRight, + double fEndLeft, + double fEndRight) + : maLineAttribute(rLineAttribute), + mfStartLeft(fStartLeft), + mfStartRight(fStartRight), + mfEndLeft(fEndLeft), + mfEndRight(fEndRight), + mbIsGap(false) + { + } + + BorderLine::BorderLine( + double fWidth) + : maLineAttribute(basegfx::BColor(), fWidth), + mfStartLeft(0.0), + mfStartRight(0.0), + mfEndLeft(0.0), + mfEndRight(0.0), + mbIsGap(true) + { + } + + BorderLine::~BorderLine() + { + } + + bool BorderLine::operator==(const BorderLine& rBorderLine) const + { + return getLineAttribute() == rBorderLine.getLineAttribute() + && getStartLeft() == rBorderLine.getStartLeft() + && getStartRight() == rBorderLine.getStartRight() + && getEndLeft() == rBorderLine.getEndLeft() + && getEndRight() == rBorderLine.getEndRight() + && isGap() == rBorderLine.isGap(); + } + + // helper to add a centered, maybe stroked line primitive to rContainer + static void addPolygonStrokePrimitive2D( + Primitive2DContainer& rContainer, + const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + { + basegfx::B2DPolygon aPolygon; + + aPolygon.append(rStart); + aPolygon.append(rEnd); + + if (rStrokeAttribute.isDefault()) + { + rContainer.push_back( + new PolygonStrokePrimitive2D( + aPolygon, + rLineAttribute)); + } + else + { + rContainer.push_back( + new PolygonStrokePrimitive2D( + aPolygon, + rLineAttribute, + rStrokeAttribute)); + } + } + + double BorderLinePrimitive2D::getFullWidth() const + { + double fRetval(0.0); + + for(const auto& candidate : maBorderLines) + { + fRetval += candidate.getLineAttribute().getWidth(); + } + + return fRetval; + } + + void BorderLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if (getStart().equal(getEnd()) || getBorderLines().empty()) + return; + + // get data and vectors + basegfx::B2DVector aVector(getEnd() - getStart()); + aVector.normalize(); + const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector)); + const double fFullWidth(getFullWidth()); + double fOffset(fFullWidth * -0.5); + + for(const auto& candidate : maBorderLines) + { + const double fWidth(candidate.getLineAttribute().getWidth()); + + if(!candidate.isGap()) + { + const basegfx::B2DVector aDeltaY(aPerpendicular * (fOffset + (fWidth * 0.5))); + const basegfx::B2DPoint aStart(getStart() + aDeltaY); + const basegfx::B2DPoint aEnd(getEnd() + aDeltaY); + const bool bStartPerpendicular(rtl::math::approxEqual(candidate.getStartLeft(), candidate.getStartRight())); + const bool bEndPerpendicular(rtl::math::approxEqual(candidate.getEndLeft(), candidate.getEndRight())); + + if(bStartPerpendicular && bEndPerpendicular) + { + // start and end extends lead to an edge perpendicular to the line, so we can just use + // a PolygonStrokePrimitive2D for representation + addPolygonStrokePrimitive2D( + rContainer, + aStart - (aVector * candidate.getStartLeft()), + aEnd + (aVector * candidate.getEndLeft()), + candidate.getLineAttribute(), + getStrokeAttribute()); + } + else + { + // start and/or end extensions lead to a lineStart/End that is *not* + // perpendicular to the line itself + if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) + { + // without stroke, we can simply represent that using a filled polygon + const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); + basegfx::B2DPolygon aPolygon; + + aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); + aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); + aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); + aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); + + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + candidate.getLineAttribute().getColor())); + } + else + { + // with stroke, we have a problem - a filled polygon would lose the + // stroke. Let's represent the start and/or end as triangles, the main + // line still as PolygonStrokePrimitive2D. + // Fill default line Start/End for stroke, so we need no adaptations in else paths + basegfx::B2DPoint aStrokeStart(aStart - (aVector * candidate.getStartLeft())); + basegfx::B2DPoint aStrokeEnd(aEnd + (aVector * candidate.getEndLeft())); + const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); + + if(!bStartPerpendicular) + { + const double fMin(std::min(candidate.getStartLeft(), candidate.getStartRight())); + const double fMax(std::max(candidate.getStartLeft(), candidate.getStartRight())); + basegfx::B2DPolygon aPolygon; + + // create a triangle with min/max values for LineStart and add + if(rtl::math::approxEqual(candidate.getStartLeft(), fMax)) + { + aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); + } + + aPolygon.append(aStart - aHalfLineOffset - (aVector * fMin)); + aPolygon.append(aStart + aHalfLineOffset - (aVector * fMin)); + + if(rtl::math::approxEqual(candidate.getStartRight(), fMax)) + { + aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); + } + + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + candidate.getLineAttribute().getColor())); + + // Adapt StrokeStart accordingly + aStrokeStart = aStart - (aVector * fMin); + } + + if(!bEndPerpendicular) + { + const double fMin(std::min(candidate.getEndLeft(), candidate.getEndRight())); + const double fMax(std::max(candidate.getEndLeft(), candidate.getEndRight())); + basegfx::B2DPolygon aPolygon; + + // create a triangle with min/max values for LineEnd and add + if(rtl::math::approxEqual(candidate.getEndLeft(), fMax)) + { + aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); + } + + if(rtl::math::approxEqual(candidate.getEndRight(), fMax)) + { + aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); + } + + aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin)); + aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin)); + + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + candidate.getLineAttribute().getColor())); + + // Adapt StrokeEnd accordingly + aStrokeEnd = aEnd + (aVector * fMin); + } + + addPolygonStrokePrimitive2D( + rContainer, + aStrokeStart, + aStrokeEnd, + candidate.getLineAttribute(), + getStrokeAttribute()); + } + } + } + + fOffset += fWidth; + } + } + + bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D& rViewInformation) const + { + if (!getStart().equal(getEnd())) + { + const basegfx::B2DHomMatrix& rOTVT = rViewInformation.getObjectToViewTransformation(); + const basegfx::B2DVector aVector(rOTVT * getEnd() - rOTVT * getStart()); + + return basegfx::fTools::equalZero(aVector.getX()) || basegfx::fTools::equalZero(aVector.getY()); + } + + return false; + } + + BorderLinePrimitive2D::BorderLinePrimitive2D( + const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + std::vector< BorderLine >&& rBorderLines, + const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute) + : maStart(rStart), + maEnd(rEnd), + maBorderLines(std::move(rBorderLines)), + maStrokeAttribute(rStrokeAttribute) + { + } + + bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + return false; + + const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive); + + 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; + } + } + } + } + + return false; + } + + // provide unique ID + sal_uInt32 BorderLinePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D; + } + + Primitive2DReference tryMergeBorderLinePrimitive2D( + const BorderLinePrimitive2D* pCandidateA, + const BorderLinePrimitive2D* pCandidateB) + { + assert(pCandidateA); + assert(pCandidateB); + + // start of candidate has to match end of this + if(!pCandidateA->getEnd().equal(pCandidateB->getStart())) + { + return Primitive2DReference(); + } + + // candidate A needs a length + if(pCandidateA->getStart().equal(pCandidateA->getEnd())) + { + return Primitive2DReference(); + } + + // candidate B needs a length + if(pCandidateB->getStart().equal(pCandidateB->getEnd())) + { + return Primitive2DReference(); + } + + // StrokeAttribute has to be equal + if(!(pCandidateA->getStrokeAttribute() == pCandidateB->getStrokeAttribute())) + { + return Primitive2DReference(); + } + + // 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))) + { + return Primitive2DReference(); + } + + // number BorderLines has to be equal + const size_t count(pCandidateA->getBorderLines().size()); + if(count != pCandidateB->getBorderLines().size()) + { + return Primitive2DReference(); + } + + for(size_t a(0); a < count; a++) + { + const BorderLine& rBT(pCandidateA->getBorderLines()[a]); + const BorderLine& rBC(pCandidateB->getBorderLines()[a]); + + // LineAttribute has to be the same + if(!(rBC.getLineAttribute() == rBT.getLineAttribute())) + { + return Primitive2DReference(); + } + + // isGap has to be the same + if(rBC.isGap() != rBT.isGap()) + { + return Primitive2DReference(); + } + + if(rBT.isGap()) + { + // when gap, width has to be equal + if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth())) + { + return Primitive2DReference(); + } + } + else + { + // when not gap, the line extends have at least reach to the center ( > 0.0), + // else there is an extend usage. When > 0.0 they just overlap, no problem + if(rBT.getEndLeft() >= 0.0 + && rBT.getEndRight() >= 0.0 + && rBC.getStartLeft() >= 0.0 + && rBC.getStartRight() >= 0.0) + { + // okay + } + else + { + return Primitive2DReference(); + } + } + } + + // all conditions met, create merged primitive + std::vector< BorderLine > aMergedBorderLines; + + for(size_t a(0); a < count; a++) + { + const BorderLine& rBT(pCandidateA->getBorderLines()[a]); + const BorderLine& rBC(pCandidateB->getBorderLines()[a]); + + if(rBT.isGap()) + { + aMergedBorderLines.push_back(rBT); + } + else + { + aMergedBorderLines.push_back( + BorderLine( + rBT.getLineAttribute(), + rBT.getStartLeft(), rBT.getStartRight(), + rBC.getEndLeft(), rBC.getEndRight())); + } + } + + return Primitive2DReference( + new BorderLinePrimitive2D( + pCandidateA->getStart(), + pCandidateB->getEnd(), + std::move(aMergedBorderLines), + pCandidateA->getStrokeAttribute())); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/controlprimitive2d.cxx b/drawinglayer/source/primitive2d/controlprimitive2d.cxx new file mode 100644 index 000000000..461400daf --- /dev/null +++ b/drawinglayer/source/primitive2d/controlprimitive2d.cxx @@ -0,0 +1,348 @@ +/* -*- 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/controlprimitive2d.hxx> +#include <com/sun/star/awt/XWindow.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 <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 <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.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> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + void ControlPrimitive2D::createXControl() + { + if(mxXControl.is() || !getControlModel().is()) + return; + + uno::Reference< beans::XPropertySet > xSet(getControlModel(), uno::UNO_QUERY); + + if(!xSet.is()) + return; + + uno::Any aValue(xSet->getPropertyValue("DefaultControl")); + OUString aUnoControlTypeName; + + if(!(aValue >>= aUnoControlTypeName)) + return; + + if(aUnoControlTypeName.isEmpty()) + return; + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference< awt::XControl > xXControl( + xContext->getServiceManager()->createInstanceWithContext(aUnoControlTypeName, xContext), uno::UNO_QUERY); + + if(xXControl.is()) + { + xXControl->setModel(getControlModel()); + + // remember XControl + mxXControl = xXControl; + } + } + + Primitive2DReference ControlPrimitive2D::createBitmapDecomposition(const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DReference xRetval; + const uno::Reference< awt::XControl >& rXControl(getXControl()); + + if(rXControl.is()) + { + uno::Reference< awt::XWindow > xControlWindow(rXControl, uno::UNO_QUERY); + + if(xControlWindow.is()) + { + // get decomposition to get size + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // get absolute discrete size (no mirror or rotate here) + aScale = basegfx::absolute(aScale); + basegfx::B2DVector aDiscreteSize(rViewInformation.getObjectToViewTransformation() * aScale); + + // limit to a maximum square size, e.g. 300x150 pixels (45000) + const double fDiscreteMax(SvtOptionsDrawinglayer::GetQuadraticFormControlRenderLimit()); + const double fDiscreteQuadratic(aDiscreteSize.getX() * aDiscreteSize.getY()); + const bool bScaleUsed(fDiscreteQuadratic > fDiscreteMax); + double fFactor(1.0); + + if(bScaleUsed) + { + // get factor and adapt to scaled size + fFactor = sqrt(fDiscreteMax / fDiscreteQuadratic); + aDiscreteSize *= fFactor; + } + + // go to integer + const sal_Int32 nSizeX(basegfx::fround(aDiscreteSize.getX())); + const sal_Int32 nSizeY(basegfx::fround(aDiscreteSize.getY())); + + if(nSizeX > 0 && nSizeY > 0) + { + // prepare VirtualDevice + ScopedVclPtrInstance< VirtualDevice > aVirtualDevice(*Application::GetDefaultDevice()); + const Size aSizePixel(nSizeX, nSizeY); + aVirtualDevice->SetOutputSizePixel(aSizePixel); + + // set size at control + xControlWindow->setPosSize(0, 0, nSizeX, nSizeY, awt::PosSize::POSSIZE); + + // get graphics and view + uno::Reference< awt::XGraphics > xGraphics(aVirtualDevice->CreateUnoGraphics()); + uno::Reference< awt::XView > xControlView(rXControl, uno::UNO_QUERY); + + if(xGraphics.is() && xControlView.is()) + { + // link graphics and view + xControlView->setGraphics(xGraphics); + + { // #i93162# For painting the control setting a Zoom (using setZoom() at the xControlView) + // is needed to define the font size. Normally this is done in + // ViewObjectContactOfUnoControl::createPrimitive2DSequence by using positionControlForPaint(). + // For some reason the difference between MapUnit::MapTwipS and MapUnit::Map100thMM still plays + // a role there so that for Draw/Impress/Calc (the MapUnit::Map100thMM users) i need to set a zoom + // here, too. The factor includes the needed scale, but is calculated by pure comparisons. It + // is somehow related to the twips/100thmm relationship. + bool bUserIs100thmm(false); + const uno::Reference< awt::XControl > xControl(xControlView, uno::UNO_QUERY); + + if(xControl.is()) + { + uno::Reference<awt::XWindowPeer> xWindowPeer(xControl->getPeer()); + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindowPeer); + if (pWindow) + { + pWindow = pWindow->GetParent(); + + if(pWindow && MapUnit::Map100thMM == pWindow->GetMapMode().GetMapUnit()) + { + bUserIs100thmm = true; + } + } + } + + if(bUserIs100thmm) + { + // calc screen zoom for text display. fFactor is already added indirectly in aDiscreteSize + basegfx::B2DVector aScreenZoom( + basegfx::fTools::equalZero(aScale.getX()) ? 1.0 : aDiscreteSize.getX() / aScale.getX(), + basegfx::fTools::equalZero(aScale.getY()) ? 1.0 : aDiscreteSize.getY() / aScale.getY()); + static const double fZoomScale(28.0); // do not ask for this constant factor, but it gets the zoom right + aScreenZoom *= fZoomScale; + + // set zoom at control view for text scaling + xControlView->setZoom(static_cast<float>(aScreenZoom.getX()), static_cast<float>(aScreenZoom.getY())); + } + } + + try + { + // try to paint it to VirtualDevice + xControlView->draw(0, 0); + + // get bitmap + const BitmapEx aContent(aVirtualDevice->GetBitmapEx(Point(), aSizePixel)); + + // to avoid scaling, use the Bitmap pixel size as primitive size + const Size aBitmapSize(aContent.GetSizePixel()); + basegfx::B2DVector aBitmapSizeLogic( + rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(aBitmapSize.getWidth() - 1, aBitmapSize.getHeight() - 1)); + + if(bScaleUsed) + { + // if scaled adapt to scaled size + aBitmapSizeLogic /= fFactor; + } + + // short form for scale and translate transformation + const basegfx::B2DHomMatrix aBitmapTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( + aBitmapSizeLogic.getX(), aBitmapSizeLogic.getY(), aTranslate.getX(), aTranslate.getY())); + + // create primitive + xRetval = new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(aContent), + aBitmapTransform); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("drawinglayer"); + } + } + } + } + } + + return xRetval; + } + + Primitive2DReference ControlPrimitive2D::createPlaceholderDecomposition() const + { + // 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)); + 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)); + + return xRetval; + } + + void ControlPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // try to create a bitmap decomposition. If that fails for some reason, + // at least create a replacement decomposition. + Primitive2DReference xReference(createBitmapDecomposition(rViewInformation)); + + if(!xReference.is()) + { + xReference = createPlaceholderDecomposition(); + } + + rContainer.push_back(xReference); + } + + ControlPrimitive2D::ControlPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const uno::Reference< awt::XControlModel >& rxControlModel) + : maTransform(rTransform), + mxControlModel(rxControlModel) + { + } + + ControlPrimitive2D::ControlPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const uno::Reference< awt::XControlModel >& rxControlModel, + const uno::Reference< awt::XControl >& rxXControl) + : maTransform(rTransform), + mxControlModel(rxControlModel), + mxXControl(rxXControl) + { + } + + const uno::Reference< awt::XControl >& ControlPrimitive2D::getXControl() const + { + if(!mxXControl.is()) + { + const_cast< ControlPrimitive2D* >(this)->createXControl(); + } + + return mxXControl; + } + + bool ControlPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + // use base class compare operator + if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + return false; + + const ControlPrimitive2D& rCompare = static_cast<const ControlPrimitive2D&>(rPrimitive); + + if(getTransform() != rCompare.getTransform()) + return false; + + // check if ControlModel references both are/are not + if (getControlModel().is() != rCompare.getControlModel().is()) + return false; + + if(getControlModel().is()) + { + // both exist, check for equality + if (getControlModel() != rCompare.getControlModel()) + return false; + } + + // 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 true; + } + + basegfx::B2DRange ControlPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // simply derivate from unit range + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + return aRetval; + } + + void ControlPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // 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 + const basegfx::B2DVector aNewScaling(rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + + if(!getBuffered2DDecomposition().empty()) + { + if(!maLastViewScaling.equal(aNewScaling)) + { + // conditions of last local decomposition have changed, delete + const_cast< ControlPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewTransformation + const_cast< ControlPrimitive2D* >(this)->maLastViewScaling = aNewScaling; + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + sal_uInt32 ControlPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_CONTROLPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/cropprimitive2d.cxx b/drawinglayer/source/primitive2d/cropprimitive2d.cxx new file mode 100644 index 000000000..76393ad05 --- /dev/null +++ b/drawinglayer/source/primitive2d/cropprimitive2d.cxx @@ -0,0 +1,155 @@ +/* -*- 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 <primitive2d/cropprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + CropPrimitive2D::CropPrimitive2D( + Primitive2DContainer&& aChildren, + const basegfx::B2DHomMatrix& rTransformation, + double fCropLeft, + double fCropTop, + double fCropRight, + double fCropBottom) + : GroupPrimitive2D(std::move(aChildren)), + maTransformation(rTransformation), + mfCropLeft(fCropLeft), + mfCropTop(fCropTop), + mfCropRight(fCropRight), + mfCropBottom(fCropBottom) + { + } + + bool CropPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const CropPrimitive2D& rCompare = static_cast< const CropPrimitive2D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation() + && getCropLeft() == rCompare.getCropLeft() + && getCropTop() == rCompare.getCropTop() + && getCropRight() == rCompare.getCropRight() + && getCropBottom() == rCompare.getCropBottom()); + } + + return false; + } + + void CropPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getChildren().empty()) + return; + + // get original object scale in unit coordinates (no mirroring) + const basegfx::B2DVector aObjectScale(basegfx::absolute(getTransformation() * basegfx::B2DVector(1.0, 1.0))); + + // we handle cropping, so when no width or no height, content will be empty, + // so only do something when we have a width and a height + if(aObjectScale.equalZero()) + return; + + // calculate crop distances in unit coordinates. They are already combined with CropScaleFactor, thus + // are relative only to object scale + const double fBackScaleX(basegfx::fTools::equalZero(aObjectScale.getX()) ? 1.0 : 1.0 / fabs(aObjectScale.getX())); + const double fBackScaleY(basegfx::fTools::equalZero(aObjectScale.getY()) ? 1.0 : 1.0 / fabs(aObjectScale.getY())); + const double fLeft(getCropLeft() * fBackScaleX); + const double fTop(getCropTop() * fBackScaleY); + const double fRight(getCropRight() * fBackScaleX); + const double fBottom(getCropBottom() * fBackScaleY); + + // calc new unit range for comparisons; the original range is the unit range + const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + const basegfx::B2DRange aNewRange( + -fLeft, + -fTop, + 1.0 + fRight, + 1.0 + fBottom); + + // if we have no overlap the crop has removed everything, so we do only + // have to create content if this is not the case + if(!aNewRange.overlaps(aUnitRange)) + return; + + // create new transform; first take out old transform to get + // to unit coordinates by inverting. Inverting should be flawless + // since we already checked that object size is not zero in X or Y + basegfx::B2DHomMatrix aNewTransform(getTransformation()); + + aNewTransform.invert(); + + // apply crop enlargement in unit coordinates + aNewTransform = basegfx::utils::createScaleTranslateB2DHomMatrix( + aNewRange.getRange(), + aNewRange.getMinimum()) * aNewTransform; + + // apply original transformation. Since we have manipulated the crop + // in unit coordinates we do not need to care about mirroring or + // a corrected point for a possible shear or rotation, this all comes for + // free + aNewTransform = getTransformation() * aNewTransform; + + // prepare TransformPrimitive2D with xPrimitive + const Primitive2DReference xTransformPrimitive( + new TransformPrimitive2D( + aNewTransform, + Primitive2DContainer(getChildren()))); + + if(aUnitRange.isInside(aNewRange)) + { + // the new range is completely inside the old range (unit range), + // so no masking is needed + rVisitor.visit(xTransformPrimitive); + } + else + { + // mask with original object's bounds + basegfx::B2DPolyPolygon aMaskPolyPolygon(basegfx::utils::createUnitPolygon()); + aMaskPolyPolygon.transform(getTransformation()); + + // create maskPrimitive with aMaskPolyPolygon and aMaskContentVector + const Primitive2DReference xMask( + new MaskPrimitive2D( + aMaskPolyPolygon, + Primitive2DContainer { xTransformPrimitive })); + + rVisitor.visit(xMask); + } + } + + // provide unique ID + sal_uInt32 CropPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_CROPPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx b/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx new file mode 100644 index 000000000..8a86f8320 --- /dev/null +++ b/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx @@ -0,0 +1,102 @@ +/* -*- 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/discretebitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +namespace drawinglayer::primitive2d +{ + void DiscreteBitmapPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // use getViewTransformation() and getObjectTransformation() from + // ObjectAndViewTransformationDependentPrimitive2D to create a BitmapPrimitive2D + // with the correct mapping + + if(getBitmapEx().IsEmpty()) + return; + + // get discrete size + const Size& rSizePixel = getBitmapEx().GetSizePixel(); + const basegfx::B2DVector aDiscreteSize(rSizePixel.Width(), rSizePixel.Height()); + + // get inverse ViewTransformation + basegfx::B2DHomMatrix aInverseViewTransformation(getViewTransformation()); + aInverseViewTransformation.invert(); + + // get size and position in world coordinates + const basegfx::B2DVector aWorldSize(aInverseViewTransformation * aDiscreteSize); + const basegfx::B2DPoint aWorldTopLeft(getObjectTransformation() * getTopLeft()); + + // build object matrix in world coordinates so that the top-left + // position remains, but possible transformations (e.g. rotations) + // in the ObjectToView stack remain and get correctly applied + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, aWorldSize.getX()); + aObjectTransform.set(1, 1, aWorldSize.getY()); + aObjectTransform.set(0, 2, aWorldTopLeft.getX()); + aObjectTransform.set(1, 2, aWorldTopLeft.getY()); + + // get inverse ObjectTransformation + basegfx::B2DHomMatrix aInverseObjectTransformation(getObjectTransformation()); + aInverseObjectTransformation.invert(); + + // transform to object coordinate system + aObjectTransform = aInverseObjectTransformation * aObjectTransform; + + // create BitmapPrimitive2D with now object-local coordinate data + rContainer.push_back( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), + aObjectTransform)); + } + + DiscreteBitmapPrimitive2D::DiscreteBitmapPrimitive2D( + const BitmapEx& rBitmapEx, + const basegfx::B2DPoint& rTopLeft) + : maBitmapEx(rBitmapEx), + maTopLeft(rTopLeft) + { + } + + bool DiscreteBitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(ObjectAndViewTransformationDependentPrimitive2D::operator==(rPrimitive)) + { + const DiscreteBitmapPrimitive2D& rCompare = static_cast<const DiscreteBitmapPrimitive2D&>(rPrimitive); + + return (getBitmapEx() == rCompare.getBitmapEx() + && getTopLeft() == rCompare.getTopLeft()); + } + + return false; + } + + // provide unique ID + sal_uInt32 DiscreteBitmapPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_DISCRETEBITMAPPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx b/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx new file mode 100644 index 000000000..290d9d2d7 --- /dev/null +++ b/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx @@ -0,0 +1,311 @@ +/* -*- 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/discreteshadowprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <osl/diagnose.h> +#include <toolkit/helper/vclunohelper.hxx> + + +namespace drawinglayer::primitive2d +{ + DiscreteShadow::DiscreteShadow(const BitmapEx& rBitmapEx) + : maBitmapEx(rBitmapEx) + { + const Size& rBitmapSize = getBitmapEx().GetSizePixel(); + + if(rBitmapSize.Width() != rBitmapSize.Height() || rBitmapSize.Width() < 7) + { + OSL_ENSURE(false, "DiscreteShadowPrimitive2D: wrong bitmap format (!)"); + maBitmapEx = BitmapEx(); + } + } + + const BitmapEx& DiscreteShadow::getTopLeft() const + { + if(maTopLeft.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maTopLeft = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maTopLeft.Crop( + ::tools::Rectangle(Point(0, 0), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maTopLeft; + } + + const BitmapEx& DiscreteShadow::getTop() const + { + if(maTop.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maTop = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maTop.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 1, 0), Size(1, nQuarter))); + } + + return maTop; + } + + const BitmapEx& DiscreteShadow::getTopRight() const + { + if(maTopRight.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maTopRight = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maTopRight.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 2, 0), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maTopRight; + } + + const BitmapEx& DiscreteShadow::getRight() const + { + if(maRight.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maRight = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maRight.Crop( + ::tools::Rectangle(Point((nQuarter * 3) + 3, (nQuarter * 2) + 1), Size(nQuarter, 1))); + } + + return maRight; + } + + const BitmapEx& DiscreteShadow::getBottomRight() const + { + if(maBottomRight.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maBottomRight = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maBottomRight.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 2, (nQuarter * 2) + 2), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maBottomRight; + } + + const BitmapEx& DiscreteShadow::getBottom() const + { + if(maBottom.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maBottom = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maBottom.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 1, (nQuarter * 3) + 3), Size(1, nQuarter))); + } + + return maBottom; + } + + const BitmapEx& DiscreteShadow::getBottomLeft() const + { + if(maBottomLeft.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maBottomLeft = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maBottomLeft.Crop( + ::tools::Rectangle(Point(0, (nQuarter * 2) + 2), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maBottomLeft; + } + + const BitmapEx& DiscreteShadow::getLeft() const + { + if(maLeft.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maLeft = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maLeft.Crop( + ::tools::Rectangle(Point(0, (nQuarter * 2) + 1), Size(nQuarter, 1))); + } + + return maLeft; + } + +} // end of namespace + + +namespace drawinglayer::primitive2d +{ + void DiscreteShadowPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + Primitive2DContainer xRetval; + + if(getDiscreteShadow().getBitmapEx().IsEmpty()) + return; + + const sal_Int32 nQuarter((getDiscreteShadow().getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const basegfx::B2DVector aScale(getTransform() * basegfx::B2DVector(1.0, 1.0)); + const double fSingleX(getDiscreteUnit() / aScale.getX()); + const double fSingleY(getDiscreteUnit() / aScale.getY()); + const double fBorderX(fSingleX * nQuarter); + const double fBorderY(fSingleY * nQuarter); + const double fBigLenX((fBorderX * 2.0) + fSingleX); + const double fBigLenY((fBorderY * 2.0) + fSingleY); + + xRetval.resize(8); + + // TopLeft + xRetval[0] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTopLeft()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + -fBorderX, + -fBorderY))); + + // Top + xRetval[1] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTop()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + 1.0 - (2.0 * (fBorderX + fSingleX)) + fSingleX, + fBorderY, + fBorderX + fSingleX, + -fBorderY))); + + // TopRight + xRetval[2] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTopRight()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + 1.0 - fBorderX, + -fBorderY))); + + // Right + xRetval[3] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getRight()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBorderX, + 1.0 - (2.0 * (fBorderY + fSingleY)) + fSingleY, + 1.0 + fSingleX, + fBorderY + fSingleY))); + + // BottomRight + xRetval[4] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottomRight()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + 1.0 - (fBorderX + fSingleX) + fSingleX, + 1.0 - (fBorderY + fSingleY) + fSingleY))); + + // Bottom + xRetval[5] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottom()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + 1.0 - (2.0 * (fBorderX + fSingleX)) + fSingleX, + fBorderY, + fBorderX + fSingleX, + 1.0 + fSingleY))); + + // BottomLeft + xRetval[6] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottomLeft()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + -fBorderX, + 1.0 - fBorderY))); + + // Left + xRetval[7] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getLeft()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBorderX, + 1.0 - (2.0 * (fBorderY + fSingleY)) + fSingleY, + -fBorderX, + fBorderY + fSingleY))); + + // put all in object transformation to get to target positions + rContainer.push_back( + new TransformPrimitive2D( + getTransform(), + std::move(xRetval))); + } + + DiscreteShadowPrimitive2D::DiscreteShadowPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const DiscreteShadow& rDiscreteShadow) + : maTransform(rTransform), + maDiscreteShadow(rDiscreteShadow) + { + } + + bool DiscreteShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const DiscreteShadowPrimitive2D& rCompare = static_cast<const DiscreteShadowPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getDiscreteShadow() == rCompare.getDiscreteShadow()); + } + + return false; + } + + basegfx::B2DRange DiscreteShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + if(getDiscreteShadow().getBitmapEx().IsEmpty()) + { + // no graphics without valid bitmap definition + return basegfx::B2DRange(); + } + else + { + // prepare normal objectrange + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + + // extract discrete shadow size and grow + const basegfx::B2DVector aScale(rViewInformation.getViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + const sal_Int32 nQuarter((getDiscreteShadow().getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const double fGrowX((1.0 / aScale.getX()) * nQuarter); + const double fGrowY((1.0 / aScale.getY()) * nQuarter); + aRetval.grow(std::max(fGrowX, fGrowY)); + + return aRetval; + } + } + + // provide unique ID + sal_uInt32 DiscreteShadowPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_DISCRETESHADOWPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx b/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx new file mode 100644 index 000000000..f072df0c3 --- /dev/null +++ b/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx @@ -0,0 +1,146 @@ +/* -*- 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/embedded3dprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/color/bcolor.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> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + bool Embedded3DPrimitive2D::impGetShadow3D() const + { + // create on demand + if(!mbShadow3DChecked && !getChildren3D().empty()) + { + // create shadow extraction processor + processor3d::Shadow3DExtractingProcessor aShadowProcessor( + getViewInformation3D(), + getObjectTransformation(), + getLightNormal(), + getShadowSlant(), + getScene3DRange()); + + // process local primitives + aShadowProcessor.process(getChildren3D()); + + // fetch result and set checked flag + const_cast< Embedded3DPrimitive2D* >(this)->maShadowPrimitives = aShadowProcessor.getPrimitive2DSequence(); + const_cast< Embedded3DPrimitive2D* >(this)->mbShadow3DChecked = true; + } + + // return if there are shadow primitives + return !maShadowPrimitives.empty(); + } + + void Embedded3DPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, 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)); + const basegfx::BColor aYellow(1.0, 1.0, 0.0); + rContainer.push_back(new PolygonHairlinePrimitive2D(aOutline, aYellow)); + } + + Embedded3DPrimitive2D::Embedded3DPrimitive2D( + const primitive3d::Primitive3DContainer& rxChildren3D, + const basegfx::B2DHomMatrix& rObjectTransformation, + const geometry::ViewInformation3D& rViewInformation3D, + const basegfx::B3DVector& rLightNormal, + double fShadowSlant, + const basegfx::B3DRange& rScene3DRange) + : mxChildren3D(rxChildren3D), + maObjectTransformation(rObjectTransformation), + maViewInformation3D(rViewInformation3D), + maLightNormal(rLightNormal), + mfShadowSlant(fShadowSlant), + maScene3DRange(rScene3DRange), + mbShadow3DChecked(false) + { + maLightNormal.normalize(); + } + + bool Embedded3DPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const Embedded3DPrimitive2D& rCompare = static_cast< const Embedded3DPrimitive2D& >(rPrimitive); + + return (getChildren3D() == rCompare.getChildren3D() + && getObjectTransformation() == rCompare.getObjectTransformation() + && getViewInformation3D() == rCompare.getViewInformation3D() + && getLightNormal() == rCompare.getLightNormal() + && getShadowSlant() == rCompare.getShadowSlant() + && getScene3DRange() == rCompare.getScene3DRange()); + } + + return false; + } + + basegfx::B2DRange Embedded3DPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + if(maB2DRange.isEmpty()) + { + // use the 3d transformation stack to create a projection of the 3D range + basegfx::B3DRange a3DRange(getChildren3D().getB3DRange(getViewInformation3D())); + a3DRange.transform(getViewInformation3D().getObjectToView()); + + // create 2d range from projected 3d and transform with scene's object transformation + basegfx::B2DRange aNewRange; + aNewRange.expand(basegfx::B2DPoint(a3DRange.getMinX(), a3DRange.getMinY())); + aNewRange.expand(basegfx::B2DPoint(a3DRange.getMaxX(), a3DRange.getMaxY())); + aNewRange.transform(getObjectTransformation()); + + // check for 3D shadows and their 2D projections. If those exist, they need to be + // taken into account + if(impGetShadow3D()) + { + const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation)); + + if(!aShadow2DRange.isEmpty()) + { + aNewRange.expand(aShadow2DRange); + } + } + + // assign to buffered value + const_cast< Embedded3DPrimitive2D* >(this)->maB2DRange = aNewRange; + } + + return maB2DRange; + } + + // provide unique ID + sal_uInt32 Embedded3DPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_EMBEDDED3DPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/epsprimitive2d.cxx b/drawinglayer/source/primitive2d/epsprimitive2d.cxx new file mode 100644 index 000000000..9e876dad5 --- /dev/null +++ b/drawinglayer/source/primitive2d/epsprimitive2d.cxx @@ -0,0 +1,84 @@ +/* -*- 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/epsprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> + +namespace drawinglayer::primitive2d +{ + void EpsPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const GDIMetaFile& rSubstituteContent = getMetaFile(); + + if( rSubstituteContent.GetActionSize() ) + { + // the default decomposition will use the Metafile replacement visualisation. + // To really use the Eps data, a renderer has to know and interpret this primitive + // directly. + + rContainer.push_back( + new MetafilePrimitive2D( + getEpsTransform(), + rSubstituteContent)); + } + } + + EpsPrimitive2D::EpsPrimitive2D( + const basegfx::B2DHomMatrix& rEpsTransform, + const GfxLink& rGfxLink, + const GDIMetaFile& rMetaFile) + : maEpsTransform(rEpsTransform), + maGfxLink(rGfxLink), + maMetaFile(rMetaFile) + { + } + + bool EpsPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const EpsPrimitive2D& rCompare = static_cast<const EpsPrimitive2D&>(rPrimitive); + + return (getEpsTransform() == rCompare.getEpsTransform() + && getGfxLink() == rCompare.getGfxLink() + && getMetaFile() == rCompare.getMetaFile()); + } + + return false; + } + + basegfx::B2DRange EpsPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // use own implementation to quickly answer the getB2DRange question. + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getEpsTransform()); + + return aRetval; + } + + // provide unique ID + sal_uInt32 EpsPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_EPSPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx new file mode 100644 index 000000000..7baf93bf5 --- /dev/null +++ b/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx @@ -0,0 +1,334 @@ +/* -*- 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/fillgradientprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <texture/texture.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void FillGradientPrimitive2D::generateMatricesAndColors( + std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) 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(nSteps == 0) + { + nSteps = nMaxSteps; + } + + if(nSteps < 2) + { + nSteps = 2; + } + + if(nSteps > nMaxSteps) + { + nSteps = nMaxSteps; + } + + nSteps = std::max(sal_uInt32(1), nSteps); + + switch(getFillGradient().getStyle()) + { + case attribute::GradientStyle::Linear: + { + texture::GeoTexSvxGradientLinear aGradient( + getDefinitionRange(), + getOutputRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Axial: + { + texture::GeoTexSvxGradientAxial aGradient( + getDefinitionRange(), + getOutputRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Radial: + { + texture::GeoTexSvxGradientRadial aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Elliptical: + { + texture::GeoTexSvxGradientElliptical aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Square: + { + texture::GeoTexSvxGradientSquare aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Rect: + { + texture::GeoTexSvxGradientRect aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + 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 + { + // 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++) + { + // 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( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + rEntries[a].maBColor)); + + // reuse inner polygon, it's the 2nd one + aCombinedPolyPoly.remove(0); + } + + // 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()) + { + case attribute::GradientStyle::Radial: + case attribute::GradientStyle::Elliptical: + { + aUnitPolygon = basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), 1.0); + break; + } + default: // GradientStyle::Linear, attribute::GradientStyle::Axial, attribute::GradientStyle::Square, attribute::GradientStyle::Rect + { + aUnitPolygon = basegfx::utils::createPolygonFromRect(basegfx::B2DRange(-1.0, -1.0, 1.0, 1.0)); + break; + } + } + + // 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); + } + } + + void FillGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, 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 + // PolyPolygons where each one describes a 'ring' for the gradient such + // that the rings will not overlap. This is useful for the old XOR-paint + // 'trick' of VCL which is recorded in Metafiles; so this version may be + // used from the MetafilePrimitive2D in its decomposition. + + if(!getFillGradient().isDefault()) + { + createFill(rContainer, /*bOverlapping*/true); + } + } + + FillGradientPrimitive2D::FillGradientPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const attribute::FillGradientAttribute& rFillGradient) + : maOutputRange(rOutputRange), + maDefinitionRange(rOutputRange), + maFillGradient(rFillGradient) + { + } + + FillGradientPrimitive2D::FillGradientPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const basegfx::B2DRange& rDefinitionRange, + const attribute::FillGradientAttribute& rFillGradient) + : maOutputRange(rOutputRange), + maDefinitionRange(rDefinitionRange), + maFillGradient(rFillGradient) + { + } + + bool FillGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const FillGradientPrimitive2D& rCompare = static_cast<const FillGradientPrimitive2D&>(rPrimitive); + + return (getOutputRange() == rCompare.getOutputRange() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillGradient() == rCompare.getFillGradient()); + } + + return false; + } + + basegfx::B2DRange FillGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return the geometrically visible area + return getOutputRange(); + } + + // provide unique ID + sal_uInt32 FillGradientPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx new file mode 100644 index 000000000..0f386eea2 --- /dev/null +++ b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx @@ -0,0 +1,131 @@ +/* -*- 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/fillgraphicprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <texture/texture.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <primitive2d/graphicprimitivehelper2d.hxx> +#include <vcl/graph.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void FillGraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const attribute::FillGraphicAttribute& rAttribute = getFillGraphic(); + + if(rAttribute.isDefault()) + return; + + const Graphic& rGraphic = rAttribute.getGraphic(); + + if(GraphicType::Bitmap != rGraphic.GetType() && GraphicType::GdiMetafile != rGraphic.GetType()) + return; + + const Size aSize(rGraphic.GetPrefSize()); + + if(!(aSize.Width() && aSize.Height())) + return; + + // we have a graphic (bitmap or metafile) with some size + if(rAttribute.getTiling()) + { + // get object range and create tiling matrices + std::vector< basegfx::B2DHomMatrix > aMatrices; + texture::GeoTexSvxTiled aTiling( + rAttribute.getGraphicRange(), + rAttribute.getOffsetX(), + rAttribute.getOffsetY()); + + // get matrices and realloc retval + aTiling.appendTransformations(aMatrices); + + // prepare content primitive + Primitive2DContainer xSeq; + create2DDecompositionOfGraphic(xSeq, + rGraphic, + basegfx::B2DHomMatrix()); + + for(const auto &a : aMatrices) + { + rContainer.push_back(new TransformPrimitive2D( + getTransformation() * a, + Primitive2DContainer(xSeq))); + } + } + else + { + // add graphic without tiling + const basegfx::B2DHomMatrix aObjectTransform( + getTransformation() * basegfx::utils::createScaleTranslateB2DHomMatrix( + rAttribute.getGraphicRange().getRange(), + rAttribute.getGraphicRange().getMinimum())); + + create2DDecompositionOfGraphic(rContainer, + rGraphic, + aObjectTransform); + } + } + + FillGraphicPrimitive2D::FillGraphicPrimitive2D( + const basegfx::B2DHomMatrix& rTransformation, + const attribute::FillGraphicAttribute& rFillGraphic) + : maTransformation(rTransformation), + maFillGraphic(rFillGraphic) + { + } + + bool FillGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const FillGraphicPrimitive2D& rCompare = static_cast< const FillGraphicPrimitive2D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation() + && getFillGraphic() == rCompare.getFillGraphic()); + } + + return false; + } + + basegfx::B2DRange FillGraphicPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return range of it + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(getTransformation()); + + return basegfx::utils::getRange(aPolygon); + } + + // provide unique ID + sal_uInt32 FillGraphicPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx b/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx new file mode 100644 index 000000000..09a82bdd8 --- /dev/null +++ b/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx @@ -0,0 +1,198 @@ +/* -*- 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/fillhatchprimitive2d.hxx> +#include <texture/texture.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void FillHatchPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getFillHatch().isDefault()) + return; + + // create hatch + const basegfx::BColor aHatchColor(getFillHatch().getColor()); + const double fAngle(getFillHatch().getAngle()); + std::vector< basegfx::B2DHomMatrix > aMatrices; + double fDistance(getFillHatch().getDistance()); + const bool bAdaptDistance(0 != getFillHatch().getMinimalDiscreteDistance()); + + // #i120230# evtl. adapt distance + if(bAdaptDistance) + { + const double fDiscreteDistance(getFillHatch().getDistance() / getDiscreteUnit()); + + if(fDiscreteDistance < static_cast<double>(getFillHatch().getMinimalDiscreteDistance())) + { + fDistance = static_cast<double>(getFillHatch().getMinimalDiscreteDistance()) * getDiscreteUnit(); + } + } + + // get hatch transformations + switch(getFillHatch().getStyle()) + { + case attribute::HatchStyle::Triple: + { + // rotated 45 degrees + texture::GeoTexSvxHatch aHatch( + getDefinitionRange(), + getOutputRange(), + fDistance, + fAngle - M_PI_4); + + aHatch.appendTransformations(aMatrices); + + [[fallthrough]]; + } + case attribute::HatchStyle::Double: + { + // rotated 90 degrees + texture::GeoTexSvxHatch aHatch( + getDefinitionRange(), + getOutputRange(), + fDistance, + fAngle - M_PI_2); + + aHatch.appendTransformations(aMatrices); + + [[fallthrough]]; + } + case attribute::HatchStyle::Single: + { + // angle as given + texture::GeoTexSvxHatch aHatch( + getDefinitionRange(), + getOutputRange(), + fDistance, + fAngle); + + aHatch.appendTransformations(aMatrices); + } + } + + // prepare return value + const bool bFillBackground(getFillHatch().isFillBackground()); + + // evtl. create filled background + if(bFillBackground) + { + // create primitive for background + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(getOutputRange())), getBColor())); + } + + // create primitives + const basegfx::B2DPoint aStart(0.0, 0.0); + const basegfx::B2DPoint aEnd(1.0, 0.0); + + for (const auto &a : aMatrices) + { + const basegfx::B2DHomMatrix& rMatrix = a; + basegfx::B2DPolygon aNewLine; + + aNewLine.append(rMatrix * aStart); + aNewLine.append(rMatrix * aEnd); + + // create hairline + rContainer.push_back(new PolygonHairlinePrimitive2D(aNewLine, aHatchColor)); + } + } + + FillHatchPrimitive2D::FillHatchPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const basegfx::BColor& rBColor, + const attribute::FillHatchAttribute& rFillHatch) + : maOutputRange(rOutputRange), + maDefinitionRange(rOutputRange), + maFillHatch(rFillHatch), + maBColor(rBColor) + { + } + + FillHatchPrimitive2D::FillHatchPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rBColor, + const attribute::FillHatchAttribute& rFillHatch) + : maOutputRange(rOutputRange), + maDefinitionRange(rDefinitionRange), + maFillHatch(rFillHatch), + maBColor(rBColor) + { + } + + bool FillHatchPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const FillHatchPrimitive2D& rCompare = static_cast<const FillHatchPrimitive2D&>(rPrimitive); + + return (getOutputRange() == rCompare.getOutputRange() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillHatch() == rCompare.getFillHatch() + && getBColor() == rCompare.getBColor()); + } + + return false; + } + + basegfx::B2DRange FillHatchPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return the geometrically visible area + return getOutputRange(); + } + + void FillHatchPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + bool bAdaptDistance(0 != getFillHatch().getMinimalDiscreteDistance()); + + if(bAdaptDistance) + { + // behave view-dependent + DiscreteMetricDependentPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + else + { + // behave view-independent + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + } + + // provide unique ID + sal_uInt32 FillHatchPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx new file mode 100644 index 000000000..6fe14c222 --- /dev/null +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -0,0 +1,62 @@ +/* -*- 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/glowprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius, + Primitive2DContainer&& rChildren) + : GroupPrimitive2D(std::move(rChildren)) + , maGlowColor(rGlowColor) + , mfGlowRadius(fRadius) +{ +} + +bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const GlowPrimitive2D& rCompare = static_cast<const GlowPrimitive2D&>(rPrimitive); + + return (getGlowRadius() == rCompare.getGlowRadius() + && getGlowColor() == rCompare.getGlowColor()); + } + + return false; +} + +basegfx::B2DRange +GlowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval(GroupPrimitive2D::getB2DRange(rViewInformation)); + // We need additional space for the glow from all sides + aRetval.grow(getGlowRadius()); + return aRetval; +} + +// provide unique ID +sal_uInt32 GlowPrimitive2D::getPrimitive2DID() const { return PRIMITIVE2D_ID_GLOWPRIMITIVE2D; } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx new file mode 100644 index 000000000..4cd7a4963 --- /dev/null +++ b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx @@ -0,0 +1,219 @@ +/* -*- 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 <algorithm> + +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <primitive2d/cropprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <primitive2d/graphicprimitivehelper2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +namespace drawinglayer::primitive2d +{ +void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, + const geometry::ViewInformation2D&) const +{ + if (0 == getGraphicAttr().GetAlpha()) + { + // content is invisible, done + return; + } + + // do not apply mirroring from GraphicAttr to the Metafile by calling + // GetTransformedGraphic, this will try to mirror the Metafile using Scale() + // at the Metafile. This again calls Scale at the single MetaFile actions, + // but this implementation never worked. I reworked that implementations, + // but for security reasons i will try not to use it. + basegfx::B2DHomMatrix aTransform(getTransform()); + + if (getGraphicAttr().IsMirrored()) + { + // content needs mirroring + const bool bHMirr(getGraphicAttr().GetMirrorFlags() & BmpMirrorFlags::Horizontal); + const bool bVMirr(getGraphicAttr().GetMirrorFlags() & BmpMirrorFlags::Vertical); + + // mirror by applying negative scale to the unit primitive and + // applying the object transformation on it. + aTransform + = basegfx::utils::createScaleB2DHomMatrix(bHMirr ? -1.0 : 1.0, bVMirr ? -1.0 : 1.0); + aTransform.translate(bHMirr ? 1.0 : 0.0, bVMirr ? 1.0 : 0.0); + aTransform = getTransform() * aTransform; + } + + // Get transformed graphic. Suppress rotation and cropping, only filtering is needed + // here (and may be replaced later on). Cropping is handled below as mask primitive (if set). + // Also need to suppress mirroring, it is part of the transformation now (see above). + // Also move transparency handling to embedding to a UnifiedTransparencePrimitive2D; do + // that by remembering original transparency and applying that later if needed + GraphicAttr aSuppressGraphicAttr(getGraphicAttr()); + + aSuppressGraphicAttr.SetCrop(0, 0, 0, 0); + aSuppressGraphicAttr.SetRotation(0_deg10); + aSuppressGraphicAttr.SetMirrorFlags(BmpMirrorFlags::NONE); + aSuppressGraphicAttr.SetAlpha(255); + + const GraphicObject& rGraphicObject = getGraphicObject(); + Graphic aTransformedGraphic(rGraphicObject.GetGraphic()); + const bool isBitmap(GraphicType::Bitmap == aTransformedGraphic.GetType() + && !aTransformedGraphic.getVectorGraphicData()); + const bool isAdjusted(getGraphicAttr().IsAdjusted()); + const bool isDrawMode(GraphicDrawMode::Standard != getGraphicAttr().GetDrawMode()); + + if (isBitmap && (isAdjusted || isDrawMode)) + { + // the pure primitive solution with the color modifiers works well, too, but when + // it is a bitmap graphic the old modification currently is faster; so use it here + // instead of creating all as in create2DColorModifierEmbeddingsAsNeeded (see below). + // Still, crop, rotation, mirroring and transparency is handled by primitives already + // (see above). + // This could even be done when vector graphic, but we explicitly want to have the + // pure primitive solution for this; this will allow vector graphics to stay vector + // graphics, independent from the color filtering stuff. This will enhance e.g. + // SVG and print quality while reducing data size at the same time. + // The other way around the old modifications when only used on already bitmap objects + // will not lose any quality. + aTransformedGraphic = rGraphicObject.GetTransformedGraphic(&aSuppressGraphicAttr); + + // reset GraphicAttr after use to not apply double + aSuppressGraphicAttr = GraphicAttr(); + } + + // create sub-content; helper takes care of correct handling of + // bitmap, svg or metafile content + Primitive2DContainer aRetval; + create2DDecompositionOfGraphic(aRetval, aTransformedGraphic, aTransform); + + if (aRetval.empty()) + { + // content is invisible, done + return; + } + + if (isAdjusted || isDrawMode) + { + // embed to needed ModifiedColorPrimitive2D's if necessary. Do this for + // adjustments and draw mode specials + aRetval = create2DColorModifierEmbeddingsAsNeeded( + std::move(aRetval), aSuppressGraphicAttr.GetDrawMode(), + std::clamp(aSuppressGraphicAttr.GetLuminance() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetContrast() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetChannelR() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetChannelG() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetChannelB() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetGamma(), 0.0, 10.0), + aSuppressGraphicAttr.IsInvert()); + + if (aRetval.empty()) + { + // content is invisible, done + return; + } + } + + if (getGraphicAttr().IsTransparent()) + { + // check for transparency + const double fTransparency( + std::clamp((255 - getGraphicAttr().GetAlpha()) * (1.0 / 255.0), 0.0, 1.0)); + + if (!basegfx::fTools::equalZero(fTransparency)) + { + Primitive2DReference aUnifiedTransparence( + new UnifiedTransparencePrimitive2D(std::move(aRetval), fTransparency)); + + aRetval = Primitive2DContainer{ aUnifiedTransparence }; + } + } + + if (getGraphicAttr().IsCropped()) + { + // check for cropping + // calculate scalings between real image size and logic object size. This + // is necessary since the crop values are relative to original bitmap size + const basegfx::B2DVector aObjectScale(aTransform * basegfx::B2DVector(1.0, 1.0)); + const basegfx::B2DVector aCropScaleFactor(rGraphicObject.calculateCropScaling( + aObjectScale.getX(), aObjectScale.getY(), getGraphicAttr().GetLeftCrop(), + getGraphicAttr().GetTopCrop(), getGraphicAttr().GetRightCrop(), + 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 }; + } + + rContainer.append(std::move(aRetval)); +} + +GraphicPrimitive2D::GraphicPrimitive2D(const basegfx::B2DHomMatrix& rTransform, + const GraphicObject& rGraphicObject, + const GraphicAttr& rGraphicAttr) + : maTransform(rTransform) + , maGraphicObject(rGraphicObject) + , maGraphicAttr(rGraphicAttr) +{ +} + +GraphicPrimitive2D::GraphicPrimitive2D(const basegfx::B2DHomMatrix& rTransform, + const GraphicObject& rGraphicObject) + : maTransform(rTransform) + , maGraphicObject(rGraphicObject) +{ +} + +bool GraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const GraphicPrimitive2D& rCompare = static_cast<const GraphicPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getGraphicObject() == rCompare.getGraphicObject() + && getGraphicAttr() == rCompare.getGraphicAttr()); + } + + return false; +} + +basegfx::B2DRange +GraphicPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + return aRetval; +} + +// provide unique ID +sal_uInt32 GraphicPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx new file mode 100644 index 000000000..c79af14dd --- /dev/null +++ b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx @@ -0,0 +1,697 @@ +/* -*- 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 <algorithm> + +#include <primitive2d/graphicprimitivehelper2d.hxx> +#include <drawinglayer/animation/animationtiming.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/animatedprimitive2d.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/numeric/ftools.hxx> + +// helper class for animated graphics + +#include <vcl/animate/Animation.hxx> +#include <vcl/graph.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/skia/SkiaHelper.hxx> + +namespace drawinglayer::primitive2d +{ + namespace { + + class AnimatedGraphicPrimitive2D : public AnimatedSwitchPrimitive2D + { + private: + /// the geometric definition + basegfx::B2DHomMatrix maTransform; + + /** the Graphic with all its content possibilities, here only + animated is allowed and gets checked by isValidData(). + an instance of Graphic is used here since it's ref-counted + and thus a safe copy for now + */ + const Graphic maGraphic; + + /// local animation processing data, excerpt from maGraphic + ::Animation maAnimation; + + /// the on-demand created VirtualDevices for frame creation + ScopedVclPtrInstance< VirtualDevice > maVirtualDevice; + ScopedVclPtrInstance< VirtualDevice > maVirtualDeviceMask; + + // index of the next frame that would be regularly prepared + sal_uInt32 mnNextFrameToPrepare; + + /// buffering of 1st frame (always active) + Primitive2DReference maBufferedFirstFrame; + + /// buffering of all frames + std::vector<Primitive2DReference> maBufferedPrimitives; + bool mbBufferingAllowed; + + /// set if the animation is huge so that just always the next frame + /// is used instead of using timing + bool mbHugeSize; + + /// helper methods + bool isValidData() const + { + return (GraphicType::Bitmap == maGraphic.GetType() + && maGraphic.IsAnimated() + && maAnimation.Count()); + } + + void ensureVirtualDeviceSizeAndState() + { + if (!isValidData()) + return; + + const Size aCurrent(maVirtualDevice->GetOutputSizePixel()); + const Size aTarget(maAnimation.GetDisplaySizePixel()); + + if (aCurrent != aTarget) + { + maVirtualDevice->EnableMapMode(false); + maVirtualDeviceMask->EnableMapMode(false); + maVirtualDevice->SetOutputSizePixel(aTarget); + maVirtualDeviceMask->SetOutputSizePixel(aTarget); + } + + maVirtualDevice->Erase(); + maVirtualDeviceMask->Erase(); + const ::tools::Rectangle aRect(Point(0, 0), aTarget); + maVirtualDeviceMask->SetFillColor(COL_BLACK); + maVirtualDeviceMask->SetLineColor(); + maVirtualDeviceMask->DrawRect(aRect); + } + + sal_uInt32 generateStepTime(sal_uInt32 nIndex) const + { + const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(nIndex)); + sal_uInt32 nWaitTime(rAnimationBitmap.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) + { + // ATM the huge value would block the timer, so + // use a long time to show first page (whole day) + nWaitTime = 100 * 60 * 60 * 24; + } + + // Bad trap: There are animated gifs with no set WaitTime (!). + // In that case use a default value. + if (0 == nWaitTime) + { + nWaitTime = 100; + } + + return nWaitTime; + } + + void createAndSetAnimationTiming() + { + if (!isValidData()) + return; + + animation::AnimationEntryLoop aAnimationLoop(maAnimation.GetLoopCount() ? maAnimation.GetLoopCount() : 0xffff); + const sal_uInt32 nCount(maAnimation.Count()); + + for (sal_uInt32 a(0); a < nCount; a++) + { + const sal_uInt32 aStepTime(generateStepTime(a)); + const animation::AnimationEntryFixed aTime(static_cast<double>(aStepTime), static_cast<double>(a) / static_cast<double>(nCount)); + + aAnimationLoop.append(aTime); + } + + animation::AnimationEntryList aAnimationEntryList; + aAnimationEntryList.append(aAnimationLoop); + + setAnimationEntry(aAnimationEntryList); + } + + Primitive2DReference createFromBuffer() const + { + // create BitmapEx by extracting from VirtualDevices + const Bitmap aMainBitmap(maVirtualDevice->GetBitmap(Point(), maVirtualDevice->GetOutputSizePixel())); + bool useAlphaMask = false; +#if defined(MACOSX) || defined(IOS) + useAlphaMask = true; +#else + // GetBitmap()-> AlphaMask is optimized with SkiaSalBitmap::InterpretAs8Bit(), 1bpp mask is not. + if( SkiaHelper::isVCLSkiaEnabled()) + useAlphaMask = true; +#endif + BitmapEx bitmap; + if( useAlphaMask ) + { + const AlphaMask aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); + bitmap = BitmapEx(aMainBitmap, aMaskBitmap); + } + else + { + const Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); + bitmap = BitmapEx(aMainBitmap, aMaskBitmap); + } + + return Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(bitmap), + getTransform())); + } + + void checkSafeToBuffer(sal_uInt32 nIndex) + { + if (mbBufferingAllowed) + { + // all frames buffered + if (!maBufferedPrimitives.empty() && nIndex < maBufferedPrimitives.size()) + { + if (!maBufferedPrimitives[nIndex].is()) + { + maBufferedPrimitives[nIndex] = createFromBuffer(); + + // check if buffering is complete + bool bBufferingComplete(true); + + for (auto const & a: maBufferedPrimitives) + { + if (!a.is()) + { + bBufferingComplete = false; + break; + } + } + + if (bBufferingComplete) + { + maVirtualDevice.disposeAndClear(); + maVirtualDeviceMask.disposeAndClear(); + } + } + } + } + else + { + // always buffer first frame + if (0 == nIndex && !maBufferedFirstFrame.is()) + { + maBufferedFirstFrame = createFromBuffer(); + } + } + } + + void createFrame(sal_uInt32 nTarget) + { + // mnNextFrameToPrepare is the target frame to create next (which implies that + // mnNextFrameToPrepare-1 *is* currently in the VirtualDevice when + // 0 != mnNextFrameToPrepare. nTarget is the target frame. + if (!isValidData()) + return; + + if (mnNextFrameToPrepare > nTarget) + { + // we are ahead request, reset mechanism to start at frame zero + ensureVirtualDeviceSizeAndState(); + mnNextFrameToPrepare = 0; + } + + while (mnNextFrameToPrepare <= nTarget) + { + // prepare step + const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare)); + + switch (rAnimationBitmap.meDisposal) + { + case Disposal::Not: + { + maVirtualDevice->DrawBitmapEx(rAnimationBitmap.maPositionPixel, rAnimationBitmap.maBitmapEx); + Bitmap aAlphaMask = rAnimationBitmap.maBitmapEx.GetAlpha(); + + if (aAlphaMask.IsEmpty()) + { + const Point aEmpty; + const ::tools::Rectangle aRect(aEmpty, maVirtualDeviceMask->GetOutputSizePixel()); + const Wallpaper aWallpaper(COL_BLACK); + maVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper); + } + else + { + BitmapEx aExpandVisibilityMask(aAlphaMask, aAlphaMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + } + + break; + } + case Disposal::Back: + { + // #i70772# react on no mask, for primitives, too. + const Bitmap & rMask(rAnimationBitmap.maBitmapEx.GetAlpha()); + const Bitmap & rContent(rAnimationBitmap.maBitmapEx.GetBitmap()); + + maVirtualDeviceMask->Erase(); + maVirtualDevice->DrawBitmap(rAnimationBitmap.maPositionPixel, rContent); + + if (rMask.IsEmpty()) + { + const ::tools::Rectangle aRect(rAnimationBitmap.maPositionPixel, rContent.GetSizePixel()); + maVirtualDeviceMask->SetFillColor(COL_BLACK); + maVirtualDeviceMask->SetLineColor(); + maVirtualDeviceMask->DrawRect(aRect); + } + else + { + BitmapEx aExpandVisibilityMask(rMask, rMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.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); + break; + } + } + + // to not waste created data, check adding to buffers + checkSafeToBuffer(mnNextFrameToPrepare); + + mnNextFrameToPrepare++; + } + } + + Primitive2DReference tryTogetFromBuffer(sal_uInt32 nIndex) const + { + if (mbBufferingAllowed) + { + // all frames buffered, check if available + if (!maBufferedPrimitives.empty() && nIndex < maBufferedPrimitives.size()) + { + if (maBufferedPrimitives[nIndex].is()) + { + return maBufferedPrimitives[nIndex]; + } + } + } + else + { + // always buffer first frame, it's sometimes requested out-of-order + if (0 == nIndex && maBufferedFirstFrame.is()) + { + return maBufferedFirstFrame; + } + } + + return Primitive2DReference(); + } + + public: + /// constructor + AnimatedGraphicPrimitive2D( + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform); + + /// data read access + const basegfx::B2DHomMatrix& getTransform() const { return maTransform; } + + /// compare operator + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + + /// override to deliver the correct expected frame dependent of timing + virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override; + }; + + } + + AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D( + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform) + : AnimatedSwitchPrimitive2D( + animation::AnimationEntryList(), + Primitive2DContainer(), + false), + maTransform(rTransform), + maGraphic(rGraphic), + maAnimation(rGraphic.GetAnimation()), + maVirtualDevice(*Application::GetDefaultDevice()), + maVirtualDeviceMask(*Application::GetDefaultDevice()), + mnNextFrameToPrepare(SAL_MAX_UINT32), + mbBufferingAllowed(false), + mbHugeSize(false) + { + // initialize AnimationTiming, needed to detect which frame is requested + // in get2DDecomposition + createAndSetAnimationTiming(); + + // check if we allow buffering + if (isValidData()) + { + // allow buffering up to a size of: + // - 64 frames + // - sizes of 256x256 pixels + // This may be offered in option values if needed + static const sal_uInt64 nAllowedSize(64 * 256 * 256); + static const sal_uInt64 nHugeSize(10000000); + const Size aTarget(maAnimation.GetDisplaySizePixel()); + const sal_uInt64 nUsedSize(static_cast<sal_uInt64>(maAnimation.Count()) * aTarget.Width() * aTarget.Height()); + + if (nUsedSize < nAllowedSize) + { + mbBufferingAllowed = true; + } + + if (nUsedSize > nHugeSize) + { + mbHugeSize = true; + } + } + + // prepare buffer space + if (mbBufferingAllowed && isValidData()) + { + maBufferedPrimitives.resize(maAnimation.Count()); + } + } + + bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + // do not use 'GroupPrimitive2D::operator==' here, that would compare + // the children. Also do not use 'BasePrimitive2D::operator==', that would + // check the ID-Type. Since we are a simple derivation without own ID, + // use the dynamic_cast RTTI directly + const AnimatedGraphicPrimitive2D* pCompare = dynamic_cast<const AnimatedGraphicPrimitive2D*>(&rPrimitive); + + // use operator== of Graphic - if that is equal, the basic definition is equal + return (nullptr != pCompare + && getTransform() == pCompare->getTransform() + && maGraphic == pCompare->maGraphic); + } + + void AnimatedGraphicPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if (!isValidData()) + return; + + Primitive2DReference aRetval; + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + const sal_uInt32 nLen(maAnimation.Count()); + sal_uInt32 nIndex(basegfx::fround(fState * static_cast<double>(nLen))); + + // nIndex is the requested frame - it is in range [0..nLen[ + // create frame representation in VirtualDevices + if (nIndex >= nLen) + { + nIndex = nLen - 1; + } + + // check buffering shortcuts, may already be created + aRetval = tryTogetFromBuffer(nIndex); + + if (aRetval.is()) + { + rVisitor.visit(aRetval); + return; + } + + // if huge size (and not the buffered 1st frame) simply + // create next frame + if (mbHugeSize && 0 != nIndex && mnNextFrameToPrepare <= nIndex) + { + nIndex = mnNextFrameToPrepare % nLen; + } + + // frame not (yet) buffered or no buffering allowed, create it + const_cast<AnimatedGraphicPrimitive2D*>(this)->createFrame(nIndex); + + // try to get from buffer again, may have been added from createFrame + aRetval = tryTogetFromBuffer(nIndex); + + if (aRetval.is()) + { + rVisitor.visit(aRetval); + return; + } + + // did not work (not buffered and not 1st frame), create from buffer + aRetval = createFromBuffer(); + + rVisitor.visit(aRetval); + } + +} // end of namespace + +namespace drawinglayer::primitive2d +{ + void create2DDecompositionOfGraphic( + Primitive2DContainer& rContainer, + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform) + { + Primitive2DContainer aRetval; + + switch(rGraphic.GetType()) + { + case GraphicType::Bitmap : + { + if(rGraphic.IsAnimated()) + { + // prepare specialized AnimatedGraphicPrimitive2D + aRetval.resize(1); + aRetval[0] = new AnimatedGraphicPrimitive2D( + rGraphic, + 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)) + { + // translate back to origin, scale to unit coordinates + basegfx::B2DHomMatrix aEmbedVectorGraphic( + basegfx::utils::createTranslateB2DHomMatrix( + -rSvgRange.getMinX(), + -rSvgRange.getMinY())); + + aEmbedVectorGraphic.scale( + 1.0 / rSvgRange.getWidth(), + 1.0 / rSvgRange.getHeight()); + + // apply created object transformation + aEmbedVectorGraphic = rTransform * aEmbedVectorGraphic; + + // add Vector Graphic Data primitives embedded + aRetval.resize(1); + aRetval[0] = new TransformPrimitive2D( + aEmbedVectorGraphic, + Primitive2DContainer(rGraphic.getVectorGraphicData()->getPrimitive2DSequence())); + } + } + else + { + aRetval.resize(1); + aRetval[0] = new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(rGraphic.GetBitmapEx()), + rTransform); + } + + break; + } + + case GraphicType::GdiMetafile : + { + // create MetafilePrimitive2D + const GDIMetaFile& rMetafile = rGraphic.GetGDIMetaFile(); + + aRetval.resize(1); + aRetval[0] = new MetafilePrimitive2D( + rTransform, + 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 + // at them. This is an error, but we need to work around this + const Size aMetaFilePrefSize(rMetafile.GetPrefSize()); + const Size aMetaFileRealSize( + rMetafile.GetBoundRect( + *Application::GetDefaultDevice()).GetSize()); + + if(aMetaFileRealSize.getWidth() > aMetaFilePrefSize.getWidth() + || aMetaFileRealSize.getHeight() > aMetaFilePrefSize.getHeight()) + { + // clipping needed. Embed to MaskPrimitive2D. Create children and mask polygon + basegfx::B2DPolygon aMaskPolygon(basegfx::utils::createUnitPolygon()); + aMaskPolygon.transform(rTransform); + + Primitive2DReference mask = new MaskPrimitive2D( + basegfx::B2DPolyPolygon(aMaskPolygon), + std::move(aRetval)); + aRetval = Primitive2DContainer { mask }; + } + break; + } + + default: + { + // nothing to create + break; + } + } + + rContainer.append(std::move(aRetval)); + } + + Primitive2DContainer create2DColorModifierEmbeddingsAsNeeded( + Primitive2DContainer&& rChildren, + GraphicDrawMode aGraphicDrawMode, + double fLuminance, + double fContrast, + double fRed, + double fGreen, + double fBlue, + double fGamma, + bool bInvert) + { + Primitive2DContainer aRetval; + + if(rChildren.empty()) + { + // no child content, done + return aRetval; + } + + // set child content as retval; that is what will be used as child content in all + // embeddings from here + aRetval = std::move(rChildren); + + if(GraphicDrawMode::Watermark == aGraphicDrawMode) + { + // this is solved by applying fixed values additionally to luminance + // and contrast, do it here and reset DrawMode to GraphicDrawMode::Standard + // original in svtools uses: + // #define WATERMARK_LUM_OFFSET 50 + // #define WATERMARK_CON_OFFSET -70 + fLuminance = std::clamp(fLuminance + 0.5, -1.0, 1.0); + fContrast = std::clamp(fContrast - 0.7, -1.0, 1.0); + aGraphicDrawMode = GraphicDrawMode::Standard; + } + + // DrawMode (GraphicDrawMode::Watermark already handled) + switch(aGraphicDrawMode) + { + case GraphicDrawMode::Greys: + { + // convert to grey + const Primitive2DReference aPrimitiveGrey( + new ModifiedColorPrimitive2D( + std::move(aRetval), + std::make_shared<basegfx::BColorModifier_gray>())); + + aRetval = Primitive2DContainer { aPrimitiveGrey }; + break; + } + case GraphicDrawMode::Mono: + { + // convert to mono (black/white with threshold 0.5) + const Primitive2DReference aPrimitiveBlackAndWhite( + new ModifiedColorPrimitive2D( + std::move(aRetval), + std::make_shared<basegfx::BColorModifier_black_and_white>(0.5))); + + aRetval = Primitive2DContainer { aPrimitiveBlackAndWhite }; + break; + } + default: // case GraphicDrawMode::Standard: + { + assert( + aGraphicDrawMode != GraphicDrawMode::Watermark + && "OOps, GraphicDrawMode::Watermark should already be handled (see above)"); + // nothing to do + break; + } + } + + // mnContPercent, mnLumPercent, mnRPercent, mnGPercent, mnBPercent + // handled in a single call + if(!basegfx::fTools::equalZero(fLuminance) + || !basegfx::fTools::equalZero(fContrast) + || !basegfx::fTools::equalZero(fRed) + || !basegfx::fTools::equalZero(fGreen) + || !basegfx::fTools::equalZero(fBlue)) + { + const Primitive2DReference aPrimitiveRGBLuminannceContrast( + new ModifiedColorPrimitive2D( + std::move(aRetval), + std::make_shared<basegfx::BColorModifier_RGBLuminanceContrast>( + fRed, + fGreen, + fBlue, + fLuminance, + fContrast))); + + aRetval = Primitive2DContainer { aPrimitiveRGBLuminannceContrast }; + } + + // gamma (boolean) + if(!basegfx::fTools::equal(fGamma, 1.0)) + { + const Primitive2DReference aPrimitiveGamma( + new ModifiedColorPrimitive2D( + std::move(aRetval), + std::make_shared<basegfx::BColorModifier_gamma>( + fGamma))); + + aRetval = Primitive2DContainer { aPrimitiveGamma }; + } + + // invert (boolean) + if(bInvert) + { + const Primitive2DReference aPrimitiveInvert( + new ModifiedColorPrimitive2D( + std::move(aRetval), + std::make_shared<basegfx::BColorModifier_invert>())); + + aRetval = Primitive2DContainer { aPrimitiveInvert }; + } + + return aRetval; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/gridprimitive2d.cxx b/drawinglayer/source/primitive2d/gridprimitive2d.cxx new file mode 100644 index 000000000..ddb468e9a --- /dev/null +++ b/drawinglayer/source/primitive2d/gridprimitive2d.cxx @@ -0,0 +1,336 @@ +/* -*- 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/gridprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void GridPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + if(!(!rViewInformation.getViewport().isEmpty() && getWidth() > 0.0 && getHeight() > 0.0)) + return; + + // decompose grid matrix to get logic size + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // create grid matrix which transforms from scaled logic to view + basegfx::B2DHomMatrix aRST(basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + aRST *= rViewInformation.getObjectToViewTransformation(); + + // get step widths + double fStepX(getWidth()); + double fStepY(getHeight()); + const double fMinimalStep(10.0); + + // guarantee a step width of 10.0 + if(basegfx::fTools::less(fStepX, fMinimalStep)) + { + fStepX = fMinimalStep; + } + + if(basegfx::fTools::less(fStepY, fMinimalStep)) + { + fStepY = fMinimalStep; + } + + // get relative distances in view coordinates + double fViewStepX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fStepX, 0.0)).getLength()); + double fViewStepY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fStepY)).getLength()); + double fSmallStepX(1.0), fViewSmallStepX(1.0), fSmallStepY(1.0), fViewSmallStepY(1.0); + sal_uInt32 nSmallStepsX(0), nSmallStepsY(0); + + // setup subdivisions + if(getSubdivisionsX()) + { + fSmallStepX = fStepX / getSubdivisionsX(); + fViewSmallStepX = fViewStepX / getSubdivisionsX(); + } + + if(getSubdivisionsY()) + { + fSmallStepY = fStepY / getSubdivisionsY(); + fViewSmallStepY = fViewStepY / getSubdivisionsY(); + } + + // correct step width + while(fViewStepX < getSmallestViewDistance()) + { + fViewStepX *= 2.0; + fStepX *= 2.0; + } + + while(fViewStepY < getSmallestViewDistance()) + { + fViewStepY *= 2.0; + fStepY *= 2.0; + } + + // correct small step width + if(getSubdivisionsX()) + { + while(fViewSmallStepX < getSmallestSubdivisionViewDistance()) + { + fViewSmallStepX *= 2.0; + fSmallStepX *= 2.0; + } + + nSmallStepsX = static_cast<sal_uInt32>(fStepX / fSmallStepX); + } + + if(getSubdivisionsY()) + { + while(fViewSmallStepY < getSmallestSubdivisionViewDistance()) + { + fViewSmallStepY *= 2.0; + fSmallStepY *= 2.0; + } + + nSmallStepsY = static_cast<sal_uInt32>(fStepY / fSmallStepY); + } + + // calculate extended viewport in which grid points may lie at all + basegfx::B2DRange aExtendedViewport; + + if(rViewInformation.getDiscreteViewport().isEmpty()) + { + // not set, use logic size to travel over all potential grid points + aExtendedViewport = basegfx::B2DRange(0.0, 0.0, aScale.getX(), aScale.getY()); + } + else + { + // transform unit range to discrete view + aExtendedViewport = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0); + basegfx::B2DHomMatrix aTrans(rViewInformation.getObjectToViewTransformation() * getTransform()); + aExtendedViewport.transform(aTrans); + + // intersect with visible part + aExtendedViewport.intersect(rViewInformation.getDiscreteViewport()); + + if(!aExtendedViewport.isEmpty()) + { + // convert back and apply scale + aTrans.invert(); + aTrans.scale(aScale.getX(), aScale.getY()); + aExtendedViewport.transform(aTrans); + + // crop start/end in X/Y to multiples of logical step width + const double fHalfCrossSize((rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(3.0, 0.0)).getLength()); + const double fMinX(floor((aExtendedViewport.getMinX() - fHalfCrossSize) / fStepX) * fStepX); + const double fMaxX(ceil((aExtendedViewport.getMaxX() + fHalfCrossSize) / fStepX) * fStepX); + const double fMinY(floor((aExtendedViewport.getMinY() - fHalfCrossSize) / fStepY) * fStepY); + const double fMaxY(ceil((aExtendedViewport.getMaxY() + fHalfCrossSize) / fStepY) * fStepY); + + // put to aExtendedViewport and crop on object logic size + aExtendedViewport = basegfx::B2DRange( + std::max(fMinX, 0.0), + std::max(fMinY, 0.0), + std::min(fMaxX, aScale.getX()), + std::min(fMaxY, aScale.getY())); + } + } + + if(aExtendedViewport.isEmpty()) + return; + + // prepare point vectors for point and cross markers + std::vector< basegfx::B2DPoint > aPositionsPoint; + std::vector< basegfx::B2DPoint > aPositionsCross; + + for(double fX(aExtendedViewport.getMinX()); fX < aExtendedViewport.getMaxX(); fX += fStepX) + { + const bool bXZero(basegfx::fTools::equalZero(fX)); + + for(double fY(aExtendedViewport.getMinY()); fY < aExtendedViewport.getMaxY(); fY += fStepY) + { + const bool bYZero(basegfx::fTools::equalZero(fY)); + + if(!bXZero && !bYZero) + { + // get discrete position and test against 3x3 area surrounding it + // since it's a cross + const double fHalfCrossSize(3.0 * 0.5); + const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fX, fY)); + const basegfx::B2DRange aDiscreteRangeCross( + aViewPos.getX() - fHalfCrossSize, aViewPos.getY() - fHalfCrossSize, + aViewPos.getX() + fHalfCrossSize, aViewPos.getY() + fHalfCrossSize); + + if(rViewInformation.getDiscreteViewport().overlaps(aDiscreteRangeCross)) + { + const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos); + aPositionsCross.push_back(aLogicPos); + } + } + + if(getSubdivisionsX() && !bYZero) + { + double fF(fX + fSmallStepX); + + for(sal_uInt32 a(1); a < nSmallStepsX && fF < aExtendedViewport.getMaxX(); a++, fF += fSmallStepX) + { + const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fF, fY)); + + if(rViewInformation.getDiscreteViewport().isInside(aViewPos)) + { + const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos); + aPositionsPoint.push_back(aLogicPos); + } + } + } + + if(getSubdivisionsY() && !bXZero) + { + double fF(fY + fSmallStepY); + + for(sal_uInt32 a(1); a < nSmallStepsY && fF < aExtendedViewport.getMaxY(); a++, fF += fSmallStepY) + { + const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fX, fF)); + + if(rViewInformation.getDiscreteViewport().isInside(aViewPos)) + { + const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos); + aPositionsPoint.push_back(aLogicPos); + } + } + } + } + } + + // prepare return value + const sal_uInt32 nCountPoint(aPositionsPoint.size()); + const sal_uInt32 nCountCross(aPositionsCross.size()); + + // add PointArrayPrimitive2D if point markers were added + if(nCountPoint) + { + rContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsPoint), getBColor())); + } + + // add MarkerArrayPrimitive2D if cross markers were added + if(!nCountCross) + return; + + 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())); + } + else + { + rContainer.push_back(new MarkerArrayPrimitive2D(std::move(aPositionsCross), getCrossMarker())); + } + } + + GridPrimitive2D::GridPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + double fWidth, + double fHeight, + double fSmallestViewDistance, + double fSmallestSubdivisionViewDistance, + sal_uInt32 nSubdivisionsX, + sal_uInt32 nSubdivisionsY, + const basegfx::BColor& rBColor, + const BitmapEx& rCrossMarker) + : maTransform(rTransform), + mfWidth(fWidth), + mfHeight(fHeight), + mfSmallestViewDistance(fSmallestViewDistance), + mfSmallestSubdivisionViewDistance(fSmallestSubdivisionViewDistance), + mnSubdivisionsX(nSubdivisionsX), + mnSubdivisionsY(nSubdivisionsY), + maBColor(rBColor), + maCrossMarker(rCrossMarker) + { + } + + bool GridPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const GridPrimitive2D& rCompare = static_cast<const GridPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getWidth() == rCompare.getWidth() + && getHeight() == rCompare.getHeight() + && getSmallestViewDistance() == rCompare.getSmallestViewDistance() + && getSmallestSubdivisionViewDistance() == rCompare.getSmallestSubdivisionViewDistance() + && getSubdivisionsX() == rCompare.getSubdivisionsX() + && getSubdivisionsY() == rCompare.getSubdivisionsY() + && getBColor() == rCompare.getBColor() + && getCrossMarker() == rCompare.getCrossMarker()); + } + + return false; + } + + basegfx::B2DRange GridPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // get object's range + basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + aUnitRange.transform(getTransform()); + + // intersect with visible part + aUnitRange.intersect(rViewInformation.getViewport()); + + return aUnitRange; + } + + void GridPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(!getBuffered2DDecomposition().empty()) + { + if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< GridPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange and ViewTransformation + const_cast< GridPrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); + const_cast< GridPrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + sal_uInt32 GridPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_GRIDPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/groupprimitive2d.cxx b/drawinglayer/source/primitive2d/groupprimitive2d.cxx new file mode 100644 index 000000000..7a39bde2c --- /dev/null +++ b/drawinglayer/source/primitive2d/groupprimitive2d.cxx @@ -0,0 +1,75 @@ +/* -*- 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/groupprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + GroupPrimitive2D::GroupPrimitive2D( Primitive2DContainer&& aChildren ) + : maChildren(std::move(aChildren)) + { + } + + /** The compare opertator uses the Sequence::==operator, so only checking if + the references are equal. All non-equal references are interpreted as + non-equal. + */ + bool GroupPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const GroupPrimitive2D& rCompare = static_cast< const GroupPrimitive2D& >(rPrimitive); + + return getChildren() == rCompare.getChildren(); + } + + return false; + } + + /// default: just return children, so all renderers not supporting group will use its content + void GroupPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + getChildren(rVisitor); + } + + sal_Int64 GroupPrimitive2D::estimateUsage() + { + size_t nRet(0); + for (auto& it : getChildren()) + { + if (it) + nRet += it->estimateUsage(); + } + return nRet; + } + + // provide unique ID + sal_uInt32 GroupPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_GROUPPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/helplineprimitive2d.cxx b/drawinglayer/source/primitive2d/helplineprimitive2d.cxx new file mode 100644 index 000000000..07d6a7f22 --- /dev/null +++ b/drawinglayer/source/primitive2d/helplineprimitive2d.cxx @@ -0,0 +1,186 @@ +/* -*- 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/helplineprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void HelplinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + if(rViewInformation.getViewport().isEmpty() || getDirection().equalZero()) + return; + + // position to view coordinates, DashLen and DashLen in logic + const basegfx::B2DPoint aViewPosition(rViewInformation.getObjectToViewTransformation() * getPosition()); + + switch(getStyle()) + { + default : // HelplineStyle2D::Point + { + const double fViewFixValue(15.0); + basegfx::B2DVector aNormalizedDirection(getDirection()); + aNormalizedDirection.normalize(); + aNormalizedDirection *= fViewFixValue; + const basegfx::B2DPoint aStartA(aViewPosition - aNormalizedDirection); + const basegfx::B2DPoint aEndA(aViewPosition + aNormalizedDirection); + basegfx::B2DPolygon aLineA; + aLineA.append(aStartA); + aLineA.append(aEndA); + aLineA.transform(rViewInformation.getInverseObjectToViewTransformation()); + rContainer.push_back(new PolygonMarkerPrimitive2D(aLineA, getRGBColA(), getRGBColB(), getDiscreteDashLength())); + + const basegfx::B2DVector aPerpendicularNormalizedDirection(basegfx::getPerpendicular(aNormalizedDirection)); + const basegfx::B2DPoint aStartB(aViewPosition - aPerpendicularNormalizedDirection); + const basegfx::B2DPoint aEndB(aViewPosition + aPerpendicularNormalizedDirection); + basegfx::B2DPolygon aLineB; + aLineB.append(aStartB); + aLineB.append(aEndB); + aLineB.transform(rViewInformation.getInverseObjectToViewTransformation()); + rContainer.push_back(new PolygonMarkerPrimitive2D(aLineB, getRGBColA(), getRGBColB(), getDiscreteDashLength())); + + break; + } + case HelplineStyle2D::Line : + { + basegfx::B2DPolygon aLine; + + if(basegfx::areParallel(getDirection(), basegfx::B2DVector(1.0, 0.0))) + { + // parallel to X-Axis, get cuts with Y-Axes + const double fCutA((rViewInformation.getDiscreteViewport().getMinX() - aViewPosition.getX()) / getDirection().getX()); + const double fCutB((rViewInformation.getDiscreteViewport().getMaxX() - aViewPosition.getX()) / getDirection().getX()); + const basegfx::B2DPoint aPosA(aViewPosition + (fCutA * getDirection())); + const basegfx::B2DPoint aPosB(aViewPosition + (fCutB * getDirection())); + const bool bBothLeft(aPosA.getX() < rViewInformation.getDiscreteViewport().getMinX() && aPosB.getX() < rViewInformation.getDiscreteViewport().getMinX()); + const bool bBothRight(aPosA.getX() > rViewInformation.getDiscreteViewport().getMaxX() && aPosB.getX() < rViewInformation.getDiscreteViewport().getMaxX()); + + if(!bBothLeft && !bBothRight) + { + aLine.append(aPosA); + aLine.append(aPosB); + } + } + else + { + // get cuts with X-Axes + const double fCutA((rViewInformation.getDiscreteViewport().getMinY() - aViewPosition.getY()) / getDirection().getY()); + const double fCutB((rViewInformation.getDiscreteViewport().getMaxY() - aViewPosition.getY()) / getDirection().getY()); + const basegfx::B2DPoint aPosA(aViewPosition + (fCutA * getDirection())); + const basegfx::B2DPoint aPosB(aViewPosition + (fCutB * getDirection())); + const bool bBothAbove(aPosA.getY() < rViewInformation.getDiscreteViewport().getMinY() && aPosB.getY() < rViewInformation.getDiscreteViewport().getMinY()); + const bool bBothBelow(aPosA.getY() > rViewInformation.getDiscreteViewport().getMaxY() && aPosB.getY() < rViewInformation.getDiscreteViewport().getMaxY()); + + if(!bBothAbove && !bBothBelow) + { + aLine.append(aPosA); + aLine.append(aPosB); + } + } + + if(aLine.count()) + { + // clip against visible area + const basegfx::B2DPolyPolygon aResult(basegfx::utils::clipPolygonOnRange(aLine, rViewInformation.getDiscreteViewport(), true, true)); + + 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())); + } + } + + break; + } + } + } + + HelplinePrimitive2D::HelplinePrimitive2D( + const basegfx::B2DPoint& rPosition, + const basegfx::B2DVector& rDirection, + HelplineStyle2D eStyle, + const basegfx::BColor& rRGBColA, + const basegfx::BColor& rRGBColB, + double fDiscreteDashLength) + : maPosition(rPosition), + maDirection(rDirection), + meStyle(eStyle), + maRGBColA(rRGBColA), + maRGBColB(rRGBColB), + mfDiscreteDashLength(fDiscreteDashLength) + { + } + + bool HelplinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const HelplinePrimitive2D& rCompare = static_cast<const HelplinePrimitive2D&>(rPrimitive); + + return (getPosition() == rCompare.getPosition() + && getDirection() == rCompare.getDirection() + && getStyle() == rCompare.getStyle() + && getRGBColA() == rCompare.getRGBColA() + && getRGBColB() == rCompare.getRGBColB() + && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); + } + + return false; + } + + void HelplinePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(!getBuffered2DDecomposition().empty()) + { + if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< HelplinePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange and ViewTransformation + const_cast< HelplinePrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); + const_cast< HelplinePrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + sal_uInt32 HelplinePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/hiddengeometryprimitive2d.cxx b/drawinglayer/source/primitive2d/hiddengeometryprimitive2d.cxx new file mode 100644 index 000000000..c1a5442b2 --- /dev/null +++ b/drawinglayer/source/primitive2d/hiddengeometryprimitive2d.cxx @@ -0,0 +1,52 @@ +/* -*- 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/hiddengeometryprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + HiddenGeometryPrimitive2D::HiddenGeometryPrimitive2D( + Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + basegfx::B2DRange HiddenGeometryPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + return getChildren().getB2DRange(rViewInformation); + } + + void HiddenGeometryPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& /*rVisitor*/, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + } + + // provide unique ID + sal_uInt32 HiddenGeometryPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/invertprimitive2d.cxx b/drawinglayer/source/primitive2d/invertprimitive2d.cxx new file mode 100644 index 000000000..e2d01381e --- /dev/null +++ b/drawinglayer/source/primitive2d/invertprimitive2d.cxx @@ -0,0 +1,43 @@ +/* -*- 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/invertprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + InvertPrimitive2D::InvertPrimitive2D( + Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + // provide unique ID + sal_uInt32 InvertPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_INVERTPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx b/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx new file mode 100644 index 000000000..cdaa15e53 --- /dev/null +++ b/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx @@ -0,0 +1,136 @@ +/* -*- 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/markerarrayprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void MarkerArrayPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + const std::vector< basegfx::B2DPoint >& rPositions = getPositions(); + const sal_uInt32 nMarkerCount(rPositions.size()); + + if(!nMarkerCount || getMarker().IsEmpty()) + return; + + // get pixel size + Size aBitmapSize(getMarker().GetSizePixel()); + + if(!(aBitmapSize.Width() && aBitmapSize.Height())) + return; + + // get logic half pixel size + basegfx::B2DVector aLogicHalfSize(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(aBitmapSize.getWidth() - 1.0, aBitmapSize.getHeight() - 1.0)); + + // use half size for expand + aLogicHalfSize *= 0.5; + + for(const auto& rPosition : rPositions) + { + const basegfx::B2DRange aRange(rPosition - aLogicHalfSize, rPosition + aLogicHalfSize); + basegfx::B2DHomMatrix aTransform; + + aTransform.set(0, 0, aRange.getWidth()); + aTransform.set(1, 1, aRange.getHeight()); + aTransform.set(0, 2, aRange.getMinX()); + aTransform.set(1, 2, aRange.getMinY()); + + rContainer.push_back( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getMarker()), + aTransform)); + } + } + + MarkerArrayPrimitive2D::MarkerArrayPrimitive2D( + std::vector< basegfx::B2DPoint >&& rPositions, + const BitmapEx& rMarker) + : maPositions(std::move(rPositions)), + maMarker(rMarker) + { + } + + bool MarkerArrayPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const MarkerArrayPrimitive2D& rCompare = static_cast<const MarkerArrayPrimitive2D&>(rPrimitive); + + return (getPositions() == rCompare.getPositions() + && getMarker() == rCompare.getMarker()); + } + + return false; + } + + basegfx::B2DRange MarkerArrayPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval; + + if(!getPositions().empty()) + { + // get the basic range from the position vector + for (auto const& pos : getPositions()) + { + aRetval.expand(pos); + } + + if(!getMarker().IsEmpty()) + { + // get pixel size + const Size aBitmapSize(getMarker().GetSizePixel()); + + if(aBitmapSize.Width() && aBitmapSize.Height()) + { + // get logic half size + basegfx::B2DVector aLogicHalfSize(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(aBitmapSize.getWidth(), aBitmapSize.getHeight())); + + // use half size for expand + aLogicHalfSize *= 0.5; + + // apply aLogicHalfSize + aRetval.expand(aRetval.getMinimum() - aLogicHalfSize); + aRetval.expand(aRetval.getMaximum() + aLogicHalfSize); + } + } + } + + return aRetval; + } + + // provide unique ID + sal_uInt32 MarkerArrayPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/maskprimitive2d.cxx b/drawinglayer/source/primitive2d/maskprimitive2d.cxx new file mode 100644 index 000000000..842085168 --- /dev/null +++ b/drawinglayer/source/primitive2d/maskprimitive2d.cxx @@ -0,0 +1,62 @@ +/* -*- 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/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + MaskPrimitive2D::MaskPrimitive2D( + const basegfx::B2DPolyPolygon& rMask, + Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)), + maMask(rMask) + { + } + + bool MaskPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const MaskPrimitive2D& rCompare = static_cast< const MaskPrimitive2D& >(rPrimitive); + + return (getMask() == rCompare.getMask()); + } + + return false; + } + + basegfx::B2DRange MaskPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + return getMask().getB2DRange(); + } + + // provide unique ID + sal_uInt32 MaskPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_MASKPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/mediaprimitive2d.cxx b/drawinglayer/source/primitive2d/mediaprimitive2d.cxx new file mode 100644 index 000000000..e097d2d98 --- /dev/null +++ b/drawinglayer/source/primitive2d/mediaprimitive2d.cxx @@ -0,0 +1,148 @@ +/* -*- 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/mediaprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <vcl/GraphicObject.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void MediaPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer xRetval; + xRetval.resize(1); + + // create background object + basegfx::B2DPolygon aBackgroundPolygon(basegfx::utils::createUnitPolygon()); + aBackgroundPolygon.transform(getTransform()); + const Primitive2DReference xRefBackground( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aBackgroundPolygon), + getBackgroundColor())); + xRetval[0] = xRefBackground; + + if(GraphicType::Bitmap == maSnapshot.GetType() || GraphicType::GdiMetafile == maSnapshot.GetType()) + { + const GraphicObject aGraphicObject(maSnapshot); + const GraphicAttr aGraphicAttr; + xRetval.resize(2); + xRetval[1] = new GraphicPrimitive2D(getTransform(), aGraphicObject, aGraphicAttr); + } + + if(getDiscreteBorder()) + { + const basegfx::B2DVector aDiscreteInLogic(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(static_cast<double>(getDiscreteBorder()), static_cast<double>(getDiscreteBorder()))); + const double fDiscreteSize(aDiscreteInLogic.getX() + aDiscreteInLogic.getY()); + + basegfx::B2DRange aSourceRange(0.0, 0.0, 1.0, 1.0); + aSourceRange.transform(getTransform()); + + basegfx::B2DRange aDestRange(aSourceRange); + aDestRange.grow(-0.5 * fDiscreteSize); + + if(basegfx::fTools::equalZero(aDestRange.getWidth()) || basegfx::fTools::equalZero(aDestRange.getHeight())) + { + // shrunk primitive has no content (zero size in X or Y), nothing to display. Still create + // invisible content for HitTest and BoundRect + const Primitive2DReference xHiddenLines(new HiddenGeometryPrimitive2D(std::move(xRetval))); + + xRetval = Primitive2DContainer { xHiddenLines, }; + } + else + { + // create transformation matrix from original range to shrunk range + basegfx::B2DHomMatrix aTransform; + aTransform.translate(-aSourceRange.getMinX(), -aSourceRange.getMinY()); + aTransform.scale(aDestRange.getWidth() / aSourceRange.getWidth(), aDestRange.getHeight() / aSourceRange.getHeight()); + aTransform.translate(aDestRange.getMinX(), aDestRange.getMinY()); + + // add transform primitive + Primitive2DReference aScaled(new TransformPrimitive2D(aTransform, std::move(xRetval))); + xRetval = Primitive2DContainer { aScaled }; + } + } + + rContainer.append(std::move(xRetval)); + } + + MediaPrimitive2D::MediaPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const OUString& rURL, + const basegfx::BColor& rBackgroundColor, + sal_uInt32 nDiscreteBorder, + const Graphic &rSnapshot) + : maTransform(rTransform), + maURL(rURL), + maBackgroundColor(rBackgroundColor), + mnDiscreteBorder(nDiscreteBorder), + maSnapshot(rSnapshot) + { + } + + bool MediaPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const MediaPrimitive2D& rCompare = static_cast<const MediaPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && maURL == rCompare.maURL + && getBackgroundColor() == rCompare.getBackgroundColor() + && getDiscreteBorder() == rCompare.getDiscreteBorder() + && maSnapshot.IsNone() == rCompare.maSnapshot.IsNone()); + } + + return false; + } + + basegfx::B2DRange MediaPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + + if(getDiscreteBorder()) + { + const basegfx::B2DVector aDiscreteInLogic(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(static_cast<double>(getDiscreteBorder()), static_cast<double>(getDiscreteBorder()))); + const double fDiscreteSize(aDiscreteInLogic.getX() + aDiscreteInLogic.getY()); + + aRetval.grow(-0.5 * fDiscreteSize); + } + + return aRetval; + } + + // provide unique ID + sal_uInt32 MediaPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_MEDIAPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx new file mode 100644 index 000000000..608be0bbc --- /dev/null +++ b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx @@ -0,0 +1,135 @@ +/* -*- 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/metafileprimitive2d.hxx> +#include <wmfemfhelper.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <vcl/canvastools.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + void MetafilePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, 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()) + { + // 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()) + { + 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 }; + } + } + + // 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; + + // embed to target transformation + const Primitive2DReference aEmbeddedTransform( + new TransformPrimitive2D( + aAdaptedTransform, + std::move(xRetval))); + + xRetval = Primitive2DContainer { aEmbeddedTransform }; + } + + rContainer.append(std::move(xRetval)); + } + + MetafilePrimitive2D::MetafilePrimitive2D( + const basegfx::B2DHomMatrix& rMetaFileTransform, + const GDIMetaFile& rMetaFile) + : maMetaFileTransform(rMetaFileTransform), + maMetaFile(rMetaFile) + { + } + + bool MetafilePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const MetafilePrimitive2D& rCompare = static_cast<const MetafilePrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getMetaFile() == rCompare.getMetaFile()); + } + + return false; + } + + basegfx::B2DRange MetafilePrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // use own implementation to quickly answer the getB2DRange question. The + // MetafilePrimitive2D assumes that all geometry is inside of the shape. If + // this is not the case (i have already seen some wrong Metafiles) it should + // be embedded to a MaskPrimitive2D + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + + return aRetval; + } + + // from MetafileAccessor + void MetafilePrimitive2D::accessMetafile(GDIMetaFile& rTargetMetafile) const + { + rTargetMetafile = maMetaFile; + } + + // provide unique ID + sal_uInt32 MetafilePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_METAFILEPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx b/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx new file mode 100644 index 000000000..2d0f572da --- /dev/null +++ b/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx @@ -0,0 +1,67 @@ +/* -*- 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/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + ModifiedColorPrimitive2D::ModifiedColorPrimitive2D( + Primitive2DContainer&& aChildren, + const basegfx::BColorModifierSharedPtr& rColorModifier) + : GroupPrimitive2D(std::move(aChildren)), + maColorModifier(rColorModifier) + { + } + + bool ModifiedColorPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const ModifiedColorPrimitive2D& rCompare = static_cast<const ModifiedColorPrimitive2D&>(rPrimitive); + + if(getColorModifier().get() == rCompare.getColorModifier().get()) + { + return true; + } + + if(!getColorModifier() || !rCompare.getColorModifier()) + { + return false; + } + + return *getColorModifier()== *rCompare.getColorModifier(); + } + + return false; + } + + // provide unique ID + sal_uInt32 ModifiedColorPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx b/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx new file mode 100644 index 000000000..577f3171a --- /dev/null +++ b/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx @@ -0,0 +1,60 @@ +/* -*- 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/objectinfoprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + ObjectInfoPrimitive2D::ObjectInfoPrimitive2D( + Primitive2DContainer&& aChildren, + const OUString& rName, + const OUString& rTitle, + const OUString& rDesc) + : GroupPrimitive2D(std::move(aChildren)), + maName(rName), + maTitle(rTitle), + maDesc(rDesc) + { + } + + bool ObjectInfoPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const ObjectInfoPrimitive2D& rCompare = static_cast<const ObjectInfoPrimitive2D&>(rPrimitive); + + return (getName() == rCompare.getName() + && getTitle() == rCompare.getTitle() + && getDesc() == rCompare.getDesc()); + } + + return false; + } + + // provide unique ID + sal_uInt32 ObjectInfoPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/pagehierarchyprimitive2d.cxx b/drawinglayer/source/primitive2d/pagehierarchyprimitive2d.cxx new file mode 100644 index 000000000..8ffd7735a --- /dev/null +++ b/drawinglayer/source/primitive2d/pagehierarchyprimitive2d.cxx @@ -0,0 +1,40 @@ +/* -*- 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/pagehierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + PageHierarchyPrimitive2D::PageHierarchyPrimitive2D(Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + // provide unique ID + sal_uInt32 PageHierarchyPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_PAGEHIERARCHYPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx b/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx new file mode 100644 index 000000000..7aa1a4405 --- /dev/null +++ b/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx @@ -0,0 +1,150 @@ +/* -*- 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/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void PagePreviewPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer aContent(getPageContent()); + + if(!(!aContent.empty() + && basegfx::fTools::more(getContentWidth(), 0.0) + && basegfx::fTools::more(getContentHeight(), 0.0))) + return; + + // 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; + + // check if content overlaps with target size and needs to be embedded with a + // clipping primitive + const basegfx::B2DRange aRealContentRange(aContent.getB2DRange(rViewInformation)); + const basegfx::B2DRange aAllowedContentRange(0.0, 0.0, getContentWidth(), getContentHeight()); + + if(!aAllowedContentRange.isInside(aRealContentRange)) + { + const Primitive2DReference xReferenceA( + new MaskPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(aAllowedContentRange)), std::move(aContent))); + aContent = Primitive2DContainer { xReferenceA }; + } + + // create a mapping from content to object. + basegfx::B2DHomMatrix aPageTrans; + + // #i101075# when keeping the aspect ratio is wanted, it is necessary to calculate + // an equidistant scaling in X and Y and a corresponding translation to + // center the output. Calculate needed scale factors + const double fScaleX(aScale.getX() / getContentWidth()); + const double fScaleY(aScale.getY() / getContentHeight()); + + // to keep the aspect, use the smaller scale and adapt missing size by translation + if(fScaleX < fScaleY) + { + // height needs to be adapted + const double fNeededHeight(aScale.getY() / fScaleX); + const double fSpaceToAdd(fNeededHeight - getContentHeight()); + + aPageTrans.translate(0.0, fSpaceToAdd * 0.5); + aPageTrans.scale(fScaleX, aScale.getY() / fNeededHeight); + } + else + { + // width needs to be adapted + const double fNeededWidth(aScale.getX() / fScaleY); + const double fSpaceToAdd(fNeededWidth - getContentWidth()); + + aPageTrans.translate(fSpaceToAdd * 0.5, 0.0); + aPageTrans.scale(aScale.getX() / fNeededWidth, fScaleY); + } + + // add the missing object transformation aspects + const basegfx::B2DHomMatrix aCombined(basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + aPageTrans = aCombined * aPageTrans; + + // embed in necessary transformation to map from SdrPage to SdrPageObject + rContainer.push_back(new TransformPrimitive2D(aPageTrans, std::move(aContent))); + } + + PagePreviewPrimitive2D::PagePreviewPrimitive2D( + const css::uno::Reference< css::drawing::XDrawPage >& rxDrawPage, + const basegfx::B2DHomMatrix& rTransform, + double fContentWidth, + double fContentHeight, + Primitive2DContainer&& rPageContent) + : mxDrawPage(rxDrawPage), + maPageContent(std::move(rPageContent)), + maTransform(rTransform), + mfContentWidth(fContentWidth), + mfContentHeight(fContentHeight) + { + } + + bool PagePreviewPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const PagePreviewPrimitive2D& rCompare = static_cast< const PagePreviewPrimitive2D& >(rPrimitive); + + return (getXDrawPage() == rCompare.getXDrawPage() + && getPageContent() == rCompare.getPageContent() + && getTransform() == rCompare.getTransform() + && getContentWidth() == rCompare.getContentWidth() + && getContentHeight() == rCompare.getContentHeight()); + } + + return false; + } + + basegfx::B2DRange PagePreviewPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation`*/) const + { + // nothing is allowed to stick out of a PagePreviewPrimitive, thus we + // can quickly deliver our range here + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + return aRetval; + } + + // provide unique ID + sal_uInt32 PagePreviewPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx new file mode 100644 index 000000000..b667545b5 --- /dev/null +++ b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx @@ -0,0 +1,370 @@ +/* -*- 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/patternfillprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <texture/texture.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <drawinglayer/converters.hxx> + +using namespace com::sun::star; + +#define MAXIMUM_SQUARE_LENGTH (186.0) +#define MINIMUM_SQUARE_LENGTH (16.0) +#define MINIMUM_TILES_LENGTH (3) + +namespace drawinglayer::primitive2d +{ + void PatternFillPrimitive2D::calculateNeededDiscreteBufferSize( + sal_uInt32& rWidth, + sal_uInt32& rHeight, + const geometry::ViewInformation2D& rViewInformation) const + { + // reset parameters + rWidth = rHeight = 0; + + // check if resolution is in the range which may be buffered + const basegfx::B2DPolyPolygon& rMaskPolygon = getMask(); + const basegfx::B2DRange aMaskRange(rMaskPolygon.getB2DRange()); + + // get discrete rounded up square size of a single tile + const basegfx::B2DHomMatrix aMaskRangeTransformation( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aMaskRange.getRange(), + aMaskRange.getMinimum())); + const basegfx::B2DHomMatrix aTransform( + rViewInformation.getObjectToViewTransformation() * aMaskRangeTransformation); + const basegfx::B2DPoint aTopLeft(aTransform * getReferenceRange().getMinimum()); + const basegfx::B2DPoint aX(aTransform * basegfx::B2DPoint(getReferenceRange().getMaxX(), getReferenceRange().getMinY())); + const basegfx::B2DPoint aY(aTransform * basegfx::B2DPoint(getReferenceRange().getMinX(), getReferenceRange().getMaxY())); + const double fW(basegfx::B2DVector(aX - aTopLeft).getLength()); + const double fH(basegfx::B2DVector(aY - aTopLeft).getLength()); + const double fSquare(fW * fH); + + if(fSquare <= 0.0) + return; + + // check if less than a maximum square pixels is used + static const sal_uInt32 fMaximumSquare(MAXIMUM_SQUARE_LENGTH * MAXIMUM_SQUARE_LENGTH); + + if(fSquare >= fMaximumSquare) + return; + + // calculate needed number of tiles and check if used more than a minimum count + const texture::GeoTexSvxTiled aTiling(getReferenceRange()); + const sal_uInt32 nTiles(aTiling.getNumberOfTiles()); + static const sal_uInt32 nMinimumTiles(MINIMUM_TILES_LENGTH * MINIMUM_TILES_LENGTH); + + if(nTiles < nMinimumTiles) + return; + + rWidth = basegfx::fround(ceil(fW)); + rHeight = basegfx::fround(ceil(fH)); + static const sal_uInt32 fMinimumSquare(MINIMUM_SQUARE_LENGTH * MINIMUM_SQUARE_LENGTH); + + if(fSquare < fMinimumSquare) + { + const double fRel(fW/fH); + rWidth = basegfx::fround(sqrt(fMinimumSquare * fRel)); + rHeight = basegfx::fround(sqrt(fMinimumSquare / fRel)); + } + } + + void PatternFillPrimitive2D::getTileSize( + sal_uInt32& rWidth, + sal_uInt32& rHeight, + const geometry::ViewInformation2D& rViewInformation) const + { + const basegfx::B2DRange aMaskRange(getMask().getB2DRange()); + + // get discrete rounded up square size of a single tile + const basegfx::B2DHomMatrix aMaskRangeTransformation( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aMaskRange.getRange(), + aMaskRange.getMinimum())); + const basegfx::B2DHomMatrix aTransform( + rViewInformation.getObjectToViewTransformation() * aMaskRangeTransformation); + const basegfx::B2DPoint aTopLeft(aTransform * getReferenceRange().getMinimum()); + const basegfx::B2DPoint aX(aTransform * basegfx::B2DPoint(getReferenceRange().getMaxX(), getReferenceRange().getMinY())); + const basegfx::B2DPoint aY(aTransform * basegfx::B2DPoint(getReferenceRange().getMinX(), getReferenceRange().getMaxY())); + const double fW(basegfx::B2DVector(aX - aTopLeft).getLength()); + const double fH(basegfx::B2DVector(aY - aTopLeft).getLength()); + + rWidth = basegfx::fround(ceil(fW)); + rHeight = basegfx::fround(ceil(fH)); + } + + Primitive2DContainer PatternFillPrimitive2D::createContent(const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer aContent; + + // see if buffering is wanted. If so, create buffered content in given resolution + 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 }; + + const BitmapEx aBitmapEx( + convertToBitmapEx( + std::move(xEmbedSeq), + aViewInformation2D, + mnDiscreteWidth, + mnDiscreteHeight, + mnDiscreteWidth * mnDiscreteHeight)); + + if(!aBitmapEx.IsEmpty()) + { + const Size& rBmpPix = aBitmapEx.GetSizePixel(); + + if(rBmpPix.Width() > 0 && rBmpPix.Height() > 0) + { + const primitive2d::Primitive2DReference xEmbedRefBitmap( + new primitive2d::BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(aBitmapEx), + basegfx::B2DHomMatrix())); + aContent = primitive2d::Primitive2DContainer { xEmbedRefBitmap }; + } + } + } + + if(aContent.empty()) + { + // buffering was not tried or did fail - reset remembered buffered size + // in any case + PatternFillPrimitive2D* pThat = const_cast< PatternFillPrimitive2D* >(this); + pThat->mnDiscreteWidth = pThat->mnDiscreteHeight = 0; + + // use children as default context + aContent = getChildren(); + + // check if content needs to be clipped + const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + const basegfx::B2DRange aContentRange(aContent.getB2DRange(rViewInformation)); + + if(!aUnitRange.isInside(aContentRange)) + { + const Primitive2DReference xRef( + new MaskPrimitive2D( + basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aUnitRange)), + std::move(aContent))); + + aContent = Primitive2DContainer { xRef }; + } + } + + return aContent; + } + + // create buffered content in given resolution + BitmapEx PatternFillPrimitive2D::createTileImage(sal_uInt32 nWidth, sal_uInt32 nHeight) const + { + const geometry::ViewInformation2D aViewInformation2D; + Primitive2DContainer aContent(createContent(aViewInformation2D)); + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D( + basegfx::utils::createScaleB2DHomMatrix(nWidth, nHeight), + std::move(aContent))); + primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; + + return convertToBitmapEx( + std::move(xEmbedSeq), + aViewInformation2D, + nWidth, + nHeight, + nWidth * nHeight); + } + + void PatternFillPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer aRetval; + + if(getChildren().empty()) + return; + + if(!(!getReferenceRange().isEmpty() && getReferenceRange().getWidth() > 0.0 && getReferenceRange().getHeight() > 0.0)) + return; + + const basegfx::B2DRange aMaskRange(getMask().getB2DRange()); + + if(!(!aMaskRange.isEmpty() && aMaskRange.getWidth() > 0.0 && aMaskRange.getHeight() > 0.0)) + return; + + // create tiling matrices + std::vector< basegfx::B2DHomMatrix > aMatrices; + texture::GeoTexSvxTiled aTiling(getReferenceRange()); + + aTiling.appendTransformations(aMatrices); + + // create content + Primitive2DContainer aContent(createContent(rViewInformation)); + + // resize result + aRetval.resize(aMatrices.size()); + + // create one primitive for each matrix + for(size_t a(0); a < aMatrices.size(); a++) + { + aRetval[a] = new TransformPrimitive2D( + aMatrices[a], + Primitive2DContainer(aContent)); + } + + // transform result which is in unit coordinates to mask's object coordinates + { + const basegfx::B2DHomMatrix aMaskTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aMaskRange.getRange(), + aMaskRange.getMinimum())); + + Primitive2DReference xRef( + new TransformPrimitive2D( + aMaskTransform, + std::move(aRetval))); + + aRetval = Primitive2DContainer { xRef }; + } + + // embed result in mask + { + rContainer.push_back( + new MaskPrimitive2D( + getMask(), + std::move(aRetval))); + } + } + + PatternFillPrimitive2D::PatternFillPrimitive2D( + const basegfx::B2DPolyPolygon& rMask, + Primitive2DContainer&& rChildren, + const basegfx::B2DRange& rReferenceRange) + : maMask(rMask), + maChildren(std::move(rChildren)), + maReferenceRange(rReferenceRange), + mnDiscreteWidth(0), + mnDiscreteHeight(0) + { + } + + bool PatternFillPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PatternFillPrimitive2D& rCompare = static_cast< const PatternFillPrimitive2D& >(rPrimitive); + + return (getMask() == rCompare.getMask() + && getChildren() == rCompare.getChildren() + && getReferenceRange() == rCompare.getReferenceRange()); + } + + return false; + } + + basegfx::B2DRange PatternFillPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /* rViewInformation */ ) const + { + return getMask().getB2DRange(); + } + + void PatternFillPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // The existing buffered decomposition uses a buffer in the remembered + // size or none if sizes are zero. Get new needed sizes which depend on + // the given ViewInformation + bool bResetBuffering = false; + sal_uInt32 nW(0); + sal_uInt32 nH(0); + calculateNeededDiscreteBufferSize(nW, nH, rViewInformation); + const bool bBufferingCurrentlyUsed(0 != mnDiscreteWidth && 0 != mnDiscreteHeight); + const bool bBufferingNextUsed(0 != nW && 0 != nH); + + if(bBufferingNextUsed) + { + // buffering is now possible + if(bBufferingCurrentlyUsed) + { + if(nW > mnDiscreteWidth || nH > mnDiscreteHeight) + { + // Higher resolution is needed than used in the existing buffered + // decomposition - create new one + bResetBuffering = true; + } + else if(double(nW * nH) / double(mnDiscreteWidth * mnDiscreteHeight) <= 0.5) + { + // Size has shrunk for 50% or more - it's worth to refresh the buffering + // to spare some resources + bResetBuffering = true; + } + } + else + { + // currently no buffering used - reset evtl. unbuffered + // decomposition to start buffering + bResetBuffering = true; + } + } + else + { + // buffering is no longer possible + if(bBufferingCurrentlyUsed) + { + // reset decomposition to allow creation of unbuffered one + bResetBuffering = true; + } + } + + if(bResetBuffering) + { + PatternFillPrimitive2D* pThat = const_cast< PatternFillPrimitive2D* >(this); + pThat->mnDiscreteWidth = nW; + pThat->mnDiscreteHeight = nH; + pThat->setBuffered2DDecomposition(Primitive2DContainer()); + } + + // call parent + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + sal_Int64 PatternFillPrimitive2D::estimateUsage() + { + size_t nRet(0); + for (auto& it : getChildren()) + if (it) + nRet += it->estimateUsage(); + return nRet; + } + + // provide unique ID + sal_uInt32 PatternFillPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/pointarrayprimitive2d.cxx b/drawinglayer/source/primitive2d/pointarrayprimitive2d.cxx new file mode 100644 index 000000000..6299e1850 --- /dev/null +++ b/drawinglayer/source/primitive2d/pointarrayprimitive2d.cxx @@ -0,0 +1,77 @@ +/* -*- 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/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + PointArrayPrimitive2D::PointArrayPrimitive2D( + std::vector< basegfx::B2DPoint >&& rPositions, + const basegfx::BColor& rRGBColor) + : maPositions(std::move(rPositions)), + maRGBColor(rRGBColor) + { + } + + bool PointArrayPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const PointArrayPrimitive2D& rCompare = static_cast<const PointArrayPrimitive2D&>(rPrimitive); + + return (getPositions() == rCompare.getPositions() + && getRGBColor() == rCompare.getRGBColor()); + } + + return false; + } + + basegfx::B2DRange PointArrayPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(maB2DRange.isEmpty()) + { + basegfx::B2DRange aNewRange; + + // get the basic range from the position vector + for (auto const& pos : getPositions()) + { + aNewRange.expand(pos); + } + + // assign to buffered value + const_cast< PointArrayPrimitive2D* >(this)->maB2DRange = aNewRange; + } + + return maB2DRange; + } + + // provide unique ID + sal_uInt32 PointArrayPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx new file mode 100644 index 000000000..1cbde11ae --- /dev/null +++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx @@ -0,0 +1,611 @@ +/* -*- 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 <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/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <com/sun/star/drawing/LineCap.hpp> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(const basegfx::B2DPolygon& rPolygon, + const basegfx::BColor& rBColor) + : maPolygon(rPolygon) + , maBColor(rBColor) +{ +} + +bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const PolygonHairlinePrimitive2D& rCompare + = static_cast<const PolygonHairlinePrimitive2D&>(rPrimitive); + + return (getB2DPolygon() == rCompare.getB2DPolygon() && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange +PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // this is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size + basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange()); + + if (!aRetval.isEmpty()) + { + // Calculate view-dependent hairline width + const basegfx::B2DVector aDiscreteSize( + rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); + const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); + + if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) + { + aRetval.grow(fDiscreteHalfLineWidth); + } + } + + // return range + return aRetval; +} + +// provide unique ID +sal_uInt32 PolygonHairlinePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D; +} + +void PolygonMarkerPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const +{ + // calculate logic DashLength + const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(getDiscreteDashLength(), 0.0)); + const double fLogicDashLength(aDashVector.getX()); + + if (fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB())) + { + // apply dashing; get line and gap snippets + std::vector<double> aDash; + basegfx::B2DPolyPolygon aDashedPolyPolyA; + basegfx::B2DPolyPolygon aDashedPolyPolyB; + + aDash.push_back(fLogicDashLength); + aDash.push_back(fLogicDashLength); + basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA, + &aDashedPolyPolyB, 2.0 * fLogicDashLength); + + rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyA, getRGBColorA())); + rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyB, getRGBColorB())); + } + else + { + rContainer.push_back(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA())); + } +} + +PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(const basegfx::B2DPolygon& rPolygon, + const basegfx::BColor& rRGBColorA, + const basegfx::BColor& rRGBColorB, + double fDiscreteDashLength) + : maPolygon(rPolygon) + , maRGBColorA(rRGBColorA) + , maRGBColorB(rRGBColorB) + , mfDiscreteDashLength(fDiscreteDashLength) +{ +} + +bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolygonMarkerPrimitive2D& rCompare + = static_cast<const PolygonMarkerPrimitive2D&>(rPrimitive); + + return (getB2DPolygon() == rCompare.getB2DPolygon() + && getRGBColorA() == rCompare.getRGBColorA() + && getRGBColorB() == rCompare.getRGBColorB() + && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); + } + + return false; +} + +basegfx::B2DRange +PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // this is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size + basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange()); + + if (!aRetval.isEmpty()) + { + // Calculate view-dependent hairline width + const basegfx::B2DVector aDiscreteSize( + rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); + const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); + + if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) + { + aRetval.grow(fDiscreteHalfLineWidth); + } + } + + // return range + return aRetval; +} + +void PolygonMarkerPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + bool bNeedNewDecomposition(false); + + if (!getBuffered2DDecomposition().empty()) + { + if (rViewInformation.getInverseObjectToViewTransformation() + != maLastInverseObjectToViewTransformation) + { + bNeedNewDecomposition = true; + } + } + + if (bNeedNewDecomposition) + { + // conditions of last local decomposition have changed, delete + const_cast<PolygonMarkerPrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + + if (getBuffered2DDecomposition().empty()) + { + // remember last used InverseObjectToViewTransformation + PolygonMarkerPrimitive2D* pThat = const_cast<PolygonMarkerPrimitive2D*>(this); + pThat->maLastInverseObjectToViewTransformation + = rViewInformation.getInverseObjectToViewTransformation(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); +} + +// provide unique ID +sal_uInt32 PolygonMarkerPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D; +} + +} // end of namespace + +namespace drawinglayer::primitive2d +{ +void PolygonStrokePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getB2DPolygon().count()) + return; + + // #i102241# try to simplify before usage + const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon())); + basegfx::B2DPolyPolygon aHairLinePolyPolygon; + + if (getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) + { + // no line dashing, just copy + aHairLinePolyPolygon.append(aB2DPolygon); + } + else + { + // apply LineStyle + basegfx::utils::applyLineDashing(aB2DPolygon, getStrokeAttribute().getDotDashArray(), + &aHairLinePolyPolygon, nullptr, + getStrokeAttribute().getFullDotDashLen()); + } + + const sal_uInt32 nCount(aHairLinePolyPolygon.count()); + + if (!getLineAttribute().isDefault() && getLineAttribute().getWidth()) + { + // create fat line data + const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0); + const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin()); + const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap()); + basegfx::B2DPolyPolygon aAreaPolyPolygon; + const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle()); + + for (sal_uInt32 a(0); a < nCount; a++) + { + // New version of createAreaGeometry; now creates bezier polygons + aAreaPolyPolygon.append(basegfx::utils::createAreaGeometry( + aHairLinePolyPolygon.getB2DPolygon(a), fHalfLineWidth, aLineJoin, aLineCap, + basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/, + 0.4 /* default fMaxPartOfEdge*/, fMiterMinimumAngle)); + } + + // create primitive + for (sal_uInt32 b(0); b < aAreaPolyPolygon.count(); b++) + { + // put into single polyPolygon primitives to make clear that this is NOT meant + // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a + // melting process may be used here one day. + const basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b)); + const basegfx::BColor aColor(getLineAttribute().getColor()); + rContainer.push_back(new PolyPolygonColorPrimitive2D(aNewPolyPolygon, aColor)); + } + } + else + { + rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aHairLinePolyPolygon, + getLineAttribute().getColor())); + } +} + +PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( + const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + : maPolygon(rPolygon) + , maLineAttribute(rLineAttribute) + , maStrokeAttribute(rStrokeAttribute) +{ + // MM01: keep these - these are no curve-decompposers but just checks + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); +} + +PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute) + : maPolygon(rPolygon) + , maLineAttribute(rLineAttribute) +{ + // MM01: keep these - these are no curve-decompposers but just checks + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); +} + +bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolygonStrokePrimitive2D& rCompare + = static_cast<const PolygonStrokePrimitive2D&>(rPrimitive); + + return (getB2DPolygon() == rCompare.getB2DPolygon() + && getLineAttribute() == rCompare.getLineAttribute() + && getStrokeAttribute() == rCompare.getStrokeAttribute()); + } + + return false; +} + +basegfx::B2DRange +PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval; + + if (getLineAttribute().getWidth()) + { + bool bUseDecomposition(false); + + if (basegfx::B2DLineJoin::Miter == getLineAttribute().getLineJoin()) + { + // if line is mitered, use parent call since mitered line + // geometry may use more space than the geometry grown by half line width + bUseDecomposition = true; + } + + if (!bUseDecomposition && css::drawing::LineCap_SQUARE == getLineAttribute().getLineCap()) + { + // when drawing::LineCap_SQUARE is used the below method to grow the polygon + // range by half line width will not work, so use decomposition. Interestingly, + // the grow method below works perfectly for LineCap_ROUND since the grow is in + // all directions and the rounded cap needs the same grow in all directions independent + // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE + bUseDecomposition = true; + } + + if (bUseDecomposition) + { + // get correct range by using the decomposition fallback, reasons see above cases + + // ofz#947 to optimize calculating the range, turn any slow dashes into a solid line + // when just calculating bounds + attribute::StrokeAttribute aOrigStrokeAttribute = maStrokeAttribute; + const_cast<PolygonStrokePrimitive2D*>(this)->maStrokeAttribute + = attribute::StrokeAttribute(); + aRetval = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + const_cast<PolygonStrokePrimitive2D*>(this)->maStrokeAttribute = aOrigStrokeAttribute; + } + else + { + // for all other B2DLINEJOIN_* get the range from the base geometry + // and expand by half the line width + aRetval = getB2DPolygon().getB2DRange(); + aRetval.grow(getLineAttribute().getWidth() * 0.5); + } + } + else + { + // this is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size + aRetval = getB2DPolygon().getB2DRange(); + + if (!aRetval.isEmpty()) + { + // Calculate view-dependent hairline width + const basegfx::B2DVector aDiscreteSize( + rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.0, 0.0)); + const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); + + if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) + { + aRetval.grow(fDiscreteHalfLineWidth); + } + } + } + + return aRetval; +} + +// provide unique ID +sal_uInt32 PolygonStrokePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D; +} + +void PolygonWavePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getB2DPolygon().count()) + return; + + const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth())); + const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight())); + + if (bHasWidth && bHasHeight) + { + // create waveline curve + const basegfx::B2DPolygon aWaveline( + basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight())); + rContainer.push_back( + new PolygonStrokePrimitive2D(aWaveline, getLineAttribute(), getStrokeAttribute())); + } + else + { + // flat waveline, decompose to simple line primitive + rContainer.push_back(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), + getStrokeAttribute())); + } +} + +PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute, + double fWaveWidth, double fWaveHeight) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute) + , mfWaveWidth(fWaveWidth) + , mfWaveHeight(fWaveHeight) +{ + if (mfWaveWidth < 0.0) + { + mfWaveWidth = 0.0; + } + + if (mfWaveHeight < 0.0) + { + mfWaveHeight = 0.0; + } +} + +PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + double fWaveWidth, double fWaveHeight) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute) + , mfWaveWidth(fWaveWidth) + , mfWaveHeight(fWaveHeight) +{ + if (mfWaveWidth < 0.0) + { + mfWaveWidth = 0.0; + } + + if (mfWaveHeight < 0.0) + { + mfWaveHeight = 0.0; + } +} + +bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (PolygonStrokePrimitive2D::operator==(rPrimitive)) + { + const PolygonWavePrimitive2D& rCompare + = static_cast<const PolygonWavePrimitive2D&>(rPrimitive); + + return (getWaveWidth() == rCompare.getWaveWidth() + && getWaveHeight() == rCompare.getWaveHeight()); + } + + return false; +} + +basegfx::B2DRange +PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // get range of parent + basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation)); + + // if WaveHeight, grow by it + if (basegfx::fTools::more(getWaveHeight(), 0.0)) + { + aRetval.grow(getWaveHeight()); + } + + // if line width, grow by it + if (basegfx::fTools::more(getLineAttribute().getWidth(), 0.0)) + { + aRetval.grow(getLineAttribute().getWidth() * 0.5); + } + + return aRetval; +} + +// provide unique ID +sal_uInt32 PolygonWavePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D; +} + +void PolygonStrokeArrowPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // copy local polygon, it may be changed + basegfx::B2DPolygon aLocalPolygon(getB2DPolygon()); + aLocalPolygon.removeDoublePoints(); + basegfx::B2DPolyPolygon aArrowA; + basegfx::B2DPolyPolygon aArrowB; + + if (!aLocalPolygon.isClosed() && aLocalPolygon.count() > 1) + { + // apply arrows + const double fPolyLength(basegfx::utils::getLength(aLocalPolygon)); + double fStart(0.0); + double fEnd(0.0); + double fStartOverlap(0.0); + double fEndOverlap(0.0); + + if (!getStart().isDefault() && getStart().isActive()) + { + // create start arrow primitive and consume + aArrowA = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(), + fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart); + + // create some overlapping, compromise between straight and peaked markers + // for marker width 0.3cm and marker line width 0.02cm + fStartOverlap = getStart().getWidth() / 15.0; + } + + if (!getEnd().isDefault() && getEnd().isActive()) + { + // create end arrow primitive and consume + aArrowB = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(), + fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd); + + // create some overlapping + fEndOverlap = getEnd().getWidth() / 15.0; + } + + if (0.0 != fStart || 0.0 != fEnd) + { + // build new poly, consume something from old poly + aLocalPolygon + = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart - fStartOverlap, + fPolyLength - fEnd + fEndOverlap, fPolyLength); + } + } + + // add shaft + rContainer.push_back( + new PolygonStrokePrimitive2D(aLocalPolygon, getLineAttribute(), getStrokeAttribute())); + + if (aArrowA.count()) + { + rContainer.push_back( + new PolyPolygonColorPrimitive2D(aArrowA, getLineAttribute().getColor())); + } + + if (aArrowB.count()) + { + rContainer.push_back( + new PolyPolygonColorPrimitive2D(aArrowB, getLineAttribute().getColor())); + } +} + +PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( + const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute, + const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute) + , maStart(rStart) + , maEnd(rEnd) +{ +} + +PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( + const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute, + const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute) + , maStart(rStart) + , maEnd(rEnd) +{ +} + +bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (PolygonStrokePrimitive2D::operator==(rPrimitive)) + { + const PolygonStrokeArrowPrimitive2D& rCompare + = static_cast<const PolygonStrokeArrowPrimitive2D&>(rPrimitive); + + return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd()); + } + + return false; +} + +basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& rViewInformation) const +{ + if (getStart().isActive() || getEnd().isActive()) + { + // use decomposition when line start/end is used + return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + } + else + { + // get range from parent + return PolygonStrokePrimitive2D::getB2DRange(rViewInformation); + } +} + +// provide unique ID +sal_uInt32 PolygonStrokeArrowPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/primitivetools2d.cxx b/drawinglayer/source/primitive2d/primitivetools2d.cxx new file mode 100644 index 000000000..7c6d426e9 --- /dev/null +++ b/drawinglayer/source/primitive2d/primitivetools2d.cxx @@ -0,0 +1,127 @@ +/* -*- 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/primitivetools2d.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void DiscreteMetricDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // 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())) + { + // conditions of last local decomposition have changed, delete + const_cast< DiscreteMetricDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid DiscreteUnit + const_cast< DiscreteMetricDependentPrimitive2D* >(this)->mfDiscreteUnit = fDiscreteUnit; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + + + + void ViewportDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // get the current Viewport + const basegfx::B2DRange& rViewport = rViewInformation.getViewport(); + + if(!getBuffered2DDecomposition().empty() && !rViewport.equal(getViewport())) + { + // conditions of last local decomposition have changed, delete + const_cast< ViewportDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid DiscreteUnit + const_cast< ViewportDependentPrimitive2D* >(this)->maViewport = rViewport; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + void ViewTransformationDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // get the current ViewTransformation + const basegfx::B2DHomMatrix& rViewTransformation = rViewInformation.getViewTransformation(); + + if(!getBuffered2DDecomposition().empty() && rViewTransformation != getViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< ViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid ViewTransformation + const_cast< ViewTransformationDependentPrimitive2D* >(this)->maViewTransformation = rViewTransformation; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + void ObjectAndViewTransformationDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // get the current ViewTransformation + const basegfx::B2DHomMatrix& rViewTransformation = rViewInformation.getViewTransformation(); + + if(!getBuffered2DDecomposition().empty() && rViewTransformation != getViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + // get the current ObjectTransformation + const basegfx::B2DHomMatrix& rObjectTransformation = rViewInformation.getObjectTransformation(); + + if(!getBuffered2DDecomposition().empty() && rObjectTransformation != getObjectTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid ViewTransformation, and ObjectTransformation + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->maViewTransformation = rViewTransformation; + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->maObjectTransformation = rObjectTransformation; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx new file mode 100644 index 000000000..0bec2258d --- /dev/null +++ b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx @@ -0,0 +1,689 @@ +/* -*- 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/sceneprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/attribute/sdrlightattribute3d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.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 <basegfx/raster/bzpixelraster.hxx> +#include <vcl/BitmapTools.hxx> +#include <comphelper/threadpool.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +using namespace com::sun::star; + +namespace +{ + BitmapEx BPixelRasterToBitmapEx(const basegfx::BZPixelRaster& rRaster, sal_uInt16 mnAntiAlialize) + { + BitmapEx aRetval; + const sal_uInt32 nWidth(mnAntiAlialize ? rRaster.getWidth()/mnAntiAlialize : rRaster.getWidth()); + const sal_uInt32 nHeight(mnAntiAlialize ? rRaster.getHeight()/mnAntiAlialize : rRaster.getHeight()); + + if(nWidth && nHeight) + { + const Size aDestSize(nWidth, nHeight); + vcl::bitmap::RawBitmap aContent(aDestSize, 32); + + if(mnAntiAlialize) + { + const sal_uInt16 nDivisor(mnAntiAlialize * mnAntiAlialize); + + for(sal_uInt32 y(0); y < nHeight; y++) + { + for(sal_uInt32 x(0); x < nWidth; x++) + { + sal_uInt16 nRed(0); + sal_uInt16 nGreen(0); + sal_uInt16 nBlue(0); + sal_uInt16 nAlpha(0); + sal_uInt32 nIndex(rRaster.getIndexFromXY(x * mnAntiAlialize, y * mnAntiAlialize)); + + for(sal_uInt32 c(0); c < mnAntiAlialize; c++) + { + for(sal_uInt32 d(0); d < mnAntiAlialize; d++) + { + const basegfx::BPixel& rPixel(rRaster.getBPixel(nIndex++)); + nRed += rPixel.getRed(); + nGreen += rPixel.getGreen(); + nBlue += rPixel.getBlue(); + nAlpha += rPixel.getAlpha(); + } + + nIndex += rRaster.getWidth() - mnAntiAlialize; + } + + nAlpha /= nDivisor; + + if(nAlpha) + { + aContent.SetPixel(y, x, Color(ColorAlpha, + static_cast<sal_uInt8>(nAlpha), + static_cast<sal_uInt8>(nRed / nDivisor), + static_cast<sal_uInt8>(nGreen / nDivisor), + static_cast<sal_uInt8>(nBlue / nDivisor) )); + } + else + aContent.SetPixel(y, x, Color(ColorAlpha, 0, 0, 0, 0)); + } + } + } + else + { + sal_uInt32 nIndex(0); + + for(sal_uInt32 y(0); y < nHeight; y++) + { + for(sal_uInt32 x(0); x < nWidth; x++) + { + const basegfx::BPixel& rPixel(rRaster.getBPixel(nIndex++)); + + if(rPixel.getAlpha()) + { + aContent.SetPixel(y, x, Color(ColorAlpha, rPixel.getAlpha(), rPixel.getRed(), rPixel.getGreen(), rPixel.getBlue())); + } + else + aContent.SetPixel(y, x, Color(ColorAlpha, 0, 0, 0, 0)); + } + } + } + + aRetval = vcl::bitmap::CreateFromData(std::move(aContent)); + + // #i101811# set PrefMapMode and PrefSize at newly created Bitmap + aRetval.SetPrefMapMode(MapMode(MapUnit::MapPixel)); + aRetval.SetPrefSize(Size(nWidth, nHeight)); + } + + return aRetval; + } +} // end of anonymous namespace + +namespace drawinglayer::primitive2d +{ + bool ScenePrimitive2D::impGetShadow3D() const + { + // create on demand + if(!mbShadow3DChecked && !getChildren3D().empty()) + { + basegfx::B3DVector aLightNormal; + const double fShadowSlant(getSdrSceneAttribute().getShadowSlant()); + const basegfx::B3DRange aScene3DRange(getChildren3D().getB3DRange(getViewInformation3D())); + + if(!maSdrLightingAttribute.getLightVector().empty()) + { + // get light normal from first light and normalize + aLightNormal = maSdrLightingAttribute.getLightVector()[0].getDirection(); + aLightNormal.normalize(); + } + + // create shadow extraction processor + processor3d::Shadow3DExtractingProcessor aShadowProcessor( + getViewInformation3D(), + getObjectTransformation(), + aLightNormal, + fShadowSlant, + aScene3DRange); + + // process local primitives + aShadowProcessor.process(getChildren3D()); + + // fetch result and set checked flag + const_cast< ScenePrimitive2D* >(this)->maShadowPrimitives = aShadowProcessor.getPrimitive2DSequence(); + const_cast< ScenePrimitive2D* >(this)->mbShadow3DChecked = true; + } + + // return if there are shadow primitives + return !maShadowPrimitives.empty(); + } + + void ScenePrimitive2D::calculateDiscreteSizes( + const geometry::ViewInformation2D& rViewInformation, + basegfx::B2DRange& rDiscreteRange, + basegfx::B2DRange& rVisibleDiscreteRange, + basegfx::B2DRange& rUnitVisibleRange) const + { + // use unit range and transform to discrete coordinates + rDiscreteRange = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0); + rDiscreteRange.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation()); + + // clip it against discrete Viewport (if set) + rVisibleDiscreteRange = rDiscreteRange; + + if(!rViewInformation.getViewport().isEmpty()) + { + rVisibleDiscreteRange.intersect(rViewInformation.getDiscreteViewport()); + } + + if(rVisibleDiscreteRange.isEmpty()) + { + rUnitVisibleRange = rVisibleDiscreteRange; + } + else + { + // create UnitVisibleRange containing unit range values [0.0 .. 1.0] describing + // the relative position of rVisibleDiscreteRange inside rDiscreteRange + const double fDiscreteScaleFactorX(basegfx::fTools::equalZero(rDiscreteRange.getWidth()) ? 1.0 : 1.0 / rDiscreteRange.getWidth()); + const double fDiscreteScaleFactorY(basegfx::fTools::equalZero(rDiscreteRange.getHeight()) ? 1.0 : 1.0 / rDiscreteRange.getHeight()); + + const double fMinX(basegfx::fTools::equal(rVisibleDiscreteRange.getMinX(), rDiscreteRange.getMinX()) + ? 0.0 + : (rVisibleDiscreteRange.getMinX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX); + const double fMinY(basegfx::fTools::equal(rVisibleDiscreteRange.getMinY(), rDiscreteRange.getMinY()) + ? 0.0 + : (rVisibleDiscreteRange.getMinY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY); + + const double fMaxX(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxX(), rDiscreteRange.getMaxX()) + ? 1.0 + : (rVisibleDiscreteRange.getMaxX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX); + const double fMaxY(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxY(), rDiscreteRange.getMaxY()) + ? 1.0 + : (rVisibleDiscreteRange.getMaxY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY); + + rUnitVisibleRange = basegfx::B2DRange(fMinX, fMinY, fMaxX, fMaxY); + } + } + + void ScenePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // 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()) + { + // test visibility + const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation)); + const basegfx::B2DRange aViewRange( + rViewInformation.getViewport()); + + if(aViewRange.isEmpty() || aShadow2DRange.overlaps(aViewRange)) + { + // add extracted 2d shadows (before 3d scene creations itself) + rContainer.append(maShadowPrimitives); + } + } + + // get the involved ranges (see helper method calculateDiscreteSizes for details) + basegfx::B2DRange aDiscreteRange; + basegfx::B2DRange aVisibleDiscreteRange; + basegfx::B2DRange aUnitVisibleRange; + + calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); + + if(aVisibleDiscreteRange.isEmpty()) + return; + + // 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()); + double fReduceFactor(1.0); + + if(fViewVisibleArea > fMaximumVisibleArea) + { + fReduceFactor = sqrt(fMaximumVisibleArea / fViewVisibleArea); + fViewSizeX *= fReduceFactor; + fViewSizeY *= fReduceFactor; + } + + if(rViewInformation.getReducedDisplayQuality()) + { + // when reducing the visualisation is allowed (e.g. an OverlayObject + // only needed for dragging), reduce resolution extra + // to speed up dragging interactions + const double fArea(fViewSizeX * fViewSizeY); + if (fArea != 0.0) + { + double fReducedVisualisationFactor(1.0 / (sqrt(fArea) * (1.0 / 170.0))); + + if(fReducedVisualisationFactor > 1.0) + { + fReducedVisualisationFactor = 1.0; + } + else if(fReducedVisualisationFactor < 0.20) + { + fReducedVisualisationFactor = 0.20; + } + + if(fReducedVisualisationFactor != 1.0) + { + fReduceFactor *= fReducedVisualisationFactor; + } + } + } + + // determine the oversample value + static const sal_uInt16 nDefaultOversampleValue(3); + const sal_uInt16 nOversampleValue(SvtOptionsDrawinglayer::IsAntiAliasing() ? nDefaultOversampleValue : 0); + + geometry::ViewInformation3D aViewInformation3D(getViewInformation3D()); + { + // calculate a transformation from DiscreteRange to evtl. rotated/sheared content. + // Start with full transformation from object to discrete units + basegfx::B2DHomMatrix aObjToUnit(rViewInformation.getObjectToViewTransformation() * getObjectTransformation()); + + // bring to unit coordinates by applying inverse DiscreteRange + aObjToUnit.translate(-aDiscreteRange.getMinX(), -aDiscreteRange.getMinY()); + if (aDiscreteRange.getWidth() != 0.0 && aDiscreteRange.getHeight() != 0.0) + { + aObjToUnit.scale(1.0 / aDiscreteRange.getWidth(), 1.0 / aDiscreteRange.getHeight()); + } + + // calculate transformed user coordinate system + const basegfx::B2DPoint aStandardNull(0.0, 0.0); + const basegfx::B2DPoint aUnitRangeTopLeft(aObjToUnit * aStandardNull); + const basegfx::B2DVector aStandardXAxis(1.0, 0.0); + const basegfx::B2DVector aUnitRangeXAxis(aObjToUnit * aStandardXAxis); + const basegfx::B2DVector aStandardYAxis(0.0, 1.0); + const basegfx::B2DVector aUnitRangeYAxis(aObjToUnit * aStandardYAxis); + + if(!aUnitRangeTopLeft.equal(aStandardNull) || !aUnitRangeXAxis.equal(aStandardXAxis) || !aUnitRangeYAxis.equal(aStandardYAxis)) + { + // build transformation from unit range to user coordinate system; the unit range + // X and Y axes are the column vectors, the null point is the offset + basegfx::B2DHomMatrix aUnitRangeToUser; + + aUnitRangeToUser.set3x2( + aUnitRangeXAxis.getX(), aUnitRangeYAxis.getX(), aUnitRangeTopLeft.getX(), + aUnitRangeXAxis.getY(), aUnitRangeYAxis.getY(), aUnitRangeTopLeft.getY()); + + // decompose to allow to apply this to the 3D transformation + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + aUnitRangeToUser.decompose(aScale, aTranslate, fRotate, fShearX); + + // apply before DeviceToView and after Projection, 3D is in range [-1.0 .. 1.0] in X,Y and Z + // and not yet flipped in Y + basegfx::B3DHomMatrix aExtendedProjection(aViewInformation3D.getProjection()); + + // bring to unit coordinates, flip Y, leave Z unchanged + aExtendedProjection.scale(0.5, -0.5, 1.0); + aExtendedProjection.translate(0.5, 0.5, 0.0); + + // apply extra; Y is flipped now, go with positive shear and rotate values + aExtendedProjection.scale(aScale.getX(), aScale.getY(), 1.0); + aExtendedProjection.shearXZ(fShearX, 0.0); + aExtendedProjection.rotate(0.0, 0.0, fRotate); + aExtendedProjection.translate(aTranslate.getX(), aTranslate.getY(), 0.0); + + // back to state after projection + aExtendedProjection.translate(-0.5, -0.5, 0.0); + aExtendedProjection.scale(2.0, -2.0, 1.0); + + aViewInformation3D = geometry::ViewInformation3D( + aViewInformation3D.getObjectTransformation(), + aViewInformation3D.getOrientation(), + aExtendedProjection, + aViewInformation3D.getDeviceToView(), + aViewInformation3D.getViewTime(), + aViewInformation3D.getExtendedInformationSequence()); + } + } + + // calculate logic render size in world coordinates for usage in renderer + const basegfx::B2DHomMatrix& aInverseOToV(rViewInformation.getInverseObjectToViewTransformation()); + const double fLogicX((aInverseOToV * basegfx::B2DVector(aDiscreteRange.getWidth() * fReduceFactor, 0.0)).getLength()); + 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()); + + // 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); + + if(!(nRasterWidth && nRasterHeight)) + return; + + // create view unit buffer + basegfx::BZPixelRaster aBZPixelRaster( + nOversampleValue ? nRasterWidth * nOversampleValue : nRasterWidth, + nOversampleValue ? nRasterHeight * nOversampleValue : nRasterHeight); + + // check for parallel execution possibilities + static bool bMultithreadAllowed = false; // loplugin:constvars:ignore + sal_Int32 nThreadCount(0); + comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool()); + + if(bMultithreadAllowed) + { + nThreadCount = rThreadPool.getWorkerCount(); + + if(nThreadCount > 1) + { + // at least use 10px per processor, so limit number of processors to + // target pixel size divided by 10 (which might be zero what is okay) + nThreadCount = std::min(nThreadCount, nRasterHeight / 10); + } + } + + if(nThreadCount > 1) + { + class Executor : public comphelper::ThreadTask + { + private: + std::unique_ptr<processor3d::ZBufferProcessor3D> mpZBufferProcessor3D; + const primitive3d::Primitive3DContainer& mrChildren3D; + + public: + explicit Executor( + std::shared_ptr<comphelper::ThreadTaskTag> const & rTag, + std::unique_ptr<processor3d::ZBufferProcessor3D> pZBufferProcessor3D, + const primitive3d::Primitive3DContainer& rChildren3D) + : comphelper::ThreadTask(rTag), + mpZBufferProcessor3D(std::move(pZBufferProcessor3D)), + mrChildren3D(rChildren3D) + { + } + + virtual void doWork() override + { + mpZBufferProcessor3D->process(mrChildren3D); + mpZBufferProcessor3D->finish(); + mpZBufferProcessor3D.reset(); + } + }; + + const sal_uInt32 nLinesPerThread(aBZPixelRaster.getHeight() / nThreadCount); + std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag(); + + for(sal_Int32 a(0); a < nThreadCount; a++) + { + std::unique_ptr<processor3d::ZBufferProcessor3D> pNewZBufferProcessor3D(new processor3d::ZBufferProcessor3D( + aViewInformation3D, + getSdrSceneAttribute(), + getSdrLightingAttribute(), + aUnitVisibleRange, + nOversampleValue, + fFullViewSizeX, + fFullViewSizeY, + aBZPixelRaster, + nLinesPerThread * a, + a + 1 == nThreadCount ? aBZPixelRaster.getHeight() : nLinesPerThread * (a + 1))); + std::unique_ptr<Executor> pExecutor(new Executor(aTag, std::move(pNewZBufferProcessor3D), getChildren3D())); + rThreadPool.pushTask(std::move(pExecutor)); + } + + rThreadPool.waitUntilDone(aTag); + } + else + { + // use default 3D primitive processor to create BitmapEx for aUnitVisiblePart and process + processor3d::ZBufferProcessor3D aZBufferProcessor3D( + aViewInformation3D, + getSdrSceneAttribute(), + getSdrLightingAttribute(), + aUnitVisibleRange, + nOversampleValue, + fFullViewSizeX, + fFullViewSizeY, + aBZPixelRaster, + 0, + aBZPixelRaster.getHeight()); + + aZBufferProcessor3D.process(getChildren3D()); + aZBufferProcessor3D.finish(); + } + + const_cast< ScenePrimitive2D* >(this)->maOldRenderedBitmap = BPixelRasterToBitmapEx(aBZPixelRaster, nOversampleValue); + const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel()); + + if(!(aBitmapSizePixel.getWidth() && aBitmapSizePixel.getHeight())) + return; + + // create transform for the created bitmap in discrete coordinates first. + basegfx::B2DHomMatrix aNew2DTransform; + + aNew2DTransform.set(0, 0, aVisibleDiscreteRange.getWidth()); + aNew2DTransform.set(1, 1, aVisibleDiscreteRange.getHeight()); + aNew2DTransform.set(0, 2, aVisibleDiscreteRange.getMinX()); + aNew2DTransform.set(1, 2, aVisibleDiscreteRange.getMinY()); + + // transform back to world coordinates for usage in primitive creation + aNew2DTransform *= aInverseOToV; + + // create bitmap primitive and add + rContainer.push_back( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(maOldRenderedBitmap), + aNew2DTransform)); + + // test: Allow to add an outline in the debugger when tests are needed + static bool bAddOutlineToCreated3DSceneRepresentation(false); // loplugin:constvars:ignore + + if(bAddOutlineToCreated3DSceneRepresentation) + { + basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon()); + aOutline.transform(aNew2DTransform); + rContainer.push_back(new PolygonHairlinePrimitive2D(aOutline, basegfx::BColor(1.0, 0.0, 0.0))); + } + } + + Primitive2DContainer ScenePrimitive2D::getGeometry2D() const + { + Primitive2DContainer aRetval; + + // create 2D projected geometry from 3D geometry + if(!getChildren3D().empty()) + { + // create 2D geometry extraction processor + processor3d::Geometry2DExtractingProcessor aGeometryProcessor( + getViewInformation3D(), + getObjectTransformation()); + + // process local primitives + aGeometryProcessor.process(getChildren3D()); + + // fetch result + aRetval = aGeometryProcessor.getPrimitive2DSequence(); + } + + return aRetval; + } + + Primitive2DContainer ScenePrimitive2D::getShadow2D() const + { + Primitive2DContainer aRetval; + + // create 2D shadows from contained 3D primitives + if(impGetShadow3D()) + { + // add extracted 2d shadows (before 3d scene creations itself) + aRetval = maShadowPrimitives; + } + + return aRetval; + } + + bool ScenePrimitive2D::tryToCheckLastVisualisationDirectHit(const basegfx::B2DPoint& rLogicHitPoint, bool& o_rResult) const + { + if(maOldRenderedBitmap.IsEmpty() || maOldUnitVisiblePart.isEmpty()) + return false; + + basegfx::B2DHomMatrix aInverseSceneTransform(getObjectTransformation()); + aInverseSceneTransform.invert(); + const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * rLogicHitPoint); + + if(!maOldUnitVisiblePart.isInside(aRelativePoint)) + return false; + + // calculate coordinates relative to visualized part + double fDivisorX(maOldUnitVisiblePart.getWidth()); + double fDivisorY(maOldUnitVisiblePart.getHeight()); + + if(basegfx::fTools::equalZero(fDivisorX)) + { + fDivisorX = 1.0; + } + + if(basegfx::fTools::equalZero(fDivisorY)) + { + fDivisorY = 1.0; + } + + 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), + mbShadow3DChecked(false), + mfOldDiscreteSizeX(0.0), + mfOldDiscreteSizeY(0.0) + { + } + + bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const ScenePrimitive2D& rCompare = static_cast<const ScenePrimitive2D&>(rPrimitive); + + return (getChildren3D() == rCompare.getChildren3D() + && getSdrSceneAttribute() == rCompare.getSdrSceneAttribute() + && getSdrLightingAttribute() == rCompare.getSdrLightingAttribute() + && getObjectTransformation() == rCompare.getObjectTransformation() + && getViewInformation3D() == rCompare.getViewInformation3D()); + } + + return false; + } + + basegfx::B2DRange ScenePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // transform unit range to discrete coordinate range + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation()); + + // force to discrete expanded bounds (it grows, so expanding works perfectly well) + aRetval.expand(basegfx::B2DTuple(floor(aRetval.getMinX()), floor(aRetval.getMinY()))); + aRetval.expand(basegfx::B2DTuple(ceil(aRetval.getMaxX()), ceil(aRetval.getMaxY()))); + + // transform back from discrete (view) to world coordinates + aRetval.transform(rViewInformation.getInverseObjectToViewTransformation()); + + // expand by evtl. existing shadow primitives + if(impGetShadow3D()) + { + const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation)); + + if(!aShadow2DRange.isEmpty()) + { + aRetval.expand(aShadow2DRange); + } + } + + return aRetval; + } + + void ScenePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // 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()) + { + basegfx::B2DRange aVisibleDiscreteRange; + calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); + bDiscreteSizesAreCalculated = true; + + // needs to be painted when the new part is not part of the last + // decomposition + if(!maOldUnitVisiblePart.isInside(aUnitVisibleRange)) + { + bNeedNewDecomposition = true; + } + + // display has changed and cannot be reused when resolution got bigger. It + // can be reused when resolution got smaller, though. + if(!bNeedNewDecomposition) + { + if(basegfx::fTools::more(aDiscreteRange.getWidth(), mfOldDiscreteSizeX) || + basegfx::fTools::more(aDiscreteRange.getHeight(), mfOldDiscreteSizeY)) + { + bNeedNewDecomposition = true; + } + } + } + + if(bNeedNewDecomposition) + { + // conditions of last local decomposition have changed, delete + const_cast< ScenePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + if(!bDiscreteSizesAreCalculated) + { + basegfx::B2DRange aVisibleDiscreteRange; + calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); + } + + // remember last used NewDiscreteSize and NewUnitVisiblePart + ScenePrimitive2D* pThat = const_cast< ScenePrimitive2D* >(this); + pThat->mfOldDiscreteSizeX = aDiscreteRange.getWidth(); + pThat->mfOldDiscreteSizeY = aDiscreteRange.getHeight(); + pThat->maOldUnitVisiblePart = aUnitVisibleRange; + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + sal_uInt32 ScenePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SCENEPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx b/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx new file mode 100644 index 000000000..5080a92a8 --- /dev/null +++ b/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx @@ -0,0 +1,106 @@ +/* -*- 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/sdrdecompositiontools2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + Primitive2DReference createHiddenGeometryPrimitives2D( + const basegfx::B2DHomMatrix& rMatrix) + { + const basegfx::B2DPolygon& aUnitOutline(basegfx::utils::createUnitPolygon()); + + return createHiddenGeometryPrimitives2D( + false/*bFilled*/, + basegfx::B2DPolyPolygon(aUnitOutline), + rMatrix); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + const basegfx::B2DPolyPolygon& rPolyPolygon) + { + return createHiddenGeometryPrimitives2D( + false/*bFilled*/, + rPolyPolygon, + basegfx::B2DHomMatrix()); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + bool bFilled, + const basegfx::B2DRange& rRange) + { + return createHiddenGeometryPrimitives2D( + bFilled, + rRange, + basegfx::B2DHomMatrix()); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + bool bFilled, + const basegfx::B2DRange& rRange, + const basegfx::B2DHomMatrix& rMatrix) + { + const basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(rRange)); + + return createHiddenGeometryPrimitives2D( + bFilled, + aOutline, + rMatrix); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + bool bFilled, + const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DHomMatrix& rMatrix) + { + // create fill or line primitive + Primitive2DReference xReference; + basegfx::B2DPolyPolygon aScaledOutline(rPolyPolygon); + aScaledOutline.transform(rMatrix); + + if(bFilled) + { + xReference = new PolyPolygonColorPrimitive2D( + aScaledOutline, + basegfx::BColor(0.0, 0.0, 0.0)); + } + else + { + const basegfx::BColor aGrayTone(0xc0 / 255.0, 0xc0 / 255.0, 0xc0 / 255.0); + + xReference = new PolyPolygonHairlinePrimitive2D( + aScaledOutline, + aGrayTone); + } + + // create HiddenGeometryPrimitive2D + return Primitive2DReference( + new HiddenGeometryPrimitive2D(Primitive2DContainer { xReference })); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx new file mode 100644 index 000000000..758aca3d2 --- /dev/null +++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx @@ -0,0 +1,94 @@ +/* -*- 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/shadowprimitive2d.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +#include <memory> + +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) + { + } + + bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const ShadowPrimitive2D& rCompare = static_cast< const ShadowPrimitive2D& >(rPrimitive); + + return (getShadowTransform() == rCompare.getShadowTransform() + && getShadowColor() == rCompare.getShadowColor() + && getShadowBlur() == rCompare.getShadowBlur()); + } + + return false; + } + + basegfx::B2DRange ShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); + aRetval.grow(getShadowBlur()); + aRetval.transform(getShadowTransform()); + return aRetval; + } + + void ShadowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) 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 + rVisitor.visit(new TransformPrimitive2D(getShadowTransform(), std::move(aSequenceB))); + } + + // provide unique ID + sal_uInt32 ShadowPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SHADOWPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx new file mode 100644 index 000000000..59cf59cd7 --- /dev/null +++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx @@ -0,0 +1,73 @@ +/* -*- 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/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> + +namespace drawinglayer::primitive2d +{ +SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + , mfRadius(fRadius) +{ +} + +bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (GroupPrimitive2D::operator==(rPrimitive)) + { + auto& rCompare = static_cast<const SoftEdgePrimitive2D&>(rPrimitive); + return getRadius() == rCompare.getRadius(); + } + + return false; +} + +void SoftEdgePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + if (getChildren().empty()) + return; + + if (!mbInMaskGeneration) + { + GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + 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()); + + const Primitive2DReference xRef( + new ModifiedColorPrimitive2D(Primitive2DContainer(getChildren()), aBColorModifier)); + rVisitor.visit(xRef); +} + +sal_uInt32 SoftEdgePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx new file mode 100644 index 000000000..7e4de87ed --- /dev/null +++ b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx @@ -0,0 +1,71 @@ +/* -*- 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/structuretagprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + StructureTagPrimitive2D::StructureTagPrimitive2D( + const vcl::PDFWriter::StructElement& rStructureElement, + bool bBackground, + bool bIsImage, + Primitive2DContainer&& aChildren, + sal_Int32 const nAnchorStructureElementId) + : GroupPrimitive2D(std::move(aChildren)), + maStructureElement(rStructureElement), + mbBackground(bBackground), + mbIsImage(bIsImage) + , m_nAnchorStructureElementId(nAnchorStructureElementId) + { + } + + bool StructureTagPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const StructureTagPrimitive2D& rCompare = static_cast<const StructureTagPrimitive2D&>(rPrimitive); + + return (isBackground() == rCompare.isBackground() && + isImage() == rCompare.isImage()); + } + + return false; + } + + // provide unique ID + sal_uInt32 StructureTagPrimitive2D::getPrimitive2DID() const + { + 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 new file mode 100644 index 000000000..6b49c4b49 --- /dev/null +++ b/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx @@ -0,0 +1,1096 @@ +/* -*- 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/svggradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <cmath> +#include <vcl/skia/SkiaHelper.hxx> + +using namespace com::sun::star; + + +namespace +{ + sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, const basegfx::BColor& rColorB, double fDelta, double fDiscreteUnit) + { + // use color distance, assume to do every color step (full quality) + sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0)); + + if(nSteps) + { + // calc discrete length to change color all 1.5 discrete units (pixels) + const sal_uInt32 nDistSteps(basegfx::fround(fDelta / (fDiscreteUnit * 1.5))); + + nSteps = std::min(nSteps, nDistSteps); + } + + // roughly cut when too big or too small + nSteps = std::min(nSteps, sal_uInt32(255)); + nSteps = std::max(nSteps, sal_uInt32(1)); + + return nSteps; + } +} // end of anonymous namespace + + +namespace drawinglayer::primitive2d +{ + void SvgGradientHelper::createSingleGradientEntryFill(Primitive2DContainer& rContainer) const + { + const SvgGradientEntryVector& rEntries = getGradientEntries(); + const sal_uInt32 nCount(rEntries.size()); + + if(nCount) + { + const SvgGradientEntry& rSingleEntry = rEntries[nCount - 1]; + const double fOpacity(rSingleEntry.getOpacity()); + + if(fOpacity > 0.0) + { + Primitive2DReference xRef( + new PolyPolygonColorPrimitive2D( + getPolyPolygon(), + rSingleEntry.getColor())); + + if(fOpacity < 1.0) + { + Primitive2DContainer aContent { xRef }; + + xRef = Primitive2DReference( + new UnifiedTransparencePrimitive2D( + std::move(aContent), + 1.0 - fOpacity)); + } + + rContainer.push_back(xRef); + } + } + else + { + OSL_ENSURE(false, "Single gradient entry construction without entry (!)"); + } + } + + void SvgGradientHelper::checkPreconditions() + { + mbPreconditionsChecked = true; + const SvgGradientEntryVector& rEntries = getGradientEntries(); + + if(rEntries.empty()) + { + // no fill at all, done + return; + } + + // sort maGradientEntries by offset, small to big + std::sort(maGradientEntries.begin(), maGradientEntries.end()); + + // gradient with at least two colors + bool bAllInvisible(true); + bool bInvalidEntries(false); + + for(const SvgGradientEntry& rCandidate : rEntries) + { + if(basegfx::fTools::equalZero(rCandidate.getOpacity())) + { + // invisible + mbFullyOpaque = false; + } + else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0)) + { + // completely opaque + bAllInvisible = false; + } + else + { + // opacity + bAllInvisible = false; + mbFullyOpaque = false; + } + + if(!basegfx::fTools::betweenOrEqualEither(rCandidate.getOffset(), 0.0, 1.0)) + { + bInvalidEntries = true; + } + } + + if(bAllInvisible) + { + // all invisible, nothing to do + return; + } + + if(bInvalidEntries) + { + // invalid entries, do nothing + SAL_WARN("drawinglayer", "SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]"); + return; + } + + const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); + + if(aPolyRange.isEmpty()) + { + // no range to fill, nothing to do + return; + } + + const double fPolyWidth(aPolyRange.getWidth()); + const double fPolyHeight(aPolyRange.getHeight()); + + if(basegfx::fTools::equalZero(fPolyWidth) || basegfx::fTools::equalZero(fPolyHeight)) + { + // no width/height to fill, nothing to do + return; + } + + mbCreatesContent = true; + + if(1 == rEntries.size()) + { + // fill with single existing color + setSingleEntry(); + } + } + + const SvgGradientEntry& SvgGradientHelper::FindEntryLessOrEqual( + sal_Int32& rInt, + const double fFrac) const + { + const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries()); + + for(SvgGradientEntryVector::const_reverse_iterator aIter(rCurrent.rbegin()); aIter != rCurrent.rend(); ++aIter) + { + if(basegfx::fTools::lessOrEqual(aIter->getOffset(), fFrac)) + { + return *aIter; + } + } + + // walk over gap to the left, be prepared for missing 0.0/1.0 entries + rInt--; + const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries()); + return rCurrent2.back(); + } + + const SvgGradientEntry& SvgGradientHelper::FindEntryMore( + sal_Int32& rInt, + const double fFrac) const + { + const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries()); + + for(SvgGradientEntryVector::const_iterator aIter(rCurrent.begin()); aIter != rCurrent.end(); ++aIter) + { + if(basegfx::fTools::more(aIter->getOffset(), fFrac)) + { + return *aIter; + } + } + + // walk over gap to the right, be prepared for missing 0.0/1.0 entries + rInt++; + const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries()); + return rCurrent2.front(); + } + + // tdf#124424 Adapted creation of color runs to do in a single effort. Previous + // version tried to do this from [0.0 .. 1.0] and to re-use transformed versions + // in the caller if SpreadMethod was on some repeat mode, but had problems when + // e.g. like in the bugdoc from the task a negative-only fStart/fEnd run was + // requested in which case it did nothing. Even when reusing the spread might + // not have been a full one from [0.0 .. 1.0]. + // This gets complicated due to mirrored runs, but also for gradient definitions + // with missing entries for 0.0 and 1.0 in which case these have to be guessed + // to be there with same parametrisation as their nearest existing entries. These + // *could* have been added at checkPreconditions() but would then create unnecessary + // spreads on zone overlaps. + void SvgGradientHelper::createRun( + Primitive2DContainer& rTargetColor, + Primitive2DContainer& rTargetOpacity, + double fStart, + double fEnd) const + { + double fInt(0.0); + double fFrac(0.0); + double fEnd2(0.0); + + if(SpreadMethod::Pad == getSpreadMethod()) + { + if(fStart < 0.0) + { + fFrac = std::modf(fStart, &fInt); + const SvgGradientEntry& rFront(getGradientEntries().front()); + const SvgGradientEntry aTemp(1.0 + fFrac, rFront.getColor(), rFront.getOpacity()); + createAtom(rTargetColor, rTargetOpacity, aTemp, rFront, static_cast<sal_Int32>(fInt - 1), 0); + fStart = rFront.getOffset(); + } + + if(fEnd > 1.0) + { + // change fEnd early, but create geometry later (after range below) + fEnd2 = fEnd; + fEnd = getGradientEntries().back().getOffset(); + } + } + + while(fStart < fEnd) + { + fFrac = std::modf(fStart, &fInt); + + if(fFrac < 0.0) + { + fInt -= 1; + fFrac = 1.0 + fFrac; + } + + sal_Int32 nIntLeft(static_cast<sal_Int32>(fInt)); + sal_Int32 nIntRight(nIntLeft); + + const SvgGradientEntry& rLeft(FindEntryLessOrEqual(nIntLeft, fFrac)); + const SvgGradientEntry& rRight(FindEntryMore(nIntRight, fFrac)); + createAtom(rTargetColor, rTargetOpacity, rLeft, rRight, nIntLeft, nIntRight); + + const double fNextfStart(static_cast<double>(nIntRight) + rRight.getOffset()); + + if(basegfx::fTools::more(fNextfStart, fStart)) + { + fStart = fNextfStart; + } + else + { + SAL_WARN("drawinglayer", "SvgGradientHelper spread error"); + fStart += 1.0; + } + } + + if(fEnd2 > 1.0) + { + // create end run for SpreadMethod::Pad late to keep correct creation order + fFrac = std::modf(fEnd2, &fInt); + const SvgGradientEntry& rBack(getGradientEntries().back()); + const SvgGradientEntry aTemp(fFrac, rBack.getColor(), rBack.getOpacity()); + createAtom(rTargetColor, rTargetOpacity, rBack, aTemp, 0, static_cast<sal_Int32>(fInt)); + } + } + + void SvgGradientHelper::createResult( + Primitive2DContainer& rContainer, + Primitive2DContainer aTargetColor, + Primitive2DContainer aTargetOpacity, + const basegfx::B2DHomMatrix& rUnitGradientToObject, + bool bInvert) const + { + Primitive2DContainer aTargetColorEntries(aTargetColor.maybeInvert(bInvert)); + Primitive2DContainer aTargetOpacityEntries(aTargetOpacity.maybeInvert(bInvert)); + + if(aTargetColorEntries.empty()) + return; + + Primitive2DReference xRefContent; + + if(!aTargetOpacityEntries.empty()) + { + const Primitive2DReference xRefOpacity = new TransparencePrimitive2D( + std::move(aTargetColorEntries), + std::move(aTargetOpacityEntries)); + + xRefContent = new TransformPrimitive2D( + rUnitGradientToObject, + Primitive2DContainer { xRefOpacity }); + } + else + { + xRefContent = new TransformPrimitive2D( + rUnitGradientToObject, + std::move(aTargetColorEntries)); + } + + rContainer.push_back(new MaskPrimitive2D( + getPolyPolygon(), + Primitive2DContainer { xRefContent })); + } + + SvgGradientHelper::SvgGradientHelper( + const basegfx::B2DHomMatrix& rGradientTransform, + const basegfx::B2DPolyPolygon& rPolyPolygon, + SvgGradientEntryVector&& rGradientEntries, + const basegfx::B2DPoint& rStart, + bool bUseUnitCoordinates, + SpreadMethod aSpreadMethod) + : maGradientTransform(rGradientTransform), + maPolyPolygon(rPolyPolygon), + maGradientEntries(std::move(rGradientEntries)), + maStart(rStart), + maSpreadMethod(aSpreadMethod), + mbPreconditionsChecked(false), + mbCreatesContent(false), + mbSingleEntry(false), + mbFullyOpaque(true), + mbUseUnitCoordinates(bUseUnitCoordinates) + { + } + + SvgGradientHelper::~SvgGradientHelper() + { + } + + const SvgGradientEntryVector& SvgGradientHelper::getMirroredGradientEntries() const + { + if(maMirroredGradientEntries.empty() && !getGradientEntries().empty()) + { + const_cast< SvgGradientHelper* >(this)->createMirroredGradientEntries(); + } + + return maMirroredGradientEntries; + } + + void SvgGradientHelper::createMirroredGradientEntries() + { + if(!maMirroredGradientEntries.empty() || getGradientEntries().empty()) + return; + + const sal_uInt32 nCount(getGradientEntries().size()); + maMirroredGradientEntries.clear(); + maMirroredGradientEntries.reserve(nCount); + + for(sal_uInt32 a(0); a < nCount; a++) + { + const SvgGradientEntry& rCandidate = getGradientEntries()[nCount - 1 - a]; + + maMirroredGradientEntries.emplace_back( + 1.0 - rCandidate.getOffset(), + rCandidate.getColor(), + rCandidate.getOpacity()); + } + } + + bool SvgGradientHelper::operator==(const SvgGradientHelper& rSvgGradientHelper) const + { + const SvgGradientHelper& rCompare = rSvgGradientHelper; + + return (getGradientTransform() == rCompare.getGradientTransform() + && getPolyPolygon() == rCompare.getPolyPolygon() + && getGradientEntries() == rCompare.getGradientEntries() + && getStart() == rCompare.getStart() + && getUseUnitCoordinates() == rCompare.getUseUnitCoordinates() + && getSpreadMethod() == rCompare.getSpreadMethod()); + } + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + void SvgLinearGradientPrimitive2D::checkPreconditions() + { + // call parent + SvgGradientHelper::checkPreconditions(); + + if(getCreatesContent()) + { + // Check Vector + const basegfx::B2DVector aVector(getEnd() - getStart()); + + if(basegfx::fTools::equalZero(aVector.getX()) && basegfx::fTools::equalZero(aVector.getY())) + { + // fill with single color using last stop color + setSingleEntry(); + } + } + } + + void SvgLinearGradientPrimitive2D::createAtom( + Primitive2DContainer& rTargetColor, + Primitive2DContainer& rTargetOpacity, + const SvgGradientEntry& rFrom, + const SvgGradientEntry& rTo, + sal_Int32 nOffsetFrom, + sal_Int32 nOffsetTo) const + { + // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset()) + if(rFrom.getOffset() == rTo.getOffset()) + { + OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)"); + } + else + { + rTargetColor.push_back( + new SvgLinearAtomPrimitive2D( + rFrom.getColor(), rFrom.getOffset() + nOffsetFrom, + rTo.getColor(), rTo.getOffset() + nOffsetTo)); + + if(!getFullyOpaque()) + { + const double fTransFrom(1.0 - rFrom.getOpacity()); + const double fTransTo(1.0 - rTo.getOpacity()); + const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom); + const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo); + + rTargetOpacity.push_back( + new SvgLinearAtomPrimitive2D( + aColorFrom, rFrom.getOffset() + nOffsetFrom, + aColorTo, rTo.getOffset() + nOffsetTo)); + } + } + } + + void SvgLinearGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getPreconditionsChecked()) + { + const_cast< SvgLinearGradientPrimitive2D* >(this)->checkPreconditions(); + } + + if(getSingleEntry()) + { + // fill with last existing color + createSingleGradientEntryFill(rContainer); + } + else if(getCreatesContent()) + { + // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely + // invisible, width and height to fill are not empty + const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); + const double fPolyWidth(aPolyRange.getWidth()); + const double fPolyHeight(aPolyRange.getHeight()); + + // create ObjectTransform based on polygon range + const basegfx::B2DHomMatrix aObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + fPolyWidth, fPolyHeight, + aPolyRange.getMinX(), aPolyRange.getMinY())); + basegfx::B2DHomMatrix aUnitGradientToObject; + + if(getUseUnitCoordinates()) + { + // interpret in unit coordinate system -> object aspect ratio will scale result + // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given + // gradient vector defined by Start,End + const basegfx::B2DVector aVector(getEnd() - getStart()); + const double fVectorLength(aVector.getLength()); + + aUnitGradientToObject.scale(fVectorLength, 1.0); + aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX())); + aUnitGradientToObject.translate(getStart().getX(), getStart().getY()); + + aUnitGradientToObject *= getGradientTransform(); + + // create full transform from unit gradient coordinates to object coordinates + // including the SvgGradient transformation + aUnitGradientToObject *= aObjectTransform; + } + else + { + // interpret in object coordinate system -> object aspect ratio will not scale result + const basegfx::B2DPoint aStart(aObjectTransform * getStart()); + const basegfx::B2DPoint aEnd(aObjectTransform * getEnd()); + const basegfx::B2DVector aVector(aEnd - aStart); + + aUnitGradientToObject.scale(aVector.getLength(), 1.0); + aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX())); + aUnitGradientToObject.translate(aStart.getX(), aStart.getY()); + + aUnitGradientToObject *= getGradientTransform(); + } + + // create inverse from it + basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject); + aObjectToUnitGradient.invert(); + + // back-transform polygon to unit gradient coordinates and get + // UnitRage. This is the range the gradient has to cover + basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon()); + aUnitPoly.transform(aObjectToUnitGradient); + const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange()); + + // prepare result vectors + Primitive2DContainer aTargetColor; + Primitive2DContainer aTargetOpacity; + + if(basegfx::fTools::more(aUnitRange.getWidth(), 0.0)) + { + // add a pre-multiply to aUnitGradientToObject to allow + // multiplication of the polygon(xl, 0.0, xr, 1.0) + const basegfx::B2DHomMatrix aPreMultiply( + basegfx::utils::createScaleTranslateB2DHomMatrix( + 1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY())); + aUnitGradientToObject = aUnitGradientToObject * aPreMultiply; + + // create full color run, including all SpreadMethod variants + createRun( + aTargetColor, + aTargetOpacity, + aUnitRange.getMinX(), + aUnitRange.getMaxX()); + } + + createResult(rContainer, std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject); + } + } + + SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D( + const basegfx::B2DHomMatrix& rGradientTransform, + const basegfx::B2DPolyPolygon& rPolyPolygon, + SvgGradientEntryVector&& rGradientEntries, + const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + bool bUseUnitCoordinates, + SpreadMethod aSpreadMethod) + : SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod), + maEnd(rEnd) + { + } + + SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D() + { + } + + bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); + + if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper)) + { + const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive); + + return (getEnd() == rCompare.getEnd()); + } + + return false; + } + + basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return ObjectRange + return getPolyPolygon().getB2DRange(); + } + + // provide unique ID + sal_uInt32 SvgLinearGradientPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D; + } + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + void SvgRadialGradientPrimitive2D::checkPreconditions() + { + // call parent + SvgGradientHelper::checkPreconditions(); + + if(getCreatesContent()) + { + // Check Radius + if(basegfx::fTools::equalZero(getRadius())) + { + // fill with single color using last stop color + setSingleEntry(); + } + } + } + + void SvgRadialGradientPrimitive2D::createAtom( + Primitive2DContainer& rTargetColor, + Primitive2DContainer& rTargetOpacity, + const SvgGradientEntry& rFrom, + const SvgGradientEntry& rTo, + sal_Int32 nOffsetFrom, + sal_Int32 nOffsetTo) const + { + // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset()) + if(rFrom.getOffset() == rTo.getOffset()) + { + OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)"); + } + else + { + const double fScaleFrom(rFrom.getOffset() + nOffsetFrom); + const double fScaleTo(rTo.getOffset() + nOffsetTo); + + if(isFocalSet()) + { + const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom)); + const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo)); + + rTargetColor.push_back( + new SvgRadialAtomPrimitive2D( + rFrom.getColor(), fScaleFrom, aTranslateFrom, + rTo.getColor(), fScaleTo, aTranslateTo)); + } + else + { + rTargetColor.push_back( + new SvgRadialAtomPrimitive2D( + rFrom.getColor(), fScaleFrom, + rTo.getColor(), fScaleTo)); + } + + if(!getFullyOpaque()) + { + const double fTransFrom(1.0 - rFrom.getOpacity()); + const double fTransTo(1.0 - rTo.getOpacity()); + const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom); + const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo); + + if(isFocalSet()) + { + const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom)); + const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo)); + + rTargetOpacity.push_back( + new SvgRadialAtomPrimitive2D( + aColorFrom, fScaleFrom, aTranslateFrom, + aColorTo, fScaleTo, aTranslateTo)); + } + else + { + rTargetOpacity.push_back( + new SvgRadialAtomPrimitive2D( + aColorFrom, fScaleFrom, + aColorTo, fScaleTo)); + } + } + } + } + + void SvgRadialGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getPreconditionsChecked()) + { + const_cast< SvgRadialGradientPrimitive2D* >(this)->checkPreconditions(); + } + + if(getSingleEntry()) + { + // fill with last existing color + createSingleGradientEntryFill(rContainer); + } + else if(getCreatesContent()) + { + // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely + // invisible, width and height to fill are not empty + const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); + const double fPolyWidth(aPolyRange.getWidth()); + const double fPolyHeight(aPolyRange.getHeight()); + + // create ObjectTransform based on polygon range + const basegfx::B2DHomMatrix aObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + fPolyWidth, fPolyHeight, + aPolyRange.getMinX(), aPolyRange.getMinY())); + basegfx::B2DHomMatrix aUnitGradientToObject; + + if(getUseUnitCoordinates()) + { + // interpret in unit coordinate system -> object aspect ratio will scale result + // create unit transform from unit vector to given linear gradient vector + aUnitGradientToObject.scale(getRadius(), getRadius()); + aUnitGradientToObject.translate(getStart().getX(), getStart().getY()); + + if(!getGradientTransform().isIdentity()) + { + aUnitGradientToObject = getGradientTransform() * aUnitGradientToObject; + } + + // create full transform from unit gradient coordinates to object coordinates + // including the SvgGradient transformation + aUnitGradientToObject = aObjectTransform * aUnitGradientToObject; + } + else + { + // interpret in object coordinate system -> object aspect ratio will not scale result + // use X-Axis with radius, it was already made relative to object width when coming from + // SVG import + const double fRadius((aObjectTransform * basegfx::B2DVector(getRadius(), 0.0)).getLength()); + const basegfx::B2DPoint aStart(aObjectTransform * getStart()); + + aUnitGradientToObject.scale(fRadius, fRadius); + aUnitGradientToObject.translate(aStart.getX(), aStart.getY()); + + aUnitGradientToObject *= getGradientTransform(); + } + + // create inverse from it + basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject); + aObjectToUnitGradient.invert(); + + // back-transform polygon to unit gradient coordinates and get + // UnitRage. This is the range the gradient has to cover + basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon()); + aUnitPoly.transform(aObjectToUnitGradient); + const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange()); + + // create range which the gradient has to cover to cover the whole given geometry. + // For circle, go from 0.0 to max radius in all directions (the corners) + double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength()); + fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength()); + fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength()); + fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength()); + + // prepare result vectors + Primitive2DContainer aTargetColor; + Primitive2DContainer aTargetOpacity; + + if(0.0 < fMax) + { + // prepare maFocalVector + if(isFocalSet()) + { + const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax; + } + + // create full color run, including all SpreadMethod variants + createRun( + aTargetColor, + aTargetOpacity, + 0.0, + fMax); + } + + createResult(rContainer, std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject, true); + } + } + + SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D( + const basegfx::B2DHomMatrix& rGradientTransform, + const basegfx::B2DPolyPolygon& rPolyPolygon, + SvgGradientEntryVector&& rGradientEntries, + const basegfx::B2DPoint& rStart, + double fRadius, + bool bUseUnitCoordinates, + SpreadMethod aSpreadMethod, + const basegfx::B2DPoint* pFocal) + : SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod), + mfRadius(fRadius), + maFocal(rStart), + maFocalVector(0.0, 0.0), + maFocalLength(0.0), + mbFocalSet(false) + { + if(pFocal && !pFocal->equal(getStart())) + { + maFocal = *pFocal; + maFocalVector = maFocal - getStart(); + mbFocalSet = true; + } + } + + SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D() + { + } + + bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); + + if(!pSvgGradientHelper || !SvgGradientHelper::operator==(*pSvgGradientHelper)) + return false; + + const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive); + + if(getRadius() == rCompare.getRadius()) + { + if(isFocalSet() == rCompare.isFocalSet()) + { + if(isFocalSet()) + { + return getFocal() == rCompare.getFocal(); + } + else + { + return true; + } + } + } + + return false; + } + + basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return ObjectRange + return getPolyPolygon().getB2DRange(); + } + + // provide unique ID + sal_uInt32 SvgRadialGradientPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D; + } + +} // end of namespace drawinglayer::primitive2d + + +// SvgLinearAtomPrimitive2D class + +namespace drawinglayer::primitive2d +{ + void SvgLinearAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const double fDelta(getOffsetB() - getOffsetA()); + + if(basegfx::fTools::equalZero(fDelta)) + return; + + // use one discrete unit for overlap (one pixel) + const double fDiscreteUnit(getDiscreteUnit()); + + // use color distance and discrete lengths to calculate step count + const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit)); + + // HACK: Splitting a gradient into adjacent polygons with gradually changing color is silly. + // If antialiasing is used to draw them, the AA-ed adjacent edges won't line up perfectly + // because of the AA (see SkiaSalGraphicsImpl::mergePolyPolygonToPrevious()). + // Make the polygons a bit wider, so they the partial overlap "fixes" this. + const double fixup = SkiaHelper::isVCLSkiaEnabled() ? fDiscreteUnit / 2 : 0; + + // tdf#117949 Use a small amount of discrete overlap at the edges. Usually this + // should be exactly 0.0 and 1.0, but there were cases when this gets clipped + // against the mask polygon which got numerically problematic. + // This change is unnecessary in that respect, but avoids that numerical havoc + // by at the same time doing no real harm AFAIK + // TTTT: Remove again when clipping is fixed (!) + + // prepare polygon in needed width at start position (with discrete overlap) + const basegfx::B2DPolygon aPolygon( + basegfx::utils::createPolygonFromRect( + basegfx::B2DRange( + getOffsetA() - fDiscreteUnit, + -0.0001, // TTTT -> should be 0.0, see comment above + getOffsetA() + (fDelta / nSteps) + fDiscreteUnit + fixup, + 1.0001))); // TTTT -> should be 1.0, see comment above + + // prepare loop (inside to outside, [0.0 .. 1.0[) + double fUnitScale(0.0); + const double fUnitStep(1.0 / 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( + basegfx::B2DPolyPolygon(aNew), + basegfx::interpolate(getColorA(), getColorB(), fUnitScale))); + } + } + + SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D( + const basegfx::BColor& aColorA, double fOffsetA, + const basegfx::BColor& aColorB, double fOffsetB) + : maColorA(aColorA), + maColorB(aColorB), + mfOffsetA(fOffsetA), + mfOffsetB(fOffsetB) + { + if(mfOffsetA > mfOffsetB) + { + OSL_ENSURE(false, "Wrong offset order (!)"); + std::swap(mfOffsetA, mfOffsetB); + } + } + + bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive); + + return (getColorA() == rCompare.getColorA() + && getColorB() == rCompare.getColorB() + && getOffsetA() == rCompare.getOffsetA() + && getOffsetB() == rCompare.getOffsetB()); + } + + return false; + } + + // provide unique ID + sal_uInt32 SvgLinearAtomPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D; + } + +} // end of namespace drawinglayer::primitive2d + + +// SvgRadialAtomPrimitive2D class + +namespace drawinglayer::primitive2d +{ + void SvgRadialAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const double fDeltaScale(getScaleB() - getScaleA()); + + if(basegfx::fTools::equalZero(fDeltaScale)) + return; + + // use one discrete unit for overlap (one pixel) + const double fDiscreteUnit(getDiscreteUnit()); + + // use color distance and discrete lengths to calculate step count + const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit)); + + // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes) + double fUnitScale(0.0); + const double fUnitStep(1.0 / nSteps); + + for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) + { + basegfx::B2DHomMatrix aTransform; + const double fEndScale(getScaleB() - (fDeltaScale * fUnitScale)); + + if(isTranslateSet()) + { + const basegfx::B2DVector aTranslate( + basegfx::interpolate( + getTranslateB(), + getTranslateA(), + fUnitScale)); + + aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix( + fEndScale, + fEndScale, + aTranslate.getX(), + aTranslate.getY()); + } + else + { + aTransform = basegfx::utils::createScaleB2DHomMatrix( + fEndScale, + fEndScale); + } + + basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle()); + + aNew.transform(aTransform); + rContainer.push_back(new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aNew), + basegfx::interpolate(getColorB(), getColorA(), fUnitScale))); + } + } + + SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( + const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA, + const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB) + : maColorA(aColorA), + maColorB(aColorB), + mfScaleA(fScaleA), + mfScaleB(fScaleB) + { + // check and evtl. set translations + if(!rTranslateA.equal(rTranslateB)) + { + mpTranslate.reset( new VectorPair(rTranslateA, rTranslateB) ); + } + + // scale A and B have to be positive + mfScaleA = std::max(mfScaleA, 0.0); + mfScaleB = std::max(mfScaleB, 0.0); + + // scale B has to be bigger than scale A; swap if different + if(mfScaleA > mfScaleB) + { + OSL_ENSURE(false, "Wrong offset order (!)"); + std::swap(mfScaleA, mfScaleB); + + if(mpTranslate) + { + std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB); + } + } + } + + SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( + const basegfx::BColor& aColorA, double fScaleA, + const basegfx::BColor& aColorB, double fScaleB) + : maColorA(aColorA), + maColorB(aColorB), + mfScaleA(fScaleA), + mfScaleB(fScaleB) + { + // scale A and B have to be positive + mfScaleA = std::max(mfScaleA, 0.0); + mfScaleB = std::max(mfScaleB, 0.0); + + // scale B has to be bigger than scale A; swap if different + if(mfScaleA > mfScaleB) + { + OSL_ENSURE(false, "Wrong offset order (!)"); + std::swap(mfScaleA, mfScaleB); + } + } + + SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D() + { + } + + bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(!DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + return false; + + const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive); + + if(getColorA() == rCompare.getColorA() + && getColorB() == rCompare.getColorB() + && getScaleA() == rCompare.getScaleA() + && getScaleB() == rCompare.getScaleB()) + { + if(isTranslateSet() && rCompare.isTranslateSet()) + { + return (getTranslateA() == rCompare.getTranslateA() + && getTranslateB() == rCompare.getTranslateB()); + } + else if(!isTranslateSet() && !rCompare.isTranslateSet()) + { + return true; + } + } + + return false; + } + + // provide unique ID + sal_uInt32 SvgRadialAtomPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textbreakuphelper.cxx b/drawinglayer/source/primitive2d/textbreakuphelper.cxx new file mode 100644 index 000000000..5ca10ce63 --- /dev/null +++ b/drawinglayer/source/primitive2d/textbreakuphelper.cxx @@ -0,0 +1,276 @@ +/* -*- 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/textbreakuphelper.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/CharType.hpp> + + +namespace drawinglayer::primitive2d +{ + TextBreakupHelper::TextBreakupHelper(const TextSimplePortionPrimitive2D& rSource) + : mrSource(rSource), + mbNoDXArray(false) + { + maDecTrans = mrSource.getTextTransform(); + mbNoDXArray = mrSource.getDXArray().empty(); + + if(mbNoDXArray) + { + // init TextLayouter when no dxarray + maTextLayouter.setFontAttribute( + mrSource.getFontAttribute(), + maDecTrans.getScale().getX(), + maDecTrans.getScale().getY(), + mrSource.getLocale()); + } + } + + TextBreakupHelper::~TextBreakupHelper() + { + } + + void TextBreakupHelper::breakupPortion(Primitive2DContainer& rTempResult, sal_Int32 nIndex, sal_Int32 nLength, bool bWordLineMode) + { + if(!(nLength && (nIndex != mrSource.getTextPosition() || nLength != mrSource.getTextLength()))) + return; + + // prepare values for new portion + basegfx::B2DHomMatrix aNewTransform; + std::vector< double > aNewDXArray; + const bool bNewStartIsNotOldStart(nIndex > mrSource.getTextPosition()); + + if(!mbNoDXArray) + { + // prepare new DXArray for the single word + aNewDXArray = std::vector< double >( + mrSource.getDXArray().begin() + (nIndex - mrSource.getTextPosition()), + mrSource.getDXArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition())); + } + + if(bNewStartIsNotOldStart) + { + // needs to be moved to a new start position + double fOffset(0.0); + + if(mbNoDXArray) + { + // evaluate using TextLayouter + fOffset = maTextLayouter.getTextWidth(mrSource.getText(), mrSource.getTextPosition(), nIndex); + } + else + { + // get from DXArray + const sal_Int32 nIndex2(nIndex - mrSource.getTextPosition()); + fOffset = mrSource.getDXArray()[nIndex2 - 1]; + } + + // need offset without FontScale for building the new transformation. The + // new transformation will be multiplied with the current text transformation + // so FontScale would be double + double fOffsetNoScale(fOffset); + const double fFontScaleX(maDecTrans.getScale().getX()); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + fOffsetNoScale /= fFontScaleX; + } + + // apply needed offset to transformation + aNewTransform.translate(fOffsetNoScale, 0.0); + + if(!mbNoDXArray) + { + // DXArray values need to be corrected with the offset, too. Here, + // take the scaled offset since the DXArray is scaled + const sal_uInt32 nArraySize(aNewDXArray.size()); + + for(sal_uInt32 a(0); a < nArraySize; a++) + { + aNewDXArray[a] -= fOffset; + } + } + } + + // add text transformation to new transformation + // coverity[swapped_arguments : FALSE] - this is in the correct order + aNewTransform *= maDecTrans.getB2DHomMatrix(); + + // callback to allow evtl. changes + const bool bCreate(allowChange(rTempResult.size(), aNewTransform, nIndex, nLength)); + + if(!bCreate) + return; + + // check if we have a decorated primitive as source + const TextDecoratedPortionPrimitive2D* pTextDecoratedPortionPrimitive2D = + dynamic_cast< const TextDecoratedPortionPrimitive2D* >(&mrSource); + + if(pTextDecoratedPortionPrimitive2D) + { + // create a TextDecoratedPortionPrimitive2D + rTempResult.push_back( + new TextDecoratedPortionPrimitive2D( + aNewTransform, + mrSource.getText(), + nIndex, + nLength, + std::move(aNewDXArray), + mrSource.getFontAttribute(), + mrSource.getLocale(), + mrSource.getFontColor(), + mrSource.getTextFillColor(), + + pTextDecoratedPortionPrimitive2D->getOverlineColor(), + pTextDecoratedPortionPrimitive2D->getTextlineColor(), + pTextDecoratedPortionPrimitive2D->getFontOverline(), + pTextDecoratedPortionPrimitive2D->getFontUnderline(), + pTextDecoratedPortionPrimitive2D->getUnderlineAbove(), + pTextDecoratedPortionPrimitive2D->getTextStrikeout(), + + // reset WordLineMode when BreakupUnit::Word is executed; else copy original + !bWordLineMode && pTextDecoratedPortionPrimitive2D->getWordLineMode(), + + pTextDecoratedPortionPrimitive2D->getTextEmphasisMark(), + pTextDecoratedPortionPrimitive2D->getEmphasisMarkAbove(), + pTextDecoratedPortionPrimitive2D->getEmphasisMarkBelow(), + pTextDecoratedPortionPrimitive2D->getTextRelief(), + pTextDecoratedPortionPrimitive2D->getShadow())); + } + else + { + // create a SimpleTextPrimitive + rTempResult.push_back( + new TextSimplePortionPrimitive2D( + aNewTransform, + mrSource.getText(), + nIndex, + nLength, + std::move(aNewDXArray), + mrSource.getFontAttribute(), + mrSource.getLocale(), + mrSource.getFontColor())); + } + } + + bool TextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& /*rNewTransform*/, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/) + { + return true; + } + + void TextBreakupHelper::breakup(BreakupUnit aBreakupUnit) + { + if(!mrSource.getTextLength()) + return; + + Primitive2DContainer aTempResult; + static css::uno::Reference< css::i18n::XBreakIterator > xBreakIterator; + + if(!xBreakIterator.is()) + { + css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xBreakIterator = css::i18n::BreakIterator::create(xContext); + } + + const OUString& rTxt = mrSource.getText(); + const sal_Int32 nTextLength(mrSource.getTextLength()); + const css::lang::Locale& rLocale = mrSource.getLocale(); + const sal_Int32 nTextPosition(mrSource.getTextPosition()); + sal_Int32 nCurrent(nTextPosition); + + switch(aBreakupUnit) + { + case BreakupUnit::Character: + { + sal_Int32 nDone; + sal_Int32 nNextCellBreak(xBreakIterator->nextCharacters(rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, nDone)); + sal_Int32 a(nTextPosition); + + for(; a < nTextPosition + nTextLength; a++) + { + if(a == nNextCellBreak) + { + breakupPortion(aTempResult, nCurrent, a - nCurrent, false); + nCurrent = a; + nNextCellBreak = xBreakIterator->nextCharacters(rTxt, a, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + } + } + + breakupPortion(aTempResult, nCurrent, a - nCurrent, false); + break; + } + case BreakupUnit::Word: + { + css::i18n::Boundary nNextWordBoundary(xBreakIterator->getWordBoundary(rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true)); + sal_Int32 a(nTextPosition); + + for(; a < nTextPosition + nTextLength; a++) + { + if(a == nNextWordBoundary.endPos) + { + if(a > nCurrent) + { + breakupPortion(aTempResult, nCurrent, a - nCurrent, true); + } + + nCurrent = a; + + // skip spaces (maybe enhanced with a bool later if needed) + { + const sal_Int32 nEndOfSpaces(xBreakIterator->endOfCharBlock(rTxt, a, rLocale, css::i18n::CharType::SPACE_SEPARATOR)); + + if(nEndOfSpaces > a) + { + nCurrent = nEndOfSpaces; + } + } + + nNextWordBoundary = xBreakIterator->getWordBoundary(rTxt, a + 1, rLocale, css::i18n::WordType::ANY_WORD, true); + } + } + + if(a > nCurrent) + { + breakupPortion(aTempResult, nCurrent, a - nCurrent, true); + } + break; + } + } + + mxResult = aTempResult; + } + + Primitive2DContainer TextBreakupHelper::extractResult(BreakupUnit aBreakupUnit) + { + if(mxResult.empty()) + { + breakup(aBreakupUnit); + } + + return std::move(mxResult); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx new file mode 100644 index 000000000..0db26fbeb --- /dev/null +++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx @@ -0,0 +1,388 @@ +/* -*- 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/textdecoratedprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <primitive2d/texteffectprimitive2d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> +#include <primitive2d/textlineprimitive2d.hxx> +#include <primitive2d/textstrikeoutprimitive2d.hxx> +#include <drawinglayer/primitive2d/textbreakuphelper.hxx> + + +namespace drawinglayer::primitive2d +{ + void TextDecoratedPortionPrimitive2D::impCreateGeometryContent( + Primitive2DContainer& rTarget, + basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, + const OUString& rText, + sal_Int32 nTextPosition, + sal_Int32 nTextLength, + const std::vector< double >& rDXArray, + const attribute::FontAttribute& rFontAttribute) const + { + // create the SimpleTextPrimitive needed in any case + rTarget.push_back(Primitive2DReference( + new TextSimplePortionPrimitive2D( + rDecTrans.getB2DHomMatrix(), + rText, + nTextPosition, + nTextLength, + std::vector(rDXArray), + rFontAttribute, + getLocale(), + getFontColor()))); + + // see if something else needs to be done + const bool bOverlineUsed(TEXT_LINE_NONE != getFontOverline()); + const bool bUnderlineUsed(TEXT_LINE_NONE != getFontUnderline()); + const bool bStrikeoutUsed(TEXT_STRIKEOUT_NONE != getTextStrikeout()); + + if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)) + return; + + // common preparations + TextLayouterDevice aTextLayouter; + + // TextLayouterDevice is needed to get metrics for text decorations like + // underline/strikeout/emphasis marks from it. For setup, the font size is needed + aTextLayouter.setFontAttribute( + getFontAttribute(), + rDecTrans.getScale().getX(), + rDecTrans.getScale().getY(), + getLocale()); + + // get text width + double fTextWidth(0.0); + + if(rDXArray.empty()) + { + fTextWidth = aTextLayouter.getTextWidth(rText, nTextPosition, nTextLength); + } + else + { + fTextWidth = rDXArray.back() * rDecTrans.getScale().getX(); + const double fFontScaleX(rDecTrans.getScale().getX()); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + // need to take FontScaling out of the DXArray + fTextWidth /= fFontScaleX; + } + } + + if(bOverlineUsed) + { + // create primitive geometry for overline + rTarget.push_back(Primitive2DReference( + new TextLinePrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + aTextLayouter.getOverlineOffset(), + aTextLayouter.getOverlineHeight(), + getFontOverline(), + getOverlineColor()))); + } + + if(bUnderlineUsed) + { + // create primitive geometry for underline + rTarget.push_back(Primitive2DReference( + new TextLinePrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + aTextLayouter.getUnderlineOffset(), + aTextLayouter.getUnderlineHeight(), + getFontUnderline(), + getTextlineColor()))); + } + + if(!bStrikeoutUsed) + return; + + // create primitive geometry for strikeout + if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) + { + // strikeout with character + const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); + + rTarget.push_back(Primitive2DReference( + new TextCharacterStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aStrikeoutChar, + getFontAttribute(), + getLocale()))); + } + else + { + // strikeout with geometry + rTarget.push_back(Primitive2DReference( + new TextGeometryStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aTextLayouter.getUnderlineHeight(), + aTextLayouter.getStrikeoutOffset(), + getTextStrikeout()))); + } + + // TODO: Handle Font Emphasis Above/Below + } + + void TextDecoratedPortionPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getWordLineMode()) + { + // support for single word mode; split to single word primitives + // using TextBreakupHelper + TextBreakupHelper aTextBreakupHelper(*this); + Primitive2DContainer aBroken(aTextBreakupHelper.extractResult(BreakupUnit::Word)); + + if(!aBroken.empty()) + { + // was indeed split to several words, use as result + rContainer.append(std::move(aBroken)); + return; + } + else + { + // no split, was already a single word. Continue to + // decompose local entity + } + } + basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); + Primitive2DContainer aRetval; + + // create basic geometry such as SimpleTextPrimitive, Overline, Underline, + // Strikeout, etc... + // prepare new font attributes WITHOUT outline + const attribute::FontAttribute aNewFontAttribute( + getFontAttribute().getFamilyName(), + getFontAttribute().getStyleName(), + getFontAttribute().getWeight(), + getFontAttribute().getSymbol(), + getFontAttribute().getVertical(), + getFontAttribute().getItalic(), + getFontAttribute().getMonospaced(), + false, // no outline anymore, handled locally + getFontAttribute().getRTL(), + getFontAttribute().getBiDiStrong()); + + // handle as one word + impCreateGeometryContent(aRetval, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); + + // 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) + { + Primitive2DReference aShadow; + + if(bHasShadow) + { + // 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()) + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEngravedDefault; + } + else + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossedDefault; + } + } + else + { + if(TEXT_RELIEF_ENGRAVED == getTextRelief()) + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEngraved; + } + else + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossed; + } + } + + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + std::move(aRetval), + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + aTextEffectStyle2D)); + aRetval = Primitive2DContainer { aNewTextEffect }; + } + else if(bHasOutline) + { + // create outline using an own helper primitive since this will + // be view-dependent + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + std::move(aRetval), + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + TextEffectStyle2D::Outline)); + aRetval = Primitive2DContainer { aNewTextEffect }; + } + + 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.append(std::move(aRetval)); + } + + TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D( + // TextSimplePortionPrimitive2D parameters + 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, + const basegfx::BColor& rFontColor, + const Color& rFillColor, + + // local parameters + const basegfx::BColor& rOverlineColor, + const basegfx::BColor& rTextlineColor, + TextLine eFontOverline, + TextLine eFontUnderline, + bool bUnderlineAbove, + TextStrikeout eTextStrikeout, + bool bWordLineMode, + TextEmphasisMark eTextEmphasisMark, + bool bEmphasisMarkAbove, + bool bEmphasisMarkBelow, + TextRelief eTextRelief, + bool bShadow) + : TextSimplePortionPrimitive2D(rNewTransform, rText, nTextPosition, nTextLength, std::move(rDXArray), rFontAttribute, rLocale, rFontColor, false, 0, rFillColor), + maOverlineColor(rOverlineColor), + maTextlineColor(rTextlineColor), + meFontOverline(eFontOverline), + meFontUnderline(eFontUnderline), + meTextStrikeout(eTextStrikeout), + meTextEmphasisMark(eTextEmphasisMark), + meTextRelief(eTextRelief), + mbUnderlineAbove(bUnderlineAbove), + mbWordLineMode(bWordLineMode), + mbEmphasisMarkAbove(bEmphasisMarkAbove), + mbEmphasisMarkBelow(bEmphasisMarkBelow), + mbShadow(bShadow) + { + } + + bool TextDecoratedPortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(TextSimplePortionPrimitive2D::operator==(rPrimitive)) + { + const TextDecoratedPortionPrimitive2D& rCompare = static_cast<const TextDecoratedPortionPrimitive2D&>(rPrimitive); + + return (getOverlineColor() == rCompare.getOverlineColor() + && getTextlineColor() == rCompare.getTextlineColor() + && getFontOverline() == rCompare.getFontOverline() + && getFontUnderline() == rCompare.getFontUnderline() + && getTextStrikeout() == rCompare.getTextStrikeout() + && getTextEmphasisMark() == rCompare.getTextEmphasisMark() + && getTextRelief() == rCompare.getTextRelief() + && getUnderlineAbove() == rCompare.getUnderlineAbove() + && getWordLineMode() == rCompare.getWordLineMode() + && getEmphasisMarkAbove() == rCompare.getEmphasisMarkAbove() + && getEmphasisMarkBelow() == rCompare.getEmphasisMarkBelow() + && getShadow() == rCompare.getShadow()); + } + + return false; + } + + // #i96475# + // Added missing implementation. Decorations may (will) stick out of the text's + // inking area, so add them if needed + basegfx::B2DRange TextDecoratedPortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // check if this needs to be a TextDecoratedPortionPrimitive2D or + // if a TextSimplePortionPrimitive2D would be sufficient + if (TEXT_LINE_NONE != getFontOverline() + || TEXT_LINE_NONE != getFontUnderline() + || TEXT_STRIKEOUT_NONE != getTextStrikeout() + || TEXT_FONT_EMPHASIS_MARK_NONE != getTextEmphasisMark() + || TEXT_RELIEF_NONE != getTextRelief() + || getShadow()) + { + // decoration is used, fallback to BufferedDecompositionPrimitive2D::getB2DRange which uses + // the own local decomposition for computation and thus creates all necessary + // geometric objects + return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + } + else + { + // no relevant decoration used, fallback to TextSimplePortionPrimitive2D::getB2DRange + return TextSimplePortionPrimitive2D::getB2DRange(rViewInformation); + } + } + + // provide unique ID + sal_uInt32 TextDecoratedPortionPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx b/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx new file mode 100644 index 000000000..dae397e1a --- /dev/null +++ b/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx @@ -0,0 +1,245 @@ +/* -*- 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 <primitive2d/texteffectprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +namespace drawinglayer::primitive2d +{ +const double fDiscreteSize(1.1); + +void TextEffectPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, 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 + const basegfx::B2DVector aDistance(rViewInformation.getInverseObjectToViewTransformation() + * basegfx::B2DVector(fDiscreteSize, fDiscreteSize)); + const basegfx::B2DVector aDiagonalDistance(aDistance * (1.0 / 1.44)); + + switch (getTextEffectStyle2D()) + { + case TextEffectStyle2D::ReliefEmbossed: + case TextEffectStyle2D::ReliefEngraved: + case TextEffectStyle2D::ReliefEmbossedDefault: + case TextEffectStyle2D::ReliefEngravedDefault: + { + // prepare transform of sub-group back to (0,0) and align to X-Axis + basegfx::B2DHomMatrix aBackTransform(basegfx::utils::createTranslateB2DHomMatrix( + -getRotationCenter().getX(), -getRotationCenter().getY())); + aBackTransform.rotate(-getDirection()); + + // prepare transform of sub-group back to its position and rotation + basegfx::B2DHomMatrix aForwardTransform( + basegfx::utils::createRotateB2DHomMatrix(getDirection())); + aForwardTransform.translate(getRotationCenter().getX(), getRotationCenter().getY()); + + // create transformation for one discrete unit + const bool bEmbossed(TextEffectStyle2D::ReliefEmbossed == getTextEffectStyle2D() + || TextEffectStyle2D::ReliefEmbossedDefault + == getTextEffectStyle2D()); + const bool bDefaultTextColor( + TextEffectStyle2D::ReliefEmbossedDefault == getTextEffectStyle2D() + || TextEffectStyle2D::ReliefEngravedDefault == getTextEffectStyle2D()); + basegfx::B2DHomMatrix aTransform(aBackTransform); + + if (bEmbossed) + { + // to bottom-right + aTransform.translate(aDiagonalDistance.getX(), aDiagonalDistance.getY()); + } + else + { + // to top-left + aTransform.translate(-aDiagonalDistance.getX(), -aDiagonalDistance.getY()); + } + + aTransform *= aForwardTransform; + + if (bDefaultTextColor) + { + // emboss/engrave in black, original forced to white + const basegfx::BColorModifierSharedPtr aBColorModifierToGray + = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0.0)); + const Primitive2DReference xModifiedColor(new ModifiedColorPrimitive2D( + Primitive2DContainer(getTextContent()), aBColorModifierToGray)); + + rContainer.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( + Primitive2DContainer(getTextContent()), aBColorModifierToWhite)); + } + else + { + // emboss/engrave in gray, keep original's color + const basegfx::BColorModifierSharedPtr aBColorModifierToGray + = std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(0.75)); // 192 + const Primitive2DReference xModifiedColor(new ModifiedColorPrimitive2D( + Primitive2DContainer(getTextContent()), aBColorModifierToGray)); + + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer{ xModifiedColor })); + + // add original, too + rContainer.push_back(new GroupPrimitive2D(Primitive2DContainer(getTextContent()))); + } + + break; + } + case TextEffectStyle2D::Outline: + { + // create transform primitives in all directions + basegfx::B2DHomMatrix aTransform; + + aTransform.set(0, 2, aDistance.getX()); + aTransform.set(1, 2, 0.0); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, aDiagonalDistance.getX()); + aTransform.set(1, 2, aDiagonalDistance.getY()); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, 0.0); + aTransform.set(1, 2, aDistance.getY()); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, -aDiagonalDistance.getX()); + aTransform.set(1, 2, aDiagonalDistance.getY()); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, -aDistance.getX()); + aTransform.set(1, 2, 0.0); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, -aDiagonalDistance.getX()); + aTransform.set(1, 2, -aDiagonalDistance.getY()); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, 0.0); + aTransform.set(1, 2, -aDistance.getY()); + rContainer.push_back( + new TransformPrimitive2D(aTransform, Primitive2DContainer(getTextContent()))); + + aTransform.set(0, 2, aDiagonalDistance.getX()); + aTransform.set(1, 2, -aDiagonalDistance.getY()); + rContainer.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( + Primitive2DContainer(getTextContent()), aBColorModifierToWhite)); + + break; + } + } +} + +TextEffectPrimitive2D::TextEffectPrimitive2D(Primitive2DContainer&& rTextContent, + const basegfx::B2DPoint& rRotationCenter, + double fDirection, + TextEffectStyle2D eTextEffectStyle2D) + : maTextContent(std::move(rTextContent)) + , maRotationCenter(rRotationCenter) + , mfDirection(fDirection) + , meTextEffectStyle2D(eTextEffectStyle2D) +{ +} + +bool TextEffectPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const TextEffectPrimitive2D& rCompare + = static_cast<const TextEffectPrimitive2D&>(rPrimitive); + + return (getTextContent() == rCompare.getTextContent() + && getRotationCenter() == rCompare.getRotationCenter() + && getDirection() == rCompare.getDirection() + && getTextEffectStyle2D() == rCompare.getTextEffectStyle2D()); + } + + return false; +} + +basegfx::B2DRange +TextEffectPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + // get range of content and grow by used fDiscreteSize. That way it is not necessary to ask + // the whole decomposition for its ranges (which may be expensive with outline mode which + // then will ask 9 times at nearly the same content. This may even be refined here using the + // TextEffectStyle information, e.g. for TEXTEFFECTSTYLE2D_RELIEF the grow needs only to + // be in two directions + basegfx::B2DRange aRetval(getTextContent().getB2DRange(rViewInformation)); + aRetval.grow(fDiscreteSize); + + return aRetval; +} + +void TextEffectPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + if (!getBuffered2DDecomposition().empty()) + { + if (maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast<TextEffectPrimitive2D*>(this)->setBuffered2DDecomposition( + Primitive2DContainer()); + } + } + + if (getBuffered2DDecomposition().empty()) + { + // remember ViewRange and ViewTransformation + const_cast<TextEffectPrimitive2D*>(this)->maLastObjectToViewTransformation + = rViewInformation.getObjectToViewTransformation(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); +} + +// provide unique ID +sal_uInt32 TextEffectPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_TEXTEFFECTPRIMITIVE2D; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textenumsprimitive2d.cxx b/drawinglayer/source/primitive2d/textenumsprimitive2d.cxx new file mode 100644 index 000000000..7f13cbbbe --- /dev/null +++ b/drawinglayer/source/primitive2d/textenumsprimitive2d.cxx @@ -0,0 +1,105 @@ +/* -*- 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/textenumsprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + TextLine mapFontLineStyleToTextLine(FontLineStyle eLineStyle) + { + switch(eLineStyle) + { + case LINESTYLE_SINGLE: return TEXT_LINE_SINGLE; + case LINESTYLE_DOUBLE: return TEXT_LINE_DOUBLE; + case LINESTYLE_DOTTED: return TEXT_LINE_DOTTED; + case LINESTYLE_DASH: return TEXT_LINE_DASH; + case LINESTYLE_LONGDASH: return TEXT_LINE_LONGDASH; + case LINESTYLE_DASHDOT: return TEXT_LINE_DASHDOT; + case LINESTYLE_DASHDOTDOT: return TEXT_LINE_DASHDOTDOT; + case LINESTYLE_SMALLWAVE: return TEXT_LINE_SMALLWAVE; + case LINESTYLE_WAVE: return TEXT_LINE_WAVE; + case LINESTYLE_DOUBLEWAVE: return TEXT_LINE_DOUBLEWAVE; + case LINESTYLE_BOLD: return TEXT_LINE_BOLD; + case LINESTYLE_BOLDDOTTED: return TEXT_LINE_BOLDDOTTED; + case LINESTYLE_BOLDDASH: return TEXT_LINE_BOLDDASH; + case LINESTYLE_BOLDLONGDASH: return TEXT_LINE_BOLDLONGDASH; + case LINESTYLE_BOLDDASHDOT: return TEXT_LINE_BOLDDASHDOT; + case LINESTYLE_BOLDDASHDOTDOT: return TEXT_LINE_BOLDDASHDOTDOT; + case LINESTYLE_BOLDWAVE: return TEXT_LINE_BOLDWAVE; + // FontLineStyle_FORCE_EQUAL_SIZE, LINESTYLE_DONTKNOW, LINESTYLE_NONE + default: return TEXT_LINE_NONE; + } + } + + FontLineStyle mapTextLineToFontLineStyle(TextLine eLineStyle) + { + switch(eLineStyle) + { + default: /*TEXT_LINE_NONE*/ return LINESTYLE_NONE; + case TEXT_LINE_SINGLE: return LINESTYLE_SINGLE; + case TEXT_LINE_DOUBLE: return LINESTYLE_DOUBLE; + case TEXT_LINE_DOTTED: return LINESTYLE_DOTTED; + case TEXT_LINE_DASH: return LINESTYLE_DASH; + case TEXT_LINE_LONGDASH: return LINESTYLE_LONGDASH; + case TEXT_LINE_DASHDOT: return LINESTYLE_DASHDOT; + case TEXT_LINE_DASHDOTDOT: return LINESTYLE_DASHDOTDOT; + case TEXT_LINE_SMALLWAVE: return LINESTYLE_SMALLWAVE; + case TEXT_LINE_WAVE: return LINESTYLE_WAVE; + case TEXT_LINE_DOUBLEWAVE: return LINESTYLE_DOUBLEWAVE; + case TEXT_LINE_BOLD: return LINESTYLE_BOLD; + case TEXT_LINE_BOLDDOTTED: return LINESTYLE_BOLDDOTTED; + case TEXT_LINE_BOLDDASH: return LINESTYLE_BOLDDASH; + case TEXT_LINE_BOLDLONGDASH: return LINESTYLE_LONGDASH; + case TEXT_LINE_BOLDDASHDOT: return LINESTYLE_BOLDDASHDOT; + case TEXT_LINE_BOLDDASHDOTDOT:return LINESTYLE_BOLDDASHDOTDOT; + case TEXT_LINE_BOLDWAVE: return LINESTYLE_BOLDWAVE; + } + } + + TextStrikeout mapFontStrikeoutToTextStrikeout(FontStrikeout eFontStrikeout) + { + switch(eFontStrikeout) + { + case STRIKEOUT_SINGLE: return TEXT_STRIKEOUT_SINGLE; + case STRIKEOUT_DOUBLE: return TEXT_STRIKEOUT_DOUBLE; + case STRIKEOUT_BOLD: return TEXT_STRIKEOUT_BOLD; + case STRIKEOUT_SLASH: return TEXT_STRIKEOUT_SLASH; + case STRIKEOUT_X: return TEXT_STRIKEOUT_X; + // FontStrikeout_FORCE_EQUAL_SIZE, STRIKEOUT_NONE, STRIKEOUT_DONTKNOW + default: return TEXT_STRIKEOUT_NONE; + } + } + + FontStrikeout mapTextStrikeoutToFontStrikeout(TextStrikeout eTextStrikeout) + { + switch(eTextStrikeout) + { + default: /*case primitive2d::TEXT_STRIKEOUT_NONE*/ return STRIKEOUT_NONE; + case TEXT_STRIKEOUT_SINGLE: return STRIKEOUT_SINGLE; + case TEXT_STRIKEOUT_DOUBLE: return STRIKEOUT_DOUBLE; + case TEXT_STRIKEOUT_BOLD: return STRIKEOUT_BOLD; + case TEXT_STRIKEOUT_SLASH: return STRIKEOUT_SLASH; + case TEXT_STRIKEOUT_X: return STRIKEOUT_X; + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx b/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx new file mode 100644 index 000000000..c7af9562a --- /dev/null +++ b/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx @@ -0,0 +1,152 @@ +/* -*- 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/texthierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + TextHierarchyLinePrimitive2D::TextHierarchyLinePrimitive2D(Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + // provide unique ID + sal_uInt32 TextHierarchyLinePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D; + } + + + TextHierarchyParagraphPrimitive2D::TextHierarchyParagraphPrimitive2D( + Primitive2DContainer&& aChildren, + sal_Int16 nOutlineLevel) + : GroupPrimitive2D(std::move(aChildren)), + mnOutlineLevel(nOutlineLevel) + { + } + + bool TextHierarchyParagraphPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TextHierarchyParagraphPrimitive2D& rCompare = static_cast<const TextHierarchyParagraphPrimitive2D&>(rPrimitive); + + return (getOutlineLevel() == rCompare.getOutlineLevel()); + } + + return false; + } + + // provide unique ID + sal_uInt32 TextHierarchyParagraphPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D; + } + + + + TextHierarchyBulletPrimitive2D::TextHierarchyBulletPrimitive2D(Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + // provide unique ID + sal_uInt32 TextHierarchyBulletPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D; + } + + + TextHierarchyBlockPrimitive2D::TextHierarchyBlockPrimitive2D(Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + // provide unique ID + sal_uInt32 TextHierarchyBlockPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D; + } + + + TextHierarchyFieldPrimitive2D::TextHierarchyFieldPrimitive2D( + Primitive2DContainer&& aChildren, + const FieldType& rFieldType, + const std::vector< std::pair< OUString, OUString>>* pNameValue) + : GroupPrimitive2D(std::move(aChildren)), + meType(rFieldType) + { + if (nullptr != pNameValue) + { + meNameValue = *pNameValue; + } + } + + OUString TextHierarchyFieldPrimitive2D::getValue(const OUString& rName) const + { + for (const std::pair< OUString, OUString >& candidate : meNameValue) + { + if (candidate.first.equals(rName)) + { + return candidate.second; + } + } + + return OUString(); + } + + bool TextHierarchyFieldPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TextHierarchyFieldPrimitive2D& rCompare = static_cast<const TextHierarchyFieldPrimitive2D&>(rPrimitive); + + return (getType() == rCompare.getType() + && meNameValue == rCompare.meNameValue); + } + + return false; + } + + // provide unique ID + sal_uInt32 TextHierarchyFieldPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D; + } + + + TextHierarchyEditPrimitive2D::TextHierarchyEditPrimitive2D(Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)) + { + } + + // provide unique ID + sal_uInt32 TextHierarchyEditPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textlayoutdevice.cxx b/drawinglayer/source/primitive2d/textlayoutdevice.cxx new file mode 100644 index 000000000..f70f9f63b --- /dev/null +++ b/drawinglayer/source/primitive2d/textlayoutdevice.cxx @@ -0,0 +1,456 @@ +/* -*- 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 <algorithm> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/unique_disposing_ptr.hxx> +#include <osl/diagnose.h> +#include <tools/gen.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/timer.hxx> +#include <vcl/virdev.hxx> +#include <vcl/font.hxx> +#include <vcl/metric.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/svapp.hxx> + +namespace drawinglayer::primitive2d +{ +namespace +{ +class ImpTimedRefDev; + +// VDev RevDevice provider + +//the scoped_timed_RefDev owns an ImpTimeRefDev and releases it on dtor +//or disposing of the default XComponentContext which causes the underlying +//OutputDevice to get released + +//The ImpTimerRefDev itself, if the timeout ever gets hit, will call +//reset on the scoped_timed_RefDev to release the ImpTimerRefDev early +//if it's unused for a few minutes +class scoped_timed_RefDev : public comphelper::unique_disposing_ptr<ImpTimedRefDev> +{ +public: + scoped_timed_RefDev() + : comphelper::unique_disposing_ptr<ImpTimedRefDev>( + (css::uno::Reference<css::lang::XComponent>( + ::comphelper::getProcessComponentContext(), css::uno::UNO_QUERY_THROW))) + { + } +}; + +class the_scoped_timed_RefDev : public rtl::Static<scoped_timed_RefDev, the_scoped_timed_RefDev> +{ +}; + +class ImpTimedRefDev : public Timer +{ + scoped_timed_RefDev& mrOwnerOfMe; + VclPtr<VirtualDevice> mpVirDev; + sal_uInt32 mnUseCount; + +public: + explicit ImpTimedRefDev(scoped_timed_RefDev& rOwnerofMe); + virtual ~ImpTimedRefDev() override; + virtual void Invoke() override; + + VirtualDevice& acquireVirtualDevice(); + void releaseVirtualDevice(); +}; + +ImpTimedRefDev::ImpTimedRefDev(scoped_timed_RefDev& rOwnerOfMe) + : Timer("drawinglayer ImpTimedRefDev destroy mpVirDev") + , mrOwnerOfMe(rOwnerOfMe) + , mpVirDev(nullptr) + , mnUseCount(0) +{ + SetTimeout(3L * 60L * 1000L); // three minutes + Start(); +} + +ImpTimedRefDev::~ImpTimedRefDev() +{ + OSL_ENSURE(0 == mnUseCount, "destruction of a still used ImpTimedRefDev (!)"); + const SolarMutexGuard aSolarGuard; + mpVirDev.disposeAndClear(); +} + +void ImpTimedRefDev::Invoke() +{ + // for obvious reasons, do not call anything after this + mrOwnerOfMe.reset(); +} + +VirtualDevice& ImpTimedRefDev::acquireVirtualDevice() +{ + if (!mpVirDev) + { + mpVirDev = VclPtr<VirtualDevice>::Create(); + mpVirDev->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1); + } + + if (!mnUseCount) + { + Stop(); + } + + mnUseCount++; + + return *mpVirDev; +} + +void ImpTimedRefDev::releaseVirtualDevice() +{ + OSL_ENSURE(mnUseCount, "mismatch call number to releaseVirtualDevice() (!)"); + mnUseCount--; + + if (!mnUseCount) + { + Start(); + } +} + +VirtualDevice& acquireGlobalVirtualDevice() +{ + scoped_timed_RefDev& rStdRefDevice = the_scoped_timed_RefDev::get(); + + if (!rStdRefDevice) + rStdRefDevice.reset(new ImpTimedRefDev(rStdRefDevice)); + + return rStdRefDevice->acquireVirtualDevice(); +} + +void releaseGlobalVirtualDevice() +{ + scoped_timed_RefDev& rStdRefDevice = the_scoped_timed_RefDev::get(); + + OSL_ENSURE(rStdRefDevice, + "releaseGlobalVirtualDevice() without prior acquireGlobalVirtualDevice() call(!)"); + rStdRefDevice->releaseVirtualDevice(); +} + +} // end of anonymous namespace + +TextLayouterDevice::TextLayouterDevice() + : mrDevice(acquireGlobalVirtualDevice()) +{ +} + +TextLayouterDevice::~TextLayouterDevice() COVERITY_NOEXCEPT_FALSE { releaseGlobalVirtualDevice(); } + +void TextLayouterDevice::setFont(const vcl::Font& rFont) { mrDevice.SetFont(rFont); } + +void TextLayouterDevice::setFontAttribute(const attribute::FontAttribute& rFontAttribute, + double fFontScaleX, double fFontScaleY, + const css::lang::Locale& rLocale) +{ + setFont(getVclFontFromFontAttribute(rFontAttribute, fFontScaleX, fFontScaleY, 0.0, rLocale)); +} + +double TextLayouterDevice::getOverlineOffset() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = (rMetric.GetInternalLeading() / 2.0) - rMetric.GetAscent(); + return fRet; +} + +double TextLayouterDevice::getUnderlineOffset() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = rMetric.GetDescent() / 2.0; + return fRet; +} + +double TextLayouterDevice::getStrikeoutOffset() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = (rMetric.GetAscent() - rMetric.GetInternalLeading()) / 3.0; + return fRet; +} + +double TextLayouterDevice::getOverlineHeight() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = rMetric.GetInternalLeading() / 2.5; + return fRet; +} + +double TextLayouterDevice::getUnderlineHeight() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = rMetric.GetDescent() / 4.0; + return fRet; +} + +double TextLayouterDevice::getTextHeight() const { return mrDevice.GetTextHeight(); } + +double TextLayouterDevice::getTextWidth(const OUString& rText, sal_uInt32 nIndex, + sal_uInt32 nLength) const +{ + return mrDevice.GetTextWidth(rText, nIndex, nLength); +} + +void TextLayouterDevice::getTextOutlines(basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector, + const OUString& rText, sal_uInt32 nIndex, + sal_uInt32 nLength, + const std::vector<double>& rDXArray) const +{ + const sal_uInt32 nDXArrayCount(rDXArray.size()); + sal_uInt32 nTextLength(nLength); + const sal_uInt32 nStringLength(rText.getLength()); + + if (nTextLength + nIndex > nStringLength) + { + nTextLength = nStringLength - nIndex; + } + + if (nDXArrayCount) + { + OSL_ENSURE(nDXArrayCount == nTextLength, + "DXArray size does not correspond to text portion size (!)"); + std::vector<sal_Int32> aIntegerDXArray(nDXArrayCount); + + for (sal_uInt32 a(0); a < nDXArrayCount; a++) + { + aIntegerDXArray[a] = basegfx::fround(rDXArray[a]); + } + + mrDevice.GetTextOutlines(rB2DPolyPolyVector, rText, nIndex, nIndex, nLength, 0, + aIntegerDXArray); + } + else + { + mrDevice.GetTextOutlines(rB2DPolyPolyVector, rText, nIndex, nIndex, nLength); + } +} + +basegfx::B2DRange TextLayouterDevice::getTextBoundRect(const OUString& rText, sal_uInt32 nIndex, + sal_uInt32 nLength) const +{ + sal_uInt32 nTextLength(nLength); + const sal_uInt32 nStringLength(rText.getLength()); + + if (nTextLength + nIndex > nStringLength) + { + nTextLength = nStringLength - nIndex; + } + + if (nTextLength) + { + ::tools::Rectangle aRect; + + mrDevice.GetTextBoundRect(aRect, rText, nIndex, nIndex, nLength); + + // #i104432#, #i102556# take empty results into account + if (!aRect.IsEmpty()) + { + return vcl::unotools::b2DRectangleFromRectangle(aRect); + } + } + + return basegfx::B2DRange(); +} + +double TextLayouterDevice::getFontAscent() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + return rMetric.GetAscent(); +} + +double TextLayouterDevice::getFontDescent() const +{ + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + return rMetric.GetDescent(); +} + +void TextLayouterDevice::addTextRectActions(const ::tools::Rectangle& rRectangle, + const OUString& rText, DrawTextFlags nStyle, + GDIMetaFile& rGDIMetaFile) const +{ + mrDevice.AddTextRectActions(rRectangle, rText, nStyle, rGDIMetaFile); +} + +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, nTextLength); + aRetval.assign(aArray.begin(), aArray.end()); + } + + return aRetval; +} + +std::vector<double> TextLayouterDevice::getCaretPositions(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(2 * nTextLength); + std::vector<sal_Int32> aArray(2 * nTextLength); + mrDevice.GetCaretPositions(rText, aArray.data(), nIndex, nTextLength); + aRetval.assign(aArray.begin(), aArray.end()); + } + + return aRetval; +} + +// helper methods for vcl font handling + +vcl::Font getVclFontFromFontAttribute(const attribute::FontAttribute& rFontAttribute, + double fFontScaleX, double fFontScaleY, double fFontRotation, + const css::lang::Locale& rLocale) +{ + // detect FontScaling + const sal_uInt32 nHeight(basegfx::fround(fabs(fFontScaleY))); + const sal_uInt32 nWidth(basegfx::fround(fabs(fFontScaleX))); + const bool bFontIsScaled(nHeight != nWidth); + +#ifdef _WIN32 + // for WIN32 systems, start with creating an unscaled font. If FontScaling + // is wanted, that width needs to be adapted using FontMetric again to get a + // width of the unscaled font + vcl::Font aRetval(rFontAttribute.getFamilyName(), rFontAttribute.getStyleName(), + Size(0, nHeight)); +#else + // for non-WIN32 systems things are easier since these accept a Font creation + // with initially nWidth != nHeight for FontScaling. Despite that, use zero for + // FontWidth when no scaling is used to explicitly have that zero when e.g. the + // Font would be recorded in a MetaFile (The MetaFile FontAction WILL record a + // set FontWidth; import that in a WIN32 system, and trouble is there) + vcl::Font aRetval(rFontAttribute.getFamilyName(), rFontAttribute.getStyleName(), + Size(bFontIsScaled ? std::max<sal_uInt32>(nWidth, 1) : 0, nHeight)); +#endif + // define various other FontAttribute + aRetval.SetAlignment(ALIGN_BASELINE); + aRetval.SetCharSet(rFontAttribute.getSymbol() ? RTL_TEXTENCODING_SYMBOL + : RTL_TEXTENCODING_UNICODE); + aRetval.SetVertical(rFontAttribute.getVertical()); + aRetval.SetWeight(static_cast<FontWeight>(rFontAttribute.getWeight())); + aRetval.SetItalic(rFontAttribute.getItalic() ? ITALIC_NORMAL : ITALIC_NONE); + aRetval.SetOutline(rFontAttribute.getOutline()); + aRetval.SetPitch(rFontAttribute.getMonospaced() ? PITCH_FIXED : PITCH_VARIABLE); + aRetval.SetLanguage(LanguageTag::convertToLanguageType(rLocale, false)); + +#ifdef _WIN32 + // for WIN32 systems, correct the FontWidth if FontScaling is used + if (bFontIsScaled && nHeight > 0) + { + const FontMetric aUnscaledFontMetric( + Application::GetDefaultDevice()->GetFontMetric(aRetval)); + + if (aUnscaledFontMetric.GetAverageFontWidth() > 0) + { + const double fScaleFactor(static_cast<double>(nWidth) / static_cast<double>(nHeight)); + const sal_uInt32 nScaledWidth(basegfx::fround( + static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()) * fScaleFactor)); + aRetval.SetAverageFontWidth(nScaledWidth); + } + } +#endif + // handle FontRotation (if defined) + if (!basegfx::fTools::equalZero(fFontRotation)) + { + int aRotate10th(-basegfx::rad2deg<10>(fFontRotation)); + aRetval.SetOrientation(Degree10(aRotate10th % 3600)); + } + + return aRetval; +} + +attribute::FontAttribute getFontAttributeFromVclFont(basegfx::B2DVector& o_rSize, + const vcl::Font& rFont, bool bRTL, + bool bBiDiStrong) +{ + const attribute::FontAttribute aRetval( + rFont.GetFamilyName(), rFont.GetStyleName(), static_cast<sal_uInt16>(rFont.GetWeight()), + RTL_TEXTENCODING_SYMBOL == rFont.GetCharSet(), rFont.IsVertical(), + ITALIC_NONE != rFont.GetItalic(), PITCH_FIXED == rFont.GetPitch(), rFont.IsOutline(), bRTL, + bBiDiStrong); + // TODO: eKerning + + // set FontHeight and init to no FontScaling + o_rSize.setY(std::max<tools::Long>(rFont.GetFontSize().getHeight(), 0)); + o_rSize.setX(o_rSize.getY()); + +#ifdef _WIN32 + // for WIN32 systems, the FontScaling at the Font is detected by + // checking that FontWidth != 0. When FontScaling is used, WIN32 + // needs to do extra stuff to detect the correct width (since it's + // zero and not equal the font height) and its relationship to + // the height + if (rFont.GetFontSize().getWidth() > 0) + { + vcl::Font aUnscaledFont(rFont); + aUnscaledFont.SetAverageFontWidth(0); + const FontMetric aUnscaledFontMetric( + Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont)); + + if (aUnscaledFontMetric.GetAverageFontWidth() > 0) + { + const double fScaleFactor( + static_cast<double>(rFont.GetFontSize().getWidth()) + / static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth())); + o_rSize.setX(fScaleFactor * o_rSize.getY()); + } + } +#else + // For non-WIN32 systems the detection is the same, but the value + // is easier achieved since width == height is interpreted as no + // scaling. Ergo, Width == 0 means width == height, and width != 0 + // means the scaling is in the direct relation of width to height + if (rFont.GetFontSize().getWidth() > 0) + { + o_rSize.setX(static_cast<double>(rFont.GetFontSize().getWidth())); + } +#endif + return aRetval; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textlineprimitive2d.cxx b/drawinglayer/source/primitive2d/textlineprimitive2d.cxx new file mode 100644 index 000000000..461a83d75 --- /dev/null +++ b/drawinglayer/source/primitive2d/textlineprimitive2d.cxx @@ -0,0 +1,290 @@ +/* -*- 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 <primitive2d/textlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/attribute/strokeattribute.hxx> +#include <drawinglayer/attribute/lineattribute.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> + + +namespace drawinglayer::primitive2d +{ + void TextLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(TEXT_LINE_NONE == getTextLine()) + return; + + bool bDoubleLine(false); + bool bWaveLine(false); + bool bBoldLine(false); + const int* pDotDashArray(nullptr); + basegfx::B2DLineJoin eLineJoin(basegfx::B2DLineJoin::NONE); + double fOffset(getOffset()); + double fHeight(getHeight()); + + static const int aDottedArray[] = { 1, 1, 0}; // DOTTED LINE + static const int aDotDashArray[] = { 1, 1, 4, 1, 0}; // DASHDOT + static const int aDashDotDotArray[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT + static const int aDashedArray[] = { 5, 2, 0}; // DASHED LINE + static const int aLongDashArray[] = { 7, 2, 0}; // LONGDASH + + // get decomposition + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getObjectTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + switch(getTextLine()) + { + default: // case TEXT_LINE_SINGLE: + { + break; + } + case TEXT_LINE_DOUBLE: + { + bDoubleLine = true; + break; + } + case TEXT_LINE_DOTTED: + { + pDotDashArray = aDottedArray; + break; + } + case TEXT_LINE_DASH: + { + pDotDashArray = aDashedArray; + break; + } + case TEXT_LINE_LONGDASH: + { + pDotDashArray = aLongDashArray; + break; + } + case TEXT_LINE_DASHDOT: + { + pDotDashArray = aDotDashArray; + break; + } + case TEXT_LINE_DASHDOTDOT: + { + pDotDashArray = aDashDotDotArray; + break; + } + case TEXT_LINE_SMALLWAVE: + { + bWaveLine = true; + break; + } + case TEXT_LINE_WAVE: + { + bWaveLine = true; + break; + } + case TEXT_LINE_DOUBLEWAVE: + { + bDoubleLine = true; + bWaveLine = true; + break; + } + case TEXT_LINE_BOLD: + { + bBoldLine = true; + break; + } + case TEXT_LINE_BOLDDOTTED: + { + bBoldLine = true; + pDotDashArray = aDottedArray; + break; + } + case TEXT_LINE_BOLDDASH: + { + bBoldLine = true; + pDotDashArray = aDashedArray; + break; + } + case TEXT_LINE_BOLDLONGDASH: + { + bBoldLine = true; + pDotDashArray = aLongDashArray; + break; + } + case TEXT_LINE_BOLDDASHDOT: + { + bBoldLine = true; + pDotDashArray = aDotDashArray; + break; + } + case TEXT_LINE_BOLDDASHDOTDOT: + { + bBoldLine = true; + pDotDashArray = aDashDotDotArray; + break; + } + case TEXT_LINE_BOLDWAVE: + { + bWaveLine = true; + bBoldLine = true; + break; + } + } + + if(bBoldLine) + { + fHeight *= 2.0; + } + + if(bDoubleLine) + { + fOffset -= 0.50 * fHeight; + fHeight *= 0.64; + } + + if(bWaveLine) + { + eLineJoin = basegfx::B2DLineJoin::Round; + fHeight *= 0.25; + } + + // prepare Line and Stroke Attributes + const attribute::LineAttribute aLineAttribute(getLineColor(), fHeight, eLineJoin); + attribute::StrokeAttribute aStrokeAttribute; + + if(pDotDashArray) + { + std::vector< double > aDoubleArray; + + for(const int* p = pDotDashArray; *p; ++p) + { + aDoubleArray.push_back(static_cast<double>(*p) * fHeight); + } + + aStrokeAttribute = attribute::StrokeAttribute(std::move(aDoubleArray)); + } + + // create base polygon and new primitive + basegfx::B2DPolygon aLine; + Primitive2DReference aNewPrimitive; + + aLine.append(basegfx::B2DPoint(0.0, fOffset)); + aLine.append(basegfx::B2DPoint(getWidth(), fOffset)); + + const basegfx::B2DHomMatrix aUnscaledTransform( + basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate)); + + aLine.transform(aUnscaledTransform); + + if(bWaveLine) + { + double fWaveWidth(10.6 * fHeight); + + if(TEXT_LINE_SMALLWAVE == getTextLine()) + { + fWaveWidth *= 0.7; + } + else if(TEXT_LINE_WAVE == getTextLine()) + { + // extra multiply to get the same WaveWidth as with the bold version + fWaveWidth *= 2.0; + } + + aNewPrimitive = Primitive2DReference(new PolygonWavePrimitive2D(aLine, aLineAttribute, aStrokeAttribute, fWaveWidth, fWaveWidth * 0.5)); + } + else + { + aNewPrimitive = Primitive2DReference(new PolygonStrokePrimitive2D(aLine, aLineAttribute, aStrokeAttribute)); + } + + // add primitive + rContainer.push_back(aNewPrimitive); + + if(!bDoubleLine) + return; + + // double line, create 2nd primitive with offset using TransformPrimitive based on + // already created NewPrimitive + double fLineDist(2.3 * fHeight); + + if(bWaveLine) + { + fLineDist = 6.3 * fHeight; + } + + // move base point of text to 0.0 and de-rotate + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aTranslate.getX(), -aTranslate.getY())); + aTransform.rotate(-fRotate); + + // translate in Y by offset + aTransform.translate(0.0, fLineDist); + + // move back and rotate + aTransform.rotate(fRotate); + aTransform.translate(aTranslate.getX(), aTranslate.getY()); + + // add transform primitive + Primitive2DContainer aContent { aNewPrimitive }; + rContainer.push_back( new TransformPrimitive2D(aTransform, std::move(aContent)) ); + } + + TextLinePrimitive2D::TextLinePrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + double fOffset, + double fHeight, + TextLine eTextLine, + const basegfx::BColor& rLineColor) + : maObjectTransformation(rObjectTransformation), + mfWidth(fWidth), + mfOffset(fOffset), + mfHeight(fHeight), + meTextLine(eTextLine), + maLineColor(rLineColor) + { + } + + bool TextLinePrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const TextLinePrimitive2D& rCompare = static_cast<const TextLinePrimitive2D&>(rPrimitive); + + return (getObjectTransformation() == rCompare.getObjectTransformation() + && getWidth() == rCompare.getWidth() + && getOffset() == rCompare.getOffset() + && getHeight() == rCompare.getHeight() + && getTextLine() == rCompare.getTextLine() + && getLineColor() == rCompare.getLineColor()); + } + + return false; + } + + // provide unique ID + sal_uInt32 TextLinePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTLINEPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textprimitive2d.cxx b/drawinglayer/source/primitive2d/textprimitive2d.cxx new file mode 100644 index 000000000..00135a3aa --- /dev/null +++ b/drawinglayer/source/primitive2d/textprimitive2d.cxx @@ -0,0 +1,309 @@ +/* -*- 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/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <primitive2d/texteffectprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +namespace +{ +// adapts fontScale for usage with TextLayouter. Input is rScale which is the extracted +// scale from a text transformation. A copy is modified so that it contains only positive +// scalings and XY-equal scalings to allow to get a non-X-scaled Vcl-Font for TextLayouter. +// rScale is adapted accordingly to contain the corrected scale which would need to be +// applied to e.g. outlines received from TextLayouter under usage of fontScale. This +// includes Y-Scale, X-Scale-correction and mirrorings. +basegfx::B2DVector getCorrectedScaleAndFontScale(basegfx::B2DVector& rScale) +{ + // copy input value + basegfx::B2DVector aFontScale(rScale); + + // correct FontHeight settings + if (basegfx::fTools::equalZero(aFontScale.getY())) + { + // no font height; choose one and adapt scale to get back to original scaling + static const double fDefaultFontScale(100.0); + rScale.setY(1.0 / fDefaultFontScale); + aFontScale.setY(fDefaultFontScale); + } + else if (basegfx::fTools::less(aFontScale.getY(), 0.0)) + { + // negative font height; invert and adapt scale to get back to original scaling + aFontScale.setY(-aFontScale.getY()); + rScale.setY(-1.0); + } + else + { + // positive font height; adapt scale; scaling will be part of the polygons + rScale.setY(1.0); + } + + // correct FontWidth settings + if (basegfx::fTools::equal(aFontScale.getX(), aFontScale.getY())) + { + // no FontScale, adapt scale + rScale.setX(1.0); + } + else + { + // If FontScale is used, force to no FontScale to get a non-scaled VCL font. + // Adapt scaling in X accordingly. + rScale.setX(aFontScale.getX() / aFontScale.getY()); + aFontScale.setX(aFontScale.getY()); + } + + return aFontScale; +} +} // end of anonymous namespace + +void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation( + basegfx::B2DPolyPolygonVector& rTarget, basegfx::B2DHomMatrix& rTransformation) const +{ + if (!getTextLength()) + return; + + // decompose object transformation to single values + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + // if decomposition returns false, create no geometry since e.g. scaling may + // be zero + if (!(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX) + && aScale.getX() != 0.0)) + return; + + // 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)) + { + aScale = basegfx::absolute(aScale); + fRotate += M_PI; + } + + // for the TextLayouterDevice, it is necessary to have a scaling representing + // the font size. Since we want to extract polygons here, it is okay to + // work just with scaling and to ignore shear, rotation and translation, + // all that can be applied to the polygons later + const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale)); + + // prepare textlayoutdevice + TextLayouterDevice aTextLayouter; + aTextLayouter.setFontAttribute(getFontAttribute(), aFontScale.getX(), aFontScale.getY(), + getLocale()); + + // When getting outlines from stretched text (aScale.getX() != 1.0) it + // is necessary to inverse-scale the DXArray (if used) to not get the + // outlines already aligned to given, but wrong DXArray + if (!getDXArray().empty() && !basegfx::fTools::equal(aScale.getX(), 1.0)) + { + std::vector<double> aScaledDXArray = getDXArray(); + const double fDXArrayScale(1.0 / aScale.getX()); + + for (double& a : aScaledDXArray) + { + a *= fDXArrayScale; + } + + // get the text outlines + aTextLayouter.getTextOutlines(rTarget, getText(), getTextPosition(), getTextLength(), + aScaledDXArray); + } + else + { + // get the text outlines + aTextLayouter.getTextOutlines(rTarget, getText(), getTextPosition(), getTextLength(), + getDXArray()); + } + + // create primitives for the outlines + const sal_uInt32 nCount(rTarget.size()); + + if (nCount) + { + // prepare object transformation for polygons + rTransformation = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, fShearX, fRotate, aTranslate); + } +} + +void TextSimplePortionPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getTextLength()) + return; + + Primitive2DContainer aRetval; + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + basegfx::B2DHomMatrix aPolygonTransform; + + // get text outlines and their object transformation + getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform); + + // create primitives for the outlines + const sal_uInt32 nCount(aB2DPolyPolyVector.size()); + + if (!nCount) + return; + + // alloc space for the primitives + aRetval.resize(nCount); + + // color-filled polypolygons + for (sal_uInt32 a(0); a < nCount; a++) + { + // prepare polypolygon + basegfx::B2DPolyPolygon& rPolyPolygon = aB2DPolyPolyVector[a]; + rPolyPolygon.transform(aPolygonTransform); + aRetval[a] = new PolyPolygonColorPrimitive2D(rPolyPolygon, getFontColor()); + } + + if (getFontAttribute().getOutline()) + { + // decompose polygon transformation to single values + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + 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 }; + } + + rContainer.append(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, + const basegfx::BColor& rFontColor, bool bFilled, tools::Long nWidthToFill, + const Color& rTextFillColor) + : maTextTransform(rNewTransform) + , maText(rText) + , mnTextPosition(nTextPosition) + , mnTextLength(nTextLength) + , maDXArray(std::move(rDXArray)) + , maFontAttribute(rFontAttribute) + , maLocale(rLocale) + , maFontColor(rFontColor) + , mbFilled(bFilled) + , mnWidthToFill(nWidthToFill) + , maTextFillColor(rTextFillColor) +{ +#if OSL_DEBUG_LEVEL > 0 + const sal_Int32 aStringLength(getText().getLength()); + OSL_ENSURE(aStringLength >= getTextPosition() + && aStringLength >= getTextPosition() + getTextLength(), + "TextSimplePortionPrimitive2D with text out of range (!)"); +#endif +} + +bool LocalesAreEqual(const css::lang::Locale& rA, const css::lang::Locale& rB) +{ + return (rA.Language == rB.Language && rA.Country == rB.Country && rA.Variant == rB.Variant); +} + +bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const TextSimplePortionPrimitive2D& rCompare + = static_cast<const TextSimplePortionPrimitive2D&>(rPrimitive); + + return (getTextTransform() == rCompare.getTextTransform() && getText() == rCompare.getText() + && getTextPosition() == rCompare.getTextPosition() + && getTextLength() == rCompare.getTextLength() + && getDXArray() == rCompare.getDXArray() + && getFontAttribute() == rCompare.getFontAttribute() + && LocalesAreEqual(getLocale(), rCompare.getLocale()) + && getFontColor() == rCompare.getFontColor() && mbFilled == rCompare.mbFilled + && mnWidthToFill == rCompare.mnWidthToFill + && maTextFillColor == rCompare.maTextFillColor); + } + + return false; +} + +basegfx::B2DRange TextSimplePortionPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (maB2DRange.isEmpty() && getTextLength()) + { + // get TextBoundRect as base size + // decompose object transformation to single values + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + if (getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX)) + { + // for the TextLayouterDevice, it is necessary to have a scaling representing + // the font size. Since we want to extract polygons here, it is okay to + // work just with scaling and to ignore shear, rotation and translation, + // all that can be applied to the polygons later + const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale)); + + // prepare textlayoutdevice + TextLayouterDevice aTextLayouter; + aTextLayouter.setFontAttribute(getFontAttribute(), aFontScale.getX(), aFontScale.getY(), + getLocale()); + + // get basic text range + basegfx::B2DRange aNewRange( + aTextLayouter.getTextBoundRect(getText(), getTextPosition(), getTextLength())); + + // #i104432#, #i102556# take empty results into account + if (!aNewRange.isEmpty()) + { + // prepare object transformation for range + const basegfx::B2DHomMatrix aRangeTransformation( + basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, fShearX, fRotate, aTranslate)); + + // apply range transformation to it + aNewRange.transform(aRangeTransformation); + + // assign to buffered value + const_cast<TextSimplePortionPrimitive2D*>(this)->maB2DRange = aNewRange; + } + } + } + + return maB2DRange; +} + +// provide unique ID +sal_uInt32 TextSimplePortionPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D; +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx new file mode 100644 index 000000000..ecd66b7a8 --- /dev/null +++ b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx @@ -0,0 +1,259 @@ +/* -*- 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 <primitive2d/textstrikeoutprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/attribute/lineattribute.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> + + +namespace drawinglayer::primitive2d +{ + BaseTextStrikeoutPrimitive2D::BaseTextStrikeoutPrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + const basegfx::BColor& rFontColor) + : maObjectTransformation(rObjectTransformation), + mfWidth(fWidth), + maFontColor(rFontColor) + { + } + + bool BaseTextStrikeoutPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const BaseTextStrikeoutPrimitive2D& rCompare = static_cast<const BaseTextStrikeoutPrimitive2D&>(rPrimitive); + + return (getObjectTransformation() == rCompare.getObjectTransformation() + && getWidth() == rCompare.getWidth() + && getFontColor() == rCompare.getFontColor()); + } + + return false; + } + + + void TextCharacterStrikeoutPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // strikeout with character + const OUString aSingleCharString(getStrikeoutChar()); + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + // get decomposition + getObjectTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // prepare TextLayouter + TextLayouterDevice aTextLayouter; + + aTextLayouter.setFontAttribute( + getFontAttribute(), + aScale.getX(), + aScale.getY(), + getLocale()); + + const double fStrikeCharWidth(aTextLayouter.getTextWidth(aSingleCharString, 0, 1)); + const double fStrikeCharCount(fabs(getWidth()/fStrikeCharWidth)); + const sal_uInt32 nStrikeCharCount(static_cast< sal_uInt32 >(fStrikeCharCount + 0.5)); + std::vector<double> aDXArray(nStrikeCharCount); + OUStringBuffer aStrikeoutString; + + for(sal_uInt32 a(0); a < nStrikeCharCount; a++) + { + aStrikeoutString.append(aSingleCharString); + aDXArray[a] = (a + 1) * fStrikeCharWidth; + } + + auto len = aStrikeoutString.getLength(); + rContainer.push_back( + new TextSimplePortionPrimitive2D( + getObjectTransformation(), + aStrikeoutString.makeStringAndClear(), + 0, + len, + std::move(aDXArray), + getFontAttribute(), + getLocale(), + getFontColor())); + } + + TextCharacterStrikeoutPrimitive2D::TextCharacterStrikeoutPrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + const basegfx::BColor& rFontColor, + sal_Unicode aStrikeoutChar, + const attribute::FontAttribute& rFontAttribute, + const css::lang::Locale& rLocale) + : BaseTextStrikeoutPrimitive2D(rObjectTransformation, fWidth, rFontColor), + maStrikeoutChar(aStrikeoutChar), + maFontAttribute(rFontAttribute), + maLocale(rLocale) + { + } + + bool TextCharacterStrikeoutPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BaseTextStrikeoutPrimitive2D::operator==(rPrimitive)) + { + const TextCharacterStrikeoutPrimitive2D& rCompare = static_cast<const TextCharacterStrikeoutPrimitive2D&>(rPrimitive); + + return (getStrikeoutChar() == rCompare.getStrikeoutChar() + && getFontAttribute() == rCompare.getFontAttribute() + && LocalesAreEqual(getLocale(), rCompare.getLocale())); + } + + return false; + } + + // provide unique ID + sal_uInt32 TextCharacterStrikeoutPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTCHARACTERSTRIKEOUTPRIMITIVE2D; + } + + void TextGeometryStrikeoutPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + OSL_ENSURE(TEXT_STRIKEOUT_SLASH != getTextStrikeout() && TEXT_STRIKEOUT_X != getTextStrikeout(), + "Wrong TEXT_STRIKEOUT type; a TextCharacterStrikeoutPrimitive2D should be used (!)"); + + // strikeout with geometry + double fStrikeoutHeight(getHeight()); + double fStrikeoutOffset(getOffset()); + bool bDoubleLine(false); + + // get decomposition + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getObjectTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // set line attribute + switch(getTextStrikeout()) + { + default : // case primitive2d::TEXT_STRIKEOUT_SINGLE: + { + break; + } + case primitive2d::TEXT_STRIKEOUT_DOUBLE: + { + bDoubleLine = true; + break; + } + case primitive2d::TEXT_STRIKEOUT_BOLD: + { + fStrikeoutHeight *= 2.0; + break; + } + } + + if(bDoubleLine) + { + fStrikeoutOffset -= 0.50 * fStrikeoutHeight; + fStrikeoutHeight *= 0.64; + } + + // create base polygon and new primitive + basegfx::B2DPolygon aStrikeoutLine; + + aStrikeoutLine.append(basegfx::B2DPoint(0.0, -fStrikeoutOffset)); + aStrikeoutLine.append(basegfx::B2DPoint(getWidth(), -fStrikeoutOffset)); + + const basegfx::B2DHomMatrix aUnscaledTransform( + basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate)); + + aStrikeoutLine.transform(aUnscaledTransform); + + // add primitive + const attribute::LineAttribute aLineAttribute(getFontColor(), fStrikeoutHeight, basegfx::B2DLineJoin::NONE); + Primitive2DContainer xRetval(1); + xRetval[0] = new PolygonStrokePrimitive2D(aStrikeoutLine, aLineAttribute); + + if(bDoubleLine) + { + // double line, create 2nd primitive with offset using TransformPrimitive based on + // already created NewPrimitive + const double fLineDist(2.0 * fStrikeoutHeight); + + // move base point of text to 0.0 and de-rotate + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aTranslate.getX(), -aTranslate.getY())); + aTransform.rotate(-fRotate); + + // translate in Y by offset + aTransform.translate(0.0, -fLineDist); + + // move back and rotate + aTransform.rotate(fRotate); + aTransform.translate(aTranslate.getX(), aTranslate.getY()); + + // add transform primitive + xRetval.push_back( + new TransformPrimitive2D( + aTransform, + Primitive2DContainer(xRetval))); + } + + rContainer.append(std::move(xRetval)); + } + + TextGeometryStrikeoutPrimitive2D::TextGeometryStrikeoutPrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + const basegfx::BColor& rFontColor, + double fHeight, + double fOffset, + TextStrikeout eTextStrikeout) + : BaseTextStrikeoutPrimitive2D(rObjectTransformation, fWidth, rFontColor), + mfHeight(fHeight), + mfOffset(fOffset), + meTextStrikeout(eTextStrikeout) + { + } + + bool TextGeometryStrikeoutPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BaseTextStrikeoutPrimitive2D::operator==(rPrimitive)) + { + const TextGeometryStrikeoutPrimitive2D& rCompare = static_cast<const TextGeometryStrikeoutPrimitive2D&>(rPrimitive); + + return (getHeight() == rCompare.getHeight() + && getOffset() == rCompare.getOffset() + && getTextStrikeout() == rCompare.getTextStrikeout()); + } + + return false; + } + + // provide unique ID + sal_uInt32 TextGeometryStrikeoutPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TEXTGEOMETRYSTRIKEOUTPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/transformprimitive2d.cxx b/drawinglayer/source/primitive2d/transformprimitive2d.cxx new file mode 100644 index 000000000..ddd1594e2 --- /dev/null +++ b/drawinglayer/source/primitive2d/transformprimitive2d.cxx @@ -0,0 +1,64 @@ +/* -*- 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/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + TransformPrimitive2D::TransformPrimitive2D( + const basegfx::B2DHomMatrix& rTransformation, + Primitive2DContainer&& aChildren) + : GroupPrimitive2D(std::move(aChildren)), + maTransformation(rTransformation) + { + } + + bool TransformPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TransformPrimitive2D& rCompare = static_cast< const TransformPrimitive2D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation()); + } + + return false; + } + + basegfx::B2DRange TransformPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); + aRetval.transform(getTransformation()); + return aRetval; + } + + // provide unique ID + sal_uInt32 TransformPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/transparenceprimitive2d.cxx b/drawinglayer/source/primitive2d/transparenceprimitive2d.cxx new file mode 100644 index 000000000..8a86b1b29 --- /dev/null +++ b/drawinglayer/source/primitive2d/transparenceprimitive2d.cxx @@ -0,0 +1,57 @@ +/* -*- 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/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + TransparencePrimitive2D::TransparencePrimitive2D( + Primitive2DContainer&& aChildren, + Primitive2DContainer&& aTransparence) + : GroupPrimitive2D(std::move(aChildren)), + maTransparence(std::move(aTransparence)) + { + } + + bool TransparencePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TransparencePrimitive2D& rCompare = static_cast<const TransparencePrimitive2D&>(rPrimitive); + + return (getTransparence() == rCompare.getTransparence()); + } + + return false; + } + + // provide unique ID + sal_uInt32 TransparencePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx b/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx new file mode 100644 index 000000000..a2ba512be --- /dev/null +++ b/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx @@ -0,0 +1,112 @@ +/* -*- 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/unifiedtransparenceprimitive2d.hxx> +#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> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + UnifiedTransparencePrimitive2D::UnifiedTransparencePrimitive2D( + Primitive2DContainer&& aChildren, + double fTransparence) + : GroupPrimitive2D(std::move(aChildren)), + mfTransparence(fTransparence) + { + } + + bool UnifiedTransparencePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const UnifiedTransparencePrimitive2D& rCompare = static_cast<const UnifiedTransparencePrimitive2D&>(rPrimitive); + + return (getTransparence() == rCompare.getTransparence()); + } + + return false; + } + + basegfx::B2DRange UnifiedTransparencePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // do not use the fallback to decomposition here since for a correct BoundRect we also + // need invisible (1.0 == getTransparence()) geometry; these would be deleted in the decomposition + return getChildren().getB2DRange( rViewInformation); + } + + void UnifiedTransparencePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(0.0 == getTransparence()) + { + // no transparence used, so just use the content + getChildren(rVisitor); + } + else if(getTransparence() > 0.0 && getTransparence() < 1.0) + { + // The idea is to create a TransparencePrimitive2D with transparent content using a fill color + // corresponding to the transparence value. Problem is that in most systems, the right + // and bottom pixel array is not filled when filling polygons, thus this would not + // always produce a complete transparent bitmap. There are some solutions: + + // - Grow the used polygon range by one discrete unit in X and Y. This + // will make the decomposition view-dependent. + + // - For all filled polygon renderings, draw the polygon outline extra. This + // would lead to unwanted side effects when using concatenated polygons. + + // - At this decomposition, add a filled polygon and a hairline polygon. This + // solution stays view-independent. + + // 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)); + 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)); + + // create sub-transparence group with a gray-colored rectangular fill polygon + rVisitor.visit(new TransparencePrimitive2D(Primitive2DContainer(getChildren()), std::move(aTransparenceContent))); + } + else + { + // completely transparent or invalid definition, add nothing + } + } + + // provide unique ID + sal_uInt32 UnifiedTransparencePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx b/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx new file mode 100644 index 000000000..6e7e134f2 --- /dev/null +++ b/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx @@ -0,0 +1,257 @@ +/* -*- 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 <primitive2d/wallpaperprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <vcl/graph.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +namespace drawinglayer::primitive2d +{ + void WallpaperBitmapPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + Primitive2DReference aRetval; + + if(!getLocalObjectRange().isEmpty() && !getBitmapEx().IsEmpty()) + { + // get bitmap PIXEL size + const Size& rPixelSize = getBitmapEx().GetSizePixel(); + + if(rPixelSize.Width() > 0 && rPixelSize.Height() > 0) + { + 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()); + + Primitive2DReference xReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), + aObjectTransform)); + + aRetval = xReference; + } + 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()) + { + 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; + } + } + } + } + + if (aRetval.is()) + rContainer.push_back(aRetval); + } + + WallpaperBitmapPrimitive2D::WallpaperBitmapPrimitive2D( + const basegfx::B2DRange& rObjectRange, + const BitmapEx& rBitmapEx, + WallpaperStyle eWallpaperStyle) + : maObjectRange(rObjectRange), + maBitmapEx(rBitmapEx), + meWallpaperStyle(eWallpaperStyle) + { + } + + bool WallpaperBitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(ViewTransformationDependentPrimitive2D::operator==(rPrimitive)) + { + const WallpaperBitmapPrimitive2D& rCompare = static_cast<const WallpaperBitmapPrimitive2D&>(rPrimitive); + + return (getLocalObjectRange() == rCompare.getLocalObjectRange() + && getBitmapEx() == rCompare.getBitmapEx() + && getWallpaperStyle() == rCompare.getWallpaperStyle()); + } + + return false; + } + + basegfx::B2DRange WallpaperBitmapPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + return getLocalObjectRange(); + } + + // provide unique ID + sal_uInt32 WallpaperBitmapPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_WALLPAPERBITMAPPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx b/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx new file mode 100644 index 000000000..5249cdfb3 --- /dev/null +++ b/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx @@ -0,0 +1,105 @@ +/* -*- 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/wrongspellprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void WrongSpellPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, 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 + // it similar to the usage in e.g. HelplinePrimitive2D. Remembering the ViewTransformation + // should be enough then. + // The view-independent wavelines work well (if You ask me). Maybe the old VCL-Behaviour is only + // in place because it was not possible/too expensive at that time to scale the wavelines with the + // view... + // With the VCL-PixelRenderer this will not even be used since it implements WrongSpellPrimitive2D + // directly and mimics the old VCL-Display there. If You implemented a new renderer without + // direct WrongSpellPrimitive2D support, You may want to do the described change here. + + // get the font height (part of scale), so decompose the matrix + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // calculate distances based on a static default (to allow testing in debugger) + static const double fDefaultDistance(0.03); + const double fFontHeight(aScale.getY()); + const double fUnderlineDistance(fFontHeight * fDefaultDistance); + const double fWaveWidth(2.0 * fUnderlineDistance); + + // the Y-distance needs to be relative to FontHeight since the points get + // transformed with the transformation containing that scale already. + const double fRelativeUnderlineDistance(basegfx::fTools::equalZero(aScale.getY()) ? 0.0 : fUnderlineDistance / aScale.getY()); + basegfx::B2DPoint aStart(getStart(), fRelativeUnderlineDistance); + basegfx::B2DPoint aStop(getStop(), fRelativeUnderlineDistance); + basegfx::B2DPolygon aPolygon; + + aPolygon.append(getTransformation() * aStart); + aPolygon.append(getTransformation() * aStop); + + // prepare line attribute + const attribute::LineAttribute aLineAttribute(getColor()); + + // create the waveline primitive + rContainer.push_back(new PolygonWavePrimitive2D(aPolygon, aLineAttribute, fWaveWidth, 0.5 * fWaveWidth)); + } + + WrongSpellPrimitive2D::WrongSpellPrimitive2D( + const basegfx::B2DHomMatrix& rTransformation, + double fStart, + double fStop, + const basegfx::BColor& rColor) + : maTransformation(rTransformation), + mfStart(fStart), + mfStop(fStop), + maColor(rColor) + { + } + + bool WrongSpellPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const WrongSpellPrimitive2D& rCompare = static_cast<const WrongSpellPrimitive2D&>(rPrimitive); + + return (getTransformation() == rCompare.getTransformation() + && getStart() == rCompare.getStart() + && getStop() == rCompare.getStop() + && getColor() == rCompare.getColor()); + } + + return false; + } + + // provide unique ID + sal_uInt32 WrongSpellPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/Tools.cxx b/drawinglayer/source/primitive3d/Tools.cxx new file mode 100644 index 000000000..c27896b04 --- /dev/null +++ b/drawinglayer/source/primitive3d/Tools.cxx @@ -0,0 +1,67 @@ +/* -*- 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/. + * + */ + +#include <drawinglayer/primitive3d/Tools.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + +using namespace css; + +namespace drawinglayer::primitive3d +{ +OUString idToString(sal_uInt32 nId) +{ + switch (nId) + { + case PRIMITIVE3D_ID_GROUPPRIMITIVE3D: + return "GROUPPRIMITIVE3D"; + case PRIMITIVE3D_ID_HATCHTEXTUREPRIMITIVE3D: + return "HATCHTEXTUREPRIMITIVE3D"; + case PRIMITIVE3D_ID_MODIFIEDCOLORPRIMITIVE3D: + return "MODIFIEDCOLORPRIMITIVE3D"; + case PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D: + return "POLYGONHAIRLINEPRIMITIVE3D"; + case PRIMITIVE3D_ID_POLYGONSTROKEPRIMITIVE3D: + return "POLYGONSTROKEPRIMITIVE3D"; + case PRIMITIVE3D_ID_POLYGONTUBEPRIMITIVE3D: + return "POLYGONTUBEPRIMITIVE3D"; + case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D: + return "POLYPOLYGONMATERIALPRIMITIVE3D"; + case PRIMITIVE3D_ID_SDRCUBEPRIMITIVE3D: + return "SDRCUBEPRIMITIVE3D"; + case PRIMITIVE3D_ID_SDREXTRUDEPRIMITIVE3D: + return "SDREXTRUDEPRIMITIVE3D"; + case PRIMITIVE3D_ID_SDRLATHEPRIMITIVE3D: + return "SDRLATHEPRIMITIVE3D"; + case PRIMITIVE3D_ID_SDRPOLYPOLYGONPRIMITIVE3D: + return "SDRPOLYPOLYGONPRIMITIVE3D"; + case PRIMITIVE3D_ID_SDRSPHEREPRIMITIVE3D: + return "SDRSPHEREPRIMITIVE3D"; + case PRIMITIVE3D_ID_SHADOWPRIMITIVE3D: + return "SHADOWPRIMITIVE3D"; + case PRIMITIVE3D_ID_UNIFIEDTRANSPARENCETEXTUREPRIMITIVE3D: + return "UNIFIEDTRANSPARENCETEXTUREPRIMITIVE3D"; + case PRIMITIVE3D_ID_GRADIENTTEXTUREPRIMITIVE3D: + return "GRADIENTTEXTUREPRIMITIVE3D"; + case PRIMITIVE3D_ID_BITMAPTEXTUREPRIMITIVE3D: + return "BITMAPTEXTUREPRIMITIVE3D"; + case PRIMITIVE3D_ID_TRANSPARENCETEXTUREPRIMITIVE3D: + return "TRANSPARENCETEXTUREPRIMITIVE3D"; + case PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D: + return "TRANSFORMPRIMITIVE3D"; + case PRIMITIVE3D_ID_HIDDENGEOMETRYPRIMITIVE3D: + return "HIDDENGEOMETRYPRIMITIVE3D"; + default: + return OUString::number((nId >> 16) & 0xFF) + "|" + OUString::number(nId & 0xFF); + } +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/baseprimitive3d.cxx b/drawinglayer/source/primitive3d/baseprimitive3d.cxx new file mode 100644 index 000000000..5bd980012 --- /dev/null +++ b/drawinglayer/source/primitive3d/baseprimitive3d.cxx @@ -0,0 +1,183 @@ +/* -*- 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/primitive3d/baseprimitive3d.hxx> +#include <drawinglayer/geometry/viewinformation3d.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <comphelper/sequence.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + BasePrimitive3D::BasePrimitive3D() + : BasePrimitive3DImplBase(m_aMutex) + { + } + + BasePrimitive3D::~BasePrimitive3D() + { + } + + bool BasePrimitive3D::operator==( const BasePrimitive3D& rPrimitive ) const + { + return (getPrimitive3DID() == rPrimitive.getPrimitive3DID()); + } + + basegfx::B3DRange BasePrimitive3D::getB3DRange(const geometry::ViewInformation3D& rViewInformation) const + { + return get3DDecomposition(rViewInformation).getB3DRange(rViewInformation); + } + + Primitive3DContainer BasePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + return Primitive3DContainer(); + } + + css::uno::Sequence< ::css::uno::Reference< ::css::graphic::XPrimitive3D > > SAL_CALL BasePrimitive3D::getDecomposition( const uno::Sequence< beans::PropertyValue >& rViewParameters ) + { + const geometry::ViewInformation3D aViewInformation(rViewParameters); + return comphelper::containerToSequence(get3DDecomposition(aViewInformation)); + } + + css::geometry::RealRectangle3D SAL_CALL BasePrimitive3D::getRange( const uno::Sequence< beans::PropertyValue >& rViewParameters ) + { + const geometry::ViewInformation3D aViewInformation(rViewParameters); + return basegfx::unotools::rectangle3DFromB3DRectangle(getB3DRange(aViewInformation)); + } + + + Primitive3DContainer BufferedDecompositionPrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + return Primitive3DContainer(); + } + + BufferedDecompositionPrimitive3D::BufferedDecompositionPrimitive3D() + { + } + + Primitive3DContainer BufferedDecompositionPrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(getBuffered3DDecomposition().empty()) + { + const Primitive3DContainer aNewSequence(create3DDecomposition(rViewInformation)); + const_cast< BufferedDecompositionPrimitive3D* >(this)->setBuffered3DDecomposition(aNewSequence); + } + + return getBuffered3DDecomposition(); + } + +// tooling + + // get range3D from a given Primitive3DReference + basegfx::B3DRange getB3DRangeFromPrimitive3DReference(const Primitive3DReference& rCandidate, const geometry::ViewInformation3D& aViewInformation) + { + basegfx::B3DRange aRetval; + + if(rCandidate.is()) + { + const BasePrimitive3D* pCandidate(static_cast< BasePrimitive3D* >(rCandidate.get())); + aRetval.expand(pCandidate->getB3DRange(aViewInformation)); + } + + return aRetval; + } + + // get range3D from a given Primitive3DContainer + basegfx::B3DRange Primitive3DContainer::getB3DRange(const geometry::ViewInformation3D& aViewInformation) const + { + basegfx::B3DRange aRetval; + + if(!empty()) + { + const size_t nCount(size()); + + for(size_t a(0); a < nCount; a++) + { + aRetval.expand(getB3DRangeFromPrimitive3DReference((*this)[a], aViewInformation)); + } + } + + return aRetval; + } + + bool arePrimitive3DReferencesEqual(const Primitive3DReference& rxA, const Primitive3DReference& rxB) + { + const bool bAIs(rxA.is()); + + if(bAIs != rxB.is()) + { + return false; + } + + if(!bAIs) + { + return true; + } + + const BasePrimitive3D* pA(static_cast< const BasePrimitive3D* >(rxA.get())); + const BasePrimitive3D* pB(static_cast< const BasePrimitive3D* >(rxB.get())); + + return pA->operator==(*pB); + } + + bool Primitive3DContainer::operator==(const Primitive3DContainer& rB) const + { + const bool bAHasElements(!empty()); + + if(bAHasElements != !rB.empty()) + { + return false; + } + + if(!bAHasElements) + { + return true; + } + + const size_t nCount(size()); + + if(nCount != rB.size()) + { + return false; + } + + for(size_t a(0); a < nCount; a++) + { + if(!arePrimitive3DReferencesEqual((*this)[a], rB[a])) + { + return false; + } + } + + return true; + } + + void Primitive3DContainer::append(const Primitive3DContainer& rSource) + { + insert(end(), rSource.begin(), rSource.end()); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/groupprimitive3d.cxx b/drawinglayer/source/primitive3d/groupprimitive3d.cxx new file mode 100644 index 000000000..69291e768 --- /dev/null +++ b/drawinglayer/source/primitive3d/groupprimitive3d.cxx @@ -0,0 +1,61 @@ +/* -*- 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/primitive3d/groupprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + GroupPrimitive3D::GroupPrimitive3D( const Primitive3DContainer& rChildren ) + : maChildren(rChildren) + { + } + + /** The compare opertator uses the Sequence::==operator, so only checking if + the references are equal. All non-equal references are interpreted as + non-equal. + */ + bool GroupPrimitive3D::operator==( const BasePrimitive3D& rPrimitive ) const + { + if(BasePrimitive3D::operator==(rPrimitive)) + { + const GroupPrimitive3D& rCompare = static_cast< const GroupPrimitive3D& >(rPrimitive); + + return getChildren() == rCompare.getChildren(); + } + + return false; + } + + /// default: just return children, so all renderers not supporting group will use its content + Primitive3DContainer GroupPrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + return getChildren(); + } + + // provide unique ID + ImplPrimitive3DIDBlock(GroupPrimitive3D, PRIMITIVE3D_ID_GROUPPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx b/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx new file mode 100644 index 000000000..a699f7170 --- /dev/null +++ b/drawinglayer/source/primitive3d/hatchtextureprimitive3d.cxx @@ -0,0 +1,310 @@ +/* -*- 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 <primitive3d/hatchtextureprimitive3d.hxx> +#include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <texture/texture.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <drawinglayer/primitive3d/polygonprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + Primitive3DContainer HatchTexturePrimitive3D::impCreate3DDecomposition() const + { + Primitive3DContainer aRetval; + + if(!getChildren().empty()) + { + const Primitive3DContainer aSource(getChildren()); + const size_t nSourceCount(aSource.size()); + std::vector< Primitive3DReference > aDestination; + + for(size_t a(0); a < nSourceCount; a++) + { + // get reference + const Primitive3DReference xReference(aSource[a]); + + if(xReference.is()) + { + const BasePrimitive3D* pBasePrimitive = static_cast< const BasePrimitive3D* >(xReference.get()); + + // it is a BasePrimitive3D implementation, use getPrimitive3DID() call for switch + // not all content is needed, remove transparencies and ModifiedColorPrimitives + switch(pBasePrimitive->getPrimitive3DID()) + { + case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D : + { + // polyPolygonMaterialPrimitive3D, check texturing and hatching + const PolyPolygonMaterialPrimitive3D& rPrimitive = static_cast< const PolyPolygonMaterialPrimitive3D& >(*pBasePrimitive); + const basegfx::B3DPolyPolygon& aFillPolyPolygon(rPrimitive.getB3DPolyPolygon()); + + if(maHatch.isFillBackground()) + { + // add original primitive for background + aDestination.push_back(xReference); + } + + if(aFillPolyPolygon.areTextureCoordinatesUsed()) + { + const sal_uInt32 nPolyCount(aFillPolyPolygon.count()); + basegfx::B2DPolyPolygon aTexPolyPolygon; + basegfx::B2DPoint a2N; + basegfx::B2DVector a2X, a2Y; + basegfx::B3DPoint a3N; + basegfx::B3DVector a3X, a3Y; + bool b2N(false), b2X(false), b2Y(false); + + for(sal_uInt32 b(0); b < nPolyCount; b++) + { + const basegfx::B3DPolygon& aPartPoly(aFillPolyPolygon.getB3DPolygon(b)); + const sal_uInt32 nPointCount(aPartPoly.count()); + basegfx::B2DPolygon aTexPolygon; + + for(sal_uInt32 c(0); c < nPointCount; c++) + { + const basegfx::B2DPoint a2Candidate(aPartPoly.getTextureCoordinate(c)); + + if(!b2N) + { + a2N = a2Candidate; + a3N = aPartPoly.getB3DPoint(c); + b2N = true; + } + else if(!b2X && !a2N.equal(a2Candidate)) + { + a2X = a2Candidate - a2N; + a3X = aPartPoly.getB3DPoint(c) - a3N; + b2X = true; + } + else if(!b2Y && !a2N.equal(a2Candidate) && !a2X.equal(a2Candidate)) + { + a2Y = a2Candidate - a2N; + + const double fCross(a2X.cross(a2Y)); + + if(!basegfx::fTools::equalZero(fCross)) + { + a3Y = aPartPoly.getB3DPoint(c) - a3N; + b2Y = true; + } + } + + aTexPolygon.append(a2Candidate); + } + + aTexPolygon.setClosed(true); + aTexPolyPolygon.append(aTexPolygon); + } + + if(b2N && b2X && b2Y) + { + // found two linearly independent 2D vectors + // get 2d range of texture coordinates + const basegfx::B2DRange aOutlineRange(basegfx::utils::getRange(aTexPolyPolygon)); + const basegfx::BColor aHatchColor(getHatch().getColor()); + const double fAngle(getHatch().getAngle()); + std::vector< basegfx::B2DHomMatrix > aMatrices; + + // get hatch transformations + switch(getHatch().getStyle()) + { + case attribute::HatchStyle::Triple: + { + // rotated 45 degrees + texture::GeoTexSvxHatch aHatch( + aOutlineRange, + aOutlineRange, + getHatch().getDistance(), + fAngle - M_PI_4); + + aHatch.appendTransformations(aMatrices); + + [[fallthrough]]; + } + case attribute::HatchStyle::Double: + { + // rotated 90 degrees + texture::GeoTexSvxHatch aHatch( + aOutlineRange, + aOutlineRange, + getHatch().getDistance(), + fAngle - M_PI_2); + + aHatch.appendTransformations(aMatrices); + + [[fallthrough]]; + } + case attribute::HatchStyle::Single: + { + // angle as given + texture::GeoTexSvxHatch aHatch( + aOutlineRange, + aOutlineRange, + getHatch().getDistance(), + fAngle); + + aHatch.appendTransformations(aMatrices); + } + } + + // create geometry from unit line + basegfx::B2DPolyPolygon a2DHatchLines; + basegfx::B2DPolygon a2DUnitLine; + a2DUnitLine.append(basegfx::B2DPoint(0.0, 0.0)); + a2DUnitLine.append(basegfx::B2DPoint(1.0, 0.0)); + + for(const basegfx::B2DHomMatrix & rMatrix : aMatrices) + { + basegfx::B2DPolygon aNewLine(a2DUnitLine); + aNewLine.transform(rMatrix); + a2DHatchLines.append(aNewLine); + } + + if(a2DHatchLines.count()) + { + // clip against texture polygon + a2DHatchLines = basegfx::utils::clipPolyPolygonOnPolyPolygon(a2DHatchLines, aTexPolyPolygon, true, true); + } + + if(a2DHatchLines.count()) + { + // create 2d matrix with 2d vectors as column vectors and 2d point as offset, this represents + // a coordinate system transformation from unit coordinates to the new coordinate system + basegfx::B2DHomMatrix a2D; + a2D.set(0, 0, a2X.getX()); + a2D.set(1, 0, a2X.getY()); + a2D.set(0, 1, a2Y.getX()); + a2D.set(1, 1, a2Y.getY()); + a2D.set(0, 2, a2N.getX()); + a2D.set(1, 2, a2N.getY()); + + // invert that transformation, so we have a back-transformation from texture coordinates + // to unit coordinates + a2D.invert(); + a2DHatchLines.transform(a2D); + + // expand back-transformed geometry to 3D + basegfx::B3DPolyPolygon a3DHatchLines(basegfx::utils::createB3DPolyPolygonFromB2DPolyPolygon(a2DHatchLines, 0.0)); + + // create 3d matrix with 3d vectors as column vectors (0,0,1 as Z) and 3d point as offset, this represents + // a coordinate system transformation from unit coordinates to the object's 3d coordinate system + basegfx::B3DHomMatrix a3D; + a3D.set(0, 0, a3X.getX()); + a3D.set(1, 0, a3X.getY()); + a3D.set(2, 0, a3X.getZ()); + a3D.set(0, 1, a3Y.getX()); + a3D.set(1, 1, a3Y.getY()); + a3D.set(2, 1, a3Y.getZ()); + a3D.set(0, 3, a3N.getX()); + a3D.set(1, 3, a3N.getY()); + a3D.set(2, 3, a3N.getZ()); + + // transform hatch lines to 3D object coordinates + a3DHatchLines.transform(a3D); + + // build primitives from this geometry + const sal_uInt32 nHatchLines(a3DHatchLines.count()); + + for(sal_uInt32 d(0); d < nHatchLines; d++) + { + const Primitive3DReference xRef(new PolygonHairlinePrimitive3D(a3DHatchLines.getB3DPolygon(d), aHatchColor)); + aDestination.push_back(xRef); + } + } + } + } + + break; + } + default : + { + // add reference to result + aDestination.push_back(xReference); + break; + } + } + } + } + + // prepare return value + const sal_uInt32 nDestSize(aDestination.size()); + aRetval.resize(nDestSize); + + for(sal_uInt32 b(0); b < nDestSize; b++) + { + aRetval[b] = aDestination[b]; + } + } + + return aRetval; + } + + HatchTexturePrimitive3D::HatchTexturePrimitive3D( + const attribute::FillHatchAttribute& rHatch, + const Primitive3DContainer& rChildren, + const basegfx::B2DVector& rTextureSize, + bool bModulate, + bool bFilter) + : TexturePrimitive3D(rChildren, rTextureSize, bModulate, bFilter), + maHatch(rHatch) + { + } + + bool HatchTexturePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(TexturePrimitive3D::operator==(rPrimitive)) + { + const HatchTexturePrimitive3D& rCompare = static_cast<const HatchTexturePrimitive3D&>(rPrimitive); + + return (getHatch() == rCompare.getHatch()); + } + + return false; + } + + Primitive3DContainer HatchTexturePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(getBuffered3DDecomposition().empty()) + { + const Primitive3DContainer aNewSequence(impCreate3DDecomposition()); + const_cast< HatchTexturePrimitive3D* >(this)->maBuffered3DDecomposition = aNewSequence; + } + + return getBuffered3DDecomposition(); + } + + // provide unique ID + ImplPrimitive3DIDBlock(HatchTexturePrimitive3D, PRIMITIVE3D_ID_HATCHTEXTUREPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/hiddengeometryprimitive3d.cxx b/drawinglayer/source/primitive3d/hiddengeometryprimitive3d.cxx new file mode 100644 index 000000000..1a367e501 --- /dev/null +++ b/drawinglayer/source/primitive3d/hiddengeometryprimitive3d.cxx @@ -0,0 +1,51 @@ +/* -*- 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 <primitive3d/hiddengeometryprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + HiddenGeometryPrimitive3D::HiddenGeometryPrimitive3D( + const Primitive3DContainer& rChildren) + : GroupPrimitive3D(rChildren) + { + } + + basegfx::B3DRange HiddenGeometryPrimitive3D::getB3DRange(const geometry::ViewInformation3D& rViewInformation) const + { + return getChildren().getB3DRange(rViewInformation); + } + + Primitive3DContainer HiddenGeometryPrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + // return empty sequence + return Primitive3DContainer(); + } + + // provide unique ID + ImplPrimitive3DIDBlock(HiddenGeometryPrimitive3D, PRIMITIVE3D_ID_HIDDENGEOMETRYPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx b/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx new file mode 100644 index 000000000..255f0235c --- /dev/null +++ b/drawinglayer/source/primitive3d/modifiedcolorprimitive3d.cxx @@ -0,0 +1,64 @@ +/* -*- 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/primitive3d/modifiedcolorprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + ModifiedColorPrimitive3D::ModifiedColorPrimitive3D( + const Primitive3DContainer& rChildren, + const basegfx::BColorModifierSharedPtr& rColorModifier) + : GroupPrimitive3D(rChildren), + maColorModifier(rColorModifier) + { + } + + bool ModifiedColorPrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(GroupPrimitive3D::operator==(rPrimitive)) + { + const ModifiedColorPrimitive3D& rCompare = static_cast<const ModifiedColorPrimitive3D&>(rPrimitive); + + if(getColorModifier().get() == rCompare.getColorModifier().get()) + { + return true; + } + + if(!getColorModifier() || !rCompare.getColorModifier()) + { + return false; + } + + return *getColorModifier() == *rCompare.getColorModifier(); + } + + return false; + } + + // provide unique ID + ImplPrimitive3DIDBlock(ModifiedColorPrimitive3D, PRIMITIVE3D_ID_MODIFIEDCOLORPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/polygonprimitive3d.cxx b/drawinglayer/source/primitive3d/polygonprimitive3d.cxx new file mode 100644 index 000000000..8fc2b4d31 --- /dev/null +++ b/drawinglayer/source/primitive3d/polygonprimitive3d.cxx @@ -0,0 +1,148 @@ +/* -*- 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/primitive3d/polygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <primitive3d/polygontubeprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + PolygonHairlinePrimitive3D::PolygonHairlinePrimitive3D( + const basegfx::B3DPolygon& rPolygon, + const basegfx::BColor& rBColor) + : maPolygon(rPolygon), + maBColor(rBColor) + { + } + + bool PolygonHairlinePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(BasePrimitive3D::operator==(rPrimitive)) + { + const PolygonHairlinePrimitive3D& rCompare = static_cast<const PolygonHairlinePrimitive3D&>(rPrimitive); + + return (getB3DPolygon() == rCompare.getB3DPolygon() + && getBColor() == rCompare.getBColor()); + } + + return false; + } + + basegfx::B3DRange PolygonHairlinePrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + return basegfx::utils::getRange(getB3DPolygon()); + } + + // provide unique ID + ImplPrimitive3DIDBlock(PolygonHairlinePrimitive3D, PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D) + + + + Primitive3DContainer PolygonStrokePrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + Primitive3DContainer aRetval; + + if(getB3DPolygon().count()) + { + basegfx::B3DPolyPolygon aHairLinePolyPolygon; + + if(0.0 == getStrokeAttribute().getFullDotDashLen()) + { + aHairLinePolyPolygon = basegfx::B3DPolyPolygon(getB3DPolygon()); + } + else + { + // apply LineStyle + basegfx::utils::applyLineDashing(getB3DPolygon(), getStrokeAttribute().getDotDashArray(), &aHairLinePolyPolygon, getStrokeAttribute().getFullDotDashLen()); + } + + // prepare result + aRetval.resize(aHairLinePolyPolygon.count()); + + if(getLineAttribute().getWidth()) + { + // create fat line data + const double fRadius(getLineAttribute().getWidth() / 2.0); + const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin()); + const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap()); + + for(sal_uInt32 a(0); a < aHairLinePolyPolygon.count(); a++) + { + // create tube primitives + const Primitive3DReference xRef( + new PolygonTubePrimitive3D( + aHairLinePolyPolygon.getB3DPolygon(a), + getLineAttribute().getColor(), + fRadius, + aLineJoin, + aLineCap)); + aRetval[a] = xRef; + } + } + else + { + // create hair line data for all sub polygons + for(sal_uInt32 a(0); a < aHairLinePolyPolygon.count(); a++) + { + const basegfx::B3DPolygon& aCandidate = aHairLinePolyPolygon.getB3DPolygon(a); + const Primitive3DReference xRef(new PolygonHairlinePrimitive3D(aCandidate, getLineAttribute().getColor())); + aRetval[a] = xRef; + } + } + } + + return aRetval; + } + + PolygonStrokePrimitive3D::PolygonStrokePrimitive3D( + const basegfx::B3DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + : maPolygon(rPolygon), + maLineAttribute(rLineAttribute), + maStrokeAttribute(rStrokeAttribute) + { + } + + bool PolygonStrokePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(BufferedDecompositionPrimitive3D::operator==(rPrimitive)) + { + const PolygonStrokePrimitive3D& rCompare = static_cast<const PolygonStrokePrimitive3D&>(rPrimitive); + + return (getB3DPolygon() == rCompare.getB3DPolygon() + && getLineAttribute() == rCompare.getLineAttribute() + && getStrokeAttribute() == rCompare.getStrokeAttribute()); + } + + return false; + } + + // provide unique ID + ImplPrimitive3DIDBlock(PolygonStrokePrimitive3D, PRIMITIVE3D_ID_POLYGONSTROKEPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx b/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx new file mode 100644 index 000000000..d8d3cc9c8 --- /dev/null +++ b/drawinglayer/source/primitive3d/polygontubeprimitive3d.cxx @@ -0,0 +1,769 @@ +/* -*- 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 <primitive3d/polygontubeprimitive3d.hxx> +#include <drawinglayer/attribute/materialattribute3d.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <drawinglayer/primitive3d/transformprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <mutex> + +namespace drawinglayer::primitive3d +{ + namespace // anonymous namespace + { + class TubeBuffer + { + private: + // data for buffered tube primitives + Primitive3DContainer m_aLineTubeList; + sal_uInt32 m_nLineTubeSegments; + attribute::MaterialAttribute3D m_aLineMaterial; + std::mutex m_aMutex; + public: + TubeBuffer() + : m_nLineTubeSegments(0) + { + } + + TubeBuffer(const TubeBuffer&) = delete; + const TubeBuffer& operator=(const TubeBuffer&) = delete; + + Primitive3DContainer getLineTubeSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial) + { + // may exclusively change cached data, use mutex + std::unique_lock aGuard(m_aMutex); + + if (nSegments != m_nLineTubeSegments || !(rMaterial == m_aLineMaterial)) + { + m_nLineTubeSegments = nSegments; + m_aLineMaterial = rMaterial; + m_aLineTubeList = Primitive3DContainer(); + } + + if (m_aLineTubeList.empty() && m_nLineTubeSegments != 0) + { + const basegfx::B3DPoint aLeft(0.0, 0.0, 0.0); + const basegfx::B3DPoint aRight(1.0, 0.0, 0.0); + basegfx::B3DPoint aLastLeft(0.0, 1.0, 0.0); + basegfx::B3DPoint aLastRight(1.0, 1.0, 0.0); + basegfx::B3DHomMatrix aRot; + aRot.rotate(2 * M_PI / static_cast<double>(m_nLineTubeSegments), 0.0, 0.0); + m_aLineTubeList.resize(m_nLineTubeSegments); + + for(sal_uInt32 a = 0; a < m_nLineTubeSegments; ++a) + { + const basegfx::B3DPoint aNextLeft(aRot * aLastLeft); + const basegfx::B3DPoint aNextRight(aRot * aLastRight); + basegfx::B3DPolygon aNewPolygon; + + aNewPolygon.append(aNextLeft); + aNewPolygon.setNormal(0, basegfx::B3DVector(aNextLeft - aLeft)); + + aNewPolygon.append(aLastLeft); + aNewPolygon.setNormal(1, basegfx::B3DVector(aLastLeft - aLeft)); + + aNewPolygon.append(aLastRight); + aNewPolygon.setNormal(2, basegfx::B3DVector(aLastRight - aRight)); + + aNewPolygon.append(aNextRight); + aNewPolygon.setNormal(3, basegfx::B3DVector(aNextRight - aRight)); + + aNewPolygon.setClosed(true); + + const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); + const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, m_aLineMaterial, false)); + m_aLineTubeList[a] = xRef; + + aLastLeft = aNextLeft; + aLastRight = aNextRight; + } + } + return m_aLineTubeList; + } + }; + + Primitive3DContainer getLineTubeSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial) + { + // static data for buffered tube primitives + static TubeBuffer theTubeBuffer; + return theTubeBuffer.getLineTubeSegments(nSegments, rMaterial); + } + + class CapBuffer + { + private: + // data for buffered cap primitives + Primitive3DContainer m_aLineCapList; + sal_uInt32 m_nLineCapSegments; + attribute::MaterialAttribute3D m_aLineMaterial; + std::mutex m_aMutex; + public: + CapBuffer() + : m_nLineCapSegments(0) + { + } + CapBuffer(const CapBuffer&) = delete; + const CapBuffer& operator=(const CapBuffer&) = delete; + + Primitive3DContainer getLineCapSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial) + { + // may exclusively change cached data, use mutex + std::unique_lock aGuard(m_aMutex); + + if (nSegments != m_nLineCapSegments || !(rMaterial == m_aLineMaterial)) + { + m_nLineCapSegments = nSegments; + m_aLineMaterial = rMaterial; + m_aLineCapList = Primitive3DContainer(); + } + + if (m_aLineCapList.empty() && m_nLineCapSegments != 0) + { + const basegfx::B3DPoint aNull(0.0, 0.0, 0.0); + basegfx::B3DPoint aLast(0.0, 1.0, 0.0); + basegfx::B3DHomMatrix aRot; + aRot.rotate(2 * M_PI / static_cast<double>(m_nLineCapSegments), 0.0, 0.0); + m_aLineCapList.resize(m_nLineCapSegments); + + for(sal_uInt32 a = 0; a < m_nLineCapSegments; ++a) + { + const basegfx::B3DPoint aNext(aRot * aLast); + basegfx::B3DPolygon aNewPolygon; + + aNewPolygon.append(aLast); + aNewPolygon.setNormal(0, basegfx::B3DVector(aLast - aNull)); + + aNewPolygon.append(aNext); + aNewPolygon.setNormal(1, basegfx::B3DVector(aNext - aNull)); + + aNewPolygon.append(aNull); + aNewPolygon.setNormal(2, basegfx::B3DVector(-1.0, 0.0, 0.0)); + + aNewPolygon.setClosed(true); + + const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); + const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, m_aLineMaterial, false)); + m_aLineCapList[a] = xRef; + + aLast = aNext; + } + } + + return m_aLineCapList; + } + }; + + Primitive3DContainer getLineCapSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial) + { + // static data for buffered cap primitives + static CapBuffer theCapBuffer; + return theCapBuffer.getLineCapSegments(nSegments, rMaterial); + } + + class CapRoundBuffer + { + private: + // data for buffered capround primitives + Primitive3DContainer m_aLineCapRoundList; + sal_uInt32 m_nLineCapRoundSegments; + attribute::MaterialAttribute3D m_aLineMaterial; + std::mutex m_aMutex; + public: + CapRoundBuffer() + : m_nLineCapRoundSegments(0) + { + } + CapRoundBuffer(const CapRoundBuffer&) = delete; + const CapRoundBuffer& operator=(const CapRoundBuffer&) = delete; + + Primitive3DContainer getLineCapRoundSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial) + { + // may exclusively change cached data, use mutex + std::unique_lock aGuard(m_aMutex); + + if (nSegments != m_nLineCapRoundSegments || !(rMaterial == m_aLineMaterial)) + { + m_nLineCapRoundSegments = nSegments; + m_aLineMaterial = rMaterial; + m_aLineCapRoundList = Primitive3DContainer(); + } + + if (m_aLineCapRoundList.empty() && m_nLineCapRoundSegments) + { + // calculate new horizontal segments + sal_uInt32 nVerSeg(nSegments / 2); + + if (nVerSeg < 1) + { + nVerSeg = 1; + } + + // create half-sphere; upper half of unit sphere + basegfx::B3DPolyPolygon aSphere( + basegfx::utils::createUnitSphereFillPolyPolygon( + nSegments, + nVerSeg, + true, + M_PI_2, 0.0, + 0.0, 2 * M_PI)); + const sal_uInt32 nCount(aSphere.count()); + + if (nCount) + { + // rotate to have sphere cap oriented to negative X-Axis; do not + // forget to transform normals, too + basegfx::B3DHomMatrix aSphereTrans; + + aSphereTrans.rotate(0.0, 0.0, M_PI_2); + aSphere.transform(aSphereTrans); + aSphere.transformNormals(aSphereTrans); + + // realloc for primitives and create based on polygon snippets + m_aLineCapRoundList.resize(nCount); + + for (sal_uInt32 a = 0; a < nCount; ++a) + { + const basegfx::B3DPolygon& aPartPolygon(aSphere.getB3DPolygon(a)); + const 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, + rMaterial, + false); + } + } + } + + return m_aLineCapRoundList; + } + + }; + + Primitive3DContainer getLineCapRoundSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial) + { + // static data for buffered cap primitives + static CapRoundBuffer theCapRoundBuffer; + return theCapRoundBuffer.getLineCapRoundSegments(nSegments, rMaterial); + } + + Primitive3DContainer getLineJoinSegments( + sal_uInt32 nSegments, + const attribute::MaterialAttribute3D& rMaterial, + double fAngle, + double fMiterMinimumAngle, + basegfx::B2DLineJoin aLineJoin) + { + // nSegments is for whole circle, adapt to half circle + const sal_uInt32 nVerSeg(nSegments >> 1); + std::vector< BasePrimitive3D* > aResultVector; + + if(nVerSeg) + { + if(basegfx::B2DLineJoin::Round == aLineJoin) + { + // calculate new horizontal segments + const sal_uInt32 nHorSeg(basegfx::fround((fAngle / (2 * M_PI)) * static_cast<double>(nSegments))); + + if(nHorSeg) + { + // create half-sphere + const basegfx::B3DPolyPolygon aSphere(basegfx::utils::createUnitSphereFillPolyPolygon(nHorSeg, nVerSeg, true, M_PI_2, -M_PI_2, 0.0, fAngle)); + + 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)); + } + } + else + { + // fallback to bevel when there is not at least one segment hor and ver + aLineJoin = basegfx::B2DLineJoin::Bevel; + } + } + + if (basegfx::B2DLineJoin::Bevel == aLineJoin || + basegfx::B2DLineJoin::Miter == aLineJoin) + { + if(basegfx::B2DLineJoin::Miter == aLineJoin) + { + const double fMiterAngle(fAngle/2.0); + + if(fMiterAngle < fMiterMinimumAngle) + { + // fallback to bevel when miter's angle is too small + aLineJoin = basegfx::B2DLineJoin::Bevel; + } + } + + const double fInc(M_PI / static_cast<double>(nVerSeg)); + const double fSin(sin(-fAngle)); + const double fCos(cos(-fAngle)); + const bool bMiter(basegfx::B2DLineJoin::Miter == aLineJoin); + const double fMiterSin(bMiter ? sin(-(fAngle/2.0)) : 0.0); + const double fMiterCos(bMiter ? cos(-(fAngle/2.0)) : 0.0); + double fPos(-M_PI_2); + basegfx::B3DPoint aPointOnXY, aPointRotY, aNextPointOnXY, aNextPointRotY; + basegfx::B3DPoint aCurrMiter, aNextMiter; + basegfx::B3DPolygon aNewPolygon, aMiterPolygon; + + // close polygon + aNewPolygon.setClosed(true); + aMiterPolygon.setClosed(true); + + for(sal_uInt32 a(0); a < nVerSeg; a++) + { + const bool bFirst(0 == a); + const bool bLast(a + 1 == nVerSeg); + + if(bFirst || !bLast) + { + fPos += fInc; + + aNextPointOnXY = basegfx::B3DPoint( + cos(fPos), + sin(fPos), + 0.0); + + aNextPointRotY = basegfx::B3DPoint( + aNextPointOnXY.getX() * fCos, + aNextPointOnXY.getY(), + aNextPointOnXY.getX() * fSin); + + if(bMiter) + { + aNextMiter = basegfx::B3DPoint( + aNextPointOnXY.getX(), + aNextPointOnXY.getY(), + fMiterSin * (aNextPointOnXY.getX() / fMiterCos)); + } + } + + if(bFirst) + { + aNewPolygon.clear(); + + if(bMiter) + { + aNewPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0)); + aNewPolygon.append(aNextPointOnXY); + aNewPolygon.append(aNextMiter); + + aMiterPolygon.clear(); + aMiterPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0)); + aMiterPolygon.append(aNextMiter); + aMiterPolygon.append(aNextPointRotY); + } + else + { + aNewPolygon.append(basegfx::B3DPoint(0.0, -1.0, 0.0)); + aNewPolygon.append(aNextPointOnXY); + aNewPolygon.append(aNextPointRotY); + } + } + else if(bLast) + { + aNewPolygon.clear(); + + if(bMiter) + { + aNewPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0)); + aNewPolygon.append(aCurrMiter); + aNewPolygon.append(aPointOnXY); + + aMiterPolygon.clear(); + aMiterPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0)); + aMiterPolygon.append(aPointRotY); + aMiterPolygon.append(aCurrMiter); + } + else + { + aNewPolygon.append(basegfx::B3DPoint(0.0, 1.0, 0.0)); + aNewPolygon.append(aPointRotY); + aNewPolygon.append(aPointOnXY); + } + } + else + { + aNewPolygon.clear(); + + if(bMiter) + { + aNewPolygon.append(aPointOnXY); + aNewPolygon.append(aNextPointOnXY); + aNewPolygon.append(aNextMiter); + aNewPolygon.append(aCurrMiter); + + aMiterPolygon.clear(); + aMiterPolygon.append(aCurrMiter); + aMiterPolygon.append(aNextMiter); + aMiterPolygon.append(aNextPointRotY); + aMiterPolygon.append(aPointRotY); + } + else + { + aNewPolygon.append(aPointRotY); + aNewPolygon.append(aPointOnXY); + aNewPolygon.append(aNextPointOnXY); + aNewPolygon.append(aNextPointRotY); + } + } + + // set normals + for(sal_uInt32 b(0); b < aNewPolygon.count(); b++) + { + aNewPolygon.setNormal(b, basegfx::B3DVector(aNewPolygon.getB3DPoint(b))); + } + + // create primitive + if(aNewPolygon.count()) + { + const basegfx::B3DPolyPolygon aNewPolyPolygon(aNewPolygon); + aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aNewPolyPolygon, rMaterial, false)); + } + + if(bMiter && aMiterPolygon.count()) + { + // set normals + for(sal_uInt32 c(0); c < aMiterPolygon.count(); c++) + { + aMiterPolygon.setNormal(c, basegfx::B3DVector(aMiterPolygon.getB3DPoint(c))); + } + + // create primitive + const basegfx::B3DPolyPolygon aMiterPolyPolygon(aMiterPolygon); + aResultVector.push_back(new PolyPolygonMaterialPrimitive3D(aMiterPolyPolygon, rMaterial, false)); + } + + // prepare next step + if(bFirst || !bLast) + { + aPointOnXY = aNextPointOnXY; + aPointRotY = aNextPointRotY; + + if(bMiter) + { + aCurrMiter = aNextMiter; + } + } + } + } + } + + Primitive3DContainer aRetval(aResultVector.size()); + + for(size_t a(0); a < aResultVector.size(); a++) + { + aRetval[a] = Primitive3DReference(aResultVector[a]); + } + + return aRetval; + } + + basegfx::B3DHomMatrix getRotationFromVector(const basegfx::B3DVector& rVector) + { + // build transformation from unit vector to vector + basegfx::B3DHomMatrix aRetval; + + // get applied rotations from angles in XY and in XZ (cartesian) + const double fRotInXY(atan2(rVector.getY(), rVector.getXZLength())); + const double fRotInXZ(atan2(-rVector.getZ(), rVector.getX())); + + // apply rotations. Rot around Z needs to be done first, so apply in two steps + aRetval.rotate(0.0, 0.0, fRotInXY); + aRetval.rotate(0.0, fRotInXZ, 0.0); + + return aRetval; + } + } // end of anonymous namespace + + +using namespace com::sun::star; + + Primitive3DContainer PolygonTubePrimitive3D::impCreate3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + const sal_uInt32 nPointCount(getB3DPolygon().count()); + std::vector< BasePrimitive3D* > aResultVector; + + if(nPointCount) + { + if(basegfx::fTools::more(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) + const bool bClosed(getB3DPolygon().isClosed()); + const bool bNoLineJoin(basegfx::B2DLineJoin::NONE == getLineJoin()); + const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1); + basegfx::B3DPoint aLast(getB3DPolygon().getB3DPoint(nPointCount - 1)); + basegfx::B3DPoint aCurr(getB3DPolygon().getB3DPoint(0)); + + for(sal_uInt32 a(0); a < nLoopCount; a++) + { + // get next data + const basegfx::B3DPoint aNext(getB3DPolygon().getB3DPoint((a + 1) % nPointCount)); + const basegfx::B3DVector aForw(aNext - aCurr); + const double fForwLen(aForw.getLength()); + + if(basegfx::fTools::more(fForwLen, 0.0)) + { + // find out if linecap is active + const bool bFirst(!a); + const bool bLast(a + 1 == nLoopCount); + const bool bLineCapPossible(!bClosed && (bFirst || bLast)); + const bool bLineCapRound(bLineCapPossible && css::drawing::LineCap_ROUND == getLineCap()); + const bool bLineCapSquare(bLineCapPossible && css::drawing::LineCap_SQUARE == getLineCap()); + + // get rotation from vector, this describes rotation from (1, 0, 0) to aForw + basegfx::B3DHomMatrix aRotVector(getRotationFromVector(aForw)); + + // prepare transformations for tube and cap + basegfx::B3DHomMatrix aTubeTrans; + basegfx::B3DHomMatrix aCapTrans; + + // cap gets radius size + aCapTrans.scale(getRadius(), getRadius(), getRadius()); + + if(bLineCapSquare) + { + // when square line cap just prolong line segment in X, maybe 2 x radius when + // first and last (simple line segment) + const double fExtraLength(bFirst && bLast ? getRadius() * 2.0 : getRadius()); + + aTubeTrans.scale(fForwLen + fExtraLength, getRadius(), getRadius()); + + if(bFirst) + { + // correct start positions for tube and cap when first and square prolonged + aTubeTrans.translate(-getRadius(), 0.0, 0.0); + aCapTrans.translate(-getRadius(), 0.0, 0.0); + } + } + else + { + // normal tube size + aTubeTrans.scale(fForwLen, getRadius(), getRadius()); + } + + // rotate and translate tube and cap + aTubeTrans *= aRotVector; + aTubeTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); + aCapTrans *= aRotVector; + aCapTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); + + if(bNoLineJoin || (!bClosed && bFirst)) + { + // line start edge, build transformed primitiveVector3D + Primitive3DContainer aSequence; + + if(bLineCapRound && bFirst) + { + // LineCapRound used + aSequence = getLineCapRoundSegments(nSegments, aMaterial); + } + else + { + // simple closing cap + aSequence = getLineCapSegments(nSegments, aMaterial); + } + + aResultVector.push_back(new TransformPrimitive3D(aCapTrans, aSequence)); + } + else + { + const basegfx::B3DVector aBack(aCurr - aLast); + const double fCross(basegfx::cross(aBack, aForw).getLength()); + + if(!basegfx::fTools::equalZero(fCross)) + { + // line connect non-parallel, aBack, aForw, use getLineJoin() + const double fAngle(acos(aBack.scalar(aForw) / (fForwLen * aBack.getLength()))); // 0.0 .. M_PI_2 + Primitive3DContainer aNewList( + getLineJoinSegments( + nSegments, + aMaterial, + fAngle, + getMiterMinimumAngle(), + getLineJoin())); + + // calculate transformation. First, get angle in YZ between nForw projected on (1, 0, 0) and nBack + basegfx::B3DHomMatrix aInvRotVector(aRotVector); + aInvRotVector.invert(); + basegfx::B3DVector aTransBack(aInvRotVector * aBack); + const double fRotInYZ(atan2(aTransBack.getY(), aTransBack.getZ())); + + // create trans by rotating unit sphere with angle 90 degrees around Y, then 180-fRot in X. + // Also apply usual scaling and translation + basegfx::B3DHomMatrix aSphereTrans; + aSphereTrans.rotate(0.0, M_PI_2, 0.0); + aSphereTrans.rotate(M_PI - fRotInYZ, 0.0, 0.0); + aSphereTrans *= aRotVector; + aSphereTrans.scale(getRadius(), getRadius(), getRadius()); + aSphereTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); + + // line start edge, build transformed primitiveVector3D + aResultVector.push_back( + new TransformPrimitive3D( + aSphereTrans, + aNewList)); + } + } + + // create line segments, build transformed primitiveVector3D + aResultVector.push_back( + new TransformPrimitive3D( + aTubeTrans, + getLineTubeSegments(nSegments, aMaterial))); + + if(bNoLineJoin || (!bClosed && bLast)) + { + // line end edge + basegfx::B3DHomMatrix aBackCapTrans; + + // Mirror (line end) and radius scale + aBackCapTrans.rotate(0.0, M_PI, 0.0); + aBackCapTrans.scale(getRadius(), getRadius(), getRadius()); + + if(bLineCapSquare && bLast) + { + // correct position when square and prolonged + aBackCapTrans.translate(fForwLen + getRadius(), 0.0, 0.0); + } + else + { + // standard position + aBackCapTrans.translate(fForwLen, 0.0, 0.0); + } + + // rotate and translate to destination + aBackCapTrans *= aRotVector; + aBackCapTrans.translate(aCurr.getX(), aCurr.getY(), aCurr.getZ()); + + // get primitiveVector3D + Primitive3DContainer aSequence; + + if(bLineCapRound && bLast) + { + // LineCapRound used + aSequence = getLineCapRoundSegments(nSegments, aMaterial); + } + else + { + // simple closing cap + aSequence = getLineCapSegments(nSegments, aMaterial); + } + + aResultVector.push_back( + new TransformPrimitive3D( + aBackCapTrans, + aSequence)); + } + } + + // prepare next loop step + aLast = aCurr; + aCurr = aNext; + } + } + else + { + // create hairline + aResultVector.push_back(new PolygonHairlinePrimitive3D(getB3DPolygon(), getBColor())); + } + } + + // prepare return value + Primitive3DContainer aRetval(aResultVector.size()); + + for(size_t a(0); a < aResultVector.size(); a++) + { + aRetval[a] = Primitive3DReference(aResultVector[a]); + } + + return aRetval; + } + + PolygonTubePrimitive3D::PolygonTubePrimitive3D( + const basegfx::B3DPolygon& rPolygon, + const basegfx::BColor& rBColor, + double fRadius, basegfx::B2DLineJoin aLineJoin, + css::drawing::LineCap aLineCap, + double fDegreeStepWidth, + double fMiterMinimumAngle) + : PolygonHairlinePrimitive3D(rPolygon, rBColor), + mfRadius(fRadius), + mfDegreeStepWidth(fDegreeStepWidth), + mfMiterMinimumAngle(fMiterMinimumAngle), + maLineJoin(aLineJoin), + maLineCap(aLineCap) + { + } + + bool PolygonTubePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(PolygonHairlinePrimitive3D::operator==(rPrimitive)) + { + const PolygonTubePrimitive3D& rCompare = static_cast<const PolygonTubePrimitive3D&>(rPrimitive); + + return (getRadius() == rCompare.getRadius() + && getDegreeStepWidth() == rCompare.getDegreeStepWidth() + && getMiterMinimumAngle() == rCompare.getMiterMinimumAngle() + && getLineJoin() == rCompare.getLineJoin() + && getLineCap() == rCompare.getLineCap()); + } + + return false; + } + + Primitive3DContainer PolygonTubePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(getLast3DDecomposition().empty()) + { + const Primitive3DContainer aNewSequence(impCreate3DDecomposition(rViewInformation)); + const_cast< PolygonTubePrimitive3D* >(this)->maLast3DDecomposition = aNewSequence; + } + + return getLast3DDecomposition(); + } + + // provide unique ID + ImplPrimitive3DIDBlock(PolygonTubePrimitive3D, PRIMITIVE3D_ID_POLYGONTUBEPRIMITIVE3D) + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx b/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx new file mode 100644 index 000000000..41e62b611 --- /dev/null +++ b/drawinglayer/source/primitive3d/polypolygonprimitive3d.cxx @@ -0,0 +1,64 @@ +/* -*- 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/primitive3d/polypolygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + PolyPolygonMaterialPrimitive3D::PolyPolygonMaterialPrimitive3D( + const basegfx::B3DPolyPolygon& rPolyPolygon, + const attribute::MaterialAttribute3D& rMaterial, + bool bDoubleSided) + : maPolyPolygon(rPolyPolygon), + maMaterial(rMaterial), + mbDoubleSided(bDoubleSided) + { + } + + bool PolyPolygonMaterialPrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(BasePrimitive3D::operator==(rPrimitive)) + { + const PolyPolygonMaterialPrimitive3D& rCompare = static_cast<const PolyPolygonMaterialPrimitive3D&>(rPrimitive); + + return (getB3DPolyPolygon() == rCompare.getB3DPolyPolygon() + && getMaterial() == rCompare.getMaterial() + && getDoubleSided() == rCompare.getDoubleSided()); + } + + return false; + } + + basegfx::B3DRange PolyPolygonMaterialPrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + return basegfx::utils::getRange(getB3DPolyPolygon()); + } + + // provide unique ID + ImplPrimitive3DIDBlock(PolyPolygonMaterialPrimitive3D, PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrcubeprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrcubeprimitive3d.cxx new file mode 100644 index 000000000..6297c7df0 --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrcubeprimitive3d.cxx @@ -0,0 +1,199 @@ +/* -*- 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/primitive3d/sdrcubeprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <primitive3d/sdrdecompositiontools3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + Primitive3DContainer SdrCubePrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + const basegfx::B3DRange aUnitRange(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); + Primitive3DContainer aRetval; + basegfx::B3DPolyPolygon aFill(basegfx::utils::createCubeFillPolyPolygonFromB3DRange(aUnitRange)); + + // normal creation + if(!getSdrLFSAttribute().getFill().isDefault()) + { + if(css::drawing::NormalsKind_SPECIFIC == getSdr3DObjectAttribute().getNormalsKind() + || css::drawing::NormalsKind_SPHERE == getSdr3DObjectAttribute().getNormalsKind()) + { + // create sphere normals + const basegfx::B3DPoint aCenter(basegfx::utils::getRange(aFill).getCenter()); + aFill = basegfx::utils::applyDefaultNormalsSphere(aFill, aCenter); + } + + if(getSdr3DObjectAttribute().getNormalsInvert()) + { + // invert normals + aFill = basegfx::utils::invertNormals(aFill); + } + } + + // texture coordinates + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // handle texture coordinates X + const bool bParallelX(css::drawing::TextureProjectionMode_PARALLEL == getSdr3DObjectAttribute().getTextureProjectionX()); + const bool bObjectSpecificX(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionX()); + const bool bSphereX(!bParallelX && (css::drawing::TextureProjectionMode_SPHERE == getSdr3DObjectAttribute().getTextureProjectionX())); + + // handle texture coordinates Y + const bool bParallelY(css::drawing::TextureProjectionMode_PARALLEL == getSdr3DObjectAttribute().getTextureProjectionY()); + const bool bObjectSpecificY(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionY()); + const bool bSphereY(!bParallelY && (css::drawing::TextureProjectionMode_SPHERE == getSdr3DObjectAttribute().getTextureProjectionY())); + + if(bParallelX || bParallelY) + { + // apply parallel texture coordinates in X and/or Y + const basegfx::B3DRange aRange(basegfx::utils::getRange(aFill)); + aFill = basegfx::utils::applyDefaultTextureCoordinatesParallel(aFill, aRange, bParallelX, bParallelY); + } + + if(bSphereX || bSphereY) + { + // apply spherical texture coordinates in X and/or Y + const basegfx::B3DRange aRange(basegfx::utils::getRange(aFill)); + const basegfx::B3DPoint aCenter(aRange.getCenter()); + aFill = basegfx::utils::applyDefaultTextureCoordinatesSphere(aFill, aCenter, bSphereX, bSphereY); + } + + if(bObjectSpecificX || bObjectSpecificY) + { + // object-specific + for(sal_uInt32 a(0); a < aFill.count(); a++) + { + basegfx::B3DPolygon aTmpPoly(aFill.getB3DPolygon(a)); + + if(aTmpPoly.count() >= 4) + { + for(sal_uInt32 b(0); b < 4; b++) + { + basegfx::B2DPoint aPoint(aTmpPoly.getTextureCoordinate(b)); + + if(bObjectSpecificX) + { + aPoint.setX((1 == b || 2 == b) ? 1.0 : 0.0); + } + + if(bObjectSpecificY) + { + aPoint.setY((2 == b || 3 == b) ? 1.0 : 0.0); + } + + aTmpPoly.setTextureCoordinate(b, aPoint); + } + + aFill.setB3DPolygon(a, aTmpPoly); + } + } + } + + // transform texture coordinates to texture size + basegfx::B2DHomMatrix aTexMatrix; + aTexMatrix.scale(getTextureSize().getX(), getTextureSize().getY()); + aFill.transformTextureCoordinates(aTexMatrix); + } + + // build vector of PolyPolygons + std::vector< basegfx::B3DPolyPolygon > a3DPolyPolygonVector; + + for(sal_uInt32 a(0); a < aFill.count(); a++) + { + a3DPolyPolygonVector.emplace_back(aFill.getB3DPolygon(a)); + } + + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // add fill + aRetval = create3DPolyPolygonFillPrimitives( + a3DPolyPolygonVector, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute(), + getSdrLFSAttribute().getFill(), + getSdrLFSAttribute().getFillFloatTransGradient()); + } + else + { + // create simplified 3d hit test geometry + aRetval = createHiddenGeometryPrimitives3D( + a3DPolyPolygonVector, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute()); + } + + // add line + if(!getSdrLFSAttribute().getLine().isDefault()) + { + basegfx::B3DPolyPolygon aLine(basegfx::utils::createCubePolyPolygonFromB3DRange(aUnitRange)); + const Primitive3DContainer aLines(create3DPolyPolygonLinePrimitives( + aLine, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aLines); + } + + // add shadow + if(!getSdrLFSAttribute().getShadow().isDefault() && !aRetval.empty()) + { + const Primitive3DContainer aShadow(createShadowPrimitive3D( + aRetval, getSdrLFSAttribute().getShadow(), getSdr3DObjectAttribute().getShadow3D())); + aRetval.append(aShadow); + } + + return aRetval; + } + + SdrCubePrimitive3D::SdrCubePrimitive3D( + const basegfx::B3DHomMatrix& rTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute) + : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute) + { + } + + basegfx::B3DRange SdrCubePrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + // use default from sdrPrimitive3D which uses transformation expanded by line width/2. + // The parent implementation which uses the ranges of the decomposition would be more + // correct, but for historical reasons it is necessary to do the old method: To get + // the range of the non-transformed geometry and transform it then. This leads to different + // ranges where the new method is more correct, but the need to keep the old behaviour + // has priority here. + return getStandard3DRange(); + } + + // provide unique ID + ImplPrimitive3DIDBlock(SdrCubePrimitive3D, PRIMITIVE3D_ID_SDRCUBEPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx b/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx new file mode 100644 index 000000000..ec6bfaf3c --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx @@ -0,0 +1,324 @@ +/* -*- 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 <primitive3d/sdrdecompositiontools3d.hxx> +#include <drawinglayer/attribute/strokeattribute.hxx> +#include <drawinglayer/primitive3d/baseprimitive3d.hxx> +#include <drawinglayer/primitive3d/polygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> +#include <drawinglayer/attribute/fillgraphicattribute.hxx> +#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <primitive3d/textureprimitive3d.hxx> +#include <drawinglayer/primitive3d/modifiedcolorprimitive3d.hxx> +#include <primitive3d/hatchtextureprimitive3d.hxx> +#include <primitive3d/shadowprimitive3d.hxx> +#include <basegfx/range/b2drange.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrobjectattribute3d.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> +#include <primitive3d/hiddengeometryprimitive3d.hxx> +#include <rtl/ref.hxx> + + +namespace drawinglayer::primitive3d +{ + basegfx::B3DRange getRangeFrom3DGeometry(std::vector< basegfx::B3DPolyPolygon >& rFill) + { + basegfx::B3DRange aRetval; + + for(const basegfx::B3DPolyPolygon & a : rFill) + { + aRetval.expand(basegfx::utils::getRange(a)); + } + + return aRetval; + } + + void applyNormalsKindSphereTo3DGeometry(std::vector< basegfx::B3DPolyPolygon >& rFill, const basegfx::B3DRange& rRange) + { + // create sphere normals + const basegfx::B3DPoint aCenter(rRange.getCenter()); + + for(basegfx::B3DPolyPolygon & a : rFill) + { + a = basegfx::utils::applyDefaultNormalsSphere(a, aCenter); + } + } + + void applyNormalsKindFlatTo3DGeometry(std::vector< basegfx::B3DPolyPolygon >& rFill) + { + for(basegfx::B3DPolyPolygon & a : rFill) + { + a.clearNormals(); + } + } + + void applyNormalsInvertTo3DGeometry(std::vector< basegfx::B3DPolyPolygon >& rFill) + { + // invert normals + for(basegfx::B3DPolyPolygon & a : rFill) + { + a = basegfx::utils::invertNormals(a); + } + } + + void applyTextureTo3DGeometry( + css::drawing::TextureProjectionMode eModeX, + css::drawing::TextureProjectionMode eModeY, + std::vector< basegfx::B3DPolyPolygon >& rFill, + const basegfx::B3DRange& rRange, + const basegfx::B2DVector& rTextureSize) + { + // handle texture coordinates X + const bool bParallelX(css::drawing::TextureProjectionMode_PARALLEL == eModeX); + const bool bSphereX(!bParallelX && (css::drawing::TextureProjectionMode_SPHERE == eModeX)); + + // handle texture coordinates Y + const bool bParallelY(css::drawing::TextureProjectionMode_PARALLEL == eModeY); + const bool bSphereY(!bParallelY && (css::drawing::TextureProjectionMode_SPHERE == eModeY)); + + if(bParallelX || bParallelY) + { + // apply parallel texture coordinates in X and/or Y + for(auto & a: rFill) + { + a = basegfx::utils::applyDefaultTextureCoordinatesParallel(a, rRange, bParallelX, bParallelY); + } + } + + if(bSphereX || bSphereY) + { + // apply spherical texture coordinates in X and/or Y + const basegfx::B3DPoint aCenter(rRange.getCenter()); + + for(auto & a: rFill) + { + a = basegfx::utils::applyDefaultTextureCoordinatesSphere(a, aCenter, bSphereX, bSphereY); + } + } + + // transform texture coordinates to texture size + basegfx::B2DHomMatrix aTexMatrix; + aTexMatrix.scale(rTextureSize.getX(), rTextureSize.getY()); + + for(auto & a: rFill) + { + a.transformTextureCoordinates(aTexMatrix); + } + } + + Primitive3DContainer create3DPolyPolygonLinePrimitives( + const basegfx::B3DPolyPolygon& rUnitPolyPolygon, + const basegfx::B3DHomMatrix& rObjectTransform, + const attribute::SdrLineAttribute& rLine) + { + // prepare fully scaled polyPolygon + basegfx::B3DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon); + aScaledPolyPolygon.transform(rObjectTransform); + + // create line and stroke attribute + const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap()); + const attribute::StrokeAttribute aStrokeAttribute(std::vector(rLine.getDotDashArray()), rLine.getFullDotDashLen()); + + // create primitives + Primitive3DContainer aRetval(aScaledPolyPolygon.count()); + + for(sal_uInt32 a(0); a < aScaledPolyPolygon.count(); a++) + { + const Primitive3DReference xRef(new PolygonStrokePrimitive3D(aScaledPolyPolygon.getB3DPolygon(a), aLineAttribute, aStrokeAttribute)); + aRetval[a] = xRef; + } + + if(0.0 != rLine.getTransparence()) + { + // create UnifiedTransparenceTexturePrimitive3D, add created primitives and exchange + const Primitive3DReference xRef(new UnifiedTransparenceTexturePrimitive3D(rLine.getTransparence(), aRetval)); + aRetval = { xRef }; + } + + return aRetval; + } + + Primitive3DContainer create3DPolyPolygonFillPrimitives( + const std::vector< basegfx::B3DPolyPolygon >& r3DPolyPolygonVector, + const basegfx::B3DHomMatrix& rObjectTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::Sdr3DObjectAttribute& aSdr3DObjectAttribute, + const attribute::SdrFillAttribute& rFill, + const attribute::FillGradientAttribute& rFillGradient) + { + Primitive3DContainer aRetval; + + if(!r3DPolyPolygonVector.empty()) + { + // create list of simple fill primitives + aRetval.resize(r3DPolyPolygonVector.size()); + + for(size_t a(0); a < r3DPolyPolygonVector.size(); a++) + { + // get scaled PolyPolygon + basegfx::B3DPolyPolygon aScaledPolyPolygon(r3DPolyPolygonVector[a]); + aScaledPolyPolygon.transform(rObjectTransform); + + if(aScaledPolyPolygon.areNormalsUsed()) + { + aScaledPolyPolygon.transformNormals(rObjectTransform); + } + + const Primitive3DReference xRef(new PolyPolygonMaterialPrimitive3D( + aScaledPolyPolygon, + aSdr3DObjectAttribute.getMaterial(), + aSdr3DObjectAttribute.getDoubleSided())); + aRetval[a] = xRef; + } + + // look for and evtl. build texture sub-group primitive + if(!rFill.getGradient().isDefault() + || !rFill.getHatch().isDefault() + || !rFill.getFillGraphic().isDefault()) + { + bool bModulate(css::drawing::TextureMode_MODULATE == aSdr3DObjectAttribute.getTextureMode()); + bool bFilter(aSdr3DObjectAttribute.getTextureFilter()); + rtl::Reference<BasePrimitive3D> pNewTexturePrimitive3D; + + if(!rFill.getGradient().isDefault()) + { + // create gradientTexture3D with sublist, add to local aRetval + pNewTexturePrimitive3D = new GradientTexturePrimitive3D( + rFill.getGradient(), + aRetval, + rTextureSize, + bModulate, + bFilter); + } + else if(!rFill.getHatch().isDefault()) + { + // create hatchTexture3D with sublist, add to local aRetval + pNewTexturePrimitive3D = new HatchTexturePrimitive3D( + rFill.getHatch(), + aRetval, + rTextureSize, + bModulate, + bFilter); + } + else // if(!rFill.getFillGraphic().isDefault()) + { + // create bitmapTexture3D with sublist, add to local aRetval + const basegfx::B2DRange aTexRange(0.0, 0.0, rTextureSize.getX(), rTextureSize.getY()); + + pNewTexturePrimitive3D = new BitmapTexturePrimitive3D( + rFill.getFillGraphic().createFillGraphicAttribute(aTexRange), + aRetval, + rTextureSize, + bModulate, + bFilter); + } + + // exchange aRetval content with texture group + const Primitive3DReference xRef(pNewTexturePrimitive3D); + aRetval = { xRef }; + + if(css::drawing::TextureKind2_LUMINANCE == aSdr3DObjectAttribute.getTextureKind()) + { + // use modified color primitive to force textures to gray + const basegfx::BColorModifierSharedPtr aBColorModifier = + std::make_shared<basegfx::BColorModifier_gray>(); + const Primitive3DReference xRef2( + new ModifiedColorPrimitive3D( + aRetval, + aBColorModifier)); + + aRetval = { xRef2 }; + } + } + + if(0.0 != rFill.getTransparence()) + { + // create UnifiedTransparenceTexturePrimitive3D with sublist and exchange + const Primitive3DReference xRef(new UnifiedTransparenceTexturePrimitive3D(rFill.getTransparence(), aRetval)); + aRetval = { xRef }; + } + else if(!rFillGradient.isDefault()) + { + // create TransparenceTexturePrimitive3D with sublist and exchange + const Primitive3DReference xRef(new TransparenceTexturePrimitive3D(rFillGradient, aRetval, rTextureSize)); + aRetval = { xRef }; + } + } + + return aRetval; + } + + Primitive3DContainer createShadowPrimitive3D( + const Primitive3DContainer& rSource, + const attribute::SdrShadowAttribute& rShadow, + bool bShadow3D) + { + // create Shadow primitives. Uses already created primitives + if(!rSource.empty() && !basegfx::fTools::moreOrEqual(rShadow.getTransparence(), 1.0)) + { + // prepare new list for shadow geometry + basegfx::B2DHomMatrix aShadowOffset; + aShadowOffset.set(0, 2, rShadow.getOffset().getX()); + aShadowOffset.set(1, 2, rShadow.getOffset().getY()); + + // create shadow primitive and add primitives + const Primitive3DReference xRef(new ShadowPrimitive3D(aShadowOffset, rShadow.getColor(), rShadow.getTransparence(), bShadow3D, rSource)); + return { xRef }; + } + else + { + return Primitive3DContainer(); + } + } + + Primitive3DContainer createHiddenGeometryPrimitives3D( + const std::vector< basegfx::B3DPolyPolygon >& r3DPolyPolygonVector, + const basegfx::B3DHomMatrix& rObjectTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::Sdr3DObjectAttribute& aSdr3DObjectAttribute) + { + // create hidden sub-geometry which can be used for HitTest + // and BoundRect calculations, but will not be visualized + const attribute::SdrFillAttribute aSimplifiedFillAttribute( + 0.0, + basegfx::BColor(), + attribute::FillGradientAttribute(), + attribute::FillHatchAttribute(), + attribute::SdrFillGraphicAttribute()); + + const Primitive3DReference aHidden( + new HiddenGeometryPrimitive3D( + create3DPolyPolygonFillPrimitives( + r3DPolyPolygonVector, + rObjectTransform, + rTextureSize, + aSdr3DObjectAttribute, + aSimplifiedFillAttribute, + attribute::FillGradientAttribute()))); + + return { aHidden }; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx b/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx new file mode 100644 index 000000000..a7015ff8e --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrextrudelathetools3d.cxx @@ -0,0 +1,982 @@ +/* -*- 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/primitive3d/sdrextrudelathetools3d.hxx> + +#include <osl/diagnose.h> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <basegfx/range/b3drange.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/geometry/viewinformation3d.hxx> +#include <numeric> + + +// decomposition helpers for extrude/lathe (rotation) objects + +namespace +{ + + // common helpers + + basegfx::B2DPolyPolygon impScalePolyPolygonOnCenter( + const basegfx::B2DPolyPolygon& rSource, + double fScale) + { + basegfx::B2DPolyPolygon aRetval(rSource); + + if(!basegfx::fTools::equalZero(fScale)) + { + const basegfx::B2DRange aRange(basegfx::utils::getRange(rSource)); + const basegfx::B2DPoint aCenter(aRange.getCenter()); + basegfx::B2DHomMatrix aTrans; + + aTrans.translate(-aCenter.getX(), -aCenter.getY()); + aTrans.scale(fScale, fScale); + aTrans.translate(aCenter.getX(), aCenter.getY()); + aRetval.transform(aTrans); + } + + return aRetval; + } + + void impGetOuterPolyPolygon( + basegfx::B2DPolyPolygon& rPolygon, + basegfx::B2DPolyPolygon& rOuterPolyPolygon, + double fOffset, + bool bCharacterMode) + { + rOuterPolyPolygon = rPolygon; + + if(!basegfx::fTools::more(fOffset, 0.0)) + return; + + if(bCharacterMode) + { + // grow the outside polygon and scale all polygons to original size. This is done + // to avoid a shrink which potentially would lead to self-intersections, but changes + // the original polygon -> not a precision step, so e.g. not usable for charts + const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolygon)); + rPolygon = basegfx::utils::growInNormalDirection(rPolygon, fOffset); + const basegfx::B2DRange aGrownRange(basegfx::utils::getRange(rPolygon)); + const double fScaleX(basegfx::fTools::equalZero(aGrownRange.getWidth()) ? 1.0 : aRange.getWidth() / aGrownRange.getWidth()); + const double fScaleY(basegfx::fTools::equalZero(aGrownRange.getHeight())? 1.0 : aRange.getHeight() / aGrownRange.getHeight()); + basegfx::B2DHomMatrix aScaleTrans; + + aScaleTrans.translate(-aGrownRange.getMinX(), -aGrownRange.getMinY()); + aScaleTrans.scale(fScaleX, fScaleY); + aScaleTrans.translate(aRange.getMinX(), aRange.getMinY()); + rPolygon.transform(aScaleTrans); + rOuterPolyPolygon.transform(aScaleTrans); + } + else + { + // use more precision, shrink the outer polygons. Since this may lead to self-intersections, + // some kind of correction should be applied here after that step + rOuterPolyPolygon = basegfx::utils::growInNormalDirection(rPolygon, -fOffset); + // basegfx::utils::correctGrowShrinkPolygonPair(rPolygon, rOuterPolyPolygon); + } + } + + void impAddInBetweenFill( + basegfx::B3DPolyPolygon& rTarget, + const basegfx::B3DPolyPolygon& rPolA, + const basegfx::B3DPolyPolygon& rPolB, + double fTexVerStart, + double fTexVerStop, + bool bCreateNormals, + bool bCreateTextureCoordinates) + { + OSL_ENSURE(rPolA.count() == rPolB.count(), "impAddInBetweenFill: unequally sized polygons (!)"); + const sal_uInt32 nPolygonCount(std::min(rPolA.count(), rPolB.count())); + + for(sal_uInt32 a(0); a < nPolygonCount; a++) + { + const basegfx::B3DPolygon& aSubA(rPolA.getB3DPolygon(a)); + const basegfx::B3DPolygon& aSubB(rPolB.getB3DPolygon(a)); + OSL_ENSURE(aSubA.count() == aSubB.count(), "impAddInBetweenFill: unequally sized polygons (!)"); + const sal_uInt32 nPointCount(std::min(aSubA.count(), aSubB.count())); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(aSubA.isClosed() ? nPointCount : nPointCount - 1); + double fTexHorMultiplicatorA(0.0), fTexHorMultiplicatorB(0.0); + double fPolygonPosA(0.0), fPolygonPosB(0.0); + + if(bCreateTextureCoordinates) + { + const double fPolygonLengthA(basegfx::utils::getLength(aSubA)); + fTexHorMultiplicatorA = basegfx::fTools::equalZero(fPolygonLengthA) ? 1.0 : 1.0 / fPolygonLengthA; + + const double fPolygonLengthB(basegfx::utils::getLength(aSubB)); + fTexHorMultiplicatorB = basegfx::fTools::equalZero(fPolygonLengthB) ? 1.0 : 1.0 / fPolygonLengthB; + } + + for(sal_uInt32 b(0); b < nEdgeCount; b++) + { + const sal_uInt32 nIndexA(b); + const sal_uInt32 nIndexB((b + 1) % nPointCount); + + const basegfx::B3DPoint aStartA(aSubA.getB3DPoint(nIndexA)); + const basegfx::B3DPoint aEndA(aSubA.getB3DPoint(nIndexB)); + const basegfx::B3DPoint aStartB(aSubB.getB3DPoint(nIndexA)); + const basegfx::B3DPoint aEndB(aSubB.getB3DPoint(nIndexB)); + + basegfx::B3DPolygon aNew; + aNew.setClosed(true); + + aNew.append(aStartA); + aNew.append(aStartB); + aNew.append(aEndB); + aNew.append(aEndA); + + if(bCreateNormals) + { + aNew.setNormal(0, aSubA.getNormal(nIndexA)); + aNew.setNormal(1, aSubB.getNormal(nIndexA)); + aNew.setNormal(2, aSubB.getNormal(nIndexB)); + aNew.setNormal(3, aSubA.getNormal(nIndexB)); + } + + if(bCreateTextureCoordinates) + { + const double fRelTexAL(fPolygonPosA * fTexHorMultiplicatorA); + const double fEdgeLengthA(basegfx::B3DVector(aEndA - aStartA).getLength()); + fPolygonPosA += fEdgeLengthA; + const double fRelTexAR(fPolygonPosA * fTexHorMultiplicatorA); + + const double fRelTexBL(fPolygonPosB * fTexHorMultiplicatorB); + const double fEdgeLengthB(basegfx::B3DVector(aEndB - aStartB).getLength()); + fPolygonPosB += fEdgeLengthB; + const double fRelTexBR(fPolygonPosB * fTexHorMultiplicatorB); + + aNew.setTextureCoordinate(0, basegfx::B2DPoint(fRelTexAL, fTexVerStart)); + aNew.setTextureCoordinate(1, basegfx::B2DPoint(fRelTexBL, fTexVerStop)); + aNew.setTextureCoordinate(2, basegfx::B2DPoint(fRelTexBR, fTexVerStop)); + aNew.setTextureCoordinate(3, basegfx::B2DPoint(fRelTexAR, fTexVerStart)); + } + + rTarget.append(aNew); + } + } + } + } + + void impSetNormal( + basegfx::B3DPolyPolygon& rCandidate, + const basegfx::B3DVector& rNormal) + { + for(sal_uInt32 a(0); a < rCandidate.count(); a++) + { + basegfx::B3DPolygon aSub(rCandidate.getB3DPolygon(a)); + + for(sal_uInt32 b(0); b < aSub.count(); b++) + { + aSub.setNormal(b, rNormal); + } + + rCandidate.setB3DPolygon(a, aSub); + } + } + + void impCreateInBetweenNormals( + basegfx::B3DPolyPolygon& rPolA, + basegfx::B3DPolyPolygon& rPolB) + { + OSL_ENSURE(rPolA.count() == rPolB.count(), "sdrExtrudePrimitive3D: unequally sized polygons (!)"); + const sal_uInt32 nPolygonCount(std::min(rPolA.count(), rPolB.count())); + + for(sal_uInt32 a(0); a < nPolygonCount; a++) + { + basegfx::B3DPolygon aSubA(rPolA.getB3DPolygon(a)); + basegfx::B3DPolygon aSubB(rPolB.getB3DPolygon(a)); + OSL_ENSURE(aSubA.count() == aSubB.count(), "sdrExtrudePrimitive3D: unequally sized polygons (!)"); + const sal_uInt32 nPointCount(std::min(aSubA.count(), aSubB.count())); + + if(nPointCount) + { + basegfx::B3DPoint aPrevA(aSubA.getB3DPoint(nPointCount - 1)); + basegfx::B3DPoint aCurrA(aSubA.getB3DPoint(0)); + const bool bClosed(aSubA.isClosed()); + + for(sal_uInt32 b(0); b < nPointCount; b++) + { + const sal_uInt32 nIndNext((b + 1) % nPointCount); + const basegfx::B3DPoint aNextA(aSubA.getB3DPoint(nIndNext)); + const basegfx::B3DPoint aCurrB(aSubB.getB3DPoint(b)); + + // vector to back + basegfx::B3DVector aDepth(aCurrB - aCurrA); + aDepth.normalize(); + + if(aDepth.equalZero()) + { + // no difference, try to get depth from next point + const basegfx::B3DPoint aNextB(aSubB.getB3DPoint(nIndNext)); + aDepth = aNextB - aNextA; + aDepth.normalize(); + } + + // vector to left (correct for non-closed lines) + const bool bFirstAndNotClosed(!bClosed && 0 == b); + basegfx::B3DVector aLeft(bFirstAndNotClosed ? aCurrA - aNextA : aPrevA - aCurrA); + aLeft.normalize(); + + // create left normal + const basegfx::B3DVector aNormalLeft(aDepth.getPerpendicular(aLeft)); + + // smooth horizontal normals + { + // vector to right (correct for non-closed lines) + const bool bLastAndNotClosed(!bClosed && b + 1 == nPointCount); + basegfx::B3DVector aRight(bLastAndNotClosed ? aCurrA - aPrevA : aNextA - aCurrA); + aRight.normalize(); + + // create right normal + const basegfx::B3DVector aNormalRight(aRight.getPerpendicular(aDepth)); + + // create smoothed in-between normal + basegfx::B3DVector aNewNormal(aNormalLeft + aNormalRight); + aNewNormal.normalize(); + + // set as new normal at polygons + aSubA.setNormal(b, aNewNormal); + aSubB.setNormal(b, aNewNormal); + } + + // prepare next step + aPrevA = aCurrA; + aCurrA = aNextA; + } + + rPolA.setB3DPolygon(a, aSubA); + rPolB.setB3DPolygon(a, aSubB); + } + } + } + + void impMixNormals( + basegfx::B3DPolyPolygon& rPolA, + const basegfx::B3DPolyPolygon& rPolB, + double fWeightA) + { + const double fWeightB(1.0 - fWeightA); + OSL_ENSURE(rPolA.count() == rPolB.count(), "sdrExtrudePrimitive3D: unequally sized polygons (!)"); + const sal_uInt32 nPolygonCount(std::min(rPolA.count(), rPolB.count())); + + for(sal_uInt32 a(0); a < nPolygonCount; a++) + { + basegfx::B3DPolygon aSubA(rPolA.getB3DPolygon(a)); + const basegfx::B3DPolygon& aSubB(rPolB.getB3DPolygon(a)); + OSL_ENSURE(aSubA.count() == aSubB.count(), "sdrExtrudePrimitive3D: unequally sized polygons (!)"); + const sal_uInt32 nPointCount(std::min(aSubA.count(), aSubB.count())); + + for(sal_uInt32 b(0); b < nPointCount; b++) + { + const basegfx::B3DVector aVA(aSubA.getNormal(b) * fWeightA); + const basegfx::B3DVector aVB(aSubB.getNormal(b) * fWeightB); + basegfx::B3DVector aVNew(aVA + aVB); + aVNew.normalize(); + aSubA.setNormal(b, aVNew); + } + + rPolA.setB3DPolygon(a, aSubA); + } + } + + bool impHasCutWith(const basegfx::B2DPolygon& rPoly, const basegfx::B2DPoint& rStart, const basegfx::B2DPoint& rEnd) + { + // polygon is closed, one of the points is a member + const sal_uInt32 nPointCount(rPoly.count()); + + if(!nPointCount) + return false; + + basegfx::B2DPoint aCurrent(rPoly.getB2DPoint(0)); + const basegfx::B2DVector aVector(rEnd - rStart); + + 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); + + if(basegfx::utils::findCut( + rStart, aVector, + aCurrent, aEdgeVector) != CutFlagValue::NONE) + { + return true; + } + + aCurrent = aNext; + } + + return false; + } +} // end of anonymous namespace + + +namespace drawinglayer::primitive3d +{ + void createLatheSlices( + Slice3DVector& rSliceVector, + const basegfx::B2DPolyPolygon& rSource, + double fBackScale, + double fDiagonal, + double fRotation, + sal_uInt32 nSteps, + bool bCharacterMode, + bool bCloseFront, + bool bCloseBack) + { + if(basegfx::fTools::equalZero(fRotation) || 0 == nSteps) + { + // no rotation or no steps, just one plane + rSliceVector.emplace_back(rSource, basegfx::B3DHomMatrix()); + } + else + { + const bool bBackScale(!basegfx::fTools::equal(fBackScale, 1.0)); + const bool bClosedRotation(!bBackScale && basegfx::fTools::equal(fRotation, 2 * M_PI)); + basegfx::B2DPolyPolygon aFront(rSource); + basegfx::B2DPolyPolygon aBack(rSource); + basegfx::B3DHomMatrix aTransformBack; + basegfx::B2DPolyPolygon aOuterBack; + + if(bClosedRotation) + { + bCloseFront = bCloseBack = false; + } + + if(bBackScale) + { + // avoid null zoom + if(basegfx::fTools::equalZero(fBackScale)) + { + fBackScale = 0.000001; + } + + // back is scaled compared to front, create scaled version + aBack = impScalePolyPolygonOnCenter(aBack, fBackScale); + } + + if(bCloseFront || bCloseBack) + { + const basegfx::B2DRange aBaseRange(basegfx::utils::getRange(aFront)); + const double fOuterLength(aBaseRange.getMaxX() * fRotation); + const double fInnerLength(aBaseRange.getMinX() * fRotation); + const double fAverageLength((fOuterLength + fInnerLength) * 0.5); + + if(bCloseFront) + { + const double fOffsetLen((fAverageLength / 12.0) * fDiagonal); + basegfx::B2DPolyPolygon aOuterFront; + impGetOuterPolyPolygon(aFront, aOuterFront, fOffsetLen, bCharacterMode); + basegfx::B3DHomMatrix aTransform; + aTransform.translate(0.0, 0.0, fOffsetLen); + rSliceVector.emplace_back(aOuterFront, aTransform, SLICETYPE3D_FRONTCAP); + } + + if(bCloseBack) + { + const double fOffsetLen((fAverageLength / 12.0) * fDiagonal); + impGetOuterPolyPolygon(aBack, aOuterBack, fOffsetLen, bCharacterMode); + aTransformBack.translate(0.0, 0.0, -fOffsetLen); + aTransformBack.rotate(0.0, fRotation, 0.0); + } + } + + // add start polygon (a = 0) + if(!bClosedRotation) + { + rSliceVector.emplace_back(aFront, basegfx::B3DHomMatrix()); + } + + // create segments (a + 1 .. nSteps) + const double fStepSize(1.0 / static_cast<double>(nSteps)); + + for(sal_uInt32 a(0); a < nSteps; a++) + { + const double fStep(static_cast<double>(a + 1) * fStepSize); + basegfx::B2DPolyPolygon aNewPoly(bBackScale ? basegfx::utils::interpolate(aFront, aBack, fStep) : aFront); + basegfx::B3DHomMatrix aNewMat; + aNewMat.rotate(0.0, fRotation * fStep, 0.0); + rSliceVector.emplace_back(aNewPoly, aNewMat); + } + + if(bCloseBack) + { + rSliceVector.emplace_back(aOuterBack, aTransformBack, SLICETYPE3D_BACKCAP); + } + } + } + + void createExtrudeSlices( + Slice3DVector& rSliceVector, + const basegfx::B2DPolyPolygon& rSource, + double fBackScale, + double fDiagonal, + double fDepth, + bool bCharacterMode, + bool bCloseFront, + bool bCloseBack) + { + if(basegfx::fTools::equalZero(fDepth)) + { + // no depth, just one plane + rSliceVector.emplace_back(rSource, basegfx::B3DHomMatrix()); + } + else + { + // there is depth, create Polygons for front,back and their default depth positions + basegfx::B2DPolyPolygon aFront(rSource); + basegfx::B2DPolyPolygon aBack(rSource); + const bool bBackScale(!basegfx::fTools::equal(fBackScale, 1.0)); + double fZFront(fDepth); // default depth for aFront + double fZBack(0.0); // default depth for aBack + basegfx::B2DPolyPolygon aOuterBack; + + if(bBackScale) + { + // avoid null zoom + if(basegfx::fTools::equalZero(fBackScale)) + { + fBackScale = 0.000001; + } + + // aFront is scaled compared to aBack, create scaled version + aFront = impScalePolyPolygonOnCenter(aFront, fBackScale); + } + + if(bCloseFront) + { + const double fOffset(fDepth * fDiagonal * 0.5); + fZFront = fDepth - fOffset; + basegfx::B2DPolyPolygon aOuterFront; + impGetOuterPolyPolygon(aFront, aOuterFront, fOffset, bCharacterMode); + basegfx::B3DHomMatrix aTransformFront; + aTransformFront.translate(0.0, 0.0, fDepth); + rSliceVector.emplace_back(aOuterFront, aTransformFront, SLICETYPE3D_FRONTCAP); + } + + if(bCloseBack) + { + const double fOffset(fDepth * fDiagonal * 0.5); + fZBack = fOffset; + impGetOuterPolyPolygon(aBack, aOuterBack, fOffset, bCharacterMode); + } + + // add front and back polygons at evtl. changed depths + { + basegfx::B3DHomMatrix aTransformA, aTransformB; + + aTransformA.translate(0.0, 0.0, fZFront); + rSliceVector.emplace_back(aFront, aTransformA); + + aTransformB.translate(0.0, 0.0, fZBack); + rSliceVector.emplace_back(aBack, aTransformB); + } + + if(bCloseBack) + { + rSliceVector.emplace_back(aOuterBack, basegfx::B3DHomMatrix(), SLICETYPE3D_BACKCAP); + } + } + } + + basegfx::B3DPolyPolygon extractHorizontalLinesFromSlice(const Slice3DVector& rSliceVector, bool bCloseHorLines) + { + basegfx::B3DPolyPolygon aRetval; + const sal_uInt32 nNumSlices(rSliceVector.size()); + + if(nNumSlices) + { + const sal_uInt32 nSlideSubPolygonCount(rSliceVector[0].getB3DPolyPolygon().count()); + + for(sal_uInt32 b(0); b < nSlideSubPolygonCount; b++) + { + const sal_uInt32 nSubPolygonPointCount(rSliceVector[0].getB3DPolyPolygon().getB3DPolygon(b).count()); + + for(sal_uInt32 c(0); c < nSubPolygonPointCount; c++) + { + basegfx::B3DPolygon aNew; + + for(sal_uInt32 d(0); d < nNumSlices; d++) + { + const bool bSamePolygonCount(nSlideSubPolygonCount == rSliceVector[d].getB3DPolyPolygon().count()); + const bool bSamePointCount(nSubPolygonPointCount == rSliceVector[d].getB3DPolyPolygon().getB3DPolygon(b).count()); + + if(bSamePolygonCount && bSamePointCount) + { + aNew.append(rSliceVector[d].getB3DPolyPolygon().getB3DPolygon(b).getB3DPoint(c)); + } + else + { + OSL_ENSURE(bSamePolygonCount, "Slice tools::PolyPolygon with different Polygon count (!)"); + OSL_ENSURE(bSamePointCount, "Slice Polygon with different point count (!)"); + } + } + + aNew.setClosed(bCloseHorLines); + aRetval.append(aNew); + } + } + } + + return aRetval; + } + + basegfx::B3DPolyPolygon extractVerticalLinesFromSlice(const Slice3DVector& rSliceVector) + { + basegfx::B3DPolyPolygon aRetval; + const sal_uInt32 nNumSlices(rSliceVector.size()); + + for(sal_uInt32 a(0); a < nNumSlices; a++) + { + aRetval.append(rSliceVector[a].getB3DPolyPolygon()); + } + + return aRetval; + } + + void extractPlanesFromSlice( + std::vector< basegfx::B3DPolyPolygon >& rFill, + const Slice3DVector& rSliceVector, + bool bCreateNormals, + bool bSmoothNormals, + bool bSmoothLids, + bool bClosed, + double fSmoothNormalsMix, + double fSmoothLidsMix, + bool bCreateTextureCoordinates, + const basegfx::B2DHomMatrix& rTexTransform) + { + const sal_uInt32 nNumSlices(rSliceVector.size()); + + if(!nNumSlices) + return; + + // common parameters + const sal_uInt32 nLoopCount(bClosed ? nNumSlices : nNumSlices - 1); + basegfx::B3DPolyPolygon aEdgeRounding; + sal_uInt32 a; + + // texture parameters + double fInvTexHeight(1.0); + std::vector<double> aTexHeightArray; + basegfx::B3DRange aTexRangeFront; + basegfx::B3DRange aTexRangeBack; + + if(bCreateTextureCoordinates) + { + aTexRangeFront = basegfx::utils::getRange(rSliceVector[0].getB3DPolyPolygon()); + aTexRangeBack = basegfx::utils::getRange(rSliceVector[nNumSlices - 1].getB3DPolyPolygon()); + + if(aTexRangeBack.getDepth() > aTexRangeBack.getWidth()) + { + // last polygon is rotated so that depth is bigger than width, exchange X and Z + // for making applyDefaultTextureCoordinatesParallel use Z instead of X for + // horizontal texture coordinate + aTexRangeBack = basegfx::B3DRange( + aTexRangeBack.getMinZ(), aTexRangeBack.getMinY(), aTexRangeBack.getMinX(), + aTexRangeBack.getMaxZ(), aTexRangeBack.getMaxY(), aTexRangeBack.getMaxX()); + } + + basegfx::B3DPoint aCenter(basegfx::utils::getRange(rSliceVector[0].getB3DPolyPolygon()).getCenter()); + + for(a = 0; a < nLoopCount; a++) + { + const basegfx::B3DPoint aNextCenter(basegfx::utils::getRange(rSliceVector[(a + 1) % nNumSlices].getB3DPolyPolygon()).getCenter()); + const double fLength(basegfx::B3DVector(aNextCenter - aCenter).getLength()); + aTexHeightArray.push_back(fLength); + aCenter = aNextCenter; + } + + const double fTexHeight(std::accumulate(aTexHeightArray.begin(), aTexHeightArray.end(), 0.0)); + + if(!basegfx::fTools::equalZero(fTexHeight)) + { + fInvTexHeight = 1.0 / fTexHeight; + } + } + + if(nLoopCount) + { + double fTexHeightPos(0.0); + for(a = 0; a < nLoopCount; a++) + { + const Slice3D& rSliceA(rSliceVector[a]); + const Slice3D& rSliceB(rSliceVector[(a + 1) % nNumSlices]); + const bool bAcceptPair(SLICETYPE3D_REGULAR == rSliceA.getSliceType() && SLICETYPE3D_REGULAR == rSliceB.getSliceType()); + basegfx::B3DPolyPolygon aPolA(rSliceA.getB3DPolyPolygon()); + basegfx::B3DPolyPolygon aPolB(rSliceB.getB3DPolyPolygon()); + + if(bAcceptPair) + { + if(bCreateNormals) + { + impCreateInBetweenNormals(aPolB, aPolA); + } + + { + const sal_uInt32 nIndPrev((a + nNumSlices - 1) % nNumSlices); + const Slice3D& rSlicePrev(rSliceVector[nIndPrev]); + basegfx::B3DPolyPolygon aPrev(rSlicePrev.getB3DPolyPolygon()); + basegfx::B3DPolyPolygon aPolAA(rSliceA.getB3DPolyPolygon()); + + if(SLICETYPE3D_FRONTCAP == rSlicePrev.getSliceType()) + { + basegfx::B3DPolyPolygon aFront(rSlicePrev.getB3DPolyPolygon()); + const bool bHasSlant(aPolAA != aPrev); + + if(bCreateTextureCoordinates) + { + aFront = basegfx::utils::applyDefaultTextureCoordinatesParallel(aFront, aTexRangeFront); + } + + if(bCreateNormals) + { + basegfx::B3DVector aNormal(0.0, 0.0, -1.0); + + if(aFront.count()) + { + aNormal = -aFront.getB3DPolygon(0).getNormal(); + } + + impSetNormal(aFront, aNormal); + + if(bHasSlant) + { + impCreateInBetweenNormals(aPolAA, aPrev); + + if(bSmoothNormals) + { + // smooth and copy + impMixNormals(aPolA, aPolAA, fSmoothNormalsMix); + aPolAA = aPolA; + } + else + { + // take over from surface + aPolAA = aPolA; + } + + if(bSmoothLids) + { + // smooth and copy + impMixNormals(aFront, aPrev, fSmoothLidsMix); + aPrev = aFront; + } + else + { + // take over from front + aPrev = aFront; + } + } + else + { + if(bSmoothNormals) + { + // smooth + impMixNormals(aPolA, aFront, fSmoothNormalsMix); + } + + if(bSmoothLids) + { + // smooth and copy + impMixNormals(aFront, aPolA, fSmoothLidsMix); + aPolA = aFront; + } + } + } + + if(bHasSlant) + { + double fTexStart{}; + double fTexStop{}; + if(bCreateTextureCoordinates) + { + fTexStart = fTexHeightPos * fInvTexHeight; + fTexStop = (fTexHeightPos - aTexHeightArray[(a + nLoopCount - 1) % nLoopCount]) * fInvTexHeight; + } + + impAddInBetweenFill(aEdgeRounding, aPolAA, aPrev, fTexStart, fTexStop, bCreateNormals, bCreateTextureCoordinates); + } + + aFront.flip(); + rFill.push_back(aFront); + } + else + { + if(bCreateNormals && bSmoothNormals && (nIndPrev != a + 1)) + { + impCreateInBetweenNormals(aPolAA, aPrev); + impMixNormals(aPolA, aPolAA, 0.5); + } + } + } + + { + const sal_uInt32 nIndNext((a + 2) % nNumSlices); + const Slice3D& rSliceNext(rSliceVector[nIndNext]); + basegfx::B3DPolyPolygon aNext(rSliceNext.getB3DPolyPolygon()); + basegfx::B3DPolyPolygon aPolBB(rSliceB.getB3DPolyPolygon()); + + if(SLICETYPE3D_BACKCAP == rSliceNext.getSliceType()) + { + basegfx::B3DPolyPolygon aBack(rSliceNext.getB3DPolyPolygon()); + const bool bHasSlant(aPolBB != aNext); + + if(bCreateTextureCoordinates) + { + aBack = basegfx::utils::applyDefaultTextureCoordinatesParallel(aBack, aTexRangeBack); + } + + if(bCreateNormals) + { + const basegfx::B3DVector aNormal(aBack.count() ? aBack.getB3DPolygon(0).getNormal() : basegfx::B3DVector(0.0, 0.0, 1.0)); + impSetNormal(aBack, aNormal); + + if(bHasSlant) + { + impCreateInBetweenNormals(aNext, aPolBB); + + if(bSmoothNormals) + { + // smooth and copy + impMixNormals(aPolB, aPolBB, fSmoothNormalsMix); + aPolBB = aPolB; + } + else + { + // take over from surface + aPolBB = aPolB; + } + + if(bSmoothLids) + { + // smooth and copy + impMixNormals(aBack, aNext, fSmoothLidsMix); + aNext = aBack; + } + else + { + // take over from back + aNext = aBack; + } + } + else + { + if(bSmoothNormals) + { + // smooth + impMixNormals(aPolB, aBack, fSmoothNormalsMix); + } + + if(bSmoothLids) + { + // smooth and copy + impMixNormals(aBack, aPolB, fSmoothLidsMix); + aPolB = aBack; + } + } + } + + if(bHasSlant) + { + double fTexStart{}; + double fTexStop{}; + if(bCreateTextureCoordinates) + { + fTexStart = (fTexHeightPos + aTexHeightArray[a] + aTexHeightArray[(a + 1) % nLoopCount]) * fInvTexHeight; + fTexStop = (fTexHeightPos + aTexHeightArray[a]) * fInvTexHeight; + } + + impAddInBetweenFill(aEdgeRounding, aNext, aPolBB, fTexStart, fTexStop, bCreateNormals, bCreateTextureCoordinates); + } + + rFill.push_back(aBack); + } + else + { + if(bCreateNormals && bSmoothNormals && (nIndNext != a)) + { + impCreateInBetweenNormals(aNext, aPolBB); + impMixNormals(aPolB, aPolBB, 0.5); + } + } + } + + double fTexStart{}; + double fTexStop{}; + if(bCreateTextureCoordinates) + { + fTexStart = (fTexHeightPos + aTexHeightArray[a]) * fInvTexHeight; + fTexStop = fTexHeightPos * fInvTexHeight; + } + + impAddInBetweenFill(aEdgeRounding, aPolB, aPolA, fTexStart, fTexStop, bCreateNormals, bCreateTextureCoordinates); + } + + if(bCreateTextureCoordinates) + { + fTexHeightPos += aTexHeightArray[a]; + } + } + } + else + { + // no loop, but a single slice (1 == nNumSlices), create a filling from the single + // front plane + const Slice3D& rSlice(rSliceVector[0]); + basegfx::B3DPolyPolygon aFront(rSlice.getB3DPolyPolygon()); + + if(bCreateTextureCoordinates) + { + aFront = basegfx::utils::applyDefaultTextureCoordinatesParallel(aFront, aTexRangeFront); + } + + if(bCreateNormals) + { + basegfx::B3DVector aNormal(0.0, 0.0, -1.0); + + if(aFront.count()) + { + aNormal = -aFront.getB3DPolygon(0).getNormal(); + } + + impSetNormal(aFront, aNormal); + } + + aFront.flip(); + rFill.push_back(aFront); + } + + if(bCreateTextureCoordinates) + { + aEdgeRounding.transformTextureCoordinates(rTexTransform); + } + + for(a = 0; a < aEdgeRounding.count(); a++) + { + rFill.emplace_back(aEdgeRounding.getB3DPolygon(a)); + } + } + + void createReducedOutlines( + const geometry::ViewInformation3D& rViewInformation, + const basegfx::B3DHomMatrix& rObjectTransform, + const basegfx::B3DPolygon& rLoopA, + const basegfx::B3DPolygon& rLoopB, + basegfx::B3DPolyPolygon& rTarget) + { + const sal_uInt32 nPointCount(rLoopA.count()); + + // with identical polygons there are no outlines + if(rLoopA == rLoopB) + return; + + if(!(nPointCount && nPointCount == rLoopB.count())) + return; + + const basegfx::B3DHomMatrix aObjectTransform(rViewInformation.getObjectToView() * rObjectTransform); + const basegfx::B2DPolygon a2DLoopA(basegfx::utils::createB2DPolygonFromB3DPolygon(rLoopA, aObjectTransform)); + const basegfx::B2DPolygon a2DLoopB(basegfx::utils::createB2DPolygonFromB3DPolygon(rLoopB, aObjectTransform)); + const basegfx::B2DPoint a2DCenterA(a2DLoopA.getB2DRange().getCenter()); + const basegfx::B2DPoint a2DCenterB(a2DLoopB.getB2DRange().getCenter()); + + // without detectable Y-Axis there are no outlines + if(a2DCenterA.equal(a2DCenterB)) + return; + + // search for outmost left and right inter-loop-edges which do not cut the loops + const basegfx::B2DPoint aCommonCenter(basegfx::average(a2DCenterA, a2DCenterB)); + const basegfx::B2DVector aAxisVector(a2DCenterA - a2DCenterB); + double fMaxLeft(0.0); + double fMaxRight(0.0); + sal_uInt32 nIndexLeft(0); + sal_uInt32 nIndexRight(0); + + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const basegfx::B2DPoint aStart(a2DLoopA.getB2DPoint(a)); + const basegfx::B2DPoint aEnd(a2DLoopB.getB2DPoint(a)); + const basegfx::B2DPoint aMiddle(basegfx::average(aStart, aEnd)); + + if(!basegfx::utils::isInside(a2DLoopA, aMiddle)) + { + if(!basegfx::utils::isInside(a2DLoopB, aMiddle)) + { + if(!impHasCutWith(a2DLoopA, aStart, aEnd)) + { + if(!impHasCutWith(a2DLoopB, aStart, aEnd)) + { + const basegfx::B2DVector aCandidateVector(aMiddle - aCommonCenter); + const double fCross(aCandidateVector.cross(aAxisVector)); + const double fDistance(aCandidateVector.getLength()); + + if(fCross > 0.0) + { + if(fDistance > fMaxLeft) + { + fMaxLeft = fDistance; + nIndexLeft = a; + } + } + else if(fCross < 0.0) + { + if(fDistance > fMaxRight) + { + fMaxRight = fDistance; + nIndexRight = a; + } + } + } + } + } + } + } + + if(fMaxLeft != 0.0) + { + basegfx::B3DPolygon aToBeAdded; + aToBeAdded.append(rLoopA.getB3DPoint(nIndexLeft)); + aToBeAdded.append(rLoopB.getB3DPoint(nIndexLeft)); + rTarget.append(aToBeAdded); + } + + if(fMaxRight != 0.0) + { + basegfx::B3DPolygon aToBeAdded; + aToBeAdded.append(rLoopA.getB3DPoint(nIndexRight)); + aToBeAdded.append(rLoopB.getB3DPoint(nIndexRight)); + rTarget.append(aToBeAdded); + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx new file mode 100644 index 000000000..919fbcf5a --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrextrudeprimitive3d.cxx @@ -0,0 +1,502 @@ +/* -*- 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 <cmath> + +#include <drawinglayer/primitive3d/sdrextrudeprimitive3d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <primitive3d/sdrdecompositiontools3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/geometry/viewinformation3d.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + Primitive3DContainer SdrExtrudePrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const + { + Primitive3DContainer aRetval; + + // get slices + const Slice3DVector& rSliceVector = getSlices(); + + if(!rSliceVector.empty()) + { + sal_uInt32 a; + + // decide what to create + const css::drawing::NormalsKind eNormalsKind(getSdr3DObjectAttribute().getNormalsKind()); + const bool bCreateNormals(css::drawing::NormalsKind_SPECIFIC == eNormalsKind); + const bool bCreateTextureCoordinatesX(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionX()); + const bool bCreateTextureCoordinatesY(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionY()); + basegfx::B2DHomMatrix aTexTransform; + + if(!getSdrLFSAttribute().getFill().isDefault() && (bCreateTextureCoordinatesX || bCreateTextureCoordinatesY)) + { + const basegfx::B2DPolygon aFirstPolygon(maCorrectedPolyPolygon.getB2DPolygon(0)); + const double fFrontLength(basegfx::utils::getLength(aFirstPolygon)); + const double fFrontArea(basegfx::utils::getArea(aFirstPolygon)); + const double fSqrtFrontArea(sqrt(fFrontArea)); + double fRelativeTextureWidth = basegfx::fTools::equalZero(fSqrtFrontArea) ? 1.0 : fFrontLength / fSqrtFrontArea; + fRelativeTextureWidth = std::trunc(fRelativeTextureWidth - 0.5); + + if(fRelativeTextureWidth < 1.0) + { + fRelativeTextureWidth = 1.0; + } + + aTexTransform.translate(-0.5, -0.5); + aTexTransform.scale(-1.0, -1.0); + aTexTransform.translate(0.5, 0.5); + aTexTransform.scale(fRelativeTextureWidth, 1.0); + } + + // create geometry + std::vector< basegfx::B3DPolyPolygon > aFill; + extractPlanesFromSlice(aFill, rSliceVector, + bCreateNormals, getSmoothNormals(), getSmoothLids(), false, + 0.5, 0.6, bCreateTextureCoordinatesX || bCreateTextureCoordinatesY, aTexTransform); + + // get full range + const basegfx::B3DRange aRange(getRangeFrom3DGeometry(aFill)); + + // normal creation + if(!getSdrLFSAttribute().getFill().isDefault()) + { + if(css::drawing::NormalsKind_SPHERE == eNormalsKind) + { + applyNormalsKindSphereTo3DGeometry(aFill, aRange); + } + else if(css::drawing::NormalsKind_FLAT == eNormalsKind) + { + applyNormalsKindFlatTo3DGeometry(aFill); + } + + if(getSdr3DObjectAttribute().getNormalsInvert()) + { + applyNormalsInvertTo3DGeometry(aFill); + } + } + + // texture coordinates + if(!getSdrLFSAttribute().getFill().isDefault()) + { + applyTextureTo3DGeometry( + getSdr3DObjectAttribute().getTextureProjectionX(), + getSdr3DObjectAttribute().getTextureProjectionY(), + aFill, + aRange, + getTextureSize()); + } + + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // add fill + aRetval = create3DPolyPolygonFillPrimitives( + aFill, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute(), + getSdrLFSAttribute().getFill(), + getSdrLFSAttribute().getFillFloatTransGradient()); + } + else + { + // create simplified 3d hit test geometry + aRetval = createHiddenGeometryPrimitives3D( + aFill, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute()); + } + + // add line + if(!getSdrLFSAttribute().getLine().isDefault()) + { + if(getSdr3DObjectAttribute().getReducedLineGeometry()) + { + // create geometric outlines with reduced line geometry for chart. + const basegfx::B3DPolyPolygon aVerLine(extractVerticalLinesFromSlice(rSliceVector)); + const sal_uInt32 nCount(aVerLine.count()); + basegfx::B3DPolyPolygon aReducedLoops; + basegfx::B3DPolyPolygon aNewLineGeometry; + + // sort out doubles (front and back planes when no edge rounding is done). Since + // this is a line geometry merged from PolyPolygons, loop over all Polygons + for(a = 0; a < nCount; a++) + { + const sal_uInt32 nReducedCount(aReducedLoops.count()); + const basegfx::B3DPolygon& aCandidate(aVerLine.getB3DPolygon(a)); + bool bAdd(true); + + if(nReducedCount) + { + for(sal_uInt32 b(0); bAdd && b < nReducedCount; b++) + { + if(aCandidate == aReducedLoops.getB3DPolygon(b)) + { + bAdd = false; + } + } + } + + if(bAdd) + { + aReducedLoops.append(aCandidate); + } + } + + // from here work with reduced loops and reduced count without changing them + const sal_uInt32 nReducedCount(aReducedLoops.count()); + + if(nReducedCount > 1) + { + for(sal_uInt32 b(1); b < nReducedCount; b++) + { + // get loop pair + const basegfx::B3DPolygon& aCandA(aReducedLoops.getB3DPolygon(b - 1)); + const basegfx::B3DPolygon& aCandB(aReducedLoops.getB3DPolygon(b)); + + // for each loop pair create the connection edges + createReducedOutlines( + rViewInformation, + getTransform(), + aCandA, + aCandB, + aNewLineGeometry); + } + } + + // add reduced loops themselves + aNewLineGeometry.append(aReducedLoops); + + // to create vertical edges at non-C1/C2 steady loops, use maCorrectedPolyPolygon + // directly since the 3D Polygons do not support this. + // + // Unfortunately there is no bezier polygon provided by the chart module; one reason is + // that the API for extrude wants a 3D polygon geometry (for historical reasons, i guess) + // and those have no beziers. Another reason is that he chart module uses self-created + // stuff to create the 2D geometry (in ShapeFactory::createPieSegment), but this geometry + // does not contain bezier infos, either. The only way which is possible for now is to 'detect' + // candidates for vertical edges of pie segments by looking for the angles in the polygon. + // + // This is all not very well designed ATM. Ideally, the ReducedLineGeometry is responsible + // for creating the outer geometry edges (createReducedOutlines), but for special edges + // like the vertical ones for pie center and both start/end, the incarnation with the + // knowledge about that it needs to create those and IS a pie segment -> in this case, + // the chart itself. + const sal_uInt32 nPolyCount(maCorrectedPolyPolygon.count()); + + for(sal_uInt32 c(0); c < nPolyCount; c++) + { + const basegfx::B2DPolygon aCandidate(maCorrectedPolyPolygon.getB2DPolygon(c)); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount > 2) + { + sal_uInt32 nIndexA(nPointCount); + sal_uInt32 nIndexB(nPointCount); + sal_uInt32 nIndexC(nPointCount); + + for(sal_uInt32 d(0); d < nPointCount; d++) + { + const sal_uInt32 nPrevInd((d + nPointCount - 1) % nPointCount); + const sal_uInt32 nNextInd((d + 1) % nPointCount); + const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(d)); + const basegfx::B2DVector aPrev(aCandidate.getB2DPoint(nPrevInd) - aPoint); + const basegfx::B2DVector aNext(aCandidate.getB2DPoint(nNextInd) - aPoint); + const double fAngle(aPrev.angle(aNext)); + + // take each angle which deviates more than 10% from going straight as + // special edge. This will detect the two outer edges of pie segments, + // but not always the center one (think about a near 180 degree pie) + if(M_PI - fabs(fAngle) > M_PI * 0.1) + { + if(nPointCount == nIndexA) + { + nIndexA = d; + } + else if(nPointCount == nIndexB) + { + nIndexB = d; + } + else if(nPointCount == nIndexC) + { + nIndexC = d; + d = nPointCount; + } + } + } + + const bool bIndexAUsed(nIndexA != nPointCount); + const bool bIndexBUsed(nIndexB != nPointCount); + bool bIndexCUsed(nIndexC != nPointCount); + + if(bIndexCUsed) + { + // already three special edges found, so the center one was already detected + // and does not need to be searched + } + else if(bIndexAUsed && bIndexBUsed) + { + // outer edges detected (they are approx. 90 degrees), but center one not. + // Look with the knowledge that it's in-between the two found ones + if(((nIndexA + 2) % nPointCount) == nIndexB) + { + nIndexC = (nIndexA + 1) % nPointCount; + } + else if(((nIndexA + nPointCount - 2) % nPointCount) == nIndexB) + { + nIndexC = (nIndexA + nPointCount - 1) % nPointCount; + } + + bIndexCUsed = (nIndexC != nPointCount); + } + + if(bIndexAUsed) + { + const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(nIndexA)); + const basegfx::B3DPoint aStart(aPoint.getX(), aPoint.getY(), 0.0); + const basegfx::B3DPoint aEnd(aPoint.getX(), aPoint.getY(), getDepth()); + basegfx::B3DPolygon aToBeAdded; + + aToBeAdded.append(aStart); + aToBeAdded.append(aEnd); + aNewLineGeometry.append(aToBeAdded); + } + + if(bIndexBUsed) + { + const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(nIndexB)); + const basegfx::B3DPoint aStart(aPoint.getX(), aPoint.getY(), 0.0); + const basegfx::B3DPoint aEnd(aPoint.getX(), aPoint.getY(), getDepth()); + basegfx::B3DPolygon aToBeAdded; + + aToBeAdded.append(aStart); + aToBeAdded.append(aEnd); + aNewLineGeometry.append(aToBeAdded); + } + + if(bIndexCUsed) + { + const basegfx::B2DPoint aPoint(aCandidate.getB2DPoint(nIndexC)); + const basegfx::B3DPoint aStart(aPoint.getX(), aPoint.getY(), 0.0); + const basegfx::B3DPoint aEnd(aPoint.getX(), aPoint.getY(), getDepth()); + basegfx::B3DPolygon aToBeAdded; + + aToBeAdded.append(aStart); + aToBeAdded.append(aEnd); + aNewLineGeometry.append(aToBeAdded); + } + } + } + + // append loops themselves + aNewLineGeometry.append(aReducedLoops); + + if(aNewLineGeometry.count()) + { + const Primitive3DContainer aLines(create3DPolyPolygonLinePrimitives( + aNewLineGeometry, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aLines); + } + } + else + { + // extract line geometry from slices + const basegfx::B3DPolyPolygon aHorLine(extractHorizontalLinesFromSlice(rSliceVector, false)); + const basegfx::B3DPolyPolygon aVerLine(extractVerticalLinesFromSlice(rSliceVector)); + + // add horizontal lines + const Primitive3DContainer aHorLines(create3DPolyPolygonLinePrimitives( + aHorLine, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aHorLines); + + // add vertical lines + const Primitive3DContainer aVerLines(create3DPolyPolygonLinePrimitives( + aVerLine, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aVerLines); + } + } + + // add shadow + if(!getSdrLFSAttribute().getShadow().isDefault() && !aRetval.empty()) + { + const Primitive3DContainer aShadow(createShadowPrimitive3D( + aRetval, getSdrLFSAttribute().getShadow(), getSdr3DObjectAttribute().getShadow3D())); + aRetval.append(aShadow); + } + } + + return aRetval; + } + + void SdrExtrudePrimitive3D::impCreateSlices() + { + // prepare the polygon. No double points, correct orientations and a correct + // outmost polygon are needed + // Also important: subdivide here to ensure equal point count for all slices (!) + maCorrectedPolyPolygon = basegfx::utils::adaptiveSubdivideByAngle(getPolyPolygon()); + maCorrectedPolyPolygon.removeDoublePoints(); + maCorrectedPolyPolygon = basegfx::utils::correctOrientations(maCorrectedPolyPolygon); + maCorrectedPolyPolygon = basegfx::utils::correctOutmostPolygon(maCorrectedPolyPolygon); + + // prepare slices as geometry + createExtrudeSlices(maSlices, maCorrectedPolyPolygon, getBackScale(), getDiagonal(), getDepth(), getCharacterMode(), getCloseFront(), getCloseBack()); + } + + const Slice3DVector& SdrExtrudePrimitive3D::getSlices() const + { + // This can be made dependent of getSdrLFSAttribute().getFill() and getSdrLFSAttribute().getLine() + // 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 ); + + const_cast< SdrExtrudePrimitive3D& >(*this).impCreateSlices(); + } + + return maSlices; + } + + SdrExtrudePrimitive3D::SdrExtrudePrimitive3D( + const basegfx::B3DHomMatrix& rTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fDepth, + double fDiagonal, + double fBackScale, + bool bSmoothNormals, + bool bSmoothLids, + bool bCharacterMode, + bool bCloseFront, + bool bCloseBack) + : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), + maPolyPolygon(rPolyPolygon), + mfDepth(fDepth), + mfDiagonal(fDiagonal), + mfBackScale(fBackScale), + mbSmoothNormals(bSmoothNormals), + mbSmoothLids(bSmoothLids), + mbCharacterMode(bCharacterMode), + mbCloseFront(bCloseFront), + mbCloseBack(bCloseBack) + { + // make sure depth is positive + if(basegfx::fTools::lessOrEqual(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)) + { + mfDiagonal = 0.0; + } + else if(basegfx::fTools::moreOrEqual(getDiagonal(), 1.0)) + { + mfDiagonal = 1.0; + } + + // no close front/back when polygon is not closed + if(getPolyPolygon().count() && !getPolyPolygon().getB2DPolygon(0).isClosed()) + { + mbCloseFront = mbCloseBack = false; + } + + // no edge rounding when not closing + if(!getCloseFront() && !getCloseBack()) + { + mfDiagonal = 0.0; + } + } + + SdrExtrudePrimitive3D::~SdrExtrudePrimitive3D() + { + } + + bool SdrExtrudePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(SdrPrimitive3D::operator==(rPrimitive)) + { + const SdrExtrudePrimitive3D& rCompare = static_cast< const SdrExtrudePrimitive3D& >(rPrimitive); + + return (getPolyPolygon() == rCompare.getPolyPolygon() + && getDepth() == rCompare.getDepth() + && getDiagonal() == rCompare.getDiagonal() + && getBackScale() == rCompare.getBackScale() + && getSmoothNormals() == rCompare.getSmoothNormals() + && getSmoothLids() == rCompare.getSmoothLids() + && getCharacterMode() == rCompare.getCharacterMode() + && getCloseFront() == rCompare.getCloseFront() + && getCloseBack() == rCompare.getCloseBack()); + } + + return false; + } + + basegfx::B3DRange SdrExtrudePrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + // use default from sdrPrimitive3D which uses transformation expanded by line width/2 + // The parent implementation which uses the ranges of the decomposition would be more + // correct, but for historical reasons it is necessary to do the old method: To get + // the range of the non-transformed geometry and transform it then. This leads to different + // ranges where the new method is more correct, but the need to keep the old behaviour + // has priority here. + return get3DRangeFromSlices(getSlices()); + } + + Primitive3DContainer SdrExtrudePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const + { + if(getSdr3DObjectAttribute().getReducedLineGeometry()) + { + if(!mpLastRLGViewInformation || + (!getBuffered3DDecomposition().empty() + && *mpLastRLGViewInformation != rViewInformation)) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // conditions of last local decomposition with reduced lines have changed. Remember + // new one and clear current decompositiopn + SdrExtrudePrimitive3D* pThat = const_cast< SdrExtrudePrimitive3D* >(this); + pThat->setBuffered3DDecomposition(Primitive3DContainer()); + pThat->mpLastRLGViewInformation = rViewInformation; + } + } + + // no test for buffering needed, call parent + return SdrPrimitive3D::get3DDecomposition(rViewInformation); + } + + // provide unique ID + ImplPrimitive3DIDBlock(SdrExtrudePrimitive3D, PRIMITIVE3D_ID_SDREXTRUDEPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx new file mode 100644 index 000000000..2115ebc3a --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrlatheprimitive3d.cxx @@ -0,0 +1,360 @@ +/* -*- 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/primitive3d/sdrlatheprimitive3d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <primitive3d/sdrdecompositiontools3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/geometry/viewinformation3d.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + Primitive3DContainer SdrLathePrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const + { + Primitive3DContainer aRetval; + + // get slices + const Slice3DVector& rSliceVector = getSlices(); + + if(!rSliceVector.empty()) + { + const bool bBackScale(!basegfx::fTools::equal(getBackScale(), 1.0)); + const bool bClosedRotation(!bBackScale && getHorizontalSegments() && basegfx::fTools::equal(getRotation(), 2 * M_PI)); + sal_uInt32 a; + + // decide what to create + const css::drawing::NormalsKind eNormalsKind(getSdr3DObjectAttribute().getNormalsKind()); + const bool bCreateNormals(css::drawing::NormalsKind_SPECIFIC == eNormalsKind); + const bool bCreateTextureCoordinatesX(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionX()); + const bool bCreateTextureCoordinatesY(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionY()); + basegfx::B2DHomMatrix aTexTransform; + + if(!getSdrLFSAttribute().getFill().isDefault() + && (bCreateTextureCoordinatesX || bCreateTextureCoordinatesY)) + { + aTexTransform.set(0, 0, 0.0); + aTexTransform.set(0, 1, 1.0); + aTexTransform.set(1, 0, 1.0); + aTexTransform.set(1, 1, 0.0); + + aTexTransform.translate(0.0, -0.5); + aTexTransform.scale(1.0, -1.0); + aTexTransform.translate(0.0, 0.5); + } + + // create geometry + std::vector< basegfx::B3DPolyPolygon > aFill; + extractPlanesFromSlice(aFill, rSliceVector, + bCreateNormals, getSmoothNormals(), getSmoothLids(), bClosedRotation, + 0.85, 0.6, bCreateTextureCoordinatesX || bCreateTextureCoordinatesY, aTexTransform); + + // get full range + const basegfx::B3DRange aRange(getRangeFrom3DGeometry(aFill)); + + // normal creation + if(!getSdrLFSAttribute().getFill().isDefault()) + { + if(css::drawing::NormalsKind_SPHERE == eNormalsKind) + { + applyNormalsKindSphereTo3DGeometry(aFill, aRange); + } + else if(css::drawing::NormalsKind_FLAT == eNormalsKind) + { + applyNormalsKindFlatTo3DGeometry(aFill); + } + + if(getSdr3DObjectAttribute().getNormalsInvert()) + { + applyNormalsInvertTo3DGeometry(aFill); + } + } + + // texture coordinates + if(!getSdrLFSAttribute().getFill().isDefault()) + { + applyTextureTo3DGeometry( + getSdr3DObjectAttribute().getTextureProjectionX(), + getSdr3DObjectAttribute().getTextureProjectionY(), + aFill, + aRange, + getTextureSize()); + } + + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // add fill + aRetval = create3DPolyPolygonFillPrimitives( + aFill, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute(), + getSdrLFSAttribute().getFill(), + getSdrLFSAttribute().getFillFloatTransGradient()); + } + else + { + // create simplified 3d hit test geometry + aRetval = createHiddenGeometryPrimitives3D( + aFill, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute()); + } + + // add line + if(!getSdrLFSAttribute().getLine().isDefault()) + { + if(getSdr3DObjectAttribute().getReducedLineGeometry()) + { + // create geometric outlines with reduced line geometry for chart + const basegfx::B3DPolyPolygon aHorLine(extractHorizontalLinesFromSlice(rSliceVector, bClosedRotation)); + const sal_uInt32 nCount(aHorLine.count()); + basegfx::B3DPolyPolygon aNewLineGeometry; + + for(a = 1; a < nCount; a++) + { + // for each loop pair create the connection edges + createReducedOutlines( + rViewInformation, + getTransform(), + aHorLine.getB3DPolygon(a - 1), + aHorLine.getB3DPolygon(a), + aNewLineGeometry); + } + + for(a = 0; a < nCount; a++) + { + // filter hor lines for empty loops (those who have their defining point on the Y-Axis) + basegfx::B3DPolygon aCandidate(aHorLine.getB3DPolygon(a)); + aCandidate.removeDoublePoints(); + + if(aCandidate.count()) + { + aNewLineGeometry.append(aCandidate); + } + } + + if(aNewLineGeometry.count()) + { + const Primitive3DContainer aLines(create3DPolyPolygonLinePrimitives( + aNewLineGeometry, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aLines); + } + } + else + { + // extract line geometry from slices + const basegfx::B3DPolyPolygon aHorLine(extractHorizontalLinesFromSlice(rSliceVector, bClosedRotation)); + const basegfx::B3DPolyPolygon aVerLine(extractVerticalLinesFromSlice(rSliceVector)); + + // add horizontal lines + const Primitive3DContainer aHorLines(create3DPolyPolygonLinePrimitives( + aHorLine, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aHorLines); + + // add vertical lines + const Primitive3DContainer aVerLines(create3DPolyPolygonLinePrimitives( + aVerLine, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aVerLines); + } + } + + // add shadow + if(!getSdrLFSAttribute().getShadow().isDefault() + && !aRetval.empty()) + { + const Primitive3DContainer aShadow(createShadowPrimitive3D( + aRetval, getSdrLFSAttribute().getShadow(), getSdr3DObjectAttribute().getShadow3D())); + aRetval.append(aShadow); + } + } + + return aRetval; + } + + void SdrLathePrimitive3D::impCreateSlices() + { + // prepare the polygon. No double points, correct orientations and a correct + // outmost polygon are needed + // Also important: subdivide here to ensure equal point count for all slices (!) + maCorrectedPolyPolygon = basegfx::utils::adaptiveSubdivideByAngle(getPolyPolygon()); + maCorrectedPolyPolygon.removeDoublePoints(); + maCorrectedPolyPolygon = basegfx::utils::correctOrientations(maCorrectedPolyPolygon); + maCorrectedPolyPolygon = basegfx::utils::correctOutmostPolygon(maCorrectedPolyPolygon); + + // check edge count of first sub-polygon. If different, reSegment polyPolygon. This ensures + // that for polyPolygons, the subPolys 1..n only get reSegmented when polygon 0 is different + // at all (and not always) + const basegfx::B2DPolygon aSubCandidate(maCorrectedPolyPolygon.getB2DPolygon(0)); + const sal_uInt32 nSubEdgeCount(aSubCandidate.isClosed() ? aSubCandidate.count() : (aSubCandidate.count() ? aSubCandidate.count() - 1 : 0)); + + if(nSubEdgeCount != getVerticalSegments()) + { + maCorrectedPolyPolygon = basegfx::utils::reSegmentPolyPolygon(maCorrectedPolyPolygon, getVerticalSegments()); + } + + // prepare slices as geometry + createLatheSlices(maSlices, maCorrectedPolyPolygon, getBackScale(), getDiagonal(), getRotation(), getHorizontalSegments(), getCharacterMode(), getCloseFront(), getCloseBack()); + } + + const Slice3DVector& SdrLathePrimitive3D::getSlices() const + { + // This can be made dependent of getSdrLFSAttribute().getFill() and getSdrLFSAttribute().getLine() + // 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 ); + + const_cast< SdrLathePrimitive3D& >(*this).impCreateSlices(); + } + + return maSlices; + } + + SdrLathePrimitive3D::SdrLathePrimitive3D( + const basegfx::B3DHomMatrix& rTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute, + const basegfx::B2DPolyPolygon& rPolyPolygon, + sal_uInt32 nHorizontalSegments, + sal_uInt32 nVerticalSegments, + double fDiagonal, + double fBackScale, + double fRotation, + bool bSmoothNormals, + bool bSmoothLids, + bool bCharacterMode, + bool bCloseFront, + bool bCloseBack) + : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), + maPolyPolygon(rPolyPolygon), + mnHorizontalSegments(nHorizontalSegments), + mnVerticalSegments(nVerticalSegments), + mfDiagonal(fDiagonal), + mfBackScale(fBackScale), + mfRotation(fRotation), + mbSmoothNormals(bSmoothNormals), + mbSmoothLids(bSmoothLids), + mbCharacterMode(bCharacterMode), + mbCloseFront(bCloseFront), + mbCloseBack(bCloseBack) + { + // make sure Rotation is positive + if(basegfx::fTools::lessOrEqual(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)) + { + mfDiagonal = 0.0; + } + else if(basegfx::fTools::moreOrEqual(getDiagonal(), 1.0)) + { + mfDiagonal = 1.0; + } + + // no close front/back when polygon is not closed + if(getPolyPolygon().count() && !getPolyPolygon().getB2DPolygon(0).isClosed()) + { + mbCloseFront = mbCloseBack = false; + } + + // no edge rounding when not closing + if(!getCloseFront() && !getCloseBack()) + { + mfDiagonal = 0.0; + } + } + + SdrLathePrimitive3D::~SdrLathePrimitive3D() + { + } + + bool SdrLathePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(SdrPrimitive3D::operator==(rPrimitive)) + { + const SdrLathePrimitive3D& rCompare = static_cast< const SdrLathePrimitive3D& >(rPrimitive); + + return (getPolyPolygon() == rCompare.getPolyPolygon() + && getHorizontalSegments() == rCompare.getHorizontalSegments() + && getVerticalSegments() == rCompare.getVerticalSegments() + && getDiagonal() == rCompare.getDiagonal() + && getBackScale() == rCompare.getBackScale() + && getRotation() == rCompare.getRotation() + && getSmoothNormals() == rCompare.getSmoothNormals() + && getSmoothLids() == rCompare.getSmoothLids() + && getCharacterMode() == rCompare.getCharacterMode() + && getCloseFront() == rCompare.getCloseFront() + && getCloseBack() == rCompare.getCloseBack()); + } + + return false; + } + + basegfx::B3DRange SdrLathePrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + // use default from sdrPrimitive3D which uses transformation expanded by line width/2 + // The parent implementation which uses the ranges of the decomposition would be more + // correct, but for historical reasons it is necessary to do the old method: To get + // the range of the non-transformed geometry and transform it then. This leads to different + // ranges where the new method is more correct, but the need to keep the old behaviour + // has priority here. + return get3DRangeFromSlices(getSlices()); + } + + Primitive3DContainer SdrLathePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& rViewInformation) const + { + if(getSdr3DObjectAttribute().getReducedLineGeometry()) + { + if(!mpLastRLGViewInformation || + (!getBuffered3DDecomposition().empty() + && *mpLastRLGViewInformation != rViewInformation)) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // conditions of last local decomposition with reduced lines have changed. Remember + // new one and clear current decompositiopn + SdrLathePrimitive3D* pThat = const_cast< SdrLathePrimitive3D* >(this); + pThat->setBuffered3DDecomposition(Primitive3DContainer()); + pThat->mpLastRLGViewInformation = rViewInformation; + } + } + + // no test for buffering needed, call parent + return SdrPrimitive3D::get3DDecomposition(rViewInformation); + } + + // provide unique ID + ImplPrimitive3DIDBlock(SdrLathePrimitive3D, PRIMITIVE3D_ID_SDRLATHEPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx new file mode 100644 index 000000000..010083b42 --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrpolypolygonprimitive3d.cxx @@ -0,0 +1,176 @@ +/* -*- 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/primitive3d/sdrpolypolygonprimitive3d.hxx> +#include <primitive3d/sdrdecompositiontools3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + Primitive3DContainer SdrPolyPolygonPrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + Primitive3DContainer aRetval; + + if(getPolyPolygon3D().count()) + { + std::vector< basegfx::B3DPolyPolygon > aFill { getPolyPolygon3D() }; + + // get full range + const basegfx::B3DRange aRange(getRangeFrom3DGeometry(aFill)); + + // #i98295# normal creation + if(!getSdrLFSAttribute().getFill().isDefault()) + { + if(css::drawing::NormalsKind_SPHERE == getSdr3DObjectAttribute().getNormalsKind()) + { + applyNormalsKindSphereTo3DGeometry(aFill, aRange); + } + else if(css::drawing::NormalsKind_FLAT == getSdr3DObjectAttribute().getNormalsKind()) + { + applyNormalsKindFlatTo3DGeometry(aFill); + } + + if(getSdr3DObjectAttribute().getNormalsInvert()) + { + applyNormalsInvertTo3DGeometry(aFill); + } + } + + // #i98314# texture coordinates + if(!getSdrLFSAttribute().getFill().isDefault()) + { + applyTextureTo3DGeometry( + getSdr3DObjectAttribute().getTextureProjectionX(), + getSdr3DObjectAttribute().getTextureProjectionY(), + aFill, + aRange, + getTextureSize()); + } + + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // add fill + aRetval = create3DPolyPolygonFillPrimitives( + aFill, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute(), + getSdrLFSAttribute().getFill(), + getSdrLFSAttribute().getFillFloatTransGradient()); + } + else + { + // create simplified 3d hit test geometry + aRetval = createHiddenGeometryPrimitives3D( + aFill, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute()); + } + + // add line + if(!getSdrLFSAttribute().getLine().isDefault()) + { + basegfx::B3DPolyPolygon aLine(getPolyPolygon3D()); + aLine.clearNormals(); + aLine.clearTextureCoordinates(); + const Primitive3DContainer aLines(create3DPolyPolygonLinePrimitives( + aLine, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aLines); + } + + // add shadow + if(!getSdrLFSAttribute().getShadow().isDefault() + && !aRetval.empty()) + { + const Primitive3DContainer aShadow(createShadowPrimitive3D( + aRetval, getSdrLFSAttribute().getShadow(), getSdr3DObjectAttribute().getShadow3D())); + aRetval.append(aShadow); + } + } + + return aRetval; + } + + SdrPolyPolygonPrimitive3D::SdrPolyPolygonPrimitive3D( + const basegfx::B3DPolyPolygon& rPolyPolygon3D, + const basegfx::B3DHomMatrix& rTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute) + : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), + maPolyPolygon3D(rPolyPolygon3D) + { + } + + bool SdrPolyPolygonPrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(SdrPrimitive3D::operator==(rPrimitive)) + { + const SdrPolyPolygonPrimitive3D& rCompare = static_cast< const SdrPolyPolygonPrimitive3D& >(rPrimitive); + + return (getPolyPolygon3D() == rCompare.getPolyPolygon3D()); + } + + return false; + } + + basegfx::B3DRange SdrPolyPolygonPrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + // added this implementation to make sure that non-visible objects of this + // kind will deliver their expansion. If not implemented, it would never deliver + // the used space for non-visible objects since the decomposition for that + // case will be empty (what is correct). To support chart ATM which relies on + // non-visible objects occupying space in 3D, this method was added + basegfx::B3DRange aRetval; + + if(getPolyPolygon3D().count()) + { + aRetval = basegfx::utils::getRange(getPolyPolygon3D()); + aRetval.transform(getTransform()); + + if(!getSdrLFSAttribute().getLine().isDefault()) + { + const attribute::SdrLineAttribute& rLine = getSdrLFSAttribute().getLine(); + + if(!rLine.isDefault() && !basegfx::fTools::equalZero(rLine.getWidth())) + { + // expand by half LineWidth as tube radius + aRetval.grow(rLine.getWidth() / 2.0); + } + } + } + + return aRetval; + } + + // provide unique ID + ImplPrimitive3DIDBlock(SdrPolyPolygonPrimitive3D, PRIMITIVE3D_ID_SDRPOLYPOLYGONPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrprimitive3d.cxx new file mode 100644 index 000000000..54fd2cf4b --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrprimitive3d.cxx @@ -0,0 +1,106 @@ +/* -*- 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/primitive3d/sdrprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + basegfx::B3DRange SdrPrimitive3D::getStandard3DRange() const + { + basegfx::B3DRange aUnitRange(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); + aUnitRange.transform(getTransform()); + + if(!getSdrLFSAttribute().getLine().isDefault()) + { + const attribute::SdrLineAttribute& rLine = getSdrLFSAttribute().getLine(); + + if(!rLine.isDefault() && !basegfx::fTools::equalZero(rLine.getWidth())) + { + // expand by held LineWidth as tube radius + aUnitRange.grow(rLine.getWidth() / 2.0); + } + } + + return aUnitRange; + } + + basegfx::B3DRange SdrPrimitive3D::get3DRangeFromSlices(const Slice3DVector& rSlices) const + { + basegfx::B3DRange aRetval; + + if(!rSlices.empty()) + { + for(const auto & rSlice : rSlices) + { + aRetval.expand(basegfx::utils::getRange(rSlice.getB3DPolyPolygon())); + } + + aRetval.transform(getTransform()); + + if(!getSdrLFSAttribute().getLine().isDefault()) + { + const attribute::SdrLineAttribute& rLine = getSdrLFSAttribute().getLine(); + + if(!rLine.isDefault() && !basegfx::fTools::equalZero(rLine.getWidth())) + { + // expand by half LineWidth as tube radius + aRetval.grow(rLine.getWidth() / 2.0); + } + } + } + + return aRetval; + } + + SdrPrimitive3D::SdrPrimitive3D( + const basegfx::B3DHomMatrix& rTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute) + : maTransform(rTransform), + maTextureSize(rTextureSize), + maSdrLFSAttribute(rSdrLFSAttribute), + maSdr3DObjectAttribute(rSdr3DObjectAttribute) + { + } + + bool SdrPrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(BufferedDecompositionPrimitive3D::operator==(rPrimitive)) + { + const SdrPrimitive3D& rCompare = static_cast< const SdrPrimitive3D& >(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getTextureSize() == rCompare.getTextureSize() + && getSdrLFSAttribute() == rCompare.getSdrLFSAttribute() + && getSdr3DObjectAttribute() == rCompare.getSdr3DObjectAttribute()); + } + + return false; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/sdrsphereprimitive3d.cxx b/drawinglayer/source/primitive3d/sdrsphereprimitive3d.cxx new file mode 100644 index 000000000..c3127261f --- /dev/null +++ b/drawinglayer/source/primitive3d/sdrsphereprimitive3d.cxx @@ -0,0 +1,205 @@ +/* -*- 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/primitive3d/sdrsphereprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <primitive3d/sdrdecompositiontools3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + Primitive3DContainer SdrSpherePrimitive3D::create3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + Primitive3DContainer aRetval; + const basegfx::B3DRange aUnitRange(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); + const bool bCreateNormals(css::drawing::NormalsKind_SPECIFIC == getSdr3DObjectAttribute().getNormalsKind() + || css::drawing::NormalsKind_SPHERE == getSdr3DObjectAttribute().getNormalsKind()); + + // create unit geometry + basegfx::B3DPolyPolygon aFill(basegfx::utils::createSphereFillPolyPolygonFromB3DRange(aUnitRange, + getHorizontalSegments(), getVerticalSegments(), bCreateNormals)); + + // normal inversion + if(!getSdrLFSAttribute().getFill().isDefault() + && bCreateNormals + && getSdr3DObjectAttribute().getNormalsInvert() + && aFill.areNormalsUsed()) + { + // invert normals + aFill = basegfx::utils::invertNormals(aFill); + } + + // texture coordinates + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // handle texture coordinates X + const bool bParallelX(css::drawing::TextureProjectionMode_PARALLEL == getSdr3DObjectAttribute().getTextureProjectionX()); + const bool bObjectSpecificX(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionX()); + const bool bSphereX(css::drawing::TextureProjectionMode_SPHERE == getSdr3DObjectAttribute().getTextureProjectionX()); + + // handle texture coordinates Y + const bool bParallelY(css::drawing::TextureProjectionMode_PARALLEL == getSdr3DObjectAttribute().getTextureProjectionY()); + const bool bObjectSpecificY(css::drawing::TextureProjectionMode_OBJECTSPECIFIC == getSdr3DObjectAttribute().getTextureProjectionY()); + const bool bSphereY(css::drawing::TextureProjectionMode_SPHERE == getSdr3DObjectAttribute().getTextureProjectionY()); + + if(bParallelX || bParallelY) + { + // apply parallel texture coordinates in X and/or Y + const basegfx::B3DRange aRange(basegfx::utils::getRange(aFill)); + aFill = basegfx::utils::applyDefaultTextureCoordinatesParallel(aFill, aRange, bParallelX, bParallelY); + } + + if(bSphereX || bObjectSpecificX || bSphereY || bObjectSpecificY) + { + double fRelativeAngle(0.0); + + if(bObjectSpecificX) + { + // Since the texture coordinates are (for historical reasons) + // different from forced to sphere texture coordinates, + // create a old version from it by rotating to old state before applying + // the texture coordinates to emulate old behaviour + fRelativeAngle = 2 * M_PI * (static_cast<double>((getHorizontalSegments() >> 1) - 1) / static_cast<double>(getHorizontalSegments())); + basegfx::B3DHomMatrix aRot; + aRot.rotate(0.0, fRelativeAngle, 0.0); + aFill.transform(aRot); + } + + // apply spherical texture coordinates in X and/or Y + const basegfx::B3DRange aRange(basegfx::utils::getRange(aFill)); + const basegfx::B3DPoint aCenter(aRange.getCenter()); + aFill = basegfx::utils::applyDefaultTextureCoordinatesSphere(aFill, aCenter, + bSphereX || bObjectSpecificX, bSphereY || bObjectSpecificY); + + if(bObjectSpecificX) + { + // rotate back again + basegfx::B3DHomMatrix aRot; + aRot.rotate(0.0, -fRelativeAngle, 0.0); + aFill.transform(aRot); + } + } + + // transform texture coordinates to texture size + basegfx::B2DHomMatrix aTexMatrix; + aTexMatrix.scale(getTextureSize().getX(), getTextureSize().getY()); + aFill.transformTextureCoordinates(aTexMatrix); + } + + // build vector of PolyPolygons + std::vector< basegfx::B3DPolyPolygon > a3DPolyPolygonVector; + + for(sal_uInt32 a(0); a < aFill.count(); a++) + { + a3DPolyPolygonVector.emplace_back(aFill.getB3DPolygon(a)); + } + + if(!getSdrLFSAttribute().getFill().isDefault()) + { + // add fill + aRetval = create3DPolyPolygonFillPrimitives( + a3DPolyPolygonVector, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute(), + getSdrLFSAttribute().getFill(), + getSdrLFSAttribute().getFillFloatTransGradient()); + } + else + { + // create simplified 3d hit test geometry + aRetval = createHiddenGeometryPrimitives3D( + a3DPolyPolygonVector, + getTransform(), + getTextureSize(), + getSdr3DObjectAttribute()); + } + + // add line + if(!getSdrLFSAttribute().getLine().isDefault()) + { + basegfx::B3DPolyPolygon aSphere(basegfx::utils::createSpherePolyPolygonFromB3DRange(aUnitRange, getHorizontalSegments(), getVerticalSegments())); + const Primitive3DContainer aLines(create3DPolyPolygonLinePrimitives( + aSphere, getTransform(), getSdrLFSAttribute().getLine())); + aRetval.append(aLines); + } + + // add shadow + if(!getSdrLFSAttribute().getShadow().isDefault() + && !aRetval.empty()) + { + const Primitive3DContainer aShadow(createShadowPrimitive3D( + aRetval, getSdrLFSAttribute().getShadow(), getSdr3DObjectAttribute().getShadow3D())); + aRetval.append(aShadow); + } + + return aRetval; + } + + SdrSpherePrimitive3D::SdrSpherePrimitive3D( + const basegfx::B3DHomMatrix& rTransform, + const basegfx::B2DVector& rTextureSize, + const attribute::SdrLineFillShadowAttribute3D& rSdrLFSAttribute, + const attribute::Sdr3DObjectAttribute& rSdr3DObjectAttribute, + sal_uInt32 nHorizontalSegments, + sal_uInt32 nVerticalSegments) + : SdrPrimitive3D(rTransform, rTextureSize, rSdrLFSAttribute, rSdr3DObjectAttribute), + mnHorizontalSegments(nHorizontalSegments), + mnVerticalSegments(nVerticalSegments) + { + } + + bool SdrSpherePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(SdrPrimitive3D::operator==(rPrimitive)) + { + const SdrSpherePrimitive3D& rCompare = static_cast< const SdrSpherePrimitive3D& >(rPrimitive); + + return (getHorizontalSegments() == rCompare.getHorizontalSegments() + && getVerticalSegments() == rCompare.getVerticalSegments()); + } + + return false; + } + + basegfx::B3DRange SdrSpherePrimitive3D::getB3DRange(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + // use default from sdrPrimitive3D which uses transformation expanded by line width/2 + // The parent implementation which uses the ranges of the decomposition would be more + // correct, but for historical reasons it is necessary to do the old method: To get + // the range of the non-transformed geometry and transform it then. This leads to different + // ranges where the new method is more correct, but the need to keep the old behaviour + // has priority here. + return getStandard3DRange(); + } + + // provide unique ID + ImplPrimitive3DIDBlock(SdrSpherePrimitive3D, PRIMITIVE3D_ID_SDRSPHEREPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/shadowprimitive3d.cxx b/drawinglayer/source/primitive3d/shadowprimitive3d.cxx new file mode 100644 index 000000000..cca2e3c6f --- /dev/null +++ b/drawinglayer/source/primitive3d/shadowprimitive3d.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <primitive3d/shadowprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + ShadowPrimitive3D::ShadowPrimitive3D( + const basegfx::B2DHomMatrix& rShadowTransform, + const basegfx::BColor& rShadowColor, + double fShadowTransparence, + bool bShadow3D, + const Primitive3DContainer& rChildren) + : GroupPrimitive3D(rChildren), + maShadowTransform(rShadowTransform), + maShadowColor(rShadowColor), + mfShadowTransparence(fShadowTransparence), + mbShadow3D(bShadow3D) + { + } + + bool ShadowPrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(GroupPrimitive3D::operator==(rPrimitive)) + { + const ShadowPrimitive3D& rCompare = static_cast<const ShadowPrimitive3D&>(rPrimitive); + + return (getShadowTransform() == rCompare.getShadowTransform() + && getShadowColor() == rCompare.getShadowColor() + && getShadowTransparence() == rCompare.getShadowTransparence() + && getShadow3D() == rCompare.getShadow3D()); + } + + return false; + } + + // provide unique ID + ImplPrimitive3DIDBlock(ShadowPrimitive3D, PRIMITIVE3D_ID_SHADOWPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/textureprimitive3d.cxx b/drawinglayer/source/primitive3d/textureprimitive3d.cxx new file mode 100644 index 000000000..7c9226bf4 --- /dev/null +++ b/drawinglayer/source/primitive3d/textureprimitive3d.cxx @@ -0,0 +1,183 @@ +/* -*- 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 <primitive3d/textureprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <basegfx/color/bcolor.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + TexturePrimitive3D::TexturePrimitive3D( + const Primitive3DContainer& rChildren, + const basegfx::B2DVector& rTextureSize, + bool bModulate, bool bFilter) + : GroupPrimitive3D(rChildren), + maTextureSize(rTextureSize), + mbModulate(bModulate), + mbFilter(bFilter) + { + } + + bool TexturePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(GroupPrimitive3D::operator==(rPrimitive)) + { + const TexturePrimitive3D& rCompare = static_cast<const TexturePrimitive3D&>(rPrimitive); + + return (getModulate() == rCompare.getModulate() + && getFilter() == rCompare.getFilter()); + } + + return false; + } + + + + UnifiedTransparenceTexturePrimitive3D::UnifiedTransparenceTexturePrimitive3D( + double fTransparence, + const Primitive3DContainer& rChildren) + : TexturePrimitive3D(rChildren, basegfx::B2DVector(), false, false), + mfTransparence(fTransparence) + { + } + + bool UnifiedTransparenceTexturePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(TexturePrimitive3D::operator==(rPrimitive)) + { + const UnifiedTransparenceTexturePrimitive3D& rCompare = static_cast<const UnifiedTransparenceTexturePrimitive3D&>(rPrimitive); + + return (getTransparence() == rCompare.getTransparence()); + } + + return false; + } + + basegfx::B3DRange UnifiedTransparenceTexturePrimitive3D::getB3DRange(const geometry::ViewInformation3D& rViewInformation) const + { + // do not use the fallback to decomposition here since for a correct BoundRect we also + // need invisible (1.0 == getTransparence()) geometry; these would be deleted in the decomposition + return getChildren().getB3DRange(rViewInformation); + } + + Primitive3DContainer UnifiedTransparenceTexturePrimitive3D::get3DDecomposition(const geometry::ViewInformation3D& /*rViewInformation*/) const + { + if(0.0 == getTransparence()) + { + // no transparence used, so just use content + return getChildren(); + } + else if(getTransparence() > 0.0 && getTransparence() < 1.0) + { + // 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); + const Primitive3DReference xRef(new TransparenceTexturePrimitive3D(aFillGradient, getChildren(), getTextureSize())); + return { xRef }; + } + else + { + // completely transparent or invalid definition, add nothing + return Primitive3DContainer(); + } + } + + // provide unique ID + ImplPrimitive3DIDBlock(UnifiedTransparenceTexturePrimitive3D, PRIMITIVE3D_ID_UNIFIEDTRANSPARENCETEXTUREPRIMITIVE3D) + + + + GradientTexturePrimitive3D::GradientTexturePrimitive3D( + const attribute::FillGradientAttribute& rGradient, + const Primitive3DContainer& rChildren, + const basegfx::B2DVector& rTextureSize, + bool bModulate, + bool bFilter) + : TexturePrimitive3D(rChildren, rTextureSize, bModulate, bFilter), + maGradient(rGradient) + { + } + + bool GradientTexturePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(TexturePrimitive3D::operator==(rPrimitive)) + { + const GradientTexturePrimitive3D& rCompare = static_cast<const GradientTexturePrimitive3D&>(rPrimitive); + + return (getGradient() == rCompare.getGradient()); + } + + return false; + } + + // provide unique ID + ImplPrimitive3DIDBlock(GradientTexturePrimitive3D, PRIMITIVE3D_ID_GRADIENTTEXTUREPRIMITIVE3D) + + + + BitmapTexturePrimitive3D::BitmapTexturePrimitive3D( + const attribute::FillGraphicAttribute& rFillGraphicAttribute, + const Primitive3DContainer& rChildren, + const basegfx::B2DVector& rTextureSize, + bool bModulate, bool bFilter) + : TexturePrimitive3D(rChildren, rTextureSize, bModulate, bFilter), + maFillGraphicAttribute(rFillGraphicAttribute) + { + } + + bool BitmapTexturePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(TexturePrimitive3D::operator==(rPrimitive)) + { + const BitmapTexturePrimitive3D& rCompare = static_cast<const BitmapTexturePrimitive3D&>(rPrimitive); + + return (getFillGraphicAttribute() == rCompare.getFillGraphicAttribute()); + } + + return false; + } + + // provide unique ID + ImplPrimitive3DIDBlock(BitmapTexturePrimitive3D, PRIMITIVE3D_ID_BITMAPTEXTUREPRIMITIVE3D) + + + + TransparenceTexturePrimitive3D::TransparenceTexturePrimitive3D( + const attribute::FillGradientAttribute& rGradient, + const Primitive3DContainer& rChildren, + const basegfx::B2DVector& rTextureSize) + : GradientTexturePrimitive3D(rGradient, rChildren, rTextureSize, false, false) + { + } + + bool TransparenceTexturePrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + return GradientTexturePrimitive3D::operator==(rPrimitive); + } + + // provide unique ID + ImplPrimitive3DIDBlock(TransparenceTexturePrimitive3D, PRIMITIVE3D_ID_TRANSPARENCETEXTUREPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive3d/transformprimitive3d.cxx b/drawinglayer/source/primitive3d/transformprimitive3d.cxx new file mode 100644 index 000000000..1ddb919bf --- /dev/null +++ b/drawinglayer/source/primitive3d/transformprimitive3d.cxx @@ -0,0 +1,61 @@ +/* -*- 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/primitive3d/transformprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive3d +{ + TransformPrimitive3D::TransformPrimitive3D( + const basegfx::B3DHomMatrix& rTransformation, + const Primitive3DContainer& rChildren) + : GroupPrimitive3D(rChildren), + maTransformation(rTransformation) + { + } + + bool TransformPrimitive3D::operator==(const BasePrimitive3D& rPrimitive) const + { + if(GroupPrimitive3D::operator==(rPrimitive)) + { + const TransformPrimitive3D& rCompare = static_cast< const TransformPrimitive3D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation()); + } + + return false; + } + + basegfx::B3DRange TransformPrimitive3D::getB3DRange(const geometry::ViewInformation3D& rViewInformation) const + { + basegfx::B3DRange aRetval(getChildren().getB3DRange(rViewInformation)); + aRetval.transform(getTransformation()); + return aRetval; + } + + // provide unique ID + ImplPrimitive3DIDBlock(TransformPrimitive3D, PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/baseprocessor2d.cxx b/drawinglayer/source/processor2d/baseprocessor2d.cxx new file mode 100644 index 000000000..211569f5f --- /dev/null +++ b/drawinglayer/source/processor2d/baseprocessor2d.cxx @@ -0,0 +1,73 @@ +/* -*- 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/Primitive2DContainer.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + void BaseProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& /*rCandidate*/) + { + } + + BaseProcessor2D::BaseProcessor2D(const geometry::ViewInformation2D& rViewInformation) + : maViewInformation2D(rViewInformation) + { + } + + BaseProcessor2D::~BaseProcessor2D() + { + } + + void BaseProcessor2D::process(const primitive2d::BasePrimitive2D& rCandidate) + { + // use the visitor API to avoid the cost of constructing Primitive2DContainers + rCandidate.get2DDecomposition(*this, getViewInformation2D()); + } + + // Primitive2DDecompositionVisitor + void BaseProcessor2D::visit(const primitive2d::Primitive2DReference& rCandidate) + { + processBasePrimitive2D(*rCandidate); + } + void BaseProcessor2D::visit(const primitive2d::Primitive2DContainer& rContainer) + { + process(rContainer); + } + void BaseProcessor2D::visit(primitive2d::Primitive2DContainer&& rCandidate) + { + process(rCandidate); + } + + void BaseProcessor2D::process(const primitive2d::Primitive2DContainer& rSource) + { + for (const primitive2d::Primitive2DReference& rCandidate : rSource) + { + if (rCandidate) + processBasePrimitive2D(*rCandidate); + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/contourextractor2d.cxx b/drawinglayer/source/processor2d/contourextractor2d.cxx new file mode 100644 index 000000000..a1d120018 --- /dev/null +++ b/drawinglayer/source/processor2d/contourextractor2d.cxx @@ -0,0 +1,193 @@ +/* -*- 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/contourextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/sceneprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + ContourExtractor2D::ContourExtractor2D( + const geometry::ViewInformation2D& rViewInformation, + bool bExtractFillOnly) + : BaseProcessor2D(rViewInformation), + mbExtractFillOnly(bExtractFillOnly) + { + } + + ContourExtractor2D::~ContourExtractor2D() + { + } + + void ContourExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(!mbExtractFillOnly) + { + // extract hairline in world coordinates + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); + aLocalPolygon.transform(getViewInformation2D().getObjectTransformation()); + + if(aLocalPolygon.isClosed()) + { + // line polygons need to be represented as open polygons to differentiate them + // from filled polygons + basegfx::utils::openWithGeometryChange(aLocalPolygon); + } + + maExtractedContour.emplace_back(aLocalPolygon); + } + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + // extract fill in world coordinates + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + aLocalPolyPolygon.transform(getViewInformation2D().getObjectTransformation()); + maExtractedContour.push_back(aLocalPolyPolygon); + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : + { + // extract BoundRect from bitmaps in world coordinates + const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate)); + basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectTransformation() * rBitmapCandidate.getTransform()); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); + maExtractedContour.emplace_back(aPolygon); + break; + } + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : + { + // extract BoundRect from MetaFiles in world coordinates + const primitive2d::MetafilePrimitive2D& rMetaCandidate(static_cast< const primitive2d::MetafilePrimitive2D& >(rCandidate)); + basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectTransformation() * rMetaCandidate.getTransform()); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); + maExtractedContour.emplace_back(aPolygon); + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D : + { + // sub-transparence group. Look at children + const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate)); + process(rTransCandidate.getChildren()); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D : + { + // extract mask in world coordinates, ignore content + const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + aMask.transform(getViewInformation2D().getObjectTransformation()); + maExtractedContour.push_back(aMask); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D : + { + // remember current ViewInformation2D + const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate)); + 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()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : + { + // 2D Scene primitive containing 3D stuff; extract 2D contour in world coordinates + const primitive2d::ScenePrimitive2D& rScenePrimitive2DCandidate(static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate)); + const primitive2d::Primitive2DContainer xExtracted2DSceneGeometry(rScenePrimitive2DCandidate.getGeometry2D()); + const primitive2d::Primitive2DContainer xExtracted2DSceneShadow(rScenePrimitive2DCandidate.getShadow2D()); + + // process content + if(!xExtracted2DSceneGeometry.empty()) + { + process(xExtracted2DSceneGeometry); + } + + // process content + if(!xExtracted2DSceneShadow.empty()) + { + process(xExtracted2DSceneShadow); + } + + break; + } + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : + { + // ignorable primitives + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D : + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D : + { + // primitives who's BoundRect will be added in world coordinates + basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + if (!aRange.isEmpty()) + { + aRange.transform(getViewInformation2D().getObjectTransformation()); + maExtractedContour.emplace_back(basegfx::utils::createPolygonFromRect(aRange)); + } + break; + } + default : + { + // process recursively + process(rCandidate); + break; + } + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/getdigitlanguage.cxx b/drawinglayer/source/processor2d/getdigitlanguage.cxx new file mode 100644 index 000000000..858284b23 --- /dev/null +++ b/drawinglayer/source/processor2d/getdigitlanguage.cxx @@ -0,0 +1,31 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <i18nlangtag/lang.h> +#include <i18nlangtag/languagetag.hxx> +#include <svl/ctloptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include "getdigitlanguage.hxx" + +LanguageType drawinglayer::detail::getDigitLanguage() { + switch (SvtCTLOptions().GetCTLTextNumerals()) { + case SvtCTLOptions::NUMERALS_ARABIC: + return LANGUAGE_ENGLISH; + case SvtCTLOptions::NUMERALS_HINDI: + return LANGUAGE_ARABIC_SAUDI_ARABIA; + default: + return Application::GetSettings().GetLanguageTag().getLanguageType(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/getdigitlanguage.hxx b/drawinglayer/source/processor2d/getdigitlanguage.hxx new file mode 100644 index 000000000..c22076fa7 --- /dev/null +++ b/drawinglayer/source/processor2d/getdigitlanguage.hxx @@ -0,0 +1,22 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <i18nlangtag/lang.h> + +namespace drawinglayer::detail +{ +/// Get digit language derived from SvtCTLOptions +LanguageType getDigitLanguage(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx new file mode 100644 index 000000000..9f838a7e1 --- /dev/null +++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx @@ -0,0 +1,76 @@ +/* -*- 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 "helperwrongspellrenderer.hxx" +#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> +#include <tools/gen.hxx> +#include <vcl/outdev.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <vcl/outdev/ScopedStates.hxx> + +using namespace css; + +namespace drawinglayer +{ +namespace +{ +constexpr sal_uInt32 constMinimumFontHeight = 5; // #define WRONG_SHOW_MIN 5 +} + +bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellCandidate, + OutputDevice& rOutputDevice, + const basegfx::B2DHomMatrix& rObjectToViewTransformation, + const basegfx::BColorModifierStack& rBColorModifierStack) +{ + const basegfx::B2DHomMatrix aLocalTransform(rObjectToViewTransformation + * rWrongSpellCandidate.getTransformation()); + const basegfx::B2DVector aFontVectorPixel(aLocalTransform * basegfx::B2DVector(0.0, 1.0)); + const sal_uInt32 nFontPixelHeight(basegfx::fround(aFontVectorPixel.getLength())); + + if (nFontPixelHeight <= constMinimumFontHeight) + return true; + + const basegfx::B2DPoint aStart(aLocalTransform + * 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())); + + // #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 + const Point aOrigin(rOutputDevice.GetMapMode().GetOrigin()); + + const basegfx::BColor aProcessedColor( + rBColorModifierStack.getModifiedColor(rWrongSpellCandidate.getColor())); + const bool bMapModeEnabledState(rOutputDevice.IsMapModeEnabled()); + + vcl::ScopedAntialiasing a(rOutputDevice, true); + rOutputDevice.EnableMapMode(false); + rOutputDevice.SetLineColor(Color(aProcessedColor)); + rOutputDevice.SetFillColor(); + rOutputDevice.DrawWaveLine(aOrigin + aVclStart, aOrigin + aVclStop); + rOutputDevice.EnableMapMode(bMapModeEnabledState); + + // cannot really go wrong + return true; +} +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx new file mode 100644 index 000000000..886ce99bb --- /dev/null +++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx @@ -0,0 +1,47 @@ +/* -*- 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 + +class OutputDevice; + +namespace drawinglayer::primitive2d +{ +class WrongSpellPrimitive2D; +} + +namespace basegfx +{ +class B2DHomMatrix; +class BColorModifierStack; +} + +// support WrongSpell rendering using VCL from primitives due to VCLs nice +// and fast solution with wavelines + +namespace drawinglayer +{ +bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellCandidate, + OutputDevice& rOutputDevice, + const basegfx::B2DHomMatrix& rObjectToViewTransformation, + const basegfx::BColorModifierStack& rBColorModifierStack); + +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/hittestprocessor2d.cxx b/drawinglayer/source/processor2d/hittestprocessor2d.cxx new file mode 100644 index 000000000..dc7d6fa7c --- /dev/null +++ b/drawinglayer/source/processor2d/hittestprocessor2d.cxx @@ -0,0 +1,551 @@ +/* -*- 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/hittestprocessor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.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> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/sceneprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <drawinglayer/processor3d/cutfindprocessor3d.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <comphelper/lok.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +namespace drawinglayer::processor2d +{ + HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation, + const basegfx::B2DPoint& rLogicHitPosition, + double fLogicHitTolerance, + bool bHitTextOnly) + : BaseProcessor2D(rViewInformation), + mfDiscreteHitTolerance(0.0), + mbCollectHitStack(false), + mbHit(false), + mbHitTextOnly(bHitTextOnly) + { + // init hit tolerance + mfDiscreteHitTolerance = fLogicHitTolerance; + + 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)) + { + // generate discrete hit tolerance + mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation() + * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength(); + } + + // generate discrete hit position + maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition; + } + + HitTestProcessor2D::~HitTestProcessor2D() + { + } + + bool HitTestProcessor2D::checkHairlineHitWithTolerance( + const basegfx::B2DPolygon& rPolygon, + double fDiscreteHitTolerance) const + { + basegfx::B2DPolygon aLocalPolygon(rPolygon); + aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get discrete range + basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange()); + + if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0)) + { + aPolygonRange.grow(fDiscreteHitTolerance); + } + + // do rough range test first + if(aPolygonRange.isInside(getDiscreteHitPosition())) + { + // check if a polygon edge is hit + return basegfx::utils::isInEpsilonRange( + aLocalPolygon, + getDiscreteHitPosition(), + fDiscreteHitTolerance); + } + + return false; + } + + bool HitTestProcessor2D::checkFillHitWithTolerance( + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fDiscreteHitTolerance) const + { + bool bRetval(false); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon); + aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get discrete range + basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange()); + const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0)); + + if(bDiscreteHitToleranceUsed) + { + aPolygonRange.grow(fDiscreteHitTolerance); + } + + // do rough range test first + if(aPolygonRange.isInside(getDiscreteHitPosition())) + { + // if a HitTolerance is given, check for polygon edge hit in epsilon first + if(bDiscreteHitToleranceUsed && + basegfx::utils::isInEpsilonRange( + aLocalPolyPolygon, + getDiscreteHitPosition(), + fDiscreteHitTolerance)) + { + bRetval = true; + } + + // check for hit in filled polyPolygon + if(!bRetval && basegfx::utils::isInside( + aLocalPolyPolygon, + getDiscreteHitPosition(), + true)) + { + bRetval = true; + } + } + + return bRetval; + } + + void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate) + { + // calculate relative point in unified 2D scene + const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition()); + + // use bitmap check in ScenePrimitive2D + bool bTryFastResult(false); + + if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult)) + { + mbHit = bTryFastResult; + } + else + { + basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation()); + aInverseSceneTransform.invert(); + const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition); + + // check if test point is inside scene's unified area at all + if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0 + && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0) + { + // get 3D view information + const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D(); + + // create HitPoint Front and Back, transform to object coordinates + basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView()); + aViewToObject.invert(); + const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0)); + const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0)); + + if(!aFront.equal(aBack)) + { + const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D(); + + if(!rPrimitives.empty()) + { + // make BoundVolume empty and overlapping test for speedup + const basegfx::B3DRange aObjectRange( + rPrimitives.getB3DRange(rObjectViewInformation3D)); + + if(!aObjectRange.isEmpty()) + { + const basegfx::B3DRange aFrontBackRange(aFront, aBack); + + if(aObjectRange.overlaps(aFrontBackRange)) + { + // bound volumes hit, geometric cut tests needed + drawinglayer::processor3d::CutFindProcessor aCutFindProcessor( + rObjectViewInformation3D, + aFront, + aBack, + true); + aCutFindProcessor.process(rPrimitives); + + mbHit = (!aCutFindProcessor.getCutPoints().empty()); + } + } + } + } + } + + if(!getHit()) + { + // empty 3D scene; Check for border hit + basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon()); + aOutline.transform(rCandidate.getObjectTransformation()); + + mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance()); + } + } + } + + void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + if(getHit()) + { + // stop processing as soon as a hit was recognized + return; + } + + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D : + { + // remember current ViewInformation2D + const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate)); + 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()); + updateViewInformation(aViewInformation2D); + + // process child content recursively + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // create hairline in discrete coordinates + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + + // use hairline test + mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // handle marker like hairline; no need to decompose in dashes + const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate)); + + // use hairline test + mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // handle stroke evtl. directly; no need to decompose to filled polygon outlines + const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate)); + const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute(); + + if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0)) + { + if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin()) + { + // if line is mitered, use decomposition since mitered line + // geometry may use more space than the geometry grown by half line width + process(rCandidate); + } + else + { + // 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)); + mbHit = checkHairlineHitWithTolerance( + rPolygonCandidate.getB2DPolygon(), + getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength()); + } + } + else + { + // hairline; fallback to hairline test. Do not decompose + // since this may decompose the hairline to dashes + mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance()); + } + } + + break; + } + case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // do not use decompose; just handle like a line with width + const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate)); + double fLogicHitTolerance(0.0); + + // if WaveHeight, grow by it + if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0)) + { + fLogicHitTolerance += rPolygonCandidate.getWaveHeight(); + } + + // if line width, grow by it + if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0)) + { + fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5; + } + + const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() + * basegfx::B2DVector(fLogicHitTolerance, 0.0)); + + mbHit = checkHairlineHitWithTolerance( + rPolygonCandidate.getB2DPolygon(), + getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength()); + } + + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // create filled polyPolygon in discrete coordinates + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + + // use fill hit test + mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D : + { + // sub-transparence group + const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate)); + + // Currently the transparence content is not taken into account; only + // the children are recursively checked for hit. This may be refined for + // parts where the content is completely transparent if needed. + process(rTransCandidate.getChildren()); + + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D : + { + // create mask in discrete coordinates; only recursively continue + // with content when HitTest position is inside the mask + const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate)); + + // use fill hit test + if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance())) + { + // recursively HitTest children + process(rMaskCandidate.getChildren()); + } + + break; + } + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + const primitive2d::ScenePrimitive2D& rScenePrimitive2D( + static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate)); + check3DHit(rScenePrimitive2D); + } + + break; + } + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_GRIDPRIMITIVE2D : + case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D : + { + // ignorable primitives + break; + } + case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D : + { + // Ignore shadows; we do not want to have shadows hittable. + // Remove this one to make shadows hittable on demand. + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D : + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D : + { + // for text use the BoundRect of the primitive itself + const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // The recently added BitmapEx::GetTransparency() makes it easy to extend + // the BitmapPrimitive2D HitTest to take the contained BitmapEx and it's + // transparency into account + const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + + if(!aRange.isEmpty()) + { + const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate)); + const BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapCandidate.getXBitmap())); + const Size& rSizePixel(aBitmapEx.GetSizePixel()); + + // When tiled rendering, don't bother with the pixel size of the candidate. + if(rSizePixel.Width() && rSizePixel.Height() && !comphelper::LibreOfficeKit::isActive()) + { + basegfx::B2DHomMatrix aBackTransform( + getViewInformation2D().getObjectToViewTransformation() * + rBitmapCandidate.getTransform()); + aBackTransform.invert(); + + const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition()); + const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + + if(aUnitRange.isInside(aRelativePoint)) + { + const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width())); + const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height())); + + mbHit = (0 != aBitmapEx.GetAlpha(nX, nY)); + } + } + else + { + // fallback to standard HitTest + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance()); + } + } + } + + break; + } + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D : + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D : + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D : + case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D : + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D : + case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D: + { + if(!getHitTextOnly()) + { + // Class of primitives for which just the BoundRect of the primitive itself + // will be used for HitTest currently. + // + // This may be refined in the future, e.g: + // - For Bitmaps, the mask and/or transparence information may be used + // - For MetaFiles, the MetaFile content may be used + const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance()); + } + } + + break; + } + case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D : + { + // HiddenGeometryPrimitive2D; the default decomposition would return an empty sequence, + // so force this primitive to process its children directly if the switch is set + // (which is the default). Else, ignore invisible content + const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate)); + const primitive2d::Primitive2DContainer& rChildren = rHiddenGeometry.getChildren(); + + if(!rChildren.empty()) + { + process(rChildren); + } + + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate)); + const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions(); + const sal_uInt32 nCount(rPositions.size()); + + for(sal_uInt32 a(0); !getHit() && a < nCount; a++) + { + const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]); + const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition()); + + if(aDistance.getLength() <= getDiscreteHitTolerance()) + { + mbHit = true; + } + } + } + + break; + } + default : + { + // process recursively + process(rCandidate); + + break; + } + } + + if (getHit() && getCollectHitStack()) + { + /// 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))); + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/linegeometryextractor2d.cxx b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx new file mode 100644 index 000000000..08ec1232d --- /dev/null +++ b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx @@ -0,0 +1,125 @@ +/* -*- 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/linegeometryextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + LineGeometryExtractor2D::LineGeometryExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation), + mbInLineGeometry(false) + { + } + + LineGeometryExtractor2D::~LineGeometryExtractor2D() + { + } + + void LineGeometryExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D : + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D : + { + // enter a line geometry group (with or without LineEnds) + bool bOldState(mbInLineGeometry); + mbInLineGeometry = true; + process(rCandidate); + mbInLineGeometry = bOldState; + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(mbInLineGeometry) + { + // extract hairline line geometry in world coordinates + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); + aLocalPolygon.transform(getViewInformation2D().getObjectTransformation()); + maExtractedHairlines.push_back(aLocalPolygon); + } + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + if(mbInLineGeometry) + { + // extract filled line geometry (line with width) + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + aLocalPolyPolygon.transform(getViewInformation2D().getObjectTransformation()); + maExtractedLineFills.push_back(aLocalPolyPolygon); + } + break; + } + 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 + const geometry::ViewInformation2D aViewInformation2D( + getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), + getViewInformation2D().getViewTransformation(), + getViewInformation2D().getViewport(), + getViewInformation2D().getVisualizedPage(), + getViewInformation2D().getViewTime()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + 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 : + { + // ignorable primitives + break; + } + default : + { + // process recursively + process(rCandidate); + break; + } + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/objectinfoextractor2d.cxx b/drawinglayer/source/processor2d/objectinfoextractor2d.cxx new file mode 100644 index 000000000..552406d53 --- /dev/null +++ b/drawinglayer/source/processor2d/objectinfoextractor2d.cxx @@ -0,0 +1,77 @@ +/* -*- 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/objectinfoextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::processor2d +{ + void ObjectInfoPrimitiveExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + if(mpFound) + return; + + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D : + { + mpFound = dynamic_cast< const primitive2d::ObjectInfoPrimitive2D* >(&rCandidate); + break; + } + default : + { + // we look for an encapsulated primitive, so do not decompose primitives + // based on GroupPrimitive2D, just visit their children. It may be that more + // group-like primitives need to be added here, but all primitives with + // grouping functionality should be implemented based on the GroupPrimitive2D + // class and have their main content accessible as children + const primitive2d::GroupPrimitive2D* pGroupPrimitive2D = dynamic_cast< const primitive2d::GroupPrimitive2D* >(&rCandidate); + + if(pGroupPrimitive2D) + { + // process group children recursively + process(pGroupPrimitive2D->getChildren()); + } + else + { + // do not process recursively, we *only* want to find existing + // ObjectInfoPrimitive2D entries + } + + break; + } + } + } + + ObjectInfoPrimitiveExtractor2D::ObjectInfoPrimitiveExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation), + mpFound(nullptr) + { + } + + ObjectInfoPrimitiveExtractor2D::~ObjectInfoPrimitiveExtractor2D() + { + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/processor2dtools.cxx b/drawinglayer/source/processor2d/processor2dtools.cxx new file mode 100644 index 000000000..7bc0f5fa0 --- /dev/null +++ b/drawinglayer/source/processor2d/processor2dtools.cxx @@ -0,0 +1,62 @@ +/* -*- 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/processor2dtools.hxx> +#include <vcl/gdimtf.hxx> +#include "vclpixelprocessor2d.hxx" +#include "vclmetafileprocessor2d.hxx" + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + std::unique_ptr<BaseProcessor2D> createPixelProcessor2DFromOutputDevice( + OutputDevice& rTargetOutDev, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) + { + // create Pixel Vcl-Processor + return std::make_unique<VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev); + } + + 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 new file mode 100644 index 000000000..c8433753a --- /dev/null +++ b/drawinglayer/source/processor2d/processorfromoutputdevice.cxx @@ -0,0 +1,50 @@ +/* -*- 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 new file mode 100644 index 000000000..fcf15b189 --- /dev/null +++ b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx @@ -0,0 +1,231 @@ +/* -*- 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/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/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + + +namespace drawinglayer::processor2d +{ + void TextAsPolygonExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + 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 + // from text decomposition: + // + // - PolyPolygonColorPrimitive2D + // - PolygonHairlinePrimitive2D + // - PolyPolygonHairlinePrimitive2D (for convenience) + // + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + if(mnInText) + { + const primitive2d::PolyPolygonColorPrimitive2D& rPoPoCoCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aPolyPolygon(rPoPoCoCandidate.getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // transform the PolyPolygon + aPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get evtl. corrected color + const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoPoCoCandidate.getBColor())); + + // add to result vector + maTarget.emplace_back(aPolyPolygon, aColor, true); + } + } + + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(mnInText) + { + const primitive2d::PolygonHairlinePrimitive2D& rPoHaCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolygon aPolygon(rPoHaCandidate.getB2DPolygon()); + + if(aPolygon.count()) + { + // transform the Polygon + aPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get evtl. corrected color + const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoHaCandidate.getBColor())); + + // add to result vector + maTarget.emplace_back(basegfx::B2DPolyPolygon(aPolygon), aColor, false); + } + } + + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D : + { + if(mnInText) + { + const primitive2d::PolyPolygonHairlinePrimitive2D& rPoPoHaCandidate(static_cast< const primitive2d::PolyPolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aPolyPolygon(rPoPoHaCandidate.getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // transform the Polygon + aPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get evtl. corrected color + const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoPoHaCandidate.getBColor())); + + // add to result vector + maTarget.emplace_back(aPolyPolygon, aColor, false); + } + } + + break; + } + + // usage of color modification stack is needed + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D : + { + const primitive2d::ModifiedColorPrimitive2D& rModifiedColorCandidate(static_cast< const primitive2d::ModifiedColorPrimitive2D& >(rCandidate)); + + if(!rModifiedColorCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedColorCandidate.getColorModifier()); + process(rModifiedColorCandidate.getChildren()); + maBColorModifierStack.pop(); + } + + 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 + const geometry::ViewInformation2D aViewInformation2D( + getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(), + getViewInformation2D().getViewTransformation(), + getViewInformation2D().getViewport(), + getViewInformation2D().getVisualizedPage(), + getViewInformation2D().getViewTime()); + 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; + } + } + } + + TextAsPolygonExtractor2D::TextAsPolygonExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation), + maBColorModifierStack(), + mnInText(0) + { + } + + TextAsPolygonExtractor2D::~TextAsPolygonExtractor2D() + { + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx new file mode 100644 index 000000000..4a2934402 --- /dev/null +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx @@ -0,0 +1,444 @@ +/* -*- 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 <sal/log.hxx> + +#include <algorithm> +#include <map> +#include <vector> + +#include "vclhelperbufferdevice.hxx" +#include <basegfx/range/b2drange.hxx> +#include <vcl/bitmapex.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <tools/stream.hxx> +#include <vcl/timer.hxx> +#include <vcl/lazydelete.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <mutex> + +// buffered VDev usage + +namespace +{ +class VDevBuffer : public Timer +{ +private: + struct Entry + { + VclPtr<VirtualDevice> buf; + bool isTransparent = false; + Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent) + : buf(vdev) + , isTransparent(bTransparent) + { + } + }; + + std::mutex m_aMutex; + + // available buffers + std::vector<Entry> maFreeBuffers; + + // allocated/used buffers (remembered to allow deleting them in destructor) + std::vector<Entry> maUsedBuffers; + + // remember what outputdevice was the template passed to VirtualDevice::Create + // so we can test if that OutputDevice was disposed before reusing a + // 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); + void free(VirtualDevice& rDevice); + + // Timer virtuals + virtual void Invoke() override; +}; + +VDevBuffer::VDevBuffer() + : Timer("drawinglayer::VDevBuffer via Invoke()") +{ + SetTimeout(10L * 1000L); // ten seconds +} + +VDevBuffer::~VDevBuffer() +{ + std::unique_lock aGuard(m_aMutex); + Stop(); + + while (!maFreeBuffers.empty()) + { + maFreeBuffers.back().buf.disposeAndClear(); + maFreeBuffers.pop_back(); + } + + while (!maUsedBuffers.empty()) + { + maUsedBuffers.back().buf.disposeAndClear(); + maUsedBuffers.pop_back(); + } +} + +bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel) +{ + 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, + bool bTransparent) +{ + std::unique_lock aGuard(m_aMutex); + VclPtr<VirtualDevice> pRetval; + + sal_Int32 nBits = rOutDev.GetBitCount(); + + bool bOkay(false); + if (!maFreeBuffers.empty()) + { + auto aFound(maFreeBuffers.end()); + + for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a) + { + assert(a->buf && "Empty pointer in VDevBuffer (!)"); + + if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent) + { + // candidate is valid due to bit depth + if (aFound != maFreeBuffers.end()) + { + // already found + if (bOkay) + { + // found is valid + const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel); + + if (bCandidateOkay) + { + // found and candidate are valid + const sal_uLong aSquare(aFound->buf->GetOutputWidthPixel() + * aFound->buf->GetOutputHeightPixel()); + const sal_uLong aCandidateSquare(a->buf->GetOutputWidthPixel() + * a->buf->GetOutputHeightPixel()); + + if (aCandidateSquare < aSquare) + { + // candidate is valid and smaller, use it + aFound = a; + } + } + else + { + // found is valid, candidate is not. Keep found + } + } + else + { + // found is invalid, use candidate + aFound = a; + bOkay = isSizeSuitable(aFound->buf, rSizePixel); + } + } + else + { + // none yet, use candidate + aFound = a; + bOkay = isSizeSuitable(aFound->buf, rSizePixel); + } + } + } + + if (aFound != maFreeBuffers.end()) + { + pRetval = aFound->buf; + maFreeBuffers.erase(aFound); + } + } + + if (pRetval) + { + // found a suitable cached virtual device, but the + // outputdevice it was based on has been disposed, + // drop it and create a new one instead as reusing + // such devices is unsafe under at least Gtk2 + if (maDeviceTemplates[pRetval]->isDisposed()) + { + maDeviceTemplates.erase(pRetval); + pRetval.disposeAndClear(); + } + else + { + if (bOkay) + { + pRetval->Erase(pRetval->PixelToLogic( + tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight()))); + } + else + { + pRetval->SetOutputSizePixel(rSizePixel, true); + } + } + } + + // no success yet, create new buffer + if (!pRetval) + { + pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT, + bTransparent ? DeviceFormat::DEFAULT + : DeviceFormat::NONE); + maDeviceTemplates[pRetval] = &rOutDev; + pRetval->SetOutputSizePixel(rSizePixel, true); + } + else + { + // reused, reset some values + pRetval->SetMapMode(); + pRetval->SetRasterOp(RasterOp::OverPaint); + } + + // remember allocated buffer + maUsedBuffers.emplace_back(pRetval, bTransparent); + + return pRetval; +} + +void VDevBuffer::free(VirtualDevice& rDevice) +{ + std::unique_lock aGuard(m_aMutex); + const auto aUsedFound + = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(), + [&rDevice](const Entry& el) { return el.buf == &rDevice; }); + SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer", + "OOps, non-registered buffer freed (!)"); + if (aUsedFound != maUsedBuffers.end()) + { + maFreeBuffers.emplace_back(*aUsedFound); + maUsedBuffers.erase(aUsedFound); + SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer", + "excessive cached buffers, " << maFreeBuffers.size() << " entries!"); + } + Start(); +} + +void VDevBuffer::Invoke() +{ + std::unique_lock aGuard(m_aMutex); + + while (!maFreeBuffers.empty()) + { + auto aLastOne = maFreeBuffers.back(); + maDeviceTemplates.erase(aLastOne.buf); + aLastOne.buf.disposeAndClear(); + maFreeBuffers.pop_back(); + } +} +} + +// support for rendering Bitmap and BitmapEx contents + +namespace drawinglayer +{ +// static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D) +VDevBuffer& getVDevBuffer() +{ + // secure global instance with Vcl's safe destroyer of external (seen by + // library base) stuff, the remembered VDevs need to be deleted before + // Vcl's deinit + static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer{}; + return *aVDevBuffer.get(); +} + +impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, bool bCrop) + : mrOutDev(rOutDev) + , mpContent(nullptr) + , mpAlpha(nullptr) +{ + basegfx::B2DRange aRangePixel(rRange); + aRangePixel.transform(mrOutDev.GetViewTransformation()); + maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()), + ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY())); + if (bCrop) + maDestPixel.Intersection({ {}, mrOutDev.GetOutputSizePixel() }); + + if (!isVisible()) + return; + + mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true); + + // #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 (!)"); + + MapMode aNewMapMode(mrOutDev.GetMapMode()); + + const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft())); + aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y())); + + mpContent->SetMapMode(aNewMapMode); + + // copy AA flag for new target + mpContent->SetAntialiasing(mrOutDev.GetAntialiasing()); + + // copy RasterOp (e.g. may be RasterOp::Xor on destination) + mpContent->SetRasterOp(mrOutDev.GetRasterOp()); +} + +impBufferDevice::~impBufferDevice() +{ + if (mpContent) + { + getVDevBuffer().free(*mpContent); + } + + if (mpAlpha) + { + getVDevBuffer().free(*mpAlpha); + } +} + +void impBufferDevice::paint(double fTrans) +{ + if (!isVisible()) + return; + + 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) + { + SvFileStream aNew( +#ifdef _WIN32 + "c:\\content.bmp", +#else + "~/content.bmp", +#endif + StreamMode::WRITE | StreamMode::TRUNC); + Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + WriteDIB(aContent, aNew, false, true); + } +#endif + + // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor) + const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp()); + mrOutDev.SetRasterOp(RasterOp::OverPaint); + + if (mpAlpha) + { + mpAlpha->EnableMapMode(false); + AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel)); + +#ifdef DBG_UTIL + if (bDoSaveForVisualControl) + { + SvFileStream aNew( +#ifdef _WIN32 + "c:\\transparence.bmp", +#else + "~/transparence.bmp", +#endif + 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)); + } + 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); + } + else + { + mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent); + } + + mrOutDev.SetRasterOp(aOrigRasterOp); + mrOutDev.EnableMapMode(bWasEnabledDst); +} + +VirtualDevice& impBufferDevice::getContent() +{ + SAL_WARN_IF(!mpContent, "drawinglayer", + "impBufferDevice: No content, check isVisible() before accessing (!)"); + return *mpContent; +} + +VirtualDevice& impBufferDevice::getTransparence() +{ + SAL_WARN_IF(!mpContent, "drawinglayer", + "impBufferDevice: No content, check isVisible() before accessing (!)"); + if (!mpAlpha) + { + mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false); + mpAlpha->SetMapMode(mpContent->GetMapMode()); + + // copy AA flag for new target; masking needs to be smooth + mpAlpha->SetAntialiasing(mpContent->GetAntialiasing()); + } + + return *mpAlpha; +} +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx new file mode 100644 index 000000000..99585b05b --- /dev/null +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx @@ -0,0 +1,51 @@ +/* -*- 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/virdev.hxx> + +namespace basegfx +{ +class B2DRange; +} + +// support methods for vcl direct gradient rendering + +namespace drawinglayer +{ +class impBufferDevice +{ + OutputDevice& mrOutDev; + VclPtr<VirtualDevice> mpContent; + VclPtr<VirtualDevice> mpAlpha; + tools::Rectangle maDestPixel; + +public: + impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, bool bCrop = true); + ~impBufferDevice(); + + void paint(double fTrans = 0.0); + bool isVisible() const { return !maDestPixel.IsEmpty(); } + VirtualDevice& getContent(); + VirtualDevice& getTransparence(); +}; +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx new file mode 100644 index 000000000..673fd093d --- /dev/null +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -0,0 +1,2571 @@ +/* -*- 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 <cmath> +#include <memory> +#include "vclmetafileprocessor2d.hxx" +#include "vclpixelprocessor2d.hxx" +#include <rtl/ustring.hxx> +#include <tools/gen.hxx> +#include <tools/stream.hxx> +#include <tools/diagnose_ex.h> +#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 <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 <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/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/controlprimitive2d.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> +#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +using namespace com::sun::star; + +// #112245# definition for maximum allowed point count due to Metafile target. +// To be on the safe side with the old tools polygon, use slightly less than +// the theoretical maximum (bad experiences with tools polygon) + +#define MAX_POLYGON_POINT_COUNT_METAFILE (0x0000fff0) + +namespace +{ +// #112245# helper to split line polygon in half +void splitLinePolygon(const basegfx::B2DPolygon& rBasePolygon, basegfx::B2DPolygon& o_aLeft, + basegfx::B2DPolygon& o_aRight) +{ + const sal_uInt32 nCount(rBasePolygon.count()); + + if (nCount) + { + const sal_uInt32 nHalfCount((nCount - 1) >> 1); + + o_aLeft = basegfx::B2DPolygon(rBasePolygon, 0, nHalfCount + 1); + o_aLeft.setClosed(false); + + o_aRight = basegfx::B2DPolygon(rBasePolygon, nHalfCount, nCount - nHalfCount); + o_aRight.setClosed(false); + + if (rBasePolygon.isClosed()) + { + o_aRight.append(rBasePolygon.getB2DPoint(0)); + + if (rBasePolygon.areControlPointsUsed()) + { + o_aRight.setControlPoints(o_aRight.count() - 1, rBasePolygon.getPrevControlPoint(0), + rBasePolygon.getNextControlPoint(0)); + } + } + } + else + { + o_aLeft.clear(); + o_aRight.clear(); + } +} + +// #112245# helper to evtl. split filled polygons to maximum metafile point count +void fillPolyPolygonNeededToBeSplit(basegfx::B2DPolyPolygon& rPolyPolygon) +{ + const sal_uInt32 nPolyCount(rPolyPolygon.count()); + + if (!nPolyCount) + return; + + basegfx::B2DPolyPolygon aSplitted; + + for (sal_uInt32 a(0); a < nPolyCount; a++) + { + const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(a)); + const sal_uInt32 nPointCount(aCandidate.count()); + bool bNeedToSplit(false); + + if (aCandidate.areControlPointsUsed()) + { + // compare with the maximum for bezier curved polygons + bNeedToSplit = nPointCount > ((MAX_POLYGON_POINT_COUNT_METAFILE / 3L) - 1); + } + else + { + // compare with the maximum for simple point polygons + bNeedToSplit = nPointCount > (MAX_POLYGON_POINT_COUNT_METAFILE - 1); + } + + if (bNeedToSplit) + { + // need to split the partial polygon + const basegfx::B2DRange aRange(aCandidate.getB2DRange()); + const basegfx::B2DPoint aCenter(aRange.getCenter()); + + if (aRange.getWidth() > aRange.getHeight()) + { + // clip in left and right + const basegfx::B2DPolyPolygon aLeft(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, false, true, aCenter.getX(), false)); + const basegfx::B2DPolyPolygon aRight(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, false, false, aCenter.getX(), false)); + + aSplitted.append(aLeft); + aSplitted.append(aRight); + } + else + { + // clip in top and bottom + const basegfx::B2DPolyPolygon aTop(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, true, true, aCenter.getY(), false)); + const basegfx::B2DPolyPolygon aBottom(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, true, false, aCenter.getY(), false)); + + aSplitted.append(aTop); + aSplitted.append(aBottom); + } + } + else + { + aSplitted.append(aCandidate); + } + } + + if (aSplitted.count() != nPolyCount) + { + rPolyPolygon = aSplitted; + } +} + +/** Filter input polypolygon for effectively empty sub-fills + + Needed to fix fdo#37559 + + @param rPoly + tools::PolyPolygon to filter + + @return converted tools PolyPolygon, w/o one-point fills + */ +tools::PolyPolygon getFillPolyPolygon(const ::basegfx::B2DPolyPolygon& rPoly) +{ + // filter input rPoly + basegfx::B2DPolyPolygon aPoly; + sal_uInt32 nCount(rPoly.count()); + for (sal_uInt32 i = 0; i < nCount; ++i) + { + const basegfx::B2DPolygon& aCandidate(rPoly.getB2DPolygon(i)); + if (!aCandidate.isClosed() || aCandidate.count() > 1) + aPoly.append(aCandidate); + } + return tools::PolyPolygon(aPoly); +} + +} // end of anonymous namespace + +namespace drawinglayer::processor2d +{ +tools::Rectangle +VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent, + GDIMetaFile& o_rContentMetafile) +{ + // Prepare VDev, MetaFile and connections + OutputDevice* pLastOutputDevice = mpOutputDevice; + GDIMetaFile* pLastMetafile = mpMetaFile; + basegfx::B2DRange aPrimitiveRange(rContent.getB2DRange(getViewInformation2D())); + + // transform primitive range with current transformation (e.g shadow offset) + aPrimitiveRange.transform(maCurrentTransformation); + + const tools::Rectangle aPrimitiveRectangle( + basegfx::fround(aPrimitiveRange.getMinX()), basegfx::fround(aPrimitiveRange.getMinY()), + basegfx::fround(aPrimitiveRange.getMaxX()), basegfx::fround(aPrimitiveRange.getMaxY())); + ScopedVclPtrInstance<VirtualDevice> aContentVDev; + MapMode aNewMapMode(pLastOutputDevice->GetMapMode()); + + mpOutputDevice = aContentVDev.get(); + mpMetaFile = &o_rContentMetafile; + aContentVDev->EnableOutput(false); + aContentVDev->SetMapMode(pLastOutputDevice->GetMapMode()); + o_rContentMetafile.Record(aContentVDev.get()); + aContentVDev->SetLineColor(pLastOutputDevice->GetLineColor()); + aContentVDev->SetFillColor(pLastOutputDevice->GetFillColor()); + aContentVDev->SetFont(pLastOutputDevice->GetFont()); + aContentVDev->SetDrawMode(pLastOutputDevice->GetDrawMode()); + aContentVDev->SetSettings(pLastOutputDevice->GetSettings()); + aContentVDev->SetRefPoint(pLastOutputDevice->GetRefPoint()); + + // dump to MetaFile + process(rContent); + + // cleanups + o_rContentMetafile.Stop(); + o_rContentMetafile.WindStart(); + aNewMapMode.SetOrigin(aPrimitiveRectangle.TopLeft()); + o_rContentMetafile.SetPrefMapMode(aNewMapMode); + o_rContentMetafile.SetPrefSize(aPrimitiveRectangle.GetSize()); + mpOutputDevice = pLastOutputDevice; + mpMetaFile = pLastMetafile; + + return aPrimitiveRectangle; +} + +void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( + Gradient& o_rVCLGradient, const attribute::FillGradientAttribute& rFiGrAtt, + bool bIsTransparenceGradient) const +{ + 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())); + } + 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.SetAngle( + Degree10(static_cast<sal_uInt32>(basegfx::rad2deg<10>(rFiGrAtt.getAngle())))); + o_rVCLGradient.SetBorder(static_cast<sal_uInt16>(rFiGrAtt.getBorder() * 100.0)); + o_rVCLGradient.SetOfsX(static_cast<sal_uInt16>(rFiGrAtt.getOffsetX() * 100.0)); + o_rVCLGradient.SetOfsY(static_cast<sal_uInt16>(rFiGrAtt.getOffsetY() * 100.0)); + o_rVCLGradient.SetSteps(rFiGrAtt.getSteps()); + + // 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; + } + } +} + +void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) +{ + if (pSvtGraphicFill && !mnSvtGraphicFillCount) + { + SvMemoryStream aMemStm; + + WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill); + mpMetaFile->AddAction(new MetaCommentAction( + "XPATHFILL_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + mnSvtGraphicFillCount++; + } +} + +void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) +{ + if (pSvtGraphicFill && mnSvtGraphicFillCount) + { + mnSvtGraphicFillCount--; + mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END")); + } +} + +double VclMetafileProcessor2D::getTransformedLineWidth(double fWidth) const +{ + // #i113922# the LineWidth is duplicated in the MetaPolylineAction, + // and also inside the SvtGraphicStroke and needs transforming into + // the same space as its coordinates here cf. fdo#61789 + // This is a partial fix. When an object transformation is used which + // e.g. contains a scaleX != scaleY, an unproportional scaling will happen. + const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation + * basegfx::B2DVector(fWidth, 0.0)); + + return aDiscreteUnit.getLength(); +} + +std::unique_ptr<SvtGraphicStroke> VclMetafileProcessor2D::impTryToCreateSvtGraphicStroke( + const basegfx::B2DPolygon& rB2DPolygon, const basegfx::BColor* pColor, + const attribute::LineAttribute* pLineAttribute, + const attribute::StrokeAttribute* pStrokeAttribute, + const attribute::LineStartEndAttribute* pStart, const attribute::LineStartEndAttribute* pEnd) +{ + std::unique_ptr<SvtGraphicStroke> pRetval; + + if (rB2DPolygon.count() && !mnSvtGraphicStrokeCount) + { + basegfx::B2DPolygon aLocalPolygon(rB2DPolygon); + basegfx::BColor aStrokeColor; + basegfx::B2DPolyPolygon aStartArrow; + basegfx::B2DPolyPolygon aEndArrow; + + if (pColor) + { + aStrokeColor = *pColor; + } + else if (pLineAttribute) + { + aStrokeColor = maBColorModifierStack.getModifiedColor(pLineAttribute->getColor()); + } + + // It IS needed to record the stroke color at all in the metafile, + // SvtGraphicStroke has NO entry for stroke color(!) + mpOutputDevice->SetLineColor(Color(aStrokeColor)); + + if (!aLocalPolygon.isClosed()) + { + double fPolyLength(0.0); + double fStart(0.0); + double fEnd(0.0); + + if (pStart && pStart->isActive()) + { + fPolyLength = basegfx::utils::getLength(aLocalPolygon); + + aStartArrow = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, pStart->getB2DPolyPolygon(), true, pStart->getWidth(), + fPolyLength, pStart->isCentered() ? 0.5 : 0.0, &fStart); + } + + if (pEnd && pEnd->isActive()) + { + if (basegfx::fTools::equalZero(fPolyLength)) + { + fPolyLength = basegfx::utils::getLength(aLocalPolygon); + } + + aEndArrow = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, pEnd->getB2DPolyPolygon(), false, pEnd->getWidth(), fPolyLength, + pEnd->isCentered() ? 0.5 : 0.0, &fEnd); + } + + if (0.0 != fStart || 0.0 != fEnd) + { + // build new poly, consume something from old poly + aLocalPolygon = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart, + fPolyLength - fEnd, fPolyLength); + } + } + + SvtGraphicStroke::JoinType eJoin(SvtGraphicStroke::joinNone); + SvtGraphicStroke::CapType eCap(SvtGraphicStroke::capButt); + double fLineWidth(0.0); + double fMiterLength(0.0); + SvtGraphicStroke::DashArray aDashArray; + + if (pLineAttribute) + { + fLineWidth = fMiterLength = getTransformedLineWidth(pLineAttribute->getWidth()); + + // get Join + switch (pLineAttribute->getLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + { + eJoin = SvtGraphicStroke::joinNone; + break; + } + case basegfx::B2DLineJoin::Bevel: + { + eJoin = SvtGraphicStroke::joinBevel; + break; + } + case basegfx::B2DLineJoin::Miter: + { + eJoin = SvtGraphicStroke::joinMiter; + // ATM 15 degrees is assumed + // TODO wait for P1383R0 and C++20's std::numbers::pi + fMiterLength /= std::sin(M_PI / 12); + break; + } + case basegfx::B2DLineJoin::Round: + { + eJoin = SvtGraphicStroke::joinRound; + break; + } + } + + // get stroke + switch (pLineAttribute->getLineCap()) + { + default: /* css::drawing::LineCap_BUTT */ + { + eCap = SvtGraphicStroke::capButt; + break; + } + case css::drawing::LineCap_ROUND: + { + eCap = SvtGraphicStroke::capRound; + break; + } + case css::drawing::LineCap_SQUARE: + { + eCap = SvtGraphicStroke::capSquare; + break; + } + } + } + + if (pStrokeAttribute) + { + // copy dash array + aDashArray = pStrokeAttribute->getDotDashArray(); + } + + // #i101734# apply current object transformation to created geometry. + // This is a partial fix. When an object transformation is used which + // e.g. contains a scaleX != scaleY, an unproportional scaling would + // have to be applied to the evtl. existing fat line. The current + // concept of PDF export and SvtGraphicStroke usage does simply not + // allow handling such definitions. The only clean way would be to + // add the transformation to SvtGraphicStroke and to handle it there + aLocalPolygon.transform(maCurrentTransformation); + aStartArrow.transform(maCurrentTransformation); + aEndArrow.transform(maCurrentTransformation); + + pRetval.reset( + new SvtGraphicStroke(tools::Polygon(aLocalPolygon), tools::PolyPolygon(aStartArrow), + tools::PolyPolygon(aEndArrow), mfCurrentUnifiedTransparence, + fLineWidth, eCap, eJoin, fMiterLength, std::move(aDashArray))); + } + + return pRetval; +} + +void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke) +{ + if (pSvtGraphicStroke && !mnSvtGraphicStrokeCount) + { + SvMemoryStream aMemStm; + + WriteSvtGraphicStroke(aMemStm, *pSvtGraphicStroke); + mpMetaFile->AddAction(new MetaCommentAction( + "XPATHSTROKE_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + mnSvtGraphicStrokeCount++; + } +} + +void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke) +{ + if (pSvtGraphicStroke && mnSvtGraphicStrokeCount) + { + mnSvtGraphicStrokeCount--; + mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END")); + } +} + +void VclMetafileProcessor2D::popStructureElement(vcl::PDFWriter::StructElement eElem) +{ + if (!maListElements.empty() && maListElements.top() == eElem) + { + maListElements.pop(); + mpPDFExtOutDevData->EndStructureElement(); + } +} + +void VclMetafileProcessor2D::popListItem() +{ + popStructureElement(vcl::PDFWriter::LIBody); + popStructureElement(vcl::PDFWriter::ListItem); +} + +void VclMetafileProcessor2D::popList() +{ + popListItem(); + popStructureElement(vcl::PDFWriter::List); +} + +// init static break iterator +uno::Reference<css::i18n::XBreakIterator> VclMetafileProcessor2D::mxBreakIterator; + +VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev) + : VclProcessor2D(rViewInformation, rOutDev) + , mpMetaFile(rOutDev.GetConnectMetaFile()) + , mnSvtGraphicFillCount(0) + , mnSvtGraphicStrokeCount(0) + , mfCurrentUnifiedTransparence(0.0) + , mpPDFExtOutDevData(dynamic_cast<vcl::PDFExtOutDevData*>(rOutDev.GetExtOutDevData())) + , mnCurrentOutlineLevel(-1) + , mbInListItem(false) + , mbBulletPresent(false) +{ + OSL_ENSURE(rOutDev.GetConnectMetaFile(), + "VclMetafileProcessor2D: Used on OutDev which has no MetaFile Target (!)"); + // draw to logic coordinates, do not initialize maCurrentTransformation to viewTransformation + // but only to ObjectTransformation. Do not change MapMode of destination. + maCurrentTransformation = rViewInformation.getObjectTransformation(); +} + +VclMetafileProcessor2D::~VclMetafileProcessor2D() +{ + // MapMode was not changed, no restore necessary +} + +/*********************************************************************************************** + + Support of MetaCommentActions in the VclMetafileProcessor2D + Found MetaCommentActions and how they are supported: + + XGRAD_SEQ_BEGIN, XGRAD_SEQ_END: + + Used inside OutputDevice::DrawGradient to mark the start and end of a MetaGradientEx action. + It is used in various exporters/importers to have direct access to the gradient before it + is rendered by VCL (and thus fragmented to polygon color actions and others). On that base, e.g. + the Metafile to SdrObject import creates its gradient objects. + Best (and safest) way to support it here is to use PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D, + map it back to the corresponding tools tools::PolyPolygon and the Gradient and just call + OutputDevice::DrawGradient which creates the necessary compatible actions. + + XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END: + + Two producers, one is vcl/source/gdi/gdimtf.cxx, line 1273. There, it is transformed + inside GDIMetaFile::Rotate, nothing to take care of here. + The second producer is in graphics/svx/source/svdraw/impgrfll.cxx, line 374. This is used + with each incarnation of Imp_GraphicFill when a metafile is recorded, fillstyle is not + XFILL_NONE and not completely transparent. It creates a SvtGraphicFill and streams it + to the comment action. A closing end token is created in the destructor. + Usages of Imp_GraphicFill are in Do_Paint_Object-methods of SdrCircObj, SdrPathObj and + SdrRectObj. + The token users pick various actions from SvtGraphicFill, so it may need to be added for all kind + of filled objects, even simple colored polygons. It is added as extra information; the + Metafile actions between the two tokens are interpreted as output generated from those + fills. Thus, users have the choice to use the SvtGraphicFill info or the created output + actions. + Even for XFillTransparenceItem it is used, thus it may need to be supported in + UnifiedTransparencePrimitive2D, too, when interpreted as normally filled PolyPolygon. + Implemented for: + PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D, + PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D, + PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D, + PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D, + and for PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D when detected unified transparence + + XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END: + + Similar to pathfill, but using SvtGraphicStroke instead. It also has two producers where one + is also the GDIMetaFile::Rotate. Another user is MetaCommentAction::Move which modifies the + contained path accordingly. + The other one is SdrObject::Imp_DrawLineGeometry. It's done when MetaFile is set at OutDev and + only when geometry is a single polygon (!). I see no reason for that; in the PS exporter this + would hinder to make use of tools::PolyPolygon strokes. I will need to add support at: + PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D + PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D + PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D + This can be done hierarchical, too. + Okay, base implementation done based on those three primitives. + + FIELD_SEQ_BEGIN, FIELD_SEQ_END + + Used from slideshow for URLs, created from diverse SvxField implementations inside + createBeginComment()/createEndComment(). createBeginComment() is used from editeng\impedit3.cxx + inside ImpEditEngine::Paint. + Created TextHierarchyFieldPrimitive2D and added needed infos there; it is a group primitive and wraps + text primitives (but is not limited to that). It contains the field type if special actions for the + support of FIELD_SEQ_BEGIN/END are needed; this is the case for Page and URL fields. If more is + needed, it may be supported there. + FIELD_SEQ_BEGIN;PageField + FIELD_SEQ_END + Okay, these are now completely supported by TextHierarchyFieldPrimitive2D. URL works, too. + + XTEXT + + XTEXT_EOC(i) end of character + XTEXT_EOW(i) end of word + XTEXT_EOS(i) end of sentence + + this three are with index and are created with the help of an i18n::XBreakIterator in + ImplDrawWithComments. Simplifying, moving out text painting, reworking to create some + data structure for holding those TEXT infos. + Supported directly by TextSimplePortionPrimitive2D with adding a Locale to the basic text + primitive. In the MetaFileRenderer, the creation is now done (see below). This has the advantage + that this creations do not need to be done for all paints all the time. This would be + expensive since the BreakIterator and it's usage is expensive and for each paint also the + whole character stops would need to be created. + Created only for TextDecoratedPortionPrimitive2D due to XTEXT_EOL and XTEXT_EOP (see below) + + XTEXT_EOL() end of line + XTEXT_EOP() end of paragraph + + First try with boolean marks at TextDecoratedPortionPrimitive2D did not work too well, + i decided to solve it with structure. I added the TextHierarchyPrimitives for this, + namely: + - TextHierarchyLinePrimitive2D: Encapsulates single line + - TextHierarchyParagraphPrimitive2D: Encapsulates single paragraph + - TextHierarchyBlockPrimitive2D: encapsulates object texts (only one ATM) + Those are now supported in hierarchy. This means the MetaFile renderer will support them + by using them, recursively using their content and adding MetaFile comments as needed. + This also means that when another text layouter will be used it will be necessary to + create/support the same HierarchyPrimitives to support users. + To transport the information using this hierarchy is best suited to all future needs; + the slideshow will be able to profit from it directly when using primitives; all other + renderers not interested in the text structure will just ignore the encapsulations. + + XTEXT_PAINTSHAPE_BEGIN, XTEXT_PAINTSHAPE_END + Supported now by the TextHierarchyBlockPrimitive2D. + + EPSReplacementGraphic: + Only used in goodies\source\filter.vcl\ieps\ieps.cxx and svx\source\xml\xmlgrhlp.cxx to + hold the original EPS which was imported in the same MetaFile as first 2 entries. Only + used to export the original again (if exists). + Not necessary to support with MetaFileRenderer. + + XTEXT_SCROLLRECT, XTEXT_PAINTRECT + Currently used to get extra MetaFile infos using GraphicExporter which again uses + SdrTextObj::GetTextScrollMetaFileAndRectangle(). ATM works with primitives since + the rectangle data is added directly by the GraphicsExporter as comment. Does not need + to be adapted at once. + When adapting later, the only user - the diashow - should directly use the provided + Animation infos in the appropriate primitives (e.g. AnimatedSwitchPrimitive2D) + + PRNSPOOL_TRANSPARENTBITMAP_BEGIN, PRNSPOOL_TRANSPARENTBITMAP_END + VCL usage when printing PL -> THB. Okay, THB confirms that it is only used as + a fix (hack) while VCL printing. It is needed to not downscale a bitmap which + was explicitly created for the printer already again to some default maximum + bitmap sizes. + Nothing to do here for the primitive renderer. + + Support for vcl::PDFExtOutDevData: + PL knows that SJ did that stuff, it's used to hold a pointer to PDFExtOutDevData at + the OutDev. When set, some extra data is written there. Trying simple PDF export and + watching if I get those infos. + Well, a PDF export does not use e.g. ImpEditEngine::Paint since the PdfFilter uses + the SdXImpressDocument::render and thus uses the VclMetafileProcessor2D. I will check + if I get a PDFExtOutDevData at the target output device. + Indeed, I get one. Checking what all may be done when that extra-device-info is there. + + All in all I have to talk to SJ. I will need to emulate some of those actions, but + i need to discuss which ones. + In the future, all those infos would be taken from the primitive sequence anyways, + thus these extensions would potentially be temporary, too. + Discussed with SJ, added the necessary support and tested it. Details follow. + + - In ImpEditEngine::Paint, paragraph infos and URL stuff is added. + Added in primitive MetaFile renderer. + Checking URL: Indeed, current version exports it, but it is missing in primitive + CWS version. Adding support. + Okay, URLs work. Checked, Done. + + - 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. + Checked form control export, it works well. Done. + + - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are + generated. I will need to check what happens here with primitives. + To support, use of GraphicPrimitive2D (PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D) may be needed. + Added support, but feature is broken in main version, so i cannot test at all. + Writing a bug to CL (or SJ) and seeing what happens (#i80380#). + SJ took a look and we got it working. Tested VCL MetaFile Renderer based export, + as intended, the original file is exported. Works, Done. + + + To be done: + + - Maybe there are more places to take care of for vcl::PDFExtOutDevData! + + +****************************************************************************************************/ + +void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + switch (rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + { + // directdraw of wrong spell primitive + // Ignore for VclMetafileProcessor2D, this is for printing and MetaFile recording only + break; + } + case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D: + { + processGraphicPrimitive2D( + static_cast<const primitive2d::GraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + { + processControlPrimitive2D( + static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D: + { + processTextHierarchyFieldPrimitive2D( + static_cast<const primitive2d::TextHierarchyFieldPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D: + { + processTextHierarchyLinePrimitive2D( + static_cast<const primitive2d::TextHierarchyLinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D: + { + processTextHierarchyBulletPrimitive2D( + static_cast<const primitive2d::TextHierarchyBulletPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D: + { + processTextHierarchyParagraphPrimitive2D( + static_cast<const primitive2d::TextHierarchyParagraphPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D: + { + processTextHierarchyBlockPrimitive2D( + static_cast<const primitive2d::TextHierarchyBlockPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + { + // for supporting TEXT_ MetaFile actions there is more to do here; get the candidate + processTextSimplePortionPrimitive2D( + static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D: + { + processPolygonStrokeArrowPrimitive2D( + static_cast<const primitive2d::PolygonStrokeArrowPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + // direct draw of transformed BitmapEx primitive; use default processing, but without + // former testing if graphic content is inside discrete local viewport; this is not + // setup for metafile targets (metafile renderer tries to render in logic coordinates, + // the mapping is kept to the OutputDevice for better Metafile recording) + RenderBitmapPrimitive2D(static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + { + processPolyPolygonGraphicPrimitive2D( + static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: + { + processPolyPolygonHatchPrimitive2D( + static_cast<const primitive2d::PolyPolygonHatchPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: + { + processPolyPolygonGradientPrimitive2D( + static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + // modified color group. Force output to unified color. Use default processing. + RenderModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + processTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + // use default transform group processing + RenderTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: + { + // new XDrawPage for ViewInformation2D + RenderPagePreviewPrimitive2D( + static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + // use default marker array processing + RenderMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + // use default point array processing + RenderPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D: + { + processStructureTagPrimitive2D( + static_cast<const primitive2d::StructureTagPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_EPSPRIMITIVE2D: + { + RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: + { + 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 + process(rCandidate); + break; + } + } +} + +void VclMetafileProcessor2D::processObjectInfoPrimitive2D( + primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D) +{ + // 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); + } + } + } + + // process content + process(rObjectInfoPrimitive2D.getChildren()); +} + +void VclMetafileProcessor2D::processGraphicPrimitive2D( + const primitive2d::GraphicPrimitive2D& rGraphicPrimitive) +{ + bool bUsingPDFExtOutDevData(false); + basegfx::B2DVector aTranslate, aScale; + static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore + + if (mpPDFExtOutDevData && !bSuppressPDFExtOutDevDataSupport) + { + // emulate data handling from UnoControlPDFExportContact, original see + // svtools/source/graphic/grfmgr.cxx + const Graphic& rGraphic = rGraphicPrimitive.getGraphicObject().GetGraphic(); + + if (rGraphic.IsGfxLink()) + { + const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr(); + + if (!rAttr.IsSpecialDrawMode() && !rAttr.IsAdjusted()) + { + const basegfx::B2DHomMatrix& rTransform = rGraphicPrimitive.getTransform(); + double fRotate, fShearX; + rTransform.decompose(aScale, aTranslate, fRotate, fShearX); + + if (basegfx::fTools::equalZero(fRotate) && (aScale.getX() > 0.0) + && (aScale.getY() > 0.0)) + { + bUsingPDFExtOutDevData = true; + mpPDFExtOutDevData->BeginGroup(); + } + } + } + } + + // process recursively and add MetaFile comment + process(rGraphicPrimitive); + + if (!bUsingPDFExtOutDevData) + return; + + // emulate data handling from UnoControlPDFExportContact, original see + // svtools/source/graphic/grfmgr.cxx + const basegfx::B2DRange aCurrentRange(aTranslate.getX(), aTranslate.getY(), + aTranslate.getX() + aScale.getX(), + aTranslate.getY() + aScale.getY()); + const tools::Rectangle aCurrentRect( + sal_Int32(floor(aCurrentRange.getMinX())), sal_Int32(floor(aCurrentRange.getMinY())), + sal_Int32(ceil(aCurrentRange.getMaxX())), sal_Int32(ceil(aCurrentRange.getMaxY()))); + const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr(); + // fdo#72530 don't pass empty Rectangle to EndGroup + tools::Rectangle aCropRect(aCurrentRect); + + if (rAttr.IsCropped()) + { + // calculate scalings between real image size and logic object size. This + // is necessary since the crop values are relative to original bitmap size + double fFactorX(1.0); + double fFactorY(1.0); + + { + const MapMode aMapMode100thmm(MapUnit::Map100thMM); + const Size aBitmapSize(OutputDevice::LogicToLogic( + rGraphicPrimitive.getGraphicObject().GetPrefSize(), + rGraphicPrimitive.getGraphicObject().GetPrefMapMode(), aMapMode100thmm)); + const double fDivX(aBitmapSize.Width() - rAttr.GetLeftCrop() - rAttr.GetRightCrop()); + const double fDivY(aBitmapSize.Height() - rAttr.GetTopCrop() - rAttr.GetBottomCrop()); + + if (!basegfx::fTools::equalZero(fDivX)) + { + fFactorX = aScale.getX() / fDivX; + } + + if (!basegfx::fTools::equalZero(fDivY)) + { + fFactorY = aScale.getY() / fDivY; + } + } + + // calculate crop range and rect + basegfx::B2DRange aCropRange; + aCropRange.expand( + aCurrentRange.getMinimum() + - basegfx::B2DPoint(rAttr.GetLeftCrop() * fFactorX, rAttr.GetTopCrop() * fFactorY)); + aCropRange.expand( + aCurrentRange.getMaximum() + + basegfx::B2DPoint(rAttr.GetRightCrop() * fFactorX, rAttr.GetBottomCrop() * fFactorY)); + + aCropRect = tools::Rectangle( + sal_Int32(floor(aCropRange.getMinX())), sal_Int32(floor(aCropRange.getMinY())), + sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY()))); + } + + // #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 + mpPDFExtOutDevData->EndGroup(rGraphicPrimitive.getGraphicObject().GetGraphic(), + 255 - rAttr.GetAlpha(), aCropRect, aCurrentRect); +} + +void VclMetafileProcessor2D::processControlPrimitive2D( + const primitive2d::ControlPrimitive2D& rControlPrimitive) +{ + const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl()); + bool bIsPrintableControl(false); + + // find out if control is printable + if (rXControl.is()) + { + try + { + uno::Reference<beans::XPropertySet> xModelProperties(rXControl->getModel(), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertyInfo( + xModelProperties.is() ? xModelProperties->getPropertySetInfo() + : uno::Reference<beans::XPropertySetInfo>()); + static const OUStringLiteral sPrintablePropertyName(u"Printable"); + + if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(sPrintablePropertyName)) + { + OSL_VERIFY(xModelProperties->getPropertyValue(sPrintablePropertyName) + >>= bIsPrintableControl); + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("drawinglayer", + "VclMetafileProcessor2D: No access to printable flag of Control"); + } + } + + // PDF export and printing only for printable controls + if (!bIsPrintableControl) + return; + + const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields()); + bool bDoProcessRecursively(true); + + if (bPDFExport) + { + // 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)); + + if (pPDFControl) + { + // still need to fill in the location (is a class Rectangle) + const basegfx::B2DRange aRangeLogic( + rControlPrimitive.getB2DRange(getViewInformation2D())); + const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aRangeLogic.getMinX())), + static_cast<sal_Int32>(floor(aRangeLogic.getMinY())), + static_cast<sal_Int32>(ceil(aRangeLogic.getMaxX())), + static_cast<sal_Int32>(ceil(aRangeLogic.getMaxY()))); + pPDFControl->Location = aRectLogic; + + Size aFontSize(pPDFControl->TextFont.GetFontSize()); + aFontSize = OutputDevice::LogicToLogic(aFontSize, MapMode(MapUnit::MapPoint), + mpOutputDevice->GetMapMode()); + pPDFControl->TextFont.SetFontSize(aFontSize); + + mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); + mpPDFExtOutDevData->CreateControl(*pPDFControl); + mpPDFExtOutDevData->EndStructureElement(); + + // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject); + // do not process recursively + bDoProcessRecursively = false; + } + else + { + // PDF export did not work, try simple output. + // Fallback to printer output by not setting bDoProcessRecursively + // to false. + } + } + + // #i93169# used flag the wrong way; true means that nothing was done yet + if (bDoProcessRecursively) + { + // printer output + try + { + // remember old graphics and create new + uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW); + const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics()); + const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics()); + + if (xNewGraphics.is()) + { + // link graphics and view + xControlView->setGraphics(xNewGraphics); + + // get position + const basegfx::B2DHomMatrix aObjectToDiscrete( + getViewInformation2D().getObjectToViewTransformation() + * rControlPrimitive.getTransform()); + const basegfx::B2DPoint aTopLeftDiscrete(aObjectToDiscrete + * basegfx::B2DPoint(0.0, 0.0)); + + // draw it + xControlView->draw(basegfx::fround(aTopLeftDiscrete.getX()), + basegfx::fround(aTopLeftDiscrete.getY())); + bDoProcessRecursively = false; + + // restore original graphics + xControlView->setGraphics(xOriginalGraphics); + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("drawinglayer", + "VclMetafileProcessor2D: Printing of Control failed"); + } + } + + // process recursively if not done yet to export as decomposition (bitmap) + if (bDoProcessRecursively) + { + process(rControlPrimitive); + } +} + +void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( + const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive) +{ + // 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"); + OUString aURL; + + switch (rFieldPrimitive.getType()) + { + default: // case drawinglayer::primitive2d::FIELD_TYPE_COMMON : + { + mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon)); + break; + } + case drawinglayer::primitive2d::FIELD_TYPE_PAGE: + { + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField")); + break; + } + case drawinglayer::primitive2d::FIELD_TYPE_URL: + { + aURL = rFieldPrimitive.getValue("URL"); + + if (!aURL.isEmpty()) + { + mpMetaFile->AddAction(new MetaCommentAction( + aCommentStringCommon, 0, reinterpret_cast<const sal_uInt8*>(aURL.getStr()), + 2 * aURL.getLength())); + } + + break; + } + } + + // process recursively + primitive2d::Primitive2DContainer rContent; + rFieldPrimitive.get2DDecomposition(rContent, getViewInformation2D()); + 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")); + + if (!(mpPDFExtOutDevData + && drawinglayer::primitive2d::FIELD_TYPE_URL == rFieldPrimitive.getType())) + return; + + // emulate data handling from ImpEditEngine::Paint + const basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D())); + 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()))); + vcl::PDFExtOutDevBookmarkEntry aBookmark; + 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); +} + +void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D( + const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive) +{ + // process recursively and add MetaFile comment + process(rLinePrimitive); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL")); +} + +void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( + const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive) +{ + // this is a part of list item, start LILabel ( = bullet) + if (mbInListItem) + { + maListElements.push(vcl::PDFWriter::LILabel); + mpPDFExtOutDevData->BeginStructureElement(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")); + + if (mbInListItem) + { + if (maListElements.top() == vcl::PDFWriter::LILabel) + { + maListElements.pop(); + mpPDFExtOutDevData->EndStructureElement(); // end LILabel + mbBulletPresent = true; + } + } +} + +void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( + const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive) +{ + const OString aCommentString("XTEXT_EOP"); + static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore + + if (nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport) + { + // Non-PDF export behaviour (metafile only). + // Process recursively and add MetaFile comment. + process(rParagraphPrimitive); + mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); + return; + } + + if (!mpPDFExtOutDevData->GetIsExportTaggedPDF()) + { + // No Tagged PDF -> Dump as Paragraph + // Emulate data handling from old ImpEditEngine::Paint + mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + + // Process recursively and add MetaFile comment + process(rParagraphPrimitive); + mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); + + // Emulate data handling from ImpEditEngine::Paint + mpPDFExtOutDevData->EndStructureElement(); + return; + } + + // Create Tagged PDF -> deeper tagged data using StructureElements. + // Use OutlineLevel from ParagraphPrimitive, ensure not below -1 what + // means 'not active' + const sal_Int16 nNewOutlineLevel( + std::max(static_cast<sal_Int16>(-1), rParagraphPrimitive.getOutlineLevel())); + + // Do we have a change in OutlineLevel compared to the current one? + if (nNewOutlineLevel != mnCurrentOutlineLevel) + { + if (nNewOutlineLevel > mnCurrentOutlineLevel) + { + // increase List level + for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a) + { + maListElements.push(vcl::PDFWriter::List); + mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::List); + } + } + else // if(nNewOutlineLevel < mnCurrentOutlineLevel) + { + // close list levels below nNewOutlineLevel completely by removing + // list items as well as list tag itself + for (sal_Int16 a(nNewOutlineLevel); a < mnCurrentOutlineLevel; ++a) + { + popList(); // end LBody LI and L + } + + // on nNewOutlineLevel close the previous list item (LBody and LI) + popListItem(); + } + + // Remember new current OutlineLevel + mnCurrentOutlineLevel = nNewOutlineLevel; + } + else // the same list level + { + // close the previous list item (LBody and LI) + popListItem(); + } + + const bool bDumpAsListItem(-1 != mnCurrentOutlineLevel); + + if (bDumpAsListItem) + { + // Dump as ListItem + maListElements.push(vcl::PDFWriter::ListItem); + mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::ListItem); + mbInListItem = true; + } + else + { + // Dump as Paragraph + mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + } + + // Process recursively and add MetaFile comment + process(rParagraphPrimitive); + mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); + + if (bDumpAsListItem) + mbInListItem = false; + else + mpPDFExtOutDevData->EndStructureElement(); // end Paragraph +} + +void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( + const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive) +{ + // add MetaFile comment, process recursively and add MetaFile comment + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN")); + process(rBlockPrimitive); + + if (mnCurrentOutlineLevel >= 0) + { + // end any opened List structure elements (LBody, LI, L) + for (sal_Int16 a(0); a <= mnCurrentOutlineLevel; ++a) + { + popList(); + } + } + + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END")); +} + +void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate) +{ + // Adapt evtl. used special DrawMode + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptTextToFillDrawMode(); + + // this is a 2nd portion of list item + // bullet has been already processed, start LIBody + if (mbInListItem && mbBulletPresent) + { + maListElements.push(vcl::PDFWriter::LIBody); + mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LIBody); + } + + // directdraw of text simple portion; use default processing + RenderTextSimpleOrDecoratedPortionPrimitive2D(rTextCandidate); + + if (mbInListItem && mbBulletPresent) + mbBulletPresent = false; + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); + + // #i101169# if(pTextDecoratedCandidate) + { + // support for TEXT_ MetaFile actions only for decorated texts + if (!mxBreakIterator.is()) + { + uno::Reference<uno::XComponentContext> xContext( + ::comphelper::getProcessComponentContext()); + mxBreakIterator = i18n::BreakIterator::create(xContext); + } + + const OUString& rTxt = rTextCandidate.getText(); + const sal_Int32 nTextLength(rTextCandidate.getTextLength()); // rTxt.getLength()); + + if (nTextLength) + { + const css::lang::Locale& rLocale = rTextCandidate.getLocale(); + const sal_Int32 nTextPosition(rTextCandidate.getTextPosition()); + + sal_Int32 nDone; + sal_Int32 nNextCellBreak(mxBreakIterator->nextCharacters( + rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, + nDone)); + css::i18n::Boundary nNextWordBoundary(mxBreakIterator->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"); + + for (sal_Int32 i(nTextPosition); i < nTextPosition + nTextLength; i++) + { + // create the entries for the respective break positions + if (i == nNextCellBreak) + { + mpMetaFile->AddAction( + new MetaCommentAction(aCommentStringA, i - nTextPosition)); + nNextCellBreak = mxBreakIterator->nextCharacters( + rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + } + if (i == nNextWordBoundary.endPos) + { + mpMetaFile->AddAction( + new MetaCommentAction(aCommentStringB, i - nTextPosition)); + nNextWordBoundary = mxBreakIterator->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); + } + } + } + } +} + +void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive) +{ + const basegfx::B2DPolygon& rBasePolygon = rHairlinePrimitive.getB2DPolygon(); + + if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) + { + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. If there are more, split the polygon in half and call recursively + basegfx::B2DPolygon aLeft, aRight; + splitLinePolygon(rBasePolygon, aLeft, aRight); + rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPLeft( + new primitive2d::PolygonHairlinePrimitive2D(aLeft, rHairlinePrimitive.getBColor())); + rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight( + new primitive2d::PolygonHairlinePrimitive2D(aRight, rHairlinePrimitive.getBColor())); + + processBasePrimitive2D(*xPLeft); + processBasePrimitive2D(*xPRight); + } + else + { + // direct draw of hairline; use default processing + // support SvtGraphicStroke MetaCommentAction + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rHairlinePrimitive.getBColor())); + std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke; + + // #i121267# Not needed, does not give better quality compared with + // the MetaActionType::POLYPOLYGON written by RenderPolygonHairlinePrimitive2D + // below + const bool bSupportSvtGraphicStroke(false); + + if (bSupportSvtGraphicStroke) + { + pSvtGraphicStroke + = impTryToCreateSvtGraphicStroke(rHairlinePrimitive.getB2DPolygon(), &aLineColor, + nullptr, nullptr, nullptr, nullptr); + + impStartSvtGraphicStroke(pSvtGraphicStroke.get()); + } + + RenderPolygonHairlinePrimitive2D(rHairlinePrimitive, false); + + if (bSupportSvtGraphicStroke) + { + impEndSvtGraphicStroke(pSvtGraphicStroke.get()); + } + } +} + +void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive) +{ + const basegfx::B2DPolygon& rBasePolygon = rStrokePrimitive.getB2DPolygon(); + + if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) + { + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. If there are more, split the polygon in half and call recursively + basegfx::B2DPolygon aLeft, aRight; + splitLinePolygon(rBasePolygon, aLeft, aRight); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPLeft( + new primitive2d::PolygonStrokePrimitive2D(aLeft, rStrokePrimitive.getLineAttribute(), + rStrokePrimitive.getStrokeAttribute())); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight( + new primitive2d::PolygonStrokePrimitive2D(aRight, rStrokePrimitive.getLineAttribute(), + rStrokePrimitive.getStrokeAttribute())); + + processBasePrimitive2D(*xPLeft); + processBasePrimitive2D(*xPRight); + } + else + { + mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + + // support SvtGraphicStroke MetaCommentAction + std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke( + rBasePolygon, nullptr, &rStrokePrimitive.getLineAttribute(), + &rStrokePrimitive.getStrokeAttribute(), nullptr, nullptr); + + impStartSvtGraphicStroke(pSvtGraphicStroke.get()); + const attribute::LineAttribute& rLine = rStrokePrimitive.getLineAttribute(); + + // create MetaPolyLineActions, but without LineStyle::Dash + if (basegfx::fTools::more(rLine.getWidth(), 0.0)) + { + const attribute::StrokeAttribute& rStroke = rStrokePrimitive.getStrokeAttribute(); + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLine.getColor())); + mpOutputDevice->SetLineColor(Color(aHairlineColor)); + mpOutputDevice->SetFillColor(); + + // use the transformed line width + LineInfo aLineInfo(LineStyle::Solid, + basegfx::fround(getTransformedLineWidth(rLine.getWidth()))); + aLineInfo.SetLineJoin(rLine.getLineJoin()); + aLineInfo.SetLineCap(rLine.getLineCap()); + + basegfx::B2DPolyPolygon aHairLinePolyPolygon; + if (0.0 == rStroke.getFullDotDashLen()) + { + aHairLinePolyPolygon.append(rBasePolygon); + } + else + { + bool done = false; + const std::vector<double>& array = rStroke.getDotDashArray(); + // The dotdash array should generally have the form + // (<dashLen> <distance>)+ (<dotLen> <distance>)* + // (where (,),+ and * have their regex meaning). + // Find out what the lengths and their counts are. + if (!array.empty() && array.size() % 2 == 0) + { + double dashLen = array[0]; + double distance = array[1]; + int dashCount = 1; + double dotLen = 0; + int dotCount = 0; + size_t pos = 2; + while (pos + 2 <= array.size()) + { + if (array[pos] != dashLen || array[pos + 1] != distance) + break; + ++dashCount; + pos += 2; + } + if (pos + 2 <= array.size() && array[pos + 1] == distance) + { + dotLen = array[pos]; + ++dotCount; + pos += 2; + while (pos + 2 <= array.size()) + { + if (array[pos] != dotLen || array[pos + 1] != distance) + break; + ++dotCount; + pos += 2; + } + } + if (array.size() == pos) + { + aHairLinePolyPolygon.append(rBasePolygon); + // This will be used by setupStrokeAttributes() in cppcanvas. + aLineInfo.SetStyle(LineStyle::Dash); + aLineInfo.SetDashCount(dashCount); + aLineInfo.SetDashLen(getTransformedLineWidth(dashLen)); + aLineInfo.SetDistance(getTransformedLineWidth(distance)); + if (dotCount != 0) + { + aLineInfo.SetDotCount(dotCount); + aLineInfo.SetDotLen(getTransformedLineWidth(dotLen)); + } + done = true; + } + } + if (!done) + { + // LineInfo can hold only limited info about dashing, apply dashing manually + // if LineInfo cannot describe it. That should not happen though. + SAL_WARN("drawinglayer", "dotdash array cannot be converted to LineInfo"); + basegfx::utils::applyLineDashing(rBasePolygon, rStroke.getDotDashArray(), + &aHairLinePolyPolygon, nullptr, + rStroke.getFullDotDashLen()); + } + } + aHairLinePolyPolygon.transform(maCurrentTransformation); + + for (sal_uInt32 a(0); a < aHairLinePolyPolygon.count(); a++) + { + const basegfx::B2DPolygon& aCandidate(aHairLinePolyPolygon.getB2DPolygon(a)); + + if (aCandidate.count() > 1) + { + const tools::Polygon aToolsPolygon(aCandidate); + + mpMetaFile->AddAction(new MetaPolyLineAction(aToolsPolygon, aLineInfo)); + } + } + } + else + { + process(rStrokePrimitive); + } + + impEndSvtGraphicStroke(pSvtGraphicStroke.get()); + + mpOutputDevice->Pop(); + } +} + +void VclMetafileProcessor2D::processPolygonStrokeArrowPrimitive2D( + const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive) +{ + const basegfx::B2DPolygon& rBasePolygon = rStrokeArrowPrimitive.getB2DPolygon(); + + if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) + { + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. If there are more, split the polygon in half and call recursively + basegfx::B2DPolygon aLeft, aRight; + splitLinePolygon(rBasePolygon, aLeft, aRight); + const attribute::LineStartEndAttribute aEmpty; + rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPLeft( + new primitive2d::PolygonStrokeArrowPrimitive2D( + aLeft, rStrokeArrowPrimitive.getLineAttribute(), + rStrokeArrowPrimitive.getStrokeAttribute(), rStrokeArrowPrimitive.getStart(), + aEmpty)); + rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPRight( + new primitive2d::PolygonStrokeArrowPrimitive2D( + aRight, rStrokeArrowPrimitive.getLineAttribute(), + rStrokeArrowPrimitive.getStrokeAttribute(), aEmpty, + rStrokeArrowPrimitive.getEnd())); + + processBasePrimitive2D(*xPLeft); + processBasePrimitive2D(*xPRight); + } + else + { + // support SvtGraphicStroke MetaCommentAction + std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke( + rBasePolygon, nullptr, &rStrokeArrowPrimitive.getLineAttribute(), + &rStrokeArrowPrimitive.getStrokeAttribute(), &rStrokeArrowPrimitive.getStart(), + &rStrokeArrowPrimitive.getEnd()); + + // write LineGeometry start marker + impStartSvtGraphicStroke(pSvtGraphicStroke.get()); + + // #i116162# When B&W is set as DrawMode, DrawModeFlags::WhiteFill is used + // to let all fills be just white; for lines DrawModeFlags::BlackLine is used + // so all line geometry is supposed to get black. Since in the in-between + // stages of line geometry drawing filled polygons are used (e.g. line + // start/ends) it is necessary to change these drawmodes to preserve + // that lines shall be black; thus change DrawModeFlags::WhiteFill to + // DrawModeFlags::BlackFill during line geometry processing to have line geometry + // parts filled black. + const DrawModeFlags nOldDrawMode(mpOutputDevice->GetDrawMode()); + const bool bDrawmodeChange(nOldDrawMode & DrawModeFlags::WhiteFill + && mnSvtGraphicStrokeCount); + + if (bDrawmodeChange) + { + mpOutputDevice->SetDrawMode((nOldDrawMode & ~DrawModeFlags::WhiteFill) + | DrawModeFlags::BlackFill); + } + + // process sub-line geometry (evtl. filled PolyPolygons) + process(rStrokeArrowPrimitive); + + if (bDrawmodeChange) + { + mpOutputDevice->SetDrawMode(nOldDrawMode); + } + + // write LineGeometry end marker + impEndSvtGraphicStroke(pSvtGraphicStroke.get()); + } +} + +void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate) +{ + // 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; + + if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) + { + // #121194# Changed implementation and checked usages of convert to metafile, + // presentation start (uses SvtGraphicFill) and printing. + + // calculate transformation. Get real object size, all values in FillGraphicAttribute + // are relative to the unified object + aLocalPolyPolygon.transform(maCurrentTransformation); + const basegfx::B2DVector aOutlineSize(aLocalPolyPolygon.getB2DRange().getRange()); + + // the scaling needs scale from pixel to logic coordinate system + const attribute::FillGraphicAttribute& rFillGraphicAttribute + = rBitmapCandidate.getFillGraphic(); + const Size aBmpSizePixel(rFillGraphicAttribute.getGraphic().GetSizePixel()); + + // setup transformation like in impgrfll. Multiply with aOutlineSize + // to get from unit coordinates in rFillGraphicAttribute.getGraphicRange() + // to object coordinates with object's top left being at (0,0). Divide + // by pixel size so that scale from pixel to logic will work in SvtGraphicFill. + const basegfx::B2DVector aTransformScale( + rFillGraphicAttribute.getGraphicRange().getRange() + / basegfx::B2DVector(std::max(1.0, double(aBmpSizePixel.Width())), + std::max(1.0, double(aBmpSizePixel.Height()))) + * aOutlineSize); + const basegfx::B2DPoint aTransformPosition( + rFillGraphicAttribute.getGraphicRange().getMinimum() * aOutlineSize); + + // setup transformation like in impgrfll + SvtGraphicFill::Transform aTransform; + + // scale values are divided by bitmap pixel sizes + aTransform.matrix[0] = aTransformScale.getX(); + aTransform.matrix[4] = aTransformScale.getY(); + + // translates are absolute + aTransform.matrix[2] = aTransformPosition.getX(); + aTransform.matrix[5] = aTransformPosition.getY(); + + pSvtGraphicFill.reset(new SvtGraphicFill( + getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd, + SvtGraphicFill::fillTexture, aTransform, rFillGraphicAttribute.getTiling(), + SvtGraphicFill::hatchSingle, Color(), SvtGraphicFill::GradientType::Linear, Color(), + Color(), 0, rFillGraphicAttribute.getGraphic())); + } + + // Do use decomposition; encapsulate with SvtGraphicFill + impStartSvtGraphicFill(pSvtGraphicFill.get()); + process(rBitmapCandidate); + impEndSvtGraphicFill(pSvtGraphicFill.get()); +} + +void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( + const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate) +{ + // need to handle PolyPolygonHatchPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END + const attribute::FillHatchAttribute& rFillHatchAttribute = rHatchCandidate.getFillHatch(); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rHatchCandidate.getB2DPolyPolygon()); + + if (aLocalPolyPolygon.getB2DRange() != rHatchCandidate.getDefinitionRange()) + { + // the range which defines the hatch is different from the range of the + // geometry (used for writer frames). This cannot be done calling vcl, thus use + // decomposition here + process(rHatchCandidate); + return; + } + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + if (rFillHatchAttribute.isFillBackground()) + { + // with fixing #i111954# (see below) the possible background + // fill of a hatched object was lost.Generate a background fill + // primitive and render it + const primitive2d::Primitive2DReference xBackground( + new primitive2d::PolyPolygonColorPrimitive2D(aLocalPolyPolygon, + rHatchCandidate.getBackgroundColor())); + + process(primitive2d::Primitive2DContainer{ xBackground }); + } + + std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; + aLocalPolyPolygon.transform(maCurrentTransformation); + + if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) + { + // re-create a VCL hatch as base data + SvtGraphicFill::HatchType eHatch(SvtGraphicFill::hatchSingle); + + switch (rFillHatchAttribute.getStyle()) + { + default: // attribute::HatchStyle::Single : + { + eHatch = SvtGraphicFill::hatchSingle; + break; + } + case attribute::HatchStyle::Double: + { + eHatch = SvtGraphicFill::hatchDouble; + break; + } + case attribute::HatchStyle::Triple: + { + eHatch = SvtGraphicFill::hatchTriple; + break; + } + } + + SvtGraphicFill::Transform aTransform; + + // scale + aTransform.matrix[0] *= rFillHatchAttribute.getDistance(); + aTransform.matrix[4] *= rFillHatchAttribute.getDistance(); + + // rotate (was never correct in impgrfll anyways, use correct angle now) + aTransform.matrix[0] *= cos(rFillHatchAttribute.getAngle()); + aTransform.matrix[1] *= -sin(rFillHatchAttribute.getAngle()); + aTransform.matrix[3] *= sin(rFillHatchAttribute.getAngle()); + aTransform.matrix[4] *= cos(rFillHatchAttribute.getAngle()); + + pSvtGraphicFill.reset(new SvtGraphicFill( + getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd, + SvtGraphicFill::fillHatch, aTransform, false, eHatch, + Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), + SvtGraphicFill::GradientType::Linear, Color(), Color(), 0, Graphic())); + } + + // Do use decomposition; encapsulate with SvtGraphicFill + impStartSvtGraphicFill(pSvtGraphicFill.get()); + + // #i111954# do NOT use decomposition, but use direct VCL-command + // process(rCandidate.get2DDecomposition(getViewInformation2D())); + const tools::PolyPolygon aToolsPolyPolygon( + basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon)); + const HatchStyle aHatchStyle( + attribute::HatchStyle::Single == rFillHatchAttribute.getStyle() + ? HatchStyle::Single + : attribute::HatchStyle::Double == rFillHatchAttribute.getStyle() ? HatchStyle::Double + : HatchStyle::Triple); + + mpOutputDevice->DrawHatch( + aToolsPolyPolygon, + Hatch(aHatchStyle, + Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), + basegfx::fround(rFillHatchAttribute.getDistance()), + Degree10(basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttribute.getAngle()))))); + + impEndSvtGraphicFill(pSvtGraphicFill.get()); +} + +void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate) +{ + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)) + { + // #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; + } + + basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon()); + + if (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 + process(rGradientCandidate); + return; + } + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + // for support of MetaCommentActions of the form XGRAD_SEQ_BEGIN, XGRAD_SEQ_END + // 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); + aLocalPolyPolygon.transform(maCurrentTransformation); + + // #i82145# ATM VCL printing of gradients using curved shapes does not work, + // i submitted the bug with the given ID to THB. When that task is fixed it is + // necessary to again remove this subdivision since it decreases possible + // printing quality (not even resolution-dependent for now). THB will tell + // me when that task is fixed in the master + const tools::PolyPolygon aToolsPolyPolygon( + getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); + + // XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END support + std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; + + if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) + { + // setup gradient stuff like in impgrfll + SvtGraphicFill::GradientType eGrad(SvtGraphicFill::GradientType::Linear); + + switch (aVCLGradient.GetStyle()) + { + default: // GradientStyle::Linear: + case GradientStyle::Axial: + eGrad = SvtGraphicFill::GradientType::Linear; + break; + case GradientStyle::Radial: + case GradientStyle::Elliptical: + eGrad = SvtGraphicFill::GradientType::Radial; + break; + case GradientStyle::Square: + case GradientStyle::Rect: + eGrad = SvtGraphicFill::GradientType::Rectangular; + break; + } + + pSvtGraphicFill.reset(new SvtGraphicFill( + aToolsPolyPolygon, Color(), 0.0, SvtGraphicFill::fillEvenOdd, + SvtGraphicFill::fillGradient, SvtGraphicFill::Transform(), false, + SvtGraphicFill::hatchSingle, Color(), eGrad, aVCLGradient.GetStartColor(), + aVCLGradient.GetEndColor(), aVCLGradient.GetSteps(), Graphic())); + } + + // call VCL directly; encapsulate with SvtGraphicFill + impStartSvtGraphicFill(pSvtGraphicFill.get()); + mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); + impEndSvtGraphicFill(pSvtGraphicFill.get()); +} + +void VclMetafileProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate) +{ + mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor())); + aLocalPolyPolygon.transform(maCurrentTransformation); + + // set line and fill color + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + + mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + + mpOutputDevice->Pop(); +} + +void VclMetafileProcessor2D::processMaskPrimitive2D( + const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + // mask group. Special handling for MetaFiles. + if (rMaskCandidate.getChildren().empty()) + return; + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (aMask.count()) + { + // prepare new mask polygon and rescue current one + aMask.transform(maCurrentTransformation); + const basegfx::B2DPolyPolygon aLastClipPolyPolygon(maClipPolyPolygon); + + if (maClipPolyPolygon.count()) + { + // there is already a clip polygon set; build clipped union of + // current mask polygon and new one + maClipPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aMask, maClipPolyPolygon, + true, // #i106516# we want the inside of aMask, not the outside + false); + } + else + { + // use mask directly + maClipPolyPolygon = aMask; + } + + if (maClipPolyPolygon.count()) + { + // set VCL clip region; subdivide before conversion to tools polygon. Subdivision necessary (!) + // Removed subdivision and fixed in vcl::Region::ImplPolyPolyRegionToBandRegionFunc() in VCL where + // the ClipRegion is built from the Polygon. An AdaptiveSubdivide on the source polygon was missing there + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->SetClipRegion(vcl::Region(maClipPolyPolygon)); + + // recursively paint content + // #i121267# Only need to process sub-content when clip polygon is *not* empty. + // If it is empty, the clip is empty and there can be nothing inside. + process(rMaskCandidate.getChildren()); + + // restore VCL clip region + mpOutputDevice->Pop(); + } + + // restore to rescued clip polygon + maClipPolyPolygon = aLastClipPolyPolygon; + } + else + { + // no mask, no clipping. recursively paint content + process(rMaskCandidate.getChildren()); + } +} + +void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate) +{ + mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + // for metafile: Need to examine what the pure vcl version is doing here actually + // - uses DrawTransparent with metafile for content and a gradient + // - uses DrawTransparent for single PolyPolygons directly. Can be detected by + // checking the content for single PolyPolygonColorPrimitive2D + const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren(); + + if (!rContent.empty()) + { + if (0.0 == rUniTransparenceCandidate.getTransparence()) + { + // not transparent at all, use content + process(rUniTransparenceCandidate.getChildren()); + } + else if (rUniTransparenceCandidate.getTransparence() > 0.0 + && rUniTransparenceCandidate.getTransparence() < 1.0) + { + // try to identify a single PolyPolygonColorPrimitive2D in the + // content part of the transparence primitive + const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor = nullptr; + static bool bForceToMetafile(false); // loplugin:constvars:ignore + + if (!bForceToMetafile && 1 == rContent.size()) + { + const primitive2d::Primitive2DReference xReference(rContent[0]); + pPoPoColor = dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( + xReference.get()); + } + + // PolyPolygonGradientPrimitive2D, PolyPolygonHatchPrimitive2D and + // PolyPolygonGraphicPrimitive2D are derived from PolyPolygonColorPrimitive2D. + // Check also for correct ID to exclude derived implementations + if (pPoPoColor + && PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D == pPoPoColor->getPrimitive2DID()) + { + // single transparent tools::PolyPolygon identified, use directly + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(pPoPoColor->getBColor())); + basegfx::B2DPolyPolygon aLocalPolyPolygon(pPoPoColor->getB2DPolyPolygon()); + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + // now transform + aLocalPolyPolygon.transform(maCurrentTransformation); + + // set line and fill color + const sal_uInt16 nTransPercentVcl(static_cast<sal_uInt16>( + basegfx::fround(rUniTransparenceCandidate.getTransparence() * 100.0))); + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + + mpOutputDevice->DrawTransparent(tools::PolyPolygon(aLocalPolyPolygon), + nTransPercentVcl); + } + else + { + // save old mfCurrentUnifiedTransparence and set new one + // so that contained SvtGraphicStroke may use the current one + const double fLastCurrentUnifiedTransparence(mfCurrentUnifiedTransparence); + // #i105377# paint the content metafile opaque as the transparency gets + // split of into the gradient below + // mfCurrentUnifiedTransparence = rUniTransparenceCandidate.getTransparence(); + mfCurrentUnifiedTransparence = 0; + + // various content, create content-metafile + GDIMetaFile aContentMetafile; + const tools::Rectangle aPrimitiveRectangle( + impDumpToMetaFile(rContent, aContentMetafile)); + + // restore mfCurrentUnifiedTransparence; it may have been used + // while processing the sub-content in impDumpToMetaFile + mfCurrentUnifiedTransparence = fLastCurrentUnifiedTransparence; + + // create uniform VCL gradient for uniform transparency + Gradient aVCLGradient; + const sal_uInt8 nTransPercentVcl(static_cast<sal_uInt8>( + basegfx::fround(rUniTransparenceCandidate.getTransparence() * 255.0))); + const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); + + aVCLGradient.SetStyle(GradientStyle::Linear); + aVCLGradient.SetStartColor(aTransColor); + aVCLGradient.SetEndColor(aTransColor); + aVCLGradient.SetAngle(0_deg10); + aVCLGradient.SetBorder(0); + aVCLGradient.SetOfsX(0); + aVCLGradient.SetOfsY(0); + aVCLGradient.SetStartIntensity(100); + aVCLGradient.SetEndIntensity(100); + aVCLGradient.SetSteps(2); + + // render it to VCL + mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient); + } + } + } + + mpOutputDevice->Pop(); +} + +void VclMetafileProcessor2D::processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransparenceCandidate) +{ + // for metafile: Need to examine what the pure vcl version is doing here actually + // - uses DrawTransparent with metafile for content and a gradient + // i can detect this here with checking the gradient part for a single + // 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(); + + if (rContent.empty() || rTransparence.empty()) + return; + + // try to identify a single FillGradientPrimitive2D in the + // transparence part of the primitive + const primitive2d::FillGradientPrimitive2D* pFiGradient = nullptr; + static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore + + if (!bForceToBigTransparentVDev && 1 == rTransparence.size()) + { + const primitive2d::Primitive2DReference xReference(rTransparence[0]); + pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>(xReference.get()); + } + + // Check also for correct ID to exclude derived implementations + if (pFiGradient && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D == pFiGradient->getPrimitive2DID()) + { + // various content, create content-metafile + GDIMetaFile aContentMetafile; + const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile)); + + // re-create a VCL-gradient from FillGradientPrimitive2D + Gradient aVCLGradient; + 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)); + + // paint + mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), + BitmapEx(aBmContent, aBmAlpha)); + } + } +} + +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)); + sal_Int32 nPreviousElement(-1); + + if (!rStructureTagCandidate.isTaggedSdrObject()) + { + bTagUsed = false; + } + + if (mpPDFExtOutDevData && bTagUsed) + { + // foreground object: tag as regular structure element + if (!rStructureTagCandidate.isBackground()) + { + if (rStructureTagCandidate.GetAnchorStructureElementId() != -1) + { + auto const nTemp = mpPDFExtOutDevData->GetCurrentStructureElement(); + bool const bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement( + rStructureTagCandidate.GetAnchorStructureElementId()); + if (bSuccess) + { + nPreviousElement = nTemp; + } + else + { + SAL_WARN("drawinglayer", "anchor structure element not found?"); + } + } + mpPDFExtOutDevData->BeginStructureElement(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: + 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: + { + auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect( + basegfx::fround(range.getMinX()), basegfx::fround(range.getMinY()), + basegfx::fround(range.getMaxX()), basegfx::fround(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + break; + } + default: + break; + } + 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); + // any other background object: do not tag + else + assert(false); + } + } + + // process children normally + process(rStructureTagCandidate.getChildren()); + + if (mpPDFExtOutDevData && bTagUsed) + { + // write end tag + mpPDFExtOutDevData->EndStructureElement(); + if (nPreviousElement != -1) + { +#ifndef NDEBUG + bool const bSuccess = +#endif + mpPDFExtOutDevData->SetCurrentStructureElement(nPreviousElement); + assert(bSuccess); + } + } +} + +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)) + { + aViewTransform.scale(fReduceFactor, fReduceFactor); + } + + // 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx new file mode 100644 index 000000000..6b66a160b --- /dev/null +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx @@ -0,0 +1,217 @@ +/* -*- 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 <stack> + +#include "vclprocessor2d.hxx" +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <vcl/pdfextoutdevdata.hxx> // vcl::PDFExtOutDevData support + +class GDIMetaFile; +namespace tools +{ +class Rectangle; +} +class Gradient; +class SvtGraphicFill; +class SvtGraphicStroke; + +namespace drawinglayer::attribute +{ +class FillGradientAttribute; +class LineAttribute; +class StrokeAttribute; +class LineStartEndAttribute; +} + +namespace drawinglayer::primitive2d +{ +class GraphicPrimitive2D; +class ControlPrimitive2D; +class TextHierarchyFieldPrimitive2D; +class TextHierarchyLinePrimitive2D; +class TextHierarchyBulletPrimitive2D; +class TextHierarchyParagraphPrimitive2D; +class TextHierarchyBlockPrimitive2D; +class TextSimplePortionPrimitive2D; +class PolygonHairlinePrimitive2D; +class PolygonStrokePrimitive2D; +class PolygonStrokeArrowPrimitive2D; +class PolyPolygonGraphicPrimitive2D; +class PolyPolygonHatchPrimitive2D; +class PolyPolygonGradientPrimitive2D; +class PolyPolygonColorPrimitive2D; +class MaskPrimitive2D; +class UnifiedTransparencePrimitive2D; +class TransparencePrimitive2D; +class ObjectInfoPrimitive2D; +class StructureTagPrimitive2D; +} + +namespace basegfx +{ +class BColor; +} + +namespace drawinglayer::processor2d +{ +/** VclMetafileProcessor2D class + + This processor derived from VclProcessor2D is the base class for rendering + all fed primitives to a classical VCL-Metafile, including all over the + time grown extra data in comments and PDF exception data creations. Also + printing needs some exception stuff. + + All in all it is needed to emulate the old ::paint output from the old + Drawinglayer as long as exporters and/or filters still use the Metafile + and the extra-data added to it (which can be seen mostly as 'extensions' + or simply as 'hacks'). + */ +class VclMetafileProcessor2D : public VclProcessor2D +{ +private: + tools::Rectangle impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent, + GDIMetaFile& o_rContentMetafile); + void + impConvertFillGradientAttributeToVCLGradient(Gradient& o_rVCLGradient, + const attribute::FillGradientAttribute& rFiGrAtt, + bool bIsTransparenceGradient) const; + void impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill); + void impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill); + std::unique_ptr<SvtGraphicStroke> + impTryToCreateSvtGraphicStroke(const basegfx::B2DPolygon& rB2DPolygon, + const basegfx::BColor* pColor, + const attribute::LineAttribute* pLineAttribute, + const attribute::StrokeAttribute* pStrokeAttribute, + const attribute::LineStartEndAttribute* pStart, + const attribute::LineStartEndAttribute* pEnd); + void impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke); + void impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke); + void popStructureElement(vcl::PDFWriter::StructElement eElem); + void popListItem(); + void popList(); + + void processGraphicPrimitive2D(const primitive2d::GraphicPrimitive2D& rGraphicPrimitive); + void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive); + void processTextHierarchyFieldPrimitive2D( + const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive); + void processTextHierarchyLinePrimitive2D( + const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive); + void processTextHierarchyBulletPrimitive2D( + const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive); + void processTextHierarchyParagraphPrimitive2D( + const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive); + void processTextHierarchyBlockPrimitive2D( + const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive); + void processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate); + void processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive); + void + processPolygonStrokePrimitive2D(const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive); + void processPolygonStrokeArrowPrimitive2D( + const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive); + void processPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate); + void processPolyPolygonHatchPrimitive2D( + const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate); + void processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate); + void processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate); + void processMaskPrimitive2D(const primitive2d::MaskPrimitive2D& rMaskCandidate); + void processUnifiedTransparencePrimitive2D( + 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; + + /// the current clipping tools::PolyPolygon from MaskPrimitive2D + basegfx::B2DPolyPolygon maClipPolyPolygon; + + /// the target MetaFile + GDIMetaFile* mpMetaFile; + + /* do not allow embedding SvtGraphicFills into each other, + use a counter to prevent that + */ + sal_uInt32 mnSvtGraphicFillCount; + + /// same for SvtGraphicStroke + sal_uInt32 mnSvtGraphicStrokeCount; + + /* hold the last unified transparence value to have it handy + on SvtGraphicStroke creation + */ + double mfCurrentUnifiedTransparence; + + /* break iterator support + made static so it only needs to be fetched once, even with many single + constructed VclMetafileProcessor2D. It's still incarnated on demand, + but exists for OOo runtime now by purpose. + */ + static css::uno::Reference<css::i18n::XBreakIterator> mxBreakIterator; + + /* vcl::PDFExtOutDevData support + For the first step, some extra actions at vcl::PDFExtOutDevData need to + be emulated with the VclMetafileProcessor2D. These are potentially temporarily + since PDF export may use PrimitiveSequences one day directly. + */ + vcl::PDFExtOutDevData* mpPDFExtOutDevData; + + // Remember the current OutlineLevel. This is used when tagged PDF export + // is used to create/write valid structured list entries using PDF statements + // like '/L', '/LI', 'LBody' instead of simple '/P' (Paragraph). + // The value -1 means 'no OutlineLevel' and values >= 0 express the level. + sal_Int16 mnCurrentOutlineLevel; + bool mbInListItem; + bool mbBulletPresent; + + 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 + */ + virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) override; + +public: + /// constructor/destructor + VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev); + virtual ~VclMetafileProcessor2D() override; +}; +} // end of namespace processor2d::drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx new file mode 100644 index 000000000..9291c1830 --- /dev/null +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -0,0 +1,1351 @@ +/* -*- 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 "vclpixelprocessor2d.hxx" +#include "vclhelperbufferdevice.hxx" +#include "helperwrongspellrenderer.hxx" +#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 <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/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/glowprimitive2d.hxx> +#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> +#include <drawinglayer/primitive2d/controlprimitive2d.hxx> +#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +#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 <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)) +{ + // prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels + maCurrentTransformation = rViewInformation.getObjectToViewTransformation(); + + // prepare output directly to pixels + mpOutputDevice->Push(vcl::PushFlags::MAPMODE); + mpOutputDevice->SetMapMode(); + + // react on AntiAliasing settings + if (SvtOptionsDrawinglayer::IsAntiAliasing()) + { + mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing | AntialiasingFlags::Enable); + } + else + { + mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing & ~AntialiasingFlags::Enable); + } +} + +VclPixelProcessor2D::~VclPixelProcessor2D() +{ + // restore MapMode + mpOutputDevice->Pop(); + + // restore AntiAliasing + mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing); +} + +void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect( + const drawinglayer::primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency) +{ + if (!rSource.getB2DPolyPolygon().count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + // no geometry, done + return; + } + + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rSource.getBColor())); + + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(), + fTransparency); +} + +bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect( + const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency) +{ + const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon()); + + if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + // no geometry, done + return true; + } + + const basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rSource.getBColor())); + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aLineColor)); + //aLocalPolygon.transform(maCurrentTransformation); + + // try drawing; if it did not work, use standard fallback + return mpOutputDevice->DrawPolyLineDirect(maCurrentTransformation, rLocalPolygon, 0.0, + fTransparency); +} + +bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect( + const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency) +{ + const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon()); + + if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + // no geometry, done + return true; + } + + if (basegfx::B2DLineJoin::NONE == rSource.getLineAttribute().getLineJoin() + && css::drawing::LineCap_BUTT != rSource.getLineAttribute().getLineCap()) + { + // better use decompose to get that combination done for now, see discussion + // at https://bugs.documentfoundation.org/show_bug.cgi?id=130478#c17 and ff + return false; + } + + // MM01: Radically change here - no dismantle/applyLineDashing, + // let that happen low-level at DrawPolyLineDirect implementations + // to open up for buffering and evtl. direct draw with sys-dep + // graphic systems. Check for stroke is in use + const bool bStrokeAttributeNotUsed(rSource.getStrokeAttribute().isDefault() + || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen()); + + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rSource.getLineAttribute().getColor())); + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aLineColor)); + + // MM01 draw direct, hand over dash data if available + return mpOutputDevice->DrawPolyLineDirect( + maCurrentTransformation, rLocalPolygon, + // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline) + rSource.getLineAttribute().getWidth(), fTransparency, + bStrokeAttributeNotUsed ? nullptr : &rSource.getStrokeAttribute().getDotDashArray(), + rSource.getLineAttribute().getLineJoin(), rSource.getLineAttribute().getLineCap(), + 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()) + { + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + { + processWrongSpellPrimitive2D( + static_cast<const primitive2d::WrongSpellPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + { + processTextSimplePortionPrimitive2D( + static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + { + processTextDecoratedPortionPrimitive2D( + static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + // direct draw of transformed BitmapEx primitive + processBitmapPrimitive2D( + static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + { + // direct draw of fillBitmapPrimitive + RenderFillGraphicPrimitive2D( + static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: + { + processPolyPolygonGradientPrimitive2D( + static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + { + // direct draw of bitmap + RenderPolyPolygonGraphicPrimitive2D( + static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: + { + processMetaFilePrimitive2D(rCandidate); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + // mask group. + RenderMaskPrimitive2DPixel( + static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + // modified color group. Force output to unified color. + RenderModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + // sub-transparence group. Draw to VDev first. + RenderTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + // transform group. + RenderTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: + { + // new XDrawPage for ViewInformation2D + RenderPagePreviewPrimitive2D( + static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + // marker array + RenderMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + // point array + RenderPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + { + processControlPrimitive2D( + static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D: + { + processFillHatchPrimitive2D( + static_cast<const primitive2d::FillHatchPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + { + processBackgroundColorPrimitive2D( + 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); + break; + } + case PRIMITIVE2D_ID_EPSPRIMITIVE2D: + { + RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D: + { + RenderSvgLinearAtomPrimitive2D( + static_cast<const primitive2d::SvgLinearAtomPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D: + { + RenderSvgRadialAtomPrimitive2D( + static_cast<const primitive2d::SvgRadialAtomPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D: + { + processBorderLinePrimitive2D( + 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( + static_cast<const drawinglayer::primitive2d::FillGradientPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D: + { + processPatternFillPrimitive2D( + static_cast<const drawinglayer::primitive2d::PatternFillPrimitive2D&>(rCandidate)); + break; + } + default: + { + SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( + rCandidate.getPrimitive2DID())); + // process recursively + process(rCandidate); + break; + } + } +} + +void VclPixelProcessor2D::processWrongSpellPrimitive2D( + const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive) +{ + if (!renderWrongSpellPrimitive2D(rWrongSpellPrimitive, *mpOutputDevice, maCurrentTransformation, + maBColorModifierStack)) + { + // fallback to decomposition (MetaFile) + process(rWrongSpellPrimitive); + } +} + +void VclPixelProcessor2D::processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate) +{ + // Adapt evtl. used special DrawMode + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptTextToFillDrawMode(); + + if (SvtOptionsDrawinglayer::IsRenderSimpleTextDirect()) + { + RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); + } + else + { + process(rCandidate); + } + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); +} + +void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate) +{ + // Adapt evtl. used special DrawMode + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptTextToFillDrawMode(); + + if (SvtOptionsDrawinglayer::IsRenderDecoratedTextDirect()) + { + RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); + } + else + { + process(rCandidate); + } + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); +} + +void VclPixelProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D) +{ + if (tryDrawPolygonHairlinePrimitive2DDirect(rPolygonHairlinePrimitive2D, 0.0)) + { + return; + } + + // direct draw of hairline + RenderPolygonHairlinePrimitive2D(rPolygonHairlinePrimitive2D, true); +} + +void VclPixelProcessor2D::processBitmapPrimitive2D( + const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + // check if graphic content is inside discrete local ViewPort + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); + const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * 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; + } + } + + RenderBitmapPrimitive2D(rBitmapCandidate); +} + +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()); + + if (!aLocalPolyPolygon.count()) + return; + + if (aStartColor == aEndColor) + { + // no gradient at all, draw as polygon in AA and non-AA case + aLocalPolyPolygon.transform(maCurrentTransformation); + mpOutputDevice->SetLineColor(); + mpOutputDevice->SetFillColor(Color(aStartColor)); + mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + } + else + { + // use the primitive decomposition of the metafile + process(rPolygonCandidate); + } +} + +void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D) +{ + // try to use directly + basegfx::B2DPolyPolygon aLocalPolyPolygon; + + tryDrawPolyPolygonColorPrimitive2DDirect(rPolyPolygonColorPrimitive2D, 0.0); + // okay, done. In this case no gaps should have to be repaired, too + + // 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() + && (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::Enable))) + return; + + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor())); + sal_uInt32 nCount(aLocalPolyPolygon.count()); + + if (!nCount) + { + aLocalPolyPolygon = rPolyPolygonColorPrimitive2D.getB2DPolyPolygon(); + aLocalPolyPolygon.transform(maCurrentTransformation); + nCount = aLocalPolyPolygon.count(); + } + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aPolygonColor)); + + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0); + } +} + +void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate) +{ + // Detect if a single PolyPolygonColorPrimitive2D is contained; in that case, + // use the faster OutputDevice::DrawTransparent method + const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren(); + + if (rContent.empty()) + return; + + if (0.0 == rUniTransparenceCandidate.getTransparence()) + { + // not transparent at all, use content + process(rUniTransparenceCandidate.getChildren()); + } + else if (rUniTransparenceCandidate.getTransparence() > 0.0 + && rUniTransparenceCandidate.getTransparence() < 1.0) + { + bool bDrawTransparentUsed(false); + + if (1 == rContent.size()) + { + const primitive2d::BasePrimitive2D* pBasePrimitive = rContent[0].get(); + + switch (pBasePrimitive->getPrimitive2DID()) + { + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + // single transparent tools::PolyPolygon identified, use directly + const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor + = static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( + pBasePrimitive); + SAL_WARN_IF(!pPoPoColor, "drawinglayer", + "OOps, PrimitiveID and PrimitiveType do not match (!)"); + bDrawTransparentUsed = true; + tryDrawPolyPolygonColorPrimitive2DDirect( + *pPoPoColor, rUniTransparenceCandidate.getTransparence()); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + // single transparent PolygonHairlinePrimitive2D identified, use directly + const primitive2d::PolygonHairlinePrimitive2D* pPoHair + = static_cast<const primitive2d::PolygonHairlinePrimitive2D*>( + pBasePrimitive); + SAL_WARN_IF(!pPoHair, "drawinglayer", + "OOps, PrimitiveID and PrimitiveType do not match (!)"); + + // do no tallow by default - problem is that self-overlapping parts of this geometry will + // not be in an all-same transparency but will already alpha-cover themselves with blending. + // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its + // content to be uniformly transparent. + // For hairline the effect is pretty minimal, but still not correct. + bDrawTransparentUsed = false; + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + // single transparent PolygonStrokePrimitive2D identified, use directly + const primitive2d::PolygonStrokePrimitive2D* pPoStroke + = static_cast<const primitive2d::PolygonStrokePrimitive2D*>(pBasePrimitive); + SAL_WARN_IF(!pPoStroke, "drawinglayer", + "OOps, PrimitiveID and PrimitiveType do not match (!)"); + + // do no tallow by default - problem is that self-overlapping parts of this geometry will + // not be in an all-same transparency but will already alpha-cover themselves with blending. + // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its + // content to be uniformly transparent. + // To check, activate and draw a wide transparent self-crossing line/curve + bDrawTransparentUsed = false; + break; + } + default: + SAL_INFO("drawinglayer", + "default case for " << drawinglayer::primitive2d::idToString( + rUniTransparenceCandidate.getPrimitive2DID())); + break; + } + } + + if (!bDrawTransparentUsed) + { + // unified sub-transparence. Draw to VDev first. + RenderUnifiedTransparencePrimitive2D(rUniTransparenceCandidate); + } + } +} + +void VclPixelProcessor2D::processControlPrimitive2D( + const primitive2d::ControlPrimitive2D& rControlPrimitive) +{ + // control primitive + const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl()); + + try + { + // remember old graphics and create new + uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW); + const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics()); + const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics()); + + if (xNewGraphics.is()) + { + // find out if the control is already visualized as a VCL-ChildWindow. If yes, + // it does not need to be painted at all. + uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW); + bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is() + && xControlWindow->isVisible()); + + // tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple + // reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the + // office is in non-layout mode (default for controls at startup). For the common office + // this means that there exists a real VCL-System-Window for the control, so it is *not* + // painted here due to being exactly obscured by that real Window (and creates danger of + // flickering, too). + // Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but + // exactly that would be needed on each client displaying the tiles (what would be hard + // to do but also would have advantages - the clients would have real controls in the + // shape of their target system which could be interacted with...). It is also what the + // office does. + // For now, fallback to just render these controls when Tiled Rendering is active to just + // have them displayed on all clients. + if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive()) + { + // Do force paint when we are in Tiled Renderer and FormControl is 'visible' + bControlIsVisibleAsChildWindow = false; + } + + if (!bControlIsVisibleAsChildWindow) + { + // Needs to be drawn. Link new graphics and view + xControlView->setGraphics(xNewGraphics); + + // get position + const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation + * rControlPrimitive.getTransform()); + const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0)); + + // Do not forget to use the evtl. offsetted origin of the target device, + // e.g. when used with mask/transparence buffer device + const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin()); + xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()), + aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY())); + + // restore original graphics + xControlView->setGraphics(xOriginalGraphics); + } + } + } + catch (const uno::Exception&) + { + // #i116763# removing since there is a good alternative when the xControlView + // is not found and it is allowed to happen + // DBG_UNHANDLED_EXCEPTION(); + + // process recursively and use the decomposition as Bitmap + process(rControlPrimitive); + } +} + +void VclPixelProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D) +{ + // try to use directly + if (tryDrawPolygonStrokePrimitive2DDirect(rPolygonStrokePrimitive2D, 0.0)) + { + return; + } + + // the stroke primitive may be decomposed to filled polygons. To keep + // evtl. set DrawModes aka DrawModeFlags::BlackLine, DrawModeFlags::GrayLine, + // DrawModeFlags::GhostedLine, DrawModeFlags::WhiteLine or DrawModeFlags::SettingsLine + // working, these need to be copied to the corresponding fill modes + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptLineToFillDrawMode(); + + // polygon stroke primitive + + // Lines with 1 and 2 pixel width without AA need special treatment since their visualization + // as filled polygons is geometrically correct but looks wrong since polygon filling avoids + // the right and bottom pixels. The used method evaluates that and takes the correct action, + // including calling recursively with decomposition if line is wide enough + RenderPolygonStrokePrimitive2D(rPolygonStrokePrimitive2D); + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); +} + +void VclPixelProcessor2D::processFillHatchPrimitive2D( + const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive) +{ + if (SvtOptionsDrawinglayer::IsAntiAliasing()) + { + // if AA is used (or ignore smoothing is on), there is no need to smooth + // hatch painting, use decomposition + process(rFillHatchPrimitive); + } + else + { + // without AA, use VCL to draw the hatch. It snaps hatch distances to the next pixel + // and forces hatch distance to be >= 3 pixels to make the hatch display look smoother. + // This is wrong in principle, but looks nicer. This could also be done here directly + // without VCL usage if needed + const attribute::FillHatchAttribute& rFillHatchAttributes + = rFillHatchPrimitive.getFillHatch(); + + // create hatch polygon in range size and discrete coordinates + basegfx::B2DRange aHatchRange(rFillHatchPrimitive.getOutputRange()); + aHatchRange.transform(maCurrentTransformation); + const basegfx::B2DPolygon aHatchPolygon(basegfx::utils::createPolygonFromRect(aHatchRange)); + + if (rFillHatchAttributes.isFillBackground()) + { + // #i111846# background fill is active; draw fill polygon + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor())); + + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aHatchPolygon); + } + + // set hatch line color + const basegfx::BColor aHatchColor( + maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor())); + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aHatchColor)); + + // get hatch style + HatchStyle eHatchStyle(HatchStyle::Single); + + switch (rFillHatchAttributes.getStyle()) + { + default: // HatchStyle::Single + { + break; + } + case attribute::HatchStyle::Double: + { + eHatchStyle = HatchStyle::Double; + break; + } + case attribute::HatchStyle::Triple: + { + eHatchStyle = HatchStyle::Triple; + break; + } + } + + // create hatch + const basegfx::B2DVector aDiscreteDistance( + maCurrentTransformation * basegfx::B2DVector(rFillHatchAttributes.getDistance(), 0.0)); + const sal_uInt32 nDistance(basegfx::fround(aDiscreteDistance.getLength())); + const sal_uInt32 nAngle10( + basegfx::rad2deg<10>(basegfx::fround(rFillHatchAttributes.getAngle()))); + ::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance, + Degree10(nAngle10)); + + // draw hatch using VCL + mpOutputDevice->DrawHatch(::tools::PolyPolygon(::tools::Polygon(aHatchPolygon)), aVCLHatch); + } +} + +void VclPixelProcessor2D::processBackgroundColorPrimitive2D( + const primitive2d::BackgroundColorPrimitive2D& rPrimitive) +{ + // #i98404# Handle directly, especially when AA is active + const AntialiasingFlags nOriginalAA(mpOutputDevice->GetAntialiasing()); + + // switch AA off in all cases + mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing() & ~AntialiasingFlags::Enable); + + // create color for fill + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rPrimitive.getBColor())); + Color aFillColor(aPolygonColor); + aFillColor.SetAlpha(255 - sal_uInt8((rPrimitive.getTransparency() * 255.0) + 0.5)); + mpOutputDevice->SetFillColor(aFillColor); + mpOutputDevice->SetLineColor(); + + // create rectangle for fill + const basegfx::B2DRange& aViewport(getViewInformation2D().getDiscreteViewport()); + const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aViewport.getMinX())), + static_cast<sal_Int32>(floor(aViewport.getMinY())), + static_cast<sal_Int32>(ceil(aViewport.getMaxX())), + static_cast<sal_Int32>(ceil(aViewport.getMaxY()))); + mpOutputDevice->DrawRect(aRectangle); + + // restore AA setting + mpOutputDevice->SetAntialiasing(nOriginalAA); +} + +void VclPixelProcessor2D::processBorderLinePrimitive2D( + const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder) +{ + // Process recursively, but switch off AntiAliasing for + // horizontal/vertical lines (*not* diagonal lines). + // Checked using AntialiasingFlags::PixelSnapHairline instead, + // but with AntiAliasing on the display really is too 'ghosty' when + // using fine stroking. Correct, but 'ghosty'. + + // It has shown that there are quite some problems here: + // - vcl OutDev renderer methods still use fallbacks to paint + // multiple single lines between discrete sizes of < 3.5 what + // looks bad and does not match + // - mix of filled Polygons and Lines is bad when AA switched off + // - Alignment of AA with non-AA may be bad in diverse different + // renderers + // + // Due to these reasons I change the strategy: Always draw AAed, but + // allow fallback to test/check and if needed. The normal case + // where BorderLines will be system-dependently snapped to have at + // least a single discrete width per partial line (there may be up to + // three) works well nowadays due to most renderers moving the AA stuff + // by 0.5 pixels (discrete units) to match well with the non-AAed parts. + // + // Env-Switch for steering this, default is off. + // Enable by setting at all (and to something) + static const char* pSwitchOffAntiAliasingForHorVerBorderlines( + getenv("SAL_SWITCH_OFF_ANTIALIASING_FOR_HOR_VER_BORTDERLINES")); + static bool bSwitchOffAntiAliasingForHorVerBorderlines( + nullptr != pSwitchOffAntiAliasingForHorVerBorderlines); + + if (bSwitchOffAntiAliasingForHorVerBorderlines + && rBorder.isHorizontalOrVertical(getViewInformation2D())) + { + AntialiasingFlags nAntiAliasing = mpOutputDevice->GetAntialiasing(); + mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable); + process(rBorder); + mpOutputDevice->SetAntialiasing(nAntiAliasing); + } + else + { + process(rBorder); + } +} + +void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + // invert primitive (currently only used for HighContrast fallback for selection in SW and SC). + // (Not true, also used at least for the drawing of dragged column and row boundaries in SC.) + // Set OutDev to XOR and switch AA off (XOR does not work with AA) + mpOutputDevice->Push(); + mpOutputDevice->SetRasterOp(RasterOp::Xor); + const AntialiasingFlags nAntiAliasing(mpOutputDevice->GetAntialiasing()); + mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable); + + // process content recursively + process(rCandidate); + + // restore OutDev + mpOutputDevice->Pop(); + mpOutputDevice->SetAntialiasing(nAntiAliasing); +} + +void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + // #i98289# + const bool bForceLineSnap(SvtOptionsDrawinglayer::IsAntiAliasing() + && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete()); + const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing()); + + if (bForceLineSnap) + { + mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline); + } + + process(rCandidate); + + if (bForceLineSnap) + { + mpOutputDevice->SetAntialiasing(nOldAntiAliase); + } +} + +namespace +{ +/* Returns 8-bit alpha mask created from passed mask. + + Negative fErodeDilateRadius values mean erode, positive - dilate. + nTransparency defines minimal transparency level. +*/ +AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double fErodeDilateRadius, + double fBlurRadius, sal_uInt8 nTransparency, + bool bConvertTo1Bit = true) +{ + // 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 ? rMask.CreateMask(COL_WHITE) : rMask); + + // 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.setHeight(aSize.Height() / 2); + aSize.setWidth(aSize.Width() / 2); + } + + // 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) + { + 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()); + + return AlphaMask(mask.GetBitmap()); +} + +drawinglayer::geometry::ViewInformation2D +expandRange(const drawinglayer::geometry::ViewInformation2D& rViewInfo, double nAmount) +{ + basegfx::B2DRange viewport(rViewInfo.getViewport()); + viewport.grow(nAmount); + return { rViewInfo.getObjectTransformation(), + rViewInfo.getViewTransformation(), + viewport, + rViewInfo.getVisualizedPage(), + rViewInfo.getViewTime(), + rViewInfo.getReducedDisplayQuality() }; +} +} + +void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate) +{ + const double nGlowRadius(rCandidate.getGlowRadius()); + // Avoid wrong effect on the cut-off side; so expand by radius + const auto aExpandedViewInfo(expandRange(getViewInformation2D(), nGlowRadius)); + basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo)); + aRange.transform(maCurrentTransformation); + basegfx::B2DVector aGlowRadiusVector(nGlowRadius, 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, false); + if (aBufferDevice.isVisible()) + { + // 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); + + // Limit the bitmap size to the visible area. + basegfx::B2DRange bitmapRange(aRange); + if (!aExpandedViewInfo.getDiscreteViewport().isEmpty()) + bitmapRange.intersect(aExpandedViewInfo.getDiscreteViewport()); + 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 + { + mpOutputDevice = pLastOutputDevice; + } + } + else + SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible"); +} + +void VclPixelProcessor2D::processSoftEdgePrimitive2D( + const primitive2d::SoftEdgePrimitive2D& rCandidate) +{ + const double nRadius(rCandidate.getRadius()); + // Avoid wrong effect on the cut-off side; so expand by diameter + const auto aExpandedViewInfo(expandRange(getViewInformation2D(), nRadius * 2)); + + basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo)); + aRange.transform(maCurrentTransformation); + basegfx::B2DVector aRadiusVector(nRadius, 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, false); + if (aBufferDevice.isVisible()) + { + // 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); + + // Limit the bitmap size to the visible area. + basegfx::B2DRange bitmapRange(aRange); + if (!aExpandedViewInfo.getDiscreteViewport().isEmpty()) + bitmapRange.intersect(aExpandedViewInfo.getDiscreteViewport()); + 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 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); + } + else + { + mpOutputDevice = pLastOutputDevice; + } + } + 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, false); + + 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; + } + + // tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions, + // what should be clear due to being developed to ectend/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 gradent 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 (!rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange())) + { + process(rPrimitive); + return; + } + + GradientStyle eGradientStyle = convertGradientStyle(rFillGradient.getStyle()); + + Gradient aGradient(eGradientStyle, Color(rFillGradient.getStartColor()), + Color(rFillGradient.getEndColor())); + + aGradient.SetAngle(Degree10(static_cast<int>(basegfx::rad2deg<10>(rFillGradient.getAngle())))); + aGradient.SetBorder(rFillGradient.getBorder() * 100); + aGradient.SetOfsX(rFillGradient.getOffsetX() * 100.0); + aGradient.SetOfsY(rFillGradient.getOffsetY() * 100.0); + aGradient.SetSteps(rFillGradient.getSteps()); + + basegfx::B2DRange aOutputRange(rPrimitive.getOutputRange()); + aOutputRange.transform(maCurrentTransformation); + basegfx::B2DRange aFullRange(rPrimitive.getDefinitionRange()); + aFullRange.transform(maCurrentTransformation); + + const tools::Rectangle aOutputRectangle( + std::floor(aOutputRange.getMinX()), std::floor(aOutputRange.getMinY()), + std::ceil(aOutputRange.getMaxX()), std::ceil(aOutputRange.getMaxY())); + const tools::Rectangle aFullRectangle( + std::floor(aFullRange.getMinX()), std::floor(aFullRange.getMinY()), + std::ceil(aFullRange.getMaxX()), std::ceil(aFullRange.getMaxY())); + + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(aOutputRectangle); + mpOutputDevice->DrawGradient(aFullRectangle, aGradient); + mpOutputDevice->Pop(); +} + +void VclPixelProcessor2D::processPatternFillPrimitive2D( + const primitive2d::PatternFillPrimitive2D& rPrimitive) +{ + const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange(); + if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0 + || rReferenceRange.getHeight() <= 0.0) + return; + + basegfx::B2DPolyPolygon aMask = rPrimitive.getMask(); + aMask.transform(maCurrentTransformation); + const basegfx::B2DRange aMaskRange(aMask.getB2DRange()); + + if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0) + return; + + sal_uInt32 nTileWidth, nTileHeight; + rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D()); + if (nTileWidth == 0 || nTileHeight == 0) + return; + BitmapEx aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight); + tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange); + + // Unless smooth edges are needed, simply use clipping. + if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing()) + { + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); + Wallpaper aWallpaper(aTileImage); + aWallpaper.SetColor(COL_TRANSPARENT); + mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper); + mpOutputDevice->Pop(); + return; + } + + impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // if the tile is a single pixel big, just flood fill with that pixel color + if (nTileWidth == 1 && nTileHeight == 1) + { + Color col = aTileImage.GetPixelColor(0, 0); + mpOutputDevice->SetLineColor(col); + mpOutputDevice->SetFillColor(col); + mpOutputDevice->DrawRect(aMaskRect); + } + else + { + Wallpaper aWallpaper(aTileImage); + aWallpaper.SetColor(COL_TRANSPARENT); + mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper); + } + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // draw mask + VirtualDevice& rMask = aBufferDevice.getTransparence(); + rMask.SetLineColor(); + rMask.SetFillColor(COL_BLACK); + rMask.DrawPolyPolygon(aMask); + + // dump buffer to outdev + aBufferDevice.paint(); +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx new file mode 100644 index 000000000..eaf212c8e --- /dev/null +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx @@ -0,0 +1,116 @@ +/* -*- 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 "vclprocessor2d.hxx" +#include <vcl/outdev.hxx> + +#include <memory> + +namespace drawinglayer::primitive2d +{ +class PolyPolygonColorPrimitive2D; +class PolygonHairlinePrimitive2D; +class PolygonStrokePrimitive2D; +class WrongSpellPrimitive2D; +class TextSimplePortionPrimitive; +class BitmapPrimitive2D; +class PolyPolygonGradientPrimitive2D; +class UnifiedTransparencePrimitive2D; +class ControlPrimitive2D; +class PolygonStrokePrimitive2D; +class FillHatchPrimitive2D; +class BackgroundColorPrimitive2D; +class BorderLinePrimitive2D; +class GlowPrimitive2D; +class ShadowPrimitive2D; +class SoftEdgePrimitive2D; +class FillGradientPrimitive2D; +class PatternFillPrimitive2D; +} + +namespace drawinglayer::processor2d +{ +/** VclPixelProcessor2D class + + This processor derived from VclProcessor2D is the base class for rendering + all fed primitives to a VCL Window. It is the currently used renderer + for all VCL editing output from the DrawingLayer. + */ +class VclPixelProcessor2D final : public VclProcessor2D +{ + struct Impl; + std::unique_ptr<Impl> m_pImpl; + + /* the local processor for BasePrimitive2D-Implementation based primitives, + called from the common process()-implementation + */ + virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) override; + + // some helpers to try direct paints (shortcuts) + void tryDrawPolyPolygonColorPrimitive2DDirect( + const primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency); + bool + tryDrawPolygonHairlinePrimitive2DDirect(const primitive2d::PolygonHairlinePrimitive2D& rSource, + double fTransparency); + bool tryDrawPolygonStrokePrimitive2DDirect(const primitive2d::PolygonStrokePrimitive2D& rSource, + double fTransparency); + + void + processWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive); + void processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate); + void processTextDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate); + void processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D); + void processBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate); + void processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate); + void processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D); + void processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate); + void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive); + void processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D); + void processFillHatchPrimitive2D(const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive); + void + processBackgroundColorPrimitive2D(const primitive2d::BackgroundColorPrimitive2D& rPrimitive); + void + 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); + +public: + /// constructor/destructor + VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev, + const basegfx::BColorModifierStack& rInitStack + = basegfx::BColorModifierStack()); + virtual ~VclPixelProcessor2D() override; +}; +} // end of namespace drawinglayer::processor2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx b/drawinglayer/source/processor2d/vclprocessor2d.cxx new file mode 100644 index 000000000..b77c1d9c0 --- /dev/null +++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx @@ -0,0 +1,1480 @@ +/* -*- 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 "vclprocessor2d.hxx" + +#include "getdigitlanguage.hxx" +#include "vclhelperbufferdevice.hxx" +#include <cmath> +#include <comphelper/string.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <tools/debug.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.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> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/textenumsprimitive2d.hxx> +#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +// for support of Title/Description in all apps when embedding pictures +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> +// control support +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> + +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> + +using namespace com::sun::star; + +namespace +{ +sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, + const basegfx::BColor& rColorB, double fDelta, + double fDiscreteUnit) +{ + // use color distance, assume to do every color step + sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0)); + + if (nSteps) + { + // calc discrete length to change color each discrete unit (pixel) + const sal_uInt32 nDistSteps(basegfx::fround(fDelta / fDiscreteUnit)); + + nSteps = std::min(nSteps, nDistSteps); + } + + // reduce quality to 3 discrete units or every 3rd color step for rendering + nSteps /= 2; + + // roughly cut when too big or too small (not full quality, reduce complexity) + nSteps = std::min(nSteps, sal_uInt32(255)); + nSteps = std::max(nSteps, sal_uInt32(1)); + + return nSteps; +} +} + +namespace drawinglayer::processor2d +{ +// rendering support + +// directdraw of text simple portion or decorated portion primitive. When decorated, all the extra +// information is translated to VCL parameters and set at the font. +// Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring +// for VCL) +void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate) +{ + // decompose matrix to have position and size of text + basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rTextCandidate.getTextTransform()); + 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)) + { + // 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 + // apply the negative scales in any form + aFontScaling = basegfx::absolute(aFontScaling); + fRotate += M_PI; + } + + if (basegfx::fTools::more(aFontScaling.getX(), 0.0) + && basegfx::fTools::more(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())); + + // Don't draw fonts without height + if (aFont.GetFontHeight() <= 0) + return; + + // set FillColor Attribute + const Color aFillColor(rTextCandidate.getTextFillColor()); + if (aFillColor != COL_TRANSPARENT) + { + aFont.SetFillColor(aFillColor); + aFont.SetTransparent(false); + } + + // handle additional font attributes + const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr; + if (rTextCandidate.getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) + pTCPP = static_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>( + &rTextCandidate); + + if (pTCPP != nullptr) + { + // set the color of text decorations + const basegfx::BColor aTextlineColor + = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor()); + mpOutputDevice->SetTextLineColor(Color(aTextlineColor)); + + // set Overline attribute + const FontLineStyle eFontOverline( + primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontOverline())); + if (eFontOverline != LINESTYLE_NONE) + { + aFont.SetOverline(eFontOverline); + const basegfx::BColor aOverlineColor + = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor()); + mpOutputDevice->SetOverlineColor(Color(aOverlineColor)); + if (pTCPP->getWordLineMode()) + aFont.SetWordLineMode(true); + } + + // set Underline attribute + const FontLineStyle eFontLineStyle( + primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontUnderline())); + if (eFontLineStyle != LINESTYLE_NONE) + { + aFont.SetUnderline(eFontLineStyle); + if (pTCPP->getWordLineMode()) + aFont.SetWordLineMode(true); + } + + // set Strikeout attribute + const FontStrikeout eFontStrikeout( + primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout())); + + if (eFontStrikeout != STRIKEOUT_NONE) + aFont.SetStrikeout(eFontStrikeout); + + // set EmphasisMark attribute + FontEmphasisMark eFontEmphasisMark = FontEmphasisMark::NONE; + switch (pTCPP->getTextEmphasisMark()) + { + default: + SAL_WARN("drawinglayer", + "Unknown EmphasisMark style " << pTCPP->getTextEmphasisMark()); + [[fallthrough]]; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE: + eFontEmphasisMark = FontEmphasisMark::NONE; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT: + eFontEmphasisMark = FontEmphasisMark::Dot; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE: + eFontEmphasisMark = FontEmphasisMark::Circle; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC: + eFontEmphasisMark = FontEmphasisMark::Disc; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT: + eFontEmphasisMark = FontEmphasisMark::Accent; + break; + } + + if (eFontEmphasisMark != FontEmphasisMark::NONE) + { + DBG_ASSERT((pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()), + "DrawingLayer: Bad EmphasisMark position!"); + if (pTCPP->getEmphasisMarkAbove()) + eFontEmphasisMark |= FontEmphasisMark::PosAbove; + else + eFontEmphasisMark |= FontEmphasisMark::PosBelow; + aFont.SetEmphasisMark(eFontEmphasisMark); + } + + // set Relief attribute + FontRelief eFontRelief = FontRelief::NONE; + switch (pTCPP->getTextRelief()) + { + default: + SAL_WARN("drawinglayer", "Unknown Relief style " << pTCPP->getTextRelief()); + [[fallthrough]]; + case primitive2d::TEXT_RELIEF_NONE: + eFontRelief = FontRelief::NONE; + break; + case primitive2d::TEXT_RELIEF_EMBOSSED: + eFontRelief = FontRelief::Embossed; + break; + case primitive2d::TEXT_RELIEF_ENGRAVED: + eFontRelief = FontRelief::Engraved; + break; + } + + if (eFontRelief != FontRelief::NONE) + aFont.SetRelief(eFontRelief); + + // set Shadow attribute + if (pTCPP->getShadow()) + aFont.SetShadow(true); + } + + // create transformed integer DXArray in view coordinate system + std::vector<sal_Int32> aTransformedDXArray; + + 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()) + { + aTransformedDXArray.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()) + { + vcl::text::ComplexTextLayoutFlags nRTLLayoutMode( + nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong); + nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl + | vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + mpOutputDevice->SetLayoutMode(nRTLLayoutMode); + } + + mpOutputDevice->SetFont(aFont); + mpOutputDevice->SetTextColor(Color(aRGBFontColor)); + + OUString aText(rTextCandidate.getText()); + sal_Int32 nPos = rTextCandidate.getTextPosition(); + sal_Int32 nLen = rTextCandidate.getTextLength(); + + if (rTextCandidate.isFilled()) + { + basegfx::B2DVector aOldFontScaling, aOldTranslate; + double fOldRotate, fOldShearX; + rTextCandidate.getTextTransform().decompose(aOldFontScaling, aOldTranslate, + fOldRotate, fOldShearX); + + tools::Long nWidthToFill = static_cast<tools::Long>( + rTextCandidate.getWidthToFill() * aFontScaling.getX() / aOldFontScaling.getX()); + + tools::Long nWidth = mpOutputDevice->GetTextArray(rTextCandidate.getText(), + &aTransformedDXArray, 0, 1); + sal_Int32 nChars = 2; + if (nWidth) + nChars = nWidthToFill / nWidth; + + OUStringBuffer aFilled(nChars); + comphelper::string::padToLength(aFilled, nChars, aText[0]); + aText = aFilled.makeStringAndClear(); + nPos = 0; + nLen = nChars; + + if (!aTransformedDXArray.empty()) + { + sal_Int32 nDX = aTransformedDXArray[0]; + aTransformedDXArray.resize(nLen); + for (sal_Int32 i = 1; i < nLen; ++i) + aTransformedDXArray[i] = aTransformedDXArray[i - 1] + nDX; + } + } + + if (!aTransformedDXArray.empty()) + { + mpOutputDevice->DrawTextArray(aStartPoint, aText, aTransformedDXArray, nPos, nLen); + } + else + { + mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen); + } + + if (rTextCandidate.getFontAttribute().getRTL()) + { + mpOutputDevice->SetLayoutMode(nOldLayoutMode); + } + + bPrimitiveAccepted = true; + } + } + + if (!bPrimitiveAccepted) + { + // let break down + process(rTextCandidate); + } +} + +// direct draw of hairline +void VclProcessor2D::RenderPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased) +{ + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor())); + mpOutputDevice->SetLineColor(Color(aHairlineColor)); + mpOutputDevice->SetFillColor(); + + basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); + aLocalPolygon.transform(maCurrentTransformation); + + if (bPixelBased && SvtOptionsDrawinglayer::IsAntiAliasing() + && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete()) + { + // #i98289# + // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete + // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since + // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This + // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering. + aLocalPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon); + } + + mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0); +} + +// direct draw of transformed BitmapEx primitive +void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapCandidate.getXBitmap())); + const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rBitmapCandidate.getTransform()); + + if (maBColorModifierStack.count()) + { + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + 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); + + mpOutputDevice->SetFillColor(Color(aModifiedColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aPolygon); + + return; + } + } + + // #122923# do no longer add Alpha channel here; the right place to do this is when really + // the own transformer is used (see OutputDevice::DrawTransformedBitmapEx). + + // draw using OutputDevice'sDrawTransformedBitmapEx + mpOutputDevice->DrawTransformedBitmapEx(aLocalTransform, aBitmapEx); +} + +void VclProcessor2D::RenderFillGraphicPrimitive2D( + 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; + + // transform object range to device coordinates (pixels). Use + // the device transformation for better accuracy + basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale); + aObjectRange.transform(mpOutputDevice->GetViewTransformation()); + + // extract discrete size of object + const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth())); + const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight())); + + // 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); + } + + aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate); + } + + bool bPainted(false); + + if (maBColorModifierStack.count()) + { + // when color modifier, apply to bitmap + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + // 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); + + mpOutputDevice->SetFillColor(Color(aModifiedColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aPolygon); + + bPainted = true; + } + } + + 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); + + nPosX -= nDiff; + nBLeft -= nDiff * nBWidth; + } + + if (nBLeft + nBWidth <= nOLeft) + { + const sal_Int32 nDiff(-nBLeft / nBWidth); + + nPosX += nDiff; + nBLeft += nDiff * nBWidth; + } + + if (nBTop > nOTop) + { + const sal_Int32 nDiff((nBTop / nBHeight) + 1); + + nPosY -= nDiff; + nBTop -= nDiff * nBHeight; + } + + if (nBTop + nBHeight <= nOTop) + { + const sal_Int32 nDiff(-nBTop / nBHeight); + + nPosY += nDiff; + nBTop += nDiff * nBHeight; + } + + // prepare OutDev + const Point aEmptyPoint(0, 0); + 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); + } + } + } + } + } + else + { + // 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); + } + } + } + } + } + } + + if (!bPrimitiveAccepted) + { + // do not accept, use decomposition + process(rFillBitmapCandidate); + } +} + +// direct draw of Graphic +void VclProcessor2D::RenderPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate) +{ + bool bDone(false); + const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon(); + + // #121194# Todo: check if this works + if (!rPolyPolygon.count()) + { + // empty polyPolygon, done + bDone = true; + } + else + { + const attribute::FillGraphicAttribute& rFillGraphicAttribute + = rPolygonCandidate.getFillGraphic(); + + // try to catch cases where the graphic will be color-modified to a single + // color (e.g. shadow) + switch (rFillGraphicAttribute.getGraphic().GetType()) + { + case GraphicType::GdiMetafile: + { + // metafiles are potentially transparent, cannot optimize, not done + break; + } + case GraphicType::Bitmap: + { + if (!rFillGraphicAttribute.getGraphic().IsTransparent() + && !rFillGraphicAttribute.getGraphic().IsAlpha()) + { + // bitmap is not transparent and has no alpha + const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count()); + + if (nBColorModifierStackCount) + { + const basegfx::BColorModifierSharedPtr& rTopmostModifier + = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount + - 1); + const basegfx::BColorModifier_replace* pReplacer + = dynamic_cast<const basegfx::BColorModifier_replace*>( + rTopmostModifier.get()); + + if (pReplacer) + { + // the bitmap fill is in unified color, so we can replace it with + // a single polygon fill. The form of the fill depends on tiling + if (rFillGraphicAttribute.getTiling()) + { + // with tiling, fill the whole tools::PolyPolygon with the modifier color + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon); + + aLocalPolyPolygon.transform(maCurrentTransformation); + mpOutputDevice->SetLineColor(); + mpOutputDevice->SetFillColor(Color(pReplacer->getBColor())); + mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + } + else + { + // without tiling, only the area common to the bitmap tile and the + // tools::PolyPolygon is filled. Create the bitmap tile area in object + // coordinates. For this, the object transformation needs to be created + // from the already scaled PolyPolygon. The tile area in object + // coordinates will always be non-rotated, so it's not necessary to + // work with a polygon here + basegfx::B2DRange aTileRange( + rFillGraphicAttribute.getGraphicRange()); + const basegfx::B2DRange aPolyPolygonRange( + rPolyPolygon.getB2DRange()); + const basegfx::B2DHomMatrix aNewObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aPolyPolygonRange.getRange(), + aPolyPolygonRange.getMinimum())); + + aTileRange.transform(aNewObjectTransform); + + // now clip the object polyPolygon against the tile range + // to get the common area + basegfx::B2DPolyPolygon aTarget + = basegfx::utils::clipPolyPolygonOnRange( + rPolyPolygon, aTileRange, true, false); + + if (aTarget.count()) + { + aTarget.transform(maCurrentTransformation); + mpOutputDevice->SetLineColor(); + mpOutputDevice->SetFillColor(Color(pReplacer->getBColor())); + mpOutputDevice->DrawPolyPolygon(aTarget); + } + } + + // simplified output executed, we are done + bDone = true; + } + } + } + break; + } + default: //GraphicType::NONE, GraphicType::Default + { + // empty graphic, we are done + bDone = true; + break; + } + } + } + + if (!bDone) + { + // use default decomposition + process(rPolygonCandidate); + } +} + +// mask group +void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + if (rMaskCandidate.getChildren().empty()) + return; + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (!aMask.count()) + return; + + aMask.transform(maCurrentTransformation); + + // Unless smooth edges are needed, simply use clipping. + if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing()) + { + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); + process(rMaskCandidate.getChildren()); + mpOutputDevice->Pop(); + return; + } + + const basegfx::B2DRange aRange(basegfx::utils::getRange(aMask)); + impBufferDevice aBufferDevice(*mpOutputDevice, aRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // paint to it + process(rMaskCandidate.getChildren()); + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // draw mask + VirtualDevice& rMask = aBufferDevice.getTransparence(); + rMask.SetLineColor(); + rMask.SetFillColor(COL_BLACK); + rMask.DrawPolyPolygon(aMask); + + // dump buffer to outdev + aBufferDevice.paint(); +} + +// modified color group. Force output to unified color. +void VclProcessor2D::RenderModifiedColorPrimitive2D( + const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate) +{ + if (!rModifiedCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } +} + +// unified sub-transparence. Draw to VDev first. +void VclProcessor2D::RenderUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + return; + + if (0.0 == rTransCandidate.getTransparence()) + { + // no transparence used, so just use the content + process(rTransCandidate.getChildren()); + } + else if (rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0) + { + // transparence is in visible range + basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); + aRange.transform(maCurrentTransformation); + impBufferDevice aBufferDevice(*mpOutputDevice, aRange); + + if (aBufferDevice.isVisible()) + { + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // paint content to it + process(rTransCandidate.getChildren()); + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // dump buffer to outdev using given transparence + aBufferDevice.paint(rTransCandidate.getTransparence()); + } + } +} + +// sub-transparence group. Draw to VDev first. +void VclProcessor2D::RenderTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + return; + + basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); + aRange.transform(maCurrentTransformation); + impBufferDevice aBufferDevice(*mpOutputDevice, aRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // paint content to it + process(rTransCandidate.getChildren()); + + // set to mask + mpOutputDevice = &aBufferDevice.getTransparence(); + + // when painting transparence masks, reset the color stack + basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack); + maBColorModifierStack = basegfx::BColorModifierStack(); + + // paint mask to it (always with transparence intensities, evtl. with AA) + process(rTransCandidate.getTransparence()); + + // back to old color stack + maBColorModifierStack = aLastBColorModifierStack; + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // dump buffer to outdev + aBufferDevice.paint(); +} + +// transform group. +void VclProcessor2D::RenderTransformPrimitive2D( + const primitive2d::TransformPrimitive2D& rTransformCandidate) +{ + // remember current transformation and ViewInformation + const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // 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()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + maCurrentTransformation = aLastCurrentTransformation; + updateViewInformation(aLastViewInformation2D); +} + +// new XDrawPage for ViewInformation2D +void VclProcessor2D::RenderPagePreviewPrimitive2D( + const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate) +{ + // remember current transformation and ViewInformation + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new local ViewInformation2D + const geometry::ViewInformation2D aViewInformation2D( + getViewInformation2D().getObjectTransformation(), + getViewInformation2D().getViewTransformation(), getViewInformation2D().getViewport(), + rPagePreviewCandidate.getXDrawPage(), getViewInformation2D().getViewTime()); + updateViewInformation(aViewInformation2D); + + // process decomposed content + process(rPagePreviewCandidate); + + // restore transformations + updateViewInformation(aLastViewInformation2D); +} + +// marker +void VclProcessor2D::RenderMarkerArrayPrimitive2D( + const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate) +{ + // get data + const std::vector<basegfx::B2DPoint>& rPositions = rMarkArrayCandidate.getPositions(); + const sal_uInt32 nCount(rPositions.size()); + + if (!nCount || rMarkArrayCandidate.getMarker().IsEmpty()) + return; + + // get pixel size + const BitmapEx& rMarker(rMarkArrayCandidate.getMarker()); + const Size aBitmapSize(rMarker.GetSizePixel()); + + if (!(aBitmapSize.Width() && aBitmapSize.Height())) + return; + + // get discrete half size + const basegfx::B2DVector aDiscreteHalfSize((aBitmapSize.getWidth() - 1.0) * 0.5, + (aBitmapSize.getHeight() - 1.0) * 0.5); + const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled()); + + // do not forget evtl. moved origin in target device MapMode when + // switching it off; it would be missing and lead to wrong positions. + // All his could be done using logic sizes and coordinates, too, but + // we want a 1:1 bitmap rendering here, so it's more safe and faster + // to work with switching off MapMode usage completely. + const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin()); + + mpOutputDevice->EnableMapMode(false); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos) + - aDiscreteHalfSize); + const Point aDiscretePoint(basegfx::fround(aDiscreteTopLeft.getX()), + basegfx::fround(aDiscreteTopLeft.getY())); + + mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker); + } + + mpOutputDevice->EnableMapMode(bWasEnabled); +} + +// point +void VclProcessor2D::RenderPointArrayPrimitive2D( + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions = rPointArrayCandidate.getPositions(); + const basegfx::BColor aRGBColor( + maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor())); + const Color aVCLColor(aRGBColor); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos); + const Point aPos(basegfx::fround(aViewPosition.getX()), + basegfx::fround(aViewPosition.getY())); + + mpOutputDevice->DrawPixel(aPos, aVCLColor); + } +} + +void VclProcessor2D::RenderPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate) +{ + // #i101491# method restructured to clearly use the DrawPolyLine + // calls starting from a defined line width + const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute(); + const double fLineWidth(rLineAttribute.getWidth()); + bool bDone(false); + + if (basegfx::fTools::more(fLineWidth, 0.0)) + { + const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation + * basegfx::B2DVector(fLineWidth, 0.0)); + const double fDiscreteLineWidth(aDiscreteUnit.getLength()); + const attribute::StrokeAttribute& rStrokeAttribute + = rPolygonStrokeCandidate.getStrokeAttribute(); + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLineAttribute.getColor())); + basegfx::B2DPolyPolygon aHairlinePolyPolygon; + + mpOutputDevice->SetLineColor(Color(aHairlineColor)); + mpOutputDevice->SetFillColor(); + + if (0.0 == rStrokeAttribute.getFullDotDashLen()) + { + // no line dashing, just copy + aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon()); + } + else + { + // else apply LineStyle + basegfx::utils::applyLineDashing( + rPolygonStrokeCandidate.getB2DPolygon(), rStrokeAttribute.getDotDashArray(), + &aHairlinePolyPolygon, nullptr, rStrokeAttribute.getFullDotDashLen()); + } + + const sal_uInt32 nCount(aHairlinePolyPolygon.count()); + + if (nCount) + { + const bool bAntiAliased(SvtOptionsDrawinglayer::IsAntiAliasing()); + aHairlinePolyPolygon.transform(maCurrentTransformation); + + if (bAntiAliased) + { + if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0)) + { + // line in range ]0.0 .. 1.0[ + // paint as simple hairline + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0); + } + + bDone = true; + } + else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0)) + { + // line in range [1.0 .. 2.0[ + // paint as 2x2 with dynamic line distance + basegfx::B2DHomMatrix aMat; + const double fDistance(fDiscreteLineWidth - 1.0); + const double fHalfDistance(fDistance * 0.5); + + for (sal_uInt32 a(0); a < nCount; a++) + { + basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a)); + + aMat.set(0, 2, -fHalfDistance); + aMat.set(1, 2, -fHalfDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, fDistance); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, 0.0); + aMat.set(1, 2, fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -fDistance); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + } + + bDone = true; + } + else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0)) + { + // line in range [2.0 .. 3.0] + // paint as cross in a 3x3 with dynamic line distance + basegfx::B2DHomMatrix aMat; + const double fDistance((fDiscreteLineWidth - 1.0) * 0.5); + + for (sal_uInt32 a(0); a < nCount; a++) + { + basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a)); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -fDistance); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, fDistance); + aMat.set(1, 2, -fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, fDistance); + aMat.set(1, 2, fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -fDistance); + aMat.set(1, 2, fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + } + + bDone = true; + } + else + { + // #i101491# line width above 3.0 + } + } + else + { + if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5)) + { + // line width below 1.5, draw the basic hairline polygon + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0); + } + + bDone = true; + } + else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5)) + { + // line width is in range ]1.5 .. 2.5], use four hairlines + // drawn in a square + for (sal_uInt32 a(0); a < nCount; a++) + { + basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a)); + basegfx::B2DHomMatrix aMat; + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, 1.0); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, 0.0); + aMat.set(1, 2, 1.0); + aCandidate.transform(aMat); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -1.0); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + } + + bDone = true; + } + else + { + // #i101491# line width is above 2.5 + } + } + + if (!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000) + { + // #i101491# If the polygon complexity uses more than a given amount, do + // use OutputDevice::DrawPolyLine directly; this will avoid buffering all + // decompositions in primitives (memory) and fallback to old line painting + // for very complex polygons, too + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), + fDiscreteLineWidth, rLineAttribute.getLineJoin(), + rLineAttribute.getLineCap(), + rLineAttribute.getMiterMinimumAngle()); + } + + bDone = true; + } + } + } + + if (!bDone) + { + // remember that we enter a PolygonStrokePrimitive2D decomposition, + // used for AA thick line drawing + mnPolygonStrokePrimitive2D++; + + // line width is big enough for standard filled polygon visualisation or zero + process(rPolygonStrokeCandidate); + + // leave PolygonStrokePrimitive2D + mnPolygonStrokePrimitive2D--; + } +} + +void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D) +{ + // The new decomposition of Metafiles made it necessary to add an Eps + // primitive to handle embedded Eps data. On some devices, this can be + // painted directly (mac, printer). + // To be able to handle the replacement correctly, i need to handle it myself + // since DrawEPS will not be able e.g. to rotate the replacement. To be able + // to do that, i added a boolean return to OutputDevice::DrawEPS(..) + // to know when EPS was handled directly already. + basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0); + aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform()); + + if (aRange.isEmpty()) + return; + + const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aRange.getMinX())), + static_cast<sal_Int32>(floor(aRange.getMinY())), + static_cast<sal_Int32>(ceil(aRange.getMaxX())), + static_cast<sal_Int32>(ceil(aRange.getMaxY()))); + + if (aRectangle.IsEmpty()) + return; + + bool bWillReallyRender = mpOutputDevice->IsDeviceOutputNecessary(); + // try to paint EPS directly without fallback visualisation + const bool bEPSPaintedDirectly + = bWillReallyRender + && mpOutputDevice->DrawEPS(aRectangle.TopLeft(), aRectangle.GetSize(), + rEpsPrimitive2D.getGfxLink()); + + if (!bEPSPaintedDirectly) + { + // use the decomposition which will correctly handle the + // fallback visualisation using full transformation (e.g. rotation) + process(rEpsPrimitive2D); + } +} + +void VclProcessor2D::RenderSvgLinearAtomPrimitive2D( + const primitive2d::SvgLinearAtomPrimitive2D& rCandidate) +{ + const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA()); + + if (!basegfx::fTools::more(fDelta, 0.0)) + return; + + const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA())); + const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB())); + + // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2) + const basegfx::B2DVector aDiscreteVector( + getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.0, 1.0)); + 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)); + + // switch off line painting + mpOutputDevice->SetLineColor(); + + // prepare polygon in needed width at start position (with discrete overlap) + const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect( + basegfx::B2DRange(rCandidate.getOffsetA() - fDiscreteUnit, 0.0, + rCandidate.getOffsetA() + (fDelta / nSteps) + fDiscreteUnit, 1.0))); + + // prepare loop ([0.0 .. 1.0[) + double fUnitScale(0.0); + const double fUnitStep(1.0 / nSteps); + + // loop and paint + for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) + { + basegfx::B2DPolygon aNew(aPolygon); + + aNew.transform(maCurrentTransformation + * basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0)); + mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorA, aColorB, fUnitScale))); + mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew)); + } +} + +void VclProcessor2D::RenderSvgRadialAtomPrimitive2D( + const primitive2d::SvgRadialAtomPrimitive2D& rCandidate) +{ + const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA()); + + if (!basegfx::fTools::more(fDeltaScale, 0.0)) + return; + + const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA())); + const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB())); + + // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2) + const basegfx::B2DVector aDiscreteVector( + getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.0, 1.0)); + 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, fDeltaScale, fDiscreteUnit)); + + // switch off line painting + mpOutputDevice->SetLineColor(); + + // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes) + double fUnitScale(0.0); + const double fUnitStep(1.0 / nSteps); + + for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) + { + basegfx::B2DHomMatrix aTransform; + const double fEndScale(rCandidate.getScaleB() - (fDeltaScale * fUnitScale)); + + if (rCandidate.isTranslateSet()) + { + const basegfx::B2DVector aTranslate(basegfx::interpolate( + rCandidate.getTranslateB(), rCandidate.getTranslateA(), fUnitScale)); + + aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix( + fEndScale, fEndScale, aTranslate.getX(), aTranslate.getY()); + } + else + { + aTransform = basegfx::utils::createScaleB2DHomMatrix(fEndScale, fEndScale); + } + + basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle()); + + aNew.transform(maCurrentTransformation * aTransform); + mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorB, aColorA, fUnitScale))); + mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew)); + } +} + +void VclProcessor2D::adaptLineToFillDrawMode() const +{ + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + + if (!(nOriginalDrawMode + & (DrawModeFlags::BlackLine | DrawModeFlags::GrayLine | DrawModeFlags::WhiteLine + | DrawModeFlags::SettingsLine))) + return; + + DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode); + + if (nOriginalDrawMode & DrawModeFlags::BlackLine) + { + nAdaptedDrawMode |= DrawModeFlags::BlackFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::BlackFill; + } + + if (nOriginalDrawMode & DrawModeFlags::GrayLine) + { + nAdaptedDrawMode |= DrawModeFlags::GrayFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::GrayFill; + } + + if (nOriginalDrawMode & DrawModeFlags::WhiteLine) + { + nAdaptedDrawMode |= DrawModeFlags::WhiteFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill; + } + + if (nOriginalDrawMode & DrawModeFlags::SettingsLine) + { + nAdaptedDrawMode |= DrawModeFlags::SettingsFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill; + } + + mpOutputDevice->SetDrawMode(nAdaptedDrawMode); +} + +void VclProcessor2D::adaptTextToFillDrawMode() const +{ + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + if (!(nOriginalDrawMode + & (DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::WhiteText + | DrawModeFlags::SettingsText))) + return; + + DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode); + + if (nOriginalDrawMode & DrawModeFlags::BlackText) + { + nAdaptedDrawMode |= DrawModeFlags::BlackFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::BlackFill; + } + + if (nOriginalDrawMode & DrawModeFlags::GrayText) + { + nAdaptedDrawMode |= DrawModeFlags::GrayFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::GrayFill; + } + + if (nOriginalDrawMode & DrawModeFlags::WhiteText) + { + nAdaptedDrawMode |= DrawModeFlags::WhiteFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill; + } + + if (nOriginalDrawMode & DrawModeFlags::SettingsText) + { + nAdaptedDrawMode |= DrawModeFlags::SettingsFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill; + } + + mpOutputDevice->SetDrawMode(nAdaptedDrawMode); +} + +// process support + +VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev, + const basegfx::BColorModifierStack& rInitStack) + : BaseProcessor2D(rViewInformation) + , mpOutputDevice(&rOutDev) + , maBColorModifierStack(rInitStack) + , mnPolygonStrokePrimitive2D(0) +{ + // set digit language, derived from SvtCTLOptions to have the correct + // number display for arabic/hindi numerals + rOutDev.SetDigitLanguage(drawinglayer::detail::getDigitLanguage()); +} + +VclProcessor2D::~VclProcessor2D() {} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclprocessor2d.hxx b/drawinglayer/source/processor2d/vclprocessor2d.hxx new file mode 100644 index 000000000..1506f481e --- /dev/null +++ b/drawinglayer/source/processor2d/vclprocessor2d.hxx @@ -0,0 +1,119 @@ +/* -*- 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 <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <vcl/vclptr.hxx> + +class OutputDevice; + +namespace drawinglayer::primitive2d +{ +class TextSimplePortionPrimitive2D; +class PolygonHairlinePrimitive2D; +class BitmapPrimitive2D; +class FillGraphicPrimitive2D; +class PolyPolygonGradientPrimitive2D; +class PolyPolygonGraphicPrimitive2D; +class MetafilePrimitive2D; +class MaskPrimitive2D; +class UnifiedTransparencePrimitive2D; +class TransparencePrimitive2D; +class TransformPrimitive2D; +class MarkerArrayPrimitive2D; +class PointArrayPrimitive2D; +class ModifiedColorPrimitive2D; +class PolygonStrokePrimitive2D; +class ControlPrimitive2D; +class PagePreviewPrimitive2D; +class EpsPrimitive2D; +class SvgLinearAtomPrimitive2D; +class SvgRadialAtomPrimitive2D; +} + +namespace drawinglayer::processor2d +{ +/** VclProcessor2D class + + This processor is the base class for VCL-Based processors. It has no + processBasePrimitive2D implementation and thus is not usable directly. + */ +class VclProcessor2D : public BaseProcessor2D +{ +protected: + // the destination OutDev + VclPtr<OutputDevice> mpOutputDevice; + + // the modifiedColorPrimitive stack + basegfx::BColorModifierStack maBColorModifierStack; + + // the current transformation. Since VCL pixel renderer transforms to pixels + // and VCL MetaFile renderer to World (logic) coordinates, the local + // ViewInformation2D cannot directly be used, but needs to be kept up to date + basegfx::B2DHomMatrix maCurrentTransformation; + + // stack value (increment and decrement) to count how deep we are in + // PolygonStrokePrimitive2D's decompositions (normally only one) + sal_uInt32 mnPolygonStrokePrimitive2D; + + // common VCL rendering support + void RenderTextSimpleOrDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate); + void RenderPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased); + void RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate); + void + RenderFillGraphicPrimitive2D(const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate); + void RenderPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate); + void RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate); + void + RenderModifiedColorPrimitive2D(const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate); + void RenderUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate); + void RenderTransparencePrimitive2D(const primitive2d::TransparencePrimitive2D& rTransCandidate); + void RenderTransformPrimitive2D(const primitive2d::TransformPrimitive2D& rTransformCandidate); + void + RenderPagePreviewPrimitive2D(const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate); + void + RenderMarkerArrayPrimitive2D(const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate); + void + RenderPointArrayPrimitive2D(const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate); + void RenderPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate); + void RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D); + void RenderSvgLinearAtomPrimitive2D(const primitive2d::SvgLinearAtomPrimitive2D& rCandidate); + void RenderSvgRadialAtomPrimitive2D(const primitive2d::SvgRadialAtomPrimitive2D& rCandidate); + + // DrawMode adaptation support + void adaptLineToFillDrawMode() const; + void adaptTextToFillDrawMode() const; + +public: + // constructor/destructor + VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev, + const basegfx::BColorModifierStack& rInitStack = basegfx::BColorModifierStack()); + virtual ~VclProcessor2D() override; +}; +} // end of namespace drawinglayer::processor2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor3d/baseprocessor3d.cxx b/drawinglayer/source/processor3d/baseprocessor3d.cxx new file mode 100644 index 000000000..02d437a1a --- /dev/null +++ b/drawinglayer/source/processor3d/baseprocessor3d.cxx @@ -0,0 +1,63 @@ +/* -*- 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/processor3d/baseprocessor3d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor3d +{ + void BaseProcessor3D::processBasePrimitive3D(const primitive3d::BasePrimitive3D& /*rCandidate*/) + { + } + + BaseProcessor3D::BaseProcessor3D(const geometry::ViewInformation3D& rViewInformation) + : maViewInformation3D(rViewInformation) + { + } + + BaseProcessor3D::~BaseProcessor3D() + { + } + + void BaseProcessor3D::process(const primitive3d::Primitive3DContainer& rSource) + { + if(rSource.empty()) + return; + + const size_t nCount(rSource.size()); + + for(size_t a(0); a < nCount; a++) + { + // get reference + const primitive3d::Primitive3DReference xReference(rSource[a]); + + if(xReference.is()) + { + const primitive3d::BasePrimitive3D* pBasePrimitive = static_cast< const primitive3d::BasePrimitive3D* >(xReference.get()); + processBasePrimitive3D(*pBasePrimitive); + } + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor3d/cutfindprocessor3d.cxx b/drawinglayer/source/processor3d/cutfindprocessor3d.cxx new file mode 100644 index 000000000..cb1f737f5 --- /dev/null +++ b/drawinglayer/source/processor3d/cutfindprocessor3d.cxx @@ -0,0 +1,192 @@ +/* -*- 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/processor3d/cutfindprocessor3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/primitive3d/transformprimitive3d.hxx> +#include <primitive3d/hatchtextureprimitive3d.hxx> +#include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <primitive3d/hiddengeometryprimitive3d.hxx> + + +namespace drawinglayer::processor3d +{ + CutFindProcessor::CutFindProcessor(const geometry::ViewInformation3D& rViewInformation, + const basegfx::B3DPoint& rFront, + const basegfx::B3DPoint& rBack, + bool bAnyHit) + : BaseProcessor3D(rViewInformation), + maFront(rFront), + maBack(rBack), + mbAnyHit(bAnyHit) + { + } + + void CutFindProcessor::processBasePrimitive3D(const primitive3d::BasePrimitive3D& rCandidate) + { + if(mbAnyHit && !maResult.empty()) + { + // stop processing as soon as a hit was recognized + return; + } + + // it is a BasePrimitive3D implementation, use getPrimitive3DID() call for switch + switch(rCandidate.getPrimitive3DID()) + { + case PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D : + { + // transform group. + const primitive3d::TransformPrimitive3D& rPrimitive = static_cast< const primitive3d::TransformPrimitive3D& >(rCandidate); + + // remember old and transform front, back to object coordinates + const basegfx::B3DPoint aLastFront(maFront); + const basegfx::B3DPoint aLastBack(maBack); + basegfx::B3DHomMatrix aInverseTrans(rPrimitive.getTransformation()); + aInverseTrans.invert(); + maFront *= aInverseTrans; + maBack *= aInverseTrans; + + // remember current and create new transformation; add new object transform from right side + const geometry::ViewInformation3D aLastViewInformation3D(getViewInformation3D()); + const geometry::ViewInformation3D aNewViewInformation3D( + aLastViewInformation3D.getObjectTransformation() * rPrimitive.getTransformation(), + aLastViewInformation3D.getOrientation(), + aLastViewInformation3D.getProjection(), + aLastViewInformation3D.getDeviceToView(), + aLastViewInformation3D.getViewTime(), + aLastViewInformation3D.getExtendedInformationSequence()); + updateViewInformation(aNewViewInformation3D); + + // #i102956# remember needed back-transform for found cuts (combine from right side) + const basegfx::B3DHomMatrix aLastCombinedTransform(maCombinedTransform); + maCombinedTransform = maCombinedTransform * rPrimitive.getTransformation(); + + // let break down + process(rPrimitive.getChildren()); + + // restore transformations and front, back + maCombinedTransform = aLastCombinedTransform; + updateViewInformation(aLastViewInformation3D); + maFront = aLastFront; + maBack = aLastBack; + break; + } + case PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D : + { + // PolygonHairlinePrimitive3D, not used for hit test with planes, ignore. This + // means that also thick line expansion will not be hit-tested as + // PolyPolygonMaterialPrimitive3D + break; + } + case PRIMITIVE3D_ID_HATCHTEXTUREPRIMITIVE3D : + { + // #i97321# + // For HatchTexturePrimitive3D, do not use the decomposition since it will produce + // clipped hatch lines in 3D. It can be used when the hatch also has a filling, but for + // simplicity, just use the children which are the PolyPolygonMaterialPrimitive3D + // which define the hatched areas anyways; for HitTest this is more than adequate + const primitive3d::HatchTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::HatchTexturePrimitive3D& >(rCandidate); + process(rPrimitive.getChildren()); + break; + } + case PRIMITIVE3D_ID_HIDDENGEOMETRYPRIMITIVE3D : + { + // HiddenGeometryPrimitive3D; the default decomposition would return an empty sequence, + // so force this primitive to process its children directly if the switch is set + // (which is the default). Else, ignore invisible content + const primitive3d::HiddenGeometryPrimitive3D& rHiddenGeometry(static_cast< const primitive3d::HiddenGeometryPrimitive3D& >(rCandidate)); + const primitive3d::Primitive3DContainer& rChildren = rHiddenGeometry.getChildren(); + + if(!rChildren.empty()) + { + process(rChildren); + } + + break; + } + case PRIMITIVE3D_ID_UNIFIEDTRANSPARENCETEXTUREPRIMITIVE3D : + { + const primitive3d::UnifiedTransparenceTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::UnifiedTransparenceTexturePrimitive3D& >(rCandidate); + const primitive3d::Primitive3DContainer& rChildren = rPrimitive.getChildren(); + + if(!rChildren.empty()) + { + process(rChildren); + } + + break; + } + case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D : + { + // PolyPolygonMaterialPrimitive3D + const primitive3d::PolyPolygonMaterialPrimitive3D& rPrimitive = static_cast< const primitive3d::PolyPolygonMaterialPrimitive3D& >(rCandidate); + + if(!maFront.equal(maBack)) + { + const basegfx::B3DPolyPolygon& rPolyPolygon = rPrimitive.getB3DPolyPolygon(); + const sal_uInt32 nPolyCount(rPolyPolygon.count()); + + if(nPolyCount) + { + const basegfx::B3DPolygon& aPolygon(rPolyPolygon.getB3DPolygon(0)); + const sal_uInt32 nPointCount(aPolygon.count()); + + if(nPointCount > 2) + { + const basegfx::B3DVector aPlaneNormal(aPolygon.getNormal()); + + if(!aPlaneNormal.equalZero()) + { + const basegfx::B3DPoint aPointOnPlane(aPolygon.getB3DPoint(0)); + double fCut(0.0); + + if(basegfx::utils::getCutBetweenLineAndPlane(aPlaneNormal, aPointOnPlane, maFront, maBack, fCut)) + { + const basegfx::B3DPoint aCutPoint(basegfx::interpolate(maFront, maBack, fCut)); + + if(basegfx::utils::isInside(rPolyPolygon, aCutPoint)) + { + // #i102956# add result. Do not forget to do this in the coordinate + // system the processor get started with, so use the collected + // combined transformation from processed TransformPrimitive3D's + maResult.push_back(maCombinedTransform * aCutPoint); + } + } + } + } + } + } + + break; + } + default : + { + // process recursively + process(rCandidate.get3DDecomposition(getViewInformation3D())); + break; + } + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor3d/defaultprocessor3d.cxx b/drawinglayer/source/processor3d/defaultprocessor3d.cxx new file mode 100644 index 000000000..b9159c46c --- /dev/null +++ b/drawinglayer/source/processor3d/defaultprocessor3d.cxx @@ -0,0 +1,598 @@ +/* -*- 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 <processor3d/defaultprocessor3d.hxx> +#include <primitive3d/textureprimitive3d.hxx> +#include <texture/texture.hxx> +#include <texture/texture3d.hxx> +#include <primitive3d/hatchtextureprimitive3d.hxx> +#include <drawinglayer/primitive3d/modifiedcolorprimitive3d.hxx> +#include <drawinglayer/primitive3d/polygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <drawinglayer/attribute/materialattribute3d.hxx> +#include <drawinglayer/primitive3d/polypolygonprimitive3d.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <com/sun/star/drawing/ShadeMode.hpp> +#include <drawinglayer/primitive3d/transformprimitive3d.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <vcl/bitmapex.hxx> +#include <drawinglayer/attribute/sdrsceneattribute3d.hxx> +#include <drawinglayer/attribute/sdrlightingattribute3d.hxx> +#include <vcl/graph.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor3d +{ + void DefaultProcessor3D::impRenderGradientTexturePrimitive3D(const primitive3d::GradientTexturePrimitive3D& rPrimitive, bool bTransparence) + { + const primitive3d::Primitive3DContainer& rSubSequence = rPrimitive.getChildren(); + + if(rSubSequence.empty()) + return; + + // rescue values + const bool bOldModulate(getModulate()); mbModulate = rPrimitive.getModulate(); + const bool bOldFilter(getFilter()); mbFilter = rPrimitive.getFilter(); + const bool bOldSimpleTextureActive(getSimpleTextureActive()); + std::shared_ptr< texture::GeoTexSvx > pOldTex = bTransparence ? mpTransparenceGeoTexSvx : mpGeoTexSvx; + + // 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)); + std::shared_ptr< texture::GeoTexSvx > pNewTex; + + if(nMaxSteps) + { + // 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: + { + pNewTex = std::make_shared<texture::GeoTexSvxGradientLinear>( + aOutlineRange, + aOutlineRange, + aStart, + aEnd, + nSteps, + rFillGradient.getBorder(), + rFillGradient.getAngle()); + break; + } + case attribute::GradientStyle::Axial: + { + pNewTex = std::make_shared<texture::GeoTexSvxGradientAxial>( + aOutlineRange, + aOutlineRange, + aStart, + aEnd, + nSteps, + rFillGradient.getBorder(), + rFillGradient.getAngle()); + break; + } + case attribute::GradientStyle::Radial: + { + pNewTex = + std::make_shared<texture::GeoTexSvxGradientRadial>( + aOutlineRange, + aStart, + aEnd, + nSteps, + rFillGradient.getBorder(), + rFillGradient.getOffsetX(), + rFillGradient.getOffsetY()); + break; + } + case attribute::GradientStyle::Elliptical: + { + pNewTex = + std::make_shared<texture::GeoTexSvxGradientElliptical>( + aOutlineRange, + aStart, + aEnd, + nSteps, + rFillGradient.getBorder(), + rFillGradient.getOffsetX(), + rFillGradient.getOffsetY(), + rFillGradient.getAngle()); + break; + } + case attribute::GradientStyle::Square: + { + pNewTex = + std::make_shared<texture::GeoTexSvxGradientSquare>( + aOutlineRange, + aStart, + aEnd, + nSteps, + rFillGradient.getBorder(), + rFillGradient.getOffsetX(), + rFillGradient.getOffsetY(), + rFillGradient.getAngle()); + break; + } + case attribute::GradientStyle::Rect: + { + pNewTex = + std::make_shared<texture::GeoTexSvxGradientRect>( + aOutlineRange, + aStart, + aEnd, + nSteps, + rFillGradient.getBorder(), + rFillGradient.getOffsetX(), + rFillGradient.getOffsetY(), + rFillGradient.getAngle()); + break; + } + } + + mbSimpleTextureActive = false; + } + else + { + // no color distance -> same color, use simple texture + pNewTex = std::make_shared<texture::GeoTexSvxMono>(aStart, 1.0 - aStart.luminance()); + mbSimpleTextureActive = true; + } + + // set created texture + if(bTransparence) + { + mpTransparenceGeoTexSvx = pNewTex; + } + else + { + mpGeoTexSvx = pNewTex; + } + + // process sub-list + process(rSubSequence); + + // restore values + mbModulate = bOldModulate; + mbFilter = bOldFilter; + mbSimpleTextureActive = bOldSimpleTextureActive; + + if(bTransparence) + { + mpTransparenceGeoTexSvx = pOldTex; + } + else + { + mpGeoTexSvx = pOldTex; + } + } + + void DefaultProcessor3D::impRenderHatchTexturePrimitive3D(const primitive3d::HatchTexturePrimitive3D& rPrimitive) + { + const primitive3d::Primitive3DContainer& rSubSequence = rPrimitive.getChildren(); + + if(rSubSequence.empty()) + return; + + // rescue values + const bool bOldModulate(getModulate()); mbModulate = rPrimitive.getModulate(); + const bool bOldFilter(getFilter()); mbFilter = rPrimitive.getFilter(); + std::shared_ptr< texture::GeoTexSvx > pOldTex = mpGeoTexSvx; + + // calculate logic pixel size in object coordinates. Create transformation view + // to object by inverting ObjectToView + basegfx::B3DHomMatrix aInvObjectToView(getViewInformation3D().getObjectToView()); + aInvObjectToView.invert(); + + // back-project discrete coordinates to object coordinates and extract + // maximum distance + const basegfx::B3DPoint aZero(aInvObjectToView * basegfx::B3DPoint(0.0, 0.0, 0.0)); + const basegfx::B3DPoint aOne(aInvObjectToView * basegfx::B3DPoint(1.0, 1.0, 1.0)); + const basegfx::B3DVector aLogicPixel(aOne - aZero); + double fLogicPixelSizeWorld(std::max(std::max(fabs(aLogicPixel.getX()), fabs(aLogicPixel.getY())), fabs(aLogicPixel.getZ()))); + + // calculate logic pixel size in texture coordinates + const double fLogicTexSizeX(fLogicPixelSizeWorld / rPrimitive.getTextureSize().getX()); + const double fLogicTexSizeY(fLogicPixelSizeWorld / rPrimitive.getTextureSize().getY()); + const double fLogicTexSize(std::max(fLogicTexSizeX, fLogicTexSizeY)); + + // create texture and set + mpGeoTexSvx = std::make_shared<texture::GeoTexSvxMultiHatch>(rPrimitive, fLogicTexSize); + + // process sub-list + process(rSubSequence); + + // restore values + mbModulate = bOldModulate; + mbFilter = bOldFilter; + mpGeoTexSvx = pOldTex; + } + + void DefaultProcessor3D::impRenderBitmapTexturePrimitive3D(const primitive3d::BitmapTexturePrimitive3D& rPrimitive) + { + const primitive3d::Primitive3DContainer& rSubSequence = rPrimitive.getChildren(); + + if(rSubSequence.empty()) + return; + + // rescue values + const bool bOldModulate(getModulate()); mbModulate = rPrimitive.getModulate(); + const bool bOldFilter(getFilter()); mbFilter = rPrimitive.getFilter(); + std::shared_ptr< texture::GeoTexSvx > pOldTex = mpGeoTexSvx; + + // create texture + const attribute::FillGraphicAttribute& rFillGraphicAttribute = rPrimitive.getFillGraphicAttribute(); + + // #121194# For 3D texture we will use the BitmapRex representation + const BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx()); + + // create range scaled by texture size + basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange()); + + aGraphicRange.transform( + basegfx::utils::createScaleB2DHomMatrix( + rPrimitive.getTextureSize())); + + if(rFillGraphicAttribute.getTiling()) + { + mpGeoTexSvx = + std::make_shared<texture::GeoTexSvxBitmapExTiled>( + aBitmapEx, + aGraphicRange, + rFillGraphicAttribute.getOffsetX(), + rFillGraphicAttribute.getOffsetY()); + } + else + { + mpGeoTexSvx = + std::make_shared<texture::GeoTexSvxBitmapEx>( + aBitmapEx, + aGraphicRange); + } + + // process sub-list + process(rSubSequence); + + // restore values + mbModulate = bOldModulate; + mbFilter = bOldFilter; + mpGeoTexSvx = pOldTex; + } + + void DefaultProcessor3D::impRenderModifiedColorPrimitive3D(const primitive3d::ModifiedColorPrimitive3D& rModifiedCandidate) + { + const primitive3d::Primitive3DContainer& rSubSequence = rModifiedCandidate.getChildren(); + + if(!rSubSequence.empty()) + { + // put modifier on stack + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + + // process sub-list + process(rModifiedCandidate.getChildren()); + + // remove modifier from stack + maBColorModifierStack.pop(); + } + } + + void DefaultProcessor3D::impRenderPolygonHairlinePrimitive3D(const primitive3d::PolygonHairlinePrimitive3D& rPrimitive) const + { + basegfx::B3DPolygon aHairline(rPrimitive.getB3DPolygon()); + + if(!aHairline.count()) + return; + + // hairlines need no extra data, clear it + aHairline.clearTextureCoordinates(); + aHairline.clearNormals(); + aHairline.clearBColors(); + + // transform to device coordinates (-1.0 .. 1.0) and check for visibility + aHairline.transform(getViewInformation3D().getObjectToView()); + const basegfx::B3DRange a3DRange(basegfx::utils::getRange(aHairline)); + const basegfx::B2DRange a2DRange(a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY()); + + if(a2DRange.overlaps(maRasterRange)) + { + const attribute::MaterialAttribute3D aMaterial(maBColorModifierStack.getModifiedColor(rPrimitive.getBColor())); + + rasterconvertB3DPolygon(aMaterial, aHairline); + } + } + + void DefaultProcessor3D::impRenderPolyPolygonMaterialPrimitive3D(const primitive3d::PolyPolygonMaterialPrimitive3D& rPrimitive) const + { + basegfx::B3DPolyPolygon aFill(rPrimitive.getB3DPolyPolygon()); + basegfx::BColor aObjectColor(rPrimitive.getMaterial().getColor()); + bool bPaintIt(aFill.count()); + + // #i98295# get ShadeMode. Correct early when only flat is possible due to missing normals + const css::drawing::ShadeMode aShadeMode( + aFill.areNormalsUsed() ? + getSdrSceneAttribute().getShadeMode() : css::drawing::ShadeMode_FLAT); + + if(bPaintIt) + { + // get rid of texture coordinates if there is no texture + if(aFill.areTextureCoordinatesUsed() && !getGeoTexSvx() && !getTransparenceGeoTexSvx()) + { + aFill.clearTextureCoordinates(); + } + + // #i98295# get rid of normals and color early when not needed + if(css::drawing::ShadeMode_FLAT == aShadeMode) + { + aFill.clearNormals(); + aFill.clearBColors(); + } + + // transform to device coordinates (-1.0 .. 1.0) and check for visibility + aFill.transform(getViewInformation3D().getObjectToView()); + const basegfx::B3DRange a3DRange(basegfx::utils::getRange(aFill)); + const basegfx::B2DRange a2DRange(a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY()); + + bPaintIt = a2DRange.overlaps(maRasterRange); + } + + // check if it shall be painted regarding hiding of normals (backface culling) + if(bPaintIt && !rPrimitive.getDoubleSided()) + { + // get plane normal of polygon in view coordinates (with ZBuffer values), + // left-handed coordinate system + const basegfx::B3DVector aPlaneNormal(aFill.getB3DPolygon(0).getNormal()); + + if(aPlaneNormal.getZ() > 0.0) + { + bPaintIt = false; + } + } + + if(!bPaintIt) + return; + + // prepare ObjectToEye in NormalTransform + basegfx::B3DHomMatrix aNormalTransform(getViewInformation3D().getOrientation() * getViewInformation3D().getObjectTransformation()); + + if(getSdrSceneAttribute().getTwoSidedLighting()) + { + // get plane normal of polygon in view coordinates (with ZBuffer values), + // left-handed coordinate system + const basegfx::B3DVector aPlaneNormal(aFill.getB3DPolygon(0).getNormal()); + + if(aPlaneNormal.getZ() > 0.0) + { + // mirror normals + aNormalTransform.scale(-1.0, -1.0, -1.0); + } + } + + switch(aShadeMode) + { + case css::drawing::ShadeMode_PHONG: + { + // phong shading. Transform normals to eye coor + aFill.transformNormals(aNormalTransform); + break; + } + case css::drawing::ShadeMode_SMOOTH: + { + // gouraud shading. Transform normals to eye coor + aFill.transformNormals(aNormalTransform); + + // prepare color model parameters, evtl. use blend color + const basegfx::BColor aColor(getModulate() ? basegfx::BColor(1.0, 1.0, 1.0) : rPrimitive.getMaterial().getColor()); + const basegfx::BColor& rSpecular(rPrimitive.getMaterial().getSpecular()); + const basegfx::BColor& rEmission(rPrimitive.getMaterial().getEmission()); + const sal_uInt16 nSpecularIntensity(rPrimitive.getMaterial().getSpecularIntensity()); + + // solve color model for each normal vector, set colors at points. Clear normals. + for(sal_uInt32 a(0); a < aFill.count(); a++) + { + basegfx::B3DPolygon aPartFill(aFill.getB3DPolygon(a)); + + for(sal_uInt32 b(0); b < aPartFill.count(); b++) + { + // solve color model. Transform normal to eye coor + const basegfx::B3DVector aNormal(aPartFill.getNormal(b)); + const basegfx::BColor aSolvedColor(getSdrLightingAttribute().solveColorModel(aNormal, aColor, rSpecular, rEmission, nSpecularIntensity)); + aPartFill.setBColor(b, aSolvedColor); + } + + // clear normals on this part polygon and write it back + aPartFill.clearNormals(); + aFill.setB3DPolygon(a, aPartFill); + } + break; + } + case css::drawing::ShadeMode_FLAT: + { + // flat shading. Get plane vector in eye coordinates + const basegfx::B3DVector aPlaneEyeNormal(aNormalTransform * rPrimitive.getB3DPolyPolygon().getB3DPolygon(0).getNormal()); + + // prepare color model parameters, evtl. use blend color + const basegfx::BColor aColor(getModulate() ? basegfx::BColor(1.0, 1.0, 1.0) : rPrimitive.getMaterial().getColor()); + const basegfx::BColor& rSpecular(rPrimitive.getMaterial().getSpecular()); + const basegfx::BColor& rEmission(rPrimitive.getMaterial().getEmission()); + const sal_uInt16 nSpecularIntensity(rPrimitive.getMaterial().getSpecularIntensity()); + + // solve color model for plane vector and use that color for whole plane + aObjectColor = getSdrLightingAttribute().solveColorModel(aPlaneEyeNormal, aColor, rSpecular, rEmission, nSpecularIntensity); + break; + } + default: // case css::drawing::ShadeMode_DRAFT: + { + // draft, just use object color which is already set. Delete all other infos + aFill.clearNormals(); + aFill.clearBColors(); + break; + } + } + + // draw it to ZBuffer + const attribute::MaterialAttribute3D aMaterial( + maBColorModifierStack.getModifiedColor(aObjectColor), + rPrimitive.getMaterial().getSpecular(), + rPrimitive.getMaterial().getEmission(), + rPrimitive.getMaterial().getSpecularIntensity()); + + rasterconvertB3DPolyPolygon(aMaterial, aFill); + } + + void DefaultProcessor3D::impRenderTransformPrimitive3D(const primitive3d::TransformPrimitive3D& rTransformCandidate) + { + // transform group. Remember current transformations + const geometry::ViewInformation3D aLastViewInformation3D(getViewInformation3D()); + + // create new transformation; add new object transform from right side + const geometry::ViewInformation3D aNewViewInformation3D( + aLastViewInformation3D.getObjectTransformation() * rTransformCandidate.getTransformation(), + aLastViewInformation3D.getOrientation(), + aLastViewInformation3D.getProjection(), + aLastViewInformation3D.getDeviceToView(), + aLastViewInformation3D.getViewTime(), + aLastViewInformation3D.getExtendedInformationSequence()); + updateViewInformation(aNewViewInformation3D); + + // let break down recursively + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation3D); + } + + void DefaultProcessor3D::processBasePrimitive3D(const primitive3d::BasePrimitive3D& rBasePrimitive) + { + // it is a BasePrimitive3D implementation, use getPrimitive3DID() call for switch + switch(rBasePrimitive.getPrimitive3DID()) + { + case PRIMITIVE3D_ID_GRADIENTTEXTUREPRIMITIVE3D : + { + // GradientTexturePrimitive3D + const primitive3d::GradientTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::GradientTexturePrimitive3D& >(rBasePrimitive); + impRenderGradientTexturePrimitive3D(rPrimitive, false); + break; + } + case PRIMITIVE3D_ID_HATCHTEXTUREPRIMITIVE3D : + { + // hatchTexturePrimitive3D + const primitive3d::HatchTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::HatchTexturePrimitive3D& >(rBasePrimitive); + impRenderHatchTexturePrimitive3D(rPrimitive); + break; + } + case PRIMITIVE3D_ID_BITMAPTEXTUREPRIMITIVE3D : + { + // BitmapTexturePrimitive3D + const primitive3d::BitmapTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::BitmapTexturePrimitive3D& >(rBasePrimitive); + impRenderBitmapTexturePrimitive3D(rPrimitive); + break; + } + case PRIMITIVE3D_ID_TRANSPARENCETEXTUREPRIMITIVE3D : + { + // TransparenceTexturePrimitive3D + const primitive3d::TransparenceTexturePrimitive3D& rPrimitive = static_cast< const primitive3d::TransparenceTexturePrimitive3D& >(rBasePrimitive); + mnTransparenceCounter++; + impRenderGradientTexturePrimitive3D(rPrimitive, true); + mnTransparenceCounter--; + break; + } + case PRIMITIVE3D_ID_MODIFIEDCOLORPRIMITIVE3D : + { + // ModifiedColorPrimitive3D + // Force output to unified color. + const primitive3d::ModifiedColorPrimitive3D& rPrimitive = static_cast< const primitive3d::ModifiedColorPrimitive3D& >(rBasePrimitive); + impRenderModifiedColorPrimitive3D(rPrimitive); + break; + } + case PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D : + { + // directdraw of PolygonHairlinePrimitive3D + const primitive3d::PolygonHairlinePrimitive3D& rPrimitive = static_cast< const primitive3d::PolygonHairlinePrimitive3D& >(rBasePrimitive); + impRenderPolygonHairlinePrimitive3D(rPrimitive); + break; + } + case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D : + { + // directdraw of PolyPolygonMaterialPrimitive3D + const primitive3d::PolyPolygonMaterialPrimitive3D& rPrimitive = static_cast< const primitive3d::PolyPolygonMaterialPrimitive3D& >(rBasePrimitive); + impRenderPolyPolygonMaterialPrimitive3D(rPrimitive); + break; + } + case PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D : + { + // transform group (TransformPrimitive3D) + impRenderTransformPrimitive3D(static_cast< const primitive3d::TransformPrimitive3D& >(rBasePrimitive)); + break; + } + default: + { + // process recursively + process(rBasePrimitive.get3DDecomposition(getViewInformation3D())); + break; + } + } + } + + DefaultProcessor3D::DefaultProcessor3D( + const geometry::ViewInformation3D& rViewInformation, + const attribute::SdrSceneAttribute& rSdrSceneAttribute, + const attribute::SdrLightingAttribute& rSdrLightingAttribute) + : BaseProcessor3D(rViewInformation), + mrSdrSceneAttribute(rSdrSceneAttribute), + mrSdrLightingAttribute(rSdrLightingAttribute), + maBColorModifierStack(), + mnTransparenceCounter(0), + mbModulate(false), + mbFilter(false), + mbSimpleTextureActive(false) + { + // a derivation has to set maRasterRange which is used in the basic render methods. + // Setting to default here ([0.0 .. 1.0] in X,Y) to avoid problems + maRasterRange.expand(basegfx::B2DTuple(0.0, 0.0)); + maRasterRange.expand(basegfx::B2DTuple(1.0, 1.0)); + } + + DefaultProcessor3D::~DefaultProcessor3D() + { + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor3d/geometry2dextractor.cxx b/drawinglayer/source/processor3d/geometry2dextractor.cxx new file mode 100644 index 000000000..568496bb9 --- /dev/null +++ b/drawinglayer/source/processor3d/geometry2dextractor.cxx @@ -0,0 +1,153 @@ +/* -*- 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 <processor3d/geometry2dextractor.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/primitive3d/transformprimitive3d.hxx> +#include <drawinglayer/primitive3d/modifiedcolorprimitive3d.hxx> +#include <drawinglayer/primitive3d/polygonprimitive3d.hxx> +#include <basegfx/polygon/b2dpolygontools.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> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor3d +{ + // as tooling, the process() implementation takes over API handling and calls this + // virtual render method when the primitive implementation is BasePrimitive3D-based. + void Geometry2DExtractingProcessor::processBasePrimitive3D(const primitive3d::BasePrimitive3D& rCandidate) + { + // it is a BasePrimitive3D implementation, use getPrimitive3DID() call for switch + switch(rCandidate.getPrimitive3DID()) + { + case PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D : + { + // transform group. Remember current transformations + const primitive3d::TransformPrimitive3D& rPrimitive = static_cast< const primitive3d::TransformPrimitive3D& >(rCandidate); + const geometry::ViewInformation3D aLastViewInformation3D(getViewInformation3D()); + + // create new transformation; add new object transform from right side + const geometry::ViewInformation3D aNewViewInformation3D( + aLastViewInformation3D.getObjectTransformation() * rPrimitive.getTransformation(), + aLastViewInformation3D.getOrientation(), + aLastViewInformation3D.getProjection(), + aLastViewInformation3D.getDeviceToView(), + aLastViewInformation3D.getViewTime(), + aLastViewInformation3D.getExtendedInformationSequence()); + updateViewInformation(aNewViewInformation3D); + + // let break down recursively + process(rPrimitive.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation3D); + break; + } + case PRIMITIVE3D_ID_MODIFIEDCOLORPRIMITIVE3D : + { + // ModifiedColorPrimitive3D; push, process and pop + const primitive3d::ModifiedColorPrimitive3D& rModifiedCandidate = static_cast< const primitive3d::ModifiedColorPrimitive3D& >(rCandidate); + const primitive3d::Primitive3DContainer& rSubSequence = rModifiedCandidate.getChildren(); + + if(!rSubSequence.empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } + break; + } + case PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D : + { + // PolygonHairlinePrimitive3D + const primitive3d::PolygonHairlinePrimitive3D& rPrimitive = static_cast< const primitive3d::PolygonHairlinePrimitive3D& >(rCandidate); + basegfx::B2DPolygon a2DHairline(basegfx::utils::createB2DPolygonFromB3DPolygon(rPrimitive.getB3DPolygon(), getViewInformation3D().getObjectToView())); + + if(a2DHairline.count()) + { + a2DHairline.transform(getObjectTransformation()); + const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(rPrimitive.getBColor())); + const primitive2d::Primitive2DReference xRef(new primitive2d::PolygonHairlinePrimitive2D(a2DHairline, aModifiedColor)); + maPrimitive2DSequence.push_back(xRef); + } + break; + } + case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D : + { + // PolyPolygonMaterialPrimitive3D + const primitive3d::PolyPolygonMaterialPrimitive3D& rPrimitive = static_cast< const primitive3d::PolyPolygonMaterialPrimitive3D& >(rCandidate); + basegfx::B2DPolyPolygon a2DFill(basegfx::utils::createB2DPolyPolygonFromB3DPolyPolygon(rPrimitive.getB3DPolyPolygon(), getViewInformation3D().getObjectToView())); + + if(a2DFill.count()) + { + a2DFill.transform(getObjectTransformation()); + const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(rPrimitive.getMaterial().getColor())); + const primitive2d::Primitive2DReference xRef(new primitive2d::PolyPolygonColorPrimitive2D(a2DFill, aModifiedColor)); + maPrimitive2DSequence.push_back(xRef); + } + break; + } + case PRIMITIVE3D_ID_GRADIENTTEXTUREPRIMITIVE3D : + case PRIMITIVE3D_ID_HATCHTEXTUREPRIMITIVE3D : + case PRIMITIVE3D_ID_BITMAPTEXTUREPRIMITIVE3D : + case PRIMITIVE3D_ID_TRANSPARENCETEXTUREPRIMITIVE3D : + case PRIMITIVE3D_ID_UNIFIEDTRANSPARENCETEXTUREPRIMITIVE3D : + { + // TexturePrimitive3D: Process children, do not try to decompose + const primitive3d::TexturePrimitive3D& rTexturePrimitive = static_cast< const primitive3d::TexturePrimitive3D& >(rCandidate); + const primitive3d::Primitive3DContainer& aChildren(rTexturePrimitive.getChildren()); + + if(!aChildren.empty()) + { + process(aChildren); + } + break; + } + case PRIMITIVE3D_ID_SHADOWPRIMITIVE3D : + { + // accept but ignore labels and shadow; these should be extracted separately + break; + } + default : + { + // process recursively + process(rCandidate.get3DDecomposition(getViewInformation3D())); + break; + } + } + } + + Geometry2DExtractingProcessor::Geometry2DExtractingProcessor( + const geometry::ViewInformation3D& rViewInformation, + const basegfx::B2DHomMatrix& rObjectTransformation) + : BaseProcessor3D(rViewInformation), + maObjectTransformation(rObjectTransformation), + maBColorModifierStack() + { + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor3d/shadow3dextractor.cxx b/drawinglayer/source/processor3d/shadow3dextractor.cxx new file mode 100644 index 000000000..91fc57aae --- /dev/null +++ b/drawinglayer/source/processor3d/shadow3dextractor.cxx @@ -0,0 +1,297 @@ +/* -*- 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 <processor3d/shadow3dextractor.hxx> +#include <primitive3d/shadowprimitive3d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive3d/transformprimitive3d.hxx> +#include <drawinglayer/primitive3d/polygonprimitive3d.hxx> +#include <basegfx/polygon/b2dpolygontools.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 <rtl/ref.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor3d +{ + // as tooling, the process() implementation takes over API handling and calls this + // virtual render method when the primitive implementation is BasePrimitive3D-based. + void Shadow3DExtractingProcessor::processBasePrimitive3D(const primitive3d::BasePrimitive3D& rCandidate) + { + // it is a BasePrimitive3D implementation, use getPrimitive3DID() call for switch + switch(rCandidate.getPrimitive3DID()) + { + case PRIMITIVE3D_ID_SHADOWPRIMITIVE3D : + { + // shadow3d object. Call recursive with content and start conversion + const primitive3d::ShadowPrimitive3D& rPrimitive = static_cast< const primitive3d::ShadowPrimitive3D& >(rCandidate); + + // set new target + primitive2d::Primitive2DContainer aNewSubList; + primitive2d::Primitive2DContainer* pLastTargetSequence = mpPrimitive2DSequence; + mpPrimitive2DSequence = &aNewSubList; + + // activate convert + const bool bLastConvert(mbConvert); + mbConvert = true; + + // set projection flag + const bool bLastUseProjection(mbUseProjection); + mbUseProjection = rPrimitive.getShadow3D(); + + // process content + process(rPrimitive.getChildren()); + + // restore values + mbUseProjection = bLastUseProjection; + mbConvert = bLastConvert; + mpPrimitive2DSequence = pLastTargetSequence; + + // create 2d shadow primitive with result. This also fetches all entries + // from aNewSubList, so there is no need to delete them + rtl::Reference<primitive2d::BasePrimitive2D> pNew = new primitive2d::ShadowPrimitive2D( + rPrimitive.getShadowTransform(), + rPrimitive.getShadowColor(), + 0, // shadow3d doesn't have rPrimitive.getShadowBlur() yet. + std::move(aNewSubList)); + + if(basegfx::fTools::more(rPrimitive.getShadowTransparence(), 0.0)) + { + // create simpleTransparencePrimitive, add created primitives + primitive2d::Primitive2DContainer aNewTransPrimitiveVector { pNew }; + + pNew = new primitive2d::UnifiedTransparencePrimitive2D( + std::move(aNewTransPrimitiveVector), + rPrimitive.getShadowTransparence()); + } + + mpPrimitive2DSequence->push_back(pNew); + + break; + } + case PRIMITIVE3D_ID_TRANSFORMPRIMITIVE3D : + { + // transform group. Remember current transformations + const primitive3d::TransformPrimitive3D& rPrimitive = static_cast< const primitive3d::TransformPrimitive3D& >(rCandidate); + const geometry::ViewInformation3D aLastViewInformation3D(getViewInformation3D()); + + // create new transformation; add new object transform from right side + const geometry::ViewInformation3D aNewViewInformation3D( + aLastViewInformation3D.getObjectTransformation() * rPrimitive.getTransformation(), + aLastViewInformation3D.getOrientation(), + aLastViewInformation3D.getProjection(), + aLastViewInformation3D.getDeviceToView(), + aLastViewInformation3D.getViewTime(), + aLastViewInformation3D.getExtendedInformationSequence()); + updateViewInformation(aNewViewInformation3D); + + if(mbShadowProjectionIsValid) + { + // update buffered WorldToEye and EyeToView + maWorldToEye = getViewInformation3D().getOrientation() * getViewInformation3D().getObjectTransformation(); + maEyeToView = getViewInformation3D().getDeviceToView() * getViewInformation3D().getProjection(); + } + + // let break down + process(rPrimitive.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation3D); + + if(mbShadowProjectionIsValid) + { + // update buffered WorldToEye and EyeToView + maWorldToEye = getViewInformation3D().getOrientation() * getViewInformation3D().getObjectTransformation(); + maEyeToView = getViewInformation3D().getDeviceToView() * getViewInformation3D().getProjection(); + } + break; + } + case PRIMITIVE3D_ID_POLYGONHAIRLINEPRIMITIVE3D : + { + // PolygonHairlinePrimitive3D + if(mbConvert) + { + const primitive3d::PolygonHairlinePrimitive3D& rPrimitive = static_cast< const primitive3d::PolygonHairlinePrimitive3D& >(rCandidate); + basegfx::B2DPolygon a2DHairline; + + if(mbUseProjection) + { + if(mbShadowProjectionIsValid) + { + a2DHairline = impDoShadowProjection(rPrimitive.getB3DPolygon()); + } + } + else + { + a2DHairline = basegfx::utils::createB2DPolygonFromB3DPolygon(rPrimitive.getB3DPolygon(), getViewInformation3D().getObjectToView()); + } + + if(a2DHairline.count()) + { + a2DHairline.transform(getObjectTransformation()); + mpPrimitive2DSequence->push_back( + new primitive2d::PolygonHairlinePrimitive2D( + a2DHairline, + basegfx::BColor())); + } + } + break; + } + case PRIMITIVE3D_ID_POLYPOLYGONMATERIALPRIMITIVE3D : + { + // PolyPolygonMaterialPrimitive3D + if(mbConvert) + { + const primitive3d::PolyPolygonMaterialPrimitive3D& rPrimitive = static_cast< const primitive3d::PolyPolygonMaterialPrimitive3D& >(rCandidate); + basegfx::B2DPolyPolygon a2DFill; + + if(mbUseProjection) + { + if(mbShadowProjectionIsValid) + { + a2DFill = impDoShadowProjection(rPrimitive.getB3DPolyPolygon()); + } + } + else + { + a2DFill = basegfx::utils::createB2DPolyPolygonFromB3DPolyPolygon(rPrimitive.getB3DPolyPolygon(), getViewInformation3D().getObjectToView()); + } + + if(a2DFill.count()) + { + a2DFill.transform(getObjectTransformation()); + mpPrimitive2DSequence->push_back( + new primitive2d::PolyPolygonColorPrimitive2D( + a2DFill, + basegfx::BColor())); + } + } + break; + } + default : + { + // process recursively + process(rCandidate.get3DDecomposition(getViewInformation3D())); + break; + } + } + } + + Shadow3DExtractingProcessor::Shadow3DExtractingProcessor( + const geometry::ViewInformation3D& rViewInformation, + const basegfx::B2DHomMatrix& rObjectTransformation, + const basegfx::B3DVector& rLightNormal, + double fShadowSlant, + const basegfx::B3DRange& rContained3DRange) + : BaseProcessor3D(rViewInformation), + mpPrimitive2DSequence(&maPrimitive2DSequence), + maObjectTransformation(rObjectTransformation), + maLightNormal(rLightNormal), + mfLightPlaneScalar(0.0), + mbShadowProjectionIsValid(false), + mbConvert(false), + mbUseProjection(false) + { + // normalize light normal, get and normalize shadow plane normal and calculate scalar from it + maLightNormal.normalize(); + maShadowPlaneNormal = basegfx::B3DVector(0.0, sin(fShadowSlant), cos(fShadowSlant)); + maShadowPlaneNormal.normalize(); + mfLightPlaneScalar = maLightNormal.scalar(maShadowPlaneNormal); + + // use only when scalar is > 0.0, so the light is in front of the object + if(!basegfx::fTools::more(mfLightPlaneScalar, 0.0)) + return; + + // prepare buffered WorldToEye and EyeToView + maWorldToEye = getViewInformation3D().getOrientation() * getViewInformation3D().getObjectTransformation(); + maEyeToView = getViewInformation3D().getDeviceToView() * getViewInformation3D().getProjection(); + + // calculate range to get front edge around which to rotate the shadow's projection + basegfx::B3DRange aContained3DRange(rContained3DRange); + aContained3DRange.transform(getWorldToEye()); + maPlanePoint.setX(maShadowPlaneNormal.getX() < 0.0 ? aContained3DRange.getMinX() : aContained3DRange.getMaxX()); + maPlanePoint.setY(maShadowPlaneNormal.getY() > 0.0 ? aContained3DRange.getMinY() : aContained3DRange.getMaxY()); + maPlanePoint.setZ(aContained3DRange.getMinZ() - (aContained3DRange.getDepth() / 8.0)); + + // set flag that shadow projection is prepared and allowed + mbShadowProjectionIsValid = true; + } + + Shadow3DExtractingProcessor::~Shadow3DExtractingProcessor() + { + OSL_ENSURE(maPrimitive2DSequence.empty(), + "OOps, someone used Shadow3DExtractingProcessor, but did not fetch the results (!)"); + } + + basegfx::B2DPolygon Shadow3DExtractingProcessor::impDoShadowProjection(const basegfx::B3DPolygon& rSource) + { + basegfx::B2DPolygon aRetval; + + for(sal_uInt32 a(0); a < rSource.count(); a++) + { + // get point, transform to eye coordinate system + basegfx::B3DPoint aCandidate(rSource.getB3DPoint(a)); + aCandidate *= getWorldToEye(); + + // we are in eye coordinates + // ray is (aCandidate + fCut * maLightNormal) + // plane is (maPlanePoint, maShadowPlaneNormal) + // maLightNormal.scalar(maShadowPlaneNormal) is already in mfLightPlaneScalar and > 0.0 + // get cut point of ray with shadow plane + const double fCut(basegfx::B3DVector(maPlanePoint - aCandidate).scalar(maShadowPlaneNormal) / mfLightPlaneScalar); + aCandidate += maLightNormal * fCut; + + // transform to view, use 2d coordinates + aCandidate *= maEyeToView; + aRetval.append(basegfx::B2DPoint(aCandidate.getX(), aCandidate.getY())); + } + + // copy closed flag + aRetval.setClosed(rSource.isClosed()); + + return aRetval; + } + + basegfx::B2DPolyPolygon Shadow3DExtractingProcessor::impDoShadowProjection(const basegfx::B3DPolyPolygon& rSource) + { + basegfx::B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0); a < rSource.count(); a++) + { + aRetval.append(impDoShadowProjection(rSource.getB3DPolygon(a))); + } + + return aRetval; + } + + const primitive2d::Primitive2DContainer& Shadow3DExtractingProcessor::getPrimitive2DSequence() const + { + return maPrimitive2DSequence; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor3d/zbufferprocessor3d.cxx b/drawinglayer/source/processor3d/zbufferprocessor3d.cxx new file mode 100644 index 000000000..aa319161b --- /dev/null +++ b/drawinglayer/source/processor3d/zbufferprocessor3d.cxx @@ -0,0 +1,638 @@ +/* -*- 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 <processor3d/zbufferprocessor3d.hxx> +#include <basegfx/raster/bzpixelraster.hxx> +#include <basegfx/raster/rasterconvert3d.hxx> +#include <drawinglayer/attribute/materialattribute3d.hxx> +#include <texture/texture.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <drawinglayer/attribute/sdrlightingattribute3d.hxx> +#include <o3tl/safeint.hxx> +#include <svtools/optionsdrawinglayer.hxx> + +using namespace com::sun::star; + +class ZBufferRasterConverter3D : public basegfx::RasterConverter3D +{ +private: + const drawinglayer::processor3d::DefaultProcessor3D& mrProcessor; + basegfx::BZPixelRaster& mrBuffer; + + // interpolators for a single line span + basegfx::ip_single maIntZ; + basegfx::ip_triple maIntColor; + basegfx::ip_triple maIntNormal; + basegfx::ip_double maIntTexture; + basegfx::ip_triple maIntInvTexture; + + // current material to use for rasterconversion + const drawinglayer::attribute::MaterialAttribute3D* mpCurrentMaterial; + + // some boolean flags for line span interpolator usages + bool mbModifyColor : 1; + bool mbUseTex : 1; + bool mbHasTexCoor : 1; + bool mbHasInvTexCoor : 1; + bool mbUseNrm : 1; + bool mbUseCol : 1; + + void getTextureCoor(basegfx::B2DPoint& rTarget) const + { + if(mbHasTexCoor) + { + rTarget.setX(maIntTexture.getX().getVal()); + rTarget.setY(maIntTexture.getY().getVal()); + } + else if(mbHasInvTexCoor) + { + const double fZFactor(maIntInvTexture.getZ().getVal()); + const double fInvZFactor(basegfx::fTools::equalZero(fZFactor) ? 1.0 : 1.0 / fZFactor); + rTarget.setX(maIntInvTexture.getX().getVal() * fInvZFactor); + rTarget.setY(maIntInvTexture.getY().getVal() * fInvZFactor); + } + } + + void incrementLineSpanInterpolators(double fStep) + { + maIntZ.increment(fStep); + + if(mbUseTex) + { + if(mbHasTexCoor) + { + maIntTexture.increment(fStep); + } + else if(mbHasInvTexCoor) + { + maIntInvTexture.increment(fStep); + } + } + + if(mbUseNrm) + { + maIntNormal.increment(fStep); + } + + if(mbUseCol) + { + maIntColor.increment(fStep); + } + } + + double decideColorAndOpacity(basegfx::BColor& rColor) const + { + // init values with full opacity and material color + OSL_ENSURE(nullptr != mpCurrentMaterial, "CurrentMaterial not set (!)"); + double fOpacity(1.0); + rColor = mpCurrentMaterial->getColor(); + + if(mbUseTex) + { + basegfx::B2DPoint aTexCoor(0.0, 0.0); + getTextureCoor(aTexCoor); + + if(mrProcessor.getGeoTexSvx()) + { + // calc color in spot. This may also set to invisible already when + // e.g. bitmap textures have transparent parts + mrProcessor.getGeoTexSvx()->modifyBColor(aTexCoor, rColor, fOpacity); + } + + if(basegfx::fTools::more(fOpacity, 0.0) && mrProcessor.getTransparenceGeoTexSvx()) + { + // calc opacity. Object has a 2nd texture, a transparence texture + mrProcessor.getTransparenceGeoTexSvx()->modifyOpacity(aTexCoor, fOpacity); + } + } + + if(basegfx::fTools::more(fOpacity, 0.0)) + { + if(mrProcessor.getGeoTexSvx()) + { + if(mbUseNrm) + { + // blend texture with phong + rColor = mrProcessor.getSdrLightingAttribute().solveColorModel( + basegfx::B3DVector(maIntNormal.getX().getVal(), maIntNormal.getY().getVal(), maIntNormal.getZ().getVal()), + rColor, + mpCurrentMaterial->getSpecular(), + mpCurrentMaterial->getEmission(), + mpCurrentMaterial->getSpecularIntensity()); + } + else if(mbUseCol) + { + // blend texture with gouraud + basegfx::BColor aBlendColor(maIntColor.getX().getVal(), maIntColor.getY().getVal(), maIntColor.getZ().getVal()); + rColor *= aBlendColor; + } + else if(mrProcessor.getModulate()) + { + // blend texture with single material color + rColor *= mpCurrentMaterial->getColor(); + } + } + else + { + if(mbUseNrm) + { + // modify color with phong + rColor = mrProcessor.getSdrLightingAttribute().solveColorModel( + basegfx::B3DVector(maIntNormal.getX().getVal(), maIntNormal.getY().getVal(), maIntNormal.getZ().getVal()), + rColor, + mpCurrentMaterial->getSpecular(), + mpCurrentMaterial->getEmission(), + mpCurrentMaterial->getSpecularIntensity()); + } + else if(mbUseCol) + { + // modify color with gouraud + rColor.setRed(maIntColor.getX().getVal()); + rColor.setGreen(maIntColor.getY().getVal()); + rColor.setBlue(maIntColor.getZ().getVal()); + } + } + + if(mbModifyColor) + { + rColor = mrProcessor.getBColorModifierStack().getModifiedColor(rColor); + } + } + + return fOpacity; + } + + void setupLineSpanInterpolators(const basegfx::RasterConversionLineEntry3D& rA, const basegfx::RasterConversionLineEntry3D& rB) + { + // get inverse XDelta + const double xInvDelta(1.0 / (rB.getX().getVal() - rA.getX().getVal())); + + // prepare Z-interpolator + const double fZA(rA.getZ().getVal()); + const double fZB(rB.getZ().getVal()); + maIntZ = basegfx::ip_single(fZA, (fZB - fZA) * xInvDelta); + + // get bools and init other interpolators on demand accordingly + mbModifyColor = mrProcessor.getBColorModifierStack().count(); + mbHasTexCoor = SCANLINE_EMPTY_INDEX != rA.getTextureIndex() && SCANLINE_EMPTY_INDEX != rB.getTextureIndex(); + mbHasInvTexCoor = SCANLINE_EMPTY_INDEX != rA.getInverseTextureIndex() && SCANLINE_EMPTY_INDEX != rB.getInverseTextureIndex(); + const bool bTextureActive(mrProcessor.getGeoTexSvx() || mrProcessor.getTransparenceGeoTexSvx()); + mbUseTex = bTextureActive && (mbHasTexCoor || mbHasInvTexCoor || mrProcessor.getSimpleTextureActive()); + const bool bUseColorTex(mbUseTex && mrProcessor.getGeoTexSvx()); + const bool bNeedNrmOrCol(!bUseColorTex || mrProcessor.getModulate()); + mbUseNrm = bNeedNrmOrCol && SCANLINE_EMPTY_INDEX != rA.getNormalIndex() && SCANLINE_EMPTY_INDEX != rB.getNormalIndex(); + mbUseCol = !mbUseNrm && bNeedNrmOrCol && SCANLINE_EMPTY_INDEX != rA.getColorIndex() && SCANLINE_EMPTY_INDEX != rB.getColorIndex(); + + if(mbUseTex) + { + if(mbHasTexCoor) + { + const basegfx::ip_double& rTA(getTextureInterpolators()[rA.getTextureIndex()]); + const basegfx::ip_double& rTB(getTextureInterpolators()[rB.getTextureIndex()]); + maIntTexture = basegfx::ip_double( + rTA.getX().getVal(), (rTB.getX().getVal() - rTA.getX().getVal()) * xInvDelta, + rTA.getY().getVal(), (rTB.getY().getVal() - rTA.getY().getVal()) * xInvDelta); + } + else if(mbHasInvTexCoor) + { + const basegfx::ip_triple& rITA(getInverseTextureInterpolators()[rA.getInverseTextureIndex()]); + const basegfx::ip_triple& rITB(getInverseTextureInterpolators()[rB.getInverseTextureIndex()]); + maIntInvTexture = basegfx::ip_triple( + rITA.getX().getVal(), (rITB.getX().getVal() - rITA.getX().getVal()) * xInvDelta, + rITA.getY().getVal(), (rITB.getY().getVal() - rITA.getY().getVal()) * xInvDelta, + rITA.getZ().getVal(), (rITB.getZ().getVal() - rITA.getZ().getVal()) * xInvDelta); + } + } + + if(mbUseNrm) + { + const basegfx::ip_triple& rNA(getNormalInterpolators()[rA.getNormalIndex()]); + const basegfx::ip_triple& rNB(getNormalInterpolators()[rB.getNormalIndex()]); + maIntNormal = basegfx::ip_triple( + rNA.getX().getVal(), (rNB.getX().getVal() - rNA.getX().getVal()) * xInvDelta, + rNA.getY().getVal(), (rNB.getY().getVal() - rNA.getY().getVal()) * xInvDelta, + rNA.getZ().getVal(), (rNB.getZ().getVal() - rNA.getZ().getVal()) * xInvDelta); + } + + if(mbUseCol) + { + const basegfx::ip_triple& rCA(getColorInterpolators()[rA.getColorIndex()]); + const basegfx::ip_triple& rCB(getColorInterpolators()[rB.getColorIndex()]); + maIntColor = basegfx::ip_triple( + rCA.getX().getVal(), (rCB.getX().getVal() - rCA.getX().getVal()) * xInvDelta, + rCA.getY().getVal(), (rCB.getY().getVal() - rCA.getY().getVal()) * xInvDelta, + rCA.getZ().getVal(), (rCB.getZ().getVal() - rCA.getZ().getVal()) * xInvDelta); + } + } + + virtual void processLineSpan(const basegfx::RasterConversionLineEntry3D& rA, const basegfx::RasterConversionLineEntry3D& rB, sal_Int32 nLine, sal_uInt32 nSpanCount) override; + +public: + ZBufferRasterConverter3D(basegfx::BZPixelRaster& rBuffer, const drawinglayer::processor3d::ZBufferProcessor3D& rProcessor) + : mrProcessor(rProcessor), + mrBuffer(rBuffer), + mpCurrentMaterial(nullptr), + mbModifyColor(false), + mbUseTex(false), + mbHasTexCoor(false), + mbHasInvTexCoor(false), + mbUseNrm(false), + mbUseCol(false) + {} + + void setCurrentMaterial(const drawinglayer::attribute::MaterialAttribute3D& rMaterial) + { + mpCurrentMaterial = &rMaterial; + } +}; + +void ZBufferRasterConverter3D::processLineSpan(const basegfx::RasterConversionLineEntry3D& rA, const basegfx::RasterConversionLineEntry3D& rB, sal_Int32 nLine, sal_uInt32 nSpanCount) +{ + if(nSpanCount & 0x0001) + return; + + 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()))))); + const sal_uInt32 nXB(std::min(mrBuffer.getWidth(), static_cast<sal_uInt32>(std::max(sal_Int32(0), basegfx::fround(rB.getX().getVal()))))); + + if(nXA >= nXB) + return; + + // prepare the span interpolators + setupLineSpanInterpolators(rA, rB); + + // bring span interpolators to start condition by incrementing with the possible difference of + // clamped and non-clamped XStart. Interpolators are setup relying on double precision + // X-values, so that difference is the correct value to compensate for possible clampings + incrementLineSpanInterpolators(static_cast<double>(nXA) - rA.getX().getVal()); + + // prepare scanline index + sal_uInt32 nScanlineIndex(mrBuffer.getIndexFromXY(nXA, static_cast<sal_uInt32>(nLine))); + basegfx::BColor aNewColor; + + while(nXA < nXB) + { + // early-test Z values if we need to do anything at all + const double fNewZ(std::clamp(maIntZ.getVal(), 0.0, 65535.0)); + const sal_uInt16 nNewZ(static_cast< sal_uInt16 >(fNewZ)); + sal_uInt16& rOldZ(mrBuffer.getZ(nScanlineIndex)); + + if(nNewZ > rOldZ) + { + // detect color and opacity for this pixel + const sal_uInt16 nOpacity(std::max(sal_Int16(0), static_cast< sal_Int16 >(decideColorAndOpacity(aNewColor) * 255.0))); + + if(nOpacity > 0) + { + // avoid color overrun + aNewColor.clamp(); + + if(nOpacity >= 0x00ff) + { + // full opacity (not transparent), set z and color + rOldZ = nNewZ; + mrBuffer.getBPixel(nScanlineIndex) = basegfx::BPixel(aNewColor, 0xff); + } + else + { + basegfx::BPixel& rDest = mrBuffer.getBPixel(nScanlineIndex); + + if(rDest.getAlpha()) + { + // mix new color by using + // color' = color * (1 - opacity) + newcolor * opacity + const sal_uInt16 nTransparence(255 - nOpacity); + rDest.setRed(static_cast<sal_uInt8>(((rDest.getRed() * nTransparence) + (static_cast<sal_uInt16>(255.0 * aNewColor.getRed()) * nOpacity)) >> 8)); + rDest.setGreen(static_cast<sal_uInt8>(((rDest.getGreen() * nTransparence) + (static_cast<sal_uInt16>(255.0 * aNewColor.getGreen()) * nOpacity)) >> 8)); + rDest.setBlue(static_cast<sal_uInt8>(((rDest.getBlue() * nTransparence) + (static_cast<sal_uInt16>(255.0 * aNewColor.getBlue()) * nOpacity)) >> 8)); + + if(255 != rDest.getAlpha()) + { + // both are transparent, mix new opacity by using + // opacity = newopacity * (1 - oldopacity) + oldopacity + rDest.setAlpha(static_cast<sal_uInt8>((nOpacity * (255 - rDest.getAlpha())) >> 8) + rDest.getAlpha()); + } + } + else + { + // dest is unused, set color + rDest = basegfx::BPixel(aNewColor, static_cast<sal_uInt8>(nOpacity)); + } + } + } + } + + // increments + nScanlineIndex++; + nXA++; + incrementLineSpanInterpolators(1.0); + } +} + +// helper class to buffer output for transparent rasterprimitives (filled areas +// and lines) until the end of processing. To ensure correct transparent +// visualisation, ZBuffers require to not set Z and to mix with the transparent +// color. If transparent rasterprimitives overlap, it gets necessary to +// paint transparent rasterprimitives from back to front to ensure that the +// mixing happens from back to front. For that purpose, transparent +// rasterprimitives are held in this class during the processing run, remember +// all data and will be rendered + +class RasterPrimitive3D +{ +private: + std::shared_ptr< drawinglayer::texture::GeoTexSvx > mpGeoTexSvx; + std::shared_ptr< drawinglayer::texture::GeoTexSvx > mpTransparenceGeoTexSvx; + drawinglayer::attribute::MaterialAttribute3D maMaterial; + basegfx::B3DPolyPolygon maPolyPolygon; + double mfCenterZ; + + bool mbModulate : 1; + bool mbFilter : 1; + bool mbSimpleTextureActive : 1; + bool mbIsLine : 1; + +public: + RasterPrimitive3D( + const std::shared_ptr< drawinglayer::texture::GeoTexSvx >& pGeoTexSvx, + const 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), + maMaterial(rMaterial), + maPolyPolygon(rPolyPolygon), + mfCenterZ(basegfx::utils::getRange(rPolyPolygon).getCenter().getZ()), + mbModulate(bModulate), + mbFilter(bFilter), + mbSimpleTextureActive(bSimpleTextureActive), + mbIsLine(bIsLine) + { + } + + bool operator<(const RasterPrimitive3D& rComp) const + { + return mfCenterZ < rComp.mfCenterZ; + } + + const std::shared_ptr< drawinglayer::texture::GeoTexSvx >& getGeoTexSvx() const { return mpGeoTexSvx; } + const std::shared_ptr< drawinglayer::texture::GeoTexSvx >& getTransparenceGeoTexSvx() const { return mpTransparenceGeoTexSvx; } + const drawinglayer::attribute::MaterialAttribute3D& getMaterial() const { return maMaterial; } + const basegfx::B3DPolyPolygon& getPolyPolygon() const { return maPolyPolygon; } + bool getModulate() const { return mbModulate; } + bool getFilter() const { return mbFilter; } + bool getSimpleTextureActive() const { return mbSimpleTextureActive; } + bool getIsLine() const { return mbIsLine; } +}; + +namespace drawinglayer::processor3d +{ + void ZBufferProcessor3D::rasterconvertB3DPolygon(const attribute::MaterialAttribute3D& rMaterial, const basegfx::B3DPolygon& rHairline) const + { + if(getTransparenceCounter()) + { + // transparent output; record for later sorting and painting from + // back to front + + maRasterPrimitive3Ds.push_back(RasterPrimitive3D( + getGeoTexSvx(), + getTransparenceGeoTexSvx(), + rMaterial, + basegfx::B3DPolyPolygon(rHairline), + getModulate(), + getFilter(), + getSimpleTextureActive(), + true)); + } + else + { + // do rasterconversion + mpZBufferRasterConverter3D->setCurrentMaterial(rMaterial); + + if(mnAntiAlialize > 1) + { + const bool bForceLineSnap(SvtOptionsDrawinglayer::IsAntiAliasing() && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete()); + + if(bForceLineSnap) + { + basegfx::B3DHomMatrix aTransform; + basegfx::B3DPolygon aSnappedHairline(rHairline); + const double fScaleDown(1.0 / mnAntiAlialize); + const double fScaleUp(mnAntiAlialize); + + // take oversampling out + aTransform.scale(fScaleDown, fScaleDown, 1.0); + aSnappedHairline.transform(aTransform); + + // snap to integer + aSnappedHairline = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aSnappedHairline); + + // add oversampling again + aTransform.identity(); + aTransform.scale(fScaleUp, fScaleUp, 1.0); + + aSnappedHairline.transform(aTransform); + + mpZBufferRasterConverter3D->rasterconvertB3DPolygon(aSnappedHairline, mnStartLine, mnStopLine, mnAntiAlialize); + } + else + { + mpZBufferRasterConverter3D->rasterconvertB3DPolygon(rHairline, mnStartLine, mnStopLine, mnAntiAlialize); + } + } + else + { + mpZBufferRasterConverter3D->rasterconvertB3DPolygon(rHairline, mnStartLine, mnStopLine, 1); + } + } + } + + void ZBufferProcessor3D::rasterconvertB3DPolyPolygon(const attribute::MaterialAttribute3D& rMaterial, const basegfx::B3DPolyPolygon& rFill) const + { + if(getTransparenceCounter()) + { + // transparent output; record for later sorting and painting from + // back to front + maRasterPrimitive3Ds.push_back(RasterPrimitive3D( + getGeoTexSvx(), + getTransparenceGeoTexSvx(), + rMaterial, + rFill, + getModulate(), + getFilter(), + getSimpleTextureActive(), + false)); + } + else + { + mpZBufferRasterConverter3D->setCurrentMaterial(rMaterial); + mpZBufferRasterConverter3D->rasterconvertB3DPolyPolygon(rFill, &maInvEyeToView, mnStartLine, mnStopLine); + } + } + + ZBufferProcessor3D::ZBufferProcessor3D( + const geometry::ViewInformation3D& rViewInformation3D, + const attribute::SdrSceneAttribute& rSdrSceneAttribute, + const attribute::SdrLightingAttribute& rSdrLightingAttribute, + const basegfx::B2DRange& rVisiblePart, + sal_uInt16 nAntiAlialize, + double fFullViewSizeX, + double fFullViewSizeY, + basegfx::BZPixelRaster& rBZPixelRaster, + sal_uInt32 nStartLine, + sal_uInt32 nStopLine) + : DefaultProcessor3D(rViewInformation3D, rSdrSceneAttribute, rSdrLightingAttribute), + mnAntiAlialize(nAntiAlialize), + mnStartLine(nStartLine), + mnStopLine(nStopLine) + { + // create DeviceToView for Z-Buffer renderer since Z is handled + // different from standard 3D transformations (Z is mirrored). Also + // the transformation includes the step from unit device coordinates + // to discrete units ([-1.0 .. 1.0] -> [minDiscrete .. maxDiscrete] + basegfx::B3DHomMatrix aDeviceToView; + + { + // step one: + // + // bring from [-1.0 .. 1.0] in X,Y and Z to [0.0 .. 1.0]. Also + // necessary to + // - flip Y due to screen orientation + // - flip Z due to Z-Buffer orientation from back to front + + aDeviceToView.scale(0.5, -0.5, -0.5); + aDeviceToView.translate(0.5, 0.5, 0.5); + } + + { + // step two: + // + // bring from [0.0 .. 1.0] in X,Y and Z to view coordinates + // + // #i102611# + // also: scale Z to [1.5 .. 65534.5]. Normally, a range of [0.0 .. 65535.0] + // could be used, but a 'unused' value is needed, so '0' is used what reduces + // the range to [1.0 .. 65535.0]. It has also shown that small numerical errors + // (smaller as basegfx::fTools::mfSmallValue, which is 0.000000001) happen. + // Instead of checking those by basegfx::fTools methods which would cost + // runtime, just add another 0.5 tolerance to the start and end of the Z-Buffer + // range, thus resulting in [1.5 .. 65534.5] + const double fMaxZDepth(65533.0); + aDeviceToView.translate(-rVisiblePart.getMinX(), -rVisiblePart.getMinY(), 0.0); + + if(mnAntiAlialize) + aDeviceToView.scale(fFullViewSizeX * mnAntiAlialize, fFullViewSizeY * mnAntiAlialize, fMaxZDepth); + else + aDeviceToView.scale(fFullViewSizeX, fFullViewSizeY, fMaxZDepth); + + aDeviceToView.translate(0.0, 0.0, 1.5); + } + + // update local ViewInformation3D with own DeviceToView + const geometry::ViewInformation3D aNewViewInformation3D( + getViewInformation3D().getObjectTransformation(), + getViewInformation3D().getOrientation(), + getViewInformation3D().getProjection(), + aDeviceToView, + getViewInformation3D().getViewTime(), + getViewInformation3D().getExtendedInformationSequence()); + updateViewInformation(aNewViewInformation3D); + + // prepare inverse EyeToView transformation. This can be done in constructor + // since changes in object transformations when processing TransformPrimitive3Ds + // do not influence this prepared partial transformation + maInvEyeToView = getViewInformation3D().getDeviceToView() * getViewInformation3D().getProjection(); + maInvEyeToView.invert(); + + // prepare maRasterRange + maRasterRange.reset(); + maRasterRange.expand(basegfx::B2DPoint(0.0, nStartLine)); + maRasterRange.expand(basegfx::B2DPoint(rBZPixelRaster.getWidth(), nStopLine)); + + // create the raster converter + mpZBufferRasterConverter3D.reset( new ZBufferRasterConverter3D(rBZPixelRaster, *this) ); + } + + ZBufferProcessor3D::~ZBufferProcessor3D() + { + mpZBufferRasterConverter3D.reset(); + + if(!maRasterPrimitive3Ds.empty()) + { + OSL_FAIL("ZBufferProcessor3D: destructed, but there are unrendered transparent geometries. Use ZBufferProcessor3D::finish() to render these (!)"); + } + } + + void ZBufferProcessor3D::finish() + { + if(maRasterPrimitive3Ds.empty()) + return; + + // there are transparent rasterprimitives + const sal_uInt32 nSize(maRasterPrimitive3Ds.size()); + + if(nSize > 1) + { + // sort them from back to front + std::sort(maRasterPrimitive3Ds.begin(), maRasterPrimitive3Ds.end()); + } + + for(sal_uInt32 a(0); a < nSize; a++) + { + // paint each one by setting the remembered data and calling + // the render method + const RasterPrimitive3D& rCandidate = maRasterPrimitive3Ds[a]; + + mpGeoTexSvx = rCandidate.getGeoTexSvx(); + mpTransparenceGeoTexSvx = rCandidate.getTransparenceGeoTexSvx(); + mbModulate = rCandidate.getModulate(); + mbFilter = rCandidate.getFilter(); + mbSimpleTextureActive = rCandidate.getSimpleTextureActive(); + + if(rCandidate.getIsLine()) + { + rasterconvertB3DPolygon( + rCandidate.getMaterial(), + rCandidate.getPolyPolygon().getB3DPolygon(0)); + } + else + { + rasterconvertB3DPolyPolygon( + rCandidate.getMaterial(), + rCandidate.getPolyPolygon()); + } + } + + // delete them to signal the destructor that all is done and + // to allow asserting there + maRasterPrimitive3Ds.clear(); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/texture/texture.cxx b/drawinglayer/source/texture/texture.cxx new file mode 100644 index 000000000..b3477f642 --- /dev/null +++ b/drawinglayer/source/texture/texture.cxx @@ -0,0 +1,766 @@ +/* -*- 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 <algorithm> + +#include <texture/texture.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/utils/gradienttools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <comphelper/random.hxx> + +namespace drawinglayer::texture +{ + namespace + { + double getRandomColorRange() + { + return comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)); + } + } + + GeoTexSvx::GeoTexSvx() + { + } + + GeoTexSvx::~GeoTexSvx() + { + } + + bool GeoTexSvx::operator==(const GeoTexSvx& /*rGeoTexSvx*/) const + { + // default implementation says yes (no data -> no difference) + return true; + } + + void GeoTexSvx::modifyBColor(const basegfx::B2DPoint& /*rUV*/, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + // base implementation creates random color (for testing only, may also be pure virtual) + rBColor.setRed(getRandomColorRange()); + rBColor.setGreen(getRandomColorRange()); + rBColor.setBlue(getRandomColorRange()); + } + + void GeoTexSvx::modifyOpacity(const basegfx::B2DPoint& rUV, double& rfOpacity) const + { + // base implementation uses inverse of luminance of solved color (for testing only, may also be pure virtual) + basegfx::BColor aBaseColor; + modifyBColor(rUV, aBaseColor, rfOpacity); + rfOpacity = 1.0 - aBaseColor.luminance(); + } + + + GeoTexSvxGradient::GeoTexSvxGradient( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + double fBorder) + : maDefinitionRange(rDefinitionRange), + maStart(rStart), + maEnd(rEnd), + mfBorder(fBorder) + { + } + + GeoTexSvxGradient::~GeoTexSvxGradient() + { + } + + bool GeoTexSvxGradient::operator==(const GeoTexSvx& rGeoTexSvx) const + { + const GeoTexSvxGradient* pCompare = dynamic_cast< const GeoTexSvxGradient* >(&rGeoTexSvx); + + return (pCompare + && maGradientInfo == pCompare->maGradientInfo + && maDefinitionRange == pCompare->maDefinitionRange + && mfBorder == pCompare->mfBorder); + } + + + GeoTexSvxGradientLinear::GeoTexSvxGradientLinear( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::B2DRange& rOutputRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder), + mfUnitMinX(0.0), + mfUnitWidth(1.0), + mfUnitMaxY(1.0) + { + maGradientInfo = basegfx::utils::createLinearODFGradientInfo( + rDefinitionRange, + nSteps, + fBorder, + fAngle); + + if(rDefinitionRange != rOutputRange) + { + basegfx::B2DRange aInvOutputRange(rOutputRange); + + aInvOutputRange.transform(maGradientInfo.getBackTextureTransform()); + mfUnitMinX = aInvOutputRange.getMinX(); + mfUnitWidth = aInvOutputRange.getWidth(); + mfUnitMaxY = aInvOutputRange.getMaxY(); + } + } + + GeoTexSvxGradientLinear::~GeoTexSvxGradientLinear() + { + } + + void GeoTexSvxGradientLinear::appendTransformationsAndColors( + std::vector< B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) + { + rOuterColor = maStart; + + if(!maGradientInfo.getSteps()) + return; + + const double fStripeWidth(1.0 / maGradientInfo.getSteps()); + B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + basegfx::B2DHomMatrix aPattern; + + // bring from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1] + aPattern.scale(0.5, 0.5); + aPattern.translate(0.5, 0.5); + + // scale and translate in X + aPattern.scale(mfUnitWidth, 1.0); + aPattern.translate(mfUnitMinX, 0.0); + + for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + { + 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) + { + fHeight += mfUnitMaxY - 1.0; + } + + 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); + } + } + + void GeoTexSvxGradientLinear::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + const double fScaler(basegfx::utils::getLinearGradientAlpha(rUV, maGradientInfo)); + + rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + } + + GeoTexSvxGradientAxial::GeoTexSvxGradientAxial( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::B2DRange& rOutputRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder), + mfUnitMinX(0.0), + mfUnitWidth(1.0) + { + maGradientInfo = basegfx::utils::createAxialODFGradientInfo( + rDefinitionRange, + nSteps, + fBorder, + fAngle); + + if(rDefinitionRange != rOutputRange) + { + basegfx::B2DRange aInvOutputRange(rOutputRange); + + aInvOutputRange.transform(maGradientInfo.getBackTextureTransform()); + mfUnitMinX = aInvOutputRange.getMinX(); + mfUnitWidth = aInvOutputRange.getWidth(); + } + } + + GeoTexSvxGradientAxial::~GeoTexSvxGradientAxial() + { + } + + void GeoTexSvxGradientAxial::appendTransformationsAndColors( + std::vector< B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) + { + rOuterColor = maEnd; + + if(!maGradientInfo.getSteps()) + return; + + const double fStripeWidth(1.0 / maGradientInfo.getSteps()); + B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + + for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + { + const double fPos(fStripeWidth * a); + basegfx::B2DHomMatrix aNew; + + // 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); + + // scale/translate in X + aNew.scale(mfUnitWidth, 1.0); + aNew.translate(mfUnitMinX, 0.0); + + // already centered in Y on X-Axis, just scale in Y + aNew.scale(1.0, 1.0 - fPos); + + // set at target + aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * aNew; + + // interpolate and set color + aB2DHomMatrixAndBColor.maBColor = interpolate(maEnd, maStart, double(a) / double(maGradientInfo.getSteps() - 1)); + + rEntries.push_back(aB2DHomMatrixAndBColor); + } + } + + void GeoTexSvxGradientAxial::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + const double fScaler(basegfx::utils::getAxialGradientAlpha(rUV, maGradientInfo)); + + rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + } + + + GeoTexSvxGradientRadial::GeoTexSvxGradientRadial( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + sal_uInt32 nSteps, + double fBorder, + double fOffsetX, + double fOffsetY) + : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + { + maGradientInfo = basegfx::utils::createRadialODFGradientInfo( + rDefinitionRange, + basegfx::B2DVector(fOffsetX,fOffsetY), + nSteps, + fBorder); + } + + GeoTexSvxGradientRadial::~GeoTexSvxGradientRadial() + { + } + + void GeoTexSvxGradientRadial::appendTransformationsAndColors( + std::vector< B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) + { + rOuterColor = maStart; + + if(!maGradientInfo.getSteps()) + return; + + const double fStepSize(1.0 / maGradientInfo.getSteps()); + B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + + for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + { + 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); + } + } + + void GeoTexSvxGradientRadial::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + const double fScaler(basegfx::utils::getRadialGradientAlpha(rUV, maGradientInfo)); + + rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + } + + + GeoTexSvxGradientElliptical::GeoTexSvxGradientElliptical( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + sal_uInt32 nSteps, + double fBorder, + double fOffsetX, + double fOffsetY, + double fAngle) + : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + { + maGradientInfo = basegfx::utils::createEllipticalODFGradientInfo( + rDefinitionRange, + basegfx::B2DVector(fOffsetX,fOffsetY), + nSteps, + fBorder, + fAngle); + } + + GeoTexSvxGradientElliptical::~GeoTexSvxGradientElliptical() + { + } + + void GeoTexSvxGradientElliptical::appendTransformationsAndColors( + std::vector< B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) + { + rOuterColor = maStart; + + if(!maGradientInfo.getSteps()) + return; + + double fWidth(1.0); + double fHeight(1.0); + double fIncrementX(0.0); + double fIncrementY(0.0); + + if(maGradientInfo.getAspectRatio() > 1.0) + { + fIncrementY = fHeight / maGradientInfo.getSteps(); + fIncrementX = fIncrementY / maGradientInfo.getAspectRatio(); + } + else + { + fIncrementX = fWidth / maGradientInfo.getSteps(); + fIncrementY = fIncrementX * maGradientInfo.getAspectRatio(); + } + + B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + + for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + { + // next step + fWidth -= fIncrementX; + fHeight -= fIncrementY; + + aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fWidth, fHeight); + aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); + rEntries.push_back(aB2DHomMatrixAndBColor); + } + } + + void GeoTexSvxGradientElliptical::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + const double fScaler(basegfx::utils::getEllipticalGradientAlpha(rUV, maGradientInfo)); + + rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + } + + + GeoTexSvxGradientSquare::GeoTexSvxGradientSquare( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + sal_uInt32 nSteps, + double fBorder, + double fOffsetX, + double fOffsetY, + double fAngle) + : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + { + maGradientInfo = basegfx::utils::createSquareODFGradientInfo( + rDefinitionRange, + basegfx::B2DVector(fOffsetX,fOffsetY), + nSteps, + fBorder, + fAngle); + } + + GeoTexSvxGradientSquare::~GeoTexSvxGradientSquare() + { + } + + void GeoTexSvxGradientSquare::appendTransformationsAndColors( + std::vector< B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) + { + rOuterColor = maStart; + + if(!maGradientInfo.getSteps()) + return; + + const double fStepSize(1.0 / maGradientInfo.getSteps()); + B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + + for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + { + 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); + } + } + + void GeoTexSvxGradientSquare::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + const double fScaler(basegfx::utils::getSquareGradientAlpha(rUV, maGradientInfo)); + + rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + } + + + GeoTexSvxGradientRect::GeoTexSvxGradientRect( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rStart, + const basegfx::BColor& rEnd, + sal_uInt32 nSteps, + double fBorder, + double fOffsetX, + double fOffsetY, + double fAngle) + : GeoTexSvxGradient(rDefinitionRange, rStart, rEnd, fBorder) + { + maGradientInfo = basegfx::utils::createRectangularODFGradientInfo( + rDefinitionRange, + basegfx::B2DVector(fOffsetX,fOffsetY), + nSteps, + fBorder, + fAngle); + } + + GeoTexSvxGradientRect::~GeoTexSvxGradientRect() + { + } + + void GeoTexSvxGradientRect::appendTransformationsAndColors( + std::vector< B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) + { + rOuterColor = maStart; + + if(!maGradientInfo.getSteps()) + return; + + double fWidth(1.0); + double fHeight(1.0); + double fIncrementX(0.0); + double fIncrementY(0.0); + + if(maGradientInfo.getAspectRatio() > 1.0) + { + fIncrementY = fHeight / maGradientInfo.getSteps(); + fIncrementX = fIncrementY / maGradientInfo.getAspectRatio(); + } + else + { + fIncrementX = fWidth / maGradientInfo.getSteps(); + fIncrementY = fIncrementX * maGradientInfo.getAspectRatio(); + } + + B2DHomMatrixAndBColor aB2DHomMatrixAndBColor; + + for(sal_uInt32 a(1); a < maGradientInfo.getSteps(); a++) + { + // next step + fWidth -= fIncrementX; + fHeight -= fIncrementY; + + aB2DHomMatrixAndBColor.maB2DHomMatrix = maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fWidth, fHeight); + aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, double(a) / double(maGradientInfo.getSteps() - 1)); + rEntries.push_back(aB2DHomMatrixAndBColor); + } + } + + void GeoTexSvxGradientRect::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + const double fScaler(basegfx::utils::getRectangularGradientAlpha(rUV, maGradientInfo)); + + rBColor = basegfx::interpolate(maStart, maEnd, fScaler); + } + + + GeoTexSvxHatch::GeoTexSvxHatch( + const basegfx::B2DRange& rDefinitionRange, + const basegfx::B2DRange& rOutputRange, + double fDistance, + double fAngle) + : maOutputRange(rOutputRange), + mfDistance(0.1), + mfAngle(fAngle), + mnSteps(10), + mbDefinitionRangeEqualsOutputRange(rDefinitionRange == rOutputRange) + { + double fTargetSizeX(rDefinitionRange.getWidth()); + double fTargetSizeY(rDefinitionRange.getHeight()); + double fTargetOffsetX(rDefinitionRange.getMinX()); + double fTargetOffsetY(rDefinitionRange.getMinY()); + + fAngle = -fAngle; + + // add object expansion + if(0.0 != fAngle) + { + const double fAbsCos(fabs(cos(fAngle))); + const double fAbsSin(fabs(sin(fAngle))); + const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin); + const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin); + fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0; + fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0; + fTargetSizeX = fNewX; + fTargetSizeY = fNewY; + } + + // add object scale before rotate + maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + // add texture rotate after scale to keep perpendicular angles + if(0.0 != fAngle) + { + basegfx::B2DPoint aCenter(0.5, 0.5); + aCenter *= maTextureTransform; + + maTextureTransform = basegfx::utils::createRotateAroundPoint(aCenter, fAngle) + * maTextureTransform; + } + + // add object translate + maTextureTransform.translate(fTargetOffsetX, fTargetOffsetY); + + // prepare height for texture + const double fSteps((0.0 != fDistance) ? fTargetSizeY / fDistance : 10.0); + mnSteps = basegfx::fround(fSteps + 0.5); + mfDistance = 1.0 / fSteps; + } + + GeoTexSvxHatch::~GeoTexSvxHatch() + { + } + + bool GeoTexSvxHatch::operator==(const GeoTexSvx& rGeoTexSvx) const + { + const GeoTexSvxHatch* pCompare = dynamic_cast< const GeoTexSvxHatch* >(&rGeoTexSvx); + return (pCompare + && maOutputRange == pCompare->maOutputRange + && maTextureTransform == pCompare->maTextureTransform + && mfDistance == pCompare->mfDistance + && mfAngle == pCompare->mfAngle + && mnSteps == pCompare->mnSteps); + } + + void GeoTexSvxHatch::appendTransformations(std::vector< basegfx::B2DHomMatrix >& rMatrices) + { + if(mbDefinitionRangeEqualsOutputRange) + { + // simple hatch where the definition area equals the output area + for(sal_uInt32 a(1); a < mnSteps; a++) + { + // create matrix + const double fOffset(mfDistance * static_cast<double>(a)); + basegfx::B2DHomMatrix aNew; + aNew.set(1, 2, fOffset); + rMatrices.push_back(maTextureTransform * aNew); + } + } + else + { + // output area is different from definition area, back-transform to get + // the output area in unit coordinates and fill this with hatch lines + // using the settings derived from the definition area + basegfx::B2DRange aBackUnitRange(maOutputRange); + + aBackUnitRange.transform(getBackTextureTransform()); + + // calculate vertical start value and a security maximum integer value to avoid death loops + double fStart(basegfx::snapToNearestMultiple(aBackUnitRange.getMinY(), mfDistance)); + const sal_uInt32 nNeededIntegerSteps(basegfx::fround((aBackUnitRange.getHeight() / mfDistance) + 0.5)); + sal_uInt32 nMaxIntegerSteps(std::min(nNeededIntegerSteps, sal_uInt32(10000))); + + while(fStart < aBackUnitRange.getMaxY() && nMaxIntegerSteps) + { + // create new transform for + basegfx::B2DHomMatrix aNew; + + // adapt x scale and position + //aNew.scale(aBackUnitRange.getWidth(), 1.0); + //aNew.translate(aBackUnitRange.getMinX(), 0.0); + aNew.set(0, 0, aBackUnitRange.getWidth()); + aNew.set(0, 2, aBackUnitRange.getMinX()); + + // adapt y position to current step + aNew.set(1, 2, fStart); + //aNew.translate(0.0, fStart); + + // add new transformation + rMatrices.push_back(maTextureTransform * aNew); + + // next step + fStart += mfDistance; + nMaxIntegerSteps--; + } + } + } + + double GeoTexSvxHatch::getDistanceToHatch(const basegfx::B2DPoint& rUV) const + { + const basegfx::B2DPoint aCoor(getBackTextureTransform() * rUV); + return fmod(aCoor.getY(), mfDistance); + } + + const basegfx::B2DHomMatrix& GeoTexSvxHatch::getBackTextureTransform() const + { + if(maBackTextureTransform.isIdentity()) + { + const_cast< GeoTexSvxHatch* >(this)->maBackTextureTransform = maTextureTransform; + const_cast< GeoTexSvxHatch* >(this)->maBackTextureTransform.invert(); + } + + return maBackTextureTransform; + } + + + GeoTexSvxTiled::GeoTexSvxTiled( + const basegfx::B2DRange& rRange, + double fOffsetX, + double fOffsetY) + : maRange(rRange), + mfOffsetX(std::clamp(fOffsetX, 0.0, 1.0)), + mfOffsetY(std::clamp(fOffsetY, 0.0, 1.0)) + { + if(!basegfx::fTools::equalZero(mfOffsetX)) + { + mfOffsetY = 0.0; + } + } + + GeoTexSvxTiled::~GeoTexSvxTiled() + { + } + + bool GeoTexSvxTiled::operator==(const GeoTexSvx& rGeoTexSvx) const + { + const GeoTexSvxTiled* pCompare = dynamic_cast< const GeoTexSvxTiled* >(&rGeoTexSvx); + + return (pCompare + && maRange == pCompare->maRange + && mfOffsetX == pCompare->mfOffsetX + && mfOffsetY == pCompare->mfOffsetY); + } + + sal_uInt32 GeoTexSvxTiled::getNumberOfTiles() const + { + sal_Int32 nTiles = 0; + iterateTiles([&](double, double) { ++nTiles; }); + return nTiles; + } + + void GeoTexSvxTiled::appendTransformations(std::vector< basegfx::B2DHomMatrix >& rMatrices) const + { + const double fWidth(maRange.getWidth()); + const double fHeight(maRange.getHeight()); + iterateTiles([&](double fPosX, double fPosY) { + rMatrices.push_back(basegfx::utils::createScaleTranslateB2DHomMatrix( + fWidth, + fHeight, + fPosX, + fPosY)); + }); + } + + void GeoTexSvxTiled::iterateTiles(std::function<void(double fPosX, double fPosY)> aFunc) const + { + const double fWidth(maRange.getWidth()); + + if(basegfx::fTools::equalZero(fWidth)) + return; + + const double fHeight(maRange.getHeight()); + + if(basegfx::fTools::equalZero(fHeight)) + return; + + double fStartX(maRange.getMinX()); + double fStartY(maRange.getMinY()); + sal_Int32 nPosX(0); + sal_Int32 nPosY(0); + + if(basegfx::fTools::more(fStartX, 0.0)) + { + const sal_Int32 nDiff(static_cast<sal_Int32>(floor(fStartX / fWidth)) + 1); + + nPosX -= nDiff; + fStartX -= nDiff * fWidth; + } + + if(basegfx::fTools::less(fStartX + fWidth, 0.0)) + { + const sal_Int32 nDiff(static_cast<sal_Int32>(floor(-fStartX / fWidth))); + + nPosX += nDiff; + fStartX += nDiff * fWidth; + } + + if(basegfx::fTools::more(fStartY, 0.0)) + { + const sal_Int32 nDiff(static_cast<sal_Int32>(floor(fStartY / fHeight)) + 1); + + nPosY -= nDiff; + fStartY -= nDiff * fHeight; + } + + if(basegfx::fTools::less(fStartY + fHeight, 0.0)) + { + const sal_Int32 nDiff(static_cast<sal_Int32>(floor(-fStartY / fHeight))); + + nPosY += nDiff; + fStartY += nDiff * fHeight; + } + + if(!basegfx::fTools::equalZero(mfOffsetY)) + { + for(double fPosX(fStartX); basegfx::fTools::less(fPosX, 1.0); fPosX += fWidth, nPosX++) + { + for(double fPosY((nPosX % 2) ? fStartY - fHeight + (mfOffsetY * fHeight) : fStartY); + basegfx::fTools::less(fPosY, 1.0); fPosY += fHeight) + aFunc(fPosX, fPosY); + } + } + else + { + for(double fPosY(fStartY); basegfx::fTools::less(fPosY, 1.0); fPosY += fHeight, nPosY++) + { + for(double fPosX((nPosY % 2) ? fStartX - fWidth + (mfOffsetX * fWidth) : fStartX); + basegfx::fTools::less(fPosX, 1.0); fPosX += fWidth) + aFunc(fPosX, fPosY); + } + } + + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/texture/texture3d.cxx b/drawinglayer/source/texture/texture3d.cxx new file mode 100644 index 000000000..0a8393799 --- /dev/null +++ b/drawinglayer/source/texture/texture3d.cxx @@ -0,0 +1,351 @@ +/* -*- 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 <algorithm> + +#include <texture/texture3d.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/BitmapTools.hxx> +#include <primitive3d/hatchtextureprimitive3d.hxx> +#include <sal/log.hxx> + +namespace drawinglayer::texture +{ + GeoTexSvxMono::GeoTexSvxMono( + const basegfx::BColor& rSingleColor, + double fOpacity) + : maSingleColor(rSingleColor), + mfOpacity(fOpacity) + { + } + + bool GeoTexSvxMono::operator==(const GeoTexSvx& rGeoTexSvx) const + { + const GeoTexSvxMono* pCompare = dynamic_cast< const GeoTexSvxMono* >(&rGeoTexSvx); + + return (pCompare + && maSingleColor == pCompare->maSingleColor + && mfOpacity == pCompare->mfOpacity); + } + + void GeoTexSvxMono::modifyBColor(const basegfx::B2DPoint& /*rUV*/, basegfx::BColor& rBColor, double& /*rfOpacity*/) const + { + rBColor = maSingleColor; + } + + void GeoTexSvxMono::modifyOpacity(const basegfx::B2DPoint& /*rUV*/, double& rfOpacity) const + { + rfOpacity = mfOpacity; + } + + + GeoTexSvxBitmapEx::GeoTexSvxBitmapEx( + const BitmapEx& rBitmapEx, + const basegfx::B2DRange& rRange) + : maBitmapEx(rBitmapEx), + maTopLeft(rRange.getMinimum()), + maSize(rRange.getRange()), + mfMulX(0.0), + mfMulY(0.0), + mbIsAlpha(maBitmapEx.IsAlpha()) + { + if(vcl::bitmap::convertBitmap32To24Plus8(maBitmapEx,maBitmapEx)) + mbIsAlpha = maBitmapEx.IsAlpha(); + // #121194# Todo: use alpha channel, too (for 3d) + maBitmap = maBitmapEx.GetBitmap(); + + if(mbIsAlpha) + { + maTransparence = rBitmapEx.GetAlpha().GetBitmap(); + mpReadTransparence = Bitmap::ScopedReadAccess(maTransparence); + } + + if (!maBitmap.IsEmpty()) + mpReadBitmap = Bitmap::ScopedReadAccess(maBitmap); + SAL_WARN_IF(!mpReadBitmap, "drawinglayer", "GeoTexSvxBitmapEx: Got no read access to Bitmap"); + if (mpReadBitmap) + { + mfMulX = static_cast<double>(mpReadBitmap->Width()) / maSize.getX(); + mfMulY = static_cast<double>(mpReadBitmap->Height()) / maSize.getY(); + } + + if(maSize.getX() <= 1.0) + { + maSize.setX(1.0); + } + + if(maSize.getY() <= 1.0) + { + maSize.setY(1.0); + } + } + + GeoTexSvxBitmapEx::~GeoTexSvxBitmapEx() + { + } + + sal_uInt8 GeoTexSvxBitmapEx::impGetTransparence(sal_Int32 rX, sal_Int32 rY) const + { + if(mbIsAlpha) + { + OSL_ENSURE(mpReadTransparence, "OOps, transparence type Bitmap, but no read access created in the constructor (?)"); + const BitmapColor aBitmapColor(mpReadTransparence->GetPixel(rY, rX)); + return aBitmapColor.GetIndex(); + } + return 0; + } + + bool GeoTexSvxBitmapEx::impIsValid(const basegfx::B2DPoint& rUV, sal_Int32& rX, sal_Int32& rY) const + { + if(mpReadBitmap) + { + rX = static_cast<sal_Int32>((rUV.getX() - maTopLeft.getX()) * mfMulX); + + if(rX >= 0 && rX < mpReadBitmap->Width()) + { + rY = static_cast<sal_Int32>((rUV.getY() - maTopLeft.getY()) * mfMulY); + + return (rY >= 0 && rY < mpReadBitmap->Height()); + } + } + + return false; + } + + void GeoTexSvxBitmapEx::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& rfOpacity) const + { + sal_Int32 nX, nY; + + if(impIsValid(rUV, nX, nY)) + { + const double fConvertColor(1.0 / 255.0); + const BitmapColor aBMCol(mpReadBitmap->GetColor(nY, nX)); + const basegfx::BColor aBSource( + static_cast<double>(aBMCol.GetRed()) * fConvertColor, + static_cast<double>(aBMCol.GetGreen()) * fConvertColor, + static_cast<double>(aBMCol.GetBlue()) * fConvertColor); + + rBColor = aBSource; + + if(mbIsAlpha) + { + // when we have a transparence, make use of it + const sal_uInt8 aLuminance(impGetTransparence(nX, nY)); + + rfOpacity = (static_cast<double>(0xff - aLuminance) * (1.0 / 255.0)); + } + else + { + rfOpacity = 1.0; + } + } + else + { + rfOpacity = 0.0; + } + } + + void GeoTexSvxBitmapEx::modifyOpacity(const basegfx::B2DPoint& rUV, double& rfOpacity) const + { + sal_Int32 nX, nY; + + if(impIsValid(rUV, nX, nY)) + { + 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)); + + rfOpacity = 1.0 - ((1.0 - fNewOpacity) * (1.0 - rfOpacity)); + } + else + { + // this texture is a color bitmap used as transparence map + const BitmapColor aBMCol(mpReadBitmap->GetColor(nY, nX)); + const Color aColor(aBMCol.GetRed(), aBMCol.GetGreen(), aBMCol.GetBlue()); + + rfOpacity = (static_cast<double>(0xff - aColor.GetLuminance()) * (1.0 / 255.0)); + } + } + else + { + rfOpacity = 0.0; + } + } + + + basegfx::B2DPoint GeoTexSvxBitmapExTiled::impGetCorrected(const basegfx::B2DPoint& rUV) const + { + double fX(rUV.getX() - maTopLeft.getX()); + double fY(rUV.getY() - maTopLeft.getY()); + + if(mbUseOffsetX) + { + const sal_Int32 nCol(static_cast< sal_Int32 >((fY < 0.0 ? maSize.getY() -fY : fY) / maSize.getY())); + + if(nCol % 2) + { + fX += mfOffsetX * maSize.getX(); + } + } + else if(mbUseOffsetY) + { + const sal_Int32 nRow(static_cast< sal_Int32 >((fX < 0.0 ? maSize.getX() -fX : fX) / maSize.getX())); + + if(nRow % 2) + { + fY += mfOffsetY * maSize.getY(); + } + } + + fX = fmod(fX, maSize.getX()); + fY = fmod(fY, maSize.getY()); + + if(fX < 0.0) + { + fX += maSize.getX(); + } + + if(fY < 0.0) + { + fY += maSize.getY(); + } + + return basegfx::B2DPoint(fX + maTopLeft.getX(), fY + maTopLeft.getY()); + } + + GeoTexSvxBitmapExTiled::GeoTexSvxBitmapExTiled( + const BitmapEx& rBitmapEx, + const basegfx::B2DRange& rRange, + double fOffsetX, + double fOffsetY) + : GeoTexSvxBitmapEx(rBitmapEx, rRange), + mfOffsetX(std::clamp(fOffsetX, 0.0, 1.0)), + mfOffsetY(std::clamp(fOffsetY, 0.0, 1.0)), + mbUseOffsetX(!basegfx::fTools::equalZero(mfOffsetX)), + mbUseOffsetY(!mbUseOffsetX && !basegfx::fTools::equalZero(mfOffsetY)) + { + } + + void GeoTexSvxBitmapExTiled::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& rfOpacity) const + { + if(mpReadBitmap) + { + GeoTexSvxBitmapEx::modifyBColor(impGetCorrected(rUV), rBColor, rfOpacity); + } + } + + void GeoTexSvxBitmapExTiled::modifyOpacity(const basegfx::B2DPoint& rUV, double& rfOpacity) const + { + if(mpReadBitmap) + { + GeoTexSvxBitmapEx::modifyOpacity(impGetCorrected(rUV), rfOpacity); + } + } + + + GeoTexSvxMultiHatch::GeoTexSvxMultiHatch( + const primitive3d::HatchTexturePrimitive3D& rPrimitive, + double fLogicPixelSize) + : mfLogicPixelSize(fLogicPixelSize) + { + const attribute::FillHatchAttribute& rHatch(rPrimitive.getHatch()); + const basegfx::B2DRange aOutlineRange(0.0, 0.0, rPrimitive.getTextureSize().getX(), rPrimitive.getTextureSize().getY()); + const double fAngleA(rHatch.getAngle()); + maColor = rHatch.getColor(); + mbFillBackground = rHatch.isFillBackground(); + mp0.reset( new GeoTexSvxHatch( + aOutlineRange, + aOutlineRange, + rHatch.getDistance(), + fAngleA) ); + + if(attribute::HatchStyle::Double == rHatch.getStyle() || attribute::HatchStyle::Triple == rHatch.getStyle()) + { + mp1.reset( new GeoTexSvxHatch( + aOutlineRange, + aOutlineRange, + rHatch.getDistance(), + fAngleA + M_PI_2) ); + } + + if(attribute::HatchStyle::Triple == rHatch.getStyle()) + { + mp2.reset( new GeoTexSvxHatch( + aOutlineRange, + aOutlineRange, + rHatch.getDistance(), + fAngleA + M_PI_4) ); + } + } + + GeoTexSvxMultiHatch::~GeoTexSvxMultiHatch() + { + } + + bool GeoTexSvxMultiHatch::impIsOnHatch(const basegfx::B2DPoint& rUV) const + { + if(mp0->getDistanceToHatch(rUV) < mfLogicPixelSize) + { + return true; + } + + if(mp1 && mp1->getDistanceToHatch(rUV) < mfLogicPixelSize) + { + return true; + } + + if(mp2 && mp2->getDistanceToHatch(rUV) < mfLogicPixelSize) + { + return true; + } + + return false; + } + + void GeoTexSvxMultiHatch::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& rfOpacity) const + { + if(impIsOnHatch(rUV)) + { + rBColor = maColor; + } + else if(!mbFillBackground) + { + rfOpacity = 0.0; + } + } + + void GeoTexSvxMultiHatch::modifyOpacity(const basegfx::B2DPoint& rUV, double& rfOpacity) const + { + if(mbFillBackground || impIsOnHatch(rUV)) + { + rfOpacity = 1.0; + } + else + { + rfOpacity = 0.0; + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/converters.cxx b/drawinglayer/source/tools/converters.cxx new file mode 100644 index 000000000..c0a92a8f4 --- /dev/null +++ b/drawinglayer/source/tools/converters.cxx @@ -0,0 +1,272 @@ +/* -*- 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/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#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 <tools/diagnose_ex.h> + +#include <drawinglayer/converters.hxx> + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/pngwrite.hxx> +#endif + +namespace drawinglayer +{ + + BitmapEx convertToBitmapEx( + drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, + sal_uInt32 nDiscreteWidth, + sal_uInt32 nDiscreteHeight, + sal_uInt32 nMaxSquarePixels) + { + BitmapEx aRetval; + + 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); + +#ifdef DBG_UTIL + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore +#endif + // render content + pContentProcessor->process(aSequence); + + // get content + pContent->EnableMapMode(false); + const Bitmap aContent(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); + } +#endif + + // create BitmapEx result + aRetval = BitmapEx(aContent, AlphaMask(aAlpha)); +#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); + } +#endif + } + + return 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(); + + 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(fWidth), basegfx::fround(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 new file mode 100644 index 000000000..c79b0ded0 --- /dev/null +++ b/drawinglayer/source/tools/emfpbrush.cxx @@ -0,0 +1,317 @@ +/* -*- 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 <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include "emfpbrush.hxx" +#include "emfppath.hxx" + +namespace emfplushelper +{ + EMFPBrush::EMFPBrush() + : type(0) + , additionalFlags(0) + , wrapMode(0) + , firstPointX(0.0) + , firstPointY(0.0) + , aWidth(0.0) + , aHeight(0.0) + , hasTransformation(false) + , blendPoints(0) + , blendFactors(nullptr) + , colorblendPoints(0) + , surroundColorsNumber(0) + , hatchStyle(HatchStyleHorizontal) + { + } + + EMFPBrush::~EMFPBrush() + { + } + + static OUString BrushTypeToString(sal_uInt32 type) + { + switch (type) + { + case BrushTypeSolidColor: return "BrushTypeSolidColor"; + case BrushTypeHatchFill: return "BrushTypeHatchFill"; + case BrushTypeTextureFill: return "BrushTypeTextureFill"; + case BrushTypePathGradient: return "BrushTypePathGradient"; + case BrushTypeLinearGradient: return "BrushTypeLinearGradient"; + } + return ""; + } + + void EMFPBrush::Read(SvStream& s, EmfPlusHelperData const & rR) + { + sal_uInt32 header; + + s.ReadUInt32(header).ReadUInt32(type); + + SAL_INFO("drawinglayer.emf", "EMF+\t\t\tHeader: 0x" << std::hex << header); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\tType: " << BrushTypeToString(type) << "(0x" << type << ")" << std::dec); + + switch (type) + { + case BrushTypeSolidColor: + { + sal_uInt32 color; + s.ReadUInt32(color); + + solidColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tSolid color: 0x" << std::hex << color << std::dec); + break; + } + case BrushTypeHatchFill: + { + sal_uInt32 style; + sal_uInt32 foregroundColor; + sal_uInt32 backgroundColor; + s.ReadUInt32(style); + s.ReadUInt32(foregroundColor); + s.ReadUInt32(backgroundColor); + + hatchStyle = static_cast<EmfPlusHatchStyle>(style); + solidColor = ::Color(ColorAlpha, (foregroundColor >> 24), (foregroundColor >> 16) & 0xff, (foregroundColor >> 8) & 0xff, foregroundColor & 0xff); + secondColor = ::Color(ColorAlpha, (backgroundColor >> 24), (backgroundColor >> 16) & 0xff, (backgroundColor >> 8) & 0xff, backgroundColor & 0xff); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tHatch style: 0x" << std::hex << style); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tForeground color: 0x" << solidColor.AsRGBHexString()); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tBackground color: 0x" << secondColor.AsRGBHexString()); + break; + } + case BrushTypeTextureFill: + { + SAL_WARN("drawinglayer.emf", "EMF+\tTODO: implement BrushTypeTextureFill brush"); + break; + } + case BrushTypePathGradient: + { + s.ReadUInt32(additionalFlags).ReadInt32(wrapMode); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tAdditional flags: 0x" << std::hex << additionalFlags << std::dec); + sal_uInt32 color; + s.ReadUInt32(color); + solidColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); + 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.ReadUInt32(surroundColorsNumber); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t number of surround colors: " << surroundColorsNumber); + + surroundColors.reset( new ::Color[surroundColorsNumber] ); + + 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); + if (i == 0) + secondColor = surroundColors[0]; + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tSurround color[" << i << "]: 0x" << std::hex << color << std::dec); + } + + if (additionalFlags & 0x01) // BrushDataPath + { + sal_Int32 pathLength; + + s.ReadInt32(pathLength); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPath length: " << pathLength); + + sal_uInt64 const pos = s.Tell(); + + sal_uInt32 pathHeader; + sal_Int32 pathPoints, pathFlags; + s.ReadUInt32(pathHeader).ReadInt32(pathPoints).ReadInt32(pathFlags); + + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPath (brush path gradient)"); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t\tHeader: 0x" << std::hex << pathHeader); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t\tPoints: " << std::dec << pathPoints); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t\tAdditional flags: 0x" << std::hex << pathFlags << std::dec); + + path.reset( new EMFPPath(pathPoints) ); + path->Read(s, pathFlags); + + s.Seek(pos + pathLength); + + const ::basegfx::B2DRectangle aBounds(::basegfx::utils::getRange(path->GetPolygon(rR, false))); + aWidth = aBounds.getWidth(); + aHeight = aBounds.getHeight(); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPolygon bounding box: " << aBounds.getMinX() << "," << aBounds.getMinY() << " " + << aBounds.getWidth() << "x" << aBounds.getHeight()); + } + else + { + sal_Int32 boundaryPointCount; + s.ReadInt32(boundaryPointCount); + + sal_uInt64 const pos = s.Tell(); + SAL_INFO("drawinglayer.emf", "EMF+\t use boundary, points: " << boundaryPointCount); + path.reset( new EMFPPath(boundaryPointCount) ); + path->Read(s, 0x0); + + s.Seek(pos + 8 * boundaryPointCount); + + const ::basegfx::B2DRectangle aBounds(::basegfx::utils::getRange(path->GetPolygon(rR, false))); + aWidth = aBounds.getWidth(); + aHeight = aBounds.getHeight(); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPolygon bounding box: " << aBounds.getMinX() << "," << aBounds.getMinY() << " " + << aBounds.getWidth() << "x" << aBounds.getHeight()); + } + + 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) // BrushDataBlendFactorsH + { + s.ReadUInt32(blendPoints); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tuse blend, points: " << blendPoints); + blendPositions.reset( new float[2 * blendPoints] ); + blendFactors = blendPositions.get() + blendPoints; + + 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 (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) // BrushDataPresetColors + { + s.ReadUInt32(colorblendPoints); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse color blend, points: " << colorblendPoints); + colorblendPositions.reset( new float[colorblendPoints] ); + colorblendColors.reset( new ::Color[colorblendPoints] ); + + for (sal_uInt32 i = 0; i < colorblendPoints; i++) + { + s.ReadFloat(colorblendPositions[i]); + SAL_INFO("drawinglayer.emf", "EMF+\tposition[" << i << "]: " << colorblendPositions[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); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tColor[" << i << "]: 0x" << std::hex << color << std::dec); + } + } + + break; + } + case BrushTypeLinearGradient: + { + s.ReadUInt32(additionalFlags).ReadInt32(wrapMode); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tLinear gradient, additional flags: 0x" << std::hex << additionalFlags << std::dec << ", wrapMode: " << wrapMode); + s.ReadFloat(firstPointX).ReadFloat(firstPointY).ReadFloat(aWidth).ReadFloat(aHeight); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tFirst gradient point: " << firstPointX << ":" << firstPointY + << ", size " << aWidth << "x" << aHeight); + sal_uInt32 color; + s.ReadUInt32(color); + solidColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tfirst color: 0x" << std::hex << color << std::dec); + s.ReadUInt32(color); + secondColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tsecond color: 0x" << std::hex << color << std::dec); + + // repeated colors, unknown meaning, see http://www.aces.uiuc.edu/~jhtodd/Metafile/MetafileRecords/ObjectBrush.html + s.ReadUInt32(color); + s.ReadUInt32(color); + + 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) // BrushDataBlendFactorsH + { + s.ReadUInt32(blendPoints); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse blend, points: " << blendPoints); + blendPositions.reset( new float[2 * blendPoints] ); + blendFactors = blendPositions.get() + blendPoints; + + 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 (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) + { + s.ReadUInt32(colorblendPoints); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse color blend, points: " << colorblendPoints); + colorblendPositions.reset( new float[colorblendPoints] ); + colorblendColors.reset( new ::Color[colorblendPoints] ); + + 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 (sal_uInt32 i = 0; i < colorblendPoints; i++) + { + s.ReadUInt32(color); + colorblendColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tColor[" << i << "]: 0x" << std::hex << color << std::dec); + } + } + + break; + } + default: + { + SAL_WARN("drawinglayer.emf", "EMF+\tunhandled brush type: " << std::hex << type << std::dec); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpbrush.hxx b/drawinglayer/source/tools/emfpbrush.hxx new file mode 100644 index 000000000..5f272a63a --- /dev/null +++ b/drawinglayer/source/tools/emfpbrush.hxx @@ -0,0 +1,128 @@ +/* -*- 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 "emfphelperdata.hxx" +#include <tools/color.hxx> + +namespace emfplushelper +{ + enum EmfPlusHatchStyle + { + HatchStyleHorizontal = 0x00000000, + HatchStyleVertical = 0x00000001, + HatchStyleForwardDiagonal = 0x00000002, + HatchStyleBackwardDiagonal = 0x00000003, + HatchStyleLargeGrid = 0x00000004, + HatchStyleDiagonalCross = 0x00000005, + HatchStyle05Percent = 0x00000006, + HatchStyle10Percent = 0x00000007, + HatchStyle20Percent = 0x00000008, + HatchStyle25Percent = 0x00000009, + HatchStyle30Percent = 0x0000000A, + HatchStyle40Percent = 0x0000000B, + HatchStyle50Percent = 0x0000000C, + HatchStyle60Percent = 0x0000000D, + HatchStyle70Percent = 0x0000000E, + HatchStyle75Percent = 0x0000000F, + HatchStyle80Percent = 0x00000010, + HatchStyle90Percent = 0x00000011, + HatchStyleLightDownwardDiagonal = 0x00000012, + HatchStyleLightUpwardDiagonal = 0x00000013, + HatchStyleDarkDownwardDiagonal = 0x00000014, + HatchStyleDarkUpwardDiagonal = 0x00000015, + HatchStyleWideDownwardDiagonal = 0x00000016, + HatchStyleWideUpwardDiagonal = 0x00000017, + HatchStyleLightVertical = 0x00000018, + HatchStyleLightHorizontal = 0x00000019, + HatchStyleNarrowVertical = 0x0000001A, + HatchStyleNarrowHorizontal = 0x0000001B, + HatchStyleDarkVertical = 0x0000001C, + HatchStyleDarkHorizontal = 0x0000001D, + HatchStyleDashedDownwardDiagonal = 0x0000001E, + HatchStyleDashedUpwardDiagonal = 0x0000001F, + HatchStyleDashedHorizontal = 0x00000020, + HatchStyleDashedVertical = 0x00000021, + HatchStyleSmallConfetti = 0x00000022, + HatchStyleLargeConfetti = 0x00000023, + HatchStyleZigZag = 0x00000024, + HatchStyleWave = 0x00000025, + HatchStyleDiagonalBrick = 0x00000026, + HatchStyleHorizontalBrick = 0x00000027, + HatchStyleWeave = 0x00000028, + HatchStylePlaid = 0x00000029, + HatchStyleDivot = 0x0000002A, + HatchStyleDottedGrid = 0x0000002B, + HatchStyleDottedDiamond = 0x0000002C, + HatchStyleShingle = 0x0000002D, + HatchStyleTrellis = 0x0000002E, + HatchStyleSphere = 0x0000002F, + HatchStyleSmallGrid = 0x00000030, + HatchStyleSmallCheckerBoard = 0x00000031, + HatchStyleLargeCheckerBoard = 0x00000032, + HatchStyleOutlinedDiamond = 0x00000033, + HatchStyleSolidDiamond = 0x00000034 + }; + + enum EmfPlusBrushType + { + BrushTypeSolidColor = 0x00000000, + BrushTypeHatchFill = 0x00000001, + BrushTypeTextureFill = 0x00000002, + BrushTypePathGradient = 0x00000003, + BrushTypeLinearGradient = 0x00000004 + }; + + struct EMFPPath; + + struct EMFPBrush : public EMFPObject + { + ::Color solidColor; + sal_uInt32 type; + sal_uInt32 additionalFlags; + + /* linear gradient */ + sal_Int32 wrapMode; + float firstPointX, firstPointY, aWidth, aHeight; + ::Color secondColor; // first color is stored in solidColor; + basegfx::B2DHomMatrix brush_transformation; + bool hasTransformation; + sal_uInt32 blendPoints; + std::unique_ptr<float[]> blendPositions; + float* blendFactors; + sal_uInt32 colorblendPoints; + std::unique_ptr<float[]> colorblendPositions; + std::unique_ptr<::Color[]> colorblendColors; + sal_uInt32 surroundColorsNumber; + std::unique_ptr<::Color[]> surroundColors; + std::unique_ptr<EMFPPath> path; + EmfPlusHatchStyle hatchStyle; + + EMFPBrush(); + virtual ~EMFPBrush() override; + + sal_uInt32 GetType() const { return type; } + const ::Color& GetColor() const { return solidColor; } + + void Read(SvStream& s, EmfPlusHelperData const & rR); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpcustomlinecap.cxx b/drawinglayer/source/tools/emfpcustomlinecap.cxx new file mode 100644 index 000000000..e457a36cc --- /dev/null +++ b/drawinglayer/source/tools/emfpcustomlinecap.cxx @@ -0,0 +1,121 @@ +/* -*- 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/log.hxx> +#include "emfpcustomlinecap.hxx" +#include "emfppath.hxx" +#include "emfppen.hxx" +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +using namespace ::com::sun::star; +using namespace ::basegfx; + +namespace emfplushelper +{ + const sal_uInt32 EmfPlusCustomLineCapDataTypeDefault = 0x00000000; + const sal_uInt32 EmfPlusCustomLineCapDataTypeAdjustableArrow = 0x00000001; + const sal_uInt32 EmfPlusCustomLineCapDataFillPath = 0x00000001; + const sal_uInt32 EmfPlusCustomLineCapDataLinePath = 0x00000002; + + EMFPCustomLineCap::EMFPCustomLineCap() + : EMFPObject() + , type(0) + , strokeStartCap(0) + , strokeEndCap(0) + , strokeJoin(0) + , miterLimit(0.0) + , widthScale(0.0) + , mbIsFilled(false) + { + } + + void EMFPCustomLineCap::ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill) + { + sal_Int32 pathLength; + s.ReadInt32(pathLength); + SAL_INFO("drawinglayer.emf", "EMF+\t\tpath length: " << pathLength); + sal_uInt32 pathHeader; + sal_Int32 pathPoints, pathFlags; + s.ReadUInt32(pathHeader).ReadInt32(pathPoints).ReadInt32(pathFlags); + SAL_INFO("drawinglayer.emf", "EMF+\t\tpath (custom cap line path)"); + SAL_INFO("drawinglayer.emf", "EMF+\t\theader: 0x" << std::hex << pathHeader << " points: " << std::dec << pathPoints << " additional flags: 0x" << std::hex << pathFlags << std::dec); + + 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; + } + + void EMFPCustomLineCap::Read(SvStream& s, EmfPlusHelperData const & rR) + { + sal_uInt32 header; + s.ReadUInt32(header).ReadUInt32(type); + SAL_INFO("drawinglayer.emf", "EMF+\t\tcustom cap"); + SAL_INFO("drawinglayer.emf", "EMF+\t\theader: 0x" << std::hex << header << " type: " << type << std::dec); + + if (type == EmfPlusCustomLineCapDataTypeDefault) + { + sal_uInt32 customLineCapDataFlags, baseCap; + float baseInset; + float fillHotSpotX, fillHotSpotY, strokeHotSpotX, strokeHotSpotY; + + s.ReadUInt32(customLineCapDataFlags).ReadUInt32(baseCap).ReadFloat(baseInset) + .ReadUInt32(strokeStartCap).ReadUInt32(strokeEndCap).ReadUInt32(strokeJoin) + .ReadFloat(miterLimit).ReadFloat(widthScale) + .ReadFloat(fillHotSpotX).ReadFloat(fillHotSpotY).ReadFloat(strokeHotSpotX).ReadFloat(strokeHotSpotY); + + 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); + + if (customLineCapDataFlags & EmfPlusCustomLineCapDataFillPath) + { + ReadPath(s, rR, true); + } + + if (customLineCapDataFlags & EmfPlusCustomLineCapDataLinePath) + { + ReadPath(s, rR, false); + } + } + else if (type == EmfPlusCustomLineCapDataTypeAdjustableArrow) + { + // TODO only reads the data, does not use them [I've had + // no test document to be able to implement it] + + sal_Int32 fillState; + float width, height, middleInset, unusedHotSpot; + + 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); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpcustomlinecap.hxx b/drawinglayer/source/tools/emfpcustomlinecap.hxx new file mode 100644 index 000000000..22ed6be6d --- /dev/null +++ b/drawinglayer/source/tools/emfpcustomlinecap.hxx @@ -0,0 +1,41 @@ +/* -*- 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 "emfphelperdata.hxx" + +namespace emfplushelper +{ + struct EMFPCustomLineCap : public EMFPObject + { + sal_uInt32 type; + sal_uInt32 strokeStartCap, strokeEndCap, strokeJoin; + float miterLimit, widthScale; + basegfx::B2DPolyPolygon polygon; + bool mbIsFilled; + + EMFPCustomLineCap(); + + void ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill); + void Read(SvStream& s, EmfPlusHelperData const & rR); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpfont.cxx b/drawinglayer/source/tools/emfpfont.cxx new file mode 100644 index 000000000..3fd6537e7 --- /dev/null +++ b/drawinglayer/source/tools/emfpfont.cxx @@ -0,0 +1,76 @@ +/* -*- 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/log.hxx> +#include <rtl/ustrbuf.hxx> +#include "emfpfont.hxx" + +namespace emfplushelper +{ + static OUString FontStyleToString(sal_uInt32 style) + { + OUStringBuffer sStyle; + + if (style & FontStyleBold) + sStyle = "\n\t\t\tFontStyleBold"; + + if (style & FontStyleItalic) + sStyle.append("\n\t\t\tFontStyleItalic"); + + if (style & FontStyleUnderline) + sStyle.append("\n\t\t\tFontStyleUnderline"); + + if (style & FontStyleStrikeout) + sStyle.append("\n\t\t\tFontStyleStrikeout"); + + return sStyle.makeStringAndClear(); + } + + void EMFPFont::Read(SvMemoryStream &s) + { + sal_uInt32 header; + sal_uInt32 reserved; + sal_uInt32 length; + s.ReadUInt32(header).ReadFloat(emSize).ReadUInt32(sizeUnit).ReadInt32(fontFlags).ReadUInt32(reserved).ReadUInt32(length); + SAL_WARN_IF((header >> 12) != 0xdbc01, "drawinglayer.emf", "Invalid header - not 0xdbc01"); + SAL_INFO("drawinglayer.emf", "EMF+\tHeader: 0x" << std::hex << (header >> 12)); + SAL_INFO("drawinglayer.emf", "EMF+\tVersion: 0x" << (header & 0x1fff)); + SAL_INFO("drawinglayer.emf", "EMF+\tSize: " << std::dec << emSize); + SAL_INFO("drawinglayer.emf", "EMF+\tUnit: " << UnitTypeToString(sizeUnit) << " (0x" << std::hex << sizeUnit << ")" << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\tFlags: " << FontStyleToString(fontFlags) << " (0x" << std::hex << fontFlags << ")"); + SAL_INFO("drawinglayer.emf", "EMF+\tReserved: 0x" << reserved << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\tLength: " << length); + + if (length <= 0 || length >= 0x4000) + return; + + rtl_uString *pStr = rtl_uString_alloc(length); + sal_Unicode *chars = pStr->buffer; + + for (sal_uInt32 i = 0; i < length; ++i) + { + s.ReadUtf16(chars[i]); + } + + family = OUString(pStr, SAL_NO_ACQUIRE); + SAL_INFO("drawinglayer.emf", "EMF+\tFamily: " << family); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpfont.hxx b/drawinglayer/source/tools/emfpfont.hxx new file mode 100644 index 000000000..3f9e24166 --- /dev/null +++ b/drawinglayer/source/tools/emfpfont.hxx @@ -0,0 +1,48 @@ +/* -*- 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 "emfphelperdata.hxx" + +namespace emfplushelper +{ + const sal_uInt32 FontStyleBold = 0x00000001; + const sal_uInt32 FontStyleItalic = 0x00000002; + const sal_uInt32 FontStyleUnderline = 0x00000004; + const sal_uInt32 FontStyleStrikeout = 0x00000008; + + + struct EMFPFont : public EMFPObject + { + float emSize; + sal_uInt32 sizeUnit; + sal_Int32 fontFlags; + OUString family; + + void Read(SvMemoryStream &s); + + bool Bold() const { return fontFlags & FontStyleBold; } + bool Italic() const { return fontFlags & FontStyleItalic; } + bool Underline() const { return fontFlags & FontStyleUnderline; } + bool Strikeout() const { return fontFlags & FontStyleStrikeout; } + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx new file mode 100644 index 000000000..aaa9b1bf7 --- /dev/null +++ b/drawinglayer/source/tools/emfphelperdata.cxx @@ -0,0 +1,2268 @@ +/* -*- 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 "emfpcustomlinecap.hxx" +#include "emfphelperdata.hxx" +#include "emfpbrush.hxx" +#include "emfppen.hxx" +#include "emfppath.hxx" +#include "emfpregion.hxx" +#include "emfpimage.hxx" +#include "emfpimageattributes.hxx" +#include "emfpfont.hxx" +#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> +#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <algorithm> + +namespace emfplushelper +{ + + enum + { + WrapModeTile = 0x00000000, + WrapModeTileFlipX = 0x00000001, + WrapModeTileFlipY = 0x00000002, + WrapModeTileFlipXY = 0x00000003, + WrapModeClamp = 0x00000004 + }; + + const char* emfTypeToName(sal_uInt16 type) + { + switch (type) + { + case EmfPlusRecordTypeHeader: return "EmfPlusRecordTypeHeader"; + case EmfPlusRecordTypeEndOfFile: return "EmfPlusRecordTypeEndOfFile"; + case EmfPlusRecordTypeComment: return "EmfPlusRecordTypeComment"; + case EmfPlusRecordTypeGetDC: return "EmfPlusRecordTypeGetDC"; + case EmfPlusRecordTypeObject: return "EmfPlusRecordTypeObject"; + case EmfPlusRecordTypeFillRects: return "EmfPlusRecordTypeFillRects"; + case EmfPlusRecordTypeDrawRects: return "EmfPlusRecordTypeDrawRects"; + case EmfPlusRecordTypeFillPolygon: return "EmfPlusRecordTypeFillPolygon"; + case EmfPlusRecordTypeDrawLines: return "EmfPlusRecordTypeDrawLines"; + case EmfPlusRecordTypeFillClosedCurve: return "EmfPlusRecordTypeFillClosedCurve"; + case EmfPlusRecordTypeFillEllipse: return "EmfPlusRecordTypeFillEllipse"; + case EmfPlusRecordTypeDrawEllipse: return "EmfPlusRecordTypeDrawEllipse"; + case EmfPlusRecordTypeFillPie: return "EmfPlusRecordTypeFillPie"; + case EmfPlusRecordTypeDrawPie: return "EmfPlusRecordTypeDrawPie"; + case EmfPlusRecordTypeDrawArc: return "EmfPlusRecordTypeDrawArc"; + case EmfPlusRecordTypeFillRegion: return "EmfPlusRecordTypeFillRegion"; + case EmfPlusRecordTypeFillPath: return "EmfPlusRecordTypeFillPath"; + case EmfPlusRecordTypeDrawPath: return "EmfPlusRecordTypeDrawPath"; + case EmfPlusRecordTypeDrawBeziers: return "EmfPlusRecordTypeDrawBeziers"; + case EmfPlusRecordTypeDrawClosedCurve: return "EmfPlusRecordTypeDrawClosedCurve"; + case EmfPlusRecordTypeDrawImage: return "EmfPlusRecordTypeDrawImage"; + case EmfPlusRecordTypeDrawImagePoints: return "EmfPlusRecordTypeDrawImagePoints"; + case EmfPlusRecordTypeDrawString: return "EmfPlusRecordTypeDrawString"; + case EmfPlusRecordTypeSetRenderingOrigin: return "EmfPlusRecordTypeSetRenderingOrigin"; + case EmfPlusRecordTypeSetAntiAliasMode: return "EmfPlusRecordTypeSetAntiAliasMode"; + case EmfPlusRecordTypeSetTextRenderingHint: return "EmfPlusRecordTypeSetTextRenderingHint"; + case EmfPlusRecordTypeSetTextContrast: return "EmfPlusRecordTypeSetTextContrast"; + case EmfPlusRecordTypeSetInterpolationMode: return "EmfPlusRecordTypeSetInterpolationMode"; + case EmfPlusRecordTypeSetPixelOffsetMode: return "EmfPlusRecordTypeSetPixelOffsetMode"; + case EmfPlusRecordTypeSetCompositingQuality: return "EmfPlusRecordTypeSetCompositingQuality"; + case EmfPlusRecordTypeSave: return "EmfPlusRecordTypeSave"; + case EmfPlusRecordTypeRestore: return "EmfPlusRecordTypeRestore"; + case EmfPlusRecordTypeBeginContainer: return "EmfPlusRecordTypeBeginContainer"; + case EmfPlusRecordTypeBeginContainerNoParams: return "EmfPlusRecordTypeBeginContainerNoParams"; + case EmfPlusRecordTypeEndContainer: return "EmfPlusRecordTypeEndContainer"; + case EmfPlusRecordTypeSetWorldTransform: return "EmfPlusRecordTypeSetWorldTransform"; + case EmfPlusRecordTypeResetWorldTransform: return "EmfPlusRecordTypeResetWorldTransform"; + case EmfPlusRecordTypeMultiplyWorldTransform: return "EmfPlusRecordTypeMultiplyWorldTransform"; + case EmfPlusRecordTypeTranslateWorldTransform: return "EmfPlusRecordTypeTranslateWorldTransform"; + case EmfPlusRecordTypeScaleWorldTransform: return "EmfPlusRecordTypeScaleWorldTransform"; + case EmfPlusRecordTypeSetPageTransform: return "EmfPlusRecordTypeSetPageTransform"; + case EmfPlusRecordTypeResetClip: return "EmfPlusRecordTypeResetClip"; + case EmfPlusRecordTypeSetClipRect: return "EmfPlusRecordTypeSetClipRect"; + case EmfPlusRecordTypeSetClipPath: return "EmfPlusRecordTypeSetClipPath"; + case EmfPlusRecordTypeSetClipRegion: return "EmfPlusRecordTypeSetClipRegion"; + case EmfPlusRecordTypeOffsetClip: return "EmfPlusRecordTypeOffsetClip"; + case EmfPlusRecordTypeDrawDriverString: return "EmfPlusRecordTypeDrawDriverString"; + } + return ""; + } + + static OUString emfObjectToName(sal_uInt16 type) + { + switch (type) + { + case EmfPlusObjectTypeBrush: return "EmfPlusObjectTypeBrush"; + case EmfPlusObjectTypePen: return "EmfPlusObjectTypePen"; + case EmfPlusObjectTypePath: return "EmfPlusObjectTypePath"; + case EmfPlusObjectTypeRegion: return "EmfPlusObjectTypeRegion"; + case EmfPlusObjectTypeImage: return "EmfPlusObjectTypeImage"; + case EmfPlusObjectTypeFont: return "EmfPlusObjectTypeFont"; + case EmfPlusObjectTypeStringFormat: return "EmfPlusObjectTypeStringFormat"; + case EmfPlusObjectTypeImageAttributes: return "EmfPlusObjectTypeImageAttributes"; + case EmfPlusObjectTypeCustomLineCap: return "EmfPlusObjectTypeCustomLineCap"; + } + return ""; + } + + static OUString PixelOffsetModeToString(sal_uInt16 nPixelOffset) + { + switch (nPixelOffset) + { + case PixelOffsetMode::PixelOffsetModeDefault: return "PixelOffsetModeDefault"; + case PixelOffsetMode::PixelOffsetModeHighSpeed: return "PixelOffsetModeHighSpeed"; + case PixelOffsetMode::PixelOffsetModeHighQuality: return "PixelOffsetModeHighQuality"; + case PixelOffsetMode::PixelOffsetModeNone: return "PixelOffsetModeNone"; + case PixelOffsetMode::PixelOffsetModeHalf: return "PixelOffsetModeHalf"; + } + return ""; + } + + static OUString SmoothingModeToString(sal_uInt16 nSmoothMode) + { + switch (nSmoothMode) + { + case SmoothingMode::SmoothingModeDefault: return "SmoothingModeDefault"; + case SmoothingMode::SmoothingModeHighSpeed: return "SmoothModeHighSpeed"; + case SmoothingMode::SmoothingModeHighQuality: return "SmoothingModeHighQuality"; + case SmoothingMode::SmoothingModeNone: return "SmoothingModeNone"; + case SmoothingMode::SmoothingModeAntiAlias8x4: return "SmoothingModeAntiAlias8x4"; + case SmoothingMode::SmoothingModeAntiAlias8x8: return "SmoothingModeAntiAlias8x8"; + } + return ""; + } + + static OUString TextRenderingHintToString(sal_uInt16 nHint) + { + switch (nHint) + { + case TextRenderingHint::TextRenderingHintSystemDefault: return "TextRenderingHintSystemDefault"; + case TextRenderingHint::TextRenderingHintSingleBitPerPixelGridFit: return "TextRenderingHintSingleBitPerPixelGridFit"; + case TextRenderingHint::TextRenderingHintSingleBitPerPixel: return "TextRenderingHintSingleBitPerPixel"; + case TextRenderingHint::TextRenderingHintAntialiasGridFit: return "TextRenderingHintAntialiasGridFit"; + case TextRenderingHint::TextRenderingHintAntialias: return "TextRenderingHintAntialias"; + case TextRenderingHint::TextRenderingHintClearTypeGridFit: return "TextRenderingHintClearTypeGridFit"; + } + return ""; + } + + static OUString InterpolationModeToString(sal_uInt16 nMode) + { + switch (nMode) + { + case InterpolationMode::InterpolationModeDefault: return "InterpolationModeDefault"; + case InterpolationMode::InterpolationModeLowQuality: return "InterpolationModeLowQuality"; + case InterpolationMode::InterpolationModeHighQuality: return "InterpolationModeHighQuality"; + case InterpolationMode::InterpolationModeBilinear: return "InterpolationModeBilinear"; + case InterpolationMode::InterpolationModeBicubic: return "InterpolationModeBicubic"; + case InterpolationMode::InterpolationModeNearestNeighbor: return "InterpolationModeNearestNeighbor"; + case InterpolationMode::InterpolationModeHighQualityBilinear: return "InterpolationModeHighQualityBilinear"; + case InterpolationMode::InterpolationModeHighQualityBicubic: return "InterpolationModeHighQualityBicubic"; + } + return ""; + } + + OUString UnitTypeToString(sal_uInt16 nType) + { + switch (nType) + { + case UnitTypeWorld: return "UnitTypeWorld"; + case UnitTypeDisplay: return "UnitTypeDisplay"; + case UnitTypePixel: return "UnitTypePixel"; + case UnitTypePoint: return "UnitTypePoint"; + case UnitTypeInch: return "UnitTypeInch"; + case UnitTypeDocument: return "UnitTypeDocument"; + case UnitTypeMillimeter: return "UnitTypeMillimeter"; + } + return ""; + } + + static bool IsBrush(sal_uInt16 flags) + { + return (!((flags >> 15) & 0x0001)); + } + + static OUString BrushIDToString(sal_uInt16 flags, sal_uInt32 brushid) + { + if (IsBrush(flags)) + return "EmfPlusBrush ID: " + OUString::number(brushid); + else + return "ARGB: 0x" + OUString::number(brushid, 16); + } + + EMFPObject::~EMFPObject() + { + } + + float EmfPlusHelperData::getUnitToPixelMultiplier(const UnitType aUnitType, const sal_uInt32 aDPI) + { + switch (aUnitType) + { + case UnitTypePixel: + return 1.0f; + + case UnitTypePoint: + return aDPI / 72.0; + + case UnitTypeInch: + return aDPI; + + case UnitTypeMillimeter: + return aDPI / 25.4; + + case UnitTypeDocument: + return aDPI / 300.0; + + case UnitTypeWorld: + case UnitTypeDisplay: + SAL_WARN("drawinglayer.emf", "EMF+\t Converting to World/Display."); + return 1.0f; + + default: + SAL_WARN("drawinglayer.emf", "EMF+\tTODO Unimplemented support of Unit Type: 0x" << std::hex << aUnitType); + return 1.0f; + } + } + + void EmfPlusHelperData::processObjectRecord(SvMemoryStream& rObjectStream, sal_uInt16 flags, sal_uInt32 dataSize, bool bUseWholeStream) + { + sal_uInt16 objecttype = flags & 0x7f00; + sal_uInt16 index = flags & 0xff; + SAL_INFO("drawinglayer.emf", "EMF+ Object: " << emfObjectToName(objecttype) << " (0x" << objecttype << ")"); + SAL_INFO("drawinglayer.emf", "EMF+\tObject slot: " << index); + SAL_INFO("drawinglayer.emf", "EMF+\tFlags: " << (flags & 0xff00)); + + switch (objecttype) + { + case EmfPlusObjectTypeBrush: + { + EMFPBrush *brush = new EMFPBrush(); + maEMFPObjects[index].reset(brush); + brush->Read(rObjectStream, *this); + break; + } + case EmfPlusObjectTypePen: + { + EMFPPen *pen = new EMFPPen(); + maEMFPObjects[index].reset(pen); + pen->Read(rObjectStream, *this); + pen->penWidth = pen->penWidth * getUnitToPixelMultiplier(static_cast<UnitType>(pen->penUnit), mnHDPI); + break; + } + case EmfPlusObjectTypePath: + { + sal_uInt32 aVersion, aPathPointCount, aPathPointFlags; + + rObjectStream.ReadUInt32(aVersion).ReadUInt32(aPathPointCount).ReadUInt32(aPathPointFlags); + SAL_INFO("drawinglayer.emf", "EMF+\t\tVersion: 0x" << std::hex << aVersion); + SAL_INFO("drawinglayer.emf", "EMF+\t\tNumber of points: " << std::dec << aPathPointCount); + SAL_INFO("drawinglayer.emf", "EMF+\t\tPath point flags: 0x" << std::hex << aPathPointFlags << std::dec); + EMFPPath *path = new EMFPPath(aPathPointCount); + maEMFPObjects[index].reset(path); + path->Read(rObjectStream, aPathPointFlags); + break; + } + case EmfPlusObjectTypeRegion: + { + EMFPRegion *region = new EMFPRegion(); + maEMFPObjects[index].reset(region); + region->ReadRegion(rObjectStream, *this); + break; + } + case EmfPlusObjectTypeImage: + { + EMFPImage *image = new EMFPImage; + maEMFPObjects[index].reset(image); + image->type = 0; + image->width = 0; + image->height = 0; + image->stride = 0; + image->pixelFormat = 0; + image->Read(rObjectStream, dataSize, bUseWholeStream); + break; + } + case EmfPlusObjectTypeFont: + { + EMFPFont *font = new EMFPFont; + maEMFPObjects[index].reset(font); + font->emSize = 0; + font->sizeUnit = 0; + font->fontFlags = 0; + font->Read(rObjectStream); + // tdf#113624 Convert unit to Pixels + font->emSize = font->emSize * getUnitToPixelMultiplier(static_cast<UnitType>(font->sizeUnit), mnHDPI); + + break; + } + case EmfPlusObjectTypeStringFormat: + { + EMFPStringFormat *stringFormat = new EMFPStringFormat(); + maEMFPObjects[index].reset(stringFormat); + stringFormat->Read(rObjectStream); + break; + } + case EmfPlusObjectTypeImageAttributes: + { + EMFPImageAttributes *imageAttributes = new EMFPImageAttributes(); + maEMFPObjects[index].reset(imageAttributes); + imageAttributes->Read(rObjectStream); + break; + } + case EmfPlusObjectTypeCustomLineCap: + { + SAL_WARN("drawinglayer.emf", "EMF+\t TODO Object type 'custom line cap' not yet implemented"); + break; + } + default: + { + SAL_WARN("drawinglayer.emf", "EMF+\t TODO Object unhandled flags: 0x" << std::hex << (flags & 0xff00) << std::dec); + } + } + } + + void EmfPlusHelperData::ReadPoint(SvStream& s, float& x, float& y, sal_uInt32 flags) + { + if (flags & 0x800) + { + // specifies a location in the coordinate space that is relative to + // the location specified by the previous element in the array. In the case of the first element in + // PointData, a previous location at coordinates (0,0) is assumed. + SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO Relative coordinates bit detected. Implement parse EMFPlusPointR"); + } + + if (flags & 0x4000) + { + sal_Int16 ix, iy; + + s.ReadInt16(ix).ReadInt16(iy); + + x = ix; + y = iy; + } + else + { + s.ReadFloat(x).ReadFloat(y); + } + } + + void EmfPlusHelperData::ReadRectangle(SvStream& s, float& x, float& y, float &width, float& height, bool bCompressed) + { + if (bCompressed) + { + sal_Int16 ix, iy, iw, ih; + + s.ReadInt16(ix).ReadInt16(iy).ReadInt16(iw).ReadInt16(ih); + + x = ix; + y = iy; + width = iw; + height = ih; + } + else + { + s.ReadFloat(x).ReadFloat(y).ReadFloat(width).ReadFloat(height); + } + } + + bool EmfPlusHelperData::readXForm(SvStream& rIn, basegfx::B2DHomMatrix& rTarget) + { + rTarget.identity(); + + if (sizeof(float) != 4) + { + OSL_FAIL("EnhWMFReader::sizeof( float ) != 4"); + return false; + } + else + { + float eM11(0.0); + float eM12(0.0); + float eM21(0.0); + float eM22(0.0); + float eDx(0.0); + float eDy(0.0); + rIn.ReadFloat(eM11).ReadFloat(eM12).ReadFloat(eM21).ReadFloat(eM22).ReadFloat(eDx).ReadFloat(eDy); + rTarget = basegfx::B2DHomMatrix( + eM11, eM21, eDx, + eM12, eM22, eDy); + } + + return true; + } + + void EmfPlusHelperData::mappingChanged() + { + if (mnPixX == 0 || mnPixY == 0) + { + SAL_WARN("drawinglayer.emf", "dimensions in pixels is 0"); + return; + } + // Call when mnMmX/mnMmY/mnPixX/mnPixY/mnFrameLeft/mnFrameTop/maWorldTransform/ changes. + // Currently not used are mnHDPI/mnVDPI/mnFrameRight/mnFrameBottom. *If* these should + // be used in the future, this method will need to be called. + // + // Re-calculate maMapTransform to contain the complete former transformation so that + // it can be applied by a single matrix multiplication or be added to an encapsulated + // primitive later + // + // To evtl. correct and see where this came from, please compare with the implementations + // of EmfPlusHelperData::MapToDevice and EmfPlusHelperData::Map* in prev versions + maMapTransform = maWorldTransform; + 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 + { + // map in one step using complete MapTransform (see mappingChanged) + return maMapTransform * ::basegfx::B2DPoint(ix, iy); + } + + Color EmfPlusHelperData::EMFPGetBrushColorOrARGBColor(const sal_uInt16 flags, const sal_uInt32 brushIndexOrColor) const { + Color color; + if (flags & 0x8000) // we use a color + { + color = Color(ColorAlpha, (brushIndexOrColor >> 24), (brushIndexOrColor >> 16) & 0xff, + (brushIndexOrColor >> 8) & 0xff, brushIndexOrColor & 0xff); + } + else // we use a brush + { + const EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get()); + if (brush) + { + color = brush->GetColor(); + if (brush->type != BrushTypeSolidColor) + SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO Brush other than solid color is not supported"); + } + } + return color; + } + + void EmfPlusHelperData::GraphicStatePush(GraphicStateMap& map, sal_Int32 index) + { + GraphicStateMap::iterator iter = map.find( index ); + + if ( iter != map.end() ) + { + map.erase( iter ); + SAL_INFO("drawinglayer.emf", "EMF+\t\tStack index: " << index << " found and erased"); + } + + wmfemfhelper::PropertyHolder state = mrPropertyHolders.Current(); + // tdf#112500 We need to save world transform somehow, during graphic state push + state.setTransformation(maWorldTransform); + map[ index ] = state; + } + + void EmfPlusHelperData::GraphicStatePop(GraphicStateMap& map, sal_Int32 index) + { + GraphicStateMap::iterator iter = map.find(index); + + if (iter != map.end()) + { + wmfemfhelper::PropertyHolder state = iter->second; + + maWorldTransform = state.getTransformation(); + 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); + } + } + + drawinglayer::attribute::LineStartEndAttribute + EmfPlusHelperData::CreateLineEnd(const sal_Int32 aCap, const float aPenWidth) const + { + const double pw = mdExtractedYScale * aPenWidth; + if (aCap == LineCapTypeSquare) + { + 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); + } + else if (aCap == LineCapTypeRound) + { + 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); + } + else if (aCap == LineCapTypeTriangle) + { + 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 (aCap == LineCapTypeSquareAnchor) + { + 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); + } + else if (aCap == LineCapTypeRoundAnchor) + { + 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 if (aCap == LineCapTypeDiamondAnchor) + { + 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); + } + else if (aCap == LineCapTypeArrowAnchor) + { + 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(); + } + + 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"); + + if (!(pen && polygon.count())) + return; + + 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); + + 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 + aStart = EmfPlusHelperData::CreateLineEnd(pen->startCap, pen->penWidth); + } + + drawinglayer::attribute::LineStartEndAttribute aEnd; + if (pen->penDataFlags & EmfPlusPenDataEndCap) + { + 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 (pen->GetColor().IsTransparent()) + { + drawinglayer::primitive2d::Primitive2DContainer aContainer; + if (aStart.isDefault() && aEnd.isDefault()) + aContainer.append(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( + polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale)))); + else + { + aContainer.resize(polygon.count()); + for (sal_uInt32 i = 0; i < polygon.count(); i++) + aContainer[i] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D( + polygon.getB2DPolygon(i), lineAttribute, + pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd)); + } + 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::PolyPolygonStrokePrimitive2D( + polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale))); + else + 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); + } + + void EmfPlusHelperData::EMFPPlusFillPolygonSolidColor(const ::basegfx::B2DPolyPolygon& polygon, Color const& color) + { + if (color.GetAlpha() == 0) + return; + + if (!color.IsTransparent()) + { + // not transparent + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + polygon, + color.getBColor())); + } + else + { + const drawinglayer::primitive2d::Primitive2DReference aPrimitive( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + polygon, + color.getBColor())); + + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer { aPrimitive }, + (255 - color.GetAlpha()) / 255.0)); + } + } + + void EmfPlusHelperData::EMFPPlusFillPolygon(const ::basegfx::B2DPolyPolygon& polygon, const bool isColor, const sal_uInt32 brushIndexOrColor) + { + if (!polygon.count()) + return; + + if (isColor) // use Color + { + SAL_INFO("drawinglayer.emf", "EMF+\t\t Fill polygon, ARGB color: 0x" << std::hex << brushIndexOrColor << std::dec); + + // EMF Alpha (1 byte): An 8-bit unsigned integer that specifies the transparency of the background, + // ranging from 0 for completely transparent to 0xFF for completely opaque. + const Color color(ColorAlpha, (brushIndexOrColor >> 24), (brushIndexOrColor >> 16) & 0xff, (brushIndexOrColor >> 8) & 0xff, brushIndexOrColor & 0xff); + EMFPPlusFillPolygonSolidColor(polygon, color); + + mrPropertyHolders.Current().setFillColor(color.getBColor()); + mrPropertyHolders.Current().setFillColorActive(true); + mrPropertyHolders.Current().setLineColorActive(false); + } + else // use Brush + { + 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 + if( !brush ) + return; + + mrPropertyHolders.Current().setFillColorActive(false); + mrPropertyHolders.Current().setLineColorActive(false); + + if (brush->type == BrushTypeSolidColor) + { + Color fillColor = brush->solidColor; + EMFPPlusFillPolygonSolidColor(polygon, fillColor); + } + else if (brush->type == BrushTypeHatchFill) + { + // EMF+ like hatching is currently not supported. These are just color blends which serve as an approximation for some of them + // for the others the hatch "background" color (secondColor in brush) is used. + + bool isHatchBlend = true; + double blendFactor = 0.0; + + switch (brush->hatchStyle) + { + case HatchStyle05Percent: blendFactor = 0.05; break; + case HatchStyle10Percent: blendFactor = 0.10; break; + case HatchStyle20Percent: blendFactor = 0.20; break; + case HatchStyle25Percent: blendFactor = 0.25; break; + case HatchStyle30Percent: blendFactor = 0.30; break; + case HatchStyle40Percent: blendFactor = 0.40; break; + case HatchStyle50Percent: blendFactor = 0.50; break; + case HatchStyle60Percent: blendFactor = 0.60; break; + case HatchStyle70Percent: blendFactor = 0.70; break; + case HatchStyle75Percent: blendFactor = 0.75; break; + case HatchStyle80Percent: blendFactor = 0.80; break; + case HatchStyle90Percent: blendFactor = 0.90; break; + default: + isHatchBlend = false; + break; + } + Color fillColor; + if (isHatchBlend) + { + fillColor = brush->solidColor; + fillColor.Merge(brush->secondColor, static_cast<sal_uInt8>(255 * blendFactor)); + } + else + { + fillColor = brush->secondColor; + } + // temporal solution: create a solid colored polygon + // TODO create a 'real' hatching primitive + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + polygon, + fillColor.getBColor())); + } + else if (brush->type == BrushTypeTextureFill) + { + SAL_WARN("drawinglayer.emf", "EMF+\tTODO: implement BrushTypeTextureFill brush"); + } + else if (brush->type == BrushTypePathGradient || brush->type == BrushTypeLinearGradient) + + { + if (brush->type == BrushTypePathGradient && !(brush->additionalFlags & 0x1)) + { + SAL_WARN("drawinglayer.emf", "EMF+\t TODO Implement displaying BrushTypePathGradient with Boundary: "); + } + ::basegfx::B2DHomMatrix aTextureTransformation; + + if (brush->hasTransformation) { + aTextureTransformation = brush->brush_transformation; + + // adjust aTextureTransformation for our world space: + // -> revert the mapping -> apply the transformation -> map back + basegfx::B2DHomMatrix aInvertedMapTrasform(maMapTransform); + aInvertedMapTrasform.invert(); + aTextureTransformation = maMapTransform * aTextureTransformation * aInvertedMapTrasform; + } + + // select the stored colors + const basegfx::BColor aStartColor = brush->solidColor.getBColor(); + const basegfx::BColor aEndColor = brush->secondColor.getBColor(); + drawinglayer::primitive2d::SvgGradientEntryVector aVector; + + if (brush->blendPositions) + { + SAL_INFO("drawinglayer.emf", "EMF+\t\tUse blend"); + + // store the blendpoints in the vector + for (sal_uInt32 i = 0; i < brush->blendPoints; i++) + { + const double aBlendPoint = brush->blendPositions[i]; + basegfx::BColor aColor; + 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); + } + } + else if (brush->colorblendPositions) + { + SAL_INFO("drawinglayer.emf", "EMF+\t\tUse color blend"); + + // store the colorBlends in the vector + for (sal_uInt32 i = 0; i < brush->colorblendPoints; i++) + { + 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 + { + 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 + // therefore, create a mapping and invert it + basegfx::B2DRange aPolygonRange= polygon.getB2DRange(); + basegfx::B2DHomMatrix aPolygonTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix( + aPolygonRange.getWidth(),aPolygonRange.getHeight(), + aPolygonRange.getMinX(), aPolygonRange.getMinY()); + aPolygonTransformation.invert(); + + if (brush->type == BrushTypeLinearGradient) + { + // support for public enum EmfPlusWrapMode + basegfx::B2DPoint aStartPoint = Map(brush->firstPointX, 0.0); + aStartPoint = aPolygonTransformation * aStartPoint; + basegfx::B2DPoint aEndPoint = Map(brush->firstPointX + brush->aWidth, 0.0); + aEndPoint = aPolygonTransformation * aEndPoint; + + // support for public enum EmfPlusWrapMode + drawinglayer::primitive2d::SpreadMethod aSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Pad); + switch(brush->wrapMode) + { + case WrapModeTile: + case WrapModeTileFlipY: + { + aSpreadMethod = drawinglayer::primitive2d::SpreadMethod::Repeat; + break; + } + case WrapModeTileFlipX: + case WrapModeTileFlipXY: + { + aSpreadMethod = drawinglayer::primitive2d::SpreadMethod::Reflect; + break; + } + default: + break; + } + + // create the same one used for SVG + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D( + aTextureTransformation, + polygon, + std::move(aVector), + aStartPoint, + aEndPoint, + false, // do not use UnitCoordinates + 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; + + // create the same one used for SVG + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D( + aTextureTransformation, + polygon, + std::move(aVector), + aCenterPoint, + 0.7, // relative radius little bigger to cover all elements + true, // use UnitCoordinates to stretch the gradient + drawinglayer::primitive2d::SpreadMethod::Pad, + nullptr)); + } + } + } + } + + EmfPlusHelperData::EmfPlusHelperData( + SvMemoryStream& rMS, + wmfemfhelper::TargetHolders& rTargetHolders, + wmfemfhelper::PropertyHolders& rPropertyHolders) + : mfPageScale(0.0), + mnOriginX(0), + mnOriginY(0), + mnHDPI(0), + mnVDPI(0), + mbSetTextContrast(false), + mnTextContrast(0), + mnFrameLeft(0), + mnFrameTop(0), + mnFrameRight(0), + mnFrameBottom(0), + mnPixX(0), + mnPixY(0), + mnMmX(0), + mnMmY(0), + mbMultipart(false), + mMFlags(0), + mrTargetHolders(rTargetHolders), + mrPropertyHolders(rPropertyHolders), + bIsGetDCProcessing(false) + { + rMS.ReadInt32(mnFrameLeft).ReadInt32(mnFrameTop).ReadInt32(mnFrameRight).ReadInt32(mnFrameBottom); + SAL_INFO("drawinglayer.emf", "EMF+ picture frame: " << mnFrameLeft << "," << mnFrameTop << " - " << mnFrameRight << "," << mnFrameBottom); + rMS.ReadInt32(mnPixX).ReadInt32(mnPixY).ReadInt32(mnMmX).ReadInt32(mnMmY); + SAL_INFO("drawinglayer.emf", "EMF+ ref device pixel size: " << mnPixX << "x" << mnPixY << " mm size: " << mnMmX << "x" << mnMmY); + readXForm(rMS, maBaseTransform); + SAL_INFO("drawinglayer.emf", "EMF+ base transform: " << maBaseTransform); + mappingChanged(); + } + + EmfPlusHelperData::~EmfPlusHelperData() + { + } + + ::basegfx::B2DPolyPolygon EmfPlusHelperData::combineClip(::basegfx::B2DPolyPolygon const & leftPolygon, int combineMode, ::basegfx::B2DPolyPolygon const & rightPolygon) + { + basegfx::B2DPolyPolygon aClippedPolyPolygon; + switch (combineMode) + { + case EmfPlusCombineModeReplace: + { + aClippedPolyPolygon = rightPolygon; + break; + } + case EmfPlusCombineModeIntersect: + { + aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( + leftPolygon, rightPolygon, true, false); + break; + } + case EmfPlusCombineModeUnion: + { + aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationOr(leftPolygon, rightPolygon); + break; + } + case EmfPlusCombineModeXOR: + { + aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationXor(leftPolygon, rightPolygon); + break; + } + case EmfPlusCombineModeExclude: + { + // Replaces the existing region with the part of itself that is not in the new region. + aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationDiff(leftPolygon, rightPolygon); + break; + } + case EmfPlusCombineModeComplement: + { + // Replaces the existing region with the part of the new region that is not in the existing region. + aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationDiff(rightPolygon, leftPolygon); + break; + } + } + return aClippedPolyPolygon; + } + + void EmfPlusHelperData::processEmfPlusData( + SvMemoryStream& rMS, + const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/) + { + sal_uInt64 length = rMS.GetSize(); + + if (length < 12) + SAL_WARN("drawinglayer.emf", "length is less than required header size"); + + // 12 is minimal valid EMF+ record size; remaining bytes are padding + while (length >= 12) + { + sal_uInt16 type, flags; + sal_uInt32 size, dataSize; + sal_uInt64 next; + + rMS.ReadUInt16(type).ReadUInt16(flags).ReadUInt32(size).ReadUInt32(dataSize); + + next = rMS.Tell() + (size - 12); + + if (size < 12) + { + SAL_WARN("drawinglayer.emf", "Size field is less than 12 bytes"); + break; + } + else if (size > length) + { + SAL_WARN("drawinglayer.emf", "Size field is greater than bytes left"); + break; + } + + if (dataSize > (size - 12)) + { + SAL_WARN("drawinglayer.emf", "DataSize field is greater than Size-12"); + break; + } + + SAL_INFO("drawinglayer.emf", "EMF+ " << emfTypeToName(type) << " (0x" << std::hex << type << ")" << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\t record size: " << size); + SAL_INFO("drawinglayer.emf", "EMF+\t flags: 0x" << std::hex << flags << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\t data size: " << dataSize); + + if (bIsGetDCProcessing) + { + 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))) + { + if (!mbMultipart) + { + mbMultipart = true; + mMFlags = flags; + mMStream.Seek(0); + } + + OSL_ENSURE(dataSize >= 4, "No room for TotalObjectSize in EmfPlusContinuedObjectRecord"); + + // 1st 4 bytes are TotalObjectSize + mMStream.WriteBytes(static_cast<const char *>(rMS.GetData()) + rMS.Tell() + 4, dataSize - 4); + SAL_INFO("drawinglayer.emf", "EMF+ read next object part size: " << size << " type: " << type << " flags: " << flags << " data size: " << dataSize); + } + else + { + if (mbMultipart) + { + SAL_INFO("drawinglayer.emf", "EMF+ multipart record flags: " << mMFlags); + mMStream.Seek(0); + processObjectRecord(mMStream, mMFlags, 0, true); + } + + mbMultipart = false; + } + + if (type != EmfPlusRecordTypeObject || !(flags & 0x8000)) + { + switch (type) + { + case EmfPlusRecordTypeHeader: + { + sal_uInt32 header, version; + + 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); + 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: + { + break; + } + case EmfPlusRecordTypeComment: + { +#if OSL_DEBUG_LEVEL > 1 + unsigned char data; + OUString hexdata; + + SAL_INFO("drawinglayer.emf", "EMF+\tDatasize: 0x" << std::hex << dataSize << std::dec); + + for (sal_uInt32 i=0; i<dataSize; i++) + { + rMS.ReadUChar(data); + + if (i % 16 == 0) + hexdata += "\n"; + + OUString padding; + if ((data & 0xF0) == 0) + padding = "0"; + + hexdata += "0x" + padding + OUString::number(data, 16) + " "; + } + + SAL_INFO("drawinglayer.emf", "EMF+\t" << hexdata); +#endif + break; + } + case EmfPlusRecordTypeGetDC: + { + bIsGetDCProcessing = true; + aGetDCState = mrPropertyHolders.Current(); + SAL_INFO("drawinglayer.emf", "EMF+\tAlready used in svtools wmf/emf filter parser"); + break; + } + case EmfPlusRecordTypeObject: + { + processObjectRecord(rMS, flags, dataSize); + break; + } + case EmfPlusRecordTypeFillPie: + case EmfPlusRecordTypeDrawPie: + case EmfPlusRecordTypeDrawArc: + { + float startAngle, sweepAngle; + + // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used + sal_uInt32 brushIndexOrColor = 999; + + if (type == EmfPlusRecordTypeFillPie) + { + rMS.ReadUInt32(brushIndexOrColor); + SAL_INFO("drawinglayer.emf", "EMF+\t FillPie colorOrIndex: " << brushIndexOrColor); + } + else if (type == EmfPlusRecordTypeDrawPie) + { + SAL_INFO("drawinglayer.emf", "EMF+\t DrawPie"); + } + else + { + SAL_INFO("drawinglayer.emf", "EMF+\t DrawArc"); + } + + rMS.ReadFloat(startAngle).ReadFloat(sweepAngle); + float dx, dy, dw, dh; + ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000)); + SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh); + startAngle = basegfx::deg2rad(startAngle); + sweepAngle = basegfx::deg2rad(sweepAngle); + float endAngle = startAngle + sweepAngle; + startAngle = fmodf(startAngle, static_cast<float>(M_PI * 2)); + + if (startAngle < 0.0) + { + startAngle += static_cast<float>(M_PI * 2.0); + } + endAngle = fmodf(endAngle, static_cast<float>(M_PI * 2.0)); + + if (endAngle < 0.0) + { + endAngle += static_cast<float>(M_PI * 2.0); + } + if (sweepAngle < 0) + { + std::swap(endAngle, startAngle); + } + + SAL_INFO("drawinglayer.emf", "EMF+\t Adjusted angles: start " << + basegfx::rad2deg(startAngle) << ", end: " << basegfx::rad2deg(endAngle) << + " startAngle: " << startAngle << " sweepAngle: " << sweepAngle); + const ::basegfx::B2DPoint centerPoint(dx + 0.5 * dw, dy + 0.5 * dh); + ::basegfx::B2DPolygon polygon( + ::basegfx::utils::createPolygonFromEllipseSegment(centerPoint, + 0.5 * dw, 0.5 * dh, + startAngle, endAngle)); + if (type != EmfPlusRecordTypeDrawArc) + { + polygon.append(centerPoint); + polygon.setClosed(true); + } + ::basegfx::B2DPolyPolygon polyPolygon(polygon); + polyPolygon.transform(maMapTransform); + if (type == EmfPlusRecordTypeFillPie) + EMFPPlusFillPolygon(polyPolygon, flags & 0x8000, brushIndexOrColor); + else + EMFPPlusDrawPolygon(polyPolygon, flags & 0xff); + } + break; + case EmfPlusRecordTypeFillPath: + { + sal_uInt32 index = flags & 0xff; + sal_uInt32 brushIndexOrColor; + rMS.ReadUInt32(brushIndexOrColor); + SAL_INFO("drawinglayer.emf", "EMF+ FillPath slot: " << index); + + EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[index].get()); + if (path) + EMFPPlusFillPolygon(path->GetPolygon(*this), flags & 0x8000, brushIndexOrColor); + else + SAL_WARN("drawinglayer.emf", "EMF+\tEmfPlusRecordTypeFillPath missing path"); + } + break; + case EmfPlusRecordTypeFillRegion: + { + sal_uInt32 index = flags & 0xff; + sal_uInt32 brushIndexOrColor; + rMS.ReadUInt32(brushIndexOrColor); + SAL_INFO("drawinglayer.emf", "EMF+\t FillRegion slot: " << index); + + 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: + case EmfPlusRecordTypeFillEllipse: + { + // Intentionally very bogus initial value to avoid MSVC complaining about potentially uninitialized local + // variable. As long as the code stays as intended, this variable will be assigned a (real) value in the case + // when it is later used. + sal_uInt32 brushIndexOrColor = 1234567; + + if (type == EmfPlusRecordTypeFillEllipse) + { + rMS.ReadUInt32(brushIndexOrColor); + } + + SAL_INFO("drawinglayer.emf", "EMF+\t " << (type == EmfPlusRecordTypeFillEllipse ? "Fill" : "Draw") << "Ellipse slot: " << (flags & 0xff)); + float dx, dy, dw, dh; + ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000)); + SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh); + ::basegfx::B2DPolyPolygon polyPolygon( + ::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(dx + 0.5 * dw, dy + 0.5 * dh), + 0.5 * dw, 0.5 * dh)); + polyPolygon.transform(maMapTransform); + if (type == EmfPlusRecordTypeFillEllipse) + EMFPPlusFillPolygon(polyPolygon, flags & 0x8000, brushIndexOrColor); + else + EMFPPlusDrawPolygon(polyPolygon, flags & 0xff); + } + break; + case EmfPlusRecordTypeFillRects: + case EmfPlusRecordTypeDrawRects: + { + // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used + sal_uInt32 brushIndexOrColor = 999; + sal_Int32 rectangles; + const bool isColor = (flags & 0x8000); + ::basegfx::B2DPolygon polygon; + + if (EmfPlusRecordTypeFillRects == type) + { + SAL_INFO("drawinglayer.emf", "EMF+\t FillRects"); + rMS.ReadUInt32(brushIndexOrColor); + SAL_INFO("drawinglayer.emf", "EMF+\t" << (isColor ? "color" : "brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec); + } + else + { + SAL_INFO("drawinglayer.emf", "EMF+\t DrawRects"); + } + + rMS.ReadInt32(rectangles); + + for (int 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)); + polygon.append(Map(x + width, y)); + polygon.append(Map(x + width, y + height)); + polygon.append(Map(x, y + height)); + 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); + } + break; + } + case EmfPlusRecordTypeFillPolygon: + { + sal_uInt32 brushIndexOrColor, points; + + rMS.ReadUInt32(brushIndexOrColor); + rMS.ReadUInt32(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 Points: " << points); + EMFPPath path(points, true); + path.Read(rMS, flags); + + // 0x2000 bit indicates whether to draw an extra line between the last point + // and the first point, to close the shape. + EMFPPlusDrawPolygon(path.GetPolygon(*this, true, (flags & 0x2000)), flags); + + break; + } + case EmfPlusRecordTypeDrawPath: + { + sal_uInt32 penIndex; + rMS.ReadUInt32(penIndex); + SAL_INFO("drawinglayer.emf", "EMF+\t Pen: " << penIndex); + + EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get()); + if (path) + EMFPPlusDrawPolygon(path->GetPolygon(*this), penIndex); + else + SAL_WARN("drawinglayer.emf", "\t\tEmfPlusRecordTypeDrawPath missing path"); + + break; + } + case EmfPlusRecordTypeDrawBeziers: + { + 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..."); + + if (aCount < 4) + { + 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); + 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: " << x2 << "," << y2 << " " << x3 << "," + << y3 << " " << x4 << "," << y4); + + aControlPointA = Map(x2, y2); + aControlPointB = Map(x3, y3); + aEndPoint = Map(x4, y4); + aPolygon.appendBezierSegment(aControlPointA, aControlPointB, aEndPoint); + // The ending coordinate of one Bezier curve is the starting coordinate of the next. + aStartPoint = aEndPoint; + } + EMFPPlusDrawPolygon(::basegfx::B2DPolyPolygon(aPolygon), flags & 0xff); + 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_WARN_IF(aTension != 0, "drawinglayer.emf", + "EMF+\t TODO Add support for tension different than 0"); + 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); + if (type == EmfPlusRecordTypeFillClosedCurve) + EMFPPlusFillPolygon(path.GetPolygon(*this, /* bMapIt */ true, + /*bAddLineToCloseShape */ true), + flags & 0x8000, brushIndexOrColor); + else + EMFPPlusDrawPolygon(path.GetPolygon(*this, /* bMapIt */ true, + /*bAddLineToCloseShape */ true), + flags & 0xff); + break; + } + case EmfPlusRecordTypeDrawImage: + case EmfPlusRecordTypeDrawImagePoints: + { + sal_uInt32 imageAttributesId; + sal_Int32 sourceUnit; + rMS.ReadUInt32(imageAttributesId).ReadInt32(sourceUnit); + SAL_INFO("drawinglayer.emf", + "EMF+\t " << (type == EmfPlusRecordTypeDrawImage ? "DrawImage" + : "DrawImagePoints") + << " image attributes Id: " << imageAttributesId + << " source unit: " << sourceUnit); + SAL_INFO("drawinglayer.emf", "EMF+\t TODO: use image attributes"); + + // Source unit of measurement type must be 1 pixel + if (EMFPImage* image = sourceUnit == UnitTypePixel ? + dynamic_cast<EMFPImage*>(maEMFPObjects[flags & 0xff].get()) : + nullptr) + { + 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 " + << (type == EmfPlusRecordTypeDrawImage ? "DrawImage" + : "DrawImagePoints") + << " source rectangle: " << sx << "," << sy << " " << sw << "x" + << sh); + + float dx(0.), dy(0.), dw(0.), dh(0.); + double fShearX = 0.0; + double fShearY = 0.0; + if (type == EmfPlusRecordTypeDrawImagePoints) + { + sal_uInt32 aCount; + rMS.ReadUInt32(aCount); + + // Number of points used by DrawImagePoints. Exactly 3 points must be specified. + if (aCount != 3) + { + SAL_WARN("drawinglayer.emf", "EMF+\t Wrong EMF+ file. Expected " + "3 points, received: " + << aCount); + break; + } + float x1, y1, x2, y2, x3, y3; + + ReadPoint(rMS, x1, y1, flags); // upper-left point + ReadPoint(rMS, x2, y2, flags); // upper-right + ReadPoint(rMS, x3, y3, flags); // lower-left + + SAL_INFO("drawinglayer.emf", "EMF+\t destination points: " + << x1 << "," << y1 << " " << x2 << "," + << y2 << " " << x3 << "," << y3); + dx = x1; + dy = y2; + dw = x2 - x1; + dh = y3 - y1; + fShearX = x3 - x1; + fShearY = y2 - y1; + } + else if (type == EmfPlusRecordTypeDrawImage) + ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000)); + + SAL_INFO("drawinglayer.emf", + "EMF+\t Rectangle: " << dx << "," << dy << " " << dw << "x" << dh); + Size aSize; + if (image->type == ImageDataTypeBitmap) + { + aSize = image->graphic.GetBitmapEx().GetSizePixel(); + SAL_INFO("drawinglayer.emf", "EMF+\t Bitmap size: " << aSize.Width() + << "x" + << aSize.Height()); + if (sx < 0) + { + // If src position is negative then we need shift image to right + dx = dx + ((-sx) / sw) * dw; + if (sx + sw <= aSize.Width()) + dw = ((sw + sx) / sw) * dw; + else + dw = (aSize.Width() / sw) * dw; + } + else if (sx + sw > aSize.Width()) + // If the src image is smaller that what we want to cut, then we need to scale down + dw = ((aSize.Width() - sx) / sw) * dw; + + if (sy < 0) + { + dy = dy + ((-sy) / sh) * dh; + if (sy + sh <= aSize.Height()) + dh = ((sh + sy) / sh) * dh; + else + dh = (aSize.Height() / sh) * dh; + } + else if (sy + sh > aSize.Height()) + dh = ((aSize.Height() - sy) / sh) * dh; + } + else + 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::B2DHomMatrix aTransformMatrix + = maMapTransform + * basegfx::B2DHomMatrix( + /* Row 0, Column 0 */ aDstSize.getX(), + /* 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 2 */ aDstPoint.getY()); + + if (image->type == ImageDataTypeBitmap) + { + 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)); + } + else + SAL_WARN("drawinglayer.emf", "EMF+\t warning: empty bitmap"); + } + else if (image->type == ImageDataTypeMetafile) + { + GDIMetaFile aGDI(image->graphic.GetGDIMetaFile()); + aGDI.Clip(aSource); + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::MetafilePrimitive2D(aTransformMatrix, + aGDI)); + } + } + else + { + SAL_WARN("drawinglayer.emf", + "EMF+\tDrawImage(Points) Wrong EMF+ file. Only Unit Type Pixel is " + "support by EMF+ specification for DrawImage(Points)"); + } + break; + } + case EmfPlusRecordTypeDrawString: + { + sal_uInt32 brushId, formatId, stringLength; + rMS.ReadUInt32(brushId).ReadUInt32(formatId).ReadUInt32(stringLength); + SAL_INFO("drawinglayer.emf", "EMF+\t FontId: " << OUString::number(flags & 0xFF)); + SAL_INFO("drawinglayer.emf", "EMF+\t BrushId: " << BrushIDToString(flags, brushId)); + SAL_INFO("drawinglayer.emf", "EMF+\t FormatId: " << formatId); + SAL_INFO("drawinglayer.emf", "EMF+\t Length: " << stringLength); + + // read the layout rectangle + float lx, ly, lw, lh; + rMS.ReadFloat(lx).ReadFloat(ly).ReadFloat(lw).ReadFloat(lh); + + SAL_INFO("drawinglayer.emf", "EMF+\t DrawString layoutRect: " << lx << "," << ly << " - " << lw << "x" << lh); + // parse the string + const OUString text = read_uInt16s_ToOUString(rMS, stringLength); + SAL_INFO("drawinglayer.emf", "EMF+\t DrawString string: " << text); + // 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 = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get()); + if (!font) + { + break; + } + mrPropertyHolders.Current().setFont(vcl::Font(font->family, Size(font->emSize, font->emSize))); + + drawinglayer::attribute::FontAttribute fontAttribute( + font->family, // font family + "", // (no) font style + font->Bold() ? 8u : 1u, // weight: 8 = bold + font->family == "SYMBOL", // symbol + stringFormat && stringFormat->DirectionVertical(), // vertical + font->Italic(), // italic + false, // monospaced + false, // outline = false, no such thing in MS-EMFPLUS + stringFormat && stringFormat->DirectionRightToLeft(), // right-to-left + false); // BiDiStrong + + css::lang::Locale locale; + double stringAlignmentHorizontalOffset = 0.0; + double stringAlignmentVerticalOffset = font->emSize; + if (stringFormat) + { + LanguageTag aLanguageTag(static_cast<LanguageType>(stringFormat->language)); + locale = aLanguageTag.getLocale(); + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + + aTextLayouter.setFontAttribute(fontAttribute, font->emSize, + font->emSize, locale); + + double fTextWidth = aTextLayouter.getTextWidth(text, 0, stringLength); + SAL_WARN_IF(stringFormat->DirectionRightToLeft(), "drawinglayer.emf", + "EMF+\t DrawString Alignment TODO For a right-to-left layout rectangle, the origin should be at the upper right."); + if (stringFormat->stringAlignment == StringAlignmentNear) + // Alignment is to the left side of the layout rectangle (lx, ly, lw, lh) + stringAlignmentHorizontalOffset = stringFormat->leadingMargin * font->emSize; + else if (stringFormat->stringAlignment == StringAlignmentCenter) + // Alignment is centered between the origin and extent of the layout rectangle + stringAlignmentHorizontalOffset = 0.5 * lw + (stringFormat->leadingMargin - stringFormat->trailingMargin) * font->emSize - 0.5 * fTextWidth; + else if (stringFormat->stringAlignment == StringAlignmentFar) + // Alignment is to the right side of the layout rectangle + stringAlignmentHorizontalOffset = lw - stringFormat->trailingMargin * font->emSize - fTextWidth; + + if (stringFormat->lineAlign == StringAlignmentNear) + stringAlignmentVerticalOffset = font->emSize; + else if (stringFormat->lineAlign == StringAlignmentCenter) + stringAlignmentVerticalOffset = 0.5 * lh + 0.5 * font->emSize; + else if (stringFormat->lineAlign == StringAlignmentFar) + stringAlignmentVerticalOffset = lh; + } + else + { + // By default LeadingMargin is 1/6 inch + // TODO for typographic fonts set value to 0. + stringAlignmentHorizontalOffset = 16.0; + + // use system default + locale = Application::GetSettings().GetLanguageTag().getLocale(); + } + + const basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix( + ::basegfx::B2DSize(font->emSize, font->emSize), + ::basegfx::B2DPoint(lx + stringAlignmentHorizontalOffset, + ly + stringAlignmentVerticalOffset)); + + Color uncorrectedColor = EMFPGetBrushColorOrARGBColor(flags, brushId); + Color color; + + if (mbSetTextContrast) + { + const auto gammaVal = mnTextContrast / 1000; + const basegfx::BColorModifier_gamma gamma(gammaVal); + + // gamma correct transparency color + sal_uInt16 alpha = uncorrectedColor.GetAlpha(); + alpha = std::clamp(std::pow(alpha, 1.0 / gammaVal), 0.0, 1.0) * 255; + + basegfx::BColor modifiedColor(gamma.getModifiedColor(uncorrectedColor.getBColor())); + color.SetRed(modifiedColor.getRed() * 255); + color.SetGreen(modifiedColor.getGreen() * 255); + color.SetBlue(modifiedColor.getBlue() * 255); + color.SetAlpha(alpha); + } + else + { + color = uncorrectedColor; + } + + mrPropertyHolders.Current().setTextColor(color.getBColor()); + mrPropertyHolders.Current().setTextColorActive(true); + + if (color.GetAlpha() > 0) + { + std::vector<double> emptyVector; + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBaseText; + if (font->Underline() || font->Strikeout()) + { + pBaseText = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( + transformMatrix, + text, + 0, // text always starts at 0 + stringLength, + std::move(emptyVector), // EMF-PLUS has no DX-array + fontAttribute, + locale, + color.getBColor(), // Font Color + COL_TRANSPARENT, // Fill Color + color.getBColor(), // OverlineColor + color.getBColor(), // TextlineColor + drawinglayer::primitive2d::TEXT_LINE_NONE, + font->Underline() ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE, + false, + font->Strikeout() ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE); + } + else + { + pBaseText = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + transformMatrix, + text, + 0, // text always starts at 0 + stringLength, + std::move(emptyVector), // EMF-PLUS has no DX-array + fontAttribute, + locale, + color.getBColor()); + } + drawinglayer::primitive2d::Primitive2DReference aPrimitiveText(pBaseText); + if (color.IsTransparent()) + { + aPrimitiveText = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText }, + (255 - color.GetAlpha()) / 255.0); + } + + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::TransformPrimitive2D( + maMapTransform, + drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText } )); + } + break; + } + case EmfPlusRecordTypeSetPageTransform: + { + rMS.ReadFloat(mfPageScale); + SAL_INFO("drawinglayer.emf", "EMF+\t Scale: " << mfPageScale << " unit: " << UnitTypeToString(flags)); + + if ((flags == UnitTypeDisplay) || (flags == UnitTypeWorld)) + { + SAL_WARN("drawinglayer.emf", "EMF+\t file error. UnitTypeDisplay and UnitTypeWorld are not supported by SetPageTransform in EMF+ specification."); + } + else + { + mnMmX *= mfPageScale * getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnHDPI); + mnMmY *= mfPageScale * getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnVDPI); + mappingChanged(); + } + break; + } + case EmfPlusRecordTypeSetRenderingOrigin: + { + rMS.ReadInt32(mnOriginX).ReadInt32(mnOriginY); + SAL_INFO("drawinglayer.emf", "EMF+\t SetRenderingOrigin, [x,y]: " << mnOriginX << "," << mnOriginY); + break; + } + case EmfPlusRecordTypeSetTextContrast: + { + const sal_uInt16 LOWERGAMMA = 1000; + const sal_uInt16 UPPERGAMMA = 2200; + + mbSetTextContrast = true; + mnTextContrast = flags & 0xFFF; + SAL_WARN_IF(mnTextContrast > UPPERGAMMA || mnTextContrast < LOWERGAMMA, + "drawinglayer.emf", "EMF+\t Gamma value is not with bounds 1000 to 2200, value is " << mnTextContrast); + mnTextContrast = std::min(mnTextContrast, UPPERGAMMA); + mnTextContrast = std::max(mnTextContrast, LOWERGAMMA); + SAL_INFO("drawinglayer.emf", "EMF+\t Text contrast: " << (mnTextContrast / 1000) << " gamma"); + break; + } + case EmfPlusRecordTypeSetTextRenderingHint: + { + sal_uInt8 nTextRenderingHint = (flags & 0xFF) >> 1; + SAL_INFO("drawinglayer.emf", "EMF+\t Text rendering hint: " << TextRenderingHintToString(nTextRenderingHint)); + SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetTextRenderingHint"); + break; + } + case EmfPlusRecordTypeSetAntiAliasMode: + { + bool bUseAntiAlias = (flags & 0x0001); + sal_uInt8 nSmoothingMode = (flags & 0xFE00) >> 1; + SAL_INFO("drawinglayer.emf", "EMF+\t Antialiasing: " << (bUseAntiAlias ? "enabled" : "disabled")); + SAL_INFO("drawinglayer.emf", "EMF+\t Smoothing mode: " << SmoothingModeToString(nSmoothingMode)); + SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetAntiAliasMode"); + break; + } + case EmfPlusRecordTypeSetInterpolationMode: + { + sal_uInt16 nInterpolationMode = flags & 0xFF; + SAL_INFO("drawinglayer.emf", "EMF+\t Interpolation mode: " << InterpolationModeToString(nInterpolationMode)); + SAL_WARN("drawinglayer.emf", "EMF+\t TODO InterpolationMode"); + break; + } + case EmfPlusRecordTypeSetPixelOffsetMode: + { + SAL_INFO("drawinglayer.emf", "EMF+\t Pixel offset mode: " << PixelOffsetModeToString(flags)); + SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetPixelOffsetMode"); + break; + } + case EmfPlusRecordTypeSetCompositingQuality: + { + SAL_INFO("drawinglayer.emf", "EMF+\t TODO SetCompositingQuality"); + break; + } + case EmfPlusRecordTypeSave: + { + sal_uInt32 stackIndex; + rMS.ReadUInt32(stackIndex); + SAL_INFO("drawinglayer.emf", "EMF+\t Save stack index: " << stackIndex); + + GraphicStatePush(mGSStack, stackIndex); + + break; + } + case EmfPlusRecordTypeRestore: + { + sal_uInt32 stackIndex; + rMS.ReadUInt32(stackIndex); + SAL_INFO("drawinglayer.emf", "EMF+\t Restore stack index: " << stackIndex); + + GraphicStatePop(mGSStack, stackIndex); + break; + } + case EmfPlusRecordTypeBeginContainer: + { + float dx, dy, dw, dh; + ReadRectangle(rMS, dx, dy, dw, dh); + SAL_INFO("drawinglayer.emf", "EMF+\t Dest RectData: " << dx << "," << dy << " " << dw << "x" << dh); + + float sx, sy, sw, sh; + ReadRectangle(rMS, sx, sy, sw, sh); + SAL_INFO("drawinglayer.emf", "EMF+\t Source RectData: " << sx << "," << sy << " " << sw << "x" << sh); + + sal_uInt32 stackIndex; + rMS.ReadUInt32(stackIndex); + SAL_INFO("drawinglayer.emf", "EMF+\t Begin Container stack index: " << stackIndex << ", PageUnit: " << flags); + + if ((flags == UnitTypeDisplay) || (flags == UnitTypeWorld)) + { + 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) ); + maWorldTransform *= transform; + mappingChanged(); + break; + } + case EmfPlusRecordTypeBeginContainerNoParams: + { + sal_uInt32 stackIndex; + rMS.ReadUInt32(stackIndex); + SAL_INFO("drawinglayer.emf", "EMF+\t Begin Container No Params stack index: " << stackIndex); + + GraphicStatePush(mGSContainerStack, stackIndex); + break; + } + case EmfPlusRecordTypeEndContainer: + { + sal_uInt32 stackIndex; + rMS.ReadUInt32(stackIndex); + SAL_INFO("drawinglayer.emf", "EMF+\t End Container stack index: " << stackIndex); + + GraphicStatePop(mGSContainerStack, stackIndex); + break; + } + case EmfPlusRecordTypeSetWorldTransform: + { + SAL_INFO("drawinglayer.emf", "EMF+\t SetWorldTransform, Post multiply: " << bool(flags & 0x2000)); + readXForm(rMS, maWorldTransform); + mappingChanged(); + SAL_INFO("drawinglayer.emf", "EMF+\t\t: " << maWorldTransform); + break; + } + case EmfPlusRecordTypeResetWorldTransform: + { + maWorldTransform.identity(); + SAL_INFO("drawinglayer.emf", "EMF+\t World transform: " << maWorldTransform); + mappingChanged(); + break; + } + case EmfPlusRecordTypeMultiplyWorldTransform: + { + SAL_INFO("drawinglayer.emf", "EMF+\t MultiplyWorldTransform, post multiply: " << bool(flags & 0x2000)); + basegfx::B2DHomMatrix transform; + readXForm(rMS, transform); + + SAL_INFO("drawinglayer.emf", + "EMF+\t Transform matrix: " << transform); + + if (flags & 0x2000) + { + // post multiply + maWorldTransform *= transform; + } + else + { + // pre multiply + transform *= maWorldTransform; + maWorldTransform = transform; + } + + mappingChanged(); + + SAL_INFO("drawinglayer.emf", + "EMF+\t World transform matrix: " << maWorldTransform); + break; + } + case EmfPlusRecordTypeTranslateWorldTransform: + { + SAL_INFO("drawinglayer.emf", "EMF+\t TranslateWorldTransform, Post multiply: " << bool(flags & 0x2000)); + + basegfx::B2DHomMatrix transform; + float eDx, eDy; + rMS.ReadFloat(eDx).ReadFloat(eDy); + transform.set(0, 2, eDx); + transform.set(1, 2, eDy); + + SAL_INFO("drawinglayer.emf", + "EMF+\t Translate matrix: " << transform); + + if (flags & 0x2000) + { + // post multiply + maWorldTransform *= transform; + } + else + { + // pre multiply + transform *= maWorldTransform; + maWorldTransform = transform; + } + + mappingChanged(); + + SAL_INFO("drawinglayer.emf", + "EMF+\t World transform matrix: " << maWorldTransform); + break; + } + case EmfPlusRecordTypeScaleWorldTransform: + { + basegfx::B2DHomMatrix transform; + float eSx, eSy; + rMS.ReadFloat(eSx).ReadFloat(eSy); + transform.set(0, 0, eSx); + transform.set(1, 1, eSy); + + SAL_INFO("drawinglayer.emf", "EMF+\t ScaleWorldTransform Sx: " << eSx << + " Sy: " << eSy << ", Post multiply:" << bool(flags & 0x2000)); + SAL_INFO("drawinglayer.emf", + "EMF+\t World transform matrix: " << maWorldTransform); + + if (flags & 0x2000) + { + // post multiply + maWorldTransform *= transform; + } + else + { + // pre multiply + transform *= maWorldTransform; + maWorldTransform = transform; + } + + mappingChanged(); + + SAL_INFO("drawinglayer.emf", + "EMF+\t World transform matrix: " << maWorldTransform); + break; + } + case EmfPlusRecordTypeRotateWorldTransform: + { + // Angle of rotation in degrees + float eAngle; + rMS.ReadFloat(eAngle); + + SAL_INFO("drawinglayer.emf", "EMF+\t RotateWorldTransform Angle: " << eAngle << + ", post multiply: " << bool(flags & 0x2000)); + // Skipping flags & 0x2000 + // For rotation transformation there is no difference between post and pre multiply + maWorldTransform.rotate(basegfx::deg2rad(eAngle)); + mappingChanged(); + + SAL_INFO("drawinglayer.emf", + "EMF+\t " << maWorldTransform); + break; + } + case EmfPlusRecordTypeResetClip: + { + SAL_INFO("drawinglayer.emf", "EMF+ ResetClip"); + // We don't need to read anything more, as Size needs to be set 0x0000000C + // and DataSize must be set to 0. + + // Resets the current clipping region for the world space to infinity. + HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders, mrPropertyHolders); + break; + } + case EmfPlusRecordTypeSetClipRect: + case EmfPlusRecordTypeSetClipPath: + case EmfPlusRecordTypeSetClipRegion: + { + int combineMode = (flags >> 8) & 0xf; + ::basegfx::B2DPolyPolygon polyPolygon; + if (type == EmfPlusRecordTypeSetClipRect) + { + SAL_INFO("drawinglayer.emf", "EMF+\t SetClipRect"); + + 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)); + + 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); + ::basegfx::B2DPolyPolygon aClippedPolyPolygon; + if (mrPropertyHolders.Current().getClipPolyPolygonActive()) + { + aClippedPolyPolygon + = combineClip(mrPropertyHolders.Current().getClipPolyPolygon(), + combineMode, polyPolygon); + } + 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: + { + float dx, dy; + rMS.ReadFloat(dx).ReadFloat(dy); + SAL_INFO("drawinglayer.emf", "EMF+\tOffset x:" << dx << ", y:" << dy); + + basegfx::B2DPolyPolygon aPolyPolygon( + mrPropertyHolders.Current().getClipPolyPolygon()); + + SAL_INFO("drawinglayer.emf", + "EMF+\t PolyPolygon before translate: " << aPolyPolygon); + + basegfx::B2DPoint aOffset = Map(dx, dy); + basegfx::B2DHomMatrix transformMatrix; + transformMatrix.set(0, 2, aOffset.getX()); + transformMatrix.set(1, 2, aOffset.getY()); + aPolyPolygon.transform(transformMatrix); + + SAL_INFO("drawinglayer.emf", + "EMF+\t PolyPolygon after translate: " << aPolyPolygon << + ", mapped offset x" << aOffset.getX() << ", mapped offset y" << aOffset.getY()); + HandleNewClipRegion(aPolyPolygon, mrTargetHolders, mrPropertyHolders); + break; + } + case EmfPlusRecordTypeDrawDriverString: + { + sal_uInt32 brushIndexOrColor; + sal_uInt32 optionFlags; + sal_uInt32 hasMatrix; + sal_uInt32 glyphsCount; + rMS.ReadUInt32(brushIndexOrColor).ReadUInt32(optionFlags).ReadUInt32(hasMatrix).ReadUInt32(glyphsCount); + SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\t Option flags: 0x" << std::hex << optionFlags << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\t Has matrix: " << hasMatrix); + SAL_INFO("drawinglayer.emf", "EMF+\t Glyphs: " << glyphsCount); + + if ((optionFlags & 1) && glyphsCount > 0) + { + std::unique_ptr<float[]> charsPosX(new float[glyphsCount]); + std::unique_ptr<float[]> charsPosY(new float[glyphsCount]); + OUString text = read_uInt16s_ToOUString(rMS, glyphsCount); + SAL_INFO("drawinglayer.emf", "EMF+\t DrawDriverString string: " << text); + + for (sal_uInt32 i = 0; i<glyphsCount; i++) + { + rMS.ReadFloat(charsPosX[i]).ReadFloat(charsPosY[i]); + SAL_INFO("drawinglayer.emf", "EMF+\t\t glyphPosition[" << i << "]: " << charsPosX[i] << "," << charsPosY[i]); + } + + basegfx::B2DHomMatrix transform; + + if (hasMatrix) + { + readXForm(rMS, transform); + SAL_INFO("drawinglayer.emf", "EMF+\tmatrix: " << transform); + } + + // get the font from the flags + EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get()); + if (!font) + { + break; + } + // done reading + + drawinglayer::attribute::FontAttribute fontAttribute( + font->family, // font family + "", // (no) font style + font->Bold() ? 8u : 1u, // weight: 8 = bold + font->family == "SYMBOL", // symbol + optionFlags & 0x2, // vertical + font->Italic(), // italic + false, // monospaced + false, // outline = false, no such thing in MS-EMFPLUS + false, // right-to-left + false); // BiDiStrong + + const Color color = EMFPGetBrushColorOrARGBColor(flags, brushIndexOrColor); + + // generate TextSimplePortionPrimitive2Ds or TextDecoratedPortionPrimitive2D + // for all portions of text with the same charsPosY values + sal_uInt32 pos = 0; + while (pos < glyphsCount) + { + //determine the current length + sal_uInt32 aLength = 1; + while (pos + aLength < glyphsCount && std::abs( charsPosY[pos + aLength] - charsPosY[pos] ) < std::numeric_limits< float >::epsilon()) + aLength++; + + // generate the DX-Array + std::vector<double> aDXArray; + for (size_t i = 0; i < aLength - 1; i++) + { + aDXArray.push_back(charsPosX[pos + i + 1] - charsPosX[pos]); + } + // last entry + aDXArray.push_back(0); + + basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix( + ::basegfx::B2DSize(font->emSize, font->emSize), + ::basegfx::B2DPoint(charsPosX[pos], charsPosY[pos])); + if (hasMatrix) + transformMatrix *= transform; + if (color.GetAlpha() > 0) + { + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBaseText; + if (font->Underline() || font->Strikeout()) + { + pBaseText = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( + transformMatrix, + text, + pos, // take character at current pos + aLength, // use determined length + std::move(aDXArray), // generated DXArray + fontAttribute, + Application::GetSettings().GetLanguageTag().getLocale(), + color.getBColor(), + COL_TRANSPARENT, + color.getBColor(), + color.getBColor(), + drawinglayer::primitive2d::TEXT_LINE_NONE, + font->Underline() ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE, + false, + font->Strikeout() ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE); + } + else + { + pBaseText = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + transformMatrix, + text, + pos, // take character at current pos + aLength, // use determined length + std::move(aDXArray), // generated DXArray + fontAttribute, + Application::GetSettings().GetLanguageTag().getLocale(), + color.getBColor()); + } + drawinglayer::primitive2d::Primitive2DReference aPrimitiveText(pBaseText); + if (color.IsTransparent()) + { + aPrimitiveText = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText }, + (255 - color.GetAlpha()) / 255.0); + } + mrTargetHolders.Current().append( + new drawinglayer::primitive2d::TransformPrimitive2D( + maMapTransform, + drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText } )); + } + + // update pos + pos += aLength; + } + } + else + { + SAL_WARN("drawinglayer.emf", "EMF+\tTODO: fonts (non-unicode glyphs chars)"); + } + break; + } + default: + { + SAL_WARN("drawinglayer.emf", "EMF+ TODO unhandled record type: 0x" << std::hex << type << std::dec); + } + } + } + + rMS.Seek(next); + + if (size <= length) + { + length -= size; + } + else + { + SAL_WARN("drawinglayer.emf", "ImplRenderer::processEMFPlus: " + "size " << size << " > length " << length); + length = 0; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfphelperdata.hxx b/drawinglayer/source/tools/emfphelperdata.hxx new file mode 100644 index 000000000..ef870f31e --- /dev/null +++ b/drawinglayer/source/tools/emfphelperdata.hxx @@ -0,0 +1,270 @@ +/* -*- 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 <wmfemfhelper.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/attribute/linestartendattribute.hxx> +#include <tools/stream.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <map> + +// predefines +class SvStream; +namespace basegfx { class B2DPolyPolygon; } + +namespace emfplushelper +{ + // EMF+ commands + #define EmfPlusRecordTypeHeader 0x4001 + #define EmfPlusRecordTypeEndOfFile 0x4002 + #define EmfPlusRecordTypeComment 0x4003 + #define EmfPlusRecordTypeGetDC 0x4004 + //TODO EmfPlusRecordTypeMultiFormatStart 0x4005 + //TODO EmfPlusRecordTypeMultiFormatSection 0x4006 + //TODO EmfPlusRecordTypeMultiFormatEnd 0x4007 + #define EmfPlusRecordTypeObject 0x4008 + //TODO EmfPlusRecordTypeClear 0x4009 + #define EmfPlusRecordTypeFillRects 0x400A + #define EmfPlusRecordTypeDrawRects 0x400B + #define EmfPlusRecordTypeFillPolygon 0x400C + #define EmfPlusRecordTypeDrawLines 0x400D + #define EmfPlusRecordTypeFillEllipse 0x400E + #define EmfPlusRecordTypeDrawEllipse 0x400F + #define EmfPlusRecordTypeFillPie 0x4010 + #define EmfPlusRecordTypeDrawPie 0x4011 + #define EmfPlusRecordTypeDrawArc 0x4012 + #define EmfPlusRecordTypeFillRegion 0x4013 + #define EmfPlusRecordTypeFillPath 0x4014 + #define EmfPlusRecordTypeDrawPath 0x4015 + #define EmfPlusRecordTypeFillClosedCurve 0x4016 + #define EmfPlusRecordTypeDrawClosedCurve 0x4017 + //TODO EmfPlusRecordTypeDrawCurve 0x4018 + #define EmfPlusRecordTypeDrawBeziers 0x4019 + #define EmfPlusRecordTypeDrawImage 0x401A + #define EmfPlusRecordTypeDrawImagePoints 0x401B + #define EmfPlusRecordTypeDrawString 0x401C + #define EmfPlusRecordTypeSetRenderingOrigin 0x401D + #define EmfPlusRecordTypeSetAntiAliasMode 0x401E + #define EmfPlusRecordTypeSetTextRenderingHint 0x401F + #define EmfPlusRecordTypeSetTextContrast 0x4020 + #define EmfPlusRecordTypeSetInterpolationMode 0x4021 + #define EmfPlusRecordTypeSetPixelOffsetMode 0x4022 + //TODO EmfPlusRecordTypeSetCompositingMode 0x4023 + #define EmfPlusRecordTypeSetCompositingQuality 0x4024 + #define EmfPlusRecordTypeSave 0x4025 + #define EmfPlusRecordTypeRestore 0x4026 + #define EmfPlusRecordTypeBeginContainer 0x4027 + #define EmfPlusRecordTypeBeginContainerNoParams 0x4028 + #define EmfPlusRecordTypeEndContainer 0x4029 + #define EmfPlusRecordTypeSetWorldTransform 0x402A + #define EmfPlusRecordTypeResetWorldTransform 0x402B + #define EmfPlusRecordTypeMultiplyWorldTransform 0x402C + #define EmfPlusRecordTypeTranslateWorldTransform 0x402D + #define EmfPlusRecordTypeScaleWorldTransform 0x402E + #define EmfPlusRecordTypeRotateWorldTransform 0x402F + #define EmfPlusRecordTypeSetPageTransform 0x4030 + #define EmfPlusRecordTypeResetClip 0x4031 + #define EmfPlusRecordTypeSetClipRect 0x4032 + #define EmfPlusRecordTypeSetClipPath 0x4033 + #define EmfPlusRecordTypeSetClipRegion 0x4034 + #define EmfPlusRecordTypeOffsetClip 0x4035 + #define EmfPlusRecordTypeDrawDriverString 0x4036 + //TODO EmfPlusRecordTypeStrokeFillPath 0x4037 + //TODO EmfPlusRecordTypeSerializableObject 0x4038 + //TODO EmfPlusRecordTypeSetTSGraphics 0x4039 + //TODO EmfPlusRecordTypeSetTSClip 0x403A + + // EMF+object types + #define EmfPlusObjectTypeBrush 0x100 + #define EmfPlusObjectTypePen 0x200 + #define EmfPlusObjectTypePath 0x300 + #define EmfPlusObjectTypeRegion 0x400 + #define EmfPlusObjectTypeImage 0x500 + #define EmfPlusObjectTypeFont 0x600 + #define EmfPlusObjectTypeStringFormat 0x700 + #define EmfPlusObjectTypeImageAttributes 0x800 + #define EmfPlusObjectTypeCustomLineCap 0x900 + + enum PixelOffsetMode + { + PixelOffsetModeDefault = 0x00, + PixelOffsetModeHighSpeed = 0x01, + PixelOffsetModeHighQuality = 0x02, + PixelOffsetModeNone = 0x03, + PixelOffsetModeHalf = 0x04 + }; + + enum SmoothingMode + { + SmoothingModeDefault = 0x00, + SmoothingModeHighSpeed = 0x01, + SmoothingModeHighQuality = 0x02, + SmoothingModeNone = 0x03, + SmoothingModeAntiAlias8x4 = 0x04, + SmoothingModeAntiAlias8x8 = 0x05 + }; + + enum InterpolationMode + { + InterpolationModeDefault = 0x00, + InterpolationModeLowQuality = 0x01, + InterpolationModeHighQuality = 0x02, + InterpolationModeBilinear = 0x03, + InterpolationModeBicubic = 0x04, + InterpolationModeNearestNeighbor = 0x05, + InterpolationModeHighQualityBilinear = 0x06, + InterpolationModeHighQualityBicubic = 0x07 + }; + + enum TextRenderingHint + { + TextRenderingHintSystemDefault = 0x00, + TextRenderingHintSingleBitPerPixelGridFit = 0x01, + TextRenderingHintSingleBitPerPixel = 0x02, + TextRenderingHintAntialiasGridFit = 0x03, + TextRenderingHintAntialias = 0x04, + TextRenderingHintClearTypeGridFit = 0x05 + }; + + enum UnitType + { + UnitTypeWorld = 0x00, + UnitTypeDisplay = 0x01, + UnitTypePixel = 0x02, + UnitTypePoint = 0x03, + UnitTypeInch = 0x04, + UnitTypeDocument = 0x05, + UnitTypeMillimeter = 0x06 + }; + + enum EmfPlusCombineMode + { + EmfPlusCombineModeReplace = 0x00000000, + EmfPlusCombineModeIntersect = 0x00000001, + EmfPlusCombineModeUnion = 0x00000002, + EmfPlusCombineModeXOR = 0x00000003, + EmfPlusCombineModeExclude = 0x00000004, + EmfPlusCombineModeComplement = 0x00000005 + }; + + const char* emfTypeToName(sal_uInt16 type); + OUString UnitTypeToString(sal_uInt16 nType); + + struct EMFPObject + { + virtual ~EMFPObject(); + }; + + typedef std::map<int, wmfemfhelper::PropertyHolder> GraphicStateMap; + + struct EmfPlusHelperData + { + private: + /* EMF+ */ + basegfx::B2DHomMatrix maBaseTransform; + basegfx::B2DHomMatrix maWorldTransform; + basegfx::B2DHomMatrix maMapTransform; + + std::unique_ptr<EMFPObject> maEMFPObjects[256]; + float mfPageScale; + sal_Int32 mnOriginX; + sal_Int32 mnOriginY; + sal_uInt32 mnHDPI; + sal_uInt32 mnVDPI; + bool mbSetTextContrast; + sal_uInt16 mnTextContrast; + + /* EMF+ emf header info */ + sal_Int32 mnFrameLeft; + sal_Int32 mnFrameTop; + sal_Int32 mnFrameRight; + sal_Int32 mnFrameBottom; + sal_Int32 mnPixX; + sal_Int32 mnPixY; + sal_Int32 mnMmX; + sal_Int32 mnMmY; + + /* multipart object data */ + bool mbMultipart; + sal_uInt16 mMFlags; + SvMemoryStream mMStream; + + /* emf+ graphic state stack */ + 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 + void processObjectRecord(SvMemoryStream& rObjectStream, sal_uInt16 flags, sal_uInt32 dataSize, bool bUseWholeStream = false); + static void ReadPoint(SvStream& s, float& x, float& y, sal_uInt32 flags); + + // internal mapper + void mappingChanged(); + + // stack actions + void GraphicStatePush(GraphicStateMap& map, sal_Int32 index); + 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); + void EMFPPlusFillPolygon(const ::basegfx::B2DPolyPolygon& polygon, const bool isColor, const sal_uInt32 brushIndexOrColor); + void EMFPPlusFillPolygonSolidColor(const ::basegfx::B2DPolyPolygon& polygon, Color const& color); + + // helper functions + Color EMFPGetBrushColorOrARGBColor(const sal_uInt16 flags, const sal_uInt32 brushIndexOrColor) const; + + public: + EmfPlusHelperData( + SvMemoryStream& rMS, + wmfemfhelper::TargetHolders& rTargetHolders, + wmfemfhelper::PropertyHolders& rPropertyHolders); + ~EmfPlusHelperData(); + + void processEmfPlusData( + SvMemoryStream& rMS, + const drawinglayer::geometry::ViewInformation2D& rViewInformation); + + // mappers + ::basegfx::B2DPoint Map(double ix, double iy) const; + + // readers + 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); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpimage.cxx b/drawinglayer/source/tools/emfpimage.cxx new file mode 100644 index 000000000..67a0cef99 --- /dev/null +++ b/drawinglayer/source/tools/emfpimage.cxx @@ -0,0 +1,80 @@ +/* -*- 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/graphicfilter.hxx> +#include <sal/log.hxx> +#include "emfpimage.hxx" + +namespace emfplushelper +{ + void EMFPImage::Read(SvMemoryStream &s, sal_uInt32 dataSize, bool bUseWholeStream) + { + sal_uInt32 header, bitmapType; + s.ReadUInt32(header).ReadUInt32(type); + SAL_INFO("drawinglayer.emf", "EMF+\timage\nEMF+\theader: 0x" << std::hex << header << " type: " << type << std::dec); + + if (ImageDataTypeBitmap == type) + { + // bitmap + s.ReadInt32(width).ReadInt32(height).ReadInt32(stride).ReadUInt32(pixelFormat).ReadUInt32(bitmapType); + SAL_INFO("drawinglayer.emf", "EMF+\tbitmap width: " << width << " height: " << height << " stride: " << stride << " pixelFormat: 0x" << std::hex << pixelFormat << " bitmapType: 0x" << bitmapType << std::dec); + + if ((bitmapType != 0) || (width == 0)) + { + // non native formats + GraphicFilter filter; + filter.ImportGraphic(graphic, u"", s); + SAL_INFO("drawinglayer.emf", "EMF+\tbitmap width: " << graphic.GetSizePixel().Width() << " height: " << graphic.GetSizePixel().Height()); + } + } + else if (ImageDataTypeMetafile == type) + { + // metafile + sal_uInt32 mfType, mfSize; + s.ReadUInt32(mfType).ReadUInt32(mfSize); + + if (bUseWholeStream) + dataSize = s.remainingSize(); + else + dataSize -= 16; + + SAL_INFO("drawinglayer.emf", "EMF+\tmetafile type: " << mfType << " dataSize: " << mfSize << " real size calculated from record dataSize: " << dataSize); + + 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, u"", mfStream); + + // debug code - write the stream to debug file /tmp/emf-stream.emf +#if OSL_DEBUG_LEVEL > 1 + mfStream.Seek(0); + static sal_Int32 emfp_debug_stream_number = 0; + OUString emfp_debug_filename = "/tmp/emf-embedded-stream" + + OUString::number(emfp_debug_stream_number++) + ".emf"; + + SvFileStream file(emfp_debug_filename, StreamMode::WRITE | StreamMode::TRUNC); + + mfStream.WriteStream(file); + file.Flush(); + file.Close(); +#endif + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpimage.hxx b/drawinglayer/source/tools/emfpimage.hxx new file mode 100644 index 000000000..75e42e7d2 --- /dev/null +++ b/drawinglayer/source/tools/emfpimage.hxx @@ -0,0 +1,48 @@ +/* -*- 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 "emfphelperdata.hxx" +#include <vcl/graph.hxx> + +namespace emfplushelper +{ + + typedef enum + { + ImageDataTypeUnknown = 0x00000000, + ImageDataTypeBitmap = 0x00000001, + ImageDataTypeMetafile = 0x00000002 + } ImageDataType; + + struct EMFPImage : public EMFPObject + { + sal_uInt32 type; + sal_Int32 width; + sal_Int32 height; + sal_Int32 stride; + sal_uInt32 pixelFormat; + Graphic graphic; + + void Read(SvMemoryStream &s, sal_uInt32 dataSize, bool bUseWholeStream); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpimageattributes.cxx b/drawinglayer/source/tools/emfpimageattributes.cxx new file mode 100644 index 000000000..c13da361b --- /dev/null +++ b/drawinglayer/source/tools/emfpimageattributes.cxx @@ -0,0 +1,73 @@ +/* -*- 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/log.hxx> + +#include "emfpimageattributes.hxx" + +using namespace ::com::sun::star; +using namespace ::basegfx; + +namespace emfplushelper +{ +EMFPImageAttributes::EMFPImageAttributes() + : EMFPObject() + , wrapMode(0) + , objectClamp(0) +{ +} + +EMFPImageAttributes::~EMFPImageAttributes() {} + +void EMFPImageAttributes::Read(SvStream& s) +{ + sal_uInt32 graphicsVersion, reserved1, reserved2, tempClampColor; + sal_uInt8 clampColorBlue, clampColorGreen, clampColorRed, clampColorAlpha; + + s.ReadUInt32(graphicsVersion) + .ReadUInt32(reserved1) + .ReadUInt32(wrapMode) + .ReadUInt32(tempClampColor) + .ReadUInt32(objectClamp) + .ReadUInt32(reserved2); + + clampColorBlue = tempClampColor >> 24; + clampColorGreen = (tempClampColor & 0x00FFFFFF) >> 16; + clampColorRed = (tempClampColor & 0x0000FFFF) >> 8; + clampColorAlpha = tempClampColor & 0x000000FF; + + clampColor.SetRed(clampColorRed); + clampColor.SetGreen(clampColorGreen); + clampColor.SetBlue(clampColorBlue); + clampColor.SetAlpha(255 - clampColorAlpha); + + SAL_INFO("drawinglayer.emf", "EMF+\timage attributes"); + SAL_WARN_IF((reserved1 != 0) || (reserved2 != 0), "drawinglayer.emf", + "Reserved field(s) not zero - reserved1: " << std::hex << reserved1 + << " reserved2: " << reserved2); + SAL_WARN_IF((objectClamp != EmpPlusRectClamp) && (objectClamp != EmpPlusBitmapClamp), + "drawinglayer.emf", "Invalid object clamp - set to" << std::hex << objectClamp); + SAL_INFO("drawinglayer.emf", "EMF+\t image graphics version: 0x" + << std::hex << graphicsVersion << " wrap mode: " << wrapMode + << " clamp color: " << clampColor + << " object clamp: " << objectClamp); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpimageattributes.hxx b/drawinglayer/source/tools/emfpimageattributes.hxx new file mode 100644 index 000000000..a8ba37553 --- /dev/null +++ b/drawinglayer/source/tools/emfpimageattributes.hxx @@ -0,0 +1,33 @@ +/* -*- 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/. + */ + +#pragma once + +#include "emfphelperdata.hxx" + +namespace emfplushelper +{ +const sal_uInt32 EmpPlusRectClamp = 0x00000000; +const sal_uInt32 EmpPlusBitmapClamp = 0x00000001; + +struct EMFPImageAttributes : public EMFPObject +{ + sal_uInt32 wrapMode; + Color clampColor; + sal_uInt32 objectClamp; + + EMFPImageAttributes(); + + virtual ~EMFPImageAttributes() override; + + void Read(SvStream& s); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfplushelper.cxx b/drawinglayer/source/tools/emfplushelper.cxx new file mode 100644 index 000000000..87276d998 --- /dev/null +++ b/drawinglayer/source/tools/emfplushelper.cxx @@ -0,0 +1,45 @@ +/* -*- 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 <emfplushelper.hxx> +#include "emfphelperdata.hxx" + +namespace emfplushelper +{ + EmfPlusHelper::EmfPlusHelper( + SvMemoryStream& rMS, + wmfemfhelper::TargetHolders& rTargetHolders, + wmfemfhelper::PropertyHolders& rPropertyHolders) + : mpD(new EmfPlusHelperData(rMS, rTargetHolders, rPropertyHolders)) + { + } + + EmfPlusHelper::~EmfPlusHelper() + { + } + + void EmfPlusHelper::processEmfPlusData( + SvMemoryStream& rMS, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) + { + mpD->processEmfPlusData(rMS, rViewInformation); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfppath.cxx b/drawinglayer/source/tools/emfppath.cxx new file mode 100644 index 000000000..4217e3616 --- /dev/null +++ b/drawinglayer/source/tools/emfppath.cxx @@ -0,0 +1,226 @@ +/* -*- 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 <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <sal/log.hxx> +#include "emfppath.hxx" + +namespace +{ + const unsigned char nTopBitInt7 = 0x80; + const unsigned char nSignBitInt7 = 0x40; + // include the sign bit so if it's negative we get + // that "missing" bit pre-set to 1 + const unsigned char nValueMaskInt7 = 0x7F; +} + +namespace emfplushelper +{ + // see 2.2.2.21 EmfPlusInteger7 + // 2.2.2.22 EmfPlusInteger15 + // and 2.2.2.37 EmfPlusPointR Object + static sal_Int16 GetEmfPlusInteger(SvStream& s) + { + unsigned char u8(0); + s.ReadUChar(u8); + + bool bIsEmfPlusInteger15 = u8 & nTopBitInt7; + bool bNegative = u8 & nSignBitInt7; + unsigned char val1 = u8 & nValueMaskInt7; + if (bNegative) + val1 |= nTopBitInt7; + if (!bIsEmfPlusInteger15) + { + return static_cast<signed char>(val1); + } + + s.ReadUChar(u8); + sal_uInt16 nRet = (val1 << 8) | u8; + return static_cast<sal_Int16>(nRet); + } + + EMFPPath::EMFPPath (sal_uInt32 _nPoints, bool bLines) + { + if (_nPoints > SAL_MAX_UINT32 / (2 * sizeof(float))) + { + _nPoints = SAL_MAX_UINT32 / (2 * sizeof(float)); + } + + nPoints = _nPoints; + pPoints.reset( new float [nPoints*2] ); + + if (!bLines) + pPointTypes.reset( new sal_uInt8 [_nPoints] ); + } + + EMFPPath::~EMFPPath () + { + } + + void EMFPPath::Read (SvStream& s, sal_uInt32 pathFlags) + { + for (sal_uInt32 i = 0; i < nPoints; i ++) + { + if (pathFlags & 0x800) + { + // EMFPlusPointR: points are stored in EMFPlusInteger7 or + // EMFPlusInteger15 objects, see section 2.2.2.21/22 + // 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; + SAL_INFO("drawinglayer.emf", "EMF+\t\t\t" << i << ". EmfPlusPointR [x,y]: " << x << ", " << y); + } + else if (pathFlags & 0x4000) + { + // 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; + } + 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]); + } + } + + if (pPointTypes) + { + for (sal_uInt32 i = 0; i < nPoints; i++) + { + s.ReadUChar(pPointTypes[i]); + SAL_INFO("drawinglayer.emf", "EMF+\tpoint type: 0x" << std::hex << static_cast<int>(pPointTypes[i]) << std::dec); + } + } + + aPolygon.clear(); + } + + ::basegfx::B2DPolyPolygon& EMFPPath::GetPolygon (EmfPlusHelperData const & rR, bool bMapIt, bool bAddLineToCloseShape) + { + ::basegfx::B2DPolygon polygon; + aPolygon.clear (); + sal_uInt32 last_normal = 0, p = 0; + ::basegfx::B2DPoint prev, mapped; + bool hasPrev = false; + + for (sal_uInt32 i = 0; i < nPoints; i ++) + { + if (p && pPointTypes && (pPointTypes [i] == 0)) + { + aPolygon.append (polygon); + last_normal = i; + p = 0; + polygon.clear (); + } + + if (bMapIt) + mapped = rR.Map (pPoints [i*2], pPoints [i*2 + 1]); + else + mapped = ::basegfx::B2DPoint (pPoints [i*2], pPoints [i*2 + 1]); + + if (pPointTypes) + { + if ((pPointTypes [i] & 0x07) == 3) + { + if (((i - last_normal )% 3) == 1) + { + polygon.setNextControlPoint (p - 1, mapped); + SAL_INFO ("drawinglayer.emf", "EMF+\t\tPolygon append next: " << p - 1 << " mapped: " << mapped.getX () << "," << mapped.getY ()); + continue; + } + else if (((i - last_normal) % 3) == 2) + { + prev = mapped; + hasPrev = true; + continue; + } + } + else + { + last_normal = i; + } + } + + polygon.append (mapped); + SAL_INFO ("drawinglayer.emf", "EMF+\t\tPoint: " << pPoints [i*2] << "," << pPoints [i*2 + 1] << " mapped: " << mapped.getX () << ":" << mapped.getY ()); + + if (hasPrev) + { + polygon.setPrevControlPoint (p, prev); + SAL_INFO ("drawinglayer.emf", "EMF+\t\tPolygon append prev: " << p << " mapped: " << prev.getX () << "," << prev.getY ()); + hasPrev = false; + } + + p++; + + if (pPointTypes && (pPointTypes [i] & 0x80)) // closed polygon + { + polygon.setClosed (true); + aPolygon.append (polygon); + SAL_INFO ("drawinglayer.emf", "EMF+\t\tClose polygon"); + last_normal = i + 1; + p = 0; + polygon.clear (); + } + } + + // Draw an extra line between the last point and the first point, to close the shape. + if (bAddLineToCloseShape) + { + polygon.setClosed (true); + } + + if (polygon.count ()) + { + aPolygon.append (polygon); + +#if OSL_DEBUG_LEVEL > 1 + for (unsigned int i=0; i<aPolygon.count(); i++) { + polygon = aPolygon.getB2DPolygon(i); + SAL_INFO ("drawinglayer.emf", "EMF+\t\tPolygon: " << i); + for (unsigned int j=0; j<polygon.count(); j++) { + ::basegfx::B2DPoint point = polygon.getB2DPoint(j); + SAL_INFO ("drawinglayer.emf", "EMF+\t\t\tPoint: " << point.getX() << "," << point.getY()); + if (polygon.isPrevControlPointUsed(j)) { + point = polygon.getPrevControlPoint(j); + SAL_INFO ("drawinglayer.emf", "EMF+\t\t\tPrev: " << point.getX() << "," << point.getY()); + } + if (polygon.isNextControlPointUsed(j)) { + point = polygon.getNextControlPoint(j); + SAL_INFO ("drawinglayer.emf", "EMF+\t\t\tNext: " << point.getX() << "," << point.getY()); + } + } + } +#endif + } + + return aPolygon; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfppath.hxx b/drawinglayer/source/tools/emfppath.hxx new file mode 100644 index 000000000..8db095a21 --- /dev/null +++ b/drawinglayer/source/tools/emfppath.hxx @@ -0,0 +1,43 @@ +/* -*- 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 "emfphelperdata.hxx" + +namespace emfplushelper +{ + struct EMFPPath : public EMFPObject + { + ::basegfx::B2DPolyPolygon aPolygon; + sal_uInt32 nPoints; + std::unique_ptr<float[]> pPoints; + std::unique_ptr<sal_uInt8[]> pPointTypes; + + EMFPPath(sal_uInt32 _nPoints, bool bLines = false); + + virtual ~EMFPPath() override; + + void Read(SvStream& s, sal_uInt32 pathFlags); + + ::basegfx::B2DPolyPolygon& GetPolygon(EmfPlusHelperData const & rR, bool bMapIt = true, bool bAddLineToCloseShape = false); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfppen.cxx b/drawinglayer/source/tools/emfppen.cxx new file mode 100644 index 000000000..adfee3bd3 --- /dev/null +++ b/drawinglayer/source/tools/emfppen.cxx @@ -0,0 +1,382 @@ +/* -*- 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 <com/sun/star/rendering/PathCapType.hpp> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <rtl/ustrbuf.hxx> + +#include "emfppen.hxx" +#include "emfpcustomlinecap.hxx" + +using namespace ::com::sun::star; +using namespace ::basegfx; + +namespace emfplushelper +{ + + EMFPPen::EMFPPen() + : penDataFlags(0) + , penUnit(0) + , penWidth(0.0) + , startCap(0) + , endCap(0) + , maLineJoin(basegfx::B2DLineJoin::Miter) + , fMiterMinimumAngle(basegfx::deg2rad(5.0)) + , dashStyle(0) + , dashCap(0) + , dashOffset(0.0) + , alignment(0) + , customStartCapLen(0) + , customEndCapLen(0) + { + } + + EMFPPen::~EMFPPen() + { + } + + static OUString PenDataFlagsToString(sal_uInt32 flags) + { + rtl::OUStringBuffer sFlags; + + if (flags & EmfPlusPenDataTransform) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataTransform"); + + if (flags & EmfPlusPenDataStartCap) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataStartCap"); + + if (flags & EmfPlusPenDataEndCap) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataEndCap"); + + if (flags & EmfPlusPenDataJoin) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataJoin"); + + if (flags & EmfPlusPenDataMiterLimit) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataMiterLimit"); + + if (flags & EmfPlusPenDataLineStyle) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataLineStyle"); + + if (flags & EmfPlusPenDataDashedLineCap) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLineCap"); + + if (flags & EmfPlusPenDataDashedLineOffset) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLineOffset"); + + if (flags & EmfPlusPenDataDashedLine) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLine"); + + if (flags & EmfPlusPenDataAlignment) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataAlignment"); + + if (flags & EmfPlusPenDataCompoundLine) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCompoundLine"); + + if (flags & EmfPlusPenDataCustomStartCap) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCustomStartCap"); + + if (flags & EmfPlusPenDataCustomEndCap) + sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCustomEndCap"); + + return sFlags.makeStringAndClear(); + } + + static OUString LineCapTypeToString(sal_uInt32 linecap) + { + switch (linecap) + { + case LineCapTypeFlat: return "LineCapTypeFlat"; + case LineCapTypeSquare: return "LineCapTypeSquare"; + case LineCapTypeRound: return "LineCapTypeRound"; + case LineCapTypeTriangle: return "LineCapTypeTriangle"; + case LineCapTypeNoAnchor: return "LineCapTypeNoAnchor"; + case LineCapTypeSquareAnchor: return "LineCapTypeSquareAnchor"; + case LineCapTypeRoundAnchor: return "LineCapTypeRoundAchor"; + case LineCapTypeDiamondAnchor: return "LineCapTypeDiamondAnchor"; + case LineCapTypeArrowAnchor: return "LineCapTypeArrowAnchor"; + case LineCapTypeAnchorMask: return "LineCapTypeAnchorMask"; + case LineCapTypeCustom: return "LineCapTypeCustom"; + } + return ""; + } + + static OUString DashedLineCapTypeToString(sal_uInt32 dashedlinecaptype) + { + switch (dashedlinecaptype) + { + case DashedLineCapTypeFlat: return "DashedLineCapTypeFlat"; + case DashedLineCapTypeRound: return "DashedLineCapTypeRound"; + case DashedLineCapTypeTriangle: return "DashedLineCapTypeTriangle"; + } + return ""; + } + + static OUString PenAlignmentToString(sal_uInt32 alignment) + { + switch (alignment) + { + case PenAlignmentCenter: return "PenAlignmentCenter"; + case PenAlignmentInset: return "PenAlignmentInset"; + case PenAlignmentLeft: return "PenAlignmentLeft"; + case PenAlignmentOutset: return "PenAlignmentOutset"; + case PenAlignmentRight: return "PenAlignmentRight"; + } + return ""; + } + + drawinglayer::attribute::StrokeAttribute + EMFPPen::GetStrokeAttribute(const double aTransformation) const + { + if (penDataFlags & EmfPlusPenDataLineStyle // pen has a predefined line style + && dashStyle != EmfPlusLineStyleCustom) + { + 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 }); + } + } + else if (penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line + { + 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)); + } + // 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; + 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); + SAL_INFO("drawinglayer.emf", "EMF+\t\tPen data flags: 0x" << penDataFlags << PenDataFlagsToString(penDataFlags)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tUnit: " << UnitTypeToString(penUnit)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tWidth: " << std::dec << penWidth); + + // If a zero width is specified, a minimum value must be used, which is determined by the units + if (penWidth == 0.0) + { //TODO Check if these values is correct + penWidth = penUnit == 0 ? 0.18f + : 0.05f; // 0.05f is taken from old EMF+ implementation (case of Unit == Pixel etc.) + } + + if (penDataFlags & EmfPlusPenDataTransform) + { + EmfPlusHelperData::readXForm(s, pen_transformation); + SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataTransform: " << pen_transformation); + } + + if (penDataFlags & EmfPlusPenDataStartCap) + { + s.ReadInt32(startCap); + SAL_INFO("drawinglayer.emf", "EMF+\t\tstartCap: " << LineCapTypeToString(startCap) << " (0x" << std::hex << startCap << ")"); + } + else + { + startCap = 0; + } + + if (penDataFlags & EmfPlusPenDataEndCap) + { + s.ReadInt32(endCap); + SAL_INFO("drawinglayer.emf", "EMF+\t\tendCap: " << LineCapTypeToString(endCap) << " (0x" << std::hex << startCap << ")"); + } + else + { + endCap = 0; + } + + if (penDataFlags & EmfPlusPenDataJoin) + { + s.ReadInt32(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 + maLineJoin = basegfx::B2DLineJoin::Miter; + + if (penDataFlags & EmfPlusPenDataMiterLimit) + { + float miterLimit; + s.ReadFloat(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 + fMiterMinimumAngle = basegfx::deg2rad(5.0); + + + if (penDataFlags & EmfPlusPenDataLineStyle) + { + s.ReadInt32(dashStyle); + SAL_INFO("drawinglayer.emf", "EMF+\t\tdashStyle: " << DashedLineCapTypeToString(dashStyle) << " (0x" << std::hex << dashStyle << ")"); + } + else + { + dashStyle = 0; + } + + if (penDataFlags & EmfPlusPenDataDashedLineCap) + { + s.ReadInt32(dashCap); + SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineCap: 0x" << std::hex << dashCap); + } + else + { + dashCap = 0; + } + + if (penDataFlags & EmfPlusPenDataDashedLineOffset) + { + s.ReadFloat(dashOffset); + SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineOffset: 0x" << std::hex << dashOffset); + } + else + { + dashOffset = 0; + } + + if (penDataFlags & EmfPlusPenDataDashedLine) + { + dashStyle = EmfPlusLineStyleCustom; + sal_uInt32 dashPatternLen; + + s.ReadUInt32(dashPatternLen); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\tdashPatternLen: " << dashPatternLen); + + dashPattern.resize( dashPatternLen ); + + 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 & EmfPlusPenDataAlignment) + { + s.ReadInt32(alignment); + SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataAlignment: " << PenAlignmentToString(alignment) << " (0x" << std::hex << alignment << ")"); + } + else + { + alignment = 0; + } + + if (penDataFlags & EmfPlusPenDataCompoundLine) + { + SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataCompoundLine"); + sal_uInt32 compoundArrayLen; + s.ReadUInt32(compoundArrayLen); + + compoundArray.resize(compoundArrayLen); + + 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 & EmfPlusPenDataCustomStartCap) + { + s.ReadUInt32(customStartCapLen); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomStartCapLen: " << customStartCapLen); + sal_uInt64 const pos = s.Tell(); + + customStartCap.reset( new EMFPCustomLineCap() ); + customStartCap->Read(s, rR); + + // maybe we don't read everything yet, play it safe ;-) + s.Seek(pos + customStartCapLen); + } + else + { + customStartCapLen = 0; + } + + if (penDataFlags & EmfPlusPenDataCustomEndCap) + { + s.ReadUInt32(customEndCapLen); + SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomEndCapLen: " << customEndCapLen); + sal_uInt64 const pos = s.Tell(); + + customEndCap.reset( new EMFPCustomLineCap() ); + customEndCap->Read(s, rR); + + // maybe we don't read everything yet, play it safe ;-) + s.Seek(pos + customEndCapLen); + } + else + { + customEndCapLen = 0; + } + + EMFPBrush::Read(s, rR); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfppen.hxx b/drawinglayer/source/tools/emfppen.hxx new file mode 100644 index 000000000..31812c8b0 --- /dev/null +++ b/drawinglayer/source/tools/emfppen.hxx @@ -0,0 +1,130 @@ +/* -*- 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 <drawinglayer/attribute/strokeattribute.hxx> +#include "emfpbrush.hxx" +#include <vector> + +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; + const sal_uInt32 EmfPlusLineJoinTypeRound = 0x00000002; + const sal_uInt32 EmfPlusLineJoinTypeMiterClipped = 0x00000003; + + const sal_Int32 EmfPlusLineStyleSolid = 0x00000000; + const sal_Int32 EmfPlusLineStyleDash = 0x00000001; + const sal_Int32 EmfPlusLineStyleDot = 0x00000002; + const sal_Int32 EmfPlusLineStyleDashDot = 0x00000003; + const sal_Int32 EmfPlusLineStyleDashDotDot = 0x00000004; + const sal_Int32 EmfPlusLineStyleCustom = 0x00000005; + + const sal_uInt32 EmfPlusPenDataTransform = 0x00000001; + const sal_uInt32 EmfPlusPenDataStartCap = 0x00000002; + const sal_uInt32 EmfPlusPenDataEndCap = 0x00000004; + const sal_uInt32 EmfPlusPenDataJoin = 0x00000008; + const sal_uInt32 EmfPlusPenDataMiterLimit = 0x00000010; + const sal_uInt32 EmfPlusPenDataLineStyle = 0x00000020; + const sal_uInt32 EmfPlusPenDataDashedLineCap = 0x00000040; + const sal_uInt32 EmfPlusPenDataDashedLineOffset = 0x00000080; + const sal_uInt32 EmfPlusPenDataDashedLine = 0x00000100; + const sal_uInt32 EmfPlusPenDataAlignment = 0x00000200; + const sal_uInt32 EmfPlusPenDataCompoundLine = 0x00000400; + const sal_uInt32 EmfPlusPenDataCustomStartCap = 0x00000800; + const sal_uInt32 EmfPlusPenDataCustomEndCap = 0x000001000; + + enum LineCapType + { + LineCapTypeFlat = 0x00000000, + LineCapTypeSquare = 0x00000001, + LineCapTypeRound = 0x00000002, + LineCapTypeTriangle = 0x00000003, + LineCapTypeNoAnchor = 0x00000010, + LineCapTypeSquareAnchor = 0x00000011, + LineCapTypeRoundAnchor = 0x00000012, + LineCapTypeDiamondAnchor = 0x00000013, + LineCapTypeArrowAnchor = 0x00000014, + LineCapTypeAnchorMask = 0x000000F0, + LineCapTypeCustom = 0x000000FF + }; + + enum LineJoinType + { + LineJoinTypeMiter = 0x00000000, + LineJoinTypeBevel = 0x00000001, + LineJoinTypeRound = 0x00000002, + LineJoinTypeMiterClipped = 0x00000003 + }; + + enum DashedLineCapType + { + DashedLineCapTypeFlat = 0x00000000, + DashedLineCapTypeRound = 0x00000002, + DashedLineCapTypeTriangle = 0x00000003 + }; + + enum PenAlignment + { + PenAlignmentCenter = 0x00000000, + PenAlignmentInset = 0x00000001, + PenAlignmentLeft = 0x00000002, + PenAlignmentOutset = 0x00000003, + PenAlignmentRight = 0x00000004 + }; + + struct EMFPCustomLineCap; + + struct EMFPPen : public EMFPBrush + { + basegfx::B2DHomMatrix pen_transformation; //TODO: This isn't used + sal_uInt32 penDataFlags; + sal_uInt32 penUnit; + float penWidth; + sal_Int32 startCap; + sal_Int32 endCap; + 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_uInt32 customStartCapLen; + std::unique_ptr<EMFPCustomLineCap> customStartCap; + sal_uInt32 customEndCapLen; + std::unique_ptr<EMFPCustomLineCap> customEndCap; + + EMFPPen(); + + virtual ~EMFPPen() override; + + void Read(SvStream& s, EmfPlusHelperData const & rR); + + drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const double aTransformation) const; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpregion.cxx b/drawinglayer/source/tools/emfpregion.cxx new file mode 100644 index 000000000..9fcced337 --- /dev/null +++ b/drawinglayer/source/tools/emfpregion.cxx @@ -0,0 +1,134 @@ +/* -*- 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 <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <sal/log.hxx> +#include "emfpregion.hxx" +#include "emfppath.hxx" + +namespace emfplushelper +{ + EMFPRegion::EMFPRegion() + { + } + + EMFPRegion::~EMFPRegion() + { + } + + ::basegfx::B2DPolyPolygon EMFPRegion::ReadRegionNode(SvStream& s, EmfPlusHelperData& rR) + { + // Regions are specified as a binary tree of region nodes, and each node must either be a terminal node + // (RegionNodeDataTypeRect, RegionNodeDataTypePath, RegionNodeDataTypeEmpty, RegionNodeDataTypeInfinite) + // or specify one or two child nodes + // (RegionNodeDataTypeAnd, RegionNodeDataTypeOr, RegionNodeDataTypeXor, + // RegionNodeDataTypeExclude, RegionNodeDataTypeComplement). + sal_uInt32 dataType; + ::basegfx::B2DPolyPolygon polygon; + s.ReadUInt32(dataType); + SAL_INFO("drawinglayer.emf", "EMF+\t Region node data type 0x" << std::hex << dataType << std::dec); + + switch (dataType) + { + case RegionNodeDataTypeAnd: // CombineModeIntersect + case RegionNodeDataTypeOr: // CombineModeUnion + case RegionNodeDataTypeXor: // CombineModeXOR + case RegionNodeDataTypeExclude: // CombineModeExclude + case RegionNodeDataTypeComplement: // CombineModeComplement + { + ::basegfx::B2DPolyPolygon leftPolygon = ReadRegionNode(s, rR); + ::basegfx::B2DPolyPolygon rightPolygon = ReadRegionNode(s, rR); + polygon = EmfPlusHelperData::combineClip(leftPolygon, dataType, rightPolygon); + break; + } + case RegionNodeDataTypeRect: + { + float dx, dy, dw, dh; + s.ReadFloat(dx).ReadFloat(dy).ReadFloat(dw).ReadFloat(dh); + SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypeRect x:" << dx << ", y:" << dy << + ", width:" << dw << ", height:" << dh); + + const ::basegfx::B2DPoint mappedStartPoint(rR.Map(dx, dy)); + const ::basegfx::B2DPoint mappedEndPoint(rR.Map(dx + dw, dy + dh)); + polygon = ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( + mappedStartPoint.getX(), + mappedStartPoint.getY(), + mappedEndPoint.getX(), + mappedEndPoint.getY()))); + break; + } + case RegionNodeDataTypePath: + { + sal_Int32 pathLength; + s.ReadInt32(pathLength); + SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypePath, Path Length: " << pathLength << " bytes"); + + sal_uInt32 header, pathFlags; + sal_Int32 points; + + s.ReadUInt32(header).ReadInt32(points).ReadUInt32(pathFlags); + SAL_INFO("drawinglayer.emf", "EMF+\t\t header: 0x" << std::hex << header << + " points: " << std::dec << points << " additional flags: 0x" << std::hex << pathFlags << std::dec); + + EMFPPath path(points); + path.Read(s, pathFlags); + polygon = path.GetPolygon(rR); + break; + } + case RegionNodeDataTypeEmpty: + { + SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypeEmpty"); + SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO we need to set empty polygon here"); + polygon = ::basegfx::B2DPolyPolygon(); + + break; + } + case RegionNodeDataTypeInfinite: + { + SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypeInfinite"); + polygon = ::basegfx::B2DPolyPolygon(); + break; + } + default: + { + SAL_WARN("drawinglayer.emf", "EMF+\t\t Unhandled region type: 0x" << std::hex << dataType << std::dec); + polygon = ::basegfx::B2DPolyPolygon(); + } + } + return polygon; + } + + void EMFPRegion::ReadRegion(SvStream& s, EmfPlusHelperData& rR) + { + sal_uInt32 header, count; + s.ReadUInt32(header).ReadUInt32(count); + // An array should be RegionNodeCount+1 of EmfPlusRegionNode objects. + SAL_INFO("drawinglayer.emf", "EMF+\t version: 0x" << std::hex << header << std::dec << ", region node count: " << count); + + regionPolyPolygon = ReadRegionNode(s, rR); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpregion.hxx b/drawinglayer/source/tools/emfpregion.hxx new file mode 100644 index 000000000..a234abbb5 --- /dev/null +++ b/drawinglayer/source/tools/emfpregion.hxx @@ -0,0 +1,50 @@ +/* -*- 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 "emfphelperdata.hxx" + +namespace emfplushelper +{ + typedef enum + { + RegionNodeDataTypeAnd = 0x00000001, + RegionNodeDataTypeOr = 0x00000002, + RegionNodeDataTypeXor = 0x00000003, + RegionNodeDataTypeExclude = 0x00000004, + RegionNodeDataTypeComplement = 0x00000005, + RegionNodeDataTypeRect = 0x10000000, + RegionNodeDataTypePath = 0x10000001, + RegionNodeDataTypeEmpty = 0x10000002, + RegionNodeDataTypeInfinite = 0x10000003 + } RegionNodeDataType; + + struct EMFPRegion : public EMFPObject + { + ::basegfx::B2DPolyPolygon regionPolyPolygon; + + EMFPRegion(); + virtual ~EMFPRegion() override; + void ReadRegion(SvStream& s, EmfPlusHelperData& rR); + ::basegfx::B2DPolyPolygon ReadRegionNode(SvStream& s, EmfPlusHelperData& rR); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpstringformat.cxx b/drawinglayer/source/tools/emfpstringformat.cxx new file mode 100644 index 000000000..0a053201b --- /dev/null +++ b/drawinglayer/source/tools/emfpstringformat.cxx @@ -0,0 +1,195 @@ +/* -*- 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/log.hxx> +#include <rtl/ustrbuf.hxx> +#include "emfpstringformat.hxx" + +namespace emfplushelper +{ + EMFPStringFormat::EMFPStringFormat() + : header(0) + , stringFormatFlags(0) + , language(0) + , stringAlignment(0) + , lineAlign(0) + , digitSubstitution(0) + , digitLanguage(0) + , firstTabOffset(0.0) + , hotkeyPrefix(0) + , leadingMargin(0.0) + , trailingMargin(0.0) + , tracking(1.0) + , trimming(0) + , tabStopCount(0) + , rangeCount(0) + { + } + + static OUString StringFormatFlags(sal_uInt32 flag) + { + OUStringBuffer sFlags; + + // These are extracted from enum in emfpstringformat.hxx + if (flag & StringFormatDirectionRightToLeft) + sFlags.append("StringFormatDirectionRightToLeft "); + + if (flag & StringFormatDirectionVertical) + sFlags.append("StringFormatDirectionVertical "); + + if (flag & StringFormatNoFitBlackBox) + sFlags.append("StringFormatNoFitBlackBox "); + + if (flag & StringFormatDisplayFormatControl) + sFlags.append("StringFormatDisplayFormatControl "); + + if (flag & StringFormatNoFontFallback) + sFlags.append("StringFormatNoFontFallback "); + + if (flag & StringFormatMeasureTrailingSpaces) + sFlags.append("StringFormatMeasureTrailingSpaces "); + + if (flag & StringFormatNoWrap) + sFlags.append("StringFormatNoWrap "); + + if (flag & StringFormatLineLimit) + sFlags.append("StringFormatLineLimit "); + + if (flag & StringFormatNoClip) + sFlags.append("StringFormatNoClip "); + + if (flag & StringFormatBypassGDI) + sFlags.append("StringFormatBypassGDI "); + + // There will be 1 extra space in the end. It could be truncated, but + // as it is for SAL_INFO() only, it would not be important + return sFlags.makeStringAndClear(); + } + + static OUString StringAlignmentString(sal_uInt32 nAlignment) + { + switch(nAlignment) + { + case StringAlignment::StringAlignmentNear: + return "StringAlignmentNear"; + case StringAlignment::StringAlignmentCenter: + return "StringAlignmentCenter"; + case StringAlignment::StringAlignmentFar: + return "StringAlignmentFar"; + default: + assert(false && nAlignment && "invalid string alignment value"); + return "INVALID"; + } + } + + static OUString DigitSubstitutionString(sal_uInt32 nSubst) + { + switch(nSubst) + { + case StringDigitSubstitution::StringDigitSubstitutionUser: + return "StringDigitSubstitutionUser"; + case StringDigitSubstitution::StringDigitSubstitutionNone: + return "StringDigitSubstitutionNone"; + case StringDigitSubstitution::StringDigitSubstitutionNational: + return "StringDigitSubstitutionNational"; + case StringDigitSubstitution::StringDigitSubstitutionTraditional: + return "StringDigitSubstitutionTraditional"; + default: + assert(false && nSubst && "invalid string digit substitution value"); + return "INVALID"; + } + } + + static OUString HotkeyPrefixString(sal_uInt32 nHotkey) + { + switch(nHotkey) + { + case HotkeyPrefix::HotkeyPrefixNone: + return "HotkeyPrefixNone"; + case HotkeyPrefix::HotkeyPrefixShow: + return "HotkeyPrefixShow"; + case HotkeyPrefix::HotkeyPrefixHide: + return "HotkeyPrefixHide"; + default: + assert(false && nHotkey && "invalid hotkey prefix value"); + return "INVALID"; + } + } + + static OUString StringTrimmingString(sal_uInt32 nTrimming) + { + switch(nTrimming) + { + case StringTrimming::StringTrimmingNone: + return "StringTrimmingNone"; + case StringTrimming::StringTrimmingCharacter: + return "StringTrimmingCharacter"; + case StringTrimming::StringTrimmingWord: + return "StringTrimmingWord"; + case StringTrimming::StringTrimmingEllipsisCharacter: + return "StringTrimmingEllipsisCharacter"; + case StringTrimming::StringTrimmingEllipsisWord: + return "StringTrimmingEllipsisWord"; + case StringTrimming::StringTrimmingEllipsisPath: + return "StringTrimmingEllipsisPath"; + default: + assert(false && nTrimming && "invalid trim value"); + return "INVALID"; + } + } + + void EMFPStringFormat::Read(SvMemoryStream &s) + { + s.ReadUInt32(header).ReadUInt32(stringFormatFlags).ReadUInt32(language); + s.ReadUInt32(stringAlignment).ReadUInt32(lineAlign).ReadUInt32(digitSubstitution).ReadUInt32(digitLanguage); + s.ReadFloat(firstTabOffset).ReadInt32(hotkeyPrefix).ReadFloat(leadingMargin).ReadFloat(trailingMargin).ReadFloat(tracking); + s.ReadInt32(trimming).ReadInt32(tabStopCount).ReadInt32(rangeCount); + // keep only the last 16 bits of language + language >>= 16; + digitLanguage >>= 16; + SAL_WARN_IF((header >> 12) != 0xdbc01, "drawinglayer.emf", "Invalid header - not 0xdbc01"); + SAL_INFO("drawinglayer.emf", "EMF+\tString format"); + SAL_INFO("drawinglayer.emf", "EMF+\t\tHeader: 0x" << std::hex << (header >> 12)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tVersion: 0x" << (header & 0x1fff) << std::dec); + SAL_INFO("drawinglayer.emf", "EMF+\t\tStringFormatFlags: " << StringFormatFlags(stringFormatFlags)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tLanguage: sublangid: 0x" << std::hex << (language >> 10) << ", primarylangid: 0x" << (language & 0xF800)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tLineAlign: " << StringAlignmentString(lineAlign)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tDigitSubstitution: " << DigitSubstitutionString(digitSubstitution)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tDigitLanguage: sublangid: 0x" << std::hex << (digitLanguage >> 10) << ", primarylangid: 0x" << (digitLanguage & 0xF800)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tFirstTabOffset: " << firstTabOffset); + SAL_INFO("drawinglayer.emf", "EMF+\t\tHotkeyPrefix: " << HotkeyPrefixString(hotkeyPrefix)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tLeadingMargin: " << leadingMargin); + SAL_INFO("drawinglayer.emf", "EMF+\t\tTrailingMargin: " << trailingMargin); + SAL_INFO("drawinglayer.emf", "EMF+\t\tTracking: " << tracking); + SAL_INFO("drawinglayer.emf", "EMF+\t\tTrimming: " << StringTrimmingString(trimming)); + SAL_INFO("drawinglayer.emf", "EMF+\t\tTabStopCount: " << tabStopCount); + SAL_INFO("drawinglayer.emf", "EMF+\t\tRangeCount: " << rangeCount); + + SAL_WARN_IF(digitSubstitution != StringDigitSubstitution::StringDigitSubstitutionNone, + "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:digitSubstitution"); + SAL_WARN_IF(firstTabOffset != 0.0, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:firstTabOffset"); + SAL_WARN_IF(hotkeyPrefix != HotkeyPrefix::HotkeyPrefixNone, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:hotkeyPrefix"); + SAL_WARN_IF(tracking != 1.0, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:tracking"); + SAL_WARN_IF(trimming != StringTrimming::StringTrimmingNone, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:trimming"); + SAL_WARN_IF(tabStopCount, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:tabStopCount"); + SAL_WARN_IF(rangeCount != 0, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:StringFormatData"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/emfpstringformat.hxx b/drawinglayer/source/tools/emfpstringformat.hxx new file mode 100644 index 000000000..b4d8cb380 --- /dev/null +++ b/drawinglayer/source/tools/emfpstringformat.hxx @@ -0,0 +1,104 @@ +/* -*- 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 "emfphelperdata.hxx" + +namespace emfplushelper +{ + const sal_uInt32 StringFormatDirectionRightToLeft = 0x00000001; + const sal_uInt32 StringFormatDirectionVertical = 0x00000002; + const sal_uInt32 StringFormatNoFitBlackBox = 0x00000004; + const sal_uInt32 StringFormatDisplayFormatControl = 0x00000020; + const sal_uInt32 StringFormatNoFontFallback = 0x00000400; + const sal_uInt32 StringFormatMeasureTrailingSpaces = 0x00000800; + const sal_uInt32 StringFormatNoWrap = 0x00001000; + const sal_uInt32 StringFormatLineLimit = 0x00002000; + const sal_uInt32 StringFormatNoClip = 0x00004000; + const sal_uInt32 StringFormatBypassGDI = 0x80000000; + + enum StringAlignment + { + StringAlignmentNear = 0x00000000, + StringAlignmentCenter = 0x00000001, + StringAlignmentFar = 0x00000002 + }; + + enum StringDigitSubstitution + { + StringDigitSubstitutionUser = 0x00000000, + StringDigitSubstitutionNone = 0x00000001, + StringDigitSubstitutionNational = 0x00000002, + StringDigitSubstitutionTraditional = 0x00000003 + }; + + enum HotkeyPrefix + { + HotkeyPrefixNone = 0x00000000, + HotkeyPrefixShow = 0x00000001, + HotkeyPrefixHide = 0x00000002 + }; + + enum StringTrimming + { + StringTrimmingNone = 0x00000000, + StringTrimmingCharacter = 0x00000001, + StringTrimmingWord = 0x00000002, + StringTrimmingEllipsisCharacter = 0x00000003, + StringTrimmingEllipsisWord = 0x00000004, + StringTrimmingEllipsisPath = 0x00000005 + }; + + struct EMFPStringFormat : public EMFPObject + { + sal_uInt32 header; + sal_uInt32 stringFormatFlags; + sal_uInt32 language; + sal_uInt32 stringAlignment; // Horizontal alignment + sal_uInt32 lineAlign; // Vertical alignment + sal_uInt32 digitSubstitution; + sal_uInt32 digitLanguage; + float firstTabOffset; + sal_Int32 hotkeyPrefix; + float leadingMargin; // Length of the space to add to the starting position of a string. + float trailingMargin; // Length of the space to leave following a string. + float tracking; + sal_Int32 trimming; + sal_Int32 tabStopCount; + sal_Int32 rangeCount; + + EMFPStringFormat(); + void Read(SvMemoryStream &s); + + // flags table from MS-EMFPLUS doc + bool DirectionRightToLeft() const { return stringFormatFlags & 0x00000001;} + bool DirectionVertical() const { return stringFormatFlags & 0x00000002;} + bool NoFitBlackBox() const { return stringFormatFlags & 0x00000004;} + bool DisplayFormatControl() const { return stringFormatFlags & 0x00000020;} + bool NoFontFallback() const { return stringFormatFlags & 0x00000400;} + bool MeasureTrailingSpaces() const { return stringFormatFlags & 0x00000800;} + bool NoWrap() const { return stringFormatFlags & 0x00001000;} + bool LineLimit() const { return stringFormatFlags & 0x00002000;} + bool NoClip() const { return stringFormatFlags & 0x00004000;} + bool BypassGDI() const { return stringFormatFlags & 0x80000000;} + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx new file mode 100644 index 000000000..63562973d --- /dev/null +++ b/drawinglayer/source/tools/primitive2dxmldump.cxx @@ -0,0 +1,1182 @@ +/* -*- 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/. + */ + +#include <drawinglayer/tools/primitive2dxmldump.hxx> + +#include <rtl/string.hxx> +#include <tools/stream.hxx> +#include <tools/XmlWriter.hxx> + +#include <math.h> +#include <memory> +#include <sal/log.hxx> + +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#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/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/svggradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/sceneprimitive2d.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 <svx/sdr/primitive2d/svx_primitivetypes2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <drawinglayer/primitive3d/baseprimitive3d.hxx> +#include <drawinglayer/primitive3d/Tools.hxx> +#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx> +#include <drawinglayer/primitive3d/sdrextrudeprimitive3d.hxx> +#include <drawinglayer/attribute/sdrlightattribute3d.hxx> +#include <drawinglayer/attribute/sdrfillattribute.hxx> +#include <drawinglayer/attribute/fillhatchattribute.hxx> +#include <drawinglayer/attribute/fillgradientattribute.hxx> +#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> +#include <drawinglayer/attribute/materialattribute3d.hxx> + +using namespace drawinglayer::primitive2d; + +namespace drawinglayer +{ +namespace +{ +const size_t constMaxActionType = 513; + +OUString convertColorToString(const basegfx::BColor& rColor) +{ + OUString aRGBString = Color(rColor).AsRGBHexString(); + return "#" + aRGBString; +} + +void writeMatrix(::tools::XmlWriter& rWriter, const basegfx::B2DHomMatrix& rMatrix) +{ + rWriter.attribute("xy11", rMatrix.get(0, 0)); + rWriter.attribute("xy12", rMatrix.get(0, 1)); + rWriter.attribute("xy13", rMatrix.get(0, 2)); + 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)); +} + +void writeMatrix3D(::tools::XmlWriter& rWriter, const basegfx::B3DHomMatrix& rMatrix) +{ + rWriter.attribute("xy11", rMatrix.get(0, 0)); + rWriter.attribute("xy12", rMatrix.get(0, 1)); + rWriter.attribute("xy13", rMatrix.get(0, 2)); + rWriter.attribute("xy14", rMatrix.get(0, 3)); + rWriter.attribute("xy21", rMatrix.get(1, 0)); + rWriter.attribute("xy22", rMatrix.get(1, 1)); + rWriter.attribute("xy23", rMatrix.get(1, 2)); + rWriter.attribute("xy24", rMatrix.get(1, 3)); + rWriter.attribute("xy31", rMatrix.get(2, 0)); + rWriter.attribute("xy32", rMatrix.get(2, 1)); + rWriter.attribute("xy33", rMatrix.get(2, 2)); + rWriter.attribute("xy34", rMatrix.get(2, 3)); + rWriter.attribute("xy41", rMatrix.get(3, 0)); + rWriter.attribute("xy42", rMatrix.get(3, 1)); + rWriter.attribute("xy43", rMatrix.get(3, 2)); + rWriter.attribute("xy44", rMatrix.get(3, 3)); +} + +void writePolyPolygon(::tools::XmlWriter& rWriter, const basegfx::B2DPolyPolygon& rB2DPolyPolygon) +{ + rWriter.startElement("polypolygon"); + const basegfx::B2DRange aB2DRange(rB2DPolyPolygon.getB2DRange()); + rWriter.attributeDouble("height", aB2DRange.getHeight()); + rWriter.attributeDouble("width", aB2DRange.getWidth()); + rWriter.attributeDouble("minx", aB2DRange.getMinX()); + rWriter.attributeDouble("miny", aB2DRange.getMinY()); + rWriter.attributeDouble("maxx", aB2DRange.getMaxX()); + rWriter.attributeDouble("maxy", aB2DRange.getMaxY()); + rWriter.attribute("path", basegfx::utils::exportToSvgD(rB2DPolyPolygon, true, true, false)); + + for (basegfx::B2DPolygon const& rPolygon : rB2DPolyPolygon) + { + rWriter.startElement("polygon"); + for (sal_uInt32 i = 0; i < rPolygon.count(); ++i) + { + basegfx::B2DPoint const& rPoint = rPolygon.getB2DPoint(i); + + rWriter.startElement("point"); + rWriter.attribute("x", OUString::number(rPoint.getX())); + rWriter.attribute("y", OUString::number(rPoint.getY())); + rWriter.endElement(); + } + rWriter.endElement(); + } + + 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) +{ + rWriter.startElement("line"); + rWriter.attribute("color", convertColorToString(rLineAttribute.getColor())); + rWriter.attribute("width", rLineAttribute.getWidth()); + switch (rLineAttribute.getLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + rWriter.attribute("linejoin", "NONE"); + break; + case basegfx::B2DLineJoin::Bevel: + rWriter.attribute("linejoin", "Bevel"); + break; + case basegfx::B2DLineJoin::Miter: + { + rWriter.attribute("linejoin", "Miter"); + rWriter.attribute("miterangle", + basegfx::rad2deg(rLineAttribute.getMiterMinimumAngle())); + break; + } + case basegfx::B2DLineJoin::Round: + rWriter.attribute("linejoin", "Round"); + break; + default: + rWriter.attribute("linejoin", "Unknown"); + break; + } + switch (rLineAttribute.getLineCap()) + { + case css::drawing::LineCap::LineCap_BUTT: + rWriter.attribute("linecap", "BUTT"); + break; + case css::drawing::LineCap::LineCap_ROUND: + rWriter.attribute("linecap", "ROUND"); + break; + case css::drawing::LineCap::LineCap_SQUARE: + rWriter.attribute("linecap", "SQUARE"); + break; + default: + rWriter.attribute("linecap", "Unknown"); + break; + } + + rWriter.endElement(); +} + +void writeSdrLineAttribute(::tools::XmlWriter& rWriter, + const drawinglayer::attribute::SdrLineAttribute& rLineAttribute) +{ + if (rLineAttribute.isDefault()) + return; + + rWriter.startElement("line"); + rWriter.attribute("color", convertColorToString(rLineAttribute.getColor())); + rWriter.attribute("width", rLineAttribute.getWidth()); + rWriter.attribute("transparence", rLineAttribute.getTransparence()); + + switch (rLineAttribute.getJoin()) + { + case basegfx::B2DLineJoin::NONE: + rWriter.attribute("linejoin", "NONE"); + break; + case basegfx::B2DLineJoin::Bevel: + rWriter.attribute("linejoin", "Bevel"); + break; + case basegfx::B2DLineJoin::Miter: + rWriter.attribute("linejoin", "Miter"); + break; + case basegfx::B2DLineJoin::Round: + rWriter.attribute("linejoin", "Round"); + break; + default: + rWriter.attribute("linejoin", "Unknown"); + break; + } + switch (rLineAttribute.getCap()) + { + case css::drawing::LineCap::LineCap_BUTT: + rWriter.attribute("linecap", "BUTT"); + break; + case css::drawing::LineCap::LineCap_ROUND: + rWriter.attribute("linecap", "ROUND"); + break; + case css::drawing::LineCap::LineCap_SQUARE: + rWriter.attribute("linecap", "SQUARE"); + break; + default: + rWriter.attribute("linecap", "Unknown"); + break; + } + + if (!rLineAttribute.getDotDashArray().empty()) + { + OUString sDotDash; + for (double fDotDash : rLineAttribute.getDotDashArray()) + { + sDotDash += OUString::number(fDotDash) + " "; + } + rWriter.attribute("dotDashArray", sDotDash); + rWriter.attribute("fullDotDashLength", rLineAttribute.getFullDotDashLen()); + } + + rWriter.endElement(); +} + +void writeSdrFillAttribute(::tools::XmlWriter& rWriter, + const drawinglayer::attribute::SdrFillAttribute& rFillAttribute) +{ + if (rFillAttribute.isDefault()) + return; + + rWriter.startElement("fill"); + rWriter.attribute("color", convertColorToString(rFillAttribute.getColor())); + rWriter.attribute("transparence", rFillAttribute.getTransparence()); + + auto const& rGradient = rFillAttribute.getGradient(); + if (!rGradient.isDefault()) + { + rWriter.startElement("gradient"); + switch (rGradient.getStyle()) + { + case drawinglayer::attribute::GradientStyle::Linear: + rWriter.attribute("style", "Linear"); + break; + case drawinglayer::attribute::GradientStyle::Axial: + rWriter.attribute("style", "Axial"); + break; + case drawinglayer::attribute::GradientStyle::Radial: + rWriter.attribute("style", "Radial"); + break; + case drawinglayer::attribute::GradientStyle::Elliptical: + rWriter.attribute("style", "Elliptical"); + break; + case drawinglayer::attribute::GradientStyle::Square: + rWriter.attribute("style", "Square"); + break; + case drawinglayer::attribute::GradientStyle::Rect: + rWriter.attribute("style", "Rect"); + break; + } + rWriter.attribute("border", rGradient.getBorder()); + rWriter.attribute("offsetX", rGradient.getOffsetX()); + 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())); + rWriter.endElement(); + } + + auto const& rHatch = rFillAttribute.getHatch(); + if (!rHatch.isDefault()) + { + rWriter.startElement("hatch"); + switch (rHatch.getStyle()) + { + case drawinglayer::attribute::HatchStyle::Single: + rWriter.attribute("style", "Single"); + break; + case drawinglayer::attribute::HatchStyle::Double: + rWriter.attribute("style", "Double"); + break; + case drawinglayer::attribute::HatchStyle::Triple: + rWriter.attribute("style", "Triple"); + break; + } + rWriter.attribute("distance", rHatch.getDistance()); + rWriter.attribute("angle", rHatch.getAngle()); + rWriter.attribute("color", convertColorToString(rHatch.getColor())); + rWriter.attribute("minimalDescreteDistance", rHatch.getMinimalDiscreteDistance()); + rWriter.attribute("isFillBackground", sal_Int32(rHatch.isFillBackground())); + rWriter.endElement(); + } + + auto const& rGraphic = rFillAttribute.getFillGraphic(); + if (!rGraphic.isDefault()) + { + rWriter.startElement("graphic"); + // TODO + rWriter.endElement(); + } + + rWriter.endElement(); +} + +void writeShadeMode(::tools::XmlWriter& rWriter, const css::drawing::ShadeMode& rMode) +{ + switch (rMode) + { + case css::drawing::ShadeMode_FLAT: + rWriter.attribute("shadeMode", "Flat"); + break; + case css::drawing::ShadeMode_SMOOTH: + rWriter.attribute("shadeMode", "Smooth"); + break; + case css::drawing::ShadeMode_PHONG: + rWriter.attribute("shadeMode", "Phong"); + break; + case css::drawing::ShadeMode_DRAFT: + rWriter.attribute("shadeMode", "Draft"); + break; + default: + rWriter.attribute("shadeMode", "Undefined"); + break; + } +} + +void writeProjectionMode(::tools::XmlWriter& rWriter, const css::drawing::ProjectionMode& rMode) +{ + switch (rMode) + { + case css::drawing::ProjectionMode_PARALLEL: + rWriter.attribute("projectionMode", "Parallel"); + break; + case css::drawing::ProjectionMode_PERSPECTIVE: + rWriter.attribute("projectionMode", "Perspective"); + break; + default: + rWriter.attribute("projectionMode", "Undefined"); + break; + } +} + +void writeNormalsKind(::tools::XmlWriter& rWriter, const css::drawing::NormalsKind& rKind) +{ + switch (rKind) + { + case css::drawing::NormalsKind_SPECIFIC: + rWriter.attribute("normalsKind", "Specific"); + break; + case css::drawing::NormalsKind_FLAT: + rWriter.attribute("normalsKind", "Flat"); + break; + case css::drawing::NormalsKind_SPHERE: + rWriter.attribute("normalsKind", "Sphere"); + break; + default: + rWriter.attribute("normalsKind", "Undefined"); + break; + } +} + +void writeTextureProjectionMode(::tools::XmlWriter& rWriter, const char* pElement, + const css::drawing::TextureProjectionMode& rMode) +{ + switch (rMode) + { + case css::drawing::TextureProjectionMode_OBJECTSPECIFIC: + rWriter.attribute(pElement, "Specific"); + break; + case css::drawing::TextureProjectionMode_PARALLEL: + rWriter.attribute(pElement, "Parallel"); + break; + case css::drawing::TextureProjectionMode_SPHERE: + rWriter.attribute(pElement, "Sphere"); + break; + default: + rWriter.attribute(pElement, "Undefined"); + break; + } +} + +void writeTextureKind(::tools::XmlWriter& rWriter, const css::drawing::TextureKind2& rKind) +{ + switch (rKind) + { + case css::drawing::TextureKind2_LUMINANCE: + rWriter.attribute("textureKind", "Luminance"); + break; + case css::drawing::TextureKind2_INTENSITY: + rWriter.attribute("textureKind", "Intensity"); + break; + case css::drawing::TextureKind2_COLOR: + rWriter.attribute("textureKind", "Color"); + break; + default: + rWriter.attribute("textureKind", "Undefined"); + break; + } +} + +void writeTextureMode(::tools::XmlWriter& rWriter, const css::drawing::TextureMode& rMode) +{ + switch (rMode) + { + case css::drawing::TextureMode_REPLACE: + rWriter.attribute("textureMode", "Replace"); + break; + case css::drawing::TextureMode_MODULATE: + rWriter.attribute("textureMode", "Modulate"); + break; + case css::drawing::TextureMode_BLEND: + rWriter.attribute("textureMode", "Blend"); + break; + default: + rWriter.attribute("textureMode", "Undefined"); + break; + } +} + +void writeMaterialAttribute(::tools::XmlWriter& rWriter, + const drawinglayer::attribute::MaterialAttribute3D& rMaterial) +{ + rWriter.startElement("material"); + rWriter.attribute("color", convertColorToString(rMaterial.getColor())); + rWriter.attribute("specular", convertColorToString(rMaterial.getSpecular())); + rWriter.attribute("emission", convertColorToString(rMaterial.getEmission())); + rWriter.attribute("specularIntensity", rMaterial.getSpecularIntensity()); + rWriter.endElement(); +} + +void writeSpreadMethod(::tools::XmlWriter& rWriter, + const drawinglayer::primitive2d::SpreadMethod& rSpreadMethod) +{ + switch (rSpreadMethod) + { + case drawinglayer::primitive2d::SpreadMethod::Pad: + rWriter.attribute("spreadmethod", "pad"); + break; + case drawinglayer::primitive2d::SpreadMethod::Reflect: + rWriter.attribute("spreadmethod", "reflect"); + break; + case drawinglayer::primitive2d::SpreadMethod::Repeat: + rWriter.attribute("spreadmethod", "repeat"); + break; + default: + rWriter.attribute("spreadmethod", "unknown"); + } +} + +} // end anonymous namespace + +Primitive2dXmlDump::Primitive2dXmlDump() + : maFilter(constMaxActionType, false) +{ +} + +Primitive2dXmlDump::~Primitive2dXmlDump() = default; + +void Primitive2dXmlDump::dump( + const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence, + const OUString& rStreamName) +{ + std::unique_ptr<SvStream> pStream; + + if (rStreamName.isEmpty()) + pStream.reset(new SvMemoryStream()); + else + pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC)); + + ::tools::XmlWriter aWriter(pStream.get()); + aWriter.startDocument(); + aWriter.startElement("primitive2D"); + + decomposeAndWrite(rPrimitive2DSequence, aWriter); + + aWriter.endElement(); + aWriter.endDocument(); + + pStream->Seek(STREAM_SEEK_TO_BEGIN); +} + +namespace +{ +class Primitive3DXmlDump +{ +public: + void decomposeAndWrite(const drawinglayer::primitive3d::Primitive3DContainer& rSequence, + ::tools::XmlWriter& rWriter) + { + for (size_t i = 0; i < rSequence.size(); i++) + { + drawinglayer::primitive3d::Primitive3DReference xReference = rSequence[i]; + const auto* pBasePrimitive + = static_cast<const drawinglayer::primitive3d::BasePrimitive3D*>(xReference.get()); + sal_uInt32 nId = pBasePrimitive->getPrimitive3DID(); + OUString sCurrentElementTag = drawinglayer::primitive3d::idToString(nId); + switch (nId) + { + case PRIMITIVE3D_ID_SDREXTRUDEPRIMITIVE3D: + { + const auto* pExtrudePrimitive3D + = static_cast<const drawinglayer::primitive3d::SdrExtrudePrimitive3D*>( + xReference.get()); + rWriter.startElement("extrude3D"); + + rWriter.startElement("matrix3D"); + writeMatrix3D(rWriter, pExtrudePrimitive3D->getTransform()); + rWriter.endElement(); + + rWriter.attribute("textureSizeX", pExtrudePrimitive3D->getTextureSize().getX()); + rWriter.attribute("textureSizeY", pExtrudePrimitive3D->getTextureSize().getY()); + auto const& rLFSAttribute = pExtrudePrimitive3D->getSdrLFSAttribute(); + writeSdrLineAttribute(rWriter, rLFSAttribute.getLine()); + writeSdrFillAttribute(rWriter, rLFSAttribute.getFill()); + + rWriter.startElement("object3Dattributes"); + { + auto const& r3DObjectAttributes + = pExtrudePrimitive3D->getSdr3DObjectAttribute(); + + writeNormalsKind(rWriter, r3DObjectAttributes.getNormalsKind()); + writeTextureProjectionMode(rWriter, "textureProjectionX", + r3DObjectAttributes.getTextureProjectionX()); + writeTextureProjectionMode(rWriter, "textureProjectionY", + r3DObjectAttributes.getTextureProjectionY()); + writeTextureKind(rWriter, r3DObjectAttributes.getTextureKind()); + writeTextureMode(rWriter, r3DObjectAttributes.getTextureMode()); + writeMaterialAttribute(rWriter, r3DObjectAttributes.getMaterial()); + + rWriter.attribute("normalsInvert", + sal_Int32(r3DObjectAttributes.getNormalsInvert())); + rWriter.attribute("doubleSided", + sal_Int32(r3DObjectAttributes.getDoubleSided())); + rWriter.attribute("shadow3D", sal_Int32(r3DObjectAttributes.getShadow3D())); + rWriter.attribute("textureFilter", + sal_Int32(r3DObjectAttributes.getTextureFilter())); + rWriter.attribute("reducedGeometry", + sal_Int32(r3DObjectAttributes.getReducedLineGeometry())); + } + rWriter.endElement(); + + rWriter.attribute("depth", pExtrudePrimitive3D->getDepth()); + rWriter.attribute("diagonal", pExtrudePrimitive3D->getDiagonal()); + rWriter.attribute("backScale", pExtrudePrimitive3D->getBackScale()); + rWriter.attribute("smoothNormals", + sal_Int32(pExtrudePrimitive3D->getSmoothNormals())); + rWriter.attribute("smoothLids", + sal_Int32(pExtrudePrimitive3D->getSmoothLids())); + rWriter.attribute("characterMode", + sal_Int32(pExtrudePrimitive3D->getCharacterMode())); + rWriter.attribute("closeFront", + sal_Int32(pExtrudePrimitive3D->getCloseFront())); + rWriter.attribute("closeBack", sal_Int32(pExtrudePrimitive3D->getCloseBack())); + writePolyPolygon(rWriter, pExtrudePrimitive3D->getPolyPolygon()); + rWriter.endElement(); + } + break; + + default: + { + rWriter.startElement("unhandled"); + rWriter.attribute("id", + OUStringToOString(sCurrentElementTag, RTL_TEXTENCODING_UTF8)); + rWriter.attribute("idNumber", nId); + + drawinglayer::geometry::ViewInformation3D aViewInformation3D; + drawinglayer::primitive3d::Primitive3DContainer aContainer; + aContainer = pBasePrimitive->get3DDecomposition(aViewInformation3D); + decomposeAndWrite(aContainer, rWriter); + rWriter.endElement(); + } + break; + } + } + } +}; +} +xmlDocUniquePtr Primitive2dXmlDump::dumpAndParse( + const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence, + const OUString& rStreamName) +{ + std::unique_ptr<SvStream> pStream; + + if (rStreamName.isEmpty()) + pStream.reset(new SvMemoryStream()); + else + pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC)); + + ::tools::XmlWriter aWriter(pStream.get()); + aWriter.startDocument(); + aWriter.startElement("primitive2D"); + + decomposeAndWrite(rPrimitive2DSequence, aWriter); + + aWriter.endElement(); + aWriter.endDocument(); + + pStream->Seek(STREAM_SEEK_TO_BEGIN); + + std::size_t nSize = pStream->remainingSize(); + std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize + 1]); + pStream->ReadBytes(pBuffer.get(), nSize); + pBuffer[nSize] = 0; + SAL_INFO("drawinglayer", "Parsed XML: " << pBuffer.get()); + + return xmlDocUniquePtr(xmlParseDoc(reinterpret_cast<xmlChar*>(pBuffer.get()))); +} + +void Primitive2dXmlDump::decomposeAndWrite( + const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence, + ::tools::XmlWriter& rWriter) +{ + for (size_t i = 0; i < rPrimitive2DSequence.size(); i++) + { + const BasePrimitive2D* pBasePrimitive = rPrimitive2DSequence[i].get(); + sal_uInt32 nId = pBasePrimitive->getPrimitive2DID(); + if (nId < maFilter.size() && maFilter[nId]) + continue; + + OUString sCurrentElementTag = drawinglayer::primitive2d::idToString(nId); + + switch (nId) + { + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + const BitmapPrimitive2D& rBitmapPrimitive2D + = dynamic_cast<const BitmapPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("bitmap"); + writeMatrix(rWriter, rBitmapPrimitive2D.getTransform()); + + const BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapPrimitive2D.getXBitmap())); + const Size& rSizePixel(aBitmapEx.GetSizePixel()); + + rWriter.attribute("height", rSizePixel.getHeight()); + rWriter.attribute("width", rSizePixel.getWidth()); + rWriter.attribute("checksum", OString(std::to_string(aBitmapEx.GetChecksum()))); + + for (tools::Long y = 0; y < rSizePixel.getHeight(); y++) + { + rWriter.startElement("data"); + OUString aBitmapData = ""; + for (tools::Long x = 0; x < rSizePixel.getHeight(); x++) + { + if (x != 0) + aBitmapData = aBitmapData + ","; + aBitmapData = aBitmapData + aBitmapEx.GetPixelColor(x, y).AsRGBHexString(); + } + rWriter.attribute("row", aBitmapData); + rWriter.endElement(); + } + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D: + { + const HiddenGeometryPrimitive2D& rHiddenGeometryPrimitive2D + = dynamic_cast<const HiddenGeometryPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("hiddengeometry"); + decomposeAndWrite(rHiddenGeometryPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + const TransformPrimitive2D& rTransformPrimitive2D + = dynamic_cast<const TransformPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("transform"); + writeMatrix(rWriter, rTransformPrimitive2D.getTransformation()); + decomposeAndWrite(rTransformPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + const PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D + = dynamic_cast<const PolyPolygonColorPrimitive2D&>(*pBasePrimitive); + + rWriter.startElement("polypolygoncolor"); + rWriter.attribute("color", + convertColorToString(rPolyPolygonColorPrimitive2D.getBColor())); + + const basegfx::B2DPolyPolygon& aB2DPolyPolygon( + rPolyPolygonColorPrimitive2D.getB2DPolyPolygon()); + writePolyPolygon(rWriter, aB2DPolyPolygon); + + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + const PointArrayPrimitive2D& rPointArrayPrimitive2D + = dynamic_cast<const PointArrayPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("pointarray"); + + rWriter.attribute("color", + convertColorToString(rPointArrayPrimitive2D.getRGBColor())); + + const std::vector<basegfx::B2DPoint> aPositions + = rPointArrayPrimitive2D.getPositions(); + for (std::vector<basegfx::B2DPoint>::const_iterator iter = aPositions.begin(); + iter != aPositions.end(); ++iter) + { + rWriter.startElement("point"); + rWriter.attribute("x", OUString::number(iter->getX())); + rWriter.attribute("y", OUString::number(iter->getY())); + rWriter.endElement(); + } + + 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 + = dynamic_cast<const PolygonStrokePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("polygonstroke"); + + rWriter.startElement("polygon"); + rWriter.content( + basegfx::utils::exportToSvgPoints(rPolygonStrokePrimitive2D.getB2DPolygon())); + rWriter.endElement(); + + writeLineAttribute(rWriter, rPolygonStrokePrimitive2D.getLineAttribute()); + writeStrokeAttribute(rWriter, rPolygonStrokePrimitive2D.getStrokeAttribute()); + rWriter.endElement(); + } + break; + case PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D: + { + const PolyPolygonStrokePrimitive2D& rPolyPolygonStrokePrimitive2D + = dynamic_cast<const PolyPolygonStrokePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("polypolygonstroke"); + + writeLineAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getLineAttribute()); + writeStrokeAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getStrokeAttribute()); + writePolyPolygon(rWriter, rPolyPolygonStrokePrimitive2D.getB2DPolyPolygon()); + + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + const PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D + = dynamic_cast<const PolygonHairlinePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("polygonhairline"); + + rWriter.attribute("color", + convertColorToString(rPolygonHairlinePrimitive2D.getBColor())); + + rWriter.startElement("polygon"); + rWriter.content( + basegfx::utils::exportToSvgPoints(rPolygonHairlinePrimitive2D.getB2DPolygon())); + rWriter.endElement(); + + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + { + const TextDecoratedPortionPrimitive2D& rTextDecoratedPortionPrimitive2D + = dynamic_cast<const TextDecoratedPortionPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("textdecoratedportion"); + writeMatrix(rWriter, rTextDecoratedPortionPrimitive2D.getTextTransform()); + + rWriter.attribute("text", rTextDecoratedPortionPrimitive2D.getText()); + rWriter.attribute( + "fontcolor", + convertColorToString(rTextDecoratedPortionPrimitive2D.getFontColor())); + + const drawinglayer::attribute::FontAttribute& aFontAttribute + = rTextDecoratedPortionPrimitive2D.getFontAttribute(); + rWriter.attribute("familyname", aFontAttribute.getFamilyName()); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_TEXTLINEPRIMITIVE2D: + { + const TextLinePrimitive2D& rTextLinePrimitive2D + = dynamic_cast<const TextLinePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("textline"); + writeMatrix(rWriter, rTextLinePrimitive2D.getObjectTransformation()); + + rWriter.attribute("width", rTextLinePrimitive2D.getWidth()); + rWriter.attribute("offset", rTextLinePrimitive2D.getOffset()); + rWriter.attribute("height", rTextLinePrimitive2D.getHeight()); + rWriter.attribute("color", + convertColorToString(rTextLinePrimitive2D.getLineColor())); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + { + const TextSimplePortionPrimitive2D& rTextSimplePortionPrimitive2D + = dynamic_cast<const TextSimplePortionPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("textsimpleportion"); + + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + if (rTextSimplePortionPrimitive2D.getTextTransform().decompose(aScale, aTranslate, + fRotate, fShearX)) + { + rWriter.attribute("width", aScale.getX()); + rWriter.attribute("height", aScale.getY()); + } + rWriter.attribute("x", aTranslate.getX()); + rWriter.attribute("y", aTranslate.getY()); + OUString aText = rTextSimplePortionPrimitive2D.getText(); + // TODO share code with sax_fastparser::FastSaxSerializer::write(). + rWriter.attribute("text", aText.replaceAll("", "	")); + rWriter.attribute("fontcolor", convertColorToString( + rTextSimplePortionPrimitive2D.getFontColor())); + + const drawinglayer::attribute::FontAttribute& aFontAttribute + = rTextSimplePortionPrimitive2D.getFontAttribute(); + rWriter.attribute("familyname", aFontAttribute.getFamilyName()); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_GROUPPRIMITIVE2D: + { + const GroupPrimitive2D& rGroupPrimitive2D + = dynamic_cast<const GroupPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("group"); + decomposeAndWrite(rGroupPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + const MaskPrimitive2D& rMaskPrimitive2D + = dynamic_cast<const MaskPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("mask"); + writePolyPolygon(rWriter, rMaskPrimitive2D.getMask()); + decomposeAndWrite(rMaskPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + const UnifiedTransparencePrimitive2D& rUnifiedTransparencePrimitive2D + = dynamic_cast<const UnifiedTransparencePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("unifiedtransparence"); + rWriter.attribute( + "transparence", + std::lround(100 * rUnifiedTransparencePrimitive2D.getTransparence())); + decomposeAndWrite(rUnifiedTransparencePrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: + { + const ObjectInfoPrimitive2D& rObjectInfoPrimitive2D + = dynamic_cast<const ObjectInfoPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("objectinfo"); + + decomposeAndWrite(rObjectInfoPrimitive2D.getChildren(), rWriter); + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D: + { + const SvgRadialGradientPrimitive2D& rSvgRadialGradientPrimitive2D + = dynamic_cast<const SvgRadialGradientPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("svgradialgradient"); + 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())); + 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; + + case PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D: + { + const SvgLinearGradientPrimitive2D& rSvgLinearGradientPrimitive2D + = dynamic_cast<const SvgLinearGradientPrimitive2D&>(*pBasePrimitive); + rWriter.startElement("svglineargradient"); + basegfx::B2DPoint aStartAttribute = rSvgLinearGradientPrimitive2D.getStart(); + basegfx::B2DPoint aEndAttribute = rSvgLinearGradientPrimitive2D.getEnd(); + + rWriter.attribute("startx", aStartAttribute.getX()); + rWriter.attribute("starty", aStartAttribute.getY()); + rWriter.attribute("endx", aEndAttribute.getX()); + rWriter.attribute("endy", aEndAttribute.getY()); + writeSpreadMethod(rWriter, rSvgLinearGradientPrimitive2D.getSpreadMethod()); + rWriter.attributeDouble( + "opacity", + rSvgLinearGradientPrimitive2D.getGradientEntries().front().getOpacity()); + + rWriter.startElement("transform"); + writeMatrix(rWriter, rSvgLinearGradientPrimitive2D.getGradientTransform()); + rWriter.endElement(); + + writePolyPolygon(rWriter, rSvgLinearGradientPrimitive2D.getPolyPolygon()); + + rWriter.endElement(); + } + break; + + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: + { + const MetafilePrimitive2D& rMetafilePrimitive2D + = dynamic_cast<const MetafilePrimitive2D&>(*pBasePrimitive); + rWriter.startElement("metafile"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + // since the graphic is not rendered in a document, we do not need a concrete view information + rMetafilePrimitive2D.get2DDecomposition( + aPrimitiveContainer, drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + } + + break; + + case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D: + { + // SdrRectanglePrimitive2D is private to us. + rWriter.startElement("sdrrectangle"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_SDRBLOCKTEXTPRIMITIVE2D: + { + // SdrBlockTextPrimitive2D is private to us. + rWriter.startElement("sdrblocktext"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D: + { + // TextHierarchyBlockPrimitive2D. + rWriter.startElement("texthierarchyblock"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D: + { + // TextHierarchyParagraphPrimitive2D. + rWriter.startElement("texthierarchyparagraph"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D: + { + // TextHierarchyLinePrimitive2D. + rWriter.startElement("texthierarchyline"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: + { + // ShadowPrimitive2D. + rWriter.startElement("shadow"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + // ModifiedColorPrimitive2D. + rWriter.startElement("modifiedColor"); + drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, + drawinglayer::geometry::ViewInformation2D()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + break; + } + + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D: + { + const auto& rScenePrimitive2D + = dynamic_cast<const drawinglayer::primitive2d::ScenePrimitive2D&>( + *pBasePrimitive); + rWriter.startElement("scene"); + + auto const& rSceneAttribute = rScenePrimitive2D.getSdrSceneAttribute(); + + rWriter.attribute("shadowSlant", rSceneAttribute.getShadowSlant()); + rWriter.attribute("isTwoSidedLighting", + sal_Int32(rSceneAttribute.getTwoSidedLighting())); + writeShadeMode(rWriter, rSceneAttribute.getShadeMode()); + writeProjectionMode(rWriter, rSceneAttribute.getProjectionMode()); + + auto const& rLightingAttribute = rScenePrimitive2D.getSdrLightingAttribute(); + rWriter.attribute("ambientLightColor", + convertColorToString(rLightingAttribute.getAmbientLightColor())); + rWriter.startElement("lights"); + for (auto const& rLight : rLightingAttribute.getLightVector()) + { + rWriter.startElement("light"); + rWriter.attribute("color", convertColorToString(rLight.getColor())); + rWriter.attribute("directionVectorX", rLight.getDirection().getX()); + rWriter.attribute("directionVectorY", rLight.getDirection().getY()); + rWriter.attribute("specular", sal_Int32(rLight.getSpecular())); + rWriter.endElement(); + } + rWriter.endElement(); + + Primitive3DXmlDump aPrimitive3DXmlDump; + aPrimitive3DXmlDump.decomposeAndWrite(rScenePrimitive2D.getChildren3D(), rWriter); + + rWriter.endElement(); + break; + } + + 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.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()); + decomposeAndWrite(aPrimitiveContainer, rWriter); + rWriter.endElement(); + } + break; + } + } +} + +} // end namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/tools/wmfemfhelper.cxx b/drawinglayer/source/tools/wmfemfhelper.cxx new file mode 100644 index 000000000..8a5dbab74 --- /dev/null +++ b/drawinglayer/source/tools/wmfemfhelper.cxx @@ -0,0 +1,3036 @@ +/* -*- 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 <wmfemfhelper.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.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> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <vcl/BitmapPalette.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <drawinglayer/primitive2d/invertprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <primitive2d/wallpaperprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <primitive2d/textlineprimitive2d.hxx> +#include <primitive2d/textstrikeoutprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> +#include <sal/log.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/gradient.hxx> +#include <vcl/hatch.hxx> +#include <vcl/outdev.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <emfplushelper.hxx> +#include <numeric> +#include <toolkit/helper/vclunohelper.hxx> + +namespace drawinglayer::primitive2d +{ + namespace { + + /** NonOverlappingFillGradientPrimitive2D class + + This is a special version of the FillGradientPrimitive2D which decomposes + to a non-overlapping geometry version of the gradient. This needs to be + used to support the old XOR paint-'trick'. + + It does not need an own identifier since a renderer who wants to interpret + it itself may do so. It just overrides the decomposition of the C++ + implementation class to do an alternative decomposition. + */ + class NonOverlappingFillGradientPrimitive2D : public FillGradientPrimitive2D + { + protected: + /// local decomposition. + virtual void create2DDecomposition(Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& rViewInformation) const override; + + public: + /// constructor + NonOverlappingFillGradientPrimitive2D( + const basegfx::B2DRange& rObjectRange, + const attribute::FillGradientAttribute& rFillGradient) + : FillGradientPrimitive2D(rObjectRange, rFillGradient) + { + } + }; + + } + + void NonOverlappingFillGradientPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if (!getFillGradient().isDefault()) + { + createFill(rContainer, false); + } + } + +} // end of namespace + +namespace wmfemfhelper +{ + /** helper class for graphic context + + This class allows to hold a complete representation of classic + VCL OutputDevice state. This data is needed for correct + interpretation of the MetaFile action flow. + */ + PropertyHolder::PropertyHolder() + : maMapUnit(MapUnit::Map100thMM), + maTextColor(sal_uInt32(COL_BLACK)), + maRasterOp(RasterOp::OverPaint), + mnLayoutMode(vcl::text::ComplexTextLayoutFlags::Default), + maLanguageType(0), + mnPushFlags(vcl::PushFlags::NONE), + mbLineColor(false), + mbFillColor(false), + mbTextColor(true), + mbTextFillColor(false), + mbTextLineColor(false), + mbOverlineColor(false), + mbClipPolyPolygonActive(false) + { + } +} + +namespace wmfemfhelper +{ + /** stack for properties + + This class builds a stack based on the PropertyHolder + class. It encapsulates the pointer/new/delete usage to + make it safe and implements the push/pop as needed by a + VCL Metafile interpreter. The critical part here are the + flag values VCL OutputDevice uses here; not all stuff is + pushed and thus needs to be copied at pop. + */ + PropertyHolders::PropertyHolders() + { + maPropertyHolders.push_back(new PropertyHolder()); + } + + void PropertyHolders::PushDefault() + { + PropertyHolder* pNew = new PropertyHolder(); + maPropertyHolders.push_back(pNew); + } + + void PropertyHolders::Push(vcl::PushFlags nPushFlags) + { + if (bool(nPushFlags)) + { + OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: PUSH with no property holders (!)"); + if (!maPropertyHolders.empty()) + { + PropertyHolder* pNew = new PropertyHolder(*maPropertyHolders.back()); + pNew->setPushFlags(nPushFlags); + maPropertyHolders.push_back(pNew); + } + } + } + + void PropertyHolders::Pop() + { + OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: POP with no property holders (!)"); + const sal_uInt32 nSize(maPropertyHolders.size()); + + if (!nSize) + return; + + const PropertyHolder* pTip = maPropertyHolders.back(); + const vcl::PushFlags nPushFlags(pTip->getPushFlags()); + + if (nPushFlags != vcl::PushFlags::NONE) + { + if (nSize > 1) + { + // copy back content for all non-set flags + PropertyHolder* pLast = maPropertyHolders[nSize - 2]; + + if (vcl::PushFlags::ALL != nPushFlags) + { + if (!(nPushFlags & vcl::PushFlags::LINECOLOR)) + { + pLast->setLineColor(pTip->getLineColor()); + pLast->setLineColorActive(pTip->getLineColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::FILLCOLOR)) + { + pLast->setFillColor(pTip->getFillColor()); + pLast->setFillColorActive(pTip->getFillColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::FONT)) + { + pLast->setFont(pTip->getFont()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTCOLOR)) + { + pLast->setTextColor(pTip->getTextColor()); + pLast->setTextColorActive(pTip->getTextColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::MAPMODE)) + { + pLast->setTransformation(pTip->getTransformation()); + pLast->setMapUnit(pTip->getMapUnit()); + } + if (!(nPushFlags & vcl::PushFlags::CLIPREGION)) + { + pLast->setClipPolyPolygon(pTip->getClipPolyPolygon()); + pLast->setClipPolyPolygonActive(pTip->getClipPolyPolygonActive()); + } + if (!(nPushFlags & vcl::PushFlags::RASTEROP)) + { + pLast->setRasterOp(pTip->getRasterOp()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTFILLCOLOR)) + { + pLast->setTextFillColor(pTip->getTextFillColor()); + pLast->setTextFillColorActive(pTip->getTextFillColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTALIGN)) + { + if (pLast->getFont().GetAlignment() != pTip->getFont().GetAlignment()) + { + vcl::Font aFont(pLast->getFont()); + aFont.SetAlignment(pTip->getFont().GetAlignment()); + pLast->setFont(aFont); + } + } + if (!(nPushFlags & vcl::PushFlags::REFPOINT)) + { + // not supported + } + if (!(nPushFlags & vcl::PushFlags::TEXTLINECOLOR)) + { + pLast->setTextLineColor(pTip->getTextLineColor()); + pLast->setTextLineColorActive(pTip->getTextLineColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTLAYOUTMODE)) + { + pLast->setLayoutMode(pTip->getLayoutMode()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTLANGUAGE)) + { + pLast->setLanguageType(pTip->getLanguageType()); + } + if (!(nPushFlags & vcl::PushFlags::OVERLINECOLOR)) + { + pLast->setOverlineColor(pTip->getOverlineColor()); + pLast->setOverlineColorActive(pTip->getOverlineColorActive()); + } + } + } + } + + // execute the pop + delete maPropertyHolders.back(); + maPropertyHolders.pop_back(); + } + + PropertyHolder& PropertyHolders::Current() + { + static PropertyHolder aDummy; + OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: CURRENT with no property holders (!)"); + return maPropertyHolders.empty() ? aDummy : *maPropertyHolders.back(); + } + + PropertyHolders::~PropertyHolders() + { + while (!maPropertyHolders.empty()) + { + delete maPropertyHolders.back(); + maPropertyHolders.pop_back(); + } + } +} + +namespace +{ + /** helper to convert a vcl::Region to a B2DPolyPolygon + when it does not yet contain one. In the future + this may be expanded to merge the polygons created + from rectangles or use a special algo to directly turn + the spans of regions to a single, already merged + PolyPolygon. + */ + basegfx::B2DPolyPolygon getB2DPolyPolygonFromRegion(const vcl::Region& rRegion) + { + basegfx::B2DPolyPolygon aRetval; + + if (!rRegion.IsEmpty()) + { + aRetval = rRegion.GetAsB2DPolyPolygon(); + } + + return aRetval; + } +} + +namespace wmfemfhelper +{ + /** Helper class to buffer and hold a Primitive target vector. It + encapsulates the new/delete functionality and allows to work + on pointers of the implementation classes. All data will + be converted to uno sequences of uno references when accessing the + data. + */ + TargetHolder::TargetHolder() + { + } + + TargetHolder::~TargetHolder() + { + } + + sal_uInt32 TargetHolder::size() const + { + return aTargets.size(); + } + + void TargetHolder::append(drawinglayer::primitive2d::BasePrimitive2D* pCandidate) + { + if (pCandidate) + { + aTargets.push_back(pCandidate); + } + } + + drawinglayer::primitive2d::Primitive2DContainer TargetHolder::getPrimitive2DSequence(const PropertyHolder& rPropertyHolder) + { + drawinglayer::primitive2d::Primitive2DContainer xRetval = std::move(aTargets); + + + if (!xRetval.empty() && rPropertyHolder.getClipPolyPolygonActive()) + { + const basegfx::B2DPolyPolygon& rClipPolyPolygon = rPropertyHolder.getClipPolyPolygon(); + + if (rClipPolyPolygon.count()) + { + drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + rClipPolyPolygon, + std::move(xRetval))); + + xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask }; + } + } + + return xRetval; + } +} + +namespace wmfemfhelper +{ + /** Helper class which builds a stack on the TargetHolder class */ + TargetHolders::TargetHolders() + { + maTargetHolders.push_back(new TargetHolder()); + } + + sal_uInt32 TargetHolders::size() const + { + return maTargetHolders.size(); + } + + void TargetHolders::Push() + { + maTargetHolders.push_back(new TargetHolder()); + } + + void TargetHolders::Pop() + { + OSL_ENSURE(maTargetHolders.size(), "TargetHolders: POP with no property holders (!)"); + if (!maTargetHolders.empty()) + { + delete maTargetHolders.back(); + maTargetHolders.pop_back(); + } + } + + TargetHolder& TargetHolders::Current() + { + static TargetHolder aDummy; + OSL_ENSURE(maTargetHolders.size(), "TargetHolders: CURRENT with no property holders (!)"); + return maTargetHolders.empty() ? aDummy : *maTargetHolders.back(); + } + + TargetHolders::~TargetHolders() + { + while (!maTargetHolders.empty()) + { + delete maTargetHolders.back(); + maTargetHolders.pop_back(); + } + } +} + +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 wmfemfhelper +{ + /** helper to create a PointArrayPrimitive2D based on current context */ + static void createPointArrayPrimitive( + std::vector< basegfx::B2DPoint >&& rPositions, + TargetHolder& rTarget, + PropertyHolder const & rProperties, + const basegfx::BColor& rBColor) + { + if(rPositions.empty()) + return; + + if(rProperties.getTransformation().isIdentity()) + { + rTarget.append( + new drawinglayer::primitive2d::PointArrayPrimitive2D( + std::move(rPositions), + rBColor)); + } + else + { + for(basegfx::B2DPoint & aPosition : rPositions) + { + aPosition = rProperties.getTransformation() * aPosition; + } + + rTarget.append( + new drawinglayer::primitive2d::PointArrayPrimitive2D( + std::move(rPositions), + rBColor)); + } + } + + /** helper to create a PolygonHairlinePrimitive2D based on current context */ + static void createHairlinePrimitive( + const basegfx::B2DPolygon& rLinePolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rLinePolygon.count()) + { + basegfx::B2DPolygon aLinePolygon(rLinePolygon); + aLinePolygon.transform(rProperties.getTransformation()); + rTarget.append( + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aLinePolygon, + rProperties.getLineColor())); + } + } + + /** helper to create a PolyPolygonColorPrimitive2D based on current context */ + static void createFillPrimitive( + const basegfx::B2DPolyPolygon& rFillPolyPolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rFillPolyPolygon.count()) + { + basegfx::B2DPolyPolygon aFillPolyPolygon(rFillPolyPolygon); + aFillPolyPolygon.transform(rProperties.getTransformation()); + rTarget.append( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + aFillPolyPolygon, + rProperties.getFillColor())); + } + } + + /** helper to create a PolygonStrokePrimitive2D based on current context */ + static void createLinePrimitive( + const basegfx::B2DPolygon& rLinePolygon, + const LineInfo& rLineInfo, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(!rLinePolygon.count()) + return; + + const bool bDashDotUsed(LineStyle::Dash == rLineInfo.GetStyle()); + const bool bWidthUsed(rLineInfo.GetWidth() > 1); + + if(bDashDotUsed || bWidthUsed) + { + basegfx::B2DPolygon aLinePolygon(rLinePolygon); + aLinePolygon.transform(rProperties.getTransformation()); + const drawinglayer::attribute::LineAttribute aLineAttribute( + rProperties.getLineColor(), + bWidthUsed ? rLineInfo.GetWidth() : 0.0, + rLineInfo.GetLineJoin(), + rLineInfo.GetLineCap()); + + if(bDashDotUsed) + { + std::vector< double > fDotDashArray = rLineInfo.GetDotDashArray(); + const double fAccumulated(std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0)); + const drawinglayer::attribute::StrokeAttribute aStrokeAttribute( + std::move(fDotDashArray), + fAccumulated); + + rTarget.append( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + aLinePolygon, + aLineAttribute, + aStrokeAttribute)); + } + else + { + rTarget.append( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + aLinePolygon, + aLineAttribute)); + } + } + else + { + createHairlinePrimitive(rLinePolygon, rTarget, rProperties); + } + } + + /** helper to create needed line and fill primitives based on current context */ + static void createHairlineAndFillPrimitive( + const basegfx::B2DPolygon& rPolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rProperties.getFillColorActive()) + { + createFillPrimitive(basegfx::B2DPolyPolygon(rPolygon), rTarget, rProperties); + } + + if(rProperties.getLineColorActive()) + { + createHairlinePrimitive(rPolygon, rTarget, rProperties); + } + } + + /** helper to create needed line and fill primitives based on current context */ + static void createHairlineAndFillPrimitive( + const basegfx::B2DPolyPolygon& rPolyPolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rProperties.getFillColorActive()) + { + createFillPrimitive(rPolyPolygon, rTarget, rProperties); + } + + if(rProperties.getLineColorActive()) + { + for(sal_uInt32 a(0); a < rPolyPolygon.count(); a++) + { + createHairlinePrimitive(rPolyPolygon.getB2DPolygon(a), rTarget, rProperties); + } + } + } + + /** helper to create DiscreteBitmapPrimitive2D based on current context. + The DiscreteBitmapPrimitive2D is especially created for this usage + since no other usage defines a bitmap visualisation based on top-left + position and size in pixels. At the end it will create a view-dependent + transformed embedding of a BitmapPrimitive2D. + */ + static void createBitmapExPrimitive( + const BitmapEx& rBitmapEx, + const Point& rPoint, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(!rBitmapEx.IsEmpty()) + { + basegfx::B2DPoint aPoint(rPoint.X(), rPoint.Y()); + aPoint = rProperties.getTransformation() * aPoint; + + rTarget.append( + new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D( + rBitmapEx, + aPoint)); + } + } + + /** helper to create BitmapPrimitive2D based on current context */ + static void createBitmapExPrimitive( + const BitmapEx& rBitmapEx, + const Point& rPoint, + const Size& rSize, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rBitmapEx.IsEmpty()) + return; + + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, rSize.Width()); + aObjectTransform.set(1, 1, rSize.Height()); + aObjectTransform.set(0, 2, rPoint.X()); + aObjectTransform.set(1, 2, rPoint.Y()); + + aObjectTransform = rProperties.getTransformation() * aObjectTransform; + + rTarget.append( + new drawinglayer::primitive2d::BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(rBitmapEx), + aObjectTransform)); + } + + /** helper to create a regular BotmapEx from a MaskAction (definitions + which use a bitmap without transparence but define one of the colors as + transparent) + */ + static BitmapEx createMaskBmpEx(const Bitmap& rBitmap, const Color& rMaskColor) + { + const Color aWhite(COL_WHITE); + BitmapPalette aBiLevelPalette { + aWhite, rMaskColor + }; + + Bitmap aMask(rBitmap.CreateMask(aWhite)); + Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N1_BPP, &aBiLevelPalette); + + aSolid.Erase(rMaskColor); + + return BitmapEx(aSolid, aMask); + } + + /** helper to convert from a VCL Gradient definition to the corresponding + data for primitive representation + */ + static drawinglayer::attribute::FillGradientAttribute createFillGradientAttribute(const Gradient& rGradient) + { + const Color aStartColor(rGradient.GetStartColor()); + const sal_uInt16 nStartIntens(rGradient.GetStartIntensity()); + basegfx::BColor aStart(aStartColor.getBColor()); + + if(nStartIntens != 100) + { + const basegfx::BColor aBlack; + aStart = interpolate(aBlack, aStart, static_cast<double>(nStartIntens) * 0.01); + } + + const Color aEndColor(rGradient.GetEndColor()); + const sal_uInt16 nEndIntens(rGradient.GetEndIntensity()); + basegfx::BColor aEnd(aEndColor.getBColor()); + + if(nEndIntens != 100) + { + const basegfx::BColor aBlack; + 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, + 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, + rGradient.GetSteps()); + } + + /** helper to convert from a VCL Hatch definition to the corresponding + data for primitive representation + */ + static drawinglayer::attribute::FillHatchAttribute createFillHatchAttribute(const Hatch& rHatch) + { + drawinglayer::attribute::HatchStyle aHatchStyle(drawinglayer::attribute::HatchStyle::Single); + + switch(rHatch.GetStyle()) + { + default : // case HatchStyle::Single : + { + aHatchStyle = drawinglayer::attribute::HatchStyle::Single; + break; + } + case HatchStyle::Double : + { + aHatchStyle = drawinglayer::attribute::HatchStyle::Double; + break; + } + case HatchStyle::Triple : + { + aHatchStyle = drawinglayer::attribute::HatchStyle::Triple; + break; + } + } + + return drawinglayer::attribute::FillHatchAttribute( + aHatchStyle, + static_cast<double>(rHatch.GetDistance()), + toRadians(rHatch.GetAngle()), + rHatch.GetColor().getBColor(), + 3, // same default as VCL, a minimum of three discrete units (pixels) offset + false); + } + + /** helper to take needed action on ClipRegion change. This method needs to be called + on any vcl::Region change, e.g. at the obvious actions doing this, but also at pop-calls + which change the vcl::Region of the current context. It takes care of creating the + current embedded context, set the new vcl::Region at the context and possibly prepare + a new target for including new geometry into the current region + */ + void HandleNewClipRegion( + const basegfx::B2DPolyPolygon& rClipPolyPolygon, + TargetHolders& rTargetHolders, + PropertyHolders& rPropertyHolders) + { + const bool bNewActive(rClipPolyPolygon.count()); + + // #i108636# The handling of new ClipPolyPolygons was not done as good as possible + // in the first version of this interpreter; e.g. when a ClipPolyPolygon was set + // initially and then using a lot of push/pop actions, the pop always leads + // to setting a 'new' ClipPolyPolygon which indeed is the return to the ClipPolyPolygon + // of the properties next on the stack. + + // This ClipPolyPolygon is identical to the current one, so there is no need to + // create a MaskPrimitive2D containing the up-to-now created primitives, but + // this was done before. While this does not lead to wrong primitive + // representations of the metafile data, it creates unnecessarily expensive + // representations. Just detecting when no really 'new' ClipPolyPolygon gets set + // solves the problem. + + if(!rPropertyHolders.Current().getClipPolyPolygonActive() && !bNewActive) + { + // no active ClipPolyPolygon exchanged by no new one, done + return; + } + + if(rPropertyHolders.Current().getClipPolyPolygonActive() && bNewActive) + { + // active ClipPolyPolygon and new active ClipPolyPolygon + if(rPropertyHolders.Current().getClipPolyPolygon() == rClipPolyPolygon) + { + // new is the same as old, done + return; + } + } + + // Here the old and the new are definitively different, maybe + // old one and/or new one is not active. + + // Handle deletion of old ClipPolyPolygon. The process evtl. created primitives which + // belong to this active ClipPolyPolygon. These need to be embedded to a + // MaskPrimitive2D accordingly. + if(rPropertyHolders.Current().getClipPolyPolygonActive() && rTargetHolders.size() > 1) + { + drawinglayer::primitive2d::Primitive2DContainer aSubContent; + + if(rPropertyHolders.Current().getClipPolyPolygon().count() + && rTargetHolders.Current().size()) + { + aSubContent = rTargetHolders.Current().getPrimitive2DSequence( + rPropertyHolders.Current()); + } + + rTargetHolders.Pop(); + + if(!aSubContent.empty()) + { + rTargetHolders.Current().append( + new drawinglayer::primitive2d::GroupPrimitive2D( + std::move(aSubContent))); + } + } + + // apply new settings to current properties by setting + // the new region now + rPropertyHolders.Current().setClipPolyPolygonActive(bNewActive); + + if(bNewActive) + { + rPropertyHolders.Current().setClipPolyPolygon(rClipPolyPolygon); + + // prepare new content holder for new active region + rTargetHolders.Push(); + } + } + + /** helper to handle the change of RasterOp. It takes care of encapsulating all current + geometry to the current RasterOp (if changed) and needs to be called on any RasterOp + change. It will also start a new geometry target to embrace to the new RasterOp if + a changing RasterOp is used. Currently, RasterOp::Xor and RasterOp::Invert are supported using + InvertPrimitive2D, and RasterOp::N0 by using a ModifiedColorPrimitive2D to force to black paint + */ + static void HandleNewRasterOp( + RasterOp aRasterOp, + TargetHolders& rTargetHolders, + PropertyHolders& rPropertyHolders) + { + // check if currently active + if(rPropertyHolders.Current().isRasterOpActive() && rTargetHolders.size() > 1) + { + drawinglayer::primitive2d::Primitive2DContainer aSubContent; + + if(rTargetHolders.Current().size()) + { + aSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()); + } + + rTargetHolders.Pop(); + + if(!aSubContent.empty()) + { + if(rPropertyHolders.Current().isRasterOpForceBlack()) + { + // force content to black + rTargetHolders.Current().append( + new drawinglayer::primitive2d::ModifiedColorPrimitive2D( + std::move(aSubContent), + std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(0.0, 0.0, 0.0)))); + } + else // if(rPropertyHolders.Current().isRasterOpInvert()) + { + // invert content + rTargetHolders.Current().append( + new drawinglayer::primitive2d::InvertPrimitive2D( + std::move(aSubContent))); + } + } + } + + // apply new settings + rPropertyHolders.Current().setRasterOp(aRasterOp); + + // check if now active + if(rPropertyHolders.Current().isRasterOpActive()) + { + // prepare new content holder for new invert + rTargetHolders.Push(); + } + } + + /** helper to create needed data to emulate the VCL Wallpaper Metafile action. + It is a quite mighty action. This helper is for simple color filled background. + */ + static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateColorWallpaper( + const basegfx::B2DRange& rRange, + const basegfx::BColor& rColor, + PropertyHolder const & rPropertyHolder) + { + basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rRange)); + aOutline.transform(rPropertyHolder.getTransformation()); + + return new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aOutline), + rColor); + } + + /** helper to create needed data to emulate the VCL Wallpaper Metafile action. + It is a quite mighty action. This helper is for gradient filled background. + */ + static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateGradientWallpaper( + const basegfx::B2DRange& rRange, + const Gradient& rGradient, + PropertyHolder const & rPropertyHolder) + { + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient. Create filled rectangle + return CreateColorWallpaper(rRange, aAttribute.getStartColor(), rPropertyHolder); + } + else + { + // really a gradient + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pRetval( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + rRange, + aAttribute)); + + if(!rPropertyHolder.getTransformation().isIdentity()) + { + const drawinglayer::primitive2d::Primitive2DReference xPrim(pRetval); + drawinglayer::primitive2d::Primitive2DContainer xSeq { xPrim }; + + pRetval = new drawinglayer::primitive2d::TransformPrimitive2D( + rPropertyHolder.getTransformation(), + std::move(xSeq)); + } + + return pRetval; + } + } + + /** helper to create needed data to emulate the VCL Wallpaper Metafile action. + It is a quite mighty action. This helper decides if color and/or gradient + background is needed for the wanted bitmap fill and then creates the needed + WallpaperBitmapPrimitive2D. This primitive was created for this purpose and + takes over all needed logic of orientations and tiling. + */ + static void CreateAndAppendBitmapWallpaper( + basegfx::B2DRange aWallpaperRange, + const Wallpaper& rWallpaper, + TargetHolder& rTarget, + PropertyHolder const & rProperty) + { + const BitmapEx aBitmapEx(rWallpaper.GetBitmap()); + const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle()); + + // if bitmap visualisation is transparent, maybe background + // needs to be filled. Create background + if(aBitmapEx.IsAlpha() + || (WallpaperStyle::Tile != eWallpaperStyle && WallpaperStyle::Scale != eWallpaperStyle)) + { + if(rWallpaper.IsGradient()) + { + rTarget.append( + CreateGradientWallpaper( + aWallpaperRange, + rWallpaper.GetGradient(), + rProperty)); + } + else if(!rWallpaper.GetColor().IsTransparent()) + { + rTarget.append( + CreateColorWallpaper( + aWallpaperRange, + rWallpaper.GetColor().getBColor(), + rProperty)); + } + } + + // use wallpaper rect if set + if(rWallpaper.IsRect() && !rWallpaper.GetRect().IsEmpty()) + { + aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(rWallpaper.GetRect()); + } + + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBitmapWallpaperFill = + new drawinglayer::primitive2d::WallpaperBitmapPrimitive2D( + aWallpaperRange, + aBitmapEx, + eWallpaperStyle); + + if(rProperty.getTransformation().isIdentity()) + { + // add directly + rTarget.append(pBitmapWallpaperFill); + } + else + { + // when a transformation is set, embed to it + const drawinglayer::primitive2d::Primitive2DReference xPrim(pBitmapWallpaperFill); + + rTarget.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rProperty.getTransformation(), + drawinglayer::primitive2d::Primitive2DContainer { xPrim })); + } + } + + /** helper to decide UnderlineAbove for text primitives */ + static bool isUnderlineAbove(const vcl::Font& rFont) + { + if(!rFont.IsVertical()) + { + return false; + } + + // the underline is right for Japanese only + return (LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()); + } + + static void createFontAttributeTransformAndAlignment( + drawinglayer::attribute::FontAttribute& rFontAttribute, + basegfx::B2DHomMatrix& rTextTransform, + basegfx::B2DVector& rAlignmentOffset, + PropertyHolder const & rProperty) + { + const vcl::Font& rFont = rProperty.getFont(); + basegfx::B2DVector aFontScaling; + + rFontAttribute = drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontScaling, + rFont, + bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiRtl), + bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong)); + + // add FontScaling + rTextTransform.scale(aFontScaling.getX(), aFontScaling.getY()); + + // take text align into account + if(ALIGN_BASELINE != rFont.GetAlignment()) + { + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rFont); + + if(ALIGN_TOP == rFont.GetAlignment()) + { + rAlignmentOffset.setY(aTextLayouterDevice.getFontAscent()); + } + else // ALIGN_BOTTOM + { + rAlignmentOffset.setY(-aTextLayouterDevice.getFontDescent()); + } + + rTextTransform.translate(rAlignmentOffset.getX(), rAlignmentOffset.getY()); + } + + // add FontRotation (if used) + if(rFont.GetOrientation()) + { + rTextTransform.rotate(-toRadians(rFont.GetOrientation())); + } + } + + /** helper which takes complete care for creating the needed text primitives. It + takes care of decorated stuff and all the geometry adaptations needed + */ + static void processMetaTextAction( + const Point& rTextStartPosition, + const OUString& rText, + sal_uInt16 nTextStart, + sal_uInt16 nTextLength, + std::vector< double >&& rDXArray, + TargetHolder& rTarget, + PropertyHolder const & rProperty) + { + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pResult; + const vcl::Font& rFont = rProperty.getFont(); + basegfx::B2DVector aAlignmentOffset(0.0, 0.0); + + if(nTextLength) + { + drawinglayer::attribute::FontAttribute aFontAttribute; + basegfx::B2DHomMatrix aTextTransform; + + // fill parameters derived from current font + createFontAttributeTransformAndAlignment( + aFontAttribute, + aTextTransform, + aAlignmentOffset, + rProperty); + + // add TextStartPosition + aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y()); + + // prepare FontColor and Locale + const basegfx::BColor aFontColor(rProperty.getTextColor()); + const Color aFillColor(rFont.GetFillColor()); + const css::lang::Locale aLocale(LanguageTag(rProperty.getLanguageType()).getLocale()); + const bool bWordLineMode(rFont.IsWordLineMode()); + + const bool bDecoratedIsNeeded( + LINESTYLE_NONE != rFont.GetOverline() + || LINESTYLE_NONE != rFont.GetUnderline() + || STRIKEOUT_NONE != rFont.GetStrikeout() + || FontEmphasisMark::NONE != (rFont.GetEmphasisMark() & FontEmphasisMark::Style) + || FontRelief::NONE != rFont.GetRelief() + || rFont.IsShadow() + || bWordLineMode); + + if(bDecoratedIsNeeded) + { + // prepare overline, underline and strikeout data + const drawinglayer::primitive2d::TextLine eFontOverline(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetOverline())); + const drawinglayer::primitive2d::TextLine eFontLineStyle(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetUnderline())); + const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rFont.GetStrikeout())); + + // check UndelineAbove + const bool bUnderlineAbove(drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && isUnderlineAbove(rFont)); + + // prepare emphasis mark data + drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE); + + switch(rFont.GetEmphasisMark() & FontEmphasisMark::Style) + { + case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break; + case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break; + case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break; + case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break; + default: break; + } + + const bool bEmphasisMarkAbove(rFont.GetEmphasisMark() & FontEmphasisMark::PosAbove); + const bool bEmphasisMarkBelow(rFont.GetEmphasisMark() & FontEmphasisMark::PosBelow); + + // prepare font relief data + drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE); + + switch(rFont.GetRelief()) + { + case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break; + case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break; + default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE + } + + // prepare shadow/outline data + const bool bShadow(rFont.IsShadow()); + + // TextDecoratedPortionPrimitive2D is needed, create one + pResult = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( + + // attributes for TextSimplePortionPrimitive2D + aTextTransform, + rText, + nTextStart, + nTextLength, + std::move(rDXArray), + aFontAttribute, + aLocale, + aFontColor, + aFillColor, + + // attributes for TextDecoratedPortionPrimitive2D + rProperty.getOverlineColorActive() ? rProperty.getOverlineColor() : aFontColor, + rProperty.getTextLineColorActive() ? rProperty.getTextLineColor() : aFontColor, + eFontOverline, + eFontLineStyle, + bUnderlineAbove, + eTextStrikeout, + bWordLineMode, + eTextEmphasisMark, + bEmphasisMarkAbove, + bEmphasisMarkBelow, + eTextRelief, + bShadow); + } + else + { + // TextSimplePortionPrimitive2D is enough + pResult = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextTransform, + rText, + nTextStart, + nTextLength, + std::vector(rDXArray), + aFontAttribute, + aLocale, + aFontColor); + } + } + + if(pResult && rProperty.getTextFillColorActive()) + { + // text background is requested, add and encapsulate both to new primitive + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rFont); + + // get text width + double fTextWidth(0.0); + + if(rDXArray.empty()) + { + fTextWidth = aTextLayouterDevice.getTextWidth(rText, nTextStart, nTextLength); + } + else + { + fTextWidth = rDXArray.back(); + } + + if(basegfx::fTools::more(fTextWidth, 0.0)) + { + // build text range + const basegfx::B2DRange aTextRange( + 0.0, -aTextLayouterDevice.getFontAscent(), + fTextWidth, aTextLayouterDevice.getFontDescent()); + + // create Transform + basegfx::B2DHomMatrix aTextTransform; + + aTextTransform.translate(aAlignmentOffset.getX(), aAlignmentOffset.getY()); + + if(rFont.GetOrientation()) + { + aTextTransform.rotate(-toRadians(rFont.GetOrientation())); + } + + aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y()); + + // prepare Primitive2DSequence, put text in foreground + drawinglayer::primitive2d::Primitive2DContainer aSequence(2); + aSequence[1] = pResult; + + // prepare filled polygon + basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aTextRange)); + aOutline.transform(aTextTransform); + + aSequence[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aOutline), + rProperty.getTextFillColor())); + + // set as group at pResult + pResult = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aSequence)); + } + } + + if(!pResult) + return; + + // add created text primitive to target + if(rProperty.getTransformation().isIdentity()) + { + rTarget.append(pResult); + } + else + { + // when a transformation is set, embed to it + const drawinglayer::primitive2d::Primitive2DReference aReference(pResult); + + rTarget.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rProperty.getTransformation(), + drawinglayer::primitive2d::Primitive2DContainer { aReference })); + } + } + + /** helper which takes complete care for creating the needed textLine primitives */ + static void processMetaTextLineAction( + const MetaTextLineAction& rAction, + TargetHolder& rTarget, + PropertyHolder const & rProperty) + { + const double fLineWidth(fabs(static_cast<double>(rAction.GetWidth()))); + + if(fLineWidth <= 0.0) + return; + + const drawinglayer::primitive2d::TextLine aOverlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetOverline())); + const drawinglayer::primitive2d::TextLine aUnderlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetUnderline())); + const drawinglayer::primitive2d::TextStrikeout aTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rAction.GetStrikeout())); + + const bool bOverlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aOverlineMode); + const bool bUnderlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aUnderlineMode); + const bool bStrikeoutUsed(drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE != aTextStrikeout); + + if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)) + return; + + drawinglayer::primitive2d::Primitive2DContainer aTargets; + basegfx::B2DVector aAlignmentOffset(0.0, 0.0); + drawinglayer::attribute::FontAttribute aFontAttribute; + basegfx::B2DHomMatrix aTextTransform; + + // fill parameters derived from current font + createFontAttributeTransformAndAlignment( + aFontAttribute, + aTextTransform, + aAlignmentOffset, + rProperty); + + // add TextStartPosition + aTextTransform.translate(rAction.GetStartPoint().X(), rAction.GetStartPoint().Y()); + + // prepare TextLayouter (used in most cases) + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + aTextLayouter.setFont(rProperty.getFont()); + + if(bOverlineUsed) + { + // create primitive geometry for overline + aTargets.push_back( + new drawinglayer::primitive2d::TextLinePrimitive2D( + aTextTransform, + fLineWidth, + aTextLayouter.getOverlineOffset(), + aTextLayouter.getOverlineHeight(), + aOverlineMode, + rProperty.getOverlineColor())); + } + + if(bUnderlineUsed) + { + // create primitive geometry for underline + aTargets.push_back( + new drawinglayer::primitive2d::TextLinePrimitive2D( + aTextTransform, + fLineWidth, + aTextLayouter.getUnderlineOffset(), + aTextLayouter.getUnderlineHeight(), + aUnderlineMode, + rProperty.getTextLineColor())); + } + + if(bStrikeoutUsed) + { + // create primitive geometry for strikeout + if(drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout + || drawinglayer::primitive2d::TEXT_STRIKEOUT_X == aTextStrikeout) + { + // strikeout with character + const sal_Unicode aStrikeoutChar( + drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout ? '/' : 'X'); + const css::lang::Locale aLocale(LanguageTag( + rProperty.getLanguageType()).getLocale()); + + aTargets.push_back( + new drawinglayer::primitive2d::TextCharacterStrikeoutPrimitive2D( + aTextTransform, + fLineWidth, + rProperty.getTextColor(), + aStrikeoutChar, + aFontAttribute, + aLocale)); + } + else + { + // strikeout with geometry + aTargets.push_back( + new drawinglayer::primitive2d::TextGeometryStrikeoutPrimitive2D( + aTextTransform, + fLineWidth, + rProperty.getTextColor(), + aTextLayouter.getUnderlineHeight(), + aTextLayouter.getStrikeoutOffset(), + aTextStrikeout)); + } + } + + if(aTargets.empty()) + return; + + // add created text primitive to target + if(rProperty.getTransformation().isIdentity()) + { + rTarget.append(std::move(aTargets)); + } + else + { + // when a transformation is set, embed to it + rTarget.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rProperty.getTransformation(), + std::move(aTargets))); + } + } + + /** This is the main interpreter method. It is designed to handle the given Metafile + completely inside the given context and target. It may use and modify the context and + target. This design allows to call itself recursively which adapted contexts and + targets as e.g. needed for the MetaActionType::FLOATTRANSPARENT where the content is expressed + as a metafile as sub-content. + + This interpreter is as free of VCL functionality as possible. It uses VCL data classes + (else reading the data would not be possible), but e.g. does NOT use a local OutputDevice + as most other MetaFile interpreters/exporters do to hold and work with the current context. + This is necessary to be able to get away from the strong internal VCL-binding. + + It tries to combine e.g. pixel and/or point actions and to stitch together single line primitives + where possible (which is not trivial with the possible line geometry definitions). + + It tries to handle clipping no longer as Regions and spans of Rectangles, but as PolyPolygon + ClipRegions with (where possible) high precision by using the best possible data quality + from the Region. The vcl::Region is unavoidable as data container, but nowadays allows the transport + of Polygon-based clip regions. Where this is not used, a Polygon is constructed from the + vcl::Region ranges. All primitive clipping uses the MaskPrimitive2D with Polygon-based clipping. + + I have marked the single MetaActions with: + + SIMPLE, DONE: + Simple, e.g nothing to do or value setting in the context + + CHECKED, WORKS WELL: + Thoroughly tested with extra written test code which created a replacement + Metafile just to test this action in various combinations + + NEEDS IMPLEMENTATION: + Not implemented and asserted, but also no usage found, neither in own Metafile + creations, nor in EMF/WMF imports (checked with a whole bunch of critical EMF/WMF + bugdocs) + + For more comments, see the single action implementations. + */ + static void implInterpretMetafile( + const GDIMetaFile& rMetaFile, + TargetHolders& rTargetHolders, + PropertyHolders& rPropertyHolders, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) + { + const size_t nCount(rMetaFile.GetActionSize()); + std::unique_ptr<emfplushelper::EmfPlusHelper> aEMFPlus; + + for(size_t nAction(0); nAction < nCount; nAction++) + { + MetaAction* pAction = rMetaFile.GetAction(nAction); + + switch(pAction->GetType()) + { + case MetaActionType::NONE : + { + /** SIMPLE, DONE */ + break; + } + case MetaActionType::PIXEL : + { + /** CHECKED, WORKS WELL */ + std::vector< basegfx::B2DPoint > aPositions; + Color aLastColor(COL_BLACK); + + while(MetaActionType::PIXEL == pAction->GetType() && nAction < nCount) + { + const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction); + + if(pA->GetColor() != aLastColor) + { + if(!aPositions.empty()) + { + createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor()); + aPositions.clear(); + } + + aLastColor = pA->GetColor(); + } + + const Point& rPoint = pA->GetPoint(); + aPositions.emplace_back(rPoint.X(), rPoint.Y()); + nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction); + } + + nAction--; + + if(!aPositions.empty()) + { + createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor()); + } + + break; + } + case MetaActionType::POINT : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + std::vector< basegfx::B2DPoint > aPositions; + + while(MetaActionType::POINT == pAction->GetType() && nAction < nCount) + { + const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction); + const Point& rPoint = pA->GetPoint(); + aPositions.emplace_back(rPoint.X(), rPoint.Y()); + nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction); + } + + nAction--; + + if(!aPositions.empty()) + { + createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), rPropertyHolders.Current().getLineColor()); + } + } + + break; + } + case MetaActionType::LINE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + basegfx::B2DPolygon aLinePolygon; + LineInfo aLineInfo; + + while(MetaActionType::LINE == pAction->GetType() && nAction < nCount) + { + const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction); + const Point& rStartPoint = pA->GetStartPoint(); + const Point& rEndPoint = pA->GetEndPoint(); + const basegfx::B2DPoint aStart(rStartPoint.X(), rStartPoint.Y()); + const basegfx::B2DPoint aEnd(rEndPoint.X(), rEndPoint.Y()); + + if(aLinePolygon.count()) + { + if(pA->GetLineInfo() == aLineInfo + && aStart == aLinePolygon.getB2DPoint(aLinePolygon.count() - 1)) + { + aLinePolygon.append(aEnd); + } + else + { + createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current()); + aLinePolygon.clear(); + aLineInfo = pA->GetLineInfo(); + aLinePolygon.append(aStart); + aLinePolygon.append(aEnd); + } + } + else + { + aLineInfo = pA->GetLineInfo(); + aLinePolygon.append(aStart); + aLinePolygon.append(aEnd); + } + + nAction++; + if (nAction < nCount) + pAction = rMetaFile.GetAction(nAction); + } + + nAction--; + if (aLinePolygon.count()) + createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::RECT : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + } + } + + break; + } + case MetaActionType::ROUNDRECT : + { + /** CHECKED, WORKS WELL */ + /** The original OutputDevice::DrawRect paints nothing when nHor or nVer is zero; but just + because the tools::Polygon operator creating the rounding does produce nonsense. I assume + this an error and create an unrounded rectangle in that case (implicit in + createPolygonFromRect) + */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const sal_uInt32 nHor(pA->GetHorzRound()); + const sal_uInt32 nVer(pA->GetVertRound()); + basegfx::B2DPolygon aOutline; + + if(nHor || nVer) + { + double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0)); + double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0)); + fRadiusX = std::clamp(fRadiusX, 0.0, 1.0); + fRadiusY = std::clamp(fRadiusY, 0.0, 1.0); + + aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY); + } + else + { + aOutline = basegfx::utils::createPolygonFromRect(aRange); + } + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + } + } + + break; + } + case MetaActionType::ELLIPSE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromEllipse( + aRange.getCenter(), aRange.getWidth() * 0.5, aRange.getHeight() * 0.5)); + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + } + } + + break; + } + case MetaActionType::ARC : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction); + const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc); + const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon()); + + createHairlinePrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::PIE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction); + const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie); + const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon()); + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::CHORD : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction); + const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord); + const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon()); + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::POLYLINE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction); + createLinePrimitive(pA->GetPolygon().getB2DPolygon(), pA->GetLineInfo(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::POLYGON : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction); + basegfx::B2DPolygon aOutline(pA->GetPolygon().getB2DPolygon()); + + // the metafile play interprets the polygons from MetaPolygonAction + // always as closed and always paints an edge from last to first point, + // so force to closed here to emulate that + if(aOutline.count() > 1 && !aOutline.isClosed()) + { + aOutline.setClosed(true); + } + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::POLYPOLYGON : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction); + basegfx::B2DPolyPolygon aPolyPolygonOutline(pA->GetPolyPolygon().getB2DPolyPolygon()); + + // the metafile play interprets the single polygons from MetaPolyPolygonAction + // always as closed and always paints an edge from last to first point, + // so force to closed here to emulate that + for(sal_uInt32 b(0); b < aPolyPolygonOutline.count(); b++) + { + basegfx::B2DPolygon aPolygonOutline(aPolyPolygonOutline.getB2DPolygon(b)); + + if(aPolygonOutline.count() > 1 && !aPolygonOutline.isClosed()) + { + aPolygonOutline.setClosed(true); + aPolyPolygonOutline.setB2DPolygon(b, aPolygonOutline); + } + } + + createHairlineAndFillPrimitive(aPolyPolygonOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::TEXT : + { + /** CHECKED, WORKS WELL */ + const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction); + sal_uInt32 nTextLength(pA->GetLen()); + const sal_uInt32 nTextIndex(pA->GetIndex()); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(nTextLength + nTextIndex > nStringLength) + { + nTextLength = nStringLength - nTextIndex; + } + + if(nTextLength && rPropertyHolders.Current().getTextColorActive()) + { + std::vector< double > aDXArray{}; + processMetaTextAction( + pA->GetPoint(), + pA->GetText(), + nTextIndex, + nTextLength, + std::move(aDXArray), + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::TEXTARRAY : + { + /** CHECKED, WORKS WELL */ + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction); + sal_uInt32 nTextLength(pA->GetLen()); + const sal_uInt32 nTextIndex(pA->GetIndex()); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(nTextLength + nTextIndex > nStringLength) + { + nTextLength = nTextIndex > nStringLength ? 0 : nStringLength - nTextIndex; + } + + if(nTextLength && rPropertyHolders.Current().getTextColorActive()) + { + // prepare DXArray (if used) + std::vector< double > aDXArray; + const std::vector<sal_Int32> & rDXArray = pA->GetDXArray(); + + if(!rDXArray.empty()) + { + aDXArray.reserve(nTextLength); + + for(sal_uInt32 a(0); a < nTextLength; a++) + { + aDXArray.push_back(static_cast<double>(rDXArray[a])); + } + } + + processMetaTextAction( + pA->GetPoint(), + pA->GetText(), + nTextIndex, + nTextLength, + std::move(aDXArray), + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::STRETCHTEXT : + { + // #i108440# StarMath uses MetaStretchTextAction, thus support is needed. + // It looks as if it pretty never really uses a width different from + // the default text-layout width, but it's not possible to be sure. + // Implemented getting the DXArray and checking for scale at all. If + // scale is more than 3.5% different, scale the DXArray before usage. + // New status: + + /** CHECKED, WORKS WELL */ + const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction); + sal_uInt32 nTextLength(pA->GetLen()); + const sal_uInt32 nTextIndex(pA->GetIndex()); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(nTextLength + nTextIndex > nStringLength) + { + nTextLength = nStringLength - nTextIndex; + } + + if(nTextLength && rPropertyHolders.Current().getTextColorActive()) + { + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont()); + + std::vector< double > aTextArray( + aTextLayouterDevice.getTextArray( + pA->GetText(), + nTextIndex, + nTextLength)); + + if(!aTextArray.empty()) + { + const double fTextLength(aTextArray.back()); + + if(0.0 != fTextLength && pA->GetWidth()) + { + const double fRelative(pA->GetWidth() / fTextLength); + + if(fabs(fRelative - 1.0) >= 0.035) + { + // when derivation is more than 3,5% from default text size, + // scale the DXArray + for(double & a : aTextArray) + { + a *= fRelative; + } + } + } + } + + processMetaTextAction( + pA->GetPoint(), + pA->GetText(), + nTextIndex, + nTextLength, + std::move(aTextArray), + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::TEXTRECT : + { + /** CHECKED, WORKS WELL */ + // OSL_FAIL("MetaActionType::TEXTRECT requested (!)"); + const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(!rRectangle.IsEmpty() && 0 != nStringLength) + { + // The problem with this action is that it describes unlayouted text + // and the layout capabilities are in EditEngine/Outliner in SVX. The + // same problem is true for VCL which internally has implementations + // to layout text in this case. There exists even a call + // OutputDevice::AddTextRectActions(...) to create the needed actions + // as 'sub-content' of a Metafile. Unfortunately i do not have an + // OutputDevice here since this interpreter tries to work without + // VCL AFAP. + // Since AddTextRectActions is the only way as long as we do not have + // a simple text layouter available, i will try to add it to the + // TextLayouterDevice isolation. + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont()); + GDIMetaFile aGDIMetaFile; + + aTextLayouterDevice.addTextRectActions( + rRectangle, pA->GetText(), pA->GetStyle(), aGDIMetaFile); + + if(aGDIMetaFile.GetActionSize()) + { + // create sub-content + drawinglayer::primitive2d::Primitive2DContainer xSubContent; + { + rTargetHolders.Push(); + + // for sub-Mteafile contents, do start with new, default render state + // #i124686# ...but copy font, this is already set accordingly + vcl::Font aTargetFont = rPropertyHolders.Current().getFont(); + rPropertyHolders.PushDefault(); + rPropertyHolders.Current().setFont(aTargetFont); + + implInterpretMetafile(aGDIMetaFile, rTargetHolders, rPropertyHolders, rViewInformation); + xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()); + rPropertyHolders.Pop(); + rTargetHolders.Pop(); + } + + if(!xSubContent.empty()) + { + // add with transformation + rTargetHolders.Current().append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rPropertyHolders.Current().getTransformation(), + std::move(xSubContent))); + } + } + } + + break; + } + case MetaActionType::BMP : + { + /** CHECKED, WORKS WELL */ + const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction); + const BitmapEx aBitmapEx(pA->GetBitmap()); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPSCALE : + { + /** CHECKED, WORKS WELL */ + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + const BitmapEx aBitmapEx(pA->GetBitmap()); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPSCALEPART : + { + /** CHECKED, WORKS WELL */ + const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + if(!rBitmap.IsEmpty()) + { + Bitmap aCroppedBitmap(rBitmap); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(!aCropRectangle.IsEmpty()) + { + aCroppedBitmap.Crop(aCropRectangle); + } + + const BitmapEx aCroppedBitmapEx(aCroppedBitmap); + createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::BMPEX : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */ + const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPEXSCALE : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */ + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPEXSCALEPART : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */ + const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + if(!rBitmapEx.IsEmpty()) + { + BitmapEx aCroppedBitmapEx(rBitmapEx); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(!aCropRectangle.IsEmpty()) + { + aCroppedBitmapEx.Crop(aCropRectangle); + } + + createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::MASK : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */ + /** Huh, no it isn't!? */ + const MetaMaskAction* pA = static_cast<const MetaMaskAction*>(pAction); + const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor())); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::MASKSCALE : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */ + const MetaMaskScaleAction* pA = static_cast<const MetaMaskScaleAction*>(pAction); + const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor())); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::MASKSCALEPART : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */ + const MetaMaskScalePartAction* pA = static_cast<const MetaMaskScalePartAction*>(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + if(!rBitmap.IsEmpty()) + { + Bitmap aCroppedBitmap(rBitmap); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(!aCropRectangle.IsEmpty()) + { + aCroppedBitmap.Crop(aCropRectangle); + } + + const BitmapEx aCroppedBitmapEx(createMaskBmpEx(aCroppedBitmap, pA->GetColor())); + createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::GRADIENT : + { + /** CHECKED, WORKS WELL */ + const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const Gradient& rGradient = pA->GetGradient(); + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient. Create filled rectangle + createFillPrimitive( + aOutline, + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + else + { + // really a gradient + aRange.transform(rPropertyHolders.Current().getTransformation()); + drawinglayer::primitive2d::Primitive2DContainer xGradient(1); + + if(rPropertyHolders.Current().isRasterOpInvert()) + { + // use a special version of FillGradientPrimitive2D which creates + // non-overlapping geometry on decomposition to make the old XOR + // paint 'trick' work. + xGradient[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::NonOverlappingFillGradientPrimitive2D( + aRange, + aAttribute)); + } + else + { + xGradient[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aAttribute)); + } + + // #i112300# clip against polygon representing the rectangle from + // the action. This is implicitly done using a temp Clipping in VCL + // when a MetaGradientAction is executed + aOutline.transform(rPropertyHolders.Current().getTransformation()); + rTargetHolders.Current().append( + new drawinglayer::primitive2d::MaskPrimitive2D( + aOutline, + std::move(xGradient))); + } + } + } + + break; + } + case MetaActionType::HATCH : + { + /** CHECKED, WORKS WELL */ + const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction); + basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon()); + + if(aOutline.count()) + { + const Hatch& rHatch = pA->GetHatch(); + const drawinglayer::attribute::FillHatchAttribute aAttribute(createFillHatchAttribute(rHatch)); + + aOutline.transform(rPropertyHolders.Current().getTransformation()); + + const basegfx::B2DRange aObjectRange(aOutline.getB2DRange()); + const drawinglayer::primitive2d::Primitive2DReference aFillHatch( + new drawinglayer::primitive2d::FillHatchPrimitive2D( + aObjectRange, + basegfx::BColor(), + aAttribute)); + + rTargetHolders.Current().append( + new drawinglayer::primitive2d::MaskPrimitive2D( + aOutline, + drawinglayer::primitive2d::Primitive2DContainer { aFillHatch })); + } + + break; + } + case MetaActionType::WALLPAPER : + { + /** CHECKED, WORKS WELL */ + const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction); + tools::Rectangle aWallpaperRectangle(pA->GetRect()); + + if(!aWallpaperRectangle.IsEmpty()) + { + const Wallpaper& rWallpaper = pA->GetWallpaper(); + const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle()); + basegfx::B2DRange aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(aWallpaperRectangle); + + if(WallpaperStyle::NONE != eWallpaperStyle) + { + if(rWallpaper.IsBitmap()) + { + // create bitmap background. Caution: This + // also will create gradient/color background(s) + // when the bitmap is transparent or not tiled + CreateAndAppendBitmapWallpaper( + aWallpaperRange, + rWallpaper, + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + else if(rWallpaper.IsGradient()) + { + // create gradient background + rTargetHolders.Current().append( + CreateGradientWallpaper( + aWallpaperRange, + rWallpaper.GetGradient(), + rPropertyHolders.Current())); + } + else if(!rWallpaper.GetColor().IsTransparent()) + { + // create color background + rTargetHolders.Current().append( + CreateColorWallpaper( + aWallpaperRange, + rWallpaper.GetColor().getBColor(), + rPropertyHolders.Current())); + } + } + } + + break; + } + case MetaActionType::CLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction); + + if(pA->IsClipping()) + { + // new clipping. Get tools::PolyPolygon and transform with current transformation + basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(pA->GetRegion())); + + aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation()); + HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + else + { + // end clipping + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + + break; + } + case MetaActionType::ISECTRECTCLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(rRectangle.IsEmpty()) + { + // intersect with empty rectangle will always give empty + // ClipPolyPolygon; start new clipping with empty PolyPolygon + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + else + { + // create transformed ClipRange + basegfx::B2DRange aClipRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + aClipRange.transform(rPropertyHolders.Current().getTransformation()); + + if(rPropertyHolders.Current().getClipPolyPolygonActive()) + { + if(0 == rPropertyHolders.Current().getClipPolyPolygon().count()) + { + // nothing to do, empty active clipPolyPolygon will stay + // empty when intersecting + } + else + { + // AND existing region and new ClipRange + const basegfx::B2DPolyPolygon aOriginalPolyPolygon( + rPropertyHolders.Current().getClipPolyPolygon()); + basegfx::B2DPolyPolygon aClippedPolyPolygon; + + if(aOriginalPolyPolygon.count()) + { + aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnRange( + aOriginalPolyPolygon, + aClipRange, + true, + false); + } + + if(aClippedPolyPolygon != aOriginalPolyPolygon) + { + // start new clipping with intersected region + HandleNewClipRegion( + aClippedPolyPolygon, + rTargetHolders, + rPropertyHolders); + } + } + } + else + { + // start new clipping with ClipRange + const basegfx::B2DPolyPolygon aNewClipPolyPolygon( + basegfx::utils::createPolygonFromRect(aClipRange)); + + HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + + break; + } + case MetaActionType::ISECTREGIONCLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction); + const vcl::Region& rNewRegion = pA->GetRegion(); + + if(rNewRegion.IsEmpty()) + { + // intersect with empty region will always give empty + // region; start new clipping with empty PolyPolygon + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + else + { + // get new ClipPolyPolygon, transform it with current transformation + basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(rNewRegion)); + aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation()); + + if(rPropertyHolders.Current().getClipPolyPolygonActive()) + { + if(0 == rPropertyHolders.Current().getClipPolyPolygon().count()) + { + // nothing to do, empty active clipPolyPolygon will stay empty + // when intersecting with any region + } + else + { + // AND existing and new region + const basegfx::B2DPolyPolygon aOriginalPolyPolygon( + rPropertyHolders.Current().getClipPolyPolygon()); + basegfx::B2DPolyPolygon aClippedPolyPolygon; + + if(aOriginalPolyPolygon.count()) + { + aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aOriginalPolyPolygon, aNewClipPolyPolygon, true, false); + } + + if(aClippedPolyPolygon != aOriginalPolyPolygon) + { + // start new clipping with intersected ClipPolyPolygon + HandleNewClipRegion(aClippedPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + } + else + { + // start new clipping with new ClipPolyPolygon + HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + + break; + } + case MetaActionType::MOVECLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction); + + if(rPropertyHolders.Current().getClipPolyPolygonActive()) + { + if(0 == rPropertyHolders.Current().getClipPolyPolygon().count()) + { + // nothing to do + } + else + { + const sal_Int32 nHor(pA->GetHorzMove()); + const sal_Int32 nVer(pA->GetVertMove()); + + if(0 != nHor || 0 != nVer) + { + // prepare translation, add current transformation + basegfx::B2DVector aVector(pA->GetHorzMove(), pA->GetVertMove()); + aVector *= rPropertyHolders.Current().getTransformation(); + basegfx::B2DHomMatrix aTransform( + basegfx::utils::createTranslateB2DHomMatrix(aVector)); + + // transform existing region + basegfx::B2DPolyPolygon aClipPolyPolygon( + rPropertyHolders.Current().getClipPolyPolygon()); + + aClipPolyPolygon.transform(aTransform); + HandleNewClipRegion(aClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + } + + break; + } + case MetaActionType::LINECOLOR : + { + /** CHECKED, WORKS WELL */ + const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction); + // tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates line draw + const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor()); + + rPropertyHolders.Current().setLineColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setLineColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::FILLCOLOR : + { + /** CHECKED, WORKS WELL */ + const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction); + // tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates polygon fill + const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor()); + + rPropertyHolders.Current().setFillColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setFillColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::TEXTCOLOR : + { + /** SIMPLE, DONE */ + const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction); + const bool bActivate(COL_TRANSPARENT != pA->GetColor()); + + rPropertyHolders.Current().setTextColorActive(bActivate); + rPropertyHolders.Current().setTextColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::TEXTFILLCOLOR : + { + /** SIMPLE, DONE */ + const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction); + const bool bWithColorArgument(pA->IsSetting()); + + if(bWithColorArgument) + { + // emulate OutputDevice::SetTextFillColor(...) WITH argument + const Color& rFontFillColor = pA->GetColor(); + rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor()); + rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor); + } + else + { + // emulate SetFillColor() <- NO argument (!) + rPropertyHolders.Current().setTextFillColorActive(false); + } + + break; + } + case MetaActionType::TEXTALIGN : + { + /** SIMPLE, DONE */ + const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction); + const TextAlign aNewTextAlign = pA->GetTextAlign(); + + // TextAlign is applied to the current font (as in + // OutputDevice::SetTextAlign which would be used when + // playing the Metafile) + if(rPropertyHolders.Current().getFont().GetAlignment() != aNewTextAlign) + { + vcl::Font aNewFont(rPropertyHolders.Current().getFont()); + aNewFont.SetAlignment(aNewTextAlign); + rPropertyHolders.Current().setFont(aNewFont); + } + + break; + } + case MetaActionType::MAPMODE : + { + /** CHECKED, WORKS WELL */ + // the most necessary MapMode to be interpreted is MapUnit::MapRelative, + // but also the others may occur. Even not yet supported ones + // may need to be added here later + const MetaMapModeAction* pA = static_cast<const MetaMapModeAction*>(pAction); + const MapMode& rMapMode = pA->GetMapMode(); + basegfx::B2DHomMatrix aMapping; + + if(MapUnit::MapRelative == rMapMode.GetMapUnit()) + { + aMapping = getTransformFromMapMode(rMapMode); + } + else + { + const auto eFrom = MapToO3tlLength(rPropertyHolders.Current().getMapUnit()), + eTo = MapToO3tlLength(rMapMode.GetMapUnit()); + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + { + const double fConvert(o3tl::convert(1.0, eFrom, eTo)); + aMapping.scale(fConvert, fConvert); + } + else + OSL_FAIL("implInterpretMetafile: MetaActionType::MAPMODE with unsupported MapUnit (!)"); + + aMapping = getTransformFromMapMode(rMapMode) * aMapping; + rPropertyHolders.Current().setMapUnit(rMapMode.GetMapUnit()); + } + + if(!aMapping.isIdentity()) + { + aMapping = aMapping * rPropertyHolders.Current().getTransformation(); + rPropertyHolders.Current().setTransformation(aMapping); + } + + break; + } + case MetaActionType::FONT : + { + /** SIMPLE, DONE */ + const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction); + rPropertyHolders.Current().setFont(pA->GetFont()); + Size aFontSize(pA->GetFont().GetFontSize()); + + if(0 == aFontSize.Height()) + { + // this should not happen but i got Metafiles where this was the + // case. A height needs to be guessed (similar to OutputDevice::ImplNewFont()) + vcl::Font aCorrectedFont(pA->GetFont()); + + // guess 16 pixel (as in VCL) + aFontSize = Size(0, 16); + + // convert to target MapUnit if not pixels + aFontSize = OutputDevice::LogicToLogic( + aFontSize, MapMode(MapUnit::MapPixel), MapMode(rPropertyHolders.Current().getMapUnit())); + + aCorrectedFont.SetFontSize(aFontSize); + rPropertyHolders.Current().setFont(aCorrectedFont); + } + + // older Metafiles have no MetaActionType::TEXTCOLOR which defines + // the FontColor now, so use the Font's color when not transparent + const Color& rFontColor = pA->GetFont().GetColor(); + const bool bActivate(COL_TRANSPARENT != rFontColor); + + if(bActivate) + { + rPropertyHolders.Current().setTextColor(rFontColor.getBColor()); + } + + // caution: do NOT deactivate here on transparent, see + // OutputDevice::SetFont(..) for more info + // rPropertyHolders.Current().setTextColorActive(bActivate); + + // for fill color emulate a MetaTextFillColorAction with !transparent as bool, + // see OutputDevice::SetFont(..) the if(mpMetaFile) case + if(bActivate) + { + const Color& rFontFillColor = pA->GetFont().GetFillColor(); + rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor()); + rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor); + } + else + { + rPropertyHolders.Current().setTextFillColorActive(false); + } + + break; + } + case MetaActionType::PUSH : + { + /** CHECKED, WORKS WELL */ + const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction); + rPropertyHolders.Push(pA->GetFlags()); + + break; + } + case MetaActionType::POP : + { + /** CHECKED, WORKS WELL */ + const bool bRegionMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::CLIPREGION); + const bool bRasterOpMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::RASTEROP); + + if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive()) + { + // end evtl. clipping + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + + if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive()) + { + // end evtl. RasterOp + HandleNewRasterOp(RasterOp::OverPaint, rTargetHolders, rPropertyHolders); + } + + rPropertyHolders.Pop(); + + if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive()) + { + // start evtl. RasterOp + HandleNewRasterOp(rPropertyHolders.Current().getRasterOp(), rTargetHolders, rPropertyHolders); + } + + if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive()) + { + // start evtl. clipping + HandleNewClipRegion( + rPropertyHolders.Current().getClipPolyPolygon(), rTargetHolders, rPropertyHolders); + } + + break; + } + case MetaActionType::RASTEROP : + { + /** CHECKED, WORKS WELL */ + const MetaRasterOpAction* pA = static_cast<const MetaRasterOpAction*>(pAction); + const RasterOp aRasterOp = pA->GetRasterOp(); + + HandleNewRasterOp(aRasterOp, rTargetHolders, rPropertyHolders); + + break; + } + case MetaActionType::Transparent : + { + /** CHECKED, WORKS WELL */ + const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction); + const basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon()); + + if(aOutline.count()) + { + const sal_uInt16 nTransparence(pA->GetTransparence()); + + if(0 == nTransparence) + { + // not transparent + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + else if(nTransparence >= 100) + { + // fully or more than transparent + } + else + { + // transparent. Create new target + rTargetHolders.Push(); + + // create primitives there and get them + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + drawinglayer::primitive2d::Primitive2DContainer aSubContent( + rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current())); + + // back to old target + rTargetHolders.Pop(); + + if(!aSubContent.empty()) + { + rTargetHolders.Current().append( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(aSubContent), + nTransparence * 0.01)); + } + } + } + + break; + } + case MetaActionType::EPS : + { + /** CHECKED, WORKS WELL */ + // To support this action, I have added a EpsPrimitive2D which will + // by default decompose to the Metafile replacement data. To support + // this EPS on screen, the renderer visualizing this has to support + // that primitive and visualize the Eps file (e.g. printing) + const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction); + const tools::Rectangle aRectangle(pA->GetPoint(), pA->GetSize()); + + if(!aRectangle.IsEmpty()) + { + // create object transform + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, aRectangle.GetWidth()); + aObjectTransform.set(1, 1, aRectangle.GetHeight()); + aObjectTransform.set(0, 2, aRectangle.Left()); + aObjectTransform.set(1, 2, aRectangle.Top()); + + // add current transformation + aObjectTransform = rPropertyHolders.Current().getTransformation() * aObjectTransform; + + // embed using EpsPrimitive + rTargetHolders.Current().append( + new drawinglayer::primitive2d::EpsPrimitive2D( + aObjectTransform, + pA->GetLink(), + pA->GetSubstitute())); + } + + break; + } + case MetaActionType::REFPOINT : + { + /** SIMPLE, DONE */ + // only used for hatch and line pattern offsets, pretty much no longer + // supported today + // const MetaRefPointAction* pA = (const MetaRefPointAction*)pAction; + break; + } + case MetaActionType::TEXTLINECOLOR : + { + /** SIMPLE, DONE */ + const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction); + const bool bActive(pA->IsSetting()); + + rPropertyHolders.Current().setTextLineColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setTextLineColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::TEXTLINE : + { + /** CHECKED, WORKS WELL */ + // actually creates overline, underline and strikeouts, so + // these should be isolated from TextDecoratedPortionPrimitive2D + // to own primitives. Done, available now. + // + // This Metaaction seems not to be used (was not used in any + // checked files). It's used in combination with the current + // Font. + const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction); + + processMetaTextLineAction( + *pA, + rTargetHolders.Current(), + rPropertyHolders.Current()); + + break; + } + case MetaActionType::FLOATTRANSPARENT : + { + /** CHECKED, WORKS WELL */ + const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction); + const basegfx::B2DRange aTargetRange( + pA->GetPoint().X(), + pA->GetPoint().Y(), + pA->GetPoint().X() + pA->GetSize().Width(), + pA->GetPoint().Y() + pA->GetSize().Height()); + + if(!aTargetRange.isEmpty()) + { + const GDIMetaFile& rContent = pA->GetGDIMetaFile(); + + if(rContent.GetActionSize()) + { + // create the sub-content with no embedding specific to the + // sub-metafile, this seems not to be used. + drawinglayer::primitive2d::Primitive2DContainer xSubContent; + { + rTargetHolders.Push(); + // #i# for sub-Mteafile contents, do start with new, default render state + rPropertyHolders.PushDefault(); + implInterpretMetafile(rContent, rTargetHolders, rPropertyHolders, rViewInformation); + xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()); + rPropertyHolders.Pop(); + rTargetHolders.Pop(); + } + + if(!xSubContent.empty()) + { + // prepare sub-content transform + basegfx::B2DHomMatrix aSubTransform; + + // create SourceRange + const basegfx::B2DRange aSourceRange( + rContent.GetPrefMapMode().GetOrigin().X(), + rContent.GetPrefMapMode().GetOrigin().Y(), + rContent.GetPrefMapMode().GetOrigin().X() + rContent.GetPrefSize().Width(), + rContent.GetPrefMapMode().GetOrigin().Y() + rContent.GetPrefSize().Height()); + + // apply mapping if aTargetRange and aSourceRange are not equal + if(!aSourceRange.equal(aTargetRange)) + { + aSubTransform.translate(-aSourceRange.getMinX(), -aSourceRange.getMinY()); + aSubTransform.scale( + aTargetRange.getWidth() / (basegfx::fTools::equalZero(aSourceRange.getWidth()) ? 1.0 : aSourceRange.getWidth()), + aTargetRange.getHeight() / (basegfx::fTools::equalZero(aSourceRange.getHeight()) ? 1.0 : aSourceRange.getHeight())); + aSubTransform.translate(aTargetRange.getMinX(), aTargetRange.getMinY()); + } + + // apply general current transformation + aSubTransform = rPropertyHolders.Current().getTransformation() * aSubTransform; + + // evtl. embed sub-content to its transformation + if(!aSubTransform.isIdentity()) + { + const drawinglayer::primitive2d::Primitive2DReference aEmbeddedTransform( + new drawinglayer::primitive2d::TransformPrimitive2D( + aSubTransform, + std::move(xSubContent))); + + xSubContent = drawinglayer::primitive2d::Primitive2DContainer { aEmbeddedTransform }; + } + + // check if gradient is a real gradient + const Gradient& rGradient = pA->GetGradient(); + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient; create UnifiedTransparencePrimitive2D + rTargetHolders.Current().append( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(xSubContent), + aAttribute.getStartColor().luminance())); + } + else + { + // really a gradient. Create gradient sub-content (with correct scaling) + basegfx::B2DRange aRange(aTargetRange); + aRange.transform(rPropertyHolders.Current().getTransformation()); + + // prepare gradient for transparent content + const drawinglayer::primitive2d::Primitive2DReference xTransparence( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aAttribute)); + + // create transparence primitive + rTargetHolders.Current().append( + new drawinglayer::primitive2d::TransparencePrimitive2D( + std::move(xSubContent), + drawinglayer::primitive2d::Primitive2DContainer { xTransparence })); + } + } + } + } + + break; + } + case MetaActionType::GRADIENTEX : + { + /** SIMPLE, DONE */ + // This is only a data holder which is interpreted inside comment actions, + // see MetaActionType::COMMENT for more info + // const MetaGradientExAction* pA = (const MetaGradientExAction*)pAction; + break; + } + case MetaActionType::LAYOUTMODE : + { + /** SIMPLE, DONE */ + const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction); + rPropertyHolders.Current().setLayoutMode(pA->GetLayoutMode()); + break; + } + case MetaActionType::TEXTLANGUAGE : + { + /** SIMPLE, DONE */ + const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction); + rPropertyHolders.Current().setLanguageType(pA->GetTextLanguage()); + break; + } + case MetaActionType::OVERLINECOLOR : + { + /** SIMPLE, DONE */ + const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction); + const bool bActive(pA->IsSetting()); + + rPropertyHolders.Current().setOverlineColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setOverlineColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::COMMENT : + { + /** CHECKED, WORKS WELL */ + // I already implemented + // XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END + // XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END, + // but opted to remove these again; it works well without them + // and makes the code less dependent from those Metafile Add-Ons + const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); + + if (pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + // XGRAD_SEQ_BEGIN, XGRAD_SEQ_END should be supported since the + // pure recorded paint of the gradients uses the XOR paint functionality + // ('trick'). This is (and will be) problematic with AntiAliasing, so it's + // better to use this info + const MetaGradientExAction* pMetaGradientExAction = nullptr; + bool bDone(false); + size_t b(nAction + 1); + + for(; !bDone && b < nCount; b++) + { + pAction = rMetaFile.GetAction(b); + + if(MetaActionType::GRADIENTEX == pAction->GetType()) + { + pMetaGradientExAction = static_cast<const MetaGradientExAction*>(pAction); + } + else if(MetaActionType::COMMENT == pAction->GetType()) + { + if (static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")) + { + bDone = true; + } + } + } + + if(bDone && pMetaGradientExAction) + { + // consume actions and skip forward + nAction = b - 1; + + // get geometry data + basegfx::B2DPolyPolygon aPolyPolygon(pMetaGradientExAction->GetPolyPolygon().getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // transform geometry + aPolyPolygon.transform(rPropertyHolders.Current().getTransformation()); + + // get and check if gradient is a real gradient + const Gradient& rGradient = pMetaGradientExAction->GetGradient(); + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient + rTargetHolders.Current().append( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + aPolyPolygon, + aAttribute.getStartColor())); + } + else + { + // really a gradient + rTargetHolders.Current().append( + new drawinglayer::primitive2d::PolyPolygonGradientPrimitive2D( + aPolyPolygon, + aAttribute)); + } + } + } + } + else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS_HEADER_INFO")) + { + if (aEMFPlus) + { + // error: should not yet exist + SAL_INFO("drawinglayer.emf", "Error: multiple EMF_PLUS_HEADER_INFO"); + } + else + { + SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pA->GetDataSize()); + SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ); + + aEMFPlus.reset( + new emfplushelper::EmfPlusHelper( + aMemoryStream, + rTargetHolders, + rPropertyHolders)); + } + } + else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS")) + { + if (!aEMFPlus) + { + // error: should exist + SAL_INFO("drawinglayer.emf", "Error: EMF_PLUS before EMF_PLUS_HEADER_INFO"); + } + else + { + static int count = -1, limit = 0x7fffffff; + + if (count == -1) + { + count = 0; + + if (char *env = getenv("EMF_PLUS_LIMIT")) + { + limit = atoi(env); + SAL_INFO("drawinglayer.emf", "EMF+ records limit: " << limit); + } + } + + SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer, size: " << pA->GetDataSize()); + + if (count < limit) + { + SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ); + + aEMFPlus->processEmfPlusData( + aMemoryStream, + rViewInformation); + } + + count++; + } + } + + break; + } + default: + { + OSL_FAIL("Unknown MetaFile Action (!)"); + break; + } + } + } + } +} + +namespace wmfemfhelper +{ + drawinglayer::primitive2d::Primitive2DContainer interpretMetafile( + const GDIMetaFile& rMetaFile, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) + { + // prepare target and properties; each will have one default entry + drawinglayer::primitive2d::Primitive2DContainer xRetval; + TargetHolders aTargetHolders; + PropertyHolders aPropertyHolders; + + // set target MapUnit at Properties + aPropertyHolders.Current().setMapUnit(rMetaFile.GetPrefMapMode().GetMapUnit()); + + // interpret the Metafile + implInterpretMetafile(rMetaFile, aTargetHolders, aPropertyHolders, rViewInformation); + + // 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 + while (aTargetHolders.size() > 1) + { + xRetval.append( + aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current())); + aTargetHolders.Pop(); + } + + xRetval.append( + aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current())); + + return xRetval; + } + +} // end of namespace wmfemfhelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |