1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
21 #include <basegfx/utils/canvastools.hxx>
22 #include <basegfx/polygon/b2dpolygontools.hxx>
23 #include <basegfx/polygon/b2dpolypolygontools.hxx>
24 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
25 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
26 #include <drawinglayer/geometry/viewinformation2d.hxx>
27 #include <basegfx/polygon/b2dlinegeometry.hxx>
28 #include <com/sun/star/drawing/LineCap.hpp>
29 #include <comphelper/random.hxx>
31 #include <converters.hxx>
33 using namespace com::sun::star
;
37 namespace drawinglayer
41 PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(
42 const basegfx::B2DPolygon
& rPolygon
,
43 const basegfx::BColor
& rBColor
)
50 bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
52 if(BasePrimitive2D::operator==(rPrimitive
))
54 const PolygonHairlinePrimitive2D
& rCompare
= static_cast<const PolygonHairlinePrimitive2D
&>(rPrimitive
);
56 return (getB2DPolygon() == rCompare
.getB2DPolygon()
57 && getBColor() == rCompare
.getBColor());
63 basegfx::B2DRange
PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D
& rViewInformation
) const
65 // this is a hairline, thus the line width is view-dependent. Get range of polygon
67 basegfx::B2DRange
aRetval(getB2DPolygon().getB2DRange());
69 if(!aRetval
.isEmpty())
71 // Calculate view-dependent hairline width
72 const basegfx::B2DVector
aDiscreteSize(rViewInformation
.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
73 const double fDiscreteHalfLineWidth(aDiscreteSize
.getLength() * 0.5);
75 if(basegfx::fTools::more(fDiscreteHalfLineWidth
, 0.0))
77 aRetval
.grow(fDiscreteHalfLineWidth
);
86 ImplPrimitive2DIDBlock(PolygonHairlinePrimitive2D
, PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
)
88 } // end of namespace primitive2d
89 } // end of namespace drawinglayer
92 namespace drawinglayer
96 void PolygonMarkerPrimitive2D::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& rViewInformation
) const
98 // calculate logic DashLength
99 const basegfx::B2DVector
aDashVector(rViewInformation
.getInverseObjectToViewTransformation() * basegfx::B2DVector(getDiscreteDashLength(), 0.0));
100 const double fLogicDashLength(aDashVector
.getX());
102 if(fLogicDashLength
> 0.0 && !getRGBColorA().equal(getRGBColorB()))
104 // apply dashing; get line and gap snippets
105 std::vector
< double > aDash
;
106 basegfx::B2DPolyPolygon aDashedPolyPolyA
;
107 basegfx::B2DPolyPolygon aDashedPolyPolyB
;
109 aDash
.push_back(fLogicDashLength
);
110 aDash
.push_back(fLogicDashLength
);
111 basegfx::utils::applyLineDashing(getB2DPolygon(), aDash
, &aDashedPolyPolyA
, &aDashedPolyPolyB
, 2.0 * fLogicDashLength
);
113 rContainer
.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyA
, getRGBColorA()));
114 rContainer
.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyB
, getRGBColorB()));
118 rContainer
.push_back(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA()));
122 PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(
123 const basegfx::B2DPolygon
& rPolygon
,
124 const basegfx::BColor
& rRGBColorA
,
125 const basegfx::BColor
& rRGBColorB
,
126 double fDiscreteDashLength
)
127 : BufferedDecompositionPrimitive2D(),
129 maRGBColorA(rRGBColorA
),
130 maRGBColorB(rRGBColorB
),
131 mfDiscreteDashLength(fDiscreteDashLength
),
132 maLastInverseObjectToViewTransformation()
136 bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
138 if(BufferedDecompositionPrimitive2D::operator==(rPrimitive
))
140 const PolygonMarkerPrimitive2D
& rCompare
= static_cast<const PolygonMarkerPrimitive2D
&>(rPrimitive
);
142 return (getB2DPolygon() == rCompare
.getB2DPolygon()
143 && getRGBColorA() == rCompare
.getRGBColorA()
144 && getRGBColorB() == rCompare
.getRGBColorB()
145 && getDiscreteDashLength() == rCompare
.getDiscreteDashLength());
151 basegfx::B2DRange
PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D
& rViewInformation
) const
153 // this is a hairline, thus the line width is view-dependent. Get range of polygon
155 basegfx::B2DRange
aRetval(getB2DPolygon().getB2DRange());
157 if(!aRetval
.isEmpty())
159 // Calculate view-dependent hairline width
160 const basegfx::B2DVector
aDiscreteSize(rViewInformation
.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
161 const double fDiscreteHalfLineWidth(aDiscreteSize
.getLength() * 0.5);
163 if(basegfx::fTools::more(fDiscreteHalfLineWidth
, 0.0))
165 aRetval
.grow(fDiscreteHalfLineWidth
);
173 void PolygonMarkerPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor
& rVisitor
, const geometry::ViewInformation2D
& rViewInformation
) const
175 ::osl::MutexGuard
aGuard( m_aMutex
);
176 bool bNeedNewDecomposition(false);
178 if(!getBuffered2DDecomposition().empty())
180 if(rViewInformation
.getInverseObjectToViewTransformation() != maLastInverseObjectToViewTransformation
)
182 bNeedNewDecomposition
= true;
186 if(bNeedNewDecomposition
)
188 // conditions of last local decomposition have changed, delete
189 const_cast< PolygonMarkerPrimitive2D
* >(this)->setBuffered2DDecomposition(Primitive2DContainer());
192 if(getBuffered2DDecomposition().empty())
194 // remember last used InverseObjectToViewTransformation
195 PolygonMarkerPrimitive2D
* pThat
= const_cast< PolygonMarkerPrimitive2D
* >(this);
196 pThat
->maLastInverseObjectToViewTransformation
= rViewInformation
.getInverseObjectToViewTransformation();
199 // use parent implementation
200 BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor
, rViewInformation
);
204 ImplPrimitive2DIDBlock(PolygonMarkerPrimitive2D
, PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D
)
206 } // end of namespace primitive2d
207 } // end of namespace drawinglayer
209 namespace drawinglayer
211 double getRandomColorRange()
213 return comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX
));
216 namespace primitive2d
218 void PolygonStrokePrimitive2D::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
220 if(getB2DPolygon().count())
222 // #i102241# try to simplify before usage
223 const basegfx::B2DPolygon
aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon()));
224 basegfx::B2DPolyPolygon aHairLinePolyPolygon
;
226 if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
228 // no line dashing, just copy
229 aHairLinePolyPolygon
.append(aB2DPolygon
);
234 basegfx::utils::applyLineDashing(
235 aB2DPolygon
, getStrokeAttribute().getDotDashArray(),
236 &aHairLinePolyPolygon
, nullptr, getStrokeAttribute().getFullDotDashLen());
239 const sal_uInt32
nCount(aHairLinePolyPolygon
.count());
241 if(!getLineAttribute().isDefault() && getLineAttribute().getWidth())
243 // create fat line data
244 const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0);
245 const basegfx::B2DLineJoin
aLineJoin(getLineAttribute().getLineJoin());
246 const css::drawing::LineCap
aLineCap(getLineAttribute().getLineCap());
247 basegfx::B2DPolyPolygon aAreaPolyPolygon
;
248 const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle());
250 for(sal_uInt32
a(0); a
< nCount
; a
++)
252 // New version of createAreaGeometry; now creates bezier polygons
253 aAreaPolyPolygon
.append(basegfx::utils::createAreaGeometry(
254 aHairLinePolyPolygon
.getB2DPolygon(a
),
258 basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/ ,
259 0.4 /* default fMaxPartOfEdge*/ ,
260 fMiterMinimumAngle
));
264 for(sal_uInt32
b(0); b
< aAreaPolyPolygon
.count(); b
++)
266 // put into single polyPolygon primitives to make clear that this is NOT meant
267 // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a
268 // melting process may be used here one day.
269 const basegfx::B2DPolyPolygon
aNewPolyPolygon(aAreaPolyPolygon
.getB2DPolygon(b
));
270 const basegfx::BColor
aColor(getLineAttribute().getColor());
271 rContainer
.push_back(new PolyPolygonColorPrimitive2D(aNewPolyPolygon
, aColor
));
276 rContainer
.push_back(
277 new PolyPolygonHairlinePrimitive2D(
278 aHairLinePolyPolygon
,
279 getLineAttribute().getColor()));
284 PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
285 const basegfx::B2DPolygon
& rPolygon
,
286 const attribute::LineAttribute
& rLineAttribute
,
287 const attribute::StrokeAttribute
& rStrokeAttribute
)
288 : BufferedDecompositionPrimitive2D(),
290 maLineAttribute(rLineAttribute
),
291 maStrokeAttribute(rStrokeAttribute
)
293 // simplify curve segments: moved here to not need to use it
294 // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
295 maPolygon
= basegfx::utils::simplifyCurveSegments(maPolygon
);
298 PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
299 const basegfx::B2DPolygon
& rPolygon
,
300 const attribute::LineAttribute
& rLineAttribute
)
301 : BufferedDecompositionPrimitive2D(),
303 maLineAttribute(rLineAttribute
),
306 // simplify curve segments: moved here to not need to use it
307 // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
308 maPolygon
= basegfx::utils::simplifyCurveSegments(maPolygon
);
311 bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
313 if(BufferedDecompositionPrimitive2D::operator==(rPrimitive
))
315 const PolygonStrokePrimitive2D
& rCompare
= static_cast<const PolygonStrokePrimitive2D
&>(rPrimitive
);
317 return (getB2DPolygon() == rCompare
.getB2DPolygon()
318 && getLineAttribute() == rCompare
.getLineAttribute()
319 && getStrokeAttribute() == rCompare
.getStrokeAttribute());
325 basegfx::B2DRange
PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D
& rViewInformation
) const
327 basegfx::B2DRange aRetval
;
329 if(getLineAttribute().getWidth())
331 bool bUseDecomposition(false);
333 if(basegfx::B2DLineJoin::Miter
== getLineAttribute().getLineJoin())
335 // if line is mitered, use parent call since mitered line
336 // geometry may use more space than the geometry grown by half line width
337 bUseDecomposition
= true;
340 if(!bUseDecomposition
&& css::drawing::LineCap_SQUARE
== getLineAttribute().getLineCap())
342 // when drawing::LineCap_SQUARE is used the below method to grow the polygon
343 // range by half line width will not work, so use decomposition. Interestingly,
344 // the grow method below works perfectly for LineCap_ROUND since the grow is in
345 // all directions and the rounded cap needs the same grow in all directions independent
346 // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE
347 bUseDecomposition
= true;
350 if (bUseDecomposition
)
352 // get correct range by using the decomposition fallback, reasons see above cases
354 // ofz#947 to optimize calculating the range, turn any slow dashes into a solid line
355 // when just calculating bounds
356 attribute::StrokeAttribute aOrigStrokeAttribute
= maStrokeAttribute
;
357 const_cast<PolygonStrokePrimitive2D
*>(this)->maStrokeAttribute
= attribute::StrokeAttribute();
358 aRetval
= BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation
);
359 const_cast<PolygonStrokePrimitive2D
*>(this)->maStrokeAttribute
= aOrigStrokeAttribute
;
363 // for all other B2DLINEJOIN_* get the range from the base geometry
364 // and expand by half the line width
365 aRetval
= getB2DPolygon().getB2DRange();
366 aRetval
.grow(getLineAttribute().getWidth() * 0.5);
371 // this is a hairline, thus the line width is view-dependent. Get range of polygon
373 aRetval
= getB2DPolygon().getB2DRange();
375 if(!aRetval
.isEmpty())
377 // Calculate view-dependent hairline width
378 const basegfx::B2DVector
aDiscreteSize(rViewInformation
.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
379 const double fDiscreteHalfLineWidth(aDiscreteSize
.getLength() * 0.5);
381 if(basegfx::fTools::more(fDiscreteHalfLineWidth
, 0.0))
383 aRetval
.grow(fDiscreteHalfLineWidth
);
392 ImplPrimitive2DIDBlock(PolygonStrokePrimitive2D
, PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
)
394 } // end of namespace primitive2d
395 } // end of namespace drawinglayer
398 namespace drawinglayer
400 namespace primitive2d
402 void PolygonWavePrimitive2D::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
404 if(getB2DPolygon().count())
406 const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth()));
407 const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight()));
409 if(bHasWidth
&& bHasHeight
)
411 // create waveline curve
412 const basegfx::B2DPolygon
aWaveline(basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight()));
413 rContainer
.push_back(new PolygonStrokePrimitive2D(aWaveline
, getLineAttribute(), getStrokeAttribute()));
417 // flat waveline, decompose to simple line primitive
418 rContainer
.push_back(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), getStrokeAttribute()));
423 PolygonWavePrimitive2D::PolygonWavePrimitive2D(
424 const basegfx::B2DPolygon
& rPolygon
,
425 const attribute::LineAttribute
& rLineAttribute
,
426 const attribute::StrokeAttribute
& rStrokeAttribute
,
429 : PolygonStrokePrimitive2D(rPolygon
, rLineAttribute
, rStrokeAttribute
),
430 mfWaveWidth(fWaveWidth
),
431 mfWaveHeight(fWaveHeight
)
433 if(mfWaveWidth
< 0.0)
438 if(mfWaveHeight
< 0.0)
444 PolygonWavePrimitive2D::PolygonWavePrimitive2D(
445 const basegfx::B2DPolygon
& rPolygon
,
446 const attribute::LineAttribute
& rLineAttribute
,
449 : PolygonStrokePrimitive2D(rPolygon
, rLineAttribute
),
450 mfWaveWidth(fWaveWidth
),
451 mfWaveHeight(fWaveHeight
)
453 if(mfWaveWidth
< 0.0)
458 if(mfWaveHeight
< 0.0)
464 bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
466 if(PolygonStrokePrimitive2D::operator==(rPrimitive
))
468 const PolygonWavePrimitive2D
& rCompare
= static_cast<const PolygonWavePrimitive2D
&>(rPrimitive
);
470 return (getWaveWidth() == rCompare
.getWaveWidth()
471 && getWaveHeight() == rCompare
.getWaveHeight());
477 basegfx::B2DRange
PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D
& rViewInformation
) const
479 // get range of parent
480 basegfx::B2DRange
aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation
));
482 // if WaveHeight, grow by it
483 if(basegfx::fTools::more(getWaveHeight(), 0.0))
485 aRetval
.grow(getWaveHeight());
488 // if line width, grow by it
489 if(basegfx::fTools::more(getLineAttribute().getWidth(), 0.0))
491 aRetval
.grow(getLineAttribute().getWidth() * 0.5);
498 ImplPrimitive2DIDBlock(PolygonWavePrimitive2D
, PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D
)
500 } // end of namespace primitive2d
501 } // end of namespace drawinglayer
504 namespace drawinglayer
506 namespace primitive2d
508 void PolygonStrokeArrowPrimitive2D::create2DDecomposition(Primitive2DContainer
& rContainer
, const geometry::ViewInformation2D
& /*rViewInformation*/) const
510 // copy local polygon, it may be changed
511 basegfx::B2DPolygon
aLocalPolygon(getB2DPolygon());
512 aLocalPolygon
.removeDoublePoints();
513 basegfx::B2DPolyPolygon aArrowA
;
514 basegfx::B2DPolyPolygon aArrowB
;
516 if(!aLocalPolygon
.isClosed() && aLocalPolygon
.count() > 1)
519 const double fPolyLength(basegfx::utils::getLength(aLocalPolygon
));
522 double fStartOverlap(0.0);
523 double fEndOverlap(0.0);
525 if(!getStart().isDefault() && getStart().isActive())
527 // create start arrow primitive and consume
528 aArrowA
= basegfx::utils::createAreaGeometryForLineStartEnd(
529 aLocalPolygon
, getStart().getB2DPolyPolygon(), true, getStart().getWidth(),
530 fPolyLength
, getStart().isCentered() ? 0.5 : 0.0, &fStart
);
532 // create some overlapping, compromise between straight and peaked markers
533 // for marker width 0.3cm and marker line width 0.02cm
534 fStartOverlap
= getStart().getWidth() / 15.0;
537 if(!getEnd().isDefault() && getEnd().isActive())
539 // create end arrow primitive and consume
540 aArrowB
= basegfx::utils::createAreaGeometryForLineStartEnd(
541 aLocalPolygon
, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(),
542 fPolyLength
, getEnd().isCentered() ? 0.5 : 0.0, &fEnd
);
544 // create some overlapping
545 fEndOverlap
= getEnd().getWidth() / 15.0;
548 if(0.0 != fStart
|| 0.0 != fEnd
)
550 // build new poly, consume something from old poly
551 aLocalPolygon
= basegfx::utils::getSnippetAbsolute(aLocalPolygon
, fStart
-fStartOverlap
, fPolyLength
- fEnd
+ fEndOverlap
, fPolyLength
);
556 rContainer
.push_back(new
557 PolygonStrokePrimitive2D(
558 aLocalPolygon
, getLineAttribute(), getStrokeAttribute()));
562 rContainer
.push_back(
563 new PolyPolygonColorPrimitive2D(
564 aArrowA
, getLineAttribute().getColor()));
569 rContainer
.push_back(
570 new PolyPolygonColorPrimitive2D(
571 aArrowB
, getLineAttribute().getColor()));
575 PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
576 const basegfx::B2DPolygon
& rPolygon
,
577 const attribute::LineAttribute
& rLineAttribute
,
578 const attribute::StrokeAttribute
& rStrokeAttribute
,
579 const attribute::LineStartEndAttribute
& rStart
,
580 const attribute::LineStartEndAttribute
& rEnd
)
581 : PolygonStrokePrimitive2D(rPolygon
, rLineAttribute
, rStrokeAttribute
),
587 PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
588 const basegfx::B2DPolygon
& rPolygon
,
589 const attribute::LineAttribute
& rLineAttribute
,
590 const attribute::LineStartEndAttribute
& rStart
,
591 const attribute::LineStartEndAttribute
& rEnd
)
592 : PolygonStrokePrimitive2D(rPolygon
, rLineAttribute
),
598 bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
600 if(PolygonStrokePrimitive2D::operator==(rPrimitive
))
602 const PolygonStrokeArrowPrimitive2D
& rCompare
= static_cast<const PolygonStrokeArrowPrimitive2D
&>(rPrimitive
);
604 return (getStart() == rCompare
.getStart()
605 && getEnd() == rCompare
.getEnd());
611 basegfx::B2DRange
PolygonStrokeArrowPrimitive2D::getB2DRange(const geometry::ViewInformation2D
& rViewInformation
) const
613 if(getStart().isActive() || getEnd().isActive())
615 // use decomposition when line start/end is used
616 return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation
);
620 // get range from parent
621 return PolygonStrokePrimitive2D::getB2DRange(rViewInformation
);
626 ImplPrimitive2DIDBlock(PolygonStrokeArrowPrimitive2D
, PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D
)
628 } // end of namespace primitive2d
629 } // end of namespace drawinglayer
631 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */