923 lines
37 KiB
C++
923 lines
37 KiB
C++
/* -*- 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/primitive2d/groupprimitive2d.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));
|
|
drawinglayer::attribute::StrokeAttribute aStrokeAttribute(std::move(aDashing));
|
|
const basegfx::B2DPoint aStart(rOrigin + (aPerpendX * aCombination.getRefModeOffset()));
|
|
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::BorderLinePrimitive2D(
|
|
aStart,
|
|
aStart + rX,
|
|
std::move(aBorderlines),
|
|
std::move(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;
|
|
}
|
|
|
|
|
|
Primitive2DReference SdrFrameBorderPrimitive2D::create2DDecomposition(
|
|
const geometry::ViewInformation2D& /*aViewInformation*/) const
|
|
{
|
|
if(getFrameBorders().empty())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return new GroupPrimitive2D(std::move(aRetval));
|
|
}
|
|
|
|
SdrFrameBorderPrimitive2D::SdrFrameBorderPrimitive2D(
|
|
SdrFrameBorderDataVector&& rFrameBorders,
|
|
bool bForceToSingleDiscreteUnit)
|
|
: maFrameBorders(std::move(rFrameBorders)),
|
|
mfMinimalNonZeroBorderWidth(0.0),
|
|
mfMinimalNonZeroBorderWidthUsedForDecompose(0.0),
|
|
mbForceToSingleDiscreteUnit(bForceToSingleDiscreteUnit)
|
|
{
|
|
if(!getFrameBorders().empty() && 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()
|
|
&& 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())
|
|
{
|
|
const_cast< SdrFrameBorderPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
|
|
}
|
|
|
|
// 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: */
|