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 <drawinglayer/geometry/viewinformation2d.hxx>
21 #include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
22 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
23 #include <basegfx/polygon/b2dpolygon.hxx>
24 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
25 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
26 #include <rtl/math.hxx>
32 namespace drawinglayer::primitive2d
34 BorderLine::BorderLine(
35 const drawinglayer::attribute::LineAttribute
& rLineAttribute
,
40 : maLineAttribute(rLineAttribute
),
41 mfStartLeft(fStartLeft
),
42 mfStartRight(fStartRight
),
44 mfEndRight(fEndRight
),
49 BorderLine::BorderLine(
51 : maLineAttribute(basegfx::BColor(), fWidth
),
60 BorderLine::~BorderLine()
64 bool BorderLine::operator==(const BorderLine
& rBorderLine
) const
66 return getLineAttribute() == rBorderLine
.getLineAttribute()
67 && getStartLeft() == rBorderLine
.getStartLeft()
68 && getStartRight() == rBorderLine
.getStartRight()
69 && getEndLeft() == rBorderLine
.getEndLeft()
70 && getEndRight() == rBorderLine
.getEndRight()
71 && isGap() == rBorderLine
.isGap();
74 // helper to add a centered, maybe stroked line primitive to rContainer
75 static void addPolygonStrokePrimitive2D(
76 Primitive2DContainer
& rContainer
,
77 const basegfx::B2DPoint
& rStart
,
78 const basegfx::B2DPoint
& rEnd
,
79 const attribute::LineAttribute
& rLineAttribute
,
80 const attribute::StrokeAttribute
& rStrokeAttribute
)
82 basegfx::B2DPolygon aPolygon
;
84 aPolygon
.append(rStart
);
85 aPolygon
.append(rEnd
);
87 if (rStrokeAttribute
.isDefault())
90 new PolygonStrokePrimitive2D(
97 new PolygonStrokePrimitive2D(
104 double BorderLinePrimitive2D::getFullWidth() const
108 for(const auto& candidate
: maBorderLines
)
110 fRetval
+= candidate
.getLineAttribute().getWidth();
116 void BorderLinePrimitive2D::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
118 if (getStart().equal(getEnd()) || getBorderLines().empty())
121 // get data and vectors
122 basegfx::B2DVector
aVector(getEnd() - getStart());
124 const basegfx::B2DVector
aPerpendicular(basegfx::getPerpendicular(aVector
));
125 const double fFullWidth(getFullWidth());
126 double fOffset(fFullWidth
* -0.5);
128 for(const auto& candidate
: maBorderLines
)
130 const double fWidth(candidate
.getLineAttribute().getWidth());
132 if(!candidate
.isGap())
134 const basegfx::B2DVector
aDeltaY(aPerpendicular
* (fOffset
+ (fWidth
* 0.5)));
135 const basegfx::B2DPoint
aStart(getStart() + aDeltaY
);
136 const basegfx::B2DPoint
aEnd(getEnd() + aDeltaY
);
137 const bool bStartPerpendicular(rtl::math::approxEqual(candidate
.getStartLeft(), candidate
.getStartRight()));
138 const bool bEndPerpendicular(rtl::math::approxEqual(candidate
.getEndLeft(), candidate
.getEndRight()));
140 if(bStartPerpendicular
&& bEndPerpendicular
)
142 // start and end extends lead to an edge perpendicular to the line, so we can just use
143 // a PolygonStrokePrimitive2D for representation
144 addPolygonStrokePrimitive2D(
146 aStart
- (aVector
* candidate
.getStartLeft()),
147 aEnd
+ (aVector
* candidate
.getEndLeft()),
148 candidate
.getLineAttribute(),
149 getStrokeAttribute());
153 // start and/or end extensions lead to a lineStart/End that is *not*
154 // perpendicular to the line itself
155 if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
157 // without stroke, we can simply represent that using a filled polygon
158 const basegfx::B2DVector
aHalfLineOffset(aPerpendicular
* (candidate
.getLineAttribute().getWidth() * 0.5));
159 basegfx::B2DPolygon aPolygon
;
161 aPolygon
.append(aStart
- aHalfLineOffset
- (aVector
* candidate
.getStartLeft()));
162 aPolygon
.append(aEnd
- aHalfLineOffset
+ (aVector
* candidate
.getEndLeft()));
163 aPolygon
.append(aEnd
+ aHalfLineOffset
+ (aVector
* candidate
.getEndRight()));
164 aPolygon
.append(aStart
+ aHalfLineOffset
- (aVector
* candidate
.getStartRight()));
166 rContainer
.push_back(
167 new PolyPolygonColorPrimitive2D(
168 basegfx::B2DPolyPolygon(aPolygon
),
169 candidate
.getLineAttribute().getColor()));
173 // with stroke, we have a problem - a filled polygon would lose the
174 // stroke. Let's represent the start and/or end as triangles, the main
175 // line still as PolygonStrokePrimitive2D.
176 // Fill default line Start/End for stroke, so we need no adaptations in else paths
177 basegfx::B2DPoint
aStrokeStart(aStart
- (aVector
* candidate
.getStartLeft()));
178 basegfx::B2DPoint
aStrokeEnd(aEnd
+ (aVector
* candidate
.getEndLeft()));
179 const basegfx::B2DVector
aHalfLineOffset(aPerpendicular
* (candidate
.getLineAttribute().getWidth() * 0.5));
181 if(!bStartPerpendicular
)
183 const double fMin(std::min(candidate
.getStartLeft(), candidate
.getStartRight()));
184 const double fMax(std::max(candidate
.getStartLeft(), candidate
.getStartRight()));
185 basegfx::B2DPolygon aPolygon
;
187 // create a triangle with min/max values for LineStart and add
188 if(rtl::math::approxEqual(candidate
.getStartLeft(), fMax
))
190 aPolygon
.append(aStart
- aHalfLineOffset
- (aVector
* candidate
.getStartLeft()));
193 aPolygon
.append(aStart
- aHalfLineOffset
- (aVector
* fMin
));
194 aPolygon
.append(aStart
+ aHalfLineOffset
- (aVector
* fMin
));
196 if(rtl::math::approxEqual(candidate
.getStartRight(), fMax
))
198 aPolygon
.append(aStart
+ aHalfLineOffset
- (aVector
* candidate
.getStartRight()));
201 rContainer
.push_back(
202 new PolyPolygonColorPrimitive2D(
203 basegfx::B2DPolyPolygon(aPolygon
),
204 candidate
.getLineAttribute().getColor()));
206 // Adapt StrokeStart accordingly
207 aStrokeStart
= aStart
- (aVector
* fMin
);
210 if(!bEndPerpendicular
)
212 const double fMin(std::min(candidate
.getEndLeft(), candidate
.getEndRight()));
213 const double fMax(std::max(candidate
.getEndLeft(), candidate
.getEndRight()));
214 basegfx::B2DPolygon aPolygon
;
216 // create a triangle with min/max values for LineEnd and add
217 if(rtl::math::approxEqual(candidate
.getEndLeft(), fMax
))
219 aPolygon
.append(aEnd
- aHalfLineOffset
+ (aVector
* candidate
.getEndLeft()));
222 if(rtl::math::approxEqual(candidate
.getEndRight(), fMax
))
224 aPolygon
.append(aEnd
+ aHalfLineOffset
+ (aVector
* candidate
.getEndRight()));
227 aPolygon
.append(aEnd
+ aHalfLineOffset
+ (aVector
* fMin
));
228 aPolygon
.append(aEnd
- aHalfLineOffset
+ (aVector
* fMin
));
230 rContainer
.push_back(
231 new PolyPolygonColorPrimitive2D(
232 basegfx::B2DPolyPolygon(aPolygon
),
233 candidate
.getLineAttribute().getColor()));
235 // Adapt StrokeEnd accordingly
236 aStrokeEnd
= aEnd
+ (aVector
* fMin
);
239 addPolygonStrokePrimitive2D(
243 candidate
.getLineAttribute(),
244 getStrokeAttribute());
253 bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D
& rViewInformation
) const
255 if (!getStart().equal(getEnd()))
257 const basegfx::B2DHomMatrix
& rOTVT
= rViewInformation
.getObjectToViewTransformation();
258 const basegfx::B2DVector
aVector(rOTVT
* getEnd() - rOTVT
* getStart());
260 return basegfx::fTools::equalZero(aVector
.getX()) || basegfx::fTools::equalZero(aVector
.getY());
266 BorderLinePrimitive2D::BorderLinePrimitive2D(
267 const basegfx::B2DPoint
& rStart
,
268 const basegfx::B2DPoint
& rEnd
,
269 std::vector
< BorderLine
>&& rBorderLines
,
270 drawinglayer::attribute::StrokeAttribute aStrokeAttribute
)
273 maBorderLines(std::move(rBorderLines
)),
274 maStrokeAttribute(std::move(aStrokeAttribute
))
278 bool BorderLinePrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
280 if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive
))
283 const BorderLinePrimitive2D
& rCompare
= static_cast<const BorderLinePrimitive2D
&>(rPrimitive
);
285 if (getStart() == rCompare
.getStart()
286 && getEnd() == rCompare
.getEnd()
287 && getStrokeAttribute() == rCompare
.getStrokeAttribute())
289 if (getBorderLines().size() == rCompare
.getBorderLines().size())
291 for (size_t a(0); a
< getBorderLines().size(); a
++)
293 if (!(getBorderLines()[a
] == rCompare
.getBorderLines()[a
]))
305 sal_uInt32
BorderLinePrimitive2D::getPrimitive2DID() const
307 return PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D
;
310 Primitive2DReference
tryMergeBorderLinePrimitive2D(
311 const BorderLinePrimitive2D
* pCandidateA
,
312 const BorderLinePrimitive2D
* pCandidateB
)
317 // start of candidate has to match end of this
318 if(!pCandidateA
->getEnd().equal(pCandidateB
->getStart()))
320 return Primitive2DReference();
323 // candidate A needs a length
324 if(pCandidateA
->getStart().equal(pCandidateA
->getEnd()))
326 return Primitive2DReference();
329 // candidate B needs a length
330 if(pCandidateB
->getStart().equal(pCandidateB
->getEnd()))
332 return Primitive2DReference();
335 // StrokeAttribute has to be equal
336 if(!(pCandidateA
->getStrokeAttribute() == pCandidateB
->getStrokeAttribute()))
338 return Primitive2DReference();
341 // direction has to be equal -> cross product == 0.0
342 const basegfx::B2DVector
aVT(pCandidateA
->getEnd() - pCandidateA
->getStart());
343 const basegfx::B2DVector
aVC(pCandidateB
->getEnd() - pCandidateB
->getStart());
344 if(!rtl::math::approxEqual(0.0, aVC
.cross(aVT
)))
346 return Primitive2DReference();
349 // number BorderLines has to be equal
350 const size_t count(pCandidateA
->getBorderLines().size());
351 if(count
!= pCandidateB
->getBorderLines().size())
353 return Primitive2DReference();
356 for(size_t a(0); a
< count
; a
++)
358 const BorderLine
& rBT(pCandidateA
->getBorderLines()[a
]);
359 const BorderLine
& rBC(pCandidateB
->getBorderLines()[a
]);
361 // LineAttribute has to be the same
362 if(!(rBC
.getLineAttribute() == rBT
.getLineAttribute()))
364 return Primitive2DReference();
367 // isGap has to be the same
368 if(rBC
.isGap() != rBT
.isGap())
370 return Primitive2DReference();
375 // when gap, width has to be equal
376 if(!rtl::math::approxEqual(rBT
.getLineAttribute().getWidth(), rBC
.getLineAttribute().getWidth()))
378 return Primitive2DReference();
383 // when not gap, the line extends have at least reach to the center ( > 0.0),
384 // else there is an extend usage. When > 0.0 they just overlap, no problem
385 if(rBT
.getEndLeft() >= 0.0
386 && rBT
.getEndRight() >= 0.0
387 && rBC
.getStartLeft() >= 0.0
388 && rBC
.getStartRight() >= 0.0)
394 return Primitive2DReference();
399 // all conditions met, create merged primitive
400 std::vector
< BorderLine
> aMergedBorderLines
;
402 for(size_t a(0); a
< count
; a
++)
404 const BorderLine
& rBT(pCandidateA
->getBorderLines()[a
]);
405 const BorderLine
& rBC(pCandidateB
->getBorderLines()[a
]);
409 aMergedBorderLines
.push_back(rBT
);
413 aMergedBorderLines
.push_back(
415 rBT
.getLineAttribute(),
416 rBT
.getStartLeft(), rBT
.getStartRight(),
417 rBC
.getEndLeft(), rBC
.getEndRight()));
421 return Primitive2DReference(
422 new BorderLinePrimitive2D(
423 pCandidateA
->getStart(),
424 pCandidateB
->getEnd(),
425 std::move(aMergedBorderLines
),
426 pCandidateA
->getStrokeAttribute()));
429 } // end of namespace
431 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */