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