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