calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / drawinglayer / source / primitive2d / borderlineprimitive2d.cxx
blob45536a53124b062c7cd779cda277b85d4d603028
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 <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>
28 #include <algorithm>
29 #include <utility>
32 namespace drawinglayer::primitive2d
34 BorderLine::BorderLine(
35 const drawinglayer::attribute::LineAttribute& rLineAttribute,
36 double fStartLeft,
37 double fStartRight,
38 double fEndLeft,
39 double fEndRight)
40 : maLineAttribute(rLineAttribute),
41 mfStartLeft(fStartLeft),
42 mfStartRight(fStartRight),
43 mfEndLeft(fEndLeft),
44 mfEndRight(fEndRight),
45 mbIsGap(false)
49 BorderLine::BorderLine(
50 double fWidth)
51 : maLineAttribute(basegfx::BColor(), fWidth),
52 mfStartLeft(0.0),
53 mfStartRight(0.0),
54 mfEndLeft(0.0),
55 mfEndRight(0.0),
56 mbIsGap(true)
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())
89 rContainer.push_back(
90 new PolygonStrokePrimitive2D(
91 std::move(aPolygon),
92 rLineAttribute));
94 else
96 rContainer.push_back(
97 new PolygonStrokePrimitive2D(
98 std::move(aPolygon),
99 rLineAttribute,
100 rStrokeAttribute));
104 double BorderLinePrimitive2D::getFullWidth() const
106 double fRetval(0.0);
108 for(const auto& candidate : maBorderLines)
110 fRetval += candidate.getLineAttribute().getWidth();
113 return fRetval;
116 void BorderLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
118 if (getStart().equal(getEnd()) || getBorderLines().empty())
119 return;
121 // get data and vectors
122 basegfx::B2DVector aVector(getEnd() - getStart());
123 aVector.normalize();
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(
145 rContainer,
146 aStart - (aVector * candidate.getStartLeft()),
147 aEnd + (aVector * candidate.getEndLeft()),
148 candidate.getLineAttribute(),
149 getStrokeAttribute());
151 else
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()));
171 else
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(
240 rContainer,
241 aStrokeStart,
242 aStrokeEnd,
243 candidate.getLineAttribute(),
244 getStrokeAttribute());
249 fOffset += fWidth;
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());
263 return false;
266 BorderLinePrimitive2D::BorderLinePrimitive2D(
267 const basegfx::B2DPoint& rStart,
268 const basegfx::B2DPoint& rEnd,
269 std::vector< BorderLine >&& rBorderLines,
270 drawinglayer::attribute::StrokeAttribute aStrokeAttribute)
271 : maStart(rStart),
272 maEnd(rEnd),
273 maBorderLines(std::move(rBorderLines)),
274 maStrokeAttribute(std::move(aStrokeAttribute))
278 bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
280 if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive))
281 return false;
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]))
295 return false;
301 return false;
304 // provide unique ID
305 sal_uInt32 BorderLinePrimitive2D::getPrimitive2DID() const
307 return PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D;
310 Primitive2DReference tryMergeBorderLinePrimitive2D(
311 const BorderLinePrimitive2D* pCandidateA,
312 const BorderLinePrimitive2D* pCandidateB)
314 assert(pCandidateA);
315 assert(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();
373 if(rBT.isGap())
375 // when gap, width has to be equal
376 if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth()))
378 return Primitive2DReference();
381 else
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)
390 // okay
392 else
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]);
407 if(rBT.isGap())
409 aMergedBorderLines.push_back(rBT);
411 else
413 aMergedBorderLines.push_back(
414 BorderLine(
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: */