1 /***************************************************************************
2     qgsvectorlayerlabeling.cpp
3     ---------------------
4     begin                : September 2015
5     copyright            : (C) 2015 by Martin Dobias
6     email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include "qgsvectorlayerlabeling.h"
16 
17 #include "qgspallabeling.h"
18 #include "qgsrulebasedlabeling.h"
19 #include "qgsvectorlayer.h"
20 #include "qgssymbollayerutils.h"
21 #include "qgssymbollayer.h"
22 #include "qgsmarkersymbollayer.h"
23 #include "qgis.h"
24 #include "qgsstyleentityvisitor.h"
25 
26 
create(const QDomElement & element,const QgsReadWriteContext & context)27 QgsAbstractVectorLayerLabeling *QgsAbstractVectorLayerLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
28 {
29   QString type = element.attribute( QStringLiteral( "type" ) );
30   if ( type == QLatin1String( "rule-based" ) )
31   {
32     return QgsRuleBasedLabeling::create( element, context );
33   }
34   else if ( type == QLatin1String( "simple" ) )
35   {
36     return QgsVectorLayerSimpleLabeling::create( element, context );
37   }
38   else
39   {
40     return nullptr;
41   }
42 }
43 
accept(QgsStyleEntityVisitorInterface *) const44 bool QgsAbstractVectorLayerLabeling::accept( QgsStyleEntityVisitorInterface * ) const
45 {
46   return true;
47 }
48 
provider(QgsVectorLayer * layer) const49 QgsVectorLayerLabelProvider *QgsVectorLayerSimpleLabeling::provider( QgsVectorLayer *layer ) const
50 {
51   return new QgsVectorLayerLabelProvider( layer, QString(), false, mSettings.get() );
52 }
53 
QgsVectorLayerSimpleLabeling(const QgsPalLayerSettings & settings)54 QgsVectorLayerSimpleLabeling::QgsVectorLayerSimpleLabeling( const QgsPalLayerSettings &settings )
55   : mSettings( new QgsPalLayerSettings( settings ) )
56 {
57 
58 }
59 
type() const60 QString QgsVectorLayerSimpleLabeling::type() const
61 {
62   return QStringLiteral( "simple" );
63 }
64 
clone() const65 QgsAbstractVectorLayerLabeling *QgsVectorLayerSimpleLabeling::clone() const
66 {
67   return new QgsVectorLayerSimpleLabeling( *mSettings );
68 }
69 
save(QDomDocument & doc,const QgsReadWriteContext & context) const70 QDomElement QgsVectorLayerSimpleLabeling::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
71 {
72   QDomElement elem = doc.createElement( QStringLiteral( "labeling" ) );
73   elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "simple" ) );
74   elem.appendChild( mSettings->writeXml( doc, context ) );
75   return elem;
76 }
77 
settings(const QString & providerId) const78 QgsPalLayerSettings QgsVectorLayerSimpleLabeling::settings( const QString &providerId ) const
79 {
80   Q_UNUSED( providerId )
81   return *mSettings;
82 }
83 
accept(QgsStyleEntityVisitorInterface * visitor) const84 bool QgsVectorLayerSimpleLabeling::accept( QgsStyleEntityVisitorInterface *visitor ) const
85 {
86   if ( mSettings )
87   {
88     QgsStyleLabelSettingsEntity entity( *mSettings );
89     if ( !visitor->visit( &entity ) )
90       return false;
91   }
92   return true;
93 }
94 
requiresAdvancedEffects() const95 bool QgsVectorLayerSimpleLabeling::requiresAdvancedEffects() const
96 {
97   return mSettings->format().containsAdvancedEffects();
98 }
99 
create(const QDomElement & element,const QgsReadWriteContext & context)100 QgsVectorLayerSimpleLabeling *QgsVectorLayerSimpleLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
101 {
102   QDomElement settingsElem = element.firstChildElement( QStringLiteral( "settings" ) );
103   if ( !settingsElem.isNull() )
104   {
105     QgsPalLayerSettings settings;
106     settings.readXml( settingsElem, context );
107     return new QgsVectorLayerSimpleLabeling( settings );
108   }
109 
110   return new QgsVectorLayerSimpleLabeling( QgsPalLayerSettings() );
111 }
112 
quadOffsetToSldAnchor(QgsPalLayerSettings::QuadrantPosition quadrantPosition)113 QPointF quadOffsetToSldAnchor( QgsPalLayerSettings::QuadrantPosition quadrantPosition )
114 {
115   double quadOffsetX = 0.5, quadOffsetY = 0.5;
116 
117   // adjust quadrant offset of labels
118   switch ( quadrantPosition )
119   {
120     case QgsPalLayerSettings::QuadrantAboveLeft:
121       quadOffsetX = 1;
122       quadOffsetY = 0;
123       break;
124     case QgsPalLayerSettings::QuadrantAbove:
125       quadOffsetX = 0.5;
126       quadOffsetY = 0;
127       break;
128     case QgsPalLayerSettings::QuadrantAboveRight:
129       quadOffsetX = 0;
130       quadOffsetY = 0;
131       break;
132     case QgsPalLayerSettings::QuadrantLeft:
133       quadOffsetX = 1;
134       quadOffsetY = 0.5;
135       break;
136     case QgsPalLayerSettings::QuadrantRight:
137       quadOffsetX = 0;
138       quadOffsetY = 0.5;
139       break;
140     case QgsPalLayerSettings::QuadrantBelowLeft:
141       quadOffsetX = 1;
142       quadOffsetY = 1;
143       break;
144     case QgsPalLayerSettings::QuadrantBelow:
145       quadOffsetX = 0.5;
146       quadOffsetY = 1;
147       break;
148     case QgsPalLayerSettings::QuadrantBelowRight:
149       quadOffsetX = 0;
150       quadOffsetY = 1.0;
151       break;
152     case QgsPalLayerSettings::QuadrantOver:
153       break;
154   }
155 
156   return QPointF( quadOffsetX, quadOffsetY );
157 }
158 
159 /*
160  * This is not a generic function encoder, just enough to encode the label case control functions
161  */
appendSimpleFunction(QDomDocument & doc,QDomElement & parent,const QString & name,const QString & attribute)162 void appendSimpleFunction( QDomDocument &doc, QDomElement &parent, const QString &name, const QString &attribute )
163 {
164   QDomElement function = doc.createElement( QStringLiteral( "ogc:Function" ) );
165   function.setAttribute( QStringLiteral( "name" ), name );
166   parent.appendChild( function );
167   QDomElement property = doc.createElement( QStringLiteral( "ogc:PropertyName" ) );
168   property.appendChild( doc.createTextNode( attribute ) );
169   function.appendChild( property );
170 }
171 
backgroundToMarkerLayer(const QgsTextBackgroundSettings & settings)172 std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBackgroundSettings &settings )
173 {
174   std::unique_ptr<QgsMarkerSymbolLayer> layer;
175   switch ( settings.type() )
176   {
177     case QgsTextBackgroundSettings::ShapeSVG:
178     {
179       QgsSvgMarkerSymbolLayer *svg = new QgsSvgMarkerSymbolLayer( settings.svgFile() );
180       svg->setStrokeWidth( settings.strokeWidth() );
181       svg->setStrokeWidthUnit( settings.strokeWidthUnit() );
182       layer.reset( svg );
183       break;
184     }
185     case QgsTextBackgroundSettings::ShapeMarkerSymbol:
186     {
187       // just grab the first layer and hope for the best
188       if ( settings.markerSymbol() && settings.markerSymbol()->symbolLayerCount() > 0 )
189       {
190         layer.reset( static_cast< QgsMarkerSymbolLayer * >( settings.markerSymbol()->symbolLayer( 0 )->clone() ) );
191         break;
192       }
193       FALLTHROUGH // not set, just go with the default
194     }
195     case QgsTextBackgroundSettings::ShapeCircle:
196     case QgsTextBackgroundSettings::ShapeEllipse:
197     case QgsTextBackgroundSettings::ShapeRectangle:
198     case QgsTextBackgroundSettings::ShapeSquare:
199     {
200       QgsSimpleMarkerSymbolLayer *marker = new QgsSimpleMarkerSymbolLayer();
201       // default value
202       QgsSimpleMarkerSymbolLayerBase::Shape shape = QgsSimpleMarkerSymbolLayerBase::Diamond;
203       switch ( settings.type() )
204       {
205         case QgsTextBackgroundSettings::ShapeCircle:
206         case QgsTextBackgroundSettings::ShapeEllipse:
207           shape = QgsSimpleMarkerSymbolLayerBase::Circle;
208           break;
209         case QgsTextBackgroundSettings::ShapeRectangle:
210         case QgsTextBackgroundSettings::ShapeSquare:
211           shape = QgsSimpleMarkerSymbolLayerBase::Square;
212           break;
213         case QgsTextBackgroundSettings::ShapeSVG:
214         case QgsTextBackgroundSettings::ShapeMarkerSymbol:
215           break;
216       }
217 
218       marker->setShape( shape );
219       marker->setStrokeWidth( settings.strokeWidth() );
220       marker->setStrokeWidthUnit( settings.strokeWidthUnit() );
221       layer.reset( marker );
222     }
223   }
224   layer->setEnabled( true );
225   // a marker does not have a size x and y, just a size (and it should be at least one)
226   QSizeF size = settings.size();
227   layer->setSize( std::max( 1., std::max( size.width(), size.height() ) ) );
228   layer->setSizeUnit( settings.sizeUnit() );
229   // fill and stroke
230   QColor fillColor = settings.fillColor();
231   QColor strokeColor = settings.strokeColor();
232   if ( settings.opacity() < 1 )
233   {
234     int alpha = std::round( settings.opacity() * 255 );
235     fillColor.setAlpha( alpha );
236     strokeColor.setAlpha( alpha );
237   }
238   layer->setFillColor( fillColor );
239   layer->setStrokeColor( strokeColor );
240   // rotation
241   if ( settings.rotationType() == QgsTextBackgroundSettings::RotationFixed )
242   {
243     layer->setAngle( settings.rotation() );
244   }
245   // offset
246   layer->setOffset( settings.offset() );
247   layer->setOffsetUnit( settings.offsetUnit() );
248 
249   return layer;
250 }
251 
writeTextSymbolizer(QDomNode & parent,QgsPalLayerSettings & settings,const QgsStringMap & props) const252 void QgsAbstractVectorLayerLabeling::writeTextSymbolizer( QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props ) const
253 {
254   QDomDocument doc = parent.ownerDocument();
255 
256   // text symbolizer
257   QDomElement textSymbolizerElement = doc.createElement( QStringLiteral( "se:TextSymbolizer" ) );
258   parent.appendChild( textSymbolizerElement );
259 
260   // label
261   QgsTextFormat format = settings.format();
262   QFont font = format.font();
263   QDomElement labelElement = doc.createElement( QStringLiteral( "se:Label" ) );
264   textSymbolizerElement.appendChild( labelElement );
265   if ( settings.isExpression )
266   {
267     labelElement.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( settings.getLabelExpression()->dump() ) ) );
268     labelElement.appendChild( doc.createTextNode( "Placeholder" ) );
269   }
270   else
271   {
272     QgsStringUtils::Capitalization capitalization = format.capitalization();
273     if ( capitalization == QgsStringUtils::MixedCase && font.capitalization() != QFont::MixedCase )
274       capitalization = static_cast< QgsStringUtils::Capitalization >( font.capitalization() );
275     if ( capitalization == QgsStringUtils::AllUppercase )
276     {
277       appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), settings.fieldName );
278     }
279     else if ( capitalization == QgsStringUtils::AllLowercase )
280     {
281       appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), settings.fieldName );
282     }
283     else if ( capitalization == QgsStringUtils::ForceFirstLetterToCapital )
284     {
285       appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), settings.fieldName );
286     }
287     else
288     {
289       QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) );
290       propertyNameElement.appendChild( doc.createTextNode( settings.fieldName ) );
291       labelElement.appendChild( propertyNameElement );
292     }
293   }
294 
295   // font
296   QDomElement fontElement = doc.createElement( QStringLiteral( "se:Font" ) );
297   textSymbolizerElement.appendChild( fontElement );
298   fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), font.family() ) );
299   double fontSize = QgsSymbolLayerUtils::rescaleUom( format.size(), format.sizeUnit(), props );
300   fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( fontSize ) ) );
301   if ( format.font().italic() )
302   {
303     fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-style" ), QStringLiteral( "italic" ) ) );
304   }
305   if ( format.font().bold() )
306   {
307     fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-weight" ), QStringLiteral( "bold" ) ) );
308   }
309 
310   // label placement
311   QDomElement labelPlacement = doc.createElement( QStringLiteral( "se:LabelPlacement" ) );
312   textSymbolizerElement.appendChild( labelPlacement );
313   double maxDisplacement = 0;
314   double repeatDistance = 0;
315   switch ( settings.placement )
316   {
317     case QgsPalLayerSettings::OverPoint:
318     {
319       QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
320       labelPlacement.appendChild( pointPlacement );
321       // anchor point
322       QPointF anchor = quadOffsetToSldAnchor( settings.quadOffset );
323       QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, anchor );
324       // displacement
325       if ( settings.xOffset > 0 || settings.yOffset > 0 )
326       {
327         QgsUnitTypes::RenderUnit offsetUnit =  settings.offsetUnits;
328         double dx = QgsSymbolLayerUtils::rescaleUom( settings.xOffset, offsetUnit, props );
329         double dy = QgsSymbolLayerUtils::rescaleUom( settings.yOffset, offsetUnit, props );
330         QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( dx, dy ) );
331       }
332       // rotation
333       if ( settings.angleOffset != 0 )
334       {
335         QDomElement rotation = doc.createElement( "se:Rotation" );
336         pointPlacement.appendChild( rotation );
337         rotation.appendChild( doc.createTextNode( QString::number( settings.angleOffset ) ) );
338       }
339     }
340     break;
341     case QgsPalLayerSettings::AroundPoint:
342     case QgsPalLayerSettings::OrderedPositionsAroundPoint:
343     {
344       QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
345       labelPlacement.appendChild( pointPlacement );
346 
347       // SLD cannot do either, but let's do a best effort setting the distance using
348       // anchor point and displacement
349       QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0, 0.5 ) );
350       QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
351       double radius = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
352       double offset = std::sqrt( radius * radius / 2 ); // make it start top/right
353       maxDisplacement = radius + 1; // lock the distance
354       QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( offset, offset ) );
355     }
356     break;
357     case QgsPalLayerSettings::Horizontal:
358     case QgsPalLayerSettings::Free:
359     case QgsPalLayerSettings::OutsidePolygons:
360     {
361       // still a point placement (for "free" it's a fallback, there is no SLD equivalent)
362       QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
363       labelPlacement.appendChild( pointPlacement );
364       QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0.5, 0.5 ) );
365       QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
366       double dist = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
367       QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( 0, dist ) );
368       break;
369     }
370     case QgsPalLayerSettings::Line:
371     case QgsPalLayerSettings::Curved:
372     case QgsPalLayerSettings::PerimeterCurved:
373     {
374       QDomElement linePlacement = doc.createElement( "se:LinePlacement" );
375       labelPlacement.appendChild( linePlacement );
376 
377       // perpendicular distance if required
378       if ( settings.dist > 0 )
379       {
380         QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
381         double dist = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
382         QDomElement perpendicular = doc.createElement( "se:PerpendicularOffset" );
383         linePlacement.appendChild( perpendicular );
384         perpendicular.appendChild( doc.createTextNode( qgsDoubleToString( dist, 2 ) ) );
385       }
386 
387       // repeat distance if required
388       if ( settings.repeatDistance > 0 )
389       {
390         QDomElement repeat = doc.createElement( "se:Repeat" );
391         linePlacement.appendChild( repeat );
392         repeat.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
393         QDomElement gap = doc.createElement( "se:Gap" );
394         linePlacement.appendChild( gap );
395         repeatDistance = QgsSymbolLayerUtils::rescaleUom( settings.repeatDistance, settings.repeatDistanceUnit, props );
396         gap.appendChild( doc.createTextNode( qgsDoubleToString( repeatDistance, 2 ) ) );
397       }
398 
399       // always generalized
400       QDomElement generalize = doc.createElement( "se:GeneralizeLine" );
401       linePlacement.appendChild( generalize );
402       generalize.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
403     }
404     break;
405   }
406 
407   // halo
408   QgsTextBufferSettings buffer = format.buffer();
409   if ( buffer.enabled() )
410   {
411     QDomElement haloElement = doc.createElement( QStringLiteral( "se:Halo" ) );
412     textSymbolizerElement.appendChild( haloElement );
413 
414     QDomElement radiusElement = doc.createElement( QStringLiteral( "se:Radius" ) );
415     haloElement.appendChild( radiusElement );
416     // the SLD uses a radius, which is actually half of the link thickness the buffer size specifies
417     double radius = QgsSymbolLayerUtils::rescaleUom( buffer.size(), buffer.sizeUnit(), props ) / 2;
418     radiusElement.appendChild( doc.createTextNode( qgsDoubleToString( radius ) ) );
419 
420     QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
421     haloElement.appendChild( fillElement );
422     fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), buffer.color().name() ) );
423     if ( buffer.opacity() != 1 )
424     {
425       fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( buffer.opacity() ) ) );
426     }
427   }
428 
429   // fill
430   QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
431   textSymbolizerElement.appendChild( fillElement );
432   fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), format.color().name() ) );
433   if ( format.opacity() != 1 )
434   {
435     fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( format.opacity() ) ) );
436   }
437 
438   // background graphic (not supported by SE 1.1, but supported by the GeoTools ecosystem as an extension)
439   QgsTextBackgroundSettings background = format.background();
440   if ( background.enabled() )
441   {
442     std::unique_ptr<QgsMarkerSymbolLayer> layer = backgroundToMarkerLayer( background );
443     layer->writeSldMarker( doc, textSymbolizerElement, props );
444   }
445 
446   // priority and zIndex, the default values are 0 and 5 in qgis (and between 0 and 10),
447   // in the GeoTools ecosystem there is a single priority value set at 1000 by default
448   if ( settings.priority != 5 || settings.zIndex > 0 )
449   {
450     QDomElement priorityElement = doc.createElement( QStringLiteral( "se:Priority" ) );
451     textSymbolizerElement.appendChild( priorityElement );
452     int priority = 500 + 1000 * settings.zIndex + ( settings.priority - 5 ) * 100;
453     if ( settings.priority == 0 && settings.zIndex > 0 )
454     {
455       // small adjustment to make sure labels in z index n+1 are all above level n despite the priority value
456       priority += 1;
457     }
458     priorityElement.appendChild( doc.createTextNode( QString::number( priority ) ) );
459   }
460 
461   // vendor options for text appearance
462   if ( font.underline() )
463   {
464     QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "underlineText" ), QStringLiteral( "true" ) );
465     textSymbolizerElement.appendChild( vo );
466   }
467   if ( font.strikeOut() )
468   {
469     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "strikethroughText" ), QStringLiteral( "true" ) );
470     textSymbolizerElement.appendChild( vo );
471   }
472   // vendor options for text positioning
473   if ( maxDisplacement > 0 )
474   {
475     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxDisplacement" ), qgsDoubleToString( maxDisplacement, 2 ) );
476     textSymbolizerElement.appendChild( vo );
477   }
478   if ( settings.placement == QgsPalLayerSettings::Curved || settings.placement == QgsPalLayerSettings::PerimeterCurved )
479   {
480     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "followLine" ), QStringLiteral( "true" ) );
481     textSymbolizerElement.appendChild( vo );
482     if ( settings.maxCurvedCharAngleIn > 0 || settings.maxCurvedCharAngleOut > 0 )
483     {
484       // SLD has no notion for this, the GeoTools ecosystem can only do a single angle
485       double angle = std::min( std::fabs( settings.maxCurvedCharAngleIn ), std::fabs( settings.maxCurvedCharAngleOut ) );
486       QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxAngleDelta" ), qgsDoubleToString( angle ) );
487       textSymbolizerElement.appendChild( vo );
488     }
489   }
490   if ( repeatDistance > 0 )
491   {
492     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "repeat" ), qgsDoubleToString( repeatDistance, 2 ) );
493     textSymbolizerElement.appendChild( vo );
494   }
495   // miscellaneous options
496   if ( settings.displayAll )
497   {
498     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "conflictResolution" ), QStringLiteral( "false" ) );
499     textSymbolizerElement.appendChild( vo );
500   }
501   if ( settings.upsidedownLabels == QgsPalLayerSettings::ShowAll )
502   {
503     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "forceLeftToRight" ), QStringLiteral( "false" ) );
504     textSymbolizerElement.appendChild( vo );
505   }
506   if ( settings.lineSettings().mergeLines() )
507   {
508     QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "group" ), QStringLiteral( "yes" ) );
509     textSymbolizerElement.appendChild( vo );
510     if ( settings.labelPerPart )
511     {
512       QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "labelAllGroup" ), QStringLiteral( "true" ) );
513       textSymbolizerElement.appendChild( vo );
514     }
515   }
516   // background symbol resize handling
517   if ( background.enabled() )
518   {
519     // enable resizing if needed
520     switch ( background.sizeType() )
521     {
522       case QgsTextBackgroundSettings::SizeBuffer:
523       {
524         QString resizeType;
525         if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle || background.type() == QgsTextBackgroundSettings::ShapeEllipse )
526         {
527           resizeType = QStringLiteral( "stretch" );
528         }
529         else
530         {
531           resizeType = QStringLiteral( "proportional" );
532         }
533         QDomElement voResize =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-resize" ), resizeType );
534         textSymbolizerElement.appendChild( voResize );
535 
536         // now hadle margin
537         QSizeF size = background.size();
538         if ( size.width() > 0 || size.height() > 0 )
539         {
540           double x = QgsSymbolLayerUtils::rescaleUom( size.width(), background.sizeUnit(), props );
541           double y = QgsSymbolLayerUtils::rescaleUom( size.height(), background.sizeUnit(), props );
542           // in case of ellipse qgis pads the size generously to make sure the text is inside the ellipse
543           // the following seems to do the trick and keep visual output similar
544           if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
545           {
546             x += fontSize / 2;
547             y += fontSize;
548           }
549           QString resizeSpec = QString( "%1 %2" ).arg( qgsDoubleToString( x, 2 ), qgsDoubleToString( y, 2 ) );
550           QDomElement voMargin =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), resizeSpec );
551           textSymbolizerElement.appendChild( voMargin );
552         }
553         break;
554       }
555       case QgsTextBackgroundSettings::SizeFixed:
556       case QgsTextBackgroundSettings::SizePercent:
557         // nothing to do here
558         break;
559     }
560   }
561 }
562 
563 
toSld(QDomNode & parent,const QgsStringMap & props) const564 void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const
565 {
566 
567   if ( mSettings->drawLabels )
568   {
569     QDomDocument doc = parent.ownerDocument();
570 
571     QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) );
572     parent.appendChild( ruleElement );
573 
574     // scale dependencies
575     if ( mSettings->scaleVisibility )
576     {
577       QgsStringMap scaleProps = QgsStringMap();
578       // tricky here, the max scale is expressed as its denominator, but it's still the max scale
579       // in other words, the smallest scale denominator....
580       scaleProps.insert( "scaleMinDenom", qgsDoubleToString( mSettings->maximumScale ) );
581       scaleProps.insert( "scaleMaxDenom", qgsDoubleToString( mSettings->minimumScale ) );
582       QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, scaleProps );
583     }
584 
585     writeTextSymbolizer( ruleElement, *mSettings, props );
586   }
587 
588 
589 }
590 
setSettings(QgsPalLayerSettings * settings,const QString & providerId)591 void QgsVectorLayerSimpleLabeling::setSettings( QgsPalLayerSettings *settings, const QString &providerId )
592 {
593   Q_UNUSED( providerId )
594 
595   if ( mSettings.get() == settings )
596     return;
597 
598   mSettings.reset( settings );
599 }
600