Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Ensure labeling respects layer's reference scale
  • Loading branch information
nyalldawson committed Jun 28, 2021
1 parent 731810c commit d9b7c67
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 12 deletions.
11 changes: 11 additions & 0 deletions src/core/labeling/qgslabelingengine.cpp
Expand Up @@ -421,6 +421,8 @@ void QgsLabelingEngine::drawLabels( QgsRenderContext &context, const QString &la
// provider will require the correct layer scope for expression preparation - at this stage, the existing expression context
// only contains generic scopes
QgsExpressionContextScopePopper popper( context.expressionContext(), provider->layerExpressionContextScope() ? new QgsExpressionContextScope( *provider->layerExpressionContextScope() ) : new QgsExpressionContextScope() );

QgsScopedRenderContextReferenceScaleOverride referenceScaleOverride( context, provider->layerReferenceScale() );
provider->startRender( context );
}

Expand All @@ -444,6 +446,9 @@ void QgsLabelingEngine::drawLabels( QgsRenderContext &context, const QString &la

context.expressionContext().setFeature( lf->feature() );
context.expressionContext().setFields( lf->feature().fields() );

QgsScopedRenderContextReferenceScaleOverride referenceScaleOverride( context, lf->provider()->layerReferenceScale() );

if ( lf->symbol() )
{
symbolScope = QgsExpressionContextUtils::updateSymbolScope( lf->symbol(), symbolScope );
Expand All @@ -468,6 +473,8 @@ void QgsLabelingEngine::drawLabels( QgsRenderContext &context, const QString &la

context.expressionContext().setFeature( lf->feature() );
context.expressionContext().setFields( lf->feature().fields() );

QgsScopedRenderContextReferenceScaleOverride referenceScaleOverride( context, lf->provider()->layerReferenceScale() );
if ( lf->symbol() )
{
symbolScope = QgsExpressionContextUtils::updateSymbolScope( lf->symbol(), symbolScope );
Expand Down Expand Up @@ -495,6 +502,8 @@ void QgsLabelingEngine::drawLabels( QgsRenderContext &context, const QString &la

context.expressionContext().setFeature( lf->feature() );
context.expressionContext().setFields( lf->feature().fields() );

QgsScopedRenderContextReferenceScaleOverride referenceScaleOverride( context, lf->provider()->layerReferenceScale() );
if ( lf->symbol() )
{
symbolScope = QgsExpressionContextUtils::updateSymbolScope( lf->symbol(), symbolScope );
Expand Down Expand Up @@ -626,6 +635,8 @@ QgsAbstractLabelProvider::QgsAbstractLabelProvider( QgsMapLayer *layer, const QS
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
{
mLayerExpressionContextScope.reset( vl->createExpressionContextScope() );
if ( const QgsFeatureRenderer *renderer = vl->renderer() )
mLayerReferenceScale = renderer->referenceScale();
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/core/labeling/qgslabelingengine.h
Expand Up @@ -163,6 +163,13 @@ class CORE_EXPORT QgsAbstractLabelProvider
*/
QgsExpressionContextScope *layerExpressionContextScope() const;

/**
* Returns the symbology reference scale of the layer associated with this provider.
*
* \since QGIS 3.22
*/
double layerReferenceScale() const { return mLayerReferenceScale; }

protected:
//! Associated labeling engine
const QgsLabelingEngine *mEngine = nullptr;
Expand All @@ -189,6 +196,7 @@ class CORE_EXPORT QgsAbstractLabelProvider
private:

std::unique_ptr< QgsExpressionContextScope > mLayerExpressionContextScope;
double mLayerReferenceScale = -1;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsAbstractLabelProvider::Flags )
Expand Down
14 changes: 10 additions & 4 deletions src/core/labeling/qgsvectorlayerlabelprovider.cpp
Expand Up @@ -559,11 +559,17 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q

component.center = centerPt;

// convert label size to render units
double labelWidthPx = context.convertToPainterUnits( label->getWidth(), QgsUnitTypes::RenderMapUnits, QgsMapUnitScale() );
double labelHeightPx = context.convertToPainterUnits( label->getHeight(), QgsUnitTypes::RenderMapUnits, QgsMapUnitScale() );
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
QgsScopedRenderContextReferenceScaleOverride referenceScaleOverride( context, 1.0 );

// convert label size to render units
double labelWidthPx = context.convertToPainterUnits( label->getWidth(), QgsUnitTypes::RenderMapUnits, QgsMapUnitScale() );
double labelHeightPx = context.convertToPainterUnits( label->getHeight(), QgsUnitTypes::RenderMapUnits, QgsMapUnitScale() );

component.size = QSizeF( labelWidthPx, labelHeightPx );
component.size = QSizeF( labelWidthPx, labelHeightPx );
}

QgsTextRenderer::drawBackground( context, component, tmpLyr.format(), QgsTextDocument(), QgsTextRenderer::Label );
}
Expand Down
53 changes: 53 additions & 0 deletions src/core/qgsrendercontext.h
Expand Up @@ -1218,6 +1218,59 @@ class QgsScopedQPainterState
QPainter *mPainter = nullptr;
};


/**
* \ingroup core
*
* \brief Scoped object for temporary override of the symbologyReferenceScale property of a QgsRenderContext.
*
* Temporarily changes the symbologyReferenceScale, before returning it to the original value on destruction.
*
* \note Not available in Python bindings
* \since QGIS 3.22
*/
class QgsScopedRenderContextReferenceScaleOverride
{
public:

/**
* Constructor for QgsScopedRenderContextReferenceScaleOverride.
*
* Temporarily sets the render \a context symbologyReferenceScale to \a scale for the lifetime of this object.
*/
QgsScopedRenderContextReferenceScaleOverride( QgsRenderContext &context, double scale )
: mContext( &context )
, mOriginalScale( context.symbologyReferenceScale() )
{
mContext->setSymbologyReferenceScale( scale );
}

/**
* Move constructor.
*/
QgsScopedRenderContextReferenceScaleOverride( QgsScopedRenderContextReferenceScaleOverride &&o ) noexcept
: mContext( o.mContext )
, mOriginalScale( o.mOriginalScale )
{
o.mContext = nullptr;
}

/**
* Returns the render context back to the original reference scale.
*/
~QgsScopedRenderContextReferenceScaleOverride()
{
if ( mContext )
mContext->setSymbologyReferenceScale( mOriginalScale );
}

private:

QgsRenderContext *mContext = nullptr;
double mOriginalScale = 0;
};


#endif

#endif
74 changes: 68 additions & 6 deletions src/core/textrenderer/qgstextrenderer.cpp
Expand Up @@ -26,6 +26,7 @@
#include "qgsmarkersymbol.h"
#include "qgsfillsymbol.h"

#include <optional>
#include <QTextBoundaryFinder>

Q_GUI_EXPORT extern int qt_defaultDpiX();
Expand Down Expand Up @@ -267,7 +268,8 @@ QFontMetricsF QgsTextRenderer::fontMetrics( QgsRenderContext &context, const Qgs
return QFontMetricsF( format.scaledFont( context, scaleFactor ), context.painter() ? context.painter()->device() : nullptr );
}

double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format,
DrawMode mode )
{
QPainter *p = context.painter();

Expand All @@ -293,7 +295,17 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend
const double penSize = context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() );

const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;

std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
if ( mode == Label )
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, 1.0 ) );
}

const QFont font = format.scaledFont( context, scaleFactor );
referenceScaleOverride.reset();

QPainterPath path;
path.setFillRule( Qt::WindingFill );
Expand Down Expand Up @@ -418,7 +430,8 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend
return advance / scaleFactor;
}

void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format,
DrawMode mode )
{
QgsTextMaskSettings mask = format.mask();

Expand All @@ -439,7 +452,17 @@ void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer
// TODO: vertical text mode was ignored when masking feature was added.
// Hopefully Oslandia come back and fix this? Hint hint...

std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
if ( mode == Label )
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, 1.0 ) );
}

const QFont font = format.scaledFont( context, scaleFactor );
referenceScaleOverride.reset();

double xOffset = 0;
for ( const QgsTextFragment &fragment : component.block )
{
Expand Down Expand Up @@ -1255,9 +1278,20 @@ void QgsTextRenderer::drawTextInternal( TextPart drawType,
if ( !fontMetrics )
{
fontScale = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;

std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
if ( mode == Label )
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, 1.0 ) );
}

const QFont f = format.scaledFont( context, fontScale );
tmpMetrics = std::make_unique< QFontMetricsF >( f );
fontMetrics = tmpMetrics.get();

referenceScaleOverride.reset();
}

double rotation = 0;
Expand Down Expand Up @@ -1392,7 +1426,14 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con

if ( mode == Rect && vAlignment != AlignTop )
{
// need to calculate overall text height in advance so that we can adjust for vertical alignment
std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
if ( mode == Label )
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, 1.0 ) );
}

const double overallHeight = textHeight( context, format, textLines, Rect );
switch ( vAlignment )
{
Expand All @@ -1407,6 +1448,7 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
ascentOffset = -( component.size.height() - overallHeight ) + ascentOffset;
break;
}
referenceScaleOverride.reset();
}

for ( const QString &line : std::as_const( textLines ) )
Expand Down Expand Up @@ -1525,12 +1567,12 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
// draw the mask below the text (for preview)
if ( format.mask().enabled() )
{
QgsTextRenderer::drawMask( context, subComponent, format );
QgsTextRenderer::drawMask( context, subComponent, format, mode );
}

if ( drawType == QgsTextRenderer::Buffer )
{
QgsTextRenderer::drawBuffer( context, subComponent, format );
QgsTextRenderer::drawBuffer( context, subComponent, format, mode );
}
else
{
Expand All @@ -1539,7 +1581,17 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
QPainter textp;
textp.begin( &textPict );
textp.setPen( Qt::NoPen );

std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
if ( mode == Label )
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, 1.0 ) );
}
const QFont font = format.scaledFont( context, fontScale );
referenceScaleOverride.reset();

textp.scale( 1 / fontScale, 1 / fontScale );

double xOffset = 0;
Expand Down Expand Up @@ -1632,7 +1684,17 @@ void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const
QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
const QStringList textLines = document.toPlainText();

std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
if ( mode == Label )
{
// label size has already been calculated using any symbology reference scale factor -- we need
// to temporarily remove the reference scale here or we'll be applying the scaling twice
referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, 1.0 ) );
}

const QFont font = format.scaledFont( context, fontScale );
referenceScaleOverride.reset();

double letterSpacing = font.letterSpacing() / fontScale;

double labelWidth = fontMetrics->maxWidth() / fontScale; // label width represents the width of one line of a multi-line label
Expand Down Expand Up @@ -1776,7 +1838,7 @@ void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const

if ( drawType == QgsTextRenderer::Buffer )
{
fragmentYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format );
fragmentYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format, mode );
}
else
{
Expand Down
6 changes: 4 additions & 2 deletions src/core/textrenderer/qgstextrenderer.h
Expand Up @@ -320,7 +320,8 @@ class CORE_EXPORT QgsTextRenderer

static double drawBuffer( QgsRenderContext &context,
const Component &component,
const QgsTextFormat &format );
const QgsTextFormat &format,
DrawMode mode );

static void drawBackground( QgsRenderContext &context,
Component component,
Expand All @@ -334,7 +335,8 @@ class CORE_EXPORT QgsTextRenderer

static void drawMask( QgsRenderContext &context,
const Component &component,
const QgsTextFormat &format );
const QgsTextFormat &format,
DrawMode mode );

static void drawText( QgsRenderContext &context,
const Component &component,
Expand Down

0 comments on commit d9b7c67

Please sign in to comment.