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