LibreOffice Module drawinglayer (master) 1
borderlineprimitive2d.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
26#include <rtl/math.hxx>
27
28#include <algorithm>
29#include <utility>
30
31
33{
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)
46 {
47 }
48
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)
57 {
58 }
59
61 {
62 }
63
64 bool BorderLine::operator==(const BorderLine& rBorderLine) const
65 {
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();
72 }
73
74 // helper to add a centered, maybe stroked line primitive to rContainer
76 Primitive2DContainer& rContainer,
77 const basegfx::B2DPoint& rStart,
78 const basegfx::B2DPoint& rEnd,
79 const attribute::LineAttribute& rLineAttribute,
80 const attribute::StrokeAttribute& rStrokeAttribute)
81 {
82 basegfx::B2DPolygon aPolygon;
83
84 aPolygon.append(rStart);
85 aPolygon.append(rEnd);
86
87 if (rStrokeAttribute.isDefault())
88 {
89 rContainer.push_back(
91 std::move(aPolygon),
92 rLineAttribute));
93 }
94 else
95 {
96 rContainer.push_back(
98 std::move(aPolygon),
99 rLineAttribute,
100 rStrokeAttribute));
101 }
102 }
103
105 {
106 double fRetval(0.0);
107
108 for(const auto& candidate : maBorderLines)
109 {
110 fRetval += candidate.getLineAttribute().getWidth();
111 }
112
113 return fRetval;
114 }
115
117 {
118 if (getStart().equal(getEnd()) || getBorderLines().empty())
119 return;
120
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);
127
128 for(const auto& candidate : maBorderLines)
129 {
130 const double fWidth(candidate.getLineAttribute().getWidth());
131
132 if(!candidate.isGap())
133 {
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()));
139
140 if(bStartPerpendicular && bEndPerpendicular)
141 {
142 // start and end extends lead to an edge perpendicular to the line, so we can just use
143 // a PolygonStrokePrimitive2D for representation
145 rContainer,
146 aStart - (aVector * candidate.getStartLeft()),
147 aEnd + (aVector * candidate.getEndLeft()),
148 candidate.getLineAttribute(),
150 }
151 else
152 {
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())
156 {
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;
160
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()));
165
166 rContainer.push_back(
168 basegfx::B2DPolyPolygon(aPolygon),
169 candidate.getLineAttribute().getColor()));
170 }
171 else
172 {
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));
180
181 if(!bStartPerpendicular)
182 {
183 const double fMin(std::min(candidate.getStartLeft(), candidate.getStartRight()));
184 const double fMax(std::max(candidate.getStartLeft(), candidate.getStartRight()));
185 basegfx::B2DPolygon aPolygon;
186
187 // create a triangle with min/max values for LineStart and add
188 if(rtl::math::approxEqual(candidate.getStartLeft(), fMax))
189 {
190 aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft()));
191 }
192
193 aPolygon.append(aStart - aHalfLineOffset - (aVector * fMin));
194 aPolygon.append(aStart + aHalfLineOffset - (aVector * fMin));
195
196 if(rtl::math::approxEqual(candidate.getStartRight(), fMax))
197 {
198 aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight()));
199 }
200
201 rContainer.push_back(
203 basegfx::B2DPolyPolygon(aPolygon),
204 candidate.getLineAttribute().getColor()));
205
206 // Adapt StrokeStart accordingly
207 aStrokeStart = aStart - (aVector * fMin);
208 }
209
210 if(!bEndPerpendicular)
211 {
212 const double fMin(std::min(candidate.getEndLeft(), candidate.getEndRight()));
213 const double fMax(std::max(candidate.getEndLeft(), candidate.getEndRight()));
214 basegfx::B2DPolygon aPolygon;
215
216 // create a triangle with min/max values for LineEnd and add
217 if(rtl::math::approxEqual(candidate.getEndLeft(), fMax))
218 {
219 aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft()));
220 }
221
222 if(rtl::math::approxEqual(candidate.getEndRight(), fMax))
223 {
224 aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight()));
225 }
226
227 aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin));
228 aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin));
229
230 rContainer.push_back(
232 basegfx::B2DPolyPolygon(aPolygon),
233 candidate.getLineAttribute().getColor()));
234
235 // Adapt StrokeEnd accordingly
236 aStrokeEnd = aEnd + (aVector * fMin);
237 }
238
240 rContainer,
241 aStrokeStart,
242 aStrokeEnd,
243 candidate.getLineAttribute(),
245 }
246 }
247 }
248
249 fOffset += fWidth;
250 }
251 }
252
254 {
255 if (!getStart().equal(getEnd()))
256 {
257 const basegfx::B2DHomMatrix& rOTVT = rViewInformation.getObjectToViewTransformation();
258 const basegfx::B2DVector aVector(rOTVT * getEnd() - rOTVT * getStart());
259
260 return basegfx::fTools::equalZero(aVector.getX()) || basegfx::fTools::equalZero(aVector.getY());
261 }
262
263 return false;
264 }
265
267 const basegfx::B2DPoint& rStart,
268 const basegfx::B2DPoint& rEnd,
269 std::vector< BorderLine >&& rBorderLines,
271 : maStart(rStart),
272 maEnd(rEnd),
273 maBorderLines(std::move(rBorderLines)),
274 maStrokeAttribute(std::move(aStrokeAttribute))
275 {
276 }
277
279 {
280 if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive))
281 return false;
282
283 const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive);
284
285 return (getStart() == rCompare.getStart()
286 && getEnd() == rCompare.getEnd()
287 && getStrokeAttribute() == rCompare.getStrokeAttribute()
288 && getBorderLines() == rCompare.getBorderLines());
289 }
290
291 // provide unique ID
293 {
295 }
296
298 const BorderLinePrimitive2D* pCandidateA,
299 const BorderLinePrimitive2D* pCandidateB)
300 {
301 assert(pCandidateA);
302 assert(pCandidateB);
303
304 // start of candidate has to match end of this
305 if(!pCandidateA->getEnd().equal(pCandidateB->getStart()))
306 {
307 return Primitive2DReference();
308 }
309
310 // candidate A needs a length
311 if(pCandidateA->getStart().equal(pCandidateA->getEnd()))
312 {
313 return Primitive2DReference();
314 }
315
316 // candidate B needs a length
317 if(pCandidateB->getStart().equal(pCandidateB->getEnd()))
318 {
319 return Primitive2DReference();
320 }
321
322 // StrokeAttribute has to be equal
323 if(!(pCandidateA->getStrokeAttribute() == pCandidateB->getStrokeAttribute()))
324 {
325 return Primitive2DReference();
326 }
327
328 // direction has to be equal -> cross product == 0.0
329 const basegfx::B2DVector aVT(pCandidateA->getEnd() - pCandidateA->getStart());
330 const basegfx::B2DVector aVC(pCandidateB->getEnd() - pCandidateB->getStart());
331 if(!rtl::math::approxEqual(0.0, aVC.cross(aVT)))
332 {
333 return Primitive2DReference();
334 }
335
336 // number BorderLines has to be equal
337 const size_t count(pCandidateA->getBorderLines().size());
338 if(count != pCandidateB->getBorderLines().size())
339 {
340 return Primitive2DReference();
341 }
342
343 for(size_t a(0); a < count; a++)
344 {
345 const BorderLine& rBT(pCandidateA->getBorderLines()[a]);
346 const BorderLine& rBC(pCandidateB->getBorderLines()[a]);
347
348 // LineAttribute has to be the same
349 if(!(rBC.getLineAttribute() == rBT.getLineAttribute()))
350 {
351 return Primitive2DReference();
352 }
353
354 // isGap has to be the same
355 if(rBC.isGap() != rBT.isGap())
356 {
357 return Primitive2DReference();
358 }
359
360 if(rBT.isGap())
361 {
362 // when gap, width has to be equal
363 if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth()))
364 {
365 return Primitive2DReference();
366 }
367 }
368 else
369 {
370 // when not gap, the line extends have at least reach to the center ( > 0.0),
371 // else there is an extend usage. When > 0.0 they just overlap, no problem
372 if(rBT.getEndLeft() >= 0.0
373 && rBT.getEndRight() >= 0.0
374 && rBC.getStartLeft() >= 0.0
375 && rBC.getStartRight() >= 0.0)
376 {
377 // okay
378 }
379 else
380 {
381 return Primitive2DReference();
382 }
383 }
384 }
385
386 // all conditions met, create merged primitive
387 std::vector< BorderLine > aMergedBorderLines;
388
389 for(size_t a(0); a < count; a++)
390 {
391 const BorderLine& rBT(pCandidateA->getBorderLines()[a]);
392 const BorderLine& rBC(pCandidateB->getBorderLines()[a]);
393
394 if(rBT.isGap())
395 {
396 aMergedBorderLines.push_back(rBT);
397 }
398 else
399 {
400 aMergedBorderLines.push_back(
402 rBT.getLineAttribute(),
403 rBT.getStartLeft(), rBT.getStartRight(),
404 rBC.getEndLeft(), rBC.getEndRight()));
405 }
406 }
407
410 pCandidateA->getStart(),
411 pCandidateB->getEnd(),
412 std::move(aMergedBorderLines),
413 pCandidateA->getStrokeAttribute()));
414 }
415
416} // end of namespace
417
418/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Point maStart
const Point maEnd
void append(const basegfx::B2DPoint &rPoint, sal_uInt32 nCount)
B2DVector & normalize()
double cross(const B2DVector &rVec) const
bool equal(const Tuple2D< TYPE > &rTup) const
TYPE getX() const
TYPE getY() const
const basegfx::B2DHomMatrix & getObjectToViewTransformation() const
On-demand prepared Object to View transformation and its inverse for convenience.
std::vector< BorderLine > maBorderLines
the single BorderLine style definition(s), one or three mostly used
const std::vector< BorderLine > & getBorderLines() const
virtual void create2DDecomposition(Primitive2DContainer &rContainer, const geometry::ViewInformation2D &rViewInformation) const override
create local decomposition
double getFullWidth() const
helper to get the full width from maBorderLines
virtual bool operator==(const BasePrimitive2D &rPrimitive) const override
compare operator
virtual sal_uInt32 getPrimitive2DID() const override
provide unique ID
BorderLinePrimitive2D(const basegfx::B2DPoint &rStart, const basegfx::B2DPoint &rEnd, std::vector< BorderLine > &&rBorderLines, drawinglayer::attribute::StrokeAttribute aStrokeAttribute)
simplified constructor for BorderLine with single edge
bool isHorizontalOrVertical(const geometry::ViewInformation2D &rViewInformation) const
helper to decide if AntiAliasing should be used
const drawinglayer::attribute::StrokeAttribute & getStrokeAttribute() const
const basegfx::B2DPoint & getStart() const
data read access
BorderLine class Helper class holding the style definition for a single part of a full BorderLine def...
bool operator==(const BorderLine &rBorderLine) const
compare operator
const drawinglayer::attribute::LineAttribute & getLineAttribute() const
BorderLine(const drawinglayer::attribute::LineAttribute &rLineAttribute, double fStartLeft=0.0, double fStartRight=0.0, double fEndLeft=0.0, double fEndRight=0.0)
#define PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D
uno_Any a
bool equalZero(const T &rfVal)
bool equal(T const &rfValA, T const &rfValB)
B2DVector getPerpendicular(const B2DVector &rNormalizedVec)
rtl::Reference< BasePrimitive2D > Primitive2DReference
Definition: CommonTypes.hxx:27
static void addPolygonStrokePrimitive2D(Primitive2DContainer &rContainer, const basegfx::B2DPoint &rStart, const basegfx::B2DPoint &rEnd, const attribute::LineAttribute &rLineAttribute, const attribute::StrokeAttribute &rStrokeAttribute)
Primitive2DReference tryMergeBorderLinePrimitive2D(const BorderLinePrimitive2D *pCandidateA, const BorderLinePrimitive2D *pCandidateB)
helper to try to merge two instances of BorderLinePrimitive2D.