1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx>
21 #include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
22 #include <drawinglayer/geometry/viewinformation2d.hxx>
23 #include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
24 #include <basegfx/polygon/b2dpolygontools.hxx>
25 #include <svtools/borderhelper.hxx>
29 double snapToDiscreteUnit(
31 double fMinimalDiscreteUnit
)
35 fValue
= std::max(fValue
, fMinimalDiscreteUnit
);
41 class StyleVectorCombination
44 struct OffsetAndHalfWidthAndColor
50 OffsetAndHalfWidthAndColor(double offset
, double halfWidth
, Color color
) :
52 mfHalfWidth(halfWidth
),
57 double mfRefModeOffset
;
58 basegfx::B2DVector maB2DVector
;
60 std::vector
< OffsetAndHalfWidthAndColor
> maOffsets
;
63 StyleVectorCombination(
64 const svx::frame::Style
& rStyle
,
65 const basegfx::B2DVector
& rB2DVector
,
68 const Color
* pForceColor
,
69 double fMinimalDiscreteUnit
)
70 : mfRefModeOffset(0.0),
71 maB2DVector(rB2DVector
),
78 svx::frame::RefMode
aRefMode(rStyle
.GetRefMode());
79 Color
aPrim(rStyle
.GetColorPrim());
80 Color
aSecn(rStyle
.GetColorSecn());
81 const bool bSecnUsed(0.0 != rStyle
.Secn());
83 // Get the single segment line widths. This is the point where the
84 // minimal discrete unit will be used if given (fMinimalDiscreteUnit). If
85 // not given it's 0.0 and thus will have no influence.
86 double fPrim(snapToDiscreteUnit(rStyle
.Prim(), fMinimalDiscreteUnit
));
87 const double fDist(snapToDiscreteUnit(rStyle
.Dist(), fMinimalDiscreteUnit
));
88 double fSecn(snapToDiscreteUnit(rStyle
.Secn(), fMinimalDiscreteUnit
));
90 // Of course also do not use svx::frame::Style::GetWidth() for obvious
92 const double fStyleWidth(fPrim
+ fDist
+ fSecn
);
98 case svx::frame::RefMode::Begin
: aRefMode
= svx::frame::RefMode::End
; break;
99 case svx::frame::RefMode::End
: aRefMode
= svx::frame::RefMode::Begin
; break;
105 std::swap(aPrim
, aSecn
);
106 std::swap(fPrim
, fSecn
);
110 if (svx::frame::RefMode::Centered
!= aRefMode
)
112 const double fHalfWidth(fStyleWidth
* 0.5);
114 if (svx::frame::RefMode::Begin
== aRefMode
)
116 // move aligned below vector
117 mfRefModeOffset
= fHalfWidth
;
119 else if (svx::frame::RefMode::End
== aRefMode
)
121 // move aligned above vector
122 mfRefModeOffset
= -fHalfWidth
;
128 // both or all three lines used
129 const bool bPrimTransparent(0xff == rStyle
.GetColorPrim().GetTransparency());
130 const bool bDistTransparent(!rStyle
.UseGapColor() || 0xff == rStyle
.GetColorGap().GetTransparency());
131 const bool bSecnTransparent(0xff == aSecn
.GetTransparency());
133 if(!bPrimTransparent
|| !bDistTransparent
|| !bSecnTransparent
)
135 const double a(mfRefModeOffset
- (fStyleWidth
* 0.5));
136 const double b(a
+ fPrim
);
137 const double c(b
+ fDist
);
138 const double d(c
+ fSecn
);
141 OffsetAndHalfWidthAndColor(
144 nullptr != pForceColor
? *pForceColor
: aPrim
));
147 OffsetAndHalfWidthAndColor(
151 ? (nullptr != pForceColor
? *pForceColor
: rStyle
.GetColorGap())
155 OffsetAndHalfWidthAndColor(
158 nullptr != pForceColor
? *pForceColor
: aSecn
));
163 // one line used, push two values, from outer to inner
164 if(0xff != rStyle
.GetColorPrim().GetTransparency())
167 OffsetAndHalfWidthAndColor(
170 nullptr != pForceColor
? *pForceColor
: aPrim
));
175 double getRefModeOffset() const { return mfRefModeOffset
; }
176 const basegfx::B2DVector
& getB2DVector() const { return maB2DVector
; }
177 double getAngle() const { return mfAngle
; }
178 bool empty() const { return maOffsets
.empty(); }
179 size_t size() const { return maOffsets
.size(); }
181 void getColorAndOffsetAndHalfWidth(size_t nIndex
, Color
& rColor
, double& rfOffset
, double& rfHalfWidth
) const
183 if(nIndex
>= maOffsets
.size())
185 const OffsetAndHalfWidthAndColor
& rCandidate(maOffsets
[nIndex
]);
186 rfOffset
= rCandidate
.mfOffset
;
187 rfHalfWidth
= rCandidate
.mfHalfWidth
;
188 rColor
= rCandidate
.maColor
;
192 class StyleVectorTable
195 std::vector
< StyleVectorCombination
> maEntries
;
204 const svx::frame::Style
& rStyle
,
205 const basegfx::B2DVector
& rMyVector
,
206 const basegfx::B2DVector
& rOtherVector
,
208 double fMinimalDiscreteUnit
)
210 if(!rStyle
.IsUsed() || basegfx::areParallel(rMyVector
, rOtherVector
))
213 // create angle between both. angle() needs vectors pointing away from the same point,
214 // so take the mirrored one. Add F_PI to get from -pi..+pi to [0..F_PI2] for sorting
215 const double fAngle(basegfx::B2DVector(-rMyVector
.getX(), -rMyVector
.getY()).angle(rOtherVector
) + F_PI
);
216 maEntries
.emplace_back(
222 fMinimalDiscreteUnit
);
227 // sort inverse from highest to lowest
231 [](const StyleVectorCombination
& a
, const StyleVectorCombination
& b
)
232 { return a
.getAngle() > b
.getAngle(); });
235 bool empty() const { return maEntries
.empty(); }
236 const std::vector
< StyleVectorCombination
>& getEntries() const{ return maEntries
; }
246 CutSet() : mfOLML(0.0), mfORML(0.0), mfOLMR(0.0), mfORMR(0.0)
250 bool operator<( const CutSet
& rOther
) const
252 const double fA(mfOLML
+ mfORML
+ mfOLMR
+ mfORMR
);
253 const double fB(rOther
.mfOLML
+ rOther
.mfORML
+ rOther
.mfOLMR
+ rOther
.mfORMR
);
258 double getSum() const { return mfOLML
+ mfORML
+ mfOLMR
+ mfORMR
; }
263 const basegfx::B2DPoint
& rLeft
,
264 const basegfx::B2DPoint
& rRight
,
265 const basegfx::B2DVector
& rX
,
266 const basegfx::B2DPoint
& rOtherLeft
,
267 const basegfx::B2DPoint
& rOtherRight
,
268 const basegfx::B2DVector
& rOtherX
)
270 basegfx::utils::findCut(
278 basegfx::utils::findCut(
286 basegfx::utils::findCut(
294 basegfx::utils::findCut(
308 ExtendSet() : mfExtLeft(0.0), mfExtRight(0.0) {}
312 std::vector
<ExtendSet
>& rExtendSet
, // target Left/Right values to fill
313 const basegfx::B2DPoint
& rOrigin
, // own vector start
314 const StyleVectorCombination
& rCombination
, // own vector and offsets for lines
315 const basegfx::B2DVector
& rPerpendX
, // normalized perpendicular to own vector
316 const std::vector
< StyleVectorCombination
>& rStyleVector
) // other vectors emerging in this point
318 if(!(!rCombination
.empty() && !rStyleVector
.empty() && rCombination
.size() == rExtendSet
.size()))
321 const size_t nOffsetA(rCombination
.size());
325 Color aMyColor
; double fMyOffset(0.0); double fMyHalfWidth(0.0);
326 rCombination
.getColorAndOffsetAndHalfWidth(0, aMyColor
, fMyOffset
, fMyHalfWidth
);
328 if(0xff != aMyColor
.GetTransparency())
330 const basegfx::B2DPoint
aLeft(rOrigin
+ (rPerpendX
* (fMyOffset
- fMyHalfWidth
)));
331 const basegfx::B2DPoint
aRight(rOrigin
+ (rPerpendX
* (fMyOffset
+ fMyHalfWidth
)));
332 std::vector
< CutSet
> aCutSets
;
334 for(const auto& rStyleCandidate
: rStyleVector
)
336 const basegfx::B2DVector
aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate
.getB2DVector()));
337 const size_t nOffsetB(rStyleCandidate
.size());
339 for(size_t other(0); other
< nOffsetB
; other
++)
341 Color aOtherColor
; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
342 rStyleCandidate
.getColorAndOffsetAndHalfWidth(other
, aOtherColor
, fOtherOffset
, fOtherHalfWidth
);
344 if(0xff != aOtherColor
.GetTransparency())
346 const basegfx::B2DPoint
aOtherLeft(rOrigin
+ (aOtherPerpend
* (fOtherOffset
- fOtherHalfWidth
)));
347 const basegfx::B2DPoint
aOtherRight(rOrigin
+ (aOtherPerpend
* (fOtherOffset
+ fOtherHalfWidth
)));
350 getCutSet(aNewCutSet
, aLeft
, aRight
, rCombination
.getB2DVector(), aOtherLeft
, aOtherRight
, rStyleCandidate
.getB2DVector());
351 aCutSets
.push_back(aNewCutSet
);
356 if(!aCutSets
.empty())
358 CutSet
aCutSet(aCutSets
[0]);
359 const size_t nNumCutSets(aCutSets
.size());
363 double fCutSet(aCutSet
.getSum());
365 for(size_t a(1); a
< nNumCutSets
; a
++)
367 const CutSet
& rCandidate(aCutSets
[a
]);
368 const double fCandidate(rCandidate
.getSum());
370 if(basegfx::fTools::equalZero(fCandidate
- fCutSet
))
372 // both have equal center point, use medium cut
373 const double fNewOLML(std::max(std::min(rCandidate
.mfOLML
, rCandidate
.mfORML
), std::min(aCutSet
.mfOLML
, aCutSet
.mfORML
)));
374 const double fNewORML(std::min(std::max(rCandidate
.mfOLML
, rCandidate
.mfORML
), std::max(aCutSet
.mfOLML
, aCutSet
.mfORML
)));
375 const double fNewOLMR(std::max(std::min(rCandidate
.mfOLMR
, rCandidate
.mfORMR
), std::min(aCutSet
.mfOLMR
, aCutSet
.mfORMR
)));
376 const double fNewORMR(std::min(std::max(rCandidate
.mfOLMR
, rCandidate
.mfORMR
), std::max(aCutSet
.mfOLMR
, aCutSet
.mfORMR
)));
377 aCutSet
.mfOLML
= fNewOLML
;
378 aCutSet
.mfORML
= fNewORML
;
379 aCutSet
.mfOLMR
= fNewOLMR
;
380 aCutSet
.mfORMR
= fNewORMR
;
381 fCutSet
= aCutSet
.getSum();
383 else if(fCandidate
< fCutSet
)
386 fCutSet
= fCandidate
;
387 aCutSet
= rCandidate
;
392 ExtendSet
& rExt(rExtendSet
[0]);
394 rExt
.mfExtLeft
= std::min(aCutSet
.mfOLML
, aCutSet
.mfORML
);
395 rExt
.mfExtRight
= std::min(aCutSet
.mfOLMR
, aCutSet
.mfORMR
);
401 size_t nVisEdgeUp(0);
402 size_t nVisEdgeDn(0);
404 for(size_t my(0); my
< nOffsetA
; my
++)
406 Color aMyColor
; double fMyOffset(0.0); double fMyHalfWidth(0.0);
407 rCombination
.getColorAndOffsetAndHalfWidth(my
, aMyColor
, fMyOffset
, fMyHalfWidth
);
409 if(0xff != aMyColor
.GetTransparency())
411 const basegfx::B2DPoint
aLeft(rOrigin
+ (rPerpendX
* (fMyOffset
- fMyHalfWidth
)));
412 const basegfx::B2DPoint
aRight(rOrigin
+ (rPerpendX
* (fMyOffset
+ fMyHalfWidth
)));
413 const bool bUpper(my
<= (nOffsetA
>> 1));
414 const StyleVectorCombination
& rStyleCandidate(bUpper
? rStyleVector
.front() : rStyleVector
.back());
415 const basegfx::B2DVector
aOtherPerpend(basegfx::getNormalizedPerpendicular(rStyleCandidate
.getB2DVector()));
416 const size_t nOffsetB(rStyleCandidate
.size());
417 std::vector
< CutSet
> aCutSets
;
419 for(size_t other(0); other
< nOffsetB
; other
++)
421 Color aOtherColor
; double fOtherOffset(0.0); double fOtherHalfWidth(0.0);
422 rStyleCandidate
.getColorAndOffsetAndHalfWidth(other
, aOtherColor
, fOtherOffset
, fOtherHalfWidth
);
424 if(0xff != aOtherColor
.GetTransparency())
426 const basegfx::B2DPoint
aOtherLeft(rOrigin
+ (aOtherPerpend
* (fOtherOffset
- fOtherHalfWidth
)));
427 const basegfx::B2DPoint
aOtherRight(rOrigin
+ (aOtherPerpend
* (fOtherOffset
+ fOtherHalfWidth
)));
429 getCutSet(aCutSet
, aLeft
, aRight
, rCombination
.getB2DVector(), aOtherLeft
, aOtherRight
, rStyleCandidate
.getB2DVector());
430 aCutSets
.push_back(aCutSet
);
434 if(!aCutSets
.empty())
436 // sort: min to start, max to end
437 std::sort(aCutSets
.begin(), aCutSets
.end());
438 const bool bOtherUpper(rStyleCandidate
.getAngle() > F_PI
);
440 // check if we need min or max
441 // bUpper bOtherUpper MinMax
446 const bool bMax(bUpper
== bOtherUpper
);
447 size_t nBaseIndex(0);
448 const size_t nNumCutSets(aCutSets
.size());
453 nBaseIndex
= nNumCutSets
- 1 - (bUpper
? nVisEdgeUp
: nVisEdgeDn
);
458 nBaseIndex
= bUpper
? nVisEdgeUp
: nVisEdgeDn
;
461 const size_t nSecuredIndex(std::clamp(nBaseIndex
, size_t(0), size_t(nNumCutSets
- 1)));
462 const CutSet
& rCutSet(aCutSets
[nSecuredIndex
]);
463 ExtendSet
& rExt(rExtendSet
[my
]);
465 rExt
.mfExtLeft
= std::min(rCutSet
.mfOLML
, rCutSet
.mfORML
);
466 rExt
.mfExtRight
= std::min(rCutSet
.mfOLMR
, rCutSet
.mfORMR
);
483 * Helper method to create the correct drawinglayer::primitive2d::BorderLinePrimitive2D
484 * for the given data, especially the correct drawinglayer::primitive2d::BorderLine entries
485 * including the correctly solved/created LineStartEnd extends
487 * rTarget : Here the evtl. created BorderLinePrimitive2D will be appended
488 * rOrigin : StartPoint of the Borderline
489 * rX : Vector of the Borderline
490 * rBorder : svx::frame::Style of the of the Borderline
491 * rStartStyleVectorTable : All other Borderlines which have to be taken into account because
492 * they have the same StartPoint as the current Borderline. These will be used to calculate
493 * the correct LineStartEnd extends tor the BorderLinePrimitive2D. The definition should be
494 * built up using svx::frame::StyleVectorTable and StyleVectorTable::add and includes:
495 * rStyle : the svx::frame::Style of one other BorderLine
496 * rMyVector : the Vector of the *new* to-be-defined BorderLine, identical to rX
497 * rOtherVector: the Vector of one other BorderLine (may be, but does not need to be normalized),
498 * always *pointing away* from the common StartPoint rOrigin
499 * bMirrored : define if rStyle of one other BorderLine shall be mirrored (e.g. bottom-right edges)
500 * With multiple BorderLines the definitions have to be CounterClockWise. This will be
501 * ensured by StyleVectorTable sorting the entries, but knowing this may allow more efficient
503 * rEndStyleVectorTable: All other BorderLines that have the same EndPoint. There are differences to
504 * the Start definitions:
505 * - do not forget to consequently use -rX for rMyVector
506 * - definitions have to be ClockWise for the EndBorderLines, will be ensured by sorting
508 * If you take all this into account, you will get correctly extended BorderLinePrimitive2D
509 * representations for the new to be defined BorderLine. That extensions will overlap nicely
510 * with the corresponding BorderLines and take all multiple line definitions in the ::Style into
512 * The internal solver is *not limited* to ::Style(s) with three parts (Left/Gap/Right), this is
513 * just due to svx::frame::Style's definitions. A new solver based on this one can be created
514 * anytime using more mulötiple borders based on the more flexible
515 * std::vector< drawinglayer::primitive2d::BorderLine > if needed.
517 void CreateBorderPrimitives(
518 drawinglayer::primitive2d::Primitive2DContainer
& rTarget
, /// target for created primitives
519 const basegfx::B2DPoint
& rOrigin
, /// start point of borderline
520 const basegfx::B2DVector
& rX
, /// X-Axis of borderline with length
521 const svx::frame::Style
& rBorder
, /// Style of borderline
522 const StyleVectorTable
& rStartStyleVectorTable
, /// Styles and vectors (pointing away) at borderline start, ccw
523 const StyleVectorTable
& rEndStyleVectorTable
, /// Styles and vectors (pointing away) at borderline end, cw
524 const Color
* pForceColor
, /// If specified, overrides frame border color.
525 double fMinimalDiscreteUnit
) /// minimal discrete unit to use for svx::frame::Style width values
527 // get offset color pairs for style, one per visible line
528 const StyleVectorCombination
aCombination(
534 fMinimalDiscreteUnit
);
536 if(aCombination
.empty())
539 const basegfx::B2DVector
aPerpendX(basegfx::getNormalizedPerpendicular(rX
));
540 const bool bHasStartStyles(!rStartStyleVectorTable
.empty());
541 const bool bHasEndStyles(!rEndStyleVectorTable
.empty());
542 const size_t nOffsets(aCombination
.size());
543 std::vector
<ExtendSet
> aExtendSetStart(nOffsets
);
544 std::vector
<ExtendSet
> aExtendSetEnd(nOffsets
);
548 // create extends for line starts, use given point/vector and offsets
549 getExtends(aExtendSetStart
, rOrigin
, aCombination
, aPerpendX
, rStartStyleVectorTable
.getEntries());
554 // Create extends for line ends, create inverse point/vector and inverse offsets.
555 const StyleVectorCombination
aMirroredCombination(
561 fMinimalDiscreteUnit
);
563 getExtends(aExtendSetEnd
, rOrigin
+ rX
, aMirroredCombination
, -aPerpendX
, rEndStyleVectorTable
.getEntries());
565 // also need to inverse the result to apply to the correct lines
566 std::reverse(aExtendSetEnd
.begin(), aExtendSetEnd
.end());
569 std::vector
< drawinglayer::primitive2d::BorderLine
> aBorderlines
;
570 const double fNegLength(-rX
.getLength());
572 for(size_t a(0); a
< nOffsets
; a
++)
575 double fMyOffset(0.0);
576 double fMyHalfWidth(0.0);
577 aCombination
.getColorAndOffsetAndHalfWidth(a
, aMyColor
, fMyOffset
, fMyHalfWidth
);
578 const ExtendSet
& rExtStart(aExtendSetStart
[a
]);
579 const ExtendSet
& rExtEnd(aExtendSetEnd
[a
]);
581 if(0xff == aMyColor
.GetTransparency())
583 aBorderlines
.push_back(
584 drawinglayer::primitive2d::BorderLine(
585 fMyHalfWidth
* 2.0));
589 aBorderlines
.push_back(
590 drawinglayer::primitive2d::BorderLine(
591 drawinglayer::attribute::LineAttribute(
592 aMyColor
.getBColor(),
594 fNegLength
* rExtStart
.mfExtLeft
,
595 fNegLength
* rExtStart
.mfExtRight
,
596 fNegLength
* rExtEnd
.mfExtRight
,
597 fNegLength
* rExtEnd
.mfExtLeft
));
601 static const double fPatScFact(10.0); // 10.0 multiply, see old code
602 const std::vector
<double> aDashing(svtools::GetLineDashing(rBorder
.Type(), rBorder
.PatternScale() * fPatScFact
));
603 const drawinglayer::attribute::StrokeAttribute
aStrokeAttribute(aDashing
);
604 const basegfx::B2DPoint
aStart(rOrigin
+ (aPerpendX
* aCombination
.getRefModeOffset()));
607 drawinglayer::primitive2d::Primitive2DReference(
608 new drawinglayer::primitive2d::BorderLinePrimitive2D(
615 double getMinimalNonZeroValue(double fCurrent
, double fNew
)
621 fCurrent
= std::min(fNew
, fCurrent
);
632 double getMinimalNonZeroBorderWidthFromStyle(double fCurrent
, const svx::frame::Style
& rStyle
)
636 fCurrent
= getMinimalNonZeroValue(fCurrent
, rStyle
.Prim());
637 fCurrent
= getMinimalNonZeroValue(fCurrent
, rStyle
.Dist());
638 fCurrent
= getMinimalNonZeroValue(fCurrent
, rStyle
.Secn());
645 namespace drawinglayer::primitive2d
647 SdrFrameBorderData::SdrConnectStyleData::SdrConnectStyleData(
648 const svx::frame::Style
& rStyle
,
649 const basegfx::B2DVector
& rNormalizedPerpendicular
,
652 maNormalizedPerpendicular(rNormalizedPerpendicular
),
653 mbStyleMirrored(bStyleMirrored
)
657 bool SdrFrameBorderData::SdrConnectStyleData::operator==(const SdrFrameBorderData::SdrConnectStyleData
& rCompare
) const
659 return mbStyleMirrored
== rCompare
.mbStyleMirrored
660 && maStyle
== rCompare
.maStyle
661 && maNormalizedPerpendicular
== rCompare
.maNormalizedPerpendicular
;
664 SdrFrameBorderData::SdrFrameBorderData(
665 const basegfx::B2DPoint
& rOrigin
,
666 const basegfx::B2DVector
& rX
,
667 const svx::frame::Style
& rStyle
,
668 const Color
* pForceColor
)
672 maColor(nullptr != pForceColor
? *pForceColor
: Color()),
673 mbForceColor(nullptr != pForceColor
),
679 void SdrFrameBorderData::addSdrConnectStyleData(
681 const svx::frame::Style
& rStyle
,
682 const basegfx::B2DVector
& rNormalizedPerpendicular
,
689 maStart
.emplace_back(rStyle
, rNormalizedPerpendicular
, bStyleMirrored
);
693 maEnd
.emplace_back(rStyle
, rNormalizedPerpendicular
, bStyleMirrored
);
698 void SdrFrameBorderData::create2DDecomposition(
699 Primitive2DContainer
& rContainer
,
700 double fMinimalDiscreteUnit
) const
702 StyleVectorTable aStartVector
;
703 StyleVectorTable aEndVector
;
704 const basegfx::B2DVector
aAxis(-maX
);
706 for(const auto& rStart
: maStart
)
711 rStart
.getNormalizedPerpendicular(),
712 rStart
.getStyleMirrored(),
713 fMinimalDiscreteUnit
);
716 for(const auto& rEnd
: maEnd
)
721 rEnd
.getNormalizedPerpendicular(),
722 rEnd
.getStyleMirrored(),
723 fMinimalDiscreteUnit
);
729 CreateBorderPrimitives(
736 mbForceColor
? &maColor
: nullptr,
737 fMinimalDiscreteUnit
);
740 double SdrFrameBorderData::getMinimalNonZeroBorderWidth() const
742 double fRetval(getMinimalNonZeroBorderWidthFromStyle(0.0, maStyle
));
744 for(const auto& rStart
: maStart
)
746 fRetval
= getMinimalNonZeroBorderWidthFromStyle(fRetval
, rStart
.getStyle());
749 for(const auto& rEnd
: maEnd
)
751 fRetval
= getMinimalNonZeroBorderWidthFromStyle(fRetval
, rEnd
.getStyle());
758 bool SdrFrameBorderData::operator==(const SdrFrameBorderData
& rCompare
) const
760 return maOrigin
== rCompare
.maOrigin
761 && maX
== rCompare
.maX
762 && maStyle
== rCompare
.maStyle
763 && maColor
== rCompare
.maColor
764 && mbForceColor
== rCompare
.mbForceColor
765 && maStart
== rCompare
.maStart
766 && maEnd
== rCompare
.maEnd
;
770 void SdrFrameBorderPrimitive2D::create2DDecomposition(
771 Primitive2DContainer
& rContainer
,
772 const geometry::ViewInformation2D
& /*aViewInformation*/) const
774 if(!getFrameBorders())
779 Primitive2DContainer aRetval
;
781 // Check and use the minimal non-zero BorderWidth for decompose
782 // if that is set and wanted
783 const double fMinimalDiscreteUnit(doForceToSingleDiscreteUnit()
784 ? mfMinimalNonZeroBorderWidthUsedForDecompose
788 // decompose all buffered SdrFrameBorderData entries and try to merge them
789 // to reduce existing number of BorderLinePrimitive2D(s)
790 for(const auto& rCandidate
: *getFrameBorders())
792 // get decomposition on one SdrFrameBorderData entry
793 Primitive2DContainer aPartial
;
794 rCandidate
.create2DDecomposition(
796 fMinimalDiscreteUnit
);
798 for(const auto& aCandidatePartial
: aPartial
)
800 bool bDidMerge(false);
802 // This algorithm is O(N^2) and repeated dynamic_cast inside would be quite costly.
803 // So check first and skip if the primitives aren't BorderLinePrimitive2D.
804 const drawinglayer::primitive2d::BorderLinePrimitive2D
* candidatePartialAsBorder
805 = dynamic_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D
*>(aCandidatePartial
.get());
806 if(candidatePartialAsBorder
)
808 for(auto& aCandidateRetval
: aRetval
)
810 const drawinglayer::primitive2d::BorderLinePrimitive2D
* candidateRetvalAsBorder
811 = dynamic_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D
*>(aCandidateRetval
.get());
812 if(candidateRetvalAsBorder
)
814 // try to merge by appending new data to existing data
815 const drawinglayer::primitive2d::Primitive2DReference
aMergeRetvalPartial(
816 drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
817 candidateRetvalAsBorder
,
818 candidatePartialAsBorder
));
820 if(aMergeRetvalPartial
.is())
822 // could append, replace existing data with merged data, done
823 aCandidateRetval
= aMergeRetvalPartial
;
828 // try to merge by appending existing data to new data
829 const drawinglayer::primitive2d::Primitive2DReference
aMergePartialRetval(
830 drawinglayer::primitive2d::tryMergeBorderLinePrimitive2D(
831 candidatePartialAsBorder
,
832 candidateRetvalAsBorder
));
834 if(aMergePartialRetval
.is())
836 // could append, replace existing data with merged data, done
837 aCandidateRetval
= aMergePartialRetval
;
847 // no merge after checking all existing data, append as new segment
848 aRetval
.append(aCandidatePartial
);
854 rContainer
.insert(rContainer
.end(), aRetval
.begin(), aRetval
.end());
857 SdrFrameBorderPrimitive2D::SdrFrameBorderPrimitive2D(
858 std::shared_ptr
<SdrFrameBorderDataVector
>& rFrameBorders
,
859 bool bForceToSingleDiscreteUnit
)
860 : BufferedDecompositionPrimitive2D(),
861 maFrameBorders(std::move(rFrameBorders
)),
862 mfMinimalNonZeroBorderWidth(0.0),
863 mfMinimalNonZeroBorderWidthUsedForDecompose(0.0),
864 mbForceToSingleDiscreteUnit(bForceToSingleDiscreteUnit
)
866 if(getFrameBorders() && doForceToSingleDiscreteUnit())
868 // detect used minimal non-zero partial border width
869 for(const auto& rCandidate
: *getFrameBorders())
871 mfMinimalNonZeroBorderWidth
= getMinimalNonZeroValue(
872 mfMinimalNonZeroBorderWidth
,
873 rCandidate
.getMinimalNonZeroBorderWidth());
878 bool SdrFrameBorderPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
880 if(BufferedDecompositionPrimitive2D::operator==(rPrimitive
))
882 const SdrFrameBorderPrimitive2D
& rCompare
= static_cast<const SdrFrameBorderPrimitive2D
&>(rPrimitive
);
884 return (getFrameBorders() == rCompare
.getFrameBorders()
885 || (getFrameBorders() && rCompare
.getFrameBorders()
886 && *getFrameBorders() == *rCompare
.getFrameBorders()))
887 && doForceToSingleDiscreteUnit() == rCompare
.doForceToSingleDiscreteUnit();
893 void SdrFrameBorderPrimitive2D::get2DDecomposition(
894 Primitive2DDecompositionVisitor
& rVisitor
,
895 const geometry::ViewInformation2D
& rViewInformation
) const
897 if(doForceToSingleDiscreteUnit())
899 // Get the current DiscreteUnit, look at X and Y and use the maximum
900 const basegfx::B2DVector
aDiscreteVector(rViewInformation
.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0));
901 double fDiscreteUnit(std::min(fabs(aDiscreteVector
.getX()), fabs(aDiscreteVector
.getY())));
903 if(fDiscreteUnit
<= mfMinimalNonZeroBorderWidth
)
905 // no need to use it, reset
909 if(fDiscreteUnit
!= mfMinimalNonZeroBorderWidthUsedForDecompose
)
911 // conditions of last local decomposition have changed, delete
913 if(!getBuffered2DDecomposition().empty())
915 const_cast< SdrFrameBorderPrimitive2D
* >(this)->setBuffered2DDecomposition(Primitive2DContainer());
918 // remember new conditions
919 const_cast< SdrFrameBorderPrimitive2D
* >(this)->mfMinimalNonZeroBorderWidthUsedForDecompose
= fDiscreteUnit
;
923 // call parent. This will call back ::create2DDecomposition above
924 // where mfMinimalNonZeroBorderWidthUsedForDecompose will be used
925 // when doForceToSingleDiscreteUnit() is true
926 BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor
, rViewInformation
);
930 ImplPrimitive2DIDBlock(SdrFrameBorderPrimitive2D
, PRIMITIVE2D_ID_SDRFRAMEBORDERTPRIMITIVE2D
)
932 } // end of namespace
934 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */