1 /***************************************************************************
2   qgstextbuffersettings.cpp
3   -----------------
4    begin                : May 2020
5    copyright            : (C) Nyall Dawson
6    email                : nyall dot dawson at gmail dot com
7 
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgstextbuffersettings.h"
18 #include "qgstextrenderer_p.h"
19 #include "qgsvectorlayer.h"
20 #include "qgspallabeling.h"
21 #include "qgssymbollayerutils.h"
22 #include "qgspainting.h"
23 #include "qgspainteffectregistry.h"
24 #include "qgstextrendererutils.h"
25 
QgsTextBufferSettings()26 QgsTextBufferSettings::QgsTextBufferSettings()
27 {
28   d = new QgsTextBufferSettingsPrivate();
29 }
30 
QgsTextBufferSettings(const QgsTextBufferSettings & other)31 QgsTextBufferSettings::QgsTextBufferSettings( const QgsTextBufferSettings &other ) //NOLINT
32   : d( other.d )
33 {
34 }
35 
operator =(const QgsTextBufferSettings & other)36 QgsTextBufferSettings &QgsTextBufferSettings::operator=( const QgsTextBufferSettings &other )  //NOLINT
37 {
38   d = other.d;
39   return *this;
40 }
41 
~QgsTextBufferSettings()42 QgsTextBufferSettings::~QgsTextBufferSettings() //NOLINT
43 {
44 
45 }
46 
operator ==(const QgsTextBufferSettings & other) const47 bool QgsTextBufferSettings::operator==( const QgsTextBufferSettings &other ) const
48 {
49   if ( d->enabled != other.enabled()
50        || d->size != other.size()
51        || d->sizeUnit != other.sizeUnit()
52        || d->sizeMapUnitScale != other.sizeMapUnitScale()
53        || d->color != other.color()
54        || d->opacity != other.opacity()
55        || d->fillBufferInterior != other.fillBufferInterior()
56        || d->joinStyle != other.joinStyle()
57        || d->blendMode != other.blendMode() )
58     return false;
59 
60   if ( static_cast< bool >( d->paintEffect ) != static_cast< bool >( other.paintEffect() )
61        || ( d->paintEffect && d->paintEffect->properties() != other.paintEffect()->properties() ) )
62     return false;
63 
64   return true;
65 }
66 
operator !=(const QgsTextBufferSettings & other) const67 bool QgsTextBufferSettings::operator!=( const QgsTextBufferSettings &other ) const
68 {
69   return !( *this == other );
70 }
71 
enabled() const72 bool QgsTextBufferSettings::enabled() const
73 {
74   return d->enabled;
75 }
76 
setEnabled(bool enabled)77 void QgsTextBufferSettings::setEnabled( bool enabled )
78 {
79   d->enabled = enabled;
80 }
81 
size() const82 double QgsTextBufferSettings::size() const
83 {
84   return d->size;
85 }
86 
setSize(double size)87 void QgsTextBufferSettings::setSize( double size )
88 {
89   d->size = size;
90 }
91 
sizeUnit() const92 QgsUnitTypes::RenderUnit QgsTextBufferSettings::sizeUnit() const
93 {
94   return d->sizeUnit;
95 }
96 
setSizeUnit(QgsUnitTypes::RenderUnit unit)97 void QgsTextBufferSettings::setSizeUnit( QgsUnitTypes::RenderUnit unit )
98 {
99   d->sizeUnit = unit;
100 }
101 
sizeMapUnitScale() const102 QgsMapUnitScale QgsTextBufferSettings::sizeMapUnitScale() const
103 {
104   return d->sizeMapUnitScale;
105 }
106 
setSizeMapUnitScale(const QgsMapUnitScale & scale)107 void QgsTextBufferSettings::setSizeMapUnitScale( const QgsMapUnitScale &scale )
108 {
109   d->sizeMapUnitScale = scale;
110 }
111 
color() const112 QColor QgsTextBufferSettings::color() const
113 {
114   return d->color;
115 }
116 
setColor(const QColor & color)117 void QgsTextBufferSettings::setColor( const QColor &color )
118 {
119   d->color = color;
120 }
121 
fillBufferInterior() const122 bool QgsTextBufferSettings::fillBufferInterior() const
123 {
124   return d->fillBufferInterior;
125 }
126 
setFillBufferInterior(bool fill)127 void QgsTextBufferSettings::setFillBufferInterior( bool fill )
128 {
129   d->fillBufferInterior = fill;
130 }
131 
opacity() const132 double QgsTextBufferSettings::opacity() const
133 {
134   return d->opacity;
135 }
136 
setOpacity(double opacity)137 void QgsTextBufferSettings::setOpacity( double opacity )
138 {
139   d->opacity = opacity;
140 }
141 
joinStyle() const142 Qt::PenJoinStyle QgsTextBufferSettings::joinStyle() const
143 {
144   return d->joinStyle;
145 }
146 
setJoinStyle(Qt::PenJoinStyle style)147 void QgsTextBufferSettings::setJoinStyle( Qt::PenJoinStyle style )
148 {
149   d->joinStyle = style;
150 }
151 
blendMode() const152 QPainter::CompositionMode QgsTextBufferSettings::blendMode() const
153 {
154   return d->blendMode;
155 }
156 
setBlendMode(QPainter::CompositionMode mode)157 void QgsTextBufferSettings::setBlendMode( QPainter::CompositionMode mode )
158 {
159   d->blendMode = mode;
160 }
161 
paintEffect() const162 const QgsPaintEffect *QgsTextBufferSettings::paintEffect() const
163 {
164   return d->paintEffect.get();
165 }
166 
setPaintEffect(QgsPaintEffect * effect)167 void QgsTextBufferSettings::setPaintEffect( QgsPaintEffect *effect )
168 {
169   d->paintEffect.reset( effect );
170 }
171 
updateDataDefinedProperties(QgsRenderContext & context,const QgsPropertyCollection & properties)172 void QgsTextBufferSettings::updateDataDefinedProperties( QgsRenderContext &context, const QgsPropertyCollection &properties )
173 {
174   if ( properties.isActive( QgsPalLayerSettings::BufferDraw ) )
175   {
176     context.expressionContext().setOriginalValueVariable( d->enabled );
177     d->enabled = properties.valueAsBool( QgsPalLayerSettings::BufferDraw, context.expressionContext(), d->enabled );
178   }
179 
180   if ( properties.isActive( QgsPalLayerSettings::BufferSize ) )
181   {
182     context.expressionContext().setOriginalValueVariable( d->size );
183     d->size = properties.valueAsDouble( QgsPalLayerSettings::BufferSize, context.expressionContext(), d->size );
184   }
185 
186   QVariant exprVal = properties.value( QgsPalLayerSettings::BufferUnit, context.expressionContext() );
187   if ( !exprVal.isNull() )
188   {
189     const QString units = exprVal.toString();
190     if ( !units.isEmpty() )
191     {
192       bool ok;
193       const QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok );
194       if ( ok )
195         d->sizeUnit = res;
196     }
197   }
198 
199   if ( properties.isActive( QgsPalLayerSettings::BufferOpacity ) )
200   {
201     context.expressionContext().setOriginalValueVariable( d->opacity * 100 );
202     const QVariant val = properties.value( QgsPalLayerSettings::BufferOpacity, context.expressionContext(), d->opacity * 100 );
203     if ( !val.isNull() )
204     {
205       d->opacity = val.toDouble() / 100.0;
206     }
207   }
208 
209   if ( properties.isActive( QgsPalLayerSettings::BufferColor ) )
210   {
211     context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( d->color ) );
212     d->color = properties.valueAsColor( QgsPalLayerSettings::BufferColor, context.expressionContext(), d->color );
213   }
214 
215   if ( properties.isActive( QgsPalLayerSettings::BufferBlendMode ) )
216   {
217     exprVal = properties.value( QgsPalLayerSettings::BufferBlendMode, context.expressionContext() );
218     const QString blendstr = exprVal.toString().trimmed();
219     if ( !blendstr.isEmpty() )
220       d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
221   }
222 
223   if ( properties.isActive( QgsPalLayerSettings::BufferJoinStyle ) )
224   {
225     exprVal = properties.value( QgsPalLayerSettings::BufferJoinStyle, context.expressionContext() );
226     const QString joinstr = exprVal.toString().trimmed();
227     if ( !joinstr.isEmpty() )
228     {
229       d->joinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( joinstr );
230     }
231   }
232 }
233 
referencedFields(const QgsRenderContext &) const234 QSet<QString> QgsTextBufferSettings::referencedFields( const QgsRenderContext & ) const
235 {
236   return QSet< QString >(); // nothing for now
237 }
238 
readFromLayer(QgsVectorLayer * layer)239 void QgsTextBufferSettings::readFromLayer( QgsVectorLayer *layer )
240 {
241   // text buffer
242   const double bufSize = layer->customProperty( QStringLiteral( "labeling/bufferSize" ), QVariant( 0.0 ) ).toDouble();
243 
244   // fix for buffer being keyed off of just its size in the past (<2.0)
245   const QVariant drawBuffer = layer->customProperty( QStringLiteral( "labeling/bufferDraw" ), QVariant() );
246   if ( drawBuffer.isValid() )
247   {
248     d->enabled = drawBuffer.toBool();
249     d->size = bufSize;
250   }
251   else if ( bufSize != 0.0 )
252   {
253     d->enabled = true;
254     d->size = bufSize;
255   }
256   else
257   {
258     // keep bufferSize at new 1.0 default
259     d->enabled = false;
260   }
261 
262   if ( layer->customProperty( QStringLiteral( "labeling/bufferSizeUnits" ) ).toString().isEmpty() )
263   {
264     const bool bufferSizeInMapUnits = layer->customProperty( QStringLiteral( "labeling/bufferSizeInMapUnits" ) ).toBool();
265     d->sizeUnit = bufferSizeInMapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
266   }
267   else
268   {
269     d->sizeUnit = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/bufferSizeUnits" ) ).toString() );
270   }
271 
272   if ( layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitScale" ) ).toString().isEmpty() )
273   {
274     //fallback to older property
275     const double oldMin = layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitMinScale" ), 0.0 ).toDouble();
276     d->sizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
277     const double oldMax = layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitMaxScale" ), 0.0 ).toDouble();
278     d->sizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
279   }
280   else
281   {
282     d->sizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitScale" ) ).toString() );
283   }
284   d->color = QgsTextRendererUtils::readColor( layer, QStringLiteral( "labeling/bufferColor" ), Qt::white, false );
285   if ( layer->customProperty( QStringLiteral( "labeling/bufferOpacity" ) ).toString().isEmpty() )
286   {
287     d->opacity = ( 1 - layer->customProperty( QStringLiteral( "labeling/bufferTransp" ) ).toInt() / 100.0 ); //0 -100
288   }
289   else
290   {
291     d->opacity = ( layer->customProperty( QStringLiteral( "labeling/bufferOpacity" ) ).toDouble() );
292   }
293   d->blendMode = QgsPainting::getCompositionMode(
294                    static_cast< QgsPainting::BlendMode >( layer->customProperty( QStringLiteral( "labeling/bufferBlendMode" ), QVariant( QgsPainting::BlendNormal ) ).toUInt() ) );
295   d->joinStyle = static_cast< Qt::PenJoinStyle >( layer->customProperty( QStringLiteral( "labeling/bufferJoinStyle" ), QVariant( Qt::RoundJoin ) ).toUInt() );
296 
297   d->fillBufferInterior = !layer->customProperty( QStringLiteral( "labeling/bufferNoFill" ), QVariant( false ) ).toBool();
298 
299   if ( layer->customProperty( QStringLiteral( "labeling/bufferEffect" ) ).isValid() )
300   {
301     QDomDocument doc( QStringLiteral( "effect" ) );
302     doc.setContent( layer->customProperty( QStringLiteral( "labeling/bufferEffect" ) ).toString() );
303     const QDomElement effectElem = doc.firstChildElement( QStringLiteral( "effect" ) ).firstChildElement( QStringLiteral( "effect" ) );
304     setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
305   }
306   else
307     setPaintEffect( nullptr );
308 }
309 
readXml(const QDomElement & elem)310 void QgsTextBufferSettings::readXml( const QDomElement &elem )
311 {
312   const QDomElement textBufferElem = elem.firstChildElement( QStringLiteral( "text-buffer" ) );
313   const double bufSize = textBufferElem.attribute( QStringLiteral( "bufferSize" ), QStringLiteral( "0" ) ).toDouble();
314 
315   // fix for buffer being keyed off of just its size in the past (<2.0)
316   const QVariant drawBuffer = textBufferElem.attribute( QStringLiteral( "bufferDraw" ) );
317   if ( drawBuffer.isValid() )
318   {
319     d->enabled = drawBuffer.toBool();
320     d->size = bufSize;
321   }
322   else if ( bufSize != 0.0 )
323   {
324     d->enabled = true;
325     d->size = bufSize;
326   }
327   else
328   {
329     // keep bufferSize at new 1.0 default
330     d->enabled = false;
331   }
332 
333   if ( !textBufferElem.hasAttribute( QStringLiteral( "bufferSizeUnits" ) ) )
334   {
335     const bool bufferSizeInMapUnits = textBufferElem.attribute( QStringLiteral( "bufferSizeInMapUnits" ) ).toInt();
336     d->sizeUnit = bufferSizeInMapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
337   }
338   else
339   {
340     d->sizeUnit = QgsUnitTypes::decodeRenderUnit( textBufferElem.attribute( QStringLiteral( "bufferSizeUnits" ) ) );
341   }
342 
343   if ( !textBufferElem.hasAttribute( QStringLiteral( "bufferSizeMapUnitScale" ) ) )
344   {
345     //fallback to older property
346     const double oldMin = textBufferElem.attribute( QStringLiteral( "bufferSizeMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
347     d->sizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
348     const double oldMax = textBufferElem.attribute( QStringLiteral( "bufferSizeMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
349     d->sizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
350   }
351   else
352   {
353     d->sizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( textBufferElem.attribute( QStringLiteral( "bufferSizeMapUnitScale" ) ) );
354   }
355   d->color = QgsSymbolLayerUtils::decodeColor( textBufferElem.attribute( QStringLiteral( "bufferColor" ), QgsSymbolLayerUtils::encodeColor( Qt::white ) ) );
356 
357   if ( !textBufferElem.hasAttribute( QStringLiteral( "bufferOpacity" ) ) )
358   {
359     d->opacity = ( 1 - textBufferElem.attribute( QStringLiteral( "bufferTransp" ) ).toInt() / 100.0 ); //0 -100
360   }
361   else
362   {
363     d->opacity = ( textBufferElem.attribute( QStringLiteral( "bufferOpacity" ) ).toDouble() );
364   }
365 
366   d->blendMode = QgsPainting::getCompositionMode(
367                    static_cast< QgsPainting::BlendMode >( textBufferElem.attribute( QStringLiteral( "bufferBlendMode" ), QString::number( QgsPainting::BlendNormal ) ).toUInt() ) );
368   d->joinStyle = static_cast< Qt::PenJoinStyle >( textBufferElem.attribute( QStringLiteral( "bufferJoinStyle" ), QString::number( Qt::RoundJoin ) ).toUInt() );
369   d->fillBufferInterior = !textBufferElem.attribute( QStringLiteral( "bufferNoFill" ), QStringLiteral( "0" ) ).toInt();
370   const QDomElement effectElem = textBufferElem.firstChildElement( QStringLiteral( "effect" ) );
371   if ( !effectElem.isNull() )
372     setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
373   else
374     setPaintEffect( nullptr );
375 }
376 
writeXml(QDomDocument & doc) const377 QDomElement QgsTextBufferSettings::writeXml( QDomDocument &doc ) const
378 {
379   // text buffer
380   QDomElement textBufferElem = doc.createElement( QStringLiteral( "text-buffer" ) );
381   textBufferElem.setAttribute( QStringLiteral( "bufferDraw" ), d->enabled );
382   textBufferElem.setAttribute( QStringLiteral( "bufferSize" ), d->size );
383   textBufferElem.setAttribute( QStringLiteral( "bufferSizeUnits" ), QgsUnitTypes::encodeUnit( d->sizeUnit ) );
384   textBufferElem.setAttribute( QStringLiteral( "bufferSizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->sizeMapUnitScale ) );
385   textBufferElem.setAttribute( QStringLiteral( "bufferColor" ), QgsSymbolLayerUtils::encodeColor( d->color ) );
386   textBufferElem.setAttribute( QStringLiteral( "bufferNoFill" ), !d->fillBufferInterior );
387   textBufferElem.setAttribute( QStringLiteral( "bufferOpacity" ), d->opacity );
388   textBufferElem.setAttribute( QStringLiteral( "bufferJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
389   textBufferElem.setAttribute( QStringLiteral( "bufferBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
390   if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
391     d->paintEffect->saveProperties( doc, textBufferElem );
392   return textBufferElem;
393 }
394