/src/libreoffice/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx
Line | Count | Source |
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 | | |
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> |
28 | | |
29 | | #include <algorithm> |
30 | | #include <utility> |
31 | | |
32 | | |
33 | | namespace drawinglayer::primitive2d |
34 | | { |
35 | | BorderLine::BorderLine( |
36 | | const drawinglayer::attribute::LineAttribute& rLineAttribute, |
37 | | double fStartLeft, |
38 | | double fStartRight, |
39 | | double fEndLeft, |
40 | | double fEndRight) |
41 | 11.2k | : maLineAttribute(rLineAttribute), |
42 | 11.2k | mfStartLeft(fStartLeft), |
43 | 11.2k | mfStartRight(fStartRight), |
44 | 11.2k | mfEndLeft(fEndLeft), |
45 | 11.2k | mfEndRight(fEndRight), |
46 | 11.2k | mbIsGap(false) |
47 | 11.2k | { |
48 | 11.2k | } |
49 | | |
50 | | BorderLine::BorderLine( |
51 | | double fWidth) |
52 | 3 | : maLineAttribute(basegfx::BColor(), fWidth), |
53 | 3 | mfStartLeft(0.0), |
54 | 3 | mfStartRight(0.0), |
55 | 3 | mfEndLeft(0.0), |
56 | 3 | mfEndRight(0.0), |
57 | 3 | mbIsGap(true) |
58 | 3 | { |
59 | 3 | } |
60 | | |
61 | | BorderLine::~BorderLine() |
62 | 22.5k | { |
63 | 22.5k | } |
64 | | |
65 | | bool BorderLine::operator==(const BorderLine& rBorderLine) const |
66 | 0 | { |
67 | 0 | return getLineAttribute() == rBorderLine.getLineAttribute() |
68 | 0 | && getStartLeft() == rBorderLine.getStartLeft() |
69 | 0 | && getStartRight() == rBorderLine.getStartRight() |
70 | 0 | && getEndLeft() == rBorderLine.getEndLeft() |
71 | 0 | && getEndRight() == rBorderLine.getEndRight() |
72 | 0 | && isGap() == rBorderLine.isGap(); |
73 | 0 | } |
74 | | |
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) |
82 | 10.9k | { |
83 | 10.9k | basegfx::B2DPolygon aPolygon; |
84 | | |
85 | 10.9k | aPolygon.append(rStart); |
86 | 10.9k | aPolygon.append(rEnd); |
87 | | |
88 | 10.9k | if (rStrokeAttribute.isDefault()) |
89 | 0 | { |
90 | 0 | rContainer.push_back( |
91 | 0 | new PolygonStrokePrimitive2D( |
92 | 0 | std::move(aPolygon), |
93 | 0 | rLineAttribute)); |
94 | 0 | } |
95 | 10.9k | else |
96 | 10.9k | { |
97 | 10.9k | rContainer.push_back( |
98 | 10.9k | new PolygonStrokePrimitive2D( |
99 | 10.9k | std::move(aPolygon), |
100 | 10.9k | rLineAttribute, |
101 | 10.9k | rStrokeAttribute)); |
102 | 10.9k | } |
103 | 10.9k | } |
104 | | |
105 | | double BorderLinePrimitive2D::getFullWidth() const |
106 | 10.9k | { |
107 | 10.9k | double fRetval(0.0); |
108 | | |
109 | 10.9k | for(const auto& candidate : maBorderLines) |
110 | 10.9k | { |
111 | 10.9k | fRetval += candidate.getLineAttribute().getWidth(); |
112 | 10.9k | } |
113 | | |
114 | 10.9k | return fRetval; |
115 | 10.9k | } |
116 | | |
117 | | Primitive2DReference BorderLinePrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const |
118 | 10.9k | { |
119 | 10.9k | if (getStart().equal(getEnd()) || getBorderLines().empty()) |
120 | 0 | return nullptr; |
121 | | |
122 | | // get data and vectors |
123 | 10.9k | basegfx::B2DVector aVector(getEnd() - getStart()); |
124 | 10.9k | aVector.normalize(); |
125 | 10.9k | const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector)); |
126 | 10.9k | const double fFullWidth(getFullWidth()); |
127 | 10.9k | double fOffset(fFullWidth * -0.5); |
128 | | |
129 | 10.9k | Primitive2DContainer aContainer; |
130 | 10.9k | for(const auto& candidate : maBorderLines) |
131 | 10.9k | { |
132 | 10.9k | const double fWidth(candidate.getLineAttribute().getWidth()); |
133 | | |
134 | 10.9k | if(!candidate.isGap()) |
135 | 10.9k | { |
136 | 10.9k | const basegfx::B2DVector aDeltaY(aPerpendicular * (fOffset + (fWidth * 0.5))); |
137 | 10.9k | const basegfx::B2DPoint aStart(getStart() + aDeltaY); |
138 | 10.9k | const basegfx::B2DPoint aEnd(getEnd() + aDeltaY); |
139 | 10.9k | const bool bStartPerpendicular(rtl::math::approxEqual(candidate.getStartLeft(), candidate.getStartRight())); |
140 | 10.9k | const bool bEndPerpendicular(rtl::math::approxEqual(candidate.getEndLeft(), candidate.getEndRight())); |
141 | | |
142 | 10.9k | if(bStartPerpendicular && bEndPerpendicular) |
143 | 10.9k | { |
144 | | // start and end extends lead to an edge perpendicular to the line, so we can just use |
145 | | // a PolygonStrokePrimitive2D for representation |
146 | 10.9k | addPolygonStrokePrimitive2D( |
147 | 10.9k | aContainer, |
148 | 10.9k | aStart - (aVector * candidate.getStartLeft()), |
149 | 10.9k | aEnd + (aVector * candidate.getEndLeft()), |
150 | 10.9k | candidate.getLineAttribute(), |
151 | 10.9k | getStrokeAttribute()); |
152 | 10.9k | } |
153 | 0 | else |
154 | 0 | { |
155 | | // start and/or end extensions lead to a lineStart/End that is *not* |
156 | | // perpendicular to the line itself |
157 | 0 | if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) |
158 | 0 | { |
159 | | // without stroke, we can simply represent that using a filled polygon |
160 | 0 | const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); |
161 | 0 | basegfx::B2DPolygon aPolygon; |
162 | |
|
163 | 0 | aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); |
164 | 0 | aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); |
165 | 0 | aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); |
166 | 0 | aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); |
167 | |
|
168 | 0 | aContainer.push_back( |
169 | 0 | new PolyPolygonColorPrimitive2D( |
170 | 0 | basegfx::B2DPolyPolygon(aPolygon), |
171 | 0 | candidate.getLineAttribute().getColor())); |
172 | 0 | } |
173 | 0 | else |
174 | 0 | { |
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 | 0 | basegfx::B2DPoint aStrokeStart(aStart - (aVector * candidate.getStartLeft())); |
180 | 0 | basegfx::B2DPoint aStrokeEnd(aEnd + (aVector * candidate.getEndLeft())); |
181 | 0 | const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); |
182 | |
|
183 | 0 | if(!bStartPerpendicular) |
184 | 0 | { |
185 | 0 | const double fMin(std::min(candidate.getStartLeft(), candidate.getStartRight())); |
186 | 0 | const double fMax(std::max(candidate.getStartLeft(), candidate.getStartRight())); |
187 | 0 | basegfx::B2DPolygon aPolygon; |
188 | | |
189 | | // create a triangle with min/max values for LineStart and add |
190 | 0 | if(rtl::math::approxEqual(candidate.getStartLeft(), fMax)) |
191 | 0 | { |
192 | 0 | aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); |
193 | 0 | } |
194 | |
|
195 | 0 | aPolygon.append(aStart - aHalfLineOffset - (aVector * fMin)); |
196 | 0 | aPolygon.append(aStart + aHalfLineOffset - (aVector * fMin)); |
197 | |
|
198 | 0 | if(rtl::math::approxEqual(candidate.getStartRight(), fMax)) |
199 | 0 | { |
200 | 0 | aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); |
201 | 0 | } |
202 | |
|
203 | 0 | aContainer.push_back( |
204 | 0 | new PolyPolygonColorPrimitive2D( |
205 | 0 | basegfx::B2DPolyPolygon(aPolygon), |
206 | 0 | candidate.getLineAttribute().getColor())); |
207 | | |
208 | | // Adapt StrokeStart accordingly |
209 | 0 | aStrokeStart = aStart - (aVector * fMin); |
210 | 0 | } |
211 | |
|
212 | 0 | if(!bEndPerpendicular) |
213 | 0 | { |
214 | 0 | const double fMin(std::min(candidate.getEndLeft(), candidate.getEndRight())); |
215 | 0 | const double fMax(std::max(candidate.getEndLeft(), candidate.getEndRight())); |
216 | 0 | basegfx::B2DPolygon aPolygon; |
217 | | |
218 | | // create a triangle with min/max values for LineEnd and add |
219 | 0 | if(rtl::math::approxEqual(candidate.getEndLeft(), fMax)) |
220 | 0 | { |
221 | 0 | aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); |
222 | 0 | } |
223 | |
|
224 | 0 | if(rtl::math::approxEqual(candidate.getEndRight(), fMax)) |
225 | 0 | { |
226 | 0 | aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); |
227 | 0 | } |
228 | |
|
229 | 0 | aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin)); |
230 | 0 | aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin)); |
231 | |
|
232 | 0 | aContainer.push_back( |
233 | 0 | new PolyPolygonColorPrimitive2D( |
234 | 0 | basegfx::B2DPolyPolygon(aPolygon), |
235 | 0 | candidate.getLineAttribute().getColor())); |
236 | | |
237 | | // Adapt StrokeEnd accordingly |
238 | 0 | aStrokeEnd = aEnd + (aVector * fMin); |
239 | 0 | } |
240 | |
|
241 | 0 | addPolygonStrokePrimitive2D( |
242 | 0 | aContainer, |
243 | 0 | aStrokeStart, |
244 | 0 | aStrokeEnd, |
245 | 0 | candidate.getLineAttribute(), |
246 | 0 | getStrokeAttribute()); |
247 | 0 | } |
248 | 0 | } |
249 | 10.9k | } |
250 | | |
251 | 10.9k | fOffset += fWidth; |
252 | 10.9k | } |
253 | 10.9k | return new GroupPrimitive2D(std::move(aContainer)); |
254 | 10.9k | } |
255 | | |
256 | | bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D& rViewInformation) const |
257 | 0 | { |
258 | 0 | if (!getStart().equal(getEnd())) |
259 | 0 | { |
260 | 0 | const basegfx::B2DHomMatrix& rOTVT = rViewInformation.getObjectToViewTransformation(); |
261 | 0 | const basegfx::B2DVector aVector(rOTVT * getEnd() - rOTVT * getStart()); |
262 | |
|
263 | 0 | return basegfx::fTools::equalZero(aVector.getX()) || basegfx::fTools::equalZero(aVector.getY()); |
264 | 0 | } |
265 | | |
266 | 0 | return false; |
267 | 0 | } |
268 | | |
269 | | BorderLinePrimitive2D::BorderLinePrimitive2D( |
270 | | const basegfx::B2DPoint& rStart, |
271 | | const basegfx::B2DPoint& rEnd, |
272 | | std::vector< BorderLine >&& rBorderLines, |
273 | | drawinglayer::attribute::StrokeAttribute aStrokeAttribute) |
274 | 11.2k | : maStart(rStart), |
275 | 11.2k | maEnd(rEnd), |
276 | 11.2k | maBorderLines(std::move(rBorderLines)), |
277 | 11.2k | maStrokeAttribute(std::move(aStrokeAttribute)) |
278 | 11.2k | { |
279 | 11.2k | } |
280 | | |
281 | | bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
282 | 0 | { |
283 | 0 | if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive)) |
284 | 0 | return false; |
285 | | |
286 | 0 | const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive); |
287 | |
|
288 | 0 | return (getStart() == rCompare.getStart() |
289 | 0 | && getEnd() == rCompare.getEnd() |
290 | 0 | && getStrokeAttribute() == rCompare.getStrokeAttribute() |
291 | 0 | && getBorderLines() == rCompare.getBorderLines()); |
292 | 0 | } |
293 | | |
294 | | // provide unique ID |
295 | | sal_uInt32 BorderLinePrimitive2D::getPrimitive2DID() const |
296 | 10.9k | { |
297 | 10.9k | return PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D; |
298 | 10.9k | } |
299 | | |
300 | | Primitive2DReference tryMergeBorderLinePrimitive2D( |
301 | | const BorderLinePrimitive2D* pCandidateA, |
302 | | const BorderLinePrimitive2D* pCandidateB) |
303 | 19.1k | { |
304 | 19.1k | assert(pCandidateA); |
305 | 19.1k | assert(pCandidateB); |
306 | | |
307 | | // start of candidate has to match end of this |
308 | 19.1k | if(!pCandidateA->getEnd().equal(pCandidateB->getStart())) |
309 | 12.6k | { |
310 | 12.6k | return Primitive2DReference(); |
311 | 12.6k | } |
312 | | |
313 | | // candidate A needs a length |
314 | 6.51k | if(pCandidateA->getStart().equal(pCandidateA->getEnd())) |
315 | 0 | { |
316 | 0 | return Primitive2DReference(); |
317 | 0 | } |
318 | | |
319 | | // candidate B needs a length |
320 | 6.51k | if(pCandidateB->getStart().equal(pCandidateB->getEnd())) |
321 | 0 | { |
322 | 0 | return Primitive2DReference(); |
323 | 0 | } |
324 | | |
325 | | // StrokeAttribute has to be equal |
326 | 6.51k | if(!(pCandidateA->getStrokeAttribute() == pCandidateB->getStrokeAttribute())) |
327 | 0 | { |
328 | 0 | return Primitive2DReference(); |
329 | 0 | } |
330 | | |
331 | | // direction has to be equal -> cross product == 0.0 |
332 | 6.51k | const basegfx::B2DVector aVT(pCandidateA->getEnd() - pCandidateA->getStart()); |
333 | 6.51k | const basegfx::B2DVector aVC(pCandidateB->getEnd() - pCandidateB->getStart()); |
334 | 6.51k | if(aVC.cross(aVT) != 0) |
335 | 6.33k | { |
336 | 6.33k | return Primitive2DReference(); |
337 | 6.33k | } |
338 | | |
339 | | // number BorderLines has to be equal |
340 | 172 | const size_t count(pCandidateA->getBorderLines().size()); |
341 | 172 | if(count != pCandidateB->getBorderLines().size()) |
342 | 0 | { |
343 | 0 | return Primitive2DReference(); |
344 | 0 | } |
345 | | |
346 | 344 | for(size_t a(0); a < count; a++) |
347 | 172 | { |
348 | 172 | const BorderLine& rBT(pCandidateA->getBorderLines()[a]); |
349 | 172 | const BorderLine& rBC(pCandidateB->getBorderLines()[a]); |
350 | | |
351 | | // LineAttribute has to be the same |
352 | 172 | if(!(rBC.getLineAttribute() == rBT.getLineAttribute())) |
353 | 0 | { |
354 | 0 | return Primitive2DReference(); |
355 | 0 | } |
356 | | |
357 | | // isGap has to be the same |
358 | 172 | if(rBC.isGap() != rBT.isGap()) |
359 | 0 | { |
360 | 0 | return Primitive2DReference(); |
361 | 0 | } |
362 | | |
363 | 172 | if(rBT.isGap()) |
364 | 0 | { |
365 | | // when gap, width has to be equal |
366 | 0 | if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth())) |
367 | 0 | { |
368 | 0 | return Primitive2DReference(); |
369 | 0 | } |
370 | 0 | } |
371 | 172 | else |
372 | 172 | { |
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 | 172 | if(rBT.getEndLeft() >= 0.0 |
376 | 172 | && rBT.getEndRight() >= 0.0 |
377 | 172 | && rBC.getStartLeft() >= 0.0 |
378 | 172 | && rBC.getStartRight() >= 0.0) |
379 | 172 | { |
380 | | // okay |
381 | 172 | } |
382 | 0 | else |
383 | 0 | { |
384 | 0 | return Primitive2DReference(); |
385 | 0 | } |
386 | 172 | } |
387 | 172 | } |
388 | | |
389 | | // all conditions met, create merged primitive |
390 | 172 | std::vector< BorderLine > aMergedBorderLines; |
391 | | |
392 | 344 | for(size_t a(0); a < count; a++) |
393 | 172 | { |
394 | 172 | const BorderLine& rBT(pCandidateA->getBorderLines()[a]); |
395 | 172 | const BorderLine& rBC(pCandidateB->getBorderLines()[a]); |
396 | | |
397 | 172 | if(rBT.isGap()) |
398 | 0 | { |
399 | 0 | aMergedBorderLines.push_back(rBT); |
400 | 0 | } |
401 | 172 | else |
402 | 172 | { |
403 | 172 | aMergedBorderLines.push_back( |
404 | 172 | BorderLine( |
405 | 172 | rBT.getLineAttribute(), |
406 | 172 | rBT.getStartLeft(), rBT.getStartRight(), |
407 | 172 | rBC.getEndLeft(), rBC.getEndRight())); |
408 | 172 | } |
409 | 172 | } |
410 | | |
411 | 172 | return |
412 | 172 | new BorderLinePrimitive2D( |
413 | 172 | pCandidateA->getStart(), |
414 | 172 | pCandidateB->getEnd(), |
415 | 172 | std::move(aMergedBorderLines), |
416 | 172 | pCandidateA->getStrokeAttribute()); |
417 | 172 | } |
418 | | |
419 | | } // end of namespace |
420 | | |
421 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |