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