QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgstextrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstextrenderer.cpp
3  -------------------
4  begin : September 2015
5  copyright : (C) Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgstextrenderer.h"
17 #include "qgsvectorlayer.h"
18 #include "qgstextformat.h"
19 #include "qgstextdocument.h"
20 #include "qgstextfragment.h"
21 #include "qgspallabeling.h"
22 #include "qgspainteffect.h"
23 #include "qgspainterswapper.h"
24 #include "qgsmarkersymbollayer.h"
25 #include "qgssymbollayerutils.h"
26 #include "qgsmarkersymbol.h"
27 #include "qgsfillsymbol.h"
28 
29 #include <optional>
30 
31 #include <QTextBoundaryFinder>
32 
33 Q_GUI_EXPORT extern int qt_defaultDpiX();
34 Q_GUI_EXPORT extern int qt_defaultDpiY();
35 
36 static void _fixQPictureDPI( QPainter *p )
37 {
38  // QPicture makes an assumption that we drawing to it with system DPI.
39  // Then when being drawn, it scales the painter. The following call
40  // negates the effect. There is no way of setting QPicture's DPI.
41  // See QTBUG-20361
42  p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
43  static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
44 }
45 
47 {
48  if ( alignment & Qt::AlignLeft )
49  return AlignLeft;
50  else if ( alignment & Qt::AlignRight )
51  return AlignRight;
52  else if ( alignment & Qt::AlignHCenter )
53  return AlignCenter;
54  else if ( alignment & Qt::AlignJustify )
55  return AlignJustify;
56 
57  // not supported?
58  return AlignLeft;
59 }
60 
62 {
63  if ( alignment & Qt::AlignTop )
64  return AlignTop;
65  else if ( alignment & Qt::AlignBottom )
66  return AlignBottom;
67  else if ( alignment & Qt::AlignVCenter )
68  return AlignVCenter;
69  //not supported
70  else if ( alignment & Qt::AlignBaseline )
71  return AlignBottom;
72 
73  return AlignTop;
74 }
75 
76 int QgsTextRenderer::sizeToPixel( double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale )
77 {
78  return static_cast< int >( c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 ); //NOLINT
79 }
80 
81 void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &text, QgsRenderContext &context, const QgsTextFormat &format, bool, VAlignment vAlignment, Qgis::TextRendererFlags flags )
82 {
83  QgsTextFormat tmpFormat = format;
84  if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
85  tmpFormat.updateDataDefinedProperties( context );
86  tmpFormat = updateShadowPosition( tmpFormat );
87 
88  QStringList textLines;
89  for ( const QString &line : text )
90  {
91  if ( flags & Qgis::TextRendererFlag::WrapLines && textRequiresWrapping( context, line, rect.width(), format ) )
92  {
93  textLines.append( wrappedText( context, line, rect.width(), format ) );
94  }
95  else
96  {
97  textLines.append( line );
98  }
99  }
100 
101  QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
102  document.applyCapitalization( format.capitalization() );
103 
104  if ( tmpFormat.background().enabled() )
105  {
106  drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Background );
107  }
108 
109  if ( tmpFormat.buffer().enabled() )
110  {
111  drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Buffer );
112  }
113 
114  drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Text );
115 }
116 
117 void QgsTextRenderer::drawText( QPointF point, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool )
118 {
119  QgsTextFormat tmpFormat = format;
120  if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
121  tmpFormat.updateDataDefinedProperties( context );
122  tmpFormat = updateShadowPosition( tmpFormat );
123 
124  QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
125  document.applyCapitalization( format.capitalization() );
126 
127  if ( tmpFormat.background().enabled() )
128  {
129  drawPart( point, rotation, alignment, document, context, tmpFormat, Background );
130  }
131 
132  if ( tmpFormat.buffer().enabled() )
133  {
134  drawPart( point, rotation, alignment, document, context, tmpFormat, Buffer );
135  }
136 
137  drawPart( point, rotation, alignment, document, context, tmpFormat, Text );
138 }
139 
140 QgsTextFormat QgsTextRenderer::updateShadowPosition( const QgsTextFormat &format )
141 {
142  if ( !format.shadow().enabled() || format.shadow().shadowPlacement() != QgsTextShadowSettings::ShadowLowest )
143  return format;
144 
145  QgsTextFormat tmpFormat = format;
146  if ( tmpFormat.background().enabled() && tmpFormat.background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadow not compatible with marker symbol backgrounds
147  {
149  }
150  else if ( tmpFormat.buffer().enabled() )
151  {
153  }
154  else
155  {
157  }
158  return tmpFormat;
159 }
160 
161 void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, HAlignment alignment,
162  const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
163 {
164  const QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
165 
166  drawPart( rect, rotation, alignment, AlignTop, document, context, format, part );
167 }
168 
169 void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, VAlignment vAlignment, const QgsTextDocument &document, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part )
170 {
171  if ( !context.painter() )
172  {
173  return;
174  }
175 
176  Component component;
177  component.dpiRatio = 1.0;
178  component.origin = rect.topLeft();
179  component.rotation = rotation;
180  component.size = rect.size();
181  component.hAlign = alignment;
182 
183  switch ( part )
184  {
185  case Background:
186  {
187  if ( !format.background().enabled() )
188  return;
189 
190  if ( !qgsDoubleNear( rotation, 0.0 ) )
191  {
192  // get rotated label's center point
193 
194  double xc = rect.width() / 2.0;
195  double yc = rect.height() / 2.0;
196 
197  double angle = -rotation;
198  double xd = xc * std::cos( angle ) - yc * std::sin( angle );
199  double yd = xc * std::sin( angle ) + yc * std::cos( angle );
200 
201  component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd );
202  }
203  else
204  {
205  component.center = rect.center();
206  }
207 
208  QgsTextRenderer::drawBackground( context, component, format, document, Rect );
209 
210  break;
211  }
212 
213  case Buffer:
214  {
215  if ( !format.buffer().enabled() )
216  break;
217  }
219  case Text:
220  case Shadow:
221  {
222  drawTextInternal( part, context, format, component,
223  document,
224  nullptr,
225  alignment, vAlignment );
226  break;
227  }
228  }
229 }
230 
231 void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
232 {
233  const QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
234  drawPart( origin, rotation, alignment, document, context, format, part );
235 }
236 
237 void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QgsTextDocument &document, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part )
238 {
239  if ( !context.painter() )
240  {
241  return;
242  }
243 
244  Component component;
245  component.dpiRatio = 1.0;
246  component.origin = origin;
247  component.rotation = rotation;
248  component.hAlign = alignment;
249 
250  switch ( part )
251  {
252  case Background:
253  {
254  if ( !format.background().enabled() )
255  return;
256 
257  QgsTextRenderer::drawBackground( context, component, format, document, Point );
258  break;
259  }
260 
261  case Buffer:
262  {
263  if ( !format.buffer().enabled() )
264  break;
265  }
267  case Text:
268  case Shadow:
269  {
270  drawTextInternal( part, context, format, component,
271  document,
272  nullptr,
273  alignment, AlignTop,
274  Point );
275  break;
276  }
277  }
278 }
279 
280 QFontMetricsF QgsTextRenderer::fontMetrics( QgsRenderContext &context, const QgsTextFormat &format, const double scaleFactor )
281 {
282  return QFontMetricsF( format.scaledFont( context, scaleFactor ), context.painter() ? context.painter()->device() : nullptr );
283 }
284 
285 double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format,
286  DrawMode mode )
287 {
288  QPainter *p = context.painter();
289 
290  QgsTextFormat::TextOrientation orientation = format.orientation();
292  {
293  if ( component.rotation >= -315 && component.rotation < -90 )
294  {
296  }
297  else if ( component.rotation >= -90 && component.rotation < -45 )
298  {
300  }
301  else
302  {
304  }
305  }
306 
307  QgsTextBufferSettings buffer = format.buffer();
308 
309  const double penSize = buffer.sizeUnit() == QgsUnitTypes::RenderPercentage
310  ? context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() ) * buffer.size() / 100
311  : context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() );
312 
313  const double scaleFactor = calculateScaleFactorForFormat( context, format );
314 
315  std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
316  if ( mode == Label )
317  {
318  // label size has already been calculated using any symbology reference scale factor -- we need
319  // to temporarily remove the reference scale here or we'll be applying the scaling twice
320  referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, -1.0 ) );
321  }
322 
323  bool isNullSize = false;
324  const QFont font = format.scaledFont( context, scaleFactor, &isNullSize );
325  if ( isNullSize )
326  return 0;
327 
328  referenceScaleOverride.reset();
329 
330  QPainterPath path;
331  path.setFillRule( Qt::WindingFill );
332  double advance = 0;
333  switch ( orientation )
334  {
336  {
337  double xOffset = 0;
338  for ( const QgsTextFragment &fragment : component.block )
339  {
340  QFont fragmentFont = font;
341  fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
342 
343  if ( component.extraWordSpacing || component.extraLetterSpacing )
344  applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing );
345 
346  path.addText( xOffset, 0, fragmentFont, fragment.text() );
347 
348  xOffset += fragment.horizontalAdvance( fragmentFont, true, scaleFactor );
349  }
350  advance = xOffset;
351  break;
352  }
353 
356  {
357  double letterSpacing = font.letterSpacing();
358  double partYOffset = component.offset.y() * scaleFactor;
359  for ( const QgsTextFragment &fragment : component.block )
360  {
361  QFont fragmentFont = font;
362  fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
363 
364  QFontMetricsF fragmentMetrics( fragmentFont );
365  const double labelWidth = fragmentMetrics.maxWidth();
366 
367  const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
368  for ( const QString &part : parts )
369  {
370  double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) - letterSpacing ) ) / 2;
371  path.addText( partXOffset, partYOffset, fragmentFont, part );
372  partYOffset += fragmentMetrics.ascent() + letterSpacing;
373  }
374  }
375  advance = partYOffset - component.offset.y() * scaleFactor;
376  break;
377  }
378  }
379 
380  QColor bufferColor = buffer.color();
381  bufferColor.setAlphaF( buffer.opacity() );
382  QPen pen( bufferColor );
383  pen.setWidthF( penSize * scaleFactor );
384  pen.setJoinStyle( buffer.joinStyle() );
385  QColor tmpColor( bufferColor );
386  // honor pref for whether to fill buffer interior
387  if ( !buffer.fillBufferInterior() )
388  {
389  tmpColor.setAlpha( 0 );
390  }
391 
392  // store buffer's drawing in QPicture for drop shadow call
393  QPicture buffPict;
394  QPainter buffp;
395  buffp.begin( &buffPict );
396  if ( buffer.paintEffect() && buffer.paintEffect()->enabled() )
397  {
398  context.setPainter( &buffp );
399  std::unique_ptr< QgsPaintEffect > tmpEffect( buffer.paintEffect()->clone() );
400 
401  tmpEffect->begin( context );
402  context.painter()->setPen( pen );
403  context.painter()->setBrush( tmpColor );
404  if ( scaleFactor != 1.0 )
405  context.painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
406  context.painter()->drawPath( path );
407  if ( scaleFactor != 1.0 )
408  context.painter()->scale( scaleFactor, scaleFactor );
409  tmpEffect->end( context );
410 
411  context.setPainter( p );
412  }
413  else
414  {
415  if ( scaleFactor != 1.0 )
416  buffp.scale( 1 / scaleFactor, 1 / scaleFactor );
417  buffp.setPen( pen );
418  buffp.setBrush( tmpColor );
419  buffp.drawPath( path );
420  }
421  buffp.end();
422 
423  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer )
424  {
425  QgsTextRenderer::Component bufferComponent = component;
426  bufferComponent.origin = QPointF( 0.0, 0.0 );
427  bufferComponent.picture = buffPict;
428  bufferComponent.pictureBuffer = penSize / 2.0;
429 
431  {
432  bufferComponent.offset.setY( bufferComponent.offset.y() - bufferComponent.size.height() );
433  }
434  drawShadow( context, bufferComponent, format );
435  }
436 
437  QgsScopedQPainterState painterState( p );
438  context.setPainterFlagsUsingContext( p );
439 
440  if ( context.useAdvancedEffects() )
441  {
442  p->setCompositionMode( buffer.blendMode() );
443  }
444 
445  // scale for any print output or image saving @ specific dpi
446  p->scale( component.dpiRatio, component.dpiRatio );
447  _fixQPictureDPI( p );
448  p->drawPicture( 0, 0, buffPict );
449 
450  return advance / scaleFactor;
451 }
452 
453 void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format,
454  DrawMode mode )
455 {
456  QgsTextMaskSettings mask = format.mask();
457 
458  // the mask is drawn to a side painter
459  // or to the main painter for preview
460  QPainter *p = context.isGuiPreview() ? context.painter() : context.maskPainter( context.currentMaskId() );
461  if ( ! p )
462  return;
463 
464  double penSize = mask.sizeUnit() == QgsUnitTypes::RenderPercentage
465  ? context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() ) * mask.size() / 100
466  : context.convertToPainterUnits( mask.size(), mask.sizeUnit(), mask.sizeMapUnitScale() );
467 
468  // buffer: draw the text with a big pen
469  QPainterPath path;
470  path.setFillRule( Qt::WindingFill );
471 
472  const double scaleFactor = calculateScaleFactorForFormat( context, format );
473 
474  // TODO: vertical text mode was ignored when masking feature was added.
475  // Hopefully Oslandia come back and fix this? Hint hint...
476 
477  std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
478  if ( mode == Label )
479  {
480  // label size has already been calculated using any symbology reference scale factor -- we need
481  // to temporarily remove the reference scale here or we'll be applying the scaling twice
482  referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, -1.0 ) );
483  }
484 
485  bool isNullSize = false;
486  const QFont font = format.scaledFont( context, scaleFactor, &isNullSize );
487  if ( isNullSize )
488  return;
489 
490  referenceScaleOverride.reset();
491 
492  double xOffset = 0;
493  for ( const QgsTextFragment &fragment : component.block )
494  {
495  QFont fragmentFont = font;
496  fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
497 
498  path.addText( xOffset, 0, fragmentFont, fragment.text() );
499 
500  xOffset += fragment.horizontalAdvance( fragmentFont, true );
501  }
502 
503  QColor bufferColor( Qt::gray );
504  bufferColor.setAlphaF( mask.opacity() );
505 
506  QPen pen;
507  QBrush brush;
508  brush.setColor( bufferColor );
509  pen.setColor( bufferColor );
510  pen.setWidthF( penSize * scaleFactor );
511  pen.setJoinStyle( mask.joinStyle() );
512 
513  QgsScopedQPainterState painterState( p );
514  context.setPainterFlagsUsingContext( p );
515 
516  // scale for any print output or image saving @ specific dpi
517  p->scale( component.dpiRatio, component.dpiRatio );
518  if ( mask.paintEffect() && mask.paintEffect()->enabled() )
519  {
520  QgsPainterSwapper swapper( context, p );
521  {
522  QgsEffectPainter effectPainter( context, mask.paintEffect() );
523  if ( scaleFactor != 1.0 )
524  context.painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
525  context.painter()->setPen( pen );
526  context.painter()->setBrush( brush );
527  context.painter()->drawPath( path );
528  if ( scaleFactor != 1.0 )
529  context.painter()->scale( scaleFactor, scaleFactor );
530  }
531  }
532  else
533  {
534  if ( scaleFactor != 1.0 )
535  p->scale( 1 / scaleFactor, 1 / scaleFactor );
536  p->setPen( pen );
537  p->setBrush( brush );
538  p->drawPath( path );
539  if ( scaleFactor != 1.0 )
540  p->scale( scaleFactor, scaleFactor );
541 
542  }
543 }
544 
545 double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF * )
546 {
547  QgsTextDocument doc;
548  if ( !format.allowHtmlFormatting() )
549  {
550  doc = QgsTextDocument::fromPlainText( textLines );
551  }
552  else
553  {
554  doc = QgsTextDocument::fromHtml( textLines );
555  }
556  doc.applyCapitalization( format.capitalization() );
557  return textWidth( context, format, doc );
558 }
559 
560 double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document )
561 {
562  //calculate max width of text lines
563  const double scaleFactor = calculateScaleFactorForFormat( context, format );
564 
565  bool isNullSize = false;
566  const QFont baseFont = format.scaledFont( context, scaleFactor, &isNullSize );
567  if ( isNullSize )
568  return 0;
569 
570  double width = 0;
571  switch ( format.orientation() )
572  {
574  {
575  double maxLineWidth = 0;
576  for ( const QgsTextBlock &block : document )
577  {
578  double blockWidth = 0;
579  for ( const QgsTextFragment &fragment : block )
580  {
581  blockWidth += fragment.horizontalAdvance( baseFont, scaleFactor );
582  }
583  maxLineWidth = std::max( maxLineWidth, blockWidth );
584  }
585  width = maxLineWidth;
586  break;
587  }
588 
590  {
591  double totalLineWidth = 0;
592  int blockIndex = 0;
593  for ( const QgsTextBlock &block : document )
594  {
595  double blockWidth = 0;
596  for ( const QgsTextFragment &fragment : block )
597  {
598  QFont fragmentFont = baseFont;
599  fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
600  blockWidth = std::max( QFontMetricsF( fragmentFont ).maxWidth(), blockWidth );
601  }
602 
603  totalLineWidth += blockIndex == 0 ? blockWidth : blockWidth * format.lineHeight();
604  blockIndex++;
605  }
606  width = totalLineWidth;
607  break;
608  }
609 
611  {
612  // label mode only
613  break;
614  }
615  }
616 
617  return width / scaleFactor;
618 }
619 
620 double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF *, Qgis::TextRendererFlags flags, double maxLineWidth )
621 {
622  QStringList lines;
623  for ( const QString &line : textLines )
624  {
625  if ( flags & Qgis::TextRendererFlag::WrapLines && maxLineWidth > 0 && textRequiresWrapping( context, line, maxLineWidth, format ) )
626  {
627  lines.append( wrappedText( context, line, maxLineWidth, format ) );
628  }
629  else
630  {
631  lines.append( line );
632  }
633  }
634 
635  if ( !format.allowHtmlFormatting() )
636  {
637  return textHeight( context, format, QgsTextDocument::fromPlainText( lines ), mode );
638  }
639  else
640  {
641  return textHeight( context, format, QgsTextDocument::fromHtml( lines ), mode );
642  }
643 }
644 
645 double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects )
646 {
647  const double scaleFactor = calculateScaleFactorForFormat( context, format );
648  bool isNullSize = false;
649  const QFont baseFont = format.scaledFont( context, scaleFactor, &isNullSize );
650  if ( isNullSize )
651  return 0;
652 
653  const QFontMetrics fm( baseFont );
654  const double height = ( character.isNull() ? fm.height() : fm.boundingRect( character ).height() ) / scaleFactor;
655 
656  if ( !includeEffects )
657  return height;
658 
659  double maxExtension = 0;
660  const double fontSize = context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() );
661  if ( format.buffer().enabled() )
662  {
663  maxExtension += format.buffer().sizeUnit() == QgsUnitTypes::RenderPercentage
664  ? fontSize * format.buffer().size() / 100
665  : context.convertToPainterUnits( format.buffer().size(), format.buffer().sizeUnit(), format.buffer().sizeMapUnitScale() );
666  }
667  if ( format.shadow().enabled() )
668  {
669  maxExtension += ( format.shadow().offsetUnit() == QgsUnitTypes::RenderPercentage
670  ? fontSize * format.shadow().offsetDistance() / 100
671  : context.convertToPainterUnits( format.shadow().offsetDistance(), format.shadow().offsetUnit(), format.shadow().offsetMapUnitScale() )
672  )
674  ? fontSize * format.shadow().blurRadius() / 100
675  : context.convertToPainterUnits( format.shadow().blurRadius(), format.shadow().blurRadiusUnit(), format.shadow().blurRadiusMapUnitScale() )
676  );
677  }
678  if ( format.background().enabled() )
679  {
680  maxExtension += context.convertToPainterUnits( std::fabs( format.background().offset().y() ), format.background().offsetUnit(), format.background().offsetMapUnitScale() )
681  + context.convertToPainterUnits( format.background().strokeWidth(), format.background().strokeWidthUnit(), format.background().strokeWidthMapUnitScale() ) / 2.0;
682  if ( format.background().sizeType() == QgsTextBackgroundSettings::SizeBuffer && format.background().size().height() > 0 )
683  {
684  maxExtension += context.convertToPainterUnits( format.background().size().height(), format.background().sizeUnit(), format.background().sizeMapUnitScale() );
685  }
686  }
687 
688  return height + maxExtension;
689 }
690 
691 bool QgsTextRenderer::textRequiresWrapping( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format )
692 {
693  if ( qgsDoubleNear( width, 0.0 ) )
694  return false;
695 
696  const QStringList multiLineSplit = text.split( '\n' );
697  const double currentTextWidth = QgsTextRenderer::textWidth( context, format, multiLineSplit );
698  return currentTextWidth > width;
699 }
700 
701 QStringList QgsTextRenderer::wrappedText( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format )
702 {
703  const QStringList lines = text.split( '\n' );
704  QStringList outLines;
705  for ( const QString &line : lines )
706  {
707  if ( textRequiresWrapping( context, line, width, format ) )
708  {
709  //first step is to identify words which must be on their own line (too long to fit)
710  const QStringList words = line.split( ' ' );
711  QStringList linesToProcess;
712  QString wordsInCurrentLine;
713  for ( const QString &word : words )
714  {
715  if ( textRequiresWrapping( context, word, width, format ) )
716  {
717  //too long to fit
718  if ( !wordsInCurrentLine.isEmpty() )
719  linesToProcess << wordsInCurrentLine;
720  wordsInCurrentLine.clear();
721  linesToProcess << word;
722  }
723  else
724  {
725  if ( !wordsInCurrentLine.isEmpty() )
726  wordsInCurrentLine.append( ' ' );
727  wordsInCurrentLine.append( word );
728  }
729  }
730  if ( !wordsInCurrentLine.isEmpty() )
731  linesToProcess << wordsInCurrentLine;
732 
733  for ( const QString &line : std::as_const( linesToProcess ) )
734  {
735  QString remainingText = line;
736  int lastPos = remainingText.lastIndexOf( ' ' );
737  while ( lastPos > -1 )
738  {
739  //check if remaining text is short enough to go in one line
740  if ( !textRequiresWrapping( context, remainingText, width, format ) )
741  {
742  break;
743  }
744 
745  if ( !textRequiresWrapping( context, remainingText.left( lastPos ), width, format ) )
746  {
747  outLines << remainingText.left( lastPos );
748  remainingText = remainingText.mid( lastPos + 1 );
749  lastPos = 0;
750  }
751  lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
752  }
753  outLines << remainingText;
754  }
755  }
756  else
757  {
758  outLines << line;
759  }
760  }
761 
762  return outLines;
763 }
764 
765 double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &doc, DrawMode mode )
766 {
767  QgsTextDocument document = doc;
768  document.applyCapitalization( format.capitalization() );
769 
770  //calculate max height of text lines
771  const double scaleFactor = calculateScaleFactorForFormat( context, format );
772 
773  bool isNullSize = false;
774  const QFont baseFont = format.scaledFont( context, scaleFactor, &isNullSize );
775  if ( isNullSize )
776  return 0;
777 
778  switch ( format.orientation() )
779  {
781  {
782  int blockIndex = 0;
783  double totalHeight = 0;
784  double lastLineLeading = 0;
785  for ( const QgsTextBlock &block : document )
786  {
787  double maxBlockHeight = 0;
788  double maxBlockLineSpacing = 0;
789  double maxBlockLeading = 0;
790  for ( const QgsTextFragment &fragment : block )
791  {
792  QFont fragmentFont = baseFont;
793  fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
794  const QFontMetricsF fm( fragmentFont );
795 
796  const double fragmentHeight = fm.ascent() + fm.descent(); // ignore +1 for baseline
797 
798  maxBlockHeight = std::max( maxBlockHeight, fragmentHeight );
799  if ( fm.lineSpacing() > maxBlockLineSpacing )
800  {
801  maxBlockLineSpacing = fm.lineSpacing();
802  maxBlockLeading = fm.leading();
803  }
804  }
805 
806  switch ( mode )
807  {
808  case Label:
809  // rendering labels needs special handling - in this case text should be
810  // drawn with the bottom left corner coinciding with origin, vs top left
811  // for standard text rendering. Line height is also slightly different.
812  totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockHeight * format.lineHeight();
813  break;
814 
815  case Rect:
816  case Point:
817  // standard rendering - designed to exactly replicate QPainter's drawText method
818  totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockLineSpacing * format.lineHeight();
819  if ( blockIndex > 0 )
820  lastLineLeading = maxBlockLeading;
821  break;
822  }
823 
824  blockIndex++;
825  }
826 
827  return ( totalHeight - lastLineLeading ) / scaleFactor;
828  }
829 
831  {
832  double maxBlockHeight = 0;
833  for ( const QgsTextBlock &block : document )
834  {
835  double blockHeight = 0;
836  int fragmentIndex = 0;
837  for ( const QgsTextFragment &fragment : block )
838  {
839  QFont fragmentFont = baseFont;
840  fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
841  const QFontMetricsF fm( fragmentFont );
842 
843  const double labelHeight = fm.ascent();
844  const double letterSpacing = fragmentFont.letterSpacing();
845 
846  blockHeight += fragmentIndex = 0 ? labelHeight * fragment.text().size() + ( fragment.text().size() - 1 ) * letterSpacing
847  : fragment.text().size() * ( labelHeight + letterSpacing );
848  fragmentIndex++;
849  }
850  maxBlockHeight = std::max( maxBlockHeight, blockHeight );
851  }
852 
853  return maxBlockHeight / scaleFactor;
854  }
855 
857  {
858  // label mode only
859  break;
860  }
861  }
862 
863  return 0;
864 }
865 
866 void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer::Component component, const QgsTextFormat &format, const QgsTextDocument &document, QgsTextRenderer::DrawMode mode )
867 {
868  QgsTextBackgroundSettings background = format.background();
869 
870  QPainter *prevP = context.painter();
871  QPainter *p = context.painter();
872  std::unique_ptr< QgsPaintEffect > tmpEffect;
873  if ( background.paintEffect() && background.paintEffect()->enabled() )
874  {
875  tmpEffect.reset( background.paintEffect()->clone() );
876  tmpEffect->begin( context );
877  p = context.painter();
878  }
879 
880  //QgsDebugMsgLevel( QStringLiteral( "Background label rotation: %1" ).arg( component.rotation() ), 4 );
881 
882  // shared calculations between shapes and SVG
883 
884  // configure angles, set component rotation and rotationOffset
885  const double originAdjustRotationRadians = -component.rotation;
887  {
888  component.rotation = -( component.rotation * 180 / M_PI ); // RotationSync
889  component.rotationOffset =
890  background.rotationType() == QgsTextBackgroundSettings::RotationOffset ? background.rotation() : 0.0;
891  }
892  else // RotationFixed
893  {
894  component.rotation = 0.0; // don't use label's rotation
895  component.rotationOffset = background.rotation();
896  }
897 
898  const double scaleFactor = calculateScaleFactorForFormat( context, format );
899 
900  if ( mode != Label )
901  {
902  // need to calculate size of text
903  double width = textWidth( context, format, document );
904  double height = textHeight( context, format, document, mode );
905 
906  switch ( mode )
907  {
908  case Rect:
909  switch ( component.hAlign )
910  {
911  case AlignLeft:
912  case AlignJustify:
913  component.center = QPointF( component.origin.x() + width / 2.0,
914  component.origin.y() + height / 2.0 );
915  break;
916 
917  case AlignCenter:
918  component.center = QPointF( component.origin.x() + component.size.width() / 2.0,
919  component.origin.y() + height / 2.0 );
920  break;
921 
922  case AlignRight:
923  component.center = QPointF( component.origin.x() + component.size.width() - width / 2.0,
924  component.origin.y() + height / 2.0 );
925  break;
926  }
927  break;
928 
929  case Point:
930  {
931  bool isNullSize = false;
932  QFontMetricsF fm( format.scaledFont( context, scaleFactor, &isNullSize ) );
933  double originAdjust = isNullSize ? 0 : ( fm.ascent() / scaleFactor / 2.0 - fm.leading() / scaleFactor / 2.0 );
934  switch ( component.hAlign )
935  {
936  case AlignLeft:
937  case AlignJustify:
938  component.center = QPointF( component.origin.x() + width / 2.0,
939  component.origin.y() - height / 2.0 + originAdjust );
940  break;
941 
942  case AlignCenter:
943  component.center = QPointF( component.origin.x(),
944  component.origin.y() - height / 2.0 + originAdjust );
945  break;
946 
947  case AlignRight:
948  component.center = QPointF( component.origin.x() - width / 2.0,
949  component.origin.y() - height / 2.0 + originAdjust );
950  break;
951  }
952 
953  // apply rotation to center point
954  if ( !qgsDoubleNear( originAdjustRotationRadians, 0 ) )
955  {
956  const double dx = component.center.x() - component.origin.x();
957  const double dy = component.center.y() - component.origin.y();
958  component.center.setX( component.origin.x() + ( std::cos( originAdjustRotationRadians ) * dx - std::sin( originAdjustRotationRadians ) * dy ) );
959  component.center.setY( component.origin.y() + ( std::sin( originAdjustRotationRadians ) * dx + std::cos( originAdjustRotationRadians ) * dy ) );
960  }
961  break;
962  }
963 
964  case Label:
965  break;
966  }
967 
969  component.size = QSizeF( width, height );
970  }
971 
972  // TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
973 
974  switch ( background.type() )
975  {
978  {
979  // all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
980 
981  if ( background.type() == QgsTextBackgroundSettings::ShapeSVG && background.svgFile().isEmpty() )
982  return;
983 
984  if ( background.type() == QgsTextBackgroundSettings::ShapeMarkerSymbol && !background.markerSymbol() )
985  return;
986 
987  double sizeOut = 0.0;
988  // only one size used for SVG/marker symbol sizing/scaling (no use of shapeSize.y() or Y field in gui)
989  if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
990  {
991  sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
992  }
993  else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
994  {
995  sizeOut = std::max( component.size.width(), component.size.height() );
996  double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
997 
998  // add buffer
999  sizeOut += bufferSize * 2;
1000  }
1001 
1002  // don't bother rendering symbols smaller than 1x1 pixels in size
1003  // TODO: add option to not show any svgs under/over a certain size
1004  if ( sizeOut < 1.0 )
1005  return;
1006 
1007  std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
1008  if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
1009  {
1010  QVariantMap map; // for SVG symbology marker
1011  map[QStringLiteral( "name" )] = background.svgFile().trimmed();
1012  map[QStringLiteral( "size" )] = QString::number( sizeOut );
1013  map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
1014  map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
1015 
1016  // offset is handled by this local painter
1017  // TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
1018  //map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
1019  //map["offset_unit"] = QgsUnitTypes::encodeUnit(
1020  // tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
1021 
1022  map[QStringLiteral( "fill" )] = background.fillColor().name();
1023  map[QStringLiteral( "outline" )] = background.strokeColor().name();
1024  map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
1025  map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
1026 
1027  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
1028  {
1029  QgsTextShadowSettings shadow = format.shadow();
1030  // configure SVG shadow specs
1031  QVariantMap shdwmap( map );
1032  shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
1033  shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
1034  shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
1035 
1036  // store SVG's drawing in QPicture for drop shadow call
1037  QPicture svgPict;
1038  QPainter svgp;
1039  svgp.begin( &svgPict );
1040 
1041  // draw shadow symbol
1042 
1043  // clone current render context map unit/mm conversion factors, but not
1044  // other map canvas parameters, then substitute this painter for use in symbology painting
1045  // NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
1046  // but will be created relative to the SVG's computed size, not the current map canvas
1047  QgsRenderContext shdwContext;
1048  shdwContext.setMapToPixel( context.mapToPixel() );
1049  shdwContext.setScaleFactor( context.scaleFactor() );
1050  shdwContext.setPainter( &svgp );
1051 
1052  std::unique_ptr< QgsSymbolLayer > symShdwL( QgsSvgMarkerSymbolLayer::create( shdwmap ) );
1053  QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL.get() );
1054  QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
1055 
1056  svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
1057  svgp.end();
1058 
1059  component.picture = svgPict;
1060  // TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
1061  component.pictureBuffer = 0.0;
1062 
1063  component.size = QSizeF( sizeOut, sizeOut );
1064  component.offset = QPointF( 0.0, 0.0 );
1065 
1066  // rotate about origin center of SVG
1067  QgsScopedQPainterState painterState( p );
1068  context.setPainterFlagsUsingContext( p );
1069 
1070  p->translate( component.center.x(), component.center.y() );
1071  p->rotate( component.rotation );
1072  double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
1073  double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
1074  p->translate( QPointF( xoff, yoff ) );
1075  p->rotate( component.rotationOffset );
1076  p->translate( -sizeOut / 2, sizeOut / 2 );
1077 
1078  drawShadow( context, component, format );
1079  }
1080  renderedSymbol.reset( );
1081 
1083  renderedSymbol.reset( new QgsMarkerSymbol( QgsSymbolLayerList() << symL ) );
1084  }
1085  else
1086  {
1087  renderedSymbol.reset( background.markerSymbol()->clone() );
1088  renderedSymbol->setSize( sizeOut );
1089  renderedSymbol->setSizeUnit( QgsUnitTypes::RenderPixels );
1090  }
1091 
1092  renderedSymbol->setOpacity( renderedSymbol->opacity() * background.opacity() );
1093 
1094  // draw the actual symbol
1095  QgsScopedQPainterState painterState( p );
1096  context.setPainterFlagsUsingContext( p );
1097 
1098  if ( context.useAdvancedEffects() )
1099  {
1100  p->setCompositionMode( background.blendMode() );
1101  }
1102  p->translate( component.center.x(), component.center.y() );
1103  p->rotate( component.rotation );
1104  double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
1105  double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
1106  p->translate( QPointF( xoff, yoff ) );
1107  p->rotate( component.rotationOffset );
1108 
1109  const QgsFeature f = context.expressionContext().feature();
1110  renderedSymbol->startRender( context, context.expressionContext().fields() );
1111  renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
1112  renderedSymbol->stopRender( context );
1113  p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
1114 
1115  break;
1116  }
1117 
1122  {
1123  double w = component.size.width();
1124  double h = component.size.height();
1125 
1126  if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
1127  {
1128  w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
1129  background.sizeMapUnitScale() );
1130  h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
1131  background.sizeMapUnitScale() );
1132  }
1133  else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
1134  {
1135  if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
1136  {
1137  if ( w > h )
1138  h = w;
1139  else if ( h > w )
1140  w = h;
1141  }
1142  else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
1143  {
1144  // start with label bound by circle
1145  h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
1146  w = h;
1147  }
1148  else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
1149  {
1150  // start with label bound by ellipse
1151  h = h * M_SQRT1_2 * 2;
1152  w = w * M_SQRT1_2 * 2;
1153  }
1154 
1155  double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
1156  background.sizeMapUnitScale() );
1157  double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
1158  background.sizeMapUnitScale() );
1159 
1160  w += bufferWidth * 2;
1161  h += bufferHeight * 2;
1162  }
1163 
1164  // offsets match those of symbology: -x = left, -y = up
1165  QRectF rect( -w / 2.0, - h / 2.0, w, h );
1166 
1167  if ( rect.isNull() )
1168  return;
1169 
1170  QgsScopedQPainterState painterState( p );
1171  context.setPainterFlagsUsingContext( p );
1172 
1173  p->translate( QPointF( component.center.x(), component.center.y() ) );
1174  p->rotate( component.rotation );
1175  double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
1176  double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
1177  p->translate( QPointF( xoff, yoff ) );
1178  p->rotate( component.rotationOffset );
1179 
1180  QPainterPath path;
1181 
1182  // Paths with curves must be enlarged before conversion to QPolygonF, or
1183  // the curves are approximated too much and appear jaggy
1184  QTransform t = QTransform::fromScale( 10, 10 );
1185  // inverse transform used to scale created polygons back to expected size
1186  QTransform ti = t.inverted();
1187 
1188  if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
1189  || background.type() == QgsTextBackgroundSettings::ShapeSquare )
1190  {
1191  if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
1192  {
1193  path.addRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
1194  }
1195  else
1196  {
1197  const double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
1198  const double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
1199  path.addRoundedRect( rect, xRadius, yRadius );
1200  }
1201  }
1202  else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
1203  || background.type() == QgsTextBackgroundSettings::ShapeCircle )
1204  {
1205  path.addEllipse( rect );
1206  }
1207  QPolygonF tempPolygon = path.toFillPolygon( t );
1208  QPolygonF polygon = ti.map( tempPolygon );
1209  QPicture shapePict;
1210  QPainter *oldp = context.painter();
1211  QPainter shapep;
1212 
1213  shapep.begin( &shapePict );
1214  context.setPainter( &shapep );
1215 
1216  std::unique_ptr< QgsFillSymbol > renderedSymbol;
1217  renderedSymbol.reset( background.fillSymbol()->clone() );
1218  renderedSymbol->setOpacity( renderedSymbol->opacity() * background.opacity() );
1219 
1220  const QgsFeature f = context.expressionContext().feature();
1221  renderedSymbol->startRender( context, context.expressionContext().fields() );
1222  renderedSymbol->renderPolygon( polygon, nullptr, &f, context );
1223  renderedSymbol->stopRender( context );
1224 
1225  shapep.end();
1226  context.setPainter( oldp );
1227 
1228  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
1229  {
1230  component.picture = shapePict;
1231  component.pictureBuffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( renderedSymbol.get(), context ) * 2;
1232 
1233  component.size = rect.size();
1234  component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
1235  drawShadow( context, component, format );
1236  }
1237 
1238  if ( context.useAdvancedEffects() )
1239  {
1240  p->setCompositionMode( background.blendMode() );
1241  }
1242 
1243  // scale for any print output or image saving @ specific dpi
1244  p->scale( component.dpiRatio, component.dpiRatio );
1245  _fixQPictureDPI( p );
1246  p->drawPicture( 0, 0, shapePict );
1247  p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
1248  break;
1249  }
1250  }
1251 
1252  if ( tmpEffect )
1253  {
1254  tmpEffect->end( context );
1255  context.setPainter( prevP );
1256  }
1257 }
1258 
1259 void QgsTextRenderer::drawShadow( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
1260 {
1261  QgsTextShadowSettings shadow = format.shadow();
1262 
1263  // incoming component sizes should be multiplied by rasterCompressFactor, as
1264  // this allows shadows to be created at paint device dpi (e.g. high resolution),
1265  // then scale device painter by 1.0 / rasterCompressFactor for output
1266 
1267  QPainter *p = context.painter();
1268  double componentWidth = component.size.width(), componentHeight = component.size.height();
1269  double xOffset = component.offset.x(), yOffset = component.offset.y();
1270  double pictbuffer = component.pictureBuffer;
1271 
1272  // generate pixmap representation of label component drawing
1273  bool mapUnits = shadow.blurRadiusUnit() == QgsUnitTypes::RenderMapUnits;
1274 
1275  const double fontSize = context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() );
1276  double radius = shadow.blurRadiusUnit() == QgsUnitTypes::RenderPercentage
1277  ? fontSize * shadow.blurRadius() / 100
1278  : context.convertToPainterUnits( shadow.blurRadius(), shadow.blurRadiusUnit(), shadow.blurRadiusMapUnitScale() );
1279  radius /= ( mapUnits ? context.scaleFactor() / component.dpiRatio : 1 );
1280  radius = static_cast< int >( radius + 0.5 ); //NOLINT
1281 
1282  // TODO: add labeling gui option to adjust blurBufferClippingScale to minimize pixels, or
1283  // to ensure shadow isn't clipped too tight. (Or, find a better method of buffering)
1284  double blurBufferClippingScale = 3.75;
1285  int blurbuffer = ( radius > 17 ? 16 : radius ) * blurBufferClippingScale;
1286 
1287  QImage blurImg( componentWidth + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
1288  componentHeight + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
1289  QImage::Format_ARGB32_Premultiplied );
1290 
1291  // TODO: add labeling gui option to not show any shadows under/over a certain size
1292  // keep very small QImages from causing paint device issues, i.e. must be at least > 1
1293  int minBlurImgSize = 1;
1294  // max limitation on QgsSvgCache is 10,000 for screen, which will probably be reasonable for future caching here, too
1295  // 4 x QgsSvgCache limit for output to print/image at higher dpi
1296  // TODO: should it be higher, scale with dpi, or have no limit? Needs testing with very large labels rendered at high dpi output
1297  int maxBlurImgSize = 40000;
1298  if ( blurImg.isNull()
1299  || ( blurImg.width() < minBlurImgSize || blurImg.height() < minBlurImgSize )
1300  || ( blurImg.width() > maxBlurImgSize || blurImg.height() > maxBlurImgSize ) )
1301  return;
1302 
1303  blurImg.fill( QColor( Qt::transparent ).rgba() );
1304  QPainter pictp;
1305  if ( !pictp.begin( &blurImg ) )
1306  return;
1307  pictp.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
1308  QPointF imgOffset( blurbuffer + pictbuffer + xOffset,
1309  blurbuffer + pictbuffer + componentHeight + yOffset );
1310 
1311  pictp.drawPicture( imgOffset,
1312  component.picture );
1313 
1314  // overlay shadow color
1315  pictp.setCompositionMode( QPainter::CompositionMode_SourceIn );
1316  pictp.fillRect( blurImg.rect(), shadow.color() );
1317  pictp.end();
1318 
1319  // blur the QImage in-place
1320  if ( shadow.blurRadius() > 0.0 && radius > 0 )
1321  {
1322  QgsSymbolLayerUtils::blurImageInPlace( blurImg, blurImg.rect(), radius, shadow.blurAlphaOnly() );
1323  }
1324 
1325 #if 0
1326  // debug rect for QImage shadow registration and clipping visualization
1327  QPainter picti;
1328  picti.begin( &blurImg );
1329  picti.setBrush( Qt::Dense7Pattern );
1330  QPen imgPen( QColor( 0, 0, 255, 255 ) );
1331  imgPen.setWidth( 1 );
1332  picti.setPen( imgPen );
1333  picti.setOpacity( 0.1 );
1334  picti.drawRect( 0, 0, blurImg.width(), blurImg.height() );
1335  picti.end();
1336 #endif
1337 
1338  const double offsetDist = shadow.offsetUnit() == QgsUnitTypes::RenderPercentage
1339  ? fontSize * shadow.offsetDistance() / 100
1340  : context.convertToPainterUnits( shadow.offsetDistance(), shadow.offsetUnit(), shadow.offsetMapUnitScale() );
1341  double angleRad = shadow.offsetAngle() * M_PI / 180; // to radians
1342  if ( shadow.offsetGlobal() )
1343  {
1344  // TODO: check for differences in rotation origin and cw/ccw direction,
1345  // when this shadow function is used for something other than labels
1346 
1347  // it's 0-->cw-->360 for labels
1348  //QgsDebugMsgLevel( QStringLiteral( "Shadow aggregated label rotation (degrees): %1" ).arg( component.rotation() + component.rotationOffset() ), 4 );
1349  angleRad -= ( component.rotation * M_PI / 180 + component.rotationOffset * M_PI / 180 );
1350  }
1351 
1352  QPointF transPt( -offsetDist * std::cos( angleRad + M_PI_2 ),
1353  -offsetDist * std::sin( angleRad + M_PI_2 ) );
1354 
1355  p->save();
1356  context.setPainterFlagsUsingContext( p );
1357  // this was historically ALWAYS set for text renderer. We may want to consider getting it to respect the
1358  // corresponding flag in the render context instead...
1359  p->setRenderHint( QPainter::SmoothPixmapTransform );
1360  if ( context.useAdvancedEffects() )
1361  {
1362  p->setCompositionMode( shadow.blendMode() );
1363  }
1364  p->setOpacity( shadow.opacity() );
1365 
1366  double scale = shadow.scale() / 100.0;
1367  // TODO: scale from center/center, left/center or left/top, instead of default left/bottom?
1368  p->scale( scale, scale );
1369  if ( component.useOrigin )
1370  {
1371  p->translate( component.origin.x(), component.origin.y() );
1372  }
1373  p->translate( transPt );
1374  p->translate( -imgOffset.x(),
1375  -imgOffset.y() );
1376  p->drawImage( 0, 0, blurImg );
1377  p->restore();
1378 
1379  // debug rects
1380 #if 0
1381  // draw debug rect for QImage painting registration
1382  p->save();
1383  p->setBrush( Qt::NoBrush );
1384  QPen imgPen( QColor( 255, 0, 0, 10 ) );
1385  imgPen.setWidth( 2 );
1386  imgPen.setStyle( Qt::DashLine );
1387  p->setPen( imgPen );
1388  p->scale( scale, scale );
1389  if ( component.useOrigin() )
1390  {
1391  p->translate( component.origin().x(), component.origin().y() );
1392  }
1393  p->translate( transPt );
1394  p->translate( -imgOffset.x(),
1395  -imgOffset.y() );
1396  p->drawRect( 0, 0, blurImg.width(), blurImg.height() );
1397  p->restore();
1398 
1399  // draw debug rect for passed in component dimensions
1400  p->save();
1401  p->setBrush( Qt::NoBrush );
1402  QPen componentRectPen( QColor( 0, 255, 0, 70 ) );
1403  componentRectPen.setWidth( 1 );
1404  if ( component.useOrigin() )
1405  {
1406  p->translate( component.origin().x(), component.origin().y() );
1407  }
1408  p->setPen( componentRectPen );
1409  p->drawRect( QRect( -xOffset, -componentHeight - yOffset, componentWidth, componentHeight ) );
1410  p->restore();
1411 #endif
1412 }
1413 
1414 
1415 void QgsTextRenderer::drawTextInternal( TextPart drawType,
1416  QgsRenderContext &context,
1417  const QgsTextFormat &format,
1418  const Component &component,
1419  const QgsTextDocument &document,
1420  const QFontMetricsF *fontMetrics,
1421  HAlignment alignment, VAlignment vAlignment, DrawMode mode )
1422 {
1423  if ( !context.painter() )
1424  {
1425  return;
1426  }
1427 
1428  double fontScale = 1.0;
1429  std::unique_ptr< QFontMetricsF > tmpMetrics;
1430  if ( !fontMetrics )
1431  {
1432  fontScale = calculateScaleFactorForFormat( context, format );
1433 
1434  std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1435  if ( mode == Label )
1436  {
1437  // label size has already been calculated using any symbology reference scale factor -- we need
1438  // to temporarily remove the reference scale here or we'll be applying the scaling twice
1439  referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, -1.0 ) );
1440  }
1441 
1442  bool isNullSize = false;
1443  const QFont f = format.scaledFont( context, fontScale, &isNullSize );
1444  if ( isNullSize )
1445  return;
1446 
1447  tmpMetrics = std::make_unique< QFontMetricsF >( f );
1448  fontMetrics = tmpMetrics.get();
1449 
1450  referenceScaleOverride.reset();
1451  }
1452 
1453  double rotation = 0;
1454  const QgsTextFormat::TextOrientation orientation = calculateRotationAndOrientationForComponent( format, component, rotation );
1455  switch ( orientation )
1456  {
1458  {
1459  drawTextInternalHorizontal( context, format, drawType, mode, component, document, fontScale, fontMetrics, alignment, vAlignment, rotation );
1460  break;
1461  }
1462 
1465  {
1466  drawTextInternalVertical( context, format, drawType, mode, component, document, fontScale, fontMetrics, alignment, vAlignment, rotation );
1467  break;
1468  }
1469  }
1470 }
1471 
1472 QgsTextFormat::TextOrientation QgsTextRenderer::calculateRotationAndOrientationForComponent( const QgsTextFormat &format, const QgsTextRenderer::Component &component, double &rotation )
1473 {
1474  rotation = -component.rotation * 180 / M_PI;
1475 
1476  switch ( format.orientation() )
1477  {
1479  {
1480  // Between 45 to 135 and 235 to 315 degrees, rely on vertical orientation
1481  if ( rotation >= -315 && rotation < -90 )
1482  {
1483  rotation -= 90;
1485  }
1486  else if ( rotation >= -90 && rotation < -45 )
1487  {
1488  rotation += 90;
1490  }
1491 
1493  }
1494 
1497  return format.orientation();
1498  }
1500 }
1501 
1502 void QgsTextRenderer::calculateExtraSpacingForLineJustification( const double spaceToDistribute, const QgsTextBlock &block, double &extraWordSpace, double &extraLetterSpace )
1503 {
1504  const QString blockText = block.toPlainText();
1505  QTextBoundaryFinder finder( QTextBoundaryFinder::Word, blockText );
1506  finder.toStart();
1507  int wordBoundaries = 0;
1508  while ( finder.toNextBoundary() != -1 )
1509  {
1510  if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
1511  wordBoundaries++;
1512  }
1513 
1514  if ( wordBoundaries > 0 )
1515  {
1516  // word boundaries found => justify by padding word spacing
1517  extraWordSpace = spaceToDistribute / wordBoundaries;
1518  }
1519  else
1520  {
1521  // no word boundaries found => justify by letter spacing
1522  QTextBoundaryFinder finder( QTextBoundaryFinder::Grapheme, blockText );
1523  finder.toStart();
1524 
1525  int graphemeBoundaries = 0;
1526  while ( finder.toNextBoundary() != -1 )
1527  {
1528  if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
1529  graphemeBoundaries++;
1530  }
1531 
1532  if ( graphemeBoundaries > 0 )
1533  {
1534  extraLetterSpace = spaceToDistribute / graphemeBoundaries;
1535  }
1536  }
1537 }
1538 
1539 void QgsTextRenderer::applyExtraSpacingForLineJustification( QFont &font, double extraWordSpace, double extraLetterSpace )
1540 {
1541  const double prevWordSpace = font.wordSpacing();
1542  font.setWordSpacing( prevWordSpace + extraWordSpace );
1543  const double prevLetterSpace = font.letterSpacing();
1544  font.setLetterSpacing( QFont::AbsoluteSpacing, prevLetterSpace + extraLetterSpace );
1545 }
1546 
1547 void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, TextPart drawType, DrawMode mode, const Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, HAlignment hAlignment,
1548  VAlignment vAlignment, double rotation )
1549 {
1550  QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
1551  const QStringList textLines = document.toPlainText();
1552 
1553  double labelWidest = 0.0;
1554  switch ( mode )
1555  {
1556  case Label:
1557  case Point:
1558  for ( const QString &line : textLines )
1559  {
1560  double labelWidth = fontMetrics->horizontalAdvance( line ) / fontScale;
1561  if ( labelWidth > labelWidest )
1562  {
1563  labelWidest = labelWidth;
1564  }
1565  }
1566  break;
1567 
1568  case Rect:
1569  labelWidest = component.size.width();
1570  break;
1571  }
1572 
1573  double labelHeight = ( fontMetrics->ascent() + fontMetrics->descent() ) / fontScale; // ignore +1 for baseline
1574  // double labelHighest = labelfm->height() + ( double )(( lines - 1 ) * labelHeight * tmpLyr.multilineHeight );
1575 
1576  // needed to move bottom of text's descender to within bottom edge of label
1577  double ascentOffset = 0.25 * fontMetrics->ascent() / fontScale; // labelfm->descent() is not enough
1578 
1579  int i = 0;
1580 
1581  bool adjustForAlignment = hAlignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
1582 
1583  if ( mode == Rect && vAlignment != AlignTop )
1584  {
1585  std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1586 
1587  const double overallHeight = textHeight( context, format, textLines, Rect );
1588  switch ( vAlignment )
1589  {
1590  case AlignTop:
1591  break;
1592 
1593  case AlignVCenter:
1594  ascentOffset = -( component.size.height() - overallHeight ) * 0.5 + ascentOffset;
1595  break;
1596 
1597  case AlignBottom:
1598  ascentOffset = -( component.size.height() - overallHeight ) + ascentOffset;
1599  break;
1600  }
1601  referenceScaleOverride.reset();
1602  }
1603 
1604  for ( const QString &line : std::as_const( textLines ) )
1605  {
1606  const QgsTextBlock block = document.at( i );
1607 
1608  const bool isFinalLineInParagraph = ( i == document.size() - 1 )
1609  || document.at( i + 1 ).toPlainText().trimmed().isEmpty();
1610 
1611  QgsScopedQPainterState painterState( context.painter() );
1612  context.setPainterFlagsUsingContext();
1613  context.painter()->translate( component.origin );
1614  if ( !qgsDoubleNear( rotation, 0.0 ) )
1615  context.painter()->rotate( rotation );
1616 
1617  // apply to the mask painter the same transformations
1618  if ( maskPainter )
1619  {
1620  maskPainter->save();
1621  maskPainter->translate( component.origin );
1622  if ( !qgsDoubleNear( rotation, 0.0 ) )
1623  maskPainter->rotate( rotation );
1624  }
1625 
1626  // figure x offset for horizontal alignment of multiple lines
1627  double xMultiLineOffset = 0.0;
1628  double labelWidth = fontMetrics->horizontalAdvance( line ) / fontScale;
1629  double extraWordSpace = 0;
1630  double extraLetterSpace = 0;
1631  if ( adjustForAlignment )
1632  {
1633  double labelWidthDiff = 0;
1634  switch ( hAlignment )
1635  {
1636  case AlignCenter:
1637  labelWidthDiff = ( labelWidest - labelWidth ) * 0.5;
1638  break;
1639 
1640  case AlignRight:
1641  labelWidthDiff = labelWidest - labelWidth;
1642  break;
1643 
1644  case AlignJustify:
1645  if ( !isFinalLineInParagraph && labelWidest > labelWidth )
1646  {
1647  calculateExtraSpacingForLineJustification( labelWidest - labelWidth, block, extraWordSpace, extraLetterSpace );
1648  labelWidth = labelWidest;
1649  }
1650  break;
1651 
1652  case AlignLeft:
1653  break;
1654  }
1655 
1656  switch ( mode )
1657  {
1658  case Label:
1659  case Rect:
1660  xMultiLineOffset = labelWidthDiff;
1661  break;
1662 
1663  case Point:
1664  {
1665  switch ( hAlignment )
1666  {
1667  case AlignRight:
1668  xMultiLineOffset = labelWidthDiff - labelWidest;
1669  break;
1670 
1671  case AlignCenter:
1672  xMultiLineOffset = labelWidthDiff - labelWidest / 2.0;
1673  break;
1674 
1675  case AlignLeft:
1676  case AlignJustify:
1677  break;
1678  }
1679  }
1680  break;
1681  }
1682  }
1683 
1684  double yMultiLineOffset = ascentOffset;
1685  switch ( mode )
1686  {
1687  case Label:
1688  // rendering labels needs special handling - in this case text should be
1689  // drawn with the bottom left corner coinciding with origin, vs top left
1690  // for standard text rendering. Line height is also slightly different.
1691  yMultiLineOffset = - ascentOffset - ( textLines.size() - 1 - i ) * labelHeight * format.lineHeight();
1692  break;
1693 
1694  case Rect:
1695  // standard rendering - designed to exactly replicate QPainter's drawText method
1696  yMultiLineOffset = - ascentOffset + labelHeight - 1 /*baseline*/ + format.lineHeight() * fontMetrics->lineSpacing() * i / fontScale;
1697  break;
1698 
1699  case Point:
1700  // standard rendering - designed to exactly replicate QPainter's drawText rect method
1701  yMultiLineOffset = 0 - ( textLines.size() - 1 - i ) * fontMetrics->lineSpacing() * format.lineHeight() / fontScale;
1702  break;
1703 
1704  }
1705 
1706  context.painter()->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
1707  if ( maskPainter )
1708  maskPainter->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
1709 
1710  Component subComponent;
1711  subComponent.block = block;
1712  subComponent.size = QSizeF( labelWidth, labelHeight );
1713  subComponent.offset = QPointF( 0.0, -ascentOffset );
1714  subComponent.rotation = -component.rotation * 180 / M_PI;
1715  subComponent.rotationOffset = 0.0;
1716  subComponent.extraWordSpacing = extraWordSpace * fontScale;
1717  subComponent.extraLetterSpacing = extraLetterSpace * fontScale;
1718 
1719  // draw the mask below the text (for preview)
1720  if ( format.mask().enabled() )
1721  {
1722  QgsTextRenderer::drawMask( context, subComponent, format, mode );
1723  }
1724 
1725  if ( drawType == QgsTextRenderer::Buffer )
1726  {
1727  QgsTextRenderer::drawBuffer( context, subComponent, format, mode );
1728  }
1729  else
1730  {
1731  // store text's drawing in QPicture for drop shadow call
1732  QPicture textPict;
1733  QPainter textp;
1734  textp.begin( &textPict );
1735  textp.setPen( Qt::NoPen );
1736 
1737  std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1738  if ( mode == Label )
1739  {
1740  // label size has already been calculated using any symbology reference scale factor -- we need
1741  // to temporarily remove the reference scale here or we'll be applying the scaling twice
1742  referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, -1.0 ) );
1743  }
1744  bool isNullSize = false;
1745  const QFont font = format.scaledFont( context, fontScale, &isNullSize );
1746  referenceScaleOverride.reset();
1747 
1748  if ( !isNullSize )
1749  {
1750  textp.scale( 1 / fontScale, 1 / fontScale );
1751 
1752  double xOffset = 0;
1753  for ( const QgsTextFragment &fragment : block )
1754  {
1755  // draw text, QPainterPath method
1756  QPainterPath path;
1757  path.setFillRule( Qt::WindingFill );
1758 
1759  QFont fragmentFont = font;
1760  fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
1761 
1762  if ( extraWordSpace || extraLetterSpace )
1763  applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
1764 
1765  path.addText( xOffset, 0, fragmentFont, fragment.text() );
1766 
1767  QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
1768  textColor.setAlphaF( fragment.characterFormat().textColor().isValid() ? textColor.alphaF() * format.opacity() : format.opacity() );
1769  textp.setBrush( textColor );
1770  textp.drawPath( path );
1771 
1772  xOffset += fragment.horizontalAdvance( fragmentFont, true );
1773  }
1774  textp.end();
1775  }
1776 
1777  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
1778  {
1779  subComponent.picture = textPict;
1780  subComponent.pictureBuffer = 0.0; // no pen width to deal with
1781  subComponent.origin = QPointF( 0.0, 0.0 );
1782 
1783  QgsTextRenderer::drawShadow( context, subComponent, format );
1784  }
1785 
1786  // paint the text
1787  if ( context.useAdvancedEffects() )
1788  {
1789  context.painter()->setCompositionMode( format.blendMode() );
1790  }
1791 
1792  // scale for any print output or image saving @ specific dpi
1793  context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
1794 
1795  switch ( context.textRenderFormat() )
1796  {
1797  case Qgis::TextRenderFormat::AlwaysOutlines:
1798  {
1799  // draw outlined text
1800  _fixQPictureDPI( context.painter() );
1801  context.painter()->drawPicture( 0, 0, textPict );
1802  break;
1803  }
1804 
1805  case Qgis::TextRenderFormat::AlwaysText:
1806  {
1807  double xOffset = 0;
1808  for ( const QgsTextFragment &fragment : block )
1809  {
1810  QFont fragmentFont = font;
1811  fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
1812 
1813  if ( extraWordSpace || extraLetterSpace )
1814  applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
1815 
1816  QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
1817  textColor.setAlphaF( fragment.characterFormat().textColor().isValid() ? textColor.alphaF() * format.opacity() : format.opacity() );
1818 
1819  context.painter()->setPen( textColor );
1820  context.painter()->setFont( fragmentFont );
1821  context.painter()->setRenderHint( QPainter::TextAntialiasing );
1822 
1823  context.painter()->scale( 1 / fontScale, 1 / fontScale );
1824  context.painter()->drawText( xOffset, 0, fragment.text() );
1825  context.painter()->scale( fontScale, fontScale );
1826 
1827  xOffset += fragment.horizontalAdvance( fragmentFont, true, fontScale );
1828  }
1829  }
1830  }
1831  }
1832  if ( maskPainter )
1833  maskPainter->restore();
1834  i++;
1835  }
1836 }
1837 
1838 void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart drawType, QgsTextRenderer::DrawMode mode, const QgsTextRenderer::Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, QgsTextRenderer::HAlignment hAlignment, QgsTextRenderer::VAlignment, double rotation )
1839 {
1840  QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
1841  const QStringList textLines = document.toPlainText();
1842 
1843  std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1844  if ( mode == Label )
1845  {
1846  // label size has already been calculated using any symbology reference scale factor -- we need
1847  // to temporarily remove the reference scale here or we'll be applying the scaling twice
1848  referenceScaleOverride.emplace( QgsScopedRenderContextReferenceScaleOverride( context, -1.0 ) );
1849  }
1850 
1851  bool isNullSize = false;
1852  const QFont font = format.scaledFont( context, fontScale, &isNullSize );
1853  if ( isNullSize )
1854  return;
1855 
1856  referenceScaleOverride.reset();
1857 
1858  double letterSpacing = font.letterSpacing() / fontScale;
1859 
1860  double labelWidth = fontMetrics->maxWidth() / fontScale; // label width represents the width of one line of a multi-line label
1861  double actualLabelWidest = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.lineHeight();
1862  double labelWidest = 0.0;
1863  switch ( mode )
1864  {
1865  case Label:
1866  case Point:
1867  labelWidest = actualLabelWidest;
1868  break;
1869 
1870  case Rect:
1871  labelWidest = component.size.width();
1872  break;
1873  }
1874 
1875  int maxLineLength = 0;
1876  for ( const QString &line : std::as_const( textLines ) )
1877  {
1878  maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1879  }
1880  double actualLabelHeight = fontMetrics->ascent() / fontScale + ( fontMetrics->ascent() / fontScale + letterSpacing ) * ( maxLineLength - 1 );
1881  double ascentOffset = fontMetrics->ascent() / fontScale;
1882 
1883  int i = 0;
1884 
1885  bool adjustForAlignment = hAlignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
1886 
1887  for ( const QgsTextBlock &block : document )
1888  {
1889  QgsScopedQPainterState painterState( context.painter() );
1890  context.setPainterFlagsUsingContext();
1891 
1892  context.painter()->translate( component.origin );
1893  if ( !qgsDoubleNear( rotation, 0.0 ) )
1894  context.painter()->rotate( rotation );
1895 
1896  // apply to the mask painter the same transformations
1897  if ( maskPainter )
1898  {
1899  maskPainter->save();
1900  maskPainter->translate( component.origin );
1901  if ( !qgsDoubleNear( rotation, 0.0 ) )
1902  maskPainter->rotate( rotation );
1903  }
1904 
1905  // figure x offset of multiple lines
1906  double xOffset = actualLabelWidest - labelWidth - ( i * labelWidth * format.lineHeight() );
1907  if ( adjustForAlignment )
1908  {
1909  double labelWidthDiff = 0;
1910  switch ( hAlignment )
1911  {
1912  case AlignCenter:
1913  labelWidthDiff = ( labelWidest - actualLabelWidest ) * 0.5;
1914  break;
1915 
1916  case AlignRight:
1917  labelWidthDiff = labelWidest - actualLabelWidest;
1918  break;
1919 
1920  case AlignLeft:
1921  case AlignJustify:
1922  break;
1923  }
1924 
1925  switch ( mode )
1926  {
1927  case Label:
1928  case Rect:
1929  xOffset += labelWidthDiff;
1930  break;
1931 
1932  case Point:
1933  break;
1934  }
1935  }
1936 
1937  double yOffset = 0.0;
1938  switch ( mode )
1939  {
1940  case Label:
1942  {
1943  if ( rotation >= -405 && rotation < -180 )
1944  {
1945  yOffset = ascentOffset;
1946  }
1947  else if ( rotation >= 0 && rotation < 45 )
1948  {
1949  xOffset -= actualLabelWidest;
1950  yOffset = -actualLabelHeight + ascentOffset + fontMetrics->descent() / fontScale;
1951  }
1952  }
1953  else
1954  {
1955  yOffset = -actualLabelHeight + ascentOffset;
1956  }
1957  break;
1958 
1959  case Point:
1960  yOffset = -actualLabelHeight + ascentOffset;
1961  break;
1962 
1963  case Rect:
1964  yOffset = ascentOffset;
1965  break;
1966  }
1967 
1968  context.painter()->translate( QPointF( xOffset, yOffset ) );
1969 
1970  double fragmentYOffset = 0;
1971  for ( const QgsTextFragment &fragment : block )
1972  {
1973  // apply some character replacement to draw symbols in vertical presentation
1974  const QString line = QgsStringUtils::substituteVerticalCharacters( fragment.text() );
1975 
1976  QFont fragmentFont( font );
1977  fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
1978 
1979  QFontMetricsF fragmentMetrics( fragmentFont );
1980 
1981  double labelHeight = fragmentMetrics.ascent() / fontScale + ( fragmentMetrics.ascent() / fontScale + letterSpacing ) * ( line.length() - 1 );
1982 
1983  Component subComponent;
1984  subComponent.block = QgsTextBlock( fragment );
1985  subComponent.size = QSizeF( labelWidth, labelHeight );
1986  subComponent.offset = QPointF( 0.0, fragmentYOffset );
1987  subComponent.rotation = -component.rotation * 180 / M_PI;
1988  subComponent.rotationOffset = 0.0;
1989 
1990  // draw the mask below the text (for preview)
1991  if ( format.mask().enabled() )
1992  {
1993  // WARNING: totally broken! (has been since mask was introduced)
1994 #if 0
1995  QgsTextRenderer::drawMask( context, subComponent, format );
1996 #endif
1997  }
1998 
1999  if ( drawType == QgsTextRenderer::Buffer )
2000  {
2001  fragmentYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format, mode );
2002  }
2003  else
2004  {
2005  // draw text, QPainterPath method
2006  QPainterPath path;
2007  path.setFillRule( Qt::WindingFill );
2008  const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
2009  double partYOffset = 0.0;
2010  for ( const auto &part : parts )
2011  {
2012  double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
2013  path.addText( partXOffset * fontScale, partYOffset * fontScale, fragmentFont, part );
2014  partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
2015  }
2016 
2017  // store text's drawing in QPicture for drop shadow call
2018  QPicture textPict;
2019  QPainter textp;
2020  textp.begin( &textPict );
2021  textp.setPen( Qt::NoPen );
2022  QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
2023  textColor.setAlphaF( fragment.characterFormat().textColor().isValid() ? textColor.alphaF() * format.opacity() : format.opacity() );
2024  textp.setBrush( textColor );
2025  textp.scale( 1 / fontScale, 1 / fontScale );
2026  textp.drawPath( path );
2027  // TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
2028  // e.g. some capitalization options, but not others
2029  //textp.setFont( tmpLyr.textFont );
2030  //textp.setPen( tmpLyr.textColor );
2031  //textp.drawText( 0, 0, component.text() );
2032  textp.end();
2033 
2034  if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
2035  {
2036  subComponent.picture = textPict;
2037  subComponent.pictureBuffer = 0.0; // no pen width to deal with
2038  subComponent.origin = QPointF( 0.0, fragmentYOffset );
2039  const double prevY = subComponent.offset.y();
2040  subComponent.offset = QPointF( 0, -labelHeight );
2041  subComponent.useOrigin = true;
2042  QgsTextRenderer::drawShadow( context, subComponent, format );
2043  subComponent.useOrigin = false;
2044  subComponent.offset = QPointF( 0, prevY );
2045  }
2046 
2047  // paint the text
2048  if ( context.useAdvancedEffects() )
2049  {
2050  context.painter()->setCompositionMode( format.blendMode() );
2051  }
2052 
2053  // scale for any print output or image saving @ specific dpi
2054  context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
2055 
2056  switch ( context.textRenderFormat() )
2057  {
2058  case Qgis::TextRenderFormat::AlwaysOutlines:
2059  {
2060  // draw outlined text
2061  _fixQPictureDPI( context.painter() );
2062  context.painter()->drawPicture( 0, fragmentYOffset, textPict );
2063  fragmentYOffset += partYOffset;
2064  break;
2065  }
2066 
2067  case Qgis::TextRenderFormat::AlwaysText:
2068  {
2069  context.painter()->setFont( fragmentFont );
2070  context.painter()->setPen( textColor );
2071  context.painter()->setRenderHint( QPainter::TextAntialiasing );
2072 
2073  double partYOffset = 0.0;
2074  for ( const QString &part : parts )
2075  {
2076  double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
2077  context.painter()->scale( 1 / fontScale, 1 / fontScale );
2078  context.painter()->drawText( partXOffset * fontScale, ( fragmentYOffset + partYOffset ) * fontScale, part );
2079  context.painter()->scale( fontScale, fontScale );
2080  partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
2081  }
2082  fragmentYOffset += partYOffset;
2083  }
2084  }
2085  }
2086  }
2087 
2088  if ( maskPainter )
2089  maskPainter->restore();
2090  i++;
2091  }
2092 }
2093 
2094 double QgsTextRenderer::calculateScaleFactorForFormat( const QgsRenderContext &context, const QgsTextFormat &format )
2095 {
2097  return 1.0;
2098 
2099  const double pixelSize = context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() );
2100 
2101  // THESE THRESHOLD MAY NEED TWEAKING!
2102 
2103  // for small font sizes we need to apply a growth scaling workaround designed to stablise the rendering of small font sizes
2104  if ( pixelSize < 50 )
2105  return FONT_WORKAROUND_SCALE;
2106  //... but for font sizes we might run into https://bugreports.qt.io/browse/QTBUG-98778, which messes up the spacing between words for large fonts!
2107  // so instead we scale down the painter so that we render the text at 200 pixel size and let painter scaling handle making it the correct size
2108  else if ( pixelSize > 200 )
2109  return 200 / pixelSize;
2110  else
2111  return 1.0;
2112 }
2113 
QgsTextRenderer::drawPart
static Q_DECL_DEPRECATED 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.
Definition: qgstextrenderer.cpp:161
QgsSymbolLayerUtils::blurImageInPlace
static void blurImageInPlace(QImage &image, QRect rect, int radius, bool alphaOnly)
Blurs an image in place, e.g. creating Qt-independent drop shadows.
Definition: qgssymbollayerutils.cpp:4018
qgstextdocument.h
QgsRenderContext::maskPainter
QPainter * maskPainter(int id=0)
Returns a mask QPainter for the render operation.
Definition: qgsrendercontext.h:144
QgsTextMaskSettings::enabled
bool enabled() const
Returns whether the mask is enabled.
Definition: qgstextmasksettings.cpp:64
QgsTextDocument::fromPlainText
static QgsTextDocument fromPlainText(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of plain text lines.
Definition: qgstextdocument.cpp:38
QgsScopedRenderContextReferenceScaleOverride
Scoped object for temporary override of the symbologyReferenceScale property of a QgsRenderContext.
Definition: qgsrendercontext.h:1375
qgspallabeling.h
QgsRenderContext::setPainterFlagsUsingContext
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
Definition: qgsrendercontext.cpp:169
QgsTextShadowSettings::offsetDistance
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
Definition: qgstextshadowsettings.cpp:103
QgsRenderContext::mapToPixel
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
Definition: qgsrendercontext.h:258
QgsTextBackgroundSettings::radiiUnit
QgsUnitTypes::RenderUnit radiiUnit() const
Returns the units used for the shape's radii.
Definition: qgstextbackgroundsettings.cpp:257
QgsTextRenderer::TextPart
TextPart
Components of text.
Definition: qgstextrenderer.h:49
QgsTextBufferSettings::sizeMapUnitScale
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
Definition: qgstextbuffersettings.cpp:102
QgsUnitTypes::RenderUnit
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:167
QgsRenderContext::expressionContext
QgsExpressionContext & expressionContext()
Gets the expression context.
Definition: qgsrendercontext.h:625
QgsTextBackgroundSettings::enabled
bool enabled() const
Returns whether the background is enabled.
Definition: qgstextbackgroundsettings.cpp:107
QgsTextDocument::size
int size() const
Returns the number of blocks in the document.
Definition: qgstextdocument.cpp:110
QgsEffectPainter
A class to manager painter saving and restoring required for effect drawing.
Definition: qgspainteffect.h:395
QgsTextFormat::shadow
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
Definition: qgstextformat.cpp:136
QgsTextRenderer::convertQtVAlignment
static VAlignment convertQtVAlignment(Qt::Alignment alignment)
Converts a Qt vertical alignment flag to a QgsTextRenderer::VAlignment value.
Definition: qgstextrenderer.cpp:61
QgsTextShadowSettings::blurRadiusMapUnitScale
QgsMapUnitScale blurRadiusMapUnitScale() const
Returns the map unit scale object for the shadow blur radius.
Definition: qgstextshadowsettings.cpp:163
qgstextfragment.h
QgsTextRenderer::Background
@ Background
Background shape.
Definition: qgstextrenderer.h:53
qgstextrenderer.h
QgsTextBufferSettings::joinStyle
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
Definition: qgstextbuffersettings.cpp:142
QgsTextFormat::orientation
TextOrientation orientation() const
Returns the orientation of the text.
Definition: qgstextformat.cpp:352
QgsTextShadowSettings::ShadowBuffer
@ ShadowBuffer
Draw shadow under buffer.
Definition: qgstextshadowsettings.h:48
QgsTextFormat::buffer
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
Definition: qgstextformat.cpp:112
QgsTextRenderer::AlignCenter
@ AlignCenter
Center align.
Definition: qgstextrenderer.h:61
QgsTextBackgroundSettings::offsetMapUnitScale
QgsMapUnitScale offsetMapUnitScale() const
Returns the map unit scale object for the shape offset.
Definition: qgstextbackgroundsettings.cpp:237
QgsTextRenderer::AlignRight
@ AlignRight
Right align.
Definition: qgstextrenderer.h:62
QgsRenderContext::setPainter
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
Definition: qgsrendercontext.h:512
QgsTextShadowSettings::offsetUnit
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units used for the shadow's offset.
Definition: qgstextshadowsettings.cpp:113
qgssymbollayerutils.h
QgsTextFormat::background
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
Definition: qgstextformat.cpp:124
QgsTextRenderer::fontMetrics
static QFontMetricsF fontMetrics(QgsRenderContext &context, const QgsTextFormat &format, double scaleFactor=1.0)
Returns the font metrics for the given text format, when rendered in the specified render context.
Definition: qgstextrenderer.cpp:280
QgsTextBlock::size
int size() const
Returns the number of fragments in the block.
Definition: qgstextblock.cpp:54
QgsRenderContext::flags
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
Definition: qgsrendercontext.cpp:224
QgsTextFormat::sizeUnit
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
Definition: qgstextformat.cpp:264
qgsmarkersymbollayer.h
QgsTextBackgroundSettings::fillColor
QColor fillColor() const
Returns the color used for filing the background shape.
Definition: qgstextbackgroundsettings.cpp:297
QgsTextBackgroundSettings
Container for settings relating to a text background object.
Definition: qgstextbackgroundsettings.h:46
QgsTextFragment::text
QString text() const
Returns the text content of the fragment.
Definition: qgstextfragment.cpp:33
QgsUnitTypes::RenderPercentage
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
QgsTextShadowSettings::scale
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
Definition: qgstextshadowsettings.cpp:193
QgsTextBackgroundSettings::fillSymbol
QgsFillSymbol * fillSymbol() const
Returns the fill symbol to be rendered in the background.
Definition: qgstextbackgroundsettings.cpp:147
QgsTextBufferSettings::opacity
double opacity() const
Returns the buffer opacity.
Definition: qgstextbuffersettings.cpp:132
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:59
QgsTextCharacterFormat::textColor
QColor textColor() const
Returns the character's text color, or an invalid color if no color override is set and the default f...
Definition: qgstextcharacterformat.cpp:35
QgsTextRenderer::VAlignment
VAlignment
Vertical alignment.
Definition: qgstextrenderer.h:78
QgsTextBackgroundSettings::offset
QPointF offset() const
Returns the offset used for drawing the background shape.
Definition: qgstextbackgroundsettings.cpp:217
QgsTextBackgroundSettings::strokeWidthMapUnitScale
QgsMapUnitScale strokeWidthMapUnitScale() const
Returns the map unit scale object for the shape stroke width.
Definition: qgstextbackgroundsettings.cpp:355
QgsTextRenderer::Shadow
@ Shadow
Drop shadow.
Definition: qgstextrenderer.h:54
QgsTextRenderer::wrappedText
static QStringList wrappedText(const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format)
Wraps a text string to multiple lines, such that each individual line will fit within the specified w...
Definition: qgstextrenderer.cpp:701
QgsTextBackgroundSettings::opacity
double opacity() const
Returns the background shape's opacity.
Definition: qgstextbackgroundsettings.cpp:277
QgsTextBackgroundSettings::RotationOffset
@ RotationOffset
Shape rotation is offset from text rotation.
Definition: qgstextbackgroundsettings.h:79
FALLTHROUGH
#define FALLTHROUGH
Definition: qgis.h:2847
QgsTextBackgroundSettings::strokeWidthUnit
QgsUnitTypes::RenderUnit strokeWidthUnit() const
Returns the units used for the shape's stroke width.
Definition: qgstextbackgroundsettings.cpp:341
qgspainteffect.h
QgsRenderContext::scaleFactor
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
Definition: qgsrendercontext.h:266
qt_defaultDpiY
Q_GUI_EXPORT int qt_defaultDpiY()
QgsTextRenderer::Text
@ Text
Text component.
Definition: qgstextrenderer.h:51
QgsTextShadowSettings::blurRadius
double blurRadius() const
Returns the blur radius for the shadow.
Definition: qgstextshadowsettings.cpp:143
QgsRenderContext::textRenderFormat
Qgis::TextRenderFormat textRenderFormat() const
Returns the text render format, which dictates how text is rendered (e.g.
Definition: qgsrendercontext.h:729
QgsTextBackgroundSettings::sizeType
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
Definition: qgstextbackgroundsettings.cpp:157
QgsTextBackgroundSettings::blendMode
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the background shape.
Definition: qgstextbackgroundsettings.cpp:287
QgsExpressionContext::fields
QgsFields fields() const
Convenience function for retrieving the fields for the context, if set.
Definition: qgsexpressioncontext.cpp:595
QgsTextShadowSettings::enabled
bool enabled() const
Returns whether the shadow is enabled.
Definition: qgstextshadowsettings.cpp:73
QgsTextMaskSettings::size
double size() const
Returns the size of the buffer.
Definition: qgstextmasksettings.cpp:85
QgsTextRenderer::textHeight
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
Definition: qgstextrenderer.cpp:620
QgsMarkerSymbol::clone
QgsMarkerSymbol * clone() const override
Returns a deep copy of this symbol.
Definition: qgsmarkersymbol.cpp:523
QgsTextBackgroundSettings::ShapeSquare
@ ShapeSquare
Square - buffered sizes only.
Definition: qgstextbackgroundsettings.h:56
QgsRenderContext::currentMaskId
int currentMaskId() const
Returns the current mask id, which can be used with maskPainter()
Definition: qgsrendercontext.h:789
QgsTextBlock::toPlainText
QString toPlainText() const
Converts the block to plain text.
Definition: qgstextblock.cpp:24
QgsTextFormat::VerticalOrientation
@ VerticalOrientation
Horizontally oriented text.
Definition: qgstextformat.h:48
QgsTextFormat::color
QColor color() const
Returns the color that text will be rendered in.
Definition: qgstextformat.cpp:297
QgsTextRenderer::textWidth
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.
Definition: qgstextrenderer.cpp:545
QgsTextRenderer::Rect
@ Rect
Text within rectangle draw mode.
Definition: qgstextrenderer.h:43
QgsTextRenderer::DrawMode
DrawMode
Draw mode to calculate width and height.
Definition: qgstextrenderer.h:41
QgsSymbolRenderContext
Definition: qgssymbolrendercontext.h:35
QgsTextShadowSettings::blurAlphaOnly
bool blurAlphaOnly() const
Returns whether only the alpha channel for the shadow will be blurred.
Definition: qgstextshadowsettings.cpp:173
QgsTextBackgroundSettings::sizeMapUnitScale
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
Definition: qgstextbackgroundsettings.cpp:187
QgsTextBackgroundSettings::rotationType
RotationType rotationType() const
Returns the method used for rotating the background shape.
Definition: qgstextbackgroundsettings.cpp:197
qt_defaultDpiX
Q_GUI_EXPORT int qt_defaultDpiX()
QgsTextFormat
Container for all settings relating to text rendering.
Definition: qgstextformat.h:40
QgsTextBackgroundSettings::size
QSizeF size() const
Returns the size of the background shape.
Definition: qgstextbackgroundsettings.cpp:167
QgsTextFormat::sizeMapUnitScale
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
Definition: qgstextformat.cpp:275
QgsTextShadowSettings::blendMode
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the drop shadow.
Definition: qgstextshadowsettings.cpp:213
QgsSymbolLayer
Definition: qgssymbollayer.h:54
QgsTextRenderer::Point
@ Point
Text at point of origin draw mode.
Definition: qgstextrenderer.h:44
QgsTextBackgroundSettings::ShapeRectangle
@ ShapeRectangle
Rectangle.
Definition: qgstextbackgroundsettings.h:55
QgsRenderContext::useAdvancedEffects
bool useAdvancedEffects() const
Returns true if advanced effects such as blend modes such be used.
Definition: qgsrendercontext.cpp:300
QgsUnitTypes::encodeUnit
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
Definition: qgsunittypes.cpp:122
QgsTextRenderer::AlignTop
@ AlignTop
Align to top.
Definition: qgstextrenderer.h:80
finder
const char * finder(const char *name)
Definition: qgscoordinatetransform.cpp:954
QgsTextFormat::updateDataDefinedProperties
void updateDataDefinedProperties(QgsRenderContext &context)
Updates the format by evaluating current values of data defined properties.
Definition: qgstextformat.cpp:861
QgsTextDocument::at
const QgsTextBlock & at(int index) const
Returns the block at the specified index.
Definition: qgstextdocument.cpp:100
QgsStringUtils::substituteVerticalCharacters
static QString substituteVerticalCharacters(QString string)
Returns a string with characters having vertical representation form substituted.
Definition: qgsstringutils.cpp:688
QgsPropertyCollection::hasActiveProperties
bool hasActiveProperties() const override
Returns true if the collection has any active properties, or false if all properties within the colle...
Definition: qgspropertycollection.cpp:304
QgsTextDocument::toPlainText
QStringList toPlainText() const
Returns a list of plain text lines of text representing the document.
Definition: qgstextdocument.cpp:115
QgsTextFragment
Stores a fragment of text along with formatting overrides to be used when rendering the fragment.
Definition: qgstextfragment.h:35
QgsTextMaskSettings
Container for settings relating to a selective masking around a text. A selective masking only makes ...
Definition: qgstextmasksettings.h:41
QgsMarkerSymbol
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgsmarkersymbol.h:30
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2265
QgsTextBackgroundSettings::ShapeCircle
@ ShapeCircle
Circle.
Definition: qgstextbackgroundsettings.h:58
QgsTextShadowSettings::ShadowLowest
@ ShadowLowest
Draw shadow below all text components.
Definition: qgstextshadowsettings.h:46
Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
QgsTextBackgroundSettings::strokeWidth
double strokeWidth() const
Returns the width of the shape's stroke (stroke).
Definition: qgstextbackgroundsettings.cpp:325
QgsPaintEffect::clone
virtual QgsPaintEffect * clone() const =0
Duplicates an effect by creating a deep copy of the effect.
QgsSvgMarkerSymbolLayer
Definition: qgsmarkersymbollayer.h:496
QgsTextShadowSettings::offsetMapUnitScale
QgsMapUnitScale offsetMapUnitScale() const
Returns the map unit scale object for the shadow offset distance.
Definition: qgstextshadowsettings.cpp:123
QgsTextFormat::capitalization
Qgis::Capitalization capitalization() const
Returns the text capitalization style.
Definition: qgstextformat.cpp:363
QgsTextRenderer::AlignJustify
@ AlignJustify
Justify align.
Definition: qgstextrenderer.h:63
QgsTextFormat::allowHtmlFormatting
bool allowHtmlFormatting() const
Returns true if text should be treated as a HTML document and HTML tags should be used for formatting...
Definition: qgstextformat.cpp:382
QgsTextShadowSettings::color
QColor color() const
Returns the color of the drop shadow.
Definition: qgstextshadowsettings.cpp:203
QgsTextRenderer::drawText
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws text within a rectangle using the specified settings.
Definition: qgstextrenderer.cpp:81
QgsRenderContext::setMapToPixel
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
Definition: qgsrendercontext.h:412
QgsTextFormat::lineHeight
double lineHeight() const
Returns the line height for text.
Definition: qgstextformat.cpp:341
QgsTextRenderer::AlignLeft
@ AlignLeft
Left align.
Definition: qgstextrenderer.h:60
QgsTextBackgroundSettings::SizeBuffer
@ SizeBuffer
Shape size is determined by adding a buffer margin around text.
Definition: qgstextbackgroundsettings.h:68
QgsTextBackgroundSettings::rotation
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
Definition: qgstextbackgroundsettings.cpp:207
QgsSvgMarkerSymbolLayer::renderPoint
void renderPoint(QPointF point, QgsSymbolRenderContext &context) override
Renders a marker at the specified point.
Definition: qgsmarkersymbollayer.cpp:2177
QgsTextMaskSettings::opacity
double opacity() const
Returns the mask's opacity.
Definition: qgstextmasksettings.cpp:125
QgsTextFormat::opacity
double opacity() const
Returns the text's opacity.
Definition: qgstextformat.cpp:308
QgsTextShadowSettings::setShadowPlacement
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
Definition: qgstextshadowsettings.cpp:88
QgsTextShadowSettings::offsetGlobal
bool offsetGlobal() const
Returns true if the global shadow offset will be used.
Definition: qgstextshadowsettings.cpp:133
QgsTextRenderer::textRequiresWrapping
static bool textRequiresWrapping(const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format)
Returns true if the specified text requires line wrapping in order to fit within the specified width ...
Definition: qgstextrenderer.cpp:691
QgsTextMaskSettings::sizeMapUnitScale
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
Definition: qgstextmasksettings.cpp:105
QgsMapUnitScale
Struct for storing maximum and minimum scales for measurements in map units.
Definition: qgsmapunitscale.h:36
QgsTextDocument::fromHtml
static QgsTextDocument fromHtml(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of HTML formatted lines.
Definition: qgstextdocument.cpp:47
QgsTextBackgroundSettings::radiiMapUnitScale
QgsMapUnitScale radiiMapUnitScale() const
Returns the map unit scale object for the shape radii.
Definition: qgstextbackgroundsettings.cpp:267
QgsScopedQPainterState
Scoped object for saving and restoring a QPainter object's state.
Definition: qgsrendercontext.h:1336
QgsTextBufferSettings
Container for settings relating to a text buffer.
Definition: qgstextbuffersettings.h:42
QgsTextFormat::scaledFont
QFont scaledFont(const QgsRenderContext &context, double scaleFactor=1.0, bool *isZeroSize=nullptr) const
Returns a font with the size scaled to match the format's size settings (including units and map unit...
Definition: qgstextformat.cpp:165
QgsTextBufferSettings::sizeUnit
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the buffer size.
Definition: qgstextbuffersettings.cpp:92
QgsTextBackgroundSettings::type
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
Definition: qgstextbackgroundsettings.cpp:117
QgsTextBackgroundSettings::svgFile
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
Definition: qgstextbackgroundsettings.cpp:127
QgsTextShadowSettings::ShadowShape
@ ShadowShape
Draw shadow under background shape.
Definition: qgstextshadowsettings.h:49
QgsTextDocument::applyCapitalization
void applyCapitalization(Qgis::Capitalization capitalization)
Applies a capitalization style to the document's text.
Definition: qgstextdocument.cpp:190
QgsTextBufferSettings::size
double size() const
Returns the size of the buffer.
Definition: qgstextbuffersettings.cpp:82
QgsUnitTypes::RenderPixels
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
QgsTextShadowSettings::offsetAngle
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
Definition: qgstextshadowsettings.cpp:93
QgsTextBackgroundSettings::ShapeEllipse
@ ShapeEllipse
Ellipse.
Definition: qgstextbackgroundsettings.h:57
QgsTextBlock
Represents a block of text consisting of one or more QgsTextFragment objects.
Definition: qgstextblock.h:35
qgsvectorlayer.h
QgsTextFormat::mask
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
Definition: qgstextformat.cpp:148
QgsRenderContext::convertToPainterUnits
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Definition: qgsrendercontext.cpp:367
QgsTextFormat::size
double size() const
Returns the size for rendered text.
Definition: qgstextformat.cpp:286
QgsTextRenderer::sizeToPixel
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...
Definition: qgstextrenderer.cpp:76
QgsTextShadowSettings
Container for settings relating to a text shadow.
Definition: qgstextshadowsettings.h:37
QgsTextBufferSettings::enabled
bool enabled() const
Returns whether the buffer is enabled.
Definition: qgstextbuffersettings.cpp:72
QgsTextCharacterFormat::updateFontForFormat
void updateFontForFormat(QFont &font, double scaleFactor=1.0) const
Updates the specified font in place, applying character formatting options which are applicable on a ...
Definition: qgstextcharacterformat.cpp:75
QgsTextFragment::characterFormat
const QgsTextCharacterFormat & characterFormat() const
Returns the character formatting for the fragment.
Definition: qgstextfragment.h:68
QgsTextFormat::dataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the format's property collection, used for data defined overrides.
Definition: qgstextformat.cpp:834
QgsTextFormat::RotationBasedOrientation
@ RotationBasedOrientation
Horizontally or vertically oriented text based on rotation (only available for map labeling)
Definition: qgstextformat.h:49
QgsTextBackgroundSettings::ShapeSVG
@ ShapeSVG
SVG file.
Definition: qgstextbackgroundsettings.h:59
c
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
Definition: porting_processing.dox:1
QgsTextBackgroundSettings::SizeFixed
@ SizeFixed
Fixed size.
Definition: qgstextbackgroundsettings.h:69
QgsSymbolLayerList
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:27
qgsmarkersymbol.h
QgsTextBackgroundSettings::paintEffect
const QgsPaintEffect * paintEffect() const
Returns the current paint effect for the background shape.
Definition: qgstextbackgroundsettings.cpp:383
QgsTextDocument
Represents a document consisting of one or more QgsTextBlock objects.
Definition: qgstextdocument.h:38
QgsExpressionContext::feature
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
Definition: qgsexpressioncontext.cpp:543
QgsTextShadowSettings::ShadowText
@ ShadowText
Draw shadow under text.
Definition: qgstextshadowsettings.h:47
QgsRenderContext::isGuiPreview
bool isGuiPreview() const
Returns the Gui preview mode.
Definition: qgsrendercontext.h:812
QgsFillSymbol::clone
QgsFillSymbol * clone() const override
Returns a deep copy of this symbol.
Definition: qgsfillsymbol.cpp:144
QgsUnitTypes::RenderUnknownUnit
@ RenderUnknownUnit
Mixed or unknown units.
Definition: qgsunittypes.h:175
QgsTextRenderer::Label
@ Label
Label-specific draw mode.
Definition: qgstextrenderer.h:45
QgsTextMaskSettings::paintEffect
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the mask.
Definition: qgstextmasksettings.cpp:135
QgsTextMaskSettings::sizeUnit
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the buffer size.
Definition: qgstextmasksettings.cpp:95
QgsTextFragment::horizontalAdvance
double horizontalAdvance(const QFont &font, bool fontHasBeenUpdatedForFragment=false, double scaleFactor=1.0) const
Returns the horizontal advance associated with this fragment, when rendered using the specified base ...
Definition: qgstextfragment.cpp:48
QgsTextRenderer::AlignVCenter
@ AlignVCenter
Center align.
Definition: qgstextrenderer.h:81
QgsTextBackgroundSettings::sizeUnit
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
Definition: qgstextbackgroundsettings.cpp:177
qgspainterswapper.h
QgsTextShadowSettings::opacity
double opacity() const
Returns the shadow's opacity.
Definition: qgstextshadowsettings.cpp:183
QgsFeature
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:55
QgsSymbolLayerUtils::estimateMaxSymbolBleed
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
Definition: qgssymbollayerutils.cpp:934
qgstextformat.h
QgsTextRenderer::Buffer
@ Buffer
Buffer component.
Definition: qgstextrenderer.h:52
QgsTextFormat::blendMode
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the text.
Definition: qgstextformat.cpp:330
QgsRenderContext::painter
QPainter * painter()
Returns the destination QPainter for the render operation.
Definition: qgsrendercontext.h:112
QgsPaintEffect::enabled
bool enabled() const
Returns whether the effect is enabled.
Definition: qgspainteffect.h:197
QgsTextBackgroundSettings::ShapeMarkerSymbol
@ ShapeMarkerSymbol
Marker symbol.
Definition: qgstextbackgroundsettings.h:60
QgsTextBufferSettings::color
QColor color() const
Returns the color of the buffer.
Definition: qgstextbuffersettings.cpp:112
QgsTextBufferSettings::fillBufferInterior
bool fillBufferInterior() const
Returns whether the interior of the buffer will be filled in.
Definition: qgstextbuffersettings.cpp:122
QgsTextBackgroundSettings::RotationFixed
@ RotationFixed
Shape rotation is a fixed angle.
Definition: qgstextbackgroundsettings.h:80
QgsTextRenderer::convertQtHAlignment
static HAlignment convertQtHAlignment(Qt::Alignment alignment)
Converts a Qt horizontal alignment flag to a QgsTextRenderer::HAlignment value.
Definition: qgstextrenderer.cpp:46
QgsTextBackgroundSettings::markerSymbol
QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol to be rendered in the background.
Definition: qgstextbackgroundsettings.cpp:137
QgsTextShadowSettings::shadowPlacement
QgsTextShadowSettings::ShadowPlacement shadowPlacement() const
Returns the placement for the drop shadow.
Definition: qgstextshadowsettings.cpp:83
QgsTextFormat::HorizontalOrientation
@ HorizontalOrientation
Vertically oriented text.
Definition: qgstextformat.h:47
QgsTextBufferSettings::blendMode
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the buffer.
Definition: qgstextbuffersettings.cpp:152
MathUtils::angle
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
qgsfillsymbol.h
QgsTextShadowSettings::blurRadiusUnit
QgsUnitTypes::RenderUnit blurRadiusUnit() const
Returns the units used for the shadow's blur radius.
Definition: qgstextshadowsettings.cpp:153
QgsTextRenderer::FONT_WORKAROUND_SCALE
static constexpr double FONT_WORKAROUND_SCALE
Scale factor for upscaling font sizes and downscaling destination painter devices.
Definition: qgstextrenderer.h:260
QgsUnitTypes::RenderMapUnits
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
QgsPainterSwapper
A class to manage painter saving and restoring required for drawing on a different painter (mask pain...
Definition: qgspainterswapper.h:34
QgsTextFormat::TextOrientation
TextOrientation
Text orientation.
Definition: qgstextformat.h:45
QgsTextRenderer::AlignBottom
@ AlignBottom
Align to bottom.
Definition: qgstextrenderer.h:82
QgsTextBackgroundSettings::strokeColor
QColor strokeColor() const
Returns the color used for outlining the background shape.
Definition: qgstextbackgroundsettings.cpp:311
QgsRenderContext::setScaleFactor
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
Definition: qgsrendercontext.h:472
QgsSvgMarkerSymbolLayer::create
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates the symbol.
Definition: qgsmarkersymbollayer.cpp:1962
Qgis::TextRendererFlag::WrapLines
@ WrapLines
Automatically wrap long lines of text.
QgsTextBackgroundSettings::offsetUnit
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units used for the shape's offset.
Definition: qgstextbackgroundsettings.cpp:227
QgsTextMaskSettings::joinStyle
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
Definition: qgstextmasksettings.cpp:115
QgsTextBackgroundSettings::radii
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
Definition: qgstextbackgroundsettings.cpp:247
QgsTextRenderer::HAlignment
HAlignment
Horizontal alignment.
Definition: qgstextrenderer.h:58
QgsTextBufferSettings::paintEffect
const QgsPaintEffect * paintEffect() const
Returns the current paint effect for the buffer.
Definition: qgstextbuffersettings.cpp:162
QgsPalLabeling::splitToGraphemes
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
Definition: qgspallabeling.cpp:3893