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