QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgslayoutitempolyline.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitempolyline.cpp
3 begin : March 2016
4 copyright : (C) 2016 Paul Blottiere, Oslandia
5 email : paul dot blottiere at oslandia dot com
6 ***************************************************************************/
7
8/***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include <limits>
20
21#include "qgscolorutils.h"
22#include "qgslayout.h"
25#include "qgslayoututils.h"
26#include "qgslinesymbol.h"
27#include "qgsreadwritecontext.h"
29#include "qgssvgcache.h"
30#include "qgssymbol.h"
31#include "qgssymbollayerutils.h"
32
33#include <QGraphicsPathItem>
34#include <QString>
35#include <QSvgRenderer>
36#include <QVector2D>
37
38#include "moc_qgslayoutitempolyline.cpp"
39
40using namespace Qt::StringLiterals;
41
44{
45 createDefaultPolylineStyleSymbol();
46}
47
49 : QgsLayoutNodesItem( polyline, layout )
50{
51 createDefaultPolylineStyleSymbol();
52}
53
55
60
65
67{
68 return QgsApplication::getThemeIcon( u"/mLayoutItemPolyline.svg"_s );
69}
70
71bool QgsLayoutItemPolyline::_addNode( const int indexPoint,
72 QPointF newPoint,
73 const double radius )
74{
75 const double distStart = computeDistance( newPoint, mPolygon[0] );
76 const double distEnd = computeDistance( newPoint, mPolygon[mPolygon.size() - 1] );
77
78 if ( indexPoint == ( mPolygon.size() - 1 ) )
79 {
80 if ( distEnd < radius )
81 mPolygon.append( newPoint );
82 else if ( distStart < radius )
83 mPolygon.insert( 0, newPoint );
84 }
85 else
86 mPolygon.insert( indexPoint + 1, newPoint );
87
88 return true;
89}
90
92{
93 if ( index < 0 || index >= mPolygon.size() || mPolygon.size() <= 2 )
94 return false;
95
96 mPolygon.remove( index );
97
98 int newSelectNode = index;
99 if ( index >= mPolygon.size() )
100 newSelectNode = mPolygon.size() - 1;
101 setSelectedNode( newSelectNode );
102
103 return true;
104}
105
106void QgsLayoutItemPolyline::createDefaultPolylineStyleSymbol()
107{
108 QVariantMap properties;
109 properties.insert( u"color"_s, u"0,0,0,255"_s );
110 properties.insert( u"width"_s, u"0.3"_s );
111 properties.insert( u"capstyle"_s, u"square"_s );
112
113 mPolylineStyleSymbol = QgsLineSymbol::createSimple( properties );
114 refreshSymbol();
115}
116
117void QgsLayoutItemPolyline::refreshSymbol()
118{
119 if ( auto *lLayout = layout() )
120 {
121 const QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( lLayout, nullptr, lLayout->renderContext().dpi() );
122 mMaxSymbolBleed = ( 25.4 / lLayout->renderContext().dpi() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mPolylineStyleSymbol.get(), rc );
123 }
124
126
127 emit frameChanged();
128}
129
130void QgsLayoutItemPolyline::drawStartMarker( QPainter *painter )
131{
132 if ( mPolygon.size() < 2 )
133 return;
134
135 switch ( mStartMarker )
136 {
138 break;
139
141 {
142 // calculate angle at start of line
143 const QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
144 const double angle = startLine.angle();
145 drawArrow( painter, mPolygon.at( 0 ), angle );
146 break;
147 }
148
150 {
151 // calculate angle at start of line
152 const QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
153 const double angle = startLine.angle();
154 drawSvgMarker( painter, mPolygon.at( 0 ), angle, mStartMarkerFile, mStartArrowHeadHeight );
155 break;
156 }
157 }
158
159}
160
161void QgsLayoutItemPolyline::drawEndMarker( QPainter *painter )
162{
163 if ( mPolygon.size() < 2 )
164 return;
165
166 switch ( mEndMarker )
167 {
169 break;
170
172 {
173 // calculate angle at end of line
174 const QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
175 const double angle = endLine.angle();
176
177 //move end point depending on arrow width
178 const QVector2D dir = QVector2D( endLine.dx(), endLine.dy() ).normalized();
179 QPointF endPoint = endLine.p2();
180 endPoint += ( dir * 0.5 * mArrowHeadWidth ).toPointF();
181
182 drawArrow( painter, endPoint, angle );
183 break;
184 }
186 {
187 // calculate angle at end of line
188 const QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
189 const double angle = endLine.angle();
190 drawSvgMarker( painter, endLine.p2(), angle, mEndMarkerFile, mEndArrowHeadHeight );
191 break;
192 }
193 }
194}
195
196void QgsLayoutItemPolyline::drawArrow( QPainter *painter, QPointF center, double angle )
197{
198 // translate angle from ccw from axis to cw from north
199 angle = 90 - angle;
200 QPen p;
201 p.setColor( mArrowHeadStrokeColor );
202 p.setWidthF( mArrowHeadStrokeWidth );
203 painter->setPen( p );
204 QBrush b;
205 b.setColor( mArrowHeadFillColor );
206 painter->setBrush( b );
207
208 drawArrowHead( painter, center.x(), center.y(), angle, mArrowHeadWidth );
209}
210
211void QgsLayoutItemPolyline::updateMarkerSvgSizes()
212{
213 setStartSvgMarkerPath( mStartMarkerFile );
214 setEndSvgMarkerPath( mEndMarkerFile );
215}
216
217void QgsLayoutItemPolyline::drawArrowHead( QPainter *p, const double x, const double y, const double angle, const double arrowHeadWidth )
218{
219 if ( !p )
220 return;
221
222 const double angleRad = angle / 180.0 * M_PI;
223 const QPointF middlePoint( x, y );
224
225 //rotate both arrow points
226 const QPointF p1 = QPointF( -arrowHeadWidth / 2.0, arrowHeadWidth );
227 const QPointF p2 = QPointF( arrowHeadWidth / 2.0, arrowHeadWidth );
228
229 QPointF p1Rotated, p2Rotated;
230 p1Rotated.setX( p1.x() * std::cos( angleRad ) + p1.y() * -std::sin( angleRad ) );
231 p1Rotated.setY( p1.x() * std::sin( angleRad ) + p1.y() * std::cos( angleRad ) );
232 p2Rotated.setX( p2.x() * std::cos( angleRad ) + p2.y() * -std::sin( angleRad ) );
233 p2Rotated.setY( p2.x() * std::sin( angleRad ) + p2.y() * std::cos( angleRad ) );
234
235 QPolygonF arrowHeadPoly;
236 arrowHeadPoly << middlePoint;
237 arrowHeadPoly << QPointF( middlePoint.x() + p1Rotated.x(), middlePoint.y() + p1Rotated.y() );
238 arrowHeadPoly << QPointF( middlePoint.x() + p2Rotated.x(), middlePoint.y() + p2Rotated.y() );
239 QPen arrowPen = p->pen();
240 arrowPen.setJoinStyle( Qt::RoundJoin );
241 QBrush arrowBrush = p->brush();
242 arrowBrush.setStyle( Qt::SolidPattern );
243 p->setPen( arrowPen );
244 p->setBrush( arrowBrush );
245 arrowBrush.setStyle( Qt::SolidPattern );
246 p->drawPolygon( arrowHeadPoly );
247}
248
249void QgsLayoutItemPolyline::drawSvgMarker( QPainter *p, QPointF point, double angle, const QString &markerPath, double height ) const
250{
251 // translate angle from ccw from axis to cw from north
252 angle = 90 - angle;
253
254 if ( mArrowHeadWidth <= 0 || height <= 0 )
255 {
256 //bad image size
257 return;
258 }
259
260 if ( markerPath.isEmpty() )
261 return;
262
263 QSvgRenderer r;
264 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( markerPath, mArrowHeadWidth, mArrowHeadFillColor, mArrowHeadStrokeColor, mArrowHeadStrokeWidth,
265 1.0 );
266 r.load( svgContent );
267
268 const QgsScopedQPainterState painterState( p );
269 p->translate( point.x(), point.y() );
270 p->rotate( angle );
271 p->translate( -mArrowHeadWidth / 2.0, -height / 2.0 );
272 r.render( p, QRectF( 0, 0, mArrowHeadWidth, height ) );
273}
274
276{
277 if ( !id().isEmpty() )
278 return id();
279
280 return tr( "<Polyline>" );
281}
282
283void QgsLayoutItemPolyline::_draw( QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem * )
284{
285 QgsRenderContext renderContext = context.renderContext();
286 // symbol clipping messes with geometry generators used in the symbol for this item, and has no
287 // valid use here. See https://github.com/qgis/QGIS/issues/58909
289
290 const QgsScopedQPainterState painterState( renderContext.painter() );
291 //setup painter scaling to dots so that raster symbology is drawn to scale
292 const double scale = renderContext.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
293 const QTransform t = QTransform::fromScale( scale, scale );
294
295 mPolylineStyleSymbol->startRender( renderContext );
296 mPolylineStyleSymbol->renderPolyline( t.map( mPolygon ), nullptr, renderContext );
297 mPolylineStyleSymbol->stopRender( renderContext );
298
299 // painter is scaled to dots, so scale back to layout units
300 renderContext.painter()->scale( renderContext.scaleFactor(), renderContext.scaleFactor() );
301
302 drawStartMarker( renderContext.painter() );
303 drawEndMarker( renderContext.painter() );
304}
305
306void QgsLayoutItemPolyline::_readXmlStyle( const QDomElement &elmt, const QgsReadWriteContext &context )
307{
308 mPolylineStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol>( elmt, context );
309}
310
312{
313 mPolylineStyleSymbol.reset( static_cast<QgsLineSymbol *>( symbol->clone() ) );
314 refreshSymbol();
315}
316
318{
319 mStartMarker = mode;
320 update();
321}
322
324{
325 mEndMarker = mode;
326 update();
327}
328
330{
331 mArrowHeadWidth = width;
332 updateMarkerSvgSizes();
333 update();
334}
335
337{
338 QPainterPath path;
339 path.addPolygon( mPolygon );
340
341 QPainterPathStroker ps;
342
343 ps.setWidth( 2 * mMaxSymbolBleed );
344 const QPainterPath strokedOutline = ps.createStroke( path );
345
346 return strokedOutline;
347}
348
350{
351 // A Polyline is valid if it has at least 2 unique points
352 QList<QPointF> uniquePoints;
353 int seen = 0;
354 for ( QPointF point : mPolygon )
355 {
356 if ( !uniquePoints.contains( point ) )
357 {
358 uniquePoints.append( point );
359 if ( ++seen > 1 )
360 return true;
361 }
362 }
363 return false;
364}
365
367{
368 return mPolylineStyleSymbol.get();
369}
370
372{
373 QSvgRenderer r;
374 mStartMarkerFile = path;
375 if ( path.isEmpty() || !r.load( path ) )
376 {
377 mStartArrowHeadHeight = 0;
378 }
379 else
380 {
381 //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
382 const QRect viewBox = r.viewBox();
383 mStartArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
384 }
386}
387
389{
390 QSvgRenderer r;
391 mEndMarkerFile = path;
392 if ( path.isEmpty() || !r.load( path ) )
393 {
394 mEndArrowHeadHeight = 0;
395 }
396 else
397 {
398 //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
399 const QRect viewBox = r.viewBox();
400 mEndArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
401 }
403}
404
406{
407 mArrowHeadStrokeColor = color;
408 update();
409}
410
412{
413 mArrowHeadFillColor = color;
414 update();
415}
416
418{
419 mArrowHeadStrokeWidth = width;
421 update();
422}
423
425{
426 if ( mPolylineStyleSymbol )
427 {
428 QgsStyleSymbolEntity entity( mPolylineStyleSymbol.get() );
429 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, uuid(), displayName() ) ) )
430 return false;
431 }
432
433 return true;
434}
435
436void QgsLayoutItemPolyline::_writeXmlStyle( QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context ) const
437{
438 const QDomElement pe = QgsSymbolLayerUtils::saveSymbol( QString(),
439 mPolylineStyleSymbol.get(),
440 doc,
441 context );
442 elmt.appendChild( pe );
443}
444
445bool QgsLayoutItemPolyline::writePropertiesToElement( QDomElement &elmt, QDomDocument &doc, const QgsReadWriteContext &context ) const
446{
448
449 // absolute paths to relative
450 const QString startMarkerPath = QgsSymbolLayerUtils::svgSymbolPathToName( mStartMarkerFile, context.pathResolver() );
451 const QString endMarkerPath = QgsSymbolLayerUtils::svgSymbolPathToName( mEndMarkerFile, context.pathResolver() );
452
453 elmt.setAttribute( u"arrowHeadWidth"_s, QString::number( mArrowHeadWidth ) );
454 elmt.setAttribute( u"arrowHeadFillColor"_s, QgsColorUtils::colorToString( mArrowHeadFillColor ) );
455 elmt.setAttribute( u"arrowHeadOutlineColor"_s, QgsColorUtils::colorToString( mArrowHeadStrokeColor ) );
456 elmt.setAttribute( u"outlineWidth"_s, QString::number( mArrowHeadStrokeWidth ) );
457 elmt.setAttribute( u"markerMode"_s, mEndMarker );
458 elmt.setAttribute( u"startMarkerMode"_s, mStartMarker );
459 elmt.setAttribute( u"startMarkerFile"_s, startMarkerPath );
460 elmt.setAttribute( u"endMarkerFile"_s, endMarkerPath );
461
462 return true;
463}
464
465bool QgsLayoutItemPolyline::readPropertiesFromElement( const QDomElement &elmt, const QDomDocument &doc, const QgsReadWriteContext &context )
466{
467 mArrowHeadWidth = elmt.attribute( u"arrowHeadWidth"_s, u"2.0"_s ).toDouble();
468 mArrowHeadFillColor = QgsColorUtils::colorFromString( elmt.attribute( u"arrowHeadFillColor"_s, u"0,0,0,255"_s ) );
469 mArrowHeadStrokeColor = QgsColorUtils::colorFromString( elmt.attribute( u"arrowHeadOutlineColor"_s, u"0,0,0,255"_s ) );
470 mArrowHeadStrokeWidth = elmt.attribute( u"outlineWidth"_s, u"1.0"_s ).toDouble();
471 // relative paths to absolute
472 const QString startMarkerPath = elmt.attribute( u"startMarkerFile"_s, QString() );
473 const QString endMarkerPath = elmt.attribute( u"endMarkerFile"_s, QString() );
476 mEndMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( u"markerMode"_s, u"0"_s ).toInt() );
477 mStartMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( u"startMarkerMode"_s, u"0"_s ).toInt() );
478
480
482 return true;
483}
484
486{
487 QRectF br = rect();
488
489 double margin = std::max( mMaxSymbolBleed, computeMarkerMargin() );
490 if ( mEndMarker == ArrowHead )
491 {
492 margin += 0.5 * mArrowHeadWidth;
493 }
494 br.adjust( -margin, -margin, margin, margin );
495 prepareGeometryChange();
497
498 // update
499 update();
500}
501
502
503double QgsLayoutItemPolyline::computeMarkerMargin() const
504{
505 double margin = 0;
506
507 if ( mStartMarker == ArrowHead || mEndMarker == ArrowHead )
508 {
509 margin = mArrowHeadStrokeWidth / 2.0 + mArrowHeadWidth * M_SQRT2;
510 }
511
512 if ( mStartMarker == SvgMarker )
513 {
514 const double startMarkerMargin = std::sqrt( 0.25 * ( mStartArrowHeadHeight * mStartArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
515 margin = std::max( startMarkerMargin, margin );
516 }
517
518 if ( mEndMarker == SvgMarker )
519 {
520 const double endMarkerMargin = std::sqrt( 0.25 * ( mEndArrowHeadHeight * mEndArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
521 margin = std::max( endMarkerMargin, margin );
522 }
523
524 return margin;
525}
@ Millimeters
Millimeters.
Definition qgis.h:5256
@ DisableSymbolClippingToExtent
Force symbol clipping to map extent to be disabled in all situations. This will result in slower rend...
Definition qgis.h:2828
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
bool isValid() const override
Must be reimplemented in subclasses.
void setArrowHeadWidth(double width)
Sets the width of line arrow heads in mm.
void setEndMarker(MarkerMode mode)
Sets the end marker mode, which controls what marker is drawn at the end of the line.
void setEndSvgMarkerPath(const QString &path)
Sets the path to a SVG marker to draw at the end of the line.
void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const override
Method called in writeXml.
void setArrowHeadStrokeWidth(double width)
Sets the pen width in millimeters for the stroke of the arrow head.
void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context) override
Method called in readXml.
void setArrowHeadFillColor(const QColor &color)
Sets the color used to fill the arrow head.
bool _removeNode(int nodeIndex) override
Method called in removeNode.
QgsLineSymbol * symbol()
Returns the line symbol used to draw the shape.
void setArrowHeadStrokeColor(const QColor &color)
Sets the color used to draw the stroke around the arrow head.
void setStartMarker(MarkerMode mode)
Sets the start marker mode, which controls what marker is drawn at the start of the line.
MarkerMode
Vertex marker mode.
@ ArrowHead
Show arrow marker.
@ NoMarker
Don't show marker.
QPainterPath shape() const override
~QgsLayoutItemPolyline() override
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
QString displayName() const override
Gets item display name.
static QgsLayoutItemPolyline * create(QgsLayout *layout)
Returns a new polyline item for the specified layout.
bool _addNode(int indexPoint, QPointF newPoint, double radius) override
Method called in addNode.
void setSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the shape.
QIcon icon() const override
Returns the item's icon.
double arrowHeadWidth() const
Returns the width of line arrow heads in mm.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr) override
Method called in paint.
void setStartSvgMarkerPath(const QString &path)
Sets the path to a SVG marker to draw at the start of the line.
QgsLayoutItemPolyline(QgsLayout *layout)
Constructor for QgsLayoutItemPolyline for the specified layout.
@ LayoutPolyline
Polyline shape item.
Contains settings and helpers relating to a render of a QgsLayoutItem.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
friend class QgsLayout
virtual QString uuid() const
Returns the item identification string.
QString id() const
Returns the item's ID name.
void frameChanged()
Emitted if the item's frame style changes.
double mMaxSymbolBleed
Max symbol bleed.
QRectF mCurrentRectangle
Current bounding rectangle of shape.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
bool setSelectedNode(int index)
Selects a node by index.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
void updateSceneRect()
Update the current scene rectangle for this item.
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidean distance between 2 nodes.
QgsLayoutNodesItem(QgsLayout *layout)
Constructor for QgsLayoutNodesItem, attached to the specified layout.
QPolygonF mPolygon
Shape's nodes.
const QgsLayout * layout() const
Returns the layout the object is attached to.
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
A line symbol type, for rendering LineString and MultiLineString geometries.
static std::unique_ptr< QgsLineSymbol > createSimple(const QVariantMap &properties)
Create a line symbol with one symbol layer: SimpleLine with specified properties.
A container for the context for various read/write operations on objects.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
Scoped object for saving and restoring a QPainter object's state.
An interface for classes which can visit style entity (e.g.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Definition qgsstyle.h:1398
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >(), bool *isMissingImage=nullptr)
Gets the SVG content corresponding to the given path.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static std::unique_ptr< QgsSymbol > loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
Contains information relating to the style entity currently being visited.