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 <drawinglayer/primitive2d/groupprimitive2d.hxx>
27 #include <rtl/math.hxx>
33 namespace drawinglayer::primitive2d
35 BorderLine::BorderLine(
36 const drawinglayer::attribute::LineAttribute
& rLineAttribute
,
41 : maLineAttribute(rLineAttribute
),
42 mfStartLeft(fStartLeft
),
43 mfStartRight(fStartRight
),
45 mfEndRight(fEndRight
),
50 BorderLine::BorderLine(
52 : maLineAttribute(basegfx::BColor(), fWidth
),
61 BorderLine::~BorderLine()
65 bool BorderLine::operator==(const BorderLine
& rBorderLine
) const
67 return getLineAttribute() == rBorderLine
.getLineAttribute()
68 && getStartLeft() == rBorderLine
.getStartLeft()
69 && getStartRight() == rBorderLine
.getStartRight()
70 && getEndLeft() == rBorderLine
.getEndLeft()
71 && getEndRight() == rBorderLine
.getEndRight()
72 && isGap() == rBorderLine
.isGap();
75 // helper to add a centered, maybe stroked line primitive to rContainer
76 static void addPolygonStrokePrimitive2D(
77 Primitive2DContainer
& rContainer
,
78 const basegfx::B2DPoint
& rStart
,
79 const basegfx::B2DPoint
& rEnd
,
80 const attribute::LineAttribute
& rLineAttribute
,
81 const attribute::StrokeAttribute
& rStrokeAttribute
)
83 basegfx::B2DPolygon aPolygon
;
85 aPolygon
.append(rStart
);
86 aPolygon
.append(rEnd
);
88 if (rStrokeAttribute
.isDefault())
91 new PolygonStrokePrimitive2D(
98 new PolygonStrokePrimitive2D(
105 double BorderLinePrimitive2D::getFullWidth() const
109 for(const auto& candidate
: maBorderLines
)
111 fRetval
+= candidate
.getLineAttribute().getWidth();
117 Primitive2DReference
BorderLinePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D
& /*rViewInformation*/) const
119 if (getStart().equal(getEnd()) || getBorderLines().empty())
122 // get data and vectors
123 basegfx::B2DVector
aVector(getEnd() - getStart());
125 const basegfx::B2DVector
aPerpendicular(basegfx::getPerpendicular(aVector
));
126 const double fFullWidth(getFullWidth());
127 double fOffset(fFullWidth
* -0.5);
129 Primitive2DContainer aContainer
;
130 for(const auto& candidate
: maBorderLines
)
132 const double fWidth(candidate
.getLineAttribute().getWidth());
134 if(!candidate
.isGap())
136 const basegfx::B2DVector
aDeltaY(aPerpendicular
* (fOffset
+ (fWidth
* 0.5)));
137 const basegfx::B2DPoint
aStart(getStart() + aDeltaY
);
138 const basegfx::B2DPoint
aEnd(getEnd() + aDeltaY
);
139 const bool bStartPerpendicular(rtl::math::approxEqual(candidate
.getStartLeft(), candidate
.getStartRight()));
140 const bool bEndPerpendicular(rtl::math::approxEqual(candidate
.getEndLeft(), candidate
.getEndRight()));
142 if(bStartPerpendicular
&& bEndPerpendicular
)
144 // start and end extends lead to an edge perpendicular to the line, so we can just use
145 // a PolygonStrokePrimitive2D for representation
146 addPolygonStrokePrimitive2D(
148 aStart
- (aVector
* candidate
.getStartLeft()),
149 aEnd
+ (aVector
* candidate
.getEndLeft()),
150 candidate
.getLineAttribute(),
151 getStrokeAttribute());
155 // start and/or end extensions lead to a lineStart/End that is *not*
156 // perpendicular to the line itself
157 if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
159 // without stroke, we can simply represent that using a filled polygon
160 const basegfx::B2DVector
aHalfLineOffset(aPerpendicular
* (candidate
.getLineAttribute().getWidth() * 0.5));
161 basegfx::B2DPolygon aPolygon
;
163 aPolygon
.append(aStart
- aHalfLineOffset
- (aVector
* candidate
.getStartLeft()));
164 aPolygon
.append(aEnd
- aHalfLineOffset
+ (aVector
* candidate
.getEndLeft()));
165 aPolygon
.append(aEnd
+ aHalfLineOffset
+ (aVector
* candidate
.getEndRight()));
166 aPolygon
.append(aStart
+ aHalfLineOffset
- (aVector
* candidate
.getStartRight()));
168 aContainer
.push_back(
169 new PolyPolygonColorPrimitive2D(
170 basegfx::B2DPolyPolygon(aPolygon
),
171 candidate
.getLineAttribute().getColor()));
175 // with stroke, we have a problem - a filled polygon would lose the
176 // stroke. Let's represent the start and/or end as triangles, the main
177 // line still as PolygonStrokePrimitive2D.
178 // Fill default line Start/End for stroke, so we need no adaptations in else paths
179 basegfx::B2DPoint
aStrokeStart(aStart
- (aVector
* candidate
.getStartLeft()));
180 basegfx::B2DPoint
aStrokeEnd(aEnd
+ (aVector
* candidate
.getEndLeft()));
181 const basegfx::B2DVector
aHalfLineOffset(aPerpendicular
* (candidate
.getLineAttribute().getWidth() * 0.5));
183 if(!bStartPerpendicular
)
185 const double fMin(std::min(candidate
.getStartLeft(), candidate
.getStartRight()));
186 const double fMax(std::max(candidate
.getStartLeft(), candidate
.getStartRight()));
187 basegfx::B2DPolygon aPolygon
;
189 // create a triangle with min/max values for LineStart and add
190 if(rtl::math::approxEqual(candidate
.getStartLeft(), fMax
))
192 aPolygon
.append(aStart
- aHalfLineOffset
- (aVector
* candidate
.getStartLeft()));
195 aPolygon
.append(aStart
- aHalfLineOffset
- (aVector
* fMin
));
196 aPolygon
.append(aStart
+ aHalfLineOffset
- (aVector
* fMin
));
198 if(rtl::math::approxEqual(candidate
.getStartRight(), fMax
))
200 aPolygon
.append(aStart
+ aHalfLineOffset
- (aVector
* candidate
.getStartRight()));
203 aContainer
.push_back(
204 new PolyPolygonColorPrimitive2D(
205 basegfx::B2DPolyPolygon(aPolygon
),
206 candidate
.getLineAttribute().getColor()));
208 // Adapt StrokeStart accordingly
209 aStrokeStart
= aStart
- (aVector
* fMin
);
212 if(!bEndPerpendicular
)
214 const double fMin(std::min(candidate
.getEndLeft(), candidate
.getEndRight()));
215 const double fMax(std::max(candidate
.getEndLeft(), candidate
.getEndRight()));
216 basegfx::B2DPolygon aPolygon
;
218 // create a triangle with min/max values for LineEnd and add
219 if(rtl::math::approxEqual(candidate
.getEndLeft(), fMax
))
221 aPolygon
.append(aEnd
- aHalfLineOffset
+ (aVector
* candidate
.getEndLeft()));
224 if(rtl::math::approxEqual(candidate
.getEndRight(), fMax
))
226 aPolygon
.append(aEnd
+ aHalfLineOffset
+ (aVector
* candidate
.getEndRight()));
229 aPolygon
.append(aEnd
+ aHalfLineOffset
+ (aVector
* fMin
));
230 aPolygon
.append(aEnd
- aHalfLineOffset
+ (aVector
* fMin
));
232 aContainer
.push_back(
233 new PolyPolygonColorPrimitive2D(
234 basegfx::B2DPolyPolygon(aPolygon
),
235 candidate
.getLineAttribute().getColor()));
237 // Adapt StrokeEnd accordingly
238 aStrokeEnd
= aEnd
+ (aVector
* fMin
);
241 addPolygonStrokePrimitive2D(
245 candidate
.getLineAttribute(),
246 getStrokeAttribute());
253 return new GroupPrimitive2D(std::move(aContainer
));
256 bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D
& rViewInformation
) const
258 if (!getStart().equal(getEnd()))
260 const basegfx::B2DHomMatrix
& rOTVT
= rViewInformation
.getObjectToViewTransformation();
261 const basegfx::B2DVector
aVector(rOTVT
* getEnd() - rOTVT
* getStart());
263 return basegfx::fTools::equalZero(aVector
.getX()) || basegfx::fTools::equalZero(aVector
.getY());
269 BorderLinePrimitive2D::BorderLinePrimitive2D(
270 const basegfx::B2DPoint
& rStart
,
271 const basegfx::B2DPoint
& rEnd
,
272 std::vector
< BorderLine
>&& rBorderLines
,
273 drawinglayer::attribute::StrokeAttribute aStrokeAttribute
)
276 maBorderLines(std::move(rBorderLines
)),
277 maStrokeAttribute(std::move(aStrokeAttribute
))
281 bool BorderLinePrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
283 if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive
))
286 const BorderLinePrimitive2D
& rCompare
= static_cast<const BorderLinePrimitive2D
&>(rPrimitive
);
288 return (getStart() == rCompare
.getStart()
289 && getEnd() == rCompare
.getEnd()
290 && getStrokeAttribute() == rCompare
.getStrokeAttribute()
291 && getBorderLines() == rCompare
.getBorderLines());
295 sal_uInt32
BorderLinePrimitive2D::getPrimitive2DID() const
297 return PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D
;
300 Primitive2DReference
tryMergeBorderLinePrimitive2D(
301 const BorderLinePrimitive2D
* pCandidateA
,
302 const BorderLinePrimitive2D
* pCandidateB
)
307 // start of candidate has to match end of this
308 if(!pCandidateA
->getEnd().equal(pCandidateB
->getStart()))
310 return Primitive2DReference();
313 // candidate A needs a length
314 if(pCandidateA
->getStart().equal(pCandidateA
->getEnd()))
316 return Primitive2DReference();
319 // candidate B needs a length
320 if(pCandidateB
->getStart().equal(pCandidateB
->getEnd()))
322 return Primitive2DReference();
325 // StrokeAttribute has to be equal
326 if(!(pCandidateA
->getStrokeAttribute() == pCandidateB
->getStrokeAttribute()))
328 return Primitive2DReference();
331 // direction has to be equal -> cross product == 0.0
332 const basegfx::B2DVector
aVT(pCandidateA
->getEnd() - pCandidateA
->getStart());
333 const basegfx::B2DVector
aVC(pCandidateB
->getEnd() - pCandidateB
->getStart());
334 if(aVC
.cross(aVT
) != 0)
336 return Primitive2DReference();
339 // number BorderLines has to be equal
340 const size_t count(pCandidateA
->getBorderLines().size());
341 if(count
!= pCandidateB
->getBorderLines().size())
343 return Primitive2DReference();
346 for(size_t a(0); a
< count
; a
++)
348 const BorderLine
& rBT(pCandidateA
->getBorderLines()[a
]);
349 const BorderLine
& rBC(pCandidateB
->getBorderLines()[a
]);
351 // LineAttribute has to be the same
352 if(!(rBC
.getLineAttribute() == rBT
.getLineAttribute()))
354 return Primitive2DReference();
357 // isGap has to be the same
358 if(rBC
.isGap() != rBT
.isGap())
360 return Primitive2DReference();
365 // when gap, width has to be equal
366 if(!rtl::math::approxEqual(rBT
.getLineAttribute().getWidth(), rBC
.getLineAttribute().getWidth()))
368 return Primitive2DReference();
373 // when not gap, the line extends have at least reach to the center ( > 0.0),
374 // else there is an extend usage. When > 0.0 they just overlap, no problem
375 if(rBT
.getEndLeft() >= 0.0
376 && rBT
.getEndRight() >= 0.0
377 && rBC
.getStartLeft() >= 0.0
378 && rBC
.getStartRight() >= 0.0)
384 return Primitive2DReference();
389 // all conditions met, create merged primitive
390 std::vector
< BorderLine
> aMergedBorderLines
;
392 for(size_t a(0); a
< count
; a
++)
394 const BorderLine
& rBT(pCandidateA
->getBorderLines()[a
]);
395 const BorderLine
& rBC(pCandidateB
->getBorderLines()[a
]);
399 aMergedBorderLines
.push_back(rBT
);
403 aMergedBorderLines
.push_back(
405 rBT
.getLineAttribute(),
406 rBT
.getStartLeft(), rBT
.getStartRight(),
407 rBC
.getEndLeft(), rBC
.getEndRight()));
412 new BorderLinePrimitive2D(
413 pCandidateA
->getStart(),
414 pCandidateB
->getEnd(),
415 std::move(aMergedBorderLines
),
416 pCandidateA
->getStrokeAttribute());
419 } // end of namespace
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */