summaryrefslogtreecommitdiffstats
path: root/svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx')
-rw-r--r--svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx928
1 files changed, 928 insertions, 0 deletions
diff --git a/svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx b/svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx
new file mode 100644
index 000000000..0610f43b0
--- /dev/null
+++ b/svx/source/sdr/primitive2d/sdrframeborderprimitive2d.cxx
@@ -0,0 +1,928 @@
+/* -*- 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 <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx>
+#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <svtools/borderhelper.hxx>
+
+namespace
+{
+ double snapToDiscreteUnit(
+ double fValue,
+ double fMinimalDiscreteUnit)
+ {
+ if(0.0 != fValue)
+ {
+ fValue = std::max(fValue, fMinimalDiscreteUnit);
+ }
+
+ return fValue;
+ }
+
+ class StyleVectorCombination
+ {
+ private:
+ struct OffsetAndHalfWidthAndColor
+ {
+ double mfOffset;
+ double mfHalfWidth;
+ Color maColor;
+
+ OffsetAndHalfWidthAndColor(double offset, double halfWidth, Color color) :
+ mfOffset(offset),
+ mfHalfWidth(halfWidth),
+ maColor(color)
+ {}
+ };
+
+ double mfRefModeOffset;
+ basegfx::B2DVector maB2DVector;
+ double mfAngle;
+ std::vector< OffsetAndHalfWidthAndColor > maOffsets;
+
+ public:
+ StyleVectorCombination(
+ const svx::frame::Style& rStyle,
+ const basegfx::B2DVector& rB2DVector,
+ double fAngle,
+ bool bMirrored,
+ const Color* pForceColor,
+ double fMinimalDiscreteUnit)
+ : mfRefModeOffset(0.0),
+ maB2DVector(rB2DVector),
+ mfAngle(fAngle)
+ {
+ if (!rStyle.IsUsed())
+ return;
+
+ svx::frame::RefMode aRefMode(rStyle.GetRefMode());
+ Color aPrim(rStyle.GetColorPrim());
+ Color aSecn(rStyle.GetColorSecn());
+ const bool bSecnUsed(0.0 != rStyle.Secn());
+
+ // Get the single segment line widths. This is the point where the
+ // minimal discrete unit will be used if given (fMinimalDiscreteUnit). If
+ // not given it's 0.0 and thus will have no influence.
+ double fPrim(snapToDiscreteUnit(rStyle.Prim(), fMinimalDiscreteUnit));
+ const double fDist(snapToDiscreteUnit(rStyle.Dist(), fMinimalDiscreteUnit));
+ double fSecn(snapToDiscreteUnit(rStyle.Secn(), fMinimalDiscreteUnit));
+
+ // Of course also do not use svx::frame::Style::GetWidth() for obvious
+ // reasons.
+ const double fStyleWidth(fPrim + fDist + fSecn);
+
+ if(bMirrored)
+ {
+ switch(aRefMode)
+ {
+ case svx::frame::RefMode::Begin: aRefMode = svx::frame::RefMode::End; break;
+ case svx::frame::RefMode::End: aRefMode = svx::frame::RefMode::Begin; break;
+ default: break;
+ }
+
+ if(bSecnUsed)
+ {
+ std::swap(aPrim, aSecn);
+ std::swap(fPrim, fSecn);
+ }
+ }
+
+ if (svx::frame::RefMode::Centered != aRefMode)
+ {
+ const double fHalfWidth(fStyleWidth * 0.5);
+
+ if (svx::frame::RefMode::Begin == aRefMode)
+ {
+ // move aligned below vector
+ mfRefModeOffset = fHalfWidth;
+ }
+ else if (svx::frame::RefMode::End == aRefMode)
+ {
+ // move aligned above vector
+ mfRefModeOffset = -fHalfWidth;
+ }
+ }
+
+ if (bSecnUsed)
+ {
+ // both or all three lines used
+ const bool bPrimTransparent(rStyle.GetColorPrim().IsFullyTransparent());
+ const bool bDistTransparent(!rStyle.UseGapColor() || rStyle.GetColorGap().IsFullyTransparent());
+ const bool bSecnTransparent(aSecn.IsFullyTransparent());
+
+ if(!bPrimTransparent || !bDistTransparent || !bSecnTransparent)
+ {
+ const double a(mfRefModeOffset - (fStyleWidth * 0.5));
+ const double b(a + fPrim);
+ const double c(b + fDist);
+ const double d(c + fSecn);
+
+ maOffsets.push_back(
+ OffsetAndHalfWidthAndColor(
+ (a + b) * 0.5,
+ fPrim * 0.5,
+ nullptr != pForceColor ? *pForceColor : aPrim));
+
+ maOffsets.push_back(
+ OffsetAndHalfWidthAndColor(
+ (b + c) * 0.5,
+ fDist * 0.5,
+ rStyle.UseGapColor()
+ ? (nullptr != pForceColor ? *pForceColor : rStyle.GetColorGap())
+ : COL_TRANSPARENT));
+
+ maOffsets.push_back(
+ OffsetAndHalfWidthAndColor(
+ (c + d) * 0.5,
+ fSecn * 0.5,
+ nullptr != pForceColor ? *pForceColor : aSecn));
+ }
+ }
+ else
+ {
+ // one line used, push two values, from outer to inner
+ if(!rStyle.GetColorPrim().IsFullyTransparent())
+ {
+ maOffsets.push_back(
+ OffsetAndHalfWidthAndColor(
+ mfRefModeOffset,
+ fPrim * 0.5,
+ nullptr != pForceColor ? *pForceColor : aPrim));
+ }
+ }
+ }
+
+ double getRefModeOffset() const { return mfRefModeOffset; }
+ const basegfx::B2DVector& getB2DVector() const { return maB2DVector; }
+ double getAngle() const { return mfAngle; }
+ bool empty() const { return maOffsets.empty(); }
+ size_t size() const { return maOffsets.size(); }
+
+ void getColorAndOffsetAndHalfWidth(size_t nIndex, Color& rColor, double& rfOffset, double& rfHalfWidth) const
+ {
+ if(nIndex >= maOffsets.size())
+ return;
+ const OffsetAndHalfWidthAndColor& rCandidate(maOffsets[nIndex]);
+ rfOffset = rCandidate.mfOffset;
+ rfHalfWidth = rCandidate.mfHalfWidth;
+ rColor = rCandidate.maColor;
+ }
+ };
+
+ class StyleVectorTable
+ {
+ private:
+ std::vector< StyleVectorCombination > maEntries;
+
+ public:
+ StyleVectorTable()
+ {
+ }
+
+ void add(
+ const svx::frame::Style& rStyle,
+ const basegfx::B2DVector& rMyVector,
+ const basegfx::B2DVector& rOtherVector,
+ bool bMirrored,
+ double fMinimalDiscreteUnit)
+ {
+ if(!rStyle.IsUsed() || basegfx::areParallel(rMyVector, rOtherVector))
+ return;
+
+ // create angle between both. angle() needs vectors pointing away from the same point,
+ // so take the mirrored one. Add M_PI to get from -pi..+pi to [0..M_PI_2] for sorting
+ const double fAngle(basegfx::B2DVector(-rMyVector.getX(), -rMyVector.getY()).angle(rOtherVector) + M_PI);
+ maEntries.emplace_back(
+ rStyle,
+ rOtherVector,
+ fAngle,
+ bMirrored,
+ nullptr,
+ fMinimalDiscreteUnit);
+ }
+
+ void sort()
+ {
+ // sort inverse from highest to lowest
+ std::sort(
+ maEntries.begin(),
+ maEntries.end(),
+ [](const StyleVectorCombination& a, const StyleVectorCombination& b)
+ { return a.getAngle() > b.getAngle(); });
+ }
+
+ bool empty() const { return maEntries.empty(); }
+ const std::vector< StyleVectorCombination >& getEntries() const{ return maEntries; }
+ };
+
+ struct CutSet
+ {
+ double mfOLML;
+ double mfORML;
+ double mfOLMR;
+ double mfORMR;
+
+ CutSet() : mfOLML(0.0), mfORML(0.0), mfOLMR(0.0), mfORMR(0.0)
+ {
+ }
+
+ bool operator<( const CutSet& rOther) const
+ {
+ const double fA(mfOLML + mfORML + mfOLMR + mfORMR);
+ const double fB(rOther.mfOLML + rOther.mfORML + rOther.mfOLMR + rOther.mfORMR);
+
+ return fA < fB;
+ }
+
+ double getSum() const { return mfOLML + mfORML + mfOLMR + mfORMR; }
+ };
+
+ void getCutSet(
+ CutSet& rCutSet,
+ const basegfx::B2DPoint& rLeft,
+ const basegfx::B2DPoint& rRight,
+ const basegfx::B2DVector& rX,
+ const basegfx::B2DPoint& rOtherLeft,
+ const basegfx::B2DPoint& rOtherRight,
+ const basegfx::B2DVector& rOtherX)
+ {
+ basegfx::utils::findCut(
+ rLeft,
+ rX,
+ rOtherLeft,
+ rOtherX,
+ CutFlagValue::LINE,
+ &rCutSet.mfOLML);
+
+ basegfx::utils::findCut(
+ rRight,
+ rX,
+ rOtherLeft,
+ rOtherX,
+ CutFlagValue::LINE,
+ &rCutSet.mfOLMR);
+
+ basegfx::utils::findCut(
+ rLeft,
+ rX,
+ rOtherRight,
+ rOtherX,
+ CutFlagValue::LINE,
+ &rCutSet.mfORML);
+
+ basegfx::utils::findCut(
+ rRight,
+ rX,
+ rOtherRight,
+ rOtherX,
+ CutFlagValue::LINE,
+ &rCutSet.mfORMR);
+ }
+
+ struct ExtendSet
+ {
+ double mfExtLeft;
+ double mfExtRight;
+
+ ExtendSet() : mfExtLeft(0.0), mfExtRight(0.0) {}
+ };
+
+ void getExtends(
+ std::vector<ExtendSet>& rExtendSet, // target Left/Right values to fill
+ const basegfx::B2DPoint& rOrigin, // own vector start
+ const StyleVectorCombination& rCombination, // own vector and offsets for lines
+ const basegfx::B2DVector& rPerpendX, // normalized perpendicular to own vector
+ const std::vector< StyleVectorCombination >& rStyleVector) // other vectors emerging in this point
+ {
+ if(!(!rCombination.empty() && !rStyleVector.empty() && rCombination.size() == rExtendSet.size()))
+ return;
+
+ const size_t nOffsetA(rCombination.size());
+
+ if(1 == nOffsetA)
+ {
+ Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
+ rCombination.getColorAndOffsetAndHalfWidth(0, aMyColor, fMyOffset, fMyHalfWidth);
+
+ if(!aMyColor.IsFullyTransparent())
+ {
+ const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
+ const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
+ std::vector< CutSet > aCutSets;
+
+ for(const auto& rStyleCandidate : rStyleVector)
+ {
+ const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
+ const size_t nOffsetB(rStyleCandidate.size());
+
+ for(size_t other(0); other < nOffsetB; other++)
+ {
+ Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
+ rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);
+
+ if(!aOtherColor.IsFullyTransparent())
+ {
+ const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
+ const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
+
+ CutSet aNewCutSet;
+ getCutSet(aNewCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
+ aCutSets.push_back(aNewCutSet);
+ }
+ }
+ }
+
+ if(!aCutSets.empty())
+ {
+ CutSet aCutSet(aCutSets[0]);
+ const size_t nNumCutSets(aCutSets.size());
+
+ if(1 != nNumCutSets)
+ {
+ double fCutSet(aCutSet.getSum());
+
+ for(size_t a(1); a < nNumCutSets; a++)
+ {
+ const CutSet& rCandidate(aCutSets[a]);
+ const double fCandidate(rCandidate.getSum());
+
+ if(basegfx::fTools::equalZero(fCandidate - fCutSet))
+ {
+ // both have equal center point, use medium cut
+ const double fNewOLML(std::max(std::min(rCandidate.mfOLML, rCandidate.mfORML), std::min(aCutSet.mfOLML, aCutSet.mfORML)));
+ const double fNewORML(std::min(std::max(rCandidate.mfOLML, rCandidate.mfORML), std::max(aCutSet.mfOLML, aCutSet.mfORML)));
+ const double fNewOLMR(std::max(std::min(rCandidate.mfOLMR, rCandidate.mfORMR), std::min(aCutSet.mfOLMR, aCutSet.mfORMR)));
+ const double fNewORMR(std::min(std::max(rCandidate.mfOLMR, rCandidate.mfORMR), std::max(aCutSet.mfOLMR, aCutSet.mfORMR)));
+ aCutSet.mfOLML = fNewOLML;
+ aCutSet.mfORML = fNewORML;
+ aCutSet.mfOLMR = fNewOLMR;
+ aCutSet.mfORMR = fNewORMR;
+ fCutSet = aCutSet.getSum();
+ }
+ else if(fCandidate < fCutSet)
+ {
+ // get minimum
+ fCutSet = fCandidate;
+ aCutSet = rCandidate;
+ }
+ }
+ }
+
+ ExtendSet& rExt(rExtendSet[0]);
+
+ rExt.mfExtLeft = std::min(aCutSet.mfOLML, aCutSet.mfORML);
+ rExt.mfExtRight = std::min(aCutSet.mfOLMR, aCutSet.mfORMR);
+ }
+ }
+ }
+ else
+ {
+ size_t nVisEdgeUp(0);
+ size_t nVisEdgeDn(0);
+
+ for(size_t my(0); my < nOffsetA; my++)
+ {
+ Color aMyColor; double fMyOffset(0.0); double fMyHalfWidth(0.0);
+ rCombination.getColorAndOffsetAndHalfWidth(my, aMyColor, fMyOffset, fMyHalfWidth);
+
+ if(!aMyColor.IsFullyTransparent())
+ {
+ const basegfx::B2DPoint aLeft(rOrigin + (rPerpendX * (fMyOffset - fMyHalfWidth)));
+ const basegfx::B2DPoint aRight(rOrigin + (rPerpendX * (fMyOffset + fMyHalfWidth)));
+ const bool bUpper(my <= (nOffsetA >> 1));
+ const StyleVectorCombination& rStyleCandidate(bUpper ? rStyleVector.front() : rStyleVector.back());
+ const basegfx::B2DVector aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate.getB2DVector()));
+ const size_t nOffsetB(rStyleCandidate.size());
+ std::vector< CutSet > aCutSets;
+
+ for(size_t other(0); other < nOffsetB; other++)
+ {
+ Color aOtherColor; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
+ rStyleCandidate.getColorAndOffsetAndHalfWidth(other, aOtherColor, fOtherOffset, fOtherHalfWidth);
+
+ if(!aOtherColor.IsFullyTransparent())
+ {
+ const basegfx::B2DPoint aOtherLeft(rOrigin + (aOtherPerpend * (fOtherOffset - fOtherHalfWidth)));
+ const basegfx::B2DPoint aOtherRight(rOrigin + (aOtherPerpend * (fOtherOffset + fOtherHalfWidth)));
+ CutSet aCutSet;
+ getCutSet(aCutSet, aLeft, aRight, rCombination.getB2DVector(), aOtherLeft, aOtherRight, rStyleCandidate.getB2DVector());
+ aCutSets.push_back(aCutSet);
+ }
+ }
+
+ if(!aCutSets.empty())
+ {
+ // sort: min to start, max to end
+ std::sort(aCutSets.begin(), aCutSets.end());
+ const bool bOtherUpper(rStyleCandidate.getAngle() > M_PI);
+
+ // check if we need min or max
+ // bUpper bOtherUpper MinMax
+ // t t max
+ // t f min
+ // f f max
+ // f t min
+ const bool bMax(bUpper == bOtherUpper);
+ size_t nBaseIndex(0);
+ const size_t nNumCutSets(aCutSets.size());
+
+ if(bMax)
+ {
+ // access at end
+ nBaseIndex = nNumCutSets - 1 - (bUpper ? nVisEdgeUp : nVisEdgeDn);
+ }
+ else
+ {
+ // access at start
+ nBaseIndex = bUpper ? nVisEdgeUp : nVisEdgeDn;
+ }
+
+ const size_t nSecuredIndex(std::clamp(nBaseIndex, size_t(0), size_t(nNumCutSets - 1)));
+ const CutSet& rCutSet(aCutSets[nSecuredIndex]);
+ ExtendSet& rExt(rExtendSet[my]);
+
+ rExt.mfExtLeft = std::min(rCutSet.mfOLML, rCutSet.mfORML);
+ rExt.mfExtRight = std::min(rCutSet.mfOLMR, rCutSet.mfORMR);
+ }
+
+ if(bUpper)
+ {
+ nVisEdgeUp++;
+ }
+ else
+ {
+ nVisEdgeDn++;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to create the correct drawinglayer::primitive2d::BorderLinePrimitive2D
+ * for the given data, especially the correct drawinglayer::primitive2d::BorderLine entries
+ * including the correctly solved/created LineStartEnd extends
+ *
+ * rTarget : Here the evtl. created BorderLinePrimitive2D will be appended
+ * rOrigin : StartPoint of the Borderline
+ * rX : Vector of the Borderline
+ * rBorder : svx::frame::Style of the of the Borderline
+ * rStartStyleVectorTable : All other Borderlines which have to be taken into account because
+ * they have the same StartPoint as the current Borderline. These will be used to calculate
+ * the correct LineStartEnd extends tor the BorderLinePrimitive2D. The definition should be
+ * built up using svx::frame::StyleVectorTable and StyleVectorTable::add and includes:
+ * rStyle : the svx::frame::Style of one other BorderLine
+ * rMyVector : the Vector of the *new* to-be-defined BorderLine, identical to rX
+ * rOtherVector: the Vector of one other BorderLine (may be, but does not need to be normalized),
+ * always *pointing away* from the common StartPoint rOrigin
+ * bMirrored : define if rStyle of one other BorderLine shall be mirrored (e.g. bottom-right edges)
+ * With multiple BorderLines the definitions have to be CounterClockWise. This will be
+ * ensured by StyleVectorTable sorting the entries, but knowing this may allow more efficient
+ * data creation.
+ * rEndStyleVectorTable: All other BorderLines that have the same EndPoint. There are differences to
+ * the Start definitions:
+ * - do not forget to consequently use -rX for rMyVector
+ * - definitions have to be ClockWise for the EndBorderLines, will be ensured by sorting
+ *
+ * If you take all this into account, you will get correctly extended BorderLinePrimitive2D
+ * representations for the new to be defined BorderLine. That extensions will overlap nicely
+ * with the corresponding BorderLines and take all multiple line definitions in the ::Style into
+ * account.
+ * The internal solver is *not limited* to ::Style(s) with three parts (Left/Gap/Right), this is
+ * just due to svx::frame::Style's definitions. A new solver based on this one can be created
+ * anytime using more mulötiple borders based on the more flexible
+ * std::vector< drawinglayer::primitive2d::BorderLine > if needed.
+ */
+ void CreateBorderPrimitives(
+ drawinglayer::primitive2d::Primitive2DContainer& rTarget, /// target for created primitives
+ const basegfx::B2DPoint& rOrigin, /// start point of borderline
+ const basegfx::B2DVector& rX, /// X-Axis of borderline with length
+ const svx::frame::Style& rBorder, /// Style of borderline
+ const StyleVectorTable& rStartStyleVectorTable, /// Styles and vectors (pointing away) at borderline start, ccw
+ const StyleVectorTable& rEndStyleVectorTable, /// Styles and vectors (pointing away) at borderline end, cw
+ const Color* pForceColor, /// If specified, overrides frame border color.
+ double fMinimalDiscreteUnit) /// minimal discrete unit to use for svx::frame::Style width values
+ {
+ // get offset color pairs for style, one per visible line
+ const StyleVectorCombination aCombination(
+ rBorder,
+ rX,
+ 0.0,
+ false,
+ pForceColor,
+ fMinimalDiscreteUnit);
+
+ if(aCombination.empty())
+ return;
+
+ const basegfx::B2DVector aPerpendX(basegfx::getNormalizedPerpendicular(rX));
+ const bool bHasStartStyles(!rStartStyleVectorTable.empty());
+ const bool bHasEndStyles(!rEndStyleVectorTable.empty());
+ const size_t nOffsets(aCombination.size());
+ std::vector<ExtendSet> aExtendSetStart(nOffsets);
+ std::vector<ExtendSet> aExtendSetEnd(nOffsets);
+
+ if(bHasStartStyles)
+ {
+ // create extends for line starts, use given point/vector and offsets
+ getExtends(aExtendSetStart, rOrigin, aCombination, aPerpendX, rStartStyleVectorTable.getEntries());
+ }
+
+ if(bHasEndStyles)
+ {
+ // Create extends for line ends, create inverse point/vector and inverse offsets.
+ const StyleVectorCombination aMirroredCombination(
+ rBorder,
+ -rX,
+ 0.0,
+ true,
+ pForceColor,
+ fMinimalDiscreteUnit);
+
+ getExtends(aExtendSetEnd, rOrigin + rX, aMirroredCombination, -aPerpendX, rEndStyleVectorTable.getEntries());
+
+ // also need to inverse the result to apply to the correct lines
+ std::reverse(aExtendSetEnd.begin(), aExtendSetEnd.end());
+ }
+
+ std::vector< drawinglayer::primitive2d::BorderLine > aBorderlines;
+ const double fNegLength(-rX.getLength());
+
+ for(size_t a(0); a < nOffsets; a++)
+ {
+ Color aMyColor;
+ double fMyOffset(0.0);
+ double fMyHalfWidth(0.0);
+ aCombination.getColorAndOffsetAndHalfWidth(a, aMyColor, fMyOffset, fMyHalfWidth);
+ const ExtendSet& rExtStart(aExtendSetStart[a]);
+ const ExtendSet& rExtEnd(aExtendSetEnd[a]);
+
+ if(aMyColor.IsFullyTransparent())
+ {
+ aBorderlines.push_back(
+ drawinglayer::primitive2d::BorderLine(
+ fMyHalfWidth * 2.0));
+ }
+ else
+ {
+ aBorderlines.push_back(
+ drawinglayer::primitive2d::BorderLine(
+ drawinglayer::attribute::LineAttribute(
+ aMyColor.getBColor(),
+ fMyHalfWidth * 2.0),
+ fNegLength * rExtStart.mfExtLeft,
+ fNegLength * rExtStart.mfExtRight,
+ fNegLength * rExtEnd.mfExtRight,
+ fNegLength * rExtEnd.mfExtLeft));
+ }
+ }
+
+ static const double fPatScFact(10.0); // 10.0 multiply, see old code
+ std::vector<double> aDashing(svtools::GetLineDashing(rBorder.Type(), rBorder.PatternScale() * fPatScFact));
+ const drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashing));
+ const basegfx::B2DPoint aStart(rOrigin + (aPerpendX * aCombination.getRefModeOffset()));
+
+ rTarget.append(
+ drawinglayer::primitive2d::Primitive2DReference(
+ new drawinglayer::primitive2d::BorderLinePrimitive2D(
+ aStart,
+ aStart + rX,
+ std::move(aBorderlines),
+ aStrokeAttribute)));
+ }
+
+ double getMinimalNonZeroValue(double fCurrent, double fNew)
+ {
+ if(0.0 != fNew)
+ {
+ if(0.0 != fCurrent)
+ {
+ fCurrent = std::min(fNew, fCurrent);
+ }
+ else
+ {
+ fCurrent = fNew;
+ }
+ }
+
+ return fCurrent;
+ }
+
+ double getMinimalNonZeroBorderWidthFromStyle(double fCurrent, const svx::frame::Style& rStyle)
+ {
+ if(rStyle.IsUsed())
+ {
+ fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Prim());
+ fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Dist());
+ fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Secn());
+ }
+
+ return fCurrent;
+ }
+}
+
+namespace drawinglayer::primitive2d
+{
+ SdrFrameBorderData::SdrConnectStyleData::SdrConnectStyleData(
+ const svx::frame::Style& rStyle,
+ const basegfx::B2DVector& rNormalizedPerpendicular,
+ bool bStyleMirrored)
+ : maStyle(rStyle),
+ maNormalizedPerpendicular(rNormalizedPerpendicular),
+ mbStyleMirrored(bStyleMirrored)
+ {
+ }
+
+ bool SdrFrameBorderData::SdrConnectStyleData::operator==(const SdrFrameBorderData::SdrConnectStyleData& rCompare) const
+ {
+ return mbStyleMirrored == rCompare.mbStyleMirrored
+ && maStyle == rCompare.maStyle
+ && maNormalizedPerpendicular == rCompare.maNormalizedPerpendicular;
+ }
+
+ SdrFrameBorderData::SdrFrameBorderData(
+ const basegfx::B2DPoint& rOrigin,
+ const basegfx::B2DVector& rX,
+ const svx::frame::Style& rStyle,
+ const Color* pForceColor)
+ : maOrigin(rOrigin),
+ maX(rX),
+ maStyle(rStyle),
+ maColor(nullptr != pForceColor ? *pForceColor : Color()),
+ mbForceColor(nullptr != pForceColor)
+ {
+ }
+
+ void SdrFrameBorderData::addSdrConnectStyleData(
+ bool bStart,
+ const svx::frame::Style& rStyle,
+ const basegfx::B2DVector& rNormalizedPerpendicular,
+ bool bStyleMirrored)
+ {
+ if(rStyle.IsUsed())
+ {
+ if(bStart)
+ {
+ maStart.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
+ }
+ else
+ {
+ maEnd.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
+ }
+ }
+ }
+
+ void SdrFrameBorderData::create2DDecomposition(
+ Primitive2DContainer& rContainer,
+ double fMinimalDiscreteUnit) const
+ {
+ StyleVectorTable aStartVector;
+ StyleVectorTable aEndVector;
+ const basegfx::B2DVector aAxis(-maX);
+
+ for(const auto& rStart : maStart)
+ {
+ aStartVector.add(
+ rStart.getStyle(),
+ maX,
+ rStart.getNormalizedPerpendicular(),
+ rStart.getStyleMirrored(),
+ fMinimalDiscreteUnit);
+ }
+
+ for(const auto& rEnd : maEnd)
+ {
+ aEndVector.add(
+ rEnd.getStyle(),
+ aAxis,
+ rEnd.getNormalizedPerpendicular(),
+ rEnd.getStyleMirrored(),
+ fMinimalDiscreteUnit);
+ }
+
+ aStartVector.sort();
+ aEndVector.sort();
+
+ CreateBorderPrimitives(
+ rContainer,
+ maOrigin,
+ maX,
+ maStyle,
+ aStartVector,
+ aEndVector,
+ mbForceColor ? &maColor : nullptr,
+ fMinimalDiscreteUnit);
+ }
+
+ double SdrFrameBorderData::getMinimalNonZeroBorderWidth() const
+ {
+ double fRetval(getMinimalNonZeroBorderWidthFromStyle(0.0, maStyle));
+
+ for(const auto& rStart : maStart)
+ {
+ fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rStart.getStyle());
+ }
+
+ for(const auto& rEnd : maEnd)
+ {
+ fRetval = getMinimalNonZeroBorderWidthFromStyle(fRetval, rEnd.getStyle());
+ }
+
+ return fRetval;
+ }
+
+
+ bool SdrFrameBorderData::operator==(const SdrFrameBorderData& rCompare) const
+ {
+ return maOrigin == rCompare.maOrigin
+ && maX == rCompare.maX
+ && maStyle == rCompare.maStyle
+ && maColor == rCompare.maColor
+ && mbForceColor == rCompare.mbForceColor
+ && maStart == rCompare.maStart
+ && maEnd == rCompare.maEnd;
+ }
+
+
+ void SdrFrameBorderPrimitive2D::create2DDecomposition(
+ Primitive2DContainer& rContainer,
+ const geometry::ViewInformation2D& /*aViewInformation*/) const
+ {
+ if(!getFrameBorders())
+ {
+ return;
+ }
+
+ Primitive2DContainer aRetval;
+
+ // Check and use the minimal non-zero BorderWidth for decompose
+ // if that is set and wanted
+ const double fMinimalDiscreteUnit(doForceToSingleDiscreteUnit()
+ ? mfMinimalNonZeroBorderWidthUsedForDecompose
+ : 0.0);
+
+ {
+ // decompose all buffered SdrFrameBorderData entries and try to merge them
+ // to reduce existing number of BorderLinePrimitive2D(s)
+ for(const auto& rCandidate : *getFrameBorders())
+ {
+ // get decomposition on one SdrFrameBorderData entry
+ Primitive2DContainer aPartial;
+ rCandidate.create2DDecomposition(
+ aPartial,
+ fMinimalDiscreteUnit);
+
+ for(const auto& aCandidatePartial : aPartial)
+ {
+ if(aRetval.empty())
+ {
+ // no local data yet, just add as 1st entry, done
+ aRetval.append(aCandidatePartial);
+ }
+ else
+ {
+ bool bDidMerge(false);
+
+ for(auto& aCandidateRetval : aRetval)
+ {
+ // try to merge by appending new data to existing data
+ const drawinglayer::primitive2d::Primitive2DReference aMergeRetvalPartial(
+ drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
+ static_cast<BorderLinePrimitive2D*>(aCandidateRetval.get()),
+ static_cast<BorderLinePrimitive2D*>(aCandidatePartial.get())));
+
+ if(aMergeRetvalPartial.is())
+ {
+ // could append, replace existing data with merged data, done
+ aCandidateRetval = aMergeRetvalPartial;
+ bDidMerge = true;
+ break;
+ }
+
+ // try to merge by appending existing data to new data
+ const drawinglayer::primitive2d::Primitive2DReference aMergePartialRetval(
+ drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
+ static_cast<BorderLinePrimitive2D*>(aCandidatePartial.get()),
+ static_cast<BorderLinePrimitive2D*>(aCandidateRetval.get())));
+
+ if(aMergePartialRetval.is())
+ {
+ // could append, replace existing data with merged data, done
+ aCandidateRetval = aMergePartialRetval;
+ bDidMerge = true;
+ break;
+ }
+ }
+
+ if(!bDidMerge)
+ {
+ // no merge after checking all existing data, append as new segment
+ aRetval.append(aCandidatePartial);
+ }
+ }
+ }
+ }
+ }
+
+ rContainer.append(std::move(aRetval));
+ }
+
+ SdrFrameBorderPrimitive2D::SdrFrameBorderPrimitive2D(
+ std::shared_ptr<SdrFrameBorderDataVector>& rFrameBorders,
+ bool bForceToSingleDiscreteUnit)
+ : maFrameBorders(std::move(rFrameBorders)),
+ mfMinimalNonZeroBorderWidth(0.0),
+ mfMinimalNonZeroBorderWidthUsedForDecompose(0.0),
+ mbForceToSingleDiscreteUnit(bForceToSingleDiscreteUnit)
+ {
+ if(getFrameBorders() && doForceToSingleDiscreteUnit())
+ {
+ // detect used minimal non-zero partial border width
+ for(const auto& rCandidate : *getFrameBorders())
+ {
+ mfMinimalNonZeroBorderWidth = getMinimalNonZeroValue(
+ mfMinimalNonZeroBorderWidth,
+ rCandidate.getMinimalNonZeroBorderWidth());
+ }
+ }
+ }
+
+ bool SdrFrameBorderPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
+ {
+ if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
+ {
+ const SdrFrameBorderPrimitive2D& rCompare = static_cast<const SdrFrameBorderPrimitive2D&>(rPrimitive);
+
+ return (getFrameBorders() == rCompare.getFrameBorders()
+ || (getFrameBorders() && rCompare.getFrameBorders()
+ && *getFrameBorders() == *rCompare.getFrameBorders()))
+ && doForceToSingleDiscreteUnit() == rCompare.doForceToSingleDiscreteUnit();
+ }
+
+ return false;
+ }
+
+ void SdrFrameBorderPrimitive2D::get2DDecomposition(
+ Primitive2DDecompositionVisitor& rVisitor,
+ const geometry::ViewInformation2D& rViewInformation) const
+ {
+ if(doForceToSingleDiscreteUnit())
+ {
+ // 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));
+ double fDiscreteUnit(std::min(fabs(aDiscreteVector.getX()), fabs(aDiscreteVector.getY())));
+
+ if(fDiscreteUnit <= mfMinimalNonZeroBorderWidth)
+ {
+ // no need to use it, reset
+ fDiscreteUnit = 0.0;
+ }
+
+ if(fDiscreteUnit != mfMinimalNonZeroBorderWidthUsedForDecompose)
+ {
+ // conditions of last local decomposition have changed, delete
+ // possible content
+ if(!getBuffered2DDecomposition().empty())
+ {
+ const_cast< SdrFrameBorderPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer());
+ }
+
+ // remember new conditions
+ const_cast< SdrFrameBorderPrimitive2D* >(this)->mfMinimalNonZeroBorderWidthUsedForDecompose = fDiscreteUnit;
+ }
+ }
+
+ // call parent. This will call back ::create2DDecomposition above
+ // where mfMinimalNonZeroBorderWidthUsedForDecompose will be used
+ // when doForceToSingleDiscreteUnit() is true
+ BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
+ }
+
+ // provide unique ID
+ sal_uInt32 SdrFrameBorderPrimitive2D::getPrimitive2DID() const
+ {
+ return PRIMITIVE2D_ID_SDRFRAMEBORDERTPRIMITIVE2D;
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */