[FEATURE][labels] New text "background" setting for marker symbol backgrounds

Allows for rendering a marker symbol in the background of labels
(complementing the existing shapes/SVG choices). This allows use
of QGIS marker symbology as a background symbol behind labels (e.g.
for highway shield labels)
This commit is contained in:
Nyall Dawson 2019-06-27 13:32:17 +10:00
parent 0bc48b7b6a
commit da79b6f0e6
22 changed files with 967 additions and 719 deletions

View File

@ -268,7 +268,8 @@ Container for settings relating to a text background object.
ShapeSquare,
ShapeEllipse,
ShapeCircle,
ShapeSVG
ShapeSVG,
ShapeMarkerSymbol,
};
enum SizeType
@ -344,6 +345,34 @@ QgsTextBackgroundSettings.ShapeSVG. The path must be absolute.
:param file: Absolute SVG file path
.. seealso:: :py:func:`svgFile`
%End
QgsMarkerSymbol *markerSymbol() const;
%Docstring
Returns the marker symbol to be rendered in the background. Ownership remains with
the background settings.
.. note::
This is only used when the type() is QgsTextBackgroundSettings.ShapeMarkerSymbol.
.. seealso:: :py:func:`setMarkerSymbol`
.. versionadded:: 3.10
%End
void setMarkerSymbol( QgsMarkerSymbol *symbol /Transfer/ );
%Docstring
Sets the current marker ``symbol`` for the background shape. Ownership is transferred
to the background settings.
.. note::
This is only used when the type() is QgsTextBackgroundSettings.ShapeMarkerSymbol.
.. seealso:: :py:func:`markerSymbol`
.. versionadded:: 3.10
%End
SizeType sizeType() const;

View File

@ -12,7 +12,7 @@
class QgsTextFormatWidget : QWidget, protected Ui::QgsTextFormatWidgetBase
class QgsTextFormatWidget : QWidget, QgsExpressionContextGenerator, protected Ui::QgsTextFormatWidgetBase
{
%Docstring
A widget for customizing text formatting settings.
@ -111,6 +111,9 @@ Controls whether data defined alignment buttons are enabled.
:param enable: set to ``True`` to enable alignment controls
%End
virtual QgsExpressionContext createExpressionContext() const;
protected slots:

View File

@ -2865,6 +2865,10 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
{
shpkind = QgsTextBackgroundSettings::ShapeSVG;
}
else if ( skind.compare( QLatin1String( "marker" ), Qt::CaseInsensitive ) == 0 )
{
shpkind = QgsTextBackgroundSettings::ShapeMarkerSymbol;
}
shapeKind = shpkind;
dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shpkind ) ) );
}
@ -2930,7 +2934,16 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
{
skip = true;
}
if ( shapeKind == QgsTextBackgroundSettings::ShapeMarkerSymbol
&& ( !background.markerSymbol()
|| ( background.markerSymbol()
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
&& ddShpSizeX == 0.0 ) ) )
{
skip = true;
}
if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
&& shapeKind != QgsTextBackgroundSettings::ShapeMarkerSymbol
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
&& ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
{

View File

@ -177,13 +177,12 @@ void QgsTextBufferSettings::setBlendMode( QPainter::CompositionMode mode )
QgsPaintEffect *QgsTextBufferSettings::paintEffect() const
{
return d->paintEffect;
return d->paintEffect.get();
}
void QgsTextBufferSettings::setPaintEffect( QgsPaintEffect *effect )
{
delete d->paintEffect;
d->paintEffect = effect;
d->paintEffect.reset( effect );
}
void QgsTextBufferSettings::readFromLayer( QgsVectorLayer *layer )
@ -337,7 +336,7 @@ QDomElement QgsTextBufferSettings::writeXml( QDomDocument &doc ) const
textBufferElem.setAttribute( QStringLiteral( "bufferOpacity" ), d->opacity );
textBufferElem.setAttribute( QStringLiteral( "bufferJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
textBufferElem.setAttribute( QStringLiteral( "bufferBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect ) )
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
d->paintEffect->saveProperties( doc, textBufferElem );
return textBufferElem;
}
@ -399,6 +398,16 @@ void QgsTextBackgroundSettings::setSvgFile( const QString &file )
d->svgFile = file;
}
QgsMarkerSymbol *QgsTextBackgroundSettings::markerSymbol() const
{
return d->markerSymbol.get();
}
void QgsTextBackgroundSettings::setMarkerSymbol( QgsMarkerSymbol *symbol )
{
d->markerSymbol.reset( symbol );
}
QgsTextBackgroundSettings::SizeType QgsTextBackgroundSettings::sizeType() const
{
return d->sizeType;
@ -601,13 +610,12 @@ void QgsTextBackgroundSettings::setJoinStyle( Qt::PenJoinStyle style )
QgsPaintEffect *QgsTextBackgroundSettings::paintEffect() const
{
return d->paintEffect;
return d->paintEffect.get();
}
void QgsTextBackgroundSettings::setPaintEffect( QgsPaintEffect *effect )
{
delete d->paintEffect;
d->paintEffect = effect;
d->paintEffect.reset( effect );
}
void QgsTextBackgroundSettings::readFromLayer( QgsVectorLayer *layer )
@ -861,6 +869,12 @@ void QgsTextBackgroundSettings::readXml( const QDomElement &elem, const QgsReadW
setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
else
setPaintEffect( nullptr );
const QDomElement symbolElem = backgroundElem.firstChildElement( QStringLiteral( "symbol" ) );
if ( !symbolElem.isNull() )
setMarkerSymbol( QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElem, context ) );
else
setMarkerSymbol( nullptr );
}
QDomElement QgsTextBackgroundSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
@ -892,8 +906,12 @@ QDomElement QgsTextBackgroundSettings::writeXml( QDomDocument &doc, const QgsRea
backgroundElem.setAttribute( QStringLiteral( "shapeJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
backgroundElem.setAttribute( QStringLiteral( "shapeOpacity" ), d->opacity );
backgroundElem.setAttribute( QStringLiteral( "shapeBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect ) )
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
d->paintEffect->saveProperties( doc, backgroundElem );
if ( d->markerSymbol )
backgroundElem.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "marker" ), d->markerSymbol.get(), doc, context ) );
return backgroundElem;
}
@ -1857,7 +1875,7 @@ QgsTextFormat QgsTextRenderer::updateShadowPosition( const QgsTextFormat &format
return format;
QgsTextFormat tmpFormat = format;
if ( tmpFormat.background().enabled() )
if ( tmpFormat.background().enabled() && tmpFormat.background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadow not compatible with marker symbol backgrounds
{
tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
}
@ -2198,264 +2216,291 @@ void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer
// TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
switch ( background.type() )
{
// all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
if ( background.svgFile().isEmpty() )
return;
double sizeOut = 0.0;
// only one size used for SVG sizing/scaling (no use of shapeSize.y() or Y field in gui)
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
case QgsTextBackgroundSettings::ShapeSVG:
case QgsTextBackgroundSettings::ShapeMarkerSymbol:
{
sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
}
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
{
sizeOut = std::max( component.size.width(), component.size.height() );
double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
// all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
// add buffer
sizeOut += bufferSize * 2;
}
if ( background.type() == QgsTextBackgroundSettings::ShapeSVG && background.svgFile().isEmpty() )
return;
// don't bother rendering symbols smaller than 1x1 pixels in size
// TODO: add option to not show any svgs under/over a certain size
if ( sizeOut < 1.0 )
return;
if ( background.type() == QgsTextBackgroundSettings::ShapeMarkerSymbol && !background.markerSymbol() )
return;
QgsStringMap map; // for SVG symbology marker
map[QStringLiteral( "name" )] = background.svgFile().trimmed();
map[QStringLiteral( "size" )] = QString::number( sizeOut );
map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
double sizeOut = 0.0;
// only one size used for SVG sizing/scaling (no use of shapeSize.y() or Y field in gui)
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
{
sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
}
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
{
sizeOut = std::max( component.size.width(), component.size.height() );
double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
// offset is handled by this local painter
// TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
//map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
//map["offset_unit"] = QgsUnitTypes::encodeUnit(
// tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
// add buffer
sizeOut += bufferSize * 2;
}
map[QStringLiteral( "fill" )] = background.fillColor().name();
map[QStringLiteral( "outline" )] = background.strokeColor().name();
map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
// don't bother rendering symbols smaller than 1x1 pixels in size
// TODO: add option to not show any svgs under/over a certain size
if ( sizeOut < 1.0 )
return;
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
{
QgsTextShadowSettings shadow = format.shadow();
// configure SVG shadow specs
QgsStringMap shdwmap( map );
shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
{
QgsStringMap map; // for SVG symbology marker
map[QStringLiteral( "name" )] = background.svgFile().trimmed();
map[QStringLiteral( "size" )] = QString::number( sizeOut );
map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
// store SVG's drawing in QPicture for drop shadow call
QPicture svgPict;
QPainter svgp;
svgp.begin( &svgPict );
// offset is handled by this local painter
// TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
//map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
//map["offset_unit"] = QgsUnitTypes::encodeUnit(
// tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
// draw shadow symbol
map[QStringLiteral( "fill" )] = background.fillColor().name();
map[QStringLiteral( "outline" )] = background.strokeColor().name();
map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
// clone current render context map unit/mm conversion factors, but not
// other map canvas parameters, then substitute this painter for use in symbology painting
// NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
// but will be created relative to the SVG's computed size, not the current map canvas
QgsRenderContext shdwContext;
shdwContext.setMapToPixel( context.mapToPixel() );
shdwContext.setScaleFactor( context.scaleFactor() );
shdwContext.setPainter( &svgp );
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
{
QgsTextShadowSettings shadow = format.shadow();
// configure SVG shadow specs
QgsStringMap shdwmap( map );
shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
QgsSymbolLayer *symShdwL = QgsSvgMarkerSymbolLayer::create( shdwmap );
QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL );
QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
// store SVG's drawing in QPicture for drop shadow call
QPicture svgPict;
QPainter svgp;
svgp.begin( &svgPict );
svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
svgp.end();
// draw shadow symbol
component.picture = svgPict;
// TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
component.pictureBuffer = 0.0;
// clone current render context map unit/mm conversion factors, but not
// other map canvas parameters, then substitute this painter for use in symbology painting
// NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
// but will be created relative to the SVG's computed size, not the current map canvas
QgsRenderContext shdwContext;
shdwContext.setMapToPixel( context.mapToPixel() );
shdwContext.setScaleFactor( context.scaleFactor() );
shdwContext.setPainter( &svgp );
component.size = QSizeF( sizeOut, sizeOut );
component.offset = QPointF( 0.0, 0.0 );
QgsSymbolLayer *symShdwL = QgsSvgMarkerSymbolLayer::create( shdwmap );
QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL );
QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
// rotate about origin center of SVG
svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
svgp.end();
component.picture = svgPict;
// TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
component.pictureBuffer = 0.0;
component.size = QSizeF( sizeOut, sizeOut );
component.offset = QPointF( 0.0, 0.0 );
// rotate about origin center of SVG
p->save();
p->translate( component.center.x(), component.center.y() );
p->rotate( component.rotation );
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
p->translate( QPointF( xoff, yoff ) );
p->rotate( component.rotationOffset );
p->translate( -sizeOut / 2, sizeOut / 2 );
if ( context.flags() & QgsRenderContext::Antialiasing )
{
p->setRenderHint( QPainter::Antialiasing );
}
drawShadow( context, component, format );
p->restore();
delete svgShdwM;
svgShdwM = nullptr;
}
renderedSymbol.reset( );
QgsSymbolLayer *symL = QgsSvgMarkerSymbolLayer::create( map );
renderedSymbol.reset( new QgsMarkerSymbol( QgsSymbolLayerList() << symL ) );
}
else
{
renderedSymbol.reset( background.markerSymbol()->clone() );
renderedSymbol->setSize( sizeOut );
renderedSymbol->setSizeUnit( QgsUnitTypes::RenderPixels );
}
renderedSymbol->setOpacity( background.opacity() );
// draw the actual symbol
p->save();
if ( context.useAdvancedEffects() )
{
p->setCompositionMode( background.blendMode() );
}
if ( context.flags() & QgsRenderContext::Antialiasing )
{
p->setRenderHint( QPainter::Antialiasing );
}
p->translate( component.center.x(), component.center.y() );
p->rotate( component.rotation );
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
p->translate( QPointF( xoff, yoff ) );
p->rotate( component.rotationOffset );
p->translate( -sizeOut / 2, sizeOut / 2 );
const QgsFeature f = context.expressionContext().feature();
renderedSymbol->startRender( context, context.expressionContext().fields() );
renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
renderedSymbol->stopRender( context );
p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
p->restore();
break;
}
case QgsTextBackgroundSettings::ShapeRectangle:
case QgsTextBackgroundSettings::ShapeCircle:
case QgsTextBackgroundSettings::ShapeSquare:
case QgsTextBackgroundSettings::ShapeEllipse:
{
double w = component.size.width();
double h = component.size.height();
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
{
w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
background.sizeMapUnitScale() );
h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
background.sizeMapUnitScale() );
}
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
{
if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
{
if ( w > h )
h = w;
else if ( h > w )
w = h;
}
else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
{
// start with label bound by circle
h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
w = h;
}
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
{
// start with label bound by ellipse
h = h * M_SQRT1_2 * 2;
w = w * M_SQRT1_2 * 2;
}
double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
background.sizeMapUnitScale() );
double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
background.sizeMapUnitScale() );
w += bufferWidth * 2;
h += bufferHeight * 2;
}
// offsets match those of symbology: -x = left, -y = up
QRectF rect( -w / 2.0, - h / 2.0, w, h );
if ( rect.isNull() )
return;
p->save();
if ( context.flags() & QgsRenderContext::Antialiasing )
{
p->setRenderHint( QPainter::Antialiasing );
}
p->translate( QPointF( component.center.x(), component.center.y() ) );
p->rotate( component.rotation );
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
p->translate( QPointF( xoff, yoff ) );
p->rotate( component.rotationOffset );
drawShadow( context, component, format );
p->restore();
double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
delete svgShdwM;
svgShdwM = nullptr;
}
// draw the actual symbol
QgsSymbolLayer *symL = QgsSvgMarkerSymbolLayer::create( map );
QgsSvgMarkerSymbolLayer *svgM = static_cast<QgsSvgMarkerSymbolLayer *>( symL );
QgsSymbolRenderContext svgContext( context, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
p->save();
if ( context.useAdvancedEffects() )
{
p->setCompositionMode( background.blendMode() );
}
if ( context.flags() & QgsRenderContext::Antialiasing )
{
p->setRenderHint( QPainter::Antialiasing );
}
p->translate( component.center.x(), component.center.y() );
p->rotate( component.rotation );
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
p->translate( QPointF( xoff, yoff ) );
p->rotate( component.rotationOffset );
svgM->renderPoint( QPointF( 0, 0 ), svgContext );
p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
p->restore();
delete svgM;
svgM = nullptr;
}
else // Generated Shapes
{
double w = component.size.width();
double h = component.size.height();
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
{
w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
background.sizeMapUnitScale() );
h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
background.sizeMapUnitScale() );
}
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
{
if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
QPen pen;
if ( background.strokeWidth() > 0 )
{
if ( w > h )
h = w;
else if ( h > w )
w = h;
}
else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
{
// start with label bound by circle
h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
w = h;
}
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
{
// start with label bound by ellipse
h = h * M_SQRT1_2 * 2;
w = w * M_SQRT1_2 * 2;
}
double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
background.sizeMapUnitScale() );
double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
background.sizeMapUnitScale() );
w += bufferWidth * 2;
h += bufferHeight * 2;
}
// offsets match those of symbology: -x = left, -y = up
QRectF rect( -w / 2.0, - h / 2.0, w, h );
if ( rect.isNull() )
return;
p->save();
if ( context.flags() & QgsRenderContext::Antialiasing )
{
p->setRenderHint( QPainter::Antialiasing );
}
p->translate( QPointF( component.center.x(), component.center.y() ) );
p->rotate( component.rotation );
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
p->translate( QPointF( xoff, yoff ) );
p->rotate( component.rotationOffset );
double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
QPen pen;
if ( background.strokeWidth() > 0 )
{
pen.setColor( background.strokeColor() );
pen.setWidthF( penSize );
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
pen.setJoinStyle( background.joinStyle() );
}
else
{
pen = Qt::NoPen;
}
// store painting in QPicture for shadow drawing
QPicture shapePict;
QPainter shapep;
shapep.begin( &shapePict );
shapep.setPen( pen );
shapep.setBrush( background.fillColor() );
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
|| background.type() == QgsTextBackgroundSettings::ShapeSquare )
{
if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
{
shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
pen.setColor( background.strokeColor() );
pen.setWidthF( penSize );
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
pen.setJoinStyle( background.joinStyle() );
}
else
{
double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
shapep.drawRoundedRect( rect, xRadius, yRadius );
pen = Qt::NoPen;
}
}
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
|| background.type() == QgsTextBackgroundSettings::ShapeCircle )
{
shapep.drawEllipse( rect );
}
shapep.end();
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
{
component.picture = shapePict;
component.pictureBuffer = penSize / 2.0;
// store painting in QPicture for shadow drawing
QPicture shapePict;
QPainter shapep;
shapep.begin( &shapePict );
shapep.setPen( pen );
shapep.setBrush( background.fillColor() );
component.size = rect.size();
component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
drawShadow( context, component, format );
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
|| background.type() == QgsTextBackgroundSettings::ShapeSquare )
{
if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
{
shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
}
else
{
double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
shapep.drawRoundedRect( rect, xRadius, yRadius );
}
}
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
|| background.type() == QgsTextBackgroundSettings::ShapeCircle )
{
shapep.drawEllipse( rect );
}
shapep.end();
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
{
component.picture = shapePict;
component.pictureBuffer = penSize / 2.0;
component.size = rect.size();
component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
drawShadow( context, component, format );
}
p->setOpacity( background.opacity() );
if ( context.useAdvancedEffects() )
{
p->setCompositionMode( background.blendMode() );
}
// scale for any print output or image saving @ specific dpi
p->scale( component.dpiRatio, component.dpiRatio );
_fixQPictureDPI( p );
p->drawPicture( 0, 0, shapePict );
p->restore();
break;
}
p->setOpacity( background.opacity() );
if ( context.useAdvancedEffects() )
{
p->setCompositionMode( background.blendMode() );
}
// scale for any print output or image saving @ specific dpi
p->scale( component.dpiRatio, component.dpiRatio );
_fixQPictureDPI( p );
p->drawPicture( 0, 0, shapePict );
p->restore();
}
if ( background.paintEffect() && background.paintEffect()->enabled() )
{
background.paintEffect()->end( context );

View File

@ -34,6 +34,7 @@ class QgsTextShadowSettingsPrivate;
class QgsTextSettingsPrivate;
class QgsVectorLayer;
class QgsPaintEffect;
class QgsMarkerSymbol;
/**
* \class QgsTextBufferSettings
@ -252,7 +253,8 @@ class CORE_EXPORT QgsTextBackgroundSettings
ShapeSquare, //!< Square - buffered sizes only
ShapeEllipse, //!< Ellipse
ShapeCircle, //!< Circle
ShapeSVG //!< SVG file
ShapeSVG, //!< SVG file
ShapeMarkerSymbol, //!< Marker symbol
};
/**
@ -327,6 +329,24 @@ class CORE_EXPORT QgsTextBackgroundSettings
*/
void setSvgFile( const QString &file );
/**
* Returns the marker symbol to be rendered in the background. Ownership remains with
* the background settings.
* \note This is only used when the type() is QgsTextBackgroundSettings::ShapeMarkerSymbol.
* \see setMarkerSymbol()
* \since QGIS 3.10
*/
QgsMarkerSymbol *markerSymbol() const;
/**
* Sets the current marker \a symbol for the background shape. Ownership is transferred
* to the background settings.
* \note This is only used when the type() is QgsTextBackgroundSettings::ShapeMarkerSymbol.
* \see markerSymbol()
* \since QGIS 3.10
*/
void setMarkerSymbol( QgsMarkerSymbol *symbol SIP_TRANSFER );
/**
* Returns the method used to determine the size of the background shape (e.g., fixed size or buffer
* around text).

View File

@ -64,11 +64,6 @@ class QgsTextBufferSettingsPrivate : public QSharedData
{
}
~QgsTextBufferSettingsPrivate()
{
delete paintEffect;
}
bool enabled = false;
double size = 1;
QgsUnitTypes::RenderUnit sizeUnit = QgsUnitTypes::RenderMillimeters;
@ -78,7 +73,7 @@ class QgsTextBufferSettingsPrivate : public QSharedData
bool fillBufferInterior = false;
Qt::PenJoinStyle joinStyle = Qt::RoundJoin;
QPainter::CompositionMode blendMode = QPainter::CompositionMode_SourceOver;
QgsPaintEffect *paintEffect = nullptr;
std::unique_ptr< QgsPaintEffect > paintEffect;
};
@ -121,14 +116,10 @@ class QgsTextBackgroundSettingsPrivate : public QSharedData
, strokeWidthMapUnitScale( other.strokeWidthMapUnitScale )
, joinStyle( other.joinStyle )
, paintEffect( other.paintEffect ? other.paintEffect->clone() : nullptr )
, markerSymbol( other.markerSymbol ? other.markerSymbol->clone() : nullptr )
{
}
~QgsTextBackgroundSettingsPrivate()
{
delete paintEffect;
}
bool enabled = false;
QgsTextBackgroundSettings::ShapeType type = QgsTextBackgroundSettings::ShapeRectangle;
QString svgFile; //!< Absolute path to SVG file
@ -152,7 +143,8 @@ class QgsTextBackgroundSettingsPrivate : public QSharedData
QgsUnitTypes::RenderUnit strokeWidthUnits = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale strokeWidthMapUnitScale;
Qt::PenJoinStyle joinStyle = Qt::BevelJoin;
QgsPaintEffect *paintEffect = nullptr;
std::unique_ptr< QgsPaintEffect > paintEffect;
std::unique_ptr< QgsMarkerSymbol > markerSymbol;
};

View File

@ -165,6 +165,16 @@ std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBack
layer.reset( svg );
break;
}
case QgsTextBackgroundSettings::ShapeMarkerSymbol:
{
// just grab the first layer and hope for the best
if ( settings.markerSymbol() && settings.markerSymbol()->symbolLayerCount() > 0 )
{
layer.reset( static_cast< QgsMarkerSymbolLayer * >( settings.markerSymbol()->symbolLayer( 0 )->clone() ) );
break;
}
FALLTHROUGH // not set, just go with the default
}
case QgsTextBackgroundSettings::ShapeCircle:
case QgsTextBackgroundSettings::ShapeEllipse:
case QgsTextBackgroundSettings::ShapeRectangle:
@ -184,6 +194,7 @@ std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBack
shape = QgsSimpleMarkerSymbolLayerBase::Square;
break;
case QgsTextBackgroundSettings::ShapeSVG:
case QgsTextBackgroundSettings::ShapeMarkerSymbol:
break;
}

View File

@ -339,7 +339,7 @@ void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext &context, pal::Lab
{
QgsTextFormat format = tmpLyr.format();
if ( tmpLyr.format().background().enabled() )
if ( tmpLyr.format().background().enabled() && tmpLyr.format().background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadows not compatible with marker symbol backgrounds
{
format.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
}

View File

@ -126,6 +126,7 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
mLayer = layer;
mTextFormatsListWidget->setLayerType( mLayer ? mLayer->geometryType() : mGeomType );
mBackgroundSymbolButton->setLayer( mLayer );
// load labeling settings from layer
updateGeometryTypeBasedWidgets();

View File

@ -27,7 +27,7 @@
///@cond PRIVATE
class GUI_EXPORT QgsLabelingGui : public QgsTextFormatWidget, private QgsExpressionContextGenerator
class GUI_EXPORT QgsLabelingGui : public QgsTextFormatWidget
{
Q_OBJECT

View File

@ -110,6 +110,7 @@ void QgsSymbolButton::showSettingsDialog()
if ( panel && panel->dockMode() )
{
QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr );
d->setPanelTitle( mDialogTitle );
d->setContext( symbolContext );
connect( d, &QgsPanelWidget::widgetChanged, this, &QgsSymbolButton::updateSymbolFromWidget );
connect( d, &QgsPanelWidget::panelAccepted, this, &QgsSymbolButton::cleanUpSymbolSelector );

View File

@ -29,6 +29,7 @@
#include "qgseffectstack.h"
#include "qgspainteffectregistry.h"
#include "qgsstylesavedialog.h"
#include "qgsexpressioncontextutils.h"
#include <QButtonGroup>
#include <QMessageBox>
@ -160,6 +161,20 @@ void QgsTextFormatWidget::initWidget()
mOffsetTypeComboBox->addItem( tr( "From point" ), QgsPalLayerSettings::FromPoint );
mOffsetTypeComboBox->addItem( tr( "From symbol bounds" ), QgsPalLayerSettings::FromSymbolBounds );
mShapeTypeCmbBx->addItem( tr( "Rectangle" ), QgsTextBackgroundSettings::ShapeRectangle );
mShapeTypeCmbBx->addItem( tr( "Square" ), QgsTextBackgroundSettings::ShapeSquare );
mShapeTypeCmbBx->addItem( tr( "Ellipse" ), QgsTextBackgroundSettings::ShapeEllipse );
mShapeTypeCmbBx->addItem( tr( "Circle" ), QgsTextBackgroundSettings::ShapeCircle );
mShapeTypeCmbBx->addItem( tr( "SVG" ), QgsTextBackgroundSettings::ShapeSVG );
mShapeTypeCmbBx->addItem( tr( "Marker Symbol" ), QgsTextBackgroundSettings::ShapeMarkerSymbol );
updateAvailableShadowPositions();
mBackgroundSymbolButton->setSymbolType( QgsSymbol::Marker );
mBackgroundSymbolButton->setDialogTitle( tr( "Background Symbol" ) );
mBackgroundSymbolButton->registerExpressionContextGenerator( this );
mBackgroundSymbolButton->setMapCanvas( mMapCanvas );
mCharDlg = new QgsCharacterSelectorDialog( this );
mRefFont = lblFontPreview->font();
@ -522,7 +537,8 @@ void QgsTextFormatWidget::initWidget()
<< mGeometryGeneratorGroupBox
<< mGeometryGenerator
<< mGeometryGeneratorType
<< mLinePlacementFlagsDDBtn;
<< mLinePlacementFlagsDDBtn
<< mBackgroundSymbolButton;
connectValueChanged( widgets, SLOT( updatePreview() ) );
connect( mQuadrantBtnGrp, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ), this, &QgsTextFormatWidget::updatePreview );
@ -610,6 +626,10 @@ void QgsTextFormatWidget::connectValueChanged( const QList<QWidget *> &widgets,
{
connect( w, SIGNAL( changed() ), this, slot );
}
else if ( QgsSymbolButton *w = qobject_cast<QgsSymbolButton *>( widget ) )
{
connect( w, SIGNAL( changed() ), this, slot );
}
else if ( QgsFieldExpressionWidget *w = qobject_cast< QgsFieldExpressionWidget *>( widget ) )
{
connect( w, SIGNAL( fieldChanged( QString ) ), this, slot );
@ -732,8 +752,9 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format )
// shape background
mShapeDrawChkBx->setChecked( background.enabled() );
mShapeTypeCmbBx->blockSignals( true );
mShapeTypeCmbBx->setCurrentIndex( background.type() );
mShapeTypeCmbBx->setCurrentIndex( mShapeTypeCmbBx->findData( background.type() ) );
mShapeTypeCmbBx->blockSignals( false );
updateAvailableShadowPositions();
mShapeSVGPathLineEdit->setText( background.svgFile() );
mShapeSizeCmbBx->setCurrentIndex( background.sizeType() );
@ -776,9 +797,11 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format )
}
mBackgroundEffectWidget->setPaintEffect( mBackgroundEffect.get() );
mBackgroundSymbolButton->setSymbol( background.markerSymbol() ? background.markerSymbol()->clone() : QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) );
// drop shadow
mShadowDrawChkBx->setChecked( shadow.enabled() );
mShadowUnderCmbBx->setCurrentIndex( shadow.shadowPlacement() );
mShadowUnderCmbBx->setCurrentIndex( mShadowUnderCmbBx->findData( shadow.shadowPlacement() ) );
mShadowOffsetAngleSpnBx->setValue( shadow.offsetAngle() );
mShadowOffsetSpnBx->setValue( shadow.offsetDistance() );
mShadowOffsetUnitWidget->setUnit( shadow.offsetUnit() );
@ -842,7 +865,7 @@ QgsTextFormat QgsTextFormatWidget::format() const
// shape background
QgsTextBackgroundSettings background;
background.setEnabled( mShapeDrawChkBx->isChecked() );
background.setType( ( QgsTextBackgroundSettings::ShapeType )mShapeTypeCmbBx->currentIndex() );
background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( mShapeTypeCmbBx->currentData().toInt() ) );
background.setSvgFile( mShapeSVGPathLineEdit->text() );
background.setSizeType( ( QgsTextBackgroundSettings::SizeType )mShapeSizeCmbBx->currentIndex() );
background.setSize( QSizeF( mShapeSizeXSpnBx->value(), mShapeSizeYSpnBx->value() ) );
@ -869,12 +892,13 @@ QgsTextFormat QgsTextFormatWidget::format() const
background.setPaintEffect( mBackgroundEffect->clone() );
else
background.setPaintEffect( nullptr );
background.setMarkerSymbol( mBackgroundSymbolButton->clonedSymbol< QgsMarkerSymbol >() );
format.setBackground( background );
// drop shadow
QgsTextShadowSettings shadow;
shadow.setEnabled( mShadowDrawChkBx->isChecked() );
shadow.setShadowPlacement( ( QgsTextShadowSettings::ShadowPlacement )mShadowUnderCmbBx->currentIndex() );
shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( mShadowUnderCmbBx->currentData().toInt() ) );
shadow.setOffsetAngle( mShadowOffsetAngleSpnBx->value() );
shadow.setOffsetDistance( mShadowOffsetSpnBx->value() );
shadow.setOffsetUnit( mShadowOffsetUnitWidget->unit() );
@ -1232,22 +1256,25 @@ void QgsTextFormatWidget::mCoordYDDBtn_activated( bool active )
}
}
void QgsTextFormatWidget::mShapeTypeCmbBx_currentIndexChanged( int index )
void QgsTextFormatWidget::mShapeTypeCmbBx_currentIndexChanged( int )
{
// shape background
bool isRect = ( ( QgsTextBackgroundSettings::ShapeType )index == QgsTextBackgroundSettings::ShapeRectangle
|| ( QgsTextBackgroundSettings::ShapeType )index == QgsTextBackgroundSettings::ShapeSquare );
bool isSVG = ( ( QgsTextBackgroundSettings::ShapeType )index == QgsTextBackgroundSettings::ShapeSVG );
QgsTextBackgroundSettings::ShapeType type = static_cast< QgsTextBackgroundSettings::ShapeType >( mShapeTypeCmbBx->currentData().toInt() );
const bool isRect = type == QgsTextBackgroundSettings::ShapeRectangle || type == QgsTextBackgroundSettings::ShapeSquare;
const bool isSVG = type == QgsTextBackgroundSettings::ShapeSVG;
const bool isMarker = type == QgsTextBackgroundSettings::ShapeMarkerSymbol;
showBackgroundPenStyle( isRect );
showBackgroundRadius( isRect );
mShapeSVGPathFrame->setVisible( isSVG );
mBackgroundSymbolButton->setVisible( isMarker );
// symbology SVG renderer only supports size^2 scaling, so we only use the x size spinbox
mShapeSizeYLabel->setVisible( !isSVG );
mShapeSizeYSpnBx->setVisible( !isSVG );
mShapeSizeYDDBtn->setVisible( !isSVG && mWidgetMode == Labeling );
mShapeSizeXLabel->setText( tr( "Size%1" ).arg( !isSVG ? tr( " X" ) : QString() ) );
mShapeSizeYLabel->setVisible( !isSVG && !isMarker );
mShapeSizeYSpnBx->setVisible( !isSVG && !isMarker );
mShapeSizeYDDBtn->setVisible( !isSVG && !isMarker && mWidgetMode == Labeling );
mShapeSizeXLabel->setText( tr( "Size%1" ).arg( !isSVG && !isMarker ? tr( " X" ) : QString() ) );
// SVG parameter setting doesn't support color's alpha component yet
mShapeFillColorBtn->setAllowOpacity( !isSVG );
@ -1263,21 +1290,23 @@ void QgsTextFormatWidget::mShapeTypeCmbBx_currentIndexChanged( int index )
}
else
{
mShapeFillColorLabel->setEnabled( true );
mShapeFillColorBtn->setEnabled( true );
mShapeFillColorDDBtn->setEnabled( true );
mShapeStrokeColorLabel->setEnabled( true );
mShapeStrokeColorBtn->setEnabled( true );
mShapeStrokeColorDDBtn->setEnabled( true );
mShapeStrokeWidthLabel->setEnabled( true );
mShapeStrokeWidthSpnBx->setEnabled( true );
mShapeStrokeWidthDDBtn->setEnabled( true );
mShapeFillColorLabel->setEnabled( !isMarker );
mShapeFillColorBtn->setEnabled( !isMarker );
mShapeFillColorDDBtn->setEnabled( !isMarker );
mShapeStrokeColorLabel->setEnabled( !isMarker );
mShapeStrokeColorBtn->setEnabled( !isMarker );
mShapeStrokeColorDDBtn->setEnabled( !isMarker );
mShapeStrokeWidthLabel->setEnabled( !isMarker );
mShapeStrokeWidthSpnBx->setEnabled( !isMarker );
mShapeStrokeWidthDDBtn->setEnabled( !isMarker );
}
// TODO: fix overriding SVG symbol's stroke width units in QgsSvgCache
// currently broken, fall back to symbol units only
mShapeStrokeWidthUnitWidget->setVisible( !isSVG );
mShapeStrokeWidthUnitWidget->setVisible( !isSVG && !isMarker );
mShapeSVGUnitsLabel->setVisible( isSVG );
mShapeStrokeUnitsDDBtn->setEnabled( !isSVG );
mShapeStrokeUnitsDDBtn->setEnabled( !isSVG && !isMarker );
updateAvailableShadowPositions();
}
void QgsTextFormatWidget::mShapeSVGPathLineEdit_textChanged( const QString &text )
@ -1376,6 +1405,28 @@ void QgsTextFormatWidget::updateSvgWidgets( const QString &svgPath )
mShapeSVGUnitsLabel->setEnabled( validSVG && strokeWidthParam );
}
void QgsTextFormatWidget::updateAvailableShadowPositions()
{
if ( mShadowUnderCmbBx->count() == 0
|| ( mShadowUnderCmbBx->findData( QgsTextShadowSettings::ShadowShape ) > -1 && mShapeTypeCmbBx->currentData().toInt() == QgsTextBackgroundSettings::ShapeMarkerSymbol )
|| ( mShadowUnderCmbBx->findData( QgsTextShadowSettings::ShadowShape ) == -1 && mShapeTypeCmbBx->currentData().toInt() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) )
{
// showing invalid choices, have to rebuild the list
QgsTextShadowSettings::ShadowPlacement currentPlacement = static_cast< QgsTextShadowSettings::ShadowPlacement >( mShadowUnderCmbBx->currentData().toInt() );
mShadowUnderCmbBx->clear();
mShadowUnderCmbBx->addItem( tr( "Lowest label component" ), QgsTextShadowSettings::ShadowLowest );
mShadowUnderCmbBx->addItem( tr( "Text" ), QgsTextShadowSettings::ShadowText );
mShadowUnderCmbBx->addItem( tr( "Buffer" ), QgsTextShadowSettings::ShadowBuffer );
if ( mShapeTypeCmbBx->currentData().toInt() != QgsTextBackgroundSettings::ShapeMarkerSymbol )
mShadowUnderCmbBx->addItem( tr( "Background" ), QgsTextShadowSettings::ShadowShape ); // not supported for marker symbol background shapes
mShadowUnderCmbBx->setCurrentIndex( mShadowUnderCmbBx->findData( currentPlacement ) );
if ( mShadowUnderCmbBx->currentIndex() == -1 )
mShadowUnderCmbBx->setCurrentIndex( 0 );
}
}
void QgsTextFormatWidget::setFormatFromStyle( const QString &name, QgsStyle::StyleEntity type )
{
switch ( type )
@ -1587,6 +1638,18 @@ void QgsTextFormatWidget::enableDataDefinedAlignment( bool enable )
mCoordAlignmentFrame->setEnabled( enable );
}
QgsExpressionContext QgsTextFormatWidget::createExpressionContext() const
{
QgsExpressionContext expContext;
expContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
<< QgsExpressionContextUtils::atlasScope( nullptr );
if ( mMapCanvas )
expContext << QgsExpressionContextUtils::mapSettingsScope( mMapCanvas->mapSettings() );
return expContext;
}
//
// QgsTextFormatDialog

View File

@ -46,7 +46,7 @@ class QgsCharacterSelectorDialog;
* \since QGIS 3.0
*/
class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextFormatWidgetBase
class GUI_EXPORT QgsTextFormatWidget : public QWidget, public QgsExpressionContextGenerator, protected Ui::QgsTextFormatWidgetBase
{
Q_OBJECT
Q_PROPERTY( QgsTextFormat format READ format )
@ -122,6 +122,8 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextForm
*/
void enableDataDefinedAlignment( bool enable );
QgsExpressionContext createExpressionContext() const override;
//! Text substitution list
QgsStringReplacementCollection mSubstitutions;
//! Quadrant button group
@ -222,6 +224,7 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextForm
void updatePreview();
void scrollPreview();
void updateSvgWidgets( const QString &svgPath );
void updateAvailableShadowPositions();
};

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,8 @@ from qgis.core import (QgsTextBufferSettings,
QgsTextFormat,
QgsUnitTypes,
QgsMapUnitScale,
QgsBlurEffect)
QgsBlurEffect,
QgsMarkerSymbol)
from qgis.gui import (QgsTextFormatWidget, QgsTextFormatDialog)
from qgis.PyQt.QtGui import (QColor, QPainter)
from qgis.PyQt.QtCore import (Qt, QSizeF, QPointF)
@ -84,6 +85,11 @@ class PyQgsTextFormatWidget(unittest.TestCase):
s.setStrokeWidthUnit(QgsUnitTypes.RenderMapUnits)
s.setStrokeWidthMapUnitScale(QgsMapUnitScale(QgsMapUnitScale(25, 26)))
s.setPaintEffect(QgsBlurEffect.create({'blur_level': '6.0', 'blur_unit': QgsUnitTypes.encodeUnit(QgsUnitTypes.RenderMillimeters), 'enabled': '1'}))
marker = QgsMarkerSymbol()
marker.setColor(QColor(100, 112, 134))
s.setMarkerSymbol(marker)
return s
def checkBackgroundSettings(self, s):

View File

@ -26,7 +26,8 @@ from qgis.core import (QgsTextBufferSettings,
QgsRenderContext,
QgsRectangle,
QgsRenderChecker,
QgsBlurEffect)
QgsBlurEffect,
QgsMarkerSymbol)
from qgis.PyQt.QtGui import (QColor, QPainter, QFont, QImage, QBrush, QPen, QFontMetricsF)
from qgis.PyQt.QtCore import (Qt, QSizeF, QPointF, QRectF, QDir, QSize)
from qgis.PyQt.QtXml import QDomDocument
@ -135,6 +136,11 @@ class PyQgsTextRenderer(unittest.TestCase):
s.setStrokeWidth(7)
s.setStrokeWidthUnit(QgsUnitTypes.RenderPoints)
s.setStrokeWidthMapUnitScale(QgsMapUnitScale(QgsMapUnitScale(25, 26)))
marker = QgsMarkerSymbol()
marker.setColor(QColor(100, 112, 134))
s.setMarkerSymbol(marker)
return s
def checkBackgroundSettings(self, s):
@ -162,6 +168,7 @@ class PyQgsTextRenderer(unittest.TestCase):
self.assertEqual(s.strokeWidth(), 7)
self.assertEqual(s.strokeWidthUnit(), QgsUnitTypes.RenderPoints)
self.assertEqual(s.strokeWidthMapUnitScale(), QgsMapUnitScale(25, 26))
self.assertEqual(s.markerSymbol().color().name(), '#647086')
def testBackgroundGettersSetters(self):
s = self.createBackgroundSettings()
@ -929,6 +936,75 @@ class PyQgsTextRenderer(unittest.TestCase):
assert self.checkRender(format, 'background_svg_buffer_mm', QgsTextRenderer.Background,
rect=QRectF(100, 100, 100, 100))
def testDrawBackgroundMarkerFixedPixels(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.background().setEnabled(True)
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
format.background().setSize(QSizeF(60, 80))
format.background().setSizeType(QgsTextBackgroundSettings.SizeFixed)
format.background().setSizeUnit(QgsUnitTypes.RenderPixels)
assert self.checkRender(format, 'background_marker_fixed_pixels', QgsTextRenderer.Background)
def testDrawBackgroundMarkerFixedMapUnits(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.background().setEnabled(True)
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
format.background().setSize(QSizeF(20, 20))
format.background().setSizeType(QgsTextBackgroundSettings.SizeFixed)
format.background().setSizeUnit(QgsUnitTypes.RenderMapUnits)
assert self.checkRender(format, 'background_marker_fixed_mapunits', QgsTextRenderer.Background)
def testDrawBackgroundMarkerFixedMM(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.background().setEnabled(True)
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
format.background().setSize(QSizeF(30, 30))
format.background().setSizeType(QgsTextBackgroundSettings.SizeFixed)
format.background().setSizeUnit(QgsUnitTypes.RenderMillimeters)
assert self.checkRender(format, 'background_marker_fixed_mm', QgsTextRenderer.Background)
def testDrawBackgroundMarkerBufferPixels(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.background().setEnabled(True)
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
format.background().setSize(QSizeF(30, 30))
format.background().setSizeType(QgsTextBackgroundSettings.SizeBuffer)
format.background().setSizeUnit(QgsUnitTypes.RenderPixels)
assert self.checkRender(format, 'background_marker_buffer_pixels', QgsTextRenderer.Background,
rect=QRectF(100, 100, 100, 100))
def testDrawBackgroundMarkerBufferMapUnits(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.background().setEnabled(True)
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
format.background().setSize(QSizeF(4, 4))
format.background().setSizeType(QgsTextBackgroundSettings.SizeBuffer)
format.background().setSizeUnit(QgsUnitTypes.RenderMapUnits)
assert self.checkRender(format, 'background_marker_buffer_mapunits', QgsTextRenderer.Background,
rect=QRectF(100, 100, 100, 100))
def testDrawBackgroundMarkerBufferMM(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.background().setEnabled(True)
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
format.background().setSize(QSizeF(10, 10))
format.background().setSizeType(QgsTextBackgroundSettings.SizeBuffer)
format.background().setSizeUnit(QgsUnitTypes.RenderMillimeters)
assert self.checkRender(format, 'background_marker_buffer_mm', QgsTextRenderer.Background,
rect=QRectF(100, 100, 100, 100))
def testDrawBackgroundRotationFixed(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB