Version 7.1.7.1, tag libreoffice-7.1.7.1
[LibreOffice.git] / svx / source / sdr / primitive2d / sdrframeborderprimitive2d.cxx
blob76af6c23b68e3137ae8fb1e458d0978d87945a66
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
27 namespace
29 double snapToDiscreteUnit(
30 double fValue,
31 double fMinimalDiscreteUnit)
33 if(0.0 != fValue)
35 fValue = std::max(fValue, fMinimalDiscreteUnit);
38 return fValue;
41 class StyleVectorCombination
43 private:
44 struct OffsetAndHalfWidthAndColor
46 double mfOffset;
47 double mfHalfWidth;
48 Color maColor;
50 OffsetAndHalfWidthAndColor(double offset, double halfWidth, Color color) :
51 mfOffset(offset),
52 mfHalfWidth(halfWidth),
53 maColor(color)
57 double mfRefModeOffset;
58 basegfx::B2DVector maB2DVector;
59 double mfAngle;
60 std::vector< OffsetAndHalfWidthAndColor > maOffsets;
62 public:
63 StyleVectorCombination(
64 const svx::frame::Style& rStyle,
65 const basegfx::B2DVector& rB2DVector,
66 double fAngle,
67 bool bMirrored,
68 const Color* pForceColor,
69 double fMinimalDiscreteUnit)
70 : mfRefModeOffset(0.0),
71 maB2DVector(rB2DVector),
72 mfAngle(fAngle),
73 maOffsets()
75 if (!rStyle.IsUsed())
76 return;
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
91 // reasons.
92 const double fStyleWidth(fPrim + fDist + fSecn);
94 if(bMirrored)
96 switch(aRefMode)
98 case svx::frame::RefMode::Begin: aRefMode = svx::frame::RefMode::End; break;
99 case svx::frame::RefMode::End: aRefMode = svx::frame::RefMode::Begin; break;
100 default: break;
103 if(bSecnUsed)
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;
126 if (bSecnUsed)
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);
140 maOffsets.push_back(
141 OffsetAndHalfWidthAndColor(
142 (a + b) * 0.5,
143 fPrim * 0.5,
144 nullptr != pForceColor ? *pForceColor : aPrim));
146 maOffsets.push_back(
147 OffsetAndHalfWidthAndColor(
148 (b + c) * 0.5,
149 fDist * 0.5,
150 rStyle.UseGapColor()
151 ? (nullptr != pForceColor ? *pForceColor : rStyle.GetColorGap())
152 : COL_TRANSPARENT));
154 maOffsets.push_back(
155 OffsetAndHalfWidthAndColor(
156 (c + d) * 0.5,
157 fSecn * 0.5,
158 nullptr != pForceColor ? *pForceColor : aSecn));
161 else
163 // one line used, push two values, from outer to inner
164 if(0xff != rStyle.GetColorPrim().GetTransparency())
166 maOffsets.push_back(
167 OffsetAndHalfWidthAndColor(
168 mfRefModeOffset,
169 fPrim * 0.5,
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())
184 return;
185 const OffsetAndHalfWidthAndColor& rCandidate(maOffsets[nIndex]);
186 rfOffset = rCandidate.mfOffset;
187 rfHalfWidth = rCandidate.mfHalfWidth;
188 rColor = rCandidate.maColor;
192 class StyleVectorTable
194 private:
195 std::vector< StyleVectorCombination > maEntries;
197 public:
198 StyleVectorTable()
199 : maEntries()
203 void add(
204 const svx::frame::Style& rStyle,
205 const basegfx::B2DVector& rMyVector,
206 const basegfx::B2DVector& rOtherVector,
207 bool bMirrored,
208 double fMinimalDiscreteUnit)
210 if(!rStyle.IsUsed() || basegfx::areParallel(rMyVector, rOtherVector))
211 return;
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(
217 rStyle,
218 rOtherVector,
219 fAngle,
220 bMirrored,
221 nullptr,
222 fMinimalDiscreteUnit);
225 void sort()
227 // sort inverse from highest to lowest
228 std::sort(
229 maEntries.begin(),
230 maEntries.end(),
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; }
239 struct CutSet
241 double mfOLML;
242 double mfORML;
243 double mfOLMR;
244 double mfORMR;
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);
255 return fA < fB;
258 double getSum() const { return mfOLML + mfORML + mfOLMR + mfORMR; }
261 void getCutSet(
262 CutSet& rCutSet,
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(
271 rLeft,
273 rOtherLeft,
274 rOtherX,
275 CutFlagValue::LINE,
276 &rCutSet.mfOLML);
278 basegfx::utils::findCut(
279 rRight,
281 rOtherLeft,
282 rOtherX,
283 CutFlagValue::LINE,
284 &rCutSet.mfOLMR);
286 basegfx::utils::findCut(
287 rLeft,
289 rOtherRight,
290 rOtherX,
291 CutFlagValue::LINE,
292 &rCutSet.mfORML);
294 basegfx::utils::findCut(
295 rRight,
297 rOtherRight,
298 rOtherX,
299 CutFlagValue::LINE,
300 &rCutSet.mfORMR);
303 struct ExtendSet
305 double mfExtLeft;
306 double mfExtRight;
308 ExtendSet() : mfExtLeft(0.0), mfExtRight(0.0) {}
311 void getExtends(
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()))
319 return;
321 const size_t nOffsetA(rCombination.size());
323 if(1 == nOffsetA)
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)));
349 CutSet aNewCutSet;
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());
361 if(1 != nNumCutSets)
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)
385 // get minimum
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);
399 else
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)));
428 CutSet aCutSet;
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
442 // t t max
443 // t f min
444 // f f max
445 // f t min
446 const bool bMax(bUpper == bOtherUpper);
447 size_t nBaseIndex(0);
448 const size_t nNumCutSets(aCutSets.size());
450 if(bMax)
452 // access at end
453 nBaseIndex = nNumCutSets - 1 - (bUpper ? nVisEdgeUp : nVisEdgeDn);
455 else
457 // access at start
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);
469 if(bUpper)
471 nVisEdgeUp++;
473 else
475 nVisEdgeDn++;
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
502 * data creation.
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
511 * account.
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(
529 rBorder,
531 0.0,
532 false,
533 pForceColor,
534 fMinimalDiscreteUnit);
536 if(aCombination.empty())
537 return;
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);
546 if(bHasStartStyles)
548 // create extends for line starts, use given point/vector and offsets
549 getExtends(aExtendSetStart, rOrigin, aCombination, aPerpendX, rStartStyleVectorTable.getEntries());
552 if(bHasEndStyles)
554 // Create extends for line ends, create inverse point/vector and inverse offsets.
555 const StyleVectorCombination aMirroredCombination(
556 rBorder,
557 -rX,
558 0.0,
559 true,
560 pForceColor,
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++)
574 Color aMyColor;
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));
587 else
589 aBorderlines.push_back(
590 drawinglayer::primitive2d::BorderLine(
591 drawinglayer::attribute::LineAttribute(
592 aMyColor.getBColor(),
593 fMyHalfWidth * 2.0),
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()));
606 rTarget.append(
607 drawinglayer::primitive2d::Primitive2DReference(
608 new drawinglayer::primitive2d::BorderLinePrimitive2D(
609 aStart,
610 aStart + rX,
611 aBorderlines,
612 aStrokeAttribute)));
615 double getMinimalNonZeroValue(double fCurrent, double fNew)
617 if(0.0 != fNew)
619 if(0.0 != fCurrent)
621 fCurrent = std::min(fNew, fCurrent);
623 else
625 fCurrent = fNew;
629 return fCurrent;
632 double getMinimalNonZeroBorderWidthFromStyle(double fCurrent, const svx::frame::Style& rStyle)
634 if(rStyle.IsUsed())
636 fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Prim());
637 fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Dist());
638 fCurrent = getMinimalNonZeroValue(fCurrent, rStyle.Secn());
641 return fCurrent;
645 namespace drawinglayer::primitive2d
647 SdrFrameBorderData::SdrConnectStyleData::SdrConnectStyleData(
648 const svx::frame::Style& rStyle,
649 const basegfx::B2DVector& rNormalizedPerpendicular,
650 bool bStyleMirrored)
651 : maStyle(rStyle),
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)
669 : maOrigin(rOrigin),
670 maX(rX),
671 maStyle(rStyle),
672 maColor(nullptr != pForceColor ? *pForceColor : Color()),
673 mbForceColor(nullptr != pForceColor),
674 maStart(),
675 maEnd()
679 void SdrFrameBorderData::addSdrConnectStyleData(
680 bool bStart,
681 const svx::frame::Style& rStyle,
682 const basegfx::B2DVector& rNormalizedPerpendicular,
683 bool bStyleMirrored)
685 if(rStyle.IsUsed())
687 if(bStart)
689 maStart.emplace_back(rStyle, rNormalizedPerpendicular, bStyleMirrored);
691 else
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)
708 aStartVector.add(
709 rStart.getStyle(),
710 maX,
711 rStart.getNormalizedPerpendicular(),
712 rStart.getStyleMirrored(),
713 fMinimalDiscreteUnit);
716 for(const auto& rEnd : maEnd)
718 aEndVector.add(
719 rEnd.getStyle(),
720 aAxis,
721 rEnd.getNormalizedPerpendicular(),
722 rEnd.getStyleMirrored(),
723 fMinimalDiscreteUnit);
726 aStartVector.sort();
727 aEndVector.sort();
729 CreateBorderPrimitives(
730 rContainer,
731 maOrigin,
732 maX,
733 maStyle,
734 aStartVector,
735 aEndVector,
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());
754 return fRetval;
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())
776 return;
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
785 : 0.0);
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(
795 aPartial,
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;
824 bDidMerge = true;
825 break;
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;
838 bDidMerge = true;
839 break;
845 if(!bDidMerge)
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();
890 return false;
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
906 fDiscreteUnit = 0.0;
909 if(fDiscreteUnit != mfMinimalNonZeroBorderWidthUsedForDecompose)
911 // conditions of last local decomposition have changed, delete
912 // possible content
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);
929 // provide unique ID
930 ImplPrimitive2DIDBlock(SdrFrameBorderPrimitive2D, PRIMITIVE2D_ID_SDRFRAMEBORDERTPRIMITIVE2D)
932 } // end of namespace
934 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */