diff --git a/python/core/auto_generated/callouts/qgscallout.sip.in b/python/core/auto_generated/callouts/qgscallout.sip.in
index c760d4e050e..e471d706dc6 100644
--- a/python/core/auto_generated/callouts/qgscallout.sip.in
+++ b/python/core/auto_generated/callouts/qgscallout.sip.in
@@ -48,6 +48,7 @@ relevant symbology elements to render them.
OffsetFromLabel,
DrawCalloutToAllParts,
AnchorPointPosition,
+ LabelAnchorPointPosition,
};
enum DrawOrder
@@ -64,6 +65,20 @@ relevant symbology elements to render them.
Centroid,
};
+ enum LabelAnchorPoint
+ {
+ LabelPointOnExterior,
+ LabelCentroid,
+ LabelTopLeft,
+ LabelTopMiddle,
+ LabelTopRight,
+ LabelMiddleLeft,
+ LabelMiddleRight,
+ LabelBottomLeft,
+ LabelBottomMiddle,
+ LabelBottomRight,
+ };
+
QgsCallout();
%Docstring
Constructor for QgsCallout.
@@ -272,6 +287,51 @@ anchor point.
.. seealso:: :py:func:`encodeAnchorPoint`
%End
+
+ LabelAnchorPoint labelAnchorPoint() const;
+%Docstring
+Returns the label's anchor point position.
+
+.. seealso:: :py:func:`setLabelAnchorPoint`
+
+.. versionadded:: 3.14
+%End
+
+ void setLabelAnchorPoint( LabelAnchorPoint anchor );
+%Docstring
+Sets the label's ``anchor`` point position.
+
+.. seealso:: :py:func:`labelAnchorPoint`
+
+.. versionadded:: 3.14
+%End
+
+ static QString encodeLabelAnchorPoint( LabelAnchorPoint anchor );
+%Docstring
+Encodes a label ``anchor`` point to its string representation.
+
+:return: encoded string
+
+.. seealso:: :py:func:`decodeLabelAnchorPoint`
+
+.. versionadded:: 3.14
+%End
+
+ static QgsCallout::LabelAnchorPoint decodeLabelAnchorPoint( const QString &name, bool *ok = 0 );
+%Docstring
+Attempts to decode a string representation of a label anchor point name to the corresponding
+anchor point.
+
+:param name: encoded label anchor point name
+:param ok: if specified, will be set to ``True`` if the anchor point was successfully decoded
+
+:return: decoded name
+
+.. seealso:: :py:func:`encodeLabelAnchorPoint`
+
+.. versionadded:: 3.14
+%End
+
protected:
virtual void draw( QgsRenderContext &context, QRectF bodyBoundingBox, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext ) = 0;
@@ -292,6 +352,13 @@ Both ``rect`` and ``anchor`` are specified in painter coordinates (i.e. pixels).
The ``calloutContext`` argument is used to specify additional contextual information about
how a callout is being rendered.
+%End
+
+ QgsGeometry labelAnchorGeometry( QRectF bodyBoundingBox, const double angle, LabelAnchorPoint anchor ) const;
+%Docstring
+Returns the anchor point geometry for a label with the given bounding box and ``anchor`` point mode.
+
+.. versionadded:: 3.14
%End
};
diff --git a/src/core/callouts/qgscallout.cpp b/src/core/callouts/qgscallout.cpp
index 326649db22e..bbdce27fad7 100644
--- a/src/core/callouts/qgscallout.cpp
+++ b/src/core/callouts/qgscallout.cpp
@@ -39,6 +39,13 @@ void QgsCallout::initPropertyDefinitions()
{ QgsCallout::OffsetFromLabel, QgsPropertyDefinition( "OffsetFromLabel", QObject::tr( "Offset from label" ), QgsPropertyDefinition::DoublePositive, origin ) },
{ QgsCallout::DrawCalloutToAllParts, QgsPropertyDefinition( "DrawCalloutToAllParts", QObject::tr( "Draw lines to all feature parts" ), QgsPropertyDefinition::Boolean, origin ) },
{ QgsCallout::AnchorPointPosition, QgsPropertyDefinition( "AnchorPointPosition", QgsPropertyDefinition::DataTypeString, QObject::tr( "Feature's anchor point position" ), QObject::tr( "string " ) + "[pole_of_inaccessibility|point_on_exterior|point_on_surface|centroid]", origin ) },
+ {
+ QgsCallout::LabelAnchorPointPosition, QgsPropertyDefinition( "LabelAnchorPointPosition", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label's anchor point position" ), QObject::tr( "string " ) + "[point_on_exterior|centroid|TL=Top left|T=Top middle|"
+ "TR=Top right|
"
+ "L=Left|R=Right|
"
+ "BL=Bottom left|B=Bottom middle|"
+ "BR=Bottom right]", origin )
+ },
};
}
@@ -52,6 +59,7 @@ QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
QVariantMap props;
props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
props.insert( QStringLiteral( "anchorPoint" ), encodeAnchorPoint( mAnchorPoint ) );
+ props.insert( QStringLiteral( "labelAnchorPoint" ), encodeLabelAnchorPoint( mLabelAnchorPoint ) );
props.insert( QStringLiteral( "ddProperties" ), mDataDefinedProperties.toVariant( propertyDefinitions() ) );
return props;
}
@@ -59,7 +67,8 @@ QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
void QgsCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext & )
{
mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
- mAnchorPoint = decodeAnchorPoint( props.value( QStringLiteral( "anchorPoint" ), QString( "" ) ).toString() );
+ mAnchorPoint = decodeAnchorPoint( props.value( QStringLiteral( "anchorPoint" ), QString() ).toString() );
+ mLabelAnchorPoint = decodeLabelAnchorPoint( props.value( QStringLiteral( "labelAnchorPoint" ), QString() ).toString() );
mDataDefinedProperties.loadVariant( props.value( QStringLiteral( "ddProperties" ) ), propertyDefinitions() );
}
@@ -184,6 +193,96 @@ QString QgsCallout::encodeAnchorPoint( AnchorPoint anchor )
return QString();
}
+QString QgsCallout::encodeLabelAnchorPoint( QgsCallout::LabelAnchorPoint anchor )
+{
+ switch ( anchor )
+ {
+ case LabelPointOnExterior:
+ return QStringLiteral( "point_on_exterior" );
+ case LabelCentroid:
+ return QStringLiteral( "centroid" );
+ case LabelTopLeft:
+ return QStringLiteral( "tl" );
+ case LabelTopMiddle:
+ return QStringLiteral( "t" );
+ case LabelTopRight:
+ return QStringLiteral( "tr" );
+ case LabelMiddleLeft:
+ return QStringLiteral( "l" );
+ case LabelMiddleRight:
+ return QStringLiteral( "r" );
+ case LabelBottomLeft:
+ return QStringLiteral( "bl" );
+ case LabelBottomMiddle:
+ return QStringLiteral( "b" );
+ case LabelBottomRight:
+ return QStringLiteral( "br" );
+ }
+
+ return QString();
+}
+
+QgsCallout::LabelAnchorPoint QgsCallout::decodeLabelAnchorPoint( const QString &name, bool *ok )
+{
+ if ( ok )
+ *ok = true;
+ QString cleaned = name.toLower().trimmed();
+
+ if ( cleaned == QLatin1String( "point_on_exterior" ) )
+ return LabelPointOnExterior;
+ else if ( cleaned == QLatin1String( "centroid" ) )
+ return LabelCentroid;
+ else if ( cleaned == QLatin1String( "tl" ) )
+ return LabelTopLeft;
+ else if ( cleaned == QLatin1String( "t" ) )
+ return LabelTopMiddle;
+ else if ( cleaned == QLatin1String( "tr" ) )
+ return LabelTopRight;
+ else if ( cleaned == QLatin1String( "l" ) )
+ return LabelMiddleLeft;
+ else if ( cleaned == QLatin1String( "r" ) )
+ return LabelMiddleRight;
+ else if ( cleaned == QLatin1String( "bl" ) )
+ return LabelBottomLeft;
+ else if ( cleaned == QLatin1String( "b" ) )
+ return LabelBottomMiddle;
+ else if ( cleaned == QLatin1String( "br" ) )
+ return LabelBottomRight;
+
+ if ( ok )
+ *ok = false;
+ return LabelPointOnExterior;
+}
+
+QgsGeometry QgsCallout::labelAnchorGeometry( QRectF rect, const double, LabelAnchorPoint anchor ) const
+{
+ QgsGeometry label( QgsGeometry::fromRect( rect ) );
+ switch ( anchor )
+ {
+ case LabelPointOnExterior:
+ return label;
+ case LabelCentroid:
+ return label.centroid();
+ case LabelTopLeft:
+ return QgsGeometry::fromPointXY( QgsPointXY( rect.bottomLeft() ) );
+ case LabelTopMiddle:
+ return QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.bottom() ) );
+ case LabelTopRight:
+ return QgsGeometry::fromPointXY( QgsPointXY( rect.bottomRight() ) );
+ case LabelMiddleLeft:
+ return QgsGeometry::fromPointXY( QgsPointXY( rect.left(), ( rect.top() + rect.bottom() ) / 2.0 ) );
+ case LabelMiddleRight:
+ return QgsGeometry::fromPointXY( QgsPointXY( rect.right(), ( rect.top() + rect.bottom() ) / 2.0 ) );
+ case LabelBottomLeft:
+ return QgsGeometry::fromPointXY( QgsPointXY( rect.topLeft() ) );
+ case LabelBottomMiddle:
+ return QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.top() ) );
+ case LabelBottomRight:
+ return QgsGeometry::fromPointXY( QgsPointXY( rect.topRight() ) );
+ }
+ return label;
+}
+
//
// QgsSimpleLineCallout
//
@@ -312,9 +411,17 @@ void QgsSimpleLineCallout::setLineSymbol( QgsLineSymbol *symbol )
mLineSymbol.reset( symbol );
}
-void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const double, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
+void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
{
- QgsGeometry label( QgsGeometry::fromRect( rect ) );
+ LabelAnchorPoint labelAnchor = labelAnchorPoint();
+ if ( dataDefinedProperties().isActive( QgsCallout::LabelAnchorPointPosition ) )
+ {
+ QString encodedAnchor = encodeLabelAnchorPoint( labelAnchor );
+ context.expressionContext().setOriginalValueVariable( encodedAnchor );
+ labelAnchor = decodeLabelAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::LabelAnchorPointPosition, context.expressionContext(), encodedAnchor ) );
+ }
+ QgsGeometry label = labelAnchorGeometry( rect, angle, labelAnchor );
+
auto drawCalloutLine = [this, &context, &label]( const QgsGeometry & partAnchor )
{
QgsGeometry line;
@@ -451,9 +558,17 @@ QgsManhattanLineCallout *QgsManhattanLineCallout::clone() const
return new QgsManhattanLineCallout( *this );
}
-void QgsManhattanLineCallout::draw( QgsRenderContext &context, QRectF rect, const double, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
+void QgsManhattanLineCallout::draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
{
- QgsGeometry label( QgsGeometry::fromRect( rect ) );
+ LabelAnchorPoint labelAnchor = labelAnchorPoint();
+ if ( dataDefinedProperties().isActive( QgsCallout::LabelAnchorPointPosition ) )
+ {
+ QString encodedAnchor = encodeLabelAnchorPoint( labelAnchor );
+ context.expressionContext().setOriginalValueVariable( encodedAnchor );
+ labelAnchor = decodeLabelAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::LabelAnchorPointPosition, context.expressionContext(), encodedAnchor ) );
+ }
+ QgsGeometry label = labelAnchorGeometry( rect, angle, labelAnchor );
+
auto drawCalloutLine = [this, &context, &label]( const QgsGeometry & partAnchor )
{
QgsGeometry line;
diff --git a/src/core/callouts/qgscallout.h b/src/core/callouts/qgscallout.h
index c62399ae94b..bed551260fc 100644
--- a/src/core/callouts/qgscallout.h
+++ b/src/core/callouts/qgscallout.h
@@ -73,6 +73,7 @@ class CORE_EXPORT QgsCallout
OffsetFromLabel, //!< Distance to offset lines from label area
DrawCalloutToAllParts, //!< Whether callout lines should be drawn to all feature parts
AnchorPointPosition, //!< Feature's anchor point position
+ LabelAnchorPointPosition, //!< Label's anchor point position
};
//! Options for draw order (stacking) of callouts
@@ -91,6 +92,24 @@ class CORE_EXPORT QgsCallout
Centroid, //!< The surface's centroid is used as anchor for polygon geometries
};
+ /**
+ * Label's anchor point position.
+ * \since QGIS 3.14
+ */
+ enum LabelAnchorPoint
+ {
+ LabelPointOnExterior, //!< The point on the label's boundary closest to the feature
+ LabelCentroid, //!< The labe's centroid
+ LabelTopLeft, //!< Top left corner of the label's boundary
+ LabelTopMiddle, //!< Top middle of the label's boundary
+ LabelTopRight, //!< Top right corner of the label's boundary
+ LabelMiddleLeft, //!< Middle left of the label's boundary
+ LabelMiddleRight, //!< Middle right of the label's boundary
+ LabelBottomLeft, //!< Bottom left corner of the label's boundary
+ LabelBottomMiddle, //!< Bottom middle of the label's boundary
+ LabelBottomRight, //!< Bottom right corner of the label's boundary
+ };
+
/**
* Constructor for QgsCallout.
*/
@@ -293,6 +312,42 @@ class CORE_EXPORT QgsCallout
*/
static QgsCallout::AnchorPoint decodeAnchorPoint( const QString &name, bool *ok = nullptr );
+
+ /**
+ * Returns the label's anchor point position.
+ *
+ * \see setLabelAnchorPoint()
+ * \since QGIS 3.14
+ */
+ LabelAnchorPoint labelAnchorPoint() const { return mLabelAnchorPoint; }
+
+ /**
+ * Sets the label's \a anchor point position.
+ *
+ * \see labelAnchorPoint()
+ * \since QGIS 3.14
+ */
+ void setLabelAnchorPoint( LabelAnchorPoint anchor ) { mLabelAnchorPoint = anchor; }
+
+ /**
+ * Encodes a label \a anchor point to its string representation.
+ * \returns encoded string
+ * \see decodeLabelAnchorPoint()
+ * \since QGIS 3.14
+ */
+ static QString encodeLabelAnchorPoint( LabelAnchorPoint anchor );
+
+ /**
+ * Attempts to decode a string representation of a label anchor point name to the corresponding
+ * anchor point.
+ * \param name encoded label anchor point name
+ * \param ok if specified, will be set to TRUE if the anchor point was successfully decoded
+ * \returns decoded name
+ * \see encodeLabelAnchorPoint()
+ * \since QGIS 3.14
+ */
+ static QgsCallout::LabelAnchorPoint decodeLabelAnchorPoint( const QString &name, bool *ok = nullptr );
+
protected:
/**
@@ -315,11 +370,18 @@ class CORE_EXPORT QgsCallout
*/
virtual void draw( QgsRenderContext &context, QRectF bodyBoundingBox, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext ) = 0;
+ /**
+ * Returns the anchor point geometry for a label with the given bounding box and \a anchor point mode.
+ * \since QGIS 3.14
+ */
+ QgsGeometry labelAnchorGeometry( QRectF bodyBoundingBox, const double angle, LabelAnchorPoint anchor ) const;
+
private:
bool mEnabled = false;
AnchorPoint mAnchorPoint = PoleOfInaccessibility;
+ LabelAnchorPoint mLabelAnchorPoint = LabelPointOnExterior;
//! Property collection for data defined callout settings
QgsPropertyCollection mDataDefinedProperties;
diff --git a/src/gui/callouts/qgscalloutwidget.cpp b/src/gui/callouts/qgscalloutwidget.cpp
index e1df0d8aa0f..118fe38fbcc 100644
--- a/src/gui/callouts/qgscalloutwidget.cpp
+++ b/src/gui/callouts/qgscalloutwidget.cpp
@@ -167,6 +167,18 @@ QgsSimpleLineCalloutWidget::QgsSimpleLineCalloutWidget( QgsVectorLayer *vl, QWid
mAnchorPointComboBox->addItem( tr( "Centroid" ), static_cast< int >( QgsCallout::Centroid ) );
connect( mAnchorPointComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsSimpleLineCalloutWidget::mAnchorPointComboBox_currentIndexChanged );
+ mLabelAnchorPointComboBox->addItem( tr( "Closest Point" ), static_cast< int >( QgsCallout::LabelPointOnExterior ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Centroid" ), static_cast< int >( QgsCallout::LabelCentroid ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Top Left" ), static_cast< int >( QgsCallout::LabelTopLeft ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Top Center" ), static_cast< int >( QgsCallout::LabelTopMiddle ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Top Right" ), static_cast< int >( QgsCallout::LabelTopRight ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Left Middle" ), static_cast< int >( QgsCallout::LabelMiddleLeft ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Right Middle" ), static_cast< int >( QgsCallout::LabelMiddleRight ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Bottom Left" ), static_cast< int >( QgsCallout::LabelBottomLeft ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Bottom Center" ), static_cast< int >( QgsCallout::LabelBottomMiddle ) );
+ mLabelAnchorPointComboBox->addItem( tr( "Bottom Right" ), static_cast< int >( QgsCallout::LabelBottomRight ) );
+ connect( mLabelAnchorPointComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsSimpleLineCalloutWidget::mLabelAnchorPointComboBox_currentIndexChanged );
+
connect( mCalloutLineStyleButton, &QgsSymbolButton::changed, this, &QgsSimpleLineCalloutWidget::lineSymbolChanged );
}
@@ -202,12 +214,14 @@ void QgsSimpleLineCalloutWidget::setCallout( QgsCallout *callout )
whileBlocking( mDrawToAllPartsCheck )->setChecked( mCallout->drawCalloutToAllParts() );
whileBlocking( mAnchorPointComboBox )->setCurrentIndex( mAnchorPointComboBox->findData( static_cast< int >( callout->anchorPoint() ) ) );
+ whileBlocking( mLabelAnchorPointComboBox )->setCurrentIndex( mLabelAnchorPointComboBox->findData( static_cast< int >( callout->labelAnchorPoint() ) ) );
registerDataDefinedButton( mMinCalloutLengthDDBtn, QgsCallout::MinimumCalloutLength );
registerDataDefinedButton( mOffsetFromAnchorDDBtn, QgsCallout::OffsetFromAnchor );
registerDataDefinedButton( mOffsetFromLabelDDBtn, QgsCallout::OffsetFromLabel );
registerDataDefinedButton( mDrawToAllPartsDDBtn, QgsCallout::DrawCalloutToAllParts );
registerDataDefinedButton( mAnchorPointDDBtn, QgsCallout::AnchorPointPosition );
+ registerDataDefinedButton( mLabelAnchorPointDDBtn, QgsCallout::LabelAnchorPointPosition );
}
void QgsSimpleLineCalloutWidget::setGeometryType( QgsWkbTypes::GeometryType type )
@@ -277,6 +291,12 @@ void QgsSimpleLineCalloutWidget::mAnchorPointComboBox_currentIndexChanged( int i
emit changed();
}
+void QgsSimpleLineCalloutWidget::mLabelAnchorPointComboBox_currentIndexChanged( int index )
+{
+ mCallout->setLabelAnchorPoint( static_cast( mLabelAnchorPointComboBox->itemData( index ).toInt() ) );
+ emit changed();
+}
+
void QgsSimpleLineCalloutWidget::drawToAllPartsToggled( bool active )
{
mCallout->setDrawCalloutToAllParts( active );
@@ -296,3 +316,4 @@ QgsManhattanLineCalloutWidget::QgsManhattanLineCalloutWidget( QgsVectorLayer *vl
///@endcond
+
diff --git a/src/gui/callouts/qgscalloutwidget.h b/src/gui/callouts/qgscalloutwidget.h
index d9f1d8ed605..6efa33f5b1f 100644
--- a/src/gui/callouts/qgscalloutwidget.h
+++ b/src/gui/callouts/qgscalloutwidget.h
@@ -152,6 +152,7 @@ class GUI_EXPORT QgsSimpleLineCalloutWidget : public QgsCalloutWidget, private U
void offsetFromLabelChanged();
void lineSymbolChanged();
void mAnchorPointComboBox_currentIndexChanged( int index );
+ void mLabelAnchorPointComboBox_currentIndexChanged( int index );
void drawToAllPartsToggled( bool active );
private:
diff --git a/src/ui/callouts/widget_simplelinecallout.ui b/src/ui/callouts/widget_simplelinecallout.ui
index eb7ab514cf0..5071b9361f7 100644
--- a/src/ui/callouts/widget_simplelinecallout.ui
+++ b/src/ui/callouts/widget_simplelinecallout.ui
@@ -6,8 +6,8 @@
0
0
- 337
- 184
+ 341
+ 201
@@ -34,13 +34,70 @@
0
- -
-
+
-
+
+
+ Line style
+
+
+
+ -
+
+
+ Anchor point
+
+
+
+ -
+
+
+
+ 10
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+
+ -
+
…
+ -
+
+
+ Offset from label area
+
+
+
+ -
+
+
+ -
+
+
+ Offset from feature
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Symbol…
+
+
+
-
@@ -54,16 +111,30 @@
- -
-
-
-
- 0
- 0
-
-
+
-
+
- Symbol…
+ …
+
+
+
+ -
+
+
+ …
+
+
+
+ -
+
+
+
+ 10
+ 0
+
+
+
+ Qt::StrongFocus
@@ -92,65 +163,20 @@
- -
-
+
-
+
- Offset from feature
+ Draw lines to all feature parts
- -
-
-
-
- 10
- 0
-
-
-
- Qt::StrongFocus
-
-
-
- -
-
+
-
+
…
- -
-
-
- …
-
-
-
- -
-
-
-
- 1
- 0
-
-
-
- 6
-
-
- 100000.000000000000000
-
-
- 0.200000000000000
-
-
- 0.000000000000000
-
-
- true
-
-
-
-
@@ -176,23 +202,10 @@
- -
-
-
-
- 10
- 0
-
-
-
- Qt::StrongFocus
-
-
-
- -
-
+
-
+
- Offset from label area
+ …
@@ -203,39 +216,43 @@
- -
-
-
- Line style
+
-
+
+
+
+ 1
+ 0
+
+
+
+ 6
+
+
+ 100000.000000000000000
+
+
+ 0.200000000000000
+
+
+ 0.000000000000000
+
+
+ true
- -
-
+
-
+
- Draw lines to all feature parts
+ Label anchor point
- -
-
-
- …
-
-
+
-
+
- -
-
-
- Anchor point
-
-
-
- -
-
-
- -
-
+
-
+
…
diff --git a/tests/src/core/testqgscallout.cpp b/tests/src/core/testqgscallout.cpp
index 718c3bdf3aa..fb81edfd00c 100644
--- a/tests/src/core/testqgscallout.cpp
+++ b/tests/src/core/testqgscallout.cpp
@@ -133,6 +133,16 @@ class TestQgsCallout: public QObject
void calloutDataDefinedOffsetFromAnchor();
void calloutOffsetFromLabel();
void calloutDataDefinedOffsetFromLabel();
+ void calloutLabelAnchorTopRight();
+ void calloutLabelAnchorTopLeft();
+ void calloutLabelAnchorTop();
+ void calloutLabelAnchorBottomLeft();
+ void calloutLabelAnchorBottom();
+ void calloutLabelAnchorBottomRight();
+ void calloutLabelAnchorLeft();
+ void calloutLabelAnchorRight();
+ void calloutLabelAnchorCentroid();
+ void calloutLabelDataDefinedAnchor();
void calloutBehindLabel();
void calloutBehindIndividualLabels();
void calloutNoDrawToAllParts();
@@ -939,6 +949,527 @@ void TestQgsCallout::calloutDataDefinedOffsetFromLabel()
QVERIFY( imageCheck( "callout_data_defined_offset_from_label", img, 20 ) );
}
+void TestQgsCallout::calloutLabelAnchorTopRight()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelTopRight );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_top_right", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorTopLeft()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelTopLeft );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_top_left", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorTop()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelTopMiddle );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_top_middle", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorBottomLeft()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelBottomLeft );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_bottom_left", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorBottom()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelBottomMiddle );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_bottom_middle", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorBottomRight()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelBottomRight );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_bottom_right", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorLeft()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelMiddleLeft );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_left", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorRight()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelMiddleRight );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_right", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelAnchorCentroid()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelCentroid );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_anchor_centroid", img, 20 ) );
+}
+
+void TestQgsCallout::calloutLabelDataDefinedAnchor()
+{
+ QSize size( 640, 480 );
+ QgsMapSettings mapSettings;
+ mapSettings.setOutputSize( size );
+ mapSettings.setExtent( vl->extent() );
+ mapSettings.setLayers( QList() << vl );
+ mapSettings.setOutputDpi( 96 );
+ mapSettings.setRotation( 45 );
+
+ QgsMapRendererSequentialJob job( mapSettings );
+ job.start();
+ job.waitForFinished();
+
+ QImage img = job.renderedImage();
+
+ QPainter p( &img );
+ QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
+ context.setPainter( &p );
+
+ QgsPalLayerSettings settings;
+ settings.fieldName = QStringLiteral( "Class" );
+ settings.placement = QgsPalLayerSettings::AroundPoint;
+ settings.dist = 10;
+
+ QgsTextFormat format;
+ format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
+ format.setSize( 12 );
+ format.setNamedStyle( QStringLiteral( "Bold" ) );
+ format.setColor( QColor( 200, 0, 200 ) );
+ settings.setFormat( format );
+
+ QgsSimpleLineCallout *callout = new QgsSimpleLineCallout();
+ callout->setEnabled( true );
+ callout->lineSymbol()->setWidth( 1 );
+ callout->setLabelAnchorPoint( QgsCallout::LabelCentroid );
+ callout->dataDefinedProperties().setProperty( QgsCallout::LabelAnchorPointPosition, QgsProperty::fromExpression( QStringLiteral( "'TL'" ) ) );
+ settings.setCallout( callout );
+
+ vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
+ vl->setLabelsEnabled( true );
+
+ QgsDefaultLabelingEngine engine;
+ engine.setMapSettings( mapSettings );
+ engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
+ //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
+ engine.run( context );
+
+ p.end();
+
+ QVERIFY( imageCheck( "callout_label_datadefined_anchor", img, 20 ) );
+}
+
void TestQgsCallout::calloutBehindLabel()
{
// test that callouts are rendered below labels
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_left/expected_callout_label_anchor_bottom_left.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_left/expected_callout_label_anchor_bottom_left.png
new file mode 100644
index 00000000000..016bb06fe9f
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_left/expected_callout_label_anchor_bottom_left.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_middle/expected_callout_label_anchor_bottom_middle.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_middle/expected_callout_label_anchor_bottom_middle.png
new file mode 100644
index 00000000000..307919025f4
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_middle/expected_callout_label_anchor_bottom_middle.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_right/expected_callout_label_anchor_bottom_right.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_right/expected_callout_label_anchor_bottom_right.png
new file mode 100644
index 00000000000..6c28531f095
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_bottom_right/expected_callout_label_anchor_bottom_right.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_centroid/expected_callout_label_anchor_centroid.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_centroid/expected_callout_label_anchor_centroid.png
new file mode 100644
index 00000000000..285e22c00f4
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_centroid/expected_callout_label_anchor_centroid.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_left/expected_callout_label_anchor_left.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_left/expected_callout_label_anchor_left.png
new file mode 100644
index 00000000000..9877ab20f04
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_left/expected_callout_label_anchor_left.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_right/expected_callout_label_anchor_right.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_right/expected_callout_label_anchor_right.png
new file mode 100644
index 00000000000..7ae3266e048
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_right/expected_callout_label_anchor_right.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_left/expected_callout_label_anchor_top_left.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_left/expected_callout_label_anchor_top_left.png
new file mode 100644
index 00000000000..e743c1e88bc
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_left/expected_callout_label_anchor_top_left.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_middle/expected_callout_label_anchor_top_middle.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_middle/expected_callout_label_anchor_top_middle.png
new file mode 100644
index 00000000000..defe3b35e13
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_middle/expected_callout_label_anchor_top_middle.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_right/expected_callout_label_anchor_top_right.png b/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_right/expected_callout_label_anchor_top_right.png
new file mode 100644
index 00000000000..9760a666503
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_anchor_top_right/expected_callout_label_anchor_top_right.png differ
diff --git a/tests/testdata/control_images/callouts/expected_callout_label_datadefined_anchor/expected_callout_label_datadefined_anchor.png b/tests/testdata/control_images/callouts/expected_callout_label_datadefined_anchor/expected_callout_label_datadefined_anchor.png
new file mode 100644
index 00000000000..e743c1e88bc
Binary files /dev/null and b/tests/testdata/control_images/callouts/expected_callout_label_datadefined_anchor/expected_callout_label_datadefined_anchor.png differ