calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / drawinglayer / source / primitive2d / polygonprimitive2d.cxx
blob2fdb535f61d9a89286f8f4deecaa8f0d2ff6f656
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 <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/drawinglayer_primitivetypes2d.hxx>
28 #include <drawinglayer/geometry/viewinformation2d.hxx>
29 #include <basegfx/polygon/b2dlinegeometry.hxx>
30 #include <com/sun/star/drawing/LineCap.hpp>
31 #include <utility>
33 using namespace com::sun::star;
35 namespace drawinglayer::primitive2d
37 PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(basegfx::B2DPolygon aPolygon,
38 const basegfx::BColor& rBColor)
39 : maPolygon(std::move(aPolygon))
40 , maBColor(rBColor)
44 bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
46 if (BasePrimitive2D::operator==(rPrimitive))
48 const PolygonHairlinePrimitive2D& rCompare
49 = static_cast<const PolygonHairlinePrimitive2D&>(rPrimitive);
51 return (getB2DPolygon() == rCompare.getB2DPolygon() && getBColor() == rCompare.getBColor());
54 return false;
57 basegfx::B2DRange
58 PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
60 // this is a hairline, thus the line width is view-dependent. Get range of polygon
61 // as base size
62 basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());
64 if (!aRetval.isEmpty())
66 // Calculate view-dependent hairline width
67 const basegfx::B2DVector aDiscreteSize(
68 rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
69 const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
71 if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
73 aRetval.grow(fDiscreteHalfLineWidth);
77 // return range
78 return aRetval;
81 // provide unique ID
82 sal_uInt32 PolygonHairlinePrimitive2D::getPrimitive2DID() const
84 return PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D;
87 void PolygonMarkerPrimitive2D::create2DDecomposition(
88 Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const
90 // calculate logic DashLength
91 const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation()
92 * basegfx::B2DVector(getDiscreteDashLength(), 0.0));
93 const double fLogicDashLength(aDashVector.getX());
95 if (fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB()))
97 // apply dashing; get line and gap snippets
98 std::vector<double> aDash;
99 basegfx::B2DPolyPolygon aDashedPolyPolyA;
100 basegfx::B2DPolyPolygon aDashedPolyPolyB;
102 aDash.push_back(fLogicDashLength);
103 aDash.push_back(fLogicDashLength);
104 basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA,
105 &aDashedPolyPolyB, 2.0 * fLogicDashLength);
107 rContainer.push_back(
108 new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyA), getRGBColorA()));
109 rContainer.push_back(
110 new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyB), getRGBColorB()));
112 else
114 rContainer.push_back(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA()));
118 PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(basegfx::B2DPolygon aPolygon,
119 const basegfx::BColor& rRGBColorA,
120 const basegfx::BColor& rRGBColorB,
121 double fDiscreteDashLength)
122 : maPolygon(std::move(aPolygon))
123 , maRGBColorA(rRGBColorA)
124 , maRGBColorB(rRGBColorB)
125 , mfDiscreteDashLength(fDiscreteDashLength)
129 bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
131 if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
133 const PolygonMarkerPrimitive2D& rCompare
134 = static_cast<const PolygonMarkerPrimitive2D&>(rPrimitive);
136 return (getB2DPolygon() == rCompare.getB2DPolygon()
137 && getRGBColorA() == rCompare.getRGBColorA()
138 && getRGBColorB() == rCompare.getRGBColorB()
139 && getDiscreteDashLength() == rCompare.getDiscreteDashLength());
142 return false;
145 basegfx::B2DRange
146 PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
148 // this is a hairline, thus the line width is view-dependent. Get range of polygon
149 // as base size
150 basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());
152 if (!aRetval.isEmpty())
154 // Calculate view-dependent hairline width
155 const basegfx::B2DVector aDiscreteSize(
156 rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
157 const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
159 if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
161 aRetval.grow(fDiscreteHalfLineWidth);
165 // return range
166 return aRetval;
169 void PolygonMarkerPrimitive2D::get2DDecomposition(
170 Primitive2DDecompositionVisitor& rVisitor,
171 const geometry::ViewInformation2D& rViewInformation) const
173 bool bNeedNewDecomposition(false);
175 if (!getBuffered2DDecomposition().empty())
177 if (rViewInformation.getInverseObjectToViewTransformation()
178 != maLastInverseObjectToViewTransformation)
180 bNeedNewDecomposition = true;
184 if (bNeedNewDecomposition)
186 // conditions of last local decomposition have changed, delete
187 const_cast<PolygonMarkerPrimitive2D*>(this)->setBuffered2DDecomposition(
188 Primitive2DContainer());
191 if (getBuffered2DDecomposition().empty())
193 // remember last used InverseObjectToViewTransformation
194 PolygonMarkerPrimitive2D* pThat = const_cast<PolygonMarkerPrimitive2D*>(this);
195 pThat->maLastInverseObjectToViewTransformation
196 = rViewInformation.getInverseObjectToViewTransformation();
199 // use parent implementation
200 BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
203 // provide unique ID
204 sal_uInt32 PolygonMarkerPrimitive2D::getPrimitive2DID() const
206 return PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D;
209 } // end of namespace
211 namespace drawinglayer::primitive2d
213 void PolygonStrokePrimitive2D::create2DDecomposition(
214 Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
216 if (!getB2DPolygon().count())
217 return;
219 // #i102241# try to simplify before usage
220 const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon()));
221 basegfx::B2DPolyPolygon aHairLinePolyPolygon;
223 if (getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
225 // no line dashing, just copy
226 aHairLinePolyPolygon.append(aB2DPolygon);
228 else
230 // apply LineStyle
231 basegfx::utils::applyLineDashing(aB2DPolygon, getStrokeAttribute().getDotDashArray(),
232 &aHairLinePolyPolygon, nullptr,
233 getStrokeAttribute().getFullDotDashLen());
236 const sal_uInt32 nCount(aHairLinePolyPolygon.count());
238 if (!getLineAttribute().isDefault() && getLineAttribute().getWidth())
240 // create fat line data
241 const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0);
242 const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin());
243 const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap());
244 basegfx::B2DPolyPolygon aAreaPolyPolygon;
245 const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle());
247 for (sal_uInt32 a(0); a < nCount; a++)
249 // New version of createAreaGeometry; now creates bezier polygons
250 aAreaPolyPolygon.append(basegfx::utils::createAreaGeometry(
251 aHairLinePolyPolygon.getB2DPolygon(a), fHalfLineWidth, aLineJoin, aLineCap,
252 basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/,
253 0.4 /* default fMaxPartOfEdge*/, fMiterMinimumAngle));
256 // create primitive
257 for (sal_uInt32 b(0); b < aAreaPolyPolygon.count(); b++)
259 // put into single polyPolygon primitives to make clear that this is NOT meant
260 // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a
261 // melting process may be used here one day.
262 basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b));
263 const basegfx::BColor aColor(getLineAttribute().getColor());
264 rContainer.push_back(
265 new PolyPolygonColorPrimitive2D(std::move(aNewPolyPolygon), aColor));
268 else
270 rContainer.push_back(new PolyPolygonHairlinePrimitive2D(std::move(aHairLinePolyPolygon),
271 getLineAttribute().getColor()));
275 PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon,
276 const attribute::LineAttribute& rLineAttribute,
277 attribute::StrokeAttribute aStrokeAttribute)
278 : maPolygon(std::move(aPolygon))
279 , maLineAttribute(rLineAttribute)
280 , maStrokeAttribute(std::move(aStrokeAttribute))
281 , maBufferedRange()
283 // MM01: keep these - these are no curve-decompposers but just checks
284 // simplify curve segments: moved here to not need to use it
285 // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
286 maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
289 PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon,
290 const attribute::LineAttribute& rLineAttribute)
291 : maPolygon(std::move(aPolygon))
292 , maLineAttribute(rLineAttribute)
293 , maBufferedRange()
295 // MM01: keep these - these are no curve-decompposers but just checks
296 // simplify curve segments: moved here to not need to use it
297 // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
298 maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
301 bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
303 if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
305 const PolygonStrokePrimitive2D& rCompare
306 = static_cast<const PolygonStrokePrimitive2D&>(rPrimitive);
308 return (getB2DPolygon() == rCompare.getB2DPolygon()
309 && getLineAttribute() == rCompare.getLineAttribute()
310 && getStrokeAttribute() == rCompare.getStrokeAttribute());
313 return false;
316 basegfx::B2DRange
317 PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
319 if (!maBufferedRange.isEmpty())
321 // use the view-independent, buffered B2DRange
322 return maBufferedRange;
325 if (getLineAttribute().getWidth())
327 bool bUseDecomposition(false);
329 if (basegfx::B2DLineJoin::Miter == getLineAttribute().getLineJoin())
331 // if line is mitered, use parent call since mitered line
332 // geometry may use more space than the geometry grown by half line width
333 bUseDecomposition = true;
336 if (!bUseDecomposition && css::drawing::LineCap_SQUARE == getLineAttribute().getLineCap())
338 // when drawing::LineCap_SQUARE is used the below method to grow the polygon
339 // range by half line width will not work, so use decomposition. Interestingly,
340 // the grow method below works perfectly for LineCap_ROUND since the grow is in
341 // all directions and the rounded cap needs the same grow in all directions independent
342 // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE
344 // NOTE: I thought about using [sqrt(2) * 0.5] a a factor for LineCap_SQUARE and not
345 // set bUseDecomposition. I even tried that it works. Then an auto-test failing showed
346 // not only that view-dependent stuff needs to be considered (what is done for the
347 // hairline case below), *BUT* also e.g. conversions to PNG/exports use the B2DRange
348 // of the geometry, so:
349 // - expanding by 1/2 LineWidth is OK for rounded
350 // - expanding by more (like sqrt(2) * 0.5 * LineWidth) immediately extends the size
351 // of e.g. geometry converted to PNG, plus many similar cases that cannot be thought
352 // of in advance.
353 // This means that converting those thought-experiment examples in (4) will and do lead
354 // to bigger e.g. Bitmap conversion(s), not avoiding but painting the free space. That
355 // could only be done by correctly and fully decomposing the geometry, including
356 // stroke, and accepting the cost...
357 bUseDecomposition = true;
360 if (bUseDecomposition)
362 // get correct range by using the decomposition fallback, reasons see above cases
364 // It is not a good idea to temporarily (re)set the PolygonStrokePrimitive2D
365 // to default. While it is understandable to use the speed advantage, it is
366 // bad for quite some reasons:
368 // (1) As described in include/drawinglayer/primitive2d/baseprimitive2d.hxx
369 // a Primitive is "noncopyable to make clear that a primitive is a read-only
370 // instance and copying or changing values is not intended". This is the base
371 // assumption for many decisions for Primitive handling.
372 // (2) For example, that the decomposition is *always* re-usable. It cannot change
373 // and is correct when it already exists since the values the decomposition is
374 // based on cannot change.
375 // (3) If this *is* done (like it was here) and the Primitive is derived from
376 // BufferedDecompositionPrimitive2D and thus buffers it's decomposition,
377 // the risk is that in this case the *wrong* decomposition will be used by
378 // other PrimitiveProcessors. Maybe not by the VclPixelProcessor2D/VclProcessor2D
379 // since it handles this primitive directly - not even sure for all cases.
380 // Sooner or later another PrimitiveProcessor will re-use this wrong temporary
381 // decomposition, and as an error, a non-stroked line will be painted/used.
382 // (4) The B2DRange is not strictly defined as minimal bound for the geometry,
383 // but it should be as small/tight as possible. Making it larger risks more
384 // area to be invalidated (repaint) and processed (all geometric stuff,l may
385 // include future and existing exports to other formats which are or will be
386 // implemented as PrimitiveProcessor). It is easy to imagine cases with much
387 // too large B2DRange - a line with a pattern that would solve to a single
388 // small start-rectangle and rest is empty, or a circle with a stroke that
389 // makes only a quarter of it visible.
391 // The reason to do this is speed, what is a good argument. But speed should
392 // only be used if the pair of [correctness/speed] does not sacrifice the correctness
393 // over the speed.
394 // Luckily there are alternatives to solve this and to keep [correctness/speed]
395 // valid:
397 // (a) Reset the temporary decomposition after having const-casted and
398 // changed maStrokeAttribute.
399 // Disadvantage: More const-cast hacks, plus this temporary decomposition
400 // will be potentially done repeatedly (every time
401 // PolygonStrokePrimitive2D::getB2DRange is called)
402 // (b) Use a temporary, local PolygonStrokePrimitive2D here, with neutral
403 // PolygonStrokePrimitive2D and call ::getB2DRange() at it. That way
404 // the buffered decomposition will not be harmed.
405 // Disadvantage: Same as (a), decomposition will be potentially done repeatedly
406 // (c) Use a temporary, local PolygonStrokePrimitive2D and buffer B2DRange
407 // locally for this Primitive. Due to (1)/(2) this cannot change, so
408 // when calculated once it is totally legal to use it.
410 // Thus here I would use (c): It accepts the disadvantages of (4) over speed, but
411 // avoids the errors/problems from (1-4).
412 // Additional argument for this: The hairline case below *also* uses the full
413 // B2DRange of the polygon, ignoring an evtl. stroke, so (4) applies
414 if (!getStrokeAttribute().isDefault())
416 // only do this if StrokeAttribute is used, else recursion may happen (!)
417 const rtl::Reference<primitive2d::PolygonStrokePrimitive2D>
418 aTemporaryPrimitiveWithoutStroke(new primitive2d::PolygonStrokePrimitive2D(
419 getB2DPolygon(), getLineAttribute()));
420 maBufferedRange
421 = aTemporaryPrimitiveWithoutStroke
422 ->BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
424 else
426 // fallback to normal decompose, that result can be used for visualization
427 // later, too. Still buffer B2DRange in maBufferedRange, so it needs to be
428 // merged into one B2DRange only once
429 maBufferedRange = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
432 else
434 // for all other B2DLINEJOIN_* get the range from the base geometry
435 // and expand by half the line width.
436 maBufferedRange = getB2DPolygon().getB2DRange();
437 maBufferedRange.grow(getLineAttribute().getWidth() * 0.5);
440 return maBufferedRange;
443 // It is a hairline, thus the line width is view-dependent. Get range of polygon
444 // as base size.
445 // CAUTION: Since a hairline *is* view-dependent,
446 // - either use maBufferedRange, additionally remember view-dependent
447 // factor & reset if that changes
448 // - or do not buffer for hairline -> not really needed, the range is buffered
449 // in the B2DPolygon, no decomposition is needed and a simple grow is cheap
450 basegfx::B2DRange aHairlineRange = getB2DPolygon().getB2DRange();
452 if (!aHairlineRange.isEmpty())
454 // Calculate view-dependent hairline width
455 const basegfx::B2DVector aDiscreteSize(
456 rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
457 const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
459 if (basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0))
461 aHairlineRange.grow(fDiscreteHalfLineWidth);
465 return aHairlineRange;
468 // provide unique ID
469 sal_uInt32 PolygonStrokePrimitive2D::getPrimitive2DID() const
471 return PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D;
474 void PolygonWavePrimitive2D::create2DDecomposition(
475 Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
477 if (!getB2DPolygon().count())
478 return;
480 const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth()));
481 const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight()));
483 if (bHasWidth && bHasHeight)
485 // create waveline curve
486 basegfx::B2DPolygon aWaveline(
487 basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight()));
488 rContainer.push_back(new PolygonStrokePrimitive2D(std::move(aWaveline), getLineAttribute(),
489 getStrokeAttribute()));
491 else
493 // flat waveline, decompose to simple line primitive
494 rContainer.push_back(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(),
495 getStrokeAttribute()));
499 PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon,
500 const attribute::LineAttribute& rLineAttribute,
501 const attribute::StrokeAttribute& rStrokeAttribute,
502 double fWaveWidth, double fWaveHeight)
503 : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute)
504 , mfWaveWidth(fWaveWidth)
505 , mfWaveHeight(fWaveHeight)
507 if (mfWaveWidth < 0.0)
509 mfWaveWidth = 0.0;
512 if (mfWaveHeight < 0.0)
514 mfWaveHeight = 0.0;
518 PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon,
519 const attribute::LineAttribute& rLineAttribute,
520 double fWaveWidth, double fWaveHeight)
521 : PolygonStrokePrimitive2D(rPolygon, rLineAttribute)
522 , mfWaveWidth(fWaveWidth)
523 , mfWaveHeight(fWaveHeight)
525 if (mfWaveWidth < 0.0)
527 mfWaveWidth = 0.0;
530 if (mfWaveHeight < 0.0)
532 mfWaveHeight = 0.0;
536 bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
538 if (PolygonStrokePrimitive2D::operator==(rPrimitive))
540 const PolygonWavePrimitive2D& rCompare
541 = static_cast<const PolygonWavePrimitive2D&>(rPrimitive);
543 return (getWaveWidth() == rCompare.getWaveWidth()
544 && getWaveHeight() == rCompare.getWaveHeight());
547 return false;
550 basegfx::B2DRange
551 PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
553 // get range of parent
554 basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation));
556 // if WaveHeight, grow by it
557 if (basegfx::fTools::more(getWaveHeight(), 0.0))
559 aRetval.grow(getWaveHeight());
562 // if line width, grow by it
563 if (basegfx::fTools::more(getLineAttribute().getWidth(), 0.0))
565 aRetval.grow(getLineAttribute().getWidth() * 0.5);
568 return aRetval;
571 // provide unique ID
572 sal_uInt32 PolygonWavePrimitive2D::getPrimitive2DID() const
574 return PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D;
577 void PolygonStrokeArrowPrimitive2D::create2DDecomposition(
578 Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
580 // copy local polygon, it may be changed
581 basegfx::B2DPolygon aLocalPolygon(getB2DPolygon());
582 aLocalPolygon.removeDoublePoints();
583 basegfx::B2DPolyPolygon aArrowA;
584 basegfx::B2DPolyPolygon aArrowB;
586 if (!aLocalPolygon.isClosed() && aLocalPolygon.count() > 1)
588 // apply arrows
589 const double fPolyLength(basegfx::utils::getLength(aLocalPolygon));
590 double fStart(0.0);
591 double fEnd(0.0);
592 double fStartOverlap(0.0);
593 double fEndOverlap(0.0);
595 if (!getStart().isDefault() && getStart().isActive())
597 // create start arrow primitive and consume
598 aArrowA = basegfx::utils::createAreaGeometryForLineStartEnd(
599 aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(),
600 fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart);
602 // create some overlapping, compromise between straight and peaked markers
603 // for marker width 0.3cm and marker line width 0.02cm
604 fStartOverlap = getStart().getWidth() / 15.0;
607 if (!getEnd().isDefault() && getEnd().isActive())
609 // create end arrow primitive and consume
610 aArrowB = basegfx::utils::createAreaGeometryForLineStartEnd(
611 aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(),
612 fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd);
614 // create some overlapping
615 fEndOverlap = getEnd().getWidth() / 15.0;
618 if (0.0 != fStart || 0.0 != fEnd)
620 // build new poly, consume something from old poly
621 aLocalPolygon
622 = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart - fStartOverlap,
623 fPolyLength - fEnd + fEndOverlap, fPolyLength);
627 // add shaft
628 rContainer.push_back(new PolygonStrokePrimitive2D(std::move(aLocalPolygon), getLineAttribute(),
629 getStrokeAttribute()));
631 if (aArrowA.count())
633 rContainer.push_back(
634 new PolyPolygonColorPrimitive2D(std::move(aArrowA), getLineAttribute().getColor()));
637 if (aArrowB.count())
639 rContainer.push_back(
640 new PolyPolygonColorPrimitive2D(std::move(aArrowB), getLineAttribute().getColor()));
644 PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
645 const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute,
646 const attribute::StrokeAttribute& rStrokeAttribute,
647 const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd)
648 : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute)
649 , maStart(rStart)
650 , maEnd(rEnd)
654 PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
655 const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute,
656 const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd)
657 : PolygonStrokePrimitive2D(rPolygon, rLineAttribute)
658 , maStart(rStart)
659 , maEnd(rEnd)
663 bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
665 if (PolygonStrokePrimitive2D::operator==(rPrimitive))
667 const PolygonStrokeArrowPrimitive2D& rCompare
668 = static_cast<const PolygonStrokeArrowPrimitive2D&>(rPrimitive);
670 return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd());
673 return false;
676 basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange(
677 const geometry::ViewInformation2D& rViewInformation) const
679 if (getStart().isActive() || getEnd().isActive())
681 // use decomposition when line start/end is used
682 return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
684 else
686 // get range from parent
687 return PolygonStrokePrimitive2D::getB2DRange(rViewInformation);
691 // provide unique ID
692 sal_uInt32 PolygonStrokeArrowPrimitive2D::getPrimitive2DID() const
694 return PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D;
697 } // end of namespace
699 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */