QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgstextrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstextrenderer.cpp
3  -------------------s
4  begin : September 2015
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 "qgstextrenderer.h"
18 #include "qgis.h"
19 #include "qgstextrenderer_p.h"
20 #include "qgsfontutils.h"
21 #include "qgspathresolver.h"
22 #include "qgsreadwritecontext.h"
23 #include "qgsvectorlayer.h"
24 #include "qgssymbollayerutils.h"
25 #include "qgspainting.h"
26 #include "qgsmarkersymbollayer.h"
27 #include "qgspainteffectregistry.h"
28 #include "qgspallabeling.h"
29 #include <QFontDatabase>
30 #include <QDesktopWidget>
31 
32 Q_GUI_EXPORT extern int qt_defaultDpiX();
33 Q_GUI_EXPORT extern int qt_defaultDpiY();
34 
36 {
37  if ( val == 0 )
39  else if ( val == 1 )
41  else if ( val == 2 )
43  else if ( val == 3 )
45  else
47 }
48 
49 static void _fixQPictureDPI( QPainter *p )
50 {
51  // QPicture makes an assumption that we drawing to it with system DPI.
52  // Then when being drawn, it scales the painter. The following call
53  // negates the effect. There is no way of setting QPicture's DPI.
54  // See QTBUG-20361
55  p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
56  static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
57 }
58 
59 static QColor _readColor( QgsVectorLayer *layer, const QString &property, const QColor &defaultColor = Qt::black, bool withAlpha = true )
60 {
61  int r = layer->customProperty( property + 'R', QVariant( defaultColor.red() ) ).toInt();
62  int g = layer->customProperty( property + 'G', QVariant( defaultColor.green() ) ).toInt();
63  int b = layer->customProperty( property + 'B', QVariant( defaultColor.blue() ) ).toInt();
64  int a = withAlpha ? layer->customProperty( property + 'A', QVariant( defaultColor.alpha() ) ).toInt() : 255;
65  return QColor( r, g, b, a );
66 }
67 
69 {
70  d = new QgsTextBufferSettingsPrivate();
71 }
72 
74  : d( other.d )
75 {
76 }
77 
79 {
80  d = other.d;
81  return *this;
82 }
83 
85 {
86 
87 }
88 
90 {
91  return d->enabled;
92 }
93 
95 {
96  d->enabled = enabled;
97 }
98 
100 {
101  return d->size;
102 }
103 
105 {
106  d->size = size;
107 }
108 
110 {
111  return d->sizeUnit;
112 }
113 
115 {
116  d->sizeUnit = unit;
117 }
118 
120 {
121  return d->sizeMapUnitScale;
122 }
123 
125 {
126  d->sizeMapUnitScale = scale;
127 }
128 
130 {
131  return d->color;
132 }
133 
135 {
136  d->color = color;
137 }
138 
140 {
141  return d->fillBufferInterior;
142 }
143 
145 {
146  d->fillBufferInterior = fill;
147 }
148 
150 {
151  return d->opacity;
152 }
153 
155 {
156  d->opacity = opacity;
157 }
158 
159 Qt::PenJoinStyle QgsTextBufferSettings::joinStyle() const
160 {
161  return d->joinStyle;
162 }
163 
164 void QgsTextBufferSettings::setJoinStyle( Qt::PenJoinStyle style )
165 {
166  d->joinStyle = style;
167 }
168 
169 QPainter::CompositionMode QgsTextBufferSettings::blendMode() const
170 {
171  return d->blendMode;
172 }
173 
174 void QgsTextBufferSettings::setBlendMode( QPainter::CompositionMode mode )
175 {
176  d->blendMode = mode;
177 }
178 
180 {
181  return d->paintEffect.get();
182 }
183 
185 {
186  d->paintEffect.reset( effect );
187 }
188 
190 {
191  if ( properties.isActive( QgsPalLayerSettings::BufferDraw ) )
192  {
193  context.expressionContext().setOriginalValueVariable( d->enabled );
194  d->enabled = properties.valueAsBool( QgsPalLayerSettings::BufferDraw, context.expressionContext(), d->enabled );
195  }
196 
197  if ( properties.isActive( QgsPalLayerSettings::BufferSize ) )
198  {
199  context.expressionContext().setOriginalValueVariable( d->size );
200  d->size = properties.valueAsDouble( QgsPalLayerSettings::BufferSize, context.expressionContext(), d->size );
201  }
202 
203  QVariant exprVal = properties.value( QgsPalLayerSettings::BufferUnit, context.expressionContext() );
204  if ( exprVal.isValid() )
205  {
206  QString units = exprVal.toString();
207  if ( !units.isEmpty() )
208  {
209  bool ok;
211  if ( ok )
212  d->sizeUnit = res;
213  }
214  }
215 
216  if ( properties.isActive( QgsPalLayerSettings::BufferOpacity ) )
217  {
218  context.expressionContext().setOriginalValueVariable( d->opacity * 100 );
219  d->opacity = properties.value( QgsPalLayerSettings::BufferOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0;
220  }
221 
222  if ( properties.isActive( QgsPalLayerSettings::BufferColor ) )
223  {
225  d->color = properties.valueAsColor( QgsPalLayerSettings::BufferColor, context.expressionContext(), d->color );
226  }
227 
229  {
230  exprVal = properties.value( QgsPalLayerSettings::BufferBlendMode, context.expressionContext() );
231  QString blendstr = exprVal.toString().trimmed();
232  if ( !blendstr.isEmpty() )
233  d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
234  }
235 
237  {
238  exprVal = properties.value( QgsPalLayerSettings::BufferJoinStyle, context.expressionContext() );
239  QString joinstr = exprVal.toString().trimmed();
240  if ( !joinstr.isEmpty() )
241  {
242  d->joinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( joinstr );
243  }
244  }
245 }
246 
248 {
249  // text buffer
250  double bufSize = layer->customProperty( QStringLiteral( "labeling/bufferSize" ), QVariant( 0.0 ) ).toDouble();
251 
252  // fix for buffer being keyed off of just its size in the past (<2.0)
253  QVariant drawBuffer = layer->customProperty( QStringLiteral( "labeling/bufferDraw" ), QVariant() );
254  if ( drawBuffer.isValid() )
255  {
256  d->enabled = drawBuffer.toBool();
257  d->size = bufSize;
258  }
259  else if ( bufSize != 0.0 )
260  {
261  d->enabled = true;
262  d->size = bufSize;
263  }
264  else
265  {
266  // keep bufferSize at new 1.0 default
267  d->enabled = false;
268  }
269 
270  if ( layer->customProperty( QStringLiteral( "labeling/bufferSizeUnits" ) ).toString().isEmpty() )
271  {
272  bool bufferSizeInMapUnits = layer->customProperty( QStringLiteral( "labeling/bufferSizeInMapUnits" ) ).toBool();
273  d->sizeUnit = bufferSizeInMapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
274  }
275  else
276  {
277  d->sizeUnit = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/bufferSizeUnits" ) ).toString() );
278  }
279 
280  if ( layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitScale" ) ).toString().isEmpty() )
281  {
282  //fallback to older property
283  double oldMin = layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitMinScale" ), 0.0 ).toDouble();
284  d->sizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
285  double oldMax = layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitMaxScale" ), 0.0 ).toDouble();
286  d->sizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
287  }
288  else
289  {
290  d->sizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/bufferSizeMapUnitScale" ) ).toString() );
291  }
292  d->color = _readColor( layer, QStringLiteral( "labeling/bufferColor" ), Qt::white, false );
293  if ( layer->customProperty( QStringLiteral( "labeling/bufferOpacity" ) ).toString().isEmpty() )
294  {
295  d->opacity = ( 1 - layer->customProperty( QStringLiteral( "labeling/bufferTransp" ) ).toInt() / 100.0 ); //0 -100
296  }
297  else
298  {
299  d->opacity = ( layer->customProperty( QStringLiteral( "labeling/bufferOpacity" ) ).toDouble() );
300  }
301  d->blendMode = QgsPainting::getCompositionMode(
302  static_cast< QgsPainting::BlendMode >( layer->customProperty( QStringLiteral( "labeling/bufferBlendMode" ), QVariant( QgsPainting::BlendNormal ) ).toUInt() ) );
303  d->joinStyle = static_cast< Qt::PenJoinStyle >( layer->customProperty( QStringLiteral( "labeling/bufferJoinStyle" ), QVariant( Qt::RoundJoin ) ).toUInt() );
304 
305  d->fillBufferInterior = !layer->customProperty( QStringLiteral( "labeling/bufferNoFill" ), QVariant( false ) ).toBool();
306 
307  if ( layer->customProperty( QStringLiteral( "labeling/bufferEffect" ) ).isValid() )
308  {
309  QDomDocument doc( QStringLiteral( "effect" ) );
310  doc.setContent( layer->customProperty( QStringLiteral( "labeling/bufferEffect" ) ).toString() );
311  QDomElement effectElem = doc.firstChildElement( QStringLiteral( "effect" ) ).firstChildElement( QStringLiteral( "effect" ) );
312  setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
313  }
314  else
315  setPaintEffect( nullptr );
316 }
317 
318 void QgsTextBufferSettings::readXml( const QDomElement &elem )
319 {
320  QDomElement textBufferElem = elem.firstChildElement( QStringLiteral( "text-buffer" ) );
321  double bufSize = textBufferElem.attribute( QStringLiteral( "bufferSize" ), QStringLiteral( "0" ) ).toDouble();
322 
323  // fix for buffer being keyed off of just its size in the past (<2.0)
324  QVariant drawBuffer = textBufferElem.attribute( QStringLiteral( "bufferDraw" ) );
325  if ( drawBuffer.isValid() )
326  {
327  d->enabled = drawBuffer.toBool();
328  d->size = bufSize;
329  }
330  else if ( bufSize != 0.0 )
331  {
332  d->enabled = true;
333  d->size = bufSize;
334  }
335  else
336  {
337  // keep bufferSize at new 1.0 default
338  d->enabled = false;
339  }
340 
341  if ( !textBufferElem.hasAttribute( QStringLiteral( "bufferSizeUnits" ) ) )
342  {
343  bool bufferSizeInMapUnits = textBufferElem.attribute( QStringLiteral( "bufferSizeInMapUnits" ) ).toInt();
344  d->sizeUnit = bufferSizeInMapUnits ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
345  }
346  else
347  {
348  d->sizeUnit = QgsUnitTypes::decodeRenderUnit( textBufferElem.attribute( QStringLiteral( "bufferSizeUnits" ) ) );
349  }
350 
351  if ( !textBufferElem.hasAttribute( QStringLiteral( "bufferSizeMapUnitScale" ) ) )
352  {
353  //fallback to older property
354  double oldMin = textBufferElem.attribute( QStringLiteral( "bufferSizeMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
355  d->sizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
356  double oldMax = textBufferElem.attribute( QStringLiteral( "bufferSizeMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
357  d->sizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
358  }
359  else
360  {
361  d->sizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( textBufferElem.attribute( QStringLiteral( "bufferSizeMapUnitScale" ) ) );
362  }
363  d->color = QgsSymbolLayerUtils::decodeColor( textBufferElem.attribute( QStringLiteral( "bufferColor" ), QgsSymbolLayerUtils::encodeColor( Qt::white ) ) );
364 
365  if ( !textBufferElem.hasAttribute( QStringLiteral( "bufferOpacity" ) ) )
366  {
367  d->opacity = ( 1 - textBufferElem.attribute( QStringLiteral( "bufferTransp" ) ).toInt() / 100.0 ); //0 -100
368  }
369  else
370  {
371  d->opacity = ( textBufferElem.attribute( QStringLiteral( "bufferOpacity" ) ).toDouble() );
372  }
373 
374  d->blendMode = QgsPainting::getCompositionMode(
375  static_cast< QgsPainting::BlendMode >( textBufferElem.attribute( QStringLiteral( "bufferBlendMode" ), QString::number( QgsPainting::BlendNormal ) ).toUInt() ) );
376  d->joinStyle = static_cast< Qt::PenJoinStyle >( textBufferElem.attribute( QStringLiteral( "bufferJoinStyle" ), QString::number( Qt::RoundJoin ) ).toUInt() );
377  d->fillBufferInterior = !textBufferElem.attribute( QStringLiteral( "bufferNoFill" ), QStringLiteral( "0" ) ).toInt();
378  QDomElement effectElem = textBufferElem.firstChildElement( QStringLiteral( "effect" ) );
379  if ( !effectElem.isNull() )
381  else
382  setPaintEffect( nullptr );
383 }
384 
385 QDomElement QgsTextBufferSettings::writeXml( QDomDocument &doc ) const
386 {
387  // text buffer
388  QDomElement textBufferElem = doc.createElement( QStringLiteral( "text-buffer" ) );
389  textBufferElem.setAttribute( QStringLiteral( "bufferDraw" ), d->enabled );
390  textBufferElem.setAttribute( QStringLiteral( "bufferSize" ), d->size );
391  textBufferElem.setAttribute( QStringLiteral( "bufferSizeUnits" ), QgsUnitTypes::encodeUnit( d->sizeUnit ) );
392  textBufferElem.setAttribute( QStringLiteral( "bufferSizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->sizeMapUnitScale ) );
393  textBufferElem.setAttribute( QStringLiteral( "bufferColor" ), QgsSymbolLayerUtils::encodeColor( d->color ) );
394  textBufferElem.setAttribute( QStringLiteral( "bufferNoFill" ), !d->fillBufferInterior );
395  textBufferElem.setAttribute( QStringLiteral( "bufferOpacity" ), d->opacity );
396  textBufferElem.setAttribute( QStringLiteral( "bufferJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
397  textBufferElem.setAttribute( QStringLiteral( "bufferBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
398  if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
399  d->paintEffect->saveProperties( doc, textBufferElem );
400  return textBufferElem;
401 }
402 
403 
404 //
405 // QgsTextBackgroundSettings
406 //
407 
409 {
410  d = new QgsTextBackgroundSettingsPrivate();
411 }
412 
414  : d( other.d )
415 {
416 
417 }
418 
420 {
421  d = other.d;
422  return *this;
423 }
424 
426 {
427 
428 }
429 
431 {
432  return d->enabled;
433 }
434 
436 {
437  d->enabled = enabled;
438 }
439 
441 {
442  return d->type;
443 }
444 
446 {
447  d->type = type;
448 }
449 
451 {
452  return d->svgFile;
453 }
454 
455 void QgsTextBackgroundSettings::setSvgFile( const QString &file )
456 {
457  d->svgFile = file;
458 }
459 
461 {
462  return d->markerSymbol.get();
463 }
464 
466 {
467  d->markerSymbol.reset( symbol );
468 }
469 
471 {
472  return d->sizeType;
473 }
474 
476 {
477  d->sizeType = type;
478 }
479 
481 {
482  return d->size;
483 }
484 
486 {
487  d->size = size;
488 }
489 
491 {
492  return d->sizeUnits;
493 }
494 
496 {
497  d->sizeUnits = unit;
498 }
499 
501 {
502  return d->sizeMapUnitScale;
503 }
504 
506 {
507  d->sizeMapUnitScale = scale;
508 }
509 
511 {
512  return d->rotationType;
513 }
514 
516 {
517  d->rotationType = type;
518 }
519 
521 {
522  return d->rotation;
523 }
524 
526 {
527  d->rotation = rotation;
528 }
529 
531 {
532  return d->offset;
533 }
534 
536 {
537  d->offset = offset;
538 }
539 
541 {
542  return d->offsetUnits;
543 }
544 
546 {
547  d->offsetUnits = units;
548 }
549 
551 {
552  return d->offsetMapUnitScale;
553 }
554 
556 {
557  d->offsetMapUnitScale = scale;
558 }
559 
561 {
562  return d->radii;
563 }
564 
566 {
567  d->radii = radii;
568 }
569 
571 {
572  return d->radiiUnits;
573 }
574 
576 {
577  d->radiiUnits = units;
578 }
579 
581 {
582  return d->radiiMapUnitScale;
583 }
584 
586 {
587  d->radiiMapUnitScale = scale;
588 }
589 
591 {
592  return d->opacity;
593 }
594 
596 {
597  d->opacity = opacity;
598 }
599 
600 QPainter::CompositionMode QgsTextBackgroundSettings::blendMode() const
601 {
602  return d->blendMode;
603 }
604 
605 void QgsTextBackgroundSettings::setBlendMode( QPainter::CompositionMode mode )
606 {
607  d->blendMode = mode;
608 }
609 
611 {
612  return d->fillColor;
613 }
614 
615 void QgsTextBackgroundSettings::setFillColor( const QColor &color )
616 {
617  d->fillColor = color;
618 }
619 
621 {
622  return d->strokeColor;
623 }
624 
625 void QgsTextBackgroundSettings::setStrokeColor( const QColor &color )
626 {
627  d->strokeColor = color;
628 }
629 
631 {
632  return d->strokeWidth;
633 }
634 
636 {
637  d->strokeWidth = width;
638 }
639 
641 {
642  return d->strokeWidthUnits;
643 }
644 
646 {
647  d->strokeWidthUnits = units;
648 }
649 
651 {
652  return d->strokeWidthMapUnitScale;
653 }
654 
656 {
657  d->strokeWidthMapUnitScale = scale;
658 }
659 
660 Qt::PenJoinStyle QgsTextBackgroundSettings::joinStyle() const
661 {
662  return d->joinStyle;
663 }
664 
665 void QgsTextBackgroundSettings::setJoinStyle( Qt::PenJoinStyle style )
666 {
667  d->joinStyle = style;
668 }
669 
671 {
672  return d->paintEffect.get();
673 }
674 
676 {
677  d->paintEffect.reset( effect );
678 }
679 
681 {
682  d->enabled = layer->customProperty( QStringLiteral( "labeling/shapeDraw" ), QVariant( false ) ).toBool();
683  d->type = static_cast< ShapeType >( layer->customProperty( QStringLiteral( "labeling/shapeType" ), QVariant( ShapeRectangle ) ).toUInt() );
684  d->svgFile = layer->customProperty( QStringLiteral( "labeling/shapeSVGFile" ), QVariant( "" ) ).toString();
685  d->sizeType = static_cast< SizeType >( layer->customProperty( QStringLiteral( "labeling/shapeSizeType" ), QVariant( SizeBuffer ) ).toUInt() );
686  d->size = QSizeF( layer->customProperty( QStringLiteral( "labeling/shapeSizeX" ), QVariant( 0.0 ) ).toDouble(),
687  layer->customProperty( QStringLiteral( "labeling/shapeSizeY" ), QVariant( 0.0 ) ).toDouble() );
688 
689  if ( layer->customProperty( QStringLiteral( "labeling/shapeSizeUnit" ) ).toString().isEmpty() )
690  {
691  d->sizeUnits = convertFromOldLabelUnit( layer->customProperty( QStringLiteral( "labeling/shapeSizeUnits" ), 0 ).toUInt() );
692  }
693  else
694  {
695  d->sizeUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/shapeSizeUnit" ) ).toString() );
696  }
697 
698  if ( layer->customProperty( QStringLiteral( "labeling/shapeSizeMapUnitScale" ) ).toString().isEmpty() )
699  {
700  //fallback to older property
701  double oldMin = layer->customProperty( QStringLiteral( "labeling/shapeSizeMapUnitMinScale" ), 0.0 ).toDouble();
702  d->sizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
703  double oldMax = layer->customProperty( QStringLiteral( "labeling/shapeSizeMapUnitMaxScale" ), 0.0 ).toDouble();
704  d->sizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
705  }
706  else
707  {
708  d->sizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/shapeSizeMapUnitScale" ) ).toString() );
709  }
710  d->rotationType = static_cast< RotationType >( layer->customProperty( QStringLiteral( "labeling/shapeRotationType" ), QVariant( RotationSync ) ).toUInt() );
711  d->rotation = layer->customProperty( QStringLiteral( "labeling/shapeRotation" ), QVariant( 0.0 ) ).toDouble();
712  d->offset = QPointF( layer->customProperty( QStringLiteral( "labeling/shapeOffsetX" ), QVariant( 0.0 ) ).toDouble(),
713  layer->customProperty( QStringLiteral( "labeling/shapeOffsetY" ), QVariant( 0.0 ) ).toDouble() );
714 
715  if ( layer->customProperty( QStringLiteral( "labeling/shapeOffsetUnit" ) ).toString().isEmpty() )
716  {
717  d->offsetUnits = convertFromOldLabelUnit( layer->customProperty( QStringLiteral( "labeling/shapeOffsetUnits" ), 0 ).toUInt() );
718  }
719  else
720  {
721  d->offsetUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/shapeOffsetUnit" ) ).toString() );
722  }
723 
724  if ( layer->customProperty( QStringLiteral( "labeling/shapeOffsetMapUnitScale" ) ).toString().isEmpty() )
725  {
726  //fallback to older property
727  double oldMin = layer->customProperty( QStringLiteral( "labeling/shapeOffsetMapUnitMinScale" ), 0.0 ).toDouble();
728  d->offsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
729  double oldMax = layer->customProperty( QStringLiteral( "labeling/shapeOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
730  d->offsetMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
731  }
732  else
733  {
734  d->offsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/shapeOffsetMapUnitScale" ) ).toString() );
735  }
736  d->radii = QSizeF( layer->customProperty( QStringLiteral( "labeling/shapeRadiiX" ), QVariant( 0.0 ) ).toDouble(),
737  layer->customProperty( QStringLiteral( "labeling/shapeRadiiY" ), QVariant( 0.0 ) ).toDouble() );
738 
739 
740  if ( layer->customProperty( QStringLiteral( "labeling/shapeRadiiUnit" ) ).toString().isEmpty() )
741  {
742  d->radiiUnits = convertFromOldLabelUnit( layer->customProperty( QStringLiteral( "labeling/shapeRadiiUnits" ), 0 ).toUInt() );
743  }
744  else
745  {
746  d->radiiUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/shapeRadiiUnit" ) ).toString() );
747  }
748 
749  if ( layer->customProperty( QStringLiteral( "labeling/shapeRadiiMapUnitScale" ) ).toString().isEmpty() )
750  {
751  //fallback to older property
752  double oldMin = layer->customProperty( QStringLiteral( "labeling/shapeRadiiMapUnitMinScale" ), 0.0 ).toDouble();
753  d->radiiMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
754  double oldMax = layer->customProperty( QStringLiteral( "labeling/shapeRadiiMapUnitMaxScale" ), 0.0 ).toDouble();
755  d->radiiMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
756  }
757  else
758  {
759  d->radiiMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/shapeRadiiMapUnitScale" ) ).toString() );
760  }
761  d->fillColor = _readColor( layer, QStringLiteral( "labeling/shapeFillColor" ), Qt::white, true );
762  d->strokeColor = _readColor( layer, QStringLiteral( "labeling/shapeBorderColor" ), Qt::darkGray, true );
763  d->strokeWidth = layer->customProperty( QStringLiteral( "labeling/shapeBorderWidth" ), QVariant( .0 ) ).toDouble();
764  if ( layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthUnit" ) ).toString().isEmpty() )
765  {
766  d->strokeWidthUnits = convertFromOldLabelUnit( layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthUnits" ), 0 ).toUInt() );
767  }
768  else
769  {
770  d->strokeWidthUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthUnit" ) ).toString() );
771  }
772  if ( layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthMapUnitScale" ) ).toString().isEmpty() )
773  {
774  //fallback to older property
775  double oldMin = layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthMapUnitMinScale" ), 0.0 ).toDouble();
776  d->strokeWidthMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
777  double oldMax = layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthMapUnitMaxScale" ), 0.0 ).toDouble();
778  d->strokeWidthMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
779  }
780  else
781  {
782  d->strokeWidthMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/shapeBorderWidthMapUnitScale" ) ).toString() );
783  }
784  d->joinStyle = static_cast< Qt::PenJoinStyle >( layer->customProperty( QStringLiteral( "labeling/shapeJoinStyle" ), QVariant( Qt::BevelJoin ) ).toUInt() );
785 
786  if ( layer->customProperty( QStringLiteral( "labeling/shapeOpacity" ) ).toString().isEmpty() )
787  {
788  d->opacity = ( 1 - layer->customProperty( QStringLiteral( "labeling/shapeTransparency" ) ).toInt() / 100.0 ); //0 -100
789  }
790  else
791  {
792  d->opacity = ( layer->customProperty( QStringLiteral( "labeling/shapeOpacity" ) ).toDouble() );
793  }
794  d->blendMode = QgsPainting::getCompositionMode(
795  static_cast< QgsPainting::BlendMode >( layer->customProperty( QStringLiteral( "labeling/shapeBlendMode" ), QVariant( QgsPainting::BlendNormal ) ).toUInt() ) );
796 
797  if ( layer->customProperty( QStringLiteral( "labeling/shapeEffect" ) ).isValid() )
798  {
799  QDomDocument doc( QStringLiteral( "effect" ) );
800  doc.setContent( layer->customProperty( QStringLiteral( "labeling/shapeEffect" ) ).toString() );
801  QDomElement effectElem = doc.firstChildElement( QStringLiteral( "effect" ) ).firstChildElement( QStringLiteral( "effect" ) );
802  setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
803  }
804  else
805  setPaintEffect( nullptr );
806 }
807 
808 void QgsTextBackgroundSettings::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
809 {
810  QDomElement backgroundElem = elem.firstChildElement( QStringLiteral( "background" ) );
811  d->enabled = backgroundElem.attribute( QStringLiteral( "shapeDraw" ), QStringLiteral( "0" ) ).toInt();
812  d->type = static_cast< ShapeType >( backgroundElem.attribute( QStringLiteral( "shapeType" ), QString::number( ShapeRectangle ) ).toUInt() );
813  d->svgFile = QgsSymbolLayerUtils::svgSymbolNameToPath( backgroundElem.attribute( QStringLiteral( "shapeSVGFile" ) ), context.pathResolver() );
814  d->sizeType = static_cast< SizeType >( backgroundElem.attribute( QStringLiteral( "shapeSizeType" ), QString::number( SizeBuffer ) ).toUInt() );
815  d->size = QSizeF( backgroundElem.attribute( QStringLiteral( "shapeSizeX" ), QStringLiteral( "0" ) ).toDouble(),
816  backgroundElem.attribute( QStringLiteral( "shapeSizeY" ), QStringLiteral( "0" ) ).toDouble() );
817 
818  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeSizeUnit" ) ) )
819  {
820  d->sizeUnits = convertFromOldLabelUnit( backgroundElem.attribute( QStringLiteral( "shapeSizeUnits" ) ).toUInt() );
821  }
822  else
823  {
824  d->sizeUnits = QgsUnitTypes::decodeRenderUnit( backgroundElem.attribute( QStringLiteral( "shapeSizeUnit" ) ) );
825  }
826 
827  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeSizeMapUnitScale" ) ) )
828  {
829  //fallback to older property
830  double oldMin = backgroundElem.attribute( QStringLiteral( "shapeSizeMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
831  d->sizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
832  double oldMax = backgroundElem.attribute( QStringLiteral( "shapeSizeMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
833  d->sizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
834  }
835  else
836  {
837  d->sizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( backgroundElem.attribute( QStringLiteral( "shapeSizeMapUnitScale" ) ) );
838  }
839  d->rotationType = static_cast< RotationType >( backgroundElem.attribute( QStringLiteral( "shapeRotationType" ), QString::number( RotationSync ) ).toUInt() );
840  d->rotation = backgroundElem.attribute( QStringLiteral( "shapeRotation" ), QStringLiteral( "0" ) ).toDouble();
841  d->offset = QPointF( backgroundElem.attribute( QStringLiteral( "shapeOffsetX" ), QStringLiteral( "0" ) ).toDouble(),
842  backgroundElem.attribute( QStringLiteral( "shapeOffsetY" ), QStringLiteral( "0" ) ).toDouble() );
843 
844  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeOffsetUnit" ) ) )
845  {
846  d->offsetUnits = convertFromOldLabelUnit( backgroundElem.attribute( QStringLiteral( "shapeOffsetUnits" ) ).toUInt() );
847  }
848  else
849  {
850  d->offsetUnits = QgsUnitTypes::decodeRenderUnit( backgroundElem.attribute( QStringLiteral( "shapeOffsetUnit" ) ) );
851  }
852 
853  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeOffsetMapUnitScale" ) ) )
854  {
855  //fallback to older property
856  double oldMin = backgroundElem.attribute( QStringLiteral( "shapeOffsetMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
857  d->offsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
858  double oldMax = backgroundElem.attribute( QStringLiteral( "shapeOffsetMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
859  d->offsetMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
860  }
861  else
862  {
863  d->offsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( backgroundElem.attribute( QStringLiteral( "shapeOffsetMapUnitScale" ) ) );
864  }
865  d->radii = QSizeF( backgroundElem.attribute( QStringLiteral( "shapeRadiiX" ), QStringLiteral( "0" ) ).toDouble(),
866  backgroundElem.attribute( QStringLiteral( "shapeRadiiY" ), QStringLiteral( "0" ) ).toDouble() );
867 
868  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeRadiiUnit" ) ) )
869  {
870  d->radiiUnits = convertFromOldLabelUnit( backgroundElem.attribute( QStringLiteral( "shapeRadiiUnits" ) ).toUInt() );
871  }
872  else
873  {
874  d->radiiUnits = QgsUnitTypes::decodeRenderUnit( backgroundElem.attribute( QStringLiteral( "shapeRadiiUnit" ) ) );
875  }
876  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeRadiiMapUnitScale" ) ) )
877  {
878  //fallback to older property
879  double oldMin = backgroundElem.attribute( QStringLiteral( "shapeRadiiMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
880  d->radiiMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
881  double oldMax = backgroundElem.attribute( QStringLiteral( "shapeRadiiMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
882  d->radiiMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
883  }
884  else
885  {
886  d->radiiMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( backgroundElem.attribute( QStringLiteral( "shapeRadiiMapUnitScale" ) ) );
887  }
888  d->fillColor = QgsSymbolLayerUtils::decodeColor( backgroundElem.attribute( QStringLiteral( "shapeFillColor" ), QgsSymbolLayerUtils::encodeColor( Qt::white ) ) );
889  d->strokeColor = QgsSymbolLayerUtils::decodeColor( backgroundElem.attribute( QStringLiteral( "shapeBorderColor" ), QgsSymbolLayerUtils::encodeColor( Qt::darkGray ) ) );
890  d->strokeWidth = backgroundElem.attribute( QStringLiteral( "shapeBorderWidth" ), QStringLiteral( "0" ) ).toDouble();
891 
892  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeBorderWidthUnit" ) ) )
893  {
894  d->strokeWidthUnits = convertFromOldLabelUnit( backgroundElem.attribute( QStringLiteral( "shapeBorderWidthUnits" ) ).toUInt() );
895  }
896  else
897  {
898  d->strokeWidthUnits = QgsUnitTypes::decodeRenderUnit( backgroundElem.attribute( QStringLiteral( "shapeBorderWidthUnit" ) ) );
899  }
900  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeBorderWidthMapUnitScale" ) ) )
901  {
902  //fallback to older property
903  double oldMin = backgroundElem.attribute( QStringLiteral( "shapeBorderWidthMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
904  d->strokeWidthMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
905  double oldMax = backgroundElem.attribute( QStringLiteral( "shapeBorderWidthMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
906  d->strokeWidthMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
907  }
908  else
909  {
910  d->strokeWidthMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( backgroundElem.attribute( QStringLiteral( "shapeBorderWidthMapUnitScale" ) ) );
911  }
912  d->joinStyle = static_cast< Qt::PenJoinStyle >( backgroundElem.attribute( QStringLiteral( "shapeJoinStyle" ), QString::number( Qt::BevelJoin ) ).toUInt() );
913 
914  if ( !backgroundElem.hasAttribute( QStringLiteral( "shapeOpacity" ) ) )
915  {
916  d->opacity = ( 1 - backgroundElem.attribute( QStringLiteral( "shapeTransparency" ) ).toInt() / 100.0 ); //0 -100
917  }
918  else
919  {
920  d->opacity = ( backgroundElem.attribute( QStringLiteral( "shapeOpacity" ) ).toDouble() );
921  }
922 
923  d->blendMode = QgsPainting::getCompositionMode(
924  static_cast< QgsPainting::BlendMode >( backgroundElem.attribute( QStringLiteral( "shapeBlendMode" ), QString::number( QgsPainting::BlendNormal ) ).toUInt() ) );
925 
926  QDomElement effectElem = backgroundElem.firstChildElement( QStringLiteral( "effect" ) );
927  if ( !effectElem.isNull() )
929  else
930  setPaintEffect( nullptr );
931 
932  const QDomElement symbolElem = backgroundElem.firstChildElement( QStringLiteral( "symbol" ) );
933  if ( !symbolElem.isNull() )
934  setMarkerSymbol( QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElem, context ) );
935  else
936  setMarkerSymbol( nullptr );
937 }
938 
939 QDomElement QgsTextBackgroundSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
940 {
941  QDomElement backgroundElem = doc.createElement( QStringLiteral( "background" ) );
942  backgroundElem.setAttribute( QStringLiteral( "shapeDraw" ), d->enabled );
943  backgroundElem.setAttribute( QStringLiteral( "shapeType" ), static_cast< unsigned int >( d->type ) );
944  backgroundElem.setAttribute( QStringLiteral( "shapeSVGFile" ), QgsSymbolLayerUtils::svgSymbolPathToName( d->svgFile, context.pathResolver() ) );
945  backgroundElem.setAttribute( QStringLiteral( "shapeSizeType" ), static_cast< unsigned int >( d->sizeType ) );
946  backgroundElem.setAttribute( QStringLiteral( "shapeSizeX" ), d->size.width() );
947  backgroundElem.setAttribute( QStringLiteral( "shapeSizeY" ), d->size.height() );
948  backgroundElem.setAttribute( QStringLiteral( "shapeSizeUnit" ), QgsUnitTypes::encodeUnit( d->sizeUnits ) );
949  backgroundElem.setAttribute( QStringLiteral( "shapeSizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->sizeMapUnitScale ) );
950  backgroundElem.setAttribute( QStringLiteral( "shapeRotationType" ), static_cast< unsigned int >( d->rotationType ) );
951  backgroundElem.setAttribute( QStringLiteral( "shapeRotation" ), d->rotation );
952  backgroundElem.setAttribute( QStringLiteral( "shapeOffsetX" ), d->offset.x() );
953  backgroundElem.setAttribute( QStringLiteral( "shapeOffsetY" ), d->offset.y() );
954  backgroundElem.setAttribute( QStringLiteral( "shapeOffsetUnit" ), QgsUnitTypes::encodeUnit( d->offsetUnits ) );
955  backgroundElem.setAttribute( QStringLiteral( "shapeOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->offsetMapUnitScale ) );
956  backgroundElem.setAttribute( QStringLiteral( "shapeRadiiX" ), d->radii.width() );
957  backgroundElem.setAttribute( QStringLiteral( "shapeRadiiY" ), d->radii.height() );
958  backgroundElem.setAttribute( QStringLiteral( "shapeRadiiUnit" ), QgsUnitTypes::encodeUnit( d->radiiUnits ) );
959  backgroundElem.setAttribute( QStringLiteral( "shapeRadiiMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->radiiMapUnitScale ) );
960  backgroundElem.setAttribute( QStringLiteral( "shapeFillColor" ), QgsSymbolLayerUtils::encodeColor( d->fillColor ) );
961  backgroundElem.setAttribute( QStringLiteral( "shapeBorderColor" ), QgsSymbolLayerUtils::encodeColor( d->strokeColor ) );
962  backgroundElem.setAttribute( QStringLiteral( "shapeBorderWidth" ), d->strokeWidth );
963  backgroundElem.setAttribute( QStringLiteral( "shapeBorderWidthUnit" ), QgsUnitTypes::encodeUnit( d->strokeWidthUnits ) );
964  backgroundElem.setAttribute( QStringLiteral( "shapeBorderWidthMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->strokeWidthMapUnitScale ) );
965  backgroundElem.setAttribute( QStringLiteral( "shapeJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
966  backgroundElem.setAttribute( QStringLiteral( "shapeOpacity" ), d->opacity );
967  backgroundElem.setAttribute( QStringLiteral( "shapeBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
968  if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
969  d->paintEffect->saveProperties( doc, backgroundElem );
970 
971  if ( d->markerSymbol )
972  backgroundElem.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "markerSymbol" ), d->markerSymbol.get(), doc, context ) );
973 
974  return backgroundElem;
975 }
976 
978 {
979  if ( properties.isActive( QgsPalLayerSettings::ShapeDraw ) )
980  {
981  context.expressionContext().setOriginalValueVariable( d->enabled );
982  d->enabled = properties.valueAsBool( QgsPalLayerSettings::ShapeDraw, context.expressionContext(), d->enabled );
983  }
984 
985  if ( properties.isActive( QgsPalLayerSettings::ShapeSizeX ) )
986  {
987  context.expressionContext().setOriginalValueVariable( d->size.width() );
988  d->size.setWidth( properties.valueAsDouble( QgsPalLayerSettings::ShapeSizeX, context.expressionContext(), d->size.width() ) );
989  }
990  if ( properties.isActive( QgsPalLayerSettings::ShapeSizeY ) )
991  {
992  context.expressionContext().setOriginalValueVariable( d->size.height() );
993  d->size.setHeight( properties.valueAsDouble( QgsPalLayerSettings::ShapeSizeY, context.expressionContext(), d->size.height() ) );
994  }
995 
996  QVariant exprVal = properties.value( QgsPalLayerSettings::ShapeSizeUnits, context.expressionContext() );
997  if ( exprVal.isValid() )
998  {
999  QString units = exprVal.toString();
1000  if ( !units.isEmpty() )
1001  {
1002  bool ok;
1004  if ( ok )
1005  d->sizeUnits = res;
1006  }
1007  }
1008 
1009  exprVal = properties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
1010  if ( exprVal.isValid() )
1011  {
1012  const QString skind = exprVal.toString().trimmed();
1013  if ( !skind.isEmpty() )
1014  {
1015  d->type = QgsTextRendererUtils::decodeShapeType( skind );
1016  }
1017  }
1018 
1019  exprVal = properties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
1020  if ( exprVal.isValid() )
1021  {
1022  QString stype = exprVal.toString().trimmed();
1023  if ( !stype.isEmpty() )
1024  {
1025  d->sizeType = QgsTextRendererUtils::decodeBackgroundSizeType( stype );
1026  }
1027  }
1028 
1029  // data defined shape SVG path?
1030  context.expressionContext().setOriginalValueVariable( d->svgFile );
1031  exprVal = properties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
1032  if ( exprVal.isValid() )
1033  {
1034  QString svgfile = exprVal.toString().trimmed();
1035  d->svgFile = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
1036  }
1037 
1038  if ( properties.isActive( QgsPalLayerSettings::ShapeRotation ) )
1039  {
1040  context.expressionContext().setOriginalValueVariable( d->rotation );
1041  d->rotation = properties.valueAsDouble( QgsPalLayerSettings::ShapeRotation, context.expressionContext(), d->rotation );
1042  }
1043  exprVal = properties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
1044  if ( exprVal.isValid() )
1045  {
1046  QString rotstr = exprVal.toString().trimmed();
1047  if ( !rotstr.isEmpty() )
1048  {
1049  d->rotationType = QgsTextRendererUtils::decodeBackgroundRotationType( rotstr );
1050  }
1051  }
1052 
1053  exprVal = properties.value( QgsPalLayerSettings::ShapeOffset, context.expressionContext() );
1054  if ( exprVal.isValid() )
1055  {
1056  bool ok = false;
1057  const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
1058  if ( ok )
1059  {
1060  d->offset = res;
1061  }
1062  }
1063  exprVal = properties.value( QgsPalLayerSettings::ShapeOffsetUnits, context.expressionContext() );
1064  if ( exprVal.isValid() )
1065  {
1066  QString units = exprVal.toString();
1067  if ( !units.isEmpty() )
1068  {
1069  bool ok;
1071  if ( ok )
1072  d->offsetUnits = res;
1073  }
1074  }
1075 
1076  exprVal = properties.value( QgsPalLayerSettings::ShapeRadii, context.expressionContext() );
1077  if ( exprVal.isValid() )
1078  {
1079  bool ok = false;
1080  const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
1081  if ( ok )
1082  {
1083  d->radii = res;
1084  }
1085  }
1086 
1087  exprVal = properties.value( QgsPalLayerSettings::ShapeRadiiUnits, context.expressionContext() );
1088  if ( exprVal.isValid() )
1089  {
1090  QString units = exprVal.toString();
1091  if ( !units.isEmpty() )
1092  {
1093  bool ok;
1095  if ( ok )
1096  d->radiiUnits = res;
1097  }
1098  }
1099 
1100  if ( properties.isActive( QgsPalLayerSettings::ShapeOpacity ) )
1101  {
1102  context.expressionContext().setOriginalValueVariable( d->opacity * 100 );
1103  d->opacity = properties.value( QgsPalLayerSettings::ShapeOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0;
1104  }
1105 
1106  if ( properties.isActive( QgsPalLayerSettings::ShapeFillColor ) )
1107  {
1109  d->fillColor = properties.valueAsColor( QgsPalLayerSettings::ShapeFillColor, context.expressionContext(), d->fillColor );
1110  }
1111  if ( properties.isActive( QgsPalLayerSettings::ShapeStrokeColor ) )
1112  {
1114  d->strokeColor = properties.valueAsColor( QgsPalLayerSettings::ShapeStrokeColor, context.expressionContext(), d->strokeColor );
1115  }
1116 
1117  if ( properties.isActive( QgsPalLayerSettings::ShapeStrokeWidth ) )
1118  {
1119  context.expressionContext().setOriginalValueVariable( d->strokeWidth );
1120  d->strokeWidth = properties.valueAsDouble( QgsPalLayerSettings::ShapeStrokeWidth, context.expressionContext(), d->strokeWidth );
1121  }
1122  exprVal = properties.value( QgsPalLayerSettings::ShapeStrokeWidthUnits, context.expressionContext() );
1123  if ( exprVal.isValid() )
1124  {
1125  QString units = exprVal.toString();
1126  if ( !units.isEmpty() )
1127  {
1128  bool ok;
1130  if ( ok )
1131  d->strokeWidthUnits = res;
1132  }
1133  }
1134 
1135  if ( properties.isActive( QgsPalLayerSettings::ShapeBlendMode ) )
1136  {
1137  exprVal = properties.value( QgsPalLayerSettings::ShapeBlendMode, context.expressionContext() );
1138  QString blendstr = exprVal.toString().trimmed();
1139  if ( !blendstr.isEmpty() )
1140  d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
1141  }
1142 
1143  if ( properties.isActive( QgsPalLayerSettings::ShapeJoinStyle ) )
1144  {
1145  exprVal = properties.value( QgsPalLayerSettings::ShapeJoinStyle, context.expressionContext() );
1146  QString joinstr = exprVal.toString().trimmed();
1147  if ( !joinstr.isEmpty() )
1148  {
1149  d->joinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( joinstr );
1150  }
1151  }
1152 }
1153 
1154 
1155 //
1156 // QgsTextShadowSettings
1157 //
1158 
1160 {
1161  d = new QgsTextShadowSettingsPrivate();
1162 }
1163 
1165  : d( other.d )
1166 {
1167 
1168 }
1169 
1171 {
1172  d = other.d;
1173  return *this;
1174 }
1175 
1177 {
1178 
1179 }
1180 
1182 {
1183  return d->enabled;
1184 }
1185 
1187 {
1188  d->enabled = enabled;
1189 }
1190 
1192 {
1193  return d->shadowUnder;
1194 }
1195 
1197 {
1198  d->shadowUnder = placement;
1199 }
1200 
1202 {
1203  return d->offsetAngle;
1204 }
1205 
1207 {
1208  d->offsetAngle = angle;
1209 }
1210 
1212 {
1213  return d->offsetDist;
1214 }
1215 
1217 {
1218  d->offsetDist = distance;
1219 }
1220 
1222 {
1223  return d->offsetUnits;
1224 }
1225 
1227 {
1228  d->offsetUnits = units;
1229 }
1230 
1232 {
1233  return d->offsetMapUnitScale;
1234 }
1235 
1237 {
1238  d->offsetMapUnitScale = scale;
1239 }
1240 
1242 {
1243  return d->offsetGlobal;
1244 }
1245 
1247 {
1248  d->offsetGlobal = global;
1249 }
1250 
1252 {
1253  return d->radius;
1254 }
1255 
1257 {
1258  d->radius = radius;
1259 }
1260 
1262 {
1263  return d->radiusUnits;
1264 }
1265 
1267 {
1268  d->radiusUnits = units;
1269 }
1270 
1272 {
1273  return d->radiusMapUnitScale;
1274 }
1275 
1277 {
1278  d->radiusMapUnitScale = scale;
1279 }
1280 
1282 {
1283  return d->radiusAlphaOnly;
1284 }
1285 
1287 {
1288  d->radiusAlphaOnly = alphaOnly;
1289 }
1290 
1292 {
1293  return d->opacity;
1294 }
1295 
1297 {
1298  d->opacity = opacity;
1299 }
1300 
1302 {
1303  return d->scale;
1304 }
1305 
1307 {
1308  d->scale = scale;
1309 }
1310 
1312 {
1313  return d->color;
1314 }
1315 
1317 {
1318  d->color = color;
1319 }
1320 
1321 QPainter::CompositionMode QgsTextShadowSettings::blendMode() const
1322 {
1323  return d->blendMode;
1324 }
1325 
1326 void QgsTextShadowSettings::setBlendMode( QPainter::CompositionMode mode )
1327 {
1328  d->blendMode = mode;
1329 }
1330 
1332 {
1333  d->enabled = layer->customProperty( QStringLiteral( "labeling/shadowDraw" ), QVariant( false ) ).toBool();
1334  d->shadowUnder = static_cast< ShadowPlacement >( layer->customProperty( QStringLiteral( "labeling/shadowUnder" ), QVariant( ShadowLowest ) ).toUInt() );//ShadowLowest;
1335  d->offsetAngle = layer->customProperty( QStringLiteral( "labeling/shadowOffsetAngle" ), QVariant( 135 ) ).toInt();
1336  d->offsetDist = layer->customProperty( QStringLiteral( "labeling/shadowOffsetDist" ), QVariant( 1.0 ) ).toDouble();
1337 
1338  if ( layer->customProperty( QStringLiteral( "labeling/shadowOffsetUnit" ) ).toString().isEmpty() )
1339  {
1340  d->offsetUnits = convertFromOldLabelUnit( layer->customProperty( QStringLiteral( "labeling/shadowOffsetUnits" ), 0 ).toUInt() );
1341  }
1342  else
1343  {
1344  d->offsetUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/shadowOffsetUnit" ) ).toString() );
1345  }
1346  if ( layer->customProperty( QStringLiteral( "labeling/shadowOffsetMapUnitScale" ) ).toString().isEmpty() )
1347  {
1348  //fallback to older property
1349  double oldMin = layer->customProperty( QStringLiteral( "labeling/shadowOffsetMapUnitMinScale" ), 0.0 ).toDouble();
1350  d->offsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1351  double oldMax = layer->customProperty( QStringLiteral( "labeling/shadowOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
1352  d->offsetMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1353  }
1354  else
1355  {
1356  d->offsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/shadowOffsetMapUnitScale" ) ).toString() );
1357  }
1358  d->offsetGlobal = layer->customProperty( QStringLiteral( "labeling/shadowOffsetGlobal" ), QVariant( true ) ).toBool();
1359  d->radius = layer->customProperty( QStringLiteral( "labeling/shadowRadius" ), QVariant( 1.5 ) ).toDouble();
1360 
1361  if ( layer->customProperty( QStringLiteral( "labeling/shadowRadiusUnit" ) ).toString().isEmpty() )
1362  {
1363  d->radiusUnits = convertFromOldLabelUnit( layer->customProperty( QStringLiteral( "labeling/shadowRadiusUnits" ), 0 ).toUInt() );
1364  }
1365  else
1366  {
1367  d->radiusUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/shadowRadiusUnit" ) ).toString() );
1368  }
1369  if ( layer->customProperty( QStringLiteral( "labeling/shadowRadiusMapUnitScale" ) ).toString().isEmpty() )
1370  {
1371  //fallback to older property
1372  double oldMin = layer->customProperty( QStringLiteral( "labeling/shadowRadiusMapUnitMinScale" ), 0.0 ).toDouble();
1373  d->radiusMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1374  double oldMax = layer->customProperty( QStringLiteral( "labeling/shadowRadiusMapUnitMaxScale" ), 0.0 ).toDouble();
1375  d->radiusMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1376  }
1377  else
1378  {
1379  d->radiusMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/shadowRadiusMapUnitScale" ) ).toString() );
1380  }
1381  d->radiusAlphaOnly = layer->customProperty( QStringLiteral( "labeling/shadowRadiusAlphaOnly" ), QVariant( false ) ).toBool();
1382 
1383  if ( layer->customProperty( QStringLiteral( "labeling/shadowOpacity" ) ).toString().isEmpty() )
1384  {
1385  d->opacity = ( 1 - layer->customProperty( QStringLiteral( "labeling/shadowTransparency" ) ).toInt() / 100.0 ); //0 -100
1386  }
1387  else
1388  {
1389  d->opacity = ( layer->customProperty( QStringLiteral( "labeling/shadowOpacity" ) ).toDouble() );
1390  }
1391  d->scale = layer->customProperty( QStringLiteral( "labeling/shadowScale" ), QVariant( 100 ) ).toInt();
1392  d->color = _readColor( layer, QStringLiteral( "labeling/shadowColor" ), Qt::black, false );
1393  d->blendMode = QgsPainting::getCompositionMode(
1394  static_cast< QgsPainting::BlendMode >( layer->customProperty( QStringLiteral( "labeling/shadowBlendMode" ), QVariant( QgsPainting::BlendMultiply ) ).toUInt() ) );
1395 }
1396 
1397 void QgsTextShadowSettings::readXml( const QDomElement &elem )
1398 {
1399  QDomElement shadowElem = elem.firstChildElement( QStringLiteral( "shadow" ) );
1400  d->enabled = shadowElem.attribute( QStringLiteral( "shadowDraw" ), QStringLiteral( "0" ) ).toInt();
1401  d->shadowUnder = static_cast< ShadowPlacement >( shadowElem.attribute( QStringLiteral( "shadowUnder" ), QString::number( ShadowLowest ) ).toUInt() );//ShadowLowest;
1402  d->offsetAngle = shadowElem.attribute( QStringLiteral( "shadowOffsetAngle" ), QStringLiteral( "135" ) ).toInt();
1403  d->offsetDist = shadowElem.attribute( QStringLiteral( "shadowOffsetDist" ), QStringLiteral( "1" ) ).toDouble();
1404 
1405  if ( !shadowElem.hasAttribute( QStringLiteral( "shadowOffsetUnit" ) ) )
1406  {
1407  d->offsetUnits = convertFromOldLabelUnit( shadowElem.attribute( QStringLiteral( "shadowOffsetUnits" ) ).toUInt() );
1408  }
1409  else
1410  {
1411  d->offsetUnits = QgsUnitTypes::decodeRenderUnit( shadowElem.attribute( QStringLiteral( "shadowOffsetUnit" ) ) );
1412  }
1413 
1414  if ( !shadowElem.hasAttribute( QStringLiteral( "shadowOffsetMapUnitScale" ) ) )
1415  {
1416  //fallback to older property
1417  double oldMin = shadowElem.attribute( QStringLiteral( "shadowOffsetMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1418  d->offsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1419  double oldMax = shadowElem.attribute( QStringLiteral( "shadowOffsetMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1420  d->offsetMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1421  }
1422  else
1423  {
1424  d->offsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( shadowElem.attribute( QStringLiteral( "shadowOffsetMapUnitScale" ) ) );
1425  }
1426  d->offsetGlobal = shadowElem.attribute( QStringLiteral( "shadowOffsetGlobal" ), QStringLiteral( "1" ) ).toInt();
1427  d->radius = shadowElem.attribute( QStringLiteral( "shadowRadius" ), QStringLiteral( "1.5" ) ).toDouble();
1428 
1429  if ( !shadowElem.hasAttribute( QStringLiteral( "shadowRadiusUnit" ) ) )
1430  {
1431  d->radiusUnits = convertFromOldLabelUnit( shadowElem.attribute( QStringLiteral( "shadowRadiusUnits" ) ).toUInt() );
1432  }
1433  else
1434  {
1435  d->radiusUnits = QgsUnitTypes::decodeRenderUnit( shadowElem.attribute( QStringLiteral( "shadowRadiusUnit" ) ) );
1436  }
1437  if ( !shadowElem.hasAttribute( QStringLiteral( "shadowRadiusMapUnitScale" ) ) )
1438  {
1439  //fallback to older property
1440  double oldMin = shadowElem.attribute( QStringLiteral( "shadowRadiusMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1441  d->radiusMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1442  double oldMax = shadowElem.attribute( QStringLiteral( "shadowRadiusMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1443  d->radiusMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1444  }
1445  else
1446  {
1447  d->radiusMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( shadowElem.attribute( QStringLiteral( "shadowRadiusMapUnitScale" ) ) );
1448  }
1449  d->radiusAlphaOnly = shadowElem.attribute( QStringLiteral( "shadowRadiusAlphaOnly" ), QStringLiteral( "0" ) ).toInt();
1450 
1451  if ( !shadowElem.hasAttribute( QStringLiteral( "shadowOpacity" ) ) )
1452  {
1453  d->opacity = ( 1 - shadowElem.attribute( QStringLiteral( "shadowTransparency" ) ).toInt() / 100.0 ); //0 -100
1454  }
1455  else
1456  {
1457  d->opacity = ( shadowElem.attribute( QStringLiteral( "shadowOpacity" ) ).toDouble() );
1458  }
1459  d->scale = shadowElem.attribute( QStringLiteral( "shadowScale" ), QStringLiteral( "100" ) ).toInt();
1460  d->color = QgsSymbolLayerUtils::decodeColor( shadowElem.attribute( QStringLiteral( "shadowColor" ), QgsSymbolLayerUtils::encodeColor( Qt::black ) ) );
1461  d->blendMode = QgsPainting::getCompositionMode(
1462  static_cast< QgsPainting::BlendMode >( shadowElem.attribute( QStringLiteral( "shadowBlendMode" ), QString::number( QgsPainting::BlendMultiply ) ).toUInt() ) );
1463 }
1464 
1465 QDomElement QgsTextShadowSettings::writeXml( QDomDocument &doc ) const
1466 {
1467  QDomElement shadowElem = doc.createElement( QStringLiteral( "shadow" ) );
1468  shadowElem.setAttribute( QStringLiteral( "shadowDraw" ), d->enabled );
1469  shadowElem.setAttribute( QStringLiteral( "shadowUnder" ), static_cast< unsigned int >( d->shadowUnder ) );
1470  shadowElem.setAttribute( QStringLiteral( "shadowOffsetAngle" ), d->offsetAngle );
1471  shadowElem.setAttribute( QStringLiteral( "shadowOffsetDist" ), d->offsetDist );
1472  shadowElem.setAttribute( QStringLiteral( "shadowOffsetUnit" ), QgsUnitTypes::encodeUnit( d->offsetUnits ) );
1473  shadowElem.setAttribute( QStringLiteral( "shadowOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->offsetMapUnitScale ) );
1474  shadowElem.setAttribute( QStringLiteral( "shadowOffsetGlobal" ), d->offsetGlobal );
1475  shadowElem.setAttribute( QStringLiteral( "shadowRadius" ), d->radius );
1476  shadowElem.setAttribute( QStringLiteral( "shadowRadiusUnit" ), QgsUnitTypes::encodeUnit( d->radiusUnits ) );
1477  shadowElem.setAttribute( QStringLiteral( "shadowRadiusMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->radiusMapUnitScale ) );
1478  shadowElem.setAttribute( QStringLiteral( "shadowRadiusAlphaOnly" ), d->radiusAlphaOnly );
1479  shadowElem.setAttribute( QStringLiteral( "shadowOpacity" ), d->opacity );
1480  shadowElem.setAttribute( QStringLiteral( "shadowScale" ), d->scale );
1481  shadowElem.setAttribute( QStringLiteral( "shadowColor" ), QgsSymbolLayerUtils::encodeColor( d->color ) );
1482  shadowElem.setAttribute( QStringLiteral( "shadowBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
1483  return shadowElem;
1484 }
1485 
1487 {
1488  if ( properties.isActive( QgsPalLayerSettings::ShadowDraw ) )
1489  {
1490  context.expressionContext().setOriginalValueVariable( d->enabled );
1491  d->enabled = properties.valueAsBool( QgsPalLayerSettings::ShadowDraw, context.expressionContext(), d->enabled );
1492  }
1493 
1494  // data defined shadow under type?
1495  QVariant exprVal = properties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
1496  if ( exprVal.isValid() )
1497  {
1498  QString str = exprVal.toString().trimmed();
1499  if ( !str.isEmpty() )
1500  {
1501  d->shadowUnder = QgsTextRendererUtils::decodeShadowPlacementType( str );
1502  }
1503  }
1504 
1506  {
1507  context.expressionContext().setOriginalValueVariable( d->offsetAngle );
1508  d->offsetAngle = properties.valueAsInt( QgsPalLayerSettings::ShadowOffsetAngle, context.expressionContext(), d->offsetAngle );
1509  }
1510  if ( properties.isActive( QgsPalLayerSettings::ShadowOffsetDist ) )
1511  {
1512  context.expressionContext().setOriginalValueVariable( d->offsetDist );
1513  d->offsetDist = properties.valueAsDouble( QgsPalLayerSettings::ShadowOffsetDist, context.expressionContext(), d->offsetDist );
1514  }
1515 
1516  exprVal = properties.value( QgsPalLayerSettings::ShadowOffsetUnits, context.expressionContext() );
1517  if ( exprVal.isValid() )
1518  {
1519  QString units = exprVal.toString();
1520  if ( !units.isEmpty() )
1521  {
1522  bool ok;
1524  if ( ok )
1525  d->offsetUnits = res;
1526  }
1527  }
1528 
1529  if ( properties.isActive( QgsPalLayerSettings::ShadowRadius ) )
1530  {
1531  context.expressionContext().setOriginalValueVariable( d->radius );
1532  d->radius = properties.valueAsDouble( QgsPalLayerSettings::ShadowRadius, context.expressionContext(), d->radius );
1533  }
1534 
1535  exprVal = properties.value( QgsPalLayerSettings::ShadowRadiusUnits, context.expressionContext() );
1536  if ( exprVal.isValid() )
1537  {
1538  QString units = exprVal.toString();
1539  if ( !units.isEmpty() )
1540  {
1541  bool ok;
1543  if ( ok )
1544  d->radiusUnits = res;
1545  }
1546  }
1547 
1548  if ( properties.isActive( QgsPalLayerSettings::ShadowOpacity ) )
1549  {
1550  context.expressionContext().setOriginalValueVariable( d->opacity * 100 );
1551  d->opacity = properties.value( QgsPalLayerSettings::ShadowOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0;
1552  }
1553 
1554  if ( properties.isActive( QgsPalLayerSettings::ShadowScale ) )
1555  {
1556  context.expressionContext().setOriginalValueVariable( d->scale );
1557  d->scale = properties.valueAsInt( QgsPalLayerSettings::ShadowScale, context.expressionContext(), d->scale );
1558  }
1559 
1560  if ( properties.isActive( QgsPalLayerSettings::ShadowColor ) )
1561  {
1563  d->color = properties.valueAsColor( QgsPalLayerSettings::ShadowColor, context.expressionContext(), d->color );
1564  }
1565 
1566  if ( properties.isActive( QgsPalLayerSettings::ShadowBlendMode ) )
1567  {
1568  exprVal = properties.value( QgsPalLayerSettings::ShadowBlendMode, context.expressionContext() );
1569  QString blendstr = exprVal.toString().trimmed();
1570  if ( !blendstr.isEmpty() )
1571  d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
1572  }
1573 }
1574 
1575 //
1576 // QgsTextFormat
1577 //
1578 
1580 {
1581  d = new QgsTextSettingsPrivate();
1582 }
1583 
1585  : mBufferSettings( other.mBufferSettings )
1586  , mBackgroundSettings( other.mBackgroundSettings )
1587  , mShadowSettings( other.mShadowSettings )
1588  , mTextFontFamily( other.mTextFontFamily )
1589  , mTextFontFound( other.mTextFontFound )
1590  , d( other.d )
1591 {
1592 
1593 }
1594 
1596 {
1597  d = other.d;
1598  mBufferSettings = other.mBufferSettings;
1599  mBackgroundSettings = other.mBackgroundSettings;
1600  mShadowSettings = other.mShadowSettings;
1601  mTextFontFamily = other.mTextFontFamily;
1602  mTextFontFound = other.mTextFontFound;
1603  return *this;
1604 }
1605 
1607 {
1608 
1609 }
1610 
1611 QFont QgsTextFormat::font() const
1612 {
1613  return d->textFont;
1614 }
1615 
1616 QFont QgsTextFormat::scaledFont( const QgsRenderContext &context ) const
1617 {
1618  QFont font = d->textFont;
1619  int fontPixelSize = QgsTextRenderer::sizeToPixel( d->fontSize, context, d->fontSizeUnits,
1620  d->fontSizeMapUnitScale );
1621  font.setPixelSize( fontPixelSize );
1622  return font;
1623 }
1624 
1625 void QgsTextFormat::setFont( const QFont &font )
1626 {
1627  d->textFont = font;
1628 }
1629 
1631 {
1632  if ( !d->textNamedStyle.isEmpty() )
1633  return d->textNamedStyle;
1634 
1635  QFontDatabase db;
1636  return db.styleString( d->textFont );
1637 }
1638 
1639 void QgsTextFormat::setNamedStyle( const QString &style )
1640 {
1641  QgsFontUtils::updateFontViaStyle( d->textFont, style );
1642  d->textNamedStyle = style;
1643 }
1644 
1646 {
1647  return d->fontSizeUnits;
1648 }
1649 
1651 {
1652  d->fontSizeUnits = unit;
1653 }
1654 
1656 {
1657  return d->fontSizeMapUnitScale;
1658 }
1659 
1661 {
1662  d->fontSizeMapUnitScale = scale;
1663 }
1664 
1665 double QgsTextFormat::size() const
1666 {
1667  return d->fontSize;
1668 }
1669 
1671 {
1672  d->fontSize = size;
1673 }
1674 
1675 QColor QgsTextFormat::color() const
1676 {
1677  return d->textColor;
1678 }
1679 
1680 void QgsTextFormat::setColor( const QColor &color )
1681 {
1682  d->textColor = color;
1683 }
1684 
1686 {
1687  return d->opacity;
1688 }
1689 
1691 {
1692  d->opacity = opacity;
1693 }
1694 
1695 QPainter::CompositionMode QgsTextFormat::blendMode() const
1696 {
1697  return d->blendMode;
1698 }
1699 
1700 void QgsTextFormat::setBlendMode( QPainter::CompositionMode mode )
1701 {
1702  d->blendMode = mode;
1703 }
1704 
1706 {
1707  return d->multilineHeight;
1708 }
1709 
1710 void QgsTextFormat::setLineHeight( double height )
1711 {
1712  d->multilineHeight = height;
1713 }
1714 
1716 {
1717  return d->orientation;
1718 }
1719 
1721 {
1722  d->orientation = orientation;
1723 }
1724 
1726 {
1727  return d->previewBackgroundColor;
1728 }
1729 
1731 {
1732  d->previewBackgroundColor = color;
1733 }
1734 
1736 {
1737  QFont appFont = QApplication::font();
1738  mTextFontFamily = layer->customProperty( QStringLiteral( "labeling/fontFamily" ), QVariant( appFont.family() ) ).toString();
1739  QString fontFamily = mTextFontFamily;
1740  if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) )
1741  {
1742  // trigger to notify about font family substitution
1743  mTextFontFound = false;
1744 
1745  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
1746  // currently only defaults to matching algorithm for resolving [foundry], if a font of similar family is found (default for QFont)
1747 
1748  // for now, do not use matching algorithm for substitution if family not found, substitute default instead
1749  fontFamily = appFont.family();
1750  }
1751  else
1752  {
1753  mTextFontFound = true;
1754  }
1755 
1756  if ( !layer->customProperty( QStringLiteral( "labeling/fontSize" ) ).isValid() )
1757  {
1758  d->fontSize = appFont.pointSizeF();
1759  }
1760  else
1761  {
1762  d->fontSize = layer->customProperty( QStringLiteral( "labeling/fontSize" ) ).toDouble();
1763  }
1764 
1765  if ( layer->customProperty( QStringLiteral( "labeling/fontSizeUnit" ) ).toString().isEmpty() )
1766  {
1767  d->fontSizeUnits = layer->customProperty( QStringLiteral( "labeling/fontSizeInMapUnits" ), QVariant( false ) ).toBool() ?
1769  }
1770  else
1771  {
1772  bool ok = false;
1773  d->fontSizeUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( QStringLiteral( "labeling/fontSizeUnit" ) ).toString(), &ok );
1774  if ( !ok )
1775  d->fontSizeUnits = QgsUnitTypes::RenderPoints;
1776  }
1777  if ( layer->customProperty( QStringLiteral( "labeling/fontSizeMapUnitScale" ) ).toString().isEmpty() )
1778  {
1779  //fallback to older property
1780  double oldMin = layer->customProperty( QStringLiteral( "labeling/fontSizeMapUnitMinScale" ), 0.0 ).toDouble();
1781  d->fontSizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1782  double oldMax = layer->customProperty( QStringLiteral( "labeling/fontSizeMapUnitMaxScale" ), 0.0 ).toDouble();
1783  d->fontSizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1784  }
1785  else
1786  {
1787  d->fontSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/fontSizeMapUnitScale" ) ).toString() );
1788  }
1789  int fontWeight = layer->customProperty( QStringLiteral( "labeling/fontWeight" ) ).toInt();
1790  bool fontItalic = layer->customProperty( QStringLiteral( "labeling/fontItalic" ) ).toBool();
1791  d->textFont = QFont( fontFamily, d->fontSize, fontWeight, fontItalic );
1792  d->textNamedStyle = QgsFontUtils::translateNamedStyle( layer->customProperty( QStringLiteral( "labeling/namedStyle" ), QVariant( "" ) ).toString() );
1793  QgsFontUtils::updateFontViaStyle( d->textFont, d->textNamedStyle ); // must come after textFont.setPointSizeF()
1794  d->textFont.setCapitalization( static_cast< QFont::Capitalization >( layer->customProperty( QStringLiteral( "labeling/fontCapitals" ), QVariant( 0 ) ).toUInt() ) );
1795  d->textFont.setUnderline( layer->customProperty( QStringLiteral( "labeling/fontUnderline" ) ).toBool() );
1796  d->textFont.setStrikeOut( layer->customProperty( QStringLiteral( "labeling/fontStrikeout" ) ).toBool() );
1797  d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, layer->customProperty( QStringLiteral( "labeling/fontLetterSpacing" ), QVariant( 0.0 ) ).toDouble() );
1798  d->textFont.setWordSpacing( layer->customProperty( QStringLiteral( "labeling/fontWordSpacing" ), QVariant( 0.0 ) ).toDouble() );
1799  d->textColor = _readColor( layer, QStringLiteral( "labeling/textColor" ), Qt::black, false );
1800  if ( layer->customProperty( QStringLiteral( "labeling/textOpacity" ) ).toString().isEmpty() )
1801  {
1802  d->opacity = ( 1 - layer->customProperty( QStringLiteral( "labeling/textTransp" ) ).toInt() / 100.0 ); //0 -100
1803  }
1804  else
1805  {
1806  d->opacity = ( layer->customProperty( QStringLiteral( "labeling/textOpacity" ) ).toDouble() );
1807  }
1808  d->blendMode = QgsPainting::getCompositionMode(
1809  static_cast< QgsPainting::BlendMode >( layer->customProperty( QStringLiteral( "labeling/blendMode" ), QVariant( QgsPainting::BlendNormal ) ).toUInt() ) );
1810  d->multilineHeight = layer->customProperty( QStringLiteral( "labeling/multilineHeight" ), QVariant( 1.0 ) ).toDouble();
1811  d->previewBackgroundColor = _readColor( layer, QStringLiteral( "labeling/previewBkgrdColor" ), "#ffffff", false );
1812 
1813  mBufferSettings.readFromLayer( layer );
1814  mShadowSettings.readFromLayer( layer );
1815  mBackgroundSettings.readFromLayer( layer );
1816 }
1817 
1818 void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
1819 {
1820  QDomElement textStyleElem;
1821  if ( elem.nodeName() == QStringLiteral( "text-style" ) )
1822  textStyleElem = elem;
1823  else
1824  textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) );
1825  QFont appFont = QApplication::font();
1826  mTextFontFamily = textStyleElem.attribute( QStringLiteral( "fontFamily" ), appFont.family() );
1827  QString fontFamily = mTextFontFamily;
1828  if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) )
1829  {
1830  // trigger to notify user about font family substitution (signal emitted in QgsVectorLayer::prepareLabelingAndDiagrams)
1831  mTextFontFound = false;
1832 
1833  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
1834  // currently only defaults to matching algorithm for resolving [foundry], if a font of similar family is found (default for QFont)
1835 
1836  // for now, do not use matching algorithm for substitution if family not found, substitute default instead
1837  fontFamily = appFont.family();
1838  }
1839  else
1840  {
1841  mTextFontFound = true;
1842  }
1843 
1844  if ( textStyleElem.hasAttribute( QStringLiteral( "fontSize" ) ) )
1845  {
1846  d->fontSize = textStyleElem.attribute( QStringLiteral( "fontSize" ) ).toDouble();
1847  }
1848  else
1849  {
1850  d->fontSize = appFont.pointSizeF();
1851  }
1852 
1853  if ( !textStyleElem.hasAttribute( QStringLiteral( "fontSizeUnit" ) ) )
1854  {
1855  d->fontSizeUnits = textStyleElem.attribute( QStringLiteral( "fontSizeInMapUnits" ) ).toUInt() == 0 ? QgsUnitTypes::RenderPoints
1857  }
1858  else
1859  {
1860  d->fontSizeUnits = QgsUnitTypes::decodeRenderUnit( textStyleElem.attribute( QStringLiteral( "fontSizeUnit" ) ) );
1861  }
1862 
1863  if ( !textStyleElem.hasAttribute( QStringLiteral( "fontSizeMapUnitScale" ) ) )
1864  {
1865  //fallback to older property
1866  double oldMin = textStyleElem.attribute( QStringLiteral( "fontSizeMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1867  d->fontSizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1868  double oldMax = textStyleElem.attribute( QStringLiteral( "fontSizeMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1869  d->fontSizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1870  }
1871  else
1872  {
1873  d->fontSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( textStyleElem.attribute( QStringLiteral( "fontSizeMapUnitScale" ) ) );
1874  }
1875  int fontWeight = textStyleElem.attribute( QStringLiteral( "fontWeight" ) ).toInt();
1876  bool fontItalic = textStyleElem.attribute( QStringLiteral( "fontItalic" ) ).toInt();
1877  d->textFont = QFont( fontFamily, d->fontSize, fontWeight, fontItalic );
1878  d->textFont.setPointSizeF( d->fontSize ); //double precision needed because of map units
1879  d->textNamedStyle = QgsFontUtils::translateNamedStyle( textStyleElem.attribute( QStringLiteral( "namedStyle" ) ) );
1880  QgsFontUtils::updateFontViaStyle( d->textFont, d->textNamedStyle ); // must come after textFont.setPointSizeF()
1881  d->textFont.setCapitalization( static_cast< QFont::Capitalization >( textStyleElem.attribute( QStringLiteral( "fontCapitals" ), QStringLiteral( "0" ) ).toUInt() ) );
1882  d->textFont.setUnderline( textStyleElem.attribute( QStringLiteral( "fontUnderline" ) ).toInt() );
1883  d->textFont.setStrikeOut( textStyleElem.attribute( QStringLiteral( "fontStrikeout" ) ).toInt() );
1884  d->textFont.setKerning( textStyleElem.attribute( QStringLiteral( "fontKerning" ), QStringLiteral( "1" ) ).toInt() );
1885  d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, textStyleElem.attribute( QStringLiteral( "fontLetterSpacing" ), QStringLiteral( "0" ) ).toDouble() );
1886  d->textFont.setWordSpacing( textStyleElem.attribute( QStringLiteral( "fontWordSpacing" ), QStringLiteral( "0" ) ).toDouble() );
1887  d->textColor = QgsSymbolLayerUtils::decodeColor( textStyleElem.attribute( QStringLiteral( "textColor" ), QgsSymbolLayerUtils::encodeColor( Qt::black ) ) );
1888  if ( !textStyleElem.hasAttribute( QStringLiteral( "textOpacity" ) ) )
1889  {
1890  d->opacity = ( 1 - textStyleElem.attribute( QStringLiteral( "textTransp" ) ).toInt() / 100.0 ); //0 -100
1891  }
1892  else
1893  {
1894  d->opacity = ( textStyleElem.attribute( QStringLiteral( "textOpacity" ) ).toDouble() );
1895  }
1896  d->orientation = QgsTextRendererUtils::decodeTextOrientation( textStyleElem.attribute( QStringLiteral( "textOrientation" ) ) );
1897  d->previewBackgroundColor = QgsSymbolLayerUtils::decodeColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QgsSymbolLayerUtils::encodeColor( Qt::white ) ) );
1898 
1899  d->blendMode = QgsPainting::getCompositionMode(
1900  static_cast< QgsPainting::BlendMode >( textStyleElem.attribute( QStringLiteral( "blendMode" ), QString::number( QgsPainting::BlendNormal ) ).toUInt() ) );
1901 
1902  if ( !textStyleElem.hasAttribute( QStringLiteral( "multilineHeight" ) ) )
1903  {
1904  QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
1905  d->multilineHeight = textFormatElem.attribute( QStringLiteral( "multilineHeight" ), QStringLiteral( "1" ) ).toDouble();
1906  }
1907  else
1908  {
1909  d->multilineHeight = textStyleElem.attribute( QStringLiteral( "multilineHeight" ), QStringLiteral( "1" ) ).toDouble();
1910  }
1911 
1912  if ( textStyleElem.firstChildElement( QStringLiteral( "text-buffer" ) ).isNull() )
1913  {
1914  mBufferSettings.readXml( elem );
1915  }
1916  else
1917  {
1918  mBufferSettings.readXml( textStyleElem );
1919  }
1920  if ( textStyleElem.firstChildElement( QStringLiteral( "shadow" ) ).isNull() )
1921  {
1922  mShadowSettings.readXml( elem );
1923  }
1924  else
1925  {
1926  mShadowSettings.readXml( textStyleElem );
1927  }
1928  if ( textStyleElem.firstChildElement( QStringLiteral( "background" ) ).isNull() )
1929  {
1930  mBackgroundSettings.readXml( elem, context );
1931  }
1932  else
1933  {
1934  mBackgroundSettings.readXml( textStyleElem, context );
1935  }
1936 
1937  QDomElement ddElem = textStyleElem.firstChildElement( QStringLiteral( "dd_properties" ) );
1938  if ( ddElem.isNull() )
1939  {
1940  ddElem = elem.firstChildElement( QStringLiteral( "dd_properties" ) );
1941  }
1942  if ( !ddElem.isNull() )
1943  {
1944  d->mDataDefinedProperties.readXml( ddElem, QgsPalLayerSettings::propertyDefinitions() );
1945  }
1946  else
1947  {
1948  d->mDataDefinedProperties.clear();
1949  }
1950 }
1951 
1952 QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
1953 {
1954  // text style
1955  QDomElement textStyleElem = doc.createElement( QStringLiteral( "text-style" ) );
1956  textStyleElem.setAttribute( QStringLiteral( "fontFamily" ), d->textFont.family() );
1957  textStyleElem.setAttribute( QStringLiteral( "namedStyle" ), QgsFontUtils::untranslateNamedStyle( d->textNamedStyle ) );
1958  textStyleElem.setAttribute( QStringLiteral( "fontSize" ), d->fontSize );
1959  textStyleElem.setAttribute( QStringLiteral( "fontSizeUnit" ), QgsUnitTypes::encodeUnit( d->fontSizeUnits ) );
1960  textStyleElem.setAttribute( QStringLiteral( "fontSizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( d->fontSizeMapUnitScale ) );
1961  textStyleElem.setAttribute( QStringLiteral( "fontWeight" ), d->textFont.weight() );
1962  textStyleElem.setAttribute( QStringLiteral( "fontItalic" ), d->textFont.italic() );
1963  textStyleElem.setAttribute( QStringLiteral( "fontStrikeout" ), d->textFont.strikeOut() );
1964  textStyleElem.setAttribute( QStringLiteral( "fontUnderline" ), d->textFont.underline() );
1965  textStyleElem.setAttribute( QStringLiteral( "textColor" ), QgsSymbolLayerUtils::encodeColor( d->textColor ) );
1966  textStyleElem.setAttribute( QStringLiteral( "previewBkgrdColor" ), QgsSymbolLayerUtils::encodeColor( d->previewBackgroundColor ) );
1967  textStyleElem.setAttribute( QStringLiteral( "fontCapitals" ), static_cast< unsigned int >( d->textFont.capitalization() ) );
1968  textStyleElem.setAttribute( QStringLiteral( "fontLetterSpacing" ), d->textFont.letterSpacing() );
1969  textStyleElem.setAttribute( QStringLiteral( "fontWordSpacing" ), d->textFont.wordSpacing() );
1970  textStyleElem.setAttribute( QStringLiteral( "fontKerning" ), d->textFont.kerning() );
1971  textStyleElem.setAttribute( QStringLiteral( "textOpacity" ), d->opacity );
1972  textStyleElem.setAttribute( QStringLiteral( "textOrientation" ), QgsTextRendererUtils::encodeTextOrientation( d->orientation ) );
1973  textStyleElem.setAttribute( QStringLiteral( "blendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
1974  textStyleElem.setAttribute( QStringLiteral( "multilineHeight" ), d->multilineHeight );
1975 
1976  QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) );
1977  d->mDataDefinedProperties.writeXml( ddElem, QgsPalLayerSettings::propertyDefinitions() );
1978 
1979  textStyleElem.appendChild( mBufferSettings.writeXml( doc ) );
1980  textStyleElem.appendChild( mBackgroundSettings.writeXml( doc, context ) );
1981  textStyleElem.appendChild( mShadowSettings.writeXml( doc ) );
1982  textStyleElem.appendChild( ddElem );
1983 
1984  return textStyleElem;
1985 }
1986 
1987 QMimeData *QgsTextFormat::toMimeData() const
1988 {
1989  //set both the mime color data, and the text (format settings).
1990  QMimeData *mimeData = new QMimeData;
1991  mimeData->setColorData( QVariant( color() ) );
1992 
1993  QgsReadWriteContext rwContext;
1994  QDomDocument textDoc;
1995  QDomElement textElem = writeXml( textDoc, rwContext );
1996  textDoc.appendChild( textElem );
1997  mimeData->setText( textDoc.toString() );
1998 
1999  return mimeData;
2000 }
2001 
2003 {
2004  QgsTextFormat format;
2005  format.setFont( font );
2006  if ( font.pointSizeF() > 0 )
2007  {
2008  format.setSize( font.pointSizeF() );
2010  }
2011  else if ( font.pixelSize() > 0 )
2012  {
2013  format.setSize( font.pixelSize() );
2015  }
2016 
2017  return format;
2018 }
2019 
2021 {
2022  QFont f = font();
2023  switch ( sizeUnit() )
2024  {
2026  f.setPointSizeF( size() );
2027  break;
2028 
2030  f.setPointSizeF( size() * 2.83464567 );
2031  break;
2032 
2034  f.setPointSizeF( size() * 72 );
2035  break;
2036 
2038  f.setPixelSize( static_cast< int >( std::round( size() ) ) );
2039  break;
2040 
2045  // no meaning here
2046  break;
2047  }
2048  return f;
2049 }
2050 
2051 QgsTextFormat QgsTextFormat::fromMimeData( const QMimeData *data, bool *ok )
2052 {
2053  if ( ok )
2054  *ok = false;
2055  QgsTextFormat format;
2056  if ( !data )
2057  return format;
2058 
2059  QString text = data->text();
2060  if ( !text.isEmpty() )
2061  {
2062  QDomDocument doc;
2063  QDomElement elem;
2064  QgsReadWriteContext rwContext;
2065 
2066  if ( doc.setContent( text ) )
2067  {
2068  elem = doc.documentElement();
2069 
2070  format.readXml( elem, rwContext );
2071  if ( ok )
2072  *ok = true;
2073  return format;
2074  }
2075  }
2076  return format;
2077 }
2078 
2080 {
2081  if ( d->blendMode != QPainter::CompositionMode_SourceOver )
2082  return true;
2083 
2084  if ( mBufferSettings.enabled() && mBufferSettings.blendMode() != QPainter::CompositionMode_SourceOver )
2085  return true;
2086 
2087  if ( mBackgroundSettings.enabled() && mBackgroundSettings.blendMode() != QPainter::CompositionMode_SourceOver )
2088  return true;
2089 
2090  if ( mShadowSettings.enabled() && mShadowSettings.blendMode() != QPainter::CompositionMode_SourceOver )
2091  return true;
2092 
2093  return false;
2094 }
2095 
2097 {
2098  return d->mDataDefinedProperties;
2099 }
2100 
2102 {
2103  return d->mDataDefinedProperties;
2104 }
2105 
2107 {
2108  d->mDataDefinedProperties = collection;
2109 }
2110 
2112 {
2113  if ( !d->mDataDefinedProperties.hasActiveProperties() )
2114  return;
2115 
2116  QString ddFontFamily;
2117  context.expressionContext().setOriginalValueVariable( d->textFont.family() );
2118  QVariant exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
2119  if ( exprVal.isValid() )
2120  {
2121  QString family = exprVal.toString().trimmed();
2122  if ( d->textFont.family() != family )
2123  {
2124  // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
2125  // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
2126  if ( QgsFontUtils::fontFamilyOnSystem( family ) )
2127  {
2128  ddFontFamily = family;
2129  }
2130  }
2131  }
2132 
2133  // data defined named font style?
2134  QString ddFontStyle;
2135  context.expressionContext().setOriginalValueVariable( d->textNamedStyle );
2136  exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
2137  if ( exprVal.isValid() )
2138  {
2139  QString fontstyle = exprVal.toString().trimmed();
2140  ddFontStyle = fontstyle;
2141  }
2142 
2143  bool ddBold = false;
2144  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Bold ) )
2145  {
2146  context.expressionContext().setOriginalValueVariable( d->textFont.bold() );
2147  ddBold = d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false ) ;
2148  }
2149 
2150  bool ddItalic = false;
2151  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Italic ) )
2152  {
2153  context.expressionContext().setOriginalValueVariable( d->textFont.italic() );
2154  ddItalic = d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false );
2155  }
2156 
2157  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
2158  // (currently defaults to what has been read in from layer settings)
2159  QFont newFont;
2160  QFontDatabase fontDb;
2161  QFont appFont = QApplication::font();
2162  bool newFontBuilt = false;
2163  if ( ddBold || ddItalic )
2164  {
2165  // new font needs built, since existing style needs removed
2166  newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : d->textFont.family() );
2167  newFontBuilt = true;
2168  newFont.setBold( ddBold );
2169  newFont.setItalic( ddItalic );
2170  }
2171  else if ( !ddFontStyle.isEmpty()
2172  && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
2173  {
2174  if ( !ddFontFamily.isEmpty() )
2175  {
2176  // both family and style are different, build font from database
2177  QFont styledfont = fontDb.font( ddFontFamily, ddFontStyle, appFont.pointSize() );
2178  if ( appFont != styledfont )
2179  {
2180  newFont = styledfont;
2181  newFontBuilt = true;
2182  }
2183  }
2184 
2185  // update the font face style
2186  QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : d->textFont, ddFontStyle );
2187  }
2188  else if ( !ddFontFamily.isEmpty() )
2189  {
2190  if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
2191  {
2192  // just family is different, build font from database
2193  QFont styledfont = fontDb.font( ddFontFamily, d->textNamedStyle, appFont.pointSize() );
2194  if ( appFont != styledfont )
2195  {
2196  newFont = styledfont;
2197  newFontBuilt = true;
2198  }
2199  }
2200  else
2201  {
2202  newFont = QFont( ddFontFamily );
2203  newFontBuilt = true;
2204  }
2205  }
2206 
2207  if ( newFontBuilt )
2208  {
2209  // copy over existing font settings
2210  newFont.setUnderline( d->textFont.underline() );
2211  newFont.setStrikeOut( d->textFont.strikeOut() );
2212  newFont.setWordSpacing( d->textFont.wordSpacing() );
2213  newFont.setLetterSpacing( QFont::AbsoluteSpacing, d->textFont.letterSpacing() );
2214  d->textFont = newFont;
2215  }
2216 
2217  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) )
2218  {
2219  context.expressionContext().setOriginalValueVariable( d->textFont.underline() );
2220  d->textFont.setUnderline( d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), d->textFont.underline() ) );
2221  }
2222 
2223  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) )
2224  {
2225  context.expressionContext().setOriginalValueVariable( d->textFont.strikeOut() );
2226  d->textFont.setStrikeOut( d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), d->textFont.strikeOut() ) );
2227  }
2228 
2229  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Color ) )
2230  {
2232  d->textColor = d->mDataDefinedProperties.valueAsColor( QgsPalLayerSettings::Color, context.expressionContext(), d->textColor );
2233  }
2234 
2235  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Size ) )
2236  {
2238  d->fontSize = d->mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), d->fontSize );
2239  }
2240 
2241  exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() );
2242  if ( exprVal.isValid() )
2243  {
2244  QString units = exprVal.toString();
2245  if ( !units.isEmpty() )
2246  {
2247  bool ok;
2249  if ( ok )
2250  d->fontSizeUnits = res;
2251  }
2252  }
2253 
2254  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontOpacity ) )
2255  {
2256  context.expressionContext().setOriginalValueVariable( d->opacity * 100 );
2257  d->opacity = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontOpacity, context.expressionContext(), d->opacity * 100 ).toDouble() / 100.0;
2258  }
2259 
2260  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
2261  {
2262  const QString encoded = QgsTextRendererUtils::encodeTextOrientation( d->orientation );
2263  context.expressionContext().setOriginalValueVariable( encoded );
2264  d->orientation = QgsTextRendererUtils::decodeTextOrientation( d->mDataDefinedProperties.value( QgsPalLayerSettings::TextOrientation, context.expressionContext(), encoded ).toString() );
2265  }
2266 
2267  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontLetterSpacing ) )
2268  {
2269  context.expressionContext().setOriginalValueVariable( d->textFont.letterSpacing() );
2270  d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, d->mDataDefinedProperties.value( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), d->textFont.letterSpacing() ).toDouble() );
2271  }
2272 
2273  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontWordSpacing ) )
2274  {
2275  context.expressionContext().setOriginalValueVariable( d->textFont.wordSpacing() );
2276  d->textFont.setWordSpacing( d->mDataDefinedProperties.value( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), d->textFont.wordSpacing() ).toDouble() );
2277  }
2278 
2279  if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontBlendMode ) )
2280  {
2281  exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontBlendMode, context.expressionContext() );
2282  QString blendstr = exprVal.toString().trimmed();
2283  if ( !blendstr.isEmpty() )
2284  d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
2285  }
2286 
2287  mShadowSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
2288  mBackgroundSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
2289  mBufferSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
2290 }
2291 
2292 QPixmap QgsTextFormat::textFormatPreviewPixmap( const QgsTextFormat &format, QSize size, const QString &previewText, int padding )
2293 {
2294  QgsTextFormat tempFormat = format;
2295  QPixmap pixmap( size );
2296  pixmap.fill( Qt::transparent );
2297  QPainter painter;
2298  painter.begin( &pixmap );
2299 
2300  painter.setRenderHint( QPainter::Antialiasing );
2301 
2302  QRect rect( 0, 0, size.width(), size.height() );
2303 
2304  // shameless eye candy - use a subtle gradient when drawing background
2305  painter.setPen( Qt::NoPen );
2306  QColor background1 = tempFormat.previewBackgroundColor();
2307  if ( ( background1.lightnessF() < 0.7 ) )
2308  {
2309  background1 = background1.darker( 125 );
2310  }
2311  else
2312  {
2313  background1 = background1.lighter( 125 );
2314  }
2315  QColor background2 = tempFormat.previewBackgroundColor();
2316  QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
2317  linearGrad.setColorAt( 0, background1 );
2318  linearGrad.setColorAt( 1, background2 );
2319  painter.setBrush( QBrush( linearGrad ) );
2320  if ( size.width() > 30 )
2321  {
2322  painter.drawRoundedRect( rect, 6, 6 );
2323  }
2324  else
2325  {
2326  // don't use rounded rect for small previews
2327  painter.drawRect( rect );
2328  }
2329  painter.setBrush( Qt::NoBrush );
2330  painter.setPen( Qt::NoPen );
2331  padding += 1; // move text away from background border
2332 
2333  QgsRenderContext context;
2334  QgsMapToPixel newCoordXForm;
2335  newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
2336  context.setMapToPixel( newCoordXForm );
2337 
2338  context.setScaleFactor( QgsApplication::desktop()->logicalDpiX() / 25.4 );
2339  context.setUseAdvancedEffects( true );
2340  context.setPainter( &painter );
2341 
2342  // slightly inset text to account for buffer/background
2343  double xtrans = 0;
2344  if ( tempFormat.buffer().enabled() )
2345  xtrans = context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
2346  if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
2347  xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
2348 
2349  double ytrans = 0.0;
2350  if ( tempFormat.buffer().enabled() )
2351  ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
2352  if ( tempFormat.background().enabled() )
2353  ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
2354 
2355  const QStringList text = QStringList() << ( previewText.isEmpty() ? QObject::tr( "Aa" ) : previewText );
2356  const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, QgsTextRenderer::Rect );
2357  QRectF textRect = rect;
2358  textRect.setLeft( xtrans + padding );
2359  textRect.setWidth( rect.width() - xtrans - 2 * padding );
2360 
2361  if ( textRect.width() > 2000 )
2362  textRect.setWidth( 2000 - 2 * padding );
2363 
2364  const double bottom = textRect.height() / 2 + textHeight / 2;
2365  textRect.setTop( bottom - textHeight );
2366  textRect.setBottom( bottom );
2367 
2368  QgsTextRenderer::drawText( textRect, 0, QgsTextRenderer::AlignCenter, text, context, tempFormat );
2369 
2370  // draw border on top of text
2371  painter.setBrush( Qt::NoBrush );
2372  painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
2373  if ( size.width() > 30 )
2374  {
2375  painter.drawRoundedRect( rect, 6, 6 );
2376  }
2377  else
2378  {
2379  // don't use rounded rect for small previews
2380  painter.drawRect( rect );
2381  }
2382  painter.end();
2383  return pixmap;
2384 }
2385 
2386 
2388 {
2389  return static_cast< int >( c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 ); //NOLINT
2390 }
2391 
2392 void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool )
2393 {
2394  QgsTextFormat tmpFormat = format;
2395  if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
2396  tmpFormat.updateDataDefinedProperties( context );
2397  tmpFormat = updateShadowPosition( tmpFormat );
2398 
2399  if ( tmpFormat.background().enabled() )
2400  {
2401  drawPart( rect, rotation, alignment, textLines, context, tmpFormat, Background );
2402  }
2403 
2404  if ( tmpFormat.buffer().enabled() )
2405  {
2406  drawPart( rect, rotation, alignment, textLines, context, tmpFormat, Buffer );
2407  }
2408 
2409  drawPart( rect, rotation, alignment, textLines, context, tmpFormat, Text );
2410 }
2411 
2412 void QgsTextRenderer::drawText( QPointF point, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool )
2413 {
2414  QgsTextFormat tmpFormat = format;
2415  if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
2416  tmpFormat.updateDataDefinedProperties( context );
2417  tmpFormat = updateShadowPosition( tmpFormat );
2418 
2419  if ( tmpFormat.background().enabled() )
2420  {
2421  drawPart( point, rotation, alignment, textLines, context, tmpFormat, Background );
2422  }
2423 
2424  if ( tmpFormat.buffer().enabled() )
2425  {
2426  drawPart( point, rotation, alignment, textLines, context, tmpFormat, Buffer );
2427  }
2428 
2429  drawPart( point, rotation, alignment, textLines, context, tmpFormat, Text );
2430 }
2431 
2432 QgsTextFormat QgsTextRenderer::updateShadowPosition( const QgsTextFormat &format )
2433 {
2434  if ( !format.shadow().enabled() || format.shadow().shadowPlacement() != QgsTextShadowSettings::ShadowLowest )
2435  return format;
2436 
2437  QgsTextFormat tmpFormat = format;
2438  if ( tmpFormat.background().enabled() && tmpFormat.background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadow not compatible with marker symbol backgrounds
2439  {
2441  }
2442  else if ( tmpFormat.buffer().enabled() )
2443  {
2445  }
2446  else
2447  {
2449  }
2450  return tmpFormat;
2451 }
2452 
2453 void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, HAlignment alignment,
2454  const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
2455 {
2456  if ( !context.painter() )
2457  {
2458  return;
2459  }
2460 
2461  Component component;
2462  component.dpiRatio = 1.0;
2463  component.origin = rect.topLeft();
2464  component.rotation = rotation;
2465  component.size = rect.size();
2466  component.hAlign = alignment;
2467 
2468  switch ( part )
2469  {
2470  case Background:
2471  {
2472  if ( !format.background().enabled() )
2473  return;
2474 
2475  if ( !qgsDoubleNear( rotation, 0.0 ) )
2476  {
2477  // get rotated label's center point
2478 
2479  double xc = rect.width() / 2.0;
2480  double yc = rect.height() / 2.0;
2481 
2482  double angle = -rotation;
2483  double xd = xc * std::cos( angle ) - yc * std::sin( angle );
2484  double yd = xc * std::sin( angle ) + yc * std::cos( angle );
2485 
2486  component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd );
2487  }
2488  else
2489  {
2490  component.center = rect.center();
2491  }
2492 
2493  QgsTextRenderer::drawBackground( context, component, format, textLines, Rect );
2494 
2495  break;
2496  }
2497 
2498  case Buffer:
2499  {
2500  if ( !format.buffer().enabled() )
2501  break;
2502  }
2503  FALLTHROUGH
2504  case Text:
2505  case Shadow:
2506  {
2507  QFontMetricsF fm( format.scaledFont( context ) );
2508  drawTextInternal( part, context, format, component,
2509  textLines,
2510  &fm,
2511  alignment );
2512  break;
2513  }
2514  }
2515 }
2516 
2517 void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
2518 {
2519  if ( !context.painter() )
2520  {
2521  return;
2522  }
2523 
2524  Component component;
2525  component.dpiRatio = 1.0;
2526  component.origin = origin;
2527  component.rotation = rotation;
2528  component.hAlign = alignment;
2529 
2530  switch ( part )
2531  {
2532  case Background:
2533  {
2534  if ( !format.background().enabled() )
2535  return;
2536 
2537  QgsTextRenderer::drawBackground( context, component, format, textLines, Point );
2538  break;
2539  }
2540 
2541  case Buffer:
2542  {
2543  if ( !format.buffer().enabled() )
2544  break;
2545  }
2546  FALLTHROUGH
2547  case Text:
2548  case Shadow:
2549  {
2550  QFontMetricsF fm( format.scaledFont( context ) );
2551  drawTextInternal( part, context, format, component,
2552  textLines,
2553  &fm,
2554  alignment,
2555  Point );
2556  break;
2557  }
2558  }
2559 }
2560 
2561 QFontMetricsF QgsTextRenderer::fontMetrics( QgsRenderContext &context, const QgsTextFormat &format )
2562 {
2563  return QFontMetricsF( format.scaledFont( context ), context.painter() ? context.painter()->device() : nullptr );
2564 }
2565 
2566 void QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format, const QFontMetricsF *fontMetrics )
2567 {
2568  QPainter *p = context.painter();
2569 
2572  {
2573  if ( component.rotation >= -315 && component.rotation < -90 )
2574  {
2575  orientation = QgsTextFormat::VerticalOrientation;
2576  }
2577  else if ( component.rotation >= -90 && component.rotation < -45 )
2578  {
2579  orientation = QgsTextFormat::VerticalOrientation;
2580  }
2581  else
2582  {
2584  }
2585  }
2586 
2587  QgsTextBufferSettings buffer = format.buffer();
2588 
2589  double penSize = context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() );
2590 
2591  QPainterPath path;
2592  path.setFillRule( Qt::WindingFill );
2593  switch ( orientation )
2594  {
2596  path.addText( 0, 0, format.scaledFont( context ), component.text );
2597  break;
2600  double letterSpacing = format.scaledFont( context ).letterSpacing();
2601  double labelWidth = fontMetrics->maxWidth();
2602  const QStringList parts = QgsPalLabeling::splitToGraphemes( component.text );
2603  double partYOffset = 0.0;
2604  for ( const auto &part : parts )
2605  {
2606  double partXOffset = ( labelWidth - ( fontMetrics->width( part ) - letterSpacing ) ) / 2;
2607  path.addText( partXOffset, partYOffset, format.scaledFont( context ), part );
2608  partYOffset += fontMetrics->ascent() + letterSpacing;
2609  }
2610  }
2611 
2612  QColor bufferColor = buffer.color();
2613  bufferColor.setAlphaF( buffer.opacity() );
2614  QPen pen( bufferColor );
2615  pen.setWidthF( penSize );
2616  pen.setJoinStyle( buffer.joinStyle() );
2617  QColor tmpColor( bufferColor );
2618  // honor pref for whether to fill buffer interior
2619  if ( !buffer.fillBufferInterior() )
2620  {
2621  tmpColor.setAlpha( 0 );
2622  }
2623 
2624  // store buffer's drawing in QPicture for drop shadow call
2625  QPicture buffPict;
2626  QPainter buffp;
2627  buffp.begin( &buffPict );
2628 
2629  if ( buffer.paintEffect() && buffer.paintEffect()->enabled() )
2630  {
2631  context.setPainter( &buffp );
2632 
2633  buffer.paintEffect()->begin( context );
2634  context.painter()->setPen( pen );
2635  context.painter()->setBrush( tmpColor );
2636  context.painter()->drawPath( path );
2637  buffer.paintEffect()->end( context );
2638 
2639  context.setPainter( p );
2640  }
2641  else
2642  {
2643  buffp.setPen( pen );
2644  buffp.setBrush( tmpColor );
2645  buffp.drawPath( path );
2646  }
2647  buffp.end();
2648 
2649  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer )
2650  {
2651  QgsTextRenderer::Component bufferComponent = component;
2652  bufferComponent.origin = QPointF( 0.0, 0.0 );
2653  bufferComponent.picture = buffPict;
2654  bufferComponent.pictureBuffer = penSize / 2.0;
2655  drawShadow( context, bufferComponent, format );
2656  }
2657  p->save();
2658  if ( context.useAdvancedEffects() )
2659  {
2660  p->setCompositionMode( buffer.blendMode() );
2661  }
2662  if ( context.flags() & QgsRenderContext::Antialiasing )
2663  {
2664  p->setRenderHint( QPainter::Antialiasing );
2665  }
2666 
2667  // scale for any print output or image saving @ specific dpi
2668  p->scale( component.dpiRatio, component.dpiRatio );
2669  _fixQPictureDPI( p );
2670  p->drawPicture( 0, 0, buffPict );
2671  p->restore();
2672 }
2673 
2674 double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics )
2675 {
2676  //calculate max width of text lines
2677  std::unique_ptr< QFontMetricsF > newFm;
2678  if ( !fontMetrics )
2679  {
2680  newFm.reset( new QFontMetricsF( format.scaledFont( context ) ) );
2681  fontMetrics = newFm.get();
2682  }
2683 
2684  double width = 0;
2685  switch ( format.orientation() )
2686  {
2688  {
2689  double maxLineWidth = 0;
2690  const auto constTextLines = textLines;
2691  for ( const QString &line : constTextLines )
2692  {
2693  maxLineWidth = std::max( maxLineWidth, fontMetrics->width( line ) );
2694  }
2695  width = maxLineWidth;
2696  break;
2697  }
2698 
2700  {
2701  double labelWidth = fontMetrics->maxWidth();
2702  width = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.lineHeight();
2703  break;
2704  }
2705 
2707  {
2708  // label mode only
2709  break;
2710  }
2711  }
2712 
2713  return width;
2714 }
2715 
2716 double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF *fontMetrics )
2717 {
2718  //calculate max width of text lines
2719  std::unique_ptr< QFontMetricsF > newFm;
2720  if ( !fontMetrics )
2721  {
2722  newFm.reset( new QFontMetricsF( format.scaledFont( context ) ) );
2723  fontMetrics = newFm.get();
2724  }
2725 
2726  switch ( format.orientation() )
2727  {
2729  {
2730  double labelHeight = fontMetrics->ascent() + fontMetrics->descent(); // ignore +1 for baseline
2731  switch ( mode )
2732  {
2733  case Label:
2734  // rendering labels needs special handling - in this case text should be
2735  // drawn with the bottom left corner coinciding with origin, vs top left
2736  // for standard text rendering. Line height is also slightly different.
2737  return labelHeight + ( textLines.size() - 1 ) * labelHeight * format.lineHeight();
2738 
2739  case Rect:
2740  case Point:
2741  // standard rendering - designed to exactly replicate QPainter's drawText method
2742  return labelHeight + ( textLines.size() - 1 ) * fontMetrics->lineSpacing() * format.lineHeight();
2743  }
2744  break;
2745  }
2746 
2748  {
2749  double labelHeight = fontMetrics->ascent();
2750  double letterSpacing = format.scaledFont( context ).letterSpacing();
2751  int maxLineLength = 0;
2752  for ( const auto &line : textLines )
2753  {
2754  if ( line.length() > maxLineLength )
2755  maxLineLength = line.length();
2756  }
2757  return labelHeight * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
2758  break;
2759  }
2760 
2762  {
2763  // label mode only
2764  break;
2765  }
2766  }
2767 
2768  return 0;
2769 }
2770 
2771 void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer::Component component, const QgsTextFormat &format,
2772  const QStringList &textLines, DrawMode mode )
2773 {
2775 
2776  QPainter *prevP = context.painter();
2777  QPainter *p = context.painter();
2778  if ( background.paintEffect() && background.paintEffect()->enabled() )
2779  {
2780  background.paintEffect()->begin( context );
2781  p = context.painter();
2782  }
2783 
2784  //QgsDebugMsgLevel( QStringLiteral( "Background label rotation: %1" ).arg( component.rotation() ), 4 );
2785 
2786  // shared calculations between shapes and SVG
2787 
2788  // configure angles, set component rotation and rotationOffset
2790  {
2791  component.rotation = -( component.rotation * 180 / M_PI ); // RotationSync
2792  component.rotationOffset =
2793  background.rotationType() == QgsTextBackgroundSettings::RotationOffset ? background.rotation() : 0.0;
2794  }
2795  else // RotationFixed
2796  {
2797  component.rotation = 0.0; // don't use label's rotation
2798  component.rotationOffset = background.rotation();
2799  }
2800 
2801  if ( mode != Label )
2802  {
2803  // need to calculate size of text
2804  QFontMetricsF fm( format.scaledFont( context ) );
2805  double width = textWidth( context, format, textLines, &fm );
2806  double height = textHeight( context, format, textLines, mode, &fm );
2807 
2808  switch ( mode )
2809  {
2810  case Rect:
2811  switch ( component.hAlign )
2812  {
2813  case AlignLeft:
2814  component.center = QPointF( component.origin.x() + width / 2.0,
2815  component.origin.y() + height / 2.0 );
2816  break;
2817 
2818  case AlignCenter:
2819  component.center = QPointF( component.origin.x() + component.size.width() / 2.0,
2820  component.origin.y() + height / 2.0 );
2821  break;
2822 
2823  case AlignRight:
2824  component.center = QPointF( component.origin.x() + component.size.width() - width / 2.0,
2825  component.origin.y() + height / 2.0 );
2826  break;
2827  }
2828  break;
2829 
2830  case Point:
2831  {
2832  double originAdjust = fm.ascent() / 2.0 - fm.leading() / 2.0;
2833  switch ( component.hAlign )
2834  {
2835  case AlignLeft:
2836  component.center = QPointF( component.origin.x() + width / 2.0,
2837  component.origin.y() - height / 2.0 + originAdjust );
2838  break;
2839 
2840  case AlignCenter:
2841  component.center = QPointF( component.origin.x(),
2842  component.origin.y() - height / 2.0 + originAdjust );
2843  break;
2844 
2845  case AlignRight:
2846  component.center = QPointF( component.origin.x() - width / 2.0,
2847  component.origin.y() - height / 2.0 + originAdjust );
2848  break;
2849  }
2850  break;
2851  }
2852 
2853  case Label:
2854  break;
2855  }
2856 
2858  component.size = QSizeF( width, height );
2859  }
2860 
2861  // TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
2862 
2863  switch ( background.type() )
2864  {
2867  {
2868  // all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
2869 
2870  if ( background.type() == QgsTextBackgroundSettings::ShapeSVG && background.svgFile().isEmpty() )
2871  return;
2872 
2873  if ( background.type() == QgsTextBackgroundSettings::ShapeMarkerSymbol && !background.markerSymbol() )
2874  return;
2875 
2876  double sizeOut = 0.0;
2877  // only one size used for SVG/marker symbol sizing/scaling (no use of shapeSize.y() or Y field in gui)
2878  if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
2879  {
2880  sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
2881  }
2882  else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
2883  {
2884  sizeOut = std::max( component.size.width(), component.size.height() );
2885  double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
2886 
2887  // add buffer
2888  sizeOut += bufferSize * 2;
2889  }
2890 
2891  // don't bother rendering symbols smaller than 1x1 pixels in size
2892  // TODO: add option to not show any svgs under/over a certain size
2893  if ( sizeOut < 1.0 )
2894  return;
2895 
2896  std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
2897  if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
2898  {
2899  QgsStringMap map; // for SVG symbology marker
2900  map[QStringLiteral( "name" )] = background.svgFile().trimmed();
2901  map[QStringLiteral( "size" )] = QString::number( sizeOut );
2902  map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
2903  map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
2904 
2905  // offset is handled by this local painter
2906  // TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
2907  //map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
2908  //map["offset_unit"] = QgsUnitTypes::encodeUnit(
2909  // tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
2910 
2911  map[QStringLiteral( "fill" )] = background.fillColor().name();
2912  map[QStringLiteral( "outline" )] = background.strokeColor().name();
2913  map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
2914  map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
2915 
2916  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
2917  {
2918  QgsTextShadowSettings shadow = format.shadow();
2919  // configure SVG shadow specs
2920  QgsStringMap shdwmap( map );
2921  shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
2922  shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
2923  shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
2924 
2925  // store SVG's drawing in QPicture for drop shadow call
2926  QPicture svgPict;
2927  QPainter svgp;
2928  svgp.begin( &svgPict );
2929 
2930  // draw shadow symbol
2931 
2932  // clone current render context map unit/mm conversion factors, but not
2933  // other map canvas parameters, then substitute this painter for use in symbology painting
2934  // NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
2935  // but will be created relative to the SVG's computed size, not the current map canvas
2936  QgsRenderContext shdwContext;
2937  shdwContext.setMapToPixel( context.mapToPixel() );
2938  shdwContext.setScaleFactor( context.scaleFactor() );
2939  shdwContext.setPainter( &svgp );
2940 
2941  QgsSymbolLayer *symShdwL = QgsSvgMarkerSymbolLayer::create( shdwmap );
2942  QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL );
2943  QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
2944 
2945  svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
2946  svgp.end();
2947 
2948  component.picture = svgPict;
2949  // TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
2950  component.pictureBuffer = 0.0;
2951 
2952  component.size = QSizeF( sizeOut, sizeOut );
2953  component.offset = QPointF( 0.0, 0.0 );
2954 
2955  // rotate about origin center of SVG
2956  p->save();
2957  p->translate( component.center.x(), component.center.y() );
2958  p->rotate( component.rotation );
2959  double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
2960  double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
2961  p->translate( QPointF( xoff, yoff ) );
2962  p->rotate( component.rotationOffset );
2963  p->translate( -sizeOut / 2, sizeOut / 2 );
2964  if ( context.flags() & QgsRenderContext::Antialiasing )
2965  {
2966  p->setRenderHint( QPainter::Antialiasing );
2967  }
2968 
2969  drawShadow( context, component, format );
2970  p->restore();
2971 
2972  delete svgShdwM;
2973  svgShdwM = nullptr;
2974  }
2975  renderedSymbol.reset( );
2976 
2978  renderedSymbol.reset( new QgsMarkerSymbol( QgsSymbolLayerList() << symL ) );
2979  }
2980  else
2981  {
2982  renderedSymbol.reset( background.markerSymbol()->clone() );
2983  renderedSymbol->setSize( sizeOut );
2984  renderedSymbol->setSizeUnit( QgsUnitTypes::RenderPixels );
2985  }
2986 
2987  renderedSymbol->setOpacity( background.opacity() );
2988 
2989  // draw the actual symbol
2990  p->save();
2991  if ( context.useAdvancedEffects() )
2992  {
2993  p->setCompositionMode( background.blendMode() );
2994  }
2995  if ( context.flags() & QgsRenderContext::Antialiasing )
2996  {
2997  p->setRenderHint( QPainter::Antialiasing );
2998  }
2999  p->translate( component.center.x(), component.center.y() );
3000  p->rotate( component.rotation );
3001  double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
3002  double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
3003  p->translate( QPointF( xoff, yoff ) );
3004  p->rotate( component.rotationOffset );
3005 
3006  const QgsFeature f = context.expressionContext().feature();
3007  renderedSymbol->startRender( context, context.expressionContext().fields() );
3008  renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
3009  renderedSymbol->stopRender( context );
3010  p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
3011  p->restore();
3012 
3013  break;
3014  }
3015 
3020  {
3021  double w = component.size.width();
3022  double h = component.size.height();
3023 
3024  if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
3025  {
3026  w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
3027  background.sizeMapUnitScale() );
3028  h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
3029  background.sizeMapUnitScale() );
3030  }
3031  else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
3032  {
3033  if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
3034  {
3035  if ( w > h )
3036  h = w;
3037  else if ( h > w )
3038  w = h;
3039  }
3040  else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
3041  {
3042  // start with label bound by circle
3043  h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
3044  w = h;
3045  }
3046  else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
3047  {
3048  // start with label bound by ellipse
3049  h = h * M_SQRT1_2 * 2;
3050  w = w * M_SQRT1_2 * 2;
3051  }
3052 
3053  double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
3054  background.sizeMapUnitScale() );
3055  double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
3056  background.sizeMapUnitScale() );
3057 
3058  w += bufferWidth * 2;
3059  h += bufferHeight * 2;
3060  }
3061 
3062  // offsets match those of symbology: -x = left, -y = up
3063  QRectF rect( -w / 2.0, - h / 2.0, w, h );
3064 
3065  if ( rect.isNull() )
3066  return;
3067 
3068  p->save();
3069  if ( context.flags() & QgsRenderContext::Antialiasing )
3070  {
3071  p->setRenderHint( QPainter::Antialiasing );
3072  }
3073  p->translate( QPointF( component.center.x(), component.center.y() ) );
3074  p->rotate( component.rotation );
3075  double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
3076  double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
3077  p->translate( QPointF( xoff, yoff ) );
3078  p->rotate( component.rotationOffset );
3079 
3080  double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
3081 
3082  QPen pen;
3083  if ( background.strokeWidth() > 0 )
3084  {
3085  pen.setColor( background.strokeColor() );
3086  pen.setWidthF( penSize );
3087  if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
3088  pen.setJoinStyle( background.joinStyle() );
3089  }
3090  else
3091  {
3092  pen = Qt::NoPen;
3093  }
3094 
3095  // store painting in QPicture for shadow drawing
3096  QPicture shapePict;
3097  QPainter shapep;
3098  shapep.begin( &shapePict );
3099  shapep.setPen( pen );
3100  shapep.setBrush( background.fillColor() );
3101 
3102  if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
3103  || background.type() == QgsTextBackgroundSettings::ShapeSquare )
3104  {
3105  if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
3106  {
3107  shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
3108  }
3109  else
3110  {
3111  double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
3112  double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
3113  shapep.drawRoundedRect( rect, xRadius, yRadius );
3114  }
3115  }
3116  else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
3117  || background.type() == QgsTextBackgroundSettings::ShapeCircle )
3118  {
3119  shapep.drawEllipse( rect );
3120  }
3121  shapep.end();
3122 
3123  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
3124  {
3125  component.picture = shapePict;
3126  component.pictureBuffer = penSize / 2.0;
3127 
3128  component.size = rect.size();
3129  component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
3130  drawShadow( context, component, format );
3131  }
3132 
3133  p->setOpacity( background.opacity() );
3134  if ( context.useAdvancedEffects() )
3135  {
3136  p->setCompositionMode( background.blendMode() );
3137  }
3138 
3139  // scale for any print output or image saving @ specific dpi
3140  p->scale( component.dpiRatio, component.dpiRatio );
3141  _fixQPictureDPI( p );
3142  p->drawPicture( 0, 0, shapePict );
3143  p->restore();
3144  break;
3145  }
3146  }
3147 
3148  if ( background.paintEffect() && background.paintEffect()->enabled() )
3149  {
3150  background.paintEffect()->end( context );
3151  context.setPainter( prevP );
3152  }
3153 }
3154 
3155 void QgsTextRenderer::drawShadow( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
3156 {
3157  QgsTextShadowSettings shadow = format.shadow();
3158 
3159  // incoming component sizes should be multiplied by rasterCompressFactor, as
3160  // this allows shadows to be created at paint device dpi (e.g. high resolution),
3161  // then scale device painter by 1.0 / rasterCompressFactor for output
3162 
3163  QPainter *p = context.painter();
3164  double componentWidth = component.size.width(), componentHeight = component.size.height();
3165  double xOffset = component.offset.x(), yOffset = component.offset.y();
3166  double pictbuffer = component.pictureBuffer;
3167 
3168  // generate pixmap representation of label component drawing
3169  bool mapUnits = shadow.blurRadiusUnit() == QgsUnitTypes::RenderMapUnits;
3170  double radius = context.convertToPainterUnits( shadow.blurRadius(), shadow.blurRadiusUnit(), shadow.blurRadiusMapUnitScale() );
3171  radius /= ( mapUnits ? context.scaleFactor() / component.dpiRatio : 1 );
3172  radius = static_cast< int >( radius + 0.5 ); //NOLINT
3173 
3174  // TODO: add labeling gui option to adjust blurBufferClippingScale to minimize pixels, or
3175  // to ensure shadow isn't clipped too tight. (Or, find a better method of buffering)
3176  double blurBufferClippingScale = 3.75;
3177  int blurbuffer = ( radius > 17 ? 16 : radius ) * blurBufferClippingScale;
3178 
3179  QImage blurImg( componentWidth + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
3180  componentHeight + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
3181  QImage::Format_ARGB32_Premultiplied );
3182 
3183  // TODO: add labeling gui option to not show any shadows under/over a certain size
3184  // keep very small QImages from causing paint device issues, i.e. must be at least > 1
3185  int minBlurImgSize = 1;
3186  // max limitation on QgsSvgCache is 10,000 for screen, which will probably be reasonable for future caching here, too
3187  // 4 x QgsSvgCache limit for output to print/image at higher dpi
3188  // TODO: should it be higher, scale with dpi, or have no limit? Needs testing with very large labels rendered at high dpi output
3189  int maxBlurImgSize = 40000;
3190  if ( blurImg.isNull()
3191  || ( blurImg.width() < minBlurImgSize || blurImg.height() < minBlurImgSize )
3192  || ( blurImg.width() > maxBlurImgSize || blurImg.height() > maxBlurImgSize ) )
3193  return;
3194 
3195  blurImg.fill( QColor( Qt::transparent ).rgba() );
3196  QPainter pictp;
3197  if ( !pictp.begin( &blurImg ) )
3198  return;
3199  pictp.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
3200  QPointF imgOffset( blurbuffer + pictbuffer + xOffset,
3201  blurbuffer + pictbuffer + componentHeight + yOffset );
3202 
3203  pictp.drawPicture( imgOffset,
3204  component.picture );
3205 
3206  // overlay shadow color
3207  pictp.setCompositionMode( QPainter::CompositionMode_SourceIn );
3208  pictp.fillRect( blurImg.rect(), shadow.color() );
3209  pictp.end();
3210 
3211  // blur the QImage in-place
3212  if ( shadow.blurRadius() > 0.0 && radius > 0 )
3213  {
3214  QgsSymbolLayerUtils::blurImageInPlace( blurImg, blurImg.rect(), radius, shadow.blurAlphaOnly() );
3215  }
3216 
3217 #if 0
3218  // debug rect for QImage shadow registration and clipping visualization
3219  QPainter picti;
3220  picti.begin( &blurImg );
3221  picti.setBrush( Qt::Dense7Pattern );
3222  QPen imgPen( QColor( 0, 0, 255, 255 ) );
3223  imgPen.setWidth( 1 );
3224  picti.setPen( imgPen );
3225  picti.setOpacity( 0.1 );
3226  picti.drawRect( 0, 0, blurImg.width(), blurImg.height() );
3227  picti.end();
3228 #endif
3229 
3230  double offsetDist = context.convertToPainterUnits( shadow.offsetDistance(), shadow.offsetUnit(), shadow.offsetMapUnitScale() );
3231  double angleRad = shadow.offsetAngle() * M_PI / 180; // to radians
3232  if ( shadow.offsetGlobal() )
3233  {
3234  // TODO: check for differences in rotation origin and cw/ccw direction,
3235  // when this shadow function is used for something other than labels
3236 
3237  // it's 0-->cw-->360 for labels
3238  //QgsDebugMsgLevel( QStringLiteral( "Shadow aggregated label rotation (degrees): %1" ).arg( component.rotation() + component.rotationOffset() ), 4 );
3239  angleRad -= ( component.rotation * M_PI / 180 + component.rotationOffset * M_PI / 180 );
3240  }
3241 
3242  QPointF transPt( -offsetDist * std::cos( angleRad + M_PI_2 ),
3243  -offsetDist * std::sin( angleRad + M_PI_2 ) );
3244 
3245  p->save();
3246  p->setRenderHint( QPainter::SmoothPixmapTransform );
3247  if ( context.flags() & QgsRenderContext::Antialiasing )
3248  {
3249  p->setRenderHint( QPainter::Antialiasing );
3250  }
3251  if ( context.useAdvancedEffects() )
3252  {
3253  p->setCompositionMode( shadow.blendMode() );
3254  }
3255  p->setOpacity( shadow.opacity() );
3256 
3257  double scale = shadow.scale() / 100.0;
3258  // TODO: scale from center/center, left/center or left/top, instead of default left/bottom?
3259  p->scale( scale, scale );
3260  if ( component.useOrigin )
3261  {
3262  p->translate( component.origin.x(), component.origin.y() );
3263  }
3264  p->translate( transPt );
3265  p->translate( -imgOffset.x(),
3266  -imgOffset.y() );
3267  p->drawImage( 0, 0, blurImg );
3268  p->restore();
3269 
3270  // debug rects
3271 #if 0
3272  // draw debug rect for QImage painting registration
3273  p->save();
3274  p->setBrush( Qt::NoBrush );
3275  QPen imgPen( QColor( 255, 0, 0, 10 ) );
3276  imgPen.setWidth( 2 );
3277  imgPen.setStyle( Qt::DashLine );
3278  p->setPen( imgPen );
3279  p->scale( scale, scale );
3280  if ( component.useOrigin() )
3281  {
3282  p->translate( component.origin().x(), component.origin().y() );
3283  }
3284  p->translate( transPt );
3285  p->translate( -imgOffset.x(),
3286  -imgOffset.y() );
3287  p->drawRect( 0, 0, blurImg.width(), blurImg.height() );
3288  p->restore();
3289 
3290  // draw debug rect for passed in component dimensions
3291  p->save();
3292  p->setBrush( Qt::NoBrush );
3293  QPen componentRectPen( QColor( 0, 255, 0, 70 ) );
3294  componentRectPen.setWidth( 1 );
3295  if ( component.useOrigin() )
3296  {
3297  p->translate( component.origin().x(), component.origin().y() );
3298  }
3299  p->setPen( componentRectPen );
3300  p->drawRect( QRect( -xOffset, -componentHeight - yOffset, componentWidth, componentHeight ) );
3301  p->restore();
3302 #endif
3303 }
3304 
3305 void QgsTextRenderer::drawTextInternal( TextPart drawType,
3306  QgsRenderContext &context,
3307  const QgsTextFormat &format,
3308  const Component &component,
3309  const QStringList &textLines,
3310  const QFontMetricsF *fontMetrics,
3311  HAlignment alignment, DrawMode mode )
3312 {
3313  if ( !context.painter() )
3314  {
3315  return;
3316  }
3317 
3319  double rotation = -component.rotation * 180 / M_PI;
3321  {
3322  // Between 45 to 135 and 235 to 315 degrees, rely on vertical orientation
3323  if ( rotation >= -315 && rotation < -90 )
3324  {
3325  rotation -= 90;
3326  orientation = QgsTextFormat::VerticalOrientation;
3327  }
3328  else if ( rotation >= -90 && rotation < -45 )
3329  {
3330  rotation += 90;
3331  orientation = QgsTextFormat::VerticalOrientation;
3332  }
3333  else
3334  {
3336  }
3337  }
3338 
3339  switch ( orientation )
3340  {
3342  {
3343  double labelWidest = 0.0;
3344  switch ( mode )
3345  {
3346  case Label:
3347  case Point:
3348  for ( const QString &line : textLines )
3349  {
3350  double labelWidth = fontMetrics->width( line );
3351  if ( labelWidth > labelWidest )
3352  {
3353  labelWidest = labelWidth;
3354  }
3355  }
3356  break;
3357 
3358  case Rect:
3359  labelWidest = component.size.width();
3360  break;
3361  }
3362 
3363  double labelHeight = fontMetrics->ascent() + fontMetrics->descent(); // ignore +1 for baseline
3364  // double labelHighest = labelfm->height() + ( double )(( lines - 1 ) * labelHeight * tmpLyr.multilineHeight );
3365 
3366  // needed to move bottom of text's descender to within bottom edge of label
3367  double ascentOffset = 0.25 * fontMetrics->ascent(); // labelfm->descent() is not enough
3368 
3369  int i = 0;
3370 
3371  bool adjustForAlignment = alignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
3372 
3373  const auto constTextLines = textLines;
3374  for ( const QString &line : constTextLines )
3375  {
3376  context.painter()->save();
3377  if ( context.flags() & QgsRenderContext::Antialiasing )
3378  {
3379  context.painter()->setRenderHint( QPainter::Antialiasing );
3380  }
3381  context.painter()->translate( component.origin );
3382  if ( !qgsDoubleNear( rotation, 0.0 ) )
3383  context.painter()->rotate( rotation );
3384 
3385  // figure x offset for horizontal alignment of multiple lines
3386  double xMultiLineOffset = 0.0;
3387  double labelWidth = fontMetrics->width( line );
3388  if ( adjustForAlignment )
3389  {
3390  double labelWidthDiff = labelWidest - labelWidth;
3391  if ( alignment == AlignCenter )
3392  {
3393  labelWidthDiff /= 2;
3394  }
3395  switch ( mode )
3396  {
3397  case Label:
3398  case Rect:
3399  xMultiLineOffset = labelWidthDiff;
3400  break;
3401 
3402  case Point:
3403  if ( alignment == AlignRight )
3404  xMultiLineOffset = labelWidthDiff - labelWidest;
3405  else if ( alignment == AlignCenter )
3406  xMultiLineOffset = labelWidthDiff - labelWidest / 2.0;
3407 
3408  break;
3409  }
3410  //QgsDebugMsgLevel( QStringLiteral( "xMultiLineOffset: %1" ).arg( xMultiLineOffset ), 4 );
3411  }
3412 
3413  double yMultiLineOffset = ascentOffset;
3414  switch ( mode )
3415  {
3416  case Label:
3417  // rendering labels needs special handling - in this case text should be
3418  // drawn with the bottom left corner coinciding with origin, vs top left
3419  // for standard text rendering. Line height is also slightly different.
3420  yMultiLineOffset = - ascentOffset - ( textLines.size() - 1 - i ) * labelHeight * format.lineHeight();
3421  break;
3422 
3423  case Rect:
3424  // standard rendering - designed to exactly replicate QPainter's drawText method
3425  yMultiLineOffset = - ascentOffset + labelHeight - 1 /*baseline*/ + format.lineHeight() * fontMetrics->lineSpacing() * i;
3426  break;
3427 
3428  case Point:
3429  // standard rendering - designed to exactly replicate QPainter's drawText rect method
3430  yMultiLineOffset = 0 - ( textLines.size() - 1 - i ) * fontMetrics->lineSpacing() * format.lineHeight();
3431  break;
3432 
3433  }
3434 
3435  context.painter()->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
3436 
3437  Component subComponent;
3438  subComponent.text = line;
3439  subComponent.size = QSizeF( labelWidth, labelHeight );
3440  subComponent.offset = QPointF( 0.0, -ascentOffset );
3441  subComponent.rotation = -component.rotation * 180 / M_PI;
3442  subComponent.rotationOffset = 0.0;
3443 
3444  if ( drawType == QgsTextRenderer::Buffer )
3445  {
3446  QgsTextRenderer::drawBuffer( context, subComponent, format, fontMetrics );
3447  }
3448  else
3449  {
3450  // draw text, QPainterPath method
3451  QPainterPath path;
3452  path.setFillRule( Qt::WindingFill );
3453  path.addText( 0, 0, format.scaledFont( context ), subComponent.text );
3454 
3455  // store text's drawing in QPicture for drop shadow call
3456  QPicture textPict;
3457  QPainter textp;
3458  textp.begin( &textPict );
3459  textp.setPen( Qt::NoPen );
3460  QColor textColor = format.color();
3461  textColor.setAlphaF( format.opacity() );
3462  textp.setBrush( textColor );
3463  textp.drawPath( path );
3464  // TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
3465  // e.g. some capitalization options, but not others
3466  //textp.setFont( tmpLyr.textFont );
3467  //textp.setPen( tmpLyr.textColor );
3468  //textp.drawText( 0, 0, component.text() );
3469  textp.end();
3470 
3471  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
3472  {
3473  subComponent.picture = textPict;
3474  subComponent.pictureBuffer = 0.0; // no pen width to deal with
3475  subComponent.origin = QPointF( 0.0, 0.0 );
3476 
3477  QgsTextRenderer::drawShadow( context, subComponent, format );
3478  }
3479 
3480  // paint the text
3481  if ( context.useAdvancedEffects() )
3482  {
3483  context.painter()->setCompositionMode( format.blendMode() );
3484  }
3485 
3486  // scale for any print output or image saving @ specific dpi
3487  context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
3488 
3489  switch ( context.textRenderFormat() )
3490  {
3492  {
3493  // draw outlined text
3494  _fixQPictureDPI( context.painter() );
3495  context.painter()->drawPicture( 0, 0, textPict );
3496  break;
3497  }
3498 
3500  {
3501  context.painter()->setFont( format.scaledFont( context ) );
3502  QColor textColor = format.color();
3503  textColor.setAlphaF( format.opacity() );
3504  context.painter()->setPen( textColor );
3505  context.painter()->setRenderHint( QPainter::TextAntialiasing );
3506  context.painter()->drawText( 0, 0, subComponent.text );
3507  }
3508  }
3509  }
3510  context.painter()->restore();
3511  i++;
3512  }
3513  break;
3514  }
3515 
3518  {
3519  double letterSpacing = format.scaledFont( context ).letterSpacing();
3520 
3521  double labelWidth = fontMetrics->maxWidth(); // label width represents the width of one line of a multi-line label
3522  double actualLabelWidest = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.lineHeight();
3523  double labelWidest = 0.0;
3524  switch ( mode )
3525  {
3526  case Label:
3527  case Point:
3528  labelWidest = actualLabelWidest;
3529  break;
3530 
3531  case Rect:
3532  labelWidest = component.size.width();
3533  break;
3534  }
3535 
3536  int maxLineLength = 0;
3537  for ( auto const &line : textLines )
3538  {
3539  maxLineLength = std::max( maxLineLength, line.length() );
3540  }
3541  double actualLabelHeight = fontMetrics->ascent() + ( fontMetrics->ascent() + letterSpacing ) * ( maxLineLength - 1 );
3542  double ascentOffset = fontMetrics->ascent();
3543 
3544  int i = 0;
3545 
3546  bool adjustForAlignment = alignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
3547 
3548  // apply some character replacement to draw symbols in vertical presentation
3549  QString lines = textLines.join( '\n' );
3550  const auto constTextLines = QgsStringUtils::substituteVerticalCharacters( lines ).split( '\n' );
3551  for ( QString line : constTextLines )
3552  {
3553  context.painter()->save();
3554  if ( context.flags() & QgsRenderContext::Antialiasing )
3555  {
3556  context.painter()->setRenderHint( QPainter::Antialiasing );
3557  }
3558  context.painter()->translate( component.origin );
3559  if ( !qgsDoubleNear( rotation, 0.0 ) )
3560  context.painter()->rotate( rotation );
3561 
3562  // figure x offset of multiple lines
3563  double xOffset = actualLabelWidest - labelWidth - ( i * labelWidth * format.lineHeight() );
3564  if ( adjustForAlignment )
3565  {
3566  double labelWidthDiff = labelWidest - actualLabelWidest;
3567  if ( alignment == AlignCenter )
3568  {
3569  labelWidthDiff /= 2;
3570  }
3571  switch ( mode )
3572  {
3573  case Label:
3574  case Rect:
3575  xOffset += labelWidthDiff;
3576  break;
3577 
3578  case Point:
3579  break;
3580  }
3581  }
3582 
3583  double yOffset = 0.0;
3584  switch ( mode )
3585  {
3586  case Label:
3588  {
3589  if ( rotation >= -405 && rotation < -180 )
3590  {
3591  yOffset = ascentOffset;
3592  }
3593  else if ( rotation >= 0 && rotation < 45 )
3594  {
3595  xOffset -= actualLabelWidest;
3596  yOffset = -actualLabelHeight + ascentOffset + fontMetrics->descent();
3597  }
3598  }
3599  else
3600  {
3601  yOffset = -actualLabelHeight + ascentOffset;
3602  }
3603  break;
3604 
3605  case Point:
3606  yOffset = -actualLabelHeight + ascentOffset;
3607  break;
3608 
3609  case Rect:
3610  yOffset = ascentOffset;
3611  break;
3612  }
3613 
3614  context.painter()->translate( QPointF( xOffset, yOffset ) );
3615 
3616  double labelHeight = fontMetrics->ascent() + ( fontMetrics->ascent() + letterSpacing ) * ( line.length() - 1 );
3617 
3618  Component subComponent;
3619  subComponent.text = line;
3620  subComponent.size = QSizeF( labelWidth, labelHeight );
3621  subComponent.offset = QPointF( 0.0, 0.0 );
3622  subComponent.rotation = -component.rotation * 180 / M_PI;
3623  subComponent.rotationOffset = 0.0;
3624 
3625  if ( drawType == QgsTextRenderer::Buffer )
3626  {
3627  QgsTextRenderer::drawBuffer( context, subComponent, format, fontMetrics );
3628  }
3629  else
3630  {
3631  // draw text, QPainterPath method
3632  QPainterPath path;
3633  path.setFillRule( Qt::WindingFill );
3634  const QStringList parts = QgsPalLabeling::splitToGraphemes( subComponent.text );
3635  double partYOffset = 0.0;
3636  for ( const auto &part : parts )
3637  {
3638  double partXOffset = ( labelWidth - ( fontMetrics->width( part ) - letterSpacing ) ) / 2;
3639  path.addText( partXOffset, partYOffset, format.scaledFont( context ), part );
3640  partYOffset += fontMetrics->ascent() + letterSpacing;
3641  }
3642 
3643  // store text's drawing in QPicture for drop shadow call
3644  QPicture textPict;
3645  QPainter textp;
3646  textp.begin( &textPict );
3647  textp.setPen( Qt::NoPen );
3648  QColor textColor = format.color();
3649  textColor.setAlphaF( format.opacity() );
3650  textp.setBrush( textColor );
3651  textp.drawPath( path );
3652  // TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
3653  // e.g. some capitalization options, but not others
3654  //textp.setFont( tmpLyr.textFont );
3655  //textp.setPen( tmpLyr.textColor );
3656  //textp.drawText( 0, 0, component.text() );
3657  textp.end();
3658 
3659  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
3660  {
3661  subComponent.picture = textPict;
3662  subComponent.pictureBuffer = 0.0; // no pen width to deal with
3663  subComponent.origin = QPointF( 0.0, 0.0 );
3664 
3665  QgsTextRenderer::drawShadow( context, subComponent, format );
3666  }
3667 
3668  // paint the text
3669  if ( context.useAdvancedEffects() )
3670  {
3671  context.painter()->setCompositionMode( format.blendMode() );
3672  }
3673 
3674  // scale for any print output or image saving @ specific dpi
3675  context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
3676 
3677  switch ( context.textRenderFormat() )
3678  {
3680  {
3681  // draw outlined text
3682  _fixQPictureDPI( context.painter() );
3683  context.painter()->drawPicture( 0, 0, textPict );
3684  break;
3685  }
3686 
3688  {
3689  context.painter()->setFont( format.scaledFont( context ) );
3690  QColor textColor = format.color();
3691  textColor.setAlphaF( format.opacity() );
3692  context.painter()->setPen( textColor );
3693  context.painter()->setRenderHint( QPainter::TextAntialiasing );
3694  context.painter()->drawText( 0, 0, subComponent.text );
3695  }
3696  }
3697  }
3698  context.painter()->restore();
3699  i++;
3700  }
3701  break;
3702  }
3703  }
3704 }
3705 
3706 
3707 //
3708 // QgsTextRendererUtils
3709 //
3710 
3712 {
3714  const QString skind = string.trimmed();
3715 
3716  if ( skind.compare( QLatin1String( "Square" ), Qt::CaseInsensitive ) == 0 )
3717  {
3719  }
3720  else if ( skind.compare( QLatin1String( "Ellipse" ), Qt::CaseInsensitive ) == 0 )
3721  {
3723  }
3724  else if ( skind.compare( QLatin1String( "Circle" ), Qt::CaseInsensitive ) == 0 )
3725  {
3727  }
3728  else if ( skind.compare( QLatin1String( "SVG" ), Qt::CaseInsensitive ) == 0 )
3729  {
3731  }
3732  else if ( skind.compare( QLatin1String( "marker" ), Qt::CaseInsensitive ) == 0 )
3733  {
3735  }
3736  return shpkind;
3737 }
3738 
3740 {
3741  const QString stype = string.trimmed();
3742  // "Buffer"
3744 
3745  if ( stype.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 )
3746  {
3748  }
3749  return sizType;
3750 }
3751 
3753 {
3754  const QString rotstr = string.trimmed();
3755  // "Sync"
3757 
3758  if ( rotstr.compare( QLatin1String( "Offset" ), Qt::CaseInsensitive ) == 0 )
3759  {
3761  }
3762  else if ( rotstr.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 )
3763  {
3765  }
3766  return rottype;
3767 }
3768 
3770 {
3771  const QString str = string.trimmed();
3772  // "Lowest"
3774 
3775  if ( str.compare( QLatin1String( "Text" ), Qt::CaseInsensitive ) == 0 )
3776  {
3778  }
3779  else if ( str.compare( QLatin1String( "Buffer" ), Qt::CaseInsensitive ) == 0 )
3780  {
3782  }
3783  else if ( str.compare( QLatin1String( "Background" ), Qt::CaseInsensitive ) == 0 )
3784  {
3786  }
3787  return shdwtype;
3788 }
3789 
3791 {
3792  switch ( orientation )
3793  {
3795  return QStringLiteral( "horizontal" );
3797  return QStringLiteral( "vertical" );
3799  return QStringLiteral( "rotation-based" );
3800  }
3801  return QString();
3802 }
3803 
3805 {
3806  if ( ok )
3807  *ok = true;
3808 
3809  QString cleaned = name.toLower().trimmed();
3810 
3811  if ( cleaned == QLatin1String( "horizontal" ) )
3813  else if ( cleaned == QLatin1String( "vertical" ) )
3815  else if ( cleaned == QLatin1String( "rotation-based" ) )
3817 
3818  if ( ok )
3819  *ok = false;
3821 }
TextOrientation
Text orientation.
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the buffer.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
The class is used as a container of context for various read/write operations on other objects...
QColor strokeColor() const
Returns the color used for outlining the background shape.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color...
Meters value as Map units.
Definition: qgsunittypes.h:154
void setScale(int scale)
Sets the scaling used for the drop shadow (in percentage of original size).
Shape size is determined by adding a buffer margin around text.
void setLineHeight(double height)
Sets the line height for text.
void setRadiiUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape&#39;s radii.
void setStrokeWidth(double width)
Sets the width of the shape&#39;s stroke (stroke).
RotationType
Methods for determining the rotation of the background shape.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the shadow offset distance.
Horizontally or vertically oriented text based on rotation (only available for map labeling) ...
void setOpacity(double opacity)
Sets the text&#39;s opacity.
void setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the shape stroke width.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shadow&#39;s offset.
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
QgsTextShadowSettings::ShadowPlacement shadowPlacement() const
Returns the placement for the drop shadow.
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
QSizeF size() const
Returns the size of the background shape.
double opacity() const
Returns the text&#39;s opacity.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the shape&#39;s size.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
void setOrientation(TextOrientation orientation)
Sets the orientation for the text.
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the drop shadow.
QPointF offset() const
Returns the offset used for drawing the background shape.
QColor fillColor() const
Returns the color used for filing the background shape.
QgsMapUnitScale strokeWidthMapUnitScale() const
Returns the map unit scale object for the shape stroke width.
static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType(const QString &string)
Decodes a string representation of a background rotation type to a type.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double...
QFont toQFont() const
Returns a QFont matching the relevant settings from this text format.
QgsTextShadowSettings & operator=(const QgsTextShadowSettings &other)
static QString substituteVerticalCharacters(QString string)
Returns a string with characters having vertical representation form substituted. ...
void readXml(const QDomElement &elem)
Read settings from a DOM element.
Use antialiasing while drawing.
void setPreviewBackgroundColor(const QColor &color)
Sets the background color that text will be rendered on for previews.
Draw shadow under buffer.
ShadowPlacement
Placement positions for text shadow.
Always render text as text objects.
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
double blurRadius() const
Returns the blur radius for the shadow.
QgsUnitTypes::RenderUnit convertFromOldLabelUnit(int val)
double opacity() const
Returns the background shape&#39;s opacity.
void setSizeMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the size.
void setStrokeColor(const QColor &color)
Sets the color used for outlining the background shape.
void setSize(double size)
Sets the size of the buffer.
double strokeWidth() const
Returns the width of the shape&#39;s stroke (stroke).
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:280
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
HAlignment
Horizontal alignment.
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
static QgsPainting::BlendMode getBlendModeEnum(QPainter::CompositionMode blendMode)
Returns a BlendMode corresponding to a QPainter::CompositionMode.
Definition: qgspainting.cpp:80
Base class for visual effects which can be applied to QPicture drawings.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape&#39;s offset.
static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
QColor color() const
Returns the color that text will be rendered in.
static void drawPart(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, TextPart part, bool drawAsOutlines=true)
Draws a single component of rendered text using the specified settings.
void setFillBufferInterior(bool fill)
Sets whether the interior of the buffer will be filled in.
void setBlurRadius(double blurRadius)
Sets the blur radius for the shadow.
void setPaintEffect(QgsPaintEffect *effect)
Sets the current paint effect for the buffer.
void setOpacity(double opacity)
Sets the shadow&#39;s opacity.
QgsTextBufferSettings & operator=(const QgsTextBufferSettings &other)
Copy constructor.
void setSizeMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the shape size.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
Mixed or unknown units.
Definition: qgsunittypes.h:153
static QPainter::CompositionMode getCompositionMode(QgsPainting::BlendMode blendMode)
Returns a QPainter::CompositionMode corresponding to a BlendMode.
Definition: qgspainting.cpp:20
Flags flags() const
Returns combination of flags used for rendering.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
QgsFields fields() const
Convenience function for retrieving the fields for the context, if set.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the background shape.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
TextRenderFormat textRenderFormat() const
Returns the text render format, which dictates how text is rendered (e.g.
static QgsTextFormat fromQFont(const QFont &font)
Returns a text format matching the settings from an input font.
void setMarkerSymbol(QgsMarkerSymbol *symbol)
Sets the current marker symbol for the background shape.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
Container for settings relating to a text background object.
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:150
static int sizeToPixel(double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale=QgsMapUnitScale())
Calculates pixel size (considering output size should be in pixel or map units, scale factors and opt...
QgsMapUnitScale offsetMapUnitScale() const
Returns the map unit scale object for the shape offset.
void setRadiiMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the shape radii.
void setBlurRadiusUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shadow&#39;s blur radius.
static QString translateNamedStyle(const QString &namedStyle)
Returns the localized named style of a font, if such a translation is available.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:612
void setBlurRadiusMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the shadow blur radius.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:860
void readXml(const QDomElement &elem)
Read settings from a DOM element.
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QgsPaintEffectRegistry * paintEffectRegistry()
Returns the application&#39;s paint effect registry, used for managing paint effects. ...
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
void setOffset(QPointF offset)
Sets the offset used for drawing the background shape.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QDomElement writeXml(QDomDocument &doc) const
Write settings into a DOM element.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
static QString encodeColor(const QColor &color)
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
static QgsTextFormat fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsTextFormat.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer&#39;s custom properties (for QGIS 2.x projects).
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection...
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:37
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer&#39;s custom properties (for QGIS 2.x projects).
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s name from its path.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the background shape.
void updateDataDefinedProperties(QgsRenderContext &context, const QgsPropertyCollection &properties)
Updates the format by evaluating current values of data defined properties.
Shape rotation is a fixed angle.
static QString encodeTextOrientation(QgsTextFormat::TextOrientation orientation)
Encodes a text orientation.
QgsMapUnitScale offsetMapUnitScale() const
Returns the map unit scale object for the shadow offset distance.
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the text.
void setUseAdvancedEffects(bool enabled)
Used to enable or disable advanced effects such as blend modes.
QColor color() const
Returns the color of the drop shadow.
void setSize(double size)
Sets the size for rendered text.
void setOffsetDistance(double distance)
Sets the distance for offsetting the position of the shadow from the text.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
static QgsTextFormat::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
Text within rectangle draw mode.
#define FALLTHROUGH
Definition: qgis.h:681
QColor previewBackgroundColor() const
Returns the background color for text previews.
QgsMapUnitScale blurRadiusMapUnitScale() const
Returns the map unit scale object for the shadow blur radius.
bool fillBufferInterior() const
Returns whether the interior of the buffer will be filled in.
TextOrientation orientation() const
Returns the orientation of the text.
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the shape offset.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
void setBlurAlphaOnly(bool alphaOnly)
Sets whether only the alpha channel for the shadow should be blurred.
Buffer component.
RotationType rotationType() const
Returns the method used for rotating the background shape.
void setSizeMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the buffer size.
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the background shape.
Always render text using path objects (AKA outlines/curves).
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the drop shadow.
static QPixmap textFormatPreviewPixmap(const QgsTextFormat &format, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for a text format.
void setColor(const QColor &color)
Sets the color for the drop shadow.
static QgsSymbolLayer * create(const QgsStringMap &properties=QgsStringMap())
void setEnabled(bool enabled)
Sets whether the text shadow will be drawn.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
Draw shadow under text.
QgsMapUnitScale radiiMapUnitScale() const
Returns the map unit scale object for the shape radii.
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
Qt::PenJoinStyle joinStyle() const
Returns the join style used for drawing the background shape.
void setNamedStyle(const QString &style)
Sets the named style for the font used for rendering text.
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:51
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
QDomElement writeXml(QDomDocument &doc) const
Write settings into a DOM element.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
SizeType
Methods for determining the background shape size.
double opacity() const
Returns the buffer opacity.
static QPainter::CompositionMode decodeBlendMode(const QString &s)
void updateDataDefinedProperties(QgsRenderContext &context)
Updates the format by evaluating current values of data defined properties.
bool enabled() const
Returns whether the effect is enabled.
Q_GUI_EXPORT int qt_defaultDpiY()
static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType(const QString &string)
Decodes a string representation of a shadow placement type to a type.
void setSize(QSizeF size)
Sets the size of the background shape.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
QMimeData * toMimeData() const
Returns new mime data representing the text format settings.
void setRadii(QSizeF radii)
Sets the radii used for rounding the corners of shapes.
static QFontMetricsF fontMetrics(QgsRenderContext &context, const QgsTextFormat &format)
Returns the font metrics for the given text format, when rendered in the specified render context...
void setDataDefinedProperties(const QgsPropertyCollection &collection)
Sets the format&#39;s property collection, used for data defined overrides.
QgsUnitTypes::RenderUnit strokeWidthUnit() const
Returns the units used for the shape&#39;s stroke width.
void setSizeType(SizeType type)
Sets the method used to determine the size of the background shape (e.g., fixed size or buffer around...
void renderPoint(QPointF point, QgsSymbolRenderContext &context) override
Renders a marker at the specified point.
void setType(ShapeType type)
Sets the type of background shape to draw (e.g., square, ellipse, SVG).
Draw shadow below all text components.
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the background shape.
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true)
Draws text within a rectangle using the specified settings.
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units used for the shape&#39;s offset.
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Set parameters for use in transforming coordinates.
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
double lineHeight() const
Returns the line height for text.
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
ShapeType
Background shape types.
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean...
TextPart
Components of text.
void updateDataDefinedProperties(QgsRenderContext &context, const QgsPropertyCollection &properties)
Updates the format by evaluating current values of data defined properties.
static QString untranslateNamedStyle(const QString &namedStyle)
Returns the english named style of a font, if possible.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
QFont scaledFont(const QgsRenderContext &context) const
Returns a font with the size scaled to match the format&#39;s size settings (including units and map unit...
Contains information about the context of a rendering operation.
Shape rotation is offset from text rotation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
static bool fontFamilyMatchOnSystem(const QString &family, QString *chosen=nullptr, bool *match=nullptr)
Check whether font family is on system.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the buffer size.
QPainter * painter()
Returns the destination QPainter for the render operation.
const QgsMapToPixel & mapToPixel() const
Returns the context&#39;s map to pixel transform, which transforms between map coordinates and device coo...
static void blurImageInPlace(QImage &image, QRect rect, int radius, bool alphaOnly)
Blurs an image in place, e.g. creating Qt-independent drop shadows.
Points (e.g., for font sizes)
Definition: qgsunittypes.h:151
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units used for the shadow&#39;s offset.
bool enabled() const
Returns whether the shadow is enabled.
void updateDataDefinedProperties(QgsRenderContext &context, const QgsPropertyCollection &properties)
Updates the format by evaluating current values of data defined properties.
QgsTextBackgroundSettings & operator=(const QgsTextBackgroundSettings &other)
Struct for storing maximum and minimum scales for measurements in map units.
QgsTextFormat & operator=(const QgsTextFormat &other)
Container for settings relating to a text shadow.
bool offsetGlobal() const
Returns true if the global shadow offset will be used.
QColor color() const
Returns the color of the buffer.
double size() const
Returns the size of the buffer.
QgsUnitTypes::RenderUnit radiiUnit() const
Returns the units used for the shape&#39;s radii.
Container for settings relating to a text buffer.
void setFillColor(const QColor &color)
Sets the color used for filing the background shape.
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
bool useAdvancedEffects() const
Returns true if advanced effects such as blend modes such be used.
double size() const
Returns the size for rendered text.
void setStrokeWidthUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape&#39;s stroke width.
bool enabled() const
Returns whether the background is enabled.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context&#39;s map to pixel transform, which transforms between map coordinates and device coordi...
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the format&#39;s property collection, used for data defined overrides.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
QgsUnitTypes::RenderUnit blurRadiusUnit() const
Returns the units used for the shadow&#39;s blur radius.
bool enabled() const
Returns whether the buffer is enabled.
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
Shape rotation is synced with text rotation.
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
Q_GUI_EXPORT int qt_defaultDpiX()
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
Draw shadow under background shape.
bool hasActiveProperties() const override
Returns true if the collection has any active properties, or false if all properties within the colle...
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the buffer.
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s path from its name.
Container for all settings relating to text rendering.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
QgsPaintEffect * createEffect(const QString &name, const QgsStringMap &properties=QgsStringMap()) const
Creates a new paint effect given the effect name and properties map.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the buffer.
void setSvgFile(const QString &file)
Sets the path to the background SVG file.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
bool blurAlphaOnly() const
Returns whether only the alpha channel for the shadow will be blurred.
void setOffsetGlobal(bool global)
Sets whether the global shadow offset should be used.
DrawMode
Draw mode to calculate width and height.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
Represents a vector layer which manages a vector based data sets.
void setOpacity(double opacity)
Sets the buffer opacity.
Square - buffered sizes only.
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an integer...
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer&#39;s custom properties (for QGIS 2.x projects).
virtual void end(QgsRenderContext &context)
Ends interception of paint operations to a render context, and draws the result to the render context...
bool containsAdvancedEffects() const
Returns true if any component of the font format requires advanced effects such as blend modes...
QString namedStyle() const
Returns the named style for the font used for rendering text (e.g., "bold").
QFont font() const
Returns the font used for rendering text.
void setRotationType(RotationType type)
Sets the method used for rotating the background shape.
void setColor(const QColor &color)
Sets the color for the buffer.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units used for the shape&#39;s size.
virtual void begin(QgsRenderContext &context)
Begins intercepting paint operations to a render context.
double opacity() const
Returns the shadow&#39;s opacity.
QgsMarkerSymbol * clone() const override
Returns a deep copy of this symbol.
Definition: qgssymbol.cpp:1708
void setOpacity(double opacity)
Sets the background shape&#39;s opacity.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:145
static QColor decodeColor(const QString &str)
QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol to be rendered in the background.
void setPaintEffect(QgsPaintEffect *effect)
Sets the current paint effect for the background shape.
Horizontally oriented text.
void setOffsetAngle(int angle)
Sets the angle for offsetting the position of the shadow from the text.
static QgsTextBackgroundSettings::ShapeType decodeShapeType(const QString &string)
Decodes a string representation of a background shape type to a type.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF *fontMetrics=nullptr)
Returns the height of a text based on a given format.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer&#39;s custom properties (for QGIS 2.x projects).
void setRotation(double rotation)
Sets the rotation for the background shape, in degrees clockwise.