diff --git a/python/core/auto_generated/qgstextrenderer.sip.in b/python/core/auto_generated/qgstextrenderer.sip.in index c59ae46e268..629308bb822 100644 --- a/python/core/auto_generated/qgstextrenderer.sip.in +++ b/python/core/auto_generated/qgstextrenderer.sip.in @@ -10,7 +10,6 @@ - class QgsTextBufferSettings { %Docstring @@ -240,6 +239,13 @@ Sets the current paint ``effect`` for the buffer. :param effect: paint effect. Ownership is transferred to the buffer settings. .. seealso:: :py:func:`paintEffect` +%End + + void updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ); +%Docstring +Updates the format by evaluating current values of data defined properties. + +.. versionadded:: 3.10 %End }; @@ -827,6 +833,13 @@ Read settings from a DOM element. Write settings into a DOM element. .. seealso:: :py:func:`readXml` +%End + + void updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ); +%Docstring +Updates the format by evaluating current values of data defined properties. + +.. versionadded:: 3.10 %End }; @@ -1167,6 +1180,13 @@ Read settings from a DOM element. Write settings into a DOM element. .. seealso:: :py:func:`readXml` +%End + + void updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ); +%Docstring +Updates the format by evaluating current values of data defined properties. + +.. versionadded:: 3.10 %End }; @@ -1549,6 +1569,34 @@ was not found on the system this will return the name of the replacement font. .. seealso:: :py:func:`fontFound` +%End + + QgsPropertyCollection &dataDefinedProperties(); +%Docstring +Returns a reference to the format's property collection, used for data defined overrides. + +.. seealso:: :py:func:`setDataDefinedProperties` + +.. versionadded:: 3.10 +%End + + + void setDataDefinedProperties( const QgsPropertyCollection &collection ); +%Docstring +Sets the format's property collection, used for data defined overrides. + +:param collection: property collection. Existing properties will be replaced. + +.. seealso:: :py:func:`dataDefinedProperties` + +.. versionadded:: 3.10 +%End + + void updateDataDefinedProperties( QgsRenderContext &context ); +%Docstring +Updates the format by evaluating current values of data defined properties. + +.. versionadded:: 3.10 %End static QPixmap textFormatPreviewPixmap( const QgsTextFormat &format, QSize size, const QString &previewText = QString(), int padding = 0 ); @@ -1727,6 +1775,42 @@ Returns the height of a text based on a given format. }; +class QgsTextRendererUtils +{ +%Docstring +Utility functions for text rendering. + +.. versionadded:: 3.10 +%End + +%TypeHeaderCode +#include "qgstextrenderer.h" +%End + public: + + static QgsTextBackgroundSettings::ShapeType decodeShapeType( const QString &string ); +%Docstring +Decodes a string representation of a background shape type to a type. +%End + + static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType( const QString &string ); +%Docstring +Decodes a string representation of a background size type to a type. +%End + + static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType( const QString &string ); +%Docstring +Decodes a string representation of a background rotation type to a type. +%End + + static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType( const QString &string ); +%Docstring +Decodes a string representation of a shadow placement type to a type. +%End + +}; + + /************************************************************************ * This file has been generated automatically from * * * diff --git a/src/core/qgspallabeling.cpp b/src/core/qgspallabeling.cpp index 930abd5e1b2..22fe439333f 100644 --- a/src/core/qgspallabeling.cpp +++ b/src/core/qgspallabeling.cpp @@ -591,13 +591,6 @@ QgsExpression *QgsPalLayerSettings::getLabelExpression() return expression; } -static Qt::PenJoinStyle _decodePenJoinStyle( const QString &str ) -{ - if ( str.compare( QLatin1String( "Miter" ), Qt::CaseInsensitive ) == 0 ) return Qt::MiterJoin; - if ( str.compare( QLatin1String( "Round" ), Qt::CaseInsensitive ) == 0 ) return Qt::RoundJoin; - return Qt::BevelJoin; // "Bevel" -} - QString updateDataDefinedString( const QString &value ) { // TODO: update or remove this when project settings for labeling are migrated to better XML layout @@ -2530,7 +2523,7 @@ bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType, if ( !joinstr.isEmpty() ) { - dataDefinedValues.insert( p, QVariant( static_cast< int >( _decodePenJoinStyle( joinstr ) ) ) ); + dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) ); return true; } return false; @@ -2920,31 +2913,8 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context ) if ( !skind.isEmpty() ) { - // "Rectangle" - QgsTextBackgroundSettings::ShapeType shpkind = QgsTextBackgroundSettings::ShapeRectangle; - - if ( skind.compare( QLatin1String( "Square" ), Qt::CaseInsensitive ) == 0 ) - { - shpkind = QgsTextBackgroundSettings::ShapeSquare; - } - else if ( skind.compare( QLatin1String( "Ellipse" ), Qt::CaseInsensitive ) == 0 ) - { - shpkind = QgsTextBackgroundSettings::ShapeEllipse; - } - else if ( skind.compare( QLatin1String( "Circle" ), Qt::CaseInsensitive ) == 0 ) - { - shpkind = QgsTextBackgroundSettings::ShapeCircle; - } - else if ( skind.compare( QLatin1String( "SVG" ), Qt::CaseInsensitive ) == 0 ) - { - 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 ) ) ); + shapeKind = QgsTextRendererUtils::decodeShapeType( skind ); + dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) ); } } @@ -2972,15 +2942,8 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context ) if ( !stype.isEmpty() ) { - // "Buffer" - QgsTextBackgroundSettings::SizeType sizType = QgsTextBackgroundSettings::SizeBuffer; - - if ( stype.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 ) - { - sizType = QgsTextBackgroundSettings::SizeFixed; - } - shpSizeType = sizType; - dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( sizType ) ) ); + shpSizeType = QgsTextRendererUtils::decodeBackgroundSizeType( stype ); + dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) ); } } @@ -3048,16 +3011,7 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context ) if ( !rotstr.isEmpty() ) { // "Sync" - QgsTextBackgroundSettings::RotationType rottype = QgsTextBackgroundSettings::RotationSync; - - if ( rotstr.compare( QLatin1String( "Offset" ), Qt::CaseInsensitive ) == 0 ) - { - rottype = QgsTextBackgroundSettings::RotationOffset; - } - else if ( rotstr.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 ) - { - rottype = QgsTextBackgroundSettings::RotationFixed; - } + QgsTextBackgroundSettings::RotationType rottype = QgsTextRendererUtils::decodeBackgroundRotationType( rotstr ); dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) ); } } @@ -3156,21 +3110,7 @@ void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context ) if ( !str.isEmpty() ) { - // "Lowest" - QgsTextShadowSettings::ShadowPlacement shdwtype = QgsTextShadowSettings::ShadowLowest; - - if ( str.compare( QLatin1String( "Text" ), Qt::CaseInsensitive ) == 0 ) - { - shdwtype = QgsTextShadowSettings::ShadowText; - } - else if ( str.compare( QLatin1String( "Buffer" ), Qt::CaseInsensitive ) == 0 ) - { - shdwtype = QgsTextShadowSettings::ShadowBuffer; - } - else if ( str.compare( QLatin1String( "Background" ), Qt::CaseInsensitive ) == 0 ) - { - shdwtype = QgsTextShadowSettings::ShadowShape; - } + QgsTextShadowSettings::ShadowPlacement shdwtype = QgsTextRendererUtils::decodeShadowPlacementType( str ); dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) ); } } diff --git a/src/core/qgstextrenderer.cpp b/src/core/qgstextrenderer.cpp index f6691948b59..50002785dbe 100644 --- a/src/core/qgstextrenderer.cpp +++ b/src/core/qgstextrenderer.cpp @@ -25,6 +25,7 @@ #include "qgspainting.h" #include "qgsmarkersymbollayer.h" #include "qgspainteffectregistry.h" +#include "qgspallabeling.h" #include #include @@ -185,6 +186,64 @@ void QgsTextBufferSettings::setPaintEffect( QgsPaintEffect *effect ) d->paintEffect.reset( effect ); } +void QgsTextBufferSettings::updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ) +{ + if ( properties.isActive( QgsPalLayerSettings::BufferDraw ) ) + { + context.expressionContext().setOriginalValueVariable( d->enabled ); + d->enabled = properties.valueAsBool( QgsPalLayerSettings::BufferDraw, context.expressionContext(), d->enabled ); + } + + if ( properties.isActive( QgsPalLayerSettings::BufferSize ) ) + { + context.expressionContext().setOriginalValueVariable( d->size ); + d->size = properties.valueAsDouble( QgsPalLayerSettings::BufferSize, context.expressionContext(), d->size ); + } + + QVariant exprVal = properties.value( QgsPalLayerSettings::BufferUnit, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->sizeUnit = res; + } + } + + if ( properties.isActive( QgsPalLayerSettings::BufferOpacity ) ) + { + context.expressionContext().setOriginalValueVariable( d->opacity * 100 ); + d->opacity = properties.value( QgsPalLayerSettings::BufferOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0; + } + + if ( properties.isActive( QgsPalLayerSettings::BufferColor ) ) + { + context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( d->color ) ); + d->color = properties.valueAsColor( QgsPalLayerSettings::BufferColor, context.expressionContext(), d->color ); + } + + if ( properties.isActive( QgsPalLayerSettings::BufferBlendMode ) ) + { + exprVal = properties.value( QgsPalLayerSettings::BufferBlendMode, context.expressionContext() ); + QString blendstr = exprVal.toString().trimmed(); + if ( !blendstr.isEmpty() ) + d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr ); + } + + if ( properties.isActive( QgsPalLayerSettings::BufferJoinStyle ) ) + { + exprVal = properties.value( QgsPalLayerSettings::BufferJoinStyle, context.expressionContext() ); + QString joinstr = exprVal.toString().trimmed(); + if ( !joinstr.isEmpty() ) + { + d->joinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ); + } + } +} + void QgsTextBufferSettings::readFromLayer( QgsVectorLayer *layer ) { // text buffer @@ -915,6 +974,181 @@ QDomElement QgsTextBackgroundSettings::writeXml( QDomDocument &doc, const QgsRea return backgroundElem; } +void QgsTextBackgroundSettings::updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ) +{ + if ( properties.isActive( QgsPalLayerSettings::ShapeDraw ) ) + { + context.expressionContext().setOriginalValueVariable( d->enabled ); + d->enabled = properties.valueAsBool( QgsPalLayerSettings::ShapeDraw, context.expressionContext(), d->enabled ); + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeSizeX ) ) + { + context.expressionContext().setOriginalValueVariable( d->size.width() ); + d->size.setWidth( properties.valueAsDouble( QgsPalLayerSettings::ShapeSizeX, context.expressionContext(), d->size.width() ) ); + } + if ( properties.isActive( QgsPalLayerSettings::ShapeSizeY ) ) + { + context.expressionContext().setOriginalValueVariable( d->size.height() ); + d->size.setHeight( properties.valueAsDouble( QgsPalLayerSettings::ShapeSizeY, context.expressionContext(), d->size.height() ) ); + } + + QVariant exprVal = properties.value( QgsPalLayerSettings::ShapeSizeUnits, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->sizeUnits = res; + } + } + + exprVal = properties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() ); + if ( exprVal.isValid() ) + { + const QString skind = exprVal.toString().trimmed(); + if ( !skind.isEmpty() ) + { + d->type = QgsTextRendererUtils::decodeShapeType( skind ); + } + } + + exprVal = properties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString stype = exprVal.toString().trimmed(); + if ( !stype.isEmpty() ) + { + d->sizeType = QgsTextRendererUtils::decodeBackgroundSizeType( stype ); + } + } + + // data defined shape SVG path? + context.expressionContext().setOriginalValueVariable( d->svgFile ); + exprVal = properties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString svgfile = exprVal.toString().trimmed(); + d->svgFile = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() ); + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeRotation ) ) + { + context.expressionContext().setOriginalValueVariable( d->rotation ); + d->rotation = properties.valueAsDouble( QgsPalLayerSettings::ShapeRotation, context.expressionContext(), d->rotation ); + } + exprVal = properties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString rotstr = exprVal.toString().trimmed(); + if ( !rotstr.isEmpty() ) + { + d->rotationType = QgsTextRendererUtils::decodeBackgroundRotationType( rotstr ); + } + } + + exprVal = properties.value( QgsPalLayerSettings::ShapeOffset, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString offset = exprVal.toString(); + if ( !offset.isEmpty() ) + { + d->offset = QgsSymbolLayerUtils::decodePoint( offset ); + } + } + exprVal = properties.value( QgsPalLayerSettings::ShapeOffsetUnits, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->offsetUnits = res; + } + } + + exprVal = properties.value( QgsPalLayerSettings::ShapeRadii, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString ptstr = exprVal.toString(); + if ( !ptstr.isEmpty() ) + { + d->radii = QgsSymbolLayerUtils::decodeSize( ptstr ); + } + } + + exprVal = properties.value( QgsPalLayerSettings::ShapeRadiiUnits, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->radiiUnits = res; + } + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeOpacity ) ) + { + context.expressionContext().setOriginalValueVariable( d->opacity * 100 ); + d->opacity = properties.value( QgsPalLayerSettings::ShapeOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0; + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeFillColor ) ) + { + context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( d->fillColor ) ); + d->fillColor = properties.valueAsColor( QgsPalLayerSettings::ShapeFillColor, context.expressionContext(), d->fillColor ); + } + if ( properties.isActive( QgsPalLayerSettings::ShapeStrokeColor ) ) + { + context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( d->strokeColor ) ); + d->strokeColor = properties.valueAsColor( QgsPalLayerSettings::ShapeStrokeColor, context.expressionContext(), d->strokeColor ); + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeStrokeWidth ) ) + { + context.expressionContext().setOriginalValueVariable( d->strokeWidth ); + d->strokeWidth = properties.valueAsDouble( QgsPalLayerSettings::ShapeStrokeWidth, context.expressionContext(), d->strokeWidth ); + } + exprVal = properties.value( QgsPalLayerSettings::ShapeStrokeWidthUnits, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->strokeWidthUnits = res; + } + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeBlendMode ) ) + { + exprVal = properties.value( QgsPalLayerSettings::ShapeBlendMode, context.expressionContext() ); + QString blendstr = exprVal.toString().trimmed(); + if ( !blendstr.isEmpty() ) + d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr ); + } + + if ( properties.isActive( QgsPalLayerSettings::ShapeJoinStyle ) ) + { + exprVal = properties.value( QgsPalLayerSettings::ShapeJoinStyle, context.expressionContext() ); + QString joinstr = exprVal.toString().trimmed(); + if ( !joinstr.isEmpty() ) + { + d->joinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ); + } + } +} + // // QgsTextShadowSettings @@ -1247,6 +1481,95 @@ QDomElement QgsTextShadowSettings::writeXml( QDomDocument &doc ) const return shadowElem; } +void QgsTextShadowSettings::updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ) +{ + if ( properties.isActive( QgsPalLayerSettings::ShadowDraw ) ) + { + context.expressionContext().setOriginalValueVariable( d->enabled ); + d->enabled = properties.valueAsBool( QgsPalLayerSettings::ShadowDraw, context.expressionContext(), d->enabled ); + } + + // data defined shadow under type? + QVariant exprVal = properties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString str = exprVal.toString().trimmed(); + if ( !str.isEmpty() ) + { + d->shadowUnder = QgsTextRendererUtils::decodeShadowPlacementType( str ); + } + } + + if ( properties.isActive( QgsPalLayerSettings::ShadowOffsetAngle ) ) + { + context.expressionContext().setOriginalValueVariable( d->offsetAngle ); + d->offsetAngle = properties.valueAsInt( QgsPalLayerSettings::ShadowOffsetAngle, context.expressionContext(), d->offsetAngle ); + } + if ( properties.isActive( QgsPalLayerSettings::ShadowOffsetDist ) ) + { + context.expressionContext().setOriginalValueVariable( d->offsetDist ); + d->offsetDist = properties.valueAsDouble( QgsPalLayerSettings::ShadowOffsetDist, context.expressionContext(), d->offsetDist ); + } + + exprVal = properties.value( QgsPalLayerSettings::ShadowOffsetUnits, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->offsetUnits = res; + } + } + + if ( properties.isActive( QgsPalLayerSettings::ShadowRadius ) ) + { + context.expressionContext().setOriginalValueVariable( d->radius ); + d->radius = properties.valueAsDouble( QgsPalLayerSettings::ShadowRadius, context.expressionContext(), d->radius ); + } + + exprVal = properties.value( QgsPalLayerSettings::ShadowRadiusUnits, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->radiusUnits = res; + } + } + + if ( properties.isActive( QgsPalLayerSettings::ShadowOpacity ) ) + { + context.expressionContext().setOriginalValueVariable( d->opacity * 100 ); + d->opacity = properties.value( QgsPalLayerSettings::ShadowOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0; + } + + if ( properties.isActive( QgsPalLayerSettings::ShadowScale ) ) + { + context.expressionContext().setOriginalValueVariable( d->scale ); + d->scale = properties.valueAsInt( QgsPalLayerSettings::ShadowScale, context.expressionContext(), d->scale ); + } + + if ( properties.isActive( QgsPalLayerSettings::ShadowColor ) ) + { + context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( d->color ) ); + d->color = properties.valueAsColor( QgsPalLayerSettings::ShadowColor, context.expressionContext(), d->color ); + } + + if ( properties.isActive( QgsPalLayerSettings::ShadowBlendMode ) ) + { + exprVal = properties.value( QgsPalLayerSettings::ShadowBlendMode, context.expressionContext() ); + QString blendstr = exprVal.toString().trimmed(); + if ( !blendstr.isEmpty() ) + d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr ); + } +} + // // QgsTextFormat // @@ -1596,6 +1919,29 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext { mBackgroundSettings.readXml( textStyleElem, context ); } + + if ( textStyleElem.firstChildElement( QStringLiteral( "dd_properties" ) ).isNull() ) + { + mBackgroundSettings.readXml( elem, context ); + } + else + { + mBackgroundSettings.readXml( textStyleElem, context ); + } + + QDomElement ddElem = textStyleElem.firstChildElement( QStringLiteral( "dd_properties" ) ); + if ( ddElem.isNull() ) + { + ddElem = elem.firstChildElement( QStringLiteral( "dd_properties" ) ); + } + if ( !ddElem.isNull() ) + { + d->mDataDefinedProperties.readXml( ddElem, QgsPalLayerSettings::propertyDefinitions() ); + } + else + { + d->mDataDefinedProperties.clear(); + } } QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const @@ -1620,9 +1966,14 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex textStyleElem.setAttribute( QStringLiteral( "blendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) ); textStyleElem.setAttribute( QStringLiteral( "multilineHeight" ), d->multilineHeight ); + QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) ); + d->mDataDefinedProperties.writeXml( ddElem, QgsPalLayerSettings::propertyDefinitions() ); + textStyleElem.appendChild( mBufferSettings.writeXml( doc ) ); textStyleElem.appendChild( mBackgroundSettings.writeXml( doc, context ) ); textStyleElem.appendChild( mShadowSettings.writeXml( doc ) ); + textStyleElem.appendChild( ddElem ); + return textStyleElem; } @@ -1735,6 +2086,195 @@ bool QgsTextFormat::containsAdvancedEffects() const return false; } +QgsPropertyCollection &QgsTextFormat::dataDefinedProperties() +{ + return d->mDataDefinedProperties; +} + +const QgsPropertyCollection &QgsTextFormat::dataDefinedProperties() const +{ + return d->mDataDefinedProperties; +} + +void QgsTextFormat::setDataDefinedProperties( const QgsPropertyCollection &collection ) +{ + d->mDataDefinedProperties = collection; +} + +void QgsTextFormat::updateDataDefinedProperties( QgsRenderContext &context ) +{ + if ( !d->mDataDefinedProperties.hasActiveProperties() ) + return; + + QString ddFontFamily; + context.expressionContext().setOriginalValueVariable( d->textFont.family() ); + QVariant exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString family = exprVal.toString().trimmed(); + if ( d->textFont.family() != family ) + { + // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature + // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here) + if ( QgsFontUtils::fontFamilyOnSystem( family ) ) + { + ddFontFamily = family; + } + } + } + + // data defined named font style? + QString ddFontStyle; + context.expressionContext().setOriginalValueVariable( d->textNamedStyle ); + exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString fontstyle = exprVal.toString().trimmed(); + ddFontStyle = fontstyle; + } + + bool ddBold = false; + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Bold ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.bold() ); + ddBold = d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false ) ; + } + + bool ddItalic = false; + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Italic ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.italic() ); + ddItalic = d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false ); + } + + // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented + // (currently defaults to what has been read in from layer settings) + QFont newFont; + QFontDatabase fontDb; + QFont appFont = QApplication::font(); + bool newFontBuilt = false; + if ( ddBold || ddItalic ) + { + // new font needs built, since existing style needs removed + newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : d->textFont.family() ); + newFontBuilt = true; + newFont.setBold( ddBold ); + newFont.setItalic( ddItalic ); + } + else if ( !ddFontStyle.isEmpty() + && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 ) + { + if ( !ddFontFamily.isEmpty() ) + { + // both family and style are different, build font from database + QFont styledfont = fontDb.font( ddFontFamily, ddFontStyle, appFont.pointSize() ); + if ( appFont != styledfont ) + { + newFont = styledfont; + newFontBuilt = true; + } + } + + // update the font face style + QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : d->textFont, ddFontStyle ); + } + else if ( !ddFontFamily.isEmpty() ) + { + if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 ) + { + // just family is different, build font from database + QFont styledfont = fontDb.font( ddFontFamily, d->textNamedStyle, appFont.pointSize() ); + if ( appFont != styledfont ) + { + newFont = styledfont; + newFontBuilt = true; + } + } + else + { + newFont = QFont( ddFontFamily ); + newFontBuilt = true; + } + } + + if ( newFontBuilt ) + { + // copy over existing font settings + newFont.setUnderline( d->textFont.underline() ); + newFont.setStrikeOut( d->textFont.strikeOut() ); + newFont.setWordSpacing( d->textFont.wordSpacing() ); + newFont.setLetterSpacing( QFont::AbsoluteSpacing, d->textFont.letterSpacing() ); + d->textFont = newFont; + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.underline() ); + d->textFont.setUnderline( d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), d->textFont.underline() ) ); + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.strikeOut() ); + d->textFont.setStrikeOut( d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), d->textFont.strikeOut() ) ); + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Color ) ) + { + context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( d->textColor ) ); + d->textColor = d->mDataDefinedProperties.valueAsColor( QgsPalLayerSettings::Color, context.expressionContext(), d->textColor ); + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Size ) ) + { + context.expressionContext().setOriginalValueVariable( size() ); + d->fontSize = d->mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), d->fontSize ); + } + + exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() ); + if ( exprVal.isValid() ) + { + QString units = exprVal.toString(); + if ( !units.isEmpty() ) + { + bool ok; + QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok ); + if ( ok ) + d->fontSizeUnits = res; + } + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontOpacity ) ) + { + context.expressionContext().setOriginalValueVariable( d->opacity * 100 ); + d->opacity = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0; + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontLetterSpacing ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.letterSpacing() ); + d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, d->mDataDefinedProperties.value( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), d->textFont.letterSpacing() ).toDouble() ); + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontWordSpacing ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.wordSpacing() ); + d->textFont.setWordSpacing( d->mDataDefinedProperties.value( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), d->textFont.wordSpacing() ).toDouble() ); + } + + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontBlendMode ) ) + { + exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontBlendMode, context.expressionContext() ); + QString blendstr = exprVal.toString().trimmed(); + if ( !blendstr.isEmpty() ) + d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr ); + } + + mShadowSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties ); + mBackgroundSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties ); + mBufferSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties ); +} + QPixmap QgsTextFormat::textFormatPreviewPixmap( const QgsTextFormat &format, QSize size, const QString &previewText, int padding ) { QgsTextFormat tempFormat = format; @@ -1837,7 +2377,10 @@ int QgsTextRenderer::sizeToPixel( double size, const QgsRenderContext &c, QgsUni void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool ) { - QgsTextFormat tmpFormat = updateShadowPosition( format ); + QgsTextFormat tmpFormat = format; + if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach + tmpFormat.updateDataDefinedProperties( context ); + tmpFormat = updateShadowPosition( tmpFormat ); if ( tmpFormat.background().enabled() ) { @@ -1854,7 +2397,10 @@ void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRend void QgsTextRenderer::drawText( QPointF point, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool ) { - QgsTextFormat tmpFormat = updateShadowPosition( format ); + QgsTextFormat tmpFormat = format; + if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach + tmpFormat.updateDataDefinedProperties( context ); + tmpFormat = updateShadowPosition( tmpFormat ); if ( tmpFormat.background().enabled() ) { @@ -2843,3 +3389,86 @@ void QgsTextRenderer::drawTextInternal( TextPart drawType, } } + +// +// QgsTextRendererUtils +// + +QgsTextBackgroundSettings::ShapeType QgsTextRendererUtils::decodeShapeType( const QString &string ) +{ + QgsTextBackgroundSettings::ShapeType shpkind = QgsTextBackgroundSettings::ShapeRectangle; + const QString skind = string.trimmed(); + + if ( skind.compare( QLatin1String( "Square" ), Qt::CaseInsensitive ) == 0 ) + { + shpkind = QgsTextBackgroundSettings::ShapeSquare; + } + else if ( skind.compare( QLatin1String( "Ellipse" ), Qt::CaseInsensitive ) == 0 ) + { + shpkind = QgsTextBackgroundSettings::ShapeEllipse; + } + else if ( skind.compare( QLatin1String( "Circle" ), Qt::CaseInsensitive ) == 0 ) + { + shpkind = QgsTextBackgroundSettings::ShapeCircle; + } + else if ( skind.compare( QLatin1String( "SVG" ), Qt::CaseInsensitive ) == 0 ) + { + shpkind = QgsTextBackgroundSettings::ShapeSVG; + } + else if ( skind.compare( QLatin1String( "marker" ), Qt::CaseInsensitive ) == 0 ) + { + shpkind = QgsTextBackgroundSettings::ShapeMarkerSymbol; + } + return shpkind; +} + +QgsTextBackgroundSettings::SizeType QgsTextRendererUtils::decodeBackgroundSizeType( const QString &string ) +{ + const QString stype = string.trimmed(); + // "Buffer" + QgsTextBackgroundSettings::SizeType sizType = QgsTextBackgroundSettings::SizeBuffer; + + if ( stype.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 ) + { + sizType = QgsTextBackgroundSettings::SizeFixed; + } + return sizType; +} + +QgsTextBackgroundSettings::RotationType QgsTextRendererUtils::decodeBackgroundRotationType( const QString &string ) +{ + const QString rotstr = string.trimmed(); + // "Sync" + QgsTextBackgroundSettings::RotationType rottype = QgsTextBackgroundSettings::RotationSync; + + if ( rotstr.compare( QLatin1String( "Offset" ), Qt::CaseInsensitive ) == 0 ) + { + rottype = QgsTextBackgroundSettings::RotationOffset; + } + else if ( rotstr.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 ) + { + rottype = QgsTextBackgroundSettings::RotationFixed; + } + return rottype; +} + +QgsTextShadowSettings::ShadowPlacement QgsTextRendererUtils::decodeShadowPlacementType( const QString &string ) +{ + const QString str = string.trimmed(); + // "Lowest" + QgsTextShadowSettings::ShadowPlacement shdwtype = QgsTextShadowSettings::ShadowLowest; + + if ( str.compare( QLatin1String( "Text" ), Qt::CaseInsensitive ) == 0 ) + { + shdwtype = QgsTextShadowSettings::ShadowText; + } + else if ( str.compare( QLatin1String( "Buffer" ), Qt::CaseInsensitive ) == 0 ) + { + shdwtype = QgsTextShadowSettings::ShadowBuffer; + } + else if ( str.compare( QLatin1String( "Background" ), Qt::CaseInsensitive ) == 0 ) + { + shdwtype = QgsTextShadowSettings::ShadowShape; + } + return shdwtype; +} diff --git a/src/core/qgstextrenderer.h b/src/core/qgstextrenderer.h index 8e1b5892183..86c85c601bb 100644 --- a/src/core/qgstextrenderer.h +++ b/src/core/qgstextrenderer.h @@ -35,6 +35,7 @@ class QgsTextSettingsPrivate; class QgsVectorLayer; class QgsPaintEffect; class QgsMarkerSymbol; +class QgsPropertyCollection; /** * \class QgsTextBufferSettings @@ -43,7 +44,6 @@ class QgsMarkerSymbol; * \note QgsTextBufferSettings objects are implicitly shared. * \since QGIS 3.0 */ - class CORE_EXPORT QgsTextBufferSettings { public: @@ -226,6 +226,12 @@ class CORE_EXPORT QgsTextBufferSettings */ void setPaintEffect( QgsPaintEffect *effect SIP_TRANSFER ); + /** + * Updates the format by evaluating current values of data defined properties. + * \since QGIS 3.10 + */ + void updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ); + private: QSharedDataPointer d; @@ -700,6 +706,12 @@ class CORE_EXPORT QgsTextBackgroundSettings */ QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const; + /** + * Updates the format by evaluating current values of data defined properties. + * \since QGIS 3.10 + */ + void updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ); + private: QSharedDataPointer d; @@ -980,6 +992,12 @@ class CORE_EXPORT QgsTextShadowSettings */ QDomElement writeXml( QDomDocument &doc ) const; + /** + * Updates the format by evaluating current values of data defined properties. + * \since QGIS 3.10 + */ + void updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties ); + private: QSharedDataPointer d; @@ -1306,6 +1324,35 @@ class CORE_EXPORT QgsTextFormat */ QString resolvedFontFamily() const { return mTextFontFamily; } + /** + * Returns a reference to the format's property collection, used for data defined overrides. + * \see setDataDefinedProperties() + * \since QGIS 3.10 + */ + QgsPropertyCollection &dataDefinedProperties(); + + /** + * Returns a reference to the format's property collection, used for data defined overrides. + * \see setDataDefinedProperties() + * \note not available in Python bindings + * \since QGIS 3.10 + */ + const QgsPropertyCollection &dataDefinedProperties() const SIP_SKIP; + + /** + * Sets the format's property collection, used for data defined overrides. + * \param collection property collection. Existing properties will be replaced. + * \see dataDefinedProperties() + * \since QGIS 3.10 + */ + void setDataDefinedProperties( const QgsPropertyCollection &collection ); + + /** + * Updates the format by evaluating current values of data defined properties. + * \since QGIS 3.10 + */ + void updateDataDefinedProperties( QgsRenderContext &context ); + /** * Returns a pixmap preview for a text \a format. * \param format text format @@ -1550,4 +1597,37 @@ class CORE_EXPORT QgsTextRenderer }; +/** + * \class QgsTextRendererUtils + * \ingroup core + * Utility functions for text rendering. + * \since QGIS 3.10 + */ +class CORE_EXPORT QgsTextRendererUtils +{ + public: + + /** + * Decodes a string representation of a background shape type to a type. + */ + static QgsTextBackgroundSettings::ShapeType decodeShapeType( const QString &string ); + + /** + * Decodes a string representation of a background size type to a type. + */ + static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType( const QString &string ); + + /** + * Decodes a string representation of a background rotation type to a type. + */ + static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType( const QString &string ); + + /** + * Decodes a string representation of a shadow placement type to a type. + */ + static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType( const QString &string ); + +}; + + #endif // QGSTEXTRENDERER_H diff --git a/src/core/qgstextrenderer_p.h b/src/core/qgstextrenderer_p.h index 034589981a5..4b0f9449090 100644 --- a/src/core/qgstextrenderer_p.h +++ b/src/core/qgstextrenderer_p.h @@ -218,6 +218,7 @@ class QgsTextSettingsPrivate : public QSharedData , blendMode( other.blendMode ) , multilineHeight( other.multilineHeight ) , previewBackgroundColor( other.previewBackgroundColor ) + , mDataDefinedProperties( other.mDataDefinedProperties ) { } @@ -232,6 +233,10 @@ class QgsTextSettingsPrivate : public QSharedData double multilineHeight = 1.0 ; //0.0 to 10.0, leading between lines as multiplyer of line height QColor previewBackgroundColor = Qt::white; + //! Property collection for data defined settings + QgsPropertyCollection mDataDefinedProperties; + + }; diff --git a/src/core/symbology/qgssymbollayerutils.cpp b/src/core/symbology/qgssymbollayerutils.cpp index e1b782c88ec..095c8ebeca2 100644 --- a/src/core/symbology/qgssymbollayerutils.cpp +++ b/src/core/symbology/qgssymbollayerutils.cpp @@ -186,9 +186,13 @@ QString QgsSymbolLayerUtils::encodePenJoinStyle( Qt::PenJoinStyle style ) Qt::PenJoinStyle QgsSymbolLayerUtils::decodePenJoinStyle( const QString &str ) { - if ( str == QLatin1String( "bevel" ) ) return Qt::BevelJoin; - if ( str == QLatin1String( "miter" ) ) return Qt::MiterJoin; - if ( str == QLatin1String( "round" ) ) return Qt::RoundJoin; + const QString cleaned = str.toLower().trimmed(); + if ( cleaned == QLatin1String( "bevel" ) ) + return Qt::BevelJoin; + if ( cleaned == QLatin1String( "miter" ) ) + return Qt::MiterJoin; + if ( cleaned == QLatin1String( "round" ) ) + return Qt::RoundJoin; return Qt::BevelJoin; } diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 8f899e95b8a..cc44e161bfd 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -27,7 +27,10 @@ from qgis.core import (QgsTextBufferSettings, QgsRectangle, QgsRenderChecker, QgsBlurEffect, - QgsMarkerSymbol) + QgsMarkerSymbol, + QgsPalLayerSettings, + QgsProperty, + QgsFontUtils) 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 @@ -48,6 +51,7 @@ class PyQgsTextRenderer(unittest.TestCase): def setUp(self): self.report = "

Python QgsTextRenderer Tests

\n" + QgsFontUtils.loadStandardTestFonts(['Bold', 'Oblique']) def tearDown(self): report_file_path = "%s/qgistest.html" % QDir.tempPath() @@ -289,6 +293,7 @@ class PyQgsTextRenderer(unittest.TestCase): s.setBlendMode(QPainter.CompositionMode_DestinationAtop) s.setLineHeight(5) s.setPreviewBackgroundColor(QColor(100, 150, 200)) + s.dataDefinedProperties().setProperty(QgsPalLayerSettings.Bold, QgsProperty.fromExpression('1>2')) return s def checkTextFormat(self, s): @@ -309,6 +314,7 @@ class PyQgsTextRenderer(unittest.TestCase): self.assertEqual(s.blendMode(), QPainter.CompositionMode_DestinationAtop) self.assertEqual(s.lineHeight(), 5) self.assertEqual(s.previewBackgroundColor().name(), '#6496c8') + self.assertEqual(s.dataDefinedProperties().property(QgsPalLayerSettings.Bold).expressionString(), '1>2') def testFormatGettersSetters(self): s = self.createFormatSettings() @@ -372,6 +378,318 @@ class PyQgsTextRenderer(unittest.TestCase): t.shadow().setBlendMode(QPainter.CompositionMode_SourceOver) self.assertFalse(t.containsAdvancedEffects()) + def testDataDefinedBufferSettings(self): + f = QgsTextFormat() + context = QgsRenderContext() + + # buffer enabled + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferDraw, QgsProperty.fromExpression('1')) + f.updateDataDefinedProperties(context) + self.assertTrue(f.buffer().enabled()) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferDraw, QgsProperty.fromExpression('0')) + context = QgsRenderContext() + f.updateDataDefinedProperties(context) + self.assertFalse(f.buffer().enabled()) + + # buffer size + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferSize, QgsProperty.fromExpression('7.8')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.buffer().size(), 7.8) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferUnit, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.buffer().sizeUnit(), QgsUnitTypes.RenderPixels) + + # buffer opacity + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferOpacity, QgsProperty.fromExpression('37')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.buffer().opacity(), 0.37) + + # blend mode + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferBlendMode, QgsProperty.fromExpression("'burn'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.buffer().blendMode(), QPainter.CompositionMode_ColorBurn) + + # join style + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferJoinStyle, QgsProperty.fromExpression("'miter'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.buffer().joinStyle(), Qt.MiterJoin) + + # color + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferColor, QgsProperty.fromExpression("'#ff0088'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.buffer().color().name(), '#ff0088') + + def testDataDefinedBackgroundSettings(self): + f = QgsTextFormat() + context = QgsRenderContext() + + # background enabled + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeDraw, QgsProperty.fromExpression('1')) + f.updateDataDefinedProperties(context) + self.assertTrue(f.background().enabled()) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeDraw, QgsProperty.fromExpression('0')) + context = QgsRenderContext() + f.updateDataDefinedProperties(context) + self.assertFalse(f.background().enabled()) + + # background size + f.background().setSize(QSizeF(13, 14)) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeSizeX, QgsProperty.fromExpression('7.8')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().size().width(), 7.8) + self.assertEqual(f.background().size().height(), 14) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeSizeY, QgsProperty.fromExpression('17.8')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().size().width(), 7.8) + self.assertEqual(f.background().size().height(), 17.8) + + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeSizeUnits, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().sizeUnit(), QgsUnitTypes.RenderPixels) + + # shape kind + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeKind, QgsProperty.fromExpression("'square'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().type(), QgsTextBackgroundSettings.ShapeSquare) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeKind, QgsProperty.fromExpression("'ellipse'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().type(), QgsTextBackgroundSettings.ShapeEllipse) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeKind, QgsProperty.fromExpression("'circle'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().type(), QgsTextBackgroundSettings.ShapeCircle) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeKind, QgsProperty.fromExpression("'svg'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().type(), QgsTextBackgroundSettings.ShapeSVG) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeKind, QgsProperty.fromExpression("'marker'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().type(), QgsTextBackgroundSettings.ShapeMarkerSymbol) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeKind, QgsProperty.fromExpression("'rect'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().type(), QgsTextBackgroundSettings.ShapeRectangle) + + # size type + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeSizeType, QgsProperty.fromExpression("'fixed'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().sizeType(), QgsTextBackgroundSettings.SizeFixed) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeSizeType, QgsProperty.fromExpression("'buffer'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().sizeType(), QgsTextBackgroundSettings.SizeBuffer) + + # svg path + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeSVGFile, QgsProperty.fromExpression("'my.svg'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().svgFile(), 'my.svg') + + # shape rotation + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeRotation, QgsProperty.fromExpression('67')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().rotation(), 67) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeRotationType, QgsProperty.fromExpression("'offset'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().rotationType(), QgsTextBackgroundSettings.RotationOffset) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeRotationType, QgsProperty.fromExpression("'fixed'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().rotationType(), QgsTextBackgroundSettings.RotationFixed) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeRotationType, QgsProperty.fromExpression("'sync'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().rotationType(), QgsTextBackgroundSettings.RotationSync) + + # shape offset + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeOffset, QgsProperty.fromExpression("'7,9'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().offset(), QPointF(7, 9)) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeOffsetUnits, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().offsetUnit(), QgsUnitTypes.RenderPixels) + + # shape radii + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeRadii, QgsProperty.fromExpression("'18,19'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().radii(), QSizeF(18, 19)) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeRadiiUnits, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().radiiUnit(), QgsUnitTypes.RenderPixels) + + # shape opacity + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeOpacity, QgsProperty.fromExpression('37')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().opacity(), 0.37) + + # color + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeFillColor, QgsProperty.fromExpression("'#ff0088'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().fillColor().name(), '#ff0088') + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeStrokeColor, QgsProperty.fromExpression("'#8800ff'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().strokeColor().name(), '#8800ff') + + # stroke width + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeStrokeWidth, QgsProperty.fromExpression('88')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().strokeWidth(), 88) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeStrokeWidthUnits, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().strokeWidthUnit(), QgsUnitTypes.RenderPixels) + + # blend mode + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeBlendMode, QgsProperty.fromExpression("'burn'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().blendMode(), QPainter.CompositionMode_ColorBurn) + + # join style + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShapeJoinStyle, QgsProperty.fromExpression("'miter'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.background().joinStyle(), Qt.MiterJoin) + + def testDataDefinedShadowSettings(self): + f = QgsTextFormat() + context = QgsRenderContext() + + # shadow enabled + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowDraw, QgsProperty.fromExpression('1')) + f.updateDataDefinedProperties(context) + self.assertTrue(f.shadow().enabled()) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowDraw, QgsProperty.fromExpression('0')) + context = QgsRenderContext() + f.updateDataDefinedProperties(context) + self.assertFalse(f.shadow().enabled()) + + # placement type + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowUnder, QgsProperty.fromExpression("'text'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().shadowPlacement(), QgsTextShadowSettings.ShadowText) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowUnder, QgsProperty.fromExpression("'buffer'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().shadowPlacement(), QgsTextShadowSettings.ShadowBuffer) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowUnder, QgsProperty.fromExpression("'background'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().shadowPlacement(), QgsTextShadowSettings.ShadowShape) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowUnder, QgsProperty.fromExpression("'svg'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().shadowPlacement(), QgsTextShadowSettings.ShadowLowest) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowUnder, QgsProperty.fromExpression("'lowest'")) + + # offset angle + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowOffsetAngle, QgsProperty.fromExpression('67')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().offsetAngle(), 67) + + # offset distance + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowOffsetDist, QgsProperty.fromExpression('38')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().offsetDistance(), 38) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowOffsetUnits, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().offsetUnit(), QgsUnitTypes.RenderPixels) + + # radius + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowRadius, QgsProperty.fromExpression('58')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().blurRadius(), 58) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowRadiusUnits, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().blurRadiusUnit(), QgsUnitTypes.RenderPixels) + + # opacity + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowOpacity, QgsProperty.fromExpression('37')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().opacity(), 0.37) + + # color + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowColor, QgsProperty.fromExpression("'#ff0088'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().color().name(), '#ff0088') + + # blend mode + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.ShadowBlendMode, QgsProperty.fromExpression("'burn'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.shadow().blendMode(), QPainter.CompositionMode_ColorBurn) + + def testDataDefinedFormatSettings(self): + f = QgsTextFormat() + font = f.font() + font.setUnderline(True) + font.setStrikeOut(True) + font.setWordSpacing(5.7) + font.setLetterSpacing(QFont.AbsoluteSpacing, 3.3) + f.setFont(font) + context = QgsRenderContext() + + # family + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Family, QgsProperty.fromExpression("'{}'".format(QgsFontUtils.getStandardTestFont().family()))) + f.updateDataDefinedProperties(context) + self.assertEqual(f.font().family(), QgsFontUtils.getStandardTestFont().family()) + + # style + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontStyle, QgsProperty.fromExpression("'Bold'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.font().styleName(), 'Bold') + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontStyle, QgsProperty.fromExpression("'Roman'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.font().styleName(), 'Roman') + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Bold, QgsProperty.fromExpression("1")) + f.updateDataDefinedProperties(context) + self.assertTrue(f.font().bold()) + self.assertFalse(f.font().italic()) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Bold, QgsProperty.fromExpression("0")) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Italic, QgsProperty.fromExpression("1")) + f.updateDataDefinedProperties(context) + self.assertFalse(f.font().bold()) + self.assertTrue(f.font().italic()) + self.assertTrue(f.font().underline()) + self.assertTrue(f.font().strikeOut()) + self.assertAlmostEqual(f.font().wordSpacing(), 5.7, 1) + self.assertAlmostEqual(f.font().letterSpacing(), 3.3, 1) + + # underline + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Underline, QgsProperty.fromExpression("0")) + f.updateDataDefinedProperties(context) + self.assertFalse(f.font().underline()) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Underline, QgsProperty.fromExpression("1")) + f.updateDataDefinedProperties(context) + self.assertTrue(f.font().underline()) + + # strikeout + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Strikeout, QgsProperty.fromExpression("0")) + f.updateDataDefinedProperties(context) + self.assertFalse(f.font().strikeOut()) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Strikeout, QgsProperty.fromExpression("1")) + f.updateDataDefinedProperties(context) + self.assertTrue(f.font().strikeOut()) + + # color + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Color, QgsProperty.fromExpression("'#ff0088'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.color().name(), '#ff0088') + + # size + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.Size, QgsProperty.fromExpression('38')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.size(), 38) + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontSizeUnit, QgsProperty.fromExpression("'pixel'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.sizeUnit(), QgsUnitTypes.RenderPixels) + + # opacity + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontOpacity, QgsProperty.fromExpression('37')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.opacity(), 0.37) + + # letter spacing + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontLetterSpacing, QgsProperty.fromExpression('58')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.font().letterSpacing(), 58) + + # word spacing + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontWordSpacing, QgsProperty.fromExpression('8')) + f.updateDataDefinedProperties(context) + self.assertEqual(f.font().wordSpacing(), 8) + + # blend mode + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontBlendMode, QgsProperty.fromExpression("'burn'")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.blendMode(), QPainter.CompositionMode_ColorBurn) + def testFontFoundFromLayer(self): layer = createEmptyLayer() layer.setCustomProperty('labeling/fontFamily', 'asdasd') @@ -1733,6 +2051,35 @@ class PyQgsTextRenderer(unittest.TestCase): assert self.checkRenderPoint(format, 'text_point_center_aligned', text=['test'], alignment=QgsTextRenderer.AlignCenter, point=QPointF(200, 200)) + def testDrawTextDataDefinedColorPoint(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(60) + format.setSizeUnit(QgsUnitTypes.RenderPoints) + format.setColor(QColor(0, 255, 0)) + format.dataDefinedProperties().setProperty(QgsPalLayerSettings.Color, QgsProperty.fromExpression("'#bb00cc'")) + assert self.checkRenderPoint(format, 'text_dd_color_point', None, text=['test'], point=QPointF(50, 200)) + + def testDrawTextDataDefinedColorRect(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(60) + format.setSizeUnit(QgsUnitTypes.RenderPoints) + format.setColor(QColor(0, 255, 0)) + format.dataDefinedProperties().setProperty(QgsPalLayerSettings.Color, QgsProperty.fromExpression("'#bb00cc'")) + assert self.checkRender(format, 'text_dd_color_rect', None, text=['test'], alignment=QgsTextRenderer.AlignCenter, rect=QRectF(100, 100, 100, 100)) + + def testDrawTextDataDefinedBufferColorPoint(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(60) + format.setSizeUnit(QgsUnitTypes.RenderPoints) + format.setColor(QColor(0, 255, 0)) + format.dataDefinedProperties().setProperty(QgsPalLayerSettings.BufferColor, QgsProperty.fromExpression("'#bb00cc'")) + format.buffer().setEnabled(True) + format.buffer().setSize(5) + assert self.checkRenderPoint(format, 'text_dd_buffer_color', None, text=['test'], point=QPointF(50, 200)) + def testTextRenderFormat(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) diff --git a/tests/testdata/control_images/text_renderer/text_dd_buffer_color/text_dd_buffer_color.png b/tests/testdata/control_images/text_renderer/text_dd_buffer_color/text_dd_buffer_color.png new file mode 100644 index 00000000000..b7ab77d35cf Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_dd_buffer_color/text_dd_buffer_color.png differ diff --git a/tests/testdata/control_images/text_renderer/text_dd_color_point/text_dd_color_point.png b/tests/testdata/control_images/text_renderer/text_dd_color_point/text_dd_color_point.png new file mode 100644 index 00000000000..d41a4faa8e5 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_dd_color_point/text_dd_color_point.png differ diff --git a/tests/testdata/control_images/text_renderer/text_dd_color_rect/text_dd_color_rect.png b/tests/testdata/control_images/text_renderer/text_dd_color_rect/text_dd_color_rect.png new file mode 100644 index 00000000000..3797e7231fb Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_dd_color_rect/text_dd_color_rect.png differ