/src/libreoffice/drawinglayer/source/primitive2d/polygonprimitive2d.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 <basegfx/polygon/b2dpolygontools.hxx> |
21 | | #include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> |
22 | | #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> |
23 | | #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> |
24 | | #include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> |
25 | | #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> |
26 | | #include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> |
27 | | #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> |
28 | | #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> |
29 | | #include <drawinglayer/primitive2d/groupprimitive2d.hxx> |
30 | | #include <drawinglayer/geometry/viewinformation2d.hxx> |
31 | | #include <basegfx/polygon/b2dlinegeometry.hxx> |
32 | | #include <com/sun/star/drawing/LineCap.hpp> |
33 | | #include <utility> |
34 | | |
35 | | using namespace com::sun::star; |
36 | | |
37 | | namespace |
38 | | { |
39 | | void implGrowHairline(basegfx::B2DRange& rRange, |
40 | | const drawinglayer::geometry::ViewInformation2D& rViewInformation) |
41 | 101 | { |
42 | 101 | if (!rRange.isEmpty()) |
43 | 101 | { |
44 | | // Calculate view-dependent hairline width |
45 | 101 | const basegfx::B2DVector aDiscreteSize( |
46 | 101 | rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); |
47 | 101 | const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); |
48 | | |
49 | 101 | if (fDiscreteHalfLineWidth > 0.0) |
50 | 101 | { |
51 | 101 | rRange.grow(fDiscreteHalfLineWidth); |
52 | 101 | } |
53 | 101 | } |
54 | 101 | } |
55 | | } |
56 | | |
57 | | namespace drawinglayer::primitive2d |
58 | | { |
59 | | PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(basegfx::B2DPolygon aPolygon, |
60 | | const basegfx::BColor& rBColor) |
61 | 301 | : maPolygon(std::move(aPolygon)) |
62 | 301 | , maBColor(rBColor) |
63 | 301 | { |
64 | 301 | } |
65 | | |
66 | | bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
67 | 0 | { |
68 | 0 | if (BasePrimitive2D::operator==(rPrimitive)) |
69 | 0 | { |
70 | 0 | const PolygonHairlinePrimitive2D& rCompare |
71 | 0 | = static_cast<const PolygonHairlinePrimitive2D&>(rPrimitive); |
72 | |
|
73 | 0 | return (getB2DPolygon() == rCompare.getB2DPolygon() && getBColor() == rCompare.getBColor()); |
74 | 0 | } |
75 | | |
76 | 0 | return false; |
77 | 0 | } |
78 | | |
79 | | basegfx::B2DRange |
80 | | PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const |
81 | 101 | { |
82 | | // this is a hairline, thus the line width is view-dependent. Get range of polygon |
83 | | // as base size |
84 | 101 | basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange()); |
85 | | |
86 | | // Calculate and grow by view-dependent hairline width |
87 | 101 | implGrowHairline(aRetval, rViewInformation); |
88 | | |
89 | | // return range |
90 | 101 | return aRetval; |
91 | 101 | } |
92 | | |
93 | | // provide unique ID |
94 | | sal_uInt32 PolygonHairlinePrimitive2D::getPrimitive2DID() const |
95 | 301 | { |
96 | 301 | return PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D; |
97 | 301 | } |
98 | | |
99 | | SingleLinePrimitive2D::SingleLinePrimitive2D(const basegfx::B2DPoint& rStart, |
100 | | const basegfx::B2DPoint& rEnd, |
101 | | const basegfx::BColor& rBColor) |
102 | 0 | : BasePrimitive2D() |
103 | 0 | , maStart(rStart) |
104 | 0 | , maEnd(rEnd) |
105 | 0 | , maBColor(rBColor) |
106 | 0 | { |
107 | 0 | } |
108 | | |
109 | | bool SingleLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
110 | 0 | { |
111 | 0 | if (BasePrimitive2D::operator==(rPrimitive)) |
112 | 0 | { |
113 | 0 | const SingleLinePrimitive2D& rCompare( |
114 | 0 | static_cast<const SingleLinePrimitive2D&>(rPrimitive)); |
115 | |
|
116 | 0 | return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd() |
117 | 0 | && getBColor() == rCompare.getBColor()); |
118 | 0 | } |
119 | | |
120 | 0 | return false; |
121 | 0 | } |
122 | | |
123 | | basegfx::B2DRange |
124 | | SingleLinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const |
125 | 0 | { |
126 | 0 | basegfx::B2DRange aRetval(getStart(), getEnd()); |
127 | | |
128 | | // Calculate and grow by view-dependent hairline width |
129 | 0 | implGrowHairline(aRetval, rViewInformation); |
130 | |
|
131 | 0 | return aRetval; |
132 | 0 | } |
133 | | |
134 | | sal_uInt32 SingleLinePrimitive2D::getPrimitive2DID() const |
135 | 0 | { |
136 | 0 | return PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D; |
137 | 0 | } |
138 | | |
139 | | void SingleLinePrimitive2D::get2DDecomposition( |
140 | | Primitive2DDecompositionVisitor& rVisitor, |
141 | | const geometry::ViewInformation2D& /*rViewInformation*/) const |
142 | 0 | { |
143 | 0 | if (getStart() == getEnd()) |
144 | 0 | { |
145 | | // single point |
146 | 0 | Primitive2DContainer aSequence = { new PointArrayPrimitive2D( |
147 | 0 | std::vector<basegfx::B2DPoint>{ getStart() }, getBColor()) }; |
148 | 0 | rVisitor.visit(aSequence); |
149 | 0 | } |
150 | 0 | else |
151 | 0 | { |
152 | | // line |
153 | 0 | Primitive2DContainer aSequence = { new PolygonHairlinePrimitive2D( |
154 | 0 | basegfx::B2DPolygon{ getStart(), getEnd() }, getBColor()) }; |
155 | 0 | rVisitor.visit(aSequence); |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | | LineRectanglePrimitive2D::LineRectanglePrimitive2D(const basegfx::B2DRange& rB2DRange, |
160 | | const basegfx::BColor& rBColor) |
161 | 0 | : BasePrimitive2D() |
162 | 0 | , maB2DRange(rB2DRange) |
163 | 0 | , maBColor(rBColor) |
164 | 0 | { |
165 | 0 | } |
166 | | |
167 | | bool LineRectanglePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
168 | 0 | { |
169 | 0 | if (BasePrimitive2D::operator==(rPrimitive)) |
170 | 0 | { |
171 | 0 | const LineRectanglePrimitive2D& rCompare( |
172 | 0 | static_cast<const LineRectanglePrimitive2D&>(rPrimitive)); |
173 | |
|
174 | 0 | return (getB2DRange() == rCompare.getB2DRange() && getBColor() == rCompare.getBColor()); |
175 | 0 | } |
176 | | |
177 | 0 | return false; |
178 | 0 | } |
179 | | |
180 | | basegfx::B2DRange |
181 | | LineRectanglePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const |
182 | 0 | { |
183 | 0 | basegfx::B2DRange aRetval(getB2DRange()); |
184 | | |
185 | | // Calculate and grow by view-dependent hairline width |
186 | 0 | implGrowHairline(aRetval, rViewInformation); |
187 | |
|
188 | 0 | return aRetval; |
189 | 0 | } |
190 | | |
191 | | sal_uInt32 LineRectanglePrimitive2D::getPrimitive2DID() const |
192 | 0 | { |
193 | 0 | return PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D; |
194 | 0 | } |
195 | | |
196 | | void LineRectanglePrimitive2D::get2DDecomposition( |
197 | | Primitive2DDecompositionVisitor& rVisitor, |
198 | | const geometry::ViewInformation2D& /*rViewInformation*/) const |
199 | 0 | { |
200 | 0 | if (getB2DRange().isEmpty()) |
201 | 0 | { |
202 | | // no geometry, done |
203 | 0 | return; |
204 | 0 | } |
205 | | |
206 | 0 | const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(getB2DRange())); |
207 | 0 | Primitive2DContainer aSequence = { new PolygonHairlinePrimitive2D(aPolygon, getBColor()) }; |
208 | 0 | rVisitor.visit(aSequence); |
209 | 0 | } |
210 | | |
211 | | Primitive2DReference PolygonMarkerPrimitive2D::create2DDecomposition( |
212 | | const geometry::ViewInformation2D& rViewInformation) const |
213 | 0 | { |
214 | | // calculate logic DashLength |
215 | 0 | const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation() |
216 | 0 | * basegfx::B2DVector(getDiscreteDashLength(), 0.0)); |
217 | 0 | const double fLogicDashLength(aDashVector.getX()); |
218 | |
|
219 | 0 | if (fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB())) |
220 | 0 | { |
221 | | // apply dashing; get line and gap snippets |
222 | 0 | std::vector<double> aDash; |
223 | 0 | basegfx::B2DPolyPolygon aDashedPolyPolyA; |
224 | 0 | basegfx::B2DPolyPolygon aDashedPolyPolyB; |
225 | |
|
226 | 0 | aDash.push_back(fLogicDashLength); |
227 | 0 | aDash.push_back(fLogicDashLength); |
228 | 0 | basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA, |
229 | 0 | &aDashedPolyPolyB, 2.0 * fLogicDashLength); |
230 | |
|
231 | 0 | Primitive2DContainer aContainer; |
232 | 0 | aContainer.push_back( |
233 | 0 | new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyA), getRGBColorA())); |
234 | 0 | aContainer.push_back( |
235 | 0 | new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyB), getRGBColorB())); |
236 | 0 | return new GroupPrimitive2D(std::move(aContainer)); |
237 | 0 | } |
238 | 0 | else |
239 | 0 | { |
240 | 0 | return new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA()); |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | | PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(basegfx::B2DPolygon aPolygon, |
245 | | const basegfx::BColor& rRGBColorA, |
246 | | const basegfx::BColor& rRGBColorB, |
247 | | double fDiscreteDashLength) |
248 | 0 | : maPolygon(std::move(aPolygon)) |
249 | 0 | , maRGBColorA(rRGBColorA) |
250 | 0 | , maRGBColorB(rRGBColorB) |
251 | 0 | , mfDiscreteDashLength(fDiscreteDashLength) |
252 | 0 | { |
253 | 0 | } |
254 | | |
255 | | bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
256 | 0 | { |
257 | 0 | if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) |
258 | 0 | { |
259 | 0 | const PolygonMarkerPrimitive2D& rCompare |
260 | 0 | = static_cast<const PolygonMarkerPrimitive2D&>(rPrimitive); |
261 | |
|
262 | 0 | return (getB2DPolygon() == rCompare.getB2DPolygon() |
263 | 0 | && getRGBColorA() == rCompare.getRGBColorA() |
264 | 0 | && getRGBColorB() == rCompare.getRGBColorB() |
265 | 0 | && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); |
266 | 0 | } |
267 | | |
268 | 0 | return false; |
269 | 0 | } |
270 | | |
271 | | basegfx::B2DRange |
272 | | PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const |
273 | 0 | { |
274 | | // this is a hairline, thus the line width is view-dependent. Get range of polygon |
275 | | // as base size |
276 | 0 | basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange()); |
277 | |
|
278 | 0 | if (!aRetval.isEmpty()) |
279 | 0 | { |
280 | | // Calculate view-dependent hairline width |
281 | 0 | const basegfx::B2DVector aDiscreteSize( |
282 | 0 | rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); |
283 | 0 | const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); |
284 | |
|
285 | 0 | if (fDiscreteHalfLineWidth > 0.0) |
286 | 0 | { |
287 | 0 | aRetval.grow(fDiscreteHalfLineWidth); |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | // return range |
292 | 0 | return aRetval; |
293 | 0 | } |
294 | | |
295 | | void PolygonMarkerPrimitive2D::get2DDecomposition( |
296 | | Primitive2DDecompositionVisitor& rVisitor, |
297 | | const geometry::ViewInformation2D& rViewInformation) const |
298 | 0 | { |
299 | 0 | bool bNeedNewDecomposition(false); |
300 | |
|
301 | 0 | if (hasBuffered2DDecomposition()) |
302 | 0 | { |
303 | 0 | if (rViewInformation.getInverseObjectToViewTransformation() |
304 | 0 | != maLastInverseObjectToViewTransformation) |
305 | 0 | { |
306 | 0 | bNeedNewDecomposition = true; |
307 | 0 | } |
308 | 0 | } |
309 | |
|
310 | 0 | if (bNeedNewDecomposition) |
311 | 0 | { |
312 | | // conditions of last local decomposition have changed, delete |
313 | 0 | const_cast<PolygonMarkerPrimitive2D*>(this)->setBuffered2DDecomposition(nullptr); |
314 | 0 | } |
315 | |
|
316 | 0 | if (!hasBuffered2DDecomposition()) |
317 | 0 | { |
318 | | // remember last used InverseObjectToViewTransformation |
319 | 0 | PolygonMarkerPrimitive2D* pThat = const_cast<PolygonMarkerPrimitive2D*>(this); |
320 | 0 | pThat->maLastInverseObjectToViewTransformation |
321 | 0 | = rViewInformation.getInverseObjectToViewTransformation(); |
322 | 0 | } |
323 | | |
324 | | // use parent implementation |
325 | 0 | BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); |
326 | 0 | } |
327 | | |
328 | | // provide unique ID |
329 | | sal_uInt32 PolygonMarkerPrimitive2D::getPrimitive2DID() const |
330 | 0 | { |
331 | 0 | return PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D; |
332 | 0 | } |
333 | | |
334 | | } // end of namespace |
335 | | |
336 | | namespace drawinglayer::primitive2d |
337 | | { |
338 | | Primitive2DReference PolygonStrokePrimitive2D::create2DDecomposition( |
339 | | const geometry::ViewInformation2D& /*rViewInformation*/) const |
340 | 199 | { |
341 | 199 | if (!getB2DPolygon().count()) |
342 | 0 | return nullptr; |
343 | | |
344 | | // #i102241# try to simplify before usage |
345 | 199 | const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon())); |
346 | 199 | basegfx::B2DPolyPolygon aHairLinePolyPolygon; |
347 | | |
348 | 199 | if (getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) |
349 | 199 | { |
350 | | // no line dashing, just copy |
351 | 199 | aHairLinePolyPolygon.append(aB2DPolygon); |
352 | 199 | } |
353 | 0 | else |
354 | 0 | { |
355 | | // apply LineStyle |
356 | 0 | basegfx::utils::applyLineDashing(aB2DPolygon, getStrokeAttribute().getDotDashArray(), |
357 | 0 | &aHairLinePolyPolygon, nullptr, |
358 | 0 | getStrokeAttribute().getFullDotDashLen()); |
359 | 0 | } |
360 | | |
361 | 199 | const sal_uInt32 nCount(aHairLinePolyPolygon.count()); |
362 | | |
363 | 199 | if (!getLineAttribute().isDefault() && getLineAttribute().getWidth()) |
364 | 3 | { |
365 | | // create fat line data |
366 | 3 | const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0); |
367 | 3 | const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin()); |
368 | 3 | const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap()); |
369 | 3 | basegfx::B2DPolyPolygon aAreaPolyPolygon; |
370 | 3 | const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle()); |
371 | | |
372 | 6 | for (sal_uInt32 a(0); a < nCount; a++) |
373 | 3 | { |
374 | | // New version of createAreaGeometry; now creates bezier polygons |
375 | 3 | aAreaPolyPolygon.append(basegfx::utils::createAreaGeometry( |
376 | 3 | aHairLinePolyPolygon.getB2DPolygon(a), fHalfLineWidth, aLineJoin, aLineCap, |
377 | 3 | basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/, |
378 | 3 | 0.4 /* default fMaxPartOfEdge*/, fMiterMinimumAngle)); |
379 | 3 | } |
380 | | |
381 | | // create primitive |
382 | 3 | Primitive2DContainer aContainer; |
383 | 6 | for (sal_uInt32 b(0); b < aAreaPolyPolygon.count(); b++) |
384 | 3 | { |
385 | | // put into single polyPolygon primitives to make clear that this is NOT meant |
386 | | // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a |
387 | | // melting process may be used here one day. |
388 | 3 | basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b)); |
389 | 3 | const basegfx::BColor aColor(getLineAttribute().getColor()); |
390 | 3 | aContainer.push_back( |
391 | 3 | new PolyPolygonColorPrimitive2D(std::move(aNewPolyPolygon), aColor)); |
392 | 3 | } |
393 | 3 | return new GroupPrimitive2D(std::move(aContainer)); |
394 | 3 | } |
395 | 196 | else |
396 | 196 | { |
397 | 196 | return new PolyPolygonHairlinePrimitive2D(std::move(aHairLinePolyPolygon), |
398 | 196 | getLineAttribute().getColor()); |
399 | 196 | } |
400 | 199 | } |
401 | | |
402 | | PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon, |
403 | | const attribute::LineAttribute& rLineAttribute, |
404 | | attribute::StrokeAttribute aStrokeAttribute) |
405 | 4.31k | : maPolygon(std::move(aPolygon)) |
406 | 4.31k | , maLineAttribute(rLineAttribute) |
407 | 4.31k | , maStrokeAttribute(std::move(aStrokeAttribute)) |
408 | 4.31k | , maBufferedRange() |
409 | 4.31k | { |
410 | | // MM01: keep these - these are no curve-decompposers but just checks |
411 | | // simplify curve segments: moved here to not need to use it |
412 | | // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect |
413 | 4.31k | maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); |
414 | 4.31k | } |
415 | | |
416 | | PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon, |
417 | | const attribute::LineAttribute& rLineAttribute) |
418 | 4 | : maPolygon(std::move(aPolygon)) |
419 | 4 | , maLineAttribute(rLineAttribute) |
420 | 4 | , maBufferedRange() |
421 | 4 | { |
422 | | // MM01: keep these - these are no curve-decompposers but just checks |
423 | | // simplify curve segments: moved here to not need to use it |
424 | | // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect |
425 | 4 | maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); |
426 | 4 | } |
427 | | |
428 | | bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
429 | 0 | { |
430 | 0 | if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) |
431 | 0 | { |
432 | 0 | const PolygonStrokePrimitive2D& rCompare |
433 | 0 | = static_cast<const PolygonStrokePrimitive2D&>(rPrimitive); |
434 | |
|
435 | 0 | return (getB2DPolygon() == rCompare.getB2DPolygon() |
436 | 0 | && getLineAttribute() == rCompare.getLineAttribute() |
437 | 0 | && getStrokeAttribute() == rCompare.getStrokeAttribute()); |
438 | 0 | } |
439 | | |
440 | 0 | return false; |
441 | 0 | } |
442 | | |
443 | | basegfx::B2DRange |
444 | | PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const |
445 | 611 | { |
446 | 611 | if (!maBufferedRange.isEmpty()) |
447 | 144 | { |
448 | | // use the view-independent, buffered B2DRange |
449 | 144 | return maBufferedRange; |
450 | 144 | } |
451 | | |
452 | 467 | if (getLineAttribute().getWidth()) |
453 | 75 | { |
454 | 75 | bool bUseDecomposition(false); |
455 | | |
456 | 75 | if (basegfx::B2DLineJoin::Miter == getLineAttribute().getLineJoin()) |
457 | 3 | { |
458 | | // if line is mitered, use parent call since mitered line |
459 | | // geometry may use more space than the geometry grown by half line width |
460 | 3 | bUseDecomposition = true; |
461 | 3 | } |
462 | | |
463 | 75 | if (!bUseDecomposition && css::drawing::LineCap_SQUARE == getLineAttribute().getLineCap()) |
464 | 0 | { |
465 | | // when drawing::LineCap_SQUARE is used the below method to grow the polygon |
466 | | // range by half line width will not work, so use decomposition. Interestingly, |
467 | | // the grow method below works perfectly for LineCap_ROUND since the grow is in |
468 | | // all directions and the rounded cap needs the same grow in all directions independent |
469 | | // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE |
470 | | |
471 | | // NOTE: I thought about using [sqrt(2) * 0.5] a a factor for LineCap_SQUARE and not |
472 | | // set bUseDecomposition. I even tried that it works. Then an auto-test failing showed |
473 | | // not only that view-dependent stuff needs to be considered (what is done for the |
474 | | // hairline case below), *BUT* also e.g. conversions to PNG/exports use the B2DRange |
475 | | // of the geometry, so: |
476 | | // - expanding by 1/2 LineWidth is OK for rounded |
477 | | // - expanding by more (like sqrt(2) * 0.5 * LineWidth) immediately extends the size |
478 | | // of e.g. geometry converted to PNG, plus many similar cases that cannot be thought |
479 | | // of in advance. |
480 | | // This means that converting those thought-experiment examples in (4) will and do lead |
481 | | // to bigger e.g. Bitmap conversion(s), not avoiding but painting the free space. That |
482 | | // could only be done by correctly and fully decomposing the geometry, including |
483 | | // stroke, and accepting the cost... |
484 | 0 | bUseDecomposition = true; |
485 | 0 | } |
486 | | |
487 | 75 | if (bUseDecomposition) |
488 | 3 | { |
489 | | // get correct range by using the decomposition fallback, reasons see above cases |
490 | | |
491 | | // It is not a good idea to temporarily (re)set the PolygonStrokePrimitive2D |
492 | | // to default. While it is understandable to use the speed advantage, it is |
493 | | // bad for quite some reasons: |
494 | | // |
495 | | // (1) As described in include/drawinglayer/primitive2d/baseprimitive2d.hxx |
496 | | // a Primitive is "noncopyable to make clear that a primitive is a read-only |
497 | | // instance and copying or changing values is not intended". This is the base |
498 | | // assumption for many decisions for Primitive handling. |
499 | | // (2) For example, that the decomposition is *always* re-usable. It cannot change |
500 | | // and is correct when it already exists since the values the decomposition is |
501 | | // based on cannot change. |
502 | | // (3) If this *is* done (like it was here) and the Primitive is derived from |
503 | | // BufferedDecompositionPrimitive2D and thus buffers it's decomposition, |
504 | | // the risk is that in this case the *wrong* decomposition will be used by |
505 | | // other PrimitiveProcessors. Maybe not by the VclPixelProcessor2D/VclProcessor2D |
506 | | // since it handles this primitive directly - not even sure for all cases. |
507 | | // Sooner or later another PrimitiveProcessor will re-use this wrong temporary |
508 | | // decomposition, and as an error, a non-stroked line will be painted/used. |
509 | | // (4) The B2DRange is not strictly defined as minimal bound for the geometry, |
510 | | // but it should be as small/tight as possible. Making it larger risks more |
511 | | // area to be invalidated (repaint) and processed (all geometric stuff,l may |
512 | | // include future and existing exports to other formats which are or will be |
513 | | // implemented as PrimitiveProcessor). It is easy to imagine cases with much |
514 | | // too large B2DRange - a line with a pattern that would solve to a single |
515 | | // small start-rectangle and rest is empty, or a circle with a stroke that |
516 | | // makes only a quarter of it visible. |
517 | | // |
518 | | // The reason to do this is speed, what is a good argument. But speed should |
519 | | // only be used if the pair of [correctness/speed] does not sacrifice the correctness |
520 | | // over the speed. |
521 | | // Luckily there are alternatives to solve this and to keep [correctness/speed] |
522 | | // valid: |
523 | | // |
524 | | // (a) Reset the temporary decomposition after having const-casted and |
525 | | // changed maStrokeAttribute. |
526 | | // Disadvantage: More const-cast hacks, plus this temporary decomposition |
527 | | // will be potentially done repeatedly (every time |
528 | | // PolygonStrokePrimitive2D::getB2DRange is called) |
529 | | // (b) Use a temporary, local PolygonStrokePrimitive2D here, with neutral |
530 | | // PolygonStrokePrimitive2D and call ::getB2DRange() at it. That way |
531 | | // the buffered decomposition will not be harmed. |
532 | | // Disadvantage: Same as (a), decomposition will be potentially done repeatedly |
533 | | // (c) Use a temporary, local PolygonStrokePrimitive2D and buffer B2DRange |
534 | | // locally for this Primitive. Due to (1)/(2) this cannot change, so |
535 | | // when calculated once it is totally legal to use it. |
536 | | // |
537 | | // Thus here I would use (c): It accepts the disadvantages of (4) over speed, but |
538 | | // avoids the errors/problems from (1-4). |
539 | | // Additional argument for this: The hairline case below *also* uses the full |
540 | | // B2DRange of the polygon, ignoring an evtl. stroke, so (4) applies |
541 | 3 | if (!getStrokeAttribute().isDefault()) |
542 | 3 | { |
543 | | // only do this if StrokeAttribute is used, else recursion may happen (!) |
544 | 3 | const rtl::Reference<primitive2d::PolygonStrokePrimitive2D> |
545 | 3 | aTemporaryPrimitiveWithoutStroke(new primitive2d::PolygonStrokePrimitive2D( |
546 | 3 | getB2DPolygon(), getLineAttribute())); |
547 | 3 | maBufferedRange |
548 | 3 | = aTemporaryPrimitiveWithoutStroke |
549 | 3 | ->BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); |
550 | 3 | } |
551 | 0 | else |
552 | 0 | { |
553 | | // fallback to normal decompose, that result can be used for visualization |
554 | | // later, too. Still buffer B2DRange in maBufferedRange, so it needs to be |
555 | | // merged into one B2DRange only once |
556 | 0 | maBufferedRange = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); |
557 | 0 | } |
558 | 3 | } |
559 | 72 | else |
560 | 72 | { |
561 | | // for all other B2DLINEJOIN_* get the range from the base geometry |
562 | | // and expand by half the line width. |
563 | 72 | maBufferedRange = getB2DPolygon().getB2DRange(); |
564 | 72 | maBufferedRange.grow(getLineAttribute().getWidth() * 0.5); |
565 | 72 | } |
566 | | |
567 | 75 | return maBufferedRange; |
568 | 75 | } |
569 | | |
570 | | // It is a hairline, thus the line width is view-dependent. Get range of polygon |
571 | | // as base size. |
572 | | // CAUTION: Since a hairline *is* view-dependent, |
573 | | // - either use maBufferedRange, additionally remember view-dependent |
574 | | // factor & reset if that changes |
575 | | // - or do not buffer for hairline -> not really needed, the range is buffered |
576 | | // in the B2DPolygon, no decomposition is needed and a simple grow is cheap |
577 | 392 | basegfx::B2DRange aHairlineRange = getB2DPolygon().getB2DRange(); |
578 | | |
579 | 392 | if (!aHairlineRange.isEmpty()) |
580 | 392 | { |
581 | | // Calculate view-dependent hairline width |
582 | 392 | const basegfx::B2DVector aDiscreteSize( |
583 | 392 | rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); |
584 | 392 | const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); |
585 | | |
586 | 392 | if (fDiscreteHalfLineWidth > 0.0) |
587 | 392 | { |
588 | 392 | aHairlineRange.grow(fDiscreteHalfLineWidth); |
589 | 392 | } |
590 | 392 | } |
591 | | |
592 | 392 | return aHairlineRange; |
593 | 467 | } |
594 | | |
595 | | // provide unique ID |
596 | | sal_uInt32 PolygonStrokePrimitive2D::getPrimitive2DID() const |
597 | 4.38k | { |
598 | 4.38k | return PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D; |
599 | 4.38k | } |
600 | | |
601 | | Primitive2DReference PolygonWavePrimitive2D::create2DDecomposition( |
602 | | const geometry::ViewInformation2D& /*rViewInformation*/) const |
603 | 0 | { |
604 | 0 | if (!getB2DPolygon().count()) |
605 | 0 | return nullptr; |
606 | | |
607 | 0 | const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth())); |
608 | 0 | const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight())); |
609 | |
|
610 | 0 | if (bHasWidth && bHasHeight) |
611 | 0 | { |
612 | | // create waveline curve |
613 | 0 | basegfx::B2DPolygon aWaveline( |
614 | 0 | basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight())); |
615 | 0 | return new PolygonStrokePrimitive2D(std::move(aWaveline), getLineAttribute(), |
616 | 0 | getStrokeAttribute()); |
617 | 0 | } |
618 | 0 | else |
619 | 0 | { |
620 | | // flat waveline, decompose to simple line primitive |
621 | 0 | return new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), |
622 | 0 | getStrokeAttribute()); |
623 | 0 | } |
624 | 0 | } |
625 | | |
626 | | PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon, |
627 | | const attribute::LineAttribute& rLineAttribute, |
628 | | const attribute::StrokeAttribute& rStrokeAttribute, |
629 | | double fWaveWidth, double fWaveHeight) |
630 | 0 | : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute) |
631 | 0 | , mfWaveWidth(fWaveWidth) |
632 | 0 | , mfWaveHeight(fWaveHeight) |
633 | 0 | { |
634 | 0 | if (mfWaveWidth < 0.0) |
635 | 0 | { |
636 | 0 | mfWaveWidth = 0.0; |
637 | 0 | } |
638 | |
|
639 | 0 | if (mfWaveHeight < 0.0) |
640 | 0 | { |
641 | 0 | mfWaveHeight = 0.0; |
642 | 0 | } |
643 | 0 | } |
644 | | |
645 | | PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon, |
646 | | const attribute::LineAttribute& rLineAttribute, |
647 | | double fWaveWidth, double fWaveHeight) |
648 | 0 | : PolygonStrokePrimitive2D(rPolygon, rLineAttribute) |
649 | 0 | , mfWaveWidth(fWaveWidth) |
650 | 0 | , mfWaveHeight(fWaveHeight) |
651 | 0 | { |
652 | 0 | if (mfWaveWidth < 0.0) |
653 | 0 | { |
654 | 0 | mfWaveWidth = 0.0; |
655 | 0 | } |
656 | |
|
657 | 0 | if (mfWaveHeight < 0.0) |
658 | 0 | { |
659 | 0 | mfWaveHeight = 0.0; |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
664 | 0 | { |
665 | 0 | if (PolygonStrokePrimitive2D::operator==(rPrimitive)) |
666 | 0 | { |
667 | 0 | const PolygonWavePrimitive2D& rCompare |
668 | 0 | = static_cast<const PolygonWavePrimitive2D&>(rPrimitive); |
669 | |
|
670 | 0 | return (getWaveWidth() == rCompare.getWaveWidth() |
671 | 0 | && getWaveHeight() == rCompare.getWaveHeight()); |
672 | 0 | } |
673 | | |
674 | 0 | return false; |
675 | 0 | } |
676 | | |
677 | | basegfx::B2DRange |
678 | | PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const |
679 | 0 | { |
680 | | // get range of parent |
681 | 0 | basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation)); |
682 | | |
683 | | // if WaveHeight, grow by it |
684 | 0 | if (getWaveHeight() > 0.0) |
685 | 0 | { |
686 | 0 | aRetval.grow(getWaveHeight()); |
687 | 0 | } |
688 | | |
689 | | // if line width, grow by it |
690 | 0 | if (getLineAttribute().getWidth() > 0.0) |
691 | 0 | { |
692 | 0 | aRetval.grow(getLineAttribute().getWidth() * 0.5); |
693 | 0 | } |
694 | |
|
695 | 0 | return aRetval; |
696 | 0 | } |
697 | | |
698 | | // provide unique ID |
699 | | sal_uInt32 PolygonWavePrimitive2D::getPrimitive2DID() const |
700 | 0 | { |
701 | 0 | return PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D; |
702 | 0 | } |
703 | | |
704 | | Primitive2DReference PolygonStrokeArrowPrimitive2D::create2DDecomposition( |
705 | | const geometry::ViewInformation2D& /*rViewInformation*/) const |
706 | 0 | { |
707 | | // copy local polygon, it may be changed |
708 | 0 | basegfx::B2DPolygon aLocalPolygon(getB2DPolygon()); |
709 | 0 | aLocalPolygon.removeDoublePoints(); |
710 | 0 | basegfx::B2DPolyPolygon aArrowA; |
711 | 0 | basegfx::B2DPolyPolygon aArrowB; |
712 | |
|
713 | 0 | if (!aLocalPolygon.isClosed() && aLocalPolygon.count() > 1) |
714 | 0 | { |
715 | | // apply arrows |
716 | 0 | const double fPolyLength(basegfx::utils::getLength(aLocalPolygon)); |
717 | 0 | double fStart(0.0); |
718 | 0 | double fEnd(0.0); |
719 | 0 | double fStartOverlap(0.0); |
720 | 0 | double fEndOverlap(0.0); |
721 | |
|
722 | 0 | if (!getStart().isDefault() && getStart().isActive()) |
723 | 0 | { |
724 | | // create start arrow primitive and consume |
725 | 0 | aArrowA = basegfx::utils::createAreaGeometryForLineStartEnd( |
726 | 0 | aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(), |
727 | 0 | fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart); |
728 | | |
729 | | // create some overlapping, compromise between straight and peaked markers |
730 | | // for marker width 0.3cm and marker line width 0.02cm |
731 | 0 | fStartOverlap = getStart().getWidth() / 15.0; |
732 | 0 | } |
733 | |
|
734 | 0 | if (!getEnd().isDefault() && getEnd().isActive()) |
735 | 0 | { |
736 | | // create end arrow primitive and consume |
737 | 0 | aArrowB = basegfx::utils::createAreaGeometryForLineStartEnd( |
738 | 0 | aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(), |
739 | 0 | fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd); |
740 | | |
741 | | // create some overlapping |
742 | 0 | fEndOverlap = getEnd().getWidth() / 15.0; |
743 | 0 | } |
744 | |
|
745 | 0 | if (0.0 != fStart || 0.0 != fEnd) |
746 | 0 | { |
747 | | // build new poly, consume something from old poly |
748 | 0 | aLocalPolygon |
749 | 0 | = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart - fStartOverlap, |
750 | 0 | fPolyLength - fEnd + fEndOverlap, fPolyLength); |
751 | 0 | } |
752 | 0 | } |
753 | | |
754 | | // add shaft |
755 | 0 | Primitive2DContainer aContainer; |
756 | 0 | aContainer.push_back(new PolygonStrokePrimitive2D(std::move(aLocalPolygon), getLineAttribute(), |
757 | 0 | getStrokeAttribute())); |
758 | |
|
759 | 0 | if (aArrowA.count()) |
760 | 0 | { |
761 | 0 | aContainer.push_back( |
762 | 0 | new PolyPolygonColorPrimitive2D(std::move(aArrowA), getLineAttribute().getColor())); |
763 | 0 | } |
764 | |
|
765 | 0 | if (aArrowB.count()) |
766 | 0 | { |
767 | 0 | aContainer.push_back( |
768 | 0 | new PolyPolygonColorPrimitive2D(std::move(aArrowB), getLineAttribute().getColor())); |
769 | 0 | } |
770 | 0 | return new GroupPrimitive2D(std::move(aContainer)); |
771 | 0 | } |
772 | | |
773 | | PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( |
774 | | const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute, |
775 | | const attribute::StrokeAttribute& rStrokeAttribute, |
776 | | const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd) |
777 | 0 | : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute) |
778 | 0 | , maStart(rStart) |
779 | 0 | , maEnd(rEnd) |
780 | 0 | { |
781 | 0 | } |
782 | | |
783 | | PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( |
784 | | const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute, |
785 | | const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd) |
786 | 0 | : PolygonStrokePrimitive2D(rPolygon, rLineAttribute) |
787 | 0 | , maStart(rStart) |
788 | 0 | , maEnd(rEnd) |
789 | 0 | { |
790 | 0 | } |
791 | | |
792 | | bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |
793 | 0 | { |
794 | 0 | if (PolygonStrokePrimitive2D::operator==(rPrimitive)) |
795 | 0 | { |
796 | 0 | const PolygonStrokeArrowPrimitive2D& rCompare |
797 | 0 | = static_cast<const PolygonStrokeArrowPrimitive2D&>(rPrimitive); |
798 | |
|
799 | 0 | return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd()); |
800 | 0 | } |
801 | | |
802 | 0 | return false; |
803 | 0 | } |
804 | | |
805 | | basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange( |
806 | | const geometry::ViewInformation2D& rViewInformation) const |
807 | 0 | { |
808 | 0 | if (getStart().isActive() || getEnd().isActive()) |
809 | 0 | { |
810 | | // use decomposition when line start/end is used |
811 | 0 | return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); |
812 | 0 | } |
813 | 0 | else |
814 | 0 | { |
815 | | // get range from parent |
816 | 0 | return PolygonStrokePrimitive2D::getB2DRange(rViewInformation); |
817 | 0 | } |
818 | 0 | } |
819 | | |
820 | | // provide unique ID |
821 | | sal_uInt32 PolygonStrokeArrowPrimitive2D::getPrimitive2DID() const |
822 | 0 | { |
823 | 0 | return PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D; |
824 | 0 | } |
825 | | |
826 | | } // end of namespace |
827 | | |
828 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |