mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
Also improve the API for QgsExpressionContextUtils::updateSymbolScope Previously it was hard to predict if the method would create storage, which caused issues for the python bindings (eg, they had a choice of either leaking or being crashy).
781 lines
26 KiB
C++
781 lines
26 KiB
C++
/***************************************************************************
|
|
qgsvectorlayerlabelprovider.cpp
|
|
--------------------------------------
|
|
Date : September 2015
|
|
Copyright : (C) 2015 by Martin Dobias
|
|
Email : wonder dot sk at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgsvectorlayerlabelprovider.h"
|
|
|
|
#include "qgsdatadefined.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgslabelsearchtree.h"
|
|
#include "qgspallabeling.h"
|
|
#include "qgstextlabelfeature.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsvectorlayerfeatureiterator.h"
|
|
#include "qgsrendererv2.h"
|
|
#include "qgspolygonv2.h"
|
|
#include "qgslinestringv2.h"
|
|
#include "qgsmultipolygonv2.h"
|
|
|
|
#include "feature.h"
|
|
#include "labelposition.h"
|
|
|
|
#include <QPicture>
|
|
|
|
using namespace pal;
|
|
|
|
Q_GUI_EXPORT extern int qt_defaultDpiX();
|
|
Q_GUI_EXPORT extern int qt_defaultDpiY();
|
|
|
|
static void _fixQPictureDPI( QPainter* p )
|
|
{
|
|
// QPicture makes an assumption that we drawing to it with system DPI.
|
|
// Then when being drawn, it scales the painter. The following call
|
|
// negates the effect. There is no way of setting QPicture's DPI.
|
|
// See QTBUG-20361
|
|
p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
|
|
static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
|
|
}
|
|
|
|
|
|
|
|
QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop, const QgsPalLayerSettings* settings, const QString& layerName )
|
|
: QgsAbstractLabelProvider( layer->id() )
|
|
, mSettings( settings ? *settings : QgsPalLayerSettings::fromLayer( layer ) )
|
|
, mLayerGeometryType( layer->geometryType() )
|
|
, mRenderer( layer->rendererV2() )
|
|
, mFields( layer->fields() )
|
|
, mCrs( layer->crs() )
|
|
{
|
|
mName = layerName.isEmpty() ? layer->id() : layerName;
|
|
|
|
if ( withFeatureLoop )
|
|
{
|
|
mSource = new QgsVectorLayerFeatureSource( layer );
|
|
mOwnsSource = true;
|
|
}
|
|
else
|
|
{
|
|
mSource = nullptr;
|
|
mOwnsSource = false;
|
|
}
|
|
|
|
init();
|
|
}
|
|
|
|
QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( const QgsPalLayerSettings& settings,
|
|
const QString& layerId,
|
|
const QgsFields& fields,
|
|
const QgsCoordinateReferenceSystem& crs,
|
|
QgsAbstractFeatureSource* source,
|
|
bool ownsSource, QgsFeatureRendererV2* renderer )
|
|
: QgsAbstractLabelProvider( layerId )
|
|
, mSettings( settings )
|
|
, mLayerGeometryType( QGis::UnknownGeometry )
|
|
, mRenderer( renderer )
|
|
, mFields( fields )
|
|
, mCrs( crs )
|
|
, mSource( source )
|
|
, mOwnsSource( ownsSource )
|
|
{
|
|
init();
|
|
}
|
|
|
|
|
|
void QgsVectorLayerLabelProvider::init()
|
|
{
|
|
mPlacement = mSettings.placement;
|
|
mLinePlacementFlags = mSettings.placementFlags;
|
|
mFlags = Flags();
|
|
if ( mSettings.drawLabels ) mFlags |= DrawLabels;
|
|
if ( mSettings.displayAll ) mFlags |= DrawAllLabels;
|
|
if ( mSettings.mergeLines ) mFlags |= MergeConnectedLines;
|
|
if ( mSettings.centroidInside ) mFlags |= CentroidMustBeInside;
|
|
if ( mSettings.fitInPolygonOnly ) mFlags |= FitInPolygonOnly;
|
|
if ( mSettings.labelPerPart ) mFlags |= LabelPerFeaturePart;
|
|
mPriority = 1 - mSettings.priority / 10.0; // convert 0..10 --> 1..0
|
|
|
|
if ( mLayerGeometryType == QGis::Point && mRenderer )
|
|
{
|
|
//override obstacle type to treat any intersection of a label with the point symbol as a high cost conflict
|
|
mObstacleType = QgsPalLayerSettings::PolygonWhole;
|
|
}
|
|
else
|
|
{
|
|
mObstacleType = mSettings.obstacleType;
|
|
}
|
|
|
|
mUpsidedownLabels = mSettings.upsidedownLabels;
|
|
}
|
|
|
|
|
|
QgsVectorLayerLabelProvider::~QgsVectorLayerLabelProvider()
|
|
{
|
|
qDeleteAll( mLabels );
|
|
|
|
if ( mOwnsSource )
|
|
delete mSource;
|
|
}
|
|
|
|
|
|
bool QgsVectorLayerLabelProvider::prepare( const QgsRenderContext& context, QStringList& attributeNames )
|
|
{
|
|
QgsPalLayerSettings& lyr = mSettings;
|
|
const QgsMapSettings& mapSettings = mEngine->mapSettings();
|
|
|
|
QgsDebugMsgLevel( "PREPARE LAYER " + mLayerId, 4 );
|
|
|
|
if ( lyr.drawLabels )
|
|
{
|
|
if ( lyr.fieldName.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( lyr.isExpression )
|
|
{
|
|
QgsExpression exp( lyr.fieldName );
|
|
if ( exp.hasEvalError() )
|
|
{
|
|
QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we aren't an expression, we check to see if we can find the column.
|
|
if ( mFields.fieldNameIndex( lyr.fieldName ) == -1 )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
lyr.mCurFields = mFields;
|
|
|
|
if ( lyr.drawLabels )
|
|
{
|
|
// add field indices for label's text, from expression or field
|
|
if ( lyr.isExpression )
|
|
{
|
|
// prepare expression for use in QgsPalLayerSettings::registerFeature()
|
|
QgsExpression* exp = lyr.getLabelExpression();
|
|
exp->prepare( &context.expressionContext() );
|
|
if ( exp->hasEvalError() )
|
|
{
|
|
QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
|
|
}
|
|
Q_FOREACH ( const QString& name, exp->referencedColumns() )
|
|
{
|
|
QgsDebugMsgLevel( "REFERENCED COLUMN = " + name, 4 );
|
|
attributeNames.append( name );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
attributeNames.append( lyr.fieldName );
|
|
}
|
|
|
|
// add field indices of data defined expression or field
|
|
QMap< QgsPalLayerSettings::DataDefinedProperties, QgsDataDefined* >::const_iterator dIt = lyr.dataDefinedProperties.constBegin();
|
|
for ( ; dIt != lyr.dataDefinedProperties.constEnd(); ++dIt )
|
|
{
|
|
QgsDataDefined* dd = dIt.value();
|
|
if ( !dd->isActive() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// NOTE: the following also prepares any expressions for later use
|
|
|
|
// store parameters for data defined expressions
|
|
QMap<QString, QVariant> exprParams;
|
|
exprParams.insert( "scale", context.rendererScale() );
|
|
|
|
dd->setExpressionParams( exprParams );
|
|
|
|
// this will return columns for expressions or field name, depending upon what is set to be used
|
|
QStringList cols = dd->referencedColumns( context.expressionContext() ); // <-- prepares any expressions, too
|
|
|
|
//QgsDebugMsgLevel( QString( "Data defined referenced columns:" ) + cols.join( "," ), 4 );
|
|
Q_FOREACH ( const QString& name, cols )
|
|
{
|
|
attributeNames.append( name );
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOW INITIALIZE QgsPalLayerSettings
|
|
|
|
// TODO: ideally these (non-configuration) members should get out of QgsPalLayerSettings to here
|
|
// (together with registerFeature() & related methods) and QgsPalLayerSettings just stores config
|
|
|
|
//raster and vector scale factors
|
|
lyr.vectorScaleFactor = context.scaleFactor();
|
|
lyr.rasterCompressFactor = context.rasterScaleFactor();
|
|
|
|
// save the pal layer to our layer context (with some additional info)
|
|
lyr.fieldIndex = mFields.fieldNameIndex( lyr.fieldName );
|
|
|
|
lyr.xform = &mapSettings.mapToPixel();
|
|
lyr.ct = nullptr;
|
|
if ( mapSettings.hasCrsTransformEnabled() )
|
|
{
|
|
if ( context.coordinateTransform() )
|
|
// this is context for layer rendering - use its CT as it includes correct datum transform
|
|
lyr.ct = context.coordinateTransform()->clone();
|
|
else
|
|
// otherwise fall back to creating our own CT - this one may not have the correct datum transform!
|
|
lyr.ct = new QgsCoordinateTransform( mCrs, mapSettings.destinationCrs() );
|
|
}
|
|
lyr.ptZero = lyr.xform->toMapCoordinates( 0, 0 );
|
|
lyr.ptOne = lyr.xform->toMapCoordinates( 1, 0 );
|
|
|
|
// rect for clipping
|
|
lyr.extentGeom = QgsGeometry::fromRect( mapSettings.visibleExtent() );
|
|
if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
|
|
{
|
|
//PAL features are prerotated, so extent also needs to be unrotated
|
|
lyr.extentGeom->rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
|
|
}
|
|
|
|
lyr.mFeatsSendingToPal = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
QList<QgsLabelFeature*> QgsVectorLayerLabelProvider::labelFeatures( QgsRenderContext &ctx )
|
|
{
|
|
if ( !mSource )
|
|
{
|
|
// we have created the provider with "own feature loop" == false
|
|
// so it is assumed that prepare() has been already called followed by registerFeature() calls
|
|
return mLabels;
|
|
}
|
|
|
|
QStringList attrNames;
|
|
if ( !prepare( ctx, attrNames ) )
|
|
return QList<QgsLabelFeature*>();
|
|
|
|
if ( mRenderer )
|
|
mRenderer->startRender( ctx, mFields );
|
|
|
|
QgsRectangle layerExtent = ctx.extent();
|
|
if ( mSettings.ct )
|
|
layerExtent = mSettings.ct->transformBoundingBox( ctx.extent(), QgsCoordinateTransform::ReverseTransform );
|
|
|
|
QgsFeatureRequest request;
|
|
request.setFilterRect( layerExtent );
|
|
request.setSubsetOfAttributes( attrNames, mFields );
|
|
QgsFeatureIterator fit = mSource->getFeatures( request );
|
|
|
|
QgsExpressionContextScope* symbolScope = new QgsExpressionContextScope();
|
|
ctx.expressionContext().appendScope( symbolScope );
|
|
QgsFeature fet;
|
|
while ( fit.nextFeature( fet ) )
|
|
{
|
|
QScopedPointer<QgsGeometry> obstacleGeometry;
|
|
if ( mRenderer )
|
|
{
|
|
QgsSymbolV2List symbols = mRenderer->originalSymbolsForFeature( fet, ctx );
|
|
if ( !symbols.isEmpty() && fet.constGeometry()->type() == QGis::Point )
|
|
{
|
|
//point feature, use symbol bounds as obstacle
|
|
obstacleGeometry.reset( QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, ctx, symbols ) );
|
|
}
|
|
if ( !symbols.isEmpty() )
|
|
{
|
|
symbolScope = QgsExpressionContextUtils::updateSymbolScope( symbols.at( 0 ), symbolScope );
|
|
}
|
|
}
|
|
ctx.expressionContext().setFeature( fet );
|
|
registerFeature( fet, ctx, obstacleGeometry.data() );
|
|
}
|
|
|
|
if ( ctx.expressionContext().lastScope() == symbolScope )
|
|
delete ctx.expressionContext().popScope();
|
|
|
|
if ( mRenderer )
|
|
mRenderer->stopRender( ctx );
|
|
|
|
return mLabels;
|
|
}
|
|
|
|
void QgsVectorLayerLabelProvider::registerFeature( QgsFeature& feature, QgsRenderContext& context, QgsGeometry* obstacleGeometry )
|
|
{
|
|
QgsLabelFeature* label = nullptr;
|
|
mSettings.registerFeature( feature, context, QString(), &label, obstacleGeometry );
|
|
if ( label )
|
|
mLabels << label;
|
|
}
|
|
|
|
QgsGeometry* QgsVectorLayerLabelProvider::getPointObstacleGeometry( QgsFeature& fet, QgsRenderContext& context, const QgsSymbolV2List& symbols )
|
|
{
|
|
if ( !fet.constGeometry() || fet.constGeometry()->isEmpty() || fet.constGeometry()->type() != QGis::Point )
|
|
return nullptr;
|
|
|
|
bool isMultiPoint = fet.constGeometry()->geometry()->nCoordinates() > 1;
|
|
QgsAbstractGeometryV2* obstacleGeom = nullptr;
|
|
if ( isMultiPoint )
|
|
obstacleGeom = new QgsMultiPolygonV2();
|
|
|
|
// for each point
|
|
for ( int i = 0; i < fet.constGeometry()->geometry()->nCoordinates(); ++i )
|
|
{
|
|
QRectF bounds;
|
|
QgsPointV2 p = fet.constGeometry()->geometry()->vertexAt( QgsVertexId( i, 0, 0 ) );
|
|
double x = p.x();
|
|
double y = p.y();
|
|
double z = 0; // dummy variable for coordinate transforms
|
|
|
|
//transform point to pixels
|
|
if ( context.coordinateTransform() )
|
|
{
|
|
context.coordinateTransform()->transformInPlace( x, y, z );
|
|
}
|
|
context.mapToPixel().transformInPlace( x, y );
|
|
|
|
QPointF pt( x, y );
|
|
Q_FOREACH ( QgsSymbolV2* symbol, symbols )
|
|
{
|
|
if ( symbol->type() == QgsSymbolV2::Marker )
|
|
{
|
|
if ( bounds.isValid() )
|
|
bounds = bounds.united( static_cast< QgsMarkerSymbolV2* >( symbol )->bounds( pt, context ) );
|
|
else
|
|
bounds = static_cast< QgsMarkerSymbolV2* >( symbol )->bounds( pt, context );
|
|
}
|
|
}
|
|
|
|
//convert bounds to a geometry
|
|
QgsLineStringV2* boundLineString = new QgsLineStringV2();
|
|
boundLineString->addVertex( QgsPointV2( bounds.topLeft() ) );
|
|
boundLineString->addVertex( QgsPointV2( bounds.topRight() ) );
|
|
boundLineString->addVertex( QgsPointV2( bounds.bottomRight() ) );
|
|
boundLineString->addVertex( QgsPointV2( bounds.bottomLeft() ) );
|
|
|
|
//then transform back to map units
|
|
//TODO - remove when labeling is refactored to use screen units
|
|
for ( int i = 0; i < boundLineString->numPoints(); ++i )
|
|
{
|
|
QgsPoint point = context.mapToPixel().toMapCoordinates( boundLineString->xAt( i ), boundLineString->yAt( i ) );
|
|
boundLineString->setXAt( i, point.x() );
|
|
boundLineString->setYAt( i, point.y() );
|
|
}
|
|
if ( context.coordinateTransform() )
|
|
{
|
|
boundLineString->transform( *context.coordinateTransform(), QgsCoordinateTransform::ReverseTransform );
|
|
}
|
|
boundLineString->close();
|
|
|
|
QgsPolygonV2* obstaclePolygon = new QgsPolygonV2();
|
|
obstaclePolygon->setExteriorRing( boundLineString );
|
|
|
|
if ( isMultiPoint )
|
|
{
|
|
static_cast<QgsMultiPolygonV2*>( obstacleGeom )->addGeometry( obstaclePolygon );
|
|
}
|
|
else
|
|
{
|
|
obstacleGeom = obstaclePolygon;
|
|
}
|
|
}
|
|
|
|
return new QgsGeometry( obstacleGeom );
|
|
}
|
|
|
|
void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const
|
|
{
|
|
if ( !mSettings.drawLabels )
|
|
return;
|
|
|
|
QgsTextLabelFeature* lf = dynamic_cast<QgsTextLabelFeature*>( label->getFeaturePart()->feature() );
|
|
|
|
// Copy to temp, editable layer settings
|
|
// these settings will be changed by any data defined values, then used for rendering label components
|
|
// settings may be adjusted during rendering of components
|
|
QgsPalLayerSettings tmpLyr( mSettings );
|
|
|
|
// apply any previously applied data defined settings for the label
|
|
const QMap< QgsPalLayerSettings::DataDefinedProperties, QVariant >& ddValues = lf->dataDefinedValues();
|
|
|
|
//font
|
|
QFont dFont = lf->definedFont();
|
|
QgsDebugMsgLevel( QString( "PAL font tmpLyr: %1, Style: %2" ).arg( tmpLyr.textFont.toString(), tmpLyr.textFont.styleName() ), 4 );
|
|
QgsDebugMsgLevel( QString( "PAL font definedFont: %1, Style: %2" ).arg( dFont.toString(), dFont.styleName() ), 4 );
|
|
tmpLyr.textFont = dFont;
|
|
|
|
if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiFollowPlacement )
|
|
{
|
|
//calculate font alignment based on label quadrant
|
|
switch ( label->getQuadrant() )
|
|
{
|
|
case LabelPosition::QuadrantAboveLeft:
|
|
case LabelPosition::QuadrantLeft:
|
|
case LabelPosition::QuadrantBelowLeft:
|
|
tmpLyr.multilineAlign = QgsPalLayerSettings::MultiRight;
|
|
break;
|
|
case LabelPosition::QuadrantAbove:
|
|
case LabelPosition::QuadrantOver:
|
|
case LabelPosition::QuadrantBelow:
|
|
tmpLyr.multilineAlign = QgsPalLayerSettings::MultiCenter;
|
|
break;
|
|
case LabelPosition::QuadrantAboveRight:
|
|
case LabelPosition::QuadrantRight:
|
|
case LabelPosition::QuadrantBelowRight:
|
|
tmpLyr.multilineAlign = QgsPalLayerSettings::MultiLeft;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update tmpLyr with any data defined text style values
|
|
QgsPalLabeling::dataDefinedTextStyle( tmpLyr, ddValues );
|
|
|
|
// update tmpLyr with any data defined text buffer values
|
|
QgsPalLabeling::dataDefinedTextBuffer( tmpLyr, ddValues );
|
|
|
|
// update tmpLyr with any data defined text formatting values
|
|
QgsPalLabeling::dataDefinedTextFormatting( tmpLyr, ddValues );
|
|
|
|
// update tmpLyr with any data defined shape background values
|
|
QgsPalLabeling::dataDefinedShapeBackground( tmpLyr, ddValues );
|
|
|
|
// update tmpLyr with any data defined drop shadow values
|
|
QgsPalLabeling::dataDefinedDropShadow( tmpLyr, ddValues );
|
|
|
|
tmpLyr.showingShadowRects = mEngine->testFlag( QgsLabelingEngineV2::DrawShadowRects );
|
|
|
|
// Render the components of a label in reverse order
|
|
// (backgrounds -> text)
|
|
|
|
if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowLowest )
|
|
{
|
|
if ( tmpLyr.shapeDraw )
|
|
{
|
|
tmpLyr.shadowUnder = QgsPalLayerSettings::ShadowShape;
|
|
}
|
|
else if ( tmpLyr.bufferDraw )
|
|
{
|
|
tmpLyr.shadowUnder = QgsPalLayerSettings::ShadowBuffer;
|
|
}
|
|
else
|
|
{
|
|
tmpLyr.shadowUnder = QgsPalLayerSettings::ShadowText;
|
|
}
|
|
}
|
|
|
|
if ( tmpLyr.shapeDraw )
|
|
{
|
|
drawLabelPrivate( label, context, tmpLyr, QgsPalLabeling::LabelShape );
|
|
}
|
|
|
|
if ( tmpLyr.bufferDraw )
|
|
{
|
|
drawLabelPrivate( label, context, tmpLyr, QgsPalLabeling::LabelBuffer );
|
|
}
|
|
|
|
drawLabelPrivate( label, context, tmpLyr, QgsPalLabeling::LabelText );
|
|
|
|
// add to the results
|
|
QString labeltext = label->getFeaturePart()->feature()->labelText();
|
|
mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, labeltext, dFont, false, lf->hasFixedPosition() );
|
|
}
|
|
|
|
|
|
void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition* label, QgsRenderContext& context, QgsPalLayerSettings& tmpLyr, QgsPalLabeling::DrawLabelType drawType, double dpiRatio ) const
|
|
{
|
|
// NOTE: this is repeatedly called for multi-part labels
|
|
QPainter* painter = context.painter();
|
|
#if 1 // XXX strk
|
|
// features are pre-rotated but not scaled/translated,
|
|
// so we only disable rotation here. Ideally, they'd be
|
|
// also pre-scaled/translated, as suggested here:
|
|
// http://hub.qgis.org/issues/11856
|
|
QgsMapToPixel xform = context.mapToPixel();
|
|
xform.setMapRotation( 0, 0, 0 );
|
|
#else
|
|
const QgsMapToPixel& xform = context.mapToPixel();
|
|
#endif
|
|
|
|
QgsLabelComponent component;
|
|
component.setDpiRatio( dpiRatio );
|
|
|
|
QgsPoint outPt = xform.transform( label->getX(), label->getY() );
|
|
// QgsPoint outPt2 = xform->transform( label->getX() + label->getWidth(), label->getY() + label->getHeight() );
|
|
// QRectF labelRect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
|
|
|
|
component.setOrigin( outPt );
|
|
component.setRotation( label->getAlpha() );
|
|
|
|
if ( mEngine->testFlag( QgsLabelingEngineV2::DrawLabelRectOnly ) ) // TODO: this should get directly to labeling engine
|
|
{
|
|
//debugging rect
|
|
if ( drawType != QgsPalLabeling::LabelText )
|
|
return;
|
|
|
|
QgsPoint outPt2 = xform.transform( label->getX() + label->getWidth(), label->getY() + label->getHeight() );
|
|
QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
|
|
painter->save();
|
|
painter->setRenderHint( QPainter::Antialiasing, false );
|
|
painter->translate( QPointF( outPt.x(), outPt.y() ) );
|
|
painter->rotate( -label->getAlpha() * 180 / M_PI );
|
|
|
|
if ( label->conflictsWithObstacle() )
|
|
{
|
|
painter->setBrush( QColor( 255, 0, 0, 100 ) );
|
|
painter->setPen( QColor( 255, 0, 0, 150 ) );
|
|
}
|
|
else
|
|
{
|
|
painter->setBrush( QColor( 0, 255, 0, 100 ) );
|
|
painter->setPen( QColor( 0, 255, 0, 150 ) );
|
|
}
|
|
|
|
painter->drawRect( rect );
|
|
painter->restore();
|
|
|
|
if ( label->getNextPart() )
|
|
drawLabelPrivate( label->getNextPart(), context, tmpLyr, drawType, dpiRatio );
|
|
|
|
return;
|
|
}
|
|
|
|
if ( drawType == QgsPalLabeling::LabelShape )
|
|
{
|
|
// get rotated label's center point
|
|
QgsPoint centerPt( outPt );
|
|
QgsPoint outPt2 = xform.transform( label->getX() + label->getWidth() / 2,
|
|
label->getY() + label->getHeight() / 2 );
|
|
|
|
double xc = outPt2.x() - outPt.x();
|
|
double yc = outPt2.y() - outPt.y();
|
|
|
|
double angle = -label->getAlpha();
|
|
double xd = xc * cos( angle ) - yc * sin( angle );
|
|
double yd = xc * sin( angle ) + yc * cos( angle );
|
|
|
|
centerPt.setX( centerPt.x() + xd );
|
|
centerPt.setY( centerPt.y() + yd );
|
|
|
|
component.setCenter( centerPt );
|
|
component.setSize( QgsPoint( label->getWidth(), label->getHeight() ) );
|
|
|
|
QgsPalLabeling::drawLabelBackground( context, component, tmpLyr );
|
|
}
|
|
|
|
else if ( drawType == QgsPalLabeling::LabelBuffer
|
|
|| drawType == QgsPalLabeling::LabelText )
|
|
{
|
|
|
|
// TODO: optimize access :)
|
|
QgsTextLabelFeature* lf = static_cast<QgsTextLabelFeature*>( label->getFeaturePart()->feature() );
|
|
QString txt = lf->text( label->getPartId() );
|
|
QFontMetricsF* labelfm = lf->labelFontMetrics();
|
|
|
|
//add the direction symbol if needed
|
|
if ( !txt.isEmpty() && tmpLyr.placement == QgsPalLayerSettings::Line &&
|
|
tmpLyr.addDirectionSymbol )
|
|
{
|
|
bool prependSymb = false;
|
|
QString symb = tmpLyr.rightDirectionSymbol;
|
|
|
|
if ( label->getReversed() )
|
|
{
|
|
prependSymb = true;
|
|
symb = tmpLyr.leftDirectionSymbol;
|
|
}
|
|
|
|
if ( tmpLyr.reverseDirectionSymbol )
|
|
{
|
|
if ( symb == tmpLyr.rightDirectionSymbol )
|
|
{
|
|
prependSymb = true;
|
|
symb = tmpLyr.leftDirectionSymbol;
|
|
}
|
|
else
|
|
{
|
|
prependSymb = false;
|
|
symb = tmpLyr.rightDirectionSymbol;
|
|
}
|
|
}
|
|
|
|
if ( tmpLyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolAbove )
|
|
{
|
|
prependSymb = true;
|
|
symb = symb + QLatin1String( "\n" );
|
|
}
|
|
else if ( tmpLyr.placeDirectionSymbol == QgsPalLayerSettings::SymbolBelow )
|
|
{
|
|
prependSymb = false;
|
|
symb = QLatin1String( "\n" ) + symb;
|
|
}
|
|
|
|
if ( prependSymb )
|
|
{
|
|
txt.prepend( symb );
|
|
}
|
|
else
|
|
{
|
|
txt.append( symb );
|
|
}
|
|
}
|
|
|
|
//QgsDebugMsgLevel( "drawLabel " + txt, 4 );
|
|
QStringList multiLineList = QgsPalLabeling::splitToLines( txt, tmpLyr.wrapChar );
|
|
int lines = multiLineList.size();
|
|
|
|
double labelWidest = 0.0;
|
|
for ( int i = 0; i < lines; ++i )
|
|
{
|
|
double labelWidth = labelfm->width( multiLineList.at( i ) );
|
|
if ( labelWidth > labelWidest )
|
|
{
|
|
labelWidest = labelWidth;
|
|
}
|
|
}
|
|
|
|
double labelHeight = labelfm->ascent() + labelfm->descent(); // ignore +1 for baseline
|
|
// double labelHighest = labelfm->height() + ( double )(( lines - 1 ) * labelHeight * tmpLyr.multilineHeight );
|
|
|
|
// needed to move bottom of text's descender to within bottom edge of label
|
|
double ascentOffset = 0.25 * labelfm->ascent(); // labelfm->descent() is not enough
|
|
|
|
for ( int i = 0; i < lines; ++i )
|
|
{
|
|
painter->save();
|
|
#if 0 // TODO: generalize some of this
|
|
LabelPosition* lp = label;
|
|
double w = lp->getWidth();
|
|
double h = lp->getHeight();
|
|
double cx = lp->getX() + w / 2.0;
|
|
double cy = lp->getY() + h / 2.0;
|
|
double scale = 1.0 / xform->mapUnitsPerPixel();
|
|
double rotation = xform->mapRotation();
|
|
double sw = w * scale;
|
|
double sh = h * scale;
|
|
QRectF rect( -sw / 2, -sh / 2, sw, sh );
|
|
painter->translate( xform->transform( QPointF( cx, cy ) ).toQPointF() );
|
|
if ( rotation )
|
|
{
|
|
// Only if not horizontal
|
|
if ( lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT &&
|
|
lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT_OVER &&
|
|
lp->getFeaturePart()->getLayer()->getArrangement() != P_HORIZ )
|
|
{
|
|
painter->rotate( rotation );
|
|
}
|
|
}
|
|
painter->translate( rect.bottomLeft() );
|
|
painter->rotate( -lp->getAlpha() * 180 / M_PI );
|
|
#else
|
|
painter->translate( QPointF( outPt.x(), outPt.y() ) );
|
|
painter->rotate( -label->getAlpha() * 180 / M_PI );
|
|
#endif
|
|
|
|
// scale down painter: the font size has been multiplied by raster scale factor
|
|
// to workaround a Qt font scaling bug with small font sizes
|
|
painter->scale( 1.0 / tmpLyr.rasterCompressFactor, 1.0 / tmpLyr.rasterCompressFactor );
|
|
|
|
// figure x offset for horizontal alignment of multiple lines
|
|
double xMultiLineOffset = 0.0;
|
|
double labelWidth = labelfm->width( multiLineList.at( i ) );
|
|
if ( lines > 1 && tmpLyr.multilineAlign != QgsPalLayerSettings::MultiLeft )
|
|
{
|
|
double labelWidthDiff = labelWidest - labelWidth;
|
|
if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiCenter )
|
|
{
|
|
labelWidthDiff /= 2;
|
|
}
|
|
xMultiLineOffset = labelWidthDiff;
|
|
//QgsDebugMsgLevel( QString( "xMultiLineOffset: %1" ).arg( xMultiLineOffset ), 4 );
|
|
}
|
|
|
|
double yMultiLineOffset = ( lines - 1 - i ) * labelHeight * tmpLyr.multilineHeight;
|
|
painter->translate( QPointF( xMultiLineOffset, - ascentOffset - yMultiLineOffset ) );
|
|
|
|
component.setText( multiLineList.at( i ) );
|
|
component.setSize( QgsPoint( labelWidth, labelHeight ) );
|
|
component.setOffset( QgsPoint( 0.0, -ascentOffset ) );
|
|
component.setRotation( -component.rotation() * 180 / M_PI );
|
|
component.setRotationOffset( 0.0 );
|
|
|
|
if ( drawType == QgsPalLabeling::LabelBuffer )
|
|
{
|
|
// draw label's buffer
|
|
QgsPalLabeling::drawLabelBuffer( context, component, tmpLyr );
|
|
}
|
|
else
|
|
{
|
|
// draw label's text, QPainterPath method
|
|
QPainterPath path;
|
|
path.setFillRule( Qt::WindingFill );
|
|
path.addText( 0, 0, tmpLyr.textFont, component.text() );
|
|
|
|
// store text's drawing in QPicture for drop shadow call
|
|
QPicture textPict;
|
|
QPainter textp;
|
|
textp.begin( &textPict );
|
|
textp.setPen( Qt::NoPen );
|
|
textp.setBrush( tmpLyr.textColor );
|
|
textp.drawPath( path );
|
|
// TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
|
|
// e.g. some capitalization options, but not others
|
|
//textp.setFont( tmpLyr.textFont );
|
|
//textp.setPen( tmpLyr.textColor );
|
|
//textp.drawText( 0, 0, component.text() );
|
|
textp.end();
|
|
|
|
if ( tmpLyr.shadowDraw && tmpLyr.shadowUnder == QgsPalLayerSettings::ShadowText )
|
|
{
|
|
component.setPicture( &textPict );
|
|
component.setPictureBuffer( 0.0 ); // no pen width to deal with
|
|
component.setOrigin( QgsPoint( 0.0, 0.0 ) );
|
|
|
|
QgsPalLabeling::drawLabelShadow( context, component, tmpLyr );
|
|
}
|
|
|
|
// paint the text
|
|
if ( context.useAdvancedEffects() )
|
|
{
|
|
painter->setCompositionMode( tmpLyr.blendMode );
|
|
}
|
|
|
|
// scale for any print output or image saving @ specific dpi
|
|
painter->scale( component.dpiRatio(), component.dpiRatio() );
|
|
|
|
if ( mEngine->testFlag( QgsLabelingEngineV2::RenderOutlineLabels ) )
|
|
{
|
|
// draw outlined text
|
|
_fixQPictureDPI( painter );
|
|
painter->drawPicture( 0, 0, textPict );
|
|
}
|
|
else
|
|
{
|
|
// draw text as text (for SVG and PDF exports)
|
|
painter->setFont( tmpLyr.textFont );
|
|
painter->setPen( tmpLyr.textColor );
|
|
painter->setRenderHint( QPainter::TextAntialiasing );
|
|
painter->drawText( 0, 0, component.text() );
|
|
}
|
|
}
|
|
painter->restore();
|
|
}
|
|
}
|
|
|
|
// NOTE: this used to be within above multi-line loop block, at end. (a mistake since 2010? [LS])
|
|
if ( label->getNextPart() )
|
|
drawLabelPrivate( label->getNextPart(), context, tmpLyr, drawType, dpiRatio );
|
|
}
|